From 8cb3be70ffb53d95d8ff51b770270e84d42c7260 Mon Sep 17 00:00:00 2001 From: Piotr Ramotowski <8025853+PeterRamotowski@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:30:17 +0200 Subject: [PATCH 01/14] docs: add documentation build dependency --- requirements-docs.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 requirements-docs.txt diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..24275cf --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1 @@ +mkdocs-material>=9.6,<10 From 0b15f671d959570cd2068fa56c13daad6dc7ad6a Mon Sep 17 00:00:00 2001 From: Piotr Ramotowski <8025853+PeterRamotowski@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:30:29 +0200 Subject: [PATCH 02/14] docs: configure responsive MkDocs Material site --- mkdocs.yml | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 mkdocs.yml diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..a128f6a --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,105 @@ +site_name: Code Principles +site_description: A human-readable software engineering knowledge base and policy system for AI coding agents. +site_url: https://peterramotowski.github.io/code-principles/ +repo_url: https://github.com/PeterRamotowski/code-principles +repo_name: PeterRamotowski/code-principles +edit_uri: edit/main/ +docs_dir: .site-docs +site_dir: .site-build +use_directory_urls: true + +nav: + - Home: index.md + - Principles: + - Browse principles: principles/index.md + - Classification: principles/CLASSIFICATION.md + - Authoring guide: principles/AUTHORING-GUIDE.md + - Decision system: + - Core Skills: core/index.md + - Project profiles: profiles/index.md + - Modifiers: modifiers/index.md + - Conflict resolution: CONFLICT-RESOLUTION.md + - Orchestrator: orchestrator/SKILL.md + - Technologies: + - Languages: languages/index.md + - Frameworks: frameworks/index.md + - Examples: + - Evaluation scenarios: evaluations/index.md + - Reference: + - Repository overview: README.md + - Specification: SPECIFICATION.md + - Architecture: ARCHITECTURE.md + - Knowledge model: KNOWLEDGE-MODEL.md + - Terminology: TERMINOLOGY.md + - Self-containment: SELF-CONTAINMENT.md + - Contributing: CONTRIBUTING.md + - Roadmap: ROADMAP.md + - Changelog: CHANGELOG.md + +theme: + name: material + language: en + features: + - navigation.tabs + - navigation.tabs.sticky + - navigation.sections + - navigation.indexes + - navigation.top + - navigation.footer + - toc.follow + - search.suggest + - search.highlight + - search.share + - content.code.copy + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: indigo + accent: indigo + toggle: + icon: material/weather-night + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: indigo + accent: indigo + toggle: + icon: material/weather-sunny + name: Switch to light mode + icon: + repo: fontawesome/brands/github + +plugins: + - search + +markdown_extensions: + - admonition + - attr_list + - md_in_html + - tables + - toc: + permalink: true + - pymdownx.details + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + +extra_css: + - stylesheets/extra.css + +extra_javascript: + - javascripts/catalog-filter.js + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/PeterRamotowski/code-principles + name: Code Principles on GitHub + +validation: + omitted_files: warn + absolute_links: relative_to_docs + unrecognized_links: warn From b7380fc1be3f49b28eecddda90d9a114fec80d53 Mon Sep 17 00:00:00 2001 From: Piotr Ramotowski <8025853+PeterRamotowski@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:31:02 +0200 Subject: [PATCH 03/14] docs: generate human-readable site from repository knowledge --- tools/generate_site_docs.py | 269 ++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 tools/generate_site_docs.py diff --git a/tools/generate_site_docs.py b/tools/generate_site_docs.py new file mode 100644 index 0000000..e2d0466 --- /dev/null +++ b/tools/generate_site_docs.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python3 +"""Build a non-normative MkDocs source tree from the repository's canonical documentation.""" +from __future__ import annotations + +import html +import shutil +from pathlib import Path +from typing import Iterable + +import yaml + +ROOT = Path(__file__).resolve().parents[1] +OUT = ROOT / ".site-docs" +SITE_SOURCE = ROOT / "docs-site" + +ROOT_DOCS = [ + "README.md", + "SPECIFICATION.md", + "ARCHITECTURE.md", + "KNOWLEDGE-MODEL.md", + "SELF-CONTAINMENT.md", + "TERMINOLOGY.md", + "CONFLICT-RESOLUTION.md", + "CONTRIBUTING.md", + "ROADMAP.md", + "CHANGELOG.md", +] + +MARKDOWN_TREES = [ + "principles", + "core", + "profiles", + "modifiers", + "languages", + "frameworks", + "orchestrator", + "evaluations", +] + + +def load_yaml(path: Path): + return yaml.safe_load(path.read_text(encoding="utf-8")) + + +def copy_markdown_tree(relative: str) -> None: + source = ROOT / relative + if not source.is_dir(): + return + for path in source.rglob("*.md"): + destination = OUT / path.relative_to(ROOT) + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(path, destination) + + +def write(relative: str, content: str) -> None: + path = OUT / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content.rstrip() + "\n", encoding="utf-8") + + +def cards(items: Iterable[dict], href_prefix: str, document_name: str, details_key: str = "description") -> str: + rendered = [] + for item in sorted(items, key=lambda value: value.get("name", value["id"])): + item_id = item["id"] + name = html.escape(str(item.get("name", item_id))) + description = html.escape(str(item.get(details_key, ""))) + search_text = html.escape(f"{name} {description} {item_id}".lower(), quote=True) + rendered.append( + f'' + f'{name}' + f'{description}' + f'{html.escape(item_id)}' + "" + ) + return "\n".join(rendered) + + +def catalog_page(title: str, intro: str, body: str, count: int) -> str: + return f"""# {title} + +{intro} + +
+ + + {count} entries +
+ +
+{body} +
+ + +""" + + +def generate_principles() -> None: + registry = load_yaml(ROOT / "principles" / "registry.yaml") + categories = { + item["id"]: item["name"] + for item in load_yaml(ROOT / "principles" / "categories.yaml")["categories"] + } + principles = registry["principles"] + groups = [] + for category_id, category_name in categories.items(): + group = [item for item in principles if item["category"] == category_id] + if not group: + continue + rows = [] + for item in sorted(group, key=lambda value: value["name"]): + entry = load_yaml(ROOT / item["source"]) + name = html.escape(entry["name"]) + summary = html.escape(entry["summary"]) + classification = html.escape(entry["classification"]) + search_text = html.escape( + f"{entry['name']} {entry['id']} {entry['summary']} {entry['classification']} {category_name}".lower(), + quote=True, + ) + rows.append( + f'' + f'{name}' + f'{summary}' + f'{classification}' + "" + ) + groups.append( + f'

{html.escape(category_name)}

' + f'
{"".join(rows)}
' + ) + + page = f"""# Principles + +Browse the canonical engineering catalogue as a human-readable reference. The YAML entries remain the source of truth; these pages are generated views. + +
+ + + {len(principles)} entries +
+ +
+{''.join(groups)} +
+ + +""" + write("principles/index.md", page) + + +def load_components(directory: str, metadata_name: str) -> list[dict]: + items = [] + for metadata in sorted((ROOT / directory).glob(f"*/{metadata_name}")): + data = load_yaml(metadata) + if data: + items.append(data) + return items + + +def generate_component_hubs() -> None: + skills = load_components("core", "skill.yaml") + write( + "core/index.md", + catalog_page( + "Core Skills", + "Core Skills turn canonical principles into language-independent decision procedures. Choose a Skill to inspect its modes, conflicts, and review rules.", + cards(skills, "", "SKILL.md"), + len(skills), + ), + ) + + profiles = load_components("profiles", "profile.yaml") + write( + "profiles/index.md", + catalog_page( + "Project profiles", + "Profiles configure the dominant artifact and failure model before language or framework refinements are applied.", + cards(profiles, "", "PROFILE.md"), + len(profiles), + ), + ) + + modifiers = load_components("modifiers", "modifier.yaml") + write( + "modifiers/index.md", + catalog_page( + "Engineering modifiers", + "Modifiers strengthen policy when a verified cross-cutting constraint applies. They never replace the base project profile.", + cards(modifiers, "", "MODIFIER.md"), + len(modifiers), + ), + ) + + languages = load_components("languages", "adapter.yaml") + write( + "languages/index.md", + catalog_page( + "Language adapters", + "Language adapters refine generic engineering policy with runtime, type-system, packaging, concurrency, and resource semantics.", + cards(languages, "", "SKILL.md"), + len(languages), + ), + ) + + frameworks = load_components("frameworks", "adapter.yaml") + write( + "frameworks/index.md", + catalog_page( + "Framework adapters", + "Framework adapters refine project policy with lifecycle, boundary, state, extension, and convention decisions.", + cards(frameworks, "", "SKILL.md"), + len(frameworks), + ), + ) + + +def generate_evaluations() -> None: + scenario_dir = ROOT / "evaluations" / "scenarios" + scenarios = [] + for path in sorted(scenario_dir.glob("*.yaml")): + data = load_yaml(path) or {} + 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)) + + 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) + rows.append( + f'' + f'{html.escape(str(title))}' + f'{description}' + f'{html.escape(str(scenario_id))}' + "" + ) + + 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.", + "\n".join(rows), + len(rows), + ), + ) + + +def main() -> None: + if OUT.exists(): + shutil.rmtree(OUT) + shutil.copytree(SITE_SOURCE, OUT) + + for filename in ROOT_DOCS: + shutil.copy2(ROOT / filename, OUT / filename) + for relative in MARKDOWN_TREES: + copy_markdown_tree(relative) + + generate_principles() + generate_component_hubs() + generate_evaluations() + print(f"Generated MkDocs source at {OUT.relative_to(ROOT)}") + + +if __name__ == "__main__": + main() From 44f625c1e609cb2d6d3805691614d02ac82eda74 Mon Sep 17 00:00:00 2001 From: Piotr Ramotowski <8025853+PeterRamotowski@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:31:22 +0200 Subject: [PATCH 04/14] docs: add documentation homepage --- docs-site/index.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 docs-site/index.md diff --git a/docs-site/index.md b/docs-site/index.md new file mode 100644 index 0000000..66f76d0 --- /dev/null +++ b/docs-site/index.md @@ -0,0 +1,85 @@ +# Code Principles + +
+ +## Engineering guidance that adapts to the project + +A self-contained, language-agnostic knowledge base and policy system for AI coding agents — presented here as a browsable engineering handbook. + +[Browse the 72 principles](principles/index.md){ .md-button .md-button--primary } +[Explore the decision system](core/index.md){ .md-button } + +
+ +
+ +
+ +### Principles + +Canonical engineering ideas with explicit applicability, rejected interpretations, trade-offs, conflicts, examples, and AI guidance. + +[Browse principles →](principles/index.md) + +
+ +
+ +### Core Skills + +Decision procedures that combine principles into practical modes for clarity, abstraction, testing, compatibility, reliability, performance, and more. + +[Explore Core Skills →](core/index.md) + +
+ +
+ +### Project profiles + +Start from what you are building: application, library, service, worker, pipeline, plugin, prototype, CLI, real-time system, infrastructure tool, and more. + +[Choose a project profile →](profiles/index.md) + +
+ +
+ +### Technology refinements + +See how generic policy is refined for JavaScript, TypeScript, Python, PHP, Go, C++, React, Next.js, Vue, Nuxt, Angular, Symfony, and Drupal. + +[Browse languages →](languages/index.md) · [Browse frameworks →](frameworks/index.md) + +
+ +
+ +## How the policy model works + +
+
1Project evidencetask, repository, explicit context
+ +
2Project profiledominant artifact and failure model
+ +
3Modifiersverified cross-cutting constraints
+ +
4Core Skillscontext-sensitive decision modes
+ +
5Technology adapterslanguage and framework semantics
+ +
6Resolved policyexplainable engineering decisions
+
+ +!!! info "The website is a generated view" + Canonical YAML and normative Markdown in the repository remain authoritative. This site is rebuilt from those sources and is never an independent source of policy. + +## Learn by example + +The repository includes executable evaluation scenarios that test profile selection, modifiers, language and framework refinements, conflict decisions, and forbidden overengineering. They are useful both as tests and as worked examples of the policy model. + +[Browse evaluation scenarios](evaluations/index.md){ .md-button } + +## Reference + +For the complete system contracts and architecture, continue with the [Specification](SPECIFICATION.md), [Architecture](ARCHITECTURE.md), [Knowledge Model](KNOWLEDGE-MODEL.md), and [Conflict Resolution](CONFLICT-RESOLUTION.md). From c42d3dd9f9d5b262d8fc2c2db47d8d3b0fa86282 Mon Sep 17 00:00:00 2001 From: Piotr Ramotowski <8025853+PeterRamotowski@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:31:48 +0200 Subject: [PATCH 05/14] docs: add responsive light and dark site styling --- docs-site/stylesheets/extra.css | 259 ++++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 docs-site/stylesheets/extra.css diff --git a/docs-site/stylesheets/extra.css b/docs-site/stylesheets/extra.css new file mode 100644 index 0000000..e71972c --- /dev/null +++ b/docs-site/stylesheets/extra.css @@ -0,0 +1,259 @@ +:root { + --cp-radius: 0.75rem; + --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%); +} + +.md-grid { + max-width: 90rem; +} + +.md-typeset h1 { + font-weight: 700; + letter-spacing: -0.02em; +} + +.md-typeset h2 { + letter-spacing: -0.015em; +} + +.hero { + padding: 2.2rem; + margin: 0 0 2rem; + border: 1px solid var(--cp-border); + border-radius: 1rem; + background: + radial-gradient(circle at 90% 10%, color-mix(in srgb, var(--md-primary-fg-color) 18%, transparent), transparent 35%), + var(--cp-surface); +} + +.hero h2 { + margin-top: 0; + max-width: 22ch; + font-size: clamp(1.8rem, 4vw, 3rem); + line-height: 1.05; +} + +.hero > p { + max-width: 62ch; + font-size: 1.05rem; +} + +.home-grid, +.catalog-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; +} + +.home-card, +.catalog-card { + border: 1px solid var(--cp-border); + border-radius: var(--cp-radius); + background: var(--cp-surface); +} + +.home-card { + padding: 1.2rem 1.25rem; +} + +.home-card h3 { + margin-top: 0; +} + +.catalog-card { + display: flex; + flex-direction: column; + gap: 0.4rem; + padding: 1rem; + color: var(--md-default-fg-color) !important; + text-decoration: none; + transition: background-color 120ms ease, border-color 120ms ease, transform 120ms ease; +} + +.catalog-card:hover, +.catalog-card:focus-visible { + background: var(--cp-surface-hover); + border-color: color-mix(in srgb, var(--md-primary-fg-color) 45%, transparent); + transform: translateY(-1px); +} + +.catalog-card__title { + color: var(--md-primary-fg-color); + font-weight: 700; + font-size: 1rem; +} + +[data-md-color-scheme="slate"] .catalog-card__title { + color: var(--md-accent-fg-color); +} + +.catalog-card__description { + flex: 1; + font-size: 0.86rem; + line-height: 1.45; +} + +.catalog-card__id, +.catalog-card__meta { + font-family: var(--md-code-font-family); + font-size: 0.68rem; + opacity: 0.72; +} + +.catalog-group { + margin: 2rem 0; +} + +.catalog-toolbar { + position: sticky; + top: 3.6rem; + z-index: 2; + display: grid; + grid-template-columns: auto minmax(12rem, 1fr) auto; + gap: 0.75rem; + align-items: center; + padding: 0.75rem; + margin: 1.25rem 0; + border: 1px solid var(--cp-border); + border-radius: var(--cp-radius); + background: color-mix(in srgb, var(--md-default-bg-color) 94%, transparent); + backdrop-filter: blur(12px); +} + +.catalog-toolbar label { + font-weight: 600; +} + +.catalog-toolbar input { + width: 100%; + min-width: 0; + padding: 0.55rem 0.7rem; + border: 1px solid var(--cp-border); + border-radius: 0.45rem; + background: var(--md-default-bg-color); + color: var(--md-default-fg-color); + font: inherit; +} + +.catalog-toolbar input:focus { + outline: 2px solid var(--md-accent-fg-color); + outline-offset: 1px; +} + +.catalog-toolbar__count { + white-space: nowrap; + font-size: 0.78rem; + opacity: 0.75; +} + +.catalog-empty { + padding: 1rem; + border: 1px dashed var(--cp-border); + border-radius: var(--cp-radius); + text-align: center; +} + +.resolution-flow { + display: grid; + grid-template-columns: repeat(11, auto); + gap: 0.45rem; + align-items: stretch; + overflow-x: auto; + padding: 0.25rem 0 0.75rem; + scrollbar-width: thin; +} + +.resolution-flow > div:not(.resolution-flow__arrow) { + display: grid; + min-width: 9.5rem; + padding: 0.85rem; + border: 1px solid var(--cp-border); + border-radius: var(--cp-radius); + background: var(--cp-surface); +} + +.resolution-flow strong { + color: var(--md-primary-fg-color); + font-size: 0.72rem; +} + +.resolution-flow span { + font-weight: 700; +} + +.resolution-flow small { + margin-top: 0.35rem; + opacity: 0.72; + line-height: 1.35; +} + +.resolution-flow__arrow { + align-self: center; + opacity: 0.55; +} + +.md-typeset table:not([class]) { + font-size: 0.78rem; +} + +@media screen and (max-width: 76.234375em) { + .catalog-toolbar { + top: 2.8rem; + } +} + +@media screen and (max-width: 60em) { + .home-grid, + .catalog-grid { + grid-template-columns: 1fr; + } + + .hero { + padding: 1.5rem; + } + + .resolution-flow { + grid-template-columns: 1fr; + overflow: visible; + } + + .resolution-flow__arrow { + transform: rotate(90deg); + justify-self: center; + height: 1rem; + } + + .resolution-flow > div:not(.resolution-flow__arrow) { + min-width: 0; + } +} + +@media screen and (max-width: 44.984375em) { + .catalog-toolbar { + position: static; + grid-template-columns: 1fr auto; + } + + .catalog-toolbar label { + grid-column: 1 / -1; + } + + .catalog-toolbar input { + min-height: 2.75rem; + } + + .md-typeset .md-button { + display: block; + width: 100%; + margin: 0.45rem 0; + text-align: center; + } +} + +@media (prefers-reduced-motion: reduce) { + .catalog-card { + transition: none; + } +} From 0f0c099eb33ceacadd44a973624f060e4316b549 Mon Sep 17 00:00:00 2001 From: Piotr Ramotowski <8025853+PeterRamotowski@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:31:57 +0200 Subject: [PATCH 06/14] docs: add mobile-friendly catalog filtering --- docs-site/javascripts/catalog-filter.js | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 docs-site/javascripts/catalog-filter.js diff --git a/docs-site/javascripts/catalog-filter.js b/docs-site/javascripts/catalog-filter.js new file mode 100644 index 0000000..74f0ea7 --- /dev/null +++ b/docs-site/javascripts/catalog-filter.js @@ -0,0 +1,45 @@ +(() => { + const normalize = (value) => value.trim().toLocaleLowerCase(); + + const initCatalogFilters = () => { + document.querySelectorAll("[data-catalog-filter]").forEach((input) => { + if (input.dataset.catalogFilterReady === "true") return; + input.dataset.catalogFilterReady = "true"; + + const root = input.closest(".md-content") || document; + const items = Array.from(root.querySelectorAll("[data-catalog-item]")); + const count = root.querySelector("[data-catalog-count]"); + const empty = root.querySelector("[data-catalog-empty]"); + + const apply = () => { + const query = normalize(input.value); + let visible = 0; + + items.forEach((item) => { + const matches = !query || normalize(item.dataset.search || item.textContent || "").includes(query); + item.hidden = !matches; + if (matches) visible += 1; + }); + + root.querySelectorAll(".catalog-group").forEach((group) => { + const groupVisible = Array.from(group.querySelectorAll("[data-catalog-item]")).some((item) => !item.hidden); + group.hidden = !groupVisible; + }); + + if (count) count.textContent = `${visible} ${visible === 1 ? "entry" : "entries"}`; + if (empty) empty.hidden = visible !== 0; + }; + + input.addEventListener("input", apply); + apply(); + }); + }; + + if (typeof document$ !== "undefined") { + document$.subscribe(initCatalogFilters); + } else if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initCatalogFilters); + } else { + initCatalogFilters(); + } +})(); From a714218f0b77c1f3cf80c1845a1e72b036e23c8a Mon Sep 17 00:00:00 2001 From: Piotr Ramotowski <8025853+PeterRamotowski@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:32:05 +0200 Subject: [PATCH 07/14] docs: deploy generated site to GitHub Pages --- .github/workflows/pages.yml | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/pages.yml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..8c1d53f --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,68 @@ +name: Deploy documentation + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: github-pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + cache: pip + cache-dependency-path: | + requirements-dev.txt + requirements-docs.txt + + - name: Install dependencies + run: python -m pip install -r requirements-dev.txt -r requirements-docs.txt + + - name: Validate repository + run: make validate + + - name: Lint normative keywords + run: make validate-normative + + - name: Generate documentation source + run: python tools/generate_site_docs.py + + - name: Build documentation + run: mkdocs build + + - name: Configure GitHub Pages + uses: actions/configure-pages@v5 + + - name: Upload GitHub Pages artifact + uses: actions/upload-pages-artifact@v4 + with: + path: .site-build + + deploy: + if: github.ref == 'refs/heads/main' + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From 51a4bf8dc5a27b3733aa6441a78bf9ab338a9650 Mon Sep 17 00:00:00 2001 From: Piotr Ramotowski <8025853+PeterRamotowski@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:32:19 +0200 Subject: [PATCH 08/14] docs: ignore generated documentation artifacts --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5c1e2a3..4be1350 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ venv/ *.tmp *.swp *.zip +.site-docs/ +.site-build/ From 3d3ecbec951b2956332d71547ef7680c40d92cd3 Mon Sep 17 00:00:00 2001 From: Piotr Ramotowski <8025853+PeterRamotowski@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:32:32 +0200 Subject: [PATCH 09/14] docs: add local documentation build targets --- Makefile | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bcb6611..e704804 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,18 @@ -.PHONY: evaluate generate test validate validate-normative manifest package resolve +.PHONY: docs docs-serve evaluate generate test validate validate-normative manifest package resolve PYTHON ?= python3 generate: $(PYTHON) tools/generate_compendium.py +docs: + $(PYTHON) tools/generate_site_docs.py + mkdocs build + +docs-serve: + $(PYTHON) tools/generate_site_docs.py + mkdocs serve + test: $(PYTHON) -m unittest discover -s tests -p 'test_*.py' From 6bc53ff38beb4d8ba086a6216fa4bd7b2ea57805 Mon Sep 17 00:00:00 2001 From: Piotr Ramotowski <8025853+PeterRamotowski@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:33:06 +0200 Subject: [PATCH 10/14] docs: fix generated catalog links for Pages paths --- tools/generate_site_docs.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tools/generate_site_docs.py b/tools/generate_site_docs.py index e2d0466..2699ce4 100644 --- a/tools/generate_site_docs.py +++ b/tools/generate_site_docs.py @@ -58,7 +58,7 @@ def write(relative: str, content: str) -> None: path.write_text(content.rstrip() + "\n", encoding="utf-8") -def cards(items: Iterable[dict], href_prefix: str, document_name: str, details_key: str = "description") -> str: +def cards(items: Iterable[dict], details_key: str = "description") -> str: rendered = [] for item in sorted(items, key=lambda value: value.get("name", value["id"])): item_id = item["id"] @@ -66,8 +66,7 @@ def cards(items: Iterable[dict], href_prefix: str, document_name: str, details_k description = html.escape(str(item.get(details_key, ""))) search_text = html.escape(f"{name} {description} {item_id}".lower(), quote=True) rendered.append( - f'' + f'' f'{name}' f'{description}' f'{html.escape(item_id)}' @@ -119,7 +118,7 @@ def generate_principles() -> None: ) rows.append( f'' + f'href="compendium/{entry["id"]}/">' f'{name}' f'{summary}' f'{classification}' @@ -165,7 +164,7 @@ def generate_component_hubs() -> None: catalog_page( "Core Skills", "Core Skills turn canonical principles into language-independent decision procedures. Choose a Skill to inspect its modes, conflicts, and review rules.", - cards(skills, "", "SKILL.md"), + cards(skills), len(skills), ), ) @@ -176,7 +175,7 @@ def generate_component_hubs() -> None: catalog_page( "Project profiles", "Profiles configure the dominant artifact and failure model before language or framework refinements are applied.", - cards(profiles, "", "PROFILE.md"), + cards(profiles), len(profiles), ), ) @@ -187,7 +186,7 @@ def generate_component_hubs() -> None: catalog_page( "Engineering modifiers", "Modifiers strengthen policy when a verified cross-cutting constraint applies. They never replace the base project profile.", - cards(modifiers, "", "MODIFIER.md"), + cards(modifiers), len(modifiers), ), ) @@ -198,7 +197,7 @@ def generate_component_hubs() -> None: catalog_page( "Language adapters", "Language adapters refine generic engineering policy with runtime, type-system, packaging, concurrency, and resource semantics.", - cards(languages, "", "SKILL.md"), + cards(languages), len(languages), ), ) @@ -209,7 +208,7 @@ def generate_component_hubs() -> None: catalog_page( "Framework adapters", "Framework adapters refine project policy with lifecycle, boundary, state, extension, and convention decisions.", - cards(frameworks, "", "SKILL.md"), + cards(frameworks), len(frameworks), ), ) From 8545b2c3632128f88134740869d3c5336c78d47e Mon Sep 17 00:00:00 2001 From: Piotr Ramotowski <8025853+PeterRamotowski@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:33:21 +0200 Subject: [PATCH 11/14] docs: verify site builds on pull requests --- .github/workflows/pages.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 8c1d53f..e857ae8 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,6 +1,7 @@ name: Deploy documentation on: + pull_request: push: branches: - main @@ -10,8 +11,8 @@ permissions: contents: read concurrency: - group: github-pages - cancel-in-progress: false + group: github-pages-${{ github.ref }} + cancel-in-progress: true jobs: build: @@ -45,15 +46,17 @@ jobs: run: mkdocs build - name: Configure GitHub Pages + if: github.event_name != 'pull_request' uses: actions/configure-pages@v5 - name: Upload GitHub Pages artifact + if: github.event_name != 'pull_request' uses: actions/upload-pages-artifact@v4 with: path: .site-build deploy: - if: github.ref == 'refs/heads/main' + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' needs: build runs-on: ubuntu-latest permissions: From 6ee9287f2352d16c4b3793c4ed16fdcaac7ea400 Mon Sep 17 00:00:00 2001 From: Piotr Ramotowski <8025853+PeterRamotowski@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:34:55 +0200 Subject: [PATCH 12/14] chore: temporarily refresh manifest on docs branch --- .github/workflows/refresh-docs-manifest.yml | 32 +++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/refresh-docs-manifest.yml diff --git a/.github/workflows/refresh-docs-manifest.yml b/.github/workflows/refresh-docs-manifest.yml new file mode 100644 index 0000000..22a7527 --- /dev/null +++ b/.github/workflows/refresh-docs-manifest.yml @@ -0,0 +1,32 @@ +name: Refresh docs branch manifest + +on: + push: + branches: + - docs/github-pages-site + +permissions: + contents: write + +jobs: + refresh: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + ref: docs/github-pages-site + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + - run: python tools/update_manifest.py + - name: Commit refreshed manifest + run: | + if git diff --quiet -- MANIFEST.sha256; then + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add MANIFEST.sha256 + git commit -m "chore: refresh distribution manifest" + git push origin HEAD:docs/github-pages-site From 090df0bbde96aa46f88cc23b088433a441ff8f48 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 18:35:04 +0000 Subject: [PATCH 13/14] chore: refresh distribution manifest --- MANIFEST.sha256 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MANIFEST.sha256 b/MANIFEST.sha256 index 003036e..50431b7 100644 --- a/MANIFEST.sha256 +++ b/MANIFEST.sha256 @@ -1,4 +1,4 @@ -3afa430e47b4b30640c8d21de565b90582f350249ab646c94ced96bf57e1a316 .gitignore +b401b8ac1cc33a55f36f5faa2d8c4372cec56b980ad00c009dbca47eab0b41b8 .gitignore c81ae9ba64df3b1c5d9dcceed7e4f79d0a7ae4776380481b913149a08d99e309 ARCHITECTURE.md eb4ef0a34a5f941361972a7df4acc4a53007ab99a8773b95dd5a56c78091bea1 CHANGELOG.md a72fabcd2dd27e96394adbd633b71d9e403766336695ddd7e21257ae8c69dc01 CONFLICT-RESOLUTION.md @@ -6,7 +6,7 @@ d80cc0c8532a0c03869d10a6d09cebda54464af5337801af5fa528c4b2cdaba1 CONTRIBUTING.m 83bd470cb3db0001eb905adeea0b55da1e121be5f1c5e0093a2c7b00bd69c971 DOCUMENTATION.md b70da374c6f05399968e77288de5db7f267e3c6b117d6c06cbdfc34168a43157 KNOWLEDGE-MODEL.md 60f7cde752f96e821832576ed9ca9ddbd22efc0c3e6541a21e6a0605cf9ddb76 LICENSE -eeebc06d9dcc1ec848136f44aad4b3105ab0987af8056826a6fbec3e97f1152c Makefile +b8047c1b1afe54e89a8086f04a43c44c36de72c472cf154e1e98527526c1bbfa Makefile f83aba41dfb4e214e85536c8f055de4dd4de6c1593cdcef78cc0eb43d07f7cdd README.md 4567c3af0536e424f8dd62cae5adeae467f4b1f0b9723659609cf0791c126a06 ROADMAP.md 75eef1e94056557516131f9d79aea0c8d9b92f102da74e44c08ec99496b69d2c SELF-CONTAINMENT.md @@ -400,6 +400,7 @@ b9687f0785ccd4f186cd27ab4e58eaddf37abc43da3d3eb6f905a3d1d860db33 schemas/resolv d55c4d1eb6edc1ecccbee5e6db1e0bea371919ad956cbc861caa30f32228e9a3 tools/distribution.py f12e52837e8b71dbc4436a2cf0a3a234f2c15823909b6c787d383451e454dcad tools/evaluate.py f90fefe7940fb9438cfec6e163724407da5166e9826f54e57c8d67aaba01e7ba tools/generate_compendium.py +889d27abdf88b2695b57e864ad17de94376f2facfd03eb46ef27b5975fd52488 tools/generate_site_docs.py 504558dd715c337641e86c194869d9d80f120183e9f0210043bb8385e89747ef tools/orchestrate.py 6b9ba027c38c88d567467bbbd52af1ec350cfa75a8825e798b0c9e6e2d771b89 tools/package.py 0acadfba1a6044eabd532f285d7e673fba395537075c6468dc1ebb66493da10d tools/update_manifest.py From 0c5df7aa16d50fcb9e9965da50f1a49ab0d2b233 Mon Sep 17 00:00:00 2001 From: Piotr Ramotowski <8025853+PeterRamotowski@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:35:18 +0200 Subject: [PATCH 14/14] chore: remove temporary manifest refresh workflow --- .github/workflows/refresh-docs-manifest.yml | 32 --------------------- 1 file changed, 32 deletions(-) delete mode 100644 .github/workflows/refresh-docs-manifest.yml diff --git a/.github/workflows/refresh-docs-manifest.yml b/.github/workflows/refresh-docs-manifest.yml deleted file mode 100644 index 22a7527..0000000 --- a/.github/workflows/refresh-docs-manifest.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Refresh docs branch manifest - -on: - push: - branches: - - docs/github-pages-site - -permissions: - contents: write - -jobs: - refresh: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - ref: docs/github-pages-site - - uses: actions/setup-python@v6 - with: - python-version: "3.13" - - run: python tools/update_manifest.py - - name: Commit refreshed manifest - run: | - if git diff --quiet -- MANIFEST.sha256; then - exit 0 - fi - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add MANIFEST.sha256 - git commit -m "chore: refresh distribution manifest" - git push origin HEAD:docs/github-pages-site