Skip to content

sdlc v0.4.0 release checklist

State: closed/archived · Genre: checklist

The first real push of sdlc through the publishing channel (“Publishing sdlc: assemble, not snapshot”). Everything below is inert prose until a human runs it — nothing here executes automatically. This covers Phases 1-3 of the sdlc plan (PR #2284) only; P5/P6 are out of scope for this release.

Retargeted 2026-09-26: this release now ships to sksizer/sdlc, sdlc’s own self-hosted single-plugin marketplace and product home, not sksizer/marketplace-sdlc — see D-P3NF-sksizer-sdlc-is-product-home. sksizer/marketplace-sdlc PR #25 — the earlier attempt at weaving sdlc into that pre-existing multi-plugin catalog — is closed, not merged. sksizer/marketplace-sdlc itself is untouched and stays in service as a separate catalog; it is simply no longer sdlc’s target. scripts/sdlc-mirror/assemble.ts now has two layouts: --layout product (this release — replaces the whole target tree, the default for a target whose origin is sksizer/sdlc) and --layout marketplace (the original surgical merge, preserved for a target actually shaped like marketplace-sdlc). Every command below has been updated for the new target; anywhere this file still says “the mirror” or “the checkout,” read sksizer/sdlc.

Read .sdlc/runs/p14-install-evidence.md first. That file is both the template for what “validation evidence” means below and the record of two blockers a real release could not proceed past at the time it was written (Findings A and B, §5’s (c) and (d) — both fixed since). An opus adversarial review pass afterward found two more real gaps in assemble.ts and the publish workflow (§5’s (e) and (f)); both are fixed as of this writing too, but §5 is still worth reading before a real release — it says what was verified and how, not just “fixed.” (b) below predates the retarget and describes a marketplace-sdlc-specific concern (another plugin’s content living alongside sdlc’s in that repo) that does not apply to --layout product’s sksizer/sdlc target, which sdlc owns outright — see the note at the top of (b).

  • Version 0.4.0 everywhere: solutions/ontological/package.json, the sdlc.md plugin manifest’s ap-version: frontmatter, CHANGELOG.md, and every rendered plugin manifest agent-pants build produces. This is already the version on main — Phase 14’s own version-bump work (#2331) landed it as prep, ahead of the actual release (see the wrinkle this causes in §2 before running anything).
  • Base: Phases 1-3 only, on top of origin/sdlc/p3-vault. Nothing from P5 (sdlc/p5-config — config CLI, hooks:, sdlc apply, removal of plugin_source) or P6 (host code) is in this release.
  • Mirror tag grammar (per PUBLISHING.md’s “Tag grammar”): monorepo tag sdlc/v0.4.0, mirror tag v0.4.0 (bare, no sdlc- prefix — confirmed against the actual publish-lib.yml push step, which pushes v${VERSION}). Some earlier campaign notes call the mirror tag sdlc-v0.4.0; that was shorthand for “the v0.4.0 release of sdlc”, not a literal git ref — go with what’s actually implemented. This grammar is unchanged by the retarget: it names a tag shape, not a repo.
  • #2296, #2312, and this plan’s own Phase 14 PR (#2331) are merged to main.
  • The retarget PR (this one — --layout product, the roster/docs/decision updates below) is reviewed and merged to main.
  • Local CI green on main, post-merge: bun run solutions/ontological/cli/sdlc.ts quality run --config sdlc.yaml --trust. Verified against sdlc.yaml’s verbs.check list and quality run --help: this one command already runs everything in check — including bun test solutions/ontological scripts ... and bun test ./.claude — so there is no separate “run the tests” step; --name check is the default, no need to pass it explicitly.
  • solutions/ontological/package.json reads 0.4.0 on main.
  • bun run solutions/ontological/scripts/build-node.ts --check passes (exit 0) on main — this is exactly the kind of drift (cli/sdlc.js silently going stale behind a skill-script move) that can reappear without warning; sdlc.yaml’s check verb runs it, but it’s worth a direct look before a real release regardless.
  • No stale local sdlc/v0.4.0 tag pointing anywhere but the intended release commit (git tag -l 'sdlc/v0.4.0', and if present, git ls-remote --tags origin 'sdlc/v0.4.0' to confirm it isn’t already on origin before deleting a local one with git tag -d) — release-lib.ts’s “tag free locally” preflight fails otherwise.
  • A human has read §5 in full and either fixed each item or explicitly accepted the risk in writing (e.g. a comment on the release PR).
  • gh auth status shows a token with repo scope covering sksizer/sdlc (public repo — write access needed to push the branch and open the PR in §2a).

Two things happen somewhat independently and can run in either order, but assemble against the real sksizer/sdlc checkout first — reviewing that diff is the whole reason this release doesn’t just let CI’s tag-triggered pipeline do everything unattended (see §5b). Unlike every other release on this channel (and unlike sdlc’s own releases from v0.5.0 on, per §2b), this first push to sksizer/sdlc is a PR, not a direct branch push — it replaces the repo’s entire pre-existing (Python-era) tree, which deserves a human’s review on the mirror side too, not just a diff read locally.

2a. Assemble into sksizer/sdlc, with human review before a PR

Section titled “2a. Assemble into sksizer/sdlc, with human review before a PR”
Terminal window
# Fresh clone dedicated to this release — NEVER a real local clone of
# sksizer/sdlc reused across runs (assemble.ts refuses that path outright
# via checkProtectedCheckout; this workflow must never touch one either).
git clone https://github.com/sksizer/sdlc.git /tmp/sdlc-release
cd <this monorepo checkout, on main, post-merge>
bun run scripts/sdlc-mirror/assemble.ts /tmp/sdlc-release --layout product --target-is-scratch

--layout product is also what assemble.ts defaults to for a target whose origin remote is sksizer/sdlc (resolveLayout) — passing it explicitly here is just belt-and-suspenders, matching what CI’s own invocation does. --target-is-scratch is required either way: /tmp/sdlc-release’s origin resolves to the now-protected sksizer/sdlc coordinate, so checkProtectedCheckout refuses it without that flag, exactly as it already refused a marketplace-sdlc clone before the retarget.

This packs the same dependency closure and runs the same standalone validation (bun install per harness, skill-script smoke, agent-pants check, node-only probe) as --layout marketplace always has — see docs/PUBLISHING.md. What’s different under --layout product: instead of a === SURGICAL MERGE REPORT === block naming only packs/sdlc/ and the "sdlc" manifest entry, the assembler removes every existing top-level path in the target except .git (tracked paths via git rm, untracked cruft via a plain filesystem delete — never a history rewrite, just an ordinary working-tree change the next commit records) and replaces it with the rendered plugin trees for all three harnesses plus a root README.md/LICENSE/.gitignore/CHANGELOG.md. Run --dry-run first (... --layout product --dry-run) to see exactly what would be removed and added before committing to anything:

  • A human reads the --dry-run output (or, on a real run, the removed/added file lists the assembler logs) and git -C /tmp/sdlc-release status / git -C /tmp/sdlc-release diff --stat before doing anything else. Confirm the only surviving pre-existing path is .git itself — everything else in the diff should be either a deletion of the old Python-era tree (python/, tests/, pyproject.toml, .github/workflows/ — the old CI, gone with everything else — justfile, vision.md, …) or an addition this assembler is expected to make. This is real, irreversible-once- merged content replacing a public repo’s entire tree — see §4 for what “irreversible” actually means here.

  • Command exits 0. See §5 for the leftover-path-gate false positives a real run hit historically (all fixed) — worth knowing the shape of a real failure here versus a new one.

  • Capture the same categories of evidence .sdlc/runs/p14-install-evidence.md captures: the dependency-closure list, the removed/added path lists (this layout’s equivalent of the merge report), the 3x bun install results, and the node-only probe output for --version plus at least two relocated ops (pick different ones than any already exercised in a rehearsal, for fresh coverage) — in all 3 harnesses. Save it as .sdlc/runs/sdlc-0.4.0-release-evidence.md (gitignored by default under .sdlc/* — force-add it the same way .sdlc/runs/p14-install-evidence.md and .sdlc/runs/p2.md are force-tracked: git add -f).

  • Once satisfied with the diff, open a PR instead of pushing straight to main — this first push replaces the whole pre-existing tree, so it gets the same review a normal PR would, even though nobody but this pipeline authored the branch:

    Terminal window
    cd /tmp/sdlc-release
    git checkout -b release/v0.4.0
    git add -A
    git commit -m "release: sdlc v0.4.0 (bun/node) — replaces the Python-era tree"
    git push -u origin release/v0.4.0
    gh pr create --repo sksizer/sdlc --base main --head release/v0.4.0 \
    --title "release: sdlc v0.4.0 (bun/node) — replaces the Python-era tree" \
    --body "First public release of the bun/node sdlc rewrite. Assembled by \
    scripts/sdlc-mirror/assemble.ts --layout product from sksizer/dev \
    (solutions/ontological). See docs/planning/notes/N-EAYX-sdlc-0-4-0-release-checklist.md \
    and docs/planning/decisions/D-P3NF-sksizer-sdlc-is-product-home.md."

    bun run scripts/sdlc-mirror/assemble.ts /tmp/sdlc-release --layout product --pr prints the equivalent commands for the current version without running any of them. Its branch step adapts to the checkout (no step when already on release/v<version>, git switch when the branch exists, git switch -c otherwise), and its PR body names the source sksizer/dev commit instead of this checklist’s path.

  • Someone reviews and merges the PR on GitHub before continuing to §2b. This is the actual gate: nothing past this point should happen until that merge lands.

Because solutions/ontological/package.json is already 0.4.0 on main (the version bump was done as prep work inside the Phase 14 branch, not reserved for release time), just release-lib sdlc <bump> --push cannot be used in its normal form — verified directly:

$ bun run solutions/ontological/scripts/release-lib.ts sdlc 0.4.0 --dry-run
error: explicit version 0.4.0 must be greater than current 0.4.0
$ bun run solutions/ontological/scripts/release-lib.ts sdlc patch --dry-run
release-lib: sdlc 0.4.0 -> 0.4.1 (tag sdlc/v0.4.1) # wrong version
$ bun run solutions/ontological/scripts/release-lib.ts sdlc minor --dry-run
release-lib: sdlc 0.4.0 -> 0.5.0 (tag sdlc/v0.5.0) # also wrong

release-lib.ts’s nextVersion requires the target to be strictly greater than the manifest’s current version — there is no “tag the current version, nothing to bump” mode. Since there’s nothing left to bump (the manifest already reads 0.4.0, and bun.lock/tests were already exercised by the Phase 14 PR’s own gate), the equivalent by hand, once main has the merge commit and §1’s preconditions are all green, and once §2a’s PR has merged on the sksizer/sdlc side:

Terminal window
git checkout main && git pull --ff-only
# confirm package.json really is 0.4.0 and the tree is clean before tagging
git tag -a sdlc/v0.4.0 -m "release(sdlc): v0.4.0"
git push origin sdlc/v0.4.0

This tag lives on this repo (sksizer/dev) and is purely informational here — it does not, by itself, push anything to sksizer/sdlc (§2a already did that, by hand, as a PR). This is a decision point, not just a mechanical workaround: an alternative is to teach release-lib.ts an explicit “already at target, tag only” path (out of scope here — release-lib.ts is on the “do not modify” list for this task). For releases after 0.4.0, prefer NOT pre-bumping the version inside a feature branch, so release-lib.ts’s normal patch/minor/major bump can do the tagging itself the way every other library on this channel works — and, per docs/PUBLISHING.md, from v0.5.0 on this repo’s own sdlc/vX.Y.Z tag push is what fires publish-lib.yml’s normal tag-triggered flow against sksizer/sdlc directly (no PR — a straight branch push, same as every other library), since by then sksizer/sdlc holds only sdlc’s own prior releases, not foreign Python-era content that needs a human’s eyes on the replacement.

If OSS_PUBLISH_TOKEN is configured as a repo secret, pushing this tag also fires publish-lib.yml in CI, which will redundantly try the assemble lane again against sksizer/sdlc — harmless for v0.4.0 specifically, since by the time this tag is pushed §2a’s PR has already merged and git tag v${VERSION} on CI’s own fresh clone will fail with “tag already exists” (§2a’s commands above push v0.4.0 to the mirror only once the PR merges, which CI’s automated push would then find already there) — but confirm this is understood rather than confusing on the day.

  • gh release list --repo sksizer/sdlc shows nothing new — this release does not create a GitHub Release, only a tag (matches every other library on this channel).
  • The mirror’s own .github/workflows/release.yml for the npm binary is not wired up yet (see Finding in §5a) — do not expect an npm publish to happen from this tag push. That’s deliberately a separate, later step once the release-wiring gap is closed. Note that the old Python-era .github/workflows/ in sksizer/sdlc is gone as of this release (§2a’s tree replacement) — there is currently no CI at all on sksizer/sdlc until that gap closes.
  • Spot-check the mirror manually: gh repo clone sksizer/sdlc /tmp/sdlc-verify -- --depth 1 and confirm the single sdlc plugin installs and runs (repeat a subset of §2a’s node-only probe against this fresh clone, not the release checkout that already has extra staging artifacts in its working tree).

Because §2a is a PR, most of the usual “irreversible push” risk is already mitigated before it ever reaches main on sksizer/sdlc: closing the PR without merging is a full rollback with zero cleanup. Once merged, the mirror is a normal git repo, pushed to with an ordinary fast-forward merge — never force-pushed, matching this channel’s own convention (PUBLISHING.md: “The mirror gets one squash commit per release” — this first push is the one exception in shape, a whole-tree replacement rather than a snapshot of one directory, but the same “never force-push” rule applies). That means:

  • Before the PR merges: close it. Nothing on sksizer/sdlc’s main changes; the release/v0.4.0 branch can be deleted or left alone.
  • Before anyone has pulled/installed the bad release, but after the PR merged: git revert the merge commit on sksizer/sdlc’s main and push normally (a new commit, not a force-push), then delete and re-push the tag if it must point somewhere else (git push origin :refs/tags/v0.4.0 && git tag v0.4.0 <good-commit> && git push origin v0.4.0 — only safe because nothing (no npm publish exists yet per §5a) has consumed that tag downstream). Note that reverting this particular merge commit restores the OLD Python-era tree, not an empty repo or a previous sdlc release — there is no previous sdlc release on this repo to fall back to.
  • If sdlc’s own monorepo tag sdlc/v0.4.0 was pushed prematurely or wrong: same idea — git push origin :refs/tags/sdlc/v0.4.0 then re-tag once fixed. This repo’s tags are informational triggers, not artifacts anyone external depends on directly.
  • What is NOT possible: unpublishing an npm package version once published (moot until §5a’s gap closes and a first npm publish actually happens — at that point, follow npm’s own unpublish window rules, which this checklist does not repeat).

5. Known open gaps — read before touching any command above

Section titled “5. Known open gaps — read before touching any command above”

One gap, (a), predates this checklist and is still open. Five more — (c) and (d) (Findings A and B, from the install-evidence file), (e) and (f) (from a later opus adversarial review), and (g) (also from that review, but in agent-pants rather than assemble.ts/the publish workflow) — were found by verification passes and are now fixed. Read what each one actually was before assuming “fixed” means “was never a real risk”:

(a) Release-wiring gap (pre-existing, still open). assemble.ts only vendors sdlc’s rendered plugin-pack surface (the three harness roots under --layout product, or packs/sdlc/ under --layout marketplace) — it does not vendor sdlc’s buildable CLI source (solutions/ontological’s package.json, lib/, scripts/, cli/) that build-binary.ts/ build-npm-wrapper.ts need to cross-compile the npm binaries. The mirror-side release template at scripts/sdlc-mirror/templates/release.yml assumes that source tree lands in the mirror at the same solutions/ontological/ relative path it has today, but nothing in the pipeline ships it there. Net effect: even after §2 pushes the plugin correctly, npm install @sksizer/sdlc will not work until this gap is closed — that template is not copied into sksizer/sdlc’s .github/workflows/ yet specifically because of this open question. See that template’s own header comment for the full reasoning. The retarget to sksizer/sdlc (D-P3NF-sksizer-sdlc-is-product-home) makes this gap easier to close later (its own Migration section tracks moving sdlc’s real source there — T-3WLL), but does not close it now: v0.4.0 still ships the vendored-tarball closure, not native source.

(b) Stale-5-pack human sign-off — DOES NOT APPLY to the sksizer/sdlc target. This finding was about sksizer/marketplace-sdlc’s real main, which carries 5 plugins (authoring, codebase, demo, repo, review) that predate this pipeline and are NOT sdlc’s to touch — see mergeSdlcPackIntoTarget’s surgical-merge behavior, still exactly as described below, for that target. It is preserved here verbatim because it records a real, hard-won finding about agent-pants build’s sweep behavior that remains true for --layout marketplace. For this release, sksizer/sdlc under --layout product owns its entire target tree outright — there is no foreign plugin’s content to protect, and (e) below’s scratch-build-then-copy structure exists for a different reason under this layout (isolating the render from the target, not protecting a sibling plugin). If a future release ever needs --layout marketplace again (e.g. against the real marketplace-sdlc), this paragraph’s original concern still applies there in full:

sksizer/marketplace-sdlc’s real main today carries 5 plugins (authoring, codebase, demo, repo, review) that predate this pipeline. Earlier drafts of this checklist assumed an assemble run would delete all 5 automatically and that a human’s only job was to sign off on that deletion before the push. That assumption no longer holds, and it was never actually safe: (e) below found that at least some content under those 5 pack directories belongs to a different, unrelated plugin (dev-agent), not to a stale sdlc split — so an automatic “vault doesn’t produce it, therefore delete it” sweep across the whole harness tree (which is what a raw agent-pants build --out <target> does) could have deleted someone else’s live plugin. --layout marketplace never attempts this: it only ever touches packs/sdlc/ and the "sdlc" manifest entry, and reports every other pack directory it finds as “left untouched” for a human to triage separately. What a human must still do, as its own manual step, outside assemble.ts, before EVER running --layout marketplace against the real marketplace-sdlc again: look at what is actually left under authoring/codebase/demo/repo/review in a real checkout, work out which parts (if any) are genuinely stale sdlc leftovers safe to delete by hand and which belong to dev-agent, and do that cleanup — if any — as its own separate, reviewed commit.

(c) Finding A — leftover-path gate false-positives on cli/sdlc.js (fixed). assemble.ts’s gate (scanForLeftoverPaths) failed on 8 hits inside the built cli/sdlc.js bundle in every harness, every time, unconditionally (before --skip-validate was even checked), blocking every real run. Full reproduction and root-cause analysis in .sdlc/runs/p14-install-evidence.md, Finding A; what was fixed and how the gate was hardened further during the adversarial-review fix round is described above, in §2a’s checklist item for this same command — not repeated here. This gate is layout-independent — it scans whatever ends up under HARNESS_PACK_DIRS regardless of how it got there — so this fix applies equally to --layout product.

A third, emergent instance of the same class of false positive surfaced after that hardening merged with a parallel fix round that added a second bun build --target=node output, lib/services/docs/list-ops.js (for sdlc docs generate running installed, no bun) — the two branches never saw each other’s diff, so the gate’s exemption list only covered the bundle that existed when it was written. A real assemble run caught it immediately (543 hits, all in list-ops.js, the same confirmed-inert categories as Finding A). Fixed by generalizing the exemption from “cli/sdlc.js specifically” to “any bundle-node-entry.ts output” — see scripts/sdlc-mirror/assemble.ts’s NODE_BUNDLE_RELPATHS.

(d) Finding B — the committed cli/sdlc.js was stale (fixed). Independent of Finding A: the currently-committed solutions/ontological/cli/sdlc.js was built once (371db2097) and never rebuilt after the CLI-ops migration commits (19b5cce54, d574f63c8, 070b9d00a, 87f1775fc) that moved the 8 skill scripts behind CLI ops. build-node.ts --check confirmed it was stale; the bundle contained none of the 8 relocated ops (verified directly — e.g. grep -c dedup-search solutions/ontological/cli/sdlc.js → 0). Full reproduction in .sdlc/runs/p14-install-evidence.md, Finding B. Fixed in a65ad2c19, which rebuilt all 4 committed copies (solutions/ontological/cli/sdlc.js and the 3 pre-rendered pack copies). Nothing in today’s quality gate would have caught this drift on its own at the time; that gap is closed now too — build-node.ts --check and build-list-ops.ts --check are both wired into sdlc.yaml’s check verb list, so a future stale-bundle regression fails local/CI checks directly instead of waiting on this checklist’s §1 precondition to catch it.

(e) Assembler could have clobbered an unrelated plugin’s content (found by an opus adversarial review, fixed) — the origin of --layout marketplace’s scratch-build-then-merge design. assemble.ts used to run agent-pants build --out <target> directly against the real mirror checkout. agent-pants build treats a harness’s WHOLE tree (everything under its one marketplace manifest — every pack, not just sdlc’s) as owned by whichever vault built it, and sweeps anything under there the vault doesn’t currently produce as “stale,” including a whole foreign plugin directory and its manifest entry. That is exactly right for a tree only sdlc has ever built — which is precisely what a --layout product target like sksizer/sdlc is, so under that layout applyProductLayout copies the scratch build’s harness roots into the target wholesale, no per-file merge needed — and exactly wrong for a real marketplace-sdlc checkout, which — per this same review — holds at least some content belonging to a different plugin (dev-agent) alongside sdlc’s. Verified by hand: a scratch checkout seeded with a foreign dev-agent pack lost that pack and its manifest entry entirely after a direct agent-pants build --out <target> run. Fixed, for --layout marketplace, by building sdlc’s pack into an isolated scratch directory first, then merging only packs/sdlc/ and only the "sdlc" manifest entry into the real target (mergeSdlcPackIntoTarget in assemble.ts) — every other pack directory and manifest entry is left byte-for-byte untouched, and both --dry-run and a real run report exactly what was touched vs. left alone. Regression-tested against a constructed foreign-pack fixture in scripts/sdlc-mirror/assemble.test.ts. Human review of a real assemble run’s --dry-run/report output is still recommended before the real push — the fixture tests prove the mechanism works, not that a specific future real checkout holds nothing this reasoning hasn’t anticipated. The same review pass also found and closed a related gap: several packages/ts/* packages in sdlc’s vendored dependency closure (color, manifest-graph, markdown-util, system-model) shipped with no package.json files allowlist, so their packed tarballs relied on npm’s unaudited default of “everything not gitignored.” All four now declare one, and assemble.ts fails the assemble outright (or warns loudly under --dry-run) if a future closure package is missing one.

(f) OSS_PUBLISH_TOKEN exposed to untrusted install scripts during assembly (found by the same review, fixed). .github/workflows/ publish-lib.yml’s “Clone mirror for assembly” step embedded OSS_PUBLISH_TOKEN into the clone URL whenever the secret was configured, even though that checkout only ever needs read access — the token then sat in .git/config for the rest of the job, reachable by anything running inside that checkout, including third-party install scripts from sdlc’s vendored dependency closure that assemble.ts’s own standalone validation installs. Fixed two ways: that clone is now always anonymous (the mirror repos are public; the one step that actually needs write access re-points origin at the authenticated URL immediately before it pushes, as it already did), and the standalone-validation bun install now runs against a manifest that declares an explicit trustedDependencies allowlist (exactly node-pty, the one dependency in the closure with a load-bearing install script — see TRUSTED_CLOSURE_INSTALL_SCRIPTS in assemble.ts), so bun blocks every other dependency’s install/postinstall script by default instead of running whatever the closure happens to contain. This CI clone step now also passes --target-is-scratch (added alongside the retarget, since the protected-checkout guard covers sksizer/sdlc the same way it always covered sksizer/marketplace-sdlc — CI’s fresh clone would otherwise be refused).

(g) Codex/Cursor skill prose called the CLI through a Claude-only token — install-breaking on both harnesses (found by the same opus review, fixed). Every skill in the vault writes its CLI call site the same way, as literal prose: ${CLAUDE_PLUGIN_ROOT}/cli/sdlc <verb> .... agent-pants copied that text unchanged into all three rendered packs. ${CLAUDE_PLUGIN_ROOT} is a Claude Code plugin token, substituted only by Claude Code itself when it loads skill/command/agent Markdown (Claude Code’s plugin manifest reference, “Environment variables” section: the token resolves in skill/command/agent content specifically, with no counterpart documented for another harness). Neither Codex CLI’s nor Cursor’s own skills documentation mentions any such substitution — Cursor’s instead instructs authors to “reference scripts in your SKILL.md using relative paths from the skill root.” An unset ${CLAUDE_PLUGIN_ROOT} expands to nothing, so every Codex/Cursor skill that called the CLI resolved the token to an empty prefix and failed outright (/cli/sdlc: No such file or directory) — reproduced directly, both before and after the fix, in .sdlc/runs/p14-assemble-evidence.log’s dated “per-harness CLI invocation fix” entry. Fixed in agent-adapt-core’s render (solutions/agent-pants/crates/agent-adapt-core/src/export.rs): a new per-harness CliInvocation on RootSpec leaves Claude’s text untouched (Claude Code substitutes the token itself) and rewrites a Codex/Cursor call site to the skill-relative scripts/sdlc shim instead — config-driven off the existing shim_target (T-VAW3 AC-3), not a hardcoded string. The shim itself is now emitted only where it can matter: into a Codex or Cursor pack, and only for a skill whose rendered body actually contained a call site to rewrite (a Claude pack never gets it at all; a skill that never calls the CLI doesn’t either, even on Codex/Cursor). Verified against all three harnesses’ actual rendered packs, copied outside the repo with bun hidden from PATH, running real task-work/pr-respond/orchestrate command lines exactly as their SKILL.md writes them, from each skill’s own directory — see .sdlc/runs/p14-assemble-evidence.log for the full transcript and a negative-control run proving the pre-fix failure was real.


← Back to Notes