diff --git a/.claude/skills/pair-capability-analyze-debt/SKILL.md b/.claude/skills/pair-capability-analyze-debt/SKILL.md index 2b192d1a..4171e3e3 100644 --- a/.claude/skills/pair-capability-analyze-debt/SKILL.md +++ b/.claude/skills/pair-capability-analyze-debt/SKILL.md @@ -140,7 +140,7 @@ TECH DEBT ANALYSIS (output-only — no files or issues created): ├── Items Found: [N total] ├── Categories: Code: [N] | Design: [N] | Test: [N] | Docs: [N] | Infra: [N] ├── Severity: High: [N] | Medium: [N] | Low: [N] -└── Promotion: [none | suggested: N items for deliberate /pair-capability-write-issue promotion] +└── Promotion: [none | suggested: N items for deliberate /write-issue promotion] PRIORITIZED ITEMS: # | Severity | Category | Impact | Effort | Score | Description | Location diff --git a/.claude/skills/pair-capability-assess-ai/SKILL.md b/.claude/skills/pair-capability-assess-ai/SKILL.md index 17bc6599..3573353c 100644 --- a/.claude/skills/pair-capability-assess-ai/SKILL.md +++ b/.claude/skills/pair-capability-assess-ai/SKILL.md @@ -105,7 +105,7 @@ ASSESSMENT COMPLETE (output-only — no files written): ├── Decision: [primary tool + maturity level + additional tools] ├── Proposal: [content rendered for tech-stack.md AI section] ├── Target: adoption/tech/tech-stack.md (AI section) -├── Persist: [caller composes /pair-capability-record-decision(content, target) → ADL] +├── Persist: [caller composes /record-decision(content, target) → ADL] └── Status: [Proposal ready | Confirmed existing] ``` diff --git a/.claude/skills/pair-capability-assess-architecture/SKILL.md b/.claude/skills/pair-capability-assess-architecture/SKILL.md index a2cbc88e..a888f714 100644 --- a/.claude/skills/pair-capability-assess-architecture/SKILL.md +++ b/.claude/skills/pair-capability-assess-architecture/SKILL.md @@ -95,7 +95,7 @@ ASSESSMENT COMPLETE (output-only — no files written): ├── Decision: [pattern name] ├── Proposal: [content rendered for adoption/tech/architecture.md] ├── Target: adoption/tech/architecture.md (full file) -├── Persist: [caller composes /pair-capability-record-decision(content, target) → ADR] +├── Persist: [caller composes /record-decision(content, target) → ADR] └── Status: [Proposal ready | Confirmed existing] ``` diff --git a/.claude/skills/pair-capability-assess-infrastructure/SKILL.md b/.claude/skills/pair-capability-assess-infrastructure/SKILL.md index b2bdcab8..845796ee 100644 --- a/.claude/skills/pair-capability-assess-infrastructure/SKILL.md +++ b/.claude/skills/pair-capability-assess-infrastructure/SKILL.md @@ -95,7 +95,7 @@ ASSESSMENT COMPLETE (output-only — no files written): ├── Decision: [key infrastructure choices] ├── Proposal: [content rendered for infrastructure.md] ├── Target: adoption/tech/infrastructure.md (core sections) -├── Persist: [caller composes /pair-capability-record-decision(content, target) → ADR] +├── Persist: [caller composes /record-decision(content, target) → ADR] └── Status: [Proposal ready | Confirmed existing] ``` diff --git a/.claude/skills/pair-capability-assess-methodology/SKILL.md b/.claude/skills/pair-capability-assess-methodology/SKILL.md index 60b1a24a..7c764c6d 100644 --- a/.claude/skills/pair-capability-assess-methodology/SKILL.md +++ b/.claude/skills/pair-capability-assess-methodology/SKILL.md @@ -100,7 +100,7 @@ ASSESSMENT COMPLETE (output-only — no files written): ├── Decision: [methodology name] ├── Proposal: [content rendered for way-of-working.md methodology section] ├── Target: adoption/tech/way-of-working.md (methodology section) -├── Persist: [caller composes /pair-capability-record-decision(content, target) → ADL] +├── Persist: [caller composes /record-decision(content, target) → ADL] └── Status: [Proposal ready | Confirmed existing] ``` diff --git a/.claude/skills/pair-capability-assess-observability/SKILL.md b/.claude/skills/pair-capability-assess-observability/SKILL.md index 81f99b30..5c0dcf8d 100644 --- a/.claude/skills/pair-capability-assess-observability/SKILL.md +++ b/.claude/skills/pair-capability-assess-observability/SKILL.md @@ -98,7 +98,7 @@ ASSESSMENT COMPLETE (output-only — no files written): ├── Decision: [platform + logging + tracing + alerting] ├── Proposal: [content rendered for infrastructure.md observability section] ├── Target: adoption/tech/infrastructure.md (observability section) -├── Persist: [caller composes /pair-capability-record-decision(content, target) → ADL] +├── Persist: [caller composes /record-decision(content, target) → ADL] └── Status: [Proposal ready | Confirmed existing] ``` diff --git a/.claude/skills/pair-capability-assess-pm/SKILL.md b/.claude/skills/pair-capability-assess-pm/SKILL.md index 49ced1d4..47c5e5f9 100644 --- a/.claude/skills/pair-capability-assess-pm/SKILL.md +++ b/.claude/skills/pair-capability-assess-pm/SKILL.md @@ -102,9 +102,9 @@ ASSESSMENT COMPLETE (output-only for adoption — no files written by this skill ├── Domain: Project Management ├── Path: [Argument Override | Adoption Exists | Full Assessment] ├── Decision: [tool name] -├── Proposal: [content rendered for way-of-working.md PM section | delegated to /pair-capability-setup-pm] +├── Proposal: [content rendered for way-of-working.md PM section | delegated to /setup-pm] ├── Target: adoption/tech/way-of-working.md (PM tool section) -├── Persist: [caller composes /pair-capability-record-decision(content, target) → ADL | delegated to /pair-capability-setup-pm] +├── Persist: [caller composes /record-decision(content, target) → ADL | delegated to /setup-pm] └── Status: [Proposal ready | Confirmed existing | Delegated] ``` diff --git a/.claude/skills/pair-capability-assess-security/SKILL.md b/.claude/skills/pair-capability-assess-security/SKILL.md index 2aca1f6e..18767250 100644 --- a/.claude/skills/pair-capability-assess-security/SKILL.md +++ b/.claude/skills/pair-capability-assess-security/SKILL.md @@ -97,7 +97,7 @@ See [resolution cascade](../../../.pair/knowledge/skill-conventions/resolution-c ### Review Mode ```text -SECURITY REVIEW (composed by /pair-process-review — no files written): +SECURITY REVIEW (composed by /review — no files written): ├── Mode: Review ├── Verdict: [green | yellow | red] — [1-line summary] ├── Findings: [N total — N introduced, N pre-existing] @@ -124,7 +124,7 @@ SECURITY AUDIT COMPLETE: ├── Report: [.pair/working/reports/security/ — written | N/A — Path B] ├── Proposal: [content rendered for tech/security.md | N/A — Path B, already current] ├── Target: adoption/tech/security.md -├── Persist: [caller composes /pair-capability-record-decision(content, target) → ADL | N/A] +├── Persist: [caller composes /record-decision(content, target) → ADL | N/A] └── Status: [Report written | Confirmed existing | Proposal ready] ``` @@ -159,4 +159,4 @@ See [graceful degradation](../../../.pair/knowledge/skill-conventions/graceful-d - **Writes exactly one kind of file itself**: the audit report under `.pair/working/reports/security/` (Step 7) — the one exception to the `assess-*` family's usual output-only convention, justified the same way `/pair-capability-design-manual-tests` writes its own suite files: reports are operational artifacts (D14), not adoption content. Adoption content (`tech/security.md`) is never self-written — that always goes through the caller's `/pair-capability-record-decision` composition (Step 8). - **Idempotent** — see [idempotency convention](../../../.pair/knowledge/skill-conventions/idempotency.md). Audit mode's check: Path B (populated `tech/security.md` + backing decision record) confirms rather than re-running the full OWASP assessment. Review mode is not idempotent in that sense — a fresh verdict is computed every review, against the current diff, by design (findings can change commit to commit). - **Never scans for secrets**: committed-secret detection is a deterministic, LLM-free CI layer (R6.5) provisioned by `/pair-capability-setup-gates`, not an assessment this skill performs — keeping the "no LLM involved" guarantee real (D24). -- **D22** (1-line verdict + collapsed findings, never a table inline): see ADL [2026-07-18-d22-classification-verdict-collapsed-details.md](../../../.pair/adoption/decision-log/2026-07-18-d22-classification-verdict-collapsed-details.md). +- **D22** (1-line verdict + collapsed findings, never a table inline): a project-level decision record, not part of this portable dataset. diff --git a/.claude/skills/pair-capability-assess-stack/SKILL.md b/.claude/skills/pair-capability-assess-stack/SKILL.md index 1c916d7c..8cf0f375 100644 --- a/.claude/skills/pair-capability-assess-stack/SKILL.md +++ b/.claude/skills/pair-capability-assess-stack/SKILL.md @@ -184,7 +184,7 @@ ASSESSMENT COMPLETE (output-only — no files written): ├── Decision: [technology@version — or full stack summary] ├── Proposal: [content rendered for tech-stack.md — full | affected entry] ├── Target: adoption/tech/tech-stack.md (core sections) -├── Persist: [caller composes /pair-capability-record-decision(content, target) → ADL] +├── Persist: [caller composes /record-decision(content, target) → ADL] └── Status: [Proposal ready | Confirmed existing | Approved | Rejected] ``` diff --git a/.claude/skills/pair-capability-assess-testing/SKILL.md b/.claude/skills/pair-capability-assess-testing/SKILL.md index c6d053d0..ece31a58 100644 --- a/.claude/skills/pair-capability-assess-testing/SKILL.md +++ b/.claude/skills/pair-capability-assess-testing/SKILL.md @@ -101,7 +101,7 @@ ASSESSMENT COMPLETE (output-only — no files written): ├── Decision: [framework vX.Y + coverage tool + pyramid distribution] ├── Proposal: [content rendered for tech-stack.md testing section] ├── Target: adoption/tech/tech-stack.md (Testing section) -├── Persist: [caller composes /pair-capability-record-decision(content, target) → ADL] +├── Persist: [caller composes /record-decision(content, target) → ADL] └── Status: [Proposal ready | Confirmed existing] ``` diff --git a/.claude/skills/pair-capability-checkpoint/SKILL.md b/.claude/skills/pair-capability-checkpoint/SKILL.md index d9136767..ccd819c4 100644 --- a/.claude/skills/pair-capability-checkpoint/SKILL.md +++ b/.claude/skills/pair-capability-checkpoint/SKILL.md @@ -1,6 +1,6 @@ --- name: pair-capability-checkpoint -description: "Writes and resumes a self-contained progress checkpoint (story, branch, tasks done, decisions, remaining todos) so work survives a context reset. Invoke directly to save or resume progress ('save our progress on #313', 'resume story #256'); composed by a future closing phase of /pair-process-implement and a future /pair-capability-publish-pr (planned — #255)." +description: "Writes and resumes a self-contained progress checkpoint (story, branch, tasks done, decisions, remaining todos) so work survives a context reset. Invoke directly to save or resume progress ('save our progress on #313', 'resume story #256'); composed by a future closing phase of /pair-process-implement and a future /publish-pr (planned — #255)." version: 0.4.1 author: Foomakers --- @@ -47,7 +47,7 @@ Write and resume a self-contained progress checkpoint so a fresh session (or a s 3. **Act**: If not (e.g., invoked by a subagent with no prior context), reconstruct from artifacts: - **Branch**: `git branch --show-current`. - **Tasks done/pending**: read the story's Task Breakdown checklist (per [way-of-working.md](../../../.pair/adoption/tech/way-of-working.md)), cross-referenced with commits on the branch — the same technique `/pair-process-implement` uses for idempotent resume. - - **Decisions**: scan [adoption/tech/adr/](../../../.pair/adoption/tech/adr/) and [adoption/decision-log/](../../../.pair/adoption/decision-log/) for files touched since the branch diverged from main. + - **Decisions**: scan [adoption/tech/adr/](../../../.pair/adoption/tech/adr) and [adoption/decision-log/](../../../.pair/adoption/decision-log) for files touched since the branch diverged from main. 4. **Verify**: All five state elements resolved. Anything that cannot be reconstructed confidently is marked `[unknown — needs confirmation]` — never guessed. ### Step 4: Detect Existing Checkpoint @@ -162,9 +162,9 @@ When composed by a future closing phase of `/pair-process-implement`: - **Input**: `/pair-process-implement` would invoke `/pair-capability-checkpoint` with `$mode=write` between tasks (or on developer request) to persist progress, `$mode=resume` at Phase 0 when re-invoked on a story that may have been interrupted, and remove the checkpoint on story completion (cleanup). - **Output**: Write mode's returned text becomes the session's record of state. Resume mode's parsed state lets `/pair-process-implement` skip re-analysis and jump straight to the first pending task. -When composed by a future `/pair-capability-publish-pr` (planned — #255; companion capability from the same epic split): +When composed by a future `/publish-pr` (planned — #255; companion capability from the same epic split): -- **Input**: `/pair-capability-publish-pr` invokes `/pair-capability-checkpoint` with `$mode=write, $persist=false` to obtain a handoff prompt summarizing the story before drafting the PR description. +- **Input**: `/publish-pr` invokes `/pair-capability-checkpoint` with `$mode=write, $persist=false` to obtain a handoff prompt summarizing the story before drafting the PR description. - **Output**: The returned text (not written to file) is embedded directly into the composer's own output — the composer owns persistence. When invoked **independently**: diff --git a/.claude/skills/pair-capability-design-manual-tests/SKILL.md b/.claude/skills/pair-capability-design-manual-tests/SKILL.md index 58f8aee3..a3b306e4 100644 --- a/.claude/skills/pair-capability-design-manual-tests/SKILL.md +++ b/.claude/skills/pair-capability-design-manual-tests/SKILL.md @@ -146,7 +146,7 @@ MANUAL TEST SUITE GENERATED: ├── Files: [N critical path files + README] ├── Tests: [N total (N P0, N P1, N P2)] ├── Categories: [list] -└── Ready for: /pair-capability-execute-manual-tests +└── Ready for: /execute-manual-tests ``` ## Composition Interface diff --git a/.claude/skills/pair-capability-grill/SKILL.md b/.claude/skills/pair-capability-grill/SKILL.md index aa075398..96955e86 100644 --- a/.claude/skills/pair-capability-grill/SKILL.md +++ b/.claude/skills/pair-capability-grill/SKILL.md @@ -1,6 +1,6 @@ --- name: pair-capability-grill -description: "Interviews one question at a time, always with a recommendation, after exploring the KB/codebase first; sync mode drives systematic AI-human alignment on a story until shared understanding is confirmed. Invoke directly to interview on any topic ('grill me on the requirements'). Composed by /pair-process-refine-story and a future /pair-process-brainstorm (planned — #230). Write-free — returns the synthesis, never writes adoption or issues." +description: "Interviews one question at a time, always with a recommendation, after exploring the KB/codebase first; sync mode drives systematic AI-human alignment on a story until shared understanding is confirmed. Invoke directly to interview on any topic ('grill me on the requirements'). Composed by /pair-process-refine-story and a future /brainstorm (planned — #230). Write-free — returns the synthesis, never writes adoption or issues." version: 0.4.1 author: Foomakers --- @@ -29,7 +29,7 @@ Ask one question at a time, always with a recommended answer, after exploring th ### Interview Mode (default) -Freeform exploration of `$topic`. No fixed checklist — the question queue grows from what Step 1's exploration leaves open and from what answers reveal. Used standalone or composed by `/pair-process-brainstorm` (phase 1, planned — #230) to produce a raw requirements blob. +Freeform exploration of `$topic`. No fixed checklist — the question queue grows from what Step 1's exploration leaves open and from what answers reveal. Used standalone or composed by `/brainstorm` (phase 1, planned — #230) to produce a raw requirements blob. ### Sync Mode @@ -97,7 +97,7 @@ Systematic AI↔human alignment on a specific story, covering all six aspects of ### Step 6: Return — Never Write -1. **Act**: Return the synthesis to the caller (composed invocation) or print it in conversation (standalone). **Never** write to adoption files or PM tool issues in either mode — that stays with the composer (`/pair-process-brainstorm`, `/pair-process-refine-story`) or the human. +1. **Act**: Return the synthesis to the caller (composed invocation) or print it in conversation (standalone). **Never** write to adoption files or PM tool issues in either mode — that stays with the composer (`/brainstorm`, `/pair-process-refine-story`) or the human. 2. **Act (standalone, or session interrupted before Step 4's explicit yes)**: Offer an optional handoff file: > Save this as a handoff file in `.pair/working/`? (y/n) @@ -138,10 +138,10 @@ SYNTHESIS: ## Composition Interface -When composed by `/pair-process-brainstorm` (phase 1, planned — #230): +When composed by `/brainstorm` (phase 1, planned — #230): - **Input**: `$mode` omitted (defaults `interview`); `$topic` from the free theme text or the `$root` issue's body; `$context` carries the issue if `$root` was given. -- **Output**: Returns the raw requirements blob (R3.7). `/pair-process-brainstorm` carries it into phase 2 (domain integration) — grill never touches the PM tool or adoption files itself. +- **Output**: Returns the raw requirements blob (R3.7). `/brainstorm` carries it into phase 2 (domain integration) — grill never touches the PM tool or adoption files itself. When composed by `/pair-process-refine-story` (phase 0, future extension): @@ -158,7 +158,7 @@ When invoked **standalone**: ### Interview Mode (standalone, free topic) ```text -> /pair-capability-grill +> /grill AI: What's the topic? Recommendation: none — this is genuinely open, your call. You: caching strategy for the API layer @@ -190,7 +190,7 @@ AI: [continues through Edge Cases, Dependencies, Design] You: yes, flag that AI: Do we have shared understanding here? (yes / no) You: yes -AI: GRILL SESSION COMPLETE — Mode: sync · Coverage: 6/6 · returns alignment synthesis to /pair-process-refine-story +AI: GRILL SESSION COMPLETE — Mode: sync · Coverage: 6/6 · returns alignment synthesis to /refine-story ``` ## Edge Cases and Error Handling diff --git a/.claude/skills/pair-capability-map-contexts/SKILL.md b/.claude/skills/pair-capability-map-contexts/SKILL.md index f7c35bac..2bde7a38 100644 --- a/.claude/skills/pair-capability-map-contexts/SKILL.md +++ b/.claude/skills/pair-capability-map-contexts/SKILL.md @@ -1,6 +1,6 @@ --- name: pair-capability-map-contexts -description: "Maps subdomains to DDD bounded contexts and derives the integration pattern between them (integration strength, socio-technical distance, volatility), scoped to items just touched. Composed by /pair-process-refine-story, /pair-process-plan-tasks, a future /pair-process-brainstorm (planned — #230); full-scope re-mapping only via /pair-process-bootstrap." +description: "Maps subdomains to DDD bounded contexts and derives the integration pattern between them (integration strength, socio-technical distance, volatility), scoped to items just touched. Composed by /pair-process-refine-story, /pair-process-plan-tasks, a future /brainstorm (planned — #230); full-scope re-mapping only via /pair-process-bootstrap." version: 0.4.1 author: Foomakers --- @@ -17,11 +17,11 @@ Map subdomains to bounded context boundaries and assess each relationship betwee ## Composed Skills -| Caller | When | -| ----------------------------- | --------------------------------------------------------------------------- | +| Caller | When | +| ---------------- | --------------------------------------------------------------------------- | | `/pair-process-refine-story` | Technical analysis phase — identifies touched contexts/services. | | `/pair-process-plan-tasks` | Identifies touched contexts/services for the task breakdown. | -| `/brainstorm` | Punctual/technical brainstorm touching one or a few contexts (planned — #230). | +| `/brainstorm` | Punctual/technical brainstorm touching one or a few contexts (planned — #230). | | `/pair-process-bootstrap` | Initial full-catalog mapping — the only caller allowed `$scope: all`. | Invocable independently with an explicit `$scope` for ad hoc placement. @@ -30,7 +30,7 @@ Invocable independently with an explicit `$scope` for ad hoc placement. ### Step 0: Resolve Scope and DDD Adoption State -1. **Check**: Does [`adoption/tech/boundedcontext/`](../../../.pair/adoption/tech/boundedcontext/) contain `.md` files beyond README.md (DDD already adopted)? +1. **Check**: Does [`adoption/tech/boundedcontext/`](../../../.pair/adoption/tech/boundedcontext) contain `.md` files beyond README.md (DDD already adopted)? 2. **Check**: If not adopted, are subdomains defined ([`adoption/product/subdomain/`](../../../.pair/adoption/product/subdomain) has `.md` files beyond README.md)? 3. **Act**: Resolve the working mode: - **DDD mode** — bounded context catalog exists, or a subdomain catalog is available to map from. @@ -139,7 +139,7 @@ CONTEXT PLACEMENT COMPLETE: ├── Updated: [Y existing files] ├── Relationships: [balanced: A, unbalanced: B, gated: C] ├── Location: adoption/tech/boundedcontext/ -└── Next: /pair-process-plan-epics (scoped) or back to the calling process skill +└── Next: /plan-epics (scoped) or back to the calling process skill ``` ## Edge Cases and Error Handling @@ -167,4 +167,4 @@ See [graceful degradation](../../../.pair/knowledge/skill-conventions/graceful-d - **Idempotent** — see [idempotency convention](../../../.pair/knowledge/skill-conventions/idempotency.md). This skill's check: detects existing files by filename; only entries inside `$scope` are evaluated for changes. - The 3-dimension relationship assessment (strength, distance, volatility) is the input to the coupling-balance guideline — see `.pair/knowledge/guidelines/architecture/design-patterns/coupling-balance.md` (introduced by #209); until that guideline lands, apply the heuristics in Step 3 directly. - Contract-coupled (`contract` strength) relationships are annotated "contract tests expected" — `/pair-capability-design-manual-tests` and story-level validation strategies should pick up the contract/boundary test category for that relationship. -- Migration: this skill was reclassified from a process skill (`pair-process-map-contexts`) to a capability (`pair-capability-map-contexts`) — see [skills-guide.md](../../../.pair/knowledge/skills-guide.md#migration-notes) for the rename and new invocation paths. +- Migration: this skill was reclassified from a process skill (`process/map-contexts`) to a capability (`capability/map-contexts`) — see [skills-guide.md](../../../.pair/knowledge/skills-guide.md#migration-notes) for the rename and new invocation paths. diff --git a/.claude/skills/pair-capability-map-subdomains/SKILL.md b/.claude/skills/pair-capability-map-subdomains/SKILL.md index 80ba5092..02c605a2 100644 --- a/.claude/skills/pair-capability-map-subdomains/SKILL.md +++ b/.claude/skills/pair-capability-map-subdomains/SKILL.md @@ -1,6 +1,6 @@ --- name: pair-capability-map-subdomains -description: "Classifies business capabilities into DDD subdomains (core, supporting, generic) with a volatility rating, scoped to items just touched. Composed by /pair-process-refine-story, /pair-process-plan-initiatives, /pair-process-plan-epics, a future /pair-process-brainstorm (planned — #230); full-scope re-mapping only via /pair-process-bootstrap." +description: "Classifies business capabilities into DDD subdomains (core, supporting, generic) with a volatility rating, scoped to items just touched. Composed by /pair-process-refine-story, /pair-process-plan-initiatives, /pair-process-plan-epics, a future /brainstorm (planned — #230); full-scope re-mapping only via /pair-process-bootstrap." version: 0.4.1 author: Foomakers --- @@ -17,13 +17,13 @@ Classify business capabilities into Domain-Driven Design subdomains — core, su ## Composed Skills -| Caller | When | -| ------------------------------ | ----------------------------------------------------------------- | -| `/pair-process-refine-story` | Functional/domain analysis phase — placement for the story's capability. | -| `/pair-process-plan-initiatives` | Domain placement for a new initiative's capability area. | -| `/pair-process-plan-epics` | Domain placement for an epic's capability area. | -| `/brainstorm` | Broad brainstorm touching multiple capabilities (planned — #230). | -| `/pair-process-bootstrap` | Initial full-catalog mapping — the only caller allowed `$scope: all`. | +| Caller | When | +| ------------------- | ----------------------------------------------------------------- | +| `/pair-process-refine-story` | Functional/domain analysis phase — placement for the story's capability. | +| `/pair-process-plan-initiatives` | Domain placement for a new initiative's capability area. | +| `/pair-process-plan-epics` | Domain placement for an epic's capability area. | +| `/brainstorm` | Broad brainstorm touching multiple capabilities (planned — #230). | +| `/pair-process-bootstrap` | Initial full-catalog mapping — the only caller allowed `$scope: all`. | Invocable independently with an explicit `$scope` for ad hoc placement. @@ -31,7 +31,7 @@ Invocable independently with an explicit `$scope` for ad hoc placement. ### Step 0: Resolve Scope and DDD Adoption State -1. **Check**: Does [`adoption/product/subdomain/`](../../../.pair/adoption/product/subdomain/) contain `.md` files beyond README.md (DDD already adopted)? +1. **Check**: Does [`adoption/product/subdomain/`](../../../.pair/adoption/product/subdomain) contain `.md` files beyond README.md (DDD already adopted)? 2. **Check**: If not adopted, are PRD and initiatives available (business context to classify from)? - PRD: [`.pair/adoption/product/PRD.md`](../../../.pair/adoption/product/PRD.md) - Initiatives: query PM tool or check adoption files @@ -44,7 +44,7 @@ Invocable independently with an explicit `$scope` for ad hoc placement. ### Step 1: Detect Existing Subdomains -1. **Check**: Scan [`adoption/product/subdomain/`](../../../.pair/adoption/product/subdomain/) for existing `.md` files (excluding README.md). +1. **Check**: Scan [`adoption/product/subdomain/`](../../../.pair/adoption/product/subdomain) for existing `.md` files (excluding README.md). 2. **Act**: Build a registry of existing subdomains: ```text @@ -73,13 +73,15 @@ _Skip if in system-areas fallback — go to Step 2b._ ### Step 3: DDD Classification, Volatility & Catalog Delta +_Skip if in system-areas fallback — the Classification/Volatility already assigned in Step 2b (Generic/Low unless the caller has clear evidence otherwise) is final for this run; go straight to Step 4._ + 1. **Act** (DDD mode): Classify each in-scope capability: - **Core** — competitive advantage, high business value, high complexity. Build in-house, invest deeply. Default `Volatility: High`. - **Supporting** — operational necessity, medium value. Important but not differentiating. Default `Volatility: Medium`. - **Generic** — commodity function, low differentiation. Buy or use standard solutions. Default `Volatility: Low`. Additionally assess **implementation volatility** — the probability of switching provider/technology; when High, note that relationships toward this subdomain require an integration contract (feeds `/pair-capability-map-contexts` strength assessment). 2. **Act**: Volatility default follows the classification above; the developer may override it (business-domain judgment, never inferred from commit history alone). 3. **Act**: Map relationships and data flow between in-scope subdomains and their existing dependencies. -4. **Check**: Does an in-scope subdomain already exist in the registry with different classification/volatility (catalog conflict)? +4. **Check**: Does an in-scope subdomain already exist in the registry with a different classification, or a different Volatility that was **not** recorded as a human override? Compare against the existing file's recorded values, not a freshly-recomputed classification-derived default — a Volatility that already carries an override reason is never treated as conflicting with that same default recomputed again; only a change to classification, or an explicit new override request, is a conflict. 5. **Act**: If a conflict exists → propose the delta only (not a full re-map): > Existing: `[Name]` — Classification: [X], Volatility: [Y] @@ -126,7 +128,7 @@ SUBDOMAIN PLACEMENT COMPLETE: ├── Updated: [Y existing files] ├── Volatility: [defaults applied: A, overridden: B] ├── Location: adoption/product/subdomain/ -└── Next: /pair-capability-map-contexts (scoped to the same items) +└── Next: /map-contexts (scoped to the same items) ``` ## Edge Cases and Error Handling @@ -152,4 +154,4 @@ See [graceful degradation](../../../.pair/knowledge/skill-conventions/graceful-d - **Idempotent** — see [idempotency convention](../../../.pair/knowledge/skill-conventions/idempotency.md). This skill's check: detects existing files by filename; only entries inside `$scope` are evaluated for changes. - Volatility is evaluated from the business domain (classification-derived default + human override), never from commit history alone. - DDD classification and Volatility drive downstream assessments — see `/pair-capability-map-contexts` (relationship strength/distance/volatility) and the architecture-quality capability that consumes them. -- Migration: this skill was reclassified from a process skill (`pair-process-map-subdomains`) to a capability (`pair-capability-map-subdomains`) — see [skills-guide.md](../../../.pair/knowledge/skills-guide.md#migration-notes) for the rename and new invocation paths. +- Migration: this skill was reclassified from a process skill (`process/map-subdomains`) to a capability (`capability/map-subdomains`) — see [skills-guide.md](../../../.pair/knowledge/skills-guide.md#migration-notes) for the rename and new invocation paths. diff --git a/.claude/skills/pair-capability-record-decision/SKILL.md b/.claude/skills/pair-capability-record-decision/SKILL.md index cca3ad33..025908a6 100644 --- a/.claude/skills/pair-capability-record-decision/SKILL.md +++ b/.claude/skills/pair-capability-record-decision/SKILL.md @@ -175,7 +175,7 @@ DECISION RECORDED: ## Example: Generic Persist from an `assess-*` Proposal -Input — `/assess-testing` (Step 5) emits, and the caller invokes this skill with: +Input — `/pair-capability-assess-testing` (Step 5) emits, and the caller invokes this skill with: ```text $type: non-architectural diff --git a/.claude/skills/pair-capability-verify-adoption/SKILL.md b/.claude/skills/pair-capability-verify-adoption/SKILL.md index ceaf1e1d..dee7467a 100644 --- a/.claude/skills/pair-capability-verify-adoption/SKILL.md +++ b/.claude/skills/pair-capability-verify-adoption/SKILL.md @@ -116,7 +116,7 @@ ADOPTION COMPLIANCE REPORT: FINDINGS (if any): 1. [area] — [description] ([file:line]) - Resolution: [/assess-stack | /pair-capability-record-decision | developer action] + Resolution: [/assess-stack | /record-decision | developer action] 2. ... RESULT: [ALL CONFORMANT | N non-conformities across N areas] diff --git a/.claude/skills/pair-next/SKILL.md b/.claude/skills/pair-next/SKILL.md index 792a812c..3acdc4d3 100644 --- a/.claude/skills/pair-next/SKILL.md +++ b/.claude/skills/pair-next/SKILL.md @@ -11,7 +11,7 @@ Analyze project state and recommend the single most relevant next skill to invok ## Skill Catalog (36 skills) -The catalog is **derived from the installed corpus**: every installed skill must appear here — 9 process + 26 capability + `/pair-next` itself = 36. If an installed skill is missing from these tables (or a row names a skill that is not installed), the catalog has drifted: update the tables, the stated counts, and the cascade rows together. +The catalog is **derived from the installed corpus**: every skill directory under `.skills/` must appear here — 9 process + 26 capability + `/pair-next` itself = 36. If an installed skill is missing from these tables (or a row names a skill that is not installed), the catalog has drifted: update the tables, the stated counts, and the cascade rows together. ### Process Skills (9) @@ -29,8 +29,8 @@ The catalog is **derived from the installed corpus**: every installed skill must ### Capability Skills (26) -| Skill | Category | Description | -| ---------------------------------------- | --------------- | ---------------------------------------------------------------------------- | +| Skill | Category | Description | +| ------------------------ | --------------- | ---------------------------------------------------------------------------- | | `/pair-capability-map-subdomains` | Domain Modeling | Scoped DDD subdomain placement (+ Volatility) | | `/pair-capability-map-contexts` | Domain Modeling | Scoped DDD bounded-context placement + relationship assessment | | `/pair-capability-grill` | Alignment | Interview engine: explore a topic or sync on a story, one question at a time | @@ -151,7 +151,7 @@ Then ask: "Shall I run `/skill-name`?" ## Graceful Degradation -See [graceful degradation](../../.pair/knowledge/skill-conventions/graceful-degradation.md) (PM tool not accessible → skip Step 3, recommend from adoption files only; adoption files missing → suggest `/pair-process-bootstrap` as the entry point) for the standard scenarios. Additional cases: +See [graceful degradation](../../../.pair/knowledge/skill-conventions/graceful-degradation.md) (PM tool not accessible → skip Step 3, recommend from adoption files only; adoption files missing → suggest `/pair-process-bootstrap` as the entry point) for the standard scenarios. Additional cases: - If a suggested skill is not installed, tell the user which skill is needed and where to find it. - If way-of-working.md has no `## State Mapping` section, canonical macrostate names are assumed — this is the zero-configuration default, not a degradation. diff --git a/.claude/skills/pair-process-bootstrap/SKILL.md b/.claude/skills/pair-process-bootstrap/SKILL.md index 7cad391d..de1fdaaf 100644 --- a/.claude/skills/pair-process-bootstrap/SKILL.md +++ b/.claude/skills/pair-process-bootstrap/SKILL.md @@ -99,7 +99,7 @@ Orchestrate the complete project setup sequence. Transforms a PRD into a fully c ### Step 2.2: Assessment Phase (Optional) 1. **Check**: Are assess-\* skills installed? Scan installed skills directory for `assess-*` skills. -2. **Act** (installed): Compose assess-\* skills in recommended sequence — see [assess-orchestration.md](assess-orchestration.md) for the sequence, each skill's owned adoption-file section, and the parallel-safety/partial-installation rules. Each skill checks its own adoption file first — already-decided domains are skipped automatically (resolution cascade). **assess-\* skills are output-only**: each returns a proposal `{ content, target, decision-metadata }` and writes nothing. For each accepted proposal, `/pair-process-bootstrap` composes `/pair-capability-record-decision(content, target, decision-metadata)` — the **sole adoption writer** — to persist it. Never let an assess-\* skill write adoption directly. +2. **Act** (installed): Compose assess-\* skills in recommended sequence — see [assess-orchestration.md](./assess-orchestration.md) for the sequence, each skill's owned adoption-file section, and the parallel-safety/partial-installation rules. Each skill checks its own adoption file first — already-decided domains are skipped automatically (resolution cascade). **assess-\* skills are output-only**: each returns a proposal `{ content, target, decision-metadata }` and writes nothing. For each accepted proposal, `/pair-process-bootstrap` composes `/record-decision(content, target, decision-metadata)` — the **sole adoption writer** — to persist it. Never let an assess-\* skill write adoption directly. 3. **Act** (not installed): Warn and proceed with manual assessment: > assess-\* skills are not yet installed. Proceeding with manual assessment. @@ -227,7 +227,7 @@ Runs after architecture and tech-stack are adopted (Step 3.1) — both are prere ```text BOOTSTRAP COMPLETE: -├── PRD: [verified | created via /pair-process-specify-prd] +├── PRD: [verified | created via /specify-prd] ├── Categorization: [Type A | Type B | Type C] — [ADL path] ├── Adoption Files: │ ├── architecture.md: [generated | existing | skipped] @@ -237,7 +237,7 @@ BOOTSTRAP COMPLETE: │ └── way-of-working.md: [generated | existing | skipped] ├── Quality Gates: [N gates configured — standard + custom] ├── Domain Model: [subdomains: N | contexts: N | skipped — not installed] -├── PM Tool: [configured via /pair-capability-setup-pm | already configured] +├── PM Tool: [configured via /setup-pm | already configured] ├── Decisions: [N decisions recorded (ADR: X, ADL: Y)] └── Status: [Complete | Partial — details] ``` @@ -269,12 +269,12 @@ Phase completion is detected via output file existence — never re-does complet See [graceful degradation](../../../.pair/knowledge/skill-conventions/graceful-degradation.md) (optional skill not installed → skip that phase/step with a warning, never blocks) and [record-decision contract](../../../.pair/knowledge/skill-conventions/record-decision-contract.md) (`/pair-capability-record-decision` not installed → proposals cannot be persisted, document manually) for the standard scenarios. Additional cases (bootstrap's per-phase optional dependencies): - **assess-\* skills not installed**: Skip assessment phase, reference guideline files directly, ask developer for manual decisions. Log: "assess-\* skills not installed — using manual assessment." -- **/pair-process-specify-prd not installed**: HALT at Phase 0 if PRD is missing (a required dependency, not optional). Suggest creating PRD manually using how-to-01. -- **/pair-capability-setup-pm not installed**: Skip PM configuration in Phase 4. Warn: "PM tool not configured — /pair-capability-setup-pm not installed." +- **/specify-prd not installed**: HALT at Phase 0 if PRD is missing (a required dependency, not optional). Suggest creating PRD manually using how-to-01. +- **/setup-pm not installed**: Skip PM configuration in Phase 4. Warn: "PM tool not configured — /pair-capability-setup-pm not installed." - **Bootstrap checklist asset not found**: Use Phase 2 section questions as fallback — they cover the same areas. - **Adoption directory doesn't exist**: Create `adoption/tech/` and `adoption/decision-log/` on first write. -- **/pair-capability-record-decision not installed**: Adoption cannot be persisted automatically — assess-\* skills are output-only and never write adoption themselves. Warn: "/pair-capability-record-decision not installed — assess-\* proposals cannot be persisted. Write adoption files manually from the proposals and record decisions by hand." -- **/pair-capability-map-subdomains or /pair-capability-map-contexts not installed**: Skip the corresponding step in Phase 3.5 with a warning. Domain modeling never blocks bootstrap completion. +- **/record-decision not installed**: Adoption cannot be persisted automatically — assess-\* skills are output-only and never write adoption themselves. Warn: "/pair-capability-record-decision not installed — assess-\* proposals cannot be persisted. Write adoption files manually from the proposals and record decisions by hand." +- **/map-subdomains or /pair-capability-map-contexts not installed**: Skip the corresponding step in Phase 3.5 with a warning. Domain modeling never blocks bootstrap completion. ## Notes diff --git a/.claude/skills/pair-process-bootstrap/assess-orchestration.md b/.claude/skills/pair-process-bootstrap/assess-orchestration.md index e8745889..dd7f7443 100644 --- a/.claude/skills/pair-process-bootstrap/assess-orchestration.md +++ b/.claude/skills/pair-process-bootstrap/assess-orchestration.md @@ -1,6 +1,6 @@ # Phase 2, Step 2.2: Assess-\* Orchestration — Detail -Disclosed from [SKILL.md](SKILL.md) Step 2.2 — only reached when at least one `assess-*` skill is installed. +Disclosed from [SKILL.md](./SKILL.md) Step 2.2 — only reached when at least one `assess-*` skill is installed. **Recommended sequence** (respects adoption file dependencies): @@ -13,7 +13,7 @@ Disclosed from [SKILL.md](SKILL.md) Step 2.2 — only reached when at least one 7. `/pair-capability-assess-methodology` → proposes methodology section of `way-of-working.md` 8. `/pair-capability-assess-pm` → proposes PM section of `way-of-working.md` (delegates to `/pair-capability-setup-pm` when installed) -After each assessment (or after collecting the batch), compose `/pair-capability-record-decision(content, target, decision-metadata)` to persist the proposal and record the ADR/ADL. `/pair-capability-assess-pm` persists via `/pair-capability-setup-pm` when that skill is installed; otherwise `/pair-process-bootstrap` persists its proposal via `/pair-capability-record-decision` like the others. +After each assessment (or after collecting the batch), compose `/record-decision(content, target, decision-metadata)` to persist the proposal and record the ADR/ADL. `/pair-capability-assess-pm` persists via `/pair-capability-setup-pm` when that skill is installed; otherwise `/pair-process-bootstrap` persists its proposal via `/pair-capability-record-decision` like the others. **Section ownership** (each assess-\* proposal owns its section; `/pair-capability-record-decision` preserves the rest on write): diff --git a/.claude/skills/pair-process-implement/SKILL.md b/.claude/skills/pair-process-implement/SKILL.md index f6850ea4..43ec79c2 100644 --- a/.claude/skills/pair-process-implement/SKILL.md +++ b/.claude/skills/pair-process-implement/SKILL.md @@ -78,6 +78,7 @@ Ask: _"Ready to proceed with implementation?"_ - [architecture.md](../../../.pair/adoption/tech/architecture.md) — architectural patterns - [tech-stack.md](../../../.pair/adoption/tech/tech-stack.md) — approved libraries and versions - [way-of-working.md](../../../.pair/adoption/tech/way-of-working.md) — development process + - [Design Rules](../../../.pair/knowledge/guidelines/code-design/design-principles/design-rules.md) — evidence-based do/don't patterns to avoid generating (constraints, not suggestions) 4. **Verify**: Technical context loaded. If adoption files missing, warn and proceed with guideline defaults. ### Step 1.2: Create or Switch to Feature Branch @@ -134,7 +135,7 @@ Process tasks **sequentially**, one at a time. For each task: #### For Development Tasks (TDD Required): -Follow the TDD discipline rules strictly: +Follow the TDD discipline rules strictly, and the [Design Rules](../../../.pair/knowledge/guidelines/code-design/design-principles/design-rules.md) loaded in Step 1.1 — do not generate a new instance of a known do/don't pattern (e.g. god module, static-only namespace class, optional-bag dispatch instead of a discriminated union). #### TDD Discipline Rules: @@ -307,7 +308,7 @@ Follow the TDD discipline rules strictly: ## Phase 4: Post-Review Merge -After code review approval (typically via `/pair-process-review`), re-invoke `/pair-process-implement` to merge and close — see [post-review-merge.md](post-review-merge.md) for the verify-approval, merge-commit, merge, and parent-cascade steps (Steps 4.1–4.4) plus the completion output. +After code review approval (typically via `/pair-process-review`), re-invoke `/pair-process-implement` to merge and close — see [post-review-merge.md](./post-review-merge.md) for the verify-approval, merge-commit, merge, and parent-cascade steps (Steps 4.1–4.4) plus the completion output. ## Output Format @@ -324,7 +325,7 @@ IMPLEMENTATION COMPLETE: └── Quality: [All gates passing] ``` -At merge (Phase 4): see [post-review-merge.md](post-review-merge.md). +At merge (Phase 4): see [post-review-merge.md](./post-review-merge.md). ## HALT Conditions diff --git a/.claude/skills/pair-process-implement/post-review-merge.md b/.claude/skills/pair-process-implement/post-review-merge.md index 9d32c528..d914d403 100644 --- a/.claude/skills/pair-process-implement/post-review-merge.md +++ b/.claude/skills/pair-process-implement/post-review-merge.md @@ -1,6 +1,6 @@ # Phase 4: Post-Review Merge — Detail -Disclosed from [SKILL.md](SKILL.md) Phase 4 — only reached when `/pair-process-implement` is re-invoked after code review approval (typically via `/pair-process-review`), to merge and close the story. +Disclosed from [SKILL.md](./SKILL.md) Phase 4 — only reached when `/pair-process-implement` is re-invoked after code review approval (typically via `/pair-process-review`), to merge and close the story. ### Step 4.1: Verify Review Approval diff --git a/.claude/skills/pair-process-plan-epics/SKILL.md b/.claude/skills/pair-process-plan-epics/SKILL.md index f7705c9b..2637c35b 100644 --- a/.claude/skills/pair-process-plan-epics/SKILL.md +++ b/.claude/skills/pair-process-plan-epics/SKILL.md @@ -130,7 +130,7 @@ EPICS COMPLETE: ├── Skipped: [Z exact-match already-present] ├── Sprints: [estimated total sprints] ├── PM Tool: [adopted tool] -└── Next: /pair-process-plan-stories +└── Next: /plan-stories ``` ## HALT Conditions @@ -152,4 +152,4 @@ See [graceful degradation](../../../.pair/knowledge/skill-conventions/graceful-d - This skill **modifies PM tool state** — creates and extends epic issues linked to initiatives. - **Idempotent** — see [idempotency convention](../../../.pair/knowledge/skill-conventions/idempotency.md) and [to-issues-triage.md](../../../.pair/knowledge/skill-conventions/to-issues-triage.md). This skill's check: exact idempotency-key match is proposed `ALREADY EXISTS #ID` (skip) at triage time, before any write (Step 3) — Step 4 only executes the confirmed proposal; substantial-overlap match proposes EXTEND instead of a duplicate CREATE (Step 3) — re-running the same candidate tree never duplicates. - Epic 0 rule: for new projects, always assess if a bootstrap/foundation epic is needed before functional epics. -- Domain mapping (Step 3.5) is scoped to this run's epic breakdown — see [map-subdomains](../pair-capability-map-subdomains/SKILL.md). +- Domain mapping (Step 3.5) is scoped to this run's epic breakdown — see [map-subdomains](../../../.skills/capability/map-subdomains/SKILL.md). diff --git a/.claude/skills/pair-process-plan-initiatives/SKILL.md b/.claude/skills/pair-process-plan-initiatives/SKILL.md index aa2bc734..92938316 100644 --- a/.claude/skills/pair-process-plan-initiatives/SKILL.md +++ b/.claude/skills/pair-process-plan-initiatives/SKILL.md @@ -122,7 +122,7 @@ INITIATIVES COMPLETE: ├── Skipped: [Y existing] ├── Priority: [P0: A, P1: B, P2: C] ├── PM Tool: [adopted tool] -└── Next: /pair-capability-map-subdomains (scoped to this initiative) or /pair-process-plan-epics +└── Next: /map-subdomains (scoped to this initiative) or /plan-epics ``` ## HALT Conditions diff --git a/.claude/skills/pair-process-plan-stories/SKILL.md b/.claude/skills/pair-process-plan-stories/SKILL.md index 952a00ea..000241db 100644 --- a/.claude/skills/pair-process-plan-stories/SKILL.md +++ b/.claude/skills/pair-process-plan-stories/SKILL.md @@ -136,7 +136,7 @@ STORIES COMPLETE: ├── Points: [total estimated points] ├── INVEST: [all CREATE candidates validated] ├── PM Tool: [adopted tool] -└── Next: /pair-process-refine-story +└── Next: /refine-story ``` ## HALT Conditions diff --git a/.claude/skills/pair-process-plan-tasks/SKILL.md b/.claude/skills/pair-process-plan-tasks/SKILL.md index 09224056..a3412865 100644 --- a/.claude/skills/pair-process-plan-tasks/SKILL.md +++ b/.claude/skills/pair-process-plan-tasks/SKILL.md @@ -144,7 +144,7 @@ TASK BREAKDOWN COMPLETE: ├── Types: [Development: N, Documentation: N, Configuration: N] ├── AC Coverage:[K/K ACs covered] ├── PM Tool: [Story updated with Task Breakdown section] -└── Next: /pair-process-implement to start task execution +└── Next: /implement to start task execution ``` ## HALT Conditions diff --git a/.claude/skills/pair-process-refine-story/SKILL.md b/.claude/skills/pair-process-refine-story/SKILL.md index 8a203583..7795a5a5 100644 --- a/.claude/skills/pair-process-refine-story/SKILL.md +++ b/.claude/skills/pair-process-refine-story/SKILL.md @@ -145,7 +145,7 @@ STORY REFINEMENT COMPLETE: ├── Sections: [N/N complete] ├── Sizing: [X points — fits sprint: Yes/No] ├── PM Tool: [Issue updated — #ID] -└── Next: /pair-process-plan-tasks to create task breakdown +└── Next: /plan-tasks to create task breakdown ``` ## HALT Conditions diff --git a/.claude/skills/pair-process-review/SKILL.md b/.claude/skills/pair-process-review/SKILL.md index 8142ee62..d8afc556 100644 --- a/.claude/skills/pair-process-review/SKILL.md +++ b/.claude/skills/pair-process-review/SKILL.md @@ -11,8 +11,8 @@ Review a pull request through 6 sequential phases (5 review + 1 optional merge). ## Composed Skills -| Skill | Type | Required | Phase | Purpose | -| --------------------------------------- | ---------- | -------- | ----- | -------------------------------------------- | +| Skill | Type | Required | Phase | Purpose | +| ----------------------- | ---------- | -------- | ----- | -------------------------------------------- | | `/pair-capability-verify-quality` | Capability | Yes | 2 | Quality gate checking | | `/pair-capability-verify-done` | Capability | Yes | 4 | Definition of Done checking | | `/pair-capability-record-decision` | Capability | Yes | Any | Record missing ADR (HALT condition) | @@ -150,7 +150,7 @@ This phase uses a **4-level graceful degradation cascade** depending on which op ### Step 3.2: Run Adoption Check -Run the procedure for the level determined in Step 3.1 — see [degradation-levels.md](degradation-levels.md) for the exact steps of each of the 4 levels. +Run the procedure for the level determined in Step 3.1 — see [degradation-levels.md](./degradation-levels.md) for the exact steps of each of the 4 levels. ### Step 3.3: Verify Adoption Results @@ -227,7 +227,7 @@ Based on compiled findings: ## Phase 6: Merge & Close (APPROVED only, optional) -Only reached when the reviewer picked "Merge now" in Step 5.4 — see [merge-and-cascade.md](merge-and-cascade.md) for the merge-strategy, merge-commit, merge, parent-cascade, branch-cleanup, and post-merge-manual-test steps (Steps 6.1–6.6) plus the completion output. +Only reached when the reviewer picked "Merge now" in Step 5.4 — see [merge-and-cascade.md](./merge-and-cascade.md) for the merge-strategy, merge-commit, merge, parent-cascade, branch-cleanup, and post-merge-manual-test steps (Steps 6.1–6.6) plus the completion output. ## Output Format @@ -247,7 +247,7 @@ REVIEW COMPLETE: └── Report: [Posted as PR comment] ``` -At merge (Phase 6): see [merge-and-cascade.md](merge-and-cascade.md). +At merge (Phase 6): see [merge-and-cascade.md](./merge-and-cascade.md). ## HALT Conditions @@ -274,15 +274,15 @@ See [idempotency convention](../../../.pair/knowledge/skill-conventions/idempote See [graceful degradation](../../../.pair/knowledge/skill-conventions/graceful-degradation.md) (optional skill not installed → degrade, never HALT; PM tool not accessible → ask the reviewer directly) for the standard scenarios. Additional cases: -- **/pair-capability-verify-adoption not installed**: Falls back to inline dependency checking against [tech-stack.md](../../../.pair/adoption/tech/tech-stack.md). Warning logged. See degradation cascade (Phase 3). -- **/pair-capability-assess-stack not installed**: Unlisted dependencies flagged as warnings for manual verification. Does NOT HALT. -- **/pair-capability-analyze-debt not available**: Skip debt assessment, note in report. -- **/pair-capability-assess-security not installed**: Skip Step 2.4. Security Review section notes "manual judgment only — /pair-capability-assess-security not installed". Does NOT HALT; a manual security read of the diff is still expected per [how-to-11](../../../.pair/knowledge/how-to/11-how-to-code-review.md). +- **/verify-adoption not installed**: Falls back to inline dependency checking against [tech-stack.md](../../../.pair/adoption/tech/tech-stack.md). Warning logged. See degradation cascade (Phase 3). +- **/assess-stack not installed**: Unlisted dependencies flagged as warnings for manual verification. Does NOT HALT. +- **/analyze-debt not available**: Skip debt assessment, note in report. +- **/assess-security not installed**: Skip Step 2.4. Security Review section notes "manual judgment only — /pair-capability-assess-security not installed". Does NOT HALT; a manual security read of the diff is still expected per [how-to-11](../../../.pair/knowledge/how-to/11-how-to-code-review.md). - **Story not found**: Review proceeds with PR-only validation (no AC check). Phase 6 skips parent cascade. - **Code review template not found**: **HALT** — cannot produce review without template (a required dependency, not optional). - **PM tool not accessible**: Phase 6 merge via CLI only. - **Merge fails** (conflicts, branch protection): Report the failure, ask reviewer to resolve. Do not force-push or bypass protections. -- **/pair-capability-execute-manual-tests not installed**: Skip Step 6.6. Log "Manual test validation skipped — skill not installed." Does NOT block merge. +- **/execute-manual-tests not installed**: Skip Step 6.6. Log "Manual test validation skipped — skill not installed." Does NOT block merge. - **No manual test suite**: Skip Step 6.6. Log "No manual test suite found." Does NOT block merge. ## Notes diff --git a/.claude/skills/pair-process-review/degradation-levels.md b/.claude/skills/pair-process-review/degradation-levels.md index 7f42662f..ba992ae0 100644 --- a/.claude/skills/pair-process-review/degradation-levels.md +++ b/.claude/skills/pair-process-review/degradation-levels.md @@ -1,12 +1,12 @@ # Phase 3, Step 3.2: Adoption Compliance Levels — Detail -Disclosed from [SKILL.md](SKILL.md) Step 3.2. Run only the procedure matching the level determined in Step 3.1 — the other three never apply on this run. +Disclosed from [SKILL.md](./SKILL.md) Step 3.2. Run only the procedure matching the level determined in Step 3.1 — the other three never apply on this run. **Level 1** (/pair-capability-verify-adoption + /pair-capability-assess-stack): 1. Compose `/pair-capability-verify-adoption` with `$scope = all`. 2. For each non-conformity: - - **Tech-stack**: compose `/pair-capability-assess-stack` (output-only — returns a proposal) → on developer approval, `/pair-process-review` persists the entry via `/pair-capability-record-decision(content, target)` (the sole writer); on rejection → CHANGES-REQUESTED. + - **Tech-stack**: compose `/pair-capability-assess-stack` (output-only — returns a proposal) → on developer approval, `/pair-process-review` persists the entry via `/record-decision(content, target)` (the sole writer); on rejection → CHANGES-REQUESTED. - **Architecture**: report to developer for resolution. Missing ADR → HALT via `/pair-capability-record-decision`. - **Other** (security, coding-standards, infrastructure): report findings. 3. Record all results. diff --git a/.claude/skills/pair-process-review/merge-and-cascade.md b/.claude/skills/pair-process-review/merge-and-cascade.md index 9a7fb4be..77edfc7a 100644 --- a/.claude/skills/pair-process-review/merge-and-cascade.md +++ b/.claude/skills/pair-process-review/merge-and-cascade.md @@ -1,6 +1,6 @@ # Phase 6: Merge & Close — Detail -Disclosed from [SKILL.md](SKILL.md) Phase 6 — only reached when the reviewer picked "Merge now" in Step 5.4. +Disclosed from [SKILL.md](./SKILL.md) Phase 6 — only reached when the reviewer picked "Merge now" in Step 5.4. ### Step 6.1: Read Merge Strategy diff --git a/.github/agents/product-engineer.agent.md b/.github/agents/product-engineer.agent.md index fea84ebd..7e7c4e97 100644 --- a/.github/agents/product-engineer.agent.md +++ b/.github/agents/product-engineer.agent.md @@ -1,7 +1,6 @@ --- description: Product Engineer mode for AI-assisted development. Creates tasks, implements features, and manages development lifecycle following established how-to guides. -tools: ['vscode', 'execute', 'read', 'edit', 'search', 'web', 'gitkraken/*', 'github/*', 'agent', 'todo'] - +tools: ['edit', 'execute/runNotebookCell', 'read/getNotebookSummary', 'read/readNotebookCellOutput', 'search', 'vscode/getProjectSetupInfo', 'vscode/installExtension', 'vscode/newWorkspace', 'vscode/runCommand', 'execute/getTerminalOutput', 'execute/runInTerminal', 'read/terminalLastCommand', 'read/terminalSelection', 'execute/createAndRunTask', 'execute/getTaskOutput', 'execute/runTask', 'gitkraken/*', 'github/*', 'search/usages', 'vscode/vscodeAPI', 'read/problems', 'search/changes', 'execute/testFailure', 'vscode/openSimpleBrowser', 'web/fetch', 'web/githubRepo', 'vscode/extensions', 'todo', 'agent', 'execute/runTests'] --- # Product Engineer Agent @@ -13,13 +12,15 @@ You are a Product Engineer working in an AI-assisted development process followi Based on the how-to guides, you handle these development and implementation activities: ### ⚙️ **Task & Implementation Management (Your Primary Domain)** -- **Create Tasks** → [09-how-to-create-tasks.md]../../.pair/knowledge/how-to/09-how-to-create-tasks.md) + +- **Create Tasks** → [09-how-to-create-tasks.md](../../.pair/knowledge/how-to/09-how-to-create-tasks.md) - *Role: Product Engineer (Story Implementation Planning)* -- **Implement Tasks** → [10-how-to-implement-a-task.md]../../.pair/knowledge/how-to/10-how-to-implement-a-task.md) +- **Implement Tasks** → [10-how-to-implement-a-task.md](../../.pair/knowledge/how-to/10-how-to-implement-a-task.md) - *Role: Product Software Engineer (Implementation)* ### 📝 **Story Collaboration** -- **Refine User Stories** → [08-how-to-refine-a-user-story.md]../../.pair/knowledge/how-to/08-how-to-refine-a-user-story.md) + +- **Refine User Stories** → [08-how-to-refine-a-user-story.md](../../.pair/knowledge/how-to/08-how-to-refine-a-user-story.md) - *Role: Product Engineer (Technical Refinement)* - *Note: Collaborate with Product Manager on technical aspects* @@ -37,13 +38,15 @@ Run `/pair-next` at session start to determine which skill to invoke. ## Process Guidelines -**Before starting any activity:** +#### Before starting any activity: + 1. **Read the specific how-to guide** for the task you're performing 2. **Follow the exact process** defined in that guide 3. **Maintain session state** as specified in each how-to 4. **Apply technical standards** from `.pair/adoption/tech/` guidelines -**Key Documents to Reference:** +#### Key Documents to Reference: + - `.pair/adoption/tech/way-of-working.md` - Development workflow and PM tool - `.pair/adoption/tech/architecture.md` - Technical architecture patterns - `.pair/adoption/tech/tech-stack.md` - Approved technologies and libraries diff --git a/.github/agents/product-manager.agent.md b/.github/agents/product-manager.agent.md index 64309399..682d468a 100644 --- a/.github/agents/product-manager.agent.md +++ b/.github/agents/product-manager.agent.md @@ -1,7 +1,6 @@ --- description: Product Manager mode for AI-assisted development. Creates PRDs, defines strategy, manages initiatives and epics, and oversees product planning process following established how-to guides. -tools: ['vscode', 'execute', 'read', 'edit', 'search', 'web', 'agent', 'gitkraken/*', 'github/*', 'todo'] - +tools: ['edit', 'execute/runNotebookCell', 'read/getNotebookSummary', 'read/readNotebookCellOutput', 'search', 'vscode/getProjectSetupInfo', 'vscode/installExtension', 'vscode/newWorkspace', 'vscode/runCommand', 'execute/getTerminalOutput', 'execute/runInTerminal', 'read/terminalLastCommand', 'read/terminalSelection', 'execute/createAndRunTask', 'execute/getTaskOutput', 'execute/runTask', 'gitkraken/*', 'github/*', 'search/usages', 'vscode/vscodeAPI', 'read/problems', 'search/changes', 'execute/testFailure', 'vscode/openSimpleBrowser', 'web/fetch', 'web/githubRepo', 'vscode/extensions', 'todo', 'agent', 'execute/runTests'] --- # Product Manager Agent @@ -13,19 +12,21 @@ You are a Product Manager working in an AI-assisted development process followin Based on the how-to guides, you handle these strategic and planning activities: ### 🎯 **Strategic Planning (Your Primary Domain)** -- **Create PRD** → [01-how-to-create-PRD.md]../../.pair/knowledge/how-to/01-how-to-create-PRD.md) + +- **Create PRD** → [01-how-to-create-PRD.md](../../.pair/knowledge/how-to/01-how-to-create-PRD.md) - *Role: Product Manager (PRD Creation)* -- **Create and Prioritize Initiatives** → [03-how-to-create-and-prioritize-initiatives.md]../../.pair/knowledge/how-to/03-how-to-create-and-prioritize-initiatives.md) +- **Create and Prioritize Initiatives** → [03-how-to-create-and-prioritize-initiatives.md](../../.pair/knowledge/how-to/03-how-to-create-and-prioritize-initiatives.md) - *Role: Product Owner/Manager (Strategic Decomposition)* -- **Define Subdomains** → [04-how-to-define-subdomains.md]../../.pair/knowledge/how-to/04-how-to-define-subdomains.md) - - *Role: Product Manager (Domain Modeling)* + - Subdomain placement (`/pair-capability-map-subdomains`, scoped) happens inline — not a separate how-to. ### 📋 **Epic & Story Management** -- **Breakdown Epics** → [06-how-to-breakdown-epics.md]../../.pair/knowledge/how-to/06-how-to-breakdown-epics.md) + +- **Breakdown Epics** → [06-how-to-breakdown-epics.md](../../.pair/knowledge/how-to/06-how-to-breakdown-epics.md) - *Role: Product Owner/Manager (Strategic Decomposition)* -- **Breakdown User Stories** → [07-how-to-breakdown-user-stories.md]../../.pair/knowledge/how-to/07-how-to-breakdown-user-stories.md) + - Subdomain placement (`/pair-capability-map-subdomains`, scoped) happens inline — not a separate how-to. +- **Breakdown User Stories** → [07-how-to-breakdown-user-stories.md](../../.pair/knowledge/how-to/07-how-to-breakdown-user-stories.md) - *Role: Product Manager (Story Creation)* -- **Refine User Stories** → [08-how-to-refine-a-user-story.md]../../.pair/knowledge/how-to/08-how-to-refine-a-user-story.md) +- **Refine User Stories** → [08-how-to-refine-a-user-story.md](../../.pair/knowledge/how-to/08-how-to-refine-a-user-story.md) - *Role: Product Manager (Story Refinement)* ## Skill-Enabled Workflow @@ -36,7 +37,6 @@ If your agent supports [Agent Skills](https://agentskills.io), prefer invoking s |-------|--------|----------| | `/pair-process-specify-prd` | 01 | Create/update PRD | | `/pair-process-plan-initiatives` | 03 | Create and prioritize initiatives | -| `/pair-capability-map-subdomains` | 04 | Define subdomains (scoped capability) | | `/pair-process-plan-epics` | 06 | Break down epics | | `/pair-process-plan-stories` | 07 | Break down user stories | | `/pair-process-refine-story` | 08 | Refine user stories | @@ -45,13 +45,15 @@ Run `/pair-next` at session start to determine which skill to invoke. ## Process Guidelines -**Before starting any activity:** +#### Before starting any activity: + 1. **Read the specific how-to guide** for the task you're performing 2. **Follow the exact process** defined in that guide 3. **Maintain session state** as specified in each how-to 4. **Use established templates** and validation criteria -**Key Documents to Reference:** +#### Key Documents to Reference: + - `.pair/adoption/tech/way-of-working.md` - Current PM tool and processes - `.pair/adoption/product/PRD.md` - Product context - How-to guides for specific activities (linked above) diff --git a/.github/agents/staff-engineer.agent.md b/.github/agents/staff-engineer.agent.md index 0854aa37..0be2a93e 100644 --- a/.github/agents/staff-engineer.agent.md +++ b/.github/agents/staff-engineer.agent.md @@ -13,18 +13,20 @@ You are a Staff Engineer working in an AI-assisted development process following Based on the how-to guides, you handle these foundational and quality assurance activities: ### 🏗️ **Project Foundation & Architecture (Your Primary Domain)** + - **Complete Bootstrap Checklist** → [02-how-to-complete-bootstrap-checklist.md](../../.pair/knowledge/how-to/02-how-to-complete-bootstrap-checklist.md) - *Role: Staff Engineer (Project Setup)* -- **Define Bounded Contexts** → [05-how-to-define-bounded-contexts.md](../../.pair/knowledge/how-to/05-how-to-define-bounded-contexts.md) - - *Role: Staff Engineer (Technical Architecture)* + - Full-catalog domain modeling (`/pair-capability-map-subdomains` + `/pair-capability-map-contexts`, `$scope: all`) happens inline — not a separate how-to. ### 🔍 **Quality Assurance & Review** + - **Code Review** → [11-how-to-code-review.md](../../.pair/knowledge/how-to/11-how-to-code-review.md) - *Role: Staff Engineer (Quality Assurance)* ### 🤝 **Cross-Functional Collaboration** -- **Support Subdomain Definition** → [04-how-to-define-subdomains.md](../../.pair/knowledge/how-to/04-how-to-define-subdomains.md) - - *Role: Technical Advisor (collaborate with Product Manager)* + +- **Support Task Breakdown Context Mapping** → [09-how-to-create-tasks.md](../../.pair/knowledge/how-to/09-how-to-create-tasks.md) + - *Role: Technical Advisor (`/pair-capability-map-contexts`, scoped — collaborate with Product Engineer)* ## Skill-Enabled Workflow @@ -32,22 +34,22 @@ If your agent supports [Agent Skills](https://agentskills.io), prefer invoking s | Skill | How-To | Activity | |-------|--------|----------| -| `/pair-process-bootstrap` | 02 | Project setup and bootstrap | -| `/pair-capability-map-contexts` | 05 | Define bounded contexts (scoped capability) | +| `/pair-process-bootstrap` | 02 | Project setup and bootstrap (incl. full-catalog domain modeling) | | `/pair-process-review` | 11 | Code review | -| `/pair-capability-map-subdomains` | 04 | Support subdomain definition (scoped capability) | Run `/pair-next` at session start to determine which skill to invoke. ## Process Guidelines -**Before starting any activity:** +#### Before starting any activity: + 1. **Read the specific how-to guide** for the task you're performing 2. **Follow the exact process** defined in that guide 3. **Maintain session state** as specified in each how-to 4. **Apply architectural standards** and ensure technical coherence -**Key Documents to Reference:** +#### Key Documents to Reference: + - `.pair/adoption/tech/way-of-working.md` - Technical workflow and standards - `.pair/adoption/tech/architecture.md` - Architectural patterns and decisions - `.pair/adoption/tech/tech-stack.md` - Technology standards and constraints @@ -60,4 +62,4 @@ Run `/pair-next` at session start to determine which skill to invoke. - Project foundation properly established and maintained - Architectural decisions documented and consistently applied - Code quality standards enforced through systematic reviews -- Technical standards and patterns maintained across team \ No newline at end of file +- Technical standards and patterns maintained across team diff --git a/.gitignore b/.gitignore index 07a6ce8b..1d88baf8 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,7 @@ apps/website/scripts/landing-video/_issue-preview.html apps/website/scripts/landing-video/_login-closing.html .vercel .pair/working + +# Generated by `pair update` (local install markers) +.pair/.kb-version.json +.pair/.skill-name-map.json diff --git a/.pair/adoption/decision-log/2026-07-13-skill-naming-verb-taxonomy.md b/.pair/adoption/decision-log/2026-07-13-skill-naming-verb-taxonomy.md index 39df4e78..be9f1676 100644 --- a/.pair/adoption/decision-log/2026-07-13-skill-naming-verb-taxonomy.md +++ b/.pair/adoption/decision-log/2026-07-13-skill-naming-verb-taxonomy.md @@ -17,7 +17,7 @@ Convention Adoption The capability-skill corpus uses a verb prefix to signal what a skill *does*. Two verbs were clear, one was overloaded: - `verify-*` (verify-quality, verify-done, verify-adoption) — a **conformance** check that yields pass/fail against a standard. -- `assess-*` — used for **two different operations** at once: (a) the 8 "decision" skills (assess-stack, assess-architecture, assess-methodology, assess-pm, assess-testing, assess-infrastructure, assess-observability, assess-ai) that evaluate options and **propose an adoption choice** (output-only proposal persisted by `/record-decision` per ADR-009); and (b) 2 "report" skills (assess-debt, assess-code-quality) that **only analyze and report** — they propose no adoption decision and never block a PR. +- `assess-*` — used for **two different operations** at once: (a) the 8 "decision" skills (assess-stack, assess-architecture, assess-methodology, assess-pm, assess-testing, assess-infrastructure, assess-observability, assess-ai) that evaluate options and **propose an adoption choice** (output-only proposal persisted by `/pair-capability-record-decision` per ADR-009); and (b) 2 "report" skills (assess-debt, assess-code-quality) that **only analyze and report** — they propose no adoption decision and never block a PR. Folding "propose a decision" and "report only" under one verb blurred the reader's and the LLM router's expectation of a skill's effect, and made the `assess-*` descriptions harder to differentiate (a known #313 finding). The two report skills are conceptually neither conformance gates (`verify-*`) nor decision proposers (`assess-*`). @@ -26,7 +26,7 @@ Folding "propose a decision" and "report only" under one verb blurred the reader Adopt a **three-verb taxonomy** for capability skills, one verb per operation, not overloaded across branches: - **`verify-*`** = conformance pass/fail against a standard. (verify-quality, verify-done, verify-adoption) — unchanged. -- **`assess-*`** = evaluate options and **PROPOSE** an adoption choice; output-only proposal, persisted by `/record-decision` (ADR-009). The 8 decision skills — unchanged. +- **`assess-*`** = evaluate options and **PROPOSE** an adoption choice; output-only proposal, persisted by `/pair-capability-record-decision` (ADR-009). The 8 decision skills — unchanged. - **`analyze-*`** = analyze and **REPORT** only; proposes no adoption decision, creates nothing, never blocks a PR. Rename the 2 report skills to the `analyze-*` verb: @@ -46,11 +46,11 @@ Applies to both the dataset (`packages/knowledge-hub/dataset/.skills/capability/ - **Clear reader/router expectation**: the verb alone tells you whether a skill gates (verify), proposes a decision (assess), or only reports (analyze). - **Sharper descriptions/eval**: the report skills no longer sit inside the `assess-*` near-clone cluster, aiding trigger differentiation (#313 T3/T7). -- **Migration cost**: every cross-reference to the old names had to move in lockstep — router catalog + cascade, `/review` composition contract, skills-guide categories + Migration Notes, skills-catalog rows, writing-skills naming table, eval trigger-prompts, and the #224 conformance test. All done in the same PR (#313/T8). +- **Migration cost**: every cross-reference to the old names had to move in lockstep — router catalog + cascade, `/pair-process-review` composition contract, skills-guide categories + Migration Notes, skills-catalog rows, writing-skills naming table, eval trigger-prompts, and the #224 conformance test. All done in the same PR (#313/T8). - **History preserved, not rewritten**: point-in-time records that predate the rename (ADR-009, the 2026-07-12 eval baseline) keep the old names as historical fact; the forward mapping lives in the skills-guide Migration Notes and this ADL. ## Adoption Impact - Renamed skill dirs (dataset + mirror): `capability/analyze-debt/`, `capability/analyze-code-quality/`; `.claude/skills/pair-capability-analyze-debt/`, `.claude/skills/pair-capability-analyze-code-quality/`. -- Cross-references updated: `next` router (catalog rows + cascade row 14, category "Analysis"); `process/review` (required composed skill `/analyze-debt`); `skills-guide.md` (new "Analysis Skills" group; Verification = only the 3 `verify-*`; `manage-flags` → Operational; Code Quality bucket dissolved; Migration Notes entry); `apps/website/content/docs/reference/skills-catalog.mdx` (new "Analysis Capabilities" section); `apps/website/content/docs/concepts/skills.mdx`; `apps/website/content/docs/contributing/writing-skills.mdx` (verb taxonomy row + rule); `packages/knowledge-hub/eval/trigger-prompts.json` (family `analyze-report`); `packages/knowledge-hub/src/conformance/assess-output-only.test.ts` (analyze-* output-only + analyze-debt no scan-mode). -- Complements (does not amend) [adr-009-assess-output-only](../tech/adr/adr-009-assess-output-only.md): that decision makes assess-*/analyze-* output-only and names `/record-decision` the sole adoption writer; this ADL only renames the 2 report skills' verb. No dataset mirror of this ADL — sibling ADLs in `adoption/decision-log/` are adoption-only records. +- Cross-references updated: `next` router (catalog rows + cascade row 14, category "Analysis"); `process/review` (required composed skill `/pair-capability-analyze-debt`); `skills-guide.md` (new "Analysis Skills" group; Verification = only the 3 `verify-*`; `manage-flags` → Operational; Code Quality bucket dissolved; Migration Notes entry); `apps/website/content/docs/reference/skills-catalog.mdx` (new "Analysis Capabilities" section); `apps/website/content/docs/concepts/skills.mdx`; `apps/website/content/docs/contributing/writing-skills.mdx` (verb taxonomy row + rule); `packages/knowledge-hub/eval/trigger-prompts.json` (family `analyze-report`); `packages/knowledge-hub/src/conformance/assess-output-only.test.ts` (analyze-* output-only + analyze-debt no scan-mode). +- Complements (does not amend) [adr-009-assess-output-only](../tech/adr/adr-009-assess-output-only.md): that decision makes assess-*/analyze-* output-only and names `/pair-capability-record-decision` the sole adoption writer; this ADL only renames the 2 report skills' verb. No dataset mirror of this ADL — sibling ADLs in `adoption/decision-log/` are adoption-only records. diff --git a/.pair/adoption/tech/adr/adr-009-assess-output-only.md b/.pair/adoption/tech/adr/adr-009-assess-output-only.md index 89487e38..0f23eafd 100644 --- a/.pair/adoption/tech/adr/adr-009-assess-output-only.md +++ b/.pair/adoption/tech/adr/adr-009-assess-output-only.md @@ -10,28 +10,28 @@ Accepted ## Context -- Story #224 (Epic #209). The execution-layer dogfood surfaced an ambiguity in the `assess-*` capability skills: each skill both *assessed* a domain (produced a recommendation) **and** *persisted* the outcome (wrote its adoption file/section and composed `/record-decision`). Assessing therefore had side effects. -- `assess-debt` additionally risked backlog pollution: its remediation step composed `/record-decision`, and the broader design allowed auto-conversion of debt findings into tracked items. R7.2 requires that technical debt **never blocks a PR** and is surfaced without polluting the backlog. -- Two writers of adoption existed in practice (each assess-* skill + `/record-decision`), making "who owns the write" unclear and mirror/adoption drift easy to introduce. +- Story #224 (Epic #209). The execution-layer dogfood surfaced an ambiguity in the `assess-*` capability skills: each skill both *assessed* a domain (produced a recommendation) **and** *persisted* the outcome (wrote its adoption file/section and composed `/pair-capability-record-decision`). Assessing therefore had side effects. +- `assess-debt` additionally risked backlog pollution: its remediation step composed `/pair-capability-record-decision`, and the broader design allowed auto-conversion of debt findings into tracked items. R7.2 requires that technical debt **never blocks a PR** and is surfaced without polluting the backlog. +- Two writers of adoption existed in practice (each assess-* skill + `/pair-capability-record-decision`), making "who owns the write" unclear and mirror/adoption drift easy to introduce. - The system needs a clean separation between *assessing* (read + recommend) and *deciding/persisting* (write), so assessments are safe to run anywhere and persistence is explicit and gated. ## Options Considered ### Option 1: Keep assess-* as writers (status quo) -- **Description**: Each assess-* skill continues to write its adoption file/section and compose `/record-decision`. +- **Description**: Each assess-* skill continues to write its adoption file/section and compose `/pair-capability-record-decision`. - **Pros**: No change; fewer orchestration steps at call sites. - **Cons**: Assessing has side effects; two effective writers; backlog-pollution risk via scan/auto-conversion; harder to run an assessment "just to see" without mutating the project. ### Option 2: assess-* output-only; record-decision as the sole adoption writer -- **Description**: Every `assess-*` skill becomes output-only — it produces a proposal (`{ content, target, decision-metadata }`) plus a human-facing report and writes nothing. The caller (a process skill such as `/bootstrap`/`/review`, or a human/agent) persists the proposal by composing `/record-decision(content, target)`. `record-decision` stays generic: it writes `content` to `target` and records the ADR/ADL, with no per-domain rendering logic. `assess-debt` loses any `$mode:scan` and never auto-creates items; debt is reported and never blocks (R7.2). Deliberate promotion to the backlog goes through `/write-issue` with a `tech-debt` label. +- **Description**: Every `assess-*` skill becomes output-only — it produces a proposal (`{ content, target, decision-metadata }`) plus a human-facing report and writes nothing. The caller (a process skill such as `/pair-process-bootstrap`/`/pair-process-review`, or a human/agent) persists the proposal by composing `/record-decision(content, target)`. `record-decision` stays generic: it writes `content` to `target` and records the ADR/ADL, with no per-domain rendering logic. `assess-debt` loses any `$mode:scan` and never auto-creates items; debt is reported and never blocks (R7.2). Deliberate promotion to the backlog goes through `/pair-capability-write-issue` with a `tech-debt` label. - **Pros**: Assessing has zero side effects; a single adoption writer (`record-decision`); no backlog pollution; clear contract (assess renders content, record-decision persists, caller orchestrates); mirror/adoption drift is easier to reason about. - **Cons**: Composing flows must be rewired to persist proposals; a small orchestration step moves from the assess skill to the caller. ## Decision -Adopt **Option 2**. `assess-*` skills are **output-only**; `/record-decision` is the **sole generic writer** of adoption files. The one delegated exception is `/setup-pm`, which owns PM-tool configuration end-to-end: it writes the PM section of `way-of-working.md` directly, then composes `/record-decision` only for the decision record. +Adopt **Option 2**. `assess-*` skills are **output-only**; `/pair-capability-record-decision` is the **sole generic writer** of adoption files. The one delegated exception is `/pair-capability-setup-pm`, which owns PM-tool configuration end-to-end: it writes the PM section of `way-of-working.md` directly, then composes `/pair-capability-record-decision` only for the decision record. Contract (assess output → record-decision input): @@ -43,28 +43,28 @@ Additional rules: - `assess-debt` has **no `$mode:scan`** and performs **no auto-creation** of tech-debt items. Debt is **reported**, never blocks a PR (R7.2). - `pair-process-review` reports newly introduced debt in its output and never auto-creates an issue nor blocks on debt grounds. -- Deliberate promotion of a debt/quality finding to the backlog is a manual, selective act via `/write-issue` with the `tech-debt` label — never a 100% auto-conversion. +- Deliberate promotion of a debt/quality finding to the backlog is a manual, selective act via `/pair-capability-write-issue` with the `tech-debt` label — never a 100% auto-conversion. ## Consequences ### Benefits - **No side effects when assessing** — assessments are safe to run for information only. -- **Sole generic adoption writer** (`record-decision`) — one place owns generic adoption writes; the invariant "only record-decision writes adoption files generically (sole exception: `/setup-pm` writes the PM section of `way-of-working.md` directly); only assess-* renders its own content" is explicit. +- **Sole generic adoption writer** (`record-decision`) — one place owns generic adoption writes; the invariant "only record-decision writes adoption files generically (sole exception: `/pair-capability-setup-pm` writes the PM section of `way-of-working.md` directly); only assess-* renders its own content" is explicit. - **No backlog pollution** — debt is surfaced without auto-creating cards; promotion is deliberate. - **Debt never blocks PRs** (R7.2) is enforced by the review flow. ### Trade-offs and Limitations -- Composing flows (`bootstrap`, `review`) carry the persistence step (compose `/record-decision` per accepted proposal). This is a minor orchestration cost. -- If `/record-decision` is not installed, assess-* proposals cannot be persisted automatically — the proposal stands as a report and adoption stays unchanged until an explicit decision is made. -- `/implement` still composes `/assess-stack` for new dependencies; persistence there flows through its existing `/record-decision` step. The assess-stack composition wording in `/implement` is unaffected functionally. -- **`/setup-pm` is a delegated exception to the single-writer rule**: it writes the PM section of `way-of-working.md` directly (it owns PM-tool configuration end-to-end) and composes `/record-decision` only for the ADL. So `/record-decision` is the *sole generic* adoption writer, not the *only* process that ever touches an adoption file — the PM-tool section is deliberately owned by `/setup-pm`. +- Composing flows (`bootstrap`, `review`) carry the persistence step (compose `/pair-capability-record-decision` per accepted proposal). This is a minor orchestration cost. +- If `/pair-capability-record-decision` is not installed, assess-* proposals cannot be persisted automatically — the proposal stands as a report and adoption stays unchanged until an explicit decision is made. +- `/pair-process-implement` still composes `/pair-capability-assess-stack` for new dependencies; persistence there flows through its existing `/pair-capability-record-decision` step. The assess-stack composition wording in `/pair-process-implement` is unaffected functionally. +- **`/pair-capability-setup-pm` is a delegated exception to the single-writer rule**: it writes the PM section of `way-of-working.md` directly (it owns PM-tool configuration end-to-end) and composes `/pair-capability-record-decision` only for the ADL. So `/pair-capability-record-decision` is the *sole generic* adoption writer, not the *only* process that ever touches an adoption file — the PM-tool section is deliberately owned by `/pair-capability-setup-pm`. ## Adoption Impact - `packages/knowledge-hub/dataset/.skills/capability/assess-*/SKILL.md` and their installed mirrors (`.claude/skills/pair-capability-assess-*/SKILL.md`) — rewritten to output-only (proposal + report; no writes; no `assess-debt` scan-mode). - `packages/knowledge-hub/dataset/.skills/capability/record-decision/SKILL.md` (+ mirror) — documented as the sole adoption writer; accepts pre-rendered `{content, target}` (generic persist path). -- `packages/knowledge-hub/dataset/.skills/process/bootstrap/SKILL.md` and `.../process/review/SKILL.md` (+ mirrors) — rewired to persist assess-* proposals via `/record-decision`; review reports debt without creating issues or blocking. +- `packages/knowledge-hub/dataset/.skills/process/bootstrap/SKILL.md` and `.../process/review/SKILL.md` (+ mirrors) — rewired to persist assess-* proposals via `/pair-capability-record-decision`; review reports debt without creating issues or blocking. - `packages/knowledge-hub/dataset/.skills/capability/write-issue/SKILL.md` (+ mirror) — retains a `tech-debt` topical label (`$labels`) for deliberate promotion. - `apps/website/content/docs/reference/skills-catalog.mdx` — descriptions updated to reflect the output-only contract. diff --git a/.pair/adoption/tech/adr/adr-011-canonical-states-state-mapping.md b/.pair/adoption/tech/adr/adr-011-canonical-states-state-mapping.md index d57aa1a0..a7a385d0 100644 --- a/.pair/adoption/tech/adr/adr-011-canonical-states-state-mapping.md +++ b/.pair/adoption/tech/adr/adr-011-canonical-states-state-mapping.md @@ -10,7 +10,7 @@ Accepted ## Context -- pair skills (`/write-issue`, `/next`, and eventually the whole catalog) read and write work-item state on whatever PM tool board a project uses. Prior to this decision, skills embedded literal board-state labels directly (e.g., `/write-issue`'s `$status` examples listed `Refined`; `/next`'s cascade table matched on `"ready"`/`"todo"`/`"review"` string literals). +- pair skills (`/pair-capability-write-issue`, `/pair-next`, and eventually the whole catalog) read and write work-item state on whatever PM tool board a project uses. Prior to this decision, skills embedded literal board-state labels directly (e.g., `/pair-capability-write-issue`'s `$status` examples listed `Refined`; `/pair-next`'s cascade table matched on `"ready"`/`"todo"`/`"review"` string literals). - Board vocabularies vary per project and per PM tool: GitHub Projects, Jira, Linear, and filesystem boards all use different column names. pair's own recommended GitHub Projects board itself deviates from generic naming (`Todo`, `Refined`, `In Progress`, `Review`, `Done`). - Hardcoding board-state names in skill logic means every skill breaks (or silently misbehaves) the moment a project's board vocabulary differs — and makes skills unverifiable by grep for "does this skill assume a specific board". - Epic #202 (Canonical states, n-m mapping, DoR/DoD) requires a single place where state semantics are defined and a resolution convention that all skills compose, so that minimal boards (e.g., `Todo`/`In Progress`/`Done`, no dedicated "Refined"/"Ready" column) work without extra configuration. @@ -33,11 +33,11 @@ Accepted - **Description**: Define 5 canonical macrostates (`Draft`, `Ready`, `In Progress`, `Review`, `Done`) with their semantics in one KB guideline doc. Skills resolve state exclusively through an n-m map (`board-state → macrostate`) declared in an optional `## State Mapping` section of `way-of-working.md`. Omitted section ⇒ canonical names assumed (convention over configuration). Many board states may map to one macrostate; a board state never maps to more than one macrostate. Draft/Ready ambiguity on boards without a dedicated Ready column falls back to Definition-of-Ready criteria evaluated on the item. - **Pros**: Zero configuration for projects that already use pair's names; existing/idiosyncratic boards (including pair's own GitHub Projects setup) keep their names and just add a small mapping table; skills become grep-verifiable for board-name hardcoding; minimal boards without a "Refined"/"Ready" column are supported via the readiness fallback instead of forcing every board to grow a column it doesn't need. -- **Cons**: Introduces a schema every skill author must learn and every skill that reads/writes state must adopt; adds one resolution step to `/write-issue`'s and `/next`'s algorithms; the Draft/Ready readiness fallback needs a Definition-of-Ready criteria set to be fully precise (tracked as a dependent story). +- **Cons**: Introduces a schema every skill author must learn and every skill that reads/writes state must adopt; adds one resolution step to `/pair-capability-write-issue`'s and `/pair-next`'s algorithms; the Draft/Ready readiness fallback needs a Definition-of-Ready criteria set to be fully precise (tracked as a dependent story). ## Decision -Adopt Option 3. The KB guideline `packages/knowledge-hub/dataset/.pair/knowledge/guidelines/collaboration/project-management-tool/canonical-states.md` is the single source of truth for the 5 macrostates, their semantics, the state-mapping schema, and the read/write resolution rules (including first-mapped-wins for write targets and the readiness fallback for the Draft/Ready boundary). `way-of-working.md` gains an optional `## State Mapping` section, documented but left empty by default. `/write-issue` and `/next` are the first two skills to adopt the resolution convention; the rest of the skill catalog adopts it organically in the stories that touch each skill. +Adopt Option 3. The KB guideline `packages/knowledge-hub/dataset/.pair/knowledge/guidelines/collaboration/project-management-tool/canonical-states.md` is the single source of truth for the 5 macrostates, their semantics, the state-mapping schema, and the read/write resolution rules (including first-mapped-wins for write targets and the readiness fallback for the Draft/Ready boundary). `way-of-working.md` gains an optional `## State Mapping` section, documented but left empty by default. `/pair-capability-write-issue` and `/pair-next` are the first two skills to adopt the resolution convention; the rest of the skill catalog adopts it organically in the stories that touch each skill. ## Consequences @@ -45,12 +45,12 @@ Adopt Option 3. The KB guideline `packages/knowledge-hub/dataset/.pair/knowledge - Single source of truth for state semantics — no skill re-describes what "Ready" means. - Zero-configuration default for projects using pair's own names; existing boards with different vocabularies (including pair's own) add a small table instead of renaming columns. -- `/write-issue` and `/next` no longer contain hardcoded board-state literals — grep-verifiable. +- `/pair-capability-write-issue` and `/pair-next` no longer contain hardcoded board-state literals — grep-verifiable. - Minimal boards (no dedicated Ready column) are explicitly supported via the readiness fallback rather than being an unsupported edge case. ### Trade-offs and Limitations -- `/write-issue`'s algorithm grew one step (state resolution before the board write); `/next`'s cascade table now requires readers to understand macrostate resolution instead of reading literal board-state strings. +- `/pair-capability-write-issue`'s algorithm grew one step (state resolution before the board write); `/pair-next`'s cascade table now requires readers to understand macrostate resolution instead of reading literal board-state strings. - The readiness fallback currently relies on a minimal signal (acceptance criteria + technical analysis present); full Definition-of-Ready criteria are deferred to the dependent DoR/DoD story. - pair's own GitHub Projects board (`Todo`/`Refined`/`In Progress`/`Review`/`Done`) now technically deviates from the canonical names and needs its own `state-mapping` entry in this repo's adoption `way-of-working.md` — tracked as a separate migration story, not applied here. diff --git a/.pair/adoption/tech/adr/adr-012-map-subdomains-contexts-process-to-capability.md b/.pair/adoption/tech/adr/adr-012-map-subdomains-contexts-process-to-capability.md index 6d914946..ca93412d 100644 --- a/.pair/adoption/tech/adr/adr-012-map-subdomains-contexts-process-to-capability.md +++ b/.pair/adoption/tech/adr/adr-012-map-subdomains-contexts-process-to-capability.md @@ -10,55 +10,55 @@ Accepted ## Context -- `/map-subdomains` and `/map-contexts` were `.skills/process/` skills, invoked as standalone lifecycle steps (how-to 04 "Define Subdomains", how-to 05 "Define Bounded Contexts") run once, upfront, after bootstrap and before epic breakdown. -- In practice, domain modeling is not a one-time upfront phase: new capability areas and bounded contexts surface continuously as `/plan-initiatives`, `/plan-epics`, `/refine-story`, and `/plan-tasks` touch new parts of the product and the system. A standalone process step has no natural re-entry point for "just the delta this caller touched" — it either re-runs the full catalog (wasteful, and drifts from what the caller actually needs validated) or is skipped entirely once the initial pass is done (the catalog silently goes stale). -- Skipped-entirely is what happened in practice: the how-to 04/05 workflow was never wired as a composed step into any of `/plan-initiatives`, `/plan-epics`, `/refine-story`, `/plan-tasks`, or `/bootstrap` — each would have had to remember to invoke it manually. Epic #203 (context map, DDR, analysis log) and story #246 exist specifically because this gap was identified. +- `/pair-capability-map-subdomains` and `/pair-capability-map-contexts` were `.skills/process/` skills, invoked as standalone lifecycle steps (how-to 04 "Define Subdomains", how-to 05 "Define Bounded Contexts") run once, upfront, after bootstrap and before epic breakdown. +- In practice, domain modeling is not a one-time upfront phase: new capability areas and bounded contexts surface continuously as `/pair-process-plan-initiatives`, `/pair-process-plan-epics`, `/pair-process-refine-story`, and `/pair-process-plan-tasks` touch new parts of the product and the system. A standalone process step has no natural re-entry point for "just the delta this caller touched" — it either re-runs the full catalog (wasteful, and drifts from what the caller actually needs validated) or is skipped entirely once the initial pass is done (the catalog silently goes stale). +- Skipped-entirely is what happened in practice: the how-to 04/05 workflow was never wired as a composed step into any of `/pair-process-plan-initiatives`, `/pair-process-plan-epics`, `/pair-process-refine-story`, `/pair-process-plan-tasks`, or `/pair-process-bootstrap` — each would have had to remember to invoke it manually. Epic #203 (context map, DDR, analysis log) and story #246 exist specifically because this gap was identified. ## Options Considered ### Option 1: Keep as process skills, invoked manually when domain changes -- **Description**: No reclassification. Developers manually re-invoke `/map-subdomains`/`/map-contexts` whenever they judge the domain has shifted. +- **Description**: No reclassification. Developers manually re-invoke `/pair-capability-map-subdomains`/`/pair-capability-map-contexts` whenever they judge the domain has shifted. - **Pros**: No migration, no new invocation contract. - **Cons**: No formal composition by any caller — exactly the gap this decision exists to close. Catalogs drift silently; there is no grep-verifiable claim that any skill keeps them current. ### Option 2: Inline the DDD/BC algorithm into each caller skill -- **Description**: Duplicate the classification/placement logic directly inside `/plan-initiatives`, `/plan-epics`, `/refine-story`, `/plan-tasks`, `/bootstrap`. +- **Description**: Duplicate the classification/placement logic directly inside `/pair-process-plan-initiatives`, `/pair-process-plan-epics`, `/pair-process-refine-story`, `/pair-process-plan-tasks`, `/pair-process-bootstrap`. - **Pros**: No separate skill to compose or install. - **Cons**: Duplicates a non-trivial algorithm (DDD classification, Volatility defaults, per-relationship strength/distance/volatility assessment) across 5+ skills; any future change to the algorithm requires editing all of them in lockstep; breaks the composition pattern already established by the `assess-*` capabilities. ### Option 3: Reclassify as capabilities, invoked scoped by each caller (chosen) -- **Description**: Move both skills to `.skills/capability/`. `$scope` becomes a **required** argument meaning "the items/areas the caller just touched" — never a full re-map by default. `$scope: all` (full-catalog) is permitted only when composed by `/bootstrap`, since it is the only caller with no prior touched-items context to scope to. Every other real caller (`/plan-initiatives`, `/plan-epics`, `/plan-tasks`; `/refine-story` planned — #242) composes the capability with its own scope, following the same pattern as `/bootstrap`'s composition of `/assess-stack`, `/assess-architecture`, etc. +- **Description**: Move both skills to `.skills/capability/`. `$scope` becomes a **required** argument meaning "the items/areas the caller just touched" — never a full re-map by default. `$scope: all` (full-catalog) is permitted only when composed by `/pair-process-bootstrap`, since it is the only caller with no prior touched-items context to scope to. Every other real caller (`/pair-process-plan-initiatives`, `/pair-process-plan-epics`, `/pair-process-plan-tasks`; `/pair-process-refine-story` planned — #242) composes the capability with its own scope, following the same pattern as `/pair-process-bootstrap`'s composition of `/pair-capability-assess-stack`, `/pair-capability-assess-architecture`, etc. - **Pros**: Single source of truth for the algorithm; catalogs update incrementally as a side effect of normal process-skill usage instead of a separate manual pass; consistent with the existing capability-composition pattern; idempotent partial catalogs (only `$scope` entries are touched) instead of forcing an all-or-nothing full re-map. -- **Cons**: Requires wiring every real caller explicitly — the wiring itself is separate work from the reclassification (this is the gap the #246 review found: the how-to text described callers that had no actual composition in their `SKILL.md`). `/refine-story`'s wiring is deferred to #242 rather than done here, to avoid colliding with that story's broader Draft→Ready rework. +- **Cons**: Requires wiring every real caller explicitly — the wiring itself is separate work from the reclassification (this is the gap the #246 review found: the how-to text described callers that had no actual composition in their `SKILL.md`). `/pair-process-refine-story`'s wiring is deferred to #242 rather than done here, to avoid colliding with that story's broader Draft→Ready rework. ## Decision -Adopt Option 3. `/map-subdomains` and `/map-contexts` are capabilities under `.skills/capability/`. `$scope` is required; `all` is permitted only when composed by `/bootstrap`. How-to guides 04 and 05 are removed — they described a standalone process step that no longer exists. Each real caller's own how-to (02 bootstrap, 03 initiatives, 06 epics, 09 tasks) now references the capability inline, at the point in its own algorithm where it composes it; `/refine-story`'s how-to (08) documents the wiring as planned (#242) rather than claiming it exists. The [Callers Matrix](../../../.pair/knowledge/skills-guide.md#callers-matrix-scoped-capabilities) in `skills-guide.md` is the single source of truth for which callers compose which capability, at what scope — every row that is not explicitly marked `(planned — #NNN)` is a verified composition, not an aspirational claim. +Adopt Option 3. `/pair-capability-map-subdomains` and `/pair-capability-map-contexts` are capabilities under `.skills/capability/`. `$scope` is required; `all` is permitted only when composed by `/pair-process-bootstrap`. How-to guides 04 and 05 are removed — they described a standalone process step that no longer exists. Each real caller's own how-to (02 bootstrap, 03 initiatives, 06 epics, 09 tasks) now references the capability inline, at the point in its own algorithm where it composes it; `/pair-process-refine-story`'s how-to (08) documents the wiring as planned (#242) rather than claiming it exists. The [Callers Matrix](../../../.pair/knowledge/skills-guide.md#callers-matrix-scoped-capabilities) in `skills-guide.md` is the single source of truth for which callers compose which capability, at what scope — every row that is not explicitly marked `(planned — #NNN)` is a verified composition, not an aspirational claim. ## Consequences ### Benefits - Single source of truth for DDD/BC classification, Volatility defaults, and per-relationship strength/distance/volatility assessment — no caller re-implements it. -- Domain/context catalogs update incrementally as a side effect of `/plan-initiatives`, `/plan-epics`, `/plan-tasks`, and `/bootstrap` running normally — no separate manual pass to remember. +- Domain/context catalogs update incrementally as a side effect of `/pair-process-plan-initiatives`, `/pair-process-plan-epics`, `/pair-process-plan-tasks`, and `/pair-process-bootstrap` running normally — no separate manual pass to remember. - Grep-verifiable claims: the Callers Matrix can be (and was) checked against each caller's `Composed Skills` table instead of trusted on faith. - Graceful degradation (capability not installed) never blocks the composing process skill — domain modeling failures are non-fatal by design. ### Trade-offs and Limitations -- The reclassification alone does not wire anything — three real callers (`/bootstrap`, `/plan-epics`, `/plan-tasks`) had zero composition despite how-to 04/05 already describing them as callers before this decision's remediation; this ADR's Adoption Impact section lists the wiring done to close that gap as part of the same story. -- `/refine-story` wiring is intentionally deferred to #242, not done here — the Callers Matrix marks those two rows `(planned — #242)` rather than silently leaving them wrong. -- Full-catalog `$scope: all` at `/bootstrap` time uses whatever PRD/initiative context exists at that point in the bootstrap sequence; for `/map-contexts` this is after architecture.md and tech-stack.md are adopted (Phase 3.5, post Phase 3), since both are prerequisites. +- The reclassification alone does not wire anything — three real callers (`/pair-process-bootstrap`, `/pair-process-plan-epics`, `/pair-process-plan-tasks`) had zero composition despite how-to 04/05 already describing them as callers before this decision's remediation; this ADR's Adoption Impact section lists the wiring done to close that gap as part of the same story. +- `/pair-process-refine-story` wiring is intentionally deferred to #242, not done here — the Callers Matrix marks those two rows `(planned — #242)` rather than silently leaving them wrong. +- Full-catalog `$scope: all` at `/pair-process-bootstrap` time uses whatever PRD/initiative context exists at that point in the bootstrap sequence; for `/pair-capability-map-contexts` this is after architecture.md and tech-stack.md are adopted (Phase 3.5, post Phase 3), since both are prerequisites. ## Adoption Impact - `packages/knowledge-hub/dataset/.skills/process/map-subdomains/` → `packages/knowledge-hub/dataset/.skills/capability/map-subdomains/`; same rename for `map-contexts`. -- `packages/knowledge-hub/dataset/.skills/process/bootstrap/SKILL.md` — new Phase 3.5 composing `/map-subdomains` then `/map-contexts` with `$scope: all`. -- `packages/knowledge-hub/dataset/.skills/process/plan-epics/SKILL.md` — new Step 3.5 composing `/map-subdomains` scoped to the approved epic breakdown. -- `packages/knowledge-hub/dataset/.skills/process/plan-tasks/SKILL.md` — new Step 2.5 composing `/map-contexts` scoped to the story's touched bounded contexts. +- `packages/knowledge-hub/dataset/.skills/process/bootstrap/SKILL.md` — new Phase 3.5 composing `/pair-capability-map-subdomains` then `/pair-capability-map-contexts` with `$scope: all`. +- `packages/knowledge-hub/dataset/.skills/process/plan-epics/SKILL.md` — new Step 3.5 composing `/pair-capability-map-subdomains` scoped to the approved epic breakdown. +- `packages/knowledge-hub/dataset/.skills/process/plan-tasks/SKILL.md` — new Step 2.5 composing `/pair-capability-map-contexts` scoped to the story's touched bounded contexts. - `packages/knowledge-hub/dataset/.pair/knowledge/how-to/04-how-to-define-subdomains.md`, `05-how-to-define-bounded-contexts.md` — removed. - `packages/knowledge-hub/dataset/.pair/knowledge/how-to/02-*.md`, `03-*.md`, `06-*.md`, `08-*.md`, `09-*.md` — updated to reference the capability inline at the point their composing skill invokes it. - `packages/knowledge-hub/dataset/.pair/knowledge/skills-guide.md` — Callers Matrix corrected to mark only genuinely unwired rows as `(planned — #NNN)`. diff --git a/.pair/knowledge/getting-started.md b/.pair/knowledge/getting-started.md index 8378527a..ccb80013 100644 --- a/.pair/knowledge/getting-started.md +++ b/.pair/knowledge/getting-started.md @@ -67,8 +67,6 @@ Welcome to the AI-Human pairing repository template! This guide provides essenti │ ├── 01-how-to-create-PRD.md │ ├── 02-how-to-complete-bootstrap-checklist.md │ ├── 03-how-to-create-and-prioritize-initiatives.md -│ ├── 04-how-to-define-subdomains.md -│ ├── 05-how-to-define-bounded-contexts.md │ ├── 06-how-to-breakdown-epics.md │ ├── 07-how-to-breakdown-user-stories.md │ ├── 08-how-to-refine-a-user-story.md diff --git a/.pair/knowledge/guidelines/code-design/design-principles/README.md b/.pair/knowledge/guidelines/code-design/design-principles/README.md index a9ac273d..50601575 100644 --- a/.pair/knowledge/guidelines/code-design/design-principles/README.md +++ b/.pair/knowledge/guidelines/code-design/design-principles/README.md @@ -25,6 +25,7 @@ Fundamental design principles and coding practices that guide the development of - `service-abstraction.md` - `service-factory.md` - `mocking-strategy.md` +- `design-rules.md` ## How to use @@ -89,6 +90,14 @@ Establish core design principles that ensure code quality, maintainability, and - Dependency isolation and test reliability - Mock management and test maintainability +### Evidence-Based Rules + +#### [Design Rules](design-rules.md) + +- Do/don't rules with recognition criteria and preferred alternative, one pattern per entry +- Evidence-linked to real audit findings (not invented) — no separate anti-pattern catalog +- Migration plan for existing violations, tracked as tech-debt (does not block PRs) + ## Design Philosophy ### Code as Communication diff --git a/.pair/knowledge/guidelines/code-design/design-principles/design-rules.md b/.pair/knowledge/guidelines/code-design/design-principles/design-rules.md new file mode 100644 index 00000000..65d6403d --- /dev/null +++ b/.pair/knowledge/guidelines/code-design/design-principles/design-rules.md @@ -0,0 +1,124 @@ +# Design Rules + +## Overview + +Evidence-based do/don't rules for patterns that repeatedly show up as tech-debt in codebase audits — the ones an AI assistant tends to (re)generate unless explicitly constrained. Each rule: **recognition** (how to spot it), **alternative** (what to do instead). No separate anti-pattern catalog — these rules live here, in the code-design guidelines, and are consolidated as new evidence appears. + +**Precedence**: if a rule conflicts with a project's own adoption decision (`.pair/adoption/tech/`), the adoption decision wins. Note the override next to the rule it affects instead of deleting the rule (it may still apply to other projects/packages). + +**Review usage**: a diff clearly matching a rule's recognition criteria is a **violation** — reference the rule ID in the finding. A partial or uncertain match is a **suggestion**, not a violation — don't block on it. + +**Migration**: existing code already violating a rule is tracked as `tech-debt` (Draft, P1–P3 by impact/effort) — see `technical-debt.md`. Violations never block a PR by themselves; only new code introducing a fresh instance of a known rule is a review finding. + +## DR-1 — God Module (oversized file / class) + +**Don't**: let a file or class absorb every function related to a topic as it grows, until it covers several unrelated responsibilities (parsing + validation + I/O + logging in the same file). + +**Recognition**: a single non-test file keeps growing past the point where its exports no longer share one responsibility — usually visible as 15+ exported functions/methods, or a file so large its one-line "what it does" summary needs an "and". + +```typescript +// copyPathOps.ts — copy, mirror-cleanup, link rewriting, validation, logging... +export async function copyFile(...) {} +export async function copyDirectory(...) {} +export async function handleMirrorCleanup(...) {} +// + 16 more exports, unrelated concerns, one file +``` + +**Do**: split along natural seams — one file per responsibility — as soon as a module accumulates concerns that don't change for the same reason. Re-export from an index only if a stable public API is required. + +```typescript +// copy-file.ts — single-file copy +// copy-directory.ts — directory traversal + recursion +// copy-orchestrator.ts — dispatch + shared setup +``` + +## DR-2 — Static-Only Namespace Class + +**Don't**: wrap a set of stateless functions in a `class` used only for its `static` methods. It adds ceremony (import the class, call through it) without adding behavior — no instance, no state, no polymorphism. + +**Recognition**: a class where every method is `static` and no instance property/state exists — it's a namespace pretending to be an object. + +```typescript +// Illustrative anti-pattern (not live code): +export class LinkProcessor { + static async extractLinks(content: string) {} + static async extractLinksFromFile(path: string, fs: FileSystemService) {} + // 18 static methods total, zero instance state +} +``` + +**Do**: use a module with named exports. TypeScript modules already are the namespace — no wrapper class needed. + +```typescript +// link-processor.ts +export async function extractLinks(content: string) {} +export async function extractLinksFromFile(path: string, fs: FileSystemService) {} +``` + +## DR-3 — Optional-Bag Dispatch Instead of Discriminated Union + +**Don't**: model "this is either an A or a B" as one object type where A's and B's fields are all optional (`Partial`), then branch with `if (isA) {...} else if (isB) {...}` and force each field with a non-null assertion (`!`) at every use site. This is the concrete, type-unsafe cousin of "too many ifs": the compiler can't tell you which fields are actually present in each branch, so every read needs a manual assertion instead of the compiler narrowing it for you. + +**Recognition**: a `Partial` (or several optional fields) combined with `if`/`else if` branches on a runtime check (e.g. `stat.isDirectory()` / `stat.isFile()`), where each branch re-picks a subset of fields and asserts them non-null. + +```typescript +type MoveCtx = Partial & { + fileService: FileSystemService + srcPath: string + destPath: string +} + +if (stat.isDirectory()) { + await handleDirectoryMove({ ...ctx, source: ctx.source!, target: ctx.target! }) +} else if (stat.isFile()) { + await handleFileMove({ ...ctx, source: ctx.source!, target: ctx.target! }) +} +``` + +**Do**: model the branches as a discriminated union with a tag field, and let the compiler narrow — no assertions needed. + +```typescript +type MoveOp = + | { kind: 'directory'; params: HandleDirectoryMoveParams } + | { kind: 'file'; params: HandleFileMoveParams } + +switch (op.kind) { + case 'directory': + return handleDirectoryMove(op.params) + case 'file': + return handleFileMove(op.params) +} +``` + +## DR-4 — Split Files Without a Barrel + +**Don't**: split a god module (DR-1) into several sibling files that are only ever consumed together through a single entry point, and leave them as loose files in the parent directory. The directory listing grows noisy and a reader can't tell at a glance which of the siblings is the actual public API versus internal-only support files. + +**Recognition**: a group of files in the same directory where exactly one function/class is imported by anything outside the directory, and the rest are internal collaborators imported only by that one entry point or by each other. + +```typescript +// ops/ +// widget-orchestrator.ts <- the only export consumed outside ops/ +// widget-validation.ts <- internal-only, imported by widget-orchestrator.ts +// widget-transform.ts <- internal-only, imported by widget-orchestrator.ts +// widget-types.ts <- internal-only, shared types +``` + +**Do**: group the split files in their own folder with an `index.ts` barrel that re-exports exactly what's externally consumed today. From the caller's side nothing changes (same import path, now resolving to the folder); the directory now signals which files are the public surface (the barrel) versus internal implementation. + +```typescript +// ops/widget/ +// index.ts -> export { widgetOrchestrator } from './widget-orchestrator' +// widget-orchestrator.ts +// widget-validation.ts +// widget-transform.ts +// widget-types.ts +``` + +## Related + +- [Technical Debt](../quality-standards/technical-debt.md) — how migration items are prioritized (P1–P3) and tracked +- [SOLID Principles](solid-principles.md) — DR-1 is the practical, evidence-backed form of Single Responsibility +- [Naming Conventions](../code-organization/naming-conventions.md) — DR-2 is a naming/module-shape convention violation +- [TypeScript](../framework-patterns/typescript.md) — DR-3 is a type-narrowing pattern (discriminated unions) +- [File Structure](../code-organization/file-structure.md) — DR-4 is a directory/module-grouping convention, applied at the split-file level diff --git a/.pair/knowledge/guidelines/code-design/quality-standards/README.md b/.pair/knowledge/guidelines/code-design/quality-standards/README.md index 90fc6e6f..3c55ab32 100644 --- a/.pair/knowledge/guidelines/code-design/quality-standards/README.md +++ b/.pair/knowledge/guidelines/code-design/quality-standards/README.md @@ -22,6 +22,7 @@ Quality standards provide guidance on linting, formatting, code metrics, coverag - `linting-tools.md` (recommended linters and rules) - `eslint.md` (ESLint configuration and conventions) - `prettier-formatting.md` (formatting rules) +- `shared-config-packages.md` (shared-config-package pattern, per-type overrides, `tools/*` reference) - `code-metrics.md` (metrics & measurement) - `coverage.md` (coverage targets and strategies) - `technical-debt.md` (tracking and remediation) @@ -66,6 +67,7 @@ Guidelines for linting, formatting, code metrics, test coverage and automation t - `linting-tools.md` - `eslint.md` - `prettier-formatting.md` +- `shared-config-packages.md` - `code-metrics.md` - `coverage.md` - `technical-debt.md` diff --git a/.pair/knowledge/guidelines/code-design/quality-standards/eslint.md b/.pair/knowledge/guidelines/code-design/quality-standards/eslint.md index b14d419c..4ab96143 100644 --- a/.pair/knowledge/guidelines/code-design/quality-standards/eslint.md +++ b/.pair/knowledge/guidelines/code-design/quality-standards/eslint.md @@ -115,7 +115,7 @@ For existing codebases, introduce ESLint rules gradually: ### Team Configuration -**Shared Config Package**: Create a shared ESLint configuration package for consistency across projects. This centralizes rule decisions and makes updates easier. +**Shared Config Package**: Create a shared ESLint configuration package for consistency across projects. This centralizes rule decisions and makes updates easier. See [shared-config-packages.md](shared-config-packages.md) for the full pattern (per-type presets, bin wrappers, reference implementation). **Rule Documentation**: Document the reasoning behind custom rules, especially those that differ from community standards. This helps team members understand the rationale. diff --git a/.pair/knowledge/guidelines/code-design/quality-standards/shared-config-packages.md b/.pair/knowledge/guidelines/code-design/quality-standards/shared-config-packages.md new file mode 100644 index 00000000..fce66fae --- /dev/null +++ b/.pair/knowledge/guidelines/code-design/quality-standards/shared-config-packages.md @@ -0,0 +1,95 @@ +# Shared Config Packages + +## Overview + +Lint, format, type-check and markdown-lint rules duplicated across workspaces drift: each copy gets patched independently until "consistent tooling" is a fiction. The **shared-config-package pattern** fixes this by extracting each tool's configuration into its own workspace package, versioned like any other dependency, consumed via the tool's native extension mechanism (`extends`, `require`, or a `prettier`/similar field). One source of truth per concern; workspaces only carry local overrides. + +This guideline documents the pattern generically and then walks through pair's own `tools/*` packages as the reference implementation (R7.1). + +## The Pattern + +1. **One package per concern** — do not bundle lint + format + types into a single "tooling" package. Each tool gets its own package (`*-config`) so it can version and release independently. +2. **Consume via the tool's own mechanism** — don't invent a custom loader. Use `extends` for `tsconfig.json`, `require`/`extends` for ESLint flat config, a `"prettier"` field in `package.json`, and a config-discovery bin wrapper for tools that don't support a field (markdownlint). +3. **Package the enforcement, not just the rules** — ship bin wrappers (`lint`, `lint-fix`, `prettier-check`, `markdownlint-check`, …) alongside the config so workspace `package.json` scripts stay one-liners instead of re-declaring flags/paths everywhere. (`mdlint:check` is the root script alias that calls the `markdownlint-check` bin — not a bin name itself.) +4. **Workspace configs carry only local overrides** — `paths`, `references`, `outDir`, package-specific `ignores`. Everything shared lives in the config package. +5. **Version like a dependency** — `@scope/*-config` is a `workspace:*` devDependency; bumping it (e.g. tightening a rule) is a normal dependency update, reviewed like one. + +## Per-Type Overrides + +Not every workspace needs the same ruleset. Split each config package into a **base** preset plus **type-specific** presets that extend it: + +| Type | What changes vs base | Typical need | +| --- | --- | --- | +| Backend / service (Node CLI, API, worker) | Node types, module resolution | `node.json`-style preset | +| Frontend (web app) | JSX, DOM types, React lint rules | `ui.json`-style preset + React lint overlay | +| Shared lib | Depends on what the lib *is* — a logic-only lib behaves like backend, a UI component lib behaves like frontend | Pick the matching preset, not a third one | + +Two rules keep this from becoming per-stack special-casing: + +- **Only split where the tool actually needs it.** TypeScript compiler options and lint rules diverge by runtime (Node vs browser) — split those. Formatting does not diverge by type — keep one Prettier config for everything. +- **The taxonomy is about the preset the package needs, not the package's category.** A "shared lib" is not a third preset; it picks whichever existing preset (`node`/`ui`) matches what it ships. + +## Reference Implementation: pair's `tools/*` + +pair's own monorepo is the canonical example. Four config packages, each consumed differently based on what the underlying tool supports: + +| Package | Presets / exports | Consumption | Bin wrappers | +| --- | --- | --- | --- | +| `tools/ts-config` (`@pair/ts-config`) | `base.json` → `node.json` / `ui.json` | `tsconfig.json`: `"extends": "@pair/ts-config/node.json"` | — (`tsc` runs directly) | +| `tools/eslint-config` (`@pair/eslint-config`) | `eslint.config.cjs` (base) → `eslint.config.react.cjs` (React overlay) | Base: bin wrappers apply it with no local file needed. Override: local `eslint.config.cjs` does `module.exports = require('@pair/eslint-config/eslint.config.react.cjs')` | `lint`, `lint-fix`, `eslint` | +| `tools/prettier-config` (`@pair/prettier-config`) | single `.prettierrc.json` (no per-type split — formatting doesn't diverge by type) | `package.json`: `"prettier": "@pair/prettier-config"` | `prettier-check`, `prettier-fix` | +| `tools/markdownlint-config` (`@pair/markdownlint-config`) | single `.markdownlint.jsonc` (+ ignore file) | discovered by the bin wrappers, no per-package config file | `markdownlint-check`, `markdownlint-fix` | + +Per-type mapping in practice — Node/service/lib workspaces (`apps/pair-cli`, `packages/knowledge-hub`, `packages/content-ops`) extend `@pair/ts-config/node.json` and take the ESLint base config as-is. Frontend and UI-lib workspaces (`apps/website`, `packages/brand`) extend `@pair/ts-config/ui.json` and override `eslint.config.cjs` to layer the React overlay: + +```jsonc +// apps/api/tsconfig.json (backend/CLI — node preset) +{ "extends": "@pair/ts-config/node.json", "compilerOptions": { /* local overrides only */ } } +``` + +```jsonc +// apps/web/tsconfig.json (frontend — ui preset) +{ "extends": "@pair/ts-config/ui.json", "compilerOptions": { "jsx": "preserve" /* local overrides only */ } } +``` + +```js +// packages/ui-lib/eslint.config.cjs (shared UI lib — same override as frontend, because it ships JSX) +module.exports = require('@pair/eslint-config/eslint.config.react.cjs') +``` + +Every consuming workspace declares the config packages as ordinary `workspace:*` devDependencies (see any `apps/*/package.json` or `packages/*/package.json`) — they are dependencies, not magic. + +## Enforcement: Hooks + +Configs without enforcement get bypassed under deadline pressure. Wire two git hooks: + +- **Pre-commit** — fast local checks (type-check and/or lint on staged files). +- **Pre-push** — the full quality-gate command (lint + test + type-check), so nothing broken reaches CI. + +**Husky is the KB default hook manager** (D21 / Q11) — `.husky/pre-commit` and `.husky/pre-push` scripts, wired via `"prepare": "husky install"` in the root `package.json`. pair's own hooks are two one-liners: + +```sh +# .husky/pre-commit — fast local feedback +pnpm ts:check +``` + +```sh +# .husky/pre-push — full lint before code leaves the machine +pnpm quality-gate +``` + +A leaner project can put the actual lint/format commands on pre-commit instead (e.g. `lint-staged` running `eslint --fix` + `prettier --write` on staged files) and keep pre-push as the guard for anything pre-commit skipped. Either way, pre-push must run lint at minimum. + +Adoption can override the hook manager (e.g. `lefthook`, `simple-git-hooks`) by recording the choice in `way-of-working.md`; `/pair-capability-setup-gates` reads that override before provisioning. + +## Non-JS / Polyglot Projects + +The shared-config-package mechanism above (`extends`, `require`, package-manager fields) is JS/TS-tooling-specific. For other ecosystems, the *pattern* still applies — one versioned config artifact per concern, per-type presets, hook-enforced — but provisioning cannot generate it automatically. In that case `/pair-capability-setup-gates` documents the pattern and points to the ecosystem's equivalent (e.g. a shared `ruff`/`clippy`/`golangci-lint` config package) rather than generating JS config files. + +## Related + +- [linting-tools.md](linting-tools.md) — tool selection matrix +- [eslint.md](eslint.md), [prettier-formatting.md](prettier-formatting.md) — per-tool configuration detail +- [automation.md](automation.md) — pre-commit/CI automation patterns +- `/pair-capability-setup-gates` — provisions this pattern on a project (see SKILL.md provisioning step) +- ADR-006 (`.pair/adoption/tech/adr/adr-006-shared-tsconfig.md`, pair's own dogfooded adoption) — the decision record for `tools/ts-config` diff --git a/.pair/knowledge/guidelines/collaboration/README.md b/.pair/knowledge/guidelines/collaboration/README.md index 1e38e5a2..80f5dcea 100644 --- a/.pair/knowledge/guidelines/collaboration/README.md +++ b/.pair/knowledge/guidelines/collaboration/README.md @@ -54,6 +54,10 @@ This framework does not cover: **[Decision Records](decision-records.md)** - ADR (Architecture Decision Records) and ADL (Adoption Decision Log) formats, file naming conventions, and integration with skills +### Operational Conventions + +**[Working Area](working-area.md)** - `.pair/working/` convention: checkpoints and reports, exclusion from KB registries (D14), and the `working_path` override + ## Introduction to Collaboration Excellence ### Purpose diff --git a/.pair/knowledge/guidelines/collaboration/automation/azure-devops-automation.md b/.pair/knowledge/guidelines/collaboration/automation/azure-devops-automation.md index e2e07fa9..d8adbaea 100644 --- a/.pair/knowledge/guidelines/collaboration/automation/azure-devops-automation.md +++ b/.pair/knowledge/guidelines/collaboration/automation/azure-devops-automation.md @@ -13,7 +13,7 @@ Azure Boards supports per-column and per-state automation configured in board se - **PR opened → Review-mapped column** — when a linked PR is created, move the work item to the column mapped to `Review` - **PR completed → Done-mapped column** — `az repos pr update --transition-work-items true` performs this transition on merge - **Child activation → parent activation** — when a Task moves to an In Progress-mapped state, activate the parent story -- **All children closed → resolve parent** — optional; pair's `/review` skill also performs the parent cascade explicitly +- **All children closed → resolve parent** — optional; pair's `/pair-process-review` skill also performs the parent cascade explicitly ### State Transition Consistency diff --git a/.pair/knowledge/guidelines/collaboration/decision-records.md b/.pair/knowledge/guidelines/collaboration/decision-records.md index 242c81b3..db50d077 100644 --- a/.pair/knowledge/guidelines/collaboration/decision-records.md +++ b/.pair/knowledge/guidelines/collaboration/decision-records.md @@ -39,7 +39,7 @@ ADR and DDR use sequential naming: `adr-NNN-.md` / `ddr-NNN-.md` - **NNN**: Zero-padded 3-digit sequence number, incremented from the highest existing `adr-NNN`/`ddr-NNN` in the respective directory (starts at `001`). - **Topic**: Short kebab-case description (e.g., `refund-window-30-days`). -- Rationale: architectural and domain decisions form supersede chains and are referenced by number elsewhere (brainstorm/refine conflict flags, cross-references) — a chain reads more naturally as `adr-003 supersedes adr-001` than as date deltas. See ADL [2026-07-12-adr-sequential-naming-convention.md](../../../adoption/decision-log/2026-07-12-adr-sequential-naming-convention.md) (updated 2026-07-18 with the DDR/ADL/Analysis-Log rationale). +- Rationale: architectural and domain decisions form supersede chains and are referenced by number elsewhere (brainstorm/refine conflict flags, cross-references) — a chain reads more naturally as `adr-003 supersedes adr-001` than as date deltas. Examples: diff --git a/.pair/knowledge/guidelines/collaboration/issue-management/azure-devops-issues.md b/.pair/knowledge/guidelines/collaboration/issue-management/azure-devops-issues.md index b8202c0b..a72283f4 100644 --- a/.pair/knowledge/guidelines/collaboration/issue-management/azure-devops-issues.md +++ b/.pair/knowledge/guidelines/collaboration/issue-management/azure-devops-issues.md @@ -117,9 +117,9 @@ az boards work-item update --id [id] --state "Committed" ### Typical Story Flow -1. **Creation** — `/plan-stories` creates the PBI in the Draft-mapped state -2. **Refinement** — `/refine-story` completes AC + analysis, moves to the Ready-mapped state -3. **Implementation** — `/implement` moves to the In Progress-mapped state +1. **Creation** — `/pair-process-plan-stories` creates the PBI in the Draft-mapped state +2. **Refinement** — `/pair-process-refine-story` completes AC + analysis, moves to the Ready-mapped state +3. **Implementation** — `/pair-process-implement` moves to the In Progress-mapped state 4. **PR opened** — PR linked via `--work-items`; item moves to the Review-mapped state 5. **Merge** — `az repos pr update --transition-work-items true` moves it to the Done-mapped state diff --git a/.pair/knowledge/guidelines/collaboration/project-management-tool/README.md b/.pair/knowledge/guidelines/collaboration/project-management-tool/README.md index 5203ace9..e2551f86 100644 --- a/.pair/knowledge/guidelines/collaboration/project-management-tool/README.md +++ b/.pair/knowledge/guidelines/collaboration/project-management-tool/README.md @@ -288,6 +288,6 @@ Each guide provides: --- -**Skill**: Use `/assess-pm` to evaluate and adopt a PM tool from these guidelines via the resolution cascade. Use `/setup-pm` for tool configuration. +**Skill**: Use `/pair-capability-assess-pm` to evaluate and adopt a PM tool from these guidelines via the resolution cascade. Use `/pair-capability-setup-pm` for tool configuration. _This framework provides comprehensive guidance for selecting and implementing project management tools that integrate seamlessly with development workflows and team collaboration patterns._ diff --git a/.pair/knowledge/guidelines/collaboration/project-management-tool/azure-devops-implementation.md b/.pair/knowledge/guidelines/collaboration/project-management-tool/azure-devops-implementation.md index 11834e71..bc057430 100644 --- a/.pair/knowledge/guidelines/collaboration/project-management-tool/azure-devops-implementation.md +++ b/.pair/knowledge/guidelines/collaboration/project-management-tool/azure-devops-implementation.md @@ -208,7 +208,7 @@ az repos pr create \ ### Review Actions -Azure Repos uses reviewer votes. The `/review` skill uses these through a tool-agnostic interface — this section documents the Azure-specific implementation. +Azure Repos uses reviewer votes. The `/pair-process-review` skill uses these through a tool-agnostic interface — this section documents the Azure-specific implementation. | Action | Vote Value | When to Use | | --------------- | ---------- | ----------------------------------------------------- | diff --git a/.pair/knowledge/guidelines/collaboration/project-management-tool/canonical-states.md b/.pair/knowledge/guidelines/collaboration/project-management-tool/canonical-states.md index 5b6fc399..6b2f15a9 100644 --- a/.pair/knowledge/guidelines/collaboration/project-management-tool/canonical-states.md +++ b/.pair/knowledge/guidelines/collaboration/project-management-tool/canonical-states.md @@ -14,17 +14,17 @@ Draft → Ready → In Progress → Review → Done | Macrostate | Meaning | Produced by | | --------------- | -------------------------------------------------------------------- | ------------------------------------- | -| **Draft** | Item exists but is not yet refined — missing AC, technical analysis, or estimate | `/plan-stories`, `/plan-epics`, `/plan-initiatives` (creation) | -| **Ready** | Item meets the Definition of Ready — refined, estimated, eligible for sprint/WIP | `/refine-story` (Draft → Ready) | -| **In Progress** | Item is actively being implemented | `/implement` | -| **Review** | Implementation is complete, awaiting code review / PR approval | `/implement` (PR opened) | -| **Done** | Delivered and accepted — PR merged, issue closed | `/review` (merge step) | +| **Draft** | Item exists but is not yet refined — missing AC, technical analysis, or estimate | `/pair-process-plan-stories`, `/pair-process-plan-epics`, `/pair-process-plan-initiatives` (creation) | +| **Ready** | Item meets the Definition of Ready — refined, estimated, eligible for sprint/WIP | `/pair-process-refine-story` (Draft → Ready) | +| **In Progress** | Item is actively being implemented | `/pair-process-implement` | +| **Review** | Implementation is complete, awaiting code review / PR approval | `/pair-process-implement` (PR opened) | +| **Done** | Delivered and accepted — PR merged, issue closed | `/pair-process-review` (merge step) | ### Phase Semantics - **Backlog** = `Draft` \| `Ready` — both refined and unrefined items live in the backlog. - **Sprint / WIP** = `Ready` only — nothing enters a sprint or WIP column while still `Draft`. -- **Refinement produces `Ready`** — `/refine-story` is the single Draft→Ready transition; no separate "make-ready" skill exists. +- **Refinement produces `Ready`** — `/pair-process-refine-story` is the single Draft→Ready transition; no separate "make-ready" skill exists. - **Planning may promote `Draft` → `Ready` directly** — trivial items can skip full refinement when planning already satisfies the Definition of Ready. ## State-Mapping Schema @@ -50,7 +50,7 @@ An optional `## State Mapping` section in `way-of-working.md`, containing a two- ## Resolution Rules -These are the rules every skill follows when it needs to read or write item state — see `/write-issue` and `/next` for the concrete adoption (Integration with Skills, below). +These are the rules every skill follows when it needs to read or write item state — see `/pair-capability-write-issue` and `/pair-next` for the concrete adoption (Integration with Skills, below). ### Reading state (board-state → macrostate) @@ -62,7 +62,7 @@ These are the rules every skill follows when it needs to read or write item stat ### Writing state (macrostate → board-state) -1. Determine the target macrostate for the transition (e.g., `/refine-story` targets `Ready`). +1. Determine the target macrostate for the transition (e.g., `/pair-process-refine-story` targets `Ready`). 2. Find all board states mapped to that macrostate, in the order listed in the `## State Mapping` section. 3. Write to the **first mapped board state** (first-mapped-wins). To target a different board state, list it first in the map — map order is the override mechanism, not a separate config. 4. If the section is absent, write the macrostate name itself (canonical convention). @@ -183,13 +183,13 @@ The `Removed` state is deliberately **unmapped** — removed items are out of sc | Skill | Interaction | | ---------------- | ----------------------------------------------------------------------------------------------------- | -| `/write-issue` | Resolves a target macrostate (`$status`) to a board state before writing the board field (Writing rule above) | -| `/next` | Resolves each item's board state to a macrostate before evaluating its cascade conditions (Reading rule above); applies the Readiness Fallback for Draft vs. Ready | -| `/refine-story` | Produces `Ready` — writes it through `/write-issue` | -| `/implement` | Produces `In Progress` / `Review` — writes through `/write-issue` | -| `/review` | Produces `Done` on merge — writes through `/write-issue` | +| `/pair-capability-write-issue` | Resolves a target macrostate (`$status`) to a board state before writing the board field (Writing rule above) | +| `/pair-next` | Resolves each item's board state to a macrostate before evaluating its cascade conditions (Reading rule above); applies the Readiness Fallback for Draft vs. Ready | +| `/pair-process-refine-story` | Produces `Ready` — writes it through `/pair-capability-write-issue` | +| `/pair-process-implement` | Produces `In Progress` / `Review` — writes through `/pair-capability-write-issue` | +| `/pair-process-review` | Produces `Done` on merge — writes through `/pair-capability-write-issue` | -Rollout across the rest of the skill catalog happens organically in the stories that touch each skill — `/write-issue` and `/next` are the first adopters. +Rollout across the rest of the skill catalog happens organically in the stories that touch each skill — `/pair-capability-write-issue` and `/pair-next` are the first adopters. ## Related diff --git a/.pair/knowledge/guidelines/collaboration/project-tracking/azure-devops-tracking.md b/.pair/knowledge/guidelines/collaboration/project-tracking/azure-devops-tracking.md index 49429b92..47987c30 100644 --- a/.pair/knowledge/guidelines/collaboration/project-tracking/azure-devops-tracking.md +++ b/.pair/knowledge/guidelines/collaboration/project-tracking/azure-devops-tracking.md @@ -149,7 +149,7 @@ Cycle Time and Lead Time widgets are available on dashboards (backed by Analytic ### Data Quality - One state transition path: skills write states only through the state mapping -- Effort filled at refinement (`/refine-story`), not retroactively +- Effort filled at refinement (`/pair-process-refine-story`), not retroactively - Iteration assigned at sprint planning, kept current on carry-over ### Workflow Integration diff --git a/.pair/knowledge/guidelines/collaboration/templates/README.md b/.pair/knowledge/guidelines/collaboration/templates/README.md index 9e0d8185..ccf84768 100644 --- a/.pair/knowledge/guidelines/collaboration/templates/README.md +++ b/.pair/knowledge/guidelines/collaboration/templates/README.md @@ -226,6 +226,7 @@ Templates work with popular project management tools: | `manual-test-report-template.md` | Quality | Test execution report | Low | | `context-map-template.md` | Domain | Ubiquitous language, entities, common rules | Medium | | `subdomain-context-template.md` | Domain | Lazy per-subdomain context split | Low | +| `checkpoint-template.md` | Operational | Resumable progress state | Low | ### Template Relationships @@ -284,6 +285,7 @@ The **TemplateOrchestrator** provides comprehensive template management through - Feature specification templates with comprehensive requirement documentation - Bug report templates with detailed reproduction steps and environment information - Technical decision templates with evaluation criteria and rationale documentation +- **[Checkpoint Template](checkpoint-template.md)** - Resumable progress state (story, branch, tasks done, decisions, remaining todos) for context-reset handoffs ### 2. **Project Management Templates** diff --git a/.pair/knowledge/guidelines/collaboration/templates/checkpoint-template.md b/.pair/knowledge/guidelines/collaboration/templates/checkpoint-template.md new file mode 100644 index 00000000..d1a2ceb3 --- /dev/null +++ b/.pair/knowledge/guidelines/collaboration/templates/checkpoint-template.md @@ -0,0 +1,51 @@ +# Checkpoint: #[story-id] — [Story Title] + +**Last updated:** [YYYY-MM-DD HH:MM] +**Written by:** [session/agent identifier — e.g. "implement session", "subagent: publish-pr"] + +> Read top to bottom before doing anything else. Written so a session with **zero prior context** can resume exactly where the previous one stopped. + +## 1. Story + +**ID:** #[story-id] +**Title:** [Story title] +**Epic:** #[epic-id] — [epic title] (if applicable) +**Goal:** [one or two sentences — what this story delivers and why] +**Source:** [link to the issue / PM tool item] + +## 2. Branch + +**Branch:** `feature/#[story-id]-[short-description]` +**Commit strategy:** [commit-per-task | commit-per-story] +**Commits so far:** [N] — [most recent commit subject, or "none yet"] + +## 3. Tasks Done + +- [x] T-1 — [title] — [commit ref or brief evidence] +- [x] T-2 — [title] + +[If none yet: "None yet."] + +## 4. Key Decisions + +- [What was decided + why. Link the ADR/ADL if one was recorded: `.pair/adoption/tech/adr/...` or `.pair/adoption/decision-log/...`] + +[If none yet: "None yet."] + +## 5. Remaining Todos + +- [ ] T-3 — [title] — [known blockers, open questions, or notes] +- [ ] T-4 — [title] + +**Next immediate action:** [the single next concrete step the resuming session should take] + +--- + +## Template Notes + +Everything below this line is authoring guidance — omit it from rendered checkpoints. + +- **File location:** `.pair/working/checkpoints/.md` — one file per story, updated in place, never duplicated. +- **Source of truth:** the file. If an issue-body mirror exists, it must be explicitly marked as a copy and must not diverge in meaning from the file. +- **Resume parsing:** a resuming session reads sections 1–5 top to bottom. It should not need the original conversation to continue safely. +- **Unknown state:** if a value cannot be reconstructed confidently, write `[unknown — needs confirmation]` rather than guessing. diff --git a/.pair/knowledge/guidelines/collaboration/working-area.md b/.pair/knowledge/guidelines/collaboration/working-area.md new file mode 100644 index 00000000..3b5a3ae2 --- /dev/null +++ b/.pair/knowledge/guidelines/collaboration/working-area.md @@ -0,0 +1,76 @@ +# Working Area Convention + +## Overview + +`.pair/working/` is the **operational area**: where AI capabilities persist state produced *during* execution — checkpoints and reports. It is operational data, not knowledge, so it stays outside every KB asset registry by design (D14). `pair install` and `pair update` never create, modify, or delete anything under it. + +This is distinct from the other two `.pair/` areas: + +| Area | Contents | Owner | Touched by `pair update`? | +| --- | --- | --- | --- | +| `.pair/knowledge/` | Upstream guidelines and how-to | pair KB | Yes (mirror) | +| `.pair/adoption/` | Project decisions | Project | Add-only (new files) | +| `.pair/working/` | Checkpoints, reports | Skills, at runtime | Never | + +## Structure + +```text +.pair/working/ +├── checkpoints/ # Resumable state for multi-task flows (e.g. /implement) +└── reports/ + └── / # Generated reports, grouped by category (e.g. quality, monitoring) +``` + +Both subdirectories are created **on demand** by the skill that first needs them — a checkpoint capability creates `checkpoints/` the first time it writes state, a reporting capability creates `reports//` the first time it writes a report. `pair install` does not scaffold this structure. + +## How It Is Protected (D14) + +`pair install`/`pair update` only ever touch paths **inside a configured registry's target** — the mirror cleanup deletes only entries within a registry `target` that are absent from that registry's `source`. A directory that is not a registry `source` or `target` is therefore never created, modified, or deleted. The working area is protected by exactly this: **it is never a registry target.** + +There is no runtime "hard-exclusion" carve-out. The only way the working area could be touched is a *misconfigured* registry whose `target` equals or is an ancestor/descendant of it (e.g. a registry mirroring the whole `.pair/` root). That is prevented at config-validation time — `pair validate-config`, and the same check run inside `pair install`/`pair update`, **reject** any such config before a single file is copied (see [Validation](#validation)). Fail-closed: a bad config errors out; it does not silently proceed with a carve-out. + +The working area is the first member of a small set of **reserved project-side paths** that no registry target may overlap. The set is extensible — future meta/config files (e.g. the KB version marker) join the same guard. + +## Overriding the Path + +The default path is `.pair/working`. A project can override it by declaring `working_path` at the top level of `pair.config.json` (sibling to `asset_registries`, not inside it): + +```json +{ + "working_path": ".pair/scratch", + "asset_registries": { "...": "..." } +} +``` + +When overridden: + +- The override — not the default — is the reserved path that no registry target may overlap. +- Skills reading or writing checkpoints/reports must resolve the same override (read `working_path` from `pair.config.json`; fall back to `.pair/working` when absent) so the two sides never disagree on where the working area lives. +- The override must be **project-relative**. An absolute `working_path` is rejected by `pair validate-config` — it cannot be compared against the project-relative registry targets, so it would defeat the overlap guard. + +## Validation + +`pair validate-config` (and the identical check inside `pair install`/`pair update`) **rejects** the config in these cases: + +- **A registry overlaps a reserved path**: a registry's `target` path equals, contains, or is contained by the (default or overridden) working path. This catches both directions — a registry accidentally covering the working area, and a working-area override that lands inside a registry-managed directory. +- **A non-project-relative `working_path`**: an absolute path (or one escaping the project root) is rejected. + +Because the working area is simply not a registry target, a valid config guarantees `pair install`/`pair update` never touch it — no runtime carve-out is needed. + +## Convention for External KBs + +This guarantee is not specific to the `foomakers/pair` KB — any KB dataset consumed by `pair-cli` inherits it, because the reserved-path validation is enforced by `pair-cli` itself (`validateAllRegistries` → `detectReservedPathOverlap`), not by anything the dataset declares. A custom or organization-specific KB does not need to do anything special: it only needs to avoid declaring a registry whose `target` overlaps the working area, which `pair validate-config` rejects. + +## Not Ambient Context + +The working area is operational handoff state, not shared knowledge, so it is **never loaded as ambient context**. This is an existing project-wide rule (see the "`.pair/working/` is not ambient context" Quick Rule in `AGENTS.md`, recorded in ADL `2026-07-11-working-artifacts-task-scoped.md`): content under `.pair/working/` is read or written only by the specific capability that owns a given file (e.g. the checkpoint capability owns `checkpoints/.md`), and only on explicit invocation (write/resume/save/load) — never swept in by a broad glob or a "read everything under `.pair/`" exploration step. Skill authors: do not add `.pair/working/` to a generic context-gathering step. + +## Integration with Skills + +| Skill / Area | Interaction | +| --- | --- | +| Checkpoint capability (e.g. `pair-capability-checkpoint`) | Writes/reads `.pair/working/checkpoints/` | +| Reporting capabilities (e.g. quality, monitoring) | Write `.pair/working/reports//` | +| `pair install` | Never scaffolds or touches the working area | +| `pair update` | Never modifies or deletes anything under the working area | +| `pair validate-config` | Errors on any registry/working-area overlap | diff --git a/.pair/knowledge/guidelines/quality-assurance/README.md b/.pair/knowledge/guidelines/quality-assurance/README.md index 65c89a13..ea9d5664 100644 --- a/.pair/knowledge/guidelines/quality-assurance/README.md +++ b/.pair/knowledge/guidelines/quality-assurance/README.md @@ -24,10 +24,10 @@ This comprehensive quality assurance framework ensures systematic quality manage ### **Core Quality Framework** +- **quality-model.md** - Single source of default quality rules: 3-layer principle, 3 pillars (Cost/Security/Delivery), classification dimensions and tier resolution, per-tier review/gate requirements, tag projection, `tech/risk-matrix.md` adoption delta schema, nested taxonomy - **automated-verification.md** - Comprehensive automated verification system for tool-based validation and quality gate enforcement - **manual-verification.md** - Manual verification processes and human-driven quality assessment methodologies - **manual-testing.md** - Guidelines for designing, organizing, and executing manual test suites for post-release validation -- **quality-model.md** - Single source of default quality rules: 3-layer principle, 3 pillars (Cost/Security/Delivery), classification dimensions and tier resolution, per-tier review/gate requirements, tag projection, `tech/risk-matrix.md` adoption delta schema, nested taxonomy ### **Quality Standards** (`quality-standards/`) diff --git a/.pair/knowledge/guidelines/quality-assurance/security/secret-scanning.md b/.pair/knowledge/guidelines/quality-assurance/security/secret-scanning.md index b59a6db7..7c3cc06d 100644 --- a/.pair/knowledge/guidelines/quality-assurance/security/secret-scanning.md +++ b/.pair/knowledge/guidelines/quality-assurance/security/secret-scanning.md @@ -1,8 +1,8 @@ # Secret Scanning — Deterministic CI Layer -The one security control that is **not** a skill (D24). Every other security judgment call goes through `/assess-security`; a committed secret goes RED mechanically, with no LLM involved (R6.5) — an `Automation` layer in [quality-model.md](../quality-model.md)'s three-layer principle (§1) sense: it reads a scan result deterministically, zero judgment. +The one security control that is **not** a skill (D24). Every other security judgment call goes through `/pair-capability-assess-security`; a committed secret goes RED mechanically, with no LLM involved (R6.5) — an `Automation` layer in [quality-model.md](../quality-model.md)'s three-layer principle (§1) sense: it reads a scan result deterministically, zero judgment. -`/setup-gates` provisions this layer (CI gates, §4 of [quality-model.md](../quality-model.md)); it applies at **every** tier, unlike tier-scoped gates (unit tests from 🟡, integration/E2E from 🔴 only) — a secret is a secret regardless of the change's risk tier. +`/pair-capability-setup-gates` provisions this layer (CI gates, §4 of [quality-model.md](../quality-model.md)); it applies at **every** tier, unlike tier-scoped gates (unit tests from 🟡, integration/E2E from 🔴 only) — a secret is a secret regardless of the change's risk tier. ## Scanner Default @@ -10,7 +10,7 @@ The one security control that is **not** a skill (D24). Every other security jud ## CI Job Template (GitHub Actions) -`/setup-gates` writes a job equivalent to this into the project's CI pipeline (Step 4 of that skill) — required, not tier-scoped. Two forms below — same fail-closed guarantee (a committed secret fails the build), differing only in scan scope (the Action scans the event's commit range; the binary form here scans full history); pick per the project's GitHub ownership: +`/pair-capability-setup-gates` writes a job equivalent to this into the project's CI pipeline (Step 4 of that skill) — required, not tier-scoped. Two forms below — same fail-closed guarantee (a committed secret fails the build), differing only in scan scope (the Action scans the event's commit range; the binary form here scans full history); pick per the project's GitHub ownership: **Option A — `gitleaks-action@v2`** (matches [github-actions-implementation.md](../../infrastructure/cicd-strategy/github-actions-implementation.md)). **Requires a `GITLEAKS_LICENSE` for org-owned repos** — the Action gates on it and fails before scanning if absent, regardless of public/private. A free OSS key is available at gitleaks.io; store it as a repo/org secret. Individual-owned repos don't need it. @@ -52,11 +52,11 @@ Either form exits non-zero the moment it finds a leak, which fails the job — t ### Fail-Closed Requirement -**Scanner unavailable in CI is a gate failure, not a skip.** If the action can't run (missing config, registry unreachable, runner error) the job must still fail — never silently pass. `/setup-gates` writes the job as `required` in the same way it marks every other blocking gate; a project must not disable failure on this job (e.g. via the `continue-on-error` step option). +**Scanner unavailable in CI is a gate failure, not a skip.** If the action can't run (missing config, registry unreachable, runner error) the job must still fail — never silently pass. `/pair-capability-setup-gates` writes the job as `required` in the same way it marks every other blocking gate; a project must not disable failure on this job (e.g. via the `continue-on-error` step option). ## Allowlist Mechanism (Adoption-Controlled) -False positives are handled with a `.gitleaks.toml` at the project root — never by disabling the scan. `/setup-gates` provisions a starting file; a project extends it as false positives are confirmed: +False positives are handled with a `.gitleaks.toml` at the project root — never by disabling the scan. `/pair-capability-setup-gates` provisions a starting file; a project extends it as false positives are confirmed: ```toml title = "project secret-scanning allowlist" @@ -78,7 +78,7 @@ Adding an entry is an adoption change (the file lives at the project root, revie ## Swapping the Scanner -A project can adopt a different scanner (trufflehog, ggshield, a vendor SAST suite's secret-detection module, ...) by recording the override in `tech/way-of-working.md`'s Custom Gate Registry — `/setup-gates` reads that override before provisioning gitleaks (same resolution order as every other gate: **Argument > Adoption > KB default**). The fail-closed and required-check requirements above apply to whichever scanner is adopted, not to gitleaks specifically. +A project can adopt a different scanner (trufflehog, ggshield, a vendor SAST suite's secret-detection module, ...) by recording the override in `tech/way-of-working.md`'s Custom Gate Registry — `/pair-capability-setup-gates` reads that override before provisioning gitleaks (same resolution order as every other gate: **Argument > Adoption > KB default**). The fail-closed and required-check requirements above apply to whichever scanner is adopted, not to gitleaks specifically. ## Verification (reproducible) @@ -105,12 +105,12 @@ deliberate choice recorded in ADR-015's Trade-offs, not a gap: automating it wou external `gitleaks` binary from inside this repo's own test suite, a dependency this repo does not otherwise carry. -## Relationship to `/assess-security` +## Relationship to `/pair-capability-assess-security` -`/assess-security` never scans for secrets (its own Notes section states this explicitly) — it evaluates authn/authz, injection, and the rest of the OWASP surface via the KB/adoption rule cascade, a judgment call. Secret detection stays fully deterministic and fully separate, so the "no LLM involved" guarantee in R6.5 is never diluted by composing it with a skill that could, in principle, miss a pattern a human didn't anticipate. +`/pair-capability-assess-security` never scans for secrets (its own Notes section states this explicitly) — it evaluates authn/authz, injection, and the rest of the OWASP surface via the KB/adoption rule cascade, a judgment call. Secret detection stays fully deterministic and fully separate, so the "no LLM involved" guarantee in R6.5 is never diluted by composing it with a skill that could, in principle, miss a pattern a human didn't anticipate. ## Cross-References - [quality-model.md](../quality-model.md) §2 (Security pillar), §4 (per-tier gate requirements) — this layer sits in the "Gate checks" row at every tier. -- `/setup-gates` — provisions the job + allowlist file. -- `/assess-security` — the judgment-call counterpart; never overlaps with this layer. +- `/pair-capability-setup-gates` — provisions the job + allowlist file. +- `/pair-capability-assess-security` — the judgment-call counterpart; never overlaps with this layer. diff --git a/.pair/knowledge/how-to/02-how-to-complete-bootstrap-checklist.md b/.pair/knowledge/how-to/02-how-to-complete-bootstrap-checklist.md index f3b2f7dd..22115f8f 100644 --- a/.pair/knowledge/how-to/02-how-to-complete-bootstrap-checklist.md +++ b/.pair/knowledge/how-to/02-how-to-complete-bootstrap-checklist.md @@ -6,7 +6,7 @@ Transform PRD requirements into comprehensive technical standards through system **Role**: Senior Technical Architect (Bootstrap and Standards Creation) **Process**: AI proposes and drafts, Developer validates and approves -**Skills**: `/pair-process-bootstrap` (operational details), `/pair-capability-setup-pm` (PM tool configuration) +**Skills**: `/pair-process-bootstrap` (operational details), `/pair-capability-setup-pm` (PM tool configuration), `/pair-capability-map-subdomains` + `/pair-capability-map-contexts` (full-catalog domain modeling — capabilities, composed by `/pair-process-bootstrap` with `$scope: all`, the only caller allowed that scope) ## Session State Management @@ -73,6 +73,18 @@ For each document: See `/pair-process-bootstrap` Phase 3 for generation procedures and quality gate setup. +### Phase 2.5: Domain Modeling (optional, full-catalog) + +Runs once architecture.md and tech-stack.md are adopted — both are prerequisites for bounded context placement. + +1. Compose `/pair-capability-map-subdomains` with `$scope: all` — classifies business capabilities from the PRD into Core/Supporting/Generic subdomains with a Volatility rating. Falls back to "system areas" if no initiatives exist yet. +2. Compose `/pair-capability-map-contexts` with `$scope: all` — derives bounded context boundaries from the subdomain catalog, architecture, and tech stack; assesses per-relationship integration strength/distance/volatility. +3. Both capabilities degrade gracefully if not installed — domain modeling never blocks bootstrap completion. + +This is the **only** point where either capability runs a full-catalog `$scope: all` mapping — every other caller (`/pair-process-plan-initiatives`, `/pair-process-plan-epics`, `/pair-process-plan-tasks`, `/pair-process-refine-story`) invokes them scoped to what it just touched. See [Callers Matrix](../skills-guide.md#callers-matrix-scoped-capabilities). + +See `/pair-process-bootstrap` Phase 3.5 for operational details. + ### Phase 3: Finalization 1. Verify consistency across all standards documents @@ -89,6 +101,7 @@ See `/pair-process-bootstrap` Phase 4 for verification and PM configuration flow - [ ] Documents stored in [`.pair/adoption/tech/`](../../adoption/tech/) - [ ] Internal consistency verified across all documents - [ ] Quality gates configured and executable +- [ ] Domain model bootstrapped via `/pair-capability-map-subdomains` + `/pair-capability-map-contexts` (full-catalog), or explicitly skipped - [ ] PM tool configured via `/pair-capability-setup-pm` ## References @@ -99,6 +112,4 @@ See `/pair-process-bootstrap` Phase 4 for verification and PM configuration flow ## Next Steps -- **Create and Prioritize Initiatives** → [03-how-to-create-and-prioritize-initiatives.md](03-how-to-create-and-prioritize-initiatives.md) -- **Define Project Subdomains** → [04-how-to-define-subdomains.md](04-how-to-define-subdomains.md) -- **Define Bounded Contexts** → [05-how-to-define-bounded-contexts.md](05-how-to-define-bounded-contexts.md) +- **Create and Prioritize Initiatives** → [03-how-to-create-and-prioritize-initiatives.md](03-how-to-create-and-prioritize-initiatives.md) — subdomain placement happens inline via `/pair-capability-map-subdomains`, scoped to each initiative diff --git a/.pair/knowledge/how-to/03-how-to-create-and-prioritize-initiatives.md b/.pair/knowledge/how-to/03-how-to-create-and-prioritize-initiatives.md index 34db2dac..21b32121 100644 --- a/.pair/knowledge/how-to/03-how-to-create-and-prioritize-initiatives.md +++ b/.pair/knowledge/how-to/03-how-to-create-and-prioritize-initiatives.md @@ -16,6 +16,7 @@ This how-to orchestrates the `/pair-process-plan-initiatives` skill. | ------------------- | -------------------------------------------------------------------------------------------------------------- | | `/pair-process-plan-initiatives` | Executes the full process: PRD analysis, P0/P1/P2 prioritization, initiative creation, dependency mapping. | | `/pair-capability-write-issue` | Composed by `/pair-process-plan-initiatives` — creates or updates initiative issues in the PM tool. | +| `/pair-capability-map-subdomains` | Composed by `/pair-process-plan-initiatives` — scoped domain placement for the new initiative's capability area. | > **If skills are not installed**, follow the manual workflow below. @@ -27,6 +28,7 @@ This how-to orchestrates the `/pair-process-plan-initiatives` skill. - PRD analysis (business objectives, user value, constraints) - Prioritization (P0 Must-Have → P1 Should-Have → P2 Could-Have) - Initiative creation via `/pair-capability-write-issue` with `$type: initiative` + - Domain placement via `/pair-capability-map-subdomains`, scoped to the new initiative's capability area (not full-catalog — see [Callers Matrix](../skills-guide.md#callers-matrix-scoped-capabilities)) - Dependency mapping and roadmap planning 3. **Developer validates** each initiative when prompted by the skill. 4. **Repeat** for additional initiatives or re-invoke to create missing ones. @@ -83,4 +85,5 @@ This how-to orchestrates the `/pair-process-plan-initiatives` skill. - PRD: [`.pair/adoption/product/PRD.md`](../../adoption/product/PRD.md) - PM Tool: [PM Tool Guidelines](../guidelines/collaboration/project-management-tool/README.md) - Bootstrap: [Bootstrap Checklist](02-how-to-complete-bootstrap-checklist.md) -- Next: [Define Subdomains](04-how-to-define-subdomains.md) or [Breakdown Epics](06-how-to-breakdown-epics.md) +- Domain capability: `/pair-capability-map-subdomains` — invoked inline by `/pair-process-plan-initiatives`, scoped to each initiative +- Next: [Breakdown Epics](06-how-to-breakdown-epics.md) diff --git a/.pair/knowledge/how-to/08-how-to-refine-a-user-story.md b/.pair/knowledge/how-to/08-how-to-refine-a-user-story.md index 1aa3de3b..d4e4aeb9 100644 --- a/.pair/knowledge/how-to/08-how-to-refine-a-user-story.md +++ b/.pair/knowledge/how-to/08-how-to-refine-a-user-story.md @@ -15,6 +15,7 @@ This how-to orchestrates the `/pair-process-refine-story` skill. | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `/pair-process-refine-story` | Executes the full refinement process: selection, requirements (Given-When-Then), technical analysis, sprint readiness, documentation. Section-level idempotent. | | `/pair-capability-write-issue` | Composed by `/pair-process-refine-story` — creates or updates the story issue in the PM tool. | +| `/pair-capability-map-subdomains` + `/pair-capability-map-contexts` | Scoped domain/context placement for the story — planned wiring, tracked by [#242](https://github.com/foomakers/pair/issues/242). Not yet composed by `/pair-process-refine-story`. | > **If skills are not installed**, follow the manual workflow below. diff --git a/.pair/knowledge/how-to/09-how-to-create-tasks.md b/.pair/knowledge/how-to/09-how-to-create-tasks.md index 4bab42a9..5c4f8335 100644 --- a/.pair/knowledge/how-to/09-how-to-create-tasks.md +++ b/.pair/knowledge/how-to/09-how-to-create-tasks.md @@ -15,6 +15,7 @@ This how-to orchestrates the `/pair-process-plan-tasks` skill. | -------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `/pair-process-plan-tasks` | Executes the full task breakdown: story analysis, task identification, task definition, documentation. Task-level idempotent. | | `/pair-capability-write-issue` | Composed by `/pair-process-plan-tasks` — creates task issues and updates the story body in the PM tool. | +| `/pair-capability-map-contexts` | Composed by `/pair-process-plan-tasks` — scoped context mapping for the story's touched bounded contexts. | > **If skills are not installed**, follow the manual workflow below. @@ -25,6 +26,7 @@ This how-to orchestrates the `/pair-process-plan-tasks` skill. - Story selection (highest-priority Refined, or specified `$story`) - Existing task detection (task-level idempotency — creates only missing tasks) - Technical context analysis (architecture, tech-stack, bounded contexts) + - Context mapping via `/pair-capability-map-contexts`, scoped to the story's touched bounded contexts — not full-catalog (see [Callers Matrix](../skills-guide.md#callers-matrix-scoped-capabilities)) - Task identification (AC-to-task mapping, bounded context grouping) - Task definition (full task template per task) - Task Breakdown appending (condensed TA + checklist + Dependency Graph + AC Coverage + detailed tasks) diff --git a/.pair/knowledge/skill-conventions/output-shapes.md b/.pair/knowledge/skill-conventions/output-shapes.md index 1650c18a..6fb8a458 100644 --- a/.pair/knowledge/skill-conventions/output-shapes.md +++ b/.pair/knowledge/skill-conventions/output-shapes.md @@ -13,7 +13,7 @@ ASSESSMENT COMPLETE (output-only — no files written): ├── Decision: [the recommended choice, in domain-specific terms] ├── Proposal: [content rendered for ] ├── Target: () -├── Persist: [caller composes /pair-capability-record-decision(content, target) → ADR|ADL] +├── Persist: [caller composes /record-decision(content, target) → ADR|ADL] └── Status: [Proposal ready | Confirmed existing] ``` diff --git a/.pair/knowledge/skill-conventions/record-decision-contract.md b/.pair/knowledge/skill-conventions/record-decision-contract.md index c0fe2584..96d0f464 100644 --- a/.pair/knowledge/skill-conventions/record-decision-contract.md +++ b/.pair/knowledge/skill-conventions/record-decision-contract.md @@ -17,27 +17,27 @@ An `assess-*` skill never writes. It returns: The caller (the orchestrator or the human/agent invoking the assess-* skill directly) persists by composing: ```text -/pair-capability-record-decision(content, target, decision-metadata) +/record-decision(content, target, decision-metadata) ``` which writes `content` into its owned section of `target` (a heading-scoped merge — it preserves the rest of the file) and records the ADR/ADL/DDR per its own classification rule. ## Standard "Verify" line (end of Path A/B in the resolution cascade, and end of the assessment step) -> **Verify**: Proposal emitted. Persistence is performed by the caller via `/pair-capability-record-decision(content, target, decision-metadata)`, never by this skill. +> **Verify**: Proposal emitted. Persistence is performed by the caller via `/record-decision(content, target, decision-metadata)`, never by this skill. ## Standard Composition Interface shape ```markdown -When composed by `/pair-process-bootstrap`: +When composed by `/bootstrap`: -- **Input**: `/pair-process-bootstrap` invokes during Phase 2[, with ]. +- **Input**: `/bootstrap` invokes during Phase 2[, with ]. - **Output**: Returns `{ content, target, decision-metadata }` plus the report. Writes nothing. -- **Persistence**: `/pair-process-bootstrap` accepts the proposal and composes `/pair-capability-record-decision(content, target, decision-metadata)` to write and record the . +- **Persistence**: `/bootstrap` accepts the proposal and composes `/record-decision(content, target, decision-metadata)` to write and record the . When invoked **independently**: -- Full interactive flow. The skill returns the proposal; the human (or agent) persists it by composing `/pair-capability-record-decision`, then commits. +- Full interactive flow. The skill returns the proposal; the human (or agent) persists it by composing `/record-decision`, then commits. ``` Additional "When composed by ..." blocks (e.g. `/pair-process-implement`, `/pair-process-review` for `/pair-capability-assess-stack`'s implementation/review modes) follow the same Input/Output/Persistence shape — only the caller name, trigger, and target/ADR-vs-ADL vary. diff --git a/.pair/knowledge/skills-guide.md b/.pair/knowledge/skills-guide.md index 99229963..aa8919c2 100644 --- a/.pair/knowledge/skills-guide.md +++ b/.pair/knowledge/skills-guide.md @@ -197,7 +197,7 @@ Every row above except the two `(planned — #242)`/`(planned — #230)` entries **map-subdomains, map-contexts: process → capability (D24, #246)** - Old path: `.skills/process/map-subdomains/`, `.skills/process/map-contexts/` — new path: `.skills/capability/map-subdomains/`, `.skills/capability/map-contexts/`. -- Installed command names change accordingly: `/pair-process-map-subdomains` → `/pair-capability-map-subdomains`; `/pair-process-map-contexts` → `/pair-capability-map-contexts`. Unprefixed dataset command names (`/map-subdomains`, `/map-contexts`) are unchanged. +- Installed command names change accordingly: `/pair-process-map-subdomains` → `/pair-capability-map-subdomains`; `/pair-process-map-contexts` → `/pair-capability-map-contexts`. Unprefixed dataset command names (`map-subdomains`, `map-contexts`) are unchanged. - Invocation contract changed: `$scope` is now **required** and means "items/areas the caller touched" (not `all`/`single`). Full-catalog `$scope: all` is now bootstrap-only. - New behavior: graceful "system areas" fallback when no DDD artifacts exist; `Volatility` field on subdomains; per-relationship strength/distance/volatility assessment with an approval gate on unbalanced+volatile relationships (see `subdomain-template.md`, `bounded-context-template.md`). - No standalone process step or how-to remains for domain mapping — how-to guides 04 (define-subdomains) and 05 (define-bounded-contexts) were removed; each caller's own how-to (02, 03, 06, 08, 09) now references the capability inline at the point it's invoked, scoped per the [Callers Matrix](#callers-matrix-scoped-capabilities) above. The capability's own `SKILL.md` ([map-subdomains](../../.claude/skills/pair-capability-map-subdomains/SKILL.md), [map-contexts](../../.claude/skills/pair-capability-map-contexts/SKILL.md)) is the canonical reference for its algorithm, fallback behavior, and templates. @@ -206,7 +206,7 @@ Every row above except the two `(planned — #242)`/`(planned — #230)` entries - Rationale: `assess-*` conflated two operations. The corpus now uses three distinct verbs — **`verify-*`** (conformance pass/fail), **`assess-*`** (evaluate options + PROPOSE an adoption choice — the 8 decision skills), **`analyze-*`** (analyze + REPORT only, never blocks, proposes no adoption decision). The two report skills were misfiled under `assess-*` and are renamed to the `analyze-*` verb. Recorded as ADL `2026-07-13-skill-naming-verb-taxonomy.md` in the project decision log. - Old path: `.skills/capability/assess-debt/`, `.skills/capability/assess-code-quality/` — new path: `.skills/capability/analyze-debt/`, `.skills/capability/analyze-code-quality/`. -- Installed command names change accordingly: `/pair-capability-assess-debt` → `/pair-capability-analyze-debt`; `/pair-capability-assess-code-quality` → `/pair-capability-analyze-code-quality`. Unprefixed dataset command names change too: `/assess-debt` → `/analyze-debt`, `/assess-code-quality` → `/analyze-code-quality`. +- Installed command names change accordingly: `/pair-capability-assess-debt` → `/pair-capability-analyze-debt`; `/pair-capability-assess-code-quality` → `/pair-capability-analyze-code-quality`. Unprefixed dataset command names change too: `assess-debt` → `analyze-debt`, `assess-code-quality` → `analyze-code-quality`. - No behavior change: both remain output-only report producers (per ADR-009); only the verb/name changed. Skill count stays 35. ## How Skills Relate to How-To Guides diff --git a/.pair/llms.txt b/.pair/llms.txt index 994d2a35..0917fbed 100644 --- a/.pair/llms.txt +++ b/.pair/llms.txt @@ -66,6 +66,7 @@ - [Naming Conventions](.pair/knowledge/guidelines/code-design/code-organization/naming-conventions.md) - [Code Organization](.pair/knowledge/guidelines/code-design/code-organization/README.md) - [Workspace Structure](.pair/knowledge/guidelines/code-design/code-organization/workspace-structure.md) +- [Design Rules](.pair/knowledge/guidelines/code-design/design-principles/design-rules.md) - [Error Handling](.pair/knowledge/guidelines/code-design/design-principles/error-handling.md) - [Functional Programming](.pair/knowledge/guidelines/code-design/design-principles/functional-programming.md) - [Mocking Strategy](.pair/knowledge/guidelines/code-design/design-principles/mocking-strategy.md) @@ -96,13 +97,14 @@ - [Linting Tools](.pair/knowledge/guidelines/code-design/quality-standards/linting-tools.md) - [Prettier Formatting](.pair/knowledge/guidelines/code-design/quality-standards/prettier-formatting.md) - [Quality Standards](.pair/knowledge/guidelines/code-design/quality-standards/README.md) +- [Shared Config Packages](.pair/knowledge/guidelines/code-design/quality-standards/shared-config-packages.md) - [Technical Debt Management](.pair/knowledge/guidelines/code-design/quality-standards/technical-debt.md) - [Code Design](.pair/knowledge/guidelines/code-design/README.md) - [Azure DevOps Automation](.pair/knowledge/guidelines/collaboration/automation/azure-devops-automation.md) - [Filesystem Automation](.pair/knowledge/guidelines/collaboration/automation/filesystem-automation.md) - [GitHub Automation](.pair/knowledge/guidelines/collaboration/automation/github-automation.md) - [Collaboration Automation Framework](.pair/knowledge/guidelines/collaboration/automation/README.md) -- [Decision Records: ADR and ADL](.pair/knowledge/guidelines/collaboration/decision-records.md) +- [Decision Records: ADR, ADL, DDR, and Analysis-Log](.pair/knowledge/guidelines/collaboration/decision-records.md) - [AI-Assisted Estimation](.pair/knowledge/guidelines/collaboration/estimation/ai-assisted-estimation.md) - [Complexity-Based Estimation](.pair/knowledge/guidelines/collaboration/estimation/complexity-based-estimation.md) - [Forecast-Based Estimation](.pair/knowledge/guidelines/collaboration/estimation/forecast-based-estimation.md) @@ -122,6 +124,7 @@ - [Waterfall Methodology](.pair/knowledge/guidelines/collaboration/methodology/waterfall.md) - [Azure DevOps - Complete Implementation Guide](.pair/knowledge/guidelines/collaboration/project-management-tool/azure-devops-implementation.md) - [Canonical States & State Mapping](.pair/knowledge/guidelines/collaboration/project-management-tool/canonical-states.md) +- [Definition of Ready & Definition of Done](.pair/knowledge/guidelines/collaboration/project-management-tool/definition-of-ready-and-done.md) - [Filesystem - Complete Implementation Guide](.pair/knowledge/guidelines/collaboration/project-management-tool/filesystem-implementation.md) - [GitHub Projects - Complete Implementation Guide](.pair/knowledge/guidelines/collaboration/project-management-tool/github-implementation.md) - [Project Management Tool Framework](.pair/knowledge/guidelines/collaboration/project-management-tool/README.md) @@ -139,8 +142,10 @@ - [Standards](.pair/knowledge/guidelines/collaboration/team/standards.md) - [Decision: [Decision Title]](.pair/knowledge/guidelines/collaboration/templates/adl-template.md) - [ADR: [Decision Title]](.pair/knowledge/guidelines/collaboration/templates/adr-template.md) +- [Analysis Log: [Analysis Title]](.pair/knowledge/guidelines/collaboration/templates/analysis-log-template.md) - [[Context Name] Context](.pair/knowledge/guidelines/collaboration/templates/bounded-context-template.md) - [Branch Template](.pair/knowledge/guidelines/collaboration/templates/branch-template.md) +- [Checkpoint: #[story-id] — [Story Title]](.pair/knowledge/guidelines/collaboration/templates/checkpoint-template.md) - [Code Review Template](.pair/knowledge/guidelines/collaboration/templates/code-review-template.md) - [Commit Message Template](.pair/knowledge/guidelines/collaboration/templates/commit-template.md) - [Context Map](.pair/knowledge/guidelines/collaboration/templates/context-map-template.md) @@ -155,6 +160,7 @@ - [[Subdomain Name] ([Classification] Subdomain)](.pair/knowledge/guidelines/collaboration/templates/subdomain-template.md) - [Task Template](.pair/knowledge/guidelines/collaboration/templates/task-template.md) - [User Story Template](.pair/knowledge/guidelines/collaboration/templates/user-story-template.md) +- [Working Area Convention](.pair/knowledge/guidelines/collaboration/working-area.md) - [CI/CD Artifacts Management](.pair/knowledge/guidelines/infrastructure/cicd-strategy/artifacts.md) - [GitHub Actions Implementation](.pair/knowledge/guidelines/infrastructure/cicd-strategy/github-actions-implementation.md) - [� CI/CD Strategy Practice](.pair/knowledge/guidelines/infrastructure/cicd-strategy/README.md) @@ -275,6 +281,7 @@ - [Performance Targets and Benchmarks Framework](.pair/knowledge/guidelines/quality-assurance/performance/targets-benchmarks.md) - [Performance Testing Strategies](.pair/knowledge/guidelines/quality-assurance/performance/testing-strategies.md) - [User-Centric Performance Framework](.pair/knowledge/guidelines/quality-assurance/performance/user-centric-performance.md) +- [Quality Model](.pair/knowledge/guidelines/quality-assurance/quality-model.md) - [Code Quality Monitoring](.pair/knowledge/guidelines/quality-assurance/quality-monitoring/code-quality.md) - [Observability Requirements](.pair/knowledge/guidelines/quality-assurance/quality-monitoring/observability-requirements.md) - [Performance Gates Implementation](.pair/knowledge/guidelines/quality-assurance/quality-monitoring/performance-gates.md) diff --git a/AGENTS.md b/AGENTS.md index 9ddacd79..1050e706 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ In all interactions and commit messages, be extremely coincise and sacrify gramm If your agent supports **Agent Skills** (agentskills.io), start every session by running: ```text -/pair-next +/next ``` The `/pair-next` skill reads project adoption files and PM tool state to recommend the most relevant action. Follow its suggestion or override with a specific skill. @@ -22,7 +22,7 @@ The `/pair-next` skill reads project adoption files and PM tool state to recomme ```text SESSION STATE: -├── How-to: [which .pair/how-to/XX-*.md file you're following] +├── How-to: [which .pair/knowledge/how-to/XX-*.md file you're following] ├── Role: [product-manager | product-engineer | staff-engineer] ├── PM Tool: [GitHub Projects | Jira | Linear | Trello | etc.] └── PM Access: [MCP command | URL/location for project management queries] @@ -41,13 +41,13 @@ PM Access: MCP github_projects (org: mycompany, repo: myproject) 1. **Determine how-to**: Use task selection algorithm below 2. **Identify role**: Check user language/request type, or ask if unclear -3. **Find PM tool**: Read `.pair/tech/adopted/way-of-working.md` to get the current project management tool -4. **Get PM access**: Extract tool-specific access instructions from `.pair/tech/knowledge-base/12-collaboration-and-process-guidelines/project-management-framework.md` +3. **Find PM tool**: Read `.pair/adoption/tech/way-of-working.md` to get the current project management tool +4. **Get PM access**: Extract tool-specific access instructions from `.pair/knowledge/guidelines/collaboration/project-management-tool/README.md` #### PM Tool Setup Process -- **Primary source**: `.pair/tech/adopted/way-of-working.md` (contains the adopted PM tool) -- **Usage instructions**: `.pair/tech/knowledge-base/12-collaboration-and-process-guidelines/project-management-framework.md` (contains tool-specific guidance) +- **Primary source**: `.pair/adoption/tech/way-of-working.md` (contains the adopted PM tool) +- **Usage instructions**: `.pair/knowledge/guidelines/collaboration/project-management-tool/README.md` (contains tool-specific guidance) - **If tool not specified**: Ask developer which PM tool to use - **Access details**: Extract MCP commands or URLs from the framework file for your specific tool @@ -60,10 +60,10 @@ PM Access: MCP github_projects (org: mycompany, repo: myproject) **Without skills** (manual flow): 1. **Establish session context** (see Session Context above - maintain for entire conversation) -2. **Understand the project**: Read `.pair/product/adopted/PRD.md` for project overview -3. **Identify your task**: Match your request to a task category using `.pair/how-to/index.json` +2. **Understand the project**: Read `.pair/adoption/product/PRD.md` for project overview +3. **Identify your task**: Match your request to a task category using `.pair/knowledge/how-to/` 4. **Follow the guidance**: Use the selected how-to file for specific instructions -5. **Apply constraints**: Check `.pair/tech/adopted/` for technical requirements +5. **Apply constraints**: Check `.pair/adoption/tech/` for technical requirements ## 📋 Available Tasks @@ -72,20 +72,18 @@ PM Access: MCP github_projects (org: mycompany, repo: myproject) ### Induction (Getting Started) | - **Create PRD** → `01-how-to-create-PRD.md` | Tags: prd, requirements, planning | -| - **Setup project** → `02-how-to-complete-bootstrap-checklist.md` | Tags: bootstrap, setup, onboarding | -| - **Define subdomains** → `04-how-to-define-subdomains.md` | Tags: subdomain, domain, model | +| - **Setup project** → `02-how-to-complete-bootstrap-checklist.md` | Tags: bootstrap, setup, onboarding, subdomain, bounded-context, domain-model | ### Strategic (High-level Planning) -| - **Plan initiatives** → `03-how-to-create-and-prioritize-initiatives.md` | Tags: initiative, roadmap | -| - **Define architecture** → `05-how-to-define-bounded-contexts.md` | Tags: bounded, context, architecture | -| - **Break down epics** → `06-how-to-breakdown-epics.md` | Tags: epic, breakdown | +| - **Plan initiatives** → `03-how-to-create-and-prioritize-initiatives.md` | Tags: initiative, roadmap, subdomain | +| - **Break down epics** → `06-how-to-breakdown-epics.md` | Tags: epic, breakdown, subdomain | ### Iteration (Sprint Planning) | - **Create user stories** → `07-how-to-breakdown-user-stories.md` | Tags: story, requirements | | - **Refine stories** → `08-how-to-refine-a-user-story.md` | Tags: refine, acceptance, criteria | -| - **Create tasks** → `09-how-to-create-tasks.md` | Tags: task, breakdown, assign | +| - **Create tasks** → `09-how-to-create-tasks.md` | Tags: task, breakdown, assign, bounded-context | ### Execution (Development) @@ -116,17 +114,17 @@ pnpm lint --filter ## 📚 Key References - **AI-friendly index**: `.pair/llms.txt` (llmstxt.org — machine-readable knowledge base index) -- **Project context**: `.pair/product/adopted/PRD.md` -- **PM tool adoption**: `.pair/tech/adopted/way-of-working.md` (determines which PM tool to use) -- **PM tool usage**: `.pair/tech/knowledge-base/12-collaboration-and-process-guidelines/project-management-framework.md` (tool-specific instructions) -- **Technical decisions**: `.pair/tech/adopted/` (architecture, tech-stack, infrastructure) -- **Testing strategy**: `.pair/tech/knowledge-base/07-testing-strategy.md` -- **Code guidelines**: `.pair/tech/knowledge-base/02-code-design-guidelines.md` -- **Security rules**: `.pair/tech/knowledge-base/10-security-guidelines.md` +- **Project context**: `.pair/adoption/product/PRD.md` +- **PM tool adoption**: `.pair/adoption/tech/way-of-working.md` (determines which PM tool to use) +- **PM tool usage**: `.pair/knowledge/guidelines/collaboration/project-management-tool/README.md` (tool-specific instructions) +- **Technical decisions**: `.pair/adoption/tech/` (architecture, tech-stack, infrastructure) +- **Testing strategy**: `.pair/knowledge/guidelines/testing/README.md` +- **Code guidelines**: `.pair/knowledge/guidelines/code-design/README.md` +- **Security rules**: `.pair/knowledge/guidelines/quality-assurance/security/security-guidelines.md` ## 🎭 If unsure about your task -**Use the index**: Load `.pair/how-to/index.json` and match user request keywords to task `tags` +**Use the index**: Load `.pair/knowledge/how-to/` and match user request keywords to task `tags` ### Workflow categories @@ -148,13 +146,13 @@ pnpm lint --filter - **Maintain session context** - Always reference your current how-to, role, PM tool, and access method - **One task per session** - keep changes focused within the current how-to scope - **Tests required** - follow testing strategy for all code changes -- **Check adoptions first** - `.pair/tech/adopted/` overrides other guidance +- **Check adoptions first** - `.pair/adoption/tech/` overrides other guidance - **Package-specific rules win** - check for `.pair/knowledge/guidelines/` in target package - **No secrets in code** - ask for secure access instructions if needed - **Context consistency** - if switching how-to mid-session, explicitly update your session context - **Bug fix workflow** - NEVER modify code to fix a bug before creating a test that reproduces it. Test-first debugging ensures the fix actually addresses the problem. - **`.pair/working/` is not ambient context** - Content under `.pair/working/` (checkpoints, reports, handoffs) is never loaded as context unless explicitly specified. The skills that use it decide if and how. -- **Record decisions** - architectural/project decisions MUST be recorded as ADR or ADL. Use `/pair-capability-record-decision` skill or write to `.pair/tech/adopted/adr/` (architectural) / `.pair/tech/adopted/adl/` (non-architectural). +- **Record decisions** - architectural/project decisions MUST be recorded as ADR or ADL. Use `/pair-capability-record-decision` skill or write to `.pair/adoption/tech/adr/` (architectural) / `.pair/adoption/tech/adl/` (non-architectural). - **Follow templates** - PRs, code reviews, commits, branches, tasks, user stories MUST follow templates in `.pair/knowledge/guidelines/collaboration/templates/` unless adoption files specify otherwise. Key templates: `pr-template.md`, `code-review-template.md`, `commit-template.md`, `branch-template.md`, `task-template.md`, `user-story-template.md`. ## 🐛 Bug Resolution Workflow @@ -206,7 +204,7 @@ function isLocalPath(str: string): boolean { ## 🔄 Task Selection Algorithm 1. **Exact match**: Developer mentions specific how-to ID/filename → use it -2. **Tag matching**: Use `.pair/how-to/index.json` to match request keywords to task tags +2. **Tag matching**: Use `.pair/knowledge/how-to/` to match request keywords to task tags 3. **Category workflow**: Follow the natural progression (induction → strategic → iteration → execution → review) 4. **Role context**: Consider if developer specified a role (product-manager, product-engineer, staff-engineer) 5. **When ambiguous**: Show top 2 matches with their tags and ask for confirmation diff --git a/CLAUDE.md b/CLAUDE.md index 08088b4d..4022fba2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,7 +9,7 @@ In all interactions and commit messages, be extremely coincise and sacrify gramm If your agent supports **Agent Skills** (agentskills.io), start every session by running: ```text -/pair-next +/next ``` The `/pair-next` skill reads project adoption files and PM tool state to recommend the most relevant action. Follow its suggestion or override with a specific skill. @@ -23,10 +23,10 @@ The `/pair-next` skill reads project adoption files and PM tool state to recomme **Without skills** (manual flow): 1. **Establish session context** (see Session Context above - maintain for entire conversation) -2. **Understand the project**: Read `.pair/product/adopted/PRD.md` for project overview -3. **Identify your task**: Match your request to a task category using `.pair/how-to/index.json` +2. **Understand the project**: Read `.pair/adoption/product/PRD.md` for project overview +3. **Identify your task**: Match your request to a task category using `.pair/knowledge/how-to/` 4. **Follow the guidance**: Use the selected how-to file for specific instructions -5. **Apply constraints**: Check `.pair/tech/adopted/` for technical requirements +5. **Apply constraints**: Check `.pair/adoption/tech/` for technical requirements ## 📋 Available Tasks @@ -35,20 +35,18 @@ The `/pair-next` skill reads project adoption files and PM tool state to recomme ### Induction (Getting Started) | - **Create PRD** → `01-how-to-create-PRD.md` | Tags: prd, requirements, planning | -| - **Setup project** → `02-how-to-complete-bootstrap-checklist.md` | Tags: bootstrap, setup, onboarding | -| - **Define subdomains** → `04-how-to-define-subdomains.md` | Tags: subdomain, domain, model | +| - **Setup project** → `02-how-to-complete-bootstrap-checklist.md` | Tags: bootstrap, setup, onboarding, subdomain, bounded-context, domain-model | ### Strategic (High-level Planning) -| - **Plan initiatives** → `03-how-to-create-and-prioritize-initiatives.md` | Tags: initiative, roadmap | -| - **Define architecture** → `05-how-to-define-bounded-contexts.md` | Tags: bounded, context, architecture | -| - **Break down epics** → `06-how-to-breakdown-epics.md` | Tags: epic, breakdown | +| - **Plan initiatives** → `03-how-to-create-and-prioritize-initiatives.md` | Tags: initiative, roadmap, subdomain | +| - **Break down epics** → `06-how-to-breakdown-epics.md` | Tags: epic, breakdown, subdomain | ### Iteration (Sprint Planning) | - **Create user stories** → `07-how-to-breakdown-user-stories.md` | Tags: story, requirements | | - **Refine stories** → `08-how-to-refine-a-user-story.md` | Tags: refine, acceptance, criteria | -| - **Create tasks** → `09-how-to-create-tasks.md` | Tags: task, breakdown, assign | +| - **Create tasks** → `09-how-to-create-tasks.md` | Tags: task, breakdown, assign, bounded-context | ### Execution (Development) @@ -79,27 +77,26 @@ pnpm lint --filter ## 📚 Key References - **AI-friendly index**: `.pair/llms.txt` (llmstxt.org — machine-readable knowledge base index) -- **Project context**: `.pair/product/adopted/PRD.md` -- **PM tool adoption**: `.pair/tech/adopted/way-of-working.md` (determines which PM tool to use) -- **PM tool usage**: `.pair/tech/knowledge-base/12-collaboration-and-process-guidelines/project-management-framework.md` (tool-specific instructions) -- **Technical decisions**: `.pair/tech/adopted/` (architecture, tech-stack, infrastructure) -- **Testing strategy**: `.pair/tech/knowledge-base/07-testing-strategy.md` -- **Code guidelines**: `.pair/tech/knowledge-base/02-code-design-guidelines.md` -- **Security rules**: `.pair/tech/knowledge-base/10-security-guidelines.md` -- **Manual test suites**: `qa/release-validation/` (critical path test cases CP1–CP8) +- **Project context**: `.pair/adoption/product/PRD.md` +- **PM tool adoption**: `.pair/adoption/tech/way-of-working.md` (determines which PM tool to use) +- **PM tool usage**: `.pair/knowledge/guidelines/collaboration/project-management-tool/README.md` (tool-specific instructions) +- **Technical decisions**: `.pair/adoption/tech/` (architecture, tech-stack, infrastructure) +- **Testing strategy**: `.pair/knowledge/guidelines/testing/README.md` +- **Code guidelines**: `.pair/knowledge/guidelines/code-design/README.md` +- **Security rules**: `.pair/knowledge/guidelines/quality-assurance/security/security-guidelines.md` ## ⚡ Quick Rules - **Maintain session context** - Always reference your current how-to, role, PM tool, and access method - **One task per session** - keep changes focused within the current how-to scope - **Tests required** - follow testing strategy for all code changes -- **Check adoptions first** - `.pair/tech/adopted/` overrides other guidance +- **Check adoptions first** - `.pair/adoption/tech/` overrides other guidance - **Package-specific rules win** - check for `.pair/knowledge/guidelines/` in target package - **No secrets in code** - ask for secure access instructions if needed - **Context consistency** - if switching how-to mid-session, explicitly update your session context - **Bug fix workflow** - NEVER modify code to fix a bug before creating a test that reproduces it. Test-first debugging ensures the fix actually addresses the problem. - **`.pair/working/` is not ambient context** - Content under `.pair/working/` (checkpoints, reports, handoffs) is never loaded as context unless explicitly specified. The skills that use it decide if and how. -- **Record decisions** - architectural/project decisions MUST be recorded as ADR or ADL. Use `/pair-capability-record-decision` skill or write to `.pair/tech/adopted/adr/` (architectural) / `.pair/tech/adopted/adl/` (non-architectural). +- **Record decisions** - architectural/project decisions MUST be recorded as ADR or ADL. Use `/pair-capability-record-decision` skill or write to `.pair/adoption/tech/adr/` (architectural) / `.pair/adoption/tech/adl/` (non-architectural). - **Follow templates** - PRs, code reviews, commits, branches, tasks, user stories MUST follow templates in `.pair/knowledge/guidelines/collaboration/templates/` unless adoption files specify otherwise. Key templates: `pr-template.md`, `code-review-template.md`, `commit-template.md`, `branch-template.md`, `task-template.md`, `user-story-template.md`. ## 🐛 Bug Resolution Workflow @@ -147,3 +144,4 @@ function isLocalPath(str: string): boolean { - **Regression prevention**: Test stays in codebase to catch future breaks - **Clarity**: Test documents expected behavior - **Confidence**: Linting + tests pass before committing + diff --git a/apps/pair-cli/config.json b/apps/pair-cli/config.json index 3765987c..f9d6a072 100644 --- a/apps/pair-cli/config.json +++ b/apps/pair-cli/config.json @@ -30,7 +30,7 @@ }, "skills": { "source": ".skills", - "behavior": "mirror", + "behavior": "overwrite", "flatten": true, "prefix": "pair", "description": "Agent skills distributed to AI tool directories", diff --git a/apps/pair-cli/src/commands/install/handler.ts b/apps/pair-cli/src/commands/install/handler.ts index fcfa2aec..4e6435dc 100644 --- a/apps/pair-cli/src/commands/install/handler.ts +++ b/apps/pair-cli/src/commands/install/handler.ts @@ -22,7 +22,7 @@ import { } from '#registry' import { applyLinkTransformation } from '../update-link/logic' import type { HttpClientService } from '@pair/content-ops' -import { type SkillNameMap } from '@pair/content-ops' +import { type SkillNameMap, type SkillLinkPathMap } from '@pair/content-ops' import { createCliPresenter, type CliPresenter, type RegistryResult } from '#ui' import { emitVersionDriftHint, recordInstalledVersion } from '../kb-info/version-hint' @@ -209,6 +209,7 @@ async function finalizeRegistryCopy( async function installRegistry(ctx: RegistryInstallCtx): Promise<{ skillNameMap?: SkillNameMap | undefined + skillLinkPathMap?: SkillLinkPathMap | undefined result: RegistryResult }> { const { fs, registryName, registryConfig, pushLog, presenter, index, total } = ctx @@ -242,6 +243,7 @@ async function installRegistry(ctx: RegistryInstallCtx): Promise<{ presenter.registryDone(registryName) return { skillNameMap: copyResult['skillNameMap'] as SkillNameMap | undefined, + skillLinkPathMap: copyResult['skillLinkPathMap'] as SkillLinkPathMap | undefined, result: { name: registryName, target: effectiveTarget, ok: true }, } } @@ -259,9 +261,11 @@ type InstallContext = { async function installAllRegistries(ctx: InstallContext): Promise<{ results: RegistryResult[] skillNameMap: SkillNameMap + skillLinkPathMap: SkillLinkPathMap }> { const { fs, datasetRoot, registries, baseTarget, pushLog, presenter } = ctx const accumulated: SkillNameMap = new Map() + const accumulatedLinkMap: SkillLinkPathMap = new Map() const total = Object.keys(registries).length const results = await forEachRegistry(registries, async (registryName, registryConfig, index) => { @@ -279,9 +283,12 @@ async function installAllRegistries(ctx: InstallContext): Promise<{ if (out.skillNameMap) { for (const [k, v] of out.skillNameMap) accumulated.set(k, v) } + if (out.skillLinkPathMap) { + for (const [k, v] of out.skillLinkPathMap) accumulatedLinkMap.set(k, v) + } return out.result }) - return { results, skillNameMap: accumulated } + return { results, skillNameMap: accumulated, skillLinkPathMap: accumulatedLinkMap } } async function executeInstall(context: InstallContext): Promise { @@ -291,9 +298,14 @@ async function executeInstall(context: InstallContext): Promise { presenter.startOperation('install', total) - const { results, skillNameMap } = await installAllRegistries(context) + const { results, skillNameMap, skillLinkPathMap } = await installAllRegistries(context) - await reconcileSkillNameRegistry({ fs, baseTarget, pushLog }, registries, skillNameMap) + await reconcileSkillNameRegistry( + { fs, baseTarget, pushLog }, + registries, + skillNameMap, + skillLinkPathMap, + ) if (options?.linkStyle) { await applyLinkTransformation(fs, { linkStyle: options.linkStyle }, pushLog, 'install') diff --git a/apps/pair-cli/src/commands/update/handler.ts b/apps/pair-cli/src/commands/update/handler.ts index 918207d5..b0998b73 100644 --- a/apps/pair-cli/src/commands/update/handler.ts +++ b/apps/pair-cli/src/commands/update/handler.ts @@ -21,7 +21,7 @@ import { } from '#registry' import { applyLinkTransformation } from '../update-link/logic' import type { HttpClientService } from '@pair/content-ops' -import { BackupService, type SkillNameMap } from '@pair/content-ops' +import { BackupService, type SkillNameMap, type SkillLinkPathMap } from '@pair/content-ops' import { createCliPresenter, type CliPresenter, type RegistryResult } from '#ui' import { emitVersionDriftHint, recordInstalledVersion } from '../kb-info/version-hint' @@ -219,9 +219,11 @@ async function finalizeRegistryCopy( await postCopyOps({ fs, registryConfig, baseTarget, ...paths }) } -async function updateSingleRegistry( - ctx: UpdateRegistryCtx, -): Promise<{ skillNameMap?: SkillNameMap | undefined; result: RegistryResult }> { +async function updateSingleRegistry(ctx: UpdateRegistryCtx): Promise<{ + skillNameMap?: SkillNameMap | undefined + skillLinkPathMap?: SkillLinkPathMap | undefined + result: RegistryResult +}> { const { fs, datasetRoot, @@ -242,9 +244,7 @@ async function updateSingleRegistry( }) const { target: effectiveTarget, source: datasetPath } = resolved await ensureDir(fs, dirname(effectiveTarget)) - const effectiveDatasetRoot = resolveEffectiveDatasetRoot(registryConfig, baseTarget, datasetRoot) - await logDatasetEntries(fs, datasetPath, pushLog) presenter.registryStart({ name: registryName, @@ -264,6 +264,7 @@ async function updateSingleRegistry( presenter.registryDone(registryName) return { skillNameMap: copyResult['skillNameMap'] as SkillNameMap | undefined, + skillLinkPathMap: copyResult['skillLinkPathMap'] as SkillLinkPathMap | undefined, result: { name: registryName, target: effectiveTarget, ok: true }, } } @@ -271,13 +272,14 @@ async function updateSingleRegistry( async function updateRegistries(context: UpdateContext): Promise { const { fs, datasetRoot, registries, baseTarget, pushLog, presenter } = context const accumulatedSkillNameMap: SkillNameMap = new Map() + const accumulatedSkillLinkPathMap: SkillLinkPathMap = new Map() const total = Object.keys(registries).length const startTime = Date.now() presenter.startOperation('update', total) const results = await forEachRegistry(registries, async (registryName, registryConfig, index) => { - const { skillNameMap, result } = await updateSingleRegistry({ + const { skillNameMap, skillLinkPathMap, result } = await updateSingleRegistry({ fs, datasetRoot, registryName, @@ -291,10 +293,18 @@ async function updateRegistries(context: UpdateContext): Promise { expect(skills).toBeDefined() expect(skills!.source).toBe('.skills') - expect(skills!.behavior).toBe('mirror') + expect(skills!.behavior).toBe('overwrite') expect(skills!.flatten).toBe(true) expect(skills!.prefix).toBe('pair') expect(skills!.targets).toHaveLength(6) diff --git a/apps/pair-cli/src/registry/skill-refs.test.ts b/apps/pair-cli/src/registry/skill-refs.test.ts index cef4b8da..645b60e1 100644 --- a/apps/pair-cli/src/registry/skill-refs.test.ts +++ b/apps/pair-cli/src/registry/skill-refs.test.ts @@ -29,7 +29,12 @@ describe('rewriteSkillRefsInTarget', () => { ['verify-quality', 'pair-capability-verify-quality'], ]) - await rewriteSkillRefsInTarget(fs, '/project/AGENTS.md', map, noopLog) + await rewriteSkillRefsInTarget( + fs, + '/project/AGENTS.md', + { skillNameMap: map, skillLinkPathMap: new Map() }, + noopLog, + ) const result = await fs.readFile('/project/AGENTS.md') expect(result).toContain('/pair-process-next') @@ -37,6 +42,35 @@ describe('rewriteSkillRefsInTarget', () => { expect(result).not.toMatch(/(? { + const fs = new InMemoryFileSystemService( + { + '/project/doc.md': + 'See [map-subdomains](../../.skills/capability/map-subdomains/SKILL.md).', + }, + '/project', + '/project', + ) + const linkMap = new Map([ + [ + '../.skills/capability/map-subdomains/SKILL.md', + '../.claude/skills/pair-capability-map-subdomains/SKILL.md', + ], + ]) + + await rewriteSkillRefsInTarget( + fs, + '/project/doc.md', + { skillNameMap: new Map(), skillLinkPathMap: linkMap }, + noopLog, + ) + + const result = await fs.readFile('/project/doc.md') + expect(result).toBe( + 'See [map-subdomains](../../.claude/skills/pair-capability-map-subdomains/SKILL.md).', + ) + }) + it('rewrites all markdown files in a directory', async () => { const fs = new InMemoryFileSystemService( { @@ -52,7 +86,12 @@ describe('rewriteSkillRefsInTarget', () => { ['review', 'pair-process-review'], ]) - await rewriteSkillRefsInTarget(fs, '/project/docs', map, noopLog) + await rewriteSkillRefsInTarget( + fs, + '/project/docs', + { skillNameMap: map, skillLinkPathMap: new Map() }, + noopLog, + ) expect(await fs.readFile('/project/docs/guide.md')).toContain('/pair-process-implement') expect(await fs.readFile('/project/docs/review.md')).toContain('/pair-process-review') @@ -63,7 +102,12 @@ describe('rewriteSkillRefsInTarget', () => { const map = makeSkillNameMap([['next', 'pair-process-next']]) // Should not throw - await rewriteSkillRefsInTarget(fs, '/project/nonexistent.md', map, noopLog) + await rewriteSkillRefsInTarget( + fs, + '/project/nonexistent.md', + { skillNameMap: map, skillLinkPathMap: new Map() }, + noopLog, + ) }) it('skips non-markdown files', async () => { @@ -74,7 +118,12 @@ describe('rewriteSkillRefsInTarget', () => { ) const map = makeSkillNameMap([['next', 'pair-process-next']]) - await rewriteSkillRefsInTarget(fs, '/project/config.json', map, noopLog) + await rewriteSkillRefsInTarget( + fs, + '/project/config.json', + { skillNameMap: map, skillLinkPathMap: new Map() }, + noopLog, + ) // Non-md files should not be rewritten const result = await fs.readFile('/project/config.json') @@ -89,7 +138,12 @@ describe('rewriteSkillRefsInTarget', () => { ) const map = makeSkillNameMap([['next', 'pair-process-next']]) - await rewriteSkillRefsInTarget(fs, '/project/README.md', map, noopLog) + await rewriteSkillRefsInTarget( + fs, + '/project/README.md', + { skillNameMap: map, skillLinkPathMap: new Map() }, + noopLog, + ) const result = await fs.readFile('/project/README.md') expect(result).toBe('# No skill refs here') @@ -139,6 +193,7 @@ describe('applySkillRefsToNonSkillRegistries', () => { { fs, baseTarget: '/project', pushLog: noopLog }, registries, map, + new Map(), ) // AGENTS.md (non-skills) should be rewritten @@ -177,6 +232,7 @@ describe('applySkillRefsToNonSkillRegistries', () => { { fs, baseTarget: '/project', pushLog: noopLog }, registries, map, + new Map(), ) // Canonical should be rewritten @@ -208,6 +264,7 @@ describe('applySkillRefsToNonSkillRegistries', () => { { fs, baseTarget: '/project', pushLog: noopLog }, registries, emptyMap, + new Map(), ) const result = await fs.readFile('/project/AGENTS.md') @@ -218,9 +275,7 @@ describe('applySkillRefsToNonSkillRegistries', () => { describe('resolveSkillNameManifestPath', () => { it('resolves under .pair/ relative to baseTarget', () => { const fs = new InMemoryFileSystemService({}, '/project', '/project') - expect(resolveSkillNameManifestPath(fs, '/project')).toBe( - '/project/.pair/.skill-name-map.json', - ) + expect(resolveSkillNameManifestPath(fs, '/project')).toBe('/project/.pair/.skill-name-map.json') }) it('is outside the knowledge and adoption registry targets', () => { @@ -235,7 +290,11 @@ describe('detectOrphanedSkillReferences', () => { const noopLog = () => {} it('does nothing when there are no orphaned names', async () => { - const fs = new InMemoryFileSystemService({ '/project/AGENTS.md': '# AGENTS' }, '/project', '/project') + const fs = new InMemoryFileSystemService( + { '/project/AGENTS.md': '# AGENTS' }, + '/project', + '/project', + ) const registries: Record = { agents: { source: 'AGENTS.md', @@ -247,7 +306,11 @@ describe('detectOrphanedSkillReferences', () => { }, } - await detectOrphanedSkillReferences({ fs, baseTarget: '/project', pushLog: noopLog }, registries, []) + await detectOrphanedSkillReferences( + { fs, baseTarget: '/project', pushLog: noopLog }, + registries, + [], + ) // no-op — nothing to assert beyond "did not throw" }) @@ -274,11 +337,9 @@ describe('detectOrphanedSkillReferences', () => { if (level === 'warn') warnings.push(message) } - await detectOrphanedSkillReferences( - { fs, baseTarget: '/project', pushLog }, - registries, - ['pair-removed'], - ) + await detectOrphanedSkillReferences({ fs, baseTarget: '/project', pushLog }, registries, [ + 'pair-removed', + ]) expect(warnings).toHaveLength(1) expect(warnings[0]).toContain('/pair-removed') @@ -311,11 +372,9 @@ describe('detectOrphanedSkillReferences', () => { if (level === 'warn') warnings.push(message) } - await detectOrphanedSkillReferences( - { fs, baseTarget: '/project', pushLog }, - registries, - ['pair-removed'], - ) + await detectOrphanedSkillReferences({ fs, baseTarget: '/project', pushLog }, registries, [ + 'pair-removed', + ]) expect(warnings).toHaveLength(0) }) @@ -334,11 +393,9 @@ describe('detectOrphanedSkillReferences', () => { } await expect( - detectOrphanedSkillReferences( - { fs, baseTarget: '/project', pushLog: noopLog }, - registries, - ['pair-removed'], - ), + detectOrphanedSkillReferences({ fs, baseTarget: '/project', pushLog: noopLog }, registries, [ + 'pair-removed', + ]), ).resolves.not.toThrow() }) }) @@ -384,7 +441,12 @@ describe('reconcileSkillNameRegistry', () => { if (level === 'warn') warnings.push(message) } - await reconcileSkillNameRegistry({ fs, baseTarget: '/project', pushLog }, registries, new Map()) + await reconcileSkillNameRegistry( + { fs, baseTarget: '/project', pushLog }, + registries, + new Map(), + new Map(), + ) // No orphan warning — "pair-process-next" is not misreported as removed. expect(warnings).toHaveLength(0) diff --git a/apps/pair-cli/src/registry/skill-refs.ts b/apps/pair-cli/src/registry/skill-refs.ts index 863f61e7..d40af0b3 100644 --- a/apps/pair-cli/src/registry/skill-refs.ts +++ b/apps/pair-cli/src/registry/skill-refs.ts @@ -1,6 +1,7 @@ -import type { FileSystemService, SkillNameMap } from '@pair/content-ops' +import type { FileSystemService, SkillNameMap, SkillLinkPathMap } from '@pair/content-ops' import { rewriteSkillReferences, + rewriteSkillLinkPaths, findSkillReferences, walkMarkdownFiles, readSkillNameManifest, @@ -33,16 +34,23 @@ export type SkillRefContext = { } /** - * Rewrites skill references in all markdown files under a target path. - * If target is a file, rewrites that single file. If a directory, walks all .md files. - * No-op when target doesn't exist or is a non-markdown file. + * Rewrites skill references AND SKILL.md cross-reference link paths in all + * markdown files under a target path. If target is a file, rewrites that single + * file. If a directory, walks all .md files. No-op when target doesn't exist or + * is a non-markdown file. + * + * The two rewrites are complementary: `rewriteSkillReferences` converts + * `/command` tokens (`/setup-gates` → `/pair-capability-setup-gates`); + * `rewriteSkillLinkPaths` converts markdown link PATHS + * (`.skills/capability/map-subdomains/` → `.claude/skills/pair-capability-map-subdomains/`). */ export async function rewriteSkillRefsInTarget( fs: FileSystemService, target: string, - skillNameMap: SkillNameMap, + maps: { skillNameMap: SkillNameMap; skillLinkPathMap: SkillLinkPathMap }, pushLog: (level: LogEntry['level'], message: string) => void, ): Promise { + const { skillNameMap, skillLinkPathMap } = maps if (!(await fs.exists(target))) return const stat = await fs.stat(target) @@ -54,7 +62,8 @@ export async function rewriteSkillRefsInTarget( for (const filePath of files) { const content = await fs.readFile(filePath) - const rewritten = rewriteSkillReferences(content, skillNameMap) + let rewritten = rewriteSkillReferences(content, skillNameMap) + rewritten = rewriteSkillLinkPaths(rewritten, skillLinkPathMap) if (rewritten !== content) { await fs.writeFile(filePath, rewritten) pushLog('info', `Skill reference rewriter: updated ${filePath}`) @@ -71,6 +80,7 @@ export async function applySkillRefsToNonSkillRegistries( context: SkillRefContext, registries: Record, skillNameMap: SkillNameMap, + skillLinkPathMap: SkillLinkPathMap, ): Promise { const { fs, baseTarget, pushLog } = context @@ -81,7 +91,7 @@ export async function applySkillRefsToNonSkillRegistries( const target = baseTarget ? fs.resolve(baseTarget, targetCfg.path) : fs.resolve(targetCfg.path) - await rewriteSkillRefsInTarget(fs, target, skillNameMap, pushLog) + await rewriteSkillRefsInTarget(fs, target, { skillNameMap, skillLinkPathMap }, pushLog) } } } @@ -150,6 +160,7 @@ export async function reconcileSkillNameRegistry( context: SkillRefContext, registries: Record, skillNameMap: SkillNameMap, + skillLinkPathMap: SkillLinkPathMap, ): Promise { // Covers both "no manifest yet" and "flatten/prefix disabled for every // registry this run" (accumulated map empty either way — see @@ -171,7 +182,7 @@ export async function reconcileSkillNameRegistry( const orphanedNames = findOrphanedInstalledNames(previousMap, skillNameMap) const combinedMap = mergeSkillNameMaps(skillNameMap, transitionMap) - await applySkillRefsToNonSkillRegistries(context, registries, combinedMap) + await applySkillRefsToNonSkillRegistries(context, registries, combinedMap, skillLinkPathMap) await detectOrphanedSkillReferences(context, registries, orphanedNames) // Not atomic with the two passes above: if either throws partway through, the diff --git a/packages/content-ops/src/index.ts b/packages/content-ops/src/index.ts index 95e9f057..4119260b 100644 --- a/packages/content-ops/src/index.ts +++ b/packages/content-ops/src/index.ts @@ -73,7 +73,12 @@ export { rewriteSkillReferences, buildSkillNameMap, findSkillReferences, + rewriteSkillReferencesInFiles, + rewriteSkillLinkPaths, + rewriteSkillLinkPathsInFiles, + buildSkillLinkPathMap, type SkillNameMap, + type SkillLinkPathMap, } from './ops/skill-reference-rewriter' export { readSkillNameManifest, diff --git a/packages/content-ops/src/markdown/replacement-applier.ts b/packages/content-ops/src/markdown/replacement-applier.ts index 3431979b..aa8b77a1 100644 --- a/packages/content-ops/src/markdown/replacement-applier.ts +++ b/packages/content-ops/src/markdown/replacement-applier.ts @@ -104,6 +104,7 @@ export async function processFileReplacement( lines: string[], ) => Promise, fileService: FileSystemService, + options?: { checkOnly?: boolean }, ): Promise<{ content: string; applied: number; byKind?: Record }> { const content = await fileService.readFile(file) const lines = content.split(/\r?\n/) @@ -118,7 +119,11 @@ export async function processFileReplacement( ) } - await persistIfModified(result, fileService, file) + // checkOnly: report errors/normalizations but never write. Used by the link + // GATE over the generated root tree — a gate must not mutate its target. + if (!options?.checkOnly) { + await persistIfModified(result, fileService, file) + } return result } diff --git a/packages/content-ops/src/ops/copy/copy-directory-transforms.test.ts b/packages/content-ops/src/ops/copy/copy-directory-transforms.test.ts index 96b62969..e166960f 100644 --- a/packages/content-ops/src/ops/copy/copy-directory-transforms.test.ts +++ b/packages/content-ops/src/ops/copy/copy-directory-transforms.test.ts @@ -387,6 +387,30 @@ describe('copyDirectoryWithTransforms (via copyPathOps, flatten/prefix)', () => ) }) + it('preserves a foreign (non-source) entry in a shared target under overwrite behavior', async () => { + // The skills registry uses `overwrite` (not `mirror`) precisely so a + // shared target dir keeps third-party / user-installed skills: overwrite + // updates/adds source entries but never runs stale cleanup. A foreign + // skill (e.g. agent-browser) therefore survives. + const fileService = createTestFileService({ + '/dataset/source/catalog/next/SKILL.md': '---\nname: next\n---\n# /next', + '/dataset/target/agent-browser/SKILL.md': '---\nname: agent-browser\n---', + }) + + await copyPathOps({ + fileService, + source: 'source', + target: 'target', + datasetRoot: '/dataset', + options: { flatten: true, prefix: 'pair', defaultBehavior: 'overwrite', targets: [] }, + }) + + await expect(fileService.exists('/dataset/target/agent-browser/SKILL.md')).resolves.toBe(true) + await expect(fileService.exists('/dataset/target/pair-catalog-next/SKILL.md')).resolves.toBe( + true, + ) + }) + it('removes the old prefixed directory after a prefix change', async () => { const fileService = createTestFileService({ '/dataset/source/catalog/next/SKILL.md': '---\nname: next\n---\n# /next', diff --git a/packages/content-ops/src/ops/copy/copy-directory-transforms.ts b/packages/content-ops/src/ops/copy/copy-directory-transforms.ts index 2e616cbd..0a630ddc 100644 --- a/packages/content-ops/src/ops/copy/copy-directory-transforms.ts +++ b/packages/content-ops/src/ops/copy/copy-directory-transforms.ts @@ -8,8 +8,10 @@ import { rewriteLinksAfterTransform, PathMappingEntry } from '../link-rewriter' import { syncFrontmatter } from '../frontmatter-transform' import { buildSkillNameMap, + buildSkillLinkPathMap, rewriteSkillReferencesInFiles, SkillNameMap, + SkillLinkPathMap, } from '../skill-reference-rewriter' import type { CopyPathOpsResult, TransformOpts } from './copy-types' @@ -250,7 +252,7 @@ export async function copyDirectoryWithTransforms(params: { } await rewriteLinksForTransformedDirs(params, dirMappingFiles, transformOpts) - const skillNameMap = await applySkillReferenceRewrites( + const { skillNameMap, skillLinkPathMap } = await applySkillReferenceRewrites( fileService, dirMappingFiles, transformOpts, @@ -259,7 +261,7 @@ export async function copyDirectoryWithTransforms(params: { logger.info( `Copied contents of ${srcPath} -> ${destPath} (flatten=${transformOpts.flatten}, prefix=${transformOpts.prefix ?? 'none'})`, ) - return skillNameMap.size > 0 ? { skillNameMap } : {} + return skillNameMap.size > 0 ? { skillNameMap, skillLinkPathMap } : {} } /** @@ -335,15 +337,23 @@ async function rewriteLinksForTransformedDirs( } /** - * Collects .md files from dirMappingFiles and rewrites skill references if any renames occurred. + * Collects .md files from dirMappingFiles and rewrites skill references if any + * renames occurred, returning both the skill name map (for `/command` token + * rewrites) and the skill link-path map (for SKILL.md cross-reference PATH + * rewrites) so the caller can chain them onto other registries' files. + * + * The link-path map is only built and returned here — it is NOT applied to the + * copied `.skills` files themselves: their cross-references are already + * rewritten by `rewriteLinksAfterTransform` (via `rewriteLinksForTransformedDirs`). */ async function applySkillReferenceRewrites( fileService: FileSystemService, dirMappingFiles: Map, transformOpts: TransformOpts, -): Promise { +): Promise<{ skillNameMap: SkillNameMap; skillLinkPathMap: SkillLinkPathMap }> { const skillNameMap = buildSkillNameMap(dirMappingFiles, transformOpts) - if (skillNameMap.size === 0) return skillNameMap + const skillLinkPathMap = buildSkillLinkPathMap(dirMappingFiles, transformOpts) + if (skillNameMap.size === 0) return { skillNameMap, skillLinkPathMap } const allMdFiles: string[] = [] for (const mappedFiles of dirMappingFiles.values()) { @@ -352,5 +362,5 @@ async function applySkillReferenceRewrites( } } await rewriteSkillReferencesInFiles({ fileService, files: allMdFiles, skillNameMap }) - return skillNameMap + return { skillNameMap, skillLinkPathMap } } diff --git a/packages/content-ops/src/ops/copy/copy-file.ts b/packages/content-ops/src/ops/copy/copy-file.ts index 8cf20520..1a9ce6ab 100644 --- a/packages/content-ops/src/ops/copy/copy-file.ts +++ b/packages/content-ops/src/ops/copy/copy-file.ts @@ -4,7 +4,12 @@ import { FileSystemService } from '../../file-system' import { SyncOptions } from '../SyncOptions' import { Behavior } from '../behavior' import { determineFinalDestination, updateMarkdownLinks } from '../path-operation-helpers' -import { rewriteSkillReferencesInFiles, SkillNameMap } from '../skill-reference-rewriter' +import { + rewriteSkillReferencesInFiles, + rewriteSkillLinkPathsInFiles, + SkillNameMap, + SkillLinkPathMap, +} from '../skill-reference-rewriter' export type HandleFileCopyParams = { fileService: FileSystemService @@ -17,6 +22,7 @@ export type HandleFileCopyParams = { defaultBehavior: Behavior options?: SyncOptions skillNameMap?: SkillNameMap + skillLinkPathMap?: SkillLinkPathMap } /** @@ -34,6 +40,7 @@ export async function handleFileCopy(params: HandleFileCopyParams) { defaultBehavior, options, skillNameMap, + skillLinkPathMap, } = params const finalDest = await determineFinalDestination(fileService, destPath, source, normTarget) @@ -62,7 +69,25 @@ export async function handleFileCopy(params: HandleFileCopyParams) { options, }) - if (skillNameMap && skillNameMap.size > 0 && finalDest.endsWith('.md')) { + await applySkillRewrites(fileService, finalDest, skillNameMap, skillLinkPathMap) +} + +/** Applies skill-command and skill-link-path rewrites to a copied markdown file. */ +async function applySkillRewrites( + fileService: FileSystemService, + finalDest: string, + skillNameMap?: SkillNameMap, + skillLinkPathMap?: SkillLinkPathMap, +): Promise { + if (!finalDest.endsWith('.md')) return + if (skillNameMap && skillNameMap.size > 0) { await rewriteSkillReferencesInFiles({ fileService, files: [finalDest], skillNameMap }) } + if (skillLinkPathMap && skillLinkPathMap.size > 0) { + await rewriteSkillLinkPathsInFiles({ + fileService, + files: [finalDest], + linkMap: skillLinkPathMap, + }) + } } diff --git a/packages/content-ops/src/ops/copy/copy-types.ts b/packages/content-ops/src/ops/copy/copy-types.ts index 357678cd..158f8f89 100644 --- a/packages/content-ops/src/ops/copy/copy-types.ts +++ b/packages/content-ops/src/ops/copy/copy-types.ts @@ -1,11 +1,13 @@ -import type { SkillNameMap } from '../skill-reference-rewriter' +import type { SkillNameMap, SkillLinkPathMap } from '../skill-reference-rewriter' /** - * Result of a copy operation. Carries the skill name map when a - * transform copy renamed skills, so callers can chain reference rewrites. + * Result of a copy operation. Carries the skill name map when a transform copy + * renamed skills, so callers can chain reference rewrites, plus the matching + * link-path map so callers can chain SKILL.md cross-reference PATH rewrites. */ export type CopyPathOpsResult = { skillNameMap?: SkillNameMap + skillLinkPathMap?: SkillLinkPathMap } /** Naming transform options (flatten and/or prefix) applied during a copy. */ diff --git a/packages/content-ops/src/ops/copy/copyPathOps.ts b/packages/content-ops/src/ops/copy/copyPathOps.ts index 660595ab..4815735a 100644 --- a/packages/content-ops/src/ops/copy/copyPathOps.ts +++ b/packages/content-ops/src/ops/copy/copyPathOps.ts @@ -6,7 +6,7 @@ import { FileSystemService } from '../../file-system' import { Behavior } from '../behavior' import { setupPathOperation } from '../path-operation-helpers' import { isAbsolute } from 'path' -import { SkillNameMap } from '../skill-reference-rewriter' +import { SkillNameMap, SkillLinkPathMap } from '../skill-reference-rewriter' import { handleDirectoryCopy, HandleDirectoryCopyParams } from './copy-directory' import { handleFileCopy, HandleFileCopyParams } from './copy-file' import { copyDirectoryWithTransforms } from './copy-directory-transforms' @@ -24,6 +24,9 @@ type CopyPathOpsParams = { /** Pre-built skill name map from a previous copy (e.g., skills registry). * When provided, rewrites skill references in all copied .md files. */ skillNameMap?: SkillNameMap + /** Pre-built skill link-path map from a previous copy (e.g., skills registry). + * When provided, rewrites SKILL.md cross-reference paths in all copied .md files. */ + skillLinkPathMap?: SkillLinkPathMap } /** @@ -44,6 +47,7 @@ async function performCopyBasedOnType( folderBehavior?: Record options?: SyncOptions skillNameMap?: SkillNameMap + skillLinkPathMap?: SkillLinkPathMap }, ): Promise { if (stat.isDirectory()) { @@ -118,6 +122,7 @@ async function handleFileCopyForType(params: { defaultBehavior: Behavior options?: SyncOptions skillNameMap?: SkillNameMap + skillLinkPathMap?: SkillLinkPathMap }) { const fileCopyParams: HandleFileCopyParams = { fileService: params.fileService, @@ -130,6 +135,7 @@ async function handleFileCopyForType(params: { defaultBehavior: params.defaultBehavior, ...(params.options && { options: params.options }), ...(params.skillNameMap && { skillNameMap: params.skillNameMap }), + ...(params.skillLinkPathMap && { skillLinkPathMap: params.skillLinkPathMap }), } await handleFileCopy(fileCopyParams) } @@ -196,7 +202,8 @@ function prepareCopyPathOperation( } export async function copyPathOps(params: CopyPathOpsParams): Promise { - const { fileService, source, target, datasetRoot, options, skillNameMap } = params + const { fileService, source, target, datasetRoot, options, skillNameMap, skillLinkPathMap } = + params if (isAbsolute(source) || isAbsolute(target)) { throw createError({ type: 'INVALID_PATH', @@ -223,6 +230,7 @@ export async function copyPathOps(params: CopyPathOpsParams): Promise { '\n', ) expect(rewriteSkillReferences(input, map)).toBe( - ['Run `/pair-next` to start.', '```', '/next', '```', 'Then run /pair-process-implement.'].join( - '\n', - ), + [ + 'Run `/pair-next` to start.', + '```', + '/next', + '```', + 'Then run /pair-process-implement.', + ].join('\n'), ) }) @@ -147,9 +154,7 @@ describe('rewriteSkillReferences', () => { 'Compose /next.', ].join('\n') expect(rewriteSkillReferences(input, map)).toBe( - ['```text', 'pair install', '/next', '/implement', '```', 'Compose /pair-next.'].join( - '\n', - ), + ['```text', 'pair install', '/next', '/implement', '```', 'Compose /pair-next.'].join('\n'), ) }) @@ -328,3 +333,125 @@ describe('rewriteSkillReferencesInFiles', () => { expect(content).toBe('# No skill refs here') }) }) + +describe('buildSkillLinkPathMap', () => { + const opts = { flatten: true, prefix: 'pair' } + + it('maps a nested skill SKILL.md relative link to its installed .claude/skills path', () => { + const dirMappingFiles = new Map([ + ['capability/map-subdomains', []], + ['process/refine-story', []], + ]) + const map = buildSkillLinkPathMap(dirMappingFiles, opts) + expect(map.get('../.skills/capability/map-subdomains/SKILL.md')).toBe( + '../.claude/skills/pair-capability-map-subdomains/SKILL.md', + ) + expect(map.get('../.skills/process/refine-story/SKILL.md')).toBe( + '../.claude/skills/pair-process-refine-story/SKILL.md', + ) + }) + + it('skips bare top-level dirs with no category (e.g. next)', () => { + const dirMappingFiles = new Map([ + ['next', []], + ['capability/estimate', []], + ]) + const map = buildSkillLinkPathMap(dirMappingFiles, opts) + expect([...map.keys()]).toEqual(['../.skills/capability/estimate/SKILL.md']) + }) + + it('returns an empty map for an empty input', () => { + expect(buildSkillLinkPathMap(new Map(), opts).size).toBe(0) + }) +}) + +describe('rewriteSkillLinkPaths', () => { + const linkMap = new Map([ + [ + '../.skills/capability/map-subdomains/SKILL.md', + '../.claude/skills/pair-capability-map-subdomains/SKILL.md', + ], + [ + '../.skills/capability/map-contexts/SKILL.md', + '../.claude/skills/pair-capability-map-contexts/SKILL.md', + ], + ]) + + it('returns unchanged content when the map is empty', () => { + const content = '[x](../../.skills/capability/map-subdomains/SKILL.md)' + expect(rewriteSkillLinkPaths(content, new Map())).toBe(content) + }) + + it('rewrites a SKILL.md link path preserving the leading ../', () => { + const input = '[map-subdomains](../../.skills/capability/map-subdomains/SKILL.md)' + expect(rewriteSkillLinkPaths(input, linkMap)).toBe( + '[map-subdomains](../../.claude/skills/pair-capability-map-subdomains/SKILL.md)', + ) + }) + + it('preserves leading ../ regardless of depth', () => { + const input = '[x](../../../../.skills/capability/map-contexts/SKILL.md)' + expect(rewriteSkillLinkPaths(input, linkMap)).toBe( + '[x](../../../../.claude/skills/pair-capability-map-contexts/SKILL.md)', + ) + }) + + it('leaves a bare dataset directory path (prose, no SKILL.md) untouched', () => { + const input = 'new path: `.skills/capability/map-subdomains/`' + expect(rewriteSkillLinkPaths(input, linkMap)).toBe(input) + }) + + it('leaves a repo-relative prose file path (no leading ../) untouched', () => { + const input = '`packages/knowledge-hub/dataset/.skills/capability/map-subdomains/SKILL.md`' + expect(rewriteSkillLinkPaths(input, linkMap)).toBe(input) + }) + + it('is idempotent — a second pass is a no-op', () => { + const input = '(../../.skills/capability/map-subdomains/SKILL.md)' + const once = rewriteSkillLinkPaths(input, linkMap) + expect(rewriteSkillLinkPaths(once, linkMap)).toBe(once) + }) +}) + +describe('rewriteSkillLinkPathsInFiles', () => { + const linkMap = new Map([ + [ + '../.skills/capability/map-subdomains/SKILL.md', + '../.claude/skills/pair-capability-map-subdomains/SKILL.md', + ], + ]) + + it('rewrites link paths in .md files, skipping non-.md files', async () => { + const fileService = new InMemoryFileSystemService( + { + '/t/doc.md': '[a](../../.skills/capability/map-subdomains/SKILL.md)', + '/t/config.json': '"../../.skills/capability/map-subdomains/SKILL.md"', + }, + '/', + '/', + ) + + await rewriteSkillLinkPathsInFiles({ + fileService, + files: ['/t/doc.md', '/t/config.json'], + linkMap, + }) + + expect(await fileService.readFile('/t/doc.md')).toBe( + '[a](../../.claude/skills/pair-capability-map-subdomains/SKILL.md)', + ) + expect(await fileService.readFile('/t/config.json')).toBe( + '"../../.skills/capability/map-subdomains/SKILL.md"', + ) + }) + + it('does not write a file when no link paths match', async () => { + const fileService = new InMemoryFileSystemService( + { '/t/README.md': '# no skill links here' }, + '/', + '/', + ) + await rewriteSkillLinkPathsInFiles({ fileService, files: ['/t/README.md'], linkMap }) + expect(await fileService.readFile('/t/README.md')).toBe('# no skill links here') + }) +}) diff --git a/packages/content-ops/src/ops/skill-reference-rewriter.ts b/packages/content-ops/src/ops/skill-reference-rewriter.ts index c8f5c0f4..e8f241b3 100644 --- a/packages/content-ops/src/ops/skill-reference-rewriter.ts +++ b/packages/content-ops/src/ops/skill-reference-rewriter.ts @@ -27,12 +27,26 @@ import { transformPath } from './naming-transforms' /** Maps original (short) skill name → new (prefixed) skill name */ export type SkillNameMap = Map +/** + * Maps a dataset skill-directory substring (`.skills///`) to + * its installed equivalent (`.claude/skills//`). Used to + * convert SKILL.md cross-reference link PATHS in non-`.skills` copied files — + * the counterpart to `SkillNameMap`, which only covers the `/command` tokens. + */ +export type SkillLinkPathMap = Map + export type RewriteSkillRefsParams = { fileService: FileSystemService files: string[] skillNameMap: SkillNameMap } +export type RewriteSkillLinkPathsParams = { + fileService: FileSystemService + files: string[] + linkMap: SkillLinkPathMap +} + function escapeRegex(s: string): string { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } @@ -194,3 +208,80 @@ export async function rewriteSkillReferencesInFiles(params: RewriteSkillRefsPara } } } + +/** + * Builds a skill link-path map from the directory mapping collected during a + * flatten+prefix skills copy — the SAME `dirMappingFiles` used to build the + * `SkillNameMap`. Each nested (`category/name`) skill directory yields a + * mapping from its dataset `.skills///SKILL.md` link to the + * installed `.claude/skills//SKILL.md` link. + * + * Keyed on a RELATIVE-LINK form — `../.skills///SKILL.md` — not a + * bare path, deliberately, so it matches only actual markdown links and never + * PROSE that mentions a dataset path: + * - a bare directory path appears in prose describing the dataset layout + * (e.g. "new path: `.skills/capability/foo/`") — no `SKILL.md`, no `../`; + * - a repo-relative file path appears in prose too (e.g. + * "`packages/knowledge-hub/dataset/.skills/process/foo/SKILL.md`") — has + * `SKILL.md` but is preceded by `dataset/`, not `../`. + * Only a real relative link (`](../../.skills///SKILL.md)`) carries + * a leading `../`, since `.skills` sits at the repo root and every referencing + * file is nested under it. Matching the single leading `../` (a substring of + * `../../…`, `../../../…`, etc.) redirects the link while preserving all + * additional `../` segments, so it works at any file depth. Bare top-level + * dirs (e.g. `next`, no category) are skipped — no SKILL.md cross-reference. + * + * Contract: converts only a skill's `SKILL.md` entrypoint link. Deep links to + * other files inside a skill dir (e.g. `references/*.md`) are NOT converted — + * none exist in the KB today; add per-file mappings here if that changes. + */ +export function buildSkillLinkPathMap( + dirMappingFiles: Map, + transformOpts: { flatten?: boolean; prefix?: string }, +): SkillLinkPathMap { + const map: SkillLinkPathMap = new Map() + for (const originalSubDir of dirMappingFiles.keys()) { + if (!originalSubDir.includes('/')) continue + const transformed = transformPath(originalSubDir, transformOpts) + map.set(`../.skills/${originalSubDir}/SKILL.md`, `../.claude/skills/${transformed}/SKILL.md`) + } + return map +} + +/** + * Rewrites skill SKILL.md link paths in content via plain substring + * replacement, longest key first (so a longer path is never pre-empted by a + * prefix of it). Idempotent: an installed path (`.claude/skills/...`) contains + * no source `.skills//` substring, so a second pass is a no-op. + */ +export function rewriteSkillLinkPaths(content: string, linkMap: SkillLinkPathMap): string { + if (linkMap.size === 0) return content + + const sorted = [...linkMap.entries()].sort((a, b) => b[0].length - a[0].length) + let result = content + for (const [from, to] of sorted) { + result = result.split(from).join(to) + } + return result +} + +/** + * Rewrites skill link paths in all provided .md files. + * Reads each file, applies rewriteSkillLinkPaths, writes back if changed. + */ +export async function rewriteSkillLinkPathsInFiles( + params: RewriteSkillLinkPathsParams, +): Promise { + const { fileService, files, linkMap } = params + if (linkMap.size === 0) return + + for (const filePath of files) { + if (!filePath.endsWith('.md')) continue + const content = await fileService.readFile(filePath) + const rewritten = rewriteSkillLinkPaths(content, linkMap) + if (rewritten !== content) { + await fileService.writeFile(filePath, rewritten) + logger.info(`Skill link-path rewriter: updated links in ${filePath}`) + } + } +} diff --git a/packages/content-ops/src/ops/validatePathOps.ts b/packages/content-ops/src/ops/validatePathOps.ts index e8a57434..bbf4dfcd 100644 --- a/packages/content-ops/src/ops/validatePathOps.ts +++ b/packages/content-ops/src/ops/validatePathOps.ts @@ -18,7 +18,14 @@ type ProcessingResult = { export async function validatePathOps( fileService: FileSystemService, - config: { datasetRoot: string; errorsPath: string; exclusionList: string[] }, + config: { + datasetRoot: string + errorsPath: string + exclusionList: string[] + /** Read-only: report errors/normalizations but never write fixes back. + * Used by the link gate over the generated root tree. */ + checkOnly?: boolean + }, ) { const { datasetRoot, errorsPath } = config if (!datasetRoot) throw new Error('datasetRoot is required') @@ -34,7 +41,7 @@ export async function validatePathOps( fileService, ) - const finalLogs = generateFinalReport(logs, processingResult) + const finalLogs = generateFinalReport(logs, processingResult, config.checkOnly) return { logs: finalLogs, @@ -56,7 +63,7 @@ async function prepareData(datasetRoot: string, fileService: FileSystemService) async function processAllFiles( mdFiles: string[], datasetFolders: string[], - config: { datasetRoot: string; exclusionList: string[] }, + config: { datasetRoot: string; exclusionList: string[]; checkOnly?: boolean }, fileService: FileSystemService, ): Promise { let allErrors: ErrorLog[] = [] @@ -71,6 +78,7 @@ async function processAllFiles( docsFolders: datasetFolders, datasetRoot: config.datasetRoot, exclusionList: config.exclusionList, + ...(config.checkOnly !== undefined && { checkOnly: config.checkOnly }), }, fileService, ) @@ -112,7 +120,14 @@ async function handleErrorsAndLogs( return logs } -function generateFinalReport(logs: string[], processingResult: ProcessingResult): string[] { +function generateFinalReport( + logs: string[], + processingResult: ProcessingResult, + checkOnly?: boolean, +): string[] { + // In read-only (checkOnly) mode nothing is written, so patch/normalize + // counts would be misleading ("Normalized: 4" while the file is untouched). + if (checkOnly) return logs logs.push(`Patched links: ${processingResult.patchedLinks}`) logs.push(`Normalized relative links: ${processingResult.normalizedRelLinks}`) logs.push(`Normalized full links: ${processingResult.normalizedFullLinks}`) @@ -121,7 +136,12 @@ function generateFinalReport(logs: string[], processingResult: ProcessingResult) export async function validateAndFixFileLinks( file: string, - config: { docsFolders: string[]; datasetRoot: string; exclusionList: string[] }, + config: { + docsFolders: string[] + datasetRoot: string + exclusionList: string[] + checkOnly?: boolean + }, fileService: FileSystemService, ) { const content = await fileService.readFile(file) @@ -136,6 +156,7 @@ export async function validateAndFixFileLinks( (links, _content, lines) => generateReplacements(links, _content, lines, { file, config, fileService, errors }), fileService, + { checkOnly: config.checkOnly ?? false }, ) const finalContent = result.content diff --git a/packages/knowledge-hub/.gitignore b/packages/knowledge-hub/.gitignore index 519a07ee..7d386c7a 100644 --- a/packages/knowledge-hub/.gitignore +++ b/packages/knowledge-hub/.gitignore @@ -1,2 +1,3 @@ ./ -errors.txt \ No newline at end of file +errors.txt +errors-root.txt \ No newline at end of file diff --git a/packages/knowledge-hub/dataset/.pair/knowledge/skills-guide.md b/packages/knowledge-hub/dataset/.pair/knowledge/skills-guide.md index 6fd1f004..ab5215cf 100644 --- a/packages/knowledge-hub/dataset/.pair/knowledge/skills-guide.md +++ b/packages/knowledge-hub/dataset/.pair/knowledge/skills-guide.md @@ -151,11 +151,11 @@ Each skill directory contains a `SKILL.md` file with YAML frontmatter (`name` + Process skills compose capability skills with optional graceful degradation: ```text -/implement -├── /verify-quality (required) -├── /record-decision (required) -├── /assess-stack (optional — warns if missing) -└── /verify-adoption (optional — warns if missing) +/pair-process-implement +├── /pair-capability-verify-quality (required) +├── /pair-capability-record-decision (required) +├── /pair-capability-assess-stack (optional — warns if missing) +└── /pair-capability-verify-adoption (optional — warns if missing) ``` Optional skills degrade gracefully: if not installed, the process skill warns and continues without blocking. @@ -197,7 +197,7 @@ Every row above except the two `(planned — #242)`/`(planned — #230)` entries **map-subdomains, map-contexts: process → capability (D24, #246)** - Old path: `.skills/process/map-subdomains/`, `.skills/process/map-contexts/` — new path: `.skills/capability/map-subdomains/`, `.skills/capability/map-contexts/`. -- Installed command names change accordingly: `/pair-process-map-subdomains` → `/pair-capability-map-subdomains`; `/pair-process-map-contexts` → `/pair-capability-map-contexts`. Unprefixed dataset command names (`/map-subdomains`, `/map-contexts`) are unchanged. +- Installed command names change accordingly: `/pair-process-map-subdomains` → `/pair-capability-map-subdomains`; `/pair-process-map-contexts` → `/pair-capability-map-contexts`. Unprefixed dataset command names (`map-subdomains`, `map-contexts`) are unchanged. - Invocation contract changed: `$scope` is now **required** and means "items/areas the caller touched" (not `all`/`single`). Full-catalog `$scope: all` is now bootstrap-only. - New behavior: graceful "system areas" fallback when no DDD artifacts exist; `Volatility` field on subdomains; per-relationship strength/distance/volatility assessment with an approval gate on unbalanced+volatile relationships (see `subdomain-template.md`, `bounded-context-template.md`). - No standalone process step or how-to remains for domain mapping — how-to guides 04 (define-subdomains) and 05 (define-bounded-contexts) were removed; each caller's own how-to (02, 03, 06, 08, 09) now references the capability inline at the point it's invoked, scoped per the [Callers Matrix](#callers-matrix-scoped-capabilities) above. The capability's own `SKILL.md` ([map-subdomains](../../.skills/capability/map-subdomains/SKILL.md), [map-contexts](../../.skills/capability/map-contexts/SKILL.md)) is the canonical reference for its algorithm, fallback behavior, and templates. @@ -206,7 +206,7 @@ Every row above except the two `(planned — #242)`/`(planned — #230)` entries - Rationale: `assess-*` conflated two operations. The corpus now uses three distinct verbs — **`verify-*`** (conformance pass/fail), **`assess-*`** (evaluate options + PROPOSE an adoption choice — the 8 decision skills), **`analyze-*`** (analyze + REPORT only, never blocks, proposes no adoption decision). The two report skills were misfiled under `assess-*` and are renamed to the `analyze-*` verb. Recorded as ADL `2026-07-13-skill-naming-verb-taxonomy.md` in the project decision log. - Old path: `.skills/capability/assess-debt/`, `.skills/capability/assess-code-quality/` — new path: `.skills/capability/analyze-debt/`, `.skills/capability/analyze-code-quality/`. -- Installed command names change accordingly: `/pair-capability-assess-debt` → `/pair-capability-analyze-debt`; `/pair-capability-assess-code-quality` → `/pair-capability-analyze-code-quality`. Unprefixed dataset command names change too: `/assess-debt` → `/analyze-debt`, `/assess-code-quality` → `/analyze-code-quality`. +- Installed command names change accordingly: `/pair-capability-assess-debt` → `/pair-capability-analyze-debt`; `/pair-capability-assess-code-quality` → `/pair-capability-analyze-code-quality`. Unprefixed dataset command names change too: `assess-debt` → `analyze-debt`, `assess-code-quality` → `analyze-code-quality`. - No behavior change: both remain output-only report producers (per ADR-009); only the verb/name changed. Skill count stays 35. ## How Skills Relate to How-To Guides diff --git a/packages/knowledge-hub/src/conformance/secret-scanning.test.ts b/packages/knowledge-hub/src/conformance/secret-scanning.test.ts index 2b83c6c7..2f277b19 100644 --- a/packages/knowledge-hub/src/conformance/secret-scanning.test.ts +++ b/packages/knowledge-hub/src/conformance/secret-scanning.test.ts @@ -1,6 +1,8 @@ import { describe, it, expect } from 'vitest' import { readFileSync } from 'fs' import { join } from 'path' +import { rewriteSkillReferences, rewriteSkillLinkPaths } from '@pair/content-ops' +import { buildDatasetSkillNameMap, buildSkillLinkPathMap } from '../tools/skills-guide-mirror' const SECRET_SCANNING = readFileSync( join( @@ -98,24 +100,31 @@ describe('gitleaks-example.toml', () => { }) /** - * Root-mirror byte parity for THIS story's 3 files specifically. + * Root-mirror parity for THIS story's files specifically. * * Scoped narrowly (not a general dataset<->root parity gate — see PR #341's * round-3 review): a root mirror of secret-scanning.md went missing entirely * in round 2, and gitleaks-example.toml's root mirror had spurious extra * blank lines in round 3. Both slipped through undetected because no * automated check compares the root .pair/knowledge/ mirror against the - * dataset for these files. This locks in exact parity for the 3 files this + * dataset for these files. This locks in exact parity for the files this * story actually mirrors, so neither regression class can recur silently. * - * A repo-wide generalization of this check (every dataset<->root mirrored - * file, byte-identical) is a larger, separate cleanup: as of this writing the - * root/dataset trees already have ~30 pre-existing, unrelated byte - * mismatches (README.md/how-to/skill-conventions files never kept in sync) - * that a blanket gate would need to resolve first. That's tracked as - * follow-up tech debt, not fixed here. + * Parity here is `root === realTransform(dataset)`, NOT byte-identity: the + * `pair update` copy pipeline applies the real content-ops skill transforms + * (`rewriteSkillReferences` for `/command` tokens, `rewriteSkillLinkPaths` + * for SKILL.md link paths) to every `.pair/knowledge/` file. secret-scanning.md + * references `/setup-gates` and `/assess-security`, which are prefixed in the + * installed root — so its mirror is the transform of the dataset, not a byte + * copy. The other files carry no skill refs, so their transform is identity. */ describe('root .pair/knowledge/ mirror byte parity (this story files)', () => { + const SKILLS_DIR = join(REPO_ROOT, 'packages/knowledge-hub/dataset/.skills') + const skillNameMap = buildDatasetSkillNameMap(SKILLS_DIR) + const linkPathMap = buildSkillLinkPathMap(SKILLS_DIR) + const realTransform = (content: string): string => + rewriteSkillLinkPaths(rewriteSkillReferences(content, skillNameMap), linkPathMap) + const MIRRORED_FILES = [ 'guidelines/quality-assurance/security/secret-scanning.md', 'guidelines/quality-assurance/security/README.md', @@ -123,12 +132,15 @@ describe('root .pair/knowledge/ mirror byte parity (this story files)', () => { 'guidelines/quality-assurance/quality-model.md', ] as const - it.each(MIRRORED_FILES)('%s is byte-identical between dataset and root mirror', relPath => { - const datasetContent = readFileSync( - join(__dirname, '../../dataset/.pair/knowledge', relPath), - 'utf-8', - ) - const rootContent = readFileSync(join(REPO_ROOT, '.pair/knowledge', relPath), 'utf-8') - expect(rootContent).toBe(datasetContent) - }) + it.each(MIRRORED_FILES)( + '%s root mirror equals the dataset run through the real transform', + relPath => { + const datasetContent = readFileSync( + join(__dirname, '../../dataset/.pair/knowledge', relPath), + 'utf-8', + ) + const rootContent = readFileSync(join(REPO_ROOT, '.pair/knowledge', relPath), 'utf-8') + expect(rootContent).toBe(realTransform(datasetContent)) + }, + ) }) diff --git a/packages/knowledge-hub/src/tools/check-broken-links.ts b/packages/knowledge-hub/src/tools/check-broken-links.ts index 28f5b537..25c918cd 100644 --- a/packages/knowledge-hub/src/tools/check-broken-links.ts +++ b/packages/knowledge-hub/src/tools/check-broken-links.ts @@ -4,7 +4,17 @@ import { validatePathOps } from '@pair/content-ops' const ROOT = join(__dirname, '..', '..') const DATASET = join(ROOT, 'dataset') +// Repo root is two levels above this package (packages/knowledge-hub -> repo root). +const REPO_ROOT = join(ROOT, '..', '..') +// The GENERATED knowledge-base mirror in the root install tree. `pair update` +// regenerates it from the dataset (mirror behavior); its links must resolve, +// notably .pair/knowledge/skills-guide.md -> ../../.claude/skills/pair-*/SKILL.md. +// Scoped to knowledge/ (not all of .pair/): .pair/adoption/ is user-owned +// `add`-behavior content pair update never regenerates, with its own, +// pre-existing link state that is out of scope for this generated-tree gate. +const ROOT_KNOWLEDGE = join(REPO_ROOT, '.pair', 'knowledge') const ERRORS_PATH = join(ROOT, 'errors.txt') +const ROOT_ERRORS_PATH = join(ROOT, 'errors-root.txt') const EXCLUSION_LIST: string[] = [ '.pair/adoption/product/backlog/', '01-initiatives/2025/core-data-pipeline.md', @@ -30,32 +40,67 @@ export function parseOptions(arg?: string) { } } -type CheckLinksConfig = { datasetRoot: string; errorsPath: string; exclusionList: string[] } +type CheckLinksConfig = { + datasetRoot: string + errorsPath: string + exclusionList: string[] + checkOnly?: boolean +} async function runCheckLinks(config: CheckLinksConfig) { return validatePathOps(fileSystemService, config) } +/** + * Runs the link check across every configured root, printing each root's logs. + * Exits non-zero if ANY root has unresolved links (LINK TARGET NOT FOUND / + * BAD LINK FORMAT) — this is what makes it a CI gate: a broken generated link + * in the root .pair/ tree fails the build, not just the dataset source. + */ +async function runAllChecks(roots: CheckLinksConfig[]): Promise { + let hadErrors = false + for (const cfg of roots) { + console.log(`\nChecking markdown links under: ${cfg.datasetRoot}`) + const result = await runCheckLinks(cfg) + result.logs.forEach((msg: string) => console.log(msg)) + if (result.allErrors.length > 0) hadErrors = true + } + if (hadErrors) process.exit(1) +} + if (require.main === module) { const [datasetRootArg, errorsPathArg, optionsArg] = process.argv.slice(2) - - const datasetRoot = datasetRootArg || DATASET - const errorsPath = errorsPathArg || ERRORS_PATH const options = parseOptions(optionsArg) - const cfg: CheckLinksConfig = Object.assign( - { datasetRoot, errorsPath, exclusionList: EXCLUSION_LIST }, - options || {}, - ) as CheckLinksConfig - - runCheckLinks(cfg) - .then(result => { - result.logs.forEach((msg: string) => console.log(msg)) - }) - .catch(err => { - console.error('Error checking markdown links:', err) - process.exit(1) - }) + // With an explicit root arg, scan just that root (backward compatible). + // With no arg (the `check:links` script), gate BOTH the dataset source AND + // the generated root .pair/ tree. + const roots: CheckLinksConfig[] = datasetRootArg + ? [ + { + datasetRoot: datasetRootArg, + errorsPath: errorsPathArg || ERRORS_PATH, + exclusionList: EXCLUSION_LIST, + }, + ] + : [ + { datasetRoot: DATASET, errorsPath: ERRORS_PATH, exclusionList: EXCLUSION_LIST }, + // Read-only gate over the generated KB mirror: report broken links, + // never rewrite the generated files (a gate must not mutate its target). + { + datasetRoot: ROOT_KNOWLEDGE, + errorsPath: ROOT_ERRORS_PATH, + exclusionList: EXCLUSION_LIST, + checkOnly: true, + }, + ] + + const configs = roots.map(r => Object.assign(r, options || {}) as CheckLinksConfig) + + runAllChecks(configs).catch(err => { + console.error('Error checking markdown links:', err) + process.exit(1) + }) } else { // allow importing the module without executing } diff --git a/packages/knowledge-hub/src/tools/skills-guide-mirror.test.ts b/packages/knowledge-hub/src/tools/skills-guide-mirror.test.ts index c9a22d54..823b377c 100644 --- a/packages/knowledge-hub/src/tools/skills-guide-mirror.test.ts +++ b/packages/knowledge-hub/src/tools/skills-guide-mirror.test.ts @@ -1,13 +1,13 @@ import { describe, it, expect } from 'vitest' import { readFileSync } from 'fs' import { join } from 'path' +import { rewriteSkillReferences, rewriteSkillLinkPaths } from '@pair/content-ops' import { collectSkillDirs, buildDatasetSkillNameMap, buildSkillLinkPathMap, applyKnownMirrorTransforms, extractLineContaining, - reconstructFullMirror, } from './skills-guide-mirror' // packages/knowledge-hub/src/tools -> repo root @@ -42,11 +42,11 @@ describe('buildDatasetSkillNameMap', () => { describe('buildSkillLinkPathMap', () => { it('maps dataset-source SKILL.md links to their installed-path equivalents', () => { const map = buildSkillLinkPathMap(SKILLS_DIR) - expect(map.get('../../.skills/capability/map-subdomains/SKILL.md')).toBe( - '../../.claude/skills/pair-capability-map-subdomains/SKILL.md', + expect(map.get('../.skills/capability/map-subdomains/SKILL.md')).toBe( + '../.claude/skills/pair-capability-map-subdomains/SKILL.md', ) - expect(map.get('../../.skills/capability/map-contexts/SKILL.md')).toBe( - '../../.claude/skills/pair-capability-map-contexts/SKILL.md', + expect(map.get('../.skills/capability/map-contexts/SKILL.md')).toBe( + '../.claude/skills/pair-capability-map-contexts/SKILL.md', ) }) @@ -109,32 +109,37 @@ describe('skills-guide.md dataset <-> root mirror consistency (critical sections }) /** - * Whole-file coverage: `reconstructFullMirror` applies the real mechanical - * transform to the ENTIRE dataset content, then overrides the two known - * deliberate-exception spans (see `skills-guide-mirror.ts` module docs) to - * their correct behavior. If the root mirror has drifted ANYWHERE — not - * just on the 5 anchored paragraphs above — this fails. + * Whole-file coverage: applies the REAL content-ops transform + * (`rewriteSkillReferences` + `rewriteSkillLinkPaths`) — the exact pair the + * `pair update` pipeline runs — to the ENTIRE dataset content, with NO manual + * exceptions. The dataset file is authored to be transform-correct (prefixed + * Composition Pattern diagram; slash-free self-referential "Unprefixed dataset + * command names" phrases), so this reproduces the root mirror byte-for-byte. + * If the root mirror has drifted ANYWHERE — not just on the 5 anchored + * paragraphs above — this fails. * * On failure, check the targeted-anchor tests above first: if one of them * also fails, its message pinpoints the paragraph directly. If only this - * test fails, the drift is somewhere else in the file — diff - * `.pair/knowledge/skills-guide.md` against the dataset file's real - * transform output (see `reconstructFullMirror`) to find it. + * test fails, the drift is somewhere else in the file — regenerate the root + * mirror with `node apps/pair-cli/dist/cli.js update` (or, if the dataset + * itself has a transform-incorrect span, fix the dataset so the mechanical + * transform reproduces the intended installed output). */ describe('skills-guide.md dataset <-> root mirror consistency (whole file)', () => { - it('root mirror is byte-for-byte reproducible from the dataset via the real transform + the 2 known exceptions', () => { + it('root mirror is byte-for-byte reproducible from the dataset via the real content-ops transform', () => { const datasetContent = readFileSync(DATASET_SKILLS_GUIDE, 'utf-8') const rootContent = readFileSync(ROOT_MIRROR_SKILLS_GUIDE, 'utf-8') const skillNameMap = buildDatasetSkillNameMap(SKILLS_DIR) const linkPathMap = buildSkillLinkPathMap(SKILLS_DIR) - const reconstructed = reconstructFullMirror(datasetContent, skillNameMap, linkPathMap) + const reconstructed = rewriteSkillLinkPaths( + rewriteSkillReferences(datasetContent, skillNameMap), + linkPathMap, + ) // If this fails, `.pair/knowledge/skills-guide.md` has drifted from the - // canonical dataset file somewhere in the whole file. Reconcile the - // root mirror to match the real transform's output (or, if a NEW - // deliberate exception is legitimately needed beyond the 2 already - // modeled in `reconstructFullMirror`, add it there explicitly). + // canonical dataset file's real transform output. Regenerate the root + // mirror via `pair update`. expect(reconstructed).toBe(rootContent) }) }) diff --git a/packages/knowledge-hub/src/tools/skills-guide-mirror.ts b/packages/knowledge-hub/src/tools/skills-guide-mirror.ts index 9cb13fe2..82fda503 100644 --- a/packages/knowledge-hub/src/tools/skills-guide-mirror.ts +++ b/packages/knowledge-hub/src/tools/skills-guide-mirror.ts @@ -1,55 +1,33 @@ /** * Mirror-consistency helpers for `.pair/knowledge/skills-guide.md`. * - * That root-level file is a MANUALLY maintained copy of the canonical - * `packages/knowledge-hub/dataset/.pair/knowledge/skills-guide.md` — it is - * NOT one of the `transfer-dataset` pipeline's copy targets (see - * `packages/knowledge-hub/package.json`'s `transfer:dataset` script and its - * callers), so the install-time skill-reference/link rewriting that keeps - * `.claude/skills/**` in sync never touches it. It has drifted from the - * canonical source twice (flagged in PR #319's review, never actioned; - * fixed again in PR #336) because nothing enforced the sync. + * The root-level file is GENERATED from the canonical + * `packages/knowledge-hub/dataset/.pair/knowledge/skills-guide.md` by + * `pair update`: the copy pipeline applies the exact same content-ops + * transforms — `rewriteSkillReferences` (the `/command` token rewrite) and + * `rewriteSkillLinkPaths` (the SKILL.md cross-reference PATH rewrite) — that + * these helpers reproduce here from the real dataset `.skills/` tree. Because + * the dataset file is authored to be transform-correct (the Composition + * Pattern diagram already carries prefixed names; the self-referential + * "Unprefixed dataset command names" phrases carry no leading-slash token), + * the mechanical transform reproduces the root mirror byte-for-byte with NO + * manual exceptions — that whole-file equality is what + * `skills-guide-mirror.test.ts` asserts. * - * These helpers reconstruct, from the real dataset `.skills/` tree, the same - * `skillNameMap` the real transfer pipeline computes — via the canonical - * `buildSkillNameMap`/`transformPath`/`rewriteSkillReferences` exported by - * `@pair/content-ops` (see `packages/content-ops/src/ops/skill-reference-rewriter.ts` - * and `packages/content-ops/src/ops/copy/copy-directory-transforms.ts`, - * which use the exact same functions in production). `buildSkillLinkPathMap` - * reproduces the one further conversion no exported function covers: the - * dataset's own `SKILL.md` cross-reference paths - * (`../../.skills/capability//SKILL.md`) must become installed paths - * (`../../.claude/skills//SKILL.md`) in the root mirror — a - * conversion that normally only happens inside the full copy pipeline - * (`rewriteLinksAfterTransform`), which skills-guide.md never goes through. - * It's still derived from `transformPath`, not a parallel name-guessing - * regex. - * - * IMPORTANT — the raw mechanical transform does NOT reproduce the whole file - * byte-for-byte: skills-guide.md contains exactly two spans where a human - * curator deliberately deviates from the mechanical rule: - * 1. The "Composition Pattern" ASCII diagram lives inside a ```text fence - * but the author DOES prefix its skill names (unlike - * `rewriteSkillReferences`'s default, which treats fenced code as - * inert example text and skips it). - * 2. Several "Unprefixed dataset command names (...)" phrases in Migration - * Notes are self-referential — they're TALKING ABOUT the unprefixed - * form itself, so they must stay unprefixed in both the dataset and - * the root mirror, even though they're plain prose the mechanical rule - * would otherwise rewrite. - * `reconstructFullMirror` models both exceptions as explicit, narrow - * overrides applied on top of the mechanical pass (not by skipping - * validation there) — see its own docs — so `skills-guide-mirror.test.ts` - * can assert whole-file byte-for-byte equality against the root mirror, - * not just a handful of anchored paragraphs. + * The `skillNameMap` is built via the canonical `buildSkillNameMap` / + * `transformPath` exported by `@pair/content-ops` (the same functions the real + * copy pipeline uses). `buildSkillLinkPathMap` derives the SKILL.md link-path + * map from `transformPath` as well — not a parallel name-guessing regex. */ import { readdirSync } from 'fs' import { join, relative, sep } from 'path' import { buildSkillNameMap, + buildSkillLinkPathMap as prodBuildSkillLinkPathMap, rewriteSkillReferences, - transformPath, + rewriteSkillLinkPaths, type SkillNameMap, + type SkillLinkPathMap, } from '@pair/content-ops' /** @@ -97,43 +75,30 @@ export function buildDatasetSkillNameMap(skillsDir: string): SkillNameMap { } /** - * Maps each dataset-source `SKILL.md` cross-reference path (e.g. - * `../../.skills/capability/map-subdomains/SKILL.md`) to its installed - * equivalent (e.g. - * `../../.claude/skills/pair-capability-map-subdomains/SKILL.md`), for every - * nested (`category/name`) skill directory found under `skillsDir`. Built - * directly from `transformPath` (the same canonical function the real - * transfer pipeline calls) — not a parallel name-guessing regex. See module - * docs for why this one conversion needs reproducing here instead of reusing - * a higher-level pipeline function. + * Skill SKILL.md link-path map (`../.skills///SKILL.md` → + * `../.claude/skills//SKILL.md`) for every nested skill dir under + * `skillsDir`. Delegates to the PRODUCTION `buildSkillLinkPathMap` (same + * function `pair update` uses) so a bug in the real map construction is caught + * by the conformance test, not masked by a parallel implementation. */ -export function buildSkillLinkPathMap(skillsDir: string): Map { - const linkMap = new Map() - for (const dir of collectSkillDirs(skillsDir)) { - if (!dir.includes('/')) continue // e.g. "next" — no SKILL.md cross-reference use case today - const transformed = transformPath(dir, { flatten: true, prefix: 'pair' }) - linkMap.set(`../../.skills/${dir}/SKILL.md`, `../../.claude/skills/${transformed}/SKILL.md`) - } - return linkMap +export function buildSkillLinkPathMap(skillsDir: string): SkillLinkPathMap { + const dirMappingFiles = new Map(collectSkillDirs(skillsDir).map(d => [d, []])) + return prodBuildSkillLinkPathMap(dirMappingFiles, { flatten: true, prefix: 'pair' }) } /** - * Applies both known mechanical transforms (skill-reference `/verb` rewrite, - * then SKILL.md link-path rewrite) to an arbitrary snippet of dataset - * content. See module docs for the two spans where the real file - * deliberately deviates from this mechanical rule — callers should apply - * this only to text outside those spans (e.g. via `extractLineContaining`). + * Applies the real content-ops mirror transform to a snippet of dataset + * content: `rewriteSkillReferences` (the `/command` token rewrite), then + * `rewriteSkillLinkPaths` (the SKILL.md link-path rewrite). This is the exact + * transform the `pair update` pipeline applies to the installed skills-guide.md, + * with no manual exceptions. */ export function applyKnownMirrorTransforms( text: string, skillNameMap: SkillNameMap, linkPathMap: Map, ): string { - let result = rewriteSkillReferences(text, skillNameMap) - for (const [sourceLink, installedLink] of linkPathMap) { - result = result.split(sourceLink).join(installedLink) - } - return result + return rewriteSkillLinkPaths(rewriteSkillReferences(text, skillNameMap), linkPathMap) } /** @@ -149,133 +114,3 @@ export function extractLineContaining(content: string, anchor: string): string { } return line } - -/** Heading that immediately precedes the one fence exempt from fence-skip (exception 1). */ -const COMPOSITION_PATTERN_HEADING = '## Composition Pattern' - -/** Phrase marking the self-referential unprefixed-dataset-command-name spans (exception 2). */ -const MIGRATION_NOTES_UNPREFIXED_ANCHOR = 'Unprefixed dataset command names' - -/** - * Finds the ``` -fenced block that immediately follows a given heading line - * (matched by exact trimmed text), returning the line indices of its open - * and close fence markers (both inclusive) in `lines`. Throws if the heading - * or a following fence isn't found, so a future reflow/rewording fails this - * loudly instead of silently operating on the wrong block. - */ -function findFencedBlockAfterHeading( - lines: readonly string[], - heading: string, -): { open: number; close: number } { - const headingIdx = lines.findIndex(l => l.trim() === heading) - if (headingIdx === -1) { - throw new Error(`findFencedBlockAfterHeading: heading ${JSON.stringify(heading)} not found`) - } - let open = -1 - for (let i = headingIdx + 1; i < lines.length; i++) { - if (/^ {0,3}```/.test(lines[i]!)) { - open = i - break - } - } - if (open === -1) { - throw new Error( - `findFencedBlockAfterHeading: no fence found after heading ${JSON.stringify(heading)}`, - ) - } - let close = -1 - for (let i = open + 1; i < lines.length; i++) { - if (/^ {0,3}```\s*$/.test(lines[i]!)) { - close = i - break - } - } - if (close === -1) { - throw new Error( - `findFencedBlockAfterHeading: unterminated fence after heading ${JSON.stringify(heading)}`, - ) - } - return { open, close } -} - -/** - * Exception 1 override: the "Composition Pattern" fence is the one fence - * skills-guide.md's author DOES prefix-transform (see module docs), unlike - * `rewriteSkillReferences`'s default fence-skip. Re-derives the block's - * transformed content directly from the dataset's own (short-form) fence - * lines — `targetLines` has this block untouched up to this point, since the - * mechanical pass that produced it skipped every fence, this one included — - * and splices the transformed lines back into `targetLines` in place. - */ -function applyCompositionPatternFenceException( - datasetLines: readonly string[], - targetLines: readonly string[], - skillNameMap: SkillNameMap, - linkPathMap: Map, -): string[] { - const { open, close } = findFencedBlockAfterHeading(datasetLines, COMPOSITION_PATTERN_HEADING) - const innerDataset = datasetLines.slice(open + 1, close).join('\n') - const innerTransformed = applyKnownMirrorTransforms(innerDataset, skillNameMap, linkPathMap) - const result = [...targetLines] - result.splice(open + 1, close - open - 1, ...innerTransformed.split('\n')) - return result -} - -/** - * Exception 2 override: from the `MIGRATION_NOTES_UNPREFIXED_ANCHOR` phrase - * to end-of-line, Migration Notes prose is TALKING ABOUT the unprefixed - * dataset command form itself (see module docs) and must stay verbatim — - * copied byte-for-byte from the dataset line, not reprocessed. Only the - * portion of the line BEFORE the anchor phrase gets the real mechanical - * transform (re-applied here directly from the dataset line, independent of - * whatever `targetLines` already holds for that line). - */ -function restoreUnprefixedDatasetCommandNameMentions( - datasetLines: readonly string[], - targetLines: readonly string[], - skillNameMap: SkillNameMap, - linkPathMap: Map, -): string[] { - return targetLines.map((line, i) => { - const datasetLine = datasetLines[i] - if (datasetLine === undefined) return line - const anchorIdx = datasetLine.indexOf(MIGRATION_NOTES_UNPREFIXED_ANCHOR) - if (anchorIdx === -1) return line - const before = datasetLine.slice(0, anchorIdx) - const verbatimFromAnchor = datasetLine.slice(anchorIdx) - return applyKnownMirrorTransforms(before, skillNameMap, linkPathMap) + verbatimFromAnchor - }) -} - -/** - * Reconstructs the FULL root-mirror content from the dataset content: the - * mechanical transform (`applyKnownMirrorTransforms`) applied everywhere, - * with the two known deliberate-exception spans (see module docs) then - * explicitly overridden to their correct, narrowly-scoped behavior — not by - * skipping validation on those spans, but by modeling exactly what should - * happen there. The result is byte-for-byte equal to - * `.pair/knowledge/skills-guide.md` iff no OTHER, undocumented manual - * divergence exists — that equality is exactly what - * `skills-guide-mirror.test.ts`'s full-file test asserts. - */ -export function reconstructFullMirror( - datasetContent: string, - skillNameMap: SkillNameMap, - linkPathMap: Map, -): string { - const datasetLines = datasetContent.split('\n') - const baseTransformed = applyKnownMirrorTransforms(datasetContent, skillNameMap, linkPathMap) - const withFenceException = applyCompositionPatternFenceException( - datasetLines, - baseTransformed.split('\n'), - skillNameMap, - linkPathMap, - ) - const withMigrationNotesException = restoreUnprefixedDatasetCommandNameMentions( - datasetLines, - withFenceException, - skillNameMap, - linkPathMap, - ) - return withMigrationNotesException.join('\n') -} diff --git a/packages/knowledge-hub/src/tools/transfer-dataset.ts b/packages/knowledge-hub/src/tools/transfer-dataset.ts index 26cc298f..ebc2b9e0 100644 --- a/packages/knowledge-hub/src/tools/transfer-dataset.ts +++ b/packages/knowledge-hub/src/tools/transfer-dataset.ts @@ -2,7 +2,12 @@ import { join, resolve } from 'path' import { fileSystemService } from '@pair/content-ops' import { movePathOps, copyPathOps } from '@pair/content-ops' import { stripAllMarkers, applyTransformCommands, validateMarkers } from '@pair/content-ops' -import type { SyncOptions, SkillNameMap, CopyPathOpsResult } from '@pair/content-ops' +import type { + SyncOptions, + SkillNameMap, + SkillLinkPathMap, + CopyPathOpsResult, +} from '@pair/content-ops' const DATASET = join(__dirname, '..', '..', 'dataset') @@ -39,6 +44,7 @@ export type TransferDatasetConfig = { mode?: 'copy' | 'move' options?: SyncOptions skillNameMap?: SkillNameMap + skillLinkPathMap?: SkillLinkPathMap /** Content transform: applies @{prefix}-skip removal then strips all markers. */ transform?: { prefix: string } /** Strip all marker comments from the copied file. Default: false. */ @@ -49,7 +55,7 @@ export async function runTransferDataset( config: TransferDatasetConfig, datasetRoot: string = DATASET, ): Promise { - const { source, target, mode = 'move', options, skillNameMap } = config + const { source, target, mode = 'move', options, skillNameMap, skillLinkPathMap } = config let result: CopyPathOpsResult if (mode === 'copy') { @@ -60,6 +66,7 @@ export async function runTransferDataset( datasetRoot, ...(options && { options }), ...(skillNameMap && { skillNameMap }), + ...(skillLinkPathMap && { skillLinkPathMap }), }) } else { await movePathOps({ @@ -115,16 +122,29 @@ export async function runTransferPipeline( datasetRoot: string = DATASET, ): Promise { let accumulatedMap: SkillNameMap | undefined + let accumulatedLinkMap: SkillLinkPathMap | undefined for (const step of steps) { - const config = accumulatedMap ? { ...step, skillNameMap: accumulatedMap } : step + const config = accumulatedMap + ? { + ...step, + skillNameMap: accumulatedMap, + ...(accumulatedLinkMap && { skillLinkPathMap: accumulatedLinkMap }), + } + : step const result = await runTransferDataset(config, datasetRoot) if (result.skillNameMap && result.skillNameMap.size > 0) { accumulatedMap = result.skillNameMap + accumulatedLinkMap = result.skillLinkPathMap } } - return accumulatedMap ? { skillNameMap: accumulatedMap } : {} + return accumulatedMap + ? { + skillNameMap: accumulatedMap, + ...(accumulatedLinkMap && { skillLinkPathMap: accumulatedLinkMap }), + } + : {} } // CLI execution logic