@triedotdev/mcp 1.0.94 → 1.0.99
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 +145 -137
- package/dist/{chunk-JAAIHNOE.js → chunk-APMV77PU.js} +21 -6
- package/dist/chunk-APMV77PU.js.map +1 -0
- package/dist/{chunk-HLSBTOVE.js → chunk-B3MNN3XB.js} +13 -18
- package/dist/{chunk-HLSBTOVE.js.map → chunk-B3MNN3XB.js.map} +1 -1
- package/dist/{chunk-IIF5XDCJ.js → chunk-DIZFGLXE.js} +787 -4696
- package/dist/chunk-DIZFGLXE.js.map +1 -0
- package/dist/{chunk-JO6RVXS6.js → chunk-F4NJ4CBP.js} +2 -2
- package/dist/{chunk-AZRCKBGF.js → chunk-FNCCZ3XB.js} +1222 -75
- package/dist/chunk-FNCCZ3XB.js.map +1 -0
- package/dist/chunk-G76DYVGX.js +136 -0
- package/dist/chunk-G76DYVGX.js.map +1 -0
- package/dist/chunk-HSNE46VE.js +956 -0
- package/dist/chunk-HSNE46VE.js.map +1 -0
- package/dist/{chunk-STEFLYPR.js → chunk-IXO4G4D3.js} +2 -2
- package/dist/{chunk-OEYIOOYB.js → chunk-JDHR5BDR.js} +2 -3
- package/dist/chunk-NIASHOAB.js +1304 -0
- package/dist/chunk-NIASHOAB.js.map +1 -0
- package/dist/{chunk-CKM6A3G6.js → chunk-OVRG5RP3.js} +6 -7
- package/dist/chunk-OVRG5RP3.js.map +1 -0
- package/dist/{chunk-RYRVEO2B.js → chunk-R3I2GCZC.js} +3 -3
- package/dist/{chunk-WT3XQCG2.js → chunk-R4AAPFXC.js} +2 -2
- package/dist/cli/create-agent.js +931 -7
- package/dist/cli/create-agent.js.map +1 -1
- package/dist/cli/main.js +151 -383
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/yolo-daemon.js +13 -20
- package/dist/cli/yolo-daemon.js.map +1 -1
- package/dist/{goal-manager-HOZ7R2QV.js → goal-manager-LAOT4QQX.js} +6 -6
- package/dist/guardian-agent-M352CBE5.js +19 -0
- package/dist/index.js +1025 -1550
- package/dist/index.js.map +1 -1
- package/dist/{issue-store-DXIOP6AK.js → issue-store-W2X33X2X.js} +4 -4
- package/dist/{progress-LHI66U7B.js → progress-PQVEM7BR.js} +2 -2
- package/dist/{vibe-code-signatures-C5A4BHXD.js → vibe-code-signatures-ELEWJFGZ.js} +3 -3
- package/dist/{vulnerability-signatures-SVIHJQO5.js → vulnerability-signatures-EIJQX2TS.js} +3 -3
- package/dist/workers/agent-worker.js +2 -11
- package/dist/workers/agent-worker.js.map +1 -1
- package/package.json +2 -2
- package/dist/agent-smith-MYQ35URL.js +0 -14
- package/dist/agent-smith-runner-4TBONXCP.js +0 -573
- package/dist/agent-smith-runner-4TBONXCP.js.map +0 -1
- package/dist/cache-manager-RMPRPD5T.js +0 -10
- package/dist/chunk-AZRCKBGF.js.map +0 -1
- package/dist/chunk-CKM6A3G6.js.map +0 -1
- package/dist/chunk-E2ZATINO.js +0 -10879
- package/dist/chunk-E2ZATINO.js.map +0 -1
- package/dist/chunk-FFWNZUG2.js +0 -266
- package/dist/chunk-FFWNZUG2.js.map +0 -1
- package/dist/chunk-FK6DQKDY.js +0 -175
- package/dist/chunk-FK6DQKDY.js.map +0 -1
- package/dist/chunk-IFGF33R5.js +0 -279
- package/dist/chunk-IFGF33R5.js.map +0 -1
- package/dist/chunk-IIF5XDCJ.js.map +0 -1
- package/dist/chunk-JAAIHNOE.js.map +0 -1
- package/dist/chunk-ODWDESYP.js +0 -141
- package/dist/chunk-ODWDESYP.js.map +0 -1
- package/dist/chunk-OWBWNXSC.js +0 -955
- package/dist/chunk-OWBWNXSC.js.map +0 -1
- package/dist/chunk-Q764X2WD.js +0 -2124
- package/dist/chunk-Q764X2WD.js.map +0 -1
- package/dist/chunk-RE6ZWXJC.js +0 -279
- package/dist/chunk-RE6ZWXJC.js.map +0 -1
- package/dist/chunk-RNJ6JKMA.js +0 -2270
- package/dist/chunk-RNJ6JKMA.js.map +0 -1
- package/dist/chunk-Y62VM3ER.js +0 -536
- package/dist/chunk-Y62VM3ER.js.map +0 -1
- package/dist/git-45LZUUYA.js +0 -29
- package/dist/guardian-agent-RB2UQP5V.js +0 -21
- package/dist/progress-LHI66U7B.js.map +0 -1
- package/dist/vibe-code-signatures-C5A4BHXD.js.map +0 -1
- package/dist/vulnerability-signatures-SVIHJQO5.js.map +0 -1
- /package/dist/{chunk-JO6RVXS6.js.map → chunk-F4NJ4CBP.js.map} +0 -0
- /package/dist/{chunk-STEFLYPR.js.map → chunk-IXO4G4D3.js.map} +0 -0
- /package/dist/{chunk-OEYIOOYB.js.map → chunk-JDHR5BDR.js.map} +0 -0
- /package/dist/{chunk-RYRVEO2B.js.map → chunk-R3I2GCZC.js.map} +0 -0
- /package/dist/{chunk-WT3XQCG2.js.map → chunk-R4AAPFXC.js.map} +0 -0
- /package/dist/{agent-smith-MYQ35URL.js.map → goal-manager-LAOT4QQX.js.map} +0 -0
- /package/dist/{cache-manager-RMPRPD5T.js.map → guardian-agent-M352CBE5.js.map} +0 -0
- /package/dist/{git-45LZUUYA.js.map → issue-store-W2X33X2X.js.map} +0 -0
- /package/dist/{goal-manager-HOZ7R2QV.js.map → progress-PQVEM7BR.js.map} +0 -0
- /package/dist/{guardian-agent-RB2UQP5V.js.map → vibe-code-signatures-ELEWJFGZ.js.map} +0 -0
- /package/dist/{issue-store-DXIOP6AK.js.map → vulnerability-signatures-EIJQX2TS.js.map} +0 -0
|
@@ -1,35 +1,32 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Executor,
|
|
3
|
-
Triager,
|
|
4
2
|
detectStack,
|
|
5
|
-
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import {
|
|
8
|
-
getDiff,
|
|
9
|
-
getRecentCommits,
|
|
10
|
-
getStagedChanges,
|
|
11
|
-
getUncommittedChanges,
|
|
12
|
-
getWorkingTreeDiff
|
|
13
|
-
} from "./chunk-FK6DQKDY.js";
|
|
3
|
+
runExecFile
|
|
4
|
+
} from "./chunk-HSNE46VE.js";
|
|
14
5
|
import {
|
|
15
6
|
ContextGraph
|
|
16
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-NIASHOAB.js";
|
|
17
8
|
import {
|
|
18
9
|
scanForVulnerabilities
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import {
|
|
21
|
-
storeIssues
|
|
22
|
-
} from "./chunk-OEYIOOYB.js";
|
|
10
|
+
} from "./chunk-F4NJ4CBP.js";
|
|
23
11
|
import {
|
|
24
12
|
scanForVibeCodeIssues
|
|
25
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-IXO4G4D3.js";
|
|
26
14
|
import {
|
|
27
15
|
Trie
|
|
28
16
|
} from "./chunk-6NLHFIYA.js";
|
|
17
|
+
import {
|
|
18
|
+
storeIssues
|
|
19
|
+
} from "./chunk-JDHR5BDR.js";
|
|
29
20
|
import {
|
|
30
21
|
getTrieDirectory,
|
|
31
22
|
getWorkingDirectory
|
|
32
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-R4AAPFXC.js";
|
|
24
|
+
import {
|
|
25
|
+
isInteractiveMode
|
|
26
|
+
} from "./chunk-APMV77PU.js";
|
|
27
|
+
import {
|
|
28
|
+
__require
|
|
29
|
+
} from "./chunk-DGUM43GV.js";
|
|
33
30
|
|
|
34
31
|
// src/cli/checkpoint.ts
|
|
35
32
|
import { existsSync } from "fs";
|
|
@@ -202,10 +199,449 @@ async function handleCheckpointCommand(args) {
|
|
|
202
199
|
}
|
|
203
200
|
}
|
|
204
201
|
|
|
205
|
-
// src/
|
|
202
|
+
// src/config/loader.ts
|
|
206
203
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
207
|
-
import { existsSync as
|
|
208
|
-
import { join as
|
|
204
|
+
import { existsSync as existsSync3 } from "fs";
|
|
205
|
+
import { join as join3, dirname } from "path";
|
|
206
|
+
|
|
207
|
+
// src/config/validation.ts
|
|
208
|
+
import { z } from "zod";
|
|
209
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
210
|
+
import { resolve, join as join2 } from "path";
|
|
211
|
+
var API_KEY_PATTERNS = {
|
|
212
|
+
anthropic: /^sk-ant-api\d{2}-[\w-]{95}$/,
|
|
213
|
+
openai: /^sk-[\w]{48}$/,
|
|
214
|
+
github: /^ghp_[\w]{36}$/,
|
|
215
|
+
vercel: /^[\w]{24}$/,
|
|
216
|
+
linear: /^lin_api_[\w]{40,60}$/
|
|
217
|
+
};
|
|
218
|
+
var ApiKeysSchema = z.object({
|
|
219
|
+
anthropic: z.string().regex(API_KEY_PATTERNS.anthropic, "Invalid Anthropic API key format").optional(),
|
|
220
|
+
openai: z.string().regex(API_KEY_PATTERNS.openai, "Invalid OpenAI API key format").optional(),
|
|
221
|
+
github: z.string().regex(API_KEY_PATTERNS.github, "Invalid GitHub token format").optional(),
|
|
222
|
+
vercel: z.string().regex(API_KEY_PATTERNS.vercel, "Invalid Vercel token format").optional(),
|
|
223
|
+
linear: z.string().optional()
|
|
224
|
+
// Linear keys can vary, so we'll be flexible but allow storage
|
|
225
|
+
});
|
|
226
|
+
var AgentConfigSchema = z.object({
|
|
227
|
+
enabled: z.array(z.string()).optional().default([]),
|
|
228
|
+
disabled: z.array(z.string()).optional().default([]),
|
|
229
|
+
parallel: z.boolean().optional().default(true),
|
|
230
|
+
maxConcurrency: z.number().int().min(1).max(20).optional().default(4),
|
|
231
|
+
timeout: z.number().int().min(1e3).max(3e5).optional().default(12e4),
|
|
232
|
+
// 2 minutes
|
|
233
|
+
cache: z.boolean().optional().default(true)
|
|
234
|
+
});
|
|
235
|
+
var ComplianceSchema = z.object({
|
|
236
|
+
standards: z.array(z.enum(["SOC2", "GDPR", "HIPAA", "CCPA", "PCI-DSS"])).optional().default(["SOC2"]),
|
|
237
|
+
enforceCompliance: z.boolean().optional().default(false),
|
|
238
|
+
reportFormat: z.enum(["json", "sarif", "csv", "html"]).optional().default("json")
|
|
239
|
+
});
|
|
240
|
+
var OutputSchema = z.object({
|
|
241
|
+
format: z.enum(["console", "json", "sarif", "junit"]).optional().default("console"),
|
|
242
|
+
level: z.enum(["critical", "serious", "moderate", "low", "all"]).optional().default("all"),
|
|
243
|
+
interactive: z.boolean().optional().default(false),
|
|
244
|
+
streaming: z.boolean().optional().default(true),
|
|
245
|
+
colors: z.boolean().optional().default(true)
|
|
246
|
+
});
|
|
247
|
+
var PathsSchema = z.object({
|
|
248
|
+
include: z.array(z.string()).optional().default([]),
|
|
249
|
+
exclude: z.array(z.string()).optional().default(["node_modules", "dist", "build", ".git"]),
|
|
250
|
+
configDir: z.string().optional().default(".trie"),
|
|
251
|
+
outputDir: z.string().optional().default("trie-reports")
|
|
252
|
+
});
|
|
253
|
+
var IntegrationsSchema = z.object({
|
|
254
|
+
github: z.object({
|
|
255
|
+
enabled: z.boolean().optional().default(false),
|
|
256
|
+
token: z.string().optional(),
|
|
257
|
+
webhook: z.string().url().optional()
|
|
258
|
+
}).optional(),
|
|
259
|
+
slack: z.object({
|
|
260
|
+
enabled: z.boolean().optional().default(false),
|
|
261
|
+
webhook: z.string().url().optional(),
|
|
262
|
+
channel: z.string().optional()
|
|
263
|
+
}).optional(),
|
|
264
|
+
jira: z.object({
|
|
265
|
+
enabled: z.boolean().optional().default(false),
|
|
266
|
+
url: z.string().url().optional(),
|
|
267
|
+
token: z.string().optional(),
|
|
268
|
+
project: z.string().optional()
|
|
269
|
+
}).optional()
|
|
270
|
+
});
|
|
271
|
+
var UserSchema = z.object({
|
|
272
|
+
name: z.string().min(1).optional(),
|
|
273
|
+
email: z.string().email().optional(),
|
|
274
|
+
role: z.enum([
|
|
275
|
+
"developer",
|
|
276
|
+
"designer",
|
|
277
|
+
"qa",
|
|
278
|
+
"devops",
|
|
279
|
+
"security",
|
|
280
|
+
"architect",
|
|
281
|
+
"manager",
|
|
282
|
+
"contributor"
|
|
283
|
+
]).optional().default("developer"),
|
|
284
|
+
github: z.string().optional(),
|
|
285
|
+
// GitHub username
|
|
286
|
+
url: z.string().url().optional()
|
|
287
|
+
// Personal/portfolio URL
|
|
288
|
+
});
|
|
289
|
+
var TrieConfigSchema = z.object({
|
|
290
|
+
version: z.string().optional().default("1.0.0"),
|
|
291
|
+
apiKeys: ApiKeysSchema.optional(),
|
|
292
|
+
agents: AgentConfigSchema.optional(),
|
|
293
|
+
compliance: ComplianceSchema.optional(),
|
|
294
|
+
output: OutputSchema.optional(),
|
|
295
|
+
paths: PathsSchema.optional(),
|
|
296
|
+
integrations: IntegrationsSchema.optional(),
|
|
297
|
+
user: UserSchema.optional()
|
|
298
|
+
// User identity for attribution
|
|
299
|
+
});
|
|
300
|
+
var ConfigValidator = class {
|
|
301
|
+
/**
|
|
302
|
+
* Validate configuration object
|
|
303
|
+
*/
|
|
304
|
+
validateConfig(config) {
|
|
305
|
+
try {
|
|
306
|
+
const validated = TrieConfigSchema.parse(config);
|
|
307
|
+
const businessErrors = this.validateBusinessLogic(validated);
|
|
308
|
+
if (businessErrors.length > 0) {
|
|
309
|
+
return { success: false, errors: businessErrors };
|
|
310
|
+
}
|
|
311
|
+
return { success: true, data: validated };
|
|
312
|
+
} catch (error) {
|
|
313
|
+
if (error instanceof z.ZodError) {
|
|
314
|
+
const errors = error.errors.map(
|
|
315
|
+
(err) => `${err.path.join(".")}: ${err.message}`
|
|
316
|
+
);
|
|
317
|
+
return { success: false, errors };
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
success: false,
|
|
321
|
+
errors: [`Configuration validation failed: ${error instanceof Error ? error.message : "Unknown error"}`]
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Validate environment variables for API keys
|
|
327
|
+
*/
|
|
328
|
+
validateEnvironment() {
|
|
329
|
+
const warnings = [];
|
|
330
|
+
const errors = [];
|
|
331
|
+
const exposedPatterns = [
|
|
332
|
+
"NEXT_PUBLIC_ANTHROPIC",
|
|
333
|
+
"REACT_APP_ANTHROPIC",
|
|
334
|
+
"VITE_ANTHROPIC",
|
|
335
|
+
"PUBLIC_ANTHROPIC"
|
|
336
|
+
];
|
|
337
|
+
for (const pattern of exposedPatterns) {
|
|
338
|
+
const envVars = Object.keys(process.env).filter((key) => key.includes(pattern));
|
|
339
|
+
for (const envVar of envVars) {
|
|
340
|
+
errors.push(`[!] Security risk: API key in client-side environment variable: ${envVar}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
let anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
344
|
+
if (!anthropicKey) {
|
|
345
|
+
try {
|
|
346
|
+
const configPath = join2(getTrieDirectory(getWorkingDirectory(void 0, true)), "config.json");
|
|
347
|
+
if (existsSync2(configPath)) {
|
|
348
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
349
|
+
anthropicKey = config.apiKeys?.anthropic;
|
|
350
|
+
}
|
|
351
|
+
} catch {
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (anthropicKey && !API_KEY_PATTERNS.anthropic.test(anthropicKey)) {
|
|
355
|
+
errors.push("ANTHROPIC_API_KEY does not match expected format");
|
|
356
|
+
}
|
|
357
|
+
if (!anthropicKey) {
|
|
358
|
+
warnings.push("ANTHROPIC_API_KEY not set - AI features will be disabled. Set in environment, .trie/config.json, or .env file");
|
|
359
|
+
}
|
|
360
|
+
if (!process.env.GITHUB_TOKEN && process.env.CI) {
|
|
361
|
+
warnings.push("GITHUB_TOKEN not set - GitHub integration disabled");
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
valid: errors.length === 0,
|
|
365
|
+
warnings,
|
|
366
|
+
errors
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Validate file paths in configuration
|
|
371
|
+
*/
|
|
372
|
+
validatePaths(paths) {
|
|
373
|
+
const errors = [];
|
|
374
|
+
if (paths?.include) {
|
|
375
|
+
for (const path8 of paths.include) {
|
|
376
|
+
const resolvedPath = resolve(path8);
|
|
377
|
+
if (!existsSync2(resolvedPath)) {
|
|
378
|
+
errors.push(`Include path does not exist: ${path8}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (paths?.configDir) {
|
|
383
|
+
const configPath = resolve(paths.configDir);
|
|
384
|
+
if (!existsSync2(configPath)) {
|
|
385
|
+
try {
|
|
386
|
+
__require("fs").mkdirSync(configPath, { recursive: true });
|
|
387
|
+
} catch {
|
|
388
|
+
errors.push(`Cannot create config directory: ${paths.configDir}`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
valid: errors.length === 0,
|
|
394
|
+
errors
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Validate integration configurations
|
|
399
|
+
*/
|
|
400
|
+
validateIntegrations(integrations) {
|
|
401
|
+
const errors = [];
|
|
402
|
+
if (integrations?.github?.enabled) {
|
|
403
|
+
if (!integrations.github.token) {
|
|
404
|
+
errors.push("GitHub integration enabled but no token provided");
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (integrations?.slack?.enabled) {
|
|
408
|
+
if (!integrations.slack.webhook) {
|
|
409
|
+
errors.push("Slack integration enabled but no webhook URL provided");
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (integrations?.jira?.enabled) {
|
|
413
|
+
if (!integrations.jira.url || !integrations.jira.token || !integrations.jira.project) {
|
|
414
|
+
errors.push("JIRA integration enabled but missing required fields (url, token, project)");
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
valid: errors.length === 0,
|
|
419
|
+
errors
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Business logic validation
|
|
424
|
+
*/
|
|
425
|
+
validateBusinessLogic(config) {
|
|
426
|
+
const errors = [];
|
|
427
|
+
if (config.agents?.enabled && config.agents?.disabled) {
|
|
428
|
+
const overlap = config.agents.enabled.filter(
|
|
429
|
+
(agent) => config.agents?.disabled?.includes(agent)
|
|
430
|
+
);
|
|
431
|
+
if (overlap.length > 0) {
|
|
432
|
+
errors.push(`Agents cannot be both enabled and disabled: ${overlap.join(", ")}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (config.agents?.maxConcurrency && config.agents.maxConcurrency > 10) {
|
|
436
|
+
errors.push("maxConcurrency should not exceed 10 for optimal performance");
|
|
437
|
+
}
|
|
438
|
+
if (config.compliance?.standards) {
|
|
439
|
+
const invalidStandards = config.compliance.standards.filter(
|
|
440
|
+
(standard) => !["SOC2", "GDPR", "HIPAA", "CCPA", "PCI-DSS"].includes(standard)
|
|
441
|
+
);
|
|
442
|
+
if (invalidStandards.length > 0) {
|
|
443
|
+
errors.push(`Invalid compliance standards: ${invalidStandards.join(", ")}`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (config.paths) {
|
|
447
|
+
const pathValidation = this.validatePaths(config.paths);
|
|
448
|
+
errors.push(...pathValidation.errors);
|
|
449
|
+
}
|
|
450
|
+
if (config.integrations) {
|
|
451
|
+
const integrationValidation = this.validateIntegrations(config.integrations);
|
|
452
|
+
errors.push(...integrationValidation.errors);
|
|
453
|
+
}
|
|
454
|
+
return errors;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Generate configuration template
|
|
458
|
+
*/
|
|
459
|
+
generateTemplate() {
|
|
460
|
+
return {
|
|
461
|
+
version: "1.0.0",
|
|
462
|
+
agents: {
|
|
463
|
+
enabled: ["security", "bugs", "types"],
|
|
464
|
+
disabled: [],
|
|
465
|
+
parallel: true,
|
|
466
|
+
maxConcurrency: 4,
|
|
467
|
+
timeout: 12e4,
|
|
468
|
+
cache: true
|
|
469
|
+
},
|
|
470
|
+
compliance: {
|
|
471
|
+
standards: ["SOC2"],
|
|
472
|
+
enforceCompliance: false,
|
|
473
|
+
reportFormat: "json"
|
|
474
|
+
},
|
|
475
|
+
output: {
|
|
476
|
+
format: "console",
|
|
477
|
+
level: "all",
|
|
478
|
+
interactive: false,
|
|
479
|
+
streaming: true,
|
|
480
|
+
colors: true
|
|
481
|
+
},
|
|
482
|
+
paths: {
|
|
483
|
+
include: [],
|
|
484
|
+
exclude: ["node_modules", "dist", "build", ".git"],
|
|
485
|
+
configDir: ".trie",
|
|
486
|
+
outputDir: "trie-reports"
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Validate and provide suggestions for improvement
|
|
492
|
+
*/
|
|
493
|
+
analyze(config) {
|
|
494
|
+
const suggestions = [];
|
|
495
|
+
const securityIssues = [];
|
|
496
|
+
const optimizations = [];
|
|
497
|
+
let score = 100;
|
|
498
|
+
let hasApiKey = Boolean(config.apiKeys?.anthropic || process.env.ANTHROPIC_API_KEY);
|
|
499
|
+
if (!hasApiKey) {
|
|
500
|
+
try {
|
|
501
|
+
const workDir = getWorkingDirectory(void 0, true);
|
|
502
|
+
const envFiles = [".env", ".env.local", ".env.production"];
|
|
503
|
+
for (const envFile of envFiles) {
|
|
504
|
+
const envPath = join2(workDir, envFile);
|
|
505
|
+
if (existsSync2(envPath)) {
|
|
506
|
+
const envContent = readFileSync(envPath, "utf-8");
|
|
507
|
+
if (envContent.includes("ANTHROPIC_API_KEY=")) {
|
|
508
|
+
hasApiKey = true;
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
} catch {
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (!hasApiKey) {
|
|
517
|
+
suggestions.push("Add ANTHROPIC_API_KEY to enable AI-powered analysis for better issue detection. Set in environment, .trie/config.json, or .env file");
|
|
518
|
+
score -= 10;
|
|
519
|
+
}
|
|
520
|
+
if (config.agents?.parallel === false) {
|
|
521
|
+
optimizations.push("Enable parallel agent execution for 3-5x faster scans");
|
|
522
|
+
score -= 15;
|
|
523
|
+
}
|
|
524
|
+
if (config.agents?.cache === false) {
|
|
525
|
+
optimizations.push("Enable result caching to speed up repeated scans");
|
|
526
|
+
score -= 10;
|
|
527
|
+
}
|
|
528
|
+
if (!config.compliance?.standards || config.compliance.standards.length === 0) {
|
|
529
|
+
suggestions.push("Configure compliance standards (SOC2, GDPR, etc.) for regulatory requirements");
|
|
530
|
+
score -= 5;
|
|
531
|
+
}
|
|
532
|
+
const hasIntegrations = config.integrations && (config.integrations.github?.enabled || config.integrations.slack?.enabled || config.integrations.jira?.enabled);
|
|
533
|
+
if (!hasIntegrations) {
|
|
534
|
+
suggestions.push("Consider enabling GitHub/Slack/JIRA integrations for better team collaboration");
|
|
535
|
+
score -= 5;
|
|
536
|
+
}
|
|
537
|
+
if (config.apiKeys) {
|
|
538
|
+
securityIssues.push("API keys in config file - consider using environment variables instead");
|
|
539
|
+
score -= 20;
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
score: Math.max(0, score),
|
|
543
|
+
suggestions,
|
|
544
|
+
securityIssues,
|
|
545
|
+
optimizations
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
var DEFAULT_CONFIG = {
|
|
550
|
+
version: "1.0.0",
|
|
551
|
+
agents: {
|
|
552
|
+
enabled: [],
|
|
553
|
+
disabled: [],
|
|
554
|
+
parallel: true,
|
|
555
|
+
maxConcurrency: 4,
|
|
556
|
+
timeout: 12e4,
|
|
557
|
+
cache: true
|
|
558
|
+
},
|
|
559
|
+
compliance: {
|
|
560
|
+
standards: ["SOC2"],
|
|
561
|
+
enforceCompliance: false,
|
|
562
|
+
reportFormat: "json"
|
|
563
|
+
},
|
|
564
|
+
output: {
|
|
565
|
+
format: "console",
|
|
566
|
+
level: "all",
|
|
567
|
+
interactive: false,
|
|
568
|
+
streaming: true,
|
|
569
|
+
colors: true
|
|
570
|
+
},
|
|
571
|
+
paths: {
|
|
572
|
+
include: [],
|
|
573
|
+
exclude: ["node_modules", "dist", "build", ".git", ".next", ".nuxt", "coverage"],
|
|
574
|
+
configDir: ".trie",
|
|
575
|
+
outputDir: "trie-reports"
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
// src/config/loader.ts
|
|
580
|
+
async function loadConfig() {
|
|
581
|
+
const validator = new ConfigValidator();
|
|
582
|
+
const configPath = join3(getTrieDirectory(getWorkingDirectory(void 0, true)), "config.json");
|
|
583
|
+
try {
|
|
584
|
+
if (!existsSync3(configPath)) {
|
|
585
|
+
return DEFAULT_CONFIG;
|
|
586
|
+
}
|
|
587
|
+
const configFile = await readFile2(configPath, "utf-8");
|
|
588
|
+
const userConfig = JSON.parse(configFile);
|
|
589
|
+
const merged = mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
590
|
+
const result = validator.validateConfig(merged);
|
|
591
|
+
if (!result.success) {
|
|
592
|
+
if (!isInteractiveMode()) {
|
|
593
|
+
console.error("Configuration validation failed:");
|
|
594
|
+
for (const error of result.errors) {
|
|
595
|
+
console.error(` - ${error}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return DEFAULT_CONFIG;
|
|
599
|
+
}
|
|
600
|
+
if (!isInteractiveMode()) {
|
|
601
|
+
const envValidation = validator.validateEnvironment();
|
|
602
|
+
for (const warning of envValidation.warnings) {
|
|
603
|
+
console.warn(warning);
|
|
604
|
+
}
|
|
605
|
+
for (const error of envValidation.errors) {
|
|
606
|
+
console.error(error);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return result.data;
|
|
610
|
+
} catch (error) {
|
|
611
|
+
if (!isInteractiveMode()) {
|
|
612
|
+
console.error("Failed to load config, using defaults:", error);
|
|
613
|
+
}
|
|
614
|
+
return DEFAULT_CONFIG;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
async function saveConfig(config) {
|
|
618
|
+
const configPath = join3(getTrieDirectory(getWorkingDirectory(void 0, true)), "config.json");
|
|
619
|
+
const dir = dirname(configPath);
|
|
620
|
+
if (!existsSync3(dir)) {
|
|
621
|
+
await mkdir2(dir, { recursive: true });
|
|
622
|
+
}
|
|
623
|
+
await writeFile2(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
624
|
+
}
|
|
625
|
+
function mergeConfig(defaults, user) {
|
|
626
|
+
if (typeof user !== "object" || user === null || Array.isArray(user)) {
|
|
627
|
+
return { ...defaults };
|
|
628
|
+
}
|
|
629
|
+
const result = { ...defaults };
|
|
630
|
+
for (const [key, value] of Object.entries(user)) {
|
|
631
|
+
const defaultValue = defaults[key];
|
|
632
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && typeof defaultValue === "object" && defaultValue !== null) {
|
|
633
|
+
result[key] = mergeConfig(defaultValue, value);
|
|
634
|
+
} else {
|
|
635
|
+
result[key] = value;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return result;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// src/utils/autonomy-config.ts
|
|
642
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
643
|
+
import { existsSync as existsSync4 } from "fs";
|
|
644
|
+
import { join as join4 } from "path";
|
|
209
645
|
|
|
210
646
|
// src/types/autonomy.ts
|
|
211
647
|
var DEFAULT_AUTONOMY_CONFIG = {
|
|
@@ -252,12 +688,12 @@ var DEFAULT_AUTONOMY_CONFIG = {
|
|
|
252
688
|
// src/utils/autonomy-config.ts
|
|
253
689
|
import { createHash } from "crypto";
|
|
254
690
|
async function loadAutonomyConfig(projectPath) {
|
|
255
|
-
const configPath =
|
|
691
|
+
const configPath = join4(getTrieDirectory(projectPath), "config.json");
|
|
256
692
|
try {
|
|
257
|
-
if (!
|
|
693
|
+
if (!existsSync4(configPath)) {
|
|
258
694
|
return { ...DEFAULT_AUTONOMY_CONFIG };
|
|
259
695
|
}
|
|
260
|
-
const content = await
|
|
696
|
+
const content = await readFile3(configPath, "utf-8");
|
|
261
697
|
const config = JSON.parse(content);
|
|
262
698
|
return mergeWithDefaults(config.autonomy || {});
|
|
263
699
|
} catch (error) {
|
|
@@ -295,11 +731,11 @@ async function getOccurrences(projectPath) {
|
|
|
295
731
|
if (occurrenceCache.has(projectPath)) {
|
|
296
732
|
return occurrenceCache.get(projectPath);
|
|
297
733
|
}
|
|
298
|
-
const occurrencesPath =
|
|
734
|
+
const occurrencesPath = join4(getTrieDirectory(projectPath), "memory", "occurrences.json");
|
|
299
735
|
const occurrences = /* @__PURE__ */ new Map();
|
|
300
736
|
try {
|
|
301
|
-
if (
|
|
302
|
-
const content = await
|
|
737
|
+
if (existsSync4(occurrencesPath)) {
|
|
738
|
+
const content = await readFile3(occurrencesPath, "utf-8");
|
|
303
739
|
const data = JSON.parse(content);
|
|
304
740
|
for (const [hash, occ] of Object.entries(data)) {
|
|
305
741
|
occurrences.set(hash, occ);
|
|
@@ -313,17 +749,17 @@ async function getOccurrences(projectPath) {
|
|
|
313
749
|
async function saveOccurrences(projectPath) {
|
|
314
750
|
const occurrences = occurrenceCache.get(projectPath);
|
|
315
751
|
if (!occurrences) return;
|
|
316
|
-
const occurrencesPath =
|
|
317
|
-
const memoryDir =
|
|
752
|
+
const occurrencesPath = join4(getTrieDirectory(projectPath), "memory", "occurrences.json");
|
|
753
|
+
const memoryDir = join4(getTrieDirectory(projectPath), "memory");
|
|
318
754
|
try {
|
|
319
|
-
if (!
|
|
320
|
-
await
|
|
755
|
+
if (!existsSync4(memoryDir)) {
|
|
756
|
+
await mkdir3(memoryDir, { recursive: true });
|
|
321
757
|
}
|
|
322
758
|
const data = {};
|
|
323
759
|
for (const [hash, occ] of occurrences.entries()) {
|
|
324
760
|
data[hash] = occ;
|
|
325
761
|
}
|
|
326
|
-
await
|
|
762
|
+
await writeFile3(occurrencesPath, JSON.stringify(data, null, 2));
|
|
327
763
|
} catch (error) {
|
|
328
764
|
console.error("Failed to save occurrences:", error);
|
|
329
765
|
}
|
|
@@ -470,8 +906,93 @@ async function getAutonomyConfig(projectPath) {
|
|
|
470
906
|
return config;
|
|
471
907
|
}
|
|
472
908
|
|
|
473
|
-
// src/agent/
|
|
909
|
+
// src/agent/git.ts
|
|
910
|
+
import { existsSync as existsSync5 } from "fs";
|
|
474
911
|
import path from "path";
|
|
912
|
+
async function execGit(args, cwd) {
|
|
913
|
+
try {
|
|
914
|
+
const { stdout } = await runExecFile(
|
|
915
|
+
"git",
|
|
916
|
+
["-C", cwd, ...args],
|
|
917
|
+
{ actor: "internal:git", triggeredBy: "manual", targetPath: cwd },
|
|
918
|
+
{ maxBuffer: 10 * 1024 * 1024, captureOutput: false }
|
|
919
|
+
);
|
|
920
|
+
return stdout.trim();
|
|
921
|
+
} catch (error) {
|
|
922
|
+
const stderr = error?.stderr?.toString();
|
|
923
|
+
if (stderr?.includes("not a git repository") || stderr?.includes("does not have any commits")) {
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
throw error;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
async function ensureRepo(projectPath) {
|
|
930
|
+
const result = await execGit(["rev-parse", "--is-inside-work-tree"], projectPath);
|
|
931
|
+
return result === "true";
|
|
932
|
+
}
|
|
933
|
+
function parseNameStatus(output) {
|
|
934
|
+
return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
935
|
+
const parts = line.split(" ");
|
|
936
|
+
const status = parts[0] ?? "";
|
|
937
|
+
const filePath = parts[1] ?? "";
|
|
938
|
+
const oldPath = parts[2];
|
|
939
|
+
const change = { status, path: filePath };
|
|
940
|
+
if (oldPath) change.oldPath = oldPath;
|
|
941
|
+
return change;
|
|
942
|
+
}).filter((entry) => entry.path.length > 0);
|
|
943
|
+
}
|
|
944
|
+
async function getRecentCommits(projectPath, limit) {
|
|
945
|
+
const isRepo = await ensureRepo(projectPath);
|
|
946
|
+
if (!isRepo) return [];
|
|
947
|
+
const output = await execGit(
|
|
948
|
+
["log", `-n`, String(limit), "--pretty=format:%H%x09%an%x09%ad%x09%s", "--date=iso"],
|
|
949
|
+
projectPath
|
|
950
|
+
);
|
|
951
|
+
if (!output) return [];
|
|
952
|
+
return output.split("\n").map((line) => {
|
|
953
|
+
const [hash, author, date, message] = line.split(" ");
|
|
954
|
+
return { hash, author, date, message };
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
async function getStagedChanges(projectPath) {
|
|
958
|
+
const isRepo = await ensureRepo(projectPath);
|
|
959
|
+
if (!isRepo) return [];
|
|
960
|
+
const output = await execGit(["diff", "--cached", "--name-status"], projectPath);
|
|
961
|
+
if (!output) return [];
|
|
962
|
+
return parseNameStatus(output);
|
|
963
|
+
}
|
|
964
|
+
async function getUncommittedChanges(projectPath) {
|
|
965
|
+
const isRepo = await ensureRepo(projectPath);
|
|
966
|
+
if (!isRepo) return [];
|
|
967
|
+
const changes = [];
|
|
968
|
+
const unstaged = await execGit(["diff", "--name-status"], projectPath);
|
|
969
|
+
if (unstaged) {
|
|
970
|
+
changes.push(...parseNameStatus(unstaged));
|
|
971
|
+
}
|
|
972
|
+
const untracked = await execGit(["ls-files", "--others", "--exclude-standard"], projectPath);
|
|
973
|
+
if (untracked) {
|
|
974
|
+
changes.push(
|
|
975
|
+
...untracked.split("\n").map((p) => p.trim()).filter(Boolean).map((p) => ({ status: "??", path: p }))
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
return changes;
|
|
979
|
+
}
|
|
980
|
+
async function getDiff(projectPath, commitHash) {
|
|
981
|
+
const isRepo = await ensureRepo(projectPath);
|
|
982
|
+
if (!isRepo) return "";
|
|
983
|
+
const diff = await execGit(["show", commitHash, "--unified=3", "--no-color"], projectPath);
|
|
984
|
+
return diff ?? "";
|
|
985
|
+
}
|
|
986
|
+
async function getWorkingTreeDiff(projectPath, stagedOnly = false) {
|
|
987
|
+
const isRepo = await ensureRepo(projectPath);
|
|
988
|
+
if (!isRepo) return "";
|
|
989
|
+
const args = stagedOnly ? ["diff", "--cached", "--unified=3", "--no-color"] : ["diff", "--unified=3", "--no-color"];
|
|
990
|
+
const diff = await execGit(args, projectPath);
|
|
991
|
+
return diff ?? "";
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// src/agent/perceive.ts
|
|
995
|
+
import path2 from "path";
|
|
475
996
|
|
|
476
997
|
// src/agent/diff-analyzer.ts
|
|
477
998
|
var RISKY_PATTERNS = [/auth/i, /token/i, /password/i, /secret/i, /validate/i, /sanitize/i];
|
|
@@ -575,7 +1096,7 @@ async function upsertWorkingChange(graph, files, projectPath) {
|
|
|
575
1096
|
return change.id;
|
|
576
1097
|
}
|
|
577
1098
|
async function ensureFileNode(graph, filePath, projectPath) {
|
|
578
|
-
const normalized =
|
|
1099
|
+
const normalized = path2.resolve(projectPath, filePath);
|
|
579
1100
|
const existing = await graph.getNode("file", normalized);
|
|
580
1101
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
581
1102
|
if (existing) {
|
|
@@ -588,7 +1109,7 @@ async function ensureFileNode(graph, filePath, projectPath) {
|
|
|
588
1109
|
}
|
|
589
1110
|
const data = {
|
|
590
1111
|
path: filePath,
|
|
591
|
-
extension:
|
|
1112
|
+
extension: path2.extname(filePath),
|
|
592
1113
|
purpose: "",
|
|
593
1114
|
riskLevel: "medium",
|
|
594
1115
|
whyRisky: null,
|
|
@@ -601,7 +1122,7 @@ async function ensureFileNode(graph, filePath, projectPath) {
|
|
|
601
1122
|
}
|
|
602
1123
|
|
|
603
1124
|
// src/agent/risk-scorer.ts
|
|
604
|
-
import
|
|
1125
|
+
import path3 from "path";
|
|
605
1126
|
var BASE_RISK = {
|
|
606
1127
|
low: 10,
|
|
607
1128
|
medium: 35,
|
|
@@ -611,8 +1132,7 @@ var BASE_RISK = {
|
|
|
611
1132
|
var SENSITIVE_PATHS = [
|
|
612
1133
|
{ pattern: /auth|login|token|session/i, weight: 20, reason: "touches authentication" },
|
|
613
1134
|
{ pattern: /payment|billing|stripe|paypal|checkout/i, weight: 25, reason: "touches payments" },
|
|
614
|
-
{ pattern: /secret|credential|env|config\/security/i, weight: 15, reason: "touches secrets/security config" }
|
|
615
|
-
{ pattern: /gdpr|privacy|pii|phi/i, weight: 15, reason: "touches sensitive data" }
|
|
1135
|
+
{ pattern: /secret|credential|env|config\/security/i, weight: 15, reason: "touches secrets/security config" }
|
|
616
1136
|
];
|
|
617
1137
|
function levelFromScore(score) {
|
|
618
1138
|
if (score >= 90) return "critical";
|
|
@@ -622,7 +1142,7 @@ function levelFromScore(score) {
|
|
|
622
1142
|
}
|
|
623
1143
|
async function scoreFile(graph, filePath, matchedPatterns = []) {
|
|
624
1144
|
const reasons = [];
|
|
625
|
-
const normalized =
|
|
1145
|
+
const normalized = path3.resolve(graph.projectRoot, filePath);
|
|
626
1146
|
const node = await graph.getNode("file", normalized);
|
|
627
1147
|
const incidents = await graph.getIncidentsForFile(filePath);
|
|
628
1148
|
let score = 10;
|
|
@@ -724,6 +1244,629 @@ async function matchPatternsForFiles(graph, files) {
|
|
|
724
1244
|
return { matches, byFile };
|
|
725
1245
|
}
|
|
726
1246
|
|
|
1247
|
+
// src/orchestrator/triager.ts
|
|
1248
|
+
var Triager = class {
|
|
1249
|
+
constructor(_config) {
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Triage a change to select appropriate agents
|
|
1253
|
+
* Note: Skills/agents have been removed - Trie is now purely a decision ledger
|
|
1254
|
+
*/
|
|
1255
|
+
async triage(_context, _forceAgents) {
|
|
1256
|
+
return [];
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Get all available agent names (deprecated - returns empty array)
|
|
1260
|
+
*/
|
|
1261
|
+
getAvailableAgents() {
|
|
1262
|
+
return [];
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
// src/utils/parallel-executor.ts
|
|
1267
|
+
import { Worker } from "worker_threads";
|
|
1268
|
+
import { cpus } from "os";
|
|
1269
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1270
|
+
import { fileURLToPath } from "url";
|
|
1271
|
+
var ParallelExecutor = class {
|
|
1272
|
+
maxWorkers;
|
|
1273
|
+
cache;
|
|
1274
|
+
streaming;
|
|
1275
|
+
activeWorkers = /* @__PURE__ */ new Set();
|
|
1276
|
+
cacheEnabled = true;
|
|
1277
|
+
useWorkerThreads = false;
|
|
1278
|
+
workerAvailable = null;
|
|
1279
|
+
warnedWorkerFallback = false;
|
|
1280
|
+
constructor(cacheManager, maxWorkers = Math.max(2, Math.min(cpus().length - 1, 8)), options) {
|
|
1281
|
+
this.maxWorkers = maxWorkers;
|
|
1282
|
+
this.cache = cacheManager;
|
|
1283
|
+
this.cacheEnabled = options?.cacheEnabled ?? true;
|
|
1284
|
+
this.useWorkerThreads = options?.useWorkerThreads ?? false;
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Set streaming manager for real-time updates
|
|
1288
|
+
*/
|
|
1289
|
+
setStreaming(streaming) {
|
|
1290
|
+
this.streaming = streaming;
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Execute agents in parallel with intelligent scheduling
|
|
1294
|
+
*/
|
|
1295
|
+
async executeAgents(agents, files, context) {
|
|
1296
|
+
if (agents.length === 0) {
|
|
1297
|
+
return /* @__PURE__ */ new Map();
|
|
1298
|
+
}
|
|
1299
|
+
if (this.streaming && this.streaming.getProgress().totalFiles === 0) {
|
|
1300
|
+
this.streaming.startScan(files.length);
|
|
1301
|
+
}
|
|
1302
|
+
const cacheResults = /* @__PURE__ */ new Map();
|
|
1303
|
+
const uncachedTasks = [];
|
|
1304
|
+
for (const agent of agents) {
|
|
1305
|
+
const cached = await this.checkAgentCache(agent, files);
|
|
1306
|
+
if (cached) {
|
|
1307
|
+
cacheResults.set(agent.name, cached);
|
|
1308
|
+
this.streaming?.completeAgent(agent.name, cached.issues);
|
|
1309
|
+
} else {
|
|
1310
|
+
uncachedTasks.push({
|
|
1311
|
+
agent,
|
|
1312
|
+
files,
|
|
1313
|
+
context,
|
|
1314
|
+
priority: agent.priority?.tier || 2,
|
|
1315
|
+
timeoutMs: context?.config?.timeoutMs || 12e4
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
uncachedTasks.sort((a, b) => a.priority - b.priority);
|
|
1320
|
+
const parallelResults = await this.executeTasksParallel(uncachedTasks);
|
|
1321
|
+
await this.cacheResults(parallelResults);
|
|
1322
|
+
const allResults = /* @__PURE__ */ new Map();
|
|
1323
|
+
for (const [agent, result] of cacheResults) {
|
|
1324
|
+
allResults.set(agent, result);
|
|
1325
|
+
}
|
|
1326
|
+
for (const result of parallelResults) {
|
|
1327
|
+
allResults.set(result.agent, result.result);
|
|
1328
|
+
}
|
|
1329
|
+
const allIssues = Array.from(allResults.values()).flatMap((r) => r.issues);
|
|
1330
|
+
this.streaming?.completeScan(allIssues);
|
|
1331
|
+
return allResults;
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Check if agent has cached results for given files
|
|
1335
|
+
*/
|
|
1336
|
+
async checkAgentCache(agent, files) {
|
|
1337
|
+
if (!this.cacheEnabled || !this.cache) {
|
|
1338
|
+
return null;
|
|
1339
|
+
}
|
|
1340
|
+
const cachedIssues = await this.cache.getCachedBatch(files, agent.name);
|
|
1341
|
+
if (cachedIssues.size === files.length) {
|
|
1342
|
+
const allIssues = Array.from(cachedIssues.values()).flat();
|
|
1343
|
+
return {
|
|
1344
|
+
agent: agent.name,
|
|
1345
|
+
issues: allIssues,
|
|
1346
|
+
executionTime: 0,
|
|
1347
|
+
// Cached
|
|
1348
|
+
success: true,
|
|
1349
|
+
metadata: {
|
|
1350
|
+
filesAnalyzed: files.length,
|
|
1351
|
+
linesAnalyzed: 0
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
return null;
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Execute tasks in parallel batches
|
|
1359
|
+
*/
|
|
1360
|
+
async executeTasksParallel(tasks) {
|
|
1361
|
+
if (tasks.length === 0) {
|
|
1362
|
+
return [];
|
|
1363
|
+
}
|
|
1364
|
+
const results = [];
|
|
1365
|
+
const batches = this.createBatches(tasks, this.maxWorkers);
|
|
1366
|
+
for (const batch of batches) {
|
|
1367
|
+
const batchResults = await Promise.all(
|
|
1368
|
+
batch.map((task) => this.executeTask(task))
|
|
1369
|
+
);
|
|
1370
|
+
results.push(...batchResults);
|
|
1371
|
+
}
|
|
1372
|
+
return results;
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Create batches for parallel execution
|
|
1376
|
+
*/
|
|
1377
|
+
createBatches(tasks, batchSize) {
|
|
1378
|
+
const batches = [];
|
|
1379
|
+
for (let i = 0; i < tasks.length; i += batchSize) {
|
|
1380
|
+
batches.push(tasks.slice(i, i + batchSize));
|
|
1381
|
+
}
|
|
1382
|
+
return batches;
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Execute a single task
|
|
1386
|
+
*/
|
|
1387
|
+
async executeTask(task) {
|
|
1388
|
+
const startTime = Date.now();
|
|
1389
|
+
this.streaming?.startAgent(task.agent.name);
|
|
1390
|
+
try {
|
|
1391
|
+
const result = this.canUseWorkers() ? await this.executeTaskInWorker(task) : await task.agent.scan(task.files, task.context);
|
|
1392
|
+
const executionTime = Date.now() - startTime;
|
|
1393
|
+
this.streaming?.completeAgent(task.agent.name, result.issues);
|
|
1394
|
+
return {
|
|
1395
|
+
agent: task.agent.name,
|
|
1396
|
+
result,
|
|
1397
|
+
fromCache: false,
|
|
1398
|
+
executionTime
|
|
1399
|
+
};
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
const executionTime = Date.now() - startTime;
|
|
1402
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1403
|
+
this.streaming?.reportError(new Error(errorMessage), `Agent: ${task.agent.name}`);
|
|
1404
|
+
return {
|
|
1405
|
+
agent: task.agent.name,
|
|
1406
|
+
result: {
|
|
1407
|
+
agent: task.agent.name,
|
|
1408
|
+
issues: [],
|
|
1409
|
+
executionTime,
|
|
1410
|
+
success: false,
|
|
1411
|
+
error: errorMessage
|
|
1412
|
+
},
|
|
1413
|
+
fromCache: false,
|
|
1414
|
+
executionTime
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
canUseWorkers() {
|
|
1419
|
+
if (!this.useWorkerThreads) {
|
|
1420
|
+
return false;
|
|
1421
|
+
}
|
|
1422
|
+
if (this.workerAvailable !== null) {
|
|
1423
|
+
return this.workerAvailable;
|
|
1424
|
+
}
|
|
1425
|
+
const workerUrl = this.getWorkerUrl();
|
|
1426
|
+
this.workerAvailable = existsSync6(fileURLToPath(workerUrl));
|
|
1427
|
+
if (!this.workerAvailable && !this.warnedWorkerFallback && !isInteractiveMode()) {
|
|
1428
|
+
console.error("Worker threads unavailable; falling back to in-process agents.");
|
|
1429
|
+
this.warnedWorkerFallback = true;
|
|
1430
|
+
}
|
|
1431
|
+
return this.workerAvailable;
|
|
1432
|
+
}
|
|
1433
|
+
getWorkerUrl() {
|
|
1434
|
+
const distDir = new URL(".", import.meta.url);
|
|
1435
|
+
return new URL("workers/agent-worker.js", distDir);
|
|
1436
|
+
}
|
|
1437
|
+
async executeTaskInWorker(task) {
|
|
1438
|
+
const workerUrl = this.getWorkerUrl();
|
|
1439
|
+
return new Promise((resolve2, reject) => {
|
|
1440
|
+
const worker = new Worker(workerUrl, {
|
|
1441
|
+
workerData: {
|
|
1442
|
+
agentName: task.agent.name,
|
|
1443
|
+
files: task.files,
|
|
1444
|
+
context: task.context
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
this.activeWorkers.add(worker);
|
|
1448
|
+
const timeout = setTimeout(() => {
|
|
1449
|
+
worker.terminate().catch(() => void 0);
|
|
1450
|
+
reject(new Error(`Agent ${task.agent.name} timed out after ${task.timeoutMs}ms`));
|
|
1451
|
+
}, task.timeoutMs);
|
|
1452
|
+
worker.on("message", (message) => {
|
|
1453
|
+
if (message?.type === "result") {
|
|
1454
|
+
clearTimeout(timeout);
|
|
1455
|
+
resolve2(message.result);
|
|
1456
|
+
} else if (message?.type === "error") {
|
|
1457
|
+
clearTimeout(timeout);
|
|
1458
|
+
reject(new Error(message.error));
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
worker.on("error", (error) => {
|
|
1462
|
+
clearTimeout(timeout);
|
|
1463
|
+
reject(error);
|
|
1464
|
+
});
|
|
1465
|
+
worker.on("exit", (code) => {
|
|
1466
|
+
this.activeWorkers.delete(worker);
|
|
1467
|
+
if (code !== 0) {
|
|
1468
|
+
clearTimeout(timeout);
|
|
1469
|
+
reject(new Error(`Worker stopped with exit code ${code}`));
|
|
1470
|
+
}
|
|
1471
|
+
});
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Cache results for future use
|
|
1476
|
+
*/
|
|
1477
|
+
async cacheResults(results) {
|
|
1478
|
+
if (!this.cacheEnabled || !this.cache) {
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
const cachePromises = results.filter((r) => r.result.success && !r.fromCache).map((r) => {
|
|
1482
|
+
const issuesByFile = this.groupIssuesByFile(r.result.issues);
|
|
1483
|
+
const perFilePromises = Object.entries(issuesByFile).map(
|
|
1484
|
+
([file, issues]) => this.cache.setCached(file, r.agent, issues, r.executionTime)
|
|
1485
|
+
);
|
|
1486
|
+
return Promise.all(perFilePromises);
|
|
1487
|
+
});
|
|
1488
|
+
await Promise.allSettled(cachePromises);
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Cleanup resources
|
|
1492
|
+
*/
|
|
1493
|
+
async cleanup() {
|
|
1494
|
+
const terminationPromises = Array.from(this.activeWorkers).map(
|
|
1495
|
+
(worker) => worker.terminate()
|
|
1496
|
+
);
|
|
1497
|
+
await Promise.allSettled(terminationPromises);
|
|
1498
|
+
this.activeWorkers.clear();
|
|
1499
|
+
}
|
|
1500
|
+
groupIssuesByFile(issues) {
|
|
1501
|
+
const grouped = {};
|
|
1502
|
+
for (const issue of issues) {
|
|
1503
|
+
if (!grouped[issue.file]) {
|
|
1504
|
+
grouped[issue.file] = [];
|
|
1505
|
+
}
|
|
1506
|
+
grouped[issue.file].push(issue);
|
|
1507
|
+
}
|
|
1508
|
+
return grouped;
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
function calculateOptimalConcurrency() {
|
|
1512
|
+
const numCPUs = cpus().length;
|
|
1513
|
+
const availableMemoryGB = process.memoryUsage().rss / 1024 / 1024 / 1024;
|
|
1514
|
+
let optimal = Math.max(2, Math.min(numCPUs - 1, 8));
|
|
1515
|
+
if (availableMemoryGB < 2) {
|
|
1516
|
+
optimal = Math.max(2, Math.floor(optimal / 2));
|
|
1517
|
+
}
|
|
1518
|
+
if (numCPUs > 8) {
|
|
1519
|
+
optimal = Math.min(optimal + 2, 12);
|
|
1520
|
+
}
|
|
1521
|
+
return optimal;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// src/utils/cache-manager.ts
|
|
1525
|
+
import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4, stat } from "fs/promises";
|
|
1526
|
+
import { join as join5 } from "path";
|
|
1527
|
+
import { createHash as createHash2 } from "crypto";
|
|
1528
|
+
var CacheManager = class {
|
|
1529
|
+
cacheDir;
|
|
1530
|
+
indexPath;
|
|
1531
|
+
VERSION = "1.0.0";
|
|
1532
|
+
MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
1533
|
+
// 24 hours
|
|
1534
|
+
MAX_ENTRIES = 1e3;
|
|
1535
|
+
constructor(baseDir) {
|
|
1536
|
+
this.cacheDir = join5(getTrieDirectory(baseDir), "cache");
|
|
1537
|
+
this.indexPath = join5(this.cacheDir, "index.json");
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Generate cache key for a file and agent combination
|
|
1541
|
+
*/
|
|
1542
|
+
generateCacheKey(filePath, agent, fileHash) {
|
|
1543
|
+
const key = `${filePath}:${agent}:${fileHash}`;
|
|
1544
|
+
return createHash2("sha256").update(key).digest("hex").slice(0, 16);
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Get file hash for cache validation
|
|
1548
|
+
*/
|
|
1549
|
+
async getFileHash(filePath) {
|
|
1550
|
+
try {
|
|
1551
|
+
const content = await readFile4(filePath, "utf-8");
|
|
1552
|
+
const stats = await stat(filePath);
|
|
1553
|
+
const hash = createHash2("sha256").update(content).digest("hex").slice(0, 16);
|
|
1554
|
+
return {
|
|
1555
|
+
hash,
|
|
1556
|
+
size: stats.size,
|
|
1557
|
+
mtime: stats.mtime.getTime()
|
|
1558
|
+
};
|
|
1559
|
+
} catch {
|
|
1560
|
+
return { hash: "", size: 0, mtime: 0 };
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* Load cache index
|
|
1565
|
+
*/
|
|
1566
|
+
async loadIndex() {
|
|
1567
|
+
try {
|
|
1568
|
+
const content = await readFile4(this.indexPath, "utf-8");
|
|
1569
|
+
return JSON.parse(content);
|
|
1570
|
+
} catch {
|
|
1571
|
+
return {
|
|
1572
|
+
version: this.VERSION,
|
|
1573
|
+
created: Date.now(),
|
|
1574
|
+
entries: {}
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Save cache index
|
|
1580
|
+
*/
|
|
1581
|
+
async saveIndex(index) {
|
|
1582
|
+
try {
|
|
1583
|
+
await mkdir4(this.cacheDir, { recursive: true });
|
|
1584
|
+
await writeFile4(this.indexPath, JSON.stringify(index, null, 2));
|
|
1585
|
+
} catch (error) {
|
|
1586
|
+
if (!isInteractiveMode()) {
|
|
1587
|
+
console.warn("Failed to save cache index:", error);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Clean up expired entries
|
|
1593
|
+
*/
|
|
1594
|
+
cleanupExpired(index) {
|
|
1595
|
+
const now = Date.now();
|
|
1596
|
+
const validEntries = {};
|
|
1597
|
+
for (const [key, entry] of Object.entries(index.entries)) {
|
|
1598
|
+
if (now - entry.timestamp < this.MAX_AGE_MS) {
|
|
1599
|
+
validEntries[key] = entry;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
const entries = Object.entries(validEntries);
|
|
1603
|
+
if (entries.length > this.MAX_ENTRIES) {
|
|
1604
|
+
entries.sort((a, b) => b[1].timestamp - a[1].timestamp);
|
|
1605
|
+
const limited = entries.slice(0, this.MAX_ENTRIES);
|
|
1606
|
+
return {
|
|
1607
|
+
...index,
|
|
1608
|
+
entries: Object.fromEntries(limited)
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
return {
|
|
1612
|
+
...index,
|
|
1613
|
+
entries: validEntries
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Get cached result for a file and agent
|
|
1618
|
+
*
|
|
1619
|
+
* Cache automatically invalidates when files change:
|
|
1620
|
+
* - Cache key includes file hash: hash(filePath:agent:fileHash)
|
|
1621
|
+
* - When file changes, hash changes, so cache key changes
|
|
1622
|
+
* - Old cache entry won't be found (different key)
|
|
1623
|
+
* - File is automatically rescanned
|
|
1624
|
+
*
|
|
1625
|
+
* This means cache auto-updates when Claude fixes code - no manual invalidation needed!
|
|
1626
|
+
*/
|
|
1627
|
+
async getCached(filePath, agent) {
|
|
1628
|
+
try {
|
|
1629
|
+
const { hash, size: _size, mtime: _mtime } = await this.getFileHash(filePath);
|
|
1630
|
+
if (!hash) return null;
|
|
1631
|
+
const index = await this.loadIndex();
|
|
1632
|
+
const cacheKey = this.generateCacheKey(filePath, agent, hash);
|
|
1633
|
+
const entry = index.entries[cacheKey];
|
|
1634
|
+
if (!entry) return null;
|
|
1635
|
+
const isValid = entry.fileHash === hash && entry.version === this.VERSION && Date.now() - entry.timestamp < this.MAX_AGE_MS;
|
|
1636
|
+
if (!isValid) {
|
|
1637
|
+
delete index.entries[cacheKey];
|
|
1638
|
+
await this.saveIndex(index);
|
|
1639
|
+
return null;
|
|
1640
|
+
}
|
|
1641
|
+
return entry.issues;
|
|
1642
|
+
} catch {
|
|
1643
|
+
return null;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Cache result for a file and agent
|
|
1648
|
+
*/
|
|
1649
|
+
async setCached(filePath, agent, issues, executionTime) {
|
|
1650
|
+
try {
|
|
1651
|
+
const { hash, size } = await this.getFileHash(filePath);
|
|
1652
|
+
if (!hash) return;
|
|
1653
|
+
const index = await this.loadIndex();
|
|
1654
|
+
const cacheKey = this.generateCacheKey(filePath, agent, hash);
|
|
1655
|
+
index.entries[cacheKey] = {
|
|
1656
|
+
version: this.VERSION,
|
|
1657
|
+
timestamp: Date.now(),
|
|
1658
|
+
fileHash: hash,
|
|
1659
|
+
fileSize: size,
|
|
1660
|
+
agent,
|
|
1661
|
+
issues,
|
|
1662
|
+
executionTime
|
|
1663
|
+
};
|
|
1664
|
+
const cleanedIndex = this.cleanupExpired(index);
|
|
1665
|
+
await this.saveIndex(cleanedIndex);
|
|
1666
|
+
} catch (error) {
|
|
1667
|
+
if (!isInteractiveMode()) {
|
|
1668
|
+
console.warn("Failed to cache result:", error);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Check if multiple files have cached results
|
|
1674
|
+
*/
|
|
1675
|
+
async getCachedBatch(files, agent) {
|
|
1676
|
+
const results = /* @__PURE__ */ new Map();
|
|
1677
|
+
await Promise.all(
|
|
1678
|
+
files.map(async (file) => {
|
|
1679
|
+
const cached = await this.getCached(file, agent);
|
|
1680
|
+
if (cached) {
|
|
1681
|
+
results.set(file, cached);
|
|
1682
|
+
}
|
|
1683
|
+
})
|
|
1684
|
+
);
|
|
1685
|
+
return results;
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Get cache statistics
|
|
1689
|
+
*/
|
|
1690
|
+
async getStats() {
|
|
1691
|
+
try {
|
|
1692
|
+
const index = await this.loadIndex();
|
|
1693
|
+
const entries = Object.values(index.entries);
|
|
1694
|
+
const totalSizeKB = entries.reduce((acc, entry) => acc + entry.fileSize, 0) / 1024;
|
|
1695
|
+
const timestamps = entries.map((e) => e.timestamp);
|
|
1696
|
+
const agents = [...new Set(entries.map((e) => e.agent))];
|
|
1697
|
+
return {
|
|
1698
|
+
totalEntries: entries.length,
|
|
1699
|
+
totalSizeKB: Math.round(totalSizeKB),
|
|
1700
|
+
oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : null,
|
|
1701
|
+
newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : null,
|
|
1702
|
+
agents
|
|
1703
|
+
};
|
|
1704
|
+
} catch {
|
|
1705
|
+
return {
|
|
1706
|
+
totalEntries: 0,
|
|
1707
|
+
totalSizeKB: 0,
|
|
1708
|
+
oldestEntry: null,
|
|
1709
|
+
newestEntry: null,
|
|
1710
|
+
agents: []
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Clean up stale cache entries by verifying file hashes
|
|
1716
|
+
* This removes entries where files have changed or no longer exist
|
|
1717
|
+
* Called periodically to keep cache clean
|
|
1718
|
+
*
|
|
1719
|
+
* Note: Since cache keys are hashed, we can't easily reverse-engineer file paths.
|
|
1720
|
+
* However, when getCached() is called, it naturally invalidates stale entries
|
|
1721
|
+
* by checking if the current file hash matches the cached hash. This method
|
|
1722
|
+
* proactively cleans up entries for known changed files.
|
|
1723
|
+
*/
|
|
1724
|
+
async cleanupStaleEntries(filePaths) {
|
|
1725
|
+
try {
|
|
1726
|
+
const index = await this.loadIndex();
|
|
1727
|
+
let removedCount = 0;
|
|
1728
|
+
const keysToRemove = [];
|
|
1729
|
+
if (filePaths && filePaths.length > 0) {
|
|
1730
|
+
const agents = /* @__PURE__ */ new Set();
|
|
1731
|
+
for (const entry of Object.values(index.entries)) {
|
|
1732
|
+
agents.add(entry.agent);
|
|
1733
|
+
}
|
|
1734
|
+
for (const filePath of filePaths) {
|
|
1735
|
+
try {
|
|
1736
|
+
const { hash: currentHash } = await this.getFileHash(filePath);
|
|
1737
|
+
if (!currentHash) {
|
|
1738
|
+
continue;
|
|
1739
|
+
}
|
|
1740
|
+
for (const agent of agents) {
|
|
1741
|
+
for (const [key, entry] of Object.entries(index.entries)) {
|
|
1742
|
+
if (entry.agent !== agent) continue;
|
|
1743
|
+
if (entry.fileHash !== currentHash) {
|
|
1744
|
+
const oldKey = this.generateCacheKey(filePath, agent, entry.fileHash);
|
|
1745
|
+
if (oldKey === key) {
|
|
1746
|
+
keysToRemove.push(key);
|
|
1747
|
+
removedCount++;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
} catch {
|
|
1753
|
+
continue;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
const uniqueKeys = [...new Set(keysToRemove)];
|
|
1758
|
+
for (const key of uniqueKeys) {
|
|
1759
|
+
delete index.entries[key];
|
|
1760
|
+
}
|
|
1761
|
+
if (removedCount > 0) {
|
|
1762
|
+
await this.saveIndex(index);
|
|
1763
|
+
}
|
|
1764
|
+
return removedCount;
|
|
1765
|
+
} catch (error) {
|
|
1766
|
+
if (!isInteractiveMode()) {
|
|
1767
|
+
console.warn("Failed to cleanup stale cache entries:", error);
|
|
1768
|
+
}
|
|
1769
|
+
return 0;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Clear all cache
|
|
1774
|
+
*/
|
|
1775
|
+
async clear() {
|
|
1776
|
+
try {
|
|
1777
|
+
const emptyIndex = {
|
|
1778
|
+
version: this.VERSION,
|
|
1779
|
+
created: Date.now(),
|
|
1780
|
+
entries: {}
|
|
1781
|
+
};
|
|
1782
|
+
await this.saveIndex(emptyIndex);
|
|
1783
|
+
} catch (error) {
|
|
1784
|
+
if (!isInteractiveMode()) {
|
|
1785
|
+
console.warn("Failed to clear cache:", error);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
};
|
|
1790
|
+
|
|
1791
|
+
// src/orchestrator/executor.ts
|
|
1792
|
+
var Executor = class {
|
|
1793
|
+
async executeAgents(agents, files, context, options) {
|
|
1794
|
+
const parallel = options?.parallel ?? true;
|
|
1795
|
+
const cacheEnabled = options?.cacheEnabled ?? true;
|
|
1796
|
+
const maxConcurrency = options?.maxConcurrency ?? calculateOptimalConcurrency();
|
|
1797
|
+
const useWorkerThreads = options?.useWorkerThreads ?? false;
|
|
1798
|
+
if (!isInteractiveMode()) {
|
|
1799
|
+
console.error(`Executing ${agents.length} scouts ${parallel ? "in parallel" : "sequentially"}...`);
|
|
1800
|
+
}
|
|
1801
|
+
if (parallel) {
|
|
1802
|
+
const cacheManager = cacheEnabled ? new CacheManager(context.workingDir) : null;
|
|
1803
|
+
const executor = new ParallelExecutor(cacheManager, maxConcurrency, {
|
|
1804
|
+
cacheEnabled,
|
|
1805
|
+
useWorkerThreads
|
|
1806
|
+
});
|
|
1807
|
+
if (options?.streaming) {
|
|
1808
|
+
executor.setStreaming(options.streaming);
|
|
1809
|
+
}
|
|
1810
|
+
const results = await executor.executeAgents(agents, files, {
|
|
1811
|
+
...context,
|
|
1812
|
+
config: { timeoutMs: options?.timeoutMs ?? 12e4 }
|
|
1813
|
+
});
|
|
1814
|
+
return agents.map((agent) => results.get(agent.name)).filter(Boolean);
|
|
1815
|
+
}
|
|
1816
|
+
const promises = agents.map(
|
|
1817
|
+
(agent) => this.executeAgentWithTimeout(agent, files, context, options?.timeoutMs ?? 3e4)
|
|
1818
|
+
);
|
|
1819
|
+
try {
|
|
1820
|
+
const results = await Promise.allSettled(promises);
|
|
1821
|
+
return results.map((result, index) => {
|
|
1822
|
+
if (result.status === "fulfilled") {
|
|
1823
|
+
if (!isInteractiveMode()) {
|
|
1824
|
+
console.error(`${agents[index].name} completed in ${result.value.executionTime}ms`);
|
|
1825
|
+
}
|
|
1826
|
+
return result.value;
|
|
1827
|
+
} else {
|
|
1828
|
+
if (!isInteractiveMode()) {
|
|
1829
|
+
console.error(`${agents[index].name} failed:`, result.reason);
|
|
1830
|
+
}
|
|
1831
|
+
return {
|
|
1832
|
+
agent: agents[index].name,
|
|
1833
|
+
issues: [],
|
|
1834
|
+
executionTime: 0,
|
|
1835
|
+
success: false,
|
|
1836
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
});
|
|
1840
|
+
} catch (error) {
|
|
1841
|
+
if (!isInteractiveMode()) {
|
|
1842
|
+
console.error("Executor error:", error);
|
|
1843
|
+
}
|
|
1844
|
+
return agents.map((agent) => ({
|
|
1845
|
+
agent: agent.name,
|
|
1846
|
+
issues: [],
|
|
1847
|
+
executionTime: 0,
|
|
1848
|
+
success: false,
|
|
1849
|
+
error: "Execution failed"
|
|
1850
|
+
}));
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
async executeAgentWithTimeout(agent, files, context, timeoutMs = 3e4) {
|
|
1854
|
+
return new Promise(async (resolve2, reject) => {
|
|
1855
|
+
const timeout = setTimeout(() => {
|
|
1856
|
+
reject(new Error(`Agent ${agent.name} timed out after ${timeoutMs}ms`));
|
|
1857
|
+
}, timeoutMs);
|
|
1858
|
+
try {
|
|
1859
|
+
const result = await agent.scan(files, context);
|
|
1860
|
+
clearTimeout(timeout);
|
|
1861
|
+
resolve2(result);
|
|
1862
|
+
} catch (error) {
|
|
1863
|
+
clearTimeout(timeout);
|
|
1864
|
+
reject(error);
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
};
|
|
1869
|
+
|
|
727
1870
|
// src/agent/reason.ts
|
|
728
1871
|
function buildDefaultCodeContext() {
|
|
729
1872
|
return {
|
|
@@ -801,7 +1944,7 @@ async function reasonAboutChanges(projectPath, files, options = {}) {
|
|
|
801
1944
|
if (options.runAgents) {
|
|
802
1945
|
const codeContext = options.codeContext ?? buildDefaultCodeContext();
|
|
803
1946
|
const triager = new Triager();
|
|
804
|
-
const agents = await triager.
|
|
1947
|
+
const agents = await triager.triage(codeContext);
|
|
805
1948
|
if (agents.length > 0) {
|
|
806
1949
|
const executor = new Executor();
|
|
807
1950
|
const scanContext = {
|
|
@@ -827,9 +1970,9 @@ async function reasonAboutChangesHumanReadable(projectPath, files, options = {})
|
|
|
827
1970
|
}
|
|
828
1971
|
|
|
829
1972
|
// src/bootstrap/files.ts
|
|
830
|
-
import { readFile as
|
|
831
|
-
import { existsSync as
|
|
832
|
-
import { join as
|
|
1973
|
+
import { readFile as readFile5, writeFile as writeFile5, unlink, mkdir as mkdir5 } from "fs/promises";
|
|
1974
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1975
|
+
import { join as join6 } from "path";
|
|
833
1976
|
var BOOTSTRAP_FILES = [
|
|
834
1977
|
{ name: "PROJECT.md", type: "user", description: "Project overview and conventions" },
|
|
835
1978
|
{ name: "RULES.md", type: "user", description: "Coding standards agents enforce" },
|
|
@@ -844,15 +1987,15 @@ async function loadBootstrapContext(workDir) {
|
|
|
844
1987
|
const contentParts = [];
|
|
845
1988
|
let needsBootstrap2 = false;
|
|
846
1989
|
for (const file of BOOTSTRAP_FILES) {
|
|
847
|
-
const filePath =
|
|
848
|
-
const exists =
|
|
1990
|
+
const filePath = join6(trieDir, file.name);
|
|
1991
|
+
const exists = existsSync7(filePath);
|
|
849
1992
|
if (file.name === "BOOTSTRAP.md" && exists) {
|
|
850
1993
|
needsBootstrap2 = true;
|
|
851
1994
|
}
|
|
852
1995
|
let content;
|
|
853
1996
|
if (exists) {
|
|
854
1997
|
try {
|
|
855
|
-
content = await
|
|
1998
|
+
content = await readFile5(filePath, "utf-8");
|
|
856
1999
|
if (content.trim() && file.type !== "one-time") {
|
|
857
2000
|
contentParts.push(`<!-- ${file.name} -->
|
|
858
2001
|
${content}`);
|
|
@@ -878,13 +2021,13 @@ ${content}`);
|
|
|
878
2021
|
async function initializeBootstrapFiles(options = {}) {
|
|
879
2022
|
const projectDir = options.workDir || getWorkingDirectory(void 0, true);
|
|
880
2023
|
const trieDir = getTrieDirectory(projectDir);
|
|
881
|
-
await
|
|
2024
|
+
await mkdir5(trieDir, { recursive: true });
|
|
882
2025
|
const created = [];
|
|
883
2026
|
const skipped = [];
|
|
884
2027
|
const stack = await detectStack(projectDir);
|
|
885
2028
|
for (const file of BOOTSTRAP_FILES) {
|
|
886
|
-
const filePath =
|
|
887
|
-
const exists =
|
|
2029
|
+
const filePath = join6(trieDir, file.name);
|
|
2030
|
+
const exists = existsSync7(filePath);
|
|
888
2031
|
if (exists && !options.force) {
|
|
889
2032
|
skipped.push(file.name);
|
|
890
2033
|
continue;
|
|
@@ -899,7 +2042,7 @@ async function initializeBootstrapFiles(options = {}) {
|
|
|
899
2042
|
}
|
|
900
2043
|
const template = getFileTemplate(file.name, stack);
|
|
901
2044
|
if (template) {
|
|
902
|
-
await
|
|
2045
|
+
await writeFile5(filePath, template);
|
|
903
2046
|
created.push(file.name);
|
|
904
2047
|
}
|
|
905
2048
|
}
|
|
@@ -907,7 +2050,7 @@ async function initializeBootstrapFiles(options = {}) {
|
|
|
907
2050
|
}
|
|
908
2051
|
async function completeBootstrap(workDir) {
|
|
909
2052
|
const projectDir = workDir || getWorkingDirectory(void 0, true);
|
|
910
|
-
const bootstrapPath =
|
|
2053
|
+
const bootstrapPath = join6(getTrieDirectory(projectDir), "BOOTSTRAP.md");
|
|
911
2054
|
try {
|
|
912
2055
|
await unlink(bootstrapPath);
|
|
913
2056
|
return true;
|
|
@@ -917,15 +2060,15 @@ async function completeBootstrap(workDir) {
|
|
|
917
2060
|
}
|
|
918
2061
|
function needsBootstrap(workDir) {
|
|
919
2062
|
const projectDir = workDir || getWorkingDirectory(void 0, true);
|
|
920
|
-
const bootstrapPath =
|
|
921
|
-
return
|
|
2063
|
+
const bootstrapPath = join6(getTrieDirectory(projectDir), "BOOTSTRAP.md");
|
|
2064
|
+
return existsSync7(bootstrapPath);
|
|
922
2065
|
}
|
|
923
2066
|
async function loadRules(workDir) {
|
|
924
2067
|
const projectDir = workDir || getWorkingDirectory(void 0, true);
|
|
925
|
-
const rulesPath =
|
|
2068
|
+
const rulesPath = join6(getTrieDirectory(projectDir), "RULES.md");
|
|
926
2069
|
try {
|
|
927
|
-
if (
|
|
928
|
-
return await
|
|
2070
|
+
if (existsSync7(rulesPath)) {
|
|
2071
|
+
return await readFile5(rulesPath, "utf-8");
|
|
929
2072
|
}
|
|
930
2073
|
} catch {
|
|
931
2074
|
}
|
|
@@ -933,10 +2076,10 @@ async function loadRules(workDir) {
|
|
|
933
2076
|
}
|
|
934
2077
|
async function loadTeamInfo(workDir) {
|
|
935
2078
|
const projectDir = workDir || getWorkingDirectory(void 0, true);
|
|
936
|
-
const teamPath =
|
|
2079
|
+
const teamPath = join6(getTrieDirectory(projectDir), "TEAM.md");
|
|
937
2080
|
try {
|
|
938
|
-
if (
|
|
939
|
-
return await
|
|
2081
|
+
if (existsSync7(teamPath)) {
|
|
2082
|
+
return await readFile5(teamPath, "utf-8");
|
|
940
2083
|
}
|
|
941
2084
|
} catch {
|
|
942
2085
|
}
|
|
@@ -1060,7 +2203,7 @@ function getBootstrapTemplate(stack) {
|
|
|
1060
2203
|
if (stack.auth) lines.push(`- **Auth:** ${stack.auth}`);
|
|
1061
2204
|
lines.push("", "## Recommended Actions", "", "### 1. Skills to Install", "Based on your stack, consider installing:", "```bash");
|
|
1062
2205
|
lines.push(skillCommands || "# No specific skills detected - browse https://skills.sh");
|
|
1063
|
-
lines.push("```", "", "### 2. Agents to Enable", `Your stack suggests these agents: ${agentList || "security,
|
|
2206
|
+
lines.push("```", "", "### 2. Agents to Enable", `Your stack suggests these agents: ${agentList || "security, bugs"}`, "");
|
|
1064
2207
|
lines.push("### 3. Configure Rules", "Add your coding standards to `.trie/RULES.md`.", "");
|
|
1065
2208
|
lines.push("### 4. Run First Scan", "```bash", "trie scan", "```", "");
|
|
1066
2209
|
lines.push("---", "", "**Delete this file after completing setup.** Run:", "```bash", "rm .trie/BOOTSTRAP.md", "```");
|
|
@@ -1091,28 +2234,28 @@ function formatFriendlyError(error) {
|
|
|
1091
2234
|
|
|
1092
2235
|
// src/context/sync.ts
|
|
1093
2236
|
import fs from "fs/promises";
|
|
1094
|
-
import
|
|
2237
|
+
import path4 from "path";
|
|
1095
2238
|
var DEFAULT_JSON_NAME = "context.json";
|
|
1096
2239
|
async function exportToJson(graph, targetPath) {
|
|
1097
2240
|
const snapshot = await graph.getSnapshot();
|
|
1098
2241
|
const json = JSON.stringify(snapshot, null, 2);
|
|
1099
|
-
const outputPath = targetPath ??
|
|
1100
|
-
await fs.mkdir(
|
|
2242
|
+
const outputPath = targetPath ?? path4.join(getTrieDirectory(graph.projectRoot), DEFAULT_JSON_NAME);
|
|
2243
|
+
await fs.mkdir(path4.dirname(outputPath), { recursive: true });
|
|
1101
2244
|
await fs.writeFile(outputPath, json, "utf8");
|
|
1102
2245
|
return json;
|
|
1103
2246
|
}
|
|
1104
2247
|
async function importFromJson(graph, json, sourcePath) {
|
|
1105
|
-
const payload = json.trim().length > 0 ? json : await fs.readFile(sourcePath ??
|
|
2248
|
+
const payload = json.trim().length > 0 ? json : await fs.readFile(sourcePath ?? path4.join(getTrieDirectory(graph.projectRoot), DEFAULT_JSON_NAME), "utf8");
|
|
1106
2249
|
const snapshot = JSON.parse(payload);
|
|
1107
2250
|
await graph.applySnapshot(snapshot);
|
|
1108
2251
|
}
|
|
1109
2252
|
|
|
1110
2253
|
// src/context/incident-index.ts
|
|
1111
|
-
import
|
|
2254
|
+
import path6 from "path";
|
|
1112
2255
|
|
|
1113
2256
|
// src/context/file-trie.ts
|
|
1114
2257
|
import fs2 from "fs";
|
|
1115
|
-
import
|
|
2258
|
+
import path5 from "path";
|
|
1116
2259
|
import { performance } from "perf_hooks";
|
|
1117
2260
|
function normalizePath(filePath) {
|
|
1118
2261
|
const normalized = filePath.replace(/\\/g, "/");
|
|
@@ -1176,9 +2319,9 @@ var FilePathTrie = class {
|
|
|
1176
2319
|
incidentCount: Array.isArray(r.value) ? r.value.length : 0
|
|
1177
2320
|
})).sort((a, b) => b.incidentCount - a.incidentCount).slice(0, limit);
|
|
1178
2321
|
}
|
|
1179
|
-
timeLookup(
|
|
2322
|
+
timeLookup(path8) {
|
|
1180
2323
|
const start = performance.now();
|
|
1181
|
-
this.getIncidents(
|
|
2324
|
+
this.getIncidents(path8);
|
|
1182
2325
|
return performance.now() - start;
|
|
1183
2326
|
}
|
|
1184
2327
|
toJSON() {
|
|
@@ -1191,7 +2334,7 @@ var FilePathTrie = class {
|
|
|
1191
2334
|
persist() {
|
|
1192
2335
|
if (!this.persistPath) return;
|
|
1193
2336
|
try {
|
|
1194
|
-
const dir =
|
|
2337
|
+
const dir = path5.dirname(this.persistPath);
|
|
1195
2338
|
if (!fs2.existsSync(dir)) {
|
|
1196
2339
|
fs2.mkdirSync(dir, { recursive: true });
|
|
1197
2340
|
}
|
|
@@ -1210,7 +2353,7 @@ var IncidentIndex = class _IncidentIndex {
|
|
|
1210
2353
|
this.graph = graph;
|
|
1211
2354
|
this.projectRoot = projectRoot;
|
|
1212
2355
|
this.trie = new FilePathTrie(
|
|
1213
|
-
options?.persistPath ??
|
|
2356
|
+
options?.persistPath ?? path6.join(getTrieDirectory(projectRoot), "incident-trie.json")
|
|
1214
2357
|
);
|
|
1215
2358
|
}
|
|
1216
2359
|
static async build(graph, projectRoot, options) {
|
|
@@ -1263,8 +2406,8 @@ var IncidentIndex = class _IncidentIndex {
|
|
|
1263
2406
|
return Array.from(files);
|
|
1264
2407
|
}
|
|
1265
2408
|
normalizePath(filePath) {
|
|
1266
|
-
const absolute =
|
|
1267
|
-
const relative =
|
|
2409
|
+
const absolute = path6.isAbsolute(filePath) ? filePath : path6.join(this.projectRoot, filePath);
|
|
2410
|
+
const relative = path6.relative(this.projectRoot, absolute);
|
|
1268
2411
|
return relative.replace(/\\/g, "/");
|
|
1269
2412
|
}
|
|
1270
2413
|
};
|
|
@@ -1404,7 +2547,7 @@ var LearningSystem = class {
|
|
|
1404
2547
|
};
|
|
1405
2548
|
|
|
1406
2549
|
// src/guardian/learning-engine.ts
|
|
1407
|
-
import
|
|
2550
|
+
import path7 from "path";
|
|
1408
2551
|
var LearningEngine = class {
|
|
1409
2552
|
projectPath;
|
|
1410
2553
|
graph;
|
|
@@ -1453,7 +2596,7 @@ var LearningEngine = class {
|
|
|
1453
2596
|
}
|
|
1454
2597
|
}
|
|
1455
2598
|
if (issuesToStore.length > 0) {
|
|
1456
|
-
const result = await storeIssues(issuesToStore,
|
|
2599
|
+
const result = await storeIssues(issuesToStore, path7.basename(this.projectPath), this.projectPath);
|
|
1457
2600
|
return result.stored;
|
|
1458
2601
|
}
|
|
1459
2602
|
return 0;
|
|
@@ -1666,11 +2809,15 @@ var LinearIngester = class {
|
|
|
1666
2809
|
};
|
|
1667
2810
|
|
|
1668
2811
|
export {
|
|
2812
|
+
loadConfig,
|
|
2813
|
+
saveConfig,
|
|
1669
2814
|
trackIssueOccurrence,
|
|
1670
2815
|
recordBypass,
|
|
1671
2816
|
shouldAutoFix,
|
|
1672
2817
|
shouldBlockPush,
|
|
1673
2818
|
getAutonomyConfig,
|
|
2819
|
+
getStagedChanges,
|
|
2820
|
+
getUncommittedChanges,
|
|
1674
2821
|
perceiveCurrentChanges,
|
|
1675
2822
|
reasonAboutChangesHumanReadable,
|
|
1676
2823
|
loadBootstrapContext,
|
|
@@ -1690,4 +2837,4 @@ export {
|
|
|
1690
2837
|
LearningEngine,
|
|
1691
2838
|
LinearIngester
|
|
1692
2839
|
};
|
|
1693
|
-
//# sourceMappingURL=chunk-
|
|
2840
|
+
//# sourceMappingURL=chunk-FNCCZ3XB.js.map
|