88 lines
6.1 KiB
Python
88 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
||
"""Describe every unresolved route from existing static source metadata; no runtime validation."""
|
||
import json
|
||
import hashlib
|
||
from collections import Counter, defaultdict
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
SOURCE = ROOT / "docs/current/feature-capability-registry.json"
|
||
OUTPUT = ROOT / "docs/current/native-layer-gap-inventory.json"
|
||
|
||
|
||
def main():
|
||
registry = json.loads(SOURCE.read_text(encoding="utf-8"))
|
||
baseline_path = ROOT / "docs/current/native-layer-gap-baseline-20260912.json"
|
||
if not baseline_path.exists() and OUTPUT.exists():
|
||
baseline_path.write_bytes(OUTPUT.read_bytes())
|
||
baseline = json.loads(baseline_path.read_text(encoding="utf-8")) if baseline_path.exists() else {}
|
||
decisions_path = ROOT / "docs/current/native-semantic-decisions.json"
|
||
decisions = {}
|
||
if decisions_path.exists():
|
||
for group in json.loads(decisions_path.read_text(encoding="utf-8")).get("groups", []):
|
||
sources = group.get("sourceDigests", {})
|
||
current = bool(sources) and all((ROOT / name).resolve().is_relative_to(ROOT)
|
||
and (ROOT / name).is_file() and hashlib.sha256((ROOT / name).read_bytes()).hexdigest() == expected
|
||
for name, expected in sources.items())
|
||
for record_id, finding in group.get("records", {}).items():
|
||
decisions[record_id] = {**finding, "sourceSnapshotCurrent": current}
|
||
by_id = {row["id"]: row for row in registry["records"]}
|
||
baseline_ids = {row["id"] for row in baseline.get("records", [])}
|
||
baseline_progress = {"total": len(baseline_ids),
|
||
"registeredNow": sum(by_id.get(key, {}).get("implementationStatus") == "routed" for key in baseline_ids),
|
||
"sourceDecisionsCurrent": sum(decisions.get(key, {}).get("sourceSnapshotCurrent") is True for key in baseline_ids),
|
||
"sourceReviewRemaining": sum(decisions.get(key, {}).get("sourceSnapshotCurrent") is not True for key in baseline_ids)}
|
||
rows = []
|
||
modules = defaultdict(Counter)
|
||
for row in registry.get("records", []):
|
||
if row.get("implementationStatus") == "routed":
|
||
continue
|
||
for gap in row.get("implementationGaps", []):
|
||
kind = gap.get("classification") or gap.get("stage") or "unclassified"
|
||
modules[row["module"]][kind] += 1
|
||
rows.append({"id": row["id"], "module": row["module"], "command": row.get("command"),
|
||
"desktopSource": row.get("sourcePath"), "desktopLine": row.get("sourceLine"),
|
||
"requirement": "retained", "implementationStatus": row.get("implementationStatus"),
|
||
"semanticDecision": decisions.get(row["id"]),
|
||
"gaps": row.get("implementationGaps", [])})
|
||
kinds = Counter(gap.get("classification") or gap.get("stage") or "unclassified"
|
||
for row in rows for gap in row["gaps"])
|
||
payload = {"schema": "freecad-web-native-gap-inventory/v1", "sourceGeneratedAt": registry.get("generatedAt"),
|
||
"scope": "All unresolved feature-capability records; static source anchors only, not a semantic completeness audit.",
|
||
"businessUnnecessaryDeterminations": 0, "automaticValidationRun": False,
|
||
"baselineProgress": baseline_progress,
|
||
"counts": {"featureTotal": len(registry["records"]), "unresolved": len(rows), "byKind": dict(kinds)},
|
||
"modules": dict(modules), "records": rows}
|
||
OUTPUT.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||
lines = ["# 原生层缺口逐项清单", "", f"登记快照:{payload['sourceGeneratedAt']}。未接通 {len(rows)} 项。",
|
||
"", f"固定基线 {baseline_progress['total']} 项:当前已登记 {baseline_progress['registeredNow']} 项;有效源码结论 {baseline_progress['sourceDecisionsCurrent']} 项;待继续源码归因 {baseline_progress['sourceReviewRemaining']} 项。已登记不等于用户已验证。",
|
||
"", "本清单保留全部业务,没有任何一项被判为业务不需要。源码候选仅用于定位,不能证明业务已实现;",
|
||
"Probe 中未找到入口不代表本机没有源码。未运行测试或业务验证。", "",
|
||
"## 模块分布", "", "| 模块 | 标记拒绝 | 有源码候选未登记 | Probe 未定位入口 | 策略/其他 |",
|
||
"| --- | ---: | ---: | ---: | ---: |"]
|
||
for module, counts in sorted(modules.items()):
|
||
rejected = counts['registration-marker-rejected']
|
||
candidates = counts['native-source-candidate-unregistered']
|
||
missing = counts['native-entry-not-located-in-probe-directory']
|
||
lines.append(f"| {module} | {rejected} | {candidates} | {missing} | {sum(counts.values()) - rejected - candidates - missing} |")
|
||
lines.extend(["", "## 每项来源与下一步", ""])
|
||
for row in rows:
|
||
lines.extend([f"### {row['id']}", "", f"桌面来源:`{row['desktopSource']}:{row['desktopLine']}`;需求保留。"])
|
||
if row.get('semanticDecision'):
|
||
decision = row['semanticDecision']
|
||
lines.extend([f"- 源码结论:{decision['classification']};指纹仍匹配:{decision['sourceSnapshotCurrent']}",
|
||
f"- 发现:{decision['finding']}", f"- 实施下一步:{decision['nextAction']}"])
|
||
for gap in row['gaps']:
|
||
lines.append(f"- 分类:`{gap.get('classification') or gap.get('stage')}`;{gap.get('reason', '')}")
|
||
for candidate in gap.get('sourceCandidates', []):
|
||
source = ROOT / candidate['path']
|
||
lines.append(f"- 原生候选:[{source.name}:{candidate['line']}]({source}:{candidate['line']})")
|
||
lines.append(f"- 下一步:{gap.get('repair') or 'Inspect the existing route policy and the supplied dependency closure; do not remove the guard without connecting the real implementation.'}")
|
||
lines.append("")
|
||
OUTPUT.with_suffix('.md').write_text('\n'.join(lines), encoding='utf-8')
|
||
print(json.dumps({**payload['counts'], "baselineProgress": baseline_progress}, ensure_ascii=False))
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|