Files
codemap-skill/scripts/query.py
T

127 lines
5.4 KiB
Python

#!/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():
try:
sys.stdout.reconfigure(encoding="utf-8") # findings/descriptions may be non-ASCII
except (AttributeError, ValueError):
pass
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()