cc-devflow 4.5.11 → 4.5.12

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 (185) hide show
  1. package/.claude/skills/cc-act/CHANGELOG.md +18 -0
  2. package/.claude/skills/cc-act/PLAYBOOK.md +17 -269
  3. package/.claude/skills/cc-act/SKILL.md +38 -425
  4. package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_INDEX_TEMPLATE.md +2 -13
  5. package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_TEMPLATE.md +1 -9
  6. package/.claude/skills/cc-act/assets/PR_BRIEF_TEMPLATE.md +21 -177
  7. package/.claude/skills/cc-act/references/closure-contract.md +12 -63
  8. package/.claude/skills/cc-act/references/git-commit-guidelines.md +5 -5
  9. package/.claude/skills/cc-act/scripts/cc-act-common.sh +5 -322
  10. package/.claude/skills/cc-act/scripts/detect-ship-target.sh +11 -2
  11. package/.claude/skills/cc-act/scripts/inspect-git-index.sh +58 -0
  12. package/.claude/skills/cc-act/scripts/render-pr-brief.sh +40 -440
  13. package/.claude/skills/cc-act/scripts/verify-act-gate.sh +10 -50
  14. package/.claude/skills/cc-check/CHANGELOG.md +18 -0
  15. package/.claude/skills/cc-check/PLAYBOOK.md +19 -273
  16. package/.claude/skills/cc-check/SKILL.md +33 -456
  17. package/.claude/skills/cc-check/references/review-contract.md +12 -147
  18. package/.claude/skills/cc-dev/CHANGELOG.md +15 -0
  19. package/.claude/skills/cc-dev/PLAYBOOK.md +1 -1
  20. package/.claude/skills/cc-dev/SKILL.md +52 -137
  21. package/.claude/skills/cc-dev/scripts/resolve-cc-devflow.sh +181 -0
  22. package/.claude/skills/cc-do/CHANGELOG.md +11 -0
  23. package/.claude/skills/cc-do/PLAYBOOK.md +19 -113
  24. package/.claude/skills/cc-do/SKILL.md +39 -245
  25. package/.claude/skills/cc-do/references/execution-recovery.md +15 -109
  26. package/.claude/skills/cc-do/scripts/cc-do-common.sh +5 -57
  27. package/.claude/skills/cc-do/scripts/check-task-status.sh +35 -65
  28. package/.claude/skills/cc-do/scripts/mark-task-complete.sh +9 -46
  29. package/.claude/skills/cc-do/scripts/select-ready-tasks.sh +29 -97
  30. package/.claude/skills/cc-investigate/CHANGELOG.md +16 -0
  31. package/.claude/skills/cc-investigate/PLAYBOOK.md +20 -180
  32. package/.claude/skills/cc-investigate/SKILL.md +64 -246
  33. package/.claude/skills/cc-investigate/assets/TASKS_TEMPLATE.md +48 -98
  34. package/.claude/skills/cc-investigate/references/investigation-contract.md +14 -218
  35. package/.claude/skills/cc-next/CHANGELOG.md +6 -0
  36. package/.claude/skills/cc-next/PLAYBOOK.md +12 -8
  37. package/.claude/skills/cc-next/SKILL.md +34 -140
  38. package/.claude/skills/cc-plan/CHANGELOG.md +16 -0
  39. package/.claude/skills/cc-plan/PLAYBOOK.md +22 -161
  40. package/.claude/skills/cc-plan/SKILL.md +45 -295
  41. package/.claude/skills/cc-plan/assets/TASKS_TEMPLATE.md +30 -228
  42. package/.claude/skills/cc-plan/references/planning-contract.md +24 -161
  43. package/.claude/skills/cc-plan/scripts/next-change-key.sh +8 -44
  44. package/.claude/skills/cc-plan/scripts/parse-task-dependencies.js +2 -2
  45. package/.claude/skills/cc-plan/scripts/validate-scope.sh +1 -1
  46. package/.claude/skills/cc-pr-land/SKILL.md +14 -114
  47. package/.claude/skills/cc-pr-review/CHANGELOG.md +4 -0
  48. package/.claude/skills/cc-pr-review/SKILL.md +20 -103
  49. package/.claude/skills/cc-review/CHANGELOG.md +17 -0
  50. package/.claude/skills/cc-review/PLAYBOOK.md +13 -86
  51. package/.claude/skills/cc-review/SKILL.md +53 -241
  52. package/.claude/skills/cc-review/references/e2e-and-plugin-verification.md +2 -2
  53. package/.claude/skills/cc-review/references/implementation-review-branch.md +7 -147
  54. package/.claude/skills/cc-review/references/plan-review-branch.md +5 -147
  55. package/.claude/skills/cc-review/references/review-methods.md +10 -218
  56. package/.claude/skills/cc-review/scripts/collect-review-context.sh +4 -63
  57. package/.claude/skills/cc-roadmap/PLAYBOOK.md +1 -1
  58. package/.claude/skills/cc-roadmap/SKILL.md +3 -3
  59. package/.claude/skills/cc-simplify/CHANGELOG.md +7 -0
  60. package/.claude/skills/cc-simplify/SKILL.md +26 -21
  61. package/.claude/skills/cc-spec-init/PLAYBOOK.md +12 -48
  62. package/.claude/skills/cc-spec-init/SKILL.md +29 -132
  63. package/.claude/skills/cc-spec-init/references/spec-contract.md +8 -17
  64. package/CHANGELOG.md +13 -0
  65. package/bin/cc-devflow-cli.js +20 -260
  66. package/bin/cc-devflow.js +44 -7
  67. package/docs/commands/README.md +1 -1
  68. package/docs/commands/README.zh-CN.md +1 -1
  69. package/docs/examples/README.md +1 -1
  70. package/docs/examples/START-HERE.md +14 -15
  71. package/docs/examples/example-bindings.json +11 -11
  72. package/docs/examples/full-design-blocked/README.md +4 -6
  73. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/{planning/tasks.md → task.md} +20 -15
  74. package/docs/examples/local-handoff/README.md +8 -11
  75. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/handoff/pr-brief.md +31 -0
  76. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/{planning/tasks.md → task.md} +18 -13
  77. package/docs/examples/pdca-loop/README.md +6 -9
  78. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/handoff/pr-brief.md +9 -11
  79. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/{planning/tasks.md → task.md} +18 -13
  80. package/docs/examples/scripts/check-example-bindings.sh +11 -62
  81. package/docs/guides/artifact-contract.md +10 -40
  82. package/docs/guides/getting-started.md +8 -8
  83. package/docs/guides/getting-started.zh-CN.md +8 -8
  84. package/docs/guides/minimize-artifacts.md +16 -130
  85. package/docs/guides/project-postmortem.md +14 -71
  86. package/lib/compiler/__tests__/skills-registry.test.js +9 -8
  87. package/lib/compiler/resource-copier.js +29 -0
  88. package/lib/skill-runtime/__tests__/archive-change.test.js +2 -2
  89. package/lib/skill-runtime/__tests__/benchmark-skills.test.js +3 -3
  90. package/lib/skill-runtime/__tests__/cli-bootstrap.integration.test.js +14 -4
  91. package/lib/skill-runtime/errors.js +3 -3
  92. package/lib/skill-runtime/index.js +5 -23
  93. package/lib/skill-runtime/paths.js +5 -52
  94. package/lib/skill-runtime/query-registry.js +4 -4
  95. package/lib/skill-runtime/query.js +89 -201
  96. package/lib/skill-runtime/store.js +4 -40
  97. package/lib/skill-runtime/trace.js +2 -2
  98. package/package.json +2 -5
  99. package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_PRINCIPLES_TEMPLATE.md +0 -29
  100. package/.claude/skills/cc-act/assets/RELEASE_NOTE_TEMPLATE.md +0 -54
  101. package/.claude/skills/cc-act/scripts/generate-status-report.sh +0 -92
  102. package/.claude/skills/cc-act/scripts/sync-act-docs.sh +0 -355
  103. package/.claude/skills/cc-check/assets/REPORT_CARD_TEMPLATE.json +0 -234
  104. package/.claude/skills/cc-check/scripts/render-report-card.js +0 -438
  105. package/.claude/skills/cc-check/scripts/verify-gate.sh +0 -85
  106. package/.claude/skills/cc-do/scripts/build-task-context.sh +0 -175
  107. package/.claude/skills/cc-do/scripts/record-review-decision.sh +0 -88
  108. package/.claude/skills/cc-do/scripts/recover-workflow.sh +0 -82
  109. package/.claude/skills/cc-do/scripts/run-problem-analysis.sh +0 -70
  110. package/.claude/skills/cc-do/scripts/verify-task-gates.sh +0 -109
  111. package/.claude/skills/cc-do/scripts/write-task-checkpoint.sh +0 -92
  112. package/.claude/skills/cc-investigate/assets/TASK_MANIFEST_TEMPLATE.json +0 -224
  113. package/.claude/skills/cc-plan/assets/TASK_MANIFEST_TEMPLATE.json +0 -178
  114. package/.claude/skills/cc-spec-init/assets/CHANGE_META_TEMPLATE.json +0 -28
  115. package/.claude/skills/cc-spec-init/scripts/validate-spec-links.sh +0 -45
  116. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/design.md +0 -234
  117. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/task-manifest.json +0 -488
  118. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/review/report-card.json +0 -189
  119. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/handoff/resume-index.md +0 -39
  120. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/handoff/status.md +0 -29
  121. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/design.md +0 -123
  122. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/task-manifest.json +0 -292
  123. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/review/report-card.json +0 -136
  124. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/handoff/status.md +0 -29
  125. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/design.md +0 -124
  126. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/task-manifest.json +0 -292
  127. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/review/report-card.json +0 -136
  128. package/docs/get-shit-done-strategy-audit.md +0 -518
  129. package/docs/skill-runtime-migration.md +0 -46
  130. package/lib/skill-runtime/__tests__/approve.test.js +0 -92
  131. package/lib/skill-runtime/__tests__/autopilot.test.js +0 -253
  132. package/lib/skill-runtime/__tests__/benchmark-artifacts.test.js +0 -165
  133. package/lib/skill-runtime/__tests__/delegation.test.js +0 -97
  134. package/lib/skill-runtime/__tests__/dispatch.test.js +0 -237
  135. package/lib/skill-runtime/__tests__/intent.test.js +0 -203
  136. package/lib/skill-runtime/__tests__/lifecycle.test.js +0 -169
  137. package/lib/skill-runtime/__tests__/planner.tdd.test.js +0 -331
  138. package/lib/skill-runtime/__tests__/prepare-pr.test.js +0 -126
  139. package/lib/skill-runtime/__tests__/query.test.js +0 -860
  140. package/lib/skill-runtime/__tests__/readiness.test.js +0 -53
  141. package/lib/skill-runtime/__tests__/release.test.js +0 -85
  142. package/lib/skill-runtime/__tests__/review-check-integration.test.js +0 -148
  143. package/lib/skill-runtime/__tests__/review-records.test.js +0 -619
  144. package/lib/skill-runtime/__tests__/runtime.integration.test.js +0 -351
  145. package/lib/skill-runtime/__tests__/schemas.test.js +0 -337
  146. package/lib/skill-runtime/__tests__/task-contract-migrate.test.js +0 -137
  147. package/lib/skill-runtime/__tests__/task-contract.test.js +0 -874
  148. package/lib/skill-runtime/__tests__/team-state.test.js +0 -51
  149. package/lib/skill-runtime/__tests__/verify-artifacts.test.js +0 -203
  150. package/lib/skill-runtime/__tests__/worker-run.test.js +0 -275
  151. package/lib/skill-runtime/__tests__/worker.test.js +0 -56
  152. package/lib/skill-runtime/__tests__/workflow-context-legacy-fallback.test.js +0 -31
  153. package/lib/skill-runtime/__tests__/workflow-context.test.js +0 -98
  154. package/lib/skill-runtime/artifacts.js +0 -88
  155. package/lib/skill-runtime/context-index.js +0 -545
  156. package/lib/skill-runtime/delegation.js +0 -533
  157. package/lib/skill-runtime/intent.js +0 -309
  158. package/lib/skill-runtime/lifecycle.js +0 -294
  159. package/lib/skill-runtime/operations/CLAUDE.md +0 -19
  160. package/lib/skill-runtime/operations/approve.js +0 -81
  161. package/lib/skill-runtime/operations/autopilot-core.js +0 -337
  162. package/lib/skill-runtime/operations/autopilot-execution.js +0 -307
  163. package/lib/skill-runtime/operations/autopilot-shared.js +0 -48
  164. package/lib/skill-runtime/operations/autopilot.js +0 -163
  165. package/lib/skill-runtime/operations/dispatch.js +0 -416
  166. package/lib/skill-runtime/operations/init.js +0 -60
  167. package/lib/skill-runtime/operations/janitor.js +0 -61
  168. package/lib/skill-runtime/operations/plan.js +0 -59
  169. package/lib/skill-runtime/operations/prepare-pr.js +0 -25
  170. package/lib/skill-runtime/operations/release.js +0 -99
  171. package/lib/skill-runtime/operations/resume.js +0 -126
  172. package/lib/skill-runtime/operations/review-records.js +0 -265
  173. package/lib/skill-runtime/operations/snapshot.js +0 -45
  174. package/lib/skill-runtime/operations/task-contract.js +0 -593
  175. package/lib/skill-runtime/operations/verify.js +0 -170
  176. package/lib/skill-runtime/operations/worker-run.js +0 -531
  177. package/lib/skill-runtime/operations/worker.js +0 -33
  178. package/lib/skill-runtime/planner.js +0 -539
  179. package/lib/skill-runtime/readiness.js +0 -84
  180. package/lib/skill-runtime/review-records.js +0 -123
  181. package/lib/skill-runtime/review.js +0 -855
  182. package/lib/skill-runtime/schemas.js +0 -746
  183. package/lib/skill-runtime/task-contract.js +0 -188
  184. package/lib/skill-runtime/team-state.js +0 -122
  185. package/lib/skill-runtime/workflow-context.js +0 -748
@@ -1,874 +0,0 @@
1
- /**
2
- * [INPUT]: 依赖 task-contract parser、operation 层与 CLI 子进程。
3
- * [OUTPUT]: 通过 parser 返回值、生成文件、validate 规则与 CLI stderr 证明 tasks.md contract 行为。
4
- * [POS]: REQ-003-minimize-workflow-artifacts T002-T006 的 Red/Green 证据。
5
- * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
- */
7
-
8
- const {
9
- extractTasksContractSummary,
10
- extractTasksRootCauseContract
11
- } = require('../task-contract');
12
- const fs = require('fs');
13
- const os = require('os');
14
- const path = require('path');
15
- const { spawnSync } = require('child_process');
16
- const { runCompile, runValidate } = require('../operations/task-contract');
17
-
18
- const CLI_BIN = path.resolve(__dirname, '../../..', 'bin/cc-devflow-cli.js');
19
-
20
- const SAMPLE_WITH_HEADING = `# REQ-003-minimize-workflow-artifacts
21
-
22
- ## Plan Meta
23
-
24
- Some plan meta prose.
25
-
26
- ## Contract Summary
27
-
28
- Change: REQ-003-minimize-workflow-artifacts
29
- Mode: plan
30
- Profile: deep
31
- Approval: approved (ExitPlanMode 2026-05-12)
32
-
33
- Goal:
34
- - Collapse default artifact surface.
35
- - Switch workflow-context canonical refs.
36
-
37
- Do Not Do:
38
- - Do not migrate REQ-001 / REQ-002 by default.
39
- - Do not rebuild task-manifest.json.
40
-
41
- Approved Direction:
42
- - Single REQ-003 PR covering v2 PR-A + PR-B + PR-C.
43
- - 18 vertical slices.
44
-
45
- Acceptance:
46
- - New REQ-003-example directory emits only tasks.md.
47
- - verify:artifacts passes.
48
-
49
- Verification:
50
-
51
- \`\`\`bash
52
- npm test
53
- npm run verify:artifacts
54
- \`\`\`
55
-
56
- Risk / Escalate If:
57
- - benchmark-workflow-context-tokens fails for REQ-001.
58
- - Total LOC exceeds 3500.
59
-
60
- ## Implementation Surface Map
61
-
62
- Other content that must not leak into the Contract Summary block.
63
- `;
64
-
65
- const SAMPLE_WITHOUT_HEADING = `# REQ-999
66
-
67
- ## Plan Meta
68
-
69
- Just plan meta.
70
-
71
- ## Something Else
72
-
73
- Unrelated.
74
- `;
75
-
76
- describe('extractTasksContractSummary', () => {
77
- describe('when `## Contract Summary` heading is present', () => {
78
- test('returns found:true', () => {
79
- const result = extractTasksContractSummary(SAMPLE_WITH_HEADING);
80
- expect(result.found).toBe(true);
81
- });
82
-
83
- test('content spans from the heading line up to (but not including) the next H2 heading', () => {
84
- const result = extractTasksContractSummary(SAMPLE_WITH_HEADING);
85
- expect(result.content).toMatch(/^## Contract Summary/);
86
- expect(result.content).not.toMatch(/## Implementation Surface Map/);
87
- expect(result.content).not.toMatch(/Other content that must not leak/);
88
- });
89
-
90
- test('content includes the trailing risk block before the next heading', () => {
91
- const result = extractTasksContractSummary(SAMPLE_WITH_HEADING);
92
- expect(result.content).toMatch(/Risk \/ Escalate If:/);
93
- expect(result.content).toMatch(/Total LOC exceeds 3500\./);
94
- });
95
-
96
- test('fields parses Change as a single-line KV', () => {
97
- const result = extractTasksContractSummary(SAMPLE_WITH_HEADING);
98
- expect(result.fields.change).toBe('REQ-003-minimize-workflow-artifacts');
99
- });
100
-
101
- test('fields parses Mode and Profile as single-line KVs', () => {
102
- const result = extractTasksContractSummary(SAMPLE_WITH_HEADING);
103
- expect(result.fields.mode).toBe('plan');
104
- expect(result.fields.profile).toBe('deep');
105
- });
106
-
107
- test('fields parses Approval as a single-line KV preserving inline parentheses', () => {
108
- const result = extractTasksContractSummary(SAMPLE_WITH_HEADING);
109
- expect(result.fields.approval).toBe('approved (ExitPlanMode 2026-05-12)');
110
- });
111
-
112
- test('fields parses Goal as a multi-line bullet list', () => {
113
- const result = extractTasksContractSummary(SAMPLE_WITH_HEADING);
114
- expect(Array.isArray(result.fields.goal)).toBe(true);
115
- expect(result.fields.goal).toEqual([
116
- 'Collapse default artifact surface.',
117
- 'Switch workflow-context canonical refs.'
118
- ]);
119
- });
120
-
121
- test('fields parses Do Not Do as a bullet list', () => {
122
- const result = extractTasksContractSummary(SAMPLE_WITH_HEADING);
123
- expect(result.fields.doNotDo).toEqual([
124
- 'Do not migrate REQ-001 / REQ-002 by default.',
125
- 'Do not rebuild task-manifest.json.'
126
- ]);
127
- });
128
-
129
- test('fields parses Approved Direction as a bullet list', () => {
130
- const result = extractTasksContractSummary(SAMPLE_WITH_HEADING);
131
- expect(result.fields.approvedDirection).toEqual([
132
- 'Single REQ-003 PR covering v2 PR-A + PR-B + PR-C.',
133
- '18 vertical slices.'
134
- ]);
135
- });
136
-
137
- test('fields parses Acceptance as a bullet list', () => {
138
- const result = extractTasksContractSummary(SAMPLE_WITH_HEADING);
139
- expect(result.fields.acceptance).toEqual([
140
- 'New REQ-003-example directory emits only tasks.md.',
141
- 'verify:artifacts passes.'
142
- ]);
143
- });
144
-
145
- test('fields parses Verification as a fenced code block string', () => {
146
- const result = extractTasksContractSummary(SAMPLE_WITH_HEADING);
147
- expect(typeof result.fields.verification).toBe('string');
148
- expect(result.fields.verification).toMatch(/npm test/);
149
- expect(result.fields.verification).toMatch(/npm run verify:artifacts/);
150
- expect(result.fields.verification).not.toMatch(/```/);
151
- });
152
-
153
- test('fields parses Risk / Escalate If as a bullet list', () => {
154
- const result = extractTasksContractSummary(SAMPLE_WITH_HEADING);
155
- expect(result.fields.risk).toEqual([
156
- 'benchmark-workflow-context-tokens fails for REQ-001.',
157
- 'Total LOC exceeds 3500.'
158
- ]);
159
- });
160
-
161
- test('fields does not include keys from sections outside Contract Summary', () => {
162
- const result = extractTasksContractSummary(SAMPLE_WITH_HEADING);
163
- expect(result.fields).not.toHaveProperty('implementationSurfaceMap');
164
- expect(result.fields).not.toHaveProperty('something');
165
- });
166
- });
167
-
168
- describe('when `## Contract Summary` heading is absent', () => {
169
- test('returns found:false', () => {
170
- const result = extractTasksContractSummary(SAMPLE_WITHOUT_HEADING);
171
- expect(result.found).toBe(false);
172
- });
173
-
174
- test('returns empty content and empty fields', () => {
175
- const result = extractTasksContractSummary(SAMPLE_WITHOUT_HEADING);
176
- expect(result.content).toBe('');
177
- expect(result.fields).toEqual({});
178
- });
179
- });
180
-
181
- describe('edge cases', () => {
182
- test('treats a heading-only block with no body as found:true with empty fields', () => {
183
- const headingOnly = '## Contract Summary\n\n## Next Heading\n\nBody\n';
184
- const result = extractTasksContractSummary(headingOnly);
185
- expect(result.found).toBe(true);
186
- expect(result.content).toMatch(/^## Contract Summary/);
187
- expect(result.fields).toEqual({});
188
- });
189
-
190
- test('is case-sensitive on the canonical heading spelling', () => {
191
- const wrongCase = '## contract summary\n\nChange: REQ-999\n\n## Next\n';
192
- const result = extractTasksContractSummary(wrongCase);
193
- expect(result.found).toBe(false);
194
- });
195
-
196
- test('throws a TypeError when input is not a string', () => {
197
- expect(() => extractTasksContractSummary(null)).toThrow(TypeError);
198
- expect(() => extractTasksContractSummary(undefined)).toThrow(TypeError);
199
- expect(() => extractTasksContractSummary(42)).toThrow(TypeError);
200
- });
201
- });
202
- });
203
-
204
- const FIX_SAMPLE_WITH_HEADING = `# FIX-042-cache-stampede
205
-
206
- ## Plan Meta
207
-
208
- Just meta.
209
-
210
- ## Root Cause Contract
211
-
212
- Change: FIX-042-cache-stampede
213
- Mode: investigation
214
- Profile: standard
215
- Diagnosis: cache stampede on cold start under parallel burst.
216
-
217
- Symptom:
218
- - p99 latency spike to 12s every deploy.
219
- - Redis CPU saturates at 100% for ~20s.
220
-
221
- Reproduction:
222
- - Restart app with empty cache.
223
- - Send 200 rps synthetic burst.
224
-
225
- Expected:
226
- - First miss population single-flight; subsequent callers await the in-flight promise.
227
-
228
- Actual:
229
- - N parallel callers all miss, all hit origin, all write back.
230
-
231
- Root Cause:
232
- - Missing in-flight promise map in \`lib/cache/remote.js\`.
233
-
234
- Evidence Chain:
235
- - flamegraph 2026-05-10 shows 184 origin calls within 50ms.
236
- - \`remote.js:42\` unconditionally calls \`origin.fetch\` under a cache-miss branch.
237
-
238
- Repair Boundary:
239
- - Introduce in-flight map keyed by cache key; reject propagation errors to all awaiters.
240
- - Do not touch eviction policy or TTL logic.
241
-
242
- Verification:
243
-
244
- \`\`\`bash
245
- npm test -- lib/cache/remote.test.js
246
- \`\`\`
247
-
248
- Prevention:
249
- - Add a regression test that fires 50 parallel callers with an empty cache and asserts origin.fetch called once.
250
-
251
- Risk / Escalate If:
252
- - If the in-flight map leaks on rejection, memory grows unbounded — escalate to owner.
253
- - If fix adds >5% p50 latency, roll back.
254
-
255
- ## Repair Plan
256
-
257
- Other content that must not leak into the Root Cause Contract block.
258
- `;
259
-
260
- const FIX_SAMPLE_WITHOUT_HEADING = `# FIX-000
261
-
262
- ## Plan Meta
263
-
264
- Nothing here.
265
-
266
- ## Something Else
267
-
268
- Unrelated.
269
- `;
270
-
271
- describe('extractTasksRootCauseContract', () => {
272
- describe('when `## Root Cause Contract` heading is present', () => {
273
- test('returns found:true', () => {
274
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
275
- expect(result.found).toBe(true);
276
- });
277
-
278
- test('content spans from the heading line up to (but not including) the next H2 heading', () => {
279
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
280
- expect(result.content).toMatch(/^## Root Cause Contract/);
281
- expect(result.content).not.toMatch(/## Repair Plan/);
282
- expect(result.content).not.toMatch(/Other content that must not leak/);
283
- });
284
-
285
- test('fields parses Change / Mode / Profile as single-line KVs', () => {
286
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
287
- expect(result.fields.change).toBe('FIX-042-cache-stampede');
288
- expect(result.fields.mode).toBe('investigation');
289
- expect(result.fields.profile).toBe('standard');
290
- });
291
-
292
- test('fields parses Diagnosis as a single-line KV preserving punctuation', () => {
293
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
294
- expect(result.fields.diagnosis).toBe('cache stampede on cold start under parallel burst.');
295
- });
296
-
297
- test('fields parses Symptom as a bullet list', () => {
298
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
299
- expect(result.fields.symptom).toEqual([
300
- 'p99 latency spike to 12s every deploy.',
301
- 'Redis CPU saturates at 100% for ~20s.'
302
- ]);
303
- });
304
-
305
- test('fields parses Reproduction as a bullet list', () => {
306
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
307
- expect(result.fields.reproduction).toEqual([
308
- 'Restart app with empty cache.',
309
- 'Send 200 rps synthetic burst.'
310
- ]);
311
- });
312
-
313
- test('fields parses Expected as a bullet list', () => {
314
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
315
- expect(result.fields.expected).toEqual([
316
- 'First miss population single-flight; subsequent callers await the in-flight promise.'
317
- ]);
318
- });
319
-
320
- test('fields parses Actual as a bullet list', () => {
321
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
322
- expect(result.fields.actual).toEqual([
323
- 'N parallel callers all miss, all hit origin, all write back.'
324
- ]);
325
- });
326
-
327
- test('fields parses Root Cause as a bullet list preserving inline code spans', () => {
328
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
329
- expect(result.fields.rootCause).toEqual([
330
- 'Missing in-flight promise map in `lib/cache/remote.js`.'
331
- ]);
332
- });
333
-
334
- test('fields parses Evidence Chain as a bullet list', () => {
335
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
336
- expect(result.fields.evidenceChain).toEqual([
337
- 'flamegraph 2026-05-10 shows 184 origin calls within 50ms.',
338
- '`remote.js:42` unconditionally calls `origin.fetch` under a cache-miss branch.'
339
- ]);
340
- });
341
-
342
- test('fields parses Repair Boundary as a bullet list', () => {
343
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
344
- expect(result.fields.repairBoundary).toEqual([
345
- 'Introduce in-flight map keyed by cache key; reject propagation errors to all awaiters.',
346
- 'Do not touch eviction policy or TTL logic.'
347
- ]);
348
- });
349
-
350
- test('fields parses Verification as a fenced command block', () => {
351
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
352
- expect(result.fields.verification).toBe('npm test -- lib/cache/remote.test.js');
353
- });
354
-
355
- test('fields parses Prevention as a bullet list', () => {
356
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
357
- expect(result.fields.prevention).toEqual([
358
- 'Add a regression test that fires 50 parallel callers with an empty cache and asserts origin.fetch called once.'
359
- ]);
360
- });
361
-
362
- test('fields parses Risk / Escalate If as a bullet list', () => {
363
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
364
- expect(result.fields.risk).toEqual([
365
- 'If the in-flight map leaks on rejection, memory grows unbounded — escalate to owner.',
366
- 'If fix adds >5% p50 latency, roll back.'
367
- ]);
368
- });
369
-
370
- test('fields does not include keys from sections outside Root Cause Contract', () => {
371
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITH_HEADING);
372
- expect(result.fields).not.toHaveProperty('repairPlan');
373
- expect(result.fields).not.toHaveProperty('something');
374
- });
375
- });
376
-
377
- describe('when `## Root Cause Contract` heading is absent', () => {
378
- test('returns found:false with empty content and empty fields', () => {
379
- const result = extractTasksRootCauseContract(FIX_SAMPLE_WITHOUT_HEADING);
380
- expect(result.found).toBe(false);
381
- expect(result.content).toBe('');
382
- expect(result.fields).toEqual({});
383
- });
384
- });
385
-
386
- describe('edge cases', () => {
387
- test('is case-sensitive on the canonical heading spelling', () => {
388
- const wrongCase = '## root cause contract\n\nChange: FIX-999\n\n## Next\n';
389
- const result = extractTasksRootCauseContract(wrongCase);
390
- expect(result.found).toBe(false);
391
- });
392
-
393
- test('throws a TypeError when input is not a string', () => {
394
- expect(() => extractTasksRootCauseContract(null)).toThrow(TypeError);
395
- expect(() => extractTasksRootCauseContract(undefined)).toThrow(TypeError);
396
- expect(() => extractTasksRootCauseContract({})).toThrow(TypeError);
397
- });
398
- });
399
- });
400
-
401
- describe('extractTasksContractSummary and extractTasksRootCauseContract share independent contracts', () => {
402
- test('a document with only Contract Summary returns found:false for Root Cause Contract', () => {
403
- const r = extractTasksRootCauseContract(SAMPLE_WITH_HEADING);
404
- expect(r.found).toBe(false);
405
- expect(r.fields).toEqual({});
406
- });
407
-
408
- test('a document with only Root Cause Contract returns found:false for Contract Summary', () => {
409
- const r = extractTasksContractSummary(FIX_SAMPLE_WITH_HEADING);
410
- expect(r.found).toBe(false);
411
- expect(r.fields).toEqual({});
412
- });
413
- });
414
-
415
- describe('task-contract compile', () => {
416
- let repoRoot;
417
-
418
- beforeEach(() => {
419
- repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-task-contract-'));
420
- fs.writeFileSync(path.join(repoRoot, 'package.json'), '{"name":"tmp"}\n');
421
- fs.mkdirSync(path.join(repoRoot, '.git'));
422
- });
423
-
424
- afterEach(() => {
425
- fs.rmSync(repoRoot, { recursive: true, force: true });
426
- });
427
-
428
- test('writes manifest whose metadata.source equals tasks.md', async () => {
429
- const changeId = 'REQ-777';
430
- const changeKey = 'REQ-777-compile-manifest';
431
- const planningDir = path.join(repoRoot, 'devflow', 'changes', changeKey, 'planning');
432
- fs.mkdirSync(planningDir, { recursive: true });
433
- fs.writeFileSync(
434
- path.join(planningDir, 'tasks.md'),
435
- [
436
- '# TASKS',
437
- '',
438
- '## Contract Summary',
439
- '',
440
- 'Change: REQ-777-compile-manifest',
441
- 'Mode: plan',
442
- 'Profile: standard',
443
- 'Approval: approved',
444
- '',
445
- 'Goal:',
446
- '- Compile manifest from tasks.md.',
447
- '',
448
- 'Do Not Do:',
449
- '- Do not add validate or migrate behavior.',
450
- '',
451
- 'Approved Direction:',
452
- '- Reuse planner.createTaskManifest.',
453
- '',
454
- 'Acceptance:',
455
- '- Manifest metadata.source is tasks.md.',
456
- '',
457
- 'Verification:',
458
- '',
459
- '```bash',
460
- 'npm test -- lib/skill-runtime/__tests__/task-contract.test.js',
461
- '```',
462
- '',
463
- 'Risk / Escalate If:',
464
- '- Missing tasks.md should exit 3.',
465
- '',
466
- '## Phase 1: Compile',
467
- '',
468
- '- [ ] T001 compile manifest',
469
- ' Goal: Write task-manifest.json from tasks.md.',
470
- ' Files: `lib/skill-runtime/operations/task-contract.js`',
471
- ' Verification: npm test -- lib/skill-runtime/__tests__/task-contract.test.js',
472
- ' Evidence: manifest metadata assertions',
473
- ''
474
- ].join('\n')
475
- );
476
-
477
- const result = await runCompile({ repoRoot, changeId, changeKey });
478
- const manifestPath = path.join(planningDir, 'task-manifest.json');
479
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
480
-
481
- expect(result).toEqual(expect.objectContaining({
482
- code: 0,
483
- changeId,
484
- changeKey,
485
- manifestPath
486
- }));
487
- expect(manifest.metadata.source).toBe('tasks.md');
488
- expect(manifest.metadata.generatedBy).toBe('cc-devflow task-contract');
489
- });
490
-
491
- test('returns exit code 3 when planning/tasks.md is missing', async () => {
492
- const changeId = 'REQ-778';
493
- const changeKey = 'REQ-778-missing-tasks';
494
- const planningDir = path.join(repoRoot, 'devflow', 'changes', changeKey, 'planning');
495
- fs.mkdirSync(planningDir, { recursive: true });
496
-
497
- const result = await runCompile({ repoRoot, changeId, changeKey });
498
-
499
- expect(result.code).toBe(3);
500
- expect(result.error).toMatch(/Missing planning\/tasks\.md/);
501
- expect(fs.existsSync(path.join(planningDir, 'task-manifest.json'))).toBe(false);
502
- });
503
-
504
- test('extracts goal and acceptance into change-meta.json', async () => {
505
- const changeId = 'REQ-780';
506
- const changeKey = 'REQ-780-compile-change-meta';
507
- const changeDir = path.join(repoRoot, 'devflow', 'changes', changeKey);
508
- const planningDir = path.join(changeDir, 'planning');
509
- fs.mkdirSync(planningDir, { recursive: true });
510
- fs.writeFileSync(
511
- path.join(planningDir, 'tasks.md'),
512
- [
513
- '# TASKS',
514
- '',
515
- '## Contract Summary',
516
- '',
517
- 'Change: REQ-780-compile-change-meta',
518
- 'Mode: plan',
519
- 'Profile: standard',
520
- 'Approval: approved',
521
- '',
522
- 'Goal:',
523
- '- Generate change metadata from the contract.',
524
- '- Keep the manifest writer unchanged.',
525
- '',
526
- 'Do Not Do:',
527
- '- Do not add validate or migrate behavior.',
528
- '',
529
- 'Approved Direction:',
530
- '- The Contract Summary is the human-authored source.',
531
- '',
532
- 'Acceptance:',
533
- '- change-meta.json contains goal items.',
534
- '- change-meta.json records specReference.',
535
- '',
536
- 'Verification:',
537
- '',
538
- '```bash',
539
- 'npm test -- lib/skill-runtime/__tests__/task-contract.test.js',
540
- '```',
541
- '',
542
- 'Risk / Escalate If:',
543
- '- Missing Contract Summary exits 3.',
544
- '',
545
- '## Phase 1: Compile',
546
- '',
547
- '- [ ] T001 compile metadata',
548
- ' Goal: Write change-meta.json from tasks.md.',
549
- ' Files: `lib/skill-runtime/operations/task-contract.js`',
550
- ' Verification: npm test -- lib/skill-runtime/__tests__/task-contract.test.js',
551
- ' Evidence: change-meta.json field assertions',
552
- ''
553
- ].join('\n')
554
- );
555
-
556
- const result = await runCompile({ repoRoot, changeId, changeKey });
557
- const changeMeta = JSON.parse(fs.readFileSync(path.join(changeDir, 'change-meta.json'), 'utf8'));
558
-
559
- expect(result.code).toBe(0);
560
- expect(changeMeta.goal).toEqual([
561
- 'Generate change metadata from the contract.',
562
- 'Keep the manifest writer unchanged.'
563
- ]);
564
- expect(changeMeta.acceptance).toEqual([
565
- 'change-meta.json contains goal items.',
566
- 'change-meta.json records specReference.'
567
- ]);
568
- expect(changeMeta.specReference).toEqual({
569
- source: 'planning/tasks.md#contract-summary',
570
- change: 'REQ-780-compile-change-meta',
571
- mode: 'plan',
572
- profile: 'standard',
573
- approval: 'approved'
574
- });
575
- expect(changeMeta._meta.generatedBy).toBe('cc-devflow task-contract');
576
- });
577
-
578
- test('compiles Root Cause Contract into generated manifest and change-meta', async () => {
579
- const changeId = 'FIX-781';
580
- const changeKey = 'FIX-781-compile-root-cause';
581
- const changeDir = path.join(repoRoot, 'devflow', 'changes', changeKey);
582
- const planningDir = path.join(changeDir, 'planning');
583
- fs.mkdirSync(planningDir, { recursive: true });
584
- fs.writeFileSync(
585
- path.join(planningDir, 'tasks.md'),
586
- [
587
- '# TASKS',
588
- '',
589
- '## Root Cause Contract',
590
- '',
591
- 'Change: FIX-781-compile-root-cause',
592
- 'Mode: investigation',
593
- 'Profile: standard',
594
- 'Diagnosis: confirmed',
595
- '',
596
- 'Symptom:',
597
- '- Task-contract compile rejects bug investigations.',
598
- '',
599
- 'Reproduction:',
600
- '- Run compile against a Root Cause Contract-only tasks.md.',
601
- '',
602
- 'Expected:',
603
- '- CLI owns task-manifest.json and change-meta.json.',
604
- '',
605
- 'Actual:',
606
- '- Compile required Contract Summary.',
607
- '',
608
- 'Root Cause:',
609
- '- The compile path reused only the planning contract parser.',
610
- '',
611
- 'Evidence Chain:',
612
- '- validate already accepts Root Cause Contract.',
613
- '',
614
- 'Repair Boundary:',
615
- '- Accept Root Cause Contract as the human-authored source.',
616
- '',
617
- 'Verification:',
618
- '',
619
- '```bash',
620
- 'npm test -- lib/skill-runtime/__tests__/task-contract.test.js',
621
- '```',
622
- '',
623
- 'Prevention:',
624
- '- Keep generated machine records behind task-contract compile.',
625
- '',
626
- 'Risk / Escalate If:',
627
- '- Generated metadata loses the root-cause source.',
628
- '',
629
- '## Phase 1: Compile',
630
- '',
631
- '- [ ] T001 compile root-cause metadata',
632
- ' Goal: Generate machine artifacts from the root-cause contract.',
633
- ' Files: `lib/skill-runtime/operations/task-contract.js`',
634
- ' Verification: npm test -- lib/skill-runtime/__tests__/task-contract.test.js',
635
- ' Evidence: generated metadata assertions',
636
- ''
637
- ].join('\n')
638
- );
639
-
640
- const result = await runCompile({ repoRoot, changeId, changeKey });
641
- const manifest = JSON.parse(fs.readFileSync(path.join(planningDir, 'task-manifest.json'), 'utf8'));
642
- const changeMeta = JSON.parse(fs.readFileSync(path.join(changeDir, 'change-meta.json'), 'utf8'));
643
-
644
- expect(result.code).toBe(0);
645
- expect(manifest.goal).toBe('The compile path reused only the planning contract parser.');
646
- expect(manifest.metadata.generatedBy).toBe('cc-devflow task-contract');
647
- expect(changeMeta.specReference.source).toBe('planning/tasks.md#root-cause-contract');
648
- expect(changeMeta.rootCause.confirmed).toEqual([
649
- 'The compile path reused only the planning contract parser.'
650
- ]);
651
- expect(changeMeta.acceptance).toEqual([
652
- 'Accept Root Cause Contract as the human-authored source.',
653
- 'npm test -- lib/skill-runtime/__tests__/task-contract.test.js'
654
- ]);
655
- expect(changeMeta._meta.generatedBy).toBe('cc-devflow task-contract');
656
- });
657
-
658
- test('returns exit code 3 when Contract Summary is missing', async () => {
659
- const changeId = 'REQ-781';
660
- const changeKey = 'REQ-781-missing-contract-summary';
661
- const changeDir = path.join(repoRoot, 'devflow', 'changes', changeKey);
662
- const planningDir = path.join(changeDir, 'planning');
663
- fs.mkdirSync(planningDir, { recursive: true });
664
- fs.writeFileSync(
665
- path.join(planningDir, 'tasks.md'),
666
- [
667
- '# TASKS',
668
- '',
669
- '## Phase 1: Compile',
670
- '',
671
- '- [ ] T001 compile metadata',
672
- ' Goal: Write nothing when Contract Summary is absent.',
673
- ' Files: `lib/skill-runtime/operations/task-contract.js`',
674
- ' Verification: npm test -- lib/skill-runtime/__tests__/task-contract.test.js',
675
- ' Evidence: exit code assertion',
676
- ''
677
- ].join('\n')
678
- );
679
-
680
- const result = await runCompile({ repoRoot, changeId, changeKey });
681
-
682
- expect(result.code).toBe(3);
683
- expect(result.error).toMatch(/Missing ## Contract Summary or ## Root Cause Contract/);
684
- expect(fs.existsSync(path.join(changeDir, 'change-meta.json'))).toBe(false);
685
- });
686
- });
687
-
688
- describe('task-contract validate', () => {
689
- let repoRoot;
690
-
691
- beforeEach(() => {
692
- repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-task-validate-'));
693
- fs.writeFileSync(path.join(repoRoot, 'package.json'), '{"name":"tmp"}\n');
694
- fs.mkdirSync(path.join(repoRoot, '.git'));
695
- });
696
-
697
- afterEach(() => {
698
- fs.rmSync(repoRoot, { recursive: true, force: true });
699
- });
700
-
701
- function tasksMarkdown(changeKey, options = {}) {
702
- const {
703
- profile = 'standard',
704
- includeContract = true,
705
- taskCount = 1,
706
- extraBody = ''
707
- } = options;
708
- const contract = includeContract
709
- ? [
710
- '## Contract Summary',
711
- '',
712
- `Change: ${changeKey}`,
713
- 'Mode: plan',
714
- `Profile: ${profile}`,
715
- 'Approval: approved',
716
- '',
717
- 'Goal:',
718
- '- Validate artifact contract.',
719
- '',
720
- 'Do Not Do:',
721
- '- Do not add migrate behavior.',
722
- '',
723
- 'Approved Direction:',
724
- '- Validate with a rule table.',
725
- '',
726
- 'Acceptance:',
727
- '- Validate exits with rule-specific codes.',
728
- '',
729
- 'Verification:',
730
- '',
731
- '```bash',
732
- 'npm test -- lib/skill-runtime/__tests__/task-contract.test.js',
733
- '```',
734
- '',
735
- 'Risk / Escalate If:',
736
- '- Budget overflow exits 6.',
737
- ''
738
- ]
739
- : [];
740
- const tasks = Array.from({ length: taskCount }, (_, index) => [
741
- `- [ ] T${String(index + 1).padStart(3, '0')} validate slice ${index + 1}`,
742
- ' Goal: Keep validation deterministic.',
743
- ' Files: `lib/skill-runtime/operations/task-contract.js`',
744
- ' Verification: npm test -- lib/skill-runtime/__tests__/task-contract.test.js',
745
- ' Evidence: exit code assertion',
746
- ` Vertical slice: Slice ${index + 1}`,
747
- ''
748
- ].join('\n'));
749
- return ['# TASKS', '', ...contract, extraBody, '## Phase 1: Validate', '', ...tasks].join('\n');
750
- }
751
-
752
- async function seedCompiledChange(changeId, changeKey, options = {}) {
753
- const changeDir = path.join(repoRoot, 'devflow', 'changes', changeKey);
754
- const planningDir = path.join(changeDir, 'planning');
755
- fs.mkdirSync(planningDir, { recursive: true });
756
- fs.writeFileSync(path.join(planningDir, 'tasks.md'), tasksMarkdown(changeKey, options));
757
- const compileResult = await runCompile({ repoRoot, changeId, changeKey });
758
- expect(compileResult.code).toBe(0);
759
- return { changeDir, planningDir };
760
- }
761
-
762
- const ruleCases = [
763
- ['C1', 2, async (ctx) => fs.writeFileSync(path.join(ctx.planningDir, 'design.md'), '# legacy design\n')],
764
- ['C2', 2, async (ctx) => fs.writeFileSync(path.join(ctx.planningDir, 'analysis.md'), '# legacy analysis\n')],
765
- ['C3', 2, async (ctx) => {
766
- const reviewDir = path.join(ctx.changeDir, 'review');
767
- fs.mkdirSync(reviewDir, { recursive: true });
768
- fs.writeFileSync(path.join(reviewDir, 'cc-review-report.md'), '# legacy review report\n');
769
- }],
770
- ['C5', 3, async (ctx) => {
771
- const manifestPath = path.join(ctx.planningDir, 'task-manifest.json');
772
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
773
- manifest.metadata.generatedBy = 'manual';
774
- fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
775
- }],
776
- ['C6', 3, async (ctx) => {
777
- const metaPath = path.join(ctx.changeDir, 'change-meta.json');
778
- const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
779
- meta._meta.generatedBy = 'manual';
780
- fs.writeFileSync(metaPath, `${JSON.stringify(meta, null, 2)}\n`);
781
- }],
782
- ['C8', 5, async (ctx) => {
783
- const reviewDir = path.join(ctx.changeDir, 'review');
784
- fs.mkdirSync(reviewDir, { recursive: true });
785
- fs.writeFileSync(path.join(reviewDir, 'review-ledger.jsonl'), '{"event":"review-started"}\n');
786
- }],
787
- ['C9', 5, async (ctx) => {
788
- const reviewDir = path.join(ctx.changeDir, 'review');
789
- fs.mkdirSync(reviewDir, { recursive: true });
790
- fs.writeFileSync(path.join(reviewDir, 'report-card.json'), '{"overall":"pass"}\n');
791
- }]
792
- ];
793
-
794
- test.each(ruleCases)('%s returns exit code %i', async (ruleId, exitCode, mutate) => {
795
- const changeId = `REQ-${ruleId.slice(1).padStart(3, '0')}`;
796
- const changeKey = `${changeId.toLowerCase()}-validate-${ruleId.toLowerCase()}`.toUpperCase().replace('_', '-');
797
- const ctx = await seedCompiledChange(changeId, changeKey);
798
- await mutate(ctx);
799
-
800
- const result = await runValidate({ repoRoot, changeId, changeKey });
801
-
802
- expect(result.code).toBe(exitCode);
803
- expect(result.stderr).toContain(ruleId);
804
- });
805
-
806
- test('returns exit code 3 for C4 when Contract Summary is missing', async () => {
807
- const changeId = 'REQ-704';
808
- const changeKey = 'REQ-704-missing-contract';
809
- const planningDir = path.join(repoRoot, 'devflow', 'changes', changeKey, 'planning');
810
- fs.mkdirSync(planningDir, { recursive: true });
811
- fs.writeFileSync(path.join(planningDir, 'tasks.md'), tasksMarkdown(changeKey, { includeContract: false }));
812
-
813
- const result = await runValidate({ repoRoot, changeId, changeKey });
814
-
815
- expect(result.code).toBe(3);
816
- expect(result.stderr).toContain('C4');
817
- });
818
-
819
- test('returns exit code 4 for C7 when tiny profile has more than one tracer slice', async () => {
820
- const changeId = 'REQ-707';
821
- const changeKey = 'REQ-707-tiny-over-sliced';
822
- await seedCompiledChange(changeId, changeKey, { profile: 'tiny', taskCount: 2 });
823
-
824
- const result = await runValidate({ repoRoot, changeId, changeKey });
825
-
826
- expect(result.code).toBe(4);
827
- expect(result.stderr).toContain('C7');
828
- });
829
-
830
- test('returns exit code 6 for C10 when tasks.md exceeds the profile budget', async () => {
831
- const changeId = 'REQ-710';
832
- const changeKey = 'REQ-710-budget-overflow';
833
- await seedCompiledChange(changeId, changeKey, {
834
- profile: 'standard',
835
- extraBody: 'x'.repeat(12000)
836
- });
837
-
838
- const result = await runValidate({ repoRoot, changeId, changeKey });
839
-
840
- expect(result.code).toBe(6);
841
- expect(result.stderr).toContain('C10');
842
- });
843
-
844
- test('passes a valid compiled task contract', async () => {
845
- const changeId = 'REQ-799';
846
- const changeKey = 'REQ-799-valid-contract';
847
- await seedCompiledChange(changeId, changeKey);
848
-
849
- const result = await runValidate({ repoRoot, changeId, changeKey });
850
-
851
- expect(result).toEqual(expect.objectContaining({
852
- code: 0,
853
- ok: true,
854
- violations: []
855
- }));
856
- });
857
-
858
- test('CLI validate failure prints the violated rule id', async () => {
859
- const changeId = 'REQ-711';
860
- const changeKey = 'REQ-711-cli-rule-output';
861
- const { planningDir } = await seedCompiledChange(changeId, changeKey);
862
- fs.writeFileSync(path.join(planningDir, 'design.md'), '# legacy design\n');
863
-
864
- const result = spawnSync(
865
- process.execPath,
866
- [CLI_BIN, 'task-contract', 'validate', '--cwd', repoRoot, '--change', changeId, '--change-key', changeKey],
867
- { encoding: 'utf8' }
868
- );
869
-
870
- expect(result.status).toBe(2);
871
- expect(result.stderr).toContain('C1');
872
- expect(result.stderr).toContain('planning/design.md');
873
- });
874
- });