tarinai/scripts/ai_inventory.py
2026-07-05 18:01:36 +09:00

212 lines
8 KiB
Python

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())