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.
Files changed (121) hide show
  1. package/.claude/skills/cc-act/CHANGELOG.md +6 -0
  2. package/.claude/skills/cc-act/SKILL.md +12 -10
  3. package/.claude/skills/cc-act/assets/PR_BRIEF_TEMPLATE.md +1 -1
  4. package/.claude/skills/cc-act/references/closure-contract.md +1 -1
  5. package/.claude/skills/cc-act/references/git-commit-guidelines.md +1 -1
  6. package/.claude/skills/cc-check/CHANGELOG.md +17 -0
  7. package/.claude/skills/cc-check/PLAYBOOK.md +1 -0
  8. package/.claude/skills/cc-check/SKILL.md +9 -5
  9. package/.claude/skills/cc-check/references/review-contract.md +7 -0
  10. package/.claude/skills/cc-check/scripts/render-report-card.js +6 -1
  11. package/.claude/skills/cc-dev/CHANGELOG.md +5 -0
  12. package/.claude/skills/cc-dev/SKILL.md +26 -1
  13. package/.claude/skills/cc-do/CHANGELOG.md +12 -0
  14. package/.claude/skills/cc-do/PLAYBOOK.md +7 -7
  15. package/.claude/skills/cc-do/SKILL.md +35 -37
  16. package/.claude/skills/cc-do/references/execution-recovery.md +18 -13
  17. package/.claude/skills/cc-do/scripts/build-task-context.sh +4 -17
  18. package/.claude/skills/cc-do/scripts/record-review-decision.sh +4 -5
  19. package/.claude/skills/cc-do/scripts/recover-workflow.sh +9 -11
  20. package/.claude/skills/cc-do/scripts/verify-task-gates.sh +12 -10
  21. package/.claude/skills/cc-do/scripts/write-task-checkpoint.sh +7 -29
  22. package/.claude/skills/cc-investigate/CHANGELOG.md +17 -0
  23. package/.claude/skills/cc-investigate/PLAYBOOK.md +6 -5
  24. package/.claude/skills/cc-investigate/SKILL.md +56 -44
  25. package/.claude/skills/cc-investigate/assets/TASKS_TEMPLATE.md +48 -5
  26. package/.claude/skills/cc-investigate/assets/TASK_MANIFEST_TEMPLATE.json +4 -3
  27. package/.claude/skills/cc-investigate/assets/{ANALYSIS_TEMPLATE.md → legacy/ANALYSIS_TEMPLATE.md} +1 -0
  28. package/.claude/skills/cc-investigate/references/investigation-contract.md +2 -2
  29. package/.claude/skills/cc-investigate/scripts/bootstrap-analysis.sh +1 -1
  30. package/.claude/skills/cc-plan/CHANGELOG.md +19 -0
  31. package/.claude/skills/cc-plan/PLAYBOOK.md +55 -53
  32. package/.claude/skills/cc-plan/SKILL.md +101 -85
  33. package/.claude/skills/cc-plan/assets/TASKS_TEMPLATE.md +47 -14
  34. package/.claude/skills/cc-plan/assets/TASK_MANIFEST_TEMPLATE.json +4 -2
  35. package/.claude/skills/cc-plan/assets/{DESIGN_TEMPLATE.md → legacy/DESIGN_TEMPLATE.md} +1 -0
  36. package/.claude/skills/cc-plan/assets/{TINY_DESIGN_TEMPLATE.md → legacy/TINY_DESIGN_TEMPLATE.md} +1 -1
  37. package/.claude/skills/cc-plan/references/planning-contract.md +11 -10
  38. package/.claude/skills/cc-review/CHANGELOG.md +6 -0
  39. package/.claude/skills/cc-review/PLAYBOOK.md +9 -11
  40. package/.claude/skills/cc-review/SKILL.md +37 -61
  41. package/.claude/skills/cc-review/references/e2e-and-plugin-verification.md +1 -1
  42. package/.claude/skills/cc-review/references/implementation-review-branch.md +5 -5
  43. package/.claude/skills/cc-review/references/plan-review-branch.md +1 -1
  44. package/.claude/skills/cc-review/references/review-methods.md +4 -4
  45. package/.claude/skills/cc-review/scripts/collect-review-context.sh +14 -7
  46. package/CHANGELOG.md +16 -0
  47. package/CONTRIBUTING.md +40 -4
  48. package/CONTRIBUTING.zh-CN.md +40 -4
  49. package/README.md +20 -8
  50. package/README.zh-CN.md +20 -8
  51. package/bin/cc-devflow-cli.js +293 -36
  52. package/docs/examples/START-HERE.md +5 -4
  53. package/docs/examples/example-bindings.json +8 -8
  54. package/docs/examples/full-design-blocked/README.md +2 -2
  55. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/design.md +2 -1
  56. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/task-manifest.json +3 -2
  57. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/tasks.md +11 -8
  58. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/review/report-card.json +4 -4
  59. package/docs/examples/local-handoff/README.md +2 -2
  60. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/design.md +2 -1
  61. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/task-manifest.json +3 -2
  62. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/tasks.md +9 -6
  63. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/review/report-card.json +1 -1
  64. package/docs/examples/pdca-loop/README.md +2 -2
  65. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/handoff/pr-brief.md +2 -2
  66. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/design.md +2 -1
  67. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/task-manifest.json +2 -1
  68. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/tasks.md +9 -6
  69. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/review/report-card.json +1 -1
  70. package/docs/examples/scripts/check-example-bindings.sh +2 -0
  71. package/docs/get-shit-done-strategy-audit.md +22 -22
  72. package/docs/guides/artifact-contract.md +1 -1
  73. package/docs/guides/getting-started.md +10 -8
  74. package/docs/guides/getting-started.zh-CN.md +10 -8
  75. package/docs/guides/minimize-artifacts.md +123 -0
  76. package/lib/compiler/__tests__/skills-registry.test.js +2 -2
  77. package/lib/skill-runtime/CLAUDE.md +1 -1
  78. package/lib/skill-runtime/__tests__/autopilot.test.js +42 -6
  79. package/lib/skill-runtime/__tests__/benchmark-artifacts.test.js +165 -0
  80. package/lib/skill-runtime/__tests__/cli-bootstrap.integration.test.js +2 -2
  81. package/lib/skill-runtime/__tests__/dispatch.test.js +8 -38
  82. package/lib/skill-runtime/__tests__/intent.test.js +4 -20
  83. package/lib/skill-runtime/__tests__/lifecycle.test.js +1 -1
  84. package/lib/skill-runtime/__tests__/paths.test.js +7 -1
  85. package/lib/skill-runtime/__tests__/planner.tdd.test.js +61 -0
  86. package/lib/skill-runtime/__tests__/prepare-pr.test.js +3 -16
  87. package/lib/skill-runtime/__tests__/query.test.js +388 -7
  88. package/lib/skill-runtime/__tests__/review-check-integration.test.js +148 -0
  89. package/lib/skill-runtime/__tests__/review-records.test.js +619 -0
  90. package/lib/skill-runtime/__tests__/runtime.integration.test.js +64 -23
  91. package/lib/skill-runtime/__tests__/schemas.test.js +43 -0
  92. package/lib/skill-runtime/__tests__/task-contract-migrate.test.js +137 -0
  93. package/lib/skill-runtime/__tests__/task-contract.test.js +783 -0
  94. package/lib/skill-runtime/__tests__/verify-artifacts.test.js +203 -0
  95. package/lib/skill-runtime/__tests__/worker-run.test.js +4 -11
  96. package/lib/skill-runtime/__tests__/workflow-context-legacy-fallback.test.js +31 -0
  97. package/lib/skill-runtime/__tests__/workflow-context.test.js +98 -0
  98. package/lib/skill-runtime/artifacts.js +0 -5
  99. package/lib/skill-runtime/context-index.js +545 -0
  100. package/lib/skill-runtime/intent.js +9 -33
  101. package/lib/skill-runtime/lifecycle.js +1 -1
  102. package/lib/skill-runtime/operations/CLAUDE.md +2 -2
  103. package/lib/skill-runtime/operations/dispatch.js +4 -42
  104. package/lib/skill-runtime/operations/init.js +2 -6
  105. package/lib/skill-runtime/operations/janitor.js +2 -18
  106. package/lib/skill-runtime/operations/resume.js +21 -38
  107. package/lib/skill-runtime/operations/review-records.js +265 -0
  108. package/lib/skill-runtime/operations/snapshot.js +1 -1
  109. package/lib/skill-runtime/operations/task-contract.js +524 -0
  110. package/lib/skill-runtime/operations/worker-run.js +2 -30
  111. package/lib/skill-runtime/paths.js +4 -4
  112. package/lib/skill-runtime/planner.js +24 -11
  113. package/lib/skill-runtime/query-registry.js +2 -2
  114. package/lib/skill-runtime/query.js +15 -2
  115. package/lib/skill-runtime/review-records.js +123 -0
  116. package/lib/skill-runtime/review.js +246 -11
  117. package/lib/skill-runtime/schemas.js +174 -12
  118. package/lib/skill-runtime/store.js +0 -10
  119. package/lib/skill-runtime/task-contract.js +187 -0
  120. package/lib/skill-runtime/workflow-context.js +748 -0
  121. package/package.json +7 -4
@@ -4,7 +4,7 @@
4
4
  职责分组
5
5
  入口层: `cli.js` 负责命令分发,`index.js` 提供给测试和内部脚本的稳定聚合入口。
6
6
  基础层: `schemas.js`、`store.js`、`paths.js` 管住契约、持久化与路径规则,避免执行层重复造轮子。
7
- 状态层: `artifacts.js`、`lifecycle.js`、`query.js`、`review.js`、`team-state.js` 维护运行时真相源与只读查询。
7
+ 状态层: `artifacts.js`、`lifecycle.js`、`query.js`、`workflow-context.js`、`review.js`、`team-state.js` 维护运行时真相源与只读查询。
8
8
  规划与交接: `planner.js`、`intent.js`、`delegation.js` 把任务解析、handoff 生成和 team/workspace 委派收口成统一语义。
9
9
  阶段操作: `operations/` 是唯一 stage 入口目录;具体阶段边界见 `operations/CLAUDE.md`。
10
10
  测试布局: `__tests__/` 紧贴模块放置单元、回归与集成测试;顶层 `test/` 不再承载 `skill-runtime` 私有测试。
@@ -9,13 +9,13 @@ const {
9
9
  getTaskManifestPath,
10
10
  getReportCardPath,
11
11
  getReleaseNotePath,
12
- getRuntimeStatePath,
13
- getCheckpointPath
12
+ getRuntimeStatePath
14
13
  } = require('../store');
15
14
  const {
16
15
  getIntentResumeIndexPath,
17
16
  getIntentPrBriefPath
18
17
  } = require('../artifacts');
18
+ const { getChangePaths } = require('../paths');
19
19
 
20
20
  jest.setTimeout(20000);
21
21
 
@@ -41,6 +41,41 @@ function markManifestReviewsPassed(repoRoot, changeId) {
41
41
  fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
42
42
  }
43
43
 
44
+ function writeCleanReviewLedger(repoRoot, changeId) {
45
+ const change = getChangePaths(repoRoot, changeId);
46
+ const ledgerPath = path.join(change.reviewDir, 'review-ledger.jsonl');
47
+ fs.mkdirSync(path.dirname(ledgerPath), { recursive: true });
48
+ fs.writeFileSync(ledgerPath, [
49
+ JSON.stringify({
50
+ schema: 'review-ledger.v2',
51
+ change: change.changeKey,
52
+ reviewId: 'RVW-20260512-001',
53
+ createdAt: '2026-05-12T00:00:00.000Z',
54
+ createdBy: 'cc-devflow-cli',
55
+ event: 'review-started',
56
+ mode: 'implementation',
57
+ scope: 'current-diff',
58
+ baseSha: 'abc123',
59
+ headSha: 'def456',
60
+ selectedNodes: [],
61
+ skippedNodes: [],
62
+ riskLanes: []
63
+ }),
64
+ JSON.stringify({
65
+ schema: 'review-ledger.v2',
66
+ change: change.changeKey,
67
+ reviewId: 'RVW-20260512-001',
68
+ createdAt: '2026-05-12T00:01:00.000Z',
69
+ createdBy: 'cc-devflow-cli',
70
+ event: 'review-closed',
71
+ status: 'clean',
72
+ blockingCount: 0,
73
+ warningCount: 0,
74
+ next: 'cc-check'
75
+ })
76
+ ].join('\n'));
77
+ }
78
+
44
79
  describe('runAutopilot', () => {
45
80
  test('stops at the approval gate after planning without writing approval-phase handoff markdown', async () => {
46
81
  const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-autopilot-'));
@@ -81,7 +116,7 @@ describe('runAutopilot', () => {
81
116
  expect(fs.existsSync(getIntentResumeIndexPath(repoRoot, 'REQ-123'))).toBe(false);
82
117
  });
83
118
 
84
- test('resumes after approval, executes delegated work, and prepares a PR from checkpoints', async () => {
119
+ test('resumes after approval, executes delegated work, and prepares a PR from task state', async () => {
85
120
  const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-autopilot-workers-'));
86
121
 
87
122
  writeJson(path.join(repoRoot, 'package.json'), {
@@ -123,17 +158,16 @@ describe('runAutopilot', () => {
123
158
  });
124
159
 
125
160
  const manifest = JSON.parse(fs.readFileSync(getTaskManifestPath(repoRoot, 'REQ-123'), 'utf8'));
126
- const delegatedCheckpoint = JSON.parse(fs.readFileSync(getCheckpointPath(repoRoot, 'REQ-123', 'T002'), 'utf8'));
127
161
  const report = JSON.parse(fs.readFileSync(getReportCardPath(repoRoot, 'REQ-123'), 'utf8'));
128
162
 
129
163
  expect(firstRun.executed).toEqual(expect.arrayContaining(['delegate', 'worker-run', 'dispatch', 'verify']));
130
164
  expect(firstRun.currentStage).toBe('verify');
131
165
  expect(manifest.tasks.find((task) => task.id === 'T002').status).toBe('passed');
132
- expect(delegatedCheckpoint.outputExcerpt).toContain('delegate-ok');
133
166
  expect(report.review.status).toBe('blocked');
134
167
  expect(fs.existsSync(getIntentPrBriefPath(repoRoot, 'REQ-123'))).toBe(false);
135
168
 
136
169
  markManifestReviewsPassed(repoRoot, 'REQ-123');
170
+ writeCleanReviewLedger(repoRoot, 'REQ-123');
137
171
 
138
172
  const secondRun = await runAutopilot({
139
173
  repoRoot,
@@ -146,7 +180,8 @@ describe('runAutopilot', () => {
146
180
 
147
181
  expect(secondRun.executed).toEqual(expect.arrayContaining(['verify', 'prepare-pr']));
148
182
  expect(secondRun.currentStage).toBe('prepare-pr');
149
- expect(prBrief).toContain('execution/tasks/T002/checkpoint.json');
183
+ expect(prBrief).toContain('planning/task-manifest.json');
184
+ expect(prBrief).not.toContain('checkpoint.json');
150
185
  });
151
186
 
152
187
  test('runs release after prepare-pr when requested for an approved plan', async () => {
@@ -195,6 +230,7 @@ describe('runAutopilot', () => {
195
230
  expect(fs.existsSync(getReleaseNotePath(repoRoot, 'REQ-123'))).toBe(false);
196
231
 
197
232
  markManifestReviewsPassed(repoRoot, 'REQ-123');
233
+ writeCleanReviewLedger(repoRoot, 'REQ-123');
198
234
 
199
235
  const result = await runAutopilot({
200
236
  repoRoot,
@@ -0,0 +1,165 @@
1
+ /**
2
+ * [INPUT]: 依赖 scripts/benchmark-artifacts.js 导出的 runBenchmarkArtifacts 和临时 artifact fixture。
3
+ * [OUTPUT]: 验证 benchmark:artifacts 使用 ceil(len/4) 估算并报告 profile 阈值 savings。
4
+ * [POS]: REQ-003-minimize-workflow-artifacts T017 的 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
+
13
+ const { runBenchmarkArtifacts } = require('../../../scripts/benchmark-artifacts');
14
+
15
+ const REPO_ROOT = path.resolve(__dirname, '../../..');
16
+ const BENCHMARK_SCRIPT = path.join(REPO_ROOT, 'scripts', 'benchmark-artifacts.js');
17
+
18
+ function writeText(filePath, text) {
19
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
20
+ fs.writeFileSync(filePath, text);
21
+ }
22
+
23
+ function writeJson(filePath, value) {
24
+ writeText(filePath, `${JSON.stringify(value, null, 2)}\n`);
25
+ }
26
+
27
+ function contractTasks({ changeKey, profile = 'standard', filler = '' }) {
28
+ return [
29
+ '# Tasks',
30
+ '',
31
+ '## Contract Summary',
32
+ '',
33
+ `Change: ${changeKey}`,
34
+ 'Mode: plan',
35
+ `Profile: ${profile}`,
36
+ 'Approval: approved',
37
+ '',
38
+ 'Goal:',
39
+ '- Minimize workflow artifacts.',
40
+ '',
41
+ 'Do Not Do:',
42
+ '- Do not change token estimator math.',
43
+ '',
44
+ 'Approved Direction:',
45
+ '- Use tasks.md plus generated JSON records.',
46
+ '',
47
+ 'Acceptance:',
48
+ '- Benchmark savings stay above threshold.',
49
+ '',
50
+ 'Verification:',
51
+ '',
52
+ '```bash',
53
+ 'npm run benchmark:artifacts',
54
+ '```',
55
+ '',
56
+ 'Risk / Escalate If:',
57
+ '- Savings fall below profile threshold.',
58
+ '',
59
+ filler,
60
+ '## Phase 1',
61
+ '',
62
+ '- [ ] T001 benchmark minimized artifact surface',
63
+ ' Vertical slice: Slice 1',
64
+ ''
65
+ ].join('\n');
66
+ }
67
+
68
+ function seedLegacyBaseline(repoRoot, changeKey, size = 6000) {
69
+ const changeDir = path.join(repoRoot, 'devflow', 'changes', changeKey);
70
+ writeText(path.join(changeDir, 'planning', 'design.md'), `# Design\n\n${'d'.repeat(size)}\n`);
71
+ writeText(path.join(changeDir, 'planning', 'analysis.md'), `# Analysis\n\n${'a'.repeat(size / 2)}\n`);
72
+ writeText(path.join(changeDir, 'planning', 'tasks.md'), `# Tasks\n\n${'t'.repeat(size / 2)}\n`);
73
+ writeJson(path.join(changeDir, 'planning', 'task-manifest.json'), { changeId: changeKey, tasks: [] });
74
+ writeJson(path.join(changeDir, 'change-meta.json'), { changeId: changeKey, goal: ['legacy'] });
75
+ writeJson(path.join(changeDir, 'review', 'report-card.json'), { overall: 'pass' });
76
+ }
77
+
78
+ function seedMinimizedChange(repoRoot, changeKey, options = {}) {
79
+ const changeDir = path.join(repoRoot, 'devflow', 'changes', changeKey);
80
+ writeText(path.join(changeDir, 'planning', 'tasks.md'), contractTasks({ changeKey, ...options }));
81
+ writeJson(path.join(changeDir, 'planning', 'task-manifest.json'), {
82
+ changeId: changeKey,
83
+ metadata: { source: 'tasks.md', generatedBy: 'cc-devflow task-contract', planVersion: 1 },
84
+ tasks: []
85
+ });
86
+ writeJson(path.join(changeDir, 'change-meta.json'), {
87
+ changeId: changeKey,
88
+ _meta: { generatedBy: 'cc-devflow task-contract' }
89
+ });
90
+ writeJson(path.join(changeDir, 'review', 'review-ledger.jsonl'), { note: 'counted as text by benchmark' });
91
+ writeJson(path.join(changeDir, 'review', 'review-findings.json'), { findings: [] });
92
+ writeJson(path.join(changeDir, 'review', 'report-card.json'), { overall: 'pass' });
93
+ }
94
+
95
+ describe('benchmark:artifacts', () => {
96
+ let repoRoot;
97
+
98
+ beforeEach(() => {
99
+ repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-benchmark-artifacts-'));
100
+ seedLegacyBaseline(repoRoot, 'REQ-001-legacy-baseline');
101
+ seedLegacyBaseline(repoRoot, 'REQ-002-legacy-baseline');
102
+ });
103
+
104
+ afterEach(() => {
105
+ fs.rmSync(repoRoot, { recursive: true, force: true });
106
+ });
107
+
108
+ test('reports standard savings >= 30% for REQ-003-example', () => {
109
+ seedMinimizedChange(repoRoot, 'REQ-003-example', { profile: 'standard' });
110
+
111
+ const result = runBenchmarkArtifacts(repoRoot);
112
+ const row = result.rows.find((item) => item.changeKey === 'REQ-003-example');
113
+
114
+ expect(result.code).toBe(0);
115
+ expect(row).toMatchObject({
116
+ profile: 'standard',
117
+ threshold_pct: 30,
118
+ correctness_pass: true
119
+ });
120
+ expect(row.savings_vs_baseline_pct).toBeGreaterThanOrEqual(30);
121
+ });
122
+
123
+ test('reports tiny savings >= 60% for tiny fixture', () => {
124
+ seedMinimizedChange(repoRoot, 'REQ-004-tiny-example', { profile: 'tiny' });
125
+
126
+ const result = runBenchmarkArtifacts(repoRoot);
127
+ const row = result.rows.find((item) => item.changeKey === 'REQ-004-tiny-example');
128
+
129
+ expect(result.code).toBe(0);
130
+ expect(row).toMatchObject({
131
+ profile: 'tiny',
132
+ threshold_pct: 60,
133
+ correctness_pass: true
134
+ });
135
+ expect(row.savings_vs_baseline_pct).toBeGreaterThanOrEqual(60);
136
+ });
137
+
138
+ test('exits 1 when savings are below the profile threshold', () => {
139
+ seedMinimizedChange(repoRoot, 'REQ-005-bloated-example', {
140
+ profile: 'standard',
141
+ filler: 'x'.repeat(20000)
142
+ });
143
+
144
+ const result = runBenchmarkArtifacts(repoRoot);
145
+
146
+ expect(result.code).toBe(1);
147
+ expect(result.rows[0]).toMatchObject({ correctness_pass: false });
148
+ });
149
+
150
+ test('CLI prints stdout JSON array', () => {
151
+ seedMinimizedChange(repoRoot, 'REQ-003-example', { profile: 'standard' });
152
+
153
+ const result = spawnSync(process.execPath, [BENCHMARK_SCRIPT, repoRoot], { encoding: 'utf8' });
154
+ const rows = JSON.parse(result.stdout);
155
+
156
+ expect(result.status).toBe(0);
157
+ expect(Array.isArray(rows)).toBe(true);
158
+ expect(rows[0]).toHaveProperty('savings_vs_baseline_pct');
159
+ });
160
+
161
+ test('package.json exposes npm run benchmark:artifacts', () => {
162
+ const pkg = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'package.json'), 'utf8'));
163
+ expect(pkg.scripts['benchmark:artifacts']).toBe('node scripts/benchmark-artifacts.js');
164
+ });
165
+ });
@@ -217,9 +217,9 @@ describe('cc-devflow cli distribution bootstrap', () => {
217
217
  expect(codexDoSkill.data.writes).toEqual(
218
218
  expect.arrayContaining([
219
219
  expect.objectContaining({
220
- path: 'devflow/changes/<change-key>/execution/tasks/<task-id>/checkpoint.json',
220
+ path: 'devflow/changes/<change-key>/execution/tasks/<task-id>/events.jsonl',
221
221
  durability: 'durable',
222
- required: true
222
+ required: false
223
223
  })
224
224
  ])
225
225
  );
@@ -7,7 +7,6 @@ const { runResume } = require('../operations/resume');
7
7
  const {
8
8
  getRuntimeStatePath,
9
9
  getTaskManifestPath,
10
- getCheckpointPath,
11
10
  getEventsPath
12
11
  } = require('../store');
13
12
 
@@ -76,7 +75,7 @@ describe('runDispatch', () => {
76
75
  expect(nextManifest.tasks[0].status).toBe('pending');
77
76
  });
78
77
 
79
- test('rejects stale results when planVersion changes during task execution and records it in checkpoint', async () => {
78
+ test('rejects stale results when planVersion changes during task execution and records it in manifest and events', async () => {
80
79
  const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-dispatch-'));
81
80
  const manifestPath = getTaskManifestPath(repoRoot, 'REQ-123');
82
81
 
@@ -133,28 +132,25 @@ describe('runDispatch', () => {
133
132
  });
134
133
 
135
134
  const nextManifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
136
- const checkpoint = JSON.parse(fs.readFileSync(getCheckpointPath(repoRoot, 'REQ-123', 'T001'), 'utf8'));
137
135
  const events = fs.readFileSync(getEventsPath(repoRoot, 'REQ-123', 'T001'), 'utf8');
138
136
 
139
137
  expect(result.success).toBe(false);
140
138
  expect(nextManifest.tasks[0].status).toBe('failed');
141
139
  expect(nextManifest.tasks[0].lastError).toContain('Stale result rejected');
142
- expect(checkpoint.planVersion).toBe(1);
143
- expect(checkpoint.error).toContain('Stale result rejected');
144
140
  expect(events).toContain('task_stale_rejected');
145
141
  });
146
142
 
147
- test('restores unresolved work from the latest stable checkpoint on resume without creating handoff markdown', async () => {
143
+ test('restores unresolved work from the latest stable manifest state on resume without creating handoff markdown', async () => {
148
144
  const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-resume-stable-'));
149
145
  const changeId = 'REQ-123';
150
146
  const manifestPath = getTaskManifestPath(repoRoot, changeId);
151
147
 
152
148
  writeJson(getRuntimeStatePath(repoRoot, changeId), {
153
149
  changeId,
154
- changeKey: 'REQ-123-recover-from-stable-checkpoint',
155
- slug: 'recover-from-stable-checkpoint',
150
+ changeKey: 'REQ-123-recover-from-stable-state',
151
+ slug: 'recover-from-stable-state',
156
152
  createdAt: '2026-04-09T01:00:00.000Z',
157
- goal: 'Recover from stable checkpoint',
153
+ goal: 'Recover from stable state',
158
154
  status: 'in_progress',
159
155
  initializedAt: '2026-04-09T01:00:00.000Z',
160
156
  plannedAt: '2026-04-09T01:01:00.000Z',
@@ -169,13 +165,13 @@ describe('runDispatch', () => {
169
165
 
170
166
  writeJson(manifestPath, {
171
167
  changeId,
172
- goal: 'Recover from stable checkpoint',
168
+ goal: 'Recover from stable state',
173
169
  createdAt: '2026-04-09T01:00:00.000Z',
174
170
  updatedAt: '2026-04-09T01:02:00.000Z',
175
171
  tasks: [
176
172
  {
177
173
  id: 'T001',
178
- title: 'Stable checkpoint task',
174
+ title: 'Stable completed task',
179
175
  type: 'TEST',
180
176
  dependsOn: [],
181
177
  touches: ['src/a.ts'],
@@ -219,32 +215,6 @@ describe('runDispatch', () => {
219
215
  }
220
216
  });
221
217
 
222
- writeJson(getCheckpointPath(repoRoot, changeId, 'T001'), {
223
- changeId,
224
- taskId: 'T001',
225
- sessionId: 'stable-session',
226
- planVersion: 1,
227
- status: 'passed',
228
- summary: 'Task passed after 1 attempt(s)',
229
- error: '',
230
- outputExcerpt: '',
231
- timestamp: '2026-04-09T01:05:00.000Z',
232
- attempt: 1
233
- });
234
-
235
- writeJson(getCheckpointPath(repoRoot, changeId, 'T002'), {
236
- changeId,
237
- taskId: 'T002',
238
- sessionId: 'failed-session',
239
- planVersion: 1,
240
- status: 'failed',
241
- summary: 'Task failed: Command failed',
242
- error: 'Command failed',
243
- outputExcerpt: 'Command failed',
244
- timestamp: '2026-04-09T01:06:00.000Z',
245
- attempt: 2
246
- });
247
-
248
218
  const result = await runResume({
249
219
  repoRoot,
250
220
  changeId,
@@ -255,7 +225,7 @@ describe('runDispatch', () => {
255
225
  const nextManifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
256
226
 
257
227
  expect(result.success).toBe(true);
258
- expect(result.restoredCheckpoint).toMatchObject({
228
+ expect(result.restoredState).toMatchObject({
259
229
  taskId: 'T001',
260
230
  status: 'passed'
261
231
  });
@@ -15,8 +15,7 @@ const {
15
15
  const {
16
16
  getRuntimeStatePath,
17
17
  getTaskManifestPath,
18
- getReportCardPath,
19
- getCheckpointPath
18
+ getReportCardPath
20
19
  } = require('../store');
21
20
 
22
21
  function writeJson(filePath, value) {
@@ -86,21 +85,6 @@ describe('intent handoff bridge', () => {
86
85
  }
87
86
  );
88
87
 
89
- writeJson(
90
- getCheckpointPath(repoRoot, 'REQ-123', 'T001'),
91
- {
92
- changeId: 'REQ-123',
93
- taskId: 'T001',
94
- sessionId: 'T001-session',
95
- planVersion: 1,
96
- status: 'passed',
97
- summary: 'Task passed after 1 attempt',
98
- error: '',
99
- outputExcerpt: 'worker-ok',
100
- timestamp: '2026-03-25T01:11:00.000Z',
101
- attempt: 1
102
- }
103
- );
104
88
  });
105
89
 
106
90
  test('syncIntentMemory removes legacy projection files instead of writing them', async () => {
@@ -152,7 +136,7 @@ describe('intent handoff bridge', () => {
152
136
  }
153
137
  });
154
138
 
155
- test('writes pr brief from manifest, checkpoints, and report-card only', async () => {
139
+ test('writes pr brief from manifest task state and report-card only', async () => {
156
140
  const prBriefPath = getIntentPrBriefPath(repoRoot, 'REQ-123');
157
141
  const manifestPath = getTaskManifestPath(repoRoot, 'REQ-123');
158
142
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
@@ -208,8 +192,8 @@ describe('intent handoff bridge', () => {
208
192
 
209
193
  expect(prBrief).toContain('PR Brief: REQ-123');
210
194
  expect(prBrief).toContain('Suggested Title: feat(req-123): Deliver autopilot memory bridge');
211
- expect(prBrief).toContain('execution/tasks/T001/checkpoint.json');
212
- expect(prBrief).toContain('execution/tasks/T002/checkpoint.json');
195
+ expect(prBrief).toContain('planning/tasks.md');
196
+ expect(prBrief).toContain('planning/task-manifest.json');
213
197
  expect(prBrief).toContain('存在 1 个 skipped 任务,需要确认是否可接受:T002');
214
198
  expect(prBrief).not.toContain('result.md');
215
199
  for (const filePath of getIntentHandoffArtifactPaths(repoRoot, 'REQ-123')) {
@@ -104,7 +104,7 @@ describe('lifecycle helpers', () => {
104
104
  };
105
105
 
106
106
  expect(deriveLifecycleNextAction({ state: approvedState, manifest, report: null })).toBe(
107
- '优先修复失败任务 T002,然后从最近稳定 checkpoint 恢复。'
107
+ '优先修复失败任务 T002,然后从 task-manifest 的最近稳定状态恢复。'
108
108
  );
109
109
  expect(
110
110
  deriveLifecycleNextAction({
@@ -4,6 +4,7 @@ const path = require('path');
4
4
 
5
5
  const {
6
6
  buildChangeKey,
7
+ getChangeKeyPrefix,
7
8
  getChangePaths,
8
9
  getChangeSlug,
9
10
  nextChangeKey
@@ -17,11 +18,16 @@ describe('change directory naming', () => {
17
18
  .toBe('FIX-123-修复-目录-命名');
18
19
  });
19
20
 
21
+ test('derives the numeric prefix from full change keys', () => {
22
+ expect(getChangeKeyPrefix('REQ-123-plan-folder-contract')).toBe('REQ-123');
23
+ expect(getChangeKeyPrefix('FIX-456-crash-on-start')).toBe('FIX-456');
24
+ });
25
+
20
26
  test('rejects change ids and explicit keys outside the canonical prefix contract', () => {
21
27
  const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-paths-'));
22
28
 
23
29
  expect(() => buildChangeKey('BUG-123', { goal: 'legacy bug id' }))
24
- .toThrow(/REQ-<number> or FIX-<number>/);
30
+ .toThrow(/REQ-<number>\[-description\] or FIX-<number>\[-description\]/);
25
31
  expect(() => getChangePaths(repoRoot, 'REQ-123', { changeKey: 'req-123-lowercase' }))
26
32
  .toThrow(/Expected REQ-123-<description>/);
27
33
 
@@ -107,6 +107,67 @@ describe('TDD Order Validation', () => {
107
107
  });
108
108
 
109
109
  describe('Manifest execution state', () => {
110
+ test('createTaskManifest reads tasks.md when legacy design.md is absent', async () => {
111
+ const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-planner-new-contract-'));
112
+ const changeId = 'REQ-322';
113
+ const changeKey = 'REQ-322-new-contract';
114
+ const tasksPath = path.join(repoRoot, 'devflow', 'changes', changeKey, 'planning', 'tasks.md');
115
+ fs.mkdirSync(path.dirname(tasksPath), { recursive: true });
116
+ fs.writeFileSync(
117
+ tasksPath,
118
+ [
119
+ '## Phase 1: Build',
120
+ '',
121
+ '- [ ] T001 [TEST] Counter behavior `src/counter.test.ts`',
122
+ '- [ ] T002 [IMPL] Counter behavior (dependsOn:T001) `src/counter.ts`'
123
+ ].join('\n')
124
+ );
125
+
126
+ const manifest = await createTaskManifest({
127
+ repoRoot,
128
+ changeId,
129
+ changeKey,
130
+ goal: 'Exercise new contract readFiles',
131
+ overwrite: true
132
+ });
133
+
134
+ expect(manifest.tasks[0].context.readFiles).toEqual(['tasks.md', 'src/counter.test.ts']);
135
+ expect(manifest.tasks[1].context.readFiles).toEqual(['tasks.md']);
136
+
137
+ fs.rmSync(repoRoot, { recursive: true, force: true });
138
+ });
139
+
140
+ test('createTaskManifest keeps design.md when legacy design.md exists', async () => {
141
+ const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-planner-legacy-contract-'));
142
+ const changeId = 'REQ-323';
143
+ const changeKey = 'REQ-323-legacy-contract';
144
+ const planningDir = path.join(repoRoot, 'devflow', 'changes', changeKey, 'planning');
145
+ fs.mkdirSync(planningDir, { recursive: true });
146
+ fs.writeFileSync(path.join(planningDir, 'design.md'), '# DESIGN\n');
147
+ fs.writeFileSync(
148
+ path.join(planningDir, 'tasks.md'),
149
+ [
150
+ '## Phase 1: Build',
151
+ '',
152
+ '- [ ] T001 [TEST] Counter behavior `src/counter.test.ts`',
153
+ '- [ ] T002 [IMPL] Counter behavior (dependsOn:T001) `src/counter.ts`'
154
+ ].join('\n')
155
+ );
156
+
157
+ const manifest = await createTaskManifest({
158
+ repoRoot,
159
+ changeId,
160
+ changeKey,
161
+ goal: 'Exercise legacy contract readFiles',
162
+ overwrite: true
163
+ });
164
+
165
+ expect(manifest.tasks[0].context.readFiles).toEqual(['design.md', 'tasks.md', 'src/counter.test.ts']);
166
+ expect(manifest.tasks[1].context.readFiles).toEqual(['design.md']);
167
+
168
+ fs.rmSync(repoRoot, { recursive: true, force: true });
169
+ });
170
+
110
171
  test('should derive active phase and current task from ready tasks', () => {
111
172
  const state = deriveManifestExecutionState([
112
173
  { id: 'T001', phase: 1, status: 'passed', dependsOn: [] },
@@ -6,8 +6,7 @@ const { runPreparePr } = require('../operations/prepare-pr');
6
6
  const {
7
7
  getRuntimeStatePath,
8
8
  getTaskManifestPath,
9
- getReportCardPath,
10
- getCheckpointPath
9
+ getReportCardPath
11
10
  } = require('../store');
12
11
  const {
13
12
  getIntentPrBriefPath,
@@ -94,19 +93,6 @@ describe('runPreparePr', () => {
94
93
  timestamp: '2026-03-25T01:11:00.000Z'
95
94
  });
96
95
 
97
- writeJson(getCheckpointPath(repoRoot, 'REQ-123', 'T001'), {
98
- changeId: 'REQ-123',
99
- taskId: 'T001',
100
- sessionId: 'task-session',
101
- planVersion: 1,
102
- status: 'passed',
103
- summary: 'Task passed after 1 attempt',
104
- error: '',
105
- outputExcerpt: 'ok',
106
- timestamp: '2026-03-25T01:09:00.000Z',
107
- attempt: 1
108
- });
109
-
110
96
  writeJson(getRuntimeStatePath(repoRoot, 'REQ-123'), {
111
97
  ...JSON.parse(fs.readFileSync(getRuntimeStatePath(repoRoot, 'REQ-123'), 'utf8')),
112
98
  approval: {
@@ -130,7 +116,8 @@ describe('runPreparePr', () => {
130
116
  expect(result.status).toBe('prepared');
131
117
  expect(result.suggestedTitle).toBe('feat(req-123): Prepare a PR-ready brief');
132
118
  expect(prBrief).toContain('PR Brief: REQ-123');
133
- expect(prBrief).toContain('execution/tasks/T001/checkpoint.json');
119
+ expect(prBrief).toContain('planning/task-manifest.json');
120
+ expect(prBrief).not.toContain('execution/tasks/T001/checkpoint.json');
134
121
  expect(prBrief).not.toContain('result.md');
135
122
  for (const filePath of getIntentHandoffArtifactPaths(repoRoot, 'REQ-123')) {
136
123
  expect(fs.existsSync(filePath)).toBe(filePath === prBriefPath);