@guanlin4924/devflow-pi 0.1.55 → 0.1.57
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/package.json +1 -1
- package/plugins/devflow-core/extensions/devflow-core.ts +14 -5
- package/plugins/devflow-core/skills/workflow-orchestrator/SKILL.md +1 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +6 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +1 -0
- package/plugins/devflow-requirements/skills/requirement-feasibility-review/SKILL.md +6 -0
- package/src/constants.ts +1 -1
- package/src/runtime.ts +33 -5
package/package.json
CHANGED
|
@@ -1017,11 +1017,20 @@ function normalizedProjectPath(raw: unknown, cwd: string): string | null {
|
|
|
1017
1017
|
return relative.replace(/^\.\//, "");
|
|
1018
1018
|
}
|
|
1019
1019
|
|
|
1020
|
-
function
|
|
1021
|
-
if (
|
|
1022
|
-
const
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1020
|
+
function encodedEditPaths(value: unknown): string[] {
|
|
1021
|
+
if (typeof value !== "string") return [];
|
|
1022
|
+
const hashline = /^\[([^#\r\n]+)(?:#[0-9a-fA-F]{4})?\]/m.exec(value)?.[1];
|
|
1023
|
+
const patchPaths = [...value.matchAll(/^\*\*\* (?:Add|Update|Delete) File:\s*(.+)$/gm)]
|
|
1024
|
+
.map(match => match[1].trim());
|
|
1025
|
+
return [...(hashline ? [hashline] : []), ...patchPaths];
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
export function inputPaths(input: unknown, cwd: string): string[] {
|
|
1029
|
+
const record = input && typeof input === "object" ? input as Record<string, unknown> : undefined;
|
|
1030
|
+
const values = record
|
|
1031
|
+
? [record.file_path, record.filePath, record.path, record.target, ...encodedEditPaths(record.input), ...encodedEditPaths(record.patch)]
|
|
1032
|
+
: encodedEditPaths(input);
|
|
1033
|
+
if (record && Array.isArray(record.paths)) values.push(...record.paths);
|
|
1025
1034
|
return values
|
|
1026
1035
|
.map(value => normalizedProjectPath(value, cwd))
|
|
1027
1036
|
.filter((value): value is string => value !== null);
|
|
@@ -114,7 +114,7 @@ After each step completes, verify the step's gate before advancing:
|
|
|
114
114
|
| requirement-triage | triage.meta.json `status` is `routed` AND `route` is `ready_for_wayfinder` | Reject advance to requirement-wayfinder |
|
|
115
115
|
| requirement-wayfinder | 决策地图.meta.json `status=ready_for_baseline` AND `ready_for_baseline=true` | Reject advance to domain-modeling |
|
|
116
116
|
| domain-modeling | 领域模型记录及 meta 存在且标识当前功能 | Reject advance to requirement-feasibility-review |
|
|
117
|
-
| requirement-feasibility-review | 可实现性审查.meta.json `status=ready_for_baseline`、`baseline_gate=pass`、`
|
|
117
|
+
| requirement-feasibility-review | 可实现性审查.meta.json `status=ready_for_baseline`、`baseline_gate=pass`、`blocking_gap_count=0` 且无 critical finding;`gap_count` 记录全部发现数 | Reject advance to requirement-baseline |
|
|
118
118
|
| requirement-baseline | baseline.md review conclusion is "通过" AND baseline.meta.json `review_status` / `traceability_status` are `passed` AND blocking counters are all 0 | Reject advance to api-contract |
|
|
119
119
|
| api-contract | api-contract.meta.json `review_status` / `traceability_status` are `passed` AND `pending_contract_count` is 0 | Reject advance to dual tracks |
|
|
120
120
|
| architecture-feasibility | Core-derived `architecture-brief.md` / meta exist. If `assessment_level=full`, require `assessment_status=ready` and `comparison_status=confirmed`; a lightweight incomplete result may proceed only as an explicitly unresolved downstream input. | Reject advance to dual tracks for an incomplete full assessment |
|
|
@@ -619,8 +619,12 @@ unless %w[prepared claimed consumed superseded failed].include?(status)
|
|
|
619
619
|
def validate_requirement_feasibility_review_gate(issues, step, data)
|
|
620
620
|
return unless step["id"] == "requirement-feasibility-review"
|
|
621
621
|
|
|
622
|
+
# gap_count is the review's full finding inventory. Older artifacts did not
|
|
623
|
+
# distinguish accepted downstream work from baseline-blocking gaps, so they
|
|
624
|
+
# retain the conservative interpretation until regenerated.
|
|
625
|
+
blocking_gap_count = data.key?("blocking_gap_count") ? data["blocking_gap_count"].to_i : data["gap_count"].to_i
|
|
622
626
|
pass = data["status"] == "ready_for_baseline" && data["baseline_gate"] == "pass"
|
|
623
|
-
pass &&=
|
|
627
|
+
pass &&= blocking_gap_count.zero?
|
|
624
628
|
pass &&= Array(data["critical_finding_ids"]).empty?
|
|
625
629
|
return if pass
|
|
626
630
|
|
|
@@ -632,6 +636,7 @@ unless %w[prepared claimed consumed superseded failed].include?(status)
|
|
|
632
636
|
status: data["status"],
|
|
633
637
|
baseline_gate: data["baseline_gate"],
|
|
634
638
|
gap_count: data["gap_count"],
|
|
639
|
+
blocking_gap_count: blocking_gap_count,
|
|
635
640
|
critical_finding_ids: data["critical_finding_ids"]
|
|
636
641
|
)
|
|
637
642
|
end
|
package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml
CHANGED
|
@@ -478,6 +478,7 @@ steps:
|
|
|
478
478
|
claim_count: { type: integer, required: true }
|
|
479
479
|
proven_count: { type: integer, required: true }
|
|
480
480
|
gap_count: { type: integer, required: true }
|
|
481
|
+
blocking_gap_count: { type: integer, required: true }
|
|
481
482
|
critical_finding_ids: { type: array, required: true }
|
|
482
483
|
next_action: { type: string, required: true }
|
|
483
484
|
|
|
@@ -44,11 +44,17 @@ Use this minimum metadata shape:
|
|
|
44
44
|
"claim_count": 0,
|
|
45
45
|
"proven_count": 0,
|
|
46
46
|
"gap_count": 0,
|
|
47
|
+
"blocking_gap_count": 0,
|
|
47
48
|
"critical_finding_ids": [],
|
|
48
49
|
"next_action": ""
|
|
49
50
|
}
|
|
50
51
|
```
|
|
51
52
|
|
|
53
|
+
`gap_count` counts every registered finding. `blocking_gap_count` counts only
|
|
54
|
+
findings that prevent requirement baseline. A pass requires
|
|
55
|
+
`blocking_gap_count: 0`; accepted non-blocking findings must stay in the
|
|
56
|
+
review's register with an owner and re-entry condition.
|
|
57
|
+
|
|
52
58
|
## Workflow
|
|
53
59
|
|
|
54
60
|
### 1. Atomize The Source Rules Before Summarizing
|
package/src/constants.ts
CHANGED
package/src/runtime.ts
CHANGED
|
@@ -278,6 +278,29 @@ export async function runInteractiveRuntime(project: ProjectContext, options: Ru
|
|
|
278
278
|
void consumeControlledRollover();
|
|
279
279
|
}, delay);
|
|
280
280
|
};
|
|
281
|
+
const waitForControlledRollover = async (): Promise<void> => {
|
|
282
|
+
const requestPath = path.join(project.root, ".planning", "rollover-request.json");
|
|
283
|
+
let requestObserved = false;
|
|
284
|
+
// The input hook may finish its async state transition just after
|
|
285
|
+
// submitInteractiveInput resolves. Wait briefly for that durable request
|
|
286
|
+
// so approval starts the fresh session without requiring another input.
|
|
287
|
+
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
288
|
+
try {
|
|
289
|
+
await readFile(requestPath, "utf8");
|
|
290
|
+
requestObserved = true;
|
|
291
|
+
} catch {
|
|
292
|
+
if (requestObserved) return;
|
|
293
|
+
}
|
|
294
|
+
await consumeControlledRollover();
|
|
295
|
+
try {
|
|
296
|
+
await readFile(requestPath, "utf8");
|
|
297
|
+
} catch {
|
|
298
|
+
if (requestObserved) return;
|
|
299
|
+
}
|
|
300
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
301
|
+
}
|
|
302
|
+
scheduleControlledRolloverRetry();
|
|
303
|
+
};
|
|
281
304
|
// A control-plane request may be created by an extension input handler that
|
|
282
305
|
// marks the message handled, so it is not guaranteed to pass through the
|
|
283
306
|
// normal submitInteractiveInput return path. Keep a small runtime-owned
|
|
@@ -298,11 +321,16 @@ export async function runInteractiveRuntime(project: ProjectContext, options: Ru
|
|
|
298
321
|
} finally {
|
|
299
322
|
interactiveSubmissionInFlight = false;
|
|
300
323
|
}
|
|
301
|
-
|
|
324
|
+
// Only an explicit approval can create a user-driven rollover after this
|
|
325
|
+
// input. Ordinary chat messages must return to the prompt immediately.
|
|
326
|
+
if (/^(?:批准|确认|同意|通过)(?:\s*(?:(?:full|完整|全量)?\s*初始化|执行|完成|继续|推进|当前(?:方案|步骤|计划)))?[。!!]?$/i.test(input.trim())) {
|
|
327
|
+
await waitForControlledRollover();
|
|
328
|
+
} else {
|
|
329
|
+
await consumeControlledRollover();
|
|
330
|
+
scheduleControlledRolloverRetry();
|
|
331
|
+
}
|
|
302
332
|
// The submitted turn may create a durable rollover request after the
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
// trigger the fresh-session handoff.
|
|
306
|
-
scheduleControlledRolloverRetry();
|
|
333
|
+
// input hook settles; the bounded wait above handles the normal path and
|
|
334
|
+
// the poller remains as a recovery path for a slow or interrupted hook.
|
|
307
335
|
}
|
|
308
336
|
}
|