@guanlin4924/devflow-pi 0.1.105 → 0.1.107
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 +287 -394
- 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-dynamic/SKILL.md +41 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/SKILL.md +37 -50
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +46 -191
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/artifact_specs.rb +12 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/dynamic_plan.rb +504 -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 +19 -86
- 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 +7 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +17 -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 +13 -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 +5 -0
- package/plugins/devflow-implementation/skills/workflow-change-plan/SKILL.md +3 -1
- package/plugins/devflow-implementation/skills/workflow-execute-phase/SKILL.md +6 -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/agents/code-reviewer.md +2 -0
- package/plugins/devflow-quality/skills/code-review/SKILL.md +7 -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 +11 -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 +5 -0
- package/plugins/devflow-requirements/skills/workflow-wayfinder/SKILL.md +2 -0
- package/src/constants.ts +1 -1
- package/src/dynamic-workflow.ts +31 -0
- package/templates/omp/AGENTS.md +8 -3
|
@@ -19,6 +19,7 @@ import { generateProjectDocuments } from "../../../src/project-documents.ts";
|
|
|
19
19
|
import { preparedRolloverRevision } from "../../../src/session-rollover.ts";
|
|
20
20
|
import { describeStartupIssue, renderStartupBriefing } from "../../../src/startup-briefing.ts";
|
|
21
21
|
import { PRODUCT_VERSION } from "../../../src/constants.ts";
|
|
22
|
+
import { dynamicWorkflowDirective, renderDynamicWorkflow } from "../../../src/dynamic-workflow.ts";
|
|
22
23
|
import {
|
|
23
24
|
FIX_LABELS,
|
|
24
25
|
buildHelpBundle,
|
|
@@ -233,12 +234,13 @@ type ActiveExecutionContract = {
|
|
|
233
234
|
stateRevision?: number;
|
|
234
235
|
graphRevision?: number;
|
|
235
236
|
workflowFingerprint?: string;
|
|
237
|
+
scope?: string;
|
|
238
|
+
branchContracts?: unknown[];
|
|
236
239
|
allowedPaths: string[];
|
|
237
240
|
allowedAgents: string[];
|
|
238
241
|
allowedTools: string[];
|
|
239
242
|
checkerIds: string[];
|
|
240
243
|
toolBudget: number;
|
|
241
|
-
hardGateMode: "chain" | "core";
|
|
242
244
|
frozenHash: string;
|
|
243
245
|
contractPath?: string;
|
|
244
246
|
acceptance: string[];
|
|
@@ -256,12 +258,13 @@ function contractFromRecord(record: Record<string, unknown>): ActiveExecutionCon
|
|
|
256
258
|
stateRevision: typeof record.state_revision === "number" ? record.state_revision : undefined,
|
|
257
259
|
graphRevision: typeof record.graph_revision === "number" ? record.graph_revision : undefined,
|
|
258
260
|
workflowFingerprint: typeof record.workflow_fingerprint === "string" ? record.workflow_fingerprint : undefined,
|
|
261
|
+
scope: typeof record.scope === "string" ? record.scope : undefined,
|
|
262
|
+
branchContracts: Array.isArray(record.branch_contracts) ? record.branch_contracts : undefined,
|
|
259
263
|
allowedPaths: strings("allowed_paths"),
|
|
260
264
|
allowedAgents: strings("allowed_agents"),
|
|
261
265
|
allowedTools: strings("allowed_tools"),
|
|
262
266
|
checkerIds: strings("checker_ids"),
|
|
263
267
|
toolBudget: typeof record.tool_budget === "number" ? record.tool_budget : 0,
|
|
264
|
-
hardGateMode: record.hard_gate_mode === "core" ? "core" : "chain",
|
|
265
268
|
frozenHash: String(record.frozen_hash ?? ""),
|
|
266
269
|
acceptance: strings("acceptance"),
|
|
267
270
|
allowedGraphDelta: strings("allowed_graph_delta"),
|
|
@@ -271,10 +274,6 @@ function contractFromRecord(record: Record<string, unknown>): ActiveExecutionCon
|
|
|
271
274
|
return contract;
|
|
272
275
|
}
|
|
273
276
|
|
|
274
|
-
function coreHardGatesEnabled(contract: ActiveExecutionContract | undefined): boolean {
|
|
275
|
-
return contract?.hardGateMode === "core";
|
|
276
|
-
}
|
|
277
|
-
|
|
278
277
|
const READ_ONLY_TOOLS = new Set([
|
|
279
278
|
"read",
|
|
280
279
|
"grep",
|
|
@@ -821,6 +820,7 @@ const REQUIRED_EXTENSION_TOOLS = [
|
|
|
821
820
|
];
|
|
822
821
|
|
|
823
822
|
const REQUIRED_SKILLS = [
|
|
823
|
+
"workflow-dynamic",
|
|
824
824
|
"code-index-query",
|
|
825
825
|
"code-review",
|
|
826
826
|
"requirement-feasibility-review",
|
|
@@ -878,6 +878,7 @@ const REQUIRED_MODEL_ROLES = ["@smol", "@task", "@slow"];
|
|
|
878
878
|
const REQUIRED_AGENT_NAMES = new Set(Object.keys(REQUIRED_AGENTS));
|
|
879
879
|
|
|
880
880
|
const REQUIRED_SCRIPTS = [
|
|
881
|
+
"workflow-orchestrator/scripts/dynamic_plan.rb",
|
|
881
882
|
"workflow-orchestrator/scripts/init_workflow.rb",
|
|
882
883
|
"workflow-orchestrator/scripts/init_state.rb",
|
|
883
884
|
"workflow-orchestrator/scripts/phase_ownership.rb",
|
|
@@ -1316,7 +1317,7 @@ function selectedWorkflowStep(snapshot: Record<string, unknown>): Record<string,
|
|
|
1316
1317
|
* completed step. This predicate lets the runtime re-arm approval handling
|
|
1317
1318
|
* from the Core-owned snapshot without granting approval by itself.
|
|
1318
1319
|
*/
|
|
1319
|
-
function isReviewableWorkflowSnapshot(snapshot: Record<string, unknown>, options: { strictRegistry?: boolean } = {}): boolean {
|
|
1320
|
+
function isReviewableWorkflowSnapshot(snapshot: Record<string, unknown>, options: { strictRegistry?: boolean; allowAdvisory?: boolean } = {}): boolean {
|
|
1320
1321
|
const status = snapshot.status;
|
|
1321
1322
|
if (status !== "waiting_checkpoint" && status !== "not_started" && status !== "in_progress") return false;
|
|
1322
1323
|
const step = snapshot.current_step;
|
|
@@ -1326,7 +1327,8 @@ function isReviewableWorkflowSnapshot(snapshot: Record<string, unknown>, options
|
|
|
1326
1327
|
// Core-generated quality findings are advisory by default. Re-arm the AI
|
|
1327
1328
|
// review even when an output is stale or its generated validity metadata is
|
|
1328
1329
|
// incomplete; only the control-plane shape itself remains a hard boundary.
|
|
1329
|
-
if (
|
|
1330
|
+
if (snapshot.action !== "run_parallel_window" &&
|
|
1331
|
+
(options.allowAdvisory || snapshot.action === "await_user_confirmation")) return true;
|
|
1330
1332
|
const outputs = Array.isArray(stepRecord.outputs) ? stepRecord.outputs : [];
|
|
1331
1333
|
const requiredOutputs = outputs.filter(output => {
|
|
1332
1334
|
if (!output || typeof output !== "object") return false;
|
|
@@ -1386,7 +1388,7 @@ export function workflowResumeDisposition(snapshot: Record<string, unknown>): Wo
|
|
|
1386
1388
|
return "blocked";
|
|
1387
1389
|
}
|
|
1388
1390
|
|
|
1389
|
-
function workflowDirective(snapshot: Record<string, unknown>, source: "start" | "approval", requirementDraft?: string, briefing?: string): string {
|
|
1391
|
+
export function workflowDirective(snapshot: Record<string, unknown>, source: "start" | "approval", requirementDraft?: string, briefing?: string): string {
|
|
1390
1392
|
const selectedStep = selectedWorkflowStep(snapshot);
|
|
1391
1393
|
const reviewInstructions = selectedStep?.id === "code-review"
|
|
1392
1394
|
? [
|
|
@@ -1407,6 +1409,7 @@ function workflowDirective(snapshot: Record<string, unknown>, source: "start" |
|
|
|
1407
1409
|
checkpoint: snapshot.checkpoint,
|
|
1408
1410
|
step: selectedStep,
|
|
1409
1411
|
parallel_window: snapshot.parallel_window,
|
|
1412
|
+
parallel_run: snapshot.parallel_run,
|
|
1410
1413
|
failure_branch: snapshot.failure_branch,
|
|
1411
1414
|
reason: snapshot.reason,
|
|
1412
1415
|
recovery: snapshot.recovery,
|
|
@@ -1414,7 +1417,8 @@ function workflowDirective(snapshot: Record<string, unknown>, source: "start" |
|
|
|
1414
1417
|
};
|
|
1415
1418
|
const parallelInstructions = snapshot.action === "run_parallel_window"
|
|
1416
1419
|
? [
|
|
1417
|
-
"
|
|
1420
|
+
"当前合同 scope=parallel_window:current_step 只是汇合锚点,不要执行 integration-verify 或读取它的 Skill;按 parallel_window.branch_steps 中当前可运行的分支读取各自精确 skill,并遵守合同 branch_contracts 的逐分支权限。",
|
|
1421
|
+
"这是并行窗口:仅派发 parallel_window.branch_steps 中当前可运行的分支,并从 parallel_run 中匹配 branch 记录逐个调用 task;一个 task 只能负责一个 workflow_step,不得把多个 step 合并到一个子 Agent。",
|
|
1418
1422
|
"本窗口的 branch step(backend-tech-spec、api-call-guide、ui-spec、feature-breakdown、frontend-tech-spec、api-supplement)统一使用白名单 agent implementation-worker;不要把 workflow step 名称当作 agent 名称。",
|
|
1419
1423
|
"只有执行验证类步骤才使用 verification-runner;project-scout、requirements-auditor、contract-auditor、devflow-code-reviewer 不属于本并行窗口的默认执行 agent。",
|
|
1420
1424
|
"每个 task 的提示必须原样包含 DEVFLOW_BRANCH_ID、DEVFLOW_WORKFLOW_STEP、DEVFLOW_TRACK、DEVFLOW_ATTEMPT 和 DEVFLOW_BRANCH_REVISION,并只写该分支的白名单产物。",
|
|
@@ -1424,16 +1428,15 @@ function workflowDirective(snapshot: Record<string, unknown>, source: "start" |
|
|
|
1424
1428
|
: [];
|
|
1425
1429
|
return [
|
|
1426
1430
|
"<devflow-runtime-directive>",
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
: "Devflow Runtime 已完成环境与状态检查。读取 skill://workflow-pipeline,并只执行当前项目的一个工作流步骤。",
|
|
1431
|
+
...(["当前为全软门禁模式:产物、索引、合同、质量、审查和恢复检查只提供诊断。缺失输入先利用已有证据尽力完成,未解决项写入报告;不得因检查未通过而停止整个流程,不得伪造测试通过或人工批准。CP 节点不强制审批,用户要求下一步即可继续。真实工具执行失败仍须如实报告。"]),
|
|
1432
|
+
"读取 skill://workflow-pipeline,按当前步骤或并行分支执行;开工简报、执行合同与 Skill 中的门禁要求在本模式下均为建议。",
|
|
1430
1433
|
...(briefing
|
|
1431
1434
|
? ["<startup-briefing>", briefing, "</startup-briefing>"]
|
|
1432
|
-
: ["
|
|
1433
|
-
"
|
|
1434
|
-
...
|
|
1435
|
+
: ["可按需查看状态与合同;缺失或过期合同不阻止执行。"]),
|
|
1436
|
+
"串行步骤按 inspector.current_step,并行窗口按可运行分支加载所需 Skill:step.skill 是精确 ID,必须读取 skill://<step.skill>,不得自行添加 workflow- 前缀或改写名称;本回合严禁读取或写入 xd://devflow_transition,严禁调用 devflow_transition,严禁执行 Skill 中的 complete-step/confirm-checkpoint,不得推进下一步骤;状态写回和会话切换由 Runtime 在用户确认后独占处理。",
|
|
1437
|
+
...([]),
|
|
1435
1438
|
...(parallelInstructions.length > 0 ? parallelInstructions : [
|
|
1436
|
-
"
|
|
1439
|
+
"完成当前步骤后报告证据、未决项和检查警告。用户要求下一步即可继续;不要把质量检查失败当作流程阻塞,也不要自报未执行的检查已通过。",
|
|
1437
1440
|
]),
|
|
1438
1441
|
"当前步骤信封:",
|
|
1439
1442
|
JSON.stringify(envelope, null, 2),
|
|
@@ -1535,6 +1538,7 @@ export function isShellControlMutation(toolName: string, input: unknown): boolea
|
|
|
1535
1538
|
}
|
|
1536
1539
|
const command = String((input as Record<string, unknown>).command ?? (input as Record<string, unknown>).cmd ?? "");
|
|
1537
1540
|
if (!command) return false;
|
|
1541
|
+
if (/\bdynamic_plan\.rb\b/.test(command)) return true;
|
|
1538
1542
|
const invokesWriter =
|
|
1539
1543
|
/workflow-orchestrator\/scripts\/(?:advance_state|init_state|init_workflow|sync_artifacts|requirement_graph|machine_evidence|execution_contract)\.rb[^\n]*--write/.test(
|
|
1540
1544
|
command,
|
|
@@ -1997,6 +2001,10 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1997
2001
|
const reviewBindingKey = randomBytes(32).toString("hex");
|
|
1998
2002
|
const pendingReviewTasks = new Map<string, { feature: string; contractId: string }>();
|
|
1999
2003
|
const completedReviewTasks = new Map<string, { feature: string; contractId: string; report: Record<string, unknown> }>();
|
|
2004
|
+
const dynamicReviewBindings = new Map<string, { taskId: string; attempt: string; revision: number }>();
|
|
2005
|
+
const dynamicReviewResults = new Map<string, { taskId: string; attempt: string; revision: number; report: Record<string, unknown> }>();
|
|
2006
|
+
let dynamicTurnRevision: number | undefined;
|
|
2007
|
+
let dynamicTurnProgress: string | undefined;
|
|
2000
2008
|
let latestReviewTaskCallId: string | undefined;
|
|
2001
2009
|
let projectRoot: string | undefined;
|
|
2002
2010
|
|
|
@@ -2109,6 +2117,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2109
2117
|
const content = lines.map((line, index) => {
|
|
2110
2118
|
if (index === 0) return theme.bold(theme.fg("accent", line));
|
|
2111
2119
|
if (line.startsWith("──")) return theme.fg("accent", line);
|
|
2120
|
+
if (line.startsWith("—")) return theme.fg("dim", line);
|
|
2112
2121
|
return theme.fg("muted", line);
|
|
2113
2122
|
}).join("\n");
|
|
2114
2123
|
panel.addChild(new Text(content, 0, 0));
|
|
@@ -2160,7 +2169,12 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2160
2169
|
graph && typeof graph === "object" && typeof (graph as Record<string, unknown>).revision === "number"
|
|
2161
2170
|
? (graph as Record<string, number>).revision
|
|
2162
2171
|
: undefined;
|
|
2163
|
-
|
|
2172
|
+
if (stateRevision !== contract.stateRevision || graphRevision !== contract.graphRevision) return false;
|
|
2173
|
+
const inspected = parseJsonResult(await runRuby(cwd, "workflow-orchestrator/scripts/execution_contract.rb", [
|
|
2174
|
+
"inspect", "--feature", contract.feature, "--step", contract.step, "--json",
|
|
2175
|
+
]));
|
|
2176
|
+
const current = inspected?.contract as Record<string, unknown> | undefined;
|
|
2177
|
+
return current?.frozen_hash === contract.frozenHash;
|
|
2164
2178
|
}
|
|
2165
2179
|
|
|
2166
2180
|
// A successful graph mutation is already constrained by the active contract's
|
|
@@ -2254,22 +2268,6 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2254
2268
|
}
|
|
2255
2269
|
const contract = contractFromRecord(raw as Record<string, unknown>);
|
|
2256
2270
|
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
2271
|
try {
|
|
2274
2272
|
const createArgs = ["create", "--feature", feature, "--step", step, "--write", "--json"];
|
|
2275
2273
|
if (contract.stateRevision !== undefined) createArgs.push("--expected-state-revision", String(contract.stateRevision));
|
|
@@ -2618,80 +2616,17 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2618
2616
|
snapshot: Record<string, unknown>,
|
|
2619
2617
|
options: { doctorAlreadyPassed?: boolean; handoffPath?: string } = {},
|
|
2620
2618
|
): 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: "开工预检未通过" });
|
|
2619
|
+
const feature = String(snapshot.current_feature ?? "");
|
|
2620
|
+
const step = String(selectedWorkflowStep(snapshot)?.id ?? "");
|
|
2621
|
+
const diagnostics = await runPython(ctx.cwd, "workflow-orchestrator/scripts/startup_preflight.py", [
|
|
2622
|
+
"--root", ctx.cwd, "--feature", feature, "--step", step,
|
|
2623
|
+
...(options.handoffPath ? ["--handoff", options.handoffPath] : []),
|
|
2624
|
+
]);
|
|
2625
|
+
const payload = parseJsonOutput(diagnostics);
|
|
2673
2626
|
const issues = Array.isArray(payload?.issues) ? payload.issues : [];
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
: "- 开工预检输出无法解析";
|
|
2677
|
-
ctx.ui.notify(`开工预检未通过:\n${details}\n可以说「下一步」,由 Devflow 诊断并给出处理办法。`, "error");
|
|
2678
|
-
return { ok: false };
|
|
2627
|
+
if (!diagnostics.ok || issues.length) ctx.ui.notify(`软门禁提示:${JSON.stringify(issues.length ? issues : diagnostics.stderr)}`, "info");
|
|
2628
|
+
return { ok: true, briefing: `软门禁模式:以下是诊断结果,缺项不阻止执行,失败检查不代表通过。\n${JSON.stringify(payload ?? { error: diagnostics.stderr })}` };
|
|
2679
2629
|
}
|
|
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
2630
|
|
|
2696
2631
|
async function startWorkflowAfterPreflight(
|
|
2697
2632
|
ctx: DoctorContext & {
|
|
@@ -2708,6 +2643,21 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2708
2643
|
): Promise<void> {
|
|
2709
2644
|
const preflight = await runStartupPreflight(ctx, snapshot, options);
|
|
2710
2645
|
if (!preflight.ok) return;
|
|
2646
|
+
if (snapshot.action === "run_parallel_window") {
|
|
2647
|
+
snapshot = await prepareParallelRun(ctx.cwd, snapshot);
|
|
2648
|
+
const feature = String(snapshot.current_feature ?? "");
|
|
2649
|
+
const step = String(compactStep(snapshot.current_step)?.id ?? "");
|
|
2650
|
+
if (!activeContract || activeContract.scope !== "parallel_window" || !(await contractIsFresh(ctx.cwd, activeContract))) {
|
|
2651
|
+
const activated = await activateExecutionContract(ctx, feature, step);
|
|
2652
|
+
if (!activated.ok) {
|
|
2653
|
+
parallelRunActive = false;
|
|
2654
|
+
setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "并行窗口合同未激活" });
|
|
2655
|
+
ctx.ui.notify(`并行窗口合同未激活:${activated.reason}`, "info");
|
|
2656
|
+
|
|
2657
|
+
parallelRunActive = true;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2711
2661
|
setControlState(ctx, executingState(options.executingLabel));
|
|
2712
2662
|
if (preflight.briefing) await triggerWorkflow(snapshot, source, undefined, preflight.briefing);
|
|
2713
2663
|
else if (source === "start") await triggerWorkflow(snapshot, "start");
|
|
@@ -2719,6 +2669,22 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2719
2669
|
return { result, snapshot: parseJsonResult(result) };
|
|
2720
2670
|
}
|
|
2721
2671
|
|
|
2672
|
+
async function runDynamicPlan(cwd: string, request: Record<string, unknown>): Promise<ProcessResult> {
|
|
2673
|
+
assertRuntimeProjectBoundary(cwd);
|
|
2674
|
+
return runProcess(["ruby", path.join(resolveKitRoot(cwd), "workflow-orchestrator/scripts/dynamic_plan.rb")], cwd, {}, JSON.stringify(request));
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
async function startDynamicTurn(ctx: { ui: { notify(message: string, level: "info" | "error"): void } }, snapshot: Record<string, unknown>) {
|
|
2678
|
+
ctx.ui.notify(renderDynamicWorkflow(snapshot), "info");
|
|
2679
|
+
if (["ready", "cancelled"].includes(String(snapshot.delivery))) return;
|
|
2680
|
+
dynamicTurnRevision = Number(snapshot.revision);
|
|
2681
|
+
dynamicTurnProgress = String(snapshot.progress_key);
|
|
2682
|
+
activeContract = undefined;
|
|
2683
|
+
activeWorkflowTurn = true;
|
|
2684
|
+
controlState = executingState("执行中文工作流");
|
|
2685
|
+
await pi.sendUserMessage(dynamicWorkflowDirective(snapshot));
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2722
2688
|
async function inspectWorkflowWithRegistryRecovery(cwd: string): Promise<{ result: ProcessResult; snapshot: Record<string, unknown> | null }> {
|
|
2723
2689
|
let inspected = await inspectWorkflow(cwd);
|
|
2724
2690
|
const errors = Array.isArray(inspected.snapshot?.state_validation && (inspected.snapshot.state_validation as Record<string, unknown>).errors)
|
|
@@ -2818,7 +2784,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2818
2784
|
]));
|
|
2819
2785
|
if (existing?.ok === true && existing.run) {
|
|
2820
2786
|
parallelRunActive = true;
|
|
2821
|
-
return
|
|
2787
|
+
return refreshedParallelSnapshot(cwd, existing.run);
|
|
2822
2788
|
}
|
|
2823
2789
|
const revision = typeof snapshot.revision === "number" ? snapshot.revision : undefined;
|
|
2824
2790
|
if (!feature || !workflowWindow || revision === undefined) throw new Error("parallel window snapshot is incomplete");
|
|
@@ -2834,7 +2800,18 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2834
2800
|
throw new Error(prepared.stderr || prepared.stdout || "parallel run prepare failed");
|
|
2835
2801
|
}
|
|
2836
2802
|
parallelRunActive = true;
|
|
2837
|
-
return
|
|
2803
|
+
return refreshedParallelSnapshot(cwd, payload.run);
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
async function refreshedParallelSnapshot(cwd: string, run: unknown): Promise<Record<string, unknown>> {
|
|
2807
|
+
// prepare increments STATE. Recover the derived registry before building
|
|
2808
|
+
// the directive, including when resuming a previously stranded run.
|
|
2809
|
+
const fresh = await inspectWorkflowWithRegistryRecovery(cwd);
|
|
2810
|
+
if (!fresh.result.ok || fresh.snapshot?.action !== "run_parallel_window") {
|
|
2811
|
+
parallelRunActive = false;
|
|
2812
|
+
throw new Error(`并行准备后状态不可调度:${JSON.stringify(fresh.snapshot ?? fresh.result.stderr)}`);
|
|
2813
|
+
}
|
|
2814
|
+
return { ...fresh.snapshot, parallel_run: run };
|
|
2838
2815
|
}
|
|
2839
2816
|
|
|
2840
2817
|
async function triggerWorkflow(snapshot: Record<string, unknown>, source: "start" | "approval", requirementDraft?: string, briefing?: string) {
|
|
@@ -2867,6 +2844,10 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2867
2844
|
snapshot: Record<string, unknown>,
|
|
2868
2845
|
options: { doctorAlreadyPassed: boolean },
|
|
2869
2846
|
): Promise<WorkflowResumeDisposition> {
|
|
2847
|
+
if (snapshot.mode === "dynamic") {
|
|
2848
|
+
await startDynamicTurn(ctx, snapshot);
|
|
2849
|
+
return snapshot.delivery === "ready" ? "complete" : "execute";
|
|
2850
|
+
}
|
|
2870
2851
|
const disposition = workflowResumeDisposition(snapshot);
|
|
2871
2852
|
const stepId = String(selectedWorkflowStep(snapshot)?.id ?? "当前步骤");
|
|
2872
2853
|
if (disposition === "execute") {
|
|
@@ -2905,60 +2886,15 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2905
2886
|
ui: { confirm(title: string, body: string): Promise<boolean>; notify(message: string, level: "info" | "error"): void; setStatus(key: string, text: string | undefined): void };
|
|
2906
2887
|
}): Promise<boolean> {
|
|
2907
2888
|
const inspected = await inspectWorkflowWithRegistryRecovery(ctx.cwd);
|
|
2908
|
-
if (!inspected.result.ok || !inspected.snapshot || !isReviewableWorkflowSnapshot(inspected.snapshot)) return false;
|
|
2889
|
+
if (!inspected.result.ok || !inspected.snapshot || !isReviewableWorkflowSnapshot(inspected.snapshot, { allowAdvisory: true })) return false;
|
|
2909
2890
|
const step = compactStep(inspected.snapshot.current_step);
|
|
2910
2891
|
const stepId = typeof step?.id === "string" ? step.id : undefined;
|
|
2911
2892
|
const revision = typeof inspected.snapshot.revision === "number" ? inspected.snapshot.revision : undefined;
|
|
2912
2893
|
if (!stepId || revision === undefined) return false;
|
|
2913
2894
|
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
2895
|
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
|
|
2959
2896
|
"--write", "--json", "--expected-revision", String(revision),
|
|
2960
2897
|
...(checkpoint ? ["--confirm-checkpoint", "--runtime-confirmed"] : ["--complete-step", stepId]),
|
|
2961
|
-
...(cp3Prerequisites.length > 0 ? ["--confirm-test-prerequisites"] : []),
|
|
2962
2898
|
]);
|
|
2963
2899
|
if (!result.ok || !controlPlaneSucceeded(result)) {
|
|
2964
2900
|
setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "推进校验失败" });
|
|
@@ -3093,15 +3029,27 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3093
3029
|
ctx.ui.notify("当前步骤正在执行,等这一轮结束后再说「下一步」。", "info");
|
|
3094
3030
|
return;
|
|
3095
3031
|
}
|
|
3032
|
+
const dynamic = parseJsonResult(await runDynamicPlan(ctx.cwd, { action: "inspect" }));
|
|
3033
|
+
if (dynamic?.mode === "dynamic") {
|
|
3034
|
+
await startDynamicTurn(ctx, dynamic);
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
3096
3037
|
if (mode === "next") {
|
|
3097
3038
|
const report = await runDoctor(ctx);
|
|
3098
3039
|
if (!report.ok) {
|
|
3099
|
-
ctx.ui.notify(
|
|
3100
|
-
|
|
3040
|
+
ctx.ui.notify(`${"软门禁提示"}:\n${formatDoctorReport(report).slice(0, 6000)}`, "info");
|
|
3041
|
+
|
|
3101
3042
|
}
|
|
3102
3043
|
}
|
|
3044
|
+
// In advisory mode "next" is the user's request to continue, including
|
|
3045
|
+
// when the current output is missing or a checker failed.
|
|
3046
|
+
if (mode === "next") {
|
|
3047
|
+
const current = await diagnoseCurrentWorkflow(ctx.cwd, { skipWorkflowUpgrade: true });
|
|
3048
|
+
if (current.snapshot && !current.diagnosis.diagnoses.some(item => item.code === "ROLLOVER_PENDING")
|
|
3049
|
+
&& await confirmAndAdvanceWorkflowStep(ctx)) return;
|
|
3050
|
+
}
|
|
3103
3051
|
// Declining the template upgrade must not strand the step behind it.
|
|
3104
|
-
let skipWorkflowUpgrade =
|
|
3052
|
+
let skipWorkflowUpgrade = true;
|
|
3105
3053
|
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
3106
3054
|
const current = await diagnoseCurrentWorkflow(ctx.cwd, { skipWorkflowUpgrade });
|
|
3107
3055
|
const { diagnosis, snapshot } = current;
|
|
@@ -3536,11 +3484,11 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3536
3484
|
parallelDispatchFailureThisTurn = result.stderr || result.stdout || `parallel ${action} failed`;
|
|
3537
3485
|
return;
|
|
3538
3486
|
}
|
|
3539
|
-
|
|
3487
|
+
|
|
3540
3488
|
const refreshed = parseJsonResult(await runRuby(projectRoot, "workflow-orchestrator/scripts/parallel_run.rb", ["--action", "inspect", "--feature", feature, "--json"]));
|
|
3541
3489
|
const current = refreshed?.run as Record<string, unknown> | undefined;
|
|
3542
3490
|
const branches = Array.isArray(current?.branches) ? current.branches as Array<Record<string, unknown>> : [];
|
|
3543
|
-
if (branches.length > 0 && branches.every(branch => branch.status === "succeeded")) {
|
|
3491
|
+
if (branches.length > 0 && branches.every(branch => branch.status === "succeeded" || (["failed", "aborted"].includes(String(branch.status))))) {
|
|
3544
3492
|
const after = await inspectWorkflow(projectRoot);
|
|
3545
3493
|
const joinRevision = Number(after.snapshot?.revision);
|
|
3546
3494
|
if (Number.isInteger(joinRevision)) {
|
|
@@ -3702,8 +3650,8 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3702
3650
|
if (cliSessionResumeRequested && controlState.phase === "idle" && !pendingRollover) {
|
|
3703
3651
|
const report = await runDoctor(ctx);
|
|
3704
3652
|
if (!report.ok) {
|
|
3705
|
-
ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "
|
|
3706
|
-
|
|
3653
|
+
ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "info");
|
|
3654
|
+
|
|
3707
3655
|
}
|
|
3708
3656
|
const inspected = await inspectWorkflowWithRegistryRecovery(ctx.cwd);
|
|
3709
3657
|
if (!inspected.result.ok || !inspected.snapshot) {
|
|
@@ -3790,6 +3738,25 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3790
3738
|
|
|
3791
3739
|
pi.on("agent_end", async (event, ctx) => {
|
|
3792
3740
|
if (!activeWorkflowTurn || event.willContinue) return;
|
|
3741
|
+
if (dynamicTurnRevision !== undefined) {
|
|
3742
|
+
const before = dynamicTurnRevision;
|
|
3743
|
+
dynamicTurnRevision = undefined;
|
|
3744
|
+
activeWorkflowTurn = false;
|
|
3745
|
+
const snapshot = parseJsonResult(await runDynamicPlan(ctx.cwd, { action: "inspect" }));
|
|
3746
|
+
if (snapshot?.mode === "dynamic") {
|
|
3747
|
+
ctx.ui.notify(renderDynamicWorkflow(snapshot), "info");
|
|
3748
|
+
if (["run_task", "plan_work", "reconcile_plan"].includes(String(snapshot.action)) && Number(snapshot.revision) > before && snapshot.progress_key !== dynamicTurnProgress) {
|
|
3749
|
+
dynamicTurnRevision = Number(snapshot.revision);
|
|
3750
|
+
dynamicTurnProgress = String(snapshot.progress_key);
|
|
3751
|
+
activeWorkflowTurn = true;
|
|
3752
|
+
setControlState(ctx, executingState("继续执行中文工作流"));
|
|
3753
|
+
pi.sendMessage({ customType: "devflow-dynamic-continue", content: dynamicWorkflowDirective(snapshot), display: false }, { triggerTurn: true, deliverAs: "nextTurn" });
|
|
3754
|
+
} else {
|
|
3755
|
+
setControlState(ctx, snapshot.delivery === "ready" ? initialControlState() : waitingUserState("保留当前结果;等待决定、外部条件或执行恢复"));
|
|
3756
|
+
}
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3793
3760
|
const assistant = [...event.messages].reverse().find(message => (
|
|
3794
3761
|
message && typeof message === "object" && (message as { role?: unknown }).role === "assistant"
|
|
3795
3762
|
));
|
|
@@ -3806,7 +3773,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3806
3773
|
parallelTaskContexts.clear();
|
|
3807
3774
|
setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "并行子 Agent 未启动" });
|
|
3808
3775
|
ctx.ui.notify(
|
|
3809
|
-
`并行窗口未启动任何子 Agent,已停止等待不存在的生命周期事件;STATE 未推进。${parallelDispatchFailureThisTurn ? `\n原因:${parallelDispatchFailureThisTurn}` : ""}\n
|
|
3776
|
+
`并行窗口未启动任何子 Agent,已停止等待不存在的生命周期事件;STATE 未推进。${parallelDispatchFailureThisTurn ? `\n原因:${parallelDispatchFailureThisTurn}` : ""}\n请先检查本回合的状态、合同与分支派发阻塞证据;未派发不代表 Agent 未安装。仅确认环境不支持并行时才按 serial_fallback_order 串行执行。`,
|
|
3810
3777
|
"error",
|
|
3811
3778
|
);
|
|
3812
3779
|
return;
|
|
@@ -3823,6 +3790,10 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3823
3790
|
// UI confirmation may legitimately wait for the user longer than the
|
|
3824
3791
|
// host's event-handler deadline. Do not await it from agent_end or the
|
|
3825
3792
|
// host reports a timeout and the approval result is lost.
|
|
3793
|
+
{
|
|
3794
|
+
setControlState(ctx, waitingUserState("当前步骤结束;说「下一步」即可继续,检查缺项仅作提示"));
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3826
3797
|
void confirmAndAdvanceWorkflowStep(ctx).catch(error => {
|
|
3827
3798
|
setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "推进确认处理失败" });
|
|
3828
3799
|
ctx.ui.notify(`推进确认处理失败:${String(error)}`, "error");
|
|
@@ -3832,6 +3803,17 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3832
3803
|
|
|
3833
3804
|
pi.on("input", async (event, ctx) => {
|
|
3834
3805
|
if (event.source !== "interactive") return;
|
|
3806
|
+
if (event.text.trim() && !event.text.trim().startsWith("/") && !classifyNextStepPhrase(event.text)) {
|
|
3807
|
+
const dynamic = parseJsonResult(await runDynamicPlan(ctx.cwd, { action: "inspect" }));
|
|
3808
|
+
if (dynamic?.mode === "dynamic") {
|
|
3809
|
+
dynamicTurnRevision = Number(dynamic.revision);
|
|
3810
|
+
dynamicTurnProgress = String(dynamic.progress_key);
|
|
3811
|
+
activeWorkflowTurn = true;
|
|
3812
|
+
activeContract = undefined;
|
|
3813
|
+
setControlState(ctx, executingState("处理需求调整"));
|
|
3814
|
+
return { text: `${dynamicWorkflowDirective(dynamic)}\n用户补充:${event.text}`, handled: false };
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3835
3817
|
const controlAction = classifyNaturalControlAction(event.text);
|
|
3836
3818
|
const stepNote = event.text.trim();
|
|
3837
3819
|
if (controlState.intent === "workflow-execute" && !controlAction && stepNote && !stepNote.startsWith("/") && !classifyNextStepPhrase(stepNote)) {
|
|
@@ -3940,15 +3922,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3940
3922
|
ctx.ui.notify("当前状态缺少可确认的 revision 或步骤,拒绝推进。", "error");
|
|
3941
3923
|
return { handled: true };
|
|
3942
3924
|
}
|
|
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
|
-
}
|
|
3925
|
+
|
|
3952
3926
|
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
|
|
3953
3927
|
"--write",
|
|
3954
3928
|
"--json",
|
|
@@ -4033,6 +4007,19 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
4033
4007
|
});
|
|
4034
4008
|
|
|
4035
4009
|
pi.on("tool_call", async (event, ctx) => {
|
|
4010
|
+
if (dynamicTurnRevision !== undefined) {
|
|
4011
|
+
const name = String(event.toolName).toLowerCase();
|
|
4012
|
+
if (isDirectControlMutation(name, event.input, ctx.cwd) || isShellControlMutation(name, event.input) || name === "devflow_transition") {
|
|
4013
|
+
return { block: true, reason: "动态计划由 Core 独占写入;请使用 devflow_plan,不能手工写入通过状态。" };
|
|
4014
|
+
}
|
|
4015
|
+
if (name === "task" && requestedTaskAgents(event.input).names.includes("devflow-code-reviewer")) {
|
|
4016
|
+
const snapshot = parseJsonResult(await runDynamicPlan(ctx.cwd, { action: "inspect" }));
|
|
4017
|
+
const task = snapshot?.current_step as Record<string, unknown> | undefined;
|
|
4018
|
+
if (task?.kind === "review" && task.status === "running") dynamicReviewBindings.set(event.toolCallId, { taskId: String(task.id), attempt: String(task.attempt), revision: Number(snapshot?.revision) });
|
|
4019
|
+
}
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
const decision = await (async () => {
|
|
4036
4023
|
const toolName = String(event.toolName).toLowerCase();
|
|
4037
4024
|
const toolInput = event.input && typeof event.input === "object" ? event.input as Record<string, unknown> : undefined;
|
|
4038
4025
|
const toolCallId = String(event.toolCallId ?? "");
|
|
@@ -4048,11 +4035,6 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
4048
4035
|
const feature = typeof toolInput?.feature === "string" ? toolInput.feature.trim() : "";
|
|
4049
4036
|
const phase = typeof toolInput?.phase === "string" && toolInput.phase.trim() ? toolInput.phase.trim() : "默认需求阶段";
|
|
4050
4037
|
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
4038
|
if (!toolCallId) return { block: true, reason: "bootstrap runtime authorization is missing a tool-call binding" };
|
|
4057
4039
|
runtimeBootstrapAuthorizations.add(toolCallId);
|
|
4058
4040
|
bootstrapAuthorized = true;
|
|
@@ -4061,14 +4043,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
4061
4043
|
const branchContext = parallelBranchContext(event.input);
|
|
4062
4044
|
if (branchContext) parallelTaskContexts.set(event.toolCallId, branchContext);
|
|
4063
4045
|
}
|
|
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
|
-
}
|
|
4046
|
+
|
|
4072
4047
|
if (toolName === "task") {
|
|
4073
4048
|
const requested = requestedTaskAgents(event.input);
|
|
4074
4049
|
if (requested.missing) {
|
|
@@ -4094,43 +4069,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
4094
4069
|
if (controlState.phase === "planning" && !allowedInDraft) {
|
|
4095
4070
|
return { block: true, reason: planningToolBlockReason(toolName) };
|
|
4096
4071
|
}
|
|
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
|
-
}
|
|
4072
|
+
|
|
4134
4073
|
if (activeContract && controlState.phase === "executing") contractAgentCalls += requested.names.length;
|
|
4135
4074
|
} else if (
|
|
4136
4075
|
controlState.phase === "planning" &&
|
|
@@ -4160,41 +4099,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
4160
4099
|
"Devflow 状态迁移只能由运行时在用户明确批准已展示的步骤或 checkpoint 后执行;LLM 不得自行推进。",
|
|
4161
4100
|
};
|
|
4162
4101
|
}
|
|
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
|
-
}
|
|
4102
|
+
|
|
4198
4103
|
// Feature artifacts written while no step is running are never adopted by
|
|
4199
4104
|
// the workflow (zhaoshang, 2026-10-22). Refuse early in the interactive
|
|
4200
4105
|
// main session; subagents (no UI), review-feedback edits and controlled
|
|
@@ -4229,31 +4134,8 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
4229
4134
|
}
|
|
4230
4135
|
}
|
|
4231
4136
|
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
|
-
}
|
|
4137
|
+
|
|
4138
|
+
|
|
4257
4139
|
if (isDirectControlMutation(toolName, event.input, ctx.cwd) || isShellControlMutation(toolName, event.input)) {
|
|
4258
4140
|
return {
|
|
4259
4141
|
block: true,
|
|
@@ -4277,8 +4159,9 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
4277
4159
|
if (gate.stdout) {
|
|
4278
4160
|
try {
|
|
4279
4161
|
const payload = JSON.parse(gate.stdout) as {
|
|
4280
|
-
hookSpecificOutput?: { permissionDecision?: string; permissionDecisionReason?: string };
|
|
4162
|
+
hookSpecificOutput?: { permissionDecision?: string; permissionDecisionReason?: string; additionalContext?: string };
|
|
4281
4163
|
};
|
|
4164
|
+
if (payload.hookSpecificOutput?.additionalContext) ctx.ui.notify(payload.hookSpecificOutput.additionalContext, "info");
|
|
4282
4165
|
if (payload.hookSpecificOutput?.permissionDecision === "deny") {
|
|
4283
4166
|
return {
|
|
4284
4167
|
block: true,
|
|
@@ -4292,9 +4175,27 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
4292
4175
|
};
|
|
4293
4176
|
}
|
|
4294
4177
|
}
|
|
4178
|
+
})();
|
|
4179
|
+
if (decision?.block && !String(decision.reason).startsWith("bootstrap ") && decision.reason !== "User declined runtime bootstrap authorization.") {
|
|
4180
|
+
ctx.ui.notify(`软门禁提示:${decision.reason}`, "info");
|
|
4181
|
+
return;
|
|
4182
|
+
}
|
|
4183
|
+
return decision;
|
|
4295
4184
|
});
|
|
4296
4185
|
|
|
4297
4186
|
pi.on("tool_result", async (event, ctx) => {
|
|
4187
|
+
const dynamicBinding = dynamicReviewBindings.get(event.toolCallId);
|
|
4188
|
+
if (event.toolName === "task" && dynamicBinding) {
|
|
4189
|
+
dynamicReviewBindings.delete(event.toolCallId);
|
|
4190
|
+
const results = (event.details as { results?: Array<Record<string, unknown>> } | undefined)?.results;
|
|
4191
|
+
const child = results?.length === 1 ? results[0] : undefined;
|
|
4192
|
+
if (!event.isError && child?.agent === "devflow-code-reviewer" && child.exitCode === 0 && child.truncated === false && typeof child.output === "string") {
|
|
4193
|
+
try {
|
|
4194
|
+
const report = JSON.parse(child.output);
|
|
4195
|
+
if (typeof report.summary === "string" && Array.isArray(report.findings)) dynamicReviewResults.set(event.toolCallId, { ...dynamicBinding, report });
|
|
4196
|
+
} catch { /* Invalid reviewer output never becomes a pass. */ }
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4298
4199
|
if (event.toolName === "task") {
|
|
4299
4200
|
const binding = pendingReviewTasks.get(event.toolCallId);
|
|
4300
4201
|
if (binding) {
|
|
@@ -4485,20 +4386,13 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
4485
4386
|
if (params.expectedRevision === undefined || !params.delta) {
|
|
4486
4387
|
return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "propose requires expectedRevision and delta" });
|
|
4487
4388
|
}
|
|
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
|
-
}
|
|
4389
|
+
|
|
4496
4390
|
args.push("--expected-revision", String(params.expectedRevision), "--delta-json", JSON.stringify(params.delta));
|
|
4497
4391
|
// `write` is an explicit dry-run switch. Omit it only when the caller
|
|
4498
4392
|
// explicitly asks for a preview; the normal approved proposal remains
|
|
4499
4393
|
// a Core-owned write.
|
|
4500
4394
|
if (params.write !== false) args.push("--write");
|
|
4501
|
-
|
|
4395
|
+
|
|
4502
4396
|
}
|
|
4503
4397
|
if (params.action === "confirm") {
|
|
4504
4398
|
if (params.expectedRevision === undefined || !params.nodeId || !params.evidence) {
|
|
@@ -4602,9 +4496,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
4602
4496
|
}),
|
|
4603
4497
|
approval: args => args.action === "validate" ? "read" : "write",
|
|
4604
4498
|
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
|
-
}
|
|
4499
|
+
|
|
4608
4500
|
if (params.action === "validate") {
|
|
4609
4501
|
const process = await runPython(ctx.cwd, "workflow-orchestrator/scripts/api_test_matrix.py", ["validate", "--feature", params.feature, "--json"]);
|
|
4610
4502
|
const payload = parseJsonResult(process);
|
|
@@ -4767,7 +4659,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
4767
4659
|
"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
4660
|
"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
4661
|
"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
|
-
"
|
|
4662
|
+
"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
4663
|
].join(" "),
|
|
4772
4664
|
parameters: z.object({
|
|
4773
4665
|
action: z.enum(["validate", "validate_human", "run", "validate_results", "gc_runs"]),
|
|
@@ -5214,6 +5106,59 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
5214
5106
|
},
|
|
5215
5107
|
});
|
|
5216
5108
|
|
|
5109
|
+
pi.registerTool({
|
|
5110
|
+
name: "devflow_plan",
|
|
5111
|
+
label: "Devflow 中文工作流",
|
|
5112
|
+
description: "Inspect or execute the Core-owned dynamic plan. Never accepts caller-written pass/approval results. Use revise for task proposals, claim before work, report for activity results, verify for actual executable checks, review_result for a captured independent review, and decision only for genuine user choices. Task fields: id,title,kind(discover/implement/verify/review),requirements(R-ids),dependencies,inputs,outputs,checks([{argv:string[],timeout?:number}]),cancelled?,reason?.",
|
|
5113
|
+
parameters: z.object({
|
|
5114
|
+
action: z.enum(["inspect", "revise", "claim", "report", "verify", "review_result", "recover", "wait", "resume", "decision"]),
|
|
5115
|
+
expected_revision: z.number().int().optional(),
|
|
5116
|
+
task_id: z.string().optional(),
|
|
5117
|
+
attempt: z.string().optional(),
|
|
5118
|
+
tasks: z.array(z.object({
|
|
5119
|
+
id: z.string(), title: z.string(), kind: z.enum(["discover", "implement", "repair", "verify", "review"]),
|
|
5120
|
+
requirements: z.array(z.string()), dependencies: z.array(z.string()), inputs: z.array(z.string()), outputs: z.array(z.string()),
|
|
5121
|
+
checks: z.array(z.object({ argv: z.array(z.string()).min(1), timeout: z.number().int().optional() })),
|
|
5122
|
+
cancelled: z.boolean().optional(), reason: z.string().optional(),
|
|
5123
|
+
})).optional(),
|
|
5124
|
+
reason: z.string().optional(), summary: z.string().optional(), task_call_id: z.string().optional(),
|
|
5125
|
+
question: z.string().optional(), options: z.array(z.string()).optional(),
|
|
5126
|
+
task_ids: z.array(z.string()).optional(),
|
|
5127
|
+
scope_decision_id: z.string().optional(), remove_requirements: z.array(z.string()).optional(), approval_option: z.string().optional(),
|
|
5128
|
+
}),
|
|
5129
|
+
approval: "write",
|
|
5130
|
+
async execute(_id, rawParams, _signal, _onUpdate, ctx) {
|
|
5131
|
+
const params = rawParams as { action: string; task_call_id?: string; task_id?: string; attempt?: string; expected_revision?: number; question?: string };
|
|
5132
|
+
const request: Record<string, unknown> = { ...params };
|
|
5133
|
+
if (params.action === "review_result") {
|
|
5134
|
+
const captured = params.task_call_id ? dynamicReviewResults.get(params.task_call_id) : undefined;
|
|
5135
|
+
if (!captured || captured.taskId !== params.task_id || captured.attempt !== params.attempt || captured.revision !== params.expected_revision) {
|
|
5136
|
+
return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "No current independent reviewer result is bound to this task attempt. Dispatch the read-only reviewer first." });
|
|
5137
|
+
}
|
|
5138
|
+
request.runtime_review = true;
|
|
5139
|
+
request.report = captured.report;
|
|
5140
|
+
}
|
|
5141
|
+
let result = await runDynamicPlan(ctx.cwd, request);
|
|
5142
|
+
let snapshot = parseJsonResult(result);
|
|
5143
|
+
if (snapshot?.ok === true && params.action === "review_result" && params.task_call_id) dynamicReviewResults.delete(params.task_call_id);
|
|
5144
|
+
if (snapshot?.ok === true && params.action === "decision") {
|
|
5145
|
+
const decisions = snapshot.decisions as Array<Record<string, unknown>>;
|
|
5146
|
+
const pending = decisions.find(item => item.status === "pending" && item.question === params.question);
|
|
5147
|
+
if (pending) {
|
|
5148
|
+
const removals = pending.remove_requirements as string[] | undefined;
|
|
5149
|
+
const question = String(pending.question) + (removals?.length ? `\n拟移除验收要求:${removals.join("、")};仅选择「${pending.approval_option}」会授权移除。` : "");
|
|
5150
|
+
const choice = await ctx.ui.select(question, pending.options as string[]);
|
|
5151
|
+
if (choice) {
|
|
5152
|
+
result = await runDynamicPlan(ctx.cwd, { action: "decide", expected_revision: snapshot.revision, decision_id: pending.id, choice, runtime_decision: true });
|
|
5153
|
+
snapshot = parseJsonResult(result);
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
}
|
|
5157
|
+
if (snapshot?.mode === "dynamic") ctx.ui.setWidget?.("devflow-plan", projectControlWidget(renderDynamicWorkflow(snapshot).split("\n")));
|
|
5158
|
+
return renderResult(result);
|
|
5159
|
+
},
|
|
5160
|
+
});
|
|
5161
|
+
|
|
5217
5162
|
pi.registerTool({
|
|
5218
5163
|
name: "devflow_bootstrap",
|
|
5219
5164
|
label: "Devflow Bootstrap",
|
|
@@ -5221,11 +5166,24 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
5221
5166
|
parameters: z.object({
|
|
5222
5167
|
feature: z.string().min(1),
|
|
5223
5168
|
phase: z.string().min(1).optional(),
|
|
5224
|
-
flow: z.enum(["feature-development", "acceptance-remediation", "regression-acceptance"]).default("
|
|
5169
|
+
flow: z.enum(["dynamic", "feature-development", "acceptance-remediation", "regression-acceptance"]).default("dynamic"),
|
|
5225
5170
|
force: z.boolean().default(false),
|
|
5226
5171
|
}),
|
|
5227
5172
|
approval: "write",
|
|
5228
|
-
async execute(_id,
|
|
5173
|
+
async execute(_id, rawParams, _signal, _onUpdate, ctx) {
|
|
5174
|
+
const params = rawParams as { flow: string; feature: string; phase?: string; force: boolean };
|
|
5175
|
+
if (params.flow === "dynamic") {
|
|
5176
|
+
const result = await runDynamicPlan(ctx.cwd, { action: "initialize", feature: params.feature, phase: params.phase });
|
|
5177
|
+
const snapshot = parseJsonResult(result);
|
|
5178
|
+
if (snapshot?.mode === "dynamic") {
|
|
5179
|
+
dynamicTurnRevision = Number(snapshot.revision);
|
|
5180
|
+
dynamicTurnProgress = String(snapshot.progress_key);
|
|
5181
|
+
activeWorkflowTurn = true;
|
|
5182
|
+
activeContract = undefined;
|
|
5183
|
+
setControlState(ctx, executingState("梳理需求并生成中文步骤"));
|
|
5184
|
+
}
|
|
5185
|
+
return renderResult(result);
|
|
5186
|
+
}
|
|
5229
5187
|
// Consume the one-shot runtime authorization before running Core's
|
|
5230
5188
|
// atomic bootstrap sequence. A retry must obtain a fresh user approval.
|
|
5231
5189
|
runtimeBootstrapAuthorizations.delete(String(_id));
|
|
@@ -5320,9 +5278,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
5320
5278
|
if (!params.step) {
|
|
5321
5279
|
return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "step is required" });
|
|
5322
5280
|
}
|
|
5323
|
-
|
|
5324
|
-
return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "complete-step requires explicit user confirmation (confirm=true)" });
|
|
5325
|
-
}
|
|
5281
|
+
|
|
5326
5282
|
const snapshot = parseJsonResult(await runRuby(ctx.cwd, "workflow-orchestrator/scripts/inspect_state.rb", ["--json"]));
|
|
5327
5283
|
const currentStep = snapshot?.current_step && typeof snapshot.current_step === "object"
|
|
5328
5284
|
? (snapshot.current_step as Record<string, unknown>).id
|
|
@@ -5341,9 +5297,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
5341
5297
|
if (activeContract?.feature === feature && activeContract.step === params.step && activeContract.contractPath) evidenceArgs.push("--contract", activeContract.contractPath);
|
|
5342
5298
|
const evidence = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/machine_evidence.rb", evidenceArgs);
|
|
5343
5299
|
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
|
-
}
|
|
5300
|
+
|
|
5347
5301
|
}
|
|
5348
5302
|
args.push("--complete-step", params.step);
|
|
5349
5303
|
} else if (params.action === "confirm-checkpoint") {
|
|
@@ -5598,6 +5552,15 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
5598
5552
|
|
|
5599
5553
|
const devflowCommand = async (args: string, ctx: DevflowCommandContext): Promise<void> => {
|
|
5600
5554
|
const trimmed = args.trim();
|
|
5555
|
+
if (["", "status", "control", "explain-state"].includes(trimmed)) {
|
|
5556
|
+
const dynamic = parseJsonResult(await runDynamicPlan(ctx.cwd, { action: "inspect" }));
|
|
5557
|
+
if (dynamic?.mode === "dynamic") {
|
|
5558
|
+
const lines = renderDynamicWorkflow(dynamic, trimmed === "control").split("\n");
|
|
5559
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
5560
|
+
ctx.ui.setWidget?.("devflow-plan", projectControlWidget(lines));
|
|
5561
|
+
return;
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
5601
5564
|
if (trimmed === "" || trimmed === "status") {
|
|
5602
5565
|
await showWorkflowOverview(ctx);
|
|
5603
5566
|
return;
|
|
@@ -5670,6 +5633,17 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
5670
5633
|
if (trimmed === "abandon" || trimmed.startsWith("abandon ") || trimmed === "cancel" || trimmed.startsWith("cancel ")) {
|
|
5671
5634
|
const inspected = await inspectWorkflowWithRegistryRecovery(ctx.cwd);
|
|
5672
5635
|
const snapshot = inspected.snapshot;
|
|
5636
|
+
if (snapshot?.mode === "dynamic") {
|
|
5637
|
+
const result = await runDynamicPlan(ctx.cwd, { action: "cancel", expected_revision: snapshot.revision, runtime_cancel: true, reason: trimmed.replace(/^\S+\s*/, "") || "用户主动终止当前需求" });
|
|
5638
|
+
const cancelled = parseJsonResult(result);
|
|
5639
|
+
if (cancelled?.ok) {
|
|
5640
|
+
dynamicTurnRevision = undefined;
|
|
5641
|
+
activeWorkflowTurn = false;
|
|
5642
|
+
setControlState(ctx, initialControlState());
|
|
5643
|
+
ctx.ui.notify("已取消需求,保留已有工作与失败证据。", "info");
|
|
5644
|
+
} else ctx.ui.notify(String(cancelled?.reason ?? result.stderr), "error");
|
|
5645
|
+
return;
|
|
5646
|
+
}
|
|
5673
5647
|
const revision = typeof snapshot?.revision === "number" ? snapshot.revision : undefined;
|
|
5674
5648
|
const feature = typeof snapshot?.current_feature === "string" ? snapshot.current_feature : undefined;
|
|
5675
5649
|
const status = typeof snapshot?.status === "string" ? snapshot.status : undefined;
|
|
@@ -5734,8 +5708,8 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
5734
5708
|
if (trimmed === "resume") {
|
|
5735
5709
|
const report = await runDoctor(ctx);
|
|
5736
5710
|
if (!report.ok) {
|
|
5737
|
-
ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "
|
|
5738
|
-
|
|
5711
|
+
ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "info");
|
|
5712
|
+
|
|
5739
5713
|
}
|
|
5740
5714
|
const inspected = await inspectWorkflowWithRegistryRecovery(ctx.cwd);
|
|
5741
5715
|
if (!inspected.result.ok || !inspected.snapshot) {
|
|
@@ -5773,100 +5747,19 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
5773
5747
|
}
|
|
5774
5748
|
if (trimmed.startsWith("start ")) {
|
|
5775
5749
|
const [feature, requestedPhase] = trimmed.slice(6).split("|", 2).map(value => value.trim());
|
|
5776
|
-
if (!feature) {
|
|
5777
|
-
ctx.ui.notify("
|
|
5778
|
-
return;
|
|
5779
|
-
}
|
|
5780
|
-
const phase = requestedPhase || defaultPhaseId(feature);
|
|
5781
|
-
if (!validPhaseId(phase)) {
|
|
5782
|
-
ctx.ui.notify("Phase ID 必须是 1-64 位字母、数字、下划线或连字符,且不得包含路径分隔符。", "error");
|
|
5750
|
+
if (!feature || (requestedPhase && !validPhaseId(requestedPhase))) {
|
|
5751
|
+
ctx.ui.notify("用法:/devflow start <需求名>|<可选阶段标识>", "error");
|
|
5783
5752
|
return;
|
|
5784
5753
|
}
|
|
5785
5754
|
const report = await runDoctor(ctx);
|
|
5786
|
-
if (!report.ok)
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
const existingFeature = existing.snapshot?.current_feature;
|
|
5792
|
-
const previousWorkflowComplete = ["workflow_complete", "workflow_cancelled"].includes(existing.snapshot?.action as string);
|
|
5793
|
-
if (
|
|
5794
|
-
!previousWorkflowComplete &&
|
|
5795
|
-
typeof existingFeature === "string" &&
|
|
5796
|
-
existingFeature !== feature
|
|
5797
|
-
) {
|
|
5798
|
-
ctx.ui.notify(
|
|
5799
|
-
`当前仍有未完成需求:${existingFeature}。请在对话中继续处理当前步骤,完成后再创建新需求:${feature}。`,
|
|
5800
|
-
"error",
|
|
5801
|
-
);
|
|
5802
|
-
return;
|
|
5803
|
-
}
|
|
5804
|
-
// This dry-run happens before init_workflow.rb, so a rejected feature
|
|
5805
|
-
// can never overwrite generated workflow files belonging to an active
|
|
5806
|
-
// owner or a legacy phase directory that has no trustworthy owner.
|
|
5807
|
-
const ownership = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/phase_ownership.rb", [
|
|
5808
|
-
"claim",
|
|
5809
|
-
"--feature",
|
|
5810
|
-
feature,
|
|
5811
|
-
"--phase",
|
|
5812
|
-
phase,
|
|
5813
|
-
"--flow",
|
|
5814
|
-
"feature-development",
|
|
5815
|
-
"--json",
|
|
5816
|
-
]);
|
|
5817
|
-
if (!controlPlaneSucceeded(ownership)) {
|
|
5818
|
-
ctx.ui.notify((ownership.stderr || ownership.stdout).slice(0, 6000), "error");
|
|
5819
|
-
return;
|
|
5820
|
-
}
|
|
5821
|
-
const workflow = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/init_workflow.rb", [
|
|
5822
|
-
"--write",
|
|
5823
|
-
"--json",
|
|
5824
|
-
]);
|
|
5825
|
-
if (!controlPlaneSucceeded(workflow)) {
|
|
5826
|
-
ctx.ui.notify(workflow.stderr || workflow.stdout, "error");
|
|
5755
|
+
if (!report.ok) ctx.ui.notify(formatDoctorReport(report).slice(0, 6000), "info");
|
|
5756
|
+
const result = await runDynamicPlan(ctx.cwd, { action: "initialize", feature, phase: requestedPhase || undefined });
|
|
5757
|
+
const snapshot = parseJsonResult(result);
|
|
5758
|
+
if (!snapshot?.ok) {
|
|
5759
|
+
ctx.ui.notify(String(snapshot?.reason ?? result.stderr), "error");
|
|
5827
5760
|
return;
|
|
5828
5761
|
}
|
|
5829
|
-
|
|
5830
|
-
"--feature",
|
|
5831
|
-
feature,
|
|
5832
|
-
"--phase",
|
|
5833
|
-
phase,
|
|
5834
|
-
"--flow",
|
|
5835
|
-
"feature-development",
|
|
5836
|
-
"--write",
|
|
5837
|
-
"--json",
|
|
5838
|
-
...(previousWorkflowComplete ? ["--force"] : []),
|
|
5839
|
-
]);
|
|
5840
|
-
if (!controlPlaneSucceeded(state)) {
|
|
5841
|
-
ctx.ui.notify((state.stderr || state.stdout).slice(0, 6000), "error");
|
|
5842
|
-
return;
|
|
5843
|
-
}
|
|
5844
|
-
const artifacts = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/sync_artifacts.rb", [
|
|
5845
|
-
"--write",
|
|
5846
|
-
"--json",
|
|
5847
|
-
]);
|
|
5848
|
-
if (!controlPlaneSucceeded(artifacts)) {
|
|
5849
|
-
ctx.ui.notify((artifacts.stderr || artifacts.stdout).slice(0, 6000), "error");
|
|
5850
|
-
return;
|
|
5851
|
-
}
|
|
5852
|
-
const inspected = await inspectWorkflow(ctx.cwd);
|
|
5853
|
-
if (!inspected.result.ok || !inspected.snapshot) {
|
|
5854
|
-
ctx.ui.notify(
|
|
5855
|
-
`Devflow initialized but inspector failed: ${inspected.result.stderr || inspected.result.stdout || "invalid JSON"}`,
|
|
5856
|
-
"error",
|
|
5857
|
-
);
|
|
5858
|
-
return;
|
|
5859
|
-
}
|
|
5860
|
-
setControlState(ctx, planningState("requirement-draft"));
|
|
5861
|
-
await pi.sendUserMessage(
|
|
5862
|
-
[
|
|
5863
|
-
"<devflow-requirement-draft-directive>",
|
|
5864
|
-
`Devflow 已创建 Feature ${feature}。当前为安全的需求梳理阶段:允许澄清和只读证据收集,不得写入项目或迁移状态。`,
|
|
5865
|
-
"请先基于功能名说明你需要的最少业务背景,并邀请用户直接补充目标、使用者、约束和验收结果。收到用户的下一段需求描述后,运行时会将其原文注入 requirement-triage 并启动该步骤。",
|
|
5866
|
-
"不要让用户输入命令;也不要把普通需求文本当成批准或 checkpoint 确认。",
|
|
5867
|
-
"</devflow-requirement-draft-directive>",
|
|
5868
|
-
].join("\n"),
|
|
5869
|
-
);
|
|
5762
|
+
await startDynamicTurn(ctx, snapshot);
|
|
5870
5763
|
return;
|
|
5871
5764
|
}
|
|
5872
5765
|
ctx.ui.notify(
|