Files
reliquary/plugins/orbit/skills/orbit/scripts/preflight.py
T
g4borg f1de89e869 🤖 Add orbit prototype plugin for Rust code review
Standalone plugin with one skill: when to use orbit and when grep, a
mandatory preflight for stale indexes and blind regions (cfg(test),
features, macros, dyn dispatch), a scripted review pass over isolated
worktree DBs, and a JSONL feedback log for mining real usage later.
Registered in the marketplace as a prototype.
2026-09-29 19:52:59 +02:00

107 lines
7.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""Recipe 5: blindness preflight — what orbit cannot see for THIS diff. Run before trusting any orbit answer.
Usage: preflight.py <env-file> <defdiff.json>
Checks (all cheap, no compilation):
1. index freshness: manifest commit == worktree HEAD, worktree clean (orbit indexes the WORKING TREE
but labels rows with HEAD's sha), skipped/errored files from the index JSON
2. changed files orbit has no definitions for (gated at a parent `mod`, tests/ dirs, outside module tree)
3. changed lines inside #[cfg(test)] / #[cfg(feature)] / other cfg regions (from defdiff attribution)
4. Cargo features of touched crates (orbit evaluates ALL features off, default ones too)
5. constructs in changed hunks that CALLS edges miss: add_systems/observers, rsx!, dyn, macro args
Prints a verdict line per area: OK / PARTIAL / BLIND, and the grep fallbacks to use.
"""
import collections, json, os, re, sys
sys.path.insert(0, __file__.rsplit('/', 1)[0])
from ob import load_env, q, git
env = load_env(sys.argv[1]); dd = json.load(open(sys.argv[2]))
DB, BW, TW, REPO, B, T = env['DB'], env['BASE_WT'], env['TIP_WT'], env['REPO'], env['BASE_SHA'], env['TIP_SHA']
H = os.path.dirname(DB); NAME = os.path.basename(DB).rsplit('.', 1)[0]
out = []
# 1. freshness
man = {m['repo_path']: m for m in q("SELECT repo_path, commit_sha, status FROM _orbit_manifest", db=DB, all_=True)}
for side, wt, sha in (('base', BW, B), ('tip', TW, T)):
m = man.get(wt); head = git(wt, 'rev-parse', 'HEAD').strip(); dirty = git(wt, 'status', '--porcelain', '--untracked-files=normal').strip()
js = f"{H}/out/{NAME}-{side}.index.json"
sk = json.load(open(js))['processing'] if os.path.exists(js) else {}
ok = m and m['commit_sha'] == head == sha and m['status'] == 'indexed' and not dirty and not sk.get('skipped_files') and not sk.get('errored_files')
out.append(f"[{'OK' if ok else 'STALE/BLIND'}] index {side}: manifest={m['commit_sha'][:8] if m else 'MISSING'} head={head[:8]} "
f"dirty={len(dirty.splitlines())} skipped={sk.get('skipped_files','?')} errored={sk.get('errored_files','?')}")
# 2. changed files without definitions
defs_by_file = collections.Counter()
for r in q("SELECT file_path, count(*) n FROM gl_definition WHERE definition_type <> 'Macro' GROUP BY 1", repo=TW, db=DB):
defs_by_file[r['file_path']] = r['n']
files = q("SELECT path, size_bytes FROM gl_file WHERE language = 'rust'", repo=TW, db=DB)
size = {f['path']: f['size_bytes'] for f in files}
changed = [f for f in dd['changed_files'] if f.endswith('.rs')]
blind = [f for f in changed if f in size and defs_by_file[f] == 0]
gone = [f for f in changed if f not in size]
def why(f, depth=0):
p = f.split('/')
if '/tests/' in f and p[2] == 'tests': return 'integration test w/ #![cfg] or not a target'
if re.search(r'(^|/)(tests?|test_\w+|\w+_tests?)(\.rs|/)', f): return 'test module (cfg(test) at parent mod)'
# find the gating `mod` decl for this file: parent dir mod.rs/lib.rs or sibling-named file
stem = p[-1][:-3] if p[-1] not in ('mod.rs', 'lib.rs', 'main.rs') else p[-2]
for cand in ('/'.join(p[:-1] + ['mod.rs']), '/'.join(p[:-1]) + '.rs', '/'.join(p[:-1] + ['lib.rs']), '/'.join(p[:-1] + ['main.rs']),
'/'.join(p[:-2] + ['mod.rs']), '/'.join(p[:-2]) + '.rs', '/'.join(p[:-2] + ['lib.rs'])):
try: L = open(f"{TW}/{cand}", errors='replace').read().split('\n')
except (FileNotFoundError, IsADirectoryError): continue
for i, l in enumerate(L):
if re.match(rf'\s*(pub(\([^)]*\))?\s+)?mod\s+{re.escape(stem)}\s*;', l):
for j in range(i - 1, max(-1, i - 4), -1):
m = re.match(r'\s*#\[cfg\((.*)\)\]', L[j])
if m: return f"gated at {cand}:{i+1} by cfg({m.group(1)})"
if depth < 6 and cand != f:
up = why(cand, depth + 1)
if up.startswith('gated'): return up
return f"declared at {cand}:{i+1}, no cfg found up the mod chain"
return 'not reachable from a crate root / gated higher up'
tot = len(changed)
out.append(f"[{'OK' if not blind else 'PARTIAL'}] {len(blind)}/{tot} changed .rs files have NO definitions in orbit (read/grep them):")
for f in sorted(blind, key=lambda f: -size[f])[:12]:
out.append(f" {f} ({size[f]//1024} KB) — {why(f)}")
if len(blind) > 12: out.append(f" … {len(blind)-12} more")
if gone: out.append(f" (deleted at tip: {len(gone)} files)")
# 3. changed lines by visibility
un = collections.Counter()
for k, n in dd['unattributed'].items(): un[k.split('|')[1]] += n
dstat = git(REPO, 'diff', '--numstat', B, T, '--', '*.rs')
nchanged = sum(int(a) + int(b) for a, b, *_ in (l.split('\t') for l in dstat.splitlines()) if a != '-')
blindl = un['cfg-test'] + un['cfg-feature'] + un['cfg-other'] + un['cfg-debug'] + un['file-has-no-defs']
out.append(f"[{'OK' if not blindl else 'PARTIAL'}] {blindl}/{nchanged} changed lines ({blindl/max(1,nchanged):.0%}) are in code orbit cannot see; unattributed: "
+ ', '.join(f"{k}={v}" for k, v in un.most_common() if k not in ('blank/comment',)))
# 4. features of touched crates
crates = sorted({f.split('/')[1] for f in changed if f.startswith('crates/')} | ({'.'} if any(not f.startswith('crates/') for f in changed) else set()))
feats = []
for c in crates:
path = f"{TW}/Cargo.toml" if c == '.' else f"{TW}/crates/{c}/Cargo.toml"
try: txt = open(path).read()
except FileNotFoundError: continue
m = re.search(r'^\[features\]\s*\n(.*?)(^\[|\Z)', txt, re.S | re.M)
if not m: continue
names = [x.group(1) for x in re.finditer(r'^([\w-]+)\s*=', m.group(1), re.M)]
d = re.search(r'^default\s*=\s*\[(.*?)\]', m.group(1), re.M | re.S)
dflt = re.findall(r'"([\w-]+)"', d.group(1)) if d else []
feats.append(f"{c if c != '.' else '(root)'}: {len([n for n in names if n != 'default'])} features" + (f", default={dflt}" if dflt else ''))
out.append(f"[INFO] orbit indexes with ALL cargo features OFF (defaults too). Touched crates with features: " + ('; '.join(feats) if feats else 'none'))
# 5. constructs CALLS misses, counted on added lines
diff = git(REPO, 'diff', '-U0', B, T, '--', '*.rs')
pat = {'add_systems/observer (fn refs, no CALLS)': r'add_systems|add_observer|\.observe\(', 'rsx! (components, no CALLS)': r'rsx!',
'dyn Trait (dispatch, few CALLS)': r'\bdyn\s+\w', 'assert!/matches!/vec! args (missed calls)': r'\b(assert\w*|matches|vec|format|debug_assert\w*)!'}
cnt = collections.Counter()
for l in diff.split('\n'):
if l.startswith('+') and not l.startswith('+++'):
for k, rx in pat.items():
if re.search(rx, l): cnt[k] += 1
out.append("[INFO] added lines with CALLS-blind constructs: " + (', '.join(f"{k}: {v}" for k, v in cnt.most_common()) or 'none'))
print(f"# preflight {B[:8]}..{T[:8]} ({os.path.basename(REPO)})")
print('\n'.join(out))
print("# fallbacks: blind files -> read them / git grep; cfg-test lines -> read the test module; "
"callers of fn refs/rsx/dyn -> git grep -nw <name>; treat every 'no callers/no implementors' as UNKNOWN until grep agrees")