nexus-agents 8.104.9 → 8.105.0

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 (145) hide show
  1. package/dist/{ast-rule-runner-ABFVZFIE.js → ast-rule-runner-SGQI3VOF.js} +4 -3
  2. package/dist/{child-mcp-config-L42WMRSO.js → child-mcp-config-6J647CLV.js} +2 -2
  3. package/dist/{chunk-6J3EIPB5.js → chunk-3NONAKKZ.js} +2 -2
  4. package/dist/{chunk-KJ3JOYAI.js → chunk-5MHIYVCE.js} +21 -4
  5. package/dist/chunk-5MHIYVCE.js.map +1 -0
  6. package/dist/{chunk-D3KUSAVV.js → chunk-5NKP4DVA.js} +4 -4
  7. package/dist/{chunk-N67B5VOS.js → chunk-75XDI2LH.js} +23 -110
  8. package/dist/chunk-75XDI2LH.js.map +1 -0
  9. package/dist/{chunk-CCTSFRMO.js → chunk-ACE5NI5A.js} +19 -14
  10. package/dist/chunk-ACE5NI5A.js.map +1 -0
  11. package/dist/{chunk-VTJ4Q2HS.js → chunk-C7DGP6G7.js} +8 -6
  12. package/dist/{chunk-VTJ4Q2HS.js.map → chunk-C7DGP6G7.js.map} +1 -1
  13. package/dist/{chunk-I3KWRIK3.js → chunk-CWWJUQY6.js} +9 -7
  14. package/dist/chunk-CWWJUQY6.js.map +1 -0
  15. package/dist/{chunk-EVKU3522.js → chunk-DXXPCVFN.js} +2 -2
  16. package/dist/{chunk-NR37NPJ5.js → chunk-E4OCHK7E.js} +5 -5
  17. package/dist/{chunk-5WGEXJXD.js → chunk-E7IXV4NF.js} +4 -4
  18. package/dist/{chunk-LYDVF5HM.js → chunk-EK6J5SHZ.js} +2 -2
  19. package/dist/{chunk-6VJAD6TX.js → chunk-G2GXD2Z2.js} +8 -8
  20. package/dist/{chunk-PAUO5OFW.js → chunk-GGP3F7FG.js} +2 -2
  21. package/dist/{chunk-BS7JCTJD.js → chunk-GRHDTIAB.js} +127 -120
  22. package/dist/{chunk-BS7JCTJD.js.map → chunk-GRHDTIAB.js.map} +1 -1
  23. package/dist/{chunk-KV2MKVGJ.js → chunk-INNBAGGQ.js} +3 -3
  24. package/dist/{chunk-AM4U2WV5.js → chunk-JYDTFMQ7.js} +4 -4
  25. package/dist/{chunk-EDLUQQOT.js → chunk-KG625C53.js} +2 -2
  26. package/dist/{chunk-VNUYAFKV.js → chunk-KRY5VIQW.js} +49 -14
  27. package/dist/chunk-KRY5VIQW.js.map +1 -0
  28. package/dist/{chunk-DURQTWU3.js → chunk-ME44FKOB.js} +2 -2
  29. package/dist/{chunk-SIQVWR3R.js → chunk-RPOGHHN7.js} +188 -137
  30. package/dist/chunk-RPOGHHN7.js.map +1 -0
  31. package/dist/{chunk-TEVQXLAI.js → chunk-SCYQZCHJ.js} +3 -3
  32. package/dist/{chunk-XDIPNMNO.js → chunk-SDUKS5RF.js} +2 -2
  33. package/dist/{chunk-ZPGB6CEP.js → chunk-SK32DF4A.js} +7 -9
  34. package/dist/chunk-SK32DF4A.js.map +1 -0
  35. package/dist/{chunk-JBCG5X7E.js → chunk-SV45NVN2.js} +2 -2
  36. package/dist/{chunk-GOREKLDR.js → chunk-TDELDKWG.js} +118 -134
  37. package/dist/chunk-TDELDKWG.js.map +1 -0
  38. package/dist/{chunk-AO7KWQ7R.js → chunk-TWIVLQIJ.js} +2 -2
  39. package/dist/{chunk-ITPPSP6G.js → chunk-U7D2NZXS.js} +2 -2
  40. package/dist/{chunk-D7G3YBCA.js → chunk-UEIQZUPN.js} +5 -5
  41. package/dist/{chunk-VJGTOYBQ.js → chunk-UMKRODKL.js} +2 -2
  42. package/dist/{chunk-XAYG7O3C.js → chunk-VUVFGITX.js} +11 -11
  43. package/dist/{chunk-3KZXHRE6.js → chunk-W62UJDZE.js} +2 -2
  44. package/dist/{chunk-WHJBMMYJ.js → chunk-WD5KUEX3.js} +2 -2
  45. package/dist/{chunk-L3XZZDC3.js → chunk-XCA2YZZZ.js} +2 -2
  46. package/dist/chunk-XEPZSDN5.js +91 -0
  47. package/dist/chunk-XEPZSDN5.js.map +1 -0
  48. package/dist/{chunk-FPEXK4QA.js → chunk-YLUCWOR6.js} +3 -3
  49. package/dist/{chunk-H4OMX2IO.js → chunk-ZKWGNNKP.js} +2 -2
  50. package/dist/{cli-circuit-breaker-UT7775N4.js → cli-circuit-breaker-NHNQX2ZB.js} +3 -3
  51. package/dist/cli.js +70 -72
  52. package/dist/cli.js.map +1 -1
  53. package/dist/{composite-router-F7YJMJRF.js → composite-router-VFEUI72D.js} +2 -2
  54. package/dist/{consensus-vote-Q5V7U4FH.js → consensus-vote-IPJFQXIK.js} +18 -17
  55. package/dist/{context-retriever-JPE7KFIY.js → context-retriever-5WQ2YWNE.js} +9 -8
  56. package/dist/{doctor-deep-VXQO47IW.js → doctor-deep-FNBDBTBM.js} +3 -3
  57. package/dist/{doctor-gateway-report-TEM5VCBO.js → doctor-gateway-report-WHI5WQ4N.js} +6 -6
  58. package/dist/{doctor-live-LTSUOG3N.js → doctor-live-MMGCD6PM.js} +26 -12
  59. package/dist/doctor-live-MMGCD6PM.js.map +1 -0
  60. package/dist/{expert-bridge-52P4UMCP.js → expert-bridge-BDO4LV26.js} +5 -5
  61. package/dist/{factory-3WRKONLJ.js → factory-QUENHU3I.js} +5 -5
  62. package/dist/{factory-6AODFBXM.js → factory-SBAHD6WP.js} +9 -9
  63. package/dist/{improvement-review-A5ISRRXZ.js → improvement-review-BK2GLXBS.js} +6 -5
  64. package/dist/index.d.ts +8 -7
  65. package/dist/index.js +33 -32
  66. package/dist/index.js.map +1 -1
  67. package/dist/{init-opencode-JCQJGZ4C.js → init-opencode-KOPBB643.js} +5 -5
  68. package/dist/issue-triage-AZQRUQOH.js +16 -0
  69. package/dist/{openai-compat-adapter-6CMTDYRM.js → openai-compat-adapter-4S32VPGP.js} +5 -5
  70. package/dist/{pr-reviewer-helpers-3DOVVFWC.js → pr-reviewer-helpers-MGBA4VLQ.js} +4 -4
  71. package/dist/{registry-command-EITBUPO4.js → registry-command-6FQFIA7D.js} +2 -2
  72. package/dist/{repo-security-plan-GPU6EB2D.js → repo-security-plan-X5NNMNEZ.js} +3 -3
  73. package/dist/{research-helpers-synthesize-26OZG733.js → research-helpers-synthesize-VIAXHYD5.js} +5 -4
  74. package/dist/{routing-memory-TQ5QSILH.js → routing-memory-RHEUT6CE.js} +2 -2
  75. package/dist/{session-memory-KVPW44QX.js → session-memory-C6VTHVNC.js} +3 -3
  76. package/dist/{setup-config-G6KNCDHR.js → setup-config-BOKVRB67.js} +3 -3
  77. package/dist/{setup-custom-api-USBCGUN7.js → setup-custom-api-EVXBKE7I.js} +4 -4
  78. package/dist/setup-data-dir-3WHFLPS4.js +34 -0
  79. package/dist/{tool-memory-JO76QLUK.js → tool-memory-IYXG7RHF.js} +5 -5
  80. package/dist/{unified-registry-DKCKGMLJ.js → unified-registry-YJSEL7AJ.js} +11 -11
  81. package/dist/{weather-report-STAFMD5D.js → weather-report-H7GRKDDA.js} +2 -2
  82. package/package.json +1 -1
  83. package/dist/chunk-CCTSFRMO.js.map +0 -1
  84. package/dist/chunk-GOREKLDR.js.map +0 -1
  85. package/dist/chunk-I3KWRIK3.js.map +0 -1
  86. package/dist/chunk-KJ3JOYAI.js.map +0 -1
  87. package/dist/chunk-N67B5VOS.js.map +0 -1
  88. package/dist/chunk-SIQVWR3R.js.map +0 -1
  89. package/dist/chunk-VNUYAFKV.js.map +0 -1
  90. package/dist/chunk-ZPGB6CEP.js.map +0 -1
  91. package/dist/doctor-live-LTSUOG3N.js.map +0 -1
  92. package/dist/issue-triage-AJVRURIZ.js +0 -16
  93. package/dist/setup-data-dir-7FDJPPS3.js +0 -34
  94. /package/dist/{ast-rule-runner-ABFVZFIE.js.map → ast-rule-runner-SGQI3VOF.js.map} +0 -0
  95. /package/dist/{child-mcp-config-L42WMRSO.js.map → child-mcp-config-6J647CLV.js.map} +0 -0
  96. /package/dist/{chunk-6J3EIPB5.js.map → chunk-3NONAKKZ.js.map} +0 -0
  97. /package/dist/{chunk-D3KUSAVV.js.map → chunk-5NKP4DVA.js.map} +0 -0
  98. /package/dist/{chunk-EVKU3522.js.map → chunk-DXXPCVFN.js.map} +0 -0
  99. /package/dist/{chunk-NR37NPJ5.js.map → chunk-E4OCHK7E.js.map} +0 -0
  100. /package/dist/{chunk-5WGEXJXD.js.map → chunk-E7IXV4NF.js.map} +0 -0
  101. /package/dist/{chunk-LYDVF5HM.js.map → chunk-EK6J5SHZ.js.map} +0 -0
  102. /package/dist/{chunk-6VJAD6TX.js.map → chunk-G2GXD2Z2.js.map} +0 -0
  103. /package/dist/{chunk-PAUO5OFW.js.map → chunk-GGP3F7FG.js.map} +0 -0
  104. /package/dist/{chunk-KV2MKVGJ.js.map → chunk-INNBAGGQ.js.map} +0 -0
  105. /package/dist/{chunk-AM4U2WV5.js.map → chunk-JYDTFMQ7.js.map} +0 -0
  106. /package/dist/{chunk-EDLUQQOT.js.map → chunk-KG625C53.js.map} +0 -0
  107. /package/dist/{chunk-DURQTWU3.js.map → chunk-ME44FKOB.js.map} +0 -0
  108. /package/dist/{chunk-TEVQXLAI.js.map → chunk-SCYQZCHJ.js.map} +0 -0
  109. /package/dist/{chunk-XDIPNMNO.js.map → chunk-SDUKS5RF.js.map} +0 -0
  110. /package/dist/{chunk-JBCG5X7E.js.map → chunk-SV45NVN2.js.map} +0 -0
  111. /package/dist/{chunk-AO7KWQ7R.js.map → chunk-TWIVLQIJ.js.map} +0 -0
  112. /package/dist/{chunk-ITPPSP6G.js.map → chunk-U7D2NZXS.js.map} +0 -0
  113. /package/dist/{chunk-D7G3YBCA.js.map → chunk-UEIQZUPN.js.map} +0 -0
  114. /package/dist/{chunk-VJGTOYBQ.js.map → chunk-UMKRODKL.js.map} +0 -0
  115. /package/dist/{chunk-XAYG7O3C.js.map → chunk-VUVFGITX.js.map} +0 -0
  116. /package/dist/{chunk-3KZXHRE6.js.map → chunk-W62UJDZE.js.map} +0 -0
  117. /package/dist/{chunk-WHJBMMYJ.js.map → chunk-WD5KUEX3.js.map} +0 -0
  118. /package/dist/{chunk-L3XZZDC3.js.map → chunk-XCA2YZZZ.js.map} +0 -0
  119. /package/dist/{chunk-FPEXK4QA.js.map → chunk-YLUCWOR6.js.map} +0 -0
  120. /package/dist/{chunk-H4OMX2IO.js.map → chunk-ZKWGNNKP.js.map} +0 -0
  121. /package/dist/{cli-circuit-breaker-UT7775N4.js.map → cli-circuit-breaker-NHNQX2ZB.js.map} +0 -0
  122. /package/dist/{composite-router-F7YJMJRF.js.map → composite-router-VFEUI72D.js.map} +0 -0
  123. /package/dist/{consensus-vote-Q5V7U4FH.js.map → consensus-vote-IPJFQXIK.js.map} +0 -0
  124. /package/dist/{context-retriever-JPE7KFIY.js.map → context-retriever-5WQ2YWNE.js.map} +0 -0
  125. /package/dist/{doctor-deep-VXQO47IW.js.map → doctor-deep-FNBDBTBM.js.map} +0 -0
  126. /package/dist/{doctor-gateway-report-TEM5VCBO.js.map → doctor-gateway-report-WHI5WQ4N.js.map} +0 -0
  127. /package/dist/{expert-bridge-52P4UMCP.js.map → expert-bridge-BDO4LV26.js.map} +0 -0
  128. /package/dist/{factory-3WRKONLJ.js.map → factory-QUENHU3I.js.map} +0 -0
  129. /package/dist/{factory-6AODFBXM.js.map → factory-SBAHD6WP.js.map} +0 -0
  130. /package/dist/{improvement-review-A5ISRRXZ.js.map → improvement-review-BK2GLXBS.js.map} +0 -0
  131. /package/dist/{init-opencode-JCQJGZ4C.js.map → init-opencode-KOPBB643.js.map} +0 -0
  132. /package/dist/{issue-triage-AJVRURIZ.js.map → issue-triage-AZQRUQOH.js.map} +0 -0
  133. /package/dist/{openai-compat-adapter-6CMTDYRM.js.map → openai-compat-adapter-4S32VPGP.js.map} +0 -0
  134. /package/dist/{pr-reviewer-helpers-3DOVVFWC.js.map → pr-reviewer-helpers-MGBA4VLQ.js.map} +0 -0
  135. /package/dist/{registry-command-EITBUPO4.js.map → registry-command-6FQFIA7D.js.map} +0 -0
  136. /package/dist/{repo-security-plan-GPU6EB2D.js.map → repo-security-plan-X5NNMNEZ.js.map} +0 -0
  137. /package/dist/{research-helpers-synthesize-26OZG733.js.map → research-helpers-synthesize-VIAXHYD5.js.map} +0 -0
  138. /package/dist/{routing-memory-TQ5QSILH.js.map → routing-memory-RHEUT6CE.js.map} +0 -0
  139. /package/dist/{session-memory-KVPW44QX.js.map → session-memory-C6VTHVNC.js.map} +0 -0
  140. /package/dist/{setup-config-G6KNCDHR.js.map → setup-config-BOKVRB67.js.map} +0 -0
  141. /package/dist/{setup-custom-api-USBCGUN7.js.map → setup-custom-api-EVXBKE7I.js.map} +0 -0
  142. /package/dist/{setup-data-dir-7FDJPPS3.js.map → setup-data-dir-3WHFLPS4.js.map} +0 -0
  143. /package/dist/{tool-memory-JO76QLUK.js.map → tool-memory-IYXG7RHF.js.map} +0 -0
  144. /package/dist/{unified-registry-DKCKGMLJ.js.map → unified-registry-YJSEL7AJ.js.map} +0 -0
  145. /package/dist/{weather-report-STAFMD5D.js.map → weather-report-H7GRKDDA.js.map} +0 -0
@@ -9,8 +9,9 @@ import {
9
9
  getBuiltInAstRulesPath,
10
10
  loadRules,
11
11
  runAstQaRules
12
- } from "./chunk-I3KWRIK3.js";
13
- import "./chunk-SIQVWR3R.js";
12
+ } from "./chunk-CWWJUQY6.js";
13
+ import "./chunk-XEPZSDN5.js";
14
+ import "./chunk-RPOGHHN7.js";
14
15
  import "./chunk-UE7A2XHM.js";
15
16
  import "./chunk-LWOX65CP.js";
16
17
  import "./chunk-PX6F3LHL.js";
@@ -26,4 +27,4 @@ export {
26
27
  loadRules,
27
28
  runAstQaRules
28
29
  };
29
- //# sourceMappingURL=ast-rule-runner-ABFVZFIE.js.map
30
+ //# sourceMappingURL=ast-rule-runner-SGQI3VOF.js.map
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-OITVVSWO.js";
4
4
  import {
5
5
  createLogger
6
- } from "./chunk-SIQVWR3R.js";
6
+ } from "./chunk-RPOGHHN7.js";
7
7
  import "./chunk-UE7A2XHM.js";
8
8
  import "./chunk-LWOX65CP.js";
9
9
  import "./chunk-PX6F3LHL.js";
@@ -56,4 +56,4 @@ async function generateMcpConfig(options) {
56
56
  export {
57
57
  generateMcpConfig
58
58
  };
59
- //# sourceMappingURL=child-mcp-config-L42WMRSO.js.map
59
+ //# sourceMappingURL=child-mcp-config-6J647CLV.js.map
@@ -2,7 +2,7 @@ import {
2
2
  createLogger,
3
3
  err,
4
4
  ok
5
- } from "./chunk-SIQVWR3R.js";
5
+ } from "./chunk-RPOGHHN7.js";
6
6
 
7
7
  // src/scm/token-resolver.ts
8
8
  import { execFile } from "node:child_process";
@@ -87,4 +87,4 @@ export {
87
87
  hasToken,
88
88
  getTokenEnvVars
89
89
  };
90
- //# sourceMappingURL=chunk-6J3EIPB5.js.map
90
+ //# sourceMappingURL=chunk-3NONAKKZ.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveClassGuardMs
3
- } from "./chunk-SIQVWR3R.js";
3
+ } from "./chunk-RPOGHHN7.js";
4
4
 
5
5
  // src/cli/cli-readiness.ts
6
6
  var READINESS_LEVELS = ["installed", "authenticated", "serves"];
@@ -16,6 +16,23 @@ function buildReadiness(cli, levels) {
16
16
  const reached = highestVerified(levels);
17
17
  return { cli, levels, ...reached !== void 0 ? { reached } : {} };
18
18
  }
19
+ function buildGatewayReadiness(cli, gateway, serves) {
20
+ const notRun = {
21
+ status: "not-attempted",
22
+ reason: `the CLI is not run: gateway model ${gateway.gatewayModel} serves this slot`
23
+ };
24
+ const levels = { installed: notRun, authenticated: notRun, serves };
25
+ return { cli, levels, gateway, ...serves.status === "verified" ? { reached: "serves" } : {} };
26
+ }
27
+ function readinessSummary(readiness) {
28
+ const g = readiness.gateway;
29
+ if (g !== void 0) {
30
+ const why = g.cliState === "disabled" ? "CLI disabled by NEXUS_DISABLED_CLIS" : "CLI not available";
31
+ const verdict = readiness.reached === "serves" ? "serves" : "not ready";
32
+ return `served by gateway model ${g.gatewayModel} (${why}) \u2014 ${verdict}`;
33
+ }
34
+ return readiness.reached === void 0 ? "not ready" : `ready through "${readiness.reached}"`;
35
+ }
19
36
  function formatReadiness(readiness) {
20
37
  const icon = (o) => {
21
38
  switch (o.status) {
@@ -36,8 +53,7 @@ function formatReadiness(readiness) {
36
53
  const detail = outcome.status === "verified" ? "" : ` \u2014 ${outcome.reason}`;
37
54
  return ` ${icon(outcome)} ${level}${detail}`;
38
55
  });
39
- const summary = readiness.reached === void 0 ? "not ready" : `ready through "${readiness.reached}"`;
40
- return [` ${readiness.cli}: ${summary}`, ...lines].join("\n");
56
+ return [` ${readiness.cli}: ${readinessSummary(readiness)}`, ...lines].join("\n");
41
57
  }
42
58
  var SERVES_PROBE_PROMPT = "Reply with the single word: ok";
43
59
  async function probeServes(adapter, timeoutMs = resolveClassGuardMs("interactive")) {
@@ -86,8 +102,9 @@ async function withDeadline(work, timeoutMs) {
86
102
 
87
103
  export {
88
104
  buildReadiness,
105
+ buildGatewayReadiness,
89
106
  formatReadiness,
90
107
  SERVES_PROBE_PROMPT,
91
108
  probeServes
92
109
  };
93
- //# sourceMappingURL=chunk-KJ3JOYAI.js.map
110
+ //# sourceMappingURL=chunk-5MHIYVCE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/cli-readiness.ts"],"sourcesContent":["/**\n * Readiness levels for a CLI adapter (#4376, #4351 criterion 7).\n *\n * Nothing in the tree could confirm that an adapter which *looks* healthy can\n * actually serve a completion. `healthCheck()` proves the binary exists at an\n * acceptable version; `cli-auth-probe` proves credentials are present and\n * unexpired, and says so explicitly — \"No live API calls.\" Neither proves the\n * adapter serves anything.\n *\n * That is the state #4351 reported: every voter returned `stop_sequence` with\n * zero tokens while every available check said healthy. The checks were not\n * wrong; they were answering a narrower question than the caller assumed.\n *\n * ## Levels, and what each one actually proves\n *\n * | level | proves | costs |\n * | --------------- | ----------------------------------------- | ----- |\n * | `installed` | the binary exists at a supported version | none |\n * | `authenticated` | credentials are present and unexpired | none |\n * | `serves` | a real completion came back with content | quota |\n *\n * `serves` is the only level that can catch #4351, and the only one that\n * spends the resource it is measuring. It therefore runs **only when\n * explicitly invoked** (`doctor --live`). Decided by a 7-voter `higher_order`\n * panel on #4376 (5 of 6 approvers): the rejected alternative cached a probe\n * result behind a TTL on the default path, which is a stale measurement\n * presented as current readiness — the same defect this module exists to fix.\n *\n * ## Not-attempted is not a failure\n *\n * The load-bearing distinction. A level that was never run reports\n * `not-attempted`, never `failed`. A reader must be able to tell \"we checked\n * and it does not serve\" from \"we did not check\", because the second is the\n * default state of every run that does not pass `--live`, and reporting it as\n * either healthy or broken would be a claim nobody measured.\n *\n * @module cli/cli-readiness\n * (Source: Issue #4376)\n */\n\nimport { resolveClassGuardMs } from '../config/timeouts.js';\nimport type { CliName } from '../cli-adapters/types.js';\n\n/** Ordered weakest to strongest. Each level subsumes the ones before it. */\nexport const READINESS_LEVELS = ['installed', 'authenticated', 'serves'] as const;\n\nexport type ReadinessLevel = (typeof READINESS_LEVELS)[number];\n\n/** What a single level's check concluded. */\nexport type LevelOutcome =\n | { readonly status: 'verified' }\n | { readonly status: 'failed'; readonly reason: string }\n /** Not run. Carries why, so the report can distinguish opt-out from error. */\n | { readonly status: 'not-attempted'; readonly reason: string };\n\n/**\n * The slot is served by a gateway model, not the CLI (#6720). Its CLI levels\n * are not measured — the CLI is disabled or not available, and is not run —\n * so only `serves` says anything, and it says it about the gateway model.\n */\ninterface GatewayServedReadiness {\n readonly gatewayModel: string;\n /** Why the CLI does not serve the slot. */\n readonly cliState: 'disabled' | 'not-available';\n}\n\nexport interface CliReadiness {\n readonly cli: CliName;\n readonly levels: Readonly<Record<ReadinessLevel, LevelOutcome>>;\n /** Present when a gateway model serves the slot; the levels then describe it. */\n readonly gateway?: GatewayServedReadiness;\n /**\n * The strongest level actually VERIFIED, or undefined when even `installed`\n * failed. Never inferred from a level that was not attempted.\n */\n readonly reached?: ReadinessLevel;\n}\n\n/** Human-readable statement of what a level does and does not prove. */\nexport const LEVEL_MEANING: Readonly<Record<ReadinessLevel, string>> = {\n installed: 'binary present at a supported version — proves nothing about auth or serving',\n authenticated: 'credentials present and unexpired, read locally — no API call was made',\n serves: 'a real completion returned content — the only level that proves the adapter works',\n};\n\n/**\n * The strongest verified level, stopping at the first gap.\n *\n * Stops rather than scanning for any verified level, because the ladder is\n * cumulative: a `serves` verification below a failed `authenticated` would\n * mean the two checks disagree, and reporting the higher one would paper over\n * that. A `not-attempted` level stops the ladder exactly like a failure —\n * absence of a check is not a pass.\n */\nexport function highestVerified(\n levels: Readonly<Record<ReadinessLevel, LevelOutcome>>\n): ReadinessLevel | undefined {\n let reached: ReadinessLevel | undefined;\n for (const level of READINESS_LEVELS) {\n if (levels[level].status !== 'verified') break;\n reached = level;\n }\n return reached;\n}\n\n/** Assemble a readiness result, deriving `reached` from the outcomes. */\nexport function buildReadiness(\n cli: CliName,\n levels: Readonly<Record<ReadinessLevel, LevelOutcome>>\n): CliReadiness {\n const reached = highestVerified(levels);\n return { cli, levels, ...(reached !== undefined ? { reached } : {}) };\n}\n\n/**\n * Readiness of a slot a gateway model serves (#6720). `installed` and\n * `authenticated` are the CLI's rungs and were not run — the CLI is not what\n * serves — so they are `not-attempted` with that reason, and `reached` is\n * `serves` exactly when the gateway model's completion came back.\n */\nexport function buildGatewayReadiness(\n cli: CliName,\n gateway: GatewayServedReadiness,\n serves: LevelOutcome\n): CliReadiness {\n const notRun: LevelOutcome = {\n status: 'not-attempted',\n reason: `the CLI is not run: gateway model ${gateway.gatewayModel} serves this slot`,\n };\n const levels = { installed: notRun, authenticated: notRun, serves };\n return { cli, levels, gateway, ...(serves.status === 'verified' ? { reached: 'serves' } : {}) };\n}\n\n/** The header of one readiness entry. */\nfunction readinessSummary(readiness: CliReadiness): string {\n const g = readiness.gateway;\n if (g !== undefined) {\n const why =\n g.cliState === 'disabled' ? 'CLI disabled by NEXUS_DISABLED_CLIS' : 'CLI not available';\n const verdict = readiness.reached === 'serves' ? 'serves' : 'not ready';\n return `served by gateway model ${g.gatewayModel} (${why}) — ${verdict}`;\n }\n return readiness.reached === undefined ? 'not ready' : `ready through \"${readiness.reached}\"`;\n}\n\n/**\n * Render one CLI's ladder, naming every level's state.\n *\n * Every level is printed, including the ones that did not run. A report that\n * silently omitted `serves` would read as a clean bill of health from a run\n * that never tested serving — which is precisely how #4351 went unnoticed.\n */\nexport function formatReadiness(readiness: CliReadiness): string {\n // Exhaustive rather than a ternary chain (#4563): a new LevelOutcome status\n // would otherwise fall into the '·' arm and render as \"not attempted\",\n // quietly misreporting whatever the new state actually means.\n const icon = (o: LevelOutcome): string => {\n switch (o.status) {\n case 'verified':\n return '✓';\n case 'failed':\n return '✗';\n case 'not-attempted':\n return '·';\n default: {\n const exhaustive: never = o;\n throw new Error(`Unhandled level outcome: ${JSON.stringify(exhaustive)}`);\n }\n }\n };\n\n const lines = READINESS_LEVELS.map((level) => {\n const outcome = readiness.levels[level];\n const detail = outcome.status === 'verified' ? '' : ` — ${outcome.reason}`;\n return ` ${icon(outcome)} ${level}${detail}`;\n });\n\n return [` ${readiness.cli}: ${readinessSummary(readiness)}`, ...lines].join('\\n');\n}\n\n/** The minimal adapter surface the `serves` probe needs. */\nexport interface ServesProbeTarget {\n execute(task: {\n content: string;\n maxTokens?: number;\n }): Promise<{ ok: true; value: { text: string } } | { ok: false; error: { message: string } }>;\n}\n\n/**\n * The smallest prompt that still proves generation happened.\n *\n * Short on purpose — this level spends real quota, so it asks for the least\n * that can distinguish \"served content\" from \"returned nothing\".\n */\nexport const SERVES_PROBE_PROMPT = 'Reply with the single word: ok';\n\n/**\n * Probe whether an adapter actually serves a completion.\n *\n * The check that matters is **non-empty content**, not a successful exit.\n * #4351's signature was every voter returning `stop_sequence` with zero\n * tokens: the call succeeded and produced nothing. An adapter that returns\n * `ok` with empty text has not served, and saying otherwise would reproduce\n * the exact reading that made the incident invisible.\n *\n * Bounded by the `interactive` operation-class guard. Found by running it: an\n * unbounded ladder over four adapters hung past two minutes with no output,\n * and a readiness check that never returns is worse than one that reports a\n * failure — the operator learns nothing either way, but waits for it. A\n * timeout is itself a legitimate not-ready result, not an error.\n *\n * @param adapter - The adapter to probe.\n * @param timeoutMs - Per-adapter ceiling; defaults to the interactive guard.\n */\nexport async function probeServes(\n adapter: ServesProbeTarget,\n timeoutMs: number = resolveClassGuardMs('interactive')\n): Promise<LevelOutcome> {\n let result: Awaited<ReturnType<ServesProbeTarget['execute']>>;\n try {\n result = await withDeadline(\n adapter.execute({ content: SERVES_PROBE_PROMPT, maxTokens: 16 }),\n timeoutMs\n );\n } catch (caught: unknown) {\n if (caught instanceof ProbeTimeout) {\n return {\n status: 'failed',\n reason: `no response within ${String(Math.round(timeoutMs / 1000))}s`,\n };\n }\n const message = caught instanceof Error ? caught.message : String(caught);\n return { status: 'failed', reason: `probe threw: ${message}` };\n }\n\n if (!result.ok) {\n return { status: 'failed', reason: result.error.message };\n }\n if (result.value.text.trim() === '') {\n return {\n status: 'failed',\n reason: 'call succeeded but returned no content — the #4351 signature',\n };\n }\n return { status: 'verified' };\n}\n\n/** Raised when a probe outlives its ceiling. */\nclass ProbeTimeout extends Error {}\n\n/**\n * Race a promise against a deadline.\n *\n * The underlying call is not cancelled — a CLI subprocess keeps running until\n * its own guard reaps it. What this bounds is how long the OPERATOR waits,\n * which is the thing that made an unbounded ladder unusable.\n */\nasync function withDeadline<T>(work: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: NodeJS.Timeout | undefined;\n const deadline = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n reject(new ProbeTimeout());\n }, timeoutMs);\n // Deliberately NOT unref'd. An unref'd timer does not hold the event loop\n // open, so if the probed call never settles and nothing else is pending,\n // Node can exit at the await before the deadline fires — the guarantee\n // voiding itself in precisely the case it exists for. `clearTimeout` in\n // the finally is what stops it outliving a call that did settle.\n });\n\n try {\n return await Promise.race([work, deadline]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n}\n"],"mappings":"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","names":[]}
@@ -7,12 +7,12 @@ import {
7
7
  readGatewayTransport,
8
8
  requireApiKey,
9
9
  validateApiKeyPresence
10
- } from "./chunk-JBCG5X7E.js";
10
+ } from "./chunk-SV45NVN2.js";
11
11
  import {
12
12
  assertCustomApiHostResolvesPublic,
13
13
  hostnameOf,
14
14
  redactApiKey
15
- } from "./chunk-PAUO5OFW.js";
15
+ } from "./chunk-GGP3F7FG.js";
16
16
  import {
17
17
  sanitizeErrorDetails
18
18
  } from "./chunk-2NDA4DHW.js";
@@ -40,7 +40,7 @@ import {
40
40
  readModelAllowlist,
41
41
  recordUsageEvent,
42
42
  refineGatewayCatalog
43
- } from "./chunk-SIQVWR3R.js";
43
+ } from "./chunk-RPOGHHN7.js";
44
44
 
45
45
  // src/adapters/openai-compat-adapter.ts
46
46
  import OpenAI2 from "openai";
@@ -1038,4 +1038,4 @@ export {
1038
1038
  createOpenAICompatAdapter,
1039
1039
  buildOpenAICompatAdapters
1040
1040
  };
1041
- //# sourceMappingURL=chunk-D3KUSAVV.js.map
1041
+ //# sourceMappingURL=chunk-5NKP4DVA.js.map
@@ -1,3 +1,6 @@
1
+ import {
2
+ resolveInsideRoot
3
+ } from "./chunk-XEPZSDN5.js";
1
4
  import {
2
5
  ParseError,
3
6
  SecurityError,
@@ -5,7 +8,7 @@ import {
5
8
  err,
6
9
  getErrorMessage,
7
10
  ok
8
- } from "./chunk-SIQVWR3R.js";
11
+ } from "./chunk-RPOGHHN7.js";
9
12
  import {
10
13
  findRepoRoot,
11
14
  getActiveWorkspaceRoot,
@@ -14,104 +17,15 @@ import {
14
17
  } from "./chunk-LWOX65CP.js";
15
18
 
16
19
  // src/cli/research-helpers-io.ts
17
- import * as fs3 from "node:fs/promises";
18
- import { realpathSync as realpathSync2, statSync } from "node:fs";
19
- import { dirname as dirname3, join, resolve as resolve3 } from "node:path";
20
-
21
- // src/security/safe-path.ts
22
- import { resolve, sep, dirname, basename, isAbsolute, relative } from "node:path";
23
- import * as fs from "node:fs";
24
- function isEnoent(err2) {
25
- return typeof err2 === "object" && err2 !== null && "code" in err2 && err2.code === "ENOENT";
26
- }
27
- function getRealpathSync() {
28
- try {
29
- const fn = fs.realpathSync;
30
- if (typeof fn !== "function") {
31
- return void 0;
32
- }
33
- return typeof fn.native === "function" ? (p) => fn.native(p) : (p) => fn(p);
34
- } catch {
35
- return void 0;
36
- }
37
- }
38
- function canonicalizeRoot(root) {
39
- const resolved = resolve(root);
40
- const realpathFn = getRealpathSync();
41
- if (realpathFn === void 0) {
42
- return resolved;
43
- }
44
- try {
45
- return realpathFn(resolved);
46
- } catch {
47
- return resolved;
48
- }
49
- }
50
- function resolvePathWithSymlinks(targetPath) {
51
- const realpathFn = getRealpathSync();
52
- if (realpathFn === void 0) {
53
- return targetPath;
54
- }
55
- let existing = targetPath;
56
- const trailing = [];
57
- for (; ; ) {
58
- try {
59
- const real = realpathFn(existing);
60
- return trailing.length === 0 ? real : resolve(real, ...trailing.reverse());
61
- } catch (err2) {
62
- if (!isEnoent(err2)) {
63
- return null;
64
- }
65
- if (isSymlink(existing)) {
66
- return null;
67
- }
68
- const parent = dirname(existing);
69
- if (parent === existing) {
70
- return null;
71
- }
72
- trailing.push(basename(existing));
73
- existing = parent;
74
- }
75
- }
76
- }
77
- function isSymlink(path) {
78
- try {
79
- return fs.lstatSync(path).isSymbolicLink();
80
- } catch {
81
- return false;
82
- }
83
- }
84
- function isWithin(parent, child) {
85
- if (child === parent) return true;
86
- const rel = relative(parent, child);
87
- if (rel === "") return true;
88
- if (rel === ".." || rel.startsWith(".." + sep) || isAbsolute(rel)) return false;
89
- return true;
90
- }
91
- function resolveInsideRoot(filePath, root = process.cwd()) {
92
- const canonicalRoot = canonicalizeRoot(root);
93
- const resolvedRoot = resolve(root);
94
- const lexicalResolved = resolve(resolvedRoot, filePath);
95
- if (!isWithin(resolvedRoot, lexicalResolved) && !isWithin(canonicalRoot, lexicalResolved)) {
96
- return null;
97
- }
98
- const realCandidate = resolvePathWithSymlinks(lexicalResolved);
99
- if (realCandidate === null) {
100
- return null;
101
- }
102
- if (!isWithin(canonicalRoot, realCandidate)) {
103
- return null;
104
- }
105
- return realCandidate;
106
- }
107
-
108
- // src/cli/research-helpers-io.ts
20
+ import * as fs2 from "node:fs/promises";
21
+ import { realpathSync, statSync } from "node:fs";
22
+ import { dirname as dirname2, join, resolve as resolve2 } from "node:path";
109
23
  import { parse as parseYaml, stringify as stringifyYaml2 } from "yaml";
110
24
 
111
25
  // src/cli/research-scaffold.ts
112
- import * as fs2 from "node:fs/promises";
26
+ import * as fs from "node:fs/promises";
113
27
  import { existsSync } from "node:fs";
114
- import { dirname as dirname2, resolve as resolve2 } from "node:path";
28
+ import { dirname, resolve } from "node:path";
115
29
  import { stringify as stringifyYaml } from "yaml";
116
30
  var SCHEMA_VERSION = "1.0";
117
31
  var ANNOUNCED = /* @__PURE__ */ new Set();
@@ -133,7 +47,7 @@ function emptyTechniquesYaml() {
133
47
  return stringifyYaml({ schema_version: SCHEMA_VERSION, techniques: {} });
134
48
  }
135
49
  async function ensureRegistryFile(rootDir, filename) {
136
- const filePath = resolve2(rootDir, REGISTRY_PATH, filename);
50
+ const filePath = resolve(rootDir, REGISTRY_PATH, filename);
137
51
  if (existsSync(filePath)) return ok(filePath);
138
52
  if (scaffoldDisabled()) {
139
53
  return err(
@@ -142,7 +56,7 @@ async function ensureRegistryFile(rootDir, filename) {
142
56
  )
143
57
  );
144
58
  }
145
- if (!existsSync(resolve2(rootDir, "docs"))) {
59
+ if (!existsSync(resolve(rootDir, "docs"))) {
146
60
  return err(
147
61
  new ParseError(
148
62
  `Registry file missing: ${filePath}. ${rootDir}/docs/ does not exist; refusing to create a Nexus subtree in a non-documented root. Run 'nexus-agents init' first, or create ${rootDir}/docs/ manually.`
@@ -150,9 +64,9 @@ async function ensureRegistryFile(rootDir, filename) {
150
64
  );
151
65
  }
152
66
  try {
153
- await fs2.mkdir(dirname2(filePath), { recursive: true });
67
+ await fs.mkdir(dirname(filePath), { recursive: true });
154
68
  const content = filename === PAPERS_FILE ? emptyPapersYaml() : emptyTechniquesYaml();
155
- await fs2.writeFile(filePath, content, "utf-8");
69
+ await fs.writeFile(filePath, content, "utf-8");
156
70
  announceOnce(filePath, filename);
157
71
  return ok(filePath);
158
72
  } catch (e) {
@@ -182,7 +96,7 @@ function findRegistryAncestor(start) {
182
96
  let current;
183
97
  let startDev;
184
98
  try {
185
- current = realpathSync2(start);
99
+ current = realpathSync(start);
186
100
  startDev = statSync(current).dev;
187
101
  } catch {
188
102
  return null;
@@ -190,7 +104,7 @@ function findRegistryAncestor(start) {
190
104
  for (let depth = 0; depth < 64; depth++) {
191
105
  if (hasRegistryDir(current)) return current;
192
106
  if (isRepoRoot(current)) return null;
193
- const parent = dirname3(current);
107
+ const parent = dirname2(current);
194
108
  if (parent === current) return null;
195
109
  try {
196
110
  if (statSync(parent).dev !== startDev) return null;
@@ -202,7 +116,7 @@ function findRegistryAncestor(start) {
202
116
  return null;
203
117
  }
204
118
  function resolveRegistryRoot(rootDir) {
205
- if (rootDir !== void 0) return resolve3(rootDir);
119
+ if (rootDir !== void 0) return resolve2(rootDir);
206
120
  const origin = getActiveWorkspaceRoot() ?? process.cwd();
207
121
  const memoised = registryRootByOrigin.get(origin);
208
122
  if (memoised !== void 0) return memoised;
@@ -213,9 +127,9 @@ function resolveRegistryRoot(rootDir) {
213
127
  }
214
128
  let fallback;
215
129
  try {
216
- fallback = realpathSync2(origin);
130
+ fallback = realpathSync(origin);
217
131
  } catch {
218
- fallback = resolve3(origin);
132
+ fallback = resolve2(origin);
219
133
  }
220
134
  if (!warnedCwdFallback) {
221
135
  warnedCwdFallback = true;
@@ -236,7 +150,7 @@ function validatePath(constructedPath, allowedRoot) {
236
150
  return {
237
151
  ok: false,
238
152
  error: new SecurityError("Path traversal detected: path escapes allowed root directory", {
239
- context: { constructedPath, allowedRoot: resolve3(allowedRoot) }
153
+ context: { constructedPath, allowedRoot: resolve2(allowedRoot) }
240
154
  })
241
155
  };
242
156
  }
@@ -251,7 +165,7 @@ async function loadTechniquesRegistry(rootDir) {
251
165
  }
252
166
  await ensureRegistryFile(root, TECHNIQUES_FILE);
253
167
  try {
254
- const content = await fs3.readFile(pathValidation.value, "utf-8");
168
+ const content = await fs2.readFile(pathValidation.value, "utf-8");
255
169
  return { ok: true, value: parseYaml(content) };
256
170
  } catch (error) {
257
171
  const message = getErrorMessage(error);
@@ -267,7 +181,7 @@ async function loadPapersRegistry(rootDir) {
267
181
  }
268
182
  await ensureRegistryFile(root, PAPERS_FILE);
269
183
  try {
270
- const content = await fs3.readFile(pathValidation.value, "utf-8");
184
+ const content = await fs2.readFile(pathValidation.value, "utf-8");
271
185
  return { ok: true, value: parseYaml(content) };
272
186
  } catch (error) {
273
187
  const message = getErrorMessage(error);
@@ -283,7 +197,7 @@ async function savePapersRegistry(registry, rootDir) {
283
197
  }
284
198
  try {
285
199
  const content = stringifyYaml2(registry, { lineWidth: 100 });
286
- await fs3.writeFile(pathValidation.value, content, "utf-8");
200
+ await fs2.writeFile(pathValidation.value, content, "utf-8");
287
201
  return { ok: true, value: void 0 };
288
202
  } catch (error) {
289
203
  const message = getErrorMessage(error);
@@ -292,7 +206,6 @@ async function savePapersRegistry(registry, rootDir) {
292
206
  }
293
207
 
294
208
  export {
295
- resolveInsideRoot,
296
209
  REGISTRY_PATH,
297
210
  resolveRegistryRoot,
298
211
  getProjectRoot,
@@ -300,4 +213,4 @@ export {
300
213
  loadPapersRegistry,
301
214
  savePapersRegistry
302
215
  };
303
- //# sourceMappingURL=chunk-N67B5VOS.js.map
216
+ //# sourceMappingURL=chunk-75XDI2LH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/research-helpers-io.ts","../src/cli/research-scaffold.ts"],"sourcesContent":["/**\n * Research Registry I/O Operations\n *\n * File operations for loading and saving research registry YAML files.\n * Includes path traversal protection per Issue #353.\n *\n * @see docs/research/RESEARCH_INDEX.md\n * @see Issue #237 (Epic #225)\n * @see Issue #353 (Security - Path traversal fix)\n * @see Issue #5053 (Registry root resolution — workspace/repo root, not cwd)\n */\n\nimport * as fs from 'node:fs/promises';\nimport { realpathSync, statSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { resolveInsideRoot } from '../security/safe-path.js';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { Result } from '../core/index.js';\nimport { SecurityError, createLogger, getErrorMessage } from '../core/index.js';\nimport { getActiveWorkspaceRoot } from '../config/nexus-data-dir.js';\nimport { findRepoRoot, isRepoRoot } from '../config/repo-root-detection.js';\nimport { ParseError } from '../core/types/workflow.js';\nimport type { TechniquesRegistry, PapersRegistry } from './research-types.js';\nimport { ensureRegistryFile } from './research-scaffold.js';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\n/** Path to research registry directory relative to project root */\nexport const REGISTRY_PATH = 'docs/research/registry';\n\n/** Filename for techniques registry */\nexport const TECHNIQUES_FILE = 'techniques.yaml';\n\n/** Filename for papers registry */\nexport const PAPERS_FILE = 'papers.yaml';\n\n// =============================================================================\n// PROJECT ROOT\n// =============================================================================\n\nconst logger = createLogger({ component: 'research-registry-root' });\n\n/**\n * Per-process memo of discovered registry roots (#5053), keyed on the origin\n * the walk started from (the active workspace root, else cwd). The MCP server\n * fetches its client's `roots` asynchronously after `initialized`, so a\n * `research_*` call can land before — or a `roots/list_changed` after — the\n * workspace root is known; keying on the origin means a changed origin\n * resolves afresh instead of inheriting the first caller's cwd-derived root.\n */\nconst registryRootByOrigin = new Map<string, string>();\n/** The cwd-fallback warning fires once per process overall, not per origin. */\nlet warnedCwdFallback = false;\n\n// @export-no-consumer-yet — see #5053. Test-only reset hook for the per-origin\n// memo and the one-shot cwd warning, the same shape as the data-dir module's\n// `_reset*ForTests` helpers; no production consumer is intended.\n/** Test helper — clears the registry-root memo and the one-shot warning. */\nexport function _resetRegistryRootForTests(): void {\n registryRootByOrigin.clear();\n warnedCwdFallback = false;\n}\n\n/** True iff `<dir>/docs/research/registry` exists as a directory. */\nfunction hasRegistryDir(dir: string): boolean {\n try {\n return statSync(join(dir, REGISTRY_PATH)).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Walks upward from `start` looking for the nearest ancestor (inclusive) that\n * already contains `docs/research/registry`, searching only within the\n * enclosing git repo: the walk ends at the first directory that `isRepoRoot`\n * (after checking that directory itself), so a nested checkout never adopts —\n * or writes into — an outer repo's registry. Mirrors `findRepoRoot`'s other\n * defenses: bounded depth, stops at the filesystem root, and refuses to cross\n * a mount point so a sandboxed workdir cannot pick up a host-side registry.\n */\nfunction findRegistryAncestor(start: string): string | null {\n let current: string;\n let startDev: number;\n try {\n current = realpathSync(start);\n startDev = statSync(current).dev;\n } catch {\n return null;\n }\n for (let depth = 0; depth < 64; depth++) {\n if (hasRegistryDir(current)) return current;\n if (isRepoRoot(current)) return null;\n const parent = dirname(current);\n if (parent === current) return null;\n try {\n if (statSync(parent).dev !== startDev) return null;\n } catch {\n return null;\n }\n current = parent;\n }\n return null;\n}\n\n/**\n * Resolves the directory that owns `docs/research/registry` (#5053).\n *\n * Resolution order:\n * 1. An explicit `rootDir` argument wins outright (resolved, never memoised).\n * 2. The nearest ancestor of the active workspace root (MCP `roots`, #3991)\n * — or of `process.cwd()` when none is set — that already contains\n * `docs/research/registry`, without crossing a repo boundary: the search\n * stops at the enclosing repo root, so a nested checkout never resolves\n * to an outer repo's registry.\n * 3. Otherwise the enclosing git repo root via `findRepoRoot`, so the\n * first-run scaffold (#2470) lands at the repo root rather than below it.\n * 4. Otherwise cwd, with a warning emitted once per process: nothing above\n * cwd identifies a project, so cwd is a guess rather than a discovery.\n *\n * Steps 2–4 are memoised per origin (workspace root or cwd), so a workspace\n * root that arrives after the first call — MCP roots are fetched after\n * `initialized` — is honoured rather than shadowed by an earlier cwd-derived\n * result. Before this resolver every helper defaulted to cwd, so a server\n * started inside `packages/nexus-agents` read (and scaffolded) a shadow\n * registry there instead of the repo's.\n */\nexport function resolveRegistryRoot(rootDir?: string): string {\n if (rootDir !== undefined) return resolve(rootDir);\n\n const origin = getActiveWorkspaceRoot() ?? process.cwd();\n const memoised = registryRootByOrigin.get(origin);\n if (memoised !== undefined) return memoised;\n\n const discovered = findRegistryAncestor(origin) ?? findRepoRoot(origin);\n if (discovered !== null) {\n registryRootByOrigin.set(origin, discovered);\n return discovered;\n }\n\n let fallback: string;\n try {\n fallback = realpathSync(origin);\n } catch {\n fallback = resolve(origin);\n }\n if (!warnedCwdFallback) {\n warnedCwdFallback = true;\n logger.warn(\n 'No research registry or git repo found above the working directory; using cwd as the research registry root',\n { cwd: fallback, registryPath: REGISTRY_PATH }\n );\n }\n registryRootByOrigin.set(origin, fallback);\n return fallback;\n}\n\n/**\n * Get the project root directory that owns the research registry.\n * Delegates to {@link resolveRegistryRoot} — no longer bare `process.cwd()`.\n */\nexport function getProjectRoot(): string {\n return resolveRegistryRoot();\n}\n\n// =============================================================================\n// PATH VALIDATION\n// =============================================================================\n\n/**\n * Validates that a constructed path stays within the allowed root directory.\n * Prevents path traversal attacks (e.g., ../../../etc/passwd).\n *\n * @param constructedPath - The full path to validate\n * @param allowedRoot - The root directory that paths must be within\n * @returns Result with validated absolute path or SecurityError\n */\nfunction validatePath(constructedPath: string, allowedRoot: string): Result<string, SecurityError> {\n const resolved = resolveInsideRoot(constructedPath, allowedRoot);\n if (resolved === null) {\n return {\n ok: false,\n error: new SecurityError('Path traversal detected: path escapes allowed root directory', {\n context: { constructedPath, allowedRoot: resolve(allowedRoot) },\n }),\n };\n }\n return { ok: true, value: resolved };\n}\n\n// =============================================================================\n// LOAD OPERATIONS\n// =============================================================================\n\n/**\n * Load techniques registry from YAML file.\n * Validates path to prevent directory traversal attacks.\n *\n * @param rootDir - Project root directory (defaults to the resolved registry root, see {@link resolveRegistryRoot})\n * @returns Result with TechniquesRegistry or SecurityError/ParseError\n */\nexport async function loadTechniquesRegistry(\n rootDir?: string\n): Promise<Result<TechniquesRegistry, SecurityError | ParseError>> {\n const root = resolveRegistryRoot(rootDir);\n const filePath = join(root, REGISTRY_PATH, TECHNIQUES_FILE);\n\n const pathValidation = validatePath(filePath, root);\n if (!pathValidation.ok) {\n return pathValidation;\n }\n\n // #2470: try to scaffold an empty techniques.yaml on first run so research_*\n // workflows don't error on a fresh install. No-op when file exists.\n // When scaffold refuses (e.g. <rootDir>/docs/ doesn't exist, or\n // NEXUS_NO_SCAFFOLD=1), fall through and let readFile produce the original\n // ENOENT error — that's the existing contract.\n await ensureRegistryFile(root, TECHNIQUES_FILE);\n\n try {\n const content = await fs.readFile(pathValidation.value, 'utf-8');\n return { ok: true, value: parseYaml(content) as TechniquesRegistry };\n } catch (error) {\n const message = getErrorMessage(error);\n return { ok: false, error: new ParseError(`Failed to load techniques registry: ${message}`) };\n }\n}\n\n/**\n * Load papers registry from YAML file.\n * Validates path to prevent directory traversal attacks.\n *\n * @param rootDir - Project root directory (defaults to the resolved registry root, see {@link resolveRegistryRoot})\n * @returns Result with PapersRegistry or SecurityError/ParseError\n */\nexport async function loadPapersRegistry(\n rootDir?: string\n): Promise<Result<PapersRegistry, SecurityError | ParseError>> {\n const root = resolveRegistryRoot(rootDir);\n const filePath = join(root, REGISTRY_PATH, PAPERS_FILE);\n\n const pathValidation = validatePath(filePath, root);\n if (!pathValidation.ok) {\n return pathValidation;\n }\n\n // #2470: try to scaffold an empty papers.yaml on first run so research_*\n // workflows don't error on a fresh install. No-op when file exists; falls\n // through silently when scaffold refuses (no docs/ root, or NEXUS_NO_SCAFFOLD).\n await ensureRegistryFile(root, PAPERS_FILE);\n\n try {\n const content = await fs.readFile(pathValidation.value, 'utf-8');\n return { ok: true, value: parseYaml(content) as PapersRegistry };\n } catch (error) {\n const message = getErrorMessage(error);\n return { ok: false, error: new ParseError(`Failed to load papers registry: ${message}`) };\n }\n}\n\n// =============================================================================\n// SAVE OPERATIONS\n// =============================================================================\n\n/**\n * Save techniques registry to YAML file.\n * Validates path to prevent directory traversal attacks.\n *\n * @param registry - The techniques registry to save\n * @param rootDir - Project root directory (defaults to the resolved registry root, see {@link resolveRegistryRoot})\n * @returns Result with void on success or SecurityError/ParseError on failure\n */\nexport async function saveTechniquesRegistry(\n registry: TechniquesRegistry,\n rootDir?: string\n): Promise<Result<void, SecurityError | ParseError>> {\n const root = resolveRegistryRoot(rootDir);\n const filePath = join(root, REGISTRY_PATH, TECHNIQUES_FILE);\n\n const pathValidation = validatePath(filePath, root);\n if (!pathValidation.ok) {\n return pathValidation;\n }\n\n try {\n const content = stringifyYaml(registry, { lineWidth: 100 });\n await fs.writeFile(pathValidation.value, content, 'utf-8');\n return { ok: true, value: undefined };\n } catch (error) {\n const message = getErrorMessage(error);\n return { ok: false, error: new ParseError(`Failed to save techniques registry: ${message}`) };\n }\n}\n\n/**\n * Save papers registry to YAML file.\n * Validates path to prevent directory traversal attacks.\n *\n * @param registry - The papers registry to save\n * @param rootDir - Project root directory (defaults to the resolved registry root, see {@link resolveRegistryRoot})\n * @returns Result with void on success or SecurityError/ParseError on failure\n */\nexport async function savePapersRegistry(\n registry: PapersRegistry,\n rootDir?: string\n): Promise<Result<void, SecurityError | ParseError>> {\n const root = resolveRegistryRoot(rootDir);\n const filePath = join(root, REGISTRY_PATH, PAPERS_FILE);\n\n const pathValidation = validatePath(filePath, root);\n if (!pathValidation.ok) {\n return pathValidation;\n }\n\n try {\n const content = stringifyYaml(registry, { lineWidth: 100 });\n await fs.writeFile(pathValidation.value, content, 'utf-8');\n return { ok: true, value: undefined };\n } catch (error) {\n const message = getErrorMessage(error);\n return { ok: false, error: new ParseError(`Failed to save papers registry: ${message}`) };\n }\n}\n","/**\n * research-scaffold — auto-create empty research registry files when missing.\n *\n * On a fresh install or in a sandboxed environment where the project doesn't\n * have `docs/research/registry/`, the research workflows previously errored\n * with `Failed to load papers registry: ENOENT`. The user has no idea what\n * to do.\n *\n * This module provides `ensureResearchRegistry()`: idempotent scaffolding\n * that creates empty YAML registries on ENOENT, announces what it did to\n * stderr (so the operator knows), and returns the path that's now safe to\n * read.\n *\n * Source: Issue #2470 (epic #2467 child).\n */\n\nimport * as fs from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { stringify as stringifyYaml } from 'yaml';\n\nimport type { Result } from '../core/index.js';\nimport { ok, err, getErrorMessage } from '../core/index.js';\nimport { ParseError } from '../core/types/workflow.js';\n\nimport {\n REGISTRY_PATH,\n PAPERS_FILE,\n TECHNIQUES_FILE,\n resolveRegistryRoot,\n} from './research-helpers-io.js';\nimport { parseBoolEnv } from '../config/defaults-env.js';\n\n/**\n * Names of the registry files this scaffolder knows how to create.\n * `sources.yaml` and `alignments.yaml` live alongside but are populated by\n * different code paths; this scaffolder only owns the two YAML registries\n * that block `research_query` / `research_synthesize` on a fresh install.\n */\ntype ScaffoldedFile = typeof PAPERS_FILE | typeof TECHNIQUES_FILE;\n\nconst SCHEMA_VERSION = '1.0';\n\nconst ANNOUNCED = new Set<string>();\n\nfunction announceOnce(path: string, kind: ScaffoldedFile): void {\n // De-dupe so that running multiple research commands in one session\n // produces one announcement per scaffolded file, not per call.\n if (ANNOUNCED.has(path)) return;\n ANNOUNCED.add(path);\n process.stderr.write(\n `[scaffold] Created empty ${kind} at ${path}; add entries via 'nexus-agents research add' or research_add MCP tool.\\n`\n );\n}\n\n/**\n * Operator opt-out. When set, scaffolding errors loudly with an actionable\n * message instead of silently writing files. Useful for CI and for\n * environments that want strict \"fail when state is wrong\" semantics.\n */\nfunction scaffoldDisabled(): boolean {\n return parseBoolEnv('NEXUS_NO_SCAFFOLD', false);\n}\n\nfunction emptyPapersYaml(): string {\n return stringifyYaml({ schema_version: SCHEMA_VERSION, papers: {} });\n}\n\nfunction emptyTechniquesYaml(): string {\n return stringifyYaml({ schema_version: SCHEMA_VERSION, techniques: {} });\n}\n\n/**\n * Create the registry directory + an empty YAML file if either is missing.\n * Idempotent: existing files are left untouched.\n *\n * Scaffolds only when `<rootDir>/docs/` already exists. That guard keeps\n * tests, vitest's package cwd, and random scratch directories from getting\n * a `docs/research/` subtree implicitly. Operators on a real project root\n * (which always has `docs/`, either from cloning the repo or running\n * `nexus-agents init`) still get the auto-create behavior the issue asks for.\n *\n * Returns the resolved file path on success, or a ParseError when scaffolding\n * is disabled / refused (so the caller can pass the error through unchanged\n * for backwards compat with existing call sites).\n */\nexport async function ensureRegistryFile(\n rootDir: string,\n filename: ScaffoldedFile\n): Promise<Result<string, ParseError>> {\n const filePath = resolve(rootDir, REGISTRY_PATH, filename);\n\n if (existsSync(filePath)) return ok(filePath);\n\n if (scaffoldDisabled()) {\n return err(\n new ParseError(\n `Registry file missing: ${filePath}. Scaffolding disabled (NEXUS_NO_SCAFFOLD=1). Create the file manually or unset the env var.`\n )\n );\n }\n\n // Refuse to create a docs/ subtree in directories that aren't already\n // documented project roots. The presence of `<rootDir>/docs/` is the\n // strong signal we use.\n if (!existsSync(resolve(rootDir, 'docs'))) {\n return err(\n new ParseError(\n `Registry file missing: ${filePath}. ${rootDir}/docs/ does not exist; ` +\n `refusing to create a Nexus subtree in a non-documented root. ` +\n `Run 'nexus-agents init' first, or create ${rootDir}/docs/ manually.`\n )\n );\n }\n\n try {\n await fs.mkdir(dirname(filePath), { recursive: true });\n const content = filename === PAPERS_FILE ? emptyPapersYaml() : emptyTechniquesYaml();\n await fs.writeFile(filePath, content, 'utf-8');\n announceOnce(filePath, filename);\n return ok(filePath);\n } catch (e: unknown) {\n return err(\n new ParseError(\n `Failed to scaffold ${filename} at ${filePath}: ${getErrorMessage(e)}. Set NEXUS_NO_SCAFFOLD=1 and create manually if scaffolding is unwanted.`\n )\n );\n }\n}\n\n/**\n * Convenience: scaffold both papers.yaml and techniques.yaml. Used by the\n * research workflow startup paths that need both registries available.\n * Without an explicit `rootDir` the files land at the resolved registry root\n * (#5053), never at a bare cwd below it.\n */\nexport async function ensureResearchRegistry(rootDir?: string): Promise<Result<void, ParseError>> {\n const root = resolveRegistryRoot(rootDir);\n const papers = await ensureRegistryFile(root, PAPERS_FILE);\n if (!papers.ok) return papers;\n const techniques = await ensureRegistryFile(root, TECHNIQUES_FILE);\n if (!techniques.ok) return techniques;\n return ok(undefined);\n}\n\n/**\n * Test-only reset of the announcement de-dupe set. Production code never\n * needs this; tests do because Vitest reuses process state across files.\n */\nexport function _resetAnnouncedForTests(): void {\n ANNOUNCED.clear();\n}\n"],"mappings":"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","names":["fs","dirname","resolve","stringifyYaml","dirname","resolve","stringifyYaml"]}
@@ -1,28 +1,28 @@
1
1
  import {
2
2
  SERVES_PROBE_PROMPT
3
- } from "./chunk-KJ3JOYAI.js";
3
+ } from "./chunk-5MHIYVCE.js";
4
4
  import {
5
5
  checkGatewayHealth,
6
6
  cliFailsVerdict,
7
7
  formatGatewaySlotWarnings,
8
8
  gatewayVerdict,
9
9
  printVoterTransportCheck
10
- } from "./chunk-VNUYAFKV.js";
10
+ } from "./chunk-KRY5VIQW.js";
11
11
  import {
12
12
  NODE_ENGINE_RANGE,
13
13
  VERSION,
14
14
  isNodeVersionSupported
15
- } from "./chunk-XDIPNMNO.js";
15
+ } from "./chunk-SDUKS5RF.js";
16
16
  import {
17
17
  allOf
18
18
  } from "./chunk-EZNIYU5T.js";
19
19
  import {
20
20
  readOpenAICompatEndpoint,
21
21
  readOpenAICompatEnv
22
- } from "./chunk-D3KUSAVV.js";
22
+ } from "./chunk-5NKP4DVA.js";
23
23
  import {
24
24
  claimGlobalRegistry
25
- } from "./chunk-NR37NPJ5.js";
25
+ } from "./chunk-E4OCHK7E.js";
26
26
  import {
27
27
  capitalize
28
28
  } from "./chunk-SX5DB6UR.js";
@@ -32,12 +32,12 @@ import {
32
32
  IN_FAMILY_FALLBACK_OPTION,
33
33
  codexMcpServerAvailable,
34
34
  createAllAdapters,
35
- isCliDisabled,
35
+ isCliAdmitted,
36
36
  probeCli
37
- } from "./chunk-GOREKLDR.js";
37
+ } from "./chunk-TDELDKWG.js";
38
38
  import {
39
39
  resolveGatewayEnv
40
- } from "./chunk-PAUO5OFW.js";
40
+ } from "./chunk-GGP3F7FG.js";
41
41
  import {
42
42
  getNexusTmpDir
43
43
  } from "./chunk-OITVVSWO.js";
@@ -55,13 +55,14 @@ import {
55
55
  getErrorMessage,
56
56
  getInTreeCapabilitiesMatrix,
57
57
  getTimeProvider,
58
+ isCliDisabled,
58
59
  isDurableCapacityText,
59
60
  ok,
60
61
  resolveClassGuardMs,
61
62
  resolveGatewayCostDeclaration,
62
63
  symbols,
63
64
  writeLine
64
- } from "./chunk-SIQVWR3R.js";
65
+ } from "./chunk-RPOGHHN7.js";
65
66
  import {
66
67
  getLearningDir,
67
68
  getOutcomesFile,
@@ -980,6 +981,7 @@ function createNotFoundResult(name, errorMsg) {
980
981
  // a determination nobody made; the auth line is not printed for an
981
982
  // uninstalled CLI anyway (#4661).
982
983
  authState: "unverified",
984
+ routerAdmits: false,
983
985
  installed: false,
984
986
  version: "N/A",
985
987
  versionStatus: "unsupported",
@@ -1012,6 +1014,7 @@ function createHealthyResult(name, health, authProbe, capacity) {
1012
1014
  versionStatus: health.versionStatus,
1013
1015
  authenticated,
1014
1016
  authState,
1017
+ routerAdmits: isCliAdmitted(health, authProbe),
1015
1018
  ...authenticated && { authMethod: detectAuthMethod(name) },
1016
1019
  ...capacity !== void 0 && { capacity }
1017
1020
  };
@@ -1368,8 +1371,10 @@ async function doctorCommand(options = {}) {
1368
1371
  const result = await runDoctor({ gatewayProbe: options.probe === true });
1369
1372
  printDoctorResults(result);
1370
1373
  if (options.gateway === true || options.probe === true) {
1371
- const { formatGatewayReport } = await import("./doctor-gateway-report-TEM5VCBO.js");
1372
- for (const line of formatGatewayReport(result.gateway)) process.stdout.write(line + "\n");
1374
+ const { formatGatewayReport } = await import("./doctor-gateway-report-WHI5WQ4N.js");
1375
+ for (const line of formatGatewayReport(result.gateway, result.clis)) {
1376
+ process.stdout.write(line + "\n");
1377
+ }
1373
1378
  }
1374
1379
  if (options.fix === true) {
1375
1380
  await runDoctorFix(result);
@@ -1385,13 +1390,13 @@ async function runDoctorFix(result) {
1385
1390
  writeLine2("\u2500".repeat(40));
1386
1391
  let fixCount = 0;
1387
1392
  if (!result.dataDirectory.rootExists || result.dataDirectory.subdirectories.some((d) => !d.exists || !d.writable)) {
1388
- const { initDataDirectories: initDataDirectories2, describeDataDirFix: describeDataDirFix2 } = await import("./setup-data-dir-7FDJPPS3.js");
1393
+ const { initDataDirectories: initDataDirectories2, describeDataDirFix: describeDataDirFix2 } = await import("./setup-data-dir-3WHFLPS4.js");
1389
1394
  const outcome = describeDataDirFix2(initDataDirectories2());
1390
1395
  writeLine2(outcome.line);
1391
1396
  if (outcome.counted) fixCount++;
1392
1397
  }
1393
1398
  if (!result.configFile.found) {
1394
- const { runConfigInitSync } = await import("./setup-config-G6KNCDHR.js");
1399
+ const { runConfigInitSync } = await import("./setup-config-BOKVRB67.js");
1395
1400
  const configResult = runConfigInitSync(process.cwd(), false, false);
1396
1401
  if (configResult.success && configResult.created) {
1397
1402
  writeLine2(`\u2713 Generated config: ${configResult.path}`);
@@ -1487,4 +1492,4 @@ export {
1487
1492
  startStdioServer,
1488
1493
  closeServer
1489
1494
  };
1490
- //# sourceMappingURL=chunk-CCTSFRMO.js.map
1495
+ //# sourceMappingURL=chunk-ACE5NI5A.js.map