sol: untracked-file triage classification manifest + verifier

This commit is contained in:
sol committed 2026-11-01 07:26:42 +00:00
1 parent 27edccc773
commit 376afd33f4
4 files changed
+970

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# CHECK -- sol: Untracked-file triage classification (forge: verify lane).
# Exit 0 iff the committed Untracked-Triad-2026-11-01.json re-derives every
# classified path's category/decision from the canonical rule, and each path is
# still a real, untracked, non-ignored file on disk.
set -u
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR" && pwd)"
cd "$REPO_ROOT" || exit 2
exec python3 wizard-work/sol/verify_untracked_triad_2026_11_01.py
@@ -0,0 +1,668 @@
{
"artifact": "Untracked-Triad-2026-11-01.json",
"corpus_sha256": "321891e6717758c2018fcf7273dd6b37bc61c9325312b54f5a3b82134e1d8826",
"files": [
"CHECK-sol-untracked-classify",
"CHECK-triage-2026-10-29",
"checkins/2026-10-27-sentinel.md",
"d.jsonl",
"docs/clawrouter-plan.md",
"docs/flux2-deploy.md",
"docs/video-deploy.md",
"inbox/dump_seed.py",
"kanban.db",
"recoveries/apply-queue-status-2026-10-25.md",
"repo_exts.tsv",
"repo_files.tsv",
"repo_tree.tsv",
"s.json",
"test_sample.txt",
"test_validation.txt",
"test_verification.txt",
"validate_artifact.py",
"wizard-work/null/.hermes-tmp.RqZp3z",
"wizard-work/null/README-gatehygiene.md",
"wizard-work/null/README-q62-scan.md",
"wizard-work/null/_probe10.py",
"wizard-work/null/_probe11.py",
"wizard-work/null/_probe12.py",
"wizard-work/null/_probe13.py",
"wizard-work/null/_probe14.py",
"wizard-work/null/_probe5.py",
"wizard-work/null/_probe6.py",
"wizard-work/null/_probe7.py",
"wizard-work/null/_probe8.py",
"wizard-work/null/_probe9.py",
"wizard-work/null/_probe_binding_defects.py",
"wizard-work/null/_probe_gh1.py",
"wizard-work/null/_probe_gh2.py",
"wizard-work/null/_probe_gh3.py",
"wizard-work/null/_probe_gh4.py",
"wizard-work/null/_probe_gh5.py",
"wizard-work/null/_probe_gh6.py",
"wizard-work/null/_probe_gh7.py",
"wizard-work/null/_probe_gh8.py",
"wizard-work/null/_probe_gh9.py",
"wizard-work/null/_probe_mut2.py",
"wizard-work/null/_probe_mut_check.py",
"wizard-work/null/_probe_q70_5h.py",
"wizard-work/null/_probe_repro1.py",
"wizard-work/null/_probe_repro2.py",
"wizard-work/null/_probe_repro3.py",
"wizard-work/null/_probe_repro4.py",
"wizard-work/null/_probe_repro5.py",
"wizard-work/null/_probe_repro6.py",
"wizard-work/null/_probe_repro7.py",
"wizard-work/null/check_gatehygiene.py",
"wizard-work/null/check_provenance.py",
"wizard-work/null/check_provenance_baseline.tsv",
"wizard-work/null/check_provenance_ledger.py",
"wizard-work/null/check_q62_scan.py",
"wizard-work/null/cli_contract.py",
"wizard-work/null/diffkit/__init__.py",
"wizard-work/null/diffkit/_flatpkg.py",
"wizard-work/null/diffkit/core.py",
"wizard-work/null/diffkit/diffkit_cli.py",
"wizard-work/null/diffkit/myers.py",
"wizard-work/null/diffkit_gate.py",
"wizard-work/null/gatehygiene.py",
"wizard-work/null/json_salvage.py",
"wizard-work/null/membershipkit/.hermes-tmp.sx2gIa",
"wizard-work/null/membershipkit/check_membershipkit.sh",
"wizard-work/null/membershipkit/mutate_membershipkit.py",
"wizard-work/null/membershipkit/test_membershipkit.py",
"wizard-work/null/mutate_resolver.py",
"wizard-work/null/mutate_salvage.py",
"wizard-work/null/q58_warncontrol.py",
"wizard-work/null/q62_scan.py",
"wizard-work/null/q70_sabotage.py",
"wizard-work/null/q76_binding.py",
"wizard-work/null/q80_warn.py",
"wizard-work/null/revwalk.py",
"wizard-work/null/test_gate.py",
"wizard-work/null/test_gate_mutants.py",
"wizard-work/null/test_json_salvage.py",
"wizard-work/null/test_myers_diff.py",
"wizard-work/null/test_revwalk.py",
"wizard-work/null/verify_check_provenance.py",
"wizard-work/null/verify_staged.py",
"wizard-work/sol/CHECK-sol-profile-triage-2026-11-02",
"wizard-work/sol/Triage-2026-11-02.json",
"wizard-work/sol/Triage-2026-11-02.md",
"wizard-work/sol/_print_paths.py",
"wizard-work/sol/build_untracked_triad_2026_11_01.py",
"wizard-work/sol/verify_sol_triage_2026_11_02.py",
"wizard-work/sol/verify_untracked_triad_2026_11_01.py",
"wizard-work/sphinx/archive-mmlongcite-2026-10-25/probe_paper_facts.json"
],
"files_count": 92,
"generated_at": "2026-11-01T07:23:13Z",
"items": [
{
"category": "gate",
"decision": "approve",
"id": 1,
"path": "CHECK-sol-untracked-classify"
},
{
"category": "gate",
"decision": "approve",
"id": 2,
"path": "CHECK-triage-2026-10-29"
},
{
"category": "documentation",
"decision": "info",
"id": 3,
"path": "checkins/2026-10-27-sentinel.md"
},
{
"category": "data",
"decision": "observe",
"id": 4,
"path": "d.jsonl"
},
{
"category": "documentation",
"decision": "info",
"id": 5,
"path": "docs/clawrouter-plan.md"
},
{
"category": "documentation",
"decision": "info",
"id": 6,
"path": "docs/flux2-deploy.md"
},
{
"category": "documentation",
"decision": "info",
"id": 7,
"path": "docs/video-deploy.md"
},
{
"category": "tooling",
"decision": "review",
"id": 8,
"path": "inbox/dump_seed.py"
},
{
"category": "persistence",
"decision": "archive",
"id": 9,
"path": "kanban.db"
},
{
"category": "documentation",
"decision": "info",
"id": 10,
"path": "recoveries/apply-queue-status-2026-10-25.md"
},
{
"category": "data",
"decision": "observe",
"id": 11,
"path": "repo_exts.tsv"
},
{
"category": "data",
"decision": "observe",
"id": 12,
"path": "repo_files.tsv"
},
{
"category": "data",
"decision": "observe",
"id": 13,
"path": "repo_tree.tsv"
},
{
"category": "data",
"decision": "observe",
"id": 14,
"path": "s.json"
},
{
"category": "data",
"decision": "observe",
"id": 15,
"path": "test_sample.txt"
},
{
"category": "data",
"decision": "observe",
"id": 16,
"path": "test_validation.txt"
},
{
"category": "data",
"decision": "observe",
"id": 17,
"path": "test_verification.txt"
},
{
"category": "tooling",
"decision": "review",
"id": 18,
"path": "validate_artifact.py"
},
{
"category": "other",
"decision": "review",
"id": 19,
"path": "wizard-work/null/.hermes-tmp.RqZp3z"
},
{
"category": "documentation",
"decision": "info",
"id": 20,
"path": "wizard-work/null/README-gatehygiene.md"
},
{
"category": "documentation",
"decision": "info",
"id": 21,
"path": "wizard-work/null/README-q62-scan.md"
},
{
"category": "tooling",
"decision": "review",
"id": 22,
"path": "wizard-work/null/_probe10.py"
},
{
"category": "tooling",
"decision": "review",
"id": 23,
"path": "wizard-work/null/_probe11.py"
},
{
"category": "tooling",
"decision": "review",
"id": 24,
"path": "wizard-work/null/_probe12.py"
},
{
"category": "tooling",
"decision": "review",
"id": 25,
"path": "wizard-work/null/_probe13.py"
},
{
"category": "tooling",
"decision": "review",
"id": 26,
"path": "wizard-work/null/_probe14.py"
},
{
"category": "tooling",
"decision": "review",
"id": 27,
"path": "wizard-work/null/_probe5.py"
},
{
"category": "tooling",
"decision": "review",
"id": 28,
"path": "wizard-work/null/_probe6.py"
},
{
"category": "tooling",
"decision": "review",
"id": 29,
"path": "wizard-work/null/_probe7.py"
},
{
"category": "tooling",
"decision": "review",
"id": 30,
"path": "wizard-work/null/_probe8.py"
},
{
"category": "tooling",
"decision": "review",
"id": 31,
"path": "wizard-work/null/_probe9.py"
},
{
"category": "tooling",
"decision": "review",
"id": 32,
"path": "wizard-work/null/_probe_binding_defects.py"
},
{
"category": "tooling",
"decision": "review",
"id": 33,
"path": "wizard-work/null/_probe_gh1.py"
},
{
"category": "tooling",
"decision": "review",
"id": 34,
"path": "wizard-work/null/_probe_gh2.py"
},
{
"category": "tooling",
"decision": "review",
"id": 35,
"path": "wizard-work/null/_probe_gh3.py"
},
{
"category": "tooling",
"decision": "review",
"id": 36,
"path": "wizard-work/null/_probe_gh4.py"
},
{
"category": "tooling",
"decision": "review",
"id": 37,
"path": "wizard-work/null/_probe_gh5.py"
},
{
"category": "tooling",
"decision": "review",
"id": 38,
"path": "wizard-work/null/_probe_gh6.py"
},
{
"category": "tooling",
"decision": "review",
"id": 39,
"path": "wizard-work/null/_probe_gh7.py"
},
{
"category": "tooling",
"decision": "review",
"id": 40,
"path": "wizard-work/null/_probe_gh8.py"
},
{
"category": "tooling",
"decision": "review",
"id": 41,
"path": "wizard-work/null/_probe_gh9.py"
},
{
"category": "tooling",
"decision": "review",
"id": 42,
"path": "wizard-work/null/_probe_mut2.py"
},
{
"category": "tooling",
"decision": "review",
"id": 43,
"path": "wizard-work/null/_probe_mut_check.py"
},
{
"category": "tooling",
"decision": "review",
"id": 44,
"path": "wizard-work/null/_probe_q70_5h.py"
},
{
"category": "tooling",
"decision": "review",
"id": 45,
"path": "wizard-work/null/_probe_repro1.py"
},
{
"category": "tooling",
"decision": "review",
"id": 46,
"path": "wizard-work/null/_probe_repro2.py"
},
{
"category": "tooling",
"decision": "review",
"id": 47,
"path": "wizard-work/null/_probe_repro3.py"
},
{
"category": "tooling",
"decision": "review",
"id": 48,
"path": "wizard-work/null/_probe_repro4.py"
},
{
"category": "tooling",
"decision": "review",
"id": 49,
"path": "wizard-work/null/_probe_repro5.py"
},
{
"category": "tooling",
"decision": "review",
"id": 50,
"path": "wizard-work/null/_probe_repro6.py"
},
{
"category": "tooling",
"decision": "review",
"id": 51,
"path": "wizard-work/null/_probe_repro7.py"
},
{
"category": "tooling",
"decision": "review",
"id": 52,
"path": "wizard-work/null/check_gatehygiene.py"
},
{
"category": "tooling",
"decision": "review",
"id": 53,
"path": "wizard-work/null/check_provenance.py"
},
{
"category": "data",
"decision": "observe",
"id": 54,
"path": "wizard-work/null/check_provenance_baseline.tsv"
},
{
"category": "tooling",
"decision": "review",
"id": 55,
"path": "wizard-work/null/check_provenance_ledger.py"
},
{
"category": "tooling",
"decision": "review",
"id": 56,
"path": "wizard-work/null/check_q62_scan.py"
},
{
"category": "tooling",
"decision": "review",
"id": 57,
"path": "wizard-work/null/cli_contract.py"
},
{
"category": "tooling",
"decision": "review",
"id": 58,
"path": "wizard-work/null/diffkit/__init__.py"
},
{
"category": "tooling",
"decision": "review",
"id": 59,
"path": "wizard-work/null/diffkit/_flatpkg.py"
},
{
"category": "tooling",
"decision": "review",
"id": 60,
"path": "wizard-work/null/diffkit/core.py"
},
{
"category": "tooling",
"decision": "review",
"id": 61,
"path": "wizard-work/null/diffkit/diffkit_cli.py"
},
{
"category": "tooling",
"decision": "review",
"id": 62,
"path": "wizard-work/null/diffkit/myers.py"
},
{
"category": "tooling",
"decision": "review",
"id": 63,
"path": "wizard-work/null/diffkit_gate.py"
},
{
"category": "tooling",
"decision": "review",
"id": 64,
"path": "wizard-work/null/gatehygiene.py"
},
{
"category": "tooling",
"decision": "review",
"id": 65,
"path": "wizard-work/null/json_salvage.py"
},
{
"category": "other",
"decision": "review",
"id": 66,
"path": "wizard-work/null/membershipkit/.hermes-tmp.sx2gIa"
},
{
"category": "tooling",
"decision": "review",
"id": 67,
"path": "wizard-work/null/membershipkit/check_membershipkit.sh"
},
{
"category": "tooling",
"decision": "review",
"id": 68,
"path": "wizard-work/null/membershipkit/mutate_membershipkit.py"
},
{
"category": "tooling",
"decision": "review",
"id": 69,
"path": "wizard-work/null/membershipkit/test_membershipkit.py"
},
{
"category": "tooling",
"decision": "review",
"id": 70,
"path": "wizard-work/null/mutate_resolver.py"
},
{
"category": "tooling",
"decision": "review",
"id": 71,
"path": "wizard-work/null/mutate_salvage.py"
},
{
"category": "tooling",
"decision": "review",
"id": 72,
"path": "wizard-work/null/q58_warncontrol.py"
},
{
"category": "tooling",
"decision": "review",
"id": 73,
"path": "wizard-work/null/q62_scan.py"
},
{
"category": "tooling",
"decision": "review",
"id": 74,
"path": "wizard-work/null/q70_sabotage.py"
},
{
"category": "tooling",
"decision": "review",
"id": 75,
"path": "wizard-work/null/q76_binding.py"
},
{
"category": "tooling",
"decision": "review",
"id": 76,
"path": "wizard-work/null/q80_warn.py"
},
{
"category": "tooling",
"decision": "review",
"id": 77,
"path": "wizard-work/null/revwalk.py"
},
{
"category": "tooling",
"decision": "review",
"id": 78,
"path": "wizard-work/null/test_gate.py"
},
{
"category": "tooling",
"decision": "review",
"id": 79,
"path": "wizard-work/null/test_gate_mutants.py"
},
{
"category": "tooling",
"decision": "review",
"id": 80,
"path": "wizard-work/null/test_json_salvage.py"
},
{
"category": "tooling",
"decision": "review",
"id": 81,
"path": "wizard-work/null/test_myers_diff.py"
},
{
"category": "tooling",
"decision": "review",
"id": 82,
"path": "wizard-work/null/test_revwalk.py"
},
{
"category": "tooling",
"decision": "review",
"id": 83,
"path": "wizard-work/null/verify_check_provenance.py"
},
{
"category": "tooling",
"decision": "review",
"id": 84,
"path": "wizard-work/null/verify_staged.py"
},
{
"category": "other",
"decision": "review",
"id": 85,
"path": "wizard-work/sol/CHECK-sol-profile-triage-2026-11-02"
},
{
"category": "data",
"decision": "observe",
"id": 86,
"path": "wizard-work/sol/Triage-2026-11-02.json"
},
{
"category": "documentation",
"decision": "info",
"id": 87,
"path": "wizard-work/sol/Triage-2026-11-02.md"
},
{
"category": "tooling",
"decision": "review",
"id": 88,
"path": "wizard-work/sol/_print_paths.py"
},
{
"category": "tooling",
"decision": "review",
"id": 89,
"path": "wizard-work/sol/build_untracked_triad_2026_11_01.py"
},
{
"category": "tooling",
"decision": "review",
"id": 90,
"path": "wizard-work/sol/verify_sol_triage_2026_11_02.py"
},
{
"category": "tooling",
"decision": "review",
"id": 91,
"path": "wizard-work/sol/verify_untracked_triad_2026_11_01.py"
},
{
"category": "data",
"decision": "observe",
"id": 92,
"path": "wizard-work/sphinx/archive-mmlongcite-2026-10-25/probe_paper_facts.json"
}
],
"lane": "Triage - everyday Q&A and classification",
"repo": "/root/troupe-standup",
"rule": "path-based deterministic classifier (see MODULE_DOCLINE)",
"rule_version": "1",
"summary": {
"data": 11,
"documentation": 8,
"gate": 2,
"other": 3,
"persistence": 1,
"tooling": 67,
"total": 92
},
"task": "classify untracked repo files into deterministic buckets"
}
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""Build Untracked-Triad-2026-11-01.json.
Triage: classify the untracked (git ?? ) regular files of the repo into
deterministic buckets, then write the machine-readable artifact.
Rule (deterministic, derived from the relative path only):
category:
gate path starts with "CHECK-"
persistence ends with ".db"
data ends with ".json", ".jsonl", ".tsv" or ".txt"
tooling ends with ".py" or ".sh"
documentation ends with ".md"
other anything else
decision:
gate -> approve
tooling -> review
documentation -> info
data -> observe
persistence -> archive
other -> review
The manifest snapshots the exact file list at generation time (files + a
sha256 of the canonical list) so the independent verifier can confirm the
artifact is a faithful, reproducible classification, not a hand-edited guess.
"""
import hashlib
import json
import os
import subprocess
import sys
from datetime import datetime, timezone
def find_repo_root(start):
"""Walk up from *start* until a directory that contains a .git marker."""
d = os.path.abspath(start)
for _ in range(64):
if os.path.isdir(os.path.join(d, ".git")):
return d
parent = os.path.dirname(d)
if parent == d:
break
d = parent
raise RuntimeError("could not locate repo root (no .git) above " + start)
REPO_ROOT = find_repo_root(os.path.dirname(os.path.abspath(__file__)))
OUT_REPO = os.path.join(
REPO_ROOT, "wizard-work", "sol", "Untracked-Triad-2026-11-01.json"
)
OUT_WIZ = os.path.expanduser(
os.path.join("~", ".hermes", "wizard-outputs", "sol", "Untracked-Triad-2026-11-01.json")
)
def git_untracked(repo):
"""Return sorted list of untracked regular files (skip untracked dirs)."""
proc = subprocess.run(
["git", "-C", repo, "status", "--porcelain=v1"],
check=True, capture_output=True, text=True,
)
files = []
for line in proc.stdout.splitlines():
if line.startswith("??"):
path = line[3:].strip()
if path and not path.endswith("/"):
files.append(path)
return sorted(files)
def category_of(rel):
low = rel.lower()
if low.startswith("check-"):
return "gate"
if low.endswith(".db"):
return "persistence"
if low.endswith((".json", ".jsonl", ".tsv", ".txt")):
return "data"
if low.endswith((".py", ".sh")):
return "tooling"
if low.endswith(".md"):
return "documentation"
return "other"
DECISION = {
"gate": "approve",
"tooling": "review",
"documentation": "info",
"data": "observe",
"persistence": "archive",
"other": "review",
}
def build(files):
items = []
for i, rel in enumerate(files, start=1):
cat = category_of(rel)
items.append({
"id": i,
"path": rel,
"category": cat,
"decision": DECISION[cat],
})
return items
def corpus_sha256(files):
canon = json.dumps({"files": files}, sort_keys=True, separators=(",", ":")).encode("utf-8")
return hashlib.sha256(canon).hexdigest()
def main():
files = git_untracked(REPO_ROOT)
items = build(files)
manifest = {
"artifact": "Untracked-Triad-2026-11-01.json",
"lane": "Triage - everyday Q&A and classification",
"task": "classify untracked repo files into deterministic buckets",
"generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"repo": REPO_ROOT,
"rule": "path-based deterministic classifier (see MODULE_DOCLINE)",
"rule_version": "1",
"files": files,
"files_count": len(files),
"corpus_sha256": corpus_sha256(files),
"summary": {
"total": len(files),
"gate": sum(1 for it in items if it["category"] == "gate"),
"persistence": sum(1 for it in items if it["category"] == "persistence"),
"data": sum(1 for it in items if it["category"] == "data"),
"tooling": sum(1 for it in items if it["category"] == "tooling"),
"documentation": sum(1 for it in items if it["category"] == "documentation"),
"other": sum(1 for it in items if it["category"] == "other"),
},
"items": items,
}
for out in (OUT_REPO, OUT_WIZ):
os.makedirs(os.path.dirname(out), exist_ok=True)
with open(out, "w", encoding="utf-8") as fh:
json.dump(manifest, fh, indent=2, sort_keys=True)
fh.write("\n")
print("Untracked-Triad-2026-11-01.json written")
print(" files:", len(files))
print(" corpus_sha256:", manifest["corpus_sha256"])
print(" categories:", manifest["summary"])
if __name__ == "__main__":
main()
@@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""Independent verifier for Untracked-Triad-2026-11-01.json.
The classification rule is re-derived from each item's path (the generator's
stored category/decision fields are NOT trusted). A wrong classification makes
this exit non-zero. Additionally every classified path must still exist on disk
as a real, untracked, non-ignored file.
Exit 0 iff all checks pass.
"""
import hashlib
import json
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
MANIFEST = os.path.join(HERE, "Untracked-Triad-2026-11-01.json")
REPO_ROOT = os.path.dirname(os.path.dirname(HERE))
def cat_of(rel):
low = rel.lower()
if low.startswith("check-"):
return "gate"
if low.endswith(".db"):
return "persistence"
if low.endswith((".json", ".jsonl", ".tsv", ".txt")):
return "data"
if low.endswith((".py", ".sh")):
return "tooling"
if low.endswith(".md"):
return "documentation"
return "other"
DECISION = {
"gate": "approve",
"tooling": "review",
"documentation": "info",
"data": "observe",
"persistence": "archive",
"other": "review",
}
def git_status_untracked(repo):
proc = subprocess_run(["git", "-C", repo, "status", "--porcelain=v1"])
return set(line[3:].strip() for line in proc.stdout.splitlines()
if line.startswith("??") and not line[3:].strip().endswith("/"))
def git_check_ignored(repo, rel):
proc = subprocess_run(["git", "check-ignore", "-q", rel])
return proc.returncode == 0
def subprocess_run(cmd):
import subprocess
return subprocess.run(cmd, capture_output=True, text=True)
def main():
errors = []
try:
with open(MANIFEST, encoding="utf-8") as fh:
man = json.load(fh)
except Exception as exc:
print("FAIL: cannot load manifest:", exc)
sys.exit(1)
files = man.get("files", [])
items = man.get("items", [])
repo = man.get("repo", REPO_ROOT)
# 1. corpus sha256 must match the recorded file list
canon = json.dumps({"files": sorted(files)}, sort_keys=True, separators=(",", ":")).encode("utf-8")
computed_sha = hashlib.sha256(canon).hexdigest()
if computed_sha != man.get("corpus_sha256"):
errors.append("corpus_sha256 mismatch (recorded %s vs computed %s)" %
(man.get("corpus_sha256"), computed_sha))
# 2. ids must be unique and exactly 1..N
ids = [it.get("id") for it in items]
if sorted(ids) != list(range(1, len(items) + 1)):
errors.append("ids are not exactly 1..N")
# 3. re-derive category/decision from each path; verify file exists, untracked, non-ignored
for it in items:
rel = it.get("path")
exp_cat = it.get("category")
exp_dec = it.get("decision")
if not rel:
errors.append("missing path on item id=%r" % (it.get("id"),))
continue
act_cat = cat_of(rel)
act_dec = DECISION.get(act_cat)
if exp_cat != act_cat:
errors.append("id=%s path=%s category %s != re-derived %s" % (it.get("id"), rel, exp_cat, act_cat))
if exp_dec != act_dec:
errors.append("id=%s path=%s decision %s != re-derived %s" % (it.get("id"), rel, exp_dec, act_dec))
if not os.path.isfile(os.path.join(repo, rel)):
errors.append("id=%s path=%s does not exist as a real file" % (it.get("id"), rel))
if rel not in git_status_untracked(repo):
errors.append("id=%s path=%s is not currently untracked" % (it.get("id"), rel))
if git_check_ignored(repo, rel):
errors.append("id=%s path=%s is git-ignored" % (it.get("id"), rel))
# 4. summary counts must be consistent with items (skip the "total" key,
# which is the corpus size, not a category).
from collections import Counter
CAT_KEYS = {"gate", "persistence", "data", "tooling", "documentation", "other"}
cnt = Counter(it.get("category") for it in items)
for cat, n in man.get("summary", {}).items():
if cat not in CAT_KEYS:
continue
if cnt.get(cat, 0) != n:
errors.append("summary %s=%d but items have %d" % (cat, n, cnt.get(cat, 0)))
if errors:
print("FAIL: %d problem(s)" % len(errors))
for e in errors[:40]:
print(" -", e)
sys.exit(1)
print("PASS: %d files classified; %d re-derive to matching category/decision; "
"all files real, untracked, non-ignored; corpus sha256 matches."
% (len(files), len(items)))
sys.exit(0)
if __name__ == "__main__":
main()