update doc and optimize

This commit is contained in:
thanhnv
2026-07-06 17:47:12 +09:00
parent ace442da0e
commit 4419cd9eae
95 changed files with 2951 additions and 1353 deletions
@@ -32,14 +32,71 @@ def parse_requirements(path: str):
return [seen[k] for k in sorted(seen)]
def existing_files(root: str, values):
def normalize_entry(value):
"""Accept either a plain path string or an object with symbol/line refs.
Backward compatible: a bare string behaves exactly as file-level tracing.
Object form: {"file": "path", "symbols": ["name", ...], "lines": [n, ...]}.
"""
if isinstance(value, str):
return value, [], []
if isinstance(value, dict):
return value.get("file", ""), list(value.get("symbols", [])), list(value.get("lines", []))
return "", [], []
def symbol_present(text: str, symbol: str) -> bool:
"""Symbol-level check: the symbol appears as a definition or reference.
Covers common TS/JS/Python forms: `class X`, `function x`, `x(`, `const x`,
`x =`, `x:` (method/property). Deliberately permissive but anchored on word
boundaries so a substring alone does not count.
"""
esc = re.escape(symbol)
patterns = [
rf"\b(?:function|class|interface|type|enum|const|let|var|def)\s+{esc}\b",
rf"\b{esc}\s*[=:(]",
]
return any(re.search(p, text) for p in patterns)
def resolve_files(root: str, values):
"""Resolve file existence plus optional symbol/line coverage.
Returns present files, missing files, per-file symbol results, and the list
of unsatisfied symbol/line references (which make the requirement FAIL).
"""
present, missing = [], []
for rel in values or []:
if os.path.isfile(os.path.join(root, rel)):
present.append(rel)
else:
symbol_results = []
missing_symbols = []
missing_lines = []
for value in values or []:
rel, symbols, lines = normalize_entry(value)
if not rel:
continue
abs_path = os.path.join(root, rel)
if not os.path.isfile(abs_path):
missing.append(rel)
return present, missing
for sym in symbols:
missing_symbols.append(f"{rel}#{sym}")
for ln in lines:
missing_lines.append(f"{rel}:L{ln}")
continue
present.append(rel)
if not symbols and not lines:
continue
with open(abs_path, encoding="utf-8", errors="replace") as fh:
text = fh.read()
total_lines = text.count("\n") + 1
for sym in symbols:
found = symbol_present(text, sym)
symbol_results.append({"file": rel, "symbol": sym, "found": found})
if not found:
missing_symbols.append(f"{rel}#{sym}")
for ln in lines:
if not isinstance(ln, int) or ln < 1 or ln > total_lines:
missing_lines.append(f"{rel}:L{ln}")
return present, missing, symbol_results, missing_symbols, missing_lines
def main() -> int:
@@ -57,11 +114,30 @@ def main() -> int:
rows = []
failures = []
total_symbols = 0
total_symbols_found = 0
for req in reqs:
entry = mapping.get(req["id"], {})
code, missing_code = existing_files(root, entry.get("code", []))
tests, missing_tests = existing_files(root, entry.get("tests", []))
status = "PASS" if code and tests and not missing_code and not missing_tests else "FAIL"
code, missing_code, code_syms, code_missing_syms, code_missing_lines = resolve_files(
root, entry.get("code", [])
)
tests, missing_tests, test_syms, test_missing_syms, test_missing_lines = resolve_files(
root, entry.get("tests", [])
)
symbol_results = code_syms + test_syms
missing_symbols = code_missing_syms + test_missing_syms
missing_lines = code_missing_lines + test_missing_lines
total_symbols += len(symbol_results)
total_symbols_found += sum(1 for s in symbol_results if s["found"])
ok = (
code
and tests
and not missing_code
and not missing_tests
and not missing_symbols
and not missing_lines
)
status = "PASS" if ok else "FAIL"
row = {
"id": req["id"],
"name": req["name"],
@@ -70,6 +146,9 @@ def main() -> int:
"tests": tests,
"missing_code": missing_code,
"missing_tests": missing_tests,
"symbol_refs": symbol_results,
"missing_symbols": missing_symbols,
"missing_lines": missing_lines,
}
rows.append(row)
if status != "PASS":
@@ -84,6 +163,9 @@ def main() -> int:
"requirements": len(reqs),
"passed": sum(1 for r in rows if r["status"] == "PASS"),
"failed": len(failures),
"symbol_refs": total_symbols,
"symbols_found": total_symbols_found,
"symbols_missing": total_symbols - total_symbols_found,
"orphan_mappings": orphan_mappings,
},
"matrix": rows,
@@ -97,14 +179,15 @@ def main() -> int:
for row in failures:
print(
f"TRACEABILITY_FAIL {row['id']} code={len(row['code'])} tests={len(row['tests'])} "
f"missing_code={len(row['missing_code'])} missing_tests={len(row['missing_tests'])}",
f"missing_code={len(row['missing_code'])} missing_tests={len(row['missing_tests'])} "
f"missing_symbols={len(row['missing_symbols'])} missing_lines={len(row['missing_lines'])}",
file=sys.stderr,
)
if orphan_mappings:
print(f"TRACEABILITY_WARN orphan_mappings={','.join(orphan_mappings)}", file=sys.stderr)
print(
f"TRACEABILITY_MATRIX requirements={len(reqs)} pass={out['summary']['passed']} "
f"fail={len(failures)} out={args.out}"
f"fail={len(failures)} symbols={total_symbols_found}/{total_symbols} out={args.out}"
)
if args.gate and failures:
return 1