@guanlin4924/devflow-pi 0.1.53 → 0.1.55
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 +36 -4
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_contract.rb +1 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/requirement_coverage.rb +20 -2
- package/src/constants.ts +1 -1
package/package.json
CHANGED
|
@@ -1361,6 +1361,31 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1361
1361
|
return stateRevision === contract.stateRevision && graphRevision === contract.graphRevision;
|
|
1362
1362
|
}
|
|
1363
1363
|
|
|
1364
|
+
// A successful graph mutation is already constrained by the active contract's
|
|
1365
|
+
// allowed kinds and the graph's compare-and-swap revision. Re-freeze the
|
|
1366
|
+
// Core-derived contract at that new revision so the next candidate remains in
|
|
1367
|
+
// the same approved step instead of being rejected as stale.
|
|
1368
|
+
async function rebaseActiveContractAfterGraphWrite(ctx: { cwd: string }, feature: string, graphRevision?: number): Promise<string | undefined> {
|
|
1369
|
+
const previous = activeContract;
|
|
1370
|
+
if (!previous || previous.feature !== feature) return undefined;
|
|
1371
|
+
const written = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/execution_contract.rb", [
|
|
1372
|
+
"create", "--feature", previous.feature, "--step", previous.step, "--write", "--json",
|
|
1373
|
+
...(previous.stateRevision === undefined ? [] : ["--expected-state-revision", String(previous.stateRevision)]),
|
|
1374
|
+
...(graphRevision === undefined ? [] : ["--expected-graph-revision", String(graphRevision)]),
|
|
1375
|
+
]);
|
|
1376
|
+
const payload = parseJsonResult(written);
|
|
1377
|
+
const refreshed = payload?.contract && typeof payload.contract === "object"
|
|
1378
|
+
? contractFromRecord(payload.contract as Record<string, unknown>)
|
|
1379
|
+
: null;
|
|
1380
|
+
if (!controlPlaneSucceeded(written) || !refreshed) {
|
|
1381
|
+
return written.stderr || written.stdout || "Core could not refresh the execution contract after graph write.";
|
|
1382
|
+
}
|
|
1383
|
+
refreshed.contractPath = typeof payload?.path === "string" ? payload.path : undefined;
|
|
1384
|
+
activeContract = refreshed;
|
|
1385
|
+
contractAgentCalls = 0;
|
|
1386
|
+
return undefined;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1364
1389
|
async function restoreActiveContract(cwd: string): Promise<ActiveExecutionContract | undefined> {
|
|
1365
1390
|
try {
|
|
1366
1391
|
const state = parseJsonResult(await runRuby(cwd, "workflow-orchestrator/scripts/inspect_state.rb", ["--json"]));
|
|
@@ -2975,10 +3000,17 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2975
3000
|
args.push("--confirmed-interactively");
|
|
2976
3001
|
}
|
|
2977
3002
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3003
|
+
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/requirement_graph.rb", args);
|
|
3004
|
+
if (graphWrite && controlPlaneSucceeded(result)) {
|
|
3005
|
+
const revision = parseJsonResult(result)?.revision;
|
|
3006
|
+
const rebaseError = await rebaseActiveContractAfterGraphWrite(
|
|
3007
|
+
ctx,
|
|
3008
|
+
params.feature,
|
|
3009
|
+
typeof revision === "number" ? revision : undefined,
|
|
3010
|
+
);
|
|
3011
|
+
if (rebaseError) ctx.ui.notify(`图谱候选已写入,但执行合同未能刷新;后续写入将保持阻塞:${rebaseError}`, "error");
|
|
3012
|
+
}
|
|
3013
|
+
return renderResult(result, { action: params.action, feature: params.feature });
|
|
2982
3014
|
},
|
|
2983
3015
|
});
|
|
2984
3016
|
|
|
@@ -11,6 +11,7 @@ require_relative "control_io"
|
|
|
11
11
|
require_relative "requirement_graph"
|
|
12
12
|
|
|
13
13
|
POLICY = {
|
|
14
|
+
"domain-modeling" => { "checkers" => %w[requirement-graph-check], "agents" => [], "budget" => 0, "allowed_graph_delta" => %w[term object behavior rule boundary] },
|
|
14
15
|
"requirement-baseline" => { "checkers" => %w[requirement-graph-check business-closure-check slice-independence-check acceptance-coverage-check change-impact-check], "agents" => [], "budget" => 0, "allowed_graph_delta" => %w[requirement rule boundary slice acceptance] },
|
|
15
16
|
"api-contract" => { "checkers" => %w[requirement-graph-check contract-coverage-check api-test-schema-check change-impact-check], "agents" => [], "budget" => 0, "allowed_graph_delta" => %w[api] },
|
|
16
17
|
"architecture-feasibility" => { "checkers" => %w[requirement-graph-check change-impact-check], "agents" => [], "budget" => 0, "allowed_graph_delta" => %w[workload_shape workload_constraint decision risk metric read_model] },
|
|
@@ -92,10 +92,16 @@ module RequirementCoverage
|
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
+
disposition_ids = {}
|
|
95
96
|
disposition_by_id = {}
|
|
96
97
|
Array(coverage["units"]).each do |entry|
|
|
98
|
+
unless entry.is_a?(Hash)
|
|
99
|
+
add(errors, "COVERAGE_UNIT_SCHEMA_INVALID", "coverage unit must be a mapping")
|
|
100
|
+
next
|
|
101
|
+
end
|
|
97
102
|
unit_id = entry["unit_id"].to_s
|
|
98
|
-
duplicate(errors,
|
|
103
|
+
duplicate(errors, disposition_ids, unit_id, "DUPLICATE_COVERAGE")
|
|
104
|
+
disposition_by_id[unit_id] ||= entry
|
|
99
105
|
add(errors, "UNIT_REF_NOT_FOUND", "coverage references an unknown unit", unit_id: unit_id) unless unit_by_id.key?(unit_id)
|
|
100
106
|
disposition = entry["disposition"]
|
|
101
107
|
unless disposition.is_a?(Hash) && %w[active deferred out_of_scope merged].include?(disposition["kind"].to_s)
|
|
@@ -138,17 +144,29 @@ module RequirementCoverage
|
|
|
138
144
|
add(errors, "SOURCE_UNITS_SCHEMA_INVALID", "source_units schema_version must be 1") unless source["schema_version"].to_i == 1
|
|
139
145
|
add(errors, "SOURCE_UNITS_NOT_CORE_GENERATED", "source_units must be produced by Core") unless source["produced_by"].to_s == "core.source_unitizer/v1"
|
|
140
146
|
add(errors, "SOURCE_UNITS_SCHEMA_INVALID", "source_document_fingerprint is required") if source["source_document_fingerprint"].to_s.empty?
|
|
141
|
-
|
|
147
|
+
unless source["units"].is_a?(Array)
|
|
148
|
+
add(errors, "SOURCE_UNITS_SCHEMA_INVALID", "units must be an array")
|
|
149
|
+
return
|
|
150
|
+
end
|
|
142
151
|
Array(source["units"]).each do |unit|
|
|
152
|
+
unless unit.is_a?(Hash)
|
|
153
|
+
add(errors, "SOURCE_UNIT_SCHEMA_INVALID", "source unit must be a mapping")
|
|
154
|
+
next
|
|
155
|
+
end
|
|
143
156
|
add(errors, "UNIT_SCHEMA_INVALID", "stable_unit_id is required") if unit["stable_unit_id"].to_s.empty?
|
|
144
157
|
add(errors, "SOURCE_UNIT_REUSED_STALE", "unit source fingerprint mismatch", unit_id: unit["stable_unit_id"]) unless unit["source_document_fingerprint"].to_s == source["source_document_fingerprint"].to_s
|
|
145
158
|
add(errors, "EXCERPT_MODIFIED", "excerpt fingerprint mismatch", unit_id: unit["stable_unit_id"]) if unit["excerpt_fingerprint"] && sha(unit["excerpt"]) != unit["excerpt_fingerprint"].to_s
|
|
159
|
+
unless unit["required_assertions"].is_a?(Array) && unit["required_assertions"].all? { |assertion| assertion.is_a?(Hash) }
|
|
160
|
+
add(errors, "SOURCE_UNIT_SCHEMA_INVALID", "required_assertions must be an array of mappings", unit_id: unit["stable_unit_id"])
|
|
161
|
+
end
|
|
146
162
|
end
|
|
147
163
|
end
|
|
148
164
|
|
|
149
165
|
def validate_coverage(coverage, errors)
|
|
150
166
|
add(errors, "COVERAGE_SCHEMA_INVALID", "coverage schema_version must be 1") unless coverage["schema_version"].to_i == 1
|
|
151
167
|
%w[claims units].each { |key| add(errors, "COVERAGE_SCHEMA_INVALID", "#{key} must be an array") unless coverage[key].is_a?(Array) }
|
|
168
|
+
Array(coverage["claims"]).each { |claim| add(errors, "CLAIM_SCHEMA_INVALID", "claim must be a mapping") unless claim.is_a?(Hash) }
|
|
169
|
+
Array(coverage["units"]).each { |unit| add(errors, "COVERAGE_UNIT_SCHEMA_INVALID", "coverage unit must be a mapping") unless unit.is_a?(Hash) }
|
|
152
170
|
end
|
|
153
171
|
|
|
154
172
|
def compare(assertion, inherited)
|
package/src/constants.ts
CHANGED