nexus-agents 2.113.0 → 2.114.1

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 (120) hide show
  1. package/dist/{child-mcp-config-UMNWY25S.js → child-mcp-config-DL5SVS6K.js} +2 -2
  2. package/dist/{chunk-TIOXPJIO.js → chunk-25QNW65Q.js} +2 -2
  3. package/dist/{chunk-5KBR7SYI.js → chunk-3ZYCKCVG.js} +7 -7
  4. package/dist/{chunk-5NE37CDW.js → chunk-4I64HTH4.js} +1243 -3339
  5. package/dist/chunk-4I64HTH4.js.map +1 -0
  6. package/dist/{chunk-5VGGVVPG.js → chunk-5KYKXB7Q.js} +3 -3
  7. package/dist/{chunk-N2HYXBQ6.js → chunk-7LS7HZU3.js} +2 -2
  8. package/dist/{chunk-WDOJESN6.js → chunk-AH645A5N.js} +2 -2
  9. package/dist/{chunk-LUCPCWIZ.js → chunk-BUHPYOT3.js} +2 -2
  10. package/dist/{chunk-JRBUSUYD.js → chunk-DHU2YECH.js} +3 -3
  11. package/dist/chunk-FO4A6HA3.js +149 -0
  12. package/dist/chunk-FO4A6HA3.js.map +1 -0
  13. package/dist/{chunk-3DB4DEFY.js → chunk-G633EA23.js} +2 -2
  14. package/dist/{chunk-7E2ZCYKA.js → chunk-GMXRM2JU.js} +2 -2
  15. package/dist/{chunk-F2ULTSZ6.js → chunk-H4EXCQCF.js} +7 -7
  16. package/dist/{chunk-2QBR4RVO.js → chunk-HEDW5QDP.js} +2 -2
  17. package/dist/{chunk-ZSJG7EB2.js → chunk-HN6X3WJB.js} +5 -144
  18. package/dist/chunk-HN6X3WJB.js.map +1 -0
  19. package/dist/{chunk-TI2BJ2OB.js → chunk-INKN7FOJ.js} +6 -6
  20. package/dist/{chunk-2JIV7Q6A.js → chunk-JZDBMHWP.js} +3 -3
  21. package/dist/{chunk-5EH7WMME.js → chunk-KISVLXVZ.js} +2 -2
  22. package/dist/{chunk-3XGPHTO6.js → chunk-KPDRVYSN.js} +2 -2
  23. package/dist/{chunk-P2K5GCLL.js → chunk-MLWVB6VV.js} +4 -4
  24. package/dist/{chunk-SXG2NPZP.js → chunk-NTKWLXXD.js} +3 -3
  25. package/dist/{chunk-JPCEQZPE.js → chunk-T74HOH67.js} +7 -7
  26. package/dist/{chunk-NCWD4UOI.js → chunk-USSYZDRI.js} +7 -7
  27. package/dist/{chunk-OMZD4OZP.js → chunk-VJ7WL5MS.js} +5 -5
  28. package/dist/chunk-VOXQULHX.js +2480 -0
  29. package/dist/chunk-VOXQULHX.js.map +1 -0
  30. package/dist/{chunk-LMMMFZ64.js → chunk-WFPEX6L6.js} +2 -2
  31. package/dist/{chunk-VQVKI4XO.js → chunk-WYBVAOMA.js} +2 -2
  32. package/dist/{chunk-FSODHLOV.js → chunk-XFJHSR5E.js} +3 -3
  33. package/dist/{chunk-RPWYEJ66.js → chunk-XKPTNVA2.js} +4 -4
  34. package/dist/{chunk-AWXWDGG5.js → chunk-XOTVHEL3.js} +2 -2
  35. package/dist/{chunk-VV47MWHN.js → chunk-ZPUFJHR5.js} +3 -3
  36. package/dist/{chunk-5OOFU3EY.js → chunk-ZTMLKLVG.js} +2 -2
  37. package/dist/{cli-circuit-breaker-XB5FPZJ5.js → cli-circuit-breaker-PWQTC7S4.js} +4 -4
  38. package/dist/cli.js +66 -63
  39. package/dist/cli.js.map +1 -1
  40. package/dist/{composite-router-VWQ65U6B.js → composite-router-ZF3JDFCJ.js} +2 -2
  41. package/dist/{consensus-vote-EW5HWO6Z.js → consensus-vote-3J473M5O.js} +12 -12
  42. package/dist/context-retriever-JSP6PVSJ.js +24 -0
  43. package/dist/{doctor-deep-RFHTNO2P.js → doctor-deep-PTHRYO64.js} +3 -3
  44. package/dist/{expert-bridge-LDWFPVHK.js → expert-bridge-UOSBT7K5.js} +4 -4
  45. package/dist/{factory-IYL24RFV.js → factory-4KDOLJQU.js} +5 -5
  46. package/dist/factory-RLXFNRIA.js +21 -0
  47. package/dist/{improvement-review-7R5Q4OD3.js → improvement-review-G6TDVPUG.js} +4 -4
  48. package/dist/index.d.ts +8 -8
  49. package/dist/index.js +31 -30
  50. package/dist/index.js.map +1 -1
  51. package/dist/{init-opencode-26HTJ5QO.js → init-opencode-ZVS375BG.js} +5 -5
  52. package/dist/{issue-triage-MVLE3SRA.js → issue-triage-CD2G32WJ.js} +5 -5
  53. package/dist/{pr-reviewer-helpers-HXEJ437Q.js → pr-reviewer-helpers-ZCTY6XLA.js} +4 -4
  54. package/dist/{registry-command-RQH4YGK5.js → registry-command-IMVEZVGE.js} +2 -2
  55. package/dist/{repo-security-plan-SIOA64KN.js → repo-security-plan-LCU5QYTR.js} +3 -3
  56. package/dist/{research-helpers-synthesize-K43DWQVG.js → research-helpers-synthesize-RPDK624P.js} +4 -3
  57. package/dist/{routing-memory-AEYB4NPO.js → routing-memory-PH5I63ZL.js} +2 -2
  58. package/dist/{session-memory-GZOSVFDD.js → session-memory-HNH5MILB.js} +3 -3
  59. package/dist/{setup-command-UZTCNASI.js → setup-command-XWHUKY7K.js} +11 -11
  60. package/dist/{setup-config-R7GWRUW2.js → setup-config-K7DMKGHB.js} +3 -3
  61. package/dist/{setup-custom-api-DXS3Y4XW.js → setup-custom-api-XAGOCQMH.js} +3 -3
  62. package/dist/{tool-memory-CXHXCI6Q.js → tool-memory-VMTMXSYX.js} +5 -5
  63. package/dist/{weather-report-PYDBRULK.js → weather-report-RYLRL76R.js} +2 -2
  64. package/package.json +1 -1
  65. package/dist/chunk-5NE37CDW.js.map +0 -1
  66. package/dist/chunk-KWHJL7HX.js +0 -276
  67. package/dist/chunk-KWHJL7HX.js.map +0 -1
  68. package/dist/chunk-ZSJG7EB2.js.map +0 -1
  69. package/dist/context-retriever-5VFAIPHM.js +0 -19
  70. package/dist/factory-AIERXI3O.js +0 -21
  71. /package/dist/{child-mcp-config-UMNWY25S.js.map → child-mcp-config-DL5SVS6K.js.map} +0 -0
  72. /package/dist/{chunk-TIOXPJIO.js.map → chunk-25QNW65Q.js.map} +0 -0
  73. /package/dist/{chunk-5KBR7SYI.js.map → chunk-3ZYCKCVG.js.map} +0 -0
  74. /package/dist/{chunk-5VGGVVPG.js.map → chunk-5KYKXB7Q.js.map} +0 -0
  75. /package/dist/{chunk-N2HYXBQ6.js.map → chunk-7LS7HZU3.js.map} +0 -0
  76. /package/dist/{chunk-WDOJESN6.js.map → chunk-AH645A5N.js.map} +0 -0
  77. /package/dist/{chunk-LUCPCWIZ.js.map → chunk-BUHPYOT3.js.map} +0 -0
  78. /package/dist/{chunk-JRBUSUYD.js.map → chunk-DHU2YECH.js.map} +0 -0
  79. /package/dist/{chunk-3DB4DEFY.js.map → chunk-G633EA23.js.map} +0 -0
  80. /package/dist/{chunk-7E2ZCYKA.js.map → chunk-GMXRM2JU.js.map} +0 -0
  81. /package/dist/{chunk-F2ULTSZ6.js.map → chunk-H4EXCQCF.js.map} +0 -0
  82. /package/dist/{chunk-2QBR4RVO.js.map → chunk-HEDW5QDP.js.map} +0 -0
  83. /package/dist/{chunk-TI2BJ2OB.js.map → chunk-INKN7FOJ.js.map} +0 -0
  84. /package/dist/{chunk-2JIV7Q6A.js.map → chunk-JZDBMHWP.js.map} +0 -0
  85. /package/dist/{chunk-5EH7WMME.js.map → chunk-KISVLXVZ.js.map} +0 -0
  86. /package/dist/{chunk-3XGPHTO6.js.map → chunk-KPDRVYSN.js.map} +0 -0
  87. /package/dist/{chunk-P2K5GCLL.js.map → chunk-MLWVB6VV.js.map} +0 -0
  88. /package/dist/{chunk-SXG2NPZP.js.map → chunk-NTKWLXXD.js.map} +0 -0
  89. /package/dist/{chunk-JPCEQZPE.js.map → chunk-T74HOH67.js.map} +0 -0
  90. /package/dist/{chunk-NCWD4UOI.js.map → chunk-USSYZDRI.js.map} +0 -0
  91. /package/dist/{chunk-OMZD4OZP.js.map → chunk-VJ7WL5MS.js.map} +0 -0
  92. /package/dist/{chunk-LMMMFZ64.js.map → chunk-WFPEX6L6.js.map} +0 -0
  93. /package/dist/{chunk-VQVKI4XO.js.map → chunk-WYBVAOMA.js.map} +0 -0
  94. /package/dist/{chunk-FSODHLOV.js.map → chunk-XFJHSR5E.js.map} +0 -0
  95. /package/dist/{chunk-RPWYEJ66.js.map → chunk-XKPTNVA2.js.map} +0 -0
  96. /package/dist/{chunk-AWXWDGG5.js.map → chunk-XOTVHEL3.js.map} +0 -0
  97. /package/dist/{chunk-VV47MWHN.js.map → chunk-ZPUFJHR5.js.map} +0 -0
  98. /package/dist/{chunk-5OOFU3EY.js.map → chunk-ZTMLKLVG.js.map} +0 -0
  99. /package/dist/{cli-circuit-breaker-XB5FPZJ5.js.map → cli-circuit-breaker-PWQTC7S4.js.map} +0 -0
  100. /package/dist/{composite-router-VWQ65U6B.js.map → composite-router-ZF3JDFCJ.js.map} +0 -0
  101. /package/dist/{consensus-vote-EW5HWO6Z.js.map → consensus-vote-3J473M5O.js.map} +0 -0
  102. /package/dist/{context-retriever-5VFAIPHM.js.map → context-retriever-JSP6PVSJ.js.map} +0 -0
  103. /package/dist/{doctor-deep-RFHTNO2P.js.map → doctor-deep-PTHRYO64.js.map} +0 -0
  104. /package/dist/{expert-bridge-LDWFPVHK.js.map → expert-bridge-UOSBT7K5.js.map} +0 -0
  105. /package/dist/{factory-AIERXI3O.js.map → factory-4KDOLJQU.js.map} +0 -0
  106. /package/dist/{factory-IYL24RFV.js.map → factory-RLXFNRIA.js.map} +0 -0
  107. /package/dist/{improvement-review-7R5Q4OD3.js.map → improvement-review-G6TDVPUG.js.map} +0 -0
  108. /package/dist/{init-opencode-26HTJ5QO.js.map → init-opencode-ZVS375BG.js.map} +0 -0
  109. /package/dist/{issue-triage-MVLE3SRA.js.map → issue-triage-CD2G32WJ.js.map} +0 -0
  110. /package/dist/{pr-reviewer-helpers-HXEJ437Q.js.map → pr-reviewer-helpers-ZCTY6XLA.js.map} +0 -0
  111. /package/dist/{registry-command-RQH4YGK5.js.map → registry-command-IMVEZVGE.js.map} +0 -0
  112. /package/dist/{repo-security-plan-SIOA64KN.js.map → repo-security-plan-LCU5QYTR.js.map} +0 -0
  113. /package/dist/{research-helpers-synthesize-K43DWQVG.js.map → research-helpers-synthesize-RPDK624P.js.map} +0 -0
  114. /package/dist/{routing-memory-AEYB4NPO.js.map → routing-memory-PH5I63ZL.js.map} +0 -0
  115. /package/dist/{session-memory-GZOSVFDD.js.map → session-memory-HNH5MILB.js.map} +0 -0
  116. /package/dist/{setup-command-UZTCNASI.js.map → setup-command-XWHUKY7K.js.map} +0 -0
  117. /package/dist/{setup-config-R7GWRUW2.js.map → setup-config-K7DMKGHB.js.map} +0 -0
  118. /package/dist/{setup-custom-api-DXS3Y4XW.js.map → setup-custom-api-XAGOCQMH.js.map} +0 -0
  119. /package/dist/{tool-memory-CXHXCI6Q.js.map → tool-memory-VMTMXSYX.js.map} +0 -0
  120. /package/dist/{weather-report-PYDBRULK.js.map → weather-report-RYLRL76R.js.map} +0 -0
@@ -7,7 +7,7 @@ import {
7
7
  toolStructuredError,
8
8
  toolSuccessStructured,
9
9
  wrapToolWithTimeout
10
- } from "./chunk-7E2ZCYKA.js";
10
+ } from "./chunk-GMXRM2JU.js";
11
11
  import {
12
12
  createLogger,
13
13
  formatZodError,
@@ -15,7 +15,7 @@ import {
15
15
  getOutcomeStore,
16
16
  getPipelineEventBus,
17
17
  getTimeProvider
18
- } from "./chunk-JRBUSUYD.js";
18
+ } from "./chunk-DHU2YECH.js";
19
19
  import {
20
20
  getNexusDataDir
21
21
  } from "./chunk-CH7QIDHQ.js";
@@ -1138,4 +1138,4 @@ export {
1138
1138
  runImprovementReview,
1139
1139
  registerImprovementReviewTool
1140
1140
  };
1141
- //# sourceMappingURL=chunk-5VGGVVPG.js.map
1141
+ //# sourceMappingURL=chunk-5KYKXB7Q.js.map
@@ -4,7 +4,7 @@ import {
4
4
  getErrorMessage,
5
5
  getTimeProvider,
6
6
  ok
7
- } from "./chunk-JRBUSUYD.js";
7
+ } from "./chunk-DHU2YECH.js";
8
8
 
9
9
  // src/context/session-memory.ts
10
10
  import * as fs from "fs";
@@ -351,4 +351,4 @@ export {
351
351
  SessionMemory,
352
352
  createSessionMemory
353
353
  };
354
- //# sourceMappingURL=chunk-N2HYXBQ6.js.map
354
+ //# sourceMappingURL=chunk-7LS7HZU3.js.map
@@ -2,7 +2,7 @@ import {
2
2
  TASK_CATEGORIES,
3
3
  getAdaptiveBonus,
4
4
  getOutcomeStore
5
- } from "./chunk-JRBUSUYD.js";
5
+ } from "./chunk-DHU2YECH.js";
6
6
 
7
7
  // src/cli/doctor-deep.ts
8
8
  var CLI_NAMES = ["claude", "gemini", "codex", "opencode"];
@@ -106,4 +106,4 @@ export {
106
106
  runDeepDiagnostics,
107
107
  formatDeepDiagnostics
108
108
  };
109
- //# sourceMappingURL=chunk-WDOJESN6.js.map
109
+ //# sourceMappingURL=chunk-AH645A5N.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getRandomProvider,
3
3
  getTimeProvider
4
- } from "./chunk-JRBUSUYD.js";
4
+ } from "./chunk-DHU2YECH.js";
5
5
 
6
6
  // src/utils/id-utils.ts
7
7
  import { randomUUID } from "crypto";
@@ -24,4 +24,4 @@ export {
24
24
  generateHyphenId,
25
25
  generateUUID
26
26
  };
27
- //# sourceMappingURL=chunk-LUCPCWIZ.js.map
27
+ //# sourceMappingURL=chunk-BUHPYOT3.js.map
@@ -11896,7 +11896,7 @@ var CompositeRouter = class _CompositeRouter {
11896
11896
  */
11897
11897
  async consultUnifiedContext(task) {
11898
11898
  try {
11899
- const { getContextForTask, inferTaskCategory } = await import("./context-retriever-5VFAIPHM.js");
11899
+ const { getContextForTask, inferTaskCategory } = await import("./context-retriever-JSP6PVSJ.js");
11900
11900
  const ctx = await getContextForTask({
11901
11901
  task: task.content,
11902
11902
  category: inferTaskCategory(task.content),
@@ -13246,11 +13246,11 @@ export {
13246
13246
  setDefaultAvailableModelsCache,
13247
13247
  isDynamicModelsEnabled,
13248
13248
  buildDefaultModelSources,
13249
+ ParseError,
13249
13250
  CompositeRouter,
13250
13251
  createCompositeRouter,
13251
13252
  ModelCapability,
13252
13253
  AgentCapability,
13253
- ParseError,
13254
13254
  OrchestratorError
13255
13255
  };
13256
- //# sourceMappingURL=chunk-JRBUSUYD.js.map
13256
+ //# sourceMappingURL=chunk-DHU2YECH.js.map
@@ -0,0 +1,149 @@
1
+ import {
2
+ resolveInsideRoot
3
+ } from "./chunk-NUBSJGQZ.js";
4
+ import {
5
+ ParseError,
6
+ SecurityError,
7
+ err,
8
+ getErrorMessage,
9
+ ok,
10
+ parseBoolEnv
11
+ } from "./chunk-DHU2YECH.js";
12
+
13
+ // src/cli/research-helpers-io.ts
14
+ import * as fs2 from "fs/promises";
15
+ import { join, resolve as resolve2 } from "path";
16
+ import { parse as parseYaml, stringify as stringifyYaml2 } from "yaml";
17
+
18
+ // src/cli/research-scaffold.ts
19
+ import * as fs from "fs/promises";
20
+ import { existsSync } from "fs";
21
+ import { dirname, resolve } from "path";
22
+ import { stringify as stringifyYaml } from "yaml";
23
+ var SCHEMA_VERSION = "1.0";
24
+ var ANNOUNCED = /* @__PURE__ */ new Set();
25
+ function announceOnce(path, kind) {
26
+ if (ANNOUNCED.has(path)) return;
27
+ ANNOUNCED.add(path);
28
+ process.stderr.write(
29
+ `[scaffold] Created empty ${kind} at ${path}; add entries via 'nexus-agents research add' or research_add MCP tool.
30
+ `
31
+ );
32
+ }
33
+ function scaffoldDisabled() {
34
+ return parseBoolEnv("NEXUS_NO_SCAFFOLD", false);
35
+ }
36
+ function emptyPapersYaml() {
37
+ return stringifyYaml({ schema_version: SCHEMA_VERSION, papers: {} });
38
+ }
39
+ function emptyTechniquesYaml() {
40
+ return stringifyYaml({ schema_version: SCHEMA_VERSION, techniques: {} });
41
+ }
42
+ async function ensureRegistryFile(rootDir, filename) {
43
+ const filePath = resolve(rootDir, REGISTRY_PATH, filename);
44
+ if (existsSync(filePath)) return ok(filePath);
45
+ if (scaffoldDisabled()) {
46
+ return err(
47
+ new ParseError(
48
+ `Registry file missing: ${filePath}. Scaffolding disabled (NEXUS_NO_SCAFFOLD=1). Create the file manually or unset the env var.`
49
+ )
50
+ );
51
+ }
52
+ if (!existsSync(resolve(rootDir, "docs"))) {
53
+ return err(
54
+ new ParseError(
55
+ `Registry file missing: ${filePath}. ${rootDir}/docs/ does not exist; refusing to create a Nexus subtree in a non-documented root. Run 'nexus-agents init' first, or create ${rootDir}/docs/ manually.`
56
+ )
57
+ );
58
+ }
59
+ try {
60
+ await fs.mkdir(dirname(filePath), { recursive: true });
61
+ const content = filename === PAPERS_FILE ? emptyPapersYaml() : emptyTechniquesYaml();
62
+ await fs.writeFile(filePath, content, "utf-8");
63
+ announceOnce(filePath, filename);
64
+ return ok(filePath);
65
+ } catch (e) {
66
+ return err(
67
+ new ParseError(
68
+ `Failed to scaffold ${filename} at ${filePath}: ${getErrorMessage(e)}. Set NEXUS_NO_SCAFFOLD=1 and create manually if scaffolding is unwanted.`
69
+ )
70
+ );
71
+ }
72
+ }
73
+
74
+ // src/cli/research-helpers-io.ts
75
+ var REGISTRY_PATH = "docs/research/registry";
76
+ var TECHNIQUES_FILE = "techniques.yaml";
77
+ var PAPERS_FILE = "papers.yaml";
78
+ function getProjectRoot() {
79
+ return process.cwd();
80
+ }
81
+ function validatePath(constructedPath, allowedRoot) {
82
+ const resolved = resolveInsideRoot(constructedPath, allowedRoot);
83
+ if (resolved === null) {
84
+ return {
85
+ ok: false,
86
+ error: new SecurityError("Path traversal detected: path escapes allowed root directory", {
87
+ context: { constructedPath, allowedRoot: resolve2(allowedRoot) }
88
+ })
89
+ };
90
+ }
91
+ return { ok: true, value: resolved };
92
+ }
93
+ async function loadTechniquesRegistry(rootDir) {
94
+ const root = rootDir ?? process.cwd();
95
+ const filePath = join(root, REGISTRY_PATH, TECHNIQUES_FILE);
96
+ const pathValidation = validatePath(filePath, root);
97
+ if (!pathValidation.ok) {
98
+ return pathValidation;
99
+ }
100
+ await ensureRegistryFile(root, TECHNIQUES_FILE);
101
+ try {
102
+ const content = await fs2.readFile(pathValidation.value, "utf-8");
103
+ return { ok: true, value: parseYaml(content) };
104
+ } catch (error) {
105
+ const message = getErrorMessage(error);
106
+ return { ok: false, error: new ParseError(`Failed to load techniques registry: ${message}`) };
107
+ }
108
+ }
109
+ async function loadPapersRegistry(rootDir) {
110
+ const root = rootDir ?? process.cwd();
111
+ const filePath = join(root, REGISTRY_PATH, PAPERS_FILE);
112
+ const pathValidation = validatePath(filePath, root);
113
+ if (!pathValidation.ok) {
114
+ return pathValidation;
115
+ }
116
+ await ensureRegistryFile(root, PAPERS_FILE);
117
+ try {
118
+ const content = await fs2.readFile(pathValidation.value, "utf-8");
119
+ return { ok: true, value: parseYaml(content) };
120
+ } catch (error) {
121
+ const message = getErrorMessage(error);
122
+ return { ok: false, error: new ParseError(`Failed to load papers registry: ${message}`) };
123
+ }
124
+ }
125
+ async function savePapersRegistry(registry, rootDir) {
126
+ const root = rootDir ?? process.cwd();
127
+ const filePath = join(root, REGISTRY_PATH, PAPERS_FILE);
128
+ const pathValidation = validatePath(filePath, root);
129
+ if (!pathValidation.ok) {
130
+ return pathValidation;
131
+ }
132
+ try {
133
+ const content = stringifyYaml2(registry, { lineWidth: 100 });
134
+ await fs2.writeFile(pathValidation.value, content, "utf-8");
135
+ return { ok: true, value: void 0 };
136
+ } catch (error) {
137
+ const message = getErrorMessage(error);
138
+ return { ok: false, error: new ParseError(`Failed to save papers registry: ${message}`) };
139
+ }
140
+ }
141
+
142
+ export {
143
+ REGISTRY_PATH,
144
+ getProjectRoot,
145
+ loadTechniquesRegistry,
146
+ loadPapersRegistry,
147
+ savePapersRegistry
148
+ };
149
+ //# sourceMappingURL=chunk-FO4A6HA3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/research-helpers-io.ts","../src/cli/research-scaffold.ts"],"sourcesContent":["/**\n * Research Registry I/O Operations\n *\n * File operations for loading and saving research registry YAML files.\n * Includes path traversal protection per Issue #353.\n *\n * @see docs/research/RESEARCH_INDEX.md\n * @see Issue #237 (Epic #225)\n * @see Issue #353 (Security - Path traversal fix)\n */\n\nimport * as fs from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport { resolveInsideRoot } from '../security/safe-path.js';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { Result } from '../core/index.js';\nimport { SecurityError, getErrorMessage } from '../core/index.js';\nimport { ParseError } from '../core/types/workflow.js';\nimport type { TechniquesRegistry, PapersRegistry } from './research-types.js';\nimport { ensureRegistryFile } from './research-scaffold.js';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\n/** Path to research registry directory relative to project root */\nexport const REGISTRY_PATH = 'docs/research/registry';\n\n/** Filename for techniques registry */\nexport const TECHNIQUES_FILE = 'techniques.yaml';\n\n/** Filename for papers registry */\nexport const PAPERS_FILE = 'papers.yaml';\n\n// =============================================================================\n// PROJECT ROOT\n// =============================================================================\n\n/**\n * Get the project root directory.\n * Note: Returns cwd since registry operations use explicit rootDir parameter\n */\nexport function getProjectRoot(): string {\n return process.cwd();\n}\n\n// =============================================================================\n// PATH VALIDATION\n// =============================================================================\n\n/**\n * Validates that a constructed path stays within the allowed root directory.\n * Prevents path traversal attacks (e.g., ../../../etc/passwd).\n *\n * @param constructedPath - The full path to validate\n * @param allowedRoot - The root directory that paths must be within\n * @returns Result with validated absolute path or SecurityError\n */\nfunction validatePath(constructedPath: string, allowedRoot: string): Result<string, SecurityError> {\n const resolved = resolveInsideRoot(constructedPath, allowedRoot);\n if (resolved === null) {\n return {\n ok: false,\n error: new SecurityError('Path traversal detected: path escapes allowed root directory', {\n context: { constructedPath, allowedRoot: resolve(allowedRoot) },\n }),\n };\n }\n return { ok: true, value: resolved };\n}\n\n// =============================================================================\n// LOAD OPERATIONS\n// =============================================================================\n\n/**\n * Load techniques registry from YAML file.\n * Validates path to prevent directory traversal attacks.\n *\n * @param rootDir - Project root directory (defaults to cwd)\n * @returns Result with TechniquesRegistry or SecurityError/ParseError\n */\nexport async function loadTechniquesRegistry(\n rootDir?: string\n): Promise<Result<TechniquesRegistry, SecurityError | ParseError>> {\n const root = rootDir ?? process.cwd();\n const filePath = join(root, REGISTRY_PATH, TECHNIQUES_FILE);\n\n const pathValidation = validatePath(filePath, root);\n if (!pathValidation.ok) {\n return pathValidation;\n }\n\n // #2470: try to scaffold an empty techniques.yaml on first run so research_*\n // workflows don't error on a fresh install. No-op when file exists.\n // When scaffold refuses (e.g. <rootDir>/docs/ doesn't exist, or\n // NEXUS_NO_SCAFFOLD=1), fall through and let readFile produce the original\n // ENOENT error — that's the existing contract.\n await ensureRegistryFile(root, TECHNIQUES_FILE);\n\n try {\n const content = await fs.readFile(pathValidation.value, 'utf-8');\n return { ok: true, value: parseYaml(content) as TechniquesRegistry };\n } catch (error) {\n const message = getErrorMessage(error);\n return { ok: false, error: new ParseError(`Failed to load techniques registry: ${message}`) };\n }\n}\n\n/**\n * Load papers registry from YAML file.\n * Validates path to prevent directory traversal attacks.\n *\n * @param rootDir - Project root directory (defaults to cwd)\n * @returns Result with PapersRegistry or SecurityError/ParseError\n */\nexport async function loadPapersRegistry(\n rootDir?: string\n): Promise<Result<PapersRegistry, SecurityError | ParseError>> {\n const root = rootDir ?? process.cwd();\n const filePath = join(root, REGISTRY_PATH, PAPERS_FILE);\n\n const pathValidation = validatePath(filePath, root);\n if (!pathValidation.ok) {\n return pathValidation;\n }\n\n // #2470: try to scaffold an empty papers.yaml on first run so research_*\n // workflows don't error on a fresh install. No-op when file exists; falls\n // through silently when scaffold refuses (no docs/ root, or NEXUS_NO_SCAFFOLD).\n await ensureRegistryFile(root, PAPERS_FILE);\n\n try {\n const content = await fs.readFile(pathValidation.value, 'utf-8');\n return { ok: true, value: parseYaml(content) as PapersRegistry };\n } catch (error) {\n const message = getErrorMessage(error);\n return { ok: false, error: new ParseError(`Failed to load papers registry: ${message}`) };\n }\n}\n\n// =============================================================================\n// SAVE OPERATIONS\n// =============================================================================\n\n/**\n * Save techniques registry to YAML file.\n * Validates path to prevent directory traversal attacks.\n *\n * @param registry - The techniques registry to save\n * @param rootDir - Project root directory (defaults to cwd)\n * @returns Result with void on success or SecurityError/ParseError on failure\n */\nexport async function saveTechniquesRegistry(\n registry: TechniquesRegistry,\n rootDir?: string\n): Promise<Result<void, SecurityError | ParseError>> {\n const root = rootDir ?? process.cwd();\n const filePath = join(root, REGISTRY_PATH, TECHNIQUES_FILE);\n\n const pathValidation = validatePath(filePath, root);\n if (!pathValidation.ok) {\n return pathValidation;\n }\n\n try {\n const content = stringifyYaml(registry, { lineWidth: 100 });\n await fs.writeFile(pathValidation.value, content, 'utf-8');\n return { ok: true, value: undefined };\n } catch (error) {\n const message = getErrorMessage(error);\n return { ok: false, error: new ParseError(`Failed to save techniques registry: ${message}`) };\n }\n}\n\n/**\n * Save papers registry to YAML file.\n * Validates path to prevent directory traversal attacks.\n *\n * @param registry - The papers registry to save\n * @param rootDir - Project root directory (defaults to cwd)\n * @returns Result with void on success or SecurityError/ParseError on failure\n */\nexport async function savePapersRegistry(\n registry: PapersRegistry,\n rootDir?: string\n): Promise<Result<void, SecurityError | ParseError>> {\n const root = rootDir ?? process.cwd();\n const filePath = join(root, REGISTRY_PATH, PAPERS_FILE);\n\n const pathValidation = validatePath(filePath, root);\n if (!pathValidation.ok) {\n return pathValidation;\n }\n\n try {\n const content = stringifyYaml(registry, { lineWidth: 100 });\n await fs.writeFile(pathValidation.value, content, 'utf-8');\n return { ok: true, value: undefined };\n } catch (error) {\n const message = getErrorMessage(error);\n return { ok: false, error: new ParseError(`Failed to save papers registry: ${message}`) };\n }\n}\n","/**\n * research-scaffold — auto-create empty research registry files when missing.\n *\n * On a fresh install or in a sandboxed environment where the project doesn't\n * have `docs/research/registry/`, the research workflows previously errored\n * with `Failed to load papers registry: ENOENT`. The user has no idea what\n * to do.\n *\n * This module provides `ensureResearchRegistry()`: idempotent scaffolding\n * that creates empty YAML registries on ENOENT, announces what it did to\n * stderr (so the operator knows), and returns the path that's now safe to\n * read.\n *\n * Source: Issue #2470 (epic #2467 child).\n */\n\nimport * as fs from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { stringify as stringifyYaml } from 'yaml';\n\nimport type { Result } from '../core/index.js';\nimport { ok, err, getErrorMessage } from '../core/index.js';\nimport { ParseError } from '../core/types/workflow.js';\n\nimport { REGISTRY_PATH, PAPERS_FILE, TECHNIQUES_FILE } from './research-helpers-io.js';\nimport { parseBoolEnv } from '../config/defaults-env.js';\n\n/**\n * Names of the registry files this scaffolder knows how to create.\n * `sources.yaml` and `alignments.yaml` live alongside but are populated by\n * different code paths; this scaffolder only owns the two YAML registries\n * that block `research_query` / `research_synthesize` on a fresh install.\n */\ntype ScaffoldedFile = typeof PAPERS_FILE | typeof TECHNIQUES_FILE;\n\nconst SCHEMA_VERSION = '1.0';\n\nconst ANNOUNCED = new Set<string>();\n\nfunction announceOnce(path: string, kind: ScaffoldedFile): void {\n // De-dupe so that running multiple research commands in one session\n // produces one announcement per scaffolded file, not per call.\n if (ANNOUNCED.has(path)) return;\n ANNOUNCED.add(path);\n process.stderr.write(\n `[scaffold] Created empty ${kind} at ${path}; add entries via 'nexus-agents research add' or research_add MCP tool.\\n`\n );\n}\n\n/**\n * Operator opt-out. When set, scaffolding errors loudly with an actionable\n * message instead of silently writing files. Useful for CI and for\n * environments that want strict \"fail when state is wrong\" semantics.\n */\nfunction scaffoldDisabled(): boolean {\n return parseBoolEnv('NEXUS_NO_SCAFFOLD', false);\n}\n\nfunction emptyPapersYaml(): string {\n return stringifyYaml({ schema_version: SCHEMA_VERSION, papers: {} });\n}\n\nfunction emptyTechniquesYaml(): string {\n return stringifyYaml({ schema_version: SCHEMA_VERSION, techniques: {} });\n}\n\n/**\n * Create the registry directory + an empty YAML file if either is missing.\n * Idempotent: existing files are left untouched.\n *\n * Scaffolds only when `<rootDir>/docs/` already exists. That guard keeps\n * tests, vitest's package cwd, and random scratch directories from getting\n * a `docs/research/` subtree implicitly. Operators on a real project root\n * (which always has `docs/`, either from cloning the repo or running\n * `nexus-agents init`) still get the auto-create behavior the issue asks for.\n *\n * Returns the resolved file path on success, or a ParseError when scaffolding\n * is disabled / refused (so the caller can pass the error through unchanged\n * for backwards compat with existing call sites).\n */\nexport async function ensureRegistryFile(\n rootDir: string,\n filename: ScaffoldedFile\n): Promise<Result<string, ParseError>> {\n const filePath = resolve(rootDir, REGISTRY_PATH, filename);\n\n if (existsSync(filePath)) return ok(filePath);\n\n if (scaffoldDisabled()) {\n return err(\n new ParseError(\n `Registry file missing: ${filePath}. Scaffolding disabled (NEXUS_NO_SCAFFOLD=1). Create the file manually or unset the env var.`\n )\n );\n }\n\n // Refuse to create a docs/ subtree in directories that aren't already\n // documented project roots. The presence of `<rootDir>/docs/` is the\n // strong signal we use.\n if (!existsSync(resolve(rootDir, 'docs'))) {\n return err(\n new ParseError(\n `Registry file missing: ${filePath}. ${rootDir}/docs/ does not exist; ` +\n `refusing to create a Nexus subtree in a non-documented root. ` +\n `Run 'nexus-agents init' first, or create ${rootDir}/docs/ manually.`\n )\n );\n }\n\n try {\n await fs.mkdir(dirname(filePath), { recursive: true });\n const content = filename === PAPERS_FILE ? emptyPapersYaml() : emptyTechniquesYaml();\n await fs.writeFile(filePath, content, 'utf-8');\n announceOnce(filePath, filename);\n return ok(filePath);\n } catch (e: unknown) {\n return err(\n new ParseError(\n `Failed to scaffold ${filename} at ${filePath}: ${getErrorMessage(e)}. Set NEXUS_NO_SCAFFOLD=1 and create manually if scaffolding is unwanted.`\n )\n );\n }\n}\n\n/**\n * Convenience: scaffold both papers.yaml and techniques.yaml. Used by the\n * research workflow startup paths that need both registries available.\n */\nexport async function ensureResearchRegistry(rootDir?: string): Promise<Result<void, ParseError>> {\n const root = rootDir ?? process.cwd();\n const papers = await ensureRegistryFile(root, PAPERS_FILE);\n if (!papers.ok) return papers;\n const techniques = await ensureRegistryFile(root, TECHNIQUES_FILE);\n if (!techniques.ok) return techniques;\n return ok(undefined);\n}\n\n/**\n * Test-only reset of the announcement de-dupe set. Production code never\n * needs this; tests do because Vitest reuses process state across files.\n */\nexport function _resetAnnouncedForTests(): void {\n ANNOUNCED.clear();\n}\n"],"mappings":"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","names":["fs","resolve","stringifyYaml","resolve","stringifyYaml"]}
@@ -3,7 +3,7 @@ import {
3
3
  err,
4
4
  getErrorMessage,
5
5
  ok
6
- } from "./chunk-JRBUSUYD.js";
6
+ } from "./chunk-DHU2YECH.js";
7
7
 
8
8
  // src/scm/types.ts
9
9
  var ScmError = class extends Error {
@@ -258,4 +258,4 @@ export {
258
258
  ScmError,
259
259
  GitHubProvider
260
260
  };
261
- //# sourceMappingURL=chunk-3DB4DEFY.js.map
261
+ //# sourceMappingURL=chunk-G633EA23.js.map
@@ -11,7 +11,7 @@ import {
11
11
  getErrorMessage,
12
12
  getTimeProvider,
13
13
  ok
14
- } from "./chunk-JRBUSUYD.js";
14
+ } from "./chunk-DHU2YECH.js";
15
15
  import {
16
16
  getNexusDataDir
17
17
  } from "./chunk-CH7QIDHQ.js";
@@ -2588,4 +2588,4 @@ export {
2588
2588
  VOTING_THRESHOLDS,
2589
2589
  ConsensusMetricsSchema
2590
2590
  };
2591
- //# sourceMappingURL=chunk-7E2ZCYKA.js.map
2591
+ //# sourceMappingURL=chunk-GMXRM2JU.js.map
@@ -4,24 +4,24 @@ import {
4
4
  createStream,
5
5
  requireApiKey,
6
6
  validateApiKeyPresence
7
- } from "./chunk-5OOFU3EY.js";
7
+ } from "./chunk-ZTMLKLVG.js";
8
8
  import {
9
9
  CUSTOM_API_BASE_URL_ENV,
10
10
  CUSTOM_API_DEFAULT_MODEL,
11
11
  PROVIDER_ENV_KEYS,
12
12
  assertCustomApiHostResolvesPublic,
13
13
  validateCustomApiBaseUrl
14
- } from "./chunk-3XGPHTO6.js";
14
+ } from "./chunk-KPDRVYSN.js";
15
15
  import {
16
16
  CliCircuitBreaker,
17
17
  DEFAULT_CIRCUIT_BREAKER_CONFIG
18
- } from "./chunk-5EH7WMME.js";
18
+ } from "./chunk-KISVLXVZ.js";
19
19
  import {
20
20
  getAvailabilityCache
21
- } from "./chunk-TIOXPJIO.js";
21
+ } from "./chunk-25QNW65Q.js";
22
22
  import {
23
23
  generateHyphenId
24
- } from "./chunk-LUCPCWIZ.js";
24
+ } from "./chunk-BUHPYOT3.js";
25
25
  import {
26
26
  CLI_SUBPROCESS_TIMEOUTS,
27
27
  CLI_TIMEOUTS,
@@ -55,7 +55,7 @@ import {
55
55
  loadModelsDevSnapshot,
56
56
  ok,
57
57
  resolveCliAlias
58
- } from "./chunk-JRBUSUYD.js";
58
+ } from "./chunk-DHU2YECH.js";
59
59
 
60
60
  // src/cli-adapters/cli-to-model-adapter.ts
61
61
  var CliToModelAdapter = class {
@@ -4793,4 +4793,4 @@ export {
4793
4793
  SdkAdapter,
4794
4794
  createAutoAdapter
4795
4795
  };
4796
- //# sourceMappingURL=chunk-F2ULTSZ6.js.map
4796
+ //# sourceMappingURL=chunk-H4EXCQCF.js.map
@@ -2,7 +2,7 @@ import {
2
2
  createLogger,
3
3
  err,
4
4
  ok
5
- } from "./chunk-JRBUSUYD.js";
5
+ } from "./chunk-DHU2YECH.js";
6
6
 
7
7
  // src/scm/token-resolver.ts
8
8
  import { execFile } from "child_process";
@@ -87,4 +87,4 @@ export {
87
87
  hasToken,
88
88
  getTokenEnvVars
89
89
  };
90
- //# sourceMappingURL=chunk-2QBR4RVO.js.map
90
+ //# sourceMappingURL=chunk-HEDW5QDP.js.map
@@ -1,147 +1,13 @@
1
1
  import {
2
- resolveInsideRoot
3
- } from "./chunk-NUBSJGQZ.js";
2
+ loadPapersRegistry
3
+ } from "./chunk-FO4A6HA3.js";
4
4
  import {
5
- ParseError,
6
- SecurityError,
7
- err,
8
- getErrorMessage,
9
- ok,
10
- parseBoolEnv
11
- } from "./chunk-JRBUSUYD.js";
5
+ getErrorMessage
6
+ } from "./chunk-DHU2YECH.js";
12
7
 
13
8
  // src/cli/research-helpers-synthesize.ts
14
9
  import { z } from "zod";
15
10
 
16
- // src/cli/research-helpers-io.ts
17
- import * as fs2 from "fs/promises";
18
- import { join, resolve as resolve2 } from "path";
19
- import { parse as parseYaml, stringify as stringifyYaml2 } from "yaml";
20
-
21
- // src/cli/research-scaffold.ts
22
- import * as fs from "fs/promises";
23
- import { existsSync } from "fs";
24
- import { dirname, resolve } from "path";
25
- import { stringify as stringifyYaml } from "yaml";
26
- var SCHEMA_VERSION = "1.0";
27
- var ANNOUNCED = /* @__PURE__ */ new Set();
28
- function announceOnce(path, kind) {
29
- if (ANNOUNCED.has(path)) return;
30
- ANNOUNCED.add(path);
31
- process.stderr.write(
32
- `[scaffold] Created empty ${kind} at ${path}; add entries via 'nexus-agents research add' or research_add MCP tool.
33
- `
34
- );
35
- }
36
- function scaffoldDisabled() {
37
- return parseBoolEnv("NEXUS_NO_SCAFFOLD", false);
38
- }
39
- function emptyPapersYaml() {
40
- return stringifyYaml({ schema_version: SCHEMA_VERSION, papers: {} });
41
- }
42
- function emptyTechniquesYaml() {
43
- return stringifyYaml({ schema_version: SCHEMA_VERSION, techniques: {} });
44
- }
45
- async function ensureRegistryFile(rootDir, filename) {
46
- const filePath = resolve(rootDir, REGISTRY_PATH, filename);
47
- if (existsSync(filePath)) return ok(filePath);
48
- if (scaffoldDisabled()) {
49
- return err(
50
- new ParseError(
51
- `Registry file missing: ${filePath}. Scaffolding disabled (NEXUS_NO_SCAFFOLD=1). Create the file manually or unset the env var.`
52
- )
53
- );
54
- }
55
- if (!existsSync(resolve(rootDir, "docs"))) {
56
- return err(
57
- new ParseError(
58
- `Registry file missing: ${filePath}. ${rootDir}/docs/ does not exist; refusing to create a Nexus subtree in a non-documented root. Run 'nexus-agents init' first, or create ${rootDir}/docs/ manually.`
59
- )
60
- );
61
- }
62
- try {
63
- await fs.mkdir(dirname(filePath), { recursive: true });
64
- const content = filename === PAPERS_FILE ? emptyPapersYaml() : emptyTechniquesYaml();
65
- await fs.writeFile(filePath, content, "utf-8");
66
- announceOnce(filePath, filename);
67
- return ok(filePath);
68
- } catch (e) {
69
- return err(
70
- new ParseError(
71
- `Failed to scaffold ${filename} at ${filePath}: ${getErrorMessage(e)}. Set NEXUS_NO_SCAFFOLD=1 and create manually if scaffolding is unwanted.`
72
- )
73
- );
74
- }
75
- }
76
-
77
- // src/cli/research-helpers-io.ts
78
- var REGISTRY_PATH = "docs/research/registry";
79
- var TECHNIQUES_FILE = "techniques.yaml";
80
- var PAPERS_FILE = "papers.yaml";
81
- function getProjectRoot() {
82
- return process.cwd();
83
- }
84
- function validatePath(constructedPath, allowedRoot) {
85
- const resolved = resolveInsideRoot(constructedPath, allowedRoot);
86
- if (resolved === null) {
87
- return {
88
- ok: false,
89
- error: new SecurityError("Path traversal detected: path escapes allowed root directory", {
90
- context: { constructedPath, allowedRoot: resolve2(allowedRoot) }
91
- })
92
- };
93
- }
94
- return { ok: true, value: resolved };
95
- }
96
- async function loadTechniquesRegistry(rootDir) {
97
- const root = rootDir ?? process.cwd();
98
- const filePath = join(root, REGISTRY_PATH, TECHNIQUES_FILE);
99
- const pathValidation = validatePath(filePath, root);
100
- if (!pathValidation.ok) {
101
- return pathValidation;
102
- }
103
- await ensureRegistryFile(root, TECHNIQUES_FILE);
104
- try {
105
- const content = await fs2.readFile(pathValidation.value, "utf-8");
106
- return { ok: true, value: parseYaml(content) };
107
- } catch (error) {
108
- const message = getErrorMessage(error);
109
- return { ok: false, error: new ParseError(`Failed to load techniques registry: ${message}`) };
110
- }
111
- }
112
- async function loadPapersRegistry(rootDir) {
113
- const root = rootDir ?? process.cwd();
114
- const filePath = join(root, REGISTRY_PATH, PAPERS_FILE);
115
- const pathValidation = validatePath(filePath, root);
116
- if (!pathValidation.ok) {
117
- return pathValidation;
118
- }
119
- await ensureRegistryFile(root, PAPERS_FILE);
120
- try {
121
- const content = await fs2.readFile(pathValidation.value, "utf-8");
122
- return { ok: true, value: parseYaml(content) };
123
- } catch (error) {
124
- const message = getErrorMessage(error);
125
- return { ok: false, error: new ParseError(`Failed to load papers registry: ${message}`) };
126
- }
127
- }
128
- async function savePapersRegistry(registry, rootDir) {
129
- const root = rootDir ?? process.cwd();
130
- const filePath = join(root, REGISTRY_PATH, PAPERS_FILE);
131
- const pathValidation = validatePath(filePath, root);
132
- if (!pathValidation.ok) {
133
- return pathValidation;
134
- }
135
- try {
136
- const content = stringifyYaml2(registry, { lineWidth: 100 });
137
- await fs2.writeFile(pathValidation.value, content, "utf-8");
138
- return { ok: true, value: void 0 };
139
- } catch (error) {
140
- const message = getErrorMessage(error);
141
- return { ok: false, error: new ParseError(`Failed to save papers registry: ${message}`) };
142
- }
143
- }
144
-
145
11
  // src/cli/research-alignment-map.ts
146
12
  var TECHNIQUE_IMPLEMENTATION_MAP = /* @__PURE__ */ new Map([
147
13
  // Routing techniques
@@ -1006,13 +872,8 @@ function attributeFindings(papers) {
1006
872
  }
1007
873
 
1008
874
  export {
1009
- REGISTRY_PATH,
1010
- getProjectRoot,
1011
- loadTechniquesRegistry,
1012
- loadPapersRegistry,
1013
- savePapersRegistry,
1014
875
  normalizeTopicToCanonical,
1015
876
  AttributedInsightSchema,
1016
877
  synthesizeResearch
1017
878
  };
1018
- //# sourceMappingURL=chunk-ZSJG7EB2.js.map
879
+ //# sourceMappingURL=chunk-HN6X3WJB.js.map