@triedotdev/mcp 1.0.49 → 1.0.51
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 +545 -406
- package/dist/agent-smith-BECRZH73.js +12 -0
- package/dist/{agent-smith-runner-ZTDCJJQG.js → agent-smith-runner-LZRXM2Q2.js} +7 -7
- package/dist/agent-smith-runner-LZRXM2Q2.js.map +1 -0
- package/dist/{chunk-KQOMSIVR.js → chunk-A43476GB.js} +13 -9
- package/dist/chunk-A43476GB.js.map +1 -0
- package/dist/{chunk-IMFD4SJC.js → chunk-ASGSTVVF.js} +1 -1
- package/dist/chunk-ASGSTVVF.js.map +1 -0
- package/dist/chunk-C3AS5OXW.js +1177 -0
- package/dist/chunk-C3AS5OXW.js.map +1 -0
- package/dist/chunk-IEFAQFDQ.js +2061 -0
- package/dist/chunk-IEFAQFDQ.js.map +1 -0
- package/dist/{chunk-GLC62PGD.js → chunk-KB5ZN6K2.js} +2 -2
- package/dist/{chunk-VZYCZXEQ.js → chunk-TOE75CFZ.js} +2034 -391
- package/dist/chunk-TOE75CFZ.js.map +1 -0
- package/dist/{chunk-JDICQHNT.js → chunk-YKUCIKTU.js} +171 -1245
- package/dist/chunk-YKUCIKTU.js.map +1 -0
- package/dist/cli/create-agent.js +2 -2
- package/dist/cli/main.js +428 -71
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/yolo-daemon.js +32 -20
- package/dist/cli/yolo-daemon.js.map +1 -1
- package/dist/comprehension-46F7ZNKL.js +821 -0
- package/dist/comprehension-46F7ZNKL.js.map +1 -0
- package/dist/index.js +478 -122
- package/dist/index.js.map +1 -1
- package/dist/workers/agent-worker.js +11 -11
- package/dist/workers/agent-worker.js.map +1 -1
- package/package.json +3 -1
- package/dist/agent-smith-5QOZXLMV.js +0 -11
- package/dist/agent-smith-runner-ZTDCJJQG.js.map +0 -1
- package/dist/chunk-6T7S77U7.js +0 -852
- package/dist/chunk-6T7S77U7.js.map +0 -1
- package/dist/chunk-IMFD4SJC.js.map +0 -1
- package/dist/chunk-JDICQHNT.js.map +0 -1
- package/dist/chunk-KQOMSIVR.js.map +0 -1
- package/dist/chunk-PZDQIFKO.js +0 -1598
- package/dist/chunk-PZDQIFKO.js.map +0 -1
- package/dist/chunk-VZYCZXEQ.js.map +0 -1
- /package/dist/{agent-smith-5QOZXLMV.js.map → agent-smith-BECRZH73.js.map} +0 -0
- /package/dist/{chunk-GLC62PGD.js.map → chunk-KB5ZN6K2.js.map} +0 -0
|
@@ -0,0 +1,1177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CustomSkill,
|
|
3
|
+
getGlobalMemoryStats,
|
|
4
|
+
getHistoricalInsights,
|
|
5
|
+
getMemoryStats,
|
|
6
|
+
getRecentIssues,
|
|
7
|
+
getSkillRegistry,
|
|
8
|
+
loadContextState
|
|
9
|
+
} from "./chunk-TOE75CFZ.js";
|
|
10
|
+
|
|
11
|
+
// src/orchestrator/triager.ts
|
|
12
|
+
var Triager = class {
|
|
13
|
+
agentRegistry = getSkillRegistry();
|
|
14
|
+
config;
|
|
15
|
+
customSkillsLoaded = false;
|
|
16
|
+
contextState = null;
|
|
17
|
+
memoryInsights = null;
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.config = {
|
|
20
|
+
minConfidence: 0.15,
|
|
21
|
+
maxAgents: 11,
|
|
22
|
+
timeoutMs: 6e4,
|
|
23
|
+
enableCostAware: false,
|
|
24
|
+
enableDependencies: true,
|
|
25
|
+
...config
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Load previous context state for smart triaging
|
|
30
|
+
*/
|
|
31
|
+
async loadPreviousContext() {
|
|
32
|
+
if (this.contextState === null) {
|
|
33
|
+
try {
|
|
34
|
+
this.contextState = await loadContextState();
|
|
35
|
+
} catch {
|
|
36
|
+
this.contextState = null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Load memory insights for memory-influenced triaging
|
|
42
|
+
*/
|
|
43
|
+
async loadMemoryInsights() {
|
|
44
|
+
if (this.memoryInsights !== null) return;
|
|
45
|
+
try {
|
|
46
|
+
const [stats, recent, historical, global] = await Promise.all([
|
|
47
|
+
getMemoryStats().catch(() => null),
|
|
48
|
+
getRecentIssues({ limit: 20 }).catch(() => []),
|
|
49
|
+
getHistoricalInsights(process.cwd()).catch(() => null),
|
|
50
|
+
getGlobalMemoryStats().catch(() => null)
|
|
51
|
+
]);
|
|
52
|
+
const recentAgents = /* @__PURE__ */ new Set();
|
|
53
|
+
for (const issue of recent) {
|
|
54
|
+
recentAgents.add(issue.agent);
|
|
55
|
+
}
|
|
56
|
+
const recurringPatternAgents = /* @__PURE__ */ new Set();
|
|
57
|
+
if (historical?.recurringPatterns) {
|
|
58
|
+
for (const pattern of historical.recurringPatterns) {
|
|
59
|
+
recurringPatternAgents.add(pattern.agent);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const crossProjectAgents = /* @__PURE__ */ new Set();
|
|
63
|
+
if (global?.patternsByAgent) {
|
|
64
|
+
for (const [agent, count] of Object.entries(global.patternsByAgent)) {
|
|
65
|
+
if (count >= 2) {
|
|
66
|
+
crossProjectAgents.add(agent);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.memoryInsights = {
|
|
71
|
+
issuesByAgent: stats?.issuesByAgent || {},
|
|
72
|
+
recentAgents,
|
|
73
|
+
recurringPatternAgents,
|
|
74
|
+
trend: historical?.improvementTrend || "unknown",
|
|
75
|
+
crossProjectAgents
|
|
76
|
+
};
|
|
77
|
+
} catch {
|
|
78
|
+
this.memoryInsights = {
|
|
79
|
+
issuesByAgent: {},
|
|
80
|
+
recentAgents: /* @__PURE__ */ new Set(),
|
|
81
|
+
recurringPatternAgents: /* @__PURE__ */ new Set(),
|
|
82
|
+
trend: "unknown",
|
|
83
|
+
crossProjectAgents: /* @__PURE__ */ new Set()
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Ensure custom skills are loaded before triaging
|
|
89
|
+
*/
|
|
90
|
+
async ensureCustomSkillsLoaded() {
|
|
91
|
+
if (!this.customSkillsLoaded) {
|
|
92
|
+
await this.agentRegistry.loadCustomSkills();
|
|
93
|
+
this.customSkillsLoaded = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async selectAgents(context, riskLevel) {
|
|
97
|
+
await this.ensureCustomSkillsLoaded();
|
|
98
|
+
await Promise.all([
|
|
99
|
+
this.loadPreviousContext(),
|
|
100
|
+
this.loadMemoryInsights()
|
|
101
|
+
]);
|
|
102
|
+
let effectiveRiskLevel = riskLevel;
|
|
103
|
+
if (this.contextState?.healthScore !== void 0 && this.contextState.healthScore < 50) {
|
|
104
|
+
if (riskLevel === "low" || riskLevel === "medium") {
|
|
105
|
+
effectiveRiskLevel = "high";
|
|
106
|
+
console.error(` \u{1F4CA} Health score ${this.contextState.healthScore}% - escalating to ${effectiveRiskLevel.toUpperCase()} risk`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (effectiveRiskLevel === "critical" || effectiveRiskLevel === "high") {
|
|
110
|
+
console.error(` \u26A0\uFE0F ${effectiveRiskLevel.toUpperCase()} risk - activating all agents for comprehensive review`);
|
|
111
|
+
return this.getAllAgents().filter((agent) => agent.shouldActivate(context));
|
|
112
|
+
}
|
|
113
|
+
const scores = this.scoreAgents(context, effectiveRiskLevel);
|
|
114
|
+
this.boostAgentsWithHistory(scores);
|
|
115
|
+
this.boostAgentsWithMemory(scores);
|
|
116
|
+
this.logAgentScoring(scores);
|
|
117
|
+
const qualified = scores.filter((s) => s.confidence >= this.config.minConfidence);
|
|
118
|
+
qualified.sort((a, b) => {
|
|
119
|
+
if (a.tier !== b.tier) return a.tier - b.tier;
|
|
120
|
+
return b.confidence - a.confidence;
|
|
121
|
+
});
|
|
122
|
+
if (this.config.enableDependencies) {
|
|
123
|
+
return this.resolveDependencies(qualified.map((s) => s.agent));
|
|
124
|
+
}
|
|
125
|
+
return qualified.map((s) => s.agent);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Boost confidence for agents that found issues in previous scans
|
|
129
|
+
*/
|
|
130
|
+
boostAgentsWithHistory(scores) {
|
|
131
|
+
if (!this.contextState?.agentStatus) return;
|
|
132
|
+
for (const score of scores) {
|
|
133
|
+
const previousRun = this.contextState.agentStatus[score.agent.name];
|
|
134
|
+
if (previousRun?.issuesFound && previousRun.issuesFound > 0) {
|
|
135
|
+
const boost = Math.min(0.3, previousRun.issuesFound * 0.05);
|
|
136
|
+
score.confidence = Math.min(1, score.confidence + boost);
|
|
137
|
+
score.reasons.push(`found ${previousRun.issuesFound} issues in last scan`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Boost confidence for agents based on memory patterns
|
|
143
|
+
*/
|
|
144
|
+
boostAgentsWithMemory(scores) {
|
|
145
|
+
if (!this.memoryInsights) return;
|
|
146
|
+
for (const score of scores) {
|
|
147
|
+
const agentName = score.agent.name;
|
|
148
|
+
const historicalCount = this.memoryInsights.issuesByAgent[agentName] || 0;
|
|
149
|
+
if (historicalCount >= 10) {
|
|
150
|
+
const boost = Math.min(0.2, historicalCount * 0.01);
|
|
151
|
+
score.confidence = Math.min(1, score.confidence + boost);
|
|
152
|
+
score.reasons.push(`${historicalCount} historical issues`);
|
|
153
|
+
}
|
|
154
|
+
if (this.memoryInsights.recentAgents.has(agentName)) {
|
|
155
|
+
score.confidence = Math.min(1, score.confidence + 0.1);
|
|
156
|
+
score.reasons.push("recent activity");
|
|
157
|
+
}
|
|
158
|
+
if (this.memoryInsights.recurringPatternAgents.has(agentName)) {
|
|
159
|
+
score.confidence = Math.min(1, score.confidence + 0.15);
|
|
160
|
+
score.reasons.push("recurring patterns");
|
|
161
|
+
}
|
|
162
|
+
if (this.memoryInsights.crossProjectAgents.has(agentName)) {
|
|
163
|
+
score.confidence = Math.min(1, score.confidence + 0.1);
|
|
164
|
+
score.reasons.push("cross-project pattern");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (this.memoryInsights.trend === "declining") {
|
|
168
|
+
for (const score of scores) {
|
|
169
|
+
if (score.confidence > 0) {
|
|
170
|
+
score.confidence = Math.min(1, score.confidence + 0.05);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
scoreAgents(context, riskLevel) {
|
|
176
|
+
const allAgents = this.getAllAgents();
|
|
177
|
+
const scores = [];
|
|
178
|
+
for (const agent of allAgents) {
|
|
179
|
+
const score = this.scoreAgent(agent, context, riskLevel);
|
|
180
|
+
scores.push(score);
|
|
181
|
+
}
|
|
182
|
+
return scores;
|
|
183
|
+
}
|
|
184
|
+
scoreAgent(agent, context, riskLevel) {
|
|
185
|
+
if (agent instanceof CustomSkill) {
|
|
186
|
+
return this.scoreCustomSkill(agent, context, riskLevel);
|
|
187
|
+
}
|
|
188
|
+
return this.scoreBuiltinAgent(agent, context, riskLevel);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Score custom skills using their activation rules
|
|
192
|
+
*/
|
|
193
|
+
scoreCustomSkill(agent, context, riskLevel) {
|
|
194
|
+
const reasons = [];
|
|
195
|
+
let confidence = agent.getActivationConfidence(context);
|
|
196
|
+
if (confidence > 0) {
|
|
197
|
+
reasons.push(`custom skill: ${agent.getMetadata().category}`);
|
|
198
|
+
const meta = agent.getMetadata();
|
|
199
|
+
if (meta.patternCount > 0) {
|
|
200
|
+
reasons.push(`${meta.patternCount} detection patterns`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (riskLevel === "high" && confidence > 0) {
|
|
204
|
+
confidence = Math.min(1, confidence * 1.2);
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
agent,
|
|
208
|
+
confidence,
|
|
209
|
+
reasons,
|
|
210
|
+
tier: agent.priority.tier,
|
|
211
|
+
isCustom: true
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Score built-in agents
|
|
216
|
+
*/
|
|
217
|
+
scoreBuiltinAgent(agent, context, riskLevel) {
|
|
218
|
+
const reasons = [];
|
|
219
|
+
let confidence = 0;
|
|
220
|
+
let tier = 3;
|
|
221
|
+
if (agent.name === "typecheck") {
|
|
222
|
+
tier = 1;
|
|
223
|
+
confidence = 1;
|
|
224
|
+
reasons.push("fundamental type safety");
|
|
225
|
+
}
|
|
226
|
+
if (agent.name === "comprehension") {
|
|
227
|
+
tier = 1;
|
|
228
|
+
confidence = 1;
|
|
229
|
+
reasons.push("stakeholder communication");
|
|
230
|
+
}
|
|
231
|
+
if (agent.name === "security") {
|
|
232
|
+
tier = 2;
|
|
233
|
+
if (context.touchesAuth) {
|
|
234
|
+
confidence += 0.4;
|
|
235
|
+
reasons.push("auth code");
|
|
236
|
+
}
|
|
237
|
+
if (context.touchesPayments) {
|
|
238
|
+
confidence += 0.4;
|
|
239
|
+
reasons.push("payment code");
|
|
240
|
+
}
|
|
241
|
+
if (context.touchesAPI) {
|
|
242
|
+
confidence += 0.3;
|
|
243
|
+
reasons.push("API endpoints");
|
|
244
|
+
}
|
|
245
|
+
if (context.touchesDatabase) {
|
|
246
|
+
confidence += 0.25;
|
|
247
|
+
reasons.push("database access");
|
|
248
|
+
}
|
|
249
|
+
if (context.touchesCrypto) {
|
|
250
|
+
confidence += 0.35;
|
|
251
|
+
reasons.push("cryptographic operations");
|
|
252
|
+
}
|
|
253
|
+
if (context.touchesUserData) {
|
|
254
|
+
confidence += 0.3;
|
|
255
|
+
reasons.push("user data");
|
|
256
|
+
}
|
|
257
|
+
if (context.touchesSecurityConfig) {
|
|
258
|
+
confidence += 0.4;
|
|
259
|
+
reasons.push("security config");
|
|
260
|
+
}
|
|
261
|
+
if (context.touchesThirdPartyAPI) {
|
|
262
|
+
confidence += 0.2;
|
|
263
|
+
reasons.push("third-party integration");
|
|
264
|
+
}
|
|
265
|
+
if (context.patterns?.hasFileUploads) {
|
|
266
|
+
confidence += 0.3;
|
|
267
|
+
reasons.push("file uploads");
|
|
268
|
+
}
|
|
269
|
+
if (riskLevel === "high") confidence *= 1.2;
|
|
270
|
+
}
|
|
271
|
+
if (agent.name === "privacy") {
|
|
272
|
+
tier = 2;
|
|
273
|
+
if (context.touchesUserData) {
|
|
274
|
+
confidence += 0.5;
|
|
275
|
+
reasons.push("PII handling");
|
|
276
|
+
}
|
|
277
|
+
if (context.touchesAuth) {
|
|
278
|
+
confidence += 0.3;
|
|
279
|
+
reasons.push("credentials");
|
|
280
|
+
}
|
|
281
|
+
if (context.touchesLogging) {
|
|
282
|
+
confidence += 0.25;
|
|
283
|
+
reasons.push("logging (may expose data)");
|
|
284
|
+
}
|
|
285
|
+
if (context.patterns?.hasEmailHandling) {
|
|
286
|
+
confidence += 0.3;
|
|
287
|
+
reasons.push("email handling");
|
|
288
|
+
}
|
|
289
|
+
if (context.patterns?.hasFormHandling) {
|
|
290
|
+
confidence += 0.2;
|
|
291
|
+
reasons.push("form data");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (agent.name === "legal") {
|
|
295
|
+
tier = 2;
|
|
296
|
+
if (context.touchesUserData) {
|
|
297
|
+
confidence += 0.4;
|
|
298
|
+
reasons.push("GDPR/CCPA");
|
|
299
|
+
}
|
|
300
|
+
if (context.touchesPayments) {
|
|
301
|
+
confidence += 0.35;
|
|
302
|
+
reasons.push("PCI-DSS");
|
|
303
|
+
}
|
|
304
|
+
if (context.patterns?.hasEmailHandling) {
|
|
305
|
+
confidence += 0.25;
|
|
306
|
+
reasons.push("CAN-SPAM");
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (agent.name === "accessibility") {
|
|
310
|
+
tier = 2;
|
|
311
|
+
if (context.touchesUI) {
|
|
312
|
+
confidence += 0.6;
|
|
313
|
+
reasons.push("UI components");
|
|
314
|
+
}
|
|
315
|
+
if (context.framework === "react" || context.framework === "vue") {
|
|
316
|
+
confidence += 0.2;
|
|
317
|
+
reasons.push(`${context.framework} framework`);
|
|
318
|
+
}
|
|
319
|
+
if (context.patterns?.hasFormHandling) {
|
|
320
|
+
confidence += 0.2;
|
|
321
|
+
reasons.push("form UX");
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (agent.name === "test") {
|
|
325
|
+
tier = 2;
|
|
326
|
+
if (context.isNewFeature) {
|
|
327
|
+
confidence += 0.4;
|
|
328
|
+
reasons.push("new feature");
|
|
329
|
+
}
|
|
330
|
+
if (context.touchesAuth) {
|
|
331
|
+
confidence += 0.35;
|
|
332
|
+
reasons.push("auth needs tests");
|
|
333
|
+
}
|
|
334
|
+
if (context.touchesPayments) {
|
|
335
|
+
confidence += 0.4;
|
|
336
|
+
reasons.push("payments need tests");
|
|
337
|
+
}
|
|
338
|
+
if (context.touchesAPI) {
|
|
339
|
+
confidence += 0.3;
|
|
340
|
+
reasons.push("API testing");
|
|
341
|
+
}
|
|
342
|
+
if (!context.hasTests) {
|
|
343
|
+
confidence += 0.2;
|
|
344
|
+
reasons.push("no existing tests");
|
|
345
|
+
}
|
|
346
|
+
if (context.complexity === "high") {
|
|
347
|
+
confidence += 0.25;
|
|
348
|
+
reasons.push("complex code");
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (agent.name === "software-architect") {
|
|
352
|
+
tier = 2;
|
|
353
|
+
if (context.isNewFeature) {
|
|
354
|
+
confidence += 0.35;
|
|
355
|
+
reasons.push("architecture review");
|
|
356
|
+
}
|
|
357
|
+
if (context.touchesDatabase) {
|
|
358
|
+
confidence += 0.35;
|
|
359
|
+
reasons.push("data modeling");
|
|
360
|
+
}
|
|
361
|
+
if (context.linesChanged > 200) {
|
|
362
|
+
confidence += 0.3;
|
|
363
|
+
reasons.push("large change");
|
|
364
|
+
}
|
|
365
|
+
if (context.touchesAPI) {
|
|
366
|
+
confidence += 0.25;
|
|
367
|
+
reasons.push("API design");
|
|
368
|
+
}
|
|
369
|
+
if (context.patterns?.hasWebSockets) {
|
|
370
|
+
confidence += 0.3;
|
|
371
|
+
reasons.push("real-time architecture");
|
|
372
|
+
}
|
|
373
|
+
if (context.patterns?.hasQueue) {
|
|
374
|
+
confidence += 0.3;
|
|
375
|
+
reasons.push("async architecture");
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (agent.name === "devops") {
|
|
379
|
+
tier = 2;
|
|
380
|
+
if (context.touchesSecurityConfig) {
|
|
381
|
+
confidence += 0.4;
|
|
382
|
+
reasons.push("security config");
|
|
383
|
+
}
|
|
384
|
+
if (context.touchesFileSystem) {
|
|
385
|
+
confidence += 0.3;
|
|
386
|
+
reasons.push("file operations");
|
|
387
|
+
}
|
|
388
|
+
if (context.touchesLogging) {
|
|
389
|
+
confidence += 0.25;
|
|
390
|
+
reasons.push("logging");
|
|
391
|
+
}
|
|
392
|
+
if (context.touchesErrorHandling) {
|
|
393
|
+
confidence += 0.2;
|
|
394
|
+
reasons.push("error handling");
|
|
395
|
+
}
|
|
396
|
+
if (context.patterns?.hasCaching) {
|
|
397
|
+
confidence += 0.25;
|
|
398
|
+
reasons.push("caching");
|
|
399
|
+
}
|
|
400
|
+
if (context.patterns?.hasRateLimiting) {
|
|
401
|
+
confidence += 0.3;
|
|
402
|
+
reasons.push("rate limiting");
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (agent.name === "bug-finding") {
|
|
406
|
+
tier = 2;
|
|
407
|
+
if (context.touchesPayments) {
|
|
408
|
+
confidence += 0.5;
|
|
409
|
+
reasons.push("payments = zero bugs");
|
|
410
|
+
}
|
|
411
|
+
if (context.isNewFeature) {
|
|
412
|
+
confidence += 0.3;
|
|
413
|
+
reasons.push("new code");
|
|
414
|
+
}
|
|
415
|
+
if (context.patterns?.hasAsyncCode) {
|
|
416
|
+
confidence += 0.35;
|
|
417
|
+
reasons.push("async patterns");
|
|
418
|
+
}
|
|
419
|
+
if (context.complexity === "high") {
|
|
420
|
+
confidence += 0.3;
|
|
421
|
+
reasons.push("complex logic");
|
|
422
|
+
}
|
|
423
|
+
if (context.touchesDatabase) {
|
|
424
|
+
confidence += 0.25;
|
|
425
|
+
reasons.push("data mutations");
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (agent.name === "user-testing") {
|
|
429
|
+
tier = 3;
|
|
430
|
+
if (context.touchesUI) {
|
|
431
|
+
confidence += 0.5;
|
|
432
|
+
reasons.push("UI changes");
|
|
433
|
+
}
|
|
434
|
+
if (context.isNewFeature && context.touchesUI) {
|
|
435
|
+
confidence += 0.3;
|
|
436
|
+
reasons.push("new UI feature");
|
|
437
|
+
}
|
|
438
|
+
if (context.patterns?.hasFormHandling) {
|
|
439
|
+
confidence += 0.25;
|
|
440
|
+
reasons.push("form UX");
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (agent.name === "trie_clean") {
|
|
444
|
+
tier = 2;
|
|
445
|
+
if (context.touchesUI) {
|
|
446
|
+
confidence += 0.4;
|
|
447
|
+
reasons.push("UI code");
|
|
448
|
+
}
|
|
449
|
+
if (context.isNewFeature) {
|
|
450
|
+
confidence += 0.3;
|
|
451
|
+
reasons.push("new feature");
|
|
452
|
+
}
|
|
453
|
+
if (context.framework === "react") {
|
|
454
|
+
confidence += 0.2;
|
|
455
|
+
reasons.push("React code");
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (agent.name === "soc2") {
|
|
459
|
+
tier = 2;
|
|
460
|
+
if (context.touchesAuth) {
|
|
461
|
+
confidence += 0.4;
|
|
462
|
+
reasons.push("authentication");
|
|
463
|
+
}
|
|
464
|
+
if (context.touchesSecurityConfig) {
|
|
465
|
+
confidence += 0.4;
|
|
466
|
+
reasons.push("security config");
|
|
467
|
+
}
|
|
468
|
+
if (context.touchesLogging) {
|
|
469
|
+
confidence += 0.3;
|
|
470
|
+
reasons.push("logging");
|
|
471
|
+
}
|
|
472
|
+
if (context.touchesAPI) {
|
|
473
|
+
confidence += 0.25;
|
|
474
|
+
reasons.push("API endpoints");
|
|
475
|
+
}
|
|
476
|
+
if (context.touchesDatabase) {
|
|
477
|
+
confidence += 0.2;
|
|
478
|
+
reasons.push("data access");
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (agent.name === "moneybags") {
|
|
482
|
+
tier = 3;
|
|
483
|
+
if (context.touchesPayments) {
|
|
484
|
+
confidence += 0.6;
|
|
485
|
+
reasons.push("payment code = high cost risk");
|
|
486
|
+
}
|
|
487
|
+
if (context.touchesAuth) {
|
|
488
|
+
confidence += 0.4;
|
|
489
|
+
reasons.push("auth bugs are expensive");
|
|
490
|
+
}
|
|
491
|
+
if (context.touchesHealthData) {
|
|
492
|
+
confidence += 0.5;
|
|
493
|
+
reasons.push("HIPAA violations");
|
|
494
|
+
}
|
|
495
|
+
if (context.touchesUserData) {
|
|
496
|
+
confidence += 0.3;
|
|
497
|
+
reasons.push("PII exposure costs");
|
|
498
|
+
}
|
|
499
|
+
if (context.touchesDatabase) {
|
|
500
|
+
confidence += 0.25;
|
|
501
|
+
reasons.push("data loss risk");
|
|
502
|
+
}
|
|
503
|
+
if (context.isNewFeature) {
|
|
504
|
+
confidence += 0.2;
|
|
505
|
+
reasons.push("new code risk");
|
|
506
|
+
}
|
|
507
|
+
if (riskLevel === "high" || riskLevel === "critical") {
|
|
508
|
+
confidence *= 1.3;
|
|
509
|
+
reasons.push("high-stakes context");
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (agent.name === "production-ready") {
|
|
513
|
+
tier = 3;
|
|
514
|
+
if (context.touchesAPI) {
|
|
515
|
+
confidence += 0.4;
|
|
516
|
+
reasons.push("API deployment");
|
|
517
|
+
}
|
|
518
|
+
if (context.touchesDatabase) {
|
|
519
|
+
confidence += 0.35;
|
|
520
|
+
reasons.push("database operations");
|
|
521
|
+
}
|
|
522
|
+
if (context.touchesAuth) {
|
|
523
|
+
confidence += 0.35;
|
|
524
|
+
reasons.push("auth system");
|
|
525
|
+
}
|
|
526
|
+
if (context.touchesPayments) {
|
|
527
|
+
confidence += 0.5;
|
|
528
|
+
reasons.push("payment processing");
|
|
529
|
+
}
|
|
530
|
+
if (context.linesChanged > 200) {
|
|
531
|
+
confidence += 0.3;
|
|
532
|
+
reasons.push("significant changes");
|
|
533
|
+
}
|
|
534
|
+
if (riskLevel === "high" || riskLevel === "critical") {
|
|
535
|
+
confidence += 0.4;
|
|
536
|
+
reasons.push("production gate check");
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
confidence = Math.min(1, confidence);
|
|
540
|
+
return { agent, confidence, reasons, tier, isCustom: false };
|
|
541
|
+
}
|
|
542
|
+
logAgentScoring(scores) {
|
|
543
|
+
console.error("\n \u{1F4CA} Agent Confidence Scores:");
|
|
544
|
+
const sorted = [...scores].sort((a, b) => b.confidence - a.confidence);
|
|
545
|
+
for (const score of sorted) {
|
|
546
|
+
const bar = this.getConfidenceBar(score.confidence);
|
|
547
|
+
const status = score.confidence >= this.config.minConfidence ? "\u2713" : "\u2717";
|
|
548
|
+
const tierLabel = score.tier === 1 ? "[T1]" : score.tier === 2 ? "[T2]" : "[T3]";
|
|
549
|
+
const customLabel = score.isCustom ? " \u{1F4DA}" : "";
|
|
550
|
+
console.error(` ${status} ${score.agent.name.padEnd(18)} ${tierLabel} ${bar} ${(score.confidence * 100).toFixed(0)}%${customLabel}`);
|
|
551
|
+
if (score.reasons.length > 0 && score.confidence > 0) {
|
|
552
|
+
console.error(` \u2514\u2500 ${score.reasons.join(", ")}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
console.error("");
|
|
556
|
+
}
|
|
557
|
+
getConfidenceBar(confidence) {
|
|
558
|
+
const filled = Math.round(confidence * 10);
|
|
559
|
+
const empty = 10 - filled;
|
|
560
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
561
|
+
}
|
|
562
|
+
resolveDependencies(agents) {
|
|
563
|
+
const agentNames = new Set(agents.map((a) => a.name));
|
|
564
|
+
const resolved = [];
|
|
565
|
+
const added = /* @__PURE__ */ new Set();
|
|
566
|
+
const dependencies = {
|
|
567
|
+
"legal": ["privacy"],
|
|
568
|
+
// legal should see privacy issues first
|
|
569
|
+
"test": ["bug-finding"],
|
|
570
|
+
// find bugs before writing tests
|
|
571
|
+
"user-testing": ["accessibility"]
|
|
572
|
+
// accessibility before UX
|
|
573
|
+
};
|
|
574
|
+
for (const agent of agents) {
|
|
575
|
+
const deps = dependencies[agent.name] || [];
|
|
576
|
+
for (const depName of deps) {
|
|
577
|
+
if (!added.has(depName)) {
|
|
578
|
+
const depAgent = this.agentRegistry.getAgent(depName);
|
|
579
|
+
if (depAgent && agentNames.has(depName)) {
|
|
580
|
+
resolved.push(depAgent);
|
|
581
|
+
added.add(depName);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (!added.has(agent.name)) {
|
|
586
|
+
resolved.push(agent);
|
|
587
|
+
added.add(agent.name);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return resolved;
|
|
591
|
+
}
|
|
592
|
+
getTriagingReason(context, riskLevel) {
|
|
593
|
+
if (riskLevel === "critical") {
|
|
594
|
+
return "Critical risk detected - activating all agents for comprehensive review";
|
|
595
|
+
}
|
|
596
|
+
const reasons = [];
|
|
597
|
+
if (context.touchesAuth) reasons.push("authentication");
|
|
598
|
+
if (context.touchesPayments) reasons.push("payments");
|
|
599
|
+
if (context.touchesDatabase) reasons.push("database");
|
|
600
|
+
if (context.touchesUserData) reasons.push("user data");
|
|
601
|
+
if (context.touchesUI) reasons.push("UI");
|
|
602
|
+
if (context.touchesAPI) reasons.push("API");
|
|
603
|
+
if (context.isNewFeature) reasons.push("new feature");
|
|
604
|
+
if (context.touchesCrypto) reasons.push("cryptography");
|
|
605
|
+
if (context.touchesThirdPartyAPI) reasons.push("3rd party API");
|
|
606
|
+
if (reasons.length === 0) {
|
|
607
|
+
return `${riskLevel} risk - general code changes`;
|
|
608
|
+
}
|
|
609
|
+
return `${riskLevel} risk: ${reasons.join(", ")}`;
|
|
610
|
+
}
|
|
611
|
+
async getSkippedAgents(context, riskLevel) {
|
|
612
|
+
await this.ensureCustomSkillsLoaded();
|
|
613
|
+
if (riskLevel === "critical") return [];
|
|
614
|
+
const scores = this.scoreAgents(context, riskLevel);
|
|
615
|
+
return scores.filter((s) => s.confidence < this.config.minConfidence).map((s) => s.agent.name);
|
|
616
|
+
}
|
|
617
|
+
async getTriagingConfidence(context, riskLevel) {
|
|
618
|
+
await this.ensureCustomSkillsLoaded();
|
|
619
|
+
const scores = this.scoreAgents(context, riskLevel);
|
|
620
|
+
const qualified = scores.filter((s) => s.confidence >= this.config.minConfidence);
|
|
621
|
+
if (qualified.length === 0) return 0.5;
|
|
622
|
+
const avgConfidence = qualified.reduce((sum, s) => sum + s.confidence, 0) / qualified.length;
|
|
623
|
+
const contextStrength = this.getContextStrength(context);
|
|
624
|
+
return Math.min(1, avgConfidence * 0.7 + contextStrength * 0.3);
|
|
625
|
+
}
|
|
626
|
+
getContextStrength(context) {
|
|
627
|
+
let signals = 0;
|
|
628
|
+
const checks = [
|
|
629
|
+
context.touchesAuth,
|
|
630
|
+
context.touchesPayments,
|
|
631
|
+
context.touchesDatabase,
|
|
632
|
+
context.touchesAPI,
|
|
633
|
+
context.touchesUI,
|
|
634
|
+
context.touchesUserData,
|
|
635
|
+
context.touchesHealthData,
|
|
636
|
+
context.touchesSecurityConfig,
|
|
637
|
+
context.touchesCrypto,
|
|
638
|
+
context.touchesFileSystem,
|
|
639
|
+
context.isNewFeature
|
|
640
|
+
];
|
|
641
|
+
for (const check of checks) {
|
|
642
|
+
if (check) signals++;
|
|
643
|
+
}
|
|
644
|
+
return signals > 0 ? Math.min(1, signals / 3) : 0.3;
|
|
645
|
+
}
|
|
646
|
+
getAllAgents() {
|
|
647
|
+
return this.agentRegistry.getAllAgents();
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Get custom skills count
|
|
651
|
+
*/
|
|
652
|
+
getCustomSkillCount() {
|
|
653
|
+
return this.agentRegistry.getCustomSkills().length;
|
|
654
|
+
}
|
|
655
|
+
// Backward compatibility alias
|
|
656
|
+
getCustomAgentCount() {
|
|
657
|
+
return this.getCustomSkillCount();
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Reload custom skills
|
|
661
|
+
*/
|
|
662
|
+
async reloadCustomSkills() {
|
|
663
|
+
await this.agentRegistry.reloadCustomSkills();
|
|
664
|
+
}
|
|
665
|
+
// Backward compatibility alias
|
|
666
|
+
async reloadCustomAgents() {
|
|
667
|
+
return this.reloadCustomSkills();
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
// src/utils/parallel-executor.ts
|
|
672
|
+
import { Worker } from "worker_threads";
|
|
673
|
+
import { cpus } from "os";
|
|
674
|
+
var ParallelExecutor = class {
|
|
675
|
+
maxWorkers;
|
|
676
|
+
cache;
|
|
677
|
+
streaming;
|
|
678
|
+
activeWorkers = /* @__PURE__ */ new Set();
|
|
679
|
+
cacheEnabled = true;
|
|
680
|
+
useWorkerThreads = false;
|
|
681
|
+
constructor(cacheManager, maxWorkers = Math.max(2, Math.min(cpus().length - 1, 8)), options) {
|
|
682
|
+
this.maxWorkers = maxWorkers;
|
|
683
|
+
this.cache = cacheManager;
|
|
684
|
+
this.cacheEnabled = options?.cacheEnabled ?? true;
|
|
685
|
+
this.useWorkerThreads = options?.useWorkerThreads ?? false;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Set streaming manager for real-time updates
|
|
689
|
+
*/
|
|
690
|
+
setStreaming(streaming) {
|
|
691
|
+
this.streaming = streaming;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Execute agents in parallel with intelligent scheduling
|
|
695
|
+
*/
|
|
696
|
+
async executeAgents(agents, files, context) {
|
|
697
|
+
if (agents.length === 0) {
|
|
698
|
+
return /* @__PURE__ */ new Map();
|
|
699
|
+
}
|
|
700
|
+
if (this.streaming && this.streaming.getProgress().totalFiles === 0) {
|
|
701
|
+
this.streaming.startScan(files.length);
|
|
702
|
+
}
|
|
703
|
+
const cacheResults = /* @__PURE__ */ new Map();
|
|
704
|
+
const uncachedTasks = [];
|
|
705
|
+
for (const agent of agents) {
|
|
706
|
+
const cached = await this.checkAgentCache(agent, files);
|
|
707
|
+
if (cached) {
|
|
708
|
+
cacheResults.set(agent.name, cached);
|
|
709
|
+
this.streaming?.completeAgent(agent.name, cached.issues);
|
|
710
|
+
} else {
|
|
711
|
+
uncachedTasks.push({
|
|
712
|
+
agent,
|
|
713
|
+
files,
|
|
714
|
+
context,
|
|
715
|
+
priority: agent.priority?.tier || 2,
|
|
716
|
+
timeoutMs: context?.config?.timeoutMs || 12e4
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
uncachedTasks.sort((a, b) => a.priority - b.priority);
|
|
721
|
+
const parallelResults = await this.executeTasksParallel(uncachedTasks);
|
|
722
|
+
await this.cacheResults(parallelResults);
|
|
723
|
+
const allResults = /* @__PURE__ */ new Map();
|
|
724
|
+
for (const [agent, result] of cacheResults) {
|
|
725
|
+
allResults.set(agent, result);
|
|
726
|
+
}
|
|
727
|
+
for (const result of parallelResults) {
|
|
728
|
+
allResults.set(result.agent, result.result);
|
|
729
|
+
}
|
|
730
|
+
const allIssues = Array.from(allResults.values()).flatMap((r) => r.issues);
|
|
731
|
+
this.streaming?.completeScan(allIssues);
|
|
732
|
+
return allResults;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Check if agent has cached results for given files
|
|
736
|
+
*/
|
|
737
|
+
async checkAgentCache(agent, files) {
|
|
738
|
+
if (!this.cacheEnabled || !this.cache) {
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
const cachedIssues = await this.cache.getCachedBatch(files, agent.name);
|
|
742
|
+
if (cachedIssues.size === files.length) {
|
|
743
|
+
const allIssues = Array.from(cachedIssues.values()).flat();
|
|
744
|
+
return {
|
|
745
|
+
agent: agent.name,
|
|
746
|
+
issues: allIssues,
|
|
747
|
+
executionTime: 0,
|
|
748
|
+
// Cached
|
|
749
|
+
success: true,
|
|
750
|
+
metadata: {
|
|
751
|
+
filesAnalyzed: files.length,
|
|
752
|
+
linesAnalyzed: 0
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Execute tasks in parallel batches
|
|
760
|
+
*/
|
|
761
|
+
async executeTasksParallel(tasks) {
|
|
762
|
+
if (tasks.length === 0) {
|
|
763
|
+
return [];
|
|
764
|
+
}
|
|
765
|
+
const results = [];
|
|
766
|
+
const batches = this.createBatches(tasks, this.maxWorkers);
|
|
767
|
+
for (const batch of batches) {
|
|
768
|
+
const batchResults = await Promise.all(
|
|
769
|
+
batch.map((task) => this.executeTask(task))
|
|
770
|
+
);
|
|
771
|
+
results.push(...batchResults);
|
|
772
|
+
}
|
|
773
|
+
return results;
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Create batches for parallel execution
|
|
777
|
+
*/
|
|
778
|
+
createBatches(tasks, batchSize) {
|
|
779
|
+
const batches = [];
|
|
780
|
+
for (let i = 0; i < tasks.length; i += batchSize) {
|
|
781
|
+
batches.push(tasks.slice(i, i + batchSize));
|
|
782
|
+
}
|
|
783
|
+
return batches;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Execute a single task
|
|
787
|
+
*/
|
|
788
|
+
async executeTask(task) {
|
|
789
|
+
const startTime = Date.now();
|
|
790
|
+
this.streaming?.startAgent(task.agent.name);
|
|
791
|
+
try {
|
|
792
|
+
const result = this.useWorkerThreads ? await this.executeTaskInWorker(task) : await task.agent.scan(task.files, task.context);
|
|
793
|
+
const executionTime = Date.now() - startTime;
|
|
794
|
+
this.streaming?.completeAgent(task.agent.name, result.issues);
|
|
795
|
+
return {
|
|
796
|
+
agent: task.agent.name,
|
|
797
|
+
result,
|
|
798
|
+
fromCache: false,
|
|
799
|
+
executionTime
|
|
800
|
+
};
|
|
801
|
+
} catch (error) {
|
|
802
|
+
const executionTime = Date.now() - startTime;
|
|
803
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
804
|
+
this.streaming?.reportError(new Error(errorMessage), `Agent: ${task.agent.name}`);
|
|
805
|
+
return {
|
|
806
|
+
agent: task.agent.name,
|
|
807
|
+
result: {
|
|
808
|
+
agent: task.agent.name,
|
|
809
|
+
issues: [],
|
|
810
|
+
executionTime,
|
|
811
|
+
success: false,
|
|
812
|
+
error: errorMessage
|
|
813
|
+
},
|
|
814
|
+
fromCache: false,
|
|
815
|
+
executionTime
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
async executeTaskInWorker(task) {
|
|
820
|
+
const distDir = new URL(".", import.meta.url);
|
|
821
|
+
const workerUrl = new URL("workers/agent-worker.js", distDir);
|
|
822
|
+
return new Promise((resolve, reject) => {
|
|
823
|
+
const worker = new Worker(workerUrl, {
|
|
824
|
+
workerData: {
|
|
825
|
+
agentName: task.agent.name,
|
|
826
|
+
files: task.files,
|
|
827
|
+
context: task.context
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
this.activeWorkers.add(worker);
|
|
831
|
+
const timeout = setTimeout(() => {
|
|
832
|
+
worker.terminate().catch(() => void 0);
|
|
833
|
+
reject(new Error(`Agent ${task.agent.name} timed out after ${task.timeoutMs}ms`));
|
|
834
|
+
}, task.timeoutMs);
|
|
835
|
+
worker.on("message", (message) => {
|
|
836
|
+
if (message?.type === "result") {
|
|
837
|
+
clearTimeout(timeout);
|
|
838
|
+
resolve(message.result);
|
|
839
|
+
} else if (message?.type === "error") {
|
|
840
|
+
clearTimeout(timeout);
|
|
841
|
+
reject(new Error(message.error));
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
worker.on("error", (error) => {
|
|
845
|
+
clearTimeout(timeout);
|
|
846
|
+
reject(error);
|
|
847
|
+
});
|
|
848
|
+
worker.on("exit", (code) => {
|
|
849
|
+
this.activeWorkers.delete(worker);
|
|
850
|
+
if (code !== 0) {
|
|
851
|
+
clearTimeout(timeout);
|
|
852
|
+
reject(new Error(`Worker stopped with exit code ${code}`));
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Cache results for future use
|
|
859
|
+
*/
|
|
860
|
+
async cacheResults(results) {
|
|
861
|
+
if (!this.cacheEnabled || !this.cache) {
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
const cachePromises = results.filter((r) => r.result.success && !r.fromCache).map((r) => {
|
|
865
|
+
const issuesByFile = this.groupIssuesByFile(r.result.issues);
|
|
866
|
+
const perFilePromises = Object.entries(issuesByFile).map(
|
|
867
|
+
([file, issues]) => this.cache.setCached(file, r.agent, issues, r.executionTime)
|
|
868
|
+
);
|
|
869
|
+
return Promise.all(perFilePromises);
|
|
870
|
+
});
|
|
871
|
+
await Promise.allSettled(cachePromises);
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Cleanup resources
|
|
875
|
+
*/
|
|
876
|
+
async cleanup() {
|
|
877
|
+
const terminationPromises = Array.from(this.activeWorkers).map(
|
|
878
|
+
(worker) => worker.terminate()
|
|
879
|
+
);
|
|
880
|
+
await Promise.allSettled(terminationPromises);
|
|
881
|
+
this.activeWorkers.clear();
|
|
882
|
+
}
|
|
883
|
+
groupIssuesByFile(issues) {
|
|
884
|
+
const grouped = {};
|
|
885
|
+
for (const issue of issues) {
|
|
886
|
+
if (!grouped[issue.file]) {
|
|
887
|
+
grouped[issue.file] = [];
|
|
888
|
+
}
|
|
889
|
+
grouped[issue.file].push(issue);
|
|
890
|
+
}
|
|
891
|
+
return grouped;
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
function calculateOptimalConcurrency() {
|
|
895
|
+
const numCPUs = cpus().length;
|
|
896
|
+
const availableMemoryGB = process.memoryUsage().rss / 1024 / 1024 / 1024;
|
|
897
|
+
let optimal = Math.max(2, Math.min(numCPUs - 1, 8));
|
|
898
|
+
if (availableMemoryGB < 2) {
|
|
899
|
+
optimal = Math.max(2, Math.floor(optimal / 2));
|
|
900
|
+
}
|
|
901
|
+
if (numCPUs > 8) {
|
|
902
|
+
optimal = Math.min(optimal + 2, 12);
|
|
903
|
+
}
|
|
904
|
+
return optimal;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// src/utils/cache-manager.ts
|
|
908
|
+
import { readFile, writeFile, mkdir, stat } from "fs/promises";
|
|
909
|
+
import { join } from "path";
|
|
910
|
+
import { createHash } from "crypto";
|
|
911
|
+
var CacheManager = class {
|
|
912
|
+
cacheDir;
|
|
913
|
+
indexPath;
|
|
914
|
+
VERSION = "1.0.0";
|
|
915
|
+
MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
916
|
+
// 24 hours
|
|
917
|
+
MAX_ENTRIES = 1e3;
|
|
918
|
+
constructor(baseDir) {
|
|
919
|
+
this.cacheDir = join(baseDir, ".trie", "cache");
|
|
920
|
+
this.indexPath = join(this.cacheDir, "index.json");
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Generate cache key for a file and agent combination
|
|
924
|
+
*/
|
|
925
|
+
generateCacheKey(filePath, agent, fileHash) {
|
|
926
|
+
const key = `${filePath}:${agent}:${fileHash}`;
|
|
927
|
+
return createHash("sha256").update(key).digest("hex").slice(0, 16);
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Get file hash for cache validation
|
|
931
|
+
*/
|
|
932
|
+
async getFileHash(filePath) {
|
|
933
|
+
try {
|
|
934
|
+
const content = await readFile(filePath, "utf-8");
|
|
935
|
+
const stats = await stat(filePath);
|
|
936
|
+
const hash = createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
937
|
+
return {
|
|
938
|
+
hash,
|
|
939
|
+
size: stats.size,
|
|
940
|
+
mtime: stats.mtime.getTime()
|
|
941
|
+
};
|
|
942
|
+
} catch {
|
|
943
|
+
return { hash: "", size: 0, mtime: 0 };
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Load cache index
|
|
948
|
+
*/
|
|
949
|
+
async loadIndex() {
|
|
950
|
+
try {
|
|
951
|
+
const content = await readFile(this.indexPath, "utf-8");
|
|
952
|
+
return JSON.parse(content);
|
|
953
|
+
} catch {
|
|
954
|
+
return {
|
|
955
|
+
version: this.VERSION,
|
|
956
|
+
created: Date.now(),
|
|
957
|
+
entries: {}
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Save cache index
|
|
963
|
+
*/
|
|
964
|
+
async saveIndex(index) {
|
|
965
|
+
try {
|
|
966
|
+
await mkdir(this.cacheDir, { recursive: true });
|
|
967
|
+
await writeFile(this.indexPath, JSON.stringify(index, null, 2));
|
|
968
|
+
} catch (error) {
|
|
969
|
+
console.warn("Failed to save cache index:", error);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Clean up expired entries
|
|
974
|
+
*/
|
|
975
|
+
cleanupExpired(index) {
|
|
976
|
+
const now = Date.now();
|
|
977
|
+
const validEntries = {};
|
|
978
|
+
for (const [key, entry] of Object.entries(index.entries)) {
|
|
979
|
+
if (now - entry.timestamp < this.MAX_AGE_MS) {
|
|
980
|
+
validEntries[key] = entry;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
const entries = Object.entries(validEntries);
|
|
984
|
+
if (entries.length > this.MAX_ENTRIES) {
|
|
985
|
+
entries.sort((a, b) => b[1].timestamp - a[1].timestamp);
|
|
986
|
+
const limited = entries.slice(0, this.MAX_ENTRIES);
|
|
987
|
+
return {
|
|
988
|
+
...index,
|
|
989
|
+
entries: Object.fromEntries(limited)
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
return {
|
|
993
|
+
...index,
|
|
994
|
+
entries: validEntries
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Get cached result for a file and agent
|
|
999
|
+
*/
|
|
1000
|
+
async getCached(filePath, agent) {
|
|
1001
|
+
try {
|
|
1002
|
+
const { hash, size: _size, mtime: _mtime } = await this.getFileHash(filePath);
|
|
1003
|
+
if (!hash) return null;
|
|
1004
|
+
const index = await this.loadIndex();
|
|
1005
|
+
const cacheKey = this.generateCacheKey(filePath, agent, hash);
|
|
1006
|
+
const entry = index.entries[cacheKey];
|
|
1007
|
+
if (!entry) return null;
|
|
1008
|
+
const isValid = entry.fileHash === hash && entry.version === this.VERSION && Date.now() - entry.timestamp < this.MAX_AGE_MS;
|
|
1009
|
+
if (!isValid) {
|
|
1010
|
+
delete index.entries[cacheKey];
|
|
1011
|
+
await this.saveIndex(index);
|
|
1012
|
+
return null;
|
|
1013
|
+
}
|
|
1014
|
+
return entry.issues;
|
|
1015
|
+
} catch {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Cache result for a file and agent
|
|
1021
|
+
*/
|
|
1022
|
+
async setCached(filePath, agent, issues, executionTime) {
|
|
1023
|
+
try {
|
|
1024
|
+
const { hash, size } = await this.getFileHash(filePath);
|
|
1025
|
+
if (!hash) return;
|
|
1026
|
+
const index = await this.loadIndex();
|
|
1027
|
+
const cacheKey = this.generateCacheKey(filePath, agent, hash);
|
|
1028
|
+
index.entries[cacheKey] = {
|
|
1029
|
+
version: this.VERSION,
|
|
1030
|
+
timestamp: Date.now(),
|
|
1031
|
+
fileHash: hash,
|
|
1032
|
+
fileSize: size,
|
|
1033
|
+
agent,
|
|
1034
|
+
issues,
|
|
1035
|
+
executionTime
|
|
1036
|
+
};
|
|
1037
|
+
const cleanedIndex = this.cleanupExpired(index);
|
|
1038
|
+
await this.saveIndex(cleanedIndex);
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
console.warn("Failed to cache result:", error);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Check if multiple files have cached results
|
|
1045
|
+
*/
|
|
1046
|
+
async getCachedBatch(files, agent) {
|
|
1047
|
+
const results = /* @__PURE__ */ new Map();
|
|
1048
|
+
await Promise.all(
|
|
1049
|
+
files.map(async (file) => {
|
|
1050
|
+
const cached = await this.getCached(file, agent);
|
|
1051
|
+
if (cached) {
|
|
1052
|
+
results.set(file, cached);
|
|
1053
|
+
}
|
|
1054
|
+
})
|
|
1055
|
+
);
|
|
1056
|
+
return results;
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Get cache statistics
|
|
1060
|
+
*/
|
|
1061
|
+
async getStats() {
|
|
1062
|
+
try {
|
|
1063
|
+
const index = await this.loadIndex();
|
|
1064
|
+
const entries = Object.values(index.entries);
|
|
1065
|
+
const totalSizeKB = entries.reduce((acc, entry) => acc + entry.fileSize, 0) / 1024;
|
|
1066
|
+
const timestamps = entries.map((e) => e.timestamp);
|
|
1067
|
+
const agents = [...new Set(entries.map((e) => e.agent))];
|
|
1068
|
+
return {
|
|
1069
|
+
totalEntries: entries.length,
|
|
1070
|
+
totalSizeKB: Math.round(totalSizeKB),
|
|
1071
|
+
oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : null,
|
|
1072
|
+
newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : null,
|
|
1073
|
+
agents
|
|
1074
|
+
};
|
|
1075
|
+
} catch {
|
|
1076
|
+
return {
|
|
1077
|
+
totalEntries: 0,
|
|
1078
|
+
totalSizeKB: 0,
|
|
1079
|
+
oldestEntry: null,
|
|
1080
|
+
newestEntry: null,
|
|
1081
|
+
agents: []
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Clear all cache
|
|
1087
|
+
*/
|
|
1088
|
+
async clear() {
|
|
1089
|
+
try {
|
|
1090
|
+
const emptyIndex = {
|
|
1091
|
+
version: this.VERSION,
|
|
1092
|
+
created: Date.now(),
|
|
1093
|
+
entries: {}
|
|
1094
|
+
};
|
|
1095
|
+
await this.saveIndex(emptyIndex);
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
console.warn("Failed to clear cache:", error);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
// src/orchestrator/executor.ts
|
|
1103
|
+
var Executor = class {
|
|
1104
|
+
async executeAgents(agents, files, context, options) {
|
|
1105
|
+
const parallel = options?.parallel ?? true;
|
|
1106
|
+
const cacheEnabled = options?.cacheEnabled ?? true;
|
|
1107
|
+
const maxConcurrency = options?.maxConcurrency ?? calculateOptimalConcurrency();
|
|
1108
|
+
const useWorkerThreads = options?.useWorkerThreads ?? false;
|
|
1109
|
+
console.error(`\u26A1 Executing ${agents.length} agents ${parallel ? "in parallel" : "sequentially"}...`);
|
|
1110
|
+
if (parallel) {
|
|
1111
|
+
const cacheManager = cacheEnabled ? new CacheManager(context.workingDir) : null;
|
|
1112
|
+
const executor = new ParallelExecutor(cacheManager, maxConcurrency, {
|
|
1113
|
+
cacheEnabled,
|
|
1114
|
+
useWorkerThreads
|
|
1115
|
+
});
|
|
1116
|
+
if (options?.streaming) {
|
|
1117
|
+
executor.setStreaming(options.streaming);
|
|
1118
|
+
}
|
|
1119
|
+
const results = await executor.executeAgents(agents, files, {
|
|
1120
|
+
...context,
|
|
1121
|
+
config: { timeoutMs: options?.timeoutMs ?? 12e4 }
|
|
1122
|
+
});
|
|
1123
|
+
return agents.map((agent) => results.get(agent.name)).filter(Boolean);
|
|
1124
|
+
}
|
|
1125
|
+
const promises = agents.map(
|
|
1126
|
+
(agent) => this.executeAgentWithTimeout(agent, files, context, options?.timeoutMs ?? 3e4)
|
|
1127
|
+
);
|
|
1128
|
+
try {
|
|
1129
|
+
const results = await Promise.allSettled(promises);
|
|
1130
|
+
return results.map((result, index) => {
|
|
1131
|
+
if (result.status === "fulfilled") {
|
|
1132
|
+
console.error(`\u2705 ${agents[index].name} completed in ${result.value.executionTime}ms`);
|
|
1133
|
+
return result.value;
|
|
1134
|
+
} else {
|
|
1135
|
+
console.error(`\u274C ${agents[index].name} failed:`, result.reason);
|
|
1136
|
+
return {
|
|
1137
|
+
agent: agents[index].name,
|
|
1138
|
+
issues: [],
|
|
1139
|
+
executionTime: 0,
|
|
1140
|
+
success: false,
|
|
1141
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
console.error("Executor error:", error);
|
|
1147
|
+
return agents.map((agent) => ({
|
|
1148
|
+
agent: agent.name,
|
|
1149
|
+
issues: [],
|
|
1150
|
+
executionTime: 0,
|
|
1151
|
+
success: false,
|
|
1152
|
+
error: "Execution failed"
|
|
1153
|
+
}));
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
async executeAgentWithTimeout(agent, files, context, timeoutMs = 3e4) {
|
|
1157
|
+
return new Promise(async (resolve, reject) => {
|
|
1158
|
+
const timeout = setTimeout(() => {
|
|
1159
|
+
reject(new Error(`Agent ${agent.name} timed out after ${timeoutMs}ms`));
|
|
1160
|
+
}, timeoutMs);
|
|
1161
|
+
try {
|
|
1162
|
+
const result = await agent.scan(files, context);
|
|
1163
|
+
clearTimeout(timeout);
|
|
1164
|
+
resolve(result);
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
clearTimeout(timeout);
|
|
1167
|
+
reject(error);
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1173
|
+
export {
|
|
1174
|
+
Triager,
|
|
1175
|
+
Executor
|
|
1176
|
+
};
|
|
1177
|
+
//# sourceMappingURL=chunk-C3AS5OXW.js.map
|