@triedotdev/mcp 1.0.138 → 1.0.140

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +184 -38
  2. package/dist/{autonomy-config-TZ6HF4FA.js → autonomy-config-ZCOSTMPD.js} +2 -2
  3. package/dist/{chunk-X3F5QDER.js → chunk-4O2KRHK4.js} +934 -132
  4. package/dist/chunk-4O2KRHK4.js.map +1 -0
  5. package/dist/{chunk-J5EMP4XW.js → chunk-5KJ4UJOY.js} +9 -4
  6. package/dist/chunk-5KJ4UJOY.js.map +1 -0
  7. package/dist/chunk-62POBLFC.js +1925 -0
  8. package/dist/chunk-62POBLFC.js.map +1 -0
  9. package/dist/{chunk-GFFUDJMK.js → chunk-75ADWWUF.js} +13 -13
  10. package/dist/chunk-75ADWWUF.js.map +1 -0
  11. package/dist/{chunk-D3AS5LY7.js → chunk-7OJ6JIPL.js} +39 -604
  12. package/dist/chunk-7OJ6JIPL.js.map +1 -0
  13. package/dist/{chunk-3RRXWX3V.js → chunk-AF2APASP.js} +38 -4
  14. package/dist/{chunk-3RRXWX3V.js.map → chunk-AF2APASP.js.map} +1 -1
  15. package/dist/{chunk-QSWUPSLK.js → chunk-FH335WL5.js} +9 -1
  16. package/dist/chunk-FH335WL5.js.map +1 -0
  17. package/dist/{chunk-Y32FM3MR.js → chunk-FPEMP54L.js} +21 -15
  18. package/dist/chunk-FPEMP54L.js.map +1 -0
  19. package/dist/{chunk-EDDT4ZIH.js → chunk-GXF6JOCN.js} +21 -323
  20. package/dist/chunk-GXF6JOCN.js.map +1 -0
  21. package/dist/chunk-LD7ZEFNY.js +132 -0
  22. package/dist/chunk-LD7ZEFNY.js.map +1 -0
  23. package/dist/chunk-NKHO34UZ.js +467 -0
  24. package/dist/chunk-NKHO34UZ.js.map +1 -0
  25. package/dist/{chunk-YOKQ25IW.js → chunk-OQ4A3RDY.js} +14 -14
  26. package/dist/{chunk-6LLH3TBZ.js → chunk-UOSTOLU7.js} +12 -12
  27. package/dist/{chunk-67GSG2ST.js → chunk-XTTZAQWJ.js} +18 -15
  28. package/dist/chunk-XTTZAQWJ.js.map +1 -0
  29. package/dist/{chunk-FOCXXIXY.js → chunk-YEIJW6X6.js} +2 -2
  30. package/dist/chunk-YOJGSRZK.js +216 -0
  31. package/dist/chunk-YOJGSRZK.js.map +1 -0
  32. package/dist/cli/main.js +573 -59
  33. package/dist/cli/main.js.map +1 -1
  34. package/dist/cli/yolo-daemon.js +15 -13
  35. package/dist/cli/yolo-daemon.js.map +1 -1
  36. package/dist/{client-JTU5TRLB.js → client-INNE2GGZ.js} +2 -2
  37. package/dist/{codebase-index-FNJ4GCBE.js → codebase-index-5SEOESWM.js} +3 -3
  38. package/dist/fast-analyzer-AYLZB5TW.js +216 -0
  39. package/dist/fast-analyzer-AYLZB5TW.js.map +1 -0
  40. package/dist/github-ingester-J2ZFYXVE.js +11 -0
  41. package/dist/{goal-manager-6BJQ36AH.js → goal-manager-ZBWKWEML.js} +3 -3
  42. package/dist/{goal-validator-GISXYANK.js → goal-validator-HNXXUCPW.js} +3 -3
  43. package/dist/{graph-X2FMRQLG.js → graph-J4OGTYCO.js} +2 -2
  44. package/dist/{hypothesis-K3KQJOXJ.js → hypothesis-JCUMZKTG.js} +3 -3
  45. package/dist/index.js +1090 -108
  46. package/dist/index.js.map +1 -1
  47. package/dist/{issue-store-BO5OWLJW.js → issue-store-LZWZIGM7.js} +2 -2
  48. package/dist/linear-ingester-JRDQAIAA.js +11 -0
  49. package/dist/linear-ingester-JRDQAIAA.js.map +1 -0
  50. package/dist/{trie-agent-XMSGMD7E.js → trie-agent-M6PHM6UD.js} +10 -10
  51. package/dist/trie-agent-M6PHM6UD.js.map +1 -0
  52. package/package.json +15 -8
  53. package/dist/chunk-67GSG2ST.js.map +0 -1
  54. package/dist/chunk-D3AS5LY7.js.map +0 -1
  55. package/dist/chunk-EDDT4ZIH.js.map +0 -1
  56. package/dist/chunk-GFFUDJMK.js.map +0 -1
  57. package/dist/chunk-J5EMP4XW.js.map +0 -1
  58. package/dist/chunk-QSWUPSLK.js.map +0 -1
  59. package/dist/chunk-X3F5QDER.js.map +0 -1
  60. package/dist/chunk-Y32FM3MR.js.map +0 -1
  61. package/dist/chunk-Z2P4WST6.js +0 -883
  62. package/dist/chunk-Z2P4WST6.js.map +0 -1
  63. /package/dist/{autonomy-config-TZ6HF4FA.js.map → autonomy-config-ZCOSTMPD.js.map} +0 -0
  64. /package/dist/{chunk-YOKQ25IW.js.map → chunk-OQ4A3RDY.js.map} +0 -0
  65. /package/dist/{chunk-6LLH3TBZ.js.map → chunk-UOSTOLU7.js.map} +0 -0
  66. /package/dist/{chunk-FOCXXIXY.js.map → chunk-YEIJW6X6.js.map} +0 -0
  67. /package/dist/{client-JTU5TRLB.js.map → client-INNE2GGZ.js.map} +0 -0
  68. /package/dist/{codebase-index-FNJ4GCBE.js.map → codebase-index-5SEOESWM.js.map} +0 -0
  69. /package/dist/{goal-manager-6BJQ36AH.js.map → github-ingester-J2ZFYXVE.js.map} +0 -0
  70. /package/dist/{goal-validator-GISXYANK.js.map → goal-manager-ZBWKWEML.js.map} +0 -0
  71. /package/dist/{graph-X2FMRQLG.js.map → goal-validator-HNXXUCPW.js.map} +0 -0
  72. /package/dist/{hypothesis-K3KQJOXJ.js.map → graph-J4OGTYCO.js.map} +0 -0
  73. /package/dist/{issue-store-BO5OWLJW.js.map → hypothesis-JCUMZKTG.js.map} +0 -0
  74. /package/dist/{trie-agent-XMSGMD7E.js.map → issue-store-LZWZIGM7.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ GitHubIngester
4
+ } from "./chunk-YOJGSRZK.js";
2
5
  import {
3
6
  CodebaseIndex
4
- } from "./chunk-3RRXWX3V.js";
7
+ } from "./chunk-AF2APASP.js";
5
8
  import {
6
- LinearIngester,
7
9
  appendToSection,
8
10
  completeBootstrap,
9
11
  getContextForAI,
@@ -13,7 +15,6 @@ import {
13
15
  initProjectInfo,
14
16
  initializeBootstrapFiles,
15
17
  loadBootstrapContext,
16
- loadConfig,
17
18
  loadContextState,
18
19
  loadProjectInfo,
19
20
  loadRules,
@@ -21,12 +22,13 @@ import {
21
22
  needsBootstrap,
22
23
  projectInfoExists,
23
24
  updateProjectSection
24
- } from "./chunk-D3AS5LY7.js";
25
+ } from "./chunk-7OJ6JIPL.js";
25
26
  import {
26
- getAutonomyConfig
27
- } from "./chunk-J5EMP4XW.js";
27
+ LinearIngester
28
+ } from "./chunk-LD7ZEFNY.js";
28
29
  import {
29
30
  ExtractionPipeline,
31
+ GitHubBranchesTool,
30
32
  InteractiveDashboard,
31
33
  StreamingManager,
32
34
  TrieCheckTool,
@@ -37,27 +39,28 @@ import {
37
39
  TrieGetGovernanceTool,
38
40
  TrieGetRelatedDecisionsTool,
39
41
  TrieGetRelatedGovernanceTool,
42
+ TriePipelineTool,
40
43
  TrieQueryContextTool,
41
44
  TrieScanTool,
42
45
  TrieTellTool,
43
46
  getPrompt,
44
47
  getSystemPrompt,
45
48
  handleCheckpointTool
46
- } from "./chunk-X3F5QDER.js";
47
- import "./chunk-YOKQ25IW.js";
49
+ } from "./chunk-4O2KRHK4.js";
48
50
  import "./chunk-23RJT5WT.js";
49
- import "./chunk-FOCXXIXY.js";
50
- import "./chunk-67GSG2ST.js";
51
+ import "./chunk-OQ4A3RDY.js";
52
+ import "./chunk-YEIJW6X6.js";
53
+ import "./chunk-XTTZAQWJ.js";
51
54
  import "./chunk-3MUCUZ46.js";
52
55
  import {
53
56
  exportToJson,
54
57
  formatFriendlyError,
55
- getChangedFilesSinceTimestamp,
56
- getGitChangedFiles,
57
58
  importFromJson,
58
- isTrieInitialized,
59
- runShellCommandSync
60
- } from "./chunk-EDDT4ZIH.js";
59
+ isTrieInitialized
60
+ } from "./chunk-GXF6JOCN.js";
61
+ import {
62
+ loadConfig
63
+ } from "./chunk-NKHO34UZ.js";
61
64
  import "./chunk-4C67GV3O.js";
62
65
  import "./chunk-ZV2K6M7T.js";
63
66
  import {
@@ -65,43 +68,51 @@ import {
65
68
  getGlobalMemoryStats,
66
69
  listTrackedProjects,
67
70
  searchGlobalPatterns
68
- } from "./chunk-6LLH3TBZ.js";
69
- import "./chunk-GFFUDJMK.js";
71
+ } from "./chunk-UOSTOLU7.js";
72
+ import {
73
+ ContextGraph
74
+ } from "./chunk-FH335WL5.js";
75
+ import "./chunk-75ADWWUF.js";
70
76
  import {
71
77
  isAIAvailable,
72
78
  runAIAnalysis
73
- } from "./chunk-Y32FM3MR.js";
79
+ } from "./chunk-FPEMP54L.js";
74
80
  import "./chunk-LT6VUZG2.js";
75
- import "./chunk-F4NJ4CBP.js";
76
- import "./chunk-IXO4G4D3.js";
77
- import "./chunk-6NLHFIYA.js";
78
- import {
79
- getStorage
80
- } from "./chunk-FG467PDD.js";
81
- import {
82
- ContextGraph
83
- } from "./chunk-QSWUPSLK.js";
84
- import {
85
- getSkillRegistry
86
- } from "./chunk-G76DYVGX.js";
87
- import {
88
- getOutputManager
89
- } from "./chunk-TIMIKBY2.js";
90
81
  import {
91
82
  findSimilarIssues,
83
+ getChangedFilesSinceTimestamp,
84
+ getGitChangedFiles,
92
85
  getMemoryStats,
93
86
  getRecentIssues,
94
87
  markIssueResolved,
95
88
  purgeIssues,
89
+ runShellCommandSync,
96
90
  searchIssues,
97
91
  storeIssues
98
- } from "./chunk-Z2P4WST6.js";
92
+ } from "./chunk-62POBLFC.js";
99
93
  import "./chunk-4MJ52WBH.js";
100
94
  import "./chunk-43X6JBEM.js";
95
+ import "./chunk-F4NJ4CBP.js";
96
+ import "./chunk-IXO4G4D3.js";
97
+ import "./chunk-6NLHFIYA.js";
98
+ import {
99
+ getStorage
100
+ } from "./chunk-FG467PDD.js";
101
+ import {
102
+ getSkillRegistry
103
+ } from "./chunk-G76DYVGX.js";
104
+ import {
105
+ getAutonomyConfig,
106
+ loadAutonomyConfig,
107
+ saveAutonomyConfig
108
+ } from "./chunk-5KJ4UJOY.js";
101
109
  import {
102
110
  getTrieDirectory,
103
111
  getWorkingDirectory
104
112
  } from "./chunk-SH7H3WRU.js";
113
+ import {
114
+ getOutputManager
115
+ } from "./chunk-TIMIKBY2.js";
105
116
  import {
106
117
  isInteractiveMode
107
118
  } from "./chunk-APMV77PU.js";
@@ -188,10 +199,248 @@ function detectAITool() {
188
199
  import { readFile } from "fs/promises";
189
200
  import { existsSync } from "fs";
190
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
191
437
  var pendingFixes = /* @__PURE__ */ new Map();
192
438
  var TrieFixTool = class {
193
439
  async execute(args) {
194
- const { issueIds, file, line, issue, fix, autoApprove = false, dryRun = false } = args || {};
440
+ const { issueIds, file, line, issue, fix, autoApprove = false, dryRun = false, action } = args || {};
441
+ if (action === "route") {
442
+ return this.routeIssues(issueIds);
443
+ }
195
444
  if (issueIds && issueIds.length > 0) {
196
445
  return this.fixByIds(issueIds, autoApprove, dryRun);
197
446
  }
@@ -211,6 +460,37 @@ var TrieFixTool = class {
211
460
  }]
212
461
  };
213
462
  }
463
+ async routeIssues(issueIds) {
464
+ const pending = getPendingFixes();
465
+ if (pending.length === 0) {
466
+ return {
467
+ content: [{ type: "text", text: "No pending issues. Run `trie_scan` first to detect issues." }]
468
+ };
469
+ }
470
+ const issues = pending.map((p) => ({
471
+ id: p.id,
472
+ severity: p.severity ?? "moderate",
473
+ effort: p.effort,
474
+ issue: p.issue,
475
+ fix: p.suggestedFix,
476
+ file: p.file,
477
+ line: p.line,
478
+ confidence: p.confidence,
479
+ autoFixable: p.autoFixable ?? false,
480
+ agent: "trie_scan",
481
+ cwe: p.cwe,
482
+ owasp: p.owasp,
483
+ category: p.category
484
+ }));
485
+ const filtered = issueIds && issueIds.length > 0 ? issues.filter((i) => issueIds.includes(i.id)) : issues;
486
+ const workDir = getWorkingDirectory(void 0, true);
487
+ const config = await loadAutonomyConfig(workDir);
488
+ const { results } = triageIssues(filtered, void 0, void 0, config);
489
+ const table = formatTriageTable(results, filtered);
490
+ return { content: [{ type: "text", text: `
491
+ ${table}
492
+ ` }] };
493
+ }
214
494
  async fixByIds(issueIds, autoApprove, dryRun) {
215
495
  const results = [];
216
496
  let fixed = 0;
@@ -254,6 +534,7 @@ ${"\u2501".repeat(60)}
254
534
 
255
535
  **Summary:** ${fixed} fixed, ${failed} failed, ${issueIds.length - fixed - failed} skipped
256
536
  `;
537
+ output += await this.appendCloudRecommendation(issueIds);
257
538
  return { content: [{ type: "text", text: output }] };
258
539
  }
259
540
  async applyFix(file, line, issue, fix, dryRun) {
@@ -505,6 +786,33 @@ trie_fix
505
786
  The AI will analyze each issue, generate the fix, and you can review before applying.
506
787
  `;
507
788
  }
789
+ async appendCloudRecommendation(handledIds) {
790
+ try {
791
+ const pending = getPendingFixes();
792
+ const remaining = pending.filter((p) => !handledIds.includes(p.id) || pendingFixes.get(p.id)?.status === "pending");
793
+ if (remaining.length === 0) return "";
794
+ const issues = remaining.map((p) => ({
795
+ id: p.id,
796
+ severity: "moderate",
797
+ issue: p.issue,
798
+ fix: p.suggestedFix,
799
+ file: p.file,
800
+ line: p.line,
801
+ confidence: p.confidence,
802
+ autoFixable: false,
803
+ agent: "trie_scan"
804
+ }));
805
+ const workDir = getWorkingDirectory(void 0, true);
806
+ const config = await loadAutonomyConfig(workDir);
807
+ const { results } = triageIssues(issues, void 0, void 0, config);
808
+ const footer = formatCloudRecommendation(results, issues);
809
+ return footer ? `
810
+ ${footer}
811
+ ` : "";
812
+ } catch {
813
+ return "";
814
+ }
815
+ }
508
816
  detectLanguage(filePath) {
509
817
  const ext = extname(filePath).toLowerCase();
510
818
  const langMap = {
@@ -519,11 +827,459 @@ The AI will analyze each issue, generate the fix, and you can review before appl
519
827
  return langMap[ext] || "plaintext";
520
828
  }
521
829
  };
830
+ function getPendingFixes() {
831
+ return Array.from(pendingFixes.values());
832
+ }
522
833
 
523
- // src/tools/test.ts
524
- import { readFile as readFile2 } from "fs/promises";
834
+ // src/tools/cloud-fix.ts
835
+ import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
525
836
  import { existsSync as existsSync2 } from "fs";
526
- import { extname as extname2, relative as relative2, resolve as resolve2, isAbsolute as isAbsolute2, dirname, basename, join } from "path";
837
+ import { join } from "path";
838
+ import { execSync } from "child_process";
839
+
840
+ // src/integrations/cursor-cloud-agent.ts
841
+ var BASE_URL = "https://api.cursor.com/v1";
842
+ var CursorCloudAgentClient = class {
843
+ apiKey;
844
+ constructor(apiKey) {
845
+ this.apiKey = apiKey;
846
+ }
847
+ /**
848
+ * Dispatch an issue to a cloud agent for fixing.
849
+ */
850
+ async dispatch(issue, triageResult, repoUrl, branch) {
851
+ const prompt = this.buildPrompt(issue, triageResult);
852
+ const body = {
853
+ prompt,
854
+ repo: repoUrl,
855
+ branch,
856
+ metadata: {
857
+ issueId: issue.id,
858
+ file: issue.file,
859
+ line: issue.line,
860
+ severity: issue.severity,
861
+ agent: issue.agent
862
+ }
863
+ };
864
+ const res = await this.request("POST", "/agents/tasks", body);
865
+ return {
866
+ jobId: res.id ?? res.taskId ?? res.jobId,
867
+ status: "dispatched",
868
+ artifactUrls: [],
869
+ dispatchedAt: (/* @__PURE__ */ new Date()).toISOString()
870
+ };
871
+ }
872
+ /**
873
+ * Poll job status.
874
+ */
875
+ async poll(jobId) {
876
+ const res = await this.request("GET", `/agents/tasks/${jobId}`);
877
+ const status = mapStatus(res.status);
878
+ const prUrl = extractPrUrl(res);
879
+ const artifactUrls = this.extractArtifacts(res);
880
+ return { status, prUrl, artifactUrls };
881
+ }
882
+ /**
883
+ * Get artifact URLs for a completed job.
884
+ */
885
+ async getArtifacts(jobId) {
886
+ const res = await this.request("GET", `/agents/tasks/${jobId}`);
887
+ return this.extractArtifacts(res);
888
+ }
889
+ /**
890
+ * Cancel a running job.
891
+ */
892
+ async cancelJob(jobId) {
893
+ await this.request("DELETE", `/agents/tasks/${jobId}`);
894
+ }
895
+ // --------------------------------------------------------------------------
896
+ // Internal
897
+ // --------------------------------------------------------------------------
898
+ buildPrompt(issue, triageResult) {
899
+ const parts = [
900
+ "You are fixing a verified issue in the codebase.",
901
+ "",
902
+ `Issue: ${issue.issue}`,
903
+ `File: ${issue.file}${issue.line ? `:${issue.line}` : ""}`,
904
+ `Severity: ${issue.severity} | Effort: ${issue.effort ?? "medium"}`,
905
+ `Agent that found it: ${issue.agent}`,
906
+ `Suggested fix: ${issue.fix}`
907
+ ];
908
+ if (issue.cwe) parts.push(`CWE: ${issue.cwe}`);
909
+ if (issue.owasp) parts.push(`OWASP: ${issue.owasp}`);
910
+ parts.push(`Triage confidence: ${triageResult.confidence.toFixed(2)}`);
911
+ parts.push(`Why cloud agent: ${triageResult.reasons.join(", ")}`);
912
+ parts.push("");
913
+ parts.push("Steps:");
914
+ parts.push(`1. Apply the minimal fix described above to ${issue.file}`);
915
+ parts.push("2. Run the existing test suite (detect test runner from package.json scripts)");
916
+ parts.push("3. Screenshot the passing test output \u2014 this is the verification artifact");
917
+ parts.push("4. If tests fail, iterate on the fix until they pass (max 3 attempts)");
918
+ parts.push("5. Open a PR with only this single change \u2014 do not bundle other fixes");
919
+ parts.push("6. Include the screenshot in the PR description as evidence");
920
+ return parts.join("\n");
921
+ }
922
+ async request(method, path2, body) {
923
+ const url = `${BASE_URL}${path2}`;
924
+ const headers = {
925
+ "Authorization": `Bearer ${this.apiKey}`,
926
+ "Content-Type": "application/json"
927
+ };
928
+ const init = { method, headers };
929
+ if (body) init.body = JSON.stringify(body);
930
+ const res = await fetch(url, init);
931
+ if (res.status === 401) {
932
+ throw new Error(
933
+ 'Cursor API key is invalid or expired.\nUpdate it: trie_cloud_fix action:configure apiKey:"key-..."'
934
+ );
935
+ }
936
+ if (res.status === 404) {
937
+ throw new Error(`Job not found: ${path2}`);
938
+ }
939
+ if (res.status >= 500) {
940
+ throw new Error(
941
+ `Cursor API returned ${res.status}. The service may be temporarily unavailable \u2014 retry in a few minutes.`
942
+ );
943
+ }
944
+ if (!res.ok) {
945
+ const text = await res.text().catch(() => "");
946
+ throw new Error(`Cursor API error ${res.status}: ${text}`);
947
+ }
948
+ return res.json();
949
+ }
950
+ extractArtifacts(res) {
951
+ const urls = [];
952
+ if (Array.isArray(res.messages)) {
953
+ for (const msg of res.messages) {
954
+ if (typeof msg.content === "string") {
955
+ const matches = msg.content.match(/https:\/\/[^\s)]+\.(png|jpg|mp4|webm)/g);
956
+ if (matches) urls.push(...matches);
957
+ }
958
+ }
959
+ }
960
+ if (Array.isArray(res.artifacts)) {
961
+ for (const a of res.artifacts) {
962
+ if (a.url) urls.push(a.url);
963
+ }
964
+ }
965
+ return [...new Set(urls)];
966
+ }
967
+ };
968
+ function mapStatus(raw) {
969
+ switch (raw) {
970
+ case "pending":
971
+ case "queued":
972
+ return "pending";
973
+ case "running":
974
+ case "in_progress":
975
+ return "running";
976
+ case "completed":
977
+ case "succeeded":
978
+ case "verified":
979
+ return "completed";
980
+ default:
981
+ return "failed";
982
+ }
983
+ }
984
+ function extractPrUrl(res) {
985
+ if (typeof res.prUrl === "string") return res.prUrl;
986
+ if (typeof res.pr_url === "string") return res.pr_url;
987
+ if (typeof res.pullRequestUrl === "string") return res.pullRequestUrl;
988
+ if (Array.isArray(res.messages)) {
989
+ for (const msg of res.messages) {
990
+ if (typeof msg.content === "string") {
991
+ const match = msg.content.match(/https:\/\/github\.com\/[^\s)]+\/pull\/\d+/);
992
+ if (match) return match[0];
993
+ }
994
+ }
995
+ }
996
+ return void 0;
997
+ }
998
+
999
+ // src/tools/cloud-fix.ts
1000
+ var TrieCloudFixTool = class {
1001
+ async execute(args) {
1002
+ const { action = "status" } = args || {};
1003
+ switch (action) {
1004
+ case "configure":
1005
+ return this.configure(args);
1006
+ case "dispatch":
1007
+ return this.dispatch(args);
1008
+ case "status":
1009
+ return this.status();
1010
+ case "artifacts":
1011
+ return this.artifacts(args);
1012
+ case "cancel":
1013
+ return this.cancel(args);
1014
+ default:
1015
+ return this.text(`Unknown action "${action}". Use: configure | dispatch | status | artifacts | cancel`);
1016
+ }
1017
+ }
1018
+ // --------------------------------------------------------------------------
1019
+ // configure
1020
+ // --------------------------------------------------------------------------
1021
+ async configure(args) {
1022
+ const apiKey = args?.apiKey || process.env.CURSOR_API_KEY;
1023
+ if (!apiKey) {
1024
+ return this.text(
1025
+ 'Missing API key.\n\nUsage: trie_cloud_fix action:configure apiKey:"key-..."\nOr set: export CURSOR_API_KEY="key-..."'
1026
+ );
1027
+ }
1028
+ const workDir = getWorkingDirectory(void 0, true);
1029
+ await saveAutonomyConfig(workDir, { cloudAgentEnabled: true, cursorApiKey: apiKey });
1030
+ return this.text("Cursor API key saved. Cloud agent dispatch enabled.");
1031
+ }
1032
+ // --------------------------------------------------------------------------
1033
+ // dispatch
1034
+ // --------------------------------------------------------------------------
1035
+ async dispatch(args) {
1036
+ const workDir = getWorkingDirectory(void 0, true);
1037
+ const apiKey = await this.resolveApiKey(workDir);
1038
+ if (!apiKey) return this.setupGuard();
1039
+ const config = await loadAutonomyConfig(workDir);
1040
+ const allIssues = this.resolveIssues(args?.issueIds);
1041
+ if (allIssues.length === 0) {
1042
+ return this.text("No issues to dispatch. Run trie_scan first, then trie_fix action:route.");
1043
+ }
1044
+ const { results, summary } = triageIssues(allIssues, void 0, void 0, config);
1045
+ const lines = [];
1046
+ lines.push(formatTriageTable(results, allIssues));
1047
+ lines.push("");
1048
+ for (const issue of allIssues) {
1049
+ const r = results.get(issue.id);
1050
+ if (r && r.strategy !== "cloud-agent") {
1051
+ lines.push(`Skipped ${issue.id}: routed to ${r.strategy} (score ${r.score >= 0 ? "+" : ""}${r.score} \u2014 ${r.reasons.join(", ")})`);
1052
+ }
1053
+ }
1054
+ if (summary.cloudAgent.length === 0) {
1055
+ lines.push("\nNo issues qualify for cloud dispatch. Use trie_fix for local fixes.");
1056
+ return this.text(lines.join("\n"));
1057
+ }
1058
+ const client = new CursorCloudAgentClient(apiKey);
1059
+ const repoUrl = this.getRepoUrl(workDir);
1060
+ const branch = this.getBranch(workDir);
1061
+ const store = await this.loadJobs(workDir);
1062
+ lines.push("\nDISPATCHED");
1063
+ for (const issue of summary.cloudAgent) {
1064
+ const triageResult = results.get(issue.id);
1065
+ try {
1066
+ const job = await client.dispatch(issue, triageResult, repoUrl, branch);
1067
+ store.jobs[issue.id] = {
1068
+ issueId: issue.id,
1069
+ jobId: job.jobId,
1070
+ status: "dispatched",
1071
+ score: triageResult.score,
1072
+ strategy: "cloud-agent",
1073
+ dispatchedAt: job.dispatchedAt,
1074
+ artifactUrls: [],
1075
+ prUrl: null
1076
+ };
1077
+ lines.push(` ${issue.id} ${shortPath2(issue.file)}:${issue.line ?? "?"} job:${job.jobId} (score +${triageResult.score})`);
1078
+ } catch (err) {
1079
+ lines.push(` ${issue.id} FAILED: ${err.message}`);
1080
+ }
1081
+ }
1082
+ await this.saveJobs(workDir, store);
1083
+ lines.push("");
1084
+ lines.push("Cloud agents are running in isolated VMs. Check back with:");
1085
+ lines.push("trie_cloud_fix action:status");
1086
+ return this.text(lines.join("\n"));
1087
+ }
1088
+ // --------------------------------------------------------------------------
1089
+ // status
1090
+ // --------------------------------------------------------------------------
1091
+ async status() {
1092
+ const workDir = getWorkingDirectory(void 0, true);
1093
+ const apiKey = await this.resolveApiKey(workDir);
1094
+ if (!apiKey) return this.setupGuard();
1095
+ const store = await this.loadJobs(workDir);
1096
+ const entries = Object.values(store.jobs);
1097
+ if (entries.length === 0) {
1098
+ return this.text("No cloud jobs. Dispatch with: trie_cloud_fix action:dispatch");
1099
+ }
1100
+ const client = new CursorCloudAgentClient(apiKey);
1101
+ const LINE = "\u2500".repeat(68);
1102
+ const lines = ["JOB STATUS", LINE];
1103
+ lines.push(padEnd2("Issue", 32) + padEnd2("Status", 12) + padEnd2("PR", 28) + "Age");
1104
+ for (const job of entries) {
1105
+ if (job.status === "dispatched" || job.status === "running") {
1106
+ try {
1107
+ const poll = await client.poll(job.jobId);
1108
+ if (poll.status === "completed" && poll.prUrl) {
1109
+ job.status = "verified";
1110
+ job.prUrl = poll.prUrl;
1111
+ } else if (poll.status === "running") {
1112
+ job.status = "running";
1113
+ } else if (poll.status === "failed") {
1114
+ job.status = "failed";
1115
+ }
1116
+ if (poll.artifactUrls.length) {
1117
+ job.artifactUrls = poll.artifactUrls;
1118
+ }
1119
+ } catch {
1120
+ }
1121
+ }
1122
+ const age = formatAge(job.dispatchedAt);
1123
+ const pr = job.prUrl ?? "\u2014";
1124
+ lines.push(padEnd2(job.issueId, 32) + padEnd2(job.status, 12) + padEnd2(pr, 28) + age);
1125
+ }
1126
+ lines.push(LINE);
1127
+ await this.saveJobs(workDir, store);
1128
+ return this.text(lines.join("\n"));
1129
+ }
1130
+ // --------------------------------------------------------------------------
1131
+ // artifacts
1132
+ // --------------------------------------------------------------------------
1133
+ async artifacts(args) {
1134
+ const workDir = getWorkingDirectory(void 0, true);
1135
+ const apiKey = await this.resolveApiKey(workDir);
1136
+ if (!apiKey) return this.setupGuard();
1137
+ const store = await this.loadJobs(workDir);
1138
+ const jobId = args?.jobId;
1139
+ const targets = jobId ? Object.values(store.jobs).filter((j) => j.jobId === jobId) : Object.values(store.jobs).filter((j) => j.status === "verified");
1140
+ if (targets.length === 0) {
1141
+ return this.text(jobId ? `No job found with id ${jobId}` : "No completed jobs with artifacts.");
1142
+ }
1143
+ const client = new CursorCloudAgentClient(apiKey);
1144
+ const lines = [];
1145
+ for (const job of targets) {
1146
+ try {
1147
+ const artifacts = await client.getArtifacts(job.jobId);
1148
+ job.artifactUrls = artifacts;
1149
+ } catch {
1150
+ }
1151
+ lines.push(`Issue: ${job.issueId}`);
1152
+ lines.push(`PR: ${job.prUrl ?? "N/A"}`);
1153
+ for (const url of job.artifactUrls) {
1154
+ const ext = url.split(".").pop();
1155
+ const label = ext === "mp4" || ext === "webm" ? "Video" : "Screenshot";
1156
+ lines.push(`${label}: ${url}`);
1157
+ }
1158
+ if (job.status === "verified") {
1159
+ lines.push("Agent verified: all tests pass with this fix applied.");
1160
+ }
1161
+ lines.push("");
1162
+ }
1163
+ await this.saveJobs(workDir, store);
1164
+ return this.text(lines.join("\n"));
1165
+ }
1166
+ // --------------------------------------------------------------------------
1167
+ // cancel
1168
+ // --------------------------------------------------------------------------
1169
+ async cancel(args) {
1170
+ const workDir = getWorkingDirectory(void 0, true);
1171
+ const apiKey = await this.resolveApiKey(workDir);
1172
+ if (!apiKey) return this.setupGuard();
1173
+ const jobId = args?.jobId;
1174
+ if (!jobId) {
1175
+ return this.text('Provide jobId to cancel. Example: trie_cloud_fix action:cancel jobId:"cursor-task-xyz"');
1176
+ }
1177
+ const store = await this.loadJobs(workDir);
1178
+ const entry = Object.values(store.jobs).find((j) => j.jobId === jobId);
1179
+ if (!entry) {
1180
+ return this.text(`Job ${jobId} not found in cloud-jobs.json.`);
1181
+ }
1182
+ const client = new CursorCloudAgentClient(apiKey);
1183
+ try {
1184
+ await client.cancelJob(jobId);
1185
+ } catch (err) {
1186
+ return this.text(`Cancel failed: ${err.message}`);
1187
+ }
1188
+ delete store.jobs[entry.issueId];
1189
+ await this.saveJobs(workDir, store);
1190
+ return this.text(`Job ${jobId} cancelled and removed.`);
1191
+ }
1192
+ // --------------------------------------------------------------------------
1193
+ // Helpers
1194
+ // --------------------------------------------------------------------------
1195
+ async resolveApiKey(workDir) {
1196
+ if (process.env.CURSOR_API_KEY) return process.env.CURSOR_API_KEY;
1197
+ const config = await loadAutonomyConfig(workDir);
1198
+ return config.cursorApiKey ?? null;
1199
+ }
1200
+ setupGuard() {
1201
+ return this.text(
1202
+ '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-..."'
1203
+ );
1204
+ }
1205
+ resolveIssues(issueIds) {
1206
+ const pending = getPendingFixes();
1207
+ const issues = pending.map((p) => ({
1208
+ id: p.id,
1209
+ severity: p.severity ?? "moderate",
1210
+ effort: p.effort,
1211
+ issue: p.issue,
1212
+ fix: p.suggestedFix,
1213
+ file: p.file,
1214
+ line: p.line,
1215
+ confidence: p.confidence,
1216
+ autoFixable: p.autoFixable ?? false,
1217
+ agent: "trie_scan",
1218
+ cwe: p.cwe,
1219
+ owasp: p.owasp,
1220
+ category: p.category
1221
+ }));
1222
+ if (issueIds && issueIds.length > 0) {
1223
+ return issues.filter((i) => issueIds.includes(i.id));
1224
+ }
1225
+ return issues;
1226
+ }
1227
+ getRepoUrl(workDir) {
1228
+ try {
1229
+ return execSync("git remote get-url origin", { cwd: workDir, encoding: "utf-8" }).trim();
1230
+ } catch {
1231
+ return "unknown";
1232
+ }
1233
+ }
1234
+ getBranch(workDir) {
1235
+ try {
1236
+ return execSync("git rev-parse --abbrev-ref HEAD", { cwd: workDir, encoding: "utf-8" }).trim();
1237
+ } catch {
1238
+ return "main";
1239
+ }
1240
+ }
1241
+ async loadJobs(workDir) {
1242
+ const path2 = join(getTrieDirectory(workDir), "cloud-jobs.json");
1243
+ try {
1244
+ if (existsSync2(path2)) {
1245
+ const raw = await readFile2(path2, "utf-8");
1246
+ return JSON.parse(raw);
1247
+ }
1248
+ } catch {
1249
+ }
1250
+ return { jobs: {} };
1251
+ }
1252
+ async saveJobs(workDir, store) {
1253
+ const trieDir = getTrieDirectory(workDir);
1254
+ if (!existsSync2(trieDir)) await mkdir(trieDir, { recursive: true });
1255
+ const path2 = join(trieDir, "cloud-jobs.json");
1256
+ await writeFile(path2, JSON.stringify(store, null, 2));
1257
+ }
1258
+ text(msg) {
1259
+ return { content: [{ type: "text", text: msg }] };
1260
+ }
1261
+ };
1262
+ function formatAge(isoDate) {
1263
+ const ms = Date.now() - new Date(isoDate).getTime();
1264
+ const mins = Math.floor(ms / 6e4);
1265
+ if (mins < 60) return `${mins}m`;
1266
+ const hrs = Math.floor(mins / 60);
1267
+ if (hrs < 24) return `${hrs}h ${mins % 60}m`;
1268
+ return `${Math.floor(hrs / 24)}d`;
1269
+ }
1270
+ function shortPath2(file) {
1271
+ const parts = file.split("/");
1272
+ return parts.length > 2 ? parts.slice(-2).join("/") : file;
1273
+ }
1274
+ function padEnd2(str, len) {
1275
+ if (str.length >= len) return str.slice(0, len);
1276
+ return str + " ".repeat(len - str.length);
1277
+ }
1278
+
1279
+ // src/tools/test.ts
1280
+ import { readFile as readFile3 } from "fs/promises";
1281
+ import { existsSync as existsSync3 } from "fs";
1282
+ import { extname as extname2, relative as relative2, resolve as resolve2, isAbsolute as isAbsolute2, dirname, basename, join as join2 } from "path";
527
1283
  var TrieTestTool = class {
528
1284
  async execute(args) {
529
1285
  const { action, files, framework, style = "unit" } = args || {};
@@ -577,12 +1333,12 @@ ${"\u2501".repeat(60)}
577
1333
  const allUnits = [];
578
1334
  for (const file of files) {
579
1335
  const resolvedPath = isAbsolute2(file) ? file : resolve2(workDir, file);
580
- if (!existsSync2(resolvedPath)) {
1336
+ if (!existsSync3(resolvedPath)) {
581
1337
  output += `[!] File not found: ${file}
582
1338
  `;
583
1339
  continue;
584
1340
  }
585
- const code = await readFile2(resolvedPath, "utf-8");
1341
+ const code = await readFile3(resolvedPath, "utf-8");
586
1342
  const language = this.detectLanguage(resolvedPath);
587
1343
  const relativePath = relative2(workDir, resolvedPath);
588
1344
  const units = this.extractTestableUnits(code, language);
@@ -615,7 +1371,7 @@ ${"\u2501".repeat(60)}
615
1371
  `;
616
1372
  for (const { file, units } of allUnits) {
617
1373
  if (units.length === 0) continue;
618
- const code = await readFile2(resolve2(workDir, file), "utf-8");
1374
+ const code = await readFile3(resolve2(workDir, file), "utf-8");
619
1375
  const language = this.detectLanguage(file);
620
1376
  const prompt = getPrompt("test", "generate", {
621
1377
  code,
@@ -661,12 +1417,12 @@ ${"\u2501".repeat(60)}
661
1417
  const workDir = getWorkingDirectory(void 0, true);
662
1418
  for (const file of files) {
663
1419
  const resolvedPath = isAbsolute2(file) ? file : resolve2(workDir, file);
664
- if (!existsSync2(resolvedPath)) {
1420
+ if (!existsSync3(resolvedPath)) {
665
1421
  output += `[!] File not found: ${file}
666
1422
  `;
667
1423
  continue;
668
1424
  }
669
- const code = await readFile2(resolvedPath, "utf-8");
1425
+ const code = await readFile3(resolvedPath, "utf-8");
670
1426
  const language = this.detectLanguage(resolvedPath);
671
1427
  const relativePath = relative2(workDir, resolvedPath);
672
1428
  const units = this.extractTestableUnits(code, language);
@@ -674,7 +1430,7 @@ ${"\u2501".repeat(60)}
674
1430
  let testCode = "";
675
1431
  let testedUnits = [];
676
1432
  if (testFile) {
677
- testCode = await readFile2(testFile, "utf-8");
1433
+ testCode = await readFile3(testFile, "utf-8");
678
1434
  testedUnits = this.findTestedUnits(testCode, units.map((u) => u.name));
679
1435
  }
680
1436
  const coverage = units.length > 0 ? Math.round(testedUnits.length / units.length * 100) : 0;
@@ -743,12 +1499,12 @@ ${"\u2501".repeat(60)}
743
1499
  const workDir = getWorkingDirectory(void 0, true);
744
1500
  for (const file of files) {
745
1501
  const resolvedPath = isAbsolute2(file) ? file : resolve2(workDir, file);
746
- if (!existsSync2(resolvedPath)) {
1502
+ if (!existsSync3(resolvedPath)) {
747
1503
  output += `[!] File not found: ${file}
748
1504
  `;
749
1505
  continue;
750
1506
  }
751
- const code = await readFile2(resolvedPath, "utf-8");
1507
+ const code = await readFile3(resolvedPath, "utf-8");
752
1508
  const relativePath = relative2(workDir, resolvedPath);
753
1509
  const patterns = this.detectTestablePatterns(code);
754
1510
  output += `### \u{1F4C4} ${relativePath}
@@ -994,16 +1750,16 @@ ${"\u2501".repeat(60)}
994
1750
  `test_${base}${ext}`
995
1751
  ];
996
1752
  for (const pattern of patterns) {
997
- const testPath = join(dir, pattern);
998
- if (existsSync2(testPath)) {
1753
+ const testPath = join2(dir, pattern);
1754
+ if (existsSync3(testPath)) {
999
1755
  return testPath;
1000
1756
  }
1001
1757
  }
1002
- const testsDir = join(dir, "__tests__");
1003
- if (existsSync2(testsDir)) {
1758
+ const testsDir = join2(dir, "__tests__");
1759
+ if (existsSync3(testsDir)) {
1004
1760
  for (const pattern of patterns) {
1005
- const testPath = join(testsDir, pattern);
1006
- if (existsSync2(testPath)) {
1761
+ const testPath = join2(testsDir, pattern);
1762
+ if (existsSync3(testPath)) {
1007
1763
  return testPath;
1008
1764
  }
1009
1765
  }
@@ -1051,9 +1807,9 @@ ${"\u2501".repeat(60)}
1051
1807
  async detectTestFramework() {
1052
1808
  const workDir = getWorkingDirectory(void 0, true);
1053
1809
  const packagePath = resolve2(workDir, "package.json");
1054
- if (existsSync2(packagePath)) {
1810
+ if (existsSync3(packagePath)) {
1055
1811
  try {
1056
- const pkg = JSON.parse(await readFile2(packagePath, "utf-8"));
1812
+ const pkg = JSON.parse(await readFile3(packagePath, "utf-8"));
1057
1813
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1058
1814
  if (deps.vitest) return "vitest";
1059
1815
  if (deps.jest) return "jest";
@@ -1061,7 +1817,7 @@ ${"\u2501".repeat(60)}
1061
1817
  } catch {
1062
1818
  }
1063
1819
  }
1064
- if (existsSync2(resolve2(workDir, "pytest.ini")) || existsSync2(resolve2(workDir, "pyproject.toml"))) {
1820
+ if (existsSync3(resolve2(workDir, "pytest.ini")) || existsSync3(resolve2(workDir, "pyproject.toml"))) {
1065
1821
  return "pytest";
1066
1822
  }
1067
1823
  return "jest";
@@ -1120,9 +1876,9 @@ trie_test action:"run" files:["src/utils.test.ts"]
1120
1876
  };
1121
1877
 
1122
1878
  // src/tools/watch.ts
1123
- import { watch, existsSync as existsSync3, readFileSync } from "fs";
1124
- import { stat, readFile as readFile3 } from "fs/promises";
1125
- import { join as join2, extname as extname3, basename as basename2 } from "path";
1879
+ import { watch, existsSync as existsSync4, readFileSync } from "fs";
1880
+ import { stat, readFile as readFile4 } from "fs/promises";
1881
+ import { join as join3, extname as extname3, basename as basename2 } from "path";
1126
1882
  import { createHash } from "crypto";
1127
1883
  var WATCH_EXTENSIONS = /* @__PURE__ */ new Set([
1128
1884
  ".ts",
@@ -1176,8 +1932,11 @@ var TrieWatchTool = class _TrieWatchTool {
1176
1932
  streamingManager = void 0;
1177
1933
  dashboard = void 0;
1178
1934
  lastHypothesisCheck = 0;
1935
+ pipelineSyncTimer = null;
1179
1936
  static HYPOTHESIS_CHECK_INTERVAL_MS = 3e5;
1180
1937
  // Check every 5 minutes
1938
+ static PIPELINE_SYNC_INTERVAL_MS = 30 * 60 * 1e3;
1939
+ // 30 minutes
1181
1940
  async execute(args) {
1182
1941
  const { action, directory, debounceMs = 1e3 } = args;
1183
1942
  switch (action) {
@@ -1237,16 +1996,27 @@ var TrieWatchTool = class _TrieWatchTool {
1237
1996
  const storage = getStorage(directory);
1238
1997
  await storage.initialize();
1239
1998
  const unresolvedNudges = await storage.queryNudges({ resolved: false });
1999
+ console.debug(`[Watch] Found ${unresolvedNudges.length} unresolved nudges in storage`);
1240
2000
  for (const nudge of unresolvedNudges) {
2001
+ let mappedSeverity = "high";
2002
+ if (nudge.severity === "critical") {
2003
+ mappedSeverity = "critical";
2004
+ } else if (nudge.severity === "high") {
2005
+ mappedSeverity = "high";
2006
+ }
1241
2007
  this.state.nudges.push({
1242
- file: nudge.file ? basename2(nudge.file) : "unknown",
2008
+ file: nudge.file || "unknown",
2009
+ // Keep full path, don't use basename
1243
2010
  message: nudge.message,
1244
- severity: nudge.severity === "critical" ? "critical" : "high",
2011
+ severity: mappedSeverity,
1245
2012
  timestamp: new Date(nudge.timestamp).toLocaleTimeString("en-US", { hour12: false })
1246
2013
  });
2014
+ console.debug(`[Watch] Loaded nudge: ${nudge.message.slice(0, 60)}... (${nudge.severity} -> ${mappedSeverity})`);
1247
2015
  }
1248
2016
  if (unresolvedNudges.length > 0) {
1249
- console.debug(`[Watch] Loaded ${unresolvedNudges.length} unresolved nudges from storage`);
2017
+ console.log(`[Watch] \u2713 Loaded ${unresolvedNudges.length} unresolved nudges from storage`);
2018
+ } else {
2019
+ console.debug(`[Watch] No unresolved nudges found in storage`);
1250
2020
  }
1251
2021
  } catch (error) {
1252
2022
  console.error("[Watch] Failed to load nudges from storage:", error);
@@ -1284,7 +2054,11 @@ var TrieWatchTool = class _TrieWatchTool {
1284
2054
  setTimeout(() => {
1285
2055
  void this.initialGoalComplianceScan();
1286
2056
  void this.initialHypothesisGeneration();
2057
+ void this.syncPipelineIntegrations();
1287
2058
  }, 1e3);
2059
+ this.pipelineSyncTimer = setInterval(() => {
2060
+ void this.syncPipelineIntegrations();
2061
+ }, _TrieWatchTool.PIPELINE_SYNC_INTERVAL_MS);
1288
2062
  const indexStatus = this.codebaseIndex?.isEmpty() ? "BUILDING (one-time, speeds up goal checks)" : "READY";
1289
2063
  return {
1290
2064
  content: [{
@@ -1322,7 +2096,7 @@ Your Trie agent is now autonomously watching and learning from your codebase.
1322
2096
  return parts.some((p) => SKIP_DIRS.has(p) || p.startsWith(".") && p !== ".");
1323
2097
  }
1324
2098
  async watchDirectory(dir, debounceMs) {
1325
- if (!existsSync3(dir)) return;
2099
+ if (!existsSync4(dir)) return;
1326
2100
  try {
1327
2101
  const dirStat = await stat(dir);
1328
2102
  if (!dirStat.isDirectory()) return;
@@ -1331,8 +2105,8 @@ Your Trie agent is now autonomously watching and learning from your codebase.
1331
2105
  if (this.shouldSkipPath(filename)) return;
1332
2106
  const ext = extname3(filename).toLowerCase();
1333
2107
  if (!WATCH_EXTENSIONS.has(ext)) return;
1334
- const fullPath = join2(dir, filename);
1335
- if (!existsSync3(fullPath)) return;
2108
+ const fullPath = join3(dir, filename);
2109
+ if (!existsSync4(fullPath)) return;
1336
2110
  this.state.pendingFiles.add(fullPath);
1337
2111
  if (this.state.scanDebounceTimer) {
1338
2112
  clearTimeout(this.state.scanDebounceTimer);
@@ -1378,7 +2152,7 @@ Detected changes in ${files.length} file(s):`);
1378
2152
  const fileContents = await Promise.all(
1379
2153
  files.map(async (file) => {
1380
2154
  try {
1381
- const content = await readFile3(file, "utf-8");
2155
+ const content = await readFile4(file, "utf-8");
1382
2156
  return { file, content };
1383
2157
  } catch {
1384
2158
  return null;
@@ -1456,7 +2230,7 @@ ${f.content.slice(0, 1e3)}`
1456
2230
  }
1457
2231
  isQuiet() {
1458
2232
  const projectPath = this.watchedDirectory || getWorkingDirectory(void 0, true);
1459
- const quietPath = join2(getTrieDirectory(projectPath), "quiet.json");
2233
+ const quietPath = join3(getTrieDirectory(projectPath), "quiet.json");
1460
2234
  try {
1461
2235
  const raw = readFileSync(quietPath, "utf-8");
1462
2236
  const data = JSON.parse(raw);
@@ -1473,7 +2247,7 @@ ${f.content.slice(0, 1e3)}`
1473
2247
  async checkAndGenerateHypotheses(projectPath) {
1474
2248
  if (!isAIAvailable()) return;
1475
2249
  try {
1476
- const { getHypothesisEngine } = await import("./hypothesis-K3KQJOXJ.js");
2250
+ const { getHypothesisEngine } = await import("./hypothesis-JCUMZKTG.js");
1477
2251
  const { getOutputManager: getOutputManager2 } = await import("./output-manager-DZO5LGSG.js");
1478
2252
  const hypothesisEngine = getHypothesisEngine(projectPath);
1479
2253
  const recentIssues = Array.from(this.state.issueCache.values()).flat();
@@ -1540,7 +2314,7 @@ ${f.content.slice(0, 1e3)}`
1540
2314
  const totalIssues = Array.from(this.state.issueCache.values()).flat().length;
1541
2315
  if (totalIssues < 5) return;
1542
2316
  try {
1543
- const { ContextGraph: ContextGraph2 } = await import("./graph-X2FMRQLG.js");
2317
+ const { ContextGraph: ContextGraph2 } = await import("./graph-J4OGTYCO.js");
1544
2318
  const { IncidentIndex } = await import("./incident-index-BWW2UEY7.js");
1545
2319
  const { TriePatternDiscovery } = await import("./pattern-discovery-F7LU5K6E.js");
1546
2320
  const graph = new ContextGraph2(projectPath);
@@ -1624,7 +2398,7 @@ ${f.content.slice(0, 1e3)}`
1624
2398
  if (lastClean && Date.now() - lastClean < this.cleanFileCooldownMs) {
1625
2399
  return 0;
1626
2400
  }
1627
- const fileNode = await graph.getNode("file", join2(projectPath, file));
2401
+ const fileNode = await graph.getNode("file", join3(projectPath, file));
1628
2402
  if (!fileNode) return score;
1629
2403
  const data = fileNode.data;
1630
2404
  const riskScores = { critical: 10, high: 6, medium: 2, low: 1 };
@@ -1683,7 +2457,7 @@ ${f.content.slice(0, 1e3)}`
1683
2457
  if (remaining < 500) return;
1684
2458
  try {
1685
2459
  const graph = new ContextGraph(projectPath);
1686
- const { getActiveGoals, recordGoalViolationCaught } = await import("./goal-validator-GISXYANK.js");
2460
+ const { getActiveGoals, recordGoalViolationCaught } = await import("./goal-validator-HNXXUCPW.js");
1687
2461
  console.debug("[AI Watcher] Loading active goals...");
1688
2462
  const activeGoals = await getActiveGoals(projectPath);
1689
2463
  const hasGoals = activeGoals.length > 0;
@@ -1712,7 +2486,7 @@ ${f.content.slice(0, 1e3)}`
1712
2486
  const fileContents = await Promise.all(
1713
2487
  filesToScan.map(async ({ file, relativePath }) => {
1714
2488
  try {
1715
- const content = await readFile3(file, "utf-8");
2489
+ const content = await readFile4(file, "utf-8");
1716
2490
  return { path: relativePath, content: content.slice(0, charLimit) };
1717
2491
  } catch {
1718
2492
  return null;
@@ -1877,7 +2651,7 @@ ${filesBlock}`,
1877
2651
  fixChangeId: null,
1878
2652
  reportedVia: "detected"
1879
2653
  });
1880
- const filePath = join2(projectPath, issue.file);
2654
+ const filePath = join3(projectPath, issue.file);
1881
2655
  const fileNode = await graph.getNode("file", filePath);
1882
2656
  if (fileNode) {
1883
2657
  await graph.addEdge(fileNode.id, incident.id, "affects");
@@ -1935,7 +2709,7 @@ ${filesBlock}`,
1935
2709
  const projectPath = this.watchedDirectory || getWorkingDirectory(void 0, true);
1936
2710
  console.debug("[Initial Hypothesis] Starting initial hypothesis generation", { projectPath });
1937
2711
  try {
1938
- const { getHypothesisEngine } = await import("./hypothesis-K3KQJOXJ.js");
2712
+ const { getHypothesisEngine } = await import("./hypothesis-JCUMZKTG.js");
1939
2713
  const hypothesisEngine = getHypothesisEngine(projectPath);
1940
2714
  console.debug("[Initial Hypothesis] Running AI-powered hypothesis generation...");
1941
2715
  const generated = await hypothesisEngine.generateHypothesesWithAI({
@@ -1993,7 +2767,7 @@ ${filesBlock}`,
1993
2767
  const projectPath = this.watchedDirectory || getWorkingDirectory(void 0, true);
1994
2768
  console.debug("[Initial Scan] Starting initial goal compliance scan", { projectPath });
1995
2769
  try {
1996
- const { getActiveGoals, recordGoalViolationCaught } = await import("./goal-validator-GISXYANK.js");
2770
+ const { getActiveGoals, recordGoalViolationCaught } = await import("./goal-validator-HNXXUCPW.js");
1997
2771
  const activeGoals = await getActiveGoals(projectPath);
1998
2772
  console.debug("[Initial Scan] Loaded goals for initial scan:", {
1999
2773
  goalCount: activeGoals.length,
@@ -2009,7 +2783,7 @@ ${filesBlock}`,
2009
2783
  const recentFiles = /* @__PURE__ */ new Set();
2010
2784
  const uncommittedFiles = await getGitChangedFiles(projectPath);
2011
2785
  if (uncommittedFiles) {
2012
- uncommittedFiles.forEach((f) => recentFiles.add(join2(projectPath, f)));
2786
+ uncommittedFiles.forEach((f) => recentFiles.add(join3(projectPath, f)));
2013
2787
  }
2014
2788
  const oneDayAgo = Date.now() - 24 * 60 * 60 * 1e3;
2015
2789
  const recentChanges = await getChangedFilesSinceTimestamp(projectPath, oneDayAgo);
@@ -2018,7 +2792,7 @@ ${filesBlock}`,
2018
2792
  }
2019
2793
  const filesToCheck = Array.from(recentFiles).filter((file) => {
2020
2794
  const ext = extname3(file).toLowerCase();
2021
- return WATCH_EXTENSIONS.has(ext) && existsSync3(file);
2795
+ return WATCH_EXTENSIONS.has(ext) && existsSync4(file);
2022
2796
  });
2023
2797
  console.debug("[Initial Scan] Files discovered for initial scan:", {
2024
2798
  totalRecentFiles: recentFiles.size,
@@ -2040,7 +2814,7 @@ ${filesBlock}`,
2040
2814
  const fileContents = await Promise.all(
2041
2815
  filesToScan.map(async (file) => {
2042
2816
  try {
2043
- const content = await readFile3(file, "utf-8");
2817
+ const content = await readFile4(file, "utf-8");
2044
2818
  const relativePath = file.replace(projectPath + "/", "");
2045
2819
  return { path: relativePath, content: content.slice(0, maxCharsPerFile) };
2046
2820
  } catch {
@@ -2146,6 +2920,50 @@ ${filesBlock}`,
2146
2920
  }
2147
2921
  }
2148
2922
  }
2923
+ async syncPipelineIntegrations() {
2924
+ const projectPath = this.watchedDirectory || getWorkingDirectory(void 0, true);
2925
+ try {
2926
+ const config = await loadConfig();
2927
+ const hasLinear = !!(config.apiKeys?.linear ?? process.env.LINEAR_API_KEY);
2928
+ const hasGithub = !!(config.apiKeys?.github ?? process.env.GITHUB_TOKEN);
2929
+ if (!hasLinear && !hasGithub) return;
2930
+ const graph = new ContextGraph(projectPath);
2931
+ if (hasLinear) {
2932
+ try {
2933
+ const { LinearIngester: LinearIngester2 } = await import("./linear-ingester-JRDQAIAA.js");
2934
+ const ingester = new LinearIngester2(projectPath, graph);
2935
+ await ingester.syncTickets();
2936
+ if (!isInteractiveMode()) {
2937
+ console.error("[Pipeline] Linear tickets synced");
2938
+ }
2939
+ } catch (error) {
2940
+ if (!isInteractiveMode()) {
2941
+ console.error(`[Pipeline] Linear sync failed: ${error}`);
2942
+ }
2943
+ }
2944
+ }
2945
+ if (hasGithub) {
2946
+ try {
2947
+ const { GitHubIngester: GitHubIngester2 } = await import("./github-ingester-J2ZFYXVE.js");
2948
+ const ingester = new GitHubIngester2(graph);
2949
+ const token = await ingester.getApiToken();
2950
+ const repoInfo = ingester.getRepoInfo(projectPath);
2951
+ if (repoInfo) {
2952
+ await ingester.syncPullRequests(repoInfo.owner, repoInfo.name, token);
2953
+ await ingester.syncIssues(repoInfo.owner, repoInfo.name, token);
2954
+ if (!isInteractiveMode()) {
2955
+ console.error("[Pipeline] GitHub PRs/issues synced");
2956
+ }
2957
+ }
2958
+ } catch (error) {
2959
+ if (!isInteractiveMode()) {
2960
+ console.error(`[Pipeline] GitHub sync failed: ${error}`);
2961
+ }
2962
+ }
2963
+ }
2964
+ } catch {
2965
+ }
2966
+ }
2149
2967
  async stopWatching() {
2150
2968
  if (!this.state.isRunning) {
2151
2969
  return {
@@ -2163,6 +2981,10 @@ ${filesBlock}`,
2163
2981
  this.extractionPipeline.close();
2164
2982
  this.extractionPipeline = null;
2165
2983
  }
2984
+ if (this.pipelineSyncTimer) {
2985
+ clearInterval(this.pipelineSyncTimer);
2986
+ this.pipelineSyncTimer = null;
2987
+ }
2166
2988
  if (this.codebaseIndex) {
2167
2989
  await this.codebaseIndex.save();
2168
2990
  this.codebaseIndex = null;
@@ -2222,7 +3044,7 @@ Use \`trie_watch start\` to begin autonomous scanning.`
2222
3044
  ).join("\n");
2223
3045
  let agencyStatus = "";
2224
3046
  try {
2225
- const { getTrieAgent } = await import("./trie-agent-XMSGMD7E.js");
3047
+ const { getTrieAgent } = await import("./trie-agent-M6PHM6UD.js");
2226
3048
  const trieAgent = getTrieAgent(this.watchedDirectory || getWorkingDirectory(void 0, true));
2227
3049
  await trieAgent.initialize();
2228
3050
  const status = await trieAgent.getAgencyStatus();
@@ -2290,7 +3112,8 @@ ${recentNudges || "(none)"}
2290
3112
  ...params.suggestedAction && { suggestedAction: params.suggestedAction }
2291
3113
  };
2292
3114
  this.state.nudges.push({
2293
- file: params.file ? basename2(params.file) : "unknown",
3115
+ file: params.file || "unknown",
3116
+ // Keep full path for consistency with loaded nudges
2294
3117
  message: params.message,
2295
3118
  severity: params.severity === "warning" || params.severity === "info" ? "high" : params.severity,
2296
3119
  timestamp: (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false })
@@ -2298,6 +3121,7 @@ ${recentNudges || "(none)"}
2298
3121
  try {
2299
3122
  const storage = getStorage(this.watchedDirectory);
2300
3123
  await storage.storeNudge(nudge);
3124
+ console.debug(`[Watch] \u2713 Persisted nudge to storage: ${nudge.message.slice(0, 60)}...`);
2301
3125
  } catch (error) {
2302
3126
  console.error("[Watch] Failed to persist nudge to storage:", error);
2303
3127
  }
@@ -2336,9 +3160,9 @@ To get a full report, run \`trie_scan\` on your codebase.`
2336
3160
  };
2337
3161
 
2338
3162
  // src/tools/pr-review.ts
2339
- import { readFile as readFile4 } from "fs/promises";
2340
- import { existsSync as existsSync4 } from "fs";
2341
- import { join as join3, basename as basename3, resolve as resolve3, isAbsolute as isAbsolute3 } from "path";
3163
+ import { readFile as readFile5 } from "fs/promises";
3164
+ import { existsSync as existsSync5 } from "fs";
3165
+ import { join as join4, basename as basename3, resolve as resolve3, isAbsolute as isAbsolute3 } from "path";
2342
3166
 
2343
3167
  // src/skills/built-in/super-reviewer.ts
2344
3168
  var CRITICAL_REVIEW_CHECKLIST = {
@@ -2452,7 +3276,7 @@ Usage:
2452
3276
  async getPRInfo(pr, worktree) {
2453
3277
  if (worktree) {
2454
3278
  const worktreePath = isAbsolute3(worktree) ? worktree : resolve3(getWorkingDirectory(void 0, true), worktree);
2455
- if (!existsSync4(worktreePath)) {
3279
+ if (!existsSync5(worktreePath)) {
2456
3280
  return { success: false, error: `Worktree not found: ${worktreePath}` };
2457
3281
  }
2458
3282
  return {
@@ -2585,8 +3409,8 @@ Usage:
2585
3409
  "rfcs"
2586
3410
  ];
2587
3411
  for (const docPath of designDocPaths) {
2588
- const fullPath = join3(cwd, docPath);
2589
- if (existsSync4(fullPath)) {
3412
+ const fullPath = join4(cwd, docPath);
3413
+ if (existsSync5(fullPath)) {
2590
3414
  }
2591
3415
  }
2592
3416
  return designDocs;
@@ -2599,9 +3423,9 @@ Usage:
2599
3423
  const cwd = getWorkingDirectory(void 0, true);
2600
3424
  await Promise.all(filePaths.map(async (filePath) => {
2601
3425
  try {
2602
- const fullPath = isAbsolute3(filePath) ? filePath : join3(cwd, filePath);
2603
- if (existsSync4(fullPath)) {
2604
- const content = await readFile4(fullPath, "utf-8");
3426
+ const fullPath = isAbsolute3(filePath) ? filePath : join4(cwd, filePath);
3427
+ if (existsSync5(fullPath)) {
3428
+ const content = await readFile5(fullPath, "utf-8");
2605
3429
  contents.set(filePath, content);
2606
3430
  }
2607
3431
  } catch {
@@ -3465,6 +4289,65 @@ var LinearSyncTool = class {
3465
4289
  }
3466
4290
  };
3467
4291
 
4292
+ // src/tools/github-sync.ts
4293
+ var GitHubSyncTool = class {
4294
+ async execute(input) {
4295
+ try {
4296
+ const workDir = input.directory || getWorkingDirectory(void 0, true);
4297
+ const graph = new ContextGraph(workDir);
4298
+ const ingester = new GitHubIngester(graph);
4299
+ const token = await ingester.getApiToken();
4300
+ if (!token) {
4301
+ return {
4302
+ isError: true,
4303
+ content: [{
4304
+ type: "text",
4305
+ text: 'GitHub token not configured.\n\nSet one of:\n \u2022 Environment variable: GITHUB_TOKEN=ghp_...\n \u2022 Config dialog (C key) \u2192 API Keys \u2192 GitHub\n \u2022 .trie/config.json: { "apiKeys": { "github": "ghp_..." } }\n\nToken needs `repo` scope for private repos, `public_repo` for public.'
4306
+ }]
4307
+ };
4308
+ }
4309
+ const repoInfo = ingester.getRepoInfo(workDir);
4310
+ if (!repoInfo) {
4311
+ return {
4312
+ isError: true,
4313
+ content: [{
4314
+ type: "text",
4315
+ text: "Could not detect GitHub repository.\n\nMake sure this directory is a git repo with a GitHub remote:\n git remote get-url origin"
4316
+ }]
4317
+ };
4318
+ }
4319
+ const prResult = await ingester.syncPullRequests(repoInfo.owner, repoInfo.name, token);
4320
+ const issueResult = await ingester.syncIssues(repoInfo.owner, repoInfo.name, token);
4321
+ const LINE = "\u2500".repeat(45);
4322
+ const lines = [
4323
+ "GITHUB SYNC",
4324
+ LINE,
4325
+ `Repo: ${repoInfo.owner}/${repoInfo.name}`,
4326
+ `Pulled ${prResult.prs} open PR${prResult.prs !== 1 ? "s" : ""}, ${issueResult.issues} open issue${issueResult.issues !== 1 ? "s" : ""}`,
4327
+ `Linked ${prResult.linkedTickets} PR${prResult.linkedTickets !== 1 ? "s" : ""} to Linear tickets`,
4328
+ `Linked ${prResult.linkedFiles} PR${prResult.linkedFiles !== 1 ? "s" : ""} to changed files in context graph`,
4329
+ LINE,
4330
+ "Next sync: trie_github_sync",
4331
+ "Or use trie_pipeline for consolidated view."
4332
+ ];
4333
+ return {
4334
+ content: [{
4335
+ type: "text",
4336
+ text: lines.join("\n")
4337
+ }]
4338
+ };
4339
+ } catch (error) {
4340
+ return {
4341
+ isError: true,
4342
+ content: [{
4343
+ type: "text",
4344
+ text: `GitHub sync failed: ${error.message}`
4345
+ }]
4346
+ };
4347
+ }
4348
+ }
4349
+ };
4350
+
3468
4351
  // src/tools/index-codebase.ts
3469
4352
  import { glob } from "glob";
3470
4353
  var INDEXABLE_EXTENSIONS = [
@@ -3617,6 +4500,7 @@ var ToolRegistry = class {
3617
4500
  initializeTools() {
3618
4501
  this.tools.set("scan", new TrieScanTool());
3619
4502
  this.tools.set("fix", new TrieFixTool());
4503
+ this.tools.set("cloud_fix", new TrieCloudFixTool());
3620
4504
  this.tools.set("explain", new TrieExplainTool());
3621
4505
  this.tools.set("test", new TrieTestTool());
3622
4506
  this.tools.set("watch", new TrieWatchTool());
@@ -3633,6 +4517,9 @@ var ToolRegistry = class {
3633
4517
  this.tools.set("ok", new TrieFeedbackTool());
3634
4518
  this.tools.set("bad", new TrieFeedbackTool());
3635
4519
  this.tools.set("linear_sync", new LinearSyncTool());
4520
+ this.tools.set("github_sync", new GitHubSyncTool());
4521
+ this.tools.set("github_branches", new GitHubBranchesTool());
4522
+ this.tools.set("pipeline", new TriePipelineTool());
3636
4523
  this.tools.set("index", new TrieIndexTool());
3637
4524
  this.tools.set("get_governance", new TrieGetGovernanceTool());
3638
4525
  this.tools.set("get_decisions", new TrieGetDecisionsTool());
@@ -3757,10 +4644,15 @@ var ToolRegistry = class {
3757
4644
  },
3758
4645
  {
3759
4646
  name: "trie_fix",
3760
- description: "Apply high-confidence fixes to code. Alias: fix",
4647
+ description: "Apply high-confidence fixes to code. Use action:route to see triage plan first. Alias: fix",
3761
4648
  inputSchema: {
3762
4649
  type: "object",
3763
4650
  properties: {
4651
+ action: {
4652
+ type: "string",
4653
+ enum: ["route"],
4654
+ description: "route: show fix routing plan without applying fixes"
4655
+ },
3764
4656
  issueIds: {
3765
4657
  type: "array",
3766
4658
  items: { type: "string" },
@@ -3773,6 +4665,34 @@ var ToolRegistry = class {
3773
4665
  }
3774
4666
  }
3775
4667
  },
4668
+ {
4669
+ name: "trie_cloud_fix",
4670
+ description: "Dispatch issues to Cursor cloud agents for verified, test-passing fixes. The cloud agent runs in an isolated VM, applies the fix, runs tests, screenshots the result, and opens a PR. Use trie_fix action:route first to see which issues qualify.",
4671
+ inputSchema: {
4672
+ type: "object",
4673
+ properties: {
4674
+ action: {
4675
+ type: "string",
4676
+ enum: ["configure", "dispatch", "status", "artifacts", "cancel"],
4677
+ description: "configure: save API key | dispatch: send to cloud | status: poll jobs | artifacts: get screenshots + PR links | cancel: abort a job"
4678
+ },
4679
+ issueIds: {
4680
+ type: "array",
4681
+ items: { type: "string" },
4682
+ description: "Issue IDs to dispatch (from trie_scan). If omitted, dispatches all cloud-eligible pending issues."
4683
+ },
4684
+ apiKey: {
4685
+ type: "string",
4686
+ description: "Cursor API key (configure action only)"
4687
+ },
4688
+ jobId: {
4689
+ type: "string",
4690
+ description: "Job ID for cancel or artifacts actions"
4691
+ }
4692
+ },
4693
+ required: ["action"]
4694
+ }
4695
+ },
3776
4696
  {
3777
4697
  name: "trie_explain",
3778
4698
  description: "Explain code, issues, or changes in plain language. Alias: explain",
@@ -4118,6 +5038,51 @@ var ToolRegistry = class {
4118
5038
  }
4119
5039
  }
4120
5040
  },
5041
+ {
5042
+ name: "trie_github_sync",
5043
+ description: "Sync open PRs and issues from GitHub into the Trie context graph. Links PRs to files they touch and to Linear tickets mentioned in PR descriptions. Run once to initialize, or periodically to stay current.",
5044
+ inputSchema: {
5045
+ type: "object",
5046
+ properties: {
5047
+ directory: { type: "string", description: "Project directory (defaults to current workspace)" }
5048
+ }
5049
+ }
5050
+ },
5051
+ {
5052
+ name: "trie_github_branches",
5053
+ description: "Fetch GitHub branches with latest commit info. Use when the user asks which branch has the latest updates, what branches exist, or when branches were last updated. Requires GitHub API key.",
5054
+ inputSchema: {
5055
+ type: "object",
5056
+ properties: {
5057
+ directory: { type: "string", description: "Project directory (defaults to current workspace)" },
5058
+ limit: { type: "number", description: "Max branches to return (default 15, max 30)" }
5059
+ }
5060
+ }
5061
+ },
5062
+ {
5063
+ name: "trie_pipeline",
5064
+ description: "Get consolidated pipeline status: open PRs, active Linear tickets, open GitHub issues, and Trie scan issues not yet in any ticket or PR (coverage gaps). Use to understand where work stands and what is falling through the cracks.",
5065
+ inputSchema: {
5066
+ type: "object",
5067
+ properties: {
5068
+ action: {
5069
+ type: "string",
5070
+ enum: ["status", "coverage", "create_tickets"],
5071
+ description: "status: full pipeline view | coverage: only show Trie issues with no ticket/PR | create_tickets: open Linear tickets for uncovered issues"
5072
+ },
5073
+ focus: {
5074
+ type: "string",
5075
+ description: "Optional file path or Linear ticket ID to narrow the view"
5076
+ },
5077
+ issueIds: {
5078
+ type: "array",
5079
+ items: { type: "string" },
5080
+ description: "Issue IDs to create tickets for (create_tickets action only)"
5081
+ },
5082
+ directory: { type: "string", description: "Project directory" }
5083
+ }
5084
+ }
5085
+ },
4121
5086
  {
4122
5087
  name: "trie_get_governance",
4123
5088
  description: "Query governance records (architectural decisions, standards, team agreements) from governance ledger with targeted retrieval. Prevents context pollution by returning only relevant records.",
@@ -4221,9 +5186,9 @@ var ToolRegistry = class {
4221
5186
  };
4222
5187
 
4223
5188
  // src/server/resource-manager.ts
4224
- import { readdir, readFile as readFile5 } from "fs/promises";
4225
- import { existsSync as existsSync5 } from "fs";
4226
- import { join as join4, dirname as dirname2 } from "path";
5189
+ import { readdir, readFile as readFile6 } from "fs/promises";
5190
+ import { existsSync as existsSync6 } from "fs";
5191
+ import { join as join5, dirname as dirname2 } from "path";
4227
5192
  import { fileURLToPath } from "url";
4228
5193
 
4229
5194
  // src/skills/installer.ts
@@ -4348,7 +5313,7 @@ var ResourceManager = class {
4348
5313
  }
4349
5314
  async getScanReportResources() {
4350
5315
  try {
4351
- const reportsDir = join4(getWorkingDirectory(void 0, true), "trie-reports");
5316
+ const reportsDir = join5(getWorkingDirectory(void 0, true), "trie-reports");
4352
5317
  const files = await readdir(reportsDir);
4353
5318
  const reportFiles = files.filter((f) => f.endsWith(".txt") || f.endsWith(".json"));
4354
5319
  return reportFiles.slice(0, 10).map((file) => ({
@@ -4448,10 +5413,10 @@ var ResourceManager = class {
4448
5413
  async getUIAppResource(uri, appId) {
4449
5414
  const currentFile = fileURLToPath(import.meta.url);
4450
5415
  const distDir = dirname2(dirname2(currentFile));
4451
- const uiDir = join4(distDir, "ui");
4452
- const htmlPath = join4(uiDir, `${appId}.html`);
5416
+ const uiDir = join5(distDir, "ui");
5417
+ const htmlPath = join5(uiDir, `${appId}.html`);
4453
5418
  try {
4454
- if (!existsSync5(htmlPath)) {
5419
+ if (!existsSync6(htmlPath)) {
4455
5420
  return {
4456
5421
  contents: [{
4457
5422
  uri,
@@ -4460,7 +5425,7 @@ var ResourceManager = class {
4460
5425
  }]
4461
5426
  };
4462
5427
  }
4463
- const content = await readFile5(htmlPath, "utf-8");
5428
+ const content = await readFile6(htmlPath, "utf-8");
4464
5429
  return {
4465
5430
  contents: [{
4466
5431
  uri,
@@ -4640,10 +5605,10 @@ var ResourceManager = class {
4640
5605
  } catch {
4641
5606
  }
4642
5607
  summary.push("---", "", "# Detailed Context", "");
4643
- const agentsMdPath = join4(getTrieDirectory(workDir), "AGENTS.md");
5608
+ const agentsMdPath = join5(getTrieDirectory(workDir), "AGENTS.md");
4644
5609
  try {
4645
- if (existsSync5(agentsMdPath)) {
4646
- const agentsContent = await readFile5(agentsMdPath, "utf-8");
5610
+ if (existsSync6(agentsMdPath)) {
5611
+ const agentsContent = await readFile6(agentsMdPath, "utf-8");
4647
5612
  summary.push(agentsContent);
4648
5613
  } else {
4649
5614
  const contextSummary = await getContextForAI();
@@ -4768,8 +5733,8 @@ This information is automatically available to Claude Code, Cursor, and other AI
4768
5733
  }
4769
5734
  async getCacheStatsResource(uri) {
4770
5735
  try {
4771
- const cachePath = join4(getTrieDirectory(getWorkingDirectory(void 0, true)), ".trie-cache.json");
4772
- const cacheContent = await readFile5(cachePath, "utf-8");
5736
+ const cachePath = join5(getTrieDirectory(getWorkingDirectory(void 0, true)), ".trie-cache.json");
5737
+ const cacheContent = await readFile6(cachePath, "utf-8");
4773
5738
  const cache = JSON.parse(cacheContent);
4774
5739
  const fileCount = Object.keys(cache.files || {}).length;
4775
5740
  const totalVulns = Object.values(cache.files || {}).reduce((acc, file) => {
@@ -4848,9 +5813,9 @@ This information is automatically available to Claude Code, Cursor, and other AI
4848
5813
  }
4849
5814
  async getScanReportResource(uri, parsedUri) {
4850
5815
  const fileName = parsedUri.replace("reports/", "");
4851
- const reportPath = join4(getWorkingDirectory(void 0, true), "trie-reports", fileName);
5816
+ const reportPath = join5(getWorkingDirectory(void 0, true), "trie-reports", fileName);
4852
5817
  try {
4853
- const content = await readFile5(reportPath, "utf-8");
5818
+ const content = await readFile6(reportPath, "utf-8");
4854
5819
  return {
4855
5820
  contents: [{
4856
5821
  uri,
@@ -5450,8 +6415,25 @@ npx playwright install chromium
5450
6415
  case "cp":
5451
6416
  case "save":
5452
6417
  return await this.toolRegistry.getTool("checkpoint").execute(args);
5453
- default:
6418
+ case "cloud_fix":
6419
+ return await this.toolRegistry.getTool("cloud_fix").execute(args);
6420
+ case "linear_sync":
6421
+ return await this.toolRegistry.getTool("linear_sync").execute(args);
6422
+ case "github_sync":
6423
+ return await this.toolRegistry.getTool("github_sync").execute(args);
6424
+ case "github_branches":
6425
+ return await this.toolRegistry.getTool("github_branches").execute(args);
6426
+ case "pipeline":
6427
+ return await this.toolRegistry.getTool("pipeline").execute(args);
6428
+ case "index":
6429
+ return await this.toolRegistry.getTool("index").execute(args);
6430
+ default: {
6431
+ const tool = this.toolRegistry.getTool(normalizedName);
6432
+ if (tool) {
6433
+ return await tool.execute(args);
6434
+ }
5454
6435
  throw new Error(`Unknown tool: ${name}`);
6436
+ }
5455
6437
  }
5456
6438
  } catch (error) {
5457
6439
  return {
@@ -5477,12 +6459,12 @@ npx playwright install chromium
5477
6459
  }
5478
6460
  normalizeName(name) {
5479
6461
  const stripNamespace = (n) => {
5480
- const trimmed = n.trim();
6462
+ const trimmed = n.trim().replace(/\s*\([^)]*\)\s*$/, "").trim();
5481
6463
  const withoutSlash = trimmed.startsWith("/") ? trimmed.slice(1) : trimmed;
5482
6464
  const parts = withoutSlash.split(":");
5483
- const base = parts[0] || withoutSlash;
6465
+ const base = (parts[0] || withoutSlash).trim();
5484
6466
  const slashParts = base.split("/");
5485
- return slashParts[slashParts.length - 1] || base;
6467
+ return (slashParts[slashParts.length - 1] || base).trim();
5486
6468
  };
5487
6469
  const rawName = stripNamespace(name);
5488
6470
  return rawName.startsWith("trie_") ? rawName.slice("trie_".length) : rawName;