T-UBKH-2283-cleanup-fold-its-logic-into-setup-resolution-retire
State: closed/done · Impact: low · Complexity: small
PR #2283’s dependency-bootstrap logic (pinned-bun
resolution + bun install --frozen-lockfile) moves into sdlc’s setup default resolution,
generalized to any node package manager; scripts/bootstrap-deps.sh goes, replaced by the
hooks:/sdlc apply machinery Phase 5 is building in parallel.
Approach
Section titled “Approach”- Setup resolution (done).
lib/config/pinned-manager.tsaddsreadPackageManagerPin(projectRoot)(parsespackage.json’spackageManager: "<pm>@<version>"field,nullunless the manager is one ofNODE_LOCKFILE_MANAGERS’ kinds) andresolvePinnedManagerInstall(candidate, projectRoot, opts)(the PATH →proto run <pm> --→env MISE_TRUSTED_CONFIG_PATHS=<root> mise exec -- <pm>ladder, stopping at the first version-matching executable, falling back to the first executable found at all with anecho ... >&2;-prefixed warning on mismatch; per-manager frozen-lockfile install argv,npm cineeds none).lib/config/verbs.ts’sdefaultSetupcalls it afterdetectDepsresolves a single candidate, so every consumer of thesetupdefault gets pinned resolution, not just this repo. Probing goes through the command seam (spawnRunner, injectable viarunnerForfor tests). Since moved topackages/ts/detect-runners/src/pinned-manager.tsbyT-6K39. - This repo’s config (done). sdlc.yaml now
has a
hooks:block (post-checkout/pre-commit, bothrun: setup/when: deps-missing,on_fail: warn/blockrespectively — matches the brief’s suggested grammar).verbs.setupwas not dropped: see the “default-layer discovery” note below for why, and its own updated comment in sdlc.yaml for the reasoning inline. Its value is nowbun install --frozen-lockfile(previously barebun install), matching what the pinned resolver would itself produce.lefthook.yml’s twodeps-bootstrapcommands carry thesdlc apply --dry-run-printed entries (see Lefthook entry below). - Delete
scripts/bootstrap-deps.sh(done) — removed in the same commit as thelefthook.ymlrewrite, since the old entries shelled to it. - Verify in a temp repo (done) — see Verification below.
default-layer discovery (why verbs.setup stays explicit)
Section titled “default-layer discovery (why verbs.setup stays explicit)”The orchestrator’s plan was to drop verbs.setup so resolveVerb('setup') falls through to the new
default-layer pinned logic. Checked empirically and it does NOT work in this repo:
defaultSetup’s heuristic tries a just/moon/mise task literally named setup/install/
bootstrap BEFORE ever trying detectDeps. This repo’s root justfile already has such a setup
recipe (mise trust && moon run :install, the whole-monorepo human-onboarding path). Verified via
@sksizer/detect-runners#collect:
- untrusted (
allowExec: false,justis parse-kind so always visible): exactly one match,just setup→defaultresolves to{ verbs: ["just setup"], source: "default" }— a full multi-language monorepo install, not the fast, TS/bun-scoped install task-work’s check verbs need. - trusted/
--trust(allowExec: true, moon’s own per-projectinstall/setuptasks also become visible): 16 matches (just setup+ 15moon run <project>:install) → ambiguous →defaultresolves to{ verbs: [], source: "none" }.
Either way default never reaches detectDeps/the pinned-manager path in THIS repo — dropping the
override would have swapped a fast, correct bun install for something far slower (untrusted) or
nothing at all (trusted). So verbs.setup stays as an explicit project-layer override, now
carrying the pinned form’s value directly. The pinned default-layer path IS exercised and correct
— confirmed against a clean temp repo with no just/moon/mise setup task (see Verification) — this
repo just isn’t a fit for triggering it via default given its own justfile.
Lefthook entry
Section titled “Lefthook entry”This step was blocked for a while: the auto-mode permission classifier denied agent edits to
lefthook.yml, so the original run stopped before it. A later session made the edit and
retired the script.
Each deps-bootstrap command’s run: is the entry sdlc apply --dry-run --trust prints,
including the inline command -v sdlc guard from inlineMissingSdlcGuard() in
lib/services/apply/hooks.ts:
pre-commit: commands: sdlc: run: 'command -v sdlc >/dev/null 2>&1 || { echo "sdlc: pre-commit: ''sdlc'' not found on PATH; skipping sdlc-managed hooks" >&2; exit 0; }; SDLC_CWD_DEV=0 sdlc hooks run pre-commit -- {0}'post-checkout: commands: sdlc: run: 'command -v sdlc >/dev/null 2>&1 || { echo "sdlc: post-checkout: ''sdlc'' not found on PATH; skipping sdlc-managed hooks" >&2; exit 0; }; SDLC_CWD_DEV=0 sdlc hooks run post-checkout -- {0}'The entries go under the existing deps-bootstrap command keys rather than sdlc. Lefthook runs
commands in name order, so the pre-commit one still sorts first, and the top-of-file
LEFTHOOK_EXCLUDE=deps-bootstrap escape hatch keeps working. The applier’s drift check
(content.includes('sdlc hooks run ${hook}')) matches on substring, not on the command name, so
the key name is free.
Missing-sdlc trade-off
Section titled “Missing-sdlc trade-off”A machine with no sdlc on PATH gets a command -v sdlc guard that warns to stderr
and exits 0 — lefthook treats that as a passing command (pre-commit has no on_fail override at
the lefthook level, so this is what keeps a missing-sdlc machine from BLOCKING the commit) —
missing sdlc warns, doesn’t block; deps genuinely missing (with sdlc present) still blocks
pre-commit per sdlc.yaml’s on_fail: block. Separately,
packages/ts/cli-tool/devlink/launcher.ts’s “no node_modules — run ‘bun install’” message (from
#2283) is a DIFFERENT backstop, for sdlc itself
failing to boot in a deps-less worktree — unrelated to this trade-off, left untouched.
| Area | Note |
|---|---|
solutions/ontological/lib/config/pinned-manager.ts | New: pin parsing + executable-selection ladder |
solutions/ontological/lib/config/pinned-manager.test.ts | New: unit tests |
solutions/ontological/lib/config/verbs.ts | defaultSetup now calls resolvePinnedManagerInstall |
sdlc.yaml | verbs.setup → bun install --frozen-lockfile; new hooks: block |
lefthook.yml | deps-bootstrap commands run sdlc hooks run <hook> |
scripts/bootstrap-deps.sh | Deleted |
Verification
Section titled “Verification”bun test solutions/ontological/lib/config— 276 pass (includes the 18 newpinned-manager.test.tscases).bun x tsc --noEmit -p .— clean.bun test solutions/ontological scripts --timeout 30000 <path-ignore flags>(thecheckverb’s line) — 3810 pass, 1 skip, 0 fail.bun test ./.claude— 71 pass.sdlc docs generate --check— clean, no drift. Pre-commit suite (lefthook run pre-commit --all-files) — exit 0.resolveVerb('setup', <this repo>, { trust: false|true })→{ verbs: ["bun install --frozen-lockfile"], source: "project" }in both cases (see thedefault-layer discovery note above for whysourceisproject, notdefault, here).sdlc apply --dry-run --trustagainst this repo, after thelefthook.ymlchange: nohooks:drift reported. A rebase in the worktree ran the newpost-checkoutentry (deps-bootstrappassed throughsdlc hooks run).- Temp-repo integration (step 4), a throwaway
git initrepo withpackage.json {packageManager: "bun@1.4.2", dependencies: {left-pad: "1.3.0"}}, a realbun.lock, andsdlc.yaml’s newhooks:block copied in, trusted viasdlc repo trust:node_modulespresent,sdlc hooks run post-checkout -- prev new 1→ exit 0, no-op.node_modulesremoved, same command →bun installruns,node_modulesreappears, exit 0 (matcheson_fail: warn’s never-block contract).node_modulesremoved,sdlc hooks run pre-commit --with a genuinely invalidbun.lock(a stand-in for “setup fails”) →sdlc hooks: pre-commit blocked by \setup` (exited 1), exit 1 — confirmson_fail: blockactually blocks with a clear sdlc-level message, on top of the underlying command's own stderr (hooks/run.tsprintsresult.stderrverbatim before its own line). Note: could NOT independently exercise "truly no bun/proto/mise anywhere" in this sandbox —sdlcitself needsbun` on PATH to run at all here — so this is the closest available proxy for that scenario; the failure-propagation mechanism it exercises is the same one that scenario would hit.
Acceptance criteria
Section titled “Acceptance criteria”- AC-3: Pinned-package-manager resolution (PATH/proto/mise ladder, frozen-lockfile flag,
mismatch warning) lives in
lib/config/, is generic (not bun/this-repo-specific), and is unit-tested with a fake command runner. - AC-4: This repo’s
sdlc.yamlcarries thehooks:block and a pinnedverbs.setup; thedefault-layer non-engagement is understood and documented, not silently wrong. - AC-1: scripts/bootstrap-deps.sh no longer exists.
- AC-2: A fresh worktree’s setup verb still bootstraps deps equivalently via
sdlc.yamlhooks + lefthook entries. Verified in a temp repo and live in this repo’slefthook.yml(see Verification).
Out of scope
Section titled “Out of scope”- Rewriting this repo’s
sdlc.yamlverbs.setupto rely on thedefaultlayer — thejustfilecollision documented above makes that actively worse here, not merely undone. - Changing
defaultSetup’s heuristic ordering (e.g. preferringdetectDepsover a same-named just/moon task) to makedefaultengage here — a real option, but a shared-cascade design change outside this task’s scope; flagged for the orchestrator to decide on separately.
Dependencies
Section titled “Dependencies”None remaining.