cc-devflow 4.5.11 → 4.5.12

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 (185) hide show
  1. package/.claude/skills/cc-act/CHANGELOG.md +18 -0
  2. package/.claude/skills/cc-act/PLAYBOOK.md +17 -269
  3. package/.claude/skills/cc-act/SKILL.md +38 -425
  4. package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_INDEX_TEMPLATE.md +2 -13
  5. package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_TEMPLATE.md +1 -9
  6. package/.claude/skills/cc-act/assets/PR_BRIEF_TEMPLATE.md +21 -177
  7. package/.claude/skills/cc-act/references/closure-contract.md +12 -63
  8. package/.claude/skills/cc-act/references/git-commit-guidelines.md +5 -5
  9. package/.claude/skills/cc-act/scripts/cc-act-common.sh +5 -322
  10. package/.claude/skills/cc-act/scripts/detect-ship-target.sh +11 -2
  11. package/.claude/skills/cc-act/scripts/inspect-git-index.sh +58 -0
  12. package/.claude/skills/cc-act/scripts/render-pr-brief.sh +40 -440
  13. package/.claude/skills/cc-act/scripts/verify-act-gate.sh +10 -50
  14. package/.claude/skills/cc-check/CHANGELOG.md +18 -0
  15. package/.claude/skills/cc-check/PLAYBOOK.md +19 -273
  16. package/.claude/skills/cc-check/SKILL.md +33 -456
  17. package/.claude/skills/cc-check/references/review-contract.md +12 -147
  18. package/.claude/skills/cc-dev/CHANGELOG.md +15 -0
  19. package/.claude/skills/cc-dev/PLAYBOOK.md +1 -1
  20. package/.claude/skills/cc-dev/SKILL.md +52 -137
  21. package/.claude/skills/cc-dev/scripts/resolve-cc-devflow.sh +181 -0
  22. package/.claude/skills/cc-do/CHANGELOG.md +11 -0
  23. package/.claude/skills/cc-do/PLAYBOOK.md +19 -113
  24. package/.claude/skills/cc-do/SKILL.md +39 -245
  25. package/.claude/skills/cc-do/references/execution-recovery.md +15 -109
  26. package/.claude/skills/cc-do/scripts/cc-do-common.sh +5 -57
  27. package/.claude/skills/cc-do/scripts/check-task-status.sh +35 -65
  28. package/.claude/skills/cc-do/scripts/mark-task-complete.sh +9 -46
  29. package/.claude/skills/cc-do/scripts/select-ready-tasks.sh +29 -97
  30. package/.claude/skills/cc-investigate/CHANGELOG.md +16 -0
  31. package/.claude/skills/cc-investigate/PLAYBOOK.md +20 -180
  32. package/.claude/skills/cc-investigate/SKILL.md +64 -246
  33. package/.claude/skills/cc-investigate/assets/TASKS_TEMPLATE.md +48 -98
  34. package/.claude/skills/cc-investigate/references/investigation-contract.md +14 -218
  35. package/.claude/skills/cc-next/CHANGELOG.md +6 -0
  36. package/.claude/skills/cc-next/PLAYBOOK.md +12 -8
  37. package/.claude/skills/cc-next/SKILL.md +34 -140
  38. package/.claude/skills/cc-plan/CHANGELOG.md +16 -0
  39. package/.claude/skills/cc-plan/PLAYBOOK.md +22 -161
  40. package/.claude/skills/cc-plan/SKILL.md +45 -295
  41. package/.claude/skills/cc-plan/assets/TASKS_TEMPLATE.md +30 -228
  42. package/.claude/skills/cc-plan/references/planning-contract.md +24 -161
  43. package/.claude/skills/cc-plan/scripts/next-change-key.sh +8 -44
  44. package/.claude/skills/cc-plan/scripts/parse-task-dependencies.js +2 -2
  45. package/.claude/skills/cc-plan/scripts/validate-scope.sh +1 -1
  46. package/.claude/skills/cc-pr-land/SKILL.md +14 -114
  47. package/.claude/skills/cc-pr-review/CHANGELOG.md +4 -0
  48. package/.claude/skills/cc-pr-review/SKILL.md +20 -103
  49. package/.claude/skills/cc-review/CHANGELOG.md +17 -0
  50. package/.claude/skills/cc-review/PLAYBOOK.md +13 -86
  51. package/.claude/skills/cc-review/SKILL.md +53 -241
  52. package/.claude/skills/cc-review/references/e2e-and-plugin-verification.md +2 -2
  53. package/.claude/skills/cc-review/references/implementation-review-branch.md +7 -147
  54. package/.claude/skills/cc-review/references/plan-review-branch.md +5 -147
  55. package/.claude/skills/cc-review/references/review-methods.md +10 -218
  56. package/.claude/skills/cc-review/scripts/collect-review-context.sh +4 -63
  57. package/.claude/skills/cc-roadmap/PLAYBOOK.md +1 -1
  58. package/.claude/skills/cc-roadmap/SKILL.md +3 -3
  59. package/.claude/skills/cc-simplify/CHANGELOG.md +7 -0
  60. package/.claude/skills/cc-simplify/SKILL.md +26 -21
  61. package/.claude/skills/cc-spec-init/PLAYBOOK.md +12 -48
  62. package/.claude/skills/cc-spec-init/SKILL.md +29 -132
  63. package/.claude/skills/cc-spec-init/references/spec-contract.md +8 -17
  64. package/CHANGELOG.md +13 -0
  65. package/bin/cc-devflow-cli.js +20 -260
  66. package/bin/cc-devflow.js +44 -7
  67. package/docs/commands/README.md +1 -1
  68. package/docs/commands/README.zh-CN.md +1 -1
  69. package/docs/examples/README.md +1 -1
  70. package/docs/examples/START-HERE.md +14 -15
  71. package/docs/examples/example-bindings.json +11 -11
  72. package/docs/examples/full-design-blocked/README.md +4 -6
  73. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/{planning/tasks.md → task.md} +20 -15
  74. package/docs/examples/local-handoff/README.md +8 -11
  75. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/handoff/pr-brief.md +31 -0
  76. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/{planning/tasks.md → task.md} +18 -13
  77. package/docs/examples/pdca-loop/README.md +6 -9
  78. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/handoff/pr-brief.md +9 -11
  79. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/{planning/tasks.md → task.md} +18 -13
  80. package/docs/examples/scripts/check-example-bindings.sh +11 -62
  81. package/docs/guides/artifact-contract.md +10 -40
  82. package/docs/guides/getting-started.md +8 -8
  83. package/docs/guides/getting-started.zh-CN.md +8 -8
  84. package/docs/guides/minimize-artifacts.md +16 -130
  85. package/docs/guides/project-postmortem.md +14 -71
  86. package/lib/compiler/__tests__/skills-registry.test.js +9 -8
  87. package/lib/compiler/resource-copier.js +29 -0
  88. package/lib/skill-runtime/__tests__/archive-change.test.js +2 -2
  89. package/lib/skill-runtime/__tests__/benchmark-skills.test.js +3 -3
  90. package/lib/skill-runtime/__tests__/cli-bootstrap.integration.test.js +14 -4
  91. package/lib/skill-runtime/errors.js +3 -3
  92. package/lib/skill-runtime/index.js +5 -23
  93. package/lib/skill-runtime/paths.js +5 -52
  94. package/lib/skill-runtime/query-registry.js +4 -4
  95. package/lib/skill-runtime/query.js +89 -201
  96. package/lib/skill-runtime/store.js +4 -40
  97. package/lib/skill-runtime/trace.js +2 -2
  98. package/package.json +2 -5
  99. package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_PRINCIPLES_TEMPLATE.md +0 -29
  100. package/.claude/skills/cc-act/assets/RELEASE_NOTE_TEMPLATE.md +0 -54
  101. package/.claude/skills/cc-act/scripts/generate-status-report.sh +0 -92
  102. package/.claude/skills/cc-act/scripts/sync-act-docs.sh +0 -355
  103. package/.claude/skills/cc-check/assets/REPORT_CARD_TEMPLATE.json +0 -234
  104. package/.claude/skills/cc-check/scripts/render-report-card.js +0 -438
  105. package/.claude/skills/cc-check/scripts/verify-gate.sh +0 -85
  106. package/.claude/skills/cc-do/scripts/build-task-context.sh +0 -175
  107. package/.claude/skills/cc-do/scripts/record-review-decision.sh +0 -88
  108. package/.claude/skills/cc-do/scripts/recover-workflow.sh +0 -82
  109. package/.claude/skills/cc-do/scripts/run-problem-analysis.sh +0 -70
  110. package/.claude/skills/cc-do/scripts/verify-task-gates.sh +0 -109
  111. package/.claude/skills/cc-do/scripts/write-task-checkpoint.sh +0 -92
  112. package/.claude/skills/cc-investigate/assets/TASK_MANIFEST_TEMPLATE.json +0 -224
  113. package/.claude/skills/cc-plan/assets/TASK_MANIFEST_TEMPLATE.json +0 -178
  114. package/.claude/skills/cc-spec-init/assets/CHANGE_META_TEMPLATE.json +0 -28
  115. package/.claude/skills/cc-spec-init/scripts/validate-spec-links.sh +0 -45
  116. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/design.md +0 -234
  117. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/task-manifest.json +0 -488
  118. package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/review/report-card.json +0 -189
  119. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/handoff/resume-index.md +0 -39
  120. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/handoff/status.md +0 -29
  121. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/design.md +0 -123
  122. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/task-manifest.json +0 -292
  123. package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/review/report-card.json +0 -136
  124. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/handoff/status.md +0 -29
  125. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/design.md +0 -124
  126. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/task-manifest.json +0 -292
  127. package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/review/report-card.json +0 -136
  128. package/docs/get-shit-done-strategy-audit.md +0 -518
  129. package/docs/skill-runtime-migration.md +0 -46
  130. package/lib/skill-runtime/__tests__/approve.test.js +0 -92
  131. package/lib/skill-runtime/__tests__/autopilot.test.js +0 -253
  132. package/lib/skill-runtime/__tests__/benchmark-artifacts.test.js +0 -165
  133. package/lib/skill-runtime/__tests__/delegation.test.js +0 -97
  134. package/lib/skill-runtime/__tests__/dispatch.test.js +0 -237
  135. package/lib/skill-runtime/__tests__/intent.test.js +0 -203
  136. package/lib/skill-runtime/__tests__/lifecycle.test.js +0 -169
  137. package/lib/skill-runtime/__tests__/planner.tdd.test.js +0 -331
  138. package/lib/skill-runtime/__tests__/prepare-pr.test.js +0 -126
  139. package/lib/skill-runtime/__tests__/query.test.js +0 -860
  140. package/lib/skill-runtime/__tests__/readiness.test.js +0 -53
  141. package/lib/skill-runtime/__tests__/release.test.js +0 -85
  142. package/lib/skill-runtime/__tests__/review-check-integration.test.js +0 -148
  143. package/lib/skill-runtime/__tests__/review-records.test.js +0 -619
  144. package/lib/skill-runtime/__tests__/runtime.integration.test.js +0 -351
  145. package/lib/skill-runtime/__tests__/schemas.test.js +0 -337
  146. package/lib/skill-runtime/__tests__/task-contract-migrate.test.js +0 -137
  147. package/lib/skill-runtime/__tests__/task-contract.test.js +0 -874
  148. package/lib/skill-runtime/__tests__/team-state.test.js +0 -51
  149. package/lib/skill-runtime/__tests__/verify-artifacts.test.js +0 -203
  150. package/lib/skill-runtime/__tests__/worker-run.test.js +0 -275
  151. package/lib/skill-runtime/__tests__/worker.test.js +0 -56
  152. package/lib/skill-runtime/__tests__/workflow-context-legacy-fallback.test.js +0 -31
  153. package/lib/skill-runtime/__tests__/workflow-context.test.js +0 -98
  154. package/lib/skill-runtime/artifacts.js +0 -88
  155. package/lib/skill-runtime/context-index.js +0 -545
  156. package/lib/skill-runtime/delegation.js +0 -533
  157. package/lib/skill-runtime/intent.js +0 -309
  158. package/lib/skill-runtime/lifecycle.js +0 -294
  159. package/lib/skill-runtime/operations/CLAUDE.md +0 -19
  160. package/lib/skill-runtime/operations/approve.js +0 -81
  161. package/lib/skill-runtime/operations/autopilot-core.js +0 -337
  162. package/lib/skill-runtime/operations/autopilot-execution.js +0 -307
  163. package/lib/skill-runtime/operations/autopilot-shared.js +0 -48
  164. package/lib/skill-runtime/operations/autopilot.js +0 -163
  165. package/lib/skill-runtime/operations/dispatch.js +0 -416
  166. package/lib/skill-runtime/operations/init.js +0 -60
  167. package/lib/skill-runtime/operations/janitor.js +0 -61
  168. package/lib/skill-runtime/operations/plan.js +0 -59
  169. package/lib/skill-runtime/operations/prepare-pr.js +0 -25
  170. package/lib/skill-runtime/operations/release.js +0 -99
  171. package/lib/skill-runtime/operations/resume.js +0 -126
  172. package/lib/skill-runtime/operations/review-records.js +0 -265
  173. package/lib/skill-runtime/operations/snapshot.js +0 -45
  174. package/lib/skill-runtime/operations/task-contract.js +0 -593
  175. package/lib/skill-runtime/operations/verify.js +0 -170
  176. package/lib/skill-runtime/operations/worker-run.js +0 -531
  177. package/lib/skill-runtime/operations/worker.js +0 -33
  178. package/lib/skill-runtime/planner.js +0 -539
  179. package/lib/skill-runtime/readiness.js +0 -84
  180. package/lib/skill-runtime/review-records.js +0 -123
  181. package/lib/skill-runtime/review.js +0 -855
  182. package/lib/skill-runtime/schemas.js +0 -746
  183. package/lib/skill-runtime/task-contract.js +0 -188
  184. package/lib/skill-runtime/team-state.js +0 -122
  185. package/lib/skill-runtime/workflow-context.js +0 -748
@@ -1,170 +0,0 @@
1
- /**
2
- * [INPUT]: 依赖 manifest 与 package scripts,依赖 shell gates(lint/typecheck/test/audit/review)。
3
- * [OUTPUT]: 生成 report-card.json,给出 quick/strict/review 门禁结论。
4
- * [POS]: skill runtime 质量门禁入口,供内部验证链路复用。
5
- * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
- */
7
-
8
- const {
9
- nowIso,
10
- getPackageScripts,
11
- readJson,
12
- writeJson,
13
- runCommand,
14
- getTaskManifestPath,
15
- getRuntimeStatePath,
16
- getReportCardPath
17
- } = require('../store');
18
- const { parseManifest, parseReportCard } = require('../schemas');
19
- const { runReviewSuite } = require('../review');
20
-
21
- const path = require('path');
22
- const fs = require('fs');
23
- const os = require('os');
24
-
25
- const REQ_CHECK_DIR = path.join(__dirname, '..', '..', '..', '.claude', 'skills', 'cc-check');
26
- const RUN_GATES_SCRIPT = path.join(REQ_CHECK_DIR, 'scripts', 'run-quality-gates.sh');
27
- const RENDER_REPORT_SCRIPT = path.join(REQ_CHECK_DIR, 'scripts', 'render-report-card.js');
28
-
29
- function shellEscape(value) {
30
- return `'${String(value).replace(/'/g, `'\\''`)}'`;
31
- }
32
-
33
- async function runGateCollection({ repoRoot, definitions }) {
34
- if (!definitions || definitions.length === 0) {
35
- return [];
36
- }
37
-
38
- const args = definitions
39
- .map((item) => `--gate ${shellEscape(`${item.name}::${item.command}`)}`)
40
- .join(' ');
41
- const result = await runCommand(`bash ${shellEscape(RUN_GATES_SCRIPT)} ${args}`, {
42
- cwd: repoRoot,
43
- timeoutMs: 20 * 60 * 1000
44
- });
45
-
46
- if (result.code !== 0) {
47
- throw new Error(result.stderr || result.stdout || 'run-quality-gates.sh failed');
48
- }
49
-
50
- return JSON.parse(result.stdout || '[]');
51
- }
52
-
53
- async function renderReportCard({ repoRoot, changeId, manifestPath, quickGates, strictGates, review }) {
54
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-cc-check-'));
55
- const quickPath = path.join(tempDir, 'quick-gates.json');
56
- const strictPath = path.join(tempDir, 'strict-gates.json');
57
- const reviewPath = path.join(tempDir, 'review.json');
58
-
59
- try {
60
- fs.writeFileSync(quickPath, `${JSON.stringify(quickGates, null, 2)}\n`);
61
- fs.writeFileSync(strictPath, `${JSON.stringify(strictGates, null, 2)}\n`);
62
- fs.writeFileSync(reviewPath, `${JSON.stringify(review, null, 2)}\n`);
63
-
64
- const command = [
65
- 'node',
66
- shellEscape(RENDER_REPORT_SCRIPT),
67
- '--change-id',
68
- shellEscape(changeId),
69
- '--manifest',
70
- shellEscape(manifestPath),
71
- '--quick',
72
- shellEscape(quickPath),
73
- '--strict',
74
- shellEscape(strictPath),
75
- '--review',
76
- shellEscape(reviewPath),
77
- '--timestamp',
78
- shellEscape(nowIso())
79
- ].join(' ');
80
-
81
- const result = await runCommand(command, { cwd: repoRoot, timeoutMs: 20 * 60 * 1000 });
82
- if (result.code !== 0) {
83
- throw new Error(result.stderr || result.stdout || 'render-report-card.js failed');
84
- }
85
-
86
- return JSON.parse(result.stdout);
87
- } finally {
88
- fs.rmSync(tempDir, { recursive: true, force: true });
89
- }
90
- }
91
-
92
- async function runVerify({ repoRoot, changeId, strict = false, skipReview = false }) {
93
- const manifestPath = getTaskManifestPath(repoRoot, changeId);
94
- const manifest = parseManifest(await readJson(manifestPath));
95
- const scripts = await getPackageScripts(repoRoot);
96
-
97
- const quickGates = await runGateCollection({
98
- repoRoot,
99
- definitions: ['lint', 'typecheck', 'test'].map((name) => ({
100
- name,
101
- command: scripts[name]
102
- ? `npm run ${name}`
103
- : `printf '__CC_DEVFLOW_SKIP__ Script ${name} is not defined\n'`
104
- }))
105
- });
106
-
107
- const strictGates = strict
108
- ? await runGateCollection({
109
- repoRoot,
110
- definitions: [
111
- {
112
- name: 'test:integration',
113
- command: scripts['test:integration']
114
- ? 'npm run test:integration'
115
- : "printf '__CC_DEVFLOW_SKIP__ Script test:integration is not defined\n'"
116
- },
117
- {
118
- name: 'audit',
119
- command: 'npm audit --audit-level=high'
120
- }
121
- ]
122
- })
123
- : [];
124
-
125
- const review = await runReviewSuite({
126
- repoRoot,
127
- changeId,
128
- manifest,
129
- strict,
130
- skipReview
131
- });
132
- const report = parseReportCard(
133
- await renderReportCard({
134
- repoRoot,
135
- changeId,
136
- manifestPath,
137
- quickGates,
138
- strictGates,
139
- review
140
- })
141
- );
142
-
143
- const outputPath = getReportCardPath(repoRoot, changeId);
144
- await writeJson(outputPath, report);
145
-
146
- // Update change-state.json with verifiedAt timestamp if passed
147
- if (report.verdict === 'pass') {
148
- const statePath = getRuntimeStatePath(repoRoot, changeId);
149
- const stateExists = require('fs').existsSync(statePath);
150
- if (stateExists) {
151
- const state = await readJson(statePath);
152
- state.status = 'verified';
153
- state.verifiedAt = nowIso();
154
- state.updatedAt = nowIso();
155
- await writeJson(statePath, state);
156
- }
157
- }
158
-
159
- return {
160
- changeId,
161
- outputPath,
162
- overall: report.overall,
163
- verdict: report.verdict || (report.overall === 'pass' ? 'pass' : 'fail'),
164
- blockingFindings: report.blockingFindings
165
- };
166
- }
167
-
168
- module.exports = {
169
- runVerify
170
- };
@@ -1,531 +0,0 @@
1
- /**
2
- * [INPUT]: 依赖 delegation/store 提供的 handoff、workspace 与 shell 执行能力,接收 changeId/workerId/taskId/command/provider 以运行本地 worker。
3
- * [OUTPUT]: 对外提供 worker-run 执行结果,并写入 session.log、state、journal、message bus、目标 assignment 状态与 provider session prompt。
4
- * [POS]: skill runtime 的本地 worker 执行壳,把 Markdown handoff bundle 接到真实本地命令执行,并以最薄 provider launcher 连接 codex/claude。
5
- * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
- */
7
-
8
- const path = require('path');
9
- const {
10
- buildWorkerHandoff,
11
- appendWorkerJournal,
12
- appendMessageBus,
13
- updateWorkerState,
14
- updateAssignmentStatus,
15
- ensureWorkerWorkspace,
16
- getWorkerSessionLogPath
17
- } = require('../delegation');
18
- const {
19
- readText,
20
- readJson,
21
- writeText,
22
- nowIso,
23
- writeJson,
24
- appendJsonl,
25
- runCommand,
26
- getEventsPath,
27
- getTaskManifestPath,
28
- getRuntimeStatePath
29
- } = require('../store');
30
- const { parseManifest, parseRuntimeState } = require('../schemas');
31
- const { applyManifestExecutionState } = require('../planner');
32
- const { syncIntentMemory } = require('../intent');
33
- const { namedError } = require('../errors');
34
-
35
- function quoteShellArg(value) {
36
- return `'${String(value).replace(/'/g, `'\"'\"'`)}'`;
37
- }
38
-
39
- function summarizeOutput(result) {
40
- const combined = [result.stdout, result.stderr]
41
- .filter(Boolean)
42
- .join('\n')
43
- .replace(/\s+/g, ' ')
44
- .trim();
45
-
46
- if (!combined) {
47
- return result.code === 0 ? 'command finished without output' : 'command failed without output';
48
- }
49
-
50
- return combined.slice(0, 240);
51
- }
52
-
53
- function quoteCommandArg(value) {
54
- return `'${String(value).replace(/'/g, `'\"'\"'`)}'`;
55
- }
56
-
57
- async function appendSessionLog(logPath, lines) {
58
- const previous = await readText(logPath, '');
59
- const chunk = `${lines.join('\n')}\n`;
60
- await writeText(logPath, `${previous}${chunk}`);
61
- }
62
-
63
- async function writeWorkerEvent(repoRoot, changeId, taskId, event, debug = false) {
64
- if (!debug && !String(event.type || '').includes('failed')) {
65
- return;
66
- }
67
- await appendJsonl(getEventsPath(repoRoot, changeId, taskId), event);
68
- }
69
-
70
- async function updateAssignments(repoRoot, changeId, handoff, fields) {
71
- for (const taskId of handoff.taskIds) {
72
- await updateAssignmentStatus(repoRoot, changeId, taskId, fields(taskId));
73
- }
74
- }
75
-
76
- function pickTaskId(handoff, requestedTaskId) {
77
- if (!requestedTaskId) {
78
- return handoff.taskIds[0];
79
- }
80
-
81
- if (!handoff.taskIds.includes(requestedTaskId)) {
82
- throw new Error(`Worker ${handoff.workerId} is not assigned to task ${requestedTaskId}`);
83
- }
84
-
85
- return requestedTaskId;
86
- }
87
-
88
- function getProviderPromptPath(handoff, taskId) {
89
- return path.join(handoff.runtimeDir, `session-${taskId}.md`);
90
- }
91
-
92
- function getProviderLastMessagePath(handoff, taskId) {
93
- return path.join(handoff.runtimeDir, `session-${taskId}.last-message.md`);
94
- }
95
-
96
- function getProviderTranscriptPath(handoff, taskId, provider) {
97
- return path.join(handoff.runtimeDir, `session-${taskId}.${provider}.jsonl`);
98
- }
99
-
100
- function formatList(items, empty = '- None') {
101
- if (!items || items.length === 0) {
102
- return empty;
103
- }
104
-
105
- return items.map((item) => `- ${item}`).join('\n');
106
- }
107
-
108
- async function buildTaskBrief(repoRoot, changeId, taskId, planVersion) {
109
- const manifest = parseManifest(await readJson(getTaskManifestPath(repoRoot, changeId)));
110
- const task = manifest.tasks.find((item) => item.id === taskId);
111
-
112
- if (!task) {
113
- throw new Error(`Task ${taskId} not found in manifest`);
114
- }
115
-
116
- return [
117
- `# Task Brief: ${task.id}`,
118
- '',
119
- `- Plan Version: ${planVersion || manifest.metadata?.planVersion || 1}`,
120
- `- Type: \`${task.type}\``,
121
- `- Title: ${task.title}`,
122
- '',
123
- '## Dependencies',
124
- '',
125
- formatList(task.dependsOn.map((item) => `\`${item}\``), '- None'),
126
- '',
127
- '## Touched Files',
128
- '',
129
- formatList(task.touches.map((item) => `\`${item}\``), '- None'),
130
- '',
131
- '## Commands',
132
- '',
133
- formatList([...task.run, ...task.checks].map((item) => `\`${item}\``))
134
- ].join('\n');
135
- }
136
-
137
- async function assertWorkerPlanFresh(repoRoot, changeId, handoff) {
138
- const manifestPath = getTaskManifestPath(repoRoot, changeId);
139
- const manifest = parseManifest(await readJson(manifestPath));
140
- const currentPlanVersion = manifest.metadata?.planVersion || 1;
141
-
142
- if (currentPlanVersion === handoff.planVersion) {
143
- return;
144
- }
145
-
146
- throw namedError(
147
- 'StalePlanVersionError',
148
- `Worker ${handoff.workerId} was assigned to planVersion ${handoff.planVersion}, but current planVersion is ${currentPlanVersion}`,
149
- {
150
- artifactRefs: [
151
- `devflow/changes/<change>/planning/task-manifest.json`,
152
- handoff.assignmentPath
153
- ],
154
- rescueAction: 'rerun delegation sync for current planVersion before worker-run',
155
- details: {
156
- workerId: handoff.workerId,
157
- assignedPlanVersion: handoff.planVersion,
158
- currentPlanVersion,
159
- manifestPath
160
- }
161
- }
162
- );
163
- }
164
-
165
- async function buildProviderPrompt(repoRootOrHandoff, handoffOrTaskId, maybeTaskId) {
166
- const repoRoot = typeof repoRootOrHandoff === 'string' ? repoRootOrHandoff : process.cwd();
167
- const handoff = typeof repoRootOrHandoff === 'string' ? handoffOrTaskId : repoRootOrHandoff;
168
- const taskId = typeof repoRootOrHandoff === 'string' ? maybeTaskId : handoffOrTaskId;
169
- const [assignment, taskBrief] = await Promise.all([
170
- readText(handoff.assignmentPath, ''),
171
- buildTaskBrief(repoRoot, handoff.changeId, taskId, handoff.planVersion)
172
- ]);
173
-
174
- return [
175
- `# Worker Session`,
176
- '',
177
- `- Change: \`${handoff.changeId}\``,
178
- `- Worker: \`${handoff.workerId}\``,
179
- `- Role: \`${handoff.role}\``,
180
- `- Task: \`${taskId}\``,
181
- `- Plan Version: ${handoff.planVersion}`,
182
- '',
183
- '## Operating Rules',
184
- '',
185
- '- 先阅读下面的 assignment 和 task brief,再执行当前任务。',
186
- '- 仅处理当前 task,不要擅自结算同 worker 的其他任务。',
187
- '- 所有关键结论先落到仓库工件,再结束会话。',
188
- '- 如果发现 plan_version 漂移,停止并返回阻塞说明。',
189
- '',
190
- '## Worker Assignment',
191
- '',
192
- assignment.trim(),
193
- '',
194
- '## Selected Task Brief',
195
- '',
196
- taskBrief.trim(),
197
- ''
198
- ].join('\n');
199
- }
200
-
201
- function buildProviderCommand({
202
- provider,
203
- providerPromptPath,
204
- providerLastMessagePath,
205
- providerTranscriptPath,
206
- workspace,
207
- providerArgs
208
- }) {
209
- const extraArgs = providerArgs ? ` ${providerArgs.trim()}` : '';
210
- const stdinPromptArg = provider === 'codex' ? ' -' : '';
211
-
212
- if (provider === 'codex') {
213
- return `cat ${quoteCommandArg(providerPromptPath)} | codex exec --json --dangerously-bypass-approvals-and-sandbox -C ${quoteCommandArg(workspace)} -o ${quoteCommandArg(providerLastMessagePath)}${stdinPromptArg}${extraArgs} | tee ${quoteCommandArg(providerTranscriptPath)}`;
214
- }
215
-
216
- if (provider === 'claude') {
217
- return `cat ${quoteCommandArg(providerPromptPath)} | claude -p --dangerously-skip-permissions --add-dir ${quoteCommandArg(workspace)}${extraArgs}`;
218
- }
219
-
220
- throw new Error(`Unsupported provider: ${provider}`);
221
- }
222
-
223
- function parseCodexTranscript(stdout = '') {
224
- const lines = stdout
225
- .split('\n')
226
- .map((line) => line.trim())
227
- .filter(Boolean);
228
-
229
- const summary = {
230
- threadId: null,
231
- agentMessages: [],
232
- commandExecutions: [],
233
- fileChanges: [],
234
- toolCalls: [],
235
- errors: []
236
- };
237
-
238
- for (const line of lines) {
239
- let event;
240
- try {
241
- event = JSON.parse(line);
242
- } catch {
243
- continue;
244
- }
245
-
246
- if (event.type === 'thread.started' && event.thread_id) {
247
- summary.threadId = event.thread_id;
248
- continue;
249
- }
250
-
251
- if (event.type !== 'item.completed' || !event.item) {
252
- continue;
253
- }
254
-
255
- if (event.item.type === 'agent_message' && event.item.text) {
256
- summary.agentMessages.push(event.item.text);
257
- continue;
258
- }
259
-
260
- if (event.item.type === 'command_execution') {
261
- summary.commandExecutions.push({
262
- command: event.item.command,
263
- status: event.item.status,
264
- exitCode: event.item.exit_code
265
- });
266
- continue;
267
- }
268
-
269
- if (event.item.type === 'file_change') {
270
- summary.fileChanges.push(...(event.item.changes || []).map((change) => `${change.kind}:${change.path}`));
271
- continue;
272
- }
273
-
274
- if (event.item.type === 'mcp_tool_call') {
275
- summary.toolCalls.push(`${event.item.server}:${event.item.tool}:${event.item.status}`);
276
- continue;
277
- }
278
-
279
- if (event.item.type === 'error' && event.item.message) {
280
- summary.errors.push(event.item.message);
281
- }
282
- }
283
-
284
- return summary;
285
- }
286
-
287
- async function updateManifestTaskState(repoRoot, changeId, taskId, fields) {
288
- const manifestPath = getTaskManifestPath(repoRoot, changeId);
289
- const manifest = parseManifest(await readJson(manifestPath));
290
- const task = manifest.tasks.find((item) => item.id === taskId);
291
-
292
- if (!task) {
293
- throw new Error(`Task ${taskId} not found in manifest`);
294
- }
295
-
296
- if (fields.status) {
297
- task.status = fields.status;
298
- }
299
- if (Number.isInteger(fields.attempts)) {
300
- task.attempts = fields.attempts;
301
- }
302
- if (Object.prototype.hasOwnProperty.call(fields, 'lastError')) {
303
- task.lastError = fields.lastError || undefined;
304
- }
305
-
306
- applyManifestExecutionState(manifest, nowIso());
307
- await writeJson(manifestPath, manifest);
308
- return manifest;
309
- }
310
-
311
- async function markRuntimeInProgress(repoRoot, changeId) {
312
- const statePath = getRuntimeStatePath(repoRoot, changeId);
313
- const state = await readJson(statePath, null);
314
- if (!state) {
315
- return;
316
- }
317
-
318
- const parsed = parseRuntimeState(state);
319
- if (parsed.status === 'planned' || parsed.status === 'initialized') {
320
- parsed.status = 'in_progress';
321
- }
322
- parsed.updatedAt = nowIso();
323
- await writeJson(statePath, parsed);
324
- }
325
-
326
- async function runWorkerCommand({
327
- repoRoot,
328
- changeId,
329
- workerId,
330
- taskId,
331
- command,
332
- provider,
333
- providerArgs,
334
- debug = false,
335
- timeoutMs = 15 * 60 * 1000
336
- }) {
337
- if (!command && !provider) {
338
- throw new Error('Missing execution mode: provide --command "<local agent command>" or --provider <codex|claude>');
339
- }
340
-
341
- if (command && provider) {
342
- throw new Error('Use either --command or --provider, not both');
343
- }
344
-
345
- const handoff = await buildWorkerHandoff(repoRoot, changeId, workerId);
346
- await assertWorkerPlanFresh(repoRoot, changeId, handoff);
347
- const primaryTaskId = pickTaskId(handoff, taskId);
348
- const assignment = {
349
- workerId,
350
- role: handoff.role
351
- };
352
- const workspace = await ensureWorkerWorkspace(repoRoot, changeId, assignment);
353
- const sessionId = `${workerId}-${Date.now()}`;
354
- const startedAt = nowIso();
355
- const sessionLogPath = handoff.sessionLogPath || getWorkerSessionLogPath(repoRoot, changeId, workerId);
356
- const providerPromptPath = getProviderPromptPath(handoff, primaryTaskId);
357
- const providerLastMessagePath = getProviderLastMessagePath(handoff, primaryTaskId);
358
- const providerTranscriptPath = provider ? getProviderTranscriptPath(handoff, primaryTaskId, provider) : null;
359
- await markRuntimeInProgress(repoRoot, changeId);
360
- const providerPrompt = provider ? await buildProviderPrompt(repoRoot, handoff, primaryTaskId) : null;
361
- if (providerPrompt) {
362
- await writeText(providerPromptPath, `${providerPrompt}\n`);
363
- }
364
- const resolvedCommand = command || buildProviderCommand({
365
- provider,
366
- providerPromptPath,
367
- providerLastMessagePath,
368
- providerTranscriptPath,
369
- workspace: workspace.workspacePath,
370
- providerArgs
371
- });
372
- const wrappedCommand = [
373
- `export CC_DEVFLOW_CHANGE_ID=${quoteShellArg(changeId)}`,
374
- `export CC_DEVFLOW_WORKER_ID=${quoteShellArg(workerId)}`,
375
- `export CC_DEVFLOW_WORKER_ROLE=${quoteShellArg(handoff.role)}`,
376
- `export CC_DEVFLOW_TASK_ID=${quoteShellArg(primaryTaskId)}`,
377
- `export CC_DEVFLOW_WORKER_STATE=${quoteShellArg(handoff.statePath)}`,
378
- `export CC_DEVFLOW_WORKER_JOURNAL=${quoteShellArg(handoff.journalPath)}`,
379
- `export CC_DEVFLOW_WORKER_SESSION_LOG=${quoteShellArg(sessionLogPath)}`,
380
- providerPrompt ? `export CC_DEVFLOW_PROVIDER_PROMPT=${quoteShellArg(providerPromptPath)}` : '',
381
- providerPrompt ? `export CC_DEVFLOW_PROVIDER_LAST_MESSAGE=${quoteShellArg(providerLastMessagePath)}` : '',
382
- providerTranscriptPath ? `export CC_DEVFLOW_PROVIDER_TRANSCRIPT=${quoteShellArg(providerTranscriptPath)}` : '',
383
- provider ? `export CC_DEVFLOW_PROVIDER=${quoteShellArg(provider)}` : '',
384
- resolvedCommand
385
- ].join('\n');
386
-
387
- await updateWorkerState(repoRoot, changeId, workerId, {
388
- role: handoff.role,
389
- planVersion: handoff.planVersion,
390
- status: 'running',
391
- currentTask: primaryTaskId,
392
- branch: workspace.mode === 'worktree' ? 'detached-worktree' : 'controller',
393
- workspace: workspace.workspacePath
394
- });
395
- await updateManifestTaskState(repoRoot, changeId, primaryTaskId, {
396
- status: 'running',
397
- attempts: 1,
398
- lastError: undefined
399
- });
400
- await updateAssignments(repoRoot, changeId, handoff, (taskId) => ({
401
- status: taskId === primaryTaskId ? 'running' : undefined,
402
- workspace: taskId === primaryTaskId ? workspace.workspacePath : undefined,
403
- sessionId: taskId === primaryTaskId ? sessionId : undefined,
404
- summary: taskId === primaryTaskId
405
- ? `worker ${workerId} started ${provider || 'custom'} execution`
406
- : undefined
407
- }));
408
- await appendWorkerJournal(
409
- repoRoot,
410
- changeId,
411
- workerId,
412
- `${startedAt} started ${sessionId} in ${workspace.mode} workspace for ${primaryTaskId} via ${provider || 'custom-command'}`
413
- );
414
- await appendMessageBus(
415
- repoRoot,
416
- changeId,
417
- `${workerId} started ${primaryTaskId} via ${provider || 'custom-command'} in ${workspace.mode} workspace`
418
- );
419
- await writeWorkerEvent(repoRoot, changeId, primaryTaskId, {
420
- type: 'worker_run_started',
421
- changeId,
422
- taskId: primaryTaskId,
423
- sessionId,
424
- provider: provider || 'custom',
425
- workerId,
426
- timestamp: startedAt
427
- }, debug);
428
- await appendSessionLog(sessionLogPath, [
429
- `# Session ${sessionId}`,
430
- `started_at: ${startedAt}`,
431
- `worker: ${workerId}`,
432
- `role: ${handoff.role}`,
433
- `workspace: ${workspace.workspacePath}`,
434
- `workspace_mode: ${workspace.mode}`,
435
- `workspace_reason: ${workspace.reason}`,
436
- `provider: ${provider || 'custom'}`,
437
- providerPrompt ? `provider_prompt: ${providerPromptPath}` : '',
438
- `command: ${resolvedCommand}`,
439
- '',
440
- '## output'
441
- ].filter(Boolean));
442
-
443
- const result = await runCommand(wrappedCommand, {
444
- cwd: workspace.cwd,
445
- timeoutMs
446
- });
447
- const finishedAt = nowIso();
448
- const finalStatus = result.code === 0 ? 'completed' : 'failed';
449
- const summary = summarizeOutput(result);
450
- const codexTranscript = provider === 'codex' ? parseCodexTranscript(result.stdout) : null;
451
-
452
- await appendSessionLog(sessionLogPath, [
453
- result.stdout ? result.stdout.trimEnd() : '',
454
- result.stderr ? `\n## stderr\n${result.stderr.trimEnd()}` : '',
455
- '',
456
- `exit_code: ${result.code}`,
457
- `finished_at: ${finishedAt}`,
458
- `duration_ms: ${result.durationMs}`,
459
- `timed_out: ${result.killedByTimeout ? 'true' : 'false'}`,
460
- ''
461
- ].filter(Boolean));
462
-
463
- await updateManifestTaskState(repoRoot, changeId, primaryTaskId, {
464
- status: result.code === 0 ? 'passed' : 'failed',
465
- attempts: 1,
466
- lastError: result.code === 0 ? undefined : (result.stderr || result.stdout || '').trim()
467
- });
468
- await writeWorkerEvent(repoRoot, changeId, primaryTaskId, {
469
- type: result.code === 0 ? 'worker_run_passed' : 'worker_run_failed',
470
- changeId,
471
- taskId: primaryTaskId,
472
- sessionId,
473
- provider: provider || 'custom',
474
- workerId,
475
- timestamp: finishedAt,
476
- error: result.code === 0 ? undefined : (result.stderr || result.stdout || '').trim()
477
- }, debug);
478
- await syncIntentMemory(repoRoot, changeId, {
479
- event: result.code === 0 ? 'worker_run_completed' : 'worker_run_failed',
480
- reason: `${workerId} ${result.code === 0 ? 'completed' : 'failed'} ${primaryTaskId} via ${provider || 'custom-command'}`
481
- });
482
-
483
- await updateWorkerState(repoRoot, changeId, workerId, {
484
- role: handoff.role,
485
- planVersion: handoff.planVersion,
486
- status: finalStatus,
487
- currentTask: result.code === 0 ? 'none' : primaryTaskId,
488
- branch: workspace.mode === 'worktree' ? 'detached-worktree' : 'controller',
489
- workspace: workspace.workspacePath
490
- });
491
- await updateAssignments(repoRoot, changeId, handoff, (taskId) => ({
492
- status: taskId === primaryTaskId ? finalStatus : undefined,
493
- workspace: taskId === primaryTaskId ? workspace.workspacePath : undefined,
494
- sessionId: taskId === primaryTaskId ? sessionId : undefined,
495
- summary: taskId === primaryTaskId ? `${workerId} ${finalStatus}: ${summary}` : undefined
496
- }));
497
- await appendWorkerJournal(
498
- repoRoot,
499
- changeId,
500
- workerId,
501
- `${finishedAt} ${sessionId} ${finalStatus} (exit ${result.code})`
502
- );
503
- await appendMessageBus(
504
- repoRoot,
505
- changeId,
506
- `${workerId} ${finalStatus} ${primaryTaskId}: ${summary}`
507
- );
508
-
509
- return {
510
- changeId,
511
- workerId,
512
- role: handoff.role,
513
- sessionId,
514
- status: finalStatus,
515
- taskId: primaryTaskId,
516
- workspace,
517
- provider: provider || null,
518
- providerPromptPath: providerPrompt ? providerPromptPath : null,
519
- providerLastMessagePath: providerPrompt ? providerLastMessagePath : null,
520
- providerTranscriptPath,
521
- command: resolvedCommand,
522
- sessionLogPath,
523
- result
524
- };
525
- }
526
-
527
- module.exports = {
528
- buildProviderCommand,
529
- buildProviderPrompt,
530
- runWorkerCommand
531
- };