diff options
Diffstat (limited to 'scripts/newos4-skill-audit.py')
| -rwxr-xr-x | scripts/newos4-skill-audit.py | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/scripts/newos4-skill-audit.py b/scripts/newos4-skill-audit.py new file mode 100755 index 0000000..c10274c --- /dev/null +++ b/scripts/newos4-skill-audit.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Audit newOS4 skill claims against the live tree. Exit 1 on any drift.""" +import json, os, re, subprocess, sys + +ROOT = '/var/www/webx11.duckdns.org/spaces/newos4' +SKILLS = os.path.expanduser('~/.hermes/profiles/webx11/skills/webx11') +PAIRS = ['newos4-core-module-editing', 'newos4-module-patch-and-verify'] + + +def read(p): + try: + with open(p, encoding='utf-8') as f: + return f.read() + except Exception: + return '' + + +def probe_version(): + loader = read(os.path.join(ROOT, 'modules/core/module-loader.js')) + m = re.search(r"LATEST_VER\s*=\s*'([^']+)'", loader) + if not m: + return False, 'LATEST_VER not found in module-loader.js' + ver = m.group(1) + n = read(os.path.join(ROOT, 'main.html')).count(ver) + return n == 3, f'{ver} appears {n}x in main.html (expect 3)' + + +def probe_read_file(): + src = read(os.path.join(ROOT, 'modules/core/module-aiassist.js')) + has_offset = 'offset: { type:' in src + has_next = 'next_offset' in src + m = re.search(r'maxContextTokens\s*=\s*(\d+)', src) + return (has_offset and has_next and bool(m)), \ + f'offset={has_offset} next_offset={has_next} maxContextTokens={m.group(1) if m else None}' + + +def probe_selection(): + src = read(os.path.join(ROOT, 'modules/ext/filemgr.js')) + return ('_multiSelect' in src and '_getSelectedPaths' in src), \ + f'_multiSelect={"_multiSelect" in src} _getSelectedPaths={"_getSelectedPaths" in src}' + + +def probe_scripts(): + need = ['scripts/rebuild-bundles.py', 'scripts/lint-no-undef.py', + 'scripts/newos4-maintain.py', 'scripts/newos4-triage.py'] + missing = [p for p in need if not os.path.exists(os.path.join(ROOT, p))] + return not missing, f'missing={missing or "none"}' + + +def probe_bundle_membership(): + loader = read(os.path.join(ROOT, 'modules/core/module-loader.js')) + return "extKeys = [" in loader, 'extKeys literal present' + + +PROBES = { + 'version': probe_version, + 'read_file_contract': probe_read_file, + 'selection_symbols': probe_selection, + 'scripts': probe_scripts, + 'ext_keys': probe_bundle_membership, +} + + +def main(): + results, failed = {}, 0 + for name, fn in PROBES.items(): + try: + ok, detail = fn() + except Exception as e: + ok, detail = False, f'probe threw: {e}' + results[name] = {'ok': ok, 'detail': detail} + print(('PASS ' if ok else 'FAIL ') + name + ': ' + detail) + failed += 0 if ok else 1 + + for pair in PAIRS: + p = os.path.join(SKILLS, pair, 'SKILL.md') + results['skill_' + pair] = {'ok': os.path.exists(p), 'detail': p} + if not os.path.exists(p): + print('FAIL skill_' + pair + ': missing ' + p) + failed += 1 + + out = '/var/lib/webx11/newos4-maintenance/skill-audit.json' + os.makedirs(os.path.dirname(out), exist_ok=True) + with open(out, 'w', encoding='utf-8') as f: + json.dump(results, f, indent=2) + print(f'skill audit: {len(PROBES) - failed + len(PAIRS)}/{len(PROBES) + len(PAIRS)} pass') + return 1 if failed else 0 + + +if __name__ == '__main__': + sys.exit(main()) |