ruvnet-brain 4.7.20 → 4.7.22

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 (260) hide show
  1. package/README.md +2 -2
  2. package/bin/install.mjs +618 -104
  3. package/console/activity.js +122 -38
  4. package/kb/download-retry.mjs +93 -21
  5. package/kb/forge-guard-injection.mjs +277 -0
  6. package/kb/forge-update.mjs +166 -38
  7. package/kb/lifecycle-evidence-retention.mjs +151 -14
  8. package/kb/model-requirements.mjs +4 -1
  9. package/kb/refresh-run.mjs +175 -27
  10. package/kb/verify-citation.mjs +15 -2
  11. package/package.json +3 -1
  12. package/plugin/.claude-plugin/plugin.json +1 -1
  13. package/plugin/.codex-plugin/plugin.json +1 -1
  14. package/plugin/mcp/managed-cli-interface.mjs +16 -8
  15. package/plugin/scripts/adr-currency-gate.mjs +2 -1
  16. package/plugin/scripts/advisory-contract.mjs +21 -0
  17. package/plugin/scripts/advocacy-route.mjs +5 -4
  18. package/plugin/scripts/agentdb-recall-process.mjs +115 -5
  19. package/plugin/scripts/agentdb-recall-reader.mjs +44 -2
  20. package/plugin/scripts/agentdb-recall.mjs +49 -17
  21. package/plugin/scripts/automatic-update.mjs +9 -1
  22. package/plugin/scripts/brain-confirmation.mjs +2 -1
  23. package/plugin/scripts/brain-footprint.mjs +72 -11
  24. package/plugin/scripts/capability-registry.mjs +1 -1
  25. package/plugin/scripts/capacity-aware-parallel-work.mjs +2 -1
  26. package/plugin/scripts/codex-overnight-notice.mjs +33 -0
  27. package/plugin/scripts/completion-claim-evidence.mjs +241 -23
  28. package/plugin/scripts/continuation-gate.mjs +75 -11
  29. package/plugin/scripts/decision-gate.mjs +13 -63
  30. package/plugin/scripts/decision-outcomes.mjs +36 -0
  31. package/plugin/scripts/decision-recall-gate.mjs +40 -5
  32. package/plugin/scripts/decision-record.mjs +8 -3
  33. package/plugin/scripts/degradation-watch.mjs +2 -1
  34. package/plugin/scripts/developer-update-lock.mjs +172 -33
  35. package/plugin/scripts/developer-update-owner.mjs +8 -5
  36. package/plugin/scripts/developer-update-policy.mjs +4 -1
  37. package/plugin/scripts/developer-update.mjs +155 -53
  38. package/plugin/scripts/doc-currency.mjs +2 -16
  39. package/plugin/scripts/duplicate-gate.mjs +2 -1
  40. package/plugin/scripts/entrypoint.mjs +34 -0
  41. package/plugin/scripts/footprint-io.mjs +135 -8
  42. package/plugin/scripts/grok-hook-adapter.mjs +2 -1
  43. package/plugin/scripts/ground-ruvnet.sh +6 -3
  44. package/plugin/scripts/grounding-answer.mjs +2 -1
  45. package/plugin/scripts/grounding-code-projection.mjs +2 -1
  46. package/plugin/scripts/grounding-substance.mjs +2 -1
  47. package/plugin/scripts/grounding-turn-gate.mjs +2 -2
  48. package/plugin/scripts/grounding-turn-mark.mjs +2 -2
  49. package/plugin/scripts/hook-context-budget.mjs +2 -1
  50. package/plugin/scripts/hook-input.mjs +2 -2
  51. package/plugin/scripts/hook-shim.mjs +9 -2
  52. package/plugin/scripts/host-update.mjs +29 -5
  53. package/plugin/scripts/identifier-preflight.mjs +2 -1
  54. package/plugin/scripts/kb-copy-proof.mjs +57 -9
  55. package/plugin/scripts/learn-capture.mjs +2 -1
  56. package/plugin/scripts/learning-store.mjs +62 -16
  57. package/plugin/scripts/lesson-bridge.mjs +2 -2
  58. package/plugin/scripts/lesson-gate.mjs +7 -39
  59. package/plugin/scripts/lesson-promote.mjs +83 -22
  60. package/plugin/scripts/lesson-scope.mjs +51 -0
  61. package/plugin/scripts/lesson-store.mjs +1 -1
  62. package/plugin/scripts/md-stamp.mjs +2 -1
  63. package/plugin/scripts/native-turn-transcript.mjs +98 -6
  64. package/plugin/scripts/nightly-scheduler.mjs +10 -5
  65. package/plugin/scripts/package-recommender.mjs +2 -2
  66. package/plugin/scripts/project-store-resolver.mjs +19 -0
  67. package/plugin/scripts/routing-outcome-capture.mjs +2 -1
  68. package/plugin/scripts/ruflo-bin.mjs +14 -6
  69. package/plugin/scripts/ruflo-memory-hooks.mjs +157 -19
  70. package/plugin/scripts/session-snapshot-hook.mjs +25 -5
  71. package/plugin/scripts/session-start-core.mjs +7 -16
  72. package/plugin/scripts/session-start-health.mjs +98 -10
  73. package/plugin/scripts/session-start-issue-alert.mjs +63 -0
  74. package/plugin/scripts/session-start-update-plane.mjs +15 -1
  75. package/plugin/scripts/signal-watch.mjs +2 -1
  76. package/plugin/scripts/spend-guard.mjs +2 -1
  77. package/plugin/scripts/substitution-wall.mjs +2 -1
  78. package/plugin/scripts/turn-outcome-capture.mjs +7 -3
  79. package/plugin/scripts/unprompted-runtime.mjs +20 -3
  80. package/plugin/scripts/update-apply.mjs +186 -15
  81. package/plugin/skills/release-proof/scripts/release-proof.mjs +2 -1
  82. package/plugin/skills/ruvnet-brain/PLAYBOOK.md +2 -2
  83. package/plugin/skills/ruvnet-brain/SKILL.md +3 -2
  84. package/scripts/adr-072-completion.mjs +2 -1
  85. package/scripts/agentdb-context.mjs +2 -1
  86. package/scripts/agentdb-fleet-doctor.mjs +30 -8
  87. package/scripts/approved-runtime.mjs +2 -1
  88. package/scripts/architecture-review-lock.mjs +2 -1
  89. package/scripts/ascii-drift.mjs +2 -1
  90. package/scripts/brain-latency-50.mjs +2 -1
  91. package/scripts/brain-novice-50.mjs +2 -16
  92. package/scripts/brain-score.mjs +13 -2
  93. package/scripts/build-bundle.mjs +2 -1
  94. package/scripts/build-concepts.mjs +2 -1
  95. package/scripts/candidate-ci-receipt.mjs +2 -1
  96. package/scripts/candidate-host-evidence.mjs +2 -1
  97. package/scripts/card-from-source.mjs +2 -1
  98. package/scripts/ci/warm-brain-models.mjs +2 -1
  99. package/scripts/claims-verify.mjs +7 -3
  100. package/scripts/claude-controlled-terminal.mjs +2 -1
  101. package/scripts/claude-terminal-mod.mjs +2 -1
  102. package/scripts/code-release-corpus.mjs +2 -1
  103. package/scripts/codex-boot-surface-proof.mjs +208 -0
  104. package/scripts/codex-hook-trust-reconcile.mjs +67 -1
  105. package/scripts/codex-hook-trust.mjs +40 -1
  106. package/scripts/codex-hook-wrapper-history.mjs +2 -1
  107. package/scripts/codex-host-execution-proof.mjs +2 -1
  108. package/scripts/completion-claim-replay.mjs +2 -1
  109. package/scripts/console-engine.mjs +8 -3
  110. package/scripts/corpus-canary.mjs +2 -1
  111. package/scripts/corpus-candidate.mjs +2 -2
  112. package/scripts/corpus-coverage-sidecar.mjs +2 -1
  113. package/scripts/corpus-dispatch-decision.mjs +2 -1
  114. package/scripts/corpus-dispatch-receipt.mjs +2 -1
  115. package/scripts/corpus-next-seed.mjs +2 -1
  116. package/scripts/corpus-qa.mjs +2 -1
  117. package/scripts/corpus-reconcile.mjs +2 -2
  118. package/scripts/corpus-watchdog.mjs +2 -1
  119. package/scripts/count-chunks.mjs +2 -1
  120. package/scripts/customer-state-matrix.mjs +2 -1
  121. package/scripts/derive-passage-content-map.mjs +2 -1
  122. package/scripts/development-push-check.mjs +2 -1
  123. package/scripts/dispatch-receipt.mjs +2 -1
  124. package/scripts/doc-currency.mjs +2 -1
  125. package/scripts/dream-issue-gate.mjs +2 -1
  126. package/scripts/dual-host-deliberation.mjs +2 -16
  127. package/scripts/dual-host-suggest.mjs +2 -16
  128. package/scripts/eval-brain.mjs +7 -3
  129. package/scripts/execution-policy.mjs +2 -1
  130. package/scripts/execution-preflight.mjs +2 -1
  131. package/scripts/falsify.mjs +2 -1
  132. package/scripts/fix-metaharness-memretrieve.mjs +2 -1
  133. package/scripts/fix-workstream.mjs +2 -1
  134. package/scripts/full-hints.mjs +2 -1
  135. package/scripts/full-suite-gate.mjs +2 -1
  136. package/scripts/grok-host.mjs +68 -0
  137. package/scripts/hook-qualify-core.mjs +3 -1
  138. package/scripts/hook-qualify.mjs +2 -1
  139. package/scripts/hook-retirement-check.mjs +2 -1
  140. package/scripts/host-registry.mjs +2 -1
  141. package/scripts/independent-review-receipt.mjs +2 -1
  142. package/scripts/ingest-new-repos.mjs +2 -1
  143. package/scripts/ingest-repo.mjs +12 -2
  144. package/scripts/integration-evidence.mjs +2 -1
  145. package/scripts/issue-fix.mjs +7 -24
  146. package/scripts/issue-watch.mjs +116 -40
  147. package/scripts/job-heartbeat.sh +54 -19
  148. package/scripts/key-canary.mjs +9 -3
  149. package/scripts/latency-to-surface.mjs +2 -1
  150. package/scripts/learning-replay-cli.mjs +97 -19
  151. package/scripts/learning-replay-execution.mjs +2 -0
  152. package/scripts/learning-replay-fixture.mjs +3 -0
  153. package/scripts/learning-replay-proof.mjs +14 -13
  154. package/scripts/learning-replay.mjs +3 -3
  155. package/scripts/learnings.mjs +2 -1
  156. package/scripts/lesson-ratify.mjs +88 -40
  157. package/scripts/loop-checkpoint.mjs +2 -1
  158. package/scripts/metaharness-receipts.mjs +2 -1
  159. package/scripts/model-catalog.mjs +2 -1
  160. package/scripts/model-currency-evidence.mjs +6 -4
  161. package/scripts/model-currency.mjs +33 -3
  162. package/scripts/model-native-catalog.mjs +2 -1
  163. package/scripts/model-router-agent-hook.mjs +2 -1
  164. package/scripts/model-router-dispatch.mjs +2 -1
  165. package/scripts/model-router-engine.mjs +2 -1
  166. package/scripts/model-router-outcome.mjs +2 -1
  167. package/scripts/model-router-setup.mjs +2 -1
  168. package/scripts/model-routing-eval.mjs +2 -1
  169. package/scripts/model-routing-gateway.mjs +2 -1
  170. package/scripts/model-routing-launchers.mjs +4 -1
  171. package/scripts/model-terminal-gateway.mjs +2 -1
  172. package/scripts/model-terminal-launchers.mjs +2 -1
  173. package/scripts/model-weekly-analyst.mjs +2 -1
  174. package/scripts/model-weekly-cycle.mjs +2 -1
  175. package/scripts/model-weekly-qualification.mjs +2 -1
  176. package/scripts/nightly-gists.sh +26 -20
  177. package/scripts/nightly-two-run-proof.mjs +2 -1
  178. package/scripts/nightly-watchdog.mjs +158 -47
  179. package/scripts/no-silent-substitution.mjs +2 -1
  180. package/scripts/notify.sh +11 -6
  181. package/scripts/ntfy-inbox.mjs +41 -18
  182. package/scripts/onboarding-console.mjs +123 -23
  183. package/scripts/oracle/abstain-threshold-sweep.mjs +2 -1
  184. package/scripts/oracle/abstain-trace.mjs +2 -1
  185. package/scripts/oracle/doc2query-generate.mjs +2 -1
  186. package/scripts/oracle/doc2query-reach.mjs +2 -1
  187. package/scripts/oracle/judge-train.mjs +2 -1
  188. package/scripts/oracle/need-set-split.mjs +2 -1
  189. package/scripts/oracle/produce-questions.mjs +2 -16
  190. package/scripts/oracle/repo-recall.mjs +2 -2
  191. package/scripts/oracle/retrieval-accuracy.mjs +2 -2
  192. package/scripts/oracle/sona-query-adapter-eval.mjs +2 -1
  193. package/scripts/oracle/source-tree.mjs +2 -15
  194. package/scripts/oracle/source-units.mjs +2 -16
  195. package/scripts/oracle/spike-run.mjs +2 -16
  196. package/scripts/oracle/validate-labels.mjs +2 -16
  197. package/scripts/org-repo-count.mjs +2 -1
  198. package/scripts/package-cards.mjs +2 -1
  199. package/scripts/prepublication-evidence.mjs +2 -1
  200. package/scripts/private-overlay.mjs +2 -1
  201. package/scripts/product-integrity-contract.mjs +2 -1
  202. package/scripts/protected-release-invocation.mjs +2 -1
  203. package/scripts/public-verification-abandon.mjs +2 -1
  204. package/scripts/public-verification-aggregate.mjs +2 -1
  205. package/scripts/public-verification-finalizer.mjs +2 -1
  206. package/scripts/public-verification-inputs.mjs +2 -1
  207. package/scripts/public-verification-lane.mjs +2 -1
  208. package/scripts/publication-receipt.mjs +2 -1
  209. package/scripts/published-surface-probe.mjs +2 -1
  210. package/scripts/qe/card-lane-gate.mjs +2 -16
  211. package/scripts/qe/session-start-gate.mjs +2 -16
  212. package/scripts/qe/ux-suite.mjs +2 -1
  213. package/scripts/qualified-candidate-check.mjs +2 -1
  214. package/scripts/rebuild-gists-from-receipts.mjs +2 -1
  215. package/scripts/recall-latency-probe.mjs +48 -0
  216. package/scripts/recommendation-eval.mjs +2 -1
  217. package/scripts/recommendation-floor.mjs +2 -1
  218. package/scripts/recommendation-judge-score.mjs +2 -1
  219. package/scripts/recommendation-real-host-score.mjs +2 -1
  220. package/scripts/recommendation-real-host.mjs +2 -1
  221. package/scripts/refresh-capability-only-store.mjs +2 -1
  222. package/scripts/refresh-model-catalog.mjs +2 -1
  223. package/scripts/rehearse-corpus-pipeline.mjs +2 -1
  224. package/scripts/release-authority.mjs +2 -1
  225. package/scripts/release-evidence-aggregate.mjs +2 -1
  226. package/scripts/release-payload.mjs +2 -1
  227. package/scripts/release-projection.mjs +2 -1
  228. package/scripts/release-proof.mjs +2 -1
  229. package/scripts/release-qualification-contract.mjs +82 -28
  230. package/scripts/release-qualification.mjs +8 -1
  231. package/scripts/release-vector.mjs +2 -1
  232. package/scripts/restore-local-ingests.mjs +2 -1
  233. package/scripts/retrieval-canary.mjs +2 -1
  234. package/scripts/route-cheap.mjs +2 -1
  235. package/scripts/route-gold-rank.mjs +2 -1
  236. package/scripts/route-index-memory.mjs +2 -1
  237. package/scripts/route-latency-warm.mjs +2 -1
  238. package/scripts/router-utilization.mjs +2 -1
  239. package/scripts/routing-flywheel.mjs +2 -1
  240. package/scripts/run-operational-benchmark.mjs +2 -1
  241. package/scripts/run-operational-benchmark.v3.mjs +2 -1
  242. package/scripts/rvf-index-audit.mjs +2 -16
  243. package/scripts/seal-gist-receipt.mjs +2 -1
  244. package/scripts/security-guidance-codex-compat.mjs +2 -1
  245. package/scripts/selfcheck.mjs +2 -1
  246. package/scripts/signal-watch.mjs +2 -1
  247. package/scripts/source-coverage.mjs +2 -1
  248. package/scripts/stabilization-receipt.mjs +2 -1
  249. package/scripts/stamp-sweep.mjs +2 -1
  250. package/scripts/status-honesty.mjs +2 -1
  251. package/scripts/sync-commands.mjs +2 -2
  252. package/scripts/top100-benchmark.mjs +2 -1
  253. package/scripts/top100-corpus.mjs +2 -1
  254. package/scripts/user-model-hook-install.mjs +2 -1
  255. package/scripts/user-model-prompt-hook.mjs +2 -1
  256. package/scripts/verify-bundle.mjs +3 -1
  257. package/scripts/verify-model-catalog.mjs +2 -1
  258. package/scripts/version.mjs +2 -1
  259. package/scripts/wired-check.mjs +13 -5
  260. package/scripts/worktree-integrity.mjs +2 -2
@@ -50,6 +50,7 @@ import path from 'node:path';
50
50
  import readline from 'node:readline';
51
51
  import { fileURLToPath } from 'node:url';
52
52
  import { FULL_HINTS } from './full-hints.mjs';
53
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
53
54
 
54
55
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
55
56
  const KB = path.join(ROOT, 'kb');
@@ -245,7 +246,7 @@ export function discoverStores(dir) {
245
246
  }
246
247
 
247
248
  // ---------------- CLI ----------------
248
- if (import.meta.url === `file://${process.argv[1]}`) {
249
+ if (isDirectInvocation(import.meta.url)) {
249
250
  const arg = (f, d) => { const i = process.argv.indexOf(f); return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : d; };
250
251
  const has = (f) => process.argv.includes(f);
251
252
  const DIR = path.resolve(arg('--dir', KB));
@@ -26,6 +26,7 @@ import { captureGistSources } from './gist-receipts.mjs';
26
26
  import { projectSourceStore, RUNTIME_LEDGER_KIND } from './rvf-generation.mjs';
27
27
  import { compareKnowledgeInputs, fromCoverage as knowledgeFromCoverage, fromSeed as knowledgeFromSeed } from './knowledge-input-digest.mjs';
28
28
  import { defaultBaseDelayMs } from './transient-gh.mjs';
29
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
29
30
 
30
31
  export { rebuildCorpusAggregates };
31
32
 
@@ -1306,8 +1307,7 @@ export async function main(argv = process.argv.slice(2), {
1306
1307
  // with no archive and no receipt on disk. Same defect, same fix as plugin/scripts/hook-input.mjs:518.
1307
1308
  function isMain() {
1308
1309
  try {
1309
- if (!process.argv[1]) return false;
1310
- return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
1310
+ return isDirectInvocation(import.meta.url);
1311
1311
  } catch {
1312
1312
  return false;
1313
1313
  }
@@ -48,6 +48,7 @@ import { AGGREGATE_ASSET, SIGNING_PUBLIC_KEY_FILE } from './approved-runtime.mjs
48
48
  import { COVERAGE_ASSET, COVERAGE_RECEIPT_ASSET, verifyCoverageSidecar } from './corpus-coverage-sidecar.mjs';
49
49
  import { parseCorpusGeneration } from './corpus-promotion.mjs';
50
50
  import { retryTransientResult } from './transient-gh.mjs';
51
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
51
52
 
52
53
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
53
54
  export const RED = 'RED';
@@ -496,6 +497,6 @@ async function main(argv = process.argv.slice(2)) {
496
497
  return result.verdict === RED ? 1 : 0;
497
498
  }
498
499
 
499
- if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url) {
500
+ if (isDirectInvocation(import.meta.url)) {
500
501
  main().then((code) => { process.exitCode = code; });
501
502
  }
@@ -23,6 +23,7 @@
23
23
  import path from 'node:path';
24
24
  import { fileURLToPath } from 'node:url';
25
25
  import { applyFix } from './claims-verify.mjs';
26
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
26
27
 
27
28
  const CHECK = process.argv.includes('--check');
28
29
 
@@ -49,6 +50,6 @@ async function main() {
49
50
  console.log(`\n[count-chunks] done — ${want} chunks is the live count; ${drifted.length} surface(s) rewritten.`);
50
51
  }
51
52
 
52
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
53
+ if (isDirectInvocation(import.meta.url)) {
53
54
  await main();
54
55
  }
@@ -34,6 +34,7 @@ import { customerEnv, customerManifestUrl, installCustomer, installedModules } f
34
34
  import { addPrivateStore, resolveRuntime } from './customer-seams.mjs';
35
35
  export { resolveRuntime };
36
36
  import { doctorSmokeArgs } from './installed-brain-health.mjs';
37
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
37
38
 
38
39
  export const REPO = 'stuinfla/ruvnet-brain';
39
40
  const readJson = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
@@ -358,6 +359,6 @@ async function main(argv = process.argv.slice(2)) {
358
359
  return 0;
359
360
  }
360
361
 
361
- if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url) {
362
+ if (isDirectInvocation(import.meta.url)) {
362
363
  main().then((code) => { process.exitCode = code; }, (error) => { console.error(error.stack || error.message); process.exitCode = 1; });
363
364
  }
@@ -16,6 +16,7 @@ import { spawnSync } from 'node:child_process';
16
16
  import { fileURLToPath } from 'node:url';
17
17
  import { digest } from './coverage-integrity.mjs';
18
18
  import { CONTENT_MAP_FILE, CONTENT_MAP_KIND, passageContentDigest } from './retrieval-passage-identity.mjs';
19
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
19
20
 
20
21
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
21
22
  const sha256File = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
@@ -68,4 +69,4 @@ function main(argv) {
68
69
  console.log(JSON.stringify({ out, resolved: Object.keys(map.entries).length, unresolved: map.unresolved.length }));
69
70
  }
70
71
 
71
- if (process.argv[1] && fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) main(process.argv.slice(2));
72
+ if (isDirectInvocation(import.meta.url)) main(process.argv.slice(2));
@@ -5,6 +5,7 @@ import fs from 'node:fs';
5
5
  import { execFileSync } from 'node:child_process';
6
6
  import path from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
8
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
8
9
 
9
10
  export function inspectPush(root, input) {
10
11
  const checked = [];
@@ -27,7 +28,7 @@ export function inspectPush(root, input) {
27
28
  return { ok: true, checked, scope: 'unpublished-commit-secret-scan' };
28
29
  }
29
30
 
30
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
31
+ if (isDirectInvocation(import.meta.url)) {
31
32
  try {
32
33
  const root = execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim();
33
34
  console.log(JSON.stringify(inspectPush(root, fs.readFileSync(0, 'utf8'))));
@@ -26,6 +26,7 @@ import fs from 'node:fs';
26
26
  import path from 'node:path';
27
27
  import { pathToFileURL } from 'node:url';
28
28
  import { CLAUDE_MODEL_IDS, CLAUDE_TIERS, PRICING, estTokens, estimateCosts, receiptLine, receiptsPath, priceOf } from './route-cheap.mjs';
29
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
29
30
 
30
31
  // Default input share when only a MEASURED TOTAL is known. A subagent's tokens are dominated by input
31
32
  // (it re-reads files and tool output on every turn); its final report is small. 0.9 is an assumption,
@@ -109,4 +110,4 @@ function main() {
109
110
  console.log(receiptLine(receipt.model, { cost: receipt.est_cost, frontier: receipt.est_frontier_cost, saved: receipt.saved, ref: receipt.inherited }));
110
111
  }
111
112
 
112
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main();
113
+ if (isDirectInvocation(import.meta.url)) main();
@@ -3,7 +3,8 @@
3
3
  import fs from 'node:fs';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { main } from '../plugin/scripts/doc-currency.mjs';
6
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
6
7
  export * from '../plugin/scripts/doc-currency.mjs';
7
8
  let direct = false;
8
- try { direct = Boolean(process.argv[1]) && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url)); } catch {}
9
+ try { direct = isDirectInvocation(import.meta.url); } catch {}
9
10
  if (direct) process.exit(main());
@@ -5,6 +5,7 @@
5
5
  // the caller supplies the finding and the current open issues, and receives a disposition before
6
6
  // invoking any GitHub write operation.
7
7
 
8
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
8
9
  const HEX_SHA = /^[0-9a-f]{7,64}$/i;
9
10
  const ISSUE_MARKER = 'dream-fingerprint:';
10
11
 
@@ -98,7 +99,7 @@ export function buildIssueBody(finding, decision = assessFinding(finding)) {
98
99
  // Adapter contract for the Dream Machine runner. Input is JSON on stdin:
99
100
  // { "finding": { ... }, "openIssues": [{ "number", "title", "body" }] }
100
101
  // Output is the disposition JSON. This command intentionally cannot create or mutate GitHub data.
101
- if (process.argv[1] === new URL(import.meta.url).pathname) {
102
+ if (isDirectInvocation(import.meta.url)) {
102
103
  let input = '';
103
104
  process.stdin.setEncoding('utf8');
104
105
  process.stdin.on('data', (chunk) => { input += chunk; });
@@ -6,6 +6,7 @@ import { spawn } from 'node:child_process';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { probeSubscriptionHosts, subscriptionOnlyEnv } from './subscription-hosts.mjs';
8
8
  import { DUAL_HOST_MODEL_IDS } from './review-model-defaults.mjs';
9
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
9
10
 
10
11
  const HOSTS = Object.freeze(['claude-code', 'codex']);
11
12
  // Top subscription models verified on the native hosts on 2026-09-10.
@@ -322,21 +323,6 @@ export async function main(argv = process.argv.slice(2), {
322
323
  }
323
324
  }
324
325
 
325
- // Entry-point guard. Compares REALPATHS on both sides: path.resolve() normalizes a path but does
326
- // NOT follow symlinks, while import.meta.url IS symlink-resolved by Node. Through a symlink (npm bin
327
- // shims, wrapper scripts, and every os.tmpdir() path on macOS) the two sides disagree, so main()
328
- // never runs -- and because nothing throws, the process exits 0. A silent exit 0 is indistinguishable
329
- // from "ran, found nothing", which is how prepareCorpusCandidate once reported SUCCESS with no
330
- // archive on disk. Reproduced live 2026-07-27; pinned by tests/unit/entrypoint-symlink.test.mjs.
331
- function isDirectInvocation() {
332
- try {
333
- if (!process.argv[1]) return false;
334
- return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
335
- } catch {
336
- return false;
337
- }
338
- }
339
-
340
- if (isDirectInvocation()) {
326
+ if (isDirectInvocation(import.meta.url)) {
341
327
  process.exitCode = await main();
342
328
  }
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
4
4
  import fs from 'node:fs';
5
5
  import { hardProblem } from './dual-host-deliberation.mjs';
6
6
  import { probeSubscriptionHosts } from './subscription-hosts.mjs';
7
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
7
8
 
8
9
  const HOSTS = Object.freeze([
9
10
  ['claude', 'claude-code'],
@@ -54,21 +55,6 @@ export function main(argv = process.argv.slice(2), {
54
55
  return 0;
55
56
  }
56
57
 
57
- // Entry-point guard. Compares REALPATHS on both sides: path.resolve() normalizes a path but does
58
- // NOT follow symlinks, while import.meta.url IS symlink-resolved by Node. Through a symlink (npm bin
59
- // shims, wrapper scripts, and every os.tmpdir() path on macOS) the two sides disagree, so main()
60
- // never runs -- and because nothing throws, the process exits 0. A silent exit 0 is indistinguishable
61
- // from "ran, found nothing", which is how prepareCorpusCandidate once reported SUCCESS with no
62
- // archive on disk. Reproduced live 2026-07-27; pinned by tests/unit/entrypoint-symlink.test.mjs.
63
- function isDirectInvocation() {
64
- try {
65
- if (!process.argv[1]) return false;
66
- return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
67
- } catch {
68
- return false;
69
- }
70
- }
71
-
72
- if (isDirectInvocation()) {
58
+ if (isDirectInvocation(import.meta.url)) {
73
59
  process.exitCode = main();
74
60
  }
@@ -32,6 +32,7 @@ import os from 'node:os';
32
32
  import path from 'node:path';
33
33
  import { spawnSync } from 'node:child_process';
34
34
  import { fileURLToPath, pathToFileURL } from 'node:url';
35
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
35
36
 
36
37
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
37
38
  const KB = process.env.RUVNET_BRAIN_KB || path.join(os.homedir(), '.cache', 'ruvnet-brain', 'kb');
@@ -91,8 +92,11 @@ export function gradeQuestion(q, { grounded, citations, bannerPresent, receipt,
91
92
  return { grounded, routed: null, abstained, pass: abstained };
92
93
  case 'provenance':
93
94
  // The mechanism under test: IF a gist chunk wins, it must carry its own status banner.
94
- // A better hit from the real repo is not a failure — it is better grounding.
95
- return { grounded, routed: null, abstained, pass: !!grounded && !abstained && (top?.repo !== 'ruv-gists' || bannerPresent) };
95
+ // A better hit from the real repo is not a failure — it is better grounding. "Wins" means
96
+ // the citation verify-citation.mjs actually resolved (`routedRepo`, the same signal `routed`
97
+ // uses below) — not merely the raw top-ranked citation, which citationResolves() can walk
98
+ // past when it is unverified/fabricated (the sibling gap already closed for `routed`).
99
+ return { grounded, routed: null, abstained, pass: !!grounded && !abstained && (routedRepo !== 'ruv-gists' || bannerPresent) };
96
100
  default:
97
101
  // Abstention is correct only for the adversarial stratum. A negative cross-encoder score on
98
102
  // an answerable question is an answer miss, even when an unrelated existing path resolves.
@@ -279,6 +283,6 @@ async function main() {
279
283
  }
280
284
  }
281
285
 
282
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
286
+ if (isDirectInvocation(import.meta.url)) {
283
287
  await main();
284
288
  }
@@ -2,6 +2,7 @@
2
2
  // One deterministic boundary between Brain knowledge and consequential execution.
3
3
  // This module is deliberately pure: it never loads credentials, calls a provider, or mutates state.
4
4
 
5
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
5
6
  const NATIVE_HOSTS = new Set(['claude', 'codex']);
6
7
  const API_EXECUTORS = new Set(['agent_execute', 'sdk', 'openrouter', 'api']);
7
8
  const ACTIONS = new Set(['read', 'write', 'delegate', 'release', 'external']);
@@ -111,7 +112,7 @@ export function classifyExecutionPolicy(input = {}) {
111
112
  };
112
113
  }
113
114
 
114
- if (import.meta.url === `file://${process.argv[1]}`) {
115
+ if (isDirectInvocation(import.meta.url)) {
115
116
  const raw = process.argv[2] || '{}';
116
117
  let input;
117
118
  try { input = JSON.parse(raw); } catch { process.stderr.write('execution-policy: input must be JSON\n'); process.exit(2); }
@@ -3,12 +3,13 @@
3
3
  // live Brain search and exact project AgentDB retrieval; prose, memory, and guessed host state do
4
4
  // not satisfy this command.
5
5
  import { classifyExecutionPolicy } from './execution-policy.mjs';
6
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
6
7
 
7
8
  export function runExecutionPreflight(input = {}) {
8
9
  return classifyExecutionPolicy({ ...input, enforceEvidence: true });
9
10
  }
10
11
 
11
- if (import.meta.url === `file://${process.argv[1]}`) {
12
+ if (isDirectInvocation(import.meta.url)) {
12
13
  let input;
13
14
  try { input = JSON.parse(process.argv[2] || '{}'); }
14
15
  catch { process.stderr.write('execution-preflight: input must be JSON\n'); process.exit(2); }
@@ -29,6 +29,7 @@ import fs from 'node:fs';
29
29
  import path from 'node:path';
30
30
  import { fileURLToPath, pathToFileURL } from 'node:url';
31
31
  import { resolveRuflo, RUFLO_MISSING } from '../plugin/scripts/ruflo-bin.mjs';
32
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
32
33
 
33
34
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
34
35
  const sh = (cmd, args) => spawnSync(cmd, args, { cwd: ROOT, encoding: 'utf8' });
@@ -238,4 +239,4 @@ function main() {
238
239
  process.exit(0);
239
240
  }
240
241
 
241
- if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) main();
242
+ if (isDirectInvocation(import.meta.url)) main();
@@ -28,6 +28,7 @@ import fs from 'node:fs';
28
28
  import path from 'node:path';
29
29
  import os from 'node:os';
30
30
  import { pathToFileURL } from 'node:url';
31
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
31
32
 
32
33
  const TARGET_DIR =
33
34
  process.env.METAHARNESS_SCRIPTS_DIR ||
@@ -113,7 +114,7 @@ export function check(dir = TARGET_DIR, log = console.log) {
113
114
  // Only act when run directly, so tests can import the pure functions above.
114
115
  // Compare in URL space (pathToFileURL), not by decoding a URL into a path: `new URL(...).pathname`
115
116
  // yields "/D:/..." on Windows and never matches, so `--apply` would silently no-op there.
116
- const invokedDirectly = process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url;
117
+ const invokedDirectly = isDirectInvocation(import.meta.url);
117
118
  if (invokedDirectly) {
118
119
  const mode = process.argv.includes('--check') ? 'check' : 'apply';
119
120
  console.log(`metaharness memRetrieve ${mode} — ${TARGET_DIR}\n`);
@@ -7,6 +7,7 @@ import { spawnSync } from 'node:child_process';
7
7
  import fs from 'node:fs';
8
8
  import path from 'node:path';
9
9
  import { pathToFileURL } from 'node:url';
10
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
10
11
 
11
12
  const FULL_SHA = /^[a-f0-9]{40}$/i;
12
13
  const SCOPED_BRANCH = /^(fix|bugfix|hotfix|issue|codex)\/[A-Za-z0-9][A-Za-z0-9._/-]*$/;
@@ -288,4 +289,4 @@ export function main(args = process.argv.slice(2)) {
288
289
  }
289
290
  }
290
291
 
291
- if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url) process.exitCode = main();
292
+ if (isDirectInvocation(import.meta.url)) process.exitCode = main();
@@ -16,6 +16,7 @@
16
16
  // v2/crates — without --keep v2 those repos index almost nothing (open-claude-code: 68 passages
17
17
  // total) and their FULL_HINTS can never match.
18
18
 
19
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
19
20
  export const FULL_HINTS = {
20
21
  'ruflo': 'v3/@claude-flow,v3/mcp,ruflo/src',
21
22
  'synthlang': 'proxy/src/cli/synthlang,proxy/src/app/synthlang',
@@ -81,7 +82,7 @@ export const KEEP_DIRS = {
81
82
  };
82
83
 
83
84
  // CLI helper so shell drivers can read the config: node scripts/full-hints.mjs <kb-name>
84
- if (import.meta.url === `file://${process.argv[1]}`) {
85
+ if (isDirectInvocation(import.meta.url)) {
85
86
  const kb = (process.argv[2] || '').toLowerCase();
86
87
  console.log(JSON.stringify({ full: FULL_HINTS[kb] || null, keep: KEEP_DIRS[kb] || null }));
87
88
  }
@@ -17,6 +17,7 @@ import os from 'node:os';
17
17
  import path from 'node:path';
18
18
  import { spawnSync } from 'node:child_process';
19
19
  import { fileURLToPath } from 'node:url';
20
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
20
21
 
21
22
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
22
23
  const key = (file, name) => `${file} :: ${name}`;
@@ -132,7 +133,7 @@ export function summaryMarkdown(result) {
132
133
  ].join('\n');
133
134
  }
134
135
 
135
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
136
+ if (isDirectInvocation(import.meta.url)) {
136
137
  const args = process.argv.slice(2);
137
138
  const value = (flag) => { const i = args.indexOf(flag); return i < 0 ? null : args[i + 1]; };
138
139
  const out = value('--report') || path.join(os.tmpdir(), `full-suite-${process.pid}.json`);
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Grok CLI status for the installer summary and `--doctor` (#301).
3
+ *
4
+ * Grok has no install step of its own: it discovers the Claude Code plugin (measured with
5
+ * `grok inspect --json` on grok 1.0.58: plugin ruvnet-brain, 10 skills, its MCP server, and the
6
+ * hook file plugin/hooks/grok-hooks.json selected by plugin/.grok-plugin/plugin.json). Grok
7
+ * discards an allowing UserPromptSubmit hook's context (~/.grok/docs/user-guide/10-hooks.md), so the
8
+ * prompt-time "search first" directive never reaches its model; a line in ~/.grok/AGENTS.md (loaded
9
+ * as a global rule — same inspect run) is what makes Grok ground itself. That line is OPT-IN only.
10
+ */
11
+ import fs from 'node:fs';
12
+ import os from 'node:os';
13
+ import path from 'node:path';
14
+
15
+ export const GROK_MD_START = '<!-- ruvnet-brain:start -->';
16
+ export const GROK_MD_END = '<!-- ruvnet-brain:end -->';
17
+ export const GROK_AGENTS_LINE = 'For anything about the RuvNet / rUv stack (Ruflo, RuVector/RVF, AgentDB, agentic-flow, and the rest), '
18
+ + 'call the `search_ruvnet` MCP tool first and cite the source paths it returns. Do not answer from memory. '
19
+ + 'If it fails, say so and run `npx ruvnet-brain --doctor`.';
20
+ export const GROK_AGENTS_BLOCK = `${GROK_MD_START}\n${GROK_AGENTS_LINE}\n${GROK_MD_END}`;
21
+
22
+ export function grokPaths(home = os.homedir()) {
23
+ const bin = path.join(home, '.grok', 'bin');
24
+ return { bin: [path.join(bin, 'grok'), path.join(bin, 'grok.exe')], agents: path.join(home, '.grok', 'AGENTS.md') };
25
+ }
26
+
27
+ /** What is true about Grok on this machine. Never runs grok; reads files only. */
28
+ export function grokStatus({ home = os.homedir(), pluginInstalled = false, hookCount = null } = {}) {
29
+ const p = grokPaths(home);
30
+ const present = p.bin.some((file) => fs.existsSync(file));
31
+ let agentsText = '';
32
+ try { agentsText = fs.readFileSync(p.agents, 'utf8'); } catch { /* absent */ }
33
+ return { present, pluginInstalled: Boolean(pluginInstalled), hookCount, agentsFile: p.agents,
34
+ groundingLine: /search_ruvnet/.test(agentsText) };
35
+ }
36
+
37
+ /** One honest line: what reaches Grok, and whether the grounding line is there. null when no Grok. */
38
+ export function grokStatusLine(s) {
39
+ if (!s.present) return null;
40
+ if (!s.pluginInstalled) {
41
+ return 'Grok CLI detected — the Brain reaches Grok only through the Claude Code plugin, which is not installed here (run npx ruvnet-brain).';
42
+ }
43
+ const hooks = Number.isInteger(s.hookCount) ? `${s.hookCount} lifecycle hooks` : 'its lifecycle hooks';
44
+ return `Grok CLI detected — search_ruvnet, the Brain skills and ${hooks} (grok-hooks.json) load from the Claude Code plugin. `
45
+ + 'Grok drops prompt-time context, so grounding relies on a search-first line in ~/.grok/AGENTS.md: '
46
+ + (s.groundingLine ? 'present.' : 'absent (opt in: npx ruvnet-brain --grok-agents-md).');
47
+ }
48
+
49
+ /**
50
+ * Add the search-first line to ~/.grok/AGENTS.md ONLY if no search_ruvnet line is there already.
51
+ * Marker-wrapped (removed by --uninstall), backed up, written atomically. Returns
52
+ * 'added' | 'present' | 'no-grok' | 'failed'.
53
+ */
54
+ export function addGrokAgentsLine({ home = os.homedir(), now = new Date() } = {}) {
55
+ const s = grokStatus({ home });
56
+ if (!s.present) return 'no-grok';
57
+ if (s.groundingLine) return 'present';
58
+ const file = s.agentsFile;
59
+ try {
60
+ const existing = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
61
+ if (existing) fs.copyFileSync(file, `${file}.bak-${now.toISOString().replace(/[:.]/g, '-')}`);
62
+ const next = existing ? `${existing.replace(/\s*$/, '')}\n\n${GROK_AGENTS_BLOCK}\n` : `${GROK_AGENTS_BLOCK}\n`;
63
+ const tmp = `${file}.ruvnet-tmp`;
64
+ fs.writeFileSync(tmp, next);
65
+ fs.renameSync(tmp, file);
66
+ return 'added';
67
+ } catch { return 'failed'; }
68
+ }
@@ -127,7 +127,9 @@ export function makeWorld(host, { root = REPO, label = 'qual' } = {}) {
127
127
  }
128
128
  export function worldEnv(w, extra = {}) {
129
129
  const env = {};
130
- for (const k of ['PATH', 'LANG', 'LC_ALL', 'SHELL', 'USER']) if (process.env[k] !== undefined) env[k] = process.env[k];
130
+ // RUVNET_STOP_NUDGES is forwarded: with it stripped, a qualification run under `full` never executed the
131
+ // hooks that hook-shim.mjs runs only in that mode (grounding-turn-gate exited 0 unrun and "qualified").
132
+ for (const k of ['PATH', 'LANG', 'LC_ALL', 'SHELL', 'USER', 'RUVNET_STOP_NUDGES']) if (process.env[k] !== undefined) env[k] = process.env[k];
131
133
  env.PATH = `${path.dirname(process.execPath)}${path.delimiter}${env.PATH || '/usr/bin:/bin'}`;
132
134
  Object.assign(env, { HOME: w.home, USERPROFILE: w.home, TMPDIR: w.tmp, CLAUDE_PROJECT_DIR: w.cwd, RUVNET_BRAIN_HOME: path.join(w.home, '.cache', 'ruvnet-brain') });
133
135
  if (w.host === 'codex') env.CODEX_HOME = path.join(w.home, '.codex');
@@ -17,6 +17,7 @@ import {
17
17
  CASES, HOSTS, auditWrites, fixturesFor, measureInjection, registrations, runCase, runConcurrent, runRepeat,
18
18
  sandboxAvailable, staticFindings,
19
19
  } from './hook-qualify-core.mjs';
20
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
20
21
 
21
22
  function arg(name, dflt = null) {
22
23
  const i = process.argv.indexOf(`--${name}`);
@@ -98,4 +99,4 @@ async function main() {
98
99
  return rows.every((r) => r.pass) ? 0 : 1;
99
100
  }
100
101
 
101
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main().then((c) => process.exit(c), (e) => { console.error(e); process.exit(2); });
102
+ if (isDirectInvocation(import.meta.url)) main().then((c) => process.exit(c), (e) => { console.error(e); process.exit(2); });
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { automaticHookRetirementStatus } from '../bin/install.mjs';
5
5
  import { continuityRegistrations } from '../plugin/scripts/continuity-hook-policy.mjs';
6
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
6
7
 
7
8
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
8
9
 
@@ -24,6 +25,6 @@ export function main() {
24
25
  return 0;
25
26
  }
26
27
 
27
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
28
+ if (isDirectInvocation(import.meta.url)) {
28
29
  process.exitCode = main();
29
30
  }
@@ -5,6 +5,7 @@ import path from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { canonicalJson, digest } from './coverage-integrity.mjs';
7
7
  import { HOST_MODES, MODE_HOSTS } from './host-install-matrix.mjs';
8
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
8
9
 
9
10
  export const HOST_ADAPTER_FILES = Object.freeze([
10
11
  'plugin/host-adapters/claude.json',
@@ -115,4 +116,4 @@ export function main(args = process.argv.slice(2)) {
115
116
  }
116
117
  }
117
118
 
118
- if (((() => { try { return process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return false; } })())) process.exitCode = main();
119
+ if (isDirectInvocation(import.meta.url)) process.exitCode = main();
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
6
6
  import { canonicalJson, digest } from './coverage-integrity.mjs';
7
7
  import { transactionIdFor } from './release-transaction.mjs';
8
8
  import { CLAUDE_FABLE_5_ID, GPT_5_6_SOL_ID, LEGACY_REVIEWER_IDENTITIES } from './review-model-defaults.mjs';
9
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
9
10
  const HEX40 = /^[a-f0-9]{40}$/;
10
11
  const HEX64 = /^[a-f0-9]{64}$/;
11
12
  const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
@@ -495,4 +496,4 @@ export function main(args = process.argv.slice(2), runtime = {}) {
495
496
  }
496
497
  }
497
498
 
498
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) process.exitCode = main();
499
+ if (isDirectInvocation(import.meta.url)) process.exitCode = main();
@@ -35,6 +35,7 @@ import path from 'node:path';
35
35
  import { fileURLToPath } from 'node:url';
36
36
  import { storeRoot, storesAt, darkStores } from '../kb/store-root.mjs';
37
37
  import { assertIsolatedMutationWorktree } from './worktree-integrity.mjs';
38
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
38
39
 
39
40
  const ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
40
41
  const OWNER = process.env.RUVNET_ORG_OWNER || 'ruvnet';
@@ -77,7 +78,7 @@ export function pendingRepos(orgRepos, root = storeRoot()) {
77
78
  }
78
79
 
79
80
  const isMain = (() => {
80
- try { return process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url); }
81
+ try { return isDirectInvocation(import.meta.url); }
81
82
  catch { return false; }
82
83
  })();
83
84
 
@@ -21,6 +21,7 @@ import { execFileSync } from 'node:child_process';
21
21
  import { fileURLToPath } from 'node:url';
22
22
  import { FULL_HINTS, KEEP_DIRS } from './full-hints.mjs';
23
23
  import { storeRoot } from '../kb/store-root.mjs';
24
+ import { repositoryNames } from '../kb/card-lane.mjs';
24
25
  import { applyPrivateOverlay } from './private-overlay.mjs';
25
26
 
26
27
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
@@ -103,11 +104,20 @@ const ok = fs.existsSync(path.join(KB, `${kb}.big.rvf`))
103
104
  // cannot reach it. 26 of 66 stores are in exactly that state. The 2026-08-12 incident (three repos
104
105
  // ingested, all reporting success, none findable) was read as a path bug; the path was only half of
105
106
  // it. A store with no card is BUILT AND DARK, and saying "searchable" of it is the product lying.
107
+ // ALIAS-AWARE, BECAUSE THE ROUTER IS (kb/store-root.mjs's darkStores(), same fix). A raw heading
108
+ // match against `kb` alone reports DARK for a store only reachable through an alias card — e.g.
109
+ // `metaharness` is built as `metaharness.big.rvf` but described under `## agent-harness-generator`.
110
+ // That exact false positive once caused a DUPLICATE `## metaharness` card to be hand-added, which
111
+ // collided with the alias card and broke routing outright (see kb/store-root.mjs's own account).
112
+ // This CLI is where a user/agent decides whether to write a new card off this message, so it is
113
+ // the highest-leverage place to get alias resolution right.
106
114
  const carded = (() => {
115
+ let headings;
107
116
  try {
108
- return new RegExp(`^##\\s+${kb}\\s*$`, 'im')
109
- .test(fs.readFileSync(path.join(KB, 'capability-cards.md'), 'utf8'));
117
+ const raw = fs.readFileSync(path.join(KB, 'capability-cards.md'), 'utf8');
118
+ headings = [...raw.matchAll(/^##\s+(\S+)\s*$/gm)].map((m) => m[1].toLowerCase());
110
119
  } catch { return false; }
120
+ return repositoryNames(kb, KB).some((name) => headings.includes(String(name).toLowerCase()));
111
121
  })();
112
122
  // AN ARTIFACT WITH NO COMMITTED RECIPE IS NOT DURABLE WORK — it is a local side effect with a
113
123
  // countdown on it. Proven overnight 2026-08-13 -> 08-14: a scheduled bundle apply extracted an
@@ -4,6 +4,7 @@ import { validateQualificationReceipt } from './release-qualification.mjs';
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
7
8
 
8
9
  export const EXCLUSION_POLICY = 'release-linux-v1';
9
10
 
@@ -67,7 +68,7 @@ export function buildQualifiedIntegrationEvidence(report, identity) {
67
68
  qualificationReceiptSha256: report.receiptSha256, requirements: report.requirements };
68
69
  }
69
70
 
70
- const isCli = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
71
+ const isCli = isDirectInvocation(import.meta.url);
71
72
  if (isCli) {
72
73
  const value = (name) => process.argv[process.argv.indexOf(name) + 1];
73
74
  const build = process.argv.includes('--qualified') ? buildQualifiedIntegrationEvidence : buildIntegrationEvidence;