T-F9TG-sdlc-dev-runtime-selector
Status: closed/done · Impact: medium · Complexity: medium
One sdlc on PATH that you can point at a live source checkout (to run
against a peer or itself) and switch back to the local/published install —
a runtime selector, the rustup/nvm model for sdlc itself, and the CLI twin
of T-ZEPK-harness-install-dev-link-mode (which selects which plugin the
skills load). Implements D-0014 point 12’s deferred SDLC_HOME override
and layers an ergonomic toggle on top.
The cli/sdlc launcher (T-ITMN-skills-normalize-sdlc-launcher)
resolves runtime/entry purely from its own location — built sdlc.js
sibling → Node, else sdlc.ts → Bun. There is no way to run one
PATH-installed sdlc against a different checkout, nor to toggle
source↔published.
| Location | Role today |
|---|---|
plugin/cli/sdlc | Bash launcher: [ -f "$here/sdlc.js" ] → node sdlc.js else bun run sdlc.ts; no override hook |
plugin/skills/orchestrate/watch_loop.sh | Already honors an SDLC_CLI env override — precedent for an env-selectable launcher |
plugin/lib/util/runtime.ts | moduleExt/runtimeExec; the runtime primitives |
Proposed
Section titled “Proposed”The launcher gains an override layer; a dev noun manages it.
Resolution order: SDLC_HOME env → $XDG_CONFIG_HOME/sdlc/home
config → local ($here). When the resolved home is a valid checkout
other than $here, the launcher execs that checkout’s entry
(bun run <home>/plugin/cli/sdlc.ts) directly — never its launcher (no
loop). The persistent config redirect applies only when
CLAUDE_PLUGIN_ROOT is UNSET (terminal CLI), so a global selector never
silently changes which code a peer’s skills run; SDLC_HOME (explicit)
always wins.
Approach
Section titled “Approach”- Launcher (
plugin/cli/sdlc) — prepend the override:home="${SDLC_HOME:-}"; if empty ANDCLAUDE_PLUGIN_ROOTis unset,home="$(cat "${XDG_CONFIG_HOME:-$HOME/.config}/sdlc/home" 2>/dev/null || true)".- If
homenon-empty, resolves to a real dir withplugin/cli/sdlc.ts, and is not the same checkout as$here→exec bun run "$home/plugin/cli/sdlc.ts" "$@"(with a clear error ifbunis absent). Else fall through to today’s local resolution.
- New
devservice (plugin/lib/services/dev/ops/), runtime-agnostic (node:fs/os/path,process.env), mirroring theharnessservice:sdlc dev use <path>— validate<path>is an sdlc checkout (hasplugin/cli/sdlc.ts); write its absolute path to<config>/sdlc/home; report whatsdlcnow runs.sdlc dev off— remove<config>/sdlc/home; report thatsdlcreverts to the local/published launcher on PATH.sdlc dev status— report the resolved source in order:SDLC_HOMEenv (if set) / config value /local (<built|source> at <path>); note whether asdlcshim is on PATH and whether the config redirect is suppressed (CLAUDE_PLUGIN_ROOT set).sdlc dev link [--bin-dir <dir>]— symlink the running launcher into<dir>(default${XDG_BIN_HOME:-$HOME/.local/bin}) assdlc; idempotent; warn if<dir>is not onPATH.
<config>=${XDG_CONFIG_HOME:-$HOME/.config}— resolved identically in bash and TS. Provide a sharedconfigHome()helper in the dev service.- Tests: stub
HOME/XDG_CONFIG_HOMEto a temp dir.usewrites the config + validates the path;offclears it;statusreports each resolution branch;linkcreates the symlink idempotently. A bash-level test of the launcher’s override branch (stub a fakehomecheckout, assert it execs that entry) is a bonus. - README: the dev-mode workflow (
dev linkonce →dev use <checkout>→ run against a peer →dev offto return to the PATH install), and theCLAUDE_PLUGIN_ROOT-suppression note.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/cli/sdlc | modify | SDLC_HOME/config override branch (suppressed when CLAUDE_PLUGIN_ROOT set) |
plugin/lib/services/dev/ops/use.ts | new | dev use <path> — write config |
plugin/lib/services/dev/ops/off.ts | new | dev off — clear config |
plugin/lib/services/dev/ops/status.ts | new | dev status — report resolution |
plugin/lib/services/dev/ops/link.ts | new | dev link — install the PATH symlink |
plugin/lib/services/dev/ops/_common.ts | new | configHome(), homeConfigPath(), checkout validation |
plugin/lib/services/dev/tests/ | new | op tests (stubbed HOME/XDG) |
plugin/cli/tests/goldens/ + sdlc.test.ts | modify | re-snapshot for the new dev noun |
README.md | modify | the dev-mode selector workflow |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
sdlc dev use <checkout>writes the abs path to$XDG_CONFIG_HOME/sdlc/home(validated as an sdlc checkout); a subsequentsdlc <verb>(invoked via the launcher withCLAUDE_PLUGIN_ROOTunset) execs that checkout’ssdlc.ts— proven by a launcher test that stubs a fakehomeand asserts the chosen entry. - AC-2:
sdlc dev offremoves the config;sdlcreverts to the local resolution ($here). - AC-3:
SDLC_HOME=<path> sdlc …overrides the config (env wins), and applies even whenCLAUDE_PLUGIN_ROOTis set; the persistent config is suppressed whenCLAUDE_PLUGIN_ROOTis set. - AC-4:
sdlc dev statusreports the active source and its origin (env / config / local) and whether a shim is on PATH. - AC-5:
sdlc dev linkcreates<bin-dir>/sdlc→ the running launcher, idempotently, and warns when<bin-dir>is not on PATH. - AC-6:
sdlc --helplists thedevnoun; goldens re-snapshotted;bunx tsc --noEmit,bun test plugin/lib/services/dev plugin/cli/tests, andgate runtime-agnosticall clean.
Out of scope
Section titled “Out of scope”- A separately-recorded published target (
dev use --publishedauto-detecting a brew/npm install): todayoff= the launcher on PATH, which is published once a published version is installed. Recording an explicit published target is a nicety for when a release channel (T-75UD) or compiled binary exists. - Multi-path deployment (brew/compiled binary) — the follow-up decision.
- The derived-plugin transform pipeline (D-0014 point 4).
Dependencies
Section titled “Dependencies”- Builds on T-ITMN-skills-normalize-sdlc-launcher (the launcher it extends) and is the CLI sibling of T-ZEPK-harness-install-dev-link-mode. No blocking deps.