Skip to content

feat(ci): two-tier first-pass bot with controlled repo-file escalation - #66

Merged
Carl723000 merged 1 commit into
ClaudeCodeUsage:mainfrom
Carl723000:ci/bot-escalation
Jul 4, 2026
Merged

Carl723000 merged 1 commit into
ClaudeCodeUsage:mainfrom
Carl723000:ci/bot-escalation

Conversation

@Carl723000

Copy link
Copy Markdown
Collaborator

Builds on #64.

What

When the cheap first-pass model (CCU_BOT_MODEL, default deepseek-v4-flash) reports it can't answer from the project docs alone, escalate:

  1. Tier 2 — a stronger model (new CCU_BOT_MODEL_PRO, default deepseek-v4-pro) with thinking on, and it may name a few repo source files to consult.
  2. The script reads those files — repo-relative text files only, no .., size-capped (16KB/file, 60KB total), must resolve inside the repo root. The model never runs a tool; it only names paths the script validates and reads. Same injection-safe property as feat(ci): controlled single-call @ccu-bot first-pass (no autonomous agent) #64, plus a controlled retrieval step.
  3. Tier 3 — if it still can't answer, the reply asks for the specific missing info/logs and suggests the issue template.

Every reply is now standardized into a skimmable shape: TL;DR/结论 → analysis → suggested next steps.

Safety

  • No autonomous agent, no agent tools — still one (or two) plain model calls + one comment.
  • File reads are script-driven and path-guarded (verified: ../../etc/passwd, /etc/hosts, NUL-injection all rejected).
  • thinking param is best-effort: the script retries once without it if a third-party endpoint rejects it, so it stays portable.
  • Still inert until AUTOMATION_ENABLED='true' + ANTHROPIC_API_KEY.

New repo variable

  • CCU_BOT_MODEL_PRO (optional, Settings → Variables) — escalation model, defaults to deepseek-v4-pro.

Builds on ClaudeCodeUsage#64. When the cheap first-pass model (CCU_BOT_MODEL, default
deepseek-v4-flash) reports it can't answer from the docs alone, escalate to a
stronger model (new CCU_BOT_MODEL_PRO, default deepseek-v4-pro) with thinking
on, and let it NAME a few repo source files. The SCRIPT reads them — repo-
relative text files only, no "..", size-capped, must resolve inside the repo
root — so there is still no agent tool and no way for a malicious issue to make
it read or execute anything arbitrary (same injection-safe property as ClaudeCodeUsage#64,
just with a controlled retrieval step). If it still can't answer, the reply asks
for the specific missing info/logs and suggests the issue template.

Also standardizes every reply into a skimmable shape: TL;DR/结论 first, then
analysis, then suggested next steps. The thinking param is best-effort — the
script retries once without it if the endpoint rejects it — so this stays
portable across third-party Anthropic-format endpoints.

Still INERT until AUTOMATION_ENABLED='true' + ANTHROPIC_API_KEY, unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
@Carl723000
Carl723000 requested a review from jack21 as a code owner July 4, 2026 17:20
@github-actions github-actions Bot added ci CI / workflow change feature Minor bump — new feature labels Jul 4, 2026
@Carl723000
Carl723000 merged commit a55968a into ClaudeCodeUsage:main Jul 4, 2026
2 of 3 checks passed
Carl723000 added a commit that referenced this pull request Jul 4, 2026
… as a no-op (#67)

- The PR first-pass now SKIPS pull requests authored by OWNER / MEMBER /
  COLLABORATOR — a maintainer's own finished PR doesn't need an auto first-pass
  (that review is for external contributors). #66 showed it firing on our own
  PR and failing on the big self-referential diff.
- An empty model reply is now a soft no-op (exit 0) instead of a hard failure,
  so a benign blank response doesn't leave a red check on the PR/issue.

Co-authored-by: Claude <noreply@anthropic.com>
Carl723000 added a commit that referenced this pull request Jul 6, 2026
…eatmap & more (#68)

* chore: V2.2 changelog scaffold, v2.0.3 + v2.1.0 dates, org metadata

- CHANGELOG: add [2.2.0] — Unreleased; date [2.1.0] — 2026-06-26; add the
  missing [2.0.3] maintenance section (macOS Keychain, node:test, issue
  templates, CI hardening, release workflow).
- package.json: repository / bugs / homepage now point at the org
  (ClaudeCodeUsage/ClaudeCodeUsage), not jack21.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(settings): tokenDecimalPlaces — decimals for compact token display

New setting (default 1, 0–2) controlling only compact token rendering (1.2M /
345.6K), separate from cost decimalPlaces; full integer counts are unaffected.
Wired through I18n.formatTokensCompact and the formatNumber compact path, the
settings catalog + SETTINGS_I18N (5 locales), ExtensionConfig and config apply.
7 new node:test cases (0/1/2 places, K/M/B, integer pass-through, clamp).

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(quota): clean status-bar quota text; opt-in reset + 5h-only

Status bar stays clean — default '5h 6% · wk 1%' (space, middot), not the dense
'5h:6%:4.8h | wk:1%:1.6d'. Two opt-in settings (both default off):
- showResetInStatusBar → '5h 6% ↻4.8h | wk 1% ↻1.6d' (compact countdown, bar sep)
- quotaFiveHourOnly → show only the 5h window
Full reset times always remain in the tooltip. Extracted a pure, dependency-free
src/quotaFormat.ts (formatQuotaStatusText / compactReset / worstShownUtilisation)
so it's unit-tested without vscode; statusBar.updateQuota now uses it. Catalog +
SETTINGS_I18N (5 locales) + config wiring. 8 new node:test cases.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(settings): dashboardAutoRefresh (positive) replacing pauseDashboardRefresh

Double-negative 'Pause dashboard auto-refresh' becomes the positive
'Dashboard auto-refresh' (key dashboardAutoRefresh, default true). One-shot
SettingsStore.migrateDashboardAutoRefresh inverts any old pauseDashboardRefresh
value (globalState or settings.json) — true→false, false/undefined→true — behind
its own migration flag so it runs even post-v1-migration. All consumers
(extension refresh gating, webview body auto-off class + toggle handler) and
SETTINGS_I18N (5 locales) updated. Compile clean; 26/26 node:test green.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(v2.2): share-card + heatmap pure-logic cores (tested)

Tested, dependency-free foundations for the two big V2.2 features; the webview
rendering + PNG export + heatmap cells will build on these in follow-up PRs.

- src/shareCard.ts (Usage Share Card / 用量卡片): totalTokens, cacheSharePct,
  modelFamily (no version exposed), selectShareBadge (deterministic, local rules:
  Context Marathoner / Cache Saver / Token Sprinter / Workflow Pilot / Steady
  Builder), buildShareCardData (privacy redaction — project name OFF by default,
  no room in the data shape for paths/ids/prompts), DEFAULT_SECTIONS,
  shareCardFilename.
- src/heatmap.ts: daysInMonth, intensityBucket (0..4), buildMonthHeatmap (zero-
  fills every day, tokens/cost/sessions metric switch).
- 11 new node:test cases (Phase 5.8 + 6.4). 37/37 green.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: V2.2 roadmap + V2.3 achievements spec

- docs/V2.2.md: what shipped in 2.2 (status-legended), the Usage Share Card
  design + enforced privacy model, the token heatmap design, the settings
  changes, session/quota status, the contributor/PR credit policy, and the
  planning items (advice 'new session/clear' nudge, Optimizer opt-in file
  reading, conversation export, subscription sharing, Codex adapter, GitHub
  profile heatmap).
- docs/V2.3-achievements.md: SPEC ONLY (no V2.2 code) — positioning, privacy,
  the full achievement catalogue (token/session/prompt/workflow/model/plugin/
  hidden), levels, anti-gaming, community roadmap, the icon-prompt template,
  milestones, and the GitHub-profile sharing plan.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(changelog): fill [2.2.0] with shipped items

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: consolidate V2.2 specs into one local master

Fold the public roadmap (docs/V2.2.md) and the two local working specs
(V2.2-FEATURES-SPEC.md, V2.2-AUTOMATION-SPEC.md) into a single gitignored
master, docs/V2.2-SPEC.md (+ a zh-CN mirror for review), so the whole V2.2
plan can be handed to one execution pass. Drop the CHANGELOG pointer to the
now-removed public roadmap; the Added/Changed entries already record what
actually shipped in 2.2.0.

* fix(stats): drop API-error retry re-logs from the Messages count

When a request errors, Claude Code retries and re-logs the same user prompt,
so an identical prompt can appear several times seconds apart around a
system:api_error record — inflating "Messages". Dedupe identical prompts that
recur within a short window (retry re-logs); genuine re-sends minutes/hours
later still count. Logic isolated in a pure, unit-tested module.

Verified on real sessions: removes only true retry re-logs, keeps repeats.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(dashboard): cache-hit-rate column in the breakdown tables

Adds a cache hit rate (cacheRead / input-side tokens) column to the All-time
monthly table, the This-month daily table, and both expanded drill-downs
(month→days, day→hours), so cache efficiency is readable per row. Reuses the
existing cacheHitRate/formatPercent helpers server-side and a small cacheHitPct
helper client-side; detail-row colspans bumped 8→9.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(dashboard): timezone-correct date bucketing + table scroll behavior

Bucketing: the month/day breakdowns mixed a UTC day key with a local month
boundary, so a record just after local midnight on the 1st was filed under the
previous month's last day (visible as a stray "30th" inside This-Month). Day
and month keys now both derive from the configured timezone (empty = system)
via a pure, unit-tested dateKeys module.

Table scroll (2b): number cells no longer wrap — compact k/M numbers fit the
panel with no horizontal scrollbar; full integer numbers overflow so only the
table scrolls, while the daily bar chart keeps its own independent scroll.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(dashboard): click a month's Token composition to drill into daily

The All-time Token-composition bars are now clickable (each is a month): a
click expands that month's detail — which now includes a per-day composition
chart next to the daily chart and table — so the input/output/cache split is
readable day by day. Reuses the existing month-detail round-trip
(toggleMonthlyDetail → getDailyData), with a small client-side composition
renderer mirroring the server one.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(i18n): finish wiring pt-BR (settings dropdown + READMEs + CLAUDE.md)

pt-BR shipped in 2.1.1 (#48) with the strings, package.json enum and
SupportedLanguage type, but was missing from the dashboard language dropdown
(settings.ts enumValues) and every README's UI-language list — so users
couldn't actually pick it. Added it in all those places and noted it in
CLAUDE.md (which also flags that de-DE strings exist but are exposed nowhere).

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(heatmap): GitHub-style contribution-graph SVG core (Claude orange)

Pure, unit-tested heatmapSvg module: lays a calendar year of daily usage
(Jan 1 → today, no future padding, GitHub current-year style) into a
contribution grid and renders a self-contained SVG — Claude-orange intensity
ramp, title row (no month-label overlap), month + weekday labels, Less/More
legend, a bottom-left source watermark, per-day tooltips — for a GitHub profile
README. Not wired to a command yet; a real-data preview was generated.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(heatmap): GitHub-style contribution-graph SVG core (Claude orange)

Pure, unit-tested heatmapSvg module for a GitHub profile README: trailing ~year
ending today (GitHub's default view, no future padding), Claude-orange intensity
ramp, a top-left summary ("5.3B tokens in Claude Code · 2026"), GitHub-style
per-day tooltips ("1.2M tokens on June 18th"), month + weekday labels, Less/More
legend, and a bottom-left source watermark. Not wired to a command yet.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(heatmap): Export Token Heatmap command (GitHub-style SVG)

Adds "Claude Code Usage: Export Token Heatmap (GitHub style SVG)" — builds a
per-day usage map (timezone-aware, via getDailyUsageMap) and writes the
contribution-graph SVG to a chosen file, offering to copy the Markdown embed
snippet for a GitHub profile README. Command-palette accessible.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(heatmap): larger fonts (title 15 / months 12 / labels 11), 12px cells

* feat(dashboard): opt-in token heatmap on the All tab (showHeatmap, default off)

Adds a showHeatmap setting (default off). When on, the All tab shows a
GitHub-style yearly token heatmap at the top, built from allRecords via
getDailyUsageMap + renderHeatmapSvg. Inline SVG, so per-day hover tooltips work
in the dashboard (unlike GitHub's image-sanitised READMEs). Mainly a shareable
re-view of existing data, hence opt-in.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(stats): exclude the compaction "session continued" summary from Messages

Verifying the Messages count on real logs surfaced ~5 counted "prompts" that
were actually Claude Code's auto-injected "This session is being continued from
a previous conversation…" compaction summary (a user-role message the user never
typed). isSyntheticUserText now drops it, alongside interrupted-request and
slash-command echoes. (Tool-result user messages were already excluded.)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: timezone is a validated dropdown, not free text (#51)

The reporter suggested a dropdown of valid zones — the right fix, since it makes
an invalid value impossible to enter. The Timezone setting is now an enum
populated from Intl.supportedValuesOf('timeZone') ('' = system default). Kept a
validation guard in I18n.setTimezone as belt-and-suspenders for old synced
configs that already hold a bad hand-typed value (which otherwise made Intl
throw and crashed the dashboard). Also the explicit claude-sonnet-5 pricing
entry. Unit-tested.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: one-time "what's new" prompt after an upgrade (feature discovery)

Shows a single dismissible notification the first time a new major.minor runs,
pointing at the dashboard — so users find new features, including the opt-in
default-off ones they'd otherwise never notice. Keyed by major.minor in
globalState (patch releases don't re-nag); skipped on fresh installs.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(content): opt-in top-10 costliest conversations (showEfficiency)

New showEfficiency setting (default off — not everyone wants cost metrics).
When on, the Content tab gains a "Top 10 costliest conversations" panel: rows
ranked by cost, each expandable (native <details>, no JS) to its tokens,
cache-hit rate, top model and project. Same toggle will gate the
cost-per-message + cache-savings chips on Today/projects (next).

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(content): efficiency chips — cost per message + realised cache savings

Under the usage summary (Today / Month / All), gated by showEfficiency: an exact
"cost per message" chip (totalCost ÷ messages) and a "cache savings" chip
(cacheRead tokens × the per-model input−cacheRead price gap — what caching
actually saved vs. full input price). Off by default.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(share-card): render usage share card as self-contained SVG + export command

Batch 6 (V2.2 share card) — make the tested shareCard.ts core visible/exportable.

- shareCardSvg.ts: renderShareCardSvg(ShareCardData) -> SVG. No DOM, no
  html-to-image dep, no CSP concerns. Landscape 1200x630 social-card layout:
  header, big token hero (falls back to cost/sessions), up to four stat tiles,
  a per-day rhythm strip, badge pill, and watermark. Only draws fields present
  in ShareCardData, so privacy stays enforced by the type's shape.
- ClaudeDataLoader.buildShareInput(records, range): aggregate inputs for
  today / last 7 days / this month, timezone-bucketed to match the dashboard.
  Returns only aggregate numbers (no records, no identifiers).
- extension.ts: "Export Usage Share Card (SVG)" command — pick range, save,
  and open for review.
- 8 renderer tests (well-formed output, hero fallback, omitted sections,
  tiles/badge/rhythm, XSS-escaping, watermark toggle). 75/75 pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(efficiency): scope efficiency chips to the Today view

Per spec the cost/message + cache-savings chips belong on Today (and
per-project), not on the Month / All-time summaries. renderUsageData now
takes an { efficiency } opt and only Today passes it, so the chips no longer
leak onto the other two scopes. Still opt-in via showEfficiency.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(efficiency): add opt-in "Cost/msg" column to the projects table

Completes the "Today + projects" efficiency surfacing. Projects is a table,
so per-project cost-per-message reads better as a column than a summary chip.
Gated by showEfficiency (off by default); shows "—" when a project has no
counted messages. Non-sortable column — sortTable() reads data-sort-* row
attributes, so inserting it doesn't disturb the other columns' sorting.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(v2.2): efficiency tokens/msg chip, curated tz dropdown, quota Xd Yh, session-delete opt-in, whats-new preview

Batch of the smaller items from Carl's review:

- Efficiency chips: add "Tokens / message"; show on Today AND Month AND
  All-time again (removed the Today-only scoping). Projects keeps its column.
- Timezone: trimmed the ~400-zone dropdown to a curated common set, each
  labelled with its live (DST-aware) UTC offset, e.g. "(UTC+08:00) Shanghai".
  Settings enum renderer now keeps a stored-but-uncurated value selectable.
- Quota tooltip: weekly reset countdown now "4d 12h" instead of "4.5d"
  (statusBar.formatCountdown); the compact status-bar form keeps "4.5d".
- Session delete: gated behind new opt-in "enableSessionDelete" (off by
  default) — it touches local history files, so it shouldn't be on for
  everyone. Resume tooltip now explains what resume does (EN/zh-CN/zh-TW).
- What's-new: extracted showWhatsNew(); added "Preview What's New" command so
  it's testable (a fresh F5 install otherwise just sets the baseline silently).

Compile clean, 75/75 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(content): rework "costliest" panel to top-10 single MESSAGES

The old panel just re-ranked sessions (a dupe of the Sessions tab). Carl wants
the Content tab's version to fit that tab's theme (AI advice / optimizer):
rank individual assistant turns and let each expand to explain itself.

- getCostliestMessages(records, limit): walks each session in timestamp order,
  attaches the preceding user prompt, and keeps the top-N billed turns by cost
  (bounded insertion). Each carries the full model id, the authoritative
  skill/plugin attribution, the 4 token types, cost, prompt, project.
- Store a truncated _promptText (300 chars) on the synthetic user-prompt
  records so the panel can show what triggered an expensive turn.
- Webview: renderCostliestMessages() — summary row = rank + prompt + cost;
  expands to the prompt (blockquote), model, skill, token breakdown, project.
  Precomputed in the extension and passed in (not re-walked per re-render).

Verified on real data (8855 records): top-10 with prompts + full model ids
(fable-5 / opus-4-8 / opus-4-7) attach correctly.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(share-card): discoverable in-dashboard preview + richer card (composition, QR, full model, peak)

Addresses Carl's share-card review:
- Discoverable: an always-visible "Share card" panel on the All tab with an
  inline, responsive preview + "Export as SVG…" button (the command alone was
  unfindable). Export now matches the preview (composition on).
- More metrics: a token-composition stacked bar (the four billed token types)
  with a legend — off in DEFAULT_SECTIONS but on in the dashboard/export.
- Bar scale: the daily-rhythm strip now has a baseline, a peak gridline and a
  "peak <N>" label so the bars carry a scale.
- Full model name: prettyModelName() → "Opus 4.8" (not just "Opus"); the card
  shows the specific model.
- Traffic (引流): a real QR to the upstream repo bottom-right, wrapped in a
  link + "★ Star us on GitHub". QR is a static embedded asset (generated once
  with `qrcode`, then uninstalled — zero runtime deps). See shareCardQr.ts.
- Canvas grew to 1200×760 to fit the new blocks; layout verified overlap-free.

+6 tests (QR link, full model, composition bar, peak label, prettyModelName).
80/80 pass, compile clean.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(content): show hidden thinking honestly + full scrollable prompt in top-10

Two fixes from Carl's review:

#6 Thinking-share = 0 bug. Models that omit their raw reasoning (Fable 5;
Opus 4.8 by default) log `"thinking":""` with the CoT encrypted in `signature`,
so the text-length estimate read 0% even though thinking was on and billed.
Now: detect a thinking block with empty text + a signature and flag the
session (ThinkingShare.hiddenThinking). The Sessions "thinking share" column
shows "hidden" (tooltip explains) instead of a misleading 0%; a partly-hidden
session shows "N%*". i18n across all 7 languages.

#1 Top-10 costliest messages: raise the stored prompt cap 300 → 4000 chars and
render the prompt in a scrollable box (max-height 220px) so the whole triggering
prompt is visible for cost analysis; giant pastes still capped + marked
"(truncated)".

Verified on real data: 33 measurable / 2 hidden / 14 genuine-zero sessions.
Compile clean, 80/80 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(settings): timezone dropdown covers all IANA zones (Common + All optgroups)

#3: the curated list was nicer but not complete. Now enumValues holds every
IANA zone (418) so any is valid, and the dropdown groups them with <optgroup>:
"Common" (25 curated, offset-labelled) at the top for quick picking, then
"All zones" (the remaining 395, alphabetical, also offset-labelled). Added an
optional enumGroups to SettingDef; the webview enum renderer draws optgroups
when present and still injects a previously-stored out-of-list value.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(share-card): configurable, default-off share panel (range/scope/metrics, on-demand)

Overhaul from Carl's review (Q2):
- Drop the QR (deleted shareCardQr.ts) — watermark now carries the repo URL.
- Default OFF: new `enableShareCard` setting; the All-tab panel shows a
  discoverable pointer to Settings until enabled.
- Config IN the plugin: range (this month / last 7d / last 12 months / today /
  last 12 specific calendar months) + scope (overall / a project / a session) +
  per-metric checkboxes. buildShareInput now takes range+scope; supports
  `month:YYYY-MM` and `year`, resolves a project/session filter, and returns
  dailyDates + a human range label ("June 2026").
- Generate-on-demand: nothing renders until "Generate preview" (no
  per-keystroke re-render). Export uses the same config (webview builds + saves).
- More selectable sections incl. a new `messages` (message count); default ON =
  est. cost + top model + cache-hit (+ token hero, rhythm, badge). Sessions,
  messages, composition, project name are opt-in.
- Token composition legend now shows the absolute amount as well as the %.
- Rhythm strip labels the first & last date so the x-axis is meaningful.
- Buttons reuse the plugin's .btn-secondary style. Canvas back to 1200×680.

Verified on real data (month/year/specific-month, project & session scope).
Compile clean, 83/83 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(heatmap): publish token heatmap to GitHub via built-in auth (Q4)

One-click "Publish Token Heatmap to GitHub" — uses VS Code's GitHub
authentication (getSession 'github' ['repo'], no PAT handling), then creates or
updates the heatmap SVG in a repo via the Contents API. Defaults to the user's
profile repo (owner/name = username) so it renders on their GitHub home page;
remembers the last repo/path. Buttons added to the heatmap panel (Export /
Publish to GitHub) + a command-palette entry. Minimal https helper (the codebase
avoids fetch). Supersedes the spec's earlier "no GitHub in V2.2" note — Carl
approved it for V2.2.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(ci): mention-driven @claude agent (Track 2 A1) — inert until owner enables

First piece of the V2.2 AI automation. A maintainer can summon Claude with
"@claude ..." in an issue / comment / PR review; it reads the repo (CLAUDE.md,
ARCHITECTURE.md, CONTRIBUTING.md) and can reply or open a PR.

SAFE BY DEFAULT — the job's `if:` requires BOTH a repo variable
AUTOMATION_ENABLED='true' (kill switch) AND the ANTHROPIC_API_KEY secret, and
only fires for OWNER/MEMBER/COLLABORATOR authors. With neither set it never runs
and spends no budget. `anthropics/claude-code-action@v1`, --max-turns 12,
timeout 15m, concurrency-guarded.

A2 (triage draft-queue) and A3 (PR first-pass review) — the auto-posting pieces
— are the next, separately-reviewed step; they need the owner to add the
ANTHROPIC_API_KEY secret first. See docs/V2.2-SPEC.md Track 2.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(settings): timezone dropdown = full UTC-offset coverage (not every city)

Carl clarified Q3: it's the UTC *offsets* that must be complete, not every
location. Replaced the 418-city "All zones" group with a "UTC offset" group
covering every offset (-12:00 … +14:00, incl. the real :30/:45 ones). Whole
hours use fixed-offset Etc/GMT zones (no DST); fractional offsets use a
representative zone with a region hint. "Common" named zones stay at the top.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(content): quantify cache-miss in top-10 costliest messages (Q7)

Seeing the prompt alone didn't explain the cost. Each costly message now carries
its cost split by token type (cache write / read / output / uncached input), and
the panel shows the main cost driver (e.g. "cache writes (miss) — 96% of cost"),
the cache-hit rate, and the full split. On real data the top turns are ~0-2%
cache-hit and cache-write-dominated — i.e. the context was re-sent uncached
(a cache miss), not that the answer was long. Now distinguishable at a glance.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(share-card): portrait + size presets, avatar, hourly today, tidy panel (Q1/Q2/Q3/Q5/Q6 review)

Share-card round 2 from Carl's review:
- Layout rewritten to flow vertically and distribute spare height as even gaps,
  so the same content reads well at every size. Size presets: Landscape
  1200×680, Square 1080×1080, Portrait 1080×1350, Story 1080×1920 (portrait for
  phones). Watermark pinned to the bottom with a clear gap — fixes the date/name
  overlap.
- Rhythm bars are centred + width-capped, so short ranges don't look sparse;
  "today" now uses HOURLY buckets (24 bars, 00:00→23:00) instead of one bar.
- Avatar: opt-in "GitHub avatar" fetches the signed-in user's avatar
  (read:user, on demand, cached) and embeds it (circular, top-right, replacing
  the badge). Renderer supports opts.size + opts.avatarDataUri.
- Config panel beautified to the plugin's card language (bordered card, field
  grid, chip checkboxes, checkerboard preview frame) + a Size select; the range
  dropdown is grouped (Rolling / Specific month) with a "(last month)" marker.
- Heatmap→GitHub publish now shows a consent modal first (it's an auth+write
  action, unlike a local export).

+4 tests (size presets, avatar embed). 85/85 pass, compile clean.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(settings): group opt-in toggles under "Optional features" (Q3)

Reorganized the settings for user habit: the four opt-in dashboard toggles
(token heatmap, efficiency insights, share card, delete-session action) move out
of the General grab-bag into a dedicated "Optional features" group, so General
holds just locale/format/behaviour. New SettingGroup 'features' + i18n label
across all 7 languages. (Share-card range dropdown grouping / "last month"
shipped in af3509e.)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(ci): let the @claude agent use a third-party Anthropic-compatible endpoint (Q8)

A Claude subscription can't drive CI (Actions needs a key). For maintainers
without an official Anthropic key, the mention agent now honours a repo variable
ANTHROPIC_BASE_URL (env on the action step) so it can point at any
Anthropic-format-compatible provider, with that provider's key in the
ANTHROPIC_API_KEY secret. Unset = official API. Still inert until enabled.
Clarified that repo guidance is read from CLAUDE.md/ARCHITECTURE.md (no full-repo
re-read each run).

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(quota): don't fabricate a 5h reset countdown while idle after a reset (#3)

The 5h / weekly windows are usage-anchored: the next window (and its reset)
only starts when you send your next message. When a window's resets_at passed
while idle, liveWindows() rolled it forward ("reset = old + 5h") and showed a
countdown that was wrong until the next real fetch — exactly the "reset already
ticking before I sent anything, then jumps when I do" bug. Now a recently-expired
window shows 0% with NO countdown (resets_at cleared → "—"); the real reset
lands on next use. (Long-expired still drops; unparseable still kept.)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(share-card+content): round-4 review — landscape-only, last-30 default, full numbers, >4 fix, avatar/refresh fixes, top-10 privacy toggle, neutral tz labels

Share card:
- Size selector removed — landscape 1200×680 only for now (other sizes need
  per-size tuning; deferred to a patch). Aesthetic redesign is coming separately.
- Default range is now the last 30 days (added `last30`), not "this month".
- New "Full numbers" toggle shows the exact token count in the hero (font
  auto-shrinks so it fits).
- Fixed the >4-metrics bug: tiles allow up to 6 and wrap to a second row.
- Rhythm first/last date labels now sit UNDER their bars (centred), not pinned
  to the axis ends — correct for short, centred ranges.
- Avatar fix: httpsGet now follows redirects (GitHub avatars 302 to a CDN — not
  following them was why the avatar came back blank); + xlink:href for export.
- Refresh no longer wipes the card: the last generated SVG + the config (range/
  scope/metrics/toggles) are remembered and rehydrated on re-render. (Same class
  of issue Carl hit with the optimizer output.)

Content: the "top 10 costliest messages" panel is now its OWN opt-in
(`showCostliestMessages`, off) — separate from the efficiency chips — because it
displays your prompt text (privacy).

Timezone: labels use canonical IANA identifiers (official/apolitical) — dropped
the editorialised country groupings; the UTC-offset group shows only the offset.

85/85 tests pass, compile clean.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(content+webview): top-10 input-interval metric, keep expanded rows on refresh, fix avatar CSP

#1 Top-10: each costly turn now shows "Time since last turn" (gap to the
previous billable turn in the same session; "New conversation" for the first),
and both Cache-hit rate and the gap are bold. A gap past the ~5-min cache TTL is
flagged. On real data the priciest turns pair 0-2% cache-hit with multi-hour
gaps — i.e. the cache went cold while idle, then had to be rewritten. Now
obvious at a glance.

#2 Auto-refresh no longer collapses expanded <details data-persist> rows — open
state is saved (vscode.setState) and restored across re-renders, so a data
refresh doesn't interrupt reading. Switching tabs resets to default (as asked).

#7 Avatar root cause: the webview CSP had no img-src, so it inherited
default-src 'none' and blocked ALL images incl. data: URIs — the avatar <image>
never rendered even once fetched. Added `img-src data:;` (data URIs only, still
no external hosts). Combined with the earlier redirect-follow fix, the avatar
should now show.

85/85 tests pass, compile clean.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(ci): AI auto first-pass on every issue + PR; rename mention handle to @ccu-bot (Q4)

Carl authorized auto-replies and added the API key (repo secret, third-party
1M-context, ANTHROPIC_BASE_URL-capable). Two new workflows:
- issue-first-pass.yml (issues: opened): posts ONE first-pass comment.
- pr-first-pass.yml (pull_request_target: opened): posts ONE first-pass review;
  base-repo checkout only, reads the diff via API — never runs PR code.

Both: labelled "🤖 Automated first-pass reply (via Claude Code)", end with a
model-generated / a-maintainer-follows-up disclaimer, read ARCHITECTURE.md/
CLAUDE.md/CONTRIBUTING.md and — if those are insufficient — the repo files
(never guess), reply in the author's language. Skip bot authors; concurrency +
timeout + max-turns. Inert until AUTOMATION_ENABLED='true'; third-party endpoint
via ANTHROPIC_BASE_URL.

Renamed the mention trigger @claude → @ccu-bot (the model may be third-party, so
"@claude" was misleading). A2 draft-queue stays deferred until this runs stably.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(share-card): avatar + badge + GitHub name together (personalized corner)

Carl: avatar and badge shouldn't be mutually exclusive, and a name adds
personality. The top-right corner now shows the avatar circle with a two-line
name / badge block to its left — any subset renders. New opt-in "GitHub name"
checkbox; getGithubIdentity() fetches avatar + display name in one call (cached),
so the name and avatar share the single read:user sign-in. Config (avatar/name)
is persisted across refresh like the rest.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(content): detect model-switch cache flush + estimate the cache TTL (Q1)

Model switch: top-10 messages now carry the previous turn's model; when the
hit rate is low the panel names the likely cause — "model switch (Opus→Sonnet)
flushed the cache" vs "idle Xh — cache went cold". Distinguishes the two
cache-miss reasons Carl noticed (a 17m/1h25m gap with a low hit rate is usually
a switch, not idle expiry).

Cache-TTL estimate (Carl approved for V2.2): estimateCacheTtl() buckets
same-model consecutive turns by idle gap and finds where the hit rate drops.
On real data the hit rate holds ~100% to ~60 min then falls off — i.e. the
warm window is ~1 hour here, NOT the assumed 5 min. Surfaced as a "Cache warmth"
insight on the Content tab (opt-in via showEfficiency; aggregate only, no
prompts): "cache stays warm ~60 min; batch complex work within that window."

86/86 tests pass, compile clean.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: @ccu-bot contributor note, data-contribution scaffold; graceful arch-doc fallback

- CONTRIBUTING.md: section explaining @ccu-bot (labelled + disclaimed,
  comment-only, ask-not-guess, off unless the maintainer enables it). (Q9)
- src/dataContribution.ts: opt-in data-contribution SCAFFOLD for future
  cross-user features (cache-TTL by tier/task, etc.). OFF by default, no
  endpoint, no-op transport; Observation type has no identifying field; tier
  redacted unless shared. +4 tests. (Q2)
- Automation: ARCHITECTURE.md is gitignored (a private local context pack), so
  CI can't read it — the first-pass prompts now say "ARCHITECTURE.md if present"
  and fall back to CLAUDE.md + reading src/. Whether to publish a scrubbed
  technical ARCHITECTURE.md is Carl's call (see docs/ARCHITECTURE-private.md). (Q6)

90/90 tests pass, compile clean.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(share-card): implement GPT "Code Pulse · Aurora Console" redesign with dual themes

Executes docs/V2.2-ShareCard-GPT-design.md. One renderer, theme tokens:
- Two themes: **Claude Cream** (default — warm/editorial) and **Aurora Dark**
  (technical/social). SHARE_CARD_THEMES token set drives bg, panels, text,
  accents, composition/rhythm colors, badge. Theme option auto|claudeCream|
  auroraDark; "auto" follows the VS Code theme (resolveShareCardTheme).
- New composition: aurora gradient bg + soft blobs, brand block ("Claude Code
  Usage" / "AI coding usage snapshot" / range), glass stat tiles with an accent
  rail, token-mix bar (semantic colors), daily-pulse strip (peak highlighted),
  brand-signature watermark. No longer looks like a dashboard screenshot.
- Badge redesigned as a sticker card with on-brand copy + a one-line
  explanation (BADGE_COPY: Cache Alchemist "缓存命中高,token 没白烧" etc.),
  sharing the corner with the avatar + GitHub name.
- Defaults follow the design: hero + 4 tiles (cost/cache/model/sessions) + token
  mix + daily pulse + badge; messages/project/workflow/peak opt-in.
- Theme selector added to the config panel (default Claude Cream), persisted.
  Palette export command + webview both theme-aware (isDark for "auto").
- Landscape 1200×680 only (square/portrait deferred, per the brief).

Privacy/self-contained constraints unchanged. Tests updated for the new look +
theme parsing/default/auto/colors/hidden-fields. 89/89 pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: publish the technical ARCHITECTURE.md (Q6 — Carl chose to publish)

Un-ignored ARCHITECTURE.md + ARCHITECTURE-zh-CN.md so they're public: they're
already technical-only (module map, data flow, exact token/cost computation,
log-format facts, invariants, refresh, release), so nothing needed scrubbing.
Now the @ccu-bot first-pass automation and contributors get a real technical
map instead of re-reading src/. Maintainer product-direction stays private in
docs/ARCHITECTURE-private.md; the public file must remain technical-only.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(share-card): add Claude Classic (orange) theme + adaptive layout fixes

- New "Claude Classic (orange)" theme — the pre-redesign single-hue orange
  (monochrome orange ramp for the token mix, no violet/mint), for Carl to
  compare against the busier Claude Cream. Aurora Dark unchanged.
- Layout: content moved up (contentTop 134); the hero now sits at a fixed
  top-left position and the full-width sections flow BELOW whichever of the
  hero / top-right corner reaches lower — so enabling the GitHub avatar no
  longer pushes the whole card down and off the canvas (the reported overflow).
  Verified: with avatar, max content y=592, watermark at 652, all within 680.
- Token mix gets internal padding (h 64→84) so it no longer crowds the tiles
  above / pulse below; inter-section gaps clamped to [14,38] (not hollow, not
  cramped). Adapts to whichever sections are enabled.

89/89 tests pass, compile clean. Theme selector now offers Cream / Classic /
Aurora Dark / Auto.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(architecture): bring the public technical map current (V2.2)

- Fixed stale facts: 6→7 languages (added pt-BR); the "~5-min cache TTL" note
  corrected to the MEASURED ~60 min (estimateCacheTtl infers it from the user's
  own turns).
- Added a "V2.2 additions" table: shareCard/shareCardSvg (dual themes),
  heatmap/heatmapSvg + GitHub publish, dateKeys, promptDedup, quotaFormat,
  dataContribution; new dataLoader methods (getCostliestMessages, estimateCacheTtl,
  buildShareInput); efficiency/cache insights; thinking-hidden; UTC-offset
  timezone; and the @ccu-bot first-pass CI (inert unless enabled).

Keeps the file technical-only; product direction stays in the private companion.
The @ccu-bot automation now reads an up-to-date map.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(share-card): default to Claude Classic (orange) theme

Carl's pick — Claude Classic is now the default (dropdown first, panel/export/
handler defaults, and resolve's light fallback); Cream + Aurora Dark + Auto stay
available. Tests updated.

Co-Authored-By: Claude <noreply@anthropic.com>

* chore(v2.2): settings i18n for new toggles (7 langs), resume tooltip de/ja/ko/pt, what's-new + CHANGELOG

Pre-release polish so V2.2 is release-ready:
- SETTINGS_I18N: added the 5 new opt-in toggles (showHeatmap, showEfficiency,
  showCostliestMessages, enableShareCard, enableSessionDelete) in de-DE / zh-TW /
  zh-CN / ja / ko / pt-BR, and refreshed the timezone help (now UTC-offset).
- Resume-session tooltip: the fuller "reopens in Claude Code / claude --resume"
  explanation now in de/ja/ko/pt too (en/zh already done).
- What's-new toast copy updated for the real V2.2 (share card, GitHub heatmap
  publish, efficiency + cache insights).
- CHANGELOG [2.2.0]: added share card (themes), heatmap publish, top-10
  costliest MESSAGES (cost split / cache-miss / interval / model-switch), cache
  warmth, efficiency chips, session-delete opt-in, UTC-offset timezone, and the
  thinking-hidden / quota-format / refresh-persist / avatar-CSP fixes.

89/89 tests pass, compile clean.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(share-card): localize the card to the UI language (no en/zh mixing)

Carl: the card was English-only, and the badge line was Chinese even on an
English card. Now the whole card (subtitle, hero unit, tile labels, token-mix
legend, pulse, watermark, and the badge title + line) renders in the UI language.
Fully localized for en / zh-CN / zh-TW (the maintainer's languages); any other UI
language falls back to English — so a card never mixes languages. Card strings +
per-language BADGE_COPY live in shareCardSvg.ts (self-contained + tested); the
webview/export pass I18n.getLocale(). Badge line width widened so English lines
aren't clipped.

90/90 tests pass. de/ja/ko/pt card localization = a follow-up (they fall back to
en for now, consistently).

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(settings): heatmap toggle references the actual "All" tab name per language

Carl: the token-heatmap toggle said "全部标签" (unclear), not the real tab name.
Now the parenthetical matches each language's All-time tab label — zh-CN
"("所有"选项卡)", zh-TW "(「所有」分頁)", ja "(「すべて」タブ)", de/pt use their
tab labels; en "(All-time tab)".

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(share-card): Chinese cards use 万/亿 units + hero keeps one decimal

Carl: on a zh-CN card the token unit should read in 万 / 亿 (万 / 億 for zh-TW),
not M/K — and the hero total should always show one decimal (e.g. "5.1亿",
"5.0B"). compact() is now language-aware (万/亿 for Chinese) with a keepDecimal
flag the hero uses; tiles, composition amounts and the pulse peak follow the
card language too.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(analysis): cache-churn cost data layer (缓存损耗账单) — first V2.2 analysis

estimateCacheChurnCost(): the $ spent RE-writing cache that a warm cache would
have served cheaply, split by the two AVOIDABLE causes — a model switch (per-
model cache flush) and an idle gap past the TTL (~60 min). Only counts a rewrite
of a cache that existed on the previous same-session turn (read≈0, write>0), so a
genuine big-new-context isn't blamed. Waste ≈ cacheCreation × (writeRate −
readRate) per model, via getModelRatesPerMillion.

IMPORTANT framing (per Carl): a switch can still be net-worth-it if the other
model is cheaper per token — this is the churn cost, not "never switch". On real
data (30d): ~$240, mostly from idle gaps (71×) not switches (5×) — so momentum
within the warm window matters more than avoiding switches here.

Data layer + 2 tests now; the styled Content-tab section + model-fit (C5) land
next (with frontend-design). 93/93 pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ci): add id-token: write to first-pass workflows (sync #58 to V2.2 branch)

* feat(content): "Experimental insights" section — cache-churn bill (first card)

The V2.2 analysis workstream, styled to the plugin's card language (theme vars,
consistent with the costly-messages / cache-warmth panels) with a small
"estimate" tag as the honest signature.

- New `showInsights` setting (Optional features, default OFF) — these are our own
  heuristics, not standardized metrics, so opt-in + labelled.
- Content tab gains an "Experimental insights" section. First card: the
  **cache-churn bill** — headline $ (last 30 days) spent re-writing cache a warm
  cache would have served cheaply, split into idle-gap vs model-switch pills, with
  the honest note that a switch can still be worth it (this is only the churn
  cost). Built on estimateCacheChurnCost; aggregate only, no prompts.

C5 model right-sizing (next card) + the other insights come next.
showInsights settings i18n (7 langs) folded into the pre-release i18n pass.
93/93 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* polish(content): insight card — separate the caveat onto its own line + crisper copy

The cache-churn card read as a wall of text. Split it: a "💡" actionable
takeaway line (recoverable — keep momentum in the warm window), then the "a
switch can still be worth it" caveat as a smaller, top-bordered note on its own
line. Pills clarified ($ · cause · count). Reads as an insight now, not a
paragraph.

Co-Authored-By: Claude <noreply@anthropic.com>

* chore(ci): sync controlled-API first-pass bot to V2.2 branch (matches #64)

* feat(content): cache-hit-rate-by-model insight + weekly throttle for cache analyses

Second insight card (Carl's top pick — cache is the key cost lever): cache hit
rate per model, with the model family's nominal provider and a volume bar.
cacheStatsByModel() aggregates read ÷ input-side per model over 30 days. Real
data shows opus-4-8 97% / deepseek-v4-pro 98% / fable 96% etc. — so you can see
which models actually serve from cache. Tip: favour low-hit models for short,
self-contained tasks. (True cross-PROVIDER comparison needs pooled cross-user
data — reserved via the dataContribution scaffold.)

Also: the cache analyses (TTL / churn / by-model) now recompute at most WEEKLY
(analysisCache), not every render — Carl's call; they move slowly.

93/93 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(content): cache card shows per-model warm window (TTL), not just hit rate

Carl's call: the durable, cross-user-comparable signal is how long each model's
cache actually stays warm — and it drifts over time — so make TTL the primary
metric. cacheStatsByModel() now also buckets same-model idle gaps per model and
infers a warm window; hit rate + volume ride along as secondary.

Real data is telling: DeepSeek-v4-pro stays warm ~240 min, Opus 4.8 ~60 min,
Fable 5 only ~2 min (bursty, rarely resumed after a gap). That per-model spread
is exactly what you can't see from a single global number.

Also fixes the bar/number ambiguity Carl flagged: the bar AND the bold number
are now the same metric (TTL); "—" shown when a model has too few gaps to
estimate. Hit% + token volume moved to a muted sub-line.

93/93 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(bot): two-tier first-pass with controlled repo-file escalation + skimmable template

When the cheap model (CCU_BOT_MODEL, default deepseek-v4-flash) says it can't
answer from the docs alone, escalate to a stronger model (CCU_BOT_MODEL_PRO,
default deepseek-v4-pro) with thinking on, and let it NAME a few repo source
files. The SCRIPT reads them — repo-relative text files only, no "..", size-
capped, must resolve inside the repo root — so there's still no agent tool and
no way for a malicious issue to make it read or run anything arbitrary. If it
still can't answer, its reply asks for the specific missing info/logs and
suggests the issue template.

Also gives every reply a skim-friendly shape: TL;DR/结论 first, then analysis,
then suggested next steps. Thinking param is best-effort (retries without it if
the endpoint rejects it), so it stays portable across third-party endpoints.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(sessions): read-only conversation viewer (opt-in) — re-read a session without spending context

Carl's use case: after /clear or /compact he wants to RE-READ his prompts and
the model's answers to jog his memory, but resume (`claude --resume`) reloads
the whole session INTO context and costs tokens. This viewer is the zero-context
path — it just reads the session .jsonl and shows it.

- conversationLog.ts: pure, tested parser (.jsonl text -> ordered display turns).
  Keeps prompts + assistant text front and centre; carries thinking / tool_use /
  tool_result too. Skips meta + sidechain lines, dedupes by uuid, truncates per
  turn, supports "last N turns". 7 unit tests.
- conversationViewerHtml.ts: frontend design per Carl's ask — user PROMPTS are
  the star (accent rail + tint + a top "jump to your prompts" nav), model TEXT
  is a readable card, thinking / tool traffic is present but quiet (collapsed
  <details>, muted). No scripts (webview enableScripts:false); VS Code theme vars
  only; a read-only badge makes the zero-context promise explicit.
- webview.ts: an eye view action on Sessions rows (gated by the new setting) ->
  viewConversation message -> openConversationViewer() reads the file and opens a
  panel. settings.ts: showConversationViewer (features, default OFF).
- i18n: viewConversation button tooltip in all 7 languages. (Viewer panel chrome
  is English-first like the other experimental surfaces; folded into the
  pre-release i18n pass.)

100/100 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(viewer): chat-mode conversation viewer — rendered Markdown, filled answers, default-hidden tools

Round of viewer feedback from Carl:
- Model answers now render Markdown (new dependency-free miniMarkdown.ts, 8
  tests) instead of showing raw source — much better to read inside the panel.
- Assistant answers get their OWN filled card (was plain), so they stand apart
  from both prompts (accent) and the muted tool traffic.
- Default is a clean CHAT view: thinking and tool activity are HIDDEN behind two
  pure-CSS toggles (checkbox hack, no scripts) — on open you see only prompts +
  answers.
- Opens on the last ~10 rounds; earlier rounds collapse into a <details>.
- Prompt nav items are now expandable to the full prompt text, each with a
  "jump ↓" link.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(sessions): add "Active" column — idle-capped hands-on time

The Duration column is the first→last wall-clock span, which is misleading for
long-lived sessions (one real session spans 957h but only ~12.5h was hands-on).
The new Active column sums the gaps between turns with each idle gap capped at
5 min (activeDurationBySession), so it reflects time actually spent. Sortable,
with a tooltip explaining the estimate; labelled in all 7 languages.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ci): don't auto-review maintainer PRs; treat an empty model reply as a no-op

Two first-pass bot fixes (also going to main via PR):
- The PR first-pass now SKIPS pull requests authored by OWNER / MEMBER /
  COLLABORATOR. A maintainer's own finished PR doesn't need an auto first-pass
  (that review is for external contributors) — and #66 showed it firing on our
  own PR and failing on the big self-referential diff.
- An empty model reply is now a soft no-op (exit 0) instead of a hard failure,
  so a benign blank response doesn't leave a red ✗ check.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(viewer): interim-answer toggle, Markdown tables, visible jump + latest marker

More viewer feedback from Carl:
- Assistant answers that aren't the round's FINAL output are marked "interim"
  and can be toggled off (default ON — intermediate results have value); the
  final answer of each round always stays.
- Markdown tables now render (added GFM table support to miniMarkdown + 2 tests).
- Prompt-nav jump link no longer clipped on long prompts — it sits outside the
  truncating text (flex), always visible; the caret still expands full text.
- The most recent prompt gets a "latest" badge in the nav for quick jumping.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(sessions,insights): active-time tuning, merge session mutating actions, C5 right-sizing

One round of Carl's feedback on sessions + insights:

- Active column: idle-gap cap raised 5m → 90m (reading/reviewing a diff is real
  work the log doesn't capture; the old cap badly under-counted). Column moved
  BEFORE Duration; header + cells carry the estimate tooltip. Help text updated
  in all 7 languages.
- Resume + Delete now share ONE opt-in (enableSessionActions, default off):
  both ACT on the user's Claude Code (reopen / trash a log), at odds with the
  extension being read-only, so they hide together. The read-only conversation
  viewer flips to default ON (showConversationViewer) — it only reads local logs.
- C5 "Premium models on lightweight turns": the compute premium spent running a
  top-tier model (output ≥ $20/M) on small-output turns (≤800 out — the
  translation / quick-edit signature), vs Haiku-4.5 rates. Surfaces a per-switch
  churn cost so it says "batch simple tasks on the cheap model", not "flip every
  turn". Real data: ~$19.74 over 2339 turns, mostly Opus. Weekly-cached.

110/110 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(insights,viewer): defer C5 card; cap prompt-nav to the last 10

- C5 "premium models on lightweight turns" is no longer rendered. Output size is
  a weak proxy for task difficulty, and the advice ("use a cheaper model for
  simple tasks") isn't something users lack — in practice they stay on the top
  model and tack simple work onto long tasks. A worthwhile version needs a real
  difficulty judgement (sampling prompt text via the opt-in AI-advice API, which
  must be clearly labelled). modelRightsizing() stays as dormant scaffolding.
- Viewer prompt-nav is capped to the last 10 prompts (matching the recent rounds
  shown) so the list is a consistent length and every jump lands in expanded
  content; a note flags any earlier prompts folded into "Show earlier".

110/110 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(viewer): cap by rounds, not raw turns — long sessions now show 10+ prompts

Bug (Carl): a long session with 79 prompts showed only the latest ~3. Cause: the
viewer capped at the last 500 RAW turns, but one prompt is followed by dozens of
tool turns, so 500 turns held only ~4 prompts.

Fix: parseConversation now caps by ROUNDS (a round = a prompt + its replies) via
maxRounds, with a raw-turn ceiling as a safety net. The viewer keeps the last 15
rounds (10 shown + a small "earlier" buffer). Parser also reports totalRounds;
the header now reads "N prompts total · … · showing last 15 of N rounds".
Verified on the real 79-prompt / 6798-turn session: nav shows the last 10
prompts, latest badge on the newest, ~0.6 MB html.

111/111 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(insights): "Big one-shot turns" card — nudge toward more checkpoints

Next insights-roadmap card (session-health), implementing the checkpoint habit
Carl wanted surfaced: sessionHealth() measures how much of your output came from
a few very large single responses (≥4k output tokens — a big one-shot generation
with no human checkpoint mid-way). Real data: ~39% of output over 321 jumbo
turns (largest ≈50k). The card frames it as a gentle nudge (smaller checkpoints
= cheaper/faster review, catch a wrong direction early), not a verdict — a big
single turn is sometimes exactly right. Weekly-cached, opt-in (showInsights).

111/111 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: viewer refreshes one reusable panel; new "Your active hours" insight

Two items from Carl's feedback:

- Conversation viewer now (re)uses a SINGLE panel: each "view" click re-reads
  the session .jsonl fresh and refreshes that panel (revealing it), instead of
  piling up a new tab per click. An ongoing session shows its latest turns.

- New insights card "Your active hours": activeHours() buckets non-cache work
  (input+output tokens) by hour-of-day in the user's timezone, with a 24-hour
  sparkline and the busiest contiguous 4-hour window + its share. Real data:
  peak 15:00–19:00, 35% of work. Tip ties it to protecting the peak window and
  keeping momentum in one warm session there. Weekly-cached, opt-in.

111/111 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(insights): "Skill ROI" card — cost vs output per skill/plugin

Next roadmap card. skillRoi() uses the authoritative _skill/_plugin attribution
Claude Code ≥2.1 stamps on usage lines: per skill, the cost spent while it was
active and the output it returned, with outPerUsd (output tokens per $) as the
ROI angle. Real data separates lean skills (lit-review ~15k tok/$, paper-plotting
~13.5k) from context-heavy ones (claude-api ~1.9k, code-review ~2.1k) — so you
can see which skills burn context for little output. Bar/number = cost; note
flags it's a proxy (cost attributed to the active skill, output isn't "value").
Weekly-cached, opt-in. (Budget-pacing card skipped for V2.2 per Carl.)

111/111 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(insights): Skill ROI shows output-tokens-per-$ as the bar AND number

Carl: the right-hand figure was a bare dollar amount, unclear as "ROI" and not
matching the bar. Now the bar and the number are the SAME metric — output tokens
returned per $ (with a "/$" unit) — sorted by it so the bars rank; cost + turns
move to the muted sub-line.

* feat(settings): live-refresh delay dropdown (0/1/2/5/10/20/30s) + settings i18n sync

- Replaces the fileWatching on/off toggle with a discrete "Live refresh delay"
  choice: Off / 1 / 2 / 5 / 10 / 20 / 30s (fileWatchSeconds, default 2s ≈ old
  1.5s). The fs watcher uses it as the debounce; Off stops watching. Help makes
  clear this only re-reads LOCAL log files — no API call; the /usage quota fetch
  is throttled separately, so a short delay doesn't hammer the official API.
- Settings i18n sync (Carl's release check): localized the setting label/help in
  all 6 non-English locales for fileWatchSeconds, plus showInsights and
  showConversationViewer (previously English-only), and renamed the
  enableSessionDelete labels to enableSessionActions to match the merged setting.

111/111 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(viewer,charts): cache-write bars were invisible; viewer "show earlier" was confusing

- Cache-write ("input cache miss") bars rendered transparent in every usage bar
  chart. Cause: .cache-creation-bar's gradient referenced var(--vscode-charts-pink),
  which VS Code doesn't define — an invalid color makes the whole gradient (and
  thus the background) invalid, so only the axis + value labels showed. Added a
  pink fallback so the gradient is valid.
- Conversation viewer: dropped the "Show earlier conversation (N rounds)" collapse.
  It was confusing — the top prompt-nav didn't reflect it, so you couldn't tell
  whether the earlier prompts were shown, and toggling it only changed the body.
  Now every loaded round renders flat and the nav lists every loaded prompt (all
  jump targets exist). Default load is the last 10 rounds ("showing last 10 of N").

111/111 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(release): V2.2 CHANGELOG + What's New; drop unused @types/glob

- CHANGELOG [2.2.0]: added the conversation viewer, Experimental insights suite
  (cache-churn / warmth-by-model / big one-shot / active hours / skill ROI),
  Sessions "Active" column, live-refresh delay control, Chinese share-card units,
  the enableSessionActions merge + timezone-aware bucketing (Changed), the
  cache-write invisible-bars fix (Fixed), and the @types/glob removal (Removed).
- Refreshed the in-extension "What's New" 2.2 blurb to match.
- Removed the unused @types/glob devDependency (folds in #63, thanks @zeyutang).

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(architecture): add v2.2 modules + Experimental-insights methods

ARCHITECTURE.md module table now lists sessionResume.ts, conversationLog.ts,
conversationViewerHtml.ts and miniMarkdown.ts, and the data-layer notes cover the
new insight methods (estimateCacheChurnCost, cacheStatsByModel, sessionHealth,
activeHours, skillRoi, activeDurationBySession, modelRightsizing).

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: timezone bucketing and cache-write cost accuracy (#62)

* fix: timezone bucketing and cache-write cost accuracy

* bucket Today/month/daily/hourly totals by the configured IANA zone
* keep the loader zone in lockstep with display; recompute on change
* split cache writes into 1h (2x) vs 5m (1.25x) when the log has the ttl
* add tests for zone bucketing and the cache-write split

* i18n: note timezone sets the daily cost boundary

* add the day-boundary clause to the timezone help in all six locales
* update the timezone description in README and the locale editions

* fix: changelog minor touch-up

* Maintainer edit: keep the cache-write TTL pricing; drop the timezone changes

The timezone-bucketing half overlaps with the shared zone-aware day-key rework
landing in V2.2, so this reduces the PR to just the cache-write TTL pricing
(pricing.ts + types.ts + pricing.test.ts) to merge cleanly. zeyutang's original
commits are preserved; the timezone work is welcome as a follow-up PR.

* Remove stray sample-card SVGs accidentally swept into the maintainer edit

---------

Co-authored-by: Carl <74130028+Carl723000@users.noreply.github.com>

* docs(changelog): credit #62 cache-write TTL pricing (@zeyutang)

* chore(release): bullet the 2.2 "What's New"; let #63 own the @types/glob removal

- What's New for 2.2 is now newline bullets (• per feature) instead of one dense
  paragraph, so the toast is scannable. Copy unchanged (Carl approved it).
- Restore @types/glob locally: #62-style, the removal now traces solely to
  zeyutang's merged #63 rather than a duplicate local edit. It's a devDependency
  (never packaged), so the vsix is unaffected; the merge to main drops it.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Zeyu Tang <tang@zeyu.one>
@Carl723000
Carl723000 deleted the ci/bot-escalation branch August 24, 2026 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI / workflow change feature Minor bump — new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant