Commit graph

1244 commits

Author SHA1 Message Date
iamtoruk
08922bdc74 Merge branch 'feat/core-extraction' into fix/port-parser
Conflict: packages/cli/src/daily-cache.ts. #926 landed
DAILY_CACHE_VERSION/MIN_SUPPORTED_VERSION at 23 for the structural codex
discovery re-derivation; this branch had claimed 17 for the midnight-straddle
re-derivation. Kept #926's rationale as the foundation and took 24, the next
free number, with the straddle reason appended: per-call day slicing changes
which day a historical straddling call lands on, so it needs its own one-time
re-derivation and cannot ride on a 23 cache written by a #926 binary.
2026-08-21 09:05:52 -07:00
Resham Joshi
41b1b3a033
Merge pull request #926 from ozymandiashh/fix/port-codex
fix(codex): validate rollouts structurally, guard the parse path
2026-08-21 08:55:45 -07:00
iamtoruk
34d50e1185 fix(cache): bump DAILY_CACHE_VERSION to 23, not 16
Base has moved since this branch opened: main shipped 17 in v0.9.20 and
now sits at 20, with 21 (#946) and 22 (#1056) claimed on the main-side
pipeline. Bumping to only 16 would let a main-built cache pass the
version check unchanged, so the widened Codex discovery re-derivation
this PR depends on would never fire for those users. Take 23 to stay
above every value a real cache file can carry on either line of
history, and update the test literal and MIN_SUPPORTED_VERSION to
match.

Also documents, next to CODEX_CACHE_VERSION, why that constant is
deliberately not bumped alongside it: the guarded record shapes were
measured at 0 occurrences across 136k real events, so forcing a full
re-parse of multi-GB rollout corpora for them is a bad trade. The
daily-cache bump alone already propagates the discovery widening.
2026-08-21 08:51:27 -07:00
iamtoruk
dc630a4c94 Merge remote-tracking branch 'origin/feat/core-extraction' into pr926-fix 2026-08-21 08:47:57 -07:00
Resham Joshi
e604f58f44
Merge pull request #925 from ozymandiashh/fix/junk-reads
fix(optimize): one junk vocabulary for the count, the trend and the display
2026-08-21 08:41:52 -07:00
Resham Joshi
aba8ed547d
Merge pull request #924 from ozymandiashh/fix/core-package-hygiene
build(core): stop shipping sourcemaps, expose schemas, tighten the barrel
2026-08-21 08:37:42 -07:00
Resham Joshi
36c3b502ba
Merge pull request #923 from ozymandiashh/fix/ci-run-cli-suite
ci: run the CLI suite
2026-08-21 08:36:40 -07:00
iamtoruk
734e53b6b6 merge: take base's cli test scripts (vitest run + lock quarantine supersede the watch-mode fix) 2026-08-21 08:33:46 -07:00
Resham Joshi
7c4a934eae
Merge pull request #1066 from getagentseal/fix/cli-vitest-ci-timeouts
test(cli): restore CI timeout headroom + lock-suite quarantine lost in packages/cli split
2026-08-21 08:32:50 -07:00
iamtoruk
a23d0526a0 test(cli): restore CI timeout headroom and lock-suite quarantine lost in packages/cli split
The packages/cli move (dc97ab49, Jul 26) forked vitest.config.ts and the test
script off main before three since-merged CI-stability fixes existed there,
and #921's later "port upstream fixes" pass (f3f5814b) missed all three since
it only cross-referenced one specific main commit:

- 30037b33: global vitest testTimeout raised from the 5s default to 30s
  (real-I/O tests exceed 5s under CI runner load)
- 8c758ddf: file-level 30s timeout on cli-status-menubar.test.ts (spawns the
  real CLI; individual cases observed needing 6-8s on a shared 2-core runner)
- 4ca2d482: cache-refresh-lock tests excluded from the parallel `test` run and
  quarantined behind a serial `test:locks` script (they contend for the fork
  pool with the spawned-subprocess tests and starve everyone under load)

None of this showed up until #923 wired `npm test --workspace=codeburn` into
CI for the first time, which then failed deterministically at the vitest
default 5000ms on exactly the three test categories these fixes cover.

Ports all three, matching main's current config/script.
2026-08-21 08:28:55 -07:00
Resham Joshi
9ba327c3e2
Merge branch 'feat/core-extraction' into fix/ci-run-cli-suite 2026-08-21 08:14:28 -07:00
Resham Joshi
35209cadad
Merge pull request #1065 from getagentseal/fix/branch-cli-suite-red
fix(cli): hermetic DeepSeek pricing + platform-aware proxy-path test, unblocks #923 cli CI job
2026-08-21 08:14:24 -07:00
Resham Joshi
1412555f3c
Merge pull request #922 from ozymandiashh/fix/semgrep-guard-fires
ci: make the bracket-assign guard actually match its targets
2026-08-21 08:13:56 -07:00
iamtoruk
5ea455ecbe test(cli): price off the bundled snapshot; fix platform-dependent proxy-path assertion
Two pre-existing CLI-suite failures blocked #923's new cli CI job on Linux:

- tests/models.test.ts DeepSeek v4 pricing (x3): loadPricing() fetched the
  live LiteLLM table during tests, and live data wins over the bundled
  snapshot, so the DeepSeek v4 assertions went red when upstream repriced.
  Ports main's fix (4866332b): CODEBURN_PRICING_SNAPSHOT_ONLY skips the fetch
  and prices purely off the bundled snapshot; env-isolation.ts sets it for
  the whole suite.

- tests/parser-proxy-pricing.test.ts case-insensitive isProxiedPath: the
  branch's isProxiedPath/normalizeProxyPath is byte-identical to main's
  (case folding is deliberately darwin/win32-only, per the function's own
  comment). The test hardcoded the macOS-only expectation, so it fails by
  design on Linux CI. Main hit and fixed the same thing (bcf11552); this
  ports that fix verbatim: assert the platform-correct behavior instead of
  a hardcoded `true`.
2026-08-21 08:11:34 -07:00
Resham Joshi
f4d44f5a61
Merge branch 'feat/core-extraction' into fix/ci-run-cli-suite 2026-08-21 08:03:52 -07:00
Resham Joshi
8cd7c2cf69
Merge pull request #921 from ozymandiashh/fix/port-test-fixes
test: port upstream fixture and retry fixes (2a4b8f2)
2026-08-21 08:03:34 -07:00
ozymandiashh
556b59d7d8 fix(parser): keep both halves of a midnight-straddling turn, and stop --provider leaking claude
Ports upstream fixes for two defects this branch never received.

**A turn that spans local midnight was filtered as a unit.** The range and day
filter keyed on the turn's FIRST call, so every later call that landed in the
requested day was discarded along with it. A long autonomous Codex run, or
Claude work that crossed midnight, made `codeburn today` under-report until the
turn ended, and multi-day totals attributed the whole turn to its start day.

Range and day filters now slice inside the turn: only the calls inside the
requested window survive, and the turn's timestamp re-anchors to its first
surviving call so turn-anchored rollups — category, editTurns, oneShotTurns,
the daily cache — land on the day the retained calls actually happened. Cost,
calls, savings and tokens bucket under each call's own local day, so day N plus
day N+1 conserves the whole-range total. A sliced turn is still classified from
its FULL call list, because category, hasEdits and retries describe the whole
exchange rather than the surviving slice — matching the Claude path.

**The inverse leak hit provider-filtered runs.** `--provider <other>` still
entered the claude scan, whose orphan pass read the entire cached claude
section, treated every cached PR-bearing transcript as no-longer-discovered and
re-injected it. By Project and By Model listed Anthropic spend under
`--provider cursor` while the headline showed cursor alone. The scan is now
guarded by an explicit in-scope check — deliberately not a directory-count
check, so when claude IS in scope but every transcript has been pruned,
PR-attributed orphans still survive.

**The daily cache is bumped to 17**, because leaving it at 15 would double-count
the post-midnight half of a straddling turn for an upgrading user. A v15
rollup finalized by the pre-fix binary holds the WHOLE turn on its start day,
and the new slicing then also puts the post-midnight call on the next day —
the same cost twice, in a cache whose ten-year retention never ages it out.
The bump mints a fresh filename; adoption marks the merged result incomplete,
so the next hydration re-derives every day whose sources survive under
per-call bucketing and carries forward only what it cannot re-derive.
16 is deliberately skipped: main already spent it on the codex
structural-discovery fix (eece4cf), so claiming 16 here would load a
main-built v16 cache — which holds only the codex fix — as current and
complete and the invalidation would never fire.

Blast radius: daily-history rows, the JSON daily fallback and range-query
session totals change shape for straddling days, as call-derived values move
to the call's own day — the intended correction, asserted by this commit's
tests. The session-cache FORMAT is unchanged.

One caveat worth stating rather than leaving to be found. The multi-day
all-provider By Activity rollup still derives today's slice from the unsliced
range parse, so a straddling turn's category cost stays anchored on its start
day and categories can sum below the headline on that surface; upstream has a
follow-up for it.
2026-08-05 17:15:30 +03:00
ozymandiashh
62bfa16b03 fix(codex): validate rollouts structurally, guard the parse path
Ports two upstream fixes this branch never received (eece4cf, 4ff3497).

**Discovery gated on a client identity string.** `isValidCodexSession`
required `payload.originator` to start with "codex". But `originator` is a
free-form client identity, not a format marker: anything driving
`codex app-server` writes structurally identical rollouts with its own value —
"t3code_desktop", "JetBrains.IntelliJ IDEA", whatever ships next. Every
third-party frontend was silently dropped, and each one needed a new allowlist
entry (#626, #873). Validation is now structural.

Be clear about what that gate was and was not. It was never a security
boundary — anyone able to write into the sessions directory could write
`"originator":"codex-cli"` and pass it. It was accidental integrity
protection, and removing it widens what gets ingested from those directories
to any well-formed `session_meta` line. The trust boundary is unchanged: write
access to the Codex home, which is itself configurable via `CODEX_HOME`. A
crafted rollout can inflate cost or impersonate a project path, exactly as it
could before by spelling the originator correctly.

**Non-string fields on the parse path.** A garbage `timestamp` threw
RangeError out of `toISOString()` and zeroed the session. Guarding only that
one would have been the smaller half of the problem: the timestamps that reach
emitted calls were unguarded too, and a numeric one produces `NaN-NaN-NaN` day
buckets that the daily cache then keeps for ten years — silent, and persistent.
Token counts could go NaN and slip past a `=== 0` check into reported cost.
`session_id` and `forked_from_id` could coerce an object into a dedup key.
All of those are now guarded; the fields still read through a raw cast are
listed nowhere, because there are none left on this path.

**The cache is bumped to 16.** Rollouts rejected before they were ever parsed
now contribute usage, and nothing downstream can notice: the aggregator serves
every day before today from this cache, with ten-year retention, so an
upgrading user would keep pre-fix history forever while today disagreed with it.

Reviewers split on this bump and both arguments are worth having. For it: cache
versions are per-branch lineage, and landing unbumped leaves a user on this
branch with stale history that merging main cannot repair retroactively.
Against: most users are unaffected and pay a full re-derivation for nothing.

The number stays 16, and it means the same thing it means on main: main's 16
was set by eece4cf, the structural-discovery fix this PR ports — same change,
same bump, no collision to resolve. The sibling PRs in this batch that need
their own invalidation are moving to 17 instead of colliding on 16.
2026-08-05 17:14:42 +03:00
ozymandiashh
913d0bd019 test: reap worker children and move fixture off window boundaries
Second-reviewer findings on top of the port (f3f5814). Test-only.

cache-refresh-lock-process: afterEach removed the temp roots but never
killed the spawned workers, which block on their barrier files
indefinitely. The success path reaps them (waitForExit after each run),
but a failed assertion or waitFor timeout leaves the blocked winner
behind; with the global retry: 2, every attempt then spawns a fresh pair
on top of the leaked ones, so they accumulate. Verified: a forced
assertion failure left 3 stray worker processes after the run before
this fix, and 0 after, with the kill path running between retry
attempts. afterEach now kills every child it spawned and waits for it to
actually die (SIGTERM, then SIGKILL after 1s), is robust to children
that already exited (exitCode !== null short-circuits), and detaches
waitForExit listeners first so a SIGTERM cannot surface an unhandled
rejection on top of the real failure.

Upstream 2a4b8f2 has the identical leaky afterEach, so this is not a
regression the port introduces; it is a latent leak the retry makes
reachable.

Same file: worker() resolved its fixture relative to process.cwd(), so
`vitest run --root packages/cli` from the repo root spawned children at
a nonexistent tests/fixtures path and every one died on ENOENT before
touching a barrier. Resolve the fixture relative to this file
(import.meta.dirname) so the suite works from any invocation cwd.

parser.test.ts createJsonlSession: the fixture dated events at exactly
now minus two days. That is safely inside the 90-day retention window
but sits exactly ON the 48h 'recent' cutoff in optimize.ts
(RECENT_WINDOW_MS — recent iff ts >= now-48h), so any clock skew between
the helper and a consumer flips the classification, and a current-month
date range (cli-date.ts `month`, which starts at local midnight of the
1st) would exclude it on the 1st-2nd. Moved the offset to 6h, clamped
into the current month. Six hours keeps the events unambiguously recent
(42h clear of the cutoff) and inside retention with ~89 days of
headroom; the clamp keeps them inside any current-month range in any
timezone (it uses the same local-calendar construction as cli-date.ts).
Both existing cases that use this helper, (a) and (f), pass with the new
offset.
2026-08-05 04:22:49 +03:00
ozymandiashh
4a971ee8a7 ci: run the CLI suite
The repo moved to npm workspaces and CI followed the core package: there is a
job for typecheck, test, build, verify-dist and pack across three node
versions. Nothing runs the CLI's suite — roughly 2470 tests, including the 29
provider bridge suites that byte-compare against goldens captured before the
extraction. Issue #809 says every phase PR passes a byte-identical parity gate;
until now nothing enforced it.

Two things stood in the way. The CLI's test script was `vitest`, which is watch
mode — in CI that hangs a runner instead of failing. And the root test script
forwarded only to the CLI workspace, so core's guardrail suite never ran from
the command a contributor reaches for, while the CLI half of it could not run
from a clean checkout at all: core's exports resolve to dist, which is
gitignored.

The root script now builds core before running either suite. That costs a
build on every local run, which is a real annoyance, but the alternative is a
script that only works if you happen to have built core earlier.

The CLI job runs the suite on two node lines: the engines floor (22.13.x) and
24.x. The second leg is not matrix sprawl — the zed bridge parity suite seeds
its fixture with zlib's zstd, which only landed in 22.15, so a floor-only job
would silently skip the byte-compare gate this job exists to run and still go
green. The floor leg keeps the >=22.13 promise enforced; the 24.x leg makes
the parity gate actually execute. The job carries a 15-minute timeout —
measured wall time for the whole suite is ~2 minutes — so a hung run cannot
burn a runner for the default six hours, and the workspace-versions check runs
before npm ci: it only reads the three manifests and the lockfile, so a drift
fails in a second instead of after a full install.
2026-08-05 04:09:46 +03:00
ozymandiashh
7be5a69c87 fix(optimize): one junk vocabulary for the count, the trend and the display
detectJunkReads took its count from core's detector and re-derived its display
and trend from the host's own JUNK_DIRS regex. The two lists disagreed: core
classifies `vendor`, `site-packages`, `out` and `target` as junk; the host's
did not.

Core was already counting reads under those segments — the count is not what
was wrong. The host's derivation was. In a repo whose junk reads all live under
one of them — Go and PHP vendor, Python site-packages, Rust target, Java or
Next out — the host loop matched nothing, so `recentJunkReads` stayed at zero.
Where the window also had recent activity, `computeTrend` read that as fixed
and returned 'resolved', and the finding was dropped. Whole ecosystems never
saw it. In mixed repos it survived but rendered incoherently: the explanation
quoted core's total while the directory list and the CLAUDE.md suggestion came
from the narrower host counts, so the numbers did not add up and the suggested
directories omitted the one actually causing the waste.

Core now exports `junkSegmentOf`, which returns the exact segment that made a
path junk. The host deletes JUNK_DIRS and JUNK_PATTERN outright and asks core
in both loops — junk-reads and duplicate-reads, which had the same split. The
host still names the directory for the payload, because a class alone cannot
render `vendor/ (5x)`; it just no longer decides what junk means.

Inside core, the precedence rule (dependency > build > vcs) lived in the order
of three loops, and adding junkSegmentOf duplicated them. Both functions now
consult one private helper, so the two cannot drift — a second copy of one rule
is what caused this bug in the first place.

Two display changes fall out. A path under two junk segments of the same table
now names the first in path order rather than the first in array order, so
`/x/build/dist/y` reports `build` where it used to report `dist`; the count is
unchanged. Terminal junk directories and Windows-style paths are now matched,
which the old slash-delimited regex missed — those align the host with what
core was already counting.

One robustness note: the old `JUNK_PATTERN.test()` coerced a truthy non-string
`file_path`, while `junkSegmentOf` would throw on one. The type is narrowed at
the point of use so the new path cannot throw where the old one could not.
2026-08-05 04:05:22 +03:00
ozymandiashh
5cf58b1154 build(core): stop shipping sourcemaps, expose schemas, tighten the barrel
Three defects in what `@codeburn/core` publishes. 0.9.20 is already on npm, so
an external consumer meets all three today.

**Sourcemaps.** tsup emitted them and `files` ships `dist`, so 41 maps rode
along — about 1.2 MB against 420 kB of JavaScript. They are self-contained
(esbuild embeds sourcesContent), so this is a weight argument, not a broken
one: what is lost is stepping into `@codeburn/core` frames in an app and
symbolicating consumer stack traces. `dist` is unminified, so that loss is
small.

**schemas/.** `files` shipped the directory but the exports map had no entry
for it, and an exports map blocks whatever it does not list — so the published
JSON Schemas, whose whole point is letting non-TypeScript consumers validate
observations and findings, could not be resolved at all.

**The barrel.** `src/index.ts` omitted detectors even though `./detectors` is a
documented public subpath, so the detector surface was invisible to anyone
importing the package root. Added as a named list rather than `export *`: the
detectors module re-exports its own helpers, and star-exporting would have made
`clamp01`, `READ_TOOL_NAMES` and `AVG_TOKENS_PER_READ` part of the root API,
where renaming one or tuning a constant becomes a visible break after 1.0.
They remain reachable through `./detectors`.

Two support changes fall out. `verify-dist` learned to resolve wildcard export
patterns — including the case where the literal prefix has no separator, which
previously scanned a truncated directory and reported a misleading error — and
walks recursively including dotfiles, because Node's `*` spans `/` and accepts
them, so a shallower check would leave a future `schemas/v2/` importable but
unverified.

A new test resolves a concrete schema through the exports map in a spawned
child and loads it as a JSON module. It lives outside import-smoke on purpose:
that suite blocks I/O to prove import-time purity, and reading a JSON module
needs exactly the I/O it forbids.
2026-08-05 03:49:47 +03:00
ozymandiashh
c71f2cd667 ci: make the bracket-assign guard actually match its targets
The prototype-pollution rule has never fired. Its `paths.include` named the
pre-workspace layout (`/src/providers/*.ts`, `/src/parser.ts`) while the job
scans `packages/cli/src/...`, so the rule selected no files and the step was
green unconditionally.

Verified before and after: with the old rule and the old scan targets a
planted bracket-assign under packages/cli/src/providers/ reports 0 findings;
with this change the same violation is reported at the right file and line,
and the real tree scans 184 files clean.

Also brings packages/core/src/providers/** into scope. Provider decoding moved
there in the extraction, so that is where records from untrusted session logs
are now turned into maps — precisely what the rule exists to guard.

Two guard-rails so the step cannot silently go vacuous again:

- The step now fails when the scan selected no files. semgrep's --json
  `paths.scanned` reflects what the rule actually selected, not the raw scan
  targets (verified: with the include paths broken but the targets intact,
  scanned drops to 0 while the step still exits 0). A low-water mark was
  considered and rejected as brittle: the provider count is a moving target
  by design and CI installs semgrep unpinned, so a magic number would need
  constant bumping and would decay back into vacuity.

- The CLI providers glob is now recursive (`**/*.ts`) like core's. The
  directory is flat today, but a future nested provider directory would be
  scanned by CI and then silently excluded by the rule; a planted violation
  under providers/nested/ is now reported. The narrow whitelist is
  deliberate and unchanged in spirit: unrelated maps elsewhere in the tree
  stay out of scope.
2026-08-05 03:43:25 +03:00
ozymandiashh
f3f5814b84 test: port upstream fixture and retry fixes (2a4b8f2)
The CLI suite is red on this branch for two reasons that upstream already
fixed and that never made it here.

parser.test.ts (a)/(f): createJsonlSession stamped fixture events at a fixed
2026-05-01. Durable providers age out at 90 days, so on 2026-07-30 the fixture
silently pruned to zero and both cases began failing with `expected +0 to be
200`. Confirmed directly: the identical fixture dated relative to now yields
one project and 200 output tokens; with the literal it yields none. Date the
events relative to now so they stay inside the window whenever the suite runs.

vitest.config.ts / cache-refresh-lock: a handful of integration tests exercise
real servers, spawned subprocesses and real filesystem locks, and starve under
a saturated parallel run — failing closed, which is correct behaviour but not
what those tests measure. A small global retry rides that out; a real
regression is deterministic and fails every attempt.

Two upstream hunks are deliberately not ported. cli-durable-totals already has
an equivalent fix on this branch (ec449af) that clamps to a fraction of the
elapsed day rather than a fixed offset. The `corrupt lock recovery` describe
does not exist here, because upstream's d514459 has not been ported either.

Test-only; no production code changed.
2026-08-05 03:21:24 +03:00
Resham Joshi
c49fa23590
Merge pull request #849 from getagentseal/phase9/releasing-doc
docs(releasing): three-field version bump, exact core pin, core-before-cli publish order
2026-07-27 15:37:01 -07:00
iamtoruk
32165ddecd docs(releasing): three-field version bump, exact core pin, core-before-cli publish order
The #847 version gate fails the old single-workspace bump flow, and the
exact @codeburn/core pin makes publish order load-bearing: a CLI published
before its core version exists on the registry is uninstallable.
2026-07-27 15:35:59 -07:00
Resham Joshi
0a57aa5187
Merge pull request #847 from laulpogan/harden/core-release
Fix core release coupling, declaration build, and CI gates
2026-07-27 15:33:17 -07:00
Paul Logan
ed4f84da45 chore: stop emitting declaration maps
package.json#files ships dist and schemas but not src, so every emitted
.d.ts.map pointed at a source file the consumer never receives. That was 151
files and roughly 135 kB of maps that resolve to nothing.

Dropping declarationMap takes the tarball from 389 files to 238 and 376 kB to
354 kB. The declarations themselves are unchanged: 151 .d.ts, all 41 export
targets still verified present.
2026-07-27 15:22:15 -07:00
Paul Logan
0ec9ea93cb fix: guard publish against a half-built dist
The build is `tsup && tsc`. tsup runs with clean:true, so it wipes dist and
writes JavaScript; if tsc then fails, dist holds .js with no declarations. The
build exits non-zero, but packages/core declared no prepublishOnly, so nothing
rebuilt at publish time and a later npm publish would ship it.

Reproduced: remove the declarations from a copy of dist and npm pack --dry-run
still succeeds, with all 41 exports subpaths pointing at files absent from the
tarball. npm pack was never the guard.

Adds prepublishOnly (build then verify) and scripts/verify-dist.mjs, which
asserts every exports target exists. CI runs verify-dist as well, so the guard
is exercised on every push rather than only on the rare publish.
2026-07-27 15:12:13 -07:00
Paul Logan
6f1f8a4462 fix: check package-lock.json in the workspace version gate
The gate compared only the three package.json manifests, so it passed while
package-lock.json still recorded a stale workspace version. npm ci does not
reject that either (checked on npm 10.9.2, 11.12.1, 11.16.0, 11.17.0), so
nothing caught the exact drift the script exists to catch.

Reproduced with a fixture whose manifests all read 0.9.20 while the lockfile
recorded packages/core at 0.9.19 and a "*" CLI dependency: the gate exited 0.
It now exits 1.
2026-07-27 15:12:04 -07:00
Paul Logan
7e29455d4f ci: gate core builds and package integrity 2026-07-27 12:01:52 -07:00
Paul Logan
0054c9ceca fix: emit core declarations with TypeScript 2026-07-27 11:57:05 -07:00
Paul Logan
c52c567cdd fix: couple core and CLI releases 2026-07-27 11:55:29 -07:00
Resham Joshi
88f298cddf
Merge pull request #844 from getagentseal/phase9/core-readme
docs(core): replace Phase 1 skeleton README; bump core to 0.9.20 for republish
2026-07-27 10:53:41 -07:00
iamtoruk
f00904d0a6 docs(core): replace Phase 1 skeleton README; bump core to 0.9.20 for republish
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.
2026-07-27 10:52:38 -07:00
Resham Joshi
20f17e8813
Merge pull request #843 from getagentseal/phase9/core-manifest
chore(core): publish-ready manifest — real description, public access
2026-07-27 10:20:48 -07:00
iamtoruk
ea4c99bc73 chore(core): publish-ready manifest — real description, public access
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.
2026-07-27 10:19:15 -07:00
Resham Joshi
d8ea608f8d
Merge pull request #842 from getagentseal/phase8/vercel-gateway
refactor(core): vercel-gateway decode into core, report fetch host-side (phase 8, network special)
2026-07-27 09:43:28 -07:00
iamtoruk
6983b29bf3 refactor(core): vercel-gateway decode into core, report fetch host-side (phase 8, network special)
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.
2026-07-27 09:40:04 -07:00
Resham Joshi
024f5062e3
Merge pull request #841 from getagentseal/phase8/stateful-kiro
refactor(core): kiro decode into core, stores and companions host-side (phase 8, stateful tier)
2026-07-27 09:14:20 -07:00
iamtoruk
0540102a3f refactor(core): kiro decode into core, stores and companions host-side (phase 8, stateful tier)
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.
2026-07-27 09:09:15 -07:00
Resham Joshi
74741a8b35
Merge pull request #840 from getagentseal/phase8/stateful-cursor
refactor(core): cursor decode into core, queries and caches host-side (phase 8, stateful tier)
2026-07-27 08:10:40 -07:00
iamtoruk
5960ae3535 refactor(core): cursor decode into core, queries and caches host-side (phase 8, stateful tier)
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.
2026-07-27 08:05:46 -07:00
Resham Joshi
be00eed4e9
Merge pull request #839 from getagentseal/phase8/stateful-antigravity
refactor(core): antigravity decode into core, stitching and caches host-side (phase 8, stateful tier)
2026-07-27 01:09:08 -07:00
iamtoruk
2f3f65533b refactor(core): antigravity decode into core, stitching and caches host-side (phase 8, stateful tier)
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.
2026-07-27 01:04:25 -07:00
Resham Joshi
87b72d7260
Merge pull request #838 from getagentseal/phase8/judgment-mistral-vibe
refactor(core): mistral-vibe decode into core with host-side cost resolution (phase 8, judgment tier)
2026-07-26 23:47:39 -07:00
iamtoruk
e11c29937c refactor(core): mistral-vibe decode into core with host-side cost resolution (phase 8, judgment tier)
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.
2026-07-26 23:43:45 -07:00
Resham Joshi
f4f4dccaec
Merge pull request #837 from getagentseal/phase8/shared-modules
refactor(core): opencode-session shared decode into core — opencode, kilo-code (phase 8, shared batch S2)
2026-07-26 22:59:47 -07:00
iamtoruk
4808b115cc refactor(core): opencode-session shared decode into core — opencode, kilo-code (phase 8, shared batch S2)
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.
2026-07-26 22:55:34 -07:00
Resham Joshi
3817289238
Merge pull request #836 from getagentseal/phase8/shared-modules
refactor(core): vscode-cline shared decode into core — cline, ibm-bob, roo-code (phase 8, shared batch S1)
2026-07-26 22:07:39 -07:00