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..f7a3e98 100644 --- a/go-linter-driven-development/CHANGELOG.md +++ b/go-linter-driven-development/CHANGELOG.md @@ -3,6 +3,73 @@ 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)**: content docs carry YAML frontmatter β€” + required `type` (the spec's one required key) and `description`; optional + `title`/`generated` (OKF's provenance key)/`tags` and lifecycle + `status`/`stale_after`, the frontmatter-native form of the ⚠️ stale flag. + Indexes stay bare, as the spec reserves them; the root index carries only + `okf_version`. R9 is a stricter profile of the spec built from spec-valid + keys, so the bundle stays consumable by any OKF tool. New falsifying + question **Q7** checks the bundle contract mechanically. +- **Drift-check rule (R9)**: a doc's index line IS its `description`, copied + verbatim β€” the description is the single source, and the conformance gate + fails when the copy drifts; `check-repo-brain.sh --fix` rewrites drifted + lines from the descriptions (the one mechanical repair the gate performs). + The map of maps is directory-shaped (per-topic subdirectories with their + own bare 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, no `## Related` + section; lateral links go inline with the reason in the sentence. `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, fenced code blocks, and glob + patterns exempt), exact-path root wiring (missing AGENTS.md routing is an + advisory), the full frontmatter contract (termination, required content-doc + keys, bare indexes, root-only `okf_version`), and every index line checked + against its target's `description`. Docsβ†’code resolution is set-based and + Go-shaped: one pass builds the repo's declaration set (single-line and + grouped `type (`/`var (`/`const (` declarations, functions, methods); a + token missing there still resolves as a whole word in any non-markdown repo + file (config keys, alert names); external `pkg.Sym` (stdlib, dependencies) + is exempt. Dogfooded on a 1,271-file production repo: ~55 s per run, zero + false-positive classes left. 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 + on content docs (never duplicated) and stripped from indexes, 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 removed**: lateral doc links go + inline, in the sentence that states the relationship β€” a relationship that + cannot find a sentence in the body is not worth an edge. + ## [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..19f34db 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, drift-checked 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..aa09800 100644 --- a/go-linter-driven-development/commands/wire-repo-brain.md +++ b/go-linter-driven-development/commands/wire-repo-brain.md @@ -19,20 +19,43 @@ 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 on content docs, stripped from indexes + (un-inferable types reported) +3. `index.md` built β€” short, grouped, one line per doc copied from each doc's + `description`; the root index carries only `okf_version` + (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) + +**Language scope**: this is the Go plugin, so code↔docs verification is +Go-first. On a repo with no Go, the pass still delivers the whole structure +layer (frontmatter, index, drift check, conventions, routing, CI gate on +structure) β€” but codeβ†’docs edges, symbol drift detection, and the file-path +ban only cover `.go` files, and doc roots are only discovered at the repo root +and `go.mod` sub-projects (a TS/Python sub-project's own docs/ is not wired β€” +it is reported, not silently skipped). Non-Go CamelCase symbols cited in +covered docs still resolve via the gate's whole-word fallback. 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..29d1601 100644 --- a/go-linter-driven-development/rules/R9-repo-brain.md +++ b/go-linter-driven-development/rules/R9-repo-brain.md @@ -8,7 +8,10 @@ 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: content docs carry YAML frontmatter, a file's path is its identity, +and every index line is drift-checked against the `description` one level down +(bundle policy below). ## Why @@ -18,7 +21,7 @@ beside its symbol and gets reviewed with every diff that touches it. Rung 2 drif on its own unless networked: nothing in a normal diff forces `docs/` open, so a feature doc rots silently β€” *unless* an edge from the changed code names it and an index line makes it findable. Rung 3 barely drifts because it is short and -regenerable. Placing a fact above its lowest viable rung therefore buys drift for +drift-checked (Q7). Placing a fact above its lowest viable rung therefore buys drift for nothing; placing it below (cramming architecture into a comment) buries it where no overview reader looks. @@ -123,12 +126,19 @@ func (p Policy) Do(ctx context.Context, op Op) error { ```markdown +--- +type: feature +description: why retries use capped full jitter; `Policy` API +--- Entry point: `Policy.Do`. Construction: `ParsePolicy` β€” validates the cap against the base delay, so an unbounded backoff cannot exist. ``` ```markdown +--- +okf_version: "0.2" +--- # Repo map **Resilience** @@ -146,7 +156,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 has one +source of truth: it IS `retry-policy.md`'s `description`, copied verbatim β€” the +conformance gate (Q7) fails the moment the copy drifts. ## Design guidance @@ -159,7 +171,7 @@ Forward guidance β€” what @documentation applies when writing docs after a featu | 0 | Storified code | none β€” it IS the behavior | the story; names carry context (owned by `R3-storifying.md`, cited not restated) | | 1 | Code comments (godoc) | low β€” lives beside the code, reviewed with diffs | the WHY within a tiered 1–5 prose-line budget (policy below); network edges: `See docs/.md` | | 2 | Repo docs | medium β€” drifts unless networked | feature/architecture docs in the doc root; point back down via greppable symbol references (edge policy below) | -| 3 | The map | minimal β€” short and regenerable | `index.md` in the doc root: one line per doc, grouped by topic; wired into CLAUDE.md / AGENTS.md | +| 3 | The map | minimal β€” short and drift-checked (Q7) | `index.md` in the doc root: one line per doc, grouped by topic; wired into CLAUDE.md / AGENTS.md | (The rung metaphor deliberately mirrors the testing composition ladder in @testing: lowest rung that can carry it, always.) @@ -327,17 +339,70 @@ 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). There is no `## Related` section: + a relationship that cannot find a sentence in the body is not worth an edge. + +### 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). R9 applies a **stricter profile** +on top; every key it requires is a valid OKF key, so the bundle stays consumable by +any OKF tool. + +- **Content docs** carry required `type` (`feature` / `architecture` / `guide` β€” + the spec's one required key) and `description` (one line β€” it IS the doc's + index line). Optional: `title` (the H1 is the title; the key never replaces + it), `generated` (OKF's provenance key: ISO 8601, last substantive update), + `tags`, and lifecycle keys `status: draft|stable|deprecated` and `stale_after` + β€” the frontmatter-native form of the ⚠️ stale flag. +- **Indexes carry no frontmatter** β€” OKF reserves `index.md` and keeps it bare, + with one spec-sanctioned exception: the root index carries `okf_version: "0.2"` + and nothing else. R9 requires that key (profile rule); any other key on any + index is a violation. +- **Drift-check rule**: a content doc's index line IS its `description`, copied + verbatim, prefixed ⚠️ when its lifecycle says so (`status: deprecated`, or + `stale_after` in the past) or when a bootstrap pass classified it stale. The + description is the single source; the conformance gate (Q7) fails when an index + line drifts from it. Sub-index lines in the root map are authored (a bare + sub-index has no `description` to copy) β€” keep them short. +- **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** β€” internally, OKF's dangling-link 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, 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 bare `index.md` (OKF's + per-directory reserved file), and the root index shrinks to one short authored + 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 + never-list β€” written for a contributor without this plugin. It is listed FIRST + in the index, one pointer line. ### Doc root discovery and monorepos @@ -345,6 +410,10 @@ rule's (Q4 below β€” they must carry why/context, not restate the identifier). `docs/` if none does. - Monorepo: each sub-project (its own `go.mod` or equivalent sub-project boundary) gets its own doc root and index; the repo-root index links the sub-indexes. + Mechanical discovery is keyed on `go.mod` β€” a sub-project in another language + keeps its docs reachable through the root index, but is outside the gate's + bundle checks and Q2's Go-scoped code↔docs verification (this rule lives in + the Go plugin; a sibling plugin owns other languages). - Nesting inside a doc root is allowed; the index (or a sub-index) covers every file in it. @@ -358,8 +427,15 @@ 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 content doc + that lacks them; copy the `description` into the doc's index line. Strip any + frontmatter an index carries beyond the root's `okf_version`. 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 +444,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 @@ -380,24 +458,36 @@ directory. 2. **Is any edge broken β€” in either direction?** Detection, codeβ†’docs: `grep -rnoE '(docs|\.ai|\.ainav)/[A-Za-z0-9._/-]+\.md' --include='*.go' .` plus `.md`-to-`.md` links inside ``; `test -f` each target. - Detection, docsβ†’code: for each backticked symbol a doc cites, - `grep -rn "type \|func " --include='*.go' .` (for methods, grep the - method name); for a cited package or directory path, `test -d` it. + Detection, docsβ†’code: build the repo's declaration set once β€” single-line + and grouped `type (` / `var (` / `const (` declarations, functions, and + methods β€” and resolve each backticked symbol against it. A token missing + from the set still resolves when it appears as a whole word in any + non-markdown repo file (config keys, alert names, test helpers). A + package-qualified `pkg.Sym` whose package is not declared in this repo is + external (stdlib, dependencies) and exempt. For a cited package or + directory path, `test -d` it. Violation: any unresolved target in either direction. Additionally, a doc citing a **file path or line number** is itself a violation of the edge policy β€” - 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 β€” - a future/roadmap symbol is written in prose or explicitly marked *(planned)*, - and *(planned)*-marked citations are exempt from resolution. + regardless of whether the coordinate currently resolves. Exempt from the + ban: URL spans (e.g. pkg.go.dev links), fenced code blocks, and glob + patterns (a span containing `*` is a pattern, not a citation). + Two exemptions, both scoped to symbol resolution and both per-LINE β€” a line + carrying either marker is skipped whole (the file-path ban has no exemption + beyond URLs): a 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 a *(planned)*-marked line is 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 +514,23 @@ 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: every content `.md` under `` starts with a terminated + frontmatter block (first line `---`, a closing `---` follows) carrying the + required keys `type` and `description`. Index files carry NO frontmatter β€” + except the root index, whose block is exactly `okf_version` (required there, + forbidden everywhere else). `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` + when it has one (⚠️-flagged lines exempt β€” recorded findings, not copies; + bare sub-index targets have no `description` and are skipped); the script's + `--fix` flag rewrites drifted lines from the descriptions. + Violation: a missing or unterminated frontmatter block on a content doc; a + missing required key; frontmatter on a sub-index; any key besides + `okf_version` on the root index; a missing root `okf_version`; a `related:` + key anywhere; a `log.md` anywhere in the doc root; an index line that + drifted from the `description` it copies. + 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-copying the line (drift-check 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..ab9392d --- /dev/null +++ b/go-linter-driven-development/scripts/check-repo-brain.sh @@ -0,0 +1,515 @@ +#!/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 so CI β€” and +# developers without the plugin β€” can hold the documentation network's +# invariants. What it enforces is the R9 profile: a strict superset of OKF +# v0.2 (rules/R9-repo-brain.md is normative; /conventions.md is the +# in-repo copy). Installed into target repos by the documentation skill's +# BOOTSTRAP pass (/wire-repo-brain). +# +# Usage: bash scripts/check-repo-brain.sh [--fix] [repo-root] (default: cwd) +# CI: one line β€” bash scripts/check-repo-brain.sh +# --fix: rewrite drifted index lines from each target doc's `description` +# (the one mechanical repair; everything else stays report-only) +# +# 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/. +# +# Language scope: structure checks (Q1, Q3, Q7, doc links) are language- +# agnostic; code<->docs verification (Q2 symbols, code-edge grep, file-path +# ban) covers Go files only. With no .go files, symbol checks are skipped and +# the rest still runs. +# +# 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β€” content docs carry terminated frontmatter with +# type/description; indexes carry NO frontmatter except +# the root index's lone okf_version (required there); +# no `related:` key; no log.md; every index line's text +# matches the target's `description` when it has one +# (⚠️ lines exempt; --fix rewrites drifted lines) +# +# Heuristics (documented, deliberate): +# - links are inline-markdown only (`[name](path.md)`, optional "title" +# stripped); reference-style links are not checked. +# - docsβ†’code checks backticked tokens shaped like exported Go identifiers +# (`Foo`, `Foo.Bar`) or package-qualified ones (`pkg.Foo`) that contain a +# lowercase letter; other backticks (paths, flags, ALL-CAPS initialisms, +# ) are skipped. +# - resolution is against a declaration set built ONCE per run from all .go +# files: single-line and grouped `type (`/`var (`/`const (` declarations, +# functions, and methods. A token missing from the set still resolves when +# it appears as a whole word in any non-markdown repo file (config keys, +# alert names, test helpers). A `pkg.Sym` whose package is not declared in +# this repo is external (stdlib, dependencies) and exempt. +# - lines carrying the ⚠️ stale flag or a *(planned)* marker are exempt from +# symbol resolution and the description copy check (R9 Q2/Q7 exemptions); +# the file:line ban has no exemption beyond URL spans, fenced code blocks, +# and glob patterns (a span containing `*` is a pattern, not a citation). +# - fenced code blocks (``` or ~~~, indented up to 3 spaces; toggle, not +# length-matched) are skipped for symbol resolution and the file:line ban. +# +# 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 + +FIX=0 +if [[ "${1:-}" == "--fix" ]]; then + FIX=1 + shift +fi +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"; } + +fixed=0 +# fix_index_line β€” rewrite the text after " β€” " on one line +fix_index_line() { + local f="$1" n="$2" tmp="$1.repobrain.tmp" + NEWDESC="$3" awk -v n="$n" ' + NR == n { i = index($0, " β€” "); if (i > 0) $0 = substr($0, 1, i - 1) " β€” " ENVIRON["NEWDESC"] } + { print } + ' "$f" > "$tmp" && mv "$tmp" "$f" +} + +# 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 + +# ---------- declaration set: built once, queried per token ---------- +# Collects package names (pkg:) plus every declared identifier β€” +# single-line and grouped type/var/const declarations, functions, methods. +DECL_AWK=' +inblock != "" { + if ($0 ~ /^\)/) { inblock = ""; next } + s = $0; sub(/^[ \t]+/, "", s) + if (s ~ /^[A-Za-z_]/) { + t = s; sub(/[ \t=([].*$/, "", t) + n = split(t, parts, ",") + for (i = 1; i <= n; i++) { + p = parts[i]; gsub(/[ \t]/, "", p) + if (p ~ /^[A-Za-z_][A-Za-z0-9_]*$/) print p + } + } + next +} +/^package [A-Za-z_]/ { s = $0; sub(/^package /, "", s); sub(/[^A-Za-z0-9_].*$/, "", s); print "pkg:" s; next } +/^(type|var|const) \(/ { inblock = "y"; next } +/^func \(/ { + s = $0; sub(/^func \([^)]*\)[ \t]*/, "", s); sub(/[ \t([].*$/, "", s) + if (s ~ /^[A-Za-z_][A-Za-z0-9_]*$/) print s + next +} +/^func [A-Za-z_]/ { + s = $0; sub(/^func /, "", s); sub(/[ \t([].*$/, "", s) + if (s ~ /^[A-Za-z_][A-Za-z0-9_]*$/) print s + next +} +/^(type|var|const) [A-Za-z_]/ { + s = $0; sub(/^(type|var|const) /, "", s) + t = s; sub(/[ \t=([].*$/, "", t) + n = split(t, parts, ",") + for (i = 1; i <= n; i++) { + p = parts[i]; gsub(/[ \t]/, "", p) + if (p ~ /^[A-Za-z_][A-Za-z0-9_]*$/) print p + } + next +} +' +DECLS="" PKGS="" +if (( have_go )); then + DECLS=$(mktemp) PKGS=$(mktemp) DECL_ALL=$(mktemp) + trap 'rm -f "$DECLS" "$PKGS"' EXIT + find . -name '*.go' -not -path '*/vendor/*' -not -path './.git/*' -print0 2>/dev/null \ + | xargs -0 awk "$DECL_AWK" 2>/dev/null | sort -u > "$DECL_ALL" + grep '^pkg:' "$DECL_ALL" | sed 's/^pkg://' > "$PKGS" + grep -v '^pkg:' "$DECL_ALL" > "$DECLS" + rm -f "$DECL_ALL" +fi + +is_repo_pkg() { grep -qxF "$1" "$PKGS" 2>/dev/null; } + +# One fence-aware awk pass per bundle extracts everything Q2's doc scan needs: +# P β€” a file:line citation outside fences/URLs/globs +# S β€” a backticked symbol-shaped token to resolve +# Tokens are then resolved as SETS (one grep against the declaration file, one +# repo-wide word grep for the whole unresolved batch) β€” never per token. +DOCSCAN_AWK=' +FNR == 1 { fence = 0 } +{ + line = $0 + if (line ~ /^ {0,3}(```|~~~)/) { fence = 1 - fence; next } + if (fence) next + gsub(/[A-Za-z][A-Za-z0-9+.\-]*:\/\/[^ )>]*/, "", line) + pf = 0 + if (line ~ /\.go/) { + n = split(line, sp, /[^A-Za-z0-9_*\/.~-]+/) + for (i = 1; i <= n; i++) { + s = sp[i] + sub(/\.+$/, "", s) + if (s ~ /\*/) continue + if (s ~ /\.go$/ || s ~ /\.go:[0-9]+$/) { pf = 1; break } + } + } + if (!pf && line ~ /(^|[^A-Za-z0-9_])line [0-9]+/) pf = 1 + if (pf) print "P\t" FILENAME "\t" FNR + if (index(line, "`") == 0) next + if (index(line, "⚠") > 0) next + if (index(line, "*(planned)*") > 0) next + m = split(line, seg, /`/) + for (i = 2; i <= m; i += 2) { + t = seg[i] + if (t !~ /[a-z]/) continue + if (t ~ /^[A-Z][A-Za-z0-9]*$/ || t ~ /^[A-Za-z][A-Za-z0-9_]*\.[A-Z][A-Za-z0-9]*$/) + print "S\t" FILENAME "\t" FNR "\t" t + } +} +' + +# ---------- 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 + [[ "$(basename "$doc")" == "log.md" ]] && continue # its own Q7 ban reports it + 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: doc scan β€” file:line ban + docsβ†’code symbol resolution. + # One awk pass extracts; resolution is set-based (see DOCSCAN_AWK above). --- + local scan; scan=$(mktemp) + find "$docroot" -type f -name '*.md' -print0 2>/dev/null \ + | xargs -0 awk "$DOCSCAN_AWK" 2>/dev/null > "$scan" + local f ln + while IFS=$'\t' read -r _ f ln; do + fail "[Q2] $f:$ln β€” cites a file path or line number (churn-prone coordinate)" + done < <(grep $'^P\t' "$scan") + if (( have_go )) && grep -q $'^S\t' "$scan"; then + local toks check members unres bad + toks=$(mktemp) check=$(mktemp) members=$(mktemp) unres=$(mktemp) bad=$(mktemp) + grep $'^S\t' "$scan" | cut -f4 | sort -u > "$toks" + # full-token -> member-to-resolve (external pkg.Sym exempt) + local t p m + while IFS= read -r t; do + case "$t" in + *.*) + p="${t%%.*}" m="${t#*.}" + case "$p" in + [a-z]*) is_repo_pkg "$p" || continue ;; # external package (stdlib, deps) β€” exempt + esac + printf '%s\t%s\n' "$t" "$m" ;; + *) printf '%s\t%s\n' "$t" "$t" ;; + esac + done < "$toks" > "$check" + cut -f2 "$check" | sort -u > "$members" + grep -vxF -f "$DECLS" "$members" > "$unres" || true + if [[ -s "$unres" ]]; then + # ONE repo-wide word grep for the whole unresolved batch + local found; found=$(mktemp) + grep -rIhoFw --exclude-dir=vendor --exclude-dir=.git --exclude='*.md' \ + -f "$unres" . 2>/dev/null | sort -u > "$found" + grep -vxF -f "$found" "$unres" > "$bad" || true + rm -f "$found" + fi + if [[ -s "$bad" ]]; then + local full mem tok + while IFS=$'\t' read -r full mem; do + grep -qxF "$mem" "$bad" || continue + while IFS=$'\t' read -r _ f ln tok; do + [[ "$tok" == "$full" ]] \ + && fail "[Q2] $f:$ln β€” backticked \`$full\` does not resolve (${mem} not declared or found in the repo)" + done < <(grep $'^S\t' "$scan") + done < "$check" + fi + rm -f "$toks" "$check" "$members" "$unres" "$bad" + fi + rm -f "$scan" + + # --- 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 + local close fm key + [[ "$(basename "$md")" == "log.md" ]] && continue # its own Q7 ban reports it + if [[ "$(basename "$md")" == "index.md" ]]; then + local is_root=0 + [[ "$(canon "$md")" == "$root_index_c" ]] && is_root=1 + if [[ "$(head -1 "$md" 2>/dev/null)" != "---" ]]; then + # bare index β€” conformant, except the root must carry okf_version + (( is_root )) && fail "[Q7] $md β€” root index missing its okf_version frontmatter" + continue + fi + close=$(fm_close_line "$md") + if [[ -z "$close" ]]; then + fail "[Q7] $md β€” unterminated frontmatter (no closing ---)" + continue + fi + if (( ! is_root )); then + fail "[Q7] $md β€” frontmatter on a sub-index (indexes stay bare; okf_version belongs to the root alone)" + continue + fi + fm=$(fm_block "$md" "$close") + printf '%s\n' "$fm" | grep -q '^okf_version:' \ + || fail "[Q7] $md β€” root index missing 'okf_version:'" + local extra + extra=$(printf '%s\n' "$fm" | grep -E '^[A-Za-z_-]+:' | grep -v '^okf_version:' | head -1) + [[ -n "$extra" ]] \ + && fail "[Q7] $md β€” root index frontmatter carries '${extra%%:*}:' (okf_version is the only allowed key)" + continue + fi + if [[ "$(head -1 "$md" 2>/dev/null)" != "---" ]]; then + fail "[Q7] $md β€” no frontmatter block (first line must be ---)" + continue + fi + close=$(fm_close_line "$md") + if [[ -z "$close" ]]; then + fail "[Q7] $md β€” unterminated frontmatter (no closing ---)" + continue + fi + 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 + for key in type description; do + printf '%s\n' "$fm" | grep -q "^${key}:" \ + || fail "[Q7] $md β€” frontmatter missing '${key}:'" + done + done < <(find "$docroot" -type f -name '*.md') + + # --- Q7: drift check β€” index line text == target's description (⚠️ exempt; + # --fix rewrites drifted lines after the read loop, never during it) --- + while IFS= read -r idx; do + local lineno=0 line fixes="" + 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 + if (( FIX )); then + fixes="${fixes}${lineno}"$'\x1f'"${desc}"$'\n' + else + fail "[Q7] $idx:$lineno β€” index line drifted from $(basename "$resolved")'s description" + fi + fi + done < "$idx" + if [[ -n "$fixes" ]]; then + local n d + while IFS=$'\x1f' read -r n d; do + [[ -z "$n" ]] && continue + fix_index_line "$idx" "$n" "$d" + fixed=$((fixed + 1)) + echo " fixed: $idx:$n β€” index line rewritten from its target's description" + done <<< "$fixes" + fi + 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 ---------- +(( fixed > 0 )) && echo "check-repo-brain: rewrote $fixed drifted index line(s)" +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..392d72e 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,21 @@ 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); lateral doc links inline, each in a sentence stating the + relationship (R9 edge policy β€” no `Related` section); + 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` β€” copied + from the doc's `description` (R9 drift-check 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 +118,43 @@ 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`**: bare except the root's `okf_version`, grouped + by topic, one line per doc β€” each line copied from the doc's `description` + (R9 drift-check 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 + still vets. Go files only β€” the gate verifies edges in `.go` files alone, so an + edge in another language is unverifiable; report such docs as unwired instead of + improvising. 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 +174,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 +191,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 +202,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 +215,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) + index + root wiring + conventions.md + check + script exist; frontmatter verified-or-added on every content 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 +228,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..52236c5 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, root index +- [Feature Doc Template](#feature-doc-template) β€” frontmatter, symbol-cited key players +- [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,54 @@ feature docs. --- +## Frontmatter Templates (OKF Bundle) + +Content docs start with YAML frontmatter (R9's bundle policy β€” the one non-menu +part of any template: the required keys are not optional). A doc's index line IS +its `description`, so write the description as the index line you want. + +**Content doc** (feature / architecture / guide): + +```yaml +--- +type: feature +description: why retries use capped full jitter; `Policy` API +# optional: +# title: Retry policy # the H1 is the title; this key never replaces it +# generated: 2026-08-20T00:00:00Z # OKF provenance: last substantive update +# tags: [resilience, retry] +# status: stable # draft | stable | deprecated +# stale_after: 2027-01-01 # past this date the index line gets the ⚠️ flag +--- +``` + +**Indexes carry no frontmatter** (OKF keeps reserved `index.md` bare). The one +exception is the root index (`/index.md`), which carries the bundle +version β€” and nothing else: + +```yaml +--- +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 +description: [the index line β€” one line, what and why; key symbols] +--- # [Feature Name] ## Problem & Solution @@ -375,13 +415,12 @@ Input β†’ Validation β†’ Processing β†’ Storage β†’ Output ## Future Considerations - [Known limitations, potential extensions] - -## Related -Edges to sibling docs: -- [auth.md](auth.md) β€” how sessions authenticate created users -- [notifications.md](notifications.md) β€” welcome-email delivery ``` +Lateral docβ†’doc links go inline, in the sentence that explains the relationship +(R9 edge policy). There is no `## Related` section β€” a relationship that cannot +find a sentence in the body is not worth an edge. + --- ## The Index and Root Wiring @@ -389,11 +428,19 @@ 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` β€” +copied verbatim, and the conformance gate fails when the copy drifts; the ⚠️ flag +rides in from the doc's lifecycle keys or a stale classification (R9's +drift-check rule): ```markdown +--- +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 +451,104 @@ 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 +bare `index.md`, and the root index shrinks to one short authored line per +sub-index (a bare sub-index has no `description` to copy β€” 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 no frontmatter +(`okf_version` 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 +description: how to maintain this doc root (read before editing docs) +--- +# 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 content doc here starts with frontmatter (copy-paste, fill in): + + --- + type: feature # feature | architecture | guide + description: + --- + +Optional on content docs: `title`, `generated` (ISO 8601, last substantive +update), `tags`, `status: draft|stable|deprecated`, `stale_after: `. +Index files carry NO frontmatter β€” except the root `index.md`, which carries +only `okf_version`. + +## Links +- The index line for a doc IS its `description` β€” the description is the single + source: update it in the doc's frontmatter, copy it to `index.md`, and the + conformance gate fails when the two drift. +- 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. There is no `## Related` section. + +## Never +- No `log.md`, no changelog sections β€” docs describe current behavior, not history. +- No `related:` key in frontmatter β€” links live in the body. +- No frontmatter on index files (the root's `okf_version` is the one exception). +- 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. +`--fix` rewrites drifted index lines from each doc's `description`. +Code↔docs checks cover Go files; docs about other languages get the structure +checks (reachability, frontmatter, index drift) but no symbol verification. ``` --- @@ -448,14 +569,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 +595,21 @@ 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 +(add optional `generated` from the doc's last substantive git touch when +evident). An index +carrying frontmatter (written by hand, or by an older plugin version) gets it +stripped β€” the root keeps only `okf_version`. 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 +646,19 @@ vets after the edit. ### Feature Documentation Checklist +- [ ] Frontmatter present with the required keys (`type`, `description` β€” R9's + bundle policy); `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 +- [ ] Lateral doc links are inline, each in a sentence stating the relationship β€” + no `## Related` section +- [ ] Doc has its one line in `index.md` β€” copied 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) |