ruvnet-brain 4.7.20 → 4.7.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/README.md +2 -2
  2. package/bin/install.mjs +494 -97
  3. package/kb/download-retry.mjs +93 -21
  4. package/kb/forge-guard-injection.mjs +277 -0
  5. package/kb/forge-update.mjs +166 -38
  6. package/kb/lifecycle-evidence-retention.mjs +151 -14
  7. package/kb/model-requirements.mjs +4 -1
  8. package/kb/refresh-run.mjs +175 -27
  9. package/kb/verify-citation.mjs +15 -2
  10. package/package.json +3 -1
  11. package/plugin/.claude-plugin/plugin.json +1 -1
  12. package/plugin/.codex-plugin/plugin.json +1 -1
  13. package/plugin/mcp/managed-cli-interface.mjs +16 -8
  14. package/plugin/scripts/adr-currency-gate.mjs +2 -1
  15. package/plugin/scripts/advisory-contract.mjs +21 -0
  16. package/plugin/scripts/advocacy-route.mjs +5 -4
  17. package/plugin/scripts/agentdb-recall.mjs +15 -5
  18. package/plugin/scripts/automatic-update.mjs +9 -1
  19. package/plugin/scripts/brain-confirmation.mjs +2 -1
  20. package/plugin/scripts/brain-footprint.mjs +72 -11
  21. package/plugin/scripts/capability-registry.mjs +1 -1
  22. package/plugin/scripts/capacity-aware-parallel-work.mjs +2 -1
  23. package/plugin/scripts/codex-overnight-notice.mjs +33 -0
  24. package/plugin/scripts/completion-claim-evidence.mjs +56 -12
  25. package/plugin/scripts/continuation-gate.mjs +28 -9
  26. package/plugin/scripts/decision-gate.mjs +13 -63
  27. package/plugin/scripts/decision-outcomes.mjs +36 -0
  28. package/plugin/scripts/decision-recall-gate.mjs +40 -5
  29. package/plugin/scripts/decision-record.mjs +8 -3
  30. package/plugin/scripts/degradation-watch.mjs +2 -1
  31. package/plugin/scripts/developer-update-lock.mjs +172 -33
  32. package/plugin/scripts/developer-update-owner.mjs +8 -5
  33. package/plugin/scripts/developer-update-policy.mjs +4 -1
  34. package/plugin/scripts/developer-update.mjs +155 -53
  35. package/plugin/scripts/doc-currency.mjs +2 -16
  36. package/plugin/scripts/duplicate-gate.mjs +2 -1
  37. package/plugin/scripts/entrypoint.mjs +34 -0
  38. package/plugin/scripts/footprint-io.mjs +135 -8
  39. package/plugin/scripts/grok-hook-adapter.mjs +2 -1
  40. package/plugin/scripts/ground-ruvnet.sh +6 -3
  41. package/plugin/scripts/grounding-answer.mjs +2 -1
  42. package/plugin/scripts/grounding-code-projection.mjs +2 -1
  43. package/plugin/scripts/grounding-substance.mjs +2 -1
  44. package/plugin/scripts/grounding-turn-gate.mjs +2 -2
  45. package/plugin/scripts/grounding-turn-mark.mjs +2 -2
  46. package/plugin/scripts/hook-context-budget.mjs +2 -1
  47. package/plugin/scripts/hook-input.mjs +2 -2
  48. package/plugin/scripts/hook-shim.mjs +9 -2
  49. package/plugin/scripts/host-update.mjs +29 -5
  50. package/plugin/scripts/identifier-preflight.mjs +2 -1
  51. package/plugin/scripts/kb-copy-proof.mjs +57 -9
  52. package/plugin/scripts/learn-capture.mjs +2 -1
  53. package/plugin/scripts/learning-store.mjs +62 -16
  54. package/plugin/scripts/lesson-bridge.mjs +2 -2
  55. package/plugin/scripts/lesson-promote.mjs +83 -22
  56. package/plugin/scripts/md-stamp.mjs +2 -1
  57. package/plugin/scripts/native-turn-transcript.mjs +98 -6
  58. package/plugin/scripts/nightly-scheduler.mjs +10 -5
  59. package/plugin/scripts/package-recommender.mjs +2 -2
  60. package/plugin/scripts/project-store-resolver.mjs +19 -0
  61. package/plugin/scripts/routing-outcome-capture.mjs +2 -1
  62. package/plugin/scripts/session-snapshot-hook.mjs +24 -4
  63. package/plugin/scripts/session-start-core.mjs +5 -15
  64. package/plugin/scripts/session-start-health.mjs +97 -10
  65. package/plugin/scripts/session-start-issue-alert.mjs +63 -0
  66. package/plugin/scripts/session-start-update-plane.mjs +15 -1
  67. package/plugin/scripts/signal-watch.mjs +2 -1
  68. package/plugin/scripts/spend-guard.mjs +2 -1
  69. package/plugin/scripts/substitution-wall.mjs +2 -1
  70. package/plugin/scripts/turn-outcome-capture.mjs +7 -3
  71. package/plugin/scripts/unprompted-runtime.mjs +20 -3
  72. package/plugin/scripts/update-apply.mjs +186 -15
  73. package/plugin/skills/release-proof/scripts/release-proof.mjs +2 -1
  74. package/plugin/skills/ruvnet-brain/PLAYBOOK.md +2 -2
  75. package/plugin/skills/ruvnet-brain/SKILL.md +3 -2
  76. package/scripts/adr-072-completion.mjs +2 -1
  77. package/scripts/agentdb-context.mjs +2 -1
  78. package/scripts/agentdb-fleet-doctor.mjs +30 -8
  79. package/scripts/approved-runtime.mjs +2 -1
  80. package/scripts/architecture-review-lock.mjs +2 -1
  81. package/scripts/ascii-drift.mjs +2 -1
  82. package/scripts/brain-latency-50.mjs +2 -1
  83. package/scripts/brain-novice-50.mjs +2 -16
  84. package/scripts/brain-score.mjs +13 -2
  85. package/scripts/build-bundle.mjs +2 -1
  86. package/scripts/build-concepts.mjs +2 -1
  87. package/scripts/candidate-ci-receipt.mjs +2 -1
  88. package/scripts/candidate-host-evidence.mjs +2 -1
  89. package/scripts/card-from-source.mjs +2 -1
  90. package/scripts/ci/warm-brain-models.mjs +2 -1
  91. package/scripts/claims-verify.mjs +7 -3
  92. package/scripts/claude-controlled-terminal.mjs +2 -1
  93. package/scripts/claude-terminal-mod.mjs +2 -1
  94. package/scripts/code-release-corpus.mjs +2 -1
  95. package/scripts/codex-hook-trust-reconcile.mjs +67 -1
  96. package/scripts/codex-hook-trust.mjs +40 -1
  97. package/scripts/codex-hook-wrapper-history.mjs +2 -1
  98. package/scripts/codex-host-execution-proof.mjs +2 -1
  99. package/scripts/completion-claim-replay.mjs +2 -1
  100. package/scripts/console-engine.mjs +8 -3
  101. package/scripts/corpus-canary.mjs +2 -1
  102. package/scripts/corpus-candidate.mjs +2 -2
  103. package/scripts/corpus-coverage-sidecar.mjs +2 -1
  104. package/scripts/corpus-dispatch-decision.mjs +2 -1
  105. package/scripts/corpus-dispatch-receipt.mjs +2 -1
  106. package/scripts/corpus-next-seed.mjs +2 -1
  107. package/scripts/corpus-qa.mjs +2 -1
  108. package/scripts/corpus-reconcile.mjs +2 -2
  109. package/scripts/corpus-watchdog.mjs +2 -1
  110. package/scripts/count-chunks.mjs +2 -1
  111. package/scripts/customer-state-matrix.mjs +2 -1
  112. package/scripts/derive-passage-content-map.mjs +2 -1
  113. package/scripts/development-push-check.mjs +2 -1
  114. package/scripts/dispatch-receipt.mjs +2 -1
  115. package/scripts/doc-currency.mjs +2 -1
  116. package/scripts/dream-issue-gate.mjs +2 -1
  117. package/scripts/dual-host-deliberation.mjs +2 -16
  118. package/scripts/dual-host-suggest.mjs +2 -16
  119. package/scripts/eval-brain.mjs +7 -3
  120. package/scripts/execution-policy.mjs +2 -1
  121. package/scripts/execution-preflight.mjs +2 -1
  122. package/scripts/falsify.mjs +2 -1
  123. package/scripts/fix-metaharness-memretrieve.mjs +2 -1
  124. package/scripts/fix-workstream.mjs +2 -1
  125. package/scripts/full-hints.mjs +2 -1
  126. package/scripts/full-suite-gate.mjs +2 -1
  127. package/scripts/grok-host.mjs +68 -0
  128. package/scripts/hook-qualify-core.mjs +3 -1
  129. package/scripts/hook-qualify.mjs +2 -1
  130. package/scripts/hook-retirement-check.mjs +2 -1
  131. package/scripts/host-registry.mjs +2 -1
  132. package/scripts/independent-review-receipt.mjs +2 -1
  133. package/scripts/ingest-new-repos.mjs +2 -1
  134. package/scripts/ingest-repo.mjs +12 -2
  135. package/scripts/integration-evidence.mjs +2 -1
  136. package/scripts/issue-fix.mjs +7 -24
  137. package/scripts/issue-watch.mjs +116 -40
  138. package/scripts/job-heartbeat.sh +54 -19
  139. package/scripts/key-canary.mjs +9 -3
  140. package/scripts/latency-to-surface.mjs +2 -1
  141. package/scripts/learning-replay-cli.mjs +97 -19
  142. package/scripts/learning-replay-execution.mjs +2 -0
  143. package/scripts/learning-replay-fixture.mjs +3 -0
  144. package/scripts/learning-replay-proof.mjs +14 -13
  145. package/scripts/learning-replay.mjs +3 -3
  146. package/scripts/learnings.mjs +2 -1
  147. package/scripts/lesson-ratify.mjs +88 -40
  148. package/scripts/loop-checkpoint.mjs +2 -1
  149. package/scripts/metaharness-receipts.mjs +2 -1
  150. package/scripts/model-catalog.mjs +2 -1
  151. package/scripts/model-currency-evidence.mjs +6 -4
  152. package/scripts/model-currency.mjs +33 -3
  153. package/scripts/model-native-catalog.mjs +2 -1
  154. package/scripts/model-router-agent-hook.mjs +2 -1
  155. package/scripts/model-router-dispatch.mjs +2 -1
  156. package/scripts/model-router-engine.mjs +2 -1
  157. package/scripts/model-router-outcome.mjs +2 -1
  158. package/scripts/model-router-setup.mjs +2 -1
  159. package/scripts/model-routing-eval.mjs +2 -1
  160. package/scripts/model-routing-gateway.mjs +2 -1
  161. package/scripts/model-routing-launchers.mjs +4 -1
  162. package/scripts/model-terminal-gateway.mjs +2 -1
  163. package/scripts/model-terminal-launchers.mjs +2 -1
  164. package/scripts/model-weekly-analyst.mjs +2 -1
  165. package/scripts/model-weekly-cycle.mjs +2 -1
  166. package/scripts/model-weekly-qualification.mjs +2 -1
  167. package/scripts/nightly-gists.sh +26 -20
  168. package/scripts/nightly-two-run-proof.mjs +2 -1
  169. package/scripts/nightly-watchdog.mjs +158 -47
  170. package/scripts/no-silent-substitution.mjs +2 -1
  171. package/scripts/notify.sh +11 -6
  172. package/scripts/ntfy-inbox.mjs +41 -18
  173. package/scripts/onboarding-console.mjs +47 -8
  174. package/scripts/oracle/abstain-threshold-sweep.mjs +2 -1
  175. package/scripts/oracle/abstain-trace.mjs +2 -1
  176. package/scripts/oracle/doc2query-generate.mjs +2 -1
  177. package/scripts/oracle/doc2query-reach.mjs +2 -1
  178. package/scripts/oracle/judge-train.mjs +2 -1
  179. package/scripts/oracle/need-set-split.mjs +2 -1
  180. package/scripts/oracle/produce-questions.mjs +2 -16
  181. package/scripts/oracle/repo-recall.mjs +2 -2
  182. package/scripts/oracle/retrieval-accuracy.mjs +2 -2
  183. package/scripts/oracle/sona-query-adapter-eval.mjs +2 -1
  184. package/scripts/oracle/source-tree.mjs +2 -15
  185. package/scripts/oracle/source-units.mjs +2 -16
  186. package/scripts/oracle/spike-run.mjs +2 -16
  187. package/scripts/oracle/validate-labels.mjs +2 -16
  188. package/scripts/org-repo-count.mjs +2 -1
  189. package/scripts/package-cards.mjs +2 -1
  190. package/scripts/prepublication-evidence.mjs +2 -1
  191. package/scripts/private-overlay.mjs +2 -1
  192. package/scripts/product-integrity-contract.mjs +2 -1
  193. package/scripts/protected-release-invocation.mjs +2 -1
  194. package/scripts/public-verification-abandon.mjs +2 -1
  195. package/scripts/public-verification-aggregate.mjs +2 -1
  196. package/scripts/public-verification-finalizer.mjs +2 -1
  197. package/scripts/public-verification-inputs.mjs +2 -1
  198. package/scripts/public-verification-lane.mjs +2 -1
  199. package/scripts/publication-receipt.mjs +2 -1
  200. package/scripts/published-surface-probe.mjs +2 -1
  201. package/scripts/qe/card-lane-gate.mjs +2 -16
  202. package/scripts/qe/session-start-gate.mjs +2 -16
  203. package/scripts/qe/ux-suite.mjs +2 -1
  204. package/scripts/qualified-candidate-check.mjs +2 -1
  205. package/scripts/rebuild-gists-from-receipts.mjs +2 -1
  206. package/scripts/recommendation-eval.mjs +2 -1
  207. package/scripts/recommendation-floor.mjs +2 -1
  208. package/scripts/recommendation-judge-score.mjs +2 -1
  209. package/scripts/recommendation-real-host-score.mjs +2 -1
  210. package/scripts/recommendation-real-host.mjs +2 -1
  211. package/scripts/refresh-capability-only-store.mjs +2 -1
  212. package/scripts/refresh-model-catalog.mjs +2 -1
  213. package/scripts/rehearse-corpus-pipeline.mjs +2 -1
  214. package/scripts/release-authority.mjs +2 -1
  215. package/scripts/release-evidence-aggregate.mjs +2 -1
  216. package/scripts/release-payload.mjs +2 -1
  217. package/scripts/release-projection.mjs +2 -1
  218. package/scripts/release-proof.mjs +2 -1
  219. package/scripts/release-qualification-contract.mjs +64 -28
  220. package/scripts/release-qualification.mjs +8 -1
  221. package/scripts/release-vector.mjs +2 -1
  222. package/scripts/restore-local-ingests.mjs +2 -1
  223. package/scripts/retrieval-canary.mjs +2 -1
  224. package/scripts/route-cheap.mjs +2 -1
  225. package/scripts/route-gold-rank.mjs +2 -1
  226. package/scripts/route-index-memory.mjs +2 -1
  227. package/scripts/route-latency-warm.mjs +2 -1
  228. package/scripts/router-utilization.mjs +2 -1
  229. package/scripts/routing-flywheel.mjs +2 -1
  230. package/scripts/run-operational-benchmark.mjs +2 -1
  231. package/scripts/run-operational-benchmark.v3.mjs +2 -1
  232. package/scripts/rvf-index-audit.mjs +2 -16
  233. package/scripts/seal-gist-receipt.mjs +2 -1
  234. package/scripts/security-guidance-codex-compat.mjs +2 -1
  235. package/scripts/selfcheck.mjs +2 -1
  236. package/scripts/signal-watch.mjs +2 -1
  237. package/scripts/source-coverage.mjs +2 -1
  238. package/scripts/stabilization-receipt.mjs +2 -1
  239. package/scripts/stamp-sweep.mjs +2 -1
  240. package/scripts/status-honesty.mjs +2 -1
  241. package/scripts/sync-commands.mjs +2 -2
  242. package/scripts/top100-benchmark.mjs +2 -1
  243. package/scripts/top100-corpus.mjs +2 -1
  244. package/scripts/user-model-hook-install.mjs +2 -1
  245. package/scripts/user-model-prompt-hook.mjs +2 -1
  246. package/scripts/verify-bundle.mjs +3 -1
  247. package/scripts/verify-model-catalog.mjs +2 -1
  248. package/scripts/version.mjs +2 -1
  249. package/scripts/wired-check.mjs +11 -5
  250. package/scripts/worktree-integrity.mjs +2 -2
@@ -430,8 +430,31 @@ function wiringSurvey() {
430
430
  }
431
431
 
432
432
  // ── Memory health (read-only probes for the project the console was launched from) ────────────────
433
+ // Dream Cycle 2026-09-18 (memory-durability, round-trip-proof): this used to also pass on ANY
434
+ // `.claude/hooks` directory existing, so a machine whose hooks folder holds only an unrelated
435
+ // script (a different plugin, a stale leftover) scored `sessionSurfacing: ok` and printed "the
436
+ // global SessionStart hook surfaces project state at launch" — a claim about a hook that was never
437
+ // installed. Only the specific recall hook's presence can support that claim.
438
+ //
439
+ // Dream Cycle 2026-09-19 (memory-durability, managed-boundary): the fix above still checked only
440
+ // the LEGACY standalone `agentdb-ensure.sh` global hook. It ignored the mechanism this very repo
441
+ // ships and documents as the automatic one — `plugin/hooks/hooks.json`'s own SessionStart entry,
442
+ // which every session restores via `project-progression-session-start.mjs` (wired through
443
+ // `session-start-core.mjs`) whenever the `ruvnet-brain` plugin itself is enabled. A machine that
444
+ // installed this plugin the real, current way (Claude Code's marketplace flow) and never separately
445
+ // placed the legacy global shell hook scored `sessionSurfacing: warn` — a false negative, flagged
446
+ // as an open scope question by PR #296's independent critic, not chased there to keep that change
447
+ // to one conceptual fix. `pluginEnabled()` mirrors the exact `k.startsWith('ruvnet-brain@')` check
448
+ // `capability-registry.mjs`'s `wiredForRoute()` already uses for the same "is our plugin the one
449
+ // Claude Code will actually load" question, rather than re-deriving a second way to ask it.
450
+ function pluginEnabled() {
451
+ const settings = readJSON(path.join(CONSOLE_ROOT, '.claude', 'settings.json'));
452
+ return Object.entries(settings?.enabledPlugins || {}).some(([k, v]) => k.startsWith('ruvnet-brain@') && v === true);
453
+ }
433
454
  function sessionHookExists() {
434
- return fs.existsSync(path.join(CONSOLE_ROOT, '.claude/hooks/agentdb-ensure.sh')) || fs.existsSync(path.join(CONSOLE_ROOT, '.claude/hooks'));
455
+ if (fs.existsSync(path.join(CONSOLE_ROOT, '.claude/hooks/agentdb-ensure.sh'))) return 'agentdb-ensure';
456
+ if (pluginEnabled()) return 'ruvnet-brain-plugin';
457
+ return null;
435
458
  }
436
459
  // WHICH FILE IS THE PROJECT'S MEMORY STORE IS NOT A CONSTANT (issue #127).
437
460
  //
@@ -496,7 +519,12 @@ function probeMemory(projectDir, { now = Date.now() } = {}) {
496
519
  // Compaction survival is a ruflo write at PreCompact (project-state-auto-* + exact readback); a refusal
497
520
  // reaches the hook-failures summary. It is not re-probed at render.
498
521
  probes.compactionSurvival = { status: 'notTested', detail: 'PreCompact stores a project-state-auto-* checkpoint through ruflo and reads it back; failures appear in the SessionStart hook-failures summary' };
499
- probes.sessionSurfacing = sessionHookExists() ? { status: 'ok', detail: 'the global SessionStart hook surfaces project state at launch' } : { status: 'warn', detail: 'no SessionStart recall hook found' };
522
+ const sessionHookVia = sessionHookExists();
523
+ probes.sessionSurfacing = sessionHookVia === 'agentdb-ensure'
524
+ ? { status: 'ok', detail: 'the global SessionStart hook surfaces project state at launch', via: sessionHookVia }
525
+ : sessionHookVia === 'ruvnet-brain-plugin'
526
+ ? { status: 'ok', detail: 'the ruvnet-brain plugin\'s own SessionStart hook surfaces project state at launch', via: sessionHookVia }
527
+ : { status: 'warn', detail: 'no SessionStart recall hook found', via: null };
500
528
  // recall quality: honestly NOT probed at render (a true probe needs an embedding query; left for an explicit deep test)
501
529
  probes.recallQuality = { status: 'notTested', detail: 'not checked this session — a real recall probe needs an embedding round-trip, which render deliberately avoids' };
502
530
 
@@ -2613,12 +2641,8 @@ function apply(ids) {
2613
2641
  } : {});
2614
2642
  if (res.ok && learningBefore) {
2615
2643
  const after = observeLearning();
2616
- // `ruflo memory distill run` owns the patterns; progress here is a known, readable learner afterwards.
2617
- const progressed = learningBefore.statusKnown && after.statusKnown;
2618
- if (!progressed) {
2619
- res.ok = false;
2620
- res.log += `\nNo measured learning progress: queue ${after.queueDir}; learner ${after.learnerCwd}. Reload to inspect the remaining evidence.`;
2621
- }
2644
+ res.ok = learningProgressed(learningBefore, after);
2645
+ if (!res.ok) res.log += `\nNo measured learning progress: ${learningProgressDetail(learningBefore, after)}. Reload to inspect the remaining evidence.`;
2622
2646
  }
2623
2647
  phaseMs.childRemedyMs += elapsedMs(remedyStartedAt);
2624
2648
  results.push({ id, ...res, undoToken });
@@ -2628,6 +2652,21 @@ function apply(ids) {
2628
2652
  return { results, timings: { ...phaseMs, totalMs: elapsedMs(startedAt) } };
2629
2653
  }
2630
2654
 
2655
+ /**
2656
+ * #377: Apply on "Your learner has gone quiet" counts as progress only when `ruflo memory distill
2657
+ * status` shows the learner's last distillation moved FORWARD. A readable store alone is not progress.
2658
+ */
2659
+ export function learningProgressed(before, after) {
2660
+ const was = Date.parse(before?.lastDistillAt || '');
2661
+ const now = Date.parse(after?.lastDistillAt || '');
2662
+ return !!(before?.statusKnown && after?.statusKnown && after.distillKnown === true
2663
+ && Number.isFinite(now) && (!Number.isFinite(was) || now > was));
2664
+ }
2665
+ export function learningProgressDetail(before, after) {
2666
+ return `learner ${after?.learningDb || before?.learningDb || 'unresolved'}; last distillation `
2667
+ + `${before?.lastDistillAt || 'not recorded'} → ${after?.distillKnown === true ? (after.lastDistillAt || 'not recorded') : 'unknown'}`;
2668
+ }
2669
+
2631
2670
  function autoEligibleIds(recommendations = []) {
2632
2671
  return recommendations
2633
2672
  .filter((rec) => rec?.scope === 'project')
@@ -20,6 +20,7 @@ import fs from 'node:fs';
20
20
  import path from 'node:path';
21
21
  import { fileURLToPath } from 'node:url';
22
22
  import { wilson } from '../eval-brain.mjs';
23
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
23
24
 
24
25
  const rate = (k, n) => { const w = wilson(k, n); return { k, n, p: +w.p.toFixed(4), lo: +w.lo.toFixed(4), hi: +w.hi.toFixed(4) }; };
25
26
  const within5 = (r) => (r.fileRank != null && r.fileRank <= 5) || (r.altFileRank != null && r.altFileRank <= 5);
@@ -59,4 +60,4 @@ async function main() {
59
60
  }
60
61
  }
61
62
 
62
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main();
63
+ if (isDirectInvocation(import.meta.url)) await main();
@@ -28,6 +28,7 @@ import fs from 'node:fs';
28
28
  import path from 'node:path';
29
29
  import readline from 'node:readline';
30
30
  import { fileURLToPath, pathToFileURL } from 'node:url';
31
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
31
32
 
32
33
  const args = process.argv.slice(2);
33
34
  const arg = (n, d) => { const i = args.indexOf(n); return i >= 0 ? args[i + 1] : d; };
@@ -136,4 +137,4 @@ async function main() {
136
137
  process.exit(0);
137
138
  }
138
139
 
139
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main();
140
+ if (isDirectInvocation(import.meta.url)) await main();
@@ -23,6 +23,7 @@ import os from 'node:os';
23
23
  import { fileURLToPath } from 'node:url';
24
24
  import { claudeArgs, spawnHost, isQuotaRefusal } from './producer-hosts.mjs';
25
25
  import { subscriptionOnlyEnv } from '../subscription-hosts.mjs';
26
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
26
27
 
27
28
  export const EXCERPT_CHARS = 1500;
28
29
  export const D2Q_SCHEMA = Object.freeze({
@@ -159,4 +160,4 @@ async function main() {
159
160
  console.log(JSON.stringify({ files: docs.length, written, stopped: stop }));
160
161
  }
161
162
 
162
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main();
163
+ if (isDirectInvocation(import.meta.url)) await main();
@@ -22,6 +22,7 @@ import fs from 'node:fs';
22
22
  import path from 'node:path';
23
23
  import { fileURLToPath, pathToFileURL } from 'node:url';
24
24
  import { wilson } from '../eval-brain.mjs';
25
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
25
26
 
26
27
  /** Collapse ranked entry hits (each pointing at a file) to the first `files` distinct files. */
27
28
  export function filesFromEntries(hits, pathOfId, files) {
@@ -107,4 +108,4 @@ async function main() {
107
108
  process.exit(0);
108
109
  }
109
110
 
110
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main();
111
+ if (isDirectInvocation(import.meta.url)) await main();
@@ -21,6 +21,7 @@ import fs from 'node:fs';
21
21
  import path from 'node:path';
22
22
  import { fileURLToPath, pathToFileURL } from 'node:url';
23
23
  import { wilson } from '../eval-brain.mjs';
24
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
24
25
 
25
26
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
26
27
  const { JUDGE_FEATURES, judgeFeatures, judgeLogit } = await import(pathToFileURL(path.join(ROOT, 'kb/judge-rank.mjs')).href);
@@ -155,4 +156,4 @@ async function main() {
155
156
  console.log(JSON.stringify({ threshold: tau, trainedOn: model.trainedOn, heldout: report.heldout }, null, 1));
156
157
  }
157
158
 
158
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main();
159
+ if (isDirectInvocation(import.meta.url)) await main();
@@ -13,6 +13,7 @@ import { createHash } from 'node:crypto';
13
13
  import fs from 'node:fs';
14
14
  import path from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
16
17
 
17
18
  export function splitNeedSet(questions, { salt = 'v1', trainFraction = 0.5 } = {}) {
18
19
  const byRepo = new Map();
@@ -45,4 +46,4 @@ async function main() {
45
46
  console.log(JSON.stringify({ train: split.train.length, heldout: split.heldout.length, contentHash: set.contentHash }));
46
47
  }
47
48
 
48
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main();
49
+ if (isDirectInvocation(import.meta.url)) await main();
@@ -34,6 +34,7 @@ import {
34
34
  DEFAULT_ROLES, LABEL_SCHEMA, PRODUCER_MODELS, VERDICT_SCHEMA, generatorPrompt, hostAdapters, isQuotaRefusal,
35
35
  judgePrompt, spawnHost,
36
36
  } from './producer-hosts.mjs';
37
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
37
38
 
38
39
  export {
39
40
  CLAUDE_SYSTEM_PROMPT, CLAUDE_TIMEOUT_MS, CODEX_TIMEOUT_MS, DEFAULT_ROLES, JUDGE_SYSTEM_PROMPT, LABEL_SCHEMA,
@@ -275,19 +276,4 @@ export async function main(argv = process.argv.slice(2)) {
275
276
  return labels.suspended ? 75 : 0;
276
277
  }
277
278
 
278
- // Entry-point guard. Compares REALPATHS on both sides: path.resolve() normalizes a path but does
279
- // NOT follow symlinks, while import.meta.url IS symlink-resolved by Node. Through a symlink (npm bin
280
- // shims, wrapper scripts, and every os.tmpdir() path on macOS) the two sides disagree, so main()
281
- // never runs -- and because nothing throws, the process exits 0. A silent exit 0 is indistinguishable
282
- // from "ran, found nothing", which is how prepareCorpusCandidate once reported SUCCESS with no
283
- // archive on disk. Reproduced live 2026-07-27; pinned by tests/unit/entrypoint-symlink.test.mjs.
284
- function isDirectInvocation() {
285
- try {
286
- if (!process.argv[1]) return false;
287
- return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
288
- } catch {
289
- return false;
290
- }
291
- }
292
-
293
- if (isDirectInvocation()) process.exitCode = await main();
279
+ if (isDirectInvocation(import.meta.url)) process.exitCode = await main();
@@ -36,6 +36,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
36
36
  import { extractZip } from '../../kb/zip-extract.mjs';
37
37
  import { validateCoverageLedger } from '../coverage-integrity.mjs';
38
38
  import { retiredFixtureStores } from '../fixture-denominator.mjs';
39
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
39
40
 
40
41
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
41
42
 
@@ -556,8 +557,7 @@ export async function main(argv = process.argv.slice(2), { searchAll = null } =
556
557
  // archive on disk (measured 2026-09-14).
557
558
  function isMain() {
558
559
  try {
559
- if (!process.argv[1]) return false;
560
- return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
560
+ return isDirectInvocation(import.meta.url);
561
561
  } catch { return false; }
562
562
  }
563
563
 
@@ -54,6 +54,7 @@ import os from 'node:os';
54
54
  import path from 'node:path';
55
55
  import { fileURLToPath } from 'node:url';
56
56
  import { extractZip } from '../../kb/zip-extract.mjs';
57
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
57
58
 
58
59
  const HERE = path.dirname(fileURLToPath(import.meta.url));
59
60
  const ROOT = path.resolve(HERE, '..', '..');
@@ -871,8 +872,7 @@ export async function main(argv = process.argv.slice(2)) {
871
872
  // with no archive and no receipt on disk. Same defect, same fix as plugin/scripts/hook-input.mjs:518.
872
873
  function isMain() {
873
874
  try {
874
- if (!process.argv[1]) return false;
875
- return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
875
+ return isDirectInvocation(import.meta.url);
876
876
  } catch {
877
877
  return false;
878
878
  }
@@ -22,6 +22,7 @@ import path from 'node:path';
22
22
  import { createRequire } from 'node:module';
23
23
  import { fileURLToPath, pathToFileURL } from 'node:url';
24
24
  import { wilson } from '../eval-brain.mjs';
25
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
25
26
 
26
27
  /** Rank (1-based) of the first hit whose path is `gold`, or null within the list. */
27
28
  export function rankOf(paths, gold) {
@@ -136,4 +137,4 @@ async function main() {
136
137
  process.exit(0);
137
138
  }
138
139
 
139
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main();
140
+ if (isDirectInvocation(import.meta.url)) await main();
@@ -20,6 +20,7 @@ import fs from 'node:fs';
20
20
  import path from 'node:path';
21
21
  import { spawnSync } from 'node:child_process';
22
22
  import { fileURLToPath } from 'node:url';
23
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
23
24
 
24
25
  export const SOURCE_TREE_VERSION = 'oracle-source-tree/1';
25
26
  export const LFS_POINTER_PREFIX = 'version https://git-lfs.github.com/spec/v1';
@@ -148,18 +149,4 @@ export function main(argv = process.argv.slice(2)) {
148
149
  return 0;
149
150
  }
150
151
 
151
- // Entry-point guard. Compares REALPATHS on both sides: path.resolve() normalizes a path but does
152
- // NOT follow symlinks, while import.meta.url IS symlink-resolved by Node. Through a symlink (npm bin
153
- // shims, wrapper scripts, and every os.tmpdir() path on macOS) the two sides disagree, so main()
154
- // never runs -- and because nothing throws, the process exits 0. Pinned by
155
- // tests/unit/entrypoint-symlink.test.mjs.
156
- function isDirectInvocation() {
157
- try {
158
- if (!process.argv[1]) return false;
159
- return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
160
- } catch {
161
- return false;
162
- }
163
- }
164
-
165
- if (isDirectInvocation()) process.exitCode = main();
152
+ if (isDirectInvocation(import.meta.url)) process.exitCode = main();
@@ -25,6 +25,7 @@ import { createHash } from 'node:crypto';
25
25
  import { execFileSync } from 'node:child_process';
26
26
  import { fileURLToPath } from 'node:url';
27
27
  import { parse as babelParse } from '@babel/parser';
28
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
28
29
 
29
30
  export const RULES_VERSION = 'oracle-source-units/1';
30
31
  export const MAX_SELECTED = 100;
@@ -373,19 +374,4 @@ export function main(argv = process.argv.slice(2)) {
373
374
  return 0;
374
375
  }
375
376
 
376
- // Entry-point guard. Compares REALPATHS on both sides: path.resolve() normalizes a path but does
377
- // NOT follow symlinks, while import.meta.url IS symlink-resolved by Node. Through a symlink (npm bin
378
- // shims, wrapper scripts, and every os.tmpdir() path on macOS) the two sides disagree, so main()
379
- // never runs -- and because nothing throws, the process exits 0. A silent exit 0 is indistinguishable
380
- // from "ran, found nothing", which is how prepareCorpusCandidate once reported SUCCESS with no
381
- // archive on disk. Reproduced live 2026-07-27; pinned by tests/unit/entrypoint-symlink.test.mjs.
382
- function isDirectInvocation() {
383
- try {
384
- if (!process.argv[1]) return false;
385
- return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
386
- } catch {
387
- return false;
388
- }
389
- }
390
-
391
- if (isDirectInvocation()) process.exitCode = main();
377
+ if (isDirectInvocation(import.meta.url)) process.exitCode = main();
@@ -17,6 +17,7 @@ import { fileURLToPath } from 'node:url';
17
17
  import { buildInventory, resolveCommit } from './source-units.mjs';
18
18
  import { produceQuestions, DEFAULT_BATCH } from './produce-questions.mjs';
19
19
  import { loadBgeEmbedder, validateLabels } from './validate-labels.mjs';
20
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
20
21
 
21
22
  function arg(argv, flag) { const i = argv.indexOf(flag); return i >= 0 ? argv[i + 1] : undefined; }
22
23
  const writeJson = (file, value) => fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
@@ -80,19 +81,4 @@ export async function main(argv = process.argv.slice(2)) {
80
81
  return 0;
81
82
  }
82
83
 
83
- // Entry-point guard. Compares REALPATHS on both sides: path.resolve() normalizes a path but does
84
- // NOT follow symlinks, while import.meta.url IS symlink-resolved by Node. Through a symlink (npm bin
85
- // shims, wrapper scripts, and every os.tmpdir() path on macOS) the two sides disagree, so main()
86
- // never runs -- and because nothing throws, the process exits 0. A silent exit 0 is indistinguishable
87
- // from "ran, found nothing", which is how prepareCorpusCandidate once reported SUCCESS with no
88
- // archive on disk. Reproduced live 2026-07-27; pinned by tests/unit/entrypoint-symlink.test.mjs.
89
- function isDirectInvocation() {
90
- try {
91
- if (!process.argv[1]) return false;
92
- return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
93
- } catch {
94
- return false;
95
- }
96
- }
97
-
98
- if (isDirectInvocation()) process.exitCode = await main();
84
+ if (isDirectInvocation(import.meta.url)) process.exitCode = await main();
@@ -23,6 +23,7 @@ import fs from 'node:fs';
23
23
  import path from 'node:path';
24
24
  import { fileURLToPath } from 'node:url';
25
25
  import { gitBlobSha, sha256Hex, unitText } from './source-units.mjs';
26
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
26
27
 
27
28
  export const THRESHOLDS = Object.freeze({
28
29
  minSpanChars: 40, minSpanTokens: 6, maxSpanFractionOfUnit: 0.9,
@@ -232,19 +233,4 @@ export async function main(argv = process.argv.slice(2)) {
232
233
  return 0;
233
234
  }
234
235
 
235
- // Entry-point guard. Compares REALPATHS on both sides: path.resolve() normalizes a path but does
236
- // NOT follow symlinks, while import.meta.url IS symlink-resolved by Node. Through a symlink (npm bin
237
- // shims, wrapper scripts, and every os.tmpdir() path on macOS) the two sides disagree, so main()
238
- // never runs -- and because nothing throws, the process exits 0. A silent exit 0 is indistinguishable
239
- // from "ran, found nothing", which is how prepareCorpusCandidate once reported SUCCESS with no
240
- // archive on disk. Reproduced live 2026-07-27; pinned by tests/unit/entrypoint-symlink.test.mjs.
241
- function isDirectInvocation() {
242
- try {
243
- if (!process.argv[1]) return false;
244
- return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
245
- } catch {
246
- return false;
247
- }
248
- }
249
-
250
- if (isDirectInvocation()) process.exitCode = await main();
236
+ if (isDirectInvocation(import.meta.url)) process.exitCode = await main();
@@ -28,6 +28,7 @@ import fs from 'node:fs';
28
28
  import path from 'node:path';
29
29
  import { spawnSync } from 'node:child_process';
30
30
  import { fileURLToPath } from 'node:url';
31
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
31
32
 
32
33
  const ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
33
34
  /** The last LIVE reading, so an offline build reuses a real measurement instead of a literal. */
@@ -103,7 +104,7 @@ export function orgRepoCount({ owner = OWNER, file = RECORD_PATH, now = new Date
103
104
  * working tree clean for `release-qe`'s stabilization seal.
104
105
  */
105
106
  const isMain = (() => {
106
- try { return process.argv[1] && fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url); }
107
+ try { return isDirectInvocation(import.meta.url); }
107
108
  catch { return false; }
108
109
  })();
109
110
 
@@ -40,6 +40,7 @@ import fs from 'node:fs';
40
40
  import path from 'node:path';
41
41
  import readline from 'node:readline';
42
42
  import { fileURLToPath } from 'node:url';
43
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
43
44
 
44
45
  const ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
45
46
  export const SCHEMA = 'ruvnet-brain.package-cards/1';
@@ -321,7 +322,7 @@ export function snapshotAgeDays(doc, now = Date.now()) {
321
322
  }
322
323
 
323
324
  const isMain = (() => {
324
- try { return process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url); }
325
+ try { return isDirectInvocation(import.meta.url); }
325
326
  catch { return false; }
326
327
  })();
327
328
 
@@ -10,6 +10,7 @@ import { readCandidateRetrieval } from './staged-host-verifier.mjs';
10
10
  import { validateRetrievalCanaryReceipt } from './retrieval-canary.mjs';
11
11
  import { payloadIdFor } from './release-payload.mjs';
12
12
  import { HOST_WARMUP_TIMEOUT_MS, RELEASE_SEARCH_DEADLINE_MS } from './host-install-matrix.mjs';
13
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
13
14
 
14
15
  const sha256 = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex');
15
16
 
@@ -191,7 +192,7 @@ export function buildPrepublicationEvidence({
191
192
  return { leaves, envelope: aggregateEvidence({ sha, payloadId: payload.payloadId, leaves }) };
192
193
  }
193
194
 
194
- const isCli = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
195
+ const isCli = isDirectInvocation(import.meta.url);
195
196
  if (isCli) {
196
197
  const arg = (name) => {
197
198
  const index = process.argv.indexOf(name);
@@ -31,6 +31,7 @@ import fs from 'node:fs';
31
31
  import path from 'node:path';
32
32
  import { pathToFileURL } from 'node:url';
33
33
  import { sha256File, projectSourceStore } from './rvf-generation.mjs';
34
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
34
35
 
35
36
  export const REQUIRED_SIDECARS = ['.big.rvf', '.big.rvf.embed.json', '.big.rvf.idmap.json', '.meta.json', '.passages.jsonl'];
36
37
  export const OPTIONAL_SIDECARS = ['.symbols.json', '-primer.md'];
@@ -267,4 +268,4 @@ function main() {
267
268
  console.log(JSON.stringify(receipt, null, 2));
268
269
  }
269
270
 
270
- if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) main();
271
+ if (isDirectInvocation(import.meta.url)) main();
@@ -8,6 +8,7 @@ import {
8
8
  digest,
9
9
  validateSourceScopeReceipt,
10
10
  } from './source-scope-receipt.mjs';
11
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
11
12
 
12
13
  export const PRODUCT_INTEGRITY_SCHEMA_VERSION = 1;
13
14
  export const PRODUCT_INTEGRITY_TEST_CLASSES = Object.freeze(['essential', 'supporting', 'obsolete']);
@@ -180,4 +181,4 @@ export function runProductIntegrityCli(argv = process.argv.slice(2), io = { stdo
180
181
  semanticReviewVerified: false, behaviorVerified: false, traceSha256: trace.traceSha256 })}\n`); return 0; }
181
182
  throw new Error('invalid product integrity argument combination');
182
183
  }
183
- if (((() => { try { return process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return false; } })())) { try { process.exitCode = runProductIntegrityCli(); } catch (error) { console.error(`[product-integrity-contract] ${error.message}`); process.exitCode = 1; } }
184
+ if (isDirectInvocation(import.meta.url)) { try { process.exitCode = runProductIntegrityCli(); } catch (error) { console.error(`[product-integrity-contract] ${error.message}`); process.exitCode = 1; } }
@@ -7,6 +7,7 @@ import path from 'node:path';
7
7
  import { pathToFileURL } from 'node:url';
8
8
  import { evaluateCandidateReceipt } from './release-proof.mjs';
9
9
  import { getVersion } from './version.mjs';
10
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
10
11
 
11
12
  export const PROTECTED_WORKFLOW = 'protected-release';
12
13
  export const PROTECTED_VERSION = getVersion();
@@ -92,7 +93,7 @@ export function validateProtectedPublishInvocation({ root = process.cwd(), env =
92
93
  };
93
94
  }
94
95
 
95
- if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url) {
96
+ if (isDirectInvocation(import.meta.url)) {
96
97
  const result = validateProtectedPublishInvocation();
97
98
  if (result.verdict !== 'PASS') {
98
99
  console.error('[protected-release] publication refused:');
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url';
8
8
  import { extractZip } from '../kb/zip-extract.mjs';
9
9
  import { abandonPublicVerificationTransaction } from './release-transaction.mjs';
10
10
  import { liveReleaseProvider } from './release-transaction-provider.mjs';
11
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
11
12
 
12
13
  function regularJson(file, max = 131072) {
13
14
  const stat = fs.lstatSync(file);
@@ -90,7 +91,7 @@ export async function abandonPublicVerification({ identityFile, outputFile, env
90
91
  return receipt;
91
92
  }
92
93
 
93
- const direct = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
94
+ const direct = isDirectInvocation(import.meta.url);
94
95
  if (direct) {
95
96
  const arg = (name) => { const index = process.argv.indexOf(name); return index < 0 ? null : process.argv[index + 1]; };
96
97
  try {
@@ -8,6 +8,7 @@ import { retrievalOracleExpectationFromPlan, validateRetrievalOracleReview } fro
8
8
  import { validateRetrievalCanaryPlan, validateRetrievalCanaryReceipt } from './retrieval-canary.mjs';
9
9
  import { validateNightlyProofReceipt, validateNativeSchedulerSmoke } from './nightly-two-run-proof.mjs';
10
10
  import { CLAUDE_FABLE_5_ID, GPT_5_6_SOL_ID, LEGACY_REVIEW_MODEL_IDS } from './review-model-defaults.mjs';
11
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
11
12
 
12
13
  export const PUBLIC_VERIFICATION_OS = Object.freeze(['linux', 'macos', 'windows']);
13
14
  export const PUBLIC_VERIFICATION_MODES = Object.freeze(['claude', 'codex', 'dual']);
@@ -359,7 +360,7 @@ async function main() {
359
360
  leaves: aggregate.metrics.leaves })}\n`);
360
361
  }
361
362
 
362
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
363
+ if (isDirectInvocation(import.meta.url)) {
363
364
  main().catch((error) => {
364
365
  process.stderr.write(`${error.message}\n`);
365
366
  process.exitCode = 1;
@@ -5,6 +5,7 @@ import path from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { finalizeReleaseTransaction } from './release-transaction.mjs';
7
7
  import { liveReleaseProvider } from './release-transaction-provider.mjs';
8
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
8
9
 
9
10
  const argument = (args, name) => {
10
11
  const index = args.indexOf(name);
@@ -61,5 +62,5 @@ export async function main(args = process.argv.slice(2)) {
61
62
  }
62
63
  }
63
64
 
64
- const invokedDirectly = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
65
+ const invokedDirectly = isDirectInvocation(import.meta.url);
65
66
  if (invokedDirectly) process.exitCode = await main();
@@ -17,6 +17,7 @@ import {
17
17
  validateRetrievalQueryEvidence,
18
18
  verifyQueryOracleSource,
19
19
  } from './retrieval-canary.mjs';
20
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
20
21
 
21
22
  const HEX40 = /^[a-f0-9]{40}$/;
22
23
  const HEX64 = /^[a-f0-9]{64}$/;
@@ -602,6 +603,6 @@ export async function main(argv = process.argv.slice(2)) {
602
603
  return result;
603
604
  }
604
605
 
605
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
606
+ if (isDirectInvocation(import.meta.url)) {
606
607
  main().catch((error) => { console.error(error.message); process.exitCode = 1; });
607
608
  }
@@ -20,6 +20,7 @@ import {
20
20
  validatePlanAgainstCoverage,
21
21
  validateRetrievalCanaryReceipt,
22
22
  } from './retrieval-canary.mjs';
23
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
23
24
 
24
25
  const HEX40 = /^[a-f0-9]{40}$/;
25
26
  const HEX64 = /^[a-f0-9]{64}$/;
@@ -353,4 +354,4 @@ export async function main(args = process.argv.slice(2)) {
353
354
  }
354
355
  }
355
356
 
356
- if (((() => { try { return process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return false; } })())) process.exitCode = await main();
357
+ if (isDirectInvocation(import.meta.url)) process.exitCode = await main();
@@ -29,6 +29,7 @@ import {
29
29
  validateNightlyProofReceipt,
30
30
  } from './nightly-two-run-proof.mjs';
31
31
  import { parseRetrievalResult } from '../kb/retrieval-result.mjs';
32
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
32
33
 
33
34
  const REPO = 'stuinfla/ruvnet-brain';
34
35
  const PACKAGE = 'ruvnet-brain';
@@ -825,4 +826,4 @@ export async function main(args = process.argv.slice(2)) {
825
826
  }
826
827
  }
827
828
 
828
- if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url) process.exitCode = await main();
829
+ if (isDirectInvocation(import.meta.url)) process.exitCode = await main();
@@ -54,6 +54,7 @@ import path from 'node:path';
54
54
  import { spawnSync } from 'node:child_process';
55
55
  import { fileURLToPath } from 'node:url';
56
56
  import { describeLatestPointer, isCodeReleaseTag, latestCodeReleaseTag } from './release-channel-kind.mjs';
57
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
57
58
 
58
59
  export const EXIT = Object.freeze({ PASS: 0, FAIL: 1, UNKNOWN: 4 });
59
60
 
@@ -275,5 +276,5 @@ export async function main() {
275
276
  return EXIT[verdict];
276
277
  }
277
278
 
278
- const invokedDirectly = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
279
+ const invokedDirectly = isDirectInvocation(import.meta.url);
279
280
  if (invokedDirectly) process.exit(await main());
@@ -28,6 +28,7 @@ import fs from 'node:fs';
28
28
  import path from 'node:path';
29
29
  import { fileURLToPath } from 'node:url';
30
30
  import { answerFromCards } from '../../kb/card-lane.mjs';
31
+ import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
31
32
 
32
33
  const HERE = path.dirname(fileURLToPath(import.meta.url));
33
34
  export const REPO_ROOT = path.resolve(HERE, '../..');
@@ -158,20 +159,5 @@ async function main() {
158
159
  process.exit(1);
159
160
  }
160
161
 
161
- // Entry-point guard. Compares REALPATHS on both sides: path.resolve() normalizes a path but does
162
- // NOT follow symlinks, while import.meta.url IS symlink-resolved by Node. Through a symlink (npm bin
163
- // shims, wrapper scripts, and every os.tmpdir() path on macOS) the two sides disagree, so main()
164
- // never runs -- and because nothing throws, the process exits 0. A silent exit 0 is indistinguishable
165
- // from "ran, found nothing", which is how prepareCorpusCandidate once reported SUCCESS with no
166
- // archive on disk. Reproduced live 2026-07-27; pinned by tests/unit/entrypoint-symlink.test.mjs.
167
- function isDirectInvocation() {
168
- try {
169
- if (!process.argv[1]) return false;
170
- return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
171
- } catch {
172
- return false;
173
- }
174
- }
175
-
176
- const invokedDirectly = isDirectInvocation();
162
+ const invokedDirectly = isDirectInvocation(import.meta.url);
177
163
  if (invokedDirectly) main();