diff --git a/.claude/skills/new-setup/SKILL.md b/.claude/skills/new-setup/SKILL.md index ba62242..f08fd16 100644 --- a/.claude/skills/new-setup/SKILL.md +++ b/.claude/skills/new-setup/SKILL.md @@ -38,12 +38,14 @@ One permitted parallelism: ### 1. Node bootstrap -If the probe reported `STATUS: unavailable` (Node isn't installed yet), install Node 22 **before** running `bash setup.sh` — otherwise the first bootstrap run is guaranteed to fail and you'll pay for it twice: +Check probe results and skip if `HOST_DEPS=ok` — Node, pnpm, `node_modules`, and `better-sqlite3`'s native binding are already in place. + +If the probe reported `STATUS: unavailable` (Node isn't installed yet — probe itself couldn't run), install Node 22 **before** running `bash setup.sh`, otherwise the first bootstrap run is guaranteed to fail: - macOS: `brew install node@22` - Linux / WSL: `curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs` -Then run `bash setup.sh`. If the probe reported any other status, run `bash setup.sh` directly — it's idempotent and verifies host deps + native modules. +Then run `bash setup.sh`. If the probe succeeded but `HOST_DEPS=missing`, run `bash setup.sh` directly — Node is there, deps aren't. Parse the status block: diff --git a/setup/probe.mjs b/setup/probe.mjs index a28f759..5aea0d4 100644 --- a/setup/probe.mjs +++ b/setup/probe.mjs @@ -268,6 +268,22 @@ function probeInferredDisplayName() { return 'User'; } +function probeHostDeps() { + const nodeModules = path.resolve(process.cwd(), 'node_modules'); + if (!fs.existsSync(nodeModules)) return 'missing'; + // better-sqlite3's compiled native binding is the canonical proof that + // `pnpm install` ran AND the native build step succeeded. Cheaper than + // actually loading the module, and unambiguous on success. + const nativeBinding = path.join( + nodeModules, + 'better-sqlite3', + 'build', + 'Release', + 'better_sqlite3.node', + ); + return fs.existsSync(nativeBinding) ? 'ok' : 'missing'; +} + function probeTimezone() { const envTz = readEnvVar('TZ'); const systemTz = Intl.DateTimeFormat().resolvedOptions().timeZone || ''; @@ -309,6 +325,7 @@ export async function run() { const serviceStatus = probeServiceStatus(); const displayName = probeInferredDisplayName(); const tz = probeTimezone(); + const hostDeps = probeHostDeps(); const [onecliStatus, cliAgentWired] = await Promise.all([ probeOnecliStatus(oneCliUrl), @@ -323,6 +340,7 @@ export async function run() { emitStatus('PROBE', { OS: osLabel, SHELL: shell, + HOST_DEPS: hostDeps, DOCKER: docker.status, IMAGE_PRESENT: docker.imagePresent, ONECLI_STATUS: onecliStatus,