@guanlin4924/devflow-pi 0.1.99 → 0.1.100
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 -1
- package/package.json +1 -1
- package/plugins/devflow-core/extensions/devflow-core.ts +77 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_workflow.rb +3 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/review_preflight.py +8 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/startup_preflight.py +6 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +6 -2
- package/src/constants.ts +1 -1
- package/src/workflow-diagnosis.ts +17 -1
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -2672,7 +2672,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2672
2672
|
const details = issues.length > 0
|
|
2673
2673
|
? issues.map(issue => `- ${describeStartupIssue(issue && typeof issue === "object" ? issue as Record<string, unknown> : {})}`).join("\n")
|
|
2674
2674
|
: "- 开工预检输出无法解析";
|
|
2675
|
-
ctx.ui.notify(`开工预检未通过:\n${details}
|
|
2675
|
+
ctx.ui.notify(`开工预检未通过:\n${details}\n可以说「下一步」,由 Devflow 诊断并给出处理办法。`, "error");
|
|
2676
2676
|
return { ok: false };
|
|
2677
2677
|
}
|
|
2678
2678
|
const facts = payload.facts && typeof payload.facts === "object" ? payload.facts as Record<string, unknown> : {};
|
|
@@ -2982,7 +2982,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2982
2982
|
return true;
|
|
2983
2983
|
}
|
|
2984
2984
|
|
|
2985
|
-
async function diagnoseCurrentWorkflow(cwd: string): Promise<{
|
|
2985
|
+
async function diagnoseCurrentWorkflow(cwd: string, options: { skipWorkflowUpgrade?: boolean } = {}): Promise<{
|
|
2986
2986
|
snapshot: Record<string, unknown> | null;
|
|
2987
2987
|
inspectError?: string;
|
|
2988
2988
|
rolloverState: Record<string, unknown> | null;
|
|
@@ -2996,6 +2996,40 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2996
2996
|
? parseJsonResult(await runRuby(cwd, "workflow-orchestrator/scripts/session_rollover.rb", ["inspect", "--json"]))
|
|
2997
2997
|
: null;
|
|
2998
2998
|
const rollover = rolloverState?.rollover as Record<string, unknown> | undefined;
|
|
2999
|
+
let workflowUpgrade = { outdated: false, blocked: false, paths: [] as string[] };
|
|
3000
|
+
if (typeof snapshot?.current_feature === "string" && !options.skipWorkflowUpgrade) {
|
|
3001
|
+
try {
|
|
3002
|
+
const upgradeRun = await runRuby(
|
|
3003
|
+
cwd,
|
|
3004
|
+
"workflow-orchestrator/scripts/init_workflow.rb",
|
|
3005
|
+
["--force", "--json"],
|
|
3006
|
+
);
|
|
3007
|
+
if (!upgradeRun.ok) throw new Error("workflow upgrade dry-run failed");
|
|
3008
|
+
const upgradeResult = parseJsonResult(upgradeRun);
|
|
3009
|
+
if (!upgradeResult) throw new Error("workflow upgrade dry-run returned invalid JSON");
|
|
3010
|
+
// init_workflow.rb returns { changes: [{ path, exists,
|
|
3011
|
+
// same_as_template, action, ... }] }; a dry-run drift has action
|
|
3012
|
+
// "would_skip_existing", so drift is derived from the boolean fields.
|
|
3013
|
+
const changes = Array.isArray(upgradeResult?.changes) ? upgradeResult.changes : [];
|
|
3014
|
+
const records = changes.filter((change): change is Record<string, unknown> =>
|
|
3015
|
+
Boolean(change && typeof change === "object" && !Array.isArray(change))
|
|
3016
|
+
);
|
|
3017
|
+
const paths = records.flatMap(change =>
|
|
3018
|
+
change.exists === true && change.same_as_template !== true && typeof change.path === "string"
|
|
3019
|
+
? [change.path]
|
|
3020
|
+
: []
|
|
3021
|
+
);
|
|
3022
|
+
workflowUpgrade = {
|
|
3023
|
+
outdated: paths.length > 0,
|
|
3024
|
+
blocked: records.some(change =>
|
|
3025
|
+
change.action === "blocked_state_mismatch" || change.action === "would_overwrite_blocked"
|
|
3026
|
+
),
|
|
3027
|
+
paths,
|
|
3028
|
+
};
|
|
3029
|
+
} catch {
|
|
3030
|
+
workflowUpgrade = { outdated: false, blocked: false, paths: [] };
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
2999
3033
|
const diagnosis = diagnoseWorkflow({
|
|
3000
3034
|
inspect: { ok: Boolean(snapshot), error: inspectError },
|
|
3001
3035
|
snapshot,
|
|
@@ -3003,6 +3037,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3003
3037
|
outputsReady: snapshot ? isReviewableWorkflowSnapshot(snapshot) : false,
|
|
3004
3038
|
rolloverPrepared: Boolean(rolloverState && typeof rolloverState.revision === "number"
|
|
3005
3039
|
&& rollover?.status === "prepared" && typeof rollover.id === "string"),
|
|
3040
|
+
workflowUpgrade,
|
|
3006
3041
|
});
|
|
3007
3042
|
return { snapshot, inspectError, rolloverState, diagnosis };
|
|
3008
3043
|
}
|
|
@@ -3063,8 +3098,10 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3063
3098
|
return;
|
|
3064
3099
|
}
|
|
3065
3100
|
}
|
|
3101
|
+
// Declining the template upgrade must not strand the step behind it.
|
|
3102
|
+
let skipWorkflowUpgrade = false;
|
|
3066
3103
|
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
3067
|
-
const current = await diagnoseCurrentWorkflow(ctx.cwd);
|
|
3104
|
+
const current = await diagnoseCurrentWorkflow(ctx.cwd, { skipWorkflowUpgrade });
|
|
3068
3105
|
const { diagnosis, snapshot } = current;
|
|
3069
3106
|
const summary = renderDiagnosis(diagnosis);
|
|
3070
3107
|
if (diagnosis.kind === "execute" && snapshot) {
|
|
@@ -3086,6 +3123,12 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3086
3123
|
{ label: skip, description: "保持现状,不做任何修改" },
|
|
3087
3124
|
]);
|
|
3088
3125
|
const fix = fixes.find(item => FIX_LABELS[item].label === choice);
|
|
3126
|
+
const upgradeOffered = diagnosis.diagnoses.some(item => item.code === "WORKFLOW_TEMPLATE_OUTDATED");
|
|
3127
|
+
if (!fix && upgradeOffered) {
|
|
3128
|
+
skipWorkflowUpgrade = true;
|
|
3129
|
+
ctx.ui.notify("本次不升级工作流定义,继续按现有定义诊断。", "info");
|
|
3130
|
+
continue;
|
|
3131
|
+
}
|
|
3089
3132
|
if (!fix) {
|
|
3090
3133
|
ctx.ui.notify("已保持现状,未做任何修改。", "info");
|
|
3091
3134
|
return;
|
|
@@ -3145,6 +3188,37 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3145
3188
|
ctx.ui.notify("登记表已修复,未推进工作流。正在重新诊断……", "info");
|
|
3146
3189
|
continue;
|
|
3147
3190
|
}
|
|
3191
|
+
if (fix === "upgrade-workflow") {
|
|
3192
|
+
const paths = diagnosis.diagnoses
|
|
3193
|
+
.find(item => item.code === "WORKFLOW_TEMPLATE_OUTDATED")?.evidence ?? [];
|
|
3194
|
+
const confirmed = await ctx.ui.confirm(
|
|
3195
|
+
"升级工作流定义",
|
|
3196
|
+
`将用当前版本内置模板替换以下工作流文件:\n${paths.map(item => `- ${item}`).join("\n")}\n\n这些文件中的手工修改会被替换;需求产物和 STATE 不会改变,也不会推进任何步骤。继续吗?`,
|
|
3197
|
+
);
|
|
3198
|
+
if (!confirmed) {
|
|
3199
|
+
skipWorkflowUpgrade = true;
|
|
3200
|
+
ctx.ui.notify("本次不升级工作流定义,继续按现有定义诊断。", "info");
|
|
3201
|
+
continue;
|
|
3202
|
+
}
|
|
3203
|
+
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/init_workflow.rb", [
|
|
3204
|
+
"--write", "--force", "--json",
|
|
3205
|
+
]);
|
|
3206
|
+
if (!controlPlaneSucceeded(result)) {
|
|
3207
|
+
ctx.ui.notify(`工作流升级失败:${describeControlFailure(result)}`, "error");
|
|
3208
|
+
return;
|
|
3209
|
+
}
|
|
3210
|
+
const synced = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/sync_artifacts.rb", [
|
|
3211
|
+
"--write", "--no-preserve", "--json",
|
|
3212
|
+
]);
|
|
3213
|
+
if (!controlPlaneSucceeded(synced)) {
|
|
3214
|
+
ctx.ui.notify(`工作流升级后的产物同步失败:${describeControlFailure(synced)}`, "error");
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
activeContract = undefined;
|
|
3218
|
+
contractAgentCalls = 0;
|
|
3219
|
+
ctx.ui.notify("工作流定义已升级。正在重新诊断……", "info");
|
|
3220
|
+
continue;
|
|
3221
|
+
}
|
|
3148
3222
|
if (fix === "help-bundle") {
|
|
3149
3223
|
const bundlePath = await writeHelpBundle(ctx, current);
|
|
3150
3224
|
ctx.ui.notify(`已生成求助包:${bundlePath}\n只包含诊断信息(状态摘要、校验错误、最近的工具报错),不含源码。可以把这个文件发给维护者。`, "info");
|
|
@@ -63,7 +63,9 @@ targets = {
|
|
|
63
63
|
"workflows-README.md" => ".devflow/planning/workflows/README.md"
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
# Evaluated for dry runs too, so callers can tell before writing whether a
|
|
67
|
+
# forced upgrade would be refused for the active STATE step set.
|
|
68
|
+
blocked_target_paths = if options[:force] && !options[:allow_state_mismatch]
|
|
67
69
|
targets.select do |template_name, target_path|
|
|
68
70
|
source = File.join(template_dir, template_name)
|
|
69
71
|
target = File.join(root, target_path)
|
|
@@ -158,7 +158,14 @@ def preflight(root: Path, feature: str, step: str, contract_path: str,
|
|
|
158
158
|
issues.append({"code": "INVALID_OBLIGATION_PATH", "index": index, "detail": str(error)})
|
|
159
159
|
continue
|
|
160
160
|
producer = obligation.get("producer")
|
|
161
|
-
if producer == "existing":
|
|
161
|
+
if producer == "existing" and relative.endswith("/"):
|
|
162
|
+
# Collection inputs (trailing "/") are directories, not files. As in
|
|
163
|
+
# gate_runtime.rb, a missing collection is allowed at entry.
|
|
164
|
+
if path.is_dir():
|
|
165
|
+
evidence.append({"kind": "input_collection", "path": relative})
|
|
166
|
+
elif path.exists() or stage != "entry":
|
|
167
|
+
issues.append({"code": "MISSING_INPUT_FACT", "index": index, "path": relative})
|
|
168
|
+
elif producer == "existing":
|
|
162
169
|
if not path.is_file():
|
|
163
170
|
issues.append({"code": "MISSING_INPUT_FACT", "index": index, "path": relative})
|
|
164
171
|
elif obligation.get("sha256") is not None and obligation.get("sha256") != sha256(path):
|
|
@@ -120,10 +120,15 @@ def startup_preflight(root: Path, feature: str, step: str,
|
|
|
120
120
|
else:
|
|
121
121
|
continue
|
|
122
122
|
rendered = render_path(relative, feature, phase)
|
|
123
|
+
collection = relative.endswith("/")
|
|
123
124
|
try:
|
|
124
|
-
|
|
125
|
+
target = review_preflight.within(root, rendered)
|
|
126
|
+
exists = target.is_dir() if collection else target.is_file()
|
|
125
127
|
except ValueError:
|
|
126
128
|
exists = False
|
|
129
|
+
# Same rule as gate_runtime.rb: a collection input may be absent at entry.
|
|
130
|
+
if collection and not exists:
|
|
131
|
+
required = False
|
|
127
132
|
input_facts.append({"path": rendered, "required": required, "exists": exists})
|
|
128
133
|
# Input readiness is independent of whether the caller supplied
|
|
129
134
|
# a frozen contract. Keep the derived contract view exempt, as
|
package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml
CHANGED
|
@@ -438,7 +438,9 @@ steps:
|
|
|
438
438
|
inputs:
|
|
439
439
|
- ".devflow/planning/requirements/{feature}/决策地图.md"
|
|
440
440
|
- ".devflow/planning/requirements/{feature}/wayfinder/"
|
|
441
|
-
-
|
|
441
|
+
# Glossary created by domain-modeling on the first feature of a project.
|
|
442
|
+
- path: ".devflow/planning/CONTEXT.md"
|
|
443
|
+
required_at_entry: false
|
|
442
444
|
outputs:
|
|
443
445
|
- path: ".devflow/planning/requirements/{feature}/领域模型记录.md"
|
|
444
446
|
type: domain-modeling
|
|
@@ -464,7 +466,9 @@ steps:
|
|
|
464
466
|
inputs:
|
|
465
467
|
- ".devflow/planning/requirements/{feature}/决策地图.md"
|
|
466
468
|
- ".devflow/planning/requirements/{feature}/领域模型记录.md"
|
|
467
|
-
-
|
|
469
|
+
# Glossary created by domain-modeling on the first feature of a project.
|
|
470
|
+
- path: ".devflow/planning/CONTEXT.md"
|
|
471
|
+
required_at_entry: false
|
|
468
472
|
outputs:
|
|
469
473
|
- path: ".devflow/planning/requirements/{feature}/可实现性审查.md"
|
|
470
474
|
type: requirement-feasibility-review
|
package/src/constants.ts
CHANGED
|
@@ -12,6 +12,7 @@ export type FixAction =
|
|
|
12
12
|
| "review-and-advance"
|
|
13
13
|
| "rerun-step"
|
|
14
14
|
| "revise-outputs"
|
|
15
|
+
| "upgrade-workflow"
|
|
15
16
|
| "repair-registry"
|
|
16
17
|
| "resume-rollover"
|
|
17
18
|
| "help-bundle"
|
|
@@ -22,6 +23,7 @@ export type DiagnosisCode =
|
|
|
22
23
|
| "NO_ACTIVE_FEATURE"
|
|
23
24
|
| "WORKFLOW_COMPLETE"
|
|
24
25
|
| "ROLLOVER_PENDING"
|
|
26
|
+
| "WORKFLOW_TEMPLATE_OUTDATED"
|
|
25
27
|
| "REGISTRY_DRIFT"
|
|
26
28
|
| "CHECKPOINT_WAITING"
|
|
27
29
|
| "STEP_OUTPUTS_READY"
|
|
@@ -56,6 +58,7 @@ export type DiagnosisInput = {
|
|
|
56
58
|
/** Every required output of the current step exists and is not marked invalid; `planned` allowed. */
|
|
57
59
|
outputsReady: boolean;
|
|
58
60
|
rolloverPrepared: boolean;
|
|
61
|
+
workflowUpgrade?: { outdated: boolean; blocked: boolean; paths: string[] };
|
|
59
62
|
};
|
|
60
63
|
|
|
61
64
|
export const FIX_LABELS: Record<FixAction, { label: string; description: string }> = {
|
|
@@ -63,9 +66,10 @@ export const FIX_LABELS: Record<FixAction, { label: string; description: string
|
|
|
63
66
|
"review-and-advance": { label: "审查后推进", description: "弹出推进确认;确认后由 Core 校验产物并推进到下一步" },
|
|
64
67
|
"rerun-step": { label: "重新执行这一步", description: "按当前步骤重新启动 AI(会先激活执行合同)" },
|
|
65
68
|
"revise-outputs": { label: "按意见修改产物", description: "回到审查状态;你直接说明要改什么,AI 修改后再推进" },
|
|
69
|
+
"upgrade-workflow": { label: "升级工作流定义", description: "用当前版本内置的模板替换项目里的工作流文件(.devflow/planning/workflows/);不改需求产物,不推进步骤" },
|
|
66
70
|
"repair-registry": { label: "修复登记表", description: "由 Core 按当前状态重建产物登记表;不推进工作流" },
|
|
67
71
|
"resume-rollover": { label: "继续会话交接", description: "完成未走完的会话切换,然后在新会话中继续" },
|
|
68
|
-
"help-bundle": { label: "生成求助包", description: "把诊断信息写到 .devflow/diagnostics/,不含源码内容" },
|
|
72
|
+
"help-bundle": { label: "生成求助包", description: "把诊断信息写到 .devflow/runs/diagnostics/,不含源码内容" },
|
|
69
73
|
"cancel-feature": { label: "弃用当前需求", description: "结束当前需求并保留现有产物;之后可以创建新需求" },
|
|
70
74
|
};
|
|
71
75
|
|
|
@@ -160,6 +164,18 @@ export function diagnoseWorkflow(input: DiagnosisInput): WorkflowDiagnosis {
|
|
|
160
164
|
diagnoses: [{ code: "WORKFLOW_COMPLETE", title: `需求「${feature}」已经结束`, evidence: [], fixes: ["create-feature"] }],
|
|
161
165
|
};
|
|
162
166
|
}
|
|
167
|
+
if (input.workflowUpgrade?.outdated && !input.workflowUpgrade.blocked) {
|
|
168
|
+
return {
|
|
169
|
+
...base,
|
|
170
|
+
kind: "repair",
|
|
171
|
+
diagnoses: [{
|
|
172
|
+
code: "WORKFLOW_TEMPLATE_OUTDATED",
|
|
173
|
+
title: "项目里的工作流定义比当前版本旧",
|
|
174
|
+
evidence: input.workflowUpgrade.paths,
|
|
175
|
+
fixes: ["upgrade-workflow"],
|
|
176
|
+
}],
|
|
177
|
+
};
|
|
178
|
+
}
|
|
163
179
|
const plan = record(snapshot.recovery_plan);
|
|
164
180
|
if (plan?.classification === "safe_repairable") {
|
|
165
181
|
return {
|