From e6da0880a363ec062427288d2028d48c35dbeffd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 13:22:03 +0000 Subject: [PATCH 01/11] =?UTF-8?q?R9:=20OKF=20bundle=20conformance=20?= =?UTF-8?q?=E2=80=94=20frontmatter=20policy,=20one-way=20links,=20Q7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The doc root becomes an Open Knowledge Format (OKF v0.2) bundle: required frontmatter on content docs, type: index on indexes (a documented R9 extension; okf_version on the root), index lines derived from the frontmatter one level down, directory-shaped map of maps with the same-commit split rule, one-way link policy with the capped Related section, conventions.md as the self-hosting doc, AGENTS.md upgraded to a first-class routing block, and new falsifying question Q7 for the bundle contract. Comment policy untouched. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CH1Y4ssZbAKJYwqBX1qxCF --- .../rules/R9-repo-brain.md | 115 ++++++++++++++++-- 1 file changed, 103 insertions(+), 12 deletions(-) diff --git a/go-linter-driven-development/rules/R9-repo-brain.md b/go-linter-driven-development/rules/R9-repo-brain.md index 2ec7c49..4d7f4db 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,24 @@ 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 +okf_version: "0.2" +--- # Repo map **Resilience** @@ -146,7 +160,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 +343,71 @@ 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. +- 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**: 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. + (e.g. `@docs/index.md`) so it is in context at session start. AGENTS.md — which + has no import syntax — carries a short plain routing 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, not just repos without a CLAUDE.md. +- **`/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 @@ -359,7 +429,12 @@ rule's (Q4 below — they must carry why/context, not restate the identifier). (`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). + and the AGENTS.md routing block (no import syntax there — plain lines pointing + at the index and `conventions.md`). +- **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 +443,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,9 +465,11 @@ 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. @@ -424,3 +503,15 @@ 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 ``, check the first line is `---` + (e.g. `head -1` per file); for every `index.md`, check its frontmatter block + contains `type: index` and no `timestamp:` key; `grep -rn '^related:'` over + doc-root frontmatter; `find -name 'log.md'`. + Violation: a doc-root `.md` with no frontmatter block; an index missing + `type: index`; a `timestamp` key on an index; a `related:` frontmatter key + anywhere; a `log.md` anywhere in the doc root. + 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). From c63b9c541a70ea8b32d160f6aeafdf7135d7283a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 13:22:03 +0000 Subject: [PATCH 02/11] documentation skill: OKF templates, conventions self-hosting, bootstrap migration reference.md gains frontmatter templates (content doc / sub-index / root index), the conventions.md template, the AGENTS.md routing block, the class-to-type mapping, and the brownfield frontmatter-migration guidance; the Feature Doc Template's Related section is reconciled with the one-way link policy (optional, max 3, reason clauses). SKILL.md's FEATURE and BOOTSTRAP steps produce and verify the bundle: derived index lines, the new teach-and-enforce step (conventions.md + check script install), and Q7 in the self-checks. /wire-repo-brain's outcomes follow suit. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CH1Y4ssZbAKJYwqBX1qxCF --- .../commands/wire-repo-brain.md | 30 ++- .../skills/documentation/SKILL.md | 95 +++++--- .../skills/documentation/reference.md | 202 ++++++++++++++++-- 3 files changed, 261 insertions(+), 66 deletions(-) diff --git a/go-linter-driven-development/commands/wire-repo-brain.md b/go-linter-driven-development/commands/wire-repo-brain.md index a897c33..27b4b7a 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. CLAUDE.md wired with the `@/index.md` import + AGENTS.md routing block + (root, and nested per sub-project in a monorepo) +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/skills/documentation/SKILL.md b/go-linter-driven-development/skills/documentation/SKILL.md index 2c7ef04..18f2c0c 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,40 @@ 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). +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**: 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 + minimal CLAUDE.md section if none exists) and the AGENTS.md routing block — + repo root and, in a monorepo, nested per sub-project. 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 +170,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 +187,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 +198,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 +211,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 +224,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..ac1615b 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,16 +474,18 @@ 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 @@ -422,12 +494,77 @@ Each sub-index follows the one-line-per-doc form above. @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 `@` import puts the map in context at session start. + +### AGENTS.md Routing Block + +AGENTS.md has no import syntax; it carries a short routing block instead — for +every tool that reads AGENTS.md rather than CLAUDE.md, not just repos without a +CLAUDE.md. 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. +- 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 +585,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 +611,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 +659,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 From c1e31124bd60acce96ac5ce2e8137bc5cac12011 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 13:22:03 +0000 Subject: [PATCH 03/11] scripts: add check-repo-brain.sh conformance gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dependency-free bash gate running R9's mechanical questions — Q1 orphans, Q2 edges in both directions plus the file:line ban, Q3 root wiring, Q7 bundle contract — with exit 0 clean/not-adopted, 1 violations, 2 usage error. Installed into target repos by the documentation skill's BOOTSTRAP pass; every failure message points at the doc root's conventions.md. Verified against a 15-case fixture matrix (true positives for each check, the two symbol-resolution exemptions, URL exemption, fence skipping). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CH1Y4ssZbAKJYwqBX1qxCF --- .../scripts/check-repo-brain.sh | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100755 go-linter-driven-development/scripts/check-repo-brain.sh 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..d33442e --- /dev/null +++ b/go-linter-driven-development/scripts/check-repo-brain.sh @@ -0,0 +1,193 @@ +#!/usr/bin/env bash +# Repo-brain conformance gate for the go-linter-driven-development plugin. +# +# Runs R9's mechanical falsifying questions over the repo's doc root (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 +# +# Checks (numbering follows rules/R9-repo-brain.md's falsifying questions): +# Q1 orphans — every non-index doc has an index line reaching it +# Q2 edges — code→docs paths resolve; doc-cited exported symbols +# grep in the repo; no file:line citations (URLs exempt) +# Q3 root wiring — CLAUDE.md or AGENTS.md references the index +# Q7 bundle contract— frontmatter on every doc-root .md; `type: index` and +# no `timestamp:` on indexes; no `related:` key; no log.md +# +# Heuristics (documented, deliberate): +# - 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 (R9 Q2's two exemptions); the file:line ban has no +# exemption beyond URLs (lines containing ://). +# - fenced code blocks 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, 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 + +DOCROOT="" +for d in .ai .ainav docs; do + [[ -d "$d" ]] && DOCROOT="$d" && break +done +if [[ -z "$DOCROOT" ]]; then + echo "check-repo-brain: no doc root (.ai/, .ainav/, docs/) — nothing to check yet; run /wire-repo-brain to bootstrap" + exit 0 +fi + +violations=0 +fail() { + echo " $1 — see $DOCROOT/conventions.md" >&2 + violations=$((violations + 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" + target="${target%%#*}" + [[ -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 +} + +# ---------- Q1: orphans — every non-index doc reachable from an index ---------- +indexed_targets="" +while IFS= read -r idx; do + while IFS= read -r raw; do + t="${raw#](}"; t="${t%)}" + [[ "$t" == *.md ]] || continue + resolved=$(resolve_link "$idx" "$t") + [[ -n "$resolved" ]] && indexed_targets="$indexed_targets$resolved +" + done < <(grep -oE '\]\([^)]+\)' "$idx" 2>/dev/null) +done < <(find "$DOCROOT" -type f -name 'index.md') + +while IFS= read -r doc; do + c=$(canon "$doc") + if ! printf '%s' "$indexed_targets" | grep -Fxq "$c"; then + fail "[Q1] $doc — orphan: no index.md lists it" + fi +done < <(find "$DOCROOT" -type f -name '*.md' ! -name 'index.md') + +# ---------- Q2: code→docs edges and doc→doc links resolve ---------- +have_go=0 +if find . -name '*.go' -not -path './vendor/*' -not -path './.git/*' -print -quit 2>/dev/null | grep -q .; then + have_go=1 +fi + +if (( have_go )); then + while IFS= read -r hit; do + file="${hit%%:*}"; rest="${hit#*:}"; line="${rest%%:*}"; target="${rest#*:}" + [[ -f "$target" ]] || fail "[Q2] $file:$line — code edge points at missing $target" + done < <(grep -rnoE '(docs|\.ai|\.ainav)/[A-Za-z0-9._/-]+\.md' \ + --include='*.go' --exclude-dir=vendor --exclude-dir=.git . 2>/dev/null) +fi + +while IFS= read -r md; do + while IFS= read -r raw; do + t="${raw#](}"; t="${t%)}" + [[ "$t" == *.md* ]] || continue + resolved=$(resolve_link "$md" "$t") + [[ -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 + in_fence=0 + lineno=0 + while IFS= read -r line; do + lineno=$((lineno + 1)) + case "$line" in '```'*) in_fence=$((1 - in_fence)); continue ;; esac + (( in_fence )) && continue + case "$line" in *'⚠️'*|*'*(planned)*'*) continue ;; esac + 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 + method="${tok#*.}" + grep -rqE "\) ?[A-Za-z0-9_]* ?\*?[A-Za-z0-9_]*\) ${method}\(|func .*\) ${method}\(" \ + --include='*.go' --exclude-dir=vendor --exclude-dir=.git . 2>/dev/null && continue + grep -rqE "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 (URLs exempt) ---------- +while IFS= read -r hit; do + file="${hit%%:*}"; rest="${hit#*:}"; line="${rest%%:*}" + fail "[Q2] $file:$line — cites a file path or line number (churn-prone coordinate)" +done < <(grep -rnE '\.go(:[0-9]+)?|line [0-9]+' "$DOCROOT" --include='*.md' 2>/dev/null | grep -v '://') + +# ---------- Q3: root wiring ---------- +if ! grep -l 'index.md' CLAUDE.md AGENTS.md >/dev/null 2>&1; then + fail "[Q3] repo root — neither CLAUDE.md nor AGENTS.md references $DOCROOT/index.md" +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 + fm=$(sed -n '2,/^---$/p' "$md") + if printf '%s\n' "$fm" | grep -q '^related:'; then + fail "[Q7] $md — 'related:' frontmatter key (links live in the body)" + fi + if [[ "$(basename "$md")" == "index.md" ]]; then + printf '%s\n' "$fm" | grep -q '^type: index' \ + || fail "[Q7] $md — index frontmatter missing 'type: index'" + if printf '%s\n' "$fm" | grep -q '^timestamp:'; then + fail "[Q7] $md — timestamp on an index (derived files get no authored churn)" + fi + fi +done < <(find "$DOCROOT" -type f -name '*.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') + +# ---------- summary ---------- +if (( violations > 0 )); then + echo "check-repo-brain: $violations violation(s) in $DOCROOT/ — rules: $DOCROOT/conventions.md" >&2 + exit 1 +fi +echo "check-repo-brain: clean ($DOCROOT/)" +exit 0 From cd4478a62945e3907981877d350b864f92f60bd8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 13:22:03 +0000 Subject: [PATCH 04/11] =?UTF-8?q?bookkeeping:=202.10.0=20=E2=80=94=20chang?= =?UTF-8?q?elog,=20READMEs,=20hunter=20hunt-focus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Version bump with the 2.10.0 changelog entry, plugin README tree and table rows for the OKF layer and the new scripts/ directory, the stale root-README version cell fixed in passing, and the pre-commit-review R9 hunter's hunt-focus extended with bundle-contract breaks. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CH1Y4ssZbAKJYwqBX1qxCF --- README.md | 2 +- .../.claude-plugin/plugin.json | 4 +- go-linter-driven-development/CHANGELOG.md | 51 +++++++++++++++++++ go-linter-driven-development/README.md | 7 +-- .../skills/pre-commit-review/SKILL.md | 2 +- 5 files changed, 59 insertions(+), 7 deletions(-) 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..753b038 100644 --- a/go-linter-driven-development/CHANGELOG.md +++ b/go-linter-driven-development/CHANGELOG.md @@ -3,6 +3,57 @@ 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. +- **New `scripts/check-repo-brain.sh`**: dependency-free conformance gate + running Q1–Q3 and Q7 (orphans, both edge directions, file:line ban, root + wiring, bundle contract); 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/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) | From 1a94a997a91407b981718d79f34b6910a28273ef Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 15:16:40 +0000 Subject: [PATCH 05/11] R9 + reference: full bundle contract in Q3/Q7, tags in example, inline-links rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Copilot review on the normative side: Q3 requires the exact /index.md path per doc root (bare index.md mentions no longer count; a monorepo sub-root may ride the repo-root index; a missing AGENTS.md routing reference is an advisory). Q7 states the full bundle contract — terminated frontmatter, per-class required keys, okf_version on the root index alone, and the index-line-equals-description derivation check with the warning-flag exemption. The canonical example's index gains tags, and the conventions template pins links to inline form (reference-style links are not checked). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CH1Y4ssZbAKJYwqBX1qxCF --- .../rules/R9-repo-brain.md | 32 +++++++++++++------ .../skills/documentation/reference.md | 2 ++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/go-linter-driven-development/rules/R9-repo-brain.md b/go-linter-driven-development/rules/R9-repo-brain.md index 4d7f4db..151dd00 100644 --- a/go-linter-driven-development/rules/R9-repo-brain.md +++ b/go-linter-driven-development/rules/R9-repo-brain.md @@ -141,6 +141,7 @@ against the base delay, so an unbounded backoff cannot exist. type: index title: Repo map description: map of all repo docs +tags: [go] okf_version: "0.2" --- # Repo map @@ -474,9 +475,14 @@ one command answers all four. 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 @@ -505,13 +511,19 @@ one command answers all four. affected section, never appending history. 7. **Does any file break the bundle contract?** - Detection: for every `.md` under ``, check the first line is `---` - (e.g. `head -1` per file); for every `index.md`, check its frontmatter block - contains `type: index` and no `timestamp:` key; `grep -rn '^related:'` over - doc-root frontmatter; `find -name 'log.md'`. - Violation: a doc-root `.md` with no frontmatter block; an index missing - `type: index`; a `timestamp` key on an index; a `related:` frontmatter key - anywhere; a `log.md` anywhere in the doc root. + 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/skills/documentation/reference.md b/go-linter-driven-development/skills/documentation/reference.md index ac1615b..9952919 100644 --- a/go-linter-driven-development/skills/documentation/reference.md +++ b/go-linter-driven-development/skills/documentation/reference.md @@ -553,6 +553,8 @@ Index files use `type: index` and never a `timestamp`. Optional on content docs: 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. From 6845e3bc222bd6285a4fe6c16871513024b684ef Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 15:16:41 +0000 Subject: [PATCH 06/11] =?UTF-8?q?check-repo-brain:=20address=20Copilot=20r?= =?UTF-8?q?eview=20=E2=80=94=20multi-root,=20transitive=20reachability,=20?= =?UTF-8?q?full=20contract?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gate now discovers a doc root at the repo root AND every go.mod sub-project, resolving code edges against the citing file's own project. Q1 walks the bundle from its root index, so an unlinked sub-index no longer launders its children. Q3 matches the exact /index.md path, accepts sub-roots linked from the repo-root index, and downgrades a missing AGENTS.md reference to an advisory. Q7 enforces the full frontmatter contract (termination, per-class required keys, root-only okf_version) and the derivation rule (index line text equals the target's description, warning-flagged lines exempt). Link extraction strips optional titles; fences match ``` and ~~~ indented up to three spaces; the file:line ban strips URL spans instead of whole lines. Verified against the extended fixture matrix (regressions + 12 new cases, including a two-bundle monorepo). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CH1Y4ssZbAKJYwqBX1qxCF --- go-linter-driven-development/CHANGELOG.md | 13 +- .../scripts/check-repo-brain.sh | 391 +++++++++++++----- 2 files changed, 289 insertions(+), 115 deletions(-) diff --git a/go-linter-driven-development/CHANGELOG.md b/go-linter-driven-development/CHANGELOG.md index 753b038..f001332 100644 --- a/go-linter-driven-development/CHANGELOG.md +++ b/go-linter-driven-development/CHANGELOG.md @@ -39,10 +39,15 @@ inside and a CI gate outside. (closest file wins), pointing every AGENTS.md-reading agent at the index and conventions.md. - **New `scripts/check-repo-brain.sh`**: dependency-free conformance gate - running Q1–Q3 and Q7 (orphans, both edge directions, file:line ban, root - wiring, bundle contract); 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. + 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 diff --git a/go-linter-driven-development/scripts/check-repo-brain.sh b/go-linter-driven-development/scripts/check-repo-brain.sh index d33442e..e7ef3e0 100755 --- a/go-linter-driven-development/scripts/check-repo-brain.sh +++ b/go-linter-driven-development/scripts/check-repo-brain.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Repo-brain conformance gate for the go-linter-driven-development plugin. # -# Runs R9's mechanical falsifying questions over the repo's doc root (an OKF +# 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). @@ -9,28 +9,43 @@ # 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 non-index doc has an index line reaching it -# Q2 edges — code→docs paths resolve; doc-cited exported symbols -# grep in the repo; no file:line citations (URLs exempt) -# Q3 root wiring — CLAUDE.md or AGENTS.md references the index -# Q7 bundle contract— frontmatter on every doc-root .md; `type: index` and -# no `timestamp:` on indexes; no `related:` key; no log.md +# 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 (R9 Q2's two exemptions); the file:line ban has no -# exemption beyond URLs (lines containing ://). -# - fenced code blocks are skipped for symbol resolution. +# 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, head, sort, wc. No jq/python. +# Uses only POSIX-portable tools: find, grep, sed, awk, head, sort, wc. No jq/python. set -u @@ -41,20 +56,50 @@ if [[ ! -d "$REPO_ROOT" ]]; then fi cd "$REPO_ROOT" || exit 2 -DOCROOT="" -for d in .ai .ainav docs; do - [[ -d "$d" ]] && DOCROOT="$d" && break -done -if [[ -z "$DOCROOT" ]]; then - echo "check-repo-brain: no doc root (.ai/, .ainav/, docs/) — nothing to check yet; run /wire-repo-brain to bootstrap" +# ---------- 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 $DOCROOT/conventions.md" >&2 + 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() { @@ -64,130 +109,254 @@ canon() { (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$base") } -# resolve_link -> absolute path ('' for URLs/anchors) +# resolve_link -> absolute path ('' for URLs/anchors) resolve_link() { - local from="$1" target="$2" + 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) + printf '%s\n' "$(canon "$docroot/${target#/}")" # bundle-relative (OKF) else printf '%s\n' "$(canon "$(dirname "$from")/$target")" fi } -# ---------- Q1: orphans — every non-index doc reachable from an index ---------- -indexed_targets="" -while IFS= read -r idx; do - while IFS= read -r raw; do - t="${raw#](}"; t="${t%)}" - [[ "$t" == *.md ]] || continue - resolved=$(resolve_link "$idx" "$t") - [[ -n "$resolved" ]] && indexed_targets="$indexed_targets$resolved -" - done < <(grep -oE '\]\([^)]+\)' "$idx" 2>/dev/null) -done < <(find "$DOCROOT" -type f -name 'index.md') - -while IFS= read -r doc; do - c=$(canon "$doc") - if ! printf '%s' "$indexed_targets" | grep -Fxq "$c"; then - fail "[Q1] $doc — orphan: no index.md lists it" - fi -done < <(find "$DOCROOT" -type f -name '*.md' ! -name 'index.md') +# 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:]]*$//' +} -# ---------- Q2: code→docs edges and doc→doc links resolve ---------- have_go=0 -if find . -name '*.go' -not -path './vendor/*' -not -path './.git/*' -print -quit 2>/dev/null | grep -q .; then +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" ]] || fail "[Q2] $file:$line — code edge points at missing $target" + [[ -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 -while IFS= read -r md; do - while IFS= read -r raw; do - t="${raw#](}"; t="${t%)}" - [[ "$t" == *.md* ]] || continue - resolved=$(resolve_link "$md" "$t") - [[ -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 - in_fence=0 - lineno=0 - while IFS= read -r line; do - lineno=$((lineno + 1)) - case "$line" in '```'*) in_fence=$((1 - in_fence)); continue ;; esac - (( in_fence )) && continue - case "$line" in *'⚠️'*|*'*(planned)*'*) continue ;; esac - 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 - method="${tok#*.}" - grep -rqE "\) ?[A-Za-z0-9_]* ?\*?[A-Za-z0-9_]*\) ${method}\(|func .*\) ${method}\(" \ - --include='*.go' --exclude-dir=vendor --exclude-dir=.git . 2>/dev/null && continue - grep -rqE "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 +# ---------- 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") -# ---------- Q2: file:line citation ban (URLs exempt) ---------- -while IFS= read -r hit; do - file="${hit%%:*}"; rest="${hit#*:}"; line="${rest%%:*}" - fail "[Q2] $file:$line — cites a file path or line number (churn-prone coordinate)" -done < <(grep -rnE '\.go(:[0-9]+)?|line [0-9]+' "$DOCROOT" --include='*.md' 2>/dev/null | grep -v '://') + # --- 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') -# ---------- Q3: root wiring ---------- -if ! grep -l 'index.md' CLAUDE.md AGENTS.md >/dev/null 2>&1; then - fail "[Q3] repo root — neither CLAUDE.md nor AGENTS.md references $DOCROOT/index.md" -fi + # --- 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') -# ---------- 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 - fm=$(sed -n '2,/^---$/p' "$md") - if printf '%s\n' "$fm" | grep -q '^related:'; then - fail "[Q7] $md — 'related:' frontmatter key (links live in the body)" + # --- 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 - if [[ "$(basename "$md")" == "index.md" ]]; then - printf '%s\n' "$fm" | grep -q '^type: index' \ - || fail "[Q7] $md — index frontmatter missing 'type: index'" - if printf '%s\n' "$fm" | grep -q '^timestamp:'; then - fail "[Q7] $md — timestamp on an index (derived files get no authored churn)" + + # --- 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 -done < <(find "$DOCROOT" -type f -name '*.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') + # --- 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) in $DOCROOT/ — rules: $DOCROOT/conventions.md" >&2 + echo "check-repo-brain: $violations violation(s) — rules: /conventions.md" >&2 exit 1 fi -echo "check-repo-brain: clean ($DOCROOT/)" +echo "check-repo-brain: clean (${ROOTS[*]})" exit 0 From 4f99e66ecf84b043fe50157b66888618c8bbf4b2 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 21:09:53 +0000 Subject: [PATCH 07/11] root wiring: CLAUDE.md embeds AGENTS.md instead of duplicating the routing block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The routing block is authored once, in AGENTS.md (root and nested per sub-project); CLAUDE.md carries two imports — @AGENTS.md and @/index.md — so Claude sessions get the same routing prose other tools read, without a second copy that could drift. R9's root wiring and fix pattern, the reference snippets, the bootstrap step, and the command outcome all state the single-source form; the conformance gate accepts both files as wiring evidence, unchanged. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CH1Y4ssZbAKJYwqBX1qxCF --- go-linter-driven-development/CHANGELOG.md | 3 ++- .../commands/wire-repo-brain.md | 4 ++-- .../rules/R9-repo-brain.md | 20 ++++++++++--------- .../skills/documentation/SKILL.md | 9 +++++---- .../skills/documentation/reference.md | 14 ++++++++----- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/go-linter-driven-development/CHANGELOG.md b/go-linter-driven-development/CHANGELOG.md index f001332..3cbd246 100644 --- a/go-linter-driven-development/CHANGELOG.md +++ b/go-linter-driven-development/CHANGELOG.md @@ -37,7 +37,8 @@ inside and a CI gate outside. - **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. + 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 diff --git a/go-linter-driven-development/commands/wire-repo-brain.md b/go-linter-driven-development/commands/wire-repo-brain.md index 27b4b7a..fd3023a 100644 --- a/go-linter-driven-development/commands/wire-repo-brain.md +++ b/go-linter-driven-development/commands/wire-repo-brain.md @@ -23,8 +23,8 @@ authoritative; this command adds nothing to it. One pass delivers the whole chai 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. CLAUDE.md wired with the `@/index.md` import + AGENTS.md routing block - (root, and nested per sub-project in a monorepo) +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 diff --git a/go-linter-driven-development/rules/R9-repo-brain.md b/go-linter-driven-development/rules/R9-repo-brain.md index 151dd00..2ab3595 100644 --- a/go-linter-driven-development/rules/R9-repo-brain.md +++ b/go-linter-driven-development/rules/R9-repo-brain.md @@ -399,12 +399,13 @@ per file, path = identity, links form the graph), plus documented R9 extensions. 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**: 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 — which - has no import syntax — carries a short plain routing 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, not just repos without a CLAUDE.md. +- **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 @@ -429,9 +430,10 @@ per file, path = identity, links form the graph), plus documented R9 extensions. - **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 - and the AGENTS.md routing block (no import syntax there — plain lines pointing - at the index and `conventions.md`). +- **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 diff --git a/go-linter-driven-development/skills/documentation/SKILL.md b/go-linter-driven-development/skills/documentation/SKILL.md index 18f2c0c..156ac0e 100644 --- a/go-linter-driven-development/skills/documentation/SKILL.md +++ b/go-linter-driven-development/skills/documentation/SKILL.md @@ -126,10 +126,11 @@ unless an R9 Q6 check shows a doc citing the reshaped code. 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**: add the `@/index.md` import to CLAUDE.md (create a - minimal CLAUDE.md section if none exists) and the AGENTS.md routing block — - repo root and, in a monorepo, nested per sub-project. Add or verify; snippets - 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 diff --git a/go-linter-driven-development/skills/documentation/reference.md b/go-linter-driven-development/skills/documentation/reference.md index 9952919..44bcafa 100644 --- a/go-linter-driven-development/skills/documentation/reference.md +++ b/go-linter-driven-development/skills/documentation/reference.md @@ -489,19 +489,23 @@ Each sub-index follows the one-line-per-doc form above, with sub-index frontmatt ### 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. +The `@` imports put the routing block and the map in context at session start. ### AGENTS.md Routing Block -AGENTS.md has no import syntax; it carries a short routing block instead — for -every tool that reads AGENTS.md rather than CLAUDE.md, not just repos without a -CLAUDE.md. 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): +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 From 3f76865796b3606104e422694c6d614e98772a91 Mon Sep 17 00:00:00 2001 From: Dan Mordechay Date: Tue, 25 Aug 2026 12:27:48 +0300 Subject: [PATCH 08/11] review fixes: true OKF conformance, honest drift-check, leaner frontmatter, script bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applies both review verdicts (Karpathy lens + OKF spec audit): - Indexes carry NO frontmatter (OKF §8): type:index/title/description/tags removed; root index keeps only okf_version. Bundles now pass a strict OKF validator — the "R9 extension" label was a spec violation, not an extension. - timestamp renamed to generated (the spec's provenance key); title now optional (the H1 is the title). Required content-doc keys: type, description, generated. - "Regenerable map" claim corrected to drift-check: the description is the single source, the index line is a verified copy — nothing regenerates. - `## Related` section removed: unenforced ceremony; lateral links go inline with the reason in the sentence. - check-repo-brain.sh: docs→code resolution now covers var/const declarations (including inside var/const blocks) and package-qualified tokens (`retry.Policy`); Q7 index branch enforces bare indexes + root-only okf_version; log.md no longer triple-reported; header states the gate enforces the R9 profile (stricter than OKF v0.2). - R9 "description + tags" vs script exact-match contradiction resolved: sub-index lines are authored (bare sub-index has no description to copy). - Verified against a fixture matrix: clean bundle, map of maps, ⚠️/(planned) exemptions, sub-index frontmatter, root extra key, missing okf_version, missing generated, related: key, drifted line, log.md, file:line ban, unresolved symbol, no-doc-root no-op, usage error. Co-Authored-By: Claude Fable 5 --- go-linter-driven-development/CHANGELOG.md | 52 ++++---- go-linter-driven-development/README.md | 2 +- .../commands/wire-repo-brain.md | 8 +- .../rules/R9-repo-brain.md | 126 +++++++++--------- .../scripts/check-repo-brain.sh | 120 ++++++++++------- .../skills/documentation/SKILL.md | 21 +-- .../skills/documentation/reference.md | 109 ++++++--------- 7 files changed, 221 insertions(+), 217 deletions(-) diff --git a/go-linter-driven-development/CHANGELOG.md b/go-linter-driven-development/CHANGELOG.md index 3cbd246..87db1cc 100644 --- a/go-linter-driven-development/CHANGELOG.md +++ b/go-linter-driven-development/CHANGELOG.md @@ -14,22 +14,21 @@ 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 +- **OKF v0.2 bundle conformance (R9)**: content docs carry YAML frontmatter — + required `type`/`description`/`generated` (OKF's provenance key), optional + `title`/`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. 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, 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 + 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 @@ -42,23 +41,26 @@ inside and a CI gate outside. - **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 + directions — doc-cited symbols resolve against type/func/var/const + declarations, including package-qualified tokens — 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, required + content-doc keys, bare indexes, root-only `okf_version`), and every index + line checked against its 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. + 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 diff --git a/go-linter-driven-development/README.md b/go-linter-driven-development/README.md index 41cfe0c..19f34db 100644 --- a/go-linter-driven-development/README.md +++ b/go-linter-driven-development/README.md @@ -83,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; doc root is an OKF bundle (frontmatter, derived index lines) | +| 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 | diff --git a/go-linter-driven-development/commands/wire-repo-brain.md b/go-linter-driven-development/commands/wire-repo-brain.md index fd3023a..0fd4538 100644 --- a/go-linter-driven-development/commands/wire-repo-brain.md +++ b/go-linter-driven-development/commands/wire-repo-brain.md @@ -20,9 +20,11 @@ authoritative; this command adds nothing to it. One pass delivers the whole chai 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); - 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) + 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 diff --git a/go-linter-driven-development/rules/R9-repo-brain.md b/go-linter-driven-development/rules/R9-repo-brain.md index 2ab3595..0aad9ea 100644 --- a/go-linter-driven-development/rules/R9-repo-brain.md +++ b/go-linter-driven-development/rules/R9-repo-brain.md @@ -9,8 +9,9 @@ invariants hold the network together: **reachability** (every doc is reachable f 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). 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). +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 @@ -20,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. @@ -127,9 +128,8 @@ func (p Policy) Do(ctx context.Context, op Op) error { --- type: feature -title: Retry policy description: why retries use capped full jitter; `Policy` API -timestamp: 2026-08-20T00:00:00Z +generated: 2026-08-20T00:00:00Z --- Entry point: `Policy.Do`. Construction: `ParsePolicy` — validates the cap against the base delay, so an unbounded backoff cannot exist. @@ -138,10 +138,6 @@ 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 @@ -161,9 +157,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. 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. +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 @@ -176,7 +172,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.) @@ -351,50 +347,50 @@ rule's (Q4 below — they must carry why/context, not restate the identifier). 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. + 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), plus documented R9 extensions. +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`), - `title`, `description`, and `timestamp`; optional `tags`, and optional lifecycle - keys `status: draft|stable|deprecated` and `stale_after` — the frontmatter-native + `description` (one line — it IS the doc's index line), and `generated` (OKF's + provenance key: ISO 8601, last substantive update). Optional: `title` (the H1 is + the title; the key never replaces it), `tags`, and 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. +- **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. + behavior-not-history, so the file must not exist in a doc root (a stricter + profile than OKF, which allows the file). - **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. + alarm — a deliberate profile inversion. 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**, 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 + 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 @@ -407,9 +403,9 @@ per file, path = identity, links form the graph), plus documented R9 extensions. `@/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. + 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 @@ -434,8 +430,9 @@ per file, path = identity, links form the graph), plus documented R9 extensions. 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 +- **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 @@ -460,9 +457,11 @@ one command answers all four. 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: for each backticked symbol a doc cites, grep for its + declaration — `grep -rnE '(type|func|var|const) \b|^\s*\s*=' --include='*.go' .` + (the `=` branch catches declarations inside `var (`/`const (` blocks; for + methods, grep the method name; for a package-qualified `pkg.Sym`, resolve the + symbol part); 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 '://'` @@ -513,19 +512,20 @@ one command answers all four. 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. + Detection: every content `.md` under `` starts with a terminated + frontmatter block (first line `---`, a closing `---` follows) carrying the + required keys `type`, `description`, `generated`. 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). + 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-deriving the line (derivation rule above). + 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 index e7ef3e0..d062fe0 100755 --- a/go-linter-driven-development/scripts/check-repo-brain.sh +++ b/go-linter-driven-development/scripts/check-repo-brain.sh @@ -1,10 +1,12 @@ #!/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). +# 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 [repo-root] (default: cwd) # CI: one line — bash scripts/check-repo-brain.sh @@ -22,22 +24,24 @@ # 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) +# Q7 bundle contract— content docs carry terminated frontmatter with +# type/description/generated; 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) # # 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. +# - 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. A bare token resolves against type/func/ +# var/const declarations, including `Foo =` inside var/const blocks. # - 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. +# symbol resolution and the description copy check (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. # @@ -201,6 +205,7 @@ check_bundle() { # 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'*) ;; *) @@ -233,17 +238,25 @@ check_bundle() { # 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 -qE '^[A-Z][A-Za-z0-9]*$|^[A-Za-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)" + local prefix="${tok%%.*}" member="${tok#*.}" + if printf '%s' "$prefix" | grep -qE '^[A-Z]'; then + # Type.Method — resolve the method + grep -rqE "func .*\) ${member}\(|func ${member}\(" \ + --include='*.go' --exclude-dir=vendor --exclude-dir=.git . 2>/dev/null && continue + fail "[Q2] $md:$lineno — backticked \`$tok\` does not resolve (method ${member} not found)" + else + # pkg.Symbol — resolve the symbol's declaration + grep -rqE "(type|func|var|const) ${member}\b|^[[:space:]]*${member}[[:space:]]*=" \ + --include='*.go' --exclude-dir=vendor --exclude-dir=.git . 2>/dev/null && continue + fail "[Q2] $md:$lineno — backticked \`$tok\` does not resolve (no declaration of ${member})" + fi else - grep -rqE "(type|func) ${tok}\b" \ + grep -rqE "(type|func|var|const) ${tok}\b|^[[:space:]]*${tok}[[:space:]]*=" \ --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})" + fail "[Q2] $md:$lineno — backticked \`$tok\` does not resolve (no declaration of ${tok})" fi done < <(printf '%s\n' "$line" | grep -oE '`[^`]+`') done < "$md" @@ -283,47 +296,54 @@ check_bundle() { # # --- 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 - local close; close=$(fm_close_line "$md") + 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") + 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 + for key in type description generated; do + printf '%s\n' "$fm" | grep -q "^${key}:" \ + || fail "[Q7] $md — frontmatter missing '${key}:'" + done done < <(find "$docroot" -type f -name '*.md') - # --- Q7: derivation — index line text == target's description (⚠️ exempt) --- + # --- Q7: drift check — index line text == target's description (⚠️ exempt) --- while IFS= read -r idx; do local lineno=0 line while IFS= read -r line; do diff --git a/go-linter-driven-development/skills/documentation/SKILL.md b/go-linter-driven-development/skills/documentation/SKILL.md index 156ac0e..5f8db3c 100644 --- a/go-linter-driven-development/skills/documentation/SKILL.md +++ b/go-linter-driven-development/skills/documentation/SKILL.md @@ -86,12 +86,13 @@ unless an R9 Q6 check shows a doc citing the reshaped code. (`Example_*`) for complex/core types. 4. **Rung 2 — feature doc**: create/update `/.md` from the reference.md template, with OKF frontmatter (required keys — R9's bundle - policy); optional `Related` edges (≤3, each with its reason — R9 edge policy); + 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` — derived - from the doc's `description` (R9 derivation rule); verify root wiring +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 and Q7 mechanically (orphans, broken edges in both directions, unwired @@ -121,11 +122,11 @@ unless an R9 Q6 check shows a doc citing the reshaped code. 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). +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 @@ -212,8 +213,8 @@ 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) + frontmattered index + root wiring + conventions.md + check - script exist; frontmatter verified-or-added on every doc; every +- 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). diff --git a/go-linter-driven-development/skills/documentation/reference.md b/go-linter-driven-development/skills/documentation/reference.md index 44bcafa..1051ef0 100644 --- a/go-linter-driven-development/skills/documentation/reference.md +++ b/go-linter-driven-development/skills/documentation/reference.md @@ -9,8 +9,8 @@ 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 -- [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` +- [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) @@ -317,46 +317,31 @@ 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 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 -title: Retry policy description: why retries use capped full jitter; `Policy` API -timestamp: 2026-08-20T00:00:00Z +generated: 2026-08-20T00:00:00Z # optional: +# title: Retry policy # the H1 is the title; this key never replaces it # 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): +**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 --- -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" --- ``` @@ -376,9 +361,8 @@ 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] +generated: [ISO 8601] --- # [Feature Name] @@ -432,15 +416,12 @@ Input → Validation → Processing → Storage → Output ## Future Considerations - [Known limitations, potential extensions] - -## Related -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 ``` +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 @@ -449,15 +430,12 @@ its reason clause, and never duplicates a link already inline (R9 edge policy): A short reference guide: grouped by topic, ONE line per doc (size and style are 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): +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 --- -type: index -title: Repo Map -description: map of all repo docs -tags: [go] okf_version: "0.2" --- # Repo Map @@ -475,17 +453,18 @@ okf_version: "0.2" ### Map of Maps (past ~300 lines) 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: +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 - [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, with sub-index frontmatter -(Frontmatter Templates; no `okf_version` — that key is the root's alone). +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 @@ -527,9 +506,8 @@ 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] +generated: [ISO 8601] --- # Doc Conventions @@ -537,35 +515,34 @@ This directory is the repo's documentation network — an OKF bundle. Markdown f 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): +Every content doc here starts with frontmatter (copy-paste, fill in): --- type: feature # feature | architecture | guide - title: - description: - timestamp: + description: + generated: --- -Index files use `type: index` and never a `timestamp`. Optional on content docs: -`tags`, `status: draft|stable|deprecated`, `stale_after: `. +Optional on content docs: `title`, `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` — update the doc's frontmatter, - then mirror the line in `index.md`. +- 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. -- 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. + 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 @@ -623,7 +600,9 @@ An existing network without frontmatter — wired by hand, or by a plugin versio 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 +`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 @@ -665,17 +644,17 @@ vets after the edit. ### Feature Documentation Checklist -- [ ] Frontmatter present with the four required keys (`type`, `title`, - `description`, `timestamp`); `description` reads as the index line +- [ ] Frontmatter present with the required keys (`type`, `description`, + `generated` — 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, 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 +- [ ] 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 From b8f94886906c011c94082956012746657d968884 Mon Sep 17 00:00:00 2001 From: Dan Mordechay Date: Tue, 25 Aug 2026 15:26:50 +0300 Subject: [PATCH 09/11] re-review round 2: generated optional, --fix index rewriter, per-line exemption wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remaining items from the second two-lens review pass: - `generated` demoted to optional: required but nothing validated or consumed it — a self-reported date that silently rots. Required content-doc keys are now `type` (OKF's one required key — dropping it would un-conform the bundle) and `description` (feeds the drift check). - New `check-repo-brain.sh --fix`: rewrites drifted index lines from each target doc's `description` — the gate already computed the correct line; the flag turns the drift check into the fixer and removes the last hand-copy step. Fixes apply after the read loop, never during it. - R9 Q2 exemption wording aligned to the script: the ⚠️ / *(planned)* exemptions are per-LINE, matching what the gate actually skips. - Spec-lawyer parentheticals trimmed from R9's bundle policy. Verified: bash -n clean; --fix rewrites the drifted line and the re-check is clean; check mode still fails on drift; full fixture matrix regressions green; missing `generated` no longer a violation. Co-Authored-By: Claude Fable 5 --- go-linter-driven-development/CHANGELOG.md | 21 ++++---- .../rules/R9-repo-brain.md | 48 ++++++++--------- .../scripts/check-repo-brain.sh | 52 +++++++++++++++---- .../skills/documentation/reference.md | 28 +++++----- 4 files changed, 91 insertions(+), 58 deletions(-) diff --git a/go-linter-driven-development/CHANGELOG.md b/go-linter-driven-development/CHANGELOG.md index 87db1cc..71fc77a 100644 --- a/go-linter-driven-development/CHANGELOG.md +++ b/go-linter-driven-development/CHANGELOG.md @@ -15,17 +15,20 @@ inside and a CI gate outside. ### Added - **OKF v0.2 bundle conformance (R9)**: content docs carry YAML frontmatter — - required `type`/`description`/`generated` (OKF's provenance key), optional - `title`/`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. + 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. 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. + 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 diff --git a/go-linter-driven-development/rules/R9-repo-brain.md b/go-linter-driven-development/rules/R9-repo-brain.md index 0aad9ea..c741c17 100644 --- a/go-linter-driven-development/rules/R9-repo-brain.md +++ b/go-linter-driven-development/rules/R9-repo-brain.md @@ -129,7 +129,6 @@ func (p Policy) Do(ctx context.Context, op Op) error { --- type: feature description: why retries use capped full jitter; `Policy` API -generated: 2026-08-20T00:00:00Z --- Entry point: `Policy.Do`. Construction: `ParsePolicy` — validates the cap against the base delay, so an unbounded backoff cannot exist. @@ -357,12 +356,12 @@ per file, path = identity, links form the graph). R9 applies a **stricter profil 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`), - `description` (one line — it IS the doc's index line), and `generated` (OKF's - provenance key: ISO 8601, last substantive update). Optional: `title` (the H1 is - the title; the key never replaces it), `tags`, and lifecycle keys - `status: draft|stable|deprecated` and `stale_after` — the frontmatter-native - form of the ⚠️ stale flag. +- **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 @@ -374,11 +373,9 @@ any OKF tool. 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 (a stricter - profile than OKF, which allows the file). -- **Broken links stay violations.** OKF tells consumers to tolerate dangling links - as not-yet-written knowledge; internally that tolerance would silence the drift - alarm — a deliberate profile inversion. The *(planned)* marker (Q2) is the one + 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. @@ -467,13 +464,13 @@ one command answers all four. 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, 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 + 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 *(planned)*-marked citations are exempt from resolution. + 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: for each doc root, `grep -l '/index.md' CLAUDE.md AGENTS.md @@ -514,13 +511,14 @@ one command answers all four. 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`, `description`, `generated`. 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). + 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:` diff --git a/go-linter-driven-development/scripts/check-repo-brain.sh b/go-linter-driven-development/scripts/check-repo-brain.sh index d062fe0..9d5ee8d 100755 --- a/go-linter-driven-development/scripts/check-repo-brain.sh +++ b/go-linter-driven-development/scripts/check-repo-brain.sh @@ -8,8 +8,10 @@ # in-repo copy). 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) +# 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/. @@ -25,11 +27,11 @@ # 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/generated; 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) +# 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" @@ -53,6 +55,11 @@ 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 @@ -105,6 +112,16 @@ fail() { } 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 @@ -337,15 +354,16 @@ check_bundle() { # 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 generated; do + 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) --- + # --- 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 + local lineno=0 line fixes="" while IFS= read -r line; do lineno=$((lineno + 1)) case "$line" in *'⚠️'*) continue ;; esac @@ -358,9 +376,22 @@ check_bundle() { # 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" + 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 --- @@ -374,6 +405,7 @@ for i in "${!PROJS[@]}"; do 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 diff --git a/go-linter-driven-development/skills/documentation/reference.md b/go-linter-driven-development/skills/documentation/reference.md index 1051ef0..6ccf75c 100644 --- a/go-linter-driven-development/skills/documentation/reference.md +++ b/go-linter-driven-development/skills/documentation/reference.md @@ -327,12 +327,12 @@ its `description`, so write the description as the index line you want. --- type: feature description: why retries use capped full jitter; `Policy` API -generated: 2026-08-20T00:00:00Z # optional: -# title: Retry policy # the H1 is the title; this key never replaces it +# 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 +# status: stable # draft | stable | deprecated +# stale_after: 2027-01-01 # past this date the index line gets the ⚠️ flag --- ``` @@ -362,7 +362,6 @@ file paths and line numbers never. --- type: feature description: [the index line — one line, what and why; key symbols] -generated: [ISO 8601] --- # [Feature Name] @@ -507,7 +506,6 @@ infrastructure, not a content doc). Listed FIRST in the index. Template: --- type: guide description: how to maintain this doc root (read before editing docs) -generated: [ISO 8601] --- # Doc Conventions @@ -520,12 +518,12 @@ Every content doc here starts with frontmatter (copy-paste, fill in): --- type: feature # feature | architecture | guide description: - generated: --- -Optional on content docs: `title`, `tags`, `status: draft|stable|deprecated`, -`stale_after: `. Index files carry NO frontmatter — except the root -`index.md`, which carries only `okf_version`. +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 @@ -548,6 +546,7 @@ Optional on content docs: `title`, `tags`, `status: draft|stable|deprecated`, ## 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`. ``` --- @@ -599,8 +598,9 @@ between capabilities → architecture. 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 -`generated` from the doc's last substantive git touch when evident. An index +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 @@ -644,8 +644,8 @@ vets after the edit. ### Feature Documentation Checklist -- [ ] Frontmatter present with the required keys (`type`, `description`, - `generated` — R9's bundle policy); `description` reads as the index line +- [ ] 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 From e0b35dcb2ea8fac54926f78328ba214f523fa8df Mon Sep 17 00:00:00 2001 From: Dan Mordechay Date: Tue, 25 Aug 2026 18:56:41 +0300 Subject: [PATCH 10/11] Q2 gate rewrite after genius-api dogfood: set-based resolution, 25 min -> 55 s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first real-repo run (31 docs, 1,271 Go files) produced 1,071 violations — near-all false positives — in 20-25 minutes. Root causes and fixes: - Per-token repo-wide greps replaced by set operations: one awk pass builds the declaration set (single-line AND grouped `type (`/`var (`/`const (` declarations, functions, methods); one awk pass extracts all doc tokens and file-path candidates (fence-aware); one batched repo-wide word-grep resolves the whole unresolved set at once. - Bare method names and grouped declarations now resolve (the two biggest false-positive classes). - External `pkg.Sym` (stdlib, dependencies) exempt — the package-name set from the same awk pass decides what is "in this repo". - Non-Go tokens (alert names, config keys) resolve via the whole-word fallback over non-markdown files instead of failing as "missing Go symbols". - file:line ban now skips fenced code blocks and glob patterns (`**/*.go`), and no longer substring-matches names like `.golangci.yml`. Re-run on the same repo: 55 s, 324 violations — all genuine (301 prose file-path citations the edge policy bans, 23 real symbol drift). Fixture matrix extended with the six discovered classes; all regressions green. Co-Authored-By: Claude Fable 5 --- go-linter-driven-development/CHANGELOG.md | 17 +- .../rules/R9-repo-brain.md | 19 +- .../scripts/check-repo-brain.sh | 200 +++++++++++++----- 3 files changed, 170 insertions(+), 66 deletions(-) diff --git a/go-linter-driven-development/CHANGELOG.md b/go-linter-driven-development/CHANGELOG.md index 71fc77a..f7a3e98 100644 --- a/go-linter-driven-development/CHANGELOG.md +++ b/go-linter-driven-development/CHANGELOG.md @@ -44,12 +44,17 @@ inside and a CI gate outside. - **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 — doc-cited symbols resolve against type/func/var/const - declarations, including package-qualified tokens — 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, required - content-doc keys, bare indexes, root-only `okf_version`), and every index - line checked against its target's `description`. BOOTSTRAP installs it into + 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. diff --git a/go-linter-driven-development/rules/R9-repo-brain.md b/go-linter-driven-development/rules/R9-repo-brain.md index c741c17..ea513e3 100644 --- a/go-linter-driven-development/rules/R9-repo-brain.md +++ b/go-linter-driven-development/rules/R9-repo-brain.md @@ -454,16 +454,19 @@ one command answers all four. 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 for its - declaration — `grep -rnE '(type|func|var|const) \b|^\s*\s*=' --include='*.go' .` - (the `=` branch catches declarations inside `var (`/`const (` blocks; for - methods, grep the method name; for a package-qualified `pkg.Sym`, resolve the - symbol part); 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. + 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 diff --git a/go-linter-driven-development/scripts/check-repo-brain.sh b/go-linter-driven-development/scripts/check-repo-brain.sh index 9d5ee8d..117ee7a 100755 --- a/go-linter-driven-development/scripts/check-repo-brain.sh +++ b/go-linter-driven-development/scripts/check-repo-brain.sh @@ -39,13 +39,19 @@ # - 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. A bare token resolves against type/func/ -# var/const declarations, including `Foo =` inside var/const blocks. +# ) 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. +# 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. +# 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) @@ -164,6 +170,96 @@ if find . -name '*.go' -not -path './vendor/*' -not -path '*/vendor/*' -not -pat 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 @@ -241,55 +337,55 @@ check_bundle() { # 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-Za-z][A-Za-z0-9_]*\.[A-Z][A-Za-z0-9]*$' || continue - printf '%s' "$tok" | grep -q '[a-z]' || continue - if [[ "$tok" == *.* ]]; then - local prefix="${tok%%.*}" member="${tok#*.}" - if printf '%s' "$prefix" | grep -qE '^[A-Z]'; then - # Type.Method — resolve the method - grep -rqE "func .*\) ${member}\(|func ${member}\(" \ - --include='*.go' --exclude-dir=vendor --exclude-dir=.git . 2>/dev/null && continue - fail "[Q2] $md:$lineno — backticked \`$tok\` does not resolve (method ${member} not found)" - else - # pkg.Symbol — resolve the symbol's declaration - grep -rqE "(type|func|var|const) ${member}\b|^[[:space:]]*${member}[[:space:]]*=" \ - --include='*.go' --exclude-dir=vendor --exclude-dir=.git . 2>/dev/null && continue - fail "[Q2] $md:$lineno — backticked \`$tok\` does not resolve (no declaration of ${member})" - fi - else - grep -rqE "(type|func|var|const) ${tok}\b|^[[:space:]]*${tok}[[:space:]]*=" \ - --include='*.go' --exclude-dir=vendor --exclude-dir=.git . 2>/dev/null && continue - fail "[Q2] $md:$lineno — backticked \`$tok\` does not resolve (no declaration of ${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)" + # --- 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 - done < <(grep -rnE '\.go(:[0-9]+)?|line [0-9]+' "$docroot" --include='*.md' 2>/dev/null) + 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" From 317325b28b9773113639507f10c1b7ba929dfa2d Mon Sep 17 00:00:00 2001 From: Dan Mordechay Date: Wed, 26 Aug 2026 20:59:23 +0300 Subject: [PATCH 11/11] document the language scope: Go-first code checks, language-agnostic structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The genius-api dogfood showed the degradation on non-Go code is silent. Five one-liners state it where each audience looks: - /wire-repo-brain: what a non-Go or mixed repo gets and loses - documentation SKILL step 6: upward edges wire Go files only — non-Go docs are reported as unwired, never wired unverifiably - R9 monorepo section: go.mod-keyed discovery; other-language sub-projects stay reachable but outside the gate - script header: structure checks language-agnostic, Q2 Go-only - conventions.md template: contributors see the scope in-repo Co-Authored-By: Claude Fable 5 --- go-linter-driven-development/commands/wire-repo-brain.md | 9 +++++++++ go-linter-driven-development/rules/R9-repo-brain.md | 4 ++++ go-linter-driven-development/scripts/check-repo-brain.sh | 5 +++++ .../skills/documentation/SKILL.md | 4 +++- .../skills/documentation/reference.md | 2 ++ 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/go-linter-driven-development/commands/wire-repo-brain.md b/go-linter-driven-development/commands/wire-repo-brain.md index 0fd4538..aa09800 100644 --- a/go-linter-driven-development/commands/wire-repo-brain.md +++ b/go-linter-driven-development/commands/wire-repo-brain.md @@ -45,6 +45,15 @@ authoritative; this command adds nothing to it. One pass delivers the whole chai 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 from frontmatter; existing edges, wiring, conventions, and diff --git a/go-linter-driven-development/rules/R9-repo-brain.md b/go-linter-driven-development/rules/R9-repo-brain.md index ea513e3..29d1601 100644 --- a/go-linter-driven-development/rules/R9-repo-brain.md +++ b/go-linter-driven-development/rules/R9-repo-brain.md @@ -410,6 +410,10 @@ any OKF tool. `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. diff --git a/go-linter-driven-development/scripts/check-repo-brain.sh b/go-linter-driven-development/scripts/check-repo-brain.sh index 117ee7a..ab9392d 100755 --- a/go-linter-driven-development/scripts/check-repo-brain.sh +++ b/go-linter-driven-development/scripts/check-repo-brain.sh @@ -16,6 +16,11 @@ # 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 diff --git a/go-linter-driven-development/skills/documentation/SKILL.md b/go-linter-driven-development/skills/documentation/SKILL.md index 5f8db3c..392d72e 100644 --- a/go-linter-driven-development/skills/documentation/SKILL.md +++ b/go-linter-driven-development/skills/documentation/SKILL.md @@ -142,7 +142,9 @@ unless an R9 Q6 check shows a doc citing the reshaped code. 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. 7. **Confirm and report**: re-run R9 Q1–Q3 and Q7 as confirmation — via the diff --git a/go-linter-driven-development/skills/documentation/reference.md b/go-linter-driven-development/skills/documentation/reference.md index 6ccf75c..52236c5 100644 --- a/go-linter-driven-development/skills/documentation/reference.md +++ b/go-linter-driven-development/skills/documentation/reference.md @@ -547,6 +547,8 @@ only `okf_version`. 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. ``` ---