Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .claude/agents/supabase-schema-guardian.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,7 +28,7 @@ Never run live Supabase/MCP calls or `npm run check:supabase-project` yourself

### 1. Project & migration safety

- **Correct project:** the live target is `Clinical KB Database` / `sjrfecxgysukkwxsowpy`. `qjgitjyhxrwxsrydablr` is stale (belongs to `Database`) — flag any use.
- **Correct project:** the live target is `Clinical KB Database` / `sjrfecxgysukkwxsowpy`.
- **No live raw SQL:** never change a retrieval RPC (or any function) on the live project with raw `execute_sql`. Require a committed migration **and** a matching `supabase/schema.sql` update. Uncommitted live edits are exactly how drift accumulated.

### 2. Tenancy — fail-closed, never fail-open
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -228,7 +228,7 @@ The shared search chrome must adapt by page ownership, not by ad-hoc padding or

- This repo targets the live Supabase project `Clinical KB Database`.
- Expected project ref: `sjrfecxgysukkwxsowpy`.
- Older unused project ref `qjgitjyhxrwxsrydablr` belongs to `Database`; treat it as stale and do not use it.
- Supabase project is `Clinical KB Database` (`sjrfecxgysukkwxsowpy`).
- Hosted migrations, `supabase/schema.sql`, `supabase/roles.sql`, CI, and deployment tooling must target role `postgres`; never assume a platform-reserved role. The single older applied migration is immutable and pinned by `npm run check:migration-role`.
- Bare-image storage scaffolding must discover its local schema owner at runtime and must never be reused as hosted migration SQL.
- Run `npm run check:migration-role` after changing Supabase SQL, migration tooling, CI replay, or disaster-recovery instructions.
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,8 +33,7 @@ SUPABASE_PROJECT_REF=sjrfecxgysukkwxsowpy
SUPABASE_PROJECT_NAME=Clinical KB Database
```

Do not use the older unused Supabase project `Database`
(`qjgitjyhxrwxsrydablr`). Local checks and runtime guards warn or fall back to
Database uses the live Supabase project `Clinical KB Database` (`sjrfecxgysukkwxsowpy`). Local checks and runtime guards warn or fall back to
demo mode if that stale ref appears in `.env.local`.

5. Database bootstrap:
Expand Down
3 changes: 1 addition & 2 deletions docs/auth-connection-cap-runbook.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,8 +53,7 @@ Concretely, the desired end state is:

1. Sign in to the Supabase dashboard and open the **`Clinical KB Database`**
project (ref `sjrfecxgysukkwxsowpy`). Confirm the ref in the URL before
touching any control — it must be `sjrfecxgysukkwxsowpy`, never the stale
`qjgitjyhxrwxsrydablr`.
touching any control. Use the active project `sjrfecxgysukkwxsowpy`.
2. **Project Settings → Database → Connection pooling** — direct URL
`https://supabase.com/dashboard/project/sjrfecxgysukkwxsowpy/database/settings`.
Fields here: **Pool Size** and **Max Client Connections**. Set **Pool Size**
Expand Down
2 changes: 1 addition & 1 deletion docs/codebase-index.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@
Structured map for AI agents and onboarding. For live routes, see `docs/site-map.md` (`npm run sitemap:update` / `sitemap:check`). For agent rules and verification gates, see `AGENTS.md`; for test execution and flake policy, see `docs/testing.md`.

**Stack:** Next.js 16, React 19, Supabase (pgvector, Storage, Auth), OpenAI, Python OCR worker.
**Live Supabase:** `Clinical KB Database` — ref `sjrfecxgysukkwxsowpy` (never use stale `qjgitjyhxrwxsrydablr`).
**Live Supabase:** `Clinical KB Database` — ref `sjrfecxgysukkwxsowpy`.

---

Expand Down
4 changes: 2 additions & 2 deletions docs/codex-prompt-playbook.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ Before using any prompt, keep these project defaults in mind:
- Do not attach to a local server unless `/api/local-project-id` confirms this
project.
- Treat the live Supabase project as `Clinical KB Database`
(`sjrfecxgysukkwxsowpy`). Do not use the stale `qjgitjyhxrwxsrydablr` ref.
(`sjrfecxgysukkwxsowpy`).
- Ask before running live provider/API work, OpenAI calls, Supabase mutations,
production data operations, deploys, commits, pushes, or destructive cleanup.
- For source/config/test changes, prefer `npm run verify:cheap` as the first
Expand DownExpand Up@@ -325,7 +325,7 @@ Start read-only:
- inspect supabase/schema.sql, relevant migrations, generated types/usages,
scripts/check-supabase-project.ts, and tests
- confirm expected project ref is sjrfecxgysukkwxsowpy
- do not use qjgitjyhxrwxsrydablr
- Verify target ref is sjrfecxgysukkwxsowpy

Before live commands or mutations, stop and ask for explicit approval.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,7 +48,7 @@
"clean:worktree": "node scripts/clean-worktree.mjs",
"verify:preflight": "npm run typecheck && npm run verify:cheap && npm run clean:worktree",
"verify:cheap": "node scripts/run-heavy.mjs --npm-script verify:cheap:internal",
"verify:cheap:internal": "npm run check:runtime && npm run check:github-actions && npm run check:ci-scope && npm run check:ci-triage && npm run check:pr-policy && npm run check:gate-manifest && npm run check:branch-review-ledger && npm run sitemap:check && npm run docs:check-index && npm run docs:check-scripts && npm run docs:check-links && npm run check:knip && npm run check:maintainability-budgets && npm run brand:check && npm run check:assets && npm run check:therapy-data-index && npm run check:type-scale && npm run check:icon-scale && npm run check:design-system-contract && npm run check:migration-role && npm run check:function-grants && npm run check:owner-scope && npm run lint && npm run typecheck && npm run test",
"verify:cheap:internal": "node scripts/verify-cheap-parallel.mjs",
"verify:pr-local": "node scripts/verify-pr-local.mjs",
"verify:ui": "npm run check:runtime && npm run test:e2e:pr",
"verify:release": "npm run check:runtime && npm run lint && npm run typecheck && npm run test && npm run build && npm run test:e2e && npm run check:production-readiness && npm run governance:release && npm run eval:quality:release",
Expand Down
2 changes: 2 additions & 0 deletions playwright.visual.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,8 @@ export default defineConfig({
timeout: 30_000,
expect: {
timeout: 10_000,
toHaveScreenshot: { maxDiffPixelRatio: 0.05 },
toMatchSnapshot: { maxDiffPixelRatio: 0.05 },
Comment on lines +12 to +13

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Playwright testConfig expect toHaveScreenshot toMatchSnapshot maxDiffPixelRatio option

💡 Result:

In Playwright, the maxDiffPixelRatio option is used to define an acceptable ratio of differing pixels (between 0 and 1) compared to the total number of pixels in an image during snapshot comparisons [1][2]. Both toHaveScreenshot and toMatchSnapshot support this option [1][2]. You can configure maxDiffPixelRatio globally or per project in your Playwright test configuration, as well as override it on a per-test basis [3]. Global Configuration To set a default value for all tests, use the defineConfig function in your playwright.config.ts file [3]: import { defineConfig } from '@playwright/test'; export default defineConfig({ expect: { toHaveScreenshot: { maxDiffPixelRatio: 0.1, // Example: 10% tolerance }, toMatchSnapshot: { maxDiffPixelRatio: 0.1, }, }, }); Usage in Tests You can also pass the option directly within your test assertions to override the global setting [3][2]: // Using toHaveScreenshot await expect(page).toHaveScreenshot('example.png', { maxDiffPixelRatio: 0.05, }); // Using toMatchSnapshot expect(await page.screenshot).toMatchSnapshot('example.png', { maxDiffPixelRatio: 0.05, }); Key Differences While both methods support pixel comparison options like maxDiffPixelRatio and maxDiffPixels, they are intended for different use cases [3][4]: toHaveScreenshot: Designed specifically for visual regression testing of full pages or locators [1][3]. It includes built-in features for waiting for screenshots to stabilize and handles image comparison automatically [1]. toMatchSnapshot: A more general-purpose matcher that compares arbitrary data, including text, binary data, and images [3][2]. It does not include the specialized screenshot stabilization features found in toHaveScreenshot [1][2].

Citations:


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== repo files of interest =="
git ls-files | rg '(^|/)playwright.*\.(ts|js|mjs|cjs|json|lock)$|(^|/)package\.json$|(^|/)package-lock\.json$|(^|/)pnpm-lock\.yaml$|(^|/)yarn\.lock$'||trueechoecho"== playwright.visual.config.ts =="if [ -f playwright.visual.config.ts ];then
cat -n playwright.visual.config.ts
fiechoecho"== playwright config files =="forfin playwright.config.ts playwright.config.js playwright.config.mjs playwright.config.cjs package.json package-lock.json pnpm-lock.yaml yarn.lock;doif [ -f"$f" ];thenecho"--- $f ---"if [ "$f"= package.json ];then
node -e "const p=require('./package.json'); console.log(JSON.stringify({scripts:p.scripts,dependencies:p.dependencies && {playwright:p.dependencies.playwright, '`@playwright/test`':p.dependencies['`@playwright/test`']},devDependencies:p.devDependencies && {playwright:p.devDependencies.playwright, '`@playwright/test`':p.devDependencies['`@playwright/test`']}}, null, 2))"elif rg -q "maxDiffPixelRatio|playwright|`@playwright/test`""$f";then
rg -n "maxDiffPixelRatio|playwright|`@playwright/test`|\"playwright\"|\"`@playwright/test`\"""$f" -C 2
fifidoneechoecho"== CSS cascade/important/layer mentions in migrations/sql/ts/js =="
rg -n "!important|`@layer`|layer|mobile|override|cascade""$(git ls-files | rg '\.(sql|ts|js|yml|yaml|md)$'|| true)" -C 1 ||true

Repository: BigSimmo/Database

Length of output: 50374


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== visual config screen settings =="
sed -n '1,220p' playwright.visual.config.ts
echoecho"== visual spec and matching files =="
git ls-files 'tests/*ui-visual-artifacts*''tests/*visual*''tests/*screenshots*''tests/*snapshot*'| sed -n '1,200p'echoecho"== references to maxDiffPixelRatio in repo =="
rg -n "maxDiffPixelRatio|maxDiffPixels|toHaveScreenshot|toMatchSnapshot|!important|`@layer`" playwright playwright.visual.config.ts playwright.config.ts tests/*.ts 2>/dev/null ||trueechoecho"== playwright erts/API docs for expect options in installed package =="
node_modules/.bin/playwright --version ||trueif [ -d node_modules/playwright ];then
rg -n "maxDiffPixelRatio|toMatchSnapshot|toHaveScreenshot|expect:" node_modules/playwright -g '*.d.ts' -g '*.js'| sed -n '1,160p'fiechoecho"== deterministic pixel-budget math for 5% diff at common viewport sizes =="
python3 - <<'PY'for w,h in [(1280,720),(1920,1080),(375,667),(390,844)]: print(f"{w}x{h}: 5% = {w*h*0.05:.0f} pixels, 1% = {w*h*0.01:.0f} pixels")PY

Repository: BigSimmo/Database

Length of output: 16570


Tighten the visual diff guard for @layer visual changes

maxDiffPixelRatio: 0.05 is accepted by Playwright 1.61.1 for both matchers, but it permits roughly 46k differing pixels at 1280×720. Since this PR changes the CSS cascade, use a tighter global ratio unless there’s noise that truly requires it, and keep mobile/!important styling covered by lower-tolerance visual checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@playwright.visual.config.ts` around lines 12 - 13, Lower the global
maxDiffPixelRatio from 0.05 to a stricter threshold for both toHaveScreenshot
and toMatchSnapshot in the visual configuration. Preserve or add lower-tolerance
checks for mobile and !important styling, only retaining a higher ratio where
demonstrable visual noise requires it.

},
reporter: "list",
use: {
Expand Down
2 changes: 1 addition & 1 deletion plugins/clinical-kb/skills/clinical-kb-workflow/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,7 +14,7 @@ Root `AGENTS.md` remains authoritative. If these notes drift, inspect the repo b
- Package manager: npm with `package-lock.json`.
- Main app routes live under `src/app`; shared RAG, OpenAI, Supabase, safety, and validation logic live under `src/lib`.
- This project targets the live Supabase project `Clinical KB Database` with project ref `sjrfecxgysukkwxsowpy`.
- Treat the older Supabase ref `qjgitjyhxrwxsrydablr` as stale.
- Verify the Supabase ref is `sjrfecxgysukkwxsowpy`.

## Local Server Safety

Expand Down
15 changes: 13 additions & 2 deletions scripts/check-gate-manifest.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,9 +18,20 @@ const pkg = JSON.parse(readFileSync("package.json", "utf8"));
const ci = readFileSync(".github/workflows/ci.yml", "utf8");

const localChain = pkg.scripts?.["verify:cheap:internal"] ?? "";
const localGates = [...localChain.matchAll(/npm run ([\w:.-]+)/g)].map((m) => m[1]);
let localGates = [];
if (localChain.includes("verify-cheap-parallel.mjs")) {
const parallelSrc = readFileSync("scripts/verify-cheap-parallel.mjs", "utf8");
const tasksMatch = parallelSrc.match(/const parallelTasks = \[([\s\S]*?)\];/);
if (tasksMatch) {
localGates = [...tasksMatch[1].matchAll(/"([^"]+)"/g)].map((m) => m[1]);
localGates.push("typecheck", "test");
}
} else {
localGates = [...localChain.matchAll(/npm run ([\w:.-]+)/g)].map((m) => m[1]);
}

if (localGates.length === 0) {
console.error("gate-manifest: could not parse verify:cheap:internal from package.json.");
console.error("gate-manifest: could not parse verify:cheap:internal from package.json or verify-cheap-parallel.mjs.");
process.exit(1);
}

Expand Down
36 changes: 36 additions & 0 deletions scripts/lib/rag-preflight-utils.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
export interface RAGPreflightChecks {
maxTokens?: number;
maxCost?: number;
requiredQualityThreshold?: number;
}

export function validatePreflightConstraints(
estimatedTokens: number,
estimatedCost: number,
constraints: RAGPreflightChecks,
): string[] {
const errors: string[] = [];

if (constraints.maxTokens && estimatedTokens > constraints.maxTokens) {
errors.push(`Estimated tokens (${estimatedTokens}) exceeds maximum allowed (${constraints.maxTokens}).`);
}

if (constraints.maxCost && estimatedCost > constraints.maxCost) {
errors.push(`Estimated cost ($${estimatedCost}) exceeds maximum allowed ($${constraints.maxCost}).`);
Comment on lines +14 to +19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not skip zero-valued limits.

The checks at Line 14 and Line 18 are bypassed when maxTokens or maxCost is 0, allowing any positive estimate through. Use explicit undefined checks and add zero-boundary tests.

Proposed fix
- if (constraints.maxTokens && estimatedTokens > constraints.maxTokens) {+ if (constraints.maxTokens !== undefined && estimatedTokens > constraints.maxTokens) {
...
- if (constraints.maxCost && estimatedCost > constraints.maxCost) {+ if (constraints.maxCost !== undefined && estimatedCost > constraints.maxCost) {
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(constraints.maxTokens&&estimatedTokens>constraints.maxTokens){
errors.push(`Estimated tokens (${estimatedTokens}) exceeds maximum allowed (${constraints.maxTokens}).`);
}
if(constraints.maxCost&&estimatedCost>constraints.maxCost){
errors.push(`Estimated cost ($${estimatedCost}) exceeds maximum allowed ($${constraints.maxCost}).`);
if(constraints.maxTokens!==undefined&&estimatedTokens>constraints.maxTokens){
errors.push(`Estimated tokens (${estimatedTokens}) exceeds maximum allowed (${constraints.maxTokens}).`);
}
if(constraints.maxCost!==undefined&&estimatedCost>constraints.maxCost){
errors.push(`Estimated cost ($${estimatedCost}) exceeds maximum allowed ($${constraints.maxCost}).`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/lib/rag-preflight-utils.ts` around lines 14 - 19, Update the
maxTokens and maxCost validations in the preflight utility to check explicitly
for undefined, so zero-valued limits are enforced and any positive estimate
produces an error. Add boundary tests covering maxTokens: 0 and maxCost: 0 while
preserving the existing behavior for undefined limits.

}

return errors;
}

export function getOfflineEnvironment() {
return {
RAG_PROVIDER_MODE: "offline",
OPENAI_API_KEY: "",
OPENAI_ORG_ID: "",
OPENAI_PROJECT_ID: "",
NEXT_PUBLIC_SUPABASE_URL: "https://offline.invalid",
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: "offline-placeholder",
SUPABASE_SERVICE_ROLE_KEY: "offline-placeholder",
SUPABASE_DB_URL: "postgresql://offline:offline@offline.invalid:5432/offline",
};
}
6 changes: 4 additions & 2 deletions scripts/test-run-lock.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -174,7 +174,8 @@ export function acquireHeavyRunLock({
}

if (owner && processIsAlive(owner.pid)) {
if (attempt < 15) {
const isTest = process.env.NODE_ENV === "test";
if (!isTest && attempt < 15) {
Comment on lines 176 to +178

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== files =="
fd -a 'test-run-lock\.mjs$'.||trueecho"== outline =="
ast-grep outline scripts/test-run-lock.mjs --view expanded 2>/dev/null | sed -n '1,220p'||trueecho"== relevant lines =="
sed -n '70,210p' scripts/test-run-lock.mjs | nl -ba -v70
echo"== acquireHeavyRunLock usages/tests =="
rg -n "acquireHeavyRunLock|NODE_ENV|environment\\s*:" -S . --glob '!node_modules' --glob '!dist' --glob '!build'| sed -n '1,240p'

Repository: BigSimmo/Database

Length of output: 940


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== scripts/test-run-lock.mjs lines 82-210 =="
awk 'NR>=82 && NR<=210 {printf "%6d\t%s\n", NR, $0}' scripts/test-run-lock.mjs
echo"== usages of acquireHeavyRunLock / environment override =="
rg -n "acquireHeavyRunLock\\(|environment\\s*:" -S . --glob '!node_modules' --glob '!dist' --glob '!build'| head -200
echo"== test-mode and NODE_ENV mentions in scripts tests =="
rg -n "acquireHeavyRunLock|testRunLockInternals|NODE_ENV|environment:" scripts --glob '*.mjs' --glob '*.js' --glob '*.ts' --glob '*.tsx'| sed -n '1,240p'||true

Repository: BigSimmo/Database

Length of output: 14792


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== offline test environment references =="
rg -n "offlineTestEnvironment|run-vitest|run-live-tests|run-playwright|NODE_ENV|testRunLockInternals" scripts tests --glob '*.mjs' --glob '*.js' --glob '*.ts' --glob '*.tsx'| sed -n '1,240p'echo"== read-only semantic probe for environment.NODE_ENV omission =="
node - <<'JS'function acquireLockAttemptWith(nodeEnvGlobal, environment) { // Mirrors the relevant conditional semantics around lines 176-178 / 191-193 const actualIsTest = environment.NODE_ENV === "test"; return actualIsTest ? "fail-fast" : "retry";}console.log({ globalTestEnvInjectedThroughEnvironment: acquireLockAttemptWith(undefined, { NODE_ENV: "test" }), globalNotTestWhenEnvironmentHasTest: acquireLockAttemptWith("development", { NODE_ENV: "test" }), globalTestNoEnvironmentNodeEnv: acquireLockAttemptWith("test", {}),});JS

Repository: BigSimmo/Database

Length of output: 8178


Use the injected environment for test-mode detection.

acquireHeavyRunLock accepts environment, but both test-mode guards read process.env.NODE_ENV. Passing { NODE_ENV: "test" } through the injected environment bypasses the fail-fast path, while a custom injected environment can differ from the global process environment. Use environment.NODE_ENV in both branches.

Proposed fix
- const isTest = process.env.NODE_ENV === "test";+ const isTest = environment.NODE_ENV === "test";
...
- const isTest = process.env.NODE_ENV === "test";+ const isTest = environment.NODE_ENV === "test";
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(owner&&processIsAlive(owner.pid)){
if(attempt<15){
constisTest=process.env.NODE_ENV==="test";
if(!isTest&&attempt<15){
if(owner&&processIsAlive(owner.pid)){
constisTest=environment.NODE_ENV==="test";
if(!isTest&&attempt<15){
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/test-run-lock.mjs` around lines 176 - 178, Update both test-mode
guards in acquireHeavyRunLock to read NODE_ENV from the injected environment
parameter instead of process.env, ensuring custom environments consistently
control fail-fast behavior and retry handling.

// 15 attempts, approx 30s
const sleepMs = Math.min(3000, 100 * Math.pow(1.5, attempt));
const waitResult = spawnSync("node", ["-e", `setTimeout(() => {}, ${sleepMs})`]);
Expand All@@ -188,7 +189,8 @@ export function acquireHeavyRunLock({
);
}
if (!owner && !lockIsOldEnoughToRecover(lockPath)) {
if (attempt < 15) {
const isTest = process.env.NODE_ENV === "test";
if (!isTest && attempt < 15) {
const sleepMs = 500;
spawnSync("node", ["-e", `setTimeout(() => {}, ${sleepMs})`]);
continue;
Expand Down
67 changes: 67 additions & 0 deletions scripts/verify-cheap-parallel.mjs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
import { spawn } from "node:child_process";

const parallelTasks = [
"check:runtime",
"check:github-actions",
"check:ci-scope",
"check:ci-triage",
"check:pr-policy",
"check:gate-manifest",
"check:branch-review-ledger",
"sitemap:check",
"docs:check-index",
"docs:check-scripts",
"docs:check-links",
"check:knip",
"check:maintainability-budgets",
"brand:check",
"check:assets",
"check:therapy-data-index",
"check:type-scale",
"check:icon-scale",
"check:design-system-contract",
"check:migration-role",
"check:function-grants",
"check:owner-scope",
"lint",
];

async function runTask(task) {
return new Promise((resolve, reject) => {
const child = spawn("npm", ["run", task], { shell: true, stdio: "pipe" });
let output = "";
child.stdout.on("data", (data) => (output += data.toString()));
child.stderr.on("data", (data) => (output += data.toString()));
child.on("close", (code) => {
if (code !== 0) {
console.error(output);
reject(new Error(`${task}`));
} else {
console.log(`✔ ${task}`);
resolve();
}
});
});
}

async function run() {
console.log("Starting parallel verification...");
try {
await Promise.all(parallelTasks.map(runTask));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound the verification fan-out

When verify:cheap runs on a typical developer or CI host, this starts all 23 gates simultaneously, including the memory-intensive ESLint and Knip analyses; because the outer heavyweight-lock token is inherited, the nested lint wrapper is treated as reentrant and does not serialize the workload. The expected one-heavy-command-at-a-time behavior is therefore bypassed, risking memory exhaustion or severe contention in the repository's baseline gate. Use a bounded worker pool and keep heavyweight analyses sequential; a focused check should assert the maximum concurrent child count.

AGENTS.md reference: AGENTS.md:L167-L168

Useful? React with 👍 / 👎.

await runTask("typecheck");

console.log("Starting tests...");
await new Promise((resolve, reject) => {
const child = spawn("npm", ["run", "test"], { shell: true, stdio: "inherit" });
child.on("close", (code) => {
if (code !== 0) reject(new Error("test"));
else resolve();
});
});
} catch (err) {
console.error(`\n✘ [FAILED] ${err.message}`);
process.exit(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Terminate sibling gates before exiting

When any fast gate fails while slower gates are still running, Promise.all rejects immediately and this process.exit(1) abandons every spawned shell without terminating or awaiting it. The outer lock is then released while those orphaned checks can continue consuming resources or writing caches concurrently with a retry; an isolated stub reproduction left all 22 sibling tasks running after the verifier exited. Track the children and terminate their process trees, or await all tasks to settle, before exiting.

Useful? React with 👍 / 👎.

Comment on lines +50 to +63

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"## Locate file"
git ls-files | rg '(^|/)verify-cheap-parallel\.mjs$'||trueecho"## File excerpt"if [ -f scripts/verify-cheap-parallel.mjs ];then
wc -l scripts/verify-cheap-parallel.mjs
sed -n '1,140p' scripts/verify-cheap-parallel.mjs | cat -n
fiecho"## Search for cleanup/cancellation patterns"
rg -n "Promise\.all|Promise\.allSettled|spawn\(|child\.(unref|kill|stdin|stdout|stderr)|on\('close'|on\(\"close\"|process\.exit" scripts/verify-cheap-parallel.mjs ||true

Repository: BigSimmo/Database

Length of output: 2940


🌐 Web query:

Node.js process.exit child processes forked processes killed on exit

💡 Result:

In Node.js, calling process.exit in the parent process does not automatically terminate or kill forked child processes [1][2]. When the parent process terminates, its child processes become "orphaned" (unless they were spawned as detached processes, in which case they continue running independently) [3][1]. Key points regarding the behavior: 1. Parent Termination: If the parent process exits, forked child processes continue to run unless explicitly handled [1][2]. Node.js does not automatically manage the lifecycle of children once the parent terminates [2]. 2. Manual Cleanup: To ensure child processes are terminated when the parent exits, you must explicitly manage this in your code [2]. A common approach is to listen for signals (such as SIGTERM or SIGINT) or the 'exit' event in the parent process, and then call subprocess.kill on each active child process [4][2]. 3. Child Self-Termination: A child process can exit itself by calling process.exit [5][4][6]. This is often the cleanest way to shut down a child process when its work is complete [4]. 4. IPC Channels: If a child process is forked with an IPC channel, the child can potentially detect when the parent process disconnects (via the 'disconnect' event) and shut itself down gracefully [5][7]. 5. Detached Processes: If you spawn a process with the detached: true option, it is intended to run independently of the parent process, and the parent will not automatically terminate it upon exiting [3][1]. If you notice child processes hanging after a parent process exits, it is likely because they were not explicitly signaled to shut down [4][1]. Implementing a cleanup mechanism in the parent's exit handler is the standard practice for ensuring all child processes are terminated [4][2].

Citations:


Terminate remaining verification tasks when one task fails.

Promise.all aborts on the first failure, but process.exit(1) leaves the other spawned npm run children active. Those orphaned checks can continue running after the script exits, so keep active child references and shut them down before exiting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/verify-cheap-parallel.mjs` around lines 50 - 63, Update the
verification flow around parallelTasks, runTask, and the catch handler to retain
references to every spawned child process and terminate all still-active
children when any task fails. Ensure cleanup runs before process.exit(1), while
preserving the existing Promise.all and test execution behavior.

}
}

run();
Loading
Loading