@guanlin4924/devflow-pi 0.1.12 → 0.1.14
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 +1 -4
- package/package.json +1 -1
- package/plugins/devflow-core/extensions/devflow-core.ts +235 -148
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +47 -15
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/control_io.rb +42 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_state.rb +21 -2
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/phase_ownership.rb +154 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/repair_control_plane.rb +35 -26
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/sync_artifacts.rb +18 -7
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +33 -8
package/README.md
CHANGED
|
@@ -29,9 +29,6 @@ devflow-pi
|
|
|
29
29
|
/devflow explain-state
|
|
30
30
|
/devflow recover
|
|
31
31
|
/devflow start <feature>|<phase>
|
|
32
|
-
/devflow continue
|
|
33
|
-
/devflow advance
|
|
34
|
-
/devflow checkpoint
|
|
35
32
|
/devflow resume
|
|
36
33
|
/devflow-usage
|
|
37
34
|
/usage-stats
|
|
@@ -118,7 +115,7 @@ bash .skills/install.sh --apply --force-config
|
|
|
118
115
|
|
|
119
116
|
首次使用时,在 OMP 的模型角色界面中分别为 `smol`、`task`、`slow` 选择具体模型;`modelRoleStorage: project` 会把选择保存在项目作用域。建议分别使用快速探索模型、代码实现模型和独立推理/审查模型。配置完成后以 `/devflow doctor` 的 `models:` 行为准。
|
|
120
117
|
|
|
121
|
-
`/devflow start`
|
|
118
|
+
`/devflow start` 会先进入安全的需求梳理:可澄清并只读检查,不写入项目。用户随后直接补充需求,原文会被带入入口分诊并启动当前步骤。每个步骤结束后,系统展示产物和证据;用户明确回复“批准执行”才会推进并运行下一步,回复修改意见或“拒绝执行”则保持原步骤。普通需求描述绝不会自动确认 checkpoint。`/devflow explain-state` 发现控制面和制品 registry 漂移时会给出只读恢复计划;用户审查后可用 `/devflow recover` 确认仅修复可由文件系统证明的 registry 字段,它绝不推进步骤或自动通过 checkpoint。恢复已有流程使用 `/devflow resume`,它同样只恢复状态而不自动执行。
|
|
122
119
|
|
|
123
120
|
## Marketplace 能力包
|
|
124
121
|
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
3
3
|
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
@@ -41,6 +41,12 @@ const READ_ONLY_TOOLS = new Set([
|
|
|
41
41
|
"usage_stats",
|
|
42
42
|
]);
|
|
43
43
|
|
|
44
|
+
const REQUIREMENT_DRAFT_TOOLS = new Set([...READ_ONLY_TOOLS, "ask"]);
|
|
45
|
+
// A Todo update changes only the client-visible task list, never the project
|
|
46
|
+
// or Devflow state. Permit it during planning so a harmless checklist cannot
|
|
47
|
+
// produce a misleading "blocked" warning during initialization.
|
|
48
|
+
const PLANNING_INTERNAL_TOOLS = new Set(["todo"]);
|
|
49
|
+
|
|
44
50
|
const PLANNING_AGENTS = new Set(["project-scout", "requirements-auditor", "contract-auditor"]);
|
|
45
51
|
|
|
46
52
|
const STATE_MUTATION_TOOLS = new Set([
|
|
@@ -91,11 +97,47 @@ export function statusText(state: SessionControlState): string {
|
|
|
91
97
|
|
|
92
98
|
export function isPlanningToolAllowed(toolName: string, taskAgents: string[] = []): boolean {
|
|
93
99
|
const normalized = toolName.toLowerCase();
|
|
100
|
+
if (PLANNING_INTERNAL_TOOLS.has(normalized)) return true;
|
|
94
101
|
if (READ_ONLY_TOOLS.has(normalized)) return true;
|
|
95
102
|
if (normalized !== "task") return false;
|
|
96
103
|
return taskAgents.length > 0 && taskAgents.every(agent => PLANNING_AGENTS.has(agent));
|
|
97
104
|
}
|
|
98
105
|
|
|
106
|
+
/** Requirement drafting may inspect with a shell, but never mutate a project. */
|
|
107
|
+
export function isReadOnlyShellCommand(command: string): boolean {
|
|
108
|
+
return command.trim() !== "" && !shellMutationPaths(command, ".").mutating;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function isRequirementDraftToolAllowed(toolName: string, input: unknown, taskAgents: string[] = []): boolean {
|
|
112
|
+
const normalized = toolName.toLowerCase();
|
|
113
|
+
if (PLANNING_INTERNAL_TOOLS.has(normalized)) return true;
|
|
114
|
+
if (["bash", "shell", "exec_command"].includes(normalized)) {
|
|
115
|
+
const record = input && typeof input === "object" ? input as Record<string, unknown> : {};
|
|
116
|
+
return isReadOnlyShellCommand(String(record.command ?? record.cmd ?? ""));
|
|
117
|
+
}
|
|
118
|
+
if (REQUIREMENT_DRAFT_TOOLS.has(normalized)) return true;
|
|
119
|
+
return normalized === "task" && taskAgents.length > 0 && taskAgents.every(agent => PLANNING_AGENTS.has(agent));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** `xd://devflow_contract` is an in-memory OMP control resource, not a
|
|
123
|
+
* project file. The planner must be able to read and activate it while the
|
|
124
|
+
* filesystem remains read-only; otherwise initialization deadlocks before it
|
|
125
|
+
* can create a bounded execution contract. */
|
|
126
|
+
export function isInternalDevflowContractResource(toolName: string, input: unknown): boolean {
|
|
127
|
+
if (!["read", "write"].includes(toolName.toLowerCase()) || !input || typeof input !== "object") return false;
|
|
128
|
+
const record = input as Record<string, unknown>;
|
|
129
|
+
return [record.path, record.uri, record.resource].some(value => value === "xd://devflow_contract");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type NaturalControlAction = "approve" | "reject";
|
|
133
|
+
|
|
134
|
+
export function classifyNaturalControlAction(input: string): NaturalControlAction | null {
|
|
135
|
+
const text = input.trim();
|
|
136
|
+
if (/^(?:批准|确认|同意|通过)(?:\s*(?:(?:full|完整|全量)?\s*初始化|执行|继续|推进|当前(?:方案|步骤|计划)))?[。!!]?$/i.test(text)) return "approve";
|
|
137
|
+
if (/^(?:拒绝|不同意|暂不|需要修改|退回)(?:执行|继续|推进|当前(?:方案|步骤|计划))?[。!!]?$/.test(text)) return "reject";
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
99
141
|
export function isStateMutationTool(toolName: string): boolean {
|
|
100
142
|
return STATE_MUTATION_TOOLS.has(toolName.toLowerCase());
|
|
101
143
|
}
|
|
@@ -230,6 +272,7 @@ const REQUIRED_AGENT_NAMES = new Set(Object.keys(REQUIRED_AGENTS));
|
|
|
230
272
|
const REQUIRED_SCRIPTS = [
|
|
231
273
|
"workflow-orchestrator/scripts/init_workflow.rb",
|
|
232
274
|
"workflow-orchestrator/scripts/init_state.rb",
|
|
275
|
+
"workflow-orchestrator/scripts/phase_ownership.rb",
|
|
233
276
|
"workflow-orchestrator/scripts/inspect_state.rb",
|
|
234
277
|
"workflow-orchestrator/scripts/advance_state.rb",
|
|
235
278
|
"workflow-orchestrator/scripts/sync_artifacts.rb",
|
|
@@ -237,6 +280,17 @@ const REQUIRED_SCRIPTS = [
|
|
|
237
280
|
"workflow-context-sync/scripts/context_sync_cursor.py",
|
|
238
281
|
];
|
|
239
282
|
|
|
283
|
+
function defaultPhaseId(feature: string): string {
|
|
284
|
+
// STATE.md is project-global, so a generic name such as "delivery" is not
|
|
285
|
+
// a safe default. Keep the generated id filesystem-safe and deterministic
|
|
286
|
+
// without requiring a user to learn a phase-naming command.
|
|
287
|
+
return `feature-${createHash("sha256").update(feature, "utf8").digest("hex").slice(0, 12)}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function validPhaseId(phase: string): boolean {
|
|
291
|
+
return /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(phase);
|
|
292
|
+
}
|
|
293
|
+
|
|
240
294
|
function resolvePluginRoot(): string {
|
|
241
295
|
const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
242
296
|
return path.resolve(process.env.DEVFLOW_PLUGIN_ROOT || path.resolve(moduleDirectory, "../.."));
|
|
@@ -464,6 +518,13 @@ function parseJsonResult(result: ProcessResult): Record<string, unknown> | null
|
|
|
464
518
|
}
|
|
465
519
|
}
|
|
466
520
|
|
|
521
|
+
/** Ruby control-plane scripts report expected validation failures as JSON with
|
|
522
|
+
* `ok: false` and intentionally keep a zero process exit code. Never mistake
|
|
523
|
+
* that structured failure for a successful state mutation. */
|
|
524
|
+
function controlPlaneSucceeded(result: ProcessResult): boolean {
|
|
525
|
+
return result.ok && parseJsonResult(result)?.ok === true;
|
|
526
|
+
}
|
|
527
|
+
|
|
467
528
|
function compactStep(value: unknown): Record<string, unknown> | null {
|
|
468
529
|
if (!value || typeof value !== "object") return null;
|
|
469
530
|
const step = value as Record<string, unknown>;
|
|
@@ -488,7 +549,7 @@ function compactStep(value: unknown): Record<string, unknown> | null {
|
|
|
488
549
|
};
|
|
489
550
|
}
|
|
490
551
|
|
|
491
|
-
function workflowDirective(snapshot: Record<string, unknown>, source: "start" | "
|
|
552
|
+
function workflowDirective(snapshot: Record<string, unknown>, source: "start" | "approval", requirementDraft?: string): string {
|
|
492
553
|
const selectedStep = compactStep(snapshot.next_step) ?? compactStep(snapshot.current_step);
|
|
493
554
|
const envelope = {
|
|
494
555
|
source,
|
|
@@ -502,19 +563,31 @@ function workflowDirective(snapshot: Record<string, unknown>, source: "start" |
|
|
|
502
563
|
failure_branch: snapshot.failure_branch,
|
|
503
564
|
reason: snapshot.reason,
|
|
504
565
|
recovery: snapshot.recovery,
|
|
566
|
+
requirement_draft: requirementDraft,
|
|
505
567
|
};
|
|
506
568
|
return [
|
|
507
569
|
"<devflow-runtime-directive>",
|
|
508
570
|
"Devflow 已通过启动预检。读取 skill://workflow-pipeline,并只执行当前项目的一个工作流步骤。",
|
|
509
571
|
"先调用 devflow_state(action=\"inspect\") 重新确认 revision,再由 workflow-orchestrator 路由当前步骤。",
|
|
510
572
|
"只按 inspector 返回的当前 step 加载所需 Skill;不得调用 devflow_transition、不得推进下一步骤、不得替用户通过人工 checkpoint。",
|
|
511
|
-
"
|
|
573
|
+
"完成本步骤的真实产物后,报告证据、未决项和验证结果并停止。请用户审查后明确回复“批准执行”才可推进;回复修改意见或“拒绝执行”则不得推进。",
|
|
512
574
|
"当前步骤信封:",
|
|
513
575
|
JSON.stringify(envelope, null, 2),
|
|
514
576
|
"</devflow-runtime-directive>",
|
|
515
577
|
].join("\n");
|
|
516
578
|
}
|
|
517
579
|
|
|
580
|
+
function projectInitializationDirective(summary: string): string {
|
|
581
|
+
return [
|
|
582
|
+
"<devflow-project-initialization-directive>",
|
|
583
|
+
"用户已批准完整项目初始化。现在处于执行模式;不要再创建 Todo、不要读取或写入 xd://devflow_contract。",
|
|
584
|
+
"执行 skill://workflow-project-context,按已确认的项目边界建立 .planning/PROJECT.md、.planning/navigation/INDEX.md、.context/spec/index.md,并按用户选择建立 full 代码索引。",
|
|
585
|
+
"只写项目初始化所需的 .planning/ 与 .context/ 控制产物;不得改动业务源码、依赖或配置。完成后运行项目初始化校验,报告生成文件和验证结果并停止,等待用户审查。",
|
|
586
|
+
summary,
|
|
587
|
+
"</devflow-project-initialization-directive>",
|
|
588
|
+
].join("\n");
|
|
589
|
+
}
|
|
590
|
+
|
|
518
591
|
function explainWorkflowSnapshot(snapshot: Record<string, unknown> | null): string {
|
|
519
592
|
if (!snapshot) return "Devflow 当前没有可解释的工作流状态。可使用 /devflow start <feature> 创建新 Feature。";
|
|
520
593
|
const step = compactStep(snapshot.next_step) ?? compactStep(snapshot.current_step);
|
|
@@ -853,8 +926,12 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
853
926
|
return { result, snapshot: parseJsonResult(result) };
|
|
854
927
|
}
|
|
855
928
|
|
|
856
|
-
|
|
857
|
-
|
|
929
|
+
let activeWorkflowTurn = false;
|
|
930
|
+
let pendingProjectInitialization: string | undefined;
|
|
931
|
+
|
|
932
|
+
async function triggerWorkflow(snapshot: Record<string, unknown>, source: "start" | "approval", requirementDraft?: string) {
|
|
933
|
+
activeWorkflowTurn = true;
|
|
934
|
+
await pi.sendUserMessage(workflowDirective(snapshot, source, requirementDraft));
|
|
858
935
|
}
|
|
859
936
|
|
|
860
937
|
pi.setLabel("Devflow Project Runtime");
|
|
@@ -879,8 +956,84 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
879
956
|
systemPrompt: [...event.systemPrompt, DEVFLOW_CHINESE_LANGUAGE_POLICY],
|
|
880
957
|
}));
|
|
881
958
|
|
|
959
|
+
pi.on("agent_end", async (event, ctx) => {
|
|
960
|
+
if (!activeWorkflowTurn || event.willContinue) return;
|
|
961
|
+
activeWorkflowTurn = false;
|
|
962
|
+
if (controlState.phase === "executing") {
|
|
963
|
+
setControlState(ctx, waitingUserState("等待用户审查步骤产物并明确决定"));
|
|
964
|
+
ctx.ui.notify("当前步骤已结束。请审查输出:明确回复“批准执行”推进,或回复修改意见/“拒绝执行”保持当前步骤。", "info");
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
|
|
882
968
|
pi.on("input", async (event, ctx) => {
|
|
883
969
|
if (event.source !== "interactive") return;
|
|
970
|
+
const controlAction = classifyNaturalControlAction(event.text);
|
|
971
|
+
if (controlState.intent === "requirement-draft" && controlState.phase === "planning") {
|
|
972
|
+
const requirementDraft = event.text.trim();
|
|
973
|
+
if (!requirementDraft) return { handled: true };
|
|
974
|
+
const inspected = await inspectWorkflow(ctx.cwd);
|
|
975
|
+
if (!inspected.result.ok || !inspected.snapshot) {
|
|
976
|
+
ctx.ui.notify(`需求分诊启动失败:${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
|
|
977
|
+
return { handled: true };
|
|
978
|
+
}
|
|
979
|
+
setControlState(ctx, executingState("执行需求入口分诊"));
|
|
980
|
+
await triggerWorkflow(inspected.snapshot, "start", requirementDraft);
|
|
981
|
+
return { handled: true };
|
|
982
|
+
}
|
|
983
|
+
if (controlState.intent === "project-init" && controlState.phase === "planning") {
|
|
984
|
+
if (controlAction === "approve") {
|
|
985
|
+
if (!pendingProjectInitialization) {
|
|
986
|
+
ctx.ui.notify("项目初始化缺少已确认的边界信息,请重新发起初始化。", "error");
|
|
987
|
+
return { handled: true };
|
|
988
|
+
}
|
|
989
|
+
setControlState(ctx, executingState("执行已批准的项目初始化"));
|
|
990
|
+
activeWorkflowTurn = true;
|
|
991
|
+
return { text: projectInitializationDirective(pendingProjectInitialization), handled: false };
|
|
992
|
+
}
|
|
993
|
+
if (controlAction === "reject") {
|
|
994
|
+
ctx.ui.notify("已保留初始化规划,未创建任何项目控制文件。请直接说明需要调整的初始化范围。", "info");
|
|
995
|
+
return { handled: true };
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
if (controlState.phase === "awaiting-approval" || controlState.phase === "waiting-user") {
|
|
999
|
+
if (controlAction === "reject") {
|
|
1000
|
+
ctx.ui.notify("已保留当前步骤和产物。请直接说明需要修改的内容;未执行任何推进。", "info");
|
|
1001
|
+
return { handled: true };
|
|
1002
|
+
}
|
|
1003
|
+
if (controlAction === "approve") {
|
|
1004
|
+
const inspected = await inspectWorkflow(ctx.cwd);
|
|
1005
|
+
if (!inspected.result.ok || !inspected.snapshot) {
|
|
1006
|
+
ctx.ui.notify(`审批处理失败:${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
|
|
1007
|
+
return { handled: true };
|
|
1008
|
+
}
|
|
1009
|
+
const revision = typeof inspected.snapshot.revision === "number" ? inspected.snapshot.revision : undefined;
|
|
1010
|
+
const checkpoint = inspected.snapshot.status === "waiting_checkpoint";
|
|
1011
|
+
const step = compactStep(inspected.snapshot.next_step) ?? compactStep(inspected.snapshot.current_step);
|
|
1012
|
+
const stepId = typeof step?.id === "string" ? step.id : undefined;
|
|
1013
|
+
if (revision === undefined || (!checkpoint && !stepId)) {
|
|
1014
|
+
ctx.ui.notify("当前状态缺少可确认的 revision 或步骤,拒绝推进。", "error");
|
|
1015
|
+
return { handled: true };
|
|
1016
|
+
}
|
|
1017
|
+
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
|
|
1018
|
+
"--write", "--json", "--expected-revision", String(revision),
|
|
1019
|
+
...(checkpoint ? ["--confirm-checkpoint"] : ["--complete-step", stepId!]),
|
|
1020
|
+
]);
|
|
1021
|
+
if (!result.ok) {
|
|
1022
|
+
setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "推进校验失败" });
|
|
1023
|
+
ctx.ui.notify(`未推进:${result.stderr || result.stdout}`, "error");
|
|
1024
|
+
return { handled: true };
|
|
1025
|
+
}
|
|
1026
|
+
const after = await inspectWorkflow(ctx.cwd);
|
|
1027
|
+
if (!after.result.ok || !after.snapshot || after.snapshot.action === "workflow_complete") {
|
|
1028
|
+
setControlState(ctx, initialControlState());
|
|
1029
|
+
ctx.ui.notify(after.snapshot?.action === "workflow_complete" ? "工作流已完成。" : "已推进,但读取下一步骤失败。", "info");
|
|
1030
|
+
return { handled: true };
|
|
1031
|
+
}
|
|
1032
|
+
setControlState(ctx, executingState("执行已批准的下一工作流步骤"));
|
|
1033
|
+
await triggerWorkflow(after.snapshot, "approval");
|
|
1034
|
+
return { handled: true };
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
884
1037
|
const intent = classifyNaturalLanguageIntent(event.text);
|
|
885
1038
|
if (intent === "explain-state") {
|
|
886
1039
|
const inspected = await inspectWorkflow(ctx.cwd);
|
|
@@ -894,6 +1047,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
894
1047
|
if (intent === "project-init") {
|
|
895
1048
|
const summary = await collectProjectInitialization(ctx);
|
|
896
1049
|
if (!summary) return { handled: true };
|
|
1050
|
+
pendingProjectInitialization = summary;
|
|
897
1051
|
const state = planningState("project-init");
|
|
898
1052
|
setControlState(ctx, state);
|
|
899
1053
|
return {
|
|
@@ -913,7 +1067,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
913
1067
|
return {
|
|
914
1068
|
text: [
|
|
915
1069
|
"<devflow-planner-directive>",
|
|
916
|
-
"
|
|
1070
|
+
"当前是需求梳理(只读)阶段。可澄清、收集证据、派发只读审查代理,且可使用不修改文件的 shell 检查。",
|
|
917
1071
|
"不得开始工作流、不得写入产物、不得调用实现代理或状态迁移。",
|
|
918
1072
|
"把用户目标、约束、未知项、反例和下一步最少问题分开列出;缺少证据时不得猜测。",
|
|
919
1073
|
event.text,
|
|
@@ -944,7 +1098,10 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
944
1098
|
if (shadows.length > 0) {
|
|
945
1099
|
return { block: true, reason: `Devflow task agent shadow detected: ${shadows.join("; ")}` };
|
|
946
1100
|
}
|
|
947
|
-
|
|
1101
|
+
const allowedInDraft = controlState.intent === "requirement-draft"
|
|
1102
|
+
? isRequirementDraftToolAllowed(toolName, event.input, requested.names)
|
|
1103
|
+
: isPlanningToolAllowed(toolName, requested.names);
|
|
1104
|
+
if (controlState.phase === "planning" && !allowedInDraft) {
|
|
948
1105
|
return { block: true, reason: planningToolBlockReason(toolName) };
|
|
949
1106
|
}
|
|
950
1107
|
if (
|
|
@@ -957,8 +1114,11 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
957
1114
|
} else if (
|
|
958
1115
|
controlState.phase === "planning" &&
|
|
959
1116
|
toolName !== "devflow_contract" &&
|
|
1117
|
+
!isInternalDevflowContractResource(toolName, event.input) &&
|
|
960
1118
|
!(toolName === "devflow_evidence" && String((event.input as Record<string, unknown> | undefined)?.action) !== "create") &&
|
|
961
|
-
!
|
|
1119
|
+
!(controlState.intent === "requirement-draft"
|
|
1120
|
+
? isRequirementDraftToolAllowed(toolName, event.input)
|
|
1121
|
+
: isPlanningToolAllowed(toolName))
|
|
962
1122
|
) {
|
|
963
1123
|
return { block: true, reason: planningToolBlockReason(toolName) };
|
|
964
1124
|
}
|
|
@@ -966,7 +1126,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
966
1126
|
return {
|
|
967
1127
|
block: true,
|
|
968
1128
|
reason:
|
|
969
|
-
"Devflow
|
|
1129
|
+
"Devflow 状态迁移只能由运行时在用户明确批准已展示的步骤或 checkpoint 后执行;LLM 不得自行推进。",
|
|
970
1130
|
};
|
|
971
1131
|
}
|
|
972
1132
|
if (toolName.startsWith("devflow_")) return;
|
|
@@ -1246,35 +1406,54 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1246
1406
|
description: "Initialize workflow definitions, STATE.md, and the artifact registry for a project feature.",
|
|
1247
1407
|
parameters: z.object({
|
|
1248
1408
|
feature: z.string().min(1),
|
|
1249
|
-
phase: z.string().min(1).
|
|
1409
|
+
phase: z.string().min(1).optional(),
|
|
1250
1410
|
flow: z.string().min(1).default("feature-development"),
|
|
1251
1411
|
force: z.boolean().default(false),
|
|
1252
1412
|
}),
|
|
1253
1413
|
approval: "write",
|
|
1254
1414
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1415
|
+
const phase = params.phase?.trim() || defaultPhaseId(params.feature);
|
|
1416
|
+
if (!validPhaseId(phase)) {
|
|
1417
|
+
return {
|
|
1418
|
+
content: [{ type: "text" as const, text: "Phase ID 必须是 1-64 位字母、数字、下划线或连字符,且不得包含路径分隔符。" }],
|
|
1419
|
+
isError: true,
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1255
1422
|
const force = params.force ? ["--force"] : [];
|
|
1423
|
+
const ownership = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/phase_ownership.rb", [
|
|
1424
|
+
"claim",
|
|
1425
|
+
"--feature",
|
|
1426
|
+
params.feature,
|
|
1427
|
+
"--phase",
|
|
1428
|
+
phase,
|
|
1429
|
+
"--flow",
|
|
1430
|
+
params.flow,
|
|
1431
|
+
"--json",
|
|
1432
|
+
]);
|
|
1433
|
+
if (!controlPlaneSucceeded(ownership)) return renderResult(ownership, { stage: "phase-ownership" });
|
|
1256
1434
|
const workflow = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/init_workflow.rb", [
|
|
1257
1435
|
"--write",
|
|
1258
1436
|
"--json",
|
|
1259
1437
|
...force,
|
|
1260
1438
|
]);
|
|
1261
|
-
if (!workflow
|
|
1439
|
+
if (!controlPlaneSucceeded(workflow)) return renderResult(workflow, { stage: "workflow" });
|
|
1262
1440
|
const state = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/init_state.rb", [
|
|
1263
1441
|
"--feature",
|
|
1264
1442
|
params.feature,
|
|
1265
1443
|
"--phase",
|
|
1266
|
-
|
|
1444
|
+
phase,
|
|
1267
1445
|
"--flow",
|
|
1268
1446
|
params.flow,
|
|
1269
1447
|
"--write",
|
|
1270
1448
|
"--json",
|
|
1271
1449
|
...force,
|
|
1272
1450
|
]);
|
|
1273
|
-
if (!state
|
|
1451
|
+
if (!controlPlaneSucceeded(state)) return renderResult(state, { stage: "state" });
|
|
1274
1452
|
const artifacts = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/sync_artifacts.rb", [
|
|
1275
1453
|
"--write",
|
|
1276
1454
|
"--json",
|
|
1277
1455
|
]);
|
|
1456
|
+
if (!controlPlaneSucceeded(artifacts)) return renderResult(artifacts, { stage: "artifacts" });
|
|
1278
1457
|
return renderResult(artifacts, {
|
|
1279
1458
|
stage: "complete",
|
|
1280
1459
|
workflow: workflow.stdout,
|
|
@@ -1359,7 +1538,8 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1359
1538
|
approval: "write",
|
|
1360
1539
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1361
1540
|
const args = ["--write", "--confirm", "--expected-revision", String(params.expectedRevision), "--json"];
|
|
1362
|
-
|
|
1541
|
+
const state = safeControlPath(ctx.cwd, params.state, "state");
|
|
1542
|
+
if (state) args.push("--state", state);
|
|
1363
1543
|
return renderResult(
|
|
1364
1544
|
await runRuby(ctx.cwd, "workflow-orchestrator/scripts/repair_control_plane.rb", args),
|
|
1365
1545
|
{ action: "repair-control-plane" },
|
|
@@ -1444,9 +1624,9 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1444
1624
|
const step = compactStep(inspected.snapshot?.next_step) ?? compactStep(inspected.snapshot?.current_step);
|
|
1445
1625
|
const stepId = typeof step?.id === "string" ? step.id : "当前步骤";
|
|
1446
1626
|
if (inspected.result.ok && status === "in_progress") {
|
|
1447
|
-
ctx.ui.notify(`控制面状态一致,无需恢复。当前步骤是 ${stepId}
|
|
1627
|
+
ctx.ui.notify(`控制面状态一致,无需恢复。当前步骤是 ${stepId};请审查当前产物后明确回复“批准执行”或提出修改意见。`, "info");
|
|
1448
1628
|
} else if (inspected.result.ok && status === "waiting_checkpoint") {
|
|
1449
|
-
ctx.ui.notify(
|
|
1629
|
+
ctx.ui.notify("控制面状态一致,无需恢复。当前正在等待 checkpoint;请审查产物后明确回复“批准执行”或提出修改意见。", "info");
|
|
1450
1630
|
} else {
|
|
1451
1631
|
ctx.ui.notify("当前状态没有可安全自动执行的恢复计划;请运行 /devflow explain-state,并按其中的阻塞证据处理。", "error");
|
|
1452
1632
|
}
|
|
@@ -1471,12 +1651,13 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1471
1651
|
ctx.ui.notify(`控制面恢复失败:${result.stderr || result.stdout}`, "error");
|
|
1472
1652
|
return;
|
|
1473
1653
|
}
|
|
1474
|
-
ctx.ui.notify("控制面 registry
|
|
1654
|
+
ctx.ui.notify("控制面 registry 已修复;未推进工作流。请审查当前步骤产物后明确回复“批准执行”或提出修改意见。", "info");
|
|
1475
1655
|
return;
|
|
1476
1656
|
}
|
|
1477
1657
|
if (trimmed === "project-init") {
|
|
1478
1658
|
const summary = await collectProjectInitialization(ctx);
|
|
1479
1659
|
if (!summary) return;
|
|
1660
|
+
pendingProjectInitialization = summary;
|
|
1480
1661
|
setControlState(ctx, planningState("project-init"));
|
|
1481
1662
|
await pi.sendUserMessage(
|
|
1482
1663
|
[
|
|
@@ -1515,138 +1696,22 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1515
1696
|
}
|
|
1516
1697
|
setControlState(ctx, waitingUserState("等待用户审查步骤产物"));
|
|
1517
1698
|
ctx.ui.notify(
|
|
1518
|
-
`Devflow 已恢复到 ${String((compactStep(inspected.snapshot.next_step) ?? compactStep(inspected.snapshot.current_step))?.id ?? "当前步骤")}
|
|
1699
|
+
`Devflow 已恢复到 ${String((compactStep(inspected.snapshot.next_step) ?? compactStep(inspected.snapshot.current_step))?.id ?? "当前步骤")};请审查当前产物后明确回复“批准执行”,或直接提出修改意见。`,
|
|
1519
1700
|
"info",
|
|
1520
1701
|
);
|
|
1521
1702
|
return;
|
|
1522
1703
|
}
|
|
1523
|
-
if (trimmed === "continue") {
|
|
1524
|
-
const report = await runDoctor(ctx);
|
|
1525
|
-
if (!report.ok) {
|
|
1526
|
-
ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "error");
|
|
1527
|
-
return;
|
|
1528
|
-
}
|
|
1529
|
-
const inspected = await inspectWorkflow(ctx.cwd);
|
|
1530
|
-
if (!inspected.result.ok || !inspected.snapshot) {
|
|
1531
|
-
ctx.ui.notify(`Devflow continue failed: ${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
|
|
1532
|
-
return;
|
|
1533
|
-
}
|
|
1534
|
-
if (inspected.snapshot.action === "workflow_complete") {
|
|
1535
|
-
ctx.ui.notify("Devflow workflow is already complete.", "info");
|
|
1536
|
-
return;
|
|
1537
|
-
}
|
|
1538
|
-
if (inspected.snapshot.status === "waiting_checkpoint") {
|
|
1539
|
-
setControlState(ctx, { intent: "workflow-execute", phase: "awaiting-approval", label: "等待用户确认 checkpoint" });
|
|
1540
|
-
ctx.ui.notify("当前步骤正在等待 checkpoint;请审查产物后使用 /devflow checkpoint。", "warning");
|
|
1541
|
-
return;
|
|
1542
|
-
}
|
|
1543
|
-
setControlState(ctx, executingState("执行当前工作流步骤(完成后停止)"));
|
|
1544
|
-
ctx.ui.notify("仅启动当前步骤;模型被禁止迁移状态或继续下一步骤。", "info");
|
|
1545
|
-
await triggerWorkflow(inspected.snapshot, "continue");
|
|
1546
|
-
return;
|
|
1547
|
-
}
|
|
1548
|
-
if (trimmed === "advance") {
|
|
1549
|
-
const inspected = await inspectWorkflow(ctx.cwd);
|
|
1550
|
-
if (!inspected.result.ok || !inspected.snapshot) {
|
|
1551
|
-
ctx.ui.notify(`Devflow advance failed: ${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
|
|
1552
|
-
return;
|
|
1553
|
-
}
|
|
1554
|
-
if (inspected.snapshot.action === "workflow_complete" && inspected.snapshot.source_flow) {
|
|
1555
|
-
const revision = typeof inspected.snapshot.revision === "number" ? inspected.snapshot.revision : undefined;
|
|
1556
|
-
if (revision === undefined) {
|
|
1557
|
-
ctx.ui.notify("补救分支缺少 revision,拒绝返回主流程。", "error");
|
|
1558
|
-
return;
|
|
1559
|
-
}
|
|
1560
|
-
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
|
|
1561
|
-
"--write",
|
|
1562
|
-
"--json",
|
|
1563
|
-
"--expected-revision",
|
|
1564
|
-
String(revision),
|
|
1565
|
-
"--exit-failure-branch",
|
|
1566
|
-
]);
|
|
1567
|
-
if (!result.ok) {
|
|
1568
|
-
ctx.ui.notify(`补救分支返回失败:${result.stderr || result.stdout}`, "error");
|
|
1569
|
-
return;
|
|
1570
|
-
}
|
|
1571
|
-
ctx.ui.notify("补救分支已返回主流程;仍需审查 reverify 结果,并在 CP-4 明确确认后才能继续。", "info");
|
|
1572
|
-
return;
|
|
1573
|
-
}
|
|
1574
|
-
if (inspected.snapshot.status === "waiting_checkpoint") {
|
|
1575
|
-
ctx.ui.notify("当前状态等待 checkpoint;请使用 /devflow checkpoint。", "warning");
|
|
1576
|
-
return;
|
|
1577
|
-
}
|
|
1578
|
-
const step = compactStep(inspected.snapshot.next_step) ?? compactStep(inspected.snapshot.current_step);
|
|
1579
|
-
const stepId = typeof step?.id === "string" ? step.id : undefined;
|
|
1580
|
-
const revision = typeof inspected.snapshot.revision === "number" ? inspected.snapshot.revision : undefined;
|
|
1581
|
-
if (!stepId || revision === undefined) {
|
|
1582
|
-
ctx.ui.notify("当前状态没有可推进的步骤或 revision;请先执行 /devflow explain-state。", "error");
|
|
1583
|
-
return;
|
|
1584
|
-
}
|
|
1585
|
-
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
|
|
1586
|
-
"--write",
|
|
1587
|
-
"--json",
|
|
1588
|
-
"--expected-revision",
|
|
1589
|
-
String(revision),
|
|
1590
|
-
"--complete-step",
|
|
1591
|
-
stepId,
|
|
1592
|
-
]);
|
|
1593
|
-
if (!result.ok) {
|
|
1594
|
-
setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "推进校验失败" });
|
|
1595
|
-
ctx.ui.notify(`步骤未推进:${result.stderr || result.stdout}`, "error");
|
|
1596
|
-
return;
|
|
1597
|
-
}
|
|
1598
|
-
const after = await inspectWorkflow(ctx.cwd);
|
|
1599
|
-
setControlState(
|
|
1600
|
-
ctx,
|
|
1601
|
-
after.snapshot?.status === "waiting_checkpoint"
|
|
1602
|
-
? { intent: "workflow-execute", phase: "awaiting-approval", label: "等待用户确认 checkpoint" }
|
|
1603
|
-
: waitingUserState("等待用户审查步骤产物"),
|
|
1604
|
-
);
|
|
1605
|
-
ctx.ui.notify(`已完成步骤 ${stepId} 的真实产物校验并推进状态。${after.snapshot?.status === "waiting_checkpoint" ? "下一步需要 /devflow checkpoint。" : "下一步请审查后使用 /devflow continue。"}`, "info");
|
|
1606
|
-
return;
|
|
1607
|
-
}
|
|
1608
|
-
if (trimmed === "checkpoint") {
|
|
1609
|
-
const inspected = await inspectWorkflow(ctx.cwd);
|
|
1610
|
-
if (!inspected.result.ok || !inspected.snapshot) {
|
|
1611
|
-
ctx.ui.notify(`Devflow checkpoint failed: ${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
|
|
1612
|
-
return;
|
|
1613
|
-
}
|
|
1614
|
-
if (inspected.snapshot.status !== "waiting_checkpoint") {
|
|
1615
|
-
ctx.ui.notify("当前并不等待 checkpoint;请使用 /devflow explain-state 查看原因。", "warning");
|
|
1616
|
-
return;
|
|
1617
|
-
}
|
|
1618
|
-
const revision = typeof inspected.snapshot.revision === "number" ? inspected.snapshot.revision : undefined;
|
|
1619
|
-
if (revision === undefined) {
|
|
1620
|
-
ctx.ui.notify("当前状态缺少 revision,拒绝确认 checkpoint。", "error");
|
|
1621
|
-
return;
|
|
1622
|
-
}
|
|
1623
|
-
const confirmed = await ctx.ui.confirm("确认 Devflow checkpoint", "你已审查当前步骤产物并同意推进到下一步骤吗?");
|
|
1624
|
-
if (!confirmed) {
|
|
1625
|
-
ctx.ui.notify("checkpoint 未确认;状态保持不变。", "info");
|
|
1626
|
-
return;
|
|
1627
|
-
}
|
|
1628
|
-
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
|
|
1629
|
-
"--write",
|
|
1630
|
-
"--json",
|
|
1631
|
-
"--expected-revision",
|
|
1632
|
-
String(revision),
|
|
1633
|
-
"--confirm-checkpoint",
|
|
1634
|
-
]);
|
|
1635
|
-
if (!result.ok) {
|
|
1636
|
-
setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "checkpoint 校验失败" });
|
|
1637
|
-
ctx.ui.notify(`checkpoint 未推进:${result.stderr || result.stdout}`, "error");
|
|
1638
|
-
return;
|
|
1639
|
-
}
|
|
1640
|
-
setControlState(ctx, waitingUserState("等待用户确认下一步骤"));
|
|
1641
|
-
ctx.ui.notify("checkpoint 已由用户确认;下一步骤尚未执行,请使用 /devflow continue。", "info");
|
|
1642
|
-
return;
|
|
1643
|
-
}
|
|
1644
1704
|
if (trimmed.startsWith("start ")) {
|
|
1645
|
-
const [feature,
|
|
1705
|
+
const [feature, requestedPhase] = trimmed.slice(6).split("|", 2).map(value => value.trim());
|
|
1646
1706
|
if (!feature) {
|
|
1647
1707
|
ctx.ui.notify("Usage: /devflow start <feature>|<phase>", "error");
|
|
1648
1708
|
return;
|
|
1649
1709
|
}
|
|
1710
|
+
const phase = requestedPhase || defaultPhaseId(feature);
|
|
1711
|
+
if (!validPhaseId(phase)) {
|
|
1712
|
+
ctx.ui.notify("Phase ID 必须是 1-64 位字母、数字、下划线或连字符,且不得包含路径分隔符。", "error");
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1650
1715
|
const report = await runDoctor(ctx);
|
|
1651
1716
|
if (!report.ok) {
|
|
1652
1717
|
ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "error");
|
|
@@ -1661,16 +1726,33 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1661
1726
|
existingFeature !== feature
|
|
1662
1727
|
) {
|
|
1663
1728
|
ctx.ui.notify(
|
|
1664
|
-
|
|
1729
|
+
`当前仍有未完成需求:${existingFeature}。请在对话中继续处理当前步骤,完成后再创建新需求:${feature}。`,
|
|
1665
1730
|
"error",
|
|
1666
1731
|
);
|
|
1667
1732
|
return;
|
|
1668
1733
|
}
|
|
1734
|
+
// This dry-run happens before init_workflow.rb, so a rejected feature
|
|
1735
|
+
// can never overwrite generated workflow files belonging to an active
|
|
1736
|
+
// owner or a legacy phase directory that has no trustworthy owner.
|
|
1737
|
+
const ownership = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/phase_ownership.rb", [
|
|
1738
|
+
"claim",
|
|
1739
|
+
"--feature",
|
|
1740
|
+
feature,
|
|
1741
|
+
"--phase",
|
|
1742
|
+
phase,
|
|
1743
|
+
"--flow",
|
|
1744
|
+
"feature-development",
|
|
1745
|
+
"--json",
|
|
1746
|
+
]);
|
|
1747
|
+
if (!controlPlaneSucceeded(ownership)) {
|
|
1748
|
+
ctx.ui.notify((ownership.stderr || ownership.stdout).slice(0, 6000), "error");
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1669
1751
|
const workflow = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/init_workflow.rb", [
|
|
1670
1752
|
"--write",
|
|
1671
1753
|
"--json",
|
|
1672
1754
|
]);
|
|
1673
|
-
if (!workflow
|
|
1755
|
+
if (!controlPlaneSucceeded(workflow)) {
|
|
1674
1756
|
ctx.ui.notify(workflow.stderr || workflow.stdout, "error");
|
|
1675
1757
|
return;
|
|
1676
1758
|
}
|
|
@@ -1685,7 +1767,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1685
1767
|
"--json",
|
|
1686
1768
|
...(previousWorkflowComplete ? ["--force"] : []),
|
|
1687
1769
|
]);
|
|
1688
|
-
if (!state
|
|
1770
|
+
if (!controlPlaneSucceeded(state)) {
|
|
1689
1771
|
ctx.ui.notify((state.stderr || state.stdout).slice(0, 6000), "error");
|
|
1690
1772
|
return;
|
|
1691
1773
|
}
|
|
@@ -1693,7 +1775,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1693
1775
|
"--write",
|
|
1694
1776
|
"--json",
|
|
1695
1777
|
]);
|
|
1696
|
-
if (!artifacts
|
|
1778
|
+
if (!controlPlaneSucceeded(artifacts)) {
|
|
1697
1779
|
ctx.ui.notify((artifacts.stderr || artifacts.stdout).slice(0, 6000), "error");
|
|
1698
1780
|
return;
|
|
1699
1781
|
}
|
|
@@ -1706,14 +1788,19 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1706
1788
|
return;
|
|
1707
1789
|
}
|
|
1708
1790
|
setControlState(ctx, planningState("requirement-draft"));
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1791
|
+
await pi.sendUserMessage(
|
|
1792
|
+
[
|
|
1793
|
+
"<devflow-requirement-draft-directive>",
|
|
1794
|
+
`Devflow 已创建 Feature ${feature}。当前为安全的需求梳理阶段:允许澄清和只读证据收集,不得写入项目或迁移状态。`,
|
|
1795
|
+
"请先基于功能名说明你需要的最少业务背景,并邀请用户直接补充目标、使用者、约束和验收结果。收到用户的下一段需求描述后,运行时会将其原文注入 requirement-triage 并启动该步骤。",
|
|
1796
|
+
"不要让用户输入命令;也不要把普通需求文本当成批准或 checkpoint 确认。",
|
|
1797
|
+
"</devflow-requirement-draft-directive>",
|
|
1798
|
+
].join("\n"),
|
|
1712
1799
|
);
|
|
1713
1800
|
return;
|
|
1714
1801
|
}
|
|
1715
1802
|
ctx.ui.notify(
|
|
1716
|
-
"Usage: /devflow doctor | status | explain-state | recover | project-init | start <feature>|<phase> | resume
|
|
1803
|
+
"Usage: /devflow doctor | status | explain-state | recover | project-init | start <feature>|<phase> | resume",
|
|
1717
1804
|
"warning",
|
|
1718
1805
|
);
|
|
1719
1806
|
},
|