nexus-agents 4.22.4 → 4.23.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 (141) hide show
  1. package/dist/{child-mcp-config-I3IQVN7P.js → child-mcp-config-FEK2HK5L.js} +5 -5
  2. package/dist/{chunk-RF5N4HME.js → chunk-2FYFHAI4.js} +2 -2
  3. package/dist/{chunk-VDEDTAHZ.js → chunk-2MPJ7UDR.js} +83 -43
  4. package/dist/{chunk-VDEDTAHZ.js.map → chunk-2MPJ7UDR.js.map} +1 -1
  5. package/dist/{chunk-YSOWQJ5S.js → chunk-32IMMUXP.js} +2 -2
  6. package/dist/{chunk-SJPZHT6D.js → chunk-3SMGCYGN.js} +2 -2
  7. package/dist/{chunk-RQV5ZUXT.js → chunk-4OW67KT2.js} +2 -2
  8. package/dist/{chunk-N45MVUY7.js → chunk-5KKJKQVE.js} +2 -2
  9. package/dist/chunk-5KKJKQVE.js.map +1 -0
  10. package/dist/{chunk-IBW6CJ4G.js → chunk-ADC7YEU2.js} +6 -6
  11. package/dist/{chunk-PX7AYAHF.js → chunk-AWMXPIMK.js} +3 -3
  12. package/dist/{chunk-HWPFUVMS.js → chunk-CMZGRDKF.js} +2 -2
  13. package/dist/{chunk-SBWVYOTV.js → chunk-EPZCODRK.js} +9 -9
  14. package/dist/{chunk-6TV765AS.js → chunk-EYQUQ2PZ.js} +2 -2
  15. package/dist/{chunk-HHLCAWAV.js → chunk-GBHJ37V3.js} +2 -2
  16. package/dist/{chunk-AXG2IGOK.js → chunk-GGQOP4VK.js} +5 -5
  17. package/dist/{chunk-DXWF7V74.js → chunk-GVLMTQ46.js} +4 -4
  18. package/dist/{chunk-3PVG7CQS.js → chunk-HPGPOZG4.js} +7 -7
  19. package/dist/{chunk-QS5K3PAY.js → chunk-IC6WLIGJ.js} +4 -4
  20. package/dist/{chunk-KGFFEQTN.js → chunk-JEAKVUWM.js} +2 -2
  21. package/dist/{chunk-RXGHRVL2.js → chunk-K2A24T36.js} +2 -2
  22. package/dist/{chunk-BTAWCQP6.js → chunk-K2DSQJEH.js} +6 -6
  23. package/dist/{chunk-I6YC5PIL.js → chunk-K7RASYIZ.js} +3 -3
  24. package/dist/{chunk-BZGTZKXY.js → chunk-LLGK3NRY.js} +2 -2
  25. package/dist/{chunk-ANVPNLZE.js → chunk-LMENHZQ6.js} +4 -4
  26. package/dist/{chunk-QQS6FVTQ.js → chunk-LMTBX4GZ.js} +2 -2
  27. package/dist/{chunk-PLO6VSSN.js → chunk-LSJ2EPOF.js} +4 -4
  28. package/dist/{chunk-7MBFKJJT.js → chunk-MSHZHT5X.js} +2 -2
  29. package/dist/{chunk-5RC54HVU.js → chunk-NMFXYX7R.js} +2 -2
  30. package/dist/{chunk-NBMWONHE.js → chunk-OGHEKPLD.js} +11 -2
  31. package/dist/{chunk-NBMWONHE.js.map → chunk-OGHEKPLD.js.map} +1 -1
  32. package/dist/{chunk-TBWKMKCX.js → chunk-OYFKB64X.js} +6 -6
  33. package/dist/{chunk-NQ65EKMG.js → chunk-PUGRS4KW.js} +9 -4
  34. package/dist/chunk-PUGRS4KW.js.map +1 -0
  35. package/dist/{chunk-3K3IPRED.js → chunk-R3H46BNY.js} +2 -2
  36. package/dist/{chunk-HLVST3JE.js → chunk-TXCJMTLN.js} +10 -10
  37. package/dist/{chunk-MCCN5UUW.js → chunk-WJGTRYWS.js} +2 -2
  38. package/dist/{chunk-ZRSKNV3E.js → chunk-XQEWXGJ5.js} +10 -10
  39. package/dist/{chunk-VSHQMS37.js → chunk-YFDE4AM3.js} +7 -7
  40. package/dist/{chunk-EPOKODTI.js → chunk-YVW743K3.js} +2 -2
  41. package/dist/{chunk-5YBFBXEB.js → chunk-Z37OVF33.js} +2 -2
  42. package/dist/{cli-circuit-breaker-OKOGXUQY.js → cli-circuit-breaker-VUWP2GRN.js} +5 -5
  43. package/dist/cli.js +46 -45
  44. package/dist/cli.js.map +1 -1
  45. package/dist/{composite-router-UFQSYHJF.js → composite-router-4F3ZVLJV.js} +4 -4
  46. package/dist/{consensus-vote-65UZRQMY.js → consensus-vote-ETJ63JRC.js} +20 -20
  47. package/dist/{context-retriever-BROUADMF.js → context-retriever-L2LTCKV6.js} +11 -11
  48. package/dist/doctor-deep-HPBHFGBZ.js +13 -0
  49. package/dist/{doctor-live-LBH7XDZX.js → doctor-live-NQ35PQ5B.js} +12 -12
  50. package/dist/{expert-bridge-H6ITLZUY.js → expert-bridge-XN5MFZOG.js} +6 -6
  51. package/dist/factory-2TJ44P7L.js +15 -0
  52. package/dist/factory-GVBBWUOE.js +25 -0
  53. package/dist/{improvement-review-LJQ2CXAP.js → improvement-review-7VJYOI2F.js} +7 -7
  54. package/dist/index.d.ts +11 -0
  55. package/dist/index.js +34 -34
  56. package/dist/{init-opencode-NTS5ZWXU.js → init-opencode-TLVVMG54.js} +8 -8
  57. package/dist/issue-triage-YREC36AC.js +17 -0
  58. package/dist/{learning-persistence-SR5F4DSY.js → learning-persistence-7O5QYDM7.js} +3 -3
  59. package/dist/{pr-reviewer-helpers-IAKFHBM6.js → pr-reviewer-helpers-4ERCFSZ7.js} +6 -6
  60. package/dist/{registry-command-6HVXEW23.js → registry-command-GN2REQQP.js} +4 -4
  61. package/dist/{repo-security-plan-MF4E5MG6.js → repo-security-plan-VPAXEENC.js} +5 -5
  62. package/dist/research-helpers-synthesize-OZOOIEFW.js +14 -0
  63. package/dist/{routing-memory-YIKR7EMC.js → routing-memory-OF7J3OE4.js} +4 -4
  64. package/dist/{session-memory-B6TFD55A.js → session-memory-O5M3CLRX.js} +5 -5
  65. package/dist/setup-config-WW5BNLVS.js +11 -0
  66. package/dist/{setup-custom-api-ATC26B2O.js → setup-custom-api-UX4XNOT6.js} +6 -6
  67. package/dist/setup-data-dir-KHLK534F.js +28 -0
  68. package/dist/{tool-memory-XDUJUM2O.js → tool-memory-PIBTMGPH.js} +8 -8
  69. package/dist/{tool-memory-registry-adapters-UMPIJLHS.js → tool-memory-registry-adapters-IMRSOAAV.js} +3 -3
  70. package/dist/unified-registry-MUXCSSG5.js +26 -0
  71. package/dist/{weather-report-YDYML3JW.js → weather-report-JOATSJWU.js} +4 -4
  72. package/package.json +1 -1
  73. package/dist/chunk-N45MVUY7.js.map +0 -1
  74. package/dist/chunk-NQ65EKMG.js.map +0 -1
  75. package/dist/doctor-deep-SBSUG54R.js +0 -13
  76. package/dist/factory-WJ6AV4WU.js +0 -15
  77. package/dist/factory-ZIYR7YJ3.js +0 -25
  78. package/dist/issue-triage-PM67ACC5.js +0 -17
  79. package/dist/research-helpers-synthesize-2XQBWVG6.js +0 -14
  80. package/dist/setup-config-A5DWVRQB.js +0 -11
  81. package/dist/setup-data-dir-DWMKVY6G.js +0 -28
  82. package/dist/unified-registry-H2NNOXB3.js +0 -26
  83. /package/dist/{child-mcp-config-I3IQVN7P.js.map → child-mcp-config-FEK2HK5L.js.map} +0 -0
  84. /package/dist/{chunk-RF5N4HME.js.map → chunk-2FYFHAI4.js.map} +0 -0
  85. /package/dist/{chunk-YSOWQJ5S.js.map → chunk-32IMMUXP.js.map} +0 -0
  86. /package/dist/{chunk-SJPZHT6D.js.map → chunk-3SMGCYGN.js.map} +0 -0
  87. /package/dist/{chunk-RQV5ZUXT.js.map → chunk-4OW67KT2.js.map} +0 -0
  88. /package/dist/{chunk-IBW6CJ4G.js.map → chunk-ADC7YEU2.js.map} +0 -0
  89. /package/dist/{chunk-PX7AYAHF.js.map → chunk-AWMXPIMK.js.map} +0 -0
  90. /package/dist/{chunk-HWPFUVMS.js.map → chunk-CMZGRDKF.js.map} +0 -0
  91. /package/dist/{chunk-SBWVYOTV.js.map → chunk-EPZCODRK.js.map} +0 -0
  92. /package/dist/{chunk-6TV765AS.js.map → chunk-EYQUQ2PZ.js.map} +0 -0
  93. /package/dist/{chunk-HHLCAWAV.js.map → chunk-GBHJ37V3.js.map} +0 -0
  94. /package/dist/{chunk-AXG2IGOK.js.map → chunk-GGQOP4VK.js.map} +0 -0
  95. /package/dist/{chunk-DXWF7V74.js.map → chunk-GVLMTQ46.js.map} +0 -0
  96. /package/dist/{chunk-3PVG7CQS.js.map → chunk-HPGPOZG4.js.map} +0 -0
  97. /package/dist/{chunk-QS5K3PAY.js.map → chunk-IC6WLIGJ.js.map} +0 -0
  98. /package/dist/{chunk-KGFFEQTN.js.map → chunk-JEAKVUWM.js.map} +0 -0
  99. /package/dist/{chunk-RXGHRVL2.js.map → chunk-K2A24T36.js.map} +0 -0
  100. /package/dist/{chunk-BTAWCQP6.js.map → chunk-K2DSQJEH.js.map} +0 -0
  101. /package/dist/{chunk-I6YC5PIL.js.map → chunk-K7RASYIZ.js.map} +0 -0
  102. /package/dist/{chunk-BZGTZKXY.js.map → chunk-LLGK3NRY.js.map} +0 -0
  103. /package/dist/{chunk-ANVPNLZE.js.map → chunk-LMENHZQ6.js.map} +0 -0
  104. /package/dist/{chunk-QQS6FVTQ.js.map → chunk-LMTBX4GZ.js.map} +0 -0
  105. /package/dist/{chunk-PLO6VSSN.js.map → chunk-LSJ2EPOF.js.map} +0 -0
  106. /package/dist/{chunk-7MBFKJJT.js.map → chunk-MSHZHT5X.js.map} +0 -0
  107. /package/dist/{chunk-5RC54HVU.js.map → chunk-NMFXYX7R.js.map} +0 -0
  108. /package/dist/{chunk-TBWKMKCX.js.map → chunk-OYFKB64X.js.map} +0 -0
  109. /package/dist/{chunk-3K3IPRED.js.map → chunk-R3H46BNY.js.map} +0 -0
  110. /package/dist/{chunk-HLVST3JE.js.map → chunk-TXCJMTLN.js.map} +0 -0
  111. /package/dist/{chunk-MCCN5UUW.js.map → chunk-WJGTRYWS.js.map} +0 -0
  112. /package/dist/{chunk-ZRSKNV3E.js.map → chunk-XQEWXGJ5.js.map} +0 -0
  113. /package/dist/{chunk-VSHQMS37.js.map → chunk-YFDE4AM3.js.map} +0 -0
  114. /package/dist/{chunk-EPOKODTI.js.map → chunk-YVW743K3.js.map} +0 -0
  115. /package/dist/{chunk-5YBFBXEB.js.map → chunk-Z37OVF33.js.map} +0 -0
  116. /package/dist/{cli-circuit-breaker-OKOGXUQY.js.map → cli-circuit-breaker-VUWP2GRN.js.map} +0 -0
  117. /package/dist/{composite-router-UFQSYHJF.js.map → composite-router-4F3ZVLJV.js.map} +0 -0
  118. /package/dist/{consensus-vote-65UZRQMY.js.map → consensus-vote-ETJ63JRC.js.map} +0 -0
  119. /package/dist/{context-retriever-BROUADMF.js.map → context-retriever-L2LTCKV6.js.map} +0 -0
  120. /package/dist/{doctor-deep-SBSUG54R.js.map → doctor-deep-HPBHFGBZ.js.map} +0 -0
  121. /package/dist/{doctor-live-LBH7XDZX.js.map → doctor-live-NQ35PQ5B.js.map} +0 -0
  122. /package/dist/{expert-bridge-H6ITLZUY.js.map → expert-bridge-XN5MFZOG.js.map} +0 -0
  123. /package/dist/{factory-WJ6AV4WU.js.map → factory-2TJ44P7L.js.map} +0 -0
  124. /package/dist/{factory-ZIYR7YJ3.js.map → factory-GVBBWUOE.js.map} +0 -0
  125. /package/dist/{improvement-review-LJQ2CXAP.js.map → improvement-review-7VJYOI2F.js.map} +0 -0
  126. /package/dist/{init-opencode-NTS5ZWXU.js.map → init-opencode-TLVVMG54.js.map} +0 -0
  127. /package/dist/{issue-triage-PM67ACC5.js.map → issue-triage-YREC36AC.js.map} +0 -0
  128. /package/dist/{learning-persistence-SR5F4DSY.js.map → learning-persistence-7O5QYDM7.js.map} +0 -0
  129. /package/dist/{pr-reviewer-helpers-IAKFHBM6.js.map → pr-reviewer-helpers-4ERCFSZ7.js.map} +0 -0
  130. /package/dist/{registry-command-6HVXEW23.js.map → registry-command-GN2REQQP.js.map} +0 -0
  131. /package/dist/{repo-security-plan-MF4E5MG6.js.map → repo-security-plan-VPAXEENC.js.map} +0 -0
  132. /package/dist/{research-helpers-synthesize-2XQBWVG6.js.map → research-helpers-synthesize-OZOOIEFW.js.map} +0 -0
  133. /package/dist/{routing-memory-YIKR7EMC.js.map → routing-memory-OF7J3OE4.js.map} +0 -0
  134. /package/dist/{session-memory-B6TFD55A.js.map → session-memory-O5M3CLRX.js.map} +0 -0
  135. /package/dist/{setup-config-A5DWVRQB.js.map → setup-config-WW5BNLVS.js.map} +0 -0
  136. /package/dist/{setup-custom-api-ATC26B2O.js.map → setup-custom-api-UX4XNOT6.js.map} +0 -0
  137. /package/dist/{setup-data-dir-DWMKVY6G.js.map → setup-data-dir-KHLK534F.js.map} +0 -0
  138. /package/dist/{tool-memory-XDUJUM2O.js.map → tool-memory-PIBTMGPH.js.map} +0 -0
  139. /package/dist/{tool-memory-registry-adapters-UMPIJLHS.js.map → tool-memory-registry-adapters-IMRSOAAV.js.map} +0 -0
  140. /package/dist/{unified-registry-H2NNOXB3.js.map → unified-registry-MUXCSSG5.js.map} +0 -0
  141. /package/dist/{weather-report-YDYML3JW.js.map → weather-report-JOATSJWU.js.map} +0 -0
@@ -2,7 +2,7 @@ import {
2
2
  CACHE_TIMEOUTS,
3
3
  createLogger,
4
4
  getTimeProvider
5
- } from "./chunk-TBWKMKCX.js";
5
+ } from "./chunk-OYFKB64X.js";
6
6
 
7
7
  // src/security/trust-types.ts
8
8
  import { z } from "zod";
@@ -560,4 +560,4 @@ export {
560
560
  resolveReputationGatingMode,
561
561
  gateWithReputation
562
562
  };
563
- //# sourceMappingURL=chunk-7MBFKJJT.js.map
563
+ //# sourceMappingURL=chunk-MSHZHT5X.js.map
@@ -4,7 +4,7 @@ import {
4
4
  err,
5
5
  getErrorMessage,
6
6
  ok
7
- } from "./chunk-TBWKMKCX.js";
7
+ } from "./chunk-OYFKB64X.js";
8
8
 
9
9
  // src/scm/types.ts
10
10
  var ScmError = class extends Error {
@@ -263,4 +263,4 @@ export {
263
263
  ScmError,
264
264
  GitHubProvider
265
265
  };
266
- //# sourceMappingURL=chunk-5RC54HVU.js.map
266
+ //# sourceMappingURL=chunk-NMFXYX7R.js.map
@@ -127,6 +127,10 @@ function inSandboxEnv() {
127
127
  }
128
128
  return false;
129
129
  }
130
+ function sandboxRootFromEnv() {
131
+ const raw = process.env["NEXUS_SANDBOX_ROOT"]?.trim();
132
+ return raw !== void 0 && raw !== "" ? resolve2(raw) : void 0;
133
+ }
130
134
  function checkExplicitEnv(cwd) {
131
135
  const fromEnv = process.env["NEXUS_DATA_DIR"]?.trim();
132
136
  if (fromEnv !== void 0 && fromEnv !== "") {
@@ -146,7 +150,12 @@ function checkHeuristics(cwd) {
146
150
  return { portable: true, dataDir: join(cwd, ".nexus-agents"), reason: "home-unwritable" };
147
151
  }
148
152
  if (inSandboxEnv()) {
149
- return { portable: true, dataDir: join(cwd, ".nexus-agents"), reason: "container-env" };
153
+ const declaredRoot = sandboxRootFromEnv();
154
+ return {
155
+ portable: true,
156
+ dataDir: join(declaredRoot ?? cwd, ".nexus-agents"),
157
+ reason: "container-env"
158
+ };
150
159
  }
151
160
  return null;
152
161
  }
@@ -383,4 +392,4 @@ export {
383
392
  getPerRepoSubdirs,
384
393
  sessionsDbPath
385
394
  };
386
- //# sourceMappingURL=chunk-NBMWONHE.js.map
395
+ //# sourceMappingURL=chunk-OGHEKPLD.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config/nexus-data-dir.ts","../src/config/sandbox-detection.ts","../src/config/repo-root-detection.ts","../src/config/portable-mode.ts"],"sourcesContent":["/**\n * Nexus runtime data directory resolver (#2302, child of #2301).\n *\n * Returns the absolute path under which nexus-agents stores runtime state.\n * Two categories of state exist (epic #2872, vote #2876):\n *\n * - **Per-repo** state (sessions, checkpoints, traces, runs, audit,\n * pipeline, tasks) — tied to the work happening in a specific codebase.\n * When `NEXUS_REPO_PREFERRED=1` is set and the caller is inside a git\n * repo, these resolve to `<repo-root>/.nexus-agents/<subdir>/`.\n * - **Cross-repo** state (learning, voting, memory, weather, research,\n * auth, usage, models manifest) — accumulates across the operator's\n * whole workflow. Always resolves to `~/.nexus-agents/<subdir>/` so the\n * learning/routing feedback loop from #1389 / #1407 keeps working.\n *\n * Resolution order (first match wins):\n * 1. `NEXUS_DATA_DIR` env var if set + non-empty. Wins for both categories\n * — the operator's explicit choice overrides the split.\n * 2. Sandbox-mode default (#2501): when `NEXUS_SANDBOX` is set, use\n * `${NEXUS_SANDBOX_ROOT ?? '/'}/.nexus-agents`. Sandboxed deployments\n * typically mount a multi-repo root; state goes there, shared across\n * repo subfolders rather than buried inside one.\n * 3. Per-repo subdirs (when a repo root is known AND `NEXUS_REPO_PREFERRED`\n * is not explicitly `'0'`): `<repo-root>/.nexus-agents/<subdir>/`. The\n * repo root is the active workspace root when one has been set via\n * `setActiveWorkspaceRoot()` (the MCP server derives it from the client's\n * declared `roots`, MCP spec — see #3991), otherwise `findRepoRoot(cwd)`.\n * The active-root path exists because a globally-installed MCP server runs\n * with `process.cwd()` outside the repo, so cwd-based detection would\n * wrongly route per-repo state to homedir. Auto-adds `.nexus-agents/` to\n * the repo's `.gitignore` on first resolution (fail-closed).\n * 4. Cross-repo subdirs when `~/.nexus-agents/` is unwritable AND we're\n * in a repo: per-repo fallback at `<repo-root>/.nexus-agents/<subdir>/`\n * with one-time stderr announce. Issue #2888 — gives sandbox users\n * a working `research/`, `memory/`, etc. without env-var wrangling\n * while preserving the vote #2876 default for normal-machine users.\n * 5. `<homedir>/.nexus-agents` (cross-repo state on normal machines,\n * plus everything when not in a git repo or when\n * `NEXUS_REPO_PREFERRED=0`).\n *\n * `NEXUS_REPO_PREFERRED` defaults **ON** as of this release per vote\n * #2876. Escape hatches preserved:\n * - `NEXUS_REPO_PREFERRED=0` — fully opt out (homedir for everything).\n * - `NEXUS_DATA_DIR=~/.nexus-agents` — explicit override wins over\n * the tier and the categorization both.\n * - `nexus-agents migrate` (#2879) — relocate existing homedir state\n * into `<repo>/.nexus-agents/` before flipping in production.\n *\n * @module config/nexus-data-dir\n */\n\nimport {\n accessSync,\n constants as fsConstants,\n copyFileSync,\n existsSync,\n mkdirSync,\n realpathSync,\n renameSync,\n statSync,\n unlinkSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\n\nimport { detectSandbox } from './sandbox-detection.js';\nimport { findRepoRoot } from './repo-root-detection.js';\nimport { ensureGitignored } from './portable-mode.js';\n\n/**\n * Subdirs scoped to a single repo's work (per the epic #2872 vote).\n * When `NEXUS_REPO_PREFERRED=1` and the caller is inside a git repo, these\n * route to `<repo-root>/.nexus-agents/`; otherwise homedir. The complement\n * (everything not in this set) always goes to homedir so cross-project\n * learning state stays intact.\n *\n * Source-of-truth for the categorization: vote #2876 conditions + audit\n * thread on epic #2872. Adding a new entry here is the appropriate way to\n * mark new state as per-repo — do that as a deliberate decision, not by\n * default.\n */\nconst PER_REPO_SUBDIRS: ReadonlySet<string> = new Set([\n // Scratch space for short-lived work (worktrees, prompt files, MCP configs).\n // Per-repo because a throwaway worktree of repo A is meaningless to repo B,\n // and because containing it here keeps it inside the already-gitignored\n // `.nexus-agents/` tree instead of accumulating in a shared `/tmp` nobody\n // reaps. A full `/tmp` is not a theoretical problem: it silently broke this\n // repo's own test suite, with ~1,100 files failing to COLLECT and zero\n // assertion failures, which reads like a code fault and is not one.\n 'tmp',\n 'sessions',\n 'checkpoints',\n 'traces',\n 'runs',\n 'audit',\n 'pipeline',\n 'tasks',\n // Async-mode job results (#3042 / epic #2631). Tied to work happening\n // in a specific codebase — a job dispatched on repo A shouldn't be\n // polled-against on repo B, which would happen if jobs/ were homedir-scoped.\n 'jobs',\n // CI health-check events (#3076 / #3084). Per-repo because outages\n // reported via `ci_health_check({ repo })` are repo-correlated; a wedge\n // on repo A's queue does not predict repo B's health.\n 'ci-health',\n // Governance artifacts — the runtime authentic-vote-record ledger\n // (`governance/vote-records.jsonl`, #3897/#3927/#3991). Per-repo because a\n // promotion proposal is scoped to the codebase being changed; with\n // `NEXUS_REPO_PREFERRED=1` (default) these route to\n // `<repo>/.nexus-agents/governance/` (gitignored), and to\n // `~/.nexus-agents/governance/` otherwise (global / non-repo install). This\n // is DISTINCT from the committed `<repo>/governance/vote-records.jsonl`\n // ledger the promotion gate reads — that artifact is reached only via the\n // explicit `NEXUS_VOTE_RECORDS_PATH` override (#3991).\n 'governance',\n]);\n\n/** Returns the absolute path to the nexus-agents data directory. */\nexport function getNexusDataDir(): string {\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') {\n return resolve(fromEnv);\n }\n const sandbox = detectSandbox();\n if (sandbox.active) {\n return resolve(sandbox.root ?? '/', '.nexus-agents');\n }\n return join(homedir(), '.nexus-agents');\n}\n\n/**\n * No-op kept for source-compatibility with consumers that called this\n * earlier in development. The resolver is no longer cached, so resetting\n * is unnecessary. Kept exported (rather than removed) to avoid breaking\n * imports in tests that may have already adopted it.\n */\nexport function resetNexusDataDirCache(): void {\n // intentionally empty — see module docstring\n}\n\n/**\n * Tracks per-process state for the auto-gitignore wiring: we only need\n * to (a) probe + append to `.gitignore` once per repo, and (b) bail\n * cleanly if the operator has explicitly silenced the auto-add via\n * `NEXUS_GITIGNORE_AUTO=0`. Reset in tests via the helper below.\n */\nconst gitignoredRoots = new Set<string>();\n\n/** Test helper — clears the auto-gitignore \"already did this\" memo. */\nexport function _resetGitignoreMemoForTests(): void {\n gitignoredRoots.clear();\n}\n\n/**\n * Active workspace root, when known from a source more authoritative than\n * `process.cwd()`. Set by the MCP server (#3991) from the client's declared\n * `roots` (MCP spec) once the initialize handshake completes, because a\n * globally-installed server runs with cwd OUTSIDE the repo being worked on —\n * so cwd-based repo detection would route per-repo `.nexus-agents` state to\n * homedir instead of `<repo>/.nexus-agents/`. CLI / in-repo callers never set\n * this, so they keep resolving via `findRepoRoot(cwd)` exactly as before.\n */\nlet activeWorkspaceRoot: string | undefined;\n\n/**\n * Records the active workspace root for repo-scoped data resolution. The\n * value is cross-trust-boundary input (it arrives over the MCP transport from\n * the client), so it is validated before use: resolved to an absolute path,\n * canonicalized via `realpathSync` (defeats symlink games), and required to\n * be an existing directory. Returns `true` when accepted, `false` when the\n * value is empty/invalid (in which case the previous root is left untouched).\n * Passing `null`/empty explicitly clears any previously-set root.\n */\nexport function setActiveWorkspaceRoot(root: string | null | undefined): boolean {\n const trimmed = root?.trim();\n if (trimmed === undefined || trimmed === '') {\n activeWorkspaceRoot = undefined;\n return false;\n }\n try {\n const canonical = realpathSync(resolve(trimmed));\n if (!isAbsolute(canonical) || !statSync(canonical).isDirectory()) return false;\n activeWorkspaceRoot = canonical;\n return true;\n } catch {\n // Nonexistent path, permission error, or broken symlink — reject and keep\n // whatever was set before so a bad late signal can't corrupt resolution.\n return false;\n }\n}\n\n/** Returns the active workspace root if one has been set, else `undefined`. */\nexport function getActiveWorkspaceRoot(): string | undefined {\n return activeWorkspaceRoot;\n}\n\n/** Test helper — clears the active workspace root memo. */\nexport function _resetActiveWorkspaceRootForTests(): void {\n activeWorkspaceRoot = undefined;\n}\n\n/**\n * Returns the repo-scoped `.nexus-agents/` directory if all of the\n * following hold: `NEXUS_REPO_PREFERRED` is NOT explicitly `'0'` (it\n * defaults ON as of vote #2876), `NEXUS_DATA_DIR` is not explicitly set\n * (explicit override always wins), no sandbox is active, and a repo root is\n * known — either the active workspace root (set via\n * `setActiveWorkspaceRoot()`) or an ancestor `.git` found by\n * `findRepoRoot(cwd)`.\n *\n * Side effect: on the first successful resolution per process per repo,\n * appends `.nexus-agents/` to the repo's `.gitignore` if not already\n * present. The operator can silence this with `NEXUS_GITIGNORE_AUTO=0`\n * (e.g. on CI runners with a frozen working tree).\n *\n * Returns `null` when any precondition fails; callers fall back to\n * `getNexusDataDir()` (homedir).\n */\nexport function getNexusRepoDir(): string | null {\n if (process.env['NEXUS_REPO_PREFERRED'] === '0') return null;\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') return null;\n if (detectSandbox().active) return null;\n // Prefer the MCP-client-declared workspace root (#3991) when the server has\n // set it; fall back to walking up from cwd for CLI / in-repo callers.\n const root = activeWorkspaceRoot ?? findRepoRoot(process.cwd());\n if (root === null) return null;\n maybeAutoGitignore(root);\n return join(root, '.nexus-agents');\n}\n\n/**\n * Best-effort fail-closed wiring: once per process per repo, ensure\n * `.nexus-agents/` is in `<repo>/.gitignore`. Silenced via\n * `NEXUS_GITIGNORE_AUTO=0`. Failures are non-fatal — the helper logs\n * to stderr and continues.\n */\nfunction maybeAutoGitignore(repoRoot: string): void {\n if (process.env['NEXUS_GITIGNORE_AUTO'] === '0') return;\n if (gitignoredRoots.has(repoRoot)) return;\n gitignoredRoots.add(repoRoot);\n // Only attempt when the repo root looks real — avoid spamming stderr\n // from temp-dir test fixtures that race the lifecycle.\n if (!existsSync(repoRoot)) return;\n ensureGitignored(repoRoot, '.nexus-agents/');\n}\n\n/**\n * Per-process memo: was the homedir base directory found writable on first\n * probe? Used to short-circuit the sandbox-fallback decision in\n * `nexusDataPath()` without spending a syscall on every call.\n */\nlet homedirWritable: boolean | undefined;\n\n/** Per-process memo: have we already announced a fallback for this subdir? */\nconst announcedFallbacks = new Set<string>();\n\n/** Test helper — clears the homedir-writability + announcement memos. */\nexport function _resetWritabilityMemoForTests(): void {\n homedirWritable = undefined;\n announcedFallbacks.clear();\n}\n\n/**\n * Returns true if `getNexusDataDir()` resolves to a writable location. Probed\n * once per process and memo'd — flips the sandbox-fallback on/off for\n * cross-repo subdirs. `mkdirSync(recursive: true)` is the safest probe\n * because it both creates the dir if missing and surfaces ENOENT/EACCES\n * issues at the same point. Failures here are non-fatal — they just route\n * cross-repo state to the per-repo fallback (issue #2888).\n */\nfunction isHomedirBaseWritable(): boolean {\n if (homedirWritable !== undefined) return homedirWritable;\n const base = getNexusDataDir();\n try {\n mkdirSync(base, { recursive: true });\n accessSync(base, fsConstants.W_OK);\n homedirWritable = true;\n } catch {\n homedirWritable = false;\n }\n return homedirWritable;\n}\n\n/**\n * Emit a one-time stderr warning when a cross-repo subdir falls back to\n * the per-repo location. Operators in sandboxes get a clear signal about\n * what happened without per-call noise.\n */\nfunction announceCrossRepoFallback(subdir: string, repoPath: string): void {\n if (announcedFallbacks.has(subdir)) return;\n announcedFallbacks.add(subdir);\n process.stderr.write(\n `[nexus] ${subdir}: homedir ~/.nexus-agents is not writable; ` +\n `using per-repo fallback at ${repoPath}. See docs/guides/SANDBOXED-USAGE.md.\\n`\n );\n}\n\n/**\n * Returns a path joined under the appropriate data directory for the\n * given subdir.\n *\n * Routing decision (first match wins):\n * 1. **Per-repo subdir + in a repo + `NEXUS_REPO_PREFERRED` not '0'** →\n * `<repo>/.nexus-agents/<subdir>/...`. Standard behavior since #2884.\n * 2. **Cross-repo subdir + homedir unwritable + in a repo** →\n * `<repo>/.nexus-agents/<subdir>/...` as a sandbox-friendly fallback\n * (issue #2888). Emits a one-time stderr warning per subdir.\n * 3. **Otherwise** → `<homedir>/.nexus-agents/<subdir>/...`. If homedir\n * is also unwritable, the actual write will surface the error.\n *\n * Existing callers don't need to change — the routing decision is driven\n * by the first segment, not by caller-declared intent.\n */\nexport function nexusDataPath(...segments: string[]): string {\n const first = segments[0];\n\n // Tier 1: per-repo subdir + repo-preferred default.\n if (first !== undefined && PER_REPO_SUBDIRS.has(first)) {\n const repoDir = getNexusRepoDir();\n if (repoDir !== null) {\n return join(repoDir, ...segments);\n }\n }\n\n // Tier 2: cross-repo subdir but homedir isn't reachable. Fall back to\n // the per-repo location if we're in a repo, so sandbox users get a\n // working `research/`, `memory/`, etc. without manual env-var setup.\n // Vote #2876 preserved: this only fires when homedir is physically\n // unreachable; normal-machine users see no change.\n if (first !== undefined && !isHomedirBaseWritable()) {\n const repoDir = getNexusRepoDir();\n if (repoDir !== null) {\n const fallbackPath = join(repoDir, ...segments);\n announceCrossRepoFallback(first, fallbackPath);\n return fallbackPath;\n }\n // No repo to fall back to AND homedir unreachable — return the\n // homedir path anyway. The caller's eventual write will surface\n // the underlying EACCES/ENOENT, which is the right error to show\n // because the environment is genuinely broken at that point.\n }\n\n return join(getNexusDataDir(), ...segments);\n}\n\n/**\n * Like `nexusDataPath()` but eagerly creates the parent directory so\n * callers don't have to remember `mkdirSync(dirname(p), { recursive: true })`\n * before every write. Issue #2890. Use when the returned path will be\n * written to immediately; for read-only resolution prefer `nexusDataPath()`.\n *\n * If the call resolves to a directory (single segment or no segments),\n * the target IS the resolved path. If it resolves to a file (multiple\n * segments), the target is its parent.\n */\nexport function nexusDataPathEnsure(...segments: string[]): string {\n const resolved = nexusDataPath(...segments);\n const target = segments.length > 1 ? dirname(resolved) : resolved;\n mkdirSync(target, { recursive: true });\n return resolved;\n}\n\n/**\n * Explicitly homedir-scoped path. Use this in code that handles\n * cross-project state (routing memory, model registry cache, auth,\n * research catalog) where you want a hard guarantee that the resolver\n * never routes per-repo, even if a new subdir gets accidentally added\n * to the `PER_REPO_SUBDIRS` allowlist later.\n */\nexport function nexusSharedPath(...segments: string[]): string {\n return join(getNexusDataDir(), ...segments);\n}\n\n/**\n * Returns the read-only categorization used by `nexusDataPath()`.\n * Exposed for tests and for tooling that wants to introspect which\n * subdirs are per-repo (e.g. the `nexus-agents migrate` command).\n */\nexport function getPerRepoSubdirs(): ReadonlySet<string> {\n return PER_REPO_SUBDIRS;\n}\n\n/** Per-process memo — the legacy session-DB relocation runs at most once. */\nlet legacySessionsDbMigrated = false;\n\n/** Test helper — clears the session-DB migration \"already did this\" memo. */\nexport function _resetSessionsDbMigrationMemoForTests(): void {\n legacySessionsDbMigrated = false;\n}\n\n/** SQLite sidecar suffixes that must travel with the main DB file. */\nconst SQLITE_SIDECAR_SUFFIXES = ['', '-wal', '-shm', '-journal'] as const;\n\n/**\n * Resolves the session-database path, performing a one-time relocation\n * of any pre-#2902 database on the first call per process.\n *\n * The session DB is per-repo episodic state and belongs in the\n * `sessions/` bucket alongside the session journals — vote #2876\n * categorized `sessions/` as per-repo, and issue #2902 (consensus\n * 3/3) extended that to the DB. Before #2902 the DB resolved via\n * `nexusDataPath('sessions.db')`; because `sessions.db` is not a\n * per-repo first segment, that landed cross-repo at\n * `~/.nexus-agents/sessions.db`, so a DB started in one repo leaked\n * into every other repo.\n *\n * If a legacy DB exists at the old location and none exists at the\n * new one, the legacy file (and any SQLite sidecars) is moved so\n * existing session history is preserved rather than silently\n * orphaned. The move is best-effort: a failure leaves the legacy DB\n * untouched for manual recovery and the caller gets a fresh DB at the\n * new path. `NEXUS_DATA_DIR` / `NEXUS_REPO_PREFERRED` / the\n * `NEXUS_SESSIONS_DB` override all sit upstream of this resolver.\n */\nexport function sessionsDbPath(): string {\n const target = nexusDataPath('sessions', 'sessions.db');\n if (!legacySessionsDbMigrated) {\n legacySessionsDbMigrated = true;\n migrateLegacySessionsDb(target);\n }\n return target;\n}\n\n/** One-time relocation of a pre-#2902 session DB. Best-effort — see `sessionsDbPath`. */\nfunction migrateLegacySessionsDb(target: string): void {\n const legacy = nexusDataPath('sessions.db'); // pre-#2902 resolution\n if (legacy === target || !existsSync(legacy) || existsSync(target)) return;\n try {\n mkdirSync(dirname(target), { recursive: true });\n for (const suffix of SQLITE_SIDECAR_SUFFIXES) {\n const from = `${legacy}${suffix}`;\n if (existsSync(from)) moveFile(from, `${target}${suffix}`);\n }\n } catch {\n // Best-effort — a migration failure must never break session storage.\n }\n}\n\n/** Moves a file, falling back to copy+unlink across a filesystem boundary. */\nfunction moveFile(from: string, to: string): void {\n try {\n renameSync(from, to);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EXDEV') throw err;\n copyFileSync(from, to);\n unlinkSync(from);\n }\n}\n","/**\n * Sandbox detection (#2501, child 1 of epic #2500).\n *\n * nexus-agents needs to know whether it's running inside a host-provided\n * sandbox (Docker Desktop Sandbox + OpenCode, Codex sandbox, locked-down\n * CI runner) so it can adjust behaviour:\n *\n * - default `NEXUS_DATA_DIR` to the multi-repo root rather than `~/...`\n * - skip CLI subprocess detection (binaries aren't there)\n * - fail-fast when the gateway is unreachable instead of degrading\n * - suppress diagnostics that don't apply\n *\n * The signal is **explicit**: the image author (Dockerfile, devcontainer,\n * harness wrapper) sets `NEXUS_SANDBOX=<flavor>`. The flavor string is\n * for diagnostics + per-flavor branching (`docker-opencode`, `codex`,\n * `claude-code`, `ci-restricted`, …); presence is the on/off signal.\n *\n * A heuristic check runs alongside for verification — if `NEXUS_SANDBOX`\n * claims `docker-opencode` but `/.dockerenv` is missing, that's a\n * misconfiguration worth surfacing to the operator.\n *\n * @module config/sandbox-detection\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\n\n/**\n * Heuristic detection result. Independent from the explicit\n * `NEXUS_SANDBOX` env var; produced by inspecting filesystem markers.\n */\nexport type SandboxHeuristic = 'docker' | 'podman' | 'unknown' | null;\n\nexport interface SandboxInfo {\n /**\n * True iff `NEXUS_SANDBOX` is set + non-empty. The explicit signal\n * the rest of the codebase keys off — never `true` by heuristic alone.\n */\n readonly active: boolean;\n /**\n * Operator-supplied flavor string, e.g. `docker-opencode`. Undefined\n * when `active === false` or the env var is empty.\n */\n readonly flavor: string | undefined;\n /**\n * Multi-repo root the user mounted. From `NEXUS_SANDBOX_ROOT`. Undefined\n * when unset; consumers that need a default substitute `/`.\n */\n readonly root: string | undefined;\n /**\n * Independent heuristic match — `null` when we couldn't run the check,\n * `'unknown'` when ran but no marker matched. Used by `doctor` to flag\n * mismatches between the explicit signal and the runtime environment.\n */\n readonly heuristicMatch: SandboxHeuristic;\n}\n\n/**\n * Detect whether nexus-agents is running inside a host-provided sandbox.\n * Pure read of env + filesystem; no caching (cheap enough to recompute,\n * and tests routinely mutate process.env).\n */\nexport function detectSandbox(): SandboxInfo {\n const flavorRaw = process.env['NEXUS_SANDBOX']?.trim();\n const flavor = flavorRaw !== undefined && flavorRaw !== '' ? flavorRaw : undefined;\n const active = flavor !== undefined;\n\n const rootRaw = process.env['NEXUS_SANDBOX_ROOT']?.trim();\n const root = rootRaw !== undefined && rootRaw !== '' ? rootRaw : undefined;\n\n return {\n active,\n flavor,\n root,\n heuristicMatch: detectContainerHeuristic(),\n };\n}\n\n/**\n * Look for filesystem markers indicating a container runtime. Used as a\n * cross-check against the explicit `NEXUS_SANDBOX` signal.\n *\n * Order of checks:\n * 1. `/.dockerenv` (Docker)\n * 2. `/run/.containerenv` (Podman)\n * 3. `/proc/1/cgroup` containing `docker` or `containerd` strings\n *\n * Returns `null` when none of the checks could run (non-Linux host with\n * no `/proc`, sandbox blocks `existsSync`, etc.) — distinct from\n * `'unknown'` (checks ran, no marker matched) so the doctor message can\n * differentiate \"we couldn't tell\" from \"we checked, no container\".\n */\nfunction detectContainerHeuristic(): SandboxHeuristic {\n try {\n if (existsSync('/.dockerenv')) return 'docker';\n if (existsSync('/run/.containerenv')) return 'podman';\n if (existsSync('/proc/1/cgroup')) {\n const cgroup = readFileSync('/proc/1/cgroup', 'utf8');\n if (/\\bdocker\\b/.test(cgroup)) return 'docker';\n if (/\\bcontainerd\\b/.test(cgroup)) return 'docker';\n }\n return 'unknown';\n } catch {\n return null;\n }\n}\n","/**\n * Repo-root detection — walks upward from a starting directory looking for\n * `.git` so callers can scope state to the current git repo. Built for\n * epic #2872 (issue #2882) to support the per-repo `.nexus-agents/` data\n * directory ratified by vote #2876.\n *\n * Defenses adopted:\n * - Walks upward but stops at filesystem root (no infinite loop on\n * symlinks-to-self).\n * - Stops at filesystem boundary (different `stat.dev`) — refuses to\n * escape across mount points. Prevents a sandboxed workdir from\n * resolving a `.git` on the host filesystem.\n * - Detects git worktrees: `.git` may be a *file* containing\n * `gitdir: <path>` rather than a directory.\n * - Realpath's the final root and rejects results that escape the\n * starting cwd by symlink.\n *\n * Deferred (tracked separately; not blocking #2882):\n * - CVE-2022-24765-style ownership check (refuse `.git` owned by a\n * different uid than the running process). The git CLI added\n * `safe.directory` for this; we don't have a comparable allowlist\n * surface yet. In CI the heuristic is too noisy to be useful\n * (runners often clone as a different uid than the workload).\n * File-system isolation (the `stat.dev` check above) covers the\n * bulk of the attack class.\n *\n * @module config/repo-root-detection\n */\n\nimport { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';\nimport { dirname, isAbsolute, resolve } from 'node:path';\n\n/**\n * Walks upward from `start` looking for `.git` (file or directory).\n * Returns the absolute path to the directory containing it, or `null`.\n *\n * Worktrees: a `.git` file (not dir) with contents `gitdir: <path>` is\n * still recognised as marking the repo root. The pointed-to gitdir is\n * NOT followed — we just need the worktree's own root.\n *\n * Filesystem boundary: stops walking if the next ancestor lives on a\n * different filesystem (different `stat.dev`). This prevents a workdir\n * mounted inside a sandbox from finding a host-side `.git`.\n */\nexport function findRepoRoot(start: string): string | null {\n if (!isAbsolute(start)) {\n start = resolve(start);\n }\n\n let current: string;\n try {\n current = realpathSync(start);\n } catch {\n return null;\n }\n\n let startDev: number | undefined;\n try {\n startDev = statSync(current).dev;\n } catch {\n return null;\n }\n\n // Walk up. Depth cap is paranoid but bounds the loop on pathological\n // symlinks even though realpathSync above should already prevent cycles.\n for (let i = 0; i < 64; i++) {\n if (isRepoRoot(current)) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n // Hit filesystem root.\n return null;\n }\n try {\n if (statSync(parent).dev !== startDev) {\n // Crossed a mount point; refuse to escape.\n return null;\n }\n } catch {\n return null;\n }\n current = parent;\n }\n return null;\n}\n\n/** True iff `dir/.git` exists as a directory or a git-worktree marker file. */\nexport function isRepoRoot(dir: string): boolean {\n const gitPath = `${dir}/.git`;\n if (!existsSync(gitPath)) return false;\n try {\n const st = statSync(gitPath);\n if (st.isDirectory()) return true;\n if (st.isFile()) {\n // Worktree: first line should be `gitdir: <path>`.\n const head = readFileSync(gitPath, 'utf-8').slice(0, 256);\n return head.startsWith('gitdir:');\n }\n } catch {\n return false;\n }\n return false;\n}\n","/**\n * portable-mode — auto-detect sandboxed execution environments and\n * redirect nexus-agents data to a workspace-local directory.\n *\n * The existing `getNexusDataDir()` already respects `NEXUS_DATA_DIR` and\n * `NEXUS_PORTABLE_MODE`. This module wires up the *auto-detection* path\n * so operators running inside a Docker / restricted-FS / sandboxed\n * environment don't have to set the env vars manually — nexus-agents\n * notices and announces the flip.\n *\n * Source: Issue #2471 (epic #2467 child).\n *\n * Detection rules (first match wins):\n * 1. `NEXUS_DATA_DIR` already set → respect, no auto-detect\n * 2. `NEXUS_PORTABLE_MODE=0` → operator opted out, no auto-detect\n * 3. `NEXUS_PORTABLE_MODE=1` → portable mode, no detection needed\n * 4. Heuristic: home directory not writable\n * 5. Heuristic: container env vars set (KUBERNETES_SERVICE_HOST,\n * DOCKER_CONTAINER, ECS_CONTAINER_METADATA_URI, SANDBOX, etc.)\n *\n * When portable mode fires:\n * - Sets `process.env.NEXUS_DATA_DIR` to `<cwd>/.nexus-agents`\n * - Announces once on stderr (subsequent calls are silent)\n * - If cwd is a git repo, appends `.nexus-agents/` to .gitignore\n *\n * To force off: `NEXUS_PORTABLE_MODE=0`. To force on without heuristics:\n * `NEXUS_PORTABLE_MODE=1`. To override target dir: `NEXUS_DATA_DIR=...`.\n */\n\nimport { accessSync, constants, existsSync, readFileSync, appendFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\n\n/** Container / sandbox environment variables that strongly imply portable mode. */\nconst SANDBOX_ENV_VARS: readonly string[] = [\n 'KUBERNETES_SERVICE_HOST',\n 'DOCKER_CONTAINER',\n 'ECS_CONTAINER_METADATA_URI',\n 'ECS_CONTAINER_METADATA_URI_V4',\n 'SANDBOX',\n 'NEXUS_SANDBOX',\n];\n\nlet DETECTED = false;\n\ninterface DetectionResult {\n readonly portable: boolean;\n readonly dataDir: string;\n readonly reason:\n | 'env-data-dir'\n | 'env-opt-out'\n | 'env-opt-in'\n | 'home-unwritable'\n | 'container-env'\n | 'default';\n}\n\nfunction isHomeWritable(): boolean {\n try {\n const home = homedir();\n if (!existsSync(home)) return false;\n accessSync(home, constants.W_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction inSandboxEnv(): boolean {\n for (const v of SANDBOX_ENV_VARS) {\n const val = process.env[v];\n if (val !== undefined && val !== '') return true;\n }\n return false;\n}\n\nfunction checkExplicitEnv(cwd: string): DetectionResult | null {\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') {\n return { portable: false, dataDir: resolve(fromEnv), reason: 'env-data-dir' };\n }\n const portableEnv = process.env['NEXUS_PORTABLE_MODE'];\n if (portableEnv === '0' || portableEnv === 'false') {\n return { portable: false, dataDir: join(homedir(), '.nexus-agents'), reason: 'env-opt-out' };\n }\n if (portableEnv === '1' || portableEnv === 'true') {\n return { portable: true, dataDir: join(cwd, '.nexus-agents'), reason: 'env-opt-in' };\n }\n return null;\n}\n\nfunction checkHeuristics(cwd: string): DetectionResult | null {\n if (!isHomeWritable()) {\n return { portable: true, dataDir: join(cwd, '.nexus-agents'), reason: 'home-unwritable' };\n }\n if (inSandboxEnv()) {\n return { portable: true, dataDir: join(cwd, '.nexus-agents'), reason: 'container-env' };\n }\n return null;\n}\n\n/**\n * Decide whether to flip nexus-agents to portable mode and what data dir\n * to use. Pure function; no side effects beyond reading env + filesystem.\n */\nexport function detectPortableMode(cwd: string = process.cwd()): DetectionResult {\n return (\n checkExplicitEnv(cwd) ??\n checkHeuristics(cwd) ?? {\n portable: false,\n dataDir: join(homedir(), '.nexus-agents'),\n reason: 'default',\n }\n );\n}\n\n/**\n * Apply detected portable mode at process startup. Idempotent (subsequent\n * calls are no-ops). Announces on stderr once when the auto-detection\n * heuristic triggers — env-opt-in/out doesn't announce because the\n * operator already knows.\n */\nexport function applyPortableMode(cwd: string = process.cwd()): void {\n if (DETECTED) return;\n DETECTED = true;\n\n const result = detectPortableMode(cwd);\n\n // No env, no heuristic match — nothing to do.\n if (!result.portable) return;\n\n // The heuristic-driven cases announce; explicit opt-in is silent.\n if (result.reason === 'home-unwritable' || result.reason === 'container-env') {\n process.stderr.write(\n `[portable-mode] Sandbox detected (${result.reason}). Using ${result.dataDir} for all nexus-agents data.\\n` +\n ` Set NEXUS_PORTABLE_MODE=0 to override; see docs/guides/SANDBOXED-USAGE.md\\n`\n );\n }\n\n process.env['NEXUS_DATA_DIR'] = result.dataDir;\n\n if (isInsideGitRepo(cwd)) {\n ensureGitignored(cwd, '.nexus-agents/');\n }\n}\n\nfunction isInsideGitRepo(cwd: string): boolean {\n try {\n const gitDir = join(cwd, '.git');\n return existsSync(gitDir) && statSync(gitDir).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Append `entry` to `<cwd>/.gitignore` if not already present. Stderr\n * announce on the first append. Exported because the repo-preferred\n * resolver in `nexus-data-dir.ts` reuses the same auto-gitignore behavior\n * when `<repo>/.nexus-agents/` becomes the active data dir (epic #2872).\n */\nexport function ensureGitignored(cwd: string, entry: string): void {\n const path = join(cwd, '.gitignore');\n try {\n const existing = existsSync(path) ? readFileSync(path, 'utf-8') : '';\n const lines = existing.split('\\n').map((l) => l.trim());\n if (lines.includes(entry) || lines.includes(entry.replace(/\\/$/, ''))) return;\n const sep = existing.length > 0 && !existing.endsWith('\\n') ? '\\n' : '';\n appendFileSync(path, `${sep}${entry}\\n`, 'utf-8');\n process.stderr.write(\n `[portable-mode] Added '${entry}' to ${path} so nexus-agents data is gitignored.\\n`\n );\n } catch (e: unknown) {\n // Non-fatal — operator can add the line themselves.\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(\n `[portable-mode] Could not auto-update ${path} (${msg}). Add '${entry}' manually if you don't want nexus-agents data tracked by git.\\n`\n );\n }\n}\n\n/** Test-only reset of the once-only state. */\nexport function _resetDetectedForTests(): void {\n DETECTED = false;\n}\n"],"mappings":"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","names":["accessSync","existsSync","realpathSync","statSync","homedir","dirname","isAbsolute","join","resolve","existsSync","readFileSync","existsSync","readFileSync","statSync","resolve","resolve","join","homedir","realpathSync","resolve","isAbsolute","statSync","join","existsSync","accessSync","join","dirname","existsSync","dirname"]}
1
+ {"version":3,"sources":["../src/config/nexus-data-dir.ts","../src/config/sandbox-detection.ts","../src/config/repo-root-detection.ts","../src/config/portable-mode.ts"],"sourcesContent":["/**\n * Nexus runtime data directory resolver (#2302, child of #2301).\n *\n * Returns the absolute path under which nexus-agents stores runtime state.\n * Two categories of state exist (epic #2872, vote #2876):\n *\n * - **Per-repo** state (sessions, checkpoints, traces, runs, audit,\n * pipeline, tasks) — tied to the work happening in a specific codebase.\n * When `NEXUS_REPO_PREFERRED=1` is set and the caller is inside a git\n * repo, these resolve to `<repo-root>/.nexus-agents/<subdir>/`.\n * - **Cross-repo** state (learning, voting, memory, weather, research,\n * auth, usage, models manifest) — accumulates across the operator's\n * whole workflow. Always resolves to `~/.nexus-agents/<subdir>/` so the\n * learning/routing feedback loop from #1389 / #1407 keeps working.\n *\n * Resolution order (first match wins):\n * 1. `NEXUS_DATA_DIR` env var if set + non-empty. Wins for both categories\n * — the operator's explicit choice overrides the split.\n * 2. Sandbox-mode default (#2501): when `NEXUS_SANDBOX` is set, use\n * `${NEXUS_SANDBOX_ROOT ?? '/'}/.nexus-agents`. Sandboxed deployments\n * typically mount a multi-repo root; state goes there, shared across\n * repo subfolders rather than buried inside one.\n * 3. Per-repo subdirs (when a repo root is known AND `NEXUS_REPO_PREFERRED`\n * is not explicitly `'0'`): `<repo-root>/.nexus-agents/<subdir>/`. The\n * repo root is the active workspace root when one has been set via\n * `setActiveWorkspaceRoot()` (the MCP server derives it from the client's\n * declared `roots`, MCP spec — see #3991), otherwise `findRepoRoot(cwd)`.\n * The active-root path exists because a globally-installed MCP server runs\n * with `process.cwd()` outside the repo, so cwd-based detection would\n * wrongly route per-repo state to homedir. Auto-adds `.nexus-agents/` to\n * the repo's `.gitignore` on first resolution (fail-closed).\n * 4. Cross-repo subdirs when `~/.nexus-agents/` is unwritable AND we're\n * in a repo: per-repo fallback at `<repo-root>/.nexus-agents/<subdir>/`\n * with one-time stderr announce. Issue #2888 — gives sandbox users\n * a working `research/`, `memory/`, etc. without env-var wrangling\n * while preserving the vote #2876 default for normal-machine users.\n * 5. `<homedir>/.nexus-agents` (cross-repo state on normal machines,\n * plus everything when not in a git repo or when\n * `NEXUS_REPO_PREFERRED=0`).\n *\n * `NEXUS_REPO_PREFERRED` defaults **ON** as of this release per vote\n * #2876. Escape hatches preserved:\n * - `NEXUS_REPO_PREFERRED=0` — fully opt out (homedir for everything).\n * - `NEXUS_DATA_DIR=~/.nexus-agents` — explicit override wins over\n * the tier and the categorization both.\n * - `nexus-agents migrate` (#2879) — relocate existing homedir state\n * into `<repo>/.nexus-agents/` before flipping in production.\n *\n * @module config/nexus-data-dir\n */\n\nimport {\n accessSync,\n constants as fsConstants,\n copyFileSync,\n existsSync,\n mkdirSync,\n realpathSync,\n renameSync,\n statSync,\n unlinkSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\n\nimport { detectSandbox } from './sandbox-detection.js';\nimport { findRepoRoot } from './repo-root-detection.js';\nimport { ensureGitignored } from './portable-mode.js';\n\n/**\n * Subdirs scoped to a single repo's work (per the epic #2872 vote).\n * When `NEXUS_REPO_PREFERRED=1` and the caller is inside a git repo, these\n * route to `<repo-root>/.nexus-agents/`; otherwise homedir. The complement\n * (everything not in this set) always goes to homedir so cross-project\n * learning state stays intact.\n *\n * Source-of-truth for the categorization: vote #2876 conditions + audit\n * thread on epic #2872. Adding a new entry here is the appropriate way to\n * mark new state as per-repo — do that as a deliberate decision, not by\n * default.\n */\nconst PER_REPO_SUBDIRS: ReadonlySet<string> = new Set([\n // Scratch space for short-lived work (worktrees, prompt files, MCP configs).\n // Per-repo because a throwaway worktree of repo A is meaningless to repo B,\n // and because containing it here keeps it inside the already-gitignored\n // `.nexus-agents/` tree instead of accumulating in a shared `/tmp` nobody\n // reaps. A full `/tmp` is not a theoretical problem: it silently broke this\n // repo's own test suite, with ~1,100 files failing to COLLECT and zero\n // assertion failures, which reads like a code fault and is not one.\n 'tmp',\n 'sessions',\n 'checkpoints',\n 'traces',\n 'runs',\n 'audit',\n 'pipeline',\n 'tasks',\n // Async-mode job results (#3042 / epic #2631). Tied to work happening\n // in a specific codebase — a job dispatched on repo A shouldn't be\n // polled-against on repo B, which would happen if jobs/ were homedir-scoped.\n 'jobs',\n // CI health-check events (#3076 / #3084). Per-repo because outages\n // reported via `ci_health_check({ repo })` are repo-correlated; a wedge\n // on repo A's queue does not predict repo B's health.\n 'ci-health',\n // Governance artifacts — the runtime authentic-vote-record ledger\n // (`governance/vote-records.jsonl`, #3897/#3927/#3991). Per-repo because a\n // promotion proposal is scoped to the codebase being changed; with\n // `NEXUS_REPO_PREFERRED=1` (default) these route to\n // `<repo>/.nexus-agents/governance/` (gitignored), and to\n // `~/.nexus-agents/governance/` otherwise (global / non-repo install). This\n // is DISTINCT from the committed `<repo>/governance/vote-records.jsonl`\n // ledger the promotion gate reads — that artifact is reached only via the\n // explicit `NEXUS_VOTE_RECORDS_PATH` override (#3991).\n 'governance',\n]);\n\n/** Returns the absolute path to the nexus-agents data directory. */\nexport function getNexusDataDir(): string {\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') {\n return resolve(fromEnv);\n }\n const sandbox = detectSandbox();\n if (sandbox.active) {\n return resolve(sandbox.root ?? '/', '.nexus-agents');\n }\n return join(homedir(), '.nexus-agents');\n}\n\n/**\n * No-op kept for source-compatibility with consumers that called this\n * earlier in development. The resolver is no longer cached, so resetting\n * is unnecessary. Kept exported (rather than removed) to avoid breaking\n * imports in tests that may have already adopted it.\n */\nexport function resetNexusDataDirCache(): void {\n // intentionally empty — see module docstring\n}\n\n/**\n * Tracks per-process state for the auto-gitignore wiring: we only need\n * to (a) probe + append to `.gitignore` once per repo, and (b) bail\n * cleanly if the operator has explicitly silenced the auto-add via\n * `NEXUS_GITIGNORE_AUTO=0`. Reset in tests via the helper below.\n */\nconst gitignoredRoots = new Set<string>();\n\n/** Test helper — clears the auto-gitignore \"already did this\" memo. */\nexport function _resetGitignoreMemoForTests(): void {\n gitignoredRoots.clear();\n}\n\n/**\n * Active workspace root, when known from a source more authoritative than\n * `process.cwd()`. Set by the MCP server (#3991) from the client's declared\n * `roots` (MCP spec) once the initialize handshake completes, because a\n * globally-installed server runs with cwd OUTSIDE the repo being worked on —\n * so cwd-based repo detection would route per-repo `.nexus-agents` state to\n * homedir instead of `<repo>/.nexus-agents/`. CLI / in-repo callers never set\n * this, so they keep resolving via `findRepoRoot(cwd)` exactly as before.\n */\nlet activeWorkspaceRoot: string | undefined;\n\n/**\n * Records the active workspace root for repo-scoped data resolution. The\n * value is cross-trust-boundary input (it arrives over the MCP transport from\n * the client), so it is validated before use: resolved to an absolute path,\n * canonicalized via `realpathSync` (defeats symlink games), and required to\n * be an existing directory. Returns `true` when accepted, `false` when the\n * value is empty/invalid (in which case the previous root is left untouched).\n * Passing `null`/empty explicitly clears any previously-set root.\n */\nexport function setActiveWorkspaceRoot(root: string | null | undefined): boolean {\n const trimmed = root?.trim();\n if (trimmed === undefined || trimmed === '') {\n activeWorkspaceRoot = undefined;\n return false;\n }\n try {\n const canonical = realpathSync(resolve(trimmed));\n if (!isAbsolute(canonical) || !statSync(canonical).isDirectory()) return false;\n activeWorkspaceRoot = canonical;\n return true;\n } catch {\n // Nonexistent path, permission error, or broken symlink — reject and keep\n // whatever was set before so a bad late signal can't corrupt resolution.\n return false;\n }\n}\n\n/** Returns the active workspace root if one has been set, else `undefined`. */\nexport function getActiveWorkspaceRoot(): string | undefined {\n return activeWorkspaceRoot;\n}\n\n/** Test helper — clears the active workspace root memo. */\nexport function _resetActiveWorkspaceRootForTests(): void {\n activeWorkspaceRoot = undefined;\n}\n\n/**\n * Returns the repo-scoped `.nexus-agents/` directory if all of the\n * following hold: `NEXUS_REPO_PREFERRED` is NOT explicitly `'0'` (it\n * defaults ON as of vote #2876), `NEXUS_DATA_DIR` is not explicitly set\n * (explicit override always wins), no sandbox is active, and a repo root is\n * known — either the active workspace root (set via\n * `setActiveWorkspaceRoot()`) or an ancestor `.git` found by\n * `findRepoRoot(cwd)`.\n *\n * Side effect: on the first successful resolution per process per repo,\n * appends `.nexus-agents/` to the repo's `.gitignore` if not already\n * present. The operator can silence this with `NEXUS_GITIGNORE_AUTO=0`\n * (e.g. on CI runners with a frozen working tree).\n *\n * Returns `null` when any precondition fails; callers fall back to\n * `getNexusDataDir()` (homedir).\n */\nexport function getNexusRepoDir(): string | null {\n if (process.env['NEXUS_REPO_PREFERRED'] === '0') return null;\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') return null;\n if (detectSandbox().active) return null;\n // Prefer the MCP-client-declared workspace root (#3991) when the server has\n // set it; fall back to walking up from cwd for CLI / in-repo callers.\n const root = activeWorkspaceRoot ?? findRepoRoot(process.cwd());\n if (root === null) return null;\n maybeAutoGitignore(root);\n return join(root, '.nexus-agents');\n}\n\n/**\n * Best-effort fail-closed wiring: once per process per repo, ensure\n * `.nexus-agents/` is in `<repo>/.gitignore`. Silenced via\n * `NEXUS_GITIGNORE_AUTO=0`. Failures are non-fatal — the helper logs\n * to stderr and continues.\n */\nfunction maybeAutoGitignore(repoRoot: string): void {\n if (process.env['NEXUS_GITIGNORE_AUTO'] === '0') return;\n if (gitignoredRoots.has(repoRoot)) return;\n gitignoredRoots.add(repoRoot);\n // Only attempt when the repo root looks real — avoid spamming stderr\n // from temp-dir test fixtures that race the lifecycle.\n if (!existsSync(repoRoot)) return;\n ensureGitignored(repoRoot, '.nexus-agents/');\n}\n\n/**\n * Per-process memo: was the homedir base directory found writable on first\n * probe? Used to short-circuit the sandbox-fallback decision in\n * `nexusDataPath()` without spending a syscall on every call.\n */\nlet homedirWritable: boolean | undefined;\n\n/** Per-process memo: have we already announced a fallback for this subdir? */\nconst announcedFallbacks = new Set<string>();\n\n/** Test helper — clears the homedir-writability + announcement memos. */\nexport function _resetWritabilityMemoForTests(): void {\n homedirWritable = undefined;\n announcedFallbacks.clear();\n}\n\n/**\n * Returns true if `getNexusDataDir()` resolves to a writable location. Probed\n * once per process and memo'd — flips the sandbox-fallback on/off for\n * cross-repo subdirs. `mkdirSync(recursive: true)` is the safest probe\n * because it both creates the dir if missing and surfaces ENOENT/EACCES\n * issues at the same point. Failures here are non-fatal — they just route\n * cross-repo state to the per-repo fallback (issue #2888).\n */\nfunction isHomedirBaseWritable(): boolean {\n if (homedirWritable !== undefined) return homedirWritable;\n const base = getNexusDataDir();\n try {\n mkdirSync(base, { recursive: true });\n accessSync(base, fsConstants.W_OK);\n homedirWritable = true;\n } catch {\n homedirWritable = false;\n }\n return homedirWritable;\n}\n\n/**\n * Emit a one-time stderr warning when a cross-repo subdir falls back to\n * the per-repo location. Operators in sandboxes get a clear signal about\n * what happened without per-call noise.\n */\nfunction announceCrossRepoFallback(subdir: string, repoPath: string): void {\n if (announcedFallbacks.has(subdir)) return;\n announcedFallbacks.add(subdir);\n process.stderr.write(\n `[nexus] ${subdir}: homedir ~/.nexus-agents is not writable; ` +\n `using per-repo fallback at ${repoPath}. See docs/guides/SANDBOXED-USAGE.md.\\n`\n );\n}\n\n/**\n * Returns a path joined under the appropriate data directory for the\n * given subdir.\n *\n * Routing decision (first match wins):\n * 1. **Per-repo subdir + in a repo + `NEXUS_REPO_PREFERRED` not '0'** →\n * `<repo>/.nexus-agents/<subdir>/...`. Standard behavior since #2884.\n * 2. **Cross-repo subdir + homedir unwritable + in a repo** →\n * `<repo>/.nexus-agents/<subdir>/...` as a sandbox-friendly fallback\n * (issue #2888). Emits a one-time stderr warning per subdir.\n * 3. **Otherwise** → `<homedir>/.nexus-agents/<subdir>/...`. If homedir\n * is also unwritable, the actual write will surface the error.\n *\n * Existing callers don't need to change — the routing decision is driven\n * by the first segment, not by caller-declared intent.\n */\nexport function nexusDataPath(...segments: string[]): string {\n const first = segments[0];\n\n // Tier 1: per-repo subdir + repo-preferred default.\n if (first !== undefined && PER_REPO_SUBDIRS.has(first)) {\n const repoDir = getNexusRepoDir();\n if (repoDir !== null) {\n return join(repoDir, ...segments);\n }\n }\n\n // Tier 2: cross-repo subdir but homedir isn't reachable. Fall back to\n // the per-repo location if we're in a repo, so sandbox users get a\n // working `research/`, `memory/`, etc. without manual env-var setup.\n // Vote #2876 preserved: this only fires when homedir is physically\n // unreachable; normal-machine users see no change.\n if (first !== undefined && !isHomedirBaseWritable()) {\n const repoDir = getNexusRepoDir();\n if (repoDir !== null) {\n const fallbackPath = join(repoDir, ...segments);\n announceCrossRepoFallback(first, fallbackPath);\n return fallbackPath;\n }\n // No repo to fall back to AND homedir unreachable — return the\n // homedir path anyway. The caller's eventual write will surface\n // the underlying EACCES/ENOENT, which is the right error to show\n // because the environment is genuinely broken at that point.\n }\n\n return join(getNexusDataDir(), ...segments);\n}\n\n/**\n * Like `nexusDataPath()` but eagerly creates the parent directory so\n * callers don't have to remember `mkdirSync(dirname(p), { recursive: true })`\n * before every write. Issue #2890. Use when the returned path will be\n * written to immediately; for read-only resolution prefer `nexusDataPath()`.\n *\n * If the call resolves to a directory (single segment or no segments),\n * the target IS the resolved path. If it resolves to a file (multiple\n * segments), the target is its parent.\n */\nexport function nexusDataPathEnsure(...segments: string[]): string {\n const resolved = nexusDataPath(...segments);\n const target = segments.length > 1 ? dirname(resolved) : resolved;\n mkdirSync(target, { recursive: true });\n return resolved;\n}\n\n/**\n * Explicitly homedir-scoped path. Use this in code that handles\n * cross-project state (routing memory, model registry cache, auth,\n * research catalog) where you want a hard guarantee that the resolver\n * never routes per-repo, even if a new subdir gets accidentally added\n * to the `PER_REPO_SUBDIRS` allowlist later.\n */\nexport function nexusSharedPath(...segments: string[]): string {\n return join(getNexusDataDir(), ...segments);\n}\n\n/**\n * Returns the read-only categorization used by `nexusDataPath()`.\n * Exposed for tests and for tooling that wants to introspect which\n * subdirs are per-repo (e.g. the `nexus-agents migrate` command).\n */\nexport function getPerRepoSubdirs(): ReadonlySet<string> {\n return PER_REPO_SUBDIRS;\n}\n\n/** Per-process memo — the legacy session-DB relocation runs at most once. */\nlet legacySessionsDbMigrated = false;\n\n/** Test helper — clears the session-DB migration \"already did this\" memo. */\nexport function _resetSessionsDbMigrationMemoForTests(): void {\n legacySessionsDbMigrated = false;\n}\n\n/** SQLite sidecar suffixes that must travel with the main DB file. */\nconst SQLITE_SIDECAR_SUFFIXES = ['', '-wal', '-shm', '-journal'] as const;\n\n/**\n * Resolves the session-database path, performing a one-time relocation\n * of any pre-#2902 database on the first call per process.\n *\n * The session DB is per-repo episodic state and belongs in the\n * `sessions/` bucket alongside the session journals — vote #2876\n * categorized `sessions/` as per-repo, and issue #2902 (consensus\n * 3/3) extended that to the DB. Before #2902 the DB resolved via\n * `nexusDataPath('sessions.db')`; because `sessions.db` is not a\n * per-repo first segment, that landed cross-repo at\n * `~/.nexus-agents/sessions.db`, so a DB started in one repo leaked\n * into every other repo.\n *\n * If a legacy DB exists at the old location and none exists at the\n * new one, the legacy file (and any SQLite sidecars) is moved so\n * existing session history is preserved rather than silently\n * orphaned. The move is best-effort: a failure leaves the legacy DB\n * untouched for manual recovery and the caller gets a fresh DB at the\n * new path. `NEXUS_DATA_DIR` / `NEXUS_REPO_PREFERRED` / the\n * `NEXUS_SESSIONS_DB` override all sit upstream of this resolver.\n */\nexport function sessionsDbPath(): string {\n const target = nexusDataPath('sessions', 'sessions.db');\n if (!legacySessionsDbMigrated) {\n legacySessionsDbMigrated = true;\n migrateLegacySessionsDb(target);\n }\n return target;\n}\n\n/** One-time relocation of a pre-#2902 session DB. Best-effort — see `sessionsDbPath`. */\nfunction migrateLegacySessionsDb(target: string): void {\n const legacy = nexusDataPath('sessions.db'); // pre-#2902 resolution\n if (legacy === target || !existsSync(legacy) || existsSync(target)) return;\n try {\n mkdirSync(dirname(target), { recursive: true });\n for (const suffix of SQLITE_SIDECAR_SUFFIXES) {\n const from = `${legacy}${suffix}`;\n if (existsSync(from)) moveFile(from, `${target}${suffix}`);\n }\n } catch {\n // Best-effort — a migration failure must never break session storage.\n }\n}\n\n/** Moves a file, falling back to copy+unlink across a filesystem boundary. */\nfunction moveFile(from: string, to: string): void {\n try {\n renameSync(from, to);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EXDEV') throw err;\n copyFileSync(from, to);\n unlinkSync(from);\n }\n}\n","/**\n * Sandbox detection (#2501, child 1 of epic #2500).\n *\n * nexus-agents needs to know whether it's running inside a host-provided\n * sandbox (Docker Desktop Sandbox + OpenCode, Codex sandbox, locked-down\n * CI runner) so it can adjust behaviour:\n *\n * - default `NEXUS_DATA_DIR` to the multi-repo root rather than `~/...`\n * - skip CLI subprocess detection (binaries aren't there)\n * - fail-fast when the gateway is unreachable instead of degrading\n * - suppress diagnostics that don't apply\n *\n * The signal is **explicit**: the image author (Dockerfile, devcontainer,\n * harness wrapper) sets `NEXUS_SANDBOX=<flavor>`. The flavor string is\n * for diagnostics + per-flavor branching (`docker-opencode`, `codex`,\n * `claude-code`, `ci-restricted`, …); presence is the on/off signal.\n *\n * A heuristic check runs alongside for verification — if `NEXUS_SANDBOX`\n * claims `docker-opencode` but `/.dockerenv` is missing, that's a\n * misconfiguration worth surfacing to the operator.\n *\n * @module config/sandbox-detection\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\n\n/**\n * Heuristic detection result. Independent from the explicit\n * `NEXUS_SANDBOX` env var; produced by inspecting filesystem markers.\n */\nexport type SandboxHeuristic = 'docker' | 'podman' | 'unknown' | null;\n\nexport interface SandboxInfo {\n /**\n * True iff `NEXUS_SANDBOX` is set + non-empty. The explicit signal\n * the rest of the codebase keys off — never `true` by heuristic alone.\n */\n readonly active: boolean;\n /**\n * Operator-supplied flavor string, e.g. `docker-opencode`. Undefined\n * when `active === false` or the env var is empty.\n */\n readonly flavor: string | undefined;\n /**\n * Multi-repo root the user mounted. From `NEXUS_SANDBOX_ROOT`. Undefined\n * when unset; consumers that need a default substitute `/`.\n */\n readonly root: string | undefined;\n /**\n * Independent heuristic match — `null` when we couldn't run the check,\n * `'unknown'` when ran but no marker matched. Used by `doctor` to flag\n * mismatches between the explicit signal and the runtime environment.\n */\n readonly heuristicMatch: SandboxHeuristic;\n}\n\n/**\n * Detect whether nexus-agents is running inside a host-provided sandbox.\n * Pure read of env + filesystem; no caching (cheap enough to recompute,\n * and tests routinely mutate process.env).\n */\nexport function detectSandbox(): SandboxInfo {\n const flavorRaw = process.env['NEXUS_SANDBOX']?.trim();\n const flavor = flavorRaw !== undefined && flavorRaw !== '' ? flavorRaw : undefined;\n const active = flavor !== undefined;\n\n const rootRaw = process.env['NEXUS_SANDBOX_ROOT']?.trim();\n const root = rootRaw !== undefined && rootRaw !== '' ? rootRaw : undefined;\n\n return {\n active,\n flavor,\n root,\n heuristicMatch: detectContainerHeuristic(),\n };\n}\n\n/**\n * Look for filesystem markers indicating a container runtime. Used as a\n * cross-check against the explicit `NEXUS_SANDBOX` signal.\n *\n * Order of checks:\n * 1. `/.dockerenv` (Docker)\n * 2. `/run/.containerenv` (Podman)\n * 3. `/proc/1/cgroup` containing `docker` or `containerd` strings\n *\n * Returns `null` when none of the checks could run (non-Linux host with\n * no `/proc`, sandbox blocks `existsSync`, etc.) — distinct from\n * `'unknown'` (checks ran, no marker matched) so the doctor message can\n * differentiate \"we couldn't tell\" from \"we checked, no container\".\n */\nfunction detectContainerHeuristic(): SandboxHeuristic {\n try {\n if (existsSync('/.dockerenv')) return 'docker';\n if (existsSync('/run/.containerenv')) return 'podman';\n if (existsSync('/proc/1/cgroup')) {\n const cgroup = readFileSync('/proc/1/cgroup', 'utf8');\n if (/\\bdocker\\b/.test(cgroup)) return 'docker';\n if (/\\bcontainerd\\b/.test(cgroup)) return 'docker';\n }\n return 'unknown';\n } catch {\n return null;\n }\n}\n","/**\n * Repo-root detection — walks upward from a starting directory looking for\n * `.git` so callers can scope state to the current git repo. Built for\n * epic #2872 (issue #2882) to support the per-repo `.nexus-agents/` data\n * directory ratified by vote #2876.\n *\n * Defenses adopted:\n * - Walks upward but stops at filesystem root (no infinite loop on\n * symlinks-to-self).\n * - Stops at filesystem boundary (different `stat.dev`) — refuses to\n * escape across mount points. Prevents a sandboxed workdir from\n * resolving a `.git` on the host filesystem.\n * - Detects git worktrees: `.git` may be a *file* containing\n * `gitdir: <path>` rather than a directory.\n * - Realpath's the final root and rejects results that escape the\n * starting cwd by symlink.\n *\n * Deferred (tracked separately; not blocking #2882):\n * - CVE-2022-24765-style ownership check (refuse `.git` owned by a\n * different uid than the running process). The git CLI added\n * `safe.directory` for this; we don't have a comparable allowlist\n * surface yet. In CI the heuristic is too noisy to be useful\n * (runners often clone as a different uid than the workload).\n * File-system isolation (the `stat.dev` check above) covers the\n * bulk of the attack class.\n *\n * @module config/repo-root-detection\n */\n\nimport { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';\nimport { dirname, isAbsolute, resolve } from 'node:path';\n\n/**\n * Walks upward from `start` looking for `.git` (file or directory).\n * Returns the absolute path to the directory containing it, or `null`.\n *\n * Worktrees: a `.git` file (not dir) with contents `gitdir: <path>` is\n * still recognised as marking the repo root. The pointed-to gitdir is\n * NOT followed — we just need the worktree's own root.\n *\n * Filesystem boundary: stops walking if the next ancestor lives on a\n * different filesystem (different `stat.dev`). This prevents a workdir\n * mounted inside a sandbox from finding a host-side `.git`.\n */\nexport function findRepoRoot(start: string): string | null {\n if (!isAbsolute(start)) {\n start = resolve(start);\n }\n\n let current: string;\n try {\n current = realpathSync(start);\n } catch {\n return null;\n }\n\n let startDev: number | undefined;\n try {\n startDev = statSync(current).dev;\n } catch {\n return null;\n }\n\n // Walk up. Depth cap is paranoid but bounds the loop on pathological\n // symlinks even though realpathSync above should already prevent cycles.\n for (let i = 0; i < 64; i++) {\n if (isRepoRoot(current)) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n // Hit filesystem root.\n return null;\n }\n try {\n if (statSync(parent).dev !== startDev) {\n // Crossed a mount point; refuse to escape.\n return null;\n }\n } catch {\n return null;\n }\n current = parent;\n }\n return null;\n}\n\n/** True iff `dir/.git` exists as a directory or a git-worktree marker file. */\nexport function isRepoRoot(dir: string): boolean {\n const gitPath = `${dir}/.git`;\n if (!existsSync(gitPath)) return false;\n try {\n const st = statSync(gitPath);\n if (st.isDirectory()) return true;\n if (st.isFile()) {\n // Worktree: first line should be `gitdir: <path>`.\n const head = readFileSync(gitPath, 'utf-8').slice(0, 256);\n return head.startsWith('gitdir:');\n }\n } catch {\n return false;\n }\n return false;\n}\n","/**\n * portable-mode — auto-detect sandboxed execution environments and\n * redirect nexus-agents data to a workspace-local directory.\n *\n * The existing `getNexusDataDir()` already respects `NEXUS_DATA_DIR` and\n * `NEXUS_PORTABLE_MODE`. This module wires up the *auto-detection* path\n * so operators running inside a Docker / restricted-FS / sandboxed\n * environment don't have to set the env vars manually — nexus-agents\n * notices and announces the flip.\n *\n * Source: Issue #2471 (epic #2467 child).\n *\n * Detection rules (first match wins):\n * 1. `NEXUS_DATA_DIR` already set → respect, no auto-detect\n * 2. `NEXUS_PORTABLE_MODE=0` → operator opted out, no auto-detect\n * 3. `NEXUS_PORTABLE_MODE=1` → portable mode, no detection needed\n * 4. Heuristic: home directory not writable\n * 5. Heuristic: container env vars set (KUBERNETES_SERVICE_HOST,\n * DOCKER_CONTAINER, ECS_CONTAINER_METADATA_URI, SANDBOX, etc.)\n *\n * When portable mode fires:\n * - Sets `process.env.NEXUS_DATA_DIR` to `<cwd>/.nexus-agents`\n * - Announces once on stderr (subsequent calls are silent)\n * - If cwd is a git repo, appends `.nexus-agents/` to .gitignore\n *\n * To force off: `NEXUS_PORTABLE_MODE=0`. To force on without heuristics:\n * `NEXUS_PORTABLE_MODE=1`. To override target dir: `NEXUS_DATA_DIR=...`.\n */\n\nimport { accessSync, constants, existsSync, readFileSync, appendFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\n\n/** Container / sandbox environment variables that strongly imply portable mode. */\nconst SANDBOX_ENV_VARS: readonly string[] = [\n 'KUBERNETES_SERVICE_HOST',\n 'DOCKER_CONTAINER',\n 'ECS_CONTAINER_METADATA_URI',\n 'ECS_CONTAINER_METADATA_URI_V4',\n 'SANDBOX',\n 'NEXUS_SANDBOX',\n];\n\nlet DETECTED = false;\n\ninterface DetectionResult {\n readonly portable: boolean;\n readonly dataDir: string;\n readonly reason:\n 'env-data-dir' | 'env-opt-out' | 'env-opt-in' | 'home-unwritable' | 'container-env' | 'default';\n}\n\nfunction isHomeWritable(): boolean {\n try {\n const home = homedir();\n if (!existsSync(home)) return false;\n accessSync(home, constants.W_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction inSandboxEnv(): boolean {\n for (const v of SANDBOX_ENV_VARS) {\n const val = process.env[v];\n if (val !== undefined && val !== '') return true;\n }\n return false;\n}\n\n/**\n * The operator-declared sandbox root, when set (#5026).\n *\n * Read directly rather than through `detectSandbox()` to keep this module free\n * of imports — it runs as a side effect of `cli-log-bootstrap` before anything\n * else loads.\n */\nfunction sandboxRootFromEnv(): string | undefined {\n const raw = process.env['NEXUS_SANDBOX_ROOT']?.trim();\n return raw !== undefined && raw !== '' ? resolve(raw) : undefined;\n}\n\nfunction checkExplicitEnv(cwd: string): DetectionResult | null {\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') {\n return { portable: false, dataDir: resolve(fromEnv), reason: 'env-data-dir' };\n }\n const portableEnv = process.env['NEXUS_PORTABLE_MODE'];\n if (portableEnv === '0' || portableEnv === 'false') {\n return { portable: false, dataDir: join(homedir(), '.nexus-agents'), reason: 'env-opt-out' };\n }\n if (portableEnv === '1' || portableEnv === 'true') {\n return { portable: true, dataDir: join(cwd, '.nexus-agents'), reason: 'env-opt-in' };\n }\n return null;\n}\n\nfunction checkHeuristics(cwd: string): DetectionResult | null {\n if (!isHomeWritable()) {\n return { portable: true, dataDir: join(cwd, '.nexus-agents'), reason: 'home-unwritable' };\n }\n if (inSandboxEnv()) {\n // #5026: prefer an explicitly declared sandbox root over cwd. `NEXUS_SANDBOX`\n // is one of SANDBOX_ENV_VARS, so setting it makes this heuristic fire and\n // stamp `NEXUS_DATA_DIR = <cwd>/.nexus-agents` — which then short-circuits\n // `getNexusDataDir`'s own sandbox branch (`nexus-data-dir.ts:124-127`)\n // before it can honour `NEXUS_SANDBOX_ROOT`. The documented purpose of that\n // variable (\"default NEXUS_DATA_DIR to the multi-repo root\") therefore never\n // happened, and state fragmented per working directory across a multi-repo\n // mount. `doctor`'s dataDirInsideRepo check exists to detect exactly that\n // layout and reports it as operator misconfiguration.\n const declaredRoot = sandboxRootFromEnv();\n return {\n portable: true,\n dataDir: join(declaredRoot ?? cwd, '.nexus-agents'),\n reason: 'container-env',\n };\n }\n return null;\n}\n\n/**\n * Decide whether to flip nexus-agents to portable mode and what data dir\n * to use. Pure function; no side effects beyond reading env + filesystem.\n */\nexport function detectPortableMode(cwd: string = process.cwd()): DetectionResult {\n return (\n checkExplicitEnv(cwd) ??\n checkHeuristics(cwd) ?? {\n portable: false,\n dataDir: join(homedir(), '.nexus-agents'),\n reason: 'default',\n }\n );\n}\n\n/**\n * Apply detected portable mode at process startup. Idempotent (subsequent\n * calls are no-ops). Announces on stderr once when the auto-detection\n * heuristic triggers — env-opt-in/out doesn't announce because the\n * operator already knows.\n */\nexport function applyPortableMode(cwd: string = process.cwd()): void {\n if (DETECTED) return;\n DETECTED = true;\n\n const result = detectPortableMode(cwd);\n\n // No env, no heuristic match — nothing to do.\n if (!result.portable) return;\n\n // The heuristic-driven cases announce; explicit opt-in is silent.\n if (result.reason === 'home-unwritable' || result.reason === 'container-env') {\n process.stderr.write(\n `[portable-mode] Sandbox detected (${result.reason}). Using ${result.dataDir} for all nexus-agents data.\\n` +\n ` Set NEXUS_PORTABLE_MODE=0 to override; see docs/guides/SANDBOXED-USAGE.md\\n`\n );\n }\n\n process.env['NEXUS_DATA_DIR'] = result.dataDir;\n\n if (isInsideGitRepo(cwd)) {\n ensureGitignored(cwd, '.nexus-agents/');\n }\n}\n\nfunction isInsideGitRepo(cwd: string): boolean {\n try {\n const gitDir = join(cwd, '.git');\n return existsSync(gitDir) && statSync(gitDir).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Append `entry` to `<cwd>/.gitignore` if not already present. Stderr\n * announce on the first append. Exported because the repo-preferred\n * resolver in `nexus-data-dir.ts` reuses the same auto-gitignore behavior\n * when `<repo>/.nexus-agents/` becomes the active data dir (epic #2872).\n */\nexport function ensureGitignored(cwd: string, entry: string): void {\n const path = join(cwd, '.gitignore');\n try {\n const existing = existsSync(path) ? readFileSync(path, 'utf-8') : '';\n const lines = existing.split('\\n').map((l) => l.trim());\n if (lines.includes(entry) || lines.includes(entry.replace(/\\/$/, ''))) return;\n const sep = existing.length > 0 && !existing.endsWith('\\n') ? '\\n' : '';\n appendFileSync(path, `${sep}${entry}\\n`, 'utf-8');\n process.stderr.write(\n `[portable-mode] Added '${entry}' to ${path} so nexus-agents data is gitignored.\\n`\n );\n } catch (e: unknown) {\n // Non-fatal — operator can add the line themselves.\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(\n `[portable-mode] Could not auto-update ${path} (${msg}). Add '${entry}' manually if you don't want nexus-agents data tracked by git.\\n`\n );\n }\n}\n\n/** Test-only reset of the once-only state. */\nexport function _resetDetectedForTests(): void {\n DETECTED = false;\n}\n"],"mappings":"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","names":["accessSync","existsSync","realpathSync","statSync","homedir","dirname","isAbsolute","join","resolve","existsSync","readFileSync","existsSync","readFileSync","statSync","resolve","resolve","join","homedir","realpathSync","resolve","isAbsolute","statSync","join","existsSync","accessSync","join","dirname","existsSync","dirname"]}
@@ -3,10 +3,10 @@ import {
3
3
  getDecisionCostFile,
4
4
  getModelSelectionShadowFile,
5
5
  isPersistenceEnabled
6
- } from "./chunk-HHLCAWAV.js";
6
+ } from "./chunk-GBHJ37V3.js";
7
7
  import {
8
8
  nexusDataPath
9
- } from "./chunk-NBMWONHE.js";
9
+ } from "./chunk-OGHEKPLD.js";
10
10
  import {
11
11
  __require
12
12
  } from "./chunk-ZPWHCABL.js";
@@ -2623,7 +2623,7 @@ function setDefaultRegistry(registry) {
2623
2623
  }
2624
2624
  async function reloadDefaultRegistry() {
2625
2625
  globalRegistry = buildDefaultRegistry();
2626
- const { resetGlobalRegistry } = await import("./unified-registry-H2NNOXB3.js");
2626
+ const { resetGlobalRegistry } = await import("./unified-registry-MUXCSSG5.js");
2627
2627
  resetGlobalRegistry();
2628
2628
  return globalRegistry;
2629
2629
  }
@@ -10213,7 +10213,7 @@ async function attachOutcomeStoreToRegistry(store) {
10213
10213
  try {
10214
10214
  const { getMemoryRegistry } = await import("./dist-IKZ53APZ.js");
10215
10215
  const { OutcomeStoreAdapter } = await import("./outcome-store-adapter-K4L3RUJE.js");
10216
- const { ensureSharedMemoryRegistry } = await import("./tool-memory-registry-adapters-UMPIJLHS.js");
10216
+ const { ensureSharedMemoryRegistry } = await import("./tool-memory-registry-adapters-IMRSOAAV.js");
10217
10217
  ensureSharedMemoryRegistry();
10218
10218
  getMemoryRegistry().attach("outcomes", new OutcomeStoreAdapter(store));
10219
10219
  } catch {
@@ -15118,7 +15118,7 @@ var CompositeRouter = class _CompositeRouter {
15118
15118
  */
15119
15119
  async consultUnifiedContext(task) {
15120
15120
  try {
15121
- const { getContextForTask, inferTaskCategory } = await import("./context-retriever-BROUADMF.js");
15121
+ const { getContextForTask, inferTaskCategory } = await import("./context-retriever-L2LTCKV6.js");
15122
15122
  const ctx = await getContextForTask({
15123
15123
  task: task.content,
15124
15124
  category: inferTaskCategory(task.content),
@@ -16711,4 +16711,4 @@ export {
16711
16711
  AgentCapability,
16712
16712
  OrchestratorError
16713
16713
  };
16714
- //# sourceMappingURL=chunk-TBWKMKCX.js.map
16714
+ //# sourceMappingURL=chunk-OYFKB64X.js.map
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  loadPapersRegistry
3
- } from "./chunk-EPOKODTI.js";
3
+ } from "./chunk-YVW743K3.js";
4
4
  import {
5
5
  getErrorMessage
6
- } from "./chunk-TBWKMKCX.js";
6
+ } from "./chunk-OYFKB64X.js";
7
7
 
8
8
  // src/cli/research-helpers-synthesize.ts
9
9
  import { z } from "zod";
@@ -767,11 +767,11 @@ function synthesizeCluster(cluster) {
767
767
  );
768
768
  }
769
769
  return {
770
+ ...insightFields(cluster.papers),
770
771
  topic: cluster.topic,
771
772
  paperCount: cluster.paperCount,
772
773
  papers: cluster.papers.map(toPaperRef),
773
774
  commonThemes,
774
- keyInsights: attributeFindings(cluster.papers).slice(0, 10),
775
775
  techniques,
776
776
  implementationOpportunities: uniqueOpportunities,
777
777
  gaps,
@@ -779,6 +779,11 @@ function synthesizeCluster(cluster) {
779
779
  qualityDistribution
780
780
  };
781
781
  }
782
+ var MAX_CLUSTER_INSIGHTS = 10;
783
+ function insightFields(papers) {
784
+ const all = attributeFindings(papers);
785
+ return { keyInsights: all.slice(0, MAX_CLUSTER_INSIGHTS), totalInsights: all.length };
786
+ }
782
787
  function findCrossCuttingThemes(clusters) {
783
788
  const tagToTopics = /* @__PURE__ */ new Map();
784
789
  for (const cluster of clusters) {
@@ -876,4 +881,4 @@ export {
876
881
  AttributedInsightSchema,
877
882
  synthesizeResearch
878
883
  };
879
- //# sourceMappingURL=chunk-NQ65EKMG.js.map
884
+ //# sourceMappingURL=chunk-PUGRS4KW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/research-helpers-synthesize.ts","../src/cli/research-alignment-map.ts","../src/research/topic-aliases.ts"],"sourcesContent":["/**\n * Research Synthesis Helper\n *\n * Groups papers by topic cluster and generates structured synthesis\n * summaries with themes, findings, and implementation opportunities.\n *\n * @module cli/research-helpers-synthesize\n * (Source: Issue #1386 — Research Synthesis Pipeline)\n */\n\nimport { z } from 'zod';\nimport { loadPapersRegistry } from './research-helpers-io.js';\nimport type { PapersRegistry } from './research-types.js';\nimport type { Result } from '../core/result.js';\nimport { getErrorMessage } from '../core/index.js';\nimport { TECHNIQUE_IMPLEMENTATION_MAP, FEATURE_GATE_INVENTORY } from './research-alignment-map.js';\nimport { normalizeTopicToCanonical } from '../research/topic-aliases.js';\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\n/** A paper entry from the registry, simplified for synthesis. */\nexport interface SynthesisPaper {\n readonly id: string;\n readonly title: string;\n readonly topics: readonly string[];\n readonly tags: readonly string[];\n readonly summary: string;\n readonly keyFindings: readonly string[];\n readonly relevance: string;\n readonly implementationStatus: string;\n readonly techniquesExtracted: readonly string[];\n readonly qualityScore: number;\n readonly evidenceTier: 'high' | 'medium' | 'low';\n /** Source URI — the paper URL, or `arxiv:<id>` (#2663 provenance). */\n readonly sourceUri?: string;\n /** Publication date as recorded in the registry (#2663 provenance). */\n readonly publicationDate?: string;\n}\n\n/**\n * A reference to a source paper, carried into synthesis output so a\n * cluster is traceable back to its inputs (#2663).\n */\nexport interface SynthesisPaperRef {\n readonly id: string;\n readonly title: string;\n readonly sourceUri?: string;\n}\n\n/**\n * A synthesized insight with its provenance (#2663). `sourcePaperIds` is\n * never empty — every merged claim is attributed. When two papers assert\n * the same finding, BOTH ids appear, so a contradiction is *representable*\n * as multiple attributed sources rather than silently collapsed into one.\n */\nexport interface AttributedInsight {\n readonly insight: string;\n readonly sourcePaperIds: readonly string[];\n}\n\n/**\n * Structural enforcement of the #2663 provenance invariant: every\n * synthesized insight must carry at least one source paper id. A doc rule\n * alone is fragile — this Zod schema makes \"no unattributed claim\" a\n * validated guarantee, not a hope.\n */\nexport const AttributedInsightSchema = z.object({\n insight: z.string().min(1),\n sourcePaperIds: z.array(z.string().min(1)).min(1),\n});\n\n/** A cluster of related papers grouped by topic. */\nexport interface PaperCluster {\n readonly topic: string;\n readonly papers: readonly SynthesisPaper[];\n readonly paperCount: number;\n}\n\n/** A technique aligned to an existing implementation. */\nexport interface TechniqueAlignment {\n readonly technique: string;\n readonly status: 'implemented' | 'partial' | 'not-started';\n readonly canonicalPath?: string | undefined;\n readonly improvementHint?: string | undefined;\n}\n\n/** Quality distribution within a cluster. */\nexport interface QualityDistribution {\n readonly avgScore: number;\n readonly high: number;\n readonly medium: number;\n readonly low: number;\n}\n\n/** Synthesis output for a single topic cluster. */\nexport interface ClusterSynthesis {\n readonly topic: string;\n readonly paperCount: number;\n readonly papers: readonly SynthesisPaperRef[];\n readonly commonThemes: readonly string[];\n readonly keyInsights: readonly AttributedInsight[];\n /**\n * How many attributed findings this cluster actually had (#5001).\n *\n * `keyInsights` is capped, and the cap bites: against the live registry six\n * of eleven clusters exceed it, `orchestration` with 55. A caller seeing\n * `paperCount: 40` beside ten insights could not tell \"these are the\n * cluster's insights\" from \"these are ten of fifty-five\". A bounded read is\n * fine; a bounded read reported as complete is not.\n */\n readonly totalInsights: number;\n readonly techniques: readonly string[];\n readonly implementationOpportunities: readonly string[];\n readonly gaps: readonly string[];\n readonly alignedTechniques: readonly TechniqueAlignment[];\n readonly qualityDistribution: QualityDistribution;\n}\n\n/** Summary of alignment between research and implementation. */\nexport interface AlignmentSummary {\n readonly implemented: number;\n readonly partial: number;\n readonly notStarted: number;\n readonly total: number;\n readonly topOpportunities: readonly string[];\n}\n\n/** Summary of a single feature gate for synthesis output. */\nexport interface FeatureGateStatus {\n readonly envVar: string;\n readonly defaultValue: string;\n readonly description: string;\n readonly linkedTechniqueCount: number;\n}\n\n/** Full synthesis result across all clusters. */\nexport interface SynthesisResult {\n readonly clusters: readonly ClusterSynthesis[];\n readonly totalPapers: number;\n readonly topicCount: number;\n readonly crossCuttingThemes: readonly string[];\n readonly alignmentSummary: AlignmentSummary;\n readonly featureGates: readonly FeatureGateStatus[];\n}\n\n/** Error for synthesis operations. */\nexport interface SynthesisError {\n readonly code: 'LOAD_ERROR' | 'NO_PAPERS';\n readonly message: string;\n}\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\n/** Minimum papers in a cluster to generate cross-cutting themes. */\nconst MIN_CROSS_CUTTING_CLUSTER_SIZE = 2;\n\n/** Tags that appear across many topics and indicate cross-cutting themes. */\nconst CROSS_CUTTING_TAG_THRESHOLD = 3;\n\n// =============================================================================\n// PUBLIC API\n// =============================================================================\n\n/**\n * Synthesize the research registry by grouping papers into topic clusters\n * and generating structured summaries.\n *\n * @param topicFilter - Optional topic to filter to a single cluster\n * @returns Structured synthesis result\n */\nexport async function synthesizeResearch(\n topicFilter?: string\n): Promise<Result<SynthesisResult, SynthesisError>> {\n const registryResult = await loadPapersRegistry();\n if (!registryResult.ok) {\n return {\n ok: false,\n error: {\n code: 'LOAD_ERROR',\n message: `Failed to load papers: ${getErrorMessage(registryResult.error)}`,\n },\n };\n }\n\n const papers = extractPapers(registryResult.value);\n if (papers.length === 0) {\n return {\n ok: false,\n error: { code: 'NO_PAPERS', message: 'No papers found in registry' },\n };\n }\n\n const clusters = groupByTopic(papers);\n const filtered =\n topicFilter !== undefined ? clusters.filter((c) => c.topic === topicFilter) : clusters;\n\n const syntheses = filtered.map(synthesizeCluster);\n const crossCutting = findCrossCuttingThemes(filtered);\n const alignmentSummary = buildAlignmentSummary(syntheses);\n const featureGates = buildFeatureGateSummary();\n\n return {\n ok: true,\n value: {\n clusters: syntheses,\n totalPapers: papers.length,\n topicCount: filtered.length,\n crossCuttingThemes: crossCutting,\n alignmentSummary,\n featureGates,\n },\n };\n}\n\n// =============================================================================\n// EXTRACTION\n// =============================================================================\n\n/**\n * Safely convert a field that types say is `readonly string[]` but may be\n * null/undefined at runtime (YAML parsing of empty array fields).\n */\nfunction safeArray(value: readonly string[]): string[] {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- YAML parsing can yield null despite type\n if (value === null || value === undefined) return [];\n return Array.from(value);\n}\n\n/** Extract papers from the registry structure. */\n/** Source URI for a paper: prefer the URL, else `arxiv:<id>` (#2663). */\nfunction paperSourceUri(p: PapersRegistry['papers'][string]): string | undefined {\n if (typeof p.url === 'string' && p.url.length > 0) return p.url;\n if (typeof p.arxiv_id === 'string' && p.arxiv_id.length > 0) return `arxiv:${p.arxiv_id}`;\n return undefined;\n}\n\nfunction extractPapers(registry: PapersRegistry): SynthesisPaper[] {\n return Object.entries(registry.papers).map(([id, p]) => {\n const sourceUri = paperSourceUri(p);\n return {\n id,\n title: p.title,\n topics: safeArray(p.topics),\n tags: safeArray(p.tags),\n summary: p.summary.trim(),\n keyFindings: safeArray(p.key_findings),\n relevance: p.relevance,\n implementationStatus: p.implementation_status,\n techniquesExtracted: safeArray(p.techniques_extracted),\n qualityScore: p.quality_score ?? 0,\n evidenceTier: p.evidence_tier ?? 'low',\n // #2663 — carry provenance from the registry into synthesis.\n ...(sourceUri !== undefined ? { sourceUri } : {}),\n ...(typeof p.publication_date === 'string' ? { publicationDate: p.publication_date } : {}),\n };\n });\n}\n\n// =============================================================================\n// GROUPING\n// =============================================================================\n\n/** Group papers by primary topic into clusters, normalizing free-form topics. */\nfunction groupByTopic(papers: readonly SynthesisPaper[]): PaperCluster[] {\n const topicMap = new Map<string, SynthesisPaper[]>();\n\n for (const paper of papers) {\n for (const rawTopic of paper.topics) {\n const topic = normalizeTopicToCanonical(rawTopic);\n const existing = topicMap.get(topic);\n if (existing !== undefined) {\n existing.push(paper);\n } else {\n topicMap.set(topic, [paper]);\n }\n }\n }\n\n const clusters: PaperCluster[] = [];\n for (const [topic, unsortedPapers] of topicMap) {\n // Sort papers within cluster by quality_score descending (high-quality first)\n const topicPapers = [...unsortedPapers].sort((a, b) => b.qualityScore - a.qualityScore);\n clusters.push({\n topic,\n papers: topicPapers,\n paperCount: topicPapers.length,\n });\n }\n\n return clusters.sort((a, b) => b.paperCount - a.paperCount);\n}\n\n// =============================================================================\n// SYNTHESIS\n// =============================================================================\n\n/** Generate synthesis for a single topic cluster. */\n/** Compute quality score distribution for a set of papers. */\nfunction computeClusterQuality(papers: readonly SynthesisPaper[]): QualityDistribution {\n const scores = papers.map((p) => p.qualityScore);\n const totalScore = scores.reduce((sum, s) => sum + s, 0);\n return {\n avgScore: scores.length > 0 ? Math.round((totalScore / scores.length) * 10) / 10 : 0,\n high: papers.filter((p) => p.evidenceTier === 'high').length,\n medium: papers.filter((p) => p.evidenceTier === 'medium').length,\n low: papers.filter((p) => p.evidenceTier === 'low').length,\n };\n}\n\nfunction synthesizeCluster(cluster: PaperCluster): ClusterSynthesis {\n const allTags = collectFrequencies(cluster.papers.flatMap((p) => [...p.tags]));\n const allTechniques = collectFrequencies(\n cluster.papers.flatMap((p) => [...p.techniquesExtracted])\n );\n\n // Common themes: tags that appear in 2+ papers\n const commonThemes = [...allTags.entries()]\n .filter(([, count]) => count >= MIN_CROSS_CUTTING_CLUSTER_SIZE)\n .sort((a, b) => b[1] - a[1])\n .map(([tag]) => tag);\n\n // Techniques across the cluster\n const techniques = [...allTechniques.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([tech, count]) => (count > 1 ? `${tech} (${String(count)} papers)` : tech));\n\n // Implementation opportunities: techniques not yet implemented\n const notStarted = cluster.papers\n .filter((p) => p.implementationStatus === 'not-started')\n .flatMap((p) => [...p.techniquesExtracted]);\n const uniqueOpportunities = [...new Set(notStarted)];\n\n // Gaps: topics with few papers or missing techniques\n const gaps: string[] = [];\n if (cluster.paperCount === 1) {\n gaps.push(`Only 1 paper — more research needed on ${cluster.topic}`);\n }\n const highRelevanceUnimplemented = cluster.papers.filter(\n (p) => p.relevance === 'high' && p.implementationStatus === 'not-started'\n );\n if (highRelevanceUnimplemented.length > 0) {\n gaps.push(\n `${String(highRelevanceUnimplemented.length)} high-relevance paper(s) not yet implemented`\n );\n }\n\n // Alignment analysis: map techniques to existing implementations\n const allTechNames = [...allTechniques.keys()];\n const alignedTechniques = analyzeClusterAlignment(allTechNames);\n\n const qualityDistribution = computeClusterQuality(cluster.papers);\n\n // Add quality gap if most papers are low-quality\n if (qualityDistribution.low > qualityDistribution.high + qualityDistribution.medium) {\n gaps.push(\n `Mostly low-evidence papers (${String(qualityDistribution.low)}/${String(cluster.paperCount)}) — findings need stronger validation`\n );\n }\n\n return {\n ...insightFields(cluster.papers),\n topic: cluster.topic,\n paperCount: cluster.paperCount,\n papers: cluster.papers.map(toPaperRef),\n commonThemes,\n techniques,\n implementationOpportunities: uniqueOpportunities,\n gaps,\n alignedTechniques,\n qualityDistribution,\n };\n}\n\n/** Insights carried per cluster. The count dropped is disclosed, not silent. */\nconst MAX_CLUSTER_INSIGHTS = 10;\n\n/**\n * The capped insight list plus the count it was capped from (#5001).\n *\n * Returned together so the two can never drift: a `keyInsights` without its\n * `totalInsights` is the silent truncation this replaced.\n */\nfunction insightFields(papers: readonly SynthesisPaper[]): {\n keyInsights: readonly AttributedInsight[];\n totalInsights: number;\n} {\n const all = attributeFindings(papers);\n return { keyInsights: all.slice(0, MAX_CLUSTER_INSIGHTS), totalInsights: all.length };\n}\n\n/** Find themes that span multiple topic clusters. */\nfunction findCrossCuttingThemes(clusters: readonly PaperCluster[]): string[] {\n const tagToTopics = new Map<string, Set<string>>();\n\n for (const cluster of clusters) {\n for (const paper of cluster.papers) {\n for (const tag of paper.tags) {\n const existing = tagToTopics.get(tag);\n if (existing !== undefined) {\n existing.add(cluster.topic);\n } else {\n tagToTopics.set(tag, new Set([cluster.topic]));\n }\n }\n }\n }\n\n return [...tagToTopics.entries()]\n .filter(([, topics]) => topics.size >= CROSS_CUTTING_TAG_THRESHOLD)\n .sort((a, b) => b[1].size - a[1].size)\n .map(([tag, topics]) => `${tag} (spans: ${[...topics].join(', ')})`);\n}\n\n// =============================================================================\n// ALIGNMENT ANALYSIS\n// =============================================================================\n\n/** Analyze technique alignment for a cluster. */\nfunction analyzeClusterAlignment(techniques: readonly string[]): TechniqueAlignment[] {\n const alignments: TechniqueAlignment[] = [];\n\n for (const tech of techniques) {\n // Strip count annotation like \"technique (2 papers)\"\n const baseTech = tech.replace(/\\s*\\(\\d+\\s*papers?\\)$/, '');\n const mapping = TECHNIQUE_IMPLEMENTATION_MAP.get(baseTech);\n\n if (mapping !== undefined) {\n alignments.push({\n technique: baseTech,\n status: mapping.status,\n canonicalPath: mapping.path,\n ...(mapping.hint !== undefined ? { improvementHint: mapping.hint } : {}),\n });\n } else {\n alignments.push({ technique: baseTech, status: 'not-started' });\n }\n }\n\n return alignments;\n}\n\n/** Build alignment summary across all clusters. */\nfunction buildAlignmentSummary(clusters: readonly ClusterSynthesis[]): AlignmentSummary {\n const allAlignments = clusters.flatMap((c) => c.alignedTechniques);\n const implemented = allAlignments.filter((a) => a.status === 'implemented').length;\n const partial = allAlignments.filter((a) => a.status === 'partial').length;\n const notStarted = allAlignments.filter((a) => a.status === 'not-started').length;\n\n // Top opportunities: partial implementations with hints (most improvable)\n const opportunities = allAlignments\n .filter((a) => a.status === 'partial' && a.improvementHint !== undefined)\n .map((a) => `${a.technique}: ${a.improvementHint ?? ''}`)\n .slice(0, 10);\n\n return {\n implemented,\n partial,\n notStarted,\n total: allAlignments.length,\n topOpportunities: opportunities,\n };\n}\n\n/** Build feature gate summary from the inventory. */\nfunction buildFeatureGateSummary(): FeatureGateStatus[] {\n return FEATURE_GATE_INVENTORY.map((g) => ({\n envVar: g.envVar,\n defaultValue: g.defaultValue,\n description: g.description,\n linkedTechniqueCount: g.techniques?.length ?? 0,\n }));\n}\n\n// =============================================================================\n// UTILITIES\n// =============================================================================\n\nfunction collectFrequencies(items: readonly string[]): Map<string, number> {\n const counts = new Map<string, number>();\n for (const item of items) {\n counts.set(item, (counts.get(item) ?? 0) + 1);\n }\n return counts;\n}\n\n/** Project a source paper to the lean reference carried into output (#2663). */\nfunction toPaperRef(p: SynthesisPaper): SynthesisPaperRef {\n return {\n id: p.id,\n title: p.title,\n ...(p.sourceUri !== undefined ? { sourceUri: p.sourceUri } : {}),\n };\n}\n\n/**\n * Deduplicate findings across a cluster's papers WHILE preserving\n * provenance (#2663). Findings are keyed by normalized text; when two\n * papers assert the same finding, both ids are collected — so a\n * contradiction is representable as multiple attributed sources rather\n * than silently collapsed. Every returned insight is validated to carry\n * at least one source id (`AttributedInsightSchema`).\n */\nfunction attributeFindings(papers: readonly SynthesisPaper[]): AttributedInsight[] {\n const byNormalized = new Map<string, { insight: string; sourcePaperIds: Set<string> }>();\n for (const paper of papers) {\n for (const finding of paper.keyFindings) {\n const normalized = finding.toLowerCase().trim();\n if (normalized.length === 0) continue;\n const existing = byNormalized.get(normalized);\n if (existing !== undefined) {\n existing.sourcePaperIds.add(paper.id);\n } else {\n byNormalized.set(normalized, {\n insight: finding,\n sourcePaperIds: new Set([paper.id]),\n });\n }\n }\n }\n return [...byNormalized.values()].map((e) =>\n AttributedInsightSchema.parse({ insight: e.insight, sourcePaperIds: [...e.sourcePaperIds] })\n );\n}\n","/**\n * Research Alignment Map\n *\n * Maps technique names from the research registry to canonical code paths\n * in the nexus-agents codebase. Used by research synthesis to determine\n * which paper techniques are implemented, partially implemented, or new.\n *\n * @module cli/research-alignment-map\n * (Source: Issue #1386 — Research Synthesis Pipeline)\n */\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\n/** Implementation status for a mapped technique. */\nexport interface TechniqueMapping {\n readonly status: 'implemented' | 'partial';\n readonly path: string;\n readonly hint?: string | undefined;\n}\n\n/** A feature gate that controls an implementation. */\nexport interface FeatureGate {\n /** Environment variable or config key. */\n readonly envVar: string;\n /** Default value when not explicitly set. */\n readonly defaultValue: string;\n /** Brief description of what the gate controls. */\n readonly description: string;\n /** Related technique names from the alignment map (if any). */\n readonly techniques?: readonly string[];\n}\n\n// =============================================================================\n// MAP\n// =============================================================================\n\n/**\n * Maps technique names to canonical code paths.\n * 'implemented' = full implementation exists matching the technique.\n * 'partial' = related code exists but doesn't fully implement the technique.\n */\nexport const TECHNIQUE_IMPLEMENTATION_MAP: ReadonlyMap<string, TechniqueMapping> = new Map([\n // Routing techniques\n ['linucb-routing', { status: 'implemented', path: 'cli-adapters/linucb-bandit.ts' }],\n ['topsis-routing', { status: 'implemented', path: 'cli-adapters/topsis-router.ts' }],\n [\n 'cascade-routing',\n { status: 'implemented', path: 'routing/stages/confidence-cascade-stage.ts' },\n ],\n [\n 'complexity-based-routing',\n { status: 'implemented', path: 'routing/stages/confidence-cascade-stage.ts' },\n ],\n [\n 'moe-routing',\n {\n status: 'implemented',\n path: 'cli-adapters/composite-router.ts',\n hint: '5-stage composite routing selects among specialized CLI experts based on task features',\n },\n ],\n ['two-stage-routing', { status: 'implemented', path: 'cli-adapters/composite-router-stages.ts' }],\n [\n 'capability-instruction-tuning',\n {\n status: 'partial',\n path: 'routing/stages/capability-match-stage.ts',\n hint: 'Static capability profiles, not instruction-tuned',\n },\n ],\n [\n 'knn-routing',\n {\n status: 'implemented',\n path: 'routing/stages/knn-routing-stage.ts',\n hint: 'KnnRoutingStage: cosine similarity over keyword vectors, K-nearest experience patterns, weighted by success rate',\n },\n ],\n ['pilot-budget-routing', { status: 'implemented', path: 'cli-adapters/budget-router.ts' }],\n [\n 'tolerance-routing',\n {\n status: 'implemented',\n path: 'cli-adapters/composite-router-helpers.ts',\n hint: 'TOPSIS_TOLERANCE_BAND_PERCENT (5%) identifies quality-equivalent candidates after TOPSIS ranking',\n },\n ],\n [\n 'sater-routing',\n {\n status: 'implemented',\n path: 'routing/stages/quality-constraint-stage.ts',\n hint: 'QualityConstraintStage filters models by min quality (0.7), max cost, max latency before selection',\n },\n ],\n [\n 'preference-trained-routing',\n { status: 'implemented', path: 'cli-adapters/preference-router.ts' },\n ],\n [\n 'strmac-state-routing',\n {\n status: 'implemented',\n path: 'context/routing-memory.ts',\n hint: 'RoutingMemory maintains state across calls (profile, experience, action cache) feeding LinUCB + weather bonus stages',\n },\n ],\n [\n 'cross-attention-routing',\n {\n status: 'implemented',\n path: 'routing/stages/capability-match-stage.ts',\n hint: 'AttentionMatrix maps task features to capability weights (configurable cross-attention)',\n },\n ],\n\n // Consensus techniques\n ['consensus-protocol', { status: 'implemented', path: 'consensus/engine.ts' }],\n ['majority-voting', { status: 'implemented', path: 'consensus/engine.ts' }],\n ['higher-order-voting', { status: 'implemented', path: 'consensus/higher-order-strategy.ts' }],\n [\n 'anti-conformity-weighting',\n { status: 'implemented', path: 'consensus/higher-order-strategy.ts' },\n ],\n [\n 'agreement-based-cascading',\n {\n status: 'implemented',\n path: 'consensus/engine.ts',\n hint: 'canCascadeEarly() closes proposals when outcome is mathematically determined',\n },\n ],\n [\n 'incremental-quorum',\n {\n status: 'implemented',\n path: 'consensus/incremental-quorum.ts',\n hint: 'Ambiguity detection + voter expansion via VoterExpansionCallback (#1408)',\n },\n ],\n [\n 'aegean-consensus',\n { status: 'partial', path: 'consensus/engine.ts', hint: 'Byzantine tolerance not implemented' },\n ],\n [\n 'cp-wbft-consensus',\n {\n status: 'partial',\n path: 'consensus/engine.ts',\n hint: 'No weighted Byzantine fault tolerance',\n },\n ],\n\n // Memory techniques\n ['adaptive-memory', { status: 'implemented', path: 'memory/adaptive/' }],\n ['mirix-six-type-memory', { status: 'implemented', path: 'memory/typed/' }],\n [\n 'mem0-memory-architecture',\n { status: 'partial', path: 'memory/', hint: 'Multi-backend memory but not mem0 pattern' },\n ],\n [\n 'graph-based-memory',\n {\n status: 'partial',\n path: 'memory/belief/',\n hint: 'Belief triples are graph-like but not full graph DB',\n },\n ],\n ['experience-memory', { status: 'implemented', path: 'memory/session/' }],\n ['hindsight-belief-memory', { status: 'implemented', path: 'memory/belief/' }],\n [\n 'profile-memory',\n {\n status: 'implemented',\n path: 'context/routing-memory.ts',\n hint: 'RoutingMemory stores model preferences per task type and feeds recommendations into composite router via PERSISTENCE_AWARE_FLAGS',\n },\n ],\n [\n 'action-memory',\n {\n status: 'implemented',\n path: 'orchestration/outcomes/',\n hint: 'OutcomeStore records actions+results+context with query-by-category/CLI/time and cross-session persistence',\n },\n ],\n ['reflection-memory', { status: 'implemented', path: 'mcp/tools/orchestrate-reflection.ts' }],\n [\n 'history-encoding',\n {\n status: 'partial',\n path: 'context/token-counter.ts',\n hint: 'Token counting exists but no learned history encoding',\n },\n ],\n\n // Orchestration techniques\n [\n 'aflow-mcts-workflows',\n {\n status: 'partial',\n path: 'workflows/aflow/',\n hint: 'AFlow scaffolding exists but MCTS not implemented',\n },\n ],\n [\n 'dynamic-agent-selection',\n { status: 'implemented', path: 'orchestration/aorchestra/agent-planner.ts' },\n ],\n ['dynamic-subagent-creation', { status: 'implemented', path: 'mcp/tools/create-expert.ts' }],\n ['role-based-protocols', { status: 'implemented', path: 'agents/expert-roles.ts' }],\n [\n 'rule-based-coordination',\n { status: 'implemented', path: 'orchestration/aorchestra/worker-dispatcher.ts' },\n ],\n [\n 'temporal-graph-orchestration',\n {\n status: 'partial',\n path: 'orchestration/graph/',\n hint: 'Graph workflows exist but not temporal-aware',\n },\n ],\n [\n 'model-based-coordination',\n {\n status: 'partial',\n path: 'orchestration/aorchestra/',\n hint: 'Worker dispatch but not model-based coordination theory',\n },\n ],\n [\n 'trinity-roles',\n {\n status: 'partial',\n path: 'agents/expert-roles.ts',\n hint: '10 expert roles, not trinity pattern',\n },\n ],\n [\n 'sew-self-evolving-workflows',\n {\n status: 'partial',\n path: 'workflows/aflow/',\n hint: 'AFlow framework but no self-evolution loop',\n },\n ],\n [\n 'scaling-coordination-predictor',\n {\n status: 'partial',\n path: 'orchestration/aorchestra/agent-planner.ts',\n hint: 'Wave sizing but no scaling prediction',\n },\n ],\n\n // Learning techniques\n [\n 'self-refine-loop',\n {\n status: 'implemented',\n path: 'mcp/tools/orchestrate-dispatch.ts',\n },\n ],\n [\n 'recursive-improvement',\n {\n status: 'implemented',\n path: 'mcp/tools/orchestrate-dispatch.ts',\n },\n ],\n [\n 'rl-orchestrator',\n {\n status: 'implemented',\n path: 'cli-adapters/linucb-bandit.ts',\n hint: 'LinUCB contextual bandit actively selects models via explore-exploit in routing pipeline',\n },\n ],\n ['reflexion-verbal-rl', { status: 'implemented', path: 'mcp/tools/orchestrate-reflection.ts' }],\n\n // Source-derived techniques (Wave 6 — from open-source project analysis)\n ['failure-lesson-injection', { status: 'implemented', path: 'orchestration/failure-lessons.ts' }],\n [\n 'skill-relevance-matching',\n {\n status: 'implemented',\n path: 'agents/experts/skill-matcher.ts',\n hint: 'Matches task keywords to agent skill descriptions for optimal routing',\n },\n ],\n [\n 'context-rot-prevention',\n {\n status: 'implemented',\n path: 'agents/context-pruner.ts',\n hint: 'Token-efficient context pruning prevents quality degradation in long sessions',\n },\n ],\n [\n 'wave-based-parallel-execution',\n {\n status: 'implemented',\n path: 'orchestration/aorchestra/worker-dispatcher.ts',\n hint: 'AOrchestra worker dispatch with cross-wave context sharing',\n },\n ],\n [\n 'write-time-deduplication',\n {\n status: 'implemented',\n path: 'mcp/tools/memory-write.ts',\n hint: 'Semantic dedup at write time prevents memory bloat',\n },\n ],\n]);\n\n// =============================================================================\n// FEATURE GATE INVENTORY\n// =============================================================================\n\n/** Inventory of feature gates linked to research-aligned techniques. */\nexport const FEATURE_GATE_INVENTORY: readonly FeatureGate[] = [\n {\n envVar: 'NEXUS_V2_MODE',\n defaultValue: 'full',\n description: 'V2 pipeline mode (off/partial/full)',\n techniques: ['two-stage-routing', 'cascade-routing'],\n },\n {\n envVar: 'NEXUS_AORCHESTRA',\n defaultValue: 'true',\n description: 'AOrchestra dynamic agent planning',\n techniques: ['dynamic-agent-selection', 'model-based-coordination'],\n },\n {\n envVar: 'NEXUS_AORCHESTRA_DISPATCH',\n defaultValue: 'true',\n description: 'AOrchestra worker dispatch',\n techniques: ['rule-based-coordination', 'self-refine-loop'],\n },\n {\n envVar: 'NEXUS_PERSIST_LEARNING',\n defaultValue: 'true',\n description: 'Cross-session routing persistence',\n techniques: ['linucb-routing', 'preference-trained-routing', 'experience-memory'],\n },\n {\n envVar: 'NEXUS_REFLECTIVE_MEMORY',\n defaultValue: 'shadow',\n description: 'Reflective memory retrieval (shadow=default, true=full, false=off)',\n techniques: ['reflection-memory', 'adaptive-memory', 'reflexion-verbal-rl'],\n },\n {\n envVar: 'NEXUS_BILLING_MODE',\n defaultValue: 'plan',\n description: 'Cost mode (plan=strongest, api=cost-aware)',\n techniques: ['pilot-budget-routing', 'topsis-routing'],\n },\n {\n envVar: 'NEXUS_WORKER_MAX_CALLS',\n defaultValue: '6',\n description: 'Max model calls per orchestrate',\n techniques: ['self-refine-loop', 'recursive-improvement'],\n },\n { envVar: 'NEXUS_AUTH_ENABLED', defaultValue: 'true', description: 'Server authentication' },\n { envVar: 'NEXUS_EVENTBUS_ENABLED', defaultValue: 'true', description: 'EventBus A2A bridge' },\n {\n envVar: 'NEXUS_RATE_LIMIT_ENABLED',\n defaultValue: 'true',\n description: 'Token-bucket rate limiter',\n },\n {\n envVar: 'NEXUS_CIRCUIT_BREAKER_THRESHOLD',\n defaultValue: '5',\n description: 'Circuit breaker failure threshold',\n },\n {\n envVar: 'NEXUS_V2_POLICY_MODE',\n defaultValue: 'block',\n description: 'Policy enforcement (off/warn/block)',\n },\n { envVar: 'NEXUS_LOG_LEVEL', defaultValue: 'info', description: 'Logging verbosity' },\n {\n envVar: 'NEXUS_DISABLE_SESSIONS',\n defaultValue: 'false',\n description: 'Disable session tracking',\n },\n {\n envVar: 'NEXUS_DISABLE_METRICS',\n defaultValue: 'false',\n description: 'Disable metrics tracking',\n },\n {\n envVar: 'NEXUS_ALLOW_MOCK_ORCHESTRATION',\n defaultValue: 'false',\n description: 'Allow mock orchestration (test/CI)',\n },\n {\n envVar: 'NEXUS_MAX_CONCURRENT_EXPERTS',\n defaultValue: '6',\n description: 'Expert pool semaphore capacity',\n },\n {\n envVar: 'NEXUS_VOTE_TIMEOUT_MS',\n defaultValue: '60000',\n description: 'Consensus vote timeout override',\n },\n {\n envVar: 'NEXUS_EXPERT_TIMEOUT_MS',\n defaultValue: '120000',\n description: 'Expert handler timeout override',\n },\n {\n envVar: 'NEXUS_WORKER_TIMEOUT_MS',\n defaultValue: '60000',\n description: 'Worker subprocess timeout override',\n },\n];\n","/**\n * Topic alias map for normalizing free-form topic strings to canonical topics.\n *\n * Maps 200+ free-form topic strings found in papers.yaml to 12 canonical topics.\n * Used by normalizeTopicToCanonical() in research-schemas.ts.\n *\n * @module research/topic-aliases\n * @see Issue #1578 — Topic taxonomy normalization\n */\n\n/**\n * Map of free-form topic strings to canonical topics.\n */\nexport const TOPIC_ALIASES: Readonly<Record<string, string>> = {\n // → consensus\n voting: 'consensus',\n 'consensus-voting': 'consensus',\n debate: 'consensus',\n quorum: 'consensus',\n byzantine: 'consensus',\n 'anti-conformity': 'consensus',\n 'agreement-based': 'consensus',\n 'correlation-aware': 'consensus',\n 'LLM consensus and multi-agent voting mechanisms': 'consensus',\n 'tolerance-parameter': 'consensus',\n\n // → routing\n 'model-routing': 'routing',\n 'model routing': 'routing',\n 'llm-routing': 'routing',\n cascade: 'routing',\n 'cascade-routing': 'routing',\n 'cost-optimization': 'routing',\n 'cost-reduction': 'routing',\n 'model-selection': 'routing',\n topsis: 'routing',\n linucb: 'routing',\n 'contextual-bandit': 'routing',\n 'quality-constrained': 'routing',\n 'budget-constraints': 'routing',\n 'unified-routing': 'routing',\n 'query-model-matching': 'routing',\n 'adaptive model routing and selection optimization': 'routing',\n 'multi-model routing optimization': 'routing',\n 'Single-head cross-attention for query-model matching': 'routing',\n 'performance-cost-tradeoff': 'routing',\n 'task-routing': 'routing',\n 'difficulty-estimation': 'routing',\n 'score-based': 'routing',\n 'optimal-weighting': 'routing',\n 'hierarchical-filtering': 'routing',\n 'preference-optimization': 'routing',\n 'preference-data': 'routing',\n dpo: 'routing',\n 'cross-attention': 'routing',\n 'cma-es': 'routing',\n profile: 'routing',\n prediction: 'routing',\n lightweight: 'routing',\n bayesian: 'routing',\n 'quality-diversity': 'routing',\n 'resource-optimization': 'routing',\n 'routing-latency': 'routing',\n optimization: 'routing',\n 'model-merging': 'routing',\n 'model-zoo': 'routing',\n\n // → memory\n 'context-management': 'memory',\n 'context-compression': 'memory',\n 'context-control': 'memory',\n compression: 'memory',\n 'kv-cache': 'memory',\n 'long-context': 'memory',\n 'long-term-memory': 'memory',\n 'episodic-memory': 'memory',\n 'graph-memory': 'memory',\n 'hierarchical-memory': 'memory',\n 'agentic-memory': 'memory',\n 'lifelong-learning': 'memory',\n 'cross-session-learning': 'memory',\n 'continual-learning': 'memory',\n 'self-editing-memory': 'memory',\n 'six-type-memory': 'memory',\n 'belief-memory': 'memory',\n 'belief-state': 'memory',\n 'action-memory': 'memory',\n 'memory-evolution': 'memory',\n 'virtual-context': 'memory',\n paging: 'memory',\n 'prompt-compression': 'memory',\n 'learned-compression': 'memory',\n zettelkasten: 'memory',\n 'knowledge-distillation': 'memory',\n 'history-encoding': 'memory',\n 'ictm-tuple': 'memory',\n 'latent-space': 'memory',\n 'vector-db': 'memory',\n embedding: 'memory',\n 'Taxonomy of context management strategies': 'memory',\n 'Task-specific context compression': 'memory',\n 'Learned compression modules': 'memory',\n rag: 'memory',\n 'retrieval augmented generation': 'memory',\n svd: 'memory',\n 'svd-mutation': 'memory',\n vae: 'memory',\n knn: 'memory',\n experience: 'memory',\n 'state-aware': 'memory',\n 'temporal-graph': 'memory',\n versioning: 'memory',\n\n // → code-generation\n 'code-repair': 'code-generation',\n 'code-agent': 'code-generation',\n 'code-review': 'code-generation',\n 'software-engineering': 'code-generation',\n 'swe-bench': 'code-generation',\n 'self-improvement': 'code-generation',\n 'self-evolving': 'code-generation',\n 'self-debug': 'code-generation',\n 'self-modification': 'code-generation',\n 'skill-library': 'code-generation',\n 'skill-discovery': 'code-generation',\n 'code review benchmarks': 'code-generation',\n 'code generation evaluation': 'code-generation',\n 'execution-feedback': 'code-generation',\n\n // → cli-tools\n mcp: 'cli-tools',\n 'MCP protocol': 'cli-tools',\n 'MCP security': 'cli-tools',\n 'MCP Security': 'cli-tools',\n 'mcp-applications': 'cli-tools',\n 'model-context-protocol': 'cli-tools',\n puppeteer: 'cli-tools',\n streaming: 'cli-tools',\n integration: 'cli-tools',\n\n // → orchestration\n 'multi-agent': 'orchestration',\n 'multi-agent-systems': 'orchestration',\n 'multi-agent orchestration': 'orchestration',\n 'Multi-Agent Orchestration': 'orchestration',\n 'multi-agent-orchestration': 'orchestration',\n 'multi-agent coordination': 'orchestration',\n 'multi-agent-coordination': 'orchestration',\n 'multi-agent-worker-dispatch': 'orchestration',\n 'multi-agent reliability': 'orchestration',\n 'multi-agent-rl': 'orchestration',\n coordination: 'orchestration',\n coordinator: 'orchestration',\n collaboration: 'orchestration',\n 'graph-orchestration': 'orchestration',\n 'agent architecture': 'orchestration',\n 'dynamic-agent-creation': 'orchestration',\n 'specialized-agents': 'orchestration',\n 'thinker-worker-verifier': 'orchestration',\n 'two-agent': 'orchestration',\n 'meta-agent': 'orchestration',\n agents: 'orchestration',\n 'workflow-optimization': 'orchestration',\n 'workflow-generation': 'orchestration',\n 'agent-communication': 'orchestration',\n 'inter-agent': 'orchestration',\n 'gvu-operator': 'orchestration',\n 'rubber-duck': 'orchestration',\n 'parallel-exploration': 'orchestration',\n kubernetes: 'orchestration',\n 'scale-to-zero': 'orchestration',\n edge: 'orchestration',\n llm: 'orchestration',\n multimodal: 'orchestration',\n production: 'orchestration',\n hybrid: 'orchestration',\n 'automatic-discovery': 'orchestration',\n roadmap: 'orchestration',\n\n // → security\n 'prompt injection defense': 'security',\n 'inter-agent-security': 'security',\n 'agent security': 'security',\n compliance: 'security',\n 'formal-verification': 'security',\n 'formal-safety': 'security',\n\n // → evaluation\n benchmark: 'evaluation',\n benchmarking: 'evaluation',\n survey: 'evaluation',\n 'autonomous agent evaluation benchmarks': 'evaluation',\n 'evaluation-methods': 'evaluation',\n 'LLM evaluation': 'evaluation',\n 'hallucination-detection': 'evaluation',\n 'self-evaluation': 'evaluation',\n 'confidence-probe': 'evaluation',\n 'confidence-aware': 'evaluation',\n verifier: 'evaluation',\n observability: 'evaluation',\n theoretical: 'evaluation',\n 'post-deployment': 'evaluation',\n capability: 'evaluation',\n 'knowledge-gaps': 'evaluation',\n helm: 'evaluation',\n\n // → safety\n 'agentic-safety': 'safety',\n 'agentic AI safety': 'safety',\n 'safety-evaluation': 'safety',\n 'reward-modeling': 'safety',\n 'constitutional-ai': 'safety',\n 'hazard-analysis': 'safety',\n stpa: 'safety',\n 'risk-assessment': 'safety',\n 'fault-tolerance': 'safety',\n 'ai-feedback': 'safety',\n rlaif: 'safety',\n 'human-in-the-loop': 'safety',\n feedback: 'safety',\n 'feedback-loop': 'safety',\n 'feedback-learning': 'safety',\n anthropic: 'safety',\n\n // → planning\n 'decision-making': 'planning',\n 'tree-search': 'planning',\n 'tree-structure': 'planning',\n 'multi-tree': 'planning',\n mcts: 'planning',\n backtrack: 'planning',\n hindsight: 'planning',\n 'task-specific': 'planning',\n\n // → tool-use\n 'tool use': 'tool-use',\n 'tool-augmented reasoning and function calling': 'tool-use',\n 'tool routing optimization': 'tool-use',\n 'dynamic-linking': 'tool-use',\n 'Sparse outcome rewards reduce tool calls by 72%': 'tool-use',\n\n // → reasoning\n 'test-time': 'reasoning',\n 'test-time-scaling': 'reasoning',\n 'inference-scaling': 'reasoning',\n 'inference-optimization': 'reasoning',\n 'self-critique': 'reasoning',\n 'self-reflection': 'reasoning',\n 'self-referential': 'reasoning',\n 'self-play': 'reasoning',\n reflexion: 'reasoning',\n reflection: 'reasoning',\n 'reasoning-optimization': 'reasoning',\n 'meta-thinking': 'reasoning',\n scaling: 'reasoning',\n recursive: 'reasoning',\n 'trajectory-abstraction': 'reasoning',\n 'verbal-rl': 'reasoning',\n exploration: 'reasoning',\n 'Reducible uncertainty modeling across agent trajectories': 'reasoning',\n 'Environmental feedback reduces uncertainty at decision gates': 'reasoning',\n 'reinforcement-learning': 'reasoning',\n transformer: 'reasoning',\n ssm: 'reasoning',\n 'sparse-activation': 'reasoning',\n 'behavior-distillation': 'reasoning',\n 'training-free': 'reasoning',\n 'transfer-learning': 'reasoning',\n 'instruction-tuning': 'reasoning',\n 'Dense process rewards improve performance by 22%': 'reasoning',\n 'prompt-optimization': 'reasoning',\n};\n\n/**\n * Normalize a free-form topic string to a canonical topic.\n * Returns the canonical topic if a mapping exists, otherwise the original string.\n */\nexport function normalizeTopicToCanonical(topic: string): string {\n return TOPIC_ALIASES[topic] ?? topic;\n}\n"],"mappings":"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","names":[]}
@@ -4,7 +4,7 @@ import {
4
4
  getErrorMessage,
5
5
  getTimeProvider,
6
6
  ok
7
- } from "./chunk-TBWKMKCX.js";
7
+ } from "./chunk-OYFKB64X.js";
8
8
 
9
9
  // src/context/session-memory.ts
10
10
  import * as fs from "fs";
@@ -352,4 +352,4 @@ export {
352
352
  SessionMemory,
353
353
  createSessionMemory
354
354
  };
355
- //# sourceMappingURL=chunk-3K3IPRED.js.map
355
+ //# sourceMappingURL=chunk-R3H46BNY.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readOpenAICompatEnv
3
- } from "./chunk-AXG2IGOK.js";
3
+ } from "./chunk-GGQOP4VK.js";
4
4
  import {
5
5
  allOf
6
6
  } from "./chunk-OQB3XC5O.js";
@@ -10,10 +10,10 @@ import {
10
10
  import {
11
11
  createAllAdapters,
12
12
  probeCli
13
- } from "./chunk-SBWVYOTV.js";
13
+ } from "./chunk-EPZCODRK.js";
14
14
  import {
15
15
  getNexusTmpDir
16
- } from "./chunk-RQV5ZUXT.js";
16
+ } from "./chunk-4OW67KT2.js";
17
17
  import {
18
18
  DEFAULT_CAPABILITIES,
19
19
  colors,
@@ -25,20 +25,20 @@ import {
25
25
  ok,
26
26
  symbols,
27
27
  writeLine
28
- } from "./chunk-TBWKMKCX.js";
28
+ } from "./chunk-OYFKB64X.js";
29
29
  import {
30
30
  getLearningDir,
31
31
  getOutcomesFile,
32
32
  getRulesFile,
33
33
  isPersistenceEnabled
34
- } from "./chunk-HHLCAWAV.js";
34
+ } from "./chunk-GBHJ37V3.js";
35
35
  import {
36
36
  detectSandbox,
37
37
  getNexusDataDir,
38
38
  getNexusRepoDir,
39
39
  getPerRepoSubdirs,
40
40
  nexusDataPath
41
- } from "./chunk-NBMWONHE.js";
41
+ } from "./chunk-OGHEKPLD.js";
42
42
 
43
43
  // src/cli/setup-data-dir.ts
44
44
  import { mkdirSync, existsSync as existsSync3 } from "fs";
@@ -170,7 +170,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
170
170
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
171
171
 
172
172
  // src/version.ts
173
- var VERSION = true ? "4.22.4" : "dev";
173
+ var VERSION = true ? "4.23.0" : "dev";
174
174
 
175
175
  // src/mcp/task-store.ts
176
176
  import { InMemoryTaskStore } from "@modelcontextprotocol/sdk/experimental/tasks";
@@ -1201,13 +1201,13 @@ async function runDoctorFix(result) {
1201
1201
  writeLine2("\u2500".repeat(40));
1202
1202
  let fixCount = 0;
1203
1203
  if (!result.dataDirectory.rootExists || result.dataDirectory.subdirectories.some((d) => !d.exists || !d.writable)) {
1204
- const { initDataDirectories: initDataDirectories2, describeDataDirFix: describeDataDirFix2 } = await import("./setup-data-dir-DWMKVY6G.js");
1204
+ const { initDataDirectories: initDataDirectories2, describeDataDirFix: describeDataDirFix2 } = await import("./setup-data-dir-KHLK534F.js");
1205
1205
  const outcome = describeDataDirFix2(initDataDirectories2());
1206
1206
  writeLine2(outcome.line);
1207
1207
  if (outcome.counted) fixCount++;
1208
1208
  }
1209
1209
  if (!result.configFile.found) {
1210
- const { runConfigInitSync } = await import("./setup-config-A5DWVRQB.js");
1210
+ const { runConfigInitSync } = await import("./setup-config-WW5BNLVS.js");
1211
1211
  const configResult = runConfigInitSync(process.cwd(), false, false);
1212
1212
  if (configResult.success && configResult.created) {
1213
1213
  writeLine2(`\u2713 Generated config: ${configResult.path}`);
@@ -1304,4 +1304,4 @@ export {
1304
1304
  startStdioServer,
1305
1305
  closeServer
1306
1306
  };
1307
- //# sourceMappingURL=chunk-HLVST3JE.js.map
1307
+ //# sourceMappingURL=chunk-TXCJMTLN.js.map