@guanlin4924/devflow-pi 0.1.105 → 0.1.106

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 (62) hide show
  1. package/README.md +10 -8
  2. package/package.json +1 -1
  3. package/plugins/devflow-core/extensions/devflow-core.ts +105 -305
  4. package/plugins/devflow-core/runtime/hooks/context_sync_gate.py +3 -1
  5. package/plugins/devflow-core/runtime/hooks/hook_common.py +6 -5
  6. package/plugins/devflow-core/runtime/hooks/index_gate.py +19 -26
  7. package/plugins/devflow-core/skills/wireframe-proposal/SKILL.md +2 -0
  8. package/plugins/devflow-core/skills/wireframe-render/SKILL.md +2 -0
  9. package/plugins/devflow-core/skills/wireframe-review/SKILL.md +2 -0
  10. package/plugins/devflow-core/skills/wireframe-validate/SKILL.md +2 -0
  11. package/plugins/devflow-core/skills/workflow-orchestrator/SKILL.md +34 -50
  12. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +40 -191
  13. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/artifact_specs.rb +12 -0
  14. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_contract.rb +38 -1
  15. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/gate_policy.rb +11 -0
  16. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/gate_result.rb +12 -0
  17. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/gate_runtime.rb +4 -9
  18. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/inspect_state.rb +13 -86
  19. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/parallel_recover.rb +24 -13
  20. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/repair_control_plane.rb +4 -4
  21. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/sync_artifacts.rb +3 -1
  22. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +8 -39
  23. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/wireframe_commit.rb +39 -3
  24. package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +2 -3
  25. package/plugins/devflow-core/skills/workflow-orchestrator/templates/regression-acceptance.yaml +0 -1
  26. package/plugins/devflow-core/skills/workflow-pipeline/SKILL.md +10 -5
  27. package/plugins/devflow-core/skills/workflow-pipeline/references/pipeline-flow.md +1 -1
  28. package/plugins/devflow-core/skills/workflow-project-context/SKILL.md +2 -0
  29. package/plugins/devflow-design/skills/workflow-api-call-guide/SKILL.md +2 -0
  30. package/plugins/devflow-design/skills/workflow-api-contract/SKILL.md +3 -1
  31. package/plugins/devflow-design/skills/workflow-api-supplement/SKILL.md +2 -0
  32. package/plugins/devflow-design/skills/workflow-architecture-feasibility/SKILL.md +2 -0
  33. package/plugins/devflow-design/skills/workflow-backend-tech-spec/SKILL.md +6 -4
  34. package/plugins/devflow-design/skills/workflow-design-rule-pack/SKILL.md +2 -0
  35. package/plugins/devflow-design/skills/workflow-feature-breakdown/SKILL.md +2 -0
  36. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/SKILL.md +6 -4
  37. package/plugins/devflow-design/skills/workflow-integration-verify/SKILL.md +3 -1
  38. package/plugins/devflow-design/skills/workflow-ui-spec/SKILL.md +4 -2
  39. package/plugins/devflow-implementation/agents/implementation-worker.md +2 -8
  40. package/plugins/devflow-implementation/skills/workflow-bug-fix/SKILL.md +2 -0
  41. package/plugins/devflow-implementation/skills/workflow-change-plan/SKILL.md +3 -1
  42. package/plugins/devflow-implementation/skills/workflow-execute-phase/SKILL.md +3 -1
  43. package/plugins/devflow-intel/skills/code-index-query/SKILL.md +2 -0
  44. package/plugins/devflow-intel/skills/workflow-code-index/SKILL.md +2 -0
  45. package/plugins/devflow-intel/skills/workflow-context-sync/SKILL.md +2 -0
  46. package/plugins/devflow-intel/skills/workflow-update-navigation/SKILL.md +3 -1
  47. package/plugins/devflow-quality/skills/code-review/SKILL.md +4 -2
  48. package/plugins/devflow-quality/skills/workflow-acceptance-test-design/SKILL.md +3 -1
  49. package/plugins/devflow-quality/skills/workflow-gen-test-guide/SKILL.md +2 -0
  50. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/SKILL.md +2 -0
  51. package/plugins/devflow-quality/skills/workflow-human-acceptance/SKILL.md +2 -0
  52. package/plugins/devflow-quality/skills/workflow-regression-test-design/SKILL.md +2 -0
  53. package/plugins/devflow-quality/skills/workflow-release-closeout/SKILL.md +7 -5
  54. package/plugins/devflow-quality/skills/workflow-run-acceptance-tests/SKILL.md +8 -6
  55. package/plugins/devflow-requirements/skills/requirement-feasibility-review/SKILL.md +2 -0
  56. package/plugins/devflow-requirements/skills/workflow-demand-clarify/SKILL.md +2 -0
  57. package/plugins/devflow-requirements/skills/workflow-domain-modeling/SKILL.md +2 -0
  58. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/SKILL.md +6 -2
  59. package/plugins/devflow-requirements/skills/workflow-requirement-triage/SKILL.md +2 -0
  60. package/plugins/devflow-requirements/skills/workflow-wayfinder/SKILL.md +2 -0
  61. package/src/constants.ts +1 -1
  62. package/templates/omp/AGENTS.md +6 -2
@@ -233,12 +233,13 @@ type ActiveExecutionContract = {
233
233
  stateRevision?: number;
234
234
  graphRevision?: number;
235
235
  workflowFingerprint?: string;
236
+ scope?: string;
237
+ branchContracts?: unknown[];
236
238
  allowedPaths: string[];
237
239
  allowedAgents: string[];
238
240
  allowedTools: string[];
239
241
  checkerIds: string[];
240
242
  toolBudget: number;
241
- hardGateMode: "chain" | "core";
242
243
  frozenHash: string;
243
244
  contractPath?: string;
244
245
  acceptance: string[];
@@ -256,12 +257,13 @@ function contractFromRecord(record: Record<string, unknown>): ActiveExecutionCon
256
257
  stateRevision: typeof record.state_revision === "number" ? record.state_revision : undefined,
257
258
  graphRevision: typeof record.graph_revision === "number" ? record.graph_revision : undefined,
258
259
  workflowFingerprint: typeof record.workflow_fingerprint === "string" ? record.workflow_fingerprint : undefined,
260
+ scope: typeof record.scope === "string" ? record.scope : undefined,
261
+ branchContracts: Array.isArray(record.branch_contracts) ? record.branch_contracts : undefined,
259
262
  allowedPaths: strings("allowed_paths"),
260
263
  allowedAgents: strings("allowed_agents"),
261
264
  allowedTools: strings("allowed_tools"),
262
265
  checkerIds: strings("checker_ids"),
263
266
  toolBudget: typeof record.tool_budget === "number" ? record.tool_budget : 0,
264
- hardGateMode: record.hard_gate_mode === "core" ? "core" : "chain",
265
267
  frozenHash: String(record.frozen_hash ?? ""),
266
268
  acceptance: strings("acceptance"),
267
269
  allowedGraphDelta: strings("allowed_graph_delta"),
@@ -271,10 +273,6 @@ function contractFromRecord(record: Record<string, unknown>): ActiveExecutionCon
271
273
  return contract;
272
274
  }
273
275
 
274
- function coreHardGatesEnabled(contract: ActiveExecutionContract | undefined): boolean {
275
- return contract?.hardGateMode === "core";
276
- }
277
-
278
276
  const READ_ONLY_TOOLS = new Set([
279
277
  "read",
280
278
  "grep",
@@ -1316,7 +1314,7 @@ function selectedWorkflowStep(snapshot: Record<string, unknown>): Record<string,
1316
1314
  * completed step. This predicate lets the runtime re-arm approval handling
1317
1315
  * from the Core-owned snapshot without granting approval by itself.
1318
1316
  */
1319
- function isReviewableWorkflowSnapshot(snapshot: Record<string, unknown>, options: { strictRegistry?: boolean } = {}): boolean {
1317
+ function isReviewableWorkflowSnapshot(snapshot: Record<string, unknown>, options: { strictRegistry?: boolean; allowAdvisory?: boolean } = {}): boolean {
1320
1318
  const status = snapshot.status;
1321
1319
  if (status !== "waiting_checkpoint" && status !== "not_started" && status !== "in_progress") return false;
1322
1320
  const step = snapshot.current_step;
@@ -1326,7 +1324,8 @@ function isReviewableWorkflowSnapshot(snapshot: Record<string, unknown>, options
1326
1324
  // Core-generated quality findings are advisory by default. Re-arm the AI
1327
1325
  // review even when an output is stale or its generated validity metadata is
1328
1326
  // incomplete; only the control-plane shape itself remains a hard boundary.
1329
- if (process.env.DEVFLOW_SOFT_GATES !== "0" && snapshot.action === "await_user_confirmation") return true;
1327
+ if (snapshot.action !== "run_parallel_window" &&
1328
+ (options.allowAdvisory || snapshot.action === "await_user_confirmation")) return true;
1330
1329
  const outputs = Array.isArray(stepRecord.outputs) ? stepRecord.outputs : [];
1331
1330
  const requiredOutputs = outputs.filter(output => {
1332
1331
  if (!output || typeof output !== "object") return false;
@@ -1386,7 +1385,7 @@ export function workflowResumeDisposition(snapshot: Record<string, unknown>): Wo
1386
1385
  return "blocked";
1387
1386
  }
1388
1387
 
1389
- function workflowDirective(snapshot: Record<string, unknown>, source: "start" | "approval", requirementDraft?: string, briefing?: string): string {
1388
+ export function workflowDirective(snapshot: Record<string, unknown>, source: "start" | "approval", requirementDraft?: string, briefing?: string): string {
1390
1389
  const selectedStep = selectedWorkflowStep(snapshot);
1391
1390
  const reviewInstructions = selectedStep?.id === "code-review"
1392
1391
  ? [
@@ -1407,6 +1406,7 @@ function workflowDirective(snapshot: Record<string, unknown>, source: "start" |
1407
1406
  checkpoint: snapshot.checkpoint,
1408
1407
  step: selectedStep,
1409
1408
  parallel_window: snapshot.parallel_window,
1409
+ parallel_run: snapshot.parallel_run,
1410
1410
  failure_branch: snapshot.failure_branch,
1411
1411
  reason: snapshot.reason,
1412
1412
  recovery: snapshot.recovery,
@@ -1414,7 +1414,8 @@ function workflowDirective(snapshot: Record<string, unknown>, source: "start" |
1414
1414
  };
1415
1415
  const parallelInstructions = snapshot.action === "run_parallel_window"
1416
1416
  ? [
1417
- "这是并行窗口:先按 parallel_run 中的 branch 记录逐个调用 task;一个 task 只能负责一个 workflow_step,不得把多个 step 合并到一个子 Agent。",
1417
+ "当前合同 scope=parallel_window:current_step 只是汇合锚点,不要执行 integration-verify 或读取它的 Skill;按 parallel_window.branch_steps 中当前可运行的分支读取各自精确 skill,并遵守合同 branch_contracts 的逐分支权限。",
1418
+ "这是并行窗口:仅派发 parallel_window.branch_steps 中当前可运行的分支,并从 parallel_run 中匹配 branch 记录逐个调用 task;一个 task 只能负责一个 workflow_step,不得把多个 step 合并到一个子 Agent。",
1418
1419
  "本窗口的 branch step(backend-tech-spec、api-call-guide、ui-spec、feature-breakdown、frontend-tech-spec、api-supplement)统一使用白名单 agent implementation-worker;不要把 workflow step 名称当作 agent 名称。",
1419
1420
  "只有执行验证类步骤才使用 verification-runner;project-scout、requirements-auditor、contract-auditor、devflow-code-reviewer 不属于本并行窗口的默认执行 agent。",
1420
1421
  "每个 task 的提示必须原样包含 DEVFLOW_BRANCH_ID、DEVFLOW_WORKFLOW_STEP、DEVFLOW_TRACK、DEVFLOW_ATTEMPT 和 DEVFLOW_BRANCH_REVISION,并只写该分支的白名单产物。",
@@ -1424,16 +1425,15 @@ function workflowDirective(snapshot: Record<string, unknown>, source: "start" |
1424
1425
  : [];
1425
1426
  return [
1426
1427
  "<devflow-runtime-directive>",
1427
- briefing
1428
- ? "Devflow Runtime 已完成开工预检(环境、交接、状态、合同、输入),以下开工简报中的事实已经核实;执行合同已激活,不要再次调用 devflow_state inspect 或 devflow_contract activate。读取 skill://workflow-pipeline,并只执行当前项目的一个工作流步骤。"
1429
- : "Devflow Runtime 已完成环境与状态检查。读取 skill://workflow-pipeline,并只执行当前项目的一个工作流步骤。",
1428
+ ...(["当前为全软门禁模式:产物、索引、合同、质量、审查和恢复检查只提供诊断。缺失输入先利用已有证据尽力完成,未解决项写入报告;不得因检查未通过而停止整个流程,不得伪造测试通过或人工批准。CP 节点不强制审批,用户要求下一步即可继续。真实工具执行失败仍须如实报告。"]),
1429
+ "读取 skill://workflow-pipeline,按当前步骤或并行分支执行;开工简报、执行合同与 Skill 中的门禁要求在本模式下均为建议。",
1430
1430
  ...(briefing
1431
1431
  ? ["<startup-briefing>", briefing, "</startup-briefing>"]
1432
- : ["先调用 devflow_state(action=\"inspect\") 重新确认 revision,再调用 devflow_contract(action=\"status\");若没有匹配当前 feature/current_step 的 fresh contract,立即调用 devflow_contract(action=\"activate\", feature=<current_feature>, step=<current_step>),激活成功后才能执行或写入。"]),
1433
- "只按 inspector 返回的当前 step 加载所需 Skill:step.skill 是精确 ID,必须读取 skill://<step.skill>,不得自行添加 workflow- 前缀或改写名称;本回合严禁读取或写入 xd://devflow_transition,严禁调用 devflow_transition,严禁执行 Skill 中的 complete-step/confirm-checkpoint,不得推进下一步骤;状态写回和会话切换由 Runtime 在用户确认后独占处理。",
1434
- ...reviewInstructions,
1432
+ : ["可按需查看状态与合同;缺失或过期合同不阻止执行。"]),
1433
+ "串行步骤按 inspector.current_step,并行窗口按可运行分支加载所需 Skill:step.skill 是精确 ID,必须读取 skill://<step.skill>,不得自行添加 workflow- 前缀或改写名称;本回合严禁读取或写入 xd://devflow_transition,严禁调用 devflow_transition,严禁执行 Skill 中的 complete-step/confirm-checkpoint,不得推进下一步骤;状态写回和会话切换由 Runtime 在用户确认后独占处理。",
1434
+ ...([]),
1435
1435
  ...(parallelInstructions.length > 0 ? parallelInstructions : [
1436
- "完成本步骤的真实产物后,报告证据、未决项和验证结果并停止。请用户审查后明确回复“批准执行”才可推进;回复修改意见或“拒绝执行”则不得推进。",
1436
+ "完成当前步骤后报告证据、未决项和检查警告。用户要求下一步即可继续;不要把质量检查失败当作流程阻塞,也不要自报未执行的检查已通过。",
1437
1437
  ]),
1438
1438
  "当前步骤信封:",
1439
1439
  JSON.stringify(envelope, null, 2),
@@ -2160,7 +2160,12 @@ export default function devflowCore(pi: ExtensionAPI) {
2160
2160
  graph && typeof graph === "object" && typeof (graph as Record<string, unknown>).revision === "number"
2161
2161
  ? (graph as Record<string, number>).revision
2162
2162
  : undefined;
2163
- return stateRevision === contract.stateRevision && graphRevision === contract.graphRevision;
2163
+ if (stateRevision !== contract.stateRevision || graphRevision !== contract.graphRevision) return false;
2164
+ const inspected = parseJsonResult(await runRuby(cwd, "workflow-orchestrator/scripts/execution_contract.rb", [
2165
+ "inspect", "--feature", contract.feature, "--step", contract.step, "--json",
2166
+ ]));
2167
+ const current = inspected?.contract as Record<string, unknown> | undefined;
2168
+ return current?.frozen_hash === contract.frozenHash;
2164
2169
  }
2165
2170
 
2166
2171
  // A successful graph mutation is already constrained by the active contract's
@@ -2254,22 +2259,6 @@ export default function devflowCore(pi: ExtensionAPI) {
2254
2259
  }
2255
2260
  const contract = contractFromRecord(raw as Record<string, unknown>);
2256
2261
  if (!contract) return { ok: false, reason: "Core returned an invalid execution contract." };
2257
- const confirmed = await ctx.ui.confirm(
2258
- "激活 Devflow 执行合同",
2259
- [
2260
- `合同:${contract.contractId}`,
2261
- `Feature:${contract.feature}`,
2262
- `步骤:${contract.step}`,
2263
- `STATE revision:${contract.stateRevision ?? "未初始化"}`,
2264
- `图谱 revision:${contract.graphRevision ?? 0}`,
2265
- `可写路径:${contract.allowedPaths.join(", ")}`,
2266
- `允许 Agent:${contract.allowedAgents.join(", ")}`,
2267
- `固定检查器:${contract.checkerIds.join(", ") || "无"}`,
2268
- `冻结哈希:${contract.frozenHash}`,
2269
- "确认后低成本模型只能在以上范围执行;未知项必须阻塞升级。",
2270
- ].join("\n"),
2271
- );
2272
- if (!confirmed) return { ok: false, reason: "User declined execution contract activation." };
2273
2262
  try {
2274
2263
  const createArgs = ["create", "--feature", feature, "--step", step, "--write", "--json"];
2275
2264
  if (contract.stateRevision !== undefined) createArgs.push("--expected-state-revision", String(contract.stateRevision));
@@ -2618,80 +2607,17 @@ export default function devflowCore(pi: ExtensionAPI) {
2618
2607
  snapshot: Record<string, unknown>,
2619
2608
  options: { doctorAlreadyPassed?: boolean; handoffPath?: string } = {},
2620
2609
  ): Promise<{ ok: true; briefing?: string } | { ok: false }> {
2621
- if (options.doctorAlreadyPassed !== true) {
2622
- const report = await runDoctor(ctx);
2623
- if (!report.ok) {
2624
- setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "开工预检失败" });
2625
- ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "error");
2626
- return { ok: false };
2627
- }
2628
- }
2629
- if (workflowResumeDisposition(snapshot) !== "execute" || snapshot.action === "run_parallel_window") {
2630
- return { ok: true };
2631
- }
2632
-
2633
- try {
2634
- const feature = typeof snapshot.current_feature === "string" ? snapshot.current_feature : "";
2635
- const step = String(selectedWorkflowStep(snapshot)?.id ?? "");
2636
- if (!feature || !step) {
2637
- setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "开工预检未通过" });
2638
- ctx.ui.notify("开工预检未通过:当前 Feature 或步骤不可用。", "error");
2639
- return { ok: false };
2640
- }
2641
- let contract = activeContract;
2642
- if (!contract || contract.feature !== feature || contract.step !== step || !(await contractIsFresh(ctx.cwd, contract))) {
2643
- const activated = await activateExecutionContract(ctx, feature, step);
2644
- if (!activated.ok) {
2645
- setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "开工预检:执行合同未激活" });
2646
- ctx.ui.notify(`开工预检:执行合同未激活。${activated.reason}`, "error");
2647
- return { ok: false };
2648
- }
2649
- contract = activated.contract;
2650
- }
2651
- if (!contract.contractPath) {
2652
- setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "开工预检:执行合同未激活" });
2653
- ctx.ui.notify("开工预检:执行合同未激活。合同冻结路径不可用。", "error");
2654
- return { ok: false };
2655
- }
2656
- const contractAbsolute = path.resolve(ctx.cwd, contract.contractPath);
2657
- const contractRelative = path.relative(ctx.cwd, contractAbsolute).split(path.sep).join("/");
2658
- if (!contractRelative || contractRelative === ".." || contractRelative.startsWith("../")) {
2659
- setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "开工预检:执行合同未激活" });
2660
- ctx.ui.notify("开工预检:执行合同未激活。合同冻结路径越出项目。", "error");
2661
- return { ok: false };
2662
- }
2663
- const preflight = await runPython(ctx.cwd, "workflow-orchestrator/scripts/startup_preflight.py", [
2664
- "--root", ctx.cwd,
2665
- "--feature", feature,
2666
- "--step", step,
2667
- "--contract", contractRelative,
2668
- ...(options.handoffPath ? ["--handoff", options.handoffPath] : []),
2669
- ]);
2670
- const payload = parseJsonOutput(preflight);
2671
- if (!payload || payload.status !== "ready") {
2672
- setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "开工预检未通过" });
2610
+ const feature = String(snapshot.current_feature ?? "");
2611
+ const step = String(selectedWorkflowStep(snapshot)?.id ?? "");
2612
+ const diagnostics = await runPython(ctx.cwd, "workflow-orchestrator/scripts/startup_preflight.py", [
2613
+ "--root", ctx.cwd, "--feature", feature, "--step", step,
2614
+ ...(options.handoffPath ? ["--handoff", options.handoffPath] : []),
2615
+ ]);
2616
+ const payload = parseJsonOutput(diagnostics);
2673
2617
  const issues = Array.isArray(payload?.issues) ? payload.issues : [];
2674
- const details = issues.length > 0
2675
- ? issues.map(issue => `- ${describeStartupIssue(issue && typeof issue === "object" ? issue as Record<string, unknown> : {})}`).join("\n")
2676
- : "- 开工预检输出无法解析";
2677
- ctx.ui.notify(`开工预检未通过:\n${details}\n可以说「下一步」,由 Devflow 诊断并给出处理办法。`, "error");
2678
- return { ok: false };
2618
+ if (!diagnostics.ok || issues.length) ctx.ui.notify(`软门禁提示:${JSON.stringify(issues.length ? issues : diagnostics.stderr)}`, "info");
2619
+ return { ok: true, briefing: `软门禁模式:以下是诊断结果,缺项不阻止执行,失败检查不代表通过。\n${JSON.stringify(payload ?? { error: diagnostics.stderr })}` };
2679
2620
  }
2680
- const facts = payload.facts && typeof payload.facts === "object" ? payload.facts as Record<string, unknown> : {};
2681
- const handoff = payload.handoff && typeof payload.handoff === "object" ? payload.handoff as Record<string, unknown> : {};
2682
- return {
2683
- ok: true,
2684
- briefing: renderStartupBriefing(facts, handoff, {
2685
- revision: snapshot.revision,
2686
- handoffProvided: Boolean(options.handoffPath),
2687
- }),
2688
- };
2689
- } catch (error) {
2690
- setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "开工预检未通过" });
2691
- ctx.ui.notify(`开工预检未通过:${error instanceof Error ? error.message : String(error)}`, "error");
2692
- return { ok: false };
2693
- }
2694
- }
2695
2621
 
2696
2622
  async function startWorkflowAfterPreflight(
2697
2623
  ctx: DoctorContext & {
@@ -2708,6 +2634,21 @@ export default function devflowCore(pi: ExtensionAPI) {
2708
2634
  ): Promise<void> {
2709
2635
  const preflight = await runStartupPreflight(ctx, snapshot, options);
2710
2636
  if (!preflight.ok) return;
2637
+ if (snapshot.action === "run_parallel_window") {
2638
+ snapshot = await prepareParallelRun(ctx.cwd, snapshot);
2639
+ const feature = String(snapshot.current_feature ?? "");
2640
+ const step = String(compactStep(snapshot.current_step)?.id ?? "");
2641
+ if (!activeContract || activeContract.scope !== "parallel_window" || !(await contractIsFresh(ctx.cwd, activeContract))) {
2642
+ const activated = await activateExecutionContract(ctx, feature, step);
2643
+ if (!activated.ok) {
2644
+ parallelRunActive = false;
2645
+ setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "并行窗口合同未激活" });
2646
+ ctx.ui.notify(`并行窗口合同未激活:${activated.reason}`, "info");
2647
+
2648
+ parallelRunActive = true;
2649
+ }
2650
+ }
2651
+ }
2711
2652
  setControlState(ctx, executingState(options.executingLabel));
2712
2653
  if (preflight.briefing) await triggerWorkflow(snapshot, source, undefined, preflight.briefing);
2713
2654
  else if (source === "start") await triggerWorkflow(snapshot, "start");
@@ -2818,7 +2759,7 @@ export default function devflowCore(pi: ExtensionAPI) {
2818
2759
  ]));
2819
2760
  if (existing?.ok === true && existing.run) {
2820
2761
  parallelRunActive = true;
2821
- return { ...snapshot, parallel_run: existing.run };
2762
+ return refreshedParallelSnapshot(cwd, existing.run);
2822
2763
  }
2823
2764
  const revision = typeof snapshot.revision === "number" ? snapshot.revision : undefined;
2824
2765
  if (!feature || !workflowWindow || revision === undefined) throw new Error("parallel window snapshot is incomplete");
@@ -2834,7 +2775,18 @@ export default function devflowCore(pi: ExtensionAPI) {
2834
2775
  throw new Error(prepared.stderr || prepared.stdout || "parallel run prepare failed");
2835
2776
  }
2836
2777
  parallelRunActive = true;
2837
- return { ...snapshot, parallel_run: payload.run };
2778
+ return refreshedParallelSnapshot(cwd, payload.run);
2779
+ }
2780
+
2781
+ async function refreshedParallelSnapshot(cwd: string, run: unknown): Promise<Record<string, unknown>> {
2782
+ // prepare increments STATE. Recover the derived registry before building
2783
+ // the directive, including when resuming a previously stranded run.
2784
+ const fresh = await inspectWorkflowWithRegistryRecovery(cwd);
2785
+ if (!fresh.result.ok || fresh.snapshot?.action !== "run_parallel_window") {
2786
+ parallelRunActive = false;
2787
+ throw new Error(`并行准备后状态不可调度:${JSON.stringify(fresh.snapshot ?? fresh.result.stderr)}`);
2788
+ }
2789
+ return { ...fresh.snapshot, parallel_run: run };
2838
2790
  }
2839
2791
 
2840
2792
  async function triggerWorkflow(snapshot: Record<string, unknown>, source: "start" | "approval", requirementDraft?: string, briefing?: string) {
@@ -2905,60 +2857,15 @@ export default function devflowCore(pi: ExtensionAPI) {
2905
2857
  ui: { confirm(title: string, body: string): Promise<boolean>; notify(message: string, level: "info" | "error"): void; setStatus(key: string, text: string | undefined): void };
2906
2858
  }): Promise<boolean> {
2907
2859
  const inspected = await inspectWorkflowWithRegistryRecovery(ctx.cwd);
2908
- if (!inspected.result.ok || !inspected.snapshot || !isReviewableWorkflowSnapshot(inspected.snapshot)) return false;
2860
+ if (!inspected.result.ok || !inspected.snapshot || !isReviewableWorkflowSnapshot(inspected.snapshot, { allowAdvisory: true })) return false;
2909
2861
  const step = compactStep(inspected.snapshot.current_step);
2910
2862
  const stepId = typeof step?.id === "string" ? step.id : undefined;
2911
2863
  const revision = typeof inspected.snapshot.revision === "number" ? inspected.snapshot.revision : undefined;
2912
2864
  if (!stepId || revision === undefined) return false;
2913
2865
  const checkpoint = inspected.snapshot.status === "waiting_checkpoint";
2914
- let cp3Prerequisites: Array<{ id?: unknown; kind?: unknown; summary?: unknown }> = [];
2915
- if (checkpoint && inspected.snapshot.checkpoint === "CP-3") {
2916
- const feature = typeof inspected.snapshot.current_feature === "string" ? inspected.snapshot.current_feature : undefined;
2917
- if (!feature) return false;
2918
- const suite = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/acceptance_test_suite.rb", [
2919
- "inspect", "--feature", feature, "--json",
2920
- ]);
2921
- const suitePayload = parseJsonResult(suite);
2922
- if (!suite.ok || !suitePayload || suitePayload.ok !== true) {
2923
- setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "CP-3 前置检查失败" });
2924
- ctx.ui.notify(`无法读取 CP-3 测试前置事实:${suite.stderr || suite.stdout}`, "error");
2925
- return true;
2926
- }
2927
- cp3Prerequisites = Array.isArray(suitePayload.unconfirmed_prerequisites)
2928
- ? suitePayload.unconfirmed_prerequisites as Array<{ id?: unknown; kind?: unknown; summary?: unknown }>
2929
- : [];
2930
- }
2931
- const prerequisiteReview = cp3Prerequisites.length > 0
2932
- ? [
2933
- "",
2934
- `CP-3 检测到 ${cp3Prerequisites.length} 个尚未确认、但被候选验收测试引用的前置事实。批准后 Core 会在同一原子事务中确认它们并冻结测试集:`,
2935
- ...cp3Prerequisites.map(item => `- ${String(item.id ?? "?")} [${String(item.kind ?? "?")}]${item.summary ? `:${String(item.summary)}` : ""}`),
2936
- ].join("\n")
2937
- : "";
2938
- const approved = await ctx.ui.confirm(
2939
- "确认推进 Devflow 工作流",
2940
- `当前步骤:${stepId}\n\n步骤产物已生成并通过基础校验。确认${checkpoint ? "当前 checkpoint 并" : ""}推进到下一步骤吗?${prerequisiteReview}`,
2941
- );
2942
- if (!approved) {
2943
- setControlState(ctx, waitingUserState("等待用户审查步骤产物并明确决定"));
2944
- ctx.ui.notify("已保留当前步骤,未推进工作流。", "info");
2945
- return true;
2946
- }
2947
- if (checkpoint && stepId === "wireframe-review") {
2948
- const feature = inspected.snapshot.current_feature;
2949
- if (typeof feature !== "string") return false;
2950
- const approval = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/wireframe_commit.rb",
2951
- wireframeApprovalArgs(feature, revision));
2952
- if (!controlPlaneSucceeded(approval)) {
2953
- setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "原型确认写入失败" });
2954
- ctx.ui.notify(`原型确认未写入:${approval.stderr || approval.stdout}`, "error");
2955
- return true;
2956
- }
2957
- }
2958
2866
  const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
2959
2867
  "--write", "--json", "--expected-revision", String(revision),
2960
2868
  ...(checkpoint ? ["--confirm-checkpoint", "--runtime-confirmed"] : ["--complete-step", stepId]),
2961
- ...(cp3Prerequisites.length > 0 ? ["--confirm-test-prerequisites"] : []),
2962
2869
  ]);
2963
2870
  if (!result.ok || !controlPlaneSucceeded(result)) {
2964
2871
  setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "推进校验失败" });
@@ -3096,12 +3003,19 @@ export default function devflowCore(pi: ExtensionAPI) {
3096
3003
  if (mode === "next") {
3097
3004
  const report = await runDoctor(ctx);
3098
3005
  if (!report.ok) {
3099
- ctx.ui.notify(`运行环境检查没有通过,暂时不能推进:\n${formatDoctorReport(report).slice(0, 6000)}`, "error");
3100
- return;
3006
+ ctx.ui.notify(`${"软门禁提示"}:\n${formatDoctorReport(report).slice(0, 6000)}`, "info");
3007
+
3101
3008
  }
3102
3009
  }
3010
+ // In advisory mode "next" is the user's request to continue, including
3011
+ // when the current output is missing or a checker failed.
3012
+ if (mode === "next") {
3013
+ const current = await diagnoseCurrentWorkflow(ctx.cwd, { skipWorkflowUpgrade: true });
3014
+ if (current.snapshot && !current.diagnosis.diagnoses.some(item => item.code === "ROLLOVER_PENDING")
3015
+ && await confirmAndAdvanceWorkflowStep(ctx)) return;
3016
+ }
3103
3017
  // Declining the template upgrade must not strand the step behind it.
3104
- let skipWorkflowUpgrade = false;
3018
+ let skipWorkflowUpgrade = true;
3105
3019
  for (let attempt = 0; attempt < 4; attempt += 1) {
3106
3020
  const current = await diagnoseCurrentWorkflow(ctx.cwd, { skipWorkflowUpgrade });
3107
3021
  const { diagnosis, snapshot } = current;
@@ -3536,11 +3450,11 @@ export default function devflowCore(pi: ExtensionAPI) {
3536
3450
  parallelDispatchFailureThisTurn = result.stderr || result.stdout || `parallel ${action} failed`;
3537
3451
  return;
3538
3452
  }
3539
- if (action !== "complete") return;
3453
+
3540
3454
  const refreshed = parseJsonResult(await runRuby(projectRoot, "workflow-orchestrator/scripts/parallel_run.rb", ["--action", "inspect", "--feature", feature, "--json"]));
3541
3455
  const current = refreshed?.run as Record<string, unknown> | undefined;
3542
3456
  const branches = Array.isArray(current?.branches) ? current.branches as Array<Record<string, unknown>> : [];
3543
- if (branches.length > 0 && branches.every(branch => branch.status === "succeeded")) {
3457
+ if (branches.length > 0 && branches.every(branch => branch.status === "succeeded" || (["failed", "aborted"].includes(String(branch.status))))) {
3544
3458
  const after = await inspectWorkflow(projectRoot);
3545
3459
  const joinRevision = Number(after.snapshot?.revision);
3546
3460
  if (Number.isInteger(joinRevision)) {
@@ -3702,8 +3616,8 @@ export default function devflowCore(pi: ExtensionAPI) {
3702
3616
  if (cliSessionResumeRequested && controlState.phase === "idle" && !pendingRollover) {
3703
3617
  const report = await runDoctor(ctx);
3704
3618
  if (!report.ok) {
3705
- ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "error");
3706
- return;
3619
+ ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "info");
3620
+
3707
3621
  }
3708
3622
  const inspected = await inspectWorkflowWithRegistryRecovery(ctx.cwd);
3709
3623
  if (!inspected.result.ok || !inspected.snapshot) {
@@ -3806,7 +3720,7 @@ export default function devflowCore(pi: ExtensionAPI) {
3806
3720
  parallelTaskContexts.clear();
3807
3721
  setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "并行子 Agent 未启动" });
3808
3722
  ctx.ui.notify(
3809
- `并行窗口未启动任何子 Agent,已停止等待不存在的生命周期事件;STATE 未推进。${parallelDispatchFailureThisTurn ? `\n原因:${parallelDispatchFailureThisTurn}` : ""}\n请检查打包 Agent 注册;若当前环境不支持并行,调度器必须按 serial_fallback_order 串行执行。`,
3723
+ `并行窗口未启动任何子 Agent,已停止等待不存在的生命周期事件;STATE 未推进。${parallelDispatchFailureThisTurn ? `\n原因:${parallelDispatchFailureThisTurn}` : ""}\n请先检查本回合的状态、合同与分支派发阻塞证据;未派发不代表 Agent 未安装。仅确认环境不支持并行时才按 serial_fallback_order 串行执行。`,
3810
3724
  "error",
3811
3725
  );
3812
3726
  return;
@@ -3823,6 +3737,10 @@ export default function devflowCore(pi: ExtensionAPI) {
3823
3737
  // UI confirmation may legitimately wait for the user longer than the
3824
3738
  // host's event-handler deadline. Do not await it from agent_end or the
3825
3739
  // host reports a timeout and the approval result is lost.
3740
+ {
3741
+ setControlState(ctx, waitingUserState("当前步骤结束;说「下一步」即可继续,检查缺项仅作提示"));
3742
+ return;
3743
+ }
3826
3744
  void confirmAndAdvanceWorkflowStep(ctx).catch(error => {
3827
3745
  setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "推进确认处理失败" });
3828
3746
  ctx.ui.notify(`推进确认处理失败:${String(error)}`, "error");
@@ -3940,15 +3858,7 @@ export default function devflowCore(pi: ExtensionAPI) {
3940
3858
  ctx.ui.notify("当前状态缺少可确认的 revision 或步骤,拒绝推进。", "error");
3941
3859
  return { handled: true };
3942
3860
  }
3943
- if (checkpoint && stepId === "wireframe-review" && typeof inspected.snapshot.current_feature === "string") {
3944
- const approval = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/wireframe_commit.rb",
3945
- wireframeApprovalArgs(inspected.snapshot.current_feature, revision));
3946
- if (!controlPlaneSucceeded(approval)) {
3947
- setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "原型确认写入失败" });
3948
- ctx.ui.notify(`原型确认未写入:${approval.stderr || approval.stdout}`, "error");
3949
- return { handled: true };
3950
- }
3951
- }
3861
+
3952
3862
  const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
3953
3863
  "--write",
3954
3864
  "--json",
@@ -4033,6 +3943,7 @@ export default function devflowCore(pi: ExtensionAPI) {
4033
3943
  });
4034
3944
 
4035
3945
  pi.on("tool_call", async (event, ctx) => {
3946
+ const decision = await (async () => {
4036
3947
  const toolName = String(event.toolName).toLowerCase();
4037
3948
  const toolInput = event.input && typeof event.input === "object" ? event.input as Record<string, unknown> : undefined;
4038
3949
  const toolCallId = String(event.toolCallId ?? "");
@@ -4048,11 +3959,6 @@ export default function devflowCore(pi: ExtensionAPI) {
4048
3959
  const feature = typeof toolInput?.feature === "string" ? toolInput.feature.trim() : "";
4049
3960
  const phase = typeof toolInput?.phase === "string" && toolInput.phase.trim() ? toolInput.phase.trim() : "默认需求阶段";
4050
3961
  if (!feature) return { block: true, reason: "bootstrap requires a feature name" };
4051
- const confirmed = await ctx.ui.confirm(
4052
- "确认启动新的 Devflow Feature",
4053
- `Feature:${feature}\n阶段:${phase}\n\n确认后由 Core 原子初始化并切换当前 Feature;不会由 AI 直接修改 STATE.md。若当前仍有未完成 Feature,Core 会拒绝切换并保持原状态。继续吗?`,
4054
- );
4055
- if (!confirmed) return { block: true, reason: "User declined runtime bootstrap authorization." };
4056
3962
  if (!toolCallId) return { block: true, reason: "bootstrap runtime authorization is missing a tool-call binding" };
4057
3963
  runtimeBootstrapAuthorizations.add(toolCallId);
4058
3964
  bootstrapAuthorized = true;
@@ -4061,14 +3967,7 @@ export default function devflowCore(pi: ExtensionAPI) {
4061
3967
  const branchContext = parallelBranchContext(event.input);
4062
3968
  if (branchContext) parallelTaskContexts.set(event.toolCallId, branchContext);
4063
3969
  }
4064
- if (coreHardGatesEnabled(activeContract) && controlState.phase === "executing" && !toolName.startsWith("devflow_") && !internalControlResource) {
4065
- if (!activeContract.allowedTools.includes(toolName)) {
4066
- return { block: true, reason: `Active contract ${activeContract.contractId} does not allow tool: ${toolName}` };
4067
- }
4068
- if (!(await contractIsFresh(ctx.cwd, activeContract))) {
4069
- return { block: true, reason: `Active contract ${activeContract.contractId} is stale against STATE or graph revision; synthesize a new contract before continuing.` };
4070
- }
4071
- }
3970
+
4072
3971
  if (toolName === "task") {
4073
3972
  const requested = requestedTaskAgents(event.input);
4074
3973
  if (requested.missing) {
@@ -4094,43 +3993,7 @@ export default function devflowCore(pi: ExtensionAPI) {
4094
3993
  if (controlState.phase === "planning" && !allowedInDraft) {
4095
3994
  return { block: true, reason: planningToolBlockReason(toolName) };
4096
3995
  }
4097
- if (
4098
- coreHardGatesEnabled(activeContract) &&
4099
- activeContract &&
4100
- controlState.phase === "executing" &&
4101
- !requested.names.every(agent => activeContract?.allowedAgents.includes(agent))
4102
- ) {
4103
- return { block: true, reason: `Active contract ${activeContract.contractId} only allows agents: ${activeContract.allowedAgents.join(", ")}` };
4104
- }
4105
- if (coreHardGatesEnabled(activeContract) && activeContract && controlState.phase === "executing" && contractAgentCalls + requested.names.length > activeContract.toolBudget) {
4106
- return { block: true, reason: `Active contract ${activeContract.contractId} exhausted its fixed agent budget (${activeContract.toolBudget}).` };
4107
- }
4108
- if (coreHardGatesEnabled(activeContract) && activeContract && controlState.phase === "executing" && activeContract.step === "code-review" && requested.names.length !== 1) {
4109
- return { block: true, reason: "Code review requires exactly one independent reviewer task." };
4110
- }
4111
- if (coreHardGatesEnabled(activeContract) && activeContract && controlState.phase === "executing" && activeContract.step === "code-review") {
4112
- const preflight = await runProcess([
4113
- "python3", path.join(resolveKitRoot(ctx.cwd), "workflow-orchestrator/scripts/review_preflight.py"),
4114
- "--root", ctx.cwd,
4115
- "--feature", activeContract.feature,
4116
- "--step", activeContract.step,
4117
- "--contract", `.devflow/planning/contracts/${activeContract.feature}/${activeContract.contractId}.json`,
4118
- "--obligations", "[]",
4119
- "--review-agent", "devflow-code-reviewer",
4120
- "--stage", "entry",
4121
- ], ctx.cwd);
4122
- let result: Record<string, unknown> | null = null;
4123
- try {
4124
- result = JSON.parse(preflight.stdout) as Record<string, unknown>;
4125
- } catch {
4126
- // An unparseable Core preflight result cannot authorize a reviewer task.
4127
- }
4128
- if (!preflight.ok || result?.status !== "ready") {
4129
- return { block: true, reason: `Code review task preflight did not pass: ${preflight.stdout || preflight.stderr || "unavailable"}` };
4130
- }
4131
- if (!toolCallId) return { block: true, reason: "Code review task needs a runtime tool-call identity." };
4132
- pendingReviewTasks.set(toolCallId, { feature: activeContract.feature, contractId: activeContract.contractId });
4133
- }
3996
+
4134
3997
  if (activeContract && controlState.phase === "executing") contractAgentCalls += requested.names.length;
4135
3998
  } else if (
4136
3999
  controlState.phase === "planning" &&
@@ -4160,41 +4023,7 @@ export default function devflowCore(pi: ExtensionAPI) {
4160
4023
  "Devflow 状态迁移只能由运行时在用户明确批准已展示的步骤或 checkpoint 后执行;LLM 不得自行推进。",
4161
4024
  };
4162
4025
  }
4163
- if (isUserConfirmedStepTransition && activeContract && coreHardGatesEnabled(activeContract)) {
4164
- const evidence = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/machine_evidence.rb", [
4165
- "--feature", activeContract.feature, "--step", activeContract.step, "--write", "--json",
4166
- ]);
4167
- const evidencePayload = parseJsonResult(evidence);
4168
- if (!evidence.ok || !evidencePayload || evidencePayload.complete !== true) {
4169
- return {
4170
- block: true,
4171
- reason: `Devflow machine evidence is incomplete; collect independent runtime evidence before completing ${activeContract.step}.`,
4172
- };
4173
- }
4174
- }
4175
- if (coreHardGatesEnabled(activeContract) && activeWorkflowTurn && !parallelRunActive && stepScopedMutationOwner(toolName, event.input) !== undefined) {
4176
- const inspected = await inspectWorkflow(ctx.cwd);
4177
- if (!inspected.result.ok || !inspected.snapshot) {
4178
- return {
4179
- block: true,
4180
- reason: `Active Devflow workflow mutation blocked because authoritative state could not be inspected: ${inspected.result.stderr || inspected.result.stdout}`,
4181
- };
4182
- }
4183
- const fresh = activeContract ? await contractIsFresh(ctx.cwd, activeContract) : false;
4184
- const reason = stepScopedMutationBlockReason(toolName, event.input, ctx.cwd, inspected.snapshot, activeContract, fresh);
4185
- if (reason) return { block: true, reason };
4186
- }
4187
- if (
4188
- coreHardGatesEnabled(activeContract) &&
4189
- !toolName.startsWith("devflow_") &&
4190
- ["write", "edit", "apply_patch", "notebook_edit", "bash", "shell", "exec_command"].includes(toolName) &&
4191
- inputPaths(event.input, ctx.cwd).some(isCoreOwnedGovernancePath)
4192
- ) {
4193
- return {
4194
- block: true,
4195
- reason: "规范需求图谱、派生视图、机器 Evidence 与合同审计只能由 Core writer 写入;Skill 只能提交候选 delta 或 agent_claim。",
4196
- };
4197
- }
4026
+
4198
4027
  // Feature artifacts written while no step is running are never adopted by
4199
4028
  // the workflow (zhaoshang, 2026-10-22). Refuse early in the interactive
4200
4029
  // main session; subagents (no UI), review-feedback edits and controlled
@@ -4229,31 +4058,8 @@ export default function devflowCore(pi: ExtensionAPI) {
4229
4058
  }
4230
4059
  }
4231
4060
  if (toolName.startsWith("devflow_")) return;
4232
- if (
4233
- coreHardGatesEnabled(activeContract) &&
4234
- activeContract &&
4235
- controlState.phase === "executing" &&
4236
- !internalControlResource &&
4237
- ["write", "edit", "apply_patch", "notebook_edit"].includes(toolName)
4238
- ) {
4239
- const paths = inputPaths(event.input, ctx.cwd);
4240
- if (paths.length === 0 || paths.some(filePath => !contractAllowsPath(activeContract!, filePath))) {
4241
- return {
4242
- block: true,
4243
- reason: `Active contract ${activeContract.contractId} only allows edits under: ${activeContract.allowedPaths.join(", ")}`,
4244
- };
4245
- }
4246
- }
4247
- if (coreHardGatesEnabled(activeContract) && activeContract && controlState.phase === "executing" && ["bash", "shell", "exec_command"].includes(toolName)) {
4248
- const command = String((event.input as Record<string, unknown> | undefined)?.command ?? (event.input as Record<string, unknown> | undefined)?.cmd ?? "");
4249
- const shell = shellMutationPaths(command, ctx.cwd);
4250
- if (shell.mutating && (shell.paths.length === 0 || shell.paths.some(filePath => !contractAllowsPath(activeContract!, filePath)))) {
4251
- return {
4252
- block: true,
4253
- reason: `Active contract ${activeContract.contractId} 不允许通过 Bash/脚本修改合同外路径;请使用合同允许路径,或先更新合同。`,
4254
- };
4255
- }
4256
- }
4061
+
4062
+
4257
4063
  if (isDirectControlMutation(toolName, event.input, ctx.cwd) || isShellControlMutation(toolName, event.input)) {
4258
4064
  return {
4259
4065
  block: true,
@@ -4277,8 +4083,9 @@ export default function devflowCore(pi: ExtensionAPI) {
4277
4083
  if (gate.stdout) {
4278
4084
  try {
4279
4085
  const payload = JSON.parse(gate.stdout) as {
4280
- hookSpecificOutput?: { permissionDecision?: string; permissionDecisionReason?: string };
4086
+ hookSpecificOutput?: { permissionDecision?: string; permissionDecisionReason?: string; additionalContext?: string };
4281
4087
  };
4088
+ if (payload.hookSpecificOutput?.additionalContext) ctx.ui.notify(payload.hookSpecificOutput.additionalContext, "info");
4282
4089
  if (payload.hookSpecificOutput?.permissionDecision === "deny") {
4283
4090
  return {
4284
4091
  block: true,
@@ -4292,6 +4099,12 @@ export default function devflowCore(pi: ExtensionAPI) {
4292
4099
  };
4293
4100
  }
4294
4101
  }
4102
+ })();
4103
+ if (decision?.block && !String(decision.reason).startsWith("bootstrap ") && decision.reason !== "User declined runtime bootstrap authorization.") {
4104
+ ctx.ui.notify(`软门禁提示:${decision.reason}`, "info");
4105
+ return;
4106
+ }
4107
+ return decision;
4295
4108
  });
4296
4109
 
4297
4110
  pi.on("tool_result", async (event, ctx) => {
@@ -4485,20 +4298,13 @@ export default function devflowCore(pi: ExtensionAPI) {
4485
4298
  if (params.expectedRevision === undefined || !params.delta) {
4486
4299
  return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "propose requires expectedRevision and delta" });
4487
4300
  }
4488
- if (coreHardGatesEnabled(activeContract) && params.delta.kind === "test_case" && activeContract?.step !== "acceptance-test-design") {
4489
- return renderResult({
4490
- ok: false,
4491
- exitCode: 1,
4492
- stdout: "",
4493
- stderr: "TC candidates are accepted only during the active acceptance-test-design contract.",
4494
- });
4495
- }
4301
+
4496
4302
  args.push("--expected-revision", String(params.expectedRevision), "--delta-json", JSON.stringify(params.delta));
4497
4303
  // `write` is an explicit dry-run switch. Omit it only when the caller
4498
4304
  // explicitly asks for a preview; the normal approved proposal remains
4499
4305
  // a Core-owned write.
4500
4306
  if (params.write !== false) args.push("--write");
4501
- if (coreHardGatesEnabled(activeContract) && activeContract?.allowedGraphDelta?.length) args.push("--allowed-kinds", activeContract.allowedGraphDelta.join(","));
4307
+
4502
4308
  }
4503
4309
  if (params.action === "confirm") {
4504
4310
  if (params.expectedRevision === undefined || !params.nodeId || !params.evidence) {
@@ -4602,9 +4408,7 @@ export default function devflowCore(pi: ExtensionAPI) {
4602
4408
  }),
4603
4409
  approval: args => args.action === "validate" ? "read" : "write",
4604
4410
  async execute(_id, params, _signal, _onUpdate, ctx) {
4605
- if (coreHardGatesEnabled(activeContract) && params.action !== "validate" && activeContract?.step !== "acceptance-test-design") {
4606
- return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "API test matrix compilation/import is allowed only during the active acceptance-test-design contract." });
4607
- }
4411
+
4608
4412
  if (params.action === "validate") {
4609
4413
  const process = await runPython(ctx.cwd, "workflow-orchestrator/scripts/api_test_matrix.py", ["validate", "--feature", params.feature, "--json"]);
4610
4414
  const payload = parseJsonResult(process);
@@ -4767,7 +4571,7 @@ export default function devflowCore(pi: ExtensionAPI) {
4767
4571
  "Actions: validate (bindings and suite), validate_human (manual TCs), run (executes every automated TC and writes the canonical acceptance-results.json; Core is the only writer), validate_results (re-checks recorded results against current approvals and hashes), gc_runs (prunes .devflow/runs/<feature> execution directories per retention rules; dry-run by default, pass write=true to delete; optional retentionDays / unreferencedTtlDays).",
4768
4572
  "Failure analysis reads only the Core-produced result.json summary; trace files and screenshots are not readable by Agent tools and are for the user to inspect locally with Playwright in a terminal.",
4769
4573
  "devflow_playwright bindings require CURRENT human approvals created with devflow_approval and confirmed by the user in /devflow control: an execution_plan approval per TC (pins the DSL, TC and suite) and an environment_profile approval for the target profile; manual or downgraded TCs need a human_result / mode_override approval. You cannot confirm approvals yourself.",
4770
- "Hard gate: a missing, superseded or expired approval, or a DSL/profile/suite/evidence hash that changed after the approval or the run, makes run and validate_results return ok=false and blocks run-acceptance-tests from completing; there is no soft override and results must never be written by hand.",
4574
+ "A missing approval or changed DSL/profile/suite/evidence hash is reported as ok=false by run and validate_results. In soft gate mode these findings do not block workflow advancement. Failed or unexecuted tests must never be reported as passed; results must never be written by hand.",
4771
4575
  ].join(" "),
4772
4576
  parameters: z.object({
4773
4577
  action: z.enum(["validate", "validate_human", "run", "validate_results", "gc_runs"]),
@@ -5320,9 +5124,7 @@ export default function devflowCore(pi: ExtensionAPI) {
5320
5124
  if (!params.step) {
5321
5125
  return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "step is required" });
5322
5126
  }
5323
- if (params.confirm !== true) {
5324
- return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "complete-step requires explicit user confirmation (confirm=true)" });
5325
- }
5127
+
5326
5128
  const snapshot = parseJsonResult(await runRuby(ctx.cwd, "workflow-orchestrator/scripts/inspect_state.rb", ["--json"]));
5327
5129
  const currentStep = snapshot?.current_step && typeof snapshot.current_step === "object"
5328
5130
  ? (snapshot.current_step as Record<string, unknown>).id
@@ -5341,9 +5143,7 @@ export default function devflowCore(pi: ExtensionAPI) {
5341
5143
  if (activeContract?.feature === feature && activeContract.step === params.step && activeContract.contractPath) evidenceArgs.push("--contract", activeContract.contractPath);
5342
5144
  const evidence = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/machine_evidence.rb", evidenceArgs);
5343
5145
  const evidencePayload = parseJsonResult(evidence);
5344
- if (!controlPlaneSucceeded(evidence) || evidencePayload?.complete !== true) {
5345
- return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "machine evidence collection failed or required outputs are incomplete" }, { action: params.action, evidence: evidence.stdout });
5346
- }
5146
+
5347
5147
  }
5348
5148
  args.push("--complete-step", params.step);
5349
5149
  } else if (params.action === "confirm-checkpoint") {
@@ -5734,8 +5534,8 @@ export default function devflowCore(pi: ExtensionAPI) {
5734
5534
  if (trimmed === "resume") {
5735
5535
  const report = await runDoctor(ctx);
5736
5536
  if (!report.ok) {
5737
- ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "error");
5738
- return;
5537
+ ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "info");
5538
+
5739
5539
  }
5740
5540
  const inspected = await inspectWorkflowWithRegistryRecovery(ctx.cwd);
5741
5541
  if (!inspected.result.ok || !inspected.snapshot) {
@@ -5784,8 +5584,8 @@ export default function devflowCore(pi: ExtensionAPI) {
5784
5584
  }
5785
5585
  const report = await runDoctor(ctx);
5786
5586
  if (!report.ok) {
5787
- ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "error");
5788
- return;
5587
+ ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "info");
5588
+
5789
5589
  }
5790
5590
  const existing = await inspectWorkflow(ctx.cwd);
5791
5591
  const existingFeature = existing.snapshot?.current_feature;