Skip to content

refactor!: builder runs without Python (Go port, milestone 2) - #29

Merged
Lutherwaves merged 11 commits into
feat/gofrom
feat/go-m2
Oct 26, 2026
Merged

Lutherwaves merged 11 commits into
feat/gofrom
feat/go-m2

Conversation

@Lutherwaves

Copy link
Copy Markdown
Owner

Milestone 2 of the Go port (spec: docs/superpowers/specs/2026-10-25-go-port-and-cleanup-design.md, plan: docs/superpowers/plans/2026-10-26-go-port-m2.md). It targets feat/go, not main: a git install runs main HEAD, and no release has the binary yet.

Every caller now runs the Go binary through bin/builder, and the Python is gone.

  • The session hook and the opencode plugin run bin/builder hook. With no binary, the hook prints the soul, exits 0, writes nothing on stderr, in under 1 s. With a broken PATH, it still gives the full text: the shim needs only Bash builtins.
  • The budget, reach and security skills and their eval allow rules name "${CLAUDE_PLUGIN_ROOT}/bin/builder" <command>. The budget skill finds an old watcher job that runs burn-proj.py and offers to replace it.
  • skills/harness/setup.sh merges .mcp.json with jq or builder mcp-merge. mcp-merge now keeps key order and exact numbers, byte-identical to the jq path.
  • The eval runner uses jq and the binary; the parity config block is a dev-only Go command, cmd/parity-config. Its path check follows os.path.realpath, which closes a hole where $HOME/missing/.. passed the guards.
  • The template test is Go (tests/templates_test.go).
  • Deleted: every .py outside the eval sample projects, the Python unittests, tests/test_parity.sh, tests/parity/.
  • scripts/validate.sh needs Go, builds the binary once, and fails on a tracked .py, a python3 word, an old tool path, or a skill that runs the shim without the quoted plugin root. A failed build is one error.

Proof: bash scripts/validate.sh passes; go test -count=3 ./... passes; tests/test_validate.sh 0 failures; a fake python3 first on PATH is never called; the hook ran end to end with a versioned binary under a temp BUILDER_HOME (45 ms) and with an empty one (soul only).

Evals not run: the paid runs for budget, reach, security and security-audit would prove the new allow patterns match what Claude Code allows. Run them before feat/go merges to main.

Calls made on the way:

  • feat/go caught up with main by a merge (chore: bring feat/go up to main), no force push.
  • The two milestone 1 fixes (builtins-only shim, ordered mcp-merge) land here, because this milestone makes both paths live.
  • The template test is excluded from the old-name guard, since it pins burn-proj.py on purpose.

Milestone 3 must: ship scripts/install.sh (the shim's message names it); fix README.md:55 and docs/install.md:3, which still say Python 3 is needed; check the shim on macOS Bash 3.2.

🤖 Generated with Claude Code

Lutherwaves and others added 11 commits October 26, 2026 16:28
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
BREAKING CHANGE: the builder binary is required; milestone 3 adds its installer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Lutherwaves
Lutherwaves merged commit d07db42 into feat/go Oct 26, 2026
2 checks passed
Lutherwaves added a commit that referenced this pull request Oct 27, 2026
* docs: plan for Go port milestone 2

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(shim): bin/builder runs with a broken PATH

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(mcpmerge): keep a .mcp.json's key order and numbers

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(hook): the session hook and the opencode plugin run bin/builder

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(harness): setup.sh merges .mcp.json with jq or the builder binary

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): budget, reach and security run bin/builder

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(templates): the template and report checks in Go

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(evals): the eval runner and parity setup without Python

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor!: builder runs without Python

BREAKING CHANGE: the builder binary is required; milestone 3 adds its installer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(validate): exclude the template test from the old-name guard

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(validate): a failed build is one error, and the budget README pastes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lutherwaves added a commit that referenced this pull request Oct 27, 2026
… merge releases it (Go port) (#30)

* refactor!: builder runs without Python (Go port, milestone 2) (#29)

* docs: plan for Go port milestone 2

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(shim): bin/builder runs with a broken PATH

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(mcpmerge): keep a .mcp.json's key order and numbers

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(hook): the session hook and the opencode plugin run bin/builder

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(harness): setup.sh merges .mcp.json with jq or the builder binary

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): budget, reach and security run bin/builder

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(templates): the template and report checks in Go

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(evals): the eval runner and parity setup without Python

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor!: builder runs without Python

BREAKING CHANGE: the builder binary is required; milestone 3 adds its installer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(validate): exclude the template test from the old-name guard

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(validate): a failed build is one error, and the budget README pastes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: plan for Go port milestone 3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(shim): bin/builder saves the install answer and says when the binary is off

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(shim): install-answer fails when it cannot save and works with a broken PATH

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(install): scripts/install.sh installs the checked builder binary

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(install): pin redirect protocols, hash from stdin, reject an empty --version

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(hook): the session offers to install the missing binary

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(hook): read the saved answer time as decimal

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): a skill stops at a missing binary and offers the install

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* ci: a merge to main releases the builder binaries, as matchblox does

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(ci): the release pull request rebases onto main, so no merge commit fails the subject check

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: the README and the install guide name the helper binary

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): a skill stops at exit 3 only when the binary is not installed or is off

reach matchblox-notify exits 3 on its normal no-route path, so the rule now keys on the shim's message too.Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(ci): the version pull request merges only after publish verified the release

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: RELEASING says the first release has no previous binary, and what to do if it stalls

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: builder offers to install its helper binary

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(tests): the shim, offer and install tests pass on macOS, where /var is a symlink

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(hook): an unreadable manifest offers no install, and a stored yes says how to stop retrying

The shim exits 4, not 3, when it cannot read the plugin version, so the
hook shows the soul and the root line without an install block that could
never succeed. The yes block names install-answer later for a failed
install. The snooze has a named constant. Tests cover a plugin with no
version and a binary that prints and fails.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(install): the installer trusts only GitHub over https, and names any other source

curl takes https only, also on a redirect, and gives up after a 10 s
connect or 300 s in all. A BUILDER_RELEASE_URL may also be file://; the
result line names it, and it writes no yes to install.answer. A signed-in
gh with no attestation command reports the provenance as not checked, with
the reason. A missing manifest and an unwritable install.answer each print
only the clean line on stderr.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(ci): releases come only from main's verified commit, queue behind the last bump, and fail loudly

Publish refuses a tag whose commit is not on main. The release job pushes
the tag through push-tag.sh, which refuses a tag that exists on another
commit, lightweight or annotated. check-bump-merged.sh stops a release while
the previous release's bump pull request is open. The publish wait, a
failed auto-merge, a closed pull request and the 40-minute wait now fail the
job, and merging=true is set only once auto-merge is on. bump-version.sh
takes the same versions as release.yml. RELEASING.md adds the tag ruleset
step and a row for each new refusal. Tests cover the new scripts and the
negative paths of promote-changelog and check-next-version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* ci: validate runs actionlint for real, and the macOS job times out after 10 minutes

actionlint v1.7.12 installs through the Go checksum database, so
validate.sh lints the workflows instead of skipping them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(ci): a release that never published holds no later release back, and a foreign tag stops the job before a draft

check-bump-merged.sh compares the plugin with the newest tag whose release
is published and verified, and skips a tag whose release is a draft, a
pre-release or missing. A broken release no longer blocks the fixed one.
push-tag.sh --check runs before gh release create, so a tag on another
commit leaves no draft for publish.yml to adopt. The publish main check
uses the origin/main of the full checkout. RELEASING.md says how to clear
a foreign tag and what to do when no bump pull request exists.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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