nexus-agents 2.95.0 → 2.96.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.
- package/README.md +4 -4
- package/dist/{child-mcp-config-NW7JMSDK.js → child-mcp-config-HZUEFKXB.js} +2 -2
- package/dist/{chunk-GC5FH2IA.js → chunk-23NNSR45.js} +7 -7
- package/dist/{chunk-ECZW5FXN.js → chunk-2472O6HR.js} +2 -2
- package/dist/{chunk-ECBH5INZ.js → chunk-2KHD2ANY.js} +4 -3
- package/dist/{chunk-ECBH5INZ.js.map → chunk-2KHD2ANY.js.map} +1 -1
- package/dist/chunk-3ZROPKLV.js +2133 -0
- package/dist/chunk-3ZROPKLV.js.map +1 -0
- package/dist/{chunk-FYGZ4MEQ.js → chunk-7P3UIYJH.js} +415 -1410
- package/dist/chunk-7P3UIYJH.js.map +1 -0
- package/dist/{chunk-WGA4VGIR.js → chunk-AJMHTC4J.js} +3 -3
- package/dist/{chunk-KHZGMF2W.js → chunk-ANH2A44T.js} +4 -4
- package/dist/{chunk-G42N55ZR.js → chunk-AREIPUCD.js} +2 -2
- package/dist/{chunk-D3JSXVX3.js → chunk-AW3HJHJT.js} +2 -2
- package/dist/{chunk-FXVJLJUF.js → chunk-DTFM37IG.js} +2 -2
- package/dist/{chunk-47Z3YPOK.js → chunk-EUUWRNDZ.js} +2 -2
- package/dist/{chunk-3HXWI34V.js → chunk-FHZUOIVC.js} +3 -3
- package/dist/{chunk-ZICVVBDW.js → chunk-FPAUF3YB.js} +2 -2
- package/dist/{chunk-55G7KQ7F.js → chunk-ICOHLRHT.js} +5 -5
- package/dist/{chunk-FTDVA33Z.js → chunk-INFJH4JX.js} +4 -4
- package/dist/{chunk-BSHDI4BH.js → chunk-JEK5CHK2.js} +2 -2
- package/dist/{chunk-7GUP6KQQ.js → chunk-MMW4KIBH.js} +2 -2
- package/dist/{chunk-LZOTH2GI.js → chunk-MVCJEIUU.js} +2 -2
- package/dist/{chunk-2X24T4ZD.js → chunk-NJN5SGU2.js} +7 -7
- package/dist/{chunk-5HBDXW44.js → chunk-OHGKVMT3.js} +2 -2
- package/dist/{chunk-4NVYN2MB.js → chunk-ONIHS7K7.js} +4 -4
- package/dist/{chunk-TEKWQNQ2.js → chunk-P7Y3KLKG.js} +3 -3
- package/dist/{chunk-O4LVC4MQ.js → chunk-R2KJ22LX.js} +2 -2
- package/dist/chunk-SRSAMAPP.js +1095 -0
- package/dist/chunk-SRSAMAPP.js.map +1 -0
- package/dist/{chunk-LXROEQAO.js → chunk-SSHL5TAX.js} +2 -2
- package/dist/{chunk-YYXJ6RTO.js → chunk-THVTU2UG.js} +3 -3
- package/dist/{chunk-3H6Q3RZM.js → chunk-XMYJWMWN.js} +4 -4
- package/dist/{chunk-PFOY5ML6.js → chunk-YSYBVLYS.js} +3 -3
- package/dist/{chunk-QUPPV6UO.js → chunk-YWIZ3UQ6.js} +2 -2
- package/dist/{chunk-6IWPN5UB.js → chunk-Z3XF5LRT.js} +2 -2
- package/dist/{chunk-VTMQND6B.js → chunk-Z7PILY4E.js} +361 -2468
- package/dist/chunk-Z7PILY4E.js.map +1 -0
- package/dist/{chunk-Y3OOHHKQ.js → chunk-ZBQXPP7M.js} +2 -2
- package/dist/{cli-circuit-breaker-LNU5DQSB.js → cli-circuit-breaker-L6SWUVOP.js} +4 -4
- package/dist/cli.js +104 -52
- package/dist/cli.js.map +1 -1
- package/dist/{composite-router-DJ54ND5X.js → composite-router-HFBOUCXD.js} +2 -2
- package/dist/{consensus-vote-XLEQFVE7.js → consensus-vote-YEZIP2JQ.js} +13 -12
- package/dist/{context-retriever-6O6TREXH.js → context-retriever-PTRDEBBH.js} +6 -6
- package/dist/{doctor-deep-PSNJPELP.js → doctor-deep-WHCDAV3Z.js} +3 -3
- package/dist/{expert-bridge-Q3OWPD4L.js → expert-bridge-N4L47MHU.js} +4 -4
- package/dist/{factory-43RVPRT5.js → factory-EFLOKY32.js} +5 -5
- package/dist/{factory-PSMTMC7V.js → factory-ZKGYCGDT.js} +5 -5
- package/dist/improvement-review-4MTIYCOZ.js +27 -0
- package/dist/index.js +52 -49
- package/dist/index.js.map +1 -1
- package/dist/{init-opencode-25AUARJH.js → init-opencode-42GKRQDA.js} +5 -5
- package/dist/{issue-triage-KZTENUDH.js → issue-triage-TEPE6VMA.js} +5 -5
- package/dist/{pr-reviewer-helpers-XENJ4E2T.js → pr-reviewer-helpers-G4O22XF6.js} +4 -4
- package/dist/{registry-command-X3O34FAK.js → registry-command-32PUZNNG.js} +2 -2
- package/dist/{repo-security-plan-XCX7EFJR.js → repo-security-plan-XWUDQB34.js} +3 -3
- package/dist/{research-helpers-synthesize-CGVR6M4S.js → research-helpers-synthesize-YI6S7SN6.js} +3 -3
- package/dist/{routing-memory-YZRYGXQC.js → routing-memory-Z3ZCTPFM.js} +2 -2
- package/dist/{session-memory-SXQAA2EN.js → session-memory-5ZZ2SXDE.js} +3 -3
- package/dist/{setup-command-VG7JZI6O.js → setup-command-U3ILRY5H.js} +11 -11
- package/dist/{setup-config-VJAK6AA2.js → setup-config-WZYLXPDO.js} +3 -3
- package/dist/{setup-custom-api-KSF2JMTS.js → setup-custom-api-K4EN47CP.js} +4 -4
- package/dist/{tool-memory-L7SDFSF5.js → tool-memory-53Y2TUR3.js} +5 -5
- package/dist/{weather-report-D6HFOK7S.js → weather-report-JOBWXWQ3.js} +2 -2
- package/dist/weather-report-JOBWXWQ3.js.map +1 -0
- package/package.json +12 -2
- package/dist/chunk-FYGZ4MEQ.js.map +0 -1
- package/dist/chunk-VTMQND6B.js.map +0 -1
- /package/dist/{child-mcp-config-NW7JMSDK.js.map → child-mcp-config-HZUEFKXB.js.map} +0 -0
- /package/dist/{chunk-GC5FH2IA.js.map → chunk-23NNSR45.js.map} +0 -0
- /package/dist/{chunk-ECZW5FXN.js.map → chunk-2472O6HR.js.map} +0 -0
- /package/dist/{chunk-WGA4VGIR.js.map → chunk-AJMHTC4J.js.map} +0 -0
- /package/dist/{chunk-KHZGMF2W.js.map → chunk-ANH2A44T.js.map} +0 -0
- /package/dist/{chunk-G42N55ZR.js.map → chunk-AREIPUCD.js.map} +0 -0
- /package/dist/{chunk-D3JSXVX3.js.map → chunk-AW3HJHJT.js.map} +0 -0
- /package/dist/{chunk-FXVJLJUF.js.map → chunk-DTFM37IG.js.map} +0 -0
- /package/dist/{chunk-47Z3YPOK.js.map → chunk-EUUWRNDZ.js.map} +0 -0
- /package/dist/{chunk-3HXWI34V.js.map → chunk-FHZUOIVC.js.map} +0 -0
- /package/dist/{chunk-ZICVVBDW.js.map → chunk-FPAUF3YB.js.map} +0 -0
- /package/dist/{chunk-55G7KQ7F.js.map → chunk-ICOHLRHT.js.map} +0 -0
- /package/dist/{chunk-FTDVA33Z.js.map → chunk-INFJH4JX.js.map} +0 -0
- /package/dist/{chunk-BSHDI4BH.js.map → chunk-JEK5CHK2.js.map} +0 -0
- /package/dist/{chunk-7GUP6KQQ.js.map → chunk-MMW4KIBH.js.map} +0 -0
- /package/dist/{chunk-LZOTH2GI.js.map → chunk-MVCJEIUU.js.map} +0 -0
- /package/dist/{chunk-2X24T4ZD.js.map → chunk-NJN5SGU2.js.map} +0 -0
- /package/dist/{chunk-5HBDXW44.js.map → chunk-OHGKVMT3.js.map} +0 -0
- /package/dist/{chunk-4NVYN2MB.js.map → chunk-ONIHS7K7.js.map} +0 -0
- /package/dist/{chunk-TEKWQNQ2.js.map → chunk-P7Y3KLKG.js.map} +0 -0
- /package/dist/{chunk-O4LVC4MQ.js.map → chunk-R2KJ22LX.js.map} +0 -0
- /package/dist/{chunk-LXROEQAO.js.map → chunk-SSHL5TAX.js.map} +0 -0
- /package/dist/{chunk-YYXJ6RTO.js.map → chunk-THVTU2UG.js.map} +0 -0
- /package/dist/{chunk-3H6Q3RZM.js.map → chunk-XMYJWMWN.js.map} +0 -0
- /package/dist/{chunk-PFOY5ML6.js.map → chunk-YSYBVLYS.js.map} +0 -0
- /package/dist/{chunk-QUPPV6UO.js.map → chunk-YWIZ3UQ6.js.map} +0 -0
- /package/dist/{chunk-6IWPN5UB.js.map → chunk-Z3XF5LRT.js.map} +0 -0
- /package/dist/{chunk-Y3OOHHKQ.js.map → chunk-ZBQXPP7M.js.map} +0 -0
- /package/dist/{cli-circuit-breaker-LNU5DQSB.js.map → cli-circuit-breaker-L6SWUVOP.js.map} +0 -0
- /package/dist/{composite-router-DJ54ND5X.js.map → composite-router-HFBOUCXD.js.map} +0 -0
- /package/dist/{consensus-vote-XLEQFVE7.js.map → consensus-vote-YEZIP2JQ.js.map} +0 -0
- /package/dist/{context-retriever-6O6TREXH.js.map → context-retriever-PTRDEBBH.js.map} +0 -0
- /package/dist/{doctor-deep-PSNJPELP.js.map → doctor-deep-WHCDAV3Z.js.map} +0 -0
- /package/dist/{expert-bridge-Q3OWPD4L.js.map → expert-bridge-N4L47MHU.js.map} +0 -0
- /package/dist/{factory-43RVPRT5.js.map → factory-EFLOKY32.js.map} +0 -0
- /package/dist/{factory-PSMTMC7V.js.map → factory-ZKGYCGDT.js.map} +0 -0
- /package/dist/{issue-triage-KZTENUDH.js.map → improvement-review-4MTIYCOZ.js.map} +0 -0
- /package/dist/{init-opencode-25AUARJH.js.map → init-opencode-42GKRQDA.js.map} +0 -0
- /package/dist/{pr-reviewer-helpers-XENJ4E2T.js.map → issue-triage-TEPE6VMA.js.map} +0 -0
- /package/dist/{repo-security-plan-XCX7EFJR.js.map → pr-reviewer-helpers-G4O22XF6.js.map} +0 -0
- /package/dist/{registry-command-X3O34FAK.js.map → registry-command-32PUZNNG.js.map} +0 -0
- /package/dist/{research-helpers-synthesize-CGVR6M4S.js.map → repo-security-plan-XWUDQB34.js.map} +0 -0
- /package/dist/{routing-memory-YZRYGXQC.js.map → research-helpers-synthesize-YI6S7SN6.js.map} +0 -0
- /package/dist/{session-memory-SXQAA2EN.js.map → routing-memory-Z3ZCTPFM.js.map} +0 -0
- /package/dist/{setup-command-VG7JZI6O.js.map → session-memory-5ZZ2SXDE.js.map} +0 -0
- /package/dist/{setup-config-VJAK6AA2.js.map → setup-command-U3ILRY5H.js.map} +0 -0
- /package/dist/{tool-memory-L7SDFSF5.js.map → setup-config-WZYLXPDO.js.map} +0 -0
- /package/dist/{setup-custom-api-KSF2JMTS.js.map → setup-custom-api-K4EN47CP.js.map} +0 -0
- /package/dist/{weather-report-D6HFOK7S.js.map → tool-memory-53Y2TUR3.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-3ZROPKLV.js";
|
|
10
|
+
import {
|
|
11
|
+
createLogger,
|
|
12
|
+
formatZodError,
|
|
13
|
+
getErrorMessage,
|
|
14
|
+
getOutcomeStore,
|
|
15
|
+
getPipelineEventBus,
|
|
16
|
+
getTimeProvider
|
|
17
|
+
} from "./chunk-2KHD2ANY.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-SRSAMAPP.js.map
|