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