from __future__ import annotations import argparse import fnmatch import json from pathlib import Path ROOT = Path(__file__).resolve().parents[1] ROUTING_FILE = ROOT / "FILES.json" EXCLUDE_DIRS = {".git", "__pycache__"} EXACT_DESCRIPTIONS = { ".gitignore": "ignore rules for local/generated artifacts", ".htaccess": "Apache/static-host cache, MIME, and service-worker behavior", "app_manifest.json": "canonical version plus CSS/JS app shell manifest", "favicon.ico": "legacy favicon", "index.html": "app DOM shell, canvas, panels, dialogs, and ordered script tags", "README.md": "small AI entrypoint and read strategy", "AI_MAP.md": "subsystem map and task-oriented read sets", "GAME_FEATURES.md": "player-facing game features, UI, items, and behavior guide", "FILES.json": "machine-readable file routing and glob semantics", "service-worker.js": "generated offline/cache service worker", } GROUP_DESCRIPTIONS = { "css": "stylesheet layer", "scripts": "repo tooling", "js": "runtime JavaScript", "assets/sprites": "creature state sprite", "assets/ui": "UI/app/tool image asset", "assets/objects": "field object image asset", "assets/sounds": "voice or sound-effect sample", } JS_PREFIX_DESCRIPTIONS = [ ("world_", "world state/view/update/tool/environment/combat/social subsystem"), ("tarinai_", "creature action/needs/social/item/update subsystem"), ("item_lifecycle_step_", "item lifecycle pipeline stage"), ("item_lifecycle_", "item lifecycle subsystem"), ("item_dynamic_", "dynamic item behavior subsystem"), ("item_", "item registry/runtime/update/render subsystem"), ("simulation_", "simulation phase subsystem"), ("ui_family_", "family tree UI subsystem"), ("ui_input_", "input handling subsystem"), ("ui_", "DOM/panel UI subsystem"), ("save_", "save/load persistence subsystem"), ("physics_", "physics helper/world subsystem"), ] JS_EXACT_DESCRIPTIONS = { "ants.js": "ant actor and ant nest worker behavior", "assets.js": "image loading, image metrics, and render caches", "audio.js": "sample playback and audio controls", "burn_motion_util.js": "burning drift and smoke motion helpers", "collision_footprint_system.js": "collision footprint shapes and overlap tests", "command_dispatcher.js": "central command routing for UI/tools/pointers/save", "constraint_system.js": "attachments, ropes, push/pull, and collision constraints", "data.js": "sprite/decor definitions, needs, config, field tuning", "display_helpers.js": "shared canvas/display helpers and weather labels", "debug_tools.js": "browser debug overlay and diagnostics", "disease_registry.js": "disease definitions and lookup", "event_bus.js": "global event bus singleton", "family_graph.js": "lineage graph helpers", "geometry_helpers.js": "shared primitive geometry helpers", "ground_types.js": "ground definitions, multipliers, limits, labels", "health.js": "health, stress, need, damage, and death helpers", "history_system.js": "rolling world statistics/history", "impact_core_system.js": "impact force and hit candidate helpers", "input_mode_manager.js": "pointer/touch/mobile input classification", "items.js": "item constructors, food/grass helpers, item preview drawing", "main.js": "startup, restore, UI binding, update/render loop", "math.js": "shared numeric helpers", "mechanical_shape_bridge.js": "mechanical item shape bridge for world queries", "mechanical_system.js": "mechanical item forces, rails, ropes, pins, fences", "perf_profiler.js": "performance profiler and quality adaptation", "registry_base.js": "registry normalization helper base", "render.js": "main canvas renderer", "restore_coordinator.js": "post-load restore ordering/fixups", "sim_core.js": "simulation primitives and shared helpers", "sound_pack.js": "logical sound ID to asset mapping", "snapshot_system.js": "world snapshot capture/restore", "structures.js": "structure definitions and placement/runtime helpers", "structure_lifecycle.js": "structure update lifecycle", "system_order.js": "named update phase order", "text_catalog.js": "Japanese UI/state/behavior labels", "version.js": "app version/cache/static query config", "weather_system.js": "weather selection and application", } def rel(path: Path) -> str: return path.relative_to(ROOT).as_posix() def iter_files() -> list[str]: files: list[str] = [] for path in ROOT.rglob("*"): if not path.is_file(): continue if any(part in EXCLUDE_DIRS for part in path.relative_to(ROOT).parts): continue files.append(rel(path)) return sorted(files) def load_routing() -> dict: return json.loads(ROUTING_FILE.read_text(encoding="utf-8")) def group_for(path: str, routing: dict) -> str: for group in routing.get("groups", []): for pattern in group.get("patterns", []): if fnmatch.fnmatch(path, pattern): return group["id"] return path.split("/", 1)[0] def describe(path: str, routing: dict) -> str: if path in EXACT_DESCRIPTIONS: return EXACT_DESCRIPTIONS[path] if path.startswith("js/"): name = path.rsplit("/", 1)[-1] if name in JS_EXACT_DESCRIPTIONS: return JS_EXACT_DESCRIPTIONS[name] for prefix, desc in JS_PREFIX_DESCRIPTIONS: if name.startswith(prefix): return desc return "runtime JavaScript module" if path.startswith("scripts/"): return "repo script/tooling" if path.startswith("css/"): return "stylesheet layer" if path.startswith("assets/"): for prefix, desc in GROUP_DESCRIPTIONS.items(): if path.startswith(prefix + "/"): return desc return "static asset" group_id = group_for(path, routing) for group in routing.get("groups", []): if group.get("id") == group_id: return group.get("meaning", "project file") return "project file" def matches_query(path: str, query: str, routing: dict) -> bool: if not query: return True q = query.lower() if q in path.lower(): return True group_id = group_for(path, routing).lower() if q == group_id or q in group_id: return True return q in describe(path, routing).lower() def build_records(query: str = "") -> list[dict]: routing = load_routing() records = [] for path in iter_files(): if not matches_query(path, query, routing): continue records.append({ "path": path, "group": group_for(path, routing), "meaning": describe(path, routing), }) return records def print_compact(records: list[dict]) -> None: groups: dict[str, list[dict]] = {} for record in records: groups.setdefault(record["group"], []).append(record) for group_id in sorted(groups): print(f"{group_id}: {len(groups[group_id])} files") meanings = sorted({record["meaning"] for record in groups[group_id]}) for meaning in meanings[:8]: print(f" - {meaning}") def print_all(records: list[dict]) -> None: for record in records: print(f"{record['path']}: {record['meaning']}") def main() -> int: parser = argparse.ArgumentParser(description="Print token-aware AI file inventory.") parser.add_argument("query", nargs="?", default="", help="Optional group/path/meaning filter, e.g. ui, world, assets.") parser.add_argument("--all", action="store_true", help="Print every matching file instead of grouped summary.") parser.add_argument("--json", action="store_true", help="Print JSON records.") args = parser.parse_args() records = build_records(args.query) if args.json: print(json.dumps(records, ensure_ascii=False, indent=2)) elif args.all: print_all(records) else: print_compact(records) return 0 if __name__ == "__main__": raise SystemExit(main())