#!/usr/bin/env python3 """ build_index.py — generate INDEX.md from workspace manifests. Walks the `vk-gramatvediba` workspace tree, reads every `uapf.yaml` and the top-level `enterprise/enterprise.yaml`, groups packages by their declared level (0–4), and emits a level-grouped index at the workspace root. Per UAPF specification §01-concepts.md, levels are an aggregation and governance scope only — directory structure must not encode them. Per §04-folder-structure.md the SHOULD-recommended workspace layout is `enterprise/` + `domains/` + `processes/` regardless of level. INDEX.md is therefore a level-grouped *view* over a spec-conformant on-disk layout, not an alternative layout. Usage: python3 tools/build-index/build_index.py Dependencies: pyyaml. """ import glob import os import sys try: import yaml except ImportError: sys.exit("error: pyyaml is required (pip install pyyaml)") def find_workspace_root(start): """Walk up from `start` until a directory containing `enterprise/` is found.""" d = os.path.abspath(start) while d != os.path.dirname(d): if os.path.isdir(os.path.join(d, "enterprise")) and \ os.path.isdir(os.path.join(d, "processes")): return d d = os.path.dirname(d) sys.exit("error: not inside a UAPF workspace (no enterprise/ + processes/)") def read_manifest(path): with open(path, encoding="utf-8") as fh: return yaml.safe_load(fh) def short(s, n=160): if not s: return "" return s.split("\n")[0][:n] LEVEL_TITLES = { 0: "L0 — Enterprise process collection index", 1: "L1 — Domain process collections", 2: "L2 — End-to-end business processes", 3: "L3 — Composed subprocesses / variants", 4: "L4 — Atomic executable processes", } def collect(ws): pkgs = {0: [], 1: [], 2: [], 3: [], 4: []} ent_path = os.path.join(ws, "enterprise", "enterprise.yaml") if os.path.isfile(ent_path): m = read_manifest(ent_path) # L0 enterprise-index nests id/name/description under `enterprise:` ent = m.get("enterprise") or m pkgs[0].append({ "id": ent.get("id", "?"), "name": ent.get("name", ""), "desc": short(ent.get("description", "")), "path": "enterprise/enterprise.yaml", "includes": [p.get("ref", "") for p in (m.get("packages") or [])], "cornerstones": {}, }) for pat in ("domains/*/uapf.yaml", "processes/*/uapf.yaml"): for p in sorted(glob.glob(os.path.join(ws, pat))): m = read_manifest(p) lvl = m.get("level") if lvl not in pkgs: continue pkgs[lvl].append({ "id": m.get("id", "?"), "name": m.get("name", ""), "desc": short(m.get("description", "")), "path": os.path.relpath(os.path.dirname(p), ws) + "/", "includes": m.get("includes") or [], "cornerstones": m.get("cornerstones") or {}, }) return pkgs def render(pkgs): L = [] L.append("# `vk-gramatvediba` workspace index") L.append("") L.append("This index lists every package in the workspace grouped by UAPF") L.append("level (L0–L4). Per UAPF specification `§01-concepts.md`, levels") L.append("are an aggregation and governance scope only; per") L.append("`§04-folder-structure.md` the SHOULD-recommended workspace layout") L.append("is `enterprise/`, `domains/`, `processes/` regardless of level.") L.append("This file is therefore a level-grouped *view* over a") L.append("spec-conformant on-disk layout, not an alternative layout.") L.append("") L.append("Regenerate with `python3 tools/build-index/build_index.py`.") L.append("") total = sum(len(v) for v in pkgs.values()) L.append(f"Total packages: **{total}** " "(L0: {0}, L1: {1}, L2: {2}, L3: {3}, L4: {4}).".format( *(len(pkgs[i]) for i in range(5)))) L.append("") for lvl in (0, 1, 2, 3, 4): L.append(f"## {LEVEL_TITLES[lvl]}") L.append("") items = pkgs[lvl] if not items: L.append("_(none)_") L.append("") continue if lvl == 0: L.append("| Package | Path | Description |") L.append("|---|---|---|") for e in items: desc = e["name"] or e["desc"] or "—" L.append(f"| `{e['id']}` | `{e['path']}` | {desc} |") elif lvl in (1, 2, 3): L.append("| Package | Path | Includes | Description |") L.append("|---|---|---|---|") for e in items: inc = ", ".join(f"`{i}`" for i in e["includes"]) or "_(none)_" desc = e["name"] or e["desc"] or "—" L.append(f"| `{e['id']}` | `{e['path']}` | {inc} | {desc} |") else: # L4 L.append("| Package | Path | Cornerstones | Description |") L.append("|---|---|---|---|") for e in items: cs = e["cornerstones"] or {} present = [k for k in ("bpmn", "dmn", "cmmn", "resources") if cs.get(k)] cs_str = ", ".join(present) or "_(none)_" desc = e["name"] or e["desc"] or "—" L.append(f"| `{e['id']}` | `{e['path']}` | {cs_str} | {desc} |") L.append("") return "\n".join(L) + "\n" def main(): ws = find_workspace_root(os.path.dirname(os.path.abspath(__file__))) pkgs = collect(ws) text = render(pkgs) out = os.path.join(ws, "INDEX.md") with open(out, "w", encoding="utf-8") as fh: fh.write(text) print(f"wrote {out} ({sum(len(v) for v in pkgs.values())} packages)") if __name__ == "__main__": main()