[#416] feat: llms.txt drift gate - #475
Conversation
Verdict
PR: #475 · Author: rucka · Reviewer: independent reviewer agent · Date: 2026-09-01 · Story: US-416 · Type: feature (tech-debt) Classification matrix — per dimension
Tier = max(assessed) with the AssessmentsSecurity — Input validationVerdict: green — the only inputs are repo-local file paths derived from Security — Output handlingVerdict: green — output is console text on a dev/CI TTY; no encoding surface. Security — Authentication / AuthorizationVerdict: green — no auth surface; the gate runs in CI with the repo checkout it already has. Security — Introduced vulnerabilitiesVerdict: green — 0 introduced, 0 pre-existing. The CostVerdict: Architecture (Coupling)Verdict: not assessed ( Bug fix — Red test before fixVerdict: not applicable — not a bug fix (tech-debt/coverage story). DetailsFindings by severityCritical (must fix before merge)
Major (must fix before merge)
Minor (must fix before merge — same bar as Major, just lower impact)
Questions (informational, never blocking)
Positive feedback
Functionality & requirements (AC coverage)
Testing & quality gates
Adoption compliance
Tech debt
Documentation
Performance & deployment
|
This comment has been minimized.
This comment has been minimized.
…depoint Review round 1 on PR #475. - `llms-index:check` was a bare `ts-node`, so it type-checked a source file whose first line imports @pair/content-ops TYPES from dist/. On a fresh `pnpm install` the gate produced none of its three outcomes — TS2307 + a ts-node stack, exit 1. Now `ts-node -T`; type-checking that source is ts:check's job, and turbo ts:check is the task carrying the ^build edge. - compareIndex diffed through Sets, so a duplicated entry line (the shape a "keep both sides" merge of llms.txt produces) reported `0 missing / 0 extra` plus "the difference is their order or whitespace" — wrong, in the case that most needs the diff. Multiset deltas now. - localeCompare with no locale sorted the index by the runtime's ICU default: 458 of 560 entries move under a small-icu Node, so the gate went red on an untouched tree and its own message told the contributor to commit the churn. Codepoint order; .pair/llms.txt regenerated through writeProjectLlmsTxt. - main() had no try/catch: an unreadable KB dir (chmod 000) escaped as an unhandled rejection instead of the broken-setup outcome. Caught, reported, exit 1. - A tree missing ONE whole section clears the broken-setup guard and is reported as drift telling the contributor to regenerate — which deletes that section from the index. Caution line when a tracked heading has zero generated entries. - dev-tools README Tools table: the gate had no row (plus the two stale ones). ADLs: codepoint-sort rule recorded; the source-import ADL gains the -T rationale.
Bound re-review to immutable deltas and require a complete finite-state inventory before review or remediation.
Round 2 of PR #475. - formatDrift's closing paragraph branches on emptiedSections: a sparse tree reads "Once the tree is complete, regenerate with `pair update`", so the message no longer ends with the imperative the caution above it forbids. - the catch-path test drives main() with an injected throwing readdir instead of an if-wrapped chmod probe that ran zero assertions under root; the chmod variant survives as an explicitly skipped case. - ADL restated: the invariant is determinism, the comparator is UTF-16 code-unit order (= codepoint for every BMP path). No comparator change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
4bc1f26 to
a7de384
Compare
…depoint Review round 1 on PR #475. - `llms-index:check` was a bare `ts-node`, so it type-checked a source file whose first line imports @pair/content-ops TYPES from dist/. On a fresh `pnpm install` the gate produced none of its three outcomes — TS2307 + a ts-node stack, exit 1. Now `ts-node -T`; type-checking that source is ts:check's job, and turbo ts:check is the task carrying the ^build edge. - compareIndex diffed through Sets, so a duplicated entry line (the shape a "keep both sides" merge of llms.txt produces) reported `0 missing / 0 extra` plus "the difference is their order or whitespace" — wrong, in the case that most needs the diff. Multiset deltas now. - localeCompare with no locale sorted the index by the runtime's ICU default: 458 of 560 entries move under a small-icu Node, so the gate went red on an untouched tree and its own message told the contributor to commit the churn. Codepoint order; .pair/llms.txt regenerated through writeProjectLlmsTxt. - main() had no try/catch: an unreadable KB dir (chmod 000) escaped as an unhandled rejection instead of the broken-setup outcome. Caught, reported, exit 1. - A tree missing ONE whole section clears the broken-setup guard and is reported as drift telling the contributor to regenerate — which deletes that section from the index. Caution line when a tracked heading has zero generated entries. - dev-tools README Tools table: the gate had no row (plus the two stale ones). ADLs: codepoint-sort rule recorded; the source-import ADL gains the -T rationale.
Round 2 of PR #475. - formatDrift's closing paragraph branches on emptiedSections: a sparse tree reads "Once the tree is complete, regenerate with `pair update`", so the message no longer ends with the imperative the caution above it forbids. - the catch-path test drives main() with an injected throwing readdir instead of an if-wrapped chmod probe that ran zero assertions under root; the chmod variant survives as an explicitly skipped case. - ADL restated: the invariant is determinism, the comparator is UTF-16 code-unit order (= codepoint for every BMP path). No comparator change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
Require real boundary evidence for externally-defined state and repair claims.\n\nRefs: #416
- 13 cases on self-contained temp KB trees: in-sync passes; un-indexed guideline listed as missing; deleted KB file listed as extra; empty and absent tracked file are drift not a crash; missing/partial tree is a distinct broken-setup outcome; failing run leaves the file byte-identical; generator and check both deterministic - expected index is never hardcoded — asserted against generateLlmsTxt's own output, so #393's newline form is consumed, not re-litigated (AC6) - Task: T-1 — Write failing fixture-based tests for the llms.txt drift check Refs: #416
…ts generator - new `llms-txt-drift-check` in dev-tools: generate → read tracked → byte compare; on drift it prints the missing/extra lines and names `pair update`, and a missing/partial KB tree is a distinct broken-setup outcome, not drift - `generateLlmsTxt` now takes `LlmsSourceFs`, a read-only 3-method slice, and the gate's adapter has no write primitive — "this gate cannot write the file it judges" is a type fact, not a review promise - the generator is imported by SOURCE path so the gate judges the working tree, never a stale dist/. That costs `composite: false` on @pair/dev-tools (an emit-time rootDir invariant on a package that emits nothing) and a @pair/content-ops devDep for the build-ORDER edge — both argued in the ADL, both repeated at their edit sites - #216's byte-equality vitest guard is REPLACED, not duplicated: the conformance test keeps only its output-shape assertions - Task: T-2 — Implement the llms.txt drift-check module Refs: #416
…gate - root `llms-index:check` alias + a step in the gate chain next to skills:conformance; ci.yml gets the SAME command as a named step, after `Run build` (ts-node type-checks the generator against content-ops' types) - a unit test could not enforce this: a KB-only change touches nothing `turbo test` hashes, so it would replay a cached PASS — the #400 rule - way-of-working records the gate: what it compares, that it names `pair update`, and that it never writes - validated empirically: an un-indexed guideline exits 1 naming the missing line, leaves .pair/llms.txt byte-identical; the clean tree exits 0 - Task: T-3 — Wire the check into ci.yml + root quality-gate Refs: #416
…depoint Review round 1 on PR #475. - `llms-index:check` was a bare `ts-node`, so it type-checked a source file whose first line imports @pair/content-ops TYPES from dist/. On a fresh `pnpm install` the gate produced none of its three outcomes — TS2307 + a ts-node stack, exit 1. Now `ts-node -T`; type-checking that source is ts:check's job, and turbo ts:check is the task carrying the ^build edge. - compareIndex diffed through Sets, so a duplicated entry line (the shape a "keep both sides" merge of llms.txt produces) reported `0 missing / 0 extra` plus "the difference is their order or whitespace" — wrong, in the case that most needs the diff. Multiset deltas now. - localeCompare with no locale sorted the index by the runtime's ICU default: 458 of 560 entries move under a small-icu Node, so the gate went red on an untouched tree and its own message told the contributor to commit the churn. Codepoint order; .pair/llms.txt regenerated through writeProjectLlmsTxt. - main() had no try/catch: an unreadable KB dir (chmod 000) escaped as an unhandled rejection instead of the broken-setup outcome. Caught, reported, exit 1. - A tree missing ONE whole section clears the broken-setup guard and is reported as drift telling the contributor to regenerate — which deletes that section from the index. Caution line when a tracked heading has zero generated entries. - dev-tools README Tools table: the gate had no row (plus the two stale ones). ADLs: codepoint-sort rule recorded; the source-import ADL gains the -T rationale.
Round 2 of PR #475. - formatDrift's closing paragraph branches on emptiedSections: a sparse tree reads "Once the tree is complete, regenerate with `pair update`", so the message no longer ends with the imperative the caution above it forbids. - the catch-path test drives main() with an injected throwing readdir instead of an if-wrapped chmod probe that ran zero assertions under root; the chmod variant survives as an explicitly skipped case. - ADL restated: the invariant is determinism, the comparator is UTF-16 code-unit order (= codepoint for every BMP path). No comparator change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
Review round 3. - changeset (patch, @pair/pair-cli): the comparator change rewrites every adopter's .pair/llms.txt on their next `pair update`; ADL 2026-08-20-a-user-facing-cli-fix-carries-its-changeset.md requires the artifact in the PR that makes the change, release timing stays human. - .gitattributes: `* text=auto eol=lf` + the index by name. Without it a `core.autocrlf=true` clone got 562 missing + 562 extra lines and advice that loops (regenerate writes LF, next checkout restores the CRs). - the gate normalizes the terminator before diffing, flags `trackedUsesCrlf`, suppresses the order/whitespace sentence and conditions the call to action on "once the checkout is normalized to LF" — real drift stays visible. - ADL: extended to the byte-reproducibility invariant (order + terminator) and gains the `## Adoption Impact` section the template requires. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
…ic one Found by running the message's own advice on a `core.autocrlf=true` clone: `git add --renormalize` is INERT when the index side is already LF (which is what .gitattributes guarantees) — it staged nothing, all 583 CR-carrying lines stayed on disk and the gate stayed red. `rm` + `git checkout --` is what forces git to rewrite the file under the attribute; verified to take the same clone from w/crlf to w/lf and the gate to exit 0. Also: strip a RUN of trailing CRs, not one, so a doubled terminator (what a hand-rolled s/\n/\r\n/ leaves on an already-CRLF file) reports as a terminator mismatch instead of every line missing AND extra. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
…p figures are the measured ones Terminator domain closed. The gate split on `\n` only, so a bare-CR file (classic Mac; a hand-rolled `s/\n/\r/`, never git) collapsed into ONE segment: on the real index 570 `missing` + 1 giant concatenated `extra`, no caution, and the closing advice was the bare `pair update` the CRLF branch exists to avoid. Split is now `/\r\n|\r|\n/`, the flag is `trackedCarriesCr` (set from any `\r`), and the caution reads "carriage return (CRLF, or a bare CR)". One test row per terminator state: LF, CRLF, mixed, doubled `\r\r\n`, bare CR, CR+LF, stray CR at EOF. Figures: the ADL, the source comment and the test comment said 562 content lines / ~1124-line dump. Measured: 583 physical, 570 non-blank, 562 ENTRIES — and the pre-normalization gate on a CRLF copy of the real index prints 570 + 570 over 1153 lines. Corrected to 570 / ~1140, matching way-of-working.md and the PR body.
3580a61 to
a5235fa
Compare
… recipe touches one file - DEVELOPMENT.md + its website twin enumerate the real `quality-gate` order (adds workflows:test, smoke-modes:check, llms-index:check) and name the llms-index remedy. - `carriageReturnCaution` drops `git config core.autocrlf false`: the `eol=lf` attribute overrides autocrlf on its own (verified on a core.autocrlf=true clone — `rm` + `git checkout --` alone → w/lf, 0 CRs, gate green, config untouched), so the step rewrote repo-local config for every file to fix one. Test flipped first (RED), ADL rule 2 updated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The "byte equality above" reference dangled: that guard moved to llms-txt-drift-check.ts and no longer lives in this file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
…like lines show their bytes - tracked-file read gets its own catch → `unreadable-index` outcome naming the FILE (EACCES / EISDIR) and `git checkout -- .pair/llms.txt` (verified on git 2.55 to replace a chmod-000 file, a directory, a dangling symlink); the KB-tree text is reserved for generator failures - missing/extra pairs that differ only in invisible bytes (BOM, trailing/leading whitespace, NBSP, zero-width) render quoted with \uXXXX escapes + one caution; a leading BOM gets its own caution (`pair update` verified to rewrite without it) - 16 new rows, one per state; 52 passed Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FfhvsS5rippi6aUbbGrf6F
…e shows its bytes
- ZERO_WIDTH is \p{Cf}+\p{Zl}+\p{Zp}+\p{Variation_Selector} (432 code points, Node 24),
not a 7-entry hand list: bidi marks U+200E/F, U+061C, U+2066-9, U+202A-E, U+2061-4,
U+180E, VS U+FE00-F now detected; astral members escaped as \u{XXXXX} via codePointAt
- an UNPAIRED line carrying an invisible char is escaped too (lone U+200B no longer
prints as a blank line under `1 extra line(s):`)
- caution counts the quoted LINES, not distinct visible forms; wording names the classes
- one SPACE_LIKE_CLASS feeds key, rendering and the carries-invisible test
- runs of 2+ spaces collapse in the key and print as … in the rendering
Rows: 13 BMP Cf/Zl/VS members, 3 astral, U+200E on a real tree, lone U+200B, unpaired
NBSP, count=3, doubled space, single space literal, 8 space-likes detected AND escaped,
astral emoji stays raw, unpaired plain line stays raw. Suite 52 -> 84.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FfhvsS5rippi6aUbbGrf6F
…its format one
`\p{Cf}` misses 4036 Default_Ignorable code points (U+034F, the Hangul
fillers, reserved-but-ignorable blocks) — a CGJ look-alike pair printed as
two identical raw lines with no caution — and carries 32 code points a font
DOES draw (Arabic number signs, END OF AYAH, Kaithi/Egyptian format
controls), quoted under a caution claiming the terminal does not show them.
Class is now `\p{Default_Ignorable_Code_Point}\p{Zl}\p{Zp}` (VS ⊂ DI,
DI ∩ White_Space = ∅, measured on Node 24.15.0). One test row per
(range × general category) of DI \ Cf, one negative row per range of Cf \ DI.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FfhvsS5rippi6aUbbGrf6F
This comment has been minimized.
This comment has been minimized.
…hat exists Separator: entry paths built with posix.join; join stays for fs access. join is platform-bound, so a Windows run emitted .pair\\knowledge\\... — unresolvable links shipped by pair install/update, and a different sort key (\\ U+005C vs / U+002F). Real path.win32 over the real index: 562 missing + 562 extra, no caution naming it. Remedy: REGENERATION_COMMAND is pnpm llms-index:regen (llms-txt-regenerate.ts), the check's exact inverse — one file, same generator, same tracked path — refusing on every state whose caution says not to regenerate. pair update named no executable here and pair-cli update without --source reverts the guideline that reddened the gate. On the write-mode offender list. Tests: AC-5 asserts the literal, not the constant against itself, plus the script name resolved against the real root package.json.
…hat exists `way-of-working.md`'s KB-index bullet credited `pair update` with generating `.pair/llms.txt` and, 100 words later, denied that binary exists — re-teaching in the adoption file the exact string ADL 2026-09-03 retired. The gate header carried the same juxtaposition (producer at line 5, "no `pair` on `PATH`" at line 75). Producer is now `pair-cli install` / `pair-cli update` in both cells; `pair update` survives once, labelled as the retired spelling. Proved against the real artifact: pnpm-packed pair-cli installed into an empty project links `pair-cli` and no `pair`, and `pair-cli install`/`update` write (and restore, byte-identical) `.pair/llms.txt`.
|
In response to the first review: #475 (comment) Remediation — final synthesis (converged, entire cycle)This cycle re-opened twice after prior convergences (comments #issuecomment-5499957423 and #issuecomment-5511252662, minimized below — each time a fresh independent re-review surfaced new findings, and this run found none). Full audit of every finding raised against this PR across all review runs and every fix round, rounds 0–8 carried over verbatim from the prior synthesis, rounds 9–11 from
Accepted / non-actionable
Final verdict: review clean — zero actionable findings on the latest independent re-review (round 12); every finding across all runs of this cycle (rounds 0–8 from the prior syntheses, rounds 9–11 from |
7b55900 to
adb9627
Compare
PR Information
PR Title: [#416] feat: llms.txt drift gate
Story/Epic: #416 — no parent epic (cross-cutting quality-gate coverage)
Type: Feature (tech-debt)
Priority: P2 (Could-Have)
Labels:
user story,tech-debt,risk:green,pr-state:to-be-reviewedSummary
What Changed
pnpm llms-index:check— a gate that regenerates.pair/llms.txtwith the CLI's owngenerateLlmsTxtand requires byte equality with the committed file, printing the missing/extra lines and never writing the file. Wired intoci.ymland the rootquality-gate.Around it, three environment axes that would each make a byte-compared artifact machine-dependent are closed at the generator, plus the remedy the gate prints:
localeCompare, no locale ⇒ runtime ICU defaultcore.autocrlf=truerewrote all 570 lines.gitattributes(* text=auto eol=lf+ the index by name); the gate diagnoses any\rpath.join⇒.pair\knowledge\...on Windowsposix.joinfor emitted paths;joinfor file-system access onlypair update— no such executable herepnpm llms-index:regen, a root script that existsWhy This Change
.pair/llms.txtis generated and tracked, and it is the indexCLAUDE.mdpoints every agent at. Nothing compared it to its generator, so adding a guideline without regenerating silently hid it from every agent — two independent misses are on record (story-local-markers.md; the two how-to guides dropped in #246 and still indexed ~5 months later).A gate on a byte-compared artifact is only trustworthy if the artifact is byte-reproducible. Each axis above was measured as a full-file false positive on an untouched tree — collation: 458 of 560 entries move under ICU; terminator: 570 missing + 570 extra; separator: 562 missing + 562 extra — with a closing imperative that either cannot work or, obeyed, commits the damage.
Story Context
User Story: As a maintainer of the pair knowledge base, I want a quality gate that fails when
.pair/llms.txtno longer matches what the generator would emit, so that the machine-readable KB index thatCLAUDE.mdpoints agents at cannot silently go stale.Acceptance Criteria:
generateLlmsTxtcheckLlmsIndexDrift;compareIndexreturnsin-synconly on exact equalityformatDrift, multiset deltas (a duplicated line is drift a set view cannot see), with look-alike pairs rendered byte-escapedmain.github/workflows/ci.ymlstepllms-index:checkquality-gatechain + the sameci.ymlstep;gate:compositionguards the chainREGENERATION_COMMAND = 'pnpm llms-index:regen', asserted as a literal AND resolved against the real rootpackage.json; the gate is handed a 3-method read-only fs slice, so "cannot write" is a type factgenerateLlmsTxt's own output; the form is never re-litigatedChanges Made
Implementation Details
packages/dev-tools/src/quality-gates/llms-txt-drift-check.ts. Four outcomes as a discriminated union, because they have different remedies:in-sync,drift,broken-setup(no indexable section — an unfinished install, not a stale index),unreadable-index(the tracked file's own permission bits or a directory in its place). Reports the terminator (trackedCarriesCr), a BOM (trackedCarriesBom) and emptied sections, and puts the matching precondition in front of the closing imperative — a report that says "restore the tree first" and then closes with a bare "regenerate" delivers, in its own call to action, the damage the caution exists to prevent.packages/dev-tools/src/quality-gates/llms-txt-regenerate.ts(pnpm llms-index:regen). The check's exact inverse: same generator, same tracked path, one file written. It runs the check first and refuses onbroken-setup,unreadable-index, an unreadable tree,trackedCarriesCrandemptiedSections, printing the gate's own message — so obeying the printed advice can never be the thing that loses a section. On theWRITE_MODE_FORMATTERSoffender list: a gate that ran its own remedy would hide the drift it exists to reveal.apps/pair-cli/src/registry/llms-generation.ts: code-unit ordering andposix.joinfor emitted paths. This is published-CLI behaviour, so it carries a patch changeset.LlmsSourceFs(3 read methods) replacesFileSystemServiceingenerateLlmsTxt's signature; the writer declares its extra power as a separate two-methodLlmsIndexSink.#216's byte-equality guard was replaced, not duplicated —llms-index-conformance.test.tskeeps only its output-shape assertions.Files Changed
packages/dev-tools/src/quality-gates/llms-txt-drift-check.ts+.test.ts,packages/dev-tools/src/quality-gates/llms-txt-regenerate.ts+.test.ts,apps/pair-cli/src/registry/llms-generation.win32.test.ts,.gitattributes,.changeset/llms-txt-deterministic-order.md, 3 ADLs under.pair/adoption/decision-log/apps/pair-cli/src/registry/llms-generation.ts,apps/pair-cli/src/registry/llms-index-conformance.test.ts,packages/dev-tools/src/quality-gates/pre-push-gate-composition.ts+.test.ts,packages/dev-tools/{package.json,tsconfig.json,README.md}, rootpackage.json,.github/workflows/ci.yml,.pair/llms.txt,.pair/adoption/tech/way-of-working.md,DEVELOPMENT.md,apps/website/content/docs/contributing/development-setup.mdxServices to Release
@pair/pair-cli(patch changeset):.pair/llms.txtis now emitted in a deterministic, locale-independent order and with POSIX separators on every platform. An adopter's nextpair-cli install/pair-cli updaterewrites the file once — uppercase-first entries sort before their lowercase siblings; a Windows adopter additionally stops shipping 562 backslash-separated, unresolvable links. Generated file, so a one-time diff, not a migration.Testing
Test Coverage
\r\r\n/ bare CR / CR+LF / stray CR at EOF; invisible-character rendering; the four outcomes), 11 for the regeneration command (write path, idempotence, BOM strip, and one row per refusal state), 53 for gate composition, 221 across the CLI registry.pnpm --filter @pair/website e2e— 39 passed (a docs.mdxchanged).Test Results
Testing Strategy
llms-generation.win32.test.tsbindspathto Node's ownpath.win32(whatrequire('path')returns on Windows) and asserts the generator emits the same bytes a POSIX machine emits, order included. On POSIXjoinandposix.joinare the same function, so this is the only shape of test that can tell the fixed code from the broken code on a Linux CI.Reviewer Guide
Boundary probes (run against the real producers, not stubs)
path.win32bound to the real generator over the real repo tree, output into the realcompareIndex/formatReportin-syncgit clone+pnpm install(nothing built), add a guideline, run the gate, type the printed command verbatim, re-run✓ regenerated→ green;git diff --stat= `.pair/llms.txtpair-cli install/pair-cli update, andpairis not a binarypnpm packofapps/pair-cli+ its two workspace deps →npm ithe three real tarballs into an emptynpm initproject → run the linked bin in an empty target dirbin = {"pair-cli":"dist/cli.js"};node_modules/.binlinkspair-cli, nopair;which pair→ not found.pair-cli install --offline --source packages/knowledge-hub/datasetwrote.pair/llms.txt(474 lines, sha1733c39ed…); after tampering the file,pair-cli updaterestored it byte-identical. The retired remedy stringpair updatenames no executable — which is why the gate printspnpm llms-index:regen.pair/llms.txtconverted to CRLFrm+git checkout --recipecore.autocrlf=trueclonegit add --renormalizestages nothing and leaves all 583 CR lines;rm+git checkout --givesw/lf, 0 CRs, gate green — withcore.autocrlfleft attrue(theeol=lfattribute overrides it)pnpm install, no build, run the gatets-node -T: type-checking made the verdict depend on whether someone had built@pair/content-ops)Testing the Changes
Documentation
Technical Decisions
2026-09-01-a-gate-imports-its-generator-by-source-and-gets-a-read-only-slice.md"composite": false, a@pair/content-opsdevDependency that ordersts:checkand resolves nothing, one hardcoded hop) and runs it transpile-only; code invoked by a gate takes the narrowest fs capability2026-09-01-a-byte-compared-generated-artifact-sorts-by-codepoint.mdeol=lfpin + terminator diagnosis, POSIX separators in emitted paths2026-09-03-a-gate-names-a-remedy-it-can-run.mdConsumed, not re-decided:
2026-08-11-a-mirror-guard-compares-the-transform.md,2026-07-31-pre-push-gate-is-check-only.md,2026-08-20-a-user-facing-cli-fix-carries-its-changeset.md.Docs updated:
DEVELOPMENT.md+ its byte-identical docs-site twin (development-setup.mdx),way-of-working.md(Quality Gates),packages/dev-tools/README.md.One command family per statement: the adoption bullet and the gate's own header name the index's producer as
pair-cli install/pair-cli update(the publishedbinispair-cli), andpair updateappears only where it is explicitly labelled the retired spelling. A file that credits a command with generating the index and denies that binary exists 100 words later re-teaches, in the project's source of truth for the gate, the exact string ADL 2026-09-03 retired.Dependencies & Related Work
Related PRs
pnpm mirrors:regenerate, the mirror-realignment writer. Unmerged, so this branch does not depend on it. The two commands are complementary and differently scoped: that one realigns the dataset mirrors (rewriting four trees and deleting target-only files under amirrorregistry); this one regenerates the single byte-compared index this gate compares. When [#419] feat: mirrors:regenerate — local, deterministic mirror realignment #476 lands it will also rewrite.pair/llms.txtas a side effect — a superset, not a conflict.Follow-up Work
.pair/llms.txt), plus the three reproducibility axes found while building it.origin/main([#413] feat: CI runs format:check — a dedicated workflow, no trigger-shaped hole #477 merged after its base). Rebasing was deliberately left to the merge decision rather than folded into a fix round.