The npm page was showing 'Status: skeleton. This package currently exports
nothing' — false since Phase 2, and badly so since Phase 8 completed. npm
auto-packs README.md regardless of the files list, and published versions
are immutable, so an accurate README needs a version bump to reach the
registry. ^0.9.19 ranges match 0.9.20.
The description still carried the Phase 1 skeleton text and wrongly named
pricing as core content (pricing is host-side by design). publishConfig
access=public is required for the scoped package's first publish.
COMPLETES Phase 8 of the @codeburn/core extraction: 36/36 provider identities
(including qwen) now decode in core.
The row -> call mapping moves to packages/core/src/providers/vercel-gateway/
verbatim: day/model/cost defaults, the all-zero skip BEFORE the dedup key is
burned, the `vercel-gateway:<day>:<model>` key with add-after-skip semantics,
`${day}T12:00:00.000Z` timestamp synthesis ('' for a missing day), and the
`${day}:${model}` session id. The decoder is pure over supplied rows: no fs,
env, clock, or network.
Everything network stays host-side and byte-identical: the authenticated
/v1/report fetch, the AI_GATEWAY_API_KEY / VERCEL_OIDC_TOKEN reads, both stderr
warnings, discovery, `network: true`, and the gate that yields nothing when the
scan has no date range.
Adapter shape adjudication: the draft used createBridgedProvider and needed two
contortions to fit it — a Symbol-keyed dateRange stashed on the shared
SessionSource, and `project` packed into the records payload then unpacked by a
decode wrapper (the bridge passes neither the date range nor the source to the
mapping step). The Symbol injection also mutated the caller's discovered source
object, an observable behavior change. Rejected both; this provider now uses a
plain bespoke adapter like antigravity/kiro, which the bridge's own header
already documents as the escape hatch for providers it was not built to cover.
No change to bridge.ts, parser.ts, pricing-pass.ts, or session-cache.ts.
Emitted key shape is unchanged and gated by key-set assertions: `costUSD` is
present and there is NO `costBasis` key, so parser.ts keeps passing the
gateway's own dollar figure through instead of repricing it.
Validator fixes on top of the draft:
- Dropped the Symbol side-channel and the packed-payload decode wrapper.
- Corrected a false claim in the core observations header: it said the raw `day`
was never emitted, but `day` is spliced verbatim into the timestamp, into
startedAt/endedAt, and into the dedup key. The envelope's date-time constraint
is what actually bounds it; a new smuggling arm pins that a hostile `day`
fails envelope validation.
- Added golden arms for both stderr warnings, for the no-date-range arm making
no fetch at all, and for the provider not mutating the discovered source.
Verification: the strengthened goldens were run against the pre-migration
provider restored in place and pass identically (13/13). Swapping the all-zero
skip and the dedup burn fails tests at both the core and CLI layers.
Core 509 tests, CLI 2470, root 2470 — all green.
Move all five kiro parse arms (legacy .chat, v1 modern execution,
workspace-session, CLI .jsonl, v2 IDE event log) into
@codeburn/core/providers/kiro as pure decode. The host keeps discovery,
every file read, the companion-file reads (CLI .json, v2 session.json),
the workspace-session mtime stat, project attribution, model display
names, and all pricing.
Behaviour is byte-identical to the pre-move provider; the four dedup-key
namespaces (kiro:, kiro:ws-session:, kiro-cli:, kiro-v2:) and the five
per-arm ParsedProviderCall key sets are unchanged.
Preserved verbatim, with tests that discriminate against plausible
refactors:
- A4's asymmetric turnIndex: a zero-output turn does NOT consume a
user_turn_metadatas slot, while the dedup-hit and bad-timestamp skip
arms DO. Every later turn's timestamp and metered credits depend on
this. All three arms are mutation-tested.
- A1's toolSequence key stays present-with-undefined for single-entry
sequences, gated by an Object.keys() assertion (toEqual cannot see it).
- A1's input tokens still derive from the already-truncated 500-char
prompt, unlike every other arm.
- A5's dedup fallback stays `execId || String(calls.length)`, evaluated
at flush time.
- The three-way workspace-session prepare/finish split keeps the mtime
stat behind both content gates rather than hoisting it.
Credits seam: core emits `credits: number` and never prices. The host
multiplies by USD_PER_KIRO_CREDIT and builds costUSD/costBasis, matching
the codebuff precedent.
Validator fixes on top of the migration:
- A1's dedup key regained the raw `data.executionId` field; a fallback to
basename(path) had been introduced, changing keys for chat files with a
missing, empty, or non-string executionId.
- The content-smuggling non-vacuousness guard was vacuous per vector (an
aggregate re-decode compared against itself); it now asserts per-vector
call counts, verified by breaking two fixtures.
- Restored a mangled comment on the load-bearing v2-root derivation and
several explanatory comments dropped during the move.
- Added goldens G1b (raw executionId), G4b (exact credit products) and
G6b (turnIndex advances across a bad-timestamp turn), plus a core test
for the same; all reproduce against the pre-move provider.
PROVIDER_PARSE_VERSIONS['kiro'] and CACHE_VERSION are unchanged. The
companion-file fingerprint blind spot (session-cache.ts) and the dead
project parameter on the old parseChatFile are left as-is.
This completes the Phase 8 tail.
Cursor is the bucket-D stateful multi-store provider: it keeps its bespoke
adapter (no createBridgedProvider) and the host retains every store read.
Tagged-record composition over five query families. The host issues all five
queries in the load-bearing order [1] composerData meta, [2] bubble COUNT,
[3] bubbles (paged or since), [4] agentKv, [5] user messages, preserving the
per-query degradation semantics: [1]/[4]/[5] degrade to empty, [2] to total=0,
and a [3] failure early-returns zero calls while still writing the (empty)
cache and skipping [4]/[5]. decodeCursor() receives the four row sets plus the
host-supplied agentKv timestamp and performs the whole stitched pass — the
pre-pass scan, the agentKv fold, the user-message queue, and all three emit
arms (per-bubble, per-conversation input, stream-only).
The 19-key emitted call shape is a cache-compatibility contract. CURSOR_CACHE_VERSION
stays 6 and PROVIDER_PARSE_VERSIONS['cursor'] is unchanged, so existing
cursor-results.json files on disk are replayed through the new path; adding,
dropping, or undefined-ing any key would poison them. The goldens gate the key
set with Object.keys() on every arm, which is what catches a key present with an
undefined value — toEqual cannot see one, and JSON.stringify drops exactly those
on the way into the cache.
Pricing, bash base-name extraction, display names, project attribution, SQL,
paging, env reads and clock reads all stay host-side; toProviderCall is the
single mapper that re-adds costBasis 'estimated', costIsEstimated true and the
resolved pricing model. No costUSD is ever emitted.
Composer-id / dedup-key envelope finding: composer ids and request ids flow into
sessionId and into the envelope's dedupKeys by design, exactly like every other
provider's machine identifiers, so the content-smuggling block deliberately does
not plant a secret there. Hashing dedup keys uniformly is a schema-wide
follow-up, not a cursor-local change.
Validator fixes on top of the migration:
- relocated the content-smuggling block from cursor-decode.test.ts into
content-smuggling.test.ts, matching all 25 other providers, and added a probe
proving each planted secret actually reaches the field it guards
- G11 now gates the undefined-key cache contract it claimed to (toEqual passed
under an injected extra undefined key; toStrictEqual plus key-set assertions
catch it)
- restored the CODEBURN_CURSOR_MAX_BUBBLES override in the golden's afterEach so
G12 cannot leak the scan budget into cursor-large-db-cap.test.ts
- restored the comment rationales that must travel with their code verbatim
(pos-cursor queue performance note, parseComposerIdFromKey CR/LF signature,
agentKv stream and pending-flush notes, dedup-key history, costIsEstimated)
The 12 goldens were cross-checked against the pre-migration provider by
restoring it in place: they pass identically on both sides. H2 (queue pop before
skip) and H4 (arm B dedup-key burn before the timestamp check) were mutation
tested and are killed by G8 and C6 respectively.
Moves every record-parsing arm into @codeburn/core/providers/antigravity:
the protobuf wire reader, the gen_metadata row decode, the RPC
generatorMetadata decode, the statusline JSONL run-collapse/delta decode,
and model canonicalization. All five emit arms (statusline, cache-hit,
sqlite, RPC, RPC-failure fallback) plus the snapshot write-path keep their
host-side control flow byte-for-byte.
Cache integrity: CACHE_VERSION stays 5 and antigravity-results.json keeps
its shape. The cache is not re-derivable — the RPC-failure fallback arm
replays cached.calls for cascades whose language server is gone — so no
bump, and the cache-write-before-seenKeys-filter ordering is preserved in
both the sqlite and RPC arms.
Pricing stays host-side: normalizePricingModel / PRICING_ALIASES remain in
the CLI, and every arm now emits through one shared toProviderCall carrying
costBasis 'estimated' plus pricingModel, and no costUSD.
parseStatusLinePayload's wall clock becomes an injected `at` parameter; the
host passes new Date().toISOString() at each call site, once per payload.
Validator fixes on top of the migration:
- content-smuggling: the hostile statusline record used the hook-payload
shape, so the decoder dropped it and the cwd/session_id assertions proved
nothing; corrected to the recorded-event shape, with a call-count guard.
- goldens: G14 maps through the exported host toProviderCall instead of a
private copy, and a new G15 pins the emit loop's turnIndex-before-seenKeys
and previousSnapshotUsage-before-skip ordering, which no golden could
previously detect.
- core: removed an unused type import and a dead CANONICAL_TOOL_NAME const;
restored the statusline reasoningTokens rationale comment.
Moves the Mistral Vibe rich decode into @codeburn/core behind the bridge:
discovery, file I/O and pricing stay CLI-side, the pure record decode
(including the even allocation of the session dollar figure and the session
token totals across assistant messages) moves byte-exact into core.
Seam adjudication: the draft brief said the `session_cost > 0` gate should
move into core. It cannot — that gate is the first arm of a three-arm cost
resolution whose last arm consults the generic price table, which is
forbidden in core. The whole resolution therefore stays host-side in
`calculateSessionCost` and core receives a pre-resolved `sessionCost`;
core keeps only the pure allocation arithmetic (allocateInteger /
allocateCost, allocationIndex / assistantOrdinal), unchanged in rounding,
remainder distribution and float operation order. The move is
observationally equivalent: the original resolved the cost once per session
during parse, with no per-call state.
Validator fixes on the draft:
- core dropped the `metadata.session_id || basename(sessionDir)` fallback,
emitting sessionId '' (and colliding dedup keys `mistral-vibe::<msg>`)
for sessions whose meta.json omits session_id. The host now passes
`sessionIdFallback`.
- `toProviderCall` set `turnId: undefined` unconditionally, adding a key
the original omits entirely on the no-assistant session-level arm.
- the moved comment block described a seam that no longer exists; adjusted
minimally to describe where the resolution actually lives.
Parity was verified arm-by-arm against the pre-migration decode checked out
in place over a 28-session adversarial fixture matrix (strict equality plus
per-call key-presence), and the adversarial allocation arms (7 over 3,
1 over 3, terminal zero-cost, idx-N keys, basename fallback, float-op order)
are promoted into the bridge and core decode tests with values captured
from the original.
Unifies the three-file OpenCode decode family — session-message.ts,
sqlite-session-parser.ts, and opencode-file-parser.ts — into a single core
module, packages/core/src/providers/opencode-session, exposing a two-arm
tagged envelope (kind: 'sqlite' | 'file') over one shared assistant-turn
builder. The SQLite driver, both WITH RECURSIVE session_tree queries,
blobToText, discovery, and extractBashCommands all stay CLI-side.
session-message.ts shrinks to the discovery-side sanitize helper;
sqlite-session-parser.ts and opencode-file-parser.ts keep their SQL and
directory walks and expose readRecords adapters. No decode logic remains
CLI-side in any of the three.
kilo-code is now fully converted: both arms — cline task dirs via the batch-S1
core vscode-cline module and SQLite via the new module — run through core
decodes from one bridged provider, and the thin createClineParser adapter S1
retained is deleted.
The CODEBURN_VERBOSE zero-yield notice is reconstructed host-side by
zed-style decode wrappers, byte-identical for both the OpenCode and KiloCode
labels, with parseFailCount/roleSkipCount derived from the decode's
malformed-json / unknown-shape diagnostics. The session-level SQLite fallback
is pre-fetched onto the envelope instead of queried lazily — output-neutral,
changing only I/O volume — and keeps its distinct key shape, emitting no
skills/subagentTypes keys.
Validator fixes on top of the migration:
- opencode's file arm emitted a spurious SQLite verbose stderr line, reporting
a previous SQLite source's session id and message count, because the
readRecords -> decode count handoff was never cleared when switching arms.
The handoff is now reset on the file path. Pinned by a new regression test.
- The S8 and F9 dedup goldens did not actually pin add-after-build: the SQLite
fixture used two different message ids, and both file fixtures were degenerate
(the CLI one wrote the same filename twice and silently overwrote; the core
one shared a text part across both messages, so the "skipped" message built
successfully). All four now fail if seenKeys.add is hoisted above the build.
- Added key-presence gates to the goldens, since toEqual accepts a
present-but-undefined key: the session-level fallback must omit
skills/subagentTypes, a message-arm call per arm must carry them, and
fallbackCostUSD must be present for cost 0 and absent for an absent cost.
Goldens were captured pre-migration and re-verified against the original tree
by restoring 38172892's sources in place; all 36 pass on both sides.
Pre-existing issues moved verbatim and left alone: the bare-Record tool map
with a truthy hit check in normalizeToolName (prototype leak on names like
'constructor'), opencode.ts's duplicated display-only tool map, and the dead
inner role guard in the SQLite arm. Every new lookup introduced by the
unification is a Map.
Cuts the decode shared by the vscode-cline family out of the CLI and into
@codeburn/core/providers/vscode-cline. This is the first shared-module batch:
one core decoder now backs four provider identities, with the provider name
sourced from context.providerId and the per-consumer fallback model passed as
a decode knob.
- cline, ibm-bob and roo-code become createBridgedProvider specs. Discovery,
file I/O and pricing stay CLI-side; vscode-cline-parser.ts keeps the
discovery helpers and gains readClineRecords (I/O adapter) plus
toClineProviderCall (rich -> ParsedProviderCall, where cost re-enters).
- kilo-code.ts is a zero-line diff. Its SQLite arm does not move until batch
S2, so createClineParser is retained but reimplemented as a thin adapter
over the core decode — I/O and mapping only, no decode logic CLI-side.
- Behavior is preserved verbatim, including the dedup burn-through: the key
is added to seenKeys before the zero-token skip, so a skipped entry burns
its key. Also preserved: the unguarded new Date(entry.ts), the cost != null
(not !== undefined) measured-cost test, and project/projectPath written
unconditionally so key presence is unchanged.
Validator pass over the migration:
- Added a permanent key-presence gate to the shared-bridge goldens. toEqual
cannot distinguish an absent key from a present-but-undefined one, which
hid exactly this class of regression in earlier batches; the goldens now
assert costUSD is absent when estimated and that project/projectPath are
always present.
- Turned the kilo-code .db: dispatch case from a negative-only assertion into
a positive one: it now captures and asserts the SQLite arm's open-failure
line, proving dispatch reached that arm rather than merely yielding nothing
(and keeping the line out of test output).
Parity was checked against the pre-migration decoder as oracle over 25 task
fixtures per provider, comparing values, key presence, key order, JSON
serialization and the resulting seenKeys sets across two dedup passes — exact
match for all four consumers and for the retained adapter.
Bridged via a tagged envelope record across the four parse paths
(jsonl / chatsession / jetbrains / otel): readRecords shapes one envelope
per source kind and a single decodeCopilot dispatches on envelope.kind.
The sqlite driver, the three OTel queries, blob reads, git-root probing,
extractBashCommands / extractOtelBashCommands, and the sanctioned
shutdown-site calculateCost residual all stay host-side. The residual
keeps its exact argument order (model, input, 0, cacheWrite, cacheRead, 0),
emits costUSD + costIsEstimated: false with no costBasis key, and carries
its explanatory comment unchanged.
Validator fixes over the draft:
- collectJetBrainsRepoDirCandidates used a [\|"] character class instead
of the original's (?:\\|") alternation, so a file:// path containing a
pipe was truncated, its dir never resolved, and the JetBrains project
fell back to the generic bucket. Restored the alternation.
- the otel arm dropped the subagentTypes key when undefined; the original
always emits it. Restored so key presence matches byte for byte.
- otel chat-span attributes were looked up with a linear Array.find per
chat span; replaced with a per-span-id map built in the same pass.
Verified by a differential harness running the pre-migration provider
against the migrated one over 27 scenarios (all eight goldens plus
edge cases), comparing calls, seenKeys, and key presence. The harness is
not committed; the scenarios it proved are now goldens G9-G17.
Move the pure record decode for five sqlite-backed providers into
@codeburn/core, keeping discovery, sqlite I/O, pricing, and bash
base-name extraction CLI-side behind the dual-registry bridge.
sqlite variant: the host opens the database, runs the SQL, textualizes
BLOBs (warp stylized_command, quickdesk content/tool_names), probes
sqlite_master table variants (quickdesk), and re-throws SQLITE_BUSY so a
transient lock on a live database retries instead of being cached as an
empty result (hermes). The core decoders receive already-normalized rows.
Validator fixes over the drafted migration (original behavior is the
authority in every case):
- warp: restored the `run_command` tool-name check as an identity
comparison. The draft had rewritten it as a bare Record lookup, so
'constructor'/'toString'/'__proto__' resolved to inherited Object
members instead of passing through. Added a regression test.
- warp: restored `safeNumber` to the host's semantics (positive finite
numbers only). The draft's rewrite accepted numeric strings and
negative values, changing token budgets and dominant-model selection
for untrusted conversation_data.
- warp: `estimateTokensFromChars` now mirrors the host helper exactly.
- devin: model DISPLAY names move back CLI-side. The draft shipped a
7-line regex standing in for the host's `getShortModelName`, which
mis-rendered every non-Claude model id. The decoder now emits the raw
generation_model / model_name ids and the CLI formats them.
- devin: dropped the added `costBasis: 'measured'`; the pre-migration
call carried `costUSD` with no basis marker. Golden corrected.
- quickdesk: fixed an inverted source-variant predicate that routed
`sourceId: 'metrics'` on a sessions.db path (and `sourceId:
'sessions-db'` on a metrics path) to the wrong parser.
- cursor-agent: the "unrecognized transcript format" warning is now
re-emitted from the real decode result via a decode wrapper. The draft
re-derived `recognized` host-side as `hasUser && hasAssistant`, which
silently dropped the warning whenever a jsonl transcript had both roles
but produced no turns.
- cursor-agent: restored the per-conversation summary cache, so the
attribution database is opened once per conversation id, not per source.
- cursor-agent/devin: the session id is derived once, host-side, and
passed through, removing a second derivation that could disagree with
the host's on Windows-style paths.
- hermes: `browser_vision` maps to Vision, matching the original table.
Goldens re-verified by running each bridge fixture through the
pre-migration provider implementation: all five reproduce field-for-field.
Category B (sqlite) variant of the bridge migration: the sqlite driver and every
SQL query stay CLI-side. Each provider's `readRecords` opens the database, runs
the same queries as before, and hands the resulting rows (blob and all) to a
pure core decoder; `toProviderCall` maps the rich, cost-free decode back onto
ParsedProviderCall, where cost re-enters via the parser.ts pricing pass.
Per provider:
- crush: session row + dominant-model query -> one combined record. Crush stores
cost in dollars, so a row with cost > 0 carries `measuredCostUSD` (costBasis
'measured'); a zero-cost row falls back to token estimation, arm order intact.
- zcode: model_usage + tool_usage row sets -> one composite record. Each turn's
tools still attach to the first non-skipped usage row of that turn only.
- zed: threads rows handed over compressed; zstd decompression, JSON parsing and
per-request/cumulative-remainder accounting are pure. The Node >= 22.15 zstd
capability check stays host-side.
- forge: conversation row handed over with `context` still serialized; JSON
parsing and per-message decode are pure. Bash base-name extraction (and its
strip-ansi dependency) stays CLI-side over the decoder's raw command strings.
- goose: session + assistant tool-message + first-user-message rows, BLOB
columns pre-converted to text host-side, bundled into one composite record.
Validator fixes (original behavior is the authority):
- forge: the draft replaced the pre-migration `mapToolName` switch with an
object-literal lookup. Tool names come straight from conversation JSON, so
names colliding with Object.prototype members ("constructor", "toString",
"__proto__", "hasOwnProperty") resolved to inherited Functions / the prototype
object and were pushed into `tools` as non-strings instead of falling through
to the identity default. Restored the switch and pinned the arm in the fixture.
- zed: the draft routed the "skipped N unreadable Zed threads" notice into
record diagnostics, which the bridge discards, silently dropping a warning the
pre-migration decode printed. Re-emitted host-side from the diagnostics count
and pinned with a stderr assertion.
- Fixture coverage extended for the arms that were regression-blind: forge's
prototype-named tool calls, zed's aggregate stderr line, and goose's
single-turn `toolSequence` omission plus the unparseable-timestamp fallback.
Parity was verified independently of the bridge tests with a git-show harness
that runs the same fixtures through the pre-migration provider files and asserts
field-for-field equality, including the extra arms above.
The seeded 'today' session used a fixed noon timestamp; under the suite's
TZ=UTC pin any run between 00:00 and ~12:30 UTC placed it in the future,
so the provider-filtered durable path (today-range ends at now) dropped it
and the parity assertion failed. Seed timestamps proportionally between
start-of-today and now so they are always today and always in the past.
All five providers wired through createBridgedProvider with decode in
packages/core only. Deep validation corrected the initial drafts: zerostack
dedup-key/sessionId/project regressions, droid and mux crude bash-extraction
reimplementations (host now runs the real extractBashCommands on raw
strings), lingtai-tui dedup-key source, open-design dropped project. Goldens
are byte-identical toEqual pins anchored to the pre-migration decode.
Move the pure record decode for grok, kimi, and codewhale into
@codeburn/core and rewire the CLI providers through createBridgedProvider,
keeping discovery, file I/O, config resolution, and pricing host-side.
Validation fixes (legacy-parity, caught arm-by-arm against the pre-lift
decode):
- grok/codewhale: the host toProviderCall wrongly wrapped bashCommands in a
Set. The legacy decode deduped nothing (flat push of extracted base
commands), so per-command breakdown counts drifted. Drop the Set.
- kimi: the core decode dropped the legacy per-turn tool Set, so repeated
tools in a turn no longer collapsed. Restore [...new Set(tools)].
- codewhale: remove an unused codeWhaleToolNameMap import.
Parity fixtures now exercise the dedup arms (repeated tools/commands) and the
goldens are pinned to the ORIGINAL in-CLI decode output, captured by running
the pre-migration providers over the same fixtures.
Redesign the three token-waste detectors as pure @codeburn/core detectors that
consume an ObservationEnvelope of fingerprinted resource refs (never raw paths):
- Envelope schema 0.2.0: CallObservation gains optional resourceReads /
resourceEdits (ResourceRef = {resourceId 16-hex, resourceClass}). claude/codex
toObservations fingerprint toolSequence file paths into these; raw paths never
cross the boundary. Keep observation-0.1.0.json frozen; emit 0.2.0.
- Host privacy key (D1): sync keystore under the codeburn config dir, random
32-byte key generated on first use, stable across runs, never emitted.
- core/detectors: junk-reads, duplicate-reads, context-bloat — pure, zero
fs/env, each emitting Finding[] with confidence(basis), machine-readable
evidence, algorithmVersion.
- optimize.ts delegates the three to core, mapping Finding -> WasteFinding;
display strings, fix payloads and trend stay host-derived.
Numbers parity: all optimize tests pass unchanged; frozen-corpus optimize JSON
(3552 sessions) is byte-identical PRE vs POST.
Carve the Codex decoder into @codeburn/core/providers/codex as a pure function
over supplied rollout records with EXPLICIT, JSON-serializable state
(CodexDecodeState): the running cumulative token counters, fork-replay cutoff,
mid-turn accumulators, id/turn bookkeeping, and the cross-file dedup memory
(seenKeys). Decoding a corpus in one pass equals decoding it in any number of
passes when the state threads between them (the resume invariant).
Cost leaves the decoder: the rich calls are cost-free and the CLI prices them
through the established estimated-cost seam (costBasis: 'estimated'), retiring
both Phase-0 residual calculateCost sites. The CLI codex provider is now
discovery + file streaming + cache I/O + pricing.
codex-results cache bumped to v8 (one deliberate bump): it persists the decoder
end-state blob + a byte offset + host-priced calls, so a grown rollout resumes
from the stored state and decodes only the appended bytes. Lossless — rollout
files are durable, so the one-time re-derive rebuilds identical data.
Adds a codex path to the content-smuggling guardrail and the import-smoke
subpath set. Byte-parity verified against a frozen 826-file corpus (codex-only
and all-providers, rel_tol 1e-9); perf within ±1.5% cold / ±0.5% warm.
Move the pure Claude JSONL decode out of packages/cli/src/parser.ts into
packages/core/src/providers/claude/: the large-line buffer scanner, line
parsing, compaction, per-call/turn extraction, rich-capture meta collectors,
turn grouping, and streaming dedup. These are pure over supplied records/lines
(no fs/env/clock/pricing) and carry content in-memory only at this host-facing
layer.
Add the minimizing transform toObservations(richDecode, {privacyKey,...}) that
maps the rich decode into the strict Phase-2 observation envelope: only
fingerprints, enums, numbers, timestamps, dedup keys, and canonical tool names
cross the boundary. Extend the content-smuggling guardrail with a real claude
decode -> toObservations case planting secrets in every captured free-text
field and asserting the serialized envelope surfaces none.
The CLI parser is now the adapter: it keeps all I/O, discovery, incremental
append, cache shapes, classification, and host-side pricing. parseApiCall /
parseAdvisorCalls / groupIntoTurns become thin wrappers that price the cost-free
core decode (calculateCost + local-model savings) and split raw bash commands
(strip-ansi stays out of the zod-only core), then map into ParsedApiCall /
ParsedTurn. Shared record + tool-vocab definitions move to core; the CLI
re-exports them so every existing import path is unchanged.
No parse-version bump; CachedCall/CachedTurn shapes and values byte-identical.
Core runtime deps stay zod-only.