cc-devflow 4.5.8 → 4.5.10

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 (149) hide show
  1. package/.claude/skills/cc-act/CHANGELOG.md +33 -0
  2. package/.claude/skills/cc-act/PLAYBOOK.md +9 -4
  3. package/.claude/skills/cc-act/SKILL.md +73 -12
  4. package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_INDEX_TEMPLATE.md +30 -0
  5. package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_PRINCIPLES_TEMPLATE.md +29 -0
  6. package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_TEMPLATE.md +103 -0
  7. package/.claude/skills/cc-act/assets/PR_BRIEF_TEMPLATE.md +61 -5
  8. package/.claude/skills/cc-act/references/closure-contract.md +4 -1
  9. package/.claude/skills/cc-act/references/git-commit-guidelines.md +342 -37
  10. package/.claude/skills/cc-act/scripts/cc-act-common.sh +29 -1
  11. package/.claude/skills/cc-act/scripts/render-pr-brief.sh +164 -0
  12. package/.claude/skills/cc-act/scripts/sync-act-docs.sh +1 -1
  13. package/.claude/skills/cc-check/CHANGELOG.md +17 -0
  14. package/.claude/skills/cc-check/PLAYBOOK.md +1 -0
  15. package/.claude/skills/cc-check/SKILL.md +9 -5
  16. package/.claude/skills/cc-check/references/review-contract.md +7 -0
  17. package/.claude/skills/cc-check/scripts/render-report-card.js +6 -1
  18. package/.claude/skills/cc-dev/CHANGELOG.md +5 -0
  19. package/.claude/skills/cc-dev/SKILL.md +26 -1
  20. package/.claude/skills/cc-do/CHANGELOG.md +23 -0
  21. package/.claude/skills/cc-do/PLAYBOOK.md +7 -7
  22. package/.claude/skills/cc-do/SKILL.md +49 -45
  23. package/.claude/skills/cc-do/references/execution-recovery.md +18 -13
  24. package/.claude/skills/cc-do/scripts/build-task-context.sh +13 -22
  25. package/.claude/skills/cc-do/scripts/mark-task-complete.sh +0 -6
  26. package/.claude/skills/cc-do/scripts/record-review-decision.sh +4 -5
  27. package/.claude/skills/cc-do/scripts/recover-workflow.sh +9 -11
  28. package/.claude/skills/cc-do/scripts/verify-task-gates.sh +12 -10
  29. package/.claude/skills/cc-do/scripts/write-task-checkpoint.sh +7 -29
  30. package/.claude/skills/cc-investigate/CHANGELOG.md +34 -0
  31. package/.claude/skills/cc-investigate/PLAYBOOK.md +21 -5
  32. package/.claude/skills/cc-investigate/SKILL.md +97 -40
  33. package/.claude/skills/cc-investigate/assets/TASKS_TEMPLATE.md +66 -4
  34. package/.claude/skills/cc-investigate/assets/TASK_MANIFEST_TEMPLATE.json +30 -59
  35. package/.claude/skills/cc-investigate/assets/{ANALYSIS_TEMPLATE.md → legacy/ANALYSIS_TEMPLATE.md} +48 -0
  36. package/.claude/skills/cc-investigate/references/investigation-contract.md +16 -2
  37. package/.claude/skills/cc-investigate/scripts/bootstrap-analysis.sh +1 -1
  38. package/.claude/skills/cc-next/CHANGELOG.md +6 -0
  39. package/.claude/skills/cc-next/PLAYBOOK.md +26 -4
  40. package/.claude/skills/cc-next/SKILL.md +39 -4
  41. package/.claude/skills/cc-plan/CHANGELOG.md +38 -0
  42. package/.claude/skills/cc-plan/PLAYBOOK.md +60 -53
  43. package/.claude/skills/cc-plan/SKILL.md +164 -87
  44. package/.claude/skills/cc-plan/assets/TASKS_TEMPLATE.md +101 -9
  45. package/.claude/skills/cc-plan/assets/TASK_MANIFEST_TEMPLATE.json +58 -229
  46. package/.claude/skills/cc-plan/assets/{DESIGN_TEMPLATE.md → legacy/DESIGN_TEMPLATE.md} +68 -0
  47. package/.claude/skills/cc-plan/assets/{TINY_DESIGN_TEMPLATE.md → legacy/TINY_DESIGN_TEMPLATE.md} +47 -1
  48. package/.claude/skills/cc-plan/references/planning-contract.md +48 -33
  49. package/.claude/skills/cc-review/CHANGELOG.md +6 -0
  50. package/.claude/skills/cc-review/PLAYBOOK.md +9 -11
  51. package/.claude/skills/cc-review/SKILL.md +37 -61
  52. package/.claude/skills/cc-review/references/e2e-and-plugin-verification.md +1 -1
  53. package/.claude/skills/cc-review/references/implementation-review-branch.md +5 -5
  54. package/.claude/skills/cc-review/references/plan-review-branch.md +1 -1
  55. package/.claude/skills/cc-review/references/review-methods.md +4 -4
  56. package/.claude/skills/cc-review/scripts/collect-review-context.sh +14 -7
  57. package/.claude/skills/cc-roadmap/CHANGELOG.md +6 -0
  58. package/.claude/skills/cc-roadmap/PLAYBOOK.md +30 -0
  59. package/.claude/skills/cc-roadmap/SKILL.md +45 -8
  60. package/.claude/skills/cc-roadmap/assets/BACKLOG_TEMPLATE.md +8 -0
  61. package/.claude/skills/cc-roadmap/assets/ROADMAP_TEMPLATE.md +22 -0
  62. package/.claude/skills/cc-roadmap/assets/TRACKING_TEMPLATE.json +32 -1
  63. package/.claude/skills/cc-roadmap/references/roadmap-dialogue.md +14 -14
  64. package/CHANGELOG.md +28 -0
  65. package/CONTRIBUTING.md +40 -4
  66. package/CONTRIBUTING.zh-CN.md +40 -4
  67. package/README.md +57 -43
  68. package/README.zh-CN.md +57 -43
  69. package/bin/cc-devflow-cli.js +293 -36
  70. package/docs/examples/START-HERE.md +5 -4
  71. package/docs/examples/example-bindings.json +10 -10
  72. package/docs/examples/full-design-blocked/BACKLOG.md +1 -1
  73. package/docs/examples/full-design-blocked/README.md +2 -2
  74. package/docs/examples/full-design-blocked/ROADMAP.md +1 -1
  75. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/design.md +2 -1
  76. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/task-manifest.json +29 -312
  77. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/tasks.md +11 -8
  78. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/review/report-card.json +4 -4
  79. package/docs/examples/full-design-blocked/roadmap.json +1 -1
  80. package/docs/examples/local-handoff/BACKLOG.md +1 -1
  81. package/docs/examples/local-handoff/README.md +2 -2
  82. package/docs/examples/local-handoff/ROADMAP.md +1 -1
  83. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/design.md +2 -1
  84. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/task-manifest.json +27 -210
  85. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/tasks.md +9 -6
  86. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/review/report-card.json +1 -1
  87. package/docs/examples/local-handoff/roadmap.json +1 -1
  88. package/docs/examples/pdca-loop/BACKLOG.md +1 -1
  89. package/docs/examples/pdca-loop/README.md +2 -2
  90. package/docs/examples/pdca-loop/ROADMAP.md +1 -1
  91. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/handoff/pr-brief.md +65 -1
  92. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/design.md +2 -1
  93. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/task-manifest.json +26 -228
  94. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/tasks.md +9 -6
  95. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/review/report-card.json +1 -1
  96. package/docs/examples/pdca-loop/roadmap.json +1 -1
  97. package/docs/examples/scripts/check-example-bindings.sh +11 -5
  98. package/docs/get-shit-done-strategy-audit.md +22 -22
  99. package/docs/guides/artifact-contract.md +44 -0
  100. package/docs/guides/getting-started.md +10 -8
  101. package/docs/guides/getting-started.zh-CN.md +10 -8
  102. package/docs/guides/minimize-artifacts.md +123 -0
  103. package/docs/guides/project-postmortem.md +78 -0
  104. package/lib/compiler/__tests__/skills-registry.test.js +2 -2
  105. package/lib/skill-runtime/CLAUDE.md +1 -1
  106. package/lib/skill-runtime/__tests__/autopilot.test.js +42 -6
  107. package/lib/skill-runtime/__tests__/benchmark-artifacts.test.js +165 -0
  108. package/lib/skill-runtime/__tests__/cli-bootstrap.integration.test.js +2 -2
  109. package/lib/skill-runtime/__tests__/dispatch.test.js +8 -38
  110. package/lib/skill-runtime/__tests__/intent.test.js +4 -20
  111. package/lib/skill-runtime/__tests__/lifecycle.test.js +1 -1
  112. package/lib/skill-runtime/__tests__/paths.test.js +7 -1
  113. package/lib/skill-runtime/__tests__/planner.tdd.test.js +63 -2
  114. package/lib/skill-runtime/__tests__/prepare-pr.test.js +3 -16
  115. package/lib/skill-runtime/__tests__/query.test.js +388 -7
  116. package/lib/skill-runtime/__tests__/review-check-integration.test.js +148 -0
  117. package/lib/skill-runtime/__tests__/review-records.test.js +619 -0
  118. package/lib/skill-runtime/__tests__/runtime.integration.test.js +64 -23
  119. package/lib/skill-runtime/__tests__/schemas.test.js +76 -2
  120. package/lib/skill-runtime/__tests__/task-contract-migrate.test.js +137 -0
  121. package/lib/skill-runtime/__tests__/task-contract.test.js +783 -0
  122. package/lib/skill-runtime/__tests__/verify-artifacts.test.js +203 -0
  123. package/lib/skill-runtime/__tests__/worker-run.test.js +4 -11
  124. package/lib/skill-runtime/__tests__/workflow-context-legacy-fallback.test.js +31 -0
  125. package/lib/skill-runtime/__tests__/workflow-context.test.js +98 -0
  126. package/lib/skill-runtime/artifacts.js +0 -5
  127. package/lib/skill-runtime/context-index.js +545 -0
  128. package/lib/skill-runtime/intent.js +9 -33
  129. package/lib/skill-runtime/lifecycle.js +1 -1
  130. package/lib/skill-runtime/operations/CLAUDE.md +2 -2
  131. package/lib/skill-runtime/operations/dispatch.js +4 -42
  132. package/lib/skill-runtime/operations/init.js +2 -6
  133. package/lib/skill-runtime/operations/janitor.js +2 -18
  134. package/lib/skill-runtime/operations/resume.js +21 -38
  135. package/lib/skill-runtime/operations/review-records.js +265 -0
  136. package/lib/skill-runtime/operations/snapshot.js +1 -1
  137. package/lib/skill-runtime/operations/task-contract.js +524 -0
  138. package/lib/skill-runtime/operations/worker-run.js +2 -30
  139. package/lib/skill-runtime/paths.js +4 -4
  140. package/lib/skill-runtime/planner.js +25 -13
  141. package/lib/skill-runtime/query-registry.js +2 -2
  142. package/lib/skill-runtime/query.js +16 -3
  143. package/lib/skill-runtime/review-records.js +123 -0
  144. package/lib/skill-runtime/review.js +246 -11
  145. package/lib/skill-runtime/schemas.js +179 -15
  146. package/lib/skill-runtime/store.js +0 -10
  147. package/lib/skill-runtime/task-contract.js +187 -0
  148. package/lib/skill-runtime/workflow-context.js +748 -0
  149. package/package.json +7 -4
@@ -101,6 +101,41 @@ describe('Skill runtime', () => {
101
101
  fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
102
102
  }
103
103
 
104
+ function writeCleanReviewLedger(changeId) {
105
+ const change = getChangePaths(repoRoot, changeId);
106
+ const ledgerPath = path.join(change.reviewDir, 'review-ledger.jsonl');
107
+ fs.mkdirSync(path.dirname(ledgerPath), { recursive: true });
108
+ fs.writeFileSync(ledgerPath, [
109
+ JSON.stringify({
110
+ schema: 'review-ledger.v2',
111
+ change: change.changeKey,
112
+ reviewId: 'RVW-20260512-001',
113
+ createdAt: '2026-05-12T00:00:00.000Z',
114
+ createdBy: 'cc-devflow-cli',
115
+ event: 'review-started',
116
+ mode: 'implementation',
117
+ scope: 'current-diff',
118
+ baseSha: 'abc123',
119
+ headSha: 'def456',
120
+ selectedNodes: [],
121
+ skippedNodes: [],
122
+ riskLanes: []
123
+ }),
124
+ JSON.stringify({
125
+ schema: 'review-ledger.v2',
126
+ change: change.changeKey,
127
+ reviewId: 'RVW-20260512-001',
128
+ createdAt: '2026-05-12T00:01:00.000Z',
129
+ createdBy: 'cc-devflow-cli',
130
+ event: 'review-closed',
131
+ status: 'clean',
132
+ blockingCount: 0,
133
+ warningCount: 0,
134
+ next: 'cc-check'
135
+ })
136
+ ].join('\n'));
137
+ }
138
+
104
139
  test('runs init -> snapshot -> plan -> dispatch -> verify -> release', async () => {
105
140
  const changeId = 'REQ-999';
106
141
  await runInit({ repoRoot, changeId, goal: 'Test skill runtime pipeline' });
@@ -133,6 +168,7 @@ describe('Skill runtime', () => {
133
168
 
134
169
  await markManifestSpec(changeId);
135
170
  await markManifestReviews(changeId, 'pass');
171
+ writeCleanReviewLedger(changeId);
136
172
 
137
173
  const verifyResult = await runVerify({
138
174
  repoRoot,
@@ -144,6 +180,11 @@ describe('Skill runtime', () => {
144
180
 
145
181
  const report = await readJson(getReportCardPath(repoRoot, changeId));
146
182
  expect(report.overall).toBe('pass');
183
+ const requirementsMet = report.claimEvidence.find((claim) => claim.claim === 'requirements-met');
184
+ expect(requirementsMet).toMatchObject({
185
+ status: 'pass',
186
+ keyObservation: 'no open task gaps recorded'
187
+ });
147
188
 
148
189
  const releaseResult = await runRelease({ repoRoot, changeId });
149
190
  expect(releaseResult.status).toBe('released');
@@ -183,7 +224,7 @@ describe('Skill runtime', () => {
183
224
  expect(report.blockingFindings.some((item) => item.includes('missing spec review proof'))).toBe(true);
184
225
  });
185
226
 
186
- test('cc-do shell helpers write runtime artifacts into devflow/changes instead of legacy flat paths', async () => {
227
+ test('cc-do shell helpers keep task truth in manifest and do not write process files by default', async () => {
187
228
  const changeId = 'REQ-2001';
188
229
  await runInit({ repoRoot, changeId, goal: 'Verify shell helper layout' });
189
230
 
@@ -207,7 +248,7 @@ describe('Skill runtime', () => {
207
248
  )}\n`
208
249
  );
209
250
 
210
- const checkpoint = spawnSync(WRITE_TASK_CHECKPOINT, [
251
+ const legacyCheckpoint = spawnSync(WRITE_TASK_CHECKPOINT, [
211
252
  '--dir',
212
253
  change.changeDir,
213
254
  '--task',
@@ -215,24 +256,9 @@ describe('Skill runtime', () => {
215
256
  '--status',
216
257
  'passed',
217
258
  '--summary',
218
- 'task finished',
219
- '--tdd-json',
220
- JSON.stringify({
221
- red: {
222
- testSeam: 'public helper command',
223
- behaviorAsserted: 'checkpoint is written under execution/tasks',
224
- allowedMocks: [],
225
- implementationDetailRisk: 'low'
226
- },
227
- testQuality: {
228
- usesPublicInterface: true,
229
- describesBehavior: true,
230
- survivesInternalRefactor: true,
231
- mocksOnlySystemBoundaries: true
232
- }
233
- })
259
+ 'task finished'
234
260
  ], { encoding: 'utf8' });
235
- expect(checkpoint.status).toBe(0);
261
+ expect(legacyCheckpoint.status).toBe(0);
236
262
 
237
263
  const specReview = spawnSync(RECORD_REVIEW_DECISION, [
238
264
  '--dir',
@@ -270,10 +296,25 @@ describe('Skill runtime', () => {
270
296
  ], { encoding: 'utf8' });
271
297
  expect(verify.status).toBe(0);
272
298
 
273
- expect(fs.existsSync(path.join(change.tasksDir, 'T001', 'checkpoint.json'))).toBe(true);
274
- const checkpointJson = JSON.parse(fs.readFileSync(path.join(change.tasksDir, 'T001', 'checkpoint.json'), 'utf8'));
275
- expect(checkpointJson.tdd.red.testSeam).toBe('public helper command');
276
- expect(checkpointJson.tdd.testQuality.usesPublicInterface).toBe(true);
299
+ const complete = spawnSync(MARK_TASK_COMPLETE, [
300
+ '--manifest',
301
+ manifestPath,
302
+ '--task',
303
+ 'T001'
304
+ ], { encoding: 'utf8' });
305
+ expect(complete.status).toBe(0);
306
+
307
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
308
+ expect(manifest.currentTaskId).toBeNull();
309
+ expect(manifest.tasks[0]).toMatchObject({
310
+ id: 'T001',
311
+ status: 'passed',
312
+ reviews: { spec: 'pass', code: 'pass' }
313
+ });
314
+
315
+ expect(fs.existsSync(path.join(change.tasksDir, 'T001', 'checkpoint.json'))).toBe(false);
316
+ expect(fs.existsSync(path.join(change.tasksDir, 'T001', 'context.md'))).toBe(false);
317
+ expect(fs.existsSync(path.join(change.tasksDir, 'T001', 'events.jsonl'))).toBe(false);
277
318
  expect(fs.existsSync(path.join(repoRoot, '.harness', 'runtime', changeId))).toBe(false);
278
319
  expect(fs.existsSync(path.join(change.tasksDir, 'T001', 'checkpoint.md'))).toBe(false);
279
320
  expect(fs.existsSync(path.join(change.tasksDir, 'T001', 'review-spec.md'))).toBe(false);
@@ -8,7 +8,6 @@ describe('Manifest schema hard constraints', () => {
8
8
  createdAt: '2026-04-10T01:00:00.000Z',
9
9
  updatedAt: '2026-04-10T01:05:00.000Z',
10
10
  currentTaskId: 'T001',
11
- activePhase: 1,
12
11
  tasks: [
13
12
  {
14
13
  id: 'T001',
@@ -73,7 +72,39 @@ describe('Manifest schema hard constraints', () => {
73
72
  });
74
73
 
75
74
  expect(manifest.currentTaskId).toBe('T001');
76
- expect(manifest.activePhase).toBe(1);
75
+ expect(manifest.activePhase).toBeUndefined();
76
+ });
77
+
78
+ test('accepts legacy activePhase input without preserving the retired field', () => {
79
+ const manifest = parseManifest({
80
+ changeId: 'REQ-560',
81
+ goal: 'Validate legacy manifest compatibility',
82
+ createdAt: '2026-04-10T01:00:00.000Z',
83
+ updatedAt: '2026-04-10T01:05:00.000Z',
84
+ currentTaskId: 'T001',
85
+ activePhase: 1,
86
+ tasks: [
87
+ {
88
+ id: 'T001',
89
+ title: '[TEST] Counter behavior',
90
+ type: 'OTHER',
91
+ phase: 1,
92
+ dependsOn: [],
93
+ run: ['echo ok'],
94
+ status: 'pending',
95
+ attempts: 0,
96
+ maxRetries: 1
97
+ }
98
+ ],
99
+ metadata: {
100
+ source: 'default',
101
+ generatedBy: 'test',
102
+ planVersion: 1
103
+ }
104
+ });
105
+
106
+ expect(manifest.currentTaskId).toBe('T001');
107
+ expect(manifest.activePhase).toBeUndefined();
77
108
  });
78
109
 
79
110
  test('rejects invalid currentTaskId for tasks.md manifest', () => {
@@ -149,6 +180,49 @@ describe('Manifest schema hard constraints', () => {
149
180
  expect(() => parseManifest({ ...baseManifest, changeId: 'BUG-556' })).toThrow(/Invalid changeId format/);
150
181
  });
151
182
 
183
+ test('accepts current full change keys and review/verification task types', () => {
184
+ const manifest = parseManifest({
185
+ changeId: 'REQ-001-personal-output-config',
186
+ goal: 'Validate current manifest shape',
187
+ createdAt: '2026-04-10T01:00:00.000Z',
188
+ updatedAt: '2026-04-10T01:05:00.000Z',
189
+ currentTaskId: 'T007',
190
+ tasks: [
191
+ {
192
+ id: 'T007',
193
+ title: '[REFACTOR] Apply review fixes',
194
+ type: 'REFACTOR',
195
+ phase: 4,
196
+ dependsOn: [],
197
+ run: ['echo ok'],
198
+ status: 'pending',
199
+ attempts: 0,
200
+ maxRetries: 1
201
+ },
202
+ {
203
+ id: 'T008',
204
+ title: '[VERIFY] Run full gates',
205
+ type: 'VERIFY',
206
+ phase: 4,
207
+ dependsOn: ['T007'],
208
+ run: ['npm test'],
209
+ status: 'pending',
210
+ attempts: 0,
211
+ maxRetries: 1
212
+ }
213
+ ],
214
+ metadata: {
215
+ source: 'planning/tasks.md',
216
+ generatedBy: 'cc-plan',
217
+ planVersion: 1
218
+ }
219
+ });
220
+
221
+ expect(manifest.changeId).toBe('REQ-001-personal-output-config');
222
+ expect(manifest.tasks.map((task) => task.type)).toEqual(['REFACTOR', 'VERIFY']);
223
+ expect(manifest.metadata.source).toBe('planning/tasks.md');
224
+ });
225
+
152
226
  test('rejects conflicting parallel tasks that share touches in same phase', () => {
153
227
  expect(() => parseManifest({
154
228
  changeId: 'REQ-557',
@@ -0,0 +1,137 @@
1
+ /**
2
+ * [INPUT]: 依赖 task-contract migrate operation、真实临时文件树与 CLI 子进程。
3
+ * [OUTPUT]: 证明 opt-in migrate 会折叠 legacy design.md,并归档原文件。
4
+ * [POS]: REQ-003-minimize-workflow-artifacts T007 的 Red/Green 证据。
5
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const os = require('os');
10
+ const path = require('path');
11
+ const { spawnSync } = require('child_process');
12
+ const { extractTasksContractSummary } = require('../task-contract');
13
+ const { runCompile, runMigrate } = require('../operations/task-contract');
14
+
15
+ const REPO_ROOT = path.resolve(__dirname, '../../..');
16
+ const CLI_BIN = path.join(REPO_ROOT, 'bin/cc-devflow-cli.js');
17
+
18
+ function writeFile(filePath, content) {
19
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
20
+ fs.writeFileSync(filePath, content);
21
+ }
22
+
23
+ function legacyTasks() {
24
+ return [
25
+ '# TASKS',
26
+ '',
27
+ '## Plan Meta',
28
+ '',
29
+ '- Requirement version: `REQ-999-legacy.v1`',
30
+ '',
31
+ '## Phase 1: Legacy',
32
+ '',
33
+ '- [ ] T001 keep old behavior',
34
+ ' Goal: Preserve the migration source task list.',
35
+ ''
36
+ ].join('\n');
37
+ }
38
+
39
+ function legacyDesign() {
40
+ return [
41
+ '# DESIGN',
42
+ '',
43
+ '## Requirement Snapshot',
44
+ '',
45
+ '- Raw ask: Keep one source of truth.',
46
+ '',
47
+ '## Approved Direction',
48
+ '',
49
+ '- Fold legacy design into tasks.md only when explicitly requested.',
50
+ ''
51
+ ].join('\n');
52
+ }
53
+
54
+ describe('task-contract migrate', () => {
55
+ let repoRoot;
56
+
57
+ beforeEach(() => {
58
+ repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-task-migrate-'));
59
+ fs.writeFileSync(path.join(repoRoot, 'package.json'), '{"name":"tmp"}\n');
60
+ fs.mkdirSync(path.join(repoRoot, '.git'));
61
+ });
62
+
63
+ afterEach(() => {
64
+ fs.rmSync(repoRoot, { recursive: true, force: true });
65
+ });
66
+
67
+ test('migrate fold-design merges design.md content into tasks.md Contract Summary', async () => {
68
+ const changeId = 'REQ-999';
69
+ const changeKey = 'REQ-999-legacy';
70
+ const changeDir = path.join(repoRoot, 'devflow', 'changes', changeKey);
71
+ const planningDir = path.join(changeDir, 'planning');
72
+ writeFile(path.join(planningDir, 'tasks.md'), legacyTasks());
73
+ writeFile(path.join(planningDir, 'design.md'), legacyDesign());
74
+
75
+ const result = await runMigrate({ repoRoot, changeId, changeKey, foldDesign: true });
76
+ const tasksText = fs.readFileSync(path.join(planningDir, 'tasks.md'), 'utf8');
77
+ const archivedDesign = path.join(changeDir, 'assets', 'legacy', 'design.md.bak');
78
+
79
+ expect(result).toEqual(expect.objectContaining({
80
+ code: 0,
81
+ changeId,
82
+ changeKey,
83
+ migrated: ['design.md']
84
+ }));
85
+ expect(extractTasksContractSummary(tasksText).found).toBe(true);
86
+ expect(tasksText).toContain('## Contract Summary');
87
+ expect(tasksText).toContain('Legacy Design:');
88
+ expect(tasksText).toContain('> # DESIGN');
89
+ expect(tasksText).toContain('> - Fold legacy design into tasks.md only when explicitly requested.');
90
+ expect(fs.existsSync(path.join(planningDir, 'design.md'))).toBe(false);
91
+ expect(fs.readFileSync(archivedDesign, 'utf8')).toBe(legacyDesign());
92
+ });
93
+
94
+ test('compile does not auto-run fold-design migration', async () => {
95
+ const changeId = 'REQ-998';
96
+ const changeKey = 'REQ-998-no-auto-migrate';
97
+ const planningDir = path.join(repoRoot, 'devflow', 'changes', changeKey, 'planning');
98
+ writeFile(path.join(planningDir, 'tasks.md'), legacyTasks());
99
+ writeFile(path.join(planningDir, 'design.md'), legacyDesign());
100
+
101
+ const result = await runCompile({ repoRoot, changeId, changeKey });
102
+
103
+ expect(result.code).toBe(3);
104
+ expect(fs.existsSync(path.join(planningDir, 'design.md'))).toBe(true);
105
+ });
106
+
107
+ test('migrate returns exit code 4 when legacy design.md is unreadable', async () => {
108
+ const changeId = 'REQ-997';
109
+ const changeKey = 'REQ-997-unreadable-design';
110
+ const planningDir = path.join(repoRoot, 'devflow', 'changes', changeKey, 'planning');
111
+ writeFile(path.join(planningDir, 'tasks.md'), legacyTasks());
112
+ fs.mkdirSync(path.join(planningDir, 'design.md'), { recursive: true });
113
+
114
+ const result = await runMigrate({ repoRoot, changeId, changeKey, foldDesign: true });
115
+
116
+ expect(result.code).toBe(4);
117
+ expect(result.error).toContain('Unable to read planning/design.md');
118
+ });
119
+
120
+ test('CLI migrate requires an explicit fold flag', () => {
121
+ const changeId = 'REQ-996';
122
+ const changeKey = 'REQ-996-cli-flags';
123
+ const planningDir = path.join(repoRoot, 'devflow', 'changes', changeKey, 'planning');
124
+ writeFile(path.join(planningDir, 'tasks.md'), legacyTasks());
125
+ writeFile(path.join(planningDir, 'design.md'), legacyDesign());
126
+
127
+ const result = spawnSync(
128
+ process.execPath,
129
+ [CLI_BIN, 'task-contract', 'migrate', '--cwd', repoRoot, '--change', changeId, '--change-key', changeKey],
130
+ { encoding: 'utf8' }
131
+ );
132
+
133
+ expect(result.status).toBe(3);
134
+ expect(result.stderr).toContain('--fold-design');
135
+ expect(fs.existsSync(path.join(planningDir, 'design.md'))).toBe(true);
136
+ });
137
+ });