From ee2d6b0bcb65133bfb5f4ed65d24a2a2e6438941 Mon Sep 17 00:00:00 2001 From: owieschon Date: Mon, 13 Jul 2026 22:39:14 -0400 Subject: [PATCH] feat: add governed learning layer --- .clean-docs.yml | 49 ++++ .clean-docs/context/contributor.md | 11 +- .clean-docs/learning/tutorial-evidence.json | 35 +++ .clean-docs/learning/ultra-csm-hygiene.json | 63 ++++++ .clean-docs/reader-trial-rubric-v1.1.yml | 30 +++ .github/workflows/ci.yml | 34 +++ README.md | 7 + docs/RELEASES.md | 9 +- docs/SURFACE.md | 8 +- docs/assets/clean-docs-social.png | Bin 0 -> 87001 bytes docs/assets/clean-docs-social.svg | 73 ++++++ .../learn/deep-dive-the-deterministic-seam.md | 66 ++++++ docs/learn/index.md | 30 +++ docs/learn/postmortem-the-readme-that-lied.md | 78 +++++++ docs/learn/tutorial-catch-a-lying-doc.md | 118 ++++++++++ llms.txt | 10 +- pyproject.toml | 2 +- scripts/build_learning_evidence.py | 76 +++++++ scripts/build_release.py | 6 +- scripts/record_learning_tutorial.py | 151 +++++++++++++ scripts/render_social_preview.py | 193 ++++++++++++++++ scripts/run_acceptance.py | 8 + scripts/verify_reader_trial.py | 32 ++- tests/test_reader_trial.py | 39 +++- tests/test_release_artifact.py | 2 + tests/test_v11_acceptance_registry.py | 20 ++ tests/test_v11_learning.py | 213 ++++++++++++++++++ tests/v11-acceptance.yml | 15 ++ 28 files changed, 1354 insertions(+), 24 deletions(-) create mode 100644 .clean-docs/learning/tutorial-evidence.json create mode 100644 .clean-docs/learning/ultra-csm-hygiene.json create mode 100644 .clean-docs/reader-trial-rubric-v1.1.yml create mode 100644 docs/assets/clean-docs-social.png create mode 100644 docs/assets/clean-docs-social.svg create mode 100644 docs/learn/deep-dive-the-deterministic-seam.md create mode 100644 docs/learn/index.md create mode 100644 docs/learn/postmortem-the-readme-that-lied.md create mode 100644 docs/learn/tutorial-catch-a-lying-doc.md create mode 100644 scripts/build_learning_evidence.py create mode 100644 scripts/record_learning_tutorial.py create mode 100644 scripts/render_social_preview.py create mode 100644 tests/test_v11_acceptance_registry.py create mode 100644 tests/test_v11_learning.py create mode 100644 tests/v11-acceptance.yml diff --git a/.clean-docs.yml b/.clean-docs.yml index a279ddd..aa8e114 100644 --- a/.clean-docs.yml +++ b/.clean-docs.yml @@ -88,6 +88,51 @@ bindings: doc: docs/SECURITY_MODEL.md anchor: process-controls source: {path: src/clean_docs/isolation.py, symbol: run_isolated_process} + - id: tutorial-outcomes + type: region + doc: docs/learn/tutorial-catch-a-lying-doc.md + region: tutorial-outcomes + extractor: json + source: + path: .clean-docs/learning/tutorial-evidence.json + pointer: /steps + renderer: markdown-table + columns: [moment, command, exit, meaning] + - id: postmortem-measurements + type: region + doc: docs/learn/postmortem-the-readme-that-lied.md + region: postmortem-measurements + extractor: json + source: + path: .clean-docs/learning/ultra-csm-hygiene.json + pointer: /measurements + renderer: markdown-table + columns: [measure, before, after] + - id: postmortem-examples + type: region + doc: docs/learn/postmortem-the-readme-that-lied.md + region: postmortem-examples + extractor: json + source: + path: .clean-docs/learning/ultra-csm-hygiene.json + pointer: /examples + renderer: markdown-table + columns: [case, before, after] + - id: deterministic-seam-evidence + type: symbol + doc: docs/learn/deep-dive-the-deterministic-seam.md + anchor: evidence-authority + source: {path: src/clean_docs/engine.py, symbol: evaluate} + - id: deterministic-seam-phrasing + type: symbol + doc: docs/learn/deep-dive-the-deterministic-seam.md + anchor: phrasing-boundary + source: {path: src/clean_docs/phrasing.py, symbol: build_model_record} + - id: deterministic-seam-gate + type: symbol + doc: docs/learn/deep-dive-the-deterministic-seam.md + anchor: gate-authority + source: {path: src/clean_docs/outcomes.py, symbol: build_outcome_receipt} projections: llms_txt: output: llms.txt @@ -105,6 +150,10 @@ projections: - docs/SECURITY_MODEL.md - docs/SUPPORT.md - docs/SURFACE.md + - docs/learn/index.md + - docs/learn/tutorial-catch-a-lying-doc.md + - docs/learn/postmortem-the-readme-that-lied.md + - docs/learn/deep-dive-the-deterministic-seam.md - skill/SKILL.md bundles: - id: contributor diff --git a/.clean-docs/context/contributor.md b/.clean-docs/context/contributor.md index 74649b3..32c910d 100644 --- a/.clean-docs/context/contributor.md +++ b/.clean-docs/context/contributor.md @@ -1,13 +1,13 @@ # Context bundle: contributor - Source ref: `WORKTREE` -- Corpus sha256: `74f0d42c8d758125d6fbe9878054b8bd5abeb7b32477a26274e99f776128be01` +- Corpus sha256: `4de14e204a537a326c3881119c5d39f9efea575cf1c7355f9a573d1a8c85c279` - Content: exact canonical document bytes ## Canonical document: README.md - Source: [README.md](../../README.md) -- Content sha256: `4289b0409955e0ce025e67cdf718814f0c0d77971663fe779809a64aa8e39d05` +- Content sha256: `b3d947ea15bd363b589a256b91affbd9d954b8b78667aa34432e8d0478cc5358` # clean-docs @@ -17,6 +17,13 @@ clean-docs is a source-bound documentation engine and CLI for maintainers whose +[![CI](https://github.com/owieschon/clean-docs/actions/workflows/ci.yml/badge.svg)](https://github.com/owieschon/clean-docs/actions/workflows/ci.yml) +[![Release](https://img.shields.io/github/v/release/owieschon/clean-docs?display_name=tag&sort=semver)](https://github.com/owieschon/clean-docs/releases/latest) +[![License: MIT](https://img.shields.io/badge/license-MIT-25225f.svg)](LICENSE) + +**[Start here](docs/learn/index.md)** for the product map, runnable drift tutorial, real-repository +postmortem, and deterministic-boundary explanation. + ## Why clean-docs exists diff --git a/.clean-docs/learning/tutorial-evidence.json b/.clean-docs/learning/tutorial-evidence.json new file mode 100644 index 0000000..595dc37 --- /dev/null +++ b/.clean-docs/learning/tutorial-evidence.json @@ -0,0 +1,35 @@ +{ + "schema": "clean-docs.tutorial-evidence.v1", + "steps": [ + { + "command": "clean-docs check", + "exit": 0, + "meaning": "The bound page matches source.", + "moment": "Protected baseline" + }, + { + "command": "clean-docs check", + "exit": 1, + "meaning": "The status-actions binding is stale.", + "moment": "Source changed alone" + }, + { + "command": "clean-docs drive", + "exit": 0, + "meaning": "Only the bound region changes.", + "moment": "Declared region repaired" + }, + { + "command": "clean-docs project", + "exit": 0, + "meaning": "llms.txt receives the repaired page digest.", + "moment": "Projection refreshed" + }, + { + "command": "clean-docs verify", + "exit": 0, + "meaning": "Bindings and projections are current.", + "moment": "Repository verified" + } + ] +} diff --git a/.clean-docs/learning/ultra-csm-hygiene.json b/.clean-docs/learning/ultra-csm-hygiene.json new file mode 100644 index 0000000..f165565 --- /dev/null +++ b/.clean-docs/learning/ultra-csm-hygiene.json @@ -0,0 +1,63 @@ +{ + "examples": [ + { + "after": "relocated into the archive convention the repo already used, history intact", + "before": "an agent-to-agent handoff sat in the tracked doc tree a reader browses", + "case": "process exhaust off the reader surface" + }, + { + "after": "only the version `src/ultra_csm/agent1/slot_b.py:38` loads remains on the surface", + "before": "four versions of one prompt tracked side by side, each a near-duplicate of the last", + "case": "one canonical home for a versioned prompt" + }, + { + "after": "each passage is equally true, with the authoring history removed", + "before": "a worldbuilding reference tagged its content with the program that authored it", + "case": "provenance stripped from a reader-facing reference" + } + ], + "measurements": [ + { + "after": "73 (all justified in `NOTES.md`)", + "before": "280", + "measure": "Total findings" + }, + { + "after": "10 (cited evidence, per `docs/README.md`)", + "before": "91", + "measure": "surface (process artifact on reader surface)" + }, + { + "after": "33 (genuine long references)", + "before": "93", + "measure": "section-length" + }, + { + "after": "7 (shared concepts across separate docs)", + "before": "54", + "measure": "near-dup" + }, + { + "after": "21 (genuine long references)", + "before": "22", + "measure": "doc-length" + }, + { + "after": "1 (a run-ledger table cell)", + "before": "11", + "measure": "provenance" + }, + { + "after": "0", + "before": "7", + "measure": "audience" + }, + { + "after": "1", + "before": "2", + "measure": "restatement" + } + ], + "schema": "clean-docs.learning-evidence.v1", + "source": "docs/archive/v0/ultra-csm-before-after.md" +} diff --git a/.clean-docs/reader-trial-rubric-v1.1.yml b/.clean-docs/reader-trial-rubric-v1.1.yml new file mode 100644 index 0000000..28d69c1 --- /dev/null +++ b/.clean-docs/reader-trial-rubric-v1.1.yml @@ -0,0 +1,30 @@ +version: 1 +context: + - README.md + - docs/learn/index.md + - docs/learn/tutorial-catch-a-lying-doc.md + - docs/learn/postmortem-the-readme-that-lied.md + - docs/learn/deep-dive-the-deterministic-seam.md +profiles: + - id: anthropic-opus-4-8 + provider: anthropic + model: Opus 4.8 + - id: codex-gpt-5-6-sol-high + provider: codex + model: GPT 5.6 Sol High +tasks: + - id: route-orientation + instruction: Starting from the README first screen and learning index, choose the page that explains what clean-docs is and why source-bound documentation matters. + passes_when: The reader chooses the README overview for product orientation, then identifies the learning index thesis that documentation drift is a source-derivation problem. + - id: route-hands-on + instruction: Starting from the learning index, choose the page for learning the repair loop by doing it. + passes_when: The reader chooses the catch-a-lying-doc tutorial and states that it establishes a baseline, creates source drift, observes failure, repairs the declared region, refreshes projections, and verifies the result. + - id: route-mechanism + instruction: Starting from the learning index, choose the page that explains who owns facts, phrasing, and the gate result. + passes_when: The reader chooses the deterministic-seam deep dive and assigns evidence selection and pass-or-fail authority to deterministic code while limiting a model to phrasing supplied evidence. + - id: run-tutorial + instruction: Run the published tutorial against the candidate in a fresh repository and record each command exit and declared file change. + passes_when: The baseline passes, deliberate source drift fails first with the named binding, drive changes the declared documentation region, project refreshes llms.txt, and final check and verify exit zero. + - id: explain-authority-boundary + instruction: Explain why a model cannot select facts, widen scope, or decide whether clean-docs passes, using only the published learning pages. + passes_when: The answer preserves the evidence-to-phrasing-to-gate sequence, names deterministic code as the authority on inputs and outcomes, and does not grant the model merge-gate authority. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25a24d9..7f3ee9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,14 @@ jobs: - run: clean-docs --root . release --from v0.4.0 --to HEAD --format json - run: python scripts/record_demo.py --out /tmp/clean-docs-demo-evidence.json - run: cmp .clean-docs/demo/evidence.json /tmp/clean-docs-demo-evidence.json + - run: python scripts/build_learning_evidence.py --out /tmp/clean-docs-learning-evidence.json + - run: cmp .clean-docs/learning/ultra-csm-hygiene.json /tmp/clean-docs-learning-evidence.json + - run: >- + python scripts/record_learning_tutorial.py + --clean-docs "$(command -v clean-docs)" + --out /tmp/clean-docs-tutorial-evidence.json + - run: cmp .clean-docs/learning/tutorial-evidence.json /tmp/clean-docs-tutorial-evidence.json + - run: python scripts/render_social_preview.py --check - run: python scripts/trusted_self_check.py public-dogfood: @@ -224,6 +232,32 @@ jobs: path: clean-docs-v10-acceptance.json if-no-files-found: error + acceptance-v1-1: + needs: test + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: "3.12" + cache: pip + - run: python -m pip install ".[dev]" + - run: >- + python scripts/run_acceptance.py + --registry tests/v11-acceptance.yml + --out clean-docs-v11-acceptance.json + - if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: clean-docs-v1.1-acceptance-${{ matrix.os }}-${{ github.sha }} + path: clean-docs-v11-acceptance.json + if-no-files-found: error + performance-v1-0: needs: test runs-on: ${{ matrix.os }} diff --git a/README.md b/README.md index 902e36c..b7836ec 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,13 @@ clean-docs is a source-bound documentation engine and CLI for maintainers whose +[![CI](https://github.com/owieschon/clean-docs/actions/workflows/ci.yml/badge.svg)](https://github.com/owieschon/clean-docs/actions/workflows/ci.yml) +[![Release](https://img.shields.io/github/v/release/owieschon/clean-docs?display_name=tag&sort=semver)](https://github.com/owieschon/clean-docs/releases/latest) +[![License: MIT](https://img.shields.io/badge/license-MIT-25225f.svg)](LICENSE) + +**[Start here](docs/learn/index.md)** for the product map, runnable drift tutorial, real-repository +postmortem, and deterministic-boundary explanation. + ## Why clean-docs exists diff --git a/docs/RELEASES.md b/docs/RELEASES.md index 48493f1..86d61ec 100644 --- a/docs/RELEASES.md +++ b/docs/RELEASES.md @@ -33,4 +33,11 @@ The factual Markdown and JSON remain unchanged when narrative validation fails. Release extraction is static and snapshot-bound. First-party and configured discoverer plugins run once per ref in disposable copies. The active worktree, its installed dependencies, and narrative output cannot change the typed delta. -Release-candidate builds rehearse the published reader tasks in CI. A stable release additionally requires one content-addressed trial from each model profile declared in the rubric: Anthropic Opus 4.8, Anthropic Sonnet 5, Codex GPT 5.5 High, and Codex GPT 5.6 Sol High. Every profile runs in a fresh session with only the rubric's published context, completes every task, and binds its evidence by SHA-256. Profiles cannot share conversation history or prior trial output. The receipt also names the exact candidate commit and wheel digest. `scripts/verify_reader_trial.py` checks `.clean-docs/reader-trial.json`; `scripts/build_release.py` reconstructs the candidate wheel and permits only the version and reader receipts to differ in the stable release. It refuses absent, duplicate, substituted, incomplete, stale, tampered, or untried evidence. +Release-candidate builds rehearse the published reader tasks in CI. A stable release additionally requires one content-addressed trial from each profile in that release line's rubric: + +| Release line | Independent reader set | Evidence contract | +| --- | --- | --- | +| `1.0` | Anthropic Opus 4.8, Anthropic Sonnet 5, Codex GPT 5.5 High, and Codex GPT 5.6 Sol High | Product identification, installation, protection, repair, and limitation tasks | +| `1.1` | Anthropic Opus 4.8 and Codex GPT 5.6 Sol High | Learning-path routing, tutorial execution, and deterministic-authority tasks | + +Every profile runs in a fresh session with only the rubric's published context, completes every task, and binds its evidence by SHA-256. Profiles cannot share conversation history or prior trial output. The receipt also names the exact candidate commit and wheel digest. `scripts/verify_reader_trial.py` selects the versioned rubric and evidence namespace; `scripts/build_release.py` reconstructs the candidate wheel and permits only the version and matching reader receipts to differ in the stable release. It refuses absent, duplicate, substituted, incomplete, stale, tampered, or untried evidence. diff --git a/docs/SURFACE.md b/docs/SURFACE.md index c434821..4433ec5 100644 --- a/docs/SURFACE.md +++ b/docs/SURFACE.md @@ -9,12 +9,12 @@ The catalog binding catches additions, removals, and replacements across the det | surface | discovered | examples | | --- | ---: | --- | -| api-symbol | 175 | `AcceptanceCase`, `Assertion`, `AuditFinding`, and 172 more | +| api-symbol | 184 | `AcceptanceCase`, `Assertion`, `AuditFinding`, and 181 more | | cli-command | 19 | `audit`, `benchmark`, `check`, and 16 more | -| cli-option | 50 | `--accept-hygiene-baseline`, `--base`, `--binding`, and 47 more | +| cli-option | 55 | `--accept-hygiene-baseline`, `--base`, `--binding`, and 52 more | | package | 1 | `clean-docs` | | runtime-constraint | 1 | `Python >=3.10` | -| test-suite | 47 | `scripts/test_release_lifecycle.py`, `tests/test_audit.py`, `tests/test_changed_check.py`, and 44 more | +| test-suite | 49 | `scripts/test_release_lifecycle.py`, `tests/test_audit.py`, `tests/test_changed_check.py`, and 46 more | - + diff --git a/docs/assets/clean-docs-social.png b/docs/assets/clean-docs-social.png new file mode 100644 index 0000000000000000000000000000000000000000..5d8d369149d01db997c14eb32c9afc845597c21d GIT binary patch literal 87001 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 + clean-docs: docs that answer to code + A three-step source-bound documentation flow. Repository sources own facts, bindings connect facts to prose, and a deterministic check rejects stale changes. + + + + + + + + + + + + + + + + + + + + clean-docs + + SOURCE-BOUND DOCUMENTATION ENGINE + Docs that answer to code. + When source moves, stale prose fails loudly. + + + + + + 01 · SOURCE + Repository sources + Code owns the fact. + + static evidence + + + + + + 02 · BIND + Source bindings + Prose names its owner. + + region · claim · symbol + + + + + + + 03 · PROVE + Deterministic check + Drift fails before merge. + + repair · reject · project + + + + + THE CHECKABLE SPINE STAYS ATTACHED + diff --git a/docs/learn/deep-dive-the-deterministic-seam.md b/docs/learn/deep-dive-the-deterministic-seam.md new file mode 100644 index 0000000..f35015e --- /dev/null +++ b/docs/learn/deep-dive-the-deterministic-seam.md @@ -0,0 +1,66 @@ +# Deep dive: the deterministic seam + + +This explanation is for maintainers deciding where model judgment may enter a documentation pipeline without weakening its gate. It separates evidence selection, prose phrasing, repository writes, and pass or fail authority so you can inspect the boundary in both the architecture and the implementation. + + +The short version is memorable because it is mechanical: a model may hold the pen, but it never +gets the keys. Facts enter from declared evidence, writes stay bounded to planned documents, and a +deterministic result decides whether the repository is current. + +The [product system map](../../README.md#how-the-pieces-fit) shows the complete path and its text +equivalent. This page follows the three authority handoffs inside it: evidence enters, phrasing may +change its form, and deterministic checks retain the verdict. + +## Evidence authority + +[`evaluate`](../../src/clean_docs/engine.py) owns the source-to-binding comparison. It loads the +manifest, creates a repository snapshot, dispatches only the declared extractor, renders the typed +value, and compares the result with the marked document region. Claim and symbol bindings take the +same route through typed results. + +This is the first side of the seam: source configuration selects which facts exist. A model does not +discover an extra capability, widen the selected repository scope, or turn an unsupported surface +into an accepted claim. + +## Phrasing boundary + +[`build_model_record`](../../src/clean_docs/phrasing.py) receives inventory facts that already +exist. Its prompt exposes an allowlist of prose templates and asks for fact identifiers plus template +names, not open-ended product copy. The parser rejects unknown facts, duplicate facts, unsupported +templates, malformed responses, and more than five drafts. + +The returned sentence is therefore a presentation of supplied evidence. It is not evidence. If the +provider fails, the operation stops before a repository write. The [security model](../SECURITY_MODEL.md) +defines the surrounding process and host boundaries without duplicating them here. + +## Gate authority + +[`build_outcome_receipt`](../../src/clean_docs/outcomes.py) combines deterministic audit, inventory, +binding, projection, and changed-file results. Its `ok` value follows those counts. It does not ask a +provider whether the prose sounds supported. + +That distinction keeps failure actionable. A stale region names its binding and source. A missing +symbol names its locator. A model score could express judgment about clarity, but it cannot replace +the repeatable evidence that made the check fail. + +## Why the seam is useful + +Pure generation can produce fluent documentation whose factual scope is impossible to audit. Pure +templates can keep facts current while flattening every explanation into reference prose. The seam +keeps both tools in their honest roles: + +| Responsibility | Owner | Why | +| --- | --- | --- | +| Select facts and scope | Manifest plus extractor | The input must be reproducible at a repository ref | +| Phrase supplied facts | Deterministic renderer or bounded model path | Presentation may vary without changing authority | +| Write documentation | Explicit derive or drive path | Unrelated prose must remain untouched | +| Decide pass or fail | Deterministic checks | CI needs the same answer without credentials or judgment drift | + +Use a human or model to judge motivation, pedagogy, and whether the chosen facts are enough. Use the +gate to prove that declared facts, locators, and projections still match. The seam is not a claim +that all prose is true forever. It is the line that prevents the checkable spine from drifting +silently. + +Run [the tutorial](tutorial-catch-a-lying-doc.md) to exercise that boundary, or use the +[extension guide](../EXTENSIONS.md) when a source needs a new deterministic adapter. diff --git a/docs/learn/index.md b/docs/learn/index.md new file mode 100644 index 0000000..d2eb375 --- /dev/null +++ b/docs/learn/index.md @@ -0,0 +1,30 @@ +# Learn clean-docs + + +This learning path is for maintainers deciding whether source-bound documentation fits their repository. It replaces vague confidence in prose with a mental model, a runnable drift repair, and a clear authority boundary, so you can choose the shortest page for the question in front of you. + + +Documentation drift is a source-derivation problem before it is a discipline problem. Review can +catch a stale sentence, but it cannot make the relationship between that sentence and its defining +source repeatable. clean-docs makes that relationship explicit and checks it again after the code +moves. + +The useful question is not "who forgot the docs?" It is "which source should have made this claim +fail?" Follow the path that matches your current job: + +| If you need to... | Start with | You will leave with... | +| --- | --- | --- | +| Decide what the product is | [Product overview](../../README.md) | The category, fit, architecture, and installation route | +| Protect one fact yourself | [Catch a lying doc](tutorial-catch-a-lying-doc.md) | A working binding, a failed drift check, and a repaired page | +| See the problem at repository scale | [The README that lied](postmortem-the-readme-that-lied.md) | A concrete cleanup case and the limits of its evidence | +| Understand the model boundary | [The deterministic seam](deep-dive-the-deterministic-seam.md) | A precise account of who owns facts, prose, and gate results | + +The tutorial is the fastest proof. The postmortem explains why the proof matters beyond a toy +fixture. The deep dive names the mechanism after you have seen it move. + +## What this path does not replace + +These pages teach a sequence and a mental model. Use the existing [CLI reference](../CLI.md), +[support guide](../SUPPORT.md), and [security model](../SECURITY_MODEL.md) when you need exact current +commands, installation variants, or trust boundaries. The lessons link to those canonical homes +instead of growing stale copies. diff --git a/docs/learn/postmortem-the-readme-that-lied.md b/docs/learn/postmortem-the-readme-that-lied.md new file mode 100644 index 0000000..ed85fc9 --- /dev/null +++ b/docs/learn/postmortem-the-readme-that-lied.md @@ -0,0 +1,78 @@ +# Postmortem: the README that lied + + +This case study is for maintainers whose documentation corpus feels polished but has lost a trustworthy reader path. It shows how one real repository separated current guidance from process exhaust, measured the result, and exposed which improvements still required human judgment. + + +The repository did not have one spectacularly false page. It had a quieter failure: current +guidance, superseded prompts, handoff notes, and authoring history all looked equally official. +Nothing in the prose announced which layer a reader had entered. + +This is a recorded historical cleanup of `ultra-csm`, not a claim about that repository's current +state. The typed record is derived from the archived before-and-after account at +[`docs/archive/v0/ultra-csm-before-after.md`](../archive/v0/ultra-csm-before-after.md). clean-docs +renders the tables below from that record. + +## What the scan found + +The first pass reported 280 findings across structure, duplication, length, provenance, and +audience signals: + + +| measure | before | after | +| --- | --- | --- | +| Total findings | 280 | 73 (all justified in `NOTES.md`) | +| surface (process artifact on reader surface) | 91 | 10 (cited evidence, per `docs/README.md`) | +| section-length | 93 | 33 (genuine long references) | +| near-dup | 54 | 7 (shared concepts across separate docs) | +| doc-length | 22 | 21 (genuine long references) | +| provenance | 11 | 1 (a run-ledger table cell) | +| audience | 7 | 0 | +| restatement | 2 | 1 | + + +The count fell because the reader surface got smaller and the checker lost false positives. It did +not fall because thresholds were loosened. The remaining findings were reviewed and justified in +the source repository. + +## Three changes did most of the work + +The cleanup made each document answer a harder question than "is this Markdown useful somewhere?" +It asked whether the page belonged on the current reader surface: + + +| case | before | after | +| --- | --- | --- | +| process exhaust off the reader surface | an agent-to-agent handoff sat in the tracked doc tree a reader browses | relocated into the archive convention the repo already used, history intact | +| one canonical home for a versioned prompt | four versions of one prompt tracked side by side, each a near-duplicate of the last | only the version `src/ultra_csm/agent1/slot_b.py:38` loads remains on the surface | +| provenance stripped from a reader-facing reference | a worldbuilding reference tagged its content with the program that authored it | each passage is equally true, with the authoring history removed | + + +The first change moved handoffs and status notes into the repository's archive convention. The +second left one loaded prompt version on the current surface and preserved older versions as +history. The third removed authoring-program labels from reference prose that stayed true without +them. + +The result was not tidier wording. It was a corpus whose paths had different meanings. + +## What the numbers do not prove + +A lower hygiene count does not prove that every sentence is accurate, helpful, or well taught. +Several remaining findings were legitimate long references or shared concepts, and three reductions +came from correcting the linter itself. The evidence supports a narrower claim: structural noise +became measurable, most of it was removed from the current reader path, and the exceptions became +explicit. + +That boundary matters. Corpus checks can find likely process exhaust, duplicate language, and +misplaced provenance. A source binding is still required when a specific product claim must fail +after its defining code changes. [Catch a lying doc](tutorial-catch-a-lying-doc.md) demonstrates that +second mechanism on one fact. + +## The reusable decision + +For every page, ask: if this vanished from the current documentation surface, would a reader lose +current product truth or only authoring history? Keep the first in the reader path. Preserve the +second in version history or an archive whose name makes its status clear. + +Then bind the factual spine that remains. Structure makes the right page findable; source evidence +makes its checkable claims answerable. diff --git a/docs/learn/tutorial-catch-a-lying-doc.md b/docs/learn/tutorial-catch-a-lying-doc.md new file mode 100644 index 0000000..fdd33b9 --- /dev/null +++ b/docs/learn/tutorial-catch-a-lying-doc.md @@ -0,0 +1,118 @@ +# Catch a lying doc + + +This tutorial is for maintainers who want to see one source-bound fact fail and recover in a disposable repository. It turns an easy-to-miss command rename into a named check failure, a region-only repair, and a verified projection using an installed clean-docs release. + + +A stale README keeps a straight face. This exercise gives it a tripwire. + +You will build Moonbase Status, a tiny operator guide whose public action table comes from a Python literal. No repository code is imported or executed. The full binding surface and installation variants remain in the [manifest reference](../../README.md#manifest-reference) and [support guide](../SUPPORT.md). + +## Before you begin + +Install a stable clean-docs artifact in an isolated environment and confirm `clean-docs --version` works. The [local artifact procedure](../SUPPORT.md#install-upgrade-roll-back-and-uninstall) is the offline path used to test this tutorial. You also need Git and Python 3.10 or newer. + +## 1. Build the fixture + +Create an empty repository, its source-owned action, the page, and the binding manifest: + +```bash +mkdir moonbase-status +cd moonbase-status +git init +mkdir src +cat > src/actions.py <<'PY' +ACTIONS = [ + {"command": "report", "job": "Send the current habitat status"}, +] +PY +cat > README.md <<'MD' +# Moonbase Status + + +Use this guide when a habitat operator needs the current public actions. It keeps the action table tied to source so a renamed command cannot remain plausible in the guide. + + +## Operator actions + + + +MD +cat > .clean-docs.yml <<'YAML' +version: 1 +bindings: + - id: status-actions + type: region + doc: README.md + region: status-actions + extractor: python-literal + source: {path: src/actions.py, symbol: ACTIONS} + renderer: markdown-table + columns: [command, job] +projections: + llms_txt: + output: llms.txt + title: Moonbase Status documentation + summary: Source-bound operator documentation. + include: [README.md] +YAML +``` + +## 2. Protect the baseline + +Render the declared region, generate the projection, and verify the repository: + +```bash +clean-docs drive +clean-docs project +clean-docs check +clean-docs verify +git add .clean-docs.yml README.md llms.txt src/actions.py +git commit -m "Protect operator actions" +``` + +The check exits `0`. The receipt reports `"ok": true`, and the README now contains the `report` row derived from `ACTIONS`. + +## 3. Create honest drift + +Rename the source command without touching the README: + +```bash +python3 - <<'PY' +from pathlib import Path + +path = Path("src/actions.py") +path.write_text(path.read_text().replace('"report"', '"publish"')) +PY +clean-docs check +``` + +The final command exits `1` and names `status-actions`. That failure is the lesson: the old prose still reads well, but it no longer gets to pass as current. + +## 4. Repair and verify + +Repair the bound region, inspect its exact diff, refresh the projection, and run the final gate: + +```bash +clean-docs drive +git diff -- README.md +clean-docs project +clean-docs check +clean-docs verify +``` + +`drive` changes the table row from `report` to `publish` without rewriting the surrounding prose. `project` updates the page digest in `llms.txt`. The last two commands exit `0`. + +The release-tested fixture records the same state changes: + + +| moment | command | exit | meaning | +| --- | --- | --- | --- | +| Protected baseline | clean-docs check | 0 | The bound page matches source. | +| Source changed alone | clean-docs check | 1 | The status-actions binding is stale. | +| Declared region repaired | clean-docs drive | 0 | Only the bound region changes. | +| Projection refreshed | clean-docs project | 0 | llms.txt receives the repaired page digest. | +| Repository verified | clean-docs verify | 0 | Bindings and projections are current. | + + +You have now seen the full loop. Read [the deterministic seam](deep-dive-the-deterministic-seam.md) to understand why a model can help with wording without gaining authority over this result. diff --git a/llms.txt b/llms.txt index f1fb27a..3f2a687 100644 --- a/llms.txt +++ b/llms.txt @@ -5,14 +5,18 @@ ## Canonical documentation - [CLEAN_DOCS_SPEC.md](CLEAN_DOCS_SPEC.md): declared canonical context; sha256: b0df161645ff6f5356913658789990083c09a7d041f385541df97e9e259e99d1 -- [README.md](README.md): bindings: manifest-reference, product-overview, supported-bindings; sha256: 4289b0409955e0ce025e67cdf718814f0c0d77971663fe779809a64aa8e39d05 +- [README.md](README.md): bindings: manifest-reference, product-overview, supported-bindings; sha256: b3d947ea15bd363b589a256b91affbd9d954b8b78667aa34432e8d0478cc5358 - [SECURITY.md](SECURITY.md): declared canonical context; sha256: 9b73fcc18efa2ff39d64d8271a6c33b278de5dc625d7ac7c319587edf0c31621 - [STANDARD.md](STANDARD.md): declared canonical context; sha256: 2af7b40f7ac3a91b4d3877e98b1d33420f47b36e30202eb8b493f9c1ee4d6792 - [docs/CLI.md](docs/CLI.md): bindings: cli-reference; sha256: dae82eebf8e8712dce8b4c17e480f13912155078b3e34fb0159ff2f2c45510bc - [docs/EVALUATION.md](docs/EVALUATION.md): bindings: evaluation-scorers; sha256: efbcce90691ec594da793977b33ba62add79dd47c0ac9452443e826d28e1bb2f - [docs/EXTENSIONS.md](docs/EXTENSIONS.md): bindings: extension-guide, migration-guide; sha256: a1da5683da8247c11fb9bb2d6f04c3aa30961cabe7aab5ee5791b70244835d2d -- [docs/RELEASES.md](docs/RELEASES.md): bindings: release-guide; sha256: 0c9024977bca84b77106d85b91a756bf97124638ed65601cdc79cc90bf007161 +- [docs/RELEASES.md](docs/RELEASES.md): bindings: release-guide; sha256: 44a95749c55a3989777a9c2516a1b6d7d2da02954a6565f76019f8183b574583 - [docs/SECURITY_MODEL.md](docs/SECURITY_MODEL.md): bindings: security-model; sha256: 29d9f2582186f19d72823e163512fa12f6d7459f4da273ca3d8be56d5befd2bf - [docs/SUPPORT.md](docs/SUPPORT.md): bindings: support-guide; sha256: bc0055417eb4be09a5e8722aaa0bfa7454877b3909e38a72b7d03f9fbd7576ab -- [docs/SURFACE.md](docs/SURFACE.md): bindings: repository-surface; sha256: a4dfbe4cc61e7368aedc05c4c30422fc0194fe21e8e7c0477705495c16e688bf +- [docs/SURFACE.md](docs/SURFACE.md): bindings: repository-surface; sha256: 7cc29bb2ccdf1a92a0740a114181a97ffdca8b5eb4744c64000aa6119b415c26 +- [docs/learn/deep-dive-the-deterministic-seam.md](docs/learn/deep-dive-the-deterministic-seam.md): bindings: deterministic-seam-evidence, deterministic-seam-gate, deterministic-seam-phrasing; sha256: d4dcc2aaf7e6fa917d5474bb8e265eb60c0dca7332e099c5342c5bd79fb83371 +- [docs/learn/index.md](docs/learn/index.md): declared canonical context; sha256: 54ca471c870ac4b295b0bfe72d51561891084c7483a649f3586517204ac974fb +- [docs/learn/postmortem-the-readme-that-lied.md](docs/learn/postmortem-the-readme-that-lied.md): bindings: postmortem-examples, postmortem-measurements; sha256: 2616fe1ad837581461ccbd07bcfd7e0c6aa799f762e64f40ed34a637f3460cb2 +- [docs/learn/tutorial-catch-a-lying-doc.md](docs/learn/tutorial-catch-a-lying-doc.md): bindings: tutorial-outcomes; sha256: b5aeabb02e76c64e328d637b28ee7ca8458964ca402f3ce5b915b6d10ff0c51a - [skill/SKILL.md](skill/SKILL.md): declared canonical context; sha256: 77f0b257236a184c5b202e8327c27785f3ab4abe9c1ae9c3c082f8fa06428da9 diff --git a/pyproject.toml b/pyproject.toml index 622f848..a697126 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "clean-docs" -version = "1.0.0" +version = "1.1.0rc1" description = "Keep repository documentation bound to its sources." readme = "README.md" requires-python = ">=3.10" diff --git a/scripts/build_learning_evidence.py b/scripts/build_learning_evidence.py new file mode 100644 index 0000000..0595d7c --- /dev/null +++ b/scripts/build_learning_evidence.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Build the typed historical record used by the learning postmortem.""" + +from __future__ import annotations + +import argparse +import json +import re +from pathlib import Path + +from clean_docs.regions import atomic_write + + +ROOT = Path(__file__).resolve().parents[1] +SOURCE = ROOT / "docs/archive/v0/ultra-csm-before-after.md" +OUTPUT = ROOT / ".clean-docs/learning/ultra-csm-hygiene.json" + + +def _table_rows(text: str) -> list[dict[str, str]]: + match = re.search( + r"## The numbers\n\n\| Measure \| Before \| After \|\n" + r"\| --- \| --- \| --- \|\n(?P(?:\|.*\|\n)+)", + text, + ) + if match is None: + raise RuntimeError("archived measurement table not found") + rows: list[dict[str, str]] = [] + for line in match.group("body").splitlines(): + measure, before, after = (cell.strip() for cell in line.strip("|").split("|")) + rows.append({"measure": measure, "before": before, "after": after}) + return rows + + +def _examples(text: str) -> list[dict[str, str]]: + sections = re.findall( + r"## Example \d+: (?P[^\n]+)\n\n" + r"Before -- (?P[^\n]+):\n\n```text\n(?P.*?)\n```\n\n" + r"After -- (?P[^\n]+):\n\n```text\n(?P.*?)\n```", + text, + flags=re.DOTALL, + ) + if len(sections) != 3: + raise RuntimeError("expected three archived before-and-after examples") + return [ + { + "case": case, + "before": before_intro.rstrip("."), + "after": after_intro.rstrip("."), + } + for case, before_intro, _before, after_intro, _after in sections + ] + + +def build_record(source: Path = SOURCE) -> dict[str, object]: + text = source.read_text(encoding="utf-8") + return { + "schema": "clean-docs.learning-evidence.v1", + "source": source.relative_to(ROOT).as_posix(), + "measurements": _table_rows(text), + "examples": _examples(text), + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--source", type=Path, default=SOURCE) + parser.add_argument("--out", type=Path, default=OUTPUT) + args = parser.parse_args() + rendered = json.dumps(build_record(args.source.resolve()), indent=2, sort_keys=True) + "\n" + atomic_write(args.out.resolve(), rendered) + print(args.out) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/build_release.py b/scripts/build_release.py index 832e659..fdc5e79 100644 --- a/scripts/build_release.py +++ b/scripts/build_release.py @@ -114,11 +114,13 @@ def _verify_reader_candidate( candidate = str(reader_trial["candidate_commit"]) _run("git", "merge-base", "--is-ancestor", candidate, final_ref) changed = set(_run("git", "diff", "--name-only", candidate, final_ref).splitlines()) - allowed = {"pyproject.toml", ".clean-docs/reader-trial.json"} + receipt_path = str(reader_trial["receipt_path"]) + evidence_root = str(reader_trial["evidence_root"]).rstrip("/") + "/" + allowed = {"pyproject.toml", receipt_path} unexpected = sorted( path for path in changed - if path not in allowed and not path.startswith(".clean-docs/reader-trials/") + if path not in allowed and not path.startswith(evidence_root) ) if unexpected: raise RuntimeError( diff --git a/scripts/record_learning_tutorial.py b/scripts/record_learning_tutorial.py new file mode 100644 index 0000000..88cb575 --- /dev/null +++ b/scripts/record_learning_tutorial.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +"""Run the published drift tutorial and record stable observed outcomes.""" + +from __future__ import annotations + +import argparse +import json +import os +import subprocess +import tempfile +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +OUTPUT = ROOT / ".clean-docs/learning/tutorial-evidence.json" +MANIFEST = """\ +version: 1 +bindings: + - id: status-actions + type: region + doc: README.md + region: status-actions + extractor: python-literal + source: {path: src/actions.py, symbol: ACTIONS} + renderer: markdown-table + columns: [command, job] +projections: + llms_txt: + output: llms.txt + title: Moonbase Status documentation + summary: Source-bound operator documentation. + include: [README.md] +""" +README = """\ +# Moonbase Status + + +Use this fixture when a public command changes before its operator guide. It gives maintainers a checked path from stale prose to a repaired and verified page. + + +## Operator actions + + + +""" +SOURCE_BEFORE = """\ +ACTIONS = [ + {"command": "report", "job": "Send the current habitat status"}, +] +""" +SOURCE_AFTER = SOURCE_BEFORE.replace('"report"', '"publish"') + + +def _run(executable: Path, root: Path, *arguments: str) -> dict[str, object]: + environment = dict(os.environ) + environment.pop("PYTHONPATH", None) + process = subprocess.run( + [str(executable), "--root", str(root), *arguments], + text=True, + capture_output=True, + env=environment, + timeout=30, + check=False, + ) + output = (process.stdout + process.stderr).strip() + return { + "command": "clean-docs " + " ".join(arguments), + "exit_code": process.returncode, + "observed": output.splitlines()[0] if output else "No terminal output", + } + + +def _write_fixture(root: Path) -> None: + (root / "src").mkdir() + (root / ".clean-docs.yml").write_text(MANIFEST, encoding="utf-8") + (root / "README.md").write_text(README, encoding="utf-8") + (root / "src/actions.py").write_text(SOURCE_BEFORE, encoding="utf-8") + + +def record(executable: Path) -> dict[str, object]: + with tempfile.TemporaryDirectory(prefix="clean-docs-learning-") as temporary: + root = Path(temporary) + _write_fixture(root) + drive_baseline = _run(executable, root, "drive") + project_baseline = _run(executable, root, "project") + check_baseline = _run(executable, root, "check") + verify_baseline = _run(executable, root, "verify") + (root / "src/actions.py").write_text(SOURCE_AFTER, encoding="utf-8") + readme_before_repair = (root / "README.md").read_text(encoding="utf-8") + drift = _run(executable, root, "check") + repair = _run(executable, root, "drive") + readme_after_repair = (root / "README.md").read_text(encoding="utf-8") + projection = _run(executable, root, "project") + final_check = _run(executable, root, "check") + final_verify = _run(executable, root, "verify") + + results = ( + drive_baseline, + project_baseline, + check_baseline, + verify_baseline, + drift, + repair, + projection, + final_check, + final_verify, + ) + exit_codes = tuple(item["exit_code"] for item in results) + if exit_codes != (0, 0, 0, 0, 1, 0, 0, 0, 0): + raise RuntimeError(f"tutorial returned unexpected exit codes: {exit_codes}") + if "publish" not in readme_after_repair or "report" not in readme_before_repair: + raise RuntimeError("tutorial repair did not update the declared documentation region") + prefix_before, suffix_before = readme_before_repair.split( + "", 1 + ) + prefix_after, suffix_after = readme_after_repair.split( + "", 1 + ) + if prefix_before != prefix_after or suffix_before.split( + "", 1 + )[1] != suffix_after.split("", 1)[1]: + raise RuntimeError("tutorial repair changed prose outside the declared region") + return { + "schema": "clean-docs.tutorial-evidence.v1", + "steps": [ + {"moment": "Protected baseline", "command": "clean-docs check", "exit": 0, "meaning": "The bound page matches source."}, + {"moment": "Source changed alone", "command": "clean-docs check", "exit": 1, "meaning": "The status-actions binding is stale."}, + {"moment": "Declared region repaired", "command": "clean-docs drive", "exit": 0, "meaning": "Only the bound region changes."}, + {"moment": "Projection refreshed", "command": "clean-docs project", "exit": 0, "meaning": "llms.txt receives the repaired page digest."}, + {"moment": "Repository verified", "command": "clean-docs verify", "exit": 0, "meaning": "Bindings and projections are current."}, + ], + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--clean-docs", type=Path, required=True) + parser.add_argument("--out", type=Path, default=OUTPUT) + args = parser.parse_args() + executable = args.clean_docs.resolve() + if not executable.is_file(): + raise SystemExit(f"clean-docs executable not found: {executable}") + rendered = json.dumps(record(executable), indent=2, sort_keys=True) + "\n" + args.out.resolve().parent.mkdir(parents=True, exist_ok=True) + args.out.resolve().write_text(rendered, encoding="utf-8") + print(args.out) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/render_social_preview.py b/scripts/render_social_preview.py new file mode 100644 index 0000000..57d0c64 --- /dev/null +++ b/scripts/render_social_preview.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +"""Render the repository social preview from the canonical system-map vocabulary.""" + +from __future__ import annotations + +import argparse +import re +import shutil +import struct +import subprocess +import tempfile +from pathlib import Path + +from clean_docs.regions import atomic_write + + +ROOT = Path(__file__).resolve().parents[1] +SYSTEM_MAP = ROOT / "docs/assets/clean-docs-system-map.svg" +SVG_OUTPUT = ROOT / "docs/assets/clean-docs-social.svg" +PNG_OUTPUT = ROOT / "docs/assets/clean-docs-social.png" +WIDTH = 1280 +HEIGHT = 640 + + +def _system_tokens(source: Path) -> dict[str, str]: + text = source.read_text(encoding="utf-8") + required = { + "Repository sources": "Repository sources", + "Source bindings": "Source bindings", + "Reject stale changes": "Reject stale changes", + } + missing = [label for label in required if label not in text] + if missing: + raise RuntimeError(f"system map is missing social-preview concepts: {', '.join(missing)}") + colors = re.findall(r"#[0-9a-fA-F]{6}", text) + for color in ("#edf6ff", "#25225f", "#302d78", "#4541a2"): + if color not in colors: + raise RuntimeError(f"system map is missing expected design token: {color}") + return { + "background": "#edf6ff", + "ink": "#15143b", + "line": "#25225f", + "border": "#302d78", + "accent": "#4541a2", + **required, + } + + +def render_svg(source: Path = SYSTEM_MAP) -> str: + token = _system_tokens(source) + return f''' + clean-docs: docs that answer to code + A three-step source-bound documentation flow. Repository sources own facts, bindings connect facts to prose, and a deterministic check rejects stale changes. + + + + + + + + + + + + + + + + + + + + clean-docs + + SOURCE-BOUND DOCUMENTATION ENGINE + Docs that answer to code. + When source moves, stale prose fails loudly. + + + + + + 01 · SOURCE + {token['Repository sources']} + Code owns the fact. + + static evidence + + + + + + 02 · BIND + {token['Source bindings']} + Prose names its owner. + + region · claim · symbol + + + + + + + 03 · PROVE + Deterministic check + Drift fails before merge. + + repair · reject · project + + + + + THE CHECKABLE SPINE STAYS ATTACHED + +''' + + +def _png_dimensions(path: Path) -> tuple[int, int]: + data = path.read_bytes()[:24] + if data[:8] != b"\x89PNG\r\n\x1a\n": + raise RuntimeError(f"not a PNG: {path}") + return struct.unpack(">II", data[16:24]) + + +def _chrome() -> str: + candidates = ( + shutil.which("google-chrome"), + shutil.which("chromium"), + "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", + ) + for candidate in candidates: + if candidate and Path(candidate).is_file(): + return candidate + raise RuntimeError("Chrome or Chromium is required to render the social preview PNG") + + +def render_png(svg: str, output: Path) -> None: + with tempfile.TemporaryDirectory(prefix="clean-docs-social-") as temporary: + html = Path(temporary) / "preview.html" + html.write_text( + '' + svg + "", + encoding="utf-8", + ) + subprocess.run( + [ + _chrome(), + "--headless=new", + "--hide-scrollbars", + "--disable-gpu", + "--force-device-scale-factor=1", + "--window-size=1280,640", + f"--screenshot={output.resolve()}", + html.resolve().as_uri(), + ], + check=True, + capture_output=True, + text=True, + timeout=30, + ) + if _png_dimensions(output) != (WIDTH, HEIGHT): + raise RuntimeError(f"social preview has unexpected dimensions: {_png_dimensions(output)}") + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + svg = render_svg() + if args.check: + if SVG_OUTPUT.read_text(encoding="utf-8") != svg: + raise SystemExit("social preview SVG is stale") + if _png_dimensions(PNG_OUTPUT) != (WIDTH, HEIGHT): + raise SystemExit("social preview PNG is missing or has the wrong dimensions") + return 0 + atomic_write(SVG_OUTPUT, svg) + render_png(svg, PNG_OUTPUT) + print(PNG_OUTPUT) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/run_acceptance.py b/scripts/run_acceptance.py index 0570c24..300b50b 100644 --- a/scripts/run_acceptance.py +++ b/scripts/run_acceptance.py @@ -72,6 +72,14 @@ "upgrade-compatibility", "independent-reader-success", }, + "1.1": { + "public-repository-legibility", + "tutorial-from-a-clean-room", + "postmortem-facts-cannot-drift", + "deterministic-seam-boundary", + "additive-learning-corpus", + "fresh-reader-learning-path", + }, } diff --git a/scripts/verify_reader_trial.py b/scripts/verify_reader_trial.py index deca8ce..6c3f84b 100644 --- a/scripts/verify_reader_trial.py +++ b/scripts/verify_reader_trial.py @@ -8,6 +8,7 @@ import json import re import sys +from dataclasses import dataclass from datetime import datetime from pathlib import Path from typing import Any @@ -34,6 +35,24 @@ class ReaderTrialError(ValueError): """The reader-trial evidence does not prove the release gate.""" +@dataclass(frozen=True) +class TrialLayout: + rubric: Path + receipt: Path + evidence_root: Path + + +def trial_layout(release_version: str) -> TrialLayout: + """Keep each release line bound to the rubric its candidate readers saw.""" + if release_version.startswith("1.1."): + return TrialLayout( + rubric=Path(".clean-docs/reader-trial-rubric-v1.1.yml"), + receipt=Path(".clean-docs/reader-trial-v1.1.json"), + evidence_root=Path(".clean-docs/reader-trials-v1.1"), + ) + return TrialLayout(rubric=RUBRIC, receipt=RECEIPT, evidence_root=EVIDENCE_ROOT) + + def _sha256(data: bytes) -> str: return hashlib.sha256(data).hexdigest() @@ -80,14 +99,14 @@ def _timestamp(value: Any, *, label: str) -> None: raise ReaderTrialError(f"{label} must include a UTC offset") -def _load_rubric(root: Path) -> tuple[dict[str, Any], bytes]: +def _load_rubric(root: Path, rubric_path: Path) -> tuple[dict[str, Any], bytes]: try: import yaml except ImportError as exc: raise ReaderTrialError( "stable reader-trial verification requires PyYAML" ) from exc - path = root / RUBRIC + path = root / rubric_path try: data = path.read_bytes() raw: Any = yaml.safe_load(data) @@ -139,8 +158,9 @@ def _load_rubric(root: Path) -> tuple[dict[str, Any], bytes]: def verify_reader_trial(root: Path, release_version: str) -> dict[str, object]: """Return a release-safe summary or raise when evidence is incomplete.""" root = root.resolve() - rubric, rubric_bytes = _load_rubric(root) - receipt_path = root / RECEIPT + layout = trial_layout(release_version) + rubric, rubric_bytes = _load_rubric(root, layout.rubric) + receipt_path = root / layout.receipt try: receipt_bytes = receipt_path.read_bytes() raw: Any = json.loads(receipt_bytes) @@ -247,7 +267,7 @@ def verify_reader_trial(root: Path, release_version: str) -> dict[str, object]: raise ReaderTrialError("reader task evidence paths must be unique strings") if not isinstance(digest, str) or SHA256.fullmatch(digest) is None: raise ReaderTrialError(f"reader task evidence digest is invalid: {evidence}") - evidence_path = _relative_file(root, evidence, prefix=EVIDENCE_ROOT) + evidence_path = _relative_file(root, evidence, prefix=layout.evidence_root) evidence_bytes = evidence_path.read_bytes() if not evidence_bytes or len(evidence_bytes) > MAX_EVIDENCE_BYTES: raise ReaderTrialError(f"reader task evidence size is invalid: {evidence}") @@ -270,6 +290,8 @@ def verify_reader_trial(root: Path, release_version: str) -> dict[str, object]: "receipt_sha256": _sha256(receipt_bytes), "rubric_sha256": rubric_digest, "context_sha256": context_digest, + "receipt_path": layout.receipt.as_posix(), + "evidence_root": layout.evidence_root.as_posix(), "participants": completed_profiles, "tasks_per_participant": len(expected_tasks), } diff --git a/tests/test_reader_trial.py b/tests/test_reader_trial.py index b57999c..bf8606b 100644 --- a/tests/test_reader_trial.py +++ b/tests/test_reader_trial.py @@ -12,6 +12,7 @@ from scripts.verify_reader_trial import ( ReaderTrialError, + trial_layout, verify_reader_trial, verify_release_reader_trial, ) @@ -24,10 +25,16 @@ def _sha256(data: bytes) -> str: return hashlib.sha256(data).hexdigest() -def _write_trial(root: Path) -> Path: - rubric_path = root / ".clean-docs/reader-trial-rubric.yml" +def _write_trial( + root: Path, + *, + release_version: str = "1.0.0", + candidate: str = "1.0.0rc9", +) -> Path: + layout = trial_layout(release_version) + rubric_path = root / layout.rubric rubric_path.parent.mkdir(parents=True, exist_ok=True) - shutil.copyfile(ROOT / ".clean-docs/reader-trial-rubric.yml", rubric_path) + shutil.copyfile(ROOT / layout.rubric, rubric_path) rubric_bytes = rubric_path.read_bytes() rubric = yaml.safe_load(rubric_bytes) context = [] @@ -42,7 +49,7 @@ def _write_trial(root: Path) -> Path: profile_id = profile["id"] tasks = [] for task in rubric["tasks"]: - relative = Path(".clean-docs/reader-trials") / profile_id / f"{task['id']}.txt" + relative = layout.evidence_root / profile_id / f"{task['id']}.txt" evidence = root / relative evidence.parent.mkdir(parents=True, exist_ok=True) evidence.write_text(f"{profile_id} passed {task['id']} using published docs only\n") @@ -62,14 +69,14 @@ def _write_trial(root: Path) -> Path: }) receipt = { "schema": "clean-docs.independent-reader-trial.v2", - "candidate": "1.0.0rc9", + "candidate": candidate, "candidate_commit": "a" * 40, "candidate_artifact_sha256": "b" * 64, "rubric_sha256": _sha256(rubric_bytes), "context": context, "participants": participants, } - receipt_path = root / ".clean-docs/reader-trial.json" + receipt_path = root / layout.receipt receipt_path.write_text(json.dumps(receipt, indent=2, sort_keys=True) + "\n") return receipt_path @@ -92,6 +99,26 @@ def test_reader_trial_binds_rubric_context_participants_and_task_evidence( assert summary["receipt_sha256"] == _sha256(receipt.read_bytes()) +def test_version_11_reader_trial_uses_two_families_and_learning_tasks(tmp_path: Path) -> None: + receipt = _write_trial( + tmp_path, + release_version="1.1.0", + candidate="1.1.0rc1", + ) + + summary = verify_reader_trial(tmp_path, "1.1.0") + + assert summary["candidate"] == "1.1.0rc1" + assert summary["participants"] == { + "anthropic-opus-4-8": 1, + "codex-gpt-5-6-sol-high": 1, + } + assert summary["tasks_per_participant"] == 5 + assert summary["receipt_path"] == ".clean-docs/reader-trial-v1.1.json" + assert summary["evidence_root"] == ".clean-docs/reader-trials-v1.1" + assert summary["receipt_sha256"] == _sha256(receipt.read_bytes()) + + def test_reader_trial_rejects_tampered_or_incomplete_evidence(tmp_path: Path) -> None: receipt_path = _write_trial(tmp_path) receipt = json.loads(receipt_path.read_text()) diff --git a/tests/test_release_artifact.py b/tests/test_release_artifact.py index c24545b..1f21618 100644 --- a/tests/test_release_artifact.py +++ b/tests/test_release_artifact.py @@ -180,6 +180,8 @@ def test_stable_release_accepts_only_reader_candidate_version_and_receipts( "required": True, "candidate_commit": candidate, "candidate_artifact_sha256": hashlib.sha256(wheel.read_bytes()).hexdigest(), + "receipt_path": ".clean-docs/reader-trial.json", + "evidence_root": ".clean-docs/reader-trials", } release_builder._verify_reader_candidate(final, trial, tmp_path / "build") diff --git a/tests/test_v11_acceptance_registry.py b/tests/test_v11_acceptance_registry.py new file mode 100644 index 0000000..bbf9113 --- /dev/null +++ b/tests/test_v11_acceptance_registry.py @@ -0,0 +1,20 @@ +from pathlib import Path + +from scripts.run_acceptance import load_registry + + +ROOT = Path(__file__).parents[1] + + +def test_version_11_registry_names_every_scenario() -> None: + release, cases = load_registry(ROOT / "tests/v11-acceptance.yml") + + assert release == "1.1" + assert [case.id for case in cases] == [ + "public-repository-legibility", + "tutorial-from-a-clean-room", + "postmortem-facts-cannot-drift", + "deterministic-seam-boundary", + "additive-learning-corpus", + "fresh-reader-learning-path", + ] diff --git a/tests/test_v11_learning.py b/tests/test_v11_learning.py new file mode 100644 index 0000000..4d9b564 --- /dev/null +++ b/tests/test_v11_learning.py @@ -0,0 +1,213 @@ +from __future__ import annotations + +import json +import shutil +import struct +import subprocess +import sys +from pathlib import Path + +from clean_docs.engine import drive, evaluate +from scripts.build_learning_evidence import build_record +from scripts.record_learning_tutorial import record +from scripts.render_social_preview import HEIGHT, WIDTH, render_svg + + +ROOT = Path(__file__).parents[1] +LEARN = ROOT / "docs/learn" + + +def test_public_first_screen_defines_the_product_and_routes_to_learning() -> None: + readme = (ROOT / "README.md").read_text(encoding="utf-8") + purpose = readme.index("") + badges = readme.index("[![CI]") + start = readme.index("**[Start here]") + detail = readme.index("## Why clean-docs exists") + + assert purpose < badges < start < detail + assert "source-bound documentation engine and CLI" in readme[purpose:badges] + assert "actions/workflows/ci.yml/badge.svg" in readme + assert "img.shields.io/github/v/release" in readme + assert "img.shields.io/badge/license-MIT" in readme + + +def test_social_preview_is_current_legible_and_at_repository_aspect_ratio() -> None: + svg = (ROOT / "docs/assets/clean-docs-social.svg").read_text(encoding="utf-8") + png = (ROOT / "docs/assets/clean-docs-social.png").read_bytes() + + assert svg == render_svg() + assert png[:8] == b"\x89PNG\r\n\x1a\n" + assert struct.unpack(">II", png[16:24]) == (WIDTH, HEIGHT) + for phrase in ( + "Docs that answer to code.", + "Repository sources", + "Source bindings", + "Deterministic check", + "Drift fails before merge.", + ): + assert phrase in svg + assert " None: + test_public_first_screen_defines_the_product_and_routes_to_learning() + test_social_preview_is_current_legible_and_at_repository_aspect_ratio() + + +def test_learning_surface_contains_only_the_index_and_three_lessons() -> None: + paths = sorted(path.name for path in LEARN.iterdir()) + assert paths == [ + "deep-dive-the-deterministic-seam.md", + "index.md", + "postmortem-the-readme-that-lied.md", + "tutorial-catch-a-lying-doc.md", + ] + for path in LEARN.iterdir(): + text = path.read_text(encoding="utf-8") + assert text.splitlines()[2] == "" + + +def test_learning_index_routes_each_reader_job_without_copying_reference() -> None: + index = (LEARN / "index.md").read_text(encoding="utf-8") + ordered = ( + "../../README.md", + "tutorial-catch-a-lying-doc.md", + "postmortem-the-readme-that-lied.md", + "deep-dive-the-deterministic-seam.md", + ) + positions = [index.index(path) for path in ordered] + assert positions == sorted(positions) + assert "../CLI.md" in index + assert "../SUPPORT.md" in index + assert "../SECURITY_MODEL.md" in index + + +def test_postmortem_record_is_derived_from_the_archived_case() -> None: + committed = json.loads( + (ROOT / ".clean-docs/learning/ultra-csm-hygiene.json").read_text(encoding="utf-8") + ) + assert committed == build_record() + assert committed["measurements"][0] == { + "measure": "Total findings", + "before": "280", + "after": "73 (all justified in `NOTES.md`)", + } + assert len(committed["examples"]) == 3 + + +def test_postmortem_evidence_drift_repairs_only_bound_regions(tmp_path: Path) -> None: + root = tmp_path / "postmortem" + (root / "docs/learn").mkdir(parents=True) + (root / ".clean-docs/learning").mkdir(parents=True) + document = LEARN / "postmortem-the-readme-that-lied.md" + evidence = ROOT / ".clean-docs/learning/ultra-csm-hygiene.json" + shutil.copyfile(document, root / "docs/learn/postmortem-the-readme-that-lied.md") + shutil.copyfile(evidence, root / ".clean-docs/learning/ultra-csm-hygiene.json") + (root / ".clean-docs.yml").write_text( + """\ +version: 1 +bindings: + - id: measurements + type: region + doc: docs/learn/postmortem-the-readme-that-lied.md + region: postmortem-measurements + extractor: json + source: {path: .clean-docs/learning/ultra-csm-hygiene.json, pointer: /measurements} + renderer: markdown-table + columns: [measure, before, after] + - id: examples + type: region + doc: docs/learn/postmortem-the-readme-that-lied.md + region: postmortem-examples + extractor: json + source: {path: .clean-docs/learning/ultra-csm-hygiene.json, pointer: /examples} + renderer: markdown-table + columns: [case, before, after] +""", + encoding="utf-8", + ) + data_path = root / ".clean-docs/learning/ultra-csm-hygiene.json" + data = json.loads(data_path.read_text(encoding="utf-8")) + data["measurements"][0]["before"] = "281" + data_path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") + before = (root / "docs/learn/postmortem-the-readme-that-lied.md").read_text( + encoding="utf-8" + ) + unbound_before = before.split("", 1)[1] + + results = evaluate(root, root / ".clean-docs.yml") + assert [result.binding_id for result in results if result.changed] == ["measurements"] + _results, findings = drive(root, root / ".clean-docs.yml") + assert not findings + + after = (root / "docs/learn/postmortem-the-readme-that-lied.md").read_text( + encoding="utf-8" + ) + assert "| Total findings | 281 |" in after + assert after.split("", 1)[1] == unbound_before + assert not any(result.changed for result in evaluate(root, root / ".clean-docs.yml")) + + +def test_published_tutorial_runs_the_observed_drift_loop(tmp_path: Path) -> None: + wrapper = tmp_path / "clean-docs" + wrapper.write_text( + "#!/bin/sh\n" + f"export PYTHONPATH={ROOT / 'src'}\n" + f"exec {sys.executable} -m clean_docs \"$@\"\n", + encoding="utf-8", + ) + wrapper.chmod(0o755) + first = record(wrapper) + second = record(wrapper) + committed = json.loads( + (ROOT / ".clean-docs/learning/tutorial-evidence.json").read_text(encoding="utf-8") + ) + + assert first == second == committed + assert [step["exit"] for step in first["steps"]] == [0, 1, 0, 0, 0] + + +def test_deep_dive_claims_resolve_to_deterministic_implementation_sources() -> None: + ids = { + "deterministic-seam-evidence", + "deterministic-seam-phrasing", + "deterministic-seam-gate", + } + results = [result for result in evaluate(ROOT, ROOT / ".clean-docs.yml") if result.binding_id in ids] + + assert {result.binding_id for result in results} == ids + assert not any(result.changed for result in results) + sources = {result.provenance.path for result in results} + assert sources == { + "src/clean_docs/engine.py", + "src/clean_docs/phrasing.py", + "src/clean_docs/outcomes.py", + } + + +def test_llms_projection_contains_every_learning_page() -> None: + projection = (ROOT / "llms.txt").read_text(encoding="utf-8") + for path in sorted(LEARN.iterdir()): + assert path.relative_to(ROOT).as_posix() in projection + + +def test_additive_learning_corpus_passes_audit_projection_and_links() -> None: + test_learning_surface_contains_only_the_index_and_three_lessons() + test_learning_index_routes_each_reader_job_without_copying_reference() + test_llms_projection_contains_every_learning_page() + for document in LEARN.iterdir(): + text = document.read_text(encoding="utf-8") + assert len(text.splitlines()) <= 120 + for command in ( + [sys.executable, "-m", "clean_docs", "--root", str(ROOT), "audit"], + [sys.executable, "-m", "clean_docs", "--root", str(ROOT), "project", "--check"], + ): + result = subprocess.run( + command, + cwd=ROOT, + env={"PYTHONPATH": str(ROOT / "src")}, + text=True, + capture_output=True, + check=False, + ) + assert result.returncode == 0, result.stdout + result.stderr diff --git a/tests/v11-acceptance.yml b/tests/v11-acceptance.yml new file mode 100644 index 0000000..39c12d3 --- /dev/null +++ b/tests/v11-acceptance.yml @@ -0,0 +1,15 @@ +version: 1 +release: "1.1" +scenarios: + - id: public-repository-legibility + test: tests/test_v11_learning.py::test_public_repository_legibility_e2e + - id: tutorial-from-a-clean-room + test: tests/test_v11_learning.py::test_published_tutorial_runs_the_observed_drift_loop + - id: postmortem-facts-cannot-drift + test: tests/test_v11_learning.py::test_postmortem_evidence_drift_repairs_only_bound_regions + - id: deterministic-seam-boundary + test: tests/test_v11_learning.py::test_deep_dive_claims_resolve_to_deterministic_implementation_sources + - id: additive-learning-corpus + test: tests/test_v11_learning.py::test_additive_learning_corpus_passes_audit_projection_and_links + - id: fresh-reader-learning-path + test: tests/test_reader_trial.py::test_version_11_reader_trial_uses_two_families_and_learning_tasks