nexus-agents 7.0.2 → 8.0.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 (165) hide show
  1. package/dist/{child-mcp-config-ORXLS5HP.js → child-mcp-config-IPF4QIVB.js} +7 -7
  2. package/dist/chunk-227USJYZ.js +7 -0
  3. package/dist/{chunk-UVO673OY.js → chunk-2XXKUAQS.js} +3 -3
  4. package/dist/{chunk-Y742U2YM.js → chunk-3MBGQAO2.js} +124 -104
  5. package/dist/chunk-3MBGQAO2.js.map +1 -0
  6. package/dist/{chunk-4OW67KT2.js → chunk-4HXPNLZS.js} +6 -6
  7. package/dist/{chunk-W4Q5NZYZ.js → chunk-4QHAMZLU.js} +3 -3
  8. package/dist/{chunk-7U7HAYPB.js → chunk-4U62WRPL.js} +25 -59
  9. package/dist/chunk-4U62WRPL.js.map +1 -0
  10. package/dist/{chunk-AGFXJUTS.js → chunk-4VZ5GK5B.js} +14 -14
  11. package/dist/{chunk-XZEYSIGG.js → chunk-5YC3SC34.js} +4 -4
  12. package/dist/{chunk-RSMG2DPB.js → chunk-64GMIUM5.js} +5 -5
  13. package/dist/{chunk-RBF64DXJ.js → chunk-6LDUEA4I.js} +5 -5
  14. package/dist/{chunk-3CGUPSBW.js → chunk-6MPT2NRF.js} +2 -2
  15. package/dist/{chunk-33FI7W3G.js → chunk-7RLFZ6OB.js} +3 -3
  16. package/dist/{chunk-KJWD2V4K.js → chunk-CCRTVIXF.js} +7 -7
  17. package/dist/{chunk-HFOQKCD2.js → chunk-DGWT7DXX.js} +3 -3
  18. package/dist/{chunk-HFOQKCD2.js.map → chunk-DGWT7DXX.js.map} +1 -1
  19. package/dist/{chunk-XREG2HTQ.js → chunk-EWKBHDAU.js} +4 -4
  20. package/dist/{chunk-QAHGHWC7.js → chunk-F33RCSOC.js} +23 -23
  21. package/dist/{chunk-D6TL5GZV.js → chunk-F4AT3BCP.js} +5 -5
  22. package/dist/{chunk-Q6IODDEA.js → chunk-FBVOBOHS.js} +12 -12
  23. package/dist/{chunk-Q6IODDEA.js.map → chunk-FBVOBOHS.js.map} +1 -1
  24. package/dist/{chunk-LVYRGNOC.js → chunk-FFZNRJ5Q.js} +2 -2
  25. package/dist/{chunk-Y5GYBP52.js → chunk-FKWMYLSZ.js} +4 -4
  26. package/dist/{chunk-KUYKSKPV.js → chunk-GHIHBN4J.js} +214 -219
  27. package/dist/chunk-GHIHBN4J.js.map +1 -0
  28. package/dist/{chunk-JNZ3D5B5.js → chunk-HBHTXBA4.js} +4 -4
  29. package/dist/{chunk-RLBJTZHA.js → chunk-HS43MFL4.js} +2 -2
  30. package/dist/{chunk-GAYSJGKD.js → chunk-INNWBLE4.js} +13 -13
  31. package/dist/{chunk-GBHJ37V3.js → chunk-ITCKXMOH.js} +4 -4
  32. package/dist/{chunk-K2AWJGQO.js → chunk-K6EHC227.js} +8 -8
  33. package/dist/{chunk-JJT7NVNY.js → chunk-LTTUQNP2.js} +4 -4
  34. package/dist/{chunk-OGHEKPLD.js → chunk-MM223CVS.js} +10 -10
  35. package/dist/{chunk-JEAKVUWM.js → chunk-ODBLHHTP.js} +3 -3
  36. package/dist/{chunk-6GYTTWNM.js → chunk-OM5BVTVF.js} +2 -2
  37. package/dist/{chunk-IWTK6FDT.js → chunk-QP423I4T.js} +4 -4
  38. package/dist/{chunk-WLIXCNZ6.js → chunk-RB3X45G5.js} +24 -24
  39. package/dist/chunk-RB3X45G5.js.map +1 -0
  40. package/dist/{chunk-VVXHIUFQ.js → chunk-T7XKLZPC.js} +6 -6
  41. package/dist/{chunk-ZGEDIKAM.js → chunk-TO5LX5VU.js} +4 -4
  42. package/dist/{chunk-6Q3KSZVI.js → chunk-U5YJKJK2.js} +2 -2
  43. package/dist/{chunk-BTWKDT4F.js → chunk-UZULX2DZ.js} +20 -20
  44. package/dist/{chunk-OHULIRP6.js → chunk-VMMMRHCW.js} +19 -19
  45. package/dist/{chunk-S2Z7CME3.js → chunk-W2R6UVZJ.js} +2 -2
  46. package/dist/{chunk-ZD4VEHEG.js → chunk-Y5I3F3YC.js} +2 -2
  47. package/dist/{chunk-KSQSWEFH.js → chunk-YYJFZ7TO.js} +26 -11
  48. package/dist/chunk-YYJFZ7TO.js.map +1 -0
  49. package/dist/{chunk-VYAXMVI5.js → chunk-ZYPEHPRM.js} +5 -5
  50. package/dist/{chunk-VYAXMVI5.js.map → chunk-ZYPEHPRM.js.map} +1 -1
  51. package/dist/{cli-circuit-breaker-5FYJDS5O.js → cli-circuit-breaker-KRIR74SW.js} +5 -5
  52. package/dist/cli.js +378 -379
  53. package/dist/cli.js.map +1 -1
  54. package/dist/{codebase-search-RHBGEWXK.js → codebase-search-3D4TW3KR.js} +2 -2
  55. package/dist/{composite-router-GE24MYPP.js → composite-router-VTPCCQQU.js} +4 -4
  56. package/dist/{consensus-vote-62FAN4ZN.js → consensus-vote-6MHOJFIA.js} +22 -22
  57. package/dist/{context-retriever-3OO6N4V6.js → context-retriever-ASWO5E6N.js} +12 -12
  58. package/dist/{dist-IKZ53APZ.js → dist-TJCQ3JLP.js} +2 -2
  59. package/dist/doctor-deep-WLVSUPFA.js +13 -0
  60. package/dist/{doctor-live-J7JIZH3T.js → doctor-live-5L4RI4GW.js} +13 -13
  61. package/dist/{expert-bridge-PWRVRFDG.js → expert-bridge-K4FSDGPL.js} +6 -6
  62. package/dist/factory-DQWPXJHP.js +15 -0
  63. package/dist/factory-X4PUAG26.js +25 -0
  64. package/dist/{improvement-review-QUU5HR7Z.js → improvement-review-UV42QAVL.js} +8 -8
  65. package/dist/index.d.ts +58 -56
  66. package/dist/index.js +65 -76
  67. package/dist/index.js.map +1 -1
  68. package/dist/{init-opencode-GDPE4U65.js → init-opencode-73YZUQQX.js} +10 -10
  69. package/dist/issue-triage-ATDH5B5W.js +17 -0
  70. package/dist/{learning-persistence-7O5QYDM7.js → learning-persistence-7N5SZHNX.js} +3 -3
  71. package/dist/{pr-reviewer-helpers-KBOP72XG.js → pr-reviewer-helpers-XRAYFBTQ.js} +6 -6
  72. package/dist/{registry-command-ZK4NQ2MX.js → registry-command-O7TLGMNL.js} +7 -7
  73. package/dist/{repo-analyze-UJOWFGNM.js → repo-analyze-DI65XOTQ.js} +2 -2
  74. package/dist/{repo-security-plan-SZU4O2TN.js → repo-security-plan-OAOLRK5Z.js} +6 -6
  75. package/dist/research-helpers-synthesize-J66KQW2N.js +14 -0
  76. package/dist/{routing-memory-2SMMUPTB.js → routing-memory-TLALXQEY.js} +4 -4
  77. package/dist/{session-memory-YQQVPLKM.js → session-memory-DMXEXMOD.js} +5 -5
  78. package/dist/setup-config-THXHCRSH.js +11 -0
  79. package/dist/{setup-custom-api-ZSBUIJID.js → setup-custom-api-EETAAEAD.js} +7 -7
  80. package/dist/setup-data-dir-SBZB5KMI.js +29 -0
  81. package/dist/tool-memory-RSRUWJFE.js +22 -0
  82. package/dist/tool-memory-registry-adapters-PCOYMYRF.js +12 -0
  83. package/dist/unified-registry-OHYTMXKA.js +26 -0
  84. package/dist/{weather-report-6AVPMHHM.js → weather-report-R5NAYRQK.js} +4 -4
  85. package/package.json +4 -8
  86. package/dist/chunk-37JIYDVH.js +0 -7
  87. package/dist/chunk-7U7HAYPB.js.map +0 -1
  88. package/dist/chunk-KSQSWEFH.js.map +0 -1
  89. package/dist/chunk-KUYKSKPV.js.map +0 -1
  90. package/dist/chunk-WLIXCNZ6.js.map +0 -1
  91. package/dist/chunk-Y742U2YM.js.map +0 -1
  92. package/dist/doctor-deep-6WHVRRVQ.js +0 -13
  93. package/dist/factory-3BZDQQYM.js +0 -25
  94. package/dist/factory-UFKRSADE.js +0 -15
  95. package/dist/issue-triage-2LNYGQRJ.js +0 -17
  96. package/dist/research-helpers-synthesize-QY3DDHTD.js +0 -14
  97. package/dist/setup-config-LMZE4IRV.js +0 -11
  98. package/dist/setup-data-dir-ZEH7TFA6.js +0 -29
  99. package/dist/tool-memory-GUPG2M25.js +0 -22
  100. package/dist/tool-memory-registry-adapters-IMRSOAAV.js +0 -12
  101. package/dist/unified-registry-RPT4O3XB.js +0 -26
  102. package/scripts/postinstall.js +0 -12
  103. /package/dist/{child-mcp-config-ORXLS5HP.js.map → child-mcp-config-IPF4QIVB.js.map} +0 -0
  104. /package/dist/{chunk-37JIYDVH.js.map → chunk-227USJYZ.js.map} +0 -0
  105. /package/dist/{chunk-UVO673OY.js.map → chunk-2XXKUAQS.js.map} +0 -0
  106. /package/dist/{chunk-4OW67KT2.js.map → chunk-4HXPNLZS.js.map} +0 -0
  107. /package/dist/{chunk-W4Q5NZYZ.js.map → chunk-4QHAMZLU.js.map} +0 -0
  108. /package/dist/{chunk-AGFXJUTS.js.map → chunk-4VZ5GK5B.js.map} +0 -0
  109. /package/dist/{chunk-XZEYSIGG.js.map → chunk-5YC3SC34.js.map} +0 -0
  110. /package/dist/{chunk-RSMG2DPB.js.map → chunk-64GMIUM5.js.map} +0 -0
  111. /package/dist/{chunk-RBF64DXJ.js.map → chunk-6LDUEA4I.js.map} +0 -0
  112. /package/dist/{chunk-3CGUPSBW.js.map → chunk-6MPT2NRF.js.map} +0 -0
  113. /package/dist/{chunk-33FI7W3G.js.map → chunk-7RLFZ6OB.js.map} +0 -0
  114. /package/dist/{chunk-KJWD2V4K.js.map → chunk-CCRTVIXF.js.map} +0 -0
  115. /package/dist/{chunk-XREG2HTQ.js.map → chunk-EWKBHDAU.js.map} +0 -0
  116. /package/dist/{chunk-QAHGHWC7.js.map → chunk-F33RCSOC.js.map} +0 -0
  117. /package/dist/{chunk-D6TL5GZV.js.map → chunk-F4AT3BCP.js.map} +0 -0
  118. /package/dist/{chunk-LVYRGNOC.js.map → chunk-FFZNRJ5Q.js.map} +0 -0
  119. /package/dist/{chunk-Y5GYBP52.js.map → chunk-FKWMYLSZ.js.map} +0 -0
  120. /package/dist/{chunk-JNZ3D5B5.js.map → chunk-HBHTXBA4.js.map} +0 -0
  121. /package/dist/{chunk-RLBJTZHA.js.map → chunk-HS43MFL4.js.map} +0 -0
  122. /package/dist/{chunk-GAYSJGKD.js.map → chunk-INNWBLE4.js.map} +0 -0
  123. /package/dist/{chunk-GBHJ37V3.js.map → chunk-ITCKXMOH.js.map} +0 -0
  124. /package/dist/{chunk-K2AWJGQO.js.map → chunk-K6EHC227.js.map} +0 -0
  125. /package/dist/{chunk-JJT7NVNY.js.map → chunk-LTTUQNP2.js.map} +0 -0
  126. /package/dist/{chunk-OGHEKPLD.js.map → chunk-MM223CVS.js.map} +0 -0
  127. /package/dist/{chunk-JEAKVUWM.js.map → chunk-ODBLHHTP.js.map} +0 -0
  128. /package/dist/{chunk-6GYTTWNM.js.map → chunk-OM5BVTVF.js.map} +0 -0
  129. /package/dist/{chunk-IWTK6FDT.js.map → chunk-QP423I4T.js.map} +0 -0
  130. /package/dist/{chunk-VVXHIUFQ.js.map → chunk-T7XKLZPC.js.map} +0 -0
  131. /package/dist/{chunk-ZGEDIKAM.js.map → chunk-TO5LX5VU.js.map} +0 -0
  132. /package/dist/{chunk-6Q3KSZVI.js.map → chunk-U5YJKJK2.js.map} +0 -0
  133. /package/dist/{chunk-BTWKDT4F.js.map → chunk-UZULX2DZ.js.map} +0 -0
  134. /package/dist/{chunk-OHULIRP6.js.map → chunk-VMMMRHCW.js.map} +0 -0
  135. /package/dist/{chunk-S2Z7CME3.js.map → chunk-W2R6UVZJ.js.map} +0 -0
  136. /package/dist/{chunk-ZD4VEHEG.js.map → chunk-Y5I3F3YC.js.map} +0 -0
  137. /package/dist/{cli-circuit-breaker-5FYJDS5O.js.map → cli-circuit-breaker-KRIR74SW.js.map} +0 -0
  138. /package/dist/{codebase-search-RHBGEWXK.js.map → codebase-search-3D4TW3KR.js.map} +0 -0
  139. /package/dist/{composite-router-GE24MYPP.js.map → composite-router-VTPCCQQU.js.map} +0 -0
  140. /package/dist/{consensus-vote-62FAN4ZN.js.map → consensus-vote-6MHOJFIA.js.map} +0 -0
  141. /package/dist/{context-retriever-3OO6N4V6.js.map → context-retriever-ASWO5E6N.js.map} +0 -0
  142. /package/dist/{dist-IKZ53APZ.js.map → dist-TJCQ3JLP.js.map} +0 -0
  143. /package/dist/{doctor-deep-6WHVRRVQ.js.map → doctor-deep-WLVSUPFA.js.map} +0 -0
  144. /package/dist/{doctor-live-J7JIZH3T.js.map → doctor-live-5L4RI4GW.js.map} +0 -0
  145. /package/dist/{expert-bridge-PWRVRFDG.js.map → expert-bridge-K4FSDGPL.js.map} +0 -0
  146. /package/dist/{factory-3BZDQQYM.js.map → factory-DQWPXJHP.js.map} +0 -0
  147. /package/dist/{factory-UFKRSADE.js.map → factory-X4PUAG26.js.map} +0 -0
  148. /package/dist/{improvement-review-QUU5HR7Z.js.map → improvement-review-UV42QAVL.js.map} +0 -0
  149. /package/dist/{init-opencode-GDPE4U65.js.map → init-opencode-73YZUQQX.js.map} +0 -0
  150. /package/dist/{issue-triage-2LNYGQRJ.js.map → issue-triage-ATDH5B5W.js.map} +0 -0
  151. /package/dist/{learning-persistence-7O5QYDM7.js.map → learning-persistence-7N5SZHNX.js.map} +0 -0
  152. /package/dist/{pr-reviewer-helpers-KBOP72XG.js.map → pr-reviewer-helpers-XRAYFBTQ.js.map} +0 -0
  153. /package/dist/{registry-command-ZK4NQ2MX.js.map → registry-command-O7TLGMNL.js.map} +0 -0
  154. /package/dist/{repo-analyze-UJOWFGNM.js.map → repo-analyze-DI65XOTQ.js.map} +0 -0
  155. /package/dist/{repo-security-plan-SZU4O2TN.js.map → repo-security-plan-OAOLRK5Z.js.map} +0 -0
  156. /package/dist/{research-helpers-synthesize-QY3DDHTD.js.map → research-helpers-synthesize-J66KQW2N.js.map} +0 -0
  157. /package/dist/{routing-memory-2SMMUPTB.js.map → routing-memory-TLALXQEY.js.map} +0 -0
  158. /package/dist/{session-memory-YQQVPLKM.js.map → session-memory-DMXEXMOD.js.map} +0 -0
  159. /package/dist/{setup-config-LMZE4IRV.js.map → setup-config-THXHCRSH.js.map} +0 -0
  160. /package/dist/{setup-custom-api-ZSBUIJID.js.map → setup-custom-api-EETAAEAD.js.map} +0 -0
  161. /package/dist/{setup-data-dir-ZEH7TFA6.js.map → setup-data-dir-SBZB5KMI.js.map} +0 -0
  162. /package/dist/{tool-memory-GUPG2M25.js.map → tool-memory-RSRUWJFE.js.map} +0 -0
  163. /package/dist/{tool-memory-registry-adapters-IMRSOAAV.js.map → tool-memory-registry-adapters-PCOYMYRF.js.map} +0 -0
  164. /package/dist/{unified-registry-RPT4O3XB.js.map → unified-registry-OHYTMXKA.js.map} +0 -0
  165. /package/dist/{weather-report-6AVPMHHM.js.map → weather-report-R5NAYRQK.js.map} +0 -0
@@ -8,7 +8,7 @@ import {
8
8
  getTimeProvider,
9
9
  isRateLimitText,
10
10
  ok
11
- } from "./chunk-Y742U2YM.js";
11
+ } from "./chunk-3MBGQAO2.js";
12
12
 
13
13
  // src/cli-adapters/circuit-breaker-types.ts
14
14
  var CircuitErrorCode = {
@@ -541,4 +541,4 @@ export {
541
541
  CliCircuitBreakerIntegration,
542
542
  createCliCircuitBreakerIntegration
543
543
  };
544
- //# sourceMappingURL=chunk-S2Z7CME3.js.map
544
+ //# sourceMappingURL=chunk-W2R6UVZJ.js.map
@@ -11,7 +11,7 @@ import {
11
11
  createGetRetryConfig,
12
12
  createGetTimeout,
13
13
  createGetToolRateLimit
14
- } from "./chunk-Y742U2YM.js";
14
+ } from "./chunk-3MBGQAO2.js";
15
15
 
16
16
  // src/config/defaults.ts
17
17
  var CUSTOM_API_DEFAULT_MODEL = "gpt-5.5";
@@ -230,4 +230,4 @@ export {
230
230
  CUSTOM_API_DEFAULT_MODEL,
231
231
  DEFAULTS
232
232
  };
233
- //# sourceMappingURL=chunk-ZD4VEHEG.js.map
233
+ //# sourceMappingURL=chunk-Y5I3F3YC.js.map
@@ -1,11 +1,9 @@
1
1
  // ../nexus-memory/dist/index.js
2
- import "better-sqlite3";
3
- import Database from "better-sqlite3";
4
- import { mkdirSync } from "fs";
5
- import { dirname } from "path";
6
- import "better-sqlite3";
7
- import { existsSync, mkdirSync as mkdirSync2, renameSync, writeFileSync } from "fs";
8
- import { dirname as dirname2, join } from "path";
2
+ import { createRequire } from "node:module";
3
+ import { mkdirSync } from "node:fs";
4
+ import { dirname } from "node:path";
5
+ import { existsSync, mkdirSync as mkdirSync2, renameSync, writeFileSync } from "node:fs";
6
+ import { dirname as dirname2, join } from "node:path";
9
7
  var KEY_SUMMARY_LIMIT = 120;
10
8
  var PAYLOAD_SUMMARY_LIMIT = 240;
11
9
  var counters = /* @__PURE__ */ new Map();
@@ -244,13 +242,30 @@ var InMemoryBackend = class {
244
242
  }
245
243
  }
246
244
  };
245
+ var requireFromHere = createRequire(import.meta.url);
246
+ function loadSqlite() {
247
+ return requireFromHere("node:sqlite");
248
+ }
247
249
  function openSqliteDatabase(dbPath) {
248
250
  if (dbPath !== ":memory:" && dbPath !== "") {
249
251
  mkdirSync(dirname(dbPath), { recursive: true });
250
252
  }
251
- const db = new Database(dbPath);
252
- db.pragma("journal_mode = WAL");
253
- return db;
253
+ const { DatabaseSync } = loadSqlite();
254
+ const db = new DatabaseSync(dbPath);
255
+ db.exec("PRAGMA journal_mode = WAL");
256
+ const narrowed = db;
257
+ return {
258
+ exec: (sql) => {
259
+ db.exec(sql);
260
+ },
261
+ prepare: narrowed.prepare.bind(narrowed),
262
+ // better-sqlite3's `close()` is idempotent; `DatabaseSync`'s throws
263
+ // `database is not open` on a second call, and shutdown paths here are
264
+ // reentrant. Preserve the contract callers were written against (#5388).
265
+ close: () => {
266
+ if (db.isOpen) db.close();
267
+ }
268
+ };
254
269
  }
255
270
  function buildWriteRow(key, value, meta) {
256
271
  return {
@@ -653,4 +668,4 @@ export {
653
668
  runImporters,
654
669
  backupSourceFile
655
670
  };
656
- //# sourceMappingURL=chunk-KSQSWEFH.js.map
671
+ //# sourceMappingURL=chunk-YYJFZ7TO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../nexus-memory/src/telemetry.ts","../../nexus-memory/src/backends/memory.ts","../../nexus-memory/src/backends/open-database.ts","../../nexus-memory/src/backends/sqlite.ts","../../nexus-memory/src/registry.ts","../../nexus-memory/src/factory.ts","../../nexus-memory/src/importer.ts"],"sourcesContent":["/**\n * Memory telemetry — aggregated counters (default) + opt-in full-audit mode.\n *\n * Phase 2 vote (#2768) settled on **C**: per-`{domain, op}` counters in\n * steady state, full per-event payloads when `NEXUS_MEMORY_AUDIT_MODE=audit`.\n *\n * The catfish-mitigation requirement is honored: audit mode emits\n * `{ op, keySummary, payloadSummary, resultSummary, cli, durationMs, hit }`\n * — not just counters — so an incident replay can reconstruct individual\n * ops. Summaries are truncated to keep emission cheap.\n *\n * @module nexus-memory/telemetry\n */\n\nimport type { MemoryEvent, MemoryEventCounters, MemoryEventListener } from './types.js';\n\nconst KEY_SUMMARY_LIMIT = 120;\nconst PAYLOAD_SUMMARY_LIMIT = 240;\n\n/** Per-`{domain, op}` rolling counters. */\nconst counters = new Map<string, MemoryEventCounters>();\nconst listeners = new Set<MemoryEventListener>();\n\n/** Audit mode is opt-in via env. Checked once per emission so the env\n * can be toggled at runtime by tests via `process.env`. */\nfunction isAuditMode(): boolean {\n return process.env['NEXUS_MEMORY_AUDIT_MODE'] === 'audit';\n}\n\nfunction counterKey(domain: string, op: MemoryEvent['op']): string {\n return `${domain}::${op}`;\n}\n\nfunction truncate(value: unknown, limit: number): string {\n let s: string;\n if (typeof value === 'string') {\n s = value;\n } else if (value === undefined) {\n s = '<undefined>';\n } else if (value === null) {\n s = '<null>';\n } else {\n try {\n s = JSON.stringify(value);\n } catch {\n s = '<unserializable>';\n }\n }\n return s.length > limit ? `${s.slice(0, limit - 1)}…` : s;\n}\n\ninterface RecordedEvent extends Omit<\n MemoryEvent,\n 'keySummary' | 'payloadSummary' | 'resultSummary'\n> {\n readonly key?: unknown;\n readonly payload?: unknown;\n readonly result?: unknown;\n}\n\nfunction updateCounter(event: RecordedEvent): void {\n const ck = counterKey(event.domain, event.op);\n const existing = counters.get(ck);\n const hitDelta = event.hit === true ? 1 : 0;\n counters.set(ck, {\n domain: event.domain,\n op: event.op,\n count: (existing?.count ?? 0) + 1,\n hitCount: (existing?.hitCount ?? 0) + hitDelta,\n totalDurationMs: (existing?.totalDurationMs ?? 0) + event.durationMs,\n maxDurationMs: Math.max(existing?.maxDurationMs ?? 0, event.durationMs),\n });\n}\n\nfunction buildPublicEvent(event: RecordedEvent, audit: boolean): MemoryEvent {\n return {\n domain: event.domain,\n op: event.op,\n durationMs: event.durationMs,\n ...(event.cli !== undefined && { cli: event.cli }),\n ...(event.hit !== undefined && { hit: event.hit }),\n ...(audit &&\n event.key !== undefined && {\n keySummary: truncate(event.key, KEY_SUMMARY_LIMIT),\n }),\n ...(audit &&\n event.payload !== undefined && {\n payloadSummary: truncate(event.payload, PAYLOAD_SUMMARY_LIMIT),\n }),\n ...(audit &&\n event.result !== undefined && {\n resultSummary: truncate(event.result, PAYLOAD_SUMMARY_LIMIT),\n }),\n };\n}\n\n/**\n * Record a memory operation. Updates counters always; emits the event\n * to subscribers always (subscribers get the full event in audit mode,\n * the aggregate-only event otherwise).\n *\n * Implementation note: the `op` argument is the typed `MemoryEvent['op']`\n * literal — backends never pass an unknown string here.\n */\nexport function recordMemoryEvent(event: RecordedEvent): void {\n updateCounter(event);\n const publicEvent = buildPublicEvent(event, isAuditMode());\n for (const listener of listeners) {\n try {\n listener(publicEvent);\n } catch {\n // Subscriber failures must never affect memory operations.\n }\n }\n}\n\n/** Snapshot of current counters. Returned array is a copy. */\nexport function getMemoryEventCounters(): readonly MemoryEventCounters[] {\n return [...counters.values()];\n}\n\n/** Subscribe to the per-event stream. Returns an unsubscribe function. */\nexport function subscribeToMemoryEvents(listener: MemoryEventListener): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\n/**\n * Reset counters + drop all subscribers. Tests should call this in\n * `beforeEach` to keep state from leaking.\n */\nexport function resetMemoryTelemetry(): void {\n counters.clear();\n listeners.clear();\n}\n","/**\n * In-memory backend — primarily for tests, but also used as the cold-archive\n * fallback when no `dbPath` is provided.\n *\n * Implements {@link IMemoryBackend}; passes the same contract test as\n * {@link SqliteBackend}.\n *\n * @module nexus-memory/backends/memory\n */\n\nimport type { z } from 'zod';\nimport { recordMemoryEvent } from '../telemetry.js';\nimport type { BackendStats, IMemoryBackend, QueryFilter, WriteMeta } from '../types.js';\n\ninterface Row<TValue> {\n readonly value: TValue;\n readonly cli?: string;\n readonly source?: string;\n readonly timestamp: number;\n readonly trustTier?: 1 | 2 | 3 | 4;\n}\n\nexport interface InMemoryBackendOptions<TValue> {\n readonly domain: string;\n /**\n * Optional Zod schema. Phase 2 vote mitigation #1 (security dissent):\n * when supplied, every `write()` validates the value first; invalid\n * payloads throw `MemoryValidationError`.\n */\n readonly schema?: z.ZodType<TValue>;\n}\n\nexport class MemoryValidationError extends Error {\n constructor(domain: string, cause: unknown) {\n super(`nexus-memory: write rejected for domain \"${domain}\": ${String(cause)}`);\n this.name = 'MemoryValidationError';\n }\n}\n\nfunction buildInMemoryRow<TValue>(value: TValue, meta: WriteMeta | undefined): Row<TValue> {\n return {\n value,\n ...(meta?.cli !== undefined && { cli: meta.cli }),\n ...(meta?.source !== undefined && { source: meta.source }),\n timestamp: meta?.timestamp ?? Date.now(),\n ...(meta?.trustTier !== undefined && { trustTier: meta.trustTier }),\n };\n}\n\n/** Apply `where`, `orderBy`, `limit` to an in-memory row set. Extracted from\n * `query` to satisfy the eslint complexity gate. */\nfunction applyInMemoryFilter<T>(values: T[], filter?: QueryFilter<T>): T[] {\n let out = values;\n if (filter?.where !== undefined) {\n const where = filter.where;\n out = out.filter((v) => {\n for (const [k, expected] of Object.entries(where)) {\n if ((v as Record<string, unknown>)[k] !== expected) return false;\n }\n return true;\n });\n }\n if (filter?.orderBy !== undefined) {\n const orderBy = filter.orderBy;\n const dir = filter.orderDir === 'desc' ? -1 : 1;\n out = [...out].sort((a, b) => {\n const av = (a as Record<string | symbol | number, unknown>)[orderBy];\n const bv = (b as Record<string | symbol | number, unknown>)[orderBy];\n if (av === bv) return 0;\n if (av === undefined || av === null) return 1;\n if (bv === undefined || bv === null) return -1;\n return (av < bv ? -1 : 1) * dir;\n });\n }\n if (filter?.limit !== undefined) {\n out = out.slice(0, filter.limit);\n }\n return out;\n}\n\nexport class InMemoryBackend<TKey, TValue> implements IMemoryBackend<TKey, TValue> {\n readonly domain: string;\n private readonly rows = new Map<TKey, Row<TValue>>();\n private readonly schema?: z.ZodType<TValue>;\n private closed = false;\n\n constructor(options: InMemoryBackendOptions<TValue>) {\n this.domain = options.domain;\n if (options.schema !== undefined) {\n this.schema = options.schema;\n }\n }\n\n async read(key: TKey): Promise<TValue | undefined> {\n this.assertOpen();\n const start = Date.now();\n const row = this.rows.get(key);\n recordMemoryEvent({\n domain: this.domain,\n op: 'read',\n hit: row !== undefined,\n durationMs: Date.now() - start,\n key,\n result: row?.value,\n });\n return Promise.resolve(row?.value);\n }\n\n private validate(value: TValue): void {\n // #4021: reject `undefined` uniformly (before the optional schema check) so\n // both backends behave identically. Previously this backend stored a phantom\n // row for `write(key, undefined)` while SqliteBackend threw a cryptic NOT NULL\n // bind error — a contract divergence. `undefined` is the missing-key sentinel\n // `read` returns; an explicit `undefined` write is a caller bug — use `null`.\n if (value === undefined) {\n throw new MemoryValidationError(this.domain, 'value must not be undefined (use null)');\n }\n if (this.schema === undefined) return;\n const result = this.schema.safeParse(value);\n if (!result.success) {\n throw new MemoryValidationError(this.domain, result.error);\n }\n }\n\n async write(key: TKey, value: TValue, meta?: WriteMeta): Promise<void> {\n this.assertOpen();\n const start = Date.now();\n this.validate(value);\n this.rows.set(key, buildInMemoryRow(value, meta));\n recordMemoryEvent({\n domain: this.domain,\n op: 'write',\n ...(meta?.cli !== undefined && { cli: meta.cli }),\n durationMs: Date.now() - start,\n key,\n payload: value,\n });\n return Promise.resolve();\n }\n\n async query(filter?: QueryFilter<TValue>): Promise<readonly TValue[]> {\n this.assertOpen();\n const start = Date.now();\n let rows = [...this.rows.values()];\n if (filter?.cli !== undefined) {\n rows = rows.filter((r) => r.cli === filter.cli);\n }\n const values = applyInMemoryFilter(\n rows.map((r) => r.value),\n filter\n );\n recordMemoryEvent({\n domain: this.domain,\n op: 'query',\n hit: values.length > 0,\n ...(filter?.cli !== undefined && { cli: filter.cli }),\n durationMs: Date.now() - start,\n key: filter,\n result: { count: values.length },\n });\n return Promise.resolve(values);\n }\n\n async delete(key: TKey): Promise<boolean> {\n this.assertOpen();\n const start = Date.now();\n const removed = this.rows.delete(key);\n recordMemoryEvent({\n domain: this.domain,\n op: 'delete',\n hit: removed,\n durationMs: Date.now() - start,\n key,\n });\n return Promise.resolve(removed);\n }\n\n async stats(): Promise<BackendStats> {\n this.assertOpen();\n const start = Date.now();\n const timestamps = [...this.rows.values()].map((r) => r.timestamp);\n const result: BackendStats = {\n domain: this.domain,\n count: this.rows.size,\n oldestTimestamp: timestamps.length > 0 ? Math.min(...timestamps) : null,\n newestTimestamp: timestamps.length > 0 ? Math.max(...timestamps) : null,\n };\n recordMemoryEvent({\n domain: this.domain,\n op: 'stats',\n durationMs: Date.now() - start,\n result,\n });\n return Promise.resolve(result);\n }\n\n async close(): Promise<void> {\n this.closed = true;\n this.rows.clear();\n return Promise.resolve();\n }\n\n private assertOpen(): void {\n if (this.closed) {\n throw new Error(`nexus-memory: backend \"${this.domain}\" is closed`);\n }\n }\n}\n","/**\n * Shared helper for opening a SQLite connection on disk.\n *\n * Centralizes the two pre-#3995 concerns that were duplicated at every\n * `new Database(dbPath)` site in this package:\n *\n * 1. **Auto-create the parent directory.** SQLite throws\n * `SQLITE_CANTOPEN` when the parent directory of `dbPath` does not exist\n * (the classic fresh-install regression — the resolver hands back a path\n * under `~/.nexus-agents/memory/` that nobody has created yet). We\n * `mkdirSync(dirname(dbPath), { recursive: true })` first so opening the\n * DB never throws merely because the directory is missing. `:memory:`\n * (and the empty string) are pure in-memory handles with no parent dir, so\n * they skip the mkdir.\n * 2. **WAL journal mode.** Keeps concurrent MCP-server + CLI readers coherent.\n *\n * Kept dep-free on purpose: nexus-memory must stay free of inter-package\n * imports so `nexus-eval-*` repos can reuse it. The canonical\n * `nexusDataPath` resolver lives in nexus-agents and injects the path here as\n * a plain string (see `getMemoryRegistry` wiring in nexus-agents). This helper\n * only guarantees that whatever absolute path it is handed is openable.\n *\n * @module nexus-memory/backends/open-database\n */\n\nimport { createRequire } from 'node:module';\n\n/**\n * Loaded through `createRequire`, not a static import (#5392).\n *\n * A static `import ... from 'node:sqlite'` is HOISTED by the bundler to the top\n * of the emitted chunk, so it evaluates before any consumer code can run. Node\n * emits the SQLite `ExperimentalWarning` at IMPORT time, so that ordering made\n * the CLI's warning filter unable to fire at all. `createRequire` keeps the load\n * synchronous — which is why node:sqlite was chosen — while deferring it to\n * first call. Node caches the module, so repeat calls are free.\n */\nconst requireFromHere = createRequire(import.meta.url);\n\nfunction loadSqlite(): typeof import('node:sqlite') {\n return requireFromHere('node:sqlite') as typeof import('node:sqlite');\n}\n\n/**\n * Result of running a prepared statement. Mirrors what both `node:sqlite` and\n * better-sqlite3 return, so consumers did not have to change (#5388).\n */\nexport interface SqliteRunResult {\n readonly changes: number | bigint;\n readonly lastInsertRowid: number | bigint;\n}\n\n/** Minimal prepared-statement surface this package actually uses. */\nexport interface SqliteStatement {\n run(...params: unknown[]): SqliteRunResult;\n get(...params: unknown[]): unknown;\n all(...params: unknown[]): unknown[];\n}\n\n/**\n * Minimal database surface this package actually uses.\n *\n * Declared locally rather than imported: nexus-memory stays free of\n * inter-package imports so `nexus-eval-*` repos can reuse it, and since #5388\n * there is no dependency to import a type FROM — `node:sqlite` is a builtin.\n */\nexport interface SqliteDatabase {\n exec(sql: string): void;\n prepare(sql: string): SqliteStatement;\n close(): void;\n}\nimport { mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\n/**\n * Open a SQLite database at `dbPath`, creating the parent directory\n * first (for on-disk paths) and enabling WAL journal mode.\n *\n * @param dbPath Absolute SQLite file path, or `':memory:'` / `''` for an\n * in-memory database (no directory is created in that case).\n */\nexport function openSqliteDatabase(dbPath: string): SqliteDatabase {\n if (dbPath !== ':memory:' && dbPath !== '') {\n // Fresh-install robustness (#3995): opening under a missing parent throws SQLITE_CANTOPEN\n // when the parent dir is missing. Create it up front, idempotently.\n mkdirSync(dirname(dbPath), { recursive: true });\n }\n const { DatabaseSync } = loadSqlite();\n const db = new DatabaseSync(dbPath);\n // `node:sqlite` has no `.pragma()` helper, so WAL goes through `exec`.\n db.exec('PRAGMA journal_mode = WAL');\n\n const narrowed = db as unknown as SqliteDatabase;\n return {\n exec: (sql: string) => {\n db.exec(sql);\n },\n prepare: narrowed.prepare.bind(narrowed),\n // better-sqlite3's `close()` is idempotent; `DatabaseSync`'s throws\n // `database is not open` on a second call, and shutdown paths here are\n // reentrant. Preserve the contract callers were written against (#5388).\n close: () => {\n if (db.isOpen) db.close();\n },\n };\n}\n","/**\n * SQLite backend — async wrapper over `node:sqlite` (which is sync).\n *\n * One table per domain (Phase 2 vote shape C, hot-table side). The schema\n * is intentionally minimal: `key`, `value` (JSON-serialized), `cli`,\n * `source`, `timestamp`, `trust_tier`. Hot-path backends extending this\n * will add typed columns and indexes for query performance.\n *\n * Async surface: every method returns `Promise<T>` so we can swap in a\n * network-backed implementation later without changing callers. The\n * actual SQLite ops are sync inside.\n *\n * @module nexus-memory/backends/sqlite\n */\n\nimport type {\n SqliteDatabase as DatabaseType,\n SqliteStatement as Statement,\n} from './open-database.js';\nimport type { z } from 'zod';\nimport { recordMemoryEvent } from '../telemetry.js';\nimport type { BackendStats, IMemoryBackend, QueryFilter, WriteMeta } from '../types.js';\nimport { MemoryValidationError } from './memory.js';\nimport { openSqliteDatabase } from './open-database.js';\n\nexport interface SqliteBackendOptions<TValue> {\n readonly domain: string;\n /** Absolute path to the SQLite file. Use `':memory:'` for tests. */\n readonly dbPath: string;\n /**\n * Zod schema for cold-archive validation (Phase 2 vote mitigation #1).\n * When supplied, every `write()` validates first.\n */\n readonly schema?: z.ZodType<TValue>;\n /** Pre-existing database handle. Used by `MemoryRegistry` to share a single connection. */\n readonly db?: DatabaseType;\n}\n\ninterface SqliteRow {\n readonly key: string;\n readonly value: string;\n readonly cli: string | null;\n readonly source: string | null;\n readonly timestamp: number;\n readonly trust_tier: number | null;\n}\n\nfunction buildWriteRow(\n key: unknown,\n value: unknown,\n meta: WriteMeta | undefined\n): Record<string, string | number | null> {\n return {\n key: keyToString(key),\n value: JSON.stringify(value),\n cli: meta?.cli ?? null,\n source: meta?.source ?? null,\n timestamp: meta?.timestamp ?? Date.now(),\n trust_tier: meta?.trustTier ?? null,\n };\n}\n\n/** Stringify a key so SQLite primary-key lookups stay simple. */\nfunction keyToString(key: unknown): string {\n if (typeof key === 'string') return key;\n if (typeof key === 'number' || typeof key === 'boolean') return String(key);\n return JSON.stringify(key);\n}\n\n/** Apply `where`, `orderBy`, `limit` to an in-memory row set. Extracted from\n * `query` to satisfy the eslint complexity gate. */\nfunction applyQueryFilter<T>(values: T[], filter?: QueryFilter<T>): T[] {\n let out = values;\n if (filter?.where !== undefined) {\n const where = filter.where;\n out = out.filter((v) => {\n for (const [k, expected] of Object.entries(where)) {\n if ((v as Record<string, unknown>)[k] !== expected) return false;\n }\n return true;\n });\n }\n if (filter?.orderBy !== undefined) {\n const orderBy = filter.orderBy;\n const dir = filter.orderDir === 'desc' ? -1 : 1;\n out = [...out].sort((a, b) => {\n const av = (a as Record<string | symbol | number, unknown>)[orderBy];\n const bv = (b as Record<string | symbol | number, unknown>)[orderBy];\n if (av === bv) return 0;\n if (av === undefined || av === null) return 1;\n if (bv === undefined || bv === null) return -1;\n return (av < bv ? -1 : 1) * dir;\n });\n }\n if (filter?.limit !== undefined) {\n out = out.slice(0, filter.limit);\n }\n return out;\n}\n\nexport class SqliteBackend<TKey, TValue> implements IMemoryBackend<TKey, TValue> {\n readonly domain: string;\n private readonly db: DatabaseType;\n private readonly ownsDb: boolean;\n private readonly schema?: z.ZodType<TValue>;\n private readonly stmts: {\n read: Statement;\n write: Statement;\n delete: Statement;\n count: Statement;\n bounds: Statement;\n queryAll: Statement;\n };\n private closed = false;\n\n constructor(options: SqliteBackendOptions<TValue>) {\n this.domain = options.domain;\n if (options.db !== undefined) {\n this.db = options.db;\n this.ownsDb = false;\n } else {\n // #3995: open via the shared helper, which creates the parent dir\n // first (fresh-install robustness) and enables WAL mode.\n this.db = openSqliteDatabase(options.dbPath);\n this.ownsDb = true;\n }\n if (options.schema !== undefined) {\n this.schema = options.schema;\n }\n this.ensureTable();\n this.stmts = this.prepareStatements();\n }\n\n private ensureTable(): void {\n // Table name = domain. Domain comes from in-tree code, never untrusted input,\n // so direct interpolation is safe; still validate the shape defensively.\n if (!/^[a-z][a-z0-9_]{0,63}$/i.test(this.domain)) {\n throw new Error(\n `nexus-memory: invalid domain \"${this.domain}\" — must match [a-zA-Z][a-zA-Z0-9_]{0,63}`\n );\n }\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.domain} (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n cli TEXT,\n source TEXT,\n timestamp INTEGER NOT NULL,\n trust_tier INTEGER\n );\n CREATE INDEX IF NOT EXISTS ${this.domain}_cli ON ${this.domain}(cli);\n CREATE INDEX IF NOT EXISTS ${this.domain}_timestamp ON ${this.domain}(timestamp);\n `);\n }\n\n private prepareStatements(): SqliteBackend<TKey, TValue>['stmts'] {\n return {\n read: this.db.prepare(\n `SELECT key, value, cli, source, timestamp, trust_tier FROM ${this.domain} WHERE key = ?`\n ),\n write: this.db.prepare(\n `INSERT INTO ${this.domain} (key, value, cli, source, timestamp, trust_tier)\n VALUES (@key, @value, @cli, @source, @timestamp, @trust_tier)\n ON CONFLICT(key) DO UPDATE SET\n value = excluded.value,\n cli = excluded.cli,\n source = excluded.source,\n timestamp = excluded.timestamp,\n trust_tier = excluded.trust_tier`\n ),\n delete: this.db.prepare(`DELETE FROM ${this.domain} WHERE key = ?`),\n count: this.db.prepare(`SELECT COUNT(*) AS count FROM ${this.domain}`),\n bounds: this.db.prepare(\n `SELECT MIN(timestamp) AS oldest, MAX(timestamp) AS newest FROM ${this.domain}`\n ),\n queryAll: this.db.prepare(\n `SELECT key, value, cli, source, timestamp, trust_tier FROM ${this.domain}`\n ),\n };\n }\n\n async read(key: TKey): Promise<TValue | undefined> {\n this.assertOpen();\n const start = Date.now();\n const row = this.stmts.read.get(keyToString(key)) as SqliteRow | undefined;\n const value = row !== undefined ? (JSON.parse(row.value) as TValue) : undefined;\n recordMemoryEvent({\n domain: this.domain,\n op: 'read',\n hit: value !== undefined,\n ...(row?.cli !== null && row?.cli !== undefined && { cli: row.cli as never }),\n durationMs: Date.now() - start,\n key,\n result: value,\n });\n return Promise.resolve(value);\n }\n\n private validate(value: TValue): void {\n // #4021: reject `undefined` uniformly (before the optional schema check) so\n // both backends behave identically. Previously SqliteBackend threw a cryptic\n // NOT NULL bind error for `write(key, undefined)` while InMemoryBackend stored\n // a phantom row. `undefined` is the missing-key sentinel; use `null` for an\n // explicit absent value.\n if (value === undefined) {\n throw new MemoryValidationError(this.domain, 'value must not be undefined (use null)');\n }\n if (this.schema === undefined) return;\n const result = this.schema.safeParse(value);\n if (!result.success) {\n throw new MemoryValidationError(this.domain, result.error);\n }\n }\n\n async write(key: TKey, value: TValue, meta?: WriteMeta): Promise<void> {\n this.assertOpen();\n const start = Date.now();\n this.validate(value);\n this.stmts.write.run(buildWriteRow(key, value, meta));\n recordMemoryEvent({\n domain: this.domain,\n op: 'write',\n ...(meta?.cli !== undefined && { cli: meta.cli }),\n durationMs: Date.now() - start,\n key,\n payload: value,\n });\n return Promise.resolve();\n }\n\n async query(filter?: QueryFilter<TValue>): Promise<readonly TValue[]> {\n this.assertOpen();\n const start = Date.now();\n // Phase 3 keeps query simple — full table scan with in-process filter.\n // Hot-path backends will override or extend with indexed columns.\n let rows = this.stmts.queryAll.all() as SqliteRow[];\n if (filter?.cli !== undefined) {\n rows = rows.filter((r) => r.cli === filter.cli);\n }\n let values = rows.map((r) => JSON.parse(r.value) as TValue);\n values = applyQueryFilter(values, filter);\n recordMemoryEvent({\n domain: this.domain,\n op: 'query',\n hit: values.length > 0,\n ...(filter?.cli !== undefined && { cli: filter.cli }),\n durationMs: Date.now() - start,\n key: filter,\n result: { count: values.length },\n });\n return Promise.resolve(values);\n }\n\n async delete(key: TKey): Promise<boolean> {\n this.assertOpen();\n const start = Date.now();\n const result = this.stmts.delete.run(keyToString(key));\n const removed = result.changes > 0;\n recordMemoryEvent({\n domain: this.domain,\n op: 'delete',\n hit: removed,\n durationMs: Date.now() - start,\n key,\n });\n return Promise.resolve(removed);\n }\n\n async stats(): Promise<BackendStats> {\n this.assertOpen();\n const start = Date.now();\n const countRow = this.stmts.count.get() as { count: number };\n const boundsRow = this.stmts.bounds.get() as { oldest: number | null; newest: number | null };\n const result: BackendStats = {\n domain: this.domain,\n count: countRow.count,\n oldestTimestamp: boundsRow.oldest,\n newestTimestamp: boundsRow.newest,\n };\n recordMemoryEvent({\n domain: this.domain,\n op: 'stats',\n durationMs: Date.now() - start,\n result,\n });\n return Promise.resolve(result);\n }\n\n async close(): Promise<void> {\n if (this.closed) return Promise.resolve();\n this.closed = true;\n if (this.ownsDb) this.db.close();\n return Promise.resolve();\n }\n\n private assertOpen(): void {\n if (this.closed) {\n throw new Error(`nexus-memory: backend \"${this.domain}\" is closed`);\n }\n }\n}\n","/**\n * MemoryRegistry — the single entry point for accessing memory backends.\n *\n * Every concept-space (experience, outcomes, beliefs, …) registers a\n * backend; callers reach it via `registry.get('experience')` or, more\n * commonly, the typed accessors that wrap the registry.\n *\n * Sharing a SQLite connection across backends keeps WAL-mode behavior\n * coherent — one writer thread, many tables.\n *\n * @module nexus-memory/registry\n */\n\nimport type { SqliteDatabase as DatabaseType } from './backends/open-database.js';\nimport type { z } from 'zod';\nimport { InMemoryBackend } from './backends/memory.js';\nimport { openSqliteDatabase } from './backends/open-database.js';\nimport { SqliteBackend } from './backends/sqlite.js';\nimport type { IMemoryBackend } from './types.js';\n\nexport interface MemoryRegistryOptions {\n /**\n * SQLite file path. Use `':memory:'` for tests. When omitted, the\n * registry creates `InMemoryBackend` instances and never touches disk\n * — the standard test-isolation pattern (Phase 2 acceptance criterion).\n */\n readonly dbPath?: string;\n}\n\nexport interface RegisterBackendOptions<TValue> {\n /** Stable domain identifier. Becomes the SQLite table name. */\n readonly domain: string;\n /** Optional Zod schema for cold-archive validation. */\n readonly schema?: z.ZodType<TValue>;\n}\n\n/**\n * The registry. One per `dbPath` (or one in-memory registry per test).\n * `MemoryRegistry.get(domain)` is `O(1)` after registration.\n */\nexport class MemoryRegistry {\n private readonly backends = new Map<string, IMemoryBackend<unknown, unknown>>();\n private readonly db?: DatabaseType;\n private closed = false;\n\n constructor(options: MemoryRegistryOptions = {}) {\n if (options.dbPath !== undefined) {\n // #3995: open via the shared helper, which creates the parent dir\n // first (fresh-install robustness) and enables WAL mode.\n this.db = openSqliteDatabase(options.dbPath);\n }\n }\n\n /**\n * Register a backend for `domain`. Throws on duplicate domain.\n *\n * When the registry has a `dbPath`, the new backend is SQLite-backed\n * and shares the registry's connection. Otherwise, an `InMemoryBackend`\n * is created (used for tests).\n */\n register<TKey, TValue>(options: RegisterBackendOptions<TValue>): IMemoryBackend<TKey, TValue> {\n this.assertOpen();\n if (this.backends.has(options.domain)) {\n throw new Error(`nexus-memory: domain \"${options.domain}\" already registered`);\n }\n const backend: IMemoryBackend<TKey, TValue> =\n this.db !== undefined\n ? new SqliteBackend<TKey, TValue>({\n domain: options.domain,\n dbPath: '<shared>', // unused when db is supplied\n db: this.db,\n ...(options.schema !== undefined && { schema: options.schema }),\n })\n : new InMemoryBackend<TKey, TValue>({\n domain: options.domain,\n ...(options.schema !== undefined && { schema: options.schema }),\n });\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- TypeScript narrows the union type here, but the Map needs the lifted IMemoryBackend<unknown, unknown>; the assertion documents the variance and survives future contract changes.\n this.backends.set(options.domain, backend as IMemoryBackend<unknown, unknown>);\n return backend;\n }\n\n /**\n * Attach an externally-managed {@link IMemoryBackend}. Use this when the\n * backend owns its own storage (e.g., a pre-existing SQLite file under\n * `~/.nexus-agents/memory/agentic.db`) and you want it discoverable\n * through the registry without changing its persistence.\n *\n * Phase 5–7 of the memory-unification epic use this to bring the\n * tool-memory backends (`agentic`, `adaptive`, `typed`, `belief`),\n * OutcomeStore, SICA, skills, etc. under a unified observability\n * contract without rewriting their internals. Each attached backend\n * still owns its own `.db` (or JSONL, etc.) until a follow-up migration\n * folds the storage in fully.\n */\n attach<TKey, TValue>(\n domain: string,\n backend: IMemoryBackend<TKey, TValue>\n ): IMemoryBackend<TKey, TValue> {\n this.assertOpen();\n if (this.backends.has(domain)) {\n throw new Error(`nexus-memory: domain \"${domain}\" already registered`);\n }\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- See `register`.\n this.backends.set(domain, backend as IMemoryBackend<unknown, unknown>);\n return backend;\n }\n\n /**\n * Get a previously-registered backend. Returns `undefined` if the\n * domain isn't registered — callers should treat that as \"not yet\n * migrated to the unified contract.\"\n */\n get<TKey, TValue>(domain: string): IMemoryBackend<TKey, TValue> | undefined {\n this.assertOpen();\n const backend = this.backends.get(domain);\n return backend as IMemoryBackend<TKey, TValue> | undefined;\n }\n\n /** List all registered domains. Useful for `memory_stats`-style readers. */\n domains(): readonly string[] {\n return [...this.backends.keys()];\n }\n\n /**\n * Close every registered backend and the shared SQLite handle. After\n * `close()` the registry rejects all further operations.\n */\n async close(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n for (const backend of this.backends.values()) {\n await backend.close();\n }\n this.backends.clear();\n if (this.db !== undefined) this.db.close();\n }\n\n private assertOpen(): void {\n if (this.closed) {\n throw new Error('nexus-memory: registry is closed');\n }\n }\n}\n\n// ============================================================================\n// Singleton accessor\n// ============================================================================\n\nlet sharedRegistry: MemoryRegistry | null = null;\n\n/**\n * Get the process-wide shared registry. Initializes on first call with\n * `dbPath` from `NEXUS_DATA_DIR` (defaults to `~/.nexus-agents/memory/memory.db`)\n * unless `setMemoryRegistry` was called first.\n *\n * Test code should call `setMemoryRegistry(createInMemoryMemoryRegistry())`\n * in `beforeEach` — this matches the existing `setOutcomeStore` pattern.\n */\nexport function getMemoryRegistry(): MemoryRegistry {\n if (sharedRegistry === null) {\n // Default path resolution happens here, NOT at module load — so tests\n // that call `setMemoryRegistry` before any `getMemoryRegistry` see\n // their in-memory instance, not a side-effectful production path.\n const dbPath = resolveDefaultDbPath();\n sharedRegistry = new MemoryRegistry({ dbPath });\n }\n return sharedRegistry;\n}\n\n/**\n * Inject a registry. Used by tests to swap in `InMemoryBackend`-backed\n * registries. After test, call again with `null` to reset, or call\n * `closeMemoryRegistry()` to dispose.\n */\nexport function setMemoryRegistry(registry: MemoryRegistry | null): void {\n sharedRegistry = registry;\n}\n\n/**\n * True when the shared registry has already been constructed or injected.\n * Lets nexus-agents inject the canonical {@link nexusDataPath}-resolved DB\n * path (#3995) WITHOUT clobbering a registry a test already set, and without\n * triggering the lazy default-path resolution that `getMemoryRegistry()`\n * would. Read-only — keeps nexus-memory dep-free.\n */\nexport function hasMemoryRegistry(): boolean {\n return sharedRegistry !== null;\n}\n\n/** Close + null out the shared registry. Idempotent. */\nexport async function closeMemoryRegistry(): Promise<void> {\n if (sharedRegistry !== null) {\n await sharedRegistry.close();\n sharedRegistry = null;\n }\n}\n\nfunction resolveDefaultDbPath(): string {\n // Match `nexus-agents`'s `nexusDataPath` convention without importing it\n // (keep nexus-memory free of inter-package deps for nexus-eval-* reuse).\n const root = process.env['NEXUS_DATA_DIR'] ?? `${process.env['HOME'] ?? '/tmp'}/.nexus-agents`;\n return `${root}/memory/memory.db`;\n}\n","/**\n * Test factories. Production code uses {@link getMemoryRegistry} from\n * `./registry.js`; tests use these helpers to avoid touching disk.\n *\n * @module nexus-memory/factory\n */\n\nimport { MemoryRegistry } from './registry.js';\n\n/**\n * Create a registry that lives entirely in memory. Every `register()`\n * call returns an {@link InMemoryBackend}; nothing is persisted.\n *\n * Use this in `beforeEach`:\n *\n * ```ts\n * beforeEach(() => {\n * setMemoryRegistry(createInMemoryMemoryRegistry());\n * });\n * afterEach(async () => {\n * await closeMemoryRegistry();\n * });\n * ```\n */\nexport function createInMemoryMemoryRegistry(): MemoryRegistry {\n return new MemoryRegistry({\n /* no dbPath → InMemoryBackend per domain */\n });\n}\n\n/**\n * Create a SQLite-backed registry rooted at `dbPath`. Pass `':memory:'`\n * for tests that need to exercise the SQLite code path (e.g.,\n * migration tests, parity tests).\n */\nexport function createSqliteMemoryRegistry(dbPath: string): MemoryRegistry {\n return new MemoryRegistry({ dbPath });\n}\n","/**\n * One-shot importer scaffold. Each Phase 4+ migration registers its own\n * importer here; the registry runs them on first launch (after a marker\n * file check) and renames source files to `.bak.<timestamp>` once\n * complete.\n *\n * This file ships the skeleton + the marker-file logic; concrete\n * importers (MobiMem JSON, OutcomeStore JSONL, agentic.db, etc.) plug in\n * during their respective migration phases.\n *\n * @module nexus-memory/importer\n */\n\nimport { existsSync, mkdirSync, renameSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport type { MemoryRegistry } from './registry.js';\n\nexport interface ImporterRun {\n readonly domain: string;\n readonly rowsImported: number;\n readonly sourcePathBackup: string | null;\n}\n\nexport interface Importer {\n /** Stable identifier — used in the marker filename to track completion. */\n readonly id: string;\n /** Logical domain this importer targets in the registry. */\n readonly domain: string;\n /**\n * Run the import. Implementations: (1) check if source data exists,\n * (2) parse + validate it, (3) write rows into the registry's backend,\n * (4) rename source to `.bak.<timestamp>` and return the backup path.\n * Idempotent — if marker file says we already ran, the registry skips it.\n */\n run(registry: MemoryRegistry): Promise<ImporterRun>;\n}\n\nconst importers = new Map<string, Importer>();\n\n/** Register an importer. Phase 4+ migrations call this at module load. */\nexport function registerImporter(importer: Importer): void {\n if (importers.has(importer.id)) {\n throw new Error(`nexus-memory: importer \"${importer.id}\" already registered`);\n }\n importers.set(importer.id, importer);\n}\n\n/** For tests: clear all registered importers. */\nexport function resetImporters(): void {\n importers.clear();\n}\n\n/** Snapshot of registered importer IDs. */\nexport function listImporters(): readonly string[] {\n return [...importers.keys()];\n}\n\nexport interface RunImportersOptions {\n /**\n * Directory holding `.imported-{id}` marker files. Defaults to the\n * registry's data dir; tests should override.\n */\n readonly markerDir: string;\n /** Skip the marker check (forces re-run). Tests only. */\n readonly force?: boolean;\n}\n\n/**\n * Run every registered importer that hasn't yet completed (per marker\n * file). Returns the list of runs that actually executed.\n *\n * Failures are NOT fatal — a single importer's exception is logged via\n * the returned `errors` array; other importers still get a chance to run.\n */\nexport async function runImporters(\n registry: MemoryRegistry,\n options: RunImportersOptions\n): Promise<{ runs: readonly ImporterRun[]; errors: readonly { id: string; error: Error }[] }> {\n mkdirSync(options.markerDir, { recursive: true });\n const runs: ImporterRun[] = [];\n const errors: { id: string; error: Error }[] = [];\n for (const importer of importers.values()) {\n const marker = join(options.markerDir, `.imported-${importer.id}`);\n if (options.force !== true && existsSync(marker)) continue;\n try {\n const run = await importer.run(registry);\n writeMarker(marker, run);\n runs.push(run);\n } catch (err) {\n errors.push({ id: importer.id, error: err instanceof Error ? err : new Error(String(err)) });\n }\n }\n return { runs, errors };\n}\n\nfunction writeMarker(path: string, run: ImporterRun): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(\n path,\n JSON.stringify({ ...run, completedAt: new Date().toISOString() }, null, 2),\n 'utf-8'\n );\n}\n\n/**\n * Rename a source file to `.bak.<unix-timestamp>`. Used by concrete\n * importers after a successful migration. Returns the backup path, or\n * `null` when the source doesn't exist.\n */\nexport function backupSourceFile(sourcePath: string): string | null {\n if (!existsSync(sourcePath)) return null;\n const backup = `${sourcePath}.bak.${String(Date.now())}`;\n renameSync(sourcePath, backup);\n return backup;\n}\n"],"mappings":"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","names":["mkdirSync","dirname"]}
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  analyzeGitHubRepo
3
- } from "./chunk-HFOQKCD2.js";
3
+ } from "./chunk-DGWT7DXX.js";
4
4
  import {
5
5
  createLogger,
6
6
  getTimeProvider
7
- } from "./chunk-Y742U2YM.js";
7
+ } from "./chunk-3MBGQAO2.js";
8
8
 
9
9
  // src/mcp/tools/scanner-registry-fetcher.ts
10
10
  import { z } from "zod";
@@ -93,8 +93,8 @@ async function downloadManifest(execFileAsync) {
93
93
  }
94
94
  async function fetchManifestFromGitHub() {
95
95
  try {
96
- const { execFile } = await import("child_process");
97
- const { promisify } = await import("util");
96
+ const { execFile } = await import("node:child_process");
97
+ const { promisify } = await import("node:util");
98
98
  const execFileAsync = promisify(execFile);
99
99
  const tag = await getLatestReleaseTag(execFileAsync);
100
100
  if (tag === null) {
@@ -721,4 +721,4 @@ export {
721
721
  generateSecurityPlan,
722
722
  buildPlanFromAnalysis
723
723
  };
724
- //# sourceMappingURL=chunk-VYAXMVI5.js.map
724
+ //# sourceMappingURL=chunk-ZYPEHPRM.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mcp/tools/scanner-registry-fetcher.ts","../src/mcp/tools/repo-security-plan-fallback.ts","../src/mcp/tools/repo-security-plan.ts"],"sourcesContent":["/**\n * nexus-agents/mcp - Scanner Registry Fetcher\n *\n * Fetches the scanner-registry.json manifest from the\n * vulnerability-scanner-registry GitHub Releases at runtime.\n * Uses a TTL cache and falls back to embedded data on failure.\n *\n * @module mcp/tools/scanner-registry-fetcher\n * (Source: Consensus vote — externalize scanner registry, 6-0 unanimous)\n */\n\nimport { z } from 'zod';\nimport { createLogger, getTimeProvider } from '../../core/index.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** A scanner entry from the registry manifest. */\nexport interface RegistryScanner {\n readonly name: string;\n readonly displayName: string;\n readonly categories: readonly string[];\n readonly license: string;\n readonly pricingModel: string;\n readonly relationships?: readonly RegistryRelationship[] | undefined;\n}\n\n/** A relationship edge between scanners. */\nexport interface RegistryRelationship {\n readonly target: string;\n readonly type: 'uses' | 'supersedes' | 'bundles' | 'competes-with';\n}\n\n/** Language matrix: category → scanner names. */\nexport interface LanguageMatrixEntry {\n readonly sast?: readonly string[] | undefined;\n readonly sca?: readonly string[] | undefined;\n readonly secrets?: readonly string[] | undefined;\n readonly container?: readonly string[] | undefined;\n readonly iac?: readonly string[] | undefined;\n readonly dast?: readonly string[] | undefined;\n}\n\n/** The full registry manifest shape. */\nexport interface ScannerRegistryManifest {\n readonly version: string;\n readonly generatedAt: string;\n readonly scanners: readonly RegistryScanner[];\n readonly languageMatrix: Readonly<Record<string, LanguageMatrixEntry>>;\n}\n\n// ============================================================================\n// Zod Schema for Validation\n// ============================================================================\n\nconst RelationshipSchema = z.object({\n target: z.string().min(1),\n type: z.enum(['uses', 'supersedes', 'bundles', 'competes-with']),\n});\n\nconst ScannerSchema = z.object({\n name: z.string().min(1),\n displayName: z.string().min(1),\n categories: z.array(z.string().min(1)),\n license: z.string().min(1),\n pricingModel: z.string().min(1),\n relationships: z.array(RelationshipSchema).optional(),\n});\n\nconst LanguageMatrixEntrySchema = z\n .object({\n sast: z.array(z.string()).optional(),\n sca: z.array(z.string()).optional(),\n secrets: z.array(z.string()).optional(),\n container: z.array(z.string()).optional(),\n iac: z.array(z.string()).optional(),\n dast: z.array(z.string()).optional(),\n })\n .loose();\n\nconst ManifestSchema = z.object({\n version: z.string().min(1),\n generatedAt: z.string().min(1),\n scanners: z.array(ScannerSchema),\n languageMatrix: z.record(z.string().max(50), LanguageMatrixEntrySchema),\n});\n\n// ============================================================================\n// Cache\n// ============================================================================\n\ninterface CacheEntry {\n manifest: ScannerRegistryManifest;\n fetchedAt: number;\n releaseTag: string;\n}\n\nconst CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\nlet cachedEntry: CacheEntry | null = null;\n\n/** Inflight fetch promise for probe coalescing (#1448). */\nlet inflightFetch: Promise<ScannerRegistryManifest | null> | undefined;\n\n/** Clear the cache and inflight state (for testing). */\nexport function clearRegistryCache(): void {\n cachedEntry = null;\n inflightFetch = undefined;\n}\n\n// ============================================================================\n// Fetcher\n// ============================================================================\n\nconst REGISTRY_REPO = 'williamzujkowski/vulnerability-scanner-registry';\nconst FETCH_TIMEOUT_MS = 10_000;\n\n/** Promisified execFile signature used by fetcher helpers. */\ntype ExecFileAsync = (\n file: string,\n args: readonly string[],\n options: { timeout?: number; maxBuffer?: number }\n) => Promise<{ stdout: string; stderr: string }>;\n\nconst logger = createLogger({ component: 'scanner-registry-fetcher' });\n\n/** Get the latest release tag name (lightweight check, no download). */\nasync function getLatestReleaseTag(execFileAsync: ExecFileAsync): Promise<string | null> {\n const { stdout } = await execFileAsync(\n 'gh',\n ['release', 'view', '--repo', REGISTRY_REPO, '--json', 'tagName', '--jq', '.tagName'],\n { timeout: FETCH_TIMEOUT_MS }\n );\n return stdout.trim() || null;\n}\n\n/** Download and parse the full manifest. */\nasync function downloadManifest(\n execFileAsync: ExecFileAsync\n): Promise<ScannerRegistryManifest | null> {\n const { stdout } = await execFileAsync(\n 'gh',\n [\n 'release',\n 'download',\n '--repo',\n REGISTRY_REPO,\n '--pattern',\n 'scanner-registry.json',\n '--output',\n '-',\n ],\n { timeout: FETCH_TIMEOUT_MS, maxBuffer: 1024 * 1024 }\n );\n\n let jsonData: unknown;\n try {\n jsonData = JSON.parse(stdout);\n } catch {\n logger.warn('Registry manifest is not valid JSON', {\n stdoutLength: stdout.length,\n preview: stdout.slice(0, 100),\n });\n return null;\n }\n\n const parsed = ManifestSchema.safeParse(jsonData);\n if (!parsed.success) {\n logger.warn('Registry manifest failed schema validation', {\n errors: parsed.error.issues.slice(0, 3),\n });\n return null;\n }\n\n logger.info('Fetched scanner registry manifest', {\n version: parsed.data.version,\n scanners: parsed.data.scanners.length,\n languages: Object.keys(parsed.data.languageMatrix).length,\n });\n return parsed.data;\n}\n\n/**\n * Fetch the scanner registry manifest from GitHub Releases.\n * If we have a cached version and the release tag hasn't changed,\n * just refreshes the cache timer (no download).\n */\nasync function fetchManifestFromGitHub(): Promise<ScannerRegistryManifest | null> {\n try {\n const { execFile } = await import('node:child_process');\n const { promisify } = await import('node:util');\n const execFileAsync = promisify(execFile);\n\n const tag = await getLatestReleaseTag(execFileAsync);\n if (tag === null) {\n logger.warn('No releases found in scanner registry');\n return null;\n }\n\n // If cached version matches the latest tag, refresh timer only\n if (cachedEntry !== null && cachedEntry.releaseTag === tag) {\n logger.debug('Scanner registry unchanged, refreshing cache timer', { tag });\n cachedEntry = { ...cachedEntry, fetchedAt: getTimeProvider().now() };\n return cachedEntry.manifest;\n }\n\n // New release — download full manifest\n const manifest = await downloadManifest(execFileAsync);\n if (manifest !== null) {\n cachedEntry = { manifest, fetchedAt: getTimeProvider().now(), releaseTag: tag };\n }\n return manifest;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.debug('Failed to fetch scanner registry', { error: msg });\n return null;\n }\n}\n\n/**\n * Get the scanner registry, fetching from GitHub if cache is stale.\n * Returns null if no cached data and fetch fails.\n */\nexport async function getRegistryManifest(): Promise<ScannerRegistryManifest | null> {\n // Check cache\n if (cachedEntry !== null) {\n const age = getTimeProvider().now() - cachedEntry.fetchedAt;\n if (age < CACHE_TTL_MS) {\n return cachedEntry.manifest;\n }\n }\n\n // Coalesce concurrent fetches — only one inflight request at a time (#1448)\n inflightFetch ??= fetchManifestFromGitHub().finally(() => {\n inflightFetch = undefined;\n });\n const manifest = await inflightFetch;\n if (manifest !== null) {\n return manifest;\n }\n\n // Return stale cache if available\n if (cachedEntry !== null) {\n logger.warn('Using stale cached registry manifest');\n return cachedEntry.manifest;\n }\n\n return null;\n}\n\n/**\n * Extract scanners from manifest into the format expected by plan builder.\n */\nexport function extractScannerEntries(\n manifest: ScannerRegistryManifest\n): readonly RegistryScanner[] {\n return manifest.scanners;\n}\n\n/**\n * Extract language matrix, normalizing to consistent category keys.\n */\nexport function extractLanguageMatrix(\n manifest: ScannerRegistryManifest\n): Readonly<Record<string, LanguageMatrixEntry>> {\n return manifest.languageMatrix;\n}\n","/**\n * nexus-agents/mcp - Fallback Scanner Data\n *\n * Embedded snapshot of the vulnerability-scanner-registry manifest.\n * Used when the live registry fetch fails (network issues, gh CLI\n * unavailable, etc.). Updated periodically from the canonical\n * registry at github.com/williamzujkowski/vulnerability-scanner-registry.\n *\n * @module mcp/tools/repo-security-plan-fallback\n * (Source: Consensus vote — externalize scanner registry, 6-0 unanimous)\n */\n\nimport type { ScannerData } from './repo-security-plan.js';\n\n// ============================================================================\n// Fallback Scanner Entries (27 scanners)\n// ============================================================================\n\nconst FALLBACK_SCANNERS: ScannerData['scanners'] = [\n {\n name: 'semgrep',\n displayName: 'Semgrep',\n categories: ['sast', 'secrets'],\n license: 'LGPL-2.1',\n pricingModel: 'freemium',\n },\n {\n name: 'codeql',\n displayName: 'CodeQL',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'freemium',\n },\n {\n name: 'bandit',\n displayName: 'Bandit',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'gosec',\n displayName: 'Gosec',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'brakeman',\n displayName: 'Brakeman',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'phpstan',\n displayName: 'PHPStan',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'freemium',\n },\n {\n name: 'shellcheck',\n displayName: 'ShellCheck',\n categories: ['sast'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'cppcheck',\n displayName: 'Cppcheck',\n categories: ['sast'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'detekt',\n displayName: 'detekt',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'spotbugs',\n displayName: 'SpotBugs',\n categories: ['sast'],\n license: 'LGPL-2.1',\n pricingModel: 'free',\n },\n {\n name: 'eslint-security',\n displayName: 'eslint-plugin-security',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'sonarqube',\n displayName: 'SonarQube',\n categories: ['sast', 'sca'],\n license: 'LGPL-3.0',\n pricingModel: 'freemium',\n },\n {\n name: 'osv-scanner',\n displayName: 'OSV-Scanner',\n categories: ['sca', 'container', 'iac', 'sbom'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'grype',\n displayName: 'Grype',\n categories: ['sca', 'container'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'snyk',\n displayName: 'Snyk',\n categories: ['sca', 'sast', 'container'],\n license: 'Proprietary',\n pricingModel: 'freemium',\n },\n {\n name: 'npm-audit',\n displayName: 'npm audit',\n categories: ['sca'],\n license: 'Artistic-2.0',\n pricingModel: 'free',\n },\n {\n name: 'pip-audit',\n displayName: 'pip-audit',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'cargo-audit',\n displayName: 'cargo-audit',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'bundler-audit',\n displayName: 'bundler-audit',\n categories: ['sca'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'govulncheck',\n displayName: 'govulncheck',\n categories: ['sca'],\n license: 'BSD-3-Clause',\n pricingModel: 'free',\n },\n {\n name: 'owasp-dependency-check',\n displayName: 'OWASP Dependency-Check',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'gitleaks',\n displayName: 'Gitleaks',\n categories: ['secrets'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'trufflehog',\n displayName: 'TruffleHog',\n categories: ['secrets'],\n license: 'AGPL-3.0',\n pricingModel: 'freemium',\n },\n {\n name: 'checkov',\n displayName: 'Checkov',\n categories: ['iac', 'sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'tfsec',\n displayName: 'tfsec',\n categories: ['iac'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'owasp-zap',\n displayName: 'OWASP ZAP',\n categories: ['dast', 'api'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'syft',\n displayName: 'Syft',\n categories: ['sbom'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'grype-image',\n displayName: 'Grype (image scan)',\n categories: ['image-currency', 'container'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n];\n\n// ============================================================================\n// Fallback Language Map (16 languages)\n// ============================================================================\n\nconst FALLBACK_LANGUAGE_MAP: ScannerData['languageMap'] = {\n TypeScript: {\n sast: ['semgrep', 'eslint-security', 'codeql'],\n sca: ['npm-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n JavaScript: {\n sast: ['semgrep', 'eslint-security', 'codeql'],\n sca: ['npm-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Python: {\n sast: ['bandit', 'semgrep', 'codeql'],\n sca: ['pip-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Java: {\n sast: ['codeql', 'semgrep', 'spotbugs'],\n sca: ['owasp-dependency-check', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Go: {\n sast: ['gosec', 'semgrep', 'codeql'],\n sca: ['govulncheck', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Ruby: {\n sast: ['brakeman', 'semgrep', 'codeql'],\n sca: ['bundler-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n PHP: {\n sast: ['phpstan', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n 'C#': {\n sast: ['codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n C: {\n sast: ['cppcheck', 'codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n 'C++': {\n sast: ['cppcheck', 'codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Rust: {\n sast: ['semgrep'],\n sca: ['cargo-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Kotlin: {\n sast: ['detekt', 'semgrep', 'codeql'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Swift: {\n sast: ['codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Scala: {\n sast: ['semgrep', 'spotbugs'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Shell: {\n sast: ['shellcheck', 'semgrep'],\n sca: [],\n secrets: ['gitleaks'],\n },\n HCL: {\n sast: ['checkov', 'tfsec'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n};\n\n// ============================================================================\n// Exported Fallback\n// ============================================================================\n\n/** Embedded scanner data snapshot used when live registry is unavailable. */\nexport const FALLBACK_SCANNER_DATA: ScannerData = {\n scanners: FALLBACK_SCANNERS,\n languageMap: FALLBACK_LANGUAGE_MAP,\n source: 'fallback',\n};\n","/**\n * nexus-agents/mcp - Repository Security Plan Logic\n *\n * Generates a language-aware security scanning pipeline recommendation\n * by composing repo_analyze output with scanner registry data.\n * Fetches fresh data from vulnerability-scanner-registry GitHub Releases;\n * falls back to embedded snapshot if fetch fails.\n *\n * @module mcp/tools/repo-security-plan\n * (Source: Issue #1079, externalization vote 6-0 unanimous)\n */\n\nimport type { RepoAnalysis } from './repo-analyze-types.js';\nimport type {\n RepoSecurityPlanInput,\n RepoSecurityPlan,\n ScannerRecommendation,\n ConflictWarning,\n CoverageAnalysis,\n} from './repo-security-plan-types.js';\nimport { analyzeGitHubRepo } from './repo-analyze.js';\nimport { getRegistryManifest } from './scanner-registry-fetcher.js';\nimport type { RegistryScanner, LanguageMatrixEntry } from './scanner-registry-fetcher.js';\nimport { FALLBACK_SCANNER_DATA } from './repo-security-plan-fallback.js';\nimport { createLogger } from '../../core/index.js';\n\nconst logger = createLogger({ component: 'repo-security-plan' });\n\n// ============================================================================\n// Scanner Data Interface (common shape for fetched + fallback)\n// ============================================================================\n\n/** Internal scanner entry used by plan builder. */\nexport interface ScannerEntry {\n readonly name: string;\n readonly displayName: string;\n readonly categories: readonly string[];\n readonly license: string;\n readonly pricingModel: string;\n readonly supersedes?: readonly string[];\n}\n\n/** Language mapping: category → scanner names. */\ninterface LanguageMapping {\n readonly sast: readonly string[];\n readonly sca: readonly string[];\n readonly secrets: readonly string[];\n}\n\n/** Resolved scanner data for plan building. */\nexport interface ScannerData {\n readonly scanners: readonly ScannerEntry[];\n readonly languageMap: Readonly<Record<string, LanguageMapping>>;\n readonly source: 'registry' | 'fallback';\n}\n\n// Re-export for consumers\nexport { FALLBACK_SCANNER_DATA } from './repo-security-plan-fallback.js';\n\n// ============================================================================\n// Registry → ScannerData Conversion\n// ============================================================================\n\nfunction convertRegistryScanner(s: RegistryScanner): ScannerEntry {\n const supersedes = s.relationships?.filter((r) => r.type === 'supersedes').map((r) => r.target);\n return {\n name: s.name,\n displayName: s.displayName,\n categories: s.categories,\n license: s.license,\n pricingModel: s.pricingModel,\n ...(supersedes !== undefined && supersedes.length > 0 ? { supersedes } : {}),\n };\n}\n\n/** Known PascalCase language names from GitHub API. Handles registry keys like \"typescript\" → \"TypeScript\". */\nconst LANGUAGE_PASCAL_MAP: Readonly<Record<string, string>> = {\n typescript: 'TypeScript',\n javascript: 'JavaScript',\n python: 'Python',\n java: 'Java',\n csharp: 'C#',\n 'c#': 'C#',\n cpp: 'C++',\n 'c++': 'C++',\n go: 'Go',\n rust: 'Rust',\n ruby: 'Ruby',\n php: 'PHP',\n swift: 'Swift',\n kotlin: 'Kotlin',\n scala: 'Scala',\n hcl: 'HCL',\n shell: 'Shell',\n dockerfile: 'Dockerfile',\n};\n\nfunction normalizeLangName(lang: string): string {\n const lower = lang.toLowerCase();\n return LANGUAGE_PASCAL_MAP[lower] ?? lang.charAt(0).toUpperCase() + lang.slice(1);\n}\n\nfunction convertLanguageMatrix(\n matrix: Readonly<Record<string, LanguageMatrixEntry>>\n): Record<string, LanguageMapping> {\n const result: Record<string, LanguageMapping> = {};\n for (const [lang, entry] of Object.entries(matrix)) {\n // Normalize language name to PascalCase (GitHub API returns PascalCase like \"TypeScript\")\n const normalized = normalizeLangName(lang);\n result[normalized] = {\n sast: entry.sast ?? [],\n sca: entry.sca ?? [],\n secrets: entry.secrets ?? [],\n };\n }\n return result;\n}\n\n/** Resolve scanner data: fetch from registry, fall back to embedded. */\nexport async function resolveScannerData(): Promise<ScannerData> {\n const manifest = await getRegistryManifest();\n if (manifest !== null) {\n logger.info('Using live scanner registry', {\n version: manifest.version,\n scanners: manifest.scanners.length,\n });\n return {\n scanners: manifest.scanners.map(convertRegistryScanner),\n languageMap: convertLanguageMatrix(manifest.languageMatrix),\n source: 'registry',\n };\n }\n\n logger.info('Using fallback scanner data');\n return FALLBACK_SCANNER_DATA;\n}\n\n// ============================================================================\n// CI Snippet Generation (GitHub Actions only for v1)\n// ============================================================================\n\nconst CI_SNIPPETS: Readonly<Record<string, string>> = {\n semgrep: '- uses: semgrep/semgrep-action@v1\\n with:\\n config: auto',\n codeql: '- uses: github/codeql-action/analyze@v3',\n grype: '- uses: anchore/scan-action@v4\\n with:\\n path: .',\n 'grype-image':\n '- uses: anchore/scan-action@v4\\n with:\\n image: ${{ env.IMAGE_TAG }}\\n severity: CRITICAL,HIGH\\n exit-code: 1',\n gitleaks: '- uses: gitleaks/gitleaks-action@v2',\n bandit: '- run: pip install bandit && bandit -r . -f json',\n gosec: '- uses: securego/gosec@master\\n with:\\n args: ./...',\n checkov: '- uses: bridgecrewio/checkov-action@master',\n 'osv-scanner': '- uses: google/osv-scanner-action@v1',\n snyk: '- uses: snyk/actions/node@master # adjust for language',\n shellcheck: '- uses: ludeeus/action-shellcheck@master',\n // #2732: scanners recommended for TypeScript repos that were missing\n // snippets, leaving most rows with ciSnippet: null.\n 'eslint-security':\n '- run: npm install --save-dev eslint-plugin-security && npx eslint --plugin security .',\n sonarqube:\n '- uses: sonarsource/sonarqube-scan-action@v3\\n env:\\n SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}',\n 'npm-audit': '- run: npm audit --audit-level=high',\n trivy:\n '- uses: aquasecurity/trivy-action@master\\n with:\\n scan-type: fs\\n severity: CRITICAL,HIGH\\n exit-code: 1',\n trufflehog: '- uses: trufflesecurity/trufflehog@main\\n with:\\n extra_args: --only-verified',\n cppcheck: '- run: cppcheck --enable=all --error-exitcode=1 .',\n spotbugs:\n '- uses: jwgmeligmeyling/spotbugs-github-action@master\\n with:\\n path: target/spotbugsXml.xml',\n 'pip-audit': '- run: pip install pip-audit && pip-audit',\n 'cargo-audit': '- run: cargo install cargo-audit --locked && cargo audit',\n 'bundler-audit': '- run: gem install bundler-audit && bundle-audit check --update',\n 'composer-audit': '- run: composer audit',\n govulncheck: '- run: go install golang.org/x/vuln/cmd/govulncheck@latest && govulncheck ./...',\n detekt: '- uses: natiginfo/action-detekt-all@1.23.6',\n brakeman: '- run: gem install brakeman && brakeman -f json',\n phpstan: '- run: composer install && vendor/bin/phpstan analyse',\n tfsec: '- uses: aquasecurity/tfsec-action@v1.0.3',\n 'owasp-dependency-check':\n '- uses: dependency-check/Dependency-Check_Action@main\\n with:\\n project: ${{ github.event.repository.name }}\\n path: .\\n format: HTML',\n 'owasp-zap':\n '- uses: zaproxy/action-baseline@v0.12.0\\n with:\\n target: ${{ env.TARGET_URL }}',\n syft: '- uses: anchore/sbom-action@v0\\n with:\\n path: .\\n format: cyclonedx-json',\n};\n\nfunction generateCiSnippet(name: string, ci: string | null): string | null {\n if (ci !== 'github-actions') return null;\n return CI_SNIPPETS[name] ?? null;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction findScanner(name: string, scanners: readonly ScannerEntry[]): ScannerEntry | undefined {\n return scanners.find((s) => s.name === name);\n}\n\nfunction isAlreadyUsed(name: string, existing: readonly string[]): boolean {\n return existing.some((t) => t.toLowerCase().includes(name.toLowerCase()));\n}\n\n/** Context passed to recommendation collectors. */\ninterface RecContext {\n readonly existing: readonly string[];\n readonly ciProvider: string | null;\n readonly language: string | null;\n readonly categoryFilter: ReadonlySet<string> | null;\n readonly maxScanners: number;\n readonly scanners: readonly ScannerEntry[];\n}\n\n/** Options for collecting recommendations in a single category. */\ninterface CategoryRecOptions {\n readonly names: readonly string[];\n readonly category: string;\n readonly rationale: (entry: ScannerEntry) => string;\n readonly priority: 'critical' | 'recommended';\n readonly ctx: RecContext;\n}\n\n/** Collect recommendations for a single category. */\nfunction collectCategoryRecs(recs: ScannerRecommendation[], opts: CategoryRecOptions): void {\n // #2732: at most one `critical` slot per category — the first scanner\n // picked is the \"must run\" one, subsequent scanners in the same\n // category demote to `recommended`. Pre-fix SAST already enforced this\n // (an inline `isFirst` flag for category === 'sast'); SCA and secrets\n // skipped the demotion and marked every entry critical, so a TypeScript\n // plan came back with 3+ critical scanners. `opts.priority` is now the\n // priority applied to the *primary* slot only; remaining slots are\n // always `recommended` regardless of what the caller passed.\n let primarySlotFilled = recs.some((r) => r.category === opts.category);\n for (const name of opts.names) {\n if (recs.length >= opts.ctx.maxScanners) break;\n if (isAlreadyUsed(name, opts.ctx.existing)) continue;\n const entry = findScanner(name, opts.ctx.scanners);\n if (!entry) continue;\n if (opts.ctx.categoryFilter && !opts.ctx.categoryFilter.has(opts.category)) continue;\n const priority = primarySlotFilled ? 'recommended' : opts.priority;\n primarySlotFilled = true;\n recs.push({\n name,\n displayName: entry.displayName,\n category: opts.category,\n license: entry.license,\n pricingModel: entry.pricingModel,\n rationale: opts.rationale(entry),\n priority,\n ciSnippet: generateCiSnippet(name, opts.ctx.ciProvider),\n });\n }\n}\n\n/** Collect language-specific recommendations (SAST + SCA + secrets). */\nfunction collectLanguageRecs(\n langMap: LanguageMapping,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n const lang = ctx.language ?? 'unknown';\n collectCategoryRecs(recs, {\n names: langMap.sast,\n category: 'sast',\n rationale: (e) => `${e.displayName} provides SAST for ${lang}`,\n priority: 'critical',\n ctx,\n });\n collectCategoryRecs(recs, {\n names: langMap.sca,\n category: 'sca',\n rationale: (e) => `${e.displayName} provides SCA for ${lang} dependencies`,\n priority: 'critical',\n ctx,\n });\n collectCategoryRecs(recs, {\n names: langMap.secrets,\n category: 'secrets',\n rationale: () => 'Detects leaked credentials and API keys in source code',\n priority: 'critical',\n ctx,\n });\n}\n\n/** Try to add a single scanner if not already present. */\nfunction tryAddScanner(\n scannerName: string,\n category: string,\n rationale: string,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n if (recs.length >= ctx.maxScanners) return;\n if (ctx.categoryFilter && !ctx.categoryFilter.has(category)) return;\n if (isAlreadyUsed(scannerName, ctx.existing)) return;\n if (recs.some((r) => r.name === scannerName)) return;\n const entry = findScanner(scannerName, ctx.scanners);\n if (!entry) return;\n recs.push({\n name: scannerName,\n displayName: entry.displayName,\n category,\n license: entry.license,\n pricingModel: entry.pricingModel,\n rationale,\n priority: 'recommended',\n ciSnippet: generateCiSnippet(scannerName, ctx.ciProvider),\n });\n}\n\n// ============================================================================\n// Conflict Detection\n// ============================================================================\n\nfunction detectConflicts(\n recs: readonly ScannerRecommendation[],\n scanners: readonly ScannerEntry[]\n): readonly ConflictWarning[] {\n const warnings: ConflictWarning[] = [];\n const names = new Set(recs.map((r) => r.name));\n detectSuperseded(names, scanners, warnings);\n detectRedundant(recs, warnings);\n return warnings;\n}\n\nfunction detectSuperseded(\n names: ReadonlySet<string>,\n scanners: readonly ScannerEntry[],\n warnings: ConflictWarning[]\n): void {\n for (const scanner of scanners) {\n if (!names.has(scanner.name)) continue;\n if (!scanner.supersedes) continue;\n for (const old of scanner.supersedes) {\n if (names.has(old)) {\n warnings.push({\n scanners: [old, scanner.name],\n type: 'superseded',\n recommendation: `${scanner.displayName} supersedes ${old}. Remove ${old}.`,\n });\n }\n }\n }\n}\n\nfunction detectRedundant(\n recs: readonly ScannerRecommendation[],\n warnings: ConflictWarning[]\n): void {\n const catMap = new Map<string, string[]>();\n for (const rec of recs) {\n const arr = catMap.get(rec.category) ?? [];\n arr.push(rec.name);\n catMap.set(rec.category, arr);\n }\n for (const [cat, scanners] of catMap) {\n if (scanners.length > 2) {\n const count = String(scanners.length);\n warnings.push({\n scanners,\n type: 'redundant',\n recommendation: `${count} scanners for ${cat}. Consider keeping top 2.`,\n });\n }\n }\n}\n\n// ============================================================================\n// Coverage Analysis\n// ============================================================================\n\nconst ALL_CATEGORIES = ['sast', 'dast', 'sca', 'secrets', 'container', 'iac', 'image-currency'];\n\nfunction buildCoverage(\n recs: readonly ScannerRecommendation[],\n existing: readonly string[],\n scanners: readonly ScannerEntry[]\n): readonly CoverageAnalysis[] {\n return ALL_CATEGORIES.map((cat) => {\n const found = recs.filter((r) => r.category === cat).map((r) => r.name);\n const existingMatch = existing.some((t) =>\n scanners.some((s) => s.categories.includes(cat) && t.toLowerCase().includes(s.name))\n );\n return { category: cat, covered: found.length > 0 || existingMatch, scanners: found };\n });\n}\n\n// ============================================================================\n// Plan Assembly\n// ============================================================================\n\n/** Options for buildPlanFromAnalysis (allows optional fields for testability). */\ninterface BuildPlanOptions {\n readonly repo: string;\n readonly categories?: readonly string[] | undefined;\n readonly maxScanners?: number | undefined;\n}\n\n/** Generate a security scanning plan for a repository (fetches live data). */\nexport async function generateSecurityPlan(\n input: RepoSecurityPlanInput\n): Promise<RepoSecurityPlan> {\n const [analysis, data] = await Promise.all([\n analyzeGitHubRepo({ repo: input.repo, depth: 'deep' }),\n resolveScannerData(),\n ]);\n return buildPlanFromAnalysis(analysis, input, data);\n}\n\n/** Collect infrastructure-specific scanner recommendations. */\nfunction collectInfraRecs(\n analysis: RepoAnalysis,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n if (analysis.hasDockerfile) {\n tryAddScanner(\n 'grype',\n 'container',\n 'Dockerfile detected — scan container images for vulnerabilities',\n recs,\n ctx\n );\n tryAddScanner('grype-image', 'image-currency', buildImageCurrencyRationale(), recs, ctx);\n }\n if (analysis.hasHelmCharts) {\n tryAddScanner(\n 'checkov',\n 'iac',\n 'Helm charts detected — scan IaC for misconfigurations',\n recs,\n ctx\n );\n }\n}\n\n/**\n * Build the rationale string for periodic container base image CVE scanning.\n * Extracted to keep collectInfraRecs within the 50-line function limit.\n */\nfunction buildImageCurrencyRationale(): string {\n return (\n 'Dockerfile detected — periodically scan built images with ' +\n '`grype image --severity CRITICAL,HIGH` to detect CVEs introduced by stale base images. ' +\n 'Pin base images to specific version tags (e.g., node:22.4.0-alpine3.20) rather than ' +\n ':latest to get reproducible scans and predictable CVE surface area. ' +\n 'Alpine-based images typically have a smaller CVE surface than Debian/Ubuntu equivalents ' +\n 'due to musl libc and a minimal package set, but verify with grype before assuming.'\n );\n}\n\n/** Pure function: build plan from analysis + scanner data (testable). */\nexport function buildPlanFromAnalysis(\n analysis: RepoAnalysis,\n input: BuildPlanOptions,\n data?: ScannerData\n): RepoSecurityPlan {\n const resolved = data ?? FALLBACK_SCANNER_DATA;\n const ctx: RecContext = {\n existing: analysis.securityTooling,\n ciProvider: analysis.ciProvider,\n language: analysis.language,\n categoryFilter: input.categories ? new Set(input.categories) : null,\n maxScanners: input.maxScanners ?? 10,\n scanners: resolved.scanners,\n };\n\n const recs: ScannerRecommendation[] = [];\n const normalizedLang = analysis.language !== null ? normalizeLangName(analysis.language) : null;\n const langMap = normalizedLang !== null ? resolved.languageMap[normalizedLang] : undefined;\n if (langMap) collectLanguageRecs(langMap, recs, ctx);\n collectInfraRecs(analysis, recs, ctx);\n\n const conflicts = detectConflicts(recs, resolved.scanners);\n const coverage = buildCoverage(recs, analysis.securityTooling, resolved.scanners);\n const uncovered = coverage.filter((c) => !c.covered).map((c) => c.category);\n\n return {\n repo: analysis.name,\n language: analysis.language,\n framework: analysis.framework,\n ciProvider: analysis.ciProvider,\n existingTooling: analysis.securityTooling,\n recommendations: recs,\n conflicts,\n coverage,\n gapsSummary: [\n ...analysis.gaps,\n ...(uncovered.length > 0 ? [`Uncovered categories: ${uncovered.join(', ')}`] : []),\n ],\n };\n}\n"],"mappings":";;;;;;;;;AAWA,SAAS,SAAS;AA6ClB,IAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,MAAM,EAAE,KAAK,CAAC,QAAQ,cAAc,WAAW,eAAe,CAAC;AACjE,CAAC;AAED,IAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;AAAA,EACrC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACzB,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC9B,eAAe,EAAE,MAAM,kBAAkB,EAAE,SAAS;AACtD,CAAC;AAED,IAAM,4BAA4B,EAC/B,OAAO;AAAA,EACN,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EACnC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EAClC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EACtC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EACxC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EAClC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AACrC,CAAC,EACA,MAAM;AAET,IAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACzB,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,UAAU,EAAE,MAAM,aAAa;AAAA,EAC/B,gBAAgB,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,yBAAyB;AACxE,CAAC;AAYD,IAAM,eAAe,KAAK,KAAK;AAC/B,IAAI,cAAiC;AAGrC,IAAI;AAGG,SAAS,qBAA2B;AACzC,gBAAc;AACd,kBAAgB;AAClB;AAMA,IAAM,gBAAgB;AACtB,IAAM,mBAAmB;AASzB,IAAM,SAAS,aAAa,EAAE,WAAW,2BAA2B,CAAC;AAGrE,eAAe,oBAAoB,eAAsD;AACvF,QAAM,EAAE,OAAO,IAAI,MAAM;AAAA,IACvB;AAAA,IACA,CAAC,WAAW,QAAQ,UAAU,eAAe,UAAU,WAAW,QAAQ,UAAU;AAAA,IACpF,EAAE,SAAS,iBAAiB;AAAA,EAC9B;AACA,SAAO,OAAO,KAAK,KAAK;AAC1B;AAGA,eAAe,iBACb,eACyC;AACzC,QAAM,EAAE,OAAO,IAAI,MAAM;AAAA,IACvB;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,EAAE,SAAS,kBAAkB,WAAW,OAAO,KAAK;AAAA,EACtD;AAEA,MAAI;AACJ,MAAI;AACF,eAAW,KAAK,MAAM,MAAM;AAAA,EAC9B,QAAQ;AACN,WAAO,KAAK,uCAAuC;AAAA,MACjD,cAAc,OAAO;AAAA,MACrB,SAAS,OAAO,MAAM,GAAG,GAAG;AAAA,IAC9B,CAAC;AACD,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,eAAe,UAAU,QAAQ;AAChD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,KAAK,8CAA8C;AAAA,MACxD,QAAQ,OAAO,MAAM,OAAO,MAAM,GAAG,CAAC;AAAA,IACxC,CAAC;AACD,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,qCAAqC;AAAA,IAC/C,SAAS,OAAO,KAAK;AAAA,IACrB,UAAU,OAAO,KAAK,SAAS;AAAA,IAC/B,WAAW,OAAO,KAAK,OAAO,KAAK,cAAc,EAAE;AAAA,EACrD,CAAC;AACD,SAAO,OAAO;AAChB;AAOA,eAAe,0BAAmE;AAChF,MAAI;AACF,UAAM,EAAE,SAAS,IAAI,MAAM,OAAO,eAAoB;AACtD,UAAM,EAAE,UAAU,IAAI,MAAM,OAAO,MAAW;AAC9C,UAAM,gBAAgB,UAAU,QAAQ;AAExC,UAAM,MAAM,MAAM,oBAAoB,aAAa;AACnD,QAAI,QAAQ,MAAM;AAChB,aAAO,KAAK,uCAAuC;AACnD,aAAO;AAAA,IACT;AAGA,QAAI,gBAAgB,QAAQ,YAAY,eAAe,KAAK;AAC1D,aAAO,MAAM,sDAAsD,EAAE,IAAI,CAAC;AAC1E,oBAAc,EAAE,GAAG,aAAa,WAAW,gBAAgB,EAAE,IAAI,EAAE;AACnE,aAAO,YAAY;AAAA,IACrB;AAGA,UAAM,WAAW,MAAM,iBAAiB,aAAa;AACrD,QAAI,aAAa,MAAM;AACrB,oBAAc,EAAE,UAAU,WAAW,gBAAgB,EAAE,IAAI,GAAG,YAAY,IAAI;AAAA,IAChF;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,WAAO,MAAM,oCAAoC,EAAE,OAAO,IAAI,CAAC;AAC/D,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,sBAA+D;AAEnF,MAAI,gBAAgB,MAAM;AACxB,UAAM,MAAM,gBAAgB,EAAE,IAAI,IAAI,YAAY;AAClD,QAAI,MAAM,cAAc;AACtB,aAAO,YAAY;AAAA,IACrB;AAAA,EACF;AAGA,oBAAkB,wBAAwB,EAAE,QAAQ,MAAM;AACxD,oBAAgB;AAAA,EAClB,CAAC;AACD,QAAM,WAAW,MAAM;AACvB,MAAI,aAAa,MAAM;AACrB,WAAO;AAAA,EACT;AAGA,MAAI,gBAAgB,MAAM;AACxB,WAAO,KAAK,sCAAsC;AAClD,WAAO,YAAY;AAAA,EACrB;AAEA,SAAO;AACT;;;ACtOA,IAAM,oBAA6C;AAAA,EACjD;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,QAAQ,SAAS;AAAA,IAC9B,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,QAAQ,KAAK;AAAA,IAC1B,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,OAAO,aAAa,OAAO,MAAM;AAAA,IAC9C,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,OAAO,WAAW;AAAA,IAC/B,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,OAAO,QAAQ,WAAW;AAAA,IACvC,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,SAAS;AAAA,IACtB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,SAAS;AAAA,IACtB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,OAAO,KAAK;AAAA,IACzB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,QAAQ,KAAK;AAAA,IAC1B,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,kBAAkB,WAAW;AAAA,IAC1C,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AACF;AAMA,IAAM,wBAAoD;AAAA,EACxD,YAAY;AAAA,IACV,MAAM,CAAC,WAAW,mBAAmB,QAAQ;AAAA,IAC7C,KAAK,CAAC,aAAa,aAAa;AAAA,IAChC,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,YAAY;AAAA,IACV,MAAM,CAAC,WAAW,mBAAmB,QAAQ;AAAA,IAC7C,KAAK,CAAC,aAAa,aAAa;AAAA,IAChC,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,QAAQ;AAAA,IACN,MAAM,CAAC,UAAU,WAAW,QAAQ;AAAA,IACpC,KAAK,CAAC,aAAa,aAAa;AAAA,IAChC,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,CAAC,UAAU,WAAW,UAAU;AAAA,IACtC,KAAK,CAAC,0BAA0B,aAAa;AAAA,IAC7C,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,IAAI;AAAA,IACF,MAAM,CAAC,SAAS,WAAW,QAAQ;AAAA,IACnC,KAAK,CAAC,eAAe,aAAa;AAAA,IAClC,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,CAAC,YAAY,WAAW,QAAQ;AAAA,IACtC,KAAK,CAAC,iBAAiB,aAAa;AAAA,IACpC,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,KAAK;AAAA,IACH,MAAM,CAAC,WAAW,SAAS;AAAA,IAC3B,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,CAAC,UAAU,SAAS;AAAA,IAC1B,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,GAAG;AAAA,IACD,MAAM,CAAC,YAAY,UAAU,SAAS;AAAA,IACtC,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,OAAO;AAAA,IACL,MAAM,CAAC,YAAY,UAAU,SAAS;AAAA,IACtC,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,CAAC,SAAS;AAAA,IAChB,KAAK,CAAC,eAAe,aAAa;AAAA,IAClC,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,QAAQ;AAAA,IACN,MAAM,CAAC,UAAU,WAAW,QAAQ;AAAA,IACpC,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,OAAO;AAAA,IACL,MAAM,CAAC,UAAU,SAAS;AAAA,IAC1B,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,OAAO;AAAA,IACL,MAAM,CAAC,WAAW,UAAU;AAAA,IAC5B,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,OAAO;AAAA,IACL,MAAM,CAAC,cAAc,SAAS;AAAA,IAC9B,KAAK,CAAC;AAAA,IACN,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,KAAK;AAAA,IACH,MAAM,CAAC,WAAW,OAAO;AAAA,IACzB,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AACF;AAOO,IAAM,wBAAqC;AAAA,EAChD,UAAU;AAAA,EACV,aAAa;AAAA,EACb,QAAQ;AACV;;;AC/RA,IAAMA,UAAS,aAAa,EAAE,WAAW,qBAAqB,CAAC;AAqC/D,SAAS,uBAAuB,GAAkC;AAChE,QAAM,aAAa,EAAE,eAAe,OAAO,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM;AAC9F,SAAO;AAAA,IACL,MAAM,EAAE;AAAA,IACR,aAAa,EAAE;AAAA,IACf,YAAY,EAAE;AAAA,IACd,SAAS,EAAE;AAAA,IACX,cAAc,EAAE;AAAA,IAChB,GAAI,eAAe,UAAa,WAAW,SAAS,IAAI,EAAE,WAAW,IAAI,CAAC;AAAA,EAC5E;AACF;AAGA,IAAM,sBAAwD;AAAA,EAC5D,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,KAAK;AAAA,EACL,OAAO;AAAA,EACP,YAAY;AACd;AAEA,SAAS,kBAAkB,MAAsB;AAC/C,QAAM,QAAQ,KAAK,YAAY;AAC/B,SAAO,oBAAoB,KAAK,KAAK,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC;AAClF;AAEA,SAAS,sBACP,QACiC;AACjC,QAAM,SAA0C,CAAC;AACjD,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAElD,UAAM,aAAa,kBAAkB,IAAI;AACzC,WAAO,UAAU,IAAI;AAAA,MACnB,MAAM,MAAM,QAAQ,CAAC;AAAA,MACrB,KAAK,MAAM,OAAO,CAAC;AAAA,MACnB,SAAS,MAAM,WAAW,CAAC;AAAA,IAC7B;AAAA,EACF;AACA,SAAO;AACT;AAGA,eAAsB,qBAA2C;AAC/D,QAAM,WAAW,MAAM,oBAAoB;AAC3C,MAAI,aAAa,MAAM;AACrB,IAAAA,QAAO,KAAK,+BAA+B;AAAA,MACzC,SAAS,SAAS;AAAA,MAClB,UAAU,SAAS,SAAS;AAAA,IAC9B,CAAC;AACD,WAAO;AAAA,MACL,UAAU,SAAS,SAAS,IAAI,sBAAsB;AAAA,MACtD,aAAa,sBAAsB,SAAS,cAAc;AAAA,MAC1D,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,EAAAA,QAAO,KAAK,6BAA6B;AACzC,SAAO;AACT;AAMA,IAAM,cAAgD;AAAA,EACpD,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,eACE;AAAA,EACF,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,SAAS;AAAA,EACT,eAAe;AAAA,EACf,MAAM;AAAA,EACN,YAAY;AAAA;AAAA;AAAA,EAGZ,mBACE;AAAA,EACF,WACE;AAAA,EACF,aAAa;AAAA,EACb,OACE;AAAA,EACF,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,UACE;AAAA,EACF,aAAa;AAAA,EACb,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,OAAO;AAAA,EACP,0BACE;AAAA,EACF,aACE;AAAA,EACF,MAAM;AACR;AAEA,SAAS,kBAAkB,MAAc,IAAkC;AACzE,MAAI,OAAO,iBAAkB,QAAO;AACpC,SAAO,YAAY,IAAI,KAAK;AAC9B;AAMA,SAAS,YAAY,MAAc,UAA6D;AAC9F,SAAO,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAC7C;AAEA,SAAS,cAAc,MAAc,UAAsC;AACzE,SAAO,SAAS,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,KAAK,YAAY,CAAC,CAAC;AAC1E;AAsBA,SAAS,oBAAoB,MAA+B,MAAgC;AAS1F,MAAI,oBAAoB,KAAK,KAAK,CAAC,MAAM,EAAE,aAAa,KAAK,QAAQ;AACrE,aAAW,QAAQ,KAAK,OAAO;AAC7B,QAAI,KAAK,UAAU,KAAK,IAAI,YAAa;AACzC,QAAI,cAAc,MAAM,KAAK,IAAI,QAAQ,EAAG;AAC5C,UAAM,QAAQ,YAAY,MAAM,KAAK,IAAI,QAAQ;AACjD,QAAI,CAAC,MAAO;AACZ,QAAI,KAAK,IAAI,kBAAkB,CAAC,KAAK,IAAI,eAAe,IAAI,KAAK,QAAQ,EAAG;AAC5E,UAAM,WAAW,oBAAoB,gBAAgB,KAAK;AAC1D,wBAAoB;AACpB,SAAK,KAAK;AAAA,MACR;AAAA,MACA,aAAa,MAAM;AAAA,MACnB,UAAU,KAAK;AAAA,MACf,SAAS,MAAM;AAAA,MACf,cAAc,MAAM;AAAA,MACpB,WAAW,KAAK,UAAU,KAAK;AAAA,MAC/B;AAAA,MACA,WAAW,kBAAkB,MAAM,KAAK,IAAI,UAAU;AAAA,IACxD,CAAC;AAAA,EACH;AACF;AAGA,SAAS,oBACP,SACA,MACA,KACM;AACN,QAAM,OAAO,IAAI,YAAY;AAC7B,sBAAoB,MAAM;AAAA,IACxB,OAAO,QAAQ;AAAA,IACf,UAAU;AAAA,IACV,WAAW,CAAC,MAAM,GAAG,EAAE,WAAW,sBAAsB,IAAI;AAAA,IAC5D,UAAU;AAAA,IACV;AAAA,EACF,CAAC;AACD,sBAAoB,MAAM;AAAA,IACxB,OAAO,QAAQ;AAAA,IACf,UAAU;AAAA,IACV,WAAW,CAAC,MAAM,GAAG,EAAE,WAAW,qBAAqB,IAAI;AAAA,IAC3D,UAAU;AAAA,IACV;AAAA,EACF,CAAC;AACD,sBAAoB,MAAM;AAAA,IACxB,OAAO,QAAQ;AAAA,IACf,UAAU;AAAA,IACV,WAAW,MAAM;AAAA,IACjB,UAAU;AAAA,IACV;AAAA,EACF,CAAC;AACH;AAGA,SAAS,cACP,aACA,UACA,WACA,MACA,KACM;AACN,MAAI,KAAK,UAAU,IAAI,YAAa;AACpC,MAAI,IAAI,kBAAkB,CAAC,IAAI,eAAe,IAAI,QAAQ,EAAG;AAC7D,MAAI,cAAc,aAAa,IAAI,QAAQ,EAAG;AAC9C,MAAI,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,WAAW,EAAG;AAC9C,QAAM,QAAQ,YAAY,aAAa,IAAI,QAAQ;AACnD,MAAI,CAAC,MAAO;AACZ,OAAK,KAAK;AAAA,IACR,MAAM;AAAA,IACN,aAAa,MAAM;AAAA,IACnB;AAAA,IACA,SAAS,MAAM;AAAA,IACf,cAAc,MAAM;AAAA,IACpB;AAAA,IACA,UAAU;AAAA,IACV,WAAW,kBAAkB,aAAa,IAAI,UAAU;AAAA,EAC1D,CAAC;AACH;AAMA,SAAS,gBACP,MACA,UAC4B;AAC5B,QAAM,WAA8B,CAAC;AACrC,QAAM,QAAQ,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAC7C,mBAAiB,OAAO,UAAU,QAAQ;AAC1C,kBAAgB,MAAM,QAAQ;AAC9B,SAAO;AACT;AAEA,SAAS,iBACP,OACA,UACA,UACM;AACN,aAAW,WAAW,UAAU;AAC9B,QAAI,CAAC,MAAM,IAAI,QAAQ,IAAI,EAAG;AAC9B,QAAI,CAAC,QAAQ,WAAY;AACzB,eAAW,OAAO,QAAQ,YAAY;AACpC,UAAI,MAAM,IAAI,GAAG,GAAG;AAClB,iBAAS,KAAK;AAAA,UACZ,UAAU,CAAC,KAAK,QAAQ,IAAI;AAAA,UAC5B,MAAM;AAAA,UACN,gBAAgB,GAAG,QAAQ,WAAW,eAAe,GAAG,YAAY,GAAG;AAAA,QACzE,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,gBACP,MACA,UACM;AACN,QAAM,SAAS,oBAAI,IAAsB;AACzC,aAAW,OAAO,MAAM;AACtB,UAAM,MAAM,OAAO,IAAI,IAAI,QAAQ,KAAK,CAAC;AACzC,QAAI,KAAK,IAAI,IAAI;AACjB,WAAO,IAAI,IAAI,UAAU,GAAG;AAAA,EAC9B;AACA,aAAW,CAAC,KAAK,QAAQ,KAAK,QAAQ;AACpC,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,QAAQ,OAAO,SAAS,MAAM;AACpC,eAAS,KAAK;AAAA,QACZ;AAAA,QACA,MAAM;AAAA,QACN,gBAAgB,GAAG,KAAK,iBAAiB,GAAG;AAAA,MAC9C,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAMA,IAAM,iBAAiB,CAAC,QAAQ,QAAQ,OAAO,WAAW,aAAa,OAAO,gBAAgB;AAE9F,SAAS,cACP,MACA,UACA,UAC6B;AAC7B,SAAO,eAAe,IAAI,CAAC,QAAQ;AACjC,UAAM,QAAQ,KAAK,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AACtE,UAAM,gBAAgB,SAAS;AAAA,MAAK,CAAC,MACnC,SAAS,KAAK,CAAC,MAAM,EAAE,WAAW,SAAS,GAAG,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,CAAC;AAAA,IACrF;AACA,WAAO,EAAE,UAAU,KAAK,SAAS,MAAM,SAAS,KAAK,eAAe,UAAU,MAAM;AAAA,EACtF,CAAC;AACH;AAcA,eAAsB,qBACpB,OAC2B;AAC3B,QAAM,CAAC,UAAU,IAAI,IAAI,MAAM,QAAQ,IAAI;AAAA,IACzC,kBAAkB,EAAE,MAAM,MAAM,MAAM,OAAO,OAAO,CAAC;AAAA,IACrD,mBAAmB;AAAA,EACrB,CAAC;AACD,SAAO,sBAAsB,UAAU,OAAO,IAAI;AACpD;AAGA,SAAS,iBACP,UACA,MACA,KACM;AACN,MAAI,SAAS,eAAe;AAC1B;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,kBAAc,eAAe,kBAAkB,4BAA4B,GAAG,MAAM,GAAG;AAAA,EACzF;AACA,MAAI,SAAS,eAAe;AAC1B;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAMA,SAAS,8BAAsC;AAC7C,SACE;AAOJ;AAGO,SAAS,sBACd,UACA,OACA,MACkB;AAClB,QAAM,WAAW,QAAQ;AACzB,QAAM,MAAkB;AAAA,IACtB,UAAU,SAAS;AAAA,IACnB,YAAY,SAAS;AAAA,IACrB,UAAU,SAAS;AAAA,IACnB,gBAAgB,MAAM,aAAa,IAAI,IAAI,MAAM,UAAU,IAAI;AAAA,IAC/D,aAAa,MAAM,eAAe;AAAA,IAClC,UAAU,SAAS;AAAA,EACrB;AAEA,QAAM,OAAgC,CAAC;AACvC,QAAM,iBAAiB,SAAS,aAAa,OAAO,kBAAkB,SAAS,QAAQ,IAAI;AAC3F,QAAM,UAAU,mBAAmB,OAAO,SAAS,YAAY,cAAc,IAAI;AACjF,MAAI,QAAS,qBAAoB,SAAS,MAAM,GAAG;AACnD,mBAAiB,UAAU,MAAM,GAAG;AAEpC,QAAM,YAAY,gBAAgB,MAAM,SAAS,QAAQ;AACzD,QAAM,WAAW,cAAc,MAAM,SAAS,iBAAiB,SAAS,QAAQ;AAChF,QAAM,YAAY,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ;AAE1E,SAAO;AAAA,IACL,MAAM,SAAS;AAAA,IACf,UAAU,SAAS;AAAA,IACnB,WAAW,SAAS;AAAA,IACpB,YAAY,SAAS;AAAA,IACrB,iBAAiB,SAAS;AAAA,IAC1B,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,aAAa;AAAA,MACX,GAAG,SAAS;AAAA,MACZ,GAAI,UAAU,SAAS,IAAI,CAAC,yBAAyB,UAAU,KAAK,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,IAClF;AAAA,EACF;AACF;","names":["logger"]}
1
+ {"version":3,"sources":["../src/mcp/tools/scanner-registry-fetcher.ts","../src/mcp/tools/repo-security-plan-fallback.ts","../src/mcp/tools/repo-security-plan.ts"],"sourcesContent":["/**\n * nexus-agents/mcp - Scanner Registry Fetcher\n *\n * Fetches the scanner-registry.json manifest from the\n * vulnerability-scanner-registry GitHub Releases at runtime.\n * Uses a TTL cache and falls back to embedded data on failure.\n *\n * @module mcp/tools/scanner-registry-fetcher\n * (Source: Consensus vote — externalize scanner registry, 6-0 unanimous)\n */\n\nimport { z } from 'zod';\nimport { createLogger, getTimeProvider } from '../../core/index.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** A scanner entry from the registry manifest. */\nexport interface RegistryScanner {\n readonly name: string;\n readonly displayName: string;\n readonly categories: readonly string[];\n readonly license: string;\n readonly pricingModel: string;\n readonly relationships?: readonly RegistryRelationship[] | undefined;\n}\n\n/** A relationship edge between scanners. */\nexport interface RegistryRelationship {\n readonly target: string;\n readonly type: 'uses' | 'supersedes' | 'bundles' | 'competes-with';\n}\n\n/** Language matrix: category → scanner names. */\nexport interface LanguageMatrixEntry {\n readonly sast?: readonly string[] | undefined;\n readonly sca?: readonly string[] | undefined;\n readonly secrets?: readonly string[] | undefined;\n readonly container?: readonly string[] | undefined;\n readonly iac?: readonly string[] | undefined;\n readonly dast?: readonly string[] | undefined;\n}\n\n/** The full registry manifest shape. */\nexport interface ScannerRegistryManifest {\n readonly version: string;\n readonly generatedAt: string;\n readonly scanners: readonly RegistryScanner[];\n readonly languageMatrix: Readonly<Record<string, LanguageMatrixEntry>>;\n}\n\n// ============================================================================\n// Zod Schema for Validation\n// ============================================================================\n\nconst RelationshipSchema = z.object({\n target: z.string().min(1),\n type: z.enum(['uses', 'supersedes', 'bundles', 'competes-with']),\n});\n\nconst ScannerSchema = z.object({\n name: z.string().min(1),\n displayName: z.string().min(1),\n categories: z.array(z.string().min(1)),\n license: z.string().min(1),\n pricingModel: z.string().min(1),\n relationships: z.array(RelationshipSchema).optional(),\n});\n\nconst LanguageMatrixEntrySchema = z\n .object({\n sast: z.array(z.string()).optional(),\n sca: z.array(z.string()).optional(),\n secrets: z.array(z.string()).optional(),\n container: z.array(z.string()).optional(),\n iac: z.array(z.string()).optional(),\n dast: z.array(z.string()).optional(),\n })\n .loose();\n\nconst ManifestSchema = z.object({\n version: z.string().min(1),\n generatedAt: z.string().min(1),\n scanners: z.array(ScannerSchema),\n languageMatrix: z.record(z.string().max(50), LanguageMatrixEntrySchema),\n});\n\n// ============================================================================\n// Cache\n// ============================================================================\n\ninterface CacheEntry {\n manifest: ScannerRegistryManifest;\n fetchedAt: number;\n releaseTag: string;\n}\n\nconst CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\nlet cachedEntry: CacheEntry | null = null;\n\n/** Inflight fetch promise for probe coalescing (#1448). */\nlet inflightFetch: Promise<ScannerRegistryManifest | null> | undefined;\n\n/** Clear the cache and inflight state (for testing). */\nexport function clearRegistryCache(): void {\n cachedEntry = null;\n inflightFetch = undefined;\n}\n\n// ============================================================================\n// Fetcher\n// ============================================================================\n\nconst REGISTRY_REPO = 'williamzujkowski/vulnerability-scanner-registry';\nconst FETCH_TIMEOUT_MS = 10_000;\n\n/** Promisified execFile signature used by fetcher helpers. */\ntype ExecFileAsync = (\n file: string,\n args: readonly string[],\n options: { timeout?: number; maxBuffer?: number }\n) => Promise<{ stdout: string; stderr: string }>;\n\nconst logger = createLogger({ component: 'scanner-registry-fetcher' });\n\n/** Get the latest release tag name (lightweight check, no download). */\nasync function getLatestReleaseTag(execFileAsync: ExecFileAsync): Promise<string | null> {\n const { stdout } = await execFileAsync(\n 'gh',\n ['release', 'view', '--repo', REGISTRY_REPO, '--json', 'tagName', '--jq', '.tagName'],\n { timeout: FETCH_TIMEOUT_MS }\n );\n return stdout.trim() || null;\n}\n\n/** Download and parse the full manifest. */\nasync function downloadManifest(\n execFileAsync: ExecFileAsync\n): Promise<ScannerRegistryManifest | null> {\n const { stdout } = await execFileAsync(\n 'gh',\n [\n 'release',\n 'download',\n '--repo',\n REGISTRY_REPO,\n '--pattern',\n 'scanner-registry.json',\n '--output',\n '-',\n ],\n { timeout: FETCH_TIMEOUT_MS, maxBuffer: 1024 * 1024 }\n );\n\n let jsonData: unknown;\n try {\n jsonData = JSON.parse(stdout);\n } catch {\n logger.warn('Registry manifest is not valid JSON', {\n stdoutLength: stdout.length,\n preview: stdout.slice(0, 100),\n });\n return null;\n }\n\n const parsed = ManifestSchema.safeParse(jsonData);\n if (!parsed.success) {\n logger.warn('Registry manifest failed schema validation', {\n errors: parsed.error.issues.slice(0, 3),\n });\n return null;\n }\n\n logger.info('Fetched scanner registry manifest', {\n version: parsed.data.version,\n scanners: parsed.data.scanners.length,\n languages: Object.keys(parsed.data.languageMatrix).length,\n });\n return parsed.data;\n}\n\n/**\n * Fetch the scanner registry manifest from GitHub Releases.\n * If we have a cached version and the release tag hasn't changed,\n * just refreshes the cache timer (no download).\n */\nasync function fetchManifestFromGitHub(): Promise<ScannerRegistryManifest | null> {\n try {\n const { execFile } = await import('node:child_process');\n const { promisify } = await import('node:util');\n const execFileAsync = promisify(execFile);\n\n const tag = await getLatestReleaseTag(execFileAsync);\n if (tag === null) {\n logger.warn('No releases found in scanner registry');\n return null;\n }\n\n // If cached version matches the latest tag, refresh timer only\n if (cachedEntry !== null && cachedEntry.releaseTag === tag) {\n logger.debug('Scanner registry unchanged, refreshing cache timer', { tag });\n cachedEntry = { ...cachedEntry, fetchedAt: getTimeProvider().now() };\n return cachedEntry.manifest;\n }\n\n // New release — download full manifest\n const manifest = await downloadManifest(execFileAsync);\n if (manifest !== null) {\n cachedEntry = { manifest, fetchedAt: getTimeProvider().now(), releaseTag: tag };\n }\n return manifest;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.debug('Failed to fetch scanner registry', { error: msg });\n return null;\n }\n}\n\n/**\n * Get the scanner registry, fetching from GitHub if cache is stale.\n * Returns null if no cached data and fetch fails.\n */\nexport async function getRegistryManifest(): Promise<ScannerRegistryManifest | null> {\n // Check cache\n if (cachedEntry !== null) {\n const age = getTimeProvider().now() - cachedEntry.fetchedAt;\n if (age < CACHE_TTL_MS) {\n return cachedEntry.manifest;\n }\n }\n\n // Coalesce concurrent fetches — only one inflight request at a time (#1448)\n inflightFetch ??= fetchManifestFromGitHub().finally(() => {\n inflightFetch = undefined;\n });\n const manifest = await inflightFetch;\n if (manifest !== null) {\n return manifest;\n }\n\n // Return stale cache if available\n if (cachedEntry !== null) {\n logger.warn('Using stale cached registry manifest');\n return cachedEntry.manifest;\n }\n\n return null;\n}\n\n/**\n * Extract scanners from manifest into the format expected by plan builder.\n */\nexport function extractScannerEntries(\n manifest: ScannerRegistryManifest\n): readonly RegistryScanner[] {\n return manifest.scanners;\n}\n\n/**\n * Extract language matrix, normalizing to consistent category keys.\n */\nexport function extractLanguageMatrix(\n manifest: ScannerRegistryManifest\n): Readonly<Record<string, LanguageMatrixEntry>> {\n return manifest.languageMatrix;\n}\n","/**\n * nexus-agents/mcp - Fallback Scanner Data\n *\n * Embedded snapshot of the vulnerability-scanner-registry manifest.\n * Used when the live registry fetch fails (network issues, gh CLI\n * unavailable, etc.). Updated periodically from the canonical\n * registry at github.com/williamzujkowski/vulnerability-scanner-registry.\n *\n * @module mcp/tools/repo-security-plan-fallback\n * (Source: Consensus vote — externalize scanner registry, 6-0 unanimous)\n */\n\nimport type { ScannerData } from './repo-security-plan.js';\n\n// ============================================================================\n// Fallback Scanner Entries (27 scanners)\n// ============================================================================\n\nconst FALLBACK_SCANNERS: ScannerData['scanners'] = [\n {\n name: 'semgrep',\n displayName: 'Semgrep',\n categories: ['sast', 'secrets'],\n license: 'LGPL-2.1',\n pricingModel: 'freemium',\n },\n {\n name: 'codeql',\n displayName: 'CodeQL',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'freemium',\n },\n {\n name: 'bandit',\n displayName: 'Bandit',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'gosec',\n displayName: 'Gosec',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'brakeman',\n displayName: 'Brakeman',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'phpstan',\n displayName: 'PHPStan',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'freemium',\n },\n {\n name: 'shellcheck',\n displayName: 'ShellCheck',\n categories: ['sast'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'cppcheck',\n displayName: 'Cppcheck',\n categories: ['sast'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'detekt',\n displayName: 'detekt',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'spotbugs',\n displayName: 'SpotBugs',\n categories: ['sast'],\n license: 'LGPL-2.1',\n pricingModel: 'free',\n },\n {\n name: 'eslint-security',\n displayName: 'eslint-plugin-security',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'sonarqube',\n displayName: 'SonarQube',\n categories: ['sast', 'sca'],\n license: 'LGPL-3.0',\n pricingModel: 'freemium',\n },\n {\n name: 'osv-scanner',\n displayName: 'OSV-Scanner',\n categories: ['sca', 'container', 'iac', 'sbom'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'grype',\n displayName: 'Grype',\n categories: ['sca', 'container'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'snyk',\n displayName: 'Snyk',\n categories: ['sca', 'sast', 'container'],\n license: 'Proprietary',\n pricingModel: 'freemium',\n },\n {\n name: 'npm-audit',\n displayName: 'npm audit',\n categories: ['sca'],\n license: 'Artistic-2.0',\n pricingModel: 'free',\n },\n {\n name: 'pip-audit',\n displayName: 'pip-audit',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'cargo-audit',\n displayName: 'cargo-audit',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'bundler-audit',\n displayName: 'bundler-audit',\n categories: ['sca'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'govulncheck',\n displayName: 'govulncheck',\n categories: ['sca'],\n license: 'BSD-3-Clause',\n pricingModel: 'free',\n },\n {\n name: 'owasp-dependency-check',\n displayName: 'OWASP Dependency-Check',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'gitleaks',\n displayName: 'Gitleaks',\n categories: ['secrets'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'trufflehog',\n displayName: 'TruffleHog',\n categories: ['secrets'],\n license: 'AGPL-3.0',\n pricingModel: 'freemium',\n },\n {\n name: 'checkov',\n displayName: 'Checkov',\n categories: ['iac', 'sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'tfsec',\n displayName: 'tfsec',\n categories: ['iac'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'owasp-zap',\n displayName: 'OWASP ZAP',\n categories: ['dast', 'api'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'syft',\n displayName: 'Syft',\n categories: ['sbom'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'grype-image',\n displayName: 'Grype (image scan)',\n categories: ['image-currency', 'container'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n];\n\n// ============================================================================\n// Fallback Language Map (16 languages)\n// ============================================================================\n\nconst FALLBACK_LANGUAGE_MAP: ScannerData['languageMap'] = {\n TypeScript: {\n sast: ['semgrep', 'eslint-security', 'codeql'],\n sca: ['npm-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n JavaScript: {\n sast: ['semgrep', 'eslint-security', 'codeql'],\n sca: ['npm-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Python: {\n sast: ['bandit', 'semgrep', 'codeql'],\n sca: ['pip-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Java: {\n sast: ['codeql', 'semgrep', 'spotbugs'],\n sca: ['owasp-dependency-check', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Go: {\n sast: ['gosec', 'semgrep', 'codeql'],\n sca: ['govulncheck', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Ruby: {\n sast: ['brakeman', 'semgrep', 'codeql'],\n sca: ['bundler-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n PHP: {\n sast: ['phpstan', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n 'C#': {\n sast: ['codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n C: {\n sast: ['cppcheck', 'codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n 'C++': {\n sast: ['cppcheck', 'codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Rust: {\n sast: ['semgrep'],\n sca: ['cargo-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Kotlin: {\n sast: ['detekt', 'semgrep', 'codeql'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Swift: {\n sast: ['codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Scala: {\n sast: ['semgrep', 'spotbugs'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Shell: {\n sast: ['shellcheck', 'semgrep'],\n sca: [],\n secrets: ['gitleaks'],\n },\n HCL: {\n sast: ['checkov', 'tfsec'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n};\n\n// ============================================================================\n// Exported Fallback\n// ============================================================================\n\n/** Embedded scanner data snapshot used when live registry is unavailable. */\nexport const FALLBACK_SCANNER_DATA: ScannerData = {\n scanners: FALLBACK_SCANNERS,\n languageMap: FALLBACK_LANGUAGE_MAP,\n source: 'fallback',\n};\n","/**\n * nexus-agents/mcp - Repository Security Plan Logic\n *\n * Generates a language-aware security scanning pipeline recommendation\n * by composing repo_analyze output with scanner registry data.\n * Fetches fresh data from vulnerability-scanner-registry GitHub Releases;\n * falls back to embedded snapshot if fetch fails.\n *\n * @module mcp/tools/repo-security-plan\n * (Source: Issue #1079, externalization vote 6-0 unanimous)\n */\n\nimport type { RepoAnalysis } from './repo-analyze-types.js';\nimport type {\n RepoSecurityPlanInput,\n RepoSecurityPlan,\n ScannerRecommendation,\n ConflictWarning,\n CoverageAnalysis,\n} from './repo-security-plan-types.js';\nimport { analyzeGitHubRepo } from './repo-analyze.js';\nimport { getRegistryManifest } from './scanner-registry-fetcher.js';\nimport type { RegistryScanner, LanguageMatrixEntry } from './scanner-registry-fetcher.js';\nimport { FALLBACK_SCANNER_DATA } from './repo-security-plan-fallback.js';\nimport { createLogger } from '../../core/index.js';\n\nconst logger = createLogger({ component: 'repo-security-plan' });\n\n// ============================================================================\n// Scanner Data Interface (common shape for fetched + fallback)\n// ============================================================================\n\n/** Internal scanner entry used by plan builder. */\nexport interface ScannerEntry {\n readonly name: string;\n readonly displayName: string;\n readonly categories: readonly string[];\n readonly license: string;\n readonly pricingModel: string;\n readonly supersedes?: readonly string[];\n}\n\n/** Language mapping: category → scanner names. */\ninterface LanguageMapping {\n readonly sast: readonly string[];\n readonly sca: readonly string[];\n readonly secrets: readonly string[];\n}\n\n/** Resolved scanner data for plan building. */\nexport interface ScannerData {\n readonly scanners: readonly ScannerEntry[];\n readonly languageMap: Readonly<Record<string, LanguageMapping>>;\n readonly source: 'registry' | 'fallback';\n}\n\n// Re-export for consumers\nexport { FALLBACK_SCANNER_DATA } from './repo-security-plan-fallback.js';\n\n// ============================================================================\n// Registry → ScannerData Conversion\n// ============================================================================\n\nfunction convertRegistryScanner(s: RegistryScanner): ScannerEntry {\n const supersedes = s.relationships?.filter((r) => r.type === 'supersedes').map((r) => r.target);\n return {\n name: s.name,\n displayName: s.displayName,\n categories: s.categories,\n license: s.license,\n pricingModel: s.pricingModel,\n ...(supersedes !== undefined && supersedes.length > 0 ? { supersedes } : {}),\n };\n}\n\n/** Known PascalCase language names from GitHub API. Handles registry keys like \"typescript\" → \"TypeScript\". */\nconst LANGUAGE_PASCAL_MAP: Readonly<Record<string, string>> = {\n typescript: 'TypeScript',\n javascript: 'JavaScript',\n python: 'Python',\n java: 'Java',\n csharp: 'C#',\n 'c#': 'C#',\n cpp: 'C++',\n 'c++': 'C++',\n go: 'Go',\n rust: 'Rust',\n ruby: 'Ruby',\n php: 'PHP',\n swift: 'Swift',\n kotlin: 'Kotlin',\n scala: 'Scala',\n hcl: 'HCL',\n shell: 'Shell',\n dockerfile: 'Dockerfile',\n};\n\nfunction normalizeLangName(lang: string): string {\n const lower = lang.toLowerCase();\n return LANGUAGE_PASCAL_MAP[lower] ?? lang.charAt(0).toUpperCase() + lang.slice(1);\n}\n\nfunction convertLanguageMatrix(\n matrix: Readonly<Record<string, LanguageMatrixEntry>>\n): Record<string, LanguageMapping> {\n const result: Record<string, LanguageMapping> = {};\n for (const [lang, entry] of Object.entries(matrix)) {\n // Normalize language name to PascalCase (GitHub API returns PascalCase like \"TypeScript\")\n const normalized = normalizeLangName(lang);\n result[normalized] = {\n sast: entry.sast ?? [],\n sca: entry.sca ?? [],\n secrets: entry.secrets ?? [],\n };\n }\n return result;\n}\n\n/** Resolve scanner data: fetch from registry, fall back to embedded. */\nexport async function resolveScannerData(): Promise<ScannerData> {\n const manifest = await getRegistryManifest();\n if (manifest !== null) {\n logger.info('Using live scanner registry', {\n version: manifest.version,\n scanners: manifest.scanners.length,\n });\n return {\n scanners: manifest.scanners.map(convertRegistryScanner),\n languageMap: convertLanguageMatrix(manifest.languageMatrix),\n source: 'registry',\n };\n }\n\n logger.info('Using fallback scanner data');\n return FALLBACK_SCANNER_DATA;\n}\n\n// ============================================================================\n// CI Snippet Generation (GitHub Actions only for v1)\n// ============================================================================\n\nconst CI_SNIPPETS: Readonly<Record<string, string>> = {\n semgrep: '- uses: semgrep/semgrep-action@v1\\n with:\\n config: auto',\n codeql: '- uses: github/codeql-action/analyze@v3',\n grype: '- uses: anchore/scan-action@v4\\n with:\\n path: .',\n 'grype-image':\n '- uses: anchore/scan-action@v4\\n with:\\n image: ${{ env.IMAGE_TAG }}\\n severity: CRITICAL,HIGH\\n exit-code: 1',\n gitleaks: '- uses: gitleaks/gitleaks-action@v2',\n bandit: '- run: pip install bandit && bandit -r . -f json',\n gosec: '- uses: securego/gosec@master\\n with:\\n args: ./...',\n checkov: '- uses: bridgecrewio/checkov-action@master',\n 'osv-scanner': '- uses: google/osv-scanner-action@v1',\n snyk: '- uses: snyk/actions/node@master # adjust for language',\n shellcheck: '- uses: ludeeus/action-shellcheck@master',\n // #2732: scanners recommended for TypeScript repos that were missing\n // snippets, leaving most rows with ciSnippet: null.\n 'eslint-security':\n '- run: npm install --save-dev eslint-plugin-security && npx eslint --plugin security .',\n sonarqube:\n '- uses: sonarsource/sonarqube-scan-action@v3\\n env:\\n SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}',\n 'npm-audit': '- run: npm audit --audit-level=high',\n trivy:\n '- uses: aquasecurity/trivy-action@master\\n with:\\n scan-type: fs\\n severity: CRITICAL,HIGH\\n exit-code: 1',\n trufflehog: '- uses: trufflesecurity/trufflehog@main\\n with:\\n extra_args: --only-verified',\n cppcheck: '- run: cppcheck --enable=all --error-exitcode=1 .',\n spotbugs:\n '- uses: jwgmeligmeyling/spotbugs-github-action@master\\n with:\\n path: target/spotbugsXml.xml',\n 'pip-audit': '- run: pip install pip-audit && pip-audit',\n 'cargo-audit': '- run: cargo install cargo-audit --locked && cargo audit',\n 'bundler-audit': '- run: gem install bundler-audit && bundle-audit check --update',\n 'composer-audit': '- run: composer audit',\n govulncheck: '- run: go install golang.org/x/vuln/cmd/govulncheck@latest && govulncheck ./...',\n detekt: '- uses: natiginfo/action-detekt-all@1.23.6',\n brakeman: '- run: gem install brakeman && brakeman -f json',\n phpstan: '- run: composer install && vendor/bin/phpstan analyse',\n tfsec: '- uses: aquasecurity/tfsec-action@v1.0.3',\n 'owasp-dependency-check':\n '- uses: dependency-check/Dependency-Check_Action@main\\n with:\\n project: ${{ github.event.repository.name }}\\n path: .\\n format: HTML',\n 'owasp-zap':\n '- uses: zaproxy/action-baseline@v0.12.0\\n with:\\n target: ${{ env.TARGET_URL }}',\n syft: '- uses: anchore/sbom-action@v0\\n with:\\n path: .\\n format: cyclonedx-json',\n};\n\nfunction generateCiSnippet(name: string, ci: string | null): string | null {\n if (ci !== 'github-actions') return null;\n return CI_SNIPPETS[name] ?? null;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction findScanner(name: string, scanners: readonly ScannerEntry[]): ScannerEntry | undefined {\n return scanners.find((s) => s.name === name);\n}\n\nfunction isAlreadyUsed(name: string, existing: readonly string[]): boolean {\n return existing.some((t) => t.toLowerCase().includes(name.toLowerCase()));\n}\n\n/** Context passed to recommendation collectors. */\ninterface RecContext {\n readonly existing: readonly string[];\n readonly ciProvider: string | null;\n readonly language: string | null;\n readonly categoryFilter: ReadonlySet<string> | null;\n readonly maxScanners: number;\n readonly scanners: readonly ScannerEntry[];\n}\n\n/** Options for collecting recommendations in a single category. */\ninterface CategoryRecOptions {\n readonly names: readonly string[];\n readonly category: string;\n readonly rationale: (entry: ScannerEntry) => string;\n readonly priority: 'critical' | 'recommended';\n readonly ctx: RecContext;\n}\n\n/** Collect recommendations for a single category. */\nfunction collectCategoryRecs(recs: ScannerRecommendation[], opts: CategoryRecOptions): void {\n // #2732: at most one `critical` slot per category — the first scanner\n // picked is the \"must run\" one, subsequent scanners in the same\n // category demote to `recommended`. Pre-fix SAST already enforced this\n // (an inline `isFirst` flag for category === 'sast'); SCA and secrets\n // skipped the demotion and marked every entry critical, so a TypeScript\n // plan came back with 3+ critical scanners. `opts.priority` is now the\n // priority applied to the *primary* slot only; remaining slots are\n // always `recommended` regardless of what the caller passed.\n let primarySlotFilled = recs.some((r) => r.category === opts.category);\n for (const name of opts.names) {\n if (recs.length >= opts.ctx.maxScanners) break;\n if (isAlreadyUsed(name, opts.ctx.existing)) continue;\n const entry = findScanner(name, opts.ctx.scanners);\n if (!entry) continue;\n if (opts.ctx.categoryFilter && !opts.ctx.categoryFilter.has(opts.category)) continue;\n const priority = primarySlotFilled ? 'recommended' : opts.priority;\n primarySlotFilled = true;\n recs.push({\n name,\n displayName: entry.displayName,\n category: opts.category,\n license: entry.license,\n pricingModel: entry.pricingModel,\n rationale: opts.rationale(entry),\n priority,\n ciSnippet: generateCiSnippet(name, opts.ctx.ciProvider),\n });\n }\n}\n\n/** Collect language-specific recommendations (SAST + SCA + secrets). */\nfunction collectLanguageRecs(\n langMap: LanguageMapping,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n const lang = ctx.language ?? 'unknown';\n collectCategoryRecs(recs, {\n names: langMap.sast,\n category: 'sast',\n rationale: (e) => `${e.displayName} provides SAST for ${lang}`,\n priority: 'critical',\n ctx,\n });\n collectCategoryRecs(recs, {\n names: langMap.sca,\n category: 'sca',\n rationale: (e) => `${e.displayName} provides SCA for ${lang} dependencies`,\n priority: 'critical',\n ctx,\n });\n collectCategoryRecs(recs, {\n names: langMap.secrets,\n category: 'secrets',\n rationale: () => 'Detects leaked credentials and API keys in source code',\n priority: 'critical',\n ctx,\n });\n}\n\n/** Try to add a single scanner if not already present. */\nfunction tryAddScanner(\n scannerName: string,\n category: string,\n rationale: string,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n if (recs.length >= ctx.maxScanners) return;\n if (ctx.categoryFilter && !ctx.categoryFilter.has(category)) return;\n if (isAlreadyUsed(scannerName, ctx.existing)) return;\n if (recs.some((r) => r.name === scannerName)) return;\n const entry = findScanner(scannerName, ctx.scanners);\n if (!entry) return;\n recs.push({\n name: scannerName,\n displayName: entry.displayName,\n category,\n license: entry.license,\n pricingModel: entry.pricingModel,\n rationale,\n priority: 'recommended',\n ciSnippet: generateCiSnippet(scannerName, ctx.ciProvider),\n });\n}\n\n// ============================================================================\n// Conflict Detection\n// ============================================================================\n\nfunction detectConflicts(\n recs: readonly ScannerRecommendation[],\n scanners: readonly ScannerEntry[]\n): readonly ConflictWarning[] {\n const warnings: ConflictWarning[] = [];\n const names = new Set(recs.map((r) => r.name));\n detectSuperseded(names, scanners, warnings);\n detectRedundant(recs, warnings);\n return warnings;\n}\n\nfunction detectSuperseded(\n names: ReadonlySet<string>,\n scanners: readonly ScannerEntry[],\n warnings: ConflictWarning[]\n): void {\n for (const scanner of scanners) {\n if (!names.has(scanner.name)) continue;\n if (!scanner.supersedes) continue;\n for (const old of scanner.supersedes) {\n if (names.has(old)) {\n warnings.push({\n scanners: [old, scanner.name],\n type: 'superseded',\n recommendation: `${scanner.displayName} supersedes ${old}. Remove ${old}.`,\n });\n }\n }\n }\n}\n\nfunction detectRedundant(\n recs: readonly ScannerRecommendation[],\n warnings: ConflictWarning[]\n): void {\n const catMap = new Map<string, string[]>();\n for (const rec of recs) {\n const arr = catMap.get(rec.category) ?? [];\n arr.push(rec.name);\n catMap.set(rec.category, arr);\n }\n for (const [cat, scanners] of catMap) {\n if (scanners.length > 2) {\n const count = String(scanners.length);\n warnings.push({\n scanners,\n type: 'redundant',\n recommendation: `${count} scanners for ${cat}. Consider keeping top 2.`,\n });\n }\n }\n}\n\n// ============================================================================\n// Coverage Analysis\n// ============================================================================\n\nconst ALL_CATEGORIES = ['sast', 'dast', 'sca', 'secrets', 'container', 'iac', 'image-currency'];\n\nfunction buildCoverage(\n recs: readonly ScannerRecommendation[],\n existing: readonly string[],\n scanners: readonly ScannerEntry[]\n): readonly CoverageAnalysis[] {\n return ALL_CATEGORIES.map((cat) => {\n const found = recs.filter((r) => r.category === cat).map((r) => r.name);\n const existingMatch = existing.some((t) =>\n scanners.some((s) => s.categories.includes(cat) && t.toLowerCase().includes(s.name))\n );\n return { category: cat, covered: found.length > 0 || existingMatch, scanners: found };\n });\n}\n\n// ============================================================================\n// Plan Assembly\n// ============================================================================\n\n/** Options for buildPlanFromAnalysis (allows optional fields for testability). */\ninterface BuildPlanOptions {\n readonly repo: string;\n readonly categories?: readonly string[] | undefined;\n readonly maxScanners?: number | undefined;\n}\n\n/** Generate a security scanning plan for a repository (fetches live data). */\nexport async function generateSecurityPlan(\n input: RepoSecurityPlanInput\n): Promise<RepoSecurityPlan> {\n const [analysis, data] = await Promise.all([\n analyzeGitHubRepo({ repo: input.repo, depth: 'deep' }),\n resolveScannerData(),\n ]);\n return buildPlanFromAnalysis(analysis, input, data);\n}\n\n/** Collect infrastructure-specific scanner recommendations. */\nfunction collectInfraRecs(\n analysis: RepoAnalysis,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n if (analysis.hasDockerfile) {\n tryAddScanner(\n 'grype',\n 'container',\n 'Dockerfile detected — scan container images for vulnerabilities',\n recs,\n ctx\n );\n tryAddScanner('grype-image', 'image-currency', buildImageCurrencyRationale(), recs, ctx);\n }\n if (analysis.hasHelmCharts) {\n tryAddScanner(\n 'checkov',\n 'iac',\n 'Helm charts detected — scan IaC for misconfigurations',\n recs,\n ctx\n );\n }\n}\n\n/**\n * Build the rationale string for periodic container base image CVE scanning.\n * Extracted to keep collectInfraRecs within the 50-line function limit.\n */\nfunction buildImageCurrencyRationale(): string {\n return (\n 'Dockerfile detected — periodically scan built images with ' +\n '`grype image --severity CRITICAL,HIGH` to detect CVEs introduced by stale base images. ' +\n 'Pin base images to specific version tags (e.g., node:22.4.0-alpine3.20) rather than ' +\n ':latest to get reproducible scans and predictable CVE surface area. ' +\n 'Alpine-based images typically have a smaller CVE surface than Debian/Ubuntu equivalents ' +\n 'due to musl libc and a minimal package set, but verify with grype before assuming.'\n );\n}\n\n/** Pure function: build plan from analysis + scanner data (testable). */\nexport function buildPlanFromAnalysis(\n analysis: RepoAnalysis,\n input: BuildPlanOptions,\n data?: ScannerData\n): RepoSecurityPlan {\n const resolved = data ?? FALLBACK_SCANNER_DATA;\n const ctx: RecContext = {\n existing: analysis.securityTooling,\n ciProvider: analysis.ciProvider,\n language: analysis.language,\n categoryFilter: input.categories ? new Set(input.categories) : null,\n maxScanners: input.maxScanners ?? 10,\n scanners: resolved.scanners,\n };\n\n const recs: ScannerRecommendation[] = [];\n const normalizedLang = analysis.language !== null ? normalizeLangName(analysis.language) : null;\n const langMap = normalizedLang !== null ? resolved.languageMap[normalizedLang] : undefined;\n if (langMap) collectLanguageRecs(langMap, recs, ctx);\n collectInfraRecs(analysis, recs, ctx);\n\n const conflicts = detectConflicts(recs, resolved.scanners);\n const coverage = buildCoverage(recs, analysis.securityTooling, resolved.scanners);\n const uncovered = coverage.filter((c) => !c.covered).map((c) => c.category);\n\n return {\n repo: analysis.name,\n language: analysis.language,\n framework: analysis.framework,\n ciProvider: analysis.ciProvider,\n existingTooling: analysis.securityTooling,\n recommendations: recs,\n conflicts,\n coverage,\n gapsSummary: [\n ...analysis.gaps,\n ...(uncovered.length > 0 ? [`Uncovered categories: ${uncovered.join(', ')}`] : []),\n ],\n };\n}\n"],"mappings":"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","names":["logger"]}
@@ -3,10 +3,10 @@ import {
3
3
  createCliCircuitBreakerIntegration,
4
4
  getCliCircuitBreakerSnapshot,
5
5
  getDefaultCliCircuitBreakerRegistry
6
- } from "./chunk-S2Z7CME3.js";
7
- import "./chunk-Y742U2YM.js";
8
- import "./chunk-GBHJ37V3.js";
9
- import "./chunk-OGHEKPLD.js";
6
+ } from "./chunk-W2R6UVZJ.js";
7
+ import "./chunk-3MBGQAO2.js";
8
+ import "./chunk-ITCKXMOH.js";
9
+ import "./chunk-MM223CVS.js";
10
10
  import "./chunk-ZPWHCABL.js";
11
11
  export {
12
12
  CliCircuitBreakerIntegration,
@@ -14,4 +14,4 @@ export {
14
14
  getCliCircuitBreakerSnapshot,
15
15
  getDefaultCliCircuitBreakerRegistry
16
16
  };
17
- //# sourceMappingURL=cli-circuit-breaker-5FYJDS5O.js.map
17
+ //# sourceMappingURL=cli-circuit-breaker-KRIR74SW.js.map