Skip to content

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.

  1. Setup resolution (done). lib/config/pinned-manager.ts adds readPackageManagerPin(projectRoot) (parses package.json’s packageManager: "<pm>@<version>" field, null unless the manager is one of NODE_LOCKFILE_MANAGERS’ kinds) and resolvePinnedManagerInstall(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 an echo ... >&2;-prefixed warning on mismatch; per-manager frozen-lockfile install argv, npm ci needs none). lib/config/verbs.ts’s defaultSetup calls it after detectDeps resolves a single candidate, so every consumer of the setup default gets pinned resolution, not just this repo. Probing goes through the command seam (spawnRunner, injectable via runnerFor for tests). Since moved to packages/ts/detect-runners/src/pinned-manager.ts by T-6K39.
  2. This repo’s config (done). sdlc.yaml now has a hooks: block (post-checkout/pre-commit, both run: setup/when: deps-missing, on_fail: warn/block respectively — matches the brief’s suggested grammar). verbs.setup was 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 now bun install --frozen-lockfile (previously bare bun install), matching what the pinned resolver would itself produce. lefthook.yml’s two deps-bootstrap commands carry the sdlc apply --dry-run-printed entries (see Lefthook entry below).
  3. Delete scripts/bootstrap-deps.sh (done) — removed in the same commit as the lefthook.yml rewrite, since the old entries shelled to it.
  4. 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, just is parse-kind so always visible): exactly one match, just setup → default resolves 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-project install/setup tasks also become visible): 16 matches (just setup + 15 moon run <project>:install) → ambiguous → default resolves 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.

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.

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.

AreaNote
solutions/ontological/lib/config/pinned-manager.tsNew: pin parsing + executable-selection ladder
solutions/ontological/lib/config/pinned-manager.test.tsNew: unit tests
solutions/ontological/lib/config/verbs.tsdefaultSetup now calls resolvePinnedManagerInstall
sdlc.yamlverbs.setup → bun install --frozen-lockfile; new hooks: block
lefthook.ymldeps-bootstrap commands run sdlc hooks run <hook>
scripts/bootstrap-deps.shDeleted
  • bun test solutions/ontological/lib/config — 276 pass (includes the 18 new pinned-manager.test.ts cases). bun x tsc --noEmit -p . — clean.
  • bun test solutions/ontological scripts --timeout 30000 <path-ignore flags> (the check verb’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 the default-layer discovery note above for why source is project, not default, here).
  • sdlc apply --dry-run --trust against this repo, after the lefthook.yml change: no hooks: drift reported. A rebase in the worktree ran the new post-checkout entry (deps-bootstrap passed through sdlc hooks run).
  • Temp-repo integration (step 4), a throwaway git init repo with package.json {packageManager: "bun@1.4.2", dependencies: {left-pad: "1.3.0"}}, a real bun.lock, and sdlc.yaml’s new hooks: block copied in, trusted via sdlc repo trust:
    • node_modules present, sdlc hooks run post-checkout -- prev new 1 → exit 0, no-op.
    • node_modules removed, same command → bun install runs, node_modules reappears, exit 0 (matches on_fail: warn’s never-block contract).
    • node_modules removed, sdlc hooks run pre-commit -- with a genuinely invalid bun.lock (a stand-in for “setup fails”) → sdlc hooks: pre-commit blocked by \setup` (exited 1), exit 1 — confirms on_fail: block actually 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.
  • 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.yaml carries the hooks: block and a pinned verbs.setup; the default-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.yaml hooks + lefthook entries. Verified in a temp repo and live in this repo’s lefthook.yml (see Verification).
  • Rewriting this repo’s sdlc.yaml verbs.setup to rely on the default layer — the justfile collision documented above makes that actively worse here, not merely undone.
  • Changing defaultSetup’s heuristic ordering (e.g. preferring detectDeps over a same-named just/moon task) to make default engage here — a real option, but a shared-cascade design change outside this task’s scope; flagged for the orchestrator to decide on separately.

None remaining.


← Back to Tasks