mirror of
https://github.com/Asixa/codemap-skill.git
synced 2026-08-28 17:01:14 +00:00
Initial commit: codemap skill (architecture map + per-module code-quality audit)
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
"""query.py — token-cheap module selector over modules.json.
|
||||
|
||||
An agent uses this to find exactly the modules it must act on WITHOUT reading the
|
||||
whole state file. Filter by grade/score level, by smell tag, by finding severity,
|
||||
by band/coupling, or by staleness; emit just ids, paths, a compact table, the
|
||||
findings, or filtered JSON.
|
||||
|
||||
Examples:
|
||||
# all C-and-below modules → just their ids (pipe into a fix loop)
|
||||
python query.py --state s.json --max-grade C --format ids
|
||||
# modules with a dual-format problem, worst first, as a table
|
||||
python query.py --state s.json --tag dual-format
|
||||
# the file globs to read for every module that has a HIGH finding
|
||||
python query.py --state s.json --sev HIGH --format paths
|
||||
# the actual findings to fix for one tag, as text
|
||||
python query.py --state s.json --tag glue --format findings
|
||||
# what needs re-auditing (stale or never scored)
|
||||
python query.py --state s.json --needs-audit --format ids
|
||||
|
||||
Filters combine with AND. --tag may be repeated (ANY by default, --match-all for AND).
|
||||
Stdlib only.
|
||||
"""
|
||||
import argparse, json, sys
|
||||
|
||||
# include a module if its score is strictly below this bound (grade and worse).
|
||||
GRADE_BOUND = {"A": 101, "B": 90, "C": 75, "D": 60, "F": 40}
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="filter modules.json for agents")
|
||||
ap.add_argument("--state", required=True)
|
||||
ap.add_argument("--max-grade", choices=list(GRADE_BOUND),
|
||||
help="include this grade AND worse (e.g. C → C,D,F)")
|
||||
ap.add_argument("--min-score", type=int)
|
||||
ap.add_argument("--max-score", type=int)
|
||||
ap.add_argument("--tag", action="append", default=[],
|
||||
help="smell tag; repeatable (ANY unless --match-all)")
|
||||
ap.add_argument("--match-all", action="store_true", help="require ALL --tag")
|
||||
ap.add_argument("--sev", choices=["HIGH", "MED", "LOW"],
|
||||
help="has at least one finding of this severity")
|
||||
ap.add_argument("--band")
|
||||
ap.add_argument("--coupling", choices=["low", "med", "high", "core"])
|
||||
ap.add_argument("--needs-audit", action="store_true",
|
||||
help="stale (contentHash != auditedHash) or never scored")
|
||||
ap.add_argument("--sort", choices=["score", "loc", "label", "band"], default="score")
|
||||
ap.add_argument("--desc", action="store_true", help="sort descending")
|
||||
ap.add_argument("--limit", type=int)
|
||||
ap.add_argument("--format", choices=["table", "ids", "paths", "findings", "json", "count"],
|
||||
default="table")
|
||||
args = ap.parse_args()
|
||||
|
||||
state = json.load(open(args.state, encoding="utf-8"))
|
||||
mods = state.get("modules", [])
|
||||
|
||||
bound = GRADE_BOUND[args.max_grade] if args.max_grade else None
|
||||
tags = set(args.tag)
|
||||
|
||||
def keep(m):
|
||||
s = m.get("score")
|
||||
if bound is not None and not (s is not None and s < bound):
|
||||
return False
|
||||
if args.min_score is not None and not (s is not None and s >= args.min_score):
|
||||
return False
|
||||
if args.max_score is not None and not (s is not None and s <= args.max_score):
|
||||
return False
|
||||
if tags:
|
||||
mt = set(m.get("tags") or [])
|
||||
if args.match_all and not tags <= mt:
|
||||
return False
|
||||
if not args.match_all and not (tags & mt):
|
||||
return False
|
||||
if args.sev and not any(f.get("sev") == args.sev for f in (m.get("findings") or [])):
|
||||
return False
|
||||
if args.band and m.get("band") != args.band:
|
||||
return False
|
||||
if args.coupling and m.get("coupling") != args.coupling:
|
||||
return False
|
||||
if args.needs_audit:
|
||||
stale = (m.get("score") is None or not m.get("auditedHash")
|
||||
or m.get("auditedHash") != m.get("contentHash"))
|
||||
if not stale:
|
||||
return False
|
||||
return True
|
||||
|
||||
sel = [m for m in mods if keep(m)]
|
||||
key = {"score": lambda m: (m.get("score") if m.get("score") is not None else 999),
|
||||
"loc": lambda m: m.get("loc") or 0,
|
||||
"label": lambda m: m.get("label", ""),
|
||||
"band": lambda m: m.get("band", "")}[args.sort]
|
||||
sel.sort(key=key, reverse=args.desc)
|
||||
if args.limit:
|
||||
sel = sel[:args.limit]
|
||||
|
||||
if args.format == "count":
|
||||
print(len(sel))
|
||||
elif args.format == "ids":
|
||||
print(" ".join(m["id"] for m in sel))
|
||||
elif args.format == "paths":
|
||||
seen, out = set(), []
|
||||
for m in sel:
|
||||
for p in (m.get("paths") or []):
|
||||
if p not in seen:
|
||||
seen.add(p); out.append(p)
|
||||
print("\n".join(out))
|
||||
elif args.format == "findings":
|
||||
for m in sel:
|
||||
for f in (m.get("findings") or []):
|
||||
print(f"{m['id']}\t{f.get('sev')}\t{f.get('loc','')}\t{f.get('text','')}")
|
||||
elif args.format == "json":
|
||||
print(json.dumps(sel, ensure_ascii=False, indent=1))
|
||||
else: # table
|
||||
print(f"# {len(sel)} of {len(mods)} modules match", file=sys.stderr)
|
||||
for m in sel:
|
||||
s = m.get("score")
|
||||
sg = f"{s if s is not None else '--'}/{m.get('grade','-')}"
|
||||
tg = ",".join(t for t in (m.get("tags") or []) if t != "clean") or "-"
|
||||
print(f"{m['id']:<22} {sg:>7} {str(m.get('loc') or 0):>6} {m.get('band',''):<12} {tg}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user