@triedotdev/mcp 1.0.142 → 1.0.144

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.
@@ -977,9 +977,9 @@ function useDashboard() {
977
977
  }
978
978
 
979
979
  // src/cli/dashboard/App.tsx
980
- import { existsSync as existsSync3 } from "fs";
981
- import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
982
- import { join as join2 } from "path";
980
+ import { existsSync as existsSync5 } from "fs";
981
+ import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
982
+ import { join as join3 } from "path";
983
983
 
984
984
  // src/cli/dashboard/components/Header.tsx
985
985
  import { Box, Text, useStdout } from "ink";
@@ -1739,7 +1739,7 @@ function truncate(str, max) {
1739
1739
  }
1740
1740
  function OverviewView() {
1741
1741
  const { state, dispatch } = useDashboard();
1742
- const { progress, signalExtraction, watch, issues, activityLog, activityPage, pendingFixes, selectedFixIndex } = state;
1742
+ const { progress, signalExtraction, watch, issues, activityLog, activityPage, pendingFixes: pendingFixes2, selectedFixIndex } = state;
1743
1743
  const { totalIssues } = progress;
1744
1744
  const { stdout } = useStdout4();
1745
1745
  const cols = stdout?.columns || 80;
@@ -1748,7 +1748,7 @@ function OverviewView() {
1748
1748
  const endTime = state.scanComplete && state.scanEndTime ? state.scanEndTime : Date.now();
1749
1749
  const elapsed = ((endTime - state.scanStartTime) / 1e3).toFixed(1);
1750
1750
  const criticalIssues = issues.filter((i) => i.severity === "critical").slice(0, 3);
1751
- const activeFixes = pendingFixes.filter((f) => f.status === "pending" || f.status === "applying");
1751
+ const activeFixes = pendingFixes2.filter((f) => f.status === "pending" || f.status === "applying");
1752
1752
  const activityRows = activeFixes.length > 0 ? 5 : 8;
1753
1753
  const startIdx = activityPage * activityRows;
1754
1754
  const pageActivities = activityLog.slice(startIdx, startIdx + activityRows);
@@ -5513,6 +5513,1191 @@ trie_explain type:"risk" target:"Adding Stripe integration for payments"
5513
5513
  }
5514
5514
  };
5515
5515
 
5516
+ // src/tools/cloud-fix.ts
5517
+ import { readFile as readFile3, writeFile, mkdir } from "fs/promises";
5518
+ import { existsSync as existsSync4, readFileSync } from "fs";
5519
+ import { join as join2 } from "path";
5520
+ import { execSync } from "child_process";
5521
+
5522
+ // src/integrations/cursor-cloud-agent.ts
5523
+ var BASE_URL = "https://api.cursor.com/v1";
5524
+ var CursorCloudAgentClient = class {
5525
+ apiKey;
5526
+ constructor(apiKey) {
5527
+ this.apiKey = apiKey;
5528
+ }
5529
+ /**
5530
+ * Dispatch an issue to a cloud agent for fixing.
5531
+ */
5532
+ async dispatch(issue, triageResult, repoUrl, branch) {
5533
+ const prompt = this.buildPrompt(issue, triageResult);
5534
+ const body = {
5535
+ prompt,
5536
+ repo: repoUrl,
5537
+ branch,
5538
+ metadata: {
5539
+ issueId: issue.id,
5540
+ file: issue.file,
5541
+ line: issue.line,
5542
+ severity: issue.severity,
5543
+ agent: issue.agent
5544
+ }
5545
+ };
5546
+ const res = await this.request("POST", "/agents/tasks", body);
5547
+ return {
5548
+ jobId: res.id ?? res.taskId ?? res.jobId,
5549
+ status: "dispatched",
5550
+ artifactUrls: [],
5551
+ dispatchedAt: (/* @__PURE__ */ new Date()).toISOString()
5552
+ };
5553
+ }
5554
+ /**
5555
+ * Poll job status.
5556
+ */
5557
+ async poll(jobId) {
5558
+ const res = await this.request("GET", `/agents/tasks/${jobId}`);
5559
+ const status = mapStatus(res.status);
5560
+ const prUrl = extractPrUrl(res);
5561
+ const artifactUrls = this.extractArtifacts(res);
5562
+ return { status, prUrl, artifactUrls };
5563
+ }
5564
+ /**
5565
+ * Get artifact URLs for a completed job.
5566
+ */
5567
+ async getArtifacts(jobId) {
5568
+ const res = await this.request("GET", `/agents/tasks/${jobId}`);
5569
+ return this.extractArtifacts(res);
5570
+ }
5571
+ /**
5572
+ * Cancel a running job.
5573
+ */
5574
+ async cancelJob(jobId) {
5575
+ await this.request("DELETE", `/agents/tasks/${jobId}`);
5576
+ }
5577
+ // --------------------------------------------------------------------------
5578
+ // Internal
5579
+ // --------------------------------------------------------------------------
5580
+ buildPrompt(issue, triageResult) {
5581
+ const parts = [
5582
+ "You are fixing a verified issue in the codebase.",
5583
+ "",
5584
+ `Issue: ${issue.issue}`,
5585
+ `File: ${issue.file}${issue.line ? `:${issue.line}` : ""}`,
5586
+ `Severity: ${issue.severity} | Effort: ${issue.effort ?? "medium"}`,
5587
+ `Agent that found it: ${issue.agent}`,
5588
+ `Suggested fix: ${issue.fix}`
5589
+ ];
5590
+ if (issue.cwe) parts.push(`CWE: ${issue.cwe}`);
5591
+ if (issue.owasp) parts.push(`OWASP: ${issue.owasp}`);
5592
+ parts.push(`Triage confidence: ${triageResult.confidence.toFixed(2)}`);
5593
+ parts.push(`Why cloud agent: ${triageResult.reasons.join(", ")}`);
5594
+ parts.push("");
5595
+ parts.push("Steps:");
5596
+ parts.push(`1. Apply the minimal fix described above to ${issue.file}`);
5597
+ parts.push("2. Run the existing test suite (detect test runner from package.json scripts)");
5598
+ parts.push("3. Screenshot the passing test output \u2014 this is the verification artifact");
5599
+ parts.push("4. If tests fail, iterate on the fix until they pass (max 3 attempts)");
5600
+ parts.push("5. Open a PR with only this single change \u2014 do not bundle other fixes");
5601
+ parts.push("6. Include the screenshot in the PR description as evidence");
5602
+ return parts.join("\n");
5603
+ }
5604
+ async request(method, path2, body) {
5605
+ const url = `${BASE_URL}${path2}`;
5606
+ const headers = {
5607
+ "Authorization": `Bearer ${this.apiKey}`,
5608
+ "Content-Type": "application/json"
5609
+ };
5610
+ const init = { method, headers };
5611
+ if (body) init.body = JSON.stringify(body);
5612
+ const res = await fetch(url, init);
5613
+ if (res.status === 401) {
5614
+ throw new Error(
5615
+ 'Cursor API key is invalid or expired.\nUpdate it: trie_cloud_fix action:configure apiKey:"key-..."'
5616
+ );
5617
+ }
5618
+ if (res.status === 404) {
5619
+ throw new Error(`Job not found: ${path2}`);
5620
+ }
5621
+ if (res.status >= 500) {
5622
+ throw new Error(
5623
+ `Cursor API returned ${res.status}. The service may be temporarily unavailable \u2014 retry in a few minutes.`
5624
+ );
5625
+ }
5626
+ if (!res.ok) {
5627
+ const text = await res.text().catch(() => "");
5628
+ throw new Error(`Cursor API error ${res.status}: ${text}`);
5629
+ }
5630
+ return res.json();
5631
+ }
5632
+ extractArtifacts(res) {
5633
+ const urls = [];
5634
+ if (Array.isArray(res.messages)) {
5635
+ for (const msg of res.messages) {
5636
+ if (typeof msg.content === "string") {
5637
+ const matches = msg.content.match(/https:\/\/[^\s)]+\.(png|jpg|mp4|webm)/g);
5638
+ if (matches) urls.push(...matches);
5639
+ }
5640
+ }
5641
+ }
5642
+ if (Array.isArray(res.artifacts)) {
5643
+ for (const a of res.artifacts) {
5644
+ if (a.url) urls.push(a.url);
5645
+ }
5646
+ }
5647
+ return [...new Set(urls)];
5648
+ }
5649
+ };
5650
+ function mapStatus(raw) {
5651
+ switch (raw) {
5652
+ case "pending":
5653
+ case "queued":
5654
+ return "pending";
5655
+ case "running":
5656
+ case "in_progress":
5657
+ return "running";
5658
+ case "completed":
5659
+ case "succeeded":
5660
+ case "verified":
5661
+ return "completed";
5662
+ default:
5663
+ return "failed";
5664
+ }
5665
+ }
5666
+ function extractPrUrl(res) {
5667
+ if (typeof res.prUrl === "string") return res.prUrl;
5668
+ if (typeof res.pr_url === "string") return res.pr_url;
5669
+ if (typeof res.pullRequestUrl === "string") return res.pullRequestUrl;
5670
+ if (Array.isArray(res.messages)) {
5671
+ for (const msg of res.messages) {
5672
+ if (typeof msg.content === "string") {
5673
+ const match = msg.content.match(/https:\/\/github\.com\/[^\s)]+\/pull\/\d+/);
5674
+ if (match) return match[0];
5675
+ }
5676
+ }
5677
+ }
5678
+ return void 0;
5679
+ }
5680
+
5681
+ // src/tools/fix-triage.ts
5682
+ var EFFORT_SCORES = {
5683
+ trivial: -6,
5684
+ easy: -3,
5685
+ medium: 0,
5686
+ hard: 4
5687
+ };
5688
+ var SEVERITY_SCORES = {
5689
+ critical: 4,
5690
+ serious: 2,
5691
+ moderate: -1,
5692
+ low: -3
5693
+ };
5694
+ function triageIssue(issue, context, occurrence, config, pipeline) {
5695
+ const reasons = [];
5696
+ if (config?.level === "passive") {
5697
+ return {
5698
+ strategy: "local-ai",
5699
+ score: 0,
5700
+ confidence: 1,
5701
+ reasons: ["Autonomy level is passive \u2014 cloud dispatch disabled"],
5702
+ fallback: "local-ai"
5703
+ };
5704
+ }
5705
+ if (config?.cloudAgentEnabled === false) {
5706
+ return {
5707
+ strategy: "local-ai",
5708
+ score: 0,
5709
+ confidence: 1,
5710
+ reasons: ["Cloud agent not enabled (run trie_cloud_fix action:configure)"],
5711
+ fallback: "local-ai"
5712
+ };
5713
+ }
5714
+ let score = 0;
5715
+ const effort = issue.effort ?? "medium";
5716
+ const effortScore = EFFORT_SCORES[effort] ?? 0;
5717
+ if (effortScore !== 0) {
5718
+ score += effortScore;
5719
+ reasons.push(`effort:${effort}`);
5720
+ }
5721
+ const severityScore = SEVERITY_SCORES[issue.severity] ?? 0;
5722
+ if (severityScore !== 0) {
5723
+ score += severityScore;
5724
+ reasons.push(`severity:${issue.severity}`);
5725
+ }
5726
+ if (issue.autoFixable) {
5727
+ score -= 2;
5728
+ reasons.push("autoFixable");
5729
+ }
5730
+ if (issue.confidence < 0.7) {
5731
+ score -= 2;
5732
+ reasons.push(`low confidence (${(issue.confidence * 100).toFixed(0)}%)`);
5733
+ }
5734
+ if (issue.cwe) {
5735
+ score += 3;
5736
+ reasons.push(`cwe:${issue.cwe}`);
5737
+ }
5738
+ if (issue.owasp) {
5739
+ score += 2;
5740
+ reasons.push(`owasp:${issue.owasp}`);
5741
+ }
5742
+ if (issue.category === "security") {
5743
+ score += 2;
5744
+ reasons.push("category:security");
5745
+ }
5746
+ if (context) {
5747
+ if (context.hasTests) {
5748
+ score += 1;
5749
+ reasons.push("has tests");
5750
+ } else {
5751
+ score -= 2;
5752
+ reasons.push("no tests");
5753
+ }
5754
+ if (context.complexity === "high") {
5755
+ score += 1;
5756
+ reasons.push("high complexity");
5757
+ }
5758
+ if (context.touchesAuth || context.touchesCrypto || context.touchesPayments) {
5759
+ score += 2;
5760
+ reasons.push("touches auth/crypto/payments");
5761
+ }
5762
+ if (context.touchesDatabase) {
5763
+ score += 1;
5764
+ reasons.push("touches database");
5765
+ }
5766
+ }
5767
+ if (occurrence) {
5768
+ if (occurrence.count >= 5) {
5769
+ score += 3;
5770
+ reasons.push(`${occurrence.count}\xD7 seen`);
5771
+ } else if (occurrence.count >= 3) {
5772
+ score += 1;
5773
+ reasons.push(`${occurrence.count}\xD7 seen`);
5774
+ }
5775
+ if (occurrence.escalationLevel === "block") {
5776
+ score += 4;
5777
+ reasons.push("escalation:block");
5778
+ } else if (occurrence.escalationLevel === "escalate") {
5779
+ score += 2;
5780
+ reasons.push("escalation:escalate");
5781
+ }
5782
+ }
5783
+ if (config?.level === "aggressive") {
5784
+ score -= 1;
5785
+ reasons.push("aggressive mode (\u22121 threshold)");
5786
+ }
5787
+ if (pipeline) {
5788
+ if (pipeline.hasLinkedPR && pipeline.prState === "open") {
5789
+ score -= 2;
5790
+ reasons.push("has open PR");
5791
+ }
5792
+ if (pipeline.hasLinkedTicket && pipeline.ticketStatus?.toLowerCase().includes("started")) {
5793
+ score -= 1;
5794
+ reasons.push("ticket in active sprint");
5795
+ }
5796
+ if (pipeline.hasLinkedTicket && pipeline.ticketPriority === "urgent") {
5797
+ score += 1;
5798
+ reasons.push("ticket:urgent");
5799
+ }
5800
+ if (!pipeline.hasLinkedTicket && !pipeline.hasLinkedPR && issue.severity === "critical") {
5801
+ score += 2;
5802
+ reasons.push("critical, no ticket/PR (falling through cracks)");
5803
+ }
5804
+ }
5805
+ let strategy;
5806
+ if (score < 0 && issue.autoFixable) {
5807
+ strategy = "inline-auto";
5808
+ } else if (score < 4) {
5809
+ strategy = "local-ai";
5810
+ } else {
5811
+ strategy = "cloud-agent";
5812
+ }
5813
+ const confidence = Math.min(1, Math.abs(score) / 8);
5814
+ return { strategy, score, confidence, reasons, fallback: "local-ai" };
5815
+ }
5816
+ function triageIssues(issues, context, occurrences, config, pipelineContexts) {
5817
+ const results = /* @__PURE__ */ new Map();
5818
+ const summary = {
5819
+ inlineAuto: [],
5820
+ localAi: [],
5821
+ cloudAgent: [],
5822
+ cloudAgentScore: 0
5823
+ };
5824
+ for (const issue of issues) {
5825
+ const occurrence = occurrences?.get(issue.id);
5826
+ const pipeline = pipelineContexts?.get(issue.id);
5827
+ const result = triageIssue(issue, context, occurrence, config, pipeline);
5828
+ results.set(issue.id, result);
5829
+ switch (result.strategy) {
5830
+ case "inline-auto":
5831
+ summary.inlineAuto.push(issue);
5832
+ break;
5833
+ case "local-ai":
5834
+ summary.localAi.push(issue);
5835
+ break;
5836
+ case "cloud-agent":
5837
+ summary.cloudAgent.push(issue);
5838
+ summary.cloudAgentScore += result.score;
5839
+ break;
5840
+ }
5841
+ }
5842
+ return { results, summary };
5843
+ }
5844
+ function formatTriageTable(results, issues) {
5845
+ const LINE = "\u2500".repeat(68);
5846
+ const lines = [];
5847
+ lines.push("FIX ROUTING PLAN");
5848
+ lines.push(LINE);
5849
+ lines.push(
5850
+ padEnd("Issue", 32) + padEnd("Strategy", 14) + padEnd("Score", 7) + "Reason"
5851
+ );
5852
+ for (const issue of issues) {
5853
+ const r = results.get(issue.id);
5854
+ if (!r) continue;
5855
+ const loc = `${shortPath(issue.file)}:${issue.line ?? "?"}`;
5856
+ const scoreStr = r.score >= 0 ? `+${r.score}` : String(r.score);
5857
+ lines.push(
5858
+ padEnd(loc, 32) + padEnd(r.strategy, 14) + padEnd(scoreStr, 7) + r.reasons.join(", ")
5859
+ );
5860
+ }
5861
+ lines.push(LINE);
5862
+ const counts = [];
5863
+ const cloud = issues.filter((i) => results.get(i.id)?.strategy === "cloud-agent");
5864
+ const local = issues.filter((i) => results.get(i.id)?.strategy === "local-ai");
5865
+ const auto = issues.filter((i) => results.get(i.id)?.strategy === "inline-auto");
5866
+ if (cloud.length) counts.push(`${cloud.length} for cloud agent`);
5867
+ if (local.length) counts.push(`${local.length} for local AI`);
5868
+ if (auto.length) counts.push(`${auto.length} auto-fixable`);
5869
+ lines.push(counts.join(", "));
5870
+ if (cloud.length > 0) {
5871
+ const ids = cloud.map((i) => `"${i.id}"`).join(",");
5872
+ lines.push("");
5873
+ lines.push(`To dispatch cloud issues: trie_cloud_fix action:dispatch issueIds:[${ids}]`);
5874
+ }
5875
+ if (local.length > 0) {
5876
+ const ids = local.map((i) => `"${i.id}"`).join(",");
5877
+ lines.push(`To fix local issues: trie_fix issueIds:[${ids}]`);
5878
+ }
5879
+ return lines.join("\n");
5880
+ }
5881
+ function formatCloudRecommendation(results, issues) {
5882
+ const cloud = issues.filter((i) => {
5883
+ const r = results.get(i.id);
5884
+ return r && r.score >= 4;
5885
+ });
5886
+ if (cloud.length === 0) return null;
5887
+ const LINE = "\u2500".repeat(65);
5888
+ const lines = [];
5889
+ lines.push(LINE);
5890
+ lines.push(
5891
+ `${cloud.length} issue${cloud.length > 1 ? "s" : ""} qualify for cloud agent verification (test-verified fix + PR):`
5892
+ );
5893
+ for (const issue of cloud) {
5894
+ const r = results.get(issue.id);
5895
+ const loc = `${shortPath(issue.file)}:${issue.line ?? "?"}`;
5896
+ const scoreStr = r.score >= 0 ? `+${r.score}` : String(r.score);
5897
+ lines.push(` \u2022 ${padEnd(loc, 30)} \u2014 score ${scoreStr} (${r.reasons.join(", ")})`);
5898
+ }
5899
+ const ids = cloud.map((i) => `"${i.id}"`).join(",");
5900
+ lines.push("");
5901
+ lines.push(`Run: trie_cloud_fix action:dispatch issueIds:[${ids}]`);
5902
+ lines.push(LINE);
5903
+ return lines.join("\n");
5904
+ }
5905
+ function shortPath(file) {
5906
+ const parts = file.split("/");
5907
+ return parts.length > 2 ? parts.slice(-2).join("/") : file;
5908
+ }
5909
+ function padEnd(str, len) {
5910
+ if (str.length >= len) return str.slice(0, len);
5911
+ return str + " ".repeat(len - str.length);
5912
+ }
5913
+
5914
+ // src/tools/fix.ts
5915
+ import { readFile as readFile2 } from "fs/promises";
5916
+ import { existsSync as existsSync3 } from "fs";
5917
+ import { extname as extname2, relative as relative2, resolve as resolve2, isAbsolute as isAbsolute2 } from "path";
5918
+ var pendingFixes = /* @__PURE__ */ new Map();
5919
+ var TrieFixTool = class {
5920
+ async execute(args) {
5921
+ const { issueIds, file, line, issue, fix, autoApprove = false, dryRun = false, action } = args || {};
5922
+ if (action === "route") {
5923
+ return this.routeIssues(issueIds);
5924
+ }
5925
+ if (issueIds && issueIds.length > 0) {
5926
+ return this.fixByIds(issueIds, autoApprove, dryRun);
5927
+ }
5928
+ if (file && fix) {
5929
+ return this.applyFix(file, line || 1, issue || "User-specified fix", fix, dryRun);
5930
+ }
5931
+ if (pendingFixes.size > 0) {
5932
+ return this.showPendingFixes();
5933
+ }
5934
+ if (file && issue) {
5935
+ return this.generateFixPrompt(file, line || 1, issue);
5936
+ }
5937
+ return {
5938
+ content: [{
5939
+ type: "text",
5940
+ text: this.getHelpText()
5941
+ }]
5942
+ };
5943
+ }
5944
+ async routeIssues(issueIds) {
5945
+ await loadPendingFixesFromMemory();
5946
+ const pending = getPendingFixes();
5947
+ if (pending.length === 0) {
5948
+ return {
5949
+ content: [{ type: "text", text: "No pending issues. Run trie_scan to detect new issues, or check memory with trie_memory action:recent." }]
5950
+ };
5951
+ }
5952
+ const issues = pending.map((p) => ({
5953
+ id: p.id,
5954
+ severity: p.severity ?? "moderate",
5955
+ effort: p.effort,
5956
+ issue: p.issue,
5957
+ fix: p.suggestedFix,
5958
+ file: p.file,
5959
+ line: p.line,
5960
+ confidence: p.confidence,
5961
+ autoFixable: p.autoFixable ?? false,
5962
+ agent: "trie_scan",
5963
+ cwe: p.cwe,
5964
+ owasp: p.owasp,
5965
+ category: p.category
5966
+ }));
5967
+ const filtered = issueIds && issueIds.length > 0 ? issues.filter((i) => issueIds.includes(i.id)) : issues;
5968
+ const workDir = getWorkingDirectory(void 0, true);
5969
+ const config = await loadAutonomyConfig(workDir);
5970
+ const { results } = triageIssues(filtered, void 0, void 0, config);
5971
+ const table = formatTriageTable(results, filtered);
5972
+ return { content: [{ type: "text", text: `
5973
+ ${table}
5974
+ ` }] };
5975
+ }
5976
+ async fixByIds(issueIds, autoApprove, dryRun) {
5977
+ const results = [];
5978
+ let fixed = 0;
5979
+ let failed = 0;
5980
+ for (const id of issueIds) {
5981
+ const pendingFix = pendingFixes.get(id);
5982
+ if (!pendingFix) {
5983
+ results.push(`\u274C Issue ${id}: Not found in pending fixes`);
5984
+ failed++;
5985
+ continue;
5986
+ }
5987
+ if (pendingFix.confidence < 0.8 && !autoApprove) {
5988
+ results.push(`[!] Issue ${id}: Confidence too low (${(pendingFix.confidence * 100).toFixed(0)}%) - use autoApprove:true to override`);
5989
+ continue;
5990
+ }
5991
+ if (dryRun) {
5992
+ results.push(`\u{1F50D} Issue ${id}: Would fix "${pendingFix.issue}" in ${pendingFix.file}:${pendingFix.line}`);
5993
+ continue;
5994
+ }
5995
+ try {
5996
+ results.push(`\u2705 Issue ${id}: Fix prepared for ${pendingFix.file}:${pendingFix.line}`);
5997
+ results.push(` Issue: ${pendingFix.issue}`);
5998
+ results.push(` Fix: ${pendingFix.suggestedFix}`);
5999
+ pendingFix.status = "applied";
6000
+ fixed++;
6001
+ } catch (error) {
6002
+ results.push(`\u274C Issue ${id}: Failed to apply - ${error}`);
6003
+ failed++;
6004
+ }
6005
+ }
6006
+ let output = `
6007
+ ${"\u2501".repeat(60)}
6008
+ `;
6009
+ output += `\u{1F527} FIX RESULTS
6010
+ `;
6011
+ output += `${"\u2501".repeat(60)}
6012
+
6013
+ `;
6014
+ output += results.join("\n");
6015
+ output += `
6016
+
6017
+ **Summary:** ${fixed} fixed, ${failed} failed, ${issueIds.length - fixed - failed} skipped
6018
+ `;
6019
+ output += await this.appendCloudRecommendation(issueIds);
6020
+ return { content: [{ type: "text", text: output }] };
6021
+ }
6022
+ async applyFix(file, line, issue, fix, dryRun) {
6023
+ const workDir = getWorkingDirectory(void 0, true);
6024
+ const filePath = isAbsolute2(file) ? file : resolve2(workDir, file);
6025
+ if (!existsSync3(filePath)) {
6026
+ return {
6027
+ content: [{
6028
+ type: "text",
6029
+ text: `\u274C File not found: ${filePath}`
6030
+ }]
6031
+ };
6032
+ }
6033
+ const content = await readFile2(filePath, "utf-8");
6034
+ const lines = content.split("\n");
6035
+ const language = this.detectLanguage(filePath);
6036
+ const contextStart = Math.max(0, line - 10);
6037
+ const contextEnd = Math.min(lines.length, line + 10);
6038
+ const contextLines = lines.slice(contextStart, contextEnd);
6039
+ const prompt = getPrompt("fix", "apply", {
6040
+ issue,
6041
+ fix,
6042
+ language,
6043
+ code: contextLines.join("\n"),
6044
+ filePath: relative2(workDir, filePath),
6045
+ line: String(line)
6046
+ });
6047
+ const systemPrompt = getSystemPrompt("fix");
6048
+ let output = `
6049
+ ${"\u2501".repeat(60)}
6050
+ `;
6051
+ output += `\u{1F527} FIX APPLICATION REQUEST
6052
+ `;
6053
+ output += `${"\u2501".repeat(60)}
6054
+
6055
+ `;
6056
+ output += `## \u{1F4CD} Target
6057
+
6058
+ `;
6059
+ output += `- **File:** \`${relative2(workDir, filePath)}\`
6060
+ `;
6061
+ output += `- **Line:** ${line}
6062
+ `;
6063
+ output += `- **Issue:** ${issue}
6064
+ `;
6065
+ output += `- **Requested Fix:** ${fix}
6066
+
6067
+ `;
6068
+ output += `## \u{1F4C4} Current Code Context
6069
+
6070
+ `;
6071
+ output += `\`\`\`${language}
6072
+ `;
6073
+ for (let i = 0; i < contextLines.length; i++) {
6074
+ const lineNum = contextStart + i + 1;
6075
+ const marker = lineNum === line ? "\u2192 " : " ";
6076
+ output += `${marker}${lineNum.toString().padStart(4)} | ${contextLines[i]}
6077
+ `;
6078
+ }
6079
+ output += `\`\`\`
6080
+
6081
+ `;
6082
+ if (dryRun) {
6083
+ output += `## \u{1F50D} Dry Run Mode
6084
+
6085
+ `;
6086
+ output += `No changes will be made. Review the fix below:
6087
+
6088
+ `;
6089
+ }
6090
+ output += `${"\u2500".repeat(60)}
6091
+ `;
6092
+ output += `## \u{1F9E0} Fix Request for AI
6093
+
6094
+ `;
6095
+ output += `**Role:** ${systemPrompt.split("\n")[0]}
6096
+
6097
+ `;
6098
+ output += prompt;
6099
+ output += `
6100
+ ${"\u2500".repeat(60)}
6101
+ `;
6102
+ output += `
6103
+ ### After generating the fix, apply it with:
6104
+
6105
+ `;
6106
+ output += `\`\`\`
6107
+ `;
6108
+ output += `Use the edit_file tool to apply the generated code changes
6109
+ `;
6110
+ output += `\`\`\`
6111
+ `;
6112
+ return { content: [{ type: "text", text: output }] };
6113
+ }
6114
+ async generateFixPrompt(file, line, issue) {
6115
+ const workDir = getWorkingDirectory(void 0, true);
6116
+ const filePath = isAbsolute2(file) ? file : resolve2(workDir, file);
6117
+ if (!existsSync3(filePath)) {
6118
+ return {
6119
+ content: [{
6120
+ type: "text",
6121
+ text: `\u274C File not found: ${filePath}`
6122
+ }]
6123
+ };
6124
+ }
6125
+ const content = await readFile2(filePath, "utf-8");
6126
+ const lines = content.split("\n");
6127
+ const language = this.detectLanguage(filePath);
6128
+ const contextStart = Math.max(0, line - 20);
6129
+ const contextEnd = Math.min(lines.length, line + 20);
6130
+ const contextLines = lines.slice(contextStart, contextEnd);
6131
+ let output = `
6132
+ ${"\u2501".repeat(60)}
6133
+ `;
6134
+ output += `\u{1F527} FIX GENERATION REQUEST
6135
+ `;
6136
+ output += `${"\u2501".repeat(60)}
6137
+
6138
+ `;
6139
+ output += `## \u{1F4CD} Issue Details
6140
+
6141
+ `;
6142
+ output += `- **File:** \`${relative2(workDir, filePath)}\`
6143
+ `;
6144
+ output += `- **Line:** ${line}
6145
+ `;
6146
+ output += `- **Issue:** ${issue}
6147
+
6148
+ `;
6149
+ output += `## \u{1F4C4} Code Context
6150
+
6151
+ `;
6152
+ output += `\`\`\`${language}
6153
+ `;
6154
+ for (let i = 0; i < contextLines.length; i++) {
6155
+ const lineNum = contextStart + i + 1;
6156
+ const marker = lineNum === line ? "\u2192 " : " ";
6157
+ output += `${marker}${lineNum.toString().padStart(4)} | ${contextLines[i]}
6158
+ `;
6159
+ }
6160
+ output += `\`\`\`
6161
+
6162
+ `;
6163
+ output += `## \u{1F9E0} Analysis Request
6164
+
6165
+ `;
6166
+ output += `Please analyze this issue and provide:
6167
+
6168
+ `;
6169
+ output += `1. **Root cause** - Why does this issue occur?
6170
+ `;
6171
+ output += `2. **Impact** - What could go wrong if unfixed?
6172
+ `;
6173
+ output += `3. **Fix** - The exact code change needed
6174
+ `;
6175
+ output += `4. **Verification** - How to test the fix works
6176
+
6177
+ `;
6178
+ output += `After analysis, you can apply the fix using the edit_file tool.
6179
+ `;
6180
+ return { content: [{ type: "text", text: output }] };
6181
+ }
6182
+ showPendingFixes() {
6183
+ let output = `
6184
+ ${"\u2501".repeat(60)}
6185
+ `;
6186
+ output += `\u{1F527} PENDING FIXES
6187
+ `;
6188
+ output += `${"\u2501".repeat(60)}
6189
+
6190
+ `;
6191
+ if (pendingFixes.size === 0) {
6192
+ output += `No pending fixes. Run \`trie_scan\` first to detect issues.
6193
+ `;
6194
+ return { content: [{ type: "text", text: output }] };
6195
+ }
6196
+ const fixes = Array.from(pendingFixes.values());
6197
+ const byStatus = {
6198
+ pending: fixes.filter((f) => f.status === "pending"),
6199
+ applied: fixes.filter((f) => f.status === "applied"),
6200
+ rejected: fixes.filter((f) => f.status === "rejected")
6201
+ };
6202
+ if (byStatus.pending.length > 0) {
6203
+ output += `## \u23F3 Pending (${byStatus.pending.length})
6204
+
6205
+ `;
6206
+ output += `| ID | File | Line | Issue | Confidence |
6207
+ `;
6208
+ output += `|----|------|------|-------|------------|
6209
+ `;
6210
+ for (const fix of byStatus.pending) {
6211
+ const conf = `${(fix.confidence * 100).toFixed(0)}%`;
6212
+ const shortFile = fix.file.split("/").slice(-2).join("/");
6213
+ output += `| ${fix.id} | ${shortFile} | ${fix.line} | ${fix.issue.slice(0, 40)}... | ${conf} |
6214
+ `;
6215
+ }
6216
+ output += "\n";
6217
+ }
6218
+ output += `### Commands
6219
+
6220
+ `;
6221
+ output += `- Fix all high-confidence: \`trie_fix autoApprove:true\`
6222
+ `;
6223
+ output += `- Fix specific: \`trie_fix issueIds:["id1", "id2"]\`
6224
+ `;
6225
+ output += `- Preview: \`trie_fix dryRun:true\`
6226
+ `;
6227
+ return { content: [{ type: "text", text: output }] };
6228
+ }
6229
+ getHelpText() {
6230
+ return `
6231
+ ${"\u2501".repeat(60)}
6232
+ \u{1F527} TRIE FIX - AI-POWERED CODE FIXING
6233
+ ${"\u2501".repeat(60)}
6234
+
6235
+ ## Usage
6236
+
6237
+ ### Fix issues from a scan:
6238
+ \`\`\`
6239
+ trie_fix issueIds:["issue-1", "issue-2"]
6240
+ \`\`\`
6241
+
6242
+ ### Auto-fix all high-confidence issues:
6243
+ \`\`\`
6244
+ trie_fix autoApprove:true
6245
+ \`\`\`
6246
+
6247
+ ### Fix specific file and line:
6248
+ \`\`\`
6249
+ trie_fix file:"src/app.ts" line:42 issue:"SQL injection" fix:"Use parameterized query"
6250
+ \`\`\`
6251
+
6252
+ ### Preview fixes without applying:
6253
+ \`\`\`
6254
+ trie_fix dryRun:true
6255
+ \`\`\`
6256
+
6257
+ ### View pending fixes:
6258
+ \`\`\`
6259
+ trie_fix
6260
+ \`\`\`
6261
+
6262
+ ## Workflow
6263
+
6264
+ 1. Run \`trie_scan\` to detect issues
6265
+ 2. Review the issues found
6266
+ 3. Run \`trie_fix\` to apply fixes
6267
+
6268
+ The AI will analyze each issue, generate the fix, and you can review before applying.
6269
+ `;
6270
+ }
6271
+ async appendCloudRecommendation(handledIds) {
6272
+ try {
6273
+ const pending = getPendingFixes();
6274
+ const remaining = pending.filter((p) => !handledIds.includes(p.id) || pendingFixes.get(p.id)?.status === "pending");
6275
+ if (remaining.length === 0) return "";
6276
+ const issues = remaining.map((p) => ({
6277
+ id: p.id,
6278
+ severity: "moderate",
6279
+ issue: p.issue,
6280
+ fix: p.suggestedFix,
6281
+ file: p.file,
6282
+ line: p.line,
6283
+ confidence: p.confidence,
6284
+ autoFixable: false,
6285
+ agent: "trie_scan"
6286
+ }));
6287
+ const workDir = getWorkingDirectory(void 0, true);
6288
+ const config = await loadAutonomyConfig(workDir);
6289
+ const { results } = triageIssues(issues, void 0, void 0, config);
6290
+ const footer = formatCloudRecommendation(results, issues);
6291
+ return footer ? `
6292
+ ${footer}
6293
+ ` : "";
6294
+ } catch {
6295
+ return "";
6296
+ }
6297
+ }
6298
+ detectLanguage(filePath) {
6299
+ const ext = extname2(filePath).toLowerCase();
6300
+ const langMap = {
6301
+ ".ts": "typescript",
6302
+ ".tsx": "tsx",
6303
+ ".js": "javascript",
6304
+ ".jsx": "jsx",
6305
+ ".py": "python",
6306
+ ".go": "go",
6307
+ ".rs": "rust"
6308
+ };
6309
+ return langMap[ext] || "plaintext";
6310
+ }
6311
+ };
6312
+ function getPendingFixes() {
6313
+ return Array.from(pendingFixes.values());
6314
+ }
6315
+ async function loadPendingFixesFromMemory() {
6316
+ try {
6317
+ const { getRecentIssues } = await import("./issue-store-LZWZIGM7.js");
6318
+ pendingFixes.clear();
6319
+ const recentIssues = await getRecentIssues({ limit: 50, includeResolved: false });
6320
+ for (const storedIssue of recentIssues) {
6321
+ const fix = {
6322
+ id: storedIssue.id,
6323
+ file: storedIssue.file,
6324
+ line: storedIssue.line || 0,
6325
+ issue: storedIssue.issue,
6326
+ suggestedFix: storedIssue.fix,
6327
+ confidence: 0.8,
6328
+ // Default confidence for memory issues
6329
+ status: "pending",
6330
+ severity: storedIssue.severity,
6331
+ autoFixable: true,
6332
+ // Memory issues are generally auto-fixable
6333
+ category: storedIssue.category
6334
+ };
6335
+ pendingFixes.set(fix.id, fix);
6336
+ }
6337
+ } catch (error) {
6338
+ console.warn("Failed to load pending fixes from memory:", error);
6339
+ }
6340
+ }
6341
+
6342
+ // src/tools/cloud-fix.ts
6343
+ var TrieCloudFixTool = class {
6344
+ async execute(args) {
6345
+ const { action = "status" } = args || {};
6346
+ switch (action) {
6347
+ case "configure":
6348
+ return this.configure(args);
6349
+ case "dispatch":
6350
+ return this.dispatch(args);
6351
+ case "status":
6352
+ return this.status();
6353
+ case "artifacts":
6354
+ return this.artifacts(args);
6355
+ case "cancel":
6356
+ return this.cancel(args);
6357
+ default:
6358
+ return this.text(`Unknown action "${action}". Use: configure | dispatch | status | artifacts | cancel`);
6359
+ }
6360
+ }
6361
+ // --------------------------------------------------------------------------
6362
+ // configure
6363
+ // --------------------------------------------------------------------------
6364
+ async configure(args) {
6365
+ const apiKey = args?.apiKey || process.env.CURSOR_API_KEY;
6366
+ if (!apiKey) {
6367
+ return this.text(
6368
+ 'Missing API key.\n\nUsage: trie_cloud_fix action:configure apiKey:"key-..."\nOr set: export CURSOR_API_KEY="key-..."'
6369
+ );
6370
+ }
6371
+ const workDir = getWorkingDirectory(void 0, true);
6372
+ await saveAutonomyConfig(workDir, { cloudAgentEnabled: true, cursorApiKey: apiKey });
6373
+ return this.text("Cursor API key saved. Cloud agent dispatch enabled.");
6374
+ }
6375
+ // --------------------------------------------------------------------------
6376
+ // dispatch
6377
+ // --------------------------------------------------------------------------
6378
+ async dispatch(args) {
6379
+ console.log("Cloud dispatch starting...");
6380
+ const workDir = getWorkingDirectory(void 0, true);
6381
+ const apiKey = await this.resolveApiKey(workDir);
6382
+ if (!apiKey) return this.setupGuard();
6383
+ const config = await loadAutonomyConfig(workDir);
6384
+ const forceCloud = args?.forceCloud === true;
6385
+ console.log("About to resolve issues...");
6386
+ let allIssues = await this.resolveIssues(args?.issueIds);
6387
+ if (allIssues.length === 0 && args?.file && args?.issue && args?.fix) {
6388
+ const adHocId = `ad-hoc-${Date.now()}`;
6389
+ allIssues = [{
6390
+ id: adHocId,
6391
+ severity: args.severity ?? "critical",
6392
+ effort: args.effort ?? "hard",
6393
+ issue: args.issue,
6394
+ fix: args.fix,
6395
+ file: args.file,
6396
+ line: args.line ?? 1,
6397
+ confidence: 0.9,
6398
+ autoFixable: false,
6399
+ agent: "user",
6400
+ category: args.category
6401
+ }];
6402
+ console.log(`Created ad-hoc issue ${adHocId} for cloud dispatch`);
6403
+ }
6404
+ if (allIssues.length === 0) {
6405
+ return this.text("No issues to dispatch. Run trie_scan to detect new issues, or check memory with trie_memory action:recent. For ad-hoc fixes, pass file, issue, and fix.");
6406
+ }
6407
+ const { results, summary } = triageIssues(allIssues, void 0, void 0, config);
6408
+ const lines = [];
6409
+ const toDispatch = forceCloud ? allIssues : summary.cloudAgent;
6410
+ if (forceCloud) {
6411
+ lines.push("FIX ROUTING PLAN (forceCloud: bypassing triage)");
6412
+ lines.push("\u2500".repeat(68));
6413
+ for (const issue of allIssues) {
6414
+ lines.push(` ${shortPath2(issue.file)}:${issue.line ?? "?"} \u2192 cloud-agent (user requested)`);
6415
+ }
6416
+ lines.push("");
6417
+ } else {
6418
+ lines.push(formatTriageTable(results, allIssues));
6419
+ lines.push("");
6420
+ for (const issue of allIssues) {
6421
+ const r = results.get(issue.id);
6422
+ if (r && r.strategy !== "cloud-agent") {
6423
+ lines.push(`Skipped ${issue.id}: routed to ${r.strategy} (score ${r.score >= 0 ? "+" : ""}${r.score} \u2014 ${r.reasons.join(", ")})`);
6424
+ }
6425
+ }
6426
+ if (summary.cloudAgent.length === 0) {
6427
+ lines.push("\nNo issues qualify for cloud dispatch. Use trie_fix for local fixes.");
6428
+ lines.push("To force cloud dispatch: trie_cloud_fix action:dispatch forceCloud:true");
6429
+ return this.text(lines.join("\n"));
6430
+ }
6431
+ }
6432
+ const client = new CursorCloudAgentClient(apiKey);
6433
+ const repoUrl = this.getRepoUrl(workDir);
6434
+ const branch = this.getBranch(workDir);
6435
+ const store = await this.loadJobs(workDir);
6436
+ lines.push("\nDISPATCHED");
6437
+ for (const issue of toDispatch) {
6438
+ const triageResult = forceCloud ? { strategy: "cloud-agent", score: 6, confidence: 1, reasons: ["forceCloud: user requested cloud dispatch"], fallback: "local-ai" } : results.get(issue.id);
6439
+ try {
6440
+ const job = await client.dispatch(issue, triageResult, repoUrl, branch);
6441
+ store.jobs[issue.id] = {
6442
+ issueId: issue.id,
6443
+ jobId: job.jobId,
6444
+ status: "dispatched",
6445
+ score: triageResult.score,
6446
+ strategy: "cloud-agent",
6447
+ dispatchedAt: job.dispatchedAt,
6448
+ artifactUrls: [],
6449
+ prUrl: null
6450
+ };
6451
+ lines.push(` ${issue.id} ${shortPath2(issue.file)}:${issue.line ?? "?"} job:${job.jobId} (score +${triageResult.score})`);
6452
+ } catch (err) {
6453
+ lines.push(` ${issue.id} FAILED: ${err.message}`);
6454
+ }
6455
+ }
6456
+ await this.saveJobs(workDir, store);
6457
+ lines.push("");
6458
+ lines.push("Cloud agents are running in isolated VMs. Check back with:");
6459
+ lines.push("trie_cloud_fix action:status");
6460
+ return this.text(lines.join("\n"));
6461
+ }
6462
+ // --------------------------------------------------------------------------
6463
+ // status
6464
+ // --------------------------------------------------------------------------
6465
+ async status() {
6466
+ const workDir = getWorkingDirectory(void 0, true);
6467
+ const apiKey = await this.resolveApiKey(workDir);
6468
+ if (!apiKey) return this.setupGuard();
6469
+ const store = await this.loadJobs(workDir);
6470
+ const entries = Object.values(store.jobs);
6471
+ if (entries.length === 0) {
6472
+ return this.text("No cloud jobs. Dispatch with: trie_cloud_fix action:dispatch");
6473
+ }
6474
+ const client = new CursorCloudAgentClient(apiKey);
6475
+ const LINE = "\u2500".repeat(68);
6476
+ const lines = ["JOB STATUS", LINE];
6477
+ lines.push(padEnd2("Issue", 32) + padEnd2("Status", 12) + padEnd2("PR", 28) + "Age");
6478
+ for (const job of entries) {
6479
+ if (job.status === "dispatched" || job.status === "running") {
6480
+ try {
6481
+ const poll = await client.poll(job.jobId);
6482
+ if (poll.status === "completed" && poll.prUrl) {
6483
+ job.status = "verified";
6484
+ job.prUrl = poll.prUrl;
6485
+ } else if (poll.status === "running") {
6486
+ job.status = "running";
6487
+ } else if (poll.status === "failed") {
6488
+ job.status = "failed";
6489
+ }
6490
+ if (poll.artifactUrls.length) {
6491
+ job.artifactUrls = poll.artifactUrls;
6492
+ }
6493
+ } catch {
6494
+ }
6495
+ }
6496
+ const age = formatAge(job.dispatchedAt);
6497
+ const pr = job.prUrl ?? "\u2014";
6498
+ lines.push(padEnd2(job.issueId, 32) + padEnd2(job.status, 12) + padEnd2(pr, 28) + age);
6499
+ }
6500
+ lines.push(LINE);
6501
+ await this.saveJobs(workDir, store);
6502
+ return this.text(lines.join("\n"));
6503
+ }
6504
+ // --------------------------------------------------------------------------
6505
+ // artifacts
6506
+ // --------------------------------------------------------------------------
6507
+ async artifacts(args) {
6508
+ const workDir = getWorkingDirectory(void 0, true);
6509
+ const apiKey = await this.resolveApiKey(workDir);
6510
+ if (!apiKey) return this.setupGuard();
6511
+ const store = await this.loadJobs(workDir);
6512
+ const jobId = args?.jobId;
6513
+ const targets = jobId ? Object.values(store.jobs).filter((j) => j.jobId === jobId) : Object.values(store.jobs).filter((j) => j.status === "verified");
6514
+ if (targets.length === 0) {
6515
+ return this.text(jobId ? `No job found with id ${jobId}` : "No completed jobs with artifacts.");
6516
+ }
6517
+ const client = new CursorCloudAgentClient(apiKey);
6518
+ const lines = [];
6519
+ for (const job of targets) {
6520
+ try {
6521
+ const artifacts = await client.getArtifacts(job.jobId);
6522
+ job.artifactUrls = artifacts;
6523
+ } catch {
6524
+ }
6525
+ lines.push(`Issue: ${job.issueId}`);
6526
+ lines.push(`PR: ${job.prUrl ?? "N/A"}`);
6527
+ for (const url of job.artifactUrls) {
6528
+ const ext = url.split(".").pop();
6529
+ const label = ext === "mp4" || ext === "webm" ? "Video" : "Screenshot";
6530
+ lines.push(`${label}: ${url}`);
6531
+ }
6532
+ if (job.status === "verified") {
6533
+ lines.push("Agent verified: all tests pass with this fix applied.");
6534
+ }
6535
+ lines.push("");
6536
+ }
6537
+ await this.saveJobs(workDir, store);
6538
+ return this.text(lines.join("\n"));
6539
+ }
6540
+ // --------------------------------------------------------------------------
6541
+ // cancel
6542
+ // --------------------------------------------------------------------------
6543
+ async cancel(args) {
6544
+ const workDir = getWorkingDirectory(void 0, true);
6545
+ const apiKey = await this.resolveApiKey(workDir);
6546
+ if (!apiKey) return this.setupGuard();
6547
+ const jobId = args?.jobId;
6548
+ if (!jobId) {
6549
+ return this.text('Provide jobId to cancel. Example: trie_cloud_fix action:cancel jobId:"cursor-task-xyz"');
6550
+ }
6551
+ const store = await this.loadJobs(workDir);
6552
+ const entry = Object.values(store.jobs).find((j) => j.jobId === jobId);
6553
+ if (!entry) {
6554
+ return this.text(`Job ${jobId} not found in cloud-jobs.json.`);
6555
+ }
6556
+ const client = new CursorCloudAgentClient(apiKey);
6557
+ try {
6558
+ await client.cancelJob(jobId);
6559
+ } catch (err) {
6560
+ return this.text(`Cancel failed: ${err.message}`);
6561
+ }
6562
+ delete store.jobs[entry.issueId];
6563
+ await this.saveJobs(workDir, store);
6564
+ return this.text(`Job ${jobId} cancelled and removed.`);
6565
+ }
6566
+ // --------------------------------------------------------------------------
6567
+ // Helpers
6568
+ // --------------------------------------------------------------------------
6569
+ async resolveApiKey(workDir) {
6570
+ if (process.env.CURSOR_API_KEY) return process.env.CURSOR_API_KEY;
6571
+ const fromEnv = this.loadCursorKeyFromEnv(workDir);
6572
+ if (fromEnv) return fromEnv;
6573
+ const config = await loadAutonomyConfig(workDir);
6574
+ return config.cursorApiKey ?? null;
6575
+ }
6576
+ loadCursorKeyFromEnv(workDir) {
6577
+ for (const envFile of [".env", ".env.local", ".env.production"]) {
6578
+ const envPath = join2(workDir, envFile);
6579
+ if (!existsSync4(envPath)) continue;
6580
+ try {
6581
+ const content = readFileSync(envPath, "utf-8");
6582
+ const match = content.match(/^\s*CURSOR_API_KEY\s*=\s*(.+)$/m);
6583
+ if (match?.[1]) {
6584
+ const key = match[1].trim().replace(/^["']|["']$/g, "");
6585
+ if (key.length > 10) return key;
6586
+ }
6587
+ } catch {
6588
+ }
6589
+ }
6590
+ return null;
6591
+ }
6592
+ setupGuard() {
6593
+ return this.text(
6594
+ 'Cloud Agent dispatch requires a Cursor API key.\n\nGet your key at: cursor.com/settings \u2192 API Keys\n\nThen either:\n \u2022 trie_cloud_fix action:configure apiKey:"key-..."\n \u2022 Or add to Config \u2192 API Keys \u2192 Cursor (press s in watch mode)\n \u2022 Or set: export CURSOR_API_KEY="key-..."\n \u2022 Or add CURSOR_API_KEY=... to your .env file'
6595
+ );
6596
+ }
6597
+ async resolveIssues(issueIds) {
6598
+ let pending = getPendingFixes();
6599
+ if (pending.length === 0) {
6600
+ try {
6601
+ console.log("Loading issues from memory...");
6602
+ const { getRecentIssues } = await import("./issue-store-LZWZIGM7.js");
6603
+ const recentIssues = await getRecentIssues({ limit: 50, includeResolved: false });
6604
+ console.log(`Found ${recentIssues.length} recent issues in memory`);
6605
+ const memoryIssues = recentIssues.map((storedIssue) => ({
6606
+ id: storedIssue.id,
6607
+ severity: storedIssue.severity || "moderate",
6608
+ issue: storedIssue.issue,
6609
+ fix: storedIssue.fix,
6610
+ file: storedIssue.file,
6611
+ line: storedIssue.line,
6612
+ confidence: 0.8,
6613
+ // Default confidence for memory issues
6614
+ autoFixable: true,
6615
+ // Memory issues are generally auto-fixable
6616
+ agent: storedIssue.agent,
6617
+ category: storedIssue.category
6618
+ }));
6619
+ console.log(`Converted ${memoryIssues.length} memory issues for cloud dispatch`);
6620
+ if (issueIds && issueIds.length > 0) {
6621
+ return memoryIssues.filter((i) => issueIds.includes(i.id));
6622
+ }
6623
+ return memoryIssues;
6624
+ } catch (error) {
6625
+ console.warn("Failed to load issues from memory:", error);
6626
+ console.warn("Error details:", error);
6627
+ }
6628
+ }
6629
+ const issues = pending.map((p) => ({
6630
+ id: p.id,
6631
+ severity: p.severity ?? "moderate",
6632
+ effort: p.effort,
6633
+ issue: p.issue,
6634
+ fix: p.suggestedFix,
6635
+ file: p.file,
6636
+ line: p.line,
6637
+ confidence: p.confidence,
6638
+ autoFixable: p.autoFixable ?? false,
6639
+ agent: "trie_scan",
6640
+ cwe: p.cwe,
6641
+ owasp: p.owasp,
6642
+ category: p.category
6643
+ }));
6644
+ if (issueIds && issueIds.length > 0) {
6645
+ return issues.filter((i) => issueIds.includes(i.id));
6646
+ }
6647
+ return issues;
6648
+ }
6649
+ getRepoUrl(workDir) {
6650
+ try {
6651
+ return execSync("git remote get-url origin", { cwd: workDir, encoding: "utf-8" }).trim();
6652
+ } catch {
6653
+ return "unknown";
6654
+ }
6655
+ }
6656
+ getBranch(workDir) {
6657
+ try {
6658
+ return execSync("git rev-parse --abbrev-ref HEAD", { cwd: workDir, encoding: "utf-8" }).trim();
6659
+ } catch {
6660
+ return "main";
6661
+ }
6662
+ }
6663
+ async loadJobs(workDir) {
6664
+ const path2 = join2(getTrieDirectory(workDir), "cloud-jobs.json");
6665
+ try {
6666
+ if (existsSync4(path2)) {
6667
+ const raw = await readFile3(path2, "utf-8");
6668
+ return JSON.parse(raw);
6669
+ }
6670
+ } catch {
6671
+ }
6672
+ return { jobs: {} };
6673
+ }
6674
+ async saveJobs(workDir, store) {
6675
+ const trieDir = getTrieDirectory(workDir);
6676
+ if (!existsSync4(trieDir)) await mkdir(trieDir, { recursive: true });
6677
+ const path2 = join2(trieDir, "cloud-jobs.json");
6678
+ await writeFile(path2, JSON.stringify(store, null, 2));
6679
+ }
6680
+ text(msg) {
6681
+ return { content: [{ type: "text", text: msg }] };
6682
+ }
6683
+ };
6684
+ function formatAge(isoDate) {
6685
+ const ms = Date.now() - new Date(isoDate).getTime();
6686
+ const mins = Math.floor(ms / 6e4);
6687
+ if (mins < 60) return `${mins}m`;
6688
+ const hrs = Math.floor(mins / 60);
6689
+ if (hrs < 24) return `${hrs}h ${mins % 60}m`;
6690
+ return `${Math.floor(hrs / 24)}d`;
6691
+ }
6692
+ function shortPath2(file) {
6693
+ const parts = file.split("/");
6694
+ return parts.length > 2 ? parts.slice(-2).join("/") : file;
6695
+ }
6696
+ function padEnd2(str, len) {
6697
+ if (str.length >= len) return str.slice(0, len);
6698
+ return str + " ".repeat(len - str.length);
6699
+ }
6700
+
5516
6701
  // src/tools/query-tools.ts
5517
6702
  var TrieGetGovernanceTool = class {
5518
6703
  async execute(input) {
@@ -5946,7 +7131,7 @@ var TriePipelineTool = class {
5946
7131
  );
5947
7132
  const prInfo = linkedPR ? `PR #${linkedPR.data.prNumber} (CI: ${linkedPR.data.ciStatus})` : "No PR yet";
5948
7133
  lines.push(
5949
- ` ${padEnd(t.data.ticketId, 10)}${padEnd(truncate2(t.data.title, 35), 37)}${padEnd(t.data.priority, 9)}${prInfo}`
7134
+ ` ${padEnd3(t.data.ticketId, 10)}${padEnd3(truncate2(t.data.title, 35), 37)}${padEnd3(t.data.priority, 9)}${prInfo}`
5950
7135
  );
5951
7136
  }
5952
7137
  lines.push("");
@@ -5963,7 +7148,7 @@ var TriePipelineTool = class {
5963
7148
  const stateTag = pr.data.isDraft ? "draft" : pr.data.state;
5964
7149
  const ticketRef = pr.data.linkedTicketIds.length > 0 ? ` (${pr.data.linkedTicketIds.join(", ")})` : "";
5965
7150
  lines.push(
5966
- ` #${padEnd(String(pr.data.prNumber), 5)}${padEnd(truncate2(pr.data.title, 30) + ticketRef, 42)}${padEnd(stateTag, 10)}CI: ${padEnd(pr.data.ciStatus, 10)}Review: ${pr.data.reviewStatus}`
7151
+ ` #${padEnd3(String(pr.data.prNumber), 5)}${padEnd3(truncate2(pr.data.title, 30) + ticketRef, 42)}${padEnd3(stateTag, 10)}CI: ${padEnd3(pr.data.ciStatus, 10)}Review: ${pr.data.reviewStatus}`
5967
7152
  );
5968
7153
  }
5969
7154
  lines.push("");
@@ -5977,7 +7162,7 @@ var TriePipelineTool = class {
5977
7162
  const labels = issue.data.labels.length > 0 ? `[${issue.data.labels.join(", ")}]` : "";
5978
7163
  const linked = issue.data.linkedTicketIds.length > 0 ? `linked: ${issue.data.linkedTicketIds.join(", ")}` : "no ticket";
5979
7164
  lines.push(
5980
- ` #${padEnd(String(issue.data.issueNumber), 5)}${padEnd(truncate2(issue.data.title, 35), 37)}${padEnd(labels, 18)}${linked}`
7165
+ ` #${padEnd3(String(issue.data.issueNumber), 5)}${padEnd3(truncate2(issue.data.title, 35), 37)}${padEnd3(labels, 18)}${linked}`
5981
7166
  );
5982
7167
  }
5983
7168
  if (ghIssues.length > 10) {
@@ -5990,8 +7175,8 @@ var TriePipelineTool = class {
5990
7175
  if (uncoveredIssues.length > 0) {
5991
7176
  lines.push(`COVERAGE GAPS (${uncoveredIssues.length} Trie issue${uncoveredIssues.length !== 1 ? "s" : ""} with no ticket or PR)`);
5992
7177
  for (const issue of uncoveredIssues.slice(0, 10)) {
5993
- const loc = `${shortPath(issue.data.file)}:${issue.data.line ?? "?"}`;
5994
- lines.push(` ${padEnd(loc, 32)}${padEnd(issue.data.severity, 10)}${truncate2(issue.data.issueText, 40)}`);
7178
+ const loc = `${shortPath3(issue.data.file)}:${issue.data.line ?? "?"}`;
7179
+ lines.push(` ${padEnd3(loc, 32)}${padEnd3(issue.data.severity, 10)}${truncate2(issue.data.issueText, 40)}`);
5995
7180
  }
5996
7181
  if (uncoveredIssues.length > 10) {
5997
7182
  lines.push(` ... and ${uncoveredIssues.length - 10} more`);
@@ -6117,8 +7302,8 @@ var TriePipelineTool = class {
6117
7302
  if (group.items.length === 0) continue;
6118
7303
  lines.push(`${group.label} (${group.items.length}):`);
6119
7304
  for (const issue of group.items.slice(0, 10)) {
6120
- const loc = `${shortPath(issue.data.file)}:${issue.data.line ?? "?"}`;
6121
- lines.push(` ${padEnd(loc, 32)}${truncate2(issue.data.issueText, 45)}`);
7305
+ const loc = `${shortPath3(issue.data.file)}:${issue.data.line ?? "?"}`;
7306
+ lines.push(` ${padEnd3(loc, 32)}${truncate2(issue.data.issueText, 45)}`);
6122
7307
  }
6123
7308
  if (group.items.length > 10) {
6124
7309
  lines.push(` ... and ${group.items.length - 10} more`);
@@ -6291,11 +7476,11 @@ var TriePipelineTool = class {
6291
7476
  return parts.join("\n");
6292
7477
  }
6293
7478
  };
6294
- function shortPath(file) {
7479
+ function shortPath3(file) {
6295
7480
  const parts = file.split("/");
6296
7481
  return parts.length > 2 ? parts.slice(-2).join("/") : file;
6297
7482
  }
6298
- function padEnd(str, len) {
7483
+ function padEnd3(str, len) {
6299
7484
  if (str.length >= len) return str.slice(0, len);
6300
7485
  return str + " ".repeat(len - str.length);
6301
7486
  }
@@ -6305,7 +7490,7 @@ function truncate2(str, maxLen) {
6305
7490
  }
6306
7491
 
6307
7492
  // src/tools/github-branches.ts
6308
- import { execSync } from "child_process";
7493
+ import { execSync as execSync2 } from "child_process";
6309
7494
  var GitHubBranchesTool = class {
6310
7495
  async execute(input) {
6311
7496
  const workDir = input.directory || getWorkingDirectory(void 0, true);
@@ -6375,7 +7560,7 @@ Check token validity and repo access. Token needs \`repo\` or \`public_repo\` sc
6375
7560
  }
6376
7561
  getRepoInfo(projectPath) {
6377
7562
  try {
6378
- const url = execSync("git remote get-url origin", {
7563
+ const url = execSync2("git remote get-url origin", {
6379
7564
  cwd: projectPath,
6380
7565
  encoding: "utf-8",
6381
7566
  stdio: ["pipe", "pipe", "pipe"]
@@ -6719,6 +7904,44 @@ var CHAT_TOOLS = [
6719
7904
  },
6720
7905
  required: ["hypothesisId"]
6721
7906
  }
7907
+ },
7908
+ {
7909
+ name: "trie_cloud_fix",
7910
+ description: 'Dispatch to Cursor Cloud agents. Default: triage routes issues. When user explicitly says they want cloud (e.g. "fix with cloud agent") \u2014 pass forceCloud:true to honor their choice, do not reject. For ad-hoc (no scan issues): pass file, issue, fix from prior trie_propose_fix. Actions: configure, dispatch, status, artifacts, cancel.',
7911
+ input_schema: {
7912
+ type: "object",
7913
+ properties: {
7914
+ action: {
7915
+ type: "string",
7916
+ enum: ["configure", "dispatch", "status", "artifacts", "cancel"],
7917
+ description: "Action: configure (save apiKey), dispatch (send issues), status (check jobs), artifacts (get results), cancel (abort job)"
7918
+ },
7919
+ apiKey: {
7920
+ type: "string",
7921
+ description: "Cursor API key (only for action:configure). Get at cursor.com/settings \u2192 API Keys."
7922
+ },
7923
+ issueIds: {
7924
+ type: "array",
7925
+ items: { type: "string" },
7926
+ description: "Issue IDs to dispatch (for action:dispatch). Omit to dispatch all cloud-eligible issues."
7927
+ },
7928
+ forceCloud: {
7929
+ type: "boolean",
7930
+ description: "When true, bypass triage and dispatch ALL issues to cloud. Use when user explicitly requests cloud fix."
7931
+ },
7932
+ file: {
7933
+ type: "string",
7934
+ description: "For ad-hoc dispatch: file path (use with issue and fix when no scan issues)"
7935
+ },
7936
+ issue: { type: "string", description: "For ad-hoc dispatch: issue description" },
7937
+ fix: { type: "string", description: "For ad-hoc dispatch: suggested fix" },
7938
+ line: { type: "number", description: "For ad-hoc dispatch: line number" },
7939
+ jobId: {
7940
+ type: "string",
7941
+ description: "Job ID for action:artifacts or action:cancel"
7942
+ }
7943
+ }
7944
+ }
6722
7945
  }
6723
7946
  ];
6724
7947
  async function executeTool(name, input, onProgress) {
@@ -7076,6 +8299,24 @@ ${truncated}`;
7076
8299
  return `Scan failed: ${error.message}`;
7077
8300
  }
7078
8301
  }
8302
+ case "trie_cloud_fix": {
8303
+ const cloudFixTool = new TrieCloudFixTool();
8304
+ const cloudFixArgs = {
8305
+ action: input.action || "status"
8306
+ };
8307
+ if (input.apiKey) cloudFixArgs.apiKey = String(input.apiKey);
8308
+ if (Array.isArray(input.issueIds)) cloudFixArgs.issueIds = input.issueIds;
8309
+ if (input.jobId) cloudFixArgs.jobId = String(input.jobId);
8310
+ if (input.forceCloud === true) cloudFixArgs.forceCloud = true;
8311
+ if (input.file) cloudFixArgs.file = String(input.file);
8312
+ if (input.issue) cloudFixArgs.issue = String(input.issue);
8313
+ if (input.fix) cloudFixArgs.fix = String(input.fix);
8314
+ if (typeof input.line === "number") cloudFixArgs.line = input.line;
8315
+ if (input.severity) cloudFixArgs.severity = String(input.severity);
8316
+ if (input.effort) cloudFixArgs.effort = String(input.effort);
8317
+ const result = await cloudFixTool.execute(cloudFixArgs);
8318
+ return textFromResult(result);
8319
+ }
7079
8320
  case "trie_test_hypothesis": {
7080
8321
  const hypothesisId = String(input.hypothesisId || "").trim();
7081
8322
  if (!hypothesisId) {
@@ -7257,10 +8498,17 @@ async function buildContext(workDir, dashboardState) {
7257
8498
  return parts.length > 0 ? parts.join("\n\n") : "No context data available yet.";
7258
8499
  }
7259
8500
  function chatHistoryToMessages(history) {
7260
- return history.map((m) => ({
7261
- role: m.role,
7262
- content: m.content
7263
- }));
8501
+ return history.map((m) => {
8502
+ let content = m.content;
8503
+ const fixes = m.pendingFixes ?? (m.pendingFix ? [m.pendingFix] : []);
8504
+ if (fixes.length > 0) {
8505
+ const fixCtx = fixes.map((f) => `file: ${f.file}, goal: ${f.goal}, violation: ${f.violation}${f.suggestedFix ? `, suggestedFix: ${f.suggestedFix}` : ""}`).join("; ");
8506
+ content = content ? `${content}
8507
+
8508
+ [Proposed fix context: ${fixCtx}]` : `[Proposed fix context: ${fixCtx}]`;
8509
+ }
8510
+ return { role: m.role, content };
8511
+ });
7264
8512
  }
7265
8513
  var SYSTEM_PROMPT = `You are Trie, a code assistant embedded in a terminal TUI.
7266
8514
 
@@ -7313,12 +8561,21 @@ var SYSTEM_PROMPT = `You are Trie, a code assistant embedded in a terminal TUI.
7313
8561
  5. AFTER the tool call completes, the system will ask for user confirmation - do NOT add your own confirmation message
7314
8562
  6. When the user says "yes", "yes to all", or "no", the system handles spawning Claude Code automatically
7315
8563
 
8564
+ **When user asks to dispatch or fix with Cursor Cloud agent:**
8565
+ - Default: Call trie_cloud_fix action:dispatch \u2014 triage runs and routes issues as usual.
8566
+ - If user EXPLICITLY says they want cloud (e.g. "fix it with cloud agent", "I want cloud", "use cursor cloud"): add forceCloud:true so triage does not override their choice. Do NOT suggest local fix instead \u2014 honor their request.
8567
+ - If there are no scan issues but the conversation has a recent trie_propose_fix: use ad-hoc dispatch with file, issue, fix from that context.
8568
+ - If the tool returns "Cloud Agent dispatch requires a Cursor API key", tell the user to configure it.
8569
+ - For status: trie_cloud_fix action:status
8570
+
7316
8571
  Examples:
7317
8572
  - User: "do we have emojis?" \u2192 Check nudges first. If none or unclear: Call trie_scan_for_goal_violations to scan the codebase.
7318
8573
  - User: "run a full scan for emojis" \u2192 Call trie_scan_for_goal_violations directly.
7319
8574
  - User: "fix the emoji violations" \u2192 Find ALL emoji violations in nudges, call trie_propose_fixes_batch ONCE with all fixes, then STOP
7320
8575
  - User responds "yes to all" after proposal \u2192 Just say "Spawning Claude Code to fix all files..." The system handles it.
7321
8576
  - User: "search for TODO comments" \u2192 If there's a goal about TODOs, use trie_scan_for_goal_violations. Otherwise explain no such goal exists.
8577
+ - User: "fix it with cloud agent" (explicit) \u2192 Call trie_cloud_fix action:dispatch forceCloud:true \u2014 honor their choice, don't suggest local.
8578
+ - User: "dispatch to cloud" (generic) \u2192 Call trie_cloud_fix action:dispatch \u2014 triage as usual.
7322
8579
 
7323
8580
  Answer concisely. Reference specific files, decisions, and patterns when relevant.`;
7324
8581
  function ChatView() {
@@ -7502,14 +8759,14 @@ ${contextBlock}`;
7502
8759
  content: result.content
7503
8760
  };
7504
8761
  const pendingFixRegex = /\[PENDING_FIX:(.+?)\]/g;
7505
- const pendingFixes = [];
8762
+ const pendingFixes2 = [];
7506
8763
  let match;
7507
8764
  while ((match = pendingFixRegex.exec(result.content)) !== null) {
7508
8765
  try {
7509
8766
  const jsonStr = match[1];
7510
8767
  if (jsonStr) {
7511
8768
  const fixData = JSON.parse(jsonStr);
7512
- pendingFixes.push(fixData);
8769
+ pendingFixes2.push(fixData);
7513
8770
  }
7514
8771
  } catch {
7515
8772
  }
@@ -7523,8 +8780,8 @@ ${contextBlock}`;
7523
8780
  const jsonStr = toolMatch[1];
7524
8781
  if (jsonStr) {
7525
8782
  const fixData = JSON.parse(jsonStr);
7526
- if (!pendingFixes.some((f) => f.file === fixData.file)) {
7527
- pendingFixes.push(fixData);
8783
+ if (!pendingFixes2.some((f) => f.file === fixData.file)) {
8784
+ pendingFixes2.push(fixData);
7528
8785
  }
7529
8786
  }
7530
8787
  } catch {
@@ -7532,13 +8789,13 @@ ${contextBlock}`;
7532
8789
  }
7533
8790
  }
7534
8791
  }
7535
- if (pendingFixes.length > 0) {
8792
+ if (pendingFixes2.length > 0) {
7536
8793
  action.content = result.content.replace(/\[PENDING_FIX:.+?\]/g, "").trim();
7537
- const firstFix = pendingFixes[0];
7538
- if (pendingFixes.length === 1 && firstFix) {
8794
+ const firstFix = pendingFixes2[0];
8795
+ if (pendingFixes2.length === 1 && firstFix) {
7539
8796
  action.pendingFix = firstFix;
7540
8797
  } else {
7541
- action.pendingFixes = pendingFixes;
8798
+ action.pendingFixes = pendingFixes2;
7542
8799
  }
7543
8800
  }
7544
8801
  if (result.toolCalls && result.toolCalls.length > 0) {
@@ -7986,8 +9243,8 @@ async function applyGoalFix(fix, dispatch) {
7986
9243
  return;
7987
9244
  }
7988
9245
  const projectPath = getWorkingDirectory(void 0, true);
7989
- const fullPath = join2(projectPath, fix.file);
7990
- const content = await readFile2(fullPath, "utf-8");
9246
+ const fullPath = join3(projectPath, fix.file);
9247
+ const content = await readFile4(fullPath, "utf-8");
7991
9248
  const result = await runAIAnalysis({
7992
9249
  systemPrompt: `You are a precise code fixer. The user has a quality goal: "${fix.goalDescription}".
7993
9250
  A violation was detected: "${fix.description}"
@@ -8013,7 +9270,7 @@ ${content}
8013
9270
  if (fixedContent.startsWith("```")) {
8014
9271
  fixedContent = fixedContent.replace(/^```\w*\n?/, "").replace(/\n?```$/, "");
8015
9272
  }
8016
- await writeFile(fullPath, fixedContent, "utf-8");
9273
+ await writeFile2(fullPath, fixedContent, "utf-8");
8017
9274
  const { recordGoalViolationFixed, getActiveGoals } = await import("./goal-validator-DA3JQ6JN.js");
8018
9275
  const goals = await getActiveGoals(projectPath);
8019
9276
  const matchedGoal = goals.find((g) => g.description === fix.goalDescription);
@@ -8037,11 +9294,11 @@ function DashboardApp({ onReady }) {
8037
9294
  dispatchRef.current = dispatch;
8038
9295
  const stateRef = useRef4(state);
8039
9296
  stateRef.current = state;
8040
- const configPath = join2(getTrieDirectory(getWorkingDirectory(void 0, true)), "agent.json");
9297
+ const configPath = join3(getTrieDirectory(getWorkingDirectory(void 0, true)), "agent.json");
8041
9298
  const loadConfig2 = useCallback7(async () => {
8042
- if (!existsSync3(configPath)) return;
9299
+ if (!existsSync5(configPath)) return;
8043
9300
  try {
8044
- const raw = await readFile2(configPath, "utf-8");
9301
+ const raw = await readFile4(configPath, "utf-8");
8045
9302
  const parsed = JSON.parse(raw);
8046
9303
  dispatchRef.current({ type: "SET_AGENT_CONFIG", config: parsed });
8047
9304
  } catch {
@@ -8050,8 +9307,8 @@ function DashboardApp({ onReady }) {
8050
9307
  const persistConfig = useCallback7(async () => {
8051
9308
  try {
8052
9309
  const workDir = getWorkingDirectory(void 0, true);
8053
- await mkdir(getTrieDirectory(workDir), { recursive: true });
8054
- await writeFile(configPath, JSON.stringify(stateRef.current.agentConfig, null, 2), "utf-8");
9310
+ await mkdir2(getTrieDirectory(workDir), { recursive: true });
9311
+ await writeFile2(configPath, JSON.stringify(stateRef.current.agentConfig, null, 2), "utf-8");
8055
9312
  const { saveAutonomyConfig: saveAutonomyConfig2, loadAutonomyConfig: loadAutonomyConfig2 } = await import("./autonomy-config-ZCOSTMPD.js");
8056
9313
  const autonomy = await loadAutonomyConfig2(workDir);
8057
9314
  autonomy.aiWatcher = stateRef.current.agentConfig.aiWatcher;
@@ -8382,6 +9639,8 @@ export {
8382
9639
  TrieScanTool,
8383
9640
  getPrompt,
8384
9641
  getSystemPrompt,
9642
+ TrieFixTool,
9643
+ TrieCloudFixTool,
8385
9644
  TrieExplainTool,
8386
9645
  StreamingManager,
8387
9646
  ExtractionPipeline,
@@ -8399,4 +9658,4 @@ export {
8399
9658
  GitHubBranchesTool,
8400
9659
  InteractiveDashboard
8401
9660
  };
8402
- //# sourceMappingURL=chunk-Y2LYDCJD.js.map
9661
+ //# sourceMappingURL=chunk-5FQTNPBP.js.map