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.
- package/.claude/skills/cc-act/CHANGELOG.md +11 -0
- package/.claude/skills/cc-act/SKILL.md +19 -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 +23 -0
- package/.claude/skills/cc-check/PLAYBOOK.md +1 -0
- package/.claude/skills/cc-check/SKILL.md +15 -9
- 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 +10 -0
- package/.claude/skills/cc-dev/SKILL.md +34 -2
- package/.claude/skills/cc-do/CHANGELOG.md +18 -0
- package/.claude/skills/cc-do/PLAYBOOK.md +7 -7
- package/.claude/skills/cc-do/SKILL.md +47 -40
- 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 +24 -0
- package/.claude/skills/cc-investigate/PLAYBOOK.md +10 -9
- package/.claude/skills/cc-investigate/SKILL.md +163 -417
- package/.claude/skills/cc-investigate/assets/TASKS_TEMPLATE.md +56 -10
- package/.claude/skills/cc-investigate/assets/TASK_MANIFEST_TEMPLATE.json +6 -6
- package/.claude/skills/cc-investigate/assets/{ANALYSIS_TEMPLATE.md → legacy/ANALYSIS_TEMPLATE.md} +1 -0
- package/.claude/skills/cc-investigate/references/investigation-contract.md +5 -4
- package/.claude/skills/cc-investigate/scripts/bootstrap-analysis.sh +1 -1
- package/.claude/skills/cc-plan/CHANGELOG.md +32 -0
- package/.claude/skills/cc-plan/PLAYBOOK.md +55 -53
- package/.claude/skills/cc-plan/SKILL.md +209 -536
- package/.claude/skills/cc-plan/assets/TASKS_TEMPLATE.md +50 -14
- package/.claude/skills/cc-plan/assets/TASK_MANIFEST_TEMPLATE.json +5 -4
- 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 +12 -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 +30 -0
- package/CONTRIBUTING.md +40 -4
- package/CONTRIBUTING.zh-CN.md +40 -4
- package/README.md +22 -8
- package/README.zh-CN.md +22 -8
- package/bin/cc-devflow-cli.js +293 -36
- package/docs/examples/START-HERE.md +6 -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 +5 -1
- package/docs/guides/getting-started.md +11 -8
- package/docs/guides/getting-started.zh-CN.md +11 -8
- package/docs/guides/minimize-artifacts.md +137 -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__/benchmark-skills.test.js +109 -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 +874 -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 +593 -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 +188 -0
- package/lib/skill-runtime/workflow-context.js +748 -0
- package/package.json +6 -2
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [INPUT]: 依赖 manifest/
|
|
3
|
-
* [OUTPUT]: 更新 task-manifest
|
|
2
|
+
* [INPUT]: 依赖 manifest/events 与 shell 执行能力,接收并行度与重试参数。
|
|
3
|
+
* [OUTPUT]: 更新 task-manifest 状态,失败或 debug 时写入每任务 events.jsonl。
|
|
4
4
|
* [POS]: skill runtime Stage-4 执行入口,被内部执行与恢复链路复用。
|
|
5
5
|
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
9
|
nowIso,
|
|
10
|
-
ensureDir,
|
|
11
10
|
appendJsonl,
|
|
12
11
|
writeJson,
|
|
13
12
|
readJson,
|
|
14
13
|
runCommand,
|
|
15
14
|
getTaskManifestPath,
|
|
16
15
|
getRuntimeStatePath,
|
|
17
|
-
|
|
18
|
-
getEventsPath,
|
|
19
|
-
getCheckpointPath
|
|
16
|
+
getEventsPath
|
|
20
17
|
} = require('../store');
|
|
21
|
-
const { parseManifest
|
|
18
|
+
const { parseManifest } = require('../schemas');
|
|
22
19
|
const { applyManifestExecutionState } = require('../planner');
|
|
23
20
|
const { syncIntentMemory } = require('../intent');
|
|
24
21
|
const {
|
|
@@ -84,12 +81,6 @@ function selectBatch(readyTasks, parallel) {
|
|
|
84
81
|
return selected;
|
|
85
82
|
}
|
|
86
83
|
|
|
87
|
-
async function writeCheckpoint(repoRoot, changeId, taskId, payload, options = {}) {
|
|
88
|
-
const checkpointPath = getCheckpointPath(repoRoot, changeId, taskId);
|
|
89
|
-
const checkpoint = parseCheckpoint(payload);
|
|
90
|
-
await writeJson(checkpointPath, checkpoint);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
84
|
async function writeEvent(repoRoot, changeId, taskId, event, debug = false) {
|
|
94
85
|
if (!debug && !String(event.type || '').includes('failed') && !String(event.type || '').includes('rejected')) {
|
|
95
86
|
return;
|
|
@@ -105,9 +96,6 @@ async function writeManifest(repoRoot, changeId, manifest) {
|
|
|
105
96
|
}
|
|
106
97
|
|
|
107
98
|
async function executeTask({ repoRoot, changeId, task, retryOverride, debug = false }) {
|
|
108
|
-
const taskRuntimeDir = getRuntimeTaskDir(repoRoot, changeId, task.id);
|
|
109
|
-
await ensureDir(taskRuntimeDir);
|
|
110
|
-
|
|
111
99
|
const assignment = await findAssignment(repoRoot, changeId, task.id);
|
|
112
100
|
const worker = await ensureWorkerWorkspace(repoRoot, changeId, assignment);
|
|
113
101
|
const sessionId = toSessionId(task.id);
|
|
@@ -200,19 +188,6 @@ async function executeTask({ repoRoot, changeId, task, retryOverride, debug = fa
|
|
|
200
188
|
task.status = 'passed';
|
|
201
189
|
task.lastError = undefined;
|
|
202
190
|
|
|
203
|
-
await writeCheckpoint(repoRoot, changeId, task.id, {
|
|
204
|
-
changeId,
|
|
205
|
-
taskId: task.id,
|
|
206
|
-
sessionId,
|
|
207
|
-
planVersion: expectedPlanVersion,
|
|
208
|
-
status: task.status,
|
|
209
|
-
summary: `Task passed after ${task.attempts} attempt(s)`,
|
|
210
|
-
error: '',
|
|
211
|
-
outputExcerpt: '',
|
|
212
|
-
timestamp: nowIso(),
|
|
213
|
-
attempt: task.attempts
|
|
214
|
-
});
|
|
215
|
-
|
|
216
191
|
await writeEvent(repoRoot, changeId, task.id, {
|
|
217
192
|
type: 'task_passed',
|
|
218
193
|
changeId,
|
|
@@ -246,19 +221,6 @@ async function executeTask({ repoRoot, changeId, task, retryOverride, debug = fa
|
|
|
246
221
|
task.status = 'failed';
|
|
247
222
|
task.lastError = failureMessage;
|
|
248
223
|
|
|
249
|
-
await writeCheckpoint(repoRoot, changeId, task.id, {
|
|
250
|
-
changeId,
|
|
251
|
-
taskId: task.id,
|
|
252
|
-
sessionId,
|
|
253
|
-
planVersion: expectedPlanVersion,
|
|
254
|
-
status: task.status,
|
|
255
|
-
summary: `Task failed: ${failureMessage.slice(0, 240)}`,
|
|
256
|
-
error: failureMessage,
|
|
257
|
-
outputExcerpt: failureMessage.slice(0, 400),
|
|
258
|
-
timestamp: nowIso(),
|
|
259
|
-
attempt: task.attempts
|
|
260
|
-
});
|
|
261
|
-
|
|
262
224
|
await writeEvent(repoRoot, changeId, task.id, {
|
|
263
225
|
type: failureMessage.includes('Stale result rejected') ? 'task_stale_rejected' : 'task_failed',
|
|
264
226
|
changeId,
|
|
@@ -11,7 +11,6 @@ const {
|
|
|
11
11
|
writeJson,
|
|
12
12
|
readJson,
|
|
13
13
|
getChangeDir,
|
|
14
|
-
getRuntimeChangeDir,
|
|
15
14
|
getRuntimeStatePath
|
|
16
15
|
} = require('../store');
|
|
17
16
|
const { getChangePaths, getChangeSlug } = require('../paths');
|
|
@@ -19,14 +18,11 @@ const { getChangePaths, getChangeSlug } = require('../paths');
|
|
|
19
18
|
async function runInit({ repoRoot, changeId, goal }) {
|
|
20
19
|
const changePaths = getChangePaths(repoRoot, changeId, { goal });
|
|
21
20
|
const changeDir = getChangeDir(repoRoot, changeId, { goal });
|
|
22
|
-
const runtimeDir = getRuntimeChangeDir(repoRoot, changeId, { goal });
|
|
23
21
|
const statePath = getRuntimeStatePath(repoRoot, changeId, { goal });
|
|
24
22
|
|
|
25
23
|
await ensureDir(changeDir);
|
|
26
24
|
await ensureDir(changePaths.metaDir);
|
|
27
25
|
await ensureDir(changePaths.planningDir);
|
|
28
|
-
await ensureDir(runtimeDir);
|
|
29
|
-
await ensureDir(changePaths.tasksDir);
|
|
30
26
|
await ensureDir(changePaths.reviewDir);
|
|
31
27
|
await ensureDir(changePaths.handoffDir);
|
|
32
28
|
|
|
@@ -37,7 +33,7 @@ async function runInit({ repoRoot, changeId, goal }) {
|
|
|
37
33
|
changeKey: changePaths.changeKey,
|
|
38
34
|
slug: getChangeSlug(changeId, changePaths.changeKey),
|
|
39
35
|
createdAt,
|
|
40
|
-
goal: goal || previous.goal || `Deliver ${changeId} safely with
|
|
36
|
+
goal: goal || previous.goal || `Deliver ${changeId} safely with task-state truth.`,
|
|
41
37
|
status: 'initialized',
|
|
42
38
|
initializedAt: previous.initializedAt || nowIso(),
|
|
43
39
|
approval: {
|
|
@@ -52,7 +48,7 @@ async function runInit({ repoRoot, changeId, goal }) {
|
|
|
52
48
|
return {
|
|
53
49
|
changeId,
|
|
54
50
|
changeDir,
|
|
55
|
-
runtimeDir,
|
|
51
|
+
runtimeDir: changePaths.executionDir,
|
|
56
52
|
changeKey: changePaths.changeKey,
|
|
57
53
|
statePath,
|
|
58
54
|
status: nextState.status
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [INPUT]: 依赖 devflow/changes 下 execution/tasks 目录与
|
|
3
|
-
* [OUTPUT]:
|
|
2
|
+
* [INPUT]: 依赖 devflow/changes 下 execution/tasks 目录与 mtime,接收保留小时阈值。
|
|
3
|
+
* [OUTPUT]: 删除过期任务运行态目录,并输出清理统计。
|
|
4
4
|
* [POS]: skill runtime 熵清理入口,被 CLI `harness:janitor` 调用。
|
|
5
5
|
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
6
|
*/
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const {
|
|
11
|
-
readJson,
|
|
12
11
|
listDirectories,
|
|
13
12
|
getRuntimeRoot
|
|
14
13
|
} = require('../store');
|
|
@@ -17,17 +16,6 @@ async function removeDirectoryRecursive(dirPath) {
|
|
|
17
16
|
await fs.promises.rm(dirPath, { recursive: true, force: true });
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
async function isTaskActive(taskDir) {
|
|
21
|
-
const checkpointPath = path.join(taskDir, 'checkpoint.json');
|
|
22
|
-
const checkpoint = await readJson(checkpointPath, null);
|
|
23
|
-
|
|
24
|
-
if (!checkpoint) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return checkpoint.status === 'running';
|
|
29
|
-
}
|
|
30
|
-
|
|
31
19
|
async function runJanitor({ repoRoot, hours = 72 }) {
|
|
32
20
|
const runtimeRoot = getRuntimeRoot(repoRoot);
|
|
33
21
|
const cutoffMs = Date.now() - Number(hours) * 60 * 60 * 1000;
|
|
@@ -46,10 +34,6 @@ async function runJanitor({ repoRoot, hours = 72 }) {
|
|
|
46
34
|
continue;
|
|
47
35
|
}
|
|
48
36
|
|
|
49
|
-
if (await isTaskActive(taskDir)) {
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
37
|
await removeDirectoryRecursive(taskDir);
|
|
54
38
|
removedTaskDirs += 1;
|
|
55
39
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [INPUT]: 依赖 manifest
|
|
3
|
-
* [OUTPUT]:
|
|
2
|
+
* [INPUT]: 依赖 manifest 当前状态与 dispatch 执行器,接收 changeId/并行度/重试参数。
|
|
3
|
+
* [OUTPUT]: 将失败执行恢复到最近已通过的 manifest 任务边界,把未稳定任务重新排队后继续执行。
|
|
4
4
|
* [POS]: skill runtime 恢复执行入口,供内部恢复链路复用。
|
|
5
5
|
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
6
|
*/
|
|
@@ -9,8 +9,7 @@ const {
|
|
|
9
9
|
readJson,
|
|
10
10
|
writeJson,
|
|
11
11
|
getTaskManifestPath,
|
|
12
|
-
getRuntimeStatePath
|
|
13
|
-
getCheckpointPath
|
|
12
|
+
getRuntimeStatePath
|
|
14
13
|
} = require('../store');
|
|
15
14
|
const { parseManifest } = require('../schemas');
|
|
16
15
|
const { applyManifestExecutionState } = require('../planner');
|
|
@@ -18,33 +17,17 @@ const { runDispatch } = require('./dispatch');
|
|
|
18
17
|
const { syncIntentMemory } = require('../intent');
|
|
19
18
|
const { getApprovalState, isExecutionApproved } = require('../lifecycle');
|
|
20
19
|
|
|
21
|
-
async function
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
timestamp: checkpoint.timestamp,
|
|
33
|
-
summary: checkpoint.summary,
|
|
34
|
-
planVersion: checkpoint.planVersion || null
|
|
35
|
-
};
|
|
36
|
-
})
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
const latestPassed = checkpoints
|
|
40
|
-
.filter((checkpoint) =>
|
|
41
|
-
checkpoint &&
|
|
42
|
-
checkpoint.status === 'passed' &&
|
|
43
|
-
checkpoint.planVersion === (manifest.metadata?.planVersion || 1)
|
|
44
|
-
)
|
|
45
|
-
.sort((left, right) => right.timestamp.localeCompare(left.timestamp))[0];
|
|
46
|
-
|
|
47
|
-
return latestPassed || null;
|
|
20
|
+
async function resolveStableManifestPoint(manifest) {
|
|
21
|
+
const passedTasks = manifest.tasks
|
|
22
|
+
.filter((task) => task.status === 'passed')
|
|
23
|
+
.map((task) => ({
|
|
24
|
+
taskId: task.id,
|
|
25
|
+
status: task.status,
|
|
26
|
+
title: task.title || '',
|
|
27
|
+
planVersion: manifest.metadata?.planVersion || 1
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
return passedTasks[passedTasks.length - 1] || null;
|
|
48
31
|
}
|
|
49
32
|
|
|
50
33
|
function restoreTaskToPending(task, restoreLabel) {
|
|
@@ -54,8 +37,8 @@ function restoreTaskToPending(task, restoreLabel) {
|
|
|
54
37
|
task.lastError = `Restored from ${restoreLabel}.${previousError}`.trim();
|
|
55
38
|
}
|
|
56
39
|
|
|
57
|
-
function
|
|
58
|
-
const restoreLabel = restoreTarget ? `stable
|
|
40
|
+
function requeueFromStableManifestPoint(manifest, restoreTarget) {
|
|
41
|
+
const restoreLabel = restoreTarget ? `stable manifest task ${restoreTarget.taskId}` : 'execution start';
|
|
59
42
|
const restoredTaskIds = [];
|
|
60
43
|
|
|
61
44
|
for (const task of manifest.tasks) {
|
|
@@ -106,16 +89,16 @@ async function runResume({
|
|
|
106
89
|
}
|
|
107
90
|
|
|
108
91
|
if (fromCheckpoint && fromCheckpoint !== 'stable') {
|
|
109
|
-
throw new Error(`Unsupported
|
|
92
|
+
throw new Error(`Unsupported resume point: ${fromCheckpoint}`);
|
|
110
93
|
}
|
|
111
94
|
|
|
112
|
-
const restoreTarget = await
|
|
113
|
-
const restoreSummary =
|
|
95
|
+
const restoreTarget = await resolveStableManifestPoint(manifest);
|
|
96
|
+
const restoreSummary = requeueFromStableManifestPoint(manifest, restoreTarget);
|
|
114
97
|
|
|
115
98
|
applyManifestExecutionState(manifest);
|
|
116
99
|
await writeJson(manifestPath, manifest);
|
|
117
100
|
await syncIntentMemory(repoRoot, changeId, {
|
|
118
|
-
event: '
|
|
101
|
+
event: 'resume_restored_manifest_state',
|
|
119
102
|
reason: restoreSummary.restoredTaskIds.length > 0
|
|
120
103
|
? `Restored ${restoreSummary.restoredTaskIds.join(', ')} from ${restoreSummary.restoreLabel}`
|
|
121
104
|
: `Resume confirmed ${restoreSummary.restoreLabel}; no unresolved tasks required requeue`
|
|
@@ -132,7 +115,7 @@ async function runResume({
|
|
|
132
115
|
|
|
133
116
|
return {
|
|
134
117
|
...dispatchResult,
|
|
135
|
-
|
|
118
|
+
restoredState: restoreTarget,
|
|
136
119
|
restoredTasks: restoreSummary.restoredTaskIds,
|
|
137
120
|
reason: dispatchResult.reason || `Resumed from ${restoreSummary.restoreLabel}`
|
|
138
121
|
};
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: 依赖 review-records helper、store.appendJsonl、ReviewLedgerEventSchema。
|
|
3
|
+
* [OUTPUT]: 实现 review durable records CLI operation,当前只写 review-started。
|
|
4
|
+
* [POS]: CLI 与 durable review ledger 的边界层;负责校验后追加 JSONL。
|
|
5
|
+
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const { getChangePaths } = require('../paths');
|
|
11
|
+
const { appendJsonl, readText, writeText } = require('../store');
|
|
12
|
+
const { parseReviewLedgerEvent } = require('../schemas');
|
|
13
|
+
const {
|
|
14
|
+
getReviewLedgerPath,
|
|
15
|
+
nextReviewId,
|
|
16
|
+
parseSkippedNode
|
|
17
|
+
} = require('../review-records');
|
|
18
|
+
|
|
19
|
+
function readLedgerEvents(text) {
|
|
20
|
+
return String(text || '')
|
|
21
|
+
.split('\n')
|
|
22
|
+
.map((line) => line.trim())
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.map((line) => JSON.parse(line));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeArray(values) {
|
|
28
|
+
return (values || [])
|
|
29
|
+
.flatMap((value) => String(value || '').split(','))
|
|
30
|
+
.map((value) => value.trim())
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function loadLedger(repoRoot, changeId, changeKey) {
|
|
35
|
+
const change = getChangePaths(repoRoot, changeId, { changeKey });
|
|
36
|
+
const ledgerPath = getReviewLedgerPath(repoRoot, changeId, { changeKey });
|
|
37
|
+
const events = readLedgerEvents(await readText(ledgerPath, ''));
|
|
38
|
+
return { change, ledgerPath, events };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function hasStarted(events, reviewId) {
|
|
42
|
+
return events.some((event) => event.reviewId === reviewId && event.event === 'review-started');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hasClosed(events, reviewId) {
|
|
46
|
+
return events.some((event) => event.reviewId === reviewId && event.event === 'review-closed');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function appendReviewEvent(options, eventFields) {
|
|
50
|
+
const {
|
|
51
|
+
repoRoot,
|
|
52
|
+
changeId,
|
|
53
|
+
changeKey,
|
|
54
|
+
reviewId,
|
|
55
|
+
now = new Date()
|
|
56
|
+
} = options;
|
|
57
|
+
const { change, ledgerPath, events } = await loadLedger(repoRoot, changeId, changeKey);
|
|
58
|
+
|
|
59
|
+
if (!reviewId || !hasStarted(events, reviewId)) {
|
|
60
|
+
return {
|
|
61
|
+
code: 4,
|
|
62
|
+
stderr: `Review ${reviewId || '<missing>'} has not been started`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (eventFields.event === 'review-closed' && hasClosed(events, reviewId)) {
|
|
67
|
+
return {
|
|
68
|
+
code: 4,
|
|
69
|
+
stderr: `Review ${reviewId} is already closed`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const event = parseReviewLedgerEvent({
|
|
74
|
+
schema: 'review-ledger.v2',
|
|
75
|
+
change: change.changeKey,
|
|
76
|
+
reviewId,
|
|
77
|
+
createdAt: now.toISOString(),
|
|
78
|
+
createdBy: 'cc-devflow-cli',
|
|
79
|
+
...eventFields
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await appendJsonl(ledgerPath, event);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
code: 0,
|
|
86
|
+
reviewId,
|
|
87
|
+
ledgerPath: path.relative(repoRoot, ledgerPath),
|
|
88
|
+
event: event.event
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function runReviewStart(options) {
|
|
93
|
+
const {
|
|
94
|
+
repoRoot,
|
|
95
|
+
changeId,
|
|
96
|
+
changeKey,
|
|
97
|
+
mode = 'implementation',
|
|
98
|
+
scope = 'current-diff',
|
|
99
|
+
baseSha = 'unknown',
|
|
100
|
+
headSha = 'unknown',
|
|
101
|
+
selectedNodes = [],
|
|
102
|
+
skippedNodes = [],
|
|
103
|
+
riskLanes = [],
|
|
104
|
+
now = new Date()
|
|
105
|
+
} = options;
|
|
106
|
+
const change = getChangePaths(repoRoot, changeId, { changeKey });
|
|
107
|
+
const ledgerPath = getReviewLedgerPath(repoRoot, changeId, { changeKey });
|
|
108
|
+
const existingEvents = readLedgerEvents(await readText(ledgerPath, ''));
|
|
109
|
+
const reviewId = nextReviewId(existingEvents, now);
|
|
110
|
+
const event = parseReviewLedgerEvent({
|
|
111
|
+
schema: 'review-ledger.v2',
|
|
112
|
+
change: change.changeKey,
|
|
113
|
+
reviewId,
|
|
114
|
+
createdAt: now.toISOString(),
|
|
115
|
+
createdBy: 'cc-devflow-cli',
|
|
116
|
+
event: 'review-started',
|
|
117
|
+
mode,
|
|
118
|
+
scope,
|
|
119
|
+
baseSha,
|
|
120
|
+
headSha,
|
|
121
|
+
selectedNodes: normalizeArray(selectedNodes),
|
|
122
|
+
skippedNodes: skippedNodes.map(parseSkippedNode),
|
|
123
|
+
riskLanes: normalizeArray(riskLanes)
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await appendJsonl(ledgerPath, event);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
code: 0,
|
|
130
|
+
reviewId,
|
|
131
|
+
ledgerPath: path.relative(repoRoot, ledgerPath),
|
|
132
|
+
event: event.event
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function runReviewRecordNode(options) {
|
|
137
|
+
return appendReviewEvent(options, {
|
|
138
|
+
event: 'review-node-checked',
|
|
139
|
+
nodeId: options.nodeId,
|
|
140
|
+
mode: options.mode || 'implementation',
|
|
141
|
+
target: options.target,
|
|
142
|
+
status: options.status || 'checked',
|
|
143
|
+
coverage: normalizeArray(options.coverage),
|
|
144
|
+
evidenceRefs: normalizeArray(options.evidenceRefs),
|
|
145
|
+
findings: normalizeArray(options.findings),
|
|
146
|
+
next: options.next || 'cc-check'
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function runReviewAddFinding(options) {
|
|
151
|
+
return appendReviewEvent(options, {
|
|
152
|
+
event: 'review-finding-added',
|
|
153
|
+
findingId: options.findingId,
|
|
154
|
+
severity: options.severity || 'important',
|
|
155
|
+
confidence: Number(options.confidence),
|
|
156
|
+
displayTier: options.displayTier || 'blocking',
|
|
157
|
+
fingerprint: options.fingerprint,
|
|
158
|
+
scope: options.scope,
|
|
159
|
+
path: options.path,
|
|
160
|
+
evidence: options.evidence,
|
|
161
|
+
recommendation: options.recommendation,
|
|
162
|
+
route: options.route || 'cc-do'
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function runReviewClose(options) {
|
|
167
|
+
return appendReviewEvent(options, {
|
|
168
|
+
event: 'review-closed',
|
|
169
|
+
status: options.status || 'clean',
|
|
170
|
+
blockingCount: Number(options.blockingCount || 0),
|
|
171
|
+
warningCount: Number(options.warningCount || 0),
|
|
172
|
+
next: options.next || 'cc-check'
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function renderReviewMarkdown({ changeKey, reviewId, events }) {
|
|
177
|
+
const closed = events.find((event) => event.event === 'review-closed');
|
|
178
|
+
const findings = events.filter((event) => event.event === 'review-finding-added');
|
|
179
|
+
const lines = [
|
|
180
|
+
'---',
|
|
181
|
+
'Output language: en',
|
|
182
|
+
`reviewId: ${reviewId}`,
|
|
183
|
+
`change: ${changeKey}`,
|
|
184
|
+
'---',
|
|
185
|
+
'',
|
|
186
|
+
`# Review ${reviewId}`,
|
|
187
|
+
'',
|
|
188
|
+
'## Summary',
|
|
189
|
+
'',
|
|
190
|
+
`- Status: ${closed?.status || 'open'}`,
|
|
191
|
+
`- Blocking findings: ${closed?.blockingCount ?? 0}`,
|
|
192
|
+
`- Warnings: ${closed?.warningCount ?? 0}`,
|
|
193
|
+
`- Next: ${closed?.next || 'cc-check'}`,
|
|
194
|
+
'',
|
|
195
|
+
'## Findings',
|
|
196
|
+
''
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
if (findings.length === 0) {
|
|
200
|
+
lines.push('- None');
|
|
201
|
+
} else {
|
|
202
|
+
for (const finding of findings) {
|
|
203
|
+
lines.push(
|
|
204
|
+
`- ${finding.findingId} [${finding.severity}/${finding.displayTier}, confidence ${finding.confidence}] ${finding.path}`,
|
|
205
|
+
` - Evidence: ${finding.evidence}`,
|
|
206
|
+
` - Recommendation: ${finding.recommendation}`,
|
|
207
|
+
` - Route: ${finding.route}`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
lines.push('', '## Ledger Events', '');
|
|
213
|
+
for (const event of events) {
|
|
214
|
+
lines.push(`- ${event.event} at ${event.createdAt}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return `${lines.join('\n')}\n`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function runReviewRender(options) {
|
|
221
|
+
const {
|
|
222
|
+
repoRoot,
|
|
223
|
+
changeId,
|
|
224
|
+
changeKey,
|
|
225
|
+
reviewId,
|
|
226
|
+
output
|
|
227
|
+
} = options;
|
|
228
|
+
const { change, events } = await loadLedger(repoRoot, changeId, changeKey);
|
|
229
|
+
const selectedReviewId = reviewId
|
|
230
|
+
|| [...events].reverse().find((event) => event.event === 'review-started')?.reviewId;
|
|
231
|
+
|
|
232
|
+
if (!selectedReviewId) {
|
|
233
|
+
return { code: 4, stderr: 'No reviewId available to render' };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!output) {
|
|
237
|
+
return { code: 4, stderr: 'review render --output is required' };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const reviewEvents = events.filter((event) => event.reviewId === selectedReviewId);
|
|
241
|
+
if (reviewEvents.length === 0) {
|
|
242
|
+
return { code: 4, stderr: `Review ${selectedReviewId} has no ledger events` };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const outputPath = path.isAbsolute(output) ? output : path.join(repoRoot, output);
|
|
246
|
+
await writeText(outputPath, renderReviewMarkdown({
|
|
247
|
+
changeKey: change.changeKey,
|
|
248
|
+
reviewId: selectedReviewId,
|
|
249
|
+
events: reviewEvents
|
|
250
|
+
}));
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
code: 0,
|
|
254
|
+
reviewId: selectedReviewId,
|
|
255
|
+
outputPath: path.relative(repoRoot, outputPath)
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = {
|
|
260
|
+
runReviewAddFinding,
|
|
261
|
+
runReviewClose,
|
|
262
|
+
runReviewRecordNode,
|
|
263
|
+
runReviewRender,
|
|
264
|
+
runReviewStart
|
|
265
|
+
};
|
|
@@ -28,7 +28,7 @@ async function collectGitFacts(repoRoot) {
|
|
|
28
28
|
|
|
29
29
|
async function runPlanningSnapshot({ repoRoot, changeId, goal }) {
|
|
30
30
|
const state = (await readJson(getRuntimeStatePath(repoRoot, changeId), {})) || {};
|
|
31
|
-
const effectiveGoal = goal || state.goal || `Deliver ${changeId} safely with
|
|
31
|
+
const effectiveGoal = goal || state.goal || `Deliver ${changeId} safely with task-state truth.`;
|
|
32
32
|
const gitFacts = await collectGitFacts(repoRoot);
|
|
33
33
|
const scripts = await getPackageScripts(repoRoot);
|
|
34
34
|
|