Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
e5f3d53
test+ci+lint: complexity-program P0 safety nets
pacphi Aug 27, 2026
ed95188
chore: sync worktree to refactor/complexity-program P0 safety nets
pacphi Aug 27, 2026
a56f9d4
fix(live): recognize newer item_completed Codex message generation
pacphi Aug 27, 2026
78e9ac2
fix(live): reuse event-schema's inferredSignal instead of a duplicate…
pacphi Aug 27, 2026
cc07a4a
refactor(status): extract host-detail rendering to its own module
pacphi Aug 27, 2026
920c8d9
fix(providers): heal retired routes from ak host pick and ak setup
pacphi Aug 27, 2026
1a50395
refactor: decompose rufloActivationSegments into per-segment functions
pacphi Aug 27, 2026
62d3ce3
refactor(status): split collectDejaVuRows into per-concern functions
pacphi Aug 27, 2026
4210441
refactor(usage): share blankSession/addUsage between transcript sources
pacphi Aug 27, 2026
04dd8a0
merge: track E — statusline segment decomposition (CC 193→9)
pacphi Aug 27, 2026
076dbe7
refactor(providers): extract convergeProviderStack shared pipeline
pacphi Aug 27, 2026
43c10cb
refactor(usage): decompose detectInsights into 13 independent detectors
pacphi Aug 27, 2026
405f0d7
refactor(status): generalize the section-registry pattern to all of c…
pacphi Aug 27, 2026
99aa4b9
merge: track D — status section registry (collect CC 250→4, 1216→118 …
pacphi Aug 27, 2026
33291c0
refactor(dashboard): split the 15-route request handler into a route …
pacphi Aug 27, 2026
56b48e8
docs: archive PR-131 consistency dossier and issue-110 swarm prompt
pacphi Aug 27, 2026
2d50d09
refactor(providers): applyAqeRouter as ordered surface reconcilers
pacphi Aug 27, 2026
ecf70bd
refactor: extract loopback-server.mjs for shared dashboard/admin plum…
pacphi Aug 27, 2026
5d78f7f
refactor(sync): ordered step registry replaces inline subsystem branches
pacphi Aug 27, 2026
3497720
refactor(telemetry): share wire-record decode between batch scan and …
pacphi Aug 27, 2026
419c663
refactor(setup): decompose run/run_machine/run_project into named steps
pacphi Aug 27, 2026
c52a845
refactor(usage): extract scan()'s per-source concerns into named func…
pacphi Aug 27, 2026
d20c9a1
refactor(live): decompose reduceLiveEvent into phase functions
pacphi Aug 27, 2026
8d7b03a
refactor(host): split pick() into parse/decide/apply stages
pacphi Aug 27, 2026
c153c9e
docs(providers): note pick/setup also heal retired routes
pacphi Aug 27, 2026
b96e8b2
fix(live): recognize item_completed in the Codex content-plane adapte…
pacphi Aug 27, 2026
befbdc8
merge: track B — telemetry decode layer + five live/usage fixes
pacphi Aug 27, 2026
d433507
refactor(providers): share the routing-retirement report line, trim s…
pacphi Aug 27, 2026
be6bc14
refactor(dashboard): split client.mjs's 4,066-line template literal i…
pacphi Aug 27, 2026
24ea672
merge: track A — one provider pipeline, reconciler surfaces, step reg…
pacphi Aug 27, 2026
0fd221f
refactor(dashboard): split styles.mjs's 1,309-line stylesheet per area
pacphi Aug 27, 2026
705ed41
docs(adr): add ADR-0036 for the dashboard client/loopback-server refa…
pacphi Aug 27, 2026
6612ecb
docs: resolve ADR-pending references now that ADR-0036 is written
pacphi Aug 27, 2026
458e3b1
merge: track C — dashboard client modularization + shared loopback se…
pacphi Aug 27, 2026
e34a4e0
fix(providers): kill the status/writer drift-comparator duplication (…
pacphi Aug 27, 2026
3645a73
merge: finisher — drift comparators re-homed onto the writer (#129 cl…
pacphi Aug 27, 2026
a9e25c0
docs(adr): ADR-0037 — complexity program structural patterns and gates
pacphi Aug 27, 2026
7dc5ae1
docs: archive the adapter-contract dossier and host-extensibility exp…
pacphi Aug 27, 2026
60e2806
test: fix Windows shim spawning and expected intelligence-503 in CI
pacphi Aug 27, 2026
b23f7ab
fix(dashboard): tolerate CRLF checkouts in the client bundle import-s…
pacphi Aug 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Source files are read as TEXT at runtime in places (the dashboard client
# collector concatenates ./client/*.mjs; admin-server inlines admin-view.mjs;
# Function.prototype.toString sources are interpolated into served bundles), so
# a CRLF checkout must never diverge from what CI's LF platforms exercise.
*.mjs text eol=lf
*.cjs text eol=lf
*.js text eol=lf
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,25 @@ jobs:
- name: Dependency audit
run: pnpm run audit

# Rendering verification: the route suites prove the server returns correct
# JSON; only this proves the page RENDERS it (no `undefined`/`NaN`/
# `[object Object]` in visible text). Drives the SYSTEM Chrome — ubuntu
# runners ship it, nothing is downloaded.
ui:
name: ui (dashboard rendering, system Chrome)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: 22
cache: pnpm
- name: Install devDependencies
run: pnpm install --frozen-lockfile
- name: Dashboard UI (Playwright, fixture corpus)
run: pnpm run test:ui

# Internal/relative links only (fast, deterministic). External links run in
# nightly — network + rate-limits make them flaky per-PR.
links:
Expand Down
4 changes: 3 additions & 1 deletion docs/PROVIDERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,9 @@ The current [OpenAI API model catalog](https://developers.openai.com/api/docs/mo
GPT-5.4 and GPT-5.4 mini, and no first-party withdrawal notice supports the former automatic
replacement claims. `ak` only adds a retirement rule when it can cite the host's direct notice; a
newer default remains a recommendation, not a route rewrite (see
[ADR-0003](adr/0003-auto-seed-dual-host-provenance.md)).
[ADR-0003](adr/0003-auto-seed-dual-host-provenance.md)). Once a citation-backed rule exists,
`ak host pick`, `ak setup`, and `ak sync` all rewrite a seeded route naming the withdrawn model —
a user-pinned route is reported, never rewritten (still routed to the replacement at run time).

`claude-opus-4-8` is **not** retired — it carries no deprecation notice and stays pinnable. It is
merely no longer the default, which `ak status` reports as routing *divergence*: a trade for you to
Expand Down
12 changes: 6 additions & 6 deletions docs/TRANSCRIPTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ rewritten; rule 3 of the module header, `usage-index.mjs:22-29`):
| Host | Store | Discovered by |
|---|---|---|
| Claude Code | `~/.claude/projects/<encoded-project-dir>/<sessionId>.jsonl` | `listClaude` (`usage-index.mjs:916-926`) — exactly one level of project directories |
| Codex CLI | `~/.codex/sessions/<yyyy>/<mm>/<dd>/rollout-<ts>-<uuid>.jsonl` | `listCodex` (`usage-index.mjs:891-904`) — the `yyyy/mm/dd` tree walk |
| Codex CLI | `~/.codex/sessions/<yyyy>/<mm>/<dd>/rollout-<ts>-<uuid>.jsonl` | `listCodex` (`usage-index.mjs:878-891`) — the `yyyy/mm/dd` tree walk |

Roots come from `defaultRoots()` (`usage-index.mjs:908-912`) and are injectable
for tests. A malformed line is skipped, never fatal (`jsonLines`,
Expand Down Expand Up @@ -65,7 +65,7 @@ An assistant entry with `isApiErrorMessage: true` is a **local placeholder**
Claude Code writes when a request dies before a real completion (connection
drop, rate limit, auth failure — `model: "<synthetic>"`, all-zero usage). It
is real engaged time but not a model attempt: counted as an *exception*, never
pushed into `models` or priced (`usage-index.mjs:551-570`; the full story is
pushed into `models` or priced (`usage-index.mjs:595-606`; the full story is
[`USAGE-SCORECARD-METRICS.md`](USAGE-SCORECARD-METRICS.md) §10).

### 1.2 Codex entry vocabulary
Expand Down Expand Up @@ -200,7 +200,7 @@ Two deliberate subtleties:
* **`tool-result` outranks `context`**: a `tool_result` block on an `isMeta`
entry is still tool feedback.

Codex user turns are `kind: 'prompt'` by construction (`usage-index.mjs:767-775`)
Codex user turns are `kind: 'prompt'` by construction (`usage-index.mjs:716-725`)
— rollouts only record real prompts as `user_message` events (§1.2).

Coverage: `tests/kit/usage-index.test.mjs` — "user-role turns carry a kind"
Expand All @@ -211,7 +211,7 @@ and image-only pastes get the right kind" (the two edges).

## 4. The `readSession` pipeline — how one session becomes a payload

`readSession(id, opts)` (`usage-index.mjs:1566-1621`) is the only way
`readSession(id, opts)` (`usage-index.mjs:1573-1629`) is the only way
transcript content leaves the module, and every step is a gate:

### 4.1 Locate, contain, bound
Expand Down Expand Up @@ -256,7 +256,7 @@ Every turn body is passed through `maskSecrets` (`usage-index.mjs:208` — the
23 secret shapes) **server-side, before
serialization**, then length-capped at `MAX_TURN_CHARS` (40,000,
`usage-index.mjs:89`) with the marker appended
(`usage-index.mjs:1721-1729`). Two invariants:
(`usage-index.mjs:1724-1734`). Two invariants:

* **Presence is the signal.** `truncated`/`originalChars` are emitted only
when the slice fired, so a complete turn cannot be misread as abridged.
Expand Down Expand Up @@ -412,7 +412,7 @@ was wrong before, for the curious.
assembled `meta` left `cost` undefined, and `fmtUsd(undefined)` renders the
truthy string `"$0.00"` — a fixed-looking zero on a panel whose whole
subject is cost. `meta.cost` is now priced via `sessionCost()` from the
same per-model usage rows `aggregate()` uses (`usage-index.mjs:1691`).
same per-model usage rows `aggregate()` uses (`usage-index.mjs:1696`).
* **Aggregate-side incidents** (the v4/v5 cache bumps, the Codex parsing
defects) are recorded in `USAGE-SCORECARD-METRICS.md` Appendix A.

Expand Down
38 changes: 19 additions & 19 deletions docs/USAGE-SCORECARD-METRICS.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,12 @@ responses = Σ over included sessions of session.responses
**Source:**

- Filter: a parsed record with zero assistant turns is dropped entirely — "no
assistant turn → not a session" (`usage-index.mjs:1123`) — and a record whose
assistant turn → not a session" (`usage-index.mjs:1070`) — and a record whose
last activity falls outside the requested window is dropped too
(`usage-index.mjs:1124`).
- `responses` accumulation: Claude increments per assistant message
(`usage-index.mjs:568-571`); Codex increments per `agent_message` event
(`usage-index.mjs:653-662`).
(`usage-index.mjs:559-563`); Codex increments per `agent_message` event
(`usage-index.mjs:727-731`).
- Totals: `totals.responses += s.responses` per included session
(`usage-index.mjs:1175`).
- Render: `kpi("sessions", fmtNum(t.sessions), fmtNum(t.responses)+" assistant
Expand Down Expand Up @@ -335,9 +335,9 @@ numbers as percentages of `t.tokens` (`dashboard/client.mjs`,
**What "input" excludes.** For both providers, the `input` counter recorded
per row is **gross input minus cached input** — Claude's parser reads
`cache_read_input_tokens` and `cache_creation_input_tokens` as separate fields
the provider already reports separately (`usage-index.mjs:598-599`); Codex's
the provider already reports separately (`telemetry-records.mjs:209-212`); Codex's
parser subtracts `cached_input_tokens` from `input_tokens` explicitly
(`usage-index.mjs:780-789`, `input: Math.max(0, gross - cacheRead)`) because
(`usage-index.mjs:741-750`, `input: Math.max(0, gross - cacheRead)`) because
Codex's own `input_tokens` field **includes** cached tokens and would
double-count them against the separately-reported `cacheRead` figure if left
as-is. This is asserted by test:
Expand Down Expand Up @@ -434,7 +434,7 @@ session data, and each needs its own fix:
- `activeIntervals()` (`usage-index.mjs:427-438`) — splits one session's
sorted timestamp list into sub-intervals wherever a gap exceeds
`IDLE_GAP_MS`; "a run of one timestamp yields a zero-length interval and so
contributes nothing" (comment, `usage-index.mjs:399-403`).
contributes nothing" (comment, `usage-index.mjs:427-431`).
- Aggregation: `totals.engagedSeconds = mergeIntervals(sessions.flatMap(s =>
s._active))` (`usage-index.mjs:1219`); `totals.spanUnionSeconds =
mergeIntervals(sessions.map(s => s._span))` (`usage-index.mjs:1218`);
Expand Down Expand Up @@ -490,7 +490,7 @@ byDay[day].sessionsActive = count of distinct sessions with any usage row that d

**Source:** the day key is the row's own `row.day`, computed once at parse
time as **local calendar day**, not UTC
(`usage-index.mjs:602`/`usage-index.mjs:798` call `localDay(at)`) — so a
(`usage-index.mjs:589`/`usage-index.mjs:745` call `localDay(at)`) — so a
session that runs from 23:58 local to 00:05 local is billed to the day its
*first* row landed on (test:
`tests/kit/usage-index.test.mjs:634`, "a session that opens before midnight
Expand Down Expand Up @@ -522,8 +522,8 @@ renders "no sessions in window" instead of zeroed figures
(`dashboard/client.mjs`).

**Formula:** identical aggregation to every other bucket
(`byProvider[s.provider]`, populated via `addTo()`, `usage-index.mjs:942-951`,
called once per session at `usage-index.mjs:1084`), keyed by the literal string
(`byProvider[s.provider]`, populated via `addTo()`, `usage-index.mjs:1036-1045`,
called once per session at `usage-index.mjs:1097`), keyed by the literal string
`"claude"` or `"codex"` assigned at parse time
(`blankSession(id, 'claude')` / `blankSession(id, 'codex')`,
`usage-index.mjs:398-408`, `parseClaude`/`parseCodex` entry points).
Expand Down Expand Up @@ -562,9 +562,9 @@ punchcard[dow + "-" + hour] += 1 per assistant/agent_message response, at its
```

**Source:** incremented once per Claude assistant turn
(`usage-index.mjs:568-571`, keyed by `punchKey(at)`) and once per Codex
`agent_message` (`usage-index.mjs:653-662`), merged into the window-level
`punchcard` object per session (`usage-index.mjs:1197`). Cell intensity is
(`usage-index.mjs:559-563`, keyed by `punchKey(at)`) and once per Codex
`agent_message` (`usage-index.mjs:727-731`), merged into the window-level
`punchcard` object per session (`usage-index.mjs:1184`). Cell intensity is
linear against the single busiest cell in the window:
`v = pcMax ? n/pcMax : 0` (`dashboard/client.mjs`) — this is a
**relative**, not absolute, scale, so the heatmap's brightest cell is always
Expand Down Expand Up @@ -613,7 +613,7 @@ rather than vanishing.
passed into `addUsage()` at the call site — `1` per Claude assistant turn
(`usage-index.mjs:596-608`), or `rec.responses` (the session's whole response
count) once per Codex session, passed at the single point Codex calls
`addUsage` (`usage-index.mjs:798-804`).
`addUsage` (`usage-index.mjs:745-804`).

**Render:** `bar(name, fmtUsd(cost), fmtTok(tokens)+" · "+fmtNum(responses)+"
resp", pct(cost, topModelCost), false)` (`dashboard/client.mjs`),
Expand All @@ -630,7 +630,7 @@ distinct underlying causes, one placeholder shape).

The parser branches on `isApiErrorMessage === true`
(`usage-index.mjs:522-531`): the turn still increments `rec.responses`
and the punchcard (`usage-index.mjs:510-513`) — it *is* real engaged
and the punchcard (`usage-index.mjs:559-563`) — it *is* real engaged
time, someone was genuinely waiting on it — but it is never pushed into
`rec.models` and `addUsage()` is never called for it, so it can no longer
create a `byModel` row of any kind. It increments a separate
Expand Down Expand Up @@ -920,7 +920,7 @@ both credential-free for ak:
`windowDurationMins: 10080` (the weekly). Windows are therefore keyed and
labelled by duration (`windowLabel`, `quota.mjs:44`), never by slot name. The
same rule applies to the historical snapshots parsed out of rollouts: the
normalizer at `usage-index.mjs:729-746` keeps a flat `windows` list keyed by
normalizer at `usage-index.mjs:686-703` keeps a flat `windows` list keyed by
`window_minutes`.

**Freshness is part of the number.** Both sides carry `fetchedAt`; the view
Expand All @@ -945,13 +945,13 @@ Codex ≥0.140 maintains its own SQLite thread ledger (`~/.codex/state_N.sqlite`
— the `N` is a migration generation, so `codexStateDb` (`codex-state.mjs:30`)
globs and takes the newest). `readCodexState` (`:49`) reads per-thread
`thread_source` (`user` vs `subagent`) plus `thread_spawn_edges`, and
`applyCodexLedger` (`usage-index.mjs:1529-1539`) overlays that onto parsed
`applyCodexLedger` (`usage-index.mjs:1532-1543`) overlays that onto parsed
sessions: a ledger-identified subagent has its token usage stripped — its
rollout replays the parent's entire token history (ccusage/ccusage#950
measured up to 91× inflation) — while the session record stays visible. The
rollout's own `session_meta.thread_source` sniff remains as the fallback when
the ledger is absent or migrated beyond recognition. Codex sessions also carry
`reasoningOutput` (`usage-index.mjs:808`) — reasoning tokens are a **subset**
`reasoningOutput` (`usage-index.mjs:755`) — reasoning tokens are a **subset**
of output tokens and are annotation only, never added to any sum.

## 14. Known limitations, restated as a single checklist
Expand Down Expand Up @@ -1023,9 +1023,9 @@ at face value (correctly avoiding the separate naive-summing bug **[C5]**
documents, since it already used last-event-only logic — see §4's worked
example) but performed **no de-duplication** against a parent session a
subagent file might be replaying. **Fix:** the parser now reads
`session_meta.thread_source` (`usage-index.mjs:697-704`, confirmed as a real
`session_meta.thread_source` (`telemetry-records.mjs:117-122`, confirmed as a real
Codex rollout field by **[C7]**) and skips the `addUsage()` call entirely
when its value is `'subagent'` (`usage-index.mjs:780-790`, guard condition
when its value is `'subagent'` (`usage-index.mjs:741`, guard condition
`rec.threadSource !== 'subagent'`). The session record itself is **not**
dropped — it remains visible in the Sessions tab with `threadSource`
surfaced (mirroring the existing `sidechain` flag Claude sessions already
Expand Down
5 changes: 3 additions & 2 deletions docs/adr/0031-capability-graduation-and-upstream-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,5 +212,6 @@ the experimental flag. This table is the source of truth for what is real.
[ruvnet/ruflo#3046](https://github.com/ruvnet/ruflo/issues/3046), each inviting the maintainer to
close-as-satisfied if the current source already provides the surface.
- Companion explainer for consumers and implementers:
[`docs/HOST-EXTENSIBILITY-EXPLAINER.html`](../HOST-EXTENSIBILITY-EXPLAINER.html); design dossier:
[`docs/ADAPTER-CONTRACT-DOSSIER.html`](../ADAPTER-CONTRACT-DOSSIER.html).
[`docs/archive/2026-08-16-artifact-host-extensibility-explainer.html`](../archive/2026-08-16-artifact-host-extensibility-explainer.html);
design dossier:
[`docs/archive/2026-08-14-artifact-adapter-contract-dossier.html`](../archive/2026-08-14-artifact-adapter-contract-dossier.html).
Loading
Loading