nexus-agents 2.95.0 → 2.97.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 (120) hide show
  1. package/README.md +4 -4
  2. package/dist/{child-mcp-config-NW7JMSDK.js → child-mcp-config-WVPJIK2J.js} +2 -2
  3. package/dist/chunk-5LGAPYX7.js +2133 -0
  4. package/dist/chunk-5LGAPYX7.js.map +1 -0
  5. package/dist/{chunk-LXROEQAO.js → chunk-65LE457J.js} +2 -2
  6. package/dist/{chunk-47Z3YPOK.js → chunk-674NONPG.js} +2 -2
  7. package/dist/{chunk-2X24T4ZD.js → chunk-6IN4MKDA.js} +7 -7
  8. package/dist/{chunk-FYGZ4MEQ.js → chunk-76FTBRUS.js} +415 -1410
  9. package/dist/chunk-76FTBRUS.js.map +1 -0
  10. package/dist/{chunk-YYXJ6RTO.js → chunk-7BLBXITB.js} +3 -3
  11. package/dist/{chunk-GC5FH2IA.js → chunk-APLZRTYC.js} +7 -7
  12. package/dist/{chunk-TEKWQNQ2.js → chunk-D3OT7TOA.js} +3 -3
  13. package/dist/{chunk-3HXWI34V.js → chunk-E43RUPLP.js} +3 -3
  14. package/dist/{chunk-LZOTH2GI.js → chunk-EY7LL4F4.js} +2 -2
  15. package/dist/{chunk-5HBDXW44.js → chunk-FDTIRCSL.js} +2 -2
  16. package/dist/{chunk-VTMQND6B.js → chunk-GAIUXYF4.js} +366 -2470
  17. package/dist/chunk-GAIUXYF4.js.map +1 -0
  18. package/dist/{chunk-D3JSXVX3.js → chunk-GERVGPTF.js} +2 -2
  19. package/dist/{chunk-6IWPN5UB.js → chunk-HIWO2WWP.js} +2 -2
  20. package/dist/{chunk-WGA4VGIR.js → chunk-HWCMNFIS.js} +3 -3
  21. package/dist/{chunk-G42N55ZR.js → chunk-HZGHPBTV.js} +2 -2
  22. package/dist/{chunk-KHZGMF2W.js → chunk-IHKPGTF3.js} +4 -4
  23. package/dist/{chunk-ECBH5INZ.js → chunk-JTQLV374.js} +71 -6
  24. package/dist/chunk-JTQLV374.js.map +1 -0
  25. package/dist/{chunk-PFOY5ML6.js → chunk-JX6VL7JF.js} +3 -3
  26. package/dist/{chunk-4NVYN2MB.js → chunk-KKVH3PDK.js} +4 -4
  27. package/dist/{chunk-3H6Q3RZM.js → chunk-ML4IF3PS.js} +4 -4
  28. package/dist/{chunk-QUPPV6UO.js → chunk-N5656LE3.js} +2 -2
  29. package/dist/{chunk-FXVJLJUF.js → chunk-NTBVUWU4.js} +2 -2
  30. package/dist/{chunk-ZICVVBDW.js → chunk-OKRTOMKX.js} +2 -2
  31. package/dist/{chunk-Y3OOHHKQ.js → chunk-OSHCOBDK.js} +2 -2
  32. package/dist/chunk-P2BKWVKF.js +1095 -0
  33. package/dist/chunk-P2BKWVKF.js.map +1 -0
  34. package/dist/{chunk-FTDVA33Z.js → chunk-SAUBPHQ7.js} +4 -4
  35. package/dist/{chunk-55G7KQ7F.js → chunk-SWNOQW7Y.js} +5 -5
  36. package/dist/{chunk-O4LVC4MQ.js → chunk-SZNNT2DH.js} +2 -2
  37. package/dist/{chunk-ECZW5FXN.js → chunk-T5TYWM5S.js} +2 -2
  38. package/dist/{chunk-7GUP6KQQ.js → chunk-UNOOL6EN.js} +2 -2
  39. package/dist/{chunk-BSHDI4BH.js → chunk-WXDWDGVL.js} +2 -2
  40. package/dist/{cli-circuit-breaker-LNU5DQSB.js → cli-circuit-breaker-STKW4DTL.js} +4 -4
  41. package/dist/cli.js +149 -52
  42. package/dist/cli.js.map +1 -1
  43. package/dist/{composite-router-DJ54ND5X.js → composite-router-POFAM7T4.js} +2 -2
  44. package/dist/{consensus-vote-XLEQFVE7.js → consensus-vote-EQAYX53Z.js} +13 -12
  45. package/dist/{context-retriever-6O6TREXH.js → context-retriever-H22L3WFV.js} +6 -6
  46. package/dist/{doctor-deep-PSNJPELP.js → doctor-deep-HGAC6N5Y.js} +3 -3
  47. package/dist/{expert-bridge-Q3OWPD4L.js → expert-bridge-IAQPOJAQ.js} +4 -4
  48. package/dist/{factory-PSMTMC7V.js → factory-P2SQS7UM.js} +5 -5
  49. package/dist/{factory-43RVPRT5.js → factory-ZIZYUDKH.js} +5 -5
  50. package/dist/improvement-review-G2HAMGZP.js +27 -0
  51. package/dist/index.d.ts +8 -1
  52. package/dist/index.js +52 -49
  53. package/dist/index.js.map +1 -1
  54. package/dist/{init-opencode-25AUARJH.js → init-opencode-RHCYZDZW.js} +5 -5
  55. package/dist/{issue-triage-KZTENUDH.js → issue-triage-VVNQIQW5.js} +5 -5
  56. package/dist/{pr-reviewer-helpers-XENJ4E2T.js → pr-reviewer-helpers-UN7RDPDN.js} +4 -4
  57. package/dist/{registry-command-X3O34FAK.js → registry-command-OVRRKYHO.js} +2 -2
  58. package/dist/{repo-security-plan-XCX7EFJR.js → repo-security-plan-X74TBYOV.js} +3 -3
  59. package/dist/{research-helpers-synthesize-CGVR6M4S.js → research-helpers-synthesize-OSP6FGNV.js} +3 -3
  60. package/dist/{routing-memory-YZRYGXQC.js → routing-memory-5T5QJF47.js} +2 -2
  61. package/dist/{session-memory-SXQAA2EN.js → session-memory-X7CJ6X4A.js} +3 -3
  62. package/dist/{setup-command-VG7JZI6O.js → setup-command-ZWJCGPJ5.js} +11 -11
  63. package/dist/{setup-config-VJAK6AA2.js → setup-config-YVIKUNOB.js} +3 -3
  64. package/dist/{setup-custom-api-KSF2JMTS.js → setup-custom-api-FLV4SELI.js} +4 -4
  65. package/dist/{tool-memory-L7SDFSF5.js → tool-memory-QNEAKRKV.js} +5 -5
  66. package/dist/{weather-report-D6HFOK7S.js → weather-report-SUVIXWKI.js} +2 -2
  67. package/dist/weather-report-SUVIXWKI.js.map +1 -0
  68. package/package.json +12 -2
  69. package/dist/chunk-ECBH5INZ.js.map +0 -1
  70. package/dist/chunk-FYGZ4MEQ.js.map +0 -1
  71. package/dist/chunk-VTMQND6B.js.map +0 -1
  72. /package/dist/{child-mcp-config-NW7JMSDK.js.map → child-mcp-config-WVPJIK2J.js.map} +0 -0
  73. /package/dist/{chunk-LXROEQAO.js.map → chunk-65LE457J.js.map} +0 -0
  74. /package/dist/{chunk-47Z3YPOK.js.map → chunk-674NONPG.js.map} +0 -0
  75. /package/dist/{chunk-2X24T4ZD.js.map → chunk-6IN4MKDA.js.map} +0 -0
  76. /package/dist/{chunk-YYXJ6RTO.js.map → chunk-7BLBXITB.js.map} +0 -0
  77. /package/dist/{chunk-GC5FH2IA.js.map → chunk-APLZRTYC.js.map} +0 -0
  78. /package/dist/{chunk-TEKWQNQ2.js.map → chunk-D3OT7TOA.js.map} +0 -0
  79. /package/dist/{chunk-3HXWI34V.js.map → chunk-E43RUPLP.js.map} +0 -0
  80. /package/dist/{chunk-LZOTH2GI.js.map → chunk-EY7LL4F4.js.map} +0 -0
  81. /package/dist/{chunk-5HBDXW44.js.map → chunk-FDTIRCSL.js.map} +0 -0
  82. /package/dist/{chunk-D3JSXVX3.js.map → chunk-GERVGPTF.js.map} +0 -0
  83. /package/dist/{chunk-6IWPN5UB.js.map → chunk-HIWO2WWP.js.map} +0 -0
  84. /package/dist/{chunk-WGA4VGIR.js.map → chunk-HWCMNFIS.js.map} +0 -0
  85. /package/dist/{chunk-G42N55ZR.js.map → chunk-HZGHPBTV.js.map} +0 -0
  86. /package/dist/{chunk-KHZGMF2W.js.map → chunk-IHKPGTF3.js.map} +0 -0
  87. /package/dist/{chunk-PFOY5ML6.js.map → chunk-JX6VL7JF.js.map} +0 -0
  88. /package/dist/{chunk-4NVYN2MB.js.map → chunk-KKVH3PDK.js.map} +0 -0
  89. /package/dist/{chunk-3H6Q3RZM.js.map → chunk-ML4IF3PS.js.map} +0 -0
  90. /package/dist/{chunk-QUPPV6UO.js.map → chunk-N5656LE3.js.map} +0 -0
  91. /package/dist/{chunk-FXVJLJUF.js.map → chunk-NTBVUWU4.js.map} +0 -0
  92. /package/dist/{chunk-ZICVVBDW.js.map → chunk-OKRTOMKX.js.map} +0 -0
  93. /package/dist/{chunk-Y3OOHHKQ.js.map → chunk-OSHCOBDK.js.map} +0 -0
  94. /package/dist/{chunk-FTDVA33Z.js.map → chunk-SAUBPHQ7.js.map} +0 -0
  95. /package/dist/{chunk-55G7KQ7F.js.map → chunk-SWNOQW7Y.js.map} +0 -0
  96. /package/dist/{chunk-O4LVC4MQ.js.map → chunk-SZNNT2DH.js.map} +0 -0
  97. /package/dist/{chunk-ECZW5FXN.js.map → chunk-T5TYWM5S.js.map} +0 -0
  98. /package/dist/{chunk-7GUP6KQQ.js.map → chunk-UNOOL6EN.js.map} +0 -0
  99. /package/dist/{chunk-BSHDI4BH.js.map → chunk-WXDWDGVL.js.map} +0 -0
  100. /package/dist/{cli-circuit-breaker-LNU5DQSB.js.map → cli-circuit-breaker-STKW4DTL.js.map} +0 -0
  101. /package/dist/{composite-router-DJ54ND5X.js.map → composite-router-POFAM7T4.js.map} +0 -0
  102. /package/dist/{consensus-vote-XLEQFVE7.js.map → consensus-vote-EQAYX53Z.js.map} +0 -0
  103. /package/dist/{context-retriever-6O6TREXH.js.map → context-retriever-H22L3WFV.js.map} +0 -0
  104. /package/dist/{doctor-deep-PSNJPELP.js.map → doctor-deep-HGAC6N5Y.js.map} +0 -0
  105. /package/dist/{expert-bridge-Q3OWPD4L.js.map → expert-bridge-IAQPOJAQ.js.map} +0 -0
  106. /package/dist/{factory-43RVPRT5.js.map → factory-P2SQS7UM.js.map} +0 -0
  107. /package/dist/{factory-PSMTMC7V.js.map → factory-ZIZYUDKH.js.map} +0 -0
  108. /package/dist/{issue-triage-KZTENUDH.js.map → improvement-review-G2HAMGZP.js.map} +0 -0
  109. /package/dist/{init-opencode-25AUARJH.js.map → init-opencode-RHCYZDZW.js.map} +0 -0
  110. /package/dist/{pr-reviewer-helpers-XENJ4E2T.js.map → issue-triage-VVNQIQW5.js.map} +0 -0
  111. /package/dist/{repo-security-plan-XCX7EFJR.js.map → pr-reviewer-helpers-UN7RDPDN.js.map} +0 -0
  112. /package/dist/{registry-command-X3O34FAK.js.map → registry-command-OVRRKYHO.js.map} +0 -0
  113. /package/dist/{research-helpers-synthesize-CGVR6M4S.js.map → repo-security-plan-X74TBYOV.js.map} +0 -0
  114. /package/dist/{routing-memory-YZRYGXQC.js.map → research-helpers-synthesize-OSP6FGNV.js.map} +0 -0
  115. /package/dist/{session-memory-SXQAA2EN.js.map → routing-memory-5T5QJF47.js.map} +0 -0
  116. /package/dist/{setup-command-VG7JZI6O.js.map → session-memory-X7CJ6X4A.js.map} +0 -0
  117. /package/dist/{setup-config-VJAK6AA2.js.map → setup-command-ZWJCGPJ5.js.map} +0 -0
  118. /package/dist/{tool-memory-L7SDFSF5.js.map → setup-config-YVIKUNOB.js.map} +0 -0
  119. /package/dist/{setup-custom-api-KSF2JMTS.js.map → setup-custom-api-FLV4SELI.js.map} +0 -0
  120. /package/dist/{weather-report-D6HFOK7S.js.map → tool-memory-QNEAKRKV.js.map} +0 -0
@@ -0,0 +1,1095 @@
1
+ import {
2
+ createSecureHandler,
3
+ getToolAnnotations,
4
+ getToolTimeout,
5
+ toSdkCallback,
6
+ toolStructuredError,
7
+ toolSuccessStructured,
8
+ wrapToolWithTimeout
9
+ } from "./chunk-5LGAPYX7.js";
10
+ import {
11
+ createLogger,
12
+ formatZodError,
13
+ getErrorMessage,
14
+ getOutcomeStore,
15
+ getPipelineEventBus,
16
+ getTimeProvider
17
+ } from "./chunk-JTQLV374.js";
18
+ import {
19
+ getNexusDataDir
20
+ } from "./chunk-CH7QIDHQ.js";
21
+
22
+ // src/mcp/tools/improvement-review.ts
23
+ import { execFile as execFile2 } from "child_process";
24
+ import { readFile } from "fs/promises";
25
+ import { promisify as promisify2 } from "util";
26
+ import { z } from "zod";
27
+
28
+ // src/mcp/middleware/tool-prerequisites.ts
29
+ import { execFile } from "child_process";
30
+ import { access, constants } from "fs/promises";
31
+ import { dirname } from "path";
32
+ import { promisify } from "util";
33
+ var execFileAsync = promisify(execFile);
34
+ async function ghCliAvailable() {
35
+ try {
36
+ await execFileAsync("gh", ["--version"], { timeout: 5e3 });
37
+ return { ok: true };
38
+ } catch (err) {
39
+ return {
40
+ ok: false,
41
+ remediation: `the 'gh' CLI is not available (${getErrorMessage(err)}) \u2014 install GitHub CLI and authenticate (\`gh auth login\`)`
42
+ };
43
+ }
44
+ }
45
+ async function dataDirWritable() {
46
+ const dir = getNexusDataDir();
47
+ try {
48
+ await access(dir, constants.W_OK);
49
+ return { ok: true };
50
+ } catch {
51
+ try {
52
+ await access(dirname(dir), constants.W_OK);
53
+ return { ok: true };
54
+ } catch (err) {
55
+ return {
56
+ ok: false,
57
+ remediation: `NEXUS_DATA_DIR (${dir}) is not writable or creatable (${getErrorMessage(err)}) \u2014 fix its permissions; run \`nexus-agents doctor\` to diagnose`
58
+ };
59
+ }
60
+ }
61
+ }
62
+ var TOOL_PREREQUISITES = {
63
+ improvement_review: {
64
+ name: "gh-cli-available",
65
+ rationale: "improvement_review's fileIssues mode shells out to `gh` to file candidate issues; without it the write path fails mid-operation",
66
+ check: ghCliAvailable
67
+ },
68
+ memory_write: {
69
+ name: "data-dir-writable",
70
+ rationale: "memory_write persists entries under NEXUS_DATA_DIR \u2014 an unwritable data dir fails the write confusingly mid-operation",
71
+ check: dataDirWritable
72
+ },
73
+ registry_import: {
74
+ name: "data-dir-writable",
75
+ rationale: "registry_import persists a draft registry entry under NEXUS_DATA_DIR when not in dryRun mode",
76
+ check: dataDirWritable
77
+ }
78
+ };
79
+ function applyPrerequisite(toolName, prereq, handler) {
80
+ return async (args, ctx) => {
81
+ let result;
82
+ try {
83
+ result = await prereq.check();
84
+ } catch (err) {
85
+ result = { ok: false, remediation: `prerequisite check threw: ${getErrorMessage(err)}` };
86
+ }
87
+ if (!result.ok) {
88
+ return toolStructuredError({
89
+ errorCategory: "permission",
90
+ message: `${toolName} blocked: prerequisite '${prereq.name}' not satisfied`,
91
+ detail: {
92
+ failedPrerequisite: prereq.name,
93
+ rationale: prereq.rationale,
94
+ remediation: result.remediation ?? "see .rules/tool-prerequisites.md"
95
+ }
96
+ });
97
+ }
98
+ return handler(args, ctx);
99
+ };
100
+ }
101
+ function withPrerequisite(toolName, handler) {
102
+ const prereq = TOOL_PREREQUISITES[toolName];
103
+ if (prereq === void 0) return handler;
104
+ return applyPrerequisite(toolName, prereq, handler);
105
+ }
106
+
107
+ // src/governance/fitness-score.ts
108
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
109
+ import { join } from "path";
110
+ function findPkgRoot() {
111
+ let dir = import.meta.dirname;
112
+ for (let i = 0; i < 10; i++) {
113
+ const pkgPath = join(dir, "package.json");
114
+ if (existsSync(pkgPath)) {
115
+ const content = readFileSync(pkgPath, "utf-8");
116
+ if (content.includes('"nexus-agents"')) return dir;
117
+ }
118
+ dir = join(dir, "..");
119
+ }
120
+ return join(import.meta.dirname, "../..");
121
+ }
122
+ var PKG_ROOT = findPkgRoot();
123
+ var SRC_ROOT = join(PKG_ROOT, "src");
124
+ var REPO_ROOT = join(PKG_ROOT, "../..");
125
+ var DOCS_ROOT = join(REPO_ROOT, "docs");
126
+ var DETERMINISM_EXCLUDES = [
127
+ /\.test\.ts$/,
128
+ /\.spec\.ts$/,
129
+ /random-provider\.ts$/,
130
+ /time-provider\.ts$/
131
+ ];
132
+ function countFiles(dir, pattern) {
133
+ if (!existsSync(dir)) return 0;
134
+ let count = 0;
135
+ for (const entry of readdirSync(dir)) {
136
+ const fullPath = join(dir, entry);
137
+ const stat = statSync(fullPath);
138
+ if (stat.isDirectory() && !entry.startsWith(".")) {
139
+ count += countFiles(fullPath, pattern);
140
+ } else if (pattern.test(entry)) {
141
+ count++;
142
+ }
143
+ }
144
+ return count;
145
+ }
146
+ function fileContains(filePath, pattern) {
147
+ if (!existsSync(filePath)) return false;
148
+ return pattern.test(readFileSync(filePath, "utf-8"));
149
+ }
150
+ function isExcluded(entry, excludePatterns) {
151
+ return excludePatterns?.some((p) => p.test(entry)) ?? false;
152
+ }
153
+ function countMatchesInFile(fullPath, contentPattern) {
154
+ const matches = readFileSync(fullPath, "utf-8").match(contentPattern);
155
+ return matches?.length ?? 0;
156
+ }
157
+ function countPatternInDir(dir, filePattern, contentPattern, excludePatterns) {
158
+ if (!existsSync(dir)) return 0;
159
+ let count = 0;
160
+ for (const entry of readdirSync(dir)) {
161
+ const fullPath = join(dir, entry);
162
+ const stat = statSync(fullPath);
163
+ if (stat.isDirectory() && !entry.startsWith(".") && entry !== "node_modules") {
164
+ count += countPatternInDir(fullPath, filePattern, contentPattern, excludePatterns);
165
+ } else if (filePattern.test(entry) && !isExcluded(entry, excludePatterns)) {
166
+ count += countMatchesInFile(fullPath, contentPattern);
167
+ }
168
+ }
169
+ return count;
170
+ }
171
+ function clamp(score, max) {
172
+ return Math.max(0, Math.min(max, score));
173
+ }
174
+ var FitnessScoreCalculator = class {
175
+ logger;
176
+ checks = [];
177
+ constructor(logger) {
178
+ this.logger = logger ?? createLogger({ component: "FitnessScoreCalculator" });
179
+ this.registerDefaultChecks();
180
+ }
181
+ /** Register default fitness checks. */
182
+ registerDefaultChecks() {
183
+ const reg = (dimension, maxPoints, name, check) => {
184
+ this.checks.push({ dimension, maxPoints, name, check });
185
+ };
186
+ reg("canonicalPaths", 20, "Canonical Paths", () => this.checkCanonicalPaths());
187
+ reg("explicitBehavior", 15, "Explicit Behavior", () => this.checkExplicitBehavior());
188
+ reg("determinism", 15, "Determinism", () => this.checkDeterminism());
189
+ reg("observability", 15, "Observability", () => this.checkObservability());
190
+ reg("configSimplicity", 10, "Config Simplicity", () => this.checkConfigSimplicity());
191
+ reg("layerSeparation", 10, "Layer Separation", () => this.checkLayerSeparation());
192
+ reg("operatorErgonomics", 10, "Operator Ergonomics", () => this.checkOperatorErgonomics());
193
+ reg(
194
+ "governanceIntegration",
195
+ 5,
196
+ "Governance Integration",
197
+ () => this.checkGovernanceIntegration()
198
+ );
199
+ }
200
+ /** Run full fitness audit. */
201
+ audit(version) {
202
+ const findings = [];
203
+ const dimensions = {};
204
+ const cliAdaptersDir = join(SRC_ROOT, "cli-adapters");
205
+ if (!existsSync(cliAdaptersDir)) {
206
+ return this.notSourceRepoResult(version);
207
+ }
208
+ for (const check of this.checks) {
209
+ this.logger.debug(`Running fitness check: ${check.name}`);
210
+ const result = check.check();
211
+ dimensions[check.dimension] = result.score;
212
+ findings.push(...result.findings);
213
+ }
214
+ const score = Object.values(dimensions).reduce((sum, val) => sum + val, 0);
215
+ this.logger.info("Fitness audit complete", { score, version });
216
+ const safeDim = (k) => dimensions[k] ?? 0;
217
+ const typedDimensions = {
218
+ canonicalPaths: safeDim("canonicalPaths"),
219
+ explicitBehavior: safeDim("explicitBehavior"),
220
+ determinism: safeDim("determinism"),
221
+ observability: safeDim("observability"),
222
+ configSimplicity: safeDim("configSimplicity"),
223
+ layerSeparation: safeDim("layerSeparation"),
224
+ operatorErgonomics: safeDim("operatorErgonomics"),
225
+ governanceIntegration: safeDim("governanceIntegration")
226
+ };
227
+ return {
228
+ score,
229
+ dimensions: typedDimensions,
230
+ findings,
231
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
232
+ version
233
+ };
234
+ }
235
+ /**
236
+ * Return when the runtime SRC_ROOT doesn't look like the nexus-agents
237
+ * source tree (#2716). One finding, score 0 — distinct from "the source
238
+ * tree is broken" because every existsSync would falsely report missing.
239
+ */
240
+ notSourceRepoResult(version) {
241
+ const finding = {
242
+ dimension: "governanceIntegration",
243
+ severity: "info",
244
+ description: `fitness-audit must run against the nexus-agents source repo, but ${SRC_ROOT}/cli-adapters does not exist. You are likely running the installed npm package via \`npx nexus-agents\`, which ships only the bundled dist/. Clone the source repo and run \`node packages/nexus-agents/dist/cli.js fitness-audit\` (or invoke \`pnpm fitness-audit\` from the workspace root).`,
245
+ pointsDeducted: 0
246
+ };
247
+ return {
248
+ score: 0,
249
+ dimensions: {
250
+ canonicalPaths: 0,
251
+ explicitBehavior: 0,
252
+ determinism: 0,
253
+ observability: 0,
254
+ configSimplicity: 0,
255
+ layerSeparation: 0,
256
+ operatorErgonomics: 0,
257
+ governanceIntegration: 0
258
+ },
259
+ findings: [finding],
260
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
261
+ version
262
+ };
263
+ }
264
+ // =========================================================================
265
+ // Individual Checks — real filesystem analysis
266
+ // =========================================================================
267
+ /**
268
+ * Check canonical paths: penalize duplicate router implementations.
269
+ *
270
+ * The current minimum is 6 (raised from 5 in #2063 after an audit):
271
+ * 1. composite-router — pipeline orchestrator
272
+ * 2. budget-router — budget/cost filtering
273
+ * 3. zero-router — hard-constraint exclusion
274
+ * 4. preference-router — user/task preference application
275
+ * 5. topsis-router — TOPSIS multi-criteria scoring
276
+ * 6. agreement-cascade-router — agreement-based cascade retry
277
+ *
278
+ * Each stage is distinct per CLAUDE.md's documented pipeline:
279
+ * Task → BudgetRouter → ZeroRouter → PreferenceRouter → TopsisRouter → Agreement → Model
280
+ */
281
+ checkCanonicalPaths() {
282
+ const findings = [];
283
+ let score = 20;
284
+ const routerCount = countFiles(join(SRC_ROOT, "cli-adapters"), /router\.ts$/);
285
+ const ROUTER_COUNT_THRESHOLD = 6;
286
+ if (routerCount > ROUTER_COUNT_THRESHOLD) {
287
+ const excess = routerCount - ROUTER_COUNT_THRESHOLD;
288
+ const deduction = Math.min(5, excess);
289
+ score -= deduction;
290
+ findings.push(
291
+ this.finding(
292
+ "canonicalPaths",
293
+ "warning",
294
+ `${String(routerCount)} router implementations found (target: <=${String(ROUTER_COUNT_THRESHOLD)})`,
295
+ deduction,
296
+ "Consolidate duplicate routers into CompositeRouter"
297
+ )
298
+ );
299
+ }
300
+ if (existsSync(join(SRC_ROOT, "cli-adapters/composite-router.ts"))) {
301
+ score += 2;
302
+ } else {
303
+ score -= 3;
304
+ findings.push(
305
+ this.finding(
306
+ "canonicalPaths",
307
+ "critical",
308
+ "CompositeRouter missing \u2014 no unified routing entry point",
309
+ 3
310
+ )
311
+ );
312
+ }
313
+ score = this.checkOrchestratorInterface(score, findings);
314
+ return { score: clamp(score, 20), findings };
315
+ }
316
+ /** Sub-check for IOrchestrator interface and adapter wiring. */
317
+ checkOrchestratorInterface(score, findings) {
318
+ const orchPath = join(SRC_ROOT, "core/types/orchestrator.ts");
319
+ if (existsSync(orchPath) && fileContains(orchPath, /interface IOrchestrator/)) {
320
+ score += 3;
321
+ } else {
322
+ score -= 2;
323
+ findings.push(
324
+ this.finding("canonicalPaths", "warning", "No IOrchestrator interface in core/types", 2)
325
+ );
326
+ }
327
+ const adapterPath = join(SRC_ROOT, "orchestration/orchestrator-adapters.ts");
328
+ if (existsSync(adapterPath) && fileContains(adapterPath, /TechLeadAdapter|PuppeteerAdapter/)) {
329
+ score += 2;
330
+ }
331
+ return score;
332
+ }
333
+ /**
334
+ * Check explicit behavior: penalize hidden/magic behavior.
335
+ *
336
+ * Implementation is intentionally filesystem-signal-based (no AST parse).
337
+ * The `NEXUS_ALLOW_MOCK_ORCHESTRATION` guard and magic-routing pattern
338
+ * grep capture the observable failure modes that historically slipped
339
+ * past review. AST-based detection of implicit fallbacks was considered
340
+ * but not pursued — it would significantly widen this function's
341
+ * footprint and the filesystem signals already catch the recurring
342
+ * regressions. Revisit only if a new class of hidden-behavior bug
343
+ * surfaces that this grep-over-source approach can't catch.
344
+ */
345
+ checkExplicitBehavior() {
346
+ const findings = [];
347
+ let score = 15;
348
+ const mockGuardCount = countPatternInDir(
349
+ SRC_ROOT,
350
+ /\.ts$/,
351
+ /NEXUS_ALLOW_MOCK_ORCHESTRATION/g,
352
+ DETERMINISM_EXCLUDES
353
+ );
354
+ if (mockGuardCount === 0) {
355
+ score -= 3;
356
+ findings.push(
357
+ this.finding(
358
+ "explicitBehavior",
359
+ "warning",
360
+ "No NEXUS_ALLOW_MOCK_ORCHESTRATION guard found \u2014 mock fallback may be implicit",
361
+ 3,
362
+ "Require explicit env var for mock orchestration"
363
+ )
364
+ );
365
+ }
366
+ const magicRouting = countPatternInDir(
367
+ SRC_ROOT,
368
+ /\.ts$/,
369
+ /fallback.*=.*true|implicitRoute/g,
370
+ DETERMINISM_EXCLUDES
371
+ );
372
+ if (magicRouting > 5) {
373
+ const deduction = Math.min(3, Math.floor(magicRouting / 3));
374
+ score -= deduction;
375
+ findings.push(
376
+ this.finding(
377
+ "explicitBehavior",
378
+ "info",
379
+ `${String(magicRouting)} implicit fallback/routing patterns detected`,
380
+ deduction
381
+ )
382
+ );
383
+ }
384
+ return { score: clamp(score, 15), findings };
385
+ }
386
+ /** Check determinism: penalize unseeded random and raw Date.now(). */
387
+ checkDeterminism() {
388
+ const findings = [];
389
+ let score = 15;
390
+ score = this.checkRandomDeterminism(score, findings);
391
+ score = this.checkTimeDeterminism(score, findings);
392
+ return { score: clamp(score, 15), findings };
393
+ }
394
+ /** Sub-check for Math.random() and injectable random provider. */
395
+ checkRandomDeterminism(score, findings) {
396
+ const randomCount = countPatternInDir(
397
+ SRC_ROOT,
398
+ /\.ts$/,
399
+ /Math\.random\(\)/g,
400
+ DETERMINISM_EXCLUDES
401
+ );
402
+ if (randomCount > 10) {
403
+ const deduction = Math.min(5, Math.floor(randomCount / 5));
404
+ score -= deduction;
405
+ findings.push(
406
+ this.finding(
407
+ "determinism",
408
+ "warning",
409
+ `${String(randomCount)} unseeded Math.random() calls in production code`,
410
+ deduction,
411
+ "Use getRandomProvider() for injectable randomness"
412
+ )
413
+ );
414
+ } else if (randomCount === 0) {
415
+ score += 2;
416
+ }
417
+ if (existsSync(join(SRC_ROOT, "core/random-provider.ts"))) {
418
+ score += 1;
419
+ }
420
+ const randomUsage = countPatternInDir(
421
+ SRC_ROOT,
422
+ /\.ts$/,
423
+ /getRandomProvider\(\)/g,
424
+ DETERMINISM_EXCLUDES
425
+ );
426
+ if (randomUsage > 5) {
427
+ score += 1;
428
+ }
429
+ return score;
430
+ }
431
+ /** Sub-check for Date.now() and injectable time provider. */
432
+ checkTimeDeterminism(score, findings) {
433
+ const dateNowCount = countPatternInDir(
434
+ SRC_ROOT,
435
+ /\.ts$/,
436
+ /Date\.now\(\)/g,
437
+ DETERMINISM_EXCLUDES
438
+ );
439
+ if (dateNowCount > 50) {
440
+ score -= 2;
441
+ findings.push(
442
+ this.finding(
443
+ "determinism",
444
+ "info",
445
+ `${String(dateNowCount)} Date.now() calls in production code`,
446
+ 2,
447
+ "Use getTimeProvider() for injectable time"
448
+ )
449
+ );
450
+ }
451
+ if (existsSync(join(SRC_ROOT, "core/time-provider.ts"))) {
452
+ score += 1;
453
+ }
454
+ const timeUsage = countPatternInDir(
455
+ SRC_ROOT,
456
+ /\.ts$/,
457
+ /getTimeProvider\(\)/g,
458
+ DETERMINISM_EXCLUDES
459
+ );
460
+ if (timeUsage > 10) {
461
+ score += 1;
462
+ }
463
+ return score;
464
+ }
465
+ /** Check observability: reward tracing, logging, and audit coverage. */
466
+ checkObservability() {
467
+ const findings = [];
468
+ let score = 15;
469
+ if (existsSync(join(SRC_ROOT, "observability/swarm-observer.ts"))) {
470
+ score += 3;
471
+ } else {
472
+ score -= 3;
473
+ findings.push(
474
+ this.finding(
475
+ "observability",
476
+ "warning",
477
+ "No SwarmObserver found",
478
+ 3,
479
+ "Add observability/swarm-observer.ts"
480
+ )
481
+ );
482
+ }
483
+ if (existsSync(join(SRC_ROOT, "core/trace.ts"))) {
484
+ score += 2;
485
+ }
486
+ const loggerCount = countPatternInDir(SRC_ROOT, /\.ts$/, /createLogger\(/g);
487
+ if (loggerCount > 50) {
488
+ score += 2;
489
+ } else {
490
+ findings.push(
491
+ this.finding(
492
+ "observability",
493
+ "info",
494
+ `Only ${String(loggerCount)} createLogger() calls (target: >50)`,
495
+ 0
496
+ )
497
+ );
498
+ }
499
+ if (existsSync(join(SRC_ROOT, "audit"))) {
500
+ score += 2;
501
+ }
502
+ return { score: clamp(score, 15), findings };
503
+ }
504
+ /** Check config simplicity: penalize excessive schema sprawl. */
505
+ checkConfigSimplicity() {
506
+ const findings = [];
507
+ let score = 10;
508
+ const schemaCount = countFiles(join(SRC_ROOT, "config"), /schema.*\.ts$/);
509
+ if (schemaCount > 10) {
510
+ score -= 2;
511
+ findings.push(
512
+ this.finding(
513
+ "configSimplicity",
514
+ "info",
515
+ `${String(schemaCount)} config schemas (target: <=10)`,
516
+ 2,
517
+ "Consolidate related schemas"
518
+ )
519
+ );
520
+ } else {
521
+ score += 1;
522
+ }
523
+ if (existsSync(join(SRC_ROOT, "config/config-loader.ts"))) {
524
+ score += 2;
525
+ }
526
+ if (existsSync(join(SRC_ROOT, "config/config-manager.ts"))) {
527
+ score += 1;
528
+ }
529
+ return { score: clamp(score, 10), findings };
530
+ }
531
+ /** Check layer separation: penalize cross-layer imports. */
532
+ checkLayerSeparation() {
533
+ const findings = [];
534
+ let score = 10;
535
+ const adapterAgentImports = countPatternInDir(
536
+ join(SRC_ROOT, "adapters"),
537
+ /\.ts$/,
538
+ /from ['"]\.\.\/agents\//g
539
+ );
540
+ if (adapterAgentImports > 0) {
541
+ const deduction = Math.min(5, adapterAgentImports);
542
+ score -= deduction;
543
+ findings.push(
544
+ this.finding(
545
+ "layerSeparation",
546
+ "warning",
547
+ `${String(adapterAgentImports)} adapter->agent import violations`,
548
+ deduction,
549
+ "Adapters should not import from agents layer"
550
+ )
551
+ );
552
+ } else {
553
+ score += 2;
554
+ }
555
+ const coreMcpImports = countPatternInDir(
556
+ join(SRC_ROOT, "core"),
557
+ /\.ts$/,
558
+ /from ['"]\.\.\/mcp\//g
559
+ );
560
+ if (coreMcpImports > 0) {
561
+ const deduction = Math.min(3, coreMcpImports);
562
+ score -= deduction;
563
+ findings.push(
564
+ this.finding(
565
+ "layerSeparation",
566
+ "critical",
567
+ `${String(coreMcpImports)} core->MCP import violations`,
568
+ deduction,
569
+ "Core must not depend on MCP layer"
570
+ )
571
+ );
572
+ } else {
573
+ score += 1;
574
+ }
575
+ return { score: clamp(score, 10), findings };
576
+ }
577
+ /** Check operator ergonomics: reward rich CLI commands. */
578
+ checkOperatorErgonomics() {
579
+ const findings = [];
580
+ let score = 10;
581
+ const commandCount = countFiles(join(SRC_ROOT, "cli"), /\.ts$/);
582
+ if (commandCount >= 20) {
583
+ score += 3;
584
+ } else {
585
+ findings.push(
586
+ this.finding(
587
+ "operatorErgonomics",
588
+ "info",
589
+ `${String(commandCount)} CLI commands (target: >=20)`,
590
+ 0
591
+ )
592
+ );
593
+ }
594
+ score = this.checkCliCommands(score, findings);
595
+ return { score: clamp(score, 10), findings };
596
+ }
597
+ /** Sub-check for essential CLI commands (doctor, setup, demo, config). */
598
+ checkCliCommands(score, findings) {
599
+ const commands = [
600
+ ["cli/doctor.ts", "Doctor command", 2],
601
+ ["cli/setup-command.ts", "Setup wizard", 2],
602
+ ["cli/demo-command.ts", "Demo command", 1],
603
+ ["cli/config-command.ts", "Config command", 1]
604
+ ];
605
+ for (const [path, name, bonus] of commands) {
606
+ if (existsSync(join(SRC_ROOT, path))) {
607
+ score += bonus;
608
+ } else {
609
+ findings.push(this.finding("operatorErgonomics", "info", `Missing ${name} (${path})`, 0));
610
+ }
611
+ }
612
+ return score;
613
+ }
614
+ /** Check governance integration: policy firewall, rate limiter, docs. */
615
+ checkGovernanceIntegration() {
616
+ const findings = [];
617
+ let score = 5;
618
+ score = this.checkGovernanceDocs(score, findings);
619
+ score = this.checkGovernanceInfra(score, findings);
620
+ return { score: clamp(score, 5), findings };
621
+ }
622
+ /** Sub-check for governance documentation artifacts. */
623
+ checkGovernanceDocs(score, findings) {
624
+ if (!existsSync(join(REPO_ROOT, "CLAUDE.md"))) {
625
+ score -= 3;
626
+ findings.push(
627
+ this.finding("governanceIntegration", "critical", "No CLAUDE.md governance document", 3)
628
+ );
629
+ } else {
630
+ score += 1;
631
+ }
632
+ if (existsSync(join(DOCS_ROOT, "architecture/wiring-graph.json"))) {
633
+ score += 1;
634
+ }
635
+ if (existsSync(join(DOCS_ROOT, "adr"))) {
636
+ score += 1;
637
+ } else {
638
+ findings.push(this.finding("governanceIntegration", "info", "No ADR directory", 0));
639
+ }
640
+ return score;
641
+ }
642
+ /** Sub-check for governance runtime infrastructure. */
643
+ checkGovernanceInfra(score, findings) {
644
+ const hasPolicyFirewall = countPatternInDir(join(SRC_ROOT, "security"), /\.ts$/, /PolicyGate|policyFirewall/g) > 0;
645
+ if (hasPolicyFirewall) {
646
+ score += 1;
647
+ } else {
648
+ findings.push(
649
+ this.finding(
650
+ "governanceIntegration",
651
+ "warning",
652
+ "No policy firewall detected in security layer",
653
+ 0
654
+ )
655
+ );
656
+ }
657
+ const hasRateLimiter = countPatternInDir(SRC_ROOT, /\.ts$/, /RateLimiter|rateLimiter/g) > 0;
658
+ if (hasRateLimiter) {
659
+ score += 1;
660
+ }
661
+ return score;
662
+ }
663
+ /** Helper to create a FitnessFinding with defaults. */
664
+ finding(dimension, severity, description2, pointsDeducted, suggestion) {
665
+ const base = { dimension, severity, description: description2, pointsDeducted };
666
+ if (suggestion !== void 0) {
667
+ return { ...base, suggestion };
668
+ }
669
+ return base;
670
+ }
671
+ };
672
+ function createFitnessScoreCalculator(logger) {
673
+ return new FitnessScoreCalculator(logger);
674
+ }
675
+ function calculateFitnessScore(version) {
676
+ const calculator = createFitnessScoreCalculator();
677
+ return calculator.audit(version);
678
+ }
679
+
680
+ // src/mcp/tools/improvement-review-signals.ts
681
+ function worstDimension(audit) {
682
+ let worst;
683
+ for (const finding of audit.findings) {
684
+ if (worst === void 0 || finding.pointsDeducted > worst.pointsDeducted) {
685
+ worst = { dimension: finding.dimension, pointsDeducted: finding.pointsDeducted };
686
+ }
687
+ }
688
+ return worst?.dimension;
689
+ }
690
+ function emitFitnessDeclinedSignal(audit, fitnessFloor, bus, logger) {
691
+ if (audit.score >= fitnessFloor) return;
692
+ try {
693
+ const dimension = worstDimension(audit);
694
+ bus.emit({
695
+ type: "signal.fitness_declined",
696
+ timestamp: getTimeProvider().now(),
697
+ score: audit.score,
698
+ floor: fitnessFloor,
699
+ ...dimension !== void 0 ? { dimension } : {}
700
+ });
701
+ } catch (error) {
702
+ logger.warn("Failed to emit signal.fitness_declined", { error: getErrorMessage(error) });
703
+ }
704
+ }
705
+
706
+ // src/mcp/tools/improvement-review.ts
707
+ var execFileAsync2 = promisify2(execFile2);
708
+ var ImprovementReviewInputSchema = z.object({
709
+ lookbackDays: z.number().int().min(1).max(90).optional().default(7).describe("Lookback window for outcome data, in days. Default 7."),
710
+ fileIssues: z.boolean().optional().default(false).describe(
711
+ "When true, file candidate issues via `gh issue create` for crossed thresholds (rate-limited to 5 per run, deduped against open issues). When false (default), return signals only."
712
+ ),
713
+ minSampleSize: z.number().int().min(1).max(1e3).optional().default(5).describe("Minimum sample size before a CLI/category signal can fire."),
714
+ fitnessFloor: z.number().int().min(0).max(100).optional().default(90).describe("Fitness score below this threshold triggers a tech-debt signal."),
715
+ selfEvalReportPath: z.string().optional().describe(
716
+ "Optional path to a self-eval JSON report (from `self-eval --json`). When set, high-confidence unanimous deprecate/refactor findings are surfaced as tech-debt signals through the same deduped/rate-limited issue path (#3224). Unreadable/malformed reports are skipped (no signal). Absent \u2192 no self-eval signals."
717
+ )
718
+ });
719
+ var MAX_ISSUES_PER_RUN = 5;
720
+ var HOUR_MS = 60 * 60 * 1e3;
721
+ var DAY_MS = 24 * HOUR_MS;
722
+ function filterByLookback(outcomes, lookbackDays, now) {
723
+ const cutoff = now - lookbackDays * DAY_MS;
724
+ return outcomes.filter((o) => {
725
+ const t = Date.parse(o.timestamp);
726
+ return Number.isFinite(t) && t >= cutoff;
727
+ });
728
+ }
729
+ function detectCliPerformanceFloor(outcomes, minSamples, windowLabel) {
730
+ const buckets = /* @__PURE__ */ new Map();
731
+ for (const o of outcomes) {
732
+ const key = `${o.cli}::${o.category}`;
733
+ const bucket = buckets.get(key) ?? { cli: o.cli, category: o.category, ok: 0, total: 0 };
734
+ bucket.total += 1;
735
+ if (o.success) bucket.ok += 1;
736
+ buckets.set(key, bucket);
737
+ }
738
+ const signals = [];
739
+ for (const b of buckets.values()) {
740
+ if (b.total < minSamples) continue;
741
+ const rate = b.ok / b.total;
742
+ if (rate >= 0.6) continue;
743
+ const ratePct = Math.round(rate * 100);
744
+ signals.push({
745
+ category: "routing",
746
+ signalKey: `routing:cli-floor:${b.cli}:${b.category}`,
747
+ severity: rate < 0.4 ? "critical" : "warning",
748
+ title: `routing: ${b.cli} success rate ${String(ratePct)}% on ${b.category} (${windowLabel})`,
749
+ body: [
750
+ `Observed performance floor breach in the ${windowLabel} window.`,
751
+ "",
752
+ `- CLI: \`${b.cli}\``,
753
+ `- Category: \`${b.category}\``,
754
+ `- Success rate: ${String(ratePct)}% (${String(b.ok)}/${String(b.total)})`,
755
+ `- Threshold: 60% with \u2265${String(minSamples)} samples`,
756
+ "",
757
+ "Consider routing this category away from this CLI, or investigating the failure pattern via `weather_report` and the OutcomeStore."
758
+ ].join("\n"),
759
+ evidence: {
760
+ samples: b.total,
761
+ window: windowLabel,
762
+ observedValue: rate,
763
+ threshold: 0.6
764
+ }
765
+ });
766
+ }
767
+ return signals;
768
+ }
769
+ function detectFailureCategoryConcentration(outcomes, windowLabel) {
770
+ const failures = outcomes.filter((o) => !o.success);
771
+ if (failures.length < 10) return [];
772
+ const byCategory = /* @__PURE__ */ new Map();
773
+ for (const f of failures) {
774
+ const cat = f.failureCategory ?? "unknown";
775
+ byCategory.set(cat, (byCategory.get(cat) ?? 0) + 1);
776
+ }
777
+ const signals = [];
778
+ for (const [cat, count] of byCategory) {
779
+ const share = count / failures.length;
780
+ if (share <= 0.5) continue;
781
+ const sharePct = Math.round(share * 100);
782
+ signals.push({
783
+ category: "bug",
784
+ signalKey: `bug:failure-concentration:${cat}`,
785
+ severity: "warning",
786
+ title: `bug: ${String(sharePct)}% of failures in ${windowLabel} share category \`${cat}\``,
787
+ body: [
788
+ `Failure-category concentration breach in the ${windowLabel} window.`,
789
+ "",
790
+ `- Category: \`${cat}\``,
791
+ `- Share: ${String(sharePct)}% (${String(count)}/${String(failures.length)} failures)`,
792
+ `- Threshold: > 50% with \u226510 failures`,
793
+ "",
794
+ "A single failure mode dominating the error budget usually means a systemic bug or routing miss. Investigate via `query_trace` and the OutcomeStore."
795
+ ].join("\n"),
796
+ evidence: {
797
+ samples: failures.length,
798
+ window: windowLabel,
799
+ observedValue: share,
800
+ threshold: 0.5
801
+ }
802
+ });
803
+ }
804
+ return signals;
805
+ }
806
+ function buildFloorSignal(audit, fitnessFloor) {
807
+ return {
808
+ category: "tech-debt",
809
+ signalKey: `tech-debt:fitness-below-floor`,
810
+ severity: audit.score < 70 ? "critical" : "warning",
811
+ title: `tech-debt: fitness score ${String(audit.score)}/100 below floor ${String(fitnessFloor)}`,
812
+ body: [
813
+ `Code fitness score has dropped below the governance floor.`,
814
+ "",
815
+ `- Score: ${String(audit.score)} / 100`,
816
+ `- Floor: ${String(fitnessFloor)} (governance threshold per CLAUDE.md)`,
817
+ `- Findings: ${String(audit.findings.length)} total`,
818
+ "",
819
+ "Run `nexus-agents fitness-audit` for the full breakdown. Critical findings:",
820
+ ...audit.findings.filter((f) => f.severity === "critical").slice(0, 5).map((f) => `- ${f.dimension}: ${f.description}`)
821
+ ].join("\n"),
822
+ evidence: { observedValue: audit.score, threshold: fitnessFloor }
823
+ };
824
+ }
825
+ function buildCriticalFindingSignal(finding) {
826
+ return {
827
+ category: "tech-debt",
828
+ signalKey: `tech-debt:fitness-critical:${finding.dimension}`,
829
+ severity: "critical",
830
+ title: `tech-debt: critical fitness finding in ${finding.dimension}`,
831
+ body: [
832
+ `Fitness audit returned a CRITICAL finding.`,
833
+ "",
834
+ `- Dimension: \`${finding.dimension}\``,
835
+ `- Description: ${finding.description}`,
836
+ `- Points deducted: ${String(finding.pointsDeducted)}`,
837
+ finding.location !== void 0 ? `- Location: ${finding.location}` : "",
838
+ finding.suggestion !== void 0 ? `- Suggestion: ${finding.suggestion}` : ""
839
+ ].filter((line) => line.length > 0).join("\n"),
840
+ evidence: { observedValue: -finding.pointsDeducted }
841
+ };
842
+ }
843
+ function detectFitnessSignals(audit, fitnessFloor) {
844
+ const signals = [];
845
+ if (audit.score < fitnessFloor) signals.push(buildFloorSignal(audit, fitnessFloor));
846
+ for (const finding of audit.findings) {
847
+ if (finding.severity === "critical") signals.push(buildCriticalFindingSignal(finding));
848
+ }
849
+ return signals;
850
+ }
851
+ var SELF_EVAL_CONFIDENCE_FLOOR = 0.8;
852
+ var SelfEvalReportSchema = z.object({
853
+ results: z.array(
854
+ z.object({
855
+ component: z.string(),
856
+ finalRecommendation: z.string(),
857
+ confidence: z.number(),
858
+ dissent: z.array(z.unknown()).optional().default([]),
859
+ evidenceQuality: z.number().optional()
860
+ })
861
+ )
862
+ });
863
+ function detectSelfEvalSignals(report, windowLabel) {
864
+ const signals = [];
865
+ for (const r of report.results) {
866
+ const actionable = r.finalRecommendation === "deprecate" || r.finalRecommendation === "refactor";
867
+ if (!actionable || r.dissent.length > 0 || r.confidence < SELF_EVAL_CONFIDENCE_FLOOR) continue;
868
+ signals.push({
869
+ category: "tech-debt",
870
+ signalKey: `tech-debt:self-eval:${r.component}:${r.finalRecommendation}`,
871
+ severity: r.finalRecommendation === "deprecate" ? "warning" : "info",
872
+ title: `tech-debt: self-eval recommends ${r.finalRecommendation} for ${r.component}`,
873
+ body: [
874
+ `All self-eval evaluators agreed (no dissent) on **${r.finalRecommendation}** for \`${r.component}\``,
875
+ `with confidence ${(r.confidence * 100).toFixed(0)}%${r.evidenceQuality !== void 0 ? ` (evidence quality ${(r.evidenceQuality * 100).toFixed(0)}%)` : ""}.`,
876
+ "",
877
+ "This is a RECOMMENDATION surfaced for human review \u2014 not an automatic change."
878
+ ].join("\n"),
879
+ evidence: {
880
+ observedValue: r.confidence,
881
+ threshold: SELF_EVAL_CONFIDENCE_FLOOR,
882
+ window: windowLabel
883
+ }
884
+ });
885
+ }
886
+ return signals;
887
+ }
888
+ async function loadSelfEvalSignals(reportPath, windowLabel, logger) {
889
+ try {
890
+ const raw = await readFile(reportPath, "utf8");
891
+ const parsed = SelfEvalReportSchema.safeParse(JSON.parse(raw));
892
+ if (!parsed.success) {
893
+ logger.warn("Self-eval report ignored \u2014 schema mismatch", {
894
+ reportPath,
895
+ error: formatZodError(parsed.error)
896
+ });
897
+ return [];
898
+ }
899
+ return detectSelfEvalSignals(parsed.data, windowLabel);
900
+ } catch (error) {
901
+ logger.warn("Self-eval report ignored \u2014 unreadable/invalid JSON", {
902
+ reportPath,
903
+ error: getErrorMessage(error)
904
+ });
905
+ return [];
906
+ }
907
+ }
908
+ async function existingIssueForSignal(signalKey) {
909
+ try {
910
+ const searchTerm = signalKey.replace(/"/g, "");
911
+ const { stdout } = await execFileAsync2("gh", [
912
+ "issue",
913
+ "list",
914
+ "--state",
915
+ "open",
916
+ "--search",
917
+ `"${searchTerm}" in:body`,
918
+ "--json",
919
+ "number,url",
920
+ "--limit",
921
+ "5"
922
+ ]);
923
+ const parsed = JSON.parse(stdout);
924
+ if (parsed.length > 0 && typeof parsed[0]?.url === "string") {
925
+ return parsed[0].url;
926
+ }
927
+ return null;
928
+ } catch {
929
+ return null;
930
+ }
931
+ }
932
+ async function fileIssueForSignal(signal) {
933
+ const body = `${signal.body}
934
+
935
+ ---
936
+
937
+ _Signal key (do not edit): \`${signal.signalKey}\` \xB7 Generated by \`improvement_review\` (#2402) \xB7 Severity: ${signal.severity}_`;
938
+ const labels = signal.category === "security" ? "security" : signal.category;
939
+ try {
940
+ const { stdout } = await execFileAsync2("gh", [
941
+ "issue",
942
+ "create",
943
+ "--title",
944
+ signal.title,
945
+ "--body",
946
+ body,
947
+ "--label",
948
+ labels
949
+ ]);
950
+ const url = stdout.trim();
951
+ if (!url.startsWith("https://")) {
952
+ return { ok: false, error: `gh returned unexpected output: ${stdout.slice(0, 200)}` };
953
+ }
954
+ return { ok: true, url };
955
+ } catch (caught) {
956
+ const message = caught instanceof Error ? caught.message : String(caught);
957
+ return { ok: false, error: message };
958
+ }
959
+ }
960
+ function safeFitnessAudit(now, ctx) {
961
+ try {
962
+ return calculateFitnessScore("improvement-review");
963
+ } catch (caught) {
964
+ ctx.logger.warn("fitness audit failed; skipping fitness signals", {
965
+ error: caught instanceof Error ? caught.message : String(caught)
966
+ });
967
+ return {
968
+ score: 100,
969
+ dimensions: {},
970
+ findings: [],
971
+ timestamp: new Date(now).toISOString(),
972
+ version: "improvement-review-fallback"
973
+ };
974
+ }
975
+ }
976
+ var SEVERITY_ORDER = {
977
+ critical: 0,
978
+ warning: 1,
979
+ info: 2
980
+ };
981
+ async function fileSignalsAsIssues(signals, ctx) {
982
+ const issuesFiled = [];
983
+ const issuesSkipped = [];
984
+ for (const signal of signals) {
985
+ if (issuesFiled.length >= MAX_ISSUES_PER_RUN) {
986
+ issuesSkipped.push({ signalKey: signal.signalKey, reason: "rate-limit" });
987
+ continue;
988
+ }
989
+ const existing = await existingIssueForSignal(signal.signalKey);
990
+ if (existing !== null) {
991
+ issuesSkipped.push({ signalKey: signal.signalKey, reason: `dup:${existing}` });
992
+ continue;
993
+ }
994
+ const result = await fileIssueForSignal(signal);
995
+ if (result.ok) {
996
+ issuesFiled.push({ signalKey: signal.signalKey, issueUrl: result.url });
997
+ ctx.logger.info("improvement signal filed", {
998
+ signalKey: signal.signalKey,
999
+ url: result.url
1000
+ });
1001
+ } else {
1002
+ issuesSkipped.push({ signalKey: signal.signalKey, reason: `error:${result.error}` });
1003
+ }
1004
+ }
1005
+ return { issuesFiled, issuesSkipped };
1006
+ }
1007
+ async function runImprovementReview(input, deps = {}) {
1008
+ const logger = deps.logger ?? createLogger({ component: "improvement_review" });
1009
+ const { lookbackDays, fileIssues, minSampleSize, fitnessFloor, selfEvalReportPath } = input;
1010
+ const now = Date.now();
1011
+ const windowLabel = `${String(lookbackDays)}d`;
1012
+ const allOutcomes = getOutcomeStore().query();
1013
+ const windowed = filterByLookback(allOutcomes, lookbackDays, now);
1014
+ const audit = safeFitnessAudit(now, { logger });
1015
+ const selfEvalSignals = selfEvalReportPath !== void 0 ? await loadSelfEvalSignals(selfEvalReportPath, windowLabel, logger) : [];
1016
+ const signals = [
1017
+ ...detectCliPerformanceFloor(windowed, minSampleSize, windowLabel),
1018
+ ...detectFailureCategoryConcentration(windowed, windowLabel),
1019
+ ...detectFitnessSignals(audit, fitnessFloor),
1020
+ ...selfEvalSignals
1021
+ ];
1022
+ signals.sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]);
1023
+ emitFitnessDeclinedSignal(audit, fitnessFloor, getPipelineEventBus(), logger);
1024
+ const { issuesFiled, issuesSkipped } = fileIssues ? await fileSignalsAsIssues(signals, { logger }) : { issuesFiled: [], issuesSkipped: [] };
1025
+ return {
1026
+ window: windowLabel,
1027
+ totalOutcomes: windowed.length,
1028
+ signals,
1029
+ issuesFiled,
1030
+ issuesSkipped
1031
+ };
1032
+ }
1033
+ async function reviewHandler(args, ctx) {
1034
+ const parsed = ImprovementReviewInputSchema.safeParse(args);
1035
+ if (!parsed.success) {
1036
+ return toolStructuredError({
1037
+ errorCategory: "validation",
1038
+ message: `Validation error: ${formatZodError(parsed.error)}`
1039
+ });
1040
+ }
1041
+ const response = await runImprovementReview(parsed.data, { logger: ctx.logger });
1042
+ return toolSuccessStructured(response);
1043
+ }
1044
+ var description = "Periodic threshold-gated observability-driven improvement loop. Reads OutcomeStore + fitness audit, surfaces patterns crossing documented thresholds as candidate findings. When fileIssues=true, files candidate GitHub issues via `gh issue create` (rate-limited to 5 per run, deduped against open issues). Never auto-merges. Replaces the deleted self-development engine (#2402).";
1045
+ var TOOL_INPUT_SCHEMA = {
1046
+ lookbackDays: z.number().int().min(1).max(90).optional().describe("Lookback window for outcome data, in days. Default 7."),
1047
+ fileIssues: z.boolean().optional().describe(
1048
+ "When true, file candidate issues for crossed thresholds (default false \u2014 return signals only)"
1049
+ ),
1050
+ minSampleSize: z.number().int().min(1).max(1e3).optional().describe("Minimum sample size before a CLI/category signal fires (default 5)."),
1051
+ fitnessFloor: z.number().int().min(0).max(100).optional().describe("Fitness score below this threshold triggers a tech-debt signal (default 90)."),
1052
+ selfEvalReportPath: z.string().optional().describe(
1053
+ "Optional path to a self-eval JSON report. High-confidence unanimous deprecate/refactor findings surface as tech-debt signals (#3224)."
1054
+ )
1055
+ };
1056
+ function registerImprovementReviewTool(server, deps) {
1057
+ const logger = deps.logger ?? createLogger({ tool: "improvement_review" });
1058
+ const secureHandler = createSecureHandler(reviewHandler, {
1059
+ toolName: "improvement_review",
1060
+ rateLimiter: deps.rateLimiter,
1061
+ logger
1062
+ });
1063
+ const guardedHandler = withPrerequisite("improvement_review", secureHandler);
1064
+ const timeoutMs = getToolTimeout("improvement_review", deps.security);
1065
+ const wrappedHandler = wrapToolWithTimeout("improvement_review", guardedHandler, {
1066
+ timeoutMs,
1067
+ logger
1068
+ });
1069
+ server.registerTool(
1070
+ "improvement_review",
1071
+ {
1072
+ description,
1073
+ inputSchema: TOOL_INPUT_SCHEMA,
1074
+ annotations: getToolAnnotations("improvement_review")
1075
+ },
1076
+ toSdkCallback(wrappedHandler)
1077
+ );
1078
+ logger.info("Registered improvement_review tool");
1079
+ }
1080
+
1081
+ export {
1082
+ withPrerequisite,
1083
+ createFitnessScoreCalculator,
1084
+ calculateFitnessScore,
1085
+ ImprovementReviewInputSchema,
1086
+ filterByLookback,
1087
+ detectCliPerformanceFloor,
1088
+ detectFailureCategoryConcentration,
1089
+ detectFitnessSignals,
1090
+ detectSelfEvalSignals,
1091
+ loadSelfEvalSignals,
1092
+ runImprovementReview,
1093
+ registerImprovementReviewTool
1094
+ };
1095
+ //# sourceMappingURL=chunk-P2BKWVKF.js.map