Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c1000aa
docs(design): bench-snapshot-summary-adr-lint
scolladon Sep 4, 2026
bfa0709
docs(adr): 800 The zero-sample guard lives in both the bench DSL and …
scolladon Sep 4, 2026
73178d0
docs(adr): 801 The benchmark-snapshot job bounds the run-phase hang w…
scolladon Sep 4, 2026
897c22f
docs(adr): 802 The converter's refusal is a separate exported guard c…
scolladon Sep 4, 2026
d30f1fe
docs(adr): 803 The fetch-pack bench receives into a fresh memory cont…
scolladon Sep 4, 2026
e3f4e8d
docs(adr): 804 A re-received identical pack is tolerated before the r…
scolladon Sep 4, 2026
3056ef2
docs(adr): 805 ADR-724 keeps one bare superseded line plus a bullet n…
scolladon Sep 4, 2026
e7ae869
docs(adr): 806 The completed backlog entry 30.3 cites ADR-752
scolladon Sep 4, 2026
be34332
docs(adr): 807 The security page cites ADR-721 for the root-set model
scolladon Sep 4, 2026
e4c2a32
docs(adr): 808 bench-summarize exports its pure functions in place an…
scolladon Sep 4, 2026
0b950c1
docs(adr): 809 A tsgit-only row renders an em dash and n/a; a no-tsgi…
scolladon Sep 4, 2026
30dc4dc
docs(plan): bench-snapshot-summary-adr-lint
scolladon Sep 4, 2026
988d457
fix(bench): receive into a fresh memory context on every fetch-pack i…
scolladon Sep 4, 2026
7dea919
fix(bench): fail the run on a warmup throw and bound the snapshot job…
scolladon Sep 4, 2026
7887b7e
fix(tooling): refuse a benchmark snapshot entry that carries no value
scolladon Sep 4, 2026
5eba23f
fix(tooling): render tsgit-only scenarios in the benchmark summary
scolladon Sep 4, 2026
dd3f455
fix(fetch-pack): tolerate an already-present pack before the rename, …
scolladon Sep 4, 2026
49816cd
docs(adr): satisfy the decision lint on status lines, anchors and cit…
scolladon Sep 4, 2026
f3c6307
chore(deps): bump outdated dependencies
scolladon Sep 4, 2026
19dc7fb
fix(fetch-pack): compare an already-present pack per artefact and ref…
scolladon Sep 5, 2026
e825e24
fix(bench): bound the compare job and pin the guard branches the revi…
scolladon Sep 5, 2026
18939be
test(tooling): exclude the bench spawn test from the proof-header heu…
scolladon Sep 5, 2026
241d559
chore(deps): bump lint-staged to 17.5.0
scolladon Sep 5, 2026
f67d593
chore(size): raise the primitives chunk budget to 62 kB for the recei…
scolladon Sep 5, 2026
ff56e6e
fix(fetch-pack): release the quarantine copy on every exit; refuse no…
scolladon Sep 5, 2026
f1e0b1c
fix(fetch-pack): state gc's replace posture, isolate the isFile guard…
scolladon Sep 5, 2026
ec9b6ed
docs(bench-snapshot-summary-adr-lint): refresh pages
scolladon Sep 5, 2026
c39c0f4
test(fetch-pack): kill the window-length and unread-occupant mutants;…
scolladon Sep 5, 2026
4d9f235
chore(deps): hold @playwright/test at 1.62.1 while WebKit 26.6 fails …
scolladon Sep 5, 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
6 changes: 6 additions & 0 deletions .claude/workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ workflow" / "the usual flow" resolve here (see CLAUDE.md §Development Workflow)
keeps the pin from rotting, and a `workers-types` change that actually matters shows up as
a type error, not as an `npm outdated` row. The exception existed before the v4 → v5
migration removed it; this restores it.
**`@playwright/test` is held at 1.62.1**: 1.63.0 ships WebKit 26.6, and on the
`ubuntu-latest` runner every headless WebKit page then fails
`navigator.storage.getDirectory()` with `UnknownError: The operation failed for an unknown
transient reason`, so the whole `e2e (webkit)` job goes red (reproduced twice on PR #295;
green on the previous build; Chromium and Firefox unaffected). Unpin once a later release
passes the WebKit e2e job.
- **`docs-drift.md` on BOTH `documentation` and `integrate`** — the `docs-pr-gate` bot
comments only once the PR exists, so the documentation phase can preempt it but cannot
see it. Integrate therefore treats that comment like any other red CI signal: read it,
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ jobs:
if: github.event_name == 'push'
needs: [unit-tests]
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
steps:
Expand Down Expand Up @@ -605,6 +606,7 @@ jobs:
contains(github.event.pull_request.labels.*.name, 'bench')
needs: [changes, unit-tests]
runs-on: ubuntu-latest
timeout-minutes: 60
# Informative only — a flagged regression is a prompt to look, never a
# merge gate. Same-runner benchmarking measures too much noise to block on.
# Compares per-scenario median-ms, asymmetric (improvements never flag),
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,7 @@ reports/stryker-incremental.json

# graft re-creates this on every build to re-admit graft/ to ripgrep; see .rgignore.
/.ignore

# Scratch vitest config dir for the bench warmup-throw spawn guard test —
# must live under the repo root to resolve `vitest/config`.
.bench-warmup-throw-*/
2 changes: 1 addition & 1 deletion .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@
{
"name": "Chunks: primitives",
"path": "dist/esm/chunks/primitives-*.js",
"limit": "61 kB",
"limit": "62 kB",
"gzip": true
},
{
Expand Down
6 changes: 5 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"tooling/test/unit/bench-ab.test.ts",
"tooling/bench-to-snapshot.ts",
"tooling/test/unit/bench-to-snapshot.test.ts",
"tooling/bench-summarize.ts",
"tooling/test/unit/bench-summarize.test.ts",
"tooling/test/unit/fixture-generator.test.ts",
"tooling/test/unit/scaled-bench.test.ts",
"tooling/test/unit/bench-dsl.test.ts",
Expand All @@ -39,7 +41,9 @@
"tooling/truthful-dts.ts",
"tooling/audit-assert-tier.ts",
"tooling/audit-assert-tier/**/*.ts",
"tooling/test/unit/audit-assert-tier.test.ts"
"tooling/test/unit/audit-assert-tier.test.ts",
"tooling/test/integration/bench-warmup-throw.test.ts",
"tooling/test/fixtures/bench/throwing-warmup.bench.ts"
]
},
"formatter": {
Expand Down
2 changes: 1 addition & 1 deletion docs/BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ Surfaced by the 2026-08 perf-remediation run (ADRs 718–736, `design/perf-remed

- [x] **30.1** rollup unpin probe — **DONE 2026-08-29: 4.63.1 builds `tokenizeLine` intact; unpinned.** The probe ran three ways against the built bundle — the emitted chunk's branch literals, a functional `[user]` header parse, and a functional malformed-`[core` refusal — each confirmed against a 4.63.0 negative control that reproduced the original defect (both branches absent; every valid header throwing `CONFIG_PARSE_ERROR`). `dispose-free-exit` passes at 4.63.1, and the emitted chunk hash matches the 4.62.5 baseline. The `check:deps` grep exception and the `.claude/workflow.md` note are both dropped. Original entry: `rollup` is pinned to 4.62.5 because 4.63.0 compiles `config-ini.ts`'s `tokenizeLine` incorrectly (deletes the live header/malformed branches after `scanHeaderPrefix`, breaking every built-bundle config parse; caught by the `dispose-free-exit` integration test). Probe 4.63.1+ against that exact function in the built bundle; unpin and drop the `check:deps` exception + `.claude/workflow.md` note when a fixed release builds it intact. Chore-sized; the failing shape is documented in the manifest.
- [x] **30.2** `reflog` command malformed-line parity — pre-existing (on main before the perf run): git's reflog machinery skips a malformed reflog line per-line and keeps reading; the `reflog` command's read path diverges from that tolerance shape (the gc retention-root walk was aligned line-grained during the 2026-08 review — `readReflogLenient` / `parseReflogLenient` in `domain/reflog/reflog-format.ts` — but the user-facing `reflog` command was out of that scope). Align the command on the same lenient per-line parse, co-pin against real git with a hand-corrupted reflog fixture. Small. · ADRs 737–747 · design/reflog-command-lenient-parity.md
- [x] **30.3** tree entry-name byte-sensitivity unification (breaking) — pre-existing: `parseTreeContent` (`domain/objects/tree.ts`) and the cursor descent (`tree-cursor.ts` consumers) handle exotic entry-name bytes (BOM-prefixed names) with different tolerance, and `parseTreeContent`'s single `normalizeFileMode` check differs from git's own two-tier malformed-vs-bad mode split that the cursor's `scanMode`/`matchFileModeBytes` already matches (probed, git 2.55.0 — see ADR-723's addendum). Unify scoped to `tree.ts` (the outlier), never by weakening the cursor; co-pin each refusal shape against git. Small, but every change needs an interop pin. **Shipped, far wider than "`tree.ts` (the outlier)" scoped it**: a design-phase measurement found two more decoded-string sites carrying the identical bug (`flatten-raw.ts`'s `validatedName`, `validate-tree.ts`'s fsck checks), so all four decoding read paths unify on raw name bytes — `resolve-tree-path.ts`'s byte-level checks are deleted outright rather than promoted, since no git read path ever refused what they checked. `TreeEntry` is now a **branded** type carrying authoritative `nameBytes` beside a derived `name`, minted only through the exported `treeEntry(mode, name, id)` factory; every `{ name, mode, id }` object-literal construction site — in `src` and in a consumer's own code — must move to a `treeEntry(...)` call. Duplicate-entry-name and `.`/`..`/embedded-`/` refusals, which no git read path ever enforced, are dropped from the object-parse layer; `.`/`..` are refused only where git refuses them, at worktree materialisation and index construction, and the separator refusal is dropped outright. A parse tier now precedes the check tier (`malformed mode` / `empty filename` → `badTree`, ahead of an octal-but-unrecognised mode's `badFilemode`), the orphaned `emptyName` msg-id is deleted, and fsck's `.git`/`.gitmodules`/`.gitattributes`/`.gitignore`/`.mailmap` comparisons now fold git's HFS/NTFS alias matrix instead of matching the literal name. Ships as a major: 3.6.0 → 4.0.0. · ADRs 748–766 · design/tree-entry-byte-sensitivity.md
- [x] **30.3** tree entry-name byte-sensitivity unification (breaking) — pre-existing: `parseTreeContent` (`domain/objects/tree.ts`) and the cursor descent (`tree-cursor.ts` consumers) handle exotic entry-name bytes (BOM-prefixed names) with different tolerance, and `parseTreeContent`'s single `normalizeFileMode` check differs from git's own two-tier malformed-vs-bad mode split that the cursor's `scanMode`/`matchFileModeBytes` already matches (probed, git 2.55.0 — see ADR-752's carried-forward mode-tier addendum). Unify scoped to `tree.ts` (the outlier), never by weakening the cursor; co-pin each refusal shape against git. Small, but every change needs an interop pin. **Shipped, far wider than "`tree.ts` (the outlier)" scoped it**: a design-phase measurement found two more decoded-string sites carrying the identical bug (`flatten-raw.ts`'s `validatedName`, `validate-tree.ts`'s fsck checks), so all four decoding read paths unify on raw name bytes — `resolve-tree-path.ts`'s byte-level checks are deleted outright rather than promoted, since no git read path ever refused what they checked. `TreeEntry` is now a **branded** type carrying authoritative `nameBytes` beside a derived `name`, minted only through the exported `treeEntry(mode, name, id)` factory; every `{ name, mode, id }` object-literal construction site — in `src` and in a consumer's own code — must move to a `treeEntry(...)` call. Duplicate-entry-name and `.`/`..`/embedded-`/` refusals, which no git read path ever enforced, are dropped from the object-parse layer; `.`/`..` are refused only where git refuses them, at worktree materialisation and index construction, and the separator refusal is dropped outright. A parse tier now precedes the check tier (`malformed mode` / `empty filename` → `badTree`, ahead of an octal-but-unrecognised mode's `badFilemode`), the orphaned `emptyName` msg-id is deleted, and fsck's `.git`/`.gitmodules`/`.gitattributes`/`.gitignore`/`.mailmap` comparisons now fold git's HFS/NTFS alias matrix instead of matching the literal name. Ships as a major: 3.6.0 → 4.0.0. · ADRs 748–766 · design/tree-entry-byte-sensitivity.md
- [x] **30.4** delta-writing packer for gc/consolidation — **shipped**: `buildPack` now selects and writes `OFS_DELTA` chains (window/depth selection per git's defaults, `pack.depth`/`pack.window`/`pack.windowMemory` config surface) and `maintenance`'s gc/consolidation path uses it, retiring the delta-free inflation note. Byte-identity with git's packer was never the contract; re-measured on the same corpora: ×1.58 barely-deltifiable and ×5.43 worst-case on a deep delta chain against `git -c pack.threads=1 repack -a -d -f`, ×1.42 on tsgit's own real history against `git -c pack.threads=1 gc` — real history carries unreachable objects that `repack -a -d` (no `-A`) drops but tsgit's `gc` retains, so `gc` is the peer that keeps both sides' object counts comparable (24 879 git vs 24 817 tsgit, within 0.25%) instead of charging tsgit for objects git already deleted — down from the retired ×1.29–6.91 base-only brackets, not ×1.00, because git orders delta-base window candidates by path/name-hash while tsgit's gc path carries no path hints and orders by size, so a long run of same-size versions of one file ties and the window samples the wrong neighbours. The design surfaced two things the entry did not anticipate: the two delta-chain readers (the resolver's `collectDeltaChain`, fsck's `walkDeltaChain`) disagreed by one hop on git's default `pack.depth = 50` cap and now both accept exactly that depth; and the absence of path hints on the gc path is what keeps the emission order size-adjacency rather than git's name-hash adjacency. `pack.depth` above 50 is honoured only up to 50 — tsgit's own readers refuse a deeper chain, so the writer never produces one they can't read back, a silent, compression-only divergence. · ADRs 767–778 · design/delta-writing-packer.md
- [x] **30.5** streaming index pass for received packs — the fetch/clone receive path streams the pack to quarantine at O(window) RSS, and the index pass now walks it back from disk in bounded windows rather than one whole-pack buffer, but delta resolution (`resolveAllEntries` in `fetch-pack.ts`) still holds every entry's inflated content in memory simultaneously — O(total inflated size) residency, typically larger than the pack itself. A true bounded-memory indexer is a `git index-pack`-scale design: sequential first pass hashing base entries and recording delta positions, second pass resolving deltas by re-reading bases from disk through a size-budgeted cache, thin-pack external bases handled on the same seam. Feature-sized: own design + ADRs; sequenced after 30.4 if the packer work reshapes the shared pack-write/read seams. · ADRs 779–790 · design/streaming-index-pass.md
- [ ] **30.6** name-hash ordering for delta-base selection — 30.4's packer orders window candidates by `(typeRank, size DESC, oid ASC)`; git orders by path/name-hash first, then size. Measured on the same corpora, tsgit lands ×1.58 (`MEDIUM_FIXTURE`) and ×5.43 (`DELTA_CHAIN_FIXTURE`) against `git -c pack.threads=1 repack -a -d -f`, and ×1.42 (tsgit's own history) against `git -c pack.threads=1 gc` — the prior ×2.05 real-history figure compared tsgit's cruft-retaining `gc` against a `repack -a -d -f` that drops unreachable objects, so it was charging tsgit for 7 000-plus objects git had already deleted; corrected to a `gc`-vs-`gc` comparison with comparable object counts on both sides, real history now lands closer to git than either synthetic corpus does. A large gain over the retired base-only brackets either way, but the size-gap case for this entry now rests on the deep-chain shape, not on real history. Root cause pinned with `git verify-pack -v`: on the deep-chain fixture 808 of 900 objects landed as non-delta bases with a max chain of 5, against git's ~43, because a long run of same-size versions of one file ties under size ordering and the window samples the wrong neighbours — real, independently evidenced, and untouched by the peer-command correction above. `pack-objects` and `bundle-create` already carry `ClosureObject.path` and could take a name hash immediately; the gc path discards the path its reachability walk already discovers, so the real work is threading it through enumeration and into `BuildPackInput`. Amends the ordering half of ADR-769 and revisits the alternative ADR-769 declined. Depends on 30.4 only — independent of 30.5. Feature-sized: own design + ADRs, and it is not proven to reach ×1.0x until re-measured.
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/389-incremental-stream-hash-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

## Status

- **Status:** superseded by ADR-718 (default posture; the incremental mechanism stands)
- **Status:** superseded by ADR-718

- **Date:** 2026-06-20
- **Design:** [design/blob-streaming.md](../design/blob-streaming.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/541-raw-node-adapter-layout-root-set.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
> containment model, canonical-prefix derivation and write-path posture are carried
> forward unchanged.

- **Status:** superseded by ADR-721 (read-path wrapper role for first-party adapters; root-set model stands)
- **Status:** superseded by ADR-721
- **Date:** 2026-07-29
- **Design:** docs/design/linked-worktree-discovery.md · **Supersedes/Refines:** refines ADR-535/ADR-537; revises the design's original §3 rooting; refines ADR-495 (commonAncestor's discovery role retired)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ than refused. The config-scope `allowSet` sequencing is preserved: scope reads k
resolving to an empty scope, never a throw.

Superseded from ADR-541: the premise that the facade wrapper is a load-bearing read-path
layer for first-party adapters. Carried forward from ADR-541: everything else — the
adapter's root-set containment model, canonical-prefix derivation for not-yet-existing
roots, and the write-path posture, all unchanged.
layer for first-party adapters.
Carried forward from ADR-541: everything else — the adapter's root-set containment model,
canonical-prefix derivation for not-yet-existing roots, and the write-path posture, all
unchanged.

## Consequences

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ subjects:
> structured result shape, the midx/`.rev` constraints and the Tier-1 surface gates
> are carried forward unchanged.

- **Status:** superseded by ADR-731 (prune semantics), ADR-732 (pack consolidation) and ADR-733 (promisor consolidation); the command and commit-graph task stand
- **Status:** superseded by ADR-731
- **Also superseded by:** ADR-732 (pack consolidation) and ADR-733 (promisor consolidation); the command and commit-graph task stand
- **Date:** 2026-08-26
- **Design:** docs/design/perf-remediation-2026-08.md (DC-2) · **Supersedes/Refines:** un-parks the gc/repack/prune backlog entry (docs/BACKLOG.md)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ keep **last**-wins; `fsck` remains the sole detector and continues to report a d
finding. `Tree.entries` is an ordered array and may legitimately contain two entries
sharing a name.

**Superseded from ADR-723:** the duplicate-entry-name refusal on read paths — both the
Superseded from ADR-723: the duplicate-entry-name refusal on read paths — both the
per-directory `Set` re-implemented in the cursor descent and `parseTreeContent`'s own
`names` set — together with the unpinned premise that git's behaviour here was unknown.

**Carried forward from ADR-723:** the ruling that the cursor's own unconditional scan
Carried forward from ADR-723: the ruling that the cursor's own unconditional scan
stays minimal and that name-shape validation is re-implemented per consumer rather than
inside `TreeCursor`; the finding that the raw merge-join deliberately carries neither
check; and the entire mode-tier addendum, which ADR-754 acts on rather than reverses.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
subjects:
- test/bench/support/bench-dsl.ts
- tooling/bench-to-snapshot.ts
---
# 800 — The zero-sample guard lives in both the bench DSL and the snapshot converter

- **Status:** accepted
- **Date:** 2026-09-04
- **Design:** docs/design/bench-snapshot-summary-adr-lint.md (D1) · **Supersedes/Refines:** none

## Context

A benchmark whose measured function throws during warmup is reported by `vitest bench` as a
pass with an empty sample list and exit code 0: tinybench stores the warmup error on the task
and throws it only when its `throws` option is set, which vitest never sets, and the run phase
returns early on a stored error before any event fires. The fetch-pack scenario shipped in that
state and the only component that noticed was the publish action on `main`, rejecting a snapshot
entry that had no value. Two holes, two layers: the bench step passed while measuring nothing,
and the publish step trusted its input.

## Options considered

1. **DSL `throws: true` only** — pros: the bench step goes red at the source with the stack that
names the scenario / cons: the publish step keeps trusting whatever reaches it.
2. **Converter refusal only** — pros: the published artefact can never carry a value-less entry /
cons: a bench file keeps passing green while measuring nothing.
3. **Both** (designer's recommendation) — pros: each layer closes the hole the other cannot /
cons: one more line in the DSL and one guard in the converter.

## Decision

**Ratified by the user: option 3.** The bench DSL passes `throws: true` in the options it
attaches to every bench it registers, so a warmup failure fails the file with its error visible.
The snapshot converter refuses, by scenario name, any raw entry that carries no sample and
therefore no value, so nothing without a value reaches the publish action.

## Consequences

A scenario that throws in warmup is now a red bench step, not a silent pass; its teardown does
not run (the already-accepted cost under ADR-791 and ADR-799, reclaimed by the explicit prune).
The run-phase hang that `throws` introduces is bounded by ADR-801. The converter's guard shape
is ADR-802.
Loading
Loading