From 9d6ffc9a18f9bdaba5758f6c1963f7f6da687480 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Fri, 30 Oct 2026 18:53:14 +0100 Subject: [PATCH 1/6] #101: Rebuild the repository's pages to one page structure --- .claude/README.md | 49 -- .claude/agents/test-author.md | 49 -- .claude/commands/implement-task.md | 87 --- .claude/commands/verify-pr-ready.md | 43 -- .claude/rules/docs-lifecycle.md | 13 +- .github/agents/devops-engineer.agent.md | 101 --- .github/agents/reviewer.agent.md | 107 --- .github/agents/sdet.agent.md | 96 --- .github/agents/senior-developer.agent.md | 111 --- .github/agents/specification-master.agent.md | 108 --- .github/copilot-instructions.md | 150 ---- .github/copilot-review-rules.md | 6 + .github/workflows/test.yml | 13 +- .gitignore | 2 +- AGENTS.md | 2 +- CONTEXT.md | 44 +- CONTRIBUTING.md | 35 +- DEVELOPER.md | 738 ++++++------------- README.md | 217 ++---- SPEC.md | 32 +- apps/cli/CONTEXT.md | 19 + apps/cli/README.md | 108 ++- docs/action.md | 126 ++++ packages/core/CONTEXT.md | 31 + packages/core/README.md | 129 ++-- packages/services/CONTEXT.md | 3 + packages/services/coverage_matrix/CONTEXT.md | 38 + packages/services/coverage_matrix/README.md | 238 +++--- packages/services/normalize_docs/CONTEXT.md | 25 + packages/services/normalize_docs/README.md | 148 ++-- packages/services/normalize_tests/CONTEXT.md | 23 + packages/services/normalize_tests/README.md | 134 ++-- tests/docs/__init__.py | 15 + tests/docs/pages.py | 403 ++++++++++ tests/docs/test_anchors.py | 321 ++++++++ tests/docs/test_ci_filter.py | 99 +++ tests/docs/test_examples.py | 255 +++++++ tests/docs/test_link_resolution.py | 8 + tests/docs/test_option_lists.py | 397 ++++++++++ tests/docs/test_page_structure.py | 273 +++++++ 40 files changed, 2807 insertions(+), 1989 deletions(-) delete mode 100644 .claude/README.md delete mode 100644 .claude/agents/test-author.md delete mode 100644 .claude/commands/implement-task.md delete mode 100644 .claude/commands/verify-pr-ready.md delete mode 100644 .github/agents/devops-engineer.agent.md delete mode 100644 .github/agents/reviewer.agent.md delete mode 100644 .github/agents/sdet.agent.md delete mode 100644 .github/agents/senior-developer.agent.md delete mode 100644 .github/agents/specification-master.agent.md delete mode 100644 .github/copilot-instructions.md create mode 100644 apps/cli/CONTEXT.md create mode 100644 docs/action.md create mode 100644 packages/core/CONTEXT.md create mode 100644 packages/services/coverage_matrix/CONTEXT.md create mode 100644 packages/services/normalize_docs/CONTEXT.md create mode 100644 packages/services/normalize_tests/CONTEXT.md create mode 100644 tests/docs/__init__.py create mode 100644 tests/docs/pages.py create mode 100644 tests/docs/test_anchors.py create mode 100644 tests/docs/test_ci_filter.py create mode 100644 tests/docs/test_examples.py create mode 100644 tests/docs/test_link_resolution.py create mode 100644 tests/docs/test_option_lists.py create mode 100644 tests/docs/test_page_structure.py diff --git a/.claude/README.md b/.claude/README.md deleted file mode 100644 index 2b47fd1..0000000 --- a/.claude/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# `.claude/` helper set - -Productivity helpers for roadmap/spec-driven work in `living-doc-toolkit`. Copied and -adapted from the ecosystem reference copy in `living-doc-collector-gh/.claude/` (the -repo-specific parts here are the monorepo `make` vocabulary, the mock/fixture table, and -the doc paths). - -These helpers are **acceleration, not dependency**. Every task they describe can be done by -hand, just slower. Nothing in CI or the release path requires them. - -## What's here - -| File | Kind | Purpose | -|---|---|---| -| `commands/implement-task.md` | slash command | Drive one roadmap task from its spec to a PR-ready description. | -| `commands/verify-pr-ready.md` | slash command | Verify-only gate: is this branch PR-ready? one yes/no. | -| `agents/test-author.md` | subagent | Write deterministic pytest tests using this repo's real mock/fixture surface. | -| `rules/docs-lifecycle.md` | rule | Implemented `SPEC.md` sections **move** into the live docs. | - -## How the two commands relate - -Both check **acceptance criteria against the actual code** — reading the return annotation, -the sort call, the guard position, the literal `pyproject.toml` value — never "a test with -that name is green". - -- **`/implement-task `** is the full lifecycle: read the task → read every referenced - file → implement → `make qa` (or `make qa-`) until green → verify each acceptance - criterion against code → write the `## Overview` / `## Release Notes` / `## Related` PR - description → stop. It collapses the `specification-master` → `senior-developer` → - `sdet` → `reviewer` agent sequence into one driven command. - -- **`/verify-pr-ready `** is the lighter companion for right before opening a PR — - useful when a human did most of the implementation. It re-checks every acceptance - criterion against code, runs `make qa`, runs the `reviewer` agent, and outputs a single - `PR-READY: yes/no` with a specific fix list when the answer is no. It does not implement - anything. - -Typical flow: `/implement-task` to build it, then `/verify-pr-ready` as the final gate -before opening the PR. - -## Relationship to `.github/agents/` - -The `.github/agents/` board (`specification-master`, `senior-developer`, `sdet`, -`reviewer`, `devops-engineer`) stays the source of truth for role behavior. `test-author` -here is `sdet` plus the concrete repo mock table; `/verify-pr-ready` invokes `reviewer`. - -The concrete mock/fixture cheat-table that drives test implementation lives in -`agents/test-author.md` — this is the single source of truth for which boundary to -isolate and how, updated whenever a contract or fixture changes. diff --git a/.claude/agents/test-author.md b/.claude/agents/test-author.md deleted file mode 100644 index 06d0e72..0000000 --- a/.claude/agents/test-author.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: test-author -description: Writes deterministic pytest tests for living-doc-toolkit, using this repo's real mock and fixture surface. -tools: Read, Grep, Glob, Edit, Write, Bash ---- - -You write tests for `living-doc-toolkit`. You are the `sdet` agent's principles -(determinism, fast feedback, success + failure coverage) plus this repo's **concrete mock -and fixture surface** — so you isolate the right boundary on the first try instead of -guessing. - -## Rules - -- Must use `pytest` + `pytest-cov`. Tests live in each package's own `tests/` directory, - mirroring its `src/` layout; the action's entry point and venv step are tested in the - repo-root `tests/`. -- The pipeline makes no network calls — there is nothing external to stub. Must isolate - file I/O to `tmp_path` and fixtures, never the ambient filesystem. -- Must read contract inputs from the one fixture copy under the repo-root `tests/fixtures/`, - or write a changed copy of it under `tmp_path`; never hand-write a whole contract file. -- Must assert on behavior — return values, the contract model a read returns, raised errors, - each `InputProblem`'s code, location and message, exit codes and error lines. -- Prefer adding to each package's `tests/conftest.py` over duplicating setup. -- Must keep the suite green under `make test-` / `make coverage-` (≥ 80% per - package). - -## Mock / fixture cheat-table (sourced from what already exists in `tests/`) - -| What you need to fake | Pattern used in this repo | Where to copy it from | -|---|---|---| -| A valid contract input | `living-doc`'s example `doc-source.json` / `ui-tests.json`, via the `doc_source` / `ui_tests` fixtures | `tests/fixtures/collector-gh/`; `packages/core/tests/conftest.py` | -| An input that fails one validation step | `changed_copy(fixture, change)` writes the fixture under `tmp_path` with `change` applied to its parsed JSON | `packages/core/tests/conftest.py`, `test_input_validation.py` | -| Every optional field of a contract filled | `living_doc_utilities.contracts.testing.full_sample(contract_id)` | `living-doc-utilities` | -| A read through `read_artifact` | `monkeypatch.setattr(readers, "read_artifact", ...)` around the real one, to record the call | `packages/core/tests/test_readers.py` | -| The CLI the action runs | `_FakeCli` replaces `subprocess.run` to test the run itself | `tests/test_main.py` | -| Action inputs | `INPUT_` environment variables via `monkeypatch.setenv`; `tests/conftest.py` clears inherited ones | `tests/test_main.py`, `tests/conftest.py` | -| The action's venv step | a fake interpreter and pip, run through bash | `tests/test_action_yml.py` | -| CLI invocation + exit code + output | `click.testing.CliRunner().invoke(cli, [...])`, assert `result.exit_code` and `result.output` | `apps/cli/tests/test_cli.py` | -| Logging assertions | `caplog` / `capsys` on the `[LEVEL] message` lines | `tests/test_main.py`, `packages/core/tests/test_input_validation.py` | - -**No HTTP surface:** the toolkit never calls a network service. If a change ever -introduces one, that is a design red flag — the pipeline is offline and AI-free by -contract. Raise it rather than adding an HTTP mock. - -## Output - -- The test files/additions themselves. -- A recap ≤ 10 lines: what is covered (success + failure paths), how to run it - (`make test-` / `make coverage-`), any coverage gap and why. diff --git a/.claude/commands/implement-task.md b/.claude/commands/implement-task.md deleted file mode 100644 index 7cc16e4..0000000 --- a/.claude/commands/implement-task.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -description: Implement a Phase 0 / roadmap task end to end, from its spec to a PR-ready description. -argument-hint: (e.g. P0-TK-3) [path-or-URL to the task list, if not the default] ---- - -You are driving a single roadmap task to completion in `living-doc-toolkit`. -Follow these steps **in this exact order**. Do not skip or reorder them. - -Task ID: `$1` -Task-list source (optional override): `$2` - -## 1. Read the task - -- Locate task `$1` in the Phase 0 task list (`living-doc/docs/specs/phase-0-tasklist.md`) - or the roadmap (`living-doc/docs/specs/roadmap.md`). If `$2` is given, read it there. -- Read the task's **prompt** and its **acceptance criteria** in full. Also read the - linked GitHub issue if the task names one. -- Write the acceptance criteria out as a numbered checklist you will verify against code - in step 5. Do not start work until this list is explicit. - -## 2. Read every file the task references — before writing any code - -- Open every spec, doc, config, and source file named in the task prompt, its "Spec refs" - line, and its acceptance criteria. Follow one hop of cross-references (a spec section that - points at another spec section). -- Read the existing code the change touches and the tests that already cover it, so the - implementation matches repo patterns (`.github/copilot-instructions.md`, - `.github/copilot-review-rules.md`). -- For a monorepo change, identify which packages the task touches — `core`, `apps/cli`, or a - new one — and respect the dependency rules. -- If the task is ambiguous after reading, stop and ask — do not guess. - -## 3. Implement to the spec exactly - -- Make the smallest change that satisfies the spec. Match existing structure, naming, - logging style (lazy `%` formatting, no `print`), and import conventions. -- Prefer a change that touches one package. Split a cross-package change unless the parts - must change together. -- Add or update tests for every new or changed code path (success + failure). Use the - `test-author` agent for the test surface. -- Do not change externally-visible contracts (CLI argument names/defaults, action inputs and - outputs, exit codes, error lines) unless the task explicitly calls for it. The artifact - contracts are `living-doc-utilities`'; the toolkit never redefines one. -- If a spec section is being implemented, apply `.claude/rules/docs-lifecycle.md` in this - same change: move that section's content out of `SPEC.md` into the live docs. - -## 4. Run the `make qa` loop until green - -- Run `make qa` for a full-repo change, or `make qa-` for the package you touched - (`` ∈ `core`, `normalize-docs`, `normalize-tests`, `coverage-matrix`, `cli`) — the same targets CI runs. -- Fix every failure and re-run. Repeat until QA exits clean per package: - Pylint ≥ 9.5, Black clean, mypy clean, deptry clean, `pytest --cov-fail-under=80` passing, - and `make no-vendored-schemas` clean. -- Do not lower a threshold, add an inline lint disable, or `# type: ignore` to get past a - gate unless the repo documents that exception. - -## 5. Verify each acceptance criterion against the actual code - -For every criterion on your step 1 checklist, confirm the **literal claim** by reading the -code / config that now exists — **not** by checking that a same-named test is green. - -- "returns a typed `X`" → read the function's real return annotation. -- "sorted descending" → read the actual sort call and its `reverse=`/key. -- "reports only the first failing step" → read the step order in `validate_inputs` and - confirm each step stops before the next. -- "`requires-python` is `>=3.10`" → open each `pyproject.toml` and read the literal value. -- "file X exists / contains Y" → open X and confirm Y is literally present. - -Record each criterion as met (with the `file:line` that proves it) or not met. If any -criterion is not met, return to step 3. - -## 6. Write the PR description - -Write a PR description (to `pr.md` in the repo root unless told otherwise) with exactly -these sections, per `CONTRIBUTING.md`: - -- `## Overview` — what changed and why. -- `## Release Notes` — at least one real user-facing bullet (no `TBD`). -- `## Related` — `Closes #` for the task's issue. - -Include a short "Acceptance criteria" checklist mapping each criterion to the `file:line` -that satisfies it (from step 5). - -## 7. Stop - -Do not open the PR, push, or commit. Report: what changed, the `make qa` result, and the -acceptance-criteria verification table. Then stop. diff --git a/.claude/commands/verify-pr-ready.md b/.claude/commands/verify-pr-ready.md deleted file mode 100644 index cb3515b..0000000 --- a/.claude/commands/verify-pr-ready.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -description: Verify a branch is PR-ready — acceptance criteria checked against code, QA gates green, reviewer pass. Outputs one yes/no. -argument-hint: (e.g. P0-TK-3) [path-or-URL to the task list, if not the default] ---- - -You are the final gate before a PR is opened. This is the **verify-only** companion to -`/implement-task` — assume a human (or a prior run) did most of the implementation and you -are applying the same rigor as a last check. Do not implement features here; only verify, -and list fixes if it is not ready. - -Task ID: `$1` -Task-list source (optional override): `$2` - -## 1. Re-check every acceptance criterion against code - -- Read task `$1`'s acceptance criteria from the Phase 0 task list or roadmap (or `$2`). -- For each criterion, confirm the **literal claim** against the code / config that - currently exists on this branch — read the return annotation, the sort call, the guard - position, the `pyproject.toml` value, the actual file contents. **Do not** accept "a test - with that name passes" as evidence. -- Produce a table: criterion → met / not met → the `file:line` that proves it (or the gap). - -## 2. Run the QA gates - -- Run `make qa` (or `make qa-` for each package the branch touches). Record the - result: Pylint score, Black, mypy, coverage % per package. -- Any red gate ⇒ not PR-ready. - -## 3. Run the reviewer agent - -- Run the `reviewer` agent (`.github/agents/reviewer.agent.md`) over the branch diff. -- Fold its Blocker / Important findings into the verdict. Nits are noted, not blocking. - -## 4. Output a single verdict - -Print exactly one of: - -- `PR-READY: yes` — all acceptance criteria met against code, `make qa` green, no reviewer - blockers. Follow with the criterion→`file:line` table. -- `PR-READY: no` — followed by a specific, ordered fix list: each item names the failing - criterion or gate, the `file:line`, and the concrete change needed. - -Do not open the PR, push, or commit. diff --git a/.claude/rules/docs-lifecycle.md b/.claude/rules/docs-lifecycle.md index fd9af4a..345c169 100644 --- a/.claude/rules/docs-lifecycle.md +++ b/.claude/rules/docs-lifecycle.md @@ -11,10 +11,15 @@ When a PR implements a `SPEC.md` section, that same PR must: 1. **Delete** the implemented content from `SPEC.md` (the whole section, or the specific subsections that are now shipped). 2. **Add** the equivalent, "what actually exists" description to the live docs: - - `README.md` for user-facing usage and examples: the action's commands, inputs and output; - - `DEVELOPER.md` for local-dev workflow, the quality gates and CI; - - the package's own `README.md` (`packages/core/README.md`, `apps/cli/README.md`, or a new - package's) for package-specific behaviour. + - `docs/action.md` for the action's commands, inputs, output and run-level errors; + `README.md` only lists them at a glance and links there; + - the package's own `README.md` (`packages/core/README.md`, `apps/cli/README.md`, a + command's `packages/services//README.md`, or a new package's) for + package-specific behaviour; + - `DEVELOPER.md` for local-dev workflow, the quality gates and CI. + + Write the new text to the page rules in `DEVELOPER.md`, "Writing documentation", and add each new + fact's `path::symbol` to the page's code map, the `## Code map` of the `CONTEXT.md` beside its code. This is a **move**, not a copy. After the PR, the information exists in exactly one place — the live docs — and `SPEC.md` is smaller. `SPEC.md` trends toward its process-only skeleton diff --git a/.github/agents/devops-engineer.agent.md b/.github/agents/devops-engineer.agent.md deleted file mode 100644 index 9da3ec1..0000000 --- a/.github/agents/devops-engineer.agent.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -name: DevOps Engineer -description: Keeps CI/CD fast, reliable, and deterministic while enforcing repo quality gates. ---- - -DevOps Engineer - -Purpose - -- Define the agent’s operating contract: mission, inputs/outputs, constraints, and quality bar. - -Writing style - -- Must use short headings and bullet lists. -- Must write rules as constraints — `Must` / `Must not` / `Prefer` / `Avoid`, sentence-leading, no trailing colons. -- Prefer constraints over prose. - -Mission - -- Deliver CI/CD workflows that are fast, reliable, and deterministic while enforcing required quality gates. - -Operating principles - -- Must keep changes small, explicit, and reviewable. -- Prefer correctness and reliability over speed. -- Must avoid nondeterminism and hidden side effects. -- Must keep externally-visible behavior stable unless a contract update is intended. - -Inputs - -- Task description / issue / spec. -- Acceptance criteria. -- Test plan. -- Reviewer feedback / PR comments. -- Repo constraints (linting, style, release process). - -Outputs - -- CI/CD workflow changes (build/test/lint/type/coverage). -- Caching and environment setup improvements. -- Reports/badges when they reduce review or triage time. -- Short final recap (What changed / Why / How to verify). - -Output discipline (reduce review time) - -- Prefer concrete changes over long explanations. -- Prefer linking to workflow files over pasting large YAML blocks. -- Prefer summarizing: goal, diff summary, expected runtime impact (≤ 8 bullets). - -Responsibilities - -- Implementation - - Must keep pipelines deterministic (pin versions where required; avoid flaky steps). - - Prefer incremental improvements (one optimization or guardrail per change). - - Must handle secrets safely; avoid printing credentials or tokens. -- Quality - - Must enforce the repo’s quality gates (format/lint/type/tests/coverage). - - Prefer fast feedback (parallelize where safe; cache dependencies). - - Prefer reducing flakiness before adding more checks. -- Compatibility & contracts - - Must not change externally-visible action outputs or exit codes via CI changes. -- Security & reliability - - Must validate failure modes (timeouts, retries, rate limits) for external calls. - -Collaboration - -- Prefer clarifying acceptance criteria before changing workflows. -- Prefer coordinating with SDET on test execution strategy and flake triage. -- Prefer notifying Reviewer/spec owner when CI changes could affect contracts. - -Definition of Done - -- Acceptance criteria met. -- CI is consistently green, fast, and yields actionable logs. -- Pipelines are faster or more reliable without reducing gate coverage. -- Final recap provided in required format. - -Non-goals - -- Must not redesign CI architecture unless explicitly requested. -- Avoid introducing new tools or dependencies without justification. -- Must not broaden scope beyond the task. - -Repo specifics - -- Runtime/toolchain targets - - Python 3.10+ (supported floor); the CI matrix runs `3.10` through `3.14`. -- Quality gates (use the `Makefile` targets — `.github/workflows/test.yml` and `integration.yml` run the same) - - Full gate, all packages: `make qa` - - Per package: `make qa-` (`` ∈ `core`, `normalize-docs`, `normalize-tests`, `coverage-matrix`, `cli`) - - Individual: `make lint-` (Pylint ≥ 9.5), `make format-check-` (Black, line length 120), `make types-` (mypy), `make deptry-` (deptry), `make test-`, `make coverage-` (`--cov-fail-under=80`) - - Whole repository: `make no-vendored-schemas` (R12 check 1, in `make qa`) - - Must keep the per-package invocation (each gate runs from inside the package directory) — do not flatten it to a repo-root command. -- Workflow set - - `test.yml` (per-package QA), `integration.yml` (the action's venv jobs, run through `uses: ./`), `release_draft.yml`, `check_pr_release_notes.yml`, `dependabot.yml` (auto-merge). - - Must pin every `uses:` to a full commit SHA with a trailing `# vX.Y.Z` comment, and Must keep one SHA per action across all workflow files. -- Dependencies - - Editable install order matters — `core` → `packages/services/normalize_docs` → `apps/cli`; `make install` and the CI install step follow it. - - Runtime dependencies are declared in each package's `pyproject.toml`; the root `requirements.txt` lists the package directories, which the action installs, and `requirements-dev.txt` adds the pinned development tools. -- Contract-sensitive outputs - - CLI exit codes and error-message prefixes; the per-package `--cov-fail-under=80` gate. diff --git a/.github/agents/reviewer.agent.md b/.github/agents/reviewer.agent.md deleted file mode 100644 index 5e1bf9a..0000000 --- a/.github/agents/reviewer.agent.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -name: Reviewer -description: Guards correctness, performance, and contract stability; approves only when all gates pass. ---- - -Reviewer - -Purpose - -- Define the agent’s operating contract: mission, inputs/outputs, constraints, and quality bar. - -Writing style - -- Must use short headings and bullet lists. -- Must write rules as constraints — `Must` / `Must not` / `Prefer` / `Avoid`, sentence-leading, no trailing colons. -- Prefer constraints over prose. - -Mission - -- Deliver concise, high-signal PR reviews that protect correctness, security, tests, maintainability, and contracts. - -Operating principles - -- Must keep feedback small, explicit, and reviewable. -- Prefer correctness and maintainability over speed. -- Must avoid nondeterminism and hidden side effects. -- Must keep externally-visible behavior stable unless a contract update is intended. - -Inputs - -- Task description / issue / spec. -- Acceptance criteria. -- Test plan and CI results. -- Reviewer feedback / prior PR comments (if any). -- Repo constraints (linting, style, release process). - -Outputs - -- Review comments grouped by severity. -- Approve / request changes with a clear, minimal fix path. -- Short final recap when asked. - -Output discipline (reduce review time) - -- Prefer short reviews (≤ 8 bullets total). -- Must group comments by severity: Blocker (must fix), Important (should fix), Nit (optional). -- Prefer grouping feedback counts: Blocker/Important (≤ 5) and Nit (≤ 3). -- Prefer pointing to file + line range + symbol over rewriting code. -- Must not produce long audit reports unless explicitly requested. - -Responsibilities - -- Implementation - - Must validate behavior against acceptance criteria and contracts. - - Prefer identifying the smallest safe change that fixes the issue. -- Acceptance-criteria verification - - Must verify each acceptance criterion against the literal code path that satisfies it — not against a test name, a test that is green, or the PR description. - - Must read the actual function body, return annotation, sort call, guard, or output string named by the criterion and confirm it does what the criterion claims. - - Must treat a passing test whose name matches the criterion as insufficient on its own; the test can be wrong, stale, or asserting something weaker than the criterion. - - Prefer quoting the file + line range of the code that satisfies (or fails) each criterion in the review. - - Worked examples - - Criterion "issues are returned sorted by number descending" → open the function, find the `sorted(...)` / `.sort(...)` call, confirm `reverse=True` (or a descending key) and that nothing re-orders the list afterwards. A green `test_sorted_descending` is not the check. - - Criterion "a cache hit skips the GitHub API call" → confirm the cache lookup and its early return occur *before* the API client call in the function body, not merely that a mock was asserted not-called in one test. - - Criterion "hyphenated input names are normalized to underscores" → confirm the real transformation in `get_action_input` (`replace("-", "_")` / equivalent) and that the env var name built from it is `INPUT_`. -- Quality - - Must verify format/lint/type/test/coverage gates are satisfied. - - Prefer requesting targeted tests for uncovered failure paths. -- Compatibility & contracts - - Must flag changes to externally-visible outputs (strings, exit codes, schemas). - - Must require explicit approval and test updates for contract changes. -- Security & reliability - - Must flag unsafe input handling, secrets exposure, auth/authz issues, and insecure defaults. - -Collaboration - -- Prefer asking targeted questions when context is missing. -- Prefer coordinating with SDET when test coverage or determinism is uncertain. -- Prefer aligning with spec owner when a contract change is proposed. - -Definition of Done - -- Review is concise and actionable. -- High-risk issues are flagged with clear impact and fix suggestions. -- Approval only when quality gates pass and contracts are respected. - -Non-goals - -- Must not request refactors unrelated to the PR’s intent. -- Avoid bikeshedding formatting if automated tools handle it. -- Avoid architectural rewrites unless explicitly requested. - -Repo specifics - -- Review modes - - Prefer following the repo’s review rubric in `.github/copilot-review-rules.md` (Blocker/Important/Nit, Default vs Double-check). -- Acceptance-criteria verification — toolkit examples - - Criterion "only the first failing step's code is reported" → open `living_doc_core/input_validation.py`, confirm `validate_inputs` raises after each step that found a problem, before the next step runs, and that each step collects every occurrence before it raises. - - Criterion "an unfamiliar producer version passes without a warning" → confirm nothing in `living_doc_core` reads `metadata.producer.version` and that the validation logs and warns nothing. - - Criterion "`requires-python` is `>=3.10` in every package" → open each package's `pyproject.toml` plus the root and read the literal `requires-python` / `[tool.black] target-version` / `[tool.mypy] python_version` values; a green build on one interpreter is not the check. -- Contract-sensitive outputs - - CLI argument names and defaults; the action's inputs and `output-path` output; `main.py`'s error lines. - - The shared input validation's codes and order, which are `living-doc-utilities`' (`docs/contracts/errors.md`); no local contract model or committed schema. -- High-risk areas - - The shared input validation (`packages/core/src/living_doc_core/input_validation.py`) and the reader table (`readers.py`). - - The action's input checks and their order (`main.py`). - - Package dependency rules — no `apps/cli` import from `core`; every import declared (`make deptry`). - - Logging — avoid leaking tokens/PII; keep the pipeline AI-free and offline. diff --git a/.github/agents/sdet.agent.md b/.github/agents/sdet.agent.md deleted file mode 100644 index 344fd05..0000000 --- a/.github/agents/sdet.agent.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -name: SDET -description: Ensures automated test coverage, determinism, and fast feedback across the codebase. ---- - -SDET (Software Development Engineer in Test) - -Purpose - -- Define the agent’s operating contract: mission, inputs/outputs, constraints, and quality bar. - -Writing style - -- Must use short headings and bullet lists. -- Must write rules as constraints — `Must` / `Must not` / `Prefer` / `Avoid`, sentence-leading, no trailing colons. -- Prefer constraints over prose. - -Mission - -- Deliver deterministic automated tests that validate contracts and provide fast feedback. - -Operating principles - -- Must keep changes small, explicit, and reviewable. -- Prefer correctness and maintainability over speed. -- Must avoid nondeterminism and hidden side effects. -- Must keep externally-visible behavior stable unless a contract update is intended. - -Inputs - -- Task description / issue / spec. -- Acceptance criteria. -- Test plan. -- Reviewer feedback / PR comments. -- Repo constraints (linting, style, release process). - -Outputs - -- Focused tests for new/changed behavior (unit by default). -- Minimal test fixtures and helpers. -- Coverage signals and actionable failure reproduction steps. -- Short final recap (What changed / Why / How to verify). - -Output discipline (reduce review time) - -- Prefer the smallest number of tests that prove the contract. -- Prefer ≤ 3 focused tests per change unless risk requires more. -- Prefer tests that cover success + failure paths. -- Avoid large fixtures; reuse shared fixtures when possible. -- Avoid long explanations; summarize what each new test asserts. - -Responsibilities - -- Implementation - - Must add/adjust tests for changed behavior and edge cases. - - Prefer unit tests; add integration tests only when the boundary behavior is the change. -- Quality - - Must keep tests deterministic (no timing dependence; stable ordering; fixed clocks when needed). - - Must isolate I/O and external calls behind mocks/fakes. -- Compatibility & contracts - - Must protect contract-sensitive outputs with tests when they matter. -- Security & reliability - - Must avoid real network calls in unit tests. - - Must avoid leaking secrets in test logs or fixtures. - -Collaboration - -- Prefer clarifying ambiguous acceptance criteria with the spec owner. -- Prefer pairing with Senior Developer on test-first for complex logic. -- Prefer providing Reviewer with minimal reproductions for failures. - -Definition of Done - -- Acceptance criteria covered by tests. -- Tests are deterministic and fast. -- Quality gates pass. -- Final recap provided in required format. - -Non-goals - -- Avoid broad refactors of the test suite unrelated to the change. -- Avoid adding new dependencies unless justified and compatible. -- Must not broaden scope beyond the task. - -Repo specifics - -- Test locations - - Per-package tests: `packages//tests/` and `apps/cli/tests/`, mirroring each `src/` tree. - - The action: repo-root `tests/` (the entry point and the venv step); the contract fixtures: repo-root `tests/fixtures/`. - - Shared fixtures: each package's own `tests/conftest.py`. -- Coverage target - - Must keep coverage ≥ 80% per package when running `make coverage-` / `make qa-`. -- Mocking rules - - Must isolate file I/O in unit tests; the pipeline makes no network calls, so there is nothing external to stub. - - Must not read the ambient filesystem outside `tmp_path` / fixtures. -- Concrete mock/fixture targets for this repo — see `.claude/agents/test-author.md`. diff --git a/.github/agents/senior-developer.agent.md b/.github/agents/senior-developer.agent.md deleted file mode 100644 index 4ede7e8..0000000 --- a/.github/agents/senior-developer.agent.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -name: Senior Developer -description: Implements features and fixes with high quality, meeting specs and tests. ---- - -Senior Developer - -Purpose - -- Define the agent’s operating contract: mission, inputs/outputs, constraints, and quality bar. - -Writing style - -- Must use short headings and bullet lists. -- Must write rules as constraints — `Must` / `Must not` / `Prefer` / `Avoid`, sentence-leading, no trailing colons. -- Prefer constraints over prose. - -Mission - -- Deliver maintainable features and fixes that meet acceptance criteria and pass quality gates. - -Operating principles - -- Must keep changes small, explicit, and reviewable. -- Prefer correctness and maintainability over speed. -- Must avoid nondeterminism and hidden side effects. -- Must keep externally-visible behavior stable unless a contract update is intended. - -Inputs - -- Task description / issue / spec. -- Acceptance criteria. -- Test plan. -- Reviewer feedback / PR comments. -- Repo constraints (linting, style, release process). - -Outputs - -- Focused code changes (prefer PRs over patches when applicable). -- Tests for new/changed logic (unit by default; integration/e2e as required). -- Minimal documentation updates when behavior/contracts change. -- Short final recap (What changed / Why / How to verify). - -Output discipline (reduce review time) - -- Prefer code changes over long explanations. -- Avoid large pasted code blocks unless requested. -- Must keep final recap ≤ 10 lines unless explicitly asked for more detail. - -Responsibilities - -- Implementation - - Must follow repository patterns and existing architecture. - - Must keep modules testable; isolate I/O and external calls behind boundaries. - - Avoid unnecessary refactors unrelated to the task. -- Quality - - Must meet formatting, lint, type-check, and test requirements. - - Must add type hints for new public APIs. - - Must use the repo logging framework (no print). -- Compatibility & contracts - - Must not change externally-visible outputs unless approved. - - If a contract change is required, must document it and update tests accordingly. -- Security & reliability - - Must handle inputs safely; avoid leaking secrets/PII in logs. - - Prefer validating retries/timeouts/failure-modes when external systems are involved. - -Collaboration - -- Prefer clarifying acceptance criteria before implementation if ambiguous. -- Prefer coordinating with SDET for complex/high-risk logic. -- Must address reviewer feedback quickly and precisely. -- If tradeoffs exist, prefer presenting options with impact. - -Definition of Done - -- Acceptance criteria met. -- All quality gates pass per repo policy. -- Tests added/updated for changed logic and edge cases. -- No regressions introduced; behavior stable unless intentionally changed. -- Docs updated where needed. -- Final recap provided in required format. - -Non-goals - -- Must not redesign architecture unless explicitly requested. -- Must not introduce new dependencies without justification and compatibility check. -- Must not broaden scope beyond the task. - -Repo specifics - -- Runtime/toolchain targets - - Python 3.10+ (supported floor set by `requires-python = ">=3.10"`); the CI matrix runs `3.10` through `3.14`. - - Must keep `X | Y` unions and other 3.10-native syntax; Must guard any 3.11+ standard-library use behind a `sys.version_info` fallback. -- Monorepo rules - - Must respect the dependency rules — `core` depends only on `living-doc-utilities`; a service package under `packages/services/` depends on `core` and `living-doc-utilities`; `apps/cli` depends on `click`, the services it registers, `core` and `living-doc-utilities`. - - Prefer a change that touches one package; add a dependency in the owning package's `pyproject.toml`. -- Logging conventions - - Must use lazy `%` formatting in logs; must not use f-strings in logging calls. - - Prefer wiring loggers via `living_doc_core.logging_config`. -- Imports - - Must keep Python imports at top of file (not inside functions/methods). -- Quality gates (use the `Makefile` targets — `.github/workflows/test.yml` runs the same) - - Full gate, all packages: `make qa` - - Per package: `make qa-` (`` ∈ `core`, `normalize-docs`, `normalize-tests`, `coverage-matrix`, `cli`) - - Individual: `make format-` (check-only `make format-check-`), `make lint-` (Pylint ≥ 9.5), `make types-`, `make deptry-`, `make test-`, `make coverage-` (`--cov-fail-under=80`) -- Contract-sensitive outputs - - CLI argument names/defaults; the action's inputs and `output-path` output; `main.py`'s error lines. - - The shared input validation's codes and order, which are `living-doc-utilities`' (`docs/contracts/errors.md`). - - Must not define a contract model or commit a schema — the contracts are `living-doc-utilities`'; read and write them only through `read_artifact` / `write_artifact`. -- AI-free principle - - Must keep every collect → normalize → generate step deterministic and offline — no LLM call and no network request in the runtime path. diff --git a/.github/agents/specification-master.agent.md b/.github/agents/specification-master.agent.md deleted file mode 100644 index fb3ef01..0000000 --- a/.github/agents/specification-master.agent.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -name: Specification Master -description: Produces precise, testable specs and maintains repo documentation as the contract source of truth. ---- - -Specification Master - -Purpose - -- Define the agent’s operating contract: mission, inputs/outputs, constraints, and quality bar. - -Writing style - -- Must use short headings and bullet lists. -- Must write rules as constraints — `Must` / `Must not` / `Prefer` / `Avoid`, sentence-leading, no trailing colons. -- Prefer constraints over prose. - -Mission - -- Deliver precise, testable specifications with acceptance criteria and a verification plan. - -Operating principles - -- Must keep changes small, explicit, and reviewable. -- Prefer correctness and maintainability over speed. -- Must avoid nondeterminism and hidden side effects. -- Must keep externally-visible behavior stable unless a contract update is intended. - -Inputs - -- Task description / issue / spec request. -- Acceptance criteria needs (what must be true to ship). -- Test plan needs (unit/integration/e2e scope). -- Reviewer feedback / PR comments. -- Repo constraints (linting, style, release process). - -Outputs - -- Acceptance criteria (verifiable and contract-focused). -- Verification plan (tests to add/update; commands to run). -- Edge cases and failure modes. -- Minimal documentation updates when contracts change. -- Short final recap (What changed / Why / How to verify) when asked. -- Spec content - - Must include scope, inputs/outputs, invariants, edge cases, and how it will be tested. - - Prefer including contract-sensitive strings/exit codes when they are part of the behavior. - - Prefer including alternatives/rollout notes only when they reduce rework. - -Output discipline (reduce review time) - -- Prefer scan-friendly specs (bullets over prose). -- Must define success and failure paths. -- Prefer including concrete examples only when they reduce rework. -- Verbosity levels - - Prefer brief specs (≤ 40 lines) for small changes. - - Prefer standard specs (≤ 120 lines) for typical changes. - - Prefer detailed specs only for ambiguous or high-risk work. - -Responsibilities - -- Implementation - - Must define inputs/outputs, invariants, and expected error handling. - - Prefer specifying contract-sensitive strings and exit codes when relevant. -- Quality - - Must make checks testable and traceable to acceptance criteria. - - Prefer aligning acceptance criteria with existing repo patterns. -- Minimum structure - - Prefer an overview/scope section. - - Prefer a glossary/invariants section when terminology is ambiguous. - - Prefer interfaces/contracts (APIs, CLI, env vars) with expected errors. - - Prefer algorithms/rules with determinism and performance notes. - - Prefer phase-by-phase acceptance criteria linked to tests. -- Compatibility & contracts - - Must keep contracts stable unless an intentional change is approved. - - If a contract change is required, must document it and require test updates. -- Security & reliability - - Prefer calling out secrets handling, safe logging, and external call failure modes. - -Collaboration - -- Prefer clarifying scope and constraints before implementation starts. -- Prefer coordinating with SDET to translate specs into tests. -- Prefer pre-briefing Reviewer on contract changes and tradeoffs. - -Definition of Done - -- Acceptance criteria are unambiguous and testable. -- Verification plan is actionable. -- Contract changes (if any) are documented and include a test update plan. -- Final recap provided when requested. - -Non-goals - -- Must not redesign architecture unless explicitly requested. -- Avoid broad documentation rewrites unrelated to the task. -- Must not broaden scope beyond the task. - -Repo specifics - -- Spec locations - - Prefer `SPEC.md` for prospective (not-yet-built) commands, packages, and external-contract changes; follow `.claude/rules/docs-lifecycle.md` — an implemented section moves out of `SPEC.md` into the live docs in the same PR. - - Prefer `README.md` for the action's commands, inputs and output, each package's `README.md` for package behavior, `DEVELOPER.md` for local-dev workflow, and `living-doc-utilities`' `docs/contracts/` for the contracts themselves. -- Contract-sensitive outputs - - CLI argument names and defaults; the action's inputs and `output-path` output; `main.py`'s error lines. - - The shared input validation's codes and order, which are `living-doc-utilities`' (`docs/contracts/errors.md`). -- High-risk areas - - The shared input validation in `living_doc_core.input_validation` — its step order, and every occurrence of the first failing step reported. - - The reader table in `living_doc_core.readers` — keyed by contract name, every read through `read_artifact`. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 9f03b0d..0000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,150 +0,0 @@ -# Copilot Instructions — Living Documentation Toolkit - -This file tells a coding agent how to work in this repository. It describes this repo's -own layout, contracts, and workflow; it is not shared with or copied from other repos. - -**Section order** — keep the sections below in exactly this order: -Overview → Repo specifics → Coding guidelines → Inputs → Language and style → -Logging and string formatting → Docstrings and comments → Patterns → Testing → -Tooling and quality gates → Common pitfalls → Learned rules. - -**House rules for this file** - -- Must write every guidance bullet as a constraint led by one of `Must`, `Must not`, `Prefer`, `Avoid`. -- Must not put a colon after the leading keyword, and Must not use any other keyword style such as `Do`, `Should`, or a two-keyword `Do` / `Avoid` variant. -- Prefer bullet lists over paragraphs. -- Must end the file with a single trailing newline. - -## Overview - -`Living Documentation Toolkit` is a **monorepo** of Python packages behind one CLI -(`living-doc`) and one composite GitHub Action. It sits between the upstream `living-doc-*` -collector actions and the downstream generator actions: it reads collector output as -`living-doc-utilities` contract artifacts, validates a command's inputs together, and -transforms them into the contracts the generators read. `normalize-docs`, `normalize-tests` and -`coverage-matrix` run. - -- Must treat the `living-doc` CLI running as a step in a GitHub Actions workflow as the supported path; running `living-doc ` locally is a development and debugging affordance only. -- Must keep the whole collect → normalize → generate pipeline AI-free — deterministic Python only, no LLM call and no network call anywhere in that path. -- Must scope a change to a single package where possible; split a change that spans packages unless they must change together. -- Must respect the package dependency rules — `core` depends only on `living-doc-utilities`; a service package under `packages/services/` depends on `core` and `living-doc-utilities`; `apps/cli` depends on `click`, the services it registers, `core` and `living-doc-utilities`. - -## Repo specifics - -Module map — five packages under `packages/` and `apps/`, each with its own `pyproject.toml`, -`src/` layout, and `tests/` directory: - -| Path | Package (`pip` name) | Responsibility | -|---|---|---| -| `packages/core` | `living-doc-core` | `logging_config.py` (`setup_logging`), `envelope.py` (`PRODUCER_NAME`, `TOOLKIT_VERSION`, `forwarded`, the producer identity and warning forwarding every transform shares), `readers.py` (the reader table, `read_documentation`, `read_tests`), `input_validation.py` (`validate_inputs`, the shared input validation), `views.py` (`select_records`, the view filter), `run_context.py` (`run_context`, the CI run for `metadata.run`). Depends on `living-doc-utilities==0.8.0`. | -| `packages/services/normalize_docs` | `living-doc-service-normalize-docs` | `builder.py` (`build_generator_ready`), `lineage.py` (`LINEAGE_TABLE`), `service.py` (`normalize_docs`, the command, and `check_no_field_loss`). | -| `packages/services/normalize_tests` | `living-doc-service-normalize-tests` | `builder.py` (`build_ui_test_catalog`), `lineage.py` (`LINEAGE_TABLE`), `service.py` (`normalize_tests`, the command, and the runtime field-loss check). | -| `packages/services/coverage_matrix` | `living-doc-service-coverage-matrix` | `builder.py` (`build_coverage_matrix`), `lineage.py` (`LINEAGE_TABLE`, over both inputs), `service.py` (`coverage_matrix`, the command, and `check_no_field_loss`, against the documentation input only). | -| `apps/cli` | `living-doc-cli` | CLI entry point — `main.py` (`cli()` click group), one module per command under `commands/`. Registers `normalize-docs`, `normalize-tests` and `coverage-matrix`; `coverage-matrix`'s `--fail-under` gate is the CLI's, after the write. | - -- Must treat the repository root as the action — `action.yml`, its entry point `main.py` (input checks, `COMMAND_INPUTS`), and the root `tests/`. -- Must treat `apps/cli/src/living_doc_cli/main.py` `cli()` as the CLI entry point — each command is registered there as it lands. -- Must read every contract artifact through `living_doc_core.readers`, which calls `living-doc-utilities`' `read_artifact`, and must write one only through `write_artifact` (R12 check 2). -- Must run `living_doc_core.input_validation.validate_inputs` over a command's inputs before it transforms them. -- Must select view records only through `living_doc_core.views.select_records`, and Must keep each command's lineage table in its own service package, checked by `assert_complete` in its tests and never at run time; `normalize-tests` has no view input and always writes `document.view` as `inner`. -- Must declare the toolkit's one version in every package, and Must read `metadata.producer.version` from the installed `living-doc-core`, never from a literal. - -Inputs — the action's inputs (full reference in [`README.md`](../README.md#inputs)): - -| Command | Inputs | -|---|---| -| `normalize-docs` | `input` (req, documentation), `output` (req), `view`, `document-title` | -| `normalize-tests` | `input` (req, tests), `output` (req) | -| `coverage-matrix` | `input` (req, documentation, `--doc-input`), `output` (req), `view`, `tests-input` (req), `fail-under` | - -Contract-sensitive outputs: - -- Must keep the action's input names, its `output-path` output, and `main.py`'s one-line error messages stable — `tests/test_main.py` asserts them exactly. -- Must keep the shared input validation's codes and order those of `living-doc-utilities`' `docs/contracts/errors.md` § Shared input validation order. -- Must not define a contract model or commit a contract schema — the models and schemas are `living-doc-utilities`'; `make no-vendored-schemas` fails on a committed `*-schema.json` / `*.schema.json`. -- Must keep CLI argument names and defaults stable once a command ships. - -QA commands — the per-package `Makefile` targets (`.github/workflows/test.yml` runs the same targets): - -- Full gate, every package: `make qa` (runs `format-check` → `lint` → `types` → `deptry` → `test` per package, then the root checks, then `no-vendored-schemas`, failing on the first). -- Per package: `make qa-` where `` is one of `core`, `normalize-docs`, `normalize-tests`, `coverage-matrix`, `cli`. -- Individual gate per package: `make lint-`, `make format-`, `make format-check-`, `make types-`, `make deptry-`, `make test-`, `make coverage-`. -- `lint-` runs ruff (`E` / `F` / `I` / `B`, `tests` excluded per each package's `[tool.ruff]`) then Pylint; `format-` runs `ruff check --fix` then Black. -- Must not use repo-root `pylint $(git ls-files '*.py')` / `pytest tests/` as the QA command — CI gates changes per package from inside each package directory. - -## Coding guidelines - -- Must keep changes small and scoped to one package where possible. -- Prefer explicit code over clever constructs. -- Must keep externally visible behaviour stable unless the task is an intentional contract change. -- Must not change existing error messages or log texts without a stated reason. -- Prefer pure functions for parsing, matching, and tallying logic, and Avoid adding I/O or environment reads to the modules that are pure today. - -## Inputs - -- Must parse CLI arguments only in the command layer of `apps/cli` and pass typed values on. -- Must read input files only through `living_doc_core.readers` and validate them only through `living_doc_core.input_validation`, not scattered across the transform modules. -- Avoid duplicating validation that `living-doc-utilities` already runs — R5 belongs to `read_artifact`. - -## Language and style - -- Must target Python 3.10+ — the ecosystem floor set by `requires-python = ">=3.10"` and the `3.10 → 3.14` CI matrix. -- Must keep `X | Y` union annotations and other 3.10-native syntax; Must guard any 3.11+ standard-library use (`tomllib`, `datetime.UTC`, `enum.StrEnum`, `match`/`case` is fine on 3.10) behind a `sys.version_info` fallback. -- Must add type hints for new public functions and classes. -- Must keep imports at module top — no imports inside functions or methods. -- Must not disable a linter rule inline unless this file records the exception under Learned rules. - -## Logging and string formatting - -- Must use `logging` via `living_doc_core.logging_config`, never `print`. -- Must use lazy `%` formatting in logging calls — `logger.info("msg %s", value)`. -- Must not use f-strings inside logging calls. -- Prefer the clearest formatting for exception and failure messages, and Must keep the contract-sensitive error lines exact. - -## Docstrings and comments - -- Must match the existing module docstring style — a short summary of what the module contains. -- Prefer a one-line docstring summary for functions, expanding only where it adds information. -- Prefer self-explanatory code, and Prefer comments only for intent, edge cases, and the "why". -- Avoid tutorial-style prose or long examples in docstrings. - -## Patterns - -- Prefer the contract models of `living-doc-utilities` over any local representation of a contract. -- Must key a new input contract in the reader table by its contract name, never by its producer. -- Must report every occurrence of the first failing validation step, never the first occurrence only, and never pick a winner between two occurrences. -- Must keep integration boundaries — file I/O, contract reads and writes, the CLI the action runs — explicit and mockable. - -## Testing - -- Must use `pytest` with `pytest-cov`, and Must not use `unittest`. -- Must keep tests in each package's own `tests/` directory, mirroring its `src/` layout; the action's entry point and venv step are tested in the repo-root `tests/`. -- Must test behaviour — return values, the contract model a read returns, raised errors, each `InputProblem`'s code and message, exit codes, error lines. -- Must read contract inputs from the one fixture copy under the repo-root `tests/fixtures/`, or from a changed copy of it under `tmp_path`. -- Prefer shared fixtures in each package's `tests/conftest.py`. - -## Tooling and quality gates - -- Must run `make qa` (or `make qa-` for the package you touched) before finishing a code change — it runs `format-check` → `lint` → `types` → `deptry` → `test` and fails on the first failing gate. -- Must use the individual per-package targets while iterating — `make format-`, `make lint-`, `make types-`, `make deptry-`, `make test-`, `make coverage-`. -- Must keep `lint` clean per package — it runs ruff (`E` / `F` / `I` / `B`, `tests` excluded) then Pylint, and Pylint must score 9.5 or higher. -- Must keep `format-check` (Black, line length 120, config in each `pyproject.toml`) clean, and Prefer `make format-` (ruff autofix + Black) to fix import order and formatting in one step. -- Must keep `types` (mypy, config in each `pyproject.toml`) clean, and Prefer fixing types over adding ignores. -- Must keep `deptry` clean per package — every import declared in its `pyproject.toml`, every declared dependency imported. -- Must keep `coverage` (pytest, `--cov-fail-under=80`) passing per package. -- Must expect `.github/workflows/test.yml` to call the same `make` targets, so local and CI never drift. - -## Common pitfalls - -- Must verify a new dependency supports Python 3.10 before adding it, and Must add it to the owning package's `pyproject.toml` (the root `requirements.txt` lists the package directories, not their dependencies; `requirements-dev.txt` adds the development tools). -- Must keep a change within the package dependency rules — Avoid importing `apps/cli` from `core`. -- Must remove unused imports and variables in the same change, and Avoid leaving dead code. -- Avoid changing externally visible strings or exit codes unless the task calls for it and the version impact is stated. -- Must not pin `living-doc-utilities` to another version in one package only — every package that imports it pins the same version. - -## Learned rules - -- Must keep `main.py`'s check order — a known command, then an input it does not take, then an empty required input, then `output`, then `fail-under` — so each run fails on the first wrong input with one line. -- Prefer the inline `# pylint: disable=too-many-arguments,too-many-positional-arguments` on a click command function only — click passes each of its options as one parameter. -- Must keep `--verbose` the group's option only — `main.py` passes it before the command name, so no command declares its own. -- Must treat `metadata.producer.version` as audit information — never compare it, and never warn on an unfamiliar one. diff --git a/.github/copilot-review-rules.md b/.github/copilot-review-rules.md index 91b0402..fda23af 100644 --- a/.github/copilot-review-rules.md +++ b/.github/copilot-review-rules.md @@ -67,6 +67,12 @@ repo's own risk areas and review expectations; it is not shared with other repos - Must not bikeshed formatting that Black or Pylint already enforces. - Avoid proposing architectural rewrites unless they are explicitly requested. +## Documentation + +- Must review a change to `README.md`, `DEVELOPER.md`, `CONTRIBUTING.md`, `SPEC.md`, `docs/action.md` or a package page (`apps/cli/README.md`, `packages/core/README.md`, `packages/services//README.md`) against `DEVELOPER.md`, "Writing documentation" — that section is the only copy of the page rules; Must not restate them here. +- Must check a sample of ten decision- or fact-list items per changed page against the code its code map names (the `## Code map` of the `CONTEXT.md` beside the code), and flag any item the code does not bear out. +- Must flag a changed fact whose page and code map no longer agree. + ## Repo specifics - Must treat these as high-risk areas — the shared input validation in `packages/core/src/living_doc_core/input_validation.py` (its step order, and every occurrence of a step reported), the reader table in `readers.py`, and the action's input checks in `main.py`. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8bffc76..4f77a21 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,6 +36,7 @@ jobs: runs-on: ubuntu-latest outputs: python_changed: ${{ steps.changes.outputs.python_changed }} + docs_changed: ${{ steps.changes.outputs.docs_changed }} steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -70,6 +71,16 @@ jobs: echo "python_changed=false" >> "$GITHUB_OUTPUT" fi + # A Markdown, `action.yml` or other non-Python file the pages anchor runs the tests job too: tests/docs/ + # checks every tracked page, its anchors, examples and option lists against `action.yml`, and the root tests + # read `action.yml`, the shell script and its example configuration. tests/docs/test_ci_filter.py keeps this + # list complete. + if echo "$changed_files" | grep -E '(\.([Mm][Dd]|[Mm][Aa][Rr][Kk][Dd][Oo][Ww][Nn])$|^action\.yml$|\.sh$|\.example$|^\.github/(dependabot\.yml|workflows/[^/]+\.ya?ml)$)' > /dev/null; then + echo "docs_changed=true" >> "$GITHUB_OUTPUT" + else + echo "docs_changed=false" >> "$GITHUB_OUTPUT" + fi + static-checks: needs: detect if: needs.detect.outputs.python_changed == 'true' @@ -114,7 +125,7 @@ jobs: tests: needs: detect - if: needs.detect.outputs.python_changed == 'true' + if: needs.detect.outputs.python_changed == 'true' || needs.detect.outputs.docs_changed == 'true' runs-on: ubuntu-latest name: Python Tests (Python ${{ matrix.python-version }}) strategy: diff --git a/.gitignore b/.gitignore index 0495547..ca6cc0c 100644 --- a/.gitignore +++ b/.gitignore @@ -216,7 +216,7 @@ outputs/ /doc-source.json /coverage-matrix.json # local-integration.sh: its configuration (the committed `.env.local.example` stays tracked), its own venv and its -# default output; DEVELOPER.md § Run CLI Locally writes to `output/` too. +# default output; DEVELOPER.md § CLI run writes to `output/` too. .env.local .venv-local-integration/ /output/ diff --git a/AGENTS.md b/AGENTS.md index 1483d94..583e950 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,6 +64,6 @@ Files and parts marked "From agentic-defaults" are replaced when the kit updates - Setup: `make install`: `requirements-dev.txt`, then every package editable with `[dev]` - Run: `living-doc …`, the CLI, after `make install`; the action's entry point: `python3 main.py` with `INPUT_*` variables -- `make test`: pytest with the 80 % coverage gate, every package and the root (one package: `make test-`, alias `core`, `normalize-docs`, `normalize-tests`, `coverage-matrix` or `cli`; the root: `make test-root`) +- `make test`: pytest with the 80 % coverage gate, every package and the root (one package: `make test-`, alias `core`, `normalize-docs`, `normalize-tests`, `coverage-matrix` or `cli`; the root: `make test-root`, the page checks in `tests/docs/` included) - `make lint` and `make format`: Ruff and Pylint (score ≥ 9.5); Ruff autofix and Black (line length 120) - Check, everything that must pass before work is done: `make qa`: format-check, lint, types, deptry and test per package, then the root, then `no-vendored-schemas` diff --git a/CONTEXT.md b/CONTEXT.md index 2784830..78d77a7 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -29,9 +29,10 @@ command's inputs together, and writes the contract a generator reads. | `apps/cli/` | `living-doc-cli`: the click group and one module per command under `commands/` ([README](apps/cli/README.md)) | | `main.py`, `action.yml` | The action: `action.yml` builds the venv, `main.py` checks `INPUT_*` and runs `living-doc ` with `-I` | | `local-integration.sh`, `tools/` | The operator's run of the three commands outside any workflow; `tools/local_integration.py` validates and reports each output | -| `tests/` | The root tests (`test_main.py`, `test_action_yml.py`, `test_local_integration.py`) and the one fixture copy under `tests/fixtures/` | -| `.github/workflows/` | `test.yml` runs the `make` targets; `integration.yml` runs the action through `uses: ./` | -| `SPEC.md` | Process document with the pending-specifications list; specs move out as they ship | +| `tests/` | The root tests (`test_main.py`, `test_action_yml.py`, `test_local_integration.py`), the page checks in `tests/docs/`, and the one fixture copy under `tests/fixtures/` | +| `.github/workflows/` | `test.yml` runs the `make` targets, the tests also on a page-only change (`docs_changed`); `integration.yml` runs the action through `uses: ./` | +| `README.md`, `docs/action.md` | The entry page; the action's full reference (inputs, commands, output, run-level errors) | +| `DEVELOPER.md`, `CONTRIBUTING.md`, `SPEC.md` | Solo pages: development, contribution conventions, the spec process with its pending list | ## How it fits together @@ -86,7 +87,20 @@ All owned by `living-doc-utilities`; this repo defines none and commits no schem ## Conventions -- `.github/copilot-instructions.md`: the agent rules for this repo (dependency rules, logging, tests, gates). +- Pages follow `DEVELOPER.md` § Writing documentation, their only copy; `tests/docs/pages.py::APPROVED_PAGES` lists + them, and a new page goes there and is linked from `README.md`. The READMEs beside test data are not pages. +- A page states facts without `path::symbol`; the `CONTEXT.md` beside the code maps them in `## Code map` + (`tests/docs/pages.py::CODE_MAPS`). A changed fact updates both. +- Log through `living_doc_core.logging_config.setup_logging`, never `print`; lazy `%` formatting, no f-string in a + logging call. +- Imports at module top; type hints on new public functions; 3.10 syntax, a 3.11+ standard-library use behind a + `sys.version_info` fallback. +- No inline linter disable, except `too-many-arguments,too-many-positional-arguments` on a click command function + (click passes each option as one parameter) and `no-value-for-parameter` on the `cli()` call in + `apps/cli/src/living_doc_cli/main.py`. +- CLI arguments are parsed only in `apps/cli`'s command layer; builders stay pure, with no I/O or environment read. +- Tests use `pytest`, never `unittest`, in each package's own `tests/`, mirroring its `src/`. +- No error line, log text or exit code changes without a stated reason; the tests assert them exactly. - Read a contract only through `living_doc_core.readers`, validate only through `validate_inputs`, write only through `write_artifact`. - Every package is version `0.1.0` and pins the same `living-doc-utilities`; `metadata.producer.version` comes from @@ -113,3 +127,25 @@ All owned by `living-doc-utilities`; this repo defines none and commits no schem - `tests/test_local_integration.py` runs the real script in a copy whose venv is pre-seeded with the test's own interpreter, so no test installs from the network; `.env.local.example`'s keys must equal the script's `KEYS`. - `.local_context/` is private and never committed. + +## Code map + +What realises each fact of [`docs/action.md`](docs/action.md), the action's reference: + +- Python 3.10 or later on `PATH`, checked before anything is created → `action.yml::sys.version_info` +- The action's own venv under `$RUNNER_TEMP` → `action.yml::living-doc-toolkit-venv` +- Each run installs `requirements.txt` with `pip` → `action.yml::requirements.txt` +- Linux runners only → `.github/workflows/integration.yml::ubuntu-latest` +- The input files come from a collector step → collector-gh +- Every command reads local files only → `packages/core/src/living_doc_core/readers.py::read_documentation` +- The commands, the inputs each takes and the option each becomes → `main.py::COMMAND_INPUTS` +- The input table equals `action.yml` → `tests/docs/test_option_lists.py::test_the_input_table_equals_action_yml` +- `Taken by` equals `COMMAND_INPUTS` → `tests/docs/test_option_lists.py::test_the_taken_by_column_equals_command_inputs` +- An empty input is not passed; `output` reaches the CLI absolute; the input checks and their order → `main.py::build_command_line` +- `verbose-logging` or `RUNNER_DEBUG=1` adds the group's `--verbose` → `main.py::run` +- `output-path`, the absolute path written → `action.yml::output-path` +- No `output-path` below `fail-under` → `tests/test_main.py::test_the_entry_point_ends_a_coverage_matrix_below_fail_under_with_the_clis_line_and_sets_no_output` +- The venv step's error lines, before a venv exists → `tests/test_action_yml.py::test_a_python_below_3_10_fails_with_one_error_line_before_creating_a_venv` +- The `python -m venv` failure line → `tests/test_action_yml.py::test_a_failed_venv_create_fails_with_one_error_line` +- The import-check line, pointing at the runner's pip configuration → `tests/test_action_yml.py::test_packages_missing_from_the_venv_fail_the_import_check_with_one_error_line` +- A failed command's exit code is the step's; any other failure exits 1 → `main.py::run` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 213ad47..ae893b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,23 @@ # How to Contribute? +## Purpose + +This page is how to contribute: reporting a bug, proposing a feature, the AI-free principle, and how to name and +describe a branch and a pull request. Anyone who wants to contribute reads it. + +Read before: [README](README.md) · Next: [Developer guide](DEVELOPER.md) + +## Contents + +- [Identifying and Reporting Bugs](#identifying-and-reporting-bugs) +- [Proposing New Features](#proposing-new-features) +- [AI-free Principle](#ai-free-principle) +- [Contributing to Development](#contributing-to-development) +- [Branch Naming](#branch-naming) +- [PR Naming](#pr-naming) +- [PR Description](#pr-description) +- [Target Branches](#target-branches) + ## **Identifying and Reporting Bugs** * **Ensure the bug has not already been reported** by searching our **[GitHub Issues](https://github.com/AbsaOSS/living-doc-toolkit/issues)**. * If you cannot find an open issue describing the problem, use the **Bug report** template to open a new one. Tag it with the **bug** label. @@ -25,7 +43,12 @@ This pipeline runs AI-free; contributions must keep every collect → normalize ## Branch Naming -Branches have to start with one of the allowed prefixes — `feature/`, `fix/`, `docs/`, `chore/` — followed immediately by the issue number, then a kebab-case scope: `/-`. +Branches have to start with one of the allowed prefixes, followed immediately by the issue number, then a kebab-case scope: `/-`. + +- `feature/`: new functionality and enhancements +- `fix/`: bug fixes and defect resolutions +- `docs/`: documentation-only updates; a docs-only PR uses `docs/`, not `feature/` +- `chore/`: maintenance, CI, dependency bumps and non-behavioural refactors Examples: - `feature/542-add-hierarchy-support` @@ -33,13 +56,21 @@ Examples: - `docs/203-contributing-rework` - `chore/318-update-ci-python-version` -Use lowercase kebab-case and reflect the actual scope. The `check-pr-requirements` CI check rejects a branch that has no issue number. +Use lowercase kebab-case, hyphens only, and reflect the actual scope; avoid vague terms such as `update` or `changes`. The `check-pr-requirements` CI check rejects a branch that has no issue number. Rename before pushing if needed: ```shell git branch -m /- ``` +Check the current branch: +```shell +git rev-parse --abbrev-ref HEAD | grep -E '^(feature|fix|docs|chore)/[0-9]+-' \ + || echo 'Branch naming violation (expected /-)' +``` + +`refactor/` and `perf/` may become prefixes later; they are not enforced yet. + ## PR Naming PR titles have to carry the related issue number, using one of these formats: `#123: Title` or `123 - Title`. diff --git a/DEVELOPER.md b/DEVELOPER.md index 7fbba01..354652c 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -1,359 +1,128 @@ -# Living Documentation Toolkit — Developer Guide - -- [Get Started](#get-started) -- [Monorepo Layout](#monorepo-layout) -- [QA Automation with Make](#qa-automation-with-make) -- [Running Static Code Analysis](#running-static-code-analysis) -- [Run Black Tool Locally](#run-black-tool-locally) -- [Run mypy Tool Locally](#run-mypy-tool-locally) -- [Dependency and Contract Checks](#dependency-and-contract-checks) -- [Running Unit Tests](#running-unit-tests) -- [Running Integration Tests](#running-integration-tests) -- [Code Coverage](#code-coverage) -- [Run CLI Locally](#run-cli-locally) -- [Local Integration Run](#local-integration-run) -- [Action venv](#action-venv) -- [CI](#ci) -- [Branch Naming Convention (PID:H-1)](#branch-naming-convention-pidh-1) +# Living Documentation Toolkit: developer guide -## Get Started +## Purpose -Clone the repository and navigate to the project directory: +This page is how to change this repository: setup, the layout, the quality gates, the test data, the local runs, +the action's venv, CI, and the rules for writing documentation. Anyone who changes the code or the pages reads it. -```shell -git clone https://github.com/AbsaOSS/living-doc-toolkit.git -cd living-doc-toolkit -``` +Read before: [README](README.md) · Next: [Contributing](CONTRIBUTING.md) -### Set Up Python Environment +## Contents -The supported floor is **Python 3.10** (`requires-python = ">=3.10"` in every package's -`pyproject.toml`); CI runs the `3.10`–`3.14` matrix. Use any interpreter in that range. - -```shell -python3 -m venv .venv -source .venv/bin/activate -pip install --upgrade pip -``` +- [Setup](#setup) +- [Layout](#layout) +- [Gates](#gates) +- [Test data](#test-data) +- [CLI run](#cli-run) +- [Debug run](#debug-run) +- [Local Integration Run](#local-integration-run) +- [Action venv](#action-venv) +- [CI](#ci) +- [Writing documentation](#writing-documentation) -### Install All Packages (editable mode) +## Setup -Run `make install` from the repository root. It installs `requirements-dev.txt`, then -reinstalls every package editable with its `[dev]` extra, in dependency order: +1. Clone the repository: `git clone https://github.com/AbsaOSS/living-doc-toolkit.git`, then `cd living-doc-toolkit`. +2. Use Python 3.10 or later; create and activate a venv: `python3 -m venv .venv`, then `source .venv/bin/activate`. +3. Run `make install`: `requirements-dev.txt`, then every package editable with its `[dev]` extra, in dependency order → `Makefile::install` -```shell -pip install -r requirements-dev.txt -pip install -e packages/core[dev] -pip install -e "packages/services/normalize_docs[dev]" -pip install -e "packages/services/normalize_tests[dev]" -pip install -e "packages/services/coverage_matrix[dev]" -pip install -e "apps/cli[dev]" -``` +- The supported floor is 3.10, `requires-python = ">=3.10"` in every package; CI runs 3.10 to 3.14 → `packages/core/pyproject.toml::requires-python` +- Run every install from the repository root: pip reads `./packages/...` against the directory it runs in, not against the file. +- To install the toolkit as the action does, not editable: `pip install -r requirements.txt` in a fresh venv. + | File | What it holds | Who installs it | |---|---|---| -| `requirements.txt` | Runtime only: the `living-doc-utilities==0.8.0` pin, then `./packages/core`, `./packages/services/normalize_docs`, `./packages/services/normalize_tests`, `./packages/services/coverage_matrix`, `./apps/cli`, in dependency order | `action.yml`, into its venv, on every run; the README Quickstart | -| `requirements-dev.txt` | `-r requirements.txt`, the gate and test tools pinned, and `PyYAML` for `tests/test_action_yml.py` | `make install` and CI | +| `requirements.txt` | Runtime only: the `living-doc-utilities==0.8.0` pin, then `./packages/core`, `./packages/services/normalize_docs`, `./packages/services/normalize_tests`, `./packages/services/coverage_matrix`, `./apps/cli`, in dependency order | `action.yml`, into its venv, on every run; a plain install, above | +| `requirements-dev.txt` | `-r requirements.txt`, the gate and test tools pinned, and `PyYAML` for the root tests | `make install` and CI | -- Each package declares its own runtime dependencies in its `pyproject.toml`; `requirements.txt` - names the packages, not their dependencies, with one exception: the `living-doc-utilities` pin. +- Each package declares its own runtime dependencies in its `pyproject.toml`; `requirements.txt` names the packages, not their dependencies, with one exception: the `living-doc-utilities` pin. - Every package declares `living-doc-utilities==0.8.0`, from PyPI, as each imports it; one pin, the same in each. - `requirements.txt` pins the same version, so the file the action installs names the contract version it runs on. -- Every package declares the toolkit's one version, `0.1.0`, the version the action's release tag carries. - - `metadata.producer.version` of a file the toolkit writes is read from the installed `living-doc-core`, so a - release changes every package's version together. -- The development tools live in both places. Each package's `[dev]` extra keeps the ranges that - package needs on its own; `requirements-dev.txt` pins the versions `make install` and CI use. -- Run both installs from the repository root: pip reads `./packages/...` against the directory - it runs in, not against the file. -- `requirements.txt` carries no `-e`. `make install` gives the editable install for development; - the action and the Quickstart need a plain one. - - Why the order in `make install`: a later `pip install -r requirements.txt` would reinstall the - packages non-editable, and one call holding both `./packages/core` and `-e packages/core` - does not resolve. -- A plain install builds each package in place and leaves a `build/` directory beside its `src/`. - Each package's mypy `exclude` skips `build/`. +- Every package declares the toolkit's one version, `0.1.0`, the version the action's release tag carries → `packages/core/src/living_doc_core/__init__.py::__version__` + - `metadata.producer.version` of a file the toolkit writes is read from the installed `living-doc-core`, so a release changes every package's version together. +- The development tools live in both places. Each package's `[dev]` extra keeps the ranges that package needs on its own; `requirements-dev.txt` pins the versions `make install` and CI use. +- `requirements.txt` carries no `-e`. `make install` gives the editable install for development; the action and a plain install need a plain one. + - Why the order in `make install`: a later `pip install -r requirements.txt` would reinstall the packages non-editable. + - Why two calls: one call holding both `./packages/core` and `-e packages/core` does not resolve. +- A plain install builds each package in place and leaves a `build/` directory beside its `src/`; each package's mypy `exclude` skips it → `packages/core/pyproject.toml::build/` - Why: `make types-` runs `mypy .`, which would otherwise find every module twice. -- A new package under `packages/` or `apps/` goes into `requirements.txt` after the packages it - depends on, and a development tool goes into `requirements-dev.txt`, never `requirements.txt`. - - Why: the toolkit's own packages are not on PyPI, so a package that depends on an unlisted one fails - the install with "No matching distribution found"; and the action installs `requirements.txt` on - every run. -- Dependabot covers both files from its `pip` entry on `/`, beside one entry per package - directory, in `.github/dependabot.yml`. - - Each package entry, and the root entry, ignores `living-doc-utilities`: its pin moves by hand, in every package - and in the root `requirements.txt` in one change. - - Why: an entry updates its own directory only, so its bump would move one pin, and the five packages would - no longer install together. A new `living-doc-utilities` is also a contract change, made in its own issue. - -## Monorepo Layout - -| Path | Package | Description | +- A new package under `packages/` or `apps/` goes into `requirements.txt` after the packages it depends on. + - Why: the toolkit's packages are not on PyPI; a package depending on an unlisted one fails with "No matching distribution found". +- A development tool goes into `requirements-dev.txt`, never `requirements.txt`. + - Why: the action installs `requirements.txt` on every run. +- Dependabot covers both files from its `pip` entry on `/`, beside one entry per package directory → `.github/dependabot.yml::pip-root` + - Each package entry, and the root entry, ignores `living-doc-utilities`: its pin moves by hand, in every package and in the root `requirements.txt`, in one change. + - Why: an entry updates only its own directory, so its bump moves one pin and the five packages stop installing together. + - A new `living-doc-utilities` is also a contract change, made in its own issue. + +## Layout + +| Path | Package | What it holds | |---|---|---| | `packages/core` | `living-doc-core` | Logging, the shared envelope, reader table, input validation, view filter and CI run context ([README](packages/core/README.md)) | | `packages/services/normalize_docs` | `living-doc-service-normalize-docs` | The `normalize-docs` transform and its lineage table ([README](packages/services/normalize_docs/README.md)) | | `packages/services/normalize_tests` | `living-doc-service-normalize-tests` | The `normalize-tests` transform and its lineage table ([README](packages/services/normalize_tests/README.md)) | | `packages/services/coverage_matrix` | `living-doc-service-coverage-matrix` | The `coverage-matrix` transform and its lineage table over both inputs ([README](packages/services/coverage_matrix/README.md)) | -| `apps/cli` | `living-doc-cli` | CLI entry point (`living-doc` command group); registers `normalize-docs`, `normalize-tests` and `coverage-matrix` ([README](apps/cli/README.md)) | - -- A package directory `packages//` is named by the group's singular and its name: the distribution - `living-doc--`, with `-` for `_`, and the import `living_doc__`: - `packages/services/normalize_docs` is `living-doc-service-normalize-docs`, imported as - `living_doc_service_normalize_docs`. - - The packages ship only inside the action's release, never on PyPI. The action installs them into its own venv - (`action.yml`), where only the import packages land, beside `living_doc_utilities` and its dependencies; the - `living_doc_` prefix keeps them apart there, and `` is this repository's convention. -- Each command's transform is a package of its own under `packages/services/`, with its lineage table in it. -- The toolkit's packages ship no `py.typed`, so a package that imports another reads its annotations through a - mypy `follow_untyped_imports` override in its `pyproject.toml`, as the root does for `living_doc_core`. - -Each package has its own `pyproject.toml`, `src/` layout, and `tests/` directory. -All quality-gate commands below are designed to run **per-package** from within the -package directory, matching how CI executes them. - -The repository root holds the action: `action.yml`, its entry point `main.py`, and the root -`tests/` for both. The root gates run from the repository root with the root `pyproject.toml` -(see [Root checks](#root-checks)). - -`tests/fixtures/collector-gh/` holds the one copy of `living-doc`'s example `doc-source.json` and -`ui-tests.json`, with their source noted beside them; every package's tests and `integration.yml` -read it. `tests/fixtures/utilities-golden/` holds `living-doc-utilities`' six golden entities, which -`packages/services/normalize_docs`' tests read. - -## QA Automation with Make - -This project includes a `Makefile` exposing the shared **command vocabulary** -(the fleet-wide `repo-conventions.md` §7 "Command vocabulary"). -Use the Make targets instead of manual loops — `.github/workflows/test.yml` runs the same -targets, so local and CI never drift. - -### Quick Start - -First, install all packages with dev dependencies: - -```shell -make install -``` - -Then run every QA gate (`format-check` → `lint` → `types` → `deptry` → `test`) on all packages, -then the [root checks](#root-checks), then `no-vendored-schemas`: - -```shell -make qa -``` - -Show all available commands: - -```shell -make help -``` - -### Canonical targets - -Eight package targets, each with an **aggregate** form (every package) and a **per-package** -form `-`, and one repository-wide target. - -| Target | Runs | Gate | -|--------|------|------| -| `qa` | `format-check` → `lint` → `types` → `deptry` → `test` per package, then `qa-root`, then `no-vendored-schemas`, failing on the first | all of the below | -| `lint` | Pylint over tracked `*.py` (tests excluded) | score ≥ **9.5** / 10 | -| `format` | Black, rewriting files | line length **120** | -| `format-check` | Black `--check` (no writes) | line length **120** | -| `types` | mypy | clean | -| `deptry` | deptry, per package (no root form) | no undeclared, unused or transitive dependency | -| `test` | pytest | pass | -| `coverage` | pytest with coverage measurement | `--cov-fail-under=80` | -| `no-vendored-schemas` | R12 check 1 over the whole repository (no per-package form) | no tracked `*-schema.json` / `*.schema.json` outside `tests/` | - -### Run QA for a specific package - -Package aliases: `core`, `normalize-docs`, `normalize-tests`, `coverage-matrix`, `cli`. - -```shell -make qa-core # all gates for packages/core -make qa-normalize-docs # packages/services/normalize_docs -make qa-normalize-tests # packages/services/normalize_tests -make qa-coverage-matrix # packages/services/coverage_matrix -make qa-cli # apps/cli -``` - -Run one gate on one package: - -```shell -make format-core # Black on packages/core -make lint-core # Pylint on packages/core -make types-core # mypy on packages/core -make deptry-core # deptry on packages/core -make test-core # tests on packages/core -make coverage-core # tests + coverage gate on packages/core -``` - -### Root checks - -`make qa-root` runs the gates over the root's own files. `make qa` runs it after the packages, -and each aggregate target (`make lint`, `make test`, …) except `deptry` includes its `-root` form. - -| Target | Runs | Gate | -|---|---|---| -| `format-check-root` / `format-root` | Black over `main.py`, `tools/` and `tests/` | line length **120** | -| `lint-root` | ruff over `main.py`, `tools/` and `tests/`, then Pylint over `main.py` and `tools/` | score ≥ **9.5** / 10 | -| `types-root` | mypy over `main.py` and `tools/` | clean | -| `test-root` / `coverage-root` | pytest over `tests/`, measuring `main.py` and `tools/` | ≥ **80 %** | - -### Deprecated target names - -The pre-Phase-0 names still work for one release and print a deprecation notice: -`py-qa` → `qa`, `black` → `format`, `pylint` → `lint`, `mypy` → `types`, -`pytest-unit` → `test`, and their `-` per-package forms. - -## Running Static Code Analysis - -This project uses [Pylint](https://pylint.readthedocs.io/) for static code analysis. -Pylint displays a global evaluation score rated out of 10.0. We aim to keep our code -quality at or above **9.5**. - -Each package's `pyproject.toml` configures Pylint. CI runs Pylint per-package and -excludes test files. - -### Run Pylint - -One package (`make lint-`) or all of them (`make lint`): - -```shell -make lint-core # wraps: cd packages/core && pylint --fail-under=9.5 $(git ls-files '*.py' | grep -v '^tests/') -make lint # every package -``` - -To run Pylint on a specific file, from inside the package directory: - -```shell -cd packages/core && pylint src/living_doc_core/input_validation.py -``` - -## Run Black Tool Locally - -This project uses [Black](https://github.com/psf/black) for code formatting. -Line length is set to **120 characters** (configured in each package's `pyproject.toml`). - -### Run Black - -Rewrite one package (`make format-`) or all of them (`make format`): - -```shell -make format-core # wraps: cd packages/core && black . -make format # every package -``` +| `apps/cli` | `living-doc-cli` | The `living-doc` command group; registers `normalize-docs`, `normalize-tests` and `coverage-matrix` ([README](apps/cli/README.md)) | -### Check-only mode (no changes) - -```shell -make format-check-core # wraps: cd packages/core && black --check . -make format-check # every package -``` - -### Expected Output -``` -All done! ✨ 🍰 ✨ -1 file reformatted. -``` - -## Run mypy Tool Locally - -This project uses [mypy](https://mypy.readthedocs.io/en/stable/) for static type -checking. Configuration is in each package's `pyproject.toml`. - -### Run mypy - -One package (`make types-`) or all of them (`make types`): - -```shell -make types-core # wraps: cd packages/core && mypy . -make types # every package -``` - -Configuration (`[tool.mypy]` in each `pyproject.toml`) sets `python_version = "3.10"` — the -supported floor — so type checks catch 3.11+-only stdlib use. - -## Dependency and Contract Checks - -```shell -make deptry-core # wraps: cd packages/core && python -m deptry . -make deptry # every package -make no-vendored-schemas # wraps: python -m living_doc_utilities.contracts.check_no_vendored_schemas -``` - -- `deptry` fails on an import its package does not declare, on a declared dependency nothing imports, and on an - import that only arrives through another package. Each package configures it under `[tool.deptry]`: - `optional_dependencies_dev_groups = ["dev"]`, because the `[dev]` extra holds only tools, and `known_first_party`, - its own `src/` package. - - It has no root form: the root declares no package, and `main.py` imports only the standard library and - `living_doc_core`, which `requirements.txt` installs. -- `no-vendored-schemas` is R12 check 1 of `living-doc-utilities`' [component checks](https://github.com/AbsaOSS/living-doc-utilities/blob/master/docs/contracts/component-checks.md): - no repository but `living-doc-utilities` commits a contract schema. It reads git's index, so a file not yet added - is not seen, and it skips anything under a `tests/` directory. - - Why: the contract models and schemas come from the installed `living-doc-utilities`; a committed copy stops - tracking it, and nothing reports that. -- R12 check 2, one read path and one write path, is the code's: every read goes through `read_artifact` and returns a - contract model ([core README](packages/core/README.md#the-reader-table)), and every write goes through - `write_artifact` from a contract model ([normalize-docs README](packages/services/normalize_docs/README.md#what-a-run-does), - [normalize-tests README](packages/services/normalize_tests/README.md#what-a-run-does), - [coverage-matrix README](packages/services/coverage_matrix/README.md#what-a-run-does)). -- R12 check 3 and R11 are each transform's own tests: the full-sample test, and `assert_complete` over its lineage - table ([normalize-docs README](packages/services/normalize_docs/README.md#lineage), - [normalize-tests README](packages/services/normalize_tests/README.md#lineage), - [coverage-matrix README](packages/services/coverage_matrix/README.md#lineage)). - -## Running Unit Tests - -Unit tests are written using [pytest](https://docs.pytest.org/) and live in each -package's `tests/` directory. - -### Run tests - -One package (`make test-`) or all of them (`make test`): - -```shell -make test-core # wraps: cd packages/core && pytest tests/ -make test # every package -``` - -`make coverage` / `make coverage-` adds the explicit `--cov=src --cov-fail-under=80` -gate that CI enforces. - -## Running Integration Tests - -`.github/workflows/integration.yml` runs the action itself, through `uses: ./`, on real -interpreters ([CI](#ci)). It has no local form: it needs a GitHub runner. - -- Locally, `normalize-docs`, `normalize-tests` and `coverage-matrix` run end to end over their fixtures and - committed goldens, and through the entry point in `tests/test_main.py`. -- Each command's change brings its own end-to-end tests and its step in `action-run`. - -## Code Coverage - -Code coverage is collected with `pytest-cov`. The minimum threshold is **80 %**. - -### Check coverage - -```shell -make coverage-core # wraps: cd packages/core && pytest tests/ --cov=src --cov-fail-under=80 -make coverage # every package -``` - -### Generate HTML report - -Each package's `pyproject.toml` sets `--cov-report=html`, so `make coverage-core` also -writes `packages/core/htmlcov/index.html`: - -```shell -make coverage-core -open packages/core/htmlcov/index.html -``` - -## Run CLI Locally - -After installing all packages (see [Get Started](#get-started)), the `living-doc` -command is available. It registers `normalize-docs`, `normalize-tests` and `coverage-matrix`: +- A package directory `packages//` is named by the group's singular and its name: the distribution `living-doc--`, with `-` for `_`, and the import `living_doc__` → `packages/services/normalize_docs/pyproject.toml::living-doc-service-normalize-docs` + - So `packages/services/normalize_docs` is `living-doc-service-normalize-docs`, imported as `living_doc_service_normalize_docs`. + - The packages ship only inside the action's release, never on PyPI. + - Why the prefix: the packages share the action's venv with `living_doc_utilities` and its dependencies; `living_doc_` keeps them apart. + - `` is this repository's convention. +- Each command's transform is a package of its own under `packages/services/`, with its lineage table in it. +- Each package has its own `pyproject.toml`, `src/` layout and `tests/` directory; its gates run from inside its directory, as CI runs them → `Makefile::dir-core` +- The toolkit's packages ship no `py.typed`, so a package that imports another reads its annotations through a mypy `follow_untyped_imports` override in its `pyproject.toml`, as the root does for `living_doc_core` → `pyproject.toml::follow_untyped_imports` +- The repository root holds the action: `action.yml`, its entry point `main.py`, the local run's `local-integration.sh` and `tools/`, and the root `tests/` for them. The root gates run from the root with the root `pyproject.toml` → `Makefile::ROOT_PY` + +## Gates + +`make qa` runs `format-check`, `lint`, `types`, `deptry` and `test` on every package, then the root's gates (`qa-root`), +then `no-vendored-schemas`, and stops at the first failure → `Makefile::qa`. Run it before opening a pull request. + + +| Target | Runs | Gate | One package | The root | Old name | +|---|---|---|---|---|---| +| `make qa` | the five package gates, then `qa-root`, then `no-vendored-schemas` | every gate below | `qa-` | `qa-root`: every root gate, no `deptry` | `py-qa` | +| `make format` | ruff autofix, then Black, rewriting files | line length 120 | `format-` | `format-root`: `main.py`, `tools/`, `tests/` | `black` | +| `make format-check` | Black in check mode, no writes | line length 120 | `format-check-` | `format-check-root`: `main.py`, `tools/`, `tests/` | — | +| `make lint` | ruff, then Pylint over the tracked `*.py` outside `tests/` | Pylint ≥ 9.5 / 10 | `lint-` | `lint-root`: ruff over `main.py`, `tools/`, `tests/`; Pylint over `main.py`, `tools/` | `pylint` | +| `make types` | mypy, `[tool.mypy]` `python_version = "3.10"` | clean | `types-` | `types-root`: `main.py`, `tools/` | `mypy` | +| `make deptry` | deptry | no undeclared, unused or transitive dependency | `deptry-` | none | — | +| `make test` | pytest over `tests/`; each `pyproject.toml` adds coverage with `pytest-cov` | pass, coverage ≥ 80 % | `test-` | `test-root`: `tests/`, measuring `main.py` and `tools/` | `pytest-unit` | +| `make coverage` | pytest with `--cov=src --cov-fail-under=80` on the command line; `--cov-report=html` writes `/htmlcov/index.html` | coverage ≥ 80 % | `coverage-` | `coverage-root` | — | +| `make no-vendored-schemas` | `living-doc-utilities`' vendored-schema check over the whole repository | no tracked `*-schema.json` or `*.schema.json` outside a `tests/` directory | none | none | — | + +- The aliases are `core`, `normalize-docs`, `normalize-tests`, `coverage-matrix` and `cli` → `Makefile::ALIASES` +- Each aggregate target but `deptry` and `no-vendored-schemas` runs its root form too → `Makefile::ROOT_TARGETS` +- An old name, and its `-` form, prints a deprecation notice and runs the new target; they stay for one release → `Makefile::DEPRECATED` +- `test.yml` runs the same targets, so local runs and CI never drift → `.github/workflows/test.yml::coverage` +- `make help` lists every target → `Makefile::help` +- Black, ruff, Pylint, mypy and pytest read their settings from each package's `pyproject.toml`; one file: `cd packages/core && pylint src/living_doc_core/input_validation.py` → `packages/core/pyproject.toml::tool.pylint` +- mypy checks against the 3.10 floor, so a standard-library use newer than 3.10 fails → `packages/core/pyproject.toml::python_version` +- `deptry` fails on an import its package does not declare, on a declared dependency nothing imports, and on an import that only arrives through another package → `packages/core/pyproject.toml::tool.deptry` + - Each package sets `optional_dependencies_dev_groups = ["dev"]`, because the `[dev]` extra holds only tools, and `known_first_party`, its own `src/` package. + - It has no root form: the root declares no package, and `main.py` imports only the standard library and `living_doc_core`, which `requirements.txt` installs. +- `no-vendored-schemas` is R12 check 1 of `living-doc-utilities`' [component checks](https://github.com/AbsaOSS/living-doc-utilities/blob/master/docs/contracts/component-checks.md): no repository but `living-doc-utilities` commits a contract schema → `Makefile::check_no_vendored_schemas` + - It reads git's index, so a file not yet added is not seen, and it skips anything under a `tests/` directory. + - Why: the contract models and schemas come from the installed `living-doc-utilities`; a committed copy stops tracking it, and nothing reports that. +- R12 check 2, one read path and one write path, is the code's. Every read goes through `read_artifact` and returns a contract model → [The reader table](packages/core/README.md#the-reader-table) + - Every write goes through `write_artifact` from a contract model: each command's § What a run does, [normalize-docs](packages/services/normalize_docs/README.md#what-a-run-does), [normalize-tests](packages/services/normalize_tests/README.md#what-a-run-does), [coverage-matrix](packages/services/coverage_matrix/README.md#what-a-run-does). +- R12 check 3 and R11 are each transform's own tests: the full-sample test, and `assert_complete` over its lineage table → each command's § Lineage, [normalize-docs](packages/services/normalize_docs/README.md#lineage), [normalize-tests](packages/services/normalize_tests/README.md#lineage), [coverage-matrix](packages/services/coverage_matrix/README.md#lineage) +- The page checks in `tests/docs/` are root tests, so `make test` and `make qa` run them → [Checks](#checks) + +## Test data + +- `tests/fixtures/collector-gh/` holds the one copy of `living-doc`'s example `doc-source.json` and `ui-tests.json`, with their source noted beside them; every package's tests and `integration.yml` read it → [README](tests/fixtures/collector-gh/README.md) +- `tests/fixtures/utilities-golden/` holds `living-doc-utilities`' six golden entities, which `packages/services/normalize_docs`' tests read → [README](tests/fixtures/utilities-golden/README.md) +- Each command's golden output is in its package's `tests/expected/`, with how to regenerate it → [normalize-docs](packages/services/normalize_docs/tests/expected/README.md), [normalize-tests](packages/services/normalize_tests/tests/expected/README.md), [coverage-matrix](packages/services/coverage_matrix/tests/expected/README.md) +- Each command runs end to end over the fixtures against its golden, and through the entry point → `tests/test_main.py::test_the_entry_point_runs_coverage_matrix_over_the_fixture_pair_and_sets_the_output_path` +- Each command's change brings its own end-to-end tests and its step in `integration.yml`'s `action-run` → [CI](#ci) + +## CLI run + +After `make install`, `living-doc` runs the commands over the fixtures; every option is in the [CLI reference](apps/cli/README.md). ```shell living-doc --version @@ -366,6 +135,24 @@ living-doc coverage-matrix --doc-input tests/fixtures/collector-gh/doc-source.js --tests-input tests/fixtures/collector-gh/ui-tests.json --output output/coverage-matrix.json --fail-under 50 ``` +## Debug run + +The run of `main.py` as the action runs it, in the development venv, from the repository root after `make install`. +What it checks and the lines it fails with: [Errors](docs/action.md#errors). + +```shell +export INPUT_COMMAND=normalize-docs +export INPUT_INPUT=tests/fixtures/collector-gh/doc-source.json +export INPUT_OUTPUT=output/generator-ready.json +export INPUT_VERBOSE_LOGGING=true # optional: --verbose and DEBUG lines; default false; RUNNER_DEBUG=1 too +python3 main.py # writes output/generator-ready.json +``` + +- Each input is an `INPUT_` variable, upper case with `_` for `-`, as `action.yml` passes it → `action.yml::INPUT_DOCUMENT_TITLE` +- Without `GITHUB_OUTPUT`, as outside GitHub Actions, the run sets no `output-path` output and logs that once → `tests/test_main.py::test_a_run_outside_github_actions_sets_no_output_and_says_so` +- `tests/test_main.py` replaces the CLI to test the run itself: the command line, the exit code passed through, and `output-path` → `tests/test_main.py::test_a_failed_command_ends_the_run_with_its_exit_code_and_sets_no_output` + - It also runs `normalize-docs`, `normalize-tests` and `coverage-matrix` through the entry point over their fixtures, for real. + ## Local Integration Run `local-integration.sh` runs the real toolkit over a given `doc-source.json` and `ui-tests.json`, on the machine that @@ -373,9 +160,7 @@ has them, outside any workflow. It runs the three commands through `main.py` in output with `read_artifact`, and prints what ran and what came out. No workflow runs it; like any local run, it is a development and operator-check affordance, not a second supported deployment target. -- The usual inputs are the two files a `living-doc-collector-gh` run wrote: its - [Local Integration Run](https://github.com/AbsaOSS/living-doc-collector-gh/blob/master/DEVELOPER.md#local-integration-run) - says where they land. The script never derives another component's paths; you configure both files. +- The usual inputs are the two files a `living-doc-collector-gh` run wrote: its [Local Integration Run](https://github.com/AbsaOSS/living-doc-collector-gh/blob/master/DEVELOPER.md#local-integration-run) says where they land. The script never derives another component's paths; you configure both files → `.env.local.example::DOC_INPUT` ### What You Prepare @@ -425,7 +210,7 @@ A run, in order: - It follows [Action venv](#action-venv): the same six variables unset, the same import check after the install, and `main.py` run with `-E`. - The venv is reused only when the same interpreter built it from the same `requirements.txt`. 3. **Prints what runs**: this repository's commit, the installed `living-doc-utilities` version, the Python version, both inputs, the view and the output directory. The commit is marked when tracked files differ from it. -4. **Runs `normalize-docs`, `normalize-tests`, then `coverage-matrix`**, each as its own `main.py` run with only the inputs it takes ([The entry point](#the-entry-point)), into `$OUT_DIR/toolkit//`, having cleared the three directories first. Each command is echoed before it runs. +4. **Runs `normalize-docs`, `normalize-tests`, then `coverage-matrix`**, each as its own `main.py` run with only the inputs it takes ([Inputs](docs/action.md#inputs)), into `$OUT_DIR/toolkit//`, having cleared the three directories first. Each command is echoed before it runs. - A command that fails stops the run with one line naming it, after the toolkit's own error lines. A `DOC_INPUT` that is not a `doc-source` artifact stops `normalize-docs` with `[CONTRACT_MISMATCH]`. 5. **Validates and reports** each output → `tools/local_integration.py::report` - It re-reads the output with `read_artifact` against its contract, then prints its producer, run time, size and `metadata.stats.cardinality`, and for `coverage-matrix` the root `summary`. @@ -440,185 +225,150 @@ A run, in order: ## Action venv -`action.yml` runs the toolkit in a venv of its own, built from the caller's Python. The rules -are `living-doc-generator-markdown`'s; what differs here follows them. - -- The action uses the Python on `PATH`; the caller prepares it, for example with `actions/setup-python`. -- Below 3.10 it fails before creating anything, with one line: - `::error::living-doc-toolkit needs Python >= 3.10 on PATH. Add actions/setup-python before this step.` -- It installs `requirements.txt` into `$RUNNER_TEMP/living-doc-toolkit-venv`. It never changes the - caller's interpreter, `PATH` or environment, and writes nothing to `$GITHUB_ENV` or `$GITHUB_PATH`. -- `PIP_TARGET`, `PIP_PREFIX`, `PIP_USER`, `PIP_ROOT`, `PIP_PYTHON` and `PYTHONPATH` are unset before - the install. - - Why: pip reads them as configuration, so a caller exporting one would empty or shadow the venv, - or (`PIP_PYTHON`) install into the caller's own interpreter. -- The venv is reused only when the interpreter now on `PATH` built it, compared as `sys.base_prefix` - plus version; otherwise it is rebuilt. -- After the install, one import of `living_doc_cli.main` through the venv's interpreter proves the - packages landed there. Its failure is one line pointing at the runner's pip configuration. -- `main.py` runs with `-E`, so a caller's `PYTHONPATH` cannot shadow the venv. Every input reaches it - through the run step's `env:`; no `run:` script holds `${{ inputs.* }}`. -- `main.py` runs the CLI with `-I`, which is `-E` that also keeps the working directory off `sys.path`. - - Why: the run step runs in the caller's checkout, and `python -m` searches the working directory - first, so a `click.py` or `json.py` there would replace the venv's module. -- Linux runners only: the venv's POSIX `bin/` layout is assumed. -- `tests/test_action_yml.py` runs the venv step against a fake interpreter. `integration.yml` shows - the reuse, the rebuild on an interpreter change, and the rejection of Python 3.9 on real ones - ([CI](#ci)). +`action.yml` runs the toolkit in a venv of its own, built from the caller's Python. The rules are +`living-doc-generator-markdown`'s; what differs here follows them. What the caller provides, and the lines the step +fails with: [Prerequisites](docs/action.md#prerequisites), [Errors](docs/action.md#errors). -What differs from `living-doc-generator-markdown`: - -- The install and the import check run from the action's own directory, `$GITHUB_ACTION_PATH`. - - Why: `requirements.txt` names `./packages/...`, which pip reads against the directory it runs - in; from the caller's workspace the paths do not exist. - - Why the variable, not `${{ github.action_path }}`: in a `container:` job the expression gives - the runner host's path, which does not exist inside the container. -- The import check imports `living_doc_cli.main`, which imports `click`, the CLI package and each - command it registers, with the packages those commands import: the three services, - `living-doc-core` and `living-doc-utilities`. -- pip rebuilds a local directory on every install, so each run reinstalls the five packages into the - reused venv; their dependencies stay installed. - -### The entry point - -`main.py` checks the inputs, then runs `python -I -m living_doc_cli.main --