diff options
| author | Agando <roduccomm@gmail.com> | 2026-10-18 14:05:18 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-10-18 14:05:18 +0200 |
| commit | 0d0d936e7b5cd41a31773069540b1ddb8836ef44 (patch) | |
| tree | 273e7ee9a72453655119bf0f63efb9b61e193d7f /scripts/gen-course-atlas.py | |
| parent | 526c420f4591f0a58f805b473fa76d3da2981eae (diff) | |
newOS4: interactive AI course (terminal module, portal, atlas, telemetry, daily maintenance+cron) + free store card
Diffstat (limited to 'scripts/gen-course-atlas.py')
| -rw-r--r-- | scripts/gen-course-atlas.py | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/scripts/gen-course-atlas.py b/scripts/gen-course-atlas.py new file mode 100644 index 0000000..559fd4c --- /dev/null +++ b/scripts/gen-course-atlas.py @@ -0,0 +1,113 @@ +#!/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() |