@guanlin4924/devflow-pi 0.1.54 → 0.1.56
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/requirement_coverage.rb +20 -2
- 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/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 |
|
|
@@ -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)
|
|
@@ -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