aboutsummaryrefslogtreecommitdiff
path: root/scripts/newos4-skill-audit.py
blob: c10274cb55244b12b039ef1c87424b96eafc200d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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())