ruvnet-brain 4.7.19 → 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 +8 -16
  64. package/plugin/scripts/session-start-health.mjs +97 -10
  65. package/plugin/scripts/session-start-issue-alert.mjs +105 -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 +164 -47
  138. package/scripts/job-heartbeat.sh +145 -13
  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 +30 -21
  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 +549 -0
  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 +71 -26
  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
@@ -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;
@@ -46,7 +46,8 @@ import path from 'node:path';
46
46
  import os from 'node:os';
47
47
  import { spawn, spawnSync } from 'node:child_process';
48
48
  import { fileURLToPath, pathToFileURL } from 'node:url';
49
- import { BOT_MARKER, OWNER_LOGIN } from './issue-watch.mjs';
49
+ import { BOT_MARKER, OWNER_LOGIN, ghJsonWithRetry } from './issue-watch.mjs';
50
+ import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
50
51
 
51
52
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
52
53
  const REPO = 'stuinfla/ruvnet-brain';
@@ -118,28 +119,10 @@ export function executionPolicy(mode = 'unattended') {
118
119
  throw new Error(`unsupported issue-fix mode: ${mode}`);
119
120
  }
120
121
 
121
- function ghJson(args) {
122
- // Retry ONCE on a transient network-shaped failure (2026-07-19: a 1am GitHub API blip — "TLS
123
- // handshake timeout" / "unexpected EOF" — failed the whole run and gonged the phone, when 20s of
124
- // patience was the honest fix). Same bounded philosophy as nightly-wrapper's retry: blind retries
125
- // fix exactly one class (transient network), so retry exactly once, log the first failure, and
126
- // still fail LOUD if it happens twice. Never a silent swallow.
127
- let lastErr;
128
- for (let attempt = 1; attempt <= 2; attempt++) {
129
- const res = spawnSync(GH_BIN, args, { encoding: 'utf8' });
130
- if (res.status === 0) return JSON.parse(res.stdout);
131
- const err = (res.stderr || res.stdout || '').trim();
132
- lastErr = new Error(`gh ${args.join(' ')} failed (exit ${res.status}): ${err}`);
133
- const transient = /TLS handshake|unexpected EOF|timeout|ECONNRESET|ETIMEDOUT|EAI_AGAIN|connection refused|temporarily unavailable/i.test(err);
134
- if (attempt === 1 && transient) {
135
- console.error(`issue-fix: transient gh/network failure (${err.slice(0, 90)}) — retrying once in 20s`);
136
- spawnSync('sleep', ['20']);
137
- continue;
138
- }
139
- break;
140
- }
141
- throw lastErr;
142
- }
122
+ // Transient gh failures (TLS handshake, "error connecting to api.github.com", HTTP 502/503/504) are retried with
123
+ // backoff by the watcher's shared helper — one rule in one place (4.7.21: the old copy here missed the 1 a.m.
124
+ // "error connecting" blip and paged). Anything else still fails LOUD on the first attempt.
125
+ const ghJson = (args) => ghJsonWithRetry(args, { bin: GH_BIN, who: 'issue-fix' });
143
126
 
144
127
  function loadState() {
145
128
  try { return JSON.parse(fs.readFileSync(STATE_PATH, 'utf8')); } catch { return {}; }
@@ -555,4 +538,4 @@ async function main() {
555
538
  process.exit(failedAttempt ? 1 : 0);
556
539
  }
557
540
 
558
- if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) await main();
541
+ if (isDirectInvocation(import.meta.url)) await main();