diff --git a/.quality-baseline.json b/.quality-baseline.json index 23939c1a..90d4e332 100644 --- a/.quality-baseline.json +++ b/.quality-baseline.json @@ -15,7 +15,7 @@ "verb": "make typecheck" }, "markdownlint": { - "count": 680, + "count": 647, "what": "markdownlint-cli2 issues over every tracked Markdown file (the DOCS_LINT_PATHS ratchet is a subset and clean)", "command": "git ls-files -z '*.md' | xargs -0 npx markdownlint-cli2@", "verb": "make docs-lint" diff --git a/AGENTS.md b/AGENTS.md index 131f0b63..619b7fc4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -113,7 +113,9 @@ reports, commit messages, pull request descriptions and docs. | `artefacts/` | Scratch notes and plans. The folder is tracked, its contents are git-ignored. | Off-limits. Do not read, search or list it unless a human gives you a file path. | Decisions (ADRs) live beside the component they govern (root ADR 0001). The root index, -`docs/architecture/adrs/README.md`, lists all of them. +`docs/architecture/adrs/README.md`, lists all of them. **Every ADR you add, change or delete follows +`docs/ADR_POLICY.md`.** Git is the archive, so an ADR states only the current decision. Overwrite a changed +decision in place. Delete a superseded one, and never refer to it again. A folder with no files yet is not written yet; the docs page that describes it says `Planned`. @@ -147,6 +149,7 @@ A change is done when all of this holds: - A change can alter a public interface, observable behaviour, the architecture or a decision. Then update the affected documentation and the decision records in the same change set. When you fill in a page marked `Planned`, remove that note and update the doc router. +- An ADR you added or changed follows `docs/ADR_POLICY.md` and has its row in the ADR index. - Python files carry the SPDX header `AGPL-3.0-or-later` and the copyright line. Documentation, schemas and published outputs are `GFDL-1.3-only`. The zones are in `docs/reference/licensing.md`. - Commits follow Conventional Commits, one logical change each, so any one can be reverted alone. @@ -160,7 +163,7 @@ A change is done when all of this holds: `docs/how-to/contribute-a-change.md`. - How to write for people: `docs/COMMUNICATION_RULES.md`. - Documentation index, style guide and decisions of record: `docs/README.md`, `docs/STYLE_GUIDE.md`, - `docs/architecture/adrs/README.md`. + `docs/architecture/adrs/README.md`. The ADR rules: `docs/ADR_POLICY.md`. - Requirements: `spec/README.md`, and `spec/readiness-requirements.md`. - What an audit checks and what an implement run changes, for a reader: `docs/reference/audit-master.md` and `docs/reference/implement-master.md`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e257ce24..532e1cf1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,7 +126,7 @@ published outputs are `GFDL-1.3-only`. The zones are in Write in plain language ([`docs/COMMUNICATION_RULES.md`](docs/COMMUNICATION_RULES.md)). Follow [`docs/STYLE_GUIDE.md`](docs/STYLE_GUIDE.md). Start at the documentation index, [`docs/README.md`](docs/README.md). A page describes what exists; a feature that is only planned is marked -`Planned`. Decisions go in an ADR ([index](docs/architecture/adrs/README.md)). +`Planned`. Decisions go in an ADR ([index](docs/architecture/adrs/README.md), [policy](docs/ADR_POLICY.md)). ## Reporting an engine defect diff --git a/Makefile b/Makefile index 66f0e682..2620d757 100644 --- a/Makefile +++ b/Makefile @@ -69,7 +69,7 @@ COVERAGE_DATA := $(CURDIR)/dev/dist/coverage/.coverage # The Markdown that `docs-lint` holds to zero violations. A ratchet: a path joins once it is clean, and the list # only grows. Everything else is reported by `docs-lint-all` and never fails. DOCS_LINT_PATHS := 'implement/SKILL.md' 'implement/state.md' 'implement/playbooks/*.md' 'implement/prompts/*.md' \ - 'docs/README.md' 'docs/STYLE_GUIDE.md' 'docs/architecture/**/*.md' 'docs/reference/branch-protection.md' 'docs/reference/dev-tooling.md' 'docs/reference/audit-master.md' 'docs/reference/implement-master.md' 'docs/how-to/contribute-a-change.md' 'docs/how-to/set-up-agent-tools.md' 'docs/COMMUNICATION_RULES.md' 'CONTRIBUTING.md' 'CHANGELOG.md' 'RELEASE_PROCESS.md' 'SECURITY.md' \ + 'docs/README.md' 'docs/STYLE_GUIDE.md' 'docs/architecture/**/*.md' 'docs/reference/branch-protection.md' 'docs/reference/dev-tooling.md' 'docs/reference/audit-master.md' 'docs/reference/implement-master.md' 'docs/how-to/contribute-a-change.md' 'docs/how-to/set-up-agent-tools.md' 'docs/COMMUNICATION_RULES.md' 'docs/ADR_POLICY.md' 'docs/templates/adr-template.md' 'CONTRIBUTING.md' 'CHANGELOG.md' 'RELEASE_PROCESS.md' 'SECURITY.md' \ 'docs/explanation/user-overview/03-trust-and-safety.md' 'docs/explanation/contributor-deep-dive/04-stack-profiles.md' 'docs/explanation/contributor-deep-dive/05-evaluation.md' 'docs/how-to/add-a-stack-profile.md' 'docs/reference/harness-notes.md' \ 'audit/engine/docs/how-to/read-the-report.md' 'docs/reference/versions.md' 'docs/how-to/implement-an-audit-report.md' \ 'docs/how-to/resume-an-interrupted-run.md' 'docs/glossary.md' 'AGENTS.md' 'audit/prompts/talking-to-the-user.md' \ diff --git a/audit/AGENTS.md b/audit/AGENTS.md index 758feec7..418b3c84 100644 --- a/audit/AGENTS.md +++ b/audit/AGENTS.md @@ -43,6 +43,7 @@ To audit a repository, follow [`SKILL.md`](SKILL.md). The rest of this file is f - The tool design: the [Tool Contract](../docs/explanation/contributor-deep-dive/01-tool-contract.md) page. - All decisions: the root index, [`docs/architecture/adrs/README.md`](../docs/architecture/adrs/README.md). +- How to add, change or delete an ADR: [`docs/ADR_POLICY.md`](../docs/ADR_POLICY.md). ## Test this folder diff --git a/audit/docs/architecture/adrs/0001-tools-import-engine.md b/audit/docs/architecture/adrs/0001-tools-import-engine.md index 61a7a550..93eeecf7 100644 --- a/audit/docs/architecture/adrs/0001-tools-import-engine.md +++ b/audit/docs/architecture/adrs/0001-tools-import-engine.md @@ -1,32 +1,21 @@ # 0001. Tools Import the Engine in Process -* **Status:** Accepted -* **Date:** 2026-10-10 -* **Last reviewed:** 2026-11-07 -* **Deciders:** [@nistapp] - -> **Paths updated (v0.3, 2026-11-07):** the decision stands. The text keeps the paths of its date: -> `brownfield-recipes/tools/` is now `audit/tools/`, and `deterministic-audit/` is now `audit/engine/`. -> The "Moved" table in the root [`CHANGELOG.md`](../../../../CHANGELOG.md) maps every old path. - ---- - ## Context -The recipes' tools and the deterministic engine are both standard-library Python, and the tools need the engine in two ways. `tools/engine.py` must run it from any working directory with one command. Later tools need pieces of it rather than a whole run: the validator wants the engine's secret-redaction patterns (S2.2), and the facts tool wants its file inventory (S2.7). +The recipes' tools and the deterministic engine are both standard-library Python, and the tools need the engine in two ways. `tools/engine.py` must run it from any working directory with one command. Other tools need pieces of it rather than a whole run: the validator wants the engine's secret-redaction patterns, and the facts tool wants its file inventory. Four constraints bind the choice: - One copy of each module. A second copy of the engine's code would drift from the first. -- No install step. A fresh clone must run `python3 brownfield-recipes/tools/.py` at once, because an audit starts on a machine where nothing is set up. +- No install step. A fresh clone must run `python3 audit/tools/.py` at once, because an audit starts on a machine where nothing is set up. - The engine's behaviour and tests do not change for the sake of the tools. - Standard library only, in both. -The engine is a package named `audit` in [`deterministic-audit/`](../../../engine/audit/__init__.py). Its entry point, [`main(argv)`](../../../engine/audit/__main__.py), takes the arguments explicitly and returns the exit code, which [ADR-0005 of the engine](../../../engine/docs/architecture/adrs/0005-module-layout-and-zipapp-distribution.md) keeps stable. +The engine is a package named `audit` in [`audit/engine/`](../../../engine/audit/__init__.py). Its entry point, [`main(argv)`](../../../engine/audit/__main__.py), takes the arguments explicitly and returns the exit code, which [ADR-0005 of the engine](../../../engine/docs/architecture/adrs/0005-module-layout-and-zipapp-distribution.md) keeps stable. ## Decision -A tool imports the engine in process. [`load_engine`](../../../tools/engine.py#L57-L66) puts `deterministic-audit/` on `sys.path`, resolved from the location of `engine.py`, and imports `audit.__main__`. It then refuses to go on if the `audit` package it got did not come from that folder, so a different package called `audit` that happens to be installed cannot be run in its place. +A tool imports the engine in process. [`load_engine`](../../../tools/engine.py#L57-L66) puts `audit/engine/` on `sys.path`, resolved from the location of `engine.py`, and imports `audit.__main__`. It then refuses to go on if the `audit` package it got did not come from that folder, so a different package called `audit` that happens to be installed cannot be run in its place. - [`tools/engine.py`](../../../tools/engine.py) calls the engine's `main(argv)` through [`run_engine`](../../../tools/engine.py#L69-L83), with its stdout and stderr captured, so the launcher's own stdout stays a single envelope. - A tool that needs engine internals calls `engine.load_engine()` first and imports from `audit` after it, so that the path and the guard are always the same. @@ -35,10 +24,10 @@ A tool imports the engine in process. [`load_engine`](../../../tools/engine.py#L ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | Run the engine as a subprocess (`python3 -m audit`) | It isolates the engine's module-level state and gives clean exit codes. But it cannot hand over internals, so the validator and the facts tool would need a second mechanism beside it. Two mechanisms for one engine is worse than one. | -| A shared `common/` package that both depend on (the design plan's fallback) | Nothing needs it: the import works, and a shared package would move engine code that has no reason to move. | +| A shared `common/` package that both depend on | Nothing needs it: the import works, and a shared package would move engine code that has no reason to move. | | Install the engine, or run the `dist/audit.pyz` build | Both add a step before the first audit. The zipapp also needs a build and goes stale against the source. | | Copy the engine's modules into `tools/` | Two copies drift. | diff --git a/audit/engine/AGENTS.md b/audit/engine/AGENTS.md index 4c19bfe3..0fd5079e 100644 --- a/audit/engine/AGENTS.md +++ b/audit/engine/AGENTS.md @@ -216,9 +216,8 @@ python3 build.py && ./dist/audit.pyz --list-checks # the si - **Doc router:** `docs/architecture/README.md` is the entry point and the ADR index. - **Categories (Diátaxis):** tutorial, how-to, reference, explanation; architecture material lives under `docs/architecture/`. -- **ADRs:** `docs/architecture/adrs/NNNN-short-title.md`, created from - [`../docs/templates/adr-template.md`](../../docs/templates/adr-template.md), indexed in `docs/architecture/README.md`. Revise in place — there is no - `Superseded` or `Deprecated` status, no tombstones, and a retired sequence number is never reused. +- **ADRs:** `docs/architecture/adrs/NNNN-short-title.md`, indexed in `docs/architecture/README.md`. Read + [`../docs/ADR_POLICY.md`](../../docs/ADR_POLICY.md) before you add, change or delete one. - **Definition of done:** when a change alters a public interface, observable behaviour, architecture, a check's semantics, or an ADR, update the affected documentation pages, their source line anchors, and the ADR index **in the same change set**. Pure internal refactors with no documentation impact are exempt. @@ -255,7 +254,7 @@ python3 build.py && ./dist/audit.pyz --list-checks # the si 9. ❌ **Do not** restate the shared style guide (`../docs/STYLE_GUIDE.md`) or the readiness framework — link to them. 10. ❌ **Do not** document a check as implemented while it is only in the catalogue. 11. ❌ **Do not** merge a check without its one-defect fixture (no fixture, no check). -12. ❌ **Do not** reuse a retired ADR number, or leave a `Superseded` / `Deprecated` tombstone. +12. ❌ **Do not** add, change or delete an ADR without following [`../docs/ADR_POLICY.md`](../../docs/ADR_POLICY.md). 13. ❌ **Do not** weaken, `.skip`, or comment out a failing test to get green (the CI skip in §6 is the one exception). 14. ❌ **Do not** push, `git init`, or commit unless KC asks or your session's instructions say to commit. 15. ❌ **Do not** verify work with an ad-hoc command; use the sanctioned surface in §3.1. diff --git a/audit/engine/docs/architecture/README.md b/audit/engine/docs/architecture/README.md index 9559a759..e229bffd 100644 --- a/audit/engine/docs/architecture/README.md +++ b/audit/engine/docs/architecture/README.md @@ -12,17 +12,17 @@ ## ADR Index -| # | Title | Status | Date | -|---|---|---|---| -| [0001](adrs/0001-report-not-a-gate.md) | The Audit Reports; It Does Not Gate | Accepted | 2026-09-28 | -| [0002](adrs/0002-read-only-and-tier-c-opt-in.md) | Read-Only by Default; Tier-C Probes Are Opt-In | Accepted | 2026-09-28 | -| [0003](adrs/0003-component-depth-declared-depth-1.md) | Component Depth: Declared Components, Depth 1 | Accepted | 2026-09-28 | -| [0004](adrs/0004-no-llm-in-v1.md) | No LLM Calls in v1 | Accepted | 2026-09-28 | -| [0005](adrs/0005-module-layout-and-zipapp-distribution.md) | Module Layout and Zipapp Distribution | Accepted | 2026-09-28 | -| [0006](adrs/0006-private-check-helpers-and-ignore-engine.md) | Private Check Helpers and the Ignore Engine | Accepted | 2026-09-28 | -| [0007](adrs/0007-json-report-render-contract.md) | The JSON Report Is the Render Contract; the Markdown Is a Template Over It | Accepted | 2026-09-30 | -| [0008](adrs/0008-what-counts-as-the-repository.md) | What Counts as the Repository | Accepted | 2026-11-05 | -| [0009](adrs/0009-nested-ignore-files-and-negation.md) | Nested Ignore Files and Negation | Accepted | 2026-11-05 | +| # | Title | Summary | +|---|---|---| +| [0001](adrs/0001-report-not-a-gate.md) | The Audit Reports; It Does Not Gate | A completed scan exits 0 whatever it finds; `--fail-on` exists but is off by default. | +| [0002](adrs/0002-read-only-and-tier-c-opt-in.md) | Read-Only by Default; Tier-C Probes Are Opt-In | The engine never writes to or installs into the target; Tier-C probes run only on explicit request. | +| [0003](adrs/0003-component-depth-declared-depth-1.md) | Component Depth: Declared Components, Depth 1 | Only components a build manifest declares are scored, and only at depth 1. | +| [0004](adrs/0004-no-llm-in-v1.md) | No LLM Calls in v1 | The engine is deterministic and standard-library only, with no model calls. | +| [0005](adrs/0005-module-layout-and-zipapp-distribution.md) | Module Layout and Zipapp Distribution | The package tree is the source; a zipapp is the distributable; nothing needs installing. | +| [0006](adrs/0006-private-check-helpers-and-ignore-engine.md) | Private Check Helpers and the Ignore Engine | Checks share two private helpers, `audit/ignore.py` and `rules/checks/_common.py`; no new stage or pack. | +| [0007](adrs/0007-json-report-render-contract.md) | The JSON Report Is the Render Contract; the Markdown Is a Template Over It | The JSON report (schema v2) is the contract; the Markdown report is a code template over it. | +| [0008](adrs/0008-what-counts-as-the-repository.md) | What Counts as the Repository | Runners rank depth first; templates, examples and fixtures are set aside, and the report lists them. | +| [0009](adrs/0009-nested-ignore-files-and-negation.md) | Nested Ignore Files and Negation | Four checks use a scoped matcher that honours nested `.gitignore` files and negation; the walk is unchanged. | ## Key Documents @@ -61,7 +61,8 @@ | [Wire the ratchet into CI](../how-to/wire-the-ratchet-into-ci.md) | Fail only on regression, never on the first scan | Published (design) | > [!TIP] -> When adding a new ADR, assign the next sequence number and add a row to the index table above before merging. +> Before you add, change or delete an ADR, read [`ADR_POLICY.md`](../../../../docs/ADR_POLICY.md). Add its row to this +> table and to the root index in the same change. ## Framework Dependencies diff --git a/audit/engine/docs/architecture/adrs/0001-report-not-a-gate.md b/audit/engine/docs/architecture/adrs/0001-report-not-a-gate.md index 9e6845f5..2ac1b1fe 100644 --- a/audit/engine/docs/architecture/adrs/0001-report-not-a-gate.md +++ b/audit/engine/docs/architecture/adrs/0001-report-not-a-gate.md @@ -1,12 +1,5 @@ # 0001. The Audit Reports; It Does Not Gate -* **Status:** Accepted -* **Date:** 2026-09-28 -* **Last reviewed:** 2026-09-28 -* **Deciders:** [@nistapp] - ---- - ## Context The tool exists to tell a team what to improve so that AI coding agents can work effectively in an existing @@ -24,8 +17,6 @@ used, whether a baseline is adequate, whether a suite is trustworthy. A verdict unverifiable properties into one pass/fail number would manufacture exactly the false confidence the framework warns about. ---- - ## Decision The audit **reports and never gates**: @@ -46,11 +37,9 @@ The audit **reports and never gates**: Implemented by the exit-code and scoring stages in `audit/evaluate.py` (`AuditResult.exit_code`); see [The Readiness Model](../user-overview/02-the-readiness-model.md) § 8. ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | Fail the build on any Phase-1 gap (a true gate) | Unfixable in one change set; the predictable outcome is a disabled check, which is worse than no check. | | No exit-code signalling at all — report only, always `0` | Removes the ratchet, and with it the only reason to re-run the audit after the first engagement. Debt would regress invisibly. | @@ -58,8 +47,6 @@ Implemented by the exit-code and scoring stages in `audit/evaluate.py` (`AuditRe | A pull-request comment bot only, no machine-readable output | Same report, but it withholds the JSON that the remediation pass consumes and makes regression diffing impossible. | | Count `UNKNOWN` as a pass so the score stays clean | Converts missing evidence into apparent quality — the precise defect the framework calls false confidence (R8). | ---- - ## Consequences ### Positive @@ -76,13 +63,3 @@ Implemented by the exit-code and scoring stages in `audit/evaluate.py` (`AuditRe will ask for the gate anyway, and the correct answer is the repository's own `check` verb, not this tool. * Severity is a judgement encoded in the rule packs; a mis-severity finding is a defect that only human review catches. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into *Alternatives Considered*. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference plus the index table in `docs/architecture/README.md` in the same change set. - -See the [style guide](../../../../../docs/STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/audit/engine/docs/architecture/adrs/0002-read-only-and-tier-c-opt-in.md b/audit/engine/docs/architecture/adrs/0002-read-only-and-tier-c-opt-in.md index 78eb47ce..2e1fbf77 100644 --- a/audit/engine/docs/architecture/adrs/0002-read-only-and-tier-c-opt-in.md +++ b/audit/engine/docs/architecture/adrs/0002-read-only-and-tier-c-opt-in.md @@ -1,12 +1,5 @@ # 0002. Read-Only by Default; Tier-C Probes Are Opt-In -* **Status:** Accepted -* **Date:** 2026-09-28 -* **Last reviewed:** 2026-09-28 -* **Deciders:** [@nistapp] - ---- - ## Context An audit runs inside a repository it does not own, usually one the operator has never run before. Three @@ -23,8 +16,6 @@ produce, and the target may be a production checkout. Third, the audit's own value depends on being safe to run anywhere, including in CI on a pull request from a fork and on a client's machine under change control. A tool that must be trusted before it can be run has already lost. ---- - ## Decision **Static scan by default; execution only by explicit request.** @@ -46,11 +37,9 @@ Implemented by the probe runner in `audit/probes.py` (`ProbeSession.run`) and th see [Scan Engine & Tiering](../contributor-deep-dive/01-scan-engine-and-tiering.md) § 6. ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | Run the target's gates always, for maximum signal | Executes untrusted code on open. Silent code execution is not a scan; it is a remote-execution feature with a report attached. | | Never run probes at all | Loses the highest-signal check in the catalogue — whether the verification loop actually works today (`TST-02`) — which is close to the whole point of the tool. | @@ -58,8 +47,6 @@ see | Install dependencies so probes can run | Mutates the target and defeats the reason the tool is stdlib-only: it must run *before* the project's toolchain exists. | | Redact secrets when rendering the report | Captured output passes through scoring and the JSON model first; rendering-time redaction leaks into structures the renderer does not own. | ---- - ## Consequences ### Positive @@ -76,13 +63,3 @@ see why in its own output rather than assuming the docs were read. * Redaction at capture time is more code than a post-processing pass, and a redaction bug is a security defect rather than a cosmetic one. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into *Alternatives Considered*. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference plus the index table in `docs/architecture/README.md` in the same change set. - -See the [style guide](../../../../../docs/STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/audit/engine/docs/architecture/adrs/0003-component-depth-declared-depth-1.md b/audit/engine/docs/architecture/adrs/0003-component-depth-declared-depth-1.md index 7a6691d5..11397c9f 100644 --- a/audit/engine/docs/architecture/adrs/0003-component-depth-declared-depth-1.md +++ b/audit/engine/docs/architecture/adrs/0003-component-depth-declared-depth-1.md @@ -1,12 +1,5 @@ # 0003. Component Depth: Declared Components, Depth 1 -* **Status:** Accepted -* **Date:** 2026-09-28 -* **Last reviewed:** 2026-09-28 -* **Deciders:** [@nistapp] - ---- - ## Context Phase 1 of the framework requires root **and component-level** `AGENTS.md` files. Component-level governance is @@ -24,8 +17,6 @@ declares a module is the person who would write its `AGENTS.md`. Recursion adds real cost: cycle and symlink handling, duplicate findings for a module of a module, a report that grows faster than its usefulness, and a per-component ratio whose denominator the reader cannot reconstruct. ---- - ## Decision **Score declared components only, at depth 1.** @@ -45,11 +36,9 @@ grows faster than its usefulness, and a per-component ratio whose denominator th Implemented by the component model in `audit/components.py` (`detect_components`); see [Scan Engine & Tiering](../contributor-deep-dive/01-scan-engine-and-tiering.md) § 4. ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | Recursive walk over all directories | Report bloat, cycle guards, and duplicate findings for nested modules; the per-component ratio stops being reconstructible by the reader. | | Infer components from directory depth without manifests | Produces noise in precisely the brownfield targets the tool serves — language package nesting would be scored as "components". | @@ -57,8 +46,6 @@ Implemented by the component model in `audit/components.py` (`detect_components` | Ask a model which directories are components | Non-deterministic, and the answer would be a guess dressed as a fact. Build manifests are ground truth ([ADR-0004](0004-no-llm-in-v1.md)). | | Score the candidate (undeclared) list as well | Punishes repositories that legitimately have a flat layout, and scores the absence of a convention rather than the absence of governance. | ---- - ## Consequences ### Positive @@ -76,13 +63,3 @@ Implemented by the component model in `audit/components.py` (`detect_components` * Repositories that declare components for packaging reasons but never intend per-component governance will receive a `PARTIAL` ratio they consider noise; the correct fix is to improve `AGENTS.md` coverage, not to weaken the check. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into *Alternatives Considered*. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference plus the index table in `docs/architecture/README.md` in the same change set. - -See the [style guide](../../../../../docs/STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/audit/engine/docs/architecture/adrs/0004-no-llm-in-v1.md b/audit/engine/docs/architecture/adrs/0004-no-llm-in-v1.md index cae16720..685de513 100644 --- a/audit/engine/docs/architecture/adrs/0004-no-llm-in-v1.md +++ b/audit/engine/docs/architecture/adrs/0004-no-llm-in-v1.md @@ -1,12 +1,5 @@ # 0004. No LLM Calls in v1 -* **Status:** Accepted -* **Date:** 2026-09-28 -* **Last reviewed:** 2026-09-28 -* **Deciders:** [@nistapp] - ---- - ## Context Several checks in the catalogue are semantic at heart. Is `AGENTS.md` *accurate* about this codebase? Do the @@ -25,15 +18,13 @@ manufacture either. There is also a practical consideration. Reports are diffed against a baseline to detect regression. A report whose content varies between runs cannot be diffed, regression-tested, or attributed to a revision of the rules. ---- - ## Decision -**Deterministic, standard-library only, no model in v1.** +**Deterministic, standard-library only, no model.** This decision binds the deterministic engine in this directory. The -[brownfield recipes](../../../../README.md) are a separate component, in development. They are meant to run the -engine first and add an LLM-assisted assessment of the items it parks as `ATTEST`, so they are not bound by it. +[audit recipe](../../../../README.md) is a separate component. It runs the engine first and adds an LLM-assisted +assessment of the items the engine parks as `ATTEST`, so it is not bound by this decision. 1. No LLM calls, no network calls by default, no embedding of a model client. 2. Every check's evidence rule must be **falsifiable by inspection** — a reviewer applies it to a fixture and @@ -44,30 +35,27 @@ engine first and add an LLM-assisted assessment of the items it parks as `ATTEST 5. Same constraint as the greenfield scaffolder, for the same reason: the tool must run before the target's toolchain exists, on a machine with nothing installed. -Prose generation — a `--narrate` mode that rewrites findings as readable paragraphs — is a **roadmap item**, and -would be restricted to prose. Verdicts are never model-produced. See +The engine has no prose-generation mode. A `--narrate` mode that rewrites findings as readable paragraphs would be +restricted to prose. Verdicts are never model-produced. See [Scan Engine & Tiering](../contributor-deep-dive/01-scan-engine-and-tiering.md) § 7. ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | LLM-assist for the semantic checks (`AGT-03`, `AGT-06`, `TST-02`) | Non-reproducible reports cannot be diffed or regression-tested, and an opinion with a scanner's authority is worse than an honest `ATTEST`. | -| LLM for remediation prose only, verdicts still deterministic | Defensible, but v1 scope creep; held as `--narrate` on the roadmap rather than built now. | +| LLM for remediation prose only, verdicts still deterministic | Defensible, but not needed for a correct report. It is the `--narrate` mode, which is not built. | | Heuristics that *approximate* the semantic checks | A pattern that guesses "the governance file is accurate" is a fabricated verdict with extra steps. | | Embed a local model to keep it offline | Still non-deterministic across machines and versions, and breaks the "runs anywhere with Python" property. | | Ask the model to *rank* findings by severity | Severity is a rule-pack attribute, reviewable and diffable. A model ranking would change between runs and make the work queue unstable. | ---- - ## Consequences ### Positive * Every finding is arguable, and every score is traceable to evidence a reader can open. -* Reports are byte-stable and therefore diffable — which is what makes the ratchet in [ADR-0001](0001-report-not-a-gate.md) possible. +* Reports are byte-stable and therefore diffable, which makes the ratchet in + [ADR-0001](0001-report-not-a-gate.md) possible. * The tool has no API key, no cost, no rate limit, and no data leaving the machine. ### Negative / Trade-offs @@ -76,14 +64,4 @@ would be restricted to prose. Verdicts are never model-produced. See a gap to users who expected the tool to answer them. * Some checks are proxied by structural signals — doc-comment coverage instead of doc quality — and those proxies must be labelled as signals rather than verdicts. -* The `--narrate` roadmap item will need re-opening this ADR's boundary between prose and verdict when it lands. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into *Alternatives Considered*. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference plus the index table in `docs/architecture/README.md` in the same change set. - -See the [style guide](../../../../../docs/STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. +* A `--narrate` mode would have to re-open this ADR's boundary between prose and verdict. diff --git a/audit/engine/docs/architecture/adrs/0005-module-layout-and-zipapp-distribution.md b/audit/engine/docs/architecture/adrs/0005-module-layout-and-zipapp-distribution.md index 595479e7..2868b9bb 100644 --- a/audit/engine/docs/architecture/adrs/0005-module-layout-and-zipapp-distribution.md +++ b/audit/engine/docs/architecture/adrs/0005-module-layout-and-zipapp-distribution.md @@ -1,31 +1,20 @@ # 0005. Module Layout and Zipapp Distribution -* **Status:** Accepted -* **Date:** 2026-09-28 -* **Last reviewed:** 2026-09-28 -* **Deciders:** [@nistapp] - ---- - ## Context -The audit began as a single-file design, inheriting `python-agentic-bootstrap`'s `DESIGN.md` rule verbatim: *"a -single Python file, standard library only"*. That rule was justified for the scaffolder on two grounds — the whole -job is directory creation plus string substitution, and a single file needs no build step, so the tool runs before -the target's toolchain exists. +The sibling scaffolder, `python-agentic-bootstrap`, has a `DESIGN.md` rule: *"a single Python file, standard +library only"*. That rule rests on two grounds. First, the whole job is directory creation plus string +substitution. Second, a single file needs no build step, so the tool runs before the target's toolchain exists. -The second ground is a real requirement and does not change here. The first does not survive contact with this +The second ground is a real requirement for the audit too. The first does not hold for this tool: the audit's own design is ~50 checks, four pipeline stages of traversal and classification, per-ecosystem component detection, a ~45-entry instruction-variant table, a probe runner and two report writers — an order of magnitude more code than the scaffolder. One file of that size is hard to navigate, impossible to unit-test by stage, and produces tracebacks that name a single line number in a single file for every failure. -The question raised was whether the code can be split into modules and then *compiled* into a single executable, -as in C. Python's answer is different in kind: a module tree can be packed into one **archive** that runs anywhere -Python runs, but it cannot be **compiled** into a machine-code binary without a C toolchain or bundling an -interpreter — both of which cost the property that motivates the rule in the first place. - ---- +Can the code be split into modules and then *compiled* into a single executable, as in C? Python's answer is +different in kind: a module tree can be packed into one **archive** that runs anywhere Python runs. It cannot be +**compiled** into a machine-code binary without a C toolchain or a bundled interpreter — both of which cost the property that motivates the rule in the first place. ## Decision @@ -55,11 +44,9 @@ interpreter — both of which cost the property that motivates the rule in the f and shipping it a second convention would be churn without benefit. The shared constraint between the two tools is *"standard library only, no build step to run"* — not *"one file"*. ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | Keep one large `audit.py` | Inherited rule, but its justification (a small script) does not hold at 10× the size. Cost is permanent: no per-stage tests, unrunnable tracebacks, painful review. | | PyInstaller / cx_Freeze | Produces a per-OS binary that bundles an interpreter (10–30 MB), needs third-party tooling at build time, and trips antivirus heuristics. Breaks "runs anywhere with Python", which is the reason the tool ships as a script. | @@ -67,11 +54,9 @@ interpreter — both of which cost the property that motivates the rule in the f | shiv / pex | Single-file executables, but third-party and only earn their keep by resolving dependencies. This project has none, so they add a dependency to solve a problem it does not have. | | A build step that concatenates modules into one `.py` | Fragile (import ordering, name collisions, `__main__` handling), loses real tracebacks, and is a bespoke build system to maintain. | | `src/` layout with `pip install -e .` | Requires an install step before the tool can run at all, which contradicts the constraint that the audit must work before the target's toolchain exists. | -| A `pyproject.toml` and PyPI packaging in v1 | Adds setuptools to a tool whose distinguishing property is having no dependencies. Distribution is roadmap v2's TypeScript npm package; v1 ships an archive attached to a release. | +| A `pyproject.toml` and PyPI packaging | Adds setuptools to a tool whose distinguishing property is having no dependencies. Distribution is roadmap v2's TypeScript npm package; v1 ships an archive attached to a release. | | Commit `dist/audit.pyz` to the repository | An unreviewable binary in git, and a second source of truth that will silently disagree with the code. | ---- - ## Consequences ### Positive @@ -86,21 +71,11 @@ interpreter — both of which cost the property that motivates the rule in the f ### Negative / Trade-offs -* Distribution now has a build step, though running does not. Two entry paths must both stay tested — +* Distribution has a build step, though running does not. Two entry paths must both stay tested — `python3 -m audit` and `./dist/audit.pyz`; the test suite builds the archive and runs it. * A `.pyz` cannot be read with `Path(__file__)`, so any future data file must go through `importlib.resources`. Considered acceptable, and the reason tables stay in code. * The package name `audit` is generic; a consumer importing it must be aware of the shadowing risk if they ship their own `audit` module. The command interface is the supported surface, not the import path. -* Two conventions now exist across the two sibling tools. This is a deliberate, recorded divergence, not drift: +* Two conventions exist across the two sibling tools. This is a deliberate, recorded divergence, not drift: the shared rule is the constraint that matters and the file-count rule was never the point. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into *Alternatives Considered*. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference plus the index table in `docs/architecture/README.md` in the same change set. - -See the [style guide](../../../../../docs/STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/audit/engine/docs/architecture/adrs/0006-private-check-helpers-and-ignore-engine.md b/audit/engine/docs/architecture/adrs/0006-private-check-helpers-and-ignore-engine.md index e7192752..8616fa3e 100644 --- a/audit/engine/docs/architecture/adrs/0006-private-check-helpers-and-ignore-engine.md +++ b/audit/engine/docs/architecture/adrs/0006-private-check-helpers-and-ignore-engine.md @@ -1,22 +1,14 @@ # 0006. Private Check Helpers and the Ignore Engine -* **Status:** Accepted -* **Date:** 2026-09-28 -* **Last reviewed:** 2026-09-28 -* **Deciders:** [@nistapp] - ---- - ## Context -ADR-0005 fixed the package layout: one module per pipeline stage under `audit/`, one module per check +ADR-0005 fixes the package layout: one module per pipeline stage under `audit/`, one module per check family under `audit/rules/checks/`. That works for behaviour that belongs to a single stage or a single pack. Two cross-cutting concerns do not fit it: 1. **Tracked-file and ignore certainty.** `SEC-01`, `SEC-03`, `DOC-03`, `EXEC-05` and `CON-03` all - need to know which files git tracks and whether an ignore rule covers a path. The only matching - that existed lived inside `audit/scan.py` as a non-git-aware approximation, and it was not - reusable without also changing what the walk skips. + need to know which files git tracks and whether an ignore rule covers a path. The walk's own + matching in `audit/scan.py` is a non-git-aware approximation. Reusing it would change what the walk skips. 2. **Verb resolution and documented commands.** `CMD-01`, `EXEC-06`, `CI-02`, `AGT-05`, `TOOL-01/02/04` and `TST-01/04` all ask "what verbs exist, what does each run, and which commands does the documentation show?". Answering that per pack guarantees several divergent answers. @@ -24,8 +16,6 @@ single pack. Two cross-cutting concerns do not fit it: Duplicating either concern across packs is the classic path to disagreement: two checks reading the same manifest by two slightly different rules will one day disagree in the same report. ---- - ## Decision **Two internal helper modules, no new pipeline stage and no new rule pack.** @@ -47,21 +37,17 @@ same manifest by two slightly different rules will one day disagree in the same They may be reshaped when real usage in units 03–13 shows which helpers are genuinely shared; a thin `_common.py` beats a speculative one. ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | Duplicate the logic in each pack | Guarantees drift: two checks reading one manifest under two subtly different rules will eventually disagree in one report. | -| Make ignore logic a new pipeline stage (S3.5) | It is not a stage — it is a query over traversal. A stage would couple `scan.py`'s walk to a separately-versioned semantic and risk shifting every existing verdict. | +| Make ignore logic a new pipeline stage | It is not a stage — it is a query over traversal. A stage would couple `scan.py`'s walk to a separately-versioned semantic and risk shifting every existing verdict. | | Fold the helpers into `audit/stack.py` or `audit/components.py` | Wrong responsibility: stack detection classifies ecosystems, it does not resolve the verbs or read ignore files. | | Add `_common` to `MODULE_NAMES` so the registry loads it | The registry wires checks to implementations; a module with no `IMPLEMENTATIONS` has no place there, and listing it would imply rule-pack status it does not have. | | Depend on a gitignore-parsing / YAML / TOML library | Violates the stdlib-only constraint that lets the audit run before the target's toolchain exists (ADR-0005). | | Make the helpers a public, semver'd API | No external consumer exists; a public contract here would freeze crude, deliberately-partial parsers before they have been shaped by use. | ---- - ## Consequences ### Positive @@ -75,20 +61,9 @@ same manifest by two slightly different rules will one day disagree in the same ### Negative / Trade-offs -* Two helper modules now exist that ADR-0005's stage/pack map does not name. They are infrastructure, +* Two helper modules exist that ADR-0005's stage/pack map does not name. They are infrastructure, not stages or packs, and this ADR is where that distinction is recorded. * The Taskfile and pyproject readers are deliberately shallow (no YAML/TOML library). They are documented as crude and are expected to be refined as implementation units adopt them. * Because there is no public contract, a helper signature may change without a deprecation path. That is intentional for internal code exercised only by this repository's own test suite. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into *Alternatives Considered*. Do NOT create a superseding ADR and do NOT leave a tombstone stub. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference plus the index table in `docs/architecture/README.md` in the same change set. -* Archived wording: `git log --follow docs/architecture/adrs/NNNN-title.md`. - -See the [style guide](../../../../../docs/STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/audit/engine/docs/architecture/adrs/0007-json-report-render-contract.md b/audit/engine/docs/architecture/adrs/0007-json-report-render-contract.md index 57f2abd0..2daaf345 100644 --- a/audit/engine/docs/architecture/adrs/0007-json-report-render-contract.md +++ b/audit/engine/docs/architecture/adrs/0007-json-report-render-contract.md @@ -1,22 +1,13 @@ # 0007. The JSON Report Is the Render Contract; the Markdown Is a Template Over It -* **Status:** Accepted -* **Date:** 2026-09-30 -* **Last reviewed:** 2026-09-30 -* **Deciders:** [@nistapp] - ---- - ## Context The audit emits two artifacts from one run: a machine-readable JSON report and a human Markdown page. -They were written by two independent functions, one per format, that each walked -the in-memory `AuditResult` separately. Nothing forced them to agree, the Markdown carried only prose -findings, and every check's structured facts — the missing verbs, the credential file×line matrix, the -harness reach list — were flattened into one human `detail` string that no renderer could turn into a -table. +Two independent writers, one per format, would each walk the in-memory `AuditResult` separately. Nothing would +force them to agree, and every check's structured facts — the missing verbs, the credential file×line matrix, the +harness reach list — would be flattened into one human `detail` string that no renderer can turn into a table. -Three forces pushed for a change: +Three forces shape the design: 1. **The Markdown should evolve quickly.** Formatting is the artifact most likely to be iterated on; it should not require touching the verdict engine or every check pack. @@ -29,8 +20,6 @@ A naive answer is a separate `.md` template file. That is unavailable: a `.pyz` a shipped data file cannot be read via `Path(__file__)` (ADR-0005 § 5) — the same reason the instruction-variant table is code. ---- - ## Decision **One report dict is the source of truth; both artifacts are rendered from it, and the template is @@ -54,11 +43,9 @@ code.** generic engine; the check packs keep their verdict logic. Adding a new derived view is one builder plus one template entry. ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | Keep two independent writers over `AuditResult` | They can disagree; the Markdown cannot be re-rendered from a saved report; formatting changes touch both paths. | | A standalone `.md` template file | A `.pyz` has no filesystem; the template must be code or shipped via `importlib.resources`, which ADR-0005 § 5 rejects. | @@ -66,8 +53,6 @@ code.** | Keep `detail` and parse it in the renderer | Prose parsing is brittle, lossy, and re-introduces the coupling the change exists to remove. | | Carry structured data in a side table keyed by check id | Splits the fact from the check that produced it; a check's evidence would live outside its outcome. | ---- - ## Consequences ### Positive @@ -84,17 +69,6 @@ code.** * Every check that wants a dedicated table must populate a payload; checks without one fall back to their `summary`. This is incremental work, deliberately not done for every check at once. -* The schema is versioned (`schema_version: "2"`) and `detail` is gone, so any external consumer of - the v1 JSON must be updated. The project is pre-release, so this was taken now rather than later. -* The template is code, not a document a non-programmer edits. That is the ADR-0005 trade the project - has already accepted. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into *Alternatives Considered*. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference plus the index table in `docs/architecture/README.md` in the same change set. - -See the [style guide](../../../../../docs/STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. +* The schema is versioned (`schema_version: "2"`) and has no `detail` field. A consumer of an older + report must update ([ADR 0008](../../../../../docs/architecture/adrs/0008-one-release-version-and-current-schemas-only.md)). +* The template is code, not a document a non-programmer edits. That is the ADR-0005 trade. diff --git a/audit/engine/docs/architecture/adrs/0008-what-counts-as-the-repository.md b/audit/engine/docs/architecture/adrs/0008-what-counts-as-the-repository.md index 3698955a..0014a192 100644 --- a/audit/engine/docs/architecture/adrs/0008-what-counts-as-the-repository.md +++ b/audit/engine/docs/architecture/adrs/0008-what-counts-as-the-repository.md @@ -1,43 +1,31 @@ # 0008. What Counts as the Repository -* **Status:** Accepted -* **Date:** 2026-11-05 -* **Last reviewed:** 2026-11-05 -* **Deciders:** [@nistapp] - ---- - ## Context Every check answers one of two questions about files: "which runner owns the verbs?" and "is file X present?". -Until this ADR the engine answered both from a flat list of every file the walk found, at any depth, with no -notion of which files are the project and which are data the project carries (templates it generates, fixtures -its tests read, examples its documentation shows). - -The audit of this repository, the framework itself, showed the cost. A nested `greenfield-bootstrap/templates/ -package.json` outranked the root `Makefile`, so `CMD-01` resolved all seven verbs against a template and -`CMD-03` reported a second gate. `CODEOWNERS`, `CONTRIBUTING`, `SECURITY`, a pull-request template, a release -document and a Dependabot file were all credited from `greenfield-bootstrap/templates/`, and `EXEC-02`, `EXEC-03`, -`BASE-01/02`, `TST-05` and `NAV-03` each read a fixture or a template as if it were the repository. The operator's -only remedy was `--exclude`, which the operator has to know to use, and which leaves no trace in the verdict. - -Three separate defects had one cause: - -1. **Precedence by kind, not by depth.** `RUNNER_PRECEDENCE` ranked `package.json` above `Makefile` whatever - their paths, and `_manifest_path` picked the shallowest file of each kind independently, so a nested - `package.json` outranked a root `Makefile`. -2. **Presence at any depth.** `Inventory.has`, `Inventory.match` and the hygiene readers (`HYG-02/03/05/06/08/09`) - matched a name at any depth, although `HYG-02`'s own text says the file lives at the root, under `.github/` or - under `docs/`. -3. **Carried data is indistinguishable from the project.** Nothing recorded that `tests/fixtures/`, +A flat list of every file the walk finds, at any depth, has no notion of which files are the project and which +are data the project carries (templates it generates, fixtures its tests read, examples its documentation shows). + +This repository, the framework itself, shows the cost. With a flat list, the nested +`greenfield/templates/package.json` outranks the root `Makefile`, so `CMD-01` resolves all seven verbs against a +template and `CMD-03` reports a second gate. Hygiene files such as `CODEOWNERS` and `SECURITY` are credited from +the templates, and `EXEC-02`, `EXEC-03`, `BASE-01/02`, `TST-05` and `NAV-03` each read a fixture or a template as +if it were the repository. The operator's only remedy is `--exclude`, which the operator has to know to use, and +which leaves no trace in the verdict. + +A flat list causes three defects: + +1. **Precedence by kind, not by depth.** A kind order puts `package.json` above `Makefile` whatever their paths. + With the shallowest file of each kind picked alone, a nested `package.json` outranks a root `Makefile`. +2. **Presence at any depth.** A name matched at any depth credits a misplaced hygiene file + (`HYG-02/03/05/06/08/09`). `HYG-02`'s own text places the file at the root, under `.github/` or under `docs/`. +3. **Carried data is indistinguishable from the project.** Nothing records that `tests/fixtures/`, `schema/examples/` or `templates/` hold files meant to be read, not run. Component discovery is a different problem and stays out of scope (ADR-0003: components are declared, depth 1; GitHub issue #20 covers nested components). This ADR decides which files are the repository at all; it does not decide how a monorepo's components are scoped. ---- - ## Decision **Three rules, applied once, in the inventory and the runner resolver, so that every check gets the same answer.** @@ -61,20 +49,18 @@ decide how a monorepo's components are scoped. was set aside by its name. Set-aside is applied after the walk (the exception needs the whole tree). It reaches git-backed checks as well: -`tracked_in_scope` drops tracked paths under a set-aside directory, as it already drops `--exclude`d paths. +`tracked_in_scope` drops tracked paths under a set-aside directory, as it drops `--exclude`d paths. **Hygiene presence is bounded.** `HYG-02/03/05/06/08/09` count a file only at the repository root or under -`.github/`, `.gitlab/` or `docs/`, which is where `HYG-02`'s text already said the file lives. A hygiene file in +`.github/`, `.gitlab/` or `docs/`, which is where `HYG-02`'s text says the file lives. A hygiene file in `packages/web/` is a component's file, not the repository's. `--exclude` stays: it is the operator's tool for anything the list does not cover (`artefacts/**`, `dist/**`, -vendored trees). It is no longer needed for template, example or fixture directories. - ---- +vendored trees). Template, example and fixture directories do not need it. ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | Each check filters out template-like paths itself | The pattern that caused the defect: thirty call sites, and one missed site reintroduces the bug. The engine's invariant (ADR-0006) is that a question asked by many packs has one answer. | | Keep carried files in `Inventory.files` with a flag, and filter in the accessors | Direct iteration over `inventory.files` is used by more than a dozen checks; every one would need the filter, and a missed one is silent. Removing the files from `files` and recording them beside it fails closed. | @@ -83,46 +69,30 @@ vendored trees). It is no longer needed for template, example or fixture directo | Consider only the repository root for every presence check | `docs/`, `.github/` and `.gitlab/` are conventional homes for repository-level files, and monorepo tooling lives in subdirectories. | | Read `.gitattributes` (`linguist-vendored`, `export-ignore`) to find carried data | Needs git and an attribute almost no repository sets; the engine must work on a bare directory (ADR-0006: `None`, never an inference, when git cannot answer). | | Let nested manifests outrank the root when they declare more verbs | Rewards the wrong file: the repository's gate is the one at its root, and a template generated into a new project will always declare more than the framework that generates it. | -| A flag to turn set-aside off | Cheap, but no repository has needed it yet. The report lists every set-aside directory, so a misfire is visible; the flag is added when one is seen. | - -> A later reversal of this decision is folded into this table, so the rejected direction -> keeps its rationale inside the accepted record. - ---- +| A flag to turn set-aside off | Cheap, but no repository needs it. The report lists every set-aside directory, so a misfire is visible. | ## Consequences ### Positive * On the framework's own repository, `CMD-01` reads the root `Makefile`, `CMD-03` finds one gate, and no hygiene, - tooling, baseline or coverage evidence comes from a template or a fixture. The fixtures `--exclude` is no longer - needed for a correct verdict. + tooling, baseline or coverage evidence comes from a template or a fixture, with no `--exclude` for the + fixtures. * `inventory.set_aside` makes a surprising verdict explainable without reading the engine. * One rank function, one list and one filter: a check added later inherits all three. ### Negative / Trade-offs * **A real directory with a carried name is set aside.** A Java package `com.acme.samples` or a Python package - named `scaffold` disappears from every check. The report names it; there is no flag to bring it back yet (see + named `scaffold` disappears from every check. The report names it; there is no flag to bring it back (see Alternatives). The risk is judged smaller than the one it removes: the three names that matter most for repositories that carry data (`templates`, `fixtures`, `examples`) rarely name source packages. * **Hazard checks lose the carried files too.** A real secret committed under `examples/` is not scanned by `SEC-02`. The alternative (hazards read carried data, credits do not) would make the engine flag every fixture that holds a fake credential on purpose, including its own. `inventory.set_aside` shows that the directory was not read. -* **Monorepo scoping is still open.** A nested `package.json` outside the list keeps its place: it is ranked below - the root's manifests, and `CMD-03` still sees it as a second runner. Declaring components for `DOC-01/02`, - `EXEC-04/07` and `NAV-01` is issue #20, deferred to v0.3 (open question Q3, default accepted). -* **Hygiene presence is stricter.** A repository that kept its only `CONTRIBUTING.md` in `packages/web/` now fails +* **Monorepo scoping is not covered.** A nested `package.json` outside the list keeps its place: it is ranked + below the root's manifests, and `CMD-03` sees it as a second runner. Declaring components for `DOC-01/02`, + `EXEC-04/07` and `NAV-01` is GitHub issue #20. +* **Hygiene presence is stricter.** A repository that keeps its only `CONTRIBUTING.md` in `packages/web/` fails `HYG-05`. These checks are informational and never score. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into *Alternatives Considered*. Do NOT create a superseding ADR and do NOT leave a tombstone stub. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference plus the index table in `docs/architecture/README.md` in the same change set. -* Archived wording: `git log --follow docs/architecture/adrs/NNNN-title.md`. - -See the [style guide](../../../../../docs/STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/audit/engine/docs/architecture/adrs/0009-nested-ignore-files-and-negation.md b/audit/engine/docs/architecture/adrs/0009-nested-ignore-files-and-negation.md index dd9f7bcf..520b2a11 100644 --- a/audit/engine/docs/architecture/adrs/0009-nested-ignore-files-and-negation.md +++ b/audit/engine/docs/architecture/adrs/0009-nested-ignore-files-and-negation.md @@ -1,29 +1,20 @@ # 0009. Nested Ignore Files and Negation -* **Status:** Accepted -* **Date:** 2026-11-05 -* **Last reviewed:** 2026-11-05 -* **Deciders:** [@nistapp] - ---- - ## Context -ADR-0006 gave the engine one ignore matcher, a documented subset kept exactly as the walk had it: root ignore -files only, `!` lines dropped, anchoring and `**` not interpreted. Four checks ask a different question from the +ADR-0006 gives the walk one ignore matcher, a documented subset: root ignore files only, `!` lines dropped, +anchoring and `**` not interpreted. Four checks ask a different question from the walk. `DOC-03` and `CON-03` ask whether the scratch directory is kept out of git; `SEC-01` and `SEC-03` ask -whether the ignore rules cover a secret shape. Two defects followed. - -1. **A nested `.gitignore` was invisible.** This repository keeps `artefacts/.gitignore` containing `*`, with - `!.gitignore` and `!README.md`. No root rule names `artefacts/`, so both checks reported "matched by no ignore - rule" (issue #22), and the tracked `README.md` read as scratch committed by mistake (issue #12). -2. **`!` never re-included.** Dropping the line was documented, but it made `SEC-03` credit a shape that a - later `!server.crt` had opened again, and made a deliberate exception look like a defect. +whether the ignore rules cover a secret shape. For them, the walk's matcher has two defects. -The operator's remedy, `--exclude`, made it worse: excluding `artefacts/**` (as every self-audit does) also removed -the nested `.gitignore` from the inventory, so the rule that ignores the directory was no longer read. +1. **A nested `.gitignore` is invisible.** This repository keeps `artefacts/.gitignore` containing `*`, with + `!.gitignore` and `!README.md`. No root rule names `artefacts/`. Both checks would report "matched by no ignore + rule". The tracked `README.md` would read as scratch committed by mistake. +2. **`!` never re-includes.** With the line dropped, `SEC-03` credits a shape that a later `!server.crt` opens + again. A deliberate exception then looks like a defect. ---- +The operator's remedy, `--exclude`, makes it worse: excluding `artefacts/**` (as every self-audit does) also +removes the nested `.gitignore` from the inventory, so the rule that ignores the directory is not read. ## Decision @@ -54,11 +45,9 @@ not move. The four checks above use a **scoped matcher** in `audit/ignore.py`: 6. **The parser stays the authority.** Neither matcher runs `git check-ignore`. When git cannot report the tracked set the checks stay `UNKNOWN`, as before. ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | Change the one matcher for the walk too | It would shift what every check reads, for a question only four checks ask. The walk keeps ADR-0006's behaviour; a later change can move it with its own ruleset bump. | | Shell out to `git check-ignore -v` | Needs git and a work tree; the engine must answer on a bare directory. It would also make the verdict depend on the host's git version and global excludes file. Left as an optional confirmation for a later revision. | @@ -66,8 +55,6 @@ not move. The four checks above use a **scoped matcher** in `audit/ignore.py`: | Treat every tracked file under a scratch root as allowed when the root is ignored | It would hide `artefacts/plan.md` force-added past the rule, which is the defect `DOC-03` exists to find. | | Read nested ignore files of every kind | `.cbmignore` and the other names are tool files read at the root; only `.gitignore` nests in git. | ---- - ## Consequences ### Positive @@ -84,14 +71,3 @@ not move. The four checks above use a **scoped matcher** in `audit/ignore.py`: what `re` reads, no core.excludesFile or `.git/info/exclude`. A rule outside the subset reads as no match. * Issue #12 (a tracked file that an ignore rule covers) is answered for the scratch roots only. Issue #22 (untracked ignored runtime data in evidence) is not touched: the walk still lists such files. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into *Alternatives Considered*. Do NOT create a superseding ADR and do NOT leave a tombstone stub. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference plus the index table in `docs/architecture/README.md` in the same change set. -* Archived wording: `git log --follow docs/architecture/adrs/NNNN-title.md`. - -See the [style guide](../../../../../docs/STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/dev/build_skills.py b/dev/build_skills.py index d17aa0ab..f0b06ee2 100644 --- a/dev/build_skills.py +++ b/dev/build_skills.py @@ -38,7 +38,7 @@ same ``metadata.version`` added. - ``references/``: the state contract, the playbooks, the kickoff prompt, the shared prompt on how to talk to the user (from ``audit/prompts/``), the check files, the stack profiles, - ADR 0003, ``spec/command-surface.md`` and the spec decisions, the state schema and its docs, and the parts of + ADR 0003, the ADR policy and the ADR template, ``spec/command-surface.md`` and the spec decisions, the state schema and its docs, and the parts of the Tools CLI reference that cover the carried tools (:data:`TOOLS_CLI_SECTIONS`). - ``scripts/``: ``progress.py``, ``check_drift.py`` and ``compare_reports.py`` (from ``implement/tools/``), and ``validate_report.py`` with the private modules they import (from ``audit/tools/``), side by side in @@ -330,6 +330,8 @@ def build_bundle(out_dir: Path) -> Path: "implement/docs/architecture/adrs/0003-push-and-pr-policy.md", "references/adrs/0003-push-and-pr-policy.md", ), + ("docs/ADR_POLICY.md", "references/adr-policy/ADR_POLICY.md"), + ("docs/templates/adr-template.md", "references/adr-policy/adr-template.md"), ("spec/command-surface.md", "references/spec/command-surface.md"), ("spec/decisions/0001-constraint-artifact.md", "references/spec/decisions/0001-constraint-artifact.md"), ("spec/decisions/0002-lint-in-the-gate.md", "references/spec/decisions/0002-lint-in-the-gate.md"), diff --git a/dev/tests/test_build_skills.py b/dev/tests/test_build_skills.py index 60ca28c9..150d896f 100644 --- a/dev/tests/test_build_skills.py +++ b/dev/tests/test_build_skills.py @@ -274,6 +274,8 @@ def test_bundle_has_its_three_parts(self) -> None: "references/checks/04-command-surface.md", "references/stacks/java.md", "references/adrs/0003-push-and-pr-policy.md", + "references/adr-policy/ADR_POLICY.md", + "references/adr-policy/adr-template.md", "references/spec/command-surface.md", "references/spec/decisions/0002-lint-in-the-gate.md", "references/schema/SCHEMA.md", @@ -312,6 +314,12 @@ def test_skill_md_links_the_shared_prompt(self) -> None: text = (self.bundle / "SKILL.md").read_text(encoding="utf-8") self.assertIn("](references/prompts/talking-to-the-user.md)", text) + def test_docs_contract_playbook_links_the_carried_adr_policy(self) -> None: + playbook = self.bundle / "references" / "playbooks" / "establish-docs-contract.md" + text = playbook.read_text(encoding="utf-8") + self.assertIn("](../adr-policy/ADR_POLICY.md)", text) + self.assertIn("](../adr-policy/adr-template.md)", text) + def test_no_framework_tool_command_is_left(self) -> None: for file in self.markdown_files(): text = file.read_text(encoding="utf-8") diff --git a/docs/ADR_POLICY.md b/docs/ADR_POLICY.md new file mode 100644 index 00000000..28ffd006 --- /dev/null +++ b/docs/ADR_POLICY.md @@ -0,0 +1,43 @@ +# ADR policy + +This page says how to write, change and delete an architecture decision record (ADR) in this repository. It is a +variant of the usual ADR conventions, tuned for AI agents rather than people. Agents load ADRs as instructions, +so a stale or historical ADR is a defect, not a record. + +Every ADR folder follows it: the root `docs/architecture/adrs/`, `spec/decisions/`, `audit/docs/architecture/adrs/`, +`implement/docs/architecture/adrs/` and `audit/engine/docs/architecture/adrs/`. Where each decision goes is +[ADR 0001](architecture/adrs/0001-decisions-live-beside-the-component.md). + +## Rules + +1. **Git is the archive.** Do not manage an ADR lifecycle. An ADR has no status, date, review stamp, list of + deciders or change log. `git log --follow ` gives its history. +2. **State the current decision only.** Write in the present tense. Do not write "previously", "in v1 we" or + "was changed on". Leave out plan step IDs, question IDs and dated owner notes. +3. **Decision changed: overwrite the ADR in place.** Keep the file name and the number. Turn the old choice into + one row of *Alternatives Considered*, with the reason it is rejected now. Never write a second ADR that + supersedes the first. +4. **Decision superseded or no longer needed: delete the file.** In the same change, remove its index row and every + link to it and mention of it. Never refer to a deleted ADR again, not even to say it was removed. +5. **Number a new ADR** as the highest number ever used in its folder, plus one. Find deleted numbers with + `git log --diff-filter=D --name-only -- `. Never reuse a number, so an old commit message cannot point + at a different decision. +6. **Write an ADR only when it earns its place.** Record a decision an agent would otherwise get wrong or reopen. + Typical cases are a dependency, a boundary, a safety rule or a format contract. Put a one-line rule in + `AGENTS.md` instead. Put a local detail in a code comment. +7. **Keep it small.** One decision per ADR, under about 100 lines. Link to the code, the spec and other pages; do + not restate them. +8. **Index it.** The root index, [`architecture/adrs/README.md`](architecture/adrs/README.md), lists every ADR in + every folder: number, title and a one-line summary. Add, change or remove the row in the same change as the ADR. +9. **Start from the template**, [`templates/adr-template.md`](templates/adr-template.md). Keep its four sections: + Context, Decision, Alternatives Considered, Consequences. +10. **Never invent a rationale.** When the reason for a decision is not in the repository, write "Rationale not + recorded; owner to fill in". +11. **Read only the ADRs you need.** Use the index summaries to pick the ADRs that govern the folder you change. + +## Where else this applies + +- The implement recipe writes ADRs into a target repository under the same rules. See the + [`establish-docs-contract`](../implement/playbooks/establish-docs-contract.md) playbook. +- The greenfield templates ship a copy of this policy for new repositories: + [`greenfield/templates/docs/ADR_POLICY.md`](../greenfield/templates/docs/ADR_POLICY.md). diff --git a/docs/README.md b/docs/README.md index dc99630d..806fb21e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -79,7 +79,8 @@ The framework is normative. If a tool disagrees with it, the tool is wrong. ## Decisions The [ADR index](architecture/adrs/README.md) lists every decision of record, in all five groups. ADRs live beside the -component they govern ([ADR 0001](architecture/adrs/0001-decisions-live-beside-the-component.md)). +component they govern ([ADR 0001](architecture/adrs/0001-decisions-live-beside-the-component.md)). The +[ADR policy](ADR_POLICY.md) says how to add, change and delete one. ## Components @@ -103,6 +104,7 @@ repository, before and after the implement recipe. | [Explanation template](templates/explanation-template.md) | Start an explanation page | | [Reference template](templates/reference-template.md) | Start a reference page | | [Architecture doc template](templates/architecture-doc-template.md) | Start an architecture page | +| [ADR policy](ADR_POLICY.md) | How to add, change and delete a decision record | | [ADR template](templates/adr-template.md) | Start a decision record | Gates and commit rules: [`AGENTS.md`](../AGENTS.md). Setup and contributing: [`CONTRIBUTING.md`](../CONTRIBUTING.md). diff --git a/docs/STYLE_GUIDE.md b/docs/STYLE_GUIDE.md index fe84e183..54ede7e1 100644 --- a/docs/STYLE_GUIDE.md +++ b/docs/STYLE_GUIDE.md @@ -22,8 +22,7 @@ This page tells you how to write a documentation page in this repository. It app 4. **Point to the source.** Name the file. Once the file exists, link to it, with line anchors where they help. Example: `[report.py#L1-L40](../audit/tools/report.py#L1-L40)`. While a file is planned, write its name as a code span and do not link to it. -5. **An ADR describes the current design only.** When a decision changes, edit the ADR in place. When it no longer - applies, delete it. Git keeps the history. +5. **An ADR describes the current design only.** The rules are in [`ADR_POLICY.md`](ADR_POLICY.md). ## 2. Kinds of page and their readers @@ -109,7 +108,7 @@ edit. 1. Where `codebase-memory-mcp` is available, use it to check symbols and file paths before you read whole files. 2. Look for an existing page first. Update it; do not write a second page on the same topic. -3. Before you add an ADR, find the highest number ever used in that folder. Use the next number. +3. Before you add, change or delete an ADR, read [`ADR_POLICY.md`](ADR_POLICY.md). Some changes alter a tool's interface, a schema, a recipe stage, what a check means or the architecture. For such a change, do all of this in the same change set: @@ -118,20 +117,12 @@ change, do all of this in the same change set: 2. Remove the `Planned` note from each page whose content now exists. 3. Update the ADR index when you add, change or delete an ADR. -## 8. ADR lifecycle +## 8. ADR life## 8. ADR lifecycle -ADRs start from `templates/adr-template.md`. +[`ADR_POLICY.md`](ADR_POLICY.md) holds every ADR rule: numbering, the index, changing and deleting. ADRs start from +[`templates/adr-template.md`](templates/adr-template.md). -1. **Add:** use the highest number ever used plus one. Numbers of deleted ADRs are not used again. -2. **Index:** add a row to [`architecture/adrs/README.md`](architecture/adrs/README.md) in the same change set. -3. **Change in place:** rewrite the body and keep the number. Move the old choice into *Alternatives Considered*. -4. **Delete** an ADR that no longer applies. Fix every link to it in the same change set. - -> [!IMPORTANT] -> An ADR has two states. `Proposed` is allowed while the change is open, but is never merged. A merged ADR is -> `Accepted`. There is no `Deprecated`, `Superseded` or `Rejected` state. - -## 9. What does not go in `docs/` +s not go in `docs/` | Not in `docs/` | Where it goes | |---|---| diff --git a/docs/architecture/adrs/0001-decisions-live-beside-the-component.md b/docs/architecture/adrs/0001-decisions-live-beside-the-component.md index 55bbdba5..5fe6e87c 100644 --- a/docs/architecture/adrs/0001-decisions-live-beside-the-component.md +++ b/docs/architecture/adrs/0001-decisions-live-beside-the-component.md @@ -1,17 +1,10 @@ # 0001. Decisions Live Beside the Component They Govern -* **Status:** Accepted -* **Date:** 2026-11-05 -* **Last reviewed:** 2026-11-07 -* **Deciders:** [@nistapp] (MR07.1, Q13) - ---- - ## Context The repository holds parts that change for different reasons: requirements (`spec/`), the audit recipe and its tools, -the implement recipe and its tools, and the audit engine. Each already had its own decision records, in its own -folder. A reader who starts at the root had no way to know they exist, and the repository's own audit found no ADR +the implement recipe and its tools, and the audit engine. Each has its own decision records, in its own folder. +Without a root index, a reader who starts at the root cannot find them, and the engine's DOC-04 check finds no ADR directory at the root. ## Decision @@ -20,8 +13,13 @@ A decision is recorded in the ADR folder of the component it governs: `spec/deci `audit/docs/architecture/adrs/`, `implement/docs/architecture/adrs/`, or `audit/engine/docs/architecture/adrs/`. A decision that spans components goes in this folder. This folder's [`README.md`](README.md) indexes all of them, and is updated in the same change set as any ADR added elsewhere. +Every ADR, in every folder, is written and changed under the [ADR policy](../../ADR_POLICY.md). + +## Alternatives Considered -The v0.3 restructure kept this decision; only the folder paths changed (owner, 2026-11-07). +| Alternative | Why it is rejected | +| --- | --- | +| Records in each component folder, with no root index | A reader who starts at the root cannot find them, and DOC-04 finds no ADR directory at the root. | ## Consequences diff --git a/docs/architecture/adrs/0002-dev-integrates-main-releases.md b/docs/architecture/adrs/0002-dev-integrates-main-releases.md index db36d67b..3ebead71 100644 --- a/docs/architecture/adrs/0002-dev-integrates-main-releases.md +++ b/docs/architecture/adrs/0002-dev-integrates-main-releases.md @@ -1,16 +1,9 @@ # 0002. `dev` Integrates Every Change; `main` Takes Only Promotions From `dev` and the Release Pull Request -* **Status:** Accepted -* **Date:** 2026-11-08 -* **Last reviewed:** 2026-11-09 (the `dev` merge queue replaces the up-to-date rule; decision 6) -* **Deciders:** [@nistapp] - ---- - ## Context -Before this decision, the repository had one protected branch, `main`. Every pull request went to `main`, and a -release was a tag on `main`. Work that was not ready for a release was on `main` together with released work. +With one protected branch, every pull request goes to it and a release is a tag on it. Work that is not ready for +a release then sits on the same branch as released work. The owner wants two protected branches, the same model as the [Digital-Assistant-SDK](https://github.com/Digital-Assistant/Digital-Assistant-SDK/blob/main/RELEASE_PROCESS.md) @@ -19,7 +12,7 @@ repository: * `dev` accepts pull requests from any branch, `main` included. It accepts no direct commits. * `main` accepts pull requests from `dev` only. -The SDK repository also uses Release Please, a GitHub App and a back-merge job. This repository adopted them too +The SDK repository also uses Release Please, a GitHub App and a back-merge job. This repository uses them too ([ADR 0005](0005-release-please-cuts-releases.md)). So `main` takes one more kind of pull request: the release pull request that release-please opens. @@ -49,34 +42,27 @@ A GitHub ruleset can require a pull request and status checks. It cannot restric `dev` gets those changes through a back-merge pull request from `chore/back-merge-vX.Y.Z`, a branch at the release commit. Its head is not `main` itself, so resolving a conflict with `dev` never needs a push to `main`. 6. **`main` does not require an up-to-date branch.** Promotions from `dev` and the release pull request both merge into - `main`. A strict "up to date" rule would make each wait for the other for no gain. Since 2026-11-09 `dev` does not - require it either: its merge queue tests each pull request merged onto the latest `dev`, so the rule only added a - CI run per update ([`branch-protection.md`](../../reference/branch-protection.md)). + `main`. A strict "up to date" rule would make each wait for the other for no gain. `dev` does not require it + either. Its merge queue tests each pull request merged onto the latest `dev`. The rule would only add a CI run per + update ([`branch-protection.md`](../../reference/branch-protection.md)). 7. **A local guard.** [`lefthook.yml`](../../../lefthook.yml) stops a commit on `main` or `dev` before the push is refused. The procedures are in [`RELEASE_PROCESS.md`](../../../RELEASE_PROCESS.md) and in [Contribute a change](../../how-to/contribute-a-change.md). ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| -| Keep one protected branch, `main` (the design before this ADR) | Unreleased work and released work share one branch. The owner wants a release branch that holds released work only. | -| No back-merge: changelogs and versions written by hand on `dev` before the promotion (this ADR until release-please, 2026-11-08) | The version strings drifted. The owner adopted release-please ([ADR 0005](0005-release-please-cuts-releases.md)), which writes to `main`, so a back-merge is needed. | +| One protected branch, `main` | Unreleased work and released work share one branch. The owner wants a release branch that holds released work only. | +| No back-merge: changelogs and versions written by hand on `dev` before the promotion | Hand-written version strings drift. release-please ([ADR 0005](0005-release-please-cuts-releases.md)) writes to `main`, so a back-merge is needed. | | `main` accepts pull requests from `dev` only, with no exception | release-please opens its release pull request into `main` from its own branch. The exception is narrow: one branch name, opened by a bot. | -| Keep `main` as the default branch (the first version of this ADR, 2026-11-08) | New pull requests and Dependabot security updates open against the default branch. With `main` as the default, they fail `Check source branch` and need a manual change of base. The owner made `dev` the default the same day. | +| `main` as the default branch | New pull requests and Dependabot security updates open against the default branch. With `main` as the default, they fail `Check source branch` and need a manual change of base. | | Restrict the source branch with the ruleset only | Rulesets cannot express a source branch. A required status check is the only way. | | Require an up-to-date branch on `main` | A promotion and the release pull request would each have to wait for, or update with, the other. | | Squash or rebase merges | One Conventional Commit per logical change must stay revertable alone. A squash joins them; a rebase rewrites them. | -> A later reversal of this decision is folded into this table, so the rejected direction -> keeps its rationale inside the accepted record. - ---- - ## Consequences ### Positive @@ -92,16 +78,3 @@ The procedures are in [`RELEASE_PROCESS.md`](../../../RELEASE_PROCESS.md) and in * Each release needs a back-merge pull request into `dev`. Until it is merged, `dev` shows the previous version. * A visitor to the repository page sees `dev`, which can hold unreleased work. A plain `git clone` also gets `dev`. The quick start clones `--branch main`, and the GitHub releases hold the released files. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into - *Alternatives Considered*. Do NOT create a superseding ADR and do NOT leave a tombstone stub. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference - plus the index table in `docs/architecture/adrs/README.md` in the same change set. -* Archived wording: `git log --follow docs/architecture/adrs/0002-dev-integrates-main-releases.md`. - -See [`docs/STYLE_GUIDE.md`](../../STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/docs/architecture/adrs/0003-dependabot-security-pull-requests-only.md b/docs/architecture/adrs/0003-dependabot-security-pull-requests-only.md index 08dae07d..e36c5c80 100644 --- a/docs/architecture/adrs/0003-dependabot-security-pull-requests-only.md +++ b/docs/architecture/adrs/0003-dependabot-security-pull-requests-only.md @@ -1,28 +1,20 @@ # 0003. Dependabot Opens Security Pull Requests Only; Major Versions Are Advisory -* **Status:** Accepted -* **Date:** 2026-11-08 -* **Last reviewed:** 2026-11-09 -* **Deciders:** [@nistapp] - ---- - ## Context -Dependabot opened weekly version-update pull requests for the GitHub Actions, for example #47 and #48. Each one is -work for the maintainer, and a major version can break CI or the release. +Weekly version-update pull requests from Dependabot are work for the maintainer, and a major version can break CI +or the release. The owner wants two things: * Dependabot opens a pull request only for a security fix. * A major version is advisory: it never arrives as a pull request that is ready to merge. -Before this decision, the dev tool versions were in the `Makefile` only (ruff, mypy, detect-secrets, coverage, -jsonschema, skills-ref, markdownlint-cli2) and in the workflows (uv). Dependabot cannot read a `Makefile`, so these -tools had no security alerts. +Dependabot cannot read a `Makefile` or a workflow input. A tool whose version is written only there gets no +security alerts. -GitHub's documentation (the Dependabot options reference and "Configuring Dependabot security updates", read on -2026-11-08) gives these facts: +GitHub's documentation (the Dependabot options reference and "Configuring Dependabot security updates") gives these +facts: * `open-pull-requests-limit: 0` stops version updates. Security updates have their own, fixed limit. * `ignore`, with `update-types`, applies to version updates and to security updates. @@ -41,7 +33,7 @@ GitHub's documentation (the Dependabot options reference and "Configuring Depend 3. **One pull request per ecosystem.** A `security-fixes` group joins the minor and patch security fixes. 4. **No `target-branch`.** The default branch is `dev` ([ADR 0002](0002-dev-integrates-main-releases.md)), so every Dependabot pull request goes to `dev` without it. -5. **The dev tools are visible to Dependabot.** Their versions move to manifests in `dev/tools/`, each with a +5. **The dev tools are visible to Dependabot.** Their versions live in manifests in `dev/tools/`, each with a lockfile: | Manifest | Lockfile | Tools | Dependabot ecosystem | @@ -62,26 +54,19 @@ GitHub's documentation (the Dependabot options reference and "Configuring Depend 7. **Routine and major upgrades are planned.** Before each release, the maintainer reviews the pins and the open alerts ([`RELEASE_PROCESS.md`](../../../RELEASE_PROCESS.md#dependency-updates)). ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| -| Weekly version updates for every level (the design before this ADR) | A pull request each week, majors included. A major can break CI or the release. | +| Weekly version updates for every level | A pull request each week, majors included. A major can break CI or the release. | | Version updates for minor and patch only | Minor releases of ruff and markdownlint often change rules, which changes many files. The owner wants security pull requests only. | -| Patch-only version updates for the dev tools, grouped monthly | Offered on 2026-11-08; the owner chose security pull requests only. | +| Patch-only version updates for the dev tools, grouped monthly | The owner wants security pull requests only. | | `target-branch: dev` on each entry | It stops `ignore` and `groups` from applying to security updates. `dev` is the default branch, so it is not needed. | | Keep the tool pins in the `Makefile` | Dependabot cannot read a `Makefile`. The tools would get no security alerts. | | `requirements.txt` without a lockfile | The repository's own audit then fails `EXEC-02` (no lockfile) for Python and Node, and `EXEC-01` for Node. | | Exclude `dev/tools/` from the self-audit | It narrows a gate to get green. | | Pin `uv` in each workflow | Four copies of one version, and Dependabot does not read a `setup-uv` input. | -> A later reversal of this decision is folded into this table, so the rejected direction -> keeps its rationale inside the accepted record. - ---- - ## Consequences ### Positive @@ -98,16 +83,3 @@ GitHub's documentation (the Dependabot options reference and "Configuring Depend * Two lockfiles, about 380 KB together, are in the repository. After a manual pin change, regenerate them (`uv lock --directory dev/tools`; `npm install --package-lock-only --ignore-scripts` in `dev/tools`). * The template and the fixtures can raise alerts that need no action. The maintainer dismisses them. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into - *Alternatives Considered*. Do NOT create a superseding ADR and do NOT leave a tombstone stub. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference - plus the index table in `docs/architecture/adrs/README.md` in the same change set. -* Archived wording: `git log --follow docs/architecture/adrs/0003-dependabot-security-pull-requests-only.md`. - -See [`docs/STYLE_GUIDE.md`](../../STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/docs/architecture/adrs/0004-plain-language-rules-and-a-prose-check.md b/docs/architecture/adrs/0004-plain-language-rules-and-a-prose-check.md index 9b2d09e2..2cf28b7c 100644 --- a/docs/architecture/adrs/0004-plain-language-rules-and-a-prose-check.md +++ b/docs/architecture/adrs/0004-plain-language-rules-and-a-prose-check.md @@ -1,35 +1,26 @@ # 0004. Plain-Language Rules in One File, Checked by a Standard-Library Prose Check -* **Status:** Accepted -* **Date:** 2026-11-08 -* **Last reviewed:** 2026-11-09 -* **Deciders:** [@nistapp] - ---- - ## Context -The owner found AI output and some docs dense and hard to follow. In chat, the owner asked agents for four things: +AI output and some docs are dense and hard to follow. The owner wants four things from agents: * STE100 (Simplified Technical English); * explanations aimed at an intern; * Mermaid diagrams; * options with a recommendation before a decision. -Each session had to ask again, and no rule said how a doc page should read. +Without a written rule, each session must ask again, and nothing says how a doc page should read. -Measured on 2026-11-08: 11 of 71 doc pages had a Mermaid diagram. Four pages averaged 30 to 39 words per sentence. +Few doc pages have a Mermaid diagram (11 of 71 in one count), and some average 30 to 39 words per sentence. STE100 sets 20 words for a procedure step and 25 for other text. -Three questions needed an answer: +Four questions need an answer: 1. Where do the rules live, so that every harness reads them? 2. How much of STE100 do we adopt? 3. Does a tool check the rules, and which tool? 4. Does the check block a change, or only advise? ---- - ## Decision **Rules.** [`docs/COMMUNICATION_RULES.md`](../../COMMUNICATION_RULES.md) holds all the rules for writing for people: @@ -58,30 +49,26 @@ baseline count holds it. A writer fixes a warning in the change that touches the * `make docs-prose [FILES=...]` prints the issues. `PROSE_ARGS=--min-severity=warning` hides the notices. * The CI `docs` job runs it with `--format github` on the changed Markdown. The issues show as annotations on the pull request, and the job does not fail on them. -* `make check` does not run it (decided 2026-11-09, v0.3.4 H2). A check that cannot fail adds only output to the +* `make check` does not run it. A check that cannot fail adds only output to the gate, and the pull request annotations already show the issues on the changed lines. **Scope.** The rules apply to this repository: agent messages and docs. The audit and implement recipes carry their own copy, `audit/prompts/talking-to-the-user.md`, for how they talk to their users. The skill bundles do not ship -this repository's `docs/`. Whether target repositories get a matching requirement is listed for v0.3.4. - ---- +this repository's `docs/`. ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | The full STE100 standard, with its approved word list | Made for maintenance manuals. The word list blocks the technical terms this repository needs. | | Rules only in `AGENTS.md` | Every line costs context in every session, and `AGENTS.md` is already long. | | Rules only in `COMMUNICATION_RULES.md` | Agents often skip a linked file. The short summary in `AGENTS.md` keeps the core in view. | -| Vale with a custom STE style (the v0.3.3 A4 pilot) | A Go binary: not on PyPI or npm, so it cannot be pinned the ADR 0003 way. Our three rules need about 200 lines of Python. | -| textlint, with `textlint-rule-terminology` (reviewed 2026-11-09) | Pinnable through `dev/tools/package.json`, and its rules can be warnings. Not needed yet: revisit when passive voice or glossary terms need a check. | -| proselint, write-good, alex, LanguageTool (reviewed 2026-11-09) | proselint is opinionated and hard to configure. write-good and alex are narrow. LanguageTool needs Java. | +| Vale with a custom STE style | A Go binary: not on PyPI or npm, so it cannot be pinned the ADR 0003 way. Our three rules need about 200 lines of Python. | +| textlint, with `textlint-rule-terminology` | Pinnable through `dev/tools/package.json`, and its rules can be warnings. Not needed yet: revisit when passive voice or glossary terms need a check. | +| proselint, write-good, alex, LanguageTool | proselint is opinionated and hard to configure. write-good and alex are narrow. LanguageTool needs Java. | | Rules without a check | Rules without a check drift. Chat cannot be checked, but docs can. | -| Run the advisory check in `make check` too (reviewed 2026-11-09) | It costs little (0.3 s over the whole tree), but it cannot fail, so the gate would only print about 1,750 issues that no one acts on there. The CI annotations put each issue on its own line in the pull request instead. | -| A blocking check: a zero-issue path list (`PROSE_PATHS`) and a ratchet count in `.quality-baseline.json` (shipped 2026-11-08, reversed 2026-11-09) | Too strict for guidelines. On 2026-11-09, 1,574 of 1,624 issues were `PL001`, and a long sentence is often the clearest one. Blocking on it slowed work without a matching gain. | - ---- +| Run the advisory check in `make check` too | It costs little (0.3 s over the whole tree), but it cannot fail, so the gate would only print about 1,750 issues that no one acts on there. The CI annotations put each issue on its own line in the pull request instead. | +| A blocking check: a zero-issue path list (`PROSE_PATHS`) and a ratchet count in `.quality-baseline.json` | Too strict for guidelines. In one count, 1,574 of 1,624 issues were `PL001`, and a long sentence is often the clearest one. Blocking on it slowed work without a matching gain. | ## Consequences @@ -96,21 +83,6 @@ this repository's `docs/`. Whether target repositories get a matching requiremen * The sentence splitter is a heuristic. It can miss a split, for example after a code span that ends with a full stop. Rewording the sentence fixes it. -* Nothing stops the count from rising. The full tree had 1,624 issues on 2026-11-09. Review, not a gate, keeps new - text readable. +* Nothing stops the count from rising. Review, not a gate, keeps new text readable. * The check cannot see passive voice, one meaning per word or the bottom-line-first rule. People and agents apply those. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or - `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into - *Alternatives Considered*. Do NOT create a superseding ADR and do NOT leave a tombstone stub. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference - plus the index table in `docs/architecture/adrs/README.md` in the same change set. -* Archived wording: `git log --follow docs/architecture/adrs/0004-plain-language-rules-and-a-prose-check.md`. - -See [`docs/STYLE_GUIDE.md`](../../STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/docs/architecture/adrs/0005-release-please-cuts-releases.md b/docs/architecture/adrs/0005-release-please-cuts-releases.md index ea7e5db2..5bb6a5ed 100644 --- a/docs/architecture/adrs/0005-release-please-cuts-releases.md +++ b/docs/architecture/adrs/0005-release-please-cuts-releases.md @@ -1,28 +1,20 @@ # 0005. Release-Please Computes the Version, Writes the Changelog and Tags the Release -* **Status:** Accepted -* **Date:** 2026-11-08 -* **Last reviewed:** 2026-11-09 -* **Deciders:** [@nistapp] - ---- - ## Context -Up to v0.3.0, a release was manual. The maintainer did five things: +A manual release takes five steps by hand: -1. opened a release preparation branch; -2. moved the `[Unreleased]` changelog entries under a dated heading; -3. changed the version strings by hand; -4. promoted `dev` to `main`; -5. pushed a signed tag. - The tag push started [`release.yml`](../../../.github/workflows/release.yml). +1. open a release preparation branch; +2. move the `[Unreleased]` changelog entries under a dated heading; +3. change the version strings; +4. promote `dev` to `main`; +5. push a signed tag. -That hand work drifted. The engine's `__version__` said `0.1.0` through the v0.2.0 and v0.3.0 releases, so -`audit --version` and every report's provenance named the wrong version. +Hand work drifts. A stale version string makes `audit --version` and every report's provenance name the wrong +version. -The commit subjects already follow Conventional Commits, and CI checks them (`dev/commit_lint.py`). So a tool can -compute the next version and write the changelog from them. The owner asked for automated versions and releases, +The commit subjects follow Conventional Commits, and CI checks them (`dev/commit_lint.py`). So a tool can +compute the next version and write the changelog from them. The owner wants automated versions and releases, with the back-merge pattern of the [Digital-Assistant-SDK](https://github.com/Digital-Assistant/Digital-Assistant-SDK/blob/main/RELEASE_PROCESS.md) repository. The greenfield template in this repository (`greenfield/templates/github/workflows/release-and-sync.yml`) @@ -48,7 +40,7 @@ uses the same pattern. 4. **The changelog is generated, then fixed for lint.** release-please writes `*` bullets and double blank lines. The `docs-lint` ratchet covers `CHANGELOG.md` and refuses both. So a job runs `make changelog-fix` (markdownlint `--fix`) on the release pull request and pushes the fix. The ratchet does not change. The recipes' own changelog, - `audit/CHANGELOG.md`, gets no new entries; the root changelog covers the recipes from v0.3.1 on. + `audit/CHANGELOG.md`, gets no new entries; the root changelog covers the recipes. 5. **Publishing.** `release.yml` has no tag-push trigger, because a tag made with `GITHUB_TOKEN` starts no workflow. `release-please.yml` dispatches it with the tag. A dispatch is the one event that `GITHUB_TOKEN` may start a workflow with. The maintainer can also dispatch it by hand. @@ -59,23 +51,16 @@ uses the same pattern. `RELEASE_APP_PRIVATE_KEY` are set. Each job asks it for the permissions that job needs. Without them, `GITHUB_TOKEN`, and the maintainer closes and reopens each bot pull request to start CI. ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| -| Keep releases manual (the design before this ADR) | The version strings drifted (`0.1.0` in the engine at v0.3.0), and the release preparation branch was hand work on every release. | +| Manual releases | Hand-kept version strings drift, and the release preparation branch is hand work on every release. | | release-please targets `dev` | The tag would be on `dev`. A release must be a tag on `main` ([ADR 0002](0002-dev-integrates-main-releases.md)). | | Keep `CHANGELOG.md` written by hand; release-please only bumps versions | Someone must still date `[Unreleased]` on every release. The owner chose a generated changelog. | | Take `CHANGELOG.md` out of the `docs-lint` ratchet | It relaxes a gate. The `--fix` job keeps the gate and costs one commit on the release pull request. | | Call `release.yml` as a reusable workflow | zizmor asks for the new `$/` self-repository syntax and the pinned actionlint rejects it. A dispatch needs neither and no suppression. | -| Back-merge from `main` itself | The `dev` ruleset requires an up-to-date head. Once `dev` moves, the pull request can only be updated by a push to `main`, which the `main` ruleset refuses. | - -> A later reversal of this decision is folded into this table, so the rejected direction -> keeps its rationale inside the accepted record. - ---- +| Back-merge from `main` itself | A conflict with `dev` is fixed by a merge commit on the pull request's head branch. With `main` as the head, that commit needs a push to `main`, which the `main` ruleset refuses. | ## Consequences @@ -83,26 +68,13 @@ uses the same pattern. * Nobody edits a version or the changelog by hand. The engine reports the released version. * The GitHub release notes are the changelog entry. -* A wrong commit type now shows: it puts the change under the wrong heading or bumps the wrong part of the version. +* A wrong commit type shows: it puts the change under the wrong heading or bumps the wrong part of the version. ### Negative / Trade-offs * Each release has two more pull requests to merge: the release pull request and the back-merge pull request. -* `main` now takes pull requests from two sources, `dev` and the release branch. +* `main` takes pull requests from two sources, `dev` and the release branch. * Without the GitHub App, CI does not start on the bot's pull requests until the maintainer closes and reopens them. -* The changelog lists commit subjects, not curated notes. A clear subject matters more than before. +* The changelog lists commit subjects, not curated notes. A clear subject matters. * A failed `release.yml` leaves a release with no files, because release-please creates the release first. The fix is a re-run (`RELEASE_PROCESS.md`, "If the release fails"). - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into - *Alternatives Considered*. Do NOT create a superseding ADR and do NOT leave a tombstone stub. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference - plus the index table in `docs/architecture/adrs/README.md` in the same change set. -* Archived wording: `git log --follow docs/architecture/adrs/0005-release-please-cuts-releases.md`. - -See [`docs/STYLE_GUIDE.md`](../../STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/docs/architecture/adrs/0006-first-run-agent-tool-offer.md b/docs/architecture/adrs/0006-first-run-agent-tool-offer.md index c0abdeb8..5924d920 100644 --- a/docs/architecture/adrs/0006-first-run-agent-tool-offer.md +++ b/docs/architecture/adrs/0006-first-run-agent-tool-offer.md @@ -1,19 +1,12 @@ # 0006. One Committed `.mcp.json`; Agents Offer Each Person a One-Time Setup for Their Harness -* **Status:** Accepted -* **Date:** 2026-11-09 -* **Last reviewed:** 2026-11-09 -* **Deciders:** [@nistapp] - ---- - ## Context This repository asks target repositories to register the code index, `codebase-memory-mcp` (`IDX-01`, -`REQ-SH-001`). Until v0.3.3 it did not register the index for itself, and its own self-audit showed that. +`REQ-SH-001`). It registers the index for itself too, so its own self-audit can pass that check. Six harnesses are in use. Each reads a different project file, and they act differently on the first start -(checked 2026-11-09 in each harness's own documentation; the table is in +(from each harness's own documentation; the table is in [Development tooling](../../reference/dev-tooling.md#agent-tools-mcp)): * Claude Code reads `.mcp.json` and asks each person to approve each server. @@ -24,9 +17,7 @@ Six harnesses are in use. Each reads a different project file, and they act diff * Antigravity keeps its servers at user level only (`agy mcp add`, checked with `agy` 1.5.1). An MCP server is a program that runs on the person's machine. Many people clone this repository only to audit -their own code; they do not need the server here. The owner decided on one committed file, not one per harness. - ---- +their own code; they do not need the server here. The owner wants one committed file, not one per harness. ## Decision @@ -56,13 +47,11 @@ their own code; they do not need the server here. The owner decided on one commi language server is an optional aid: `pyrightconfig.json` mirrors the `mypy.ini` import paths, and the offer does not cover it. ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| -| A committed config file for each harness | The owner chose one file. Six copies drift, and opencode would start a committed server with no prompt | +| A committed config file for each harness | The owner wants one file. Six copies drift, and opencode would start a committed server with no prompt | | `npx -y codebase-memory-mcp@` in `.mcp.json` | The package downloads a native binary when it runs. The version would need a second place or a generator. It can differ from the version that built the person's index | | A dedicated skill for the offer | Not every harness reads skills. Every harness reads `AGENTS.md` | | Detect the harness from environment variables only | Only Claude Code's variable is confirmed. A wrong guess writes the wrong file | @@ -71,8 +60,6 @@ their own code; they do not need the server here. The owner decided on one commi | Pre-approve the server in `.claude/settings.json` | It would start a program on every contributor's machine without asking | | No offer: document the setup only | The setup is easy to miss, and agents then read whole files where a graph query would do | ---- - ## Consequences ### Positive @@ -87,16 +74,3 @@ their own code; they do not need the server here. The owner decided on one commi * The translation follows each harness's current format (opencode `mcp.servers`, checked with opencode v2.0.24). A harness that changes its format needs a change in `dev/mcp_config.py`. * Each person installs a binary to get the benefit. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into - *Alternatives Considered*. Do NOT create a superseding ADR and do NOT leave a tombstone stub. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference - plus the index table in `docs/architecture/adrs/README.md` in the same change set. -* Archived wording: `git log --follow docs/architecture/adrs/0006-first-run-agent-tool-offer.md`. - -See [`docs/STYLE_GUIDE.md`](../../STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/docs/architecture/adrs/0007-security-verb-audits-dependencies-with-osv-scanner.md b/docs/architecture/adrs/0007-security-verb-audits-dependencies-with-osv-scanner.md index bc015f2a..ff15d6cc 100644 --- a/docs/architecture/adrs/0007-security-verb-audits-dependencies-with-osv-scanner.md +++ b/docs/architecture/adrs/0007-security-verb-audits-dependencies-with-osv-scanner.md @@ -1,15 +1,8 @@ # 0007. The Security Verb Audits the Dev Tools' Dependencies with osv-scanner -* **Status:** Accepted -* **Date:** 2026-11-09 -* **Last reviewed:** 2026-11-09 -* **Deciders:** [@nistapp] - ---- - ## Context -`make security` ran a secrets scan only. The repository's own audit reported `TOOL-04` PARTIAL: a security verb +A security verb that runs only a secrets scan leaves `TOOL-04` at PARTIAL in the repository's own audit: the verb exists, but it runs no dependency audit. The engine and the tools have no third-party dependencies. The dev tools do: about 500 packages across `dev/tools/uv.lock`, `dev/tools/package-lock.json` and their own dependencies ([ADR 0003](0003-dependabot-security-pull-requests-only.md)). @@ -20,9 +13,6 @@ variables or follow a prerequisite target. A separate `make deps-audit` target w The owner wants the most effective tool. Contributors may install a toolchain; users may not, and users never run `make security`: they run the standard-library `audit.pyz` and the skill bundles. -The first scan on 2026-11-09 found 8 advisories: uv (2), katex, re2js and 4 in Go modules. Five were fixed by -version updates. Three have no fix that reaches this repository and are accepted with a reason. - ## Decision 1. **osv-scanner runs inside `make security`.** After the secrets scan, the @@ -42,16 +32,13 @@ version updates. Three have no fix that reaches this repository and are accepted OpenSSF Scorecard does. 5. **No offline switch.** `make security` needs the network (osv.dev). CI has it; a contributor without it cannot run the gate. -6. **Contributors need Go 1.27 or newer.** `make setup` checks for it. Since 2026-11-09 actionlint is a Go tool - too (`make check`): its PyPI wrapper downloaded the binary from GitHub releases on each install, and a GitHub - outage failed CI. CI installs it with `actions/setup-go` from +6. **Contributors need Go 1.27 or newer.** `make setup` checks for it. actionlint is a Go tool + too (`make check`). CI installs it with `actions/setup-go` from `go.mod`; the release job does so without a cache. ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | pip-audit plus `npm audit --package-lock-only` | Two tools and a `uv export` step. Two advisory sources instead of OSV, which aggregates GitHub advisories, PyPA and the Go database. osv-scanner reads `uv.lock` directly. | | A separate `make deps-audit`; `security` stays offline | `TOOL-04` stays PARTIAL. Changing the engine to accept it would bend a check to suit this repository. | @@ -59,13 +46,9 @@ version updates. Three have no fix that reaches this repository and are accepted | osv-scanner binary on `PATH`, version in a pin file | Dependabot cannot see a pin file. Every contributor installs it by hand, at any version. | | `google/osv-scanner-action` in CI only | CI would run something other than `make security`. Local and CI results could differ. | | Report only, exit 0 | `TOOL-04` would pass on a check that never blocks. | +| actionlint through its PyPI wrapper | The wrapper downloads the binary from GitHub releases on each install, so a GitHub outage fails CI. | | Scan only the Python and Node lockfiles | The Go modules of osv-scanner itself would raise Dependabot alerts that no gate checks. | -> A later reversal of this decision is folded into this table, so the rejected direction -> keeps its rationale inside the accepted record. - ---- - ## Consequences ### Positive @@ -81,16 +64,3 @@ version updates. Three have no fix that reaches this repository and are accepted * A new advisory turns every pull request red until someone fixes or accepts it. That is intended. * `go.mod` and `go.sum` add about 700 lines. A raised indirect Go dependency differs from the set that upstream osv-scanner tested. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into - *Alternatives Considered*. Do NOT create a superseding ADR and do NOT leave a tombstone stub. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference - plus the index table in `docs/architecture/adrs/README.md` in the same change set. -* Archived wording: `git log --follow docs/architecture/adrs/0007-security-verb-audits-dependencies-with-osv-scanner.md`. - -See [`docs/STYLE_GUIDE.md`](../../STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/docs/architecture/adrs/0008-one-release-version-and-current-schemas-only.md b/docs/architecture/adrs/0008-one-release-version-and-current-schemas-only.md index ed093949..c7450cff 100644 --- a/docs/architecture/adrs/0008-one-release-version-and-current-schemas-only.md +++ b/docs/architecture/adrs/0008-one-release-version-and-current-schemas-only.md @@ -1,27 +1,16 @@ # 0008. Stamp One Release Version in Every Report and State, and Read Only the Current Schemas -* **Status:** Accepted -* **Date:** 2026-11-09 -* **Last reviewed:** 2026-11-09 -* **Deciders:** [@nistapp] - ---- - ## Context A report from release 0.3.0 and a report from release 0.3.3 can differ: other checks, other wording, other fields. -Before this decision, no file said which release made it. The audit report carried `recipe.version: 0.1.0` and the -implement state `0.2.0-dev`, whatever the release. A user who installs a release bundle has no git checkout, so the -report's commit was `null` too. The implement recipe could not tell if a report was too old for it, and a resumed run -could not tell if the framework changed between sessions. +Without a release stamp, no file says which release made it. A user who installs a release bundle has no git +checkout, so the report's commit is `null` too. The implement recipe then cannot tell if a report is too old for it, +and a resumed run cannot tell if the framework changed between sessions. release-please manages one package, `.`, and the audit and implement recipes ship together in one release ([ADR 0005](0005-release-please-cuts-releases.md)). -The framework has no outside user yet. The public releases so far served to test the CI and release pipeline. So -there is no old report or state that a tool must keep reading. - ---- +The framework has no outside user yet, so there is no old report or state that a tool must keep reading. ## Decision @@ -47,11 +36,9 @@ there is no old report or state that a tool must keep reading. [`docs/reference/versions.md`](../../reference/versions.md) lists every number, where it is written and each message. ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | A version per component: one for the audit recipe, one for the implement recipe | Two release-please packages, two tags and a compatibility table between them. The recipes ship together. | | One shared constant that both tools import, such as the engine's `__version__` | The implement bundle loads the engine from the audit skill beside it. A mixed install would stamp the audit skill's version as the implement recipe's. | @@ -62,11 +49,6 @@ there is no old report or state that a tool must keep reading. | A `framework_history[]` list in the state | It repeats what the log holds. | | Write the end of the policy in this ADR only | Easy to forget at 1.0.0. The test makes the release fail until someone decides. | -> A later reversal of this decision is folded into this table, so the rejected direction -> keeps its rationale inside the accepted record. - ---- - ## Consequences ### Positive @@ -78,20 +60,7 @@ there is no old report or state that a tool must keep reading. ### Negative / Trade-offs * A change to only one recipe still bumps the release version that both recipes carry. -* Every report and state made before this decision is unreadable by the current tools. Run a new audit. +* A report or state from another release is unreadable by the current tools. Run a new audit. * Between two releases, every checkout carries the last released version. Only the commit tells two such reports apart, and a bundle has no commit. * The "first outside user" trigger has no check. Someone has to notice it. - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into - *Alternatives Considered*. Do NOT create a superseding ADR and do NOT leave a tombstone stub. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference - plus the index table in `docs/architecture/adrs/README.md` in the same change set. -* Archived wording: `git log --follow docs/architecture/adrs/0008-one-release-version-and-current-schemas-only.md`. - -See [`docs/STYLE_GUIDE.md`](../../STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/docs/architecture/adrs/README.md b/docs/architecture/adrs/README.md index 93570a58..c6e54d0b 100644 --- a/docs/architecture/adrs/README.md +++ b/docs/architecture/adrs/README.md @@ -6,52 +6,52 @@ The root router is [`docs/README.md`](../../README.md). ## Repository level: `docs/architecture/adrs/` -| ADR | Title | Status | Date | -| --- | --- | --- | --- | -| [0001](0001-decisions-live-beside-the-component.md) | Decisions Live Beside the Component They Govern | Accepted | 2026-11-05 | -| [0002](0002-dev-integrates-main-releases.md) | `dev` Integrates Every Change; `main` Takes Only Promotions From `dev` and the Release Pull Request | Accepted | 2026-11-08 | -| [0003](0003-dependabot-security-pull-requests-only.md) | Dependabot Opens Security Pull Requests Only; Major Versions Are Advisory | Accepted | 2026-11-08 | -| [0004](0004-plain-language-rules-and-a-prose-check.md) | Plain-Language Rules in One File, Checked by a Standard-Library Prose Check | Accepted | 2026-11-08 | -| [0005](0005-release-please-cuts-releases.md) | Release-Please Computes the Version, Writes the Changelog and Tags the Release | Accepted | 2026-11-08 | -| [0006](0006-first-run-agent-tool-offer.md) | One Committed `.mcp.json`; Agents Offer Each Person a One-Time Setup for Their Harness | Accepted | 2026-11-09 | -| [0007](0007-security-verb-audits-dependencies-with-osv-scanner.md) | The Security Verb Audits the Dev Tools' Dependencies with osv-scanner | Accepted | 2026-11-09 | -| [0008](0008-one-release-version-and-current-schemas-only.md) | Stamp One Release Version in Every Report and State, and Read Only the Current Schemas | Accepted | 2026-11-09 | +| ADR | Title | Summary | +| --- | --- | --- | +| [0001](0001-decisions-live-beside-the-component.md) | Decisions Live Beside the Component They Govern | Each ADR lives in the folder of the component it governs; this page indexes all of them. | +| [0002](0002-dev-integrates-main-releases.md) | `dev` Integrates Every Change; `main` Takes Only Promotions From `dev` and the Release Pull Request | Every change merges into `dev`; `main` takes only promotions from `dev` and the release pull request. | +| [0003](0003-dependabot-security-pull-requests-only.md) | Dependabot Opens Security Pull Requests Only; Major Versions Are Advisory | Dependabot opens security pull requests only; a major-version upgrade is the maintainer's manual step. | +| [0004](0004-plain-language-rules-and-a-prose-check.md) | Plain-Language Rules in One File, Checked by a Standard-Library Prose Check | `docs/COMMUNICATION_RULES.md` holds the writing rules; `make docs-prose` checks them and never fails. | +| [0005](0005-release-please-cuts-releases.md) | Release-Please Computes the Version, Writes the Changelog and Tags the Release | release-please sets the version from commit subjects, writes `CHANGELOG.md` and tags `vX.Y.Z`. | +| [0006](0006-first-run-agent-tool-offer.md) | One Committed `.mcp.json`; Agents Offer Each Person a One-Time Setup for Their Harness | One committed `.mcp.json`; each agent offers its person a one-time, consented setup for their harness. | +| [0007](0007-security-verb-audits-dependencies-with-osv-scanner.md) | The Security Verb Audits the Dev Tools' Dependencies with osv-scanner | `make security` runs osv-scanner over every dev-tool lockfile after the secrets scan. | +| [0008](0008-one-release-version-and-current-schemas-only.md) | Stamp One Release Version in Every Report and State, and Read Only the Current Schemas | Reports and state files carry the one release version; readers accept only the current schemas. | ## Requirements: `spec/decisions/` -| ADR | Title | Status | Date | -| --- | --- | --- | --- | -| [0001](../../../spec/decisions/0001-constraint-artifact.md) | Use the prohibitions section as the deny list | Accepted | 2026-10-10 | -| [0002](../../../spec/decisions/0002-lint-in-the-gate.md) | Lint is gated through `format:check` | Accepted | 2026-11-05 | -| [0003](../../../spec/decisions/0003-dependency-scan-verb.md) | Any verb may run the dependency scan; `security` is the preferred one | Accepted | 2026-11-10 | +| ADR | Title | Summary | +| --- | --- | --- | +| [0001](../../../spec/decisions/0001-constraint-artifact.md) | Use the prohibitions section as the deny list | The deny list is the instruction file's prohibitions section; there is no separate allow list. | +| [0002](../../../spec/decisions/0002-lint-in-the-gate.md) | Lint is gated through `format:check` | `check` is `format:check`, `typecheck` and `test`; `format:check` must include the lint rules. | +| [0003](../../../spec/decisions/0003-dependency-scan-verb.md) | Any verb may run the dependency scan; `security` is the preferred one | A verb must run a dependency scan; it should be `security`, and CI should run it too. | ## Audit recipe and tools: `audit/docs/architecture/adrs/` -| ADR | Title | Status | Date | -| --- | --- | --- | --- | -| [0001](../../../audit/docs/architecture/adrs/0001-tools-import-engine.md) | Tools Import the Engine in Process | Accepted | 2026-10-10 | +| ADR | Title | Summary | +| --- | --- | --- | +| [0001](../../../audit/docs/architecture/adrs/0001-tools-import-engine.md) | Tools Import the Engine in Process | Audit tools import the engine in process through `load_engine`; the engine never imports the tools. | ## Implement recipe and tools: `implement/docs/architecture/adrs/` -The numbers continue from the audit group; both groups were one set before v0.3. +Number 0001 is retired in this group. -| ADR | Title | Status | Date | -| --- | --- | --- | --- | -| [0002](../../../implement/docs/architecture/adrs/0002-progress-writes-state-in-target.md) | `progress.py` Writes the State Files Inside the Target | Accepted | 2026-10-29 | -| [0003](../../../implement/docs/architecture/adrs/0003-push-and-pr-policy.md) | The Implement Recipe Pushes and Opens a Pull Request Only When the Owner Opted In | Accepted | 2026-11-05 | +| ADR | Title | Summary | +| --- | --- | --- | +| [0002](../../../implement/docs/architecture/adrs/0002-progress-writes-state-in-target.md) | `progress.py` Writes the State Files Inside the Target | `progress.py` may write only the target's two state files under `docs/agentic-readiness/`. | +| [0003](../../../implement/docs/architecture/adrs/0003-push-and-pr-policy.md) | The Implement Recipe Pushes and Opens a Pull Request Only When the Owner Opted In | The recipe stops at a local branch unless the owner opted in to a push and one pull request. | ## Audit engine: `audit/engine/docs/architecture/adrs/` The engine's own index is in its [`architecture/README.md`](../../../audit/engine/docs/architecture/README.md#adr-index). -| ADR | Title | Status | Date | -| --- | --- | --- | --- | -| [0001](../../../audit/engine/docs/architecture/adrs/0001-report-not-a-gate.md) | The Audit Reports; It Does Not Gate | Accepted | 2026-09-28 | -| [0002](../../../audit/engine/docs/architecture/adrs/0002-read-only-and-tier-c-opt-in.md) | Read-Only by Default; Tier-C Probes Are Opt-In | Accepted | 2026-09-28 | -| [0003](../../../audit/engine/docs/architecture/adrs/0003-component-depth-declared-depth-1.md) | Component Depth: Declared Components, Depth 1 | Accepted | 2026-09-28 | -| [0004](../../../audit/engine/docs/architecture/adrs/0004-no-llm-in-v1.md) | No LLM Calls in v1 | Accepted | 2026-09-28 | -| [0005](../../../audit/engine/docs/architecture/adrs/0005-module-layout-and-zipapp-distribution.md) | Module Layout and Zipapp Distribution | Accepted | 2026-09-28 | -| [0006](../../../audit/engine/docs/architecture/adrs/0006-private-check-helpers-and-ignore-engine.md) | Private Check Helpers and the Ignore Engine | Accepted | 2026-09-28 | -| [0007](../../../audit/engine/docs/architecture/adrs/0007-json-report-render-contract.md) | The JSON Report Is the Render Contract; the Markdown Is a Template Over It | Accepted | 2026-09-30 | -| [0008](../../../audit/engine/docs/architecture/adrs/0008-what-counts-as-the-repository.md) | What Counts as the Repository | Accepted | 2026-11-05 | -| [0009](../../../audit/engine/docs/architecture/adrs/0009-nested-ignore-files-and-negation.md) | Nested Ignore Files and Negation | Accepted | 2026-11-05 | +| ADR | Title | Summary | +| --- | --- | --- | +| [0001](../../../audit/engine/docs/architecture/adrs/0001-report-not-a-gate.md) | The Audit Reports; It Does Not Gate | A completed scan exits 0 whatever it finds; `--fail-on` exists but is off by default. | +| [0002](../../../audit/engine/docs/architecture/adrs/0002-read-only-and-tier-c-opt-in.md) | Read-Only by Default; Tier-C Probes Are Opt-In | The engine never writes to or installs into the target; Tier-C probes run only on explicit request. | +| [0003](../../../audit/engine/docs/architecture/adrs/0003-component-depth-declared-depth-1.md) | Component Depth: Declared Components, Depth 1 | Only components a build manifest declares are scored, and only at depth 1. | +| [0004](../../../audit/engine/docs/architecture/adrs/0004-no-llm-in-v1.md) | No LLM Calls in v1 | The engine is deterministic and standard-library only, with no model calls. | +| [0005](../../../audit/engine/docs/architecture/adrs/0005-module-layout-and-zipapp-distribution.md) | Module Layout and Zipapp Distribution | The package tree is the source; a zipapp is the distributable; nothing needs installing. | +| [0006](../../../audit/engine/docs/architecture/adrs/0006-private-check-helpers-and-ignore-engine.md) | Private Check Helpers and the Ignore Engine | Checks share two private helpers, `audit/ignore.py` and `rules/checks/_common.py`; no new stage or pack. | +| [0007](../../../audit/engine/docs/architecture/adrs/0007-json-report-render-contract.md) | The JSON Report Is the Render Contract; the Markdown Is a Template Over It | The JSON report (schema v2) is the contract; the Markdown report is a code template over it. | +| [0008](../../../audit/engine/docs/architecture/adrs/0008-what-counts-as-the-repository.md) | What Counts as the Repository | Runners rank depth first; templates, examples and fixtures are set aside, and the report lists them. | +| [0009](../../../audit/engine/docs/architecture/adrs/0009-nested-ignore-files-and-negation.md) | Nested Ignore Files and Negation | Four checks use a scoped matcher that honours nested `.gitignore` files and negation; the walk is unchanged. | diff --git a/docs/reference/implement-master.md b/docs/reference/implement-master.md index 8843b8ec..d1a85564 100644 --- a/docs/reference/implement-master.md +++ b/docs/reference/implement-master.md @@ -269,7 +269,7 @@ Full playbook: [`add-baselines-and-ratchet.md`](../../implement/playbooks/add-ba ### `establish-docs-contract` An agent finds permanent knowledge in one place: `docs/` has an index, a short style guide that the instruction -file names, a decision log with a template and an index, and a short architecture page with a diagram that was +file names, a decision log with a policy, a template and an index, and a short architecture page with a diagram that was checked. Scratch work stays out of version control. Structural traps (very large files, binaries) are listed, not moved. @@ -278,7 +278,7 @@ not moved. | [`DOC-01` docs/ exists](audit-master.md#doc-01-docs-exists) | Confirms `docs/` holds Markdown, adds `docs/README.md` as the index; never creates a second docs home | | [`DOC-02` docs/STYLE_GUIDE.md exists](audit-master.md#doc-02-docsstyle_guidemd-exists) | Adds a one-page `docs/STYLE_GUIDE.md` and one line in the instruction file that names it | | [`DOC-03` artefacts/ exists and is ignored](audit-master.md#doc-03-artefacts-exists-and-is-ignored) | Verifies the scratch directory rule; adds the ignore rule only if a scratch directory exists | -| [`DOC-04` ADR directory and index](audit-master.md#doc-04-adr-directory-and-index) | Adds `docs/adr/` with a template, an index and two or three first records marked `proposed` | +| [`DOC-04` ADR directory and index](audit-master.md#doc-04-adr-directory-and-index) | Adds `docs/adr/` with an ADR policy page, a template, an index and two or three first records for the owner to review | | [`DOC-05` Docs are readable](audit-master.md#doc-05-docs-are-readable) | Not written yet: the steps that add plain-language rules to the style guide and a prose check are planned. Until then, report the finding to the owner and leave it open | | [`NAV-01` Declared component boundaries](audit-master.md#nav-01-declared-component-boundaries) | Verification only: lists the declared components in the architecture page | | [`NAV-02` Obvious entry points](audit-master.md#nav-02-obvious-entry-points) | Verification only: names the entry points in the architecture page | @@ -292,11 +292,12 @@ not moved. - Do not create a second `docs/` or restructure the existing report folders. - Do not copy content, paths, names or values from a private repository or workspace into an open-source repository's docs or ADRs, or from any target repository into the framework. -- Do not invent the reason behind a decision. Inferred means "proposed" and says it is inferred. +- Do not invent the reason behind a decision. Say what is inferred, or write "Rationale not recorded; owner to + fill in". - Do not move, resize, delete or untrack files for NAV-03. - Do not put values from env files, credentials, realm exports or keystores in any page. - Do not install a docs tool the owner has not approved, and do not use an unpinned `npx` in CI. -- Do not accept an ADR on the owner's behalf. +- Do not add a status, date or approval line to an ADR. The owner's merge is the acceptance. Full playbook: [`establish-docs-contract.md`](../../implement/playbooks/establish-docs-contract.md). diff --git a/docs/templates/adr-template.md b/docs/templates/adr-template.md index d2ce290c..b3a2d22a 100644 --- a/docs/templates/adr-template.md +++ b/docs/templates/adr-template.md @@ -1,51 +1,30 @@ -# [NNNN]. [Short Imperative Title] +# NNNN. Short Imperative Title -* **Status:** Accepted *(Proposed is permitted in-flight but MUST NOT be merged)* -* **Date:** YYYY-MM-DD -* **Last reviewed:** YYYY-MM-DD -* **Deciders:** [@github-handle] - ---- + ## Context -[Technical context, problem statement, business driver, and constraints. -Mention what alternative solutions were evaluated.] - ---- + ## Decision -[Chosen design, pattern, or dependency. Links to affected source files.] - ---- + ## Alternatives Considered -| Alternative | Why it was rejected | -|---|---| -| [Option B] | [Reason] | - -> A later reversal of this decision is folded into this table, so the rejected direction -> keeps its rationale inside the accepted record. +| Alternative | Why it is rejected | +| --- | --- | +| Option B | Reason | ---- + ## Consequences ### Positive -* [Key benefit 1] -### Negative / Trade-offs -* [Known trade-off] - ---- +- Benefit -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current design.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into *Alternatives Considered*. Do NOT create a superseding ADR and do NOT leave a tombstone stub. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference plus the index table in `docs/architecture/README.md` in the same change set. -* Archived wording: `git log --follow docs/architecture/adrs/NNNN-title.md`. +### Negative / Trade-offs -See [`docs/STYLE_GUIDE.md`](../STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. +- Trade-off diff --git a/greenfield/templates/AGENTS.md b/greenfield/templates/AGENTS.md index 031342ba..17d21c31 100644 --- a/greenfield/templates/AGENTS.md +++ b/greenfield/templates/AGENTS.md @@ -108,7 +108,7 @@ The `codebase-memory-mcp` tool is mandatory. You must use it proactively before - **`docs/` is the single source of truth** for permanent documentation; `artefacts/` is transient and off-limits to agents (see §4). - **Every `.md` file under `docs/` MUST follow `docs/STYLE_GUIDE.md`** — the canonical authoring rules. Link to the relevant section rather than restating rules here, because restated rules drift. - **Definition of done:** when a change alters a public interface, observable behaviour, architecture, or an ADR, update the affected documentation pages, their source anchors, and the ADR index tables **in the same change set**. Pure internal refactors with no observable or documentation impact do not require doc edits. -- New or revised ADRs MUST follow `docs/STYLE_GUIDE.md` § ADR Lifecycle and be registered in `docs/architecture/README.md`. +- **Every ADR you add, change or delete follows `docs/ADR_POLICY.md`.** Register every ADR in `docs/architecture/README.md`. --- @@ -147,7 +147,7 @@ section only states the layout and the triggers; the style guide states the rule - **Single Source of Truth:** `docs/` is the only valid directory for permanent documentation. - **Categories:** content is classified as tutorial, how-to, reference, or explanation; architecture material lives under `docs/architecture/`. -- **ADRs:** Architectural Decision Records live in `docs/architecture/adrs/NNNN-short-title.md` and are indexed in `docs/architecture/README.md`. New runtime dependencies or architectural changes require an ADR. +- **ADRs:** Architectural Decision Records live in `docs/architecture/adrs/NNNN-short-title.md` and are indexed in `docs/architecture/README.md`. New runtime dependencies or architectural changes require an ADR. The rules: `docs/ADR_POLICY.md`. - **Transience:** Agent scratchpads, implementation plans, and WIP research go in `artefacts/` (git-ignored contents, and off-limits to agents per §4). - **Glossary:** domain terms are defined once in `docs/architecture/glossary.md` and used consistently everywhere else. @@ -175,7 +175,7 @@ These rules apply to chat replies, plans, reports, commit messages, pull request ## 10. Explicit "Do Not Do" Rules -1. ❌ **Do not** introduce runtime dependencies without an approved ADR. +1. ❌ **Do not** introduce runtime dependencies without an ADR that records the decision. 2. ❌ **Do not** use `any`, `// @ts-ignore`, or `as any`. 3. ❌ **Do not** merge failing tests. Fix them. 4. ❌ **Do not** skip `codebase-memory-mcp` indexing at the start of a session. diff --git a/greenfield/templates/CONTRIBUTING.md b/greenfield/templates/CONTRIBUTING.md index f82ba998..888b38ed 100644 --- a/greenfield/templates/CONTRIBUTING.md +++ b/greenfield/templates/CONTRIBUTING.md @@ -44,4 +44,4 @@ Thank you for considering contributing to this project! - `docs/` is the single source of truth for permanent documentation; `artefacts/` is transient scratch (agents must not read it unless you pass an explicit path). - Every `.md` file under `docs/` MUST follow [`docs/STYLE_GUIDE.md`](docs/STYLE_GUIDE.md). - A change that alters a public interface, observable behaviour, architecture, or an ADR MUST update the affected doc pages and their source anchors in the same PR. -- New ADRs: `docs/architecture/adrs/NNNN-short-title.md`, plus a row in `docs/architecture/README.md`. +- New ADRs: `docs/architecture/adrs/NNNN-short-title.md`, plus a row in `docs/architecture/README.md`. Follow `docs/ADR_POLICY.md`. diff --git a/greenfield/templates/docs/ADR_POLICY.md b/greenfield/templates/docs/ADR_POLICY.md new file mode 100644 index 00000000..48a9379a --- /dev/null +++ b/greenfield/templates/docs/ADR_POLICY.md @@ -0,0 +1,35 @@ +# ADR policy + +This page says how to write, change and delete an architecture decision record (ADR) in this repository. It is a +variant of the usual ADR conventions, tuned for AI agents rather than people. Agents load ADRs as instructions, +so a stale or historical ADR is a defect, not a record. + +ADRs live in [`architecture/adrs/`](architecture/adrs/) as `NNNN-short-title.md`. The index is +[`architecture/README.md`](architecture/README.md). + +## Rules + +1. **Git is the archive.** Do not manage an ADR lifecycle. An ADR has no status, date, review stamp, list of + deciders or change log. `git log --follow ` gives its history. +2. **State the current decision only.** Write in the present tense. Do not write "previously", "in v1 we" or + "was changed on". Leave out plan step IDs, ticket IDs and dated notes. +3. **Decision changed: overwrite the ADR in place.** Keep the file name and the number. Turn the old choice into + one row of *Alternatives Considered*, with the reason it is rejected now. Never write a second ADR that + supersedes the first. +4. **Decision superseded or no longer needed: delete the file.** In the same change, remove its index row and every + link to it and mention of it. Never refer to a deleted ADR again, not even to say it was removed. +5. **Number a new ADR** as the highest number ever used, plus one. Find deleted numbers with + `git log --diff-filter=D --name-only -- docs/architecture/adrs/`. Never reuse a number, so an old commit + message cannot point at a different decision. +6. **Write an ADR only when it earns its place.** Record a decision an agent would otherwise get wrong or reopen. + Typical cases are a runtime dependency, a boundary, a safety rule or a format contract. Put a one-line rule in + `AGENTS.md` instead. Put a local detail in a code comment. +7. **Keep it small.** One decision per ADR, under about 100 lines. Link to the code, the specs and other pages; do + not restate them. +8. **Index it.** The index lists every ADR: number, title and a one-line summary. Add, change or remove the row in + the same change as the ADR. +9. **Start from the template**, [`templates/adr-template.md`](templates/adr-template.md). Keep its four sections: + Context, Decision, Alternatives Considered, Consequences. +10. **Never invent a rationale.** When the reason for a decision is not in the repository, write "Rationale not + recorded; owner to fill in". +11. **Read only the ADRs you need.** Use the index summaries to pick the ADRs that govern the code you change. diff --git a/greenfield/templates/docs/STYLE_GUIDE.md b/greenfield/templates/docs/STYLE_GUIDE.md index f78885b5..112517f6 100644 --- a/greenfield/templates/docs/STYLE_GUIDE.md +++ b/greenfield/templates/docs/STYLE_GUIDE.md @@ -12,7 +12,8 @@ 2. **Zero Specification Drift:** Code and architecture specs MUST remain synchronized. Every behavioral change requires a matching spec update **in the same change set**. 3. **Empirical Grounding:** Never document aspirational features as existing facts. All claims must be verifiable against source code or tests; planned work MUST carry an explicit notice banner. 4. **Symbol Anchoring:** Reference code symbols using fully-qualified names and explicit file paths with markdown links to source line anchors (e.g. `[orchestrator.ts#L37-L61](../src/core/orchestrator.ts#L37-L61)`). -5. **Living ADRs (Current-State Only):** An ADR MUST describe only the shipped, current codebase. When a decision changes, revise the ADR body **in place**; when an ADR becomes irrelevant, delete it. Git is the archive — never keep tombstone, superseded, or deprecated stubs. +5. **Current-State ADRs:** An ADR states only the current decision; git is the archive. The rules are in + [`ADR_POLICY.md`](ADR_POLICY.md). --- @@ -48,6 +49,7 @@ Rules: | This style guide | `docs/STYLE_GUIDE.md` | `STYLE_GUIDE.md` | | Architecture overview / explanations | `docs/architecture/` | `.md` | | Architectural Decision Records | `docs/architecture/adrs/` | `NNNN-short-title.md` | +| ADR policy | `docs/ADR_POLICY.md` | `ADR_POLICY.md` | | ADR index & doc router | `docs/architecture/README.md` | `README.md` | | Domain glossary | `docs/architecture/glossary.md` | `glossary.md` | | How-to guides | `docs/how-to/` | `.md` | @@ -65,7 +67,7 @@ New documents MUST start from the matching template in `docs/templates/`. 1. **Query-first:** consult `codebase-memory-mcp` (`search_graph`, `get_code_snippet`, `get_architecture`) to verify symbols and file locations against the current codebase before grepping or reading whole files. 2. Check existing pages in `docs/` — update rather than duplicate. -3. Check `docs/architecture/adrs/` for the highest sequence number ever used before creating an ADR. +3. Before you add, change or delete an ADR, read [`ADR_POLICY.md`](ADR_POLICY.md). ### 5.2 Doc Maintenance Trigger (definition of done) @@ -85,40 +87,10 @@ Pure internal refactors with no observable or documentation impact do not requir --- -## 6. ADR Lifecycle +## 6. ADRs -ADRs live in `docs/architecture/adrs/` and are created from `docs/templates/adr-template.md`. - -```markdown -# NNNN. Short Title - -* **Status:** Accepted -* **Date:** YYYY-MM-DD -* **Last reviewed:** YYYY-MM-DD -* **Deciders:** [@github-handle] - -## Context -[Technical context, problem statement, alternatives considered.] - -## Decision -[Chosen architecture, pattern, or dependency, with links to source interfaces.] - -## Consequences -### Positive -* [Benefit] - -### Negative / Trade-offs -* [Trade-off] -``` - -1. **Create:** assign `highest-ever sequence number + 1`. A number whose ADR was deleted is retired and MUST NOT be reused. -2. **Record:** use `docs/templates/adr-template.md`. -3. **Index:** add a row to the index table in `docs/architecture/README.md` in the same change set. -4. **Revise in place:** when a decision changes, overwrite the ADR body and keep its number. Fold the reversal into the ADR's own *Alternatives considered* table. Do NOT create a second "superseding" ADR and do NOT leave a tombstone stub. Use `git log --follow` to recover older wording. -5. **Delete when irrelevant:** when an ADR no longer describes anything in the current codebase, delete the file and update every inbound reference in the same change set. Feature-level deprecation belongs in the glossary or roadmap, never as a lingering ADR. - -> [!IMPORTANT] -> There is no `Deprecated`, `Superseded`, or `Rejected` status. A merged ADR is always `Accepted` (`Proposed` is allowed in-flight but MUST NOT be merged). Stale instructions pollute agent context, so staleness is treated as a defect, not as history. +ADRs live in `docs/architecture/adrs/` and start from `docs/templates/adr-template.md`. How to add, change or +delete one: [`ADR_POLICY.md`](ADR_POLICY.md). --- diff --git a/greenfield/templates/docs/architecture/README.md b/greenfield/templates/docs/architecture/README.md index 2e175d12..5864c60b 100644 --- a/greenfield/templates/docs/architecture/README.md +++ b/greenfield/templates/docs/architecture/README.md @@ -2,9 +2,9 @@ ## ADR Index -| # | Title | Status | Date | -|---|---|---|---| -| [0001](adrs/0001-initial-architecture.md) | Initial Architecture & Technology Choices | Accepted | YYYY-MM-DD | +| # | Title | Summary | +|---|---|---| +| [0001](adrs/0001-initial-architecture.md) | Initial Architecture & Tooling Choices | TypeScript strict on Node 24, a pure DI core, Vitest, Biome and seven package-script verbs. | ## Key Documents @@ -25,4 +25,5 @@ | *Pages added as the project evolves* | — | > [!TIP] -> When adding a new ADR, assign the next sequence number and add a row to the index table above before merging. +> Before you add, change or delete an ADR, read [`ADR_POLICY.md`](../ADR_POLICY.md). Add, change or remove its +> row in this table in the same change. diff --git a/greenfield/templates/docs/architecture/adrs/0001-initial-architecture.md b/greenfield/templates/docs/architecture/adrs/0001-initial-architecture.md index 6c8664de..dfd9a4d1 100644 --- a/greenfield/templates/docs/architecture/adrs/0001-initial-architecture.md +++ b/greenfield/templates/docs/architecture/adrs/0001-initial-architecture.md @@ -1,23 +1,10 @@ # 0001. Initial Architecture & Tooling Choices -* **Status:** Accepted -* **Date:** YYYY-MM-DD -* **Last reviewed:** YYYY-MM-DD -* **Deciders:** [@github-handle] - ---- - ## Context -Day-0 decisions for this repository, made before any application code exists. AI coding -agents will work in this repository from commit #1, so the choices below are optimised for -**agent determinism** (one obvious way to verify work) and **bounded blast radius** (explicit -DI seams, no ambient OS access in core logic) rather than for human convenience alone. - -Alternatives considered are recorded in the table below, including the option of deferring -the decision and retrofitting later. - ---- +These are the foundation choices of this repository. AI coding agents work here from the first +commit. So the choices favour **agent determinism**: one obvious way to verify work. They also favour +**bounded blast radius**: explicit DI seams and no ambient OS access in core logic. ## Decision @@ -32,19 +19,15 @@ the decision and retrofitting later. | Documentation | `docs/` is the source of truth, governed by `docs/STYLE_GUIDE.md`; `AGENTS.md` points at it and states the definition of done | Rules live in one place; restating them elsewhere guarantees drift. | | Docs hosting of transient work | `artefacts/` (git-ignored contents, off-limits to agents) | Keeps unverified scratch out of agent context and out of the index. | ---- - ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | ESLint + Prettier instead of Biome | Two tools, two configs, two ignore mechanisms — and `format:check` would no longer cover lint, so the seven-verb surface would keep a `lint` verb with nothing behind it. Costed concretely: a 9-file swap that redefines `lint` as a `typecheck` alias. Revisit only if a required rule set is unavailable in Biome. | | Makefile or Taskfile as the primary runner | Adds a second language and a second definition of the gate. Acceptable only as a thin wrapper over the same package scripts. | | `lint` as a separate code linter with no type-check verb | Conflating lint and type-check hides which gate failed and tempts agents to "fix" formatting instead of types. | | Retrofitting standards after the first feature | Standards applied to a moving codebase become a migration project instead of a Day-0 default. | -| Tombstone/superseded ADR stubs | Git is the archive; stale stubs are consumed by agents as current instruction. ADRs here are living, current-state documents. | - ---- +| Tombstone or superseded ADR stubs | Git is the archive; agents read stale stubs as current instructions. ADRs follow [`docs/ADR_POLICY.md`](../../ADR_POLICY.md). | ## Consequences @@ -61,14 +44,3 @@ the decision and retrofitting later. * A single aggregate gate is slower than running only the check you think you need. * Requiring an ADR for new runtime dependencies adds friction to dependency adoption (intentional). - ---- - -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current codebase.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into *Alternatives Considered*. Do NOT create a superseding ADR and do NOT leave a tombstone stub. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference plus the index table in `docs/architecture/README.md` in the same change set. -* Archived wording: `git log --follow docs/architecture/adrs/NNNN-title.md`. - -See [`docs/STYLE_GUIDE.md`](../../STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. diff --git a/greenfield/templates/docs/templates/adr-template.md b/greenfield/templates/docs/templates/adr-template.md index 64ce298d..b3a2d22a 100644 --- a/greenfield/templates/docs/templates/adr-template.md +++ b/greenfield/templates/docs/templates/adr-template.md @@ -1,51 +1,30 @@ -# [NNNN]. [Short Imperative Title] +# NNNN. Short Imperative Title -* **Status:** Accepted *(Proposed is permitted in-flight but MUST NOT be merged)* -* **Date:** YYYY-MM-DD -* **Last reviewed:** YYYY-MM-DD -* **Deciders:** [@github-handle] - ---- + ## Context -[Technical context, problem statement, business driver, and constraints. -Mention what alternative solutions were evaluated.] - ---- + ## Decision -[Chosen design, pattern, or dependency. Links to affected source interfaces.] - ---- + ## Alternatives Considered -| Alternative | Why it was rejected | -|---|---| -| [Option B] | [Reason] | - -> A later reversal of this decision is folded into this table, so the rejected direction -> keeps its rationale inside the accepted record. +| Alternative | Why it is rejected | +| --- | --- | +| Option B | Reason | ---- + ## Consequences ### Positive -* [Key benefit 1] -### Negative / Trade-offs -* [Known trade-off] - ---- +- Benefit -## Lifecycle — Living, Current-State ADRs - -* **This ADR MUST describe only the shipped, current codebase.** There is no `Deprecated`, `Superseded`, or `Rejected` status. -* **Decision changed?** Overwrite the body, keep the file and its number, and fold the reversal into *Alternatives Considered*. Do NOT create a superseding ADR and do NOT leave a tombstone stub. -* **Decision no longer relevant?** Delete the file, retire its number forever, and update every inbound reference plus the index table in `docs/architecture/README.md` in the same change set. -* Archived wording: `git log --follow docs/architecture/adrs/NNNN-title.md`. +### Negative / Trade-offs -See [`docs/STYLE_GUIDE.md`](../STYLE_GUIDE.md) § ADR Lifecycle for the canonical rules. +- Trade-off diff --git a/greenfield/templates/github/PULL_REQUEST_TEMPLATE.md b/greenfield/templates/github/PULL_REQUEST_TEMPLATE.md index 8e40df4e..ca0004b8 100644 --- a/greenfield/templates/github/PULL_REQUEST_TEMPLATE.md +++ b/greenfield/templates/github/PULL_REQUEST_TEMPLATE.md @@ -17,6 +17,6 @@ - [ ] `npm run security` passes (no high-severity advisories) - [ ] Tests added for new public methods/interfaces - [ ] Docs updated in the same change set if a public interface, observable behaviour, architecture, or ADR changed (per `docs/STYLE_GUIDE.md`) -- [ ] ADR created, or revised in place, and indexed in `docs/architecture/README.md` +- [ ] ADR added, overwritten in place or deleted per `docs/ADR_POLICY.md`, and the index in `docs/architecture/README.md` updated - [ ] No `any`, `@ts-ignore`, or `as any` introduced - [ ] Conventional Commit message format used diff --git a/implement/AGENTS.md b/implement/AGENTS.md index 49dd7b18..7b63c0b2 100644 --- a/implement/AGENTS.md +++ b/implement/AGENTS.md @@ -41,6 +41,7 @@ folder. - The tool reference: [`docs/reference/tools-cli.md`](../docs/reference/tools-cli.md). - All decisions: the root index, [`docs/architecture/adrs/README.md`](../docs/architecture/adrs/README.md). +- How to add, change or delete an ADR: [`docs/ADR_POLICY.md`](../docs/ADR_POLICY.md). ## Test this folder diff --git a/implement/docs/architecture/adrs/0002-progress-writes-state-in-target.md b/implement/docs/architecture/adrs/0002-progress-writes-state-in-target.md index 767b3c2b..f9ff7b54 100644 --- a/implement/docs/architecture/adrs/0002-progress-writes-state-in-target.md +++ b/implement/docs/architecture/adrs/0002-progress-writes-state-in-target.md @@ -1,12 +1,5 @@ # 0002. `progress.py` Writes the State Files Inside the Target -* **Status:** Accepted -* **Date:** 2026-10-29 -* **Last reviewed:** 2026-10-29 -* **Deciders:** [@nistapp] - ---- - ## Context The implement recipe keeps one state per target repository: which findings are queued, done, partial, skipped or @@ -42,7 +35,7 @@ only these two files: ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | Print the state on stdout, or write it to an `--out` path outside the target, and let the agent copy it in | Rule 6 would stay absolute, but the copy step is unvalidated. A session can skip it, copy a stale render, or edit the copy, which is the corruption the tool exists to stop. Every later transition would need a manual read-modify-write. | | An explicit `--state ` that must end in one of the two names, wherever it is | The same exception in practice, with weaker path checks: the tool would not know which repository the file belongs to. | @@ -56,6 +49,6 @@ only these two files: ### Negative / Trade-offs -* Rule 6 is no longer absolute; a reader of the contract must know this exception. +* Rule 6 is not absolute; a reader of the contract must know this exception. * For a moment during each write a temporary file exists in the target's `docs/agentic-readiness/` folder. * A later tool that wants the same exception needs its own ADR. This one does not extend to it. diff --git a/implement/docs/architecture/adrs/0003-push-and-pr-policy.md b/implement/docs/architecture/adrs/0003-push-and-pr-policy.md index bda49776..e8b9cc09 100644 --- a/implement/docs/architecture/adrs/0003-push-and-pr-policy.md +++ b/implement/docs/architecture/adrs/0003-push-and-pr-policy.md @@ -1,23 +1,14 @@ # 0003. The Implement Recipe Pushes and Opens a Pull Request Only When the Owner Opted In -* **Status:** Accepted -* **Date:** 2026-11-05 -* **Last reviewed:** 2026-11-05 -* **Deciders:** [@nistapp] (Q1, accepted 2026-11-05) - ---- - ## Context -Three pages said three things about the end of a theme. [`implement/SKILL.md`](../../../SKILL.md) said the -recipe pushes the theme branch and opens a pull request. The -[purpose and non-goals page](../../../../docs/explanation/user-overview/01-purpose-and-non-goals.md) -said it never pushes and never opens one. The -[trust and safety page](../../../../docs/explanation/user-overview/03-trust-and-safety.md) said -the question was open. +Three pages describe the end of a theme: [`implement/SKILL.md`](../../../SKILL.md), the +[purpose and non-goals page](../../../../docs/explanation/user-overview/01-purpose-and-non-goals.md) and the +[trust and safety page](../../../../docs/explanation/user-overview/03-trust-and-safety.md). Without one rule, they +can disagree on whether the recipe pushes the theme branch and opens a pull request. -Both repositories the recipe has run on today let a session push the phase branch and open the pull request; the -orchestrator or the owner merges. A reader who trusts the non-goals page would be surprised by that. A recipe that +The repositories the recipe runs on let a session push the phase branch and open the pull request; the +orchestrator or the owner merges. A recipe that never pushes would break the one-pull-request-per-theme flow those runs depend on. ## Decision @@ -34,7 +25,7 @@ never pushes would break the one-pull-request-per-theme flow those runs depend o ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | Always push and open a pull request | The agent would act with a push credential in repositories whose owner never said so, and the non-goals page would be false. | | Never push | Every playbook ending and the Stage 1 and Stage 4 steps would change, and each theme would need a manual push by a person. | diff --git a/implement/playbooks/establish-docs-contract.md b/implement/playbooks/establish-docs-contract.md index 7b32238c..2e37a2b0 100644 --- a/implement/playbooks/establish-docs-contract.md +++ b/implement/playbooks/establish-docs-contract.md @@ -6,7 +6,7 @@ ## Goal An agent finds permanent knowledge in one place: `docs/` has an index, a short style guide that the instruction -file names, a decision log with a template and an index, and a short architecture page with a diagram that was +file names, a decision log with a policy, a template and an index, and a short architecture page with a diagram that was checked. Scratch work stays out of version control. Structural traps (very large files, binaries) are listed, not moved. @@ -17,7 +17,7 @@ not moved. | DOC-01 | [03-documentation-contract.md](../../audit/checks/03-documentation-contract.md#doc-01--docs-exists) | Confirms `docs/` holds Markdown, adds `docs/README.md` as the index; never creates a second docs home | | DOC-02 | [03-documentation-contract.md](../../audit/checks/03-documentation-contract.md#doc-02--docsstyle_guidemd-exists) | Adds a one-page `docs/STYLE_GUIDE.md` and one line in the instruction file that names it | | DOC-03 | [03-documentation-contract.md](../../audit/checks/03-documentation-contract.md#doc-03--artefacts-exists-and-is-ignored) | Verifies the scratch directory rule; adds the ignore rule only if a scratch directory exists | -| DOC-04 | [03-documentation-contract.md](../../audit/checks/03-documentation-contract.md#doc-04--adr-directory-and-index) | Adds `docs/adr/` with a template, an index and two or three first records marked `proposed` | +| DOC-04 | [03-documentation-contract.md](../../audit/checks/03-documentation-contract.md#doc-04--adr-directory-and-index) | Adds `docs/adr/` with an ADR policy page, a template, an index and two or three first records for the owner to review | | DOC-05 | [03-documentation-contract.md](../../audit/checks/03-documentation-contract.md#doc-05--docs-are-readable) | Not written yet: the steps that add plain-language rules to the style guide and a prose check are planned. Until then, report the finding to the owner and leave it open | | NAV-01 | [11-navigation.md](../../audit/checks/11-navigation.md#nav-01--declared-component-boundaries) | Verification only: lists the declared components in the architecture page | | NAV-02 | [11-navigation.md](../../audit/checks/11-navigation.md#nav-02--obvious-entry-points) | Verification only: names the entry points in the architecture page | @@ -68,14 +68,14 @@ They stay here so a repository that has one has a procedure, and so the re-audit 2. **Style guide source.** Default: adapt the framework's [`docs/STYLE_GUIDE.md`](../../docs/STYLE_GUIDE.md) (Diátaxis) to one page. The alternative (the owner's own house rules) is allowed only when those rules are not private to a different repository. -3. **First ADRs.** Ask which two or three existing decisions to record. If the owner has no preference, pick them - from the code and the existing docs (a schema-migration tool, a search or storage engine, an authentication - or tenant-selection mechanism, a branch model) and say why you picked them. Mark each `proposed`. +3. **First ADRs.** Ask which two or three existing decisions to record. With no owner preference, pick them + from the code and the existing docs, and say why you picked them. Good candidates: a schema-migration tool, a + search or storage engine, an authentication or tenant-selection mechanism, a branch model. 4. **Architecture overview scope.** Default: modules or apps, entry points, data stores, one authentication or request flow, from the code index of this repository only. -5. **Instruction-file line.** One line naming `docs/STYLE_GUIDE.md`, `docs/adr/` and `docs/architecture/`, - placed beside the existing pointer line (section per `agents_md.pointer_section`). The AGENTS.md theme (T7) - reorganises the file later; tell it in the status that the line exists. +5. **Instruction-file line.** One line naming `docs/STYLE_GUIDE.md`, `docs/adr/`, the ADR policy page and + `docs/architecture/`, placed beside the existing pointer line (section per `agents_md.pointer_section`). + The AGENTS.md theme (T7) reorganises the file later; tell it in the status that the line exists. 6. **NAV-03 findings.** The owner chooses per path: document (default), or skip as by-design. Moving or untracking a file is a separate owner decision and, for generated output, belongs to the [`untrack-generated`](untrack-generated.md) playbook. @@ -102,14 +102,20 @@ They stay here so a repository that has one has a procedure, and so the re-audit validated before saving, relative links, "no values from env files, credentials or keystores", "cite code by path", "decisions go in `docs/adr/`". Link to the lint config instead of restating its rules. Add the instruction-file line (Inputs 5). The check reads the literal string `STYLE_GUIDE.md`. -4. **ADRs (DOC-04).** Create `docs/adr/` with `README.md` (the index: a table of number, title, status, one - line each, linking every file by its path), `0000-template.md` (adapted from the greenfield - [`adr-template.md`](../../greenfield/templates/docs/templates/adr-template.md): Status, Date, - Context, Decision, Alternatives considered, Consequences) and the first records as `0001-short-title.md`. +4. **ADRs (DOC-04).** Create `docs/adr/` with four parts: + - `README.md`, the index: a table of number, title and a one-line summary, linking every file by its path. + - `0000-template.md`, copied from the framework's [`adr-template.md`](../../docs/templates/adr-template.md): + Context, Decision, Alternatives Considered, Consequences. It has no status, date or decider fields. + - An ADR policy page, adapted from the framework's [`ADR_POLICY.md`](../../docs/ADR_POLICY.md). Keep its + rules; replace the framework's folder list and links with the target's. Name the page to fit the target's + file names: `docs/ADR_POLICY.md` beside `docs/STYLE_GUIDE.md` by default, or `docs/adr-policy.md` where + the docs use lower-case names. The instruction-file line names it (Inputs 5). + - The first records, as `0001-short-title.md`. + Rules: - - Status is `proposed` until the owner accepts; the owner changes it. This differs from the greenfield - template (which forbids merging `Proposed`): a retrospective record whose rationale the agent inferred - must say so on its face. + - Git is the archive: no record carries a status, a date or an approval line. The owner accepts the records + by merging the branch, and edits or deletes one before that. The pull request body (or the status note, + when no pull request is opened) lists each record as waiting for the owner's review. - The engine matches the numbered-file pattern (three or four digits, hyphen, slug, `.md`). The template file `0000-template.md` counts as a numbered entry, so list it in the index too. - Context and Decision come from the code and the existing docs, with file paths. State plainly what is @@ -181,8 +187,8 @@ The repository already has Node, so the job can reuse its pinned Node version. - NAV-03 census paths: recorded in the architecture page and the state note; the check keeps reading `PARTIAL` because the engine counts, it does not judge. Mark the item `partial` with that reason, or `skipped` if the owner calls it by design. -- A proposed ADR the owner has not accepted: stays `proposed`, listed in the pull request body as waiting for - the owner. +- An ADR the owner has not reviewed: listed in the pull request body or the status note as waiting for the + owner. The owner edits, deletes or merges it. - A docs job the merge gate cannot yet require: left advisory, noted for the CI theme. ## Verification @@ -192,7 +198,7 @@ The repository already has Node, so the job can reuse its pinned Node version. | DOC-01 | `git ls-files 'docs/*.md' 'docs/**/*.md'` | At least one file; `docs/README.md` links every page; no second docs home | | DOC-02 | `test -f docs/STYLE_GUIDE.md` and `grep -n STYLE_GUIDE.md ` | File exists; the instruction file names it | | DOC-03 | `git ls-files artefacts artifacts` and `git check-ignore -v artefacts/x` | No scratch directory, or nothing tracked and the rule matches | -| DOC-04 | List `docs/adr/`; read `docs/adr/README.md` | At least one numbered file; the index links every numbered file by path, the template included | +| DOC-04 | List `docs/adr/`; read `docs/adr/README.md`; `grep -n -i 'adr' ` | At least one numbered file; the index links every numbered file by path, the template included; the ADR policy page exists and the instruction file names it | | NAV-01 | Read the components table in the overview | Every declared component listed with its manifest path | | NAV-02 | Read the entry-points list | Each entry point is a real path (check with `test -f`) | | NAV-03 | The "Repository notes" section | Every counted path is listed with a tag; nothing moved (`git status` shows only docs and config) | @@ -212,11 +218,12 @@ index). - Do not create a second `docs/` or restructure the existing report folders. - Do not copy content, paths, names or values from a private repository or workspace into an open-source repository's docs or ADRs, or from any target repository into the framework. -- Do not invent the reason behind a decision. Inferred means "proposed" and says it is inferred. +- Do not invent the reason behind a decision. Say what is inferred, or write "Rationale not recorded; owner to + fill in". - Do not move, resize, delete or untrack files for NAV-03. - Do not put values from env files, credentials, realm exports or keystores in any page. - Do not install a docs tool the owner has not approved, and do not use an unpinned `npx` in CI. -- Do not accept an ADR on the owner's behalf. +- Do not add a status, date or approval line to an ADR. The owner's merge is the acceptance. ## State updates diff --git a/spec/README.md b/spec/README.md index f8bb25da..8321fdb6 100644 --- a/spec/README.md +++ b/spec/README.md @@ -62,7 +62,7 @@ The report format itself is defined later by the report schema in `audit/schema/ ### Decision records -Where the framework documents leave a question open, the answer is recorded in `decisions/NNNN-short-title.md`, written from the [greenfield ADR template](../greenfield/templates/docs/templates/adr-template.md). A record is a living, current-state document: when the decision changes, its body is overwritten and the reversal is folded into its *Alternatives Considered* table. Numbers are never reused. The requirements the decision affects state its outcome, so the record does not need reading to apply them. +Where the framework documents leave a question open, the answer is recorded in `decisions/NNNN-short-title.md`, written from the [ADR template](../docs/templates/adr-template.md) and kept under the [ADR policy](../docs/ADR_POLICY.md). The requirements the decision affects state its outcome, so the record does not need reading to apply them. ### Supporting formats diff --git a/spec/decisions/0001-constraint-artifact.md b/spec/decisions/0001-constraint-artifact.md index 1aaf3150..06d2ff91 100644 --- a/spec/decisions/0001-constraint-artifact.md +++ b/spec/decisions/0001-constraint-artifact.md @@ -1,26 +1,17 @@ # 0001. Use the prohibitions section as the deny list -* **Status:** Accepted -* **Date:** 2026-10-10 -* **Last reviewed:** 2026-10-10 -* **Deciders:** K. C. Ramakrishna (decided 2026-10-09; written down 2026-10-10) - ---- - ## Context Phase 1 of the framework asks a repository to "establish initial Constraint Engineering allow/deny lists" ([Phased-Approach.md](../../docs/framework/phased-approach.md#activities)). Its Key Deliverables table gives the form as "Config files" and the consumer as agents. It names no file and no schema. Constraint Engineering itself is described in [Overview.md](../../docs/framework/overview.md#constraint-engineering). -The audit engine could not check what no document defined. Before it adopted this decision: +The audit engine cannot check what no document defines. Without a defined artifact: -* `CON-01` ("Allow/deny artifact present") was blocked and carried weight 0. -* `CON-02` ("Test directories protected") accepted one of about twenty root-level file names, such as `.cursorignore`, `constraints.yaml` and `denylist.txt`. Without one it fell back to ignore rules and to CI text, and never passed. -* Two equally well-governed repositories got different verdicts depending on the file name they happened to choose. +* `CON-01` ("Allow/deny artifact present") is blocked and carries weight 0. +* `CON-02` ("Test directories protected") must accept any of about twenty root-level file names, such as `.cursorignore`, `constraints.yaml` and `denylist.txt`. Without one it falls back to ignore rules and to CI text, and never passes. +* Two equally well-governed repositories get different verdicts depending on the file name they happen to choose. An agent reads the instruction file at the start of every session. It reads no other file unless its harness is configured to. The framework's own template already has a "Do Not Do" section in that file ([greenfield `AGENTS.md`](../../greenfield/templates/AGENTS.md), section 10). ---- - ## Decision 1. **The deny list is the prohibitions section of the instruction file.** It is the section whose heading says what an agent must not do. "Do Not Do" is the template's heading; "Never", "Prohibited", "Forbidden" and "Deny" are accepted too. It is the only allow/deny artifact the spec requires or checks. @@ -38,7 +29,7 @@ Three requirements concern the deny list, and each answers one question: An absence is reported once, by the check that owns it. With no instruction file, `AGT-01` reports it and the other three are `UNKNOWN`. With no prohibitions section, `AGT-07` reports it and `CON-01` and `CON-02` are `UNKNOWN`. -### What the two checks mean now +### What the two checks mean A prohibitions entry *names a path* when the path, or a pattern that covers it, appears in a code span in the entry. @@ -49,25 +40,22 @@ A prohibitions entry *names a path* when the path, or a pattern that covers it, | `FAIL` | The section names no path or pattern. | It names none of the test directories. | | `UNKNOWN` | No instruction file, or no prohibitions section. | The same, or the repository has no test directory. | -`CON-02` stops counting constraint files, ignore rules and CI text as evidence. A repository that protects its tests only that way is reported as not naming them in its deny list. The change is deliberate: the other mechanisms are not the artifact. +`CON-02` does not count constraint files, ignore rules or CI text as evidence. A repository that protects its tests only that way is reported as not naming them in its deny list. This is deliberate: the other mechanisms are not the artifact. ### `CON-04`, the draft emitter -`CON-04` stays as a drafting aid. Its value is the deterministic scan: which secret, migration, generated, test, infrastructure and production paths exist. Its output changes: a Markdown section to paste under the instruction file's prohibitions heading, in place of `constraints.draft.yaml`, which nothing reads any more. - ---- +`CON-04` stays as a drafting aid. Its value is the deterministic scan: which secret, migration, generated, test, infrastructure and production paths exist. Its output is a Markdown section to paste under the instruction file's prohibitions heading. ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | A neutral machine-readable constraints file, with generators for each harness | No harness reads it, so it adds a file and a generator without adding enforcement. It can come later if enforcement is wanted, and the section can be its source. | | Harness-native configuration as the policy | One configuration per harness, and a repository may serve several. The policy would sit in files an agent does not read as instructions. | | Keep accepting any of the root-level file names | A verdict that depends on a naming choice, and no single place for the agent or the playbook to write to. | +| `CON-04` writes a `constraints.draft.yaml` | Nothing reads that file. | | Retire `CON-04` | The scan that finds the candidates is deterministic and tested. A model should not rediscover the same paths by exploring the repository. | ---- - ## Consequences ### Positive @@ -80,11 +68,5 @@ A prohibitions entry *names a path* when the path, or a pattern that covers it, * The section is prose, so the checks are heuristics: headings and code spans. A path written without backticks is not seen. * The list is a statement, not a mechanism. An agent can ignore it, and optional native configuration is how a team enforces it. -* A repository governed through ignore files loses the credit it had under the old `CON-02`. +* A repository governed through ignore files gets no credit for them in `CON-02`. * The Key Deliverables table in [Phased-Approach.md](../../docs/framework/phased-approach.md#key-deliverables) says "Config files". The spec reads that as the instruction file, and the table's wording is left to the framework's maintainer. - ---- - -## Lifecycle - -A decision record describes the current decision only. When the decision changes, overwrite the body, keep the file and its number, and fold the reversal into *Alternatives Considered*. See [README.md](../README.md#decision-records). diff --git a/spec/decisions/0002-lint-in-the-gate.md b/spec/decisions/0002-lint-in-the-gate.md index 8f343067..d053d54d 100644 --- a/spec/decisions/0002-lint-in-the-gate.md +++ b/spec/decisions/0002-lint-in-the-gate.md @@ -1,19 +1,12 @@ # 0002. Lint is gated through `format:check` -* **Status:** Accepted -* **Date:** 2026-11-05 -* **Last reviewed:** 2026-11-05 -* **Deciders:** K. C. Ramakrishna (Q2, accepted 2026-11-05) - ---- - ## Context -[REQ-SH-022](../readiness-requirements.md#req-sh-022--check-composes-the-read-only-verbs) said `check` runs -`format:check`, `typecheck` and `test`, and said nothing about lint. The [command surface](../command-surface.md) -already described `format:check` as "formatting and lint rules" and listed `lint` as not part of `check`. The -framework's own `Makefile` follows the second reading: its `format-check` runs the formatter's check and the linter. -The two pages could be read to mean that lint is gated, or that it is not. +[REQ-SH-022](../readiness-requirements.md#req-sh-022--check-composes-the-read-only-verbs) says `check` runs +`format:check`, `typecheck` and `test`. The [command surface](../command-surface.md) describes `format:check` as +"formatting and lint rules" and lists `lint` as not part of `check`. The framework's own `Makefile` runs the +formatter's check and the linter in its `format-check`. Without a rule, the two pages can be read to mean that lint +is gated, or that it is not. ## Decision @@ -25,16 +18,16 @@ The two pages could be read to mean that lint is gated, or that it is not. ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| | `check` = `format:check` + `lint` + `typecheck` + `test` | Changes the requirement, the verb table, the `CMD-02` check and every existing target's `check`. `lint` and `format:check` overlap in most runners. | -| `check` MUST gate lint by either route (`format:check` includes it, or `check` also runs `lint`) | Closes the case where a separate `lint` is not gated, but `CMD-02` would have to recognise two shapes. Left for later if that case matters. | +| `check` MUST gate lint by either route (`format:check` includes it, or `check` also runs `lint`) | Closes the case where a separate `lint` is not gated, but `CMD-02` would have to recognise two shapes. | ## Consequences ### Positive -* No change for a target that already follows the command surface; the framework's own `Makefile` already conforms. +* A target that follows the command surface needs no change; the framework's own `Makefile` conforms. * One sentence in the requirement states the rule. ### Negative / Trade-offs diff --git a/spec/decisions/0003-dependency-scan-verb.md b/spec/decisions/0003-dependency-scan-verb.md index 1ea605ac..69204a2e 100644 --- a/spec/decisions/0003-dependency-scan-verb.md +++ b/spec/decisions/0003-dependency-scan-verb.md @@ -1,34 +1,27 @@ # 0003. Any verb may run the dependency scan; `security` is the preferred one -* **Status:** Accepted -* **Date:** 2026-11-10 -* **Last reviewed:** 2026-11-10 -* **Deciders:** K. C. Ramakrishna (v0.3.5.1 question 3.1, accepted 2026-11-10) - ---- - ## Context -[REQ-SH-030](../readiness-requirements.md#req-sh-030--dependency-vulnerability-scan) said "the `security` verb -MUST run a dependency vulnerability scan". The `TOOL-04` check gives `PASS` when any verb runs the scan, for example -a `check` that runs `osv-scanner`. A repository that scans inside `check` is as safe for an agent as one with a -`security` verb: the risk is an unscanned dependency, not the name of the verb. The requirement and the check -disagreed, and the requirement is normative, so one of them had to change. +[REQ-SH-030](../readiness-requirements.md#req-sh-030--dependency-vulnerability-scan) asks for a dependency +vulnerability scan. The `TOOL-04` check gives `PASS` when any verb runs the scan, for example a `check` that runs +`osv-scanner`. A repository that scans inside `check` is as safe for an agent as one with a `security` verb: the +risk is an unscanned dependency, not the name of the verb. The requirement is normative, so it must say the same +as the check. ## Decision 1. **A verb MUST run a dependency vulnerability scan.** Any verb of the [command surface](../command-surface.md) counts. 2. **The verb SHOULD be `security`.** It is the verb the command surface names for this gate. -3. **CI SHOULD run the same scan**, as before. +3. **CI SHOULD run the same scan.** -`TOOL-04` already reads the requirement this way, so no verdict moves. +`TOOL-04` reads the requirement this way. ## Alternatives Considered -| Alternative | Why it was rejected | +| Alternative | Why it is rejected | |---|---| -| Keep MUST on `security`; `TOOL-04` gives `PARTIAL` when only another verb runs the scan | Moves verdicts on repositories that are safe today, for a naming rule. Needs an engine change and a ruleset bump. | +| MUST on `security`; `TOOL-04` gives `PARTIAL` when only another verb runs the scan | Marks down safe repositories for a naming rule. Needs an engine change and a ruleset bump. | ## Consequences