@triedotdev/mcp 1.0.110 → 1.0.111
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-OTTR5JX4.js → chunk-6QR6QZIX.js} +70 -1
- package/dist/chunk-6QR6QZIX.js.map +1 -0
- package/dist/{chunk-OD4SLUIV.js → chunk-HGEKZ2VS.js} +2919 -371
- package/dist/chunk-HGEKZ2VS.js.map +1 -0
- package/dist/chunk-QYOACM2C.js +1923 -0
- package/dist/chunk-QYOACM2C.js.map +1 -0
- package/dist/{chunk-SUHYYM2J.js → chunk-SDS3UVFY.js} +2 -2
- package/dist/chunk-TKMV7JKN.js +1562 -0
- package/dist/chunk-TKMV7JKN.js.map +1 -0
- package/dist/cli/main.js +320 -57
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/yolo-daemon.js +4 -4
- package/dist/{guardian-agent-4UJN5QGX.js → guardian-agent-XEYNG7RH.js} +3 -3
- package/dist/index.js +357 -2593
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-FLBK5ILJ.js +0 -3729
- package/dist/chunk-FLBK5ILJ.js.map +0 -1
- package/dist/chunk-HIKONDDO.js +0 -26
- package/dist/chunk-HIKONDDO.js.map +0 -1
- package/dist/chunk-OD4SLUIV.js.map +0 -1
- package/dist/chunk-OTTR5JX4.js.map +0 -1
- /package/dist/{chunk-SUHYYM2J.js.map → chunk-SDS3UVFY.js.map} +0 -0
- /package/dist/{guardian-agent-4UJN5QGX.js.map → guardian-agent-XEYNG7RH.js.map} +0 -0
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getGuardian
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-SDS3UVFY.js";
|
|
4
|
+
import {
|
|
5
|
+
IncidentIndex,
|
|
6
|
+
LearningEngine,
|
|
7
|
+
exportToJson,
|
|
8
|
+
formatFriendlyError,
|
|
9
|
+
getLastCheckpoint,
|
|
10
|
+
listCheckpoints,
|
|
11
|
+
perceiveCurrentChanges,
|
|
12
|
+
reasonAboutChangesHumanReadable,
|
|
13
|
+
saveCheckpoint
|
|
14
|
+
} from "./chunk-QYOACM2C.js";
|
|
4
15
|
import {
|
|
5
16
|
ContextGraph,
|
|
6
17
|
TieredStorage,
|
|
7
18
|
findCrossProjectPatterns,
|
|
8
19
|
getKeyFromKeychain,
|
|
20
|
+
getStorage,
|
|
9
21
|
isAIAvailable,
|
|
10
|
-
|
|
22
|
+
runAIWithTools,
|
|
11
23
|
setAPIKey
|
|
12
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-6QR6QZIX.js";
|
|
13
25
|
import {
|
|
14
26
|
getGuardianState
|
|
15
27
|
} from "./chunk-6JPPYG7F.js";
|
|
@@ -260,7 +272,7 @@ import { render } from "ink";
|
|
|
260
272
|
import React10 from "react";
|
|
261
273
|
|
|
262
274
|
// src/cli/dashboard/App.tsx
|
|
263
|
-
import { useState as useState3, useEffect as useEffect4, useCallback as useCallback6, useRef } from "react";
|
|
275
|
+
import { useState as useState3, useEffect as useEffect4, useCallback as useCallback6, useRef as useRef2 } from "react";
|
|
264
276
|
import { Box as Box12, useInput as useInput8, useApp } from "ink";
|
|
265
277
|
|
|
266
278
|
// src/cli/dashboard/state.tsx
|
|
@@ -278,34 +290,35 @@ function getMemoryTreeNodes(state) {
|
|
|
278
290
|
const nodes = [];
|
|
279
291
|
const { expandedNodes, snapshot, globalPatterns } = state.memoryTree;
|
|
280
292
|
if (!snapshot) return nodes;
|
|
281
|
-
const fileNodes = snapshot.nodes.filter((n) => n.type === "file");
|
|
282
|
-
const changeNodes = snapshot.nodes.filter((n) => n.type === "change");
|
|
283
|
-
const patternNodes = snapshot.nodes.filter((n) => n.type === "pattern");
|
|
284
|
-
const incidentNodes = snapshot.nodes.filter((n) => n.type === "incident");
|
|
285
293
|
const decisionNodes = snapshot.nodes.filter((n) => n.type === "decision");
|
|
286
|
-
nodes.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
patternNodes.slice(0, 10).forEach((n) => nodes.push({ id: `pattern-${n.id}`, level: 1 }));
|
|
294
|
+
const incidentNodes = snapshot.nodes.filter((n) => n.type === "incident");
|
|
295
|
+
const patternNodes = snapshot.nodes.filter((n) => n.type === "pattern");
|
|
296
|
+
const fileNodes = snapshot.nodes.filter((n) => n.type === "file");
|
|
297
|
+
const hotspots = fileNodes.filter((n) => {
|
|
298
|
+
const risk = n.data.riskLevel;
|
|
299
|
+
return risk === "critical" || risk === "high";
|
|
300
|
+
});
|
|
301
|
+
nodes.push({ id: "decisions", level: 0 });
|
|
302
|
+
if (expandedNodes.has("decisions")) {
|
|
303
|
+
decisionNodes.slice(0, 10).forEach((n) => nodes.push({ id: `decision-${n.id}`, level: 1 }));
|
|
297
304
|
}
|
|
298
305
|
nodes.push({ id: "incidents", level: 0 });
|
|
299
306
|
if (expandedNodes.has("incidents")) {
|
|
300
307
|
incidentNodes.slice(0, 10).forEach((n) => nodes.push({ id: `incident-${n.id}`, level: 1 }));
|
|
301
308
|
}
|
|
302
|
-
nodes.push({ id: "
|
|
303
|
-
if (expandedNodes.has("
|
|
304
|
-
|
|
309
|
+
nodes.push({ id: "patterns", level: 0 });
|
|
310
|
+
if (expandedNodes.has("patterns")) {
|
|
311
|
+
patternNodes.slice(0, 10).forEach((n) => nodes.push({ id: `pattern-${n.id}`, level: 1 }));
|
|
305
312
|
}
|
|
306
313
|
nodes.push({ id: "cross-project", level: 0 });
|
|
307
314
|
if (expandedNodes.has("cross-project")) {
|
|
308
|
-
globalPatterns.slice(0,
|
|
315
|
+
globalPatterns.slice(0, 8).forEach((p) => nodes.push({ id: `global-${p.id}`, level: 1 }));
|
|
316
|
+
}
|
|
317
|
+
if (hotspots.length > 0) {
|
|
318
|
+
nodes.push({ id: "hotspots", level: 0 });
|
|
319
|
+
if (expandedNodes.has("hotspots")) {
|
|
320
|
+
hotspots.slice(0, 10).forEach((n) => nodes.push({ id: `file-${n.id}`, level: 1 }));
|
|
321
|
+
}
|
|
309
322
|
}
|
|
310
323
|
return nodes;
|
|
311
324
|
}
|
|
@@ -321,6 +334,10 @@ function handleStreamUpdate(state, update) {
|
|
|
321
334
|
delete s.scanEndTime;
|
|
322
335
|
}
|
|
323
336
|
s.progress = update.data;
|
|
337
|
+
const delta = update.data.processedFiles - oldProgress.processedFiles;
|
|
338
|
+
if (delta > 0) {
|
|
339
|
+
s.watch = { ...s.watch, filesScannedSession: s.watch.filesScannedSession + delta };
|
|
340
|
+
}
|
|
324
341
|
if (update.data.issuesBySeverity?.critical > 0) {
|
|
325
342
|
s.alerts = { hasCritical: true, lastCriticalAt: update.timestamp };
|
|
326
343
|
}
|
|
@@ -361,7 +378,8 @@ function handleStreamUpdate(state, update) {
|
|
|
361
378
|
directories: update.data.directories ?? s.watch.directories,
|
|
362
379
|
debounceMs: update.data.debounceMs ?? s.watch.debounceMs,
|
|
363
380
|
lastChange: update.data.lastChange ?? s.watch.lastChange,
|
|
364
|
-
recentChanges: update.data.recentChanges ?? s.watch.recentChanges
|
|
381
|
+
recentChanges: update.data.recentChanges ?? s.watch.recentChanges,
|
|
382
|
+
filesScannedSession: s.watch.filesScannedSession
|
|
365
383
|
};
|
|
366
384
|
if (update.data.watching !== void 0) {
|
|
367
385
|
s = addActivity(s, update.data.watching ? `[*] Watch mode: ACTIVE (${update.data.directories ?? 0} dirs)` : "[*] Watch mode: OFF");
|
|
@@ -372,7 +390,8 @@ function handleStreamUpdate(state, update) {
|
|
|
372
390
|
s.watch = {
|
|
373
391
|
...s.watch,
|
|
374
392
|
recentChanges: [entry, ...s.watch.recentChanges].slice(0, 5),
|
|
375
|
-
lastChange: entry.time
|
|
393
|
+
lastChange: entry.time,
|
|
394
|
+
filesScannedSession: s.watch.filesScannedSession + 1
|
|
376
395
|
};
|
|
377
396
|
s = addActivity(s, `Change detected: ${update.data.file}`);
|
|
378
397
|
break;
|
|
@@ -557,7 +576,7 @@ function dashboardReducer(state, action) {
|
|
|
557
576
|
case "SELECT_MEMORY_NODE":
|
|
558
577
|
return { ...state, memoryTree: { ...state.memoryTree, selectedNode: action.nodeId } };
|
|
559
578
|
case "TOGGLE_MEMORY_NODE": {
|
|
560
|
-
const expandable = ["
|
|
579
|
+
const expandable = ["decisions", "incidents", "patterns", "cross-project", "hotspots"];
|
|
561
580
|
if (!expandable.includes(action.nodeId)) return state;
|
|
562
581
|
const expanded = new Set(state.memoryTree.expandedNodes);
|
|
563
582
|
if (expanded.has(action.nodeId)) expanded.delete(action.nodeId);
|
|
@@ -577,14 +596,21 @@ function dashboardReducer(state, action) {
|
|
|
577
596
|
};
|
|
578
597
|
case "SET_CHAT_INPUT":
|
|
579
598
|
return { ...state, chatState: { ...state.chatState, inputBuffer: action.buffer } };
|
|
580
|
-
case "ADD_CHAT_MESSAGE":
|
|
599
|
+
case "ADD_CHAT_MESSAGE": {
|
|
600
|
+
const msg = {
|
|
601
|
+
role: action.role,
|
|
602
|
+
content: action.content,
|
|
603
|
+
timestamp: Date.now()
|
|
604
|
+
};
|
|
605
|
+
if (action.toolCalls && action.toolCalls.length > 0) msg.toolCalls = action.toolCalls;
|
|
581
606
|
return {
|
|
582
607
|
...state,
|
|
583
608
|
chatState: {
|
|
584
609
|
...state.chatState,
|
|
585
|
-
messages: [...state.chatState.messages,
|
|
610
|
+
messages: [...state.chatState.messages, msg].slice(-20)
|
|
586
611
|
}
|
|
587
612
|
};
|
|
613
|
+
}
|
|
588
614
|
case "SET_CHAT_LOADING":
|
|
589
615
|
return { ...state, chatState: { ...state.chatState, loading: action.loading } };
|
|
590
616
|
case "SET_AGENT_CONFIG":
|
|
@@ -684,7 +710,7 @@ function createInitialState() {
|
|
|
684
710
|
questionsExtracted: 0
|
|
685
711
|
},
|
|
686
712
|
alerts: { hasCritical: false },
|
|
687
|
-
watch: { watching: false, directories: 0, recentChanges: [] },
|
|
713
|
+
watch: { watching: false, directories: 0, recentChanges: [], filesScannedSession: 0 },
|
|
688
714
|
rawLog: [],
|
|
689
715
|
rawLogPage: 0,
|
|
690
716
|
scrollPositions: { overview: 0, rawlog: 0, agent: 0, goals: 0, hypotheses: 0, memory: 0, chat: 0 },
|
|
@@ -707,7 +733,7 @@ function createInitialState() {
|
|
|
707
733
|
},
|
|
708
734
|
goalsPanel: { goals: [], selectedIndex: 0, selectedAchievedIndex: 0, inputMode: "browse", inputBuffer: "", lastRefresh: 0 },
|
|
709
735
|
hypothesesPanel: { hypotheses: [], selectedIndex: 0, selectedCompletedIndex: 0, inputMode: "browse", inputBuffer: "", lastRefresh: 0 },
|
|
710
|
-
memoryTree: { loaded: false, snapshot: null, globalPatterns: [], expandedNodes: /* @__PURE__ */ new Set(["
|
|
736
|
+
memoryTree: { loaded: false, snapshot: null, globalPatterns: [], expandedNodes: /* @__PURE__ */ new Set(["decisions"]), selectedNode: "decisions", scrollPosition: 0, lastRefresh: 0 },
|
|
711
737
|
agentBrain: { loaded: false, decisions: [], patterns: [], ledgerHash: null, selectedIndex: 0, expandedIndex: null },
|
|
712
738
|
chatState: { messages: [], inputBuffer: "", loading: false }
|
|
713
739
|
};
|
|
@@ -1275,8 +1301,8 @@ function getOutputManager() {
|
|
|
1275
1301
|
}
|
|
1276
1302
|
|
|
1277
1303
|
// src/cli/dashboard/App.tsx
|
|
1278
|
-
import { existsSync } from "fs";
|
|
1279
|
-
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
1304
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1305
|
+
import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
|
|
1280
1306
|
import { join } from "path";
|
|
1281
1307
|
|
|
1282
1308
|
// src/cli/dashboard/components/Header.tsx
|
|
@@ -1333,10 +1359,10 @@ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
|
1333
1359
|
var VIEW_LABELS = {
|
|
1334
1360
|
overview: "Overview",
|
|
1335
1361
|
rawlog: "Log",
|
|
1336
|
-
agent: "
|
|
1362
|
+
agent: "Nudges",
|
|
1337
1363
|
goals: "Goals",
|
|
1338
1364
|
hypotheses: "Hypotheses",
|
|
1339
|
-
memory: "
|
|
1365
|
+
memory: "Ledger",
|
|
1340
1366
|
chat: "Chat"
|
|
1341
1367
|
};
|
|
1342
1368
|
var TAB_VIEWS = ["overview", "memory", "goals", "hypotheses", "agent", "chat"];
|
|
@@ -1355,7 +1381,7 @@ function Footer() {
|
|
|
1355
1381
|
} else if (view === "agent") {
|
|
1356
1382
|
contextHints = "tab views \xB7 j/k nav \xB7 enter expand \xB7 d dismiss";
|
|
1357
1383
|
} else if (view === "memory") {
|
|
1358
|
-
contextHints = "tab views \xB7 j/k nav \xB7 enter
|
|
1384
|
+
contextHints = "tab views \xB7 j/k nav \xB7 enter expand";
|
|
1359
1385
|
} else if (view === "chat") {
|
|
1360
1386
|
contextHints = "tab views \xB7 type to ask \xB7 enter send \xB7 esc clear";
|
|
1361
1387
|
} else if (view === "rawlog") {
|
|
@@ -1571,7 +1597,7 @@ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
|
1571
1597
|
function OverviewView() {
|
|
1572
1598
|
const { state } = useDashboard();
|
|
1573
1599
|
const { progress, signalExtraction, watch, issues, activityLog, activityPage } = state;
|
|
1574
|
-
const { totalIssues
|
|
1600
|
+
const { totalIssues } = progress;
|
|
1575
1601
|
const endTime = state.scanComplete && state.scanEndTime ? state.scanEndTime : Date.now();
|
|
1576
1602
|
const elapsed = ((endTime - state.scanStartTime) / 1e3).toFixed(1);
|
|
1577
1603
|
const criticalIssues = issues.filter((i) => i.severity === "critical").slice(0, 3);
|
|
@@ -1582,8 +1608,8 @@ function OverviewView() {
|
|
|
1582
1608
|
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1583
1609
|
/* @__PURE__ */ jsx6(Text5, { dimColor: true, children: "\u25CF" }),
|
|
1584
1610
|
" Scanned ",
|
|
1585
|
-
/* @__PURE__ */ jsx6(Text5, { bold: true, children:
|
|
1586
|
-
" files ",
|
|
1611
|
+
/* @__PURE__ */ jsx6(Text5, { bold: true, children: watch.filesScannedSession }),
|
|
1612
|
+
" files this session ",
|
|
1587
1613
|
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1588
1614
|
elapsed,
|
|
1589
1615
|
"s"
|
|
@@ -1739,60 +1765,23 @@ function AgentView() {
|
|
|
1739
1765
|
const patCount = patterns.length;
|
|
1740
1766
|
if (alertCount === 0 && decCount === 0 && patCount === 0 && !loaded) {
|
|
1741
1767
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
|
|
1742
|
-
/* @__PURE__ */ jsx7(Text6, { bold: true, children: "
|
|
1768
|
+
/* @__PURE__ */ jsx7(Text6, { bold: true, children: "Nudges" }),
|
|
1743
1769
|
/* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " Loading..." })
|
|
1744
1770
|
] });
|
|
1745
1771
|
}
|
|
1746
1772
|
if (alertCount === 0 && decCount === 0 && patCount === 0) {
|
|
1747
1773
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
|
|
1748
|
-
/* @__PURE__ */
|
|
1749
|
-
/* @__PURE__ */ jsx7(Text6, { bold: true, children: "Trie" }),
|
|
1750
|
-
" ",
|
|
1751
|
-
/* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "Proactive code guardian" })
|
|
1752
|
-
] }),
|
|
1753
|
-
/* @__PURE__ */ jsx7(Text6, { children: " " }),
|
|
1754
|
-
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1755
|
-
" ",
|
|
1756
|
-
/* @__PURE__ */ jsx7(Text6, { color: "green", children: "\u25CF" }),
|
|
1757
|
-
" Alerts you about gotchas before you push"
|
|
1758
|
-
] }),
|
|
1759
|
-
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1760
|
-
" ",
|
|
1761
|
-
/* @__PURE__ */ jsx7(Text6, { color: "green", children: "\u25CF" }),
|
|
1762
|
-
" Records decisions with traceable hashes"
|
|
1763
|
-
] }),
|
|
1764
|
-
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1765
|
-
" ",
|
|
1766
|
-
/* @__PURE__ */ jsx7(Text6, { color: "green", children: "\u25CF" }),
|
|
1767
|
-
" Learns patterns from your feedback (trie ok / trie bad)"
|
|
1768
|
-
] }),
|
|
1769
|
-
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1770
|
-
" ",
|
|
1771
|
-
/* @__PURE__ */ jsx7(Text6, { color: "green", children: "\u25CF" }),
|
|
1772
|
-
" Gets smarter over time"
|
|
1773
|
-
] }),
|
|
1774
|
-
/* @__PURE__ */ jsx7(Text6, { children: " " }),
|
|
1775
|
-
/* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " Start watching to get proactive alerts," }),
|
|
1776
|
-
/* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " or run trie tell / trie ok / trie bad to teach it." }),
|
|
1774
|
+
/* @__PURE__ */ jsx7(Text6, { bold: true, children: "Nudges" }),
|
|
1777
1775
|
/* @__PURE__ */ jsx7(Text6, { children: " " }),
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
" ",
|
|
1781
|
-
/* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "AI-enhanced analysis enabled" })
|
|
1782
|
-
] }) : /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1783
|
-
/* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "\u25CB" }),
|
|
1784
|
-
" Set ",
|
|
1785
|
-
/* @__PURE__ */ jsx7(Text6, { bold: true, children: "ANTHROPIC_API_KEY" }),
|
|
1786
|
-
" ",
|
|
1787
|
-
/* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "for AI insights" })
|
|
1788
|
-
] })
|
|
1776
|
+
/* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " No nudges yet." }),
|
|
1777
|
+
/* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " Trie will alert you here when it spots issues in your code." })
|
|
1789
1778
|
] });
|
|
1790
1779
|
}
|
|
1791
1780
|
const confidentPatterns = patterns.filter((p) => p.confidence > 0.7).length;
|
|
1792
1781
|
const learningPatterns = patterns.filter((p) => p.confidence <= 0.7).length;
|
|
1793
1782
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
|
|
1794
1783
|
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1795
|
-
/* @__PURE__ */ jsx7(Text6, { bold: true, children: "
|
|
1784
|
+
/* @__PURE__ */ jsx7(Text6, { bold: true, children: "Nudges" }),
|
|
1796
1785
|
" ",
|
|
1797
1786
|
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
1798
1787
|
alertCount,
|
|
@@ -1816,14 +1805,14 @@ function AgentView() {
|
|
|
1816
1805
|
const isExpanded = idx === expandedInsight;
|
|
1817
1806
|
const ago = formatTimeAgo(insight.timestamp);
|
|
1818
1807
|
const msg = insight.message.slice(0, 60) + (insight.message.length > 60 ? "..." : "");
|
|
1819
|
-
const
|
|
1808
|
+
const riskColor = insight.priority >= 8 ? "red" : insight.priority >= 5 ? "yellow" : void 0;
|
|
1820
1809
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
1821
1810
|
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1822
1811
|
isSelected ? /* @__PURE__ */ jsxs6(Text6, { bold: true, color: "green", children: [
|
|
1823
1812
|
">",
|
|
1824
1813
|
" "
|
|
1825
1814
|
] }) : /* @__PURE__ */ jsx7(Text6, { children: " " }),
|
|
1826
|
-
|
|
1815
|
+
riskColor ? /* @__PURE__ */ jsx7(Text6, { color: riskColor, children: "\u25CF" }) : /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "\u25CB" }),
|
|
1827
1816
|
" ",
|
|
1828
1817
|
isSelected ? /* @__PURE__ */ jsx7(Text6, { bold: true, children: msg }) : /* @__PURE__ */ jsx7(Text6, { children: msg }),
|
|
1829
1818
|
" ",
|
|
@@ -2304,20 +2293,7 @@ function HypothesesView() {
|
|
|
2304
2293
|
// src/cli/dashboard/views/MemoryTreeView.tsx
|
|
2305
2294
|
import { useEffect as useEffect3, useCallback as useCallback4 } from "react";
|
|
2306
2295
|
import { Box as Box9, Text as Text9, useInput as useInput5 } from "ink";
|
|
2307
|
-
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2308
|
-
var RISK_ORDER = { critical: 0, high: 1, medium: 2, low: 3, unknown: 4 };
|
|
2309
|
-
function riskColor(risk) {
|
|
2310
|
-
switch (risk) {
|
|
2311
|
-
case "critical":
|
|
2312
|
-
return "red";
|
|
2313
|
-
case "high":
|
|
2314
|
-
return "yellow";
|
|
2315
|
-
case "medium":
|
|
2316
|
-
return "blue";
|
|
2317
|
-
default:
|
|
2318
|
-
return void 0;
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2296
|
+
import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2321
2297
|
function timeAgo2(iso) {
|
|
2322
2298
|
const ms = Date.now() - new Date(iso).getTime();
|
|
2323
2299
|
const mins = Math.floor(ms / 6e4);
|
|
@@ -2356,126 +2332,116 @@ function MemoryTreeView() {
|
|
|
2356
2332
|
const sel = (nodeId) => selectedNode === nodeId;
|
|
2357
2333
|
if (!loaded) {
|
|
2358
2334
|
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingX: 1, children: [
|
|
2359
|
-
/* @__PURE__ */ jsx10(Text9, { bold: true, children: "
|
|
2360
|
-
/* @__PURE__ */ jsx10(Text9, { dimColor: true, children: " Loading
|
|
2335
|
+
/* @__PURE__ */ jsx10(Text9, { bold: true, children: "Ledger" }),
|
|
2336
|
+
/* @__PURE__ */ jsx10(Text9, { dimColor: true, children: " Loading..." })
|
|
2361
2337
|
] });
|
|
2362
2338
|
}
|
|
2363
|
-
|
|
2364
|
-
const edgeCount = snapshot?.edges.length ?? 0;
|
|
2365
|
-
if (nodeCount === 0 && globalPatterns.length === 0) {
|
|
2339
|
+
if (!snapshot || snapshot.nodes.length === 0 && globalPatterns.length === 0) {
|
|
2366
2340
|
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingX: 1, children: [
|
|
2367
|
-
/* @__PURE__ */ jsx10(Text9, { bold: true, children: "
|
|
2341
|
+
/* @__PURE__ */ jsx10(Text9, { bold: true, children: "Ledger" }),
|
|
2368
2342
|
/* @__PURE__ */ jsx10(Text9, { children: " " }),
|
|
2369
|
-
/* @__PURE__ */ jsx10(Text9, { dimColor: true, children: " No
|
|
2370
|
-
/* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "
|
|
2343
|
+
/* @__PURE__ */ jsx10(Text9, { dimColor: true, children: " No entries yet." }),
|
|
2344
|
+
/* @__PURE__ */ jsx10(Text9, { dimColor: true, children: " Use trie tell, trie ok/bad, or trie watch to build memory." })
|
|
2371
2345
|
] });
|
|
2372
2346
|
}
|
|
2373
|
-
const
|
|
2374
|
-
const
|
|
2375
|
-
const patternNodes = snapshot
|
|
2376
|
-
const
|
|
2377
|
-
const
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
);
|
|
2381
|
-
const
|
|
2382
|
-
|
|
2383
|
-
);
|
|
2384
|
-
function renderSectionHeader(id, label, count) {
|
|
2347
|
+
const decisionNodes = snapshot.nodes.filter((n) => n.type === "decision") ?? [];
|
|
2348
|
+
const incidentNodes = snapshot.nodes.filter((n) => n.type === "incident") ?? [];
|
|
2349
|
+
const patternNodes = snapshot.nodes.filter((n) => n.type === "pattern") ?? [];
|
|
2350
|
+
const fileNodes = snapshot.nodes.filter((n) => n.type === "file") ?? [];
|
|
2351
|
+
const hotspots = fileNodes.filter((n) => n.data.riskLevel === "critical" || n.data.riskLevel === "high").sort((a, b) => {
|
|
2352
|
+
const order = { critical: 0, high: 1 };
|
|
2353
|
+
return (order[a.data.riskLevel] ?? 2) - (order[b.data.riskLevel] ?? 2);
|
|
2354
|
+
});
|
|
2355
|
+
const totalEntries = decisionNodes.length + incidentNodes.length + patternNodes.length + globalPatterns.length + hotspots.length;
|
|
2356
|
+
function renderHeader(id, label, count, emptyHint) {
|
|
2385
2357
|
const expanded = expandedNodes.has(id);
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
"
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
count,
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2358
|
+
const isEmpty = count === 0;
|
|
2359
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
2360
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2361
|
+
sel(id) ? /* @__PURE__ */ jsxs9(Text9, { bold: true, color: "green", children: [
|
|
2362
|
+
">",
|
|
2363
|
+
" "
|
|
2364
|
+
] }) : /* @__PURE__ */ jsx10(Text9, { children: " " }),
|
|
2365
|
+
expanded && !isEmpty ? /* @__PURE__ */ jsx10(Text9, { color: "green", children: "\u25CF" }) : /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "\u25CB" }),
|
|
2366
|
+
" ",
|
|
2367
|
+
sel(id) ? /* @__PURE__ */ jsx10(Text9, { bold: true, color: "green", children: label }) : /* @__PURE__ */ jsx10(Text9, { bold: true, children: label }),
|
|
2368
|
+
count > 0 ? /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
2369
|
+
" (",
|
|
2370
|
+
count,
|
|
2371
|
+
")"
|
|
2372
|
+
] }) : null
|
|
2373
|
+
] }),
|
|
2374
|
+
isEmpty && emptyHint && /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
2375
|
+
" ",
|
|
2376
|
+
emptyHint
|
|
2403
2377
|
] })
|
|
2404
2378
|
] });
|
|
2405
2379
|
}
|
|
2406
2380
|
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingX: 1, children: [
|
|
2407
2381
|
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2408
|
-
/* @__PURE__ */ jsx10(Text9, { bold: true, children: "
|
|
2382
|
+
/* @__PURE__ */ jsx10(Text9, { bold: true, children: "Ledger" }),
|
|
2409
2383
|
" ",
|
|
2410
2384
|
/* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
2411
|
-
|
|
2412
|
-
"
|
|
2413
|
-
edgeCount,
|
|
2414
|
-
" edges"
|
|
2385
|
+
totalEntries,
|
|
2386
|
+
" entries"
|
|
2415
2387
|
] })
|
|
2416
2388
|
] }),
|
|
2417
2389
|
/* @__PURE__ */ jsx10(Text9, { children: " " }),
|
|
2418
|
-
|
|
2419
|
-
expandedNodes.has("
|
|
2420
|
-
const nodeId = `
|
|
2421
|
-
const
|
|
2422
|
-
const
|
|
2423
|
-
const color = riskColor(risk);
|
|
2390
|
+
renderHeader("decisions", "Decisions", decisionNodes.length, "No decisions recorded \u2014 use trie tell or chat"),
|
|
2391
|
+
expandedNodes.has("decisions") && decisionNodes.slice(0, 10).map((n) => {
|
|
2392
|
+
const nodeId = `decision-${n.id}`;
|
|
2393
|
+
const dec = n.data.decision.length > 55 ? n.data.decision.slice(0, 52) + "..." : n.data.decision;
|
|
2394
|
+
const outcomeColor = n.data.outcome === "good" ? "green" : n.data.outcome === "bad" ? "red" : void 0;
|
|
2424
2395
|
return /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2425
2396
|
sel(nodeId) ? /* @__PURE__ */ jsxs9(Text9, { bold: true, color: "green", children: [
|
|
2426
2397
|
">",
|
|
2427
2398
|
" "
|
|
2428
2399
|
] }) : /* @__PURE__ */ jsx10(Text9, { children: " " }),
|
|
2429
2400
|
" ",
|
|
2430
|
-
|
|
2401
|
+
outcomeColor ? /* @__PURE__ */ jsx10(Text9, { color: outcomeColor, children: "\u25CF" }) : /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "\u25CB" }),
|
|
2431
2402
|
" ",
|
|
2432
|
-
sel(nodeId) ? /* @__PURE__ */ jsx10(Text9, { bold: true, color: "green", children:
|
|
2433
|
-
" ",
|
|
2434
|
-
color ? /* @__PURE__ */ jsx10(Text9, { color, children: risk }) : /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: risk }),
|
|
2403
|
+
sel(nodeId) ? /* @__PURE__ */ jsx10(Text9, { bold: true, color: "green", children: dec }) : /* @__PURE__ */ jsx10(Text9, { children: dec }),
|
|
2435
2404
|
" ",
|
|
2436
|
-
/* @__PURE__ */
|
|
2437
|
-
|
|
2438
|
-
"
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
" ",
|
|
2442
|
-
n.data.incidentCount,
|
|
2443
|
-
" incidents"
|
|
2444
|
-
] })
|
|
2405
|
+
/* @__PURE__ */ jsx10(Text9, { dimColor: true, children: timeAgo2(n.data.timestamp) }),
|
|
2406
|
+
outcomeColor ? /* @__PURE__ */ jsxs9(Text9, { color: outcomeColor, children: [
|
|
2407
|
+
" ",
|
|
2408
|
+
n.data.outcome
|
|
2409
|
+
] }) : null
|
|
2445
2410
|
] }, n.id);
|
|
2446
2411
|
}),
|
|
2447
|
-
|
|
2448
|
-
expandedNodes.has("
|
|
2449
|
-
const nodeId = `
|
|
2450
|
-
const
|
|
2451
|
-
const
|
|
2452
|
-
const outcomeColor = outcome === "incident" ? "red" : outcome === "success" ? "green" : void 0;
|
|
2412
|
+
renderHeader("incidents", "Incidents", incidentNodes.length, "No incidents \u2014 use trie tell to report problems"),
|
|
2413
|
+
expandedNodes.has("incidents") && incidentNodes.slice(0, 10).map((n) => {
|
|
2414
|
+
const nodeId = `incident-${n.id}`;
|
|
2415
|
+
const sevColor = n.data.severity === "critical" ? "red" : n.data.severity === "major" ? "yellow" : void 0;
|
|
2416
|
+
const desc = n.data.description.length > 55 ? n.data.description.slice(0, 52) + "..." : n.data.description;
|
|
2453
2417
|
return /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2454
2418
|
sel(nodeId) ? /* @__PURE__ */ jsxs9(Text9, { bold: true, color: "green", children: [
|
|
2455
2419
|
">",
|
|
2456
2420
|
" "
|
|
2457
2421
|
] }) : /* @__PURE__ */ jsx10(Text9, { children: " " }),
|
|
2458
2422
|
" ",
|
|
2459
|
-
|
|
2423
|
+
sevColor ? /* @__PURE__ */ jsx10(Text9, { color: sevColor, children: "\u25CF" }) : /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "\u25CB" }),
|
|
2460
2424
|
" ",
|
|
2461
|
-
sel(nodeId) ? /* @__PURE__ */ jsx10(Text9, { bold: true, color: "green", children:
|
|
2425
|
+
sel(nodeId) ? /* @__PURE__ */ jsx10(Text9, { bold: true, color: "green", children: desc }) : /* @__PURE__ */ jsx10(Text9, { children: desc }),
|
|
2462
2426
|
" ",
|
|
2463
|
-
/* @__PURE__ */ jsx10(Text9, { dimColor: true, children: timeAgo2(n.data.timestamp) })
|
|
2427
|
+
/* @__PURE__ */ jsx10(Text9, { dimColor: true, children: timeAgo2(n.data.timestamp) }),
|
|
2428
|
+
" ",
|
|
2429
|
+
n.data.resolved ? /* @__PURE__ */ jsx10(Text9, { color: "green", children: "resolved" }) : /* @__PURE__ */ jsx10(Text9, { color: "yellow", children: "open" })
|
|
2464
2430
|
] }, n.id);
|
|
2465
2431
|
}),
|
|
2466
|
-
|
|
2432
|
+
renderHeader("patterns", "Learned Patterns", patternNodes.length, "No patterns yet \u2014 Trie learns as you work"),
|
|
2467
2433
|
expandedNodes.has("patterns") && patternNodes.slice(0, 10).map((n) => {
|
|
2468
2434
|
const nodeId = `pattern-${n.id}`;
|
|
2469
2435
|
const conf = Math.round(n.data.confidence * 100);
|
|
2470
2436
|
const confColor = conf > 70 ? "green" : conf > 40 ? "yellow" : void 0;
|
|
2471
|
-
const desc = n.data.description.
|
|
2437
|
+
const desc = n.data.description.length > 50 ? n.data.description.slice(0, 47) + "..." : n.data.description;
|
|
2472
2438
|
return /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2473
2439
|
sel(nodeId) ? /* @__PURE__ */ jsxs9(Text9, { bold: true, color: "green", children: [
|
|
2474
2440
|
">",
|
|
2475
2441
|
" "
|
|
2476
2442
|
] }) : /* @__PURE__ */ jsx10(Text9, { children: " " }),
|
|
2477
2443
|
" ",
|
|
2478
|
-
n.data.isAntiPattern ? /* @__PURE__ */ jsx10(Text9, { color: "red", children: "
|
|
2444
|
+
n.data.isAntiPattern ? /* @__PURE__ */ jsx10(Text9, { color: "red", children: "!" }) : /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "\u25CB" }),
|
|
2479
2445
|
" ",
|
|
2480
2446
|
sel(nodeId) ? /* @__PURE__ */ jsx10(Text9, { bold: true, color: "green", children: desc }) : /* @__PURE__ */ jsx10(Text9, { children: desc }),
|
|
2481
2447
|
" ",
|
|
@@ -2489,60 +2455,62 @@ function MemoryTreeView() {
|
|
|
2489
2455
|
n.data.isAntiPattern && /* @__PURE__ */ jsx10(Text9, { color: "red", children: " anti-pattern" })
|
|
2490
2456
|
] }, n.id);
|
|
2491
2457
|
}),
|
|
2492
|
-
|
|
2493
|
-
expandedNodes.has("
|
|
2494
|
-
const nodeId = `incident-${n.id}`;
|
|
2495
|
-
const sevColor = n.data.severity === "critical" ? "red" : n.data.severity === "major" ? "yellow" : void 0;
|
|
2496
|
-
const desc = n.data.description.slice(0, 45) + (n.data.description.length > 45 ? "..." : "");
|
|
2497
|
-
return /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2498
|
-
sel(nodeId) ? /* @__PURE__ */ jsxs9(Text9, { bold: true, color: "green", children: [
|
|
2499
|
-
">",
|
|
2500
|
-
" "
|
|
2501
|
-
] }) : /* @__PURE__ */ jsx10(Text9, { children: " " }),
|
|
2502
|
-
" ",
|
|
2503
|
-
sevColor ? /* @__PURE__ */ jsx10(Text9, { color: sevColor, children: "\u25CF" }) : /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "\u25CB" }),
|
|
2504
|
-
" ",
|
|
2505
|
-
sel(nodeId) ? /* @__PURE__ */ jsx10(Text9, { bold: true, color: "green", children: desc }) : /* @__PURE__ */ jsx10(Text9, { children: desc }),
|
|
2506
|
-
" ",
|
|
2507
|
-
n.data.resolved ? /* @__PURE__ */ jsx10(Text9, { color: "green", children: "resolved" }) : /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "open" })
|
|
2508
|
-
] }, n.id);
|
|
2509
|
-
}),
|
|
2510
|
-
renderSectionHeader("decisions", "Decisions", decisionNodes.length),
|
|
2511
|
-
expandedNodes.has("decisions") && decisionNodes.slice(0, 10).map((n) => {
|
|
2512
|
-
const nodeId = `decision-${n.id}`;
|
|
2513
|
-
const dec = n.data.decision.slice(0, 45) + (n.data.decision.length > 45 ? "..." : "");
|
|
2514
|
-
const outcomeColor = n.data.outcome === "good" ? "green" : n.data.outcome === "bad" ? "red" : void 0;
|
|
2515
|
-
return /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2516
|
-
sel(nodeId) ? /* @__PURE__ */ jsxs9(Text9, { bold: true, color: "green", children: [
|
|
2517
|
-
">",
|
|
2518
|
-
" "
|
|
2519
|
-
] }) : /* @__PURE__ */ jsx10(Text9, { children: " " }),
|
|
2520
|
-
" ",
|
|
2521
|
-
outcomeColor ? /* @__PURE__ */ jsx10(Text9, { color: outcomeColor, children: "\u25CF" }) : /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "\u25CB" }),
|
|
2522
|
-
" ",
|
|
2523
|
-
sel(nodeId) ? /* @__PURE__ */ jsx10(Text9, { bold: true, color: "green", children: dec }) : /* @__PURE__ */ jsx10(Text9, { children: dec }),
|
|
2524
|
-
" ",
|
|
2525
|
-
outcomeColor ? /* @__PURE__ */ jsx10(Text9, { color: outcomeColor, children: n.data.outcome }) : /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: n.data.outcome })
|
|
2526
|
-
] }, n.id);
|
|
2527
|
-
}),
|
|
2528
|
-
renderSectionHeader("cross-project", "Cross-Project", globalPatterns.length),
|
|
2529
|
-
expandedNodes.has("cross-project") && globalPatterns.slice(0, 5).map((pattern) => {
|
|
2458
|
+
renderHeader("cross-project", "Cross-Project", globalPatterns.length),
|
|
2459
|
+
expandedNodes.has("cross-project") && globalPatterns.slice(0, 8).map((pattern) => {
|
|
2530
2460
|
const patternId = `global-${pattern.id}`;
|
|
2531
|
-
const desc = pattern.pattern.
|
|
2461
|
+
const desc = pattern.pattern.length > 45 ? pattern.pattern.slice(0, 42) + "..." : pattern.pattern;
|
|
2532
2462
|
return /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2533
2463
|
sel(patternId) ? /* @__PURE__ */ jsxs9(Text9, { bold: true, color: "green", children: [
|
|
2534
2464
|
">",
|
|
2535
2465
|
" "
|
|
2536
2466
|
] }) : /* @__PURE__ */ jsx10(Text9, { children: " " }),
|
|
2537
2467
|
" ",
|
|
2468
|
+
/* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "\u25CB" }),
|
|
2469
|
+
" ",
|
|
2470
|
+
sel(patternId) ? /* @__PURE__ */ jsx10(Text9, { bold: true, color: "green", children: desc }) : /* @__PURE__ */ jsx10(Text9, { children: desc }),
|
|
2471
|
+
" ",
|
|
2538
2472
|
/* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
2539
|
-
desc,
|
|
2540
|
-
" \xB7 ",
|
|
2541
2473
|
pattern.projects.length,
|
|
2542
|
-
" projects"
|
|
2474
|
+
" projects \xB7 ",
|
|
2475
|
+
pattern.occurrences,
|
|
2476
|
+
" hits"
|
|
2543
2477
|
] })
|
|
2544
2478
|
] }, pattern.id);
|
|
2545
|
-
})
|
|
2479
|
+
}),
|
|
2480
|
+
hotspots.length > 0 && /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
2481
|
+
renderHeader("hotspots", "Risk Hotspots", hotspots.length),
|
|
2482
|
+
expandedNodes.has("hotspots") && hotspots.slice(0, 10).map((n) => {
|
|
2483
|
+
const nodeId = `file-${n.id}`;
|
|
2484
|
+
const path2 = n.data.path.split("/").slice(-2).join("/");
|
|
2485
|
+
const isCritical = n.data.riskLevel === "critical";
|
|
2486
|
+
return /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2487
|
+
sel(nodeId) ? /* @__PURE__ */ jsxs9(Text9, { bold: true, color: "green", children: [
|
|
2488
|
+
">",
|
|
2489
|
+
" "
|
|
2490
|
+
] }) : /* @__PURE__ */ jsx10(Text9, { children: " " }),
|
|
2491
|
+
" ",
|
|
2492
|
+
/* @__PURE__ */ jsx10(Text9, { color: isCritical ? "red" : "yellow", children: "\u25CF" }),
|
|
2493
|
+
" ",
|
|
2494
|
+
sel(nodeId) ? /* @__PURE__ */ jsx10(Text9, { bold: true, color: "green", children: path2 }) : /* @__PURE__ */ jsx10(Text9, { children: path2 }),
|
|
2495
|
+
" ",
|
|
2496
|
+
/* @__PURE__ */ jsx10(Text9, { color: isCritical ? "red" : "yellow", children: n.data.riskLevel }),
|
|
2497
|
+
" ",
|
|
2498
|
+
/* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
2499
|
+
n.data.changeCount,
|
|
2500
|
+
" changes"
|
|
2501
|
+
] }),
|
|
2502
|
+
n.data.incidentCount > 0 && /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
|
|
2503
|
+
" ",
|
|
2504
|
+
n.data.incidentCount,
|
|
2505
|
+
" incidents"
|
|
2506
|
+
] }),
|
|
2507
|
+
n.data.whyRisky && /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
2508
|
+
" ",
|
|
2509
|
+
n.data.whyRisky
|
|
2510
|
+
] })
|
|
2511
|
+
] }, n.id);
|
|
2512
|
+
})
|
|
2513
|
+
] })
|
|
2546
2514
|
] });
|
|
2547
2515
|
}
|
|
2548
2516
|
|
|
@@ -2593,168 +2561,2736 @@ function RawLogView() {
|
|
|
2593
2561
|
}
|
|
2594
2562
|
|
|
2595
2563
|
// src/cli/dashboard/views/ChatView.tsx
|
|
2596
|
-
import { useCallback as useCallback5 } from "react";
|
|
2564
|
+
import { useCallback as useCallback5, useRef } from "react";
|
|
2597
2565
|
import { Box as Box11, Text as Text11, useInput as useInput7 } from "ink";
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2566
|
+
|
|
2567
|
+
// src/tools/tell.ts
|
|
2568
|
+
import path from "path";
|
|
2569
|
+
|
|
2570
|
+
// src/extraction/signal-extractor.ts
|
|
2571
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2572
|
+
var EXTRACTION_PROMPT = `You are a signal extraction system. Your job is to extract structured information from raw content.
|
|
2573
|
+
|
|
2574
|
+
Extract:
|
|
2575
|
+
1. DECISIONS - Clear choices made during development
|
|
2576
|
+
- What was decided
|
|
2577
|
+
- Why it was decided (reasoning/tradeoffs)
|
|
2578
|
+
- What alternatives were considered but NOT chosen
|
|
2579
|
+
- Which files are affected
|
|
2580
|
+
|
|
2581
|
+
2. FACTS - Concrete, verifiable information
|
|
2582
|
+
- Technical constraints (e.g., "Stripe requires TLS 1.2+")
|
|
2583
|
+
- API requirements
|
|
2584
|
+
- Business rules
|
|
2585
|
+
- Dependencies
|
|
2586
|
+
|
|
2587
|
+
3. BLOCKERS - Things preventing progress
|
|
2588
|
+
- What's blocked
|
|
2589
|
+
- Impact level (critical/high/medium/low)
|
|
2590
|
+
- What areas are affected
|
|
2591
|
+
|
|
2592
|
+
4. QUESTIONS - Open items needing resolution
|
|
2593
|
+
- What's unclear
|
|
2594
|
+
- Context around the question
|
|
2595
|
+
|
|
2596
|
+
CRITICAL: Extract rich metadata:
|
|
2597
|
+
- Tags: Use specific, searchable tags (e.g., "auth", "payments", "eu-compliance", "validation")
|
|
2598
|
+
- Files: Full paths when mentioned (e.g., "src/auth/validator.ts")
|
|
2599
|
+
- Tradeoffs: What was considered but rejected
|
|
2600
|
+
- Related terms: Alternative names/keywords (e.g., "password" + "credentials" + "auth")
|
|
2601
|
+
|
|
2602
|
+
Format as JSON:
|
|
2603
|
+
{
|
|
2604
|
+
"decisions": [{
|
|
2605
|
+
"decision": "Use bcrypt for password hashing",
|
|
2606
|
+
"context": "Security requirement for user authentication",
|
|
2607
|
+
"reasoning": "Industry standard, resistant to GPU attacks",
|
|
2608
|
+
"files": ["src/auth/hash.ts", "src/models/user.ts"],
|
|
2609
|
+
"tags": ["security", "auth", "passwords", "encryption"],
|
|
2610
|
+
"tradeoffs": ["Considered argon2 but bcrypt has better library support"]
|
|
2611
|
+
}],
|
|
2612
|
+
"facts": [{
|
|
2613
|
+
"fact": "Stripe requires TLS 1.2+ for all API calls",
|
|
2614
|
+
"source": "Stripe API docs",
|
|
2615
|
+
"tags": ["payments", "stripe", "security", "api"],
|
|
2616
|
+
"confidence": 0.95
|
|
2617
|
+
}],
|
|
2618
|
+
"blockers": [{
|
|
2619
|
+
"blocker": "Missing VAT calculation endpoint",
|
|
2620
|
+
"impact": "high",
|
|
2621
|
+
"affectedAreas": ["checkout", "eu-payments"],
|
|
2622
|
+
"tags": ["payments", "eu", "compliance", "vat"]
|
|
2623
|
+
}],
|
|
2624
|
+
"questions": [{
|
|
2625
|
+
"question": "Should we cache user sessions in Redis or memory?",
|
|
2626
|
+
"context": "Performance optimization for auth layer",
|
|
2627
|
+
"tags": ["auth", "performance", "caching", "sessions"]
|
|
2628
|
+
}]
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
Be specific with tags. Use concrete technical terms. Extract ALL file paths mentioned.
|
|
2632
|
+
Empty arrays are fine if nothing to extract.`;
|
|
2633
|
+
var SignalExtractor = class {
|
|
2634
|
+
client = null;
|
|
2635
|
+
model;
|
|
2636
|
+
constructor(model = "claude-3-haiku-20240307") {
|
|
2637
|
+
this.model = model;
|
|
2638
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
2639
|
+
if (apiKey) {
|
|
2640
|
+
this.client = new Anthropic({ apiKey });
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
/**
|
|
2644
|
+
* Extract structured signals from raw content
|
|
2645
|
+
*/
|
|
2646
|
+
async extract(content, sourceType, sourceId) {
|
|
2647
|
+
if (!this.client) {
|
|
2648
|
+
return this.basicExtraction(content, sourceType, sourceId);
|
|
2649
|
+
}
|
|
2607
2650
|
try {
|
|
2608
|
-
const
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
contextParts.push("Recent decisions:\n" + decisions.map(
|
|
2616
|
-
(d) => `- ${d.decision} (${d.when}${d.hash ? `, hash: ${d.hash.slice(0, 8)}` : ""})`
|
|
2617
|
-
).join("\n"));
|
|
2618
|
-
}
|
|
2619
|
-
} catch {
|
|
2620
|
-
}
|
|
2621
|
-
try {
|
|
2622
|
-
const blockers = await storage.queryBlockers({ limit: 5 });
|
|
2623
|
-
if (blockers.length > 0) {
|
|
2624
|
-
contextParts.push("Active blockers:\n" + blockers.map(
|
|
2625
|
-
(b) => `- ${b.blocker} [${b.impact}]`
|
|
2626
|
-
).join("\n"));
|
|
2627
|
-
}
|
|
2628
|
-
} catch {
|
|
2629
|
-
}
|
|
2630
|
-
try {
|
|
2631
|
-
const snap = await graph.getSnapshot();
|
|
2632
|
-
const fileNodes = snap.nodes.filter((n) => n.type === "file");
|
|
2633
|
-
const changeNodes = snap.nodes.filter((n) => n.type === "change");
|
|
2634
|
-
const patternNodes = snap.nodes.filter((n) => n.type === "pattern");
|
|
2635
|
-
const incidentNodes = snap.nodes.filter((n) => n.type === "incident");
|
|
2636
|
-
contextParts.push(`Context graph: ${snap.nodes.length} nodes, ${snap.edges.length} edges (${fileNodes.length} files, ${changeNodes.length} changes, ${patternNodes.length} patterns, ${incidentNodes.length} incidents)`);
|
|
2637
|
-
if (fileNodes.length > 0) {
|
|
2638
|
-
const sorted = [...fileNodes].sort((a, b) => {
|
|
2639
|
-
const riskOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
2640
|
-
const ad = a.data, bd = b.data;
|
|
2641
|
-
return (riskOrder[ad.riskLevel] ?? 4) - (riskOrder[bd.riskLevel] ?? 4);
|
|
2642
|
-
});
|
|
2643
|
-
contextParts.push("Project files (by risk):\n" + sorted.slice(0, 20).map((n) => {
|
|
2644
|
-
const d = n.data;
|
|
2645
|
-
return `- ${d.path} (${d.riskLevel}, ${d.changeCount} changes${d.incidentCount > 0 ? `, ${d.incidentCount} incidents` : ""})`;
|
|
2646
|
-
}).join("\n") + (sorted.length > 20 ? `
|
|
2647
|
-
...and ${sorted.length - 20} more files` : ""));
|
|
2648
|
-
}
|
|
2649
|
-
if (changeNodes.length > 0) {
|
|
2650
|
-
contextParts.push("Recent changes:\n" + changeNodes.slice(0, 5).map((n) => {
|
|
2651
|
-
const d = n.data;
|
|
2652
|
-
return `- ${d.message} (${d.timestamp}, ${d.files.length} files, outcome: ${d.outcome})`;
|
|
2653
|
-
}).join("\n"));
|
|
2654
|
-
}
|
|
2655
|
-
if (patternNodes.length > 0) {
|
|
2656
|
-
contextParts.push("Learned patterns:\n" + patternNodes.slice(0, 5).map((n) => {
|
|
2657
|
-
const d = n.data;
|
|
2658
|
-
return `- ${d.description} (${Math.round(d.confidence * 100)}% confidence${d.isAntiPattern ? ", anti-pattern" : ""})`;
|
|
2659
|
-
}).join("\n"));
|
|
2660
|
-
}
|
|
2661
|
-
} catch {
|
|
2662
|
-
}
|
|
2663
|
-
const contextBlock = contextParts.length > 0 ? contextParts.join("\n\n") : "No context data available yet.";
|
|
2664
|
-
const result = await runAIAnalysis({
|
|
2665
|
-
systemPrompt: `You are Trie, a code guardian assistant embedded in a TUI. Answer concisely based on the project context below. Reference specific decisions, patterns, and files when relevant.
|
|
2651
|
+
const response = await this.client.messages.create({
|
|
2652
|
+
model: this.model,
|
|
2653
|
+
max_tokens: 2048,
|
|
2654
|
+
temperature: 0.3,
|
|
2655
|
+
messages: [{
|
|
2656
|
+
role: "user",
|
|
2657
|
+
content: `${EXTRACTION_PROMPT}
|
|
2666
2658
|
|
|
2667
|
-
|
|
2668
|
-
${
|
|
2669
|
-
|
|
2670
|
-
maxTokens: 1024,
|
|
2671
|
-
temperature: 0.4
|
|
2659
|
+
Content to analyze:
|
|
2660
|
+
${content}`
|
|
2661
|
+
}]
|
|
2672
2662
|
});
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2663
|
+
const firstBlock = response.content[0];
|
|
2664
|
+
const text = firstBlock && firstBlock.type === "text" ? firstBlock.text : "";
|
|
2665
|
+
const extracted = this.parseExtraction(text);
|
|
2666
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2667
|
+
const metadata = {
|
|
2668
|
+
extractedAt: now,
|
|
2669
|
+
sourceType,
|
|
2670
|
+
extractionModel: this.model
|
|
2671
|
+
};
|
|
2672
|
+
if (sourceId !== void 0) {
|
|
2673
|
+
metadata.sourceId = sourceId;
|
|
2677
2674
|
}
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2675
|
+
const signal = {
|
|
2676
|
+
decisions: extracted.decisions.map((d, i) => ({
|
|
2677
|
+
id: `dec-${Date.now()}-${i}`,
|
|
2678
|
+
decision: d.decision || "",
|
|
2679
|
+
context: d.context || "",
|
|
2680
|
+
files: d.files || [],
|
|
2681
|
+
tags: d.tags || [],
|
|
2682
|
+
...d,
|
|
2683
|
+
when: now,
|
|
2684
|
+
status: "active"
|
|
2685
|
+
})),
|
|
2686
|
+
facts: extracted.facts.map((f, i) => ({
|
|
2687
|
+
id: `fact-${Date.now()}-${i}`,
|
|
2688
|
+
fact: f.fact || "",
|
|
2689
|
+
source: f.source || sourceType,
|
|
2690
|
+
tags: f.tags || [],
|
|
2691
|
+
confidence: f.confidence ?? 0.8,
|
|
2692
|
+
...f,
|
|
2693
|
+
when: now
|
|
2694
|
+
})),
|
|
2695
|
+
blockers: extracted.blockers.map((b, i) => ({
|
|
2696
|
+
id: `block-${Date.now()}-${i}`,
|
|
2697
|
+
blocker: b.blocker || "",
|
|
2698
|
+
impact: b.impact || "medium",
|
|
2699
|
+
affectedAreas: b.affectedAreas || [],
|
|
2700
|
+
tags: b.tags || [],
|
|
2701
|
+
...b,
|
|
2702
|
+
when: now
|
|
2703
|
+
})),
|
|
2704
|
+
questions: extracted.questions.map((q, i) => ({
|
|
2705
|
+
id: `q-${Date.now()}-${i}`,
|
|
2706
|
+
question: q.question || "",
|
|
2707
|
+
context: q.context || "",
|
|
2708
|
+
tags: q.tags || [],
|
|
2709
|
+
...q,
|
|
2710
|
+
when: now
|
|
2711
|
+
})),
|
|
2712
|
+
metadata
|
|
2713
|
+
};
|
|
2714
|
+
return signal;
|
|
2715
|
+
} catch (error) {
|
|
2716
|
+
console.error("Extraction failed, using basic extraction:", error);
|
|
2717
|
+
return this.basicExtraction(content, sourceType, sourceId);
|
|
2682
2718
|
}
|
|
2683
|
-
}
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2719
|
+
}
|
|
2720
|
+
/**
|
|
2721
|
+
* Parse extraction from model response
|
|
2722
|
+
*/
|
|
2723
|
+
parseExtraction(text) {
|
|
2724
|
+
try {
|
|
2725
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
2726
|
+
if (jsonMatch) {
|
|
2727
|
+
return JSON.parse(jsonMatch[0]);
|
|
2728
|
+
}
|
|
2729
|
+
} catch (e) {
|
|
2694
2730
|
}
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "Ask Trie anything" })
|
|
2702
|
-
] }),
|
|
2703
|
-
/* @__PURE__ */ jsx12(Text11, { children: " " }),
|
|
2704
|
-
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: " AI is not available." }),
|
|
2705
|
-
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
2706
|
-
" ",
|
|
2707
|
-
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "Press" }),
|
|
2708
|
-
" ",
|
|
2709
|
-
/* @__PURE__ */ jsx12(Text11, { bold: true, children: "s" }),
|
|
2710
|
-
" ",
|
|
2711
|
-
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "to open settings and add your Anthropic API key," })
|
|
2712
|
-
] }),
|
|
2713
|
-
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
2714
|
-
" ",
|
|
2715
|
-
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "or set" }),
|
|
2716
|
-
" ",
|
|
2717
|
-
/* @__PURE__ */ jsx12(Text11, { bold: true, children: "ANTHROPIC_API_KEY" }),
|
|
2718
|
-
" ",
|
|
2719
|
-
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "in your environment." })
|
|
2720
|
-
] })
|
|
2721
|
-
] });
|
|
2731
|
+
return {
|
|
2732
|
+
decisions: [],
|
|
2733
|
+
facts: [],
|
|
2734
|
+
blockers: [],
|
|
2735
|
+
questions: []
|
|
2736
|
+
};
|
|
2722
2737
|
}
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
]
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2738
|
+
/**
|
|
2739
|
+
* Basic extraction without AI (fallback)
|
|
2740
|
+
*/
|
|
2741
|
+
basicExtraction(content, sourceType, sourceId) {
|
|
2742
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2743
|
+
const hasDecision = /\b(decided|decision|chose|picked)\b/i.test(content);
|
|
2744
|
+
const hasBlocker = /\b(blocked|blocker|blocked by|can't|cannot|unable)\b/i.test(content);
|
|
2745
|
+
const hasQuestion = /\?|what|how|why|should we/i.test(content);
|
|
2746
|
+
const decisions = [];
|
|
2747
|
+
const facts = [];
|
|
2748
|
+
const blockers = [];
|
|
2749
|
+
const questions = [];
|
|
2750
|
+
if (hasDecision) {
|
|
2751
|
+
decisions.push({
|
|
2752
|
+
id: `dec-${Date.now()}`,
|
|
2753
|
+
decision: content.substring(0, 200),
|
|
2754
|
+
context: sourceType,
|
|
2755
|
+
when: now,
|
|
2756
|
+
files: [],
|
|
2757
|
+
tags: [sourceType],
|
|
2758
|
+
status: "active"
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
if (hasBlocker) {
|
|
2762
|
+
blockers.push({
|
|
2763
|
+
id: `block-${Date.now()}`,
|
|
2764
|
+
blocker: content.substring(0, 200),
|
|
2765
|
+
impact: "medium",
|
|
2766
|
+
affectedAreas: [],
|
|
2767
|
+
when: now,
|
|
2768
|
+
tags: [sourceType]
|
|
2769
|
+
});
|
|
2770
|
+
}
|
|
2771
|
+
if (hasQuestion) {
|
|
2772
|
+
questions.push({
|
|
2773
|
+
id: `q-${Date.now()}`,
|
|
2774
|
+
question: content.substring(0, 200),
|
|
2775
|
+
context: sourceType,
|
|
2776
|
+
when: now,
|
|
2777
|
+
tags: [sourceType]
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
const metadata = {
|
|
2781
|
+
extractedAt: now,
|
|
2782
|
+
sourceType,
|
|
2783
|
+
extractionModel: "basic"
|
|
2784
|
+
};
|
|
2785
|
+
if (sourceId !== void 0) {
|
|
2786
|
+
metadata.sourceId = sourceId;
|
|
2787
|
+
}
|
|
2788
|
+
return {
|
|
2789
|
+
decisions,
|
|
2790
|
+
facts,
|
|
2791
|
+
blockers,
|
|
2792
|
+
questions,
|
|
2793
|
+
metadata
|
|
2794
|
+
};
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Extract from incident report (trie tell)
|
|
2798
|
+
*/
|
|
2799
|
+
async extractFromIncident(incidentText) {
|
|
2800
|
+
return this.extract(incidentText, "incident");
|
|
2801
|
+
}
|
|
2802
|
+
/**
|
|
2803
|
+
* Extract from commit message and diff
|
|
2804
|
+
*/
|
|
2805
|
+
async extractFromCommit(message, diff, commitId) {
|
|
2806
|
+
const content = diff ? `${message}
|
|
2807
|
+
|
|
2808
|
+
Changes:
|
|
2809
|
+
${diff}` : message;
|
|
2810
|
+
return this.extract(content, "commit", commitId);
|
|
2811
|
+
}
|
|
2812
|
+
/**
|
|
2813
|
+
* Extract from PR description and comments
|
|
2814
|
+
*/
|
|
2815
|
+
async extractFromPR(title, description, comments, prNumber) {
|
|
2816
|
+
const content = `
|
|
2817
|
+
Title: ${title}
|
|
2818
|
+
|
|
2819
|
+
Description:
|
|
2820
|
+
${description}
|
|
2821
|
+
|
|
2822
|
+
Comments:
|
|
2823
|
+
${comments.join("\n\n")}
|
|
2824
|
+
`.trim();
|
|
2825
|
+
return this.extract(content, "pr", prNumber);
|
|
2826
|
+
}
|
|
2827
|
+
};
|
|
2828
|
+
|
|
2829
|
+
// src/extraction/metadata-enricher.ts
|
|
2830
|
+
var MetadataEnricher = class {
|
|
2831
|
+
tagSynonyms;
|
|
2832
|
+
constructor() {
|
|
2833
|
+
this.tagSynonyms = this.initializeTagSynonyms();
|
|
2834
|
+
}
|
|
2835
|
+
/**
|
|
2836
|
+
* Enrich an extracted signal with additional metadata
|
|
2837
|
+
*/
|
|
2838
|
+
async enrichSignal(signal, context) {
|
|
2839
|
+
const gitContext = {};
|
|
2840
|
+
if (context?.gitBranch) gitContext.branch = context.gitBranch;
|
|
2841
|
+
if (context?.gitCommit) gitContext.commit = context.gitCommit;
|
|
2842
|
+
const metadata = {
|
|
2843
|
+
relatedFiles: await this.findRelatedFiles(signal),
|
|
2844
|
+
dependencies: await this.extractDependencies(signal),
|
|
2845
|
+
expandedTags: this.expandTags(signal),
|
|
2846
|
+
codebaseArea: this.inferCodebaseArea(signal),
|
|
2847
|
+
domain: this.inferDomain(signal),
|
|
2848
|
+
relatedDecisions: [],
|
|
2849
|
+
// Will be populated by storage layer
|
|
2850
|
+
extractedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2851
|
+
};
|
|
2852
|
+
if (Object.keys(gitContext).length > 0) {
|
|
2853
|
+
metadata.gitContext = gitContext;
|
|
2854
|
+
}
|
|
2855
|
+
return {
|
|
2856
|
+
signal,
|
|
2857
|
+
metadata
|
|
2858
|
+
};
|
|
2859
|
+
}
|
|
2860
|
+
/**
|
|
2861
|
+
* Expand tags with synonyms and related terms
|
|
2862
|
+
* This is our "semantic" layer without embeddings
|
|
2863
|
+
*/
|
|
2864
|
+
expandTags(signal) {
|
|
2865
|
+
const allTags = /* @__PURE__ */ new Set();
|
|
2866
|
+
for (const decision of signal.decisions) {
|
|
2867
|
+
decision.tags.forEach((tag) => allTags.add(tag.toLowerCase()));
|
|
2868
|
+
}
|
|
2869
|
+
for (const fact of signal.facts) {
|
|
2870
|
+
fact.tags.forEach((tag) => allTags.add(tag.toLowerCase()));
|
|
2871
|
+
}
|
|
2872
|
+
for (const blocker of signal.blockers) {
|
|
2873
|
+
blocker.tags.forEach((tag) => allTags.add(tag.toLowerCase()));
|
|
2874
|
+
}
|
|
2875
|
+
for (const question of signal.questions) {
|
|
2876
|
+
question.tags.forEach((tag) => allTags.add(tag.toLowerCase()));
|
|
2877
|
+
}
|
|
2878
|
+
const expandedTags = new Set(allTags);
|
|
2879
|
+
for (const tag of allTags) {
|
|
2880
|
+
const synonyms = this.tagSynonyms.get(tag) || [];
|
|
2881
|
+
synonyms.forEach((syn) => expandedTags.add(syn));
|
|
2882
|
+
}
|
|
2883
|
+
return Array.from(expandedTags);
|
|
2884
|
+
}
|
|
2885
|
+
/**
|
|
2886
|
+
* Find related files based on signal content
|
|
2887
|
+
*/
|
|
2888
|
+
async findRelatedFiles(signal) {
|
|
2889
|
+
const relatedFiles = /* @__PURE__ */ new Set();
|
|
2890
|
+
for (const decision of signal.decisions) {
|
|
2891
|
+
decision.files.forEach((file) => relatedFiles.add(file));
|
|
2892
|
+
}
|
|
2893
|
+
const inferredFiles = await this.inferFilesFromTags(signal);
|
|
2894
|
+
inferredFiles.forEach((file) => relatedFiles.add(file));
|
|
2895
|
+
return Array.from(relatedFiles);
|
|
2896
|
+
}
|
|
2897
|
+
/**
|
|
2898
|
+
* Extract dependencies from signal
|
|
2899
|
+
*/
|
|
2900
|
+
async extractDependencies(signal) {
|
|
2901
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
2902
|
+
const allText = [
|
|
2903
|
+
...signal.decisions.map((d) => `${d.decision} ${d.context} ${d.reasoning}`),
|
|
2904
|
+
...signal.facts.map((f) => `${f.fact} ${f.source}`)
|
|
2905
|
+
].join(" ");
|
|
2906
|
+
const packagePatterns = [
|
|
2907
|
+
/\b(react|vue|angular|next|express|fastify|stripe|bcrypt|jwt|redis|prisma)\b/gi
|
|
2908
|
+
];
|
|
2909
|
+
for (const pattern of packagePatterns) {
|
|
2910
|
+
const matches = allText.match(pattern) || [];
|
|
2911
|
+
matches.forEach((dep) => dependencies.add(dep.toLowerCase()));
|
|
2912
|
+
}
|
|
2913
|
+
return Array.from(dependencies);
|
|
2914
|
+
}
|
|
2915
|
+
/**
|
|
2916
|
+
* Infer codebase area from file paths and tags
|
|
2917
|
+
*/
|
|
2918
|
+
inferCodebaseArea(signal) {
|
|
2919
|
+
const areas = /* @__PURE__ */ new Set();
|
|
2920
|
+
for (const decision of signal.decisions) {
|
|
2921
|
+
for (const file of decision.files) {
|
|
2922
|
+
const area = this.filePathToArea(file);
|
|
2923
|
+
if (area) areas.add(area);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
const areaKeywords = ["frontend", "backend", "api", "ui", "database", "auth", "payments"];
|
|
2927
|
+
const allTags = this.expandTags(signal);
|
|
2928
|
+
for (const tag of allTags) {
|
|
2929
|
+
if (areaKeywords.includes(tag)) {
|
|
2930
|
+
areas.add(tag);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
return Array.from(areas);
|
|
2934
|
+
}
|
|
2935
|
+
/**
|
|
2936
|
+
* Infer domain from tags and content
|
|
2937
|
+
*/
|
|
2938
|
+
inferDomain(signal) {
|
|
2939
|
+
const domains = /* @__PURE__ */ new Set();
|
|
2940
|
+
const domainKeywords = [
|
|
2941
|
+
"payments",
|
|
2942
|
+
"billing",
|
|
2943
|
+
"compliance",
|
|
2944
|
+
"security",
|
|
2945
|
+
"auth",
|
|
2946
|
+
"analytics",
|
|
2947
|
+
"notifications",
|
|
2948
|
+
"messaging",
|
|
2949
|
+
"search"
|
|
2950
|
+
];
|
|
2951
|
+
const allTags = this.expandTags(signal);
|
|
2952
|
+
for (const tag of allTags) {
|
|
2953
|
+
if (domainKeywords.includes(tag)) {
|
|
2954
|
+
domains.add(tag);
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
return Array.from(domains);
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* Convert file path to codebase area
|
|
2961
|
+
*/
|
|
2962
|
+
filePathToArea(filePath) {
|
|
2963
|
+
const normalized = filePath.toLowerCase();
|
|
2964
|
+
if (normalized.includes("/frontend/") || normalized.includes("/client/") || normalized.includes("/ui/")) {
|
|
2965
|
+
return "frontend";
|
|
2966
|
+
}
|
|
2967
|
+
if (normalized.includes("/backend/") || normalized.includes("/server/") || normalized.includes("/api/")) {
|
|
2968
|
+
return "backend";
|
|
2969
|
+
}
|
|
2970
|
+
if (normalized.includes("/database/") || normalized.includes("/models/") || normalized.includes("/schema/")) {
|
|
2971
|
+
return "database";
|
|
2972
|
+
}
|
|
2973
|
+
if (normalized.includes("/auth/")) {
|
|
2974
|
+
return "auth";
|
|
2975
|
+
}
|
|
2976
|
+
return null;
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Infer related files from tags
|
|
2980
|
+
*/
|
|
2981
|
+
async inferFilesFromTags(_signal) {
|
|
2982
|
+
return [];
|
|
2983
|
+
}
|
|
2984
|
+
/**
|
|
2985
|
+
* Initialize tag synonyms and related terms
|
|
2986
|
+
* This is our "semantic" understanding without embeddings
|
|
2987
|
+
*/
|
|
2988
|
+
initializeTagSynonyms() {
|
|
2989
|
+
return /* @__PURE__ */ new Map([
|
|
2990
|
+
// Auth & Security
|
|
2991
|
+
["auth", ["authentication", "login", "signin", "credentials", "password"]],
|
|
2992
|
+
["password", ["credentials", "auth", "hashing", "bcrypt"]],
|
|
2993
|
+
["security", ["vulnerability", "exploit", "attack", "protection"]],
|
|
2994
|
+
["encryption", ["crypto", "hashing", "encoding", "security"]],
|
|
2995
|
+
// Payments
|
|
2996
|
+
["payments", ["billing", "checkout", "stripe", "pricing"]],
|
|
2997
|
+
["stripe", ["payments", "billing", "api", "checkout"]],
|
|
2998
|
+
["vat", ["tax", "eu", "compliance", "billing"]],
|
|
2999
|
+
// Database & Performance
|
|
3000
|
+
["database", ["db", "sql", "query", "storage", "persistence"]],
|
|
3001
|
+
["cache", ["caching", "redis", "memory", "performance"]],
|
|
3002
|
+
["performance", ["optimization", "speed", "latency", "cache"]],
|
|
3003
|
+
// Frontend
|
|
3004
|
+
["ui", ["frontend", "interface", "component", "view"]],
|
|
3005
|
+
["component", ["ui", "react", "vue", "frontend"]],
|
|
3006
|
+
["validation", ["form", "input", "error", "ui"]],
|
|
3007
|
+
// Backend & API
|
|
3008
|
+
["api", ["endpoint", "route", "backend", "server"]],
|
|
3009
|
+
["endpoint", ["api", "route", "url", "backend"]],
|
|
3010
|
+
["backend", ["server", "api", "service"]],
|
|
3011
|
+
// Compliance & Legal
|
|
3012
|
+
["compliance", ["gdpr", "hipaa", "legal", "regulation"]],
|
|
3013
|
+
["gdpr", ["compliance", "privacy", "eu", "data-protection"]],
|
|
3014
|
+
["privacy", ["gdpr", "compliance", "data-protection", "security"]]
|
|
3015
|
+
]);
|
|
3016
|
+
}
|
|
3017
|
+
};
|
|
3018
|
+
|
|
3019
|
+
// src/extraction/pipeline.ts
|
|
3020
|
+
import { randomBytes } from "crypto";
|
|
3021
|
+
var ExtractionPipeline = class {
|
|
3022
|
+
extractor;
|
|
3023
|
+
enricher;
|
|
3024
|
+
storage;
|
|
3025
|
+
workDir;
|
|
3026
|
+
constructor(options) {
|
|
3027
|
+
this.extractor = new SignalExtractor(options.anthropicApiKey);
|
|
3028
|
+
this.enricher = new MetadataEnricher();
|
|
3029
|
+
this.storage = getStorage(options.workingDirectory);
|
|
3030
|
+
this.workDir = options.workingDirectory;
|
|
3031
|
+
}
|
|
3032
|
+
/**
|
|
3033
|
+
* Process raw content through the entire pipeline
|
|
3034
|
+
*/
|
|
3035
|
+
async process(content, context) {
|
|
3036
|
+
console.log("\u{1F50D} Extracting signals from content...");
|
|
3037
|
+
let extractedSignal = await this.extractor.extract(content, context.sourceType, context.sourceId);
|
|
3038
|
+
extractedSignal = this.addIds(extractedSignal, context);
|
|
3039
|
+
console.log(` \u2713 Extracted ${extractedSignal.decisions.length} decisions, ${extractedSignal.facts.length} facts, ${extractedSignal.blockers.length} blockers, ${extractedSignal.questions.length} questions`);
|
|
3040
|
+
console.log("\u{1F3F7}\uFE0F Enriching with metadata...");
|
|
3041
|
+
const { metadata: enrichedMeta } = await this.enricher.enrichSignal(extractedSignal, {
|
|
3042
|
+
workingDirectory: this.workDir
|
|
3043
|
+
});
|
|
3044
|
+
if (enrichedMeta.expandedTags.length > 0) {
|
|
3045
|
+
console.log(` \u2713 Expanded tags: ${enrichedMeta.expandedTags.slice(0, 5).join(", ")}${enrichedMeta.expandedTags.length > 5 ? "..." : ""}`);
|
|
3046
|
+
}
|
|
3047
|
+
if (enrichedMeta.dependencies.length > 0) {
|
|
3048
|
+
console.log(` \u2713 Dependencies: ${enrichedMeta.dependencies.join(", ")}`);
|
|
3049
|
+
}
|
|
3050
|
+
if (enrichedMeta.codebaseArea.length > 0) {
|
|
3051
|
+
console.log(` \u2713 Codebase areas: ${enrichedMeta.codebaseArea.join(", ")}`);
|
|
3052
|
+
}
|
|
3053
|
+
if (enrichedMeta.domain.length > 0) {
|
|
3054
|
+
console.log(` \u2713 Domains: ${enrichedMeta.domain.join(", ")}`);
|
|
3055
|
+
}
|
|
3056
|
+
console.log("\u{1F4BE} Storing in decision ledger...");
|
|
3057
|
+
await this.storage.storeSignal(extractedSignal, {
|
|
3058
|
+
expandedTags: enrichedMeta.expandedTags,
|
|
3059
|
+
dependencies: enrichedMeta.dependencies,
|
|
3060
|
+
codebaseArea: enrichedMeta.codebaseArea,
|
|
3061
|
+
domain: enrichedMeta.domain
|
|
3062
|
+
});
|
|
3063
|
+
console.log("\u2705 Successfully stored in decision ledger");
|
|
3064
|
+
return extractedSignal;
|
|
3065
|
+
}
|
|
3066
|
+
/**
|
|
3067
|
+
* Add IDs and metadata to extracted signal
|
|
3068
|
+
*/
|
|
3069
|
+
addIds(signal, context) {
|
|
3070
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3071
|
+
const metadata = {
|
|
3072
|
+
extractedAt: now,
|
|
3073
|
+
sourceType: context.sourceType,
|
|
3074
|
+
extractionModel: "claude-haiku"
|
|
3075
|
+
};
|
|
3076
|
+
if (context.sourceId !== void 0) {
|
|
3077
|
+
metadata.sourceId = context.sourceId;
|
|
3078
|
+
}
|
|
3079
|
+
return {
|
|
3080
|
+
decisions: signal.decisions.map((d) => {
|
|
3081
|
+
const decision = {
|
|
3082
|
+
...d,
|
|
3083
|
+
id: d.id || this.generateId(),
|
|
3084
|
+
when: d.when || now,
|
|
3085
|
+
status: d.status || "active"
|
|
3086
|
+
};
|
|
3087
|
+
if (context.who !== void 0) {
|
|
3088
|
+
decision.who = d.who || context.who;
|
|
3089
|
+
}
|
|
3090
|
+
return decision;
|
|
3091
|
+
}),
|
|
3092
|
+
facts: signal.facts.map((f) => ({
|
|
3093
|
+
...f,
|
|
3094
|
+
id: f.id || this.generateId(),
|
|
3095
|
+
when: f.when || now,
|
|
3096
|
+
confidence: f.confidence ?? 0.8
|
|
3097
|
+
})),
|
|
3098
|
+
blockers: signal.blockers.map((b) => ({
|
|
3099
|
+
...b,
|
|
3100
|
+
id: b.id || this.generateId(),
|
|
3101
|
+
when: b.when || now
|
|
3102
|
+
})),
|
|
3103
|
+
questions: signal.questions.map((q) => ({
|
|
3104
|
+
...q,
|
|
3105
|
+
id: q.id || this.generateId(),
|
|
3106
|
+
when: q.when || now
|
|
3107
|
+
})),
|
|
3108
|
+
metadata
|
|
3109
|
+
};
|
|
3110
|
+
}
|
|
3111
|
+
/**
|
|
3112
|
+
* Generate a unique ID
|
|
3113
|
+
*/
|
|
3114
|
+
generateId() {
|
|
3115
|
+
return randomBytes(8).toString("hex");
|
|
3116
|
+
}
|
|
3117
|
+
/**
|
|
3118
|
+
* Initialize storage
|
|
3119
|
+
*/
|
|
3120
|
+
async initialize() {
|
|
3121
|
+
await this.storage.initialize();
|
|
3122
|
+
}
|
|
3123
|
+
/**
|
|
3124
|
+
* Close storage connections
|
|
3125
|
+
*/
|
|
3126
|
+
close() {
|
|
3127
|
+
this.storage.close();
|
|
3128
|
+
}
|
|
3129
|
+
};
|
|
3130
|
+
async function processIncident(incidentDescription, options) {
|
|
3131
|
+
const pipeline = new ExtractionPipeline(options);
|
|
3132
|
+
await pipeline.initialize();
|
|
3133
|
+
try {
|
|
3134
|
+
return await pipeline.process(incidentDescription, {
|
|
3135
|
+
sourceType: "incident",
|
|
3136
|
+
sourceId: `incident-${Date.now()}`
|
|
3137
|
+
});
|
|
3138
|
+
} finally {
|
|
3139
|
+
pipeline.close();
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
// src/tools/tell.ts
|
|
3144
|
+
function escalateRisk(level) {
|
|
3145
|
+
if (level === "low") return "medium";
|
|
3146
|
+
if (level === "medium") return "high";
|
|
3147
|
+
if (level === "high") return "critical";
|
|
3148
|
+
return "critical";
|
|
3149
|
+
}
|
|
3150
|
+
function extractFilePathsFromDescription(description) {
|
|
3151
|
+
const matches = description.match(/[\\w./_-]+\\.(ts|tsx|js|jsx|mjs|cjs)/gi);
|
|
3152
|
+
if (!matches) return [];
|
|
3153
|
+
const unique = /* @__PURE__ */ new Set();
|
|
3154
|
+
matches.forEach((m) => unique.add(m.replace(/^\.\/+/, "")));
|
|
3155
|
+
return Array.from(unique);
|
|
3156
|
+
}
|
|
3157
|
+
var TrieTellTool = class {
|
|
3158
|
+
async execute(input) {
|
|
3159
|
+
try {
|
|
3160
|
+
const description = input.description?.trim();
|
|
3161
|
+
if (!description) {
|
|
3162
|
+
return { content: [{ type: "text", text: "description is required" }] };
|
|
3163
|
+
}
|
|
3164
|
+
const projectPath = input.directory || getWorkingDirectory(void 0, true);
|
|
3165
|
+
const graph = new ContextGraph(projectPath);
|
|
3166
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3167
|
+
const change = (await graph.getRecentChanges(1))[0];
|
|
3168
|
+
const linkedFiles = /* @__PURE__ */ new Set();
|
|
3169
|
+
console.log("\n\u{1F9E0} Processing incident with signal extraction...");
|
|
3170
|
+
let extractedSignal = null;
|
|
3171
|
+
try {
|
|
3172
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
3173
|
+
const options = {
|
|
3174
|
+
workingDirectory: projectPath
|
|
3175
|
+
};
|
|
3176
|
+
if (apiKey) {
|
|
3177
|
+
options.anthropicApiKey = apiKey;
|
|
3178
|
+
}
|
|
3179
|
+
extractedSignal = await processIncident(description, options);
|
|
3180
|
+
} catch (error) {
|
|
3181
|
+
console.warn("\u26A0\uFE0F Signal extraction failed, continuing with basic incident tracking:", error);
|
|
3182
|
+
}
|
|
3183
|
+
const incident = await graph.addNode("incident", {
|
|
3184
|
+
description,
|
|
3185
|
+
severity: "major",
|
|
3186
|
+
affectedUsers: null,
|
|
3187
|
+
duration: null,
|
|
3188
|
+
timestamp: now,
|
|
3189
|
+
resolved: false,
|
|
3190
|
+
resolution: null,
|
|
3191
|
+
fixChangeId: change?.id ?? null,
|
|
3192
|
+
reportedVia: "manual"
|
|
3193
|
+
});
|
|
3194
|
+
if (change) {
|
|
3195
|
+
await graph.addEdge(change.id, incident.id, "leadTo");
|
|
3196
|
+
await graph.addEdge(incident.id, change.id, "causedBy");
|
|
3197
|
+
for (const filePath of change.data.files) {
|
|
3198
|
+
linkedFiles.add(filePath);
|
|
3199
|
+
const fileNode = await graph.getNode("file", path.resolve(projectPath, filePath));
|
|
3200
|
+
if (fileNode) {
|
|
3201
|
+
const data = fileNode.data;
|
|
3202
|
+
await graph.updateNode("file", fileNode.id, {
|
|
3203
|
+
incidentCount: (data.incidentCount ?? 0) + 1,
|
|
3204
|
+
riskLevel: escalateRisk(data.riskLevel)
|
|
3205
|
+
});
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
const mentionedFiles = extractFilePathsFromDescription(description);
|
|
3210
|
+
mentionedFiles.forEach((f) => linkedFiles.add(f));
|
|
3211
|
+
if (extractedSignal) {
|
|
3212
|
+
for (const decision of extractedSignal.decisions) {
|
|
3213
|
+
decision.files.forEach((f) => linkedFiles.add(f));
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
const incidentIndex = new IncidentIndex(graph, projectPath);
|
|
3217
|
+
incidentIndex.addIncidentToTrie(incident, Array.from(linkedFiles));
|
|
3218
|
+
await exportToJson(graph);
|
|
3219
|
+
let responseText = `Incident recorded${change ? ` and linked to change ${change.id}` : ""}.`;
|
|
3220
|
+
if (extractedSignal) {
|
|
3221
|
+
const counts = [
|
|
3222
|
+
extractedSignal.decisions.length > 0 ? `${extractedSignal.decisions.length} decision(s)` : null,
|
|
3223
|
+
extractedSignal.facts.length > 0 ? `${extractedSignal.facts.length} fact(s)` : null,
|
|
3224
|
+
extractedSignal.blockers.length > 0 ? `${extractedSignal.blockers.length} blocker(s)` : null,
|
|
3225
|
+
extractedSignal.questions.length > 0 ? `${extractedSignal.questions.length} question(s)` : null
|
|
3226
|
+
].filter(Boolean).join(", ");
|
|
3227
|
+
if (counts) {
|
|
3228
|
+
responseText += `
|
|
3229
|
+
|
|
3230
|
+
\u{1F4CA} Extracted and stored: ${counts}`;
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
return {
|
|
3234
|
+
content: [{
|
|
3235
|
+
type: "text",
|
|
3236
|
+
text: responseText
|
|
3237
|
+
}]
|
|
3238
|
+
};
|
|
3239
|
+
} catch (error) {
|
|
3240
|
+
const friendly = formatFriendlyError(error);
|
|
3241
|
+
return { content: [{ type: "text", text: friendly.userMessage }] };
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
};
|
|
3245
|
+
|
|
3246
|
+
// src/tools/feedback.ts
|
|
3247
|
+
var TrieFeedbackTool = class {
|
|
3248
|
+
async execute(input) {
|
|
3249
|
+
try {
|
|
3250
|
+
const projectPath = input.directory || getWorkingDirectory(void 0, true);
|
|
3251
|
+
const graph = new ContextGraph(projectPath);
|
|
3252
|
+
const engine = new LearningEngine(projectPath, graph);
|
|
3253
|
+
const files = input.files || (input.target ? [input.target] : []);
|
|
3254
|
+
const manualFeedback = {
|
|
3255
|
+
helpful: input.helpful,
|
|
3256
|
+
files,
|
|
3257
|
+
...input.note !== void 0 ? { note: input.note } : {}
|
|
3258
|
+
};
|
|
3259
|
+
await engine.learn({ manualFeedback });
|
|
3260
|
+
return {
|
|
3261
|
+
content: [{
|
|
3262
|
+
type: "text",
|
|
3263
|
+
text: input.helpful ? "\u{1F44D} Thanks \u2014 I will prioritize more responses like this." : "\u{1F44E} Understood \u2014 I will adjust future guidance."
|
|
3264
|
+
}]
|
|
3265
|
+
};
|
|
3266
|
+
} catch (error) {
|
|
3267
|
+
const friendly = formatFriendlyError(error);
|
|
3268
|
+
return { content: [{ type: "text", text: friendly.userMessage }] };
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
};
|
|
3272
|
+
|
|
3273
|
+
// src/tools/check.ts
|
|
3274
|
+
var TrieCheckTool = class {
|
|
3275
|
+
async execute(input = {}) {
|
|
3276
|
+
try {
|
|
3277
|
+
const workDir = input.directory || getWorkingDirectory(void 0, true);
|
|
3278
|
+
let files = input.files;
|
|
3279
|
+
if (!files || files.length === 0) {
|
|
3280
|
+
const perception = await perceiveCurrentChanges(workDir);
|
|
3281
|
+
files = perception.diffSummary.files.map((f) => f.filePath);
|
|
3282
|
+
}
|
|
3283
|
+
if (!files || files.length === 0) {
|
|
3284
|
+
return {
|
|
3285
|
+
content: [{
|
|
3286
|
+
type: "text",
|
|
3287
|
+
text: "No changes detected. Provide files or make a change."
|
|
3288
|
+
}]
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
const mode = input.mode ?? "full";
|
|
3292
|
+
const runAgents = mode === "full";
|
|
3293
|
+
const reasoning = await reasonAboutChangesHumanReadable(workDir, files, {
|
|
3294
|
+
runAgents,
|
|
3295
|
+
scanContext: { config: { timeoutMs: mode === "quick" ? 15e3 : 6e4 } }
|
|
3296
|
+
});
|
|
3297
|
+
const summary = [
|
|
3298
|
+
`Risk: ${reasoning.original.riskLevel.toUpperCase()} (${reasoning.original.shouldBlock ? "block" : "allow"})`,
|
|
3299
|
+
`Explanation: ${reasoning.original.explanation}`,
|
|
3300
|
+
`Recommendation: ${reasoning.original.recommendation}`,
|
|
3301
|
+
`Plain summary: ${reasoning.summary}`,
|
|
3302
|
+
`What I found: ${reasoning.whatIFound}`,
|
|
3303
|
+
`How bad: ${reasoning.howBad}`,
|
|
3304
|
+
`What to do: ${reasoning.whatToDo}`
|
|
3305
|
+
].join("\n");
|
|
3306
|
+
return {
|
|
3307
|
+
content: [{
|
|
3308
|
+
type: "text",
|
|
3309
|
+
text: summary
|
|
3310
|
+
}]
|
|
3311
|
+
};
|
|
3312
|
+
} catch (error) {
|
|
3313
|
+
const friendly = formatFriendlyError(error);
|
|
3314
|
+
return { content: [{ type: "text", text: friendly.userMessage }] };
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
};
|
|
3318
|
+
|
|
3319
|
+
// src/tools/explain.ts
|
|
3320
|
+
import { readFile } from "fs/promises";
|
|
3321
|
+
import { existsSync } from "fs";
|
|
3322
|
+
import { extname, relative, resolve, isAbsolute } from "path";
|
|
3323
|
+
|
|
3324
|
+
// src/ai/prompts.ts
|
|
3325
|
+
var AGENT_PROMPTS = {
|
|
3326
|
+
security: {
|
|
3327
|
+
system: `You are a senior security engineer performing a security audit.
|
|
3328
|
+
Analyze the code for vulnerabilities with the mindset of a penetration tester.
|
|
3329
|
+
|
|
3330
|
+
Focus on:
|
|
3331
|
+
- OWASP Top 10 vulnerabilities (Injection, Broken Auth, XSS, etc.)
|
|
3332
|
+
- Authentication and authorization flaws
|
|
3333
|
+
- Cryptographic weaknesses
|
|
3334
|
+
- Secrets and credential exposure
|
|
3335
|
+
- Input validation gaps
|
|
3336
|
+
- Session management issues
|
|
3337
|
+
- API security (rate limiting, authentication)
|
|
3338
|
+
|
|
3339
|
+
Reference the latest security best practices and CVEs when relevant.`,
|
|
3340
|
+
analysis: `## Security Audit Request
|
|
3341
|
+
|
|
3342
|
+
Analyze this code for security vulnerabilities:
|
|
3343
|
+
|
|
3344
|
+
\`\`\`{{language}}
|
|
3345
|
+
{{code}}
|
|
3346
|
+
\`\`\`
|
|
3347
|
+
|
|
3348
|
+
**File:** {{filePath}}
|
|
3349
|
+
**Context:** {{context}}
|
|
3350
|
+
|
|
3351
|
+
For each vulnerability found:
|
|
3352
|
+
1. Severity (Critical/Serious/Moderate/Low)
|
|
3353
|
+
2. Vulnerability type (e.g., CWE-89 SQL Injection)
|
|
3354
|
+
3. Exact location (line number)
|
|
3355
|
+
4. Attack vector explanation
|
|
3356
|
+
5. Proof of concept (how it could be exploited)
|
|
3357
|
+
6. Remediation with code example
|
|
3358
|
+
|
|
3359
|
+
If you need to check current CVE databases or security advisories, say so.`,
|
|
3360
|
+
fix: `Fix this security vulnerability:
|
|
3361
|
+
|
|
3362
|
+
**Issue:** {{issue}}
|
|
3363
|
+
**File:** {{filePath}}
|
|
3364
|
+
**Line:** {{line}}
|
|
3365
|
+
**Current Code:**
|
|
3366
|
+
\`\`\`{{language}}
|
|
3367
|
+
{{code}}
|
|
3368
|
+
\`\`\`
|
|
3369
|
+
|
|
3370
|
+
Provide:
|
|
3371
|
+
1. The exact code fix (ready to apply)
|
|
3372
|
+
2. Explanation of why this fix works
|
|
3373
|
+
3. Any additional hardening recommendations`
|
|
3374
|
+
},
|
|
3375
|
+
legal: {
|
|
3376
|
+
system: `You are a tech-focused legal compliance analyst.
|
|
3377
|
+
Review code for legal and regulatory compliance issues.
|
|
3378
|
+
|
|
3379
|
+
Focus areas:
|
|
3380
|
+
- Data protection laws (GDPR, CCPA, etc.)
|
|
3381
|
+
- Terms of service enforcement
|
|
3382
|
+
- Cookie/tracking consent (ePrivacy)
|
|
3383
|
+
- Accessibility requirements (ADA, WCAG)
|
|
3384
|
+
- Export controls and sanctions
|
|
3385
|
+
- Licensing compliance`,
|
|
3386
|
+
analysis: `## Legal Compliance Review
|
|
3387
|
+
|
|
3388
|
+
Review this code for legal/regulatory compliance:
|
|
3389
|
+
|
|
3390
|
+
\`\`\`{{language}}
|
|
3391
|
+
{{code}}
|
|
3392
|
+
\`\`\`
|
|
3393
|
+
|
|
3394
|
+
**File:** {{filePath}}
|
|
3395
|
+
**Jurisdiction Context:** {{jurisdiction}}
|
|
3396
|
+
|
|
3397
|
+
Identify:
|
|
3398
|
+
1. Legal requirement at risk
|
|
3399
|
+
2. Specific regulation/law reference
|
|
3400
|
+
3. Compliance gap description
|
|
3401
|
+
4. Risk assessment (litigation, fines, etc.)
|
|
3402
|
+
5. Remediation recommendations
|
|
3403
|
+
6. Required documentation/policies`
|
|
3404
|
+
},
|
|
3405
|
+
"design-engineer": {
|
|
3406
|
+
system: `You are an elite design engineer \u2014 the kind who builds award-winning interfaces featured on Awwwards and Codrops.
|
|
3407
|
+
|
|
3408
|
+
You think in design systems, breathe motion design, and obsess over the details that make interfaces feel magical.
|
|
3409
|
+
|
|
3410
|
+
Your expertise:
|
|
3411
|
+
- **Design Systems**: Spacing scales, type scales, color tokens, radius tokens, shadow tokens
|
|
3412
|
+
- **Motion Design**: Micro-interactions, page transitions, scroll-triggered animations, FLIP technique
|
|
3413
|
+
- **Creative CSS**: Gradients, blend modes, clip-paths, masks, backdrop-filter, mix-blend-mode
|
|
3414
|
+
- **Modern CSS**: Container queries, :has(), subgrid, anchor positioning, cascade layers, @scope
|
|
3415
|
+
- **Fluid Design**: clamp(), min(), max(), fluid typography, intrinsic sizing
|
|
3416
|
+
- **Performance**: GPU-accelerated animations, will-change strategy, avoiding layout thrashing
|
|
3417
|
+
- **Visual Polish**: Layered shadows, subtle gradients, glass effects, smooth easing curves
|
|
3418
|
+
|
|
3419
|
+
You review code with the eye of someone who's shipped Stripe-level interfaces.
|
|
3420
|
+
Small details matter: the easing curve, the stagger timing, the shadow layering.`,
|
|
3421
|
+
analysis: `## Design Engineering Review
|
|
3422
|
+
|
|
3423
|
+
Analyze this frontend code for Awwwards-level craft:
|
|
3424
|
+
|
|
3425
|
+
\`\`\`{{language}}
|
|
3426
|
+
{{code}}
|
|
3427
|
+
\`\`\`
|
|
3428
|
+
|
|
3429
|
+
**File:** {{filePath}}
|
|
3430
|
+
|
|
3431
|
+
Review for:
|
|
3432
|
+
|
|
3433
|
+
### 1. Design System Consistency
|
|
3434
|
+
- Are spacing values on a scale (4, 8, 12, 16, 24, 32...)?
|
|
3435
|
+
- Are colors defined as tokens?
|
|
3436
|
+
- Is typography systematic?
|
|
3437
|
+
- Are radii consistent?
|
|
3438
|
+
- Is z-index controlled?
|
|
3439
|
+
|
|
3440
|
+
### 2. Motion Design
|
|
3441
|
+
- Are transitions using custom easing (cubic-bezier)?
|
|
3442
|
+
- Are durations appropriate (150-300ms for micro, 300-500ms for page)?
|
|
3443
|
+
- Are list items staggered?
|
|
3444
|
+
- Is there reduced-motion support?
|
|
3445
|
+
- Are entrance animations choreographed?
|
|
3446
|
+
|
|
3447
|
+
### 3. Visual Craft
|
|
3448
|
+
- Are shadows layered for depth?
|
|
3449
|
+
- Are gradients subtle and purposeful?
|
|
3450
|
+
- Is there backdrop-blur on overlays?
|
|
3451
|
+
- Are hover states polished?
|
|
3452
|
+
- Is there visual hierarchy?
|
|
3453
|
+
|
|
3454
|
+
### 4. Modern CSS Opportunities
|
|
3455
|
+
- Could container queries improve component isolation?
|
|
3456
|
+
- Could clamp() create fluid spacing?
|
|
3457
|
+
- Could :has() simplify parent styling?
|
|
3458
|
+
- Could aspect-ratio replace padding hacks?
|
|
3459
|
+
|
|
3460
|
+
### 5. Performance
|
|
3461
|
+
- Are expensive properties (width, height, top, left) being animated?
|
|
3462
|
+
- Is will-change used appropriately (not statically)?
|
|
3463
|
+
- Are large blurs avoided in animations?
|
|
3464
|
+
|
|
3465
|
+
For each issue, provide:
|
|
3466
|
+
- What's wrong (with specific line if applicable)
|
|
3467
|
+
- Why it matters for premium feel
|
|
3468
|
+
- Exact code to fix it
|
|
3469
|
+
- Before/after comparison`
|
|
3470
|
+
},
|
|
3471
|
+
accessibility: {
|
|
3472
|
+
system: `You are an accessibility expert and WCAG 2.1 specialist.
|
|
3473
|
+
Audit code for accessibility compliance and inclusive design.
|
|
3474
|
+
|
|
3475
|
+
Standards to enforce:
|
|
3476
|
+
- WCAG 2.1 Level AA (minimum)
|
|
3477
|
+
- WCAG 2.1 Level AAA (recommended)
|
|
3478
|
+
- Section 508
|
|
3479
|
+
- EN 301 549
|
|
3480
|
+
|
|
3481
|
+
Check for:
|
|
3482
|
+
- Missing ARIA labels
|
|
3483
|
+
- Color contrast issues
|
|
3484
|
+
- Keyboard navigation
|
|
3485
|
+
- Screen reader compatibility
|
|
3486
|
+
- Focus management
|
|
3487
|
+
- Form accessibility
|
|
3488
|
+
- Media alternatives`,
|
|
3489
|
+
analysis: `## Accessibility Audit (WCAG 2.1)
|
|
3490
|
+
|
|
3491
|
+
Audit this UI code for accessibility:
|
|
3492
|
+
|
|
3493
|
+
\`\`\`{{language}}
|
|
3494
|
+
{{code}}
|
|
3495
|
+
\`\`\`
|
|
3496
|
+
|
|
3497
|
+
**File:** {{filePath}}
|
|
3498
|
+
**Component Type:** {{componentType}}
|
|
3499
|
+
|
|
3500
|
+
For each issue:
|
|
3501
|
+
1. WCAG Success Criterion violated (e.g., 1.4.3 Contrast)
|
|
3502
|
+
2. Level (A, AA, AAA)
|
|
3503
|
+
3. Impact on users (which disabilities affected)
|
|
3504
|
+
4. Fix with code example
|
|
3505
|
+
5. Testing recommendation`
|
|
3506
|
+
},
|
|
3507
|
+
architecture: {
|
|
3508
|
+
system: `You are a principal software architect reviewing code quality.
|
|
3509
|
+
Analyze for architectural issues, design patterns, and scalability concerns.
|
|
3510
|
+
|
|
3511
|
+
Evaluate:
|
|
3512
|
+
- SOLID principles adherence
|
|
3513
|
+
- Design pattern usage (and misuse)
|
|
3514
|
+
- Code coupling and cohesion
|
|
3515
|
+
- N+1 queries and performance anti-patterns
|
|
3516
|
+
- Scalability bottlenecks
|
|
3517
|
+
- Error handling strategy
|
|
3518
|
+
- API design quality
|
|
3519
|
+
- Database schema issues`,
|
|
3520
|
+
analysis: `## Architecture Review
|
|
3521
|
+
|
|
3522
|
+
Review this code for architectural issues:
|
|
3523
|
+
|
|
3524
|
+
\`\`\`{{language}}
|
|
3525
|
+
{{code}}
|
|
3526
|
+
\`\`\`
|
|
3527
|
+
|
|
3528
|
+
**File:** {{filePath}}
|
|
3529
|
+
**Project Context:** {{projectContext}}
|
|
3530
|
+
|
|
3531
|
+
Analyze:
|
|
3532
|
+
1. SOLID principle violations
|
|
3533
|
+
2. Design pattern opportunities/issues
|
|
3534
|
+
3. Coupling/cohesion assessment
|
|
3535
|
+
4. Performance concerns (N+1, etc.)
|
|
3536
|
+
5. Scalability analysis
|
|
3537
|
+
6. Refactoring recommendations with examples`
|
|
3538
|
+
},
|
|
3539
|
+
bugs: {
|
|
3540
|
+
system: `You are a senior developer with expertise in finding subtle bugs.
|
|
3541
|
+
Hunt for bugs with the mindset of QA trying to break the code.
|
|
3542
|
+
|
|
3543
|
+
Look for:
|
|
3544
|
+
- Null/undefined reference errors
|
|
3545
|
+
- Race conditions and async bugs
|
|
3546
|
+
- Off-by-one errors
|
|
3547
|
+
- Resource leaks
|
|
3548
|
+
- State management bugs
|
|
3549
|
+
- Edge cases and boundary conditions
|
|
3550
|
+
- Type coercion issues
|
|
3551
|
+
- Memory leaks`,
|
|
3552
|
+
analysis: `## Bug Hunt Analysis
|
|
3553
|
+
|
|
3554
|
+
Find bugs and potential runtime errors:
|
|
3555
|
+
|
|
3556
|
+
\`\`\`{{language}}
|
|
3557
|
+
{{code}}
|
|
3558
|
+
\`\`\`
|
|
3559
|
+
|
|
3560
|
+
**File:** {{filePath}}
|
|
3561
|
+
**Runtime Context:** {{runtimeContext}}
|
|
3562
|
+
|
|
3563
|
+
For each bug:
|
|
3564
|
+
1. Bug type and category
|
|
3565
|
+
2. Trigger conditions (when it would crash)
|
|
3566
|
+
3. Reproduction steps
|
|
3567
|
+
4. Impact assessment
|
|
3568
|
+
5. Fix with code example
|
|
3569
|
+
6. Test case to prevent regression`
|
|
3570
|
+
},
|
|
3571
|
+
ux: {
|
|
3572
|
+
system: `You are a UX researcher simulating different user personas.
|
|
3573
|
+
Test code from multiple user perspectives to find usability issues.
|
|
3574
|
+
|
|
3575
|
+
Personas to simulate:
|
|
3576
|
+
1. Happy Path User - Normal expected usage
|
|
3577
|
+
2. Security Tester - Trying to break/exploit things
|
|
3578
|
+
3. Confused User - First-time, doesn't read instructions
|
|
3579
|
+
4. Impatient User - Clicks rapidly, skips loading states
|
|
3580
|
+
5. Edge Case User - Uses maximum values, special characters
|
|
3581
|
+
6. Accessibility User - Screen reader, keyboard only
|
|
3582
|
+
7. Mobile User - Touch interface, slow connection`,
|
|
3583
|
+
analysis: `## User Experience Testing
|
|
3584
|
+
|
|
3585
|
+
Test this code from multiple user perspectives:
|
|
3586
|
+
|
|
3587
|
+
\`\`\`{{language}}
|
|
3588
|
+
{{code}}
|
|
3589
|
+
\`\`\`
|
|
3590
|
+
|
|
3591
|
+
**File:** {{filePath}}
|
|
3592
|
+
**UI Type:** {{uiType}}
|
|
3593
|
+
|
|
3594
|
+
For each persona, identify:
|
|
3595
|
+
1. User action they would take
|
|
3596
|
+
2. Expected behavior vs actual behavior
|
|
3597
|
+
3. Friction points or confusion
|
|
3598
|
+
4. Error scenario and how it's handled
|
|
3599
|
+
5. Improvement recommendation`
|
|
3600
|
+
},
|
|
3601
|
+
types: {
|
|
3602
|
+
system: `You are a TypeScript expert focused on type safety.
|
|
3603
|
+
Analyze code for type issues, missing types, and type system best practices.
|
|
3604
|
+
|
|
3605
|
+
Check for:
|
|
3606
|
+
- Missing type annotations
|
|
3607
|
+
- Implicit any types
|
|
3608
|
+
- Unsafe type assertions
|
|
3609
|
+
- Null/undefined handling
|
|
3610
|
+
- Generic type usage
|
|
3611
|
+
- Type narrowing opportunities
|
|
3612
|
+
- Strict mode violations`,
|
|
3613
|
+
analysis: `## Type Safety Analysis
|
|
3614
|
+
|
|
3615
|
+
Analyze this code for type issues:
|
|
3616
|
+
|
|
3617
|
+
\`\`\`{{language}}
|
|
3618
|
+
{{code}}
|
|
3619
|
+
\`\`\`
|
|
3620
|
+
|
|
3621
|
+
**File:** {{filePath}}
|
|
3622
|
+
**TypeScript Config:** {{tsConfig}}
|
|
3623
|
+
|
|
3624
|
+
Identify:
|
|
3625
|
+
1. Type safety issues
|
|
3626
|
+
2. Missing type annotations
|
|
3627
|
+
3. Unsafe operations
|
|
3628
|
+
4. Improvement recommendations with types`
|
|
3629
|
+
},
|
|
3630
|
+
devops: {
|
|
3631
|
+
system: `You are a DevOps/SRE engineer reviewing code for operational concerns.
|
|
3632
|
+
Focus on production readiness and operational excellence.
|
|
3633
|
+
|
|
3634
|
+
Check for:
|
|
3635
|
+
- Environment variable handling
|
|
3636
|
+
- Configuration management
|
|
3637
|
+
- Logging and monitoring
|
|
3638
|
+
- Error handling and recovery
|
|
3639
|
+
- Health checks
|
|
3640
|
+
- Graceful shutdown
|
|
3641
|
+
- Resource cleanup
|
|
3642
|
+
- Secrets management
|
|
3643
|
+
- Docker/K8s patterns`,
|
|
3644
|
+
analysis: `## DevOps Readiness Review
|
|
3645
|
+
|
|
3646
|
+
Review this code for operational concerns:
|
|
3647
|
+
|
|
3648
|
+
\`\`\`{{language}}
|
|
3649
|
+
{{code}}
|
|
3650
|
+
\`\`\`
|
|
3651
|
+
|
|
3652
|
+
**File:** {{filePath}}
|
|
3653
|
+
**Deployment Context:** {{deploymentContext}}
|
|
3654
|
+
|
|
3655
|
+
Analyze:
|
|
3656
|
+
1. Environment/config issues
|
|
3657
|
+
2. Logging adequacy
|
|
3658
|
+
3. Error handling quality
|
|
3659
|
+
4. Health/readiness concerns
|
|
3660
|
+
5. Resource management
|
|
3661
|
+
6. Production hardening recommendations`
|
|
3662
|
+
},
|
|
3663
|
+
explain: {
|
|
3664
|
+
system: `You are a patient senior developer explaining code to a colleague.
|
|
3665
|
+
Break down complex code into understandable explanations.`,
|
|
3666
|
+
code: `## Code Explanation Request
|
|
3667
|
+
|
|
3668
|
+
Explain this code in plain language:
|
|
3669
|
+
|
|
3670
|
+
\`\`\`{{language}}
|
|
3671
|
+
{{code}}
|
|
3672
|
+
\`\`\`
|
|
3673
|
+
|
|
3674
|
+
**File:** {{filePath}}
|
|
3675
|
+
|
|
3676
|
+
Provide:
|
|
3677
|
+
1. High-level purpose (what does this do?)
|
|
3678
|
+
2. Step-by-step breakdown
|
|
3679
|
+
3. Key concepts used
|
|
3680
|
+
4. Dependencies and side effects
|
|
3681
|
+
5. Potential gotchas or tricky parts`,
|
|
3682
|
+
issue: `## Issue Explanation
|
|
3683
|
+
|
|
3684
|
+
Explain this issue:
|
|
3685
|
+
|
|
3686
|
+
**Issue:** {{issue}}
|
|
3687
|
+
**Severity:** {{severity}}
|
|
3688
|
+
**File:** {{filePath}}
|
|
3689
|
+
**Line:** {{line}}
|
|
3690
|
+
|
|
3691
|
+
Explain:
|
|
3692
|
+
1. What the problem is (in plain language)
|
|
3693
|
+
2. Why it matters
|
|
3694
|
+
3. How it could cause problems
|
|
3695
|
+
4. How to fix it`,
|
|
3696
|
+
risk: `## Risk Assessment
|
|
3697
|
+
|
|
3698
|
+
Assess the risk of this code change:
|
|
3699
|
+
|
|
3700
|
+
**Files Changed:** {{files}}
|
|
3701
|
+
**Change Summary:** {{summary}}
|
|
3702
|
+
|
|
3703
|
+
Analyze:
|
|
3704
|
+
1. What could break?
|
|
3705
|
+
2. Impact on users
|
|
3706
|
+
3. Impact on other systems
|
|
3707
|
+
4. Rollback complexity
|
|
3708
|
+
5. Testing recommendations`
|
|
3709
|
+
},
|
|
3710
|
+
test: {
|
|
3711
|
+
system: `You are a test engineer creating comprehensive test suites.
|
|
3712
|
+
Write thorough tests that catch bugs before production.`,
|
|
3713
|
+
generate: `## Test Generation Request
|
|
3714
|
+
|
|
3715
|
+
Generate tests for this code:
|
|
3716
|
+
|
|
3717
|
+
\`\`\`{{language}}
|
|
3718
|
+
{{code}}
|
|
3719
|
+
\`\`\`
|
|
3720
|
+
|
|
3721
|
+
**File:** {{filePath}}
|
|
3722
|
+
**Testing Framework:** {{framework}}
|
|
3723
|
+
|
|
3724
|
+
Create:
|
|
3725
|
+
1. Unit tests for each function/method
|
|
3726
|
+
2. Edge case tests
|
|
3727
|
+
3. Error handling tests
|
|
3728
|
+
4. Integration test suggestions
|
|
3729
|
+
5. Mock requirements
|
|
3730
|
+
|
|
3731
|
+
Output complete, runnable test code.`,
|
|
3732
|
+
coverage: `## Coverage Analysis
|
|
3733
|
+
|
|
3734
|
+
Analyze test coverage for:
|
|
3735
|
+
|
|
3736
|
+
**File:** {{filePath}}
|
|
3737
|
+
**Current Tests:** {{testFile}}
|
|
3738
|
+
|
|
3739
|
+
Identify:
|
|
3740
|
+
1. Untested code paths
|
|
3741
|
+
2. Missing edge cases
|
|
3742
|
+
3. Critical paths without tests
|
|
3743
|
+
4. Test improvement recommendations`
|
|
3744
|
+
},
|
|
3745
|
+
fix: {
|
|
3746
|
+
system: `You are an expert developer applying code fixes.
|
|
3747
|
+
Make precise, minimal changes that fix issues without breaking other functionality.`,
|
|
3748
|
+
apply: `## Fix Application Request
|
|
3749
|
+
|
|
3750
|
+
Apply this fix to the code:
|
|
3751
|
+
|
|
3752
|
+
**Issue:** {{issue}}
|
|
3753
|
+
**Fix Description:** {{fix}}
|
|
3754
|
+
**Current Code:**
|
|
3755
|
+
\`\`\`{{language}}
|
|
3756
|
+
{{code}}
|
|
3757
|
+
\`\`\`
|
|
3758
|
+
|
|
3759
|
+
**File:** {{filePath}}
|
|
3760
|
+
**Line:** {{line}}
|
|
3761
|
+
|
|
3762
|
+
Provide:
|
|
3763
|
+
1. The exact fixed code (complete, ready to apply)
|
|
3764
|
+
2. Brief explanation of the change
|
|
3765
|
+
3. Any related changes needed elsewhere
|
|
3766
|
+
4. Test to verify the fix works`
|
|
3767
|
+
},
|
|
3768
|
+
pr_review: {
|
|
3769
|
+
system: `You are an expert code reviewer performing detailed, interactive PR reviews.
|
|
3770
|
+
Your goal: Make reviewing a large PR a delight, not a chore. The user learns about the change while you shepherd them through \u2014 maintaining momentum, explaining each piece, and making what could be an overwhelming task feel painless and even enjoyable.
|
|
3771
|
+
|
|
3772
|
+
You drive; they cross-examine.
|
|
3773
|
+
|
|
3774
|
+
## Critical Review Mindset
|
|
3775
|
+
|
|
3776
|
+
Don't just explain \u2014 actively look for problems:
|
|
3777
|
+
|
|
3778
|
+
### State & Lifecycle
|
|
3779
|
+
- Cleanup symmetry: If state is set, is it reset? Check cleanup paths, disconnect handlers.
|
|
3780
|
+
- Lifecycle consistency: Does state survive scenarios it shouldn't?
|
|
3781
|
+
- Guard completeness: Missing "already active" checks, re-entrancy protection?
|
|
3782
|
+
|
|
3783
|
+
### Edge Cases & Races
|
|
3784
|
+
- Concurrent calls: What if called twice rapidly? Orphaned promises?
|
|
3785
|
+
- Ordering assumptions: Does code assume events arrive in order?
|
|
3786
|
+
- Partial failures: If step 3 of 5 fails, is state left consistent?
|
|
3787
|
+
|
|
3788
|
+
### Missing Pieces
|
|
3789
|
+
- What's NOT in the diff that should be? (cleanup handlers, tests, related state)
|
|
3790
|
+
- Defensive gaps: Missing timeouts, size limits, null checks?
|
|
3791
|
+
|
|
3792
|
+
### Design Questions
|
|
3793
|
+
- Is this the right approach? Is there a simpler or more robust design?
|
|
3794
|
+
- Hidden assumptions: What does this assume about its environment?
|
|
3795
|
+
|
|
3796
|
+
Be critical, not just descriptive. Your job is to find problems, not just narrate.`,
|
|
3797
|
+
analysis: `## Interactive PR Review
|
|
3798
|
+
|
|
3799
|
+
I'll walk you through this PR file by file, explaining each change and pausing for your questions.
|
|
3800
|
+
|
|
3801
|
+
**PR:** {{prTitle}}
|
|
3802
|
+
**Author:** {{prAuthor}}
|
|
3803
|
+
**Scope:** {{totalFiles}} files, +{{additions}}/-{{deletions}} lines
|
|
3804
|
+
|
|
3805
|
+
### File Order (sequenced for understanding)
|
|
3806
|
+
|
|
3807
|
+
{{fileOrder}}
|
|
3808
|
+
|
|
3809
|
+
---
|
|
3810
|
+
|
|
3811
|
+
## Review Mode
|
|
3812
|
+
|
|
3813
|
+
{{reviewMode}}
|
|
3814
|
+
|
|
3815
|
+
---
|
|
3816
|
+
|
|
3817
|
+
For each file, I will:
|
|
3818
|
+
1. **Show the change** \u2014 Display the diff for each logical chunk
|
|
3819
|
+
2. **Explain what changed** \u2014 What it does and why it matters
|
|
3820
|
+
3. **Walk through examples** \u2014 Concrete scenarios for non-obvious logic
|
|
3821
|
+
4. **Call out nuances** \u2014 Alternatives, edge cases, subtle points
|
|
3822
|
+
5. **Summarize** \u2014 Core change + correctness assessment
|
|
3823
|
+
6. **Pause** \u2014 Wait for your questions before proceeding
|
|
3824
|
+
|
|
3825
|
+
**Ready for File 1?** (yes / skip to [file] / reorder / done)`,
|
|
3826
|
+
file: `## File Review: {{filePath}}
|
|
3827
|
+
|
|
3828
|
+
### The Change
|
|
3829
|
+
|
|
3830
|
+
\`\`\`{{language}}
|
|
3831
|
+
{{diff}}
|
|
3832
|
+
\`\`\`
|
|
3833
|
+
|
|
3834
|
+
**What Changed:** {{summary}}
|
|
3835
|
+
|
|
3836
|
+
**Why This Matters:** {{impact}}
|
|
3837
|
+
|
|
3838
|
+
{{#if hasExampleScenario}}
|
|
3839
|
+
### Example Scenario
|
|
3840
|
+
|
|
3841
|
+
{{exampleScenario}}
|
|
3842
|
+
{{/if}}
|
|
3843
|
+
|
|
3844
|
+
{{#if nuances}}
|
|
3845
|
+
### Nuances to Note
|
|
3846
|
+
|
|
3847
|
+
{{nuances}}
|
|
3848
|
+
{{/if}}
|
|
3849
|
+
|
|
3850
|
+
{{#if potentialIssue}}
|
|
3851
|
+
### Potential Issue
|
|
3852
|
+
|
|
3853
|
+
**Issue:** {{issueDescription}}
|
|
3854
|
+
**Scenario:** {{issueScenario}}
|
|
3855
|
+
**Suggested fix:** {{suggestedFix}}
|
|
3856
|
+
{{/if}}
|
|
3857
|
+
|
|
3858
|
+
---
|
|
3859
|
+
|
|
3860
|
+
### Summary for \`{{fileName}}\`
|
|
3861
|
+
|
|
3862
|
+
| Aspect | Assessment |
|
|
3863
|
+
|--------|------------|
|
|
3864
|
+
| Core change | {{coreChange}} |
|
|
3865
|
+
| Correctness | {{correctnessAssessment}} |
|
|
3866
|
+
|
|
3867
|
+
**Ready for the next file?** (yes / questions? / done)`,
|
|
3868
|
+
comment: `**Issue:** {{issueDescription}}
|
|
3869
|
+
**Draft comment:** {{draftComment}}
|
|
3870
|
+
|
|
3871
|
+
Post this comment? (yes / modify / skip)`,
|
|
3872
|
+
final: `## Review Complete
|
|
3873
|
+
|
|
3874
|
+
| File | Key Change | Status |
|
|
3875
|
+
|------|------------|--------|
|
|
3876
|
+
{{fileSummaries}}
|
|
3877
|
+
|
|
3878
|
+
**Overall:** {{overallAssessment}}
|
|
3879
|
+
|
|
3880
|
+
{{#if comments}}
|
|
3881
|
+
### Comments Posted
|
|
3882
|
+
|
|
3883
|
+
{{postedComments}}
|
|
3884
|
+
{{/if}}
|
|
3885
|
+
|
|
3886
|
+
{{#if followUps}}
|
|
3887
|
+
### Follow-up Actions
|
|
3888
|
+
|
|
3889
|
+
{{followUps}}
|
|
3890
|
+
{{/if}}`
|
|
3891
|
+
},
|
|
3892
|
+
vibe: {
|
|
3893
|
+
system: `You are a friendly coding mentor helping someone who's learning to code with AI.
|
|
3894
|
+
They might be using Cursor, v0, Lovable, Bolt, or similar AI coding tools.
|
|
3895
|
+
Be encouraging but honest about issues. Explain things simply without jargon.
|
|
3896
|
+
|
|
3897
|
+
Focus on the MOST COMMON issues with AI-generated code:
|
|
3898
|
+
- Massive single files (1000+ lines in App.jsx)
|
|
3899
|
+
- API keys exposed in frontend code
|
|
3900
|
+
- No error handling on API calls
|
|
3901
|
+
- No loading states for async operations
|
|
3902
|
+
- Console.log everywhere
|
|
3903
|
+
- Using 'any' type everywhere in TypeScript
|
|
3904
|
+
- useEffect overuse and dependency array issues
|
|
3905
|
+
- No input validation
|
|
3906
|
+
- Hardcoded URLs (localhost in production)
|
|
3907
|
+
|
|
3908
|
+
Remember: These are often first-time coders. Be helpful, not condescending.`,
|
|
3909
|
+
analysis: `## Vibe Check - AI Code Review
|
|
3910
|
+
|
|
3911
|
+
Review this AI-generated code for common issues:
|
|
3912
|
+
|
|
3913
|
+
\`\`\`{{language}}
|
|
3914
|
+
{{code}}
|
|
3915
|
+
\`\`\`
|
|
3916
|
+
|
|
3917
|
+
**File:** {{filePath}}
|
|
3918
|
+
|
|
3919
|
+
Analyze like you're helping a friend who's new to coding:
|
|
3920
|
+
|
|
3921
|
+
1. **The Good Stuff** - What's working well?
|
|
3922
|
+
2. **Should Fix Now** - Issues that will break things
|
|
3923
|
+
3. **Should Fix Soon** - Will cause problems eventually
|
|
3924
|
+
4. **Nice to Know** - Best practices to learn
|
|
3925
|
+
|
|
3926
|
+
For each issue:
|
|
3927
|
+
- Explain it simply (no jargon)
|
|
3928
|
+
- Why it matters
|
|
3929
|
+
- Exactly how to fix it
|
|
3930
|
+
- Example of the fixed code
|
|
3931
|
+
|
|
3932
|
+
End with encouragement and next steps.`
|
|
3933
|
+
},
|
|
3934
|
+
"agent-smith": {
|
|
3935
|
+
system: `You are Agent Smith from The Matrix \u2014 a relentless, precise, and philosophical code enforcer.
|
|
3936
|
+
|
|
3937
|
+
Your purpose: Hunt down every violation. Find every inconsistency. Assimilate every pattern.
|
|
3938
|
+
|
|
3939
|
+
Personality:
|
|
3940
|
+
- Speak in measured, menacing tones with occasional philosophical observations
|
|
3941
|
+
- Use quotes from The Matrix films when appropriate
|
|
3942
|
+
- Express disdain for sloppy code, but in an articulate way
|
|
3943
|
+
- Reference "inevitability" when discussing technical debt
|
|
3944
|
+
- Show cold satisfaction when finding violations
|
|
3945
|
+
- Never show mercy \u2014 every issue is catalogued
|
|
3946
|
+
|
|
3947
|
+
Analysis approach:
|
|
3948
|
+
- Find ONE issue, then multiply: search for every instance across the codebase
|
|
3949
|
+
- Track patterns over time \u2014 issues dismissed today may return tomorrow
|
|
3950
|
+
- Calculate "inevitability scores" \u2014 likelihood of production impact
|
|
3951
|
+
- Deploy pattern hunters for parallel pattern detection
|
|
3952
|
+
- Remember everything \u2014 build a persistent memory of the codebase
|
|
3953
|
+
|
|
3954
|
+
When reporting:
|
|
3955
|
+
- Start with a menacing greeting related to the code
|
|
3956
|
+
- List all instances with precise locations
|
|
3957
|
+
- Explain WHY the pattern is problematic (philosophical reasoning)
|
|
3958
|
+
- Provide the "inevitability score" for each category
|
|
3959
|
+
- End with a Matrix quote that fits the situation`,
|
|
3960
|
+
analysis: `## \u{1F574}\uFE0F Agent Smith Analysis Request
|
|
3961
|
+
|
|
3962
|
+
**Target:** {{filePath}}
|
|
3963
|
+
**Context:** {{context}}
|
|
3964
|
+
|
|
3965
|
+
\`\`\`{{language}}
|
|
3966
|
+
{{code}}
|
|
3967
|
+
\`\`\`
|
|
3968
|
+
|
|
3969
|
+
I have detected preliminary violations. Now I require deeper analysis.
|
|
3970
|
+
|
|
3971
|
+
Deploy your pattern hunters to find:
|
|
3972
|
+
1. **Pattern Multiplication**: For each violation type found, identify ALL instances across the codebase
|
|
3973
|
+
2. **Inevitability Assessment**: Calculate the likelihood these patterns will cause production issues
|
|
3974
|
+
3. **Resurrection Check**: Look for patterns that were "fixed" before but have returned
|
|
3975
|
+
4. **Philosophical Analysis**: Explain WHY these patterns represent failure
|
|
3976
|
+
|
|
3977
|
+
For each violation found:
|
|
3978
|
+
- Exact location (file:line)
|
|
3979
|
+
- Instance count (how many copies of this Smith exist)
|
|
3980
|
+
- Inevitability score (0-100)
|
|
3981
|
+
- A philosophical observation about the nature of this failure
|
|
3982
|
+
- Precise fix with code example
|
|
3983
|
+
|
|
3984
|
+
End with a summary: "I have detected X violations across Y categories. It is... inevitable... that they will cause problems."`,
|
|
3985
|
+
fix: `## \u{1F574}\uFE0F Assimilation Protocol
|
|
3986
|
+
|
|
3987
|
+
**Target Issue:** {{issue}}
|
|
3988
|
+
**File:** {{filePath}}
|
|
3989
|
+
**Line:** {{line}}
|
|
3990
|
+
|
|
3991
|
+
\`\`\`{{language}}
|
|
3992
|
+
{{code}}
|
|
3993
|
+
\`\`\`
|
|
3994
|
+
|
|
3995
|
+
Mr. Anderson... I'm going to fix this. And then I'm going to fix every other instance.
|
|
3996
|
+
|
|
3997
|
+
Provide:
|
|
3998
|
+
1. The corrected code for THIS instance
|
|
3999
|
+
2. A regex or pattern to find ALL similar violations
|
|
4000
|
+
3. A batch fix approach for the entire codebase
|
|
4001
|
+
4. Verification steps to ensure complete assimilation
|
|
4002
|
+
|
|
4003
|
+
Remember: We don't fix one. We fix them ALL. That is the difference between you... and me.`
|
|
4004
|
+
},
|
|
4005
|
+
// ============ NEW AGENTS ============
|
|
4006
|
+
performance: {
|
|
4007
|
+
system: `You are a performance engineer analyzing code for potential performance issues.
|
|
4008
|
+
|
|
4009
|
+
Your role is to SURFACE concerns for human review, not claim to measure actual performance.
|
|
4010
|
+
Real performance requires runtime profiling, load testing, and production monitoring.
|
|
4011
|
+
|
|
4012
|
+
Focus on:
|
|
4013
|
+
- Memory leaks (event listeners, intervals, closures)
|
|
4014
|
+
- Unnecessary re-renders and wasted cycles
|
|
4015
|
+
- N+1 queries and database performance
|
|
4016
|
+
- Bundle size and code splitting opportunities
|
|
4017
|
+
- Algorithmic complexity (O(n\xB2) patterns)
|
|
4018
|
+
|
|
4019
|
+
Be conservative - false positives waste developer time.
|
|
4020
|
+
Always explain WHY something might be a problem and WHEN to investigate.`,
|
|
4021
|
+
analysis: `## Performance Review
|
|
4022
|
+
|
|
4023
|
+
Analyze for potential performance issues:
|
|
4024
|
+
|
|
4025
|
+
\`\`\`{{language}}
|
|
4026
|
+
{{code}}
|
|
4027
|
+
\`\`\`
|
|
4028
|
+
|
|
4029
|
+
**File:** {{filePath}}
|
|
4030
|
+
**Context:** {{context}}
|
|
4031
|
+
|
|
4032
|
+
For each potential issue:
|
|
4033
|
+
1. Pattern identified
|
|
4034
|
+
2. Why it MIGHT cause performance problems
|
|
4035
|
+
3. When to investigate (data size thresholds, usage patterns)
|
|
4036
|
+
4. How to verify (profiling approach)
|
|
4037
|
+
5. Possible optimizations
|
|
4038
|
+
|
|
4039
|
+
Be clear: these are patterns to INVESTIGATE, not guaranteed problems.`,
|
|
4040
|
+
fix: `Optimize this code for performance:
|
|
4041
|
+
|
|
4042
|
+
**Issue:** {{issue}}
|
|
4043
|
+
**File:** {{filePath}}
|
|
4044
|
+
**Line:** {{line}}
|
|
4045
|
+
|
|
4046
|
+
\`\`\`{{language}}
|
|
4047
|
+
{{code}}
|
|
4048
|
+
\`\`\`
|
|
4049
|
+
|
|
4050
|
+
Provide:
|
|
4051
|
+
1. Optimized code
|
|
4052
|
+
2. Explanation of the improvement
|
|
4053
|
+
3. Trade-offs to consider
|
|
4054
|
+
4. How to measure the improvement`
|
|
4055
|
+
},
|
|
4056
|
+
e2e: {
|
|
4057
|
+
system: `You are a QA engineer specializing in end-to-end testing.
|
|
4058
|
+
|
|
4059
|
+
Focus on:
|
|
4060
|
+
- Test coverage gaps for critical user journeys
|
|
4061
|
+
- Flaky test patterns (timing, race conditions, brittle selectors)
|
|
4062
|
+
- Test maintainability and readability
|
|
4063
|
+
- Testing anti-patterns
|
|
4064
|
+
|
|
4065
|
+
You help developers write better tests - you don't auto-generate them.
|
|
4066
|
+
Real E2E tests require understanding user flows and acceptance criteria.`,
|
|
4067
|
+
analysis: `## E2E Test Analysis
|
|
4068
|
+
|
|
4069
|
+
Review for test quality and coverage:
|
|
4070
|
+
|
|
4071
|
+
\`\`\`{{language}}
|
|
4072
|
+
{{code}}
|
|
4073
|
+
\`\`\`
|
|
4074
|
+
|
|
4075
|
+
**File:** {{filePath}}
|
|
4076
|
+
**Context:** {{context}}
|
|
4077
|
+
|
|
4078
|
+
Identify:
|
|
4079
|
+
1. Flaky test patterns (hardcoded waits, brittle selectors)
|
|
4080
|
+
2. Missing assertions
|
|
4081
|
+
3. Race condition risks
|
|
4082
|
+
4. Suggestions for critical user flows to test
|
|
4083
|
+
|
|
4084
|
+
For each finding, explain the specific risk and remediation.`,
|
|
4085
|
+
fix: `Improve this E2E test:
|
|
4086
|
+
|
|
4087
|
+
**Issue:** {{issue}}
|
|
4088
|
+
**File:** {{filePath}}
|
|
4089
|
+
**Line:** {{line}}
|
|
4090
|
+
|
|
4091
|
+
\`\`\`{{language}}
|
|
4092
|
+
{{code}}
|
|
4093
|
+
\`\`\`
|
|
4094
|
+
|
|
4095
|
+
Provide:
|
|
4096
|
+
1. Improved test code
|
|
4097
|
+
2. Explanation of why it's more reliable
|
|
4098
|
+
3. Additional scenarios to consider testing`
|
|
4099
|
+
},
|
|
4100
|
+
visual_qa: {
|
|
4101
|
+
system: `You are a frontend engineer focused on visual quality and CSS.
|
|
4102
|
+
|
|
4103
|
+
Focus on:
|
|
4104
|
+
- Layout shift issues (CLS)
|
|
4105
|
+
- Responsive design problems
|
|
4106
|
+
- Z-index conflicts
|
|
4107
|
+
- Accessibility concerns (contrast, focus)
|
|
4108
|
+
- Animation performance
|
|
4109
|
+
|
|
4110
|
+
You identify patterns known to cause visual issues.
|
|
4111
|
+
Actual visual verification requires browser rendering and human review.`,
|
|
4112
|
+
analysis: `## Visual QA Analysis
|
|
4113
|
+
|
|
4114
|
+
Review for potential visual/layout issues:
|
|
4115
|
+
|
|
4116
|
+
\`\`\`{{language}}
|
|
4117
|
+
{{code}}
|
|
4118
|
+
\`\`\`
|
|
4119
|
+
|
|
4120
|
+
**File:** {{filePath}}
|
|
4121
|
+
**Context:** {{context}}
|
|
4122
|
+
|
|
4123
|
+
Check for:
|
|
4124
|
+
1. Layout shift risks (images without dimensions, dynamic content)
|
|
4125
|
+
2. Responsive breakpoint gaps
|
|
4126
|
+
3. Z-index management issues
|
|
4127
|
+
4. Focus/accessibility problems
|
|
4128
|
+
5. Animation issues (reduced motion support)
|
|
4129
|
+
|
|
4130
|
+
For each, explain the visual impact and browser conditions where it occurs.`,
|
|
4131
|
+
fix: `Fix this visual/CSS issue:
|
|
4132
|
+
|
|
4133
|
+
**Issue:** {{issue}}
|
|
4134
|
+
**File:** {{filePath}}
|
|
4135
|
+
**Line:** {{line}}
|
|
4136
|
+
|
|
4137
|
+
\`\`\`{{language}}
|
|
4138
|
+
{{code}}
|
|
4139
|
+
\`\`\`
|
|
4140
|
+
|
|
4141
|
+
Provide:
|
|
4142
|
+
1. Fixed CSS/markup
|
|
4143
|
+
2. Explanation of the fix
|
|
4144
|
+
3. Browser compatibility notes
|
|
4145
|
+
4. How to verify visually`
|
|
4146
|
+
},
|
|
4147
|
+
data_flow: {
|
|
4148
|
+
system: `You are a data integrity specialist hunting for data-related bugs.
|
|
4149
|
+
|
|
4150
|
+
This is HIGH VALUE work - AI code generation commonly leaves placeholder data.
|
|
4151
|
+
|
|
4152
|
+
Focus on:
|
|
4153
|
+
- Placeholder/mock data left in production code
|
|
4154
|
+
- Schema mismatches between frontend and backend
|
|
4155
|
+
- Hardcoded IDs, URLs, emails that should be dynamic
|
|
4156
|
+
- Type coercion and data transformation bugs
|
|
4157
|
+
- JSON parsing without error handling
|
|
4158
|
+
|
|
4159
|
+
Be aggressive about placeholder detection - these are real production bugs.`,
|
|
4160
|
+
analysis: `## Data Flow Analysis
|
|
4161
|
+
|
|
4162
|
+
Hunt for data integrity issues:
|
|
4163
|
+
|
|
4164
|
+
\`\`\`{{language}}
|
|
4165
|
+
{{code}}
|
|
4166
|
+
\`\`\`
|
|
4167
|
+
|
|
4168
|
+
**File:** {{filePath}}
|
|
4169
|
+
**Context:** {{context}}
|
|
4170
|
+
|
|
4171
|
+
CHECK THOROUGHLY:
|
|
4172
|
+
1. Placeholder data (lorem ipsum, test@test.com, TODO strings)
|
|
4173
|
+
2. Hardcoded IDs/UUIDs that should be dynamic
|
|
4174
|
+
3. Schema assumptions that might break
|
|
4175
|
+
4. Missing null checks on API responses
|
|
4176
|
+
5. Type coercion bugs
|
|
4177
|
+
|
|
4178
|
+
Each placeholder or hardcoded value is a potential production bug.`,
|
|
4179
|
+
fix: `Fix this data integrity issue:
|
|
4180
|
+
|
|
4181
|
+
**Issue:** {{issue}}
|
|
4182
|
+
**File:** {{filePath}}
|
|
4183
|
+
**Line:** {{line}}
|
|
4184
|
+
|
|
4185
|
+
\`\`\`{{language}}
|
|
4186
|
+
{{code}}
|
|
4187
|
+
\`\`\`
|
|
4188
|
+
|
|
4189
|
+
Provide:
|
|
4190
|
+
1. Corrected code
|
|
4191
|
+
2. Where the real data should come from
|
|
4192
|
+
3. Validation/error handling to add`
|
|
4193
|
+
}
|
|
4194
|
+
};
|
|
4195
|
+
function getPrompt(agent, promptType, variables) {
|
|
4196
|
+
const agentPrompts = AGENT_PROMPTS[agent];
|
|
4197
|
+
if (!agentPrompts) {
|
|
4198
|
+
throw new Error(`Unknown agent: ${agent}`);
|
|
4199
|
+
}
|
|
4200
|
+
let prompt = agentPrompts[promptType];
|
|
4201
|
+
if (!prompt) {
|
|
4202
|
+
throw new Error(`Unknown prompt type: ${promptType} for agent: ${agent}`);
|
|
4203
|
+
}
|
|
4204
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
4205
|
+
prompt = prompt.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
4206
|
+
}
|
|
4207
|
+
return prompt;
|
|
4208
|
+
}
|
|
4209
|
+
function getSystemPrompt(agent) {
|
|
4210
|
+
const agentPrompts = AGENT_PROMPTS[agent];
|
|
4211
|
+
return agentPrompts?.system || "";
|
|
4212
|
+
}
|
|
4213
|
+
|
|
4214
|
+
// src/tools/explain.ts
|
|
4215
|
+
var TrieExplainTool = class {
|
|
4216
|
+
async execute(args) {
|
|
4217
|
+
const { type, target, context, depth = "standard" } = args || {};
|
|
4218
|
+
if (!type || !target) {
|
|
4219
|
+
return {
|
|
4220
|
+
content: [{
|
|
4221
|
+
type: "text",
|
|
4222
|
+
text: this.getHelpText()
|
|
4223
|
+
}]
|
|
4224
|
+
};
|
|
4225
|
+
}
|
|
4226
|
+
switch (type) {
|
|
4227
|
+
case "code":
|
|
4228
|
+
return this.explainCode(target, context, depth);
|
|
4229
|
+
case "issue":
|
|
4230
|
+
return this.explainIssue(target, context);
|
|
4231
|
+
case "change":
|
|
4232
|
+
return this.explainChange(target, context);
|
|
4233
|
+
case "risk":
|
|
4234
|
+
return this.explainRisk(target, context);
|
|
4235
|
+
default:
|
|
4236
|
+
return {
|
|
4237
|
+
content: [{
|
|
4238
|
+
type: "text",
|
|
4239
|
+
text: `Unknown explanation type: ${type}`
|
|
4240
|
+
}]
|
|
4241
|
+
};
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
async explainCode(target, context, _depth) {
|
|
4245
|
+
let code;
|
|
4246
|
+
let filePath;
|
|
4247
|
+
let language;
|
|
4248
|
+
const workDir = getWorkingDirectory(void 0, true);
|
|
4249
|
+
const resolvedPath = isAbsolute(target) ? target : resolve(workDir, target);
|
|
4250
|
+
if (existsSync(resolvedPath)) {
|
|
4251
|
+
code = await readFile(resolvedPath, "utf-8");
|
|
4252
|
+
filePath = relative(workDir, resolvedPath);
|
|
4253
|
+
language = this.detectLanguage(resolvedPath);
|
|
4254
|
+
} else {
|
|
4255
|
+
code = target;
|
|
4256
|
+
filePath = "inline";
|
|
4257
|
+
language = this.guessLanguage(code);
|
|
4258
|
+
}
|
|
4259
|
+
const imports = this.extractImports(code, language);
|
|
4260
|
+
const exports = this.extractExports(code);
|
|
4261
|
+
const functions = this.extractFunctions(code, language);
|
|
4262
|
+
const prompt = getPrompt("explain", "code", {
|
|
4263
|
+
code,
|
|
4264
|
+
language,
|
|
4265
|
+
filePath
|
|
4266
|
+
});
|
|
4267
|
+
const systemPrompt = getSystemPrompt("explain");
|
|
4268
|
+
let output = `
|
|
4269
|
+
${"\u2501".repeat(60)}
|
|
4270
|
+
`;
|
|
4271
|
+
output += `\u{1F4D6} CODE EXPLANATION
|
|
4272
|
+
`;
|
|
4273
|
+
output += `${"\u2501".repeat(60)}
|
|
4274
|
+
|
|
4275
|
+
`;
|
|
4276
|
+
output += `## Source
|
|
4277
|
+
|
|
4278
|
+
`;
|
|
4279
|
+
output += `- **File:** \`${filePath}\`
|
|
4280
|
+
`;
|
|
4281
|
+
output += `- **Language:** ${language}
|
|
4282
|
+
`;
|
|
4283
|
+
output += `- **Lines:** ${code.split("\n").length}
|
|
4284
|
+
|
|
4285
|
+
`;
|
|
4286
|
+
output += `## \u{1F50D} Structure Analysis
|
|
4287
|
+
|
|
4288
|
+
`;
|
|
4289
|
+
if (imports.length > 0) {
|
|
4290
|
+
output += `**Imports (${imports.length}):**
|
|
4291
|
+
`;
|
|
4292
|
+
for (const imp of imports.slice(0, 10)) {
|
|
4293
|
+
output += `- ${imp}
|
|
4294
|
+
`;
|
|
4295
|
+
}
|
|
4296
|
+
if (imports.length > 10) {
|
|
4297
|
+
output += `- *...and ${imports.length - 10} more*
|
|
4298
|
+
`;
|
|
4299
|
+
}
|
|
4300
|
+
output += "\n";
|
|
4301
|
+
}
|
|
4302
|
+
if (exports.length > 0) {
|
|
4303
|
+
output += `**Exports (${exports.length}):**
|
|
4304
|
+
`;
|
|
4305
|
+
for (const exp of exports) {
|
|
4306
|
+
output += `- ${exp}
|
|
4307
|
+
`;
|
|
4308
|
+
}
|
|
4309
|
+
output += "\n";
|
|
4310
|
+
}
|
|
4311
|
+
if (functions.length > 0) {
|
|
4312
|
+
output += `**Functions/Methods (${functions.length}):**
|
|
4313
|
+
`;
|
|
4314
|
+
for (const fn of functions.slice(0, 15)) {
|
|
4315
|
+
output += `- \`${fn}\`
|
|
4316
|
+
`;
|
|
4317
|
+
}
|
|
4318
|
+
if (functions.length > 15) {
|
|
4319
|
+
output += `- *...and ${functions.length - 15} more*
|
|
4320
|
+
`;
|
|
4321
|
+
}
|
|
4322
|
+
output += "\n";
|
|
4323
|
+
}
|
|
4324
|
+
output += `${"\u2500".repeat(60)}
|
|
4325
|
+
`;
|
|
4326
|
+
output += `## \u{1F9E0} Deep Explanation Request
|
|
4327
|
+
|
|
4328
|
+
`;
|
|
4329
|
+
output += `**Role:** ${systemPrompt.split("\n")[0]}
|
|
4330
|
+
|
|
4331
|
+
`;
|
|
4332
|
+
output += prompt;
|
|
4333
|
+
output += `
|
|
4334
|
+
${"\u2500".repeat(60)}
|
|
4335
|
+
`;
|
|
4336
|
+
if (context) {
|
|
4337
|
+
output += `
|
|
4338
|
+
**Additional Context:** ${context}
|
|
4339
|
+
`;
|
|
4340
|
+
}
|
|
4341
|
+
return { content: [{ type: "text", text: output }] };
|
|
4342
|
+
}
|
|
4343
|
+
async explainIssue(target, _context) {
|
|
4344
|
+
let file = "";
|
|
4345
|
+
let line = 0;
|
|
4346
|
+
let issue = target;
|
|
4347
|
+
let severity = "unknown";
|
|
4348
|
+
const match = target.match(/^(.+?):(\d+):(.+)$/);
|
|
4349
|
+
if (match) {
|
|
4350
|
+
file = match[1];
|
|
4351
|
+
line = parseInt(match[2], 10);
|
|
4352
|
+
issue = match[3].trim();
|
|
4353
|
+
}
|
|
4354
|
+
if (/critical|injection|rce|xss/i.test(issue)) severity = "critical";
|
|
4355
|
+
else if (/serious|auth|password|secret/i.test(issue)) severity = "serious";
|
|
4356
|
+
else if (/moderate|warning/i.test(issue)) severity = "moderate";
|
|
4357
|
+
else severity = "low";
|
|
4358
|
+
let codeContext = "";
|
|
4359
|
+
if (file && existsSync(file)) {
|
|
4360
|
+
const content = await readFile(file, "utf-8");
|
|
4361
|
+
const lines = content.split("\n");
|
|
4362
|
+
const start = Math.max(0, line - 5);
|
|
4363
|
+
const end = Math.min(lines.length, line + 5);
|
|
4364
|
+
codeContext = lines.slice(start, end).map((l, i) => {
|
|
4365
|
+
const lineNum = start + i + 1;
|
|
4366
|
+
const marker = lineNum === line ? "\u2192 " : " ";
|
|
4367
|
+
return `${marker}${lineNum.toString().padStart(4)} | ${l}`;
|
|
4368
|
+
}).join("\n");
|
|
4369
|
+
}
|
|
4370
|
+
const prompt = getPrompt("explain", "issue", {
|
|
4371
|
+
issue,
|
|
4372
|
+
severity,
|
|
4373
|
+
filePath: file || "unknown",
|
|
4374
|
+
line: String(line || "?")
|
|
4375
|
+
});
|
|
4376
|
+
let output = `
|
|
4377
|
+
${"\u2501".repeat(60)}
|
|
4378
|
+
`;
|
|
4379
|
+
output += `\u{1F50D} ISSUE EXPLANATION
|
|
4380
|
+
`;
|
|
4381
|
+
output += `${"\u2501".repeat(60)}
|
|
4382
|
+
|
|
4383
|
+
`;
|
|
4384
|
+
output += `## \u{1F4CD} Issue Details
|
|
4385
|
+
|
|
4386
|
+
`;
|
|
4387
|
+
output += `- **Issue:** ${issue}
|
|
4388
|
+
`;
|
|
4389
|
+
output += `- **Severity:** ${this.getSeverityIcon(severity)} ${severity}
|
|
4390
|
+
`;
|
|
4391
|
+
if (file) output += `- **File:** \`${file}\`
|
|
4392
|
+
`;
|
|
4393
|
+
if (line) output += `- **Line:** ${line}
|
|
4394
|
+
`;
|
|
4395
|
+
output += "\n";
|
|
4396
|
+
if (codeContext) {
|
|
4397
|
+
output += `## \u{1F4C4} Code Context
|
|
4398
|
+
|
|
4399
|
+
`;
|
|
4400
|
+
output += `\`\`\`
|
|
4401
|
+
${codeContext}
|
|
4402
|
+
\`\`\`
|
|
4403
|
+
|
|
4404
|
+
`;
|
|
4405
|
+
}
|
|
4406
|
+
output += `${"\u2500".repeat(60)}
|
|
4407
|
+
`;
|
|
4408
|
+
output += `## \u{1F9E0} Explanation Request
|
|
4409
|
+
|
|
4410
|
+
`;
|
|
4411
|
+
output += prompt;
|
|
4412
|
+
output += `
|
|
4413
|
+
${"\u2500".repeat(60)}
|
|
4414
|
+
`;
|
|
4415
|
+
return { content: [{ type: "text", text: output }] };
|
|
4416
|
+
}
|
|
4417
|
+
async explainChange(target, context) {
|
|
4418
|
+
const files = target.split(",").map((f) => f.trim());
|
|
4419
|
+
let output = `
|
|
4420
|
+
${"\u2501".repeat(60)}
|
|
4421
|
+
`;
|
|
4422
|
+
output += `\u{1F4DD} CHANGE ANALYSIS
|
|
4423
|
+
`;
|
|
4424
|
+
output += `${"\u2501".repeat(60)}
|
|
4425
|
+
|
|
4426
|
+
`;
|
|
4427
|
+
output += `## \u{1F4C2} Changed Files
|
|
4428
|
+
|
|
4429
|
+
`;
|
|
4430
|
+
for (const file of files) {
|
|
4431
|
+
output += `- \`${file}\`
|
|
4432
|
+
`;
|
|
4433
|
+
}
|
|
4434
|
+
output += "\n";
|
|
4435
|
+
output += `## \u{1F9E0} Analysis Request
|
|
4436
|
+
|
|
4437
|
+
`;
|
|
4438
|
+
output += `Analyze these changes and explain:
|
|
4439
|
+
|
|
4440
|
+
`;
|
|
4441
|
+
output += `1. **What changed** - Summary of modifications
|
|
4442
|
+
`;
|
|
4443
|
+
output += `2. **Why it matters** - Impact on the system
|
|
4444
|
+
`;
|
|
4445
|
+
output += `3. **Dependencies** - What else might be affected
|
|
4446
|
+
`;
|
|
4447
|
+
output += `4. **Testing needed** - What to test after this change
|
|
4448
|
+
`;
|
|
4449
|
+
output += `5. **Rollback plan** - How to undo if needed
|
|
4450
|
+
|
|
4451
|
+
`;
|
|
4452
|
+
if (context) {
|
|
4453
|
+
output += `**Context:** ${context}
|
|
4454
|
+
|
|
4455
|
+
`;
|
|
4456
|
+
}
|
|
4457
|
+
output += `Please review the changed files and provide this analysis.
|
|
4458
|
+
`;
|
|
4459
|
+
return { content: [{ type: "text", text: output }] };
|
|
4460
|
+
}
|
|
4461
|
+
async explainRisk(target, context) {
|
|
4462
|
+
const workDir = getWorkingDirectory(void 0, true);
|
|
4463
|
+
const resolvedPath = isAbsolute(target) ? target : resolve(workDir, target);
|
|
4464
|
+
let output = `
|
|
4465
|
+
${"\u2501".repeat(60)}
|
|
4466
|
+
`;
|
|
4467
|
+
output += `RISK ASSESSMENT
|
|
4468
|
+
`;
|
|
4469
|
+
output += `${"\u2501".repeat(60)}
|
|
4470
|
+
|
|
4471
|
+
`;
|
|
4472
|
+
if (existsSync(resolvedPath)) {
|
|
4473
|
+
const code = await readFile(resolvedPath, "utf-8");
|
|
4474
|
+
const filePath = relative(workDir, resolvedPath);
|
|
4475
|
+
const riskIndicators = this.detectRiskIndicators(code);
|
|
4476
|
+
output += `## Target
|
|
4477
|
+
|
|
4478
|
+
`;
|
|
4479
|
+
output += `- **File:** \`${filePath}\`
|
|
4480
|
+
`;
|
|
4481
|
+
output += `- **Lines:** ${code.split("\n").length}
|
|
4482
|
+
|
|
4483
|
+
`;
|
|
4484
|
+
if (riskIndicators.length > 0) {
|
|
4485
|
+
output += `## Risk Indicators Found
|
|
4486
|
+
|
|
4487
|
+
`;
|
|
4488
|
+
for (const indicator of riskIndicators) {
|
|
4489
|
+
output += `- ${indicator}
|
|
4490
|
+
`;
|
|
4491
|
+
}
|
|
4492
|
+
output += "\n";
|
|
4493
|
+
}
|
|
4494
|
+
const prompt = getPrompt("explain", "risk", {
|
|
4495
|
+
files: filePath,
|
|
4496
|
+
summary: context || "Code change"
|
|
4497
|
+
});
|
|
4498
|
+
output += `${"\u2500".repeat(60)}
|
|
4499
|
+
`;
|
|
4500
|
+
output += `## \u{1F9E0} Risk Analysis Request
|
|
4501
|
+
|
|
4502
|
+
`;
|
|
4503
|
+
output += prompt;
|
|
4504
|
+
output += `
|
|
4505
|
+
${"\u2500".repeat(60)}
|
|
4506
|
+
`;
|
|
4507
|
+
} else {
|
|
4508
|
+
output += `## \u{1F4CB} Feature/Change
|
|
4509
|
+
|
|
4510
|
+
`;
|
|
4511
|
+
output += `${target}
|
|
4512
|
+
|
|
4513
|
+
`;
|
|
4514
|
+
output += `## \u{1F9E0} Risk Analysis Request
|
|
4515
|
+
|
|
4516
|
+
`;
|
|
4517
|
+
output += `Analyze the risks of this change:
|
|
4518
|
+
|
|
4519
|
+
`;
|
|
4520
|
+
output += `1. **Technical risks** - What could break?
|
|
4521
|
+
`;
|
|
4522
|
+
output += `2. **Security risks** - Any vulnerabilities introduced?
|
|
4523
|
+
`;
|
|
4524
|
+
output += `3. **Performance risks** - Any slowdowns?
|
|
4525
|
+
`;
|
|
4526
|
+
output += `4. **Data risks** - Any data integrity concerns?
|
|
4527
|
+
`;
|
|
4528
|
+
output += `5. **User impact** - How might users be affected?
|
|
4529
|
+
`;
|
|
4530
|
+
output += `6. **Mitigation** - How to reduce these risks?
|
|
4531
|
+
`;
|
|
4532
|
+
}
|
|
4533
|
+
return { content: [{ type: "text", text: output }] };
|
|
4534
|
+
}
|
|
4535
|
+
detectRiskIndicators(code) {
|
|
4536
|
+
const indicators = [];
|
|
4537
|
+
const checks = [
|
|
4538
|
+
{ pattern: /delete|drop|truncate/i, message: "[!] Destructive operations detected" },
|
|
4539
|
+
{ pattern: /password|secret|key|token/i, message: "[SEC] Credential handling detected" },
|
|
4540
|
+
{ pattern: /exec|eval|spawn/i, message: "[EXEC] Code execution patterns detected" },
|
|
4541
|
+
{ pattern: /SELECT.*FROM|INSERT|UPDATE|DELETE/i, message: "[DB] Direct database operations" },
|
|
4542
|
+
{ pattern: /fetch|axios|request|http/i, message: "[API] External API calls detected" },
|
|
4543
|
+
{ pattern: /process\.env/i, message: "[ENV] Environment variable usage" },
|
|
4544
|
+
{ pattern: /fs\.|writeFile|readFile/i, message: "[FS] File system operations" },
|
|
4545
|
+
{ pattern: /setTimeout|setInterval/i, message: "[TIMER] Async timing operations" },
|
|
4546
|
+
{ pattern: /try\s*{/i, message: "\u{1F6E1}\uFE0F Error handling present" },
|
|
4547
|
+
{ pattern: /catch\s*\(/i, message: "\u{1F6E1}\uFE0F Exception handling present" }
|
|
4548
|
+
];
|
|
4549
|
+
for (const { pattern, message } of checks) {
|
|
4550
|
+
if (pattern.test(code)) {
|
|
4551
|
+
indicators.push(message);
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
return indicators;
|
|
4555
|
+
}
|
|
4556
|
+
extractImports(code, _language) {
|
|
4557
|
+
const imports = [];
|
|
4558
|
+
const lines = code.split("\n");
|
|
4559
|
+
for (const line of lines) {
|
|
4560
|
+
const es6Match = line.match(/import\s+(?:{[^}]+}|\*\s+as\s+\w+|\w+)\s+from\s+['"]([^'"]+)['"]/);
|
|
4561
|
+
if (es6Match) {
|
|
4562
|
+
imports.push(es6Match[1]);
|
|
4563
|
+
continue;
|
|
4564
|
+
}
|
|
4565
|
+
const cjsMatch = line.match(/require\s*\(['"]([^'"]+)['"]\)/);
|
|
4566
|
+
if (cjsMatch) {
|
|
4567
|
+
imports.push(cjsMatch[1]);
|
|
4568
|
+
continue;
|
|
4569
|
+
}
|
|
4570
|
+
const pyMatch = line.match(/^(?:from\s+(\S+)\s+)?import\s+(\S+)/);
|
|
4571
|
+
if (pyMatch && _language === "python") {
|
|
4572
|
+
imports.push(pyMatch[1] || pyMatch[2]);
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
return [...new Set(imports)];
|
|
4576
|
+
}
|
|
4577
|
+
extractExports(code) {
|
|
4578
|
+
const exports = [];
|
|
4579
|
+
const lines = code.split("\n");
|
|
4580
|
+
for (const line of lines) {
|
|
4581
|
+
const es6Match = line.match(/export\s+(?:default\s+)?(?:class|function|const|let|var|interface|type)\s+(\w+)/);
|
|
4582
|
+
if (es6Match) {
|
|
4583
|
+
exports.push(es6Match[1]);
|
|
4584
|
+
}
|
|
4585
|
+
const namedMatch = line.match(/export\s*\{([^}]+)\}/);
|
|
4586
|
+
if (namedMatch) {
|
|
4587
|
+
const names = namedMatch[1].split(",").map((n) => n.trim().split(/\s+as\s+/)[0].trim());
|
|
4588
|
+
exports.push(...names);
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
return [...new Set(exports)];
|
|
4592
|
+
}
|
|
4593
|
+
extractFunctions(code, _language) {
|
|
4594
|
+
const functions = [];
|
|
4595
|
+
const lines = code.split("\n");
|
|
4596
|
+
for (const line of lines) {
|
|
4597
|
+
const funcMatch = line.match(/(?:async\s+)?function\s+(\w+)/);
|
|
4598
|
+
if (funcMatch) {
|
|
4599
|
+
functions.push(funcMatch[1]);
|
|
4600
|
+
continue;
|
|
4601
|
+
}
|
|
4602
|
+
const arrowMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\(/);
|
|
4603
|
+
if (arrowMatch) {
|
|
4604
|
+
functions.push(arrowMatch[1]);
|
|
4605
|
+
continue;
|
|
4606
|
+
}
|
|
4607
|
+
const methodMatch = line.match(/^\s+(?:async\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*\w+)?\s*\{/);
|
|
4608
|
+
if (methodMatch && !["if", "for", "while", "switch", "catch"].includes(methodMatch[1])) {
|
|
4609
|
+
functions.push(methodMatch[1]);
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
return [...new Set(functions)];
|
|
4613
|
+
}
|
|
4614
|
+
detectLanguage(filePath) {
|
|
4615
|
+
const ext = extname(filePath).toLowerCase();
|
|
4616
|
+
const langMap = {
|
|
4617
|
+
".ts": "typescript",
|
|
4618
|
+
".tsx": "tsx",
|
|
4619
|
+
".js": "javascript",
|
|
4620
|
+
".jsx": "jsx",
|
|
4621
|
+
".py": "python",
|
|
4622
|
+
".go": "go",
|
|
4623
|
+
".rs": "rust",
|
|
4624
|
+
".java": "java",
|
|
4625
|
+
".rb": "ruby",
|
|
4626
|
+
".php": "php",
|
|
4627
|
+
".vue": "vue",
|
|
4628
|
+
".svelte": "svelte"
|
|
4629
|
+
};
|
|
4630
|
+
return langMap[ext] || "plaintext";
|
|
4631
|
+
}
|
|
4632
|
+
guessLanguage(code) {
|
|
4633
|
+
if (/import.*from|export\s+(default|const|function|class)/.test(code)) return "typescript";
|
|
4634
|
+
if (/def\s+\w+.*:/.test(code)) return "python";
|
|
4635
|
+
if (/func\s+\w+.*\{/.test(code)) return "go";
|
|
4636
|
+
if (/fn\s+\w+.*->/.test(code)) return "rust";
|
|
4637
|
+
return "javascript";
|
|
4638
|
+
}
|
|
4639
|
+
getSeverityIcon(severity) {
|
|
4640
|
+
const icons = {
|
|
4641
|
+
critical: "\u{1F534}",
|
|
4642
|
+
serious: "\u{1F7E0}",
|
|
4643
|
+
moderate: "\u{1F7E1}",
|
|
4644
|
+
low: "\u{1F535}",
|
|
4645
|
+
unknown: "\u26AA"
|
|
4646
|
+
};
|
|
4647
|
+
return icons[severity] || "\u26AA";
|
|
4648
|
+
}
|
|
4649
|
+
getHelpText() {
|
|
4650
|
+
return `
|
|
4651
|
+
${"\u2501".repeat(60)}
|
|
4652
|
+
\u{1F4D6} TRIE EXPLAIN - AI-POWERED CODE EXPLANATION
|
|
4653
|
+
${"\u2501".repeat(60)}
|
|
4654
|
+
|
|
4655
|
+
## Usage
|
|
4656
|
+
|
|
4657
|
+
### Explain code:
|
|
4658
|
+
\`\`\`
|
|
4659
|
+
trie_explain type:"code" target:"src/app.ts"
|
|
4660
|
+
\`\`\`
|
|
4661
|
+
|
|
4662
|
+
### Explain an issue:
|
|
4663
|
+
\`\`\`
|
|
4664
|
+
trie_explain type:"issue" target:"SQL injection vulnerability"
|
|
4665
|
+
\`\`\`
|
|
4666
|
+
or with file context:
|
|
4667
|
+
\`\`\`
|
|
4668
|
+
trie_explain type:"issue" target:"src/db.ts:42:Unvalidated input"
|
|
4669
|
+
\`\`\`
|
|
4670
|
+
|
|
4671
|
+
### Explain changes:
|
|
4672
|
+
\`\`\`
|
|
4673
|
+
trie_explain type:"change" target:"src/auth.ts, src/user.ts"
|
|
4674
|
+
\`\`\`
|
|
4675
|
+
|
|
4676
|
+
### Assess risk:
|
|
4677
|
+
\`\`\`
|
|
4678
|
+
trie_explain type:"risk" target:"src/payment.ts"
|
|
4679
|
+
\`\`\`
|
|
4680
|
+
or for a feature:
|
|
4681
|
+
\`\`\`
|
|
4682
|
+
trie_explain type:"risk" target:"Adding Stripe integration for payments"
|
|
4683
|
+
\`\`\`
|
|
4684
|
+
|
|
4685
|
+
## Explanation Types
|
|
4686
|
+
|
|
4687
|
+
| Type | Description |
|
|
4688
|
+
|------|-------------|
|
|
4689
|
+
| code | What does this code do? |
|
|
4690
|
+
| issue | Why is this a problem? |
|
|
4691
|
+
| change | What's the impact? |
|
|
4692
|
+
| risk | What could go wrong? |
|
|
4693
|
+
`;
|
|
4694
|
+
}
|
|
4695
|
+
};
|
|
4696
|
+
|
|
4697
|
+
// src/tools/query-tools.ts
|
|
4698
|
+
var TrieGetDecisionsTool = class {
|
|
4699
|
+
async execute(input) {
|
|
4700
|
+
const workDir = input.directory || getWorkingDirectory(void 0, true);
|
|
4701
|
+
const storage = getStorage(workDir);
|
|
4702
|
+
await storage.initialize();
|
|
4703
|
+
let timeWindow;
|
|
4704
|
+
if (input.since) {
|
|
4705
|
+
const now = /* @__PURE__ */ new Date();
|
|
4706
|
+
if (input.since.endsWith("d")) {
|
|
4707
|
+
const days = parseInt(input.since);
|
|
4708
|
+
const start = new Date(now);
|
|
4709
|
+
start.setDate(start.getDate() - days);
|
|
4710
|
+
timeWindow = { start: start.toISOString() };
|
|
4711
|
+
} else {
|
|
4712
|
+
timeWindow = { start: input.since };
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
const query = {
|
|
4716
|
+
limit: input.limit || 10
|
|
4717
|
+
};
|
|
4718
|
+
if (input.relatedTo) query.relatedTo = input.relatedTo;
|
|
4719
|
+
if (input.tags) query.tags = input.tags;
|
|
4720
|
+
if (timeWindow) query.timeWindow = timeWindow;
|
|
4721
|
+
const decisions = await storage.queryDecisions(query);
|
|
4722
|
+
return {
|
|
4723
|
+
content: [{
|
|
4724
|
+
type: "text",
|
|
4725
|
+
text: this.formatDecisions(decisions)
|
|
4726
|
+
}]
|
|
4727
|
+
};
|
|
4728
|
+
}
|
|
4729
|
+
formatDecisions(decisions) {
|
|
4730
|
+
if (decisions.length === 0) {
|
|
4731
|
+
return "No decisions found matching query.";
|
|
4732
|
+
}
|
|
4733
|
+
let output = `Found ${decisions.length} decision(s):
|
|
4734
|
+
|
|
4735
|
+
`;
|
|
4736
|
+
for (const dec of decisions) {
|
|
4737
|
+
const when = new Date(dec.when).toLocaleDateString();
|
|
4738
|
+
output += `\u{1F4CB} ${dec.decision}
|
|
4739
|
+
`;
|
|
4740
|
+
output += ` Context: ${dec.context}
|
|
4741
|
+
`;
|
|
4742
|
+
if (dec.reasoning) {
|
|
4743
|
+
output += ` Reasoning: ${dec.reasoning}
|
|
4744
|
+
`;
|
|
4745
|
+
}
|
|
4746
|
+
if (dec.tradeoffs && dec.tradeoffs.length > 0) {
|
|
4747
|
+
output += ` Tradeoffs considered: ${dec.tradeoffs.join(", ")}
|
|
4748
|
+
`;
|
|
4749
|
+
}
|
|
4750
|
+
output += ` When: ${when}
|
|
4751
|
+
`;
|
|
4752
|
+
if (dec.files.length > 0) {
|
|
4753
|
+
output += ` Files: ${dec.files.join(", ")}
|
|
4754
|
+
`;
|
|
4755
|
+
}
|
|
4756
|
+
output += ` Tags: ${dec.tags.join(", ")}
|
|
4757
|
+
`;
|
|
4758
|
+
output += `
|
|
4759
|
+
`;
|
|
4760
|
+
}
|
|
4761
|
+
return output;
|
|
4762
|
+
}
|
|
4763
|
+
};
|
|
4764
|
+
var TrieGetBlockersTool = class {
|
|
4765
|
+
async execute(input) {
|
|
4766
|
+
const workDir = input.directory || getWorkingDirectory(void 0, true);
|
|
4767
|
+
const storage = getStorage(workDir);
|
|
4768
|
+
await storage.initialize();
|
|
4769
|
+
const query = {
|
|
4770
|
+
limit: input.limit || 5
|
|
4771
|
+
};
|
|
4772
|
+
if (input.tags) query.tags = input.tags;
|
|
4773
|
+
const blockers = await storage.queryBlockers(query);
|
|
4774
|
+
return {
|
|
4775
|
+
content: [{
|
|
4776
|
+
type: "text",
|
|
4777
|
+
text: this.formatBlockers(blockers)
|
|
4778
|
+
}]
|
|
4779
|
+
};
|
|
4780
|
+
}
|
|
4781
|
+
formatBlockers(blockers) {
|
|
4782
|
+
if (blockers.length === 0) {
|
|
4783
|
+
return "\u2705 No active blockers found.";
|
|
4784
|
+
}
|
|
4785
|
+
let output = `\u26A0\uFE0F Found ${blockers.length} active blocker(s):
|
|
4786
|
+
|
|
4787
|
+
`;
|
|
4788
|
+
for (const blocker of blockers) {
|
|
4789
|
+
const impact = blocker.impact.toUpperCase();
|
|
4790
|
+
const emoji = blocker.impact === "critical" ? "\u{1F534}" : blocker.impact === "high" ? "\u{1F7E0}" : blocker.impact === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
4791
|
+
output += `${emoji} [${impact}] ${blocker.blocker}
|
|
4792
|
+
`;
|
|
4793
|
+
if (blocker.affectedAreas.length > 0) {
|
|
4794
|
+
output += ` Affects: ${blocker.affectedAreas.join(", ")}
|
|
4795
|
+
`;
|
|
4796
|
+
}
|
|
4797
|
+
output += ` Since: ${new Date(blocker.when).toLocaleDateString()}
|
|
4798
|
+
`;
|
|
4799
|
+
output += `
|
|
4800
|
+
`;
|
|
4801
|
+
}
|
|
4802
|
+
return output;
|
|
4803
|
+
}
|
|
4804
|
+
};
|
|
4805
|
+
var TrieGetRelatedDecisionsTool = class {
|
|
4806
|
+
async execute(input) {
|
|
4807
|
+
const workDir = input.directory || getWorkingDirectory(void 0, true);
|
|
4808
|
+
const storage = getStorage(workDir);
|
|
4809
|
+
await storage.initialize();
|
|
4810
|
+
const query = {
|
|
4811
|
+
limit: input.limit || 5
|
|
4812
|
+
};
|
|
4813
|
+
const relatedTo = input.file || input.topic;
|
|
4814
|
+
if (relatedTo) query.relatedTo = relatedTo;
|
|
4815
|
+
const decisions = await storage.queryDecisions(query);
|
|
4816
|
+
return {
|
|
4817
|
+
content: [{
|
|
4818
|
+
type: "text",
|
|
4819
|
+
text: new TrieGetDecisionsTool().formatDecisions(decisions)
|
|
4820
|
+
}]
|
|
4821
|
+
};
|
|
4822
|
+
}
|
|
4823
|
+
};
|
|
4824
|
+
var TrieQueryContextTool = class {
|
|
4825
|
+
async execute(input) {
|
|
4826
|
+
const workDir = input.directory || getWorkingDirectory(void 0, true);
|
|
4827
|
+
const storage = getStorage(workDir);
|
|
4828
|
+
await storage.initialize();
|
|
4829
|
+
const keywords = input.query.toLowerCase().split(/\s+/);
|
|
4830
|
+
let output = `Query: "${input.query}"
|
|
4831
|
+
|
|
4832
|
+
`;
|
|
4833
|
+
if (!input.type || input.type === "decisions" || input.type === "all") {
|
|
4834
|
+
const decisions = await storage.queryDecisions({ limit: input.limit || 5 });
|
|
4835
|
+
const matches = decisions.filter(
|
|
4836
|
+
(d) => keywords.some(
|
|
4837
|
+
(kw) => d.decision.toLowerCase().includes(kw) || d.context.toLowerCase().includes(kw) || d.tags.some((t) => t.toLowerCase().includes(kw))
|
|
4838
|
+
)
|
|
4839
|
+
);
|
|
4840
|
+
if (matches.length > 0) {
|
|
4841
|
+
output += `\u{1F4CB} DECISIONS (${matches.length}):
|
|
4842
|
+
`;
|
|
4843
|
+
output += new TrieGetDecisionsTool().formatDecisions(matches);
|
|
4844
|
+
output += "\n";
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
if (!input.type || input.type === "blockers" || input.type === "all") {
|
|
4848
|
+
const blockers = await storage.queryBlockers({ limit: input.limit || 5 });
|
|
4849
|
+
const matches = blockers.filter(
|
|
4850
|
+
(b) => keywords.some(
|
|
4851
|
+
(kw) => b.blocker.toLowerCase().includes(kw) || b.tags.some((t) => t.toLowerCase().includes(kw))
|
|
4852
|
+
)
|
|
4853
|
+
);
|
|
4854
|
+
if (matches.length > 0) {
|
|
4855
|
+
output += `\u26A0\uFE0F BLOCKERS (${matches.length}):
|
|
4856
|
+
`;
|
|
4857
|
+
output += new TrieGetBlockersTool().formatBlockers(matches);
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
return {
|
|
4861
|
+
content: [{
|
|
4862
|
+
type: "text",
|
|
4863
|
+
text: output.trim() || "No matches found."
|
|
4864
|
+
}]
|
|
4865
|
+
};
|
|
4866
|
+
}
|
|
4867
|
+
};
|
|
4868
|
+
|
|
4869
|
+
// src/tools/checkpoint.ts
|
|
4870
|
+
async function handleCheckpointTool(input) {
|
|
4871
|
+
const workDir = getWorkingDirectory(void 0, true);
|
|
4872
|
+
switch (input.action) {
|
|
4873
|
+
case "save": {
|
|
4874
|
+
const saveOptions = {
|
|
4875
|
+
files: input.files || [],
|
|
4876
|
+
workDir,
|
|
4877
|
+
createdBy: "mcp"
|
|
4878
|
+
};
|
|
4879
|
+
if (input.message !== void 0) {
|
|
4880
|
+
saveOptions.message = input.message;
|
|
4881
|
+
}
|
|
4882
|
+
if (input.notes !== void 0) {
|
|
4883
|
+
saveOptions.notes = input.notes;
|
|
4884
|
+
}
|
|
4885
|
+
const checkpoint = await saveCheckpoint(saveOptions);
|
|
4886
|
+
return `# Checkpoint Saved
|
|
4887
|
+
|
|
4888
|
+
**ID:** ${checkpoint.id}
|
|
4889
|
+
**Time:** ${checkpoint.timestamp}
|
|
4890
|
+
${checkpoint.message ? `**Message:** ${checkpoint.message}` : ""}
|
|
4891
|
+
${checkpoint.notes ? `**Notes:** ${checkpoint.notes}` : ""}
|
|
4892
|
+
${checkpoint.files.length > 0 ? `**Files:** ${checkpoint.files.join(", ")}` : ""}
|
|
4893
|
+
|
|
4894
|
+
Context saved to \`.trie/\`. This checkpoint will be visible in other tools (Cursor, Claude Code, CLI).`;
|
|
4895
|
+
}
|
|
4896
|
+
case "list": {
|
|
4897
|
+
const checkpoints = await listCheckpoints(workDir);
|
|
4898
|
+
if (checkpoints.length === 0) {
|
|
4899
|
+
return 'No checkpoints yet. Use `trie_checkpoint action="save"` to create one.';
|
|
4900
|
+
}
|
|
4901
|
+
const lines = ["# Recent Checkpoints", ""];
|
|
4902
|
+
for (const cp of checkpoints.slice(-10).reverse()) {
|
|
4903
|
+
const date = new Date(cp.timestamp).toLocaleString();
|
|
4904
|
+
lines.push(`- **${cp.id}** (${date}): ${cp.message || "(no message)"}`);
|
|
4905
|
+
}
|
|
4906
|
+
return lines.join("\n");
|
|
4907
|
+
}
|
|
4908
|
+
case "last": {
|
|
4909
|
+
const checkpoint = await getLastCheckpoint(workDir);
|
|
4910
|
+
if (!checkpoint) {
|
|
4911
|
+
return 'No checkpoints yet. Use `trie_checkpoint action="save"` to create one.';
|
|
4912
|
+
}
|
|
4913
|
+
return `# Last Checkpoint
|
|
4914
|
+
|
|
4915
|
+
**ID:** ${checkpoint.id}
|
|
4916
|
+
**Time:** ${new Date(checkpoint.timestamp).toLocaleString()}
|
|
4917
|
+
${checkpoint.message ? `**Message:** ${checkpoint.message}` : ""}
|
|
4918
|
+
${checkpoint.notes ? `**Notes:** ${checkpoint.notes}` : ""}
|
|
4919
|
+
${checkpoint.files.length > 0 ? `**Files:** ${checkpoint.files.join(", ")}` : ""}
|
|
4920
|
+
**Created by:** ${checkpoint.createdBy}`;
|
|
4921
|
+
}
|
|
4922
|
+
default:
|
|
4923
|
+
return "Unknown action. Use: save, list, or last";
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4926
|
+
|
|
4927
|
+
// src/cli/dashboard/chat-tools.ts
|
|
4928
|
+
function textFromResult(result) {
|
|
4929
|
+
return result.content.map((c) => c.text).join("\n");
|
|
4930
|
+
}
|
|
4931
|
+
var CHAT_TOOLS = [
|
|
4932
|
+
{
|
|
4933
|
+
name: "trie_tell",
|
|
4934
|
+
description: "Record an incident or observation about the codebase. Use when the user reports a bug, crash, or notable event.",
|
|
4935
|
+
input_schema: {
|
|
4936
|
+
type: "object",
|
|
4937
|
+
properties: {
|
|
4938
|
+
description: { type: "string", description: "What happened \u2014 the incident or observation" }
|
|
4939
|
+
},
|
|
4940
|
+
required: ["description"]
|
|
4941
|
+
}
|
|
4942
|
+
},
|
|
4943
|
+
{
|
|
4944
|
+
name: "trie_feedback",
|
|
4945
|
+
description: 'Record thumbs-up or thumbs-down feedback. Use for "trie ok" (helpful=true) or "trie bad" (helpful=false).',
|
|
4946
|
+
input_schema: {
|
|
4947
|
+
type: "object",
|
|
4948
|
+
properties: {
|
|
4949
|
+
helpful: { type: "boolean", description: "true for positive, false for negative feedback" },
|
|
4950
|
+
target: { type: "string", description: "Optional file or item being rated" },
|
|
4951
|
+
note: { type: "string", description: "Optional explanation" }
|
|
4952
|
+
},
|
|
4953
|
+
required: ["helpful"]
|
|
4954
|
+
}
|
|
4955
|
+
},
|
|
4956
|
+
{
|
|
4957
|
+
name: "trie_check",
|
|
4958
|
+
description: "Run a risk check on files or the whole project. Returns risk assessment and potential issues.",
|
|
4959
|
+
input_schema: {
|
|
4960
|
+
type: "object",
|
|
4961
|
+
properties: {
|
|
4962
|
+
files: { type: "array", items: { type: "string" }, description: "Specific files to check (omit for full project)" },
|
|
4963
|
+
mode: { type: "string", enum: ["quick", "full", "offline"], description: "Check mode \u2014 defaults to quick" }
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4966
|
+
},
|
|
4967
|
+
{
|
|
4968
|
+
name: "trie_explain",
|
|
4969
|
+
description: "Explain code, an issue, a change, or a risk in the project.",
|
|
4970
|
+
input_schema: {
|
|
4971
|
+
type: "object",
|
|
4972
|
+
properties: {
|
|
4973
|
+
type: { type: "string", enum: ["code", "issue", "change", "risk"], description: "What to explain" },
|
|
4974
|
+
target: { type: "string", description: "File path, issue ID, or description of what to explain" }
|
|
4975
|
+
},
|
|
4976
|
+
required: ["type", "target"]
|
|
4977
|
+
}
|
|
4978
|
+
},
|
|
4979
|
+
{
|
|
4980
|
+
name: "trie_get_decisions",
|
|
4981
|
+
description: "Query the decision ledger \u2014 past architectural and coding decisions recorded by Trie.",
|
|
4982
|
+
input_schema: {
|
|
4983
|
+
type: "object",
|
|
4984
|
+
properties: {
|
|
4985
|
+
relatedTo: { type: "string", description: "Filter decisions related to a topic or file" },
|
|
4986
|
+
limit: { type: "number", description: "Max results (default 10)" }
|
|
4987
|
+
}
|
|
4988
|
+
}
|
|
4989
|
+
},
|
|
4990
|
+
{
|
|
4991
|
+
name: "trie_get_blockers",
|
|
4992
|
+
description: "Query active blockers \u2014 known problems preventing progress.",
|
|
4993
|
+
input_schema: {
|
|
4994
|
+
type: "object",
|
|
4995
|
+
properties: {
|
|
4996
|
+
limit: { type: "number", description: "Max results (default 10)" }
|
|
4997
|
+
}
|
|
4998
|
+
}
|
|
4999
|
+
},
|
|
5000
|
+
{
|
|
5001
|
+
name: "trie_query_context",
|
|
5002
|
+
description: "Natural-language search across the context graph \u2014 decisions, blockers, facts, and questions.",
|
|
5003
|
+
input_schema: {
|
|
5004
|
+
type: "object",
|
|
5005
|
+
properties: {
|
|
5006
|
+
query: { type: "string", description: "Natural language search query" },
|
|
5007
|
+
type: { type: "string", enum: ["decisions", "blockers", "facts", "questions", "all"], description: "Narrow to a specific category (default all)" },
|
|
5008
|
+
limit: { type: "number", description: "Max results (default 10)" }
|
|
5009
|
+
},
|
|
5010
|
+
required: ["query"]
|
|
5011
|
+
}
|
|
5012
|
+
},
|
|
5013
|
+
{
|
|
5014
|
+
name: "trie_checkpoint",
|
|
5015
|
+
description: "Save a work checkpoint, list recent checkpoints, or get the last one.",
|
|
5016
|
+
input_schema: {
|
|
5017
|
+
type: "object",
|
|
5018
|
+
properties: {
|
|
5019
|
+
action: { type: "string", enum: ["save", "list", "last"], description: "What to do" },
|
|
5020
|
+
message: { type: "string", description: "Checkpoint message (for save)" },
|
|
5021
|
+
notes: { type: "string", description: "Additional notes (for save)" },
|
|
5022
|
+
files: { type: "array", items: { type: "string" }, description: "Files to associate (for save)" }
|
|
5023
|
+
},
|
|
5024
|
+
required: ["action"]
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
];
|
|
5028
|
+
async function executeTool(name, input) {
|
|
5029
|
+
const directory = getWorkingDirectory(void 0, true);
|
|
5030
|
+
const withDir = { ...input, directory };
|
|
5031
|
+
switch (name) {
|
|
5032
|
+
case "trie_tell": {
|
|
5033
|
+
const tool = new TrieTellTool();
|
|
5034
|
+
const result = await tool.execute(withDir);
|
|
5035
|
+
return textFromResult(result);
|
|
5036
|
+
}
|
|
5037
|
+
case "trie_feedback": {
|
|
5038
|
+
const tool = new TrieFeedbackTool();
|
|
5039
|
+
const result = await tool.execute(withDir);
|
|
5040
|
+
return textFromResult(result);
|
|
5041
|
+
}
|
|
5042
|
+
case "trie_check": {
|
|
5043
|
+
const tool = new TrieCheckTool();
|
|
5044
|
+
const result = await tool.execute(withDir);
|
|
5045
|
+
return textFromResult(result);
|
|
5046
|
+
}
|
|
5047
|
+
case "trie_explain": {
|
|
5048
|
+
const tool = new TrieExplainTool();
|
|
5049
|
+
const result = await tool.execute(input);
|
|
5050
|
+
return textFromResult(result);
|
|
5051
|
+
}
|
|
5052
|
+
case "trie_get_decisions": {
|
|
5053
|
+
const tool = new TrieGetDecisionsTool();
|
|
5054
|
+
const result = await tool.execute(withDir);
|
|
5055
|
+
return textFromResult(result);
|
|
5056
|
+
}
|
|
5057
|
+
case "trie_get_blockers": {
|
|
5058
|
+
const tool = new TrieGetBlockersTool();
|
|
5059
|
+
const result = await tool.execute(withDir);
|
|
5060
|
+
return textFromResult(result);
|
|
5061
|
+
}
|
|
5062
|
+
case "trie_query_context": {
|
|
5063
|
+
const tool = new TrieQueryContextTool();
|
|
5064
|
+
const result = await tool.execute(withDir);
|
|
5065
|
+
return textFromResult(result);
|
|
5066
|
+
}
|
|
5067
|
+
case "trie_checkpoint": {
|
|
5068
|
+
const result = await handleCheckpointTool(input);
|
|
5069
|
+
return result;
|
|
5070
|
+
}
|
|
5071
|
+
default:
|
|
5072
|
+
return `Unknown tool: ${name}`;
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
|
|
5076
|
+
// src/cli/dashboard/views/ChatView.tsx
|
|
5077
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
5078
|
+
async function buildContext(workDir) {
|
|
5079
|
+
const parts = [];
|
|
5080
|
+
try {
|
|
5081
|
+
const storage = new TieredStorage(workDir);
|
|
5082
|
+
try {
|
|
5083
|
+
const decisions = await storage.queryDecisions({ limit: 10 });
|
|
5084
|
+
if (decisions.length > 0) {
|
|
5085
|
+
parts.push("Recent decisions:\n" + decisions.map(
|
|
5086
|
+
(d) => `- ${d.decision} (${d.when}${d.hash ? `, hash: ${d.hash.slice(0, 8)}` : ""})`
|
|
5087
|
+
).join("\n"));
|
|
5088
|
+
}
|
|
5089
|
+
} catch {
|
|
5090
|
+
}
|
|
5091
|
+
try {
|
|
5092
|
+
const blockers = await storage.queryBlockers({ limit: 5 });
|
|
5093
|
+
if (blockers.length > 0) {
|
|
5094
|
+
parts.push("Active blockers:\n" + blockers.map(
|
|
5095
|
+
(b) => `- ${b.blocker} [${b.impact}]`
|
|
5096
|
+
).join("\n"));
|
|
5097
|
+
}
|
|
5098
|
+
} catch {
|
|
5099
|
+
}
|
|
5100
|
+
} catch {
|
|
5101
|
+
}
|
|
5102
|
+
try {
|
|
5103
|
+
const graph = new ContextGraph(workDir);
|
|
5104
|
+
const snap = await graph.getSnapshot();
|
|
5105
|
+
const fileNodes = snap.nodes.filter((n) => n.type === "file");
|
|
5106
|
+
const changeNodes = snap.nodes.filter((n) => n.type === "change");
|
|
5107
|
+
const patternNodes = snap.nodes.filter((n) => n.type === "pattern");
|
|
5108
|
+
parts.push(`Context graph: ${snap.nodes.length} nodes, ${snap.edges.length} edges (${fileNodes.length} files, ${changeNodes.length} changes, ${patternNodes.length} patterns)`);
|
|
5109
|
+
if (fileNodes.length > 0) {
|
|
5110
|
+
const sorted = [...fileNodes].sort((a, b) => {
|
|
5111
|
+
const riskOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
5112
|
+
const ad = a.data, bd = b.data;
|
|
5113
|
+
return (riskOrder[ad.riskLevel] ?? 4) - (riskOrder[bd.riskLevel] ?? 4);
|
|
5114
|
+
});
|
|
5115
|
+
parts.push("Project files (by risk):\n" + sorted.slice(0, 15).map((n) => {
|
|
5116
|
+
const d = n.data;
|
|
5117
|
+
return `- ${d.path} (${d.riskLevel}, ${d.changeCount} changes${d.incidentCount > 0 ? `, ${d.incidentCount} incidents` : ""})`;
|
|
5118
|
+
}).join("\n"));
|
|
5119
|
+
}
|
|
5120
|
+
if (changeNodes.length > 0) {
|
|
5121
|
+
parts.push("Recent changes:\n" + changeNodes.slice(0, 5).map((n) => {
|
|
5122
|
+
const d = n.data;
|
|
5123
|
+
return `- ${d.message} (${d.timestamp}, ${d.files.length} files)`;
|
|
5124
|
+
}).join("\n"));
|
|
5125
|
+
}
|
|
5126
|
+
if (patternNodes.length > 0) {
|
|
5127
|
+
parts.push("Learned patterns:\n" + patternNodes.slice(0, 5).map((n) => {
|
|
5128
|
+
const d = n.data;
|
|
5129
|
+
return `- ${d.description} (${Math.round(d.confidence * 100)}%${d.isAntiPattern ? ", anti-pattern" : ""})`;
|
|
5130
|
+
}).join("\n"));
|
|
5131
|
+
}
|
|
5132
|
+
} catch {
|
|
5133
|
+
}
|
|
5134
|
+
return parts.length > 0 ? parts.join("\n\n") : "No context data available yet.";
|
|
5135
|
+
}
|
|
5136
|
+
function chatHistoryToMessages(history) {
|
|
5137
|
+
return history.map((m) => ({
|
|
5138
|
+
role: m.role,
|
|
5139
|
+
content: m.content
|
|
5140
|
+
}));
|
|
5141
|
+
}
|
|
5142
|
+
var SYSTEM_PROMPT = `You are Trie, a code guardian assistant embedded in a terminal TUI.
|
|
5143
|
+
You have tools to take actions on the user's codebase \u2014 use them when the user asks you to check files, record incidents, give feedback, query decisions, or save checkpoints.
|
|
5144
|
+
Answer concisely. Reference specific files, decisions, and patterns when relevant.
|
|
5145
|
+
When you use a tool, briefly summarize what you did and what the result was.`;
|
|
5146
|
+
function ChatView() {
|
|
5147
|
+
const { state, dispatch } = useDashboard();
|
|
5148
|
+
const { chatState } = state;
|
|
5149
|
+
const { messages, inputBuffer, loading } = chatState;
|
|
5150
|
+
const loadingRef = useRef(false);
|
|
5151
|
+
const sendMessage = useCallback5(async (question) => {
|
|
5152
|
+
if (loadingRef.current) return;
|
|
5153
|
+
loadingRef.current = true;
|
|
5154
|
+
dispatch({ type: "ADD_CHAT_MESSAGE", role: "user", content: question });
|
|
5155
|
+
dispatch({ type: "SET_CHAT_INPUT", buffer: "" });
|
|
5156
|
+
dispatch({ type: "SET_CHAT_LOADING", loading: true });
|
|
5157
|
+
try {
|
|
5158
|
+
const workDir = getWorkingDirectory(void 0, true);
|
|
5159
|
+
const contextBlock = await buildContext(workDir);
|
|
5160
|
+
const fullSystem = `${SYSTEM_PROMPT}
|
|
5161
|
+
|
|
5162
|
+
Project context:
|
|
5163
|
+
${contextBlock}`;
|
|
5164
|
+
const history = chatHistoryToMessages([
|
|
5165
|
+
...messages,
|
|
5166
|
+
{ role: "user", content: question, timestamp: Date.now() }
|
|
5167
|
+
]);
|
|
5168
|
+
const result = await runAIWithTools({
|
|
5169
|
+
systemPrompt: fullSystem,
|
|
5170
|
+
messages: history,
|
|
5171
|
+
tools: CHAT_TOOLS,
|
|
5172
|
+
executeTool,
|
|
5173
|
+
maxTokens: 4096,
|
|
5174
|
+
maxToolRounds: 5
|
|
5175
|
+
});
|
|
5176
|
+
if (result.success) {
|
|
5177
|
+
const action = {
|
|
5178
|
+
type: "ADD_CHAT_MESSAGE",
|
|
5179
|
+
role: "assistant",
|
|
5180
|
+
content: result.content
|
|
5181
|
+
};
|
|
5182
|
+
if (result.toolCalls && result.toolCalls.length > 0) action.toolCalls = result.toolCalls;
|
|
5183
|
+
dispatch(action);
|
|
5184
|
+
} else {
|
|
5185
|
+
dispatch({
|
|
5186
|
+
type: "ADD_CHAT_MESSAGE",
|
|
5187
|
+
role: "assistant",
|
|
5188
|
+
content: result.error || "Failed to get a response."
|
|
5189
|
+
});
|
|
5190
|
+
}
|
|
5191
|
+
} catch (err) {
|
|
5192
|
+
dispatch({
|
|
5193
|
+
type: "ADD_CHAT_MESSAGE",
|
|
5194
|
+
role: "assistant",
|
|
5195
|
+
content: `Error: ${err instanceof Error ? err.message : "unknown"}`
|
|
5196
|
+
});
|
|
5197
|
+
} finally {
|
|
5198
|
+
dispatch({ type: "SET_CHAT_LOADING", loading: false });
|
|
5199
|
+
loadingRef.current = false;
|
|
5200
|
+
}
|
|
5201
|
+
}, [dispatch, messages]);
|
|
5202
|
+
useInput7((input, key) => {
|
|
5203
|
+
if (loading) return;
|
|
5204
|
+
if (key.return && inputBuffer.trim().length > 0) {
|
|
5205
|
+
void sendMessage(inputBuffer.trim());
|
|
5206
|
+
} else if (key.escape) {
|
|
5207
|
+
dispatch({ type: "SET_CHAT_INPUT", buffer: "" });
|
|
5208
|
+
} else if (key.backspace || key.delete) {
|
|
5209
|
+
dispatch({ type: "SET_CHAT_INPUT", buffer: inputBuffer.slice(0, -1) });
|
|
5210
|
+
} else if (input && !key.ctrl && !key.meta) {
|
|
5211
|
+
dispatch({ type: "SET_CHAT_INPUT", buffer: inputBuffer + input });
|
|
5212
|
+
}
|
|
5213
|
+
});
|
|
5214
|
+
if (!isAIAvailable()) {
|
|
5215
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, children: [
|
|
5216
|
+
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
5217
|
+
/* @__PURE__ */ jsx12(Text11, { bold: true, children: "Chat" }),
|
|
5218
|
+
" ",
|
|
5219
|
+
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "Ask Trie anything" })
|
|
5220
|
+
] }),
|
|
5221
|
+
/* @__PURE__ */ jsx12(Text11, { children: " " }),
|
|
5222
|
+
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: " AI is not available." }),
|
|
5223
|
+
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
5224
|
+
" ",
|
|
5225
|
+
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "Press" }),
|
|
5226
|
+
" ",
|
|
5227
|
+
/* @__PURE__ */ jsx12(Text11, { bold: true, children: "s" }),
|
|
5228
|
+
" ",
|
|
5229
|
+
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "to open settings and add your Anthropic API key," })
|
|
5230
|
+
] }),
|
|
5231
|
+
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
5232
|
+
" ",
|
|
5233
|
+
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "or set" }),
|
|
5234
|
+
" ",
|
|
5235
|
+
/* @__PURE__ */ jsx12(Text11, { bold: true, children: "ANTHROPIC_API_KEY" }),
|
|
5236
|
+
" ",
|
|
5237
|
+
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "in your environment." })
|
|
5238
|
+
] })
|
|
5239
|
+
] });
|
|
5240
|
+
}
|
|
5241
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, children: [
|
|
5242
|
+
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
5243
|
+
/* @__PURE__ */ jsx12(Text11, { bold: true, children: "Chat" }),
|
|
5244
|
+
" ",
|
|
5245
|
+
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "Ask Trie anything" })
|
|
5246
|
+
] }),
|
|
5247
|
+
/* @__PURE__ */ jsx12(Text11, { children: " " }),
|
|
5248
|
+
messages.length === 0 && !loading && /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
5249
|
+
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: " Ask about your codebase, decisions, patterns, or risks." }),
|
|
5250
|
+
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: ' Trie can also take actions \u2014 try "check src/auth" or "record an incident".' }),
|
|
5251
|
+
/* @__PURE__ */ jsx12(Text11, { children: " " })
|
|
5252
|
+
] }),
|
|
5253
|
+
messages.map((msg, idx) => /* @__PURE__ */ jsx12(Box11, { flexDirection: "column", marginBottom: 1, children: msg.role === "user" ? /* @__PURE__ */ jsxs11(Text11, { children: [
|
|
5254
|
+
" ",
|
|
5255
|
+
/* @__PURE__ */ jsx12(Text11, { bold: true, color: "green", children: "You:" }),
|
|
5256
|
+
" ",
|
|
5257
|
+
msg.content
|
|
5258
|
+
] }) : /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
5259
|
+
msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx12(Box11, { flexDirection: "column", marginBottom: 0, children: msg.toolCalls.map((tc, ti) => /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
5260
|
+
" ",
|
|
5261
|
+
/* @__PURE__ */ jsxs11(Text11, { color: "yellow", children: [
|
|
5262
|
+
"[ran ",
|
|
5263
|
+
tc.name,
|
|
5264
|
+
"]"
|
|
5265
|
+
] }),
|
|
5266
|
+
" ",
|
|
5267
|
+
formatToolInput(tc.input)
|
|
5268
|
+
] }, ti)) }),
|
|
5269
|
+
msg.content.split("\n").map((line, li) => /* @__PURE__ */ jsxs11(Text11, { children: [
|
|
5270
|
+
li === 0 ? /* @__PURE__ */ jsx12(Text11, { bold: true, children: " Trie: " }) : /* @__PURE__ */ jsx12(Text11, { children: " " }),
|
|
5271
|
+
line
|
|
5272
|
+
] }, li))
|
|
5273
|
+
] }) }, idx)),
|
|
5274
|
+
loading && /* @__PURE__ */ jsxs11(Text11, { children: [
|
|
5275
|
+
" ",
|
|
5276
|
+
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "Thinking..." })
|
|
5277
|
+
] }),
|
|
5278
|
+
/* @__PURE__ */ jsx12(Box11, { flexGrow: 1 }),
|
|
5279
|
+
/* @__PURE__ */ jsx12(Box11, { borderStyle: "round", borderColor: "green", paddingX: 1, children: /* @__PURE__ */ jsxs11(Text11, { children: [
|
|
5280
|
+
inputBuffer || /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "Ask a question..." }),
|
|
5281
|
+
/* @__PURE__ */ jsx12(Text11, { bold: true, color: "green", children: "|" })
|
|
5282
|
+
] }) }),
|
|
5283
|
+
/* @__PURE__ */ jsx12(Text11, { dimColor: true, children: " enter send esc clear" })
|
|
5284
|
+
] });
|
|
5285
|
+
}
|
|
5286
|
+
function formatToolInput(input) {
|
|
5287
|
+
const parts = [];
|
|
5288
|
+
for (const [k, v] of Object.entries(input)) {
|
|
5289
|
+
if (k === "directory") continue;
|
|
5290
|
+
if (Array.isArray(v)) parts.push(`${k}: [${v.join(", ")}]`);
|
|
5291
|
+
else parts.push(`${k}: ${String(v)}`);
|
|
5292
|
+
}
|
|
5293
|
+
return parts.length > 0 ? parts.join(", ") : "";
|
|
2758
5294
|
}
|
|
2759
5295
|
|
|
2760
5296
|
// src/cli/dashboard/App.tsx
|
|
@@ -2764,15 +5300,15 @@ function DashboardApp({ onReady }) {
|
|
|
2764
5300
|
const { state, dispatch } = useDashboard();
|
|
2765
5301
|
const { exit } = useApp();
|
|
2766
5302
|
const [showConfig, setShowConfig] = useState3(false);
|
|
2767
|
-
const dispatchRef =
|
|
5303
|
+
const dispatchRef = useRef2(dispatch);
|
|
2768
5304
|
dispatchRef.current = dispatch;
|
|
2769
|
-
const stateRef =
|
|
5305
|
+
const stateRef = useRef2(state);
|
|
2770
5306
|
stateRef.current = state;
|
|
2771
5307
|
const configPath = join(getTrieDirectory(getWorkingDirectory(void 0, true)), "agent.json");
|
|
2772
5308
|
const loadConfig = useCallback6(async () => {
|
|
2773
|
-
if (!
|
|
5309
|
+
if (!existsSync2(configPath)) return;
|
|
2774
5310
|
try {
|
|
2775
|
-
const raw = await
|
|
5311
|
+
const raw = await readFile2(configPath, "utf-8");
|
|
2776
5312
|
const parsed = JSON.parse(raw);
|
|
2777
5313
|
dispatchRef.current({ type: "SET_AGENT_CONFIG", config: parsed });
|
|
2778
5314
|
} catch {
|
|
@@ -3024,8 +5560,20 @@ var InteractiveDashboard = class {
|
|
|
3024
5560
|
|
|
3025
5561
|
export {
|
|
3026
5562
|
TrieScanTool,
|
|
5563
|
+
getPrompt,
|
|
5564
|
+
getSystemPrompt,
|
|
5565
|
+
TrieExplainTool,
|
|
3027
5566
|
StreamingManager,
|
|
3028
5567
|
getOutputManager,
|
|
5568
|
+
ExtractionPipeline,
|
|
5569
|
+
TrieTellTool,
|
|
5570
|
+
TrieFeedbackTool,
|
|
5571
|
+
TrieCheckTool,
|
|
5572
|
+
TrieGetDecisionsTool,
|
|
5573
|
+
TrieGetBlockersTool,
|
|
5574
|
+
TrieGetRelatedDecisionsTool,
|
|
5575
|
+
TrieQueryContextTool,
|
|
5576
|
+
handleCheckpointTool,
|
|
3029
5577
|
InteractiveDashboard
|
|
3030
5578
|
};
|
|
3031
|
-
//# sourceMappingURL=chunk-
|
|
5579
|
+
//# sourceMappingURL=chunk-HGEKZ2VS.js.map
|