cc-devflow 4.1.5 → 4.1.6

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 (111) hide show
  1. package/.claude/CLAUDE.md +87 -1091
  2. package/.claude/commands/core/architecture.md +2 -2
  3. package/.claude/commands/core/guidelines.md +2 -2
  4. package/.claude/commands/core/roadmap.md +4 -4
  5. package/.claude/commands/core/style.md +40 -268
  6. package/.claude/commands/flow/CLAUDE.md +28 -0
  7. package/.claude/commands/flow/archive.md +2 -2
  8. package/.claude/commands/flow/checklist.md +9 -251
  9. package/.claude/commands/flow/clarify.md +9 -127
  10. package/.claude/commands/flow/constitution.md +1 -1
  11. package/.claude/commands/flow/context.md +1 -1
  12. package/.claude/commands/flow/dev.md +19 -395
  13. package/.claude/commands/flow/ideate.md +13 -13
  14. package/.claude/commands/flow/init.md +19 -30
  15. package/.claude/commands/flow/new.md +12 -268
  16. package/.claude/commands/flow/quality.md +10 -153
  17. package/.claude/commands/flow/release.md +18 -81
  18. package/.claude/commands/flow/restart.md +15 -16
  19. package/.claude/commands/flow/spec.md +14 -164
  20. package/.claude/commands/flow/status.md +12 -12
  21. package/.claude/commands/flow/update.md +4 -4
  22. package/.claude/commands/flow/upgrade.md +6 -6
  23. package/.claude/commands/flow/verify.md +19 -78
  24. package/.claude/commands/flow/workspace.md +1 -1
  25. package/.claude/docs/guides/INIT_TROUBLESHOOTING.md +7 -7
  26. package/.claude/docs/guides/NEW_TROUBLESHOOTING.md +44 -96
  27. package/.claude/docs/guides/ROADMAP_TROUBLESHOOTING.md +1 -1
  28. package/.claude/docs/guides/TASK_COMPLETION_MARKING.md +5 -5
  29. package/.claude/docs/templates/ATTEMPT_TEMPLATE.md +1 -1
  30. package/.claude/docs/templates/BACKLOG_TEMPLATE.md +3 -3
  31. package/.claude/docs/templates/CLARIFICATION_REPORT_TEMPLATE.md +5 -5
  32. package/.claude/docs/templates/ERROR_LOG_TEMPLATE.md +2 -2
  33. package/.claude/docs/templates/INIT_FLOW_TEMPLATE.md +3 -3
  34. package/.claude/docs/templates/NEW_ORCHESTRATION_TEMPLATE.md +33 -64
  35. package/.claude/docs/templates/RESEARCH_TEMPLATE.md +3 -3
  36. package/.claude/docs/templates/ROADMAP_DIALOGUE_TEMPLATE.md +2 -2
  37. package/.claude/docs/templates/ROADMAP_TEMPLATE.md +2 -2
  38. package/.claude/docs/templates/STYLE_TEMPLATE.md +3 -3
  39. package/.claude/docs/templates/UI_PROTOTYPE_TEMPLATE.md +8 -9
  40. package/.claude/guides/workflow-guides/flow-orchestrator.md +31 -265
  41. package/.claude/hooks/CLAUDE.md +1 -1
  42. package/.claude/hooks/checklist-gate.js +4 -4
  43. package/.claude/hooks/inject-agent-context.ts +2 -2
  44. package/.claude/scripts/calculate-checklist-completion.sh +2 -2
  45. package/.claude/scripts/check-prerequisites.sh +2 -2
  46. package/.claude/scripts/checklist-errors.sh +4 -4
  47. package/.claude/scripts/flow-quality-full.sh +5 -5
  48. package/.claude/scripts/flow-quality-quick.sh +4 -4
  49. package/.claude/scripts/flow-workspace-init.sh +2 -2
  50. package/.claude/scripts/generate-clarification-report.sh +4 -4
  51. package/.claude/scripts/recover-workflow.sh +70 -73
  52. package/.claude/scripts/run-quality-gates.sh +1 -1
  53. package/.claude/scripts/setup-epic.sh +2 -2
  54. package/.claude/scripts/setup-ralph-loop.sh +2 -2
  55. package/.claude/scripts/validate-research.sh +1 -1
  56. package/.claude/scripts/verify-setup.sh +1 -1
  57. package/.claude/skills/cc-devflow-orchestrator/SKILL.md +88 -108
  58. package/.claude/skills/workflow/CLAUDE.md +24 -0
  59. package/.claude/skills/workflow/flow-dev/CLAUDE.md +14 -76
  60. package/.claude/skills/workflow/flow-dev/SKILL.md +29 -67
  61. package/.claude/skills/workflow/flow-dev/context.jsonl +4 -8
  62. package/.claude/skills/workflow/flow-init/SKILL.md +24 -151
  63. package/.claude/skills/workflow/flow-init/assets/RESEARCH_TEMPLATE.md +1 -1
  64. package/.claude/skills/workflow/flow-init/context.jsonl +3 -3
  65. package/.claude/skills/workflow/flow-init/scripts/check-prerequisites.sh +1 -1
  66. package/.claude/skills/workflow/flow-init/scripts/validate-research.sh +1 -1
  67. package/.claude/skills/workflow/flow-release/SKILL.md +23 -56
  68. package/.claude/skills/workflow/flow-release/context.jsonl +5 -7
  69. package/.claude/skills/workflow/flow-spec/CLAUDE.md +15 -101
  70. package/.claude/skills/workflow/flow-spec/SKILL.md +15 -518
  71. package/.claude/skills/workflow/flow-spec/context.jsonl +5 -7
  72. package/.claude/skills/workflow/flow-verify/CLAUDE.md +10 -0
  73. package/.claude/skills/workflow/flow-verify/SKILL.md +53 -0
  74. package/.claude/skills/workflow/flow-verify/context.jsonl +5 -0
  75. package/.claude/skills/workflow.yaml +72 -267
  76. package/CHANGELOG.md +31 -0
  77. package/README.md +91 -69
  78. package/README.zh-CN.md +90 -67
  79. package/bin/harness.js +22 -0
  80. package/docs/commands/README.md +34 -38
  81. package/docs/commands/README.zh-CN.md +34 -36
  82. package/docs/commands/core-roadmap.md +2 -2
  83. package/docs/commands/core-roadmap.zh-CN.md +2 -2
  84. package/docs/commands/core-style.md +29 -381
  85. package/docs/commands/core-style.zh-CN.md +29 -381
  86. package/docs/commands/flow-init.md +10 -10
  87. package/docs/commands/flow-init.zh-CN.md +11 -11
  88. package/docs/commands/flow-new.md +25 -260
  89. package/docs/commands/flow-new.zh-CN.md +26 -257
  90. package/docs/guides/getting-started.md +16 -15
  91. package/docs/guides/getting-started.zh-CN.md +10 -12
  92. package/lib/compiler/__tests__/manifest.test.js +156 -0
  93. package/lib/compiler/__tests__/parser.test.js +21 -0
  94. package/lib/compiler/index.js +17 -1
  95. package/lib/compiler/manifest.js +68 -6
  96. package/lib/compiler/parser.js +5 -0
  97. package/lib/harness/CLAUDE.md +21 -0
  98. package/lib/harness/cli.js +208 -0
  99. package/lib/harness/index.js +16 -0
  100. package/lib/harness/operations/dispatch.js +285 -0
  101. package/lib/harness/operations/init.js +48 -0
  102. package/lib/harness/operations/janitor.js +74 -0
  103. package/lib/harness/operations/pack.js +100 -0
  104. package/lib/harness/operations/plan.js +29 -0
  105. package/lib/harness/operations/release.js +83 -0
  106. package/lib/harness/operations/resume.js +44 -0
  107. package/lib/harness/operations/verify.js +163 -0
  108. package/lib/harness/planner.js +141 -0
  109. package/lib/harness/schemas.js +108 -0
  110. package/lib/harness/store.js +240 -0
  111. package/package.json +9 -1
@@ -0,0 +1,285 @@
1
+ /**
2
+ * [INPUT]: 依赖 manifest/checkpoint/events 与 shell 执行能力,接收并行度与重试参数。
3
+ * [OUTPUT]: 更新 task-manifest 状态,写入每任务 events.jsonl 与 checkpoint.json。
4
+ * [POS]: harness Stage-4 执行入口,被 CLI `harness:dispatch` 与 `harness:resume` 复用。
5
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
+ */
7
+
8
+ const {
9
+ nowIso,
10
+ ensureDir,
11
+ appendJsonl,
12
+ writeJson,
13
+ readJson,
14
+ runCommand,
15
+ getTaskManifestPath,
16
+ getRuntimeTaskDir,
17
+ getEventsPath,
18
+ getCheckpointPath
19
+ } = require('../store');
20
+ const { parseManifest, parseCheckpoint } = require('../schemas');
21
+
22
+ function toSessionId(taskId) {
23
+ return `${taskId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
24
+ }
25
+
26
+ function dependenciesPassed(task, taskMap) {
27
+ return task.dependsOn.every((depId) => {
28
+ const dep = taskMap.get(depId);
29
+ return dep && dep.status === 'passed';
30
+ });
31
+ }
32
+
33
+ function dependenciesFailed(task, taskMap) {
34
+ return task.dependsOn.some((depId) => {
35
+ const dep = taskMap.get(depId);
36
+ return dep && (dep.status === 'failed' || dep.status === 'skipped');
37
+ });
38
+ }
39
+
40
+ function hasTouchConflict(task, lockedTouches) {
41
+ if (!task.touches || task.touches.length === 0) {
42
+ return false;
43
+ }
44
+
45
+ return task.touches.some((filePath) => lockedTouches.has(filePath));
46
+ }
47
+
48
+ function selectBatch(readyTasks, parallel) {
49
+ const selected = [];
50
+ const lockedTouches = new Set();
51
+
52
+ for (const task of readyTasks) {
53
+ if (selected.length >= parallel) {
54
+ break;
55
+ }
56
+
57
+ if (hasTouchConflict(task, lockedTouches)) {
58
+ continue;
59
+ }
60
+
61
+ selected.push(task);
62
+ for (const touched of task.touches) {
63
+ lockedTouches.add(touched);
64
+ }
65
+ }
66
+
67
+ return selected;
68
+ }
69
+
70
+ async function writeCheckpoint(repoRoot, changeId, taskId, payload) {
71
+ const checkpointPath = getCheckpointPath(repoRoot, changeId, taskId);
72
+ const checkpoint = parseCheckpoint(payload);
73
+ await writeJson(checkpointPath, checkpoint);
74
+ }
75
+
76
+ async function writeEvent(repoRoot, changeId, taskId, event) {
77
+ const eventsPath = getEventsPath(repoRoot, changeId, taskId);
78
+ await appendJsonl(eventsPath, event);
79
+ }
80
+
81
+ async function executeTask({ repoRoot, changeId, task, retryOverride }) {
82
+ const taskRuntimeDir = getRuntimeTaskDir(repoRoot, changeId, task.id);
83
+ await ensureDir(taskRuntimeDir);
84
+
85
+ const sessionId = toSessionId(task.id);
86
+ const commands = [...task.run, ...task.checks];
87
+ const taskRetry = Number.isInteger(retryOverride) ? retryOverride : task.maxRetries;
88
+ const maxAttempts = Math.max(1, taskRetry + 1);
89
+
90
+ while (task.attempts < maxAttempts) {
91
+ task.attempts += 1;
92
+ task.status = 'running';
93
+
94
+ await writeCheckpoint(repoRoot, changeId, task.id, {
95
+ changeId,
96
+ taskId: task.id,
97
+ sessionId,
98
+ status: task.status,
99
+ summary: `Task started (attempt ${task.attempts}/${maxAttempts})`,
100
+ timestamp: nowIso(),
101
+ attempt: task.attempts
102
+ });
103
+
104
+ await writeEvent(repoRoot, changeId, task.id, {
105
+ type: 'task_started',
106
+ changeId,
107
+ taskId: task.id,
108
+ sessionId,
109
+ attempt: task.attempts,
110
+ timestamp: nowIso()
111
+ });
112
+
113
+ let failed = false;
114
+ let failureMessage = '';
115
+
116
+ for (const command of commands) {
117
+ const result = await runCommand(command, {
118
+ cwd: repoRoot,
119
+ timeoutMs: 30 * 60 * 1000
120
+ });
121
+
122
+ await writeEvent(repoRoot, changeId, task.id, {
123
+ type: 'command_finished',
124
+ changeId,
125
+ taskId: task.id,
126
+ sessionId,
127
+ attempt: task.attempts,
128
+ command,
129
+ code: result.code,
130
+ durationMs: result.durationMs,
131
+ timestamp: nowIso()
132
+ });
133
+
134
+ if (result.code !== 0) {
135
+ failed = true;
136
+ failureMessage = (result.stderr || result.stdout || 'Command failed').trim();
137
+ break;
138
+ }
139
+ }
140
+
141
+ if (!failed) {
142
+ task.status = 'passed';
143
+ task.lastError = undefined;
144
+
145
+ await writeCheckpoint(repoRoot, changeId, task.id, {
146
+ changeId,
147
+ taskId: task.id,
148
+ sessionId,
149
+ status: task.status,
150
+ summary: `Task passed after ${task.attempts} attempt(s)`,
151
+ timestamp: nowIso(),
152
+ attempt: task.attempts
153
+ });
154
+
155
+ await writeEvent(repoRoot, changeId, task.id, {
156
+ type: 'task_passed',
157
+ changeId,
158
+ taskId: task.id,
159
+ sessionId,
160
+ attempts: task.attempts,
161
+ timestamp: nowIso()
162
+ });
163
+
164
+ return;
165
+ }
166
+
167
+ task.status = 'failed';
168
+ task.lastError = failureMessage;
169
+
170
+ await writeCheckpoint(repoRoot, changeId, task.id, {
171
+ changeId,
172
+ taskId: task.id,
173
+ sessionId,
174
+ status: task.status,
175
+ summary: `Task failed: ${failureMessage.slice(0, 240)}`,
176
+ timestamp: nowIso(),
177
+ attempt: task.attempts
178
+ });
179
+
180
+ await writeEvent(repoRoot, changeId, task.id, {
181
+ type: 'task_failed',
182
+ changeId,
183
+ taskId: task.id,
184
+ sessionId,
185
+ attempt: task.attempts,
186
+ error: failureMessage,
187
+ timestamp: nowIso()
188
+ });
189
+ }
190
+ }
191
+
192
+ function summarizeTasks(tasks) {
193
+ return tasks.reduce(
194
+ (acc, task) => {
195
+ acc[task.status] = (acc[task.status] || 0) + 1;
196
+ return acc;
197
+ },
198
+ { pending: 0, running: 0, passed: 0, failed: 0, skipped: 0 }
199
+ );
200
+ }
201
+
202
+ async function runDispatch({ repoRoot, changeId, parallel = 3, maxRetries, resume = false }) {
203
+ const manifestPath = getTaskManifestPath(repoRoot, changeId);
204
+ const manifest = parseManifest(await readJson(manifestPath));
205
+
206
+ if (resume) {
207
+ for (const task of manifest.tasks) {
208
+ if (task.status === 'running') {
209
+ task.status = 'pending';
210
+ }
211
+ }
212
+ }
213
+
214
+ const safeParallel = Math.max(1, Number.parseInt(parallel, 10) || 1);
215
+
216
+ while (true) {
217
+ const taskMap = new Map(manifest.tasks.map((task) => [task.id, task]));
218
+ const pending = manifest.tasks.filter((task) => task.status === 'pending');
219
+
220
+ if (pending.length === 0) {
221
+ break;
222
+ }
223
+
224
+ for (const task of pending) {
225
+ if (dependenciesFailed(task, taskMap)) {
226
+ task.status = 'skipped';
227
+ task.lastError = 'Blocked by failed dependency';
228
+ }
229
+ }
230
+
231
+ const ready = manifest.tasks.filter(
232
+ (task) => task.status === 'pending' && dependenciesPassed(task, taskMap)
233
+ );
234
+
235
+ if (ready.length === 0) {
236
+ await writeJson(manifestPath, {
237
+ ...manifest,
238
+ updatedAt: nowIso()
239
+ });
240
+
241
+ return {
242
+ changeId,
243
+ manifestPath,
244
+ summary: summarizeTasks(manifest.tasks),
245
+ success: false,
246
+ reason: 'No ready tasks left. Check dependencies and failed tasks.'
247
+ };
248
+ }
249
+
250
+ const batch = selectBatch(ready, safeParallel);
251
+
252
+ if (batch.length === 0) {
253
+ const firstReady = ready[0];
254
+ batch.push(firstReady);
255
+ }
256
+
257
+ await Promise.all(
258
+ batch.map((task) =>
259
+ executeTask({
260
+ repoRoot,
261
+ changeId,
262
+ task,
263
+ retryOverride: Number.isInteger(maxRetries) ? maxRetries : undefined
264
+ })
265
+ )
266
+ );
267
+
268
+ manifest.updatedAt = nowIso();
269
+ await writeJson(manifestPath, manifest);
270
+ }
271
+
272
+ const summary = summarizeTasks(manifest.tasks);
273
+ const success = summary.failed === 0 && summary.pending === 0;
274
+
275
+ return {
276
+ changeId,
277
+ manifestPath,
278
+ summary,
279
+ success
280
+ };
281
+ }
282
+
283
+ module.exports = {
284
+ runDispatch
285
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * [INPUT]: 依赖 store 提供 repo/change 路径与读写能力,接收 changeId/goal 初始化参数。
3
+ * [OUTPUT]: 写入 requirement 目录与 harness-state.json,返回初始化摘要。
4
+ * [POS]: harness Stage-1 初始化入口,被 CLI `harness:init` 调用。
5
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
+ */
7
+
8
+ const {
9
+ nowIso,
10
+ ensureDir,
11
+ writeJson,
12
+ readJson,
13
+ getRequirementDir,
14
+ getRuntimeChangeDir,
15
+ getHarnessStatePath
16
+ } = require('../store');
17
+
18
+ async function runInit({ repoRoot, changeId, goal }) {
19
+ const requirementDir = getRequirementDir(repoRoot, changeId);
20
+ const runtimeDir = getRuntimeChangeDir(repoRoot, changeId);
21
+ const statePath = getHarnessStatePath(repoRoot, changeId);
22
+
23
+ await ensureDir(requirementDir);
24
+ await ensureDir(runtimeDir);
25
+
26
+ const previous = (await readJson(statePath, {})) || {};
27
+ const nextState = {
28
+ changeId,
29
+ goal: goal || previous.goal || `Deliver ${changeId} safely with auditable checkpoints.`,
30
+ status: 'initialized',
31
+ initializedAt: previous.initializedAt || nowIso(),
32
+ updatedAt: nowIso()
33
+ };
34
+
35
+ await writeJson(statePath, nextState);
36
+
37
+ return {
38
+ changeId,
39
+ requirementDir,
40
+ runtimeDir,
41
+ statePath,
42
+ status: nextState.status
43
+ };
44
+ }
45
+
46
+ module.exports = {
47
+ runInit
48
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * [INPUT]: 依赖 .harness/runtime 目录与 checkpoint 状态,接收保留小时阈值。
3
+ * [OUTPUT]: 删除过期且非运行中的任务运行态目录,并输出清理统计。
4
+ * [POS]: harness 熵清理入口,被 CLI `harness:janitor` 调用。
5
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const {
11
+ readJson,
12
+ listDirectories,
13
+ getRuntimeRoot
14
+ } = require('../store');
15
+
16
+ async function removeDirectoryRecursive(dirPath) {
17
+ await fs.promises.rm(dirPath, { recursive: true, force: true });
18
+ }
19
+
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
+ async function runJanitor({ repoRoot, hours = 72 }) {
32
+ const runtimeRoot = getRuntimeRoot(repoRoot);
33
+ const cutoffMs = Date.now() - Number(hours) * 60 * 60 * 1000;
34
+
35
+ const changeDirs = await listDirectories(runtimeRoot);
36
+ let removedTaskDirs = 0;
37
+ let removedChangeDirs = 0;
38
+
39
+ for (const changeDir of changeDirs) {
40
+ const taskDirs = await listDirectories(changeDir);
41
+
42
+ for (const taskDir of taskDirs) {
43
+ const stat = await fs.promises.stat(taskDir);
44
+ const isStale = stat.mtimeMs < cutoffMs;
45
+ if (!isStale) {
46
+ continue;
47
+ }
48
+
49
+ if (await isTaskActive(taskDir)) {
50
+ continue;
51
+ }
52
+
53
+ await removeDirectoryRecursive(taskDir);
54
+ removedTaskDirs += 1;
55
+ }
56
+
57
+ const remaining = await listDirectories(changeDir);
58
+ if (remaining.length === 0) {
59
+ await removeDirectoryRecursive(changeDir);
60
+ removedChangeDirs += 1;
61
+ }
62
+ }
63
+
64
+ return {
65
+ runtimeRoot,
66
+ removedTaskDirs,
67
+ removedChangeDirs,
68
+ cutoffHours: Number(hours)
69
+ };
70
+ }
71
+
72
+ module.exports = {
73
+ runJanitor
74
+ };
@@ -0,0 +1,100 @@
1
+ /**
2
+ * [INPUT]: 依赖 store 读取 harness-state/package scripts,并通过 git 命令收集仓库事实。
3
+ * [OUTPUT]: 生成 context-package.md,提供可复现的目标/约束/下一步命令。
4
+ * [POS]: harness Stage-2 上下文打包入口,被 CLI `harness:pack` 调用。
5
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
+ */
7
+
8
+ const {
9
+ nowIso,
10
+ readJson,
11
+ writeText,
12
+ getContextPackagePath,
13
+ getHarnessStatePath,
14
+ runCommand,
15
+ getPackageScripts
16
+ } = require('../store');
17
+
18
+ async function collectGitFacts(repoRoot) {
19
+ const [branch, commit, status] = await Promise.all([
20
+ runCommand('git rev-parse --abbrev-ref HEAD', { cwd: repoRoot }),
21
+ runCommand('git rev-parse HEAD', { cwd: repoRoot }),
22
+ runCommand('git status --short', { cwd: repoRoot })
23
+ ]);
24
+
25
+ return {
26
+ branch: branch.code === 0 ? branch.stdout.trim() : 'unknown',
27
+ commit: commit.code === 0 ? commit.stdout.trim() : 'unknown',
28
+ status: status.code === 0 ? status.stdout.trim() : '(clean)'
29
+ };
30
+ }
31
+
32
+ function buildContextMarkdown({ changeId, goal, gitFacts, scripts }) {
33
+ const nextCommands = [
34
+ `npm run harness:plan -- --change-id ${changeId}`,
35
+ `npm run harness:dispatch -- --change-id ${changeId} --parallel 3`,
36
+ `npm run harness:verify -- --change-id ${changeId} --strict`,
37
+ `npm run harness:release -- --change-id ${changeId}`
38
+ ];
39
+
40
+ return [
41
+ `# Context Package - ${changeId}`,
42
+ '',
43
+ `- Generated: ${nowIso()}`,
44
+ `- Goal: ${goal}`,
45
+ '',
46
+ '## Repository Facts',
47
+ '',
48
+ `- Branch: ${gitFacts.branch}`,
49
+ `- Commit: ${gitFacts.commit}`,
50
+ '',
51
+ '### Git Status',
52
+ '',
53
+ '```text',
54
+ gitFacts.status || '(clean)',
55
+ '```',
56
+ '',
57
+ '## Constraints',
58
+ '',
59
+ '- Keep tasks dependency-aware and checkpointed.',
60
+ '- Block release when strict verification fails.',
61
+ '- Preserve auditable runtime logs in .harness/runtime/.',
62
+ '',
63
+ '## Available npm scripts',
64
+ '',
65
+ '```text',
66
+ Object.keys(scripts).sort().join('\n') || '(none)',
67
+ '```',
68
+ '',
69
+ '## Next Commands',
70
+ '',
71
+ ...nextCommands.map((command) => `- \`${command}\``),
72
+ ''
73
+ ].join('\n');
74
+ }
75
+
76
+ async function runPack({ repoRoot, changeId, goal }) {
77
+ const state = (await readJson(getHarnessStatePath(repoRoot, changeId), {})) || {};
78
+ const effectiveGoal = goal || state.goal || `Deliver ${changeId} safely with auditable checkpoints.`;
79
+ const gitFacts = await collectGitFacts(repoRoot);
80
+ const scripts = await getPackageScripts(repoRoot);
81
+ const context = buildContextMarkdown({
82
+ changeId,
83
+ goal: effectiveGoal,
84
+ gitFacts,
85
+ scripts
86
+ });
87
+
88
+ const outputPath = getContextPackagePath(repoRoot, changeId);
89
+ await writeText(outputPath, `${context}`);
90
+
91
+ return {
92
+ changeId,
93
+ outputPath,
94
+ goal: effectiveGoal
95
+ };
96
+ }
97
+
98
+ module.exports = {
99
+ runPack
100
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * [INPUT]: 依赖 planner.createTaskManifest,接收 changeId/goal/overwrite 参数。
3
+ * [OUTPUT]: 生成并返回已校验的 task-manifest.json 摘要。
4
+ * [POS]: harness Stage-3 计划生成入口,被 CLI `harness:plan` 调用。
5
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
+ */
7
+
8
+ const { createTaskManifest } = require('../planner');
9
+ const { getTaskManifestPath } = require('../store');
10
+
11
+ async function runPlan({ repoRoot, changeId, goal, overwrite }) {
12
+ const manifest = await createTaskManifest({
13
+ repoRoot,
14
+ changeId,
15
+ goal,
16
+ overwrite
17
+ });
18
+
19
+ return {
20
+ changeId,
21
+ manifestPath: getTaskManifestPath(repoRoot, changeId),
22
+ taskCount: manifest.tasks.length,
23
+ source: manifest.metadata.source
24
+ };
25
+ }
26
+
27
+ module.exports = {
28
+ runPlan
29
+ };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * [INPUT]: 依赖 report-card 与 task-manifest 的最终状态。
3
+ * [OUTPUT]: 在 requirement 目录生成 RELEASE_NOTE.md,并更新 harness-state 为 released。
4
+ * [POS]: harness 发布收尾入口,被 CLI `harness:release` 调用。
5
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
+ */
7
+
8
+ const {
9
+ nowIso,
10
+ readJson,
11
+ writeText,
12
+ writeJson,
13
+ getReportCardPath,
14
+ getTaskManifestPath,
15
+ getReleaseNotePath,
16
+ getHarnessStatePath
17
+ } = require('../store');
18
+ const { parseReportCard, parseManifest } = require('../schemas');
19
+
20
+ function formatReleaseNote({ changeId, manifest, report }) {
21
+ const passedTasks = manifest.tasks.filter((task) => task.status === 'passed');
22
+ const failedTasks = manifest.tasks.filter((task) => task.status === 'failed');
23
+
24
+ return [
25
+ `# Release Note - ${changeId}`,
26
+ '',
27
+ `- Released At: ${nowIso()}`,
28
+ `- Verification: ${report.overall.toUpperCase()}`,
29
+ '',
30
+ '## Task Summary',
31
+ '',
32
+ `- Passed: ${passedTasks.length}`,
33
+ `- Failed: ${failedTasks.length}`,
34
+ '',
35
+ '## Completed Tasks',
36
+ '',
37
+ ...(passedTasks.length > 0
38
+ ? passedTasks.map((task) => `- ${task.id}: ${task.title}`)
39
+ : ['- (none)']),
40
+ '',
41
+ '## Blocking Findings',
42
+ '',
43
+ ...(report.blockingFindings.length > 0
44
+ ? report.blockingFindings.map((item) => `- ${item}`)
45
+ : ['- None']),
46
+ ''
47
+ ].join('\n');
48
+ }
49
+
50
+ async function runRelease({ repoRoot, changeId }) {
51
+ const reportPath = getReportCardPath(repoRoot, changeId);
52
+ const manifestPath = getTaskManifestPath(repoRoot, changeId);
53
+ const statePath = getHarnessStatePath(repoRoot, changeId);
54
+
55
+ const report = parseReportCard(await readJson(reportPath));
56
+ const manifest = parseManifest(await readJson(manifestPath));
57
+
58
+ if (report.overall !== 'pass') {
59
+ throw new Error('Release blocked: report-card overall is not pass');
60
+ }
61
+
62
+ const note = formatReleaseNote({ changeId, manifest, report });
63
+ const releaseNotePath = getReleaseNotePath(repoRoot, changeId);
64
+
65
+ await writeText(releaseNotePath, note);
66
+ await writeJson(statePath, {
67
+ changeId,
68
+ goal: manifest.goal,
69
+ status: 'released',
70
+ releasedAt: nowIso(),
71
+ updatedAt: nowIso()
72
+ });
73
+
74
+ return {
75
+ changeId,
76
+ releaseNotePath,
77
+ status: 'released'
78
+ };
79
+ }
80
+
81
+ module.exports = {
82
+ runRelease
83
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * [INPUT]: 依赖 manifest 当前任务状态与 dispatch 执行器,接收 changeId/并行度/重试参数。
3
+ * [OUTPUT]: 将 running 与可重试 failed 任务回置为 pending,并继续调度执行。
4
+ * [POS]: harness 恢复执行入口,被 CLI `harness:resume` 调用。
5
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
6
+ */
7
+
8
+ const { readJson, writeJson, getTaskManifestPath } = require('../store');
9
+ const { parseManifest } = require('../schemas');
10
+ const { runDispatch } = require('./dispatch');
11
+
12
+ async function runResume({ repoRoot, changeId, parallel, maxRetries }) {
13
+ const manifestPath = getTaskManifestPath(repoRoot, changeId);
14
+ const manifest = parseManifest(await readJson(manifestPath));
15
+
16
+ for (const task of manifest.tasks) {
17
+ if (task.status === 'running') {
18
+ task.status = 'pending';
19
+ task.lastError = 'Resumed from interrupted running state';
20
+ continue;
21
+ }
22
+
23
+ if (task.status === 'failed') {
24
+ const retryLimit = Number.isInteger(maxRetries) ? maxRetries : task.maxRetries;
25
+ if (task.attempts <= retryLimit) {
26
+ task.status = 'pending';
27
+ }
28
+ }
29
+ }
30
+
31
+ await writeJson(manifestPath, manifest);
32
+
33
+ return runDispatch({
34
+ repoRoot,
35
+ changeId,
36
+ parallel,
37
+ maxRetries,
38
+ resume: true
39
+ });
40
+ }
41
+
42
+ module.exports = {
43
+ runResume
44
+ };