cc-devflow 4.5.9 → 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.
- package/.claude/skills/cc-act/CHANGELOG.md +6 -0
- package/.claude/skills/cc-act/SKILL.md +12 -10
- package/.claude/skills/cc-act/assets/PR_BRIEF_TEMPLATE.md +1 -1
- package/.claude/skills/cc-act/references/closure-contract.md +1 -1
- package/.claude/skills/cc-act/references/git-commit-guidelines.md +1 -1
- package/.claude/skills/cc-check/CHANGELOG.md +17 -0
- package/.claude/skills/cc-check/PLAYBOOK.md +1 -0
- package/.claude/skills/cc-check/SKILL.md +9 -5
- package/.claude/skills/cc-check/references/review-contract.md +7 -0
- package/.claude/skills/cc-check/scripts/render-report-card.js +6 -1
- package/.claude/skills/cc-dev/CHANGELOG.md +5 -0
- package/.claude/skills/cc-dev/SKILL.md +26 -1
- package/.claude/skills/cc-do/CHANGELOG.md +12 -0
- package/.claude/skills/cc-do/PLAYBOOK.md +7 -7
- package/.claude/skills/cc-do/SKILL.md +35 -37
- package/.claude/skills/cc-do/references/execution-recovery.md +18 -13
- package/.claude/skills/cc-do/scripts/build-task-context.sh +4 -17
- package/.claude/skills/cc-do/scripts/record-review-decision.sh +4 -5
- package/.claude/skills/cc-do/scripts/recover-workflow.sh +9 -11
- package/.claude/skills/cc-do/scripts/verify-task-gates.sh +12 -10
- package/.claude/skills/cc-do/scripts/write-task-checkpoint.sh +7 -29
- package/.claude/skills/cc-investigate/CHANGELOG.md +17 -0
- package/.claude/skills/cc-investigate/PLAYBOOK.md +6 -5
- package/.claude/skills/cc-investigate/SKILL.md +56 -44
- package/.claude/skills/cc-investigate/assets/TASKS_TEMPLATE.md +48 -5
- package/.claude/skills/cc-investigate/assets/TASK_MANIFEST_TEMPLATE.json +4 -3
- package/.claude/skills/cc-investigate/assets/{ANALYSIS_TEMPLATE.md → legacy/ANALYSIS_TEMPLATE.md} +1 -0
- package/.claude/skills/cc-investigate/references/investigation-contract.md +2 -2
- package/.claude/skills/cc-investigate/scripts/bootstrap-analysis.sh +1 -1
- package/.claude/skills/cc-plan/CHANGELOG.md +19 -0
- package/.claude/skills/cc-plan/PLAYBOOK.md +55 -53
- package/.claude/skills/cc-plan/SKILL.md +101 -85
- package/.claude/skills/cc-plan/assets/TASKS_TEMPLATE.md +47 -14
- package/.claude/skills/cc-plan/assets/TASK_MANIFEST_TEMPLATE.json +4 -2
- package/.claude/skills/cc-plan/assets/{DESIGN_TEMPLATE.md → legacy/DESIGN_TEMPLATE.md} +1 -0
- package/.claude/skills/cc-plan/assets/{TINY_DESIGN_TEMPLATE.md → legacy/TINY_DESIGN_TEMPLATE.md} +1 -1
- package/.claude/skills/cc-plan/references/planning-contract.md +11 -10
- package/.claude/skills/cc-review/CHANGELOG.md +6 -0
- package/.claude/skills/cc-review/PLAYBOOK.md +9 -11
- package/.claude/skills/cc-review/SKILL.md +37 -61
- package/.claude/skills/cc-review/references/e2e-and-plugin-verification.md +1 -1
- package/.claude/skills/cc-review/references/implementation-review-branch.md +5 -5
- package/.claude/skills/cc-review/references/plan-review-branch.md +1 -1
- package/.claude/skills/cc-review/references/review-methods.md +4 -4
- package/.claude/skills/cc-review/scripts/collect-review-context.sh +14 -7
- package/CHANGELOG.md +16 -0
- package/CONTRIBUTING.md +40 -4
- package/CONTRIBUTING.zh-CN.md +40 -4
- package/README.md +20 -8
- package/README.zh-CN.md +20 -8
- package/bin/cc-devflow-cli.js +293 -36
- package/docs/examples/START-HERE.md +5 -4
- package/docs/examples/example-bindings.json +8 -8
- package/docs/examples/full-design-blocked/README.md +2 -2
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/design.md +2 -1
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/task-manifest.json +3 -2
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/tasks.md +11 -8
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/review/report-card.json +4 -4
- package/docs/examples/local-handoff/README.md +2 -2
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/design.md +2 -1
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/task-manifest.json +3 -2
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/tasks.md +9 -6
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/review/report-card.json +1 -1
- package/docs/examples/pdca-loop/README.md +2 -2
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/handoff/pr-brief.md +2 -2
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/design.md +2 -1
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/task-manifest.json +2 -1
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/tasks.md +9 -6
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/review/report-card.json +1 -1
- package/docs/examples/scripts/check-example-bindings.sh +2 -0
- package/docs/get-shit-done-strategy-audit.md +22 -22
- package/docs/guides/artifact-contract.md +1 -1
- package/docs/guides/getting-started.md +10 -8
- package/docs/guides/getting-started.zh-CN.md +10 -8
- package/docs/guides/minimize-artifacts.md +123 -0
- package/lib/compiler/__tests__/skills-registry.test.js +2 -2
- package/lib/skill-runtime/CLAUDE.md +1 -1
- package/lib/skill-runtime/__tests__/autopilot.test.js +42 -6
- package/lib/skill-runtime/__tests__/benchmark-artifacts.test.js +165 -0
- package/lib/skill-runtime/__tests__/cli-bootstrap.integration.test.js +2 -2
- package/lib/skill-runtime/__tests__/dispatch.test.js +8 -38
- package/lib/skill-runtime/__tests__/intent.test.js +4 -20
- package/lib/skill-runtime/__tests__/lifecycle.test.js +1 -1
- package/lib/skill-runtime/__tests__/paths.test.js +7 -1
- package/lib/skill-runtime/__tests__/planner.tdd.test.js +61 -0
- package/lib/skill-runtime/__tests__/prepare-pr.test.js +3 -16
- package/lib/skill-runtime/__tests__/query.test.js +388 -7
- package/lib/skill-runtime/__tests__/review-check-integration.test.js +148 -0
- package/lib/skill-runtime/__tests__/review-records.test.js +619 -0
- package/lib/skill-runtime/__tests__/runtime.integration.test.js +64 -23
- package/lib/skill-runtime/__tests__/schemas.test.js +43 -0
- package/lib/skill-runtime/__tests__/task-contract-migrate.test.js +137 -0
- package/lib/skill-runtime/__tests__/task-contract.test.js +783 -0
- package/lib/skill-runtime/__tests__/verify-artifacts.test.js +203 -0
- package/lib/skill-runtime/__tests__/worker-run.test.js +4 -11
- package/lib/skill-runtime/__tests__/workflow-context-legacy-fallback.test.js +31 -0
- package/lib/skill-runtime/__tests__/workflow-context.test.js +98 -0
- package/lib/skill-runtime/artifacts.js +0 -5
- package/lib/skill-runtime/context-index.js +545 -0
- package/lib/skill-runtime/intent.js +9 -33
- package/lib/skill-runtime/lifecycle.js +1 -1
- package/lib/skill-runtime/operations/CLAUDE.md +2 -2
- package/lib/skill-runtime/operations/dispatch.js +4 -42
- package/lib/skill-runtime/operations/init.js +2 -6
- package/lib/skill-runtime/operations/janitor.js +2 -18
- package/lib/skill-runtime/operations/resume.js +21 -38
- package/lib/skill-runtime/operations/review-records.js +265 -0
- package/lib/skill-runtime/operations/snapshot.js +1 -1
- package/lib/skill-runtime/operations/task-contract.js +524 -0
- package/lib/skill-runtime/operations/worker-run.js +2 -30
- package/lib/skill-runtime/paths.js +4 -4
- package/lib/skill-runtime/planner.js +24 -11
- package/lib/skill-runtime/query-registry.js +2 -2
- package/lib/skill-runtime/query.js +15 -2
- package/lib/skill-runtime/review-records.js +123 -0
- package/lib/skill-runtime/review.js +246 -11
- package/lib/skill-runtime/schemas.js +174 -12
- package/lib/skill-runtime/store.js +0 -10
- package/lib/skill-runtime/task-contract.js +187 -0
- package/lib/skill-runtime/workflow-context.js +748 -0
- 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
|
|
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
|
|
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(
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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);
|
|
@@ -180,6 +180,49 @@ describe('Manifest schema hard constraints', () => {
|
|
|
180
180
|
expect(() => parseManifest({ ...baseManifest, changeId: 'BUG-556' })).toThrow(/Invalid changeId format/);
|
|
181
181
|
});
|
|
182
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
|
+
|
|
183
226
|
test('rejects conflicting parallel tasks that share touches in same phase', () => {
|
|
184
227
|
expect(() => parseManifest({
|
|
185
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
|
+
});
|