@chenmk/superflow 0.5.6 → 0.5.7

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 (41) hide show
  1. package/README.md +12 -0
  2. package/README.zh-CN.md +10 -0
  3. package/assets/scripts/superflow-managed-work-check.mjs +286 -126
  4. package/assets/skills/superflow-pipeline/references/managed-work.md +5 -0
  5. package/assets/skills-en/superflow-pipeline/references/managed-work.md +7 -0
  6. package/dist/app/cli.js +12 -0
  7. package/dist/app/cli.js.map +1 -1
  8. package/dist/app/commands/cleanup.js +17 -0
  9. package/dist/app/commands/cleanup.js.map +1 -0
  10. package/dist/app/commands/pipeline.js +16 -5
  11. package/dist/app/commands/pipeline.js.map +1 -1
  12. package/dist/domains/config/cli-help.js +8 -0
  13. package/dist/domains/config/cli-help.js.map +1 -1
  14. package/dist/domains/managed-work/acceptance-contract.js +139 -0
  15. package/dist/domains/managed-work/acceptance-contract.js.map +1 -0
  16. package/dist/domains/managed-work/cleanup.js +247 -0
  17. package/dist/domains/managed-work/cleanup.js.map +1 -0
  18. package/dist/domains/managed-work/context-manifest.js +26 -0
  19. package/dist/domains/managed-work/context-manifest.js.map +1 -1
  20. package/dist/domains/managed-work/contract.js +36 -4
  21. package/dist/domains/managed-work/contract.js.map +1 -1
  22. package/dist/domains/managed-work/control.js +11 -5
  23. package/dist/domains/managed-work/control.js.map +1 -1
  24. package/dist/domains/managed-work/execution-contract.js +33 -4
  25. package/dist/domains/managed-work/execution-contract.js.map +1 -1
  26. package/dist/domains/managed-work/host-review.js +4 -0
  27. package/dist/domains/managed-work/host-review.js.map +1 -1
  28. package/dist/domains/managed-work/prompts.js +52 -0
  29. package/dist/domains/managed-work/prompts.js.map +1 -1
  30. package/dist/domains/managed-work/runner.js +12 -4
  31. package/dist/domains/managed-work/runner.js.map +1 -1
  32. package/dist/domains/managed-work/schemas.js +13 -7
  33. package/dist/domains/managed-work/schemas.js.map +1 -1
  34. package/dist/domains/managed-work/state.js +1 -0
  35. package/dist/domains/managed-work/state.js.map +1 -1
  36. package/dist/domains/managed-work/storage.js +68 -0
  37. package/dist/domains/managed-work/storage.js.map +1 -1
  38. package/dist/domains/managed-work/types.js.map +1 -1
  39. package/dist/mcp/server.js +20 -0
  40. package/dist/mcp/server.js.map +1 -1
  41. package/package.json +1 -1
@@ -1,99 +1,110 @@
1
1
  #!/usr/bin/env node
2
- import crypto from 'crypto';
3
- import fs from 'fs';
4
- import path from 'path';
2
+ import crypto from "crypto";
3
+ import fs from "fs";
4
+ import path from "path";
5
5
 
6
- const [projectArg = '.', taskId, mode] = process.argv.slice(2);
7
- const beforeReadyEvent = mode === '--before-ready-event';
6
+ const [projectArg = ".", taskId, mode] = process.argv.slice(2);
7
+ const beforeReadyEvent = mode === "--before-ready-event";
8
8
  if (!taskId) {
9
- console.error('用法: superflow-managed-work-check.mjs <project-root> <task-id>');
9
+ console.error(
10
+ "用法: superflow-managed-work-check.mjs <project-root> <task-id>",
11
+ );
10
12
  process.exit(2);
11
13
  }
12
14
 
13
15
  const projectRoot = path.resolve(projectArg);
14
- const taskDir = path.join(projectRoot, '.superflow', 'tasks', taskId);
15
- const taskFile = path.join(taskDir, 'task.json');
16
+ const taskDir = path.join(projectRoot, ".superflow", "tasks", taskId);
17
+ const taskFile = path.join(taskDir, "task.json");
16
18
  if (!fs.existsSync(taskFile)) fail(`缺少任务状态: ${taskFile}`);
17
- const task = JSON.parse(fs.readFileSync(taskFile, 'utf8'));
19
+ const task = JSON.parse(fs.readFileSync(taskFile, "utf8"));
18
20
  verifyContract(task);
19
- const runsDir = path.join(taskDir, 'runs');
21
+ const runsDir = path.join(taskDir, "runs");
20
22
  const runs = fs.existsSync(runsDir)
21
23
  ? fs.readdirSync(runsDir).filter((entry) => {
22
24
  const candidate = path.join(runsDir, entry);
23
25
  return (
24
- entry.startsWith('run-') &&
26
+ entry.startsWith("run-") &&
25
27
  fs.statSync(candidate).isDirectory() &&
26
- fs.existsSync(path.join(candidate, 'run-state.json'))
28
+ fs.existsSync(path.join(candidate, "run-state.json"))
27
29
  );
28
30
  })
29
31
  : [];
30
32
  if (runs.length !== 1) fail(`首版要求一个活动 run,实际 ${runs.length}`);
31
33
 
32
34
  const runDir = path.join(runsDir, runs[0]);
33
- const stateFile = path.join(runDir, 'run-state.json');
34
- const journalFile = path.join(runDir, 'progress.jsonl');
35
- const reportFile = path.join(runDir, 'task-report.md');
35
+ const stateFile = path.join(runDir, "run-state.json");
36
+ const journalFile = path.join(runDir, "progress.jsonl");
37
+ const reportFile = path.join(runDir, "task-report.md");
36
38
  for (const file of [stateFile, journalFile, reportFile]) {
37
39
  if (!fs.existsSync(file)) fail(`缺少必备运行产物: ${file}`);
38
40
  }
39
- const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
41
+ const state = JSON.parse(fs.readFileSync(stateFile, "utf8"));
40
42
 
41
43
  if (task.budgets?.unlimitedAgentInvocations !== true) {
42
- assertMax('reviewRound', state.reviewRound, 10);
44
+ assertMax("reviewRound", state.reviewRound, 10);
43
45
  assertMax(
44
- 'executorInvocations',
46
+ "executorInvocations",
45
47
  state.executorInvocations,
46
48
  20 + (state.executorInvocationCredits ?? 0),
47
49
  );
48
50
  assertMax(
49
- 'totalAgentInvocations',
51
+ "totalAgentInvocations",
50
52
  state.totalAgentInvocations,
51
53
  30 + (state.totalAgentInvocationCredits ?? 0),
52
54
  );
53
- assertMax('reviewInvocations', state.reviewInvocations, 10);
55
+ assertMax("reviewInvocations", state.reviewInvocations, 10);
54
56
  }
55
- if (state.contractHash !== task.contractHash) fail('任务合同哈希与运行状态不一致');
56
- if (state.status !== task.status) fail('任务合同状态与运行状态不一致');
57
+ if (state.contractHash !== task.contractHash)
58
+ fail("任务合同哈希与运行状态不一致");
59
+ if (state.status !== task.status) fail("任务合同状态与运行状态不一致");
57
60
  if (state.executorInvocations > 0 && !state.executorSession?.sessionId) {
58
- fail('已有执行调用但缺少执行会话编号');
61
+ fail("已有执行调用但缺少执行会话编号");
59
62
  }
60
63
  const events = verifyJournal(journalFile);
61
64
 
62
65
  const deliveryStatuses = new Set([
63
- 'local_delivery_ready',
64
- 'environment_validation_blocked',
65
- 'release_ready',
66
+ "local_delivery_ready",
67
+ "environment_validation_blocked",
68
+ "release_ready",
66
69
  ]);
67
70
  if (deliveryStatuses.has(state.status)) {
68
71
  if (!state.lastExecutorResult || !fs.existsSync(state.lastExecutorResult)) {
69
- fail('交付状态缺少最终执行结果');
72
+ fail("交付状态缺少最终执行结果");
70
73
  }
71
74
  if (!state.lastReviewResult || !fs.existsSync(state.lastReviewResult)) {
72
- fail('交付状态缺少最终检查结果');
75
+ fail("交付状态缺少最终检查结果");
73
76
  }
74
- assertInsideRun(state.lastExecutorResult, runDir, '最终执行结果');
75
- assertInsideRun(state.lastReviewResult, runDir, '最终检查结果');
76
- const executor = JSON.parse(fs.readFileSync(state.lastExecutorResult, 'utf8'));
77
- const review = JSON.parse(fs.readFileSync(state.lastReviewResult, 'utf8'));
77
+ assertInsideRun(state.lastExecutorResult, runDir, "最终执行结果");
78
+ assertInsideRun(state.lastReviewResult, runDir, "最终检查结果");
79
+ const executor = JSON.parse(
80
+ fs.readFileSync(state.lastExecutorResult, "utf8"),
81
+ );
82
+ const review = JSON.parse(fs.readFileSync(state.lastReviewResult, "utf8"));
83
+ verifyAcceptanceReview(task, runDir);
78
84
  const completion = verifyOpenSpecTasksComplete(task, executor);
79
85
  verifyDeliveryStatus(state.status, completion);
80
86
  if (
81
- task.source === 'sdd' &&
87
+ task.source === "sdd" &&
82
88
  !review.verificationCommands?.some(
83
89
  (item) =>
84
90
  item.exitCode === 0 &&
85
91
  /openspec\s+instructions\s+apply/i.test(item.command),
86
92
  )
87
93
  ) {
88
- fail('SDD 最终评审缺少 openspec instructions apply 命令证据');
94
+ fail("SDD 最终评审缺少 openspec instructions apply 命令证据");
89
95
  }
90
- if (review.result !== 'pass' || review.findings?.some((item) => item.blocking)) {
91
- fail('交付状态与最终检查结论不一致');
96
+ if (
97
+ review.result !== "pass" ||
98
+ review.findings?.some((item) => item.blocking)
99
+ ) {
100
+ fail("交付状态与最终检查结论不一致");
92
101
  }
93
102
  verifyExecutorEvidence(task, executor, review, runDir);
94
- const readyEvents = events.filter((event) => event.eventType === 'run.delivery_ready');
103
+ const readyEvents = events.filter(
104
+ (event) => event.eventType === "run.delivery_ready",
105
+ );
95
106
  const reopenedEvents = events.filter(
96
- (event) => event.eventType === 'run.delivery_reopened',
107
+ (event) => event.eventType === "run.delivery_reopened",
97
108
  );
98
109
  const expectedReadyEvents = beforeReadyEvent ? 0 : 1;
99
110
  if (readyEvents.length !== reopenedEvents.length + expectedReadyEvents) {
@@ -118,54 +129,83 @@ function verifyContract(contract) {
118
129
  (!Number.isInteger(budgets.executorPhysicalStopAt) ||
119
130
  budgets.executorPhysicalStopAt <= 0)
120
131
  ) {
121
- fail('executorPhysicalStopAt 必须是正整数');
132
+ fail("executorPhysicalStopAt 必须是正整数");
122
133
  }
123
- assertPositiveMax('maxReviewRounds', budgets.maxReviewRounds, 10, true);
124
- assertPositiveMax('maxExecutorInvocations', budgets.maxExecutorInvocations, 20, true);
125
- assertPositiveMax('maxTotalAgentInvocations', budgets.maxTotalAgentInvocations, 30, true);
126
- assertPositiveMax('maxActiveRunHours', budgets.maxActiveRunHours, 24, false);
127
- assertPositiveMax('maxSingleInvocationHours', budgets.maxSingleInvocationHours, 3, false);
134
+ assertPositiveMax("maxReviewRounds", budgets.maxReviewRounds, 10, true);
135
+ assertPositiveMax(
136
+ "maxExecutorInvocations",
137
+ budgets.maxExecutorInvocations,
138
+ 20,
139
+ true,
140
+ );
141
+ assertPositiveMax(
142
+ "maxTotalAgentInvocations",
143
+ budgets.maxTotalAgentInvocations,
144
+ 30,
145
+ true,
146
+ );
147
+ assertPositiveMax("maxActiveRunHours", budgets.maxActiveRunHours, 24, false);
148
+ assertPositiveMax(
149
+ "maxSingleInvocationHours",
150
+ budgets.maxSingleInvocationHours,
151
+ 3,
152
+ false,
153
+ );
128
154
  if (contract.supervisorAgent === contract.executorAgent) {
129
- fail('监督 Agent 和执行 Agent 不能相同');
155
+ fail("监督 Agent 和执行 Agent 不能相同");
130
156
  }
131
- if (contract.supervisorExecution !== 'external_host') {
132
- fail('监督执行模式只允许 external_host');
157
+ if (contract.supervisorExecution !== "external_host") {
158
+ fail("监督执行模式只允许 external_host");
133
159
  }
134
160
  const permissions = contract.permissions ?? {};
135
- for (const key of ['gitCommit', 'gitPush', 'productionWrites', 'bypassSandbox']) {
161
+ for (const key of [
162
+ "gitCommit",
163
+ "gitPush",
164
+ "productionWrites",
165
+ "bypassSandbox",
166
+ ]) {
136
167
  if (permissions[key] !== false) fail(`不可覆盖的权限边界被放宽: ${key}`);
137
168
  }
138
169
  const disclosure = permissions.externalModelDataDisclosure;
139
- if (!disclosure?.approved || !disclosure.approvedBy || !disclosure.approvedAt) {
140
- fail('缺少任务级外部研发 Agent 源码披露授权');
170
+ if (
171
+ !disclosure?.approved ||
172
+ !disclosure.approvedBy ||
173
+ !disclosure.approvedAt
174
+ ) {
175
+ fail("缺少任务级外部研发 Agent 源码披露授权");
141
176
  }
142
177
  if (!Array.isArray(disclosure.scope) || disclosure.scope.length === 0) {
143
- fail('外部研发 Agent 源码披露授权缺少仓库作用域');
178
+ fail("外部研发 Agent 源码披露授权缺少仓库作用域");
144
179
  }
145
180
  if (contract.taskPrompt) {
146
181
  const expectedSnapshot = path.join(
147
182
  path.resolve(contract.projectRoot),
148
- '.superflow',
149
- 'tasks',
183
+ ".superflow",
184
+ "tasks",
150
185
  contract.taskId,
151
- 'source-prompt.md',
186
+ "source-prompt.md",
152
187
  );
153
188
  if (path.resolve(contract.taskPrompt.snapshotPath) !== expectedSnapshot) {
154
- fail('冻结 Prompt 快照路径不属于当前托管任务');
189
+ fail("冻结 Prompt 快照路径不属于当前托管任务");
155
190
  }
156
- if (!fs.existsSync(expectedSnapshot)) fail('冻结 Prompt 快照不存在');
157
- const promptHash = crypto.createHash('sha256')
158
- .update(fs.readFileSync(expectedSnapshot, 'utf8'))
159
- .digest('hex');
160
- if (promptHash !== contract.taskPrompt.sha256) fail('冻结 Prompt 快照哈希不一致');
191
+ if (!fs.existsSync(expectedSnapshot)) fail("冻结 Prompt 快照不存在");
192
+ const promptHash = crypto
193
+ .createHash("sha256")
194
+ .update(fs.readFileSync(expectedSnapshot, "utf8"))
195
+ .digest("hex");
196
+ if (promptHash !== contract.taskPrompt.sha256)
197
+ fail("冻结 Prompt 快照哈希不一致");
161
198
  }
199
+ verifyAcceptanceContractSnapshot(contract);
162
200
  const immutable = {
163
201
  schemaVersion: contract.schemaVersion,
164
202
  taskId: contract.taskId,
165
203
  request: contract.request,
166
204
  source: contract.source,
167
205
  projectRoot: path.resolve(contract.projectRoot),
168
- relatedProjectRoots: contract.relatedProjectRoots.map((root) => path.resolve(root)),
206
+ relatedProjectRoots: contract.relatedProjectRoots.map((root) =>
207
+ path.resolve(root),
208
+ ),
169
209
  profile: contract.profile,
170
210
  ...(contract.language ? { language: contract.language } : {}),
171
211
  objective: contract.objective,
@@ -179,44 +219,133 @@ function verifyContract(contract) {
179
219
  ...(contract.supervisorExecution
180
220
  ? { supervisorExecution: contract.supervisorExecution }
181
221
  : {}),
182
- ...(contract.executionMode ? { executionMode: contract.executionMode } : {}),
222
+ ...(contract.executionMode
223
+ ? { executionMode: contract.executionMode }
224
+ : {}),
225
+ ...(contract.retention ? { retention: contract.retention } : {}),
226
+ ...(contract.taskKind ? { taskKind: contract.taskKind } : {}),
227
+ ...(contract.acceptanceContract
228
+ ? { acceptanceContract: contract.acceptanceContract }
229
+ : {}),
183
230
  permissions: contract.permissions,
184
231
  budgets: contract.budgets,
185
232
  };
186
- const actual = crypto.createHash('sha256').update(JSON.stringify(immutable)).digest('hex');
187
- if (actual !== contract.contractHash) fail('任务合同内容与冻结哈希不一致');
233
+ const actual = crypto
234
+ .createHash("sha256")
235
+ .update(JSON.stringify(immutable))
236
+ .digest("hex");
237
+ if (actual !== contract.contractHash) fail("任务合同内容与冻结哈希不一致");
238
+ }
239
+
240
+ function verifyAcceptanceContractSnapshot(contract) {
241
+ if (!contract.acceptanceContract) return;
242
+ const taskDir = path.join(
243
+ path.resolve(contract.projectRoot),
244
+ ".superflow",
245
+ "tasks",
246
+ contract.taskId,
247
+ );
248
+ const snapshot = path.join(taskDir, "acceptance-contract.json");
249
+ const projection = path.join(taskDir, "acceptance-contract.md");
250
+ if (!fs.existsSync(snapshot) || !fs.existsSync(projection)) {
251
+ fail("冻结验收合同快照或投影缺失");
252
+ }
253
+ const value = JSON.parse(fs.readFileSync(snapshot, "utf8"));
254
+ const acceptanceHash = crypto
255
+ .createHash("sha256")
256
+ .update(JSON.stringify(contract.acceptanceContract))
257
+ .digest("hex");
258
+ if (
259
+ value.schemaVersion !== 1 ||
260
+ value.taskId !== contract.taskId ||
261
+ value.contractHash !== contract.contractHash ||
262
+ value.acceptanceContractHash !== acceptanceHash ||
263
+ JSON.stringify(value.acceptanceContract) !==
264
+ JSON.stringify(contract.acceptanceContract)
265
+ ) {
266
+ fail("冻结验收合同快照与任务合同不一致");
267
+ }
268
+ }
269
+
270
+ function verifyAcceptanceReview(task, runDir) {
271
+ if (!task.acceptanceContract) return;
272
+ const firstReviewFile = path.join(runDir, "review-result-1.json");
273
+ if (!fs.existsSync(firstReviewFile)) {
274
+ fail("冻结验收合同缺少首轮 Host 评审结果");
275
+ }
276
+ const review = JSON.parse(fs.readFileSync(firstReviewFile, "utf8"));
277
+ const expected = acceptanceReferences(task.acceptanceContract);
278
+ const reviewed = review.acceptanceCoverage?.reviewed;
279
+ if (!Array.isArray(reviewed) || new Set(reviewed).size !== reviewed.length) {
280
+ fail("最终 Host 评审缺少唯一验收合同覆盖记录");
281
+ }
282
+ const actual = new Set(reviewed);
283
+ if (
284
+ expected.length !== actual.size ||
285
+ expected.some((reference) => !actual.has(reference))
286
+ ) {
287
+ fail("最终 Host 评审未覆盖全部冻结验收合同条目");
288
+ }
289
+ const known = new Set(expected);
290
+ for (const finding of review.findings ?? []) {
291
+ if (
292
+ !Array.isArray(finding.acceptanceContractRefs) ||
293
+ finding.acceptanceContractRefs.length === 0 ||
294
+ finding.acceptanceContractRefs.some((reference) => !known.has(reference))
295
+ ) {
296
+ fail(`finding ${finding.id ?? "unknown"} 未映射有效验收合同条目`);
297
+ }
298
+ }
299
+ }
300
+
301
+ function acceptanceReferences(contract) {
302
+ return [
303
+ ...contract.businessInvariants.map((_, index) => `invariant:${index + 1}`),
304
+ ...contract.sourceCoverage.map((_, index) => `source:${index + 1}`),
305
+ ...contract.deliverables.map((_, index) => `deliverable:${index + 1}`),
306
+ ...contract.verification.map((_, index) => `verification:${index + 1}`),
307
+ ...contract.exclusions.map((_, index) => `exclusion:${index + 1}`),
308
+ ];
188
309
  }
189
310
 
190
311
  function assertPositiveMax(name, value, max, integer) {
191
- if (!Number.isFinite(value) || value <= 0 || value > max || (integer && !Number.isInteger(value))) {
312
+ if (
313
+ !Number.isFinite(value) ||
314
+ value <= 0 ||
315
+ value > max ||
316
+ (integer && !Number.isInteger(value))
317
+ ) {
192
318
  fail(`${name}=${value} 超出硬上限或格式非法`);
193
319
  }
194
320
  }
195
321
 
196
322
  function verifyOpenSpecTasksComplete(task, executor) {
197
- if (task.source !== 'sdd' || !task.taskPrompt?.originalPath) {
323
+ if (task.source !== "sdd" || !task.taskPrompt?.originalPath) {
198
324
  return { localPending: [], environmentPending: [], releasePending: [] };
199
325
  }
200
326
  let current = path.dirname(path.resolve(task.taskPrompt.originalPath));
201
327
  const projectRoot = path.resolve(task.projectRoot);
202
328
  while (current.startsWith(projectRoot)) {
203
- const tasksFile = path.join(current, 'tasks.md');
329
+ const tasksFile = path.join(current, "tasks.md");
204
330
  if (fs.existsSync(tasksFile)) {
205
- const tasks = fs.readFileSync(tasksFile, 'utf8')
331
+ const tasks = fs
332
+ .readFileSync(tasksFile, "utf8")
206
333
  .split(/\r?\n/)
207
334
  .filter((line) => /^\s*- \[[ xX]\]/.test(line))
208
335
  .map((line, index) => parseCompletionTask(line, index));
209
336
  const localPending = tasks.filter(
210
- (item) => !item.completed && item.category === 'local_required',
337
+ (item) => !item.completed && item.category === "local_required",
211
338
  );
212
339
  const environmentPending = tasks.filter(
213
- (item) => !item.completed && item.category === 'environment_required',
340
+ (item) => !item.completed && item.category === "environment_required",
214
341
  );
215
342
  const releasePending = tasks.filter(
216
- (item) => !item.completed && item.category === 'release_required',
343
+ (item) => !item.completed && item.category === "release_required",
217
344
  );
218
345
  if (localPending.length > 0) {
219
- fail(`OpenSpec tasks.md 仍有 ${localPending.length} 个本地可执行任务未完成`);
346
+ fail(
347
+ `OpenSpec tasks.md 仍有 ${localPending.length} 个本地可执行任务未完成`,
348
+ );
220
349
  }
221
350
  const evidence = new Map(
222
351
  (executor.taskEvidence ?? []).map((item) => [item.taskId, item]),
@@ -227,11 +356,11 @@ function verifyOpenSpecTasksComplete(task, executor) {
227
356
  const derivedEvidencePaths = [
228
357
  ...(executor.evidence ?? []),
229
358
  tasksFile,
230
- path.join(current, 'test-report.md'),
359
+ path.join(current, "test-report.md"),
231
360
  ].filter((item) => taskEvidencePathExists(task, item));
232
361
  for (const item of tasks.filter(
233
362
  (taskItem) =>
234
- taskItem.completed && taskItem.category === 'local_required',
363
+ taskItem.completed && taskItem.category === "local_required",
235
364
  )) {
236
365
  const proof = evidence.get(item.taskId);
237
366
  if (successfulCommands.length > 0 && derivedEvidencePaths.length > 0) {
@@ -241,10 +370,12 @@ function verifyOpenSpecTasksComplete(task, executor) {
241
370
  fail(`已勾选本地任务 ${item.taskId} 缺少可推导的真实交付证据`);
242
371
  }
243
372
  if (
244
- proof.owner !== 'executor' ||
373
+ proof.owner !== "executor" ||
245
374
  !proof.evidencePaths?.length ||
246
375
  !proof.verificationCommands?.length ||
247
- !proof.evidencePaths.every((item) => taskEvidencePathExists(task, item)) ||
376
+ !proof.evidencePaths.every((item) =>
377
+ taskEvidencePathExists(task, item),
378
+ ) ||
248
379
  !proof.verificationCommands.every((command) =>
249
380
  executor.commands?.some(
250
381
  (item) =>
@@ -252,7 +383,9 @@ function verifyOpenSpecTasksComplete(task, executor) {
252
383
  verificationCommandCovered(command, item.command),
253
384
  ),
254
385
  ) ||
255
- !proof.changedFiles.every((file) => executor.changedFiles?.includes(file))
386
+ !proof.changedFiles.every((file) =>
387
+ executor.changedFiles?.includes(file),
388
+ )
256
389
  ) {
257
390
  fail(`已勾选本地任务 ${item.taskId} 缺少结构化交付证据`);
258
391
  }
@@ -272,7 +405,7 @@ function verifyOpenSpecTasksComplete(task, executor) {
272
405
  if (parent === current) break;
273
406
  current = parent;
274
407
  }
275
- fail('SDD 托管任务未找到 OpenSpec tasks.md');
408
+ fail("SDD 托管任务未找到 OpenSpec tasks.md");
276
409
  }
277
410
 
278
411
  function isAcceptedEvidenceCommand(command) {
@@ -302,7 +435,7 @@ function verificationCommandCovered(expected, actual) {
302
435
  function mavenTestSelectors(command) {
303
436
  if (!/(?:^|\s)(?:\.\/)?mvn(?:\s|$)/.test(command)) return [];
304
437
  const match = command.match(/-Dtest=([^\s]+)/);
305
- return match?.[1].split(',').filter(Boolean) ?? [];
438
+ return match?.[1].split(",").filter(Boolean) ?? [];
306
439
  }
307
440
 
308
441
  function taskEvidencePathExists(task, evidencePath) {
@@ -325,7 +458,7 @@ function findTaskDirectory(task) {
325
458
  (root) => path.resolve(root),
326
459
  );
327
460
  while (roots.some((root) => current.startsWith(root))) {
328
- if (fs.existsSync(path.join(current, 'tasks.md'))) return current;
461
+ if (fs.existsSync(path.join(current, "tasks.md"))) return current;
329
462
  const parent = path.dirname(current);
330
463
  if (parent === current) break;
331
464
  current = parent;
@@ -335,61 +468,70 @@ function findTaskDirectory(task) {
335
468
 
336
469
  function parseCompletionTask(line, index) {
337
470
  const completed = /^\s*- \[[xX]\]/.test(line);
338
- const text = line.replace(/^\s*- \[[ xX]\]\s*/, '').trim();
471
+ const text = line.replace(/^\s*- \[[ xX]\]\s*/, "").trim();
339
472
  const match = text.match(/^([A-Za-z]*\d+(?:\.\d+)*|[A-Z][A-Z0-9_-]+)\b/);
340
473
  const category = /\[environment_required\]/i.test(text)
341
- ? 'environment_required'
474
+ ? "environment_required"
342
475
  : /\[release_required\]/i.test(text)
343
- ? 'release_required'
344
- : 'local_required';
476
+ ? "release_required"
477
+ : "local_required";
345
478
  return { taskId: match?.[1] ?? `task-${index + 1}`, completed, category };
346
479
  }
347
480
 
348
481
  function verifyDeliveryStatus(status, completion) {
349
- if (status === 'environment_validation_blocked' && completion.environmentPending.length === 0) {
350
- fail('environment_validation_blocked 状态没有待完成环境验收');
482
+ if (
483
+ status === "environment_validation_blocked" &&
484
+ completion.environmentPending.length === 0
485
+ ) {
486
+ fail("environment_validation_blocked 状态没有待完成环境验收");
351
487
  }
352
- if (status === 'local_delivery_ready' && completion.environmentPending.length > 0) {
353
- fail('local_delivery_ready 状态仍有环境验收未完成');
488
+ if (
489
+ status === "local_delivery_ready" &&
490
+ completion.environmentPending.length > 0
491
+ ) {
492
+ fail("local_delivery_ready 状态仍有环境验收未完成");
354
493
  }
355
494
  if (
356
- status === 'release_ready' &&
495
+ status === "release_ready" &&
357
496
  completion.environmentPending.length + completion.releasePending.length > 0
358
497
  ) {
359
- fail('release_ready 状态仍有环境或发布任务未完成');
498
+ fail("release_ready 状态仍有环境或发布任务未完成");
360
499
  }
361
500
  }
362
501
 
363
502
  function verifyExecutorEvidence(task, result, review, runDir) {
364
- if (!['ready_for_review', 'blocked'].includes(result.status)) {
365
- fail('最终执行结果既不可检查也不是诚实阻塞状态');
503
+ if (!["ready_for_review", "blocked"].includes(result.status)) {
504
+ fail("最终执行结果既不可检查也不是诚实阻塞状态");
366
505
  }
367
- if (!Array.isArray(result.commands)) fail('最终执行结果缺少命令证据');
368
- if (result.status === 'blocked' && review.result !== 'pass') {
369
- fail('执行者阻塞结果未经监督者判定为非阻断发布前置');
506
+ if (!Array.isArray(result.commands)) fail("最终执行结果缺少命令证据");
507
+ if (result.status === "blocked" && review.result !== "pass") {
508
+ fail("执行者阻塞结果未经监督者判定为非阻断发布前置");
370
509
  }
371
- if (result.status === 'ready_for_review' && result.blockers?.length > 0) {
372
- fail('可检查执行结果仍含阻塞项');
510
+ if (result.status === "ready_for_review" && result.blockers?.length > 0) {
511
+ fail("可检查执行结果仍含阻塞项");
373
512
  }
374
- if (task.profile === 'engineering' || task.profile === 'sdd') {
375
- const successful = loadExecutorCommands(runDir)
376
- .filter((item) => item.exitCode === 0);
513
+ if (task.profile === "engineering" || task.profile === "sdd") {
514
+ const successful = loadExecutorCommands(runDir).filter(
515
+ (item) => item.exitCode === 0,
516
+ );
377
517
  const categories = new Set(
378
518
  successful.flatMap((item) => verificationCategories(item.command)),
379
519
  );
380
520
  const required = requiredVerificationCategories(projectRoot);
381
- if (categories.size < required) fail('工程任务缺少至少两类成功验证证据');
521
+ if (categories.size < required) fail("工程任务缺少至少两类成功验证证据");
382
522
  }
383
523
  }
384
524
 
385
525
  function requiredVerificationCategories(root) {
386
- const packageFile = path.join(root, 'package.json');
526
+ const packageFile = path.join(root, "package.json");
387
527
  if (!fs.existsSync(packageFile)) return 2;
388
528
  try {
389
- const scripts = JSON.parse(fs.readFileSync(packageFile, 'utf8')).scripts ?? {};
390
- const hasTest = typeof scripts.test === 'string';
391
- const hasRunnableTarget = ['build', 'start', 'dev', 'serve']
392
- .some((name) => typeof scripts[name] === 'string');
529
+ const scripts =
530
+ JSON.parse(fs.readFileSync(packageFile, "utf8")).scripts ?? {};
531
+ const hasTest = typeof scripts.test === "string";
532
+ const hasRunnableTarget = ["build", "start", "dev", "serve"].some(
533
+ (name) => typeof scripts[name] === "string",
534
+ );
393
535
  return hasTest && !hasRunnableTarget ? 1 : 2;
394
536
  } catch {
395
537
  return 2;
@@ -397,51 +539,69 @@ function requiredVerificationCategories(root) {
397
539
  }
398
540
 
399
541
  function loadExecutorCommands(runDir) {
400
- return fs.readdirSync(runDir)
542
+ return fs
543
+ .readdirSync(runDir)
401
544
  .filter((entry) => /^executor-result-\d+\.json$/.test(entry))
402
545
  .flatMap((entry) => {
403
546
  const result = JSON.parse(
404
- fs.readFileSync(path.join(runDir, entry), 'utf8'),
547
+ fs.readFileSync(path.join(runDir, entry), "utf8"),
405
548
  );
406
549
  return Array.isArray(result.commands) ? result.commands : [];
407
550
  });
408
551
  }
409
552
 
410
- function verificationCategories(command = '') {
553
+ function verificationCategories(command = "") {
411
554
  const value = command.toLowerCase();
412
555
  const categories = [];
413
- if (/(^|\s)(test|vitest|jest|pytest|phpunit|go\s+test|cargo\s+test)(\s|$)/.test(value)
414
- || /(?:npm|pnpm|yarn)\s+(?:run\s+)?test/.test(value)
415
- || /mvn(?:\s+[^\s]+)*\s+test/.test(value)
416
- || /gradle\w*\s+test/.test(value)) categories.push('test');
417
- if (/(?:npm|pnpm|yarn)\s+(?:run\s+)?build/.test(value)
418
- || /(?:^|[\s/])(?:vite|vue-cli-service)\s+build(?:\s|$)/.test(value)
419
- || /(^|\s)(tsc|make|cmake|compile)(\s|$)/.test(value)
420
- || /mvn(?:\s+[^\s]+)*\s+(?:compile|package|verify)/.test(value)
421
- || /gradle\w*\s+(?:build|assemble)/.test(value)
422
- || /cargo\s+build|go\s+build/.test(value)) categories.push('build');
423
- if (/(?:npm|pnpm|yarn)\s+(?:run\s+)?(?:start|dev|serve)/.test(value)
424
- || /spring-boot:run|docker\s+compose\s+up|(^|\s)(curl|wget)(\s|$)/.test(value)
425
- || /health(?:check)?|integration|e2e/.test(value)) categories.push('runtime');
556
+ if (
557
+ /(^|\s)(test|vitest|jest|pytest|phpunit|go\s+test|cargo\s+test)(\s|$)/.test(
558
+ value,
559
+ ) ||
560
+ /(?:npm|pnpm|yarn)\s+(?:run\s+)?test/.test(value) ||
561
+ /mvn(?:\s+[^\s]+)*\s+test/.test(value) ||
562
+ /gradle\w*\s+test/.test(value)
563
+ )
564
+ categories.push("test");
565
+ if (
566
+ /(?:npm|pnpm|yarn)\s+(?:run\s+)?build/.test(value) ||
567
+ /(?:^|[\s/])(?:vite|vue-cli-service)\s+build(?:\s|$)/.test(value) ||
568
+ /(^|\s)(tsc|make|cmake|compile)(\s|$)/.test(value) ||
569
+ /mvn(?:\s+[^\s]+)*\s+(?:compile|package|verify)/.test(value) ||
570
+ /gradle\w*\s+(?:build|assemble)/.test(value) ||
571
+ /cargo\s+build|go\s+build/.test(value)
572
+ )
573
+ categories.push("build");
574
+ if (
575
+ /(?:npm|pnpm|yarn)\s+(?:run\s+)?(?:start|dev|serve)/.test(value) ||
576
+ /spring-boot:run|docker\s+compose\s+up|(^|\s)(curl|wget)(\s|$)/.test(
577
+ value,
578
+ ) ||
579
+ /health(?:check)?|integration|e2e/.test(value)
580
+ )
581
+ categories.push("runtime");
426
582
  return categories;
427
583
  }
428
584
 
429
585
  function assertInsideRun(file, runDir, label) {
430
586
  const relative = path.relative(path.resolve(runDir), path.resolve(file));
431
- if (relative.startsWith('..') || path.isAbsolute(relative)) {
587
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
432
588
  fail(`${label}不在当前运行目录内`);
433
589
  }
434
590
  }
435
591
 
436
592
  function verifyJournal(file) {
437
- const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/).filter(Boolean);
593
+ const lines = fs.readFileSync(file, "utf8").split(/\r?\n/).filter(Boolean);
438
594
  let previous = null;
439
595
  const events = lines.map((line, index) => {
440
596
  const event = JSON.parse(line);
441
597
  const { eventHash, ...base } = event;
442
- const expected = crypto.createHash('sha256').update(JSON.stringify(base)).digest('hex');
598
+ const expected = crypto
599
+ .createHash("sha256")
600
+ .update(JSON.stringify(base))
601
+ .digest("hex");
443
602
  if (event.sequence !== index + 1) fail(`事件序号断裂: ${event.sequence}`);
444
- if (event.previousEventHash !== previous) fail(`事件前序哈希不一致: ${event.sequence}`);
603
+ if (event.previousEventHash !== previous)
604
+ fail(`事件前序哈希不一致: ${event.sequence}`);
445
605
  if (eventHash !== expected) fail(`事件哈希不一致: ${event.sequence}`);
446
606
  previous = eventHash;
447
607
  return event;