@triedotdev/mcp 1.0.169 → 1.0.170
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -545
- package/dist/chunk-2YXOBNKW.js +619 -0
- package/dist/chunk-2YXOBNKW.js.map +1 -0
- package/dist/chunk-QR64Y5TI.js +363 -0
- package/dist/chunk-QR64Y5TI.js.map +1 -0
- package/dist/cli/main.d.ts +0 -15
- package/dist/cli/main.js +356 -3098
- package/dist/cli/main.js.map +1 -1
- package/dist/index.js +2 -34
- package/dist/index.js.map +1 -1
- package/dist/server/mcp-server.js +2 -34
- package/package.json +8 -31
- package/dist/autonomy-config-FSERX3O3.js +0 -30
- package/dist/autonomy-config-FSERX3O3.js.map +0 -1
- package/dist/chat-store-JNGNTDSN.js +0 -15
- package/dist/chat-store-JNGNTDSN.js.map +0 -1
- package/dist/chunk-2HF65EHQ.js +0 -311
- package/dist/chunk-2HF65EHQ.js.map +0 -1
- package/dist/chunk-3XR6WVAW.js +0 -4011
- package/dist/chunk-3XR6WVAW.js.map +0 -1
- package/dist/chunk-43X6JBEM.js +0 -36
- package/dist/chunk-43X6JBEM.js.map +0 -1
- package/dist/chunk-6NLHFIYA.js +0 -344
- package/dist/chunk-6NLHFIYA.js.map +0 -1
- package/dist/chunk-7IO4YUI3.js +0 -1827
- package/dist/chunk-7IO4YUI3.js.map +0 -1
- package/dist/chunk-AHD2CBQ7.js +0 -846
- package/dist/chunk-AHD2CBQ7.js.map +0 -1
- package/dist/chunk-BUTOP5EB.js +0 -931
- package/dist/chunk-BUTOP5EB.js.map +0 -1
- package/dist/chunk-DGUM43GV.js +0 -11
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-EFWVF6TI.js +0 -267
- package/dist/chunk-EFWVF6TI.js.map +0 -1
- package/dist/chunk-F6WFNUAY.js +0 -216
- package/dist/chunk-F6WFNUAY.js.map +0 -1
- package/dist/chunk-FBNURWRY.js +0 -662
- package/dist/chunk-FBNURWRY.js.map +0 -1
- package/dist/chunk-FQ45QP5A.js +0 -361
- package/dist/chunk-FQ45QP5A.js.map +0 -1
- package/dist/chunk-FVRO5RN3.js +0 -1306
- package/dist/chunk-FVRO5RN3.js.map +0 -1
- package/dist/chunk-G2TGF6TR.js +0 -573
- package/dist/chunk-G2TGF6TR.js.map +0 -1
- package/dist/chunk-G3I7SZLW.js +0 -354
- package/dist/chunk-G3I7SZLW.js.map +0 -1
- package/dist/chunk-GTKYBOXL.js +0 -700
- package/dist/chunk-GTKYBOXL.js.map +0 -1
- package/dist/chunk-HVCDY3AK.js +0 -850
- package/dist/chunk-HVCDY3AK.js.map +0 -1
- package/dist/chunk-I2O5OYQT.js +0 -727
- package/dist/chunk-I2O5OYQT.js.map +0 -1
- package/dist/chunk-JVMBCWKS.js +0 -348
- package/dist/chunk-JVMBCWKS.js.map +0 -1
- package/dist/chunk-KCUOWRPX.js +0 -816
- package/dist/chunk-KCUOWRPX.js.map +0 -1
- package/dist/chunk-KDHN2ZQE.js +0 -313
- package/dist/chunk-KDHN2ZQE.js.map +0 -1
- package/dist/chunk-ME2OERF5.js +0 -345
- package/dist/chunk-ME2OERF5.js.map +0 -1
- package/dist/chunk-OBQ74FOU.js +0 -27
- package/dist/chunk-OBQ74FOU.js.map +0 -1
- package/dist/chunk-Q5EKA5YA.js +0 -254
- package/dist/chunk-Q5EKA5YA.js.map +0 -1
- package/dist/chunk-Q63FFI6D.js +0 -132
- package/dist/chunk-Q63FFI6D.js.map +0 -1
- package/dist/chunk-SASNMSB5.js +0 -12597
- package/dist/chunk-SASNMSB5.js.map +0 -1
- package/dist/chunk-T63OHG4Q.js +0 -440
- package/dist/chunk-T63OHG4Q.js.map +0 -1
- package/dist/chunk-TN5WEKWI.js +0 -173
- package/dist/chunk-TN5WEKWI.js.map +0 -1
- package/dist/chunk-VUL52BQL.js +0 -402
- package/dist/chunk-VUL52BQL.js.map +0 -1
- package/dist/chunk-VVITXIHN.js +0 -189
- package/dist/chunk-VVITXIHN.js.map +0 -1
- package/dist/chunk-WCN7S3EI.js +0 -14
- package/dist/chunk-WCN7S3EI.js.map +0 -1
- package/dist/chunk-XPZZFPBZ.js +0 -491
- package/dist/chunk-XPZZFPBZ.js.map +0 -1
- package/dist/chunk-ZJF5FTBX.js +0 -1396
- package/dist/chunk-ZJF5FTBX.js.map +0 -1
- package/dist/chunk-ZV2K6M7T.js +0 -74
- package/dist/chunk-ZV2K6M7T.js.map +0 -1
- package/dist/cli/create-agent.d.ts +0 -1
- package/dist/cli/create-agent.js +0 -1050
- package/dist/cli/create-agent.js.map +0 -1
- package/dist/cli/yolo-daemon.d.ts +0 -1
- package/dist/cli/yolo-daemon.js +0 -421
- package/dist/cli/yolo-daemon.js.map +0 -1
- package/dist/client-NJPZE5JT.js +0 -28
- package/dist/client-NJPZE5JT.js.map +0 -1
- package/dist/codebase-index-VAPF32XX.js +0 -12
- package/dist/codebase-index-VAPF32XX.js.map +0 -1
- package/dist/fast-analyzer-3GCCZMLK.js +0 -216
- package/dist/fast-analyzer-3GCCZMLK.js.map +0 -1
- package/dist/git-EO5SRFMN.js +0 -28
- package/dist/git-EO5SRFMN.js.map +0 -1
- package/dist/github-ingester-ZOKK6GRS.js +0 -11
- package/dist/github-ingester-ZOKK6GRS.js.map +0 -1
- package/dist/goal-manager-QUKX2W6C.js +0 -25
- package/dist/goal-manager-QUKX2W6C.js.map +0 -1
- package/dist/goal-validator-2SFSKKVU.js +0 -24
- package/dist/goal-validator-2SFSKKVU.js.map +0 -1
- package/dist/graph-B3NA4S7I.js +0 -10
- package/dist/graph-B3NA4S7I.js.map +0 -1
- package/dist/hypothesis-KCPBR652.js +0 -23
- package/dist/hypothesis-KCPBR652.js.map +0 -1
- package/dist/incident-index-EFNUSGWL.js +0 -11
- package/dist/incident-index-EFNUSGWL.js.map +0 -1
- package/dist/insight-store-EC4PLSAW.js +0 -22
- package/dist/insight-store-EC4PLSAW.js.map +0 -1
- package/dist/issue-store-YAXTNRRY.js +0 -36
- package/dist/issue-store-YAXTNRRY.js.map +0 -1
- package/dist/ledger-TWZTGDFA.js +0 -58
- package/dist/ledger-TWZTGDFA.js.map +0 -1
- package/dist/linear-ingester-XXPAZZRW.js +0 -11
- package/dist/linear-ingester-XXPAZZRW.js.map +0 -1
- package/dist/output-manager-RVJ37XKA.js +0 -13
- package/dist/output-manager-RVJ37XKA.js.map +0 -1
- package/dist/parse-goal-violation-SACGFG3C.js +0 -8
- package/dist/parse-goal-violation-SACGFG3C.js.map +0 -1
- package/dist/pattern-discovery-F7LU5K6E.js +0 -8
- package/dist/pattern-discovery-F7LU5K6E.js.map +0 -1
- package/dist/progress-SRQ2V3BP.js +0 -18
- package/dist/progress-SRQ2V3BP.js.map +0 -1
- package/dist/project-state-AHPA77SM.js +0 -28
- package/dist/project-state-AHPA77SM.js.map +0 -1
- package/dist/sync-M2FSWPBC.js +0 -12
- package/dist/sync-M2FSWPBC.js.map +0 -1
- package/dist/terminal-spawn-5YXDMUCF.js +0 -157
- package/dist/terminal-spawn-5YXDMUCF.js.map +0 -1
- package/dist/tiered-storage-DYNC5CQ6.js +0 -13
- package/dist/tiered-storage-DYNC5CQ6.js.map +0 -1
- package/dist/trie-agent-I3HAHY2G.js +0 -26
- package/dist/trie-agent-I3HAHY2G.js.map +0 -1
- package/dist/ui/chat.html +0 -1014
- package/dist/ui/goals.html +0 -967
- package/dist/ui/hypotheses.html +0 -1011
- package/dist/ui/ledger.html +0 -954
- package/dist/ui/nudges.html +0 -995
- package/dist/vibe-code-signatures-5ZULYP3D.js +0 -987
- package/dist/vibe-code-signatures-5ZULYP3D.js.map +0 -1
- package/dist/vulnerability-signatures-2URZSXAQ.js +0 -983
- package/dist/vulnerability-signatures-2URZSXAQ.js.map +0 -1
package/dist/chunk-Q5EKA5YA.js
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Trie
|
|
3
|
-
} from "./chunk-6NLHFIYA.js";
|
|
4
|
-
import {
|
|
5
|
-
atomicWriteJSON
|
|
6
|
-
} from "./chunk-43X6JBEM.js";
|
|
7
|
-
import {
|
|
8
|
-
getTrieDirectory
|
|
9
|
-
} from "./chunk-VVITXIHN.js";
|
|
10
|
-
|
|
11
|
-
// src/context/codebase-index.ts
|
|
12
|
-
import { readFile, stat } from "fs/promises";
|
|
13
|
-
import { createHash } from "crypto";
|
|
14
|
-
import { join } from "path";
|
|
15
|
-
import { existsSync, readFileSync } from "fs";
|
|
16
|
-
var CodebaseIndex = class {
|
|
17
|
-
trie = new Trie();
|
|
18
|
-
projectPath;
|
|
19
|
-
indexPath;
|
|
20
|
-
lastUpdated = null;
|
|
21
|
-
constructor(projectPath) {
|
|
22
|
-
this.projectPath = projectPath;
|
|
23
|
-
this.indexPath = join(getTrieDirectory(projectPath), "codebase-index.json");
|
|
24
|
-
this.load();
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Load index from disk if it exists
|
|
28
|
-
*/
|
|
29
|
-
load() {
|
|
30
|
-
if (!existsSync(this.indexPath)) return;
|
|
31
|
-
try {
|
|
32
|
-
const raw = JSON.parse(readFileSync(this.indexPath, "utf-8"));
|
|
33
|
-
if (raw.trie) {
|
|
34
|
-
this.trie = Trie.fromJSON(raw.trie);
|
|
35
|
-
}
|
|
36
|
-
if (raw.lastUpdated) {
|
|
37
|
-
this.lastUpdated = raw.lastUpdated;
|
|
38
|
-
}
|
|
39
|
-
} catch (error) {
|
|
40
|
-
console.error("Failed to load codebase index:", error);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Save index to disk
|
|
45
|
-
*/
|
|
46
|
-
async save() {
|
|
47
|
-
this.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
48
|
-
const data = {
|
|
49
|
-
version: 1,
|
|
50
|
-
lastUpdated: this.lastUpdated,
|
|
51
|
-
fileCount: this.trie.getWithPrefix("").length,
|
|
52
|
-
trie: this.trie.toJSON()
|
|
53
|
-
};
|
|
54
|
-
await atomicWriteJSON(this.indexPath, data);
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Add or update a file in the index
|
|
58
|
-
* Returns null if the file doesn't exist or can't be read
|
|
59
|
-
*/
|
|
60
|
-
async indexFile(filePath) {
|
|
61
|
-
if (filePath.startsWith("/")) {
|
|
62
|
-
if (!filePath.toLowerCase().startsWith(this.projectPath.toLowerCase())) {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
const absolutePath = filePath.startsWith("/") ? filePath : join(this.projectPath, filePath);
|
|
67
|
-
try {
|
|
68
|
-
const content = await readFile(absolutePath, "utf-8");
|
|
69
|
-
const stats = await stat(absolutePath);
|
|
70
|
-
const hash = createHash("sha256").update(content).digest("hex");
|
|
71
|
-
const metadata = {
|
|
72
|
-
path: filePath,
|
|
73
|
-
hash,
|
|
74
|
-
size: stats.size,
|
|
75
|
-
lastModified: stats.mtimeMs,
|
|
76
|
-
type: filePath.split(".").pop() || ""
|
|
77
|
-
};
|
|
78
|
-
const existing = this.trie.search(filePath);
|
|
79
|
-
if (existing.found && existing.value) {
|
|
80
|
-
if (existing.value.hash === hash) {
|
|
81
|
-
if (existing.value.violations) metadata.violations = existing.value.violations;
|
|
82
|
-
if (existing.value.lastScanned != null) metadata.lastScanned = existing.value.lastScanned;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
this.trie.insert(filePath, metadata);
|
|
86
|
-
return metadata;
|
|
87
|
-
} catch (error) {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Get file metadata from index
|
|
93
|
-
*/
|
|
94
|
-
getFile(filePath) {
|
|
95
|
-
const result = this.trie.search(filePath);
|
|
96
|
-
return result.found && result.value ? result.value : null;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Check if file has changed since last index
|
|
100
|
-
*/
|
|
101
|
-
async hasChanged(filePath) {
|
|
102
|
-
if (filePath.startsWith("/") && !filePath.toLowerCase().startsWith(this.projectPath.toLowerCase())) {
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
const metadata = this.getFile(filePath);
|
|
106
|
-
if (!metadata) return true;
|
|
107
|
-
const absolutePath = filePath.startsWith("/") ? filePath : join(this.projectPath, filePath);
|
|
108
|
-
try {
|
|
109
|
-
const stats = await stat(absolutePath);
|
|
110
|
-
if (stats.mtimeMs !== metadata.lastModified) return true;
|
|
111
|
-
const content = await readFile(absolutePath, "utf-8");
|
|
112
|
-
const hash = createHash("sha256").update(content).digest("hex");
|
|
113
|
-
return hash !== metadata.hash;
|
|
114
|
-
} catch {
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Get files in a directory
|
|
120
|
-
*/
|
|
121
|
-
getDirectoryFiles(directoryPath) {
|
|
122
|
-
const prefix = directoryPath.endsWith("/") ? directoryPath : `${directoryPath}/`;
|
|
123
|
-
const matches = this.trie.getWithPrefix(prefix);
|
|
124
|
-
return matches.map((m) => m.value).filter((v) => v !== null);
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Get files that match a pattern (uses Trie prefix matching)
|
|
128
|
-
*/
|
|
129
|
-
getFilesWithPrefix(prefix) {
|
|
130
|
-
const matches = this.trie.getWithPrefix(prefix);
|
|
131
|
-
return matches.map((m) => m.value).filter((v) => v !== null);
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Get files by type (extension)
|
|
135
|
-
*/
|
|
136
|
-
getFilesByType(type) {
|
|
137
|
-
const allFiles = this.trie.getWithPrefix("");
|
|
138
|
-
return allFiles.map((m) => m.value).filter((v) => v != null && v.type === type);
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Record goal violation scan result for a file
|
|
142
|
-
*/
|
|
143
|
-
recordViolation(filePath, goalId, goalDescription, found, details, confidence) {
|
|
144
|
-
const metadata = this.getFile(filePath);
|
|
145
|
-
if (!metadata) return;
|
|
146
|
-
metadata.lastScanned = Date.now();
|
|
147
|
-
if (!metadata.violations) metadata.violations = [];
|
|
148
|
-
metadata.violations = metadata.violations.filter((v) => v.goalId !== goalId);
|
|
149
|
-
const entry = {
|
|
150
|
-
goalId,
|
|
151
|
-
goalDescription,
|
|
152
|
-
found,
|
|
153
|
-
timestamp: Date.now()
|
|
154
|
-
};
|
|
155
|
-
if (details !== void 0) entry.details = details;
|
|
156
|
-
if (confidence !== void 0) entry.confidence = confidence;
|
|
157
|
-
metadata.violations.push(entry);
|
|
158
|
-
this.trie.insert(filePath, metadata);
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Get cached violation results for a file
|
|
162
|
-
*/
|
|
163
|
-
getCachedViolations(filePath, goalId) {
|
|
164
|
-
const metadata = this.getFile(filePath);
|
|
165
|
-
if (!metadata || !metadata.violations) return void 0;
|
|
166
|
-
if (goalId) {
|
|
167
|
-
return metadata.violations.filter((v) => v.goalId === goalId);
|
|
168
|
-
}
|
|
169
|
-
return metadata.violations;
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Check if the index is empty (needs initial indexing)
|
|
173
|
-
*/
|
|
174
|
-
isEmpty() {
|
|
175
|
-
return this.trie.getWithPrefix("").length === 0;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Get statistics about the index
|
|
179
|
-
*/
|
|
180
|
-
getStats() {
|
|
181
|
-
const allFiles = this.trie.getWithPrefix("").map((m) => m.value).filter((v) => v !== null);
|
|
182
|
-
const stats = {
|
|
183
|
-
totalFiles: allFiles.length,
|
|
184
|
-
totalSize: allFiles.reduce((sum, f) => sum + f.size, 0),
|
|
185
|
-
filesByType: {},
|
|
186
|
-
scannedFiles: allFiles.filter((f) => f.lastScanned).length,
|
|
187
|
-
filesWithViolations: allFiles.filter((f) => f.violations && f.violations.some((v) => v.found)).length,
|
|
188
|
-
lastUpdated: this.lastUpdated
|
|
189
|
-
};
|
|
190
|
-
for (const file of allFiles) {
|
|
191
|
-
stats.filesByType[file.type] = (stats.filesByType[file.type] || 0) + 1;
|
|
192
|
-
}
|
|
193
|
-
return stats;
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Clear stale cached results (older than maxAgeMs)
|
|
197
|
-
*/
|
|
198
|
-
clearStaleCache(maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
|
|
199
|
-
const allFiles = this.trie.getWithPrefix("").map((m) => m.value).filter((v) => v !== null);
|
|
200
|
-
const cutoff = Date.now() - maxAgeMs;
|
|
201
|
-
let cleared = 0;
|
|
202
|
-
for (const file of allFiles) {
|
|
203
|
-
if (file.violations) {
|
|
204
|
-
const before = file.violations.length;
|
|
205
|
-
file.violations = file.violations.filter((v) => v.timestamp > cutoff);
|
|
206
|
-
cleared += before - file.violations.length;
|
|
207
|
-
if (file.violations.length === 0) {
|
|
208
|
-
delete file.lastScanned;
|
|
209
|
-
}
|
|
210
|
-
this.trie.insert(file.path, file);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return cleared;
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Get file count
|
|
217
|
-
*/
|
|
218
|
-
getFileCount() {
|
|
219
|
-
return this.trie.getWithPrefix("").length;
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Get approximate cache size in bytes
|
|
223
|
-
*/
|
|
224
|
-
getCacheSize() {
|
|
225
|
-
const allFiles = this.trie.getWithPrefix("").map((m) => m.value).filter((v) => v !== null);
|
|
226
|
-
return allFiles.reduce((sum, file) => {
|
|
227
|
-
const violationSize = file.violations ? file.violations.reduce((vSum, v) => vSum + (v.details?.length || 50), 0) : 0;
|
|
228
|
-
return sum + file.path.length + violationSize;
|
|
229
|
-
}, 0);
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Get last updated timestamp
|
|
233
|
-
*/
|
|
234
|
-
getLastUpdated() {
|
|
235
|
-
return this.lastUpdated;
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Set cached violations for a file and goal (used by fast analyzer)
|
|
239
|
-
*/
|
|
240
|
-
setCachedViolations(filePath, goalId, violations) {
|
|
241
|
-
const metadata = this.getFile(filePath);
|
|
242
|
-
if (!metadata) return;
|
|
243
|
-
metadata.lastScanned = Date.now();
|
|
244
|
-
if (!metadata.violations) metadata.violations = [];
|
|
245
|
-
metadata.violations = metadata.violations.filter((v) => v.goalId !== goalId);
|
|
246
|
-
metadata.violations.push(...violations);
|
|
247
|
-
this.trie.insert(filePath, metadata);
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
export {
|
|
252
|
-
CodebaseIndex
|
|
253
|
-
};
|
|
254
|
-
//# sourceMappingURL=chunk-Q5EKA5YA.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/context/codebase-index.ts"],"sourcesContent":["/**\n * Codebase Index - Fast file indexing for goal violation detection\n * \n * Uses Trie structure for efficient file lookup and caching to avoid\n * re-scanning unchanged files.\n * \n * For large codebases, this dramatically improves goal check performance:\n * - Indexes file hashes to detect changes\n * - Caches AI analysis results\n * - Uses Trie for O(m) lookups where m = path length\n * - Stores file metadata (size, type, last modified, etc.)\n */\n\nimport { readFile, stat } from 'fs/promises';\nimport { createHash } from 'crypto';\nimport { join } from 'path';\nimport { existsSync, readFileSync } from 'fs';\nimport { Trie } from '../trie/trie.js';\nimport { getTrieDirectory } from '../utils/workspace.js';\nimport { atomicWriteJSON } from '../utils/atomic-write.js';\n\nexport interface FileMetadata {\n path: string;\n hash: string; // SHA-256 of content\n size: number;\n lastModified: number;\n type: string; // Extension\n lastScanned?: number; // Timestamp of last AI scan\n violations?: {\n goalId: string;\n goalDescription: string;\n found: boolean;\n details?: string;\n confidence?: number;\n timestamp: number;\n }[];\n}\n\nexport interface CodebaseIndexData {\n version: number;\n lastUpdated: string;\n fileCount: number;\n trie: any; // Serialized Trie\n}\n\nexport class CodebaseIndex {\n private trie: Trie<FileMetadata> = new Trie();\n private projectPath: string;\n private indexPath: string;\n private lastUpdated: string | null = null;\n\n constructor(projectPath: string) {\n this.projectPath = projectPath;\n this.indexPath = join(getTrieDirectory(projectPath), 'codebase-index.json');\n this.load();\n }\n\n /**\n * Load index from disk if it exists\n */\n private load(): void {\n if (!existsSync(this.indexPath)) return;\n\n try {\n const raw = JSON.parse(readFileSync(this.indexPath, 'utf-8'));\n if (raw.trie) {\n this.trie = Trie.fromJSON<FileMetadata>(raw.trie);\n }\n if (raw.lastUpdated) {\n this.lastUpdated = raw.lastUpdated;\n }\n } catch (error) {\n console.error('Failed to load codebase index:', error);\n }\n }\n\n /**\n * Save index to disk\n */\n async save(): Promise<void> {\n this.lastUpdated = new Date().toISOString();\n const data: CodebaseIndexData = {\n version: 1,\n lastUpdated: this.lastUpdated,\n fileCount: this.trie.getWithPrefix('').length,\n trie: this.trie.toJSON(),\n };\n\n await atomicWriteJSON(this.indexPath, data);\n }\n\n /**\n * Add or update a file in the index\n * Returns null if the file doesn't exist or can't be read\n */\n async indexFile(filePath: string): Promise<FileMetadata | null> {\n // Safety check: if filePath is already absolute and doesn't start with projectPath,\n // this is likely a bug (stale cache entry from different project)\n if (filePath.startsWith('/')) {\n // Already absolute - check if it's within our project\n if (!filePath.toLowerCase().startsWith(this.projectPath.toLowerCase())) {\n return null; // Wrong project, skip silently\n }\n // It's absolute but within our project - use as-is\n }\n \n // Build absolute path: if filePath is relative, join with projectPath\n // If filePath is absolute (and within project), use it directly\n const absolutePath = filePath.startsWith('/') ? filePath : join(this.projectPath, filePath);\n \n try {\n const content = await readFile(absolutePath, 'utf-8');\n const stats = await stat(absolutePath);\n const hash = createHash('sha256').update(content).digest('hex');\n\n const metadata: FileMetadata = {\n path: filePath,\n hash,\n size: stats.size,\n lastModified: stats.mtimeMs,\n type: filePath.split('.').pop() || '',\n };\n\n // Check if file exists in index\n const existing = this.trie.search(filePath);\n if (existing.found && existing.value) {\n // File exists - preserve violations if hash matches\n if (existing.value.hash === hash) {\n if (existing.value.violations) metadata.violations = existing.value.violations;\n if (existing.value.lastScanned != null) metadata.lastScanned = existing.value.lastScanned;\n }\n }\n\n this.trie.insert(filePath, metadata);\n return metadata;\n } catch (error) {\n // File doesn't exist, was deleted, or can't be read - return null\n return null;\n }\n }\n\n /**\n * Get file metadata from index\n */\n getFile(filePath: string): FileMetadata | null {\n const result = this.trie.search(filePath);\n return result.found && result.value ? result.value : null;\n }\n\n /**\n * Check if file has changed since last index\n */\n async hasChanged(filePath: string): Promise<boolean> {\n // Safety check for absolute paths from wrong project\n if (filePath.startsWith('/') && !filePath.toLowerCase().startsWith(this.projectPath.toLowerCase())) {\n return true; // Wrong project, treat as changed (will be skipped later)\n }\n \n const metadata = this.getFile(filePath);\n if (!metadata) return true; // Not indexed = changed\n\n // Build absolute path correctly\n const absolutePath = filePath.startsWith('/') ? filePath : join(this.projectPath, filePath);\n try {\n const stats = await stat(absolutePath);\n if (stats.mtimeMs !== metadata.lastModified) return true;\n\n // Double-check with hash for certainty\n const content = await readFile(absolutePath, 'utf-8');\n const hash = createHash('sha256').update(content).digest('hex');\n return hash !== metadata.hash;\n } catch {\n return true; // Error = assume changed\n }\n }\n\n /**\n * Get files in a directory\n */\n getDirectoryFiles(directoryPath: string): FileMetadata[] {\n const prefix = directoryPath.endsWith('/') ? directoryPath : `${directoryPath}/`;\n const matches = this.trie.getWithPrefix(prefix);\n return matches.map(m => m.value).filter((v): v is FileMetadata => v !== null);\n }\n\n /**\n * Get files that match a pattern (uses Trie prefix matching)\n */\n getFilesWithPrefix(prefix: string): FileMetadata[] {\n const matches = this.trie.getWithPrefix(prefix);\n return matches.map(m => m.value).filter((v): v is FileMetadata => v !== null);\n }\n\n /**\n * Get files by type (extension)\n */\n getFilesByType(type: string): FileMetadata[] {\n const allFiles = this.trie.getWithPrefix('');\n return allFiles\n .map(m => m.value)\n .filter((v): v is FileMetadata => v != null && v.type === type);\n }\n\n /**\n * Record goal violation scan result for a file\n */\n recordViolation(\n filePath: string,\n goalId: string,\n goalDescription: string,\n found: boolean,\n details?: string,\n confidence?: number\n ): void {\n const metadata = this.getFile(filePath);\n if (!metadata) return;\n\n metadata.lastScanned = Date.now();\n if (!metadata.violations) metadata.violations = [];\n\n // Remove existing violation for this goal\n metadata.violations = metadata.violations.filter(v => v.goalId !== goalId);\n\n // Add new violation result\n const entry: { goalId: string; goalDescription: string; found: boolean; timestamp: number; details?: string; confidence?: number } = {\n goalId,\n goalDescription,\n found,\n timestamp: Date.now(),\n };\n if (details !== undefined) entry.details = details;\n if (confidence !== undefined) entry.confidence = confidence;\n metadata.violations.push(entry);\n\n this.trie.insert(filePath, metadata);\n }\n\n /**\n * Get cached violation results for a file\n */\n getCachedViolations(filePath: string, goalId?: string): FileMetadata['violations'] {\n const metadata = this.getFile(filePath);\n if (!metadata || !metadata.violations) return undefined;\n\n if (goalId) {\n return metadata.violations.filter(v => v.goalId === goalId);\n }\n\n return metadata.violations;\n }\n\n /**\n * Check if the index is empty (needs initial indexing)\n */\n isEmpty(): boolean {\n return this.trie.getWithPrefix('').length === 0;\n }\n\n /**\n * Get statistics about the index\n */\n getStats(): {\n totalFiles: number;\n totalSize: number;\n filesByType: Record<string, number>;\n scannedFiles: number;\n filesWithViolations: number;\n lastUpdated: string | null;\n } {\n const allFiles = this.trie.getWithPrefix('').map(m => m.value).filter((v): v is FileMetadata => v !== null);\n\n const stats = {\n totalFiles: allFiles.length,\n totalSize: allFiles.reduce((sum, f) => sum + f.size, 0),\n filesByType: {} as Record<string, number>,\n scannedFiles: allFiles.filter(f => f.lastScanned).length,\n filesWithViolations: allFiles.filter(f => f.violations && f.violations.some(v => v.found)).length,\n lastUpdated: this.lastUpdated,\n };\n\n for (const file of allFiles) {\n stats.filesByType[file.type] = (stats.filesByType[file.type] || 0) + 1;\n }\n\n return stats;\n }\n\n /**\n * Clear stale cached results (older than maxAgeMs)\n */\n clearStaleCache(maxAgeMs: number = 7 * 24 * 60 * 60 * 1000): number {\n const allFiles = this.trie.getWithPrefix('').map(m => m.value).filter((v): v is FileMetadata => v !== null);\n const cutoff = Date.now() - maxAgeMs;\n let cleared = 0;\n\n for (const file of allFiles) {\n if (file.violations) {\n const before = file.violations.length;\n file.violations = file.violations.filter(v => v.timestamp > cutoff);\n cleared += before - file.violations.length;\n if (file.violations.length === 0) {\n delete file.lastScanned;\n }\n this.trie.insert(file.path, file);\n }\n }\n\n return cleared;\n }\n\n /**\n * Get file count\n */\n getFileCount(): number {\n return this.trie.getWithPrefix('').length;\n }\n\n /**\n * Get approximate cache size in bytes\n */\n getCacheSize(): number {\n const allFiles = this.trie.getWithPrefix('').map(m => m.value).filter((v): v is FileMetadata => v !== null);\n return allFiles.reduce((sum, file) => {\n const violationSize = file.violations\n ? file.violations.reduce((vSum, v) => vSum + (v.details?.length || 50), 0)\n : 0;\n return sum + file.path.length + violationSize;\n }, 0);\n }\n\n /**\n * Get last updated timestamp\n */\n getLastUpdated(): string | null {\n return this.lastUpdated;\n }\n\n /**\n * Set cached violations for a file and goal (used by fast analyzer)\n */\n setCachedViolations(\n filePath: string,\n goalId: string,\n violations: Array<{\n goalId: string;\n goalDescription: string;\n found: boolean;\n details?: string;\n confidence?: number;\n timestamp: number;\n }>\n ): void {\n const metadata = this.getFile(filePath);\n if (!metadata) return;\n\n metadata.lastScanned = Date.now();\n if (!metadata.violations) metadata.violations = [];\n\n // Remove existing violations for this goal\n metadata.violations = metadata.violations.filter(v => v.goalId !== goalId);\n\n // Add new violations\n metadata.violations.push(...violations);\n\n this.trie.insert(filePath, metadata);\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-Q63FFI6D.js
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
loadConfig
|
|
3
|
-
} from "./chunk-XPZZFPBZ.js";
|
|
4
|
-
|
|
5
|
-
// src/ingest/linear-ingester.ts
|
|
6
|
-
import path from "path";
|
|
7
|
-
var LinearIngester = class {
|
|
8
|
-
graph;
|
|
9
|
-
constructor(_projectPath, graph) {
|
|
10
|
-
this.graph = graph;
|
|
11
|
-
}
|
|
12
|
-
async syncTickets() {
|
|
13
|
-
const apiKey = await this.getApiKey();
|
|
14
|
-
if (!apiKey) {
|
|
15
|
-
console.warn('Linear API key not found. Run "trie linear auth <key>" to enable ticket sync.');
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
const tickets = await this.fetchActiveTickets(apiKey);
|
|
19
|
-
for (const ticket of tickets) {
|
|
20
|
-
await this.ingestTicket(ticket);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
async getApiKey() {
|
|
24
|
-
const config = await loadConfig();
|
|
25
|
-
return config.apiKeys?.linear ?? process.env.LINEAR_API_KEY ?? null;
|
|
26
|
-
}
|
|
27
|
-
async fetchActiveTickets(apiKey) {
|
|
28
|
-
const query = `
|
|
29
|
-
query {
|
|
30
|
-
issues(filter: { state: { type: { in: ["started", "unstarted"] } } }) {
|
|
31
|
-
nodes {
|
|
32
|
-
id
|
|
33
|
-
identifier
|
|
34
|
-
title
|
|
35
|
-
description
|
|
36
|
-
priority
|
|
37
|
-
url
|
|
38
|
-
status {
|
|
39
|
-
name
|
|
40
|
-
}
|
|
41
|
-
assignee {
|
|
42
|
-
name
|
|
43
|
-
}
|
|
44
|
-
labels {
|
|
45
|
-
nodes {
|
|
46
|
-
name
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
createdAt
|
|
50
|
-
updatedAt
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
`;
|
|
55
|
-
const response = await fetch("https://api.linear.app/graphql", {
|
|
56
|
-
method: "POST",
|
|
57
|
-
headers: {
|
|
58
|
-
"Content-Type": "application/json",
|
|
59
|
-
"Authorization": apiKey
|
|
60
|
-
},
|
|
61
|
-
body: JSON.stringify({ query })
|
|
62
|
-
});
|
|
63
|
-
if (!response.ok) {
|
|
64
|
-
throw new Error(`Linear API error: ${response.statusText}`);
|
|
65
|
-
}
|
|
66
|
-
const data = await response.json();
|
|
67
|
-
return data.data.issues.nodes;
|
|
68
|
-
}
|
|
69
|
-
async ingestTicket(ticket) {
|
|
70
|
-
const labels = ticket.labels.nodes.map((l) => l.name);
|
|
71
|
-
const intentVibe = this.extractIntentVibes(ticket.title, ticket.description, labels);
|
|
72
|
-
const linkedFiles = this.extractLinkedFiles(ticket.description);
|
|
73
|
-
const data = {
|
|
74
|
-
ticketId: ticket.identifier,
|
|
75
|
-
title: ticket.title,
|
|
76
|
-
description: ticket.description || "",
|
|
77
|
-
priority: this.mapPriority(ticket.priority),
|
|
78
|
-
labels,
|
|
79
|
-
intentVibe,
|
|
80
|
-
linkedFiles,
|
|
81
|
-
status: ticket.status.name,
|
|
82
|
-
assignee: ticket.assignee?.name || null,
|
|
83
|
-
url: ticket.url || `https://linear.app/issue/${ticket.identifier}`,
|
|
84
|
-
createdAt: ticket.createdAt,
|
|
85
|
-
updatedAt: ticket.updatedAt
|
|
86
|
-
};
|
|
87
|
-
await this.graph.addNode("linear-ticket", data);
|
|
88
|
-
for (const file of linkedFiles) {
|
|
89
|
-
const resolvedPath = path.resolve(this.graph.projectRoot, file);
|
|
90
|
-
const fileNode = await this.graph.getNode("file", resolvedPath);
|
|
91
|
-
if (fileNode) {
|
|
92
|
-
await this.graph.addEdge(`linear:${ticket.identifier}`, fileNode.id, "relatedTo");
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
extractIntentVibes(title, description, labels) {
|
|
97
|
-
const vibes = /* @__PURE__ */ new Set();
|
|
98
|
-
const text = (title + " " + (description || "") + " " + labels.join(" ")).toLowerCase();
|
|
99
|
-
if (text.includes("performance") || text.includes("slow") || text.includes("optimize")) vibes.add("performance");
|
|
100
|
-
if (text.includes("security") || text.includes("auth") || text.includes("vulnerability")) vibes.add("security");
|
|
101
|
-
if (text.includes("refactor") || text.includes("cleanup") || text.includes("debt")) vibes.add("refactor");
|
|
102
|
-
if (text.includes("feature") || text.includes("new")) vibes.add("feature");
|
|
103
|
-
if (text.includes("bug") || text.includes("fix") || text.includes("broken")) vibes.add("bug");
|
|
104
|
-
if (text.includes("breaking") || text.includes("major change")) vibes.add("breaking-change");
|
|
105
|
-
return Array.from(vibes);
|
|
106
|
-
}
|
|
107
|
-
extractLinkedFiles(description) {
|
|
108
|
-
if (!description) return [];
|
|
109
|
-
const matches = description.match(/[\w./_-]+\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs)/gi);
|
|
110
|
-
if (!matches) return [];
|
|
111
|
-
return Array.from(new Set(matches.map((m) => m.replace(/^\.\/+/, ""))));
|
|
112
|
-
}
|
|
113
|
-
mapPriority(priority) {
|
|
114
|
-
switch (priority) {
|
|
115
|
-
case 1:
|
|
116
|
-
return "urgent";
|
|
117
|
-
case 2:
|
|
118
|
-
return "high";
|
|
119
|
-
case 3:
|
|
120
|
-
return "medium";
|
|
121
|
-
case 4:
|
|
122
|
-
return "low";
|
|
123
|
-
default:
|
|
124
|
-
return "none";
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
export {
|
|
130
|
-
LinearIngester
|
|
131
|
-
};
|
|
132
|
-
//# sourceMappingURL=chunk-Q63FFI6D.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ingest/linear-ingester.ts"],"sourcesContent":["import path from 'node:path';\nimport { loadConfig } from '../config/loader.js';\nimport { ContextGraph } from '../context/graph.js';\nimport type { LinearTicketNodeData } from '../context/nodes.js';\n\nexport interface LinearTicket {\n id: string;\n identifier: string;\n title: string;\n description: string;\n priority: number;\n url: string;\n status: { name: string };\n assignee?: { name: string };\n labels: { nodes: { name: string }[] };\n createdAt: string;\n updatedAt: string;\n}\n\nexport class LinearIngester {\n private readonly graph: ContextGraph;\n\n constructor(_projectPath: string, graph: ContextGraph) {\n this.graph = graph;\n }\n\n async syncTickets(): Promise<void> {\n const apiKey = await this.getApiKey();\n if (!apiKey) {\n console.warn('Linear API key not found. Run \"trie linear auth <key>\" to enable ticket sync.');\n return;\n }\n\n const tickets = await this.fetchActiveTickets(apiKey);\n for (const ticket of tickets) {\n await this.ingestTicket(ticket);\n }\n }\n\n private async getApiKey(): Promise<string | null> {\n const config = await loadConfig();\n return config.apiKeys?.linear ?? process.env.LINEAR_API_KEY ?? null;\n }\n\n private async fetchActiveTickets(apiKey: string): Promise<LinearTicket[]> {\n const query = `\n query {\n issues(filter: { state: { type: { in: [\"started\", \"unstarted\"] } } }) {\n nodes {\n id\n identifier\n title\n description\n priority\n url\n status {\n name\n }\n assignee {\n name\n }\n labels {\n nodes {\n name\n }\n }\n createdAt\n updatedAt\n }\n }\n }\n `;\n\n const response = await fetch('https://api.linear.app/graphql', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': apiKey,\n },\n body: JSON.stringify({ query }),\n });\n\n if (!response.ok) {\n throw new Error(`Linear API error: ${response.statusText}`);\n }\n\n const data = (await response.json()) as any;\n return data.data.issues.nodes as LinearTicket[];\n }\n\n private async ingestTicket(ticket: LinearTicket): Promise<void> {\n const labels = ticket.labels.nodes.map(l => l.name);\n const intentVibe = this.extractIntentVibes(ticket.title, ticket.description, labels);\n \n // Attempt to find linked files in description or via labels (heuristic)\n const linkedFiles = this.extractLinkedFiles(ticket.description);\n\n const data: LinearTicketNodeData = {\n ticketId: ticket.identifier,\n title: ticket.title,\n description: ticket.description || '',\n priority: this.mapPriority(ticket.priority),\n labels,\n intentVibe,\n linkedFiles,\n status: ticket.status.name,\n assignee: ticket.assignee?.name || null,\n url: ticket.url || `https://linear.app/issue/${ticket.identifier}`,\n createdAt: ticket.createdAt,\n updatedAt: ticket.updatedAt,\n };\n\n await this.graph.addNode('linear-ticket', data);\n\n // Link to files if found (file node IDs are normalized absolute paths)\n for (const file of linkedFiles) {\n const resolvedPath = path.resolve(this.graph.projectRoot, file);\n const fileNode = await this.graph.getNode('file', resolvedPath);\n if (fileNode) {\n await this.graph.addEdge(`linear:${ticket.identifier}`, fileNode.id, 'relatedTo');\n }\n }\n }\n\n private extractIntentVibes(title: string, description: string, labels: string[]): string[] {\n const vibes = new Set<string>();\n const text = (title + ' ' + (description || '') + ' ' + labels.join(' ')).toLowerCase();\n\n if (text.includes('performance') || text.includes('slow') || text.includes('optimize')) vibes.add('performance');\n if (text.includes('security') || text.includes('auth') || text.includes('vulnerability')) vibes.add('security');\n if (text.includes('refactor') || text.includes('cleanup') || text.includes('debt')) vibes.add('refactor');\n if (text.includes('feature') || text.includes('new')) vibes.add('feature');\n if (text.includes('bug') || text.includes('fix') || text.includes('broken')) vibes.add('bug');\n if (text.includes('breaking') || text.includes('major change')) vibes.add('breaking-change');\n\n return Array.from(vibes);\n }\n\n private extractLinkedFiles(description: string): string[] {\n if (!description) return [];\n const matches = description.match(/[\\w./_-]+\\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs)/gi);\n if (!matches) return [];\n return Array.from(new Set(matches.map(m => m.replace(/^\\.\\/+/, ''))));\n }\n\n private mapPriority(priority: number): string {\n switch (priority) {\n case 1: return 'urgent';\n case 2: return 'high';\n case 3: return 'medium';\n case 4: return 'low';\n default: return 'none';\n }\n }\n}\n"],"mappings":"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","names":[]}
|