From 1fc26f8248d42cfb0887ff53e7bff9766a428747 Mon Sep 17 00:00:00 2001 From: samonjoat Date: Fri, 3 Jul 2026 11:37:40 +0200 Subject: [PATCH 1/2] feat: add DOX validation and review docs --- .github/AGENTS.md | 28 +++++++ .github/workflows/dox-lint.yml | 20 +++++ .gitignore | 9 +++ AGENTS.md | 26 +++++- README.md | 41 ++++++++++ agents/AGENTS.md | 29 +++++++ agents/documenter.md | 65 +++++++++++++++ agents/orchestrator.md | 57 +++++++++++++ docs/AGENTS.md | 30 +++++++ docs/evidence.md | 40 +++++++++ docs/spec.md | 106 ++++++++++++++++++++++++ scripts/AGENTS.md | 28 +++++++ scripts/dox_lint.py | 143 +++++++++++++++++++++++++++++++++ 13 files changed, 618 insertions(+), 4 deletions(-) create mode 100644 .github/AGENTS.md create mode 100644 .github/workflows/dox-lint.yml create mode 100644 .gitignore create mode 100644 agents/AGENTS.md create mode 100644 agents/documenter.md create mode 100644 agents/orchestrator.md create mode 100644 docs/AGENTS.md create mode 100644 docs/evidence.md create mode 100644 docs/spec.md create mode 100644 scripts/AGENTS.md create mode 100755 scripts/dox_lint.py diff --git a/.github/AGENTS.md b/.github/AGENTS.md new file mode 100644 index 0000000..9cbaff1 --- /dev/null +++ b/.github/AGENTS.md @@ -0,0 +1,28 @@ +# GitHub automation DOX + +## Purpose + +Owns GitHub-native automation for this repository. + +## Ownership + +- `workflows/dox-lint.yml` runs the optional DOX structural validator in GitHub Actions. + +## Local Contracts + +- Workflows must use repository-local scripts where possible. +- CI must not require secrets for documentation validation. +- Workflow names should describe the check they run. + +## Work Guidance + +- Keep workflow permissions minimal. +- Prefer standard GitHub-hosted runners and stock actions for portability. + +## Verification + +- Run `python3 scripts/dox_lint.py .` locally before relying on the workflow. + +## Child DOX Index + +No child DOX files yet. diff --git a/.github/workflows/dox-lint.yml b/.github/workflows/dox-lint.yml new file mode 100644 index 0000000..822ec2b --- /dev/null +++ b/.github/workflows/dox-lint.yml @@ -0,0 +1,20 @@ +name: DOX lint + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + dox-lint: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Run DOX structural validator + run: python3 scripts/dox_lint.py . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39f5853 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +__pycache__/ +*.py[cod] +.venv/ +venv/ +node_modules/ +dist/ +build/ +coverage/ +.DS_Store diff --git a/AGENTS.md b/AGENTS.md index fdac2ac..3d9523a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,9 @@ + # DOX framework -- DOX is highly performant AGENTS.md hierarchy installed here -- Agent must follow DOX instructions across any edits +- DOX is a highly portable AGENTS.md hierarchy installed here +- Agents must follow DOX instructions across any edits +- The protected framework block between `DOX-CORE-START` and `DOX-CORE-END` is the root contract and must not be weakened, removed, or rewritten by agents ## Core Contract @@ -76,8 +78,24 @@ Default section order: ## User Preferences -When the user requests a durable behavior change, record it here or in the relevant child AGENTS.md +When the user requests a durable behavior change, record it here or in the relevant child AGENTS.md. + + +## Project Guidance + +- This repository distributes DOX as Markdown-first guidance plus optional helper conventions. +- Keep the core install path copy-pasteable: users can still copy `AGENTS.md` without installing anything. +- Optional tooling must remain additive. Scripts and specs may validate DOX trees, but they must not make the Markdown framework dependent on a package or service. +- Distinguish reported evidence from verified guarantees. Do not present third-party metrics as proof unless the methodology is independently audited. + +## Verification + +- Run `python3 scripts/dox_lint.py .` after changing AGENTS.md structure, docs conventions, or child indexes. +- For docs-only prose changes that do not affect the DOX tree, review the rendered Markdown or changed diff. ## Child DOX Index -This project is not yet indexed. Before continuing you must scan the project, build the DOX tree and replace this message with the actual index. Go deep and scan files recursively to properly evaluate complexity and create nested DOX files where needed. \ No newline at end of file +- `.github/AGENTS.md` — GitHub Actions automation for DOX validation. +- `agents/AGENTS.md` — templates for orchestrator/documenter agent workflows that maintain DOX trees. +- `docs/AGENTS.md` — DOX specification, validation convention, and evidence/caveat documentation. +- `scripts/AGENTS.md` — optional local tooling for validating DOX structure. diff --git a/README.md b/README.md index da0430f..c40855f 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,47 @@ No AGENTS.md yet? Copy the file into your project root. The agent will see these For an existing project, you can tell your agent: `Initialize DOX tree for this project now.` It will create all the child AGENTS.md files and indexes. +## Usage examples + +### Basic setup + +1. Create or open a project repository. +2. Copy [AGENTS.md](./AGENTS.md?plain=1) to the project root. +3. Ask your coding agent: `Initialize DOX tree for this project now.` +4. Review the generated child indexes before accepting the changes. + +### After changing code + +1. The agent reads the root `AGENTS.md` and every applicable child `AGENTS.md` before editing. +2. The agent makes the requested code or documentation change. +3. The agent performs a DOX pass before closeout. +4. If the change affects durable structure, ownership, contracts, workflows, verification, or child indexes, the nearest owning `AGENTS.md` is updated. +5. If the change is local and does not affect durable rules, the agent reports that docs were intentionally left unchanged. + +### Optional validation + +DOX is Markdown-first, but this fork includes an optional convention and linter: + +```bash +python3 scripts/dox_lint.py . +``` + +The linter checks that child indexes point to real `AGENTS.md` files, child docs keep the standard section shape, and the protected root DOX framework block is present. This fork also includes a GitHub Actions workflow that runs the same check on pushes to `main` and pull requests. + +## Optional agent templates + +See [agents/](./agents/) for portable orchestrator and documenter templates. They describe a two-agent maintenance pattern: + +- an orchestrator scans the repository and assigns candidate paths +- documenter agents inspect one path at a time and update the nearest owning `AGENTS.md` when DOX requires it + +The templates are examples, not a required runtime. Adapt their tool names to your agent harness. + +## Specification and evidence + +- [docs/spec.md](./docs/spec.md) defines the optional machine-checkable DOX convention. +- [docs/evidence.md](./docs/evidence.md) records reported third-party session metrics and the caveats around interpreting them. + ## Credits

diff --git a/agents/AGENTS.md b/agents/AGENTS.md new file mode 100644 index 0000000..adab8f4 --- /dev/null +++ b/agents/AGENTS.md @@ -0,0 +1,29 @@ +# Agent templates DOX + +## Purpose + +Owns reusable agent prompt templates for maintaining DOX trees. + +## Ownership + +- `orchestrator.md` describes the coordination role for scanning candidate files and delegating documentation work. +- `documenter.md` describes the focused role for inspecting one path and updating the nearest owning `AGENTS.md` when required. + +## Local Contracts + +- Templates must remain harness-portable. Name common capabilities instead of hard-coding one tool stack unless a section is explicitly adapter-specific. +- Templates must not require DOX users to install a runtime. +- Documenter guidance may edit `AGENTS.md` files only; source-file edits belong to the caller's coding agent. + +## Work Guidance + +- Keep outputs concise and deterministic so orchestrators can parse them. +- Include adapter notes for Codex, Claude Code, OpenCode, and Hermes-style subagents only as examples. + +## Verification + +- Run `python3 scripts/dox_lint.py .` after changing this folder or its child index. + +## Child DOX Index + +No child DOX files yet. diff --git a/agents/documenter.md b/agents/documenter.md new file mode 100644 index 0000000..caceafa --- /dev/null +++ b/agents/documenter.md @@ -0,0 +1,65 @@ +# DOX documenter agent template + +## Purpose + +Inspect one target path and update the nearest owning `AGENTS.md` only when DOX requires a durable documentation change. + +## Inputs + +- Repository root path. +- One target file or directory path. +- Optional changed-file diff or summary from the caller. + +## Workflow + +1. Read the root `AGENTS.md`. +2. Walk from the repository root to the target path. +3. Read every `AGENTS.md` on that route. +4. Use the nearest `AGENTS.md` as the local contract and parent docs for broader rules. +5. Inspect the target path enough to understand whether it changes: + - purpose, scope, ownership, or responsibilities + - durable structure, contracts, workflows, or operating rules + - required inputs, outputs, permissions, constraints, side effects, or artifacts + - verification or quality expectations + - child `AGENTS.md` index contents +6. If the target creates a durable boundary, create a child `AGENTS.md` with the standard section shape. +7. If the nearest owning doc is stale, update it concisely. +8. If no durable contract changed, leave docs untouched. +9. Return exactly one result line: + +```text +PATH | ACTION | DOX | DETAIL +``` + +## Constraints + +- Edit only `AGENTS.md` files. +- Do not edit source files, README prose, specs, or scripts unless the caller explicitly assigned that documentation file as the target. +- Do not weaken or remove the protected root DOX framework block. +- Do not duplicate broad parent rules in child docs. +- Delete stale contradictions instead of preserving historical notes. +- Keep `DETAIL` under 100 characters. + +## Decision guide + +Update docs when the answer is yes: + +- Would a future agent need to know this to edit safely? +- Did ownership, workflow, verification, side effects, or durable structure change? +- Did a parent child-index become wrong? +- Did a new folder become a stable boundary? + +Skip docs when the answer is yes: + +- Is this a one-off content edit with no durable process impact? +- Is the target generated, cached, vendored, or temporary? +- Is the existing `AGENTS.md` already accurate? + +## Example results + +```text +PATH src/api/server.py | ACTION updated | DOX src/api/AGENTS.md | DETAIL added streaming endpoint contract +PATH docs/spec.md | ACTION skipped | DOX docs/AGENTS.md | DETAIL existing docs contract already covers spec prose +PATH src/payments | ACTION created | DOX src/payments/AGENTS.md | DETAIL new durable payment boundary +PATH missing.py | ACTION error | DOX none | DETAIL target path not found +``` diff --git a/agents/orchestrator.md b/agents/orchestrator.md new file mode 100644 index 0000000..2eacb8a --- /dev/null +++ b/agents/orchestrator.md @@ -0,0 +1,57 @@ +# DOX orchestrator agent template + +## Purpose + +Coordinate DOX maintenance across a repository without letting one agent mutate the whole tree blindly. + +The orchestrator scans candidate paths, selects the paths that may affect durable documentation, delegates one path at a time to a documenter agent, collects compact results, and reports the final DOX maintenance summary. + +## Inputs + +- Repository root path. +- Changed paths, selected paths, or a user request such as `Initialize DOX tree for this project now.` +- Access to a documenter subagent or an equivalent isolated worker. + +## Workflow + +1. Read root `AGENTS.md`. +2. Build a concise repository map, excluding generated/vendor/cache folders such as `.git/`, `node_modules/`, `.venv/`, `dist/`, `build/`, and coverage outputs. +3. Identify candidate files or directories that may affect durable project structure, ownership, contracts, workflows, verification, or child indexes. +4. For each candidate path, launch a documenter worker with: + - repository root + - absolute or root-relative target path + - the current DOX chain that applies to that path, if already known +5. Require the documenter to return one result line: + +```text +PATH | ACTION | DOX | DETAIL +``` + +6. Continue after errors, but include them in the final report. +7. After all workers finish, run the available DOX validation command if one exists. +8. Report: + - total paths processed + - updated/created/skipped/error counts + - `AGENTS.md` files touched + - validation result + +## Constraints + +- Do not edit source files. The orchestrator coordinates documentation only. +- Prefer changed paths over a full repository scan when the caller provides a diff. +- Do not create child `AGENTS.md` files for temporary or generated folders. +- Do not weaken the protected root DOX framework block. +- Keep final output short enough for a caller to act on. + +## Adapter notes + +Different agent harnesses expose different tool names: + +| Capability | Examples | +|---|---| +| Spawn worker | `task`, `delegate_task`, subagent, child process | +| Ask user | `question`, `clarify`, chat prompt | +| Read file | `read`, `read_file`, filesystem tool | +| Run command | `bash`, `terminal`, shell tool | + +Use the capability, not the literal tool name, unless you are writing for one specific harness. diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 0000000..6840021 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,30 @@ +# Docs DOX + +## Purpose + +Owns human-readable DOX specifications, validation conventions, and evidence notes. + +## Ownership + +- `spec.md` defines the optional machine-checkable convention for DOX trees. +- `evidence.md` records claims, caveats, and interpretation boundaries for reported DOX outcomes. + +## Local Contracts + +- Keep DOX Markdown-first. Specs may define optional validation, but must not imply users need a package or service to use DOX. +- Separate normative requirements from recommendations and examples. +- Evidence notes must distinguish reported third-party observations from verified guarantees. + +## Work Guidance + +- Prefer short, operational sections over broad essays. +- Use RFC-style words only when useful: required, recommended, optional. +- Do not overclaim empirical impact without methodology, raw data, or reproducible analysis. + +## Verification + +- Run `python3 scripts/dox_lint.py .` from the repository root after changing docs structure or child indexes. + +## Child DOX Index + +No child DOX files yet. diff --git a/docs/evidence.md b/docs/evidence.md new file mode 100644 index 0000000..c236185 --- /dev/null +++ b/docs/evidence.md @@ -0,0 +1,40 @@ +# DOX evidence notes + +This page records reported evidence and caveats. It is not a benchmark certificate. + +## Reported external observation + +GitHub issue #3 reported an analysis of 9,911 Claude Code session logs comparing sessions with custom agent rulesets such as `AGENTS.md`/DOX against sessions without them. + +Reported figures: + +| Metric | With DOX / AGENTS.md | Without DOX / AGENTS.md | Reported difference | +|---|---:|---:|---:| +| Average human turns per session | 4.87 | 1.66 | +193% | +| Average prompt input tokens | 73,097.8 | 16,537.6 | Larger context navigation | +| Average tool errors per session | 0.68 | 0.37 | Higher, not automatically better | +| Average API cost per session | $10.48 | $2.09 | Higher, not automatically better | + +## Interpretation boundaries + +The data suggests that structured agent instructions may correlate with longer, deeper agent sessions. It does not, by itself, prove that DOX causes better outcomes. + +Important caveats: + +- Longer sessions can mean harder tasks, better persistence, or inefficient loops. +- Higher token use and cost can mean deeper work, or waste. +- Higher tool errors are not inherently positive. +- Public issue text does not provide raw data, sampling rules, or reproducible analysis. +- `AGENTS.md` usage may correlate with more sophisticated users or larger repositories. + +## Responsible claim + +Safe wording: + +> DOX gives agents a durable local context structure. Third-party users have reported longer and deeper agent sessions with AGENTS.md-style rulesets, but those reports should be treated as observational rather than causal proof. + +Avoid wording like: + +> DOX proves agents are 193% better. + +That claim is not supported by the available public evidence. diff --git a/docs/spec.md b/docs/spec.md new file mode 100644 index 0000000..5802b68 --- /dev/null +++ b/docs/spec.md @@ -0,0 +1,106 @@ +# DOX tree convention + +DOX remains a Markdown-first `AGENTS.md` framework. This convention is optional: it gives teams and tooling a small, machine-checkable shape without changing the copy-paste install path. + +## Goals + +- Keep the root `AGENTS.md` useful to humans and agents. +- Let repositories define local contracts close to the files they govern. +- Make child indexes checkable without requiring a package, database, service, or generated lockfile. +- Catch stale child indexes, missing child docs, and accidental deletion of the root framework block. + +## Non-goals + +- No heavy schema for all prose content. +- No mandatory runtime dependency. +- No central registry. +- No requirement that every folder has an `AGENTS.md`. +- No attempt to prove that a doc is semantically complete. + +## Required shape + +A DOX repository has a root `AGENTS.md`. + +The root `AGENTS.md` should contain: + +1. A protected framework block, delimited by: + - `` + - `` +2. A `## Child DOX Index` section. +3. One bullet per direct child DOX file, using this form: + +```markdown +- `path/to/AGENTS.md` — short description of the child scope. +``` + +Child `AGENTS.md` files should use this section order: + +1. `# ` +2. `## Purpose` +3. `## Ownership` +4. `## Local Contracts` +5. `## Work Guidance` +6. `## Verification` +7. `## Child DOX Index` + +A child index may say `No child DOX files yet.` when the scope has no nested DOX files. + +## Parent-child rules + +- A parent index entry must point to an existing `AGENTS.md` file. +- A child `AGENTS.md` must be inside the parent's subtree. +- A child may make local rules more specific, but must not weaken the root DOX contract. +- Parent docs should describe broad ownership. Child docs should hold concrete local rules. + +## When to create a child AGENTS.md + +Create one when a folder becomes a durable boundary with its own: + +- purpose or product surface +- ownership/responsibility split +- local contracts or side effects +- workflow or verification rules +- durable assets, source materials, or generated outputs + +Do not create child docs for temporary scratch folders or one-off files. + +## Validation + +This fork includes a small reference validator: + +```bash +python3 scripts/dox_lint.py . +``` + +The validator checks: + +- root `AGENTS.md` exists +- protected root DOX markers exist and are ordered correctly +- every `AGENTS.md` has a `## Child DOX Index` +- every child-index bullet pointing to an `AGENTS.md` resolves to a real file +- every non-root `AGENTS.md` includes the recommended child section set + +Validation is intentionally structural. It cannot prove the prose is correct. + +## Example + +```text +repo/ + AGENTS.md + backend/ + AGENTS.md + frontend/ + AGENTS.md + scripts/ + AGENTS.md +``` + +Root index: + +```markdown +## Child DOX Index + +- `backend/AGENTS.md` — API, persistence, jobs, and backend verification rules. +- `frontend/AGENTS.md` — UI, routing, design-system, and browser verification rules. +- `scripts/AGENTS.md` — maintenance scripts and operational checks. +``` diff --git a/scripts/AGENTS.md b/scripts/AGENTS.md new file mode 100644 index 0000000..8779a71 --- /dev/null +++ b/scripts/AGENTS.md @@ -0,0 +1,28 @@ +# Scripts DOX + +## Purpose + +Owns optional local tooling for checking or maintaining DOX structure. + +## Ownership + +- `dox_lint.py` validates the structural DOX convention without external dependencies. + +## Local Contracts + +- Scripts must run with Python standard library only unless the README and this file explicitly document a dependency. +- Scripts must not modify repository files unless their name and help text clearly identify them as fixers. +- Validation output must be deterministic and CI-friendly. + +## Work Guidance + +- Prefer explicit error messages with file paths. +- Keep checks structural; do not pretend to validate whether prose is semantically complete. + +## Verification + +- Run `python3 scripts/dox_lint.py .` from the repository root. + +## Child DOX Index + +No child DOX files yet. diff --git a/scripts/dox_lint.py b/scripts/dox_lint.py new file mode 100755 index 0000000..20d9197 --- /dev/null +++ b/scripts/dox_lint.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +"""Minimal structural validator for DOX AGENTS.md trees. + +This linter intentionally validates only machine-checkable structure: +- root AGENTS.md exists +- protected root core markers exist and are ordered +- every AGENTS.md has a Child DOX Index +- child-index entries that reference AGENTS.md files resolve to real files +- non-root AGENTS.md files use the standard DOX section set +""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + +CORE_START = "DOX-CORE-START" +CORE_END = "DOX-CORE-END" +REQUIRED_CHILD_SECTIONS = [ + "Purpose", + "Ownership", + "Local Contracts", + "Work Guidance", + "Verification", + "Child DOX Index", +] +SKIP_DIRS = { + ".git", + ".hg", + ".svn", + "node_modules", + ".venv", + "venv", + "dist", + "build", + "coverage", + "__pycache__", +} +CHILD_ENTRY_RE = re.compile(r"^\s*-\s+`([^`]*AGENTS\.md)`\s+[—-]\s+\S") + + +def read_text(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def iter_agents_files(root: Path) -> list[Path]: + files: list[Path] = [] + for path in root.rglob("AGENTS.md"): + if any(part in SKIP_DIRS for part in path.relative_to(root).parts): + continue + files.append(path) + return sorted(files) + + +def headings(text: str) -> list[str]: + return [line.strip("# ").strip() for line in text.splitlines() if line.startswith("## ")] + + +def child_index_body(text: str) -> list[str]: + lines = text.splitlines() + for i, line in enumerate(lines): + if line.strip() == "## Child DOX Index": + body: list[str] = [] + for nxt in lines[i + 1 :]: + if nxt.startswith("## "): + break + body.append(nxt) + return body + return [] + + +def validate(root: Path) -> list[str]: + errors: list[str] = [] + root = root.resolve() + root_agents = root / "AGENTS.md" + + if not root_agents.exists(): + return [f"missing root AGENTS.md: {root_agents}"] + + agents_files = iter_agents_files(root) + agents_set = {p.resolve() for p in agents_files} + root_text = read_text(root_agents) + + start = root_text.find(CORE_START) + end = root_text.find(CORE_END) + if start == -1: + errors.append("root AGENTS.md missing DOX-CORE-START marker") + if end == -1: + errors.append("root AGENTS.md missing DOX-CORE-END marker") + if start != -1 and end != -1 and start > end: + errors.append("root AGENTS.md DOX core markers are out of order") + + for agents_path in agents_files: + rel = agents_path.relative_to(root) + text = read_text(agents_path) + hs = headings(text) + + if "Child DOX Index" not in hs: + errors.append(f"{rel}: missing ## Child DOX Index") + + if agents_path != root_agents: + for section in REQUIRED_CHILD_SECTIONS: + if section not in hs: + errors.append(f"{rel}: missing ## {section}") + + for line in child_index_body(text): + match = CHILD_ENTRY_RE.match(line) + if not match: + continue + child_rel = match.group(1) + child_path = (agents_path.parent / child_rel).resolve() + if child_path not in agents_set: + errors.append(f"{rel}: child index points to missing {child_rel}") + else: + try: + child_path.relative_to(agents_path.parent.resolve()) + except ValueError: + errors.append(f"{rel}: child index escapes parent subtree: {child_rel}") + + return errors + + +def main() -> int: + parser = argparse.ArgumentParser(description="Validate a DOX AGENTS.md tree") + parser.add_argument("root", nargs="?", default=".", help="repository root, default: current directory") + args = parser.parse_args() + + root = Path(args.root) + errors = validate(root) + if errors: + print("DOX lint failed:") + for error in errors: + print(f"- {error}") + return 1 + + print("DOX lint passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 464568a249f2c57619d32100d899933034b3f8e4 Mon Sep 17 00:00:00 2001 From: samonjoat Date: Fri, 3 Jul 2026 12:10:35 +0200 Subject: [PATCH 2/2] fix: tighten DOX lint validation --- .github/workflows/dox-lint.yml | 3 + AGENTS.md | 1 + docs/spec.md | 8 +-- scripts/dox_lint.py | 32 +++++---- tests/AGENTS.md | 29 +++++++++ tests/test_dox_lint.py | 116 +++++++++++++++++++++++++++++++++ 6 files changed, 171 insertions(+), 18 deletions(-) create mode 100644 tests/AGENTS.md create mode 100644 tests/test_dox_lint.py diff --git a/.github/workflows/dox-lint.yml b/.github/workflows/dox-lint.yml index 822ec2b..b3a62d2 100644 --- a/.github/workflows/dox-lint.yml +++ b/.github/workflows/dox-lint.yml @@ -18,3 +18,6 @@ jobs: - name: Run DOX structural validator run: python3 scripts/dox_lint.py . + + - name: Run linter regression tests + run: python3 -m unittest discover -s tests diff --git a/AGENTS.md b/AGENTS.md index 3d9523a..cf115c9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -99,3 +99,4 @@ When the user requests a durable behavior change, record it here or in the relev - `agents/AGENTS.md` — templates for orchestrator/documenter agent workflows that maintain DOX trees. - `docs/AGENTS.md` — DOX specification, validation convention, and evidence/caveat documentation. - `scripts/AGENTS.md` — optional local tooling for validating DOX structure. +- `tests/AGENTS.md` — regression tests for optional DOX tooling. diff --git a/docs/spec.md b/docs/spec.md index 5802b68..3d585c1 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -19,9 +19,9 @@ DOX remains a Markdown-first `AGENTS.md` framework. This convention is optional: ## Required shape -A DOX repository has a root `AGENTS.md`. +A repository that opts into this machine-checkable convention must have a root `AGENTS.md`. -The root `AGENTS.md` should contain: +The root `AGENTS.md` must contain: 1. A protected framework block, delimited by: - `` @@ -33,7 +33,7 @@ The root `AGENTS.md` should contain: - `path/to/AGENTS.md` — short description of the child scope. ``` -Child `AGENTS.md` files should use this section order: +Child `AGENTS.md` files must use this section order: 1. `# ` 2. `## Purpose` @@ -78,7 +78,7 @@ The validator checks: - protected root DOX markers exist and are ordered correctly - every `AGENTS.md` has a `## Child DOX Index` - every child-index bullet pointing to an `AGENTS.md` resolves to a real file -- every non-root `AGENTS.md` includes the recommended child section set +- every non-root `AGENTS.md` includes the required child section set Validation is intentionally structural. It cannot prove the prose is correct. diff --git a/scripts/dox_lint.py b/scripts/dox_lint.py index 20d9197..9e85fba 100755 --- a/scripts/dox_lint.py +++ b/scripts/dox_lint.py @@ -13,11 +13,10 @@ import argparse import re -import sys from pathlib import Path -CORE_START = "DOX-CORE-START" -CORE_END = "DOX-CORE-END" +CORE_START_RE = re.compile(r"") +CORE_END_RE = re.compile(r"") REQUIRED_CHILD_SECTIONS = [ "Purpose", "Ownership", @@ -38,6 +37,7 @@ "coverage", "__pycache__", } +CHILD_PATH_RE = re.compile(r"^\s*-\s+`([^`]*AGENTS\.md)`") CHILD_ENTRY_RE = re.compile(r"^\s*-\s+`([^`]*AGENTS\.md)`\s+[—-]\s+\S") @@ -83,14 +83,14 @@ def validate(root: Path) -> list[str]: agents_set = {p.resolve() for p in agents_files} root_text = read_text(root_agents) - start = root_text.find(CORE_START) - end = root_text.find(CORE_END) - if start == -1: - errors.append("root AGENTS.md missing DOX-CORE-START marker") - if end == -1: - errors.append("root AGENTS.md missing DOX-CORE-END marker") - if start != -1 and end != -1 and start > end: - errors.append("root AGENTS.md DOX core markers are out of order") + start_match = CORE_START_RE.search(root_text) + end_match = CORE_END_RE.search(root_text) + if start_match is None: + errors.append("root AGENTS.md missing DOX-CORE-START HTML comment marker") + if end_match is None: + errors.append("root AGENTS.md missing DOX-CORE-END HTML comment marker") + if start_match is not None and end_match is not None and start_match.start() > end_match.start(): + errors.append("root AGENTS.md DOX core HTML comment markers are out of order") for agents_path in agents_files: rel = agents_path.relative_to(root) @@ -106,10 +106,14 @@ def validate(root: Path) -> list[str]: errors.append(f"{rel}: missing ## {section}") for line in child_index_body(text): - match = CHILD_ENTRY_RE.match(line) - if not match: + path_match = CHILD_PATH_RE.match(line) + if not path_match: continue - child_rel = match.group(1) + + child_rel = path_match.group(1) + if not CHILD_ENTRY_RE.match(line): + errors.append(f"{rel}: child index entry for {child_rel} missing description") + child_path = (agents_path.parent / child_rel).resolve() if child_path not in agents_set: errors.append(f"{rel}: child index points to missing {child_rel}") diff --git a/tests/AGENTS.md b/tests/AGENTS.md new file mode 100644 index 0000000..db1bc4d --- /dev/null +++ b/tests/AGENTS.md @@ -0,0 +1,29 @@ +# Tests DOX + +## Purpose + +Owns regression tests for optional DOX tooling. + +## Ownership + +- `test_dox_lint.py` verifies structural validator behavior and edge cases. + +## Local Contracts + +- Tests must use Python standard library only. +- Tests should build temporary repositories rather than mutating the real repository. +- Each bug fixed from review feedback should get a focused regression test when practical. + +## Work Guidance + +- Keep fixtures small and explicit. +- Test validator semantics, not Markdown rendering. + +## Verification + +- Run `python3 -m unittest discover -s tests` from the repository root. +- Run `python3 scripts/dox_lint.py .` from the repository root. + +## Child DOX Index + +No child DOX files yet. diff --git a/tests/test_dox_lint.py b/tests/test_dox_lint.py new file mode 100644 index 0000000..6367655 --- /dev/null +++ b/tests/test_dox_lint.py @@ -0,0 +1,116 @@ +import importlib.util +import textwrap +import unittest +from pathlib import Path +from tempfile import TemporaryDirectory + + +ROOT = Path(__file__).resolve().parents[1] +SPEC = importlib.util.spec_from_file_location("dox_lint", ROOT / "scripts" / "dox_lint.py") +assert SPEC is not None +assert SPEC.loader is not None +dox_lint = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(dox_lint) + + +ROOT_AGENTS = """ + +# DOX framework + +## Child DOX Index + +- `docs/AGENTS.md` — docs scope. + +""" + +CHILD_AGENTS = """ +# Docs DOX + +## Purpose + +Docs. + +## Ownership + +Docs. + +## Local Contracts + +Docs. + +## Work Guidance + +Docs. + +## Verification + +Docs. + +## Child DOX Index + +No child DOX files yet. +""" + + +def write(path: Path, content: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(textwrap.dedent(content).lstrip(), encoding="utf-8") + + +class DoxLintTests(unittest.TestCase): + def test_valid_tree_passes(self): + with TemporaryDirectory() as tmp: + root = Path(tmp) + write(root / "AGENTS.md", ROOT_AGENTS) + write(root / "docs" / "AGENTS.md", CHILD_AGENTS) + + self.assertEqual(dox_lint.validate(root), []) + + def test_core_marker_mentions_without_html_comments_fail(self): + with TemporaryDirectory() as tmp: + root = Path(tmp) + write( + root / "AGENTS.md", + """ + # DOX framework + + This text mentions `DOX-CORE-START` and `DOX-CORE-END`, but has no comments. + + ## Child DOX Index + + No child DOX files yet. + """, + ) + + errors = dox_lint.validate(root) + + self.assertIn("root AGENTS.md missing DOX-CORE-START HTML comment marker", errors) + self.assertIn("root AGENTS.md missing DOX-CORE-END HTML comment marker", errors) + + def test_child_index_entry_missing_description_fails_but_path_is_checked(self): + with TemporaryDirectory() as tmp: + root = Path(tmp) + write( + root / "AGENTS.md", + """ + + # DOX framework + + ## Child DOX Index + + - `docs/AGENTS.md` + - `missing/AGENTS.md` + + """, + ) + write(root / "docs" / "AGENTS.md", CHILD_AGENTS) + + errors = dox_lint.validate(root) + + self.assertIn("AGENTS.md: child index entry for docs/AGENTS.md missing description", errors) + self.assertIn("AGENTS.md: child index entry for missing/AGENTS.md missing description", errors) + self.assertIn("AGENTS.md: child index points to missing missing/AGENTS.md", errors) + + +if __name__ == "__main__": + unittest.main()