cc-devflow 4.5.9 → 4.5.11

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 (122) hide show
  1. package/.claude/skills/cc-act/CHANGELOG.md +11 -0
  2. package/.claude/skills/cc-act/SKILL.md +19 -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 +23 -0
  7. package/.claude/skills/cc-check/PLAYBOOK.md +1 -0
  8. package/.claude/skills/cc-check/SKILL.md +15 -9
  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 +10 -0
  12. package/.claude/skills/cc-dev/SKILL.md +34 -2
  13. package/.claude/skills/cc-do/CHANGELOG.md +18 -0
  14. package/.claude/skills/cc-do/PLAYBOOK.md +7 -7
  15. package/.claude/skills/cc-do/SKILL.md +47 -40
  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 +24 -0
  23. package/.claude/skills/cc-investigate/PLAYBOOK.md +10 -9
  24. package/.claude/skills/cc-investigate/SKILL.md +163 -417
  25. package/.claude/skills/cc-investigate/assets/TASKS_TEMPLATE.md +56 -10
  26. package/.claude/skills/cc-investigate/assets/TASK_MANIFEST_TEMPLATE.json +6 -6
  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 +5 -4
  29. package/.claude/skills/cc-investigate/scripts/bootstrap-analysis.sh +1 -1
  30. package/.claude/skills/cc-plan/CHANGELOG.md +32 -0
  31. package/.claude/skills/cc-plan/PLAYBOOK.md +55 -53
  32. package/.claude/skills/cc-plan/SKILL.md +209 -536
  33. package/.claude/skills/cc-plan/assets/TASKS_TEMPLATE.md +50 -14
  34. package/.claude/skills/cc-plan/assets/TASK_MANIFEST_TEMPLATE.json +5 -4
  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 +12 -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 +30 -0
  47. package/CONTRIBUTING.md +40 -4
  48. package/CONTRIBUTING.zh-CN.md +40 -4
  49. package/README.md +22 -8
  50. package/README.zh-CN.md +22 -8
  51. package/bin/cc-devflow-cli.js +293 -36
  52. package/docs/examples/START-HERE.md +6 -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 +5 -1
  73. package/docs/guides/getting-started.md +11 -8
  74. package/docs/guides/getting-started.zh-CN.md +11 -8
  75. package/docs/guides/minimize-artifacts.md +137 -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__/benchmark-skills.test.js +109 -0
  81. package/lib/skill-runtime/__tests__/cli-bootstrap.integration.test.js +2 -2
  82. package/lib/skill-runtime/__tests__/dispatch.test.js +8 -38
  83. package/lib/skill-runtime/__tests__/intent.test.js +4 -20
  84. package/lib/skill-runtime/__tests__/lifecycle.test.js +1 -1
  85. package/lib/skill-runtime/__tests__/paths.test.js +7 -1
  86. package/lib/skill-runtime/__tests__/planner.tdd.test.js +61 -0
  87. package/lib/skill-runtime/__tests__/prepare-pr.test.js +3 -16
  88. package/lib/skill-runtime/__tests__/query.test.js +388 -7
  89. package/lib/skill-runtime/__tests__/review-check-integration.test.js +148 -0
  90. package/lib/skill-runtime/__tests__/review-records.test.js +619 -0
  91. package/lib/skill-runtime/__tests__/runtime.integration.test.js +64 -23
  92. package/lib/skill-runtime/__tests__/schemas.test.js +43 -0
  93. package/lib/skill-runtime/__tests__/task-contract-migrate.test.js +137 -0
  94. package/lib/skill-runtime/__tests__/task-contract.test.js +874 -0
  95. package/lib/skill-runtime/__tests__/verify-artifacts.test.js +203 -0
  96. package/lib/skill-runtime/__tests__/worker-run.test.js +4 -11
  97. package/lib/skill-runtime/__tests__/workflow-context-legacy-fallback.test.js +31 -0
  98. package/lib/skill-runtime/__tests__/workflow-context.test.js +98 -0
  99. package/lib/skill-runtime/artifacts.js +0 -5
  100. package/lib/skill-runtime/context-index.js +545 -0
  101. package/lib/skill-runtime/intent.js +9 -33
  102. package/lib/skill-runtime/lifecycle.js +1 -1
  103. package/lib/skill-runtime/operations/CLAUDE.md +2 -2
  104. package/lib/skill-runtime/operations/dispatch.js +4 -42
  105. package/lib/skill-runtime/operations/init.js +2 -6
  106. package/lib/skill-runtime/operations/janitor.js +2 -18
  107. package/lib/skill-runtime/operations/resume.js +21 -38
  108. package/lib/skill-runtime/operations/review-records.js +265 -0
  109. package/lib/skill-runtime/operations/snapshot.js +1 -1
  110. package/lib/skill-runtime/operations/task-contract.js +593 -0
  111. package/lib/skill-runtime/operations/worker-run.js +2 -30
  112. package/lib/skill-runtime/paths.js +4 -4
  113. package/lib/skill-runtime/planner.js +24 -11
  114. package/lib/skill-runtime/query-registry.js +2 -2
  115. package/lib/skill-runtime/query.js +15 -2
  116. package/lib/skill-runtime/review-records.js +123 -0
  117. package/lib/skill-runtime/review.js +246 -11
  118. package/lib/skill-runtime/schemas.js +174 -12
  119. package/lib/skill-runtime/store.js +0 -10
  120. package/lib/skill-runtime/task-contract.js +188 -0
  121. package/lib/skill-runtime/workflow-context.js +748 -0
  122. package/package.json +6 -2
@@ -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);
@@ -6,6 +6,7 @@ const {
6
6
  getFullState,
7
7
  getNextTask,
8
8
  getProgress,
9
+ getWorkflowContext,
9
10
  listQueryIds,
10
11
  runQuery
11
12
  } = require('../query');
@@ -288,6 +289,386 @@ describe('query helpers', () => {
288
289
  expect(next.id).toBe('T002');
289
290
  });
290
291
 
292
+ test('returns compact workflow context for the current ready task', async () => {
293
+ const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-query-workflow-context-'));
294
+ const manifestPath = getTaskManifestPath(repoRoot, 'REQ-126');
295
+ const planningDir = path.dirname(manifestPath);
296
+ const changeDir = path.dirname(planningDir);
297
+
298
+ fs.mkdirSync(planningDir, { recursive: true });
299
+ fs.writeFileSync(path.join(planningDir, 'design.md'), '# Design\n');
300
+ fs.writeFileSync(path.join(planningDir, 'tasks.md'), '# Tasks\n');
301
+ writeJson(path.join(changeDir, 'change-meta.json'), {
302
+ spec: {
303
+ primaryCapability: 'cap-compact-context',
304
+ syncStatus: 'planned'
305
+ }
306
+ });
307
+ writeJson(manifestPath, {
308
+ changeId: 'REQ-126',
309
+ goal: 'Expose compact workflow context',
310
+ createdAt: '2026-05-12T01:00:00.000Z',
311
+ updatedAt: '2026-05-12T01:05:00.000Z',
312
+ currentTaskId: 'T002',
313
+ planningMeta: {
314
+ reqPlanSkillVersion: '3.8.8'
315
+ },
316
+ tasks: [
317
+ {
318
+ id: 'T001',
319
+ title: 'Completed test task',
320
+ type: 'TEST',
321
+ phase: 1,
322
+ dependsOn: [],
323
+ touches: ['src/feature.test.ts'],
324
+ files: ['src/feature.test.ts'],
325
+ run: ['npm test -- src/feature.test.ts'],
326
+ checks: [],
327
+ acceptance: [],
328
+ verification: ['npm test -- src/feature.test.ts'],
329
+ evidence: ['red output'],
330
+ context: { readFiles: ['planning/design.md'], commands: ['npm test -- src/feature.test.ts'], notes: [] },
331
+ status: 'passed',
332
+ attempts: 1,
333
+ maxRetries: 1
334
+ },
335
+ {
336
+ id: 'T002',
337
+ title: 'Implement compact context',
338
+ type: 'IMPL',
339
+ phase: 1,
340
+ dependsOn: ['T001'],
341
+ touches: ['lib/skill-runtime/query.js'],
342
+ files: ['lib/skill-runtime/query.js'],
343
+ run: ['npm test -- lib/skill-runtime/__tests__/query.test.js'],
344
+ checks: [],
345
+ acceptance: [],
346
+ verification: ['npm test -- lib/skill-runtime/__tests__/query.test.js'],
347
+ evidence: ['green output'],
348
+ context: {
349
+ readFiles: ['design.md', 'tasks.md', 'change-meta.json', 'lib/skill-runtime/query.js'],
350
+ commands: ['npm test -- lib/skill-runtime/__tests__/query.test.js'],
351
+ notes: ['Read the context index before opening deep docs']
352
+ },
353
+ status: 'pending',
354
+ attempts: 0,
355
+ maxRetries: 1
356
+ }
357
+ ],
358
+ metadata: {
359
+ source: 'test',
360
+ generatedBy: 'test',
361
+ planVersion: 2
362
+ }
363
+ });
364
+
365
+ const context = await getWorkflowContext(repoRoot, 'REQ-126');
366
+
367
+ expect(context.nextAction).toMatchObject({
368
+ skill: 'cc-do',
369
+ action: 'execute-current-task',
370
+ taskId: 'T002'
371
+ });
372
+ expect(context.currentTask).toMatchObject({
373
+ id: 'T002',
374
+ context: {
375
+ readFiles: expect.arrayContaining(['design.md', 'tasks.md', 'change-meta.json', 'lib/skill-runtime/query.js'])
376
+ }
377
+ });
378
+ expect(context.progressiveDisclosure).toMatchObject({
379
+ mode: 'compact-first',
380
+ rule: expect.stringContaining('context index'),
381
+ packetOnly: {
382
+ contractDigest: expect.any(String),
383
+ manifestDigest: expect.any(String),
384
+ currentTaskDigest: expect.any(String),
385
+ evidenceDigest: expect.any(String),
386
+ mustNotForgetDigest: expect.any(String)
387
+ }
388
+ });
389
+ expect(context.source.manifest).toMatchObject({
390
+ path: expect.stringContaining('planning/task-manifest.json'),
391
+ hash: expect.stringMatching(/^sha256:/)
392
+ });
393
+ expect(context.progressiveDisclosure.sourceHashes).toEqual(expect.objectContaining({
394
+ [context.source.manifest.path]: expect.stringMatching(/^sha256:/)
395
+ }));
396
+ expect(context.progressiveDisclosure.mustNotForget).toMatchObject({
397
+ goal: {
398
+ value: 'Expose compact workflow context',
399
+ source: {
400
+ ref: expect.stringContaining('planning/task-manifest.json#/goal'),
401
+ hash: expect.stringMatching(/^sha256:/)
402
+ }
403
+ },
404
+ nonNegotiables: expect.arrayContaining([
405
+ expect.objectContaining({
406
+ value: expect.stringContaining('read-only'),
407
+ source: expect.objectContaining({ ref: expect.any(String) })
408
+ })
409
+ ]),
410
+ acceptanceGates: expect.arrayContaining([
411
+ expect.objectContaining({
412
+ value: 'npm test -- lib/skill-runtime/__tests__/query.test.js',
413
+ source: expect.objectContaining({ ref: expect.stringContaining('planning/task-manifest.json#/tasks/T002') })
414
+ })
415
+ ])
416
+ });
417
+ expect(context.progressiveDisclosure.defaultOpen).toEqual(expect.arrayContaining([
418
+ expect.objectContaining({
419
+ ref: expect.stringContaining('planning/design.md#design'),
420
+ reason: 'primary task contract',
421
+ exists: true,
422
+ sourceHash: expect.stringMatching(/^sha256:/)
423
+ }),
424
+ expect.objectContaining({
425
+ ref: expect.stringContaining('planning/task-manifest.json#/tasks/T002'),
426
+ reason: 'current task source of truth',
427
+ sourceHash: expect.stringMatching(/^sha256:/)
428
+ }),
429
+ expect.objectContaining({
430
+ ref: expect.stringContaining('change-meta.json#/spec')
431
+ })
432
+ ]));
433
+ expect(context.progressiveDisclosure.defaultOpen.every((entry) => entry.exists === true)).toBe(true);
434
+ expect(context.progressiveDisclosure.defaultRead).toBeUndefined();
435
+ expect(context.progressiveDisclosure.deepOpen).toEqual(expect.arrayContaining([
436
+ expect.objectContaining({
437
+ when: 'scope_or_contract_uncertain',
438
+ conditions: expect.arrayContaining(['confidence.scope < 0.85']),
439
+ refs: expect.arrayContaining([
440
+ expect.objectContaining({ ref: expect.stringContaining('planning/design.md') })
441
+ ])
442
+ }),
443
+ expect.objectContaining({
444
+ when: 'implementation_details_needed',
445
+ refs: expect.arrayContaining([
446
+ expect.objectContaining({ ref: 'lib/skill-runtime/query.js' })
447
+ ])
448
+ })
449
+ ]));
450
+ expect(context.progressiveDisclosure.defaultOpen).not.toEqual(expect.arrayContaining([
451
+ 'design.md',
452
+ 'tasks.md',
453
+ 'change-meta.json'
454
+ ]));
455
+ expect(context.progressiveDisclosure.commandsToTrust).toContain('npm test -- lib/skill-runtime/__tests__/query.test.js');
456
+ expect(context.progressiveDisclosure.openWhen).toEqual(expect.arrayContaining([
457
+ expect.objectContaining({
458
+ trigger: expect.stringContaining('all tasks are complete'),
459
+ conditions: expect.arrayContaining(['nextAction.skill == cc-act'])
460
+ })
461
+ ]));
462
+ expect(context.planningMeta).toMatchObject({
463
+ reqPlanSkillVersion: '3.8.8',
464
+ sourceCapability: 'cap-compact-context',
465
+ specSyncStatus: 'planned'
466
+ });
467
+ });
468
+
469
+ test('routes compact workflow context through check and act after execution', async () => {
470
+ const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-query-workflow-terminal-'));
471
+
472
+ writeJson(getTaskManifestPath(repoRoot, 'REQ-127'), {
473
+ changeId: 'REQ-127',
474
+ goal: 'Expose terminal workflow routing',
475
+ createdAt: '2026-05-12T01:00:00.000Z',
476
+ updatedAt: '2026-05-12T01:05:00.000Z',
477
+ tasks: [
478
+ {
479
+ id: 'T001',
480
+ status: 'passed',
481
+ verification: ['npm test -- final.test.js'],
482
+ context: {
483
+ commands: ['npm test -- final.test.js']
484
+ }
485
+ }
486
+ ],
487
+ metadata: {
488
+ source: 'test',
489
+ generatedBy: 'test',
490
+ planVersion: 1
491
+ }
492
+ });
493
+
494
+ await expect(getWorkflowContext(repoRoot, 'REQ-127')).resolves.toMatchObject({
495
+ nextAction: {
496
+ skill: 'cc-check',
497
+ action: 'build-fresh-verdict'
498
+ },
499
+ progressiveDisclosure: {
500
+ commandsToTrust: ['npm test -- final.test.js']
501
+ }
502
+ });
503
+
504
+ writeJson(getReportCardPath(repoRoot, 'REQ-127'), {
505
+ changeId: 'REQ-127',
506
+ verdict: 'pass',
507
+ overall: 'pass',
508
+ reroute: 'none',
509
+ specSyncReady: false,
510
+ blockingFindings: [],
511
+ timestamp: '2026-05-12T01:10:00.000Z'
512
+ });
513
+
514
+ await expect(getWorkflowContext(repoRoot, 'REQ-127')).resolves.toMatchObject({
515
+ nextAction: {
516
+ skill: 'cc-check',
517
+ action: 'build-fresh-verdict',
518
+ blockers: expect.arrayContaining(['specSyncReady is not true'])
519
+ }
520
+ });
521
+
522
+ writeJson(getReportCardPath(repoRoot, 'REQ-127'), {
523
+ changeId: 'REQ-127',
524
+ verdict: 'pass',
525
+ overall: 'pass',
526
+ reroute: 'none',
527
+ specSyncReady: true,
528
+ blockingFindings: [],
529
+ timestamp: '2026-05-12T01:11:00.000Z'
530
+ });
531
+
532
+ await expect(getWorkflowContext(repoRoot, 'REQ-127')).resolves.toMatchObject({
533
+ nextAction: {
534
+ skill: 'cc-act',
535
+ action: 'ship-or-handoff'
536
+ }
537
+ });
538
+ });
539
+
540
+ test('resumes current running task before selecting another ready task', async () => {
541
+ const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-query-workflow-resume-'));
542
+
543
+ writeJson(getTaskManifestPath(repoRoot, 'REQ-130'), {
544
+ changeId: 'REQ-130',
545
+ currentTaskId: 'T002',
546
+ tasks: [
547
+ { id: 'T001', status: 'passed' },
548
+ {
549
+ id: 'T002',
550
+ status: 'running',
551
+ dependsOn: ['T001'],
552
+ verification: ['npm test -- src/current.test.ts'],
553
+ context: {
554
+ commands: ['npm test -- src/current.test.ts']
555
+ }
556
+ },
557
+ {
558
+ id: 'T003',
559
+ status: 'pending',
560
+ dependsOn: ['T001'],
561
+ verification: ['npm test -- src/ready.test.ts'],
562
+ context: {
563
+ commands: ['npm test -- src/ready.test.ts']
564
+ }
565
+ }
566
+ ],
567
+ metadata: {
568
+ source: 'test',
569
+ generatedBy: 'test',
570
+ planVersion: 1
571
+ }
572
+ });
573
+
574
+ await expect(getWorkflowContext(repoRoot, 'REQ-130')).resolves.toMatchObject({
575
+ nextAction: {
576
+ skill: 'cc-do',
577
+ action: 'resume-current-task',
578
+ taskId: 'T002'
579
+ },
580
+ currentTask: {
581
+ id: 'T002',
582
+ status: 'running'
583
+ }
584
+ });
585
+ });
586
+
587
+ test('resumes inferred running task before selecting ready task when currentTaskId is missing', async () => {
588
+ const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-query-workflow-inferred-resume-'));
589
+
590
+ writeJson(getTaskManifestPath(repoRoot, 'REQ-132'), {
591
+ changeId: 'REQ-132',
592
+ tasks: [
593
+ { id: 'T001', status: 'passed' },
594
+ {
595
+ id: 'T002',
596
+ status: 'running',
597
+ dependsOn: ['T001'],
598
+ verification: ['npm test -- src/current.test.ts'],
599
+ context: {
600
+ commands: ['npm test -- src/current.test.ts']
601
+ }
602
+ },
603
+ {
604
+ id: 'T003',
605
+ status: 'pending',
606
+ dependsOn: ['T001'],
607
+ verification: ['npm test -- src/ready.test.ts'],
608
+ context: {
609
+ commands: ['npm test -- src/ready.test.ts']
610
+ }
611
+ }
612
+ ],
613
+ metadata: {
614
+ source: 'test',
615
+ generatedBy: 'test',
616
+ planVersion: 1
617
+ }
618
+ });
619
+
620
+ await expect(getWorkflowContext(repoRoot, 'REQ-132')).resolves.toMatchObject({
621
+ nextAction: {
622
+ skill: 'cc-do',
623
+ action: 'resume-current-task',
624
+ taskId: 'T002'
625
+ },
626
+ currentTask: {
627
+ id: 'T002',
628
+ status: 'running'
629
+ }
630
+ });
631
+ });
632
+
633
+ test('reports missing verification commands instead of trusting the query command', async () => {
634
+ const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-query-workflow-missing-commands-'));
635
+
636
+ writeJson(getTaskManifestPath(repoRoot, 'REQ-131'), {
637
+ changeId: 'REQ-131',
638
+ currentTaskId: 'T001',
639
+ tasks: [
640
+ {
641
+ id: 'T001',
642
+ status: 'pending',
643
+ context: {
644
+ readFiles: ['design.md']
645
+ }
646
+ }
647
+ ],
648
+ metadata: {
649
+ source: 'test',
650
+ generatedBy: 'test',
651
+ planVersion: 1
652
+ }
653
+ });
654
+
655
+ await expect(getWorkflowContext(repoRoot, 'REQ-131')).resolves.toMatchObject({
656
+ nextAction: {
657
+ skill: 'cc-plan',
658
+ action: 'repair-task-verification',
659
+ taskId: 'T001'
660
+ },
661
+ progressiveDisclosure: {
662
+ commandsToTrust: [],
663
+ missingVerificationCommands: true,
664
+ manifestIssue: 'current task has no executable verification command',
665
+ failClosed: expect.arrayContaining([
666
+ expect.stringContaining('verification command is missing')
667
+ ])
668
+ }
669
+ });
670
+ });
671
+
291
672
  test('dispatches typed query ids with trace metadata', async () => {
292
673
  const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-query-registry-'));
293
674
 
@@ -321,7 +702,7 @@ describe('query helpers', () => {
321
702
  }
322
703
  });
323
704
 
324
- expect(listQueryIds()).toEqual(expect.arrayContaining(['full-state', 'next-task', 'progress']));
705
+ expect(listQueryIds()).toEqual(expect.arrayContaining(['full-state', 'next-task', 'progress', 'workflow-context']));
325
706
  });
326
707
 
327
708
  test('requires a full change key when duplicate local numbers exist', async () => {
@@ -354,7 +735,7 @@ describe('query helpers', () => {
354
735
  },
355
736
  trace: {
356
737
  event: 'query.progress.failed',
357
- nextAction: 'inspect-runtime-artifacts'
738
+ nextAction: 'inspect-workflow-artifacts'
358
739
  }
359
740
  });
360
741
 
@@ -381,7 +762,7 @@ describe('query helpers', () => {
381
762
  queryId: 'unknown-query',
382
763
  error: {
383
764
  name: 'UnknownQueryError',
384
- rescueAction: 'use one of: full-state, next-task, progress, ship-readiness'
765
+ rescueAction: 'use one of: full-state, next-task, progress, ship-readiness, workflow-context'
385
766
  },
386
767
  trace: {
387
768
  nextAction: 'choose-supported-query'
@@ -400,11 +781,11 @@ describe('query helpers', () => {
400
781
  artifactRefs: [
401
782
  expect.stringContaining('task-manifest.json')
402
783
  ],
403
- rescueAction: 'create required runtime artifacts before running this query'
784
+ rescueAction: 'create required workflow artifacts before running this query'
404
785
  },
405
786
  trace: {
406
787
  event: 'query.progress.failed',
407
- nextAction: 'create required runtime artifacts before running this query'
788
+ nextAction: 'create required workflow artifacts before running this query'
408
789
  }
409
790
  });
410
791
  });
@@ -423,11 +804,11 @@ describe('query helpers', () => {
423
804
  artifactRefs: [
424
805
  expect.stringContaining('task-manifest.json')
425
806
  ],
426
- rescueAction: 'repair or regenerate the invalid runtime artifact before running this query'
807
+ rescueAction: 'repair or regenerate the invalid workflow artifact before running this query'
427
808
  },
428
809
  trace: {
429
810
  event: 'query.progress.failed',
430
- nextAction: 'repair or regenerate the invalid runtime artifact before running this query'
811
+ nextAction: 'repair or regenerate the invalid workflow artifact before running this query'
431
812
  }
432
813
  });
433
814
  });