Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions .claude/hooks/session-start.sh
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,46 @@
#!/bin/bash
# SessionStart hook for Claude Code on the web.
# The app is engine-strict on Node 24.x / npm 11.x, but web containers ship an
# older Node on PATH, so nothing installs or runs until Node 24 is present.
# Installs Node 24 into $HOME/.node24 (cached with the container), exposes it
# via $CLAUDE_ENV_FILE, and installs npm dependencies.
# The app is engine-strict on Node >=24.15 <25 / npm 11.x, but web containers
# ship an older Node on PATH, so nothing installs or runs until a Node meeting
# that floor is present. Installs one into $HOME/.node24 (cached with the
# container), exposes it via $CLAUDE_ENV_FILE, and installs npm dependencies.
set -euo pipefail

if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
exit 0
fi

NODE_VERSION="24.13.0"
NODE_VERSION="24.19.0"
# Keep in step with the floor in package.json engines.node. A matching major is
# not enough: dev dependencies (jsdom) carry a minor-level floor, so a 24.13 on
# PATH satisfied the old major-only check and then failed `npm ci` with
# EBADENGINE. That blocked PRs #1611, #1697, #1705 and #1740.
NODE_MINIMUM="24.15.0"
# Exclusive major ceiling, matching the "<25" half of engines.node. Checking only
# the floor would let a container shipping Node 25+ skip provisioning and then
# fail `npm ci`, which is the same blind-spot as the major-only check above.
NODE_MAJOR_CEILING="25"
NODE_HOME="$HOME/.node24"
NODE_BIN="$NODE_HOME/node-v${NODE_VERSION}-linux-x64/bin"

current_major="$(node -v 2>/dev/null | sed -E 's/^v([0-9]+).*/\1/' || echo 0)"
if [ "$current_major" != "24" ] && [ ! -x "$NODE_BIN/node" ]; then
echo "[session-start] Installing Node ${NODE_VERSION} (found v${current_major:-none})"
supported_runtime() {
local version="$1"
local actual_major actual_minor actual_patch minimum_major minimum_minor minimum_patch
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 1
IFS=. read -r actual_major actual_minor actual_patch <<< "$version"
IFS=. read -r minimum_major minimum_minor minimum_patch <<< "$NODE_MINIMUM"

(( actual_major < NODE_MAJOR_CEILING )) || return 1
(( actual_major > minimum_major )) && return 0
(( actual_major == minimum_major )) || return 1
(( actual_minor > minimum_minor )) && return 0
(( actual_minor == minimum_minor )) || return 1
(( actual_patch >= minimum_patch ))
}

current_version="$(node -v 2>/dev/null | sed -E 's/^v//' || true)"
if ! supported_runtime "$current_version" && [ ! -x "$NODE_BIN/node" ]; then
echo "[session-start] Installing Node ${NODE_VERSION} (found v${current_version:-none}, need >= ${NODE_MINIMUM} and < ${NODE_MAJOR_CEILING})"
mkdir -p "$NODE_HOME"
curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" \
| tar -xJ -C "$NODE_HOME"
Expand All@@ -27,6 +51,10 @@ if [ -x "$NODE_BIN/node" ]; then
echo "export PATH=\"$NODE_BIN:\$PATH\"" >> "$CLAUDE_ENV_FILE"
fi

if ! supported_runtime "$(node -v 2>/dev/null | sed -E 's/^v//' || true)"; then
echo "[session-start] WARNING: node $(node -v 2>/dev/null || echo 'not found') is outside the supported >=${NODE_MINIMUM} <${NODE_MAJOR_CEILING} range; npm ci will refuse to install."
fi

echo "[session-start] Using node $(node -v) / npm $(npm -v)"

cd "$CLAUDE_PROJECT_DIR"
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1066,7 +1066,7 @@ Use `docs/codex-cloud.md` as the environment contract:
Durable notes for Cloud Agents. Standard commands live in `README.md` and `package.json`; only non-obvious caveats are captured here.

- Context7 peer-library docs habit (and the Next 16 local-docs carve-out) lives in `docs/agents-guide.md`. Project MCP is local `@upstash/context7-mcp@3.2.5` with `CONTEXT7_API_KEY` from env/Secrets. If the host-injected Context7 MCP returns quota exceeded, use `npx ctx7 library|docs …` with the same secret — do not invent peer APIs from training data.
- Runtime: the app hard-requires Node 24.x / npm 11.x (`engine-strict`, and `scripts/dev-free-port.mjs` exits on any other major). Node 24 is installed via nvm and symlinked into `/usr/local/cargo/bin` (first entry in `PATH`) so `node`/`npm` resolve to v24 in every shell. If a shell ever resolves `/exec-daemon/node` (v22) instead, prepend the installed nvm Node 24 bin to `PATH` (for example `"$HOME/.nvm/versions/node/v24.18.1/bin"`; run `ls "$HOME/.nvm/versions/node"` to confirm the exact patch version).
- Runtime: the app hard-requires Node >=24.15.0 <25 / npm 11.x (`engine-strict`; the preinstall and runtime gates enforce the minor floor, while `scripts/dev-free-port.mjs` rejects other majors). A compatible Node 24 is installed via nvm and symlinked into `/usr/local/cargo/bin` (first entry in `PATH`) so `node`/`npm` resolve to it in every shell. If a shell ever resolves `/exec-daemon/node` (v22) instead, prepend the installed nvm Node 24 bin to `PATH` (for example `"$HOME/.nvm/versions/node/v24.18.1/bin"`; run `ls "$HOME/.nvm/versions/node"` to confirm the exact patch version).
- Live vs demo mode: the app auto-detects. When the Supabase + OpenAI env vars below are present (set them as Cloud Agent **Secrets** so they inject into `.env.local`/`process.env`), `isDemoMode()` (`src/lib/env.ts`) is false and the app runs against the live `Clinical KB Database` project (~2000 indexed docs) with OpenAI answer generation. When they are absent, dev auto-falls back to demo mode using the synthetic corpus in `src/lib/demo-data.ts` / `public/demo-documents/`. Required for live mode: `NEXT_PUBLIC_SUPABASE_URL`, `SUPABASE_PROJECT_REF`, `SUPABASE_PROJECT_NAME`, `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` (`sb_publishable_…`), `SUPABASE_SERVICE_ROLE_KEY` (accepts the `sb_secret_…` secret key), `OPENAI_API_KEY`. Keep `RAG_PROVIDER_MODE=auto` so OpenAI is used with graceful source-only fallback. `E2E_USER_EMAIL`/`E2E_USER_PASSWORD` power CI env-check and Playwright.
- Live-mode caveat: `RAG_PROVIDER_MODE=auto` attempts OpenAI (fast → strong route); if generation fails the built-in quality gates it silently degrades to a deterministic "Source-only" answer that still cites real documents — this is expected, not a failure. The header sign-in UI exposes magic-link + OAuth only (no password field), but the `/api/answer` + retrieval flow works server-side without a browser session.
- What still won't run in this VM even with secrets: `npm run worker` also needs the Python OCR stack (`worker/python/requirements.txt`) and heavy parsing deps; Supabase edge functions need Deno v2.x + deployment. `verify:release` additionally runs governance/eval gates. Treat missing-secret failures of `check:supabase-project`/`verify:release` in demo mode as expected, not regressions.
Expand Down
2 changes: 2 additions & 0 deletions docs/branch-review-ledger.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -837,3 +837,5 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie
| 2026-08-09 | cursor/differentials-diagnosis-links-9f18 | 0e77e7bc0842bef4ffc045c6dbea4626152490d1 | differentials diagnosis term links | implemented exact+alias termLinks chips on diagnosis+presentation pages; vitest 58/58; verify:pr-local green | vitest differential-diagnosis-links+detail+section-nav+route; verify:pr-local; ensure spot-check |
| 2026-08-09 | cursor/differentials-diagnosis-links-9f18 | 0daa9e2f9fc84e879fd661da94203568309234a6 | PR #1768 Autopilot+Bugbot review-and-fix | Merged origin/main (DIRTY was ledger+detail-page staleness; merge-tree clean). Fixed SEGMENT_SPLIT to spaced-slash only so Delirium / medical psychosis links while alcohol/benzo, DVT/PE, food/fluid stay intact. Dispositioned: Copilot termLinks ??{} + Fragment key already fixed; CodeRabbit clean-keys moot (visibleSectionItems already cleans); CodeRabbit bare-slash split rejected (clinical harm). No Bugbot findings. Threads cleared on push. Merge left to user. | vitest differential-diagnosis-links+detail+route 49/49; verify:cheap exit 0 (543 files, 5828 passed/4 skipped); verify:pr-local exit 0 (lint/typecheck/test/build/rag-fixtures); merge-tree clean vs origin/main; no provider gates |
| 2026-08-09 | cursor/differentials-diagnosis-links-9f18 | f784e81bcc0b53ef76b3da07a8e81f96d9bf0c71 | pr-1768 unblock | merged origin/main onto ba590f9; merge-tree clean; DIRTY mergeability cleared; push tip follows amend with this ledger | merge-tree clean; threads resolved; auto-merge was armed |
| 2026-08-09 | claude/planning-build-intelligence-9ot0nm | 3df3cb3993f73cda4dbbc4ac7549f84b3c6ea7ed | Node 24.15 engine floor: engines.node, preinstall hook, check:runtime, session-start provisioning, codex-cloud assertion | Authored and handed off as PR #1771; closes #285; operationalRisk true, clinicalRisk/ragRanking false | test 5800 passed/1 pre-existing root-uid failure (pr-handoff-stop, confirmed on stashed clean tree); lint 0; typecheck 0; prettier --check . pass; check:runtime pass; check:codex-cloud pass; check:outstanding-issues pass; preinstall boundary proof 24.13/24.14.9 reject, 24.15/24.19 accept, 25.0.0 reject; contract test mutation-checked red |
| 2026-08-09 | pull/1771 | 466ec4216272c31c5f754db213dbdc529583b167 | PR 1771 runtime floor enforcement | P2: Cloud and Desktop setup paths remain major-only; do not merge until range-aware | static review; check:runtime PASS; check:codex-cloud PASS; ledger PASS; outstanding issues PASS; focused Vitest blocked by active Playwright lease |
Loading
Loading