@triedotdev/mcp 1.0.168 → 1.0.170
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -500
- package/dist/chunk-2YXOBNKW.js +619 -0
- package/dist/chunk-2YXOBNKW.js.map +1 -0
- package/dist/chunk-QR64Y5TI.js +363 -0
- package/dist/chunk-QR64Y5TI.js.map +1 -0
- package/dist/cli/main.d.ts +0 -15
- package/dist/cli/main.js +356 -3100
- package/dist/cli/main.js.map +1 -1
- package/dist/index.js +2 -36
- package/dist/index.js.map +1 -1
- package/dist/server/mcp-server.js +2 -36
- package/package.json +8 -31
- package/dist/autonomy-config-FSERX3O3.js +0 -30
- package/dist/autonomy-config-FSERX3O3.js.map +0 -1
- package/dist/chat-store-JNGNTDSN.js +0 -15
- package/dist/chat-store-JNGNTDSN.js.map +0 -1
- package/dist/chunk-2HF65EHQ.js +0 -311
- package/dist/chunk-2HF65EHQ.js.map +0 -1
- package/dist/chunk-43X6JBEM.js +0 -36
- package/dist/chunk-43X6JBEM.js.map +0 -1
- package/dist/chunk-4MXH2ZPT.js +0 -1827
- package/dist/chunk-4MXH2ZPT.js.map +0 -1
- package/dist/chunk-575YT2SD.js +0 -737
- package/dist/chunk-575YT2SD.js.map +0 -1
- package/dist/chunk-5BRRRTN6.js +0 -354
- package/dist/chunk-5BRRRTN6.js.map +0 -1
- package/dist/chunk-6NLHFIYA.js +0 -344
- package/dist/chunk-6NLHFIYA.js.map +0 -1
- package/dist/chunk-7WITSO22.js +0 -824
- package/dist/chunk-7WITSO22.js.map +0 -1
- package/dist/chunk-DGUM43GV.js +0 -11
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-EFWVF6TI.js +0 -267
- package/dist/chunk-EFWVF6TI.js.map +0 -1
- package/dist/chunk-F6WFNUAY.js +0 -216
- package/dist/chunk-F6WFNUAY.js.map +0 -1
- package/dist/chunk-FQ45QP5A.js +0 -361
- package/dist/chunk-FQ45QP5A.js.map +0 -1
- package/dist/chunk-G2TGF6TR.js +0 -573
- package/dist/chunk-G2TGF6TR.js.map +0 -1
- package/dist/chunk-GTKYBOXL.js +0 -700
- package/dist/chunk-GTKYBOXL.js.map +0 -1
- package/dist/chunk-HVCDY3AK.js +0 -850
- package/dist/chunk-HVCDY3AK.js.map +0 -1
- package/dist/chunk-JVMBCWKS.js +0 -348
- package/dist/chunk-JVMBCWKS.js.map +0 -1
- package/dist/chunk-KDHN2ZQE.js +0 -313
- package/dist/chunk-KDHN2ZQE.js.map +0 -1
- package/dist/chunk-LQIMKE3P.js +0 -12524
- package/dist/chunk-LQIMKE3P.js.map +0 -1
- package/dist/chunk-ME2OERF5.js +0 -345
- package/dist/chunk-ME2OERF5.js.map +0 -1
- package/dist/chunk-MRHKX5M5.js +0 -662
- package/dist/chunk-MRHKX5M5.js.map +0 -1
- package/dist/chunk-OBQ74FOU.js +0 -27
- package/dist/chunk-OBQ74FOU.js.map +0 -1
- package/dist/chunk-OMR4YCBS.js +0 -987
- package/dist/chunk-OMR4YCBS.js.map +0 -1
- package/dist/chunk-Q5EKA5YA.js +0 -254
- package/dist/chunk-Q5EKA5YA.js.map +0 -1
- package/dist/chunk-Q63FFI6D.js +0 -132
- package/dist/chunk-Q63FFI6D.js.map +0 -1
- package/dist/chunk-SY6KQG44.js +0 -983
- package/dist/chunk-SY6KQG44.js.map +0 -1
- package/dist/chunk-T63OHG4Q.js +0 -440
- package/dist/chunk-T63OHG4Q.js.map +0 -1
- package/dist/chunk-TN5WEKWI.js +0 -173
- package/dist/chunk-TN5WEKWI.js.map +0 -1
- package/dist/chunk-VUL52BQL.js +0 -402
- package/dist/chunk-VUL52BQL.js.map +0 -1
- package/dist/chunk-VVITXIHN.js +0 -189
- package/dist/chunk-VVITXIHN.js.map +0 -1
- package/dist/chunk-WCN7S3EI.js +0 -14
- package/dist/chunk-WCN7S3EI.js.map +0 -1
- package/dist/chunk-XE6KQRKZ.js +0 -816
- package/dist/chunk-XE6KQRKZ.js.map +0 -1
- package/dist/chunk-XPZZFPBZ.js +0 -491
- package/dist/chunk-XPZZFPBZ.js.map +0 -1
- package/dist/chunk-XTFWT2XM.js +0 -727
- package/dist/chunk-XTFWT2XM.js.map +0 -1
- package/dist/chunk-YDHUCDHM.js +0 -4011
- package/dist/chunk-YDHUCDHM.js.map +0 -1
- package/dist/chunk-YZ6Y2H3P.js +0 -1289
- package/dist/chunk-YZ6Y2H3P.js.map +0 -1
- package/dist/chunk-ZJF5FTBX.js +0 -1396
- package/dist/chunk-ZJF5FTBX.js.map +0 -1
- package/dist/chunk-ZV2K6M7T.js +0 -74
- package/dist/chunk-ZV2K6M7T.js.map +0 -1
- package/dist/cli/create-agent.d.ts +0 -1
- package/dist/cli/create-agent.js +0 -1050
- package/dist/cli/create-agent.js.map +0 -1
- package/dist/cli/yolo-daemon.d.ts +0 -1
- package/dist/cli/yolo-daemon.js +0 -423
- package/dist/cli/yolo-daemon.js.map +0 -1
- package/dist/client-NJPZE5JT.js +0 -28
- package/dist/client-NJPZE5JT.js.map +0 -1
- package/dist/codebase-index-VAPF32XX.js +0 -12
- package/dist/codebase-index-VAPF32XX.js.map +0 -1
- package/dist/fast-analyzer-XXYMOXRK.js +0 -216
- package/dist/fast-analyzer-XXYMOXRK.js.map +0 -1
- package/dist/git-EO5SRFMN.js +0 -28
- package/dist/git-EO5SRFMN.js.map +0 -1
- package/dist/github-ingester-ZOKK6GRS.js +0 -11
- package/dist/github-ingester-ZOKK6GRS.js.map +0 -1
- package/dist/goal-manager-YOB7VWK7.js +0 -25
- package/dist/goal-manager-YOB7VWK7.js.map +0 -1
- package/dist/goal-validator-ULKIBDPX.js +0 -24
- package/dist/goal-validator-ULKIBDPX.js.map +0 -1
- package/dist/graph-B3NA4S7I.js +0 -10
- package/dist/graph-B3NA4S7I.js.map +0 -1
- package/dist/hypothesis-7BFFT5JY.js +0 -23
- package/dist/hypothesis-7BFFT5JY.js.map +0 -1
- package/dist/incident-index-EFNUSGWL.js +0 -11
- package/dist/incident-index-EFNUSGWL.js.map +0 -1
- package/dist/insight-store-EC4PLSAW.js +0 -22
- package/dist/insight-store-EC4PLSAW.js.map +0 -1
- package/dist/issue-store-ZIRP23EP.js +0 -36
- package/dist/issue-store-ZIRP23EP.js.map +0 -1
- package/dist/ledger-TWZTGDFA.js +0 -58
- package/dist/ledger-TWZTGDFA.js.map +0 -1
- package/dist/linear-ingester-XXPAZZRW.js +0 -11
- package/dist/linear-ingester-XXPAZZRW.js.map +0 -1
- package/dist/output-manager-RVJ37XKA.js +0 -13
- package/dist/output-manager-RVJ37XKA.js.map +0 -1
- package/dist/parse-goal-violation-SACGFG3C.js +0 -8
- package/dist/parse-goal-violation-SACGFG3C.js.map +0 -1
- package/dist/pattern-discovery-F7LU5K6E.js +0 -8
- package/dist/pattern-discovery-F7LU5K6E.js.map +0 -1
- package/dist/progress-SRQ2V3BP.js +0 -18
- package/dist/progress-SRQ2V3BP.js.map +0 -1
- package/dist/project-state-AHPA77SM.js +0 -28
- package/dist/project-state-AHPA77SM.js.map +0 -1
- package/dist/sync-M2FSWPBC.js +0 -12
- package/dist/sync-M2FSWPBC.js.map +0 -1
- package/dist/terminal-spawn-5YXDMUCF.js +0 -157
- package/dist/terminal-spawn-5YXDMUCF.js.map +0 -1
- package/dist/tiered-storage-Z3YCR465.js +0 -12
- package/dist/tiered-storage-Z3YCR465.js.map +0 -1
- package/dist/trie-agent-3YDPEGHJ.js +0 -28
- package/dist/trie-agent-3YDPEGHJ.js.map +0 -1
- package/dist/ui/chat.html +0 -1014
- package/dist/ui/goals.html +0 -967
- package/dist/ui/hypotheses.html +0 -1011
- package/dist/ui/ledger.html +0 -954
- package/dist/ui/nudges.html +0 -995
- package/dist/vibe-code-signatures-F6URTBW3.js +0 -16
- package/dist/vibe-code-signatures-F6URTBW3.js.map +0 -1
- package/dist/vulnerability-signatures-T7SKHORW.js +0 -18
- package/dist/vulnerability-signatures-T7SKHORW.js.map +0 -1
package/dist/chunk-F6WFNUAY.js
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
loadConfig
|
|
3
|
-
} from "./chunk-XPZZFPBZ.js";
|
|
4
|
-
|
|
5
|
-
// src/ingest/github-ingester.ts
|
|
6
|
-
import path from "path";
|
|
7
|
-
import { execSync } from "child_process";
|
|
8
|
-
var TICKET_PATTERN = /\b([A-Z]{2,10}-\d+)\b/g;
|
|
9
|
-
var GitHubIngester = class {
|
|
10
|
-
graph;
|
|
11
|
-
constructor(graph) {
|
|
12
|
-
this.graph = graph;
|
|
13
|
-
}
|
|
14
|
-
async getApiToken() {
|
|
15
|
-
const config = await loadConfig();
|
|
16
|
-
return config.apiKeys?.github ?? process.env.GITHUB_TOKEN ?? null;
|
|
17
|
-
}
|
|
18
|
-
getRepoInfo(projectPath) {
|
|
19
|
-
try {
|
|
20
|
-
const url = execSync("git remote get-url origin", {
|
|
21
|
-
cwd: projectPath,
|
|
22
|
-
encoding: "utf-8",
|
|
23
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
24
|
-
}).trim();
|
|
25
|
-
const sshMatch = url.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
26
|
-
if (sshMatch) {
|
|
27
|
-
return { owner: sshMatch[1], name: sshMatch[2] };
|
|
28
|
-
}
|
|
29
|
-
const httpsMatch = url.match(/github\.com\/([^/]+)\/([^/.]+)/);
|
|
30
|
-
if (httpsMatch) {
|
|
31
|
-
return { owner: httpsMatch[1], name: httpsMatch[2] };
|
|
32
|
-
}
|
|
33
|
-
return null;
|
|
34
|
-
} catch {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
async syncPullRequests(owner, repo, token) {
|
|
39
|
-
const prs = await this.fetchJson(
|
|
40
|
-
`https://api.github.com/repos/${owner}/${repo}/pulls?state=open&per_page=50`,
|
|
41
|
-
token
|
|
42
|
-
);
|
|
43
|
-
let linkedTickets = 0;
|
|
44
|
-
let linkedFiles = 0;
|
|
45
|
-
for (const pr of prs) {
|
|
46
|
-
const filesChanged = await this.fetchPRFiles(owner, repo, pr.number, token);
|
|
47
|
-
const reviewStatus = await this.fetchReviewStatus(owner, repo, pr.number, token);
|
|
48
|
-
const ciStatus = await this.fetchCIStatus(owner, repo, pr.head.ref, token);
|
|
49
|
-
const linkedTicketIds = this.extractTicketIds(pr.title, pr.body);
|
|
50
|
-
const state = pr.draft ? "draft" : pr.state === "open" ? "open" : "closed";
|
|
51
|
-
const data = {
|
|
52
|
-
prNumber: pr.number,
|
|
53
|
-
title: pr.title,
|
|
54
|
-
url: pr.html_url,
|
|
55
|
-
state,
|
|
56
|
-
author: pr.user.login,
|
|
57
|
-
branch: pr.head.ref,
|
|
58
|
-
baseBranch: pr.base.ref,
|
|
59
|
-
isDraft: pr.draft,
|
|
60
|
-
filesChanged,
|
|
61
|
-
linkedTicketIds,
|
|
62
|
-
ciStatus,
|
|
63
|
-
reviewStatus,
|
|
64
|
-
createdAt: pr.created_at,
|
|
65
|
-
updatedAt: pr.updated_at,
|
|
66
|
-
...pr.merged_at != null && { mergedAt: pr.merged_at }
|
|
67
|
-
};
|
|
68
|
-
const prNodeId = `pr:${pr.number}`;
|
|
69
|
-
const existingNode = await this.graph.getNode("pull-request", prNodeId);
|
|
70
|
-
if (existingNode) {
|
|
71
|
-
await this.graph.updateNode("pull-request", prNodeId, data);
|
|
72
|
-
} else {
|
|
73
|
-
await this.graph.addNode("pull-request", data);
|
|
74
|
-
}
|
|
75
|
-
for (const file of filesChanged) {
|
|
76
|
-
const resolvedPath = path.resolve(this.graph.projectRoot, file);
|
|
77
|
-
const fileNode = await this.graph.getNode("file", resolvedPath);
|
|
78
|
-
if (fileNode) {
|
|
79
|
-
linkedFiles++;
|
|
80
|
-
await this.graph.addEdge(prNodeId, fileNode.id, "affects");
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
for (const ticketId of linkedTicketIds) {
|
|
84
|
-
const ticketNodeId = `linear:${ticketId}`;
|
|
85
|
-
const ticketNode = await this.graph.getNode("linear-ticket", ticketNodeId);
|
|
86
|
-
if (ticketNode) {
|
|
87
|
-
linkedTickets++;
|
|
88
|
-
await this.graph.addEdge(ticketNodeId, prNodeId, "implements");
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return { prs: prs.length, linkedTickets, linkedFiles };
|
|
93
|
-
}
|
|
94
|
-
async syncIssues(owner, repo, token) {
|
|
95
|
-
const allItems = await this.fetchJson(
|
|
96
|
-
`https://api.github.com/repos/${owner}/${repo}/issues?state=open&per_page=50`,
|
|
97
|
-
token
|
|
98
|
-
);
|
|
99
|
-
const issues = allItems.filter((item) => !item.pull_request);
|
|
100
|
-
for (const issue of issues) {
|
|
101
|
-
const linkedTicketIds = this.extractTicketIds(issue.title, issue.body);
|
|
102
|
-
const data = {
|
|
103
|
-
issueNumber: issue.number,
|
|
104
|
-
title: issue.title,
|
|
105
|
-
url: issue.html_url,
|
|
106
|
-
state: issue.state,
|
|
107
|
-
labels: issue.labels.map((l) => l.name),
|
|
108
|
-
assignees: issue.assignees.map((a) => a.login),
|
|
109
|
-
body: issue.body ?? "",
|
|
110
|
-
linkedTicketIds,
|
|
111
|
-
createdAt: issue.created_at,
|
|
112
|
-
updatedAt: issue.updated_at,
|
|
113
|
-
...issue.closed_at != null && { closedAt: issue.closed_at }
|
|
114
|
-
};
|
|
115
|
-
const issueNodeId = `gh-issue:${issue.number}`;
|
|
116
|
-
const existingNode = await this.graph.getNode("github-issue", issueNodeId);
|
|
117
|
-
if (existingNode) {
|
|
118
|
-
await this.graph.updateNode("github-issue", issueNodeId, data);
|
|
119
|
-
} else {
|
|
120
|
-
await this.graph.addNode("github-issue", data);
|
|
121
|
-
}
|
|
122
|
-
for (const ticketId of linkedTicketIds) {
|
|
123
|
-
const ticketNodeId = `linear:${ticketId}`;
|
|
124
|
-
const ticketNode = await this.graph.getNode("linear-ticket", ticketNodeId);
|
|
125
|
-
if (ticketNode) {
|
|
126
|
-
await this.graph.addEdge(issueNodeId, ticketNodeId, "relatedTo");
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return { issues: issues.length };
|
|
131
|
-
}
|
|
132
|
-
async fetchPRFiles(owner, repo, prNumber, token) {
|
|
133
|
-
try {
|
|
134
|
-
const files = await this.fetchJson(
|
|
135
|
-
`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/files?per_page=100`,
|
|
136
|
-
token
|
|
137
|
-
);
|
|
138
|
-
return files.map((f) => f.filename);
|
|
139
|
-
} catch {
|
|
140
|
-
return [];
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
async fetchReviewStatus(owner, repo, prNumber, token) {
|
|
144
|
-
try {
|
|
145
|
-
const reviews = await this.fetchJson(
|
|
146
|
-
`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews`,
|
|
147
|
-
token
|
|
148
|
-
);
|
|
149
|
-
if (reviews.length === 0) return "none";
|
|
150
|
-
const states = reviews.map((r) => r.state);
|
|
151
|
-
if (states.includes("CHANGES_REQUESTED")) return "changes_requested";
|
|
152
|
-
if (states.includes("APPROVED")) return "approved";
|
|
153
|
-
return "pending";
|
|
154
|
-
} catch {
|
|
155
|
-
return "none";
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
async fetchCIStatus(owner, repo, ref, token) {
|
|
159
|
-
try {
|
|
160
|
-
const status = await this.fetchJson(
|
|
161
|
-
`https://api.github.com/repos/${owner}/${repo}/commits/${ref}/status`,
|
|
162
|
-
token
|
|
163
|
-
);
|
|
164
|
-
switch (status.state) {
|
|
165
|
-
case "success":
|
|
166
|
-
return "passing";
|
|
167
|
-
case "failure":
|
|
168
|
-
case "error":
|
|
169
|
-
return "failing";
|
|
170
|
-
case "pending":
|
|
171
|
-
return "pending";
|
|
172
|
-
default:
|
|
173
|
-
return "unknown";
|
|
174
|
-
}
|
|
175
|
-
} catch {
|
|
176
|
-
return "unknown";
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
extractTicketIds(title, body) {
|
|
180
|
-
const text = `${title} ${body ?? ""}`;
|
|
181
|
-
const matches = text.match(TICKET_PATTERN);
|
|
182
|
-
return matches ? [...new Set(matches)] : [];
|
|
183
|
-
}
|
|
184
|
-
async fetchJson(url, token) {
|
|
185
|
-
const response = await fetch(url, {
|
|
186
|
-
headers: {
|
|
187
|
-
"Accept": "application/vnd.github.v3+json",
|
|
188
|
-
"Authorization": `Bearer ${token}`,
|
|
189
|
-
"User-Agent": "trie-agents",
|
|
190
|
-
"X-GitHub-Api-Version": "2022-11-28"
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
if (response.status === 401) {
|
|
194
|
-
throw new Error(
|
|
195
|
-
"GitHub token is invalid or expired. Set a valid token:\n \u2022 Environment: GITHUB_TOKEN=ghp_...\n \u2022 Config: trie config \u2192 API Keys \u2192 GitHub"
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
if (response.status === 403) {
|
|
199
|
-
const resetHeader = response.headers.get("X-RateLimit-Reset");
|
|
200
|
-
const resetTime = resetHeader ? new Date(parseInt(resetHeader, 10) * 1e3).toLocaleTimeString() : "unknown";
|
|
201
|
-
throw new Error(`GitHub API rate limit exceeded. Resets at ${resetTime}.`);
|
|
202
|
-
}
|
|
203
|
-
if (response.status === 404) {
|
|
204
|
-
throw new Error("Repository not found or token lacks access. Ensure the token has `repo` scope.");
|
|
205
|
-
}
|
|
206
|
-
if (!response.ok) {
|
|
207
|
-
throw new Error(`GitHub API error ${response.status}: ${response.statusText}`);
|
|
208
|
-
}
|
|
209
|
-
return await response.json();
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
export {
|
|
214
|
-
GitHubIngester
|
|
215
|
-
};
|
|
216
|
-
//# sourceMappingURL=chunk-F6WFNUAY.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ingest/github-ingester.ts"],"sourcesContent":["import path from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { loadConfig } from '../config/loader.js';\nimport { ContextGraph } from '../context/graph.js';\nimport type { PullRequestNodeData, GitHubIssueNodeData } from '../context/nodes.js';\n\nconst TICKET_PATTERN = /\\b([A-Z]{2,10}-\\d+)\\b/g;\n\ninterface GitHubPR {\n number: number;\n title: string;\n html_url: string;\n state: string;\n draft: boolean;\n user: { login: string };\n head: { ref: string };\n base: { ref: string };\n body: string | null;\n created_at: string;\n updated_at: string;\n merged_at: string | null;\n}\n\ninterface GitHubFile {\n filename: string;\n}\n\ninterface GitHubReview {\n state: string;\n}\n\ninterface GitHubIssue {\n number: number;\n title: string;\n html_url: string;\n state: string;\n labels: Array<{ name: string }>;\n assignees: Array<{ login: string }>;\n body: string | null;\n pull_request?: unknown;\n created_at: string;\n updated_at: string;\n closed_at: string | null;\n}\n\ninterface GitHubCommitStatus {\n state: string;\n}\n\nexport interface GitHubSyncResult {\n prs: number;\n issues: number;\n linkedTickets: number;\n linkedFiles: number;\n}\n\nexport class GitHubIngester {\n private readonly graph: ContextGraph;\n\n constructor(graph: ContextGraph) {\n this.graph = graph;\n }\n\n async getApiToken(): Promise<string | null> {\n const config = await loadConfig();\n return config.apiKeys?.github ?? process.env.GITHUB_TOKEN ?? null;\n }\n\n getRepoInfo(projectPath: string): { owner: string; name: string } | null {\n try {\n const url = execSync('git remote get-url origin', {\n cwd: projectPath,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n // SSH: git@github.com:owner/repo.git\n const sshMatch = url.match(/github\\.com[:/]([^/]+)\\/([^/.]+)/);\n if (sshMatch) {\n return { owner: sshMatch[1]!, name: sshMatch[2]! };\n }\n\n // HTTPS: https://github.com/owner/repo.git\n const httpsMatch = url.match(/github\\.com\\/([^/]+)\\/([^/.]+)/);\n if (httpsMatch) {\n return { owner: httpsMatch[1]!, name: httpsMatch[2]! };\n }\n\n return null;\n } catch {\n return null;\n }\n }\n\n async syncPullRequests(owner: string, repo: string, token: string): Promise<{ prs: number; linkedTickets: number; linkedFiles: number }> {\n const prs = await this.fetchJson<GitHubPR[]>(\n `https://api.github.com/repos/${owner}/${repo}/pulls?state=open&per_page=50`,\n token,\n );\n\n let linkedTickets = 0;\n let linkedFiles = 0;\n\n for (const pr of prs) {\n const filesChanged = await this.fetchPRFiles(owner, repo, pr.number, token);\n const reviewStatus = await this.fetchReviewStatus(owner, repo, pr.number, token);\n const ciStatus = await this.fetchCIStatus(owner, repo, pr.head.ref, token);\n const linkedTicketIds = this.extractTicketIds(pr.title, pr.body);\n\n const state: PullRequestNodeData['state'] = pr.draft ? 'draft' : pr.state === 'open' ? 'open' : 'closed';\n\n const data: PullRequestNodeData = {\n prNumber: pr.number,\n title: pr.title,\n url: pr.html_url,\n state,\n author: pr.user.login,\n branch: pr.head.ref,\n baseBranch: pr.base.ref,\n isDraft: pr.draft,\n filesChanged,\n linkedTicketIds,\n ciStatus,\n reviewStatus,\n createdAt: pr.created_at,\n updatedAt: pr.updated_at,\n ...(pr.merged_at != null && { mergedAt: pr.merged_at }),\n };\n\n const prNodeId = `pr:${pr.number}`;\n\n const existingNode = await this.graph.getNode('pull-request', prNodeId);\n if (existingNode) {\n await this.graph.updateNode('pull-request', prNodeId, data);\n } else {\n await this.graph.addNode('pull-request', data);\n }\n\n // Link PR to files it touches (file node IDs are normalized absolute paths)\n for (const file of filesChanged) {\n const resolvedPath = path.resolve(this.graph.projectRoot, file);\n const fileNode = await this.graph.getNode('file', resolvedPath);\n if (fileNode) {\n linkedFiles++;\n await this.graph.addEdge(prNodeId, fileNode.id, 'affects');\n }\n }\n\n // Link Linear tickets to this PR\n for (const ticketId of linkedTicketIds) {\n const ticketNodeId = `linear:${ticketId}`;\n const ticketNode = await this.graph.getNode('linear-ticket', ticketNodeId);\n if (ticketNode) {\n linkedTickets++;\n await this.graph.addEdge(ticketNodeId, prNodeId, 'implements');\n }\n }\n }\n\n return { prs: prs.length, linkedTickets, linkedFiles };\n }\n\n async syncIssues(owner: string, repo: string, token: string): Promise<{ issues: number }> {\n const allItems = await this.fetchJson<GitHubIssue[]>(\n `https://api.github.com/repos/${owner}/${repo}/issues?state=open&per_page=50`,\n token,\n );\n\n // GitHub returns PRs in the issues endpoint — filter them out\n const issues = allItems.filter(item => !item.pull_request);\n\n for (const issue of issues) {\n const linkedTicketIds = this.extractTicketIds(issue.title, issue.body);\n\n const data: GitHubIssueNodeData = {\n issueNumber: issue.number,\n title: issue.title,\n url: issue.html_url,\n state: issue.state as 'open' | 'closed',\n labels: issue.labels.map(l => l.name),\n assignees: issue.assignees.map(a => a.login),\n body: issue.body ?? '',\n linkedTicketIds,\n createdAt: issue.created_at,\n updatedAt: issue.updated_at,\n ...(issue.closed_at != null && { closedAt: issue.closed_at }),\n };\n\n const issueNodeId = `gh-issue:${issue.number}`;\n\n const existingNode = await this.graph.getNode('github-issue', issueNodeId);\n if (existingNode) {\n await this.graph.updateNode('github-issue', issueNodeId, data);\n } else {\n await this.graph.addNode('github-issue', data);\n }\n\n // Link to related Linear tickets\n for (const ticketId of linkedTicketIds) {\n const ticketNodeId = `linear:${ticketId}`;\n const ticketNode = await this.graph.getNode('linear-ticket', ticketNodeId);\n if (ticketNode) {\n await this.graph.addEdge(issueNodeId, ticketNodeId, 'relatedTo');\n }\n }\n }\n\n return { issues: issues.length };\n }\n\n private async fetchPRFiles(owner: string, repo: string, prNumber: number, token: string): Promise<string[]> {\n try {\n const files = await this.fetchJson<GitHubFile[]>(\n `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/files?per_page=100`,\n token,\n );\n return files.map(f => f.filename);\n } catch {\n return [];\n }\n }\n\n private async fetchReviewStatus(\n owner: string,\n repo: string,\n prNumber: number,\n token: string,\n ): Promise<PullRequestNodeData['reviewStatus']> {\n try {\n const reviews = await this.fetchJson<GitHubReview[]>(\n `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews`,\n token,\n );\n if (reviews.length === 0) return 'none';\n\n const states = reviews.map(r => r.state);\n if (states.includes('CHANGES_REQUESTED')) return 'changes_requested';\n if (states.includes('APPROVED')) return 'approved';\n return 'pending';\n } catch {\n return 'none';\n }\n }\n\n private async fetchCIStatus(\n owner: string,\n repo: string,\n ref: string,\n token: string,\n ): Promise<PullRequestNodeData['ciStatus']> {\n try {\n const status = await this.fetchJson<GitHubCommitStatus>(\n `https://api.github.com/repos/${owner}/${repo}/commits/${ref}/status`,\n token,\n );\n switch (status.state) {\n case 'success': return 'passing';\n case 'failure': case 'error': return 'failing';\n case 'pending': return 'pending';\n default: return 'unknown';\n }\n } catch {\n return 'unknown';\n }\n }\n\n private extractTicketIds(title: string, body: string | null): string[] {\n const text = `${title} ${body ?? ''}`;\n const matches = text.match(TICKET_PATTERN);\n return matches ? [...new Set(matches)] : [];\n }\n\n private async fetchJson<T>(url: string, token: string): Promise<T> {\n const response = await fetch(url, {\n headers: {\n 'Accept': 'application/vnd.github.v3+json',\n 'Authorization': `Bearer ${token}`,\n 'User-Agent': 'trie-agents',\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n\n if (response.status === 401) {\n throw new Error(\n 'GitHub token is invalid or expired. Set a valid token:\\n' +\n ' • Environment: GITHUB_TOKEN=ghp_...\\n' +\n ' • Config: trie config → API Keys → GitHub',\n );\n }\n\n if (response.status === 403) {\n const resetHeader = response.headers.get('X-RateLimit-Reset');\n const resetTime = resetHeader\n ? new Date(parseInt(resetHeader, 10) * 1000).toLocaleTimeString()\n : 'unknown';\n throw new Error(`GitHub API rate limit exceeded. Resets at ${resetTime}.`);\n }\n\n if (response.status === 404) {\n throw new Error('Repository not found or token lacks access. Ensure the token has `repo` scope.');\n }\n\n if (!response.ok) {\n throw new Error(`GitHub API error ${response.status}: ${response.statusText}`);\n }\n\n return (await response.json()) as T;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-FQ45QP5A.js
DELETED
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getTrieDirectory,
|
|
3
|
-
getWorkingDirectory
|
|
4
|
-
} from "./chunk-VVITXIHN.js";
|
|
5
|
-
|
|
6
|
-
// src/ai/client.ts
|
|
7
|
-
import Anthropic from "@anthropic-ai/sdk";
|
|
8
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
9
|
-
import { execSync } from "child_process";
|
|
10
|
-
import { join } from "path";
|
|
11
|
-
var clientInstance = null;
|
|
12
|
-
var apiKeyChecked = false;
|
|
13
|
-
var apiKeyAvailable = false;
|
|
14
|
-
function isAIAvailable() {
|
|
15
|
-
if (!apiKeyChecked) {
|
|
16
|
-
checkAPIKey();
|
|
17
|
-
}
|
|
18
|
-
return apiKeyAvailable;
|
|
19
|
-
}
|
|
20
|
-
function getKeyFromKeychain() {
|
|
21
|
-
if (process.platform !== "darwin") return null;
|
|
22
|
-
try {
|
|
23
|
-
const result = execSync(
|
|
24
|
-
'security find-generic-password -a "trie" -s "anthropic-api-key" -w 2>/dev/null',
|
|
25
|
-
{ encoding: "utf-8" }
|
|
26
|
-
).trim();
|
|
27
|
-
return result.length > 10 ? result : null;
|
|
28
|
-
} catch {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function saveKeyToKeychain(key) {
|
|
33
|
-
if (process.platform !== "darwin") return false;
|
|
34
|
-
try {
|
|
35
|
-
try {
|
|
36
|
-
execSync('security delete-generic-password -a "trie" -s "anthropic-api-key" 2>/dev/null');
|
|
37
|
-
} catch {
|
|
38
|
-
}
|
|
39
|
-
execSync(`security add-generic-password -a "trie" -s "anthropic-api-key" -w "${key.replace(/"/g, '\\"')}"`);
|
|
40
|
-
return true;
|
|
41
|
-
} catch {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
function setAPIKey(key) {
|
|
46
|
-
process.env.ANTHROPIC_API_KEY = key;
|
|
47
|
-
clientInstance = null;
|
|
48
|
-
apiKeyChecked = true;
|
|
49
|
-
apiKeyAvailable = true;
|
|
50
|
-
if (saveKeyToKeychain(key)) {
|
|
51
|
-
return { saved: true, method: "keychain" };
|
|
52
|
-
}
|
|
53
|
-
try {
|
|
54
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
55
|
-
const trieDir = getTrieDirectory(workDir);
|
|
56
|
-
const configPath = join(trieDir, "config.json");
|
|
57
|
-
let config = {};
|
|
58
|
-
if (existsSync(configPath)) {
|
|
59
|
-
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
60
|
-
}
|
|
61
|
-
if (!config.apiKeys || typeof config.apiKeys !== "object") config.apiKeys = {};
|
|
62
|
-
config.apiKeys.anthropic = key;
|
|
63
|
-
mkdirSync(trieDir, { recursive: true });
|
|
64
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
65
|
-
} catch {
|
|
66
|
-
}
|
|
67
|
-
return { saved: true, method: "config" };
|
|
68
|
-
}
|
|
69
|
-
function checkAPIKey() {
|
|
70
|
-
apiKeyChecked = true;
|
|
71
|
-
const envApiKey = process.env.ANTHROPIC_API_KEY;
|
|
72
|
-
if (envApiKey && envApiKey.length > 10) {
|
|
73
|
-
apiKeyAvailable = true;
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const keychainKey = getKeyFromKeychain();
|
|
77
|
-
if (keychainKey) {
|
|
78
|
-
process.env.ANTHROPIC_API_KEY = keychainKey;
|
|
79
|
-
apiKeyAvailable = true;
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
try {
|
|
83
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
84
|
-
const configPath = join(getTrieDirectory(workDir), "config.json");
|
|
85
|
-
if (existsSync(configPath)) {
|
|
86
|
-
const configContent = readFileSync(configPath, "utf-8");
|
|
87
|
-
const config = JSON.parse(configContent);
|
|
88
|
-
if (config.apiKeys?.anthropic && config.apiKeys.anthropic.length > 10) {
|
|
89
|
-
process.env.ANTHROPIC_API_KEY = config.apiKeys.anthropic;
|
|
90
|
-
apiKeyAvailable = true;
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
} catch {
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
98
|
-
const envFiles = [".env", ".env.local", ".env.production"];
|
|
99
|
-
for (const envFile of envFiles) {
|
|
100
|
-
const envPath = join(workDir, envFile);
|
|
101
|
-
if (existsSync(envPath)) {
|
|
102
|
-
const envContent = readFileSync(envPath, "utf-8");
|
|
103
|
-
const lines = envContent.split("\n");
|
|
104
|
-
for (const line of lines) {
|
|
105
|
-
const match = line.match(/^\s*ANTHROPIC_API_KEY\s*=\s*(.+)$/);
|
|
106
|
-
if (match && match[1]) {
|
|
107
|
-
const key = match[1].trim().replace(/^["']|["']$/g, "");
|
|
108
|
-
if (key.length > 10) {
|
|
109
|
-
process.env.ANTHROPIC_API_KEY = key;
|
|
110
|
-
apiKeyAvailable = true;
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
} catch {
|
|
118
|
-
}
|
|
119
|
-
apiKeyAvailable = false;
|
|
120
|
-
}
|
|
121
|
-
function getClient() {
|
|
122
|
-
if (!isAIAvailable()) {
|
|
123
|
-
throw new Error(
|
|
124
|
-
"ANTHROPIC_API_KEY not found. Set it in your environment to enable AI-powered analysis.\nExample: export ANTHROPIC_API_KEY=sk-ant-..."
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
if (!clientInstance) {
|
|
128
|
-
clientInstance = new Anthropic();
|
|
129
|
-
}
|
|
130
|
-
return clientInstance;
|
|
131
|
-
}
|
|
132
|
-
function tryGetClient() {
|
|
133
|
-
if (!isAIAvailable()) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
if (!clientInstance) {
|
|
137
|
-
clientInstance = new Anthropic();
|
|
138
|
-
}
|
|
139
|
-
return clientInstance;
|
|
140
|
-
}
|
|
141
|
-
async function runAIAnalysis(request) {
|
|
142
|
-
const client = tryGetClient();
|
|
143
|
-
if (!client) {
|
|
144
|
-
return {
|
|
145
|
-
success: false,
|
|
146
|
-
content: "",
|
|
147
|
-
error: "AI not available - ANTHROPIC_API_KEY not set"
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
try {
|
|
151
|
-
const response = await client.messages.create(
|
|
152
|
-
{
|
|
153
|
-
model: "claude-sonnet-4-20250514",
|
|
154
|
-
// Increase default token limit to prevent truncation in Trie Watch
|
|
155
|
-
max_tokens: request.maxTokens || 8192,
|
|
156
|
-
temperature: request.temperature ?? 0.3,
|
|
157
|
-
system: request.systemPrompt,
|
|
158
|
-
messages: [
|
|
159
|
-
{
|
|
160
|
-
role: "user",
|
|
161
|
-
content: request.userPrompt
|
|
162
|
-
}
|
|
163
|
-
]
|
|
164
|
-
},
|
|
165
|
-
request.signal ? { signal: request.signal } : void 0
|
|
166
|
-
);
|
|
167
|
-
const textContent = response.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
|
|
168
|
-
return {
|
|
169
|
-
success: true,
|
|
170
|
-
content: textContent,
|
|
171
|
-
tokensUsed: {
|
|
172
|
-
input: response.usage.input_tokens,
|
|
173
|
-
output: response.usage.output_tokens
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
} catch (error) {
|
|
177
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
178
|
-
if (errorMessage.includes("authentication") || errorMessage.includes("API key")) {
|
|
179
|
-
return {
|
|
180
|
-
success: false,
|
|
181
|
-
content: "",
|
|
182
|
-
error: "Invalid API key. Check your ANTHROPIC_API_KEY."
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
if (errorMessage.includes("rate limit")) {
|
|
186
|
-
return {
|
|
187
|
-
success: false,
|
|
188
|
-
content: "",
|
|
189
|
-
error: "Rate limited. Try again in a moment."
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
return {
|
|
193
|
-
success: false,
|
|
194
|
-
content: "",
|
|
195
|
-
error: `AI analysis failed: ${errorMessage}`
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
async function analyzeCodeIssues(request) {
|
|
200
|
-
const systemPrompts = {
|
|
201
|
-
validate: `You are a senior code reviewer. Analyze the following issues detected by static analysis.
|
|
202
|
-
For each issue:
|
|
203
|
-
1. Determine if it's a TRUE POSITIVE (real problem) or FALSE POSITIVE (not actually an issue)
|
|
204
|
-
2. Provide a confidence score (0-100)
|
|
205
|
-
3. Explain your reasoning briefly
|
|
206
|
-
|
|
207
|
-
Output format:
|
|
208
|
-
### Issue 1: [file:line]
|
|
209
|
-
**Verdict:** TRUE_POSITIVE / FALSE_POSITIVE
|
|
210
|
-
**Confidence:** [0-100]
|
|
211
|
-
**Reason:** [one line explanation]
|
|
212
|
-
**Fix:** [specific fix if true positive]`,
|
|
213
|
-
expand: `You are a security expert and senior architect. Given these detected code issues, look deeper:
|
|
214
|
-
1. Are there related problems in the same code that were missed?
|
|
215
|
-
2. What's the root cause of these patterns?
|
|
216
|
-
3. What's the blast radius if these issues cause problems?
|
|
217
|
-
4. What architectural changes would prevent these patterns?
|
|
218
|
-
|
|
219
|
-
Be specific. Reference line numbers. Provide code examples.`,
|
|
220
|
-
fix: `You are an expert programmer. For each issue, provide a specific fix:
|
|
221
|
-
1. Show the exact code change needed
|
|
222
|
-
2. Explain why this fix works
|
|
223
|
-
3. Note any edge cases to consider
|
|
224
|
-
|
|
225
|
-
Use markdown code blocks with the language specified.`,
|
|
226
|
-
explain: `You are a patient teacher explaining code issues to a junior developer.
|
|
227
|
-
For each issue:
|
|
228
|
-
1. Explain what the problem is in simple terms
|
|
229
|
-
2. Explain why it's a problem (what could go wrong)
|
|
230
|
-
3. Explain the fix and why it works
|
|
231
|
-
4. Provide a learning resource if relevant`
|
|
232
|
-
};
|
|
233
|
-
const issuesText = request.issues.map((issue, i) => {
|
|
234
|
-
let text = `### Issue ${i + 1}: ${issue.file}${issue.line ? ":" + issue.line : ""}
|
|
235
|
-
`;
|
|
236
|
-
text += `**Problem:** ${issue.issue}
|
|
237
|
-
`;
|
|
238
|
-
if (issue.code) {
|
|
239
|
-
text += `**Code:**
|
|
240
|
-
\`\`\`
|
|
241
|
-
${issue.code}
|
|
242
|
-
\`\`\`
|
|
243
|
-
`;
|
|
244
|
-
}
|
|
245
|
-
return text;
|
|
246
|
-
}).join("\n");
|
|
247
|
-
const userPrompt = `${request.context ? `Context: ${request.context}
|
|
248
|
-
|
|
249
|
-
` : ""}${issuesText}`;
|
|
250
|
-
return runAIAnalysis({
|
|
251
|
-
systemPrompt: systemPrompts[request.analysisType],
|
|
252
|
-
userPrompt,
|
|
253
|
-
// Increase token limit to prevent response truncation
|
|
254
|
-
maxTokens: 8192,
|
|
255
|
-
temperature: 0.2
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
function getAIStatusMessage() {
|
|
259
|
-
if (isAIAvailable()) {
|
|
260
|
-
return "[AI] AI-powered analysis enabled";
|
|
261
|
-
}
|
|
262
|
-
return "[!] AI not available (ANTHROPIC_API_KEY not set) - using pattern-only mode";
|
|
263
|
-
}
|
|
264
|
-
async function runAIWithTools(request) {
|
|
265
|
-
const client = tryGetClient();
|
|
266
|
-
if (!client) {
|
|
267
|
-
return { success: false, content: "", error: "AI not available - ANTHROPIC_API_KEY not set" };
|
|
268
|
-
}
|
|
269
|
-
const maxRounds = request.maxToolRounds ?? 5;
|
|
270
|
-
const messages = [...request.messages];
|
|
271
|
-
const allToolCalls = [];
|
|
272
|
-
const allToolResults = [];
|
|
273
|
-
const { onProgress } = request;
|
|
274
|
-
try {
|
|
275
|
-
for (let round = 0; round < maxRounds; round++) {
|
|
276
|
-
onProgress?.(round === 0 ? "Analyzing..." : `Processing (round ${round + 1})...`);
|
|
277
|
-
const response = await client.messages.create({
|
|
278
|
-
model: "claude-sonnet-4-20250514",
|
|
279
|
-
// Increase default token limit to prevent truncation in Trie Watch
|
|
280
|
-
max_tokens: request.maxTokens || 8192,
|
|
281
|
-
temperature: 0.3,
|
|
282
|
-
system: request.systemPrompt,
|
|
283
|
-
messages,
|
|
284
|
-
tools: request.tools
|
|
285
|
-
});
|
|
286
|
-
const toolUseBlocks = response.content.filter(
|
|
287
|
-
(b) => b.type === "tool_use"
|
|
288
|
-
);
|
|
289
|
-
const textBlocks = response.content.filter(
|
|
290
|
-
(b) => b.type === "text"
|
|
291
|
-
);
|
|
292
|
-
if (toolUseBlocks.length === 0) {
|
|
293
|
-
const result2 = {
|
|
294
|
-
success: true,
|
|
295
|
-
content: textBlocks.map((b) => b.text).join("\n")
|
|
296
|
-
};
|
|
297
|
-
if (allToolCalls.length > 0) {
|
|
298
|
-
result2.toolCalls = allToolCalls;
|
|
299
|
-
result2.toolResults = allToolResults;
|
|
300
|
-
}
|
|
301
|
-
return result2;
|
|
302
|
-
}
|
|
303
|
-
messages.push({ role: "assistant", content: response.content });
|
|
304
|
-
const toolResults = [];
|
|
305
|
-
for (const block of toolUseBlocks) {
|
|
306
|
-
const input = block.input ?? {};
|
|
307
|
-
const toolLabel = block.name.replace(/^trie_/, "").replace(/_/g, " ");
|
|
308
|
-
onProgress?.(`Running ${toolLabel}...`);
|
|
309
|
-
let resultText;
|
|
310
|
-
let isError = false;
|
|
311
|
-
try {
|
|
312
|
-
resultText = await request.executeTool(block.name, input, onProgress);
|
|
313
|
-
} catch (err) {
|
|
314
|
-
resultText = `Tool error: ${err instanceof Error ? err.message : String(err)}`;
|
|
315
|
-
isError = true;
|
|
316
|
-
}
|
|
317
|
-
allToolCalls.push({ name: block.name, input, result: resultText });
|
|
318
|
-
allToolResults.push(resultText);
|
|
319
|
-
toolResults.push({
|
|
320
|
-
type: "tool_result",
|
|
321
|
-
tool_use_id: block.id,
|
|
322
|
-
content: resultText,
|
|
323
|
-
is_error: isError
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
messages.push({ role: "user", content: toolResults });
|
|
327
|
-
if (response.stop_reason === "end_turn") {
|
|
328
|
-
const text = textBlocks.map((b) => b.text).join("\n");
|
|
329
|
-
const result2 = { success: true, content: text || "Done." };
|
|
330
|
-
if (allToolCalls.length > 0) {
|
|
331
|
-
result2.toolCalls = allToolCalls;
|
|
332
|
-
result2.toolResults = allToolResults;
|
|
333
|
-
}
|
|
334
|
-
return result2;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
const result = { success: true, content: "Reached maximum tool rounds." };
|
|
338
|
-
if (allToolCalls.length > 0) {
|
|
339
|
-
result.toolCalls = allToolCalls;
|
|
340
|
-
result.toolResults = allToolResults;
|
|
341
|
-
}
|
|
342
|
-
return result;
|
|
343
|
-
} catch (error) {
|
|
344
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
345
|
-
return { success: false, content: "", error: `AI tool-use failed: ${errorMessage}` };
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
export {
|
|
350
|
-
isAIAvailable,
|
|
351
|
-
getKeyFromKeychain,
|
|
352
|
-
saveKeyToKeychain,
|
|
353
|
-
setAPIKey,
|
|
354
|
-
getClient,
|
|
355
|
-
tryGetClient,
|
|
356
|
-
runAIAnalysis,
|
|
357
|
-
analyzeCodeIssues,
|
|
358
|
-
getAIStatusMessage,
|
|
359
|
-
runAIWithTools
|
|
360
|
-
};
|
|
361
|
-
//# sourceMappingURL=chunk-FQ45QP5A.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ai/client.ts"],"sourcesContent":["/**\n * Centralized AI Client\n * \n * Handles API key detection, graceful fallbacks, and AI-powered analysis.\n * All agents use this for AI-enhanced analysis.\n * \n * API Key Setup:\n * 1. Set ANTHROPIC_API_KEY in your environment\n * 2. Or add it to your MCP server config (mcp.json)\n * 3. Or create .env file in your project root\n * \n * If no API key is found, agents fall back to pattern-only mode.\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';\nimport { execSync } from 'node:child_process';\nimport { join } from 'path';\nimport { getWorkingDirectory, getTrieDirectory } from '../utils/workspace.js';\n\n// Cached client instance\nlet clientInstance: Anthropic | null = null;\nlet apiKeyChecked = false;\nlet apiKeyAvailable = false;\n\n/**\n * Check if AI is available (API key is set)\n */\nexport function isAIAvailable(): boolean {\n if (!apiKeyChecked) {\n checkAPIKey();\n }\n return apiKeyAvailable;\n}\n\n/**\n * Read API key from Apple Keychain (macOS only)\n */\nexport function getKeyFromKeychain(): string | null {\n if (process.platform !== 'darwin') return null;\n try {\n const result = execSync(\n 'security find-generic-password -a \"trie\" -s \"anthropic-api-key\" -w 2>/dev/null',\n { encoding: 'utf-8' }\n ).trim();\n return result.length > 10 ? result : null;\n } catch { return null; }\n}\n\n/**\n * Save API key to Apple Keychain (macOS only)\n */\nexport function saveKeyToKeychain(key: string): boolean {\n if (process.platform !== 'darwin') return false;\n try {\n try { execSync('security delete-generic-password -a \"trie\" -s \"anthropic-api-key\" 2>/dev/null'); } catch { /* not found */ }\n execSync(`security add-generic-password -a \"trie\" -s \"anthropic-api-key\" -w \"${key.replace(/\"/g, '\\\\\"')}\"`);\n return true;\n } catch { return false; }\n}\n\n/**\n * Save API key and refresh the cached state\n */\nexport function setAPIKey(key: string): { saved: boolean; method: 'keychain' | 'config' } {\n process.env.ANTHROPIC_API_KEY = key;\n clientInstance = null;\n apiKeyChecked = true;\n apiKeyAvailable = true;\n\n if (saveKeyToKeychain(key)) {\n return { saved: true, method: 'keychain' };\n }\n\n try {\n const workDir = getWorkingDirectory(undefined, true);\n const trieDir = getTrieDirectory(workDir);\n const configPath = join(trieDir, 'config.json');\n let config: Record<string, unknown> = {};\n if (existsSync(configPath)) {\n config = JSON.parse(readFileSync(configPath, 'utf-8'));\n }\n if (!config.apiKeys || typeof config.apiKeys !== 'object') config.apiKeys = {};\n (config.apiKeys as Record<string, string>).anthropic = key;\n mkdirSync(trieDir, { recursive: true });\n writeFileSync(configPath, JSON.stringify(config, null, 2));\n } catch { /* best effort */ }\n\n return { saved: true, method: 'config' };\n}\n\n/**\n * Check for API key in environment, keychain, config, and .env files\n */\nfunction checkAPIKey(): void {\n apiKeyChecked = true;\n \n // 1. Check standard env var first\n const envApiKey = process.env.ANTHROPIC_API_KEY;\n if (envApiKey && envApiKey.length > 10) {\n apiKeyAvailable = true;\n return;\n }\n\n // 2. Check Apple Keychain (macOS)\n const keychainKey = getKeyFromKeychain();\n if (keychainKey) {\n process.env.ANTHROPIC_API_KEY = keychainKey;\n apiKeyAvailable = true;\n return;\n }\n \n // 3. Check config file\n try {\n const workDir = getWorkingDirectory(undefined, true);\n const configPath = join(getTrieDirectory(workDir), 'config.json');\n \n if (existsSync(configPath)) {\n const configContent = readFileSync(configPath, 'utf-8');\n const config = JSON.parse(configContent);\n \n if (config.apiKeys?.anthropic && config.apiKeys.anthropic.length > 10) {\n process.env.ANTHROPIC_API_KEY = config.apiKeys.anthropic;\n apiKeyAvailable = true;\n return;\n }\n }\n } catch {\n // Config file doesn't exist or couldn't be read\n }\n \n // 4. Check .env files\n try {\n const workDir = getWorkingDirectory(undefined, true);\n const envFiles = ['.env', '.env.local', '.env.production'];\n \n for (const envFile of envFiles) {\n const envPath = join(workDir, envFile);\n if (existsSync(envPath)) {\n const envContent = readFileSync(envPath, 'utf-8');\n const lines = envContent.split('\\n');\n \n for (const line of lines) {\n const match = line.match(/^\\s*ANTHROPIC_API_KEY\\s*=\\s*(.+)$/);\n if (match && match[1]) {\n const key = match[1].trim().replace(/^[\"']|[\"']$/g, '');\n if (key.length > 10) {\n process.env.ANTHROPIC_API_KEY = key;\n apiKeyAvailable = true;\n return;\n }\n }\n }\n }\n }\n } catch {\n // .env file doesn't exist or couldn't be read\n }\n \n apiKeyAvailable = false;\n}\n\n/**\n * Get the Anthropic client (lazy initialized)\n * Throws if API key is not available\n */\nexport function getClient(): Anthropic {\n if (!isAIAvailable()) {\n throw new Error(\n 'ANTHROPIC_API_KEY not found. Set it in your environment to enable AI-powered analysis.\\n' +\n 'Example: export ANTHROPIC_API_KEY=sk-ant-...'\n );\n }\n \n if (!clientInstance) {\n clientInstance = new Anthropic();\n }\n \n return clientInstance;\n}\n\n/**\n * Try to get client, return null if not available\n */\nexport function tryGetClient(): Anthropic | null {\n if (!isAIAvailable()) {\n return null;\n }\n \n if (!clientInstance) {\n clientInstance = new Anthropic();\n }\n \n return clientInstance;\n}\n\nexport interface AIAnalysisRequest {\n systemPrompt: string;\n userPrompt: string;\n maxTokens?: number;\n temperature?: number;\n /** Optional AbortSignal to cancel the request */\n signal?: AbortSignal;\n}\n\nexport interface AIAnalysisResult {\n success: boolean;\n content: string;\n error?: string;\n tokensUsed?: {\n input: number;\n output: number;\n };\n}\n\n/**\n * Run AI analysis with the given prompts\n */\nexport async function runAIAnalysis(request: AIAnalysisRequest): Promise<AIAnalysisResult> {\n const client = tryGetClient();\n \n if (!client) {\n return {\n success: false,\n content: '',\n error: 'AI not available - ANTHROPIC_API_KEY not set'\n };\n }\n \n try {\n const response = await client.messages.create(\n {\n model: 'claude-sonnet-4-20250514',\n // Increase default token limit to prevent truncation in Trie Watch\n max_tokens: request.maxTokens || 8192,\n temperature: request.temperature ?? 0.3,\n system: request.systemPrompt,\n messages: [\n {\n role: 'user',\n content: request.userPrompt\n }\n ]\n },\n request.signal ? { signal: request.signal } : undefined\n );\n \n // Extract text content - ensure full text is preserved without truncation\n const textContent = response.content\n .filter((block): block is Anthropic.TextBlock => block.type === 'text')\n .map(block => block.text)\n .join('\\n');\n \n return {\n success: true,\n content: textContent,\n tokensUsed: {\n input: response.usage.input_tokens,\n output: response.usage.output_tokens\n }\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n \n // Check for specific error types\n if (errorMessage.includes('authentication') || errorMessage.includes('API key')) {\n return {\n success: false,\n content: '',\n error: 'Invalid API key. Check your ANTHROPIC_API_KEY.'\n };\n }\n \n if (errorMessage.includes('rate limit')) {\n return {\n success: false,\n content: '',\n error: 'Rate limited. Try again in a moment.'\n };\n }\n \n return {\n success: false,\n content: '',\n error: `AI analysis failed: ${errorMessage}`\n };\n }\n}\n\n/**\n * AI analysis for code issues\n */\nexport interface CodeIssueAnalysisRequest {\n issues: Array<{\n file: string;\n line?: number;\n issue: string;\n code?: string;\n }>;\n analysisType: 'validate' | 'expand' | 'fix' | 'explain';\n context?: string;\n}\n\n/**\n * Run AI analysis on code issues\n * Used by agents to validate, expand, or fix detected issues\n */\nexport async function analyzeCodeIssues(request: CodeIssueAnalysisRequest): Promise<AIAnalysisResult> {\n const systemPrompts: Record<CodeIssueAnalysisRequest['analysisType'], string> = {\n validate: `You are a senior code reviewer. Analyze the following issues detected by static analysis.\nFor each issue:\n1. Determine if it's a TRUE POSITIVE (real problem) or FALSE POSITIVE (not actually an issue)\n2. Provide a confidence score (0-100)\n3. Explain your reasoning briefly\n\nOutput format:\n### Issue 1: [file:line]\n**Verdict:** TRUE_POSITIVE / FALSE_POSITIVE\n**Confidence:** [0-100]\n**Reason:** [one line explanation]\n**Fix:** [specific fix if true positive]`,\n\n expand: `You are a security expert and senior architect. Given these detected code issues, look deeper:\n1. Are there related problems in the same code that were missed?\n2. What's the root cause of these patterns?\n3. What's the blast radius if these issues cause problems?\n4. What architectural changes would prevent these patterns?\n\nBe specific. Reference line numbers. Provide code examples.`,\n\n fix: `You are an expert programmer. For each issue, provide a specific fix:\n1. Show the exact code change needed\n2. Explain why this fix works\n3. Note any edge cases to consider\n\nUse markdown code blocks with the language specified.`,\n\n explain: `You are a patient teacher explaining code issues to a junior developer.\nFor each issue:\n1. Explain what the problem is in simple terms\n2. Explain why it's a problem (what could go wrong)\n3. Explain the fix and why it works\n4. Provide a learning resource if relevant`\n };\n\n const issuesText = request.issues.map((issue, i) => {\n let text = `### Issue ${i + 1}: ${issue.file}${issue.line ? ':' + issue.line : ''}\\n`;\n text += `**Problem:** ${issue.issue}\\n`;\n if (issue.code) {\n // Preserve complete code without truncation for analysis\n text += `**Code:**\\n\\`\\`\\`\\n${issue.code}\\n\\`\\`\\`\\n`;\n }\n return text;\n }).join('\\n');\n\n const userPrompt = `${request.context ? `Context: ${request.context}\\n\\n` : ''}${issuesText}`;\n\n return runAIAnalysis({\n systemPrompt: systemPrompts[request.analysisType],\n userPrompt,\n // Increase token limit to prevent response truncation\n maxTokens: 8192,\n temperature: 0.2\n });\n}\n\n/**\n * Get status message about AI availability\n */\nexport function getAIStatusMessage(): string {\n if (isAIAvailable()) {\n return '[AI] AI-powered analysis enabled';\n }\n return '[!] AI not available (ANTHROPIC_API_KEY not set) - using pattern-only mode';\n}\n\nexport interface AIToolCallRecord {\n name: string;\n input: Record<string, unknown>;\n result?: string;\n}\n\nexport interface AIToolCallResult {\n success: boolean;\n content: string;\n error?: string;\n toolCalls?: AIToolCallRecord[];\n toolResults?: string[];\n}\n\n/**\n * Run AI analysis with tool-use support.\n * Loops until Claude returns a final text response or hits maxToolRounds.\n */\nexport async function runAIWithTools(request: {\n systemPrompt: string;\n messages: Anthropic.MessageParam[];\n tools: Anthropic.Tool[];\n executeTool: (name: string, input: Record<string, unknown>, onProgress?: (msg: string) => void) => Promise<string>;\n maxTokens?: number;\n maxToolRounds?: number;\n onProgress?: (message: string) => void;\n}): Promise<AIToolCallResult> {\n const client = tryGetClient();\n if (!client) {\n return { success: false, content: '', error: 'AI not available - ANTHROPIC_API_KEY not set' };\n }\n\n const maxRounds = request.maxToolRounds ?? 5;\n const messages: Anthropic.MessageParam[] = [...request.messages];\n const allToolCalls: AIToolCallRecord[] = [];\n const allToolResults: string[] = [];\n\n const { onProgress } = request;\n\n try {\n for (let round = 0; round < maxRounds; round++) {\n onProgress?.(round === 0 ? 'Analyzing...' : `Processing (round ${round + 1})...`);\n const response = await client.messages.create({\n model: 'claude-sonnet-4-20250514',\n // Increase default token limit to prevent truncation in Trie Watch\n max_tokens: request.maxTokens || 8192,\n temperature: 0.3,\n system: request.systemPrompt,\n messages,\n tools: request.tools,\n });\n\n const toolUseBlocks = response.content.filter(\n (b): b is Anthropic.ToolUseBlock => b.type === 'tool_use'\n );\n const textBlocks = response.content.filter(\n (b): b is Anthropic.TextBlock => b.type === 'text'\n );\n\n if (toolUseBlocks.length === 0) {\n // Preserve complete text content without truncation\n const result: AIToolCallResult = {\n success: true,\n content: textBlocks.map(b => b.text).join('\\n'),\n };\n if (allToolCalls.length > 0) {\n result.toolCalls = allToolCalls;\n result.toolResults = allToolResults;\n }\n return result;\n }\n\n // Claude wants to use tools -- execute them\n messages.push({ role: 'assistant', content: response.content });\n\n const toolResults: Anthropic.ToolResultBlockParam[] = [];\n for (const block of toolUseBlocks) {\n const input = (block.input ?? {}) as Record<string, unknown>;\n const toolLabel = block.name.replace(/^trie_/, '').replace(/_/g, ' ');\n onProgress?.(`Running ${toolLabel}...`);\n let resultText: string;\n let isError = false;\n try {\n resultText = await request.executeTool(block.name, input, onProgress);\n } catch (err) {\n resultText = `Tool error: ${err instanceof Error ? err.message : String(err)}`;\n isError = true;\n }\n allToolCalls.push({ name: block.name, input, result: resultText });\n allToolResults.push(resultText);\n toolResults.push({\n type: 'tool_result',\n tool_use_id: block.id,\n content: resultText,\n is_error: isError,\n });\n }\n\n messages.push({ role: 'user', content: toolResults });\n\n if (response.stop_reason === 'end_turn') {\n // Preserve complete text without truncation\n const text = textBlocks.map(b => b.text).join('\\n');\n const result: AIToolCallResult = { success: true, content: text || 'Done.' };\n if (allToolCalls.length > 0) {\n result.toolCalls = allToolCalls;\n result.toolResults = allToolResults;\n }\n return result;\n }\n }\n\n const result: AIToolCallResult = { success: true, content: 'Reached maximum tool rounds.' };\n if (allToolCalls.length > 0) {\n result.toolCalls = allToolCalls;\n result.toolResults = allToolResults;\n }\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return { success: false, content: '', error: `AI tool-use failed: ${errorMessage}` };\n }\n}\n"],"mappings":"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","names":["result"]}
|