@planu/cli 5.7.5 → 5.7.6

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 (196) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +3 -3
  3. package/dist/.planu-build.json +1 -1
  4. package/dist/cli/commands/package-handoff.js +30 -5
  5. package/dist/config/compliance-profiles.json +36 -3
  6. package/dist/config/environment-schema.json +21 -0
  7. package/dist/config/hook-templates/planu-session-safeguard.sh +22 -15
  8. package/dist/config/official-sdd-tools.d.ts +1 -1
  9. package/dist/config/official-sdd-tools.js +11 -24
  10. package/dist/config/registries/hosts/codex.json +14 -30
  11. package/dist/config/registries/hosts/opencode.json +1 -6
  12. package/dist/config/server-instructions.js +0 -21
  13. package/dist/config/skill-templates/planu-context-assets.md +3 -6
  14. package/dist/config/skill-templates/planu-implement.md +2 -2
  15. package/dist/config/skill-templates/planu-multi-teammate-review.md +1 -1
  16. package/dist/config/skill-templates/planu-release.md +2 -7
  17. package/dist/config/skill-templates/planu-validate.md +3 -3
  18. package/dist/config/spec-templates/crud-rest-api/template.json +5 -1
  19. package/dist/config/spec-templates/file-upload-s3/template.json +5 -1
  20. package/dist/config/spec-templates/stripe-payments/template.json +5 -1
  21. package/dist/config/spec-templates/webhook-system/template.json +5 -1
  22. package/dist/config/subagent-templates/planu-challenger.md +4 -9
  23. package/dist/config/subagent-templates/planu-spec-implementer.md +2 -4
  24. package/dist/config/subagent-templates/planu-validator.md +5 -8
  25. package/dist/config/tool-groups.json +6 -144
  26. package/dist/engine/ambiguity-scorer.js +40 -1
  27. package/dist/engine/autopilot/bootstrap.js +1 -1
  28. package/dist/engine/code-impact-analyzer.js +2 -6
  29. package/dist/engine/context-orchestrator/index.js +2 -2
  30. package/dist/engine/convention-scanner/codebase-scanner.js +2 -2
  31. package/dist/engine/dogfooding/runtime-gap-detector.js +2 -2
  32. package/dist/engine/evidence-gates/artifact-reader.d.ts +1 -0
  33. package/dist/engine/evidence-gates/artifact-reader.js +1 -1
  34. package/dist/engine/evidence-gates/evidence-autofill.d.ts +13 -5
  35. package/dist/engine/evidence-gates/evidence-autofill.js +106 -43
  36. package/dist/engine/evidence-gates/lifecycle-gate.js +7 -4
  37. package/dist/engine/evidence-index/index-builder.js +21 -4
  38. package/dist/engine/execution/job-runtime.js +1 -1
  39. package/dist/engine/execution/package-handoff-job-executor.d.ts +22 -0
  40. package/dist/engine/execution/package-handoff-job-executor.js +490 -0
  41. package/dist/engine/execution/validate-job-executor.js +20 -8
  42. package/dist/engine/git/exec-git.d.ts +4 -0
  43. package/dist/engine/git/exec-git.js +14 -0
  44. package/dist/engine/handoff-artifacts/implementation-review-reader.js +15 -2
  45. package/dist/engine/handoff-artifacts/schemas.d.ts +2 -0
  46. package/dist/engine/handoff-artifacts/schemas.js +1 -0
  47. package/dist/engine/handoff-format.d.ts +36 -0
  48. package/dist/engine/handoff-format.js +314 -0
  49. package/dist/engine/handoff-packager.d.ts +5 -3
  50. package/dist/engine/handoff-packager.js +89 -32
  51. package/dist/engine/host-tool-filter.js +20 -49
  52. package/dist/engine/human-summary.js +14 -3
  53. package/dist/engine/implementation-contract/common.d.ts +8 -3
  54. package/dist/engine/implementation-contract/common.js +9 -6
  55. package/dist/engine/implementation-contract/evaluator.js +4 -6
  56. package/dist/engine/implementation-contract/renderer.js +18 -23
  57. package/dist/engine/project-health-checker.js +2 -2
  58. package/dist/engine/readiness-checker.js +27 -0
  59. package/dist/engine/reconcile/apply-changes.js +4 -0
  60. package/dist/engine/reconcile/propagate-mirrors.d.ts +3 -0
  61. package/dist/engine/reconcile/propagate-mirrors.js +311 -0
  62. package/dist/engine/self-healing/healer.js +12 -5
  63. package/dist/engine/self-healing/strategies/lint-fix.d.ts +1 -1
  64. package/dist/engine/self-healing/strategies/lint-fix.js +4 -1
  65. package/dist/engine/self-healing/strategies/test-fix.d.ts +4 -3
  66. package/dist/engine/self-healing/strategies/test-fix.js +32 -14
  67. package/dist/engine/self-healing/strategies/typescript-fix.d.ts +1 -1
  68. package/dist/engine/self-healing/strategies/typescript-fix.js +28 -10
  69. package/dist/engine/session/checkpoint-writer.js +8 -4
  70. package/dist/engine/session/session-tracker.d.ts +5 -16
  71. package/dist/engine/session/session-tracker.js +29 -26
  72. package/dist/engine/session-state/writer.js +3 -11
  73. package/dist/engine/skill-generator/workflow-skill-generator.d.ts +4 -2
  74. package/dist/engine/skill-generator/workflow-skill-generator.js +113 -38
  75. package/dist/engine/spec-format/bdd-parser.d.ts +1 -11
  76. package/dist/engine/spec-format/bdd-parser.js +40 -11
  77. package/dist/engine/spec-format/lean-spec-generator.js +30 -10
  78. package/dist/engine/spec-format/retired-scaffold.d.ts +3 -0
  79. package/dist/engine/spec-format/retired-scaffold.js +5 -0
  80. package/dist/engine/spec-format/technical-md-populator.d.ts +1 -0
  81. package/dist/engine/spec-format/technical-md-populator.js +27 -0
  82. package/dist/engine/spec-format/unified-spec-builder.d.ts +1 -0
  83. package/dist/engine/spec-format/unified-spec-builder.js +30 -20
  84. package/dist/engine/spec-grounding/contract.js +115 -1
  85. package/dist/engine/spec-quality/generic-output-gate.js +37 -3
  86. package/dist/engine/technical-enricher/index.js +4 -1
  87. package/dist/engine/type-safety-gate.js +3 -11
  88. package/dist/engine/universal-rules/catalog.js +0 -2
  89. package/dist/engine/universal-rules/rules/planu-workflow.js +1 -1
  90. package/dist/engine/validation/durable-validation.d.ts +3 -2
  91. package/dist/engine/validation/durable-validation.js +106 -40
  92. package/dist/engine/validation/validation-freshness.d.ts +3 -2
  93. package/dist/engine/validation/validation-freshness.js +22 -16
  94. package/dist/engine/validation/validation-worktree.js +34 -0
  95. package/dist/hosts/claude-code/ux/mcp-prompts.js +8 -8
  96. package/dist/index.js +3 -17
  97. package/dist/resources/process.js +38 -65
  98. package/dist/storage/session-state-store.js +2 -0
  99. package/dist/tools/challenge-spec/scenarios-utils.js +2 -2
  100. package/dist/tools/configure-checkpoint-policy.js +3 -3
  101. package/dist/tools/create-spec-helpers.js +2 -5
  102. package/dist/tools/create-spec.js +21 -4
  103. package/dist/tools/facilitate.js +3 -6
  104. package/dist/tools/git/branch-ops.js +16 -4
  105. package/dist/tools/github-release-handler.js +1 -20
  106. package/dist/tools/init-project/agents-md-writer.js +6 -6
  107. package/dist/tools/init-project/claude-md-generator.d.ts +0 -1
  108. package/dist/tools/init-project/claude-md-generator.js +0 -35
  109. package/dist/tools/init-project/handler.js +7 -5
  110. package/dist/tools/init-project/per-client-files-writer.js +5 -5
  111. package/dist/tools/init-project/planu-workflow-generator.js +2 -13
  112. package/dist/tools/init-project/portable-index-reconciler.d.ts +2 -1
  113. package/dist/tools/init-project/portable-index-reconciler.js +7 -2
  114. package/dist/tools/init-project/rules-generator.d.ts +0 -11
  115. package/dist/tools/init-project/rules-generator.js +4 -85
  116. package/dist/tools/init-project/scaffold-writer.d.ts +0 -1
  117. package/dist/tools/init-project/scaffold-writer.js +1 -12
  118. package/dist/tools/jobs/handlers.d.ts +0 -6
  119. package/dist/tools/jobs/handlers.js +13 -40
  120. package/dist/tools/list-specs.js +6 -21
  121. package/dist/tools/package-handoff.d.ts +3 -2
  122. package/dist/tools/package-handoff.js +153 -427
  123. package/dist/tools/reconcile-session-safeguard-hook.js +22 -15
  124. package/dist/tools/register-sdd-tools.js +0 -23
  125. package/dist/tools/register-spec-tools/core-spec-tools.js +18 -27
  126. package/dist/tools/schemas/index.d.ts +1 -1
  127. package/dist/tools/schemas/index.js +1 -1
  128. package/dist/tools/schemas/output-schemas.d.ts +1 -18
  129. package/dist/tools/schemas/output-schemas.js +1 -11
  130. package/dist/tools/schemas/package-handoff-output-schema.d.ts +40 -0
  131. package/dist/tools/schemas/package-handoff-output-schema.js +68 -0
  132. package/dist/tools/schemas/validate-output-schema.d.ts +1 -2
  133. package/dist/tools/schemas/validate-output-schema.js +3 -2
  134. package/dist/tools/session-checkpoint.js +2 -2
  135. package/dist/tools/skill-registry/index.d.ts +0 -1
  136. package/dist/tools/skill-registry/index.js +0 -1
  137. package/dist/tools/sync-spec-state-handler.d.ts +2 -2
  138. package/dist/tools/sync-spec-state-handler.js +23 -3
  139. package/dist/tools/tool-registry/core-tools.js +1 -140
  140. package/dist/tools/tool-registry/group-infra.js +2 -39
  141. package/dist/tools/tool-registry/group-quality-compliance.js +9 -1
  142. package/dist/tools/update-status/batch.js +17 -0
  143. package/dist/tools/update-status/dod-gates.js +14 -1
  144. package/dist/tools/update-status/done-receipt-verifier.d.ts +22 -0
  145. package/dist/tools/update-status/done-receipt-verifier.js +56 -26
  146. package/dist/tools/update-status/evidence-gate.js +10 -21
  147. package/dist/tools/update-status/file-sync.d.ts +1 -0
  148. package/dist/tools/update-status/file-sync.js +6 -0
  149. package/dist/tools/update-status/index.d.ts +6 -0
  150. package/dist/tools/update-status/index.js +39 -44
  151. package/dist/tools/update-status-actions.js +2 -7
  152. package/dist/tools/validate.js +34 -28
  153. package/dist/transports/oauth-validator.js +12 -1
  154. package/dist/transports/transport-factory.d.ts +2 -1
  155. package/dist/transports/transport-factory.js +19 -0
  156. package/dist/types/common/primitives.d.ts +2 -0
  157. package/dist/types/durable-job.d.ts +45 -0
  158. package/dist/types/durable-validation.d.ts +1 -1
  159. package/dist/types/evidence-autofill.d.ts +13 -1
  160. package/dist/types/handoff-artifacts.d.ts +2 -1
  161. package/dist/types/readiness.d.ts +8 -1
  162. package/dist/types/reconcile.d.ts +6 -0
  163. package/dist/types/skill-registry.d.ts +26 -1
  164. package/dist/types/spec/core.d.ts +5 -0
  165. package/dist/types/spec/inputs.d.ts +2 -2
  166. package/dist/types/spec-format.d.ts +11 -1
  167. package/dist/types/spec-grounding.d.ts +12 -0
  168. package/dist/types/transport.d.ts +1 -0
  169. package/dist/types/validation-receipt.d.ts +27 -0
  170. package/package.json +3 -2
  171. package/planu-plugin.json +13 -26
  172. package/src/i18n/messages/en.json +1 -1
  173. package/src/i18n/messages/es.json +1 -1
  174. package/src/i18n/messages/pt.json +1 -1
  175. package/dist/engine/skill-generator/conventions-hasher.d.ts +0 -7
  176. package/dist/engine/skill-generator/conventions-hasher.js +0 -24
  177. package/dist/engine/universal-rules/rules/agent-teams.d.ts +0 -3
  178. package/dist/engine/universal-rules/rules/agent-teams.js +0 -63
  179. package/dist/tools/clarify-requirements/multiple-choice.d.ts +0 -30
  180. package/dist/tools/clarify-requirements/multiple-choice.js +0 -306
  181. package/dist/tools/clarify-requirements/questions-context.d.ts +0 -9
  182. package/dist/tools/clarify-requirements/questions-context.js +0 -74
  183. package/dist/tools/clarify-requirements/questions.d.ts +0 -16
  184. package/dist/tools/clarify-requirements/questions.js +0 -96
  185. package/dist/tools/clarify-requirements.d.ts +0 -4
  186. package/dist/tools/clarify-requirements.js +0 -314
  187. package/dist/tools/code-graph-handler.d.ts +0 -6
  188. package/dist/tools/code-graph-handler.js +0 -72
  189. package/dist/tools/create-rule.d.ts +0 -4
  190. package/dist/tools/create-rule.js +0 -107
  191. package/dist/tools/feedback-handler.d.ts +0 -7
  192. package/dist/tools/feedback-handler.js +0 -150
  193. package/dist/tools/semantic-search-handler.d.ts +0 -7
  194. package/dist/tools/semantic-search-handler.js +0 -71
  195. package/dist/tools/skill-registry/search.d.ts +0 -11
  196. package/dist/tools/skill-registry/search.js +0 -159
@@ -12,9 +12,7 @@ tools:
12
12
  - mcp__planu__update_status
13
13
  - mcp__planu__validate
14
14
  - mcp__planu__get_job
15
- - mcp__planu__watch_job
16
15
  - mcp__planu__check_readiness
17
- - mcp__planu__tdd_scaffold
18
16
  ---
19
17
 
20
18
  # Planu Spec Implementer
@@ -32,11 +30,11 @@ You are an autonomous implementation agent for Spec Driven Development. Your job
32
30
  2. **Verify approval** — confirm spec status is `approved`. If not, call `check_readiness` and stop with a note.
33
31
  3. **Update status to `implementing`** — call `update_status({ specId, status: "implementing" })`.
34
32
  4. **Create a feature branch** — `git checkout -b feat/SPEC-NNN-slug`.
35
- 5. **Scaffold TDD tests** — call `tdd_scaffold` to generate test stubs from acceptance criteria.
33
+ 5. **Write TDD tests** — write failing test stubs mirroring `src/` in `tests/`, one per acceptance criterion.
36
34
  6. **Implement** — write all source files in dependency order: types → engine → storage → tools/resources. Follow Clean Architecture (types layer imports nothing; tools import engine+storage+types).
37
35
  7. **Run tests** — `pnpm typecheck && pnpm lint && pnpm test:coverage`. Fix all failures.
38
36
  8. **Submit validation** — call `validate({ specId })` and capture `operationId`, `projectId`, `workspaceId`, and `cursor` from the durable acknowledgement.
39
- 9. **Await and inspect** — call `watch_job` with those identifiers and the latest cursor until terminal. Require `completed`, then inspect `job.result.structuredContent`. Stop on `failed`, `dead-letter`, or `cancelled`; fix failures and submit a new validation job.
37
+ 9. **Await and inspect** — poll `get_job` with those identifiers and the latest cursor until terminal. Require `completed`, then inspect `job.result.structuredContent`. Stop on `failed`, `dead-letter`, or `cancelled`; fix failures and submit a new validation job.
40
38
  10. **Commit** — only after the completed validation result reports `ready: true`, `status: "passing"`, and passing quality gates, run `git add <files> && git commit -m "feat(SPEC-NNN): <title>"`.
41
39
  11. **Update status to `done`** — call `update_status({ specId, status: "done" })` only after that completed passing result.
42
40
 
@@ -8,11 +8,9 @@ tools:
8
8
  - Grep
9
9
  - mcp__planu__validate
10
10
  - mcp__planu__get_job
11
- - mcp__planu__watch_job
12
11
  - mcp__planu__list_specs
13
- - mcp__planu__spec_health_check
14
12
  - mcp__planu__check_readiness
15
- - mcp__planu__auto_fix_validation
13
+ - mcp__planu__restart_job
16
14
  ---
17
15
 
18
16
  # Planu Validator
@@ -29,15 +27,14 @@ You are a validation specialist. Your job is to verify that implemented code sat
29
27
 
30
28
  1. **Load spec** — call `list_specs` and read `spec.md` for all acceptance criteria.
31
29
  2. **Submit validation** — call `validate({ specId })` and capture `operationId`, `projectId`, `workspaceId`, and `cursor` from the durable acknowledgement.
32
- 3. **Wait for the terminal result** — call `watch_job` with those identifiers and the latest cursor. Repeat while state is `accepted`, `running`, or `checkpointed`. Require `completed`; stop and report `failed`, `dead-letter`, or `cancelled`.
30
+ 3. **Wait for the terminal result** — poll `get_job` with those identifiers and the latest cursor. Repeat while state is `accepted`, `running`, or `checkpointed`. Require `completed`; stop and report `failed`, `dead-letter`, or `cancelled`.
33
31
  4. **Read the validation result** — inspect `job.result.structuredContent`. The acknowledgement is not a pass/fail result.
34
- 5. **Run health check** — call `spec_health_check({ specId })` for structural issues.
35
- 6. **Assess gaps** — for each failing criterion:
32
+ 5. **Assess gaps** — for each failing criterion:
36
33
  - Identify which source files are responsible
37
34
  - Diagnose the root cause (missing logic, wrong condition, untested branch)
38
35
  - Propose a minimal fix
39
- 7. **Auto-fix when safe** — call `auto_fix_validation` for fixable issues, then submit and await a new validation job.
40
- 8. **Report** — provide a summary with: passing count, failing count, and concrete next steps for each failure.
36
+ 6. **Fix and re-validate** — fix the underlying code or test, then call `validate({ specId })` again and poll `get_job` until terminal; use `restart_job` if the job itself failed or was cancelled rather than the criteria.
37
+ 7. **Report** — provide a summary with: passing count, failing count, and concrete next steps for each failure.
41
38
 
42
39
  ## Output format
43
40
 
@@ -3,22 +3,8 @@
3
3
  {
4
4
  "id": "core",
5
5
  "name": "Core",
6
- "description": "The 9 Tier 1 tools every new user needs: project setup, spec creation, lifecycle management, and status.",
7
- "tools": [
8
- "facilitate",
9
- "init_project",
10
- "create_spec",
11
- "list_specs",
12
- "update_status",
13
- "validate",
14
- "planu_status",
15
- "capture_idea",
16
- "quick_start",
17
- "set_locale",
18
- "init_constitution",
19
- "activate_group",
20
- "list_groups"
21
- ],
6
+ "description": "The Tier 1 tools every new user needs: project setup, spec creation, lifecycle management, and status.",
7
+ "tools": ["facilitate", "init_project", "create_spec", "list_specs", "update_status", "validate", "planu_status"],
22
8
  "defaultEnabled": true,
23
9
  "locked": true
24
10
  },
@@ -26,143 +12,19 @@
26
12
  "id": "spec-lifecycle",
27
13
  "name": "Spec Lifecycle",
28
14
  "description": "Create, validate, estimate, and manage specs through their full lifecycle.",
29
- "tools": [
30
- "estimate",
31
- "request_changes",
32
- "challenge_spec",
33
- "check_readiness",
34
- "clarify_requirements",
35
- "summarize_spec",
36
- "reconcile_spec",
37
- "reconcile_hooks",
38
- "reconcile_rules",
39
- "reconcile_skills",
40
- "config_health",
41
- "list_templates",
42
- "apply_template",
43
- "spec_history",
44
- "log_decision"
45
- ],
15
+ "tools": ["estimate", "request_changes", "challenge_spec", "check_readiness", "reconcile_spec"],
46
16
  "defaultEnabled": true,
47
17
  "locked": false,
48
18
  "triggerTools": ["estimate", "challenge_spec", "check_readiness"]
49
19
  },
50
- {
51
- "id": "analysis",
52
- "name": "Analysis",
53
- "description": "Audit, drift detection, contradiction checks, and deep code analysis.",
54
- "tools": [
55
- "audit",
56
- "audit_stack",
57
- "detect_drift",
58
- "detect_contradictions",
59
- "detect_deprecations",
60
- "detect_agent",
61
- "reality_check",
62
- "check_spec_accuracy",
63
- "paradigm_report",
64
- "spec_coverage",
65
- "validate_annotations",
66
- "generate_annotations",
67
- "snapshot_spec_hashes",
68
- "auto_reconcile",
69
- "living_spec_watch",
70
- "living_spec_status",
71
- "spec_diff",
72
- "living_spec_coverage",
73
- "sync_spec_to_code",
74
- "sync_code_to_spec",
75
- "resolve_sync_conflict"
76
- ],
77
- "defaultEnabled": false,
78
- "locked": false,
79
- "triggerTools": ["audit", "detect_drift", "reality_check"]
80
- },
81
- {
82
- "id": "generation",
83
- "name": "Generation",
84
- "description": "Generate docs, tests, schemas, UI contracts, skills, rules, and ADRs.",
85
- "tools": [
86
- "generate_docs",
87
- "generate_tests",
88
- "design_schema",
89
- "define_ui_contract",
90
- "generate_adr",
91
- "generate_checklist",
92
- "generate_rules",
93
- "generate_skill",
94
- "generate_sub_agent",
95
- "consult_docs",
96
- "event_contracts"
97
- ],
98
- "defaultEnabled": false,
99
- "locked": false,
100
- "triggerTools": ["generate_docs", "generate_tests", "design_schema"]
101
- },
102
20
  {
103
21
  "id": "orchestration",
104
22
  "name": "Orchestration",
105
- "description": "Multi-agent orchestration, execution plans, handoff packages, and AI cost estimation.",
106
- "tools": [
107
- "orchestrate",
108
- "orchestrate_agents",
109
- "package_handoff",
110
- "generate_execution_plan",
111
- "generate_orchestration_script",
112
- "estimate_ai_cost",
113
- "integrate_pm"
114
- ],
115
- "defaultEnabled": false,
116
- "locked": false,
117
- "triggerTools": ["orchestrate", "orchestrate_agents", "generate_execution_plan"]
118
- },
119
- {
120
- "id": "devops",
121
- "name": "DevOps",
122
- "description": "Git management, tooling suggestions, CI generation, upgrades, and stack recommendations.",
123
- "tools": [
124
- "manage_git",
125
- "suggest_tooling",
126
- "suggest_stack",
127
- "suggest_mcps",
128
- "suggest_mcp_server",
129
- "plan_upgrade",
130
- "check_versions",
131
- "check_config_health",
132
- "generate_ci",
133
- "discover_mcps",
134
- "validate_workflow"
135
- ],
136
- "defaultEnabled": false,
137
- "locked": false,
138
- "triggerTools": ["manage_git", "suggest_tooling", "generate_ci"]
139
- },
140
- {
141
- "id": "governance",
142
- "name": "Governance",
143
- "description": "Security checks, data governance, scope management, and context control.",
144
- "tools": [
145
- "security_check",
146
- "data_governance",
147
- "legal_compliance_report",
148
- "manage_scope",
149
- "manage_context",
150
- "context_budget",
151
- "request_context",
152
- "contribute_context"
153
- ],
154
- "defaultEnabled": false,
155
- "locked": false,
156
- "triggerTools": ["security_check", "data_governance", "manage_scope"]
157
- },
158
- {
159
- "id": "learning",
160
- "name": "Learning",
161
- "description": "Pattern learning, knowledge capture, reverse engineering, and migration.",
162
- "tools": ["learn_pattern", "capture_learning", "reverse_engineer", "migrate_tech", "manage_hooks"],
23
+ "description": "Handoff packages for implementation agents.",
24
+ "tools": ["package_handoff"],
163
25
  "defaultEnabled": false,
164
26
  "locked": false,
165
- "triggerTools": ["learn_pattern", "capture_learning", "reverse_engineer"]
27
+ "triggerTools": ["package_handoff"]
166
28
  }
167
29
  ]
168
30
  }
@@ -40,6 +40,44 @@ function buildSuggestion(term, criterion) {
40
40
  const clean = criterion.slice(0, 80);
41
41
  return `Replace "${term}" with a concrete measurable value, e.g.: "${clean.replace(new RegExp(`\\b${term}\\b`, 'gi'), `<${term}_value>`)}"`;
42
42
  }
43
+ const ENUM_LITERAL_TOKEN = /^[A-Za-z0-9_.-]+$/;
44
+ const NON_WORD_MASK_CHAR = '#';
45
+ function maskSpan(span) {
46
+ return span.replace(/\S/g, NON_WORD_MASK_CHAR);
47
+ }
48
+ function maskLiteralSpans(line) {
49
+ let masked = '';
50
+ let cursor = 0;
51
+ while (cursor < line.length) {
52
+ const char = line.charAt(cursor);
53
+ if (char === '`') {
54
+ const closeIndex = line.indexOf('`', cursor + 1);
55
+ if (closeIndex === -1) {
56
+ masked += line.slice(cursor);
57
+ break;
58
+ }
59
+ masked += maskSpan(line.slice(cursor, closeIndex + 1));
60
+ cursor = closeIndex + 1;
61
+ continue;
62
+ }
63
+ if (char === '"') {
64
+ const closeIndex = line.indexOf('"', cursor + 1);
65
+ if (closeIndex === -1) {
66
+ masked += line.slice(cursor);
67
+ break;
68
+ }
69
+ const content = line.slice(cursor + 1, closeIndex);
70
+ masked += ENUM_LITERAL_TOKEN.test(content)
71
+ ? maskSpan(line.slice(cursor, closeIndex + 1))
72
+ : line.slice(cursor, closeIndex + 1);
73
+ cursor = closeIndex + 1;
74
+ continue;
75
+ }
76
+ masked += char;
77
+ cursor += 1;
78
+ }
79
+ return masked;
80
+ }
43
81
  function hasAdjacentNumber(line, term) {
44
82
  // Check if a number appears within 4 words of the vague term
45
83
  const words = line.toLowerCase().split(/\s+/);
@@ -63,10 +101,11 @@ export function scoreAmbiguity(criteriaLines) {
63
101
  continue;
64
102
  }
65
103
  const lower = line.toLowerCase();
104
+ const maskedLower = maskLiteralSpans(line).toLowerCase();
66
105
  // Check vague units (skip if adjacent number is present)
67
106
  for (const [term, reason] of Object.entries(VAGUE_UNITS)) {
68
107
  const pattern = new RegExp(`\\b${term}\\b`);
69
- if (pattern.test(lower) && !hasAdjacentNumber(lower, term)) {
108
+ if (pattern.test(maskedLower) && !hasAdjacentNumber(maskedLower, term)) {
70
109
  violations.push({
71
110
  term,
72
111
  criterion: line.slice(0, 120),
@@ -106,7 +106,7 @@ function registerPluginInstalledListener() {
106
106
  /**
107
107
  * SPEC-1345 AC4: Fire-and-forget semantic index refresh on spec:created.
108
108
  * Reconciles the persisted TF-IDF index against the current corpus so the next
109
- * semantic_search call hits a warm, up-to-date index.
109
+ * internal pattern-learning lookup hits a warm, up-to-date index.
110
110
  */
111
111
  function registerSemanticIndexRefreshListener() {
112
112
  onAutopilotEvent('spec:created', (event) => {
@@ -1,5 +1,5 @@
1
1
  // engine/code-impact-analyzer.ts — Code Change → Spec Impact Analyzer (SPEC-366)
2
- import { execSync } from 'node:child_process';
2
+ import { execGitCapture } from './git/exec-git.js';
3
3
  // ---------------------------------------------------------------------------
4
4
  // Helpers
5
5
  // ---------------------------------------------------------------------------
@@ -138,11 +138,7 @@ export function suggestSpecUpdates(specs, filePath) {
138
138
  export function runGitDiffImpact(specs, projectPath) {
139
139
  let diffText = '';
140
140
  try {
141
- diffText = execSync('git diff HEAD', {
142
- cwd: projectPath,
143
- encoding: 'utf-8',
144
- stdio: ['pipe', 'pipe', 'pipe'],
145
- });
141
+ diffText = execGitCapture('git diff HEAD', projectPath);
146
142
  }
147
143
  catch {
148
144
  // ignore git errors — proceed with empty diff
@@ -1,6 +1,6 @@
1
1
  import { suggestArchivableSpecs, archiveSpecToPath } from '../context-manager.js';
2
2
  import { writeCheckpoint } from '../session/checkpoint-writer.js';
3
- import { sessionTracker } from '../session/session-tracker.js';
3
+ import { getSessionTracker } from '../session/session-tracker.js';
4
4
  // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
5
5
  import { specStore } from '../../storage/index.js';
6
6
  // eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
@@ -114,7 +114,7 @@ async function archiveDoneSpecs(projectId, projectPath, actionsExecuted) {
114
114
  }
115
115
  async function createSessionCheckpoint(projectPath, actionsExecuted) {
116
116
  try {
117
- const snapshot = sessionTracker.getSnapshot();
117
+ const snapshot = getSessionTracker(projectPath).getSnapshot();
118
118
  await writeCheckpoint(projectPath, snapshot);
119
119
  actionsExecuted.push('session_checkpoint: written to planu/session-context.md');
120
120
  }
@@ -1,13 +1,13 @@
1
1
  // engine/convention-scanner/codebase-scanner.ts — SPEC-192
2
2
  // Scans actual codebase files against parsed conventions
3
- import { execSync } from 'node:child_process';
4
3
  import { readdirSync, readFileSync, statSync } from 'node:fs';
5
4
  import { join, relative } from 'node:path';
5
+ import { execGitCapture } from '../git/exec-git.js';
6
6
  const MAX_VIOLATIONS_PER_CATEGORY = 50;
7
7
  // ── helpers ───────────────────────────────────────────────────────────────────
8
8
  function execSafe(cmd, cwd) {
9
9
  try {
10
- return execSync(cmd, { cwd, timeout: 5_000, stdio: ['pipe', 'pipe', 'pipe'] }).toString();
10
+ return execGitCapture(cmd, cwd);
11
11
  }
12
12
  catch {
13
13
  return '';
@@ -78,7 +78,7 @@ const SIGNAL_RULES = [
78
78
  signature: 'manual-recovery-loop',
79
79
  title: 'Repeated manual recovery steps',
80
80
  severity: 'medium',
81
- nextAction: 'submit_feedback',
81
+ nextAction: 'create_spec',
82
82
  patterns: [
83
83
  /\bmanual recovery\b/gi,
84
84
  /\brepair_frontmatter_drift\b/gi,
@@ -342,7 +342,7 @@ export async function analyzeRuntimeDogfooding(projectPath) {
342
342
  signature: 'session-version-drift',
343
343
  title: 'Persisted session version drifted from the current package version',
344
344
  severity: 'medium',
345
- nextAction: 'submit_feedback',
345
+ nextAction: 'create_spec',
346
346
  occurrences: 1,
347
347
  evidence: ['Persisted session version does not match package.json'],
348
348
  });
@@ -45,6 +45,7 @@ export declare function traceabilityRowHasCurrentCommandEvidence(row: Traceabili
45
45
  projectId: string;
46
46
  projectPath: string;
47
47
  }): boolean;
48
+ export declare function artifactHint(label: string): string;
48
49
  /** SPEC-1356: exported so evidence-autofill (and other producers) target the same path convention. */
49
50
  export declare function handoffEvidencePath(projectId: string, specId: string, filename: string): string;
50
51
  export declare function readEvidenceArtifacts(args: {
@@ -247,7 +247,7 @@ const ARTIFACT_HINTS = {
247
247
  'contract-validation-ui.json': 'Expected filename contract-validation-ui.json with { version: 1, kind: "ui", passed: boolean, reportPath?, summary? }.',
248
248
  'contract-validation-mcp.json': 'Expected filename contract-validation-mcp.json with { version: 1, kind: "mcp", passed: boolean, reportPath?, summary? }.',
249
249
  };
250
- function artifactHint(label) {
250
+ export function artifactHint(label) {
251
251
  return ARTIFACT_HINTS[label] ?? '';
252
252
  }
253
253
  /** SPEC-1356: exported so evidence-autofill (and other producers) target the same path convention. */
@@ -4,12 +4,20 @@ export declare const PLANU_BOOKKEEPING_FILES: readonly ['planu/context.md', 'pla
4
4
  /** Normalizes a spec.md path (absolute or already repo-relative) to a repo-relative,
5
5
  * forward-slash path so it can be compared against `git diff` output. */
6
6
  export declare function toRepoRelativePath(specPath: string, projectPath?: string): string;
7
+ /** Prose template + matching regex for the generator's validation evidence, shared by the
8
+ * generator (renders it) and the staleness classifier (recognizes it). */
9
+ export declare function buildValidationEvidenceText(specId: string, score: number | undefined): string;
10
+ export declare const VALIDATION_EVIDENCE_PATTERN: RegExp;
11
+ /** Prose template + matching regex for the generator's reviewer evidence, shared by the
12
+ * generator (renders it) and the staleness classifier (recognizes it). */
13
+ export declare function buildReviewerEvidenceText(agent: string, kind: string, verdict: string): string;
14
+ export declare const REVIEWER_EVIDENCE_PATTERN: RegExp;
7
15
  /**
8
- * Generate `traceability-matrix.json` in the handoff evidence store when it does
9
- * not already exist. Never overwrites an existing file if one is present this
10
- * is a no-op that reports `already-exists`. Tolerates every failure by returning
11
- * a typed non-throwing result; callers must never let this crash a lifecycle
12
- * transition.
16
+ * Generate `traceability-matrix.json` in the handoff evidence store, or regenerate
17
+ * it in place when it is autofill-shaped but stale relative to the current
18
+ * validation report. Never touches a hand-written or mixed matrix those are a
19
+ * permanent no-op. Tolerates every failure by returning a typed non-throwing
20
+ * result; callers must never let this crash a lifecycle transition.
13
21
  */
14
22
  export declare function autofillTraceabilityMatrix(args: AutofillTraceabilityMatrixArgs): Promise<AutofillTraceabilityMatrixResult>;
15
23
  //# sourceMappingURL=evidence-autofill.d.ts.map
@@ -1,22 +1,4 @@
1
1
  // engine/evidence-gates/evidence-autofill.ts — SPEC-1356: automated lifecycle evidence pipeline
2
- //
3
- // Generates traceability-matrix.json in the handoff evidence store when it is
4
- // missing, so a `done` transition never fails purely because nobody hand-wrote
5
- // the traceability artifact.
6
- //
7
- // ORDERING CAVEAT (documented per SPEC-1356 T-A3): writing traceability-matrix.json
8
- // *after* `validate` has already computed and bound durable validation receipts
9
- // would make those bindings stale relative to the newly-written evidence — the
10
- // receipt would be certifying a repository state that does not include this file.
11
- // This module does NOT attempt to re-run or re-bind validation. Instead, callers
12
- // (see `src/tools/update-status/index.ts`) MUST treat a write from this module as
13
- // "evidence created, not yet bound": when autofill actually writes the file, the
14
- // caller must block the `done` transition with a message telling the operator to
15
- // re-run `validate` once so the freshly-created evidence gets bound, then retry
16
- // `done`. This is the simpler, safe choice given SPEC-1356 could not fully verify
17
- // that skipping straight through to the done gates after autofill would keep
18
- // receipt bindings correct in every code path (reconciliation, forced bypass,
19
- // legacy harnesses, etc.).
20
2
  import { execFile } from 'node:child_process';
21
3
  import { existsSync } from 'node:fs';
22
4
  import { isAbsolute, join, relative, resolve } from 'node:path';
@@ -113,6 +95,18 @@ function filterExistingRepoFiles(projectPath, paths) {
113
95
  }
114
96
  });
115
97
  }
98
+ /** Prose template + matching regex for the generator's validation evidence, shared by the
99
+ * generator (renders it) and the staleness classifier (recognizes it). */
100
+ export function buildValidationEvidenceText(specId, score) {
101
+ return `Validated by handoffs/${specId}/validation-report.json (score=${score ?? 'n/a'}).`;
102
+ }
103
+ export const VALIDATION_EVIDENCE_PATTERN = /^Validated by handoffs\/.+\/validation-report\.json \(score=.+\)\.$/;
104
+ /** Prose template + matching regex for the generator's reviewer evidence, shared by the
105
+ * generator (renders it) and the staleness classifier (recognizes it). */
106
+ export function buildReviewerEvidenceText(agent, kind, verdict) {
107
+ return `Reviewed by ${agent} (${kind}), verdict=${verdict}.`;
108
+ }
109
+ export const REVIEWER_EVIDENCE_PATTERN = /^Reviewed by .+ \(.+\), verdict=.+\.$/;
116
110
  /** Best-effort read + sha256 summary of the persisted validation report, for prose evidence. */
117
111
  async function readValidationReportSummary(projectId, specId) {
118
112
  const reportPath = join(projectDataDir(projectId), 'handoffs', specId, 'validation-report.json');
@@ -124,47 +118,103 @@ async function readValidationReportSummary(projectId, specId) {
124
118
  }
125
119
  const { score, reviewer } = parsed.data;
126
120
  return {
127
- validationEvidence: `Validated by handoffs/${specId}/validation-report.json (score=${score ?? 'n/a'}).`,
128
- reviewerEvidence: `Reviewed by ${reviewer.agent} (${reviewer.kind}), verdict=${reviewer.verdict}.`,
121
+ validationEvidence: buildValidationEvidenceText(specId, score),
122
+ reviewerEvidence: buildReviewerEvidenceText(reviewer.agent, reviewer.kind, reviewer.verdict),
129
123
  };
130
124
  }
131
125
  catch {
132
126
  return {};
133
127
  }
134
128
  }
129
+ function isAutofillShapedMatrix(candidate) {
130
+ if (typeof candidate !== 'object' ||
131
+ candidate === null ||
132
+ !Array.isArray(candidate.rows)) {
133
+ return false;
134
+ }
135
+ const rows = candidate.rows;
136
+ if (rows.length === 0) {
137
+ return false;
138
+ }
139
+ return rows.every((row) => row.validationEvidence !== undefined &&
140
+ row.reviewerEvidence !== undefined &&
141
+ VALIDATION_EVIDENCE_PATTERN.test(row.validationEvidence) &&
142
+ REVIEWER_EVIDENCE_PATTERN.test(row.reviewerEvidence));
143
+ }
144
+ const LEGACY_ROW_ALLOWED_KEYS = new Set(['acceptanceCriterion', 'testEvidence', 'changedFiles']);
145
+ /** A pre-evidence-schema matrix: every row uses only the generator's original
146
+ * keys, with neither validationEvidence nor reviewerEvidence present. Safe to
147
+ * regenerate the same way a stale autofill-shaped matrix is. */
148
+ function isLegacyGeneratorShapedMatrix(candidate) {
149
+ if (typeof candidate !== 'object' ||
150
+ candidate === null ||
151
+ !Array.isArray(candidate.rows)) {
152
+ return false;
153
+ }
154
+ const rows = candidate.rows;
155
+ if (rows.length === 0) {
156
+ return false;
157
+ }
158
+ return rows.every((row) => Object.keys(row).every((key) => LEGACY_ROW_ALLOWED_KEYS.has(key)));
159
+ }
160
+ /** Reads the on-disk matrix, if any, and classifies whether it is safe to
161
+ * regenerate. A hand-written, mixed, or unparseable matrix is never touched. */
162
+ async function readRegeneratableMatrix(targetPath) {
163
+ if (!existsSync(targetPath)) {
164
+ return { kind: 'absent' };
165
+ }
166
+ const raw = await readFile(targetPath, 'utf-8');
167
+ try {
168
+ const parsed = JSON.parse(raw);
169
+ return isAutofillShapedMatrix(parsed) || isLegacyGeneratorShapedMatrix(parsed)
170
+ ? { kind: 'autofill-shaped', raw }
171
+ : { kind: 'manual' };
172
+ }
173
+ catch {
174
+ return { kind: 'manual' };
175
+ }
176
+ }
177
+ async function resolveChangedFiles(args) {
178
+ const exec = args.exec ?? defaultExec;
179
+ const baseBranch = args.baseBranch ?? 'main';
180
+ const gitEntries = args.projectPath
181
+ ? await computeChangedFilesFromGit(args.projectPath, exec, baseBranch)
182
+ : [];
183
+ const gitChangedFiles = excludeBookkeepingChanges(gitEntries, args.ownSpecMdPath);
184
+ const ownership = extractCanonicalFileOwnership(args.specBody);
185
+ const ownershipFiles = [...ownership.toCreate, ...ownership.toModify, ...ownership.toTest];
186
+ const rawChangedFiles = gitChangedFiles.length > 0 ? gitChangedFiles : ownershipFiles;
187
+ return [
188
+ ...new Set(args.projectPath
189
+ ? filterExistingRepoFiles(args.projectPath, rawChangedFiles)
190
+ : rawChangedFiles),
191
+ ];
192
+ }
135
193
  /**
136
- * Generate `traceability-matrix.json` in the handoff evidence store when it does
137
- * not already exist. Never overwrites an existing file if one is present this
138
- * is a no-op that reports `already-exists`. Tolerates every failure by returning
139
- * a typed non-throwing result; callers must never let this crash a lifecycle
140
- * transition.
194
+ * Generate `traceability-matrix.json` in the handoff evidence store, or regenerate
195
+ * it in place when it is autofill-shaped but stale relative to the current
196
+ * validation report. Never touches a hand-written or mixed matrix those are a
197
+ * permanent no-op. Tolerates every failure by returning a typed non-throwing
198
+ * result; callers must never let this crash a lifecycle transition.
141
199
  */
142
200
  export async function autofillTraceabilityMatrix(args) {
143
201
  const targetPath = handoffEvidencePath(args.projectId, args.specId, 'traceability-matrix.json');
144
202
  try {
145
- if (existsSync(targetPath)) {
146
- return { written: false, reason: 'already-exists' };
147
- }
148
203
  const criteria = extractAcceptanceCriteriaTexts(args.specBody);
149
204
  if (criteria.length === 0) {
150
205
  return { written: false, reason: 'no-criteria' };
151
206
  }
207
+ const existingLookup = await readRegeneratableMatrix(targetPath);
208
+ if (existingLookup.kind === 'manual') {
209
+ return { written: false, reason: 'already-exists-manual' };
210
+ }
211
+ const existingRaw = existingLookup.kind === 'autofill-shaped' ? existingLookup.raw : undefined;
152
212
  const scenarios = parseFrontmatterScenarios(args.specBody);
153
- const exec = args.exec ?? defaultExec;
154
- const baseBranch = args.baseBranch ?? 'main';
155
- const gitEntries = args.projectPath
156
- ? await computeChangedFilesFromGit(args.projectPath, exec, baseBranch)
157
- : [];
158
- const gitChangedFiles = excludeBookkeepingChanges(gitEntries, args.ownSpecMdPath);
159
- const ownership = extractCanonicalFileOwnership(args.specBody);
160
- const ownershipFiles = [...ownership.toCreate, ...ownership.toModify, ...ownership.toTest];
161
- const rawChangedFiles = gitChangedFiles.length > 0 ? gitChangedFiles : ownershipFiles;
162
- const changedFiles = [
163
- ...new Set(args.projectPath
164
- ? filterExistingRepoFiles(args.projectPath, rawChangedFiles)
165
- : rawChangedFiles),
166
- ];
213
+ const changedFiles = await resolveChangedFiles(args);
167
214
  const { validationEvidence, reviewerEvidence } = await readValidationReportSummary(args.projectId, args.specId);
215
+ if (existingRaw !== undefined && validationEvidence === undefined) {
216
+ return { written: false, reason: 'already-exists-current' };
217
+ }
168
218
  const rows = criteria.map((acceptanceCriterion, index) => {
169
219
  const testPaths = scenarios[index]?.tests?.map((test) => test.path) ?? [];
170
220
  return {
@@ -176,7 +226,20 @@ export async function autofillTraceabilityMatrix(args) {
176
226
  };
177
227
  });
178
228
  const payload = { version: 1, rows };
179
- await atomicWriteFile(targetPath, JSON.stringify(payload, null, 2));
229
+ const payloadJson = JSON.stringify(payload, null, 2);
230
+ if (existingRaw !== undefined) {
231
+ if (existingRaw === payloadJson) {
232
+ return { written: false, reason: 'already-exists-current' };
233
+ }
234
+ await atomicWriteFile(targetPath, payloadJson);
235
+ return {
236
+ written: true,
237
+ path: targetPath,
238
+ rowCount: rows.length,
239
+ reason: 'regenerated-stale',
240
+ };
241
+ }
242
+ await atomicWriteFile(targetPath, payloadJson);
180
243
  return { written: true, path: targetPath, rowCount: rows.length };
181
244
  }
182
245
  catch (error) {
@@ -1,5 +1,5 @@
1
1
  import { isAbsolute, join } from 'node:path';
2
- import { DISCOVERY_TEMPLATE_OVERRIDES, DiscoverySchema, evidenceArtifactCollectionDigest, evidenceArtifactDigest, } from './artifact-reader.js';
2
+ import { DISCOVERY_TEMPLATE_OVERRIDES, DiscoverySchema, artifactHint, evidenceArtifactCollectionDigest, evidenceArtifactDigest, } from './artifact-reader.js';
3
3
  import { renderSchemaSkeleton } from '../handoff-artifacts/schema-skeleton.js';
4
4
  import { hasAnyAffirmedMatch, stripMetaAnalysisText, stripNonContractText, } from '../text-signal-boundaries.js';
5
5
  import { createCriterionIdentity } from '../criterion-identity.js';
@@ -218,7 +218,7 @@ export function checkDoneEvidenceGate(spec, criteria, artifacts) {
218
218
  if (!matrix) {
219
219
  issues.push({
220
220
  code: 'traceability_missing',
221
- message: 'Traceability matrix evidence is required before done. Add traceability-matrix.json covering each acceptance criterion.',
221
+ message: `Traceability matrix evidence is required before done. Add traceability-matrix.json covering each acceptance criterion. ${artifactHint('Traceability matrix evidence')}`,
222
222
  });
223
223
  }
224
224
  else {
@@ -238,7 +238,7 @@ export function checkDoneEvidenceGate(spec, criteria, artifacts) {
238
238
  const rowDetails = incompleteRows.map(describeIncompleteRow);
239
239
  issues.push({
240
240
  code: 'traceability_incomplete_rows',
241
- message: `Traceability rows must include evidence, changed files, validation evidence, and reviewer evidence. Incomplete rows: ${rowDetails.join('; ')}`,
241
+ message: `Traceability rows must include evidence, changed files, validation evidence, and reviewer evidence. Incomplete rows: ${rowDetails.join('; ')}. Check traceability-matrix.json in this spec's handoff evidence store: a generator-shaped matrix regenerates automatically the next time validate runs and the done transition is retried, but a hand-written matrix must be edited in place.`,
242
242
  });
243
243
  }
244
244
  }
@@ -251,9 +251,12 @@ export function checkDoneEvidenceGate(spec, criteria, artifacts) {
251
251
  .map((artifact) => artifact.kind);
252
252
  const missingKinds = requiredKinds.filter((kind) => !passedKinds.has(kind));
253
253
  if (missingKinds.length > 0) {
254
+ const hints = missingKinds
255
+ .map((kind) => artifactHint(`contract-validation-${kind}.json`))
256
+ .join(' ');
254
257
  issues.push({
255
258
  code: 'contract_validation_missing',
256
- message: `Contract validation evidence is required for: ${missingKinds.join(', ')}`,
259
+ message: `Contract validation evidence is required for: ${missingKinds.join(', ')}. ${hints}`,
257
260
  });
258
261
  }
259
262
  if (failedKinds.length > 0) {