Repository navigation
ci(guard): fail when a non-bundled @claude-flow/* leaf changes without a publish - #3391
Merged
Merged
Conversation
…t a publish Adds scripts/audit-leaf-package-publish.mjs, wired into v3-ci.yml's existing audit job (semver is already installed there). Only four @claude-flow/* components are bundled into the release tarballs — INTERNAL_RUNTIME_PACKAGES in stage-internal-runtime-bundles.mjs: security, codex, mcp, plugin-agent-federation. cli-core, neural, shared and memory resolve from the REGISTRY at whatever v3/@claude-flow/cli/package.json pins, so a merged source change in one of those does NOT ship with the three-package train: the release silently carries the last published copy. This has bitten twice. #3334 fixed RUFLO_INTELLIGENCE_MODE in @claude-flow/memory; v3.42.1 shipped the train only and a fresh consumer install had zero references to the fix (#3335). #3390's reasoningBank activation spanned cli/src/memory/memory-bridge.ts AND memory/src/controller-registry.ts — publishing only the train would have shipped the bridge half and left the embedder half unreachable, leaving reasoningBank exactly as inert as before. It was caught by hand and memory@3.0.0-alpha.25 went out alongside v3.42.5. What makes it easy to miss is that CI goes GREEN: the leaf's own package tests run against workspace source, so the change looks verified while being unshippable. The audit asserts: 1. every non-bundled leaf's workspace version is covered by the CLI's declared range (a miss means the release resolves to OLDER code than this repo's source); 2. diff mode — if a leaf's publishable source changed vs the base ref but its version did not, fail with remediation. It imports INTERNAL_RUNTIME_PACKAGES rather than restating it, so the bundled and non-bundled sets cannot drift apart; exits non-zero if it inspects zero leaves, so a broken classifier can't report clean; and reports a skipped diff check as NOT EXERCISED rather than as a pass. @claude-flow/shared is entered as a time-boxed accepted finding (expires 2026-10-21): the CLI pins 3.0.0-alpha.7 while the workspace is at 3.0.0-alpha.8, which IS published — stale, not unshippable. Raising the pin edits v3/@claude-flow/cli deps and so forces a pnpm-lock.yaml regen in the same change (#2540 -> #2552), which wants its own PR. An EXPIRED entry fails the guard rather than muting it. Negative-controlled, all four paths: - memory source changed, no bump -> FAIL (replays #3390) - same change with a version bump -> pass - test-only change in a leaf -> pass (not publishable) - waiver expiry backdated -> FAIL, "no longer honoured" Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The guard's first real CI run passed but reported: NOTE: diff check NOT EXERCISED — could not diff against 'origin/main' (shallow clone?) So only the range check ran; check 2 — the half that replays #3335/#3390 by catching a leaf source change with no version bump — sat out entirely. The NOT EXERCISED note is what made that visible instead of reading as a pass, which is precisely why a skipped check must never print like a clean one. Two causes, both fixed: - `git fetch --depth=1 origin $GITHUB_BASE_REF` fetches the base tip with no shared history, so `base...HEAD` dies with "fatal: no merge base". Now fetches --depth=50 with an explicit +refs/heads/<base>:refs/remotes/origin/<base> refspec. - The script only attempted three-dot. It now falls back to two-dot, which needs no common ancestor, and reports which mode actually ran. Verified against the exact failing condition — an orphan branch with no merge base against origin/main, where `git diff origin/main...HEAD` returns "fatal: no merge base": the guard now reports "diff check ran (two-dot ...)" rather than skipping. NC1 re-confirmed: a leaf source change without a version bump still fails, now accompanied by "diff check ran (three-dot ...)" proving the check executed rather than being skipped into a pass. Co-Authored-By: Claude Opus 5 (1M context) <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.
Why
Only four
@claude-flow/*components are bundled into the release tarballs —INTERNAL_RUNTIME_PACKAGESinstage-internal-runtime-bundles.mjs:security,codex,mcp,plugin-agent-federation. Everything else the CLI depends on —cli-core,neural,shared,memory— resolves from the registry at whateverv3/@claude-flow/cli/package.jsonpins.So a merged source change in one of those does not ship with the three-package train. The release silently carries the last published copy of that leaf.
This has bitten twice:
RUFLO_INTELLIGENCE_MODE(#3334) never reaches consumers —@claude-flow/memoryleaf package not republished #3335 — feat(sona): default the learning mode from RUFLO_INTELLIGENCE_MODE #3334 fixedRUFLO_INTELLIGENCE_MODEin@claude-flow/memory. v3.42.1 shippedcli/claude-flow/rufloonly;memorywas last published weeks earlier, so a fresh consumer install had zero references to the fix.cli/src/memory/memory-bridge.tsandmemory/src/controller-registry.ts. Publishing only the train would have shipped the bridge half and left the embedder half unreachable, leavingreasoningBankexactly as inert as before. Caught by hand;memory@3.0.0-alpha.25went out alongsidev3.42.5.What makes it easy to miss: CI goes green. The leaf's own package tests run against workspace source, so the change looks verified while being unshippable.
What the guard asserts
versiondid not — fail, with remediation.Design choices that matter:
INTERNAL_RUNTIME_PACKAGESinstead of restating it, so the bundled/non-bundled sets cannot drift apart. A hardcoded copy would go stale the moment someone adds a package to the staging script — and then wave through the exact bug this exists to catch.NOT EXERCISED, never a silent pass.Accepted finding
@claude-flow/sharedis entered as a time-boxed waiver (expires 2026-10-21): the CLI pins3.0.0-alpha.7while the workspace is3.0.0-alpha.8, which is published — stale, not unshippable. Raising the pin editsv3/@claude-flow/clideps and therefore forces apnpm-lock.yamlregen in the same change (#2540 → #2552 — frozen-lockfile otherwise fails ~25 jobs), so it wants its own PR. An expired entry fails the guard rather than muting it.Negative controls
A guard that cannot fail is worthless, so all four paths were exercised:
memorysource changed, no version bump🤖 Generated with Claude Code