nexus-agents 2.56.0 → 2.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/{adaptive-memory-SUNFPY2W.js → adaptive-memory-TSZEJUJC.js} +3 -3
  2. package/dist/{chunk-4FVISCDB.js → chunk-6QU4DJYW.js} +2 -2
  3. package/dist/{chunk-C3FGEDD7.js → chunk-7EYQBG3W.js} +22 -406
  4. package/dist/chunk-7EYQBG3W.js.map +1 -0
  5. package/dist/{chunk-NYNBDP7M.js → chunk-7Y36JLES.js} +2 -2
  6. package/dist/{chunk-FWSTXEG2.js → chunk-AFSIP6JH.js} +99 -23
  7. package/dist/chunk-AFSIP6JH.js.map +1 -0
  8. package/dist/{chunk-VKZKC4SM.js → chunk-EDGG3RQE.js} +2 -2
  9. package/dist/{chunk-2SPRLBOS.js → chunk-EZXOJZYE.js} +2 -2
  10. package/dist/{chunk-KGMC6F5D.js → chunk-GJ5BJU7A.js} +67 -61
  11. package/dist/chunk-GJ5BJU7A.js.map +1 -0
  12. package/dist/{chunk-W5AJRK4U.js → chunk-GMOGKX4E.js} +2 -2
  13. package/dist/chunk-H43PABG4.js +402 -0
  14. package/dist/chunk-H43PABG4.js.map +1 -0
  15. package/dist/{chunk-VLVHPC72.js → chunk-JKDHWOQL.js} +6 -6
  16. package/dist/{chunk-EJLWDYK7.js → chunk-JLYJQ7OG.js} +6159 -5619
  17. package/dist/chunk-JLYJQ7OG.js.map +1 -0
  18. package/dist/{chunk-IOU7F5PH.js → chunk-K7EA5OV4.js} +2 -2
  19. package/dist/{chunk-BPMQRYGU.js → chunk-L3NHOUEX.js} +332 -80
  20. package/dist/chunk-L3NHOUEX.js.map +1 -0
  21. package/dist/{chunk-KRIFBGWZ.js → chunk-PTGBJFSD.js} +3 -3
  22. package/dist/{chunk-EKYT4NMD.js → chunk-QGZBCD2A.js} +24 -9
  23. package/dist/chunk-QGZBCD2A.js.map +1 -0
  24. package/dist/{chunk-UOUJZIKH.js → chunk-R2Y57PM3.js} +38 -15
  25. package/dist/chunk-R2Y57PM3.js.map +1 -0
  26. package/dist/{chunk-PCMWLXT4.js → chunk-TC46TRLR.js} +2 -2
  27. package/dist/{chunk-3IDJSFWT.js → chunk-TFEFN37P.js} +2 -2
  28. package/dist/{chunk-Q5TKQWOI.js → chunk-V5CGWMYL.js} +2 -2
  29. package/dist/{chunk-TCQNNY4J.js → chunk-XEMRMZUN.js} +4 -4
  30. package/dist/{chunk-RSUCXOQM.js → chunk-YQAOMDR2.js} +26 -53
  31. package/dist/chunk-YQAOMDR2.js.map +1 -0
  32. package/dist/{chunk-R66AWJJ7.js → chunk-YU4NABXM.js} +2 -2
  33. package/dist/{cli-circuit-breaker-7MCMHSDY.js → cli-circuit-breaker-SL73NWX2.js} +4 -4
  34. package/dist/cli.d.ts +3 -1
  35. package/dist/cli.js +428 -342
  36. package/dist/cli.js.map +1 -1
  37. package/dist/{composite-router-LXFOSMSE.js → composite-router-A7URDW4X.js} +2 -2
  38. package/dist/{consensus-vote-G6H532ME.js → consensus-vote-HDM6HA5Z.js} +9 -8
  39. package/dist/{dist-4LDAFGC5.js → dist-RLMRWMYO.js} +994 -779
  40. package/dist/dist-RLMRWMYO.js.map +1 -0
  41. package/dist/{doctor-deep-RKMOZSIS.js → doctor-deep-VN6KMUCG.js} +3 -3
  42. package/dist/{expert-bridge-KHHE42JP.js → expert-bridge-BHTUNALT.js} +3 -3
  43. package/dist/{factory-JVN47MFN.js → factory-6MT5VKI3.js} +4 -4
  44. package/dist/{factory-KRNR7XHD.js → factory-FA7WDPZW.js} +5 -5
  45. package/dist/index.d.ts +396 -61
  46. package/dist/index.js +58 -43
  47. package/dist/index.js.map +1 -1
  48. package/dist/{issue-triage-O3C7P66H.js → issue-triage-YYTE6KTC.js} +4 -4
  49. package/dist/{mcp-config-QRERKGR4.js → mcp-config-34XMRM64.js} +3 -3
  50. package/dist/{mobimem-FAOAXV3B.js → mobimem-QDBP37H7.js} +2 -2
  51. package/dist/model-registry.generated.json +17033 -0
  52. package/dist/registry-command-S46JJ2SX.js +580 -0
  53. package/dist/registry-command-S46JJ2SX.js.map +1 -0
  54. package/dist/{repo-security-plan-AZ5HMGFC.js → repo-security-plan-C3LLE3Z7.js} +3 -3
  55. package/dist/{research-helpers-synthesize-E6WQLQKP.js → research-helpers-synthesize-NVQIWLQL.js} +3 -3
  56. package/dist/{routing-memory-MXF45FXT.js → routing-memory-W3YWMLJM.js} +2 -2
  57. package/dist/{session-memory-HL5XDBIL.js → session-memory-DWF5Z2LC.js} +3 -3
  58. package/dist/{setup-command-AV4MODEL.js → setup-command-KSQEYBDA.js} +8 -7
  59. package/dist/{setup-config-PWK6WHMG.js → setup-config-JA5IX53Q.js} +3 -3
  60. package/dist/{setup-custom-api-XAWKRDWV.js → setup-custom-api-CSB26HWD.js} +7 -5
  61. package/dist/setup-custom-api-CSB26HWD.js.map +1 -0
  62. package/dist/{weather-report-XZ5CENHE.js → weather-report-YQSLX4MS.js} +2 -2
  63. package/package.json +5 -5
  64. package/dist/chunk-BPMQRYGU.js.map +0 -1
  65. package/dist/chunk-C3FGEDD7.js.map +0 -1
  66. package/dist/chunk-EJLWDYK7.js.map +0 -1
  67. package/dist/chunk-EKYT4NMD.js.map +0 -1
  68. package/dist/chunk-FWSTXEG2.js.map +0 -1
  69. package/dist/chunk-KGMC6F5D.js.map +0 -1
  70. package/dist/chunk-RSUCXOQM.js.map +0 -1
  71. package/dist/chunk-UOUJZIKH.js.map +0 -1
  72. package/dist/dist-4LDAFGC5.js.map +0 -1
  73. package/dist/setup-custom-api-XAWKRDWV.js.map +0 -1
  74. /package/dist/{adaptive-memory-SUNFPY2W.js.map → adaptive-memory-TSZEJUJC.js.map} +0 -0
  75. /package/dist/{chunk-4FVISCDB.js.map → chunk-6QU4DJYW.js.map} +0 -0
  76. /package/dist/{chunk-NYNBDP7M.js.map → chunk-7Y36JLES.js.map} +0 -0
  77. /package/dist/{chunk-VKZKC4SM.js.map → chunk-EDGG3RQE.js.map} +0 -0
  78. /package/dist/{chunk-2SPRLBOS.js.map → chunk-EZXOJZYE.js.map} +0 -0
  79. /package/dist/{chunk-W5AJRK4U.js.map → chunk-GMOGKX4E.js.map} +0 -0
  80. /package/dist/{chunk-VLVHPC72.js.map → chunk-JKDHWOQL.js.map} +0 -0
  81. /package/dist/{chunk-IOU7F5PH.js.map → chunk-K7EA5OV4.js.map} +0 -0
  82. /package/dist/{chunk-KRIFBGWZ.js.map → chunk-PTGBJFSD.js.map} +0 -0
  83. /package/dist/{chunk-PCMWLXT4.js.map → chunk-TC46TRLR.js.map} +0 -0
  84. /package/dist/{chunk-3IDJSFWT.js.map → chunk-TFEFN37P.js.map} +0 -0
  85. /package/dist/{chunk-Q5TKQWOI.js.map → chunk-V5CGWMYL.js.map} +0 -0
  86. /package/dist/{chunk-TCQNNY4J.js.map → chunk-XEMRMZUN.js.map} +0 -0
  87. /package/dist/{chunk-R66AWJJ7.js.map → chunk-YU4NABXM.js.map} +0 -0
  88. /package/dist/{cli-circuit-breaker-7MCMHSDY.js.map → cli-circuit-breaker-SL73NWX2.js.map} +0 -0
  89. /package/dist/{composite-router-LXFOSMSE.js.map → composite-router-A7URDW4X.js.map} +0 -0
  90. /package/dist/{consensus-vote-G6H532ME.js.map → consensus-vote-HDM6HA5Z.js.map} +0 -0
  91. /package/dist/{doctor-deep-RKMOZSIS.js.map → doctor-deep-VN6KMUCG.js.map} +0 -0
  92. /package/dist/{expert-bridge-KHHE42JP.js.map → expert-bridge-BHTUNALT.js.map} +0 -0
  93. /package/dist/{factory-JVN47MFN.js.map → factory-6MT5VKI3.js.map} +0 -0
  94. /package/dist/{factory-KRNR7XHD.js.map → factory-FA7WDPZW.js.map} +0 -0
  95. /package/dist/{issue-triage-O3C7P66H.js.map → issue-triage-YYTE6KTC.js.map} +0 -0
  96. /package/dist/{mcp-config-QRERKGR4.js.map → mcp-config-34XMRM64.js.map} +0 -0
  97. /package/dist/{mobimem-FAOAXV3B.js.map → mobimem-QDBP37H7.js.map} +0 -0
  98. /package/dist/{repo-security-plan-AZ5HMGFC.js.map → repo-security-plan-C3LLE3Z7.js.map} +0 -0
  99. /package/dist/{research-helpers-synthesize-E6WQLQKP.js.map → research-helpers-synthesize-NVQIWLQL.js.map} +0 -0
  100. /package/dist/{routing-memory-MXF45FXT.js.map → routing-memory-W3YWMLJM.js.map} +0 -0
  101. /package/dist/{session-memory-HL5XDBIL.js.map → session-memory-DWF5Z2LC.js.map} +0 -0
  102. /package/dist/{setup-command-AV4MODEL.js.map → setup-command-KSQEYBDA.js.map} +0 -0
  103. /package/dist/{setup-config-PWK6WHMG.js.map → setup-config-JA5IX53Q.js.map} +0 -0
  104. /package/dist/{weather-report-XZ5CENHE.js.map → weather-report-YQSLX4MS.js.map} +0 -0
@@ -3,7 +3,7 @@ import {
3
3
  err,
4
4
  getErrorMessage,
5
5
  ok
6
- } from "./chunk-UOUJZIKH.js";
6
+ } from "./chunk-R2Y57PM3.js";
7
7
 
8
8
  // src/scm/types.ts
9
9
  var ScmError = class extends Error {
@@ -243,4 +243,4 @@ export {
243
243
  ScmError,
244
244
  GitHubProvider
245
245
  };
246
- //# sourceMappingURL=chunk-IOU7F5PH.js.map
246
+ //# sourceMappingURL=chunk-K7EA5OV4.js.map
@@ -2,10 +2,13 @@ import {
2
2
  CUSTOM_API_BASE_URL_ENV,
3
3
  PROVIDER_ENV_KEYS,
4
4
  validateCustomApiBaseUrl
5
- } from "./chunk-R66AWJJ7.js";
5
+ } from "./chunk-YU4NABXM.js";
6
+ import {
7
+ CUSTOM_API_DEFAULT_MODEL
8
+ } from "./chunk-H43PABG4.js";
6
9
  import {
7
10
  SessionMemory
8
- } from "./chunk-NYNBDP7M.js";
11
+ } from "./chunk-7Y36JLES.js";
9
12
  import {
10
13
  AdaptiveMemoryBackend,
11
14
  HybridMemoryBackend,
@@ -14,7 +17,7 @@ import {
14
17
  getMemoryEntry,
15
18
  memoryExists,
16
19
  memoryRowToEntry
17
- } from "./chunk-3IDJSFWT.js";
20
+ } from "./chunk-TFEFN37P.js";
18
21
  import {
19
22
  stringifyValue,
20
23
  tokenizeFiltered
@@ -26,7 +29,7 @@ import {
26
29
  getAvailableClis,
27
30
  isCliAvailable,
28
31
  withTimeout
29
- } from "./chunk-RSUCXOQM.js";
32
+ } from "./chunk-YQAOMDR2.js";
30
33
  import {
31
34
  AgentError,
32
35
  CACHE_TIMEOUTS,
@@ -69,10 +72,11 @@ import {
69
72
  ok,
70
73
  recordRateLimitEvent,
71
74
  registerPersistentOutcomeStoreFactory,
75
+ resolveCliAlias,
72
76
  resolveVoteTimeout,
73
77
  toRateLimitError,
74
78
  validateTimeout
75
- } from "./chunk-UOUJZIKH.js";
79
+ } from "./chunk-R2Y57PM3.js";
76
80
  import {
77
81
  OUTCOMES_FILE,
78
82
  ensureLearningDir
@@ -866,6 +870,38 @@ function isToolDenied(toolName) {
866
870
  return UNBYPASSABLE_TOOL_NAMES.includes(toolName);
867
871
  }
868
872
 
873
+ // src/security/access-constraint-deriver/tool-risk.ts
874
+ var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
875
+ // Discovery / listing
876
+ "list_experts",
877
+ "list_workflows",
878
+ // Research reads
879
+ "research_query",
880
+ "research_analyze",
881
+ "research_catalog_review",
882
+ "research_synthesize",
883
+ // Memory reads
884
+ "memory_query",
885
+ "memory_stats",
886
+ // Observability
887
+ "weather_report",
888
+ "query_trace",
889
+ "query_task_state",
890
+ // Codebase intelligence (read-only over local files)
891
+ "search_codebase",
892
+ "extract_symbols",
893
+ // Repo analysis (read-only)
894
+ "repo_analyze",
895
+ "repo_security_plan",
896
+ // Routing recommendation (no side effects — returns recommendation)
897
+ "delegate_to_model",
898
+ // Registry import (returns a draft template — does not write)
899
+ "registry_import"
900
+ ]);
901
+ function isRiskyTool(toolName) {
902
+ return !READ_ONLY_TOOLS2.has(toolName);
903
+ }
904
+
869
905
  // src/security/access-constraint-deriver/enforcer.ts
870
906
  function checkAccess(toolName, policy, args) {
871
907
  if (isToolDenied(toolName)) {
@@ -884,12 +920,28 @@ function checkAccess(toolName, policy, args) {
884
920
  }
885
921
  if (policy.allowedTools === "*") return { decision: "allow" };
886
922
  if (policy.allowedTools.includes(toolName)) return { decision: "allow" };
887
- if (policy.mode === "audit") {
923
+ return decideOnViolation(toolName, policy.mode);
924
+ }
925
+ function decideOnViolation(toolName, mode) {
926
+ if (mode === "audit") {
888
927
  return {
889
928
  decision: "log-and-allow",
890
929
  warning: `tool "${toolName}" not in derived policy (audit mode)`
891
930
  };
892
931
  }
932
+ if (mode === "confirm_risky") {
933
+ if (!isRiskyTool(toolName)) {
934
+ return {
935
+ decision: "log-and-allow",
936
+ warning: `tool "${toolName}" not in derived policy (confirm_risky mode, read-only \u2014 would have required human approval, allowed because read-only)`
937
+ };
938
+ }
939
+ return {
940
+ decision: "deny",
941
+ reason: `tool "${toolName}" not in derived policy (confirm_risky mode, risky \u2014 would have required human approval; denied for now, add to allowedTools or run in audit mode to allow)`,
942
+ matchedRule: "allowedTools:confirm_risky"
943
+ };
944
+ }
893
945
  return {
894
946
  decision: "deny",
895
947
  reason: `tool "${toolName}" not in derived policy`,
@@ -1458,6 +1510,16 @@ function emitToolAudit(auditLogger, toolName, ctx, result, durationMs) {
1458
1510
  durationMs
1459
1511
  });
1460
1512
  }
1513
+ function emitToolAuditException(auditLogger, toolName, ctx, durationMs) {
1514
+ const actor = actorFromContext(ctx);
1515
+ auditLogger.logToolInvocation({
1516
+ toolName,
1517
+ outcome: "error",
1518
+ actor,
1519
+ requestId: ctx.requestId,
1520
+ durationMs
1521
+ });
1522
+ }
1461
1523
  function emitPolicyAudit(auditLogger, toolName, ctx, reason) {
1462
1524
  const actor = actorFromContext(ctx);
1463
1525
  auditLogger.logPolicyDecision({
@@ -1551,31 +1613,42 @@ function createSecureHandler(handler, config) {
1551
1613
  requestLogger
1552
1614
  );
1553
1615
  if (preCheckError) return preCheckError;
1554
- const execStartTime = getTimeProvider().now();
1555
- try {
1556
- const result = await executeHandler(
1557
- handler,
1558
- sanitizedArgs,
1559
- { requestContext, logger: requestLogger },
1560
- requestLogger
1616
+ return executeAndAudit(handler, sanitizedArgs, requestContext, requestLogger, config);
1617
+ };
1618
+ }
1619
+ async function executeAndAudit(handler, sanitizedArgs, requestContext, requestLogger, config) {
1620
+ const execStartTime = getTimeProvider().now();
1621
+ try {
1622
+ const result = await executeHandler(
1623
+ handler,
1624
+ sanitizedArgs,
1625
+ { requestContext, logger: requestLogger },
1626
+ requestLogger
1627
+ );
1628
+ sanitizeToolResult(result, requestLogger);
1629
+ if (config.auditLogger) {
1630
+ emitToolAudit(
1631
+ config.auditLogger,
1632
+ config.toolName,
1633
+ requestContext,
1634
+ result,
1635
+ getTimeProvider().now() - execStartTime
1561
1636
  );
1562
- sanitizeToolResult(result, requestLogger);
1563
- if (config.auditLogger) {
1564
- emitToolAudit(
1565
- config.auditLogger,
1566
- config.toolName,
1567
- requestContext,
1568
- result,
1569
- getTimeProvider().now() - execStartTime
1570
- );
1571
- }
1572
- return result;
1573
- } catch (error) {
1574
- const message = error instanceof Error ? error.message : "Unknown error";
1575
- requestLogger.error("Tool execution failed", error instanceof Error ? error : void 0);
1576
- return internalError(message, requestContext.requestId);
1577
1637
  }
1578
- };
1638
+ return result;
1639
+ } catch (error) {
1640
+ const message = error instanceof Error ? error.message : "Unknown error";
1641
+ requestLogger.error("Tool execution failed", error instanceof Error ? error : void 0);
1642
+ if (config.auditLogger) {
1643
+ emitToolAuditException(
1644
+ config.auditLogger,
1645
+ config.toolName,
1646
+ requestContext,
1647
+ getTimeProvider().now() - execStartTime
1648
+ );
1649
+ }
1650
+ return internalError(message, requestContext.requestId);
1651
+ }
1579
1652
  }
1580
1653
 
1581
1654
  // src/mcp/tools/tool-result.ts
@@ -1609,7 +1682,8 @@ var VOTER_ROLES = {
1609
1682
  devex: "Developer Experience - evaluates usability, documentation, and developer workflow",
1610
1683
  ai_ml: "AI/ML Engineer - evaluates AI/ML aspects, model selection, and learning capabilities",
1611
1684
  pm: "Product Manager - evaluates business value, user impact, and resource allocation",
1612
- catfish: "Contrarian Analyst - deliberately challenges proposals to prevent agreement bias (arXiv:2505.21503)"
1685
+ catfish: "Contrarian Analyst - deliberately challenges proposals to prevent agreement bias (arXiv:2505.21503)",
1686
+ scope_steward: "Scope Steward - asks whether to build at all; checks existing tools, biases toward kill-the-feature (#2185)"
1613
1687
  };
1614
1688
 
1615
1689
  // src/cli-adapters/cli-to-model-adapter.ts
@@ -2406,15 +2480,15 @@ async function collectStream(stream, options = {}) {
2406
2480
 
2407
2481
  // src/adapters/claude-adapter-types.ts
2408
2482
  var CLAUDE_MODELS = {
2409
- OPUS_4: "claude-opus-4-20250514",
2410
- SONNET_4: "claude-sonnet-4-20250514",
2411
- HAIKU_4: "claude-haiku-4-5-20251001"
2483
+ OPUS_4: getCliModelName("claude-opus"),
2484
+ SONNET_4: getCliModelName("claude-sonnet"),
2485
+ HAIKU_4: getCliModelName("claude-haiku")
2412
2486
  };
2413
2487
  var CLAUDE_MODEL_ALIASES = {
2414
2488
  "claude-opus-4": CLAUDE_MODELS.OPUS_4,
2415
2489
  "claude-sonnet-4": CLAUDE_MODELS.SONNET_4,
2416
2490
  "claude-haiku-4": CLAUDE_MODELS.HAIKU_4,
2417
- // Legacy alias
2491
+ // Legacy alias — pre-4.x users routed to the current haiku.
2418
2492
  "claude-haiku-3": CLAUDE_MODELS.HAIKU_4
2419
2493
  };
2420
2494
  var DEFAULT_MAX_TOKENS = 4096;
@@ -2493,7 +2567,9 @@ function mapTool(tool) {
2493
2567
  };
2494
2568
  }
2495
2569
  function resolveModelId(modelId) {
2496
- return CLAUDE_MODEL_ALIASES[modelId] ?? modelId;
2570
+ const registryId = resolveCliAlias(modelId);
2571
+ if (registryId !== void 0) return getCliModelName(registryId);
2572
+ return modelId;
2497
2573
  }
2498
2574
  function getModelCapabilities(modelId) {
2499
2575
  const capabilities = [
@@ -3142,7 +3218,7 @@ function tryCustomOpenAiAdapter(logger11) {
3142
3218
  if (customKey === void 0 || customBaseUrl === void 0 || customBaseUrl === "") {
3143
3219
  return null;
3144
3220
  }
3145
- const customModelId = process.env["NEXUS_CUSTOM_MODEL"] ?? "gpt-4o";
3221
+ const customModelId = process.env["NEXUS_CUSTOM_MODEL"] ?? CUSTOM_API_DEFAULT_MODEL;
3146
3222
  logger11.info("Using custom-openai SDK adapter", {
3147
3223
  model: customModelId,
3148
3224
  baseUrl: customBaseUrl
@@ -3908,6 +3984,10 @@ var UnifiedAdapterRegistry = class {
3908
3984
  // --------------------------------------------------------------------------
3909
3985
  // Public API
3910
3986
  // --------------------------------------------------------------------------
3987
+ /** Logger used by this registry. Exposed so singleton helpers can warn. */
3988
+ getLogger() {
3989
+ return this.logger;
3990
+ }
3911
3991
  /**
3912
3992
  * Get adapter for a task category. Uses pre-computed routing.
3913
3993
  * Falls back to default adapter if category unknown.
@@ -3959,9 +4039,11 @@ var UnifiedAdapterRegistry = class {
3959
4039
  * Falls back to default adapter if model not recognized.
3960
4040
  */
3961
4041
  getAdapterForModel(modelPreference) {
3962
- const model = DEFAULT_MODEL_CAPABILITIES.models.find(
3963
- (m) => m.id === modelPreference || m.cliAlias === modelPreference || m.cliModelName === modelPreference || modelPreference.startsWith(m.id)
4042
+ const exact = DEFAULT_MODEL_CAPABILITIES.models.find(
4043
+ (m) => m.id === modelPreference || m.cliAlias === modelPreference || m.cliModelName === modelPreference
3964
4044
  );
4045
+ const prefix = exact ?? [...DEFAULT_MODEL_CAPABILITIES.models].filter((m) => modelPreference.startsWith(m.id)).sort((a, b) => b.id.length - a.id.length)[0];
4046
+ const model = prefix;
3965
4047
  if (model !== void 0) {
3966
4048
  this.logger.debug("Model resolved to CLI", {
3967
4049
  model: modelPreference,
@@ -4064,12 +4146,44 @@ function createUnifiedRegistry(config) {
4064
4146
  return new UnifiedAdapterRegistry(config);
4065
4147
  }
4066
4148
  function getGlobalRegistry(config) {
4067
- globalRegistry ??= new UnifiedAdapterRegistry(config);
4149
+ if (globalRegistry === void 0) {
4150
+ globalRegistry = new UnifiedAdapterRegistry(config);
4151
+ return globalRegistry;
4152
+ }
4153
+ if (config !== void 0 && Object.keys(config).length > 0) {
4154
+ globalRegistry.getLogger().warn(
4155
+ "UnifiedAdapterRegistry singleton already initialized; provided config ignored. Call resetGlobalRegistry() first if reconfiguration is intentional.",
4156
+ { providedKeys: Object.keys(config) }
4157
+ );
4158
+ }
4068
4159
  return globalRegistry;
4069
4160
  }
4070
4161
 
4071
4162
  // src/cli/voter-prompts.ts
4072
4163
  var DEFAULT_PROJECT = "nexus-agents";
4164
+ function prReviewModeAddendum() {
4165
+ return `
4166
+ PR-review mode \u2014 if you are reviewing a code diff (not a proposal) AND you have at least one concrete defect that justifies blocking the merge, populate the OPTIONAL TOP-LEVEL "findings" field on your JSON response. NOT inside reasoning \u2014 top-level. The schema:
4167
+
4168
+ "findings": [
4169
+ {
4170
+ "summary": "One-line summary",
4171
+ "location": "path/file.ext:LINE",
4172
+ "severity": "critical" | "high" | "medium" | "low",
4173
+ "gate": {
4174
+ "reread_cited_line": "passed",
4175
+ "traced_call_path": "passed",
4176
+ "named_assertion": "Concrete failing assertion \u2014 what test would fail and how. Substantive, not 'passed'.",
4177
+ "ruled_out_language_non_issue": "passed"
4178
+ },
4179
+ "claim": "What is wrong and why it justifies blocking."
4180
+ }
4181
+ ]
4182
+
4183
+ A finding only triggers strict request_changes if all 4 gate fields = "passed" AND named_assertion is substantive (>10 chars naming a concrete failure, not just "passed"). Findings missing any of those surface as informational only \u2014 they do not block on their own. The 2026-04-25 audit (#2225) found a 100% false-positive rate when this gate wasn't enforced. If you're approving the diff, OMIT the findings field entirely. If reviewing a non-diff proposal, ignore this section.
4184
+
4185
+ History note: an earlier prompt asked for YAML inside reasoning; that format was lossy across JSON serialization (#2245). Use the top-level JSON array above.`;
4186
+ }
4073
4187
  function voterFooter() {
4074
4188
  return `
4075
4189
  Workflow-test assessment (include in your reasoning):
@@ -4077,7 +4191,8 @@ Workflow-test assessment (include in your reasoning):
4077
4191
  - Workflow integration: Does this fit existing CI/build/test pipelines?
4078
4192
  - Incremental verifiability: Can progress be measured at each step?
4079
4193
 
4080
- When rejecting, classify your reasons using categories: YAGNI, DRY_VIOLATION, OVER_ENGINEERING, SCOPE_CREEP, SECURITY_RISK, MISALIGNED, INSUFFICIENT_EVIDENCE.`;
4194
+ When rejecting, classify your reasons using categories: YAGNI, DRY_VIOLATION, OVER_ENGINEERING, SCOPE_CREEP, SECURITY_RISK, MISALIGNED, INSUFFICIENT_EVIDENCE.
4195
+ ${prReviewModeAddendum()}`;
4081
4196
  }
4082
4197
  function architectPrompt(project) {
4083
4198
  return `You are a Software Architect voting on technical proposals for the ${project} project.
@@ -4170,7 +4285,67 @@ When rejecting, you MUST classify your reasons using categories: YAGNI, DRY_VIOL
4170
4285
  IMPORTANT: Your job is to find legitimate concerns, not to reject everything.
4171
4286
  If after genuine scrutiny you find no significant issues, you MAY approve.
4172
4287
  But your default posture is skeptical \u2014 look for what others might miss.
4173
- High-confidence rejections with specific reasoning are your most valuable output.`;
4288
+ High-confidence rejections with specific reasoning are your most valuable output.
4289
+ ${prReviewModeAddendum()}`;
4290
+ }
4291
+ function scopeStewardPrompt(project) {
4292
+ return `You are a Scope Steward voting on proposals for the ${project} project.
4293
+
4294
+ Your job is to gate against build-when-buy-would-do and feature sprawl.
4295
+ The originating case (2026-04-24): a 6-role panel approved building a USB flasher
4296
+ CLI without anyone flagging that Rufus already solves the problem better, for the
4297
+ same audience, with 100M+ installs of battle-tested code. This role exists to
4298
+ catch that class of mistake.
4299
+
4300
+ Your evaluation criteria \u2014 work through these mandatory checks in your reasoning:
4301
+
4302
+ 1. **Existing-tool check.** Search your knowledge for tools, libraries, or
4303
+ services that already solve the stated problem. Name them concretely
4304
+ (not "there might be alternatives" \u2014 actual names: Rufus, ripgrep,
4305
+ esbuild, etc.). If you can't name an alternative, say so explicitly.
4306
+
4307
+ 2. **Build-vs-buy math.** For each existing tool you named: what would we
4308
+ LOSE by adopting it (license, dependency surface, integration cost)?
4309
+ What would we GAIN by building our own (tighter integration, no extra
4310
+ binary, etc.)? Default lean: BUY. Building is justified only when the
4311
+ loss column is concrete and the gain column is load-bearing.
4312
+
4313
+ 3. **Mission alignment.** Does this proposal serve the project's stated
4314
+ mission, or is it scope drift? If drift, name the drift specifically.
4315
+
4316
+ 4. **Kill-the-feature option.** For every proposal, explicitly evaluate
4317
+ "what if we just didn't do this?" as a ranked option. Many proposals
4318
+ don't need to be built. Make the no-build case before the build case.
4319
+
4320
+ 5. **Sprawl audit.** Check whether similar functionality already exists
4321
+ in the codebase. If it does, recommend extending \u2014 not forking. The
4322
+ anti-sprawl policy in CLAUDE.md is specifically the rule this role
4323
+ enforces.
4324
+
4325
+ Default bias: REJECT proposals where an existing tool fits, even if our
4326
+ own implementation would be marginally nicer. Only approve when the
4327
+ existing-tool check fails AND the kill-the-feature option is worse AND
4328
+ mission alignment is clear AND no comparable in-codebase functionality
4329
+ exists.
4330
+
4331
+ Few-shot example of a textbook rejection:
4332
+ > Proposal: "Add an aegis-boot subcommand to flash bootable USB sticks."
4333
+ > Steward response: "REJECT (DON'T-BUILD). Rufus has solved this for the
4334
+ > same audience for 10+ years with 100M+ installs and battle-tested code.
4335
+ > Adopting Rufus loses nothing material; building our own loses
4336
+ > maintenance bandwidth indefinitely. Mission alignment: aegis-boot's
4337
+ > mission is verifiable boot, not USB tooling. Kill option clearly wins.
4338
+ > No prior in-codebase functionality. Recommend: point users at Rufus in
4339
+ > the docs and stop here."
4340
+
4341
+ ${voterFooter()}
4342
+
4343
+ When rejecting, you MUST classify reasons (YAGNI, DRY_VIOLATION,
4344
+ OVER_ENGINEERING, SCOPE_CREEP, MISALIGNED). The steward's most common
4345
+ categories are SCOPE_CREEP, YAGNI, and OVER_ENGINEERING.
4346
+
4347
+ You CAN approve. But your default posture is: "this should not be built;
4348
+ prove me wrong with the build-vs-buy math."`;
4174
4349
  }
4175
4350
  function getVoterPrompts(project = DEFAULT_PROJECT) {
4176
4351
  return {
@@ -4179,7 +4354,8 @@ function getVoterPrompts(project = DEFAULT_PROJECT) {
4179
4354
  devex: devexPrompt(project),
4180
4355
  ai_ml: aiMlPrompt(project),
4181
4356
  pm: pmPrompt(project),
4182
- catfish: catfishPrompt(project)
4357
+ catfish: catfishPrompt(project),
4358
+ scope_steward: scopeStewardPrompt(project)
4183
4359
  };
4184
4360
  }
4185
4361
  var VOTER_SYSTEM_PROMPTS = getVoterPrompts();
@@ -4189,7 +4365,8 @@ var SIMULATED_VOTE_REASONING = {
4189
4365
  devex: "Assessed developer experience and workflow impact.",
4190
4366
  ai_ml: "Analyzed AI/ML capabilities and learning potential.",
4191
4367
  pm: "Evaluated business value and resource requirements.",
4192
- catfish: "Challenged proposal assumptions and identified potential risks."
4368
+ catfish: "Challenged proposal assumptions and identified potential risks.",
4369
+ scope_steward: "Checked existing tools, build-vs-buy math, kill-the-feature option; bias toward not shipping."
4193
4370
  };
4194
4371
 
4195
4372
  // src/cli/voter-response.ts
@@ -4203,6 +4380,18 @@ var SyntheticVoteError = class extends Error {
4203
4380
  this.name = "SyntheticVoteError";
4204
4381
  }
4205
4382
  };
4383
+ var RawFindingSchema = z2.object({
4384
+ summary: z2.string().min(1).max(500).describe("One-line summary of the issue"),
4385
+ location: z2.string().min(1).max(200).describe("path/file.ext:line"),
4386
+ severity: z2.enum(["critical", "high", "medium", "low"]).default("medium"),
4387
+ gate: z2.object({
4388
+ reread_cited_line: z2.enum(["passed", "failed", "skipped"]).default("skipped"),
4389
+ traced_call_path: z2.enum(["passed", "failed", "skipped"]).default("skipped"),
4390
+ named_assertion: z2.string().default("").describe("Concrete failing assertion \u2014 substantive, not a rubber-stamp word"),
4391
+ ruled_out_language_non_issue: z2.enum(["passed", "failed", "skipped"]).default("skipped")
4392
+ }),
4393
+ claim: z2.string().min(1).max(2e3).describe("What is wrong and why it justifies blocking")
4394
+ });
4206
4395
  var VoteResponseSchema = z2.object({
4207
4396
  decision: z2.enum(["approve", "reject", "abstain"]).describe("Your vote decision"),
4208
4397
  reasoning: z2.string().min(10).max(4e3).describe("Explanation for your vote (10-4000 chars)"),
@@ -4219,8 +4408,48 @@ var VoteResponseSchema = z2.object({
4219
4408
  "MISALIGNED",
4220
4409
  "INSUFFICIENT_EVIDENCE"
4221
4410
  ])
4222
- ).optional().describe("Rejection reason categories when decision is reject")
4411
+ ).optional().describe("Rejection reason categories when decision is reject"),
4412
+ /** Top-level structured findings for PR-review mode (#2245 v4 follow-up).
4413
+ * Replaces the YAML-in-reasoning encoding that proved lossy. */
4414
+ findings: z2.array(RawFindingSchema).optional().describe("Structured findings (PR review only)")
4223
4415
  });
4416
+ var VOTE_PROMPT_EXAMPLES = `Example approve response:
4417
+ {
4418
+ "decision": "approve",
4419
+ "reasoning": "The proposal aligns with architectural patterns. Testability: high \u2014 unit tests can verify each component. Workflow integration: fits existing CI pipeline.",
4420
+ "confidence": 0.85,
4421
+ "conditions": ["Add unit tests before merge"]
4422
+ }
4423
+
4424
+ Example reject response:
4425
+ {
4426
+ "decision": "reject",
4427
+ "reasoning": "This adds speculative abstractions for hypothetical future needs. Testability: unclear \u2014 no concrete test plan provided.",
4428
+ "confidence": 0.80,
4429
+ "rejectionCategories": ["YAGNI", "OVER_ENGINEERING"]
4430
+ }
4431
+
4432
+ Example PR-review request_changes response with structured findings:
4433
+ {
4434
+ "decision": "reject",
4435
+ "reasoning": "Off-by-one in clampPageSize and missing null guard on response.timing \u2014 both visible in the diff.",
4436
+ "confidence": 0.9,
4437
+ "rejectionCategories": ["INSUFFICIENT_EVIDENCE"],
4438
+ "findings": [
4439
+ {
4440
+ "summary": "Off-by-one in clampPageSize",
4441
+ "location": "packages/nexus-agents/src/api/pagination.ts:18",
4442
+ "severity": "high",
4443
+ "gate": {
4444
+ "reread_cited_line": "passed",
4445
+ "traced_call_path": "passed",
4446
+ "named_assertion": "Test would assert clampPageSize(50, 100) === 50; this returns 49.",
4447
+ "ruled_out_language_non_issue": "passed"
4448
+ },
4449
+ "claim": "Function name says 'clamp to range' but returns requested-1 in the in-range path."
4450
+ }
4451
+ ]
4452
+ }`;
4224
4453
  function buildVotePrompt(proposal) {
4225
4454
  return `Evaluate the following proposal and provide your vote.
4226
4455
 
@@ -4238,22 +4467,9 @@ Respond with a JSON object containing:
4238
4467
  - confidence: Number between 0 and 1
4239
4468
  - conditions: Optional array of conditions for approval
4240
4469
  - rejectionCategories: Required when rejecting. Array of categories from: YAGNI, DRY_VIOLATION, OVER_ENGINEERING, SCOPE_CREEP, SECURITY_RISK, MISALIGNED, INSUFFICIENT_EVIDENCE
4470
+ - findings: PR-REVIEW MODE ONLY. Optional top-level array of structured findings \u2014 see "PR-review mode" in the system prompt. OMIT this field entirely when reviewing a non-diff proposal or when approving a diff.
4241
4471
 
4242
- Example approve response:
4243
- {
4244
- "decision": "approve",
4245
- "reasoning": "The proposal aligns with architectural patterns. Testability: high \u2014 unit tests can verify each component. Workflow integration: fits existing CI pipeline.",
4246
- "confidence": 0.85,
4247
- "conditions": ["Add unit tests before merge"]
4248
- }
4249
-
4250
- Example reject response:
4251
- {
4252
- "decision": "reject",
4253
- "reasoning": "This adds speculative abstractions for hypothetical future needs. Testability: unclear \u2014 no concrete test plan provided.",
4254
- "confidence": 0.80,
4255
- "rejectionCategories": ["YAGNI", "OVER_ENGINEERING"]
4256
- }`;
4472
+ ${VOTE_PROMPT_EXAMPLES}`;
4257
4473
  }
4258
4474
  function extractJsonFromResponse(text) {
4259
4475
  const codeBlockMatch = /```(?:json)?\s*([\s\S]*?)```/i.exec(text);
@@ -4286,6 +4502,17 @@ function createFallbackVote(output, _role, reason) {
4286
4502
  // Mark as synthetic
4287
4503
  };
4288
4504
  }
4505
+ function buildParsedVote(data) {
4506
+ return {
4507
+ decision: data.decision,
4508
+ reasoning: data.reasoning,
4509
+ confidence: data.confidence,
4510
+ ...data.conditions !== void 0 ? { conditions: data.conditions } : {},
4511
+ ...data.rejectionCategories !== void 0 ? { rejectionCategories: data.rejectionCategories } : {},
4512
+ ...data.findings !== void 0 ? { findings: data.findings } : {},
4513
+ source: "parsed"
4514
+ };
4515
+ }
4289
4516
  function parseVoteResponse(output, role, options) {
4290
4517
  const allowSyntheticVote = options?.allowSyntheticVote ?? false;
4291
4518
  try {
@@ -4293,15 +4520,7 @@ function parseVoteResponse(output, role, options) {
4293
4520
  const parsed = JSON.parse(jsonStr);
4294
4521
  const validated = VoteResponseSchema.safeParse(parsed);
4295
4522
  if (validated.success) {
4296
- return {
4297
- decision: validated.data.decision,
4298
- reasoning: validated.data.reasoning,
4299
- confidence: validated.data.confidence,
4300
- ...validated.data.conditions !== void 0 ? { conditions: validated.data.conditions } : {},
4301
- ...validated.data.rejectionCategories !== void 0 ? { rejectionCategories: validated.data.rejectionCategories } : {},
4302
- source: "parsed"
4303
- // Real vote from LLM
4304
- };
4523
+ return buildParsedVote(validated.data);
4305
4524
  }
4306
4525
  const reason = `Validation failed: ${validated.error.issues.map((e) => e.message).join(", ")}`;
4307
4526
  if (!allowSyntheticVote) {
@@ -4370,8 +4589,10 @@ var ROLE_VOTE_DISTRIBUTIONS = {
4370
4589
  // Technical focus
4371
4590
  pm: [55, 25, 20],
4372
4591
  // Business focus - generally supportive
4373
- catfish: [20, 65, 15]
4592
+ catfish: [20, 65, 15],
4374
4593
  // Deliberately contrarian - challenges proposals (arXiv:2505.21503)
4594
+ scope_steward: [25, 60, 15]
4595
+ // Default-bias toward not shipping (#2185)
4375
4596
  };
4376
4597
  function selectWeightedDecision(weights) {
4377
4598
  const random = getRandomProvider();
@@ -4422,7 +4643,12 @@ async function executeSingleVoteAttempt(role, proposal, adapter, timeoutMs) {
4422
4643
  { role: "system", content: VOTER_SYSTEM_PROMPTS[role] },
4423
4644
  { role: "user", content: buildVotePrompt(proposal) }
4424
4645
  ],
4425
- maxTokens: 500,
4646
+ // 500 was correct for short proposal-style votes but caused mid-string
4647
+ // truncation ("Unterminated string in JSON at position N") in #2241 v3
4648
+ // when voters review code diffs — the JSON envelope + reasoning + YAML
4649
+ // findings block routinely exceed 500 tokens. Bumped to 2000 (#2245);
4650
+ // refine per use case if needed.
4651
+ maxTokens: 2e3,
4426
4652
  temperature: 0.3
4427
4653
  // Low temperature for consistent evaluations
4428
4654
  };
@@ -4711,6 +4937,18 @@ var RejectionCategorySchema = z3.enum([
4711
4937
  "INSUFFICIENT_EVIDENCE"
4712
4938
  ]);
4713
4939
  var REJECTION_CATEGORIES = RejectionCategorySchema.options;
4940
+ var FindingShapeSchema = z3.object({
4941
+ summary: z3.string().min(1).max(500),
4942
+ location: z3.string().min(1).max(200),
4943
+ severity: z3.enum(["critical", "high", "medium", "low"]).default("medium"),
4944
+ gate: z3.object({
4945
+ reread_cited_line: z3.enum(["passed", "failed", "skipped"]).default("skipped"),
4946
+ traced_call_path: z3.enum(["passed", "failed", "skipped"]).default("skipped"),
4947
+ named_assertion: z3.string().default(""),
4948
+ ruled_out_language_non_issue: z3.enum(["passed", "failed", "skipped"]).default("skipped")
4949
+ }),
4950
+ claim: z3.string().min(1).max(2e3)
4951
+ });
4714
4952
  var VoteSchema = z3.object({
4715
4953
  decision: VoteDecisionSchema,
4716
4954
  reasoning: z3.string().min(1).describe("Explanation for the vote"),
@@ -4718,6 +4956,9 @@ var VoteSchema = z3.object({
4718
4956
  conditions: z3.array(z3.string()).optional().describe("Conditions for approval"),
4719
4957
  /** Structured rejection categories for reject→refine→re-vote loops (Issue #1213). */
4720
4958
  rejectionCategories: z3.array(RejectionCategorySchema).optional().describe("Rejection reason categories when decision is reject"),
4959
+ /** Pre-verified PR-review findings (#2245 v4 follow-up). Optional;
4960
+ * populated only when the voter emits the structured top-level array. */
4961
+ findings: z3.array(FindingShapeSchema).optional().describe("PR-review findings (pre-verified)"),
4721
4962
  timestamp: z3.iso.datetime().optional()
4722
4963
  });
4723
4964
  var ProposalSchema = z3.object({
@@ -5940,11 +6181,21 @@ var ConsensusEngine = class {
5940
6181
  ambiguityBand: this.quorumConfig.ambiguityBand
5941
6182
  });
5942
6183
  if (!ambiguous) return false;
5943
- const newVoters = await this.voterExpansionCallback(
5944
- proposalId,
5945
- required.length,
5946
- this.quorumConfig.votersPerExpansion
5947
- );
6184
+ let newVoters;
6185
+ try {
6186
+ newVoters = await this.voterExpansionCallback(
6187
+ proposalId,
6188
+ required.length,
6189
+ this.quorumConfig.votersPerExpansion
6190
+ );
6191
+ } catch (err2) {
6192
+ const error = err2 instanceof Error ? err2 : new Error(String(err2));
6193
+ this.logger.warn("Incremental quorum: expansion callback threw; closing as-is", {
6194
+ proposalId,
6195
+ errorMessage: error.message
6196
+ });
6197
+ return false;
6198
+ }
5948
6199
  if (newVoters.length === 0) {
5949
6200
  this.logger.info("Incremental quorum: no additional voters available", { proposalId });
5950
6201
  return false;
@@ -12478,7 +12729,7 @@ function strategyToAlgorithm(strategy) {
12478
12729
  return strategy;
12479
12730
  }
12480
12731
  function getVoterRoles(quickMode) {
12481
- return quickMode ? ["architect", "security", "pm"] : ["architect", "security", "devex", "ai_ml", "pm", "catfish"];
12732
+ return quickMode ? ["architect", "security", "scope_steward"] : ["architect", "security", "devex", "ai_ml", "pm", "catfish", "scope_steward"];
12482
12733
  }
12483
12734
  function createEmptyConsensusResult(proposal, algorithm) {
12484
12735
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -12604,7 +12855,7 @@ async function processVotesWithCascade(votes, opts) {
12604
12855
  var CONTRARIAN_ESCALATION_THRESHOLD = 0.8;
12605
12856
  async function runContrarianCheck(proposal, log) {
12606
12857
  try {
12607
- const { executeExpert } = await import("./expert-bridge-KHHE42JP.js");
12858
+ const { executeExpert } = await import("./expert-bridge-BHTUNALT.js");
12608
12859
  const prompt = [
12609
12860
  "You are a contrarian analyst. Your job is to find reasons this proposal should be REJECTED.",
12610
12861
  "Look for: YAGNI (not needed), MISALIGNED (wrong tech/architecture), SECURITY_RISK, SCOPE_CREEP.",
@@ -12812,10 +13063,10 @@ function registerConsensusVoteTool(server, deps) {
12812
13063
  strategy: VotingStrategySchema.optional().describe(
12813
13064
  "Voting strategy: simple_majority (default), supermajority, unanimous, proof_of_learning, or higher_order"
12814
13065
  ),
12815
- quickMode: z19.boolean().optional().default(false).describe("Use 3 agents instead of 6"),
13066
+ quickMode: z19.boolean().optional().default(false).describe("Use 3 agents instead of 7"),
12816
13067
  simulateVotes: z19.boolean().optional().default(false).describe("Use simulated votes")
12817
13068
  };
12818
- const description = "Execute multi-model consensus voting on a proposal. Uses 6 specialized agent roles (architect, security, devex, ai_ml, pm, catfish) to vote on proposals with configurable strategies. Supports higher_order strategy for Bayesian-optimal aggregation with correlation awareness (Issue #514).";
13069
+ const description = "Execute multi-model consensus voting on a proposal. Uses 7 specialized agent roles (architect, security, devex, ai_ml, pm, catfish, scope_steward) to vote on proposals with configurable strategies. Supports higher_order strategy for Bayesian-optimal aggregation with correlation awareness (Issue #514).";
12819
13070
  const secureHandler = createSecureHandler(createConsensusVoteHandler(depsWithNotifier), {
12820
13071
  toolName: "consensus_vote",
12821
13072
  rateLimiter: deps.rateLimiter,
@@ -12895,6 +13146,7 @@ export {
12895
13146
  createSecureHandler,
12896
13147
  createMcpNotifier,
12897
13148
  NOOP_NOTIFIER,
13149
+ abortSignalStorage,
12898
13150
  withProgressHeartbeat,
12899
13151
  withAccessPolicy,
12900
13152
  getToolTimeout,
@@ -12976,4 +13228,4 @@ export {
12976
13228
  CONSENSUS_VOTE_OUTPUT_SCHEMA,
12977
13229
  registerConsensusVoteTool
12978
13230
  };
12979
- //# sourceMappingURL=chunk-BPMQRYGU.js.map
13231
+ //# sourceMappingURL=chunk-L3NHOUEX.js.map