@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanlin4924/devflow-pi",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
4
4
  "type": "module",
5
5
  "description": "Devflow 专项研发 Pi,基于固定版本 OMP Runtime 组装研发状态机、代码智能、设计、实现与质量门禁。",
6
6
  "license": "UNLICENSED",
@@ -1017,11 +1017,20 @@ function normalizedProjectPath(raw: unknown, cwd: string): string | null {
1017
1017
  return relative.replace(/^\.\//, "");
1018
1018
  }
1019
1019
 
1020
- function inputPaths(input: unknown, cwd: string): string[] {
1021
- if (!input || typeof input !== "object") return [];
1022
- const record = input as Record<string, unknown>;
1023
- const values = [record.file_path, record.filePath, record.path, record.target];
1024
- if (Array.isArray(record.paths)) values.push(...record.paths);
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`、`gap_count=0` 且无 critical finding | Reject advance to requirement-baseline |
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, disposition_by_id, unit_id, "DUPLICATE_COVERAGE")
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
- add(errors, "SOURCE_UNITS_SCHEMA_INVALID", "units must be an array") unless source["units"].is_a?(Array)
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 &&= data["gap_count"].to_i.zero?
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  export const PRODUCT_NAME = "Devflow Pi";
2
- export const PRODUCT_VERSION = "0.1.54";
2
+ export const PRODUCT_VERSION = "0.1.56";
3
3
  export const PROJECT_SCHEMA_VERSION = 1;
4
4
  export const OMP_RUNTIME_VERSION = "17.2.7";
5
5