You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As a contributor whose push was blocked by the mirror-equality guard I want an explicit command that realigns the generated mirrors from the local dataset, named by the gate and run by /pair-capability-publish-pr So that the remedy is deterministic and one step — instead of a command that updates the knowledge base to its latest published version, which is not what the situation calls for
Where: a root script, the gate's remedy message (PRE_PUSH_REMEDY + DEVELOPMENT.md + its docs-site twin), and /pair-capability-publish-pr.
Epic Context
Parent Epic: none — standalone tooling/process defect Status: Refined Priority: P1 (Should-Have) — the drift class this addresses bit seven times in one day, three of them because a mirror was hand-ported instead of regenerated
Classification
risk:yellow · cost: green (not projected — Active: risk only)
Matrix — per dimension
Dimension
Tier
Source
Note
Service/domain criticality
green
Criticality Table
tech/risk-matrix.md now lists every touched deployable (packages/dev-tools, apps/website, packages/knowledge-hub) as Low — no Medium default, no unlisted-conservative-High (D21)
Change/diff risk
yellow
story scope
Multiple non-paired modules touched — root script, packages/dev-tools, DEVELOPMENT.md, the docs-site twin — plus a publish-pr step that commits; change-risk.dataset-mirror-pairs collapses only the skill's own dataset+mirror pair into one module, not the rest, so shared code is still touched
Business impact
yellow
subdomain class
Integration & Process Standardization (Supporting) — the new Development Tooling Standards (Generic) subdomain covers lint/format/TS/markdownlint/brand only, not gate/CI/process tooling, so this story's scope stays Supporting
Security relevance
green
path heuristic
No credentials; the command reads the local dataset and writes generated files inside the repo
Coupling balance
green
subdomain volatility + integrations
Wraps an existing CLI path; no new integration
Tier = yellow (max rule). tier-resolution.default-artifact-downgrade does not apply: Change/diff risk is yellow and is not an eligible artifact dimension (only Service/domain-criticality and/or Business-impact yellows qualify), so the downgrade-to-green condition is not met. Gate checks for 🟡: lint + type + build + unit.
Re-classified 2026-08-14 against corrected adoption inputs (Criticality Table, tier-resolution.default-artifact-downgrade override, Development Tooling Standards Generic subdomain — none of which existed at prior classification): Service/domain criticality moves yellow → green; tier stays risk:yellow, confirmed not lowered, held by Change/diff risk and Business impact.
Problem
When a dataset file is edited, its generated mirrors (.claude/skills/**, .pair/**) go out of sync, and the mirror-equality guard blocks the push. The documented remedy — in PRE_PUSH_REMEDY, DEVELOPMENT.md and the docs-site twin — is pair update.
That is the wrong command for the job. DEVELOPMENT.md:154 documents pair update as "Update knowledge base to latest version": it resolves and installs the published KB. What the situation needs is regeneration from the local dataset, which is a different operation — pair update --source <local dataset>, the form already exercised by CP3 and by the source-resolution smoke scenario.
Two consequences, both observed:
Disproportionate remedy. To fix a reformatted table, a contributor is told to run a knowledge-base update. Three of the seven drift incidents on record were hand-ports — someone chose to edit the mirror by hand rather than run that command, which is exactly the behaviour the guard exists to prevent.
Non-determinism where determinism is required. A remedy that can pull a newer KB makes the fix depend on what has been published, not on what is in the working tree.
Decision that produced this card (maintainer, 2026-08-05)
Recorded against the Open Decision section of ADL 2026-07-31-pre-push-gate-is-check-only.md, which asked whether the gate should apply fixes (option A) and/or pnpm format should realign the mirrors (option B).
Neither, as written.pair update is an install command, not a formatting one: folding it into pnpm format would mean every format could touch the KB, and the gate applying fixes at pre-push cannot reach the commits being pushed. Instead: a dedicated, explicit command, named by the gate and executed at the point where a commit is still possible — PR creation. pnpm format stays formatting.
Acceptance Criteria
Functional Requirements
Given-When-Then Format:
Given a repo whose generated mirrors are out of sync with the local dataset When the new command runs Then the mirrors are regenerated from the local dataset and no published KB version is fetched or installed
Given the command When it is run twice in a row Then the second run produces no diff (idempotent)
Given the mirror-equality guard fails When the contributor reads the remedy Then it names this command, and no longer pair update — in all three places that state the remedy (PRE_PUSH_REMEDY, DEVELOPMENT.md, the docs-site twin), whose load-bearing paragraph must stay byte-identical except for the ADL link form
Given/pair-capability-publish-pr When it prepares a branch for a pull request Then it runs the command and, if it produced changes, commits them as their own commit with a message naming the regeneration — never mixed into a feature commit
Givenpublish-pr running on a branch with no drift When the command runs Then nothing is committed and nothing is reported as done — a no-op stays silent
Givenpnpm format When this story lands Then it is unchanged: formatting does not regenerate mirrors, and does not reach outside format scope
Given the command runs where git or the dataset is unavailable When it fails Then it exits non-zero with the reason — it never reports success over a no-op (the same fail-loud rule the formatter wrappers follow)
Given a contributor who wants the check without the write When they need to know whether mirrors are in sync Then the existing guard already answers that; this command never runs in "check" mode, so there is exactly one writer and one checker
Business Rules
Regeneration is local and deterministic. The source is the working tree's dataset, never a published release.
pnpm format stays formatting. The scope boundary is the reason this is a separate command rather than a step inside it.
The write happens where a commit is possible. Pre-push is too late (the commits exist); PR creation is the point where regeneration can still become part of the branch.
Generated output is committed separately, so a reviewer can tell authored changes from regenerated ones at a glance.
The remedy is stated once and identically in the three documents that advertise it.
Edge Cases and Error Handling
Drift in a file the branch never touched (regeneration picks up someone else's earlier miss): commit it separately anyway, and say so — surprising, but better than pushing known-stale output.
Regeneration produces a huge diff (a dataset-wide change): the separate commit keeps the PR readable; the reviewer sees one regeneration commit rather than noise across the feature commits.
Command run on a dirty working tree: it writes generated files only; unstaged authored changes must be left untouched, and publish-pr must not commit them.
A mirror hand-edited earlier: regeneration overwrites it. That is correct — and the reason the guard exists — so the message should say the mirror was regenerated, not "fixed".
publish-pr invoked twice: idempotence (AC2) means the second run commits nothing.
Definition of Done Checklist
All acceptance criteria implemented and verified
Test written first: drifted mirror ⇒ regenerated; second run ⇒ no diff; missing dataset ⇒ non-zero exit
Root script added, wrapping the local-source regeneration (no new generation logic)
Remedy text updated in PRE_PUSH_REMEDY, DEVELOPMENT.md and the docs-site twin, with the byte-identical paragraph preserved
/pair-capability-publish-pr runs it and commits separately when there is a diff; silent no-op otherwise
pnpm format verified unchanged (gate:composition still green)
Dataset edited as source for the skill change, mirror regenerated, skills:conformance green
🟡 gate checks green: lint + type + build + unit
Story Sizing and Sprint Readiness
Refined Story Points
Final Story Points: 3 (M) Confidence Level: High Sizing Justification: The script is a thin wrapper over a CLI path that already exists (--source with a local dataset). The work is the wiring: three documents whose paragraph must stay byte-identical, and a publish-pr step that commits — the only part that needs care, because a step that commits on the contributor's behalf must be predictable and silent when idle.
Sprint Capacity Validation
Sprint Fit Assessment: Yes Development Time Estimate: ~0.5 day Testing Time Estimate: ~0.25 day
Dependencies and Coordination
Story Dependencies
Prerequisite Stories: none Dependent Stories: none — but it makes #393 cheaper: that card's regeneration step becomes this one command, and its "regenerate, don't hand-port" rule gets a tool to point at Shared Components: pair-capability-publish-pr (mutex with any card touching that skill), root package.json scripts (coordinates with #414, which rewrites the format scripts), DEVELOPMENT.md
External Dependencies
Related decisions: ADL 2026-07-31-pre-push-gate-is-check-only.md — this story closes its Open Decision · the check-only rule (the gate reports, never fixes) · #414 (brings the mirrors into format-check scope, which is what makes a correct regeneration command necessary rather than nice)
Validation and Testing Strategy
Acceptance Testing Approach
Testing Methods: unit/integration tests over a fixture repo — drifted mirror regenerated, idempotence on a second run, non-zero exit with no dataset, unstaged authored changes untouched. Then an end-to-end pass: edit a dataset file, run publish-pr on a branch, and confirm one separate regeneration commit and a green gate. Test Data Requirements: fixture repo with a dataset and its mirrors, one deliberately drifted.
Success Metrics
The gate's remedy is a single command that always produces the same result
No hand-ported mirror appears in a PR after this lands
pnpm format behaves exactly as before
Notes and Additional Context
Refinement Session Insights (2026-08-05):
The remedy was the wrong command, not just a missing automation.pair update updates the KB to its latest published version; the situation needs regeneration from the local dataset. That mismatch is the most plausible explanation for three hand-ports on record.
Both options in the ADL were declined as written: the gate applying fixes writes files the contributor never staged and cannot reach the commits being pushed; folding an install command into pnpm format makes formatting able to change the knowledge base.
PR creation is the right moment — the latest point where the regenerated output can still enter the branch, and an explicit, tracked act rather than a hook side effect.
Team Concerns: publish-pr will commit on the contributor's behalf. That is acceptable only because the content is generated output committed separately and labelled — the same reasoning that made a write-mode formatter in the gate unacceptable applies here, and the difference is that the write is explicit, isolated in its own commit, and happens where the author is still in control.
Technical Analysis
Implementation Approach
Technical Strategy: expose the existing local-source regeneration as a named root script; point the three remedy texts at it; call it from publish-pr and commit its output separately when non-empty.
Key Components:
root package.json — the new script (wrapper only, no new generation logic)
packages/dev-tools/src/quality-gates/pre-push-gate-composition.ts — PRE_PUSH_REMEDY text (unit-tested)
DEVELOPMENT.md + apps/website/content/docs/contributing/development-setup.mdx — the byte-identical paragraph
pair-capability-publish-pr — the step and its commit
Summary: Expose the existing pair update --source <local dataset> path as a named root package.json script — a thin wrapper, no new generation logic — that regenerates .claude/skills/** and .pair/** from the working tree's dataset and never touches a published KB version.
Type: Feature Implementation
Description: This is the deterministic remedy the story introduces. It must call the CLI's existing local-source resolution (the same form CP3 and the source-resolution smoke scenario already exercise: pair update --source packages/knowledge-hub/dataset) — no new regeneration logic, no network/version resolution. It must be idempotent (a second run produces no diff) and must fail loud (non-zero exit with the reason) when git or the dataset is unavailable, mirroring the fail-loud rule the formatter wrappers already follow (packages/dev-tools/src/quality-gates/pre-push-gate-composition.ts header comment on sync-version/test:perf). It never runs in "check" mode — the existing mirror-equality guard is the checker; this is the sole writer.
Acceptance Criteria:
Primary deliverable: a root package.json script (e.g. mirrors:regenerate) that runs pair update --source <local dataset path> and nothing else.
Quality standard: no new mirror-generation logic — delegates entirely to the CLI's existing --source path.
Integration requirement: consumed by T-4 (publish-pr) and named by T-2/T-3 (remedy text).
Verification method: fixture-repo tests (T-1's own test-first pass, per DoD) — drifted mirror ⇒ regenerated; second run ⇒ no diff; missing dataset/no git ⇒ non-zero exit with reason.
Technical Requirements:
Functionality: reads the local dataset only; never resolves or installs a published KB version.
Performance: n/a beyond existing CLI performance.
Security: none — no credentials involved (reads local dataset, writes generated files inside the repo).
Compatibility: same environments the CLI already supports.
Implementation Approach:
Technical Design: root package.json script string invoking the CLI binary with --source pointed at packages/knowledge-hub/dataset (or the CLI's own default-source-dir convention if one already resolves this without the flag — confirm against apps/pair-cli/src/config/kb-resolver.ts before hardcoding a path).
Technical: apps/pair-cli's --source resolution (already exists, per ADL 2026-07-31-pre-push-gate-is-check-only.md).
Tasks: none — this is the foundation task.
Implementation Steps:
Write the failing tests first (fixture repo: drifted mirror, idempotent second run, missing-dataset failure) — bug-fix workflow applies (test-first).
Add the root script wrapping pair update --source <local dataset>.
Run the tests, confirm green.
Manually verify: edit a dataset file, run the script, confirm the mirror regenerates and a second run is a no-op.
Testing Strategy:
Unit Tests: fixture repo with a dataset + drifted mirror — asserts regeneration; asserts idempotence on re-run; asserts non-zero exit with a clear reason when git/dataset is unavailable; asserts no network call / no published-version resolution occurs (mitigates the "resolves a published KB instead" risk from the Technical Risks table).
Integration Tests: none at this task level (covered in T-5 end-to-end).
Manual Testing: run twice locally against a real drifted mirror.
Notes: No new generation logic — this task is wiring only, per the story's Technical Strategy.
T-2: Rename the remedy in PRE_PUSH_REMEDY (with unit tests)
Summary: Update PRE_PUSH_REMEDY in packages/dev-tools/src/quality-gates/pre-push-gate-composition.ts to name T-1's new command instead of pair update, and update its unit tests.
Type: Bug Fix
Description:PRE_PUSH_REMEDY currently reads: "...re-sync the generated .claude/skills/** copies (pair update) in the same commit...". Replace pair update with the new command name from T-1. The load-bearing paragraph shared with DEVELOPMENT.md/the docs-site twin must stay byte-identical except for the ADL link form (per AC-3) — coordinate wording with T-3 before finalizing either file. pre-push-gate-composition.test.ts already asserts properties of PRE_PUSH_REMEDY (e.g. that REMEDY_SCRIPT exists in package.json) — extend or add an assertion that the remedy names the new script and that the script exists, mirroring the existing REMEDY_SCRIPT check (lines ~259-266 of the source file).
Acceptance Criteria:
Primary deliverable: PRE_PUSH_REMEDY string updated to name T-1's script instead of pair update.
Quality standard: existing test suite extended to assert the new command is named and exists as a root script (same pattern as the current REMEDY_SCRIPT existence check).
Integration requirement: wording of the shared paragraph matches T-3's edit byte-for-byte (except the ADL link form).
Verification method: pnpm --filter @pair/dev-tools test green; pnpm gate:composition green.
Technical Requirements:
Functionality: string content change only, no behavioral change to the gate logic.
Performance: n/a.
Security: n/a.
Compatibility: n/a.
Implementation Approach:
Technical Design: edit the PRE_PUSH_REMEDY constant string; add/extend a test asserting the new script name is referenced and present in package.json scripts.
Bounded Context & Modules: Integration & Process Standardization (packages/dev-tools/src/quality-gates).
Technical: T-1's script name must be finalized first.
Tasks: T-1.
Implementation Steps:
Confirm T-1's final script name.
Update PRE_PUSH_REMEDY text, keeping the shared paragraph byte-identical to T-3's version (except ADL link form).
Update/extend the unit test asserting the remedy names an existing script.
Run pnpm --filter @pair/dev-tools test and pnpm gate:composition.
Testing Strategy:
Unit Tests: assert PRE_PUSH_REMEDY contains the new script name and no longer contains pair update as the fix instruction; assert the named script exists in root package.json.
Manual Testing: trigger the guard locally and read the printed remedy.
Notes: Coordinate wording with T-3 before either file is finalized — the byte-identical-paragraph requirement is cross-checked by an existing test (per the story's "the paragraph equality is the existing check" mitigation).
T-3: Rename the remedy in DEVELOPMENT.md and its docs-site twin
Summary: Update the same remedy paragraph in DEVELOPMENT.md (root) and apps/website/content/docs/contributing/development-setup.mdx (the docs-site twin) to name T-1's new command, keeping the two byte-identical to each other and to T-2's PRE_PUSH_REMEDY text except for the ADL link form (relative markdown link in DEVELOPMENT.md vs. the absolute GitHub URL in the .mdx).
Type: Documentation
Description: Both files currently document pair update as the two-step remedy for a format:check-caused mirror drift (DEVELOPMENT.md:130-138, development-setup.mdx:83-91) and both list pair update in a CLI-commands table (DEVELOPMENT.md:155) with the description "Update knowledge base to latest version" — that table entry is correct and must NOT be changed (it documents pair update itself, not the remedy); only the remedy paragraph's parenthetical changes. docs:staleness (apps/website/lib/docs-staleness-check.ts) enforces derivation from the dataset for skill/capability counts — verify this specific prose paragraph is not one of its derived checks (it reads as hand-authored dev-process prose, not a dataset-derived fact) before editing freely.
Acceptance Criteria:
Primary deliverable: DEVELOPMENT.md and development-setup.mdx remedy paragraphs both name T-1's script instead of pair update.
Quality standard: the two paragraphs are byte-identical to each other and to T-2's PRE_PUSH_REMEDY wording, differing only in the ADL link form.
Integration requirement: the CLI-commands table row for pair update (DEVELOPMENT.md:155) is left untouched — it documents a different command.
Verification method: pnpm docs:staleness green; manual diff of the two paragraphs confirms byte-equality modulo the link.
Technical Requirements:
Functionality: documentation-only change.
Performance: n/a.
Security: n/a.
Compatibility: n/a.
Implementation Approach:
Technical Design: text edit in both files, mirroring T-2's finalized wording.
Bounded Context & Modules: Integration & Process Standardization (docs distribution) / Knowledge & Standards (authored prose) — per the context map's authored-by/disseminated-by split.
Technical: T-1's script name must be finalized first; wording coordinated with T-2.
Tasks: T-1, T-2 (wording).
Implementation Steps:
Update DEVELOPMENT.md's remedy paragraph with T-1's script name.
Update development-setup.mdx's remedy paragraph identically, swapping only the ADL link form (relative path → absolute GitHub URL, already the existing pattern).
Diff both paragraphs against T-2's PRE_PUSH_REMEDY wording to confirm byte-equality (modulo the link).
Run pnpm docs:staleness.
Testing Strategy:
Unit Tests: none (docs-only) — covered by the existing byte-equality check the story references ("the paragraph equality is the existing check").
Integration Tests: pnpm docs:staleness.
Manual Testing: side-by-side read of all three copies.
Notes: Do not touch the pair update row in the CLI commands table — only the remedy prose.
T-4: Wire regeneration + separate commit into /pair-capability-publish-pr
Priority: P0 | Estimated Hours: 2.5h | Bounded Context: Development Collaboration (skill authoring) / Integration & Process Standardization (the step's effect)
Summary: Add a step to /pair-capability-publish-pr (.claude/skills/pair-capability-publish-pr/SKILL.md) that runs T-1's regeneration command while preparing the branch, and — only if it produced a diff — commits the generated files as their own commit, named as a regeneration, never mixed into a feature commit. A no-op run commits nothing and reports nothing.
Type: Feature Implementation
Description: Per the story's edge cases: the command must write generated files only, leaving unstaged authored changes untouched, and publish-pr must never commit those authored changes. Drift in a file the branch never touched must still be committed (separately, and reported), since pushing known-stale output is worse. The natural insertion point is Phase 2 ("Resolve Merge Strategy & Prepare Base"), after the branch is confirmed pushed and before the PR body is composed — i.e., before anything reads the diff for the PR body (Phase 3 reads git diff --name-only <base-branch>...HEAD for Changes Made, which should already reflect the regeneration commit if any).
Acceptance Criteria:
Primary deliverable: a new step in the SKILL.md algorithm (with output-format row) that runs T-1's command and conditionally commits.
Quality standard: silent no-op (AC-5) — no commit, no output row noise, when the command produces no diff.
Integration requirement: the commit, when made, is separate from feature commits and named as a regeneration (e.g. commit message chore: regenerate mirrors from local dataset); it must not touch unstaged authored changes.
Verification method: T-5's end-to-end test.
Technical Requirements:
Functionality: run-then-check-diff-then-conditionally-commit, mirroring the pattern already used elsewhere in the skill for "read-back confirms the write" (Phase 4's re-read discipline).
Performance: negligible — one CLI invocation plus a git status/git diff check.
Security: none.
Compatibility: n/a.
Implementation Approach:
Technical Design: Act: run T-1's script; Check: does git status --porcelain show changes confined to the generated mirror paths; Skip: none → report nothing, move on; Act: stage only the generated paths and commit with a naming convention that says "regenerated", never "fixed"; Verify: git log shows exactly one new commit, and git status still shows the pre-existing unstaged authored changes (if any) untouched.
Bounded Context & Modules: .claude/skills/pair-capability-publish-pr/SKILL.md (Development Collaboration authorship — skills/workflows content, per the context map).
Files to Modify/Create:
.claude/skills/pair-capability-publish-pr/SKILL.md — new algorithm step + output-format row (composed skills table update if /pair-capability-verify-quality's scope is affected — likely not).
packages/knowledge-hub/dataset/.skills/... — the dataset source for this skill (mirror regenerated afterward, per the story's own DoD item: "Dataset edited as source for the skill change, mirror regenerated").
Technical: T-1's script must exist and be invocable.
Tasks: T-1.
Implementation Steps:
Edit the SKILL.md dataset source (packages/knowledge-hub/dataset/.skills/...), never the generated .claude/skills/** copy directly.
Add the new step to Phase 2 (or a clearly labeled sub-step) per the design above.
Add the corresponding output-format row (e.g. ├── Mirrors: [regenerated — commit <sha> | no drift — nothing to commit]).
Regenerate the mirror via T-1's own command and confirm skills:conformance is green.
Update the HALT/Graceful Degradation sections only if this step introduces a new failure mode worth documenting (e.g. T-1's command failing — non-zero exit — should HALT publish-pr before the PR is touched, consistent with Phase 1's gate-red HALT).
Testing Strategy:
Unit Tests: n/a at the skill-authoring level (skills are prose, not code) — verified by T-5's behavioral end-to-end test instead.
Integration Tests: T-5.
Manual Testing: run publish-pr once on a branch with a deliberately drifted mirror and once on a clean branch; confirm the commit appears only in the first case.
Notes: This is the task that most needs care per the story's sizing rationale ("a step that commits on the contributor's behalf must be predictable and silent when idle").
T-5: End-to-end test — dataset edit → publish-pr → one regeneration commit
Summary: Fixture-repo integration test exercising the full path: edit a dataset file, run publish-pr on a branch, confirm exactly one separate regeneration commit is produced and the gate stays green — plus the no-op and dirty-tree edge cases.
Type: Testing
Description: Covers the story's "Acceptance Testing Approach" end-to-end pass and the edge cases not already covered by T-1's unit tests: a mirror hand-edited earlier gets overwritten and reported as "regenerated" (not "fixed"); a dataset-wide drift produces one commit, not noise across feature commits; running publish-pr twice commits nothing the second time (AC-2 applied at the publish-pr level, not just the script level).
Acceptance Criteria:
Primary deliverable: an automated test (fixture repo, following the existing InMemoryFileSystemService/fixture-repo conventions used elsewhere in packages/dev-tools) exercising: drifted mirror → regenerated + committed separately; clean branch → silent no-op; missing dataset/no git → non-zero exit, publish-pr HALTs before touching the PR; unstaged authored changes left untouched after the run.
Quality standard: test data matches the story's stated test-data requirement ("fixture repo with a dataset and its mirrors, one deliberately drifted").
Integration requirement: exercises T-1 through T-4 together, not each in isolation.
Verification method: test suite green; pnpm quality-gate green on the fixture scenario.
Technical Requirements:
Functionality: covers AC-1, AC-2, AC-4, AC-5, AC-7 in combination.
Performance: n/a.
Security: n/a.
Compatibility: n/a.
Implementation Approach:
Technical Design: reuse the fixture-repo pattern from source-resolution.sh / existing dev-tools test fixtures rather than inventing a new harness.
Bounded Context & Modules: packages/dev-tools (or wherever publish-pr's underlying automation lives, if it is code rather than pure skill-prose — confirm during implementation; if publish-pr is skill-prose only, this becomes a scripted smoke scenario alongside scripts/smoke-tests/scenarios/source-resolution.sh).
Files to Modify/Create:
New test/fixture file(s) — exact path TBD during implementation, following the 1:1 module-to-test-file convention where code exists, or a new smoke-test scenario otherwise.
Summary: Confirm pnpm format behaves exactly as before this story (AC-6), confirm gate:composition stays green, and update ADL 2026-07-31-pre-push-gate-is-check-only.md to record that its Open Decision is now closed by this story.
Type: Documentation / Verification
Description:pnpm format must not gain any mirror-regeneration behavior — the whole point of a dedicated command is that formatting stays formatting. This task is the final DoD sweep: run the full pnpm quality-gate locally, confirm format is untouched (diff package.json's format/format:check/prettier:fix/mdlint:fix scripts against main), and record closure of the ADL's Open Decision (the story body already states "this story closes its Open Decision" — the ADL file itself should reflect that per the project's decision-recording convention).
Acceptance Criteria:
Primary deliverable: pnpm format scripts diff empty against main; pnpm gate:composition green; ADL updated to show the Open Decision as closed, referencing this story.
Quality standard: no drive-by changes to format/format:check scripts.
Integration requirement: n/a.
Verification method: git diff main -- package.json shows no change to the format/format:check/prettier:*/mdlint:* script entries; pnpm gate:composition.
Technical Requirements:
Functionality: n/a — verification task.
Performance: n/a.
Security: n/a.
Compatibility: n/a.
Implementation Approach:
Technical Design: run the gate, diff the scripts, edit the ADL.
.pair/adoption/decision-log/2026-07-31-pre-push-gate-is-check-only.md — mark the Open Decision closed, linking this story.
Technical Standards: record-decision guidance (ADL maintenance is normally /pair-capability-record-decision's job — invoke it rather than hand-editing, per the project's "record decisions" quick rule).
Dependencies:
Technical: T-1 through T-5 complete.
Tasks: T-1, T-2, T-3, T-4, T-5.
Implementation Steps:
Run pnpm quality-gate and confirm green.
Diff package.json's format-related scripts against main.
Invoke /pair-capability-record-decision (or hand-edit if unavailable) to close the ADL's Open Decision, referencing this story.
Final DoD checklist sweep against the story body.
Testing Strategy:
Unit Tests: n/a.
Integration Tests: pnpm quality-gate (full local run).
Manual Testing: read the ADL to confirm the Open Decision section now reads as closed.
Notes: This is the story's closing task — nothing here should require new code if T-1 through T-5 are correctly scoped.
Story Statement
As a contributor whose push was blocked by the mirror-equality guard
I want an explicit command that realigns the generated mirrors from the local dataset, named by the gate and run by
/pair-capability-publish-prSo that the remedy is deterministic and one step — instead of a command that updates the knowledge base to its latest published version, which is not what the situation calls for
Where: a root script, the gate's remedy message (
PRE_PUSH_REMEDY+DEVELOPMENT.md+ its docs-site twin), and/pair-capability-publish-pr.Epic Context
Parent Epic: none — standalone tooling/process defect
Status: Refined
Priority: P1 (Should-Have) — the drift class this addresses bit seven times in one day, three of them because a mirror was hand-ported instead of regenerated
Classification
risk:yellow· cost: green (not projected —Active: riskonly)Matrix — per dimension
tech/risk-matrix.mdnow lists every touched deployable (packages/dev-tools,apps/website,packages/knowledge-hub) as Low — no Medium default, no unlisted-conservative-High (D21)packages/dev-tools,DEVELOPMENT.md, the docs-site twin — plus apublish-prstep that commits;change-risk.dataset-mirror-pairscollapses only the skill's own dataset+mirror pair into one module, not the rest, so shared code is still touchedIntegration & Process Standardization(Supporting) — the newDevelopment Tooling Standards(Generic) subdomain covers lint/format/TS/markdownlint/brand only, not gate/CI/process tooling, so this story's scope stays SupportingTier = yellow (max rule).
tier-resolution.default-artifact-downgradedoes not apply: Change/diff risk is yellow and is not an eligible artifact dimension (only Service/domain-criticality and/or Business-impact yellows qualify), so the downgrade-to-green condition is not met. Gate checks for 🟡: lint + type + build + unit.Re-classified 2026-08-14 against corrected adoption inputs (Criticality Table,
tier-resolution.default-artifact-downgradeoverride,Development Tooling StandardsGeneric subdomain — none of which existed at prior classification): Service/domain criticality moves yellow → green; tier staysrisk:yellow, confirmed not lowered, held by Change/diff risk and Business impact.Problem
When a dataset file is edited, its generated mirrors (
.claude/skills/**,.pair/**) go out of sync, and the mirror-equality guard blocks the push. The documented remedy — inPRE_PUSH_REMEDY,DEVELOPMENT.mdand the docs-site twin — ispair update.That is the wrong command for the job.
DEVELOPMENT.md:154documentspair updateas "Update knowledge base to latest version": it resolves and installs the published KB. What the situation needs is regeneration from the local dataset, which is a different operation —pair update --source <local dataset>, the form already exercised byCP3and by thesource-resolutionsmoke scenario.Two consequences, both observed:
Decision that produced this card (maintainer, 2026-08-05)
Recorded against the Open Decision section of ADL
2026-07-31-pre-push-gate-is-check-only.md, which asked whether the gate should apply fixes (option A) and/orpnpm formatshould realign the mirrors (option B).Neither, as written.
pair updateis an install command, not a formatting one: folding it intopnpm formatwould mean every format could touch the KB, and the gate applying fixes at pre-push cannot reach the commits being pushed. Instead: a dedicated, explicit command, named by the gate and executed at the point where a commit is still possible — PR creation.pnpm formatstays formatting.Acceptance Criteria
Functional Requirements
Given-When-Then Format:
Given a repo whose generated mirrors are out of sync with the local dataset
When the new command runs
Then the mirrors are regenerated from the local dataset and no published KB version is fetched or installed
Given the command
When it is run twice in a row
Then the second run produces no diff (idempotent)
Given the mirror-equality guard fails
When the contributor reads the remedy
Then it names this command, and no longer
pair update— in all three places that state the remedy (PRE_PUSH_REMEDY,DEVELOPMENT.md, the docs-site twin), whose load-bearing paragraph must stay byte-identical except for the ADL link formGiven
/pair-capability-publish-prWhen it prepares a branch for a pull request
Then it runs the command and, if it produced changes, commits them as their own commit with a message naming the regeneration — never mixed into a feature commit
Given
publish-prrunning on a branch with no driftWhen the command runs
Then nothing is committed and nothing is reported as done — a no-op stays silent
Given
pnpm formatWhen this story lands
Then it is unchanged: formatting does not regenerate mirrors, and does not reach outside format scope
Given the command runs where git or the dataset is unavailable
When it fails
Then it exits non-zero with the reason — it never reports success over a no-op (the same fail-loud rule the formatter wrappers follow)
Given a contributor who wants the check without the write
When they need to know whether mirrors are in sync
Then the existing guard already answers that; this command never runs in "check" mode, so there is exactly one writer and one checker
Business Rules
pnpm formatstays formatting. The scope boundary is the reason this is a separate command rather than a step inside it.Edge Cases and Error Handling
publish-prmust not commit them.publish-prinvoked twice: idempotence (AC2) means the second run commits nothing.Definition of Done Checklist
PRE_PUSH_REMEDY,DEVELOPMENT.mdand the docs-site twin, with the byte-identical paragraph preserved/pair-capability-publish-prruns it and commits separately when there is a diff; silent no-op otherwisepnpm formatverified unchanged (gate:compositionstill green)skills:conformancegreenStory Sizing and Sprint Readiness
Refined Story Points
Final Story Points: 3 (M)
Confidence Level: High
Sizing Justification: The script is a thin wrapper over a CLI path that already exists (
--sourcewith a local dataset). The work is the wiring: three documents whose paragraph must stay byte-identical, and apublish-prstep that commits — the only part that needs care, because a step that commits on the contributor's behalf must be predictable and silent when idle.Sprint Capacity Validation
Sprint Fit Assessment: Yes
Development Time Estimate: ~0.5 day
Testing Time Estimate: ~0.25 day
Dependencies and Coordination
Story Dependencies
Prerequisite Stories: none
Dependent Stories: none — but it makes #393 cheaper: that card's regeneration step becomes this one command, and its "regenerate, don't hand-port" rule gets a tool to point at
Shared Components:
pair-capability-publish-pr(mutex with any card touching that skill), rootpackage.jsonscripts (coordinates with #414, which rewrites the format scripts),DEVELOPMENT.mdExternal Dependencies
Related decisions: ADL
2026-07-31-pre-push-gate-is-check-only.md— this story closes its Open Decision · the check-only rule (the gate reports, never fixes) · #414 (brings the mirrors into format-check scope, which is what makes a correct regeneration command necessary rather than nice)Validation and Testing Strategy
Acceptance Testing Approach
Testing Methods: unit/integration tests over a fixture repo — drifted mirror regenerated, idempotence on a second run, non-zero exit with no dataset, unstaged authored changes untouched. Then an end-to-end pass: edit a dataset file, run
publish-pron a branch, and confirm one separate regeneration commit and a green gate.Test Data Requirements: fixture repo with a dataset and its mirrors, one deliberately drifted.
Success Metrics
pnpm formatbehaves exactly as beforeNotes and Additional Context
Refinement Session Insights (2026-08-05):
pair updateupdates the KB to its latest published version; the situation needs regeneration from the local dataset. That mismatch is the most plausible explanation for three hand-ports on record.pnpm formatmakes formatting able to change the knowledge base.Team Concerns:
publish-prwill commit on the contributor's behalf. That is acceptable only because the content is generated output committed separately and labelled — the same reasoning that made a write-mode formatter in the gate unacceptable applies here, and the difference is that the write is explicit, isolated in its own commit, and happens where the author is still in control.Technical Analysis
Implementation Approach
Technical Strategy: expose the existing local-source regeneration as a named root script; point the three remedy texts at it; call it from
publish-prand commit its output separately when non-empty.Key Components:
package.json— the new script (wrapper only, no new generation logic)packages/dev-tools/src/quality-gates/pre-push-gate-composition.ts—PRE_PUSH_REMEDYtext (unit-tested)DEVELOPMENT.md+apps/website/content/docs/contributing/development-setup.mdx— the byte-identical paragraphpair-capability-publish-pr— the step and its commitData Flow: local dataset → regeneration (
--source) → generated mirrors → (inpublish-pr) separate commit → gate green.Integration Points: the pre-push gate's message,
publish-pr, the mirror-equality guards,#414's format scope.Design: not required
Technical Risks and Mitigation
publish-prcommits authored changes along with generated onespublish-proutputTask Breakdown
PRE_PUSH_REMEDY(with unit tests)DEVELOPMENT.mdand its docs-site twin/pair-capability-publish-prpublish-pr→ one regeneration commitpnpm format/gate:compositionunchanged and close the ADL's Open DecisionDependency Graph
AC Coverage
publish-prruns it, separate commit)pnpm formatunchanged)T-1: Root script for local-source mirror regeneration
Priority: P0 | Estimated Hours: 3h | Bounded Context: Integration & Process Standardization
Summary: Expose the existing
pair update --source <local dataset>path as a named rootpackage.jsonscript — a thin wrapper, no new generation logic — that regenerates.claude/skills/**and.pair/**from the working tree's dataset and never touches a published KB version.Type: Feature Implementation
Description: This is the deterministic remedy the story introduces. It must call the CLI's existing local-source resolution (the same form
CP3and thesource-resolutionsmoke scenario already exercise:pair update --source packages/knowledge-hub/dataset) — no new regeneration logic, no network/version resolution. It must be idempotent (a second run produces no diff) and must fail loud (non-zero exit with the reason) when git or the dataset is unavailable, mirroring the fail-loud rule the formatter wrappers already follow (packages/dev-tools/src/quality-gates/pre-push-gate-composition.tsheader comment onsync-version/test:perf). It never runs in "check" mode — the existing mirror-equality guard is the checker; this is the sole writer.Acceptance Criteria:
package.jsonscript (e.g.mirrors:regenerate) that runspair update --source <local dataset path>and nothing else.--sourcepath.publish-pr) and named by T-2/T-3 (remedy text).Technical Requirements:
Implementation Approach:
package.jsonscript string invoking the CLI binary with--sourcepointed atpackages/knowledge-hub/dataset(or the CLI's own default-source-dir convention if one already resolves this without the flag — confirm againstapps/pair-cli/src/config/kb-resolver.tsbefore hardcoding a path).packages/knowledge-hub, root scripts).package.json— add the new script.Dependencies:
apps/pair-cli's--sourceresolution (already exists, per ADL2026-07-31-pre-push-gate-is-check-only.md).Implementation Steps:
pair update --source <local dataset>.Testing Strategy:
Notes: No new generation logic — this task is wiring only, per the story's Technical Strategy.
T-2: Rename the remedy in
PRE_PUSH_REMEDY(with unit tests)Priority: P0 | Estimated Hours: 1.5h | Bounded Context: Integration & Process Standardization
Summary: Update
PRE_PUSH_REMEDYinpackages/dev-tools/src/quality-gates/pre-push-gate-composition.tsto name T-1's new command instead ofpair update, and update its unit tests.Type: Bug Fix
Description:
PRE_PUSH_REMEDYcurrently reads: "...re-sync the generated.claude/skills/**copies (pair update) in the same commit...". Replacepair updatewith the new command name from T-1. The load-bearing paragraph shared withDEVELOPMENT.md/the docs-site twin must stay byte-identical except for the ADL link form (per AC-3) — coordinate wording with T-3 before finalizing either file.pre-push-gate-composition.test.tsalready asserts properties ofPRE_PUSH_REMEDY(e.g. thatREMEDY_SCRIPTexists inpackage.json) — extend or add an assertion that the remedy names the new script and that the script exists, mirroring the existingREMEDY_SCRIPTcheck (lines ~259-266 of the source file).Acceptance Criteria:
PRE_PUSH_REMEDYstring updated to name T-1's script instead ofpair update.REMEDY_SCRIPTexistence check).pnpm --filter @pair/dev-tools testgreen;pnpm gate:compositiongreen.Technical Requirements:
Implementation Approach:
PRE_PUSH_REMEDYconstant string; add/extend a test asserting the new script name is referenced and present inpackage.jsonscripts.packages/dev-tools/src/quality-gates).packages/dev-tools/src/quality-gates/pre-push-gate-composition.ts— updatePRE_PUSH_REMEDY.packages/dev-tools/src/quality-gates/pre-push-gate-composition.test.ts— update/add assertions.Dependencies:
Implementation Steps:
PRE_PUSH_REMEDYtext, keeping the shared paragraph byte-identical to T-3's version (except ADL link form).pnpm --filter @pair/dev-tools testandpnpm gate:composition.Testing Strategy:
PRE_PUSH_REMEDYcontains the new script name and no longer containspair updateas the fix instruction; assert the named script exists in rootpackage.json.gate:compositionself-check stays green.Notes: Coordinate wording with T-3 before either file is finalized — the byte-identical-paragraph requirement is cross-checked by an existing test (per the story's "the paragraph equality is the existing check" mitigation).
T-3: Rename the remedy in
DEVELOPMENT.mdand its docs-site twinPriority: P0 | Estimated Hours: 1h | Bounded Context: Integration & Process Standardization
Summary: Update the same remedy paragraph in
DEVELOPMENT.md(root) andapps/website/content/docs/contributing/development-setup.mdx(the docs-site twin) to name T-1's new command, keeping the two byte-identical to each other and to T-2'sPRE_PUSH_REMEDYtext except for the ADL link form (relative markdown link inDEVELOPMENT.mdvs. the absolute GitHub URL in the.mdx).Type: Documentation
Description: Both files currently document
pair updateas the two-step remedy for aformat:check-caused mirror drift (DEVELOPMENT.md:130-138,development-setup.mdx:83-91) and both listpair updatein a CLI-commands table (DEVELOPMENT.md:155) with the description "Update knowledge base to latest version" — that table entry is correct and must NOT be changed (it documentspair updateitself, not the remedy); only the remedy paragraph's parenthetical changes.docs:staleness(apps/website/lib/docs-staleness-check.ts) enforces derivation from the dataset for skill/capability counts — verify this specific prose paragraph is not one of its derived checks (it reads as hand-authored dev-process prose, not a dataset-derived fact) before editing freely.Acceptance Criteria:
DEVELOPMENT.mdanddevelopment-setup.mdxremedy paragraphs both name T-1's script instead ofpair update.PRE_PUSH_REMEDYwording, differing only in the ADL link form.pair update(DEVELOPMENT.md:155) is left untouched — it documents a different command.pnpm docs:stalenessgreen; manual diff of the two paragraphs confirms byte-equality modulo the link.Technical Requirements:
Implementation Approach:
DEVELOPMENT.md— remedy paragraph (lines ~130-138).apps/website/content/docs/contributing/development-setup.mdx— remedy paragraph (lines ~83-91).Dependencies:
Implementation Steps:
DEVELOPMENT.md's remedy paragraph with T-1's script name.development-setup.mdx's remedy paragraph identically, swapping only the ADL link form (relative path → absolute GitHub URL, already the existing pattern).PRE_PUSH_REMEDYwording to confirm byte-equality (modulo the link).pnpm docs:staleness.Testing Strategy:
pnpm docs:staleness.Notes: Do not touch the
pair updaterow in the CLI commands table — only the remedy prose.T-4: Wire regeneration + separate commit into
/pair-capability-publish-prPriority: P0 | Estimated Hours: 2.5h | Bounded Context: Development Collaboration (skill authoring) / Integration & Process Standardization (the step's effect)
Summary: Add a step to
/pair-capability-publish-pr(.claude/skills/pair-capability-publish-pr/SKILL.md) that runs T-1's regeneration command while preparing the branch, and — only if it produced a diff — commits the generated files as their own commit, named as a regeneration, never mixed into a feature commit. A no-op run commits nothing and reports nothing.Type: Feature Implementation
Description: Per the story's edge cases: the command must write generated files only, leaving unstaged authored changes untouched, and
publish-prmust never commit those authored changes. Drift in a file the branch never touched must still be committed (separately, and reported), since pushing known-stale output is worse. The natural insertion point is Phase 2 ("Resolve Merge Strategy & Prepare Base"), after the branch is confirmed pushed and before the PR body is composed — i.e., before anything reads the diff for the PR body (Phase 3 readsgit diff --name-only <base-branch>...HEADfor Changes Made, which should already reflect the regeneration commit if any).Acceptance Criteria:
SKILL.mdalgorithm (with output-format row) that runs T-1's command and conditionally commits.chore: regenerate mirrors from local dataset); it must not touch unstaged authored changes.Technical Requirements:
git status/git diffcheck.Implementation Approach:
Act: run T-1's script;Check: doesgit status --porcelainshow changes confined to the generated mirror paths;Skip: none → report nothing, move on;Act: stage only the generated paths and commit with a naming convention that says "regenerated", never "fixed";Verify:git logshows exactly one new commit, andgit statusstill shows the pre-existing unstaged authored changes (if any) untouched..claude/skills/pair-capability-publish-pr/SKILL.md(Development Collaboration authorship — skills/workflows content, per the context map)..claude/skills/pair-capability-publish-pr/SKILL.md— new algorithm step + output-format row (composed skills table update if/pair-capability-verify-quality's scope is affected — likely not).packages/knowledge-hub/dataset/.skills/...— the dataset source for this skill (mirror regenerated afterward, per the story's own DoD item: "Dataset edited as source for the skill change, mirror regenerated").Dependencies:
Implementation Steps:
packages/knowledge-hub/dataset/.skills/...), never the generated.claude/skills/**copy directly.├── Mirrors: [regenerated — commit <sha> | no drift — nothing to commit]).skills:conformanceis green.publish-prbefore the PR is touched, consistent with Phase 1's gate-red HALT).Testing Strategy:
publish-pronce on a branch with a deliberately drifted mirror and once on a clean branch; confirm the commit appears only in the first case.Notes: This is the task that most needs care per the story's sizing rationale ("a step that commits on the contributor's behalf must be predictable and silent when idle").
T-5: End-to-end test — dataset edit →
publish-pr→ one regeneration commitPriority: P1 | Estimated Hours: 2h | Bounded Context: Integration & Process Standardization
Summary: Fixture-repo integration test exercising the full path: edit a dataset file, run
publish-pron a branch, confirm exactly one separate regeneration commit is produced and the gate stays green — plus the no-op and dirty-tree edge cases.Type: Testing
Description: Covers the story's "Acceptance Testing Approach" end-to-end pass and the edge cases not already covered by T-1's unit tests: a mirror hand-edited earlier gets overwritten and reported as "regenerated" (not "fixed"); a dataset-wide drift produces one commit, not noise across feature commits; running
publish-prtwice commits nothing the second time (AC-2 applied at thepublish-prlevel, not just the script level).Acceptance Criteria:
InMemoryFileSystemService/fixture-repo conventions used elsewhere inpackages/dev-tools) exercising: drifted mirror → regenerated + committed separately; clean branch → silent no-op; missing dataset/no git → non-zero exit,publish-prHALTs before touching the PR; unstaged authored changes left untouched after the run.pnpm quality-gategreen on the fixture scenario.Technical Requirements:
Implementation Approach:
source-resolution.sh/ existing dev-tools test fixtures rather than inventing a new harness.packages/dev-tools(or whereverpublish-pr's underlying automation lives, if it is code rather than pure skill-prose — confirm during implementation; ifpublish-pris skill-prose only, this becomes a scripted smoke scenario alongsidescripts/smoke-tests/scenarios/source-resolution.sh).Dependencies:
Implementation Steps:
publish-prstep) and assert one regeneration commit.Testing Strategy:
Notes: This task is the story's acceptance test, not incidental coverage — it directly implements "Acceptance Testing Approach" from the story body.
T-6: Verify
pnpm format/gate:compositionunchanged and close the ADL's Open DecisionPriority: P2 | Estimated Hours: 1h | Bounded Context: Integration & Process Standardization
Summary: Confirm
pnpm formatbehaves exactly as before this story (AC-6), confirmgate:compositionstays green, and update ADL2026-07-31-pre-push-gate-is-check-only.mdto record that its Open Decision is now closed by this story.Type: Documentation / Verification
Description:
pnpm formatmust not gain any mirror-regeneration behavior — the whole point of a dedicated command is that formatting stays formatting. This task is the final DoD sweep: run the fullpnpm quality-gatelocally, confirmformatis untouched (diffpackage.json'sformat/format:check/prettier:fix/mdlint:fixscripts againstmain), and record closure of the ADL's Open Decision (the story body already states "this story closes its Open Decision" — the ADL file itself should reflect that per the project's decision-recording convention).Acceptance Criteria:
pnpm formatscripts diff empty againstmain;pnpm gate:compositiongreen; ADL updated to show the Open Decision as closed, referencing this story.format/format:checkscripts.git diff main -- package.jsonshows no change to theformat/format:check/prettier:*/mdlint:*script entries;pnpm gate:composition.Technical Requirements:
Implementation Approach:
.pair/adoption/decision-log/2026-07-31-pre-push-gate-is-check-only.md..pair/adoption/decision-log/2026-07-31-pre-push-gate-is-check-only.md— mark the Open Decision closed, linking this story./pair-capability-record-decision's job — invoke it rather than hand-editing, per the project's "record decisions" quick rule).Dependencies:
Implementation Steps:
pnpm quality-gateand confirm green.package.json's format-related scripts againstmain./pair-capability-record-decision(or hand-edit if unavailable) to close the ADL's Open Decision, referencing this story.Testing Strategy:
pnpm quality-gate(full local run).Notes: This is the story's closing task — nothing here should require new code if T-1 through T-5 are correctly scoped.