@triedotdev/mcp 1.0.142 → 1.0.143
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/dist/{chunk-Y2LYDCJD.js → chunk-AIJF67CF.js} +1237 -33
- package/dist/chunk-AIJF67CF.js.map +1 -0
- package/dist/cli/yolo-daemon.js +1 -1
- package/dist/index.js +84 -1225
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/chunk-Y2LYDCJD.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -32,8 +32,10 @@ import {
|
|
|
32
32
|
InteractiveDashboard,
|
|
33
33
|
StreamingManager,
|
|
34
34
|
TrieCheckTool,
|
|
35
|
+
TrieCloudFixTool,
|
|
35
36
|
TrieExplainTool,
|
|
36
37
|
TrieFeedbackTool,
|
|
38
|
+
TrieFixTool,
|
|
37
39
|
TrieGetBlockersTool,
|
|
38
40
|
TrieGetDecisionsTool,
|
|
39
41
|
TrieGetGovernanceTool,
|
|
@@ -46,7 +48,7 @@ import {
|
|
|
46
48
|
getPrompt,
|
|
47
49
|
getSystemPrompt,
|
|
48
50
|
handleCheckpointTool
|
|
49
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-AIJF67CF.js";
|
|
50
52
|
import "./chunk-23RJT5WT.js";
|
|
51
53
|
import "./chunk-N2EDZTKG.js";
|
|
52
54
|
import {
|
|
@@ -91,9 +93,7 @@ import {
|
|
|
91
93
|
getSkillRegistry
|
|
92
94
|
} from "./chunk-G76DYVGX.js";
|
|
93
95
|
import {
|
|
94
|
-
getAutonomyConfig
|
|
95
|
-
loadAutonomyConfig,
|
|
96
|
-
saveAutonomyConfig
|
|
96
|
+
getAutonomyConfig
|
|
97
97
|
} from "./chunk-5KJ4UJOY.js";
|
|
98
98
|
import {
|
|
99
99
|
findSimilarIssues,
|
|
@@ -195,1151 +195,10 @@ function detectAITool() {
|
|
|
195
195
|
};
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
// src/tools/
|
|
198
|
+
// src/tools/test.ts
|
|
199
199
|
import { readFile } from "fs/promises";
|
|
200
200
|
import { existsSync } from "fs";
|
|
201
|
-
import { extname, relative, resolve, isAbsolute } from "path";
|
|
202
|
-
|
|
203
|
-
// src/tools/fix-triage.ts
|
|
204
|
-
var EFFORT_SCORES = {
|
|
205
|
-
trivial: -6,
|
|
206
|
-
easy: -3,
|
|
207
|
-
medium: 0,
|
|
208
|
-
hard: 4
|
|
209
|
-
};
|
|
210
|
-
var SEVERITY_SCORES = {
|
|
211
|
-
critical: 4,
|
|
212
|
-
serious: 2,
|
|
213
|
-
moderate: -1,
|
|
214
|
-
low: -3
|
|
215
|
-
};
|
|
216
|
-
function triageIssue(issue, context, occurrence, config, pipeline) {
|
|
217
|
-
const reasons = [];
|
|
218
|
-
if (config?.level === "passive") {
|
|
219
|
-
return {
|
|
220
|
-
strategy: "local-ai",
|
|
221
|
-
score: 0,
|
|
222
|
-
confidence: 1,
|
|
223
|
-
reasons: ["Autonomy level is passive \u2014 cloud dispatch disabled"],
|
|
224
|
-
fallback: "local-ai"
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
if (config?.cloudAgentEnabled === false) {
|
|
228
|
-
return {
|
|
229
|
-
strategy: "local-ai",
|
|
230
|
-
score: 0,
|
|
231
|
-
confidence: 1,
|
|
232
|
-
reasons: ["Cloud agent not enabled (run trie_cloud_fix action:configure)"],
|
|
233
|
-
fallback: "local-ai"
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
let score = 0;
|
|
237
|
-
const effort = issue.effort ?? "medium";
|
|
238
|
-
const effortScore = EFFORT_SCORES[effort] ?? 0;
|
|
239
|
-
if (effortScore !== 0) {
|
|
240
|
-
score += effortScore;
|
|
241
|
-
reasons.push(`effort:${effort}`);
|
|
242
|
-
}
|
|
243
|
-
const severityScore = SEVERITY_SCORES[issue.severity] ?? 0;
|
|
244
|
-
if (severityScore !== 0) {
|
|
245
|
-
score += severityScore;
|
|
246
|
-
reasons.push(`severity:${issue.severity}`);
|
|
247
|
-
}
|
|
248
|
-
if (issue.autoFixable) {
|
|
249
|
-
score -= 2;
|
|
250
|
-
reasons.push("autoFixable");
|
|
251
|
-
}
|
|
252
|
-
if (issue.confidence < 0.7) {
|
|
253
|
-
score -= 2;
|
|
254
|
-
reasons.push(`low confidence (${(issue.confidence * 100).toFixed(0)}%)`);
|
|
255
|
-
}
|
|
256
|
-
if (issue.cwe) {
|
|
257
|
-
score += 3;
|
|
258
|
-
reasons.push(`cwe:${issue.cwe}`);
|
|
259
|
-
}
|
|
260
|
-
if (issue.owasp) {
|
|
261
|
-
score += 2;
|
|
262
|
-
reasons.push(`owasp:${issue.owasp}`);
|
|
263
|
-
}
|
|
264
|
-
if (issue.category === "security") {
|
|
265
|
-
score += 2;
|
|
266
|
-
reasons.push("category:security");
|
|
267
|
-
}
|
|
268
|
-
if (context) {
|
|
269
|
-
if (context.hasTests) {
|
|
270
|
-
score += 1;
|
|
271
|
-
reasons.push("has tests");
|
|
272
|
-
} else {
|
|
273
|
-
score -= 2;
|
|
274
|
-
reasons.push("no tests");
|
|
275
|
-
}
|
|
276
|
-
if (context.complexity === "high") {
|
|
277
|
-
score += 1;
|
|
278
|
-
reasons.push("high complexity");
|
|
279
|
-
}
|
|
280
|
-
if (context.touchesAuth || context.touchesCrypto || context.touchesPayments) {
|
|
281
|
-
score += 2;
|
|
282
|
-
reasons.push("touches auth/crypto/payments");
|
|
283
|
-
}
|
|
284
|
-
if (context.touchesDatabase) {
|
|
285
|
-
score += 1;
|
|
286
|
-
reasons.push("touches database");
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
if (occurrence) {
|
|
290
|
-
if (occurrence.count >= 5) {
|
|
291
|
-
score += 3;
|
|
292
|
-
reasons.push(`${occurrence.count}\xD7 seen`);
|
|
293
|
-
} else if (occurrence.count >= 3) {
|
|
294
|
-
score += 1;
|
|
295
|
-
reasons.push(`${occurrence.count}\xD7 seen`);
|
|
296
|
-
}
|
|
297
|
-
if (occurrence.escalationLevel === "block") {
|
|
298
|
-
score += 4;
|
|
299
|
-
reasons.push("escalation:block");
|
|
300
|
-
} else if (occurrence.escalationLevel === "escalate") {
|
|
301
|
-
score += 2;
|
|
302
|
-
reasons.push("escalation:escalate");
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
if (config?.level === "aggressive") {
|
|
306
|
-
score -= 1;
|
|
307
|
-
reasons.push("aggressive mode (\u22121 threshold)");
|
|
308
|
-
}
|
|
309
|
-
if (pipeline) {
|
|
310
|
-
if (pipeline.hasLinkedPR && pipeline.prState === "open") {
|
|
311
|
-
score -= 2;
|
|
312
|
-
reasons.push("has open PR");
|
|
313
|
-
}
|
|
314
|
-
if (pipeline.hasLinkedTicket && pipeline.ticketStatus?.toLowerCase().includes("started")) {
|
|
315
|
-
score -= 1;
|
|
316
|
-
reasons.push("ticket in active sprint");
|
|
317
|
-
}
|
|
318
|
-
if (pipeline.hasLinkedTicket && pipeline.ticketPriority === "urgent") {
|
|
319
|
-
score += 1;
|
|
320
|
-
reasons.push("ticket:urgent");
|
|
321
|
-
}
|
|
322
|
-
if (!pipeline.hasLinkedTicket && !pipeline.hasLinkedPR && issue.severity === "critical") {
|
|
323
|
-
score += 2;
|
|
324
|
-
reasons.push("critical, no ticket/PR (falling through cracks)");
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
let strategy;
|
|
328
|
-
if (score < 0 && issue.autoFixable) {
|
|
329
|
-
strategy = "inline-auto";
|
|
330
|
-
} else if (score < 4) {
|
|
331
|
-
strategy = "local-ai";
|
|
332
|
-
} else {
|
|
333
|
-
strategy = "cloud-agent";
|
|
334
|
-
}
|
|
335
|
-
const confidence = Math.min(1, Math.abs(score) / 8);
|
|
336
|
-
return { strategy, score, confidence, reasons, fallback: "local-ai" };
|
|
337
|
-
}
|
|
338
|
-
function triageIssues(issues, context, occurrences, config, pipelineContexts) {
|
|
339
|
-
const results = /* @__PURE__ */ new Map();
|
|
340
|
-
const summary = {
|
|
341
|
-
inlineAuto: [],
|
|
342
|
-
localAi: [],
|
|
343
|
-
cloudAgent: [],
|
|
344
|
-
cloudAgentScore: 0
|
|
345
|
-
};
|
|
346
|
-
for (const issue of issues) {
|
|
347
|
-
const occurrence = occurrences?.get(issue.id);
|
|
348
|
-
const pipeline = pipelineContexts?.get(issue.id);
|
|
349
|
-
const result = triageIssue(issue, context, occurrence, config, pipeline);
|
|
350
|
-
results.set(issue.id, result);
|
|
351
|
-
switch (result.strategy) {
|
|
352
|
-
case "inline-auto":
|
|
353
|
-
summary.inlineAuto.push(issue);
|
|
354
|
-
break;
|
|
355
|
-
case "local-ai":
|
|
356
|
-
summary.localAi.push(issue);
|
|
357
|
-
break;
|
|
358
|
-
case "cloud-agent":
|
|
359
|
-
summary.cloudAgent.push(issue);
|
|
360
|
-
summary.cloudAgentScore += result.score;
|
|
361
|
-
break;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
return { results, summary };
|
|
365
|
-
}
|
|
366
|
-
function formatTriageTable(results, issues) {
|
|
367
|
-
const LINE = "\u2500".repeat(68);
|
|
368
|
-
const lines = [];
|
|
369
|
-
lines.push("FIX ROUTING PLAN");
|
|
370
|
-
lines.push(LINE);
|
|
371
|
-
lines.push(
|
|
372
|
-
padEnd("Issue", 32) + padEnd("Strategy", 14) + padEnd("Score", 7) + "Reason"
|
|
373
|
-
);
|
|
374
|
-
for (const issue of issues) {
|
|
375
|
-
const r = results.get(issue.id);
|
|
376
|
-
if (!r) continue;
|
|
377
|
-
const loc = `${shortPath(issue.file)}:${issue.line ?? "?"}`;
|
|
378
|
-
const scoreStr = r.score >= 0 ? `+${r.score}` : String(r.score);
|
|
379
|
-
lines.push(
|
|
380
|
-
padEnd(loc, 32) + padEnd(r.strategy, 14) + padEnd(scoreStr, 7) + r.reasons.join(", ")
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
lines.push(LINE);
|
|
384
|
-
const counts = [];
|
|
385
|
-
const cloud = issues.filter((i) => results.get(i.id)?.strategy === "cloud-agent");
|
|
386
|
-
const local = issues.filter((i) => results.get(i.id)?.strategy === "local-ai");
|
|
387
|
-
const auto = issues.filter((i) => results.get(i.id)?.strategy === "inline-auto");
|
|
388
|
-
if (cloud.length) counts.push(`${cloud.length} for cloud agent`);
|
|
389
|
-
if (local.length) counts.push(`${local.length} for local AI`);
|
|
390
|
-
if (auto.length) counts.push(`${auto.length} auto-fixable`);
|
|
391
|
-
lines.push(counts.join(", "));
|
|
392
|
-
if (cloud.length > 0) {
|
|
393
|
-
const ids = cloud.map((i) => `"${i.id}"`).join(",");
|
|
394
|
-
lines.push("");
|
|
395
|
-
lines.push(`To dispatch cloud issues: trie_cloud_fix action:dispatch issueIds:[${ids}]`);
|
|
396
|
-
}
|
|
397
|
-
if (local.length > 0) {
|
|
398
|
-
const ids = local.map((i) => `"${i.id}"`).join(",");
|
|
399
|
-
lines.push(`To fix local issues: trie_fix issueIds:[${ids}]`);
|
|
400
|
-
}
|
|
401
|
-
return lines.join("\n");
|
|
402
|
-
}
|
|
403
|
-
function formatCloudRecommendation(results, issues) {
|
|
404
|
-
const cloud = issues.filter((i) => {
|
|
405
|
-
const r = results.get(i.id);
|
|
406
|
-
return r && r.score >= 4;
|
|
407
|
-
});
|
|
408
|
-
if (cloud.length === 0) return null;
|
|
409
|
-
const LINE = "\u2500".repeat(65);
|
|
410
|
-
const lines = [];
|
|
411
|
-
lines.push(LINE);
|
|
412
|
-
lines.push(
|
|
413
|
-
`${cloud.length} issue${cloud.length > 1 ? "s" : ""} qualify for cloud agent verification (test-verified fix + PR):`
|
|
414
|
-
);
|
|
415
|
-
for (const issue of cloud) {
|
|
416
|
-
const r = results.get(issue.id);
|
|
417
|
-
const loc = `${shortPath(issue.file)}:${issue.line ?? "?"}`;
|
|
418
|
-
const scoreStr = r.score >= 0 ? `+${r.score}` : String(r.score);
|
|
419
|
-
lines.push(` \u2022 ${padEnd(loc, 30)} \u2014 score ${scoreStr} (${r.reasons.join(", ")})`);
|
|
420
|
-
}
|
|
421
|
-
const ids = cloud.map((i) => `"${i.id}"`).join(",");
|
|
422
|
-
lines.push("");
|
|
423
|
-
lines.push(`Run: trie_cloud_fix action:dispatch issueIds:[${ids}]`);
|
|
424
|
-
lines.push(LINE);
|
|
425
|
-
return lines.join("\n");
|
|
426
|
-
}
|
|
427
|
-
function shortPath(file) {
|
|
428
|
-
const parts = file.split("/");
|
|
429
|
-
return parts.length > 2 ? parts.slice(-2).join("/") : file;
|
|
430
|
-
}
|
|
431
|
-
function padEnd(str, len) {
|
|
432
|
-
if (str.length >= len) return str.slice(0, len);
|
|
433
|
-
return str + " ".repeat(len - str.length);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// src/tools/fix.ts
|
|
437
|
-
var pendingFixes = /* @__PURE__ */ new Map();
|
|
438
|
-
var TrieFixTool = class {
|
|
439
|
-
async execute(args) {
|
|
440
|
-
const { issueIds, file, line, issue, fix, autoApprove = false, dryRun = false, action } = args || {};
|
|
441
|
-
if (action === "route") {
|
|
442
|
-
return this.routeIssues(issueIds);
|
|
443
|
-
}
|
|
444
|
-
if (issueIds && issueIds.length > 0) {
|
|
445
|
-
return this.fixByIds(issueIds, autoApprove, dryRun);
|
|
446
|
-
}
|
|
447
|
-
if (file && fix) {
|
|
448
|
-
return this.applyFix(file, line || 1, issue || "User-specified fix", fix, dryRun);
|
|
449
|
-
}
|
|
450
|
-
if (pendingFixes.size > 0) {
|
|
451
|
-
return this.showPendingFixes();
|
|
452
|
-
}
|
|
453
|
-
if (file && issue) {
|
|
454
|
-
return this.generateFixPrompt(file, line || 1, issue);
|
|
455
|
-
}
|
|
456
|
-
return {
|
|
457
|
-
content: [{
|
|
458
|
-
type: "text",
|
|
459
|
-
text: this.getHelpText()
|
|
460
|
-
}]
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
async routeIssues(issueIds) {
|
|
464
|
-
await loadPendingFixesFromMemory();
|
|
465
|
-
const pending = getPendingFixes();
|
|
466
|
-
if (pending.length === 0) {
|
|
467
|
-
return {
|
|
468
|
-
content: [{ type: "text", text: "No pending issues. Run trie_scan to detect new issues, or check memory with trie_memory action:recent." }]
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
const issues = pending.map((p) => ({
|
|
472
|
-
id: p.id,
|
|
473
|
-
severity: p.severity ?? "moderate",
|
|
474
|
-
effort: p.effort,
|
|
475
|
-
issue: p.issue,
|
|
476
|
-
fix: p.suggestedFix,
|
|
477
|
-
file: p.file,
|
|
478
|
-
line: p.line,
|
|
479
|
-
confidence: p.confidence,
|
|
480
|
-
autoFixable: p.autoFixable ?? false,
|
|
481
|
-
agent: "trie_scan",
|
|
482
|
-
cwe: p.cwe,
|
|
483
|
-
owasp: p.owasp,
|
|
484
|
-
category: p.category
|
|
485
|
-
}));
|
|
486
|
-
const filtered = issueIds && issueIds.length > 0 ? issues.filter((i) => issueIds.includes(i.id)) : issues;
|
|
487
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
488
|
-
const config = await loadAutonomyConfig(workDir);
|
|
489
|
-
const { results } = triageIssues(filtered, void 0, void 0, config);
|
|
490
|
-
const table = formatTriageTable(results, filtered);
|
|
491
|
-
return { content: [{ type: "text", text: `
|
|
492
|
-
${table}
|
|
493
|
-
` }] };
|
|
494
|
-
}
|
|
495
|
-
async fixByIds(issueIds, autoApprove, dryRun) {
|
|
496
|
-
const results = [];
|
|
497
|
-
let fixed = 0;
|
|
498
|
-
let failed = 0;
|
|
499
|
-
for (const id of issueIds) {
|
|
500
|
-
const pendingFix = pendingFixes.get(id);
|
|
501
|
-
if (!pendingFix) {
|
|
502
|
-
results.push(`\u274C Issue ${id}: Not found in pending fixes`);
|
|
503
|
-
failed++;
|
|
504
|
-
continue;
|
|
505
|
-
}
|
|
506
|
-
if (pendingFix.confidence < 0.8 && !autoApprove) {
|
|
507
|
-
results.push(`[!] Issue ${id}: Confidence too low (${(pendingFix.confidence * 100).toFixed(0)}%) - use autoApprove:true to override`);
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
510
|
-
if (dryRun) {
|
|
511
|
-
results.push(`\u{1F50D} Issue ${id}: Would fix "${pendingFix.issue}" in ${pendingFix.file}:${pendingFix.line}`);
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
try {
|
|
515
|
-
results.push(`\u2705 Issue ${id}: Fix prepared for ${pendingFix.file}:${pendingFix.line}`);
|
|
516
|
-
results.push(` Issue: ${pendingFix.issue}`);
|
|
517
|
-
results.push(` Fix: ${pendingFix.suggestedFix}`);
|
|
518
|
-
pendingFix.status = "applied";
|
|
519
|
-
fixed++;
|
|
520
|
-
} catch (error) {
|
|
521
|
-
results.push(`\u274C Issue ${id}: Failed to apply - ${error}`);
|
|
522
|
-
failed++;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
let output = `
|
|
526
|
-
${"\u2501".repeat(60)}
|
|
527
|
-
`;
|
|
528
|
-
output += `\u{1F527} FIX RESULTS
|
|
529
|
-
`;
|
|
530
|
-
output += `${"\u2501".repeat(60)}
|
|
531
|
-
|
|
532
|
-
`;
|
|
533
|
-
output += results.join("\n");
|
|
534
|
-
output += `
|
|
535
|
-
|
|
536
|
-
**Summary:** ${fixed} fixed, ${failed} failed, ${issueIds.length - fixed - failed} skipped
|
|
537
|
-
`;
|
|
538
|
-
output += await this.appendCloudRecommendation(issueIds);
|
|
539
|
-
return { content: [{ type: "text", text: output }] };
|
|
540
|
-
}
|
|
541
|
-
async applyFix(file, line, issue, fix, dryRun) {
|
|
542
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
543
|
-
const filePath = isAbsolute(file) ? file : resolve(workDir, file);
|
|
544
|
-
if (!existsSync(filePath)) {
|
|
545
|
-
return {
|
|
546
|
-
content: [{
|
|
547
|
-
type: "text",
|
|
548
|
-
text: `\u274C File not found: ${filePath}`
|
|
549
|
-
}]
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
const content = await readFile(filePath, "utf-8");
|
|
553
|
-
const lines = content.split("\n");
|
|
554
|
-
const language = this.detectLanguage(filePath);
|
|
555
|
-
const contextStart = Math.max(0, line - 10);
|
|
556
|
-
const contextEnd = Math.min(lines.length, line + 10);
|
|
557
|
-
const contextLines = lines.slice(contextStart, contextEnd);
|
|
558
|
-
const prompt = getPrompt("fix", "apply", {
|
|
559
|
-
issue,
|
|
560
|
-
fix,
|
|
561
|
-
language,
|
|
562
|
-
code: contextLines.join("\n"),
|
|
563
|
-
filePath: relative(workDir, filePath),
|
|
564
|
-
line: String(line)
|
|
565
|
-
});
|
|
566
|
-
const systemPrompt = getSystemPrompt("fix");
|
|
567
|
-
let output = `
|
|
568
|
-
${"\u2501".repeat(60)}
|
|
569
|
-
`;
|
|
570
|
-
output += `\u{1F527} FIX APPLICATION REQUEST
|
|
571
|
-
`;
|
|
572
|
-
output += `${"\u2501".repeat(60)}
|
|
573
|
-
|
|
574
|
-
`;
|
|
575
|
-
output += `## \u{1F4CD} Target
|
|
576
|
-
|
|
577
|
-
`;
|
|
578
|
-
output += `- **File:** \`${relative(workDir, filePath)}\`
|
|
579
|
-
`;
|
|
580
|
-
output += `- **Line:** ${line}
|
|
581
|
-
`;
|
|
582
|
-
output += `- **Issue:** ${issue}
|
|
583
|
-
`;
|
|
584
|
-
output += `- **Requested Fix:** ${fix}
|
|
585
|
-
|
|
586
|
-
`;
|
|
587
|
-
output += `## \u{1F4C4} Current Code Context
|
|
588
|
-
|
|
589
|
-
`;
|
|
590
|
-
output += `\`\`\`${language}
|
|
591
|
-
`;
|
|
592
|
-
for (let i = 0; i < contextLines.length; i++) {
|
|
593
|
-
const lineNum = contextStart + i + 1;
|
|
594
|
-
const marker = lineNum === line ? "\u2192 " : " ";
|
|
595
|
-
output += `${marker}${lineNum.toString().padStart(4)} | ${contextLines[i]}
|
|
596
|
-
`;
|
|
597
|
-
}
|
|
598
|
-
output += `\`\`\`
|
|
599
|
-
|
|
600
|
-
`;
|
|
601
|
-
if (dryRun) {
|
|
602
|
-
output += `## \u{1F50D} Dry Run Mode
|
|
603
|
-
|
|
604
|
-
`;
|
|
605
|
-
output += `No changes will be made. Review the fix below:
|
|
606
|
-
|
|
607
|
-
`;
|
|
608
|
-
}
|
|
609
|
-
output += `${"\u2500".repeat(60)}
|
|
610
|
-
`;
|
|
611
|
-
output += `## \u{1F9E0} Fix Request for AI
|
|
612
|
-
|
|
613
|
-
`;
|
|
614
|
-
output += `**Role:** ${systemPrompt.split("\n")[0]}
|
|
615
|
-
|
|
616
|
-
`;
|
|
617
|
-
output += prompt;
|
|
618
|
-
output += `
|
|
619
|
-
${"\u2500".repeat(60)}
|
|
620
|
-
`;
|
|
621
|
-
output += `
|
|
622
|
-
### After generating the fix, apply it with:
|
|
623
|
-
|
|
624
|
-
`;
|
|
625
|
-
output += `\`\`\`
|
|
626
|
-
`;
|
|
627
|
-
output += `Use the edit_file tool to apply the generated code changes
|
|
628
|
-
`;
|
|
629
|
-
output += `\`\`\`
|
|
630
|
-
`;
|
|
631
|
-
return { content: [{ type: "text", text: output }] };
|
|
632
|
-
}
|
|
633
|
-
async generateFixPrompt(file, line, issue) {
|
|
634
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
635
|
-
const filePath = isAbsolute(file) ? file : resolve(workDir, file);
|
|
636
|
-
if (!existsSync(filePath)) {
|
|
637
|
-
return {
|
|
638
|
-
content: [{
|
|
639
|
-
type: "text",
|
|
640
|
-
text: `\u274C File not found: ${filePath}`
|
|
641
|
-
}]
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
const content = await readFile(filePath, "utf-8");
|
|
645
|
-
const lines = content.split("\n");
|
|
646
|
-
const language = this.detectLanguage(filePath);
|
|
647
|
-
const contextStart = Math.max(0, line - 20);
|
|
648
|
-
const contextEnd = Math.min(lines.length, line + 20);
|
|
649
|
-
const contextLines = lines.slice(contextStart, contextEnd);
|
|
650
|
-
let output = `
|
|
651
|
-
${"\u2501".repeat(60)}
|
|
652
|
-
`;
|
|
653
|
-
output += `\u{1F527} FIX GENERATION REQUEST
|
|
654
|
-
`;
|
|
655
|
-
output += `${"\u2501".repeat(60)}
|
|
656
|
-
|
|
657
|
-
`;
|
|
658
|
-
output += `## \u{1F4CD} Issue Details
|
|
659
|
-
|
|
660
|
-
`;
|
|
661
|
-
output += `- **File:** \`${relative(workDir, filePath)}\`
|
|
662
|
-
`;
|
|
663
|
-
output += `- **Line:** ${line}
|
|
664
|
-
`;
|
|
665
|
-
output += `- **Issue:** ${issue}
|
|
666
|
-
|
|
667
|
-
`;
|
|
668
|
-
output += `## \u{1F4C4} Code Context
|
|
669
|
-
|
|
670
|
-
`;
|
|
671
|
-
output += `\`\`\`${language}
|
|
672
|
-
`;
|
|
673
|
-
for (let i = 0; i < contextLines.length; i++) {
|
|
674
|
-
const lineNum = contextStart + i + 1;
|
|
675
|
-
const marker = lineNum === line ? "\u2192 " : " ";
|
|
676
|
-
output += `${marker}${lineNum.toString().padStart(4)} | ${contextLines[i]}
|
|
677
|
-
`;
|
|
678
|
-
}
|
|
679
|
-
output += `\`\`\`
|
|
680
|
-
|
|
681
|
-
`;
|
|
682
|
-
output += `## \u{1F9E0} Analysis Request
|
|
683
|
-
|
|
684
|
-
`;
|
|
685
|
-
output += `Please analyze this issue and provide:
|
|
686
|
-
|
|
687
|
-
`;
|
|
688
|
-
output += `1. **Root cause** - Why does this issue occur?
|
|
689
|
-
`;
|
|
690
|
-
output += `2. **Impact** - What could go wrong if unfixed?
|
|
691
|
-
`;
|
|
692
|
-
output += `3. **Fix** - The exact code change needed
|
|
693
|
-
`;
|
|
694
|
-
output += `4. **Verification** - How to test the fix works
|
|
695
|
-
|
|
696
|
-
`;
|
|
697
|
-
output += `After analysis, you can apply the fix using the edit_file tool.
|
|
698
|
-
`;
|
|
699
|
-
return { content: [{ type: "text", text: output }] };
|
|
700
|
-
}
|
|
701
|
-
showPendingFixes() {
|
|
702
|
-
let output = `
|
|
703
|
-
${"\u2501".repeat(60)}
|
|
704
|
-
`;
|
|
705
|
-
output += `\u{1F527} PENDING FIXES
|
|
706
|
-
`;
|
|
707
|
-
output += `${"\u2501".repeat(60)}
|
|
708
|
-
|
|
709
|
-
`;
|
|
710
|
-
if (pendingFixes.size === 0) {
|
|
711
|
-
output += `No pending fixes. Run \`trie_scan\` first to detect issues.
|
|
712
|
-
`;
|
|
713
|
-
return { content: [{ type: "text", text: output }] };
|
|
714
|
-
}
|
|
715
|
-
const fixes = Array.from(pendingFixes.values());
|
|
716
|
-
const byStatus = {
|
|
717
|
-
pending: fixes.filter((f) => f.status === "pending"),
|
|
718
|
-
applied: fixes.filter((f) => f.status === "applied"),
|
|
719
|
-
rejected: fixes.filter((f) => f.status === "rejected")
|
|
720
|
-
};
|
|
721
|
-
if (byStatus.pending.length > 0) {
|
|
722
|
-
output += `## \u23F3 Pending (${byStatus.pending.length})
|
|
723
|
-
|
|
724
|
-
`;
|
|
725
|
-
output += `| ID | File | Line | Issue | Confidence |
|
|
726
|
-
`;
|
|
727
|
-
output += `|----|------|------|-------|------------|
|
|
728
|
-
`;
|
|
729
|
-
for (const fix of byStatus.pending) {
|
|
730
|
-
const conf = `${(fix.confidence * 100).toFixed(0)}%`;
|
|
731
|
-
const shortFile = fix.file.split("/").slice(-2).join("/");
|
|
732
|
-
output += `| ${fix.id} | ${shortFile} | ${fix.line} | ${fix.issue.slice(0, 40)}... | ${conf} |
|
|
733
|
-
`;
|
|
734
|
-
}
|
|
735
|
-
output += "\n";
|
|
736
|
-
}
|
|
737
|
-
output += `### Commands
|
|
738
|
-
|
|
739
|
-
`;
|
|
740
|
-
output += `- Fix all high-confidence: \`trie_fix autoApprove:true\`
|
|
741
|
-
`;
|
|
742
|
-
output += `- Fix specific: \`trie_fix issueIds:["id1", "id2"]\`
|
|
743
|
-
`;
|
|
744
|
-
output += `- Preview: \`trie_fix dryRun:true\`
|
|
745
|
-
`;
|
|
746
|
-
return { content: [{ type: "text", text: output }] };
|
|
747
|
-
}
|
|
748
|
-
getHelpText() {
|
|
749
|
-
return `
|
|
750
|
-
${"\u2501".repeat(60)}
|
|
751
|
-
\u{1F527} TRIE FIX - AI-POWERED CODE FIXING
|
|
752
|
-
${"\u2501".repeat(60)}
|
|
753
|
-
|
|
754
|
-
## Usage
|
|
755
|
-
|
|
756
|
-
### Fix issues from a scan:
|
|
757
|
-
\`\`\`
|
|
758
|
-
trie_fix issueIds:["issue-1", "issue-2"]
|
|
759
|
-
\`\`\`
|
|
760
|
-
|
|
761
|
-
### Auto-fix all high-confidence issues:
|
|
762
|
-
\`\`\`
|
|
763
|
-
trie_fix autoApprove:true
|
|
764
|
-
\`\`\`
|
|
765
|
-
|
|
766
|
-
### Fix specific file and line:
|
|
767
|
-
\`\`\`
|
|
768
|
-
trie_fix file:"src/app.ts" line:42 issue:"SQL injection" fix:"Use parameterized query"
|
|
769
|
-
\`\`\`
|
|
770
|
-
|
|
771
|
-
### Preview fixes without applying:
|
|
772
|
-
\`\`\`
|
|
773
|
-
trie_fix dryRun:true
|
|
774
|
-
\`\`\`
|
|
775
|
-
|
|
776
|
-
### View pending fixes:
|
|
777
|
-
\`\`\`
|
|
778
|
-
trie_fix
|
|
779
|
-
\`\`\`
|
|
780
|
-
|
|
781
|
-
## Workflow
|
|
782
|
-
|
|
783
|
-
1. Run \`trie_scan\` to detect issues
|
|
784
|
-
2. Review the issues found
|
|
785
|
-
3. Run \`trie_fix\` to apply fixes
|
|
786
|
-
|
|
787
|
-
The AI will analyze each issue, generate the fix, and you can review before applying.
|
|
788
|
-
`;
|
|
789
|
-
}
|
|
790
|
-
async appendCloudRecommendation(handledIds) {
|
|
791
|
-
try {
|
|
792
|
-
const pending = getPendingFixes();
|
|
793
|
-
const remaining = pending.filter((p) => !handledIds.includes(p.id) || pendingFixes.get(p.id)?.status === "pending");
|
|
794
|
-
if (remaining.length === 0) return "";
|
|
795
|
-
const issues = remaining.map((p) => ({
|
|
796
|
-
id: p.id,
|
|
797
|
-
severity: "moderate",
|
|
798
|
-
issue: p.issue,
|
|
799
|
-
fix: p.suggestedFix,
|
|
800
|
-
file: p.file,
|
|
801
|
-
line: p.line,
|
|
802
|
-
confidence: p.confidence,
|
|
803
|
-
autoFixable: false,
|
|
804
|
-
agent: "trie_scan"
|
|
805
|
-
}));
|
|
806
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
807
|
-
const config = await loadAutonomyConfig(workDir);
|
|
808
|
-
const { results } = triageIssues(issues, void 0, void 0, config);
|
|
809
|
-
const footer = formatCloudRecommendation(results, issues);
|
|
810
|
-
return footer ? `
|
|
811
|
-
${footer}
|
|
812
|
-
` : "";
|
|
813
|
-
} catch {
|
|
814
|
-
return "";
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
detectLanguage(filePath) {
|
|
818
|
-
const ext = extname(filePath).toLowerCase();
|
|
819
|
-
const langMap = {
|
|
820
|
-
".ts": "typescript",
|
|
821
|
-
".tsx": "tsx",
|
|
822
|
-
".js": "javascript",
|
|
823
|
-
".jsx": "jsx",
|
|
824
|
-
".py": "python",
|
|
825
|
-
".go": "go",
|
|
826
|
-
".rs": "rust"
|
|
827
|
-
};
|
|
828
|
-
return langMap[ext] || "plaintext";
|
|
829
|
-
}
|
|
830
|
-
};
|
|
831
|
-
function getPendingFixes() {
|
|
832
|
-
return Array.from(pendingFixes.values());
|
|
833
|
-
}
|
|
834
|
-
async function loadPendingFixesFromMemory() {
|
|
835
|
-
try {
|
|
836
|
-
const { getRecentIssues: getRecentIssues2 } = await import("./issue-store-LZWZIGM7.js");
|
|
837
|
-
pendingFixes.clear();
|
|
838
|
-
const recentIssues = await getRecentIssues2({ limit: 50, includeResolved: false });
|
|
839
|
-
for (const storedIssue of recentIssues) {
|
|
840
|
-
const fix = {
|
|
841
|
-
id: storedIssue.id,
|
|
842
|
-
file: storedIssue.file,
|
|
843
|
-
line: storedIssue.line || 0,
|
|
844
|
-
issue: storedIssue.issue,
|
|
845
|
-
suggestedFix: storedIssue.fix,
|
|
846
|
-
confidence: 0.8,
|
|
847
|
-
// Default confidence for memory issues
|
|
848
|
-
status: "pending",
|
|
849
|
-
severity: storedIssue.severity,
|
|
850
|
-
autoFixable: true,
|
|
851
|
-
// Memory issues are generally auto-fixable
|
|
852
|
-
category: storedIssue.category
|
|
853
|
-
};
|
|
854
|
-
pendingFixes.set(fix.id, fix);
|
|
855
|
-
}
|
|
856
|
-
} catch (error) {
|
|
857
|
-
console.warn("Failed to load pending fixes from memory:", error);
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
// src/tools/cloud-fix.ts
|
|
862
|
-
import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
|
|
863
|
-
import { existsSync as existsSync2 } from "fs";
|
|
864
|
-
import { join } from "path";
|
|
865
|
-
import { execSync } from "child_process";
|
|
866
|
-
|
|
867
|
-
// src/integrations/cursor-cloud-agent.ts
|
|
868
|
-
var BASE_URL = "https://api.cursor.com/v1";
|
|
869
|
-
var CursorCloudAgentClient = class {
|
|
870
|
-
apiKey;
|
|
871
|
-
constructor(apiKey) {
|
|
872
|
-
this.apiKey = apiKey;
|
|
873
|
-
}
|
|
874
|
-
/**
|
|
875
|
-
* Dispatch an issue to a cloud agent for fixing.
|
|
876
|
-
*/
|
|
877
|
-
async dispatch(issue, triageResult, repoUrl, branch) {
|
|
878
|
-
const prompt = this.buildPrompt(issue, triageResult);
|
|
879
|
-
const body = {
|
|
880
|
-
prompt,
|
|
881
|
-
repo: repoUrl,
|
|
882
|
-
branch,
|
|
883
|
-
metadata: {
|
|
884
|
-
issueId: issue.id,
|
|
885
|
-
file: issue.file,
|
|
886
|
-
line: issue.line,
|
|
887
|
-
severity: issue.severity,
|
|
888
|
-
agent: issue.agent
|
|
889
|
-
}
|
|
890
|
-
};
|
|
891
|
-
const res = await this.request("POST", "/agents/tasks", body);
|
|
892
|
-
return {
|
|
893
|
-
jobId: res.id ?? res.taskId ?? res.jobId,
|
|
894
|
-
status: "dispatched",
|
|
895
|
-
artifactUrls: [],
|
|
896
|
-
dispatchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
/**
|
|
900
|
-
* Poll job status.
|
|
901
|
-
*/
|
|
902
|
-
async poll(jobId) {
|
|
903
|
-
const res = await this.request("GET", `/agents/tasks/${jobId}`);
|
|
904
|
-
const status = mapStatus(res.status);
|
|
905
|
-
const prUrl = extractPrUrl(res);
|
|
906
|
-
const artifactUrls = this.extractArtifacts(res);
|
|
907
|
-
return { status, prUrl, artifactUrls };
|
|
908
|
-
}
|
|
909
|
-
/**
|
|
910
|
-
* Get artifact URLs for a completed job.
|
|
911
|
-
*/
|
|
912
|
-
async getArtifacts(jobId) {
|
|
913
|
-
const res = await this.request("GET", `/agents/tasks/${jobId}`);
|
|
914
|
-
return this.extractArtifacts(res);
|
|
915
|
-
}
|
|
916
|
-
/**
|
|
917
|
-
* Cancel a running job.
|
|
918
|
-
*/
|
|
919
|
-
async cancelJob(jobId) {
|
|
920
|
-
await this.request("DELETE", `/agents/tasks/${jobId}`);
|
|
921
|
-
}
|
|
922
|
-
// --------------------------------------------------------------------------
|
|
923
|
-
// Internal
|
|
924
|
-
// --------------------------------------------------------------------------
|
|
925
|
-
buildPrompt(issue, triageResult) {
|
|
926
|
-
const parts = [
|
|
927
|
-
"You are fixing a verified issue in the codebase.",
|
|
928
|
-
"",
|
|
929
|
-
`Issue: ${issue.issue}`,
|
|
930
|
-
`File: ${issue.file}${issue.line ? `:${issue.line}` : ""}`,
|
|
931
|
-
`Severity: ${issue.severity} | Effort: ${issue.effort ?? "medium"}`,
|
|
932
|
-
`Agent that found it: ${issue.agent}`,
|
|
933
|
-
`Suggested fix: ${issue.fix}`
|
|
934
|
-
];
|
|
935
|
-
if (issue.cwe) parts.push(`CWE: ${issue.cwe}`);
|
|
936
|
-
if (issue.owasp) parts.push(`OWASP: ${issue.owasp}`);
|
|
937
|
-
parts.push(`Triage confidence: ${triageResult.confidence.toFixed(2)}`);
|
|
938
|
-
parts.push(`Why cloud agent: ${triageResult.reasons.join(", ")}`);
|
|
939
|
-
parts.push("");
|
|
940
|
-
parts.push("Steps:");
|
|
941
|
-
parts.push(`1. Apply the minimal fix described above to ${issue.file}`);
|
|
942
|
-
parts.push("2. Run the existing test suite (detect test runner from package.json scripts)");
|
|
943
|
-
parts.push("3. Screenshot the passing test output \u2014 this is the verification artifact");
|
|
944
|
-
parts.push("4. If tests fail, iterate on the fix until they pass (max 3 attempts)");
|
|
945
|
-
parts.push("5. Open a PR with only this single change \u2014 do not bundle other fixes");
|
|
946
|
-
parts.push("6. Include the screenshot in the PR description as evidence");
|
|
947
|
-
return parts.join("\n");
|
|
948
|
-
}
|
|
949
|
-
async request(method, path2, body) {
|
|
950
|
-
const url = `${BASE_URL}${path2}`;
|
|
951
|
-
const headers = {
|
|
952
|
-
"Authorization": `Bearer ${this.apiKey}`,
|
|
953
|
-
"Content-Type": "application/json"
|
|
954
|
-
};
|
|
955
|
-
const init = { method, headers };
|
|
956
|
-
if (body) init.body = JSON.stringify(body);
|
|
957
|
-
const res = await fetch(url, init);
|
|
958
|
-
if (res.status === 401) {
|
|
959
|
-
throw new Error(
|
|
960
|
-
'Cursor API key is invalid or expired.\nUpdate it: trie_cloud_fix action:configure apiKey:"key-..."'
|
|
961
|
-
);
|
|
962
|
-
}
|
|
963
|
-
if (res.status === 404) {
|
|
964
|
-
throw new Error(`Job not found: ${path2}`);
|
|
965
|
-
}
|
|
966
|
-
if (res.status >= 500) {
|
|
967
|
-
throw new Error(
|
|
968
|
-
`Cursor API returned ${res.status}. The service may be temporarily unavailable \u2014 retry in a few minutes.`
|
|
969
|
-
);
|
|
970
|
-
}
|
|
971
|
-
if (!res.ok) {
|
|
972
|
-
const text = await res.text().catch(() => "");
|
|
973
|
-
throw new Error(`Cursor API error ${res.status}: ${text}`);
|
|
974
|
-
}
|
|
975
|
-
return res.json();
|
|
976
|
-
}
|
|
977
|
-
extractArtifacts(res) {
|
|
978
|
-
const urls = [];
|
|
979
|
-
if (Array.isArray(res.messages)) {
|
|
980
|
-
for (const msg of res.messages) {
|
|
981
|
-
if (typeof msg.content === "string") {
|
|
982
|
-
const matches = msg.content.match(/https:\/\/[^\s)]+\.(png|jpg|mp4|webm)/g);
|
|
983
|
-
if (matches) urls.push(...matches);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
if (Array.isArray(res.artifacts)) {
|
|
988
|
-
for (const a of res.artifacts) {
|
|
989
|
-
if (a.url) urls.push(a.url);
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
return [...new Set(urls)];
|
|
993
|
-
}
|
|
994
|
-
};
|
|
995
|
-
function mapStatus(raw) {
|
|
996
|
-
switch (raw) {
|
|
997
|
-
case "pending":
|
|
998
|
-
case "queued":
|
|
999
|
-
return "pending";
|
|
1000
|
-
case "running":
|
|
1001
|
-
case "in_progress":
|
|
1002
|
-
return "running";
|
|
1003
|
-
case "completed":
|
|
1004
|
-
case "succeeded":
|
|
1005
|
-
case "verified":
|
|
1006
|
-
return "completed";
|
|
1007
|
-
default:
|
|
1008
|
-
return "failed";
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
function extractPrUrl(res) {
|
|
1012
|
-
if (typeof res.prUrl === "string") return res.prUrl;
|
|
1013
|
-
if (typeof res.pr_url === "string") return res.pr_url;
|
|
1014
|
-
if (typeof res.pullRequestUrl === "string") return res.pullRequestUrl;
|
|
1015
|
-
if (Array.isArray(res.messages)) {
|
|
1016
|
-
for (const msg of res.messages) {
|
|
1017
|
-
if (typeof msg.content === "string") {
|
|
1018
|
-
const match = msg.content.match(/https:\/\/github\.com\/[^\s)]+\/pull\/\d+/);
|
|
1019
|
-
if (match) return match[0];
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
return void 0;
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
// src/tools/cloud-fix.ts
|
|
1027
|
-
var TrieCloudFixTool = class {
|
|
1028
|
-
async execute(args) {
|
|
1029
|
-
const { action = "status" } = args || {};
|
|
1030
|
-
switch (action) {
|
|
1031
|
-
case "configure":
|
|
1032
|
-
return this.configure(args);
|
|
1033
|
-
case "dispatch":
|
|
1034
|
-
return this.dispatch(args);
|
|
1035
|
-
case "status":
|
|
1036
|
-
return this.status();
|
|
1037
|
-
case "artifacts":
|
|
1038
|
-
return this.artifacts(args);
|
|
1039
|
-
case "cancel":
|
|
1040
|
-
return this.cancel(args);
|
|
1041
|
-
default:
|
|
1042
|
-
return this.text(`Unknown action "${action}". Use: configure | dispatch | status | artifacts | cancel`);
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
// --------------------------------------------------------------------------
|
|
1046
|
-
// configure
|
|
1047
|
-
// --------------------------------------------------------------------------
|
|
1048
|
-
async configure(args) {
|
|
1049
|
-
const apiKey = args?.apiKey || process.env.CURSOR_API_KEY;
|
|
1050
|
-
if (!apiKey) {
|
|
1051
|
-
return this.text(
|
|
1052
|
-
'Missing API key.\n\nUsage: trie_cloud_fix action:configure apiKey:"key-..."\nOr set: export CURSOR_API_KEY="key-..."'
|
|
1053
|
-
);
|
|
1054
|
-
}
|
|
1055
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
1056
|
-
await saveAutonomyConfig(workDir, { cloudAgentEnabled: true, cursorApiKey: apiKey });
|
|
1057
|
-
return this.text("Cursor API key saved. Cloud agent dispatch enabled.");
|
|
1058
|
-
}
|
|
1059
|
-
// --------------------------------------------------------------------------
|
|
1060
|
-
// dispatch
|
|
1061
|
-
// --------------------------------------------------------------------------
|
|
1062
|
-
async dispatch(args) {
|
|
1063
|
-
console.log("Cloud dispatch starting...");
|
|
1064
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
1065
|
-
const apiKey = await this.resolveApiKey(workDir);
|
|
1066
|
-
if (!apiKey) return this.setupGuard();
|
|
1067
|
-
const config = await loadAutonomyConfig(workDir);
|
|
1068
|
-
console.log("About to resolve issues...");
|
|
1069
|
-
const allIssues = await this.resolveIssues(args?.issueIds);
|
|
1070
|
-
console.log(`Resolved ${allIssues.length} issues`);
|
|
1071
|
-
if (allIssues.length === 0) {
|
|
1072
|
-
return this.text("No issues to dispatch. Run trie_scan to detect new issues, or check memory with trie_memory action:recent.");
|
|
1073
|
-
}
|
|
1074
|
-
const { results, summary } = triageIssues(allIssues, void 0, void 0, config);
|
|
1075
|
-
const lines = [];
|
|
1076
|
-
lines.push(formatTriageTable(results, allIssues));
|
|
1077
|
-
lines.push("");
|
|
1078
|
-
for (const issue of allIssues) {
|
|
1079
|
-
const r = results.get(issue.id);
|
|
1080
|
-
if (r && r.strategy !== "cloud-agent") {
|
|
1081
|
-
lines.push(`Skipped ${issue.id}: routed to ${r.strategy} (score ${r.score >= 0 ? "+" : ""}${r.score} \u2014 ${r.reasons.join(", ")})`);
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
if (summary.cloudAgent.length === 0) {
|
|
1085
|
-
lines.push("\nNo issues qualify for cloud dispatch. Use trie_fix for local fixes.");
|
|
1086
|
-
return this.text(lines.join("\n"));
|
|
1087
|
-
}
|
|
1088
|
-
const client = new CursorCloudAgentClient(apiKey);
|
|
1089
|
-
const repoUrl = this.getRepoUrl(workDir);
|
|
1090
|
-
const branch = this.getBranch(workDir);
|
|
1091
|
-
const store = await this.loadJobs(workDir);
|
|
1092
|
-
lines.push("\nDISPATCHED");
|
|
1093
|
-
for (const issue of summary.cloudAgent) {
|
|
1094
|
-
const triageResult = results.get(issue.id);
|
|
1095
|
-
try {
|
|
1096
|
-
const job = await client.dispatch(issue, triageResult, repoUrl, branch);
|
|
1097
|
-
store.jobs[issue.id] = {
|
|
1098
|
-
issueId: issue.id,
|
|
1099
|
-
jobId: job.jobId,
|
|
1100
|
-
status: "dispatched",
|
|
1101
|
-
score: triageResult.score,
|
|
1102
|
-
strategy: "cloud-agent",
|
|
1103
|
-
dispatchedAt: job.dispatchedAt,
|
|
1104
|
-
artifactUrls: [],
|
|
1105
|
-
prUrl: null
|
|
1106
|
-
};
|
|
1107
|
-
lines.push(` ${issue.id} ${shortPath2(issue.file)}:${issue.line ?? "?"} job:${job.jobId} (score +${triageResult.score})`);
|
|
1108
|
-
} catch (err) {
|
|
1109
|
-
lines.push(` ${issue.id} FAILED: ${err.message}`);
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
await this.saveJobs(workDir, store);
|
|
1113
|
-
lines.push("");
|
|
1114
|
-
lines.push("Cloud agents are running in isolated VMs. Check back with:");
|
|
1115
|
-
lines.push("trie_cloud_fix action:status");
|
|
1116
|
-
return this.text(lines.join("\n"));
|
|
1117
|
-
}
|
|
1118
|
-
// --------------------------------------------------------------------------
|
|
1119
|
-
// status
|
|
1120
|
-
// --------------------------------------------------------------------------
|
|
1121
|
-
async status() {
|
|
1122
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
1123
|
-
const apiKey = await this.resolveApiKey(workDir);
|
|
1124
|
-
if (!apiKey) return this.setupGuard();
|
|
1125
|
-
const store = await this.loadJobs(workDir);
|
|
1126
|
-
const entries = Object.values(store.jobs);
|
|
1127
|
-
if (entries.length === 0) {
|
|
1128
|
-
return this.text("No cloud jobs. Dispatch with: trie_cloud_fix action:dispatch");
|
|
1129
|
-
}
|
|
1130
|
-
const client = new CursorCloudAgentClient(apiKey);
|
|
1131
|
-
const LINE = "\u2500".repeat(68);
|
|
1132
|
-
const lines = ["JOB STATUS", LINE];
|
|
1133
|
-
lines.push(padEnd2("Issue", 32) + padEnd2("Status", 12) + padEnd2("PR", 28) + "Age");
|
|
1134
|
-
for (const job of entries) {
|
|
1135
|
-
if (job.status === "dispatched" || job.status === "running") {
|
|
1136
|
-
try {
|
|
1137
|
-
const poll = await client.poll(job.jobId);
|
|
1138
|
-
if (poll.status === "completed" && poll.prUrl) {
|
|
1139
|
-
job.status = "verified";
|
|
1140
|
-
job.prUrl = poll.prUrl;
|
|
1141
|
-
} else if (poll.status === "running") {
|
|
1142
|
-
job.status = "running";
|
|
1143
|
-
} else if (poll.status === "failed") {
|
|
1144
|
-
job.status = "failed";
|
|
1145
|
-
}
|
|
1146
|
-
if (poll.artifactUrls.length) {
|
|
1147
|
-
job.artifactUrls = poll.artifactUrls;
|
|
1148
|
-
}
|
|
1149
|
-
} catch {
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
const age = formatAge(job.dispatchedAt);
|
|
1153
|
-
const pr = job.prUrl ?? "\u2014";
|
|
1154
|
-
lines.push(padEnd2(job.issueId, 32) + padEnd2(job.status, 12) + padEnd2(pr, 28) + age);
|
|
1155
|
-
}
|
|
1156
|
-
lines.push(LINE);
|
|
1157
|
-
await this.saveJobs(workDir, store);
|
|
1158
|
-
return this.text(lines.join("\n"));
|
|
1159
|
-
}
|
|
1160
|
-
// --------------------------------------------------------------------------
|
|
1161
|
-
// artifacts
|
|
1162
|
-
// --------------------------------------------------------------------------
|
|
1163
|
-
async artifacts(args) {
|
|
1164
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
1165
|
-
const apiKey = await this.resolveApiKey(workDir);
|
|
1166
|
-
if (!apiKey) return this.setupGuard();
|
|
1167
|
-
const store = await this.loadJobs(workDir);
|
|
1168
|
-
const jobId = args?.jobId;
|
|
1169
|
-
const targets = jobId ? Object.values(store.jobs).filter((j) => j.jobId === jobId) : Object.values(store.jobs).filter((j) => j.status === "verified");
|
|
1170
|
-
if (targets.length === 0) {
|
|
1171
|
-
return this.text(jobId ? `No job found with id ${jobId}` : "No completed jobs with artifacts.");
|
|
1172
|
-
}
|
|
1173
|
-
const client = new CursorCloudAgentClient(apiKey);
|
|
1174
|
-
const lines = [];
|
|
1175
|
-
for (const job of targets) {
|
|
1176
|
-
try {
|
|
1177
|
-
const artifacts = await client.getArtifacts(job.jobId);
|
|
1178
|
-
job.artifactUrls = artifacts;
|
|
1179
|
-
} catch {
|
|
1180
|
-
}
|
|
1181
|
-
lines.push(`Issue: ${job.issueId}`);
|
|
1182
|
-
lines.push(`PR: ${job.prUrl ?? "N/A"}`);
|
|
1183
|
-
for (const url of job.artifactUrls) {
|
|
1184
|
-
const ext = url.split(".").pop();
|
|
1185
|
-
const label = ext === "mp4" || ext === "webm" ? "Video" : "Screenshot";
|
|
1186
|
-
lines.push(`${label}: ${url}`);
|
|
1187
|
-
}
|
|
1188
|
-
if (job.status === "verified") {
|
|
1189
|
-
lines.push("Agent verified: all tests pass with this fix applied.");
|
|
1190
|
-
}
|
|
1191
|
-
lines.push("");
|
|
1192
|
-
}
|
|
1193
|
-
await this.saveJobs(workDir, store);
|
|
1194
|
-
return this.text(lines.join("\n"));
|
|
1195
|
-
}
|
|
1196
|
-
// --------------------------------------------------------------------------
|
|
1197
|
-
// cancel
|
|
1198
|
-
// --------------------------------------------------------------------------
|
|
1199
|
-
async cancel(args) {
|
|
1200
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
1201
|
-
const apiKey = await this.resolveApiKey(workDir);
|
|
1202
|
-
if (!apiKey) return this.setupGuard();
|
|
1203
|
-
const jobId = args?.jobId;
|
|
1204
|
-
if (!jobId) {
|
|
1205
|
-
return this.text('Provide jobId to cancel. Example: trie_cloud_fix action:cancel jobId:"cursor-task-xyz"');
|
|
1206
|
-
}
|
|
1207
|
-
const store = await this.loadJobs(workDir);
|
|
1208
|
-
const entry = Object.values(store.jobs).find((j) => j.jobId === jobId);
|
|
1209
|
-
if (!entry) {
|
|
1210
|
-
return this.text(`Job ${jobId} not found in cloud-jobs.json.`);
|
|
1211
|
-
}
|
|
1212
|
-
const client = new CursorCloudAgentClient(apiKey);
|
|
1213
|
-
try {
|
|
1214
|
-
await client.cancelJob(jobId);
|
|
1215
|
-
} catch (err) {
|
|
1216
|
-
return this.text(`Cancel failed: ${err.message}`);
|
|
1217
|
-
}
|
|
1218
|
-
delete store.jobs[entry.issueId];
|
|
1219
|
-
await this.saveJobs(workDir, store);
|
|
1220
|
-
return this.text(`Job ${jobId} cancelled and removed.`);
|
|
1221
|
-
}
|
|
1222
|
-
// --------------------------------------------------------------------------
|
|
1223
|
-
// Helpers
|
|
1224
|
-
// --------------------------------------------------------------------------
|
|
1225
|
-
async resolveApiKey(workDir) {
|
|
1226
|
-
if (process.env.CURSOR_API_KEY) return process.env.CURSOR_API_KEY;
|
|
1227
|
-
const config = await loadAutonomyConfig(workDir);
|
|
1228
|
-
return config.cursorApiKey ?? null;
|
|
1229
|
-
}
|
|
1230
|
-
setupGuard() {
|
|
1231
|
-
return this.text(
|
|
1232
|
-
'Cloud Agent dispatch requires a Cursor API key.\n\nGet your key at: cursor.com/settings \u2192 API Keys\nThen run: trie_cloud_fix action:configure apiKey:"key-..."\n\nOr set the environment variable: export CURSOR_API_KEY="key-..."'
|
|
1233
|
-
);
|
|
1234
|
-
}
|
|
1235
|
-
async resolveIssues(issueIds) {
|
|
1236
|
-
let pending = getPendingFixes();
|
|
1237
|
-
if (pending.length === 0) {
|
|
1238
|
-
try {
|
|
1239
|
-
console.log("Loading issues from memory...");
|
|
1240
|
-
const { getRecentIssues: getRecentIssues2 } = await import("./issue-store-LZWZIGM7.js");
|
|
1241
|
-
const recentIssues = await getRecentIssues2({ limit: 50, includeResolved: false });
|
|
1242
|
-
console.log(`Found ${recentIssues.length} recent issues in memory`);
|
|
1243
|
-
const memoryIssues = recentIssues.map((storedIssue) => ({
|
|
1244
|
-
id: storedIssue.id,
|
|
1245
|
-
severity: storedIssue.severity || "moderate",
|
|
1246
|
-
issue: storedIssue.issue,
|
|
1247
|
-
fix: storedIssue.fix,
|
|
1248
|
-
file: storedIssue.file,
|
|
1249
|
-
line: storedIssue.line,
|
|
1250
|
-
confidence: 0.8,
|
|
1251
|
-
// Default confidence for memory issues
|
|
1252
|
-
autoFixable: true,
|
|
1253
|
-
// Memory issues are generally auto-fixable
|
|
1254
|
-
agent: storedIssue.agent,
|
|
1255
|
-
category: storedIssue.category
|
|
1256
|
-
}));
|
|
1257
|
-
console.log(`Converted ${memoryIssues.length} memory issues for cloud dispatch`);
|
|
1258
|
-
if (issueIds && issueIds.length > 0) {
|
|
1259
|
-
return memoryIssues.filter((i) => issueIds.includes(i.id));
|
|
1260
|
-
}
|
|
1261
|
-
return memoryIssues;
|
|
1262
|
-
} catch (error) {
|
|
1263
|
-
console.warn("Failed to load issues from memory:", error);
|
|
1264
|
-
console.warn("Error details:", error);
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
const issues = pending.map((p) => ({
|
|
1268
|
-
id: p.id,
|
|
1269
|
-
severity: p.severity ?? "moderate",
|
|
1270
|
-
effort: p.effort,
|
|
1271
|
-
issue: p.issue,
|
|
1272
|
-
fix: p.suggestedFix,
|
|
1273
|
-
file: p.file,
|
|
1274
|
-
line: p.line,
|
|
1275
|
-
confidence: p.confidence,
|
|
1276
|
-
autoFixable: p.autoFixable ?? false,
|
|
1277
|
-
agent: "trie_scan",
|
|
1278
|
-
cwe: p.cwe,
|
|
1279
|
-
owasp: p.owasp,
|
|
1280
|
-
category: p.category
|
|
1281
|
-
}));
|
|
1282
|
-
if (issueIds && issueIds.length > 0) {
|
|
1283
|
-
return issues.filter((i) => issueIds.includes(i.id));
|
|
1284
|
-
}
|
|
1285
|
-
return issues;
|
|
1286
|
-
}
|
|
1287
|
-
getRepoUrl(workDir) {
|
|
1288
|
-
try {
|
|
1289
|
-
return execSync("git remote get-url origin", { cwd: workDir, encoding: "utf-8" }).trim();
|
|
1290
|
-
} catch {
|
|
1291
|
-
return "unknown";
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
getBranch(workDir) {
|
|
1295
|
-
try {
|
|
1296
|
-
return execSync("git rev-parse --abbrev-ref HEAD", { cwd: workDir, encoding: "utf-8" }).trim();
|
|
1297
|
-
} catch {
|
|
1298
|
-
return "main";
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
async loadJobs(workDir) {
|
|
1302
|
-
const path2 = join(getTrieDirectory(workDir), "cloud-jobs.json");
|
|
1303
|
-
try {
|
|
1304
|
-
if (existsSync2(path2)) {
|
|
1305
|
-
const raw = await readFile2(path2, "utf-8");
|
|
1306
|
-
return JSON.parse(raw);
|
|
1307
|
-
}
|
|
1308
|
-
} catch {
|
|
1309
|
-
}
|
|
1310
|
-
return { jobs: {} };
|
|
1311
|
-
}
|
|
1312
|
-
async saveJobs(workDir, store) {
|
|
1313
|
-
const trieDir = getTrieDirectory(workDir);
|
|
1314
|
-
if (!existsSync2(trieDir)) await mkdir(trieDir, { recursive: true });
|
|
1315
|
-
const path2 = join(trieDir, "cloud-jobs.json");
|
|
1316
|
-
await writeFile(path2, JSON.stringify(store, null, 2));
|
|
1317
|
-
}
|
|
1318
|
-
text(msg) {
|
|
1319
|
-
return { content: [{ type: "text", text: msg }] };
|
|
1320
|
-
}
|
|
1321
|
-
};
|
|
1322
|
-
function formatAge(isoDate) {
|
|
1323
|
-
const ms = Date.now() - new Date(isoDate).getTime();
|
|
1324
|
-
const mins = Math.floor(ms / 6e4);
|
|
1325
|
-
if (mins < 60) return `${mins}m`;
|
|
1326
|
-
const hrs = Math.floor(mins / 60);
|
|
1327
|
-
if (hrs < 24) return `${hrs}h ${mins % 60}m`;
|
|
1328
|
-
return `${Math.floor(hrs / 24)}d`;
|
|
1329
|
-
}
|
|
1330
|
-
function shortPath2(file) {
|
|
1331
|
-
const parts = file.split("/");
|
|
1332
|
-
return parts.length > 2 ? parts.slice(-2).join("/") : file;
|
|
1333
|
-
}
|
|
1334
|
-
function padEnd2(str, len) {
|
|
1335
|
-
if (str.length >= len) return str.slice(0, len);
|
|
1336
|
-
return str + " ".repeat(len - str.length);
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
// src/tools/test.ts
|
|
1340
|
-
import { readFile as readFile3 } from "fs/promises";
|
|
1341
|
-
import { existsSync as existsSync3 } from "fs";
|
|
1342
|
-
import { extname as extname2, relative as relative2, resolve as resolve2, isAbsolute as isAbsolute2, dirname, basename, join as join2 } from "path";
|
|
201
|
+
import { extname, relative, resolve, isAbsolute, dirname, basename, join } from "path";
|
|
1343
202
|
var TrieTestTool = class {
|
|
1344
203
|
async execute(args) {
|
|
1345
204
|
const { action, files, framework, style = "unit" } = args || {};
|
|
@@ -1392,15 +251,15 @@ ${"\u2501".repeat(60)}
|
|
|
1392
251
|
const workDir = getWorkingDirectory(void 0, true);
|
|
1393
252
|
const allUnits = [];
|
|
1394
253
|
for (const file of files) {
|
|
1395
|
-
const resolvedPath =
|
|
1396
|
-
if (!
|
|
254
|
+
const resolvedPath = isAbsolute(file) ? file : resolve(workDir, file);
|
|
255
|
+
if (!existsSync(resolvedPath)) {
|
|
1397
256
|
output += `[!] File not found: ${file}
|
|
1398
257
|
`;
|
|
1399
258
|
continue;
|
|
1400
259
|
}
|
|
1401
|
-
const code = await
|
|
260
|
+
const code = await readFile(resolvedPath, "utf-8");
|
|
1402
261
|
const language = this.detectLanguage(resolvedPath);
|
|
1403
|
-
const relativePath =
|
|
262
|
+
const relativePath = relative(workDir, resolvedPath);
|
|
1404
263
|
const units = this.extractTestableUnits(code, language);
|
|
1405
264
|
allUnits.push({ file: relativePath, units });
|
|
1406
265
|
output += `### \u{1F4C4} ${relativePath}
|
|
@@ -1431,7 +290,7 @@ ${"\u2501".repeat(60)}
|
|
|
1431
290
|
`;
|
|
1432
291
|
for (const { file, units } of allUnits) {
|
|
1433
292
|
if (units.length === 0) continue;
|
|
1434
|
-
const code = await
|
|
293
|
+
const code = await readFile(resolve(workDir, file), "utf-8");
|
|
1435
294
|
const language = this.detectLanguage(file);
|
|
1436
295
|
const prompt = getPrompt("test", "generate", {
|
|
1437
296
|
code,
|
|
@@ -1476,21 +335,21 @@ ${"\u2501".repeat(60)}
|
|
|
1476
335
|
`;
|
|
1477
336
|
const workDir = getWorkingDirectory(void 0, true);
|
|
1478
337
|
for (const file of files) {
|
|
1479
|
-
const resolvedPath =
|
|
1480
|
-
if (!
|
|
338
|
+
const resolvedPath = isAbsolute(file) ? file : resolve(workDir, file);
|
|
339
|
+
if (!existsSync(resolvedPath)) {
|
|
1481
340
|
output += `[!] File not found: ${file}
|
|
1482
341
|
`;
|
|
1483
342
|
continue;
|
|
1484
343
|
}
|
|
1485
|
-
const code = await
|
|
344
|
+
const code = await readFile(resolvedPath, "utf-8");
|
|
1486
345
|
const language = this.detectLanguage(resolvedPath);
|
|
1487
|
-
const relativePath =
|
|
346
|
+
const relativePath = relative(workDir, resolvedPath);
|
|
1488
347
|
const units = this.extractTestableUnits(code, language);
|
|
1489
348
|
const testFile = await this.findTestFile(resolvedPath);
|
|
1490
349
|
let testCode = "";
|
|
1491
350
|
let testedUnits = [];
|
|
1492
351
|
if (testFile) {
|
|
1493
|
-
testCode = await
|
|
352
|
+
testCode = await readFile(testFile, "utf-8");
|
|
1494
353
|
testedUnits = this.findTestedUnits(testCode, units.map((u) => u.name));
|
|
1495
354
|
}
|
|
1496
355
|
const coverage = units.length > 0 ? Math.round(testedUnits.length / units.length * 100) : 0;
|
|
@@ -1501,7 +360,7 @@ ${"\u2501".repeat(60)}
|
|
|
1501
360
|
output += `**Coverage:** ${coverageIcon} ${coverage}% (${testedUnits.length}/${units.length} units)
|
|
1502
361
|
`;
|
|
1503
362
|
if (testFile) {
|
|
1504
|
-
output += `**Test file:** \`${
|
|
363
|
+
output += `**Test file:** \`${relative(workDir, testFile)}\`
|
|
1505
364
|
`;
|
|
1506
365
|
} else {
|
|
1507
366
|
output += `**Test file:** \u274C Not found
|
|
@@ -1558,14 +417,14 @@ ${"\u2501".repeat(60)}
|
|
|
1558
417
|
`;
|
|
1559
418
|
const workDir = getWorkingDirectory(void 0, true);
|
|
1560
419
|
for (const file of files) {
|
|
1561
|
-
const resolvedPath =
|
|
1562
|
-
if (!
|
|
420
|
+
const resolvedPath = isAbsolute(file) ? file : resolve(workDir, file);
|
|
421
|
+
if (!existsSync(resolvedPath)) {
|
|
1563
422
|
output += `[!] File not found: ${file}
|
|
1564
423
|
`;
|
|
1565
424
|
continue;
|
|
1566
425
|
}
|
|
1567
|
-
const code = await
|
|
1568
|
-
const relativePath =
|
|
426
|
+
const code = await readFile(resolvedPath, "utf-8");
|
|
427
|
+
const relativePath = relative(workDir, resolvedPath);
|
|
1569
428
|
const patterns = this.detectTestablePatterns(code);
|
|
1570
429
|
output += `### \u{1F4C4} ${relativePath}
|
|
1571
430
|
|
|
@@ -1801,8 +660,8 @@ ${"\u2501".repeat(60)}
|
|
|
1801
660
|
}
|
|
1802
661
|
async findTestFile(sourcePath) {
|
|
1803
662
|
const dir = dirname(sourcePath);
|
|
1804
|
-
const base = basename(sourcePath,
|
|
1805
|
-
const ext =
|
|
663
|
+
const base = basename(sourcePath, extname(sourcePath));
|
|
664
|
+
const ext = extname(sourcePath);
|
|
1806
665
|
const patterns = [
|
|
1807
666
|
`${base}.test${ext}`,
|
|
1808
667
|
`${base}.spec${ext}`,
|
|
@@ -1810,16 +669,16 @@ ${"\u2501".repeat(60)}
|
|
|
1810
669
|
`test_${base}${ext}`
|
|
1811
670
|
];
|
|
1812
671
|
for (const pattern of patterns) {
|
|
1813
|
-
const testPath =
|
|
1814
|
-
if (
|
|
672
|
+
const testPath = join(dir, pattern);
|
|
673
|
+
if (existsSync(testPath)) {
|
|
1815
674
|
return testPath;
|
|
1816
675
|
}
|
|
1817
676
|
}
|
|
1818
|
-
const testsDir =
|
|
1819
|
-
if (
|
|
677
|
+
const testsDir = join(dir, "__tests__");
|
|
678
|
+
if (existsSync(testsDir)) {
|
|
1820
679
|
for (const pattern of patterns) {
|
|
1821
|
-
const testPath =
|
|
1822
|
-
if (
|
|
680
|
+
const testPath = join(testsDir, pattern);
|
|
681
|
+
if (existsSync(testPath)) {
|
|
1823
682
|
return testPath;
|
|
1824
683
|
}
|
|
1825
684
|
}
|
|
@@ -1866,10 +725,10 @@ ${"\u2501".repeat(60)}
|
|
|
1866
725
|
}
|
|
1867
726
|
async detectTestFramework() {
|
|
1868
727
|
const workDir = getWorkingDirectory(void 0, true);
|
|
1869
|
-
const packagePath =
|
|
1870
|
-
if (
|
|
728
|
+
const packagePath = resolve(workDir, "package.json");
|
|
729
|
+
if (existsSync(packagePath)) {
|
|
1871
730
|
try {
|
|
1872
|
-
const pkg = JSON.parse(await
|
|
731
|
+
const pkg = JSON.parse(await readFile(packagePath, "utf-8"));
|
|
1873
732
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1874
733
|
if (deps.vitest) return "vitest";
|
|
1875
734
|
if (deps.jest) return "jest";
|
|
@@ -1877,13 +736,13 @@ ${"\u2501".repeat(60)}
|
|
|
1877
736
|
} catch {
|
|
1878
737
|
}
|
|
1879
738
|
}
|
|
1880
|
-
if (
|
|
739
|
+
if (existsSync(resolve(workDir, "pytest.ini")) || existsSync(resolve(workDir, "pyproject.toml"))) {
|
|
1881
740
|
return "pytest";
|
|
1882
741
|
}
|
|
1883
742
|
return "jest";
|
|
1884
743
|
}
|
|
1885
744
|
detectLanguage(filePath) {
|
|
1886
|
-
const ext =
|
|
745
|
+
const ext = extname(filePath).toLowerCase();
|
|
1887
746
|
const langMap = {
|
|
1888
747
|
".ts": "typescript",
|
|
1889
748
|
".tsx": "tsx",
|
|
@@ -1936,9 +795,9 @@ trie_test action:"run" files:["src/utils.test.ts"]
|
|
|
1936
795
|
};
|
|
1937
796
|
|
|
1938
797
|
// src/tools/watch.ts
|
|
1939
|
-
import { watch, existsSync as
|
|
1940
|
-
import { stat, readFile as
|
|
1941
|
-
import { join as
|
|
798
|
+
import { watch, existsSync as existsSync2, readFileSync } from "fs";
|
|
799
|
+
import { stat, readFile as readFile2 } from "fs/promises";
|
|
800
|
+
import { join as join2, extname as extname2, basename as basename2 } from "path";
|
|
1942
801
|
import { createHash } from "crypto";
|
|
1943
802
|
var WATCH_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1944
803
|
".ts",
|
|
@@ -2156,17 +1015,17 @@ Your Trie agent is now autonomously watching and learning from your codebase.
|
|
|
2156
1015
|
return parts.some((p) => SKIP_DIRS.has(p) || p.startsWith(".") && p !== ".");
|
|
2157
1016
|
}
|
|
2158
1017
|
async watchDirectory(dir, debounceMs) {
|
|
2159
|
-
if (!
|
|
1018
|
+
if (!existsSync2(dir)) return;
|
|
2160
1019
|
try {
|
|
2161
1020
|
const dirStat = await stat(dir);
|
|
2162
1021
|
if (!dirStat.isDirectory()) return;
|
|
2163
1022
|
const watcher = watch(dir, { persistent: true, recursive: true }, (_eventType, filename) => {
|
|
2164
1023
|
if (!filename) return;
|
|
2165
1024
|
if (this.shouldSkipPath(filename)) return;
|
|
2166
|
-
const ext =
|
|
1025
|
+
const ext = extname2(filename).toLowerCase();
|
|
2167
1026
|
if (!WATCH_EXTENSIONS.has(ext)) return;
|
|
2168
|
-
const fullPath =
|
|
2169
|
-
if (!
|
|
1027
|
+
const fullPath = join2(dir, filename);
|
|
1028
|
+
if (!existsSync2(fullPath)) return;
|
|
2170
1029
|
this.state.pendingFiles.add(fullPath);
|
|
2171
1030
|
if (this.state.scanDebounceTimer) {
|
|
2172
1031
|
clearTimeout(this.state.scanDebounceTimer);
|
|
@@ -2212,7 +1071,7 @@ Detected changes in ${files.length} file(s):`);
|
|
|
2212
1071
|
const fileContents = await Promise.all(
|
|
2213
1072
|
files.map(async (file) => {
|
|
2214
1073
|
try {
|
|
2215
|
-
const content = await
|
|
1074
|
+
const content = await readFile2(file, "utf-8");
|
|
2216
1075
|
return { file, content };
|
|
2217
1076
|
} catch {
|
|
2218
1077
|
return null;
|
|
@@ -2290,7 +1149,7 @@ ${f.content.slice(0, 1e3)}`
|
|
|
2290
1149
|
}
|
|
2291
1150
|
isQuiet() {
|
|
2292
1151
|
const projectPath = this.watchedDirectory || getWorkingDirectory(void 0, true);
|
|
2293
|
-
const quietPath =
|
|
1152
|
+
const quietPath = join2(getTrieDirectory(projectPath), "quiet.json");
|
|
2294
1153
|
try {
|
|
2295
1154
|
const raw = readFileSync(quietPath, "utf-8");
|
|
2296
1155
|
const data = JSON.parse(raw);
|
|
@@ -2458,7 +1317,7 @@ ${f.content.slice(0, 1e3)}`
|
|
|
2458
1317
|
if (lastClean && Date.now() - lastClean < this.cleanFileCooldownMs) {
|
|
2459
1318
|
return 0;
|
|
2460
1319
|
}
|
|
2461
|
-
const fileNode = await graph.getNode("file",
|
|
1320
|
+
const fileNode = await graph.getNode("file", join2(projectPath, file));
|
|
2462
1321
|
if (!fileNode) return score;
|
|
2463
1322
|
const data = fileNode.data;
|
|
2464
1323
|
const riskScores = { critical: 10, high: 6, medium: 2, low: 1 };
|
|
@@ -2546,7 +1405,7 @@ ${f.content.slice(0, 1e3)}`
|
|
|
2546
1405
|
const fileContents = await Promise.all(
|
|
2547
1406
|
filesToScan.map(async ({ file, relativePath }) => {
|
|
2548
1407
|
try {
|
|
2549
|
-
const content = await
|
|
1408
|
+
const content = await readFile2(file, "utf-8");
|
|
2550
1409
|
return { path: relativePath, content: content.slice(0, charLimit) };
|
|
2551
1410
|
} catch {
|
|
2552
1411
|
return null;
|
|
@@ -2711,7 +1570,7 @@ ${filesBlock}`,
|
|
|
2711
1570
|
fixChangeId: null,
|
|
2712
1571
|
reportedVia: "detected"
|
|
2713
1572
|
});
|
|
2714
|
-
const filePath =
|
|
1573
|
+
const filePath = join2(projectPath, issue.file);
|
|
2715
1574
|
const fileNode = await graph.getNode("file", filePath);
|
|
2716
1575
|
if (fileNode) {
|
|
2717
1576
|
await graph.addEdge(fileNode.id, incident.id, "affects");
|
|
@@ -2843,7 +1702,7 @@ ${filesBlock}`,
|
|
|
2843
1702
|
const recentFiles = /* @__PURE__ */ new Set();
|
|
2844
1703
|
const uncommittedFiles = await getGitChangedFiles(projectPath);
|
|
2845
1704
|
if (uncommittedFiles) {
|
|
2846
|
-
uncommittedFiles.forEach((f) => recentFiles.add(
|
|
1705
|
+
uncommittedFiles.forEach((f) => recentFiles.add(join2(projectPath, f)));
|
|
2847
1706
|
}
|
|
2848
1707
|
const oneDayAgo = Date.now() - 24 * 60 * 60 * 1e3;
|
|
2849
1708
|
const recentChanges = await getChangedFilesSinceTimestamp(projectPath, oneDayAgo);
|
|
@@ -2851,8 +1710,8 @@ ${filesBlock}`,
|
|
|
2851
1710
|
recentChanges.forEach((f) => recentFiles.add(f));
|
|
2852
1711
|
}
|
|
2853
1712
|
const filesToCheck = Array.from(recentFiles).filter((file) => {
|
|
2854
|
-
const ext =
|
|
2855
|
-
return WATCH_EXTENSIONS.has(ext) &&
|
|
1713
|
+
const ext = extname2(file).toLowerCase();
|
|
1714
|
+
return WATCH_EXTENSIONS.has(ext) && existsSync2(file);
|
|
2856
1715
|
});
|
|
2857
1716
|
console.debug("[Initial Scan] Files discovered for initial scan:", {
|
|
2858
1717
|
totalRecentFiles: recentFiles.size,
|
|
@@ -2874,7 +1733,7 @@ ${filesBlock}`,
|
|
|
2874
1733
|
const fileContents = await Promise.all(
|
|
2875
1734
|
filesToScan.map(async (file) => {
|
|
2876
1735
|
try {
|
|
2877
|
-
const content = await
|
|
1736
|
+
const content = await readFile2(file, "utf-8");
|
|
2878
1737
|
const relativePath = file.replace(projectPath + "/", "");
|
|
2879
1738
|
return { path: relativePath, content: content.slice(0, maxCharsPerFile) };
|
|
2880
1739
|
} catch {
|
|
@@ -3220,9 +2079,9 @@ To get a full report, run \`trie_scan\` on your codebase.`
|
|
|
3220
2079
|
};
|
|
3221
2080
|
|
|
3222
2081
|
// src/tools/pr-review.ts
|
|
3223
|
-
import { readFile as
|
|
3224
|
-
import { existsSync as
|
|
3225
|
-
import { join as
|
|
2082
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
2083
|
+
import { existsSync as existsSync3 } from "fs";
|
|
2084
|
+
import { join as join3, basename as basename3, resolve as resolve2, isAbsolute as isAbsolute2 } from "path";
|
|
3226
2085
|
|
|
3227
2086
|
// src/skills/built-in/super-reviewer.ts
|
|
3228
2087
|
var CRITICAL_REVIEW_CHECKLIST = {
|
|
@@ -3335,8 +2194,8 @@ Usage:
|
|
|
3335
2194
|
*/
|
|
3336
2195
|
async getPRInfo(pr, worktree) {
|
|
3337
2196
|
if (worktree) {
|
|
3338
|
-
const worktreePath =
|
|
3339
|
-
if (!
|
|
2197
|
+
const worktreePath = isAbsolute2(worktree) ? worktree : resolve2(getWorkingDirectory(void 0, true), worktree);
|
|
2198
|
+
if (!existsSync3(worktreePath)) {
|
|
3340
2199
|
return { success: false, error: `Worktree not found: ${worktreePath}` };
|
|
3341
2200
|
}
|
|
3342
2201
|
return {
|
|
@@ -3469,8 +2328,8 @@ Usage:
|
|
|
3469
2328
|
"rfcs"
|
|
3470
2329
|
];
|
|
3471
2330
|
for (const docPath of designDocPaths) {
|
|
3472
|
-
const fullPath =
|
|
3473
|
-
if (
|
|
2331
|
+
const fullPath = join3(cwd, docPath);
|
|
2332
|
+
if (existsSync3(fullPath)) {
|
|
3474
2333
|
}
|
|
3475
2334
|
}
|
|
3476
2335
|
return designDocs;
|
|
@@ -3483,9 +2342,9 @@ Usage:
|
|
|
3483
2342
|
const cwd = getWorkingDirectory(void 0, true);
|
|
3484
2343
|
await Promise.all(filePaths.map(async (filePath) => {
|
|
3485
2344
|
try {
|
|
3486
|
-
const fullPath =
|
|
3487
|
-
if (
|
|
3488
|
-
const content = await
|
|
2345
|
+
const fullPath = isAbsolute2(filePath) ? filePath : join3(cwd, filePath);
|
|
2346
|
+
if (existsSync3(fullPath)) {
|
|
2347
|
+
const content = await readFile3(fullPath, "utf-8");
|
|
3489
2348
|
contents.set(filePath, content);
|
|
3490
2349
|
}
|
|
3491
2350
|
} catch {
|
|
@@ -5246,9 +4105,9 @@ var ToolRegistry = class {
|
|
|
5246
4105
|
};
|
|
5247
4106
|
|
|
5248
4107
|
// src/server/resource-manager.ts
|
|
5249
|
-
import { readdir, readFile as
|
|
5250
|
-
import { existsSync as
|
|
5251
|
-
import { join as
|
|
4108
|
+
import { readdir, readFile as readFile4 } from "fs/promises";
|
|
4109
|
+
import { existsSync as existsSync4 } from "fs";
|
|
4110
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
5252
4111
|
import { fileURLToPath } from "url";
|
|
5253
4112
|
|
|
5254
4113
|
// src/skills/installer.ts
|
|
@@ -5373,7 +4232,7 @@ var ResourceManager = class {
|
|
|
5373
4232
|
}
|
|
5374
4233
|
async getScanReportResources() {
|
|
5375
4234
|
try {
|
|
5376
|
-
const reportsDir =
|
|
4235
|
+
const reportsDir = join4(getWorkingDirectory(void 0, true), "trie-reports");
|
|
5377
4236
|
const files = await readdir(reportsDir);
|
|
5378
4237
|
const reportFiles = files.filter((f) => f.endsWith(".txt") || f.endsWith(".json"));
|
|
5379
4238
|
return reportFiles.slice(0, 10).map((file) => ({
|
|
@@ -5473,10 +4332,10 @@ var ResourceManager = class {
|
|
|
5473
4332
|
async getUIAppResource(uri, appId) {
|
|
5474
4333
|
const currentFile = fileURLToPath(import.meta.url);
|
|
5475
4334
|
const distDir = dirname2(dirname2(currentFile));
|
|
5476
|
-
const uiDir =
|
|
5477
|
-
const htmlPath =
|
|
4335
|
+
const uiDir = join4(distDir, "ui");
|
|
4336
|
+
const htmlPath = join4(uiDir, `${appId}.html`);
|
|
5478
4337
|
try {
|
|
5479
|
-
if (!
|
|
4338
|
+
if (!existsSync4(htmlPath)) {
|
|
5480
4339
|
return {
|
|
5481
4340
|
contents: [{
|
|
5482
4341
|
uri,
|
|
@@ -5485,7 +4344,7 @@ var ResourceManager = class {
|
|
|
5485
4344
|
}]
|
|
5486
4345
|
};
|
|
5487
4346
|
}
|
|
5488
|
-
const content = await
|
|
4347
|
+
const content = await readFile4(htmlPath, "utf-8");
|
|
5489
4348
|
return {
|
|
5490
4349
|
contents: [{
|
|
5491
4350
|
uri,
|
|
@@ -5665,10 +4524,10 @@ var ResourceManager = class {
|
|
|
5665
4524
|
} catch {
|
|
5666
4525
|
}
|
|
5667
4526
|
summary.push("---", "", "# Detailed Context", "");
|
|
5668
|
-
const agentsMdPath =
|
|
4527
|
+
const agentsMdPath = join4(getTrieDirectory(workDir), "AGENTS.md");
|
|
5669
4528
|
try {
|
|
5670
|
-
if (
|
|
5671
|
-
const agentsContent = await
|
|
4529
|
+
if (existsSync4(agentsMdPath)) {
|
|
4530
|
+
const agentsContent = await readFile4(agentsMdPath, "utf-8");
|
|
5672
4531
|
summary.push(agentsContent);
|
|
5673
4532
|
} else {
|
|
5674
4533
|
const contextSummary = await getContextForAI();
|
|
@@ -5793,8 +4652,8 @@ This information is automatically available to Claude Code, Cursor, and other AI
|
|
|
5793
4652
|
}
|
|
5794
4653
|
async getCacheStatsResource(uri) {
|
|
5795
4654
|
try {
|
|
5796
|
-
const cachePath =
|
|
5797
|
-
const cacheContent = await
|
|
4655
|
+
const cachePath = join4(getTrieDirectory(getWorkingDirectory(void 0, true)), ".trie-cache.json");
|
|
4656
|
+
const cacheContent = await readFile4(cachePath, "utf-8");
|
|
5798
4657
|
const cache = JSON.parse(cacheContent);
|
|
5799
4658
|
const fileCount = Object.keys(cache.files || {}).length;
|
|
5800
4659
|
const totalVulns = Object.values(cache.files || {}).reduce((acc, file) => {
|
|
@@ -5873,9 +4732,9 @@ This information is automatically available to Claude Code, Cursor, and other AI
|
|
|
5873
4732
|
}
|
|
5874
4733
|
async getScanReportResource(uri, parsedUri) {
|
|
5875
4734
|
const fileName = parsedUri.replace("reports/", "");
|
|
5876
|
-
const reportPath =
|
|
4735
|
+
const reportPath = join4(getWorkingDirectory(void 0, true), "trie-reports", fileName);
|
|
5877
4736
|
try {
|
|
5878
|
-
const content = await
|
|
4737
|
+
const content = await readFile4(reportPath, "utf-8");
|
|
5879
4738
|
return {
|
|
5880
4739
|
contents: [{
|
|
5881
4740
|
uri,
|
|
@@ -6022,32 +4881,32 @@ async function findOpenPort() {
|
|
|
6022
4881
|
return portChecks.find((port) => port !== null) || null;
|
|
6023
4882
|
}
|
|
6024
4883
|
async function checkPort(port) {
|
|
6025
|
-
return new Promise((
|
|
4884
|
+
return new Promise((resolve3) => {
|
|
6026
4885
|
const testServer = createServer();
|
|
6027
4886
|
let portInUse = false;
|
|
6028
4887
|
testServer.once("error", (err) => {
|
|
6029
4888
|
if (err.code === "EADDRINUSE") {
|
|
6030
4889
|
portInUse = true;
|
|
6031
|
-
testHttpPort(port).then(
|
|
4890
|
+
testHttpPort(port).then(resolve3).catch(() => resolve3(false));
|
|
6032
4891
|
} else {
|
|
6033
|
-
|
|
4892
|
+
resolve3(false);
|
|
6034
4893
|
}
|
|
6035
4894
|
});
|
|
6036
4895
|
testServer.once("listening", () => {
|
|
6037
4896
|
testServer.close();
|
|
6038
|
-
|
|
4897
|
+
resolve3(false);
|
|
6039
4898
|
});
|
|
6040
4899
|
setTimeout(() => {
|
|
6041
4900
|
if (!portInUse) {
|
|
6042
4901
|
testServer.close();
|
|
6043
|
-
|
|
4902
|
+
resolve3(false);
|
|
6044
4903
|
}
|
|
6045
4904
|
}, 1e3);
|
|
6046
4905
|
testServer.listen(port, "127.0.0.1");
|
|
6047
4906
|
});
|
|
6048
4907
|
}
|
|
6049
4908
|
async function testHttpPort(port) {
|
|
6050
|
-
return new Promise((
|
|
4909
|
+
return new Promise((resolve3) => {
|
|
6051
4910
|
const req = request({
|
|
6052
4911
|
hostname: "localhost",
|
|
6053
4912
|
port,
|
|
@@ -6055,18 +4914,18 @@ async function testHttpPort(port) {
|
|
|
6055
4914
|
method: "GET",
|
|
6056
4915
|
timeout: 2e3
|
|
6057
4916
|
}, (res) => {
|
|
6058
|
-
|
|
4917
|
+
resolve3(res.statusCode !== void 0);
|
|
6059
4918
|
res.on("data", () => {
|
|
6060
4919
|
});
|
|
6061
4920
|
res.on("end", () => {
|
|
6062
4921
|
});
|
|
6063
4922
|
});
|
|
6064
4923
|
req.on("error", () => {
|
|
6065
|
-
|
|
4924
|
+
resolve3(false);
|
|
6066
4925
|
});
|
|
6067
4926
|
req.on("timeout", () => {
|
|
6068
4927
|
req.destroy();
|
|
6069
|
-
|
|
4928
|
+
resolve3(false);
|
|
6070
4929
|
});
|
|
6071
4930
|
req.end();
|
|
6072
4931
|
});
|