mirror of
https://github.com/Asixa/codemap-skill.git
synced 2026-08-28 17:01:14 +00:00
123 lines
5.2 KiB
Python
123 lines
5.2 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():
|
|
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()
|