@planu/cli 5.7.5 → 5.7.7

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 (210) hide show
  1. package/CHANGELOG.md +88 -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-review-panel-trigger.sh +2 -3
  8. package/dist/config/hook-templates/planu-session-safeguard.sh +22 -15
  9. package/dist/config/official-sdd-tools.d.ts +1 -1
  10. package/dist/config/official-sdd-tools.js +11 -24
  11. package/dist/config/registries/hosts/codex.json +14 -30
  12. package/dist/config/registries/hosts/opencode.json +1 -6
  13. package/dist/config/server-instructions.js +0 -21
  14. package/dist/config/skill-templates/planu-context-assets.md +3 -6
  15. package/dist/config/skill-templates/planu-implement.md +2 -2
  16. package/dist/config/skill-templates/planu-multi-teammate-review.md +8 -12
  17. package/dist/config/skill-templates/planu-release.md +2 -7
  18. package/dist/config/skill-templates/planu-validate.md +3 -3
  19. package/dist/config/spec-templates/crud-rest-api/template.json +5 -1
  20. package/dist/config/spec-templates/file-upload-s3/template.json +5 -1
  21. package/dist/config/spec-templates/stripe-payments/template.json +5 -1
  22. package/dist/config/spec-templates/webhook-system/template.json +5 -1
  23. package/dist/config/subagent-templates/planu-challenger.md +4 -9
  24. package/dist/config/subagent-templates/planu-spec-implementer.md +2 -4
  25. package/dist/config/subagent-templates/planu-validator.md +5 -8
  26. package/dist/config/tool-groups.json +6 -144
  27. package/dist/engine/ambiguity-scorer.js +40 -1
  28. package/dist/engine/autopilot/bootstrap.js +1 -1
  29. package/dist/engine/code-impact-analyzer.js +2 -6
  30. package/dist/engine/context-orchestrator/index.js +2 -2
  31. package/dist/engine/convention-scanner/codebase-scanner.js +2 -2
  32. package/dist/engine/dogfooding/runtime-gap-detector.js +2 -2
  33. package/dist/engine/evidence-gates/artifact-reader.d.ts +1 -0
  34. package/dist/engine/evidence-gates/artifact-reader.js +1 -1
  35. package/dist/engine/evidence-gates/evidence-autofill.d.ts +13 -5
  36. package/dist/engine/evidence-gates/evidence-autofill.js +106 -43
  37. package/dist/engine/evidence-gates/evidence-skeletons.js +3 -1
  38. package/dist/engine/evidence-gates/lifecycle-gate.js +7 -4
  39. package/dist/engine/evidence-index/index-builder.js +21 -4
  40. package/dist/engine/execution/job-runtime.js +1 -1
  41. package/dist/engine/execution/package-handoff-job-executor.d.ts +22 -0
  42. package/dist/engine/execution/package-handoff-job-executor.js +490 -0
  43. package/dist/engine/execution/validate-job-executor.js +20 -8
  44. package/dist/engine/git/exec-git.d.ts +4 -0
  45. package/dist/engine/git/exec-git.js +14 -0
  46. package/dist/engine/handoff-artifacts/implementation-review-reader.js +15 -2
  47. package/dist/engine/handoff-artifacts/schemas.d.ts +2 -0
  48. package/dist/engine/handoff-artifacts/schemas.js +1 -0
  49. package/dist/engine/handoff-format.d.ts +36 -0
  50. package/dist/engine/handoff-format.js +314 -0
  51. package/dist/engine/handoff-packager.d.ts +5 -3
  52. package/dist/engine/handoff-packager.js +89 -32
  53. package/dist/engine/host-tool-filter.js +20 -49
  54. package/dist/engine/human-summary.js +14 -3
  55. package/dist/engine/implementation-contract/common.d.ts +8 -3
  56. package/dist/engine/implementation-contract/common.js +9 -6
  57. package/dist/engine/implementation-contract/evaluator.js +4 -6
  58. package/dist/engine/implementation-contract/renderer.js +18 -23
  59. package/dist/engine/project-health-checker.js +2 -2
  60. package/dist/engine/readiness-checker.js +43 -5
  61. package/dist/engine/reconcile/apply-changes.js +4 -0
  62. package/dist/engine/reconcile/propagate-mirrors.d.ts +3 -0
  63. package/dist/engine/reconcile/propagate-mirrors.js +311 -0
  64. package/dist/engine/self-healing/healer.js +12 -5
  65. package/dist/engine/self-healing/strategies/lint-fix.d.ts +1 -1
  66. package/dist/engine/self-healing/strategies/lint-fix.js +4 -1
  67. package/dist/engine/self-healing/strategies/test-fix.d.ts +4 -3
  68. package/dist/engine/self-healing/strategies/test-fix.js +32 -14
  69. package/dist/engine/self-healing/strategies/typescript-fix.d.ts +1 -1
  70. package/dist/engine/self-healing/strategies/typescript-fix.js +28 -10
  71. package/dist/engine/session/checkpoint-writer.js +8 -4
  72. package/dist/engine/session/session-tracker.d.ts +5 -16
  73. package/dist/engine/session/session-tracker.js +29 -26
  74. package/dist/engine/session-state/writer.js +3 -11
  75. package/dist/engine/skill-generator/workflow-skill-generator.d.ts +4 -2
  76. package/dist/engine/skill-generator/workflow-skill-generator.js +113 -38
  77. package/dist/engine/spec-format/bdd-parser.d.ts +1 -11
  78. package/dist/engine/spec-format/bdd-parser.js +40 -11
  79. package/dist/engine/spec-format/lean-spec-generator.js +32 -12
  80. package/dist/engine/spec-format/retired-scaffold.d.ts +3 -0
  81. package/dist/engine/spec-format/retired-scaffold.js +5 -0
  82. package/dist/engine/spec-format/technical-md-populator.d.ts +1 -0
  83. package/dist/engine/spec-format/technical-md-populator.js +27 -0
  84. package/dist/engine/spec-format/unified-spec-builder.d.ts +1 -0
  85. package/dist/engine/spec-format/unified-spec-builder.js +30 -20
  86. package/dist/engine/spec-grounding/contract.d.ts +3 -3
  87. package/dist/engine/spec-grounding/contract.js +65 -4
  88. package/dist/engine/spec-grounding/grounding-entry-parser.d.ts +5 -0
  89. package/dist/engine/spec-grounding/grounding-entry-parser.js +185 -0
  90. package/dist/engine/spec-quality/generic-output-gate.js +37 -3
  91. package/dist/engine/technical-enricher/index.js +4 -1
  92. package/dist/engine/type-safety-gate.js +3 -11
  93. package/dist/engine/universal-rules/catalog.js +0 -2
  94. package/dist/engine/universal-rules/rules/planu-workflow.js +1 -1
  95. package/dist/engine/validation/durable-validation.d.ts +3 -2
  96. package/dist/engine/validation/durable-validation.js +106 -40
  97. package/dist/engine/validation/validation-freshness.d.ts +3 -2
  98. package/dist/engine/validation/validation-freshness.js +22 -16
  99. package/dist/engine/validation/validation-worktree.js +34 -0
  100. package/dist/hosts/claude-code/ux/mcp-prompts.js +8 -8
  101. package/dist/index.js +3 -17
  102. package/dist/resources/process.js +38 -65
  103. package/dist/storage/session-state-store.js +2 -0
  104. package/dist/storage/spec-store.js +8 -1
  105. package/dist/tools/bump-spec-version.js +5 -5
  106. package/dist/tools/challenge-spec/scenarios-utils.js +2 -2
  107. package/dist/tools/configure-checkpoint-policy.js +3 -3
  108. package/dist/tools/create-spec-helpers.js +2 -5
  109. package/dist/tools/create-spec.js +21 -4
  110. package/dist/tools/facilitate.js +3 -6
  111. package/dist/tools/git/branch-ops.js +16 -4
  112. package/dist/tools/github-release-handler.js +1 -20
  113. package/dist/tools/init-project/agents-md-writer.js +6 -6
  114. package/dist/tools/init-project/claude-md-generator.d.ts +0 -1
  115. package/dist/tools/init-project/claude-md-generator.js +0 -35
  116. package/dist/tools/init-project/handler.js +7 -5
  117. package/dist/tools/init-project/per-client-files-writer.js +5 -5
  118. package/dist/tools/init-project/planu-workflow-generator.js +2 -13
  119. package/dist/tools/init-project/portable-index-reconciler.d.ts +2 -1
  120. package/dist/tools/init-project/portable-index-reconciler.js +7 -2
  121. package/dist/tools/init-project/rules-generator.d.ts +0 -11
  122. package/dist/tools/init-project/rules-generator.js +4 -85
  123. package/dist/tools/init-project/scaffold-writer.d.ts +0 -1
  124. package/dist/tools/init-project/scaffold-writer.js +1 -12
  125. package/dist/tools/init-project/skills-multi-teammate-review-writer.js +1 -1
  126. package/dist/tools/jobs/handlers.d.ts +0 -6
  127. package/dist/tools/jobs/handlers.js +13 -40
  128. package/dist/tools/list-specs.js +6 -21
  129. package/dist/tools/multi-teammate-review.js +1 -14
  130. package/dist/tools/package-handoff.d.ts +3 -2
  131. package/dist/tools/package-handoff.js +153 -427
  132. package/dist/tools/reconcile-session-safeguard-hook.js +22 -15
  133. package/dist/tools/register-platform-tools/design-stack-tools.js +3 -3
  134. package/dist/tools/register-sdd-tools.js +0 -23
  135. package/dist/tools/register-spec-tools/analysis-tools.js +22 -9
  136. package/dist/tools/register-spec-tools/core-spec-tools.js +115 -70
  137. package/dist/tools/schemas/index.d.ts +1 -1
  138. package/dist/tools/schemas/index.js +1 -1
  139. package/dist/tools/schemas/output-schemas.d.ts +1 -18
  140. package/dist/tools/schemas/output-schemas.js +1 -11
  141. package/dist/tools/schemas/package-handoff-output-schema.d.ts +40 -0
  142. package/dist/tools/schemas/package-handoff-output-schema.js +68 -0
  143. package/dist/tools/schemas/validate-output-schema.d.ts +1 -2
  144. package/dist/tools/schemas/validate-output-schema.js +3 -2
  145. package/dist/tools/session-checkpoint.js +2 -2
  146. package/dist/tools/skill-registry/index.d.ts +0 -1
  147. package/dist/tools/skill-registry/index.js +0 -1
  148. package/dist/tools/sync-spec-state-handler.d.ts +2 -2
  149. package/dist/tools/sync-spec-state-handler.js +23 -3
  150. package/dist/tools/tool-registry/core-tools.js +72 -164
  151. package/dist/tools/tool-registry/group-infra.js +2 -39
  152. package/dist/tools/tool-registry/group-quality-compliance.js +20 -15
  153. package/dist/tools/tool-registry-helpers.js +6 -2
  154. package/dist/tools/update-status/batch.js +17 -0
  155. package/dist/tools/update-status/dod-gates.js +14 -1
  156. package/dist/tools/update-status/done-receipt-verifier.d.ts +22 -0
  157. package/dist/tools/update-status/done-receipt-verifier.js +56 -26
  158. package/dist/tools/update-status/evidence-gate.js +10 -21
  159. package/dist/tools/update-status/file-sync.d.ts +1 -0
  160. package/dist/tools/update-status/file-sync.js +6 -0
  161. package/dist/tools/update-status/index.d.ts +6 -0
  162. package/dist/tools/update-status/index.js +39 -44
  163. package/dist/tools/update-status-actions.js +2 -7
  164. package/dist/tools/validate.js +34 -28
  165. package/dist/transports/oauth-validator.js +12 -1
  166. package/dist/transports/transport-factory.d.ts +2 -1
  167. package/dist/transports/transport-factory.js +19 -0
  168. package/dist/types/common/primitives.d.ts +2 -0
  169. package/dist/types/durable-job.d.ts +45 -0
  170. package/dist/types/durable-validation.d.ts +1 -1
  171. package/dist/types/evidence-autofill.d.ts +13 -1
  172. package/dist/types/handoff-artifacts.d.ts +2 -1
  173. package/dist/types/readiness.d.ts +8 -1
  174. package/dist/types/reconcile.d.ts +6 -0
  175. package/dist/types/skill-registry.d.ts +26 -1
  176. package/dist/types/spec/core.d.ts +5 -0
  177. package/dist/types/spec/inputs.d.ts +2 -2
  178. package/dist/types/spec-format.d.ts +11 -1
  179. package/dist/types/spec-grounding.d.ts +26 -0
  180. package/dist/types/transport.d.ts +1 -0
  181. package/dist/types/validation-receipt.d.ts +27 -0
  182. package/package.json +3 -2
  183. package/planu-plugin.json +13 -26
  184. package/src/i18n/messages/en.json +1 -1
  185. package/src/i18n/messages/es.json +1 -1
  186. package/src/i18n/messages/pt.json +1 -1
  187. package/dist/engine/skill-generator/conventions-hasher.d.ts +0 -7
  188. package/dist/engine/skill-generator/conventions-hasher.js +0 -24
  189. package/dist/engine/universal-rules/rules/agent-teams.d.ts +0 -3
  190. package/dist/engine/universal-rules/rules/agent-teams.js +0 -63
  191. package/dist/tools/clarify-requirements/multiple-choice.d.ts +0 -30
  192. package/dist/tools/clarify-requirements/multiple-choice.js +0 -306
  193. package/dist/tools/clarify-requirements/questions-context.d.ts +0 -9
  194. package/dist/tools/clarify-requirements/questions-context.js +0 -74
  195. package/dist/tools/clarify-requirements/questions.d.ts +0 -16
  196. package/dist/tools/clarify-requirements/questions.js +0 -96
  197. package/dist/tools/clarify-requirements.d.ts +0 -4
  198. package/dist/tools/clarify-requirements.js +0 -314
  199. package/dist/tools/code-graph-handler.d.ts +0 -6
  200. package/dist/tools/code-graph-handler.js +0 -72
  201. package/dist/tools/create-rule.d.ts +0 -4
  202. package/dist/tools/create-rule.js +0 -107
  203. package/dist/tools/feedback-handler.d.ts +0 -7
  204. package/dist/tools/feedback-handler.js +0 -150
  205. package/dist/tools/semantic-search-handler.d.ts +0 -7
  206. package/dist/tools/semantic-search-handler.js +0 -71
  207. package/dist/tools/skill-registry/search.d.ts +0 -11
  208. package/dist/tools/skill-registry/search.js +0 -159
  209. package/dist/tools/tool-registry/deprecated-stubs.d.ts +0 -19
  210. package/dist/tools/tool-registry/deprecated-stubs.js +0 -65
@@ -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) {
@@ -32,7 +32,9 @@ function outOfScopeFromBody(specBody) {
32
32
  }
33
33
  /** Build a strict-schema discovery.json skeleton grounded in the spec's own grounding metadata. */
34
34
  export function generateDiscoverySkeleton(args) {
35
- const records = args.body ? parseCriterionGroundingRecords(specFrontmatter(args.body)) : [];
35
+ const records = args.body
36
+ ? parseCriterionGroundingRecords(specFrontmatter(args.body)).records
37
+ : [];
36
38
  const rules = records.length > 0 ? [...new Set(records.map((record) => record.text))] : [DEFAULT_RULE];
37
39
  const examples = records.length > 0
38
40
  ? records.map((record) => ({
@@ -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) {
@@ -1,5 +1,5 @@
1
- import { stat } from 'node:fs/promises';
2
1
  import { isAbsolute, join, normalize } from 'node:path';
2
+ import { pathExistsByStat as pathExists } from '../../core/shared/fs.js';
3
3
  import { createCriterionIdentity, criterionIdentitiesMatch } from '../criterion-identity.js';
4
4
  export function criterionKey(criterion) {
5
5
  return createCriterionIdentity(criterion).stableKey;
@@ -9,6 +9,10 @@ function truncateAtEvidenceMarker(value) {
9
9
  const markerIndex = value.search(EVIDENCE_MARKER);
10
10
  return markerIndex === -1 ? value : value.slice(0, markerIndex);
11
11
  }
12
+ const LINE_CITATION_SUFFIX = /:\d+(?:-\d+)?$/;
13
+ function pathForExistenceCheck(value) {
14
+ return value.replace(LINE_CITATION_SUFFIX, '');
15
+ }
12
16
  export async function buildSpecEvidenceIndex(args) {
13
17
  const diagnostics = [...args.artifacts.invalidArtifacts];
14
18
  const matrixRows = args.artifacts.traceabilityMatrix?.rows ?? [];
@@ -129,12 +133,25 @@ async function pathRecord(kind, value, source, args, fieldName) {
129
133
  if (!args.projectPath) {
130
134
  return record(kind, value, 'missing', source, evidencePathReason(fieldName, 'projectPath unavailable'));
131
135
  }
132
- const exists = await stat(safe.path)
133
- .then(() => true)
134
- .catch(() => false);
136
+ const literalExists = await pathExists(safe.path);
137
+ const exists = literalExists || (await fallbackExists(value, fieldName, args.projectPath));
135
138
  const missingReason = evidencePathReason(fieldName, `Missing path: ${value}`);
136
139
  return record(kind, value, exists ? 'valid' : missingPathStatus(fieldName), source, exists ? undefined : missingReason);
137
140
  }
141
+ async function fallbackExists(value, fieldName, projectPath) {
142
+ if (fieldName === 'changedFiles') {
143
+ return false;
144
+ }
145
+ const strippedValue = pathForExistenceCheck(value);
146
+ if (strippedValue === value || strippedValue.length === 0) {
147
+ return false;
148
+ }
149
+ const strippedSafe = resolveSafeProjectPath(strippedValue, projectPath, fieldName);
150
+ if (!strippedSafe.ok) {
151
+ return false;
152
+ }
153
+ return pathExists(strippedSafe.path);
154
+ }
138
155
  function missingPathStatus(fieldName) {
139
156
  return fieldName === 'changedFiles' ? 'missing' : 'stale';
140
157
  }
@@ -249,7 +249,7 @@ function isQueueRelevant(job) {
249
249
  return true;
250
250
  }
251
251
  const kind = job.payload.kind;
252
- return kind === undefined || kind === 'validate';
252
+ return kind === undefined || kind === 'validate' || kind === 'package-handoff';
253
253
  }
254
254
  function remainingBudgetMs(job, now = Date.now()) {
255
255
  const payload = job.payload && typeof job.payload === 'object'
@@ -0,0 +1,22 @@
1
+ import { DurableJobRuntime } from './job-runtime.js';
2
+ import type { DurableJobRecord, PackageHandoffDurableJobPayload, PackageHandoffStageResult } from '../../types/durable-job.js';
3
+ export declare function isPackageHandoffJob(value: unknown): boolean;
4
+ export declare function parsePackageHandoffPayload(value: unknown): PackageHandoffDurableJobPayload | undefined;
5
+ /** Bounds one stage with AbortSignal.timeout + Promise.race; the stage's own promise is orphaned on timeout. */
6
+ export declare function runStageWithDeadline<T>(stage: string, budgetMs: number, fn: (signal: AbortSignal) => Promise<T>): Promise<PackageHandoffStageResult<T>>;
7
+ /** Executes accepted package-handoff jobs directly against the packaging engine, never through MCP. */
8
+ export declare class PackageHandoffJobExecutor {
9
+ private readonly runtime;
10
+ private readonly workerId;
11
+ constructor(runtime: DurableJobRuntime, workerId?: string);
12
+ runOnce(projectId: string, workspaceId?: string): Promise<readonly DurableJobRecord[]>;
13
+ runUntilIdle(projectId: string, workspaceId?: string): Promise<void>;
14
+ private candidates;
15
+ private execute;
16
+ private runJob;
17
+ }
18
+ /** Schedules background execution after the submit response can be flushed. */
19
+ export declare function schedulePackageHandoffJobExecution(projectId: string, workspaceId?: string): void;
20
+ /** Re-enqueues accepted/checkpointed package-handoff work discovered after a process restart. */
21
+ export declare function recoverPackageHandoffJobsAtStartup(): void;
22
+ //# sourceMappingURL=package-handoff-job-executor.d.ts.map