@guanlin4924/devflow-pi 0.1.104 → 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.
- package/README.md +10 -8
- package/package.json +1 -1
- package/plugins/devflow-core/extensions/devflow-core.ts +105 -305
- package/plugins/devflow-core/runtime/hooks/context_sync_gate.py +3 -1
- package/plugins/devflow-core/runtime/hooks/hook_common.py +6 -5
- package/plugins/devflow-core/runtime/hooks/index_gate.py +19 -26
- package/plugins/devflow-core/skills/wireframe-proposal/SKILL.md +2 -0
- package/plugins/devflow-core/skills/wireframe-render/SKILL.md +2 -0
- package/plugins/devflow-core/skills/wireframe-review/SKILL.md +2 -0
- package/plugins/devflow-core/skills/wireframe-validate/SKILL.md +2 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/SKILL.md +34 -50
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +40 -191
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/artifact_specs.rb +12 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_contract.rb +38 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/gate_policy.rb +11 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/gate_result.rb +12 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/gate_runtime.rb +4 -9
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/inspect_state.rb +22 -87
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/parallel_recover.rb +24 -13
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/repair_control_plane.rb +4 -4
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/sync_artifacts.rb +3 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +8 -39
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/wireframe_commit.rb +39 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +2 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/regression-acceptance.yaml +0 -1
- package/plugins/devflow-core/skills/workflow-pipeline/SKILL.md +10 -5
- package/plugins/devflow-core/skills/workflow-pipeline/references/pipeline-flow.md +1 -1
- package/plugins/devflow-core/skills/workflow-project-context/SKILL.md +2 -0
- package/plugins/devflow-design/skills/workflow-api-call-guide/SKILL.md +2 -0
- package/plugins/devflow-design/skills/workflow-api-contract/SKILL.md +3 -1
- package/plugins/devflow-design/skills/workflow-api-supplement/SKILL.md +2 -0
- package/plugins/devflow-design/skills/workflow-architecture-feasibility/SKILL.md +2 -0
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/SKILL.md +6 -4
- package/plugins/devflow-design/skills/workflow-design-rule-pack/SKILL.md +2 -0
- package/plugins/devflow-design/skills/workflow-feature-breakdown/SKILL.md +2 -0
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/SKILL.md +6 -4
- package/plugins/devflow-design/skills/workflow-integration-verify/SKILL.md +3 -1
- package/plugins/devflow-design/skills/workflow-ui-spec/SKILL.md +4 -2
- package/plugins/devflow-implementation/agents/implementation-worker.md +2 -8
- package/plugins/devflow-implementation/skills/workflow-bug-fix/SKILL.md +2 -0
- package/plugins/devflow-implementation/skills/workflow-change-plan/SKILL.md +3 -1
- package/plugins/devflow-implementation/skills/workflow-execute-phase/SKILL.md +3 -1
- package/plugins/devflow-intel/skills/code-index-query/SKILL.md +2 -0
- package/plugins/devflow-intel/skills/workflow-code-index/SKILL.md +2 -0
- package/plugins/devflow-intel/skills/workflow-context-sync/SKILL.md +2 -0
- package/plugins/devflow-intel/skills/workflow-update-navigation/SKILL.md +3 -1
- package/plugins/devflow-quality/skills/code-review/SKILL.md +4 -2
- package/plugins/devflow-quality/skills/workflow-acceptance-test-design/SKILL.md +3 -1
- package/plugins/devflow-quality/skills/workflow-gen-test-guide/SKILL.md +2 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/SKILL.md +2 -0
- package/plugins/devflow-quality/skills/workflow-human-acceptance/SKILL.md +2 -0
- package/plugins/devflow-quality/skills/workflow-regression-test-design/SKILL.md +2 -0
- package/plugins/devflow-quality/skills/workflow-release-closeout/SKILL.md +7 -5
- package/plugins/devflow-quality/skills/workflow-run-acceptance-tests/SKILL.md +8 -6
- package/plugins/devflow-requirements/skills/requirement-feasibility-review/SKILL.md +2 -0
- package/plugins/devflow-requirements/skills/workflow-demand-clarify/SKILL.md +2 -0
- package/plugins/devflow-requirements/skills/workflow-domain-modeling/SKILL.md +2 -0
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/SKILL.md +6 -2
- package/plugins/devflow-requirements/skills/workflow-requirement-triage/SKILL.md +2 -0
- package/plugins/devflow-requirements/skills/workflow-wayfinder/SKILL.md +2 -0
- package/src/constants.ts +1 -1
- package/src/workflow-diagnosis.ts +21 -1
- 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 (
|
|
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
|
-
"
|
|
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
|
-
|
|
1428
|
-
|
|
1429
|
-
: "Devflow Runtime 已完成环境与状态检查。读取 skill://workflow-pipeline,并只执行当前项目的一个工作流步骤。",
|
|
1428
|
+
...(["当前为全软门禁模式:产物、索引、合同、质量、审查和恢复检查只提供诊断。缺失输入先利用已有证据尽力完成,未解决项写入报告;不得因检查未通过而停止整个流程,不得伪造测试通过或人工批准。CP 节点不强制审批,用户要求下一步即可继续。真实工具执行失败仍须如实报告。"]),
|
|
1429
|
+
"读取 skill://workflow-pipeline,按当前步骤或并行分支执行;开工简报、执行合同与 Skill 中的门禁要求在本模式下均为建议。",
|
|
1430
1430
|
...(briefing
|
|
1431
1431
|
? ["<startup-briefing>", briefing, "</startup-briefing>"]
|
|
1432
|
-
: ["
|
|
1433
|
-
"
|
|
1434
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
2622
|
-
const
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
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
|
-
|
|
2675
|
-
|
|
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
|
|
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
|
|
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(
|
|
3100
|
-
|
|
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 =
|
|
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
|
-
|
|
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), "
|
|
3706
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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), "
|
|
5738
|
-
|
|
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), "
|
|
5788
|
-
|
|
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;
|