@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
@@ -7,63 +7,34 @@ import { fileURLToPath } from 'node:url';
7
7
  // Core AGENTS.md tool reference — curated list of key Planu tools
8
8
  // ---------------------------------------------------------------------------
9
9
  export const CORE_AGENTS_MD_TOOLS = [
10
+ { name: 'init_project', description: 'Load or bootstrap Planu project context' },
11
+ { name: 'facilitate', description: 'Universal entry point for SDD workflow' },
10
12
  { name: 'create_spec', description: 'Create a new spec from a description' },
11
13
  { name: 'list_specs', description: 'List all specs and their status' },
12
14
  { name: 'update_status', description: 'Move a spec through workflow stages' },
15
+ {
16
+ name: 'update_status_batch',
17
+ description: 'Batch update many specs to the same status in one MCP execution',
18
+ },
13
19
  { name: 'validate', description: 'Check implementation against spec criteria' },
14
20
  { name: 'challenge_spec', description: 'Stress-test a spec for gaps before implementation' },
15
21
  { name: 'check_readiness', description: 'Verify spec has enough detail to implement' },
16
- { name: 'planu_status', description: 'Quick project health snapshot' },
17
- { name: 'facilitate', description: 'Universal entry point for SDD workflow' },
18
- { name: 'plan_mode', description: 'Preview implementation plan before writing code' },
19
- { name: 'generate_steering_file', description: 'Generate host-aware steering files' },
20
- { name: 'search_specs', description: 'Search specs with fuzzy text matching' },
21
- { name: 'spec_quality_score', description: 'Compute quality score for a spec' },
22
- { name: 'analyze_spec_size', description: 'Analyze if a spec should be split' },
23
- { name: 'decompose_spec', description: 'Decompose a spec into agent tasks' },
24
- { name: 'estimate', description: 'Estimate time and cost for a spec' },
25
- { name: 'session_checkpoint', description: 'Save session context' },
26
- { name: 'project_overview', description: 'Single-call project state query' },
27
- { name: 'scan_project', description: 'Scan project and generate specs' },
28
- { name: 'validate_workflow', description: 'Validate a workflow definition' },
29
- { name: 'tdd_scaffold', description: 'Generate TDD test stubs from spec criteria' },
30
- { name: 'coverage_gap_analyzer', description: 'Analyze untested acceptance criteria' },
31
- { name: 'security_scan', description: 'Scan dependencies for vulnerabilities' },
32
- { name: 'bootstrap_skills', description: 'Auto-install skills for detected stack' },
33
- { name: 'manage_drift', description: 'Detect and resolve spec drift' },
34
- { name: 'sync_spec_state', description: 'Sync spec status between frontmatter and store' },
35
- { name: 'health_check_all', description: 'Run health check on all specs' },
36
- { name: 'tokens', description: 'Token usage dashboard' },
37
- { name: 'configure_workers', description: 'Configure background analysis workers' },
38
22
  {
39
- name: 'approve_spec',
40
- description: 'Record that a reviewer has approved a spec — requires reviewer token',
23
+ name: 'bump_spec_version',
24
+ description: "Bump the SemVer spec_version of a spec's frontmatter",
25
+ },
26
+ { name: 'reconcile_spec', description: 'Reconcile implementation drift back into the spec' },
27
+ {
28
+ name: 'migrate_legacy_spec',
29
+ description: 'Persist a synthesized spec.md and retire legacy files',
41
30
  },
42
- { name: 'lock_spec', description: 'Acquire exclusive lock on a spec' },
43
- { name: 'register_api_surface', description: 'Register API surfaces exposed by a spec' },
44
- { name: 'analyze_breaking_changes', description: 'Analyze specs impacted by API changes' },
45
- { name: 'track_tech_debt', description: 'Track a new tech debt item' },
46
- { name: 'capture_idea', description: 'Capture a lightweight idea' },
47
- { name: 'log_lesson', description: 'Log a lesson learned' },
48
- { name: 'semantic_search', description: 'Search project knowledge by meaning' },
49
- { name: 'render_spec_for_provider', description: 'Render spec for target LLM provider' },
50
- { name: 'convert_to_bdd', description: 'Convert spec criteria to BDD format' },
51
- { name: 'generate_e2e_tests', description: 'Generate E2E tests from spec criteria' },
52
- { name: 'run_spec_lint', description: 'Run custom lint rules against specs' },
53
- { name: 'suggest_criteria', description: 'Suggest missing best-practice criteria' },
54
- { name: 'check_compliance', description: 'Unified compliance tool' },
55
- { name: 'create_release', description: 'Create a GitHub Release' },
56
- { name: 'generate_changelog', description: 'Generate changelog from merged PRs' },
57
- { name: 'create_pr_from_spec', description: 'Create a PR from a spec' },
58
- { name: 'onboarding_progress', description: 'View onboarding progress' },
59
- { name: 'list_templates', description: 'Browse spec template catalog' },
60
- { name: 'generate_docs_site', description: 'Generate static HTML docs site' },
61
- { name: 'plan_team_distribution', description: 'Distribute specs across agents' },
62
- { name: 'optimize_spec_scheduling', description: 'Minimize agent idle time' },
63
- { name: 'context_window_status', description: 'Estimate token consumption' },
64
- { name: 'archive_spec_context', description: 'Archive a spec to free tokens' },
65
- { name: 'workspace_overview', description: 'Get overview of all projects' },
66
- { name: 'search_all_projects', description: 'Search specs across all projects' },
31
+ { name: 'package_handoff', description: 'Generate an implementation handoff package for a spec' },
32
+ { name: 'planu_status', description: 'Quick project health snapshot' },
33
+ { name: 'session_checkpoint', description: 'Save session context' },
34
+ { name: 'estimate', description: 'Estimate time and cost for a spec' },
35
+ { name: 'request_changes', description: 'Request changes on a spec, moving it back to draft' },
36
+ { name: 'get_job', description: 'Read the durable state of a local Planu job' },
37
+ { name: 'restart_job', description: 'Restart a cancelled or failed durable job' },
67
38
  ];
68
39
  // ---------------------------------------------------------------------------
69
40
  // Interactive detection heuristics
@@ -89,20 +89,31 @@ export function buildListSpecsSummary(total, byStatus) {
89
89
  return `No plans found in this project yet. Use create_spec to add your first one.`;
90
90
  }
91
91
  const get = (key) => byStatus[key] ?? 0;
92
- const inProgress = get('implementing') + get('approved');
92
+ const inProgress = get('implementing');
93
+ const approved = get('approved');
93
94
  const inReview = get('review');
95
+ const approvedClause = approved > 0 ? `${String(approved)} approved ready to implement` : '';
94
96
  if (inReview > 0 && inProgress > 0) {
95
97
  const reviewNoun = inReview === 1 ? 'plan' : 'plans';
96
- return `You have ${String(inProgress)} in progress and ${String(inReview)} ${reviewNoun} ready to review. Start with the ones under review.`;
98
+ const suffix = approvedClause ? ` and ${approvedClause}` : '';
99
+ return `You have ${String(inProgress)} in progress and ${String(inReview)} ${reviewNoun} ready to review${suffix}. Start with the ones under review.`;
97
100
  }
98
101
  if (inReview > 0) {
99
102
  const noun = inReview === 1 ? 'plan' : 'plans';
100
- return `You have ${String(inReview)} ${noun} ready to review. Approve or request changes to move forward.`;
103
+ const suffix = approvedClause ? ` You also have ${approvedClause}.` : '';
104
+ return `You have ${String(inReview)} ${noun} ready to review. Approve or request changes to move forward.${suffix}`;
105
+ }
106
+ if (inProgress > 0 && approved > 0) {
107
+ return `You have ${String(inProgress)} in progress and ${approvedClause}. Run validate when implementation is done.`;
101
108
  }
102
109
  if (inProgress > 0) {
103
110
  const noun = inProgress === 1 ? 'plan' : 'plans';
104
111
  return `You have ${String(inProgress)} ${noun} in progress. Run validate when implementation is done.`;
105
112
  }
113
+ if (approved > 0) {
114
+ const noun = approved === 1 ? 'plan' : 'plans';
115
+ return `You have ${String(approved)} ${noun} approved ready to implement. Run validate when implementation is done.`;
116
+ }
106
117
  return `You have ${String(total)} plans in this project. Approve a plan to start implementing.`;
107
118
  }
108
119
  /** estimate — time and cost estimated for a spec */
@@ -1,6 +1,6 @@
1
1
  export declare const IMPLEMENTATION_CONTRACT_SECTION = "Implementation Contract";
2
2
  /** Subsections every rendered contract must include — omitting one is a defect. */
3
- export declare const IMPLEMENTATION_CONTRACT_REQUIRED_SUBSECTIONS: readonly ['User Outcome', 'File-Level Work Plan', 'Acceptance-To-Verification Map', 'Verification Commands'];
3
+ export declare const IMPLEMENTATION_CONTRACT_REQUIRED_SUBSECTIONS: readonly ['User Outcome'];
4
4
  /**
5
5
  * SPEC-1406: subsections that carry no grounded content are omitted from the rendered
6
6
  * body rather than filled with restated-criterion filler. Their absence is not an
@@ -8,10 +8,15 @@ export declare const IMPLEMENTATION_CONTRACT_REQUIRED_SUBSECTIONS: readonly ['Us
8
8
  * already-persisted specs that still carry these sections (with real, non-empty
9
9
  * content) transitionable: `evaluateImplementationContract` only requires them when
10
10
  * the rendered body chose to include the heading.
11
+ *
12
+ * SPEC-1435: extends the same omission precedent to File-Level Work Plan,
13
+ * Acceptance-To-Verification Map, and Verification Commands — the renderer used to
14
+ * fill these with a constant "no evidence was grounded" sentence that its own quality
15
+ * gates could not see. Now the renderer omits the subsection instead.
11
16
  */
12
- export declare const IMPLEMENTATION_CONTRACT_OPTIONAL_SUBSECTIONS: readonly ['Behavior Contract', 'Edge Cases And Failure Modes', 'Non-Goals And Forbidden Approaches'];
17
+ export declare const IMPLEMENTATION_CONTRACT_OPTIONAL_SUBSECTIONS: readonly ['Behavior Contract', 'Edge Cases And Failure Modes', 'Non-Goals And Forbidden Approaches', 'File-Level Work Plan', 'Acceptance-To-Verification Map', 'Verification Commands'];
13
18
  /** Union of required + optional subsections. Kept for callers that only need the full set. */
14
- export declare const IMPLEMENTATION_CONTRACT_SUBSECTIONS: readonly ["User Outcome", "File-Level Work Plan", "Acceptance-To-Verification Map", "Verification Commands", "Behavior Contract", "Edge Cases And Failure Modes", "Non-Goals And Forbidden Approaches"];
19
+ export declare const IMPLEMENTATION_CONTRACT_SUBSECTIONS: readonly ["User Outcome", "Behavior Contract", "Edge Cases And Failure Modes", "Non-Goals And Forbidden Approaches", "File-Level Work Plan", "Acceptance-To-Verification Map", "Verification Commands"];
15
20
  export declare function hasImplementationContract(specBody: string): boolean;
16
21
  export declare function extractTopLevelSection(body: string, sectionName: string): string;
17
22
  //# sourceMappingURL=common.d.ts.map
@@ -1,12 +1,7 @@
1
1
  import { escapeRegex } from '../../core/shared/strings.js';
2
2
  export const IMPLEMENTATION_CONTRACT_SECTION = 'Implementation Contract';
3
3
  /** Subsections every rendered contract must include — omitting one is a defect. */
4
- export const IMPLEMENTATION_CONTRACT_REQUIRED_SUBSECTIONS = [
5
- 'User Outcome',
6
- 'File-Level Work Plan',
7
- 'Acceptance-To-Verification Map',
8
- 'Verification Commands',
9
- ];
4
+ export const IMPLEMENTATION_CONTRACT_REQUIRED_SUBSECTIONS = ['User Outcome'];
10
5
  /**
11
6
  * SPEC-1406: subsections that carry no grounded content are omitted from the rendered
12
7
  * body rather than filled with restated-criterion filler. Their absence is not an
@@ -14,11 +9,19 @@ export const IMPLEMENTATION_CONTRACT_REQUIRED_SUBSECTIONS = [
14
9
  * already-persisted specs that still carry these sections (with real, non-empty
15
10
  * content) transitionable: `evaluateImplementationContract` only requires them when
16
11
  * the rendered body chose to include the heading.
12
+ *
13
+ * SPEC-1435: extends the same omission precedent to File-Level Work Plan,
14
+ * Acceptance-To-Verification Map, and Verification Commands — the renderer used to
15
+ * fill these with a constant "no evidence was grounded" sentence that its own quality
16
+ * gates could not see. Now the renderer omits the subsection instead.
17
17
  */
18
18
  export const IMPLEMENTATION_CONTRACT_OPTIONAL_SUBSECTIONS = [
19
19
  'Behavior Contract',
20
20
  'Edge Cases And Failure Modes',
21
21
  'Non-Goals And Forbidden Approaches',
22
+ 'File-Level Work Plan',
23
+ 'Acceptance-To-Verification Map',
24
+ 'Verification Commands',
22
25
  ];
23
26
  /** Union of required + optional subsections. Kept for callers that only need the full set. */
24
27
  export const IMPLEMENTATION_CONTRACT_SUBSECTIONS = [
@@ -83,12 +83,10 @@ function evaluateSubsection(section, content) {
83
83
  }
84
84
  const trimmed = content.trim();
85
85
  if (trimmed.length === 0) {
86
- if (!OPTIONAL_SUBSECTIONS.has(section)) {
87
- issues.push({
88
- code: 'contract_subsection_empty',
89
- message: `Implementation Contract subsection "${section}" is empty.`,
90
- });
91
- }
86
+ issues.push({
87
+ code: 'contract_subsection_empty',
88
+ message: `Implementation Contract subsection "${section}" is empty.`,
89
+ });
92
90
  return issues;
93
91
  }
94
92
  for (const pattern of FILLER_PATTERNS) {
@@ -3,32 +3,32 @@ export function buildImplementationContractSection(input) {
3
3
  const criteria = input.criteria;
4
4
  const testFiles = input.files.test.map((file) => file.path);
5
5
  const verificationCommands = input.verificationCommands;
6
+ const filePlan = renderFilePlan(input.files);
6
7
  const verificationRows = renderVerificationRows(criteria, testFiles, verificationCommands);
7
8
  const failureModes = renderFailureModes(criteria);
8
9
  const nonGoals = renderNonGoals(input.outOfScope);
10
+ const verificationCommandLines = verificationCommands.length > 0
11
+ ? verificationCommands.map((command) => `- \`${command}\``)
12
+ : criteria.length > 0
13
+ ? []
14
+ : ['- Needs decision: provide a repository-supported verification command.'];
9
15
  const lines = [
10
16
  `## ${IMPLEMENTATION_CONTRACT_SECTION}`,
11
17
  '### User Outcome',
12
18
  criteria[0]?.text ??
13
19
  'Needs decision: define the exact observable outcome this spec must deliver.',
14
20
  '',
15
- '### File-Level Work Plan',
16
- ...renderFilePlan(input.files, criteria.length > 0),
17
- '',
18
- '### Acceptance-To-Verification Map',
19
- ...verificationRows,
20
- '',
21
- // SPEC-1406 (DEFECT 5): a subsection with no grounded content is omitted rather than
22
- // filled with a restated-criterion placeholder — prefer omission over filler.
21
+ // SPEC-1406 (DEFECT 5) / SPEC-1435: a subsection with no grounded content is omitted
22
+ // rather than filled with a restated-criterion placeholder — prefer omission over filler.
23
+ ...(filePlan.length > 0 ? ['### File-Level Work Plan', ...filePlan, ''] : []),
24
+ ...(verificationRows.length > 0
25
+ ? ['### Acceptance-To-Verification Map', ...verificationRows, '']
26
+ : []),
23
27
  ...(failureModes.length > 0 ? ['### Edge Cases And Failure Modes', ...failureModes, ''] : []),
24
28
  ...(nonGoals.length > 0 ? ['### Non-Goals And Forbidden Approaches', ...nonGoals, ''] : []),
25
- '### Verification Commands',
26
- ...(verificationCommands.length > 0
27
- ? verificationCommands.map((command) => `- \`${command}\``)
28
- : criteria.length > 0
29
- ? ['- No repository-supported verification command was grounded for this candidate.']
30
- : ['- Needs decision: provide a repository-supported verification command.']),
31
- '',
29
+ ...(verificationCommandLines.length > 0
30
+ ? ['### Verification Commands', ...verificationCommandLines, '']
31
+ : []),
32
32
  ];
33
33
  return lines.join('\n');
34
34
  }
@@ -38,7 +38,7 @@ export function appendImplementationContractIfMissing(specBody, input) {
38
38
  }
39
39
  return `${specBody.trimEnd()}\n\n${buildImplementationContractSection(input)}`;
40
40
  }
41
- function renderFilePlan(files, hasGroundedBehavior) {
41
+ function renderFilePlan(files) {
42
42
  const lines = [];
43
43
  for (const [label, entries] of [
44
44
  ['Create', files.create],
@@ -49,12 +49,7 @@ function renderFilePlan(files, hasGroundedBehavior) {
49
49
  lines.push(`- ${label}: \`${entry.path}\` (${entry.status})`);
50
50
  }
51
51
  }
52
- if (lines.length > 0) {
53
- return lines;
54
- }
55
- return hasGroundedBehavior
56
- ? ['- No file ownership was grounded for this behavior.']
57
- : ['- Needs decision: identify expected source and test files.'];
52
+ return lines;
58
53
  }
59
54
  /**
60
55
  * SPEC-1406 (DEFECT 1, round 2): renders the Acceptance-To-Verification Map body.
@@ -80,7 +75,7 @@ function renderVerificationRows(criteria, testFiles, commands) {
80
75
  return rows;
81
76
  }
82
77
  if (testFiles.length === 0) {
83
- return ['- No repository-supported verification evidence was grounded for these criteria.'];
78
+ return [];
84
79
  }
85
80
  return renderSharedVerificationRow(testFiles, commands);
86
81
  }
@@ -2,15 +2,15 @@ import { technologyValue } from './technology-registry.js';
2
2
  import { fileExists } from '../core/shared/fs.js';
3
3
  // @crash-shield-ignore-file — config/cache reader for Planu-controlled JSON; writer is this codebase, shape guaranteed by build/seed.
4
4
  // engine/project-health-checker.ts — SPEC-180 + SPEC-192 + SPEC-177: Project health check with convention scanning
5
- import { execSync } from 'node:child_process';
6
5
  import { readFile } from 'node:fs/promises';
7
6
  import { join, relative } from 'node:path';
8
7
  import { parseConventions, scanConventions } from './convention-scanner/index.js';
9
8
  import { fastFindPatterns } from './core-bridge.js';
9
+ import { execGitCapture } from './git/exec-git.js';
10
10
  // ── helpers ──────────────────────────────────────────────────────────────────
11
11
  function execSafe(cmd, cwd) {
12
12
  try {
13
- return execSync(cmd, { cwd, stdio: ['pipe', 'pipe', 'pipe'] }).toString();
13
+ return execGitCapture(cmd, cwd);
14
14
  }
15
15
  catch {
16
16
  return '';
@@ -10,11 +10,24 @@ import { findScenariosWithoutTests, parseFrontmatterScenarios, } from './validat
10
10
  import { normalizeExecutableEvidence } from './validator/executable-evidence.js';
11
11
  import { evaluateImplementationContract } from './implementation-contract/index.js';
12
12
  import { extractNormalizedAcceptanceCriteria } from './spec-format/acceptance-criteria.js';
13
+ import { isRetiredScaffoldText } from './spec-format/retired-scaffold.js';
13
14
  import { detectCrossSpecPremiseContradictions } from './contradiction-detector.js';
14
15
  // ── SPEC-784: Technical section quality constants ─────────────────────────────
15
16
  const TECHNICAL_MIN_CHARS = 500;
16
17
  // Detects "See technical.md", "See spec.md", or "See `<file>` technical.md" patterns
17
18
  const TECHNICAL_PLACEHOLDER_REGEX = /\bSee\s+(?:`?[^`\n]+`?\s+)?(?:technical|spec)\.md/i;
19
+ const BDD_CRITERION_RE = /\bGIVEN\b[\s\S]*\bWHEN\b[\s\S]*\bTHEN\b/i;
20
+ function isExecutableBddLine(line) {
21
+ return !isRetiredScaffoldText(line) && BDD_CRITERION_RE.test(line);
22
+ }
23
+ function checkZeroBddCriteria(criteriaLines) {
24
+ if (criteriaLines.length === 0 || criteriaLines.some(isExecutableBddLine)) {
25
+ return [];
26
+ }
27
+ return [
28
+ 'NO_BDD_CRITERIA: no acceptance criterion parses as GIVEN/WHEN/THEN — restate at least one criterion in executable form',
29
+ ];
30
+ }
18
31
  // ── Helpers ──────────────────────────────────────────────────────────────────
19
32
  function scoreHuCompleteness(spec) {
20
33
  const blockers = [];
@@ -73,6 +86,15 @@ function countFrontmatterScenarios(raw) {
73
86
  // Each scenario entry starts with " - title:" (2-space indent)
74
87
  return (block.match(/^\s+-\s+title:/gm) ?? []).length;
75
88
  }
89
+ function frontmatterScenarioScavengeWarning(bddScenarioCount, normalizedCriteria) {
90
+ const bodyCriteriaCount = normalizedCriteria.filter((criterion) => criterion.source === 'body').length;
91
+ if (bddScenarioCount === 0 || bddScenarioCount >= bodyCriteriaCount) {
92
+ return null;
93
+ }
94
+ return (`frontmatter_scenarios_below_body_criteria: frontmatter declares ${String(bddScenarioCount)} ` +
95
+ `scenarios but body renders ${String(bodyCriteriaCount)} acceptance criteria — scenarios may ` +
96
+ `have been scavenged from prose`);
97
+ }
76
98
  async function readFichaContent(spec) {
77
99
  return readSpecTechnicalSection(spec);
78
100
  }
@@ -418,6 +440,7 @@ export async function checkSpecReadiness(spec, mode, projectHash) {
418
440
  ...earsBlockers,
419
441
  ];
420
442
  const allWarnings = [...hu.warnings, ...criteria.warnings, ...files.warnings, ...deps.warnings];
443
+ allBlockers.push(...checkZeroBddCriteria(effectiveCriteriaLines));
421
444
  if (mode === 'strict' && spec.scope !== 'trivial' && spec.difficulty >= 3) {
422
445
  for (const issue of evaluateImplementationContract(huContent, canonicalCriteriaLines)) {
423
446
  allBlockers.push(`${issue.code}: ${issue.message}`);
@@ -432,6 +455,10 @@ export async function checkSpecReadiness(spec, mode, projectHash) {
432
455
  allBlockers.push(`scenarios_missing_tests: scenario "${title}" has no tests array — add tests entries as a block list (tests: with one "- path" line per test) or an inline array (tests: ["path"]) (SPEC-732)`);
433
456
  }
434
457
  }
458
+ const scavengeWarning = frontmatterScenarioScavengeWarning(bddScenarioCount, normalizedCriteria);
459
+ if (scavengeWarning) {
460
+ allWarnings.push(scavengeWarning);
461
+ }
435
462
  // SPEC-1214: BDD-criteria executable-evidence gate — when a spec has no frontmatter
436
463
  // scenarios, its criteria must carry a TEST: marker mapping to an executable test file,
437
464
  // or validation will later score 0 with "No executable scenarios declared".
@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises';
2
2
  import { atomicWriteFile } from '../safety/atomic-write-file.js';
3
3
  import { SECTIONS_WITHOUT_LITERAL_BODY_TEXT, } from '../../types/index.js';
4
4
  import { escapeRegex } from '../../core/shared/strings.js';
5
+ import { propagateMirrors } from './propagate-mirrors.js';
5
6
  function findSectionBounds(content, section) {
6
7
  const headingRe = new RegExp(`^(#{1,6})\\s+${escapeRegex(section)}\\s*$`, 'mi');
7
8
  const match = headingRe.exec(content);
@@ -54,6 +55,7 @@ export async function applyChangesToSpec(specPath, changes) {
54
55
  let modified = content;
55
56
  let applied = 0;
56
57
  const skipped = [];
58
+ const appliedSections = new Set();
57
59
  for (const change of approvedTextChanges) {
58
60
  const bounds = findSectionBounds(modified, change.section);
59
61
  if (bounds === null) {
@@ -92,10 +94,12 @@ export async function applyChangesToSpec(specPath, changes) {
92
94
  change.newValue +
93
95
  modified.slice(absoluteIdx + change.originalValue.length);
94
96
  applied++;
97
+ appliedSections.add(change.section);
95
98
  }
96
99
  if (applied === 0) {
97
100
  return { applied: 0, skipped, fileWritten: false };
98
101
  }
102
+ modified = propagateMirrors(modified, appliedSections);
99
103
  await atomicWriteFile(specPath, modified);
100
104
  return { applied, skipped, fileWritten: true };
101
105
  }
@@ -0,0 +1,3 @@
1
+ import type { AppliedSections } from '../../types/index.js';
2
+ export declare function propagateMirrors(content: string, appliedSections: AppliedSections): string;
3
+ //# sourceMappingURL=propagate-mirrors.d.ts.map