go-ldd: extract a language-neutral core and generate the plugin directory - #27
Open
buzzdan wants to merge 16 commits into
Open
go-ldd: extract a language-neutral core and generate the plugin directory#27buzzdan wants to merge 16 commits into
buzzdan wants to merge 16 commits into
Conversation
Add tools/ldd-gen, a small Go module that renders core/ (language-neutral templates) plus lang/<lang>/ (profile, includes, overrides, passthrough) into the plugin directory the marketplace serves. In this first step the whole Go plugin is passthrough under lang/go/passthrough/, so the rendering is a copy and `ldd-gen -check` passes by construction. Later commits move one group of files at a time into core/ while the check stays green. Also add the root go.work (so `go run ./tools/ldd-gen` works from the repo root without a root go.mod), the Taskfile tasks generate, check, test-gate and test-gen, the CI workflow that runs them, core/README.md as the templating contract, and a CLAUDE.md note that the plugin directory is generated and must not be edited by hand. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The four agents and scripts/check-repo-brain.sh now render from core/. rule-hunter substitutes the source glob in its worked example; the overabstraction skeptic is already language-free and moves verbatim; the comment critic includes its directive list; the lint fixer substitutes the default lint-fix command and includes its mechanical-issue list, routing table and hard limits. The gate keeps its driver in core and includes the Go adapter block, marker lines included, from lang/go/scripts/. The fixture matrix (check-repo-brain_test.sh) stays passthrough: besides the conformant fixture it embeds Go in several individual cases, so a split would need many includes. It is listed for the second language binding. `task check` stays green and the differential run of the matrix against the previously committed gate reports zero differences. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Each rule keeps Principle, Why, Design guidance, Fix pattern and the section headings in core/. The Canonical example body and the Falsifying questions body (after the one-line lead-in) move to lang/go/rules/R<n>/ and come back through includes. These four rules need no other substitution. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Same split as the previous commit. R9's monorepo note names the project marker file, which becomes the profile scalar; nothing else in these six rules' portable sections is language-specific. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… an include Same split as the earlier rule commits. R6 substitutes the test-file glob in its Fix pattern. R10's Design guidance bullet that names the linter config and its concurrency linters moves to an include, since it is tooling text rather than a design principle. These two rules are the ones a second language is expected to override wholesale, so they land last and alone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…o core Skill references substitute the plugin name; the refactoring skill's four tooling blocks (routing table, file and package routing, testing integration, nolint prohibition) become includes, and its reference.md is portable as it stands. The review skill substitutes the diff glob and the project marker and includes its nolint finding and critic prefilter command. The design skill substitutes the language name and includes its linter triggers, layout-scan example and package-structure sketch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
linter-driven-development substitutes the language name, command prefix, plugin name, project marker and default test command; its pre-flight steps that name project files and tools, and the package-scoped lint line, become includes. maxims.md is portable as written: the Go proverbs it quotes are attributed quotes, not Go-only guidance. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…locks are includes The objective (with the language name substituted), when-to-use, philosophy and composition ladder are portable and stay in core. The quick start, reusable infrastructure, the three workflows, key patterns, output format, checklists and success criteria name testify, httptest, build tags and Go test commands throughout, so each block becomes one include. reference.md and examples/ stay passthrough: they are a Go harness catalogue. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The edge line's comment prefix becomes a profile scalar, and the sentence that says how an edge is verified and which files the gate covers becomes an include. The remaining Go words (godoc, doc.go, testable examples) are inline idioms left for the second language binding to resolve. skills/documentation/reference.md stays passthrough: its toolbox is portable but its worked examples are Go code, and the seam is recorded with the residue in the last commit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Each command file is named core/commands/{{.CmdPrefix}}-<name>.md and renders
to commands/go-ldd-<name>.md; every slash-command mention and Skill
reference is a template scalar. The analyze command's project-discovery and
file-scope blocks, and wire-repo-brain's language-scope paragraph and edge
verification note, become includes; quickfix and review substitute the
default test and lint commands.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…entry `ldd-gen lint-core` scans core/ for language-specific text. Hard residue (plugin-name literal, linter name, source glob, nolint directive) fails the command; none remains. Soft residue (nil, ctx, goroutine, interface, godoc and friends) is counted per token and per file and written between markers into core/README.md by `task lint-core`, which CI runs and which fails when the committed section is stale. core/README.md also lists what stays in the binding and why. The root README's development steps describe the generate-and-commit flow, docs/generator.md explains the generator and its checks, and the plugin CHANGELOG gains an Unreleased entry under Internal: the one intentional change to the generated directory in this series. The plugin version stays 2.10.0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Fixes from the pre-merge review, none changing the rendered plugin: - A missing core/ is refused up front instead of rendering an empty tree that would delete every templated plugin file; a walk root that does not exist is an error, and a repository with no binding fails -check. - profile.Parse rejects a plugin name that is not a plain directory name and malformed ignore patterns, so a typo can neither point the generator at the wrong directory nor switch the ignore list off. Patterns without a slash match a name anywhere; the Go profile ignores .DS_Store. - Overrides carry their own executable bit, and an override with no core file to replace is an error. Ignored directories are never removed, even when empty. The non-empty-directory check uses errno values, not an error string. - The residue report counts distinct lines, sorts hits with a stable tie-break, names bare plugin and command-prefix literals as hard residue, and lists Go linter and library names as soft residue. - The Taskfile variable is BINDING, not LANG, which the shell locale would override; CI runs the generator tests from the module directory (the root pattern is rejected under go.work) and adds the docs gate; -check with -lang is an error; a failed diff command is reported, not hidden. - Falsifying-question includes start on their first line; the blank line lives in the core template, as core/README.md says. - Comments and docs follow the review: plain words, no overstated claims. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t files The plugin name check now rejects any separator, the root, and hidden names, and Generate refuses to write unless the rendering carries .claude-plugin/plugin.json and the target directory either holds that manifest too or holds nothing the generator would delete. A wrong plugin name in a profile can no longer delete other parts of the repository. Files whose name starts with a dot are skipped as sources, so a .DS_Store under core/ or a passthrough directory no longer fails the render. An override that exists but cannot be read is an error instead of a silent fall back to the core file. Taskfile descriptions say "on disk" like the rest of the docs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… to inputs Generate now refuses to write when two bindings name the same plugin directory, when the rendered manifest has no name, or when the target directory's manifest names a different plugin. Files inside the target that the rendering owns count as touched even when ignored, so a foreign directory that happens to hold such a path is refused too. The input walk skips only the names the profile ignores (.DS_Store), not every hidden file, so a binding can ship files like .mcp.json. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… -check The input walk skips a fixed list of editor and OS droppings (.DS_Store, ._*, *.swp, *~, .#*, Thumbs.db, desktop.ini) plus any name the profile ignores without a slash, for files and whole directories alike, so an open editor or Finder cannot break or pollute a rendering while real hidden plugin files such as .mcp.json still render. -check now also fails when two bindings name the same plugin. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The evals clone lives below the repository root, so the root go.work would otherwise stop the runner module from building. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Phase 1.5 of the go-ldd roadmap: the Go plugin's source now lives in two places, and the directory the marketplace serves is generated from them.
core/holds the language-neutral text: rules, skills, agents, commands and the repo-brain gate, as Gotext/templatefiles with two constructs only (profile scalars and{{include "..."}}).lang/go/is the Go binding:profile.yaml(11 scalars plus an ignore list), the include snippets (canonical examples, falsifying questions, routing tables, Go-specific blocks), andpassthrough/for files copied through unchanged.tools/ldd-gen(its own Go module; the root gets ago.work, not ago.mod) renders a binding into its plugin directory, checks a directory against its rendering, and reports language residue left incore/.go-linter-driven-development/is now generated and must not be edited by hand.CLAUDE.md, the root README anddocs/generator.mdsay so;core/README.mdis the templating contract and carries the generated Residue section.Proof that nothing moved
The generated directory differs from
mainby exactly one file: the CHANGELOG entry under Unreleased (9 lines). Every commit in the series kepttask checkgreen. The differential run of the gate's fixture matrix againstmain's script reports zero differences. Plugin version stays 2.10.0.Checks
task checktask lint-coretask docs:checkgo test ./...intools/ldd-gengolangci-lint runintools/ldd-gentask test-gatesed; unchanged frommain, passes on Linux)CI (
.github/workflows/generate-check.yml, the repo's first workflow) runs all of these on every pull request.Review rounds
Three review rounds ran before this PR was opened (rule hunters, skeptic, comment critic, and the semantic reviewers on the whole diff, then again on each fix commit). Findings fixed: a missing
core/rendered as empty and would have deleted the plugin; a malformed ignore pattern switched the ignore list off; the Taskfile variableLANGcollided with the shell locale;go test ./tools/...from the root is rejected undergo.work;plugin: /or another plugin's name passed validation and would have wiped the target; the residue tables counted hits as lines; the empty-directory check compared an error string. The generator now refuses to write anywhere but its own plugin directory.What stays in the binding for now
skills/documentation/reference.md,scripts/check-repo-brain_test.sh, the testing reference and examples, the worked case studies, and the plugin's own README, CHANGELOG, manifest and hooks are passthrough.core/README.mdlists why for each.Not in this PR
The behavioral eval re-run (cheap and medium tiers against the generated plugin, compared with the recorded baseline) costs real money and runs only after explicit approval.
🤖 Generated with Claude Code