From a38e83860510a4229d5658960657cd7448aff278 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 25 Jul 2026 22:41:48 +0800 Subject: [PATCH 1/4] fix(audit): port safe #1186 remediation onto current main Supersede conflicted PR #1186 by landing only the intentional audit fixes: library-health skeleton/CLS, eval concurrency without duplicate bindings, async heavy-run lock heartbeats with force/stale reclaim, dry-run branch cleanup, and skill-create scaffolding with the interface openai.yaml shape. --- package.json | 2 + scripts/eval-retrieval.ts | 149 ++++++++++-------- scripts/run-heavy.mjs | 35 ++-- scripts/skill-create.mjs | 81 ++++++++++ scripts/sweep-merged-branches.mjs | 75 +++++++++ scripts/test-run-lock.mjs | 41 ++++- .../DocumentManagerPanel.tsx | 30 +++- tests/audit-remediation-tooling.test.ts | 51 ++++++ tests/test-runner-safety.test.ts | 26 +++ 9 files changed, 408 insertions(+), 82 deletions(-) create mode 100644 scripts/skill-create.mjs create mode 100644 scripts/sweep-merged-branches.mjs create mode 100644 tests/audit-remediation-tooling.test.ts diff --git a/package.json b/package.json index c36cbfb98..52c72444b 100644 --- a/package.json +++ b/package.json @@ -181,6 +181,8 @@ "workflow:rag-lab": "node scripts/productivity-workflow.mjs rag-lab", "workflow:operator-closeout": "node scripts/productivity-workflow.mjs operator-closeout", "workflow:lifecycle": "node scripts/productivity-workflow.mjs lifecycle", + "skill:create": "node scripts/skill-create.mjs", + "branch:cleanup": "node scripts/sweep-merged-branches.mjs", "skills": "node scripts/list-database-skills.mjs", "skills:sync": "node scripts/sync-skills.mjs", "check:skills": "node scripts/list-database-skills.mjs --check", diff --git a/scripts/eval-retrieval.ts b/scripts/eval-retrieval.ts index 7a048b6a4..a6e808eba 100644 --- a/scripts/eval-retrieval.ts +++ b/scripts/eval-retrieval.ts @@ -902,7 +902,6 @@ async function main() { ? allCases.filter((item) => item.query.toLowerCase().includes(args.query!.toLowerCase()) || item.id === args.query) : allCases; const cases = filteredCases.slice(0, args.limit ?? filteredCases.length); - const results: GoldenRetrievalResult[] = []; const readinessWarnings = await visualReadinessWarnings(supabase, cases); if (!args.json) { @@ -912,71 +911,93 @@ async function main() { for (const warning of readinessWarnings) console.warn(`WARN ${warning}`); } - for (let caseIndex = 0; caseIndex < cases.length; caseIndex += 1) { - const testCase = cases[caseIndex]!; - await pauseBetweenEvalCases({ - caseIndex, - forceEmbedding: testCase.forceEmbedding || args.forceEmbedding, - }); - const startedAt = Date.now(); - const searchPromise = withProviderBackoff(`retrieval:${testCase.id}`, () => - searchChunksWithTelemetry({ - query: testCase.query, - ownerId, - topK: retrievalLimitForGoldenCase(testCase), - minSimilarity: 0.12, - skipCache: args.mode !== "latency", - forceEmbedding: testCase.forceEmbedding || args.forceEmbedding, - }), - ); - const searchOutcome = await withCaseTimeout(searchPromise, args.caseTimeoutMs); - const search = searchOutcome.timedOut - ? { - results: [] as SearchResult[], - telemetry: { - query_class: testCase.expectedQueryClass, - retrieval_strategy: "timeout", - embedding_skipped: false, - text_fast_path_latency_ms: 0, - embedding_latency_ms: 0, - supabase_rpc_latency_ms: args.caseTimeoutMs, - rerank_latency_ms: 0, - retrieval_layer_counts: {}, - retrieval_layer_top_scores: {}, - retrieval_layer_latencies_ms: {}, - }, - } - : searchOutcome.value; - const latencyMs = searchOutcome.timedOut - ? args.caseTimeoutMs - : latencyFromTelemetry(search.telemetry) || Date.now() - startedAt; - const latencyFailures = latencyFailuresForCase({ latencyMs, timedOut: searchOutcome.timedOut }, args); - const result = evaluateGoldenRetrievalCase({ - testCase, - results: search.results, - telemetry: search.telemetry, - latencyMs, - timedOut: searchOutcome.timedOut, - latencyFailures, - globalForceEmbedding: args.forceEmbedding, - }); - results.push(result); - - if (!args.json) { - const status = - args.mode === "latency" - ? result.latencyFailures?.length - ? "SLOW" - : "OK" - : result.failures.length - ? "FAIL" - : "PASS"; - console.log( - `${status} ${result.id} hit@${result.topK}=${result.hitAtK ? "1" : "0"} docRecall@5=${result.documentRecallAt5.toFixed(2)} contentRecall@5=${result.contentRecallAt5.toFixed(2)} rr@10=${result.reciprocalRankAt10.toFixed(2)} contentRR@10=${result.contentReciprocalRankAt10.toFixed(2)} ndcg@10=${result.ndcgAt10.toFixed(2)} irrelevant@10=${result.irrelevantSourceRateAt10.toFixed(2)} signalCoverage@10=${result.requiredSignalCoverageAt10.toFixed(2)} latency=${result.latencyMs}ms strategy=${result.retrievalStrategy ?? "none"} gate=${result.coverageGateReason ?? "none"} layers=${JSON.stringify(result.retrievalLayerCounts ?? {})}`, - ); - } + function pLimit(concurrency: number) { + let active = 0; + const queue: Array<() => void> = []; + return async (fn: () => Promise): Promise => { + if (active >= concurrency) await new Promise((resolve) => queue.push(resolve)); + active += 1; + try { + return await fn(); + } finally { + active -= 1; + queue.shift()?.(); + } + }; } + // Latency mode stays serial so p50/p90 timing is not distorted by concurrent load. + const concurrency = args.mode === "latency" ? 1 : 5; + const limitEvaluations = pLimit(concurrency); + + const results: GoldenRetrievalResult[] = await Promise.all( + cases.map((testCase, caseIndex) => + limitEvaluations(async () => { + await pauseBetweenEvalCases({ + caseIndex, + forceEmbedding: testCase.forceEmbedding || args.forceEmbedding, + }); + const startedAt = Date.now(); + const searchPromise = withProviderBackoff(`retrieval:${testCase.id}`, () => + searchChunksWithTelemetry({ + query: testCase.query, + ownerId, + topK: retrievalLimitForGoldenCase(testCase), + minSimilarity: 0.12, + skipCache: args.mode !== "latency", + forceEmbedding: testCase.forceEmbedding || args.forceEmbedding, + }), + ); + const searchOutcome = await withCaseTimeout(searchPromise, args.caseTimeoutMs); + const search = searchOutcome.timedOut + ? { + results: [] as SearchResult[], + telemetry: { + query_class: testCase.expectedQueryClass, + retrieval_strategy: "timeout", + embedding_skipped: false, + text_fast_path_latency_ms: 0, + embedding_latency_ms: 0, + supabase_rpc_latency_ms: args.caseTimeoutMs, + rerank_latency_ms: 0, + retrieval_layer_counts: {}, + retrieval_layer_top_scores: {}, + retrieval_layer_latencies_ms: {}, + }, + } + : searchOutcome.value; + const latencyMs = searchOutcome.timedOut + ? args.caseTimeoutMs + : latencyFromTelemetry(search.telemetry) || Date.now() - startedAt; + const latencyFailures = latencyFailuresForCase({ latencyMs, timedOut: searchOutcome.timedOut }, args); + const result = evaluateGoldenRetrievalCase({ + testCase, + results: search.results, + telemetry: search.telemetry, + latencyMs, + timedOut: searchOutcome.timedOut, + latencyFailures, + globalForceEmbedding: args.forceEmbedding, + }); + + if (!args.json) { + const status = + args.mode === "latency" + ? result.latencyFailures?.length + ? "SLOW" + : "OK" + : result.failures.length + ? "FAIL" + : "PASS"; + console.log( + `${status} ${result.id} hit@${result.topK}=${result.hitAtK ? "1" : "0"} docRecall@5=${result.documentRecallAt5.toFixed(2)} contentRecall@5=${result.contentRecallAt5.toFixed(2)} rr@10=${result.reciprocalRankAt10.toFixed(2)} contentRR@10=${result.contentReciprocalRankAt10.toFixed(2)} ndcg@10=${result.ndcgAt10.toFixed(2)} irrelevant@10=${result.irrelevantSourceRateAt10.toFixed(2)} signalCoverage@10=${result.requiredSignalCoverageAt10.toFixed(2)} latency=${result.latencyMs}ms strategy=${result.retrievalStrategy ?? "none"} gate=${result.coverageGateReason ?? "none"} layers=${JSON.stringify(result.retrievalLayerCounts ?? {})}`, + ); + } + return result; + }), + ), + ); + const summary = summarizeGoldenRetrievalResults(results); const latencyThresholdFailures = args.mode === "latency" diff --git a/scripts/run-heavy.mjs b/scripts/run-heavy.mjs index 5e7b038d4..312faab3c 100644 --- a/scripts/run-heavy.mjs +++ b/scripts/run-heavy.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -import { spawnSync } from "node:child_process"; +import { spawn } from "node:child_process"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { childProcessExitCode } from "./child-process-result.mjs"; @@ -13,25 +13,42 @@ if (args[0] !== "--npm-script" || !args[1]) { } const script = args[1]; -const forwarded = args.slice(2); -const lock = acquireHeavyRunLock({ projectRoot, command: `npm run ${script}` }); -let exitCode = 1; -try { +const rawForwarded = args.slice(2); +const forceLockRelease = rawForwarded.includes("--force-lock-release"); +const forwarded = rawForwarded.filter((argument) => argument !== "--force-lock-release"); +const lock = acquireHeavyRunLock({ + projectRoot, + command: `npm run ${script}`, + forceLockRelease, +}); + +function runNpmScript() { const npmExecPath = process.env.npm_execpath; - const result = npmExecPath - ? spawnSync(process.execPath, [npmExecPath, "run", script, ...(forwarded.length ? ["--", ...forwarded] : [])], { + const child = npmExecPath + ? spawn(process.execPath, [npmExecPath, "run", script, ...(forwarded.length ? ["--", ...forwarded] : [])], { cwd: projectRoot, env: lock.environment, stdio: "inherit", }) - : spawnSync( + : spawn( process.platform === "win32" ? "cmd.exe" : "npm", process.platform === "win32" ? ["/d", "/s", "/c", `npm run ${script}`] : ["run", script, ...(forwarded.length ? ["--", ...forwarded] : [])], { cwd: projectRoot, env: lock.environment, stdio: "inherit" }, ); - exitCode = childProcessExitCode(result); + + return new Promise((resolve, reject) => { + child.on("error", reject); + child.on("close", (status, signal) => { + resolve(childProcessExitCode({ status, signal })); + }); + }); +} + +let exitCode = 1; +try { + exitCode = await runNpmScript(); } finally { lock.release(); } diff --git a/scripts/skill-create.mjs b/scripts/skill-create.mjs new file mode 100644 index 000000000..e4d0db570 --- /dev/null +++ b/scripts/skill-create.mjs @@ -0,0 +1,81 @@ +#!/usr/bin/env node +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptDirectory = path.dirname(fileURLToPath(import.meta.url)); +const repositoryRoot = path.resolve(scriptDirectory, ".."); +const skillsRoot = path.join(repositoryRoot, ".agents", "skills"); +const catalogPath = path.join(skillsRoot, "catalog.json"); + +function toDisplayName(skillName) { + return skillName + .split("-") + .filter(Boolean) + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(" "); +} + +function createSkill(skillName) { + if (!/^[a-z0-9-]+$/.test(skillName)) { + console.error(`Invalid skill name: ${skillName}. Only lowercase letters, numbers, and dashes are allowed.`); + process.exit(1); + } + + const skillDir = path.join(skillsRoot, skillName); + if (fs.existsSync(skillDir)) { + console.error(`Skill directory already exists: ${skillDir}`); + process.exit(1); + } + + fs.mkdirSync(path.join(skillDir, "agents"), { recursive: true }); + + const displayName = toDisplayName(skillName); + const shortDescription = `Run the ${displayName} Database skill workflow`.slice(0, 64); + const paddedShortDescription = + shortDescription.length < 25 ? `${shortDescription} for this repository task` : shortDescription; + + const skillMdContent = `--- +name: ${skillName} +description: ${displayName} helper for Database work. Use this skill when the task matches ${skillName}. +--- +# ${displayName} + +1. Inspect the current branch, status, and touched paths. +2. Apply the smallest safe change for ${skillName}. +3. Run the narrowest local verification before widening. +`; + fs.writeFileSync(path.join(skillDir, "SKILL.md"), skillMdContent, "utf8"); + + const openaiYamlContent = `interface: + display_name: "${displayName}" + short_description: "${paddedShortDescription}" + default_prompt: "Use $${skillName} for this Database task." +`; + fs.writeFileSync(path.join(skillDir, "agents", "openai.yaml"), openaiYamlContent, "utf8"); + + const catalog = JSON.parse(fs.readFileSync(catalogPath, "utf8")); + const everydayCategory = catalog.categories.find((category) => category.name === "Everyday"); + if (!everydayCategory) { + console.error('Catalog is missing the "Everyday" category.'); + process.exit(1); + } + if (!everydayCategory.skills.includes(skillName)) { + everydayCategory.skills.push(skillName); + fs.writeFileSync(catalogPath, `${JSON.stringify(catalog, null, 2)}\n`, "utf8"); + } + + const canonicalCount = new Set(catalog.categories.flatMap((category) => category.skills)).size; + console.log(`Successfully created skill: ${skillName}`); + console.log(`Catalog now lists ${canonicalCount} canonical skills.`); + console.log("Next: refine SKILL.md, then update AGENTS.md and tests/database-skills.test.ts counts if needed."); + console.log("Verify with: npm run check:skills"); +} + +const args = process.argv.slice(2); +if (args.length !== 1) { + console.error("Usage: npm run skill:create -- "); + process.exit(1); +} + +createSkill(args[0]); diff --git a/scripts/sweep-merged-branches.mjs b/scripts/sweep-merged-branches.mjs new file mode 100644 index 000000000..4c554d918 --- /dev/null +++ b/scripts/sweep-merged-branches.mjs @@ -0,0 +1,75 @@ +#!/usr/bin/env node +import { spawnSync } from "node:child_process"; + +function runGit(args) { + const result = spawnSync("git", args, { encoding: "utf8" }); + if (result.error) throw result.error; + if (result.status !== 0) { + const stderr = typeof result.stderr === "string" ? result.stderr.trim() : ""; + throw new Error(stderr || `git ${args.join(" ")} failed with status ${result.status}`); + } + return (result.stdout || "").trim(); +} + +function parseArgs(argv) { + const apply = argv.includes("--apply"); + const prune = !argv.includes("--no-prune"); + if (argv.includes("--help") || argv.includes("-h")) { + console.log(`Usage: node scripts/sweep-merged-branches.mjs [--apply] [--no-prune] + +Dry-run by default. Lists local branches already merged into main. +Pass --apply to delete those local branches with git branch -d. +Remote branches are never deleted.`); + process.exit(0); + } + return { apply, prune }; +} + +function main() { + const { apply, prune } = parseArgs(process.argv.slice(2)); + + if (prune) { + runGit(["remote", "prune", "origin"]); + } + + const currentBranch = runGit(["rev-parse", "--abbrev-ref", "HEAD"]); + const mergedBranches = runGit(["branch", "--merged", "main"]) + .split("\n") + .map((line) => line.trim().replace(/^\*\s+/, "")) + .filter(Boolean); + + const protectedBranches = new Set(["main", "master", "develop", currentBranch]); + const branchesToDelete = mergedBranches.filter( + (branch) => !protectedBranches.has(branch) && !branch.startsWith("release/"), + ); + + if (branchesToDelete.length === 0) { + console.log("No merged local branches to clean up."); + return; + } + + if (!apply) { + console.log("Dry-run only. Candidates that would be deleted with --apply:"); + for (const branch of branchesToDelete) console.log(`- ${branch}`); + console.log(`\nRe-run with --apply to delete ${branchesToDelete.length} local branch(es).`); + return; + } + + for (const branch of branchesToDelete) { + console.log(`Deleting merged branch: ${branch}`); + try { + runGit(["branch", "-d", branch]); + } catch (error) { + console.warn(`Failed to delete branch ${branch}: ${error.message}`); + } + } + + console.log("Branch cleanup complete."); +} + +try { + main(); +} catch (error) { + console.error(`Error cleaning up branches: ${error.message}`); + process.exit(1); +} diff --git a/scripts/test-run-lock.mjs b/scripts/test-run-lock.mjs index b0015cde4..50d088e51 100644 --- a/scripts/test-run-lock.mjs +++ b/scripts/test-run-lock.mjs @@ -1,5 +1,5 @@ import { createHash, randomUUID } from "node:crypto"; -import { mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs"; +import { mkdirSync, readFileSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import { spawnSync } from "node:child_process"; @@ -8,6 +8,8 @@ import { redactSensitiveText } from "./sensitive-text.mjs"; const tokenEnvironmentKey = "CLINICAL_KB_HEAVY_LOCK_TOKEN"; const pathEnvironmentKey = "CLINICAL_KB_HEAVY_LOCK_PATH"; const incompleteLockGraceMs = 30_000; +const staleLockHeartbeatMs = 60_000; +const staleLockReclaimMs = 30 * 60 * 1000; function processIsAlive(pid) { if (!Number.isInteger(pid) || pid <= 0) return false; @@ -74,6 +76,7 @@ function lockIsOldEnoughToRecover(lockPath, now = Date.now()) { * baseDirectory?: string; * repositoryIdentity?: string; * processId?: number; + * forceLockRelease?: boolean; * }} options */ export function acquireHeavyRunLock({ @@ -83,6 +86,7 @@ export function acquireHeavyRunLock({ baseDirectory, repositoryIdentity = resolveRepositoryIdentity(projectRoot), processId = process.pid, + forceLockRelease = false, }) { if (!projectRoot) throw new Error("projectRoot is required for the Database heavyweight-run lock."); const lockPath = lockPathFor(repositoryIdentity, baseDirectory); @@ -116,6 +120,18 @@ export function acquireHeavyRunLock({ }; writeFileSync(path.join(lockPath, "owner.json"), `${JSON.stringify(owner, null, 2)}\n`, "utf8"); let released = false; + const ownerFile = path.join(lockPath, "owner.json"); + // Keep mtime fresh while the holder process event loop is alive. Pair with + // async child execution in run-heavy.mjs so heartbeat timers can fire. + const heartbeatInterval = setInterval(() => { + try { + const now = new Date(); + utimesSync(ownerFile, now, now); + } catch { + // Ignore if the lock was already released. + } + }, staleLockHeartbeatMs); + heartbeatInterval.unref?.(); return { path: lockPath, owner, @@ -128,12 +144,35 @@ export function acquireHeavyRunLock({ release() { if (released) return; released = true; + clearInterval(heartbeatInterval); if (readOwner(lockPath)?.token === token) rmSync(lockPath, { recursive: true, force: true }); }, }; } catch (error) { if (error?.code !== "EEXIST") throw error; const owner = readOwner(lockPath); + let isStale = false; + if (owner) { + if (!processIsAlive(owner.pid)) { + isStale = true; + } else { + try { + const mtime = statSync(path.join(lockPath, "owner.json")).mtimeMs; + if (Date.now() - mtime > staleLockReclaimMs) isStale = true; + } catch { + isStale = true; + } + } + } + + if (forceLockRelease || isStale) { + console.warn( + `[AUDIT] Breaking ${isStale ? "stale" : "forced"} heavyweight lock at ${lockPath} (was PID ${owner?.pid || "unknown"})`, + ); + rmSync(lockPath, { recursive: true, force: true }); + continue; + } + if (owner && processIsAlive(owner.pid)) { if (attempt < 15) { // 15 attempts, approx 30s diff --git a/src/components/clinical-dashboard/DocumentManagerPanel.tsx b/src/components/clinical-dashboard/DocumentManagerPanel.tsx index 43593f925..92fe53380 100644 --- a/src/components/clinical-dashboard/DocumentManagerPanel.tsx +++ b/src/components/clinical-dashboard/DocumentManagerPanel.tsx @@ -815,29 +815,33 @@ export function LibraryHealthStrip({ { target: "documents" as const, label: "Documents", - value: loading ? "Loading" : `${indexedDocuments} indexed`, + value: loading ? "" : `${indexedDocuments} indexed`, tone: loading ? toneNeutral : indexedDocuments ? toneSuccess : toneWarning, actionLabel: "Show indexed document files", }, { target: "setup" as const, label: "Setup", - value: `${readyChecks}/${checks.length || fallbackSetupChecks.length} ready`, - tone: readyChecks === (checks.length || fallbackSetupChecks.length) ? toneSuccess : toneWarning, + value: loading ? "" : `${readyChecks}/${checks.length || fallbackSetupChecks.length} ready`, + tone: loading + ? toneNeutral + : readyChecks === (checks.length || fallbackSetupChecks.length) + ? toneSuccess + : toneWarning, actionLabel: "Show setup checks", }, { target: "indexing" as const, label: "Indexing", - value: activeJobs + activeBatches ? `${activeJobs + activeBatches} active` : "Idle", - tone: activeJobs + activeBatches ? toneInfo : toneNeutral, + value: loading ? "" : activeJobs + activeBatches ? `${activeJobs + activeBatches} active` : "Idle", + tone: loading ? toneNeutral : activeJobs + activeBatches ? toneInfo : toneNeutral, actionLabel: "Show indexing progress", }, { target: "failures" as const, label: "Failures", - value: failedWork ? `${failedWork} needs review` : "None", - tone: failedWork ? toneDanger : toneNeutral, + value: loading ? "" : failedWork ? `${failedWork} needs review` : "None", + tone: loading ? toneNeutral : failedWork ? toneDanger : toneNeutral, actionLabel: "Show failed indexing work", }, ]; @@ -847,6 +851,7 @@ export function LibraryHealthStrip({ data-testid="library-health-strip" className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-3 shadow-[var(--shadow-inset)]" aria-label="Library health" + style={{ containIntrinsicSize: "auto 76px", contentVisibility: "auto" }} >

Library health

@@ -863,9 +868,18 @@ export function LibraryHealthStrip({ item.tone, )} aria-label={item.actionLabel} + aria-busy={loading || undefined} + style={{ containIntrinsicSize: "auto 48px", contentVisibility: "auto" }} >

{item.label}

-

{item.value}

+ {loading ? ( +
+ ) : ( +

{item.value}

+ )} ))}
diff --git a/tests/audit-remediation-tooling.test.ts b/tests/audit-remediation-tooling.test.ts new file mode 100644 index 000000000..da569d440 --- /dev/null +++ b/tests/audit-remediation-tooling.test.ts @@ -0,0 +1,51 @@ +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); + +describe("skill:create scaffolding", () => { + it("writes the interface openai.yaml shape used by existing skills", () => { + const source = readFileSync(path.join(repositoryRoot, "scripts/skill-create.mjs"), "utf8"); + expect(source).toContain("interface:"); + expect(source).toContain("display_name:"); + expect(source).toContain("short_description:"); + expect(source).toContain("default_prompt:"); + expect(source).toContain("Usage: npm run skill:create -- "); + expect(source).toContain("Use $${skillName}"); + expect(source).not.toContain('short_description: "Short description'); + expect(source).not.toMatch(/openaiYamlContent = `name: \$\{skillName\}/); + }); +}); + +describe("branch:cleanup dry-run default", () => { + it("defaults to dry-run and deletes with argv-safe git calls", () => { + const source = readFileSync(path.join(repositoryRoot, "scripts/sweep-merged-branches.mjs"), "utf8"); + expect(source).toContain("Dry-run by default"); + expect(source).toContain("--apply"); + expect(source).toContain('runGit(["branch", "-d", branch])'); + expect(source).not.toContain("`git branch -d ${branch}`"); + expect(source).not.toContain("execSync"); + }); +}); + +describe("run-heavy lock holders stay async", () => { + it("uses spawn instead of spawnSync so lock heartbeats can fire", () => { + const source = readFileSync(path.join(repositoryRoot, "scripts/run-heavy.mjs"), "utf8"); + expect(source).toContain('import { spawn } from "node:child_process"'); + expect(source).not.toContain("spawnSync"); + expect(source).toContain("forceLockRelease"); + expect(source).toContain("await runNpmScript()"); + }); +}); + +describe("eval-retrieval parallelization", () => { + it("keeps a single results binding after parallel map", () => { + const source = readFileSync(path.join(repositoryRoot, "scripts/eval-retrieval.ts"), "utf8"); + const resultsBindings = source.match(/const results\b/g) ?? []; + expect(resultsBindings).toHaveLength(1); + expect(source).toContain("const results: GoldenRetrievalResult[] = await Promise.all("); + expect(source).toContain('args.mode === "latency" ? 1 : 5'); + }); +}); diff --git a/tests/test-runner-safety.test.ts b/tests/test-runner-safety.test.ts index ef2367e42..79ed93dbb 100644 --- a/tests/test-runner-safety.test.ts +++ b/tests/test-runner-safety.test.ts @@ -117,6 +117,32 @@ describe("repository-wide heavyweight lock", () => { replacement.release(); }); + it("allows an explicit force-lock-release to replace a live owner", () => { + const baseDirectory = temporaryDirectory("clinical-kb-force-lock-"); + const repositoryIdentity = path.join(baseDirectory, "shared.git"); + const first = acquireHeavyRunLock({ + projectRoot: path.join(baseDirectory, "worktree-a"), + repositoryIdentity, + baseDirectory, + environment: {}, + command: "first", + }); + + const replacement = acquireHeavyRunLock({ + projectRoot: path.join(baseDirectory, "worktree-b"), + repositoryIdentity, + baseDirectory, + environment: {}, + command: "replacement", + forceLockRelease: true, + }); + + expect(replacement.owner.token).not.toBe(first.owner.token); + expect(readFileSync(path.join(replacement.path, "owner.json"), "utf8")).toContain(replacement.owner.token); + first.release(); + replacement.release(); + }); + it("keeps a live owner's lock even when startedAt is older than five minutes", () => { const baseDirectory = temporaryDirectory("clinical-kb-live-old-lock-"); const repositoryIdentity = path.join(baseDirectory, "shared.git"); From 960bb32ef258daf79bf928f13d03b621c949e3f2 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 25 Jul 2026 22:42:40 +0800 Subject: [PATCH 2/4] docs(ledger): record #1186 superseding remediation review --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index f0a4938e4..6b18606df 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -956,3 +956,4 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-25 | execute-audit-code-remediation (PR #1162) | f07c711a0e1ee853b128733a51db182d8192c3c4 | CI unblock after Bugbot | Fixed Prettier (11 files), restored package-lock/.npmrc to main so blocking npm audit is advisory (lockfile_changed=false), updated mobile-composer-reserve contract for answer-home hero breakpoint. Prior tip e5c8c49c had Static/Safety/Unit failures. | format:check; focused Vitest 41; ci-change-scope lockfile_changed=false; no provider-backed checks. | | 2026-07-25 | execute-audit-code-remediation (PR #1162) | 1de1b32f562c0a972997750a5dc97a02ab1a9c15 | Production UI fix | Fixed ui-tools services referral header test (H1/quick-filters contract). Prior tip 09c6eb2d had Static/Safety/Unit/Migration green; only Production UI failed. | Local Playwright chromium services referral test PASS; no provider-backed checks. | | 2026-07-25 | execute-audit-code-remediation (PR #1162) | b9b56c140eb14cbba5a2c2230e3fa28d3a791add | CI unblock after bot sync | Tip 96188eca had PR required SUCCESS (Static/Safety/Unit/Build/Migration/Production UI). Hosted pr-branch-sync then merged main (a420b86b/b9b56c14), leaving CI action_required for bot-authored runs. Pushing agent commit to re-trigger non-bot CI. | Prior tip 96188eca hosted CI green; local services referral Playwright PASS; no provider-backed checks. | +| 2026-07-25 | PR supersede #1186 / `cursor/pr1186-audit-remediation-c94c` | `a38e83860510a4229d5658960657cd7448aff278` | Clean main-based port of intentional #1186 audit fixes | SUPERSEDE #1186 (do not merge old PR). Ported intentional 16-file delta onto current main; dropped conflicted checkpoint tree and placeholder skills. Fixed eval single results binding; async run-heavy so lock heartbeat fires; branch:cleanup dry-run default + argv-safe deletes; skill-create interface YAML. Close #1186. | Focused Vitest tooling+lock 6/6; check:skills 33; prettier on touched files; no provider/live eval runs. | From 4cb22e45dfe46b1975fa15ddd028c7e14ceb5fab Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:07:50 +0800 Subject: [PATCH 3/4] docs(ledger): record #1186 close and #1209 CI green --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 7c8347818..d0b9fd8ac 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -960,3 +960,4 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-25 | execute-audit-code-remediation (PR #1162) | b9b56c140eb14cbba5a2c2230e3fa28d3a791add | CI unblock after bot sync | Tip 96188eca had PR required SUCCESS (Static/Safety/Unit/Build/Migration/Production UI). Hosted pr-branch-sync then merged main (a420b86b/b9b56c14), leaving CI action_required for bot-authored runs. Pushing agent commit to re-trigger non-bot CI. | Prior tip 96188eca hosted CI green; local services referral Playwright PASS; no provider-backed checks. | | 2026-07-25 | PR supersede #1186 / `cursor/pr1186-audit-remediation-c94c` | `a38e83860510a4229d5658960657cd7448aff278` | Clean main-based port of intentional #1186 audit fixes | SUPERSEDE #1186 (do not merge old PR). Ported intentional 16-file delta onto current main; dropped conflicted checkpoint tree and placeholder skills. Fixed eval single results binding; async run-heavy so lock heartbeat fires; branch:cleanup dry-run default + argv-safe deletes; skill-create interface YAML. Close #1186. | Focused Vitest tooling+lock 6/6; check:skills 33; prettier on touched files; no provider/live eval runs. | | 2026-07-25 | `cursor/ledger-066-067-519b` | f04392a408eceee14215c15169cbd6b70ac2041c | Close stale ledger #066/#067 (+ drop resolved #030/#075 from queue) | READY. #066 proven on main via #1174; #067 already fixed in #1191 in-process preflight. Docs-only ledger sync; no code change. | Local proof: `git show`/`log` for #1174/#1191; preflight test already in-process on main; ledger integrity asserts. No providers. | +| 2026-07-25 | execute-audit-remediation-plan (PR #1188) | 8b8639113925601e1687bfe4f1f29c44a4308b61 | Bugbot/diff-review: maintainability remediation tip | BLOCK: tip tree carries unresolved conflict markers (answer/upload APIs, clinical-dashboard, services, tests); invalid `async export function sha256Hex` in indexing-v3 utils; ClinicalDashboard still calls removed `renderSystemNotice`; merge-tree vs main conflicts in check-github-action-pins.mjs + ui-primitives.tsx. Intended notices extraction/dynamic imports look mostly sound; search-scope/migration not in three-dot product delta. | `git grep` conflict markers on tip (none on origin/main); `git show` for ClinicalDashboard:3824 + utils.ts:191; `git merge-tree --write-tree origin/main 8b8639113`; no provider-backed checks. | From 61f6df8b487f99191d6d6716736a2a5653dbee68 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:08:39 +0800 Subject: [PATCH 4/4] docs(ledger): record #1186 closeout and #1209 CI green --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index d0b9fd8ac..9381e74bf 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -961,3 +961,4 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-25 | PR supersede #1186 / `cursor/pr1186-audit-remediation-c94c` | `a38e83860510a4229d5658960657cd7448aff278` | Clean main-based port of intentional #1186 audit fixes | SUPERSEDE #1186 (do not merge old PR). Ported intentional 16-file delta onto current main; dropped conflicted checkpoint tree and placeholder skills. Fixed eval single results binding; async run-heavy so lock heartbeat fires; branch:cleanup dry-run default + argv-safe deletes; skill-create interface YAML. Close #1186. | Focused Vitest tooling+lock 6/6; check:skills 33; prettier on touched files; no provider/live eval runs. | | 2026-07-25 | `cursor/ledger-066-067-519b` | f04392a408eceee14215c15169cbd6b70ac2041c | Close stale ledger #066/#067 (+ drop resolved #030/#075 from queue) | READY. #066 proven on main via #1174; #067 already fixed in #1191 in-process preflight. Docs-only ledger sync; no code change. | Local proof: `git show`/`log` for #1174/#1191; preflight test already in-process on main; ledger integrity asserts. No providers. | | 2026-07-25 | execute-audit-remediation-plan (PR #1188) | 8b8639113925601e1687bfe4f1f29c44a4308b61 | Bugbot/diff-review: maintainability remediation tip | BLOCK: tip tree carries unresolved conflict markers (answer/upload APIs, clinical-dashboard, services, tests); invalid `async export function sha256Hex` in indexing-v3 utils; ClinicalDashboard still calls removed `renderSystemNotice`; merge-tree vs main conflicts in check-github-action-pins.mjs + ui-primitives.tsx. Intended notices extraction/dynamic imports look mostly sound; search-scope/migration not in three-dot product delta. | `git grep` conflict markers on tip (none on origin/main); `git show` for ClinicalDashboard:3824 + utils.ts:191; `git merge-tree --write-tree origin/main 8b8639113`; no provider-backed checks. | +| 2026-07-25 | PR #1209 / `cursor/pr1186-audit-remediation-c94c` | `4cb22e45dfe46b1975fa15ddd028c7e14ceb5fab` | Close #1186 + babysit #1209 CI | DONE. Closed #1186 as superseded. Synced origin/main (MERGEABLE/CLEAN). Fixed PR-policy RAG impact line. Hosted PR required SUCCESS (Static/Safety/Unit/Build/Production UI/Advisory UI/containers) on pre-ledger tip; docs-only follow-up pushed. Ready to merge; auto-merge not enabled. | gh pr checks; local policy ok; no provider/eval runs. |