cc-devflow 4.5.9 → 4.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/cc-act/CHANGELOG.md +6 -0
- package/.claude/skills/cc-act/SKILL.md +12 -10
- package/.claude/skills/cc-act/assets/PR_BRIEF_TEMPLATE.md +1 -1
- package/.claude/skills/cc-act/references/closure-contract.md +1 -1
- package/.claude/skills/cc-act/references/git-commit-guidelines.md +1 -1
- package/.claude/skills/cc-check/CHANGELOG.md +17 -0
- package/.claude/skills/cc-check/PLAYBOOK.md +1 -0
- package/.claude/skills/cc-check/SKILL.md +9 -5
- package/.claude/skills/cc-check/references/review-contract.md +7 -0
- package/.claude/skills/cc-check/scripts/render-report-card.js +6 -1
- package/.claude/skills/cc-dev/CHANGELOG.md +5 -0
- package/.claude/skills/cc-dev/SKILL.md +26 -1
- package/.claude/skills/cc-do/CHANGELOG.md +12 -0
- package/.claude/skills/cc-do/PLAYBOOK.md +7 -7
- package/.claude/skills/cc-do/SKILL.md +35 -37
- package/.claude/skills/cc-do/references/execution-recovery.md +18 -13
- package/.claude/skills/cc-do/scripts/build-task-context.sh +4 -17
- package/.claude/skills/cc-do/scripts/record-review-decision.sh +4 -5
- package/.claude/skills/cc-do/scripts/recover-workflow.sh +9 -11
- package/.claude/skills/cc-do/scripts/verify-task-gates.sh +12 -10
- package/.claude/skills/cc-do/scripts/write-task-checkpoint.sh +7 -29
- package/.claude/skills/cc-investigate/CHANGELOG.md +17 -0
- package/.claude/skills/cc-investigate/PLAYBOOK.md +6 -5
- package/.claude/skills/cc-investigate/SKILL.md +56 -44
- package/.claude/skills/cc-investigate/assets/TASKS_TEMPLATE.md +48 -5
- package/.claude/skills/cc-investigate/assets/TASK_MANIFEST_TEMPLATE.json +4 -3
- package/.claude/skills/cc-investigate/assets/{ANALYSIS_TEMPLATE.md → legacy/ANALYSIS_TEMPLATE.md} +1 -0
- package/.claude/skills/cc-investigate/references/investigation-contract.md +2 -2
- package/.claude/skills/cc-investigate/scripts/bootstrap-analysis.sh +1 -1
- package/.claude/skills/cc-plan/CHANGELOG.md +19 -0
- package/.claude/skills/cc-plan/PLAYBOOK.md +55 -53
- package/.claude/skills/cc-plan/SKILL.md +101 -85
- package/.claude/skills/cc-plan/assets/TASKS_TEMPLATE.md +47 -14
- package/.claude/skills/cc-plan/assets/TASK_MANIFEST_TEMPLATE.json +4 -2
- package/.claude/skills/cc-plan/assets/{DESIGN_TEMPLATE.md → legacy/DESIGN_TEMPLATE.md} +1 -0
- package/.claude/skills/cc-plan/assets/{TINY_DESIGN_TEMPLATE.md → legacy/TINY_DESIGN_TEMPLATE.md} +1 -1
- package/.claude/skills/cc-plan/references/planning-contract.md +11 -10
- package/.claude/skills/cc-review/CHANGELOG.md +6 -0
- package/.claude/skills/cc-review/PLAYBOOK.md +9 -11
- package/.claude/skills/cc-review/SKILL.md +37 -61
- package/.claude/skills/cc-review/references/e2e-and-plugin-verification.md +1 -1
- package/.claude/skills/cc-review/references/implementation-review-branch.md +5 -5
- package/.claude/skills/cc-review/references/plan-review-branch.md +1 -1
- package/.claude/skills/cc-review/references/review-methods.md +4 -4
- package/.claude/skills/cc-review/scripts/collect-review-context.sh +14 -7
- package/CHANGELOG.md +16 -0
- package/CONTRIBUTING.md +40 -4
- package/CONTRIBUTING.zh-CN.md +40 -4
- package/README.md +20 -8
- package/README.zh-CN.md +20 -8
- package/bin/cc-devflow-cli.js +293 -36
- package/docs/examples/START-HERE.md +5 -4
- package/docs/examples/example-bindings.json +8 -8
- package/docs/examples/full-design-blocked/README.md +2 -2
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/design.md +2 -1
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/task-manifest.json +3 -2
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/tasks.md +11 -8
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/review/report-card.json +4 -4
- package/docs/examples/local-handoff/README.md +2 -2
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/design.md +2 -1
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/task-manifest.json +3 -2
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/tasks.md +9 -6
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/review/report-card.json +1 -1
- package/docs/examples/pdca-loop/README.md +2 -2
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/handoff/pr-brief.md +2 -2
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/design.md +2 -1
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/task-manifest.json +2 -1
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/tasks.md +9 -6
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/review/report-card.json +1 -1
- package/docs/examples/scripts/check-example-bindings.sh +2 -0
- package/docs/get-shit-done-strategy-audit.md +22 -22
- package/docs/guides/artifact-contract.md +1 -1
- package/docs/guides/getting-started.md +10 -8
- package/docs/guides/getting-started.zh-CN.md +10 -8
- package/docs/guides/minimize-artifacts.md +123 -0
- package/lib/compiler/__tests__/skills-registry.test.js +2 -2
- package/lib/skill-runtime/CLAUDE.md +1 -1
- package/lib/skill-runtime/__tests__/autopilot.test.js +42 -6
- package/lib/skill-runtime/__tests__/benchmark-artifacts.test.js +165 -0
- package/lib/skill-runtime/__tests__/cli-bootstrap.integration.test.js +2 -2
- package/lib/skill-runtime/__tests__/dispatch.test.js +8 -38
- package/lib/skill-runtime/__tests__/intent.test.js +4 -20
- package/lib/skill-runtime/__tests__/lifecycle.test.js +1 -1
- package/lib/skill-runtime/__tests__/paths.test.js +7 -1
- package/lib/skill-runtime/__tests__/planner.tdd.test.js +61 -0
- package/lib/skill-runtime/__tests__/prepare-pr.test.js +3 -16
- package/lib/skill-runtime/__tests__/query.test.js +388 -7
- package/lib/skill-runtime/__tests__/review-check-integration.test.js +148 -0
- package/lib/skill-runtime/__tests__/review-records.test.js +619 -0
- package/lib/skill-runtime/__tests__/runtime.integration.test.js +64 -23
- package/lib/skill-runtime/__tests__/schemas.test.js +43 -0
- package/lib/skill-runtime/__tests__/task-contract-migrate.test.js +137 -0
- package/lib/skill-runtime/__tests__/task-contract.test.js +783 -0
- package/lib/skill-runtime/__tests__/verify-artifacts.test.js +203 -0
- package/lib/skill-runtime/__tests__/worker-run.test.js +4 -11
- package/lib/skill-runtime/__tests__/workflow-context-legacy-fallback.test.js +31 -0
- package/lib/skill-runtime/__tests__/workflow-context.test.js +98 -0
- package/lib/skill-runtime/artifacts.js +0 -5
- package/lib/skill-runtime/context-index.js +545 -0
- package/lib/skill-runtime/intent.js +9 -33
- package/lib/skill-runtime/lifecycle.js +1 -1
- package/lib/skill-runtime/operations/CLAUDE.md +2 -2
- package/lib/skill-runtime/operations/dispatch.js +4 -42
- package/lib/skill-runtime/operations/init.js +2 -6
- package/lib/skill-runtime/operations/janitor.js +2 -18
- package/lib/skill-runtime/operations/resume.js +21 -38
- package/lib/skill-runtime/operations/review-records.js +265 -0
- package/lib/skill-runtime/operations/snapshot.js +1 -1
- package/lib/skill-runtime/operations/task-contract.js +524 -0
- package/lib/skill-runtime/operations/worker-run.js +2 -30
- package/lib/skill-runtime/paths.js +4 -4
- package/lib/skill-runtime/planner.js +24 -11
- package/lib/skill-runtime/query-registry.js +2 -2
- package/lib/skill-runtime/query.js +15 -2
- package/lib/skill-runtime/review-records.js +123 -0
- package/lib/skill-runtime/review.js +246 -11
- package/lib/skill-runtime/schemas.js +174 -12
- package/lib/skill-runtime/store.js +0 -10
- package/lib/skill-runtime/task-contract.js +187 -0
- package/lib/skill-runtime/workflow-context.js +748 -0
- package/package.json +7 -4
|
@@ -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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
});
|