Skip to content

[US-449] docs: every invocation names pair-cli, and the staleness gate can see when it does not - #473

Open
rucka wants to merge 21 commits into
mainfrom
feature/US-449-pair-cli-canonical-docs
Open

[US-449] docs: every invocation names pair-cli, and the staleness gate can see when it does not#473
rucka wants to merge 21 commits into
mainfrom
feature/US-449-pair-cli-canonical-docs

Conversation

@rucka

@rucka rucka commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

PR Information

PR Title: [US-449] docs: every invocation names pair-cli, and the staleness gate can see when it does not
Story/Epic: #449
Type: Documentation (with four tested behavior changes — see Breaking / Behavior Changes)
Assignee: rucka
Labels: documentation, user story, tech-debt, risk:yellow

Summary

What Changed

The published binary is pair-cli. The docs, the CLI's own printed help, the shipped KB dataset, the release scripts, the manual-test procedures, the pre-push gate's printed remedy, .gitignore, the live adoption docs and the source comments describing all of the above said pair. Three things happened here:

  1. The staleness gate learned to see it. INVOCATION_PREFIX now captures the binary (pair-cli|pair) instead of pinning the literal, so a bare pair <cmd> is a wrong-binary error reported once, and the alternation is BUILT from PUBLISHED_BIN (const BINARY = `(${PUBLISHED_BIN}|pair)`) — which is what makes it genuinely the single place the canonical name is written in the website package, rather than one of two.
  2. The CLI's own Usage: line names the binary, and its usage metadata is finally rendered. program.name() was pkg.name, so pair-cli install --help opened with Usage: @pair/pair-cli install [options] — the first line printed, and a copy-paste that answers command not found: @pair/pair-cli. It now comes from the sole bin key of the manifest. metadata.usage was dead (never passed to commander at all): it is wired into cmd.usage(), so the eleven usage: strings the registry gate checks are the line the reader sees.
  3. Everything the widened rule reaches was renamed — plus the surfaces no gate reaches but a human executes or copy-pastes.

Why This Change

A reader copy-pastes pair install from the docs and the shell answers command not found. Nothing in CI could see it, so it spread: run/metadata.ts arrived from #451, after this story was refined, carrying usage: 'pair run [options]', and every green check stayed green.

Story Context

User Story: as a reader of pair's documentation and printed help, I want every invocation to name the binary that actually exists, so that what I copy-paste runs.
Acceptance Criteria: AC1–AC7 on the issue. AC5 (customization/organization.mdx) was already satisfied before this story started — verified zero repo-wide, and the file is untouched here (no numstat row). AC3 is two halves and only one was: the pair kb validate FORM was already zero repo-wide (git show origin/main:…/examples.mdx | grep -c 'pair kb validate' → 0), but the AC's binary requirement was not — reference/cli/{examples,workflows}.mdx carry 67 renamed lines here (43 + 24 per git diff origin/main...HEAD --numstat), e.g. pair install --list-targetspair-cli install --list-targets. AC3 is satisfied BY this PR; read those two files. AC3's sibling form needed a code fix too: see Behavior Changes.

Changes Made

Implementation Details

  • The gate rule (apps/website/lib/docs-staleness-check.ts) — binary captured and judged; separator is ONE literal space (an aligned whitespace column is a diagram, not an invocation — the four PM-tool pages map their hierarchy as pair Linear), which excludes a TAB for the same reason; the runner (npx / pnpm dlx / pnpm exec / yarn dlx, plus a bare pnpm that takes no flag run) is a prefix of the captured binary, not a slot that swallows it, and its flag run consumes --package/-p together with the package name that follows them and accepts the --flag=value spelling; the command token is flag-aware, because pair --version is an invocation too; the span rule tokenizes real code spans and anchors the prefix at the span's content, so neither a closing fence nor a closing inline span can make the text after it an invocation; and the wrong-binary message only prescribes write "pair-cli <cmd>" when that command exists.
  • The CLI's printed help (apps/pair-cli/src/cli.ts) — program.name() = publishedBinName(pkg), the sole bin key; usageArguments() strips the <bin> <command> head commander prints itself so metadata.usage renders verbatim. Pinned against the rendered helpInformation, not the registry field.
  • A second gate, in the CLI (apps/pair-cli/src/commands/index.test.ts) — every registry usage and every examples entry must start with the published binary, read from the sole bin key of apps/pair-cli/package.json rather than compared against a literal, so a bin rename turns the gate red instead of leaving it green over 11 usage strings naming a binary no install creates. usage is held to the full <bin> <command> prefix, which is what keeps cli.ts's fallback branch dead.
  • The rename, wherever the rule reaches and wherever a human executes or copy-pastes: 29 docs pages · all 11 command metadata.ts · printed hints/usage/errors in apps/pair-cli/src and in the scaffold-kb templates generated into a consumer's repo · the pair-cli update regenerate hints in packages/knowledge-hub's mirror tools · the shipped KB dataset (24 spans / 7 files) · qa/release-validation/*.md · scripts/workflows/release/** · DEVELOPMENT.md · PRE_PUSH_REMEDY · .gitignore and the two live adoption documents · source comments and test names, repo-wide (29 spans / 16 files).

Files Changed

149 files, +1573 / −743. apps/website/content/docs (29 pages) + apps/website/lib · apps/pair-cli/src (cli.ts, metadata, printed strings, scaffold templates, the registry gate) · packages/knowledge-hub/dataset + its regenerated twins under root .pair/knowledge/**, .claude/skills/**, .claude/workflows/** · packages/content-ops · packages/dev-tools · qa/release-validation · scripts/workflows/release · DEVELOPMENT.md · .gitignore · 2 adoption docs · one ADL.

The dataset was edited at its canonical source only. The twins are the OUTPUT of a real pair-cli update run; the three mirror guards (mirror-guard, skill-md-mirror, workflow-mirror) assert that inside pnpm quality-gate.

Services to Release

  • @foomakers/pair-cli: the Usage: line, help, examples and error strings now name pair-cli. No flag, command, or exit-code change — the strings a user reads, not the interface they call.
  • @pair/knowledge-hub (KB dataset): the installed .pair/knowledge/** a consumer gets no longer instructs them to run a binary no install creates.

Testing

Test Coverage

  • Unit: docs-staleness-check.test.ts 81 → 106 (every published runner form — npx, the real npx --no @foomakers/pair-cli <cmd>, pnpm dlx, bare pnpm — the --package/-p and --flag=value forms in both directions; plus the non-invocations that must stay unflagged, pnpm --filter <pkg> <script> and pnpm add -D @foomakers/pair-cli; a flag as the command token; the TAB-separator exclusion; closing fence AND closing inline span; the DOUBLED-backtick span in both directions, a doubled span whose CONTENT holds backticks (both shapes the corpus actually carries) and the line-mate invocation each of them used to blind, and the unbackticked prose that is the actual exemption path; the wrong-binary message text, both directions). cli.test.ts + commands/index.test.ts 34 → 40 (the rendered Usage: line top-level and per-command, publishedBinName, usageArguments, the registry gate bound to the published bin). pre-push-gate-composition.test.ts +1.
  • Modified existing tests: packages/knowledge-hub/src/tools/{mirror-guard,skill-md-mirror}.test.ts pin the printed regenerate hint, whose text changed. Assertion text only, no behavior change — flagged because a modified existing test is something a reviewer must be told, not discover.
  • Manual (no automated gate exists for either script): both release scripts were run for real — smoke-test-kb-package.sh against a fixture repo, smoke-test-npm-artifact.sh against a fixture npm tarball. Evidence below.

Test Results

pnpm quality-gate                    ✅ exit 0 — 22/22 turbo tasks
pnpm smoke-tests (full local suite)  ✅ exit 0 — all scenarios
pnpm --filter @pair/website e2e      ✅ 39/39 chromium
docs-staleness-check.test.ts         ✅ 106/106
cli.test.ts + commands/index.test.ts ✅ 40/40
pnpm docs:staleness                  ✅ 44 skills, 11 commands in sync

Tier: Pre-merge tiering: disabled ⇒ the full adopted suite IS the CI-parity set. dup:check prints 18 pre-existing clones and exits 0 — none in a file this PR touches.

Testing Strategy

Every gate here was verified RED before being trusted, and the two ungated scripts were executed:

mutation / run result
revert run/metadata.ts to usage: 'pair run [options]' expected [ 'run.usage: pair run [options]' ] to deeply equal []
rename bin pair-clipairc in apps/pair-cli/package.json 72 offenders; the same mutation under the old literal comparison: green
restore .name(chalk.blue(pkg.name)) and drop the cmd.usage() call expected 'Usage: @pair/pair-cli [options] [comm…' to be 'Usage: pair-cli [options] [command]' + the same for install --help
restore the old INVOCATION_PREFIX 3 of the 4 npx tests fail
rename the published bin: set PUBLISHED_BIN = 'pairx' in the real module and run checkDocsCommands on three fenced lines With the alternation as a LITERAL: pairx install[] — the newly-correct form not recognised as an invocation at all (silent blindness) — while pair-cli install is reported wrong against pairx. With it BUILT from the constant: pairx install[] correctly recognised, pair installwrite "pairx install". Editing only the constant, which the ADL instructs, is now sufficient
restore CODE_SPAN to the single-backtick pairing (/`([^`\n]+)`/g) the 2 new cases fail. Real module, real command dirs: Seepair-cli installthen `pair update` next.[] (the control with single backticks is flagged), Notefooand then `pair update` next.[] — ANY doubled span, even one naming no binary, consumed one of its two closing backticks and flipped span parity for the rest of the line
real gate, a REAL page that already needs a doubled span: append an inline pair install invocation to reference/skill-management.mdx:219 OLD rule: PASS — 44 skills, 11 commands in sync — the wrong binary ships green. NEW rule: FAIL — 1 issuereference/skill-management.mdx tells the reader to run "pair install" … write "pair-cli install". Page reverted ⇒ PASS. Same result on :211; the identical text on line 10 of the same file was flagged either way, which is what isolates the doubled span as the cause
real gate, real pages: edit reference/cli/workflows.mdx:269 to pnpm dlx pair install, :272 to pnpm dlx pair kb validate, tutorials/team-setup.mdx:48 to `pnpm pair install` OLD rule: PASS — 44 skills, 11 commands in sync (green on the exact drift this story exists to catch). NEW rule: FAIL — 3 issues — the two wrong-binary lines plus "pair kb" … is not one of its commands. Docs reverted ⇒ PASS again, with no false positive on the corpus's 39 pnpm --filter / pnpm add / npm install -g lines
real gate, real pages: replace reference/cli/workflows.mdx:269 with npx --package @foomakers/pair-cli pair-cli install (correct) + npx --package=@foomakers/pair-cli pair install (drift) OLD rule: FAIL — 1 issue… run "pair-cli pair-cli", which is not a command: red on the CORRECT line, silent on the drifted one. NEW rule: FAIL — 1 issue… run "pair install" … write "pair-cli install": the correct line clean, the drift caught. Page reverted ⇒ PASS
revert RUNNER_FLAG to a lone-flag run 3 failures: expected [ Array(1) ] to deeply equal [] (the correct --package idiom flagged), … to deeply equal [ Array(1) ] (-p giving the wrong message), expected [] to deeply equal [ Array(1) ] (the =value drift returning [])
restore COMMAND_TOKEN to [A-Za-z][\w.-]* pair --version — the line 9 pages carry — is not seen as an invocation at all; the flag cases fail
restore the "after any backtick" span rule `config.json` pair skills resolve state. is read as an invocation of skills — 1 error where 0 is correct
restore the unconditional wrong-binary remedy expected '…tells the reader to run "pair init"…' to contain '"init" is not one of its commands'
restore pair update in PRE_PUSH_REMEDY expected 'Formatting is checked…' to contain 'pair-cli update'
real binary: node apps/pair-cli/dist/cli.js {--help, install --help, kb-cache --help} Usage: pair-cli [options] [command] · Usage: pair-cli install [target] [options] · Usage: pair-cli kb-cache [list|prune] [options] — the last two are metadata.usage verbatim
real script: smoke-test-npm-artifact.sh against a fixture @foomakers/pair-cli tarball whose bin.pair-cli execs the built CLI BEFORE: 18-line run, both tests and the whole standardized suite skipped, completed successfully, exit 0. AFTER: 1240-line run — Test A + Test B execute the installed binary and run-all.sh --binary …/.bin/pair-cli --ci --offline-only runs the full suite
same script, fixture declaring bin.pair instead exit 1Error: the installed artifact linked no executable at …/.bin/pair-cli
which pin guards the fenceCODE_SPAN content class stripped of its non-backtick anchors (([^\n]+?)) 106/106 green, the closing-fence case included: the anchors are not what any current test discriminates on, so no assertion may claim to guard them
same file, CODE_SPAN reverted to the pre-tokenizer "after a backtick" shape (/(?<=`)\s*()([^\n]*)/g) 7 failed, first of them does not let a closing fence reach into the next paragraph — that case IS the fence's discriminating pin, and it is what the docstring now names

Breaking Changes

No API, CLI-interface or data change. Three behavior changes, all deliberate, all named here because the rest of the diff is a rename:

  1. The staleness rule itself — a bare pair <cmd> in a docs page now fails docs:staleness. Fully tested. It also fixes a latent bug class in the same rule: the span rule used to match after any backtick, so a closing fence bled into the paragraph below ("pair creates Markdown files" → an invocation of creates, 8 real failures the moment bare pair counted) and a closing inline span bled into the prose after it on the same line (`config.json` pair skills … → an invocation of skills, which would turn a correct docs edit red with advice that corrupts the sentence, since pair is the product name on ~10 pages). Tokenizing real spans removes that class. A third member of it: a doubled span left a stray backtick that blinded every later inline invocation on the same line — on lines the corpus already ships. All three are pinned.

  2. scripts/workflows/release/smoke-test-kb-package.sh — two executable lines, not strings:

    • Its CLI detection probed command -v pair and node_modules/.bin/pair — a binary no install creates. On a repo where pair-cli is npm-installed, origin/main's script prints ❌ Error: pair CLI not found and exits before Test 1; this branch prints ✓ Found npm-installed pair-cli.
    • It then ran $PAIR_CMD kb package --output …. The real CLI: kb package --output out.ziperror: unknown option '--output'; package --output out.zip → parses and proceeds. package is a top-level command; kb is not a command group. This is AC3's own pair kb validate defect class — missed at refinement because the AC was checked by grepping one command name instead of the form.
  3. scripts/workflows/release/smoke-test-npm-artifact.sh — the same defect, in the release workflow, plus a warning that hid it. PAIR_BIN/INSTALLED_BIN pointed at node_modules/.bin/pair; the package declares one bin key, pair-cli, so every branch took its fallback — Test A ran npx pair-cli (resolving outside the artifact under test), Test B printed Warning: Test B skipped, the standardized suite printed Warning: Installed binary not found … Skipping suite., and the script exited 0. A release gate green having exercised nothing. Paths fixed, the npx fallback removed, and the two warnings are now one hard exit 1: a smoke test that cannot find the binary it just installed has not passed, it has not run.

  4. Both release wrappers' exit-code diagnostic is now reachable. Each echoed the suite's exit code after a bare call under set -e, so the line printed only when the suite had already passed — never on the one path a diagnostic exists for. || RET=$? keeps it.

Neither release script has an automated gate (neither is in scripts/smoke-tests/**, and CI runs smoke-test-npm-artifact.sh only inside the release workflow), so both were verified by executing them — see the Testing Strategy table.

Documentation

  • Technical Decisions: ADL 2026-08-30-docs-staleness-invocation-rule-and-repo-wide-pair-cli-sweep.md — the gate's reach defines the sweep, the single-space/TAB trade, the npx prefix, the span-tokenizing rule, the printed Usage: line, the comment boundary, and the full in/out scope list. tech/infrastructure.md states the canonical name is enforced; .pair/llms.txt indexes the ADL.
  • PRE_PUSH_REMEDY is in scope as the PRINTED remedy. ADL 2026-07-31-pre-push-gate-is-check-only requires the two-step remedy to agree in three places — DEVELOPMENT.md, its development-setup.mdx twin, and this string. It is the only one a developer copy-pastes out of a terminal, so it is renamed here and pinned by test.
  • Live adoption prose and .gitignore are in scope; append-only history is not. .gitignore's "Generated by pair-cli update" header is a developer-facing instruction, and the two adoption documents state current facts. .pair/adoption/decision-log/**, .pair/adoption/tech/adr/** and the CHANGELOGs keep their pair spellings — that is the record of what was once true.
  • The rename carries no code spans into YAML frontmatter. A page's description is a plain string: app/docs/[[...slug]]/page.tsx renders it raw in <DocsDescription> (the subtitle under the title) and hands the same string to metadata.description (the <meta name="description"> a search/social preview shows), so backticks there would be two visible characters in both. Verified in the prerendered HTML of the three pages whose description names a command. Frontmatter prose is also outside the staleness rule's positional reach by design, so nothing is lost by writing it bare.
  • Why PUBLISHED_BIN in the website package is a literal while the CLI reads its manifest — recorded as a rejected alternative in the ADL: having apps/website read apps/pair-cli/package.json would couple a separate deployable for one string, and the CLI registry gate (which reads the sole bin key) is what makes a bin rename impossible to land with the docs gate silently still prescribing the old name. It is one literal: the captured alternation is derived from it, so acting on that record is a one-line edit. The legacy pair stays a literal beside it because it is the known-WRONG binary, not a second name for the published one.
  • Deliberately left bare (so their pair is not read as a miss): scripts/smoke-tests/**/*.sh (every .sh under it, scenarios/ and lib/ included) including their comments and log lines (run_pair is a shell function, not the binary — its README is in scope: it describes what each scenario verifies in the reader's own terms, so its pair install was a copy-paste that fails) · .github/workflows/*.yml step names · the product name in prose ("pair installs bridge files") · and fixtures or comments that deliberately quote the WRONG form as the defect being illustrateddocs-staleness-check.ts, the wrong-binary cases in docs-staleness-check.test.ts, commands/index.test.ts's quote of the shipped usage: 'pair run [options]', and ADL 2026-07-31's Context. Renaming those erases the evidence the gates exist for.

Reviewer Guide

Why the diff is 149 files and not the ~15 the story enumerated

The gate defines the reach, and the DoD requires docs:staleness green. Measured, not estimated: run this branch's rule over origin/main's docs tree (all 84 pages) ⇒ 78 wrong-binary errors across 28 pages. The sweep changed 29 — those 28 plus customization/adopt.mdx, whose single occurrence sits in a fenced tree-diagram comment, outside the positional rule, renamed for consistency and named as such in the ADL rather than hidden in the count. The remaining files are the ungated surfaces a human executes (release scripts, QA procedures, scripts/smoke-tests/README.md), the shipped dataset, the CLI's printed help, the pre-push remedy, and the source comments describing the same invocations.

Review focus

  1. apps/website/lib/docs-staleness-check.ts — the only non-mechanical docs logic. The docstring carries each rule's reason, including two knowing trades: neither pair-cli <cmd> (two spaces) nor pair-cli\t<cmd> (tab) is flagged — no such form exists in the docs today — in exchange for immunity to the alignment diagrams; and npx/pnpm dlx/pnpm exec/yarn dlx consume a flag run while a bare pnpm does not, because pnpm --filter <pkg> puts a package name in flag-argument position and this repo's package is literally called pair-cli (pnpm --filter @pair/pair-cli build would be reported as the nonexistent command build on three correct pages). The rule that makes that asymmetry checkable rather than folkloric: a flag whose ARGUMENT is a package name is consumed WITH its argument and must be listed in RUNNER_FLAG (--package/-p is), and a runner with an unlisted one gets no flag run at all — which is bare pnpm/--filter. Both trades say what to narrow instead of re-widening, and every case is pinned. CODE_SPAN implements CommonMark's own closer rule — an opening RUN of N backticks closed by the first run of exactly N, content free to hold runs of any other length and forbidden to begin or end with one (so a fence still yields no span). That is not pedantry: pairing single backticks read a doubled delimiter's own content fine, but consumed only ONE of its two closing backticks, and the leftover flipped span parity for the REST of the line — every later inline invocation on it went unseen, silently. The corpus already carries three inline doubled spans over two lines (reference/skill-management.mdx:211, which holds two, and :219, which holds one — each quoting a backticked literal), so it was live, not latent. Doubling is still not an exemption; the way to quote a wrong form on a docs page is unbackticked prose, outside this positional rule by construction. One property the suite deliberately does not assert: "a fence yields no span" is unobservable through checkDocsCommands — a fenced line is scanned by the fence pass regardless and errors dedup by binary+command, so both passes reading the same line collapse to one error. The fence is pinned through what it can change (the fence pass's own cases, and the closing fence that must not reach the paragraph below). The two fixtures that reproduce the corpus's doubled spans are :211/:219 plus the inline invocation an author would add on the same line — that line-mate IS the case, so dropping the appended invocation from either fixture leaves a line with no invocation and the suite goes red on the spot (measured, both). Dropping it from cell additionally leaves :211 byte-for-byte, i.e. duplicates the corpus-verbatim assertion beside it; prose is only the span-bearing excerpt of :219 (the corpus clause runs on), so there is no restored-corpus reading of it at all. The test comment states exactly that.
  2. apps/pair-cli/src/cli.ts — the program name and the newly rendered cmd.usage(). The tests assert the rendered help, not the registry field, because a gate on an unrendered field is exactly what shipped before.
  3. apps/pair-cli/src/commands/index.test.ts — the registry gate reads the published bin from the manifest; a second bin key fails the test too.
  4. scripts/workflows/release/smoke-test-{kb-package,npm-artifact}.sh — the executable lines, and the warning→failure change in the second.
  5. Everything else is a rename; skim it for a missed span rather than read it line by line. Residue check: git grep -nE '$?pair [a-z][a-z-]*'minus the declared exclusions returns only the deliberate wrong-form fixtures and.pair/llms.txt`'s generated index entry for an excluded record's title.

Testing the changes

git checkout feature/US-449-pair-cli-canonical-docs
pnpm install && pnpm turbo build --filter=@pair/knowledge-hub...
pnpm quality-gate
pnpm --filter @pair/website exec vitest run lib/docs-staleness-check.test.ts   # 106
pnpm --filter @pair/website docs:staleness                                     # the real gate on the real pages
pnpm --filter @pair/pair-cli exec vitest run src/cli.test.ts src/commands/index.test.ts
pnpm --filter @pair/dev-tools exec vitest run src/quality-gates/pre-push-gate-composition.test.ts
node apps/pair-cli/dist/cli.js install --help | head -1   # Usage: pair-cli install [target] [options]

@rucka rucka added documentation Improvements or additions to documentation user story Work item representing a user story tech-debt Tracked technical debt (living backlog, R7.2 — never blocks a PR) risk:yellow Classification: medium risk tier labels Aug 30, 2026
@rucka rucka self-assigned this Aug 30, 2026
@rucka rucka added the pr-state:to-be-reviewed PR state: awaiting review / gate label Aug 30, 2026
@rucka

rucka commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict

risk:yellow · cost:n/aCHANGES-REQUESTED — the rename is correct and the gate widening is proven, but three surfaces the ADL claims to cover are still bare, and one of them was invalidated by this diff.

Open findings: 6. (2 Major, 3 Minor, 1 Question; plus 1 non-actionable CI note.)

PR: #473 · Author: Gianluca Carucci · Reviewer: independent reviewer agent · Date: 2026-08-30 · Story: US-449 · Type: docs

Classification matrix — per dimension
Dimension Tier Source Note
Service/domain criticality green tech/risk-matrix.mdapps/pair-cli, apps/website = Low confirmed
Change/diff risk yellow 102 files / 4 workspaces, string-only confirmed (wider than refinement, same kind)
Business impact yellow Adoption & Guidelines (Supporting) confirmed
Security relevance green /pair-capability-assess-security not invoked; diff is display strings + one regex, no input/authz surface no vulnerability introduced
Coupling balance not assessed /pair-capability-assess-coupling until #263

Tier = max(assessed) = yellow, unchanged from refinement.

Assessments

Security — Input validation

Verdict: green — no input path touched; the only logic change is a docs-lint regex over repo-local files.

Security — Output handling

Verdict: green — changed strings are CLI help/error text, no encoding surface.

Security — Authentication / Authorization

Verdict: green — not applicable, no auth path in the diff.

Security — Introduced vulnerabilities

Verdict: green — 0 introduced, 0 pre-existing in scope. The regex is anchored and bounded ([A-Za-z][\w.-]*), no catastrophic-backtracking shape. gitleaks --log-opts origin/main..HEAD on the three commits: no leaks found.

Cost

Verdict: cost:n/a — cost dimension not in this project's Active list.

Architecture (Coupling)

Verdict: not assessed — until #263.

Bug fix — Red test before fix

Verdict: green — the latent fence-bleed bug carried in T-2 has a dedicated reproduction (docs-staleness-check.test.tsdoes not let a closing fence reach into the next paragraph), and it fails on the pre-fix ` + \s* shape.

Details

Findings by severity

Critical (must fix before merge)

  • none.

Major (must fix before merge)

  • qa/release-validation/CP3-cli-install-update.md:170,385,408 — this PR changed the two printed error strings (install/handler.ts:249Use 'pair-cli update' to update.; update/handler.ts:196Use 'pair-cli install' first.) but left the manual-test expectations that pin them. A release-validation operator running CP3 §"install over an existing install" now reads "Error message suggests using pair update instead", sees pair-cli update on screen, and has to decide whether that is a FAIL — the release checklist asserts text the CLI no longer prints. The ADL justifies leaving qa/release-validation/*.md bare on the grounds that only headings are affected; lines 170/385/408 are expected-output assertions, not headings, so the stated rationale does not cover them. Fix: update those three lines to pair-cli, and the scope lines that share the wrong name (CP3:4, CP8-packaging.md:4, README.md:40).
  • packages/knowledge-hub/dataset/** (7 files, 24 occurrences) — the shipped KB dataset was not swept, so the exact copy-paste failure the story exists to remove survives in the product's own installed knowledge: guidelines/collaboration/working-area.md (12 — e.g. "rejected by pair validate-config", "Touched by pair update?"), .skills/loop/SKILL.md (3 — pair run), technical-standards/ai-development/skill-conventions/guided-quick-setup.md (4 — pair package), infrastructure/cicd-strategy/tier-aware-pipeline.md (2 — pair install), quality-assurance/delivery-metrics.md (1), skill-conventions/README.md (1), .workflows/pair-loop.js (1), plus the generated twins under root .pair/knowledge/** and .claude/skills/**. Concrete: a consumer runs pair-cli install, opens the installed .pair/knowledge/guidelines/collaboration/working-area.md, and every command it names (pair install, pair update, pair validate-config) is a command not found. By the gate's own positional rule — a binary at the start of a code span is an invocation — these are invocations of a binary that does not exist; they are simply outside the gate's apps/website/content/docs scope. The ADL's scope clause ("every surface that documents or prints an invocation") and its explicit exclusion list do not mention the dataset at all, so this is an unclaimed gap rather than a declared boundary. Fix: rename them in the dataset and regenerate the mirrors in the same commit (pair-cli update, per DEVELOPMENT.md's two-step remedy) — ~24 mechanical spans.

Minor (must fix before merge — same bar as Major, just lower impact)

  • scripts/workflows/release/smoke-test-kb-package.sh:47 — prints Please run 'pair install' first when .pair/ is missing. This is a printed hint to a human, which the ADL puts explicitly in scope, and it is not covered by any of the three declared exclusions (scripts/smoke-tests/**, workflow step names, QA headings) — it lives under scripts/workflows/release/, and pair install here is an instruction, not a shell-function name. Fix: pair-cli install.
  • apps/website/lib/docs-staleness-check.ts:446,461 — the docstring says the binary counts "optionally behind $ or npx [--no] <pkg>", but no npx-prefixed invocation can ever match, so the four tutorial/troubleshooting pages that use that form are unchecked. Verified by calling the exported function on this branch: checkDocsCommands returns [] for both ```bash\nnpx --no @foomakers/pair-cli kb-valdate\n``` and ```bash\nnpx pair-cli bogus-command\n``` — a misspelled command behind npx ships silently, on pages that really do use it (support/troubleshooting.mdx:28,220, tutorials/first-project.mdx:73, existing-project.mdx:48, team-setup.mdx:48, enterprise-adoption.mdx:76). The suite's one npx test (npx --no pair-cli update) passes only by an accidental parse — --no is consumed as the package token and the literal pair-cli then matches as the binary — so it gives false confidence, and AC7's npx edge case is not actually verified. Fix: make the runner clause a prefix of the captured binary, e.g. (?:npx\s+(?:--no\s+)?)?(?:@[\w.-]+/)?(pair-cli|pair) , and add a test using the real @scope/pair-cli form.
  • apps/pair-cli/src/commands/*/metadata.ts (no covering test) — the surface where 11 of the drifted files lived stays ungated: nothing asserts that usage/examples name pair-cli, and docs:staleness reads only apps/website/content/docs. Concrete recurrence, already observed once: run/metadata.ts arrived from pair-cli execution adapter: run a card on any engine (pi, opencode, claude -p) — portable fan-out, amends ADR-017 #451 after refinement carrying the bare pair run form, and no gate said anything — the next command added the same way regresses the same way while CI stays green, which is exactly the "invisible and could recur" this PR names as its reason to exist. Fix: one conformance test over the command registry asserting every usage and every examples entry starts with pair-cli .
  • .pair/adoption/decision-log/2026-08-30-docs-staleness-invocation-rule-and-repo-wide-pair-cli-sweep.md:23 — the bolded claim reads "The real reach is 32 docs pages, not 2", while the same sentence and the Decision section say 28 published docs pages, and the diff touches 29 files under apps/website/content/docs. A decision record is read later as fact; three numbers for one quantity means none of them can be trusted. Fix: state one number and make it the measured one.

Questions (informational, never blocking)

  • apps/website/lib/docs-staleness-check.ts:459 — narrowing the separator from \s+ to a single literal space also removed multi-space coverage the old rule had: checkDocsCommands returns [] for ```bash\npair-cli bogus-two-spaces\n```, which main would have flagged. No docs page uses that form today (grep for `pair-cli +[a-z] over apps/website/content/docs: zero hits), so this is latent, and the diagram rationale for the single space is sound. Worth a line in the docstring saying the trade was made knowingly?
Positive feedback
  • The widening is proven, not asserted: feeding main's reference/cli/commands.mdx to this branch's checkDocsCommands yields 11 distinct wrong-binary findings, i.e. the gate really does catch the drift it was blind to.
  • The wrong-binary error short-circuits the unknown-command branch, so a renamed line is reported once — with a test that pins the "once".
  • The \s*-crosses-a-closing-fence bug was found, reproduced and fixed rather than worked around, and the diagram case is handled by a positional rule instead of an allow-list, consistent with the rule the file already argued for.
  • PUBLISHED_BIN gives the canonical name exactly one home in the checker.
Functionality & requirements (AC coverage)
AC Verdict Evidence
AC1 — help output names pair-cli met all usage/examples + cli.ts help footer renamed
AC2 — every metadata.ts invocation starts pair-cli met grep -E "'(\$ )?pair " over the 11 files → 0 hits (no gate protects it — Minor 3)
AC3 — examples.mdx / workflows.mdx; zero pair kb validate met repo-wide grep → 1 hit, the ADL's own retrospective mention
AC4 — DEVELOPMENT.md table + 2 prose mentions met verified in diff
AC5 — organization.mdx carries no bare invocation met only pair-cli package / pair-cli install present
AC6 — bare pair <cmd> reported as an error met 3 new tests + 11 findings on main's commands.mdx
AC7 — existing behavior regression-free partial prose/version/flag/unknown-command all still pass; the npx edge case named in AC7 is not verified — it never matched (Minor 2)
BR — zero bare pair <cmd> in the "Where" files met
BR — no dual naming partial 24 bare invocations remain in the shipped dataset (Major 2), 1 in a release script (Minor 1)
Testing & quality gates
  • apps/website docs-staleness-check.test.ts81/81 PASS (run in a detached worktree pinned to 862b3704).
  • pnpm docs:staleness equivalent (tsx lib/docs-staleness-check.ts) — PASS, "44 skills, 11 commands in sync".
  • CI on this head: build SUCCESS, smoke SUCCESS, preview SUCCESS, secret-scan FAILURE — see the non-actionable note below.
  • Coverage gap: no test binds the printed hints/metadata.ts strings to the canonical name (Minor 3).
Adoption compliance
  • Degradation level: 1 — decision recorded as an ADL, indexed in .pair/llms.txt, infrastructure.md updated with the enforcement fact, cross-referenced both ways.
  • No new dependency, no architecture change; the ADL correctly records the scope expansion rather than hiding it.
  • ADL scope statement overstates its reach (Major 2) — the content is right, the boundary is not.
Tech debt
  • The gate covers one of the three surfaces this PR renamed (published docs). The other two (metadata.ts/printed strings, shipped dataset) remain rename-by-hand — Minor 3 closes the first cheaply.
Non-actionable (recorded, not blocking on this PR)
  • CI secret-scan red on 862b3704gitleaks reports 1 leak (generic-api-key, scripts/smoke-tests/scenarios/pr-state-flow.sh:430, TOKEN_HEAD='…'). It is not from this diff: the finding's commit is 5e99d538 on feature/US-398-solo-maintainer-approval-token, which git merge-base --is-ancestor 5e99d538 HEAD reports as NOT an ancestor of this branch, and a scan scoped to origin/main..HEAD (3 commits) reports no leaks found. CI's checkout fetches all refs, so the job sees another branch's commit. It still holds the merge gate red until that branch is fixed — flagged for the human merge gate, not fixable here.
Documentation
  • Docs, ADL, adoption and index updated together.
Performance & deployment
  • No hot path touched; the added capture group is O(1) on an already-linear scan.
  • Rollback = revert; strings only, bin unchanged.

rucka added a commit that referenced this pull request Aug 30, 2026
… ones

Review round 1 on PR #473. Seven findings, all fixed in place.

Swept (the sweep's own reach, not the file list):
- shipped KB dataset (24 spans / 7 files) + the mirrors regenerated by the real
  `pair-cli update`, never hand-edited — an installed KB was telling consumers to
  run commands no install creates
- qa/release-validation: the three expected-output assertions an operator compares
  against the real error message, plus the Scope/MT-* lines
- scripts/workflows/release: hints, comments, and TWO EXECUTABLE lines in
  smoke-test-kb-package.sh — detection probed `node_modules/.bin/pair` (aborts on a
  repo where pair-cli IS installed) and it ran `kb package --output`, which the CLI
  answers `error: unknown option '--output'`

Gates that did not exist:
- commands/index.test.ts asserts every registry `usage`/`examples` names pair-cli —
  the surface where #451's `pair run` slipped past every green check
- the npx clause was a package SLOT that swallowed the binary, so no npx-prefixed
  invocation could ever match; the runner is now a prefix of the captured binary

ADL: one measured number (29 pages), dataset+release-scripts moved into the stated
scope, the two behavior changes named for review.

Refs #449
@rucka rucka changed the title [#449] docs: pair-cli canonical invocations + gate widened to catch a bare pair <cmd> [US-449] docs: every invocation names pair-cli, and the staleness gate can see when it does not Aug 30, 2026
@rucka

rucka commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator Author

In response to the first review comment.

Escalation — round 14 (13 review rounds, working log: .pair/working/reviews/449.md)

No prior escalate-flush or convergence-synthesis comment exists on this PR to minimize/mark-outdated — this is the first escalation for this cycle.

Rounds so far (summary — full detail + evidence per round in the working log)

round commit findings addressed
1 6a58aa61 canonical rename sweep (qa/release-validation, knowledge-hub dataset, smoke-test hint, npx runner regex, command usage/examples, ADL)
2 7f8939f3 pre-push-gate printed remedy + docstring, 29-span sweep, registry gate reads bin from manifest, doubled-space docstring
3 294d586a commander .name()/.usage() wired to manifest, npm-artifact smoke-test hard-fail, code-span tokenization, wrong-binary message wording, .gitignore + adoption docs sweep, pair update regex fix
4 1bcd1d7a frontmatter description backticks (3 pages), ADL rejected-alternative note
5 a15b802b flag-aware COMMAND_TOKEN, smoke-test exit-code-after-set-e fix, migrations sweep follow-on
6 810e1bfb smoke-tests README sweep, runner-group widening (npx/pnpm dlx/pnpm exec/yarn dlx/bare pnpm)
7 44c0d551 RUNNER_FLAG package-argument consumption, docstring/ADL criterion correction, exclusion glob broadened
8 4435e922 doubled-delimiter docstring correction, derived BINARY alternation, ADL "single place" claim made true, PR body AC3 scope correction
9 c6a85844 CommonMark closer-rule code-span regex (fixes real corpus lines skill-management.mdx:211,219), ADL decision-1/exclusion corrections
10 bff4e614 dropped a fence assertion that pinned nothing (measured), off-by-one comment fix
11 7c4e120f cell/prose fixture description precision (verbatim vs excerpt)
12 da9a5b1c "restore" wording replaced with the measured red-suite consequence (both fixtures go red, not silent collapse)
13 b1cef967 "restore to its corpus line" narrowed to fit only cell (the edit, not the noun) — prose has no corpus line

Quality gates have stayed green every round (pnpm quality-gate, pnpm smoke-tests, website e2e, docs-staleness-check.test.ts, docs:staleness). Nothing escalated or deferred in rounds 1-13.

Still-open actionable findings (round 14)

  • [Questions] apps/website/lib/docs-staleness-check.test.ts:589 — Cosmetic ragged wrap left by the round-13 edit, with no failure case. Line 589 (// exists and it simply turns red. A delimiter run that is balanced but whose) measures 79 chars while the rest of the same comment block measures 94-100 (579=102, 580=97, 581=97, 582=98, 583=97, 584=98, 585=100, 586=94, 587=99, 588=96, 589=79, 590=99). The short line sits mid-paragraph, reading as if a sentence ended there when it does not. Raised as a Question, not a defect: it cannot break the gate — prettier does not reflow // comments and the file passes verbatim (prettier --config tools/prettier-config/.prettierrc.json --check reports clean despite printWidth 100; line 579 is pre-existing and already exceeds it at 102). Recommendation (optional, human's call): re-flow lines 587-590 to fill to ~98 chars uniformly. No functional change either way.

Convention for continuing this cycle

Further rework or re-review on this story — including manual out-of-band rounds — should be appended to the working log .pair/working/reviews/449.md, not posted as standalone PR comments. The next orchestrated run on this story continues the same cycle; its eventual convergence will synthesize ONE final remediation comment and minimize this one plus the first-review comment.

Note: the working log is an untracked file living only in the persistent authoring worktree ../pair-worktrees/449. That worktree must be preserved until merge — if it is pruned/recreated, the audit trail of all 13 rounds is lost (this comment and the first-review comment would still remain on the PR, and the first-review signal still prevents a duplicate first review on the next run, but the round-by-round evidence would not).

Not merged. No human decision has been made on the open Question above.

Bound re-review to immutable deltas and require a complete finite-state inventory before review or remediation.
Require real boundary evidence for externally-defined state and repair claims.\n\nRefs: #416
@rucka
rucka force-pushed the feature/US-449-pair-cli-canonical-docs branch from b1cef96 to 2c76abf Compare September 2, 2026 05:39
rucka added a commit that referenced this pull request Sep 2, 2026
… ones

Review round 1 on PR #473. Seven findings, all fixed in place.

Swept (the sweep's own reach, not the file list):
- shipped KB dataset (24 spans / 7 files) + the mirrors regenerated by the real
  `pair-cli update`, never hand-edited — an installed KB was telling consumers to
  run commands no install creates
- qa/release-validation: the three expected-output assertions an operator compares
  against the real error message, plus the Scope/MT-* lines
- scripts/workflows/release: hints, comments, and TWO EXECUTABLE lines in
  smoke-test-kb-package.sh — detection probed `node_modules/.bin/pair` (aborts on a
  repo where pair-cli IS installed) and it ran `kb package --output`, which the CLI
  answers `error: unknown option '--output'`

Gates that did not exist:
- commands/index.test.ts asserts every registry `usage`/`examples` names pair-cli —
  the surface where #451's `pair run` slipped past every green check
- the npx clause was a package SLOT that swallowed the binary, so no npx-prefixed
  invocation could ever match; the runner is now a prefix of the captured binary

ADL: one measured number (29 pages), dataset+release-scripts moved into the stated
scope, the two behavior changes named for review.

Refs #449
rucka and others added 5 commits September 3, 2026 20:51
Questions are, by the review template's own definition, questions for the human:
items the reviewer marks "No change requested". The floor defaulted to none, so
they entered the set the fixer must resolve, and each answer enlarged the diff and
created fresh surface for the next review.

Measured on PR #477: the PR reached a zero-actionable APPROVED twice; both times
the following round implemented the Questions and the re-review found new Minor
findings inside the code that round had just added (three, then two).

Work now completes when Critical, Major and Minor are closed. An explicit
severityFloor still wins. The default is applied softly - a template without
Minor, or without a usable ranking, falls back to no floor instead of throwing,
because a default must not break a run that never asked for it.

- ADL 2026-09-03-the-review-severity-floor-defaults-to-minor.md
- three tests: Questions-only converges and is carried, a Minor still blocks,
  a vocabulary without Minor falls back

Refs: #219

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… yaml-parsed allow-list (#477)

The check-only pre-push gate was the sole enforcement point for formatting, and `--no-verify` skips it. CI now runs `format:check` in a dedicated workflow, and the workflow's own shape is guarded by a composition check.

The guard parses `.github/workflows/format.yml` with yaml@2.8.2 and expresses every rule as an allow-list over the parsed document, per ADL 2026-09-01; the hand-rolled line reader it replaces is retired. Nothing is ever rewritten in CI — write-mode formatters are banned by the same guard.

Also carries a path-scoped `.gitleaks.toml` allowlist for a synthetic 40-hex SHA fixture in scripts/smoke-tests, which gitleaks matched as a generic-api-key at historical commit 5e99d53.

Closes #413

🤖 Generated with [Claude Code](https://claude.com/claude-code)
The published bin is `pair-cli` (ADL 2026-08-25); every documented and
printed invocation said `pair`, which no npm install ever creates — a
copy-paste that fails with `command not found`.

- 310 invocations across 28 published docs pages (not the 2 the
  refinement enumerated: commands.mdx alone carried 81)
- all 11 command `metadata.ts` usage/examples (the ten refined + `run`)
- printed hints, usage and error strings in `apps/pair-cli/src`, the
  `scaffold-kb` templates it generates, and the `pair-cli update`
  regenerate hints in `packages/knowledge-hub`'s mirror tools
- `DEVELOPMENT.md`'s command table and mirror-guard prose

Prose mentions of "pair" as the product name, the PM-tool mapping
diagrams, and `run_pair` in the smoke suite are left untouched.

Refs: #449
`INVOCATION_PREFIX` matched the literal `pair-cli`, so the gate that
exists to catch drift in documented commands was structurally blind to
the wrong binary. It now captures the binary and judges it.

- bare `pair <cmd>` is its own error, reported once (never also as an
  unknown command)
- separator is ONE space, not `\s+`: an aligned column is a diagram
  (`pair                    Linear`), never an invocation
- span rule allows only horizontal whitespace after the backtick — a
  CLOSING fence ends with one, and `\s*` reached across the newline
  into the next paragraph ("pair creates Markdown files" read as a
  command). Latent while only `pair-cli` matched; 8 failures once bare
  `pair` counted.

Tests written first (RED verified) for the new error, the shell-prompt
and once-only cases, and for the diagram/prose/fence-reach
non-regressions; npx, version-string and flag cases unchanged.

Refs: #449
The 2026-08-25 ADL scoped the rename from a two-file grep; turning the
gate on found 310 invocations across 28 pages, plus the diagram and
closing-fence false positives the rule had to answer. Recorded as an
ADL rather than left in a commit message.

- ADL 2026-08-30: binary-capturing rule, one-space separator, no
  fence-reach, and the sweep's real perimeter (incl. what stays bare)
- infrastructure.md: the canonical name is now ENFORCED by the
  docs:staleness gate, cross-referencing both ADLs
- .pair/llms.txt: index the new decision (generator conformance)

Refs: #449
rucka and others added 14 commits September 3, 2026 21:43
… ones

Review round 1 on PR #473. Seven findings, all fixed in place.

Swept (the sweep's own reach, not the file list):
- shipped KB dataset (24 spans / 7 files) + the mirrors regenerated by the real
  `pair-cli update`, never hand-edited — an installed KB was telling consumers to
  run commands no install creates
- qa/release-validation: the three expected-output assertions an operator compares
  against the real error message, plus the Scope/MT-* lines
- scripts/workflows/release: hints, comments, and TWO EXECUTABLE lines in
  smoke-test-kb-package.sh — detection probed `node_modules/.bin/pair` (aborts on a
  repo where pair-cli IS installed) and it ran `kb package --output`, which the CLI
  answers `error: unknown option '--output'`

Gates that did not exist:
- commands/index.test.ts asserts every registry `usage`/`examples` names pair-cli —
  the surface where #451's `pair run` slipped past every green check
- the npx clause was a package SLOT that swallowed the binary, so no npx-prefixed
  invocation could ever match; the runner is now a prefix of the captured binary

ADL: one measured number (29 pages), dataset+release-scripts moved into the stated
scope, the two behavior changes named for review.

Refs #449
The three places ADL 2026-07-31 requires to agree had drifted to two: the
two documents said `pair-cli update`, PRE_PUSH_REMEDY still printed
`pair update` — the copy-pasted one, and the one that does not exist.
Pinned by test now.

Comment boundary declared instead of accidental: 29 spans over 16 files
swept (content-ops, knowledge-hub tools + conformance, dev-tools,
docs-staleness-check itself), the deliberate wrong-form examples left
alone, both stated in the ADL scope clause.

Registry gate reads the sole `bin` key from package.json instead of a
literal, so renaming the published name turns it red (72 offenders)
instead of green. Separator docstring now covers TAB, pinned.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
…d nothing

Review round 3.

- cli.ts: program name = the sole `bin` key, not `pkg.name` — `--help` opened
  `Usage: @pair/pair-cli install [options]`, a copy-paste that answers
  `command not found`. `metadata.usage` was DEAD (never passed to commander);
  now rendered via `cmd.usage()`, so the registry gate and the reader assert the
  same string. Tests read the RENDERED help.
- smoke-test-npm-artifact.sh: `.bin/pair` → `.bin/pair-cli` (npm links only the
  declared bin key). Every branch took its fallback: Test A ran `npx`, Test B and
  the whole standardized suite were SKIPPED, exit 0. Warnings → hard failure.
- docs-staleness-check.ts: span rule tokenizes real code spans instead of
  matching after ANY backtick — a CLOSING inline span + prose was read as an
  invocation. Wrong-binary message no longer recommends a command that does not
  exist.
- `.gitignore` + 2 live adoption docs swept; pre-push negative pin uses a
  lookbehind so offset 0 is covered.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
The rename put `pair-cli run` / `pair-cli update` inside YAML frontmatter
`description`, which page.tsx renders raw in <DocsDescription> and in
metadata.description: the /docs/integrations/pi subtitle and its
<meta name="description"> (the Google/social snippet) both carried two
visible backticks. Verified against the real build output before/after.

Same defect on migrations/v0.4-to-v0.5 (pre-existing, line already in this
diff) — same contract, swept with it. No gate added: frontmatter prose is
outside the staleness rule's positional reach by design.

ADL: names the CLI registry gate (reads the sole `bin` key) as what keeps
the website's PUBLISHED_BIN literal safe — the asymmetry is deliberate.
…ics were unreachable

`pair --version` was invisible: the command token was `[A-Za-z][\w.-]*`, so a
leading `-` failed the WHOLE prefix and the line was never seen as an
invocation. 9 pages carry that exact form correctly spelled — the slip is one
edit away. Token is flag-aware now; only the binary half runs on a flag.

Both release wrappers echoed the suite exit code AFTER a bare call under
`set -e`, so the line only ever printed on success. `|| RET=$?` keeps the
diagnostic reachable on the one path it exists for.

Refs #449
… a binary no install creates

`npx` was the only runner the invocation prefix knew, so every pnpm/yarn form
the docs actually publish sat outside the rule: `pnpm dlx pair install` ->
NO MATCH, `pnpm pair install` -> NO MATCH. Drop the `-cli` on
workflows.mdx:269 or team-setup.mdx:48 and the gate returned [] — verified on
the real pages, old rule PASS / new rule FAIL with 3 issues. Bare `pnpm` takes
no flag run on purpose: `pnpm --filter @pair/pair-cli build` would otherwise
report `build` as a nonexistent command on three correct pages. Both halves
pinned.

scripts/smoke-tests/README.md still told a contributor to run `pair install`,
`pair package`, `pair kb-validate` and four more. The ADL excluded the whole
directory on a `run_pair`-is-a-shell-function rationale that does not reach a
README describing what each scenario verifies; exclusion narrowed to `**.sh`.

Refs #449
…e flag

`npx --package @foomakers/pair-cli pair-cli install` is correct and is the
canonical npx idiom when a bin differs from its package name. The flag run ate
`--package` alone, `@foomakers/` became the scope, the first `pair-cli` the
binary, and the REAL binary token its command: `"pair-cli pair-cli" … is not a
command` — CI red on a correct page, no edit clears it but deleting a correct
line. Other direction: no `=value` form existed, so
`npx --package=@foomakers/pair-cli pair install` returned [] — drift, green,
behind a LISTED runner. Same for `-p` and for `pnpm dlx --package`.

RUNNER_FLAG consumes `--package`/`-p` with its argument and accepts
`--flag=value`. Bare `pnpm` still takes no flag run — its `--filter` is exactly
such a flag and is deliberately unlisted, which is now the criterion the ADL
states instead of "flags cannot take a package argument", which the code
contradicted.

ADL exclusion glob `scripts/smoke-tests/**.sh` → `**/*.sh`: `**` adjacent to
`.sh` degrades to `*` in one segment, so the recorded rule left
`scenarios/*.sh` and `lib/*.sh` — where `run_pair` lives — reading as in scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
…ten twice

The `CODE_SPAN` docstring claimed a doubled delimiter (``pair install``) is
invisible to the gate and prescribed "balance the delimiter run". False: the
attempt at the outer backtick fails, the scan retries one char on and pairs the
INNER delimiters. Real `checkDocsCommands` on `Run ``pair install`` now.` →
`write "pair-cli install"`. A writer trusting the comment to quote a wrong form
deliberately got an unexplained red plus a remedy that adds nothing. Paragraph
corrected, both directions pinned (RED: a lookbehind/lookahead CODE_SPAN — the
shape the comment described — fails the 2 new cases).

`INVOCATION_PREFIX` hardcoded `(pair-cli|pair)` beside `PUBLISHED_BIN`, so the
name lived in two places while comment + ADL called the constant the single one.
With `PUBLISHED_BIN='pairx'`: literal form → `pairx install` returns [] (blind to
the new drift) and `pair-cli install` reported wrong against `pairx`. Built from
the constant now → `pairx install` clean, `pair install` → write "pairx install".

ADL: the "single place" claim made true (alternation derived), doubled-span
coverage recorded, and AC3 scoped — only the `pair kb validate` FORM was already
zero; the 67 bare-`pair` lines on reference/cli/{examples,workflows}.mdx are
renamed BY this branch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
`CODE_SPAN` paired single backticks, so a doubled span consumed ONE of its
two closing backticks; the leftover flipped span parity and every later
inline invocation on that line went unseen — silent, no error. Live, not
latent: `reference/skill-management.mdx:211,219` already carry inline
doubled spans (they quote a backticked literal), and appending an inline
`pair install` to :219 shipped green on the real gate.

Balancing the run alone is not enough — the reason to double a delimiter
is backticked CONTENT — so `CODE_SPAN` implements CommonMark's closer
rule: opening run of N, closed by the first run of exactly N, content
free to hold other runs and forbidden to start/end with a backtick (so a
fence still yields no span). Docstring + ADL decision 3 corrected: they
asserted the opposite.

Also: ADL decision 1's rename evidence labels the two `[]` (blind vs
correct) and adds the discriminating `pair-cli install` -> silent case;
the wrong-form exclusion is stated per FILE, not per line — its
`:499,510-511` was off by four from the round it was written and never
covered the spans later rounds added.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
… count off by one

"A fence yields no span" was asserted through checkDocsCommands, which cannot
observe it: a fenced LINE is scanned by the fence pass anyway and errors dedup
by binary+command, so the two passes reading the same line collapse to one
error either way. The fixture's only invocation was the CORRECT `pair-cli
install`, so it returned [] from the fence pass alone. Measured: the whole
suite stays green under a content class stripped of the non-backtick anchors
(`[^\n]+?`) — 106/106 — so the assertion would not have caught the degradation
it was written to guard. The fence's observable pin is the closing-fence case,
which DOES go red (7 failures) under the pre-tokenizer "after a backtick" shape.
Assertion dropped, comment says which pin is real; docstring/ADL "the fence is
pinned" narrowed to what it can actually change.

The corpus carries THREE doubled spans across TWO lines, not two:
skill-management.mdx:211 holds two of them, :219 one. Docstring, ADL decision 3
and the test comment all said "both". Corrected in all three.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UQJzGMhRqBRRboxMrRqFPP
… fixtures go red

The comment said dropping the appended invocation "collapses the case into
the corpus-verbatim assertion below". Measured: `cell` and `prose` both
yield 0 errors when restored, and the loop asserts toHaveLength(1) — the
suite fails at :594 in both cases, loudly. The collapse claim is true only
of `cell`, and only as duplication (its restored line IS the :599 fixture,
byte-identical); `prose` has no counterpart there.

Comment-only; no behavior change.
… edit, not the noun

`prose` is an EXCERPT of skill-management.mdx:219 (the corpus clause runs on
`; only triple-backtick/tilde *blocks* are excluded.`), so it has no corpus line
to be restored to; the operative edit for both fixtures is dropping the appended
`Run \`pair install\` to apply.`. Comment-only, no behaviour delta.

Measured: cell 1 err / restored 0; prose 1 err / appendix dropped 0;
both mutations -> suite 1 failed at :598 `expected [] to have a length of 1`.
Regenerated with pair update (offline, local dataset) to resolve the .pair/llms.txt rebase conflict.

Refs: #449

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FfhvsS5rippi6aUbbGrf6F
@rucka
rucka force-pushed the feature/US-449-pair-cli-canonical-docs branch from 2c76abf to 5139cb3 Compare September 3, 2026 19:54
@rucka
rucka force-pushed the main branch 2 times, most recently from 7b55900 to adb9627 Compare September 8, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation pr-state:to-be-reviewed PR state: awaiting review / gate risk:yellow Classification: medium risk tier tech-debt Tracked technical debt (living backlog, R7.2 — never blocks a PR) user story Work item representing a user story

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant