specpipe 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +111 -311
  2. package/package.json +2 -1
  3. package/src/cli.js +16 -6
  4. package/src/commands/diff.js +1 -1
  5. package/src/commands/init-agents.js +40 -20
  6. package/src/commands/init-global.js +88 -33
  7. package/src/commands/init-interactive.js +71 -0
  8. package/src/commands/init.js +61 -22
  9. package/src/commands/remove.js +159 -49
  10. package/src/commands/upgrade.js +21 -56
  11. package/src/lib/agent-guards.js +34 -78
  12. package/src/lib/agent-install.js +38 -25
  13. package/src/lib/agents.js +53 -11
  14. package/src/lib/claude-global.js +50 -77
  15. package/src/lib/hooks.js +203 -0
  16. package/src/lib/installer.js +73 -61
  17. package/src/lib/reconcile.js +13 -8
  18. package/templates/{.claude/hooks → hooks}/file-guard.js +26 -21
  19. package/templates/hooks/specpipe-read-guard.sh +94 -21
  20. package/templates/hooks/specpipe-shell-guard.sh +121 -29
  21. package/templates/rules/specpipe-rules.md +77 -0
  22. package/templates/skills/sp-build/SKILL.md +101 -1
  23. package/templates/skills/sp-build-behavior-matrix/SKILL.md +876 -0
  24. package/templates/skills/sp-challenge/SKILL.md +34 -0
  25. package/templates/skills/sp-challenge-behavior-matrix/SKILL.md +289 -0
  26. package/templates/skills/sp-explore/SKILL.md +132 -0
  27. package/templates/skills/sp-explore-behavior-matrix/SKILL.md +862 -0
  28. package/templates/skills/sp-fix/SKILL.md +73 -1
  29. package/templates/skills/sp-fix-behavior-matrix/SKILL.md +338 -0
  30. package/templates/skills/sp-investigate/SKILL.md +70 -0
  31. package/templates/skills/sp-investigate-behavior-matrix/SKILL.md +718 -0
  32. package/templates/skills/sp-plan/SKILL.md +90 -0
  33. package/templates/skills/sp-plan-behavior-matrix/SKILL.md +1037 -0
  34. package/templates/skills/sp-review/SKILL.md +29 -3
  35. package/templates/skills/sp-review-behavior-matrix/SKILL.md +294 -0
  36. package/templates/.claude/CLAUDE.md +0 -79
  37. package/templates/.claude/hooks/path-guard.sh +0 -118
  38. package/templates/.claude/hooks/self-review.sh +0 -27
  39. package/templates/.claude/hooks/sensitive-guard.sh +0 -227
  40. package/templates/.claude/settings.json +0 -68
  41. package/templates/docs/WORKFLOW.md +0 -325
  42. package/templates/docs/specs/.gitkeep +0 -0
  43. package/templates/rules/specpipe-guards.md +0 -40
  44. package/templates/scripts/test-hooks.sh +0 -66
  45. /package/templates/{.claude/hooks → hooks}/comment-guard.js +0 -0
  46. /package/templates/{.claude/hooks → hooks}/glob-guard.js +0 -0
@@ -108,6 +108,8 @@ Before writing anything, run this checklist:
108
108
  | P0-1 | **Keyword scan** | **If GA available:** `ga_symbols` on 3-5 keywords from the feature description for indexed definitions, then `ga_file_summary` on each match to scope. **If GA unavailable** or the keyword matches only string/comment text (no symbol hits): grep. |
109
109
  | P0-2 | **Related specs** | List `docs/specs/` directories. Read the main spec of any related feature. Is there overlap? |
110
110
  | P0-2b | **Explore doc** | Derive feature name from `$ARGUMENTS` as kebab-case (same convention as `docs/specs/<feature>/`). Check `docs/explore/<feature-name>.md`. If no exact match, list `docs/explore/` and fuzzy-match by keywords. If found → read it. Log: "Explore findings found for '<feature>' — using as primary input. Skipping P0-3, P0-4 (already covered)." Continue with P0-5, P0-6. Full field-to-section mapping + Mode A/B/C scope: see **Explore → Spec mapping** subsection right after this table. |
111
+ | P0-2c | **Invariant registry** | Use the invariant registry README/schema as base knowledge; README examples are not runtime entries. Then read matching project-local entries under `docs/invariants/INV-*.md` when their `component_keys`, `sibling_set`, `shared_anchor`, title, or origin bugs match the feature. Carry `confirmed`/`enforced` invariants into `## Constraints & Invariants`; treat `candidate` entries as risk notes or confirmation GAPs, not automatic requirements. |
112
+ | P0-2d | **Sibling discovery** | If the feature changes an existing operation or fixes a bug, run the same candidate-only recipe as `/sp-explore` Phase 0.5 unless an explore doc already provides a Sibling Candidate Table. Sources: raw symptom/feature nouns, project-local invariants, shared anchors (`ga_callers` or grep), fuzzy sibling names (`create_from_*`, `*_from_*`, `send_*invite*`, `*_outcome*`, `reschedule*`, `book_next*`, etc.), and recent git co-change. Output feeds `## Sibling Candidate Table`; high/medium candidates must be `cover`, `GAP-NNN`, or `ignore(reason)`. Candidates do not become requirements until confirmed. |
111
113
  | P0-3 | **Dependency scan** | `ga_architecture` for the module map and `ga_importers` / `ga_callees` on touched symbols to see what this code reaches. Manual import-grep is a fallback. |
112
114
  | P0-4 | **Reusable utilities** | `ga_symbols` (fuzzy match) on names like `validate`, `format`, `parse`, `<domain>Helper` to find existing helpers; `ga_hubs` to surface the most-connected utilities worth reusing. |
113
115
  | P0-5 | **Project patterns** | Identify test framework, naming conventions, directory structure from existing code. |
@@ -128,6 +130,7 @@ When P0-2b found a `docs/explore/<feature>.md`, route its fields into the spec l
128
130
  | Feature + Happy path | Overview + Stories (happy-path AS) |
129
131
  | Unhappy paths + Edge cases + Input validation + External integration failure | Stories (error-path AS) via Scenario Derivation triggers. If the outcome lives in the explore's *Open questions* instead of being stated → emit a Gap, not a guessed AS |
130
132
  | Business rules | Constraints & Invariants. **A business rule that must hold across MORE THAN ONE endpoint/surface (idempotency / at-most-once / money-conservation / exactly-once / authorization) → a Constraint carrying `scope:`/`surfaces:` (cross-surface invariant); the Scenario Derivation "Cross-surface invariant pass" then forces per-surface coverage (CC5).** |
133
+ | State / viewer / surface coverage | `## Behavior Matrix`. When the explore doc or feature text identifies states/statuses, viewer roles, or multiple read/write surfaces, build the matrix from those three axes. Each non-N/A cell becomes an `AS-NNN`; unknown cells become `GAP-NNN (status: open)`; intentionally irrelevant cells become `N/A` with a reason. This is the shared artifact QA can consume directly: matrix cell = AS = TC target. |
131
134
  | Data impact | Data Model. A migration / irreversible mutation also flips its owning story to `autonomous: checkpoint` |
132
135
  | Out of scope | Not in Scope |
133
136
  | Permissions | Story description. Auth / payment / admin-only roles on a destructive story → `autonomous: checkpoint` |
@@ -375,6 +378,13 @@ AS-004: <short description>
375
378
  ## Constraints & Invariants
376
379
  [rules that must ALWAYS hold.
377
380
 
381
+ When a rule comes from `docs/invariants/INV-*.md`, preserve its id and status in prose:
382
+ `Source invariant: INV-### (status: candidate | confirmed | enforced | retired)`.
383
+ Only `confirmed` and `enforced` invariants may create mandatory AS/Behavior Matrix coverage.
384
+ `candidate` entries are discovery evidence: either confirm them from current requirements/code,
385
+ record a `GAP-NNN (status: open)` for confirmation, or leave them as risk notes. Do not
386
+ auto-promote a noisy candidate into a requirement.
387
+
378
388
  A constraint that must hold across MORE THAN ONE story/surface — a **cross-surface invariant**
379
389
  (idempotency / at-most-once / money-conservation / exactly-once / authorization) — carries two
380
390
  OPTIONAL fields so its coverage is checked at every surface, not once globally:
@@ -402,6 +412,58 @@ point to a `GAP-NNN`. See Phase 1 §"Linked fields — pin cross-spec contracts
402
412
  - `<field>` — consumed by `<sibling>:AS-NNN` on <surface> (<persisted+served | transient-in-response>).
403
413
  Produced by `<sibling>:AS-NNN` on <surface>. ✔ match. | ✘ <surface|lifecycle> mismatch → GAP-NNN.]
404
414
 
415
+ ## Sibling Surface Map
416
+ [OPTIONAL — required when P0-2d or the explore handoff found sibling candidates.
417
+
418
+ Purpose: separate noisy discovery evidence from confirmed planning surfaces. The Candidate Table
419
+ is evidence; the Confirmed Surface Map is what can feed `## Behavior Matrix`.
420
+
421
+ Sibling Candidate Table:
422
+ | Candidate | Operation | Evidence | Confidence | Disposition |
423
+ |---|---|---|---|---|
424
+ | `<surface/path/symbol>` | same create/update/delete/send/read op? | ga_callers / grep / co-change / invariant / user text | high / medium / low | cover / GAP-### / ignore(reason) |
425
+
426
+ Confirmed sibling surfaces:
427
+ - `<surface/path/symbol>` — why confirmed; feeds Behavior Matrix surface `<surface>`
428
+
429
+ Rules:
430
+ - Every high/medium candidate must have `cover`, `GAP-###`, or `ignore(reason)`.
431
+ - `cover` means the candidate is confirmed by current requirements/code evidence and must appear as a Behavior Matrix surface or AS/GAP coverage.
432
+ - `GAP-###` means the candidate appears material but expected behavior/scope is unknown.
433
+ - `ignore(reason)` means the candidate is a false positive or explicitly out of scope.
434
+ - Low-confidence candidates may remain notes. They do not create requirements by themselves.]
435
+
436
+ ## Behavior Matrix
437
+ [OPTIONAL — required when the feature has any state/status/stage, role/viewer-specific behavior,
438
+ permission-dependent affordance, cross-module read/write path, or the same record appears on more
439
+ than one surface. Omit only when none of those triggers exist.
440
+
441
+ Purpose: move QA's post-code state/viewer/surface sweep into the spec before code. This matrix
442
+ is the bridge between dev and QA: each non-N/A cell maps to one stable `AS-NNN` that QA may use
443
+ as the TC target.
444
+
445
+ Axes:
446
+ - `States`: statuses/stages/lifecycle points from the actual state machine or requirement text.
447
+ - `Viewers`: roles or viewer relationships that can see/act on the record.
448
+ - `Surfaces`: confirmed sibling surfaces plus create/read/action surfaces, including list rows, detail pages, feeds, dashboards,
449
+ notifications/email, calendar/provider views, API list/single-get, and cross-module targets.
450
+
451
+ Every cell must be one of:
452
+ - `AS-NNN` — confirmed expected behavior is specified by an acceptance scenario.
453
+ - `GAP-NNN (open)` — the cell is triggered by the feature but expected behavior is not decided.
454
+ - `N/A: <reason>` — the combination cannot occur or is intentionally irrelevant.
455
+
456
+ | State | Viewer | Surface | Expected behavior | Source / timing | Cascade / parity obligations | Coverage |
457
+ |---|---|---|---|---|---|---|
458
+ | `<state>` | `<role/viewer>` | `<surface>` | label/visibility/actions/data shown | authoritative source + realtime/refresh/persisted lifecycle | queues/counts/feed/calendar/notifications/read-model parity | AS-### / GAP-### / N/A: reason |
459
+
460
+ Rules:
461
+ - No blank cells. Blank means the spec is incomplete.
462
+ - Do not write "all states", "all viewers", or "all surfaces" unless the listed AS explicitly enumerates them.
463
+ - A state-transition cell must list cascade obligations or `none`.
464
+ - A read-surface cell must list source/timing/lifecycle: realtime, refresh-required, transient response, or persisted+served on later reads.
465
+ - A parity cell must name every surface that must show the same value; "same data everywhere" is not coverage.]
466
+
405
467
  ## UI Notes
406
468
  [OPTIONAL — include for UI-bearing features. Converts the explore doc's `UI sketches` (free ASCII) into a disciplined Component Tree the build can consume without re-deriving structure.
407
469
 
@@ -529,6 +591,27 @@ Two kinds of "missing", handled differently:
529
591
 
530
592
  **Cross-surface invariant pass (run after the atom list).** For every constraint stated as a system-wide rule (or carrying `scope:`/`surfaces:` in `## Constraints & Invariants`), enumerate EVERY story whose `When` can exercise it. Each such story binds it via `**Applies Constraints:**` and carries an AS asserting the invariant's OUTCOME at that surface. **For a stated cross-surface invariant the minimal-safe outcome (at-most-once: a repeat at this surface causes ≤1 effect) IS assertable** — it is the stated invariant applied to the surface, not a new requirement — so write the outcome AS; do NOT downgrade the whole surface to a bare Gap. If the surface's *mechanism* is unstated (e.g. a provider idempotency-key vs a server-side guard), attach a `GAP-NNN` from that AS for the mechanism detail — outcome asserted, mechanism deferred. A surface becomes a *bare* Gap (no AS) only when no minimal-safe outcome is assertable at all. This removes the AS-vs-Gap coin-flip: a money/effect surface under a stated invariant ALWAYS gets an outcome AS (+ a mechanism Gap if needed), never a bare Gap by default. This does NOT invent the invariant — it is already stated; it forces a stated invariant to be acknowledged at every surface it reaches, closing the "stated once, silently dropped at a second endpoint" hole (an idempotency rule that guarded one endpoint but not a second one — the class of bug this pass exists to kill). Still derive-or-Gap for any genuinely unstated outcome: never a guessed one.
531
593
 
594
+ **Sibling Surface Map pass (run before the Behavior Matrix pass).** If P0-2d or the explore handoff found sibling candidates, create `## Sibling Surface Map`. Treat discovery output as evidence, not a contract:
595
+ - High/medium candidates require a disposition: `cover`, `GAP-NNN`, or `ignore(reason)`.
596
+ - `cover` candidates become confirmed sibling surfaces and may feed Behavior Matrix surfaces.
597
+ - `GAP-NNN` candidates do not become BM cells until clarified, but the gap must name what is unknown.
598
+ - `ignore(reason)` candidates must state why they are false positive or out of scope.
599
+ - Low-confidence candidates can remain notes and do not block.
600
+
601
+ This pass is the upstream discovery gate for sibling-drift bugs: first find plausible sibling entry-points, then deliberately confirm or park them. It must not manufacture requirements from fuzzy matches.
602
+
603
+ **Behavior Matrix pass (run after the cross-surface invariant pass).** If the feature triggers any state/status/stage, viewer role, permission-dependent affordance, cross-module read/write path, or repeated read surface, create `## Behavior Matrix` before finalizing AS. Derive the axes only from the spec/explore/codebase scan:
604
+ - States from the state machine or named lifecycle/status values.
605
+ - Viewers from roles, owners/assignees, actor/recipient relationships, and permission rules.
606
+ - Surfaces from confirmed sibling surfaces, Module Dependency Map style source/target pairs, UI pages, API read paths, notifications/email, calendar/provider surfaces, feeds, counts, and dashboards.
607
+
608
+ For each material cell, either:
609
+ - write an AS asserting the exact behavior for that state/viewer/surface, including label, visibility, allowed action, data source/timing, and cascade/parity obligations when applicable;
610
+ - write a `GAP-NNN (status: open)` if the cell is triggered but the expected behavior is unknown;
611
+ - or mark `N/A: <reason>` if the combination cannot occur or is intentionally irrelevant.
612
+
613
+ This pass is not a canned edge checklist. It only expands axes that the feature already triggered. Its job is to stop "one happy-path cell specified, the rest discovered by QA on staging."
614
+
532
615
  **Do NOT add a universal canned edge checklist** (always-ask concurrency / duplicate-submit / deleted-item / stale-session) to every story — that manufactures requirements the text never stated, the exact fabrication this section forbids. Only stated atoms + stated constraints drive AS derivation here. The non-obvious adversarial cases are `/sp-challenge`'s job (its Failure-Mode lens); code-path/branch (if/else) depth is `/sp-build`'s Coverage Map. Three skills, three different questions — `/sp-plan` owns only what the spec states.
533
616
 
534
617
  ### Writing Instructions
@@ -576,11 +659,15 @@ Include only sections that apply:
576
659
  | CC9 | Every story has a `**Source:**` line anchoring it to a requirement clause (or ticket); no AS has a Then that is only "see GAP-NNN" | Add the Source; convert any gap-shell AS into a pure Gap |
577
660
  | CC10 | When a `docs/explore/<feature>.md` was used as input, every **non-empty** explore field listed in the Explore → Spec mapping has either produced spec content or been recorded as a `GAP-NNN` / Clarification. Empty explore sections do not fail this check | Re-walk the mapping; if a non-empty explore field has no destination, route it (or record it as a Gap) — closes the "format compliance hides meaning leak" risk |
578
661
  | CC11 | **(producer/consumer splits only)** Every linked field a consumer side reads has a producer AS (or Constraint) delivering it on the SAME surface at the SAME lifecycle point; the `## Linked Fields` block pins both sides; every "consumes fields defined in `<sibling>`" reference resolves to a real field in that sibling's `## Data Model`; **AND each linked field designates a consumer-side seam integration AS (tested as a real integration, not mocked)**. Skip entirely for a standalone spec with no cross-spec field dependency | Pin both sides (Phase 1 §"Linked fields"); on surface/lifecycle mismatch record a `GAP-NNN` and fix the producer AS or correct the consumer — never code. Verify each named field exists in the sibling Data Model. Add the seam AS if missing |
662
+ | CC12 | If the feature triggers state/viewer/surface behavior, `## Behavior Matrix` exists and every material cell has `Coverage` = `AS-NNN`, `GAP-NNN`, or `N/A: <reason>`. No blank coverage cells. Every non-N/A cell maps to an existing AS/GAP; every `N/A` has a concrete reason. State-transition cells list cascade obligations or `none`; read-surface cells list source/timing/lifecycle | Add the matrix, fill the cells, bind each non-N/A cell to AS/GAP, or explain why the matrix is not applicable |
663
+ | CC13 | Every matching `docs/invariants/INV-*.md` entry is handled according to status: `enforced` → referenced `test_ref` or equivalent regression is named in implementation guidance; `confirmed` → sibling/component coverage is represented by AS/GAP/N/A; `candidate` → confirmation GAP/risk note or explicit ignore reason; `retired` → ignored with reason if matched | Add invariant coverage, record a confirmation GAP/risk note, or explain why the invariant does not apply |
664
+ | CC14 | If sibling discovery ran or the explore handoff includes a Sibling Candidate Table, every high/medium candidate has `cover`, `GAP-NNN`, or `ignore(reason)`. Only `cover` candidates feed `## Behavior Matrix`; `GAP`/`ignore` candidates do not create AS/BM requirements | Add the disposition, create the confirmation GAP, or remove the candidate with reason |
579
665
 
580
666
  **CC5 enforcement procedure (mandatory, no exceptions):**
581
667
  1. Enumerate every `C-xxx` line in `## Constraints & Invariants`.
582
668
  2. For EACH constraint, append explicit IDs at end of line: `(AS-###, AS-###)` for stated outcomes, OR `(GAP-###)` if unspecified. Story refs `(S-002)` are NOT coverage.
583
669
  2b. **If the constraint carries `scope:`/`surfaces:`** (cross-surface invariant) — require one `AS-###` (or `GAP-###`) PER listed surface, written `surface → AS-###`. A surface with neither AS nor Gap = FAIL (this is the cross-surface-invariant gate — the createIntent class). For every story in `scope`, confirm its `**Applies Constraints:**` lists this constraint. **An AS counts for a surface ONLY if it actually ASSERTS the invariant at that surface** — a story that binds the constraint but whose AS do not assert it there ⇒ that surface needs an AS or a `GAP-###`, NOT "covered" by binding alone. **Prefer the outcome AS:** for a stated cross-surface invariant the per-surface at-most-once outcome is a stated atom (the invariant applied to the surface), so it MUST be an AS; a *bare* Gap (no AS) at a surface is correct only when even the minimal-safe outcome is genuinely unassertable — otherwise write the outcome AS plus, if the mechanism is unstated, a Gap from it. (This makes the per-surface catch mechanical AND deterministic — no AS-vs-Gap coin-flip between runs.)
670
+ 2c. **If `## Behavior Matrix` exists** — require each row's `Coverage` cell to name an existing `AS-NNN`, existing `GAP-NNN`, or `N/A: <reason>`. A referenced AS/GAP counts only if it mentions the same state, viewer, surface, and concrete expected behavior (or the open question for that cell). Generic AS text like "shows correct status" does NOT cover a matrix cell.
584
671
  3. If no AS exists yet:
585
672
  - Stated outcome → write a new AS (split if multi-case per Writing Instructions §"one specific behavior").
586
673
  - Trigger present, outcome unspecified → write `GAP-NNN (status: open)` and reference it.
@@ -849,6 +936,9 @@ After updating, verify:
849
936
  | CC8 | No `depends_on` anywhere in the spec points to a removed/missing story ID; graph stays a DAG | Fix dangling refs — **when removing a story, scan the WHOLE spec for `depends_on` pointing to its ID** (justified exception to "no mass-migration": a dangling ref deadlocks `/sp-build`) |
850
937
  | CC10 | When this Mode C update was driven by a `docs/explore/<feature>.md` (e.g. new explore content), every non-empty explore field used as input has produced spec content (in touched stories/sections) or been recorded as a `GAP-NNN` / Clarification. Empty explore fields and untouched-this-run sections do not fail this check | Re-walk the Explore → Spec mapping (Phase 0); route any non-empty explore field that has no destination, or record it as a Gap |
851
938
  | CC11 | **(producer/consumer splits only)** For every linked field this run ADDS or MODIFIES — a new/changed consumer read, a new/changed producer AS, or a `Then` whose surface or lifecycle moves — a producer AS still delivers it on the SAME surface at the SAME lifecycle point, the `## Linked Fields` block reflects the change, **and a consumer-side seam integration AS (real integration, not mocked) exists for it**. Untouched legacy linked fields are not re-audited. Skip if the spec has no cross-spec field dependency | Re-pin the touched field both sides (Phase 1 §"Linked fields"); on surface/lifecycle mismatch record a `GAP-NNN` and fix the producer AS or correct the consumer — never code; add the seam AS if the touched field lacks one. A linked-field change is behavioural (M4/M5 if it moves a Given/When/Then) → classify Major and snapshot |
939
+ | CC12 | If this run ADDS or MODIFIES state/viewer/surface behavior, the touched `## Behavior Matrix` cells exist and each has `Coverage` = `AS-NNN`, `GAP-NNN`, or `N/A: <reason>`. Untouched legacy matrix cells are not re-audited. If no matrix exists and this update introduces the trigger, add the matrix for the touched scope | Add/update the matrix cells and bind non-N/A cells to AS/GAP; do not ship a new state/viewer/surface path as prose only |
940
+ | CC13 | If this run touches a component named by `docs/invariants/INV-*.md`, the matching invariant entry is handled by status (`enforced` test_ref/equivalent named; `confirmed` covered or GAP/N/A; `candidate` confirmation GAP/risk note; `retired` ignored with reason). Untouched unrelated invariants are not re-audited | Add/update invariant coverage or record the confirmation/risk disposition |
941
+ | CC14 | If this Mode C update adds/modifies an existing operation or bug-fix path, sibling discovery has either run for the touched scope or a prior Sibling Surface Map is reused. High/medium candidates are disposed as `cover`, `GAP-NNN`, or `ignore(reason)`; only `cover` candidates feed new BM cells | Run sibling discovery for the touched scope, then update the Sibling Surface Map disposition |
852
942
 
853
943
  > **⛔ Consistency check is NOT optional.**
854
944
  > Run CC1-CC6 after EVERY update (Major and Minor).