diff --git a/README.md b/README.md index 6a2571e..66cd18c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A [Claude Code](https://claude.ai/code) plugin marketplace for **linter-driven d | | Plugin | Version | For | |---|--------|---------|-----| -| 🐹 | [`go-linter-driven-development`](go-linter-driven-development/README.md) | 2.6.0 | Go | +| 🐹 | [`go-linter-driven-development`](go-linter-driven-development/README.md) | 2.10.0 | Go | | βš›οΈ | [`ts-react-linter-driven-development`](ts-react-linter-driven-development/README.md) | 1.2.0 | TypeScript + React | Plus the standalone rule documents the plugins grew out of: diff --git a/go-linter-driven-development/.claude-plugin/plugin.json b/go-linter-driven-development/.claude-plugin/plugin.json index 10e6e29..dc78570 100644 --- a/go-linter-driven-development/.claude-plugin/plugin.json +++ b/go-linter-driven-development/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "go-linter-driven-development", - "version": "2.9.1", - "description": "Rules-as-data linter-driven development workflow for Go: 12 single-source rule files, thin directional skills (design, TDD implementation, refactoring, testing, review, documentation), and a hunter/skeptic/critic review architecture with parallel single-rule reviewers, an over-abstraction skeptic, and a comment critic enforcing the Comment Value Toolbox", + "version": "2.10.0", + "description": "Rules-as-data linter-driven development workflow for Go: 12 single-source rule files, thin directional skills (design, TDD implementation, refactoring, testing, review, documentation), a hunter/skeptic/critic review architecture with parallel single-rule reviewers, an over-abstraction skeptic, and a comment critic enforcing the Comment Value Toolbox, plus an OKF-conformant repo brain with a shippable CI conformance gate", "author": { "name": "Dan Mordechay" }, diff --git a/go-linter-driven-development/CHANGELOG.md b/go-linter-driven-development/CHANGELOG.md index f3447ad..3cbd246 100644 --- a/go-linter-driven-development/CHANGELOG.md +++ b/go-linter-driven-development/CHANGELOG.md @@ -3,6 +3,63 @@ All notable changes to the `go-linter-driven-development` plugin are documented here. Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [Semantic Versioning](https://semver.org/). +## [2.10.0] - 2026-08-20 + +The repo brain had one audience: a session with this plugin installed. In a +monorepo most contributors β€” and their agents β€” don't have it, so the network's +rules lived nowhere they could find and nothing enforced them. This release +makes the doc root a standard, self-describing artifact: an Open Knowledge +Format (OKF v0.2) bundle any tool can consume, with its own maintenance manual +inside and a CI gate outside. + +### Added + +- **OKF v0.2 bundle conformance (R9)**: every doc-root file carries YAML + frontmatter β€” content docs `type`/`title`/`description`/`timestamp` (+ + optional `tags` and lifecycle `status`/`stale_after`, the frontmatter-native + form of the ⚠️ stale flag); indexes carry `type: index` (a documented R9 + extension β€” OKF keeps reserved index.md bare) and never a `timestamp`; the + root index carries `okf_version`. New falsifying question **Q7** checks the + bundle contract mechanically. +- **Derivation rule (R9)**: every index line is derived from the frontmatter + one level down β€” a doc's line IS its `description` β€” so the map is + regenerable and cannot drift. The map of maps is directory-shaped (per-topic + subdirectories with their own frontmattered index.md), and the split lands in + the same commit as the `See docs/...` path rewrite. +- **One-way link policy (R9)**: write an edge only when no structure implies + it β€” no childβ†’parent backlinks, no `related:` frontmatter key, lateral links + inline with the reason in the sentence; an optional `## Related` section is + capped at 3 reason-claused entries (a ceiling, not a quota). `log.md` is + never emitted. +- **Self-hosting conventions doc**: BOOTSTRAP creates `/conventions.md` + (template in reference.md) β€” the network's maintenance rules written for + contributors without this plugin, listed first in the index. +- **AGENTS.md routing block**: upgraded from "fallback when CLAUDE.md is + absent" to first-class multi-tool coverage β€” root and nested per sub-project + (closest file wins), pointing every AGENTS.md-reading agent at the index and + conventions.md. Authored once: CLAUDE.md embeds it via `@AGENTS.md` (plus the + `@/index.md` map import) instead of duplicating the routing prose. +- **New `scripts/check-repo-brain.sh`**: dependency-free conformance gate + running Q1–Q3 and Q7 over every doc root (repo root plus each go.mod + sub-project): transitive reachability from the root index, both edge + directions, the file:line ban (URL spans stripped, not whole lines), + exact-path root wiring (missing AGENTS.md routing is an advisory), the full + frontmatter contract (termination, per-class required keys, root-only + `okf_version`), and index-line derivation against each target's + `description`. BOOTSTRAP installs it into target repos and suggests the + one-line CI wiring. Exit 0 clean/not-adopted, 1 violations, 2 usage error; + every failure message points at conventions.md. + +### Changed + +- **BOOTSTRAP is now a migration pass too**: frontmatter is verified-or-added + (never duplicated), so a network wired by an older plugin version converges + to the current rules in one idempotent re-run; an un-inferable `type` goes to + the advisory report, never guessed. +- **Feature Doc Template's `Related` section** reconciled with the one-way link + policy: optional, ≀3 entries, each with a reason clause, none duplicating an + inline link. + ## [2.9.1] - 2026-07-23 A real 143-line file surfaced the gap v2.9.0 left open: every one of its nine diff --git a/go-linter-driven-development/README.md b/go-linter-driven-development/README.md index 3df8656..41cfe0c 100644 --- a/go-linter-driven-development/README.md +++ b/go-linter-driven-development/README.md @@ -24,6 +24,7 @@ go-linter-driven-development/ β”‚ pre-commit-review Β· testing Β· documentation (thin directional views) β”œβ”€β”€ agents/ rule-hunter Β· overabstraction-skeptic Β· lint-fixer (isolated workers) β”œβ”€β”€ commands/ go-ldd-analyze Β· autopilot Β· quickfix Β· prepare Β· review Β· status Β· wire-repo-brain +β”œβ”€β”€ scripts/ check-repo-brain.sh β€” repo-brain conformance gate, installed into target repos by /wire-repo-brain └── hooks/ package-size gate ``` @@ -82,7 +83,7 @@ Isolated contexts matter: the `lint-fixer` loop's token noise stays out of your | R6 | [`rules/R6-test-only-interfaces.md`](rules/R6-test-only-interfaces.md) | No interface whose only second implementer is a test double | | R7 | [`rules/R7-test-placement.md`](rules/R7-test-placement.md) | `pkg_test` only, no wantErr conditionals, right-rung tests, no sleeps | | R8 | [`rules/R8-no-globals.md`](rules/R8-no-globals.md) | No package-level state; no `context.Background()` in library code | -| R9 | [`rules/R9-repo-brain.md`](rules/R9-repo-brain.md) | Documentation network: fact at its lowest rung, reachable from the root, edges both directions; index wired into CLAUDE.md | +| R9 | [`rules/R9-repo-brain.md`](rules/R9-repo-brain.md) | Documentation network: fact at its lowest rung, reachable from the root, edges both directions; index wired into CLAUDE.md; doc root is an OKF bundle (frontmatter, derived index lines) | | R10 | [`rules/R10-concurrency-safety.md`](rules/R10-concurrency-safety.md) | Goroutines with owners and exit paths; shared state guarded where it lives; no production sleeps | | R11 | [`rules/R11-conditional-dispatch.md`](rules/R11-conditional-dispatch.md) | One dispatch owner per kind/variant family (Anti-IF): duplicated kind-switches become interface/map dispatch chosen once at the boundary; a single switch stays and goes exhaustive | | R12 | [`rules/R12-mutation-discipline.md`](rules/R12-mutation-discipline.md) | Mutation only through invariant-owning methods: constructors copy collections in, queries copy (or iterate) out, no query/modifier hybrids, no setters around validating constructors | @@ -106,7 +107,7 @@ Isolated contexts matter: the `lint-fixer` loop's token noise stays out of your | [`@refactoring`](skills/refactoring/SKILL.md) | BACKWARD view β€” routes each linter/review failure to its owning rule's Fix pattern; preparatory mode reshapes ahead of a planned change (Phase 1.5) | | [`@pre-commit-review`](skills/pre-commit-review/SKILL.md) | Orchestrates the hunter/skeptic review (Phase 4); reports, never edits | | [`@testing`](skills/testing/SKILL.md) | The composition ladder β€” test each behavior at the lowest rung that contains it | -| [`@documentation`](skills/documentation/SKILL.md) | Repo-brain author (R9) β€” behavior docs + network wiring; FEATURE mode (Phase 5) / BOOTSTRAP mode | +| [`@documentation`](skills/documentation/SKILL.md) | Repo-brain author (R9) β€” behavior docs + network wiring, OKF conformance + conventions self-hosting; FEATURE mode (Phase 5) / BOOTSTRAP mode | **Agents β†’ spawned by** (payload-fed, isolated): @@ -126,7 +127,7 @@ Isolated contexts matter: the `lint-fixer` loop's token noise stays out of your | [`/go-ldd-analyze [files]`](commands/go-ldd-analyze.md) | πŸ” Tests + lint + review, combined report | ❌ No | βœ… Optional | | [`/go-ldd-review [files]`](commands/go-ldd-review.md) | πŸ” Commit-readiness check | ❌ No | βœ… Optional | | [`/go-ldd-status`](commands/go-ldd-status.md) | Show current phase + progress | N/A | β€” | -| [`/wire-repo-brain [path]`](commands/wire-repo-brain.md) | Wire the documentation network in one pass: upward edges β†’ docs β†’ index.md β†’ CLAUDE.md (@documentation BOOTSTRAP) | βœ… Wiring only | βœ… Optional | +| [`/wire-repo-brain [path]`](commands/wire-repo-brain.md) | Wire the documentation network in one pass: frontmatter β†’ upward edges β†’ docs β†’ index.md β†’ CLAUDE.md/AGENTS.md + conventions.md + conformance script (@documentation BOOTSTRAP) | βœ… Wiring only | βœ… Optional | ## How Auto-Detection Works diff --git a/go-linter-driven-development/commands/wire-repo-brain.md b/go-linter-driven-development/commands/wire-repo-brain.md index a897c33..fd3023a 100644 --- a/go-linter-driven-development/commands/wire-repo-brain.md +++ b/go-linter-driven-development/commands/wire-repo-brain.md @@ -19,20 +19,32 @@ mode** against `$ARGUMENTS` (default: the current repo root). The skill's protoc authoritative; this command adds nothing to it. One pass delivers the whole chain: 1. Doc root discovered (`.ai/` β†’ `.ainav/` β†’ `docs/`; per sub-project in a monorepo) -2. Existing docs inventoried and classified (stale docs indexed with a ⚠️ flag) -3. `index.md` built β€” short, grouped, one line per doc (map of maps past ~300 lines) -4. CLAUDE.md wired with the `@/index.md` import (AGENTS.md: plain reference) -5. **Upward edges wired**: every confidently-anchorable doc gets its one-line +2. Existing docs inventoried and classified (stale docs indexed with a ⚠️ flag); + OKF frontmatter verified-or-added (un-inferable types reported) +3. `index.md` built β€” frontmattered, short, grouped, one line per doc derived from + each doc's `description` (directory-shaped map of maps past ~300 lines) +4. AGENTS.md routing block authored once (root, and nested per sub-project in a + monorepo); CLAUDE.md embeds it (`@AGENTS.md`) + the `@/index.md` import +5. `/conventions.md` created/verified (listed first in the index) and the + plugin's `scripts/check-repo-brain.sh` installed β€” the report suggests the CI + one-liner +6. **Upward edges wired**: every confidently-anchorable doc gets its one-line `// See /.md ...` edge on its front-door symbol -6. R9 confirmation pass + the advisory findings report (broken edges, edge-policy - violations, rung-2 gaps, stale/unwired docs) +7. R9 confirmation pass β€” Q1–Q3 and Q7 via the installed script β€” + the advisory + findings report (broken edges, edge-policy violations, rung-2 gaps, + stale/unwired docs, types needing a human call) **What this command does NOT do** (by design β€” the skill's constraints): - Generate or rewrite content docs β€” gaps are reported for FEATURE mode to fill + (conventions.md and the copied check script are the two sanctioned artifacts) - Decide the fate of stale docs β€” refresh / remove / keep-as-roadmap is your call -- Touch anything beyond doc files, `index.md`, CLAUDE.md/AGENTS.md, and one-line - godoc edge additions (verified with `go vet` after each) +- Add CI workflows β€” the report only suggests `bash scripts/check-repo-brain.sh` +- Touch anything beyond doc files, `index.md`, `conventions.md`, + CLAUDE.md/AGENTS.md, the copied check script, and one-line godoc edge additions + (verified with `go vet` after each) When it finishes, review the report, then `git diff` β€” the changes should read as pure documentation-network wiring. Re-run any time: the pass is idempotent (existing -index lines are refreshed, existing edges and wiring are verified, not duplicated). +index lines are refreshed from frontmatter; existing edges, wiring, conventions, and +the script are verified, not duplicated β€” a repo wired by an older plugin version +converges to the current rules in one pass). diff --git a/go-linter-driven-development/rules/R9-repo-brain.md b/go-linter-driven-development/rules/R9-repo-brain.md index 2ec7c49..2ab3595 100644 --- a/go-linter-driven-development/rules/R9-repo-brain.md +++ b/go-linter-driven-development/rules/R9-repo-brain.md @@ -8,7 +8,9 @@ carry it, higher rungs summarizing and pointing down, never duplicating. Two invariants hold the network together: **reachability** (every doc is reachable from the root: CLAUDE.md β†’ index.md β†’ doc β€” no orphans) and **bidirectionality** (code points up at its feature doc; docs point down at code via greppable symbols; the -index points everywhere). +index points everywhere). The doc root itself is an Open Knowledge Format (OKF +v0.2) bundle: every file carries YAML frontmatter, a file's path is its identity, +and the map is derived from the frontmatter below it (bundle policy below). ## Why @@ -123,12 +125,25 @@ func (p Policy) Do(ctx context.Context, op Op) error { ```markdown +--- +type: feature +title: Retry policy +description: why retries use capped full jitter; `Policy` API +timestamp: 2026-08-20T00:00:00Z +--- Entry point: `Policy.Do`. Construction: `ParsePolicy` β€” validates the cap against the base delay, so an unbounded backoff cannot exist. ``` ```markdown +--- +type: index +title: Repo map +description: map of all repo docs +tags: [go] +okf_version: "0.2" +--- # Repo map **Resilience** @@ -146,7 +161,9 @@ the WHY the code cannot (the incident) and carries the upward edge to the featur doc; the doc points down with the greppable tokens `Policy.Do` and `ParsePolicy` β€” no path, no line number β€” and is listed in the index; CLAUDE.md imports the index, so the whole map is in context at session start. Grep `Policy` or open CLAUDE.md: -either way, the jitter incident is two hops away. +either way, the jitter incident is two hops away. And the index line is not +authored twice: it IS `retry-policy.md`'s `description`, derived from the +frontmatter β€” the map regenerates instead of drifting. ## Design guidance @@ -327,17 +344,72 @@ rule's (Q4 below β€” they must carry why/context, not restate the identifier). devices at **package/directory granularity** β€” a directories-only tree is just a set of package-path citations; file-level leaf entries are the violation. Prune the leaves, keep the tree. +- **Links are one-way β€” write an edge only when no structure implies it.** A doc's + parent is `index.md` in its own directory, derivable from the path alone: never + write a childβ†’parent backlink, and never a `related:` frontmatter key β€” body + links ARE the machine-readable graph. Lateral docβ†’doc links go inline, with the + relationship stated in the sentence that carries the link ("auth retries use the + capped-jitter policy β€” [retry-policy.md](retry-policy.md)"). The one axis no + structure carries is code↔docs β€” which is exactly why those edges are written in + both directions and grep-verified (Q2). +- **Optional `## Related` section**: for real relationships that found no natural + sentence in the body. At most 3 entries β€” a cap, not a quota; zero is a valid + count, and a doc with none has no section. Every entry carries a reason clause + (a bare link is a violation, same as a WHAT-comment), and never duplicates a + link already inline. Entries are ordinary edges to Q2. + +### Frontmatter β€” the doc root as an OKF bundle (rungs 2–3) + +The doc root conforms to Open Knowledge Format v0.2 (markdown bundle: one concept +per file, path = identity, links form the graph), plus documented R9 extensions. + +- **Content docs** carry required `type` (`feature` / `architecture` / `guide`), + `title`, `description`, and `timestamp`; optional `tags`, and optional lifecycle + keys `status: draft|stable|deprecated` and `stale_after` β€” the frontmatter-native + form of the ⚠️ stale flag. +- **Indexes carry frontmatter as an R9 extension** (OKF v0.2 keeps reserved + `index.md` files frontmatter-free except a root `okf_version`): every index gets + `type: index`, `title`, `description`, `tags`; the root index adds + `okf_version: "0.2"`. **Never a `timestamp` on an index** β€” the index is derived, + not authored, and a churning timestamp in a derived file is drift bait. +- **Derivation rule**: every index line is derived from the frontmatter one level + down β€” a doc's line from its `description`, prefixed ⚠️ when its lifecycle says + so (`status: deprecated`, or `stale_after` in the past) or when a bootstrap pass + classified it stale; a sub-index's line in the root map from that sub-index's + `description` + `tags`. The map is regenerable, so it cannot drift from the + frontmatter that owns each fact. +- **Never emit `log.md`** β€” OKF reserves it for change history; this rule is + behavior-not-history, so the file must not exist in a doc root. +- **Broken links stay violations.** OKF tells consumers to tolerate dangling links + as not-yet-written knowledge; internally that tolerance would silence the drift + alarm. The *(planned)* marker (Q2) is the one sanctioned form of a + not-yet-written reference. +- Copy-pasteable templates (content doc, index, root index, conventions doc) live + in @documentation's reference.md; only the policy lives here. ### The index (rung 3) and the root - `index.md` lives in the doc root and MUST stay short: a concise reference guide, **one line per doc**, grouped by topic. It is the map, not a doc. -- Past ~300 lines it becomes a **map of maps**: the root index shrinks to links to - short topic or sub-project sub-indexes. This keeps the imported root cheap and - every doc still two hops from CLAUDE.md. -- **Root wiring**: CLAUDE.md embeds the map via an `@/index.md` import - (e.g. `@docs/index.md`) so it is in context at session start. AGENTS.md is the - fallback host where CLAUDE.md is not used. +- Past ~300 lines it becomes a **map of maps**, and the split is directory-shaped: + each topic becomes a subdirectory with its own frontmattered `index.md` (OKF's + per-directory reserved file), and the root index shrinks to one derived line per + sub-index. The imported root stays cheap and every doc is still two hops away + (the root map is hop 0 β€” it rides in with the CLAUDE.md import). The split moves + files, so it lands in the **same commit** as the Q2-driven rewrite of code-side + `See docs/...` paths β€” a moved doc with a stale code edge is a broken network + between commits. +- **Root wiring**: the routing block is authored ONCE, in AGENTS.md β€” a short + plain block (start at the index; conventions in `/conventions.md`) at + the repo root and, in a monorepo, nested per sub-project (closest file wins). + It serves every tool that reads AGENTS.md instead of CLAUDE.md. CLAUDE.md never + duplicates it: it embeds AGENTS.md via `@AGENTS.md` and adds the + `@/index.md` import (e.g. `@docs/index.md`) so the map itself is in + context at session start. +- **`/conventions.md` is the self-hosting doc** (`type: guide`): the + network's own maintenance rules β€” frontmatter templates, link rules, the Related + policy, the never-list β€” written for a contributor without this plugin. It is + listed FIRST in the index, one pointer line. ### Doc root discovery and monorepos @@ -358,8 +430,14 @@ rule's (Q4 below β€” they must carry why/context, not restate the identifier). - **Rewire orphan doc**: add its one line to `index.md` *and* add a code-side edge (`See docs/.md`) from the package or type it describes β€” both invariants, reachability and bidirectionality, in one move. -- **Wire the root**: add or repair the `@/index.md` import in CLAUDE.md - (AGENTS.md has no import syntax β€” use a plain reference line). +- **Wire the root**: add or repair the AGENTS.md routing block (plain lines + pointing at the index and `conventions.md` β€” authored once, there) and + CLAUDE.md's two imports: `@AGENTS.md` and `@/index.md`. CLAUDE.md + never restates the routing prose. +- **Add missing frontmatter**: verify-or-add the required keys on any doc or index + that lacks them; derive the index line from the `description`. A `type` that + cannot be inferred from the doc's content is reported for a human call, never + guessed silently. - **Update the stale doc with the behavior change**: rewrite the affected section to describe current behavior β€” never append a changelog entry (the behavior-not-history discipline lives in @documentation). @@ -368,7 +446,9 @@ rule's (Q4 below β€” they must carry why/context, not restate the identifier). Answer each with evidence (`file:line`, command output) β€” never a bare verdict. Determine the doc root first (discovery order above); `` below is that -directory. +directory. Q1–Q3 and Q7 are fully mechanical: the plugin ships them as +`scripts/check-repo-brain.sh` (installed into the repo by the bootstrap pass), so +one command answers all four. 1. **Is any doc an orphan?** Detection: `find -name '*.md' ! -name 'index.md'` versus the link @@ -388,16 +468,23 @@ directory. detection: `grep -nE '\.go(:[0-9]+)?|line [0-9]+' /*.md | grep -v '://'` (the `://` filter exempts URLs, e.g. pkg.go.dev links) β€” regardless of whether the coordinate currently resolves. - Two exemptions: an index line carrying the ⚠️ stale flag (cites an unresolved - `Symbol`) is a recorded finding, not a broken edge β€” the decision to refresh, - remove, or keep it is the user's. And backticks are a resolvability contract β€” + Two exemptions, both scoped to symbol resolution (the file-path ban has no + exemption beyond URLs): an index line carrying the ⚠️ stale flag (cites an + unresolved `Symbol`) is a recorded finding, not a broken edge β€” the decision to + refresh, remove, or keep it is the user's. And backticks are a resolvability + contract β€” a future/roadmap symbol is written in prose or explicitly marked *(planned)*, and *(planned)*-marked citations are exempt from resolution. 3. **Is the root unwired?** - Detection: `grep -l 'index.md' CLAUDE.md AGENTS.md 2>/dev/null`. - Violation: no hit β€” the map exists but is not in context at session start; - the `@/index.md` import is missing. + Detection: for each doc root, `grep -l '/index.md' CLAUDE.md AGENTS.md + 2>/dev/null` in the root's owning project directory β€” the exact path, never a + bare `index.md` mention. A monorepo sub-root also counts as wired when the + repo-root index links into it. + Violation: no hit anywhere β€” the map exists but is not in context at session + start; the `@/index.md` import is missing. + Advisory: CLAUDE.md is wired but AGENTS.md lacks the routing reference β€” every + tool that reads AGENTS.md instead of CLAUDE.md starts blind. 4. **Does a doc comment on an exported symbol state WHAT instead of WHY?** Detection: for each exported declaration in the diff @@ -424,3 +511,21 @@ directory. Violation (advisory): a package with a citing feature doc changed and the doc did not β€” flag it with the doc's path as evidence; the fix is updating the affected section, never appending history. + +7. **Does any file break the bundle contract?** + Detection: for every `.md` under ``: the first line is `---` and a + closing `---` follows (an unterminated block is broken); the block carries the + required keys β€” content docs `type`, `title`, `description`, `timestamp`; + indexes `type: index`, `title`, `description`, `tags` and never `timestamp`; + the root index also `okf_version`, and only the root (the key is the root's + alone). `grep -rn '^related:'` over doc-root frontmatter; + `find -name 'log.md'`. For every index line shaped + `- [doc](path) β€” text`, compare the text against the target's `description` + (⚠️-flagged lines exempt β€” they are recorded findings, not derivable lines). + Violation: a missing or unterminated frontmatter block; a missing required + key; a `timestamp` on an index; `okf_version` off the root index; a + `related:` key anywhere; a `log.md` anywhere in the doc root; an index line + that drifted from the `description` it derives from. + Advisory branch: a doc whose `stale_after` is in the past (or + `status: deprecated`) with no ⚠️ on its index line β€” recorded staleness the + map does not show; the fix is re-deriving the line (derivation rule above). diff --git a/go-linter-driven-development/scripts/check-repo-brain.sh b/go-linter-driven-development/scripts/check-repo-brain.sh new file mode 100755 index 0000000..e7ef3e0 --- /dev/null +++ b/go-linter-driven-development/scripts/check-repo-brain.sh @@ -0,0 +1,362 @@ +#!/usr/bin/env bash +# Repo-brain conformance gate for the go-linter-driven-development plugin. +# +# Runs R9's mechanical falsifying questions over every doc root (each an OKF +# bundle) so CI β€” and developers without the plugin β€” can hold the +# documentation network's invariants. Installed into target repos by the +# documentation skill's BOOTSTRAP pass (/wire-repo-brain). +# +# Usage: bash scripts/check-repo-brain.sh [repo-root] (default: cwd) +# CI: one line β€” bash scripts/check-repo-brain.sh +# +# Doc roots are discovered at the repo root AND at every sub-project (a +# directory holding go.mod), using R9's order: .ai/ -> .ainav/ -> docs/. +# +# Checks (numbering follows rules/R9-repo-brain.md's falsifying questions): +# Q1 orphans β€” every doc is reachable from its bundle's root index, +# transitively through sub-indexes +# Q2 edges β€” codeβ†’docs paths resolve; doc links resolve; doc-cited +# exported symbols grep in the repo; no file:line +# citations (URL spans stripped before the test) +# Q3 root wiring β€” CLAUDE.md or AGENTS.md in the root's owning project +# carries the exact /index.md path (a monorepo +# sub-root may instead be linked from the repo-root +# index); AGENTS.md missing the reference is an advisory +# Q7 bundle contractβ€” frontmatter present and terminated with the required +# keys (content docs: type/title/description/timestamp; +# indexes: type: index/title/description/tags, never +# timestamp; root index alone adds okf_version); no +# `related:` key; no log.md; every index line's text +# matches the target's `description` (⚠️ lines exempt) +# +# Heuristics (documented, deliberate): +# - links are inline-markdown only (`[name](path.md)`, optional "title" +# stripped); reference-style links are not checked. +# - docsβ†’code checks only backticked tokens shaped like exported Go +# identifiers (`Foo`, `Foo.Bar`) that contain a lowercase letter; other +# backticks (paths, flags, ALL-CAPS initialisms, ) are skipped. +# - lines carrying the ⚠️ stale flag or a *(planned)* marker are exempt from +# symbol resolution and derivation (R9 Q2/Q7 exemptions); the file:line ban +# has no exemption beyond URL spans. +# - fenced code blocks (``` or ~~~, indented up to 3 spaces; toggle, not +# length-matched) are skipped for symbol resolution. +# +# Exit codes: 0 clean (or repo has no doc root yet β€” advisory no-op) +# 1 one or more violations (details on stderr, summary last) +# 2 usage error +# +# Uses only POSIX-portable tools: find, grep, sed, awk, head, sort, wc. No jq/python. + +set -u + +REPO_ROOT="${1:-$(pwd)}" +if [[ ! -d "$REPO_ROOT" ]]; then + echo "check-repo-brain: not a directory: $REPO_ROOT" >&2 + exit 2 +fi +cd "$REPO_ROOT" || exit 2 + +# ---------- doc-root discovery: repo root + every go.mod directory ---------- +discover_docroot() { # -> docroot path or '' + local base="$1" d p + for d in .ai .ainav docs; do + if [[ "$base" == "." ]]; then p="$d"; else p="$base/$d"; fi + [[ -d "$p" ]] && { printf '%s\n' "$p"; return; } + done +} + +PROJS=() +ROOTS=() +seen_roots=" " +add_root() { # + local r + r=$(discover_docroot "$1") + [[ -z "$r" ]] && return + case "$seen_roots" in *" $r "*) return ;; esac + seen_roots="$seen_roots$r " + PROJS+=("$1") + ROOTS+=("$r") +} +add_root "." +while IFS= read -r gm; do + p=$(dirname "$gm"); p="${p#./}" + [[ "$p" == "." || -z "$p" ]] && continue + add_root "$p" +done < <(find . -name go.mod -not -path '*/vendor/*' -not -path './.git/*' 2>/dev/null | sort) + +if (( ${#ROOTS[@]} == 0 )); then + echo "check-repo-brain: no doc root (.ai/, .ainav/, docs/) at the repo root or any go.mod sub-project β€” nothing to check yet; run /wire-repo-brain to bootstrap" + exit 0 +fi +ROOT_BUNDLE="" +for i in "${!PROJS[@]}"; do + [[ "${PROJS[$i]}" == "." ]] && ROOT_BUNDLE="${ROOTS[$i]}" +done + +violations=0 +CUR_DOCROOT="${ROOTS[0]}" +fail() { + echo " $1 β€” see $CUR_DOCROOT/conventions.md" >&2 + violations=$((violations + 1)) +} +note() { echo " advisory: $1"; } + +# canon -> physical path with .. resolved (empty if parent dir missing) +canon() { + local dir base + dir=$(dirname "$1") + base=$(basename "$1") + (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$base") +} + +# resolve_link -> absolute path ('' for URLs/anchors) +resolve_link() { + local from="$1" target="$2" docroot="$3" + target="${target%%#*}" + target="${target%% *}" # strip optional "title" + [[ -z "$target" || "$target" == *"://"* ]] && return 0 + if [[ "$target" == /* ]]; then + printf '%s\n' "$(canon "$docroot/${target#/}")" # bundle-relative (OKF) + else + printf '%s\n' "$(canon "$(dirname "$from")/$target")" + fi +} + +# frontmatter helpers ----------------------------------------------------- +fm_close_line() { # -> line number of closing --- (or '') + awk 'NR > 1 && /^---$/ { print NR; exit }' "$1" +} +fm_block() { # -> frontmatter body + sed -n "2,$(( $2 - 1 ))p" "$1" +} +desc_of() { # -> description value ('' if none) + local close + [[ "$(head -1 "$1" 2>/dev/null)" == "---" ]] || return 0 + close=$(fm_close_line "$1") + [[ -z "$close" ]] && return 0 + fm_block "$1" "$close" | grep -m1 '^description:' \ + | sed -e 's/^description:[[:space:]]*//' -e 's/[[:space:]]*$//' +} + +have_go=0 +if find . -name '*.go' -not -path './vendor/*' -not -path '*/vendor/*' -not -path './.git/*' -print -quit 2>/dev/null | grep -q .; then + have_go=1 +fi + +# ---------- Q2: codeβ†’docs edges (repo-wide; resolved from repo root, then the +# citing file's own sub-project) ---------- +docroot_for_file() { # -> docroot of the longest matching project dir + local f="${1#./}" best="" i + for i in "${!PROJS[@]}"; do + local p="${PROJS[$i]}" + [[ "$p" == "." ]] && { [[ -z "$best" ]] && best="${ROOTS[$i]}"; continue; } + case "$f" in "$p"/*) best="${ROOTS[$i]}" ;; esac + done + printf '%s\n' "${best:-${ROOTS[0]}}" +} + +if (( have_go )); then + while IFS= read -r hit; do + file="${hit%%:*}"; rest="${hit#*:}"; line="${rest%%:*}"; target="${rest#*:}" + [[ -f "$target" ]] && continue + proj_ok=0 + for i in "${!PROJS[@]}"; do + p="${PROJS[$i]}"; [[ "$p" == "." ]] && continue + case "${file#./}" in "$p"/*) [[ -f "$p/$target" ]] && proj_ok=1 ;; esac + done + if (( ! proj_ok )); then + CUR_DOCROOT=$(docroot_for_file "$file") + fail "[Q2] $file:$line β€” code edge points at missing $target" + fi + done < <(grep -rnoE '(docs|\.ai|\.ainav)/[A-Za-z0-9._/-]+\.md' \ + --include='*.go' --exclude-dir=vendor --exclude-dir=.git . 2>/dev/null) +fi + +# ---------- per-bundle checks ---------- +check_bundle() { # + local proj="$1" docroot="$2" + CUR_DOCROOT="$docroot" + local root_index="$docroot/index.md" + local root_index_c + root_index_c=$(canon "$root_index") + + # --- Q1: transitive reachability from the bundle's root index --- + local reachable="" visited="" queue=("$root_index") + while (( ${#queue[@]} > 0 )); do + local idx="${queue[0]}"; queue=("${queue[@]:1}") + local idx_c; idx_c=$(canon "$idx") + case "$visited" in *"$idx_c"$'\n'*) continue ;; esac + visited="$visited$idx_c"$'\n' + [[ -f "$idx" ]] || continue + while IFS= read -r raw; do + local t="${raw#](}"; t="${t%)}" + [[ "$t" == *.md* ]] || continue + local resolved; resolved=$(resolve_link "$idx" "$t" "$docroot") + [[ -z "$resolved" ]] && continue + reachable="$reachable$resolved"$'\n' + [[ "$(basename "$resolved")" == "index.md" ]] && queue+=("$resolved") + done < <(grep -oE '\]\([^)]+\)' "$idx" 2>/dev/null) + done + if [[ ! -f "$root_index" ]]; then + fail "[Q1] $docroot β€” no index.md: the bundle has no map" + fi + while IFS= read -r doc; do + local c; c=$(canon "$doc") + [[ "$c" == "$root_index_c" ]] && continue + case "$reachable" in *"$c"$'\n'*) ;; *) + fail "[Q1] $doc β€” orphan: not reachable from $root_index" ;; + esac + done < <(find "$docroot" -type f -name '*.md') + + # --- Q2: every doc link resolves --- + while IFS= read -r md; do + while IFS= read -r raw; do + local t="${raw#](}"; t="${t%)}" + [[ "$t" == *.md* ]] || continue + local resolved; resolved=$(resolve_link "$md" "$t" "$docroot") + [[ -z "$resolved" ]] && continue + [[ -f "$resolved" ]] || fail "[Q2] $md β€” link target does not exist: $t" + done < <(grep -oE '\]\([^)]+\)' "$md" 2>/dev/null) + done < <(find "$docroot" -type f -name '*.md') + + # --- Q2: docsβ†’code β€” backticked exported symbols must grep --- + if (( have_go )); then + while IFS= read -r md; do + local in_fence=0 lineno=0 line + while IFS= read -r line; do + lineno=$((lineno + 1)) + if printf '%s' "$line" | grep -qE '^ {0,3}(```|~~~)'; then + in_fence=$((1 - in_fence)); continue + fi + (( in_fence )) && continue + case "$line" in *'⚠️'*|*'*(planned)*'*) continue ;; esac + local tok + while IFS= read -r tok; do + tok="${tok#\`}"; tok="${tok%\`}" + printf '%s' "$tok" | grep -qE '^[A-Z][A-Za-z0-9]*(\.[A-Z][A-Za-z0-9]*)?$' || continue + printf '%s' "$tok" | grep -q '[a-z]' || continue + if [[ "$tok" == *.* ]]; then + local method="${tok#*.}" + grep -rqE "func .*\) ${method}\(|func ${method}\(" \ + --include='*.go' --exclude-dir=vendor --exclude-dir=.git . 2>/dev/null && continue + fail "[Q2] $md:$lineno β€” backticked \`$tok\` does not resolve (method ${method} not found)" + else + grep -rqE "(type|func) ${tok}\b" \ + --include='*.go' --exclude-dir=vendor --exclude-dir=.git . 2>/dev/null && continue + fail "[Q2] $md:$lineno β€” backticked \`$tok\` does not resolve (no type/func ${tok})" + fi + done < <(printf '%s\n' "$line" | grep -oE '`[^`]+`') + done < "$md" + done < <(find "$docroot" -type f -name '*.md') + fi + + # --- Q2: file:line citation ban (URL spans stripped, not whole lines) --- + while IFS= read -r hit; do + local file="${hit%%:*}"; local rest="${hit#*:}"; local line="${rest%%:*}" + local text="${rest#*:}" + local stripped + stripped=$(printf '%s' "$text" | sed -E 's#[A-Za-z][A-Za-z0-9+.-]*://[^ )>]*##g') + if printf '%s' "$stripped" | grep -qE '\.go(:[0-9]+)?|line [0-9]+'; then + fail "[Q2] $file:$line β€” cites a file path or line number (churn-prone coordinate)" + fi + done < <(grep -rnE '\.go(:[0-9]+)?|line [0-9]+' "$docroot" --include='*.md' 2>/dev/null) + + # --- Q3: root wiring (exact path; sub-roots may ride the repo-root index) --- + local rel="$docroot" + [[ "$proj" != "." ]] && rel="${docroot#$proj/}" + local claude="CLAUDE.md" agents="AGENTS.md" + [[ "$proj" != "." ]] && { claude="$proj/CLAUDE.md"; agents="$proj/AGENTS.md"; } + local wired_claude=0 wired_agents=0 + [[ -f "$claude" ]] && grep -q "$rel/index.md" "$claude" && wired_claude=1 + [[ -f "$agents" ]] && grep -q "$rel/index.md" "$agents" && wired_agents=1 + if (( ! wired_claude && ! wired_agents )); then + local via_root=0 + if [[ "$proj" != "." && -n "$ROOT_BUNDLE" ]]; then + grep -rq "$docroot/index.md" "$ROOT_BUNDLE" --include='*.md' 2>/dev/null && via_root=1 + fi + if (( ! via_root )); then + fail "[Q3] $proj β€” neither $claude nor $agents references $rel/index.md" + fi + elif (( ! wired_agents )); then + note "[Q3] $agents lacks the $rel/index.md routing reference β€” AGENTS.md-reading tools start blind" + fi + + # --- Q7: bundle contract --- + while IFS= read -r md; do + if [[ "$(head -1 "$md" 2>/dev/null)" != "---" ]]; then + fail "[Q7] $md β€” no frontmatter block (first line must be ---)" + continue + fi + local close; close=$(fm_close_line "$md") + if [[ -z "$close" ]]; then + fail "[Q7] $md β€” unterminated frontmatter (no closing ---)" + continue + fi + local fm; fm=$(fm_block "$md" "$close") + if printf '%s\n' "$fm" | grep -q '^related:'; then + fail "[Q7] $md β€” 'related:' frontmatter key (links live in the body)" + fi + local key + if [[ "$(basename "$md")" == "index.md" ]]; then + printf '%s\n' "$fm" | grep -q '^type: index' \ + || fail "[Q7] $md β€” index frontmatter missing 'type: index'" + for key in title description tags; do + printf '%s\n' "$fm" | grep -q "^${key}:" \ + || fail "[Q7] $md β€” index frontmatter missing '${key}:'" + done + if printf '%s\n' "$fm" | grep -q '^timestamp:'; then + fail "[Q7] $md β€” timestamp on an index (derived files get no authored churn)" + fi + local is_root=0 + [[ "$(canon "$md")" == "$root_index_c" ]] && is_root=1 + if (( is_root )); then + printf '%s\n' "$fm" | grep -q '^okf_version:' \ + || fail "[Q7] $md β€” root index missing 'okf_version:'" + elif printf '%s\n' "$fm" | grep -q '^okf_version:'; then + fail "[Q7] $md β€” okf_version on a sub-index (the key is the root's alone)" + fi + else + for key in type title description timestamp; do + printf '%s\n' "$fm" | grep -q "^${key}:" \ + || fail "[Q7] $md β€” frontmatter missing '${key}:'" + done + fi + done < <(find "$docroot" -type f -name '*.md') + + # --- Q7: derivation β€” index line text == target's description (⚠️ exempt) --- + while IFS= read -r idx; do + local lineno=0 line + while IFS= read -r line; do + lineno=$((lineno + 1)) + case "$line" in *'⚠️'*) continue ;; esac + printf '%s' "$line" | grep -qE '^- \[[^]]*\]\([^)]*\.md[^)]*\) β€” ' || continue + local t; t=$(printf '%s' "$line" | sed -E 's/^- \[[^]]*\]\(([^)]*)\).*/\1/') + local tail="${line#* β€” }" + tail=$(printf '%s' "$tail" | sed -e 's/[[:space:]]*$//') + local resolved; resolved=$(resolve_link "$idx" "$t" "$docroot") + [[ -n "$resolved" && -f "$resolved" ]] || continue + local desc; desc=$(desc_of "$resolved") + [[ -z "$desc" ]] && continue + if [[ "$tail" != "$desc" ]]; then + fail "[Q7] $idx:$lineno β€” index line drifted from $(basename "$resolved")'s description" + fi + done < "$idx" + done < <(find "$docroot" -type f -name 'index.md') + + # --- Q7: no log.md --- + while IFS= read -r lg; do + fail "[Q7] $lg β€” log.md is reserved for change history; docs describe current behavior" + done < <(find "$docroot" -type f -name 'log.md') +} + +for i in "${!PROJS[@]}"; do + check_bundle "${PROJS[$i]}" "${ROOTS[$i]}" +done + +# ---------- summary ---------- +if (( violations > 0 )); then + echo "check-repo-brain: $violations violation(s) β€” rules: /conventions.md" >&2 + exit 1 +fi +echo "check-repo-brain: clean (${ROOTS[*]})" +exit 0 diff --git a/go-linter-driven-development/skills/documentation/SKILL.md b/go-linter-driven-development/skills/documentation/SKILL.md index 2c7ef04..156ac0e 100644 --- a/go-linter-driven-development/skills/documentation/SKILL.md +++ b/go-linter-driven-development/skills/documentation/SKILL.md @@ -8,8 +8,9 @@ description: | it into the network. BOOTSTRAP mode: on request ("set up docs", "create an index", "make this repo AI-navigable", /wire-repo-brain) or when FEATURE mode finds no doc root β€” discovers - the doc root, builds index.md, wires CLAUDE.md, wires missing codeβ†’docs edges, - reports gaps. + the doc root, verifies-or-adds OKF frontmatter, builds index.md, wires + CLAUDE.md/AGENTS.md, wires missing codeβ†’docs edges, installs conventions.md and + the conformance check script, reports gaps. NOT a changelog - documents current behavior, not change history. allowed-tools: - Read @@ -25,8 +26,9 @@ allowed-tools: Author and maintain the repo brain: a documentation network where any entry point β€” a grep hit on a symbol, a file open, CLAUDE.md at session start β€” reaches full context within two hops. Everything normative (the documentation ladder, both network -invariants, the comment policy, the edge policy, the index policy, root wiring, -doc-root discovery) lives ONCE in `../../rules/R9-repo-brain.md`; this skill is the +invariants, the comment policy, the edge policy, the index policy, the OKF +frontmatter/bundle policy, root wiring, doc-root discovery) lives ONCE in +`../../rules/R9-repo-brain.md`; this skill is the actor that applies it. Templates live in `reference.md` β€” they are menus, never forms. @@ -83,17 +85,20 @@ unless an R9 Q6 check shows a doc citing the reshaped code. recommendation in the report β€” never extra lines. Add testable examples (`Example_*`) for complex/core types. 4. **Rung 2 β€” feature doc**: create/update `/.md` from the - reference.md template: `Related` edges to sibling docs; key players as - `Symbol | Role | Package`; entry points cite symbols β€” never file paths or line - numbers (R9 edge policy). Bug fix β†’ update the existing doc's affected section; - do not create a new doc. -5. **Rung 3 β€” the map**: add/refresh the doc's one line in `index.md`; verify root - wiring (`@/index.md` import in CLAUDE.md, AGENTS.md fallback). + reference.md template, with OKF frontmatter (required keys β€” R9's bundle + policy); optional `Related` edges (≀3, each with its reason β€” R9 edge policy); + key players as `Symbol | Role | Package`; entry points cite symbols β€” never + file paths or line numbers (R9 edge policy). Bug fix β†’ update the existing + doc's affected section; do not create a new doc. +5. **Rung 3 β€” the map**: add/refresh the doc's one line in `index.md` β€” derived + from the doc's `description` (R9 derivation rule); verify root wiring + (`@/index.md` import in CLAUDE.md, AGENTS.md routing block). 6. **Self-check**: run R9's falsifying-question detections on the touched scope β€” - Q1–Q3 mechanically (orphans, broken edges in both directions, unwired root), - Q4–Q6 over the diff (WHAT-comments, naked exported API, silently-changed doc). - The detection commands live in R9; never restate them. Fix every hit before - reporting. + Q1–Q3 and Q7 mechanically (orphans, broken edges in both directions, unwired + root, bundle contract β€” the repo's `scripts/check-repo-brain.sh` runs all four + in one pass when installed), Q4–Q6 over the diff (WHAT-comments, naked exported + API, silently-changed doc). The detection commands live in R9; never restate + them. Fix every hit before reporting. 7. **Comment critique**: spawn the `comment-critic` agent (Agent tool) on the full diff β€” not just the comments this run wrote; in-body comments left by earlier phases are in scope too. Its spawn prompt MUST contain: (a) R9's comment-policy @@ -112,27 +117,41 @@ unless an R9 Q6 check shows a doc citing the reshaped code. 1. **Discover doc root(s)** per R9's discovery order (`.ai/` β†’ `.ainav/` β†’ `docs/`; create `docs/` if none exists). Monorepo β†’ one doc root + index per sub-project. -2. **Inventory existing docs** and classify each: feature / architecture / guide / - stale (classification table in reference.md). -3. **Build or rebuild `index.md`**: a short reference guide β€” grouped by topic, one - line per doc; past ~300 lines it becomes a map of maps with short sub-indexes - (R9 index policy; templates in reference.md). -4. **Wire the root**: add the `@/index.md` import to CLAUDE.md (create a - minimal CLAUDE.md section if none exists); AGENTS.md has no import syntax β€” use - the plain-reference fallback. Snippets in reference.md. -5. **Wire missing upward edges**: for each indexed (non-stale) doc with no code-side +2. **Inventory existing docs**, classify each (feature / architecture / guide / + stale β€” classification table in reference.md), and **verify-or-add frontmatter** + (migration guidance in reference.md): a doc already conformant is left alone; an + un-inferable `type` goes to the advisory report, never guessed. +3. **Build or rebuild `index.md`**: frontmattered, grouped by topic, one line per + doc β€” each line derived from the frontmatter one level down (R9 derivation + rule); past ~300 lines it becomes a directory-shaped map of maps, and the split + lands in the same commit as the `See docs/...` path rewrite (R9 index policy; + templates in reference.md). +4. **Wire the root**: author the routing block once, in AGENTS.md β€” repo root + and, in a monorepo, nested per sub-project β€” then wire CLAUDE.md with the + `@AGENTS.md` embed plus the `@/index.md` import (create a minimal + CLAUDE.md section if none exists; never restate the routing prose there). + Add or verify; snippets in reference.md. +5. **Teach and enforce**: create-or-verify `/conventions.md` (template in + reference.md) β€” the ONE content file bootstrap generates (network + infrastructure, not a content doc) β€” listed FIRST in the index; copy the + plugin's `scripts/check-repo-brain.sh` into the target repo's `scripts/` + (verify-or-copy β€” a diverged copy is reported, never overwritten). The report + suggests CI wiring as plain `bash scripts/check-repo-brain.sh`; never add a + workflow file. +6. **Wire missing upward edges**: for each indexed (non-stale) doc with no code-side edge, add ONE line β€” `// See /.md ...` β€” to the front-door anchor's existing doc comment (anchor heuristic in reference.md), then confirm the package still vets. Wiring only: never rewrite the comment around it, never wire a stale doc (its ⚠️ index flag is the finding), and skip β€” as a reported gap β€” any doc whose anchor you cannot identify with confidence. -6. **Confirm and report**: re-run R9 Q1–Q3 as confirmation β€” a Q1 hit (a doc with no - index line) means step 3 didn't land and a Q3 hit means step 4 didn't; repair - either before reporting, and verify every edge added in step 5 resolves. The - ADVISORY findings list carries Q2 hits plus rung-2 gaps (two-signal criterion in - reference.md) and any doc left unwired in step 5. Bootstrap wires and maps; it - NEVER mass-generates content docs β€” those are written incrementally by FEATURE - mode. +7. **Confirm and report**: re-run R9 Q1–Q3 and Q7 as confirmation β€” via the + installed script β€” a Q1 hit (a doc with no index line) means step 3 didn't land, + a Q3 hit means step 4 didn't, a Q7 hit means step 2 or 5 didn't; repair any + before reporting, and verify every edge added in step 6 resolves. The ADVISORY + findings list carries Q2 hits plus rung-2 gaps (two-signal criterion in + reference.md), any doc left unwired in step 6, and any `type` needing a human + call. Bootstrap wires and maps; it NEVER mass-generates content docs β€” those are + written incrementally by FEATURE mode. @@ -152,7 +171,7 @@ Network edges added: - docsβ†’code: β†’ - root: @/index.md in CLAUDE.md (verified/added) -R9 self-check: Q1–Q3 clean Β· Q4–Q6 clean over diff +R9 self-check: Q1–Q3, Q7 clean Β· Q4–Q6 clean over diff (or per hit: : β€” fixed by ) Comment critic: reviewed β€” deleted Β· trimmed Β· rewritten Β· @@ -169,7 +188,10 @@ BOOTSTRAP mode: BOOTSTRAP COMPLETE Doc root(s): Index: /index.md built β€” docs, groups; map of maps: -Root wiring: CLAUDE.md @import (or AGENTS.md plain reference) +Frontmatter: verified, added +Root wiring: CLAUDE.md @import Β· AGENTS.md routing block +Conventions: /conventions.md +Check script: scripts/check-repo-brain.sh β€” suggest CI: bash scripts/check-repo-brain.sh Upward edges: wired β€” ← (), ... Advisory findings (reported, not fixed β€” FEATURE mode writes content): @@ -177,6 +199,8 @@ Advisory findings (reported, not fixed β€” FEATURE mode writes content): - broken edge: β†’ (unresolved) - gap: β€” - stale: β€” indexed with ⚠️ flag; cites unresolved ; not edge-wired +- type?: β€” class not inferable; needs a human call +- diverged script: scripts/check-repo-brain.sh differs from the plugin's β€” not overwritten ``` @@ -188,8 +212,11 @@ Advisory findings (reported, not fixed β€” FEATURE mode writes content): - FEATURE: the comment-critic ran over the full diff, every non-KEEP verdict was applied (R3 routes reported, not fixed), and the one re-critique confirmed clean β€” or the remainder is reported as-is. -- BOOTSTRAP: root(s) + index + root wiring exist; every confidently-anchorable doc - has an upward edge; gaps reported; zero content docs generated. +- BOOTSTRAP: root(s) + frontmattered index + root wiring + conventions.md + check + script exist; frontmatter verified-or-added on every doc; every + confidently-anchorable doc has an upward edge; gaps reported; zero content docs + generated (conventions.md and the copied script are the two sanctioned + artifacts). - All prose passes the 5-year reader test; zero changelog-style entries. @@ -198,7 +225,8 @@ This skill MUST NOT: - Restate R9 content β€” the documentation ladder, invariants, and policies are cited, never copied. - Append change history to docs β€” current behavior only, always. -- Mass-generate content docs in BOOTSTRAP mode β€” advisory gap report only. +- Mass-generate content docs in BOOTSTRAP mode β€” advisory gap report only + (conventions.md and the copied check script are the two sanctioned artifacts). - Fill templates for their own sake β€” reference.md's templates are menus; R9's comment policy decides what earns its place. - Spawn anything other than `comment-critic`, loop the critique more than one diff --git a/go-linter-driven-development/skills/documentation/reference.md b/go-linter-driven-development/skills/documentation/reference.md index 2d68947..44bcafa 100644 --- a/go-linter-driven-development/skills/documentation/reference.md +++ b/go-linter-driven-development/skills/documentation/reference.md @@ -9,10 +9,12 @@ policy, index policy, root wiring, doc-root discovery β€” lives ONCE in - [Comment Value Toolbox](#comment-value-toolbox) β€” the growable catalog of ways a comment delivers value - [Godoc Menus](#godoc-menus) β€” package, type, function menus; testable examples -- [Feature Doc Template](#feature-doc-template) β€” with `Related` edges and symbol-cited key players -- [The Index and Root Wiring](#the-index-and-root-wiring) β€” index.md, map of maps, CLAUDE.md/AGENTS.md snippets +- [Frontmatter Templates (OKF Bundle)](#frontmatter-templates-okf-bundle) β€” content doc, index, root index +- [Feature Doc Template](#feature-doc-template) β€” frontmatter, symbol-cited key players, optional `Related` +- [The Index and Root Wiring](#the-index-and-root-wiring) β€” index.md, map of maps, CLAUDE.md import, AGENTS.md routing block +- [Conventions Doc (Self-Hosting)](#conventions-doc-self-hosting) β€” the `conventions.md` template bootstrap installs - [Doc Roots and Monorepos](#doc-roots-and-monorepos) -- [Bootstrap Classification](#bootstrap-classification) β€” feature / architecture / guide / stale; rung-2 gap criterion; upward-edge anchor heuristic +- [Bootstrap Classification](#bootstrap-classification) β€” feature / architecture / guide / stale; frontmatter migration; rung-2 gap criterion; upward-edge anchor heuristic - [Checklists](#checklists) β€” feature docs, code comments, quality gates - [Guidelines](#guidelines) β€” bug-fix documentation, managing documentation size - [Examples](#examples) β€” good vs bad worked examples @@ -313,16 +315,71 @@ feature docs. --- +## Frontmatter Templates (OKF Bundle) + +Every `.md` in the doc root starts with YAML frontmatter (R9's bundle policy β€” the +one non-menu part of any template: the required keys are not optional). Index lines +are derived from the `description` one level down, so write the description as the +index line you want. + +**Content doc** (feature / architecture / guide): + +```yaml +--- +type: feature +title: Retry policy +description: why retries use capped full jitter; `Policy` API +timestamp: 2026-08-20T00:00:00Z +# optional: +# tags: [resilience, retry] +# status: stable # draft | stable | deprecated +# stale_after: 2027-01-01 # past this date the index line gets the ⚠️ flag +--- +``` + +**Sub-index** (`/index.md` β€” an R9 extension; OKF keeps indexes bare): + +```yaml +--- +type: index +title: Resilience +description: retries, circuit breaking, timeouts +tags: [resilience, retry, backoff] +--- +``` + +**Root index** (`/index.md`) β€” same keys plus the bundle version; never a +`timestamp` on any index (derived files don't get authored churn): + +```yaml +--- +type: index +title: Repo map +description: map of all repo docs +tags: [go] +okf_version: "0.2" +--- +``` + +--- + ## Feature Doc Template -The sections are a menu too: a small feature may need only Problem & Solution, Entry -Points, and Related. All code citations follow R9's edge policy: exported symbols +The sections are a menu too: a small feature may need only Problem & Solution and +Entry Points. The frontmatter block is the exception β€” its required keys always +ship (Frontmatter Templates above). All code citations follow R9's edge policy: exported symbols first β€” the shortest token that greps uniquely, package-qualified only on ambiguity; package or directory paths when a location is genuinely needed (directories for symbol-less artifacts like examples/, paired with the symbols they demonstrate); file paths and line numbers never. ```markdown +--- +type: feature +title: [Feature Name] +description: [the index line β€” one line, what and why; key symbols] +timestamp: [ISO 8601] +--- # [Feature Name] ## Problem & Solution @@ -377,7 +434,9 @@ Input β†’ Validation β†’ Processing β†’ Storage β†’ Output - [Known limitations, potential extensions] ## Related -Edges to sibling docs: +Optional, and capped at 3 β€” a ceiling, not a quota: zero entries means no section. +Only relationships that found no natural sentence in the body; every entry carries +its reason clause, and never duplicates a link already inline (R9 edge policy): - [auth.md](auth.md) β€” how sessions authenticate created users - [notifications.md](notifications.md) β€” welcome-email delivery ``` @@ -389,11 +448,22 @@ Edges to sibling docs: ### index.md Template A short reference guide: grouped by topic, ONE line per doc (size and style are -normative in R9's index policy): +normative in R9's index policy). Each line IS the linked doc's `description` β€” +derived, never authored twice; the ⚠️ flag rides in from the doc's lifecycle keys +or a stale classification (R9's derivation rule): ```markdown +--- +type: index +title: Repo Map +description: map of all repo docs +tags: [go] +okf_version: "0.2" +--- # Repo Map +- [conventions.md](conventions.md) β€” how to maintain this doc root (read before editing docs) + **Resilience** - [retry-policy.md](retry-policy.md) β€” why retries use capped full jitter; `Policy` API @@ -404,30 +474,103 @@ normative in R9's index policy): ### Map of Maps (past ~300 lines) -The root index shrinks to links to short topic or sub-project sub-indexes (R9): +The split is directory-shaped: each topic becomes a subdirectory with its own +frontmattered `index.md`, and the root index shrinks to one line per sub-index, +derived from that sub-index's `description` + `tags` (R9). The split moves files β€” +it lands in the same commit as the rewrite of the code-side `See docs/...` paths: ```markdown -# Repo Map - - [Resilience](resilience/index.md) β€” retries, circuit breaking, timeouts - [Users](users/index.md) β€” identity, sessions, notifications ``` -Each sub-index follows the one-line-per-doc form above. +Each sub-index follows the one-line-per-doc form above, with sub-index frontmatter +(Frontmatter Templates; no `okf_version` β€” that key is the root's alone). ### CLAUDE.md Wiring Snippet +CLAUDE.md never restates the routing prose β€” it embeds AGENTS.md (the single +authored routing block, below) and imports the map: + ```markdown ## Documentation +@AGENTS.md @docs/index.md ``` -The `@` import puts the map in context at session start. AGENTS.md has no import -syntax β€” fall back to a plain reference: +The `@` imports put the routing block and the map in context at session start. + +### AGENTS.md Routing Block + +The routing block is authored once, here β€” for every tool that reads AGENTS.md, +with CLAUDE.md embedding this file rather than duplicating it. At the repo root +and, in a monorepo, nested per sub-project (agents use the closest file, so each +sub-project's block names its own doc root): ```markdown ## Documentation -Start at docs/index.md β€” the map of all repo docs. +Docs live in docs/ β€” start at docs/index.md, the map of all repo docs. +Before adding or editing anything under docs/, read docs/conventions.md +(frontmatter, link rules, what never to do). +When you change exported API behavior, update the doc that cites it and its +index line. Check your work: bash scripts/check-repo-brain.sh +``` + +--- + +## Conventions Doc (Self-Hosting) + +`/conventions.md` is the network's own maintenance manual, written for a +contributor without this plugin β€” the ONE content file bootstrap generates (network +infrastructure, not a content doc). Listed FIRST in the index. Template: + +```markdown +--- +type: guide +title: Doc conventions +description: how to maintain this doc root (read before editing docs) +timestamp: [ISO 8601] +--- +# Doc Conventions + +This directory is the repo's documentation network β€” an OKF bundle. Markdown files +with YAML frontmatter; `index.md` is the map; links form the graph. Rules: + +## Frontmatter +Every `.md` here starts with frontmatter. Content docs (copy-paste, fill in): + + --- + type: feature # feature | architecture | guide + title: + description: + timestamp: + --- + +Index files use `type: index` and never a `timestamp`. Optional on content docs: +`tags`, `status: draft|stable|deprecated`, `stale_after: `. + +## Links +- The index line for a doc IS its `description` β€” update the doc's frontmatter, + then mirror the line in `index.md`. +- Cite code by exported symbol (`` or `.`), never by file + path or line number. Backticks are a promise: a backticked symbol must grep in + this repo (mark future ones *(planned)* and write them without backticks). +- Link related docs inline, in the sentence that explains the relationship. + Links are one-way: never add a link back to `index.md` or a parent. + Use inline links only β€” `[name](path.md)`; reference-style links are not + checked by the conformance gate. +- An optional `## Related` section may close a doc: at most 3 entries, each with + a reason ("β€” how sessions authenticate created users"), none duplicating an + inline link. + +## Never +- No `log.md`, no changelog sections β€” docs describe current behavior, not history. +- No `related:` key in frontmatter β€” links live in the body. +- No file paths or line numbers as code references. + +## Check your work +Run `bash scripts/check-repo-brain.sh` from the repo root β€” it verifies the rules +above mechanically and points at this file when something breaks. ``` --- @@ -448,14 +591,16 @@ Start at docs/index.md β€” the map of all repo docs. ## Bootstrap Classification -Classify each inventoried doc; the class decides its index line and grouping: +Classify each inventoried doc; the class decides its index line, grouping, and +frontmatter `type` (stale is a lifecycle, not a type β€” it keeps the class it would +otherwise have, expressed via `status`/`stale_after` plus the flagged line): -| Class | Signals | Index treatment | -|-------|---------|-----------------| -| **feature** | describes one capability's behavior; cites its symbols | group under its topic | -| **architecture** | cross-feature structure, system-wide patterns | its own "Architecture" group | -| **guide** | setup, how-to, onboarding, runbooks | "Guides" group | -| **stale** | cites symbols/packages that no longer resolve; describes removed behavior | index with a FLAGGED line (below); the flag is the advisory finding | +| Class | Signals | Index treatment | Frontmatter `type` | +|-------|---------|-----------------|--------------------| +| **feature** | describes one capability's behavior; cites its symbols | group under its topic | `feature` | +| **architecture** | cross-feature structure, system-wide patterns | its own "Architecture" group | `architecture` | +| **guide** | setup, how-to, onboarding, runbooks | "Guides" group | `guide` | +| **stale** | cites symbols/packages that no longer resolve; describes removed behavior | index with a FLAGGED line (below); the flag is the advisory finding | its underlying class | **Stale never means unindexed** β€” R9's Q1 reachability invariant always wins. A stale doc gets a flagged index line naming the unresolved symbol: @@ -472,6 +617,18 @@ Bootstrap never decides. When unsure between feature and architecture: one capability β†’ feature; the seams between capabilities β†’ architecture. +### Frontmatter Migration (Brownfield) + +An existing network without frontmatter β€” wired by hand, or by a plugin version +before the OKF layer β€” is just another brownfield state. **Verify-or-add, never +duplicate**: a doc that already has conformant frontmatter is left alone; a doc +without gets the required keys, with `description` written as its index line and +`timestamp` from the doc's last substantive git touch when evident. A `type` the +classification table cannot settle goes to the advisory report +(`type?: β€” class not inferable`) β€” never guessed silently. Same for +`conventions.md` and the check script: create or verify, and report a diverged +script rather than overwriting it. + ### Rung-2 Gap Criterion (BOOTSTRAP) FEATURE mode anchors R9 Q5 on the diff; bootstrap has no diff. Report a rung-2 gap @@ -508,14 +665,19 @@ vets after the edit. ### Feature Documentation Checklist +- [ ] Frontmatter present with the four required keys (`type`, `title`, + `description`, `timestamp`); `description` reads as the index line - [ ] Clear problem statement and high-level solution approach - [ ] Entry points listed, cited by symbol (e.g. `POST /users` β†’ `UserHandler.Create`) - [ ] Key players table with Symbol, Role, and Package β€” no file paths, no line numbers - [ ] Design decisions explained with rationale, connected to coding principles - [ ] Data flow and integration points documented - [ ] Usage examples are runnable and copy-pasteable -- [ ] `Related` section carries edges to sibling docs -- [ ] Doc has its one line in `index.md`, and at least one code-side edge names it +- [ ] `Related` section, if present, has ≀3 entries, each with a reason clause, + none duplicating an inline link +- [ ] Doc has its one line in `index.md` β€” derived from its `description` β€” and at + least one code-side edge names it +- [ ] No `log.md`, no changelog sections, no `related:` frontmatter key ### Code Comments Checklist diff --git a/go-linter-driven-development/skills/pre-commit-review/SKILL.md b/go-linter-driven-development/skills/pre-commit-review/SKILL.md index 229f5e8..a790b3c 100644 --- a/go-linter-driven-development/skills/pre-commit-review/SKILL.md +++ b/go-linter-driven-development/skills/pre-commit-review/SKILL.md @@ -53,7 +53,7 @@ A rule with zero hits is skipped β€” no hunter spawned for it. | R6 | `../../rules/R6-test-only-interfaces.md` | interfaces whose only second implementer is a test double | | R7 | `../../rules/R7-test-placement.md` | internal test packages; wantErr conditionals; wrong-rung tests; sleeps | | R8 | `../../rules/R8-no-globals.md` | package-level state; `context.Background()` in library code | -| R9 | `../../rules/R9-repo-brain.md` | orphan docs; broken doc edges (both directions); WHAT-comments on exported API; unwired root | +| R9 | `../../rules/R9-repo-brain.md` | orphan docs; broken doc edges (both directions); WHAT-comments on exported API; unwired root; bundle-contract breaks (missing frontmatter, index timestamps, log.md) | | R10 | `../../rules/R10-concurrency-safety.md` | goroutines without exit paths or owners; unguarded shared-state writes; production sleeps; decorative mutexes | | R11 | `../../rules/R11-conditional-dispatch.md` | one discriminator switched in β‰₯2 places; type switches in domain logic; unknown-kind defaults away from the boundary; flag arguments; unearned dispatch abstractions (inverse) | | R12 | `../../rules/R12-mutation-discipline.md` | internal slices/maps returned by reference; constructors aliasing caller collections; query/modifier hybrids; setters around validating constructors; ceremony copies (inverse) |