diff --git a/MANIFEST.sha256 b/MANIFEST.sha256 index 50431b7..4a490ea 100644 --- a/MANIFEST.sha256 +++ b/MANIFEST.sha256 @@ -400,7 +400,7 @@ b9687f0785ccd4f186cd27ab4e58eaddf37abc43da3d3eb6f905a3d1d860db33 schemas/resolv d55c4d1eb6edc1ecccbee5e6db1e0bea371919ad956cbc861caa30f32228e9a3 tools/distribution.py f12e52837e8b71dbc4436a2cf0a3a234f2c15823909b6c787d383451e454dcad tools/evaluate.py f90fefe7940fb9438cfec6e163724407da5166e9826f54e57c8d67aaba01e7ba tools/generate_compendium.py -889d27abdf88b2695b57e864ad17de94376f2facfd03eb46ef27b5975fd52488 tools/generate_site_docs.py +0c062fd50ccf31fdcbbd37538570cca4b85bd4026f6be6909454c7dc9f1c8753 tools/generate_site_docs.py 504558dd715c337641e86c194869d9d80f120183e9f0210043bb8385e89747ef tools/orchestrate.py 6b9ba027c38c88d567467bbbd52af1ec350cfa75a8825e798b0c9e6e2d771b89 tools/package.py 0acadfba1a6044eabd532f285d7e673fba395537075c6468dc1ebb66493da10d tools/update_manifest.py diff --git a/docs-site/stylesheets/extra.css b/docs-site/stylesheets/extra.css index e71972c..ef40c62 100644 --- a/docs-site/stylesheets/extra.css +++ b/docs-site/stylesheets/extra.css @@ -1,5 +1,8 @@ :root { --cp-radius: 0.75rem; +} + +:root > * { --cp-border: color-mix(in srgb, var(--md-default-fg-color) 16%, transparent); --cp-surface: color-mix(in srgb, var(--md-default-bg-color) 94%, var(--md-primary-fg-color) 6%); --cp-surface-hover: color-mix(in srgb, var(--md-default-bg-color) 86%, var(--md-primary-fg-color) 14%); diff --git a/tools/generate_site_docs.py b/tools/generate_site_docs.py index 2699ce4..cbb719b 100644 --- a/tools/generate_site_docs.py +++ b/tools/generate_site_docs.py @@ -37,6 +37,14 @@ "evaluations", ] +COMPONENT_INDEX_SOURCE = { + "core": "SKILL.md", + "profiles": "PROFILE.md", + "modifiers": "MODIFIER.md", + "languages": "SKILL.md", + "frameworks": "SKILL.md", +} + def load_yaml(path: Path): return yaml.safe_load(path.read_text(encoding="utf-8")) @@ -46,11 +54,18 @@ def copy_markdown_tree(relative: str) -> None: source = ROOT / relative if not source.is_dir(): return + + index_source = COMPONENT_INDEX_SOURCE.get(relative) for path in source.rglob("*.md"): destination = OUT / path.relative_to(ROOT) destination.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(path, destination) + # Component cards link to ///. Publish the canonical + # component document at that directory index as well as its source name. + if index_source and path.name == index_source and path.parent.parent == source: + shutil.copy2(path, destination.parent / "index.md") + def write(relative: str, content: str) -> None: path = OUT / relative @@ -214,6 +229,112 @@ def generate_component_hubs() -> None: ) +def inline_code(value) -> str: + text = str(value).replace("`", "\\`") + return f"`{text}`" + + +def render_value(value) -> str: + if value is None: + return "—" + if isinstance(value, list): + return ", ".join(inline_code(item) for item in value) if value else "—" + if isinstance(value, dict): + return "; ".join(f"{inline_code(key)}: {inline_code(item)}" for key, item in value.items()) or "—" + return inline_code(value) + + +def markdown_table(mapping: dict) -> str: + if not mapping: + return "_None._" + rows = ["| Item | Expected |", "| --- | --- |"] + for key, value in mapping.items(): + rendered = render_value(value).replace("|", "\\|") + rows.append(f"| {inline_code(key)} | {rendered} |") + return "\n".join(rows) + + +def yaml_block(value) -> str: + dumped = yaml.safe_dump(value, sort_keys=False, allow_unicode=True).rstrip() + return f"```yaml\n{dumped}\n```" + + +def scenario_page(path: Path, data: dict) -> str: + scenario_id = data.get("id", path.stem) + title = data.get("name") or data.get("title") or scenario_id.replace("-", " ").title() + description = data.get("description") or "Executable policy resolution scenario." + scenario_input = data.get("input") or {} + expected = data.get("expected") or {} + request = scenario_input.get("request") or "" + repository_signals = scenario_input.get("repository_signals") or {} + forbidden = data.get("forbidden") or [] + principles = data.get("principles_under_test") or [] + + repository_sections = [] + for filename, content in repository_signals.items(): + repository_sections.append(f"### `{filename}`\n\n```text\n{str(content).rstrip()}\n```") + repository_body = "\n\n".join(repository_sections) if repository_sections else "_No repository signals._" + + principle_links = "\n".join( + f"- [{principle}](../../principles/compendium/{principle}.md)" for principle in principles + ) or "_None._" + decision_list = "\n".join(f"- {inline_code(item)}" for item in expected.get("significant_decisions") or []) or "_None._" + forbidden_list = "\n".join(f"- {item}" for item in forbidden) or "_None._" + + return f"""# {title} + +{description} + +!!! info "Generated example" + This page is generated from `{path.relative_to(ROOT)}`. The scenario YAML remains the executable source of truth. + +## Request + +> {request} + +## Repository evidence + +{repository_body} + +## Expected resolution + +| Resolution layer | Expected | +| --- | --- | +| Profile | {render_value(expected.get('profile'))} | +| Modifiers | {render_value(expected.get('modifiers'))} | +| Language adapters | {render_value(expected.get('language_adapters'))} | +| Framework adapters | {render_value(expected.get('framework_adapters'))} | + +### Normalized context + +{yaml_block(expected.get('context') or {})} + +### Skill modes + +{markdown_table(expected.get('skill_modes') or {})} + +### Conflict decisions + +{markdown_table(expected.get('conflicts') or {})} + +### Significant decisions + +{decision_list} + +### Principles under test + +{principle_links} + +### Forbidden outcomes + +{forbidden_list} + +## Scenario source + +{yaml_block(data)} +""" + + def generate_evaluations() -> None: scenario_dir = ROOT / "evaluations" / "scenarios" scenarios = [] @@ -222,26 +343,26 @@ def generate_evaluations() -> None: scenario_id = data.get("id", path.stem) title = data.get("name") or data.get("title") or scenario_id.replace("-", " ").title() request = ((data.get("input") or {}).get("request") or "") - scenarios.append((scenario_id, title, request, path.name)) + description = data.get("description") or request or "Executable policy scenario" + scenarios.append((scenario_id, title, description, data, path)) rows = [] - for scenario_id, title, request, filename in scenarios: - description = html.escape(str(request)) if request else "Executable policy scenario" - search_text = html.escape(f"{scenario_id} {title} {request}".lower(), quote=True) + for scenario_id, title, description, data, path in scenarios: + search_text = html.escape(f"{scenario_id} {title} {description}".lower(), quote=True) rows.append( - f'' + f'' f'{html.escape(str(title))}' - f'{description}' + f'{html.escape(str(description))}' f'{html.escape(str(scenario_id))}' "" ) + write(f"evaluations/{scenario_id}/index.md", scenario_page(path, data)) write( "evaluations/index.md", catalog_page( "Evaluation scenarios", - "These executable scenarios exercise policy selection, conflict boundaries, technology refinements, and rejected overengineering. The scenario YAML remains executable test input in the repository.", + "Browse generated, human-readable views of the executable scenarios that exercise policy selection, conflict boundaries, technology refinements, and rejected overengineering.", "\n".join(rows), len(rows), ),