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
@@ -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
  });
@@ -0,0 +1,148 @@
1
+ /**
2
+ * [INPUT]: 依赖 runReviewSuite、临时 devflow review artifacts。
3
+ * [OUTPUT]: 验证 cc-check review 读取 review-findings.json 优先并按 legacy 层级 fallback。
4
+ * [POS]: REQ-003-minimize-workflow-artifacts T015 的 Red/Green 证据。
5
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const os = require('os');
10
+ const path = require('path');
11
+
12
+ const { runReviewSuite } = require('../review');
13
+
14
+ function writeJson(filePath, value) {
15
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
16
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
17
+ }
18
+
19
+ function writeText(filePath, value) {
20
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
21
+ fs.writeFileSync(filePath, value);
22
+ }
23
+
24
+ function makeReviewRoot(changeId, changeKey) {
25
+ const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-review-check-'));
26
+ const reviewDir = path.join(repoRoot, 'devflow', 'changes', changeKey, 'review');
27
+ fs.mkdirSync(reviewDir, { recursive: true });
28
+ return { repoRoot, reviewDir, changeId, changeKey };
29
+ }
30
+
31
+ function findingsDoc(overrides = {}) {
32
+ return {
33
+ schema: 'review-findings.v2',
34
+ change: 'REQ-140-review-records',
35
+ reviewId: 'RVW-20260512-001',
36
+ headSha: 'def456',
37
+ freshness: {
38
+ status: 'fresh',
39
+ reviewedCommit: 'def456',
40
+ currentCommit: 'def456',
41
+ commitsSinceReview: 0
42
+ },
43
+ summary: {
44
+ status: 'findings',
45
+ blockingCount: 1,
46
+ warningCount: 0,
47
+ next: 'cc-do'
48
+ },
49
+ findings: [
50
+ {
51
+ id: 'F001',
52
+ severity: 'important',
53
+ confidence: 8,
54
+ displayTier: 'blocking',
55
+ fingerprint: 'sha256:f001',
56
+ scope: 'inside current requirement blast radius',
57
+ path: 'lib/skill-runtime/review.js',
58
+ evidence: 'review records should be consumed before legacy report text',
59
+ recommendation: 'read review-findings.json first',
60
+ route: 'cc-do'
61
+ }
62
+ ],
63
+ ...overrides
64
+ };
65
+ }
66
+
67
+ async function runReviewCase(files) {
68
+ const ctx = makeReviewRoot('REQ-140', 'REQ-140-review-records');
69
+ if (files.findings) {
70
+ writeJson(path.join(ctx.reviewDir, 'review-findings.json'), files.findings);
71
+ }
72
+ if (files.ledger) {
73
+ writeText(path.join(ctx.reviewDir, 'review-ledger.jsonl'), files.ledger);
74
+ }
75
+ if (files.legacyReport) {
76
+ writeText(path.join(ctx.reviewDir, 'cc-review-report.md'), files.legacyReport);
77
+ }
78
+
79
+ const review = await runReviewSuite({
80
+ repoRoot: ctx.repoRoot,
81
+ changeId: ctx.changeId,
82
+ manifest: { tasks: [] },
83
+ strict: false,
84
+ skipReview: true
85
+ });
86
+ fs.rmSync(ctx.repoRoot, { recursive: true, force: true });
87
+ return review;
88
+ }
89
+
90
+ describe('cc-check review record fallback', () => {
91
+ test('reads review-findings.json before ledger or legacy report', async () => {
92
+ const review = await runReviewCase({
93
+ findings: findingsDoc(),
94
+ ledger: '{"schema":"review-ledger.v2","change":"REQ-140-review-records","reviewId":"RVW-20260512-002","createdAt":"2026-05-12T00:00:00.000Z","createdBy":"cc-devflow-cli","event":"review-closed","status":"clean","blockingCount":0,"warningCount":0,"next":"cc-check"}\n',
95
+ legacyReport: '# Legacy Report\n'
96
+ });
97
+
98
+ expect(review.recordReview).toMatchObject({
99
+ source: 'review-findings.json',
100
+ status: 'fail',
101
+ freshness: { status: 'fresh' }
102
+ });
103
+ expect(review.findings[0]).toMatchObject({
104
+ id: 'F001',
105
+ source: 'review-records',
106
+ severity: 'important',
107
+ displayTier: 'blocking'
108
+ });
109
+ });
110
+
111
+ test('falls back to ledger tail when findings doc is missing', async () => {
112
+ const review = await runReviewCase({
113
+ ledger: [
114
+ '{"schema":"review-ledger.v2","change":"REQ-140-review-records","reviewId":"RVW-20260512-001","createdAt":"2026-05-12T00:00:00.000Z","createdBy":"cc-devflow-cli","event":"review-started","mode":"implementation","scope":"current-diff","baseSha":"abc123","headSha":"def456","selectedNodes":[],"skippedNodes":[],"riskLanes":[]}',
115
+ '{"schema":"review-ledger.v2","change":"REQ-140-review-records","reviewId":"RVW-20260512-001","createdAt":"2026-05-12T00:01:00.000Z","createdBy":"cc-devflow-cli","event":"review-closed","status":"clean","blockingCount":0,"warningCount":0,"next":"cc-check"}'
116
+ ].join('\n')
117
+ });
118
+
119
+ expect(review.recordReview).toMatchObject({
120
+ source: 'review-ledger.jsonl',
121
+ status: 'pass',
122
+ summary: expect.stringContaining('review ledger')
123
+ });
124
+ });
125
+
126
+ test('falls back to legacy cc-review-report.md with unknown freshness', async () => {
127
+ const review = await runReviewCase({
128
+ legacyReport: '# Legacy Review\n\nNo blocking findings.\n'
129
+ });
130
+
131
+ expect(review.recordReview).toMatchObject({
132
+ source: 'cc-review-report.md',
133
+ status: 'pass',
134
+ freshness: { status: 'unknown' }
135
+ });
136
+ });
137
+
138
+ test('blocks when all review records are missing', async () => {
139
+ const review = await runReviewCase({});
140
+
141
+ expect(review.status).toBe('blocked');
142
+ expect(review.recordReview).toMatchObject({
143
+ source: 'review-records',
144
+ status: 'blocked',
145
+ summary: expect.stringContaining('review-missing')
146
+ });
147
+ });
148
+ });