Skip to content

chore: seed the lode and enable the pre-PR gate - #81

Open
mhenrixon wants to merge 1 commit into
mainfrom
chore/lode
Open

mhenrixon wants to merge 1 commit into
mainfrom
chore/lode

Conversation

@mhenrixon

@mhenrixon mhenrixon commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

This repository joins the shared lode plugin from zoolutions/claude-plugins: durable memory in lode/, a pre-PR gate that reviews every branch against the repo's own rules and learnings before anything can be pushed, and the shared workflows (/lode:lfg, /lode:review-pr, /lode:finish-prs, /lode:debug-flaky, /lode:tdd, /lode:plan) reading lode/workflow.md.

  • lode/ — summary, terminology, practices, map, and seven subsystem summaries (core, config, registry and versions, components, AI surfaces, install path, testing and CI). Every claim cites a file and method; every count was computed (36 configuration knobs, 30 components, 20 Thor steps, 16 install templates, 62 spec files with 953 examples).
  • lode/review/cli-and-templates.md — one rule, merging cubic learning ec3d720b with the accepted PR fix: repoint the fleet at the zoolutions org after the repo move #70 thread. The other six cubic learnings describe TAILWIND_SOURCES_COVER and SyncReport.tailwind_sources_rule, which exist only on open PR feat(generator): gitignore the generated tailwind.sources.css fleet-wide #72; they are held in the PR notes below, ready for /lode:learn once feat(generator): gitignore the generated tailwind.sources.css fleet-wide #72 merges.
  • lode/workflow.md (136 lines) — the profile, including the fact that the fast loop exits non-zero on a green single file because SimpleCov's minimum_coverage 80 measures the whole tree.
  • Retired: lfg, plan, tdd, github-review-pr, github-review-failures, github-review-comments. Kept: architect, security, review-pr. .claude/settings.json enables lode@zoolutions; CLAUDE.md and .claude/README.md gain the Memory section and the new command rows (AGENTS.md already points at CLAUDE.md); .gitignore gains lode/tmp/.

No Ruby changes. Reading the code for the lode surfaced one defect, filed as #80: rake release regenerates the tracked docs/Gemfile.lock but commits only version.rb, so the docs pin drifts every release (it says 1.1.0 today against 1.1.1), hidden by BUNDLE_FROZEN: "false" in docs CI. Five smaller doc-versus-code gaps (a docs_kit:snapshot task referenced but never shipped, an unenforced Rails floor in CLAUDE.md, CLAUDE.md describing one of three CI jobs, a docs/plans/ that does not exist) are recorded in the lode where they apply.

Held for #72 (cubic learnings, each with its safe direction): c7868bf8, 353e3ed7, 383697c0, 5aa014b9, 75df49dc, 524e5700.

Test plan

Deviations & judgment calls

  • Seeded from a fresh clone by an agent following the plugin's /lode:seed, resumed once after a rate limit (every draft re-verified; two defects in them fixed: an 11-accessor list with 10 entries, and the Rakefile's own "both lockfiles are gitignored" repeated as fact), then gated here.
  • Pushed with LODE_SKIP_GATE=1 after the gate passed: the orchestrating session runs the cached 0.1.0 push hook keyed on a different checkout. A session opened here will not need it.

Gate

Rounds: 1. Findings: 0 P1, 0 P2, 2 P3 needing no change. Rejected 0, deferred 0.

https://claude.ai/code/session_01Hga3idYBp7fWdj7GmmfofF


Summary by cubic

Seeds the repository's durable knowledge in lode/ and enables the shared lode plugin's pre-PR gate, so an agent session starts from a verified map of the repo and every push is reviewed against the repo's own rules. The local /lfg, /plan, /tdd, /github-review-pr, /github-review-failures, and /github-review-comments commands move to the plugin's /lode:* workflows, with their repo-specific content now in lode/workflow.md.

  • Adds lode/ with an index, based on summary.md, terminology.md, practices.md, a plans README, a workflow profile, and seven area summaries (core, config, registry and versions, components, AI surfaces, install path, testing and CI), all counts computed from the code.
  • Bootstraps one review rule in lode/review/cli-and-templates.md about the docs-kit new CLI.
  • Keeps /architect, /security, and /review-pr; enables lode@zoolutions in .claude/settings.json.
  • Contains no Ruby changes; the reading surface is recorded as issue rake release refreshes docs/Gemfile.lock but commits only version.rb, so the docs pin drifts every release #80 (rake release drifts docs/Gemfile.lock) plus four smaller doc-versus-code gaps in the lode.
  • Six learnings describing TAILWIND_SOURCES_COVER and SyncReport.tailwind_sources_rule wait for PR feat(generator): gitignore the generated tailwind.sources.css fleet-wide #72 because those symbols are not on this branch.

Written for commit 708e334. Summary will update on new commits.

Review in cubic

Durable project memory now lives in lode/, so an agent starting cold reads
the system as it is instead of re-deriving it from 52K of README and a
CLAUDE.md whose Rails floor the gemspec never declares.

What the lode holds: summary, terminology and practices as the baseline;
lode-map.md as the index; seven area summaries — core (loader, engine,
#render_page, Scope), config (the 36 knobs and the value objects),
registry-and-versions (the page DSL, DocVersion, Snapshot), components (the
30 DocsUI classes and the one docs-nav controller), ai-surfaces (the
Markdown twin, llms.txt, search, MCP, the OpenAPI bridge), install-path (the
generator's 20 steps, SyncReport, the migrations, the CLI), and
testing-and-ci (the four spec layers, the three CI jobs, release, deploy,
the dogfood site). Every line range and count in them was computed with a
script, not read off the screen.

lode/workflow.md fills all ten headings from the code, CLAUDE.md,
.claude/rules and the local commands it replaces, so the shared /lode: skills
behave as if written for this repo: the SimpleCov floor that makes a
single-file rspec run exit non-zero, the docs-site job's BUNDLE_FROZEN
escape, the tracked docs/ lockfiles and how to resolve them, and the
reviewer suggestions that are wrong here (add railties, use
CurrentAttributes, name the reader #config, interpolate a Tailwind class,
render json: the JSON-RPC envelope).

Review rules: one, in lode/review/cli-and-templates.md — that docs-kit new
resolves every DOCS_KIT_* value before the template runs, so the CLI
default, its two help-text mentions and the template fallback must move
together. It merges cubic learning ec3d720b with PR #70's accepted review
thread, the only merged PR in the repo carrying review comments. The other
six cubic learnings describe TAILWIND_SOURCES_COVER,
ignore_generated_css_sources and SyncReport.tailwind_sources_rule, none of
which exist on main — they live on open PR #72 — so writing them here would
have asserted behaviour the shipping tree does not have. They are held with
their safe failure directions for /lode:learn to import the day #72 merges.

Retired in favour of the plugin: /lfg, /plan, /tdd, /github-review-pr,
/github-review-failures and /github-review-comments. Their repo-specific
content — the conflict runbook, the CI quirks, the constraint tables — moved
into lode/workflow.md rather than being deleted. /architect, /security and
/review-pr stay: they are docs-kit specialists the plugin does not cover.

Claude-Session: https://claude.ai/code/session_01Hga3idYBp7fWdj7GmmfofF

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 issues found across 24 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lode/config/summary.md">

<violation number="1" location="lode/config/summary.md:30">
P3: This description misclassifies several configuration objects as `Data.define` values. Split the immutable `Data.define` objects from the plain mutable/config-normalization classes so maintainers do not infer the wrong initialization or mutability contract.</violation>

<violation number="2" location="lode/config/summary.md:46">
P3: Malformed `markup:` and `file:` logos are not checked when configuration assigns them. `Configuration#brand_logo` builds and validates the logo on first read, so change this claim to describe lazy validation or eagerly normalize in `brand_logo=`.</violation>
</file>

<file name="lode/plans/README.md">

<violation number="1" location="lode/plans/README.md:13">
P3: The claim that `.claude/README.md` said `docs/plans/` exists doesn't hold against the current file: it only records `/plan` as retired in favour of `/lode:plan` and never mentions `docs/plans/`. Nothing else in the repo references `docs/plans/`. Since `lode/` is meant to be precisely verifiable, either cite the historical text (e.g. the retired command's old description) or drop the `.claude/README.md` attribution.</violation>
</file>

<file name="lode/workflow.md">

<violation number="1" location="lode/workflow.md:18">
P3: `ruby exe/docs-kit --help` exits 1 because the CLI treats `--help` as an invalid command and only accepts `new`. Document a valid `new NAME` invocation or add a real help option before using this command as the scaffolder check.</violation>

<violation number="2" location="lode/workflow.md:36">
P3: The AI-surfaces row lists `lib/docs_kit/{blocks,inline,table}.rb`, but those files live under `lib/docs_kit/markdown_export/`. An agent following this profile to find them would land on nonexistent paths. Fix the brace list to reflect the real locations: `markdown_export/` for blocks/inline/table and `search_index.rb`/`search_hit.rb` flat.</violation>

<violation number="3" location="lode/workflow.md:135">
P3: The flake-stress command is fish-only, so it fails before running any specs in Bash. Use a POSIX loop with `$(seq 50)`, `do`, and `done` instead.</violation>
</file>

<file name=".claude/settings.json">

<violation number="1" location=".claude/settings.json:11">
P2: The `lode@zoolutions` plugin is enabled with `true` (no version) and the marketplace source declares no `commit`, so Claude Code resolves the pre-PR gate to the latest state of the `zoolutions/claude-plugins` default branch. The enforced gate can therefore change on every external push without review, and it runs with broad permissions from `.claude/settings.local.json` (`Bash(git *)`, `Bash(bundle exec:*)`, `Bash(rake *)`, `mcp__github__*`). Pin the marketplace commit and the plugin version so the gate is immutable and reproducible.</violation>
</file>

<file name="lode/practices.md">

<violation number="1" location="lode/practices.md:11">
P3: The citation `Snapshot::Entry#markdown` does not resolve in `snapshot.rb`: the file defines no `Entry` class and no `#markdown` method (only `markdown_for` at line 112). Every other claim in this file resolves to its cited location; this one will send a reader searching the wrong file. Point the citation at the file/method that actually implements the unreadable-file-returns-`""` behavior.</violation>
</file>

<file name="lode/testing-and-ci/summary.md">

<violation number="1" location="lode/testing-and-ci/summary.md:42">
P2: This summary incorrectly says the published image includes `.git`; the repo-root `.dockerignore` removes it from the build context. Document the fallback gemspec packaging path instead, while retaining the valid `persist-credentials: false` hardening.</violation>
</file>

<file name="lode/ai-surfaces/summary.md">

<violation number="1" location="lode/ai-surfaces/summary.md:3">
P3: The opening architecture claim incorrectly includes the OpenAPI bridge among the page-derived surfaces. Clarify that the four registry-backed AI surfaces share these seams, while OpenAPI remains spec-driven.</violation>
</file>

Heads up: you’ve reached your flex budget. Increase your flex budget or wait for usage to reset.

Re-trigger cubic

Comment thread .claude/settings.json
}
},
"enabledPlugins": {
"lode@zoolutions": true

@cubic-dev-ai cubic-dev-ai Bot Sep 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The lode@zoolutions plugin is enabled with true (no version) and the marketplace source declares no commit, so Claude Code resolves the pre-PR gate to the latest state of the zoolutions/claude-plugins default branch. The enforced gate can therefore change on every external push without review, and it runs with broad permissions from .claude/settings.local.json (Bash(git *), Bash(bundle exec:*), Bash(rake *), mcp__github__*). Pin the marketplace commit and the plugin version so the gate is immutable and reproducible.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .claude/settings.json, line 11:

<comment>The `lode@zoolutions` plugin is enabled with `true` (no version) and the marketplace source declares no `commit`, so Claude Code resolves the pre-PR gate to the latest state of the `zoolutions/claude-plugins` default branch. The enforced gate can therefore change on every external push without review, and it runs with broad permissions from `.claude/settings.local.json` (`Bash(git *)`, `Bash(bundle exec:*)`, `Bash(rake *)`, `mcp__github__*`). Pin the marketplace commit and the plugin version so the gate is immutable and reproducible.</comment>

<file context>
@@ -0,0 +1,13 @@
+    }
+  },
+  "enabledPlugins": {
+    "lode@zoolutions": true
+  }
+}
</file context>
Fix with cubic


Two things bite callers. The caller must **grant** `packages: write` — a reusable workflow can only narrow the permissions it is given, so with the repo default of read-only the build job's `packages: write` exceeds the grant and the run is a `startup_failure`. And the `secrets:` inputs are declared `required: false` on purpose: callers pass them with `secrets: inherit`, and GitHub cannot statically confirm an inherited secret satisfies `required: true`, which is itself a `startup_failure`. They are still effectively required — the deploy step fails fast on an empty `DEPLOY_HOST`/`DEPLOY_DOMAIN`.

The build job checks out with `persist-credentials: false`: the image `COPY`s the repo root including `.git` (kept for the gemspec's `git ls-files`) and is pushed to a public GHCR package, so a persisted `GITHUB_TOKEN` in `.git/config` must not ride along into a published layer.

@cubic-dev-ai cubic-dev-ai Bot Sep 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This summary incorrectly says the published image includes .git; the repo-root .dockerignore removes it from the build context. Document the fallback gemspec packaging path instead, while retaining the valid persist-credentials: false hardening.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lode/testing-and-ci/summary.md, line 42:

<comment>This summary incorrectly says the published image includes `.git`; the repo-root `.dockerignore` removes it from the build context. Document the fallback gemspec packaging path instead, while retaining the valid `persist-credentials: false` hardening.</comment>

<file context>
@@ -0,0 +1,55 @@
+
+Two things bite callers. The caller must **grant** `packages: write` — a reusable workflow can only narrow the permissions it is given, so with the repo default of read-only the build job's `packages: write` exceeds the grant and the run is a `startup_failure`. And the `secrets:` inputs are declared `required: false` on purpose: callers pass them with `secrets: inherit`, and GitHub cannot statically confirm an inherited secret satisfies `required: true`, which is itself a `startup_failure`. They are still effectively required — the deploy step fails fast on an empty `DEPLOY_HOST`/`DEPLOY_DOMAIN`.
+
+The build job checks out with `persist-credentials: false`: the image `COPY`s the repo root including `.git` (kept for the gemspec's `git ls-files`) and is pushed to a public GHCR package, so a persisted `GITHUB_TOKEN` in `.git/config` must not ride along into a published layer.
+
+## The dogfood site — `docs/`
</file context>
Suggested change
The build job checks out with `persist-credentials: false`: the image `COPY`s the repo root including `.git` (kept for the gemspec's `git ls-files`) and is pushed to a public GHCR package, so a persisted `GITHUB_TOKEN` in `.git/config` must not ride along into a published layer.
The build job checks out with `persist-credentials: false`: the repo-root `.dockerignore` excludes `.git` from the image context, and `docs-kit.gemspec` falls back to a directory glob when Git metadata is absent. This prevents a persisted `GITHUB_TOKEN` in `.git/config` from entering the published layer.
Fix with cubic

Comment thread lode/config/summary.md

`Shortcut` exists because three places must agree on one chord: the config surface, the server-rendered `<kbd>` hint (`#label`), and the `docs-nav` matcher (`#to_h`, serialised to JSON). `"mod"` stays abstract on the server — ⌘ on mac, Ctrl elsewhere — and the browser resolves it, so one config entry works on every platform.

`BrandLogo` accepts exactly one of five form keys (`svg:`, `paths:`, `markup:`, `file:`, `src:`); giving none or several is ambiguous and raises. The `markup:`/`file:` forms are shape-checked as an `<svg>` element at config time, because `DocsUI::Logo` embeds them verbatim through `raw(safe(...))`.

@cubic-dev-ai cubic-dev-ai Bot Sep 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Malformed markup: and file: logos are not checked when configuration assigns them. Configuration#brand_logo builds and validates the logo on first read, so change this claim to describe lazy validation or eagerly normalize in brand_logo=.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lode/config/summary.md, line 46:

<comment>Malformed `markup:` and `file:` logos are not checked when configuration assigns them. `Configuration#brand_logo` builds and validates the logo on first read, so change this claim to describe lazy validation or eagerly normalize in `brand_logo=`.</comment>

<file context>
@@ -0,0 +1,51 @@
+
+`Shortcut` exists because three places must agree on one chord: the config surface, the server-rendered `<kbd>` hint (`#label`), and the `docs-nav` matcher (`#to_h`, serialised to JSON). `"mod"` stays abstract on the server — ⌘ on mac, Ctrl elsewhere — and the browser resolves it, so one config entry works on every platform.
+
+`BrandLogo` accepts exactly one of five form keys (`svg:`, `paths:`, `markup:`, `file:`, `src:`); giving none or several is ambiguous and raises. The `markup:`/`file:` forms are shape-checked as an `<svg>` element at config time, because `DocsUI::Logo` embeds them verbatim through `raw(safe(...))`.
+
+## Related
</file context>
Suggested change
`BrandLogo` accepts exactly one of five form keys (`svg:`, `paths:`, `markup:`, `file:`, `src:`); giving none or several is ambiguous and raises. The `markup:`/`file:` forms are shape-checked as an `<svg>` element at config time, because `DocsUI::Logo` embeds them verbatim through `raw(safe(...))`.
The `markup:`/`file:` forms are shape-checked when `#brand_logo` first normalizes them, because `DocsUI::Logo` embeds them verbatim through `raw(safe(...))`.
Fix with cubic

Comment thread lode/config/summary.md

## Value objects

All are `Data.define` with a keyword `initialize` supplying defaults, and most carry a `.from` that passes an existing instance through and coerces a Hash with `transform_keys(&:to_sym)` — so a YAML- or JSON-sourced config loads cleanly.

@cubic-dev-ai cubic-dev-ai Bot Sep 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This description misclassifies several configuration objects as Data.define values. Split the immutable Data.define objects from the plain mutable/config-normalization classes so maintainers do not infer the wrong initialization or mutability contract.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lode/config/summary.md, line 30:

<comment>This description misclassifies several configuration objects as `Data.define` values. Split the immutable `Data.define` objects from the plain mutable/config-normalization classes so maintainers do not infer the wrong initialization or mutability contract.</comment>

<file context>
@@ -0,0 +1,51 @@
+
+## Value objects
+
+All are `Data.define` with a keyword `initialize` supplying defaults, and most carry a `.from` that passes an existing instance through and coerces a Hash with `transform_keys(&:to_sym)` — so a YAML- or JSON-sourced config loads cleanly.
+
+| Object | File | Role |
</file context>
Suggested change
All are `Data.define` with a keyword `initialize` supplying defaults, and most carry a `.from` that passes an existing instance through and coerces a Hash with `transform_keys(&:to_sym)` — so a YAML- or JSON-sourced config loads cleanly.
The immutable value objects (`NavItem`, `TopbarLink`, `DocVersion`, `SearchHit`, `ApiClient`, and `ApiRequest`) use `Data.define` with keyword initializers; `Shortcut`, `BrandLogo`, `SeoConfig`, and `LandingConfig` are plain classes, and most provide `.from` coercion for Hash-backed config.
Fix with cubic

Comment thread lode/plans/README.md
`lode/plans/YYYY-MM-DD-<slug>.md`. Use it only when the plan should not be public
or is a working draft that will become an issue.

`docs/plans/` does **not** exist in this repository, despite what the retired

@cubic-dev-ai cubic-dev-ai Bot Sep 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The claim that .claude/README.md said docs/plans/ exists doesn't hold against the current file: it only records /plan as retired in favour of /lode:plan and never mentions docs/plans/. Nothing else in the repo references docs/plans/. Since lode/ is meant to be precisely verifiable, either cite the historical text (e.g. the retired command's old description) or drop the .claude/README.md attribution.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lode/plans/README.md, line 13:

<comment>The claim that `.claude/README.md` said `docs/plans/` exists doesn't hold against the current file: it only records `/plan` as retired in favour of `/lode:plan` and never mentions `docs/plans/`. Nothing else in the repo references `docs/plans/`. Since `lode/` is meant to be precisely verifiable, either cite the historical text (e.g. the retired command's old description) or drop the `.claude/README.md` attribution.</comment>

<file context>
@@ -0,0 +1,21 @@
+`lode/plans/YYYY-MM-DD-<slug>.md`. Use it only when the plan should not be public
+or is a working draft that will become an issue.
+
+`docs/plans/` does **not** exist in this repository, despite what the retired
+`/plan` command and `.claude/README.md` said. `docs/` is the dogfood Rails app;
+nothing under it is a plan.
</file context>
Fix with cubic

Comment thread lode/workflow.md
|---|---|---|
| Client runtime | `app/javascript/docs_kit/controllers/docs_nav_controller.js` (the only one) | owned here — but it may only *enhance*. Never add a second controller; never make a page require it. |
| Components | `app/components/docs_ui/*.rb` (30 classes + `page_helpers.rb`) | owned here. New chrome is a `DocsUI::` component, never raw daisyUI markup, and it reads `DocsKit.configuration` rather than hardcoding. |
| AI surfaces | `lib/docs_kit/{markdown_export,blocks,inline,table,llms_text,search_index,search_hit,mcp_tools,mcp_server,open_api}.rb`, `app/controllers/docs_kit/*.rb` | owned here. A new consumer calls `LlmsText.pages` / `.renderable_for`; it does not re-derive the page list. |

@cubic-dev-ai cubic-dev-ai Bot Sep 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The AI-surfaces row lists lib/docs_kit/{blocks,inline,table}.rb, but those files live under lib/docs_kit/markdown_export/. An agent following this profile to find them would land on nonexistent paths. Fix the brace list to reflect the real locations: markdown_export/ for blocks/inline/table and search_index.rb/search_hit.rb flat.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lode/workflow.md, line 36:

<comment>The AI-surfaces row lists `lib/docs_kit/{blocks,inline,table}.rb`, but those files live under `lib/docs_kit/markdown_export/`. An agent following this profile to find them would land on nonexistent paths. Fix the brace list to reflect the real locations: `markdown_export/` for blocks/inline/table and `search_index.rb`/`search_hit.rb` flat.</comment>

<file context>
@@ -0,0 +1,136 @@
+|---|---|---|
+| Client runtime | `app/javascript/docs_kit/controllers/docs_nav_controller.js` (the only one) | owned here — but it may only *enhance*. Never add a second controller; never make a page require it. |
+| Components | `app/components/docs_ui/*.rb` (30 classes + `page_helpers.rb`) | owned here. New chrome is a `DocsUI::` component, never raw daisyUI markup, and it reads `DocsKit.configuration` rather than hardcoding. |
+| AI surfaces | `lib/docs_kit/{markdown_export,blocks,inline,table,llms_text,search_index,search_hit,mcp_tools,mcp_server,open_api}.rb`, `app/controllers/docs_kit/*.rb` | owned here. A new consumer calls `LlmsText.pages` / `.renderable_for`; it does not re-derive the page list. |
+| Registry + versions | `lib/docs_kit/{registry,doc_version,snapshot,nav_item}.rb` | owned here. `Registry`'s public API is what sites depend on — additive only. |
+| Config | `lib/docs_kit/configuration.rb` plus the value objects | owned here. A new knob ships with a default that renders nothing. |
</file context>
Suggested change
| AI surfaces | `lib/docs_kit/{markdown_export,blocks,inline,table,llms_text,search_index,search_hit,mcp_tools,mcp_server,open_api}.rb`, `app/controllers/docs_kit/*.rb` | owned here. A new consumer calls `LlmsText.pages` / `.renderable_for`; it does not re-derive the page list. |
| AI surfaces | `lib/docs_kit/markdown_export/{blocks,inline,table}.rb`, `lib/docs_kit/{llms_text,search_index,search_hit,mcp_tools,mcp_server,open_api}.rb`, `app/controllers/docs_kit/*.rb` | owned here. A new consumer calls `LlmsText.pages` / `.renderable_for`; it does not re-derive the page list. |
Fix with cubic

Comment thread lode/practices.md

## Degrade on the render path; raise at config time

A render must not take the site down for a config mistake that has a sensible fallback: an unresolvable Rouge theme name returns nil and falls back to the default (`Configuration#resolve_theme`, `configuration.rb:650-654`), an unreadable snapshot manifest reads back as an empty snapshot (`Snapshot#read_manifest`, `snapshot.rb:140-149`), an unreadable snapshot file renders as `""` (`Snapshot::Entry#markdown`), an unknown `params[:version]` resolves to the current version rather than 404ing (`Configuration#resolve_version`, `configuration.rb:391-393`).

@cubic-dev-ai cubic-dev-ai Bot Sep 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The citation Snapshot::Entry#markdown does not resolve in snapshot.rb: the file defines no Entry class and no #markdown method (only markdown_for at line 112). Every other claim in this file resolves to its cited location; this one will send a reader searching the wrong file. Point the citation at the file/method that actually implements the unreadable-file-returns-"" behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lode/practices.md, line 11:

<comment>The citation `Snapshot::Entry#markdown` does not resolve in `snapshot.rb`: the file defines no `Entry` class and no `#markdown` method (only `markdown_for` at line 112). Every other claim in this file resolves to its cited location; this one will send a reader searching the wrong file. Point the citation at the file/method that actually implements the unreadable-file-returns-`""` behavior.</comment>

<file context>
@@ -0,0 +1,49 @@
+
+## Degrade on the render path; raise at config time
+
+A render must not take the site down for a config mistake that has a sensible fallback: an unresolvable Rouge theme name returns nil and falls back to the default (`Configuration#resolve_theme`, `configuration.rb:650-654`), an unreadable snapshot manifest reads back as an empty snapshot (`Snapshot#read_manifest`, `snapshot.rb:140-149`), an unreadable snapshot file renders as `""` (`Snapshot::Entry#markdown`), an unknown `params[:version]` resolves to the current version rather than 404ing (`Configuration#resolve_version`, `configuration.rb:391-393`).
+
+The exceptions are deliberate and are all *configuration shape* errors, where there is nothing meaningful to degrade to: `c.topbar_brand` outside `TOPBAR_BRAND_MODES` raises `ArgumentError` at assignment (`configuration.rb:219`), `on_page` outside `ON_PAGE_MODES` raises in `coerce_on_page_mode` (`configuration.rb:535-543`), a malformed `c.brand_logo` raises on first read (`configuration.rb:352-356`), and `openapi_document` raises `DocsKit::Error` naming the knob when `c.openapi` is unset (`configuration.rb:443-451`). Loud at boot beats silently wrong in production.
</file context>
Fix with cubic

@@ -0,0 +1,53 @@
# AI surfaces — the Markdown twin, llms.txt, search, MCP

Four consumer-facing surfaces, all derived from the pages themselves so none can drift from the docs. `DocsKit::LlmsText.pages` is the shared enumeration seam and `DocsKit::MarkdownExport` is the shared rendering seam; every surface below is a thin skin over those two.

@cubic-dev-ai cubic-dev-ai Bot Sep 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The opening architecture claim incorrectly includes the OpenAPI bridge among the page-derived surfaces. Clarify that the four registry-backed AI surfaces share these seams, while OpenAPI remains spec-driven.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lode/ai-surfaces/summary.md, line 3:

<comment>The opening architecture claim incorrectly includes the OpenAPI bridge among the page-derived surfaces. Clarify that the four registry-backed AI surfaces share these seams, while OpenAPI remains spec-driven.</comment>

<file context>
@@ -0,0 +1,53 @@
+# AI surfaces — the Markdown twin, llms.txt, search, MCP
+
+Four consumer-facing surfaces, all derived from the pages themselves so none can drift from the docs. `DocsKit::LlmsText.pages` is the shared enumeration seam and `DocsKit::MarkdownExport` is the shared rendering seam; every surface below is a thin skin over those two.
+
+## `DocsKit::MarkdownExport` — the twin
</file context>
Suggested change
Four consumer-facing surfaces, all derived from the pages themselves so none can drift from the docs. `DocsKit::LlmsText.pages` is the shared enumeration seam and `DocsKit::MarkdownExport` is the shared rendering seam; every surface below is a thin skin over those two.
The Markdown twin and three registry-backed AI surfaces below derive from the pages themselves, so they cannot drift from the docs. `DocsKit::LlmsText.pages` is the shared enumeration seam and `DocsKit::MarkdownExport` is the shared rendering seam; the separate OpenAPI bridge is spec-driven.
Fix with cubic

Comment thread lode/workflow.md

- The manual check a user of the change would do: `cd docs && bin/dev`, open `/docs`, and look at the page the change touches — with JavaScript disabled as well as enabled. For a Markdown-twin or AI-surface change, also fetch `/docs/<slug>.md`, `/llms.txt` and `/docs/search?q=…`. For a generator change, run `rails g docs_kit:install` twice against a throwaway app and diff — the second run must change nothing.
- A CSS-affecting change (a new emitted class) needs `cd docs && bun run build:css` and a look at the built output; a class no source line spells literally will be missing.
- Stress iterations for a flake proof: 50 runs of the suspect spec file with `--seed` varied (`for i in (seq 50); bundle exec rspec <file> --seed $i; end`); for an ordering-dependent failure, reproduce the exact `--seed` CI printed first.

@cubic-dev-ai cubic-dev-ai Bot Sep 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The flake-stress command is fish-only, so it fails before running any specs in Bash. Use a POSIX loop with $(seq 50), do, and done instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lode/workflow.md, line 135:

<comment>The flake-stress command is fish-only, so it fails before running any specs in Bash. Use a POSIX loop with `$(seq 50)`, `do`, and `done` instead.</comment>

<file context>
@@ -0,0 +1,136 @@
+
+- The manual check a user of the change would do: `cd docs && bin/dev`, open `/docs`, and look at the page the change touches — with JavaScript disabled as well as enabled. For a Markdown-twin or AI-surface change, also fetch `/docs/<slug>.md`, `/llms.txt` and `/docs/search?q=…`. For a generator change, run `rails g docs_kit:install` twice against a throwaway app and diff — the second run must change nothing.
+- A CSS-affecting change (a new emitted class) needs `cd docs && bun run build:css` and a look at the built output; a class no source line spells literally will be missing.
+- Stress iterations for a flake proof: 50 runs of the suspect spec file with `--seed` varied (`for i in (seq 50); bundle exec rspec <file> --seed $i; end`); for an ordering-dependent failure, reproduce the exact `--seed` CI printed first.
+- Where evidence goes: `lode/tmp/` (gitignored, never committed) unless the PR needs an auditable trail.
</file context>
Suggested change
- Stress iterations for a flake proof: 50 runs of the suspect spec file with `--seed` varied (`for i in (seq 50); bundle exec rspec <file> --seed $i; end`); for an ordering-dependent failure, reproduce the exact `--seed` CI printed first.
- Stress iterations for a flake proof: 50 runs of the suspect spec file with `--seed` varied (`for i in $(seq 50); do bundle exec rspec spec/path_to_spec.rb --seed "$i" || exit $?; done`); for an ordering-dependent failure, reproduce the exact `--seed` CI printed first.
Fix with cubic

Comment thread lode/workflow.md
| one CI cell locally | `bundle exec rake` for the `rake` job; `cd docs && bundle exec rspec` for `docs-site` (needs a Playwright chromium: `cd docs && bunx --bun playwright install chromium`); `bundle config set --local without mcp && bundle install && bundle exec rspec` for `without-mcp` — **unset it afterwards** (`bundle config unset --local without`) or every later run hides the MCP specs | |
| docs build / check | `cd docs && bin/ci` (setup → rubocop → bundler-audit → `bin/importmap audit` → brakeman → rspec). `cd docs && bun run build:css` rebuilds the Tailwind/daisyUI CSS | always from inside `docs/` — it is a separate app with its own bundle, `.rubocop.yml`, `.rspec` and lockfiles. There is no `package.json` at the repo root. |
| run the app | `cd docs && bin/dev` (`bin/rails server`) | the dogfood site is the only runnable app in the repo |
| the scaffolder | `ruby exe/docs-kit --help` | `docs-kit new NAME [--image OWNER/REPO] [--service NAME] [--gem-source SRC]`; it shells out to `rails new`, so only run it for real in a throwaway directory |

@cubic-dev-ai cubic-dev-ai Bot Sep 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: ruby exe/docs-kit --help exits 1 because the CLI treats --help as an invalid command and only accepts new. Document a valid new NAME invocation or add a real help option before using this command as the scaffolder check.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lode/workflow.md, line 18:

<comment>`ruby exe/docs-kit --help` exits 1 because the CLI treats `--help` as an invalid command and only accepts `new`. Document a valid `new NAME` invocation or add a real help option before using this command as the scaffolder check.</comment>

<file context>
@@ -0,0 +1,136 @@
+| one CI cell locally | `bundle exec rake` for the `rake` job; `cd docs && bundle exec rspec` for `docs-site` (needs a Playwright chromium: `cd docs && bunx --bun playwright install chromium`); `bundle config set --local without mcp && bundle install && bundle exec rspec` for `without-mcp` — **unset it afterwards** (`bundle config unset --local without`) or every later run hides the MCP specs | |
+| docs build / check | `cd docs && bin/ci` (setup → rubocop → bundler-audit → `bin/importmap audit` → brakeman → rspec). `cd docs && bun run build:css` rebuilds the Tailwind/daisyUI CSS | always from inside `docs/` — it is a separate app with its own bundle, `.rubocop.yml`, `.rspec` and lockfiles. There is no `package.json` at the repo root. |
+| run the app | `cd docs && bin/dev` (`bin/rails server`) | the dogfood site is the only runnable app in the repo |
+| the scaffolder | `ruby exe/docs-kit --help` | `docs-kit new NAME [--image OWNER/REPO] [--service NAME] [--gem-source SRC]`; it shells out to `rails new`, so only run it for real in a throwaway directory |
+
+## Branches and PRs
</file context>
Suggested change
| the scaffolder | `ruby exe/docs-kit --help` | `docs-kit new NAME [--image OWNER/REPO] [--service NAME] [--gem-source SRC]`; it shells out to `rails new`, so only run it for real in a throwaway directory |
| the scaffolder | `ruby exe/docs-kit new NAME [--image OWNER/REPO] [--service NAME] [--gem-source SRC]` | it shells out to `rails new`, so only run it for real in a throwaway directory |
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant