@triedotdev/mcp 1.0.169 → 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 -545
- 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 -3098
- package/dist/cli/main.js.map +1 -1
- package/dist/index.js +2 -34
- package/dist/index.js.map +1 -1
- package/dist/server/mcp-server.js +2 -34
- 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-3XR6WVAW.js +0 -4011
- package/dist/chunk-3XR6WVAW.js.map +0 -1
- package/dist/chunk-43X6JBEM.js +0 -36
- package/dist/chunk-43X6JBEM.js.map +0 -1
- package/dist/chunk-6NLHFIYA.js +0 -344
- package/dist/chunk-6NLHFIYA.js.map +0 -1
- package/dist/chunk-7IO4YUI3.js +0 -1827
- package/dist/chunk-7IO4YUI3.js.map +0 -1
- package/dist/chunk-AHD2CBQ7.js +0 -846
- package/dist/chunk-AHD2CBQ7.js.map +0 -1
- package/dist/chunk-BUTOP5EB.js +0 -931
- package/dist/chunk-BUTOP5EB.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-FBNURWRY.js +0 -662
- package/dist/chunk-FBNURWRY.js.map +0 -1
- package/dist/chunk-FQ45QP5A.js +0 -361
- package/dist/chunk-FQ45QP5A.js.map +0 -1
- package/dist/chunk-FVRO5RN3.js +0 -1306
- package/dist/chunk-FVRO5RN3.js.map +0 -1
- package/dist/chunk-G2TGF6TR.js +0 -573
- package/dist/chunk-G2TGF6TR.js.map +0 -1
- package/dist/chunk-G3I7SZLW.js +0 -354
- package/dist/chunk-G3I7SZLW.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-I2O5OYQT.js +0 -727
- package/dist/chunk-I2O5OYQT.js.map +0 -1
- package/dist/chunk-JVMBCWKS.js +0 -348
- package/dist/chunk-JVMBCWKS.js.map +0 -1
- package/dist/chunk-KCUOWRPX.js +0 -816
- package/dist/chunk-KCUOWRPX.js.map +0 -1
- package/dist/chunk-KDHN2ZQE.js +0 -313
- package/dist/chunk-KDHN2ZQE.js.map +0 -1
- package/dist/chunk-ME2OERF5.js +0 -345
- package/dist/chunk-ME2OERF5.js.map +0 -1
- package/dist/chunk-OBQ74FOU.js +0 -27
- package/dist/chunk-OBQ74FOU.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-SASNMSB5.js +0 -12597
- package/dist/chunk-SASNMSB5.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-XPZZFPBZ.js +0 -491
- package/dist/chunk-XPZZFPBZ.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 -421
- 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-3GCCZMLK.js +0 -216
- package/dist/fast-analyzer-3GCCZMLK.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-QUKX2W6C.js +0 -25
- package/dist/goal-manager-QUKX2W6C.js.map +0 -1
- package/dist/goal-validator-2SFSKKVU.js +0 -24
- package/dist/goal-validator-2SFSKKVU.js.map +0 -1
- package/dist/graph-B3NA4S7I.js +0 -10
- package/dist/graph-B3NA4S7I.js.map +0 -1
- package/dist/hypothesis-KCPBR652.js +0 -23
- package/dist/hypothesis-KCPBR652.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-YAXTNRRY.js +0 -36
- package/dist/issue-store-YAXTNRRY.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-DYNC5CQ6.js +0 -13
- package/dist/tiered-storage-DYNC5CQ6.js.map +0 -1
- package/dist/trie-agent-I3HAHY2G.js +0 -26
- package/dist/trie-agent-I3HAHY2G.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-5ZULYP3D.js +0 -987
- package/dist/vibe-code-signatures-5ZULYP3D.js.map +0 -1
- package/dist/vulnerability-signatures-2URZSXAQ.js +0 -983
- package/dist/vulnerability-signatures-2URZSXAQ.js.map +0 -1
package/dist/chunk-3XR6WVAW.js
DELETED
|
@@ -1,4011 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
GitHubIngester
|
|
3
|
-
} from "./chunk-F6WFNUAY.js";
|
|
4
|
-
import {
|
|
5
|
-
appendToSection,
|
|
6
|
-
completeBootstrap,
|
|
7
|
-
getContextForAI,
|
|
8
|
-
getProjectInfoStructured,
|
|
9
|
-
getProjectSection,
|
|
10
|
-
getProjectSections,
|
|
11
|
-
initProjectInfo,
|
|
12
|
-
initializeBootstrapFiles,
|
|
13
|
-
loadBootstrapContext,
|
|
14
|
-
loadContextState,
|
|
15
|
-
loadProjectInfo,
|
|
16
|
-
loadRules,
|
|
17
|
-
loadTeamInfo,
|
|
18
|
-
needsBootstrap,
|
|
19
|
-
projectInfoExists,
|
|
20
|
-
updateProjectSection
|
|
21
|
-
} from "./chunk-HVCDY3AK.js";
|
|
22
|
-
import {
|
|
23
|
-
LinearIngester
|
|
24
|
-
} from "./chunk-Q63FFI6D.js";
|
|
25
|
-
import {
|
|
26
|
-
GitHubBranchesTool,
|
|
27
|
-
TrieCheckTool,
|
|
28
|
-
TrieCloudFixTool,
|
|
29
|
-
TrieExplainTool,
|
|
30
|
-
TrieFeedbackTool,
|
|
31
|
-
TrieFixTool,
|
|
32
|
-
TrieGetBlockersTool,
|
|
33
|
-
TrieGetDecisionsTool,
|
|
34
|
-
TrieGetGovernanceTool,
|
|
35
|
-
TrieGetNudgesTool,
|
|
36
|
-
TrieGetRelatedDecisionsTool,
|
|
37
|
-
TrieGetRelatedGovernanceTool,
|
|
38
|
-
TriePipelineTool,
|
|
39
|
-
TrieQueryContextTool,
|
|
40
|
-
TrieQueryLedgerBlocksTool,
|
|
41
|
-
TrieTellTool,
|
|
42
|
-
TrieWatchTool,
|
|
43
|
-
getPrompt,
|
|
44
|
-
getSystemPrompt
|
|
45
|
-
} from "./chunk-SASNMSB5.js";
|
|
46
|
-
import {
|
|
47
|
-
CodebaseIndex
|
|
48
|
-
} from "./chunk-Q5EKA5YA.js";
|
|
49
|
-
import {
|
|
50
|
-
formatFriendlyError,
|
|
51
|
-
isTrieInitialized
|
|
52
|
-
} from "./chunk-FVRO5RN3.js";
|
|
53
|
-
import {
|
|
54
|
-
exportToJson
|
|
55
|
-
} from "./chunk-OBQ74FOU.js";
|
|
56
|
-
import {
|
|
57
|
-
loadConfig
|
|
58
|
-
} from "./chunk-XPZZFPBZ.js";
|
|
59
|
-
import {
|
|
60
|
-
findCrossProjectPatterns,
|
|
61
|
-
getGlobalMemoryStats,
|
|
62
|
-
listTrackedProjects,
|
|
63
|
-
searchGlobalPatterns
|
|
64
|
-
} from "./chunk-AHD2CBQ7.js";
|
|
65
|
-
import {
|
|
66
|
-
ContextGraph
|
|
67
|
-
} from "./chunk-VUL52BQL.js";
|
|
68
|
-
import {
|
|
69
|
-
getStorage
|
|
70
|
-
} from "./chunk-BUTOP5EB.js";
|
|
71
|
-
import {
|
|
72
|
-
findSimilarIssues,
|
|
73
|
-
getMemoryStats,
|
|
74
|
-
getRecentIssues,
|
|
75
|
-
markIssueResolved,
|
|
76
|
-
purgeIssues,
|
|
77
|
-
searchIssues
|
|
78
|
-
} from "./chunk-KCUOWRPX.js";
|
|
79
|
-
import {
|
|
80
|
-
getTrieDirectory,
|
|
81
|
-
getWorkingDirectory
|
|
82
|
-
} from "./chunk-VVITXIHN.js";
|
|
83
|
-
import {
|
|
84
|
-
isInteractiveMode
|
|
85
|
-
} from "./chunk-KDHN2ZQE.js";
|
|
86
|
-
import {
|
|
87
|
-
runShellCommandSync
|
|
88
|
-
} from "./chunk-2HF65EHQ.js";
|
|
89
|
-
|
|
90
|
-
// src/server/mcp-server.ts
|
|
91
|
-
import { Server as Server2 } from "@modelcontextprotocol/sdk/server/index.js";
|
|
92
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
93
|
-
import {
|
|
94
|
-
CallToolRequestSchema,
|
|
95
|
-
ListToolsRequestSchema,
|
|
96
|
-
ListResourcesRequestSchema,
|
|
97
|
-
ReadResourceRequestSchema
|
|
98
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
99
|
-
|
|
100
|
-
// src/utils/ai-tool-detector.ts
|
|
101
|
-
function detectAITool() {
|
|
102
|
-
if (process.env.CLAUDE_CODE_VERSION || process.env.CLAUDE_CODE) {
|
|
103
|
-
return {
|
|
104
|
-
name: "Claude Code",
|
|
105
|
-
preferredOutputFormat: "markdown",
|
|
106
|
-
supportsDiffs: true,
|
|
107
|
-
supportsInlineActions: true
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
if (process.env.CURSOR_IDE || process.env.CURSOR_TRACE_ID) {
|
|
111
|
-
return {
|
|
112
|
-
name: "Cursor",
|
|
113
|
-
preferredOutputFormat: "rich-text",
|
|
114
|
-
supportsDiffs: true,
|
|
115
|
-
supportsInlineActions: false
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
if (process.env.OPENCODE_INSTANCE) {
|
|
119
|
-
return {
|
|
120
|
-
name: "OpenCode",
|
|
121
|
-
preferredOutputFormat: "plain-text",
|
|
122
|
-
supportsDiffs: false,
|
|
123
|
-
supportsInlineActions: false
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
if (process.env.VSCODE_PID || process.env.VSCODE_IPC_HOOK) {
|
|
127
|
-
return {
|
|
128
|
-
name: "VS Code",
|
|
129
|
-
preferredOutputFormat: "markdown",
|
|
130
|
-
supportsDiffs: true,
|
|
131
|
-
supportsInlineActions: false
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
const parentProcess = process.env.PARENT_PROCESS_NAME || process.env._ || "";
|
|
135
|
-
if (parentProcess.toLowerCase().includes("claude")) {
|
|
136
|
-
return {
|
|
137
|
-
name: "Claude Code",
|
|
138
|
-
preferredOutputFormat: "markdown",
|
|
139
|
-
supportsDiffs: true,
|
|
140
|
-
supportsInlineActions: true
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
if (parentProcess.toLowerCase().includes("cursor")) {
|
|
144
|
-
return {
|
|
145
|
-
name: "Cursor",
|
|
146
|
-
preferredOutputFormat: "rich-text",
|
|
147
|
-
supportsDiffs: true,
|
|
148
|
-
supportsInlineActions: false
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
if (process.env.npm_execpath || process.argv[1]?.includes("npx")) {
|
|
152
|
-
return {
|
|
153
|
-
name: "MCP Client",
|
|
154
|
-
preferredOutputFormat: "markdown",
|
|
155
|
-
supportsDiffs: true,
|
|
156
|
-
supportsInlineActions: false
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
name: "MCP Client",
|
|
161
|
-
preferredOutputFormat: "markdown",
|
|
162
|
-
supportsDiffs: true,
|
|
163
|
-
supportsInlineActions: false
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// src/tools/test.ts
|
|
168
|
-
import { readFile } from "fs/promises";
|
|
169
|
-
import { existsSync } from "fs";
|
|
170
|
-
import { extname, relative, resolve, isAbsolute, dirname, basename, join } from "path";
|
|
171
|
-
var TrieTestTool = class {
|
|
172
|
-
async execute(args) {
|
|
173
|
-
const { action, files, framework, style = "unit" } = args || {};
|
|
174
|
-
if (!action || !files || files.length === 0) {
|
|
175
|
-
return {
|
|
176
|
-
content: [{
|
|
177
|
-
type: "text",
|
|
178
|
-
text: this.getHelpText()
|
|
179
|
-
}]
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
switch (action) {
|
|
183
|
-
case "generate":
|
|
184
|
-
return this.generateTests(files, framework, style);
|
|
185
|
-
case "coverage":
|
|
186
|
-
return this.analyzeCoverage(files);
|
|
187
|
-
case "suggest":
|
|
188
|
-
return this.suggestTests(files);
|
|
189
|
-
case "run":
|
|
190
|
-
return this.runTestsInfo(files);
|
|
191
|
-
default:
|
|
192
|
-
return {
|
|
193
|
-
content: [{
|
|
194
|
-
type: "text",
|
|
195
|
-
text: `Unknown action: ${action}`
|
|
196
|
-
}]
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
async generateTests(files, framework, style) {
|
|
201
|
-
const detectedFramework = framework || await this.detectTestFramework();
|
|
202
|
-
let output = `
|
|
203
|
-
${"\u2501".repeat(60)}
|
|
204
|
-
`;
|
|
205
|
-
output += `\u{1F9EA} TEST GENERATION
|
|
206
|
-
`;
|
|
207
|
-
output += `${"\u2501".repeat(60)}
|
|
208
|
-
|
|
209
|
-
`;
|
|
210
|
-
output += `## \u2699\uFE0F Configuration
|
|
211
|
-
|
|
212
|
-
`;
|
|
213
|
-
output += `- **Framework:** ${detectedFramework}
|
|
214
|
-
`;
|
|
215
|
-
output += `- **Style:** ${style}
|
|
216
|
-
`;
|
|
217
|
-
output += `- **Files:** ${files.length}
|
|
218
|
-
|
|
219
|
-
`;
|
|
220
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
221
|
-
const allUnits = [];
|
|
222
|
-
for (const file of files) {
|
|
223
|
-
const resolvedPath = isAbsolute(file) ? file : resolve(workDir, file);
|
|
224
|
-
if (!existsSync(resolvedPath)) {
|
|
225
|
-
output += `[!] File not found: ${file}
|
|
226
|
-
`;
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
const code = await readFile(resolvedPath, "utf-8");
|
|
230
|
-
const language = this.detectLanguage(resolvedPath);
|
|
231
|
-
const relativePath = relative(workDir, resolvedPath);
|
|
232
|
-
const units = this.extractTestableUnits(code, language);
|
|
233
|
-
allUnits.push({ file: relativePath, units });
|
|
234
|
-
output += `### \u{1F4C4} ${relativePath}
|
|
235
|
-
|
|
236
|
-
`;
|
|
237
|
-
output += `Found **${units.length}** testable units:
|
|
238
|
-
|
|
239
|
-
`;
|
|
240
|
-
for (const unit of units) {
|
|
241
|
-
const complexityIcon = unit.complexity > 10 ? "\u{1F534}" : unit.complexity > 5 ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
242
|
-
output += `- ${complexityIcon} \`${unit.name}\` (${unit.type}, complexity: ${unit.complexity})
|
|
243
|
-
`;
|
|
244
|
-
if (unit.dependencies.length > 0) {
|
|
245
|
-
output += ` - Dependencies: ${unit.dependencies.join(", ")}
|
|
246
|
-
`;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
output += "\n";
|
|
250
|
-
}
|
|
251
|
-
output += `${"\u2500".repeat(60)}
|
|
252
|
-
`;
|
|
253
|
-
output += `## \u{1F9E0} Test Generation Request
|
|
254
|
-
|
|
255
|
-
`;
|
|
256
|
-
const systemPrompt = getSystemPrompt("test");
|
|
257
|
-
output += `**Role:** ${systemPrompt.split("\n")[0]}
|
|
258
|
-
|
|
259
|
-
`;
|
|
260
|
-
for (const { file, units } of allUnits) {
|
|
261
|
-
if (units.length === 0) continue;
|
|
262
|
-
const code = await readFile(resolve(workDir, file), "utf-8");
|
|
263
|
-
const language = this.detectLanguage(file);
|
|
264
|
-
const prompt = getPrompt("test", "generate", {
|
|
265
|
-
code,
|
|
266
|
-
language,
|
|
267
|
-
filePath: file,
|
|
268
|
-
framework: detectedFramework
|
|
269
|
-
});
|
|
270
|
-
output += `### Tests for ${file}
|
|
271
|
-
|
|
272
|
-
`;
|
|
273
|
-
output += prompt;
|
|
274
|
-
output += "\n\n";
|
|
275
|
-
}
|
|
276
|
-
output += `${"\u2500".repeat(60)}
|
|
277
|
-
`;
|
|
278
|
-
output += `
|
|
279
|
-
## \u{1F4DD} Expected Test Output
|
|
280
|
-
|
|
281
|
-
`;
|
|
282
|
-
output += `Generate complete test files with:
|
|
283
|
-
`;
|
|
284
|
-
output += `- All imports and setup
|
|
285
|
-
`;
|
|
286
|
-
output += `- Tests for each function/method
|
|
287
|
-
`;
|
|
288
|
-
output += `- Edge cases and error scenarios
|
|
289
|
-
`;
|
|
290
|
-
output += `- Mock requirements clearly stated
|
|
291
|
-
`;
|
|
292
|
-
output += `- Ready to copy and run
|
|
293
|
-
`;
|
|
294
|
-
return { content: [{ type: "text", text: output }] };
|
|
295
|
-
}
|
|
296
|
-
async analyzeCoverage(files) {
|
|
297
|
-
let output = `
|
|
298
|
-
${"\u2501".repeat(60)}
|
|
299
|
-
`;
|
|
300
|
-
output += `\u{1F4CA} COVERAGE ANALYSIS
|
|
301
|
-
`;
|
|
302
|
-
output += `${"\u2501".repeat(60)}
|
|
303
|
-
|
|
304
|
-
`;
|
|
305
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
306
|
-
for (const file of files) {
|
|
307
|
-
const resolvedPath = isAbsolute(file) ? file : resolve(workDir, file);
|
|
308
|
-
if (!existsSync(resolvedPath)) {
|
|
309
|
-
output += `[!] File not found: ${file}
|
|
310
|
-
`;
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
const code = await readFile(resolvedPath, "utf-8");
|
|
314
|
-
const language = this.detectLanguage(resolvedPath);
|
|
315
|
-
const relativePath = relative(workDir, resolvedPath);
|
|
316
|
-
const units = this.extractTestableUnits(code, language);
|
|
317
|
-
const testFile = await this.findTestFile(resolvedPath);
|
|
318
|
-
let testCode = "";
|
|
319
|
-
let testedUnits = [];
|
|
320
|
-
if (testFile) {
|
|
321
|
-
testCode = await readFile(testFile, "utf-8");
|
|
322
|
-
testedUnits = this.findTestedUnits(testCode, units.map((u) => u.name));
|
|
323
|
-
}
|
|
324
|
-
const coverage = units.length > 0 ? Math.round(testedUnits.length / units.length * 100) : 0;
|
|
325
|
-
const coverageIcon = coverage >= 80 ? "\u{1F7E2}" : coverage >= 50 ? "\u{1F7E1}" : "\u{1F534}";
|
|
326
|
-
output += `### \u{1F4C4} ${relativePath}
|
|
327
|
-
|
|
328
|
-
`;
|
|
329
|
-
output += `**Coverage:** ${coverageIcon} ${coverage}% (${testedUnits.length}/${units.length} units)
|
|
330
|
-
`;
|
|
331
|
-
if (testFile) {
|
|
332
|
-
output += `**Test file:** \`${relative(workDir, testFile)}\`
|
|
333
|
-
`;
|
|
334
|
-
} else {
|
|
335
|
-
output += `**Test file:** \u274C Not found
|
|
336
|
-
`;
|
|
337
|
-
}
|
|
338
|
-
output += "\n";
|
|
339
|
-
const untested = units.filter((u) => !testedUnits.includes(u.name));
|
|
340
|
-
if (untested.length > 0) {
|
|
341
|
-
output += `**Missing Tests:**
|
|
342
|
-
`;
|
|
343
|
-
for (const unit of untested) {
|
|
344
|
-
const priority = unit.complexity > 5 ? "\u{1F534} HIGH" : "\u{1F7E1} MEDIUM";
|
|
345
|
-
output += `- ${priority}: \`${unit.name}\` (${unit.type})
|
|
346
|
-
`;
|
|
347
|
-
}
|
|
348
|
-
output += "\n";
|
|
349
|
-
}
|
|
350
|
-
if (testedUnits.length > 0) {
|
|
351
|
-
output += `**Tested:**
|
|
352
|
-
`;
|
|
353
|
-
for (const name of testedUnits) {
|
|
354
|
-
output += `- \u2705 \`${name}\`
|
|
355
|
-
`;
|
|
356
|
-
}
|
|
357
|
-
output += "\n";
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
output += `${"\u2500".repeat(60)}
|
|
361
|
-
`;
|
|
362
|
-
output += `## \u{1F4CB} Recommendations
|
|
363
|
-
|
|
364
|
-
`;
|
|
365
|
-
output += `To improve coverage:
|
|
366
|
-
`;
|
|
367
|
-
output += `1. Focus on high-complexity untested functions first
|
|
368
|
-
`;
|
|
369
|
-
output += `2. Add edge case tests for existing coverage
|
|
370
|
-
`;
|
|
371
|
-
output += `3. Consider integration tests for complex interactions
|
|
372
|
-
`;
|
|
373
|
-
output += `
|
|
374
|
-
Run \`trie_test action:generate files:["file.ts"]\` to generate missing tests.
|
|
375
|
-
`;
|
|
376
|
-
return { content: [{ type: "text", text: output }] };
|
|
377
|
-
}
|
|
378
|
-
async suggestTests(files) {
|
|
379
|
-
let output = `
|
|
380
|
-
${"\u2501".repeat(60)}
|
|
381
|
-
`;
|
|
382
|
-
output += `\u{1F4A1} TEST SUGGESTIONS
|
|
383
|
-
`;
|
|
384
|
-
output += `${"\u2501".repeat(60)}
|
|
385
|
-
|
|
386
|
-
`;
|
|
387
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
388
|
-
for (const file of files) {
|
|
389
|
-
const resolvedPath = isAbsolute(file) ? file : resolve(workDir, file);
|
|
390
|
-
if (!existsSync(resolvedPath)) {
|
|
391
|
-
output += `[!] File not found: ${file}
|
|
392
|
-
`;
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
const code = await readFile(resolvedPath, "utf-8");
|
|
396
|
-
const relativePath = relative(workDir, resolvedPath);
|
|
397
|
-
const patterns = this.detectTestablePatterns(code);
|
|
398
|
-
output += `### \u{1F4C4} ${relativePath}
|
|
399
|
-
|
|
400
|
-
`;
|
|
401
|
-
if (patterns.length === 0) {
|
|
402
|
-
output += `No specific test suggestions for this file.
|
|
403
|
-
|
|
404
|
-
`;
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
output += `| Priority | Pattern | Suggested Test |
|
|
408
|
-
`;
|
|
409
|
-
output += `|----------|---------|----------------|
|
|
410
|
-
`;
|
|
411
|
-
for (const pattern of patterns) {
|
|
412
|
-
output += `| ${pattern.priority} | ${pattern.name} | ${pattern.suggestion} |
|
|
413
|
-
`;
|
|
414
|
-
}
|
|
415
|
-
output += "\n";
|
|
416
|
-
}
|
|
417
|
-
return { content: [{ type: "text", text: output }] };
|
|
418
|
-
}
|
|
419
|
-
async runTestsInfo(files) {
|
|
420
|
-
const framework = await this.detectTestFramework();
|
|
421
|
-
let output = `
|
|
422
|
-
${"\u2501".repeat(60)}
|
|
423
|
-
`;
|
|
424
|
-
output += `RUN TESTS
|
|
425
|
-
`;
|
|
426
|
-
output += `${"\u2501".repeat(60)}
|
|
427
|
-
|
|
428
|
-
`;
|
|
429
|
-
output += `## Detected Configuration
|
|
430
|
-
|
|
431
|
-
`;
|
|
432
|
-
output += `- **Framework:** ${framework}
|
|
433
|
-
`;
|
|
434
|
-
output += `- **Files:** ${files.join(", ")}
|
|
435
|
-
|
|
436
|
-
`;
|
|
437
|
-
output += `## Commands
|
|
438
|
-
|
|
439
|
-
`;
|
|
440
|
-
switch (framework) {
|
|
441
|
-
case "jest":
|
|
442
|
-
output += `\`\`\`bash
|
|
443
|
-
`;
|
|
444
|
-
output += `# Run all tests
|
|
445
|
-
`;
|
|
446
|
-
output += `npx jest
|
|
447
|
-
|
|
448
|
-
`;
|
|
449
|
-
output += `# Run specific files
|
|
450
|
-
`;
|
|
451
|
-
output += `npx jest ${files.join(" ")}
|
|
452
|
-
|
|
453
|
-
`;
|
|
454
|
-
output += `# Run with coverage
|
|
455
|
-
`;
|
|
456
|
-
output += `npx jest --coverage
|
|
457
|
-
`;
|
|
458
|
-
output += `\`\`\`
|
|
459
|
-
`;
|
|
460
|
-
break;
|
|
461
|
-
case "vitest":
|
|
462
|
-
output += `\`\`\`bash
|
|
463
|
-
`;
|
|
464
|
-
output += `# Run all tests
|
|
465
|
-
`;
|
|
466
|
-
output += `npx vitest run
|
|
467
|
-
|
|
468
|
-
`;
|
|
469
|
-
output += `# Run specific files
|
|
470
|
-
`;
|
|
471
|
-
output += `npx vitest run ${files.join(" ")}
|
|
472
|
-
|
|
473
|
-
`;
|
|
474
|
-
output += `# Run with coverage
|
|
475
|
-
`;
|
|
476
|
-
output += `npx vitest run --coverage
|
|
477
|
-
`;
|
|
478
|
-
output += `\`\`\`
|
|
479
|
-
`;
|
|
480
|
-
break;
|
|
481
|
-
case "pytest":
|
|
482
|
-
output += `\`\`\`bash
|
|
483
|
-
`;
|
|
484
|
-
output += `# Run all tests
|
|
485
|
-
`;
|
|
486
|
-
output += `pytest
|
|
487
|
-
|
|
488
|
-
`;
|
|
489
|
-
output += `# Run specific files
|
|
490
|
-
`;
|
|
491
|
-
output += `pytest ${files.join(" ")}
|
|
492
|
-
|
|
493
|
-
`;
|
|
494
|
-
output += `# Run with coverage
|
|
495
|
-
`;
|
|
496
|
-
output += `pytest --cov
|
|
497
|
-
`;
|
|
498
|
-
output += `\`\`\`
|
|
499
|
-
`;
|
|
500
|
-
break;
|
|
501
|
-
default:
|
|
502
|
-
output += `Run your test framework with the specified files.
|
|
503
|
-
`;
|
|
504
|
-
}
|
|
505
|
-
output += `
|
|
506
|
-
*Note: This tool provides test commands but doesn't execute them directly.*
|
|
507
|
-
`;
|
|
508
|
-
return { content: [{ type: "text", text: output }] };
|
|
509
|
-
}
|
|
510
|
-
extractTestableUnits(code, language) {
|
|
511
|
-
const units = [];
|
|
512
|
-
const lines = code.split("\n");
|
|
513
|
-
for (let i = 0; i < lines.length; i++) {
|
|
514
|
-
const line = lines[i] || "";
|
|
515
|
-
const funcMatch = line.match(/(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/);
|
|
516
|
-
if (funcMatch) {
|
|
517
|
-
const endLine = this.findBlockEnd(lines, i);
|
|
518
|
-
const body = lines.slice(i, endLine + 1).join("\n");
|
|
519
|
-
units.push({
|
|
520
|
-
name: funcMatch[1],
|
|
521
|
-
type: "function",
|
|
522
|
-
startLine: i + 1,
|
|
523
|
-
endLine: endLine + 1,
|
|
524
|
-
signature: `${funcMatch[1]}(${funcMatch[2]})`,
|
|
525
|
-
complexity: this.calculateComplexity(body),
|
|
526
|
-
dependencies: this.extractDependencies(body)
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
const arrowMatch = line.match(/(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*(?:=>|:)/);
|
|
530
|
-
if (arrowMatch) {
|
|
531
|
-
const endLine = this.findBlockEnd(lines, i);
|
|
532
|
-
const body = lines.slice(i, endLine + 1).join("\n");
|
|
533
|
-
units.push({
|
|
534
|
-
name: arrowMatch[1],
|
|
535
|
-
type: "function",
|
|
536
|
-
startLine: i + 1,
|
|
537
|
-
endLine: endLine + 1,
|
|
538
|
-
signature: arrowMatch[1],
|
|
539
|
-
complexity: this.calculateComplexity(body),
|
|
540
|
-
dependencies: this.extractDependencies(body)
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
const classMatch = line.match(/(?:export\s+)?class\s+(\w+)/);
|
|
544
|
-
if (classMatch) {
|
|
545
|
-
const endLine = this.findBlockEnd(lines, i);
|
|
546
|
-
units.push({
|
|
547
|
-
name: classMatch[1],
|
|
548
|
-
type: "class",
|
|
549
|
-
startLine: i + 1,
|
|
550
|
-
endLine: endLine + 1,
|
|
551
|
-
signature: `class ${classMatch[1]}`,
|
|
552
|
-
complexity: this.calculateComplexity(lines.slice(i, endLine + 1).join("\n")),
|
|
553
|
-
dependencies: []
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
const componentMatch = line.match(/(?:export\s+)?(?:const|function)\s+([A-Z]\w+)\s*[=:]/);
|
|
557
|
-
if (componentMatch && /jsx|tsx/.test(language)) {
|
|
558
|
-
const endLine = this.findBlockEnd(lines, i);
|
|
559
|
-
units.push({
|
|
560
|
-
name: componentMatch[1],
|
|
561
|
-
type: "component",
|
|
562
|
-
startLine: i + 1,
|
|
563
|
-
endLine: endLine + 1,
|
|
564
|
-
signature: componentMatch[1],
|
|
565
|
-
complexity: this.calculateComplexity(lines.slice(i, endLine + 1).join("\n")),
|
|
566
|
-
dependencies: this.extractDependencies(lines.slice(i, endLine + 1).join("\n"))
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
return units;
|
|
571
|
-
}
|
|
572
|
-
findBlockEnd(lines, startLine) {
|
|
573
|
-
let braceCount = 0;
|
|
574
|
-
let started = false;
|
|
575
|
-
for (let i = startLine; i < lines.length; i++) {
|
|
576
|
-
const line = lines[i] || "";
|
|
577
|
-
for (const char of line) {
|
|
578
|
-
if (char === "{") {
|
|
579
|
-
braceCount++;
|
|
580
|
-
started = true;
|
|
581
|
-
} else if (char === "}") {
|
|
582
|
-
braceCount--;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
if (started && braceCount === 0) {
|
|
586
|
-
return i;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
return lines.length - 1;
|
|
590
|
-
}
|
|
591
|
-
calculateComplexity(code) {
|
|
592
|
-
let complexity = 1;
|
|
593
|
-
const patterns = [
|
|
594
|
-
/if\s*\(/g,
|
|
595
|
-
/else\s+if/g,
|
|
596
|
-
/\?\s*[^:]/g,
|
|
597
|
-
// Conditionals
|
|
598
|
-
/for\s*\(/g,
|
|
599
|
-
/while\s*\(/g,
|
|
600
|
-
/do\s*\{/g,
|
|
601
|
-
// Loops
|
|
602
|
-
/catch\s*\(/g,
|
|
603
|
-
// Error handling
|
|
604
|
-
/&&/g,
|
|
605
|
-
/\|\|/g,
|
|
606
|
-
// Logical operators
|
|
607
|
-
/case\s+/g
|
|
608
|
-
// Switch cases
|
|
609
|
-
];
|
|
610
|
-
for (const pattern of patterns) {
|
|
611
|
-
const matches = code.match(pattern);
|
|
612
|
-
if (matches) {
|
|
613
|
-
complexity += matches.length;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
return complexity;
|
|
617
|
-
}
|
|
618
|
-
extractDependencies(code) {
|
|
619
|
-
const deps = [];
|
|
620
|
-
const calls = code.match(/\b([a-z]\w+)\s*\(/gi) || [];
|
|
621
|
-
const builtins = ["if", "for", "while", "switch", "catch", "function", "return", "throw", "new", "await", "async"];
|
|
622
|
-
for (const call of calls) {
|
|
623
|
-
const name = call.replace(/\s*\($/, "");
|
|
624
|
-
if (!builtins.includes(name.toLowerCase()) && !deps.includes(name)) {
|
|
625
|
-
deps.push(name);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
return deps.slice(0, 10);
|
|
629
|
-
}
|
|
630
|
-
async findTestFile(sourcePath) {
|
|
631
|
-
const dir = dirname(sourcePath);
|
|
632
|
-
const base = basename(sourcePath, extname(sourcePath));
|
|
633
|
-
const ext = extname(sourcePath);
|
|
634
|
-
const patterns = [
|
|
635
|
-
`${base}.test${ext}`,
|
|
636
|
-
`${base}.spec${ext}`,
|
|
637
|
-
`${base}_test${ext}`,
|
|
638
|
-
`test_${base}${ext}`
|
|
639
|
-
];
|
|
640
|
-
for (const pattern of patterns) {
|
|
641
|
-
const testPath = join(dir, pattern);
|
|
642
|
-
if (existsSync(testPath)) {
|
|
643
|
-
return testPath;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
const testsDir = join(dir, "__tests__");
|
|
647
|
-
if (existsSync(testsDir)) {
|
|
648
|
-
for (const pattern of patterns) {
|
|
649
|
-
const testPath = join(testsDir, pattern);
|
|
650
|
-
if (existsSync(testPath)) {
|
|
651
|
-
return testPath;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
return null;
|
|
656
|
-
}
|
|
657
|
-
findTestedUnits(testCode, unitNames) {
|
|
658
|
-
const tested = [];
|
|
659
|
-
for (const name of unitNames) {
|
|
660
|
-
const patterns = [
|
|
661
|
-
new RegExp(`describe\\s*\\([^)]*${name}`, "i"),
|
|
662
|
-
new RegExp(`it\\s*\\([^)]*${name}`, "i"),
|
|
663
|
-
new RegExp(`test\\s*\\([^)]*${name}`, "i"),
|
|
664
|
-
new RegExp(`expect\\s*\\([^)]*${name}`, "i")
|
|
665
|
-
];
|
|
666
|
-
for (const pattern of patterns) {
|
|
667
|
-
if (pattern.test(testCode)) {
|
|
668
|
-
tested.push(name);
|
|
669
|
-
break;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
return tested;
|
|
674
|
-
}
|
|
675
|
-
detectTestablePatterns(code) {
|
|
676
|
-
const patterns = [];
|
|
677
|
-
const checks = [
|
|
678
|
-
{ pattern: /async\s+function|await\s+/i, priority: "\u{1F534} HIGH", name: "Async code", suggestion: "Test async flows and error handling" },
|
|
679
|
-
{ pattern: /try\s*{[^}]*catch/i, priority: "\u{1F534} HIGH", name: "Error handling", suggestion: "Test both success and error paths" },
|
|
680
|
-
{ pattern: /if\s*\([^)]*&&[^)]*\)/i, priority: "\u{1F7E1} MED", name: "Complex conditionals", suggestion: "Test all condition branches" },
|
|
681
|
-
{ pattern: /\.map\(|\.filter\(|\.reduce\(/i, priority: "\u{1F7E1} MED", name: "Array operations", suggestion: "Test with empty, single, multiple items" },
|
|
682
|
-
{ pattern: /fetch\(|axios\.|request\(/i, priority: "\u{1F534} HIGH", name: "HTTP requests", suggestion: "Mock API calls, test error responses" },
|
|
683
|
-
{ pattern: /localStorage|sessionStorage/i, priority: "\u{1F7E1} MED", name: "Storage usage", suggestion: "Mock storage, test read/write" },
|
|
684
|
-
{ pattern: /setTimeout|setInterval/i, priority: "\u{1F7E1} MED", name: "Timers", suggestion: "Use fake timers in tests" },
|
|
685
|
-
{ pattern: /useState|useEffect|useCallback/i, priority: "\u{1F534} HIGH", name: "React hooks", suggestion: "Test hook behavior and updates" },
|
|
686
|
-
{ pattern: /form|input|submit/i, priority: "\u{1F7E1} MED", name: "Form handling", suggestion: "Test validation and submission" }
|
|
687
|
-
];
|
|
688
|
-
for (const { pattern, priority, name, suggestion } of checks) {
|
|
689
|
-
if (pattern.test(code)) {
|
|
690
|
-
patterns.push({ priority, name, suggestion });
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
return patterns;
|
|
694
|
-
}
|
|
695
|
-
async detectTestFramework() {
|
|
696
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
697
|
-
const packagePath = resolve(workDir, "package.json");
|
|
698
|
-
if (existsSync(packagePath)) {
|
|
699
|
-
try {
|
|
700
|
-
const pkg = JSON.parse(await readFile(packagePath, "utf-8"));
|
|
701
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
702
|
-
if (deps.vitest) return "vitest";
|
|
703
|
-
if (deps.jest) return "jest";
|
|
704
|
-
if (deps.mocha) return "mocha";
|
|
705
|
-
} catch {
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
if (existsSync(resolve(workDir, "pytest.ini")) || existsSync(resolve(workDir, "pyproject.toml"))) {
|
|
709
|
-
return "pytest";
|
|
710
|
-
}
|
|
711
|
-
return "jest";
|
|
712
|
-
}
|
|
713
|
-
detectLanguage(filePath) {
|
|
714
|
-
const ext = extname(filePath).toLowerCase();
|
|
715
|
-
const langMap = {
|
|
716
|
-
".ts": "typescript",
|
|
717
|
-
".tsx": "tsx",
|
|
718
|
-
".js": "javascript",
|
|
719
|
-
".jsx": "jsx",
|
|
720
|
-
".py": "python",
|
|
721
|
-
".go": "go",
|
|
722
|
-
".rs": "rust"
|
|
723
|
-
};
|
|
724
|
-
return langMap[ext] || "javascript";
|
|
725
|
-
}
|
|
726
|
-
getHelpText() {
|
|
727
|
-
return `
|
|
728
|
-
${"\u2501".repeat(60)}
|
|
729
|
-
\u{1F9EA} TRIE TEST - AI-POWERED TEST GENERATION
|
|
730
|
-
${"\u2501".repeat(60)}
|
|
731
|
-
|
|
732
|
-
## Usage
|
|
733
|
-
|
|
734
|
-
### Generate tests:
|
|
735
|
-
\`\`\`
|
|
736
|
-
trie_test action:"generate" files:["src/utils.ts"]
|
|
737
|
-
\`\`\`
|
|
738
|
-
|
|
739
|
-
### Analyze coverage:
|
|
740
|
-
\`\`\`
|
|
741
|
-
trie_test action:"coverage" files:["src/utils.ts"]
|
|
742
|
-
\`\`\`
|
|
743
|
-
|
|
744
|
-
### Get test suggestions:
|
|
745
|
-
\`\`\`
|
|
746
|
-
trie_test action:"suggest" files:["src/app.ts"]
|
|
747
|
-
\`\`\`
|
|
748
|
-
|
|
749
|
-
### Get run commands:
|
|
750
|
-
\`\`\`
|
|
751
|
-
trie_test action:"run" files:["src/utils.test.ts"]
|
|
752
|
-
\`\`\`
|
|
753
|
-
|
|
754
|
-
## Options
|
|
755
|
-
|
|
756
|
-
| Option | Values | Description |
|
|
757
|
-
|--------|--------|-------------|
|
|
758
|
-
| action | generate, coverage, suggest, run | What to do |
|
|
759
|
-
| files | Array of paths | Files to analyze |
|
|
760
|
-
| framework | jest, vitest, mocha, pytest | Test framework |
|
|
761
|
-
| style | unit, integration, e2e, all | Test style |
|
|
762
|
-
`;
|
|
763
|
-
}
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
// src/tools/pr-review.ts
|
|
767
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
768
|
-
import { existsSync as existsSync2 } from "fs";
|
|
769
|
-
import { join as join2, basename as basename2, resolve as resolve2, isAbsolute as isAbsolute2 } from "path";
|
|
770
|
-
var TriePRReviewTool = class {
|
|
771
|
-
// Review workflow is now ledger-driven; keep a lightweight built-in checklist.
|
|
772
|
-
CRITICAL_REVIEW_CHECKLIST = {
|
|
773
|
-
stateAndLifecycle: [
|
|
774
|
-
"Uninitialized state accessed before setup",
|
|
775
|
-
"Missing cleanup on unmount/dispose",
|
|
776
|
-
"State mutations in wrong lifecycle phase"
|
|
777
|
-
],
|
|
778
|
-
edgeCasesAndRaces: [
|
|
779
|
-
"Race conditions in async operations",
|
|
780
|
-
"Missing error handling for edge cases",
|
|
781
|
-
"Unhandled promise rejections"
|
|
782
|
-
],
|
|
783
|
-
missingPieces: [
|
|
784
|
-
"Missing input validation",
|
|
785
|
-
"Missing error handling",
|
|
786
|
-
"Missing logging/monitoring"
|
|
787
|
-
]
|
|
788
|
-
};
|
|
789
|
-
exec(command, cwd, maxBuffer) {
|
|
790
|
-
const opts = {
|
|
791
|
-
captureOutput: false,
|
|
792
|
-
redactOutput: true,
|
|
793
|
-
...cwd ? { cwd } : {},
|
|
794
|
-
...maxBuffer ? { maxBuffer } : {}
|
|
795
|
-
};
|
|
796
|
-
const { stdout } = runShellCommandSync(
|
|
797
|
-
command,
|
|
798
|
-
{ actor: "tool:pr-review", triggeredBy: "manual", targetPath: cwd ?? getWorkingDirectory(void 0, true) },
|
|
799
|
-
opts
|
|
800
|
-
);
|
|
801
|
-
return stdout.trimEnd();
|
|
802
|
-
}
|
|
803
|
-
async execute(args) {
|
|
804
|
-
const {
|
|
805
|
-
pr,
|
|
806
|
-
worktree,
|
|
807
|
-
mode,
|
|
808
|
-
files: _specificFiles
|
|
809
|
-
// Reserved for future file filtering
|
|
810
|
-
} = args;
|
|
811
|
-
try {
|
|
812
|
-
const prInfo = await this.getPRInfo(pr, worktree);
|
|
813
|
-
if (!prInfo.success) {
|
|
814
|
-
return {
|
|
815
|
-
content: [{
|
|
816
|
-
type: "text",
|
|
817
|
-
text: `${prInfo.error}
|
|
818
|
-
|
|
819
|
-
Usage:
|
|
820
|
-
- \`pr_review 12345\` \u2014 review specific PR
|
|
821
|
-
- \`pr_review\` \u2014 review PR for current branch
|
|
822
|
-
- \`pr_review ../worktree\` \u2014 review worktree changes`
|
|
823
|
-
}]
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
const changes = await this.getChanges(prInfo);
|
|
827
|
-
if (changes.files.length === 0) {
|
|
828
|
-
return {
|
|
829
|
-
content: [{
|
|
830
|
-
type: "text",
|
|
831
|
-
text: `No changes found to review.`
|
|
832
|
-
}]
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
const designDocs = await this.findDesignDocs(changes.files, prInfo);
|
|
836
|
-
const workflow = this.buildReviewWorkflow(changes.files, {
|
|
837
|
-
...prInfo.number ? { prNumber: prInfo.number } : {},
|
|
838
|
-
...prInfo.title ? { prTitle: prInfo.title } : {},
|
|
839
|
-
...prInfo.author ? { prAuthor: prInfo.author } : {},
|
|
840
|
-
...prInfo.baseBranch ? { baseBranch: prInfo.baseBranch } : {},
|
|
841
|
-
...prInfo.headBranch ? { headBranch: prInfo.headBranch } : {},
|
|
842
|
-
mode: mode || "own",
|
|
843
|
-
designDocs
|
|
844
|
-
});
|
|
845
|
-
const fileContents = await this.preloadFiles(changes.files.map((f) => f.path));
|
|
846
|
-
const reviewPrompt = this.generateReviewPrompt(workflow, changes, fileContents);
|
|
847
|
-
return {
|
|
848
|
-
content: [{
|
|
849
|
-
type: "text",
|
|
850
|
-
text: reviewPrompt
|
|
851
|
-
}]
|
|
852
|
-
};
|
|
853
|
-
} catch (error) {
|
|
854
|
-
return {
|
|
855
|
-
content: [{
|
|
856
|
-
type: "text",
|
|
857
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
858
|
-
}]
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
/**
|
|
863
|
-
* Get PR information from GitHub or local worktree
|
|
864
|
-
*/
|
|
865
|
-
async getPRInfo(pr, worktree) {
|
|
866
|
-
if (worktree) {
|
|
867
|
-
const worktreePath = isAbsolute2(worktree) ? worktree : resolve2(getWorkingDirectory(void 0, true), worktree);
|
|
868
|
-
if (!existsSync2(worktreePath)) {
|
|
869
|
-
return { success: false, error: `Worktree not found: ${worktreePath}` };
|
|
870
|
-
}
|
|
871
|
-
return {
|
|
872
|
-
success: true,
|
|
873
|
-
type: "worktree",
|
|
874
|
-
path: worktreePath,
|
|
875
|
-
title: `Local changes in ${basename2(worktreePath)}`,
|
|
876
|
-
author: this.getGitUser(),
|
|
877
|
-
baseBranch: "HEAD~1",
|
|
878
|
-
headBranch: "HEAD"
|
|
879
|
-
};
|
|
880
|
-
}
|
|
881
|
-
if (pr) {
|
|
882
|
-
return this.getPRFromGitHub(pr);
|
|
883
|
-
}
|
|
884
|
-
try {
|
|
885
|
-
this.exec("git branch --show-current");
|
|
886
|
-
const prJson = this.exec(
|
|
887
|
-
`gh pr view --json number,title,author,headRefName,baseRefName`
|
|
888
|
-
);
|
|
889
|
-
const prData = JSON.parse(prJson);
|
|
890
|
-
return {
|
|
891
|
-
success: true,
|
|
892
|
-
type: "github",
|
|
893
|
-
number: String(prData.number),
|
|
894
|
-
title: prData.title,
|
|
895
|
-
author: prData.author?.login || "unknown",
|
|
896
|
-
baseBranch: prData.baseRefName,
|
|
897
|
-
headBranch: prData.headRefName
|
|
898
|
-
};
|
|
899
|
-
} catch {
|
|
900
|
-
return {
|
|
901
|
-
success: true,
|
|
902
|
-
type: "local",
|
|
903
|
-
title: "Local uncommitted changes",
|
|
904
|
-
author: this.getGitUser(),
|
|
905
|
-
baseBranch: "HEAD",
|
|
906
|
-
headBranch: "working tree"
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
/**
|
|
911
|
-
* Get PR info from GitHub CLI
|
|
912
|
-
*/
|
|
913
|
-
async getPRFromGitHub(prNumber) {
|
|
914
|
-
try {
|
|
915
|
-
const prJson = this.exec(
|
|
916
|
-
`gh pr view ${prNumber} --json number,title,author,headRefName,baseRefName`
|
|
917
|
-
);
|
|
918
|
-
const prData = JSON.parse(prJson);
|
|
919
|
-
return {
|
|
920
|
-
success: true,
|
|
921
|
-
type: "github",
|
|
922
|
-
number: String(prData.number),
|
|
923
|
-
title: prData.title,
|
|
924
|
-
author: prData.author?.login || "unknown",
|
|
925
|
-
baseBranch: prData.baseRefName,
|
|
926
|
-
headBranch: prData.headRefName
|
|
927
|
-
};
|
|
928
|
-
} catch (error) {
|
|
929
|
-
return {
|
|
930
|
-
success: false,
|
|
931
|
-
error: `Failed to get PR #${prNumber}. Is \`gh\` CLI installed and authenticated?`
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
/**
|
|
936
|
-
* Get changes (diff) for the PR or local changes
|
|
937
|
-
*/
|
|
938
|
-
async getChanges(prInfo) {
|
|
939
|
-
let diffOutput;
|
|
940
|
-
try {
|
|
941
|
-
if (prInfo.type === "github" && prInfo.number) {
|
|
942
|
-
diffOutput = this.exec(`gh pr diff ${prInfo.number}`, void 0, 50 * 1024 * 1024);
|
|
943
|
-
} else if (prInfo.type === "worktree" && prInfo.path) {
|
|
944
|
-
diffOutput = this.exec(`git diff HEAD`, prInfo.path, 50 * 1024 * 1024);
|
|
945
|
-
} else {
|
|
946
|
-
diffOutput = this.exec(`git diff HEAD`, void 0, 50 * 1024 * 1024);
|
|
947
|
-
if (!diffOutput.trim()) {
|
|
948
|
-
diffOutput = this.exec(`git diff --cached`, void 0, 50 * 1024 * 1024);
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
} catch {
|
|
952
|
-
diffOutput = "";
|
|
953
|
-
}
|
|
954
|
-
const files = this.parseDiff(diffOutput);
|
|
955
|
-
return { files, fullDiff: diffOutput };
|
|
956
|
-
}
|
|
957
|
-
/**
|
|
958
|
-
* Parse git diff output into structured file changes
|
|
959
|
-
*/
|
|
960
|
-
parseDiff(diffOutput) {
|
|
961
|
-
const files = [];
|
|
962
|
-
const fileDiffs = diffOutput.split(/^diff --git /m).slice(1);
|
|
963
|
-
for (const fileDiff of fileDiffs) {
|
|
964
|
-
const lines = fileDiff.split("\n");
|
|
965
|
-
const headerLine = lines[0] || "";
|
|
966
|
-
const pathMatch = headerLine.match(/a\/(.+?) b\/(.+)/);
|
|
967
|
-
if (!pathMatch || !pathMatch[2]) continue;
|
|
968
|
-
const path = pathMatch[2];
|
|
969
|
-
const diff = `diff --git ${fileDiff}`;
|
|
970
|
-
let additions = 0;
|
|
971
|
-
let deletions = 0;
|
|
972
|
-
for (const line of lines) {
|
|
973
|
-
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
974
|
-
additions++;
|
|
975
|
-
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
976
|
-
deletions++;
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
files.push({ path, diff, additions, deletions, status: "modified" });
|
|
980
|
-
}
|
|
981
|
-
return files;
|
|
982
|
-
}
|
|
983
|
-
buildReviewWorkflow(files, context) {
|
|
984
|
-
const totalAdditions = files.reduce((sum, f) => sum + (f.additions || 0), 0);
|
|
985
|
-
const totalDeletions = files.reduce((sum, f) => sum + (f.deletions || 0), 0);
|
|
986
|
-
const reviewMode = context.mode === "others" ? "others" : "own";
|
|
987
|
-
const reviewInstructions = {
|
|
988
|
-
mode: reviewMode,
|
|
989
|
-
description: reviewMode === "own" ? "Your PR \u2014 focus on footguns, missing tests, and production risks." : "Someone else's PR \u2014 focus on correctness, clarity, and maintainability."
|
|
990
|
-
};
|
|
991
|
-
const sorted = [...files].sort((a, b) => a.path.localeCompare(b.path));
|
|
992
|
-
const fileOrder = sorted.map((f, i) => ({
|
|
993
|
-
index: i + 1,
|
|
994
|
-
path: f.path,
|
|
995
|
-
reason: i === 0 ? "Start here to establish baseline context" : "Proceed in filename order"
|
|
996
|
-
}));
|
|
997
|
-
return {
|
|
998
|
-
metadata: {
|
|
999
|
-
...context.prNumber ? { prNumber: context.prNumber } : {},
|
|
1000
|
-
prTitle: context.prTitle || "PR Review",
|
|
1001
|
-
prAuthor: context.prAuthor || "unknown",
|
|
1002
|
-
baseBranch: context.baseBranch || "unknown",
|
|
1003
|
-
headBranch: context.headBranch || "unknown",
|
|
1004
|
-
totalFiles: files.length,
|
|
1005
|
-
totalAdditions,
|
|
1006
|
-
totalDeletions,
|
|
1007
|
-
designDocs: context.designDocs
|
|
1008
|
-
},
|
|
1009
|
-
fileOrder,
|
|
1010
|
-
reviewInstructions
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
/**
|
|
1014
|
-
* Find related design docs
|
|
1015
|
-
*/
|
|
1016
|
-
async findDesignDocs(files, prInfo) {
|
|
1017
|
-
const designDocs = [];
|
|
1018
|
-
const cwd = prInfo.path || getWorkingDirectory(void 0, true);
|
|
1019
|
-
for (const file of files) {
|
|
1020
|
-
if (file.path.includes("design_docs/") || file.path.includes("docs/design/") || file.path.includes("rfcs/")) {
|
|
1021
|
-
designDocs.push(file.path);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
const designDocPaths = [
|
|
1025
|
-
"design_docs",
|
|
1026
|
-
"docs/design",
|
|
1027
|
-
"docs/rfcs",
|
|
1028
|
-
"rfcs"
|
|
1029
|
-
];
|
|
1030
|
-
for (const docPath of designDocPaths) {
|
|
1031
|
-
const fullPath = join2(cwd, docPath);
|
|
1032
|
-
if (existsSync2(fullPath)) {
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
return designDocs;
|
|
1036
|
-
}
|
|
1037
|
-
/**
|
|
1038
|
-
* Pre-load all changed files for instant responses during review
|
|
1039
|
-
*/
|
|
1040
|
-
async preloadFiles(filePaths) {
|
|
1041
|
-
const contents = /* @__PURE__ */ new Map();
|
|
1042
|
-
const cwd = getWorkingDirectory(void 0, true);
|
|
1043
|
-
await Promise.all(filePaths.map(async (filePath) => {
|
|
1044
|
-
try {
|
|
1045
|
-
const fullPath = isAbsolute2(filePath) ? filePath : join2(cwd, filePath);
|
|
1046
|
-
if (existsSync2(fullPath)) {
|
|
1047
|
-
const content = await readFile2(fullPath, "utf-8");
|
|
1048
|
-
contents.set(filePath, content);
|
|
1049
|
-
}
|
|
1050
|
-
} catch {
|
|
1051
|
-
}
|
|
1052
|
-
}));
|
|
1053
|
-
return contents;
|
|
1054
|
-
}
|
|
1055
|
-
/**
|
|
1056
|
-
* Get git user name
|
|
1057
|
-
*/
|
|
1058
|
-
getGitUser() {
|
|
1059
|
-
try {
|
|
1060
|
-
return this.exec("git config user.name");
|
|
1061
|
-
} catch {
|
|
1062
|
-
return "unknown";
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
/**
|
|
1066
|
-
* Generate the full review prompt for Claude to execute
|
|
1067
|
-
*/
|
|
1068
|
-
generateReviewPrompt(workflow, changes, _fileContents) {
|
|
1069
|
-
const { metadata, fileOrder, reviewInstructions } = workflow;
|
|
1070
|
-
let prompt = `# \u{1F50D} Super Reviewer \u2014 Interactive PR Review
|
|
1071
|
-
|
|
1072
|
-
`;
|
|
1073
|
-
if (metadata.prNumber) {
|
|
1074
|
-
prompt += `## PR #${metadata.prNumber}: ${metadata.prTitle}
|
|
1075
|
-
|
|
1076
|
-
`;
|
|
1077
|
-
} else {
|
|
1078
|
-
prompt += `## ${metadata.prTitle}
|
|
1079
|
-
|
|
1080
|
-
`;
|
|
1081
|
-
}
|
|
1082
|
-
prompt += `**Author:** ${metadata.prAuthor}
|
|
1083
|
-
`;
|
|
1084
|
-
prompt += `**Branch:** \`${metadata.headBranch}\` \u2192 \`${metadata.baseBranch}\`
|
|
1085
|
-
`;
|
|
1086
|
-
prompt += `**Scope:** ${metadata.totalFiles} files, +${metadata.totalAdditions}/-${metadata.totalDeletions} lines
|
|
1087
|
-
|
|
1088
|
-
`;
|
|
1089
|
-
prompt += `---
|
|
1090
|
-
|
|
1091
|
-
`;
|
|
1092
|
-
prompt += `## Review Mode
|
|
1093
|
-
|
|
1094
|
-
`;
|
|
1095
|
-
prompt += `**Current Mode:** ${reviewInstructions.mode === "own" ? "1" : "2"}. ${reviewInstructions.description}
|
|
1096
|
-
|
|
1097
|
-
`;
|
|
1098
|
-
prompt += `> To switch modes, say "mode 1" for your own PR or "mode 2" for someone else's
|
|
1099
|
-
|
|
1100
|
-
`;
|
|
1101
|
-
prompt += `---
|
|
1102
|
-
|
|
1103
|
-
`;
|
|
1104
|
-
prompt += `## Files to Review (ordered for understanding)
|
|
1105
|
-
|
|
1106
|
-
`;
|
|
1107
|
-
prompt += `| # | File | Why this order |
|
|
1108
|
-
`;
|
|
1109
|
-
prompt += `|---|------|----------------|
|
|
1110
|
-
`;
|
|
1111
|
-
for (const file of fileOrder) {
|
|
1112
|
-
const stats = `+${changes.files.find((f) => f.path === file.path)?.additions || 0}/-${changes.files.find((f) => f.path === file.path)?.deletions || 0}`;
|
|
1113
|
-
prompt += `| ${file.index} | \`${file.path}\` (${stats}) | ${file.reason} |
|
|
1114
|
-
`;
|
|
1115
|
-
}
|
|
1116
|
-
prompt += `
|
|
1117
|
-
---
|
|
1118
|
-
|
|
1119
|
-
`;
|
|
1120
|
-
prompt += `## Critical Review Mindset
|
|
1121
|
-
|
|
1122
|
-
`;
|
|
1123
|
-
prompt += `As we go through each file, I'll actively look for:
|
|
1124
|
-
|
|
1125
|
-
`;
|
|
1126
|
-
prompt += `**State & Lifecycle:**
|
|
1127
|
-
`;
|
|
1128
|
-
for (const check of this.CRITICAL_REVIEW_CHECKLIST.stateAndLifecycle) {
|
|
1129
|
-
prompt += `- ${check}
|
|
1130
|
-
`;
|
|
1131
|
-
}
|
|
1132
|
-
prompt += `
|
|
1133
|
-
**Edge Cases & Races:**
|
|
1134
|
-
`;
|
|
1135
|
-
for (const check of this.CRITICAL_REVIEW_CHECKLIST.edgeCasesAndRaces) {
|
|
1136
|
-
prompt += `- ${check}
|
|
1137
|
-
`;
|
|
1138
|
-
}
|
|
1139
|
-
prompt += `
|
|
1140
|
-
**Missing Pieces:**
|
|
1141
|
-
`;
|
|
1142
|
-
for (const check of this.CRITICAL_REVIEW_CHECKLIST.missingPieces) {
|
|
1143
|
-
prompt += `- ${check}
|
|
1144
|
-
`;
|
|
1145
|
-
}
|
|
1146
|
-
prompt += `
|
|
1147
|
-
---
|
|
1148
|
-
|
|
1149
|
-
`;
|
|
1150
|
-
prompt += `## File Diffs (pre-loaded for instant review)
|
|
1151
|
-
|
|
1152
|
-
`;
|
|
1153
|
-
prompt += `<details>
|
|
1154
|
-
<summary>\u{1F4C1} All file diffs (click to expand)</summary>
|
|
1155
|
-
|
|
1156
|
-
`;
|
|
1157
|
-
for (const file of fileOrder) {
|
|
1158
|
-
const fileChange = changes.files.find((f) => f.path === file.path);
|
|
1159
|
-
if (fileChange) {
|
|
1160
|
-
prompt += `### ${file.path}
|
|
1161
|
-
|
|
1162
|
-
`;
|
|
1163
|
-
prompt += `\`\`\`diff
|
|
1164
|
-
${fileChange.diff}
|
|
1165
|
-
\`\`\`
|
|
1166
|
-
|
|
1167
|
-
`;
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
prompt += `</details>
|
|
1171
|
-
|
|
1172
|
-
`;
|
|
1173
|
-
prompt += `---
|
|
1174
|
-
|
|
1175
|
-
`;
|
|
1176
|
-
prompt += `## Ready to Begin
|
|
1177
|
-
|
|
1178
|
-
`;
|
|
1179
|
-
prompt += `I'll walk you through each file, explaining what changed and why. After each file, I'll pause for your questions.
|
|
1180
|
-
|
|
1181
|
-
`;
|
|
1182
|
-
prompt += `**Commands during review:**
|
|
1183
|
-
`;
|
|
1184
|
-
prompt += `- \`yes\` or \`y\` \u2014 continue to next file
|
|
1185
|
-
`;
|
|
1186
|
-
prompt += `- \`skip to [filename]\` \u2014 jump to specific file
|
|
1187
|
-
`;
|
|
1188
|
-
prompt += `- \`questions?\` \u2014 ask about current file
|
|
1189
|
-
`;
|
|
1190
|
-
prompt += `- \`reorder\` \u2014 change the file order
|
|
1191
|
-
`;
|
|
1192
|
-
prompt += `- \`done\` \u2014 end review and show summary
|
|
1193
|
-
|
|
1194
|
-
`;
|
|
1195
|
-
prompt += `**Ready for File 1?** (\`${fileOrder[0]?.path || "no files"}\`)
|
|
1196
|
-
|
|
1197
|
-
`;
|
|
1198
|
-
prompt += `*(say "yes" to start, or ask me anything)*
|
|
1199
|
-
`;
|
|
1200
|
-
return prompt;
|
|
1201
|
-
}
|
|
1202
|
-
};
|
|
1203
|
-
|
|
1204
|
-
// src/tools/project-info.ts
|
|
1205
|
-
var TrieProjectInfoTool = class {
|
|
1206
|
-
async execute(args) {
|
|
1207
|
-
const workDir = args.directory || getWorkingDirectory(void 0, true);
|
|
1208
|
-
const action = args.action || "view";
|
|
1209
|
-
try {
|
|
1210
|
-
switch (action) {
|
|
1211
|
-
case "view":
|
|
1212
|
-
return await this.handleView(workDir, args.section);
|
|
1213
|
-
case "init":
|
|
1214
|
-
return await this.handleInit(workDir);
|
|
1215
|
-
case "update":
|
|
1216
|
-
return await this.handleUpdate(workDir, args.section, args.content);
|
|
1217
|
-
case "append":
|
|
1218
|
-
return await this.handleAppend(workDir, args.section, args.content);
|
|
1219
|
-
case "sections":
|
|
1220
|
-
return await this.handleSections(workDir);
|
|
1221
|
-
case "raw":
|
|
1222
|
-
return await this.handleRaw(workDir);
|
|
1223
|
-
default:
|
|
1224
|
-
return this.error(`Unknown action: ${action}. Use: view, init, update, append, sections, raw`);
|
|
1225
|
-
}
|
|
1226
|
-
} catch (error) {
|
|
1227
|
-
return this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
async handleView(workDir, section) {
|
|
1231
|
-
if (!projectInfoExists(workDir)) {
|
|
1232
|
-
return this.formatResponse(`## Project Info Not Found
|
|
1233
|
-
|
|
1234
|
-
No \`.trie/PROJECT.md\` file exists in this project.
|
|
1235
|
-
|
|
1236
|
-
**To create one, use:**
|
|
1237
|
-
\`\`\`
|
|
1238
|
-
trie_project action="init"
|
|
1239
|
-
\`\`\`
|
|
1240
|
-
|
|
1241
|
-
Or run: \`trie project init\`
|
|
1242
|
-
|
|
1243
|
-
This will create a template with sections for:
|
|
1244
|
-
- Project Overview
|
|
1245
|
-
- Technology Stack
|
|
1246
|
-
- Architecture
|
|
1247
|
-
- Coding Conventions
|
|
1248
|
-
- Environment
|
|
1249
|
-
- Team
|
|
1250
|
-
- Compliance
|
|
1251
|
-
- AI Instructions`);
|
|
1252
|
-
}
|
|
1253
|
-
if (section) {
|
|
1254
|
-
const sectionContent = await getProjectSection(section, workDir);
|
|
1255
|
-
if (sectionContent === null) {
|
|
1256
|
-
const sections = await getProjectSections(workDir);
|
|
1257
|
-
return this.formatResponse(`## Section Not Found: "${section}"
|
|
1258
|
-
|
|
1259
|
-
Available sections:
|
|
1260
|
-
${sections.map((s) => `- ${s}`).join("\n")}`);
|
|
1261
|
-
}
|
|
1262
|
-
return this.formatResponse(`## ${section}
|
|
1263
|
-
|
|
1264
|
-
${sectionContent}`);
|
|
1265
|
-
}
|
|
1266
|
-
const info = await getProjectInfoStructured(workDir);
|
|
1267
|
-
let output = `## Project Information
|
|
1268
|
-
|
|
1269
|
-
**Path:** \`${info.path}\`
|
|
1270
|
-
**Sections:** ${Object.keys(info.sections).length}
|
|
1271
|
-
|
|
1272
|
-
---
|
|
1273
|
-
|
|
1274
|
-
`;
|
|
1275
|
-
for (const [name, content] of Object.entries(info.sections)) {
|
|
1276
|
-
output += `### ${name}
|
|
1277
|
-
|
|
1278
|
-
${content}
|
|
1279
|
-
|
|
1280
|
-
---
|
|
1281
|
-
|
|
1282
|
-
`;
|
|
1283
|
-
}
|
|
1284
|
-
return this.formatResponse(output);
|
|
1285
|
-
}
|
|
1286
|
-
async handleInit(workDir) {
|
|
1287
|
-
const result = await initProjectInfo(workDir);
|
|
1288
|
-
if (result.created) {
|
|
1289
|
-
return this.formatResponse(`## PROJECT.md Created
|
|
1290
|
-
|
|
1291
|
-
**Path:** \`${result.path}\`
|
|
1292
|
-
|
|
1293
|
-
A new PROJECT.md file has been created with a template including sections for:
|
|
1294
|
-
- Project Overview
|
|
1295
|
-
- Technology Stack
|
|
1296
|
-
- Architecture
|
|
1297
|
-
- Coding Conventions
|
|
1298
|
-
- Environment
|
|
1299
|
-
- Team
|
|
1300
|
-
- Compliance
|
|
1301
|
-
- AI Instructions
|
|
1302
|
-
|
|
1303
|
-
**Next steps:**
|
|
1304
|
-
1. Open the file and fill in your project details
|
|
1305
|
-
2. The content will be available via \`trie://project\` resource
|
|
1306
|
-
3. AI assistants will use this context when working on your project
|
|
1307
|
-
|
|
1308
|
-
**View the template:**
|
|
1309
|
-
\`\`\`
|
|
1310
|
-
trie_project action="view"
|
|
1311
|
-
\`\`\``);
|
|
1312
|
-
}
|
|
1313
|
-
return this.formatResponse(`## PROJECT.md Already Exists
|
|
1314
|
-
|
|
1315
|
-
**Path:** \`${result.path}\`
|
|
1316
|
-
|
|
1317
|
-
Use \`trie_project action="view"\` to see the current content.`);
|
|
1318
|
-
}
|
|
1319
|
-
async handleUpdate(workDir, section, content) {
|
|
1320
|
-
if (!section) {
|
|
1321
|
-
return this.error("Missing required parameter: section");
|
|
1322
|
-
}
|
|
1323
|
-
if (!content) {
|
|
1324
|
-
return this.error("Missing required parameter: content");
|
|
1325
|
-
}
|
|
1326
|
-
const success = await updateProjectSection(section, content, workDir);
|
|
1327
|
-
if (success) {
|
|
1328
|
-
return this.formatResponse(`## Section Updated: "${section}"
|
|
1329
|
-
|
|
1330
|
-
The "${section}" section has been updated with your new content.
|
|
1331
|
-
|
|
1332
|
-
**View updated section:**
|
|
1333
|
-
\`\`\`
|
|
1334
|
-
trie_project action="view" section="${section}"
|
|
1335
|
-
\`\`\``);
|
|
1336
|
-
}
|
|
1337
|
-
const sections = await getProjectSections(workDir);
|
|
1338
|
-
return this.error(`Could not update section "${section}".
|
|
1339
|
-
|
|
1340
|
-
Available sections:
|
|
1341
|
-
${sections.map((s) => `- ${s}`).join("\n")}`);
|
|
1342
|
-
}
|
|
1343
|
-
async handleAppend(workDir, section, content) {
|
|
1344
|
-
if (!section) {
|
|
1345
|
-
return this.error("Missing required parameter: section");
|
|
1346
|
-
}
|
|
1347
|
-
if (!content) {
|
|
1348
|
-
return this.error("Missing required parameter: content");
|
|
1349
|
-
}
|
|
1350
|
-
const success = await appendToSection(section, content, workDir);
|
|
1351
|
-
if (success) {
|
|
1352
|
-
return this.formatResponse(`## Content Appended to: "${section}"
|
|
1353
|
-
|
|
1354
|
-
Your content has been added to the "${section}" section.
|
|
1355
|
-
|
|
1356
|
-
**View updated section:**
|
|
1357
|
-
\`\`\`
|
|
1358
|
-
trie_project action="view" section="${section}"
|
|
1359
|
-
\`\`\``);
|
|
1360
|
-
}
|
|
1361
|
-
return this.error(`Could not append to section "${section}". Make sure the section exists.`);
|
|
1362
|
-
}
|
|
1363
|
-
async handleSections(workDir) {
|
|
1364
|
-
if (!projectInfoExists(workDir)) {
|
|
1365
|
-
return this.formatResponse(`## No PROJECT.md Found
|
|
1366
|
-
|
|
1367
|
-
Run \`trie_project action="init"\` to create one.`);
|
|
1368
|
-
}
|
|
1369
|
-
const sections = await getProjectSections(workDir);
|
|
1370
|
-
return this.formatResponse(`## Available Sections
|
|
1371
|
-
|
|
1372
|
-
${sections.map((s, i) => `${i + 1}. **${s}**`).join("\n")}
|
|
1373
|
-
|
|
1374
|
-
**View a section:**
|
|
1375
|
-
\`\`\`
|
|
1376
|
-
trie_project action="view" section="Section Name"
|
|
1377
|
-
\`\`\`
|
|
1378
|
-
|
|
1379
|
-
**Update a section:**
|
|
1380
|
-
\`\`\`
|
|
1381
|
-
trie_project action="update" section="Section Name" content="New content..."
|
|
1382
|
-
\`\`\``);
|
|
1383
|
-
}
|
|
1384
|
-
async handleRaw(workDir) {
|
|
1385
|
-
const content = await loadProjectInfo(workDir);
|
|
1386
|
-
if (!content) {
|
|
1387
|
-
return this.formatResponse(`No PROJECT.md found. Run \`trie_project action="init"\` to create one.`);
|
|
1388
|
-
}
|
|
1389
|
-
return this.formatResponse(content);
|
|
1390
|
-
}
|
|
1391
|
-
formatResponse(text) {
|
|
1392
|
-
return {
|
|
1393
|
-
content: [{ type: "text", text }]
|
|
1394
|
-
};
|
|
1395
|
-
}
|
|
1396
|
-
error(message) {
|
|
1397
|
-
return {
|
|
1398
|
-
content: [{ type: "text", text: `**Error:** ${message}` }]
|
|
1399
|
-
};
|
|
1400
|
-
}
|
|
1401
|
-
};
|
|
1402
|
-
|
|
1403
|
-
// src/tools/init.ts
|
|
1404
|
-
var TrieInitTool = class {
|
|
1405
|
-
async execute(params) {
|
|
1406
|
-
const action = params.action || "init";
|
|
1407
|
-
const workDir = params.directory || getWorkingDirectory(void 0, true);
|
|
1408
|
-
if (action === "status") {
|
|
1409
|
-
const needs = needsBootstrap(workDir);
|
|
1410
|
-
const context = await loadBootstrapContext(workDir);
|
|
1411
|
-
const existingFiles = context.files.filter((f) => f.exists).map((f) => f.name);
|
|
1412
|
-
const missingFiles = context.files.filter((f) => !f.exists).map((f) => f.name);
|
|
1413
|
-
return [
|
|
1414
|
-
"# Bootstrap Status",
|
|
1415
|
-
"",
|
|
1416
|
-
`**Bootstrap pending:** ${needs ? "Yes" : "No"}`,
|
|
1417
|
-
"",
|
|
1418
|
-
"## Files",
|
|
1419
|
-
"",
|
|
1420
|
-
"**Existing:**",
|
|
1421
|
-
existingFiles.length > 0 ? existingFiles.map((f) => `- .trie/${f}`).join("\n") : "- None",
|
|
1422
|
-
"",
|
|
1423
|
-
"**Missing:**",
|
|
1424
|
-
missingFiles.length > 0 ? missingFiles.map((f) => `- .trie/${f}`).join("\n") : "- None"
|
|
1425
|
-
].join("\n");
|
|
1426
|
-
}
|
|
1427
|
-
if (action === "complete") {
|
|
1428
|
-
const result2 = await completeBootstrap(workDir);
|
|
1429
|
-
if (result2) {
|
|
1430
|
-
return "Bootstrap completed. BOOTSTRAP.md has been deleted.";
|
|
1431
|
-
}
|
|
1432
|
-
return "No BOOTSTRAP.md file found.";
|
|
1433
|
-
}
|
|
1434
|
-
if (action === "context") {
|
|
1435
|
-
const context = await loadBootstrapContext(workDir);
|
|
1436
|
-
if (!context.injectedContent) {
|
|
1437
|
-
return 'No bootstrap context available. Run trie_init with action="init" first.';
|
|
1438
|
-
}
|
|
1439
|
-
return context.injectedContent;
|
|
1440
|
-
}
|
|
1441
|
-
const initOptions = {
|
|
1442
|
-
workDir
|
|
1443
|
-
};
|
|
1444
|
-
if (params.force !== void 0) {
|
|
1445
|
-
initOptions.force = params.force;
|
|
1446
|
-
}
|
|
1447
|
-
if (params.skipBootstrap !== void 0) {
|
|
1448
|
-
initOptions.skipBootstrap = params.skipBootstrap;
|
|
1449
|
-
}
|
|
1450
|
-
const result = await initializeBootstrapFiles(initOptions);
|
|
1451
|
-
const lines = [
|
|
1452
|
-
"# Trie Workspace Initialized",
|
|
1453
|
-
""
|
|
1454
|
-
];
|
|
1455
|
-
if (result.created.length > 0) {
|
|
1456
|
-
lines.push("## Created Files", "");
|
|
1457
|
-
for (const file of result.created) {
|
|
1458
|
-
lines.push(`- .trie/${file}`);
|
|
1459
|
-
}
|
|
1460
|
-
lines.push("");
|
|
1461
|
-
}
|
|
1462
|
-
if (result.skipped.length > 0) {
|
|
1463
|
-
lines.push("## Skipped (already exist)", "");
|
|
1464
|
-
for (const file of result.skipped) {
|
|
1465
|
-
lines.push(`- .trie/${file}`);
|
|
1466
|
-
}
|
|
1467
|
-
lines.push("");
|
|
1468
|
-
}
|
|
1469
|
-
lines.push("## Detected Stack", "");
|
|
1470
|
-
if (result.stack.framework) lines.push(`- **Framework:** ${result.stack.framework}`);
|
|
1471
|
-
if (result.stack.language) lines.push(`- **Language:** ${result.stack.language}`);
|
|
1472
|
-
if (result.stack.database) lines.push(`- **Database:** ${result.stack.database}`);
|
|
1473
|
-
if (result.stack.auth) lines.push(`- **Auth:** ${result.stack.auth}`);
|
|
1474
|
-
if (result.stack.packageManager) lines.push(`- **Package Manager:** ${result.stack.packageManager}`);
|
|
1475
|
-
lines.push("");
|
|
1476
|
-
if (result.stack.suggestedSkills.length > 0) {
|
|
1477
|
-
lines.push("## Suggested Skills", "");
|
|
1478
|
-
for (const skill of result.stack.suggestedSkills) {
|
|
1479
|
-
lines.push(`\`\`\`bash`);
|
|
1480
|
-
lines.push(`trie skills add ${skill}`);
|
|
1481
|
-
lines.push(`\`\`\``);
|
|
1482
|
-
}
|
|
1483
|
-
lines.push("");
|
|
1484
|
-
}
|
|
1485
|
-
lines.push(
|
|
1486
|
-
"## Next Steps",
|
|
1487
|
-
"",
|
|
1488
|
-
"1. Edit `.trie/PROJECT.md` with your project description",
|
|
1489
|
-
"2. Define coding standards in `.trie/RULES.md`",
|
|
1490
|
-
"3. Run `trie watch` to analyze your codebase",
|
|
1491
|
-
'4. Run `trie_init` with action="complete" when setup is done'
|
|
1492
|
-
);
|
|
1493
|
-
return lines.join("\n");
|
|
1494
|
-
}
|
|
1495
|
-
};
|
|
1496
|
-
|
|
1497
|
-
// src/tools/memory-search.ts
|
|
1498
|
-
var TrieMemorySearchTool = class {
|
|
1499
|
-
async execute(params) {
|
|
1500
|
-
const action = params.action || "search";
|
|
1501
|
-
const workDir = params.directory || getWorkingDirectory(void 0, true);
|
|
1502
|
-
let resultText;
|
|
1503
|
-
switch (action) {
|
|
1504
|
-
case "search":
|
|
1505
|
-
resultText = await this.handleSearch(params, workDir);
|
|
1506
|
-
break;
|
|
1507
|
-
case "stats":
|
|
1508
|
-
resultText = await this.handleStats(workDir);
|
|
1509
|
-
break;
|
|
1510
|
-
case "recent":
|
|
1511
|
-
resultText = await this.handleRecent(params, workDir);
|
|
1512
|
-
break;
|
|
1513
|
-
case "similar":
|
|
1514
|
-
resultText = await this.handleSimilar(params, workDir);
|
|
1515
|
-
break;
|
|
1516
|
-
case "resolve":
|
|
1517
|
-
resultText = await this.handleResolve(params, workDir);
|
|
1518
|
-
break;
|
|
1519
|
-
case "global":
|
|
1520
|
-
resultText = await this.handleGlobal(params);
|
|
1521
|
-
break;
|
|
1522
|
-
case "purge":
|
|
1523
|
-
resultText = await this.handlePurge(params, workDir);
|
|
1524
|
-
break;
|
|
1525
|
-
case "nudges":
|
|
1526
|
-
resultText = await this.handleNudges(params, workDir);
|
|
1527
|
-
break;
|
|
1528
|
-
case "clear-nudges":
|
|
1529
|
-
resultText = await this.handleClearNudges(params, workDir);
|
|
1530
|
-
break;
|
|
1531
|
-
default:
|
|
1532
|
-
resultText = "Unknown action. Use: search, stats, recent, similar, resolve, global, purge, nudges, or clear-nudges";
|
|
1533
|
-
}
|
|
1534
|
-
return {
|
|
1535
|
-
content: [{
|
|
1536
|
-
type: "text",
|
|
1537
|
-
text: resultText
|
|
1538
|
-
}]
|
|
1539
|
-
};
|
|
1540
|
-
}
|
|
1541
|
-
async handleSearch(params, workDir) {
|
|
1542
|
-
if (!params.query) {
|
|
1543
|
-
return "Missing required parameter: query";
|
|
1544
|
-
}
|
|
1545
|
-
const searchOptions = {
|
|
1546
|
-
workDir,
|
|
1547
|
-
limit: params.limit || 10
|
|
1548
|
-
};
|
|
1549
|
-
if (params.severity !== void 0) {
|
|
1550
|
-
searchOptions.severity = params.severity;
|
|
1551
|
-
}
|
|
1552
|
-
if (params.agent !== void 0) {
|
|
1553
|
-
searchOptions.agent = params.agent;
|
|
1554
|
-
}
|
|
1555
|
-
if (params.includeResolved !== void 0) {
|
|
1556
|
-
searchOptions.includeResolved = params.includeResolved;
|
|
1557
|
-
}
|
|
1558
|
-
const results = await searchIssues(params.query, searchOptions);
|
|
1559
|
-
if (results.length === 0) {
|
|
1560
|
-
return `No issues found matching "${params.query}"`;
|
|
1561
|
-
}
|
|
1562
|
-
const lines = [
|
|
1563
|
-
`# Search Results: "${params.query}"`,
|
|
1564
|
-
"",
|
|
1565
|
-
`Found ${results.length} matching issue(s):`,
|
|
1566
|
-
""
|
|
1567
|
-
];
|
|
1568
|
-
for (const result of results) {
|
|
1569
|
-
const i = result.issue;
|
|
1570
|
-
const status = i.resolved ? " [RESOLVED]" : "";
|
|
1571
|
-
lines.push(
|
|
1572
|
-
`## [${i.severity.toUpperCase()}]${status} ${i.issue.slice(0, 80)}`,
|
|
1573
|
-
"",
|
|
1574
|
-
`- **File:** \`${i.file}\`${i.line ? `:${i.line}` : ""}`,
|
|
1575
|
-
`- **Agent:** ${i.agent}`,
|
|
1576
|
-
`- **Score:** ${(result.score * 100).toFixed(0)}%`,
|
|
1577
|
-
`- **Date:** ${new Date(i.timestamp).toLocaleDateString()}`,
|
|
1578
|
-
`- **Fix:** ${i.fix.slice(0, 150)}${i.fix.length > 150 ? "..." : ""}`,
|
|
1579
|
-
""
|
|
1580
|
-
);
|
|
1581
|
-
}
|
|
1582
|
-
return lines.join("\n");
|
|
1583
|
-
}
|
|
1584
|
-
async handleStats(workDir) {
|
|
1585
|
-
const stats = await getMemoryStats(workDir);
|
|
1586
|
-
const globalStats = await getGlobalMemoryStats();
|
|
1587
|
-
const lines = [
|
|
1588
|
-
"# Memory Statistics",
|
|
1589
|
-
"",
|
|
1590
|
-
"## Local Issue Memory",
|
|
1591
|
-
"",
|
|
1592
|
-
`- **Active Issues:** ${stats.activeIssues}`,
|
|
1593
|
-
`- **Resolved:** ${stats.resolvedCount}`,
|
|
1594
|
-
`- **Total (all-time):** ${stats.totalIssues}`
|
|
1595
|
-
];
|
|
1596
|
-
const cap = stats.capacityInfo;
|
|
1597
|
-
if (cap.isAtCap) {
|
|
1598
|
-
lines.push(
|
|
1599
|
-
"",
|
|
1600
|
-
"### \u26A0\uFE0F CAPACITY WARNING",
|
|
1601
|
-
"",
|
|
1602
|
-
`**Memory is at maximum capacity (${cap.current}/${cap.max} issues, ${cap.percentFull}%)**`,
|
|
1603
|
-
"",
|
|
1604
|
-
"Trie caps issue storage at 10,000 for performance. New repeats are deduplicated.",
|
|
1605
|
-
"Older issues are compacted into summaries, and if it still exceeds the cap the oldest/lowest-value issues are pruned.",
|
|
1606
|
-
"",
|
|
1607
|
-
"**Options to free up space:**",
|
|
1608
|
-
"- `trie memory purge smart` - Remove resolved and old low-priority issues (recommended)",
|
|
1609
|
-
"- `trie memory purge resolved` - Remove all resolved issues",
|
|
1610
|
-
"- `trie memory purge old` - Remove issues older than 90 days",
|
|
1611
|
-
"- `trie memory purge all` - Clear all issues (keeps summaries)"
|
|
1612
|
-
);
|
|
1613
|
-
} else if (cap.percentFull >= 80) {
|
|
1614
|
-
lines.push(
|
|
1615
|
-
"",
|
|
1616
|
-
`### \u2139\uFE0F Memory Usage: ${cap.percentFull}% (${cap.current}/${cap.max})`,
|
|
1617
|
-
"",
|
|
1618
|
-
`You're approaching the storage cap. Consider running \`trie memory purge smart\` to free up space.`
|
|
1619
|
-
);
|
|
1620
|
-
} else {
|
|
1621
|
-
lines.push(`- **Memory Usage:** ${cap.percentFull}% (${cap.current}/${cap.max})`);
|
|
1622
|
-
}
|
|
1623
|
-
if (stats.deduplicationStats.duplicatesAvoided > 0) {
|
|
1624
|
-
lines.push(
|
|
1625
|
-
"",
|
|
1626
|
-
"### Deduplication",
|
|
1627
|
-
"",
|
|
1628
|
-
`- **Unique Patterns:** ${stats.deduplicationStats.uniquePatterns}`,
|
|
1629
|
-
`- **Duplicates Avoided:** ${stats.deduplicationStats.duplicatesAvoided}`
|
|
1630
|
-
);
|
|
1631
|
-
}
|
|
1632
|
-
if (stats.oldestIssue) {
|
|
1633
|
-
lines.push("", `- **Date Range:** ${stats.oldestIssue.split("T")[0]} to ${stats.newestIssue?.split("T")[0]}`);
|
|
1634
|
-
}
|
|
1635
|
-
lines.push("", "### By Severity", "");
|
|
1636
|
-
for (const [severity, count] of Object.entries(stats.issuesBySeverity)) {
|
|
1637
|
-
lines.push(`- ${severity}: ${count}`);
|
|
1638
|
-
}
|
|
1639
|
-
lines.push("", "### By Agent", "");
|
|
1640
|
-
const sortedAgents = Object.entries(stats.issuesByAgent).sort((a, b) => b[1] - a[1]);
|
|
1641
|
-
for (const [agent, count] of sortedAgents.slice(0, 10)) {
|
|
1642
|
-
lines.push(`- ${agent}: ${count}`);
|
|
1643
|
-
}
|
|
1644
|
-
lines.push(
|
|
1645
|
-
"",
|
|
1646
|
-
"## Global Cross-Project Memory",
|
|
1647
|
-
"",
|
|
1648
|
-
`- **Tracked Projects:** ${globalStats.trackedProjects}`,
|
|
1649
|
-
`- **Total Patterns:** ${globalStats.totalPatterns}`,
|
|
1650
|
-
`- **Cross-Project Patterns:** ${globalStats.crossProjectPatterns}`,
|
|
1651
|
-
`- **Fixed Patterns:** ${globalStats.fixedPatterns}`
|
|
1652
|
-
);
|
|
1653
|
-
return lines.join("\n");
|
|
1654
|
-
}
|
|
1655
|
-
async handleRecent(params, workDir) {
|
|
1656
|
-
const issues = await getRecentIssues({
|
|
1657
|
-
workDir,
|
|
1658
|
-
limit: params.limit || 10
|
|
1659
|
-
});
|
|
1660
|
-
if (issues.length === 0) {
|
|
1661
|
-
return "No recent issues found.";
|
|
1662
|
-
}
|
|
1663
|
-
const lines = [
|
|
1664
|
-
"# Recent Issues (Last 7 Days)",
|
|
1665
|
-
""
|
|
1666
|
-
];
|
|
1667
|
-
for (const i of issues) {
|
|
1668
|
-
const status = i.resolved ? " [RESOLVED]" : "";
|
|
1669
|
-
lines.push(
|
|
1670
|
-
`## [${i.severity.toUpperCase()}]${status} ${i.issue.slice(0, 70)}`,
|
|
1671
|
-
"",
|
|
1672
|
-
`- \`${i.file}\`${i.line ? `:${i.line}` : ""}`,
|
|
1673
|
-
`- Agent: ${i.agent} | ${new Date(i.timestamp).toLocaleDateString()}`,
|
|
1674
|
-
""
|
|
1675
|
-
);
|
|
1676
|
-
}
|
|
1677
|
-
return lines.join("\n");
|
|
1678
|
-
}
|
|
1679
|
-
async handleSimilar(params, workDir) {
|
|
1680
|
-
if (!params.query) {
|
|
1681
|
-
return "Missing required parameter: query (issue description to find similar issues for)";
|
|
1682
|
-
}
|
|
1683
|
-
const mockIssue = {
|
|
1684
|
-
id: "search",
|
|
1685
|
-
severity: "moderate",
|
|
1686
|
-
issue: params.query,
|
|
1687
|
-
fix: "",
|
|
1688
|
-
file: "",
|
|
1689
|
-
agent: "search",
|
|
1690
|
-
confidence: 1,
|
|
1691
|
-
autoFixable: false
|
|
1692
|
-
};
|
|
1693
|
-
const results = await findSimilarIssues(mockIssue, {
|
|
1694
|
-
workDir,
|
|
1695
|
-
limit: params.limit || 5
|
|
1696
|
-
});
|
|
1697
|
-
if (results.length === 0) {
|
|
1698
|
-
return "No similar issues found.";
|
|
1699
|
-
}
|
|
1700
|
-
const lines = [
|
|
1701
|
-
`# Similar Issues`,
|
|
1702
|
-
"",
|
|
1703
|
-
`Found ${results.length} similar issue(s):`,
|
|
1704
|
-
""
|
|
1705
|
-
];
|
|
1706
|
-
for (const result of results) {
|
|
1707
|
-
const i = result.issue;
|
|
1708
|
-
lines.push(
|
|
1709
|
-
`## [${i.severity.toUpperCase()}] ${i.issue.slice(0, 70)}`,
|
|
1710
|
-
"",
|
|
1711
|
-
`- **File:** \`${i.file}\``,
|
|
1712
|
-
`- **Similarity:** ${(result.score * 100).toFixed(0)}%`,
|
|
1713
|
-
""
|
|
1714
|
-
);
|
|
1715
|
-
}
|
|
1716
|
-
return lines.join("\n");
|
|
1717
|
-
}
|
|
1718
|
-
async handleResolve(params, workDir) {
|
|
1719
|
-
if (!params.issueId) {
|
|
1720
|
-
return "Missing required parameter: issueId";
|
|
1721
|
-
}
|
|
1722
|
-
const result = await markIssueResolved(params.issueId, workDir);
|
|
1723
|
-
if (result) {
|
|
1724
|
-
return `Issue ${params.issueId} marked as resolved.`;
|
|
1725
|
-
}
|
|
1726
|
-
return `Issue ${params.issueId} not found.`;
|
|
1727
|
-
}
|
|
1728
|
-
async handleGlobal(params) {
|
|
1729
|
-
if (params.query) {
|
|
1730
|
-
const patterns2 = await searchGlobalPatterns(params.query, {
|
|
1731
|
-
limit: params.limit || 10
|
|
1732
|
-
});
|
|
1733
|
-
if (patterns2.length === 0) {
|
|
1734
|
-
return `No global patterns found matching "${params.query}"`;
|
|
1735
|
-
}
|
|
1736
|
-
const lines2 = [
|
|
1737
|
-
`# Global Pattern Search: "${params.query}"`,
|
|
1738
|
-
""
|
|
1739
|
-
];
|
|
1740
|
-
for (const p of patterns2) {
|
|
1741
|
-
lines2.push(
|
|
1742
|
-
`## [${p.severity.toUpperCase()}] ${p.pattern.slice(0, 60)}`,
|
|
1743
|
-
"",
|
|
1744
|
-
`- **Occurrences:** ${p.occurrences} across ${p.projects.length} projects`,
|
|
1745
|
-
`- **Agent:** ${p.agent}`,
|
|
1746
|
-
`- **Projects:** ${p.projects.slice(0, 3).join(", ")}`,
|
|
1747
|
-
p.fixApplied ? `- **Fixed in:** ${p.fixApplied.project}` : "",
|
|
1748
|
-
""
|
|
1749
|
-
);
|
|
1750
|
-
}
|
|
1751
|
-
return lines2.join("\n");
|
|
1752
|
-
}
|
|
1753
|
-
const patterns = await findCrossProjectPatterns(2);
|
|
1754
|
-
const projects = await listTrackedProjects();
|
|
1755
|
-
const lines = [
|
|
1756
|
-
"# Global Cross-Project Memory",
|
|
1757
|
-
"",
|
|
1758
|
-
`**Tracked Projects:** ${projects.length}`,
|
|
1759
|
-
`**Cross-Project Patterns:** ${patterns.length}`,
|
|
1760
|
-
""
|
|
1761
|
-
];
|
|
1762
|
-
if (patterns.length > 0) {
|
|
1763
|
-
lines.push("## Top Cross-Project Patterns", "");
|
|
1764
|
-
for (const p of patterns.slice(0, 5)) {
|
|
1765
|
-
lines.push(
|
|
1766
|
-
`### [${p.severity.toUpperCase()}] ${p.pattern.slice(0, 50)}`,
|
|
1767
|
-
"",
|
|
1768
|
-
`- Seen ${p.occurrences}x across ${p.projects.length} projects`,
|
|
1769
|
-
p.fixApplied ? `- Fixed in ${p.fixApplied.project}` : "- Not yet fixed",
|
|
1770
|
-
""
|
|
1771
|
-
);
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
if (projects.length > 0) {
|
|
1775
|
-
lines.push("## Recent Projects", "", "| Project | Issues |", "|---------|--------|");
|
|
1776
|
-
for (const p of projects.slice(0, 5)) {
|
|
1777
|
-
lines.push(`| ${p.name} | ${p.totalIssues} |`);
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
return lines.join("\n");
|
|
1781
|
-
}
|
|
1782
|
-
async handlePurge(params, workDir) {
|
|
1783
|
-
const strategy = params.purgeStrategy || "smart";
|
|
1784
|
-
const options = { workDir };
|
|
1785
|
-
if (params.daysOld !== void 0) {
|
|
1786
|
-
options.daysOld = params.daysOld;
|
|
1787
|
-
}
|
|
1788
|
-
const result = await purgeIssues(strategy, options);
|
|
1789
|
-
const lines = [
|
|
1790
|
-
"# Memory Purge Complete",
|
|
1791
|
-
"",
|
|
1792
|
-
`**Strategy:** ${strategy}`,
|
|
1793
|
-
`**Removed:** ${result.removed} issues`,
|
|
1794
|
-
`**Remaining:** ${result.remaining} issues`,
|
|
1795
|
-
""
|
|
1796
|
-
];
|
|
1797
|
-
switch (strategy) {
|
|
1798
|
-
case "smart":
|
|
1799
|
-
lines.push(
|
|
1800
|
-
"**What was removed:**",
|
|
1801
|
-
"- Resolved issues older than 30 days",
|
|
1802
|
-
"- Low-priority issues (info/low severity) older than 30 days",
|
|
1803
|
-
"",
|
|
1804
|
-
"**What was kept:**",
|
|
1805
|
-
"- All critical and high severity issues",
|
|
1806
|
-
"- All unresolved issues",
|
|
1807
|
-
"- All issues from the last 30 days"
|
|
1808
|
-
);
|
|
1809
|
-
break;
|
|
1810
|
-
case "resolved":
|
|
1811
|
-
lines.push("**Removed all resolved issues.**", "**Kept all unresolved issues.**");
|
|
1812
|
-
break;
|
|
1813
|
-
case "old":
|
|
1814
|
-
lines.push(
|
|
1815
|
-
`**Removed all issues older than ${params.daysOld || 90} days.**`,
|
|
1816
|
-
`**Kept all recent issues.**`
|
|
1817
|
-
);
|
|
1818
|
-
break;
|
|
1819
|
-
case "all":
|
|
1820
|
-
lines.push(
|
|
1821
|
-
"**Cleared all issues from active memory.**",
|
|
1822
|
-
"",
|
|
1823
|
-
"Historical summaries are preserved in compacted-summaries.json."
|
|
1824
|
-
);
|
|
1825
|
-
break;
|
|
1826
|
-
}
|
|
1827
|
-
lines.push("", "**Note:** Compacted historical summaries were preserved for trend analysis.");
|
|
1828
|
-
return lines.join("\n");
|
|
1829
|
-
}
|
|
1830
|
-
async handleNudges(params, workDir) {
|
|
1831
|
-
const storage = getStorage(workDir);
|
|
1832
|
-
await storage.initialize();
|
|
1833
|
-
const queryParams = {
|
|
1834
|
-
limit: params.limit || 20
|
|
1835
|
-
};
|
|
1836
|
-
if (!params.includeResolved) {
|
|
1837
|
-
queryParams.resolved = false;
|
|
1838
|
-
}
|
|
1839
|
-
const nudges = await storage.queryNudges(queryParams);
|
|
1840
|
-
if (nudges.length === 0) {
|
|
1841
|
-
return params.includeResolved ? "No nudges found in storage." : "No unresolved nudges. All clear!";
|
|
1842
|
-
}
|
|
1843
|
-
const lines = [
|
|
1844
|
-
"# Nudges (Goal Violations & Alerts)",
|
|
1845
|
-
"",
|
|
1846
|
-
`Found ${nudges.length} nudge(s):`,
|
|
1847
|
-
""
|
|
1848
|
-
];
|
|
1849
|
-
for (const nudge of nudges) {
|
|
1850
|
-
const status = nudge.resolved ? ` [${nudge.resolution || "RESOLVED"}]` : "";
|
|
1851
|
-
const severity = nudge.severity.toUpperCase();
|
|
1852
|
-
lines.push(
|
|
1853
|
-
`## [${severity}]${status} ${nudge.message.slice(0, 80)}${nudge.message.length > 80 ? "..." : ""}`,
|
|
1854
|
-
"",
|
|
1855
|
-
`- **ID:** \`${nudge.id}\``,
|
|
1856
|
-
nudge.file ? `- **File:** \`${nudge.file}\`` : "",
|
|
1857
|
-
`- **Time:** ${new Date(nudge.timestamp).toLocaleString()}`,
|
|
1858
|
-
nudge.suggestedAction ? `- **Suggested:** ${nudge.suggestedAction}` : "",
|
|
1859
|
-
""
|
|
1860
|
-
);
|
|
1861
|
-
}
|
|
1862
|
-
lines.push(
|
|
1863
|
-
"---",
|
|
1864
|
-
"",
|
|
1865
|
-
"**Actions:**",
|
|
1866
|
-
"- `trie_memory action:clear-nudges nudgeId:<id>` - Dismiss a specific nudge",
|
|
1867
|
-
"- `trie_memory action:clear-nudges clearAll:true` - Clear all nudges",
|
|
1868
|
-
'- `trie_memory action:clear-nudges pattern:"<text>"` - Clear nudges matching pattern'
|
|
1869
|
-
);
|
|
1870
|
-
return lines.join("\n");
|
|
1871
|
-
}
|
|
1872
|
-
async handleClearNudges(params, workDir) {
|
|
1873
|
-
const storage = getStorage(workDir);
|
|
1874
|
-
await storage.initialize();
|
|
1875
|
-
if (params.clearAll) {
|
|
1876
|
-
await storage.clearAllNudges();
|
|
1877
|
-
return "\u2713 All nudges have been cleared.";
|
|
1878
|
-
}
|
|
1879
|
-
if (params.nudgeId) {
|
|
1880
|
-
await storage.dismissNudge(params.nudgeId);
|
|
1881
|
-
return `\u2713 Nudge \`${params.nudgeId}\` has been dismissed.`;
|
|
1882
|
-
}
|
|
1883
|
-
if (params.pattern) {
|
|
1884
|
-
const count = await storage.resolveNudgesByPattern(params.pattern, "dismissed");
|
|
1885
|
-
return count > 0 ? `\u2713 Cleared ${count} nudge(s) matching "${params.pattern}".` : `No nudges found matching "${params.pattern}".`;
|
|
1886
|
-
}
|
|
1887
|
-
return [
|
|
1888
|
-
"Missing parameter. Specify one of:",
|
|
1889
|
-
"- `nudgeId` - ID of specific nudge to dismiss",
|
|
1890
|
-
"- `clearAll: true` - Clear all nudges",
|
|
1891
|
-
"- `pattern` - Clear nudges matching text pattern",
|
|
1892
|
-
"",
|
|
1893
|
-
"Use `trie_memory action:nudges` to list current nudges and their IDs."
|
|
1894
|
-
].join("\n");
|
|
1895
|
-
}
|
|
1896
|
-
};
|
|
1897
|
-
|
|
1898
|
-
// src/tools/context.ts
|
|
1899
|
-
var TrieContextTool = class {
|
|
1900
|
-
async execute(input = {}) {
|
|
1901
|
-
try {
|
|
1902
|
-
const workDir = input.directory || getWorkingDirectory(void 0, true);
|
|
1903
|
-
const graph = new ContextGraph(workDir);
|
|
1904
|
-
const snapshot = await graph.getSnapshot();
|
|
1905
|
-
await exportToJson(graph);
|
|
1906
|
-
const summary = `Nodes: ${snapshot.nodes.length}, Edges: ${snapshot.edges.length}, Exported: ${snapshot.exported_at}`;
|
|
1907
|
-
return {
|
|
1908
|
-
content: [{
|
|
1909
|
-
type: "text",
|
|
1910
|
-
text: summary
|
|
1911
|
-
}],
|
|
1912
|
-
data: snapshot
|
|
1913
|
-
};
|
|
1914
|
-
} catch (error) {
|
|
1915
|
-
const friendly = formatFriendlyError(error);
|
|
1916
|
-
return { content: [{ type: "text", text: friendly.userMessage }] };
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
};
|
|
1920
|
-
|
|
1921
|
-
// src/tools/linear-sync.ts
|
|
1922
|
-
var LinearSyncTool = class {
|
|
1923
|
-
async execute(input) {
|
|
1924
|
-
try {
|
|
1925
|
-
const workDir = input.directory || getWorkingDirectory(void 0, true);
|
|
1926
|
-
const graph = new ContextGraph(workDir);
|
|
1927
|
-
const ingester = new LinearIngester(workDir, graph);
|
|
1928
|
-
await ingester.syncTickets();
|
|
1929
|
-
return {
|
|
1930
|
-
content: [{
|
|
1931
|
-
type: "text",
|
|
1932
|
-
text: "\u2705 Linear tickets synced successfully. Trie now has context on your active tasks."
|
|
1933
|
-
}]
|
|
1934
|
-
};
|
|
1935
|
-
} catch (error) {
|
|
1936
|
-
return {
|
|
1937
|
-
isError: true,
|
|
1938
|
-
content: [{
|
|
1939
|
-
type: "text",
|
|
1940
|
-
text: `Failed to sync Linear tickets: ${error.message}`
|
|
1941
|
-
}]
|
|
1942
|
-
};
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
};
|
|
1946
|
-
|
|
1947
|
-
// src/tools/github-sync.ts
|
|
1948
|
-
var GitHubSyncTool = class {
|
|
1949
|
-
async execute(input) {
|
|
1950
|
-
try {
|
|
1951
|
-
const workDir = input.directory || getWorkingDirectory(void 0, true);
|
|
1952
|
-
const graph = new ContextGraph(workDir);
|
|
1953
|
-
const ingester = new GitHubIngester(graph);
|
|
1954
|
-
const token = await ingester.getApiToken();
|
|
1955
|
-
if (!token) {
|
|
1956
|
-
return {
|
|
1957
|
-
isError: true,
|
|
1958
|
-
content: [{
|
|
1959
|
-
type: "text",
|
|
1960
|
-
text: 'GitHub token not configured.\n\nSet one of:\n \u2022 Environment variable: GITHUB_TOKEN=ghp_...\n \u2022 Config dialog (C key) \u2192 API Keys \u2192 GitHub\n \u2022 .trie/config.json: { "apiKeys": { "github": "ghp_..." } }\n\nToken needs `repo` scope for private repos, `public_repo` for public.'
|
|
1961
|
-
}]
|
|
1962
|
-
};
|
|
1963
|
-
}
|
|
1964
|
-
const repoInfo = ingester.getRepoInfo(workDir);
|
|
1965
|
-
if (!repoInfo) {
|
|
1966
|
-
return {
|
|
1967
|
-
isError: true,
|
|
1968
|
-
content: [{
|
|
1969
|
-
type: "text",
|
|
1970
|
-
text: "Could not detect GitHub repository.\n\nMake sure this directory is a git repo with a GitHub remote:\n git remote get-url origin"
|
|
1971
|
-
}]
|
|
1972
|
-
};
|
|
1973
|
-
}
|
|
1974
|
-
const prResult = await ingester.syncPullRequests(repoInfo.owner, repoInfo.name, token);
|
|
1975
|
-
const issueResult = await ingester.syncIssues(repoInfo.owner, repoInfo.name, token);
|
|
1976
|
-
const LINE = "\u2500".repeat(45);
|
|
1977
|
-
const lines = [
|
|
1978
|
-
"GITHUB SYNC",
|
|
1979
|
-
LINE,
|
|
1980
|
-
`Repo: ${repoInfo.owner}/${repoInfo.name}`,
|
|
1981
|
-
`Pulled ${prResult.prs} open PR${prResult.prs !== 1 ? "s" : ""}, ${issueResult.issues} open issue${issueResult.issues !== 1 ? "s" : ""}`,
|
|
1982
|
-
`Linked ${prResult.linkedTickets} PR${prResult.linkedTickets !== 1 ? "s" : ""} to Linear tickets`,
|
|
1983
|
-
`Linked ${prResult.linkedFiles} PR${prResult.linkedFiles !== 1 ? "s" : ""} to changed files in context graph`,
|
|
1984
|
-
LINE,
|
|
1985
|
-
"Next sync: trie_github_sync",
|
|
1986
|
-
"Or use trie_pipeline for consolidated view."
|
|
1987
|
-
];
|
|
1988
|
-
return {
|
|
1989
|
-
content: [{
|
|
1990
|
-
type: "text",
|
|
1991
|
-
text: lines.join("\n")
|
|
1992
|
-
}]
|
|
1993
|
-
};
|
|
1994
|
-
} catch (error) {
|
|
1995
|
-
return {
|
|
1996
|
-
isError: true,
|
|
1997
|
-
content: [{
|
|
1998
|
-
type: "text",
|
|
1999
|
-
text: `GitHub sync failed: ${error.message}`
|
|
2000
|
-
}]
|
|
2001
|
-
};
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
};
|
|
2005
|
-
|
|
2006
|
-
// src/tools/index-codebase.ts
|
|
2007
|
-
import { glob } from "glob";
|
|
2008
|
-
var INDEXABLE_EXTENSIONS = [
|
|
2009
|
-
"ts",
|
|
2010
|
-
"tsx",
|
|
2011
|
-
"js",
|
|
2012
|
-
"jsx",
|
|
2013
|
-
"mjs",
|
|
2014
|
-
"vue",
|
|
2015
|
-
"svelte",
|
|
2016
|
-
"astro",
|
|
2017
|
-
"py",
|
|
2018
|
-
"go",
|
|
2019
|
-
"rs",
|
|
2020
|
-
"java",
|
|
2021
|
-
"c",
|
|
2022
|
-
"cpp",
|
|
2023
|
-
"h",
|
|
2024
|
-
"hpp",
|
|
2025
|
-
"cs",
|
|
2026
|
-
"rb",
|
|
2027
|
-
"php",
|
|
2028
|
-
"css",
|
|
2029
|
-
"scss",
|
|
2030
|
-
"html"
|
|
2031
|
-
];
|
|
2032
|
-
var TrieIndexTool = class {
|
|
2033
|
-
async execute(params) {
|
|
2034
|
-
const action = params.action || "full";
|
|
2035
|
-
const workDir = getWorkingDirectory(params.directory, true);
|
|
2036
|
-
if (!isTrieInitialized(workDir)) {
|
|
2037
|
-
return "Trie is not initialized for this project. Run `trie init` first.";
|
|
2038
|
-
}
|
|
2039
|
-
const codebaseIndex = new CodebaseIndex(workDir);
|
|
2040
|
-
if (action === "status") {
|
|
2041
|
-
const stats2 = codebaseIndex.getStats();
|
|
2042
|
-
let lastUpdatedStr = "Never";
|
|
2043
|
-
if (stats2.lastUpdated) {
|
|
2044
|
-
try {
|
|
2045
|
-
const date = new Date(stats2.lastUpdated);
|
|
2046
|
-
lastUpdatedStr = date.toLocaleString("en-US", {
|
|
2047
|
-
year: "numeric",
|
|
2048
|
-
month: "short",
|
|
2049
|
-
day: "numeric",
|
|
2050
|
-
hour: "numeric",
|
|
2051
|
-
minute: "2-digit"
|
|
2052
|
-
});
|
|
2053
|
-
} catch {
|
|
2054
|
-
lastUpdatedStr = "Unknown";
|
|
2055
|
-
}
|
|
2056
|
-
}
|
|
2057
|
-
return [
|
|
2058
|
-
"# Codebase Index Status",
|
|
2059
|
-
"",
|
|
2060
|
-
`**Total files indexed:** ${stats2.totalFiles}`,
|
|
2061
|
-
`**Total size:** ${(stats2.totalSize / 1024 / 1024).toFixed(2)} MB`,
|
|
2062
|
-
`**Files scanned for goals:** ${stats2.scannedFiles}`,
|
|
2063
|
-
`**Files with violations:** ${stats2.filesWithViolations}`,
|
|
2064
|
-
`**Last updated:** ${lastUpdatedStr}`,
|
|
2065
|
-
"",
|
|
2066
|
-
"## Files by Type",
|
|
2067
|
-
"",
|
|
2068
|
-
...Object.entries(stats2.filesByType).sort(([, a], [, b]) => b - a).slice(0, 10).map(([type, count]) => `- .${type}: ${count} files`),
|
|
2069
|
-
"",
|
|
2070
|
-
"---",
|
|
2071
|
-
"",
|
|
2072
|
-
'Run `trie_index` with action="full" to re-index the codebase.',
|
|
2073
|
-
'Run `trie_index` with action="clear" to clear stale cache entries.'
|
|
2074
|
-
].join("\n");
|
|
2075
|
-
}
|
|
2076
|
-
if (action === "clear") {
|
|
2077
|
-
const cleared = codebaseIndex.clearStaleCache();
|
|
2078
|
-
await codebaseIndex.save();
|
|
2079
|
-
return `Cleared ${cleared} stale cache entries.`;
|
|
2080
|
-
}
|
|
2081
|
-
const pattern = `${workDir}/**/*.{${INDEXABLE_EXTENSIONS.join(",")}}`;
|
|
2082
|
-
const allFiles = await glob(pattern, {
|
|
2083
|
-
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/.trie/**", "**/coverage/**"],
|
|
2084
|
-
nodir: true
|
|
2085
|
-
});
|
|
2086
|
-
let indexed = 0;
|
|
2087
|
-
let failed = 0;
|
|
2088
|
-
const startTime = Date.now();
|
|
2089
|
-
const BATCH_SIZE = 50;
|
|
2090
|
-
for (let i = 0; i < allFiles.length; i += BATCH_SIZE) {
|
|
2091
|
-
const batch = allFiles.slice(i, i + BATCH_SIZE);
|
|
2092
|
-
await Promise.all(batch.map(async (filePath) => {
|
|
2093
|
-
try {
|
|
2094
|
-
let relativePath = filePath;
|
|
2095
|
-
if (filePath.toLowerCase().startsWith(workDir.toLowerCase() + "/")) {
|
|
2096
|
-
relativePath = filePath.slice(workDir.length + 1);
|
|
2097
|
-
}
|
|
2098
|
-
const result = await codebaseIndex.indexFile(relativePath);
|
|
2099
|
-
if (result) {
|
|
2100
|
-
indexed++;
|
|
2101
|
-
} else {
|
|
2102
|
-
failed++;
|
|
2103
|
-
}
|
|
2104
|
-
} catch {
|
|
2105
|
-
failed++;
|
|
2106
|
-
}
|
|
2107
|
-
}));
|
|
2108
|
-
}
|
|
2109
|
-
await codebaseIndex.save();
|
|
2110
|
-
const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
2111
|
-
const stats = codebaseIndex.getStats();
|
|
2112
|
-
return [
|
|
2113
|
-
"# Codebase Indexing Complete",
|
|
2114
|
-
"",
|
|
2115
|
-
`**Files indexed:** ${indexed}`,
|
|
2116
|
-
`**Files failed:** ${failed}`,
|
|
2117
|
-
`**Duration:** ${duration}s`,
|
|
2118
|
-
"",
|
|
2119
|
-
"## Index Statistics",
|
|
2120
|
-
"",
|
|
2121
|
-
`- Total files: ${stats.totalFiles}`,
|
|
2122
|
-
`- Total size: ${(stats.totalSize / 1024 / 1024).toFixed(2)} MB`,
|
|
2123
|
-
"",
|
|
2124
|
-
"## Files by Type",
|
|
2125
|
-
"",
|
|
2126
|
-
...Object.entries(stats.filesByType).sort(([, a], [, b]) => b - a).slice(0, 10).map(([type, count]) => `- .${type}: ${count} files`),
|
|
2127
|
-
"",
|
|
2128
|
-
"---",
|
|
2129
|
-
"",
|
|
2130
|
-
"The index will be automatically updated when files change during watch mode.",
|
|
2131
|
-
"Goal checks will now use cached results for unchanged files."
|
|
2132
|
-
].join("\n");
|
|
2133
|
-
}
|
|
2134
|
-
};
|
|
2135
|
-
|
|
2136
|
-
// src/server/tool-registry.ts
|
|
2137
|
-
var ToolRegistry = class {
|
|
2138
|
-
tools = /* @__PURE__ */ new Map();
|
|
2139
|
-
definitions = [];
|
|
2140
|
-
constructor() {
|
|
2141
|
-
this.initializeTools();
|
|
2142
|
-
this.defineToolSchemas();
|
|
2143
|
-
}
|
|
2144
|
-
initializeTools() {
|
|
2145
|
-
this.tools.set("fix", new TrieFixTool());
|
|
2146
|
-
this.tools.set("cloud_fix", new TrieCloudFixTool());
|
|
2147
|
-
this.tools.set("explain", new TrieExplainTool());
|
|
2148
|
-
this.tools.set("test", new TrieTestTool());
|
|
2149
|
-
this.tools.set("watch", new TrieWatchTool());
|
|
2150
|
-
this.tools.set("pr_review", new TriePRReviewTool());
|
|
2151
|
-
this.tools.set("project", new TrieProjectInfoTool());
|
|
2152
|
-
this.tools.set("init", new TrieInitTool());
|
|
2153
|
-
this.tools.set("memory", new TrieMemorySearchTool());
|
|
2154
|
-
this.tools.set("check", new TrieCheckTool());
|
|
2155
|
-
this.tools.set("tell", new TrieTellTool());
|
|
2156
|
-
this.tools.set("context", new TrieContextTool());
|
|
2157
|
-
this.tools.set("feedback", new TrieFeedbackTool());
|
|
2158
|
-
this.tools.set("ok", new TrieFeedbackTool());
|
|
2159
|
-
this.tools.set("bad", new TrieFeedbackTool());
|
|
2160
|
-
this.tools.set("linear_sync", new LinearSyncTool());
|
|
2161
|
-
this.tools.set("github_sync", new GitHubSyncTool());
|
|
2162
|
-
this.tools.set("github_branches", new GitHubBranchesTool());
|
|
2163
|
-
this.tools.set("pipeline", new TriePipelineTool());
|
|
2164
|
-
this.tools.set("index", new TrieIndexTool());
|
|
2165
|
-
this.tools.set("get_governance", new TrieGetGovernanceTool());
|
|
2166
|
-
this.tools.set("get_decisions", new TrieGetDecisionsTool());
|
|
2167
|
-
this.tools.set("get_blockers", new TrieGetBlockersTool());
|
|
2168
|
-
this.tools.set("get_nudges", new TrieGetNudgesTool());
|
|
2169
|
-
this.tools.set("query_ledger_blocks", new TrieQueryLedgerBlocksTool());
|
|
2170
|
-
this.tools.set("get_related_governance", new TrieGetRelatedGovernanceTool());
|
|
2171
|
-
this.tools.set("get_related_decisions", new TrieGetRelatedDecisionsTool());
|
|
2172
|
-
this.tools.set("query_context", new TrieQueryContextTool());
|
|
2173
|
-
}
|
|
2174
|
-
defineToolSchemas() {
|
|
2175
|
-
this.definitions = [
|
|
2176
|
-
{
|
|
2177
|
-
name: "trie",
|
|
2178
|
-
description: "Quick menu of available Trie commands. TIP: Read trie://context first for project state and priorities. Call with `action` to run directly.",
|
|
2179
|
-
inputSchema: {
|
|
2180
|
-
type: "object",
|
|
2181
|
-
properties: {
|
|
2182
|
-
action: {
|
|
2183
|
-
type: "string",
|
|
2184
|
-
description: "Optional quick action: pr_review, watch, fix, explain, test, visual_qa_browser"
|
|
2185
|
-
},
|
|
2186
|
-
files: {
|
|
2187
|
-
type: "array",
|
|
2188
|
-
items: { type: "string" },
|
|
2189
|
-
description: "Files to scan (absolute or relative)"
|
|
2190
|
-
},
|
|
2191
|
-
directory: {
|
|
2192
|
-
type: "string",
|
|
2193
|
-
description: "Directory to scan (defaults to detected workspace)"
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
},
|
|
2198
|
-
{
|
|
2199
|
-
name: "trie_fix",
|
|
2200
|
-
description: "Apply high-confidence fixes to code. Use action:route to see triage plan first. Alias: fix",
|
|
2201
|
-
inputSchema: {
|
|
2202
|
-
type: "object",
|
|
2203
|
-
properties: {
|
|
2204
|
-
action: {
|
|
2205
|
-
type: "string",
|
|
2206
|
-
enum: ["route"],
|
|
2207
|
-
description: "route: show fix routing plan without applying fixes"
|
|
2208
|
-
},
|
|
2209
|
-
issueIds: {
|
|
2210
|
-
type: "array",
|
|
2211
|
-
items: { type: "string" },
|
|
2212
|
-
description: "Specific issues to fix (empty = all high-confidence)"
|
|
2213
|
-
},
|
|
2214
|
-
autoApprove: {
|
|
2215
|
-
type: "boolean",
|
|
2216
|
-
description: "Skip human review for high-confidence fixes"
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
|
-
},
|
|
2221
|
-
{
|
|
2222
|
-
name: "trie_cloud_fix",
|
|
2223
|
-
description: "Dispatch issues to Cursor cloud agents for verified, test-passing fixes. The cloud agent runs in an isolated VM, applies the fix, runs tests, screenshots the result, and opens a PR. CRITICAL: Ad-hoc mode (file+issue+fix) dispatches ONLY that single issue. Use trie_fix action:route first to see which issues qualify.",
|
|
2224
|
-
inputSchema: {
|
|
2225
|
-
type: "object",
|
|
2226
|
-
properties: {
|
|
2227
|
-
action: {
|
|
2228
|
-
type: "string",
|
|
2229
|
-
enum: ["configure", "dispatch", "status", "artifacts", "cancel"],
|
|
2230
|
-
description: "configure: save API key | dispatch: send to cloud | status: poll jobs | artifacts: get screenshots + PR links | cancel: abort a job"
|
|
2231
|
-
},
|
|
2232
|
-
issueIds: {
|
|
2233
|
-
type: "array",
|
|
2234
|
-
items: { type: "string" },
|
|
2235
|
-
description: "Issue IDs to dispatch (from trie_fix/watch). If omitted, dispatches all cloud-eligible pending issues. IGNORED if file+issue+fix are provided (ad-hoc mode)."
|
|
2236
|
-
},
|
|
2237
|
-
forceCloud: {
|
|
2238
|
-
type: "boolean",
|
|
2239
|
-
description: "When true, bypass triage and dispatch ALL resolved issues to cloud agents. Use when user explicitly requests cloud fix. In ad-hoc mode, this is implied."
|
|
2240
|
-
},
|
|
2241
|
-
file: {
|
|
2242
|
-
type: "string",
|
|
2243
|
-
description: "For ad-hoc single-incident dispatch: file path. When provided with issue+fix, ONLY this single issue is dispatched (memory issues are ignored)."
|
|
2244
|
-
},
|
|
2245
|
-
issue: {
|
|
2246
|
-
type: "string",
|
|
2247
|
-
description: "For ad-hoc dispatch: issue description (use with file and fix)"
|
|
2248
|
-
},
|
|
2249
|
-
fix: {
|
|
2250
|
-
type: "string",
|
|
2251
|
-
description: "For ad-hoc dispatch: suggested fix (use with file and issue)"
|
|
2252
|
-
},
|
|
2253
|
-
line: { type: "number", description: "For ad-hoc dispatch: line number" },
|
|
2254
|
-
severity: { type: "string", description: "For ad-hoc dispatch: severity (critical, serious, moderate, low)" },
|
|
2255
|
-
effort: { type: "string", description: "For ad-hoc dispatch: effort (trivial, easy, medium, hard)" },
|
|
2256
|
-
apiKey: {
|
|
2257
|
-
type: "string",
|
|
2258
|
-
description: "Cursor API key (configure action only)"
|
|
2259
|
-
},
|
|
2260
|
-
jobId: {
|
|
2261
|
-
type: "string",
|
|
2262
|
-
description: "Job ID for cancel or artifacts actions"
|
|
2263
|
-
}
|
|
2264
|
-
},
|
|
2265
|
-
required: ["action"]
|
|
2266
|
-
}
|
|
2267
|
-
},
|
|
2268
|
-
{
|
|
2269
|
-
name: "trie_explain",
|
|
2270
|
-
description: "Explain code, issues, or changes in plain language. Alias: explain",
|
|
2271
|
-
inputSchema: {
|
|
2272
|
-
type: "object",
|
|
2273
|
-
properties: {
|
|
2274
|
-
type: {
|
|
2275
|
-
type: "string",
|
|
2276
|
-
enum: ["code", "issue", "change", "risk"]
|
|
2277
|
-
},
|
|
2278
|
-
target: {
|
|
2279
|
-
type: "string",
|
|
2280
|
-
description: "What to explain (file path, issue ID, etc.)"
|
|
2281
|
-
}
|
|
2282
|
-
},
|
|
2283
|
-
required: ["type", "target"]
|
|
2284
|
-
}
|
|
2285
|
-
},
|
|
2286
|
-
{
|
|
2287
|
-
name: "trie_feedback",
|
|
2288
|
-
description: "Record quick feedback about a warning or suggestion (thumbs up/down). Alias: trie_ok, trie_bad",
|
|
2289
|
-
inputSchema: {
|
|
2290
|
-
type: "object",
|
|
2291
|
-
properties: {
|
|
2292
|
-
helpful: { type: "boolean", description: "true for \u{1F44D}, false for \u{1F44E}" },
|
|
2293
|
-
target: { type: "string", description: "Optional file or item being rated" },
|
|
2294
|
-
note: { type: "string", description: "Optional short note about why" },
|
|
2295
|
-
files: {
|
|
2296
|
-
type: "array",
|
|
2297
|
-
items: { type: "string" },
|
|
2298
|
-
description: "Optional related files (absolute or relative)"
|
|
2299
|
-
},
|
|
2300
|
-
directory: { type: "string", description: "Working directory (defaults to auto-detected)" }
|
|
2301
|
-
},
|
|
2302
|
-
required: ["helpful"]
|
|
2303
|
-
}
|
|
2304
|
-
},
|
|
2305
|
-
{
|
|
2306
|
-
name: "trie_check",
|
|
2307
|
-
description: "Run Trie risk check on current changes. Modes: quick, full, offline.",
|
|
2308
|
-
inputSchema: {
|
|
2309
|
-
type: "object",
|
|
2310
|
-
properties: {
|
|
2311
|
-
directory: { type: "string" },
|
|
2312
|
-
files: { type: "array", items: { type: "string" } },
|
|
2313
|
-
mode: { type: "string", enum: ["quick", "full", "offline"] }
|
|
2314
|
-
}
|
|
2315
|
-
}
|
|
2316
|
-
},
|
|
2317
|
-
{
|
|
2318
|
-
name: "trie_tell",
|
|
2319
|
-
description: "Report an incident so Trie can learn.",
|
|
2320
|
-
inputSchema: {
|
|
2321
|
-
type: "object",
|
|
2322
|
-
properties: {
|
|
2323
|
-
description: { type: "string" },
|
|
2324
|
-
directory: { type: "string" }
|
|
2325
|
-
},
|
|
2326
|
-
required: ["description"]
|
|
2327
|
-
}
|
|
2328
|
-
},
|
|
2329
|
-
{
|
|
2330
|
-
name: "trie_context",
|
|
2331
|
-
description: "Return current context snapshot (nodes/edges) and export context.json.",
|
|
2332
|
-
inputSchema: {
|
|
2333
|
-
type: "object",
|
|
2334
|
-
properties: {
|
|
2335
|
-
directory: { type: "string" }
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
},
|
|
2339
|
-
{
|
|
2340
|
-
name: "trie_test",
|
|
2341
|
-
description: "Generate or reason about tests. Alias: test",
|
|
2342
|
-
inputSchema: {
|
|
2343
|
-
type: "object",
|
|
2344
|
-
properties: {
|
|
2345
|
-
action: {
|
|
2346
|
-
type: "string",
|
|
2347
|
-
enum: ["generate", "coverage", "suggest", "run"],
|
|
2348
|
-
description: "Test action to perform"
|
|
2349
|
-
},
|
|
2350
|
-
files: {
|
|
2351
|
-
type: "array",
|
|
2352
|
-
items: { type: "string" },
|
|
2353
|
-
description: "Target source files"
|
|
2354
|
-
},
|
|
2355
|
-
framework: {
|
|
2356
|
-
type: "string",
|
|
2357
|
-
description: "Optional framework hint (jest, vitest, playwright, etc.)"
|
|
2358
|
-
},
|
|
2359
|
-
style: {
|
|
2360
|
-
type: "string",
|
|
2361
|
-
description: "Test style (unit, integration, e2e). Defaults to unit."
|
|
2362
|
-
}
|
|
2363
|
-
},
|
|
2364
|
-
required: ["action", "files"]
|
|
2365
|
-
}
|
|
2366
|
-
},
|
|
2367
|
-
{
|
|
2368
|
-
name: "trie_watch",
|
|
2369
|
-
description: "Autonomous watch mode. Alias: watch",
|
|
2370
|
-
inputSchema: {
|
|
2371
|
-
type: "object",
|
|
2372
|
-
properties: {
|
|
2373
|
-
action: {
|
|
2374
|
-
type: "string",
|
|
2375
|
-
enum: ["start", "stop", "status", "issues"],
|
|
2376
|
-
description: "Watch action"
|
|
2377
|
-
},
|
|
2378
|
-
directory: {
|
|
2379
|
-
type: "string",
|
|
2380
|
-
description: "Workspace directory to watch (optional, auto-detected)"
|
|
2381
|
-
},
|
|
2382
|
-
debounceMs: {
|
|
2383
|
-
type: "number",
|
|
2384
|
-
description: "Debounce time for scans (ms)"
|
|
2385
|
-
}
|
|
2386
|
-
},
|
|
2387
|
-
required: ["action"]
|
|
2388
|
-
}
|
|
2389
|
-
},
|
|
2390
|
-
{
|
|
2391
|
-
name: "trie_project",
|
|
2392
|
-
description: "View and manage project information (.trie/PROJECT.md). Store project context for AI assistants. Alias: project",
|
|
2393
|
-
inputSchema: {
|
|
2394
|
-
type: "object",
|
|
2395
|
-
properties: {
|
|
2396
|
-
action: {
|
|
2397
|
-
type: "string",
|
|
2398
|
-
enum: ["view", "init", "update", "append", "sections", "raw"],
|
|
2399
|
-
description: "Action: view (default), init (create template), update (replace section), append (add to section), sections (list), raw (full file)"
|
|
2400
|
-
},
|
|
2401
|
-
section: {
|
|
2402
|
-
type: "string",
|
|
2403
|
-
description: 'Section name (e.g., "Project Overview", "Technology Stack", "AI Instructions")'
|
|
2404
|
-
},
|
|
2405
|
-
content: {
|
|
2406
|
-
type: "string",
|
|
2407
|
-
description: "Content for update/append actions"
|
|
2408
|
-
},
|
|
2409
|
-
directory: {
|
|
2410
|
-
type: "string",
|
|
2411
|
-
description: "Project directory (defaults to current workspace)"
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2415
|
-
},
|
|
2416
|
-
{
|
|
2417
|
-
name: "trie_init",
|
|
2418
|
-
description: "Initialize bootstrap files (.trie/RULES.md, .trie/TEAM.md, .trie/BOOTSTRAP.md). Detects stack.",
|
|
2419
|
-
inputSchema: {
|
|
2420
|
-
type: "object",
|
|
2421
|
-
properties: {
|
|
2422
|
-
action: {
|
|
2423
|
-
type: "string",
|
|
2424
|
-
enum: ["init", "status", "complete", "context"],
|
|
2425
|
-
description: "Action: init (create files), status (check state), complete (finish bootstrap), context (get injected content)"
|
|
2426
|
-
},
|
|
2427
|
-
directory: {
|
|
2428
|
-
type: "string",
|
|
2429
|
-
description: "Project directory (defaults to current workspace)"
|
|
2430
|
-
},
|
|
2431
|
-
force: {
|
|
2432
|
-
type: "boolean",
|
|
2433
|
-
description: "Overwrite existing files"
|
|
2434
|
-
},
|
|
2435
|
-
skipBootstrap: {
|
|
2436
|
-
type: "boolean",
|
|
2437
|
-
description: "Skip creating BOOTSTRAP.md"
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
},
|
|
2442
|
-
{
|
|
2443
|
-
name: "trie_memory",
|
|
2444
|
-
description: "Search and manage issue memory. Find similar issues, view stats, search across projects, and purge old issues.",
|
|
2445
|
-
inputSchema: {
|
|
2446
|
-
type: "object",
|
|
2447
|
-
properties: {
|
|
2448
|
-
action: {
|
|
2449
|
-
type: "string",
|
|
2450
|
-
enum: ["search", "stats", "recent", "similar", "resolve", "global", "purge"],
|
|
2451
|
-
description: "Action: search (find issues), stats (show statistics), recent (recent issues), similar (find similar), resolve (mark resolved), global (cross-project), purge (free up memory)"
|
|
2452
|
-
},
|
|
2453
|
-
query: {
|
|
2454
|
-
type: "string",
|
|
2455
|
-
description: "Search query for search/similar/global actions"
|
|
2456
|
-
},
|
|
2457
|
-
issueId: {
|
|
2458
|
-
type: "string",
|
|
2459
|
-
description: "Issue ID for resolve action"
|
|
2460
|
-
},
|
|
2461
|
-
limit: {
|
|
2462
|
-
type: "number",
|
|
2463
|
-
description: "Maximum results to return"
|
|
2464
|
-
},
|
|
2465
|
-
severity: {
|
|
2466
|
-
type: "array",
|
|
2467
|
-
items: { type: "string" },
|
|
2468
|
-
description: "Filter by severity levels"
|
|
2469
|
-
},
|
|
2470
|
-
agent: {
|
|
2471
|
-
type: "string",
|
|
2472
|
-
description: "Filter by agent name"
|
|
2473
|
-
},
|
|
2474
|
-
includeResolved: {
|
|
2475
|
-
type: "boolean",
|
|
2476
|
-
description: "Include resolved issues in search"
|
|
2477
|
-
},
|
|
2478
|
-
directory: {
|
|
2479
|
-
type: "string",
|
|
2480
|
-
description: "Project directory (defaults to current workspace)"
|
|
2481
|
-
},
|
|
2482
|
-
purgeStrategy: {
|
|
2483
|
-
type: "string",
|
|
2484
|
-
enum: ["smart", "resolved", "old", "all"],
|
|
2485
|
-
description: "Purge strategy: smart (remove resolved & old low-priority), resolved (all resolved), old (older than daysOld), all (clear all)"
|
|
2486
|
-
},
|
|
2487
|
-
daysOld: {
|
|
2488
|
-
type: "number",
|
|
2489
|
-
description: "Days threshold for old purge strategy (default 90)"
|
|
2490
|
-
}
|
|
2491
|
-
}
|
|
2492
|
-
},
|
|
2493
|
-
// MCP Apps: Interactive memory viewer
|
|
2494
|
-
_meta: {
|
|
2495
|
-
ui: { resourceUri: "ui://trie/memory-viewer" }
|
|
2496
|
-
}
|
|
2497
|
-
},
|
|
2498
|
-
{
|
|
2499
|
-
name: "trie_index",
|
|
2500
|
-
description: "Index codebase for fast goal checking and file lookups. Index is auto-updated during watch mode. Use this for initial indexing or to rebuild the index.",
|
|
2501
|
-
inputSchema: {
|
|
2502
|
-
type: "object",
|
|
2503
|
-
properties: {
|
|
2504
|
-
action: {
|
|
2505
|
-
type: "string",
|
|
2506
|
-
enum: ["full", "status", "clear"],
|
|
2507
|
-
description: "Action: full (index all files), status (show index stats), clear (remove stale cache)"
|
|
2508
|
-
},
|
|
2509
|
-
directory: {
|
|
2510
|
-
type: "string",
|
|
2511
|
-
description: "Project directory (defaults to current workspace)"
|
|
2512
|
-
}
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
},
|
|
2516
|
-
// Add remaining tool definitions...
|
|
2517
|
-
this.createSpecialAgentDefinitions()
|
|
2518
|
-
].flat();
|
|
2519
|
-
}
|
|
2520
|
-
createSpecialAgentDefinitions() {
|
|
2521
|
-
return [
|
|
2522
|
-
{
|
|
2523
|
-
name: "trie_visual_qa_browser",
|
|
2524
|
-
description: "Capture screenshots at mobile/tablet/desktop viewports for visual QA. Alias: visual_qa_browser",
|
|
2525
|
-
inputSchema: {
|
|
2526
|
-
type: "object",
|
|
2527
|
-
properties: {
|
|
2528
|
-
url: { type: "string", description: "URL to screenshot" },
|
|
2529
|
-
port: { type: "number", description: "Specific port to check" },
|
|
2530
|
-
waitForSelector: { type: "string", description: "CSS selector to wait for" },
|
|
2531
|
-
waitMs: { type: "number", description: "Additional wait time" }
|
|
2532
|
-
}
|
|
2533
|
-
},
|
|
2534
|
-
// MCP Apps: Interactive visual QA gallery
|
|
2535
|
-
_meta: {
|
|
2536
|
-
ui: { resourceUri: "ui://trie/visual-qa" }
|
|
2537
|
-
}
|
|
2538
|
-
},
|
|
2539
|
-
{
|
|
2540
|
-
name: "trie_pr_review",
|
|
2541
|
-
description: "\u{1F50D} Interactive PR review: walks through changes file-by-file. Alias: pr_review",
|
|
2542
|
-
inputSchema: {
|
|
2543
|
-
type: "object",
|
|
2544
|
-
properties: {
|
|
2545
|
-
pr: { type: "string", description: "PR number to review" },
|
|
2546
|
-
worktree: { type: "string", description: "Path to worktree directory" },
|
|
2547
|
-
mode: {
|
|
2548
|
-
type: "string",
|
|
2549
|
-
enum: ["own", "others"],
|
|
2550
|
-
description: "Review mode"
|
|
2551
|
-
},
|
|
2552
|
-
files: {
|
|
2553
|
-
type: "array",
|
|
2554
|
-
items: { type: "string" },
|
|
2555
|
-
description: "Specific files to review"
|
|
2556
|
-
}
|
|
2557
|
-
}
|
|
2558
|
-
},
|
|
2559
|
-
// MCP Apps: Interactive PR review UI
|
|
2560
|
-
_meta: {
|
|
2561
|
-
ui: { resourceUri: "ui://trie/pr-review" }
|
|
2562
|
-
}
|
|
2563
|
-
},
|
|
2564
|
-
{
|
|
2565
|
-
name: "trie_linear_sync",
|
|
2566
|
-
description: "Sync active Linear tickets to build context for JIT defect prediction. Alias: linear_sync",
|
|
2567
|
-
inputSchema: {
|
|
2568
|
-
type: "object",
|
|
2569
|
-
properties: {
|
|
2570
|
-
directory: { type: "string", description: "Project directory" }
|
|
2571
|
-
}
|
|
2572
|
-
}
|
|
2573
|
-
},
|
|
2574
|
-
{
|
|
2575
|
-
name: "trie_github_sync",
|
|
2576
|
-
description: "Sync open PRs and issues from GitHub into the Trie context graph. Links PRs to files they touch and to Linear tickets mentioned in PR descriptions. Run once to initialize, or periodically to stay current.",
|
|
2577
|
-
inputSchema: {
|
|
2578
|
-
type: "object",
|
|
2579
|
-
properties: {
|
|
2580
|
-
directory: { type: "string", description: "Project directory (defaults to current workspace)" }
|
|
2581
|
-
}
|
|
2582
|
-
}
|
|
2583
|
-
},
|
|
2584
|
-
{
|
|
2585
|
-
name: "trie_github_branches",
|
|
2586
|
-
description: "Fetch GitHub branches with latest commit info. Use when the user asks which branch has the latest updates, what branches exist, or when branches were last updated. Requires GitHub API key.",
|
|
2587
|
-
inputSchema: {
|
|
2588
|
-
type: "object",
|
|
2589
|
-
properties: {
|
|
2590
|
-
directory: { type: "string", description: "Project directory (defaults to current workspace)" },
|
|
2591
|
-
limit: { type: "number", description: "Max branches to return (default 15, max 30)" }
|
|
2592
|
-
}
|
|
2593
|
-
}
|
|
2594
|
-
},
|
|
2595
|
-
{
|
|
2596
|
-
name: "trie_pipeline",
|
|
2597
|
-
description: "Get consolidated pipeline status: open PRs, active Linear tickets, open GitHub issues, and Trie issues not yet in any ticket or PR (coverage gaps). Use to understand where work stands and what is falling through the cracks.",
|
|
2598
|
-
inputSchema: {
|
|
2599
|
-
type: "object",
|
|
2600
|
-
properties: {
|
|
2601
|
-
action: {
|
|
2602
|
-
type: "string",
|
|
2603
|
-
enum: ["status", "coverage", "create_tickets"],
|
|
2604
|
-
description: "status: full pipeline view | coverage: only show Trie issues with no ticket/PR | create_tickets: open Linear tickets for uncovered issues"
|
|
2605
|
-
},
|
|
2606
|
-
focus: {
|
|
2607
|
-
type: "string",
|
|
2608
|
-
description: "Optional file path or Linear ticket ID to narrow the view"
|
|
2609
|
-
},
|
|
2610
|
-
issueIds: {
|
|
2611
|
-
type: "array",
|
|
2612
|
-
items: { type: "string" },
|
|
2613
|
-
description: "Issue IDs to create tickets for (create_tickets action only)"
|
|
2614
|
-
},
|
|
2615
|
-
directory: { type: "string", description: "Project directory" }
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
},
|
|
2619
|
-
{
|
|
2620
|
-
name: "trie_get_governance",
|
|
2621
|
-
description: "Query governance records (architectural decisions, standards, team agreements) from governance ledger with targeted retrieval. Prevents context pollution by returning only relevant records.",
|
|
2622
|
-
inputSchema: {
|
|
2623
|
-
type: "object",
|
|
2624
|
-
properties: {
|
|
2625
|
-
relatedTo: { type: "string", description: "File path or topic to find related governance" },
|
|
2626
|
-
tags: { type: "array", items: { type: "string" }, description: "Filter by tags" },
|
|
2627
|
-
since: { type: "string", description: 'Time filter: ISO date or "7d", "30d", "90d"' },
|
|
2628
|
-
limit: { type: "number", description: "Max results (default 10)" },
|
|
2629
|
-
directory: { type: "string", description: "Working directory" }
|
|
2630
|
-
}
|
|
2631
|
-
}
|
|
2632
|
-
},
|
|
2633
|
-
{
|
|
2634
|
-
name: "trie_get_decisions",
|
|
2635
|
-
description: "[DEPRECATED - use trie_get_governance] Query governance records from ledger. Backward compatibility alias.",
|
|
2636
|
-
inputSchema: {
|
|
2637
|
-
type: "object",
|
|
2638
|
-
properties: {
|
|
2639
|
-
relatedTo: { type: "string", description: "File path or topic to find related governance" },
|
|
2640
|
-
tags: { type: "array", items: { type: "string" }, description: "Filter by tags" },
|
|
2641
|
-
since: { type: "string", description: 'Time filter: ISO date or "7d", "30d", "90d"' },
|
|
2642
|
-
limit: { type: "number", description: "Max results (default 10)" },
|
|
2643
|
-
directory: { type: "string", description: "Working directory" }
|
|
2644
|
-
}
|
|
2645
|
-
}
|
|
2646
|
-
},
|
|
2647
|
-
{
|
|
2648
|
-
name: "trie_get_blockers",
|
|
2649
|
-
description: 'Get active blockers from governance ledger. Returns only unresolved blockers. NOT the same as "blocks" (ledger chain) \u2014 use trie_query_ledger_blocks for that.',
|
|
2650
|
-
inputSchema: {
|
|
2651
|
-
type: "object",
|
|
2652
|
-
properties: {
|
|
2653
|
-
tags: { type: "array", items: { type: "string" }, description: "Filter by tags" },
|
|
2654
|
-
limit: { type: "number", description: "Max results (default 5)" },
|
|
2655
|
-
directory: { type: "string", description: "Working directory" }
|
|
2656
|
-
}
|
|
2657
|
-
}
|
|
2658
|
-
},
|
|
2659
|
-
{
|
|
2660
|
-
name: "trie_get_nudges",
|
|
2661
|
-
description: "Get unresolved nudges (goal violations). Use when automating fixes: call trie_get_nudges, then trie_propose_fix or trie_propose_fixes_batch with file/goal/violation from results.",
|
|
2662
|
-
inputSchema: {
|
|
2663
|
-
type: "object",
|
|
2664
|
-
properties: {
|
|
2665
|
-
limit: { type: "number", description: "Max results (default 20)" },
|
|
2666
|
-
severity: { type: "string", enum: ["critical", "high", "warning", "info"], description: "Filter by severity" },
|
|
2667
|
-
file: { type: "string", description: "Filter by file path" },
|
|
2668
|
-
directory: { type: "string", description: "Working directory" }
|
|
2669
|
-
}
|
|
2670
|
-
}
|
|
2671
|
-
},
|
|
2672
|
-
{
|
|
2673
|
-
name: "trie_query_ledger_blocks",
|
|
2674
|
-
description: 'Query the ledger chain blocks \u2014 the tamper-evident chain of recorded issues. Use when user asks about "blocks", "ledger chain", "what issues in my blocks", "most frequent issues in blocks", etc. Returns block summary and aggregation by file, severity, and agent.',
|
|
2675
|
-
inputSchema: {
|
|
2676
|
-
type: "object",
|
|
2677
|
-
properties: {
|
|
2678
|
-
analysis: { type: "string", enum: ["summary", "issues", "all"], description: "What to analyze: summary, issues (default), or all" },
|
|
2679
|
-
limit: { type: "number", description: "Max blocks to include (default 50)" },
|
|
2680
|
-
directory: { type: "string", description: "Working directory" }
|
|
2681
|
-
}
|
|
2682
|
-
}
|
|
2683
|
-
},
|
|
2684
|
-
{
|
|
2685
|
-
name: "trie_get_related_governance",
|
|
2686
|
-
description: "Find governance records related to a specific governance record, file, or topic. Targeted context retrieval.",
|
|
2687
|
-
inputSchema: {
|
|
2688
|
-
type: "object",
|
|
2689
|
-
properties: {
|
|
2690
|
-
governanceId: { type: "string", description: "Governance ID to find related records" },
|
|
2691
|
-
file: { type: "string", description: "File path to find related governance" },
|
|
2692
|
-
topic: { type: "string", description: "Topic to find related governance" },
|
|
2693
|
-
limit: { type: "number", description: "Max results (default 5)" },
|
|
2694
|
-
directory: { type: "string", description: "Working directory" }
|
|
2695
|
-
}
|
|
2696
|
-
}
|
|
2697
|
-
},
|
|
2698
|
-
{
|
|
2699
|
-
name: "trie_get_related_decisions",
|
|
2700
|
-
description: "[DEPRECATED - use trie_get_related_governance] Find related governance records. Backward compatibility alias.",
|
|
2701
|
-
inputSchema: {
|
|
2702
|
-
type: "object",
|
|
2703
|
-
properties: {
|
|
2704
|
-
decisionId: { type: "string", description: "Decision ID to find related records" },
|
|
2705
|
-
file: { type: "string", description: "File path to find related governance" },
|
|
2706
|
-
topic: { type: "string", description: "Topic to find related governance" },
|
|
2707
|
-
limit: { type: "number", description: "Max results (default 5)" },
|
|
2708
|
-
directory: { type: "string", description: "Working directory" }
|
|
2709
|
-
}
|
|
2710
|
-
}
|
|
2711
|
-
},
|
|
2712
|
-
{
|
|
2713
|
-
name: "trie_query_context",
|
|
2714
|
-
description: 'Natural-language search across ALL Trie context: goals, hypotheses, nudges (goal violations), incidents, governance, blockers. Use for "what are my goals", "show hypotheses", "any nudges", "show incidents", "recent governance", etc.',
|
|
2715
|
-
inputSchema: {
|
|
2716
|
-
type: "object",
|
|
2717
|
-
properties: {
|
|
2718
|
-
query: { type: "string", description: "Natural language query (e.g. goals, hypotheses, nudges, incidents, governance)" },
|
|
2719
|
-
type: {
|
|
2720
|
-
type: "string",
|
|
2721
|
-
enum: ["goals", "hypotheses", "nudges", "incidents", "governance", "decisions", "blockers", "facts", "questions", "all"],
|
|
2722
|
-
description: 'Type of context to query (default: all). "decisions" is deprecated, use "governance"'
|
|
2723
|
-
},
|
|
2724
|
-
limit: { type: "number", description: "Max results per type (default 10)" },
|
|
2725
|
-
directory: { type: "string", description: "Working directory" }
|
|
2726
|
-
},
|
|
2727
|
-
required: ["query"]
|
|
2728
|
-
}
|
|
2729
|
-
}
|
|
2730
|
-
];
|
|
2731
|
-
}
|
|
2732
|
-
getTool(name) {
|
|
2733
|
-
return this.tools.get(name);
|
|
2734
|
-
}
|
|
2735
|
-
getAllTools() {
|
|
2736
|
-
return this.definitions;
|
|
2737
|
-
}
|
|
2738
|
-
getToolNames() {
|
|
2739
|
-
return Array.from(this.tools.keys());
|
|
2740
|
-
}
|
|
2741
|
-
hasTool(name) {
|
|
2742
|
-
return this.tools.has(name);
|
|
2743
|
-
}
|
|
2744
|
-
};
|
|
2745
|
-
|
|
2746
|
-
// src/server/resource-manager.ts
|
|
2747
|
-
import { readdir, readFile as readFile3 } from "fs/promises";
|
|
2748
|
-
import { existsSync as existsSync3 } from "fs";
|
|
2749
|
-
import { join as join3, dirname as dirname2 } from "path";
|
|
2750
|
-
import { fileURLToPath } from "url";
|
|
2751
|
-
var UI_APPS = {
|
|
2752
|
-
"ledger": {
|
|
2753
|
-
name: "Decision Ledger",
|
|
2754
|
-
description: "Track decisions, blockers, facts, and questions across your project"
|
|
2755
|
-
},
|
|
2756
|
-
"goals": {
|
|
2757
|
-
name: "Goals & Progress",
|
|
2758
|
-
description: "Monitor code quality goals and track progress with visual indicators"
|
|
2759
|
-
},
|
|
2760
|
-
"hypotheses": {
|
|
2761
|
-
name: "Hypotheses",
|
|
2762
|
-
description: "Test and validate development theories with evidence-based reasoning"
|
|
2763
|
-
},
|
|
2764
|
-
"nudges": {
|
|
2765
|
-
name: "Nudges & Insights",
|
|
2766
|
-
description: "AI-powered code quality insights, warnings, and suggestions"
|
|
2767
|
-
},
|
|
2768
|
-
"chat": {
|
|
2769
|
-
name: "AI Chat",
|
|
2770
|
-
description: "Interactive chat with Trie assistant for code analysis and guidance"
|
|
2771
|
-
}
|
|
2772
|
-
};
|
|
2773
|
-
var ResourceManager = class {
|
|
2774
|
-
/**
|
|
2775
|
-
* Get all available resources dynamically
|
|
2776
|
-
*/
|
|
2777
|
-
async getAvailableResources() {
|
|
2778
|
-
const resources = [];
|
|
2779
|
-
resources.push(
|
|
2780
|
-
{
|
|
2781
|
-
uri: "trie://context",
|
|
2782
|
-
name: "AI Context (Consolidated)",
|
|
2783
|
-
description: "Single source of truth: project state, coding rules, recent issues, and cross-project patterns. Read this first.",
|
|
2784
|
-
mimeType: "text/markdown"
|
|
2785
|
-
},
|
|
2786
|
-
{
|
|
2787
|
-
uri: "trie://project",
|
|
2788
|
-
name: "Project Information",
|
|
2789
|
-
description: "User-defined project context (description, conventions, architecture, AI instructions)",
|
|
2790
|
-
mimeType: "text/markdown"
|
|
2791
|
-
},
|
|
2792
|
-
{
|
|
2793
|
-
uri: "trie://context/state",
|
|
2794
|
-
name: "Context State",
|
|
2795
|
-
description: "Detailed context state with scan history and priorities",
|
|
2796
|
-
mimeType: "application/json"
|
|
2797
|
-
},
|
|
2798
|
-
{
|
|
2799
|
-
uri: "trie://config",
|
|
2800
|
-
name: "Trie Configuration",
|
|
2801
|
-
description: "Current Trie configuration settings",
|
|
2802
|
-
mimeType: "application/json"
|
|
2803
|
-
},
|
|
2804
|
-
{
|
|
2805
|
-
uri: "trie://cache/stats",
|
|
2806
|
-
name: "Cache Statistics",
|
|
2807
|
-
description: "Trie scan cache statistics and performance metrics",
|
|
2808
|
-
mimeType: "application/json"
|
|
2809
|
-
},
|
|
2810
|
-
{
|
|
2811
|
-
uri: "trie://signatures",
|
|
2812
|
-
name: "Vulnerability Signatures",
|
|
2813
|
-
description: "Summary of loaded vulnerability detection signatures",
|
|
2814
|
-
mimeType: "application/json"
|
|
2815
|
-
},
|
|
2816
|
-
{
|
|
2817
|
-
uri: "trie://bootstrap",
|
|
2818
|
-
name: "Bootstrap Status",
|
|
2819
|
-
description: "Bootstrap file status and project setup context",
|
|
2820
|
-
mimeType: "application/json"
|
|
2821
|
-
},
|
|
2822
|
-
{
|
|
2823
|
-
uri: "trie://rules",
|
|
2824
|
-
name: "Project Rules",
|
|
2825
|
-
description: "User-defined coding standards from .trie/RULES.md",
|
|
2826
|
-
mimeType: "text/markdown"
|
|
2827
|
-
},
|
|
2828
|
-
{
|
|
2829
|
-
uri: "trie://team",
|
|
2830
|
-
name: "Team Info",
|
|
2831
|
-
description: "Team ownership and escalation from .trie/TEAM.md",
|
|
2832
|
-
mimeType: "text/markdown"
|
|
2833
|
-
},
|
|
2834
|
-
{
|
|
2835
|
-
uri: "trie://memory",
|
|
2836
|
-
name: "Issue Memory",
|
|
2837
|
-
description: "Issue memory statistics and recent issues",
|
|
2838
|
-
mimeType: "application/json"
|
|
2839
|
-
},
|
|
2840
|
-
{
|
|
2841
|
-
uri: "trie://memory/global",
|
|
2842
|
-
name: "Global Memory",
|
|
2843
|
-
description: "Cross-project patterns and statistics",
|
|
2844
|
-
mimeType: "application/json"
|
|
2845
|
-
}
|
|
2846
|
-
);
|
|
2847
|
-
resources.push(...await this.getScanReportResources());
|
|
2848
|
-
resources.push(...this.getUIAppResources());
|
|
2849
|
-
return resources;
|
|
2850
|
-
}
|
|
2851
|
-
async getScanReportResources() {
|
|
2852
|
-
try {
|
|
2853
|
-
const reportsDir = join3(getWorkingDirectory(void 0, true), "trie-reports");
|
|
2854
|
-
const files = await readdir(reportsDir);
|
|
2855
|
-
const reportFiles = files.filter((f) => f.endsWith(".txt") || f.endsWith(".json"));
|
|
2856
|
-
return reportFiles.slice(0, 10).map((file) => ({
|
|
2857
|
-
uri: `trie://reports/${file}`,
|
|
2858
|
-
name: `Scan Report: ${file}`,
|
|
2859
|
-
description: `Trie scan report from ${file}`,
|
|
2860
|
-
mimeType: file.endsWith(".json") ? "application/json" : "text/plain"
|
|
2861
|
-
}));
|
|
2862
|
-
} catch {
|
|
2863
|
-
return [];
|
|
2864
|
-
}
|
|
2865
|
-
}
|
|
2866
|
-
/**
|
|
2867
|
-
* Read content for a specific resource
|
|
2868
|
-
*/
|
|
2869
|
-
async readResourceContent(uri) {
|
|
2870
|
-
if (uri.startsWith("ui://trie/")) {
|
|
2871
|
-
const appId = uri.replace("ui://trie/", "");
|
|
2872
|
-
return await this.getUIAppResource(uri, appId);
|
|
2873
|
-
}
|
|
2874
|
-
const parsedUri = uri.replace("trie://", "");
|
|
2875
|
-
if (parsedUri === "context") {
|
|
2876
|
-
return await this.getContextResource(uri);
|
|
2877
|
-
}
|
|
2878
|
-
if (parsedUri === "project") {
|
|
2879
|
-
return await this.getProjectResource(uri);
|
|
2880
|
-
}
|
|
2881
|
-
if (parsedUri === "context/state") {
|
|
2882
|
-
return await this.getContextStateResource(uri);
|
|
2883
|
-
}
|
|
2884
|
-
if (parsedUri === "config") {
|
|
2885
|
-
return await this.getConfigResource(uri);
|
|
2886
|
-
}
|
|
2887
|
-
if (parsedUri === "cache/stats") {
|
|
2888
|
-
return await this.getCacheStatsResource(uri);
|
|
2889
|
-
}
|
|
2890
|
-
if (parsedUri === "signatures") {
|
|
2891
|
-
return await this.getSignaturesResource(uri);
|
|
2892
|
-
}
|
|
2893
|
-
if (parsedUri === "bootstrap") {
|
|
2894
|
-
return await this.getBootstrapResource(uri);
|
|
2895
|
-
}
|
|
2896
|
-
if (parsedUri === "rules") {
|
|
2897
|
-
return await this.getRulesResource(uri);
|
|
2898
|
-
}
|
|
2899
|
-
if (parsedUri === "team") {
|
|
2900
|
-
return await this.getTeamResource(uri);
|
|
2901
|
-
}
|
|
2902
|
-
if (parsedUri === "memory") {
|
|
2903
|
-
return await this.getMemoryResource(uri);
|
|
2904
|
-
}
|
|
2905
|
-
if (parsedUri === "memory/global") {
|
|
2906
|
-
return await this.getGlobalMemoryResource(uri);
|
|
2907
|
-
}
|
|
2908
|
-
if (parsedUri.startsWith("reports/")) {
|
|
2909
|
-
return await this.getScanReportResource(uri, parsedUri);
|
|
2910
|
-
}
|
|
2911
|
-
throw new Error(`Unknown resource: ${uri}`);
|
|
2912
|
-
}
|
|
2913
|
-
/**
|
|
2914
|
-
* Get UI App resources for MCP Apps
|
|
2915
|
-
*/
|
|
2916
|
-
getUIAppResources() {
|
|
2917
|
-
return Object.entries(UI_APPS).map(([id, app]) => ({
|
|
2918
|
-
uri: `ui://trie/${id}`,
|
|
2919
|
-
name: app.name,
|
|
2920
|
-
description: app.description,
|
|
2921
|
-
mimeType: "text/html;profile=mcp-app"
|
|
2922
|
-
}));
|
|
2923
|
-
}
|
|
2924
|
-
/**
|
|
2925
|
-
* Read UI App resource content (bundled HTML)
|
|
2926
|
-
*/
|
|
2927
|
-
async getUIAppResource(uri, appId) {
|
|
2928
|
-
const currentFile = fileURLToPath(import.meta.url);
|
|
2929
|
-
const distDir = dirname2(dirname2(currentFile));
|
|
2930
|
-
const uiDir = join3(distDir, "ui");
|
|
2931
|
-
const htmlPath = join3(uiDir, `${appId}.html`);
|
|
2932
|
-
try {
|
|
2933
|
-
if (!existsSync3(htmlPath)) {
|
|
2934
|
-
return {
|
|
2935
|
-
contents: [{
|
|
2936
|
-
uri,
|
|
2937
|
-
mimeType: "text/html;profile=mcp-app",
|
|
2938
|
-
text: this.getFallbackUIHtml(appId)
|
|
2939
|
-
}]
|
|
2940
|
-
};
|
|
2941
|
-
}
|
|
2942
|
-
const content = await readFile3(htmlPath, "utf-8");
|
|
2943
|
-
return {
|
|
2944
|
-
contents: [{
|
|
2945
|
-
uri,
|
|
2946
|
-
mimeType: "text/html;profile=mcp-app",
|
|
2947
|
-
text: content
|
|
2948
|
-
}]
|
|
2949
|
-
};
|
|
2950
|
-
} catch (error) {
|
|
2951
|
-
return {
|
|
2952
|
-
contents: [{
|
|
2953
|
-
uri,
|
|
2954
|
-
mimeType: "text/html;profile=mcp-app",
|
|
2955
|
-
text: this.getFallbackUIHtml(appId)
|
|
2956
|
-
}]
|
|
2957
|
-
};
|
|
2958
|
-
}
|
|
2959
|
-
}
|
|
2960
|
-
/**
|
|
2961
|
-
* Generate fallback HTML for UI apps that haven't been built yet
|
|
2962
|
-
*/
|
|
2963
|
-
getFallbackUIHtml(appId) {
|
|
2964
|
-
const app = UI_APPS[appId];
|
|
2965
|
-
const title = app?.name || "Trie UI";
|
|
2966
|
-
const description = app?.description || "Loading...";
|
|
2967
|
-
return `<!DOCTYPE html>
|
|
2968
|
-
<html lang="en">
|
|
2969
|
-
<head>
|
|
2970
|
-
<meta charset="UTF-8">
|
|
2971
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2972
|
-
<title>${title} - Trie</title>
|
|
2973
|
-
<style>
|
|
2974
|
-
:root {
|
|
2975
|
-
--bg: #0d1117;
|
|
2976
|
-
--surface: #161b22;
|
|
2977
|
-
--border: #30363d;
|
|
2978
|
-
--text: #e6edf3;
|
|
2979
|
-
--text-muted: #8b949e;
|
|
2980
|
-
--primary: #58a6ff;
|
|
2981
|
-
}
|
|
2982
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
2983
|
-
body {
|
|
2984
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
2985
|
-
background: var(--bg);
|
|
2986
|
-
color: var(--text);
|
|
2987
|
-
display: flex;
|
|
2988
|
-
align-items: center;
|
|
2989
|
-
justify-content: center;
|
|
2990
|
-
min-height: 100vh;
|
|
2991
|
-
padding: 24px;
|
|
2992
|
-
}
|
|
2993
|
-
.container {
|
|
2994
|
-
text-align: center;
|
|
2995
|
-
max-width: 400px;
|
|
2996
|
-
}
|
|
2997
|
-
h1 {
|
|
2998
|
-
font-size: 24px;
|
|
2999
|
-
margin-bottom: 8px;
|
|
3000
|
-
}
|
|
3001
|
-
p {
|
|
3002
|
-
color: var(--text-muted);
|
|
3003
|
-
margin-bottom: 24px;
|
|
3004
|
-
}
|
|
3005
|
-
.status {
|
|
3006
|
-
display: inline-flex;
|
|
3007
|
-
align-items: center;
|
|
3008
|
-
gap: 8px;
|
|
3009
|
-
padding: 8px 16px;
|
|
3010
|
-
background: var(--surface);
|
|
3011
|
-
border: 1px solid var(--border);
|
|
3012
|
-
border-radius: 8px;
|
|
3013
|
-
color: var(--primary);
|
|
3014
|
-
}
|
|
3015
|
-
.spinner {
|
|
3016
|
-
width: 16px;
|
|
3017
|
-
height: 16px;
|
|
3018
|
-
border: 2px solid var(--border);
|
|
3019
|
-
border-top-color: var(--primary);
|
|
3020
|
-
border-radius: 50%;
|
|
3021
|
-
animation: spin 0.8s linear infinite;
|
|
3022
|
-
}
|
|
3023
|
-
@keyframes spin { to { transform: rotate(360deg); } }
|
|
3024
|
-
</style>
|
|
3025
|
-
</head>
|
|
3026
|
-
<body>
|
|
3027
|
-
<div class="container">
|
|
3028
|
-
<h1>${title}</h1>
|
|
3029
|
-
<p>${description}</p>
|
|
3030
|
-
<div class="status">
|
|
3031
|
-
<div class="spinner"></div>
|
|
3032
|
-
<span>Connecting to Trie...</span>
|
|
3033
|
-
</div>
|
|
3034
|
-
</div>
|
|
3035
|
-
<script type="module">
|
|
3036
|
-
// MCP Apps SDK connection
|
|
3037
|
-
import { App } from "@modelcontextprotocol/ext-apps";
|
|
3038
|
-
|
|
3039
|
-
const app = new App();
|
|
3040
|
-
await app.connect();
|
|
3041
|
-
|
|
3042
|
-
app.ontoolresult = (result) => {
|
|
3043
|
-
document.querySelector('.status span').textContent = 'Connected! Waiting for data...';
|
|
3044
|
-
console.log('Received tool result:', result);
|
|
3045
|
-
};
|
|
3046
|
-
</script>
|
|
3047
|
-
</body>
|
|
3048
|
-
</html>`;
|
|
3049
|
-
}
|
|
3050
|
-
async getContextResource(uri) {
|
|
3051
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
3052
|
-
const state = await loadContextState();
|
|
3053
|
-
const summary = [
|
|
3054
|
-
"# Trie Context",
|
|
3055
|
-
"",
|
|
3056
|
-
"> Quick reference for AI assistants. Detailed sections below.",
|
|
3057
|
-
"",
|
|
3058
|
-
"## Quick Status",
|
|
3059
|
-
"",
|
|
3060
|
-
`| Metric | Value |`,
|
|
3061
|
-
`|--------|-------|`,
|
|
3062
|
-
`| Last Scan | ${state.lastScan ? new Date(state.lastScan.timestamp).toLocaleDateString() : "Never"} |`,
|
|
3063
|
-
`| Critical Issues | ${state.lastScan?.issues.critical ?? 0} |`,
|
|
3064
|
-
`| Total Issues | ${state.lastScan?.issues.total ?? 0} |`,
|
|
3065
|
-
""
|
|
3066
|
-
];
|
|
3067
|
-
if (state.activePriorities.length > 0) {
|
|
3068
|
-
summary.push("## Top Priorities", "");
|
|
3069
|
-
state.activePriorities.slice(0, 3).forEach((p, i) => {
|
|
3070
|
-
summary.push(`${i + 1}. ${p}`);
|
|
3071
|
-
});
|
|
3072
|
-
summary.push("");
|
|
3073
|
-
}
|
|
3074
|
-
try {
|
|
3075
|
-
const recentIssues = await getRecentIssues({ workDir, limit: 3 });
|
|
3076
|
-
if (recentIssues.length > 0) {
|
|
3077
|
-
summary.push("## Recent Issues", "");
|
|
3078
|
-
recentIssues.forEach((i) => {
|
|
3079
|
-
summary.push(`- [${i.severity.toUpperCase()}] ${i.issue.slice(0, 50)}... (\`${i.file.split("/").pop()}\`)`);
|
|
3080
|
-
});
|
|
3081
|
-
summary.push("");
|
|
3082
|
-
}
|
|
3083
|
-
} catch {
|
|
3084
|
-
}
|
|
3085
|
-
try {
|
|
3086
|
-
const storage = getStorage(workDir);
|
|
3087
|
-
await storage.initialize();
|
|
3088
|
-
const nudges = await storage.queryNudges({ resolved: false, limit: 5 });
|
|
3089
|
-
if (nudges.length > 0) {
|
|
3090
|
-
summary.push("## Recent Goal Violations (Nudges)", "");
|
|
3091
|
-
summary.push("> Use `trie_get_nudges` for full list, then `trie_propose_fix` or `trie_propose_fixes_batch` to automate fixes.", "");
|
|
3092
|
-
nudges.forEach((n) => {
|
|
3093
|
-
const file = n.file || "unknown";
|
|
3094
|
-
summary.push(`- [${n.severity}] ${n.message.slice(0, 60)}${n.message.length > 60 ? "..." : ""} (\`${file.split("/").pop()}\`)`);
|
|
3095
|
-
});
|
|
3096
|
-
summary.push("");
|
|
3097
|
-
}
|
|
3098
|
-
} catch {
|
|
3099
|
-
}
|
|
3100
|
-
try {
|
|
3101
|
-
const patterns = await findCrossProjectPatterns(2);
|
|
3102
|
-
if (patterns.length > 0) {
|
|
3103
|
-
summary.push("## Watch For (seen in other projects)", "");
|
|
3104
|
-
patterns.slice(0, 2).forEach((p) => {
|
|
3105
|
-
summary.push(`- ${p.pattern.slice(0, 40)}... (${p.occurrences}x across ${p.projects.length} projects)`);
|
|
3106
|
-
});
|
|
3107
|
-
summary.push("");
|
|
3108
|
-
}
|
|
3109
|
-
} catch {
|
|
3110
|
-
}
|
|
3111
|
-
const rules = await loadRules(workDir);
|
|
3112
|
-
if (rules) {
|
|
3113
|
-
const ruleHeaders = rules.match(/^##?\s+.+$/gm)?.slice(0, 5) || [];
|
|
3114
|
-
if (ruleHeaders.length > 0) {
|
|
3115
|
-
summary.push("## Coding Rules (see trie://rules for full)", "");
|
|
3116
|
-
ruleHeaders.forEach((h) => summary.push(`- ${h.replace(/^#+\s*/, "")}`));
|
|
3117
|
-
summary.push("");
|
|
3118
|
-
}
|
|
3119
|
-
}
|
|
3120
|
-
summary.push("## Available Tools", "");
|
|
3121
|
-
summary.push("| Tool | Purpose |");
|
|
3122
|
-
summary.push("|------|---------|");
|
|
3123
|
-
summary.push("| `trie_fix` | Apply high-confidence fixes |");
|
|
3124
|
-
summary.push("| `trie_get_nudges` | Get goal violations for automation |");
|
|
3125
|
-
summary.push("| `trie_propose_fix` / `trie_propose_fixes_batch` | Fix nudges from trie_get_nudges |");
|
|
3126
|
-
summary.push("| `trie_memory` | Search issue history |");
|
|
3127
|
-
summary.push("| `trie_init` | Initialize bootstrap files |");
|
|
3128
|
-
summary.push("");
|
|
3129
|
-
summary.push("---", "", "# Detailed Context", "");
|
|
3130
|
-
const agentsMdPath = join3(getTrieDirectory(workDir), "AGENTS.md");
|
|
3131
|
-
try {
|
|
3132
|
-
if (existsSync3(agentsMdPath)) {
|
|
3133
|
-
const agentsContent = await readFile3(agentsMdPath, "utf-8");
|
|
3134
|
-
summary.push(agentsContent);
|
|
3135
|
-
} else {
|
|
3136
|
-
const contextSummary = await getContextForAI();
|
|
3137
|
-
summary.push(contextSummary);
|
|
3138
|
-
}
|
|
3139
|
-
} catch {
|
|
3140
|
-
const contextSummary = await getContextForAI();
|
|
3141
|
-
summary.push(contextSummary);
|
|
3142
|
-
}
|
|
3143
|
-
return {
|
|
3144
|
-
contents: [{
|
|
3145
|
-
uri,
|
|
3146
|
-
mimeType: "text/markdown",
|
|
3147
|
-
text: summary.join("\n")
|
|
3148
|
-
}]
|
|
3149
|
-
};
|
|
3150
|
-
}
|
|
3151
|
-
async getContextStateResource(uri) {
|
|
3152
|
-
const state = await loadContextState();
|
|
3153
|
-
return {
|
|
3154
|
-
contents: [{
|
|
3155
|
-
uri,
|
|
3156
|
-
mimeType: "application/json",
|
|
3157
|
-
text: JSON.stringify(state, null, 2)
|
|
3158
|
-
}]
|
|
3159
|
-
};
|
|
3160
|
-
}
|
|
3161
|
-
async getProjectResource(uri) {
|
|
3162
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
3163
|
-
if (!projectInfoExists(workDir)) {
|
|
3164
|
-
return {
|
|
3165
|
-
contents: [{
|
|
3166
|
-
uri,
|
|
3167
|
-
mimeType: "text/markdown",
|
|
3168
|
-
text: `# Project Information Not Found
|
|
3169
|
-
|
|
3170
|
-
No \`.trie/PROJECT.md\` file exists in this project.
|
|
3171
|
-
|
|
3172
|
-
## Create One
|
|
3173
|
-
|
|
3174
|
-
Use the \`trie_project\` tool with action="init" to create a PROJECT.md template:
|
|
3175
|
-
|
|
3176
|
-
\`\`\`
|
|
3177
|
-
trie_project action="init"
|
|
3178
|
-
\`\`\`
|
|
3179
|
-
|
|
3180
|
-
Or run from CLI:
|
|
3181
|
-
\`\`\`
|
|
3182
|
-
trie project init
|
|
3183
|
-
\`\`\`
|
|
3184
|
-
|
|
3185
|
-
## What is PROJECT.md?
|
|
3186
|
-
|
|
3187
|
-
PROJECT.md is a user-defined file that stores important project context:
|
|
3188
|
-
- Project description and purpose
|
|
3189
|
-
- Technology stack
|
|
3190
|
-
- Architecture decisions
|
|
3191
|
-
- Coding conventions
|
|
3192
|
-
- Environment info
|
|
3193
|
-
- Team ownership
|
|
3194
|
-
- Compliance requirements
|
|
3195
|
-
- Special instructions for AI assistants
|
|
3196
|
-
|
|
3197
|
-
This information is automatically available to Claude Code, Cursor, and other AI tools.
|
|
3198
|
-
`
|
|
3199
|
-
}]
|
|
3200
|
-
};
|
|
3201
|
-
}
|
|
3202
|
-
const content = await loadProjectInfo(workDir);
|
|
3203
|
-
return {
|
|
3204
|
-
contents: [{
|
|
3205
|
-
uri,
|
|
3206
|
-
mimeType: "text/markdown",
|
|
3207
|
-
text: content || ""
|
|
3208
|
-
}]
|
|
3209
|
-
};
|
|
3210
|
-
}
|
|
3211
|
-
async getConfigResource(uri) {
|
|
3212
|
-
const config = await loadConfig();
|
|
3213
|
-
return {
|
|
3214
|
-
contents: [{
|
|
3215
|
-
uri,
|
|
3216
|
-
mimeType: "application/json",
|
|
3217
|
-
text: JSON.stringify(config, null, 2)
|
|
3218
|
-
}]
|
|
3219
|
-
};
|
|
3220
|
-
}
|
|
3221
|
-
async getCacheStatsResource(uri) {
|
|
3222
|
-
try {
|
|
3223
|
-
const cachePath = join3(getTrieDirectory(getWorkingDirectory(void 0, true)), ".trie-cache.json");
|
|
3224
|
-
const cacheContent = await readFile3(cachePath, "utf-8");
|
|
3225
|
-
const cache = JSON.parse(cacheContent);
|
|
3226
|
-
const fileCount = Object.keys(cache.files || {}).length;
|
|
3227
|
-
const totalVulns = Object.values(cache.files || {}).reduce((acc, file) => {
|
|
3228
|
-
return acc + (file.vulnerabilities?.length || 0);
|
|
3229
|
-
}, 0);
|
|
3230
|
-
return {
|
|
3231
|
-
contents: [{
|
|
3232
|
-
uri,
|
|
3233
|
-
mimeType: "application/json",
|
|
3234
|
-
text: JSON.stringify({
|
|
3235
|
-
version: cache.version,
|
|
3236
|
-
created: new Date(cache.created).toISOString(),
|
|
3237
|
-
lastUpdated: new Date(cache.lastUpdated).toISOString(),
|
|
3238
|
-
cachedFiles: fileCount,
|
|
3239
|
-
totalVulnerabilitiesFound: totalVulns,
|
|
3240
|
-
cacheAgeMinutes: Math.round((Date.now() - cache.lastUpdated) / 6e4)
|
|
3241
|
-
}, null, 2)
|
|
3242
|
-
}]
|
|
3243
|
-
};
|
|
3244
|
-
} catch {
|
|
3245
|
-
return {
|
|
3246
|
-
contents: [{
|
|
3247
|
-
uri,
|
|
3248
|
-
mimeType: "application/json",
|
|
3249
|
-
text: JSON.stringify({
|
|
3250
|
-
error: "No cache found. Run a scan first to build the cache.",
|
|
3251
|
-
hint: "Run trie watch to analyze your codebase"
|
|
3252
|
-
}, null, 2)
|
|
3253
|
-
}]
|
|
3254
|
-
};
|
|
3255
|
-
}
|
|
3256
|
-
}
|
|
3257
|
-
async getSignaturesResource(uri) {
|
|
3258
|
-
const { getVulnerabilityStats } = await import("./vulnerability-signatures-2URZSXAQ.js");
|
|
3259
|
-
const { getVibeCodeStats } = await import("./vibe-code-signatures-5ZULYP3D.js");
|
|
3260
|
-
const vulnStats = getVulnerabilityStats();
|
|
3261
|
-
const vibeStats = getVibeCodeStats();
|
|
3262
|
-
return {
|
|
3263
|
-
contents: [{
|
|
3264
|
-
uri,
|
|
3265
|
-
mimeType: "application/json",
|
|
3266
|
-
text: JSON.stringify({
|
|
3267
|
-
vulnerabilitySignatures: vulnStats,
|
|
3268
|
-
vibeCodeSignatures: vibeStats,
|
|
3269
|
-
totalSignatures: vulnStats.total + vibeStats.total
|
|
3270
|
-
}, null, 2)
|
|
3271
|
-
}]
|
|
3272
|
-
};
|
|
3273
|
-
}
|
|
3274
|
-
async getScanReportResource(uri, parsedUri) {
|
|
3275
|
-
const fileName = parsedUri.replace("reports/", "");
|
|
3276
|
-
const reportPath = join3(getWorkingDirectory(void 0, true), "trie-reports", fileName);
|
|
3277
|
-
try {
|
|
3278
|
-
const content = await readFile3(reportPath, "utf-8");
|
|
3279
|
-
return {
|
|
3280
|
-
contents: [{
|
|
3281
|
-
uri,
|
|
3282
|
-
mimeType: fileName.endsWith(".json") ? "application/json" : "text/plain",
|
|
3283
|
-
text: content
|
|
3284
|
-
}]
|
|
3285
|
-
};
|
|
3286
|
-
} catch {
|
|
3287
|
-
throw new Error(`Report not found: ${fileName}`);
|
|
3288
|
-
}
|
|
3289
|
-
}
|
|
3290
|
-
async getBootstrapResource(uri) {
|
|
3291
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
3292
|
-
const context = await loadBootstrapContext(workDir);
|
|
3293
|
-
return {
|
|
3294
|
-
contents: [{
|
|
3295
|
-
uri,
|
|
3296
|
-
mimeType: "application/json",
|
|
3297
|
-
text: JSON.stringify({
|
|
3298
|
-
needsBootstrap: context.needsBootstrap,
|
|
3299
|
-
files: context.files.map((f) => ({
|
|
3300
|
-
name: f.name,
|
|
3301
|
-
type: f.type,
|
|
3302
|
-
exists: f.exists
|
|
3303
|
-
})),
|
|
3304
|
-
hasInjectedContent: !!context.injectedContent
|
|
3305
|
-
}, null, 2)
|
|
3306
|
-
}]
|
|
3307
|
-
};
|
|
3308
|
-
}
|
|
3309
|
-
async getRulesResource(uri) {
|
|
3310
|
-
const rules = await loadRules();
|
|
3311
|
-
if (!rules) {
|
|
3312
|
-
return {
|
|
3313
|
-
contents: [{
|
|
3314
|
-
uri,
|
|
3315
|
-
mimeType: "text/markdown",
|
|
3316
|
-
text: `# No Rules Defined
|
|
3317
|
-
|
|
3318
|
-
No \`.trie/RULES.md\` file exists in this project.
|
|
3319
|
-
|
|
3320
|
-
Use \`trie_init\` to create one, or create it manually with your coding standards.
|
|
3321
|
-
`
|
|
3322
|
-
}]
|
|
3323
|
-
};
|
|
3324
|
-
}
|
|
3325
|
-
return {
|
|
3326
|
-
contents: [{
|
|
3327
|
-
uri,
|
|
3328
|
-
mimeType: "text/markdown",
|
|
3329
|
-
text: rules
|
|
3330
|
-
}]
|
|
3331
|
-
};
|
|
3332
|
-
}
|
|
3333
|
-
async getTeamResource(uri) {
|
|
3334
|
-
const team = await loadTeamInfo();
|
|
3335
|
-
if (!team) {
|
|
3336
|
-
return {
|
|
3337
|
-
contents: [{
|
|
3338
|
-
uri,
|
|
3339
|
-
mimeType: "text/markdown",
|
|
3340
|
-
text: `# No Team Info Defined
|
|
3341
|
-
|
|
3342
|
-
No \`.trie/TEAM.md\` file exists in this project.
|
|
3343
|
-
|
|
3344
|
-
Use \`trie_init\` to create one, or create it manually with team ownership info.
|
|
3345
|
-
`
|
|
3346
|
-
}]
|
|
3347
|
-
};
|
|
3348
|
-
}
|
|
3349
|
-
return {
|
|
3350
|
-
contents: [{
|
|
3351
|
-
uri,
|
|
3352
|
-
mimeType: "text/markdown",
|
|
3353
|
-
text: team
|
|
3354
|
-
}]
|
|
3355
|
-
};
|
|
3356
|
-
}
|
|
3357
|
-
async getMemoryResource(uri) {
|
|
3358
|
-
const workDir = getWorkingDirectory(void 0, true);
|
|
3359
|
-
const stats = await getMemoryStats(workDir);
|
|
3360
|
-
const recent = await getRecentIssues({ workDir, limit: 5 });
|
|
3361
|
-
return {
|
|
3362
|
-
contents: [{
|
|
3363
|
-
uri,
|
|
3364
|
-
mimeType: "application/json",
|
|
3365
|
-
text: JSON.stringify({
|
|
3366
|
-
stats,
|
|
3367
|
-
recentIssues: recent.map((i) => ({
|
|
3368
|
-
severity: i.severity,
|
|
3369
|
-
issue: i.issue.slice(0, 100),
|
|
3370
|
-
file: i.file,
|
|
3371
|
-
agent: i.agent,
|
|
3372
|
-
timestamp: i.timestamp,
|
|
3373
|
-
resolved: i.resolved
|
|
3374
|
-
}))
|
|
3375
|
-
}, null, 2)
|
|
3376
|
-
}]
|
|
3377
|
-
};
|
|
3378
|
-
}
|
|
3379
|
-
async getGlobalMemoryResource(uri) {
|
|
3380
|
-
const stats = await getGlobalMemoryStats();
|
|
3381
|
-
const patterns = await findCrossProjectPatterns(2);
|
|
3382
|
-
return {
|
|
3383
|
-
contents: [{
|
|
3384
|
-
uri,
|
|
3385
|
-
mimeType: "application/json",
|
|
3386
|
-
text: JSON.stringify({
|
|
3387
|
-
stats,
|
|
3388
|
-
topPatterns: patterns.slice(0, 10).map((p) => ({
|
|
3389
|
-
pattern: p.pattern.slice(0, 80),
|
|
3390
|
-
severity: p.severity,
|
|
3391
|
-
agent: p.agent,
|
|
3392
|
-
occurrences: p.occurrences,
|
|
3393
|
-
projectCount: p.projects.length,
|
|
3394
|
-
hasFixApplied: !!p.fixApplied
|
|
3395
|
-
}))
|
|
3396
|
-
}, null, 2)
|
|
3397
|
-
}]
|
|
3398
|
-
};
|
|
3399
|
-
}
|
|
3400
|
-
};
|
|
3401
|
-
|
|
3402
|
-
// src/tools/visual-qa-browser.ts
|
|
3403
|
-
import { chromium } from "playwright";
|
|
3404
|
-
import { createServer } from "net";
|
|
3405
|
-
import { request } from "http";
|
|
3406
|
-
var DEFAULT_VIEWPORTS = [
|
|
3407
|
-
{ name: "mobile", width: 375, height: 812 },
|
|
3408
|
-
// iPhone X
|
|
3409
|
-
{ name: "tablet", width: 768, height: 1024 },
|
|
3410
|
-
// iPad
|
|
3411
|
-
{ name: "desktop", width: 1440, height: 900 }
|
|
3412
|
-
// Standard desktop
|
|
3413
|
-
];
|
|
3414
|
-
async function findOpenPort() {
|
|
3415
|
-
const commonPorts = [3e3, 3001, 5173, 5174, 4200, 8080, 8e3, 8888, 5e3, 4e3];
|
|
3416
|
-
const portChecks = await Promise.all(
|
|
3417
|
-
commonPorts.map(async (port) => {
|
|
3418
|
-
const isOpen = await checkPort(port);
|
|
3419
|
-
return isOpen ? port : null;
|
|
3420
|
-
})
|
|
3421
|
-
);
|
|
3422
|
-
return portChecks.find((port) => port !== null) || null;
|
|
3423
|
-
}
|
|
3424
|
-
async function checkPort(port) {
|
|
3425
|
-
return new Promise((resolve3) => {
|
|
3426
|
-
const testServer = createServer();
|
|
3427
|
-
let portInUse = false;
|
|
3428
|
-
testServer.once("error", (err) => {
|
|
3429
|
-
if (err.code === "EADDRINUSE") {
|
|
3430
|
-
portInUse = true;
|
|
3431
|
-
testHttpPort(port).then(resolve3).catch(() => resolve3(false));
|
|
3432
|
-
} else {
|
|
3433
|
-
resolve3(false);
|
|
3434
|
-
}
|
|
3435
|
-
});
|
|
3436
|
-
testServer.once("listening", () => {
|
|
3437
|
-
testServer.close();
|
|
3438
|
-
resolve3(false);
|
|
3439
|
-
});
|
|
3440
|
-
setTimeout(() => {
|
|
3441
|
-
if (!portInUse) {
|
|
3442
|
-
testServer.close();
|
|
3443
|
-
resolve3(false);
|
|
3444
|
-
}
|
|
3445
|
-
}, 1e3);
|
|
3446
|
-
testServer.listen(port, "127.0.0.1");
|
|
3447
|
-
});
|
|
3448
|
-
}
|
|
3449
|
-
async function testHttpPort(port) {
|
|
3450
|
-
return new Promise((resolve3) => {
|
|
3451
|
-
const req = request({
|
|
3452
|
-
hostname: "localhost",
|
|
3453
|
-
port,
|
|
3454
|
-
path: "/",
|
|
3455
|
-
method: "GET",
|
|
3456
|
-
timeout: 2e3
|
|
3457
|
-
}, (res) => {
|
|
3458
|
-
resolve3(res.statusCode !== void 0);
|
|
3459
|
-
res.on("data", () => {
|
|
3460
|
-
});
|
|
3461
|
-
res.on("end", () => {
|
|
3462
|
-
});
|
|
3463
|
-
});
|
|
3464
|
-
req.on("error", () => {
|
|
3465
|
-
resolve3(false);
|
|
3466
|
-
});
|
|
3467
|
-
req.on("timeout", () => {
|
|
3468
|
-
req.destroy();
|
|
3469
|
-
resolve3(false);
|
|
3470
|
-
});
|
|
3471
|
-
req.end();
|
|
3472
|
-
});
|
|
3473
|
-
}
|
|
3474
|
-
async function captureScreenshots(url, viewports, options) {
|
|
3475
|
-
let browser = null;
|
|
3476
|
-
const screenshots = [];
|
|
3477
|
-
try {
|
|
3478
|
-
browser = await chromium.launch({
|
|
3479
|
-
headless: true
|
|
3480
|
-
});
|
|
3481
|
-
for (const viewport of viewports) {
|
|
3482
|
-
const page = await browser.newPage({
|
|
3483
|
-
viewport: { width: viewport.width, height: viewport.height }
|
|
3484
|
-
});
|
|
3485
|
-
try {
|
|
3486
|
-
await page.goto(url, {
|
|
3487
|
-
waitUntil: "networkidle",
|
|
3488
|
-
timeout: 3e4
|
|
3489
|
-
});
|
|
3490
|
-
if (options.waitForSelector) {
|
|
3491
|
-
await page.waitForSelector(options.waitForSelector, { timeout: 1e4 });
|
|
3492
|
-
}
|
|
3493
|
-
if (options.waitMs) {
|
|
3494
|
-
await page.waitForTimeout(options.waitMs);
|
|
3495
|
-
}
|
|
3496
|
-
const screenshotBuffer = await page.screenshot({
|
|
3497
|
-
fullPage: true,
|
|
3498
|
-
type: "png"
|
|
3499
|
-
});
|
|
3500
|
-
screenshots.push({
|
|
3501
|
-
viewport: viewport.name,
|
|
3502
|
-
width: viewport.width,
|
|
3503
|
-
height: viewport.height,
|
|
3504
|
-
base64: screenshotBuffer.toString("base64"),
|
|
3505
|
-
mimeType: "image/png"
|
|
3506
|
-
});
|
|
3507
|
-
} finally {
|
|
3508
|
-
await page.close();
|
|
3509
|
-
}
|
|
3510
|
-
}
|
|
3511
|
-
} finally {
|
|
3512
|
-
if (browser) {
|
|
3513
|
-
await browser.close();
|
|
3514
|
-
}
|
|
3515
|
-
}
|
|
3516
|
-
return screenshots;
|
|
3517
|
-
}
|
|
3518
|
-
async function runVisualQA(options = {}) {
|
|
3519
|
-
let url = options.url;
|
|
3520
|
-
if (!url) {
|
|
3521
|
-
if (options.port) {
|
|
3522
|
-
const isServing = await testHttpPort(options.port);
|
|
3523
|
-
if (!isServing) {
|
|
3524
|
-
return {
|
|
3525
|
-
success: false,
|
|
3526
|
-
url: `http://localhost:${options.port}`,
|
|
3527
|
-
screenshots: [],
|
|
3528
|
-
error: `Port ${options.port} is not serving HTTP. Is your dev server running on this port?
|
|
3529
|
-
|
|
3530
|
-
Try:
|
|
3531
|
-
1. Verify your dev server is running: curl http://localhost:${options.port}
|
|
3532
|
-
2. Check if the port is correct
|
|
3533
|
-
3. Provide full URL: trie_visual_qa_browser url:"http://localhost:${options.port}"`,
|
|
3534
|
-
analysisPrompt: ""
|
|
3535
|
-
};
|
|
3536
|
-
}
|
|
3537
|
-
url = `http://localhost:${options.port}`;
|
|
3538
|
-
} else {
|
|
3539
|
-
if (!isInteractiveMode()) {
|
|
3540
|
-
console.error("Visual QA: Auto-detecting running dev server...");
|
|
3541
|
-
}
|
|
3542
|
-
const port = await findOpenPort();
|
|
3543
|
-
if (!port) {
|
|
3544
|
-
return {
|
|
3545
|
-
success: false,
|
|
3546
|
-
url: "",
|
|
3547
|
-
screenshots: [],
|
|
3548
|
-
error: 'No running dev server found on common ports (3000, 3001, 5173, 5174, 4200, 8080, 8000, 8888, 5000, 4000).\n\nPlease:\n1. Start your dev server, OR\n2. Provide a URL: trie_visual_qa_browser url:"http://localhost:3000", OR\n3. Specify a port: trie_visual_qa_browser port:3000',
|
|
3549
|
-
analysisPrompt: ""
|
|
3550
|
-
};
|
|
3551
|
-
}
|
|
3552
|
-
url = `http://localhost:${port}`;
|
|
3553
|
-
if (!isInteractiveMode()) {
|
|
3554
|
-
console.error(` \u2713 Found server on port ${port}`);
|
|
3555
|
-
}
|
|
3556
|
-
}
|
|
3557
|
-
}
|
|
3558
|
-
const viewports = options.viewports || DEFAULT_VIEWPORTS;
|
|
3559
|
-
try {
|
|
3560
|
-
if (!isInteractiveMode()) {
|
|
3561
|
-
console.error(`\u{1F4F8} Visual QA: Capturing screenshots from ${url}`);
|
|
3562
|
-
console.error(` Viewports: ${viewports.map((v) => `${v.name} (${v.width}x${v.height})`).join(", ")}`);
|
|
3563
|
-
}
|
|
3564
|
-
const screenshots = await captureScreenshots(url, viewports, {
|
|
3565
|
-
waitForSelector: options.waitForSelector,
|
|
3566
|
-
waitMs: options.waitMs
|
|
3567
|
-
});
|
|
3568
|
-
if (!isInteractiveMode()) {
|
|
3569
|
-
console.error(` \u2713 Captured ${screenshots.length} screenshots`);
|
|
3570
|
-
}
|
|
3571
|
-
const analysisPrompt = buildAnalysisPrompt(url, screenshots);
|
|
3572
|
-
return {
|
|
3573
|
-
success: true,
|
|
3574
|
-
url,
|
|
3575
|
-
screenshots,
|
|
3576
|
-
analysisPrompt
|
|
3577
|
-
};
|
|
3578
|
-
} catch (error) {
|
|
3579
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3580
|
-
if (errorMessage.includes("net::ERR_CONNECTION_REFUSED") || errorMessage.includes("ECONNREFUSED")) {
|
|
3581
|
-
return {
|
|
3582
|
-
success: false,
|
|
3583
|
-
url,
|
|
3584
|
-
screenshots: [],
|
|
3585
|
-
error: `Cannot connect to ${url}. Is your dev server running?
|
|
3586
|
-
|
|
3587
|
-
Try:
|
|
3588
|
-
1. Start your dev server (e.g., npm start, npm run dev)
|
|
3589
|
-
2. Specify the URL explicitly: trie_visual_qa_browser url:"http://localhost:3000"
|
|
3590
|
-
3. Or specify the port: trie_visual_qa_browser port:3000`,
|
|
3591
|
-
analysisPrompt: ""
|
|
3592
|
-
};
|
|
3593
|
-
}
|
|
3594
|
-
if (errorMessage.includes("Executable doesn't exist") || errorMessage.includes("BrowserType")) {
|
|
3595
|
-
return {
|
|
3596
|
-
success: false,
|
|
3597
|
-
url,
|
|
3598
|
-
screenshots: [],
|
|
3599
|
-
error: "Playwright browsers not installed. Run: npx playwright install chromium",
|
|
3600
|
-
analysisPrompt: ""
|
|
3601
|
-
};
|
|
3602
|
-
}
|
|
3603
|
-
if (errorMessage.includes("timeout") || errorMessage.includes("Navigation timeout")) {
|
|
3604
|
-
return {
|
|
3605
|
-
success: false,
|
|
3606
|
-
url,
|
|
3607
|
-
screenshots: [],
|
|
3608
|
-
error: `Page load timeout for ${url}. The page may be taking too long to load or may have JavaScript errors.
|
|
3609
|
-
|
|
3610
|
-
Try:
|
|
3611
|
-
1. Check if the page loads in your browser
|
|
3612
|
-
2. Increase wait time: trie_visual_qa_browser url:"${url}" waitMs:5000
|
|
3613
|
-
3. Wait for specific element: trie_visual_qa_browser url:"${url}" waitForSelector:".main-content"`,
|
|
3614
|
-
analysisPrompt: ""
|
|
3615
|
-
};
|
|
3616
|
-
}
|
|
3617
|
-
return {
|
|
3618
|
-
success: false,
|
|
3619
|
-
url,
|
|
3620
|
-
screenshots: [],
|
|
3621
|
-
error: `Screenshot capture failed: ${errorMessage}
|
|
3622
|
-
|
|
3623
|
-
Troubleshooting:
|
|
3624
|
-
1. Verify ${url} is accessible in your browser
|
|
3625
|
-
2. Check that your dev server is running
|
|
3626
|
-
3. Try specifying the URL explicitly: trie_visual_qa_browser url:"${url}"`,
|
|
3627
|
-
analysisPrompt: ""
|
|
3628
|
-
};
|
|
3629
|
-
}
|
|
3630
|
-
}
|
|
3631
|
-
function buildAnalysisPrompt(url, screenshots) {
|
|
3632
|
-
const viewportList = screenshots.map((s) => `- ${s.viewport}: ${s.width}x${s.height}`).join("\n");
|
|
3633
|
-
return `## Visual QA Analysis
|
|
3634
|
-
|
|
3635
|
-
I've captured screenshots of **${url}** at ${screenshots.length} viewports:
|
|
3636
|
-
|
|
3637
|
-
${viewportList}
|
|
3638
|
-
|
|
3639
|
-
Please analyze these screenshots for:
|
|
3640
|
-
|
|
3641
|
-
### Layout Issues
|
|
3642
|
-
- Overlapping elements or text
|
|
3643
|
-
- Content overflow or clipping
|
|
3644
|
-
- Broken layouts at specific breakpoints
|
|
3645
|
-
- Misaligned elements
|
|
3646
|
-
- Unexpected gaps or spacing
|
|
3647
|
-
|
|
3648
|
-
### Responsive Design
|
|
3649
|
-
- Mobile navigation issues
|
|
3650
|
-
- Text too small to read on mobile
|
|
3651
|
-
- Touch targets too small (<44px)
|
|
3652
|
-
- Horizontal scrolling on mobile
|
|
3653
|
-
- Content not adapting to viewport
|
|
3654
|
-
|
|
3655
|
-
### Visual Polish
|
|
3656
|
-
- Inconsistent spacing or alignment
|
|
3657
|
-
- Poor color contrast (text hard to read)
|
|
3658
|
-
- Broken images or missing assets
|
|
3659
|
-
- Loading states stuck/visible
|
|
3660
|
-
- Z-index issues (elements overlapping incorrectly)
|
|
3661
|
-
|
|
3662
|
-
### Accessibility
|
|
3663
|
-
- Missing focus indicators
|
|
3664
|
-
- Color-only information
|
|
3665
|
-
- Text over images without sufficient contrast
|
|
3666
|
-
- Very small text (<12px)
|
|
3667
|
-
|
|
3668
|
-
### General Quality
|
|
3669
|
-
- Does it look professional?
|
|
3670
|
-
- Is the hierarchy clear?
|
|
3671
|
-
- Are interactive elements obvious?
|
|
3672
|
-
- Any obvious bugs or glitches?
|
|
3673
|
-
|
|
3674
|
-
For each issue found:
|
|
3675
|
-
1. **Viewport**: Which viewport(s) affected
|
|
3676
|
-
2. **Location**: Where on the page
|
|
3677
|
-
3. **Issue**: What's wrong
|
|
3678
|
-
4. **Severity**: Critical/Serious/Moderate/Low
|
|
3679
|
-
5. **Fix**: How to resolve it
|
|
3680
|
-
|
|
3681
|
-
If the UI looks good, say so! Note what's working well.`;
|
|
3682
|
-
}
|
|
3683
|
-
function formatMCPResponse(result) {
|
|
3684
|
-
if (!result.success) {
|
|
3685
|
-
return {
|
|
3686
|
-
content: [{
|
|
3687
|
-
type: "text",
|
|
3688
|
-
text: `# Visual QA Failed
|
|
3689
|
-
|
|
3690
|
-
${result.error}
|
|
3691
|
-
|
|
3692
|
-
## Troubleshooting
|
|
3693
|
-
|
|
3694
|
-
1. Make sure your dev server is running
|
|
3695
|
-
2. Try: \`trie_visual_qa url:"http://localhost:3000"\`
|
|
3696
|
-
3. If Playwright isn't installed: \`npx playwright install chromium\``
|
|
3697
|
-
}]
|
|
3698
|
-
};
|
|
3699
|
-
}
|
|
3700
|
-
const content = [];
|
|
3701
|
-
content.push({
|
|
3702
|
-
type: "text",
|
|
3703
|
-
text: result.analysisPrompt
|
|
3704
|
-
});
|
|
3705
|
-
for (const screenshot of result.screenshots) {
|
|
3706
|
-
content.push({
|
|
3707
|
-
type: "text",
|
|
3708
|
-
text: `
|
|
3709
|
-
### ${screenshot.viewport} (${screenshot.width}x${screenshot.height})
|
|
3710
|
-
`
|
|
3711
|
-
});
|
|
3712
|
-
content.push({
|
|
3713
|
-
type: "image",
|
|
3714
|
-
data: screenshot.base64,
|
|
3715
|
-
mimeType: screenshot.mimeType
|
|
3716
|
-
});
|
|
3717
|
-
}
|
|
3718
|
-
return { content };
|
|
3719
|
-
}
|
|
3720
|
-
|
|
3721
|
-
// src/server/request-handlers.ts
|
|
3722
|
-
var RequestHandlers = class {
|
|
3723
|
-
constructor(toolRegistry, resourceManager) {
|
|
3724
|
-
this.toolRegistry = toolRegistry;
|
|
3725
|
-
this.resourceManager = resourceManager;
|
|
3726
|
-
}
|
|
3727
|
-
/**
|
|
3728
|
-
* Handle tool execution requests
|
|
3729
|
-
*/
|
|
3730
|
-
async handleToolCall(name, args) {
|
|
3731
|
-
const normalizedName = this.normalizeName(name);
|
|
3732
|
-
if (args && !args.directory && !args.files) {
|
|
3733
|
-
const workingDir = getWorkingDirectory(void 0, true);
|
|
3734
|
-
if (workingDir !== process.cwd()) {
|
|
3735
|
-
args.directory = workingDir;
|
|
3736
|
-
}
|
|
3737
|
-
}
|
|
3738
|
-
try {
|
|
3739
|
-
switch (normalizedName) {
|
|
3740
|
-
case "trie":
|
|
3741
|
-
return await this.handleTrieMenu(args);
|
|
3742
|
-
case "scan":
|
|
3743
|
-
return await this.toolRegistry.getTool("scan").execute(args);
|
|
3744
|
-
case "fix":
|
|
3745
|
-
return await this.toolRegistry.getTool("fix").execute(args);
|
|
3746
|
-
case "explain":
|
|
3747
|
-
return await this.toolRegistry.getTool("explain").execute(args);
|
|
3748
|
-
case "test":
|
|
3749
|
-
return await this.toolRegistry.getTool("test").execute(args);
|
|
3750
|
-
case "register_agent":
|
|
3751
|
-
return await this.toolRegistry.getTool("register_agent").execute(args);
|
|
3752
|
-
case "watch":
|
|
3753
|
-
return await this.toolRegistry.getTool("watch").execute(args);
|
|
3754
|
-
case "visual_qa_browser": {
|
|
3755
|
-
try {
|
|
3756
|
-
const result = await runVisualQA(args);
|
|
3757
|
-
return formatMCPResponse(result);
|
|
3758
|
-
} catch (error) {
|
|
3759
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3760
|
-
return {
|
|
3761
|
-
content: [{
|
|
3762
|
-
type: "text",
|
|
3763
|
-
text: `# Visual QA Error
|
|
3764
|
-
|
|
3765
|
-
Failed to capture screenshots: ${errorMessage}
|
|
3766
|
-
|
|
3767
|
-
## Troubleshooting
|
|
3768
|
-
|
|
3769
|
-
1. **Check if your dev server is running:**
|
|
3770
|
-
- Try accessing the URL in your browser
|
|
3771
|
-
- Common ports: 3000, 5173, 8080
|
|
3772
|
-
|
|
3773
|
-
2. **Specify the URL explicitly:**
|
|
3774
|
-
\`\`\`
|
|
3775
|
-
trie_visual_qa_browser url:"http://localhost:3000"
|
|
3776
|
-
\`\`\`
|
|
3777
|
-
|
|
3778
|
-
3. **Specify a port:**
|
|
3779
|
-
\`\`\`
|
|
3780
|
-
trie_visual_qa_browser port:3000
|
|
3781
|
-
\`\`\`
|
|
3782
|
-
|
|
3783
|
-
4. **Install Playwright browsers (if needed):**
|
|
3784
|
-
\`\`\`
|
|
3785
|
-
npx playwright install chromium
|
|
3786
|
-
\`\`\`
|
|
3787
|
-
|
|
3788
|
-
5. **Check for JavaScript errors:**
|
|
3789
|
-
- Open browser dev tools
|
|
3790
|
-
- Look for console errors
|
|
3791
|
-
- The page may be failing to load`
|
|
3792
|
-
}]
|
|
3793
|
-
};
|
|
3794
|
-
}
|
|
3795
|
-
}
|
|
3796
|
-
case "pr_review":
|
|
3797
|
-
return await this.toolRegistry.getTool("pr_review").execute(args);
|
|
3798
|
-
case "project":
|
|
3799
|
-
case "project_info":
|
|
3800
|
-
return await this.toolRegistry.getTool("project").execute(args);
|
|
3801
|
-
case "init":
|
|
3802
|
-
return await this.toolRegistry.getTool("init").execute(args);
|
|
3803
|
-
case "memory":
|
|
3804
|
-
return await this.toolRegistry.getTool("memory").execute(args);
|
|
3805
|
-
case "check":
|
|
3806
|
-
return await this.toolRegistry.getTool("check").execute(args);
|
|
3807
|
-
case "tell":
|
|
3808
|
-
return await this.toolRegistry.getTool("tell").execute(args);
|
|
3809
|
-
case "feedback":
|
|
3810
|
-
case "ok":
|
|
3811
|
-
case "bad":
|
|
3812
|
-
return await this.toolRegistry.getTool("feedback").execute(args);
|
|
3813
|
-
case "reconcile":
|
|
3814
|
-
return await this.toolRegistry.getTool("reconcile").execute(args);
|
|
3815
|
-
case "context":
|
|
3816
|
-
return await this.toolRegistry.getTool("context").execute(args);
|
|
3817
|
-
case "cloud_fix":
|
|
3818
|
-
return await this.toolRegistry.getTool("cloud_fix").execute(args);
|
|
3819
|
-
case "linear_sync":
|
|
3820
|
-
return await this.toolRegistry.getTool("linear_sync").execute(args);
|
|
3821
|
-
case "github_sync":
|
|
3822
|
-
return await this.toolRegistry.getTool("github_sync").execute(args);
|
|
3823
|
-
case "github_branches":
|
|
3824
|
-
return await this.toolRegistry.getTool("github_branches").execute(args);
|
|
3825
|
-
case "pipeline":
|
|
3826
|
-
return await this.toolRegistry.getTool("pipeline").execute(args);
|
|
3827
|
-
case "index":
|
|
3828
|
-
return await this.toolRegistry.getTool("index").execute(args);
|
|
3829
|
-
default: {
|
|
3830
|
-
const tool = this.toolRegistry.getTool(normalizedName);
|
|
3831
|
-
if (tool) {
|
|
3832
|
-
return await tool.execute(args);
|
|
3833
|
-
}
|
|
3834
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
3835
|
-
}
|
|
3836
|
-
}
|
|
3837
|
-
} catch (error) {
|
|
3838
|
-
return {
|
|
3839
|
-
content: [{
|
|
3840
|
-
type: "text",
|
|
3841
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
3842
|
-
}]
|
|
3843
|
-
};
|
|
3844
|
-
}
|
|
3845
|
-
}
|
|
3846
|
-
/**
|
|
3847
|
-
* Handle resource listing requests
|
|
3848
|
-
*/
|
|
3849
|
-
async handleListResources() {
|
|
3850
|
-
const resources = await this.resourceManager.getAvailableResources();
|
|
3851
|
-
return { resources };
|
|
3852
|
-
}
|
|
3853
|
-
/**
|
|
3854
|
-
* Handle resource reading requests
|
|
3855
|
-
*/
|
|
3856
|
-
async handleReadResource(uri) {
|
|
3857
|
-
return await this.resourceManager.readResourceContent(uri);
|
|
3858
|
-
}
|
|
3859
|
-
normalizeName(name) {
|
|
3860
|
-
const stripNamespace = (n) => {
|
|
3861
|
-
const trimmed = n.trim().replace(/\s*\([^)]*\)\s*$/, "").trim();
|
|
3862
|
-
const withoutSlash = trimmed.startsWith("/") ? trimmed.slice(1) : trimmed;
|
|
3863
|
-
const parts = withoutSlash.split(":");
|
|
3864
|
-
const base = (parts[0] || withoutSlash).trim();
|
|
3865
|
-
const slashParts = base.split("/");
|
|
3866
|
-
return (slashParts[slashParts.length - 1] || base).trim();
|
|
3867
|
-
};
|
|
3868
|
-
const rawName = stripNamespace(name);
|
|
3869
|
-
return rawName.startsWith("trie_") ? rawName.slice("trie_".length) : rawName;
|
|
3870
|
-
}
|
|
3871
|
-
async handleTrieMenu(args) {
|
|
3872
|
-
const actionInput = typeof args?.action === "string" ? args.action : null;
|
|
3873
|
-
if (actionInput) {
|
|
3874
|
-
const normalizedAction = this.normalizeName(actionInput);
|
|
3875
|
-
if (normalizedAction === "trie") {
|
|
3876
|
-
return {
|
|
3877
|
-
content: [{
|
|
3878
|
-
type: "text",
|
|
3879
|
-
text: 'Use `trie` without action for the menu, or provide a concrete action like "scan" or "security".'
|
|
3880
|
-
}]
|
|
3881
|
-
};
|
|
3882
|
-
}
|
|
3883
|
-
return await this.handleToolCall(normalizedAction, { ...args });
|
|
3884
|
-
}
|
|
3885
|
-
return {
|
|
3886
|
-
content: [{
|
|
3887
|
-
type: "text",
|
|
3888
|
-
text: [
|
|
3889
|
-
"## Trie Quick Actions",
|
|
3890
|
-
"",
|
|
3891
|
-
"**Most common:**",
|
|
3892
|
-
"- `trie` (no args) \u2014 show this menu",
|
|
3893
|
-
'- `action: "fix"` \u2014 high-confidence fixes',
|
|
3894
|
-
'- `action: "watch"` \u2014 start/stop/status autonomous watch',
|
|
3895
|
-
'- `action: "pr_review"` \u2014 interactive PR review',
|
|
3896
|
-
"",
|
|
3897
|
-
"**Other actions:**",
|
|
3898
|
-
'- `action: "test"` \u2014 generate/coverage/suggest/run tests (requires `files` + `action`)',
|
|
3899
|
-
'- `action: "explain"` \u2014 explain code/issues/risks',
|
|
3900
|
-
'- `action: "visual_qa_browser"` \u2014 screenshots for visual QA',
|
|
3901
|
-
"",
|
|
3902
|
-
"All commands accept `trie_` prefix (e.g., `trie_fix`, `trie_watch`)."
|
|
3903
|
-
].join("\n")
|
|
3904
|
-
}]
|
|
3905
|
-
};
|
|
3906
|
-
}
|
|
3907
|
-
};
|
|
3908
|
-
|
|
3909
|
-
// src/server/mcp-server.ts
|
|
3910
|
-
var MCPServer = class {
|
|
3911
|
-
server;
|
|
3912
|
-
toolRegistry;
|
|
3913
|
-
resourceManager;
|
|
3914
|
-
requestHandlers;
|
|
3915
|
-
constructor() {
|
|
3916
|
-
this.server = new Server2(
|
|
3917
|
-
{
|
|
3918
|
-
name: "trie",
|
|
3919
|
-
version: "1.0.0",
|
|
3920
|
-
description: "Intelligent Agent Orchestration for AI Coding Tools. IMPORTANT: Read trie://context first to understand project state, priorities, and recent scan history before making changes."
|
|
3921
|
-
},
|
|
3922
|
-
{
|
|
3923
|
-
capabilities: {
|
|
3924
|
-
tools: {},
|
|
3925
|
-
resources: {},
|
|
3926
|
-
// Enable MCP Apps - interactive UI components in AI clients
|
|
3927
|
-
// See: https://blog.modelcontextprotocol.io/posts/2026-01-26-mcp-apps/
|
|
3928
|
-
experimental: {
|
|
3929
|
-
ui: {}
|
|
3930
|
-
}
|
|
3931
|
-
}
|
|
3932
|
-
}
|
|
3933
|
-
);
|
|
3934
|
-
this.toolRegistry = new ToolRegistry();
|
|
3935
|
-
this.resourceManager = new ResourceManager();
|
|
3936
|
-
this.requestHandlers = new RequestHandlers(this.toolRegistry, this.resourceManager);
|
|
3937
|
-
this.setupRequestHandlers();
|
|
3938
|
-
}
|
|
3939
|
-
setupRequestHandlers() {
|
|
3940
|
-
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
3941
|
-
return {
|
|
3942
|
-
tools: this.toolRegistry.getAllTools()
|
|
3943
|
-
};
|
|
3944
|
-
});
|
|
3945
|
-
this.server.setRequestHandler(CallToolRequestSchema, async (request2) => {
|
|
3946
|
-
const { name, arguments: args } = request2.params;
|
|
3947
|
-
return await this.requestHandlers.handleToolCall(name, args);
|
|
3948
|
-
});
|
|
3949
|
-
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
3950
|
-
return await this.requestHandlers.handleListResources();
|
|
3951
|
-
});
|
|
3952
|
-
this.server.setRequestHandler(ReadResourceRequestSchema, async (request2) => {
|
|
3953
|
-
const { uri } = request2.params;
|
|
3954
|
-
return await this.requestHandlers.handleReadResource(uri);
|
|
3955
|
-
});
|
|
3956
|
-
}
|
|
3957
|
-
/**
|
|
3958
|
-
* Show startup banner
|
|
3959
|
-
*/
|
|
3960
|
-
showStartupBanner(toolCount, aiTool) {
|
|
3961
|
-
console.error(`
|
|
3962
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
3963
|
-
\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
|
|
3964
|
-
\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557
|
|
3965
|
-
\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D
|
|
3966
|
-
\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
3967
|
-
\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
3968
|
-
Your central registry for Trie tools
|
|
3969
|
-
|
|
3970
|
-
by Louis Kishfy
|
|
3971
|
-
|
|
3972
|
-
Download the Trie workspace: https://www.trie.dev
|
|
3973
|
-
Follow me on X: https://x.com/louiskishfy
|
|
3974
|
-
|
|
3975
|
-
${toolCount} tools ready | ${aiTool}
|
|
3976
|
-
|
|
3977
|
-
Quick Start:
|
|
3978
|
-
\u2022 "Scan this code" - Run Trie analysis
|
|
3979
|
-
\u2022 "Use trie" - Open the Trie menu
|
|
3980
|
-
\u2022 "Use trie_fix" - Apply high-confidence fixes
|
|
3981
|
-
|
|
3982
|
-
Ready.
|
|
3983
|
-
`);
|
|
3984
|
-
}
|
|
3985
|
-
/**
|
|
3986
|
-
* Initialize and start the server
|
|
3987
|
-
*/
|
|
3988
|
-
async start() {
|
|
3989
|
-
try {
|
|
3990
|
-
const aiTool = detectAITool();
|
|
3991
|
-
await loadConfig();
|
|
3992
|
-
const toolCount = this.toolRegistry.getAllTools().length;
|
|
3993
|
-
this.showStartupBanner(toolCount, aiTool.name);
|
|
3994
|
-
const transport = new StdioServerTransport();
|
|
3995
|
-
await this.server.connect(transport);
|
|
3996
|
-
} catch (error) {
|
|
3997
|
-
console.error("Fatal error starting MCP server:", error);
|
|
3998
|
-
process.exit(1);
|
|
3999
|
-
}
|
|
4000
|
-
}
|
|
4001
|
-
};
|
|
4002
|
-
async function startServer() {
|
|
4003
|
-
const server = new MCPServer();
|
|
4004
|
-
await server.start();
|
|
4005
|
-
}
|
|
4006
|
-
|
|
4007
|
-
export {
|
|
4008
|
-
MCPServer,
|
|
4009
|
-
startServer
|
|
4010
|
-
};
|
|
4011
|
-
//# sourceMappingURL=chunk-3XR6WVAW.js.map
|