#!/usr/bin/env python3 """Build course/atlas.json from newOS4 source. Ground truth for every lesson claim.""" import json, os, re, glob, hashlib from datetime import datetime, timezone ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) OUT = os.path.join(ROOT, 'course', 'atlas.json') CORE_DIR, EXT_DIR = os.path.join(ROOT, 'modules', 'core'), os.path.join(ROOT, 'modules', 'ext') SKIP = {'core-bundle.js', 'critical-ext-bundle.js', 'sandbox-worker.js'} CATEGORY = [ ('module-fs', 'filesystem'), ('virtual-fs', 'filesystem'), ('module-aiassist', 'ai'), ('module-ollama', 'ai'), ('module-memory-graph', 'ai'), ('module-smartctx', 'ai'), ('module-neural-vfs', 'ai'), ('module-world-model', 'ai'), ('module-sandbox', 'ai'), ('module-searxng', 'web'), ('module-fetcher', 'web'), ('webdav', 'cloud'), ('editor', 'editor'), ('filemgr', 'filesystem'), ('finder', 'filesystem'), ('diff', 'editor'), ('cron', 'automation'), ('kanban', 'automation'), ('shell', 'shell'), ('busybox', 'shell'), ('theme', 'ui'), ('floating', 'ui'), ('voice', 'ui'), ('three', 'ui'), ('console', 'dev'), ('debug-panel', 'dev'), ('wpm', 'system'), ('tutorial', 'learning'), ('skill-pack-core', 'ai'), ('cryptogzip', 'system'), ('module-agent-registry', 'agents'), ('module-orchestrator', 'agents'), ('module-leases', 'agents'), ('module-chatstudio', 'ai'), ('module-servermon', 'system'), ('module-config', 'system'), ('module-system', 'system'), ('course', 'learning'), ] def category_for(filename, cmd): for frag, cat in CATEGORY: if frag in filename: return cat if cmd.startswith('webdav'): return 'cloud' if cmd.startswith('cron') or cmd.startswith('kanban'): return 'automation' if cmd.startswith('ai-') or cmd in ('ai', 'ask'): return 'ai' return 'general' def tier_lists(): rebuild = open(os.path.join(ROOT, 'scripts', 'rebuild-bundles.py')).read() def listed(name): m = re.search(name + r"\s*=\s*\[([^\]]+)\]", rebuild, re.S) return set(re.findall(r"([a-z0-9-]+\.js)", m.group(1))) if m else set() loader = open(os.path.join(ROOT, 'modules', 'core', 'module-loader.js')).read() m = re.search(r'extKeys\s*=\s*\[([^\]]+)\]', loader) ext = set(re.findall(r"'([a-z0-9-]+)'", m.group(1))) if m else set() return listed('CORE_FILES'), listed('CRITICAL_EXT_FILES'), ext CORE_FILES, CRITICAL_EXT_FILES, EXT_KEYS = tier_lists() def module_tier(filename): base = os.path.basename(filename) if base in CRITICAL_EXT_FILES: return 'critical-ext-bundle' if base in CORE_FILES: return 'core-bundle' if base[:-3] in EXT_KEYS: return 'ext-auto' return 'ext-manual' def desc_map(text): out = {} for m in re.finditer(r"'([a-z0-9][a-z0-9_-]{1,40})'\s*:\s*'((?:[^'\\]|\\.){8,300})'", text): cmd, desc = m.group(1), m.group(2) if len(desc) < 10 or ' ' not in desc or desc.endswith('.js'): continue out.setdefault(cmd, desc.replace("\\'", "'")) return out def main(): commands, modules = {}, {} files = sorted(glob.glob(os.path.join(CORE_DIR, '*.js')) + glob.glob(os.path.join(EXT_DIR, '*.js'))) for path in files: base = os.path.basename(path) if base in SKIP: continue text = open(path, encoding='utf-8', errors='ignore').read() cmds = sorted(set(re.findall(r"commands\['([A-Za-z0-9_-]+)'\]\s*=", text))) if not cmds: continue modname = base[:-3] modules[modname] = {'file': os.path.relpath(path, ROOT), 'tier': module_tier(path), 'commands': cmds, 'lines': text.count('\n') + 1} dm = desc_map(text) for c in cmds: commands[c] = {'module': modname, 'file': os.path.relpath(path, ROOT), 'tier': module_tier(path), 'category': category_for(base, c), 'desc': dm.get(c, ''), 'builtin': False, 'example': c} term = open(os.path.join(ROOT, 'modules', 'core', 'terminal.js'), encoding='utf-8').read() for c in sorted(set(re.findall(r"cmd === '([a-z0-9-]+)'", term))): if c in commands: continue commands[c] = {'module': 'terminal', 'file': 'modules/core/terminal.js', 'tier': 'core-bundle', 'category': 'system', 'desc': '', 'builtin': True, 'example': c} idx_path = os.path.join(ROOT, 'modules-index.json') mod_by_cmd = {} if os.path.exists(idx_path): idx = json.load(open(idx_path)) for m in idx.get('modules', {}).values(): for c in m.get('commands', []): mod_by_cmd.setdefault(c, m) for c, e in commands.items(): if not e['desc']: m = mod_by_cmd.get(c) or {} e['desc'] = (m.get('description') or f'{c} — command from {e["module"]}')[:300] payload = { 'version': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'), 'source_hash': hashlib.sha256(''.join(sorted(commands)).encode()).hexdigest()[:16], 'commands': dict(sorted(commands.items())), 'modules': dict(sorted(modules.items())), 'coverage': {'commands': len(commands), 'described': sum(1 for e in commands.values() if e['desc']), 'modules': len(modules), 'tiers': {t: sum(1 for e in commands.values() if e['tier'] == t) for t in ('core-bundle', 'critical-ext-bundle', 'ext-auto', 'ext-manual')}}, } os.makedirs(os.path.dirname(OUT), exist_ok=True) tmp = OUT + '.tmp' with open(tmp, 'w', encoding='utf-8') as f: json.dump(payload, f, ensure_ascii=False, indent=1) os.replace(tmp, OUT) os.chmod(OUT, 0o644) print(json.dumps(payload['coverage'], ensure_ascii=False)) if __name__ == '__main__': main()