@triedotdev/mcp 1.0.43 → 1.0.45
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 +286 -41
- package/dist/{chunk-2I6CFJTR.js → chunk-5AS3BWAZ.js} +128 -30
- package/dist/chunk-5AS3BWAZ.js.map +1 -0
- package/dist/chunk-BAME4KVK.js +533 -0
- package/dist/chunk-BAME4KVK.js.map +1 -0
- package/dist/{chunk-PG3GMCGH.js → chunk-GLC62PGD.js} +1 -1
- package/dist/{chunk-PG3GMCGH.js.map → chunk-GLC62PGD.js.map} +1 -1
- package/dist/chunk-PZDQIFKO.js +1598 -0
- package/dist/chunk-PZDQIFKO.js.map +1 -0
- package/dist/{chunk-GBGONSOR.js → chunk-R5HWHP5N.js} +202 -39
- package/dist/chunk-R5HWHP5N.js.map +1 -0
- package/dist/cli/create-agent.js +1 -1
- package/dist/cli/create-agent.js.map +1 -1
- package/dist/cli/main.js +336 -21
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/yolo-daemon.js +3 -3
- package/dist/index.js +794 -68
- package/dist/index.js.map +1 -1
- package/dist/workers/agent-worker.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-2I6CFJTR.js.map +0 -1
- package/dist/chunk-52SSNKXS.js +0 -814
- package/dist/chunk-52SSNKXS.js.map +0 -1
- package/dist/chunk-GBGONSOR.js.map +0 -1
|
@@ -0,0 +1,1598 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getWorkingDirectory
|
|
3
|
+
} from "./chunk-IMFD4SJC.js";
|
|
4
|
+
|
|
5
|
+
// src/utils/project-info.ts
|
|
6
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
7
|
+
import { existsSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
var PROJECT_MD_PATH = ".trie/PROJECT.md";
|
|
10
|
+
function getProjectTemplate() {
|
|
11
|
+
return `# Project Information
|
|
12
|
+
|
|
13
|
+
> This file stores important project context for AI assistants.
|
|
14
|
+
> Edit freely - this file is yours, not auto-generated.
|
|
15
|
+
> Available via MCP resource: \`trie://project\`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Project Overview
|
|
20
|
+
|
|
21
|
+
<!-- Describe your project's purpose and goals -->
|
|
22
|
+
|
|
23
|
+
[Add project description here]
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Technology Stack
|
|
28
|
+
|
|
29
|
+
<!-- List frameworks, languages, databases, cloud services, etc. -->
|
|
30
|
+
|
|
31
|
+
- **Language:**
|
|
32
|
+
- **Framework:**
|
|
33
|
+
- **Database:**
|
|
34
|
+
- **Hosting:**
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Architecture
|
|
39
|
+
|
|
40
|
+
<!-- Key patterns, architectural decisions, and system design -->
|
|
41
|
+
|
|
42
|
+
[Describe your architecture here]
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Coding Conventions
|
|
47
|
+
|
|
48
|
+
<!-- Style guidelines, naming conventions, patterns to follow -->
|
|
49
|
+
|
|
50
|
+
-
|
|
51
|
+
-
|
|
52
|
+
-
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Environment
|
|
57
|
+
|
|
58
|
+
<!-- URLs, API endpoints, deployment info -->
|
|
59
|
+
|
|
60
|
+
| Environment | URL | Notes |
|
|
61
|
+
|-------------|-----|-------|
|
|
62
|
+
| Development | | |
|
|
63
|
+
| Staging | | |
|
|
64
|
+
| Production | | |
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Team
|
|
69
|
+
|
|
70
|
+
<!-- Ownership, contacts, responsibilities -->
|
|
71
|
+
|
|
72
|
+
- **Owner:**
|
|
73
|
+
- **Team:**
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Compliance
|
|
78
|
+
|
|
79
|
+
<!-- HIPAA, SOC2, GDPR, PCI-DSS requirements if applicable -->
|
|
80
|
+
|
|
81
|
+
- [ ] GDPR
|
|
82
|
+
- [ ] SOC2
|
|
83
|
+
- [ ] HIPAA
|
|
84
|
+
- [ ] PCI-DSS
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## AI Instructions
|
|
89
|
+
|
|
90
|
+
<!-- Special instructions for AI assistants working on this project -->
|
|
91
|
+
|
|
92
|
+
When working on this project, AI assistants should:
|
|
93
|
+
|
|
94
|
+
1.
|
|
95
|
+
2.
|
|
96
|
+
3.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
*This file is read by Trie agents and exposed via \`trie://project\` MCP resource.*
|
|
101
|
+
*Edit this file to provide context to Claude Code, Cursor, GitHub Actions, and other AI tools.*
|
|
102
|
+
`;
|
|
103
|
+
}
|
|
104
|
+
function projectInfoExists(workDir) {
|
|
105
|
+
const dir = workDir || getWorkingDirectory(void 0, true);
|
|
106
|
+
const projectPath = join(dir, PROJECT_MD_PATH);
|
|
107
|
+
return existsSync(projectPath);
|
|
108
|
+
}
|
|
109
|
+
async function loadProjectInfo(workDir) {
|
|
110
|
+
const dir = workDir || getWorkingDirectory(void 0, true);
|
|
111
|
+
const projectPath = join(dir, PROJECT_MD_PATH);
|
|
112
|
+
try {
|
|
113
|
+
if (!existsSync(projectPath)) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return await readFile(projectPath, "utf-8");
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function saveProjectInfo(content, workDir) {
|
|
122
|
+
const dir = workDir || getWorkingDirectory(void 0, true);
|
|
123
|
+
const trieDir = join(dir, ".trie");
|
|
124
|
+
const projectPath = join(dir, PROJECT_MD_PATH);
|
|
125
|
+
await mkdir(trieDir, { recursive: true });
|
|
126
|
+
await writeFile(projectPath, content, "utf-8");
|
|
127
|
+
}
|
|
128
|
+
async function initProjectInfo(workDir) {
|
|
129
|
+
const dir = workDir || getWorkingDirectory(void 0, true);
|
|
130
|
+
const projectPath = join(dir, PROJECT_MD_PATH);
|
|
131
|
+
if (existsSync(projectPath)) {
|
|
132
|
+
return { created: false, path: projectPath };
|
|
133
|
+
}
|
|
134
|
+
await saveProjectInfo(getProjectTemplate(), dir);
|
|
135
|
+
return { created: true, path: projectPath };
|
|
136
|
+
}
|
|
137
|
+
async function getProjectSection(sectionName, workDir) {
|
|
138
|
+
const content = await loadProjectInfo(workDir);
|
|
139
|
+
if (!content) return null;
|
|
140
|
+
const sectionRegex = new RegExp(
|
|
141
|
+
`## ${escapeRegex(sectionName)}\\s*\\n([\\s\\S]*?)(?=\\n## |\\n---\\s*$|$)`,
|
|
142
|
+
"i"
|
|
143
|
+
);
|
|
144
|
+
const match = content.match(sectionRegex);
|
|
145
|
+
if (match) {
|
|
146
|
+
return match[1].trim();
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
async function updateProjectSection(sectionName, newContent, workDir) {
|
|
151
|
+
let content = await loadProjectInfo(workDir);
|
|
152
|
+
if (!content) {
|
|
153
|
+
await initProjectInfo(workDir);
|
|
154
|
+
content = await loadProjectInfo(workDir);
|
|
155
|
+
if (!content) return false;
|
|
156
|
+
}
|
|
157
|
+
const sectionRegex = new RegExp(
|
|
158
|
+
`(## ${escapeRegex(sectionName)}\\s*\\n)([\\s\\S]*?)((?=\\n## )|(?=\\n---\\s*$)|$)`,
|
|
159
|
+
"i"
|
|
160
|
+
);
|
|
161
|
+
if (content.match(sectionRegex)) {
|
|
162
|
+
const updatedContent = content.replace(sectionRegex, `$1
|
|
163
|
+
${newContent}
|
|
164
|
+
|
|
165
|
+
$3`);
|
|
166
|
+
await saveProjectInfo(updatedContent, workDir);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
async function appendToSection(sectionName, contentToAdd, workDir) {
|
|
172
|
+
const currentContent = await getProjectSection(sectionName, workDir);
|
|
173
|
+
if (currentContent === null) return false;
|
|
174
|
+
const newContent = currentContent + "\n" + contentToAdd;
|
|
175
|
+
return updateProjectSection(sectionName, newContent, workDir);
|
|
176
|
+
}
|
|
177
|
+
async function getProjectSections(workDir) {
|
|
178
|
+
const content = await loadProjectInfo(workDir);
|
|
179
|
+
if (!content) return [];
|
|
180
|
+
const sectionRegex = /^## (.+)$/gm;
|
|
181
|
+
const sections = [];
|
|
182
|
+
let match;
|
|
183
|
+
while ((match = sectionRegex.exec(content)) !== null) {
|
|
184
|
+
sections.push(match[1].trim());
|
|
185
|
+
}
|
|
186
|
+
return sections;
|
|
187
|
+
}
|
|
188
|
+
async function getProjectInfoStructured(workDir) {
|
|
189
|
+
const dir = workDir || getWorkingDirectory(void 0, true);
|
|
190
|
+
const projectPath = join(dir, PROJECT_MD_PATH);
|
|
191
|
+
const content = await loadProjectInfo(dir);
|
|
192
|
+
if (!content) {
|
|
193
|
+
return {
|
|
194
|
+
exists: false,
|
|
195
|
+
path: projectPath,
|
|
196
|
+
sections: {},
|
|
197
|
+
raw: null
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const sectionNames = await getProjectSections(dir);
|
|
201
|
+
const sections = {};
|
|
202
|
+
for (const name of sectionNames) {
|
|
203
|
+
const sectionContent = await getProjectSection(name, dir);
|
|
204
|
+
if (sectionContent) {
|
|
205
|
+
sections[name] = sectionContent;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
exists: true,
|
|
210
|
+
path: projectPath,
|
|
211
|
+
sections,
|
|
212
|
+
raw: content
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function escapeRegex(str) {
|
|
216
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/skills/installer.ts
|
|
220
|
+
import { mkdir as mkdir2, rm, writeFile as writeFile2, readdir, readFile as readFile3, access, cp } from "fs/promises";
|
|
221
|
+
import { join as join3 } from "path";
|
|
222
|
+
import { exec } from "child_process";
|
|
223
|
+
import { promisify } from "util";
|
|
224
|
+
|
|
225
|
+
// src/skills/parser.ts
|
|
226
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
227
|
+
import { join as join2 } from "path";
|
|
228
|
+
async function parseSkillMd(skillPath) {
|
|
229
|
+
const skillMdPath = join2(skillPath, "SKILL.md");
|
|
230
|
+
const rawContent = await readFile2(skillMdPath, "utf-8");
|
|
231
|
+
const frontmatterMatch = rawContent.match(/^---\n([\s\S]*?)\n---/);
|
|
232
|
+
if (!frontmatterMatch || !frontmatterMatch[1]) {
|
|
233
|
+
throw new Error("Invalid SKILL.md: missing YAML frontmatter");
|
|
234
|
+
}
|
|
235
|
+
const frontmatter = parseYamlFrontmatter(frontmatterMatch[1]);
|
|
236
|
+
if (!frontmatter.name || !frontmatter.description) {
|
|
237
|
+
throw new Error("Invalid SKILL.md: missing required name or description in frontmatter");
|
|
238
|
+
}
|
|
239
|
+
const content = rawContent.slice(frontmatterMatch[0].length).trim();
|
|
240
|
+
return {
|
|
241
|
+
frontmatter,
|
|
242
|
+
content,
|
|
243
|
+
rawContent
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function parseYamlFrontmatter(yaml) {
|
|
247
|
+
const result = {};
|
|
248
|
+
const lines = yaml.split("\n");
|
|
249
|
+
for (const line of lines) {
|
|
250
|
+
const trimmed = line.trim();
|
|
251
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
252
|
+
const colonIndex = trimmed.indexOf(":");
|
|
253
|
+
if (colonIndex === -1) continue;
|
|
254
|
+
const key = trimmed.slice(0, colonIndex).trim();
|
|
255
|
+
let value = trimmed.slice(colonIndex + 1).trim();
|
|
256
|
+
if (value === "") continue;
|
|
257
|
+
if (typeof value === "string") {
|
|
258
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
259
|
+
value = value.slice(1, -1);
|
|
260
|
+
}
|
|
261
|
+
if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
|
|
262
|
+
value = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, ""));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
result[key] = value;
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/skills/installer.ts
|
|
271
|
+
var execAsync = promisify(exec);
|
|
272
|
+
async function installSkill(source, skillName) {
|
|
273
|
+
const parts = source.split("/");
|
|
274
|
+
if (parts.length < 2) {
|
|
275
|
+
return { success: false, name: "unknown", error: "Invalid source format. Use owner/repo or owner/repo/skill-name" };
|
|
276
|
+
}
|
|
277
|
+
const owner = parts[0];
|
|
278
|
+
const repo = parts[1];
|
|
279
|
+
const specifiedSkill = skillName || parts[2];
|
|
280
|
+
const skillsDir = join3(getWorkingDirectory(void 0, true), ".trie", "skills");
|
|
281
|
+
const tempDir = join3(skillsDir, `.temp-${Date.now()}`);
|
|
282
|
+
try {
|
|
283
|
+
await mkdir2(skillsDir, { recursive: true });
|
|
284
|
+
const repoUrl = `https://github.com/${owner}/${repo}.git`;
|
|
285
|
+
await execAsync(`git clone --depth 1 "${repoUrl}" "${tempDir}"`, { timeout: 6e4 });
|
|
286
|
+
const sourcePath = await findSkillPath(tempDir, specifiedSkill);
|
|
287
|
+
if (!sourcePath) {
|
|
288
|
+
throw new Error(`SKILL.md not found in repository. Searched in: root, skills/, ${specifiedSkill || "subdirectories"}`);
|
|
289
|
+
}
|
|
290
|
+
const parsed = await parseSkillMd(sourcePath);
|
|
291
|
+
const name = parsed.frontmatter.name;
|
|
292
|
+
const targetPath = join3(skillsDir, name);
|
|
293
|
+
await rm(targetPath, { recursive: true, force: true });
|
|
294
|
+
await cp(sourcePath, targetPath, { recursive: true });
|
|
295
|
+
await writeFile2(join3(targetPath, ".installed.json"), JSON.stringify({
|
|
296
|
+
installedFrom: source,
|
|
297
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
298
|
+
repository: `${owner}/${repo}`
|
|
299
|
+
}, null, 2));
|
|
300
|
+
return { success: true, name, path: targetPath };
|
|
301
|
+
} catch (error) {
|
|
302
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
303
|
+
return { success: false, name: skillName || "unknown", error: message };
|
|
304
|
+
} finally {
|
|
305
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async function findSkillPath(repoPath, skillName) {
|
|
310
|
+
const searchPaths = [];
|
|
311
|
+
if (skillName) {
|
|
312
|
+
searchPaths.push(
|
|
313
|
+
join3(repoPath, "skills", skillName),
|
|
314
|
+
join3(repoPath, skillName)
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
searchPaths.push(
|
|
318
|
+
repoPath,
|
|
319
|
+
join3(repoPath, "skill")
|
|
320
|
+
);
|
|
321
|
+
if (!skillName) {
|
|
322
|
+
try {
|
|
323
|
+
const skillsSubdir = join3(repoPath, "skills");
|
|
324
|
+
await access(skillsSubdir);
|
|
325
|
+
const entries = await readdir(skillsSubdir, { withFileTypes: true });
|
|
326
|
+
for (const entry of entries) {
|
|
327
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
328
|
+
searchPaths.push(join3(skillsSubdir, entry.name));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
for (const searchPath of searchPaths) {
|
|
335
|
+
try {
|
|
336
|
+
await parseSkillMd(searchPath);
|
|
337
|
+
return searchPath;
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
async function listInstalledSkills() {
|
|
344
|
+
const skillsDir = join3(getWorkingDirectory(void 0, true), ".trie", "skills");
|
|
345
|
+
const skills = [];
|
|
346
|
+
try {
|
|
347
|
+
const entries = await readdir(skillsDir, { withFileTypes: true });
|
|
348
|
+
for (const entry of entries) {
|
|
349
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
350
|
+
const skillPath = join3(skillsDir, entry.name);
|
|
351
|
+
try {
|
|
352
|
+
const parsed = await parseSkillMd(skillPath);
|
|
353
|
+
const metaPath = join3(skillPath, ".installed.json");
|
|
354
|
+
let meta = { installedFrom: "unknown", installedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
355
|
+
try {
|
|
356
|
+
meta = JSON.parse(await readFile3(metaPath, "utf-8"));
|
|
357
|
+
} catch {
|
|
358
|
+
}
|
|
359
|
+
skills.push({
|
|
360
|
+
name: parsed.frontmatter.name,
|
|
361
|
+
description: parsed.frontmatter.description,
|
|
362
|
+
path: skillPath,
|
|
363
|
+
installedFrom: meta.installedFrom,
|
|
364
|
+
installedAt: meta.installedAt
|
|
365
|
+
});
|
|
366
|
+
} catch {
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
return skills;
|
|
372
|
+
}
|
|
373
|
+
async function removeSkill(skillName) {
|
|
374
|
+
const skillsDir = join3(getWorkingDirectory(void 0, true), ".trie", "skills");
|
|
375
|
+
const skillPath = join3(skillsDir, skillName);
|
|
376
|
+
try {
|
|
377
|
+
await rm(skillPath, { recursive: true });
|
|
378
|
+
return true;
|
|
379
|
+
} catch {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/memory/issue-store.ts
|
|
385
|
+
import { mkdir as mkdir4, writeFile as writeFile4, readFile as readFile5, readdir as readdir2 } from "fs/promises";
|
|
386
|
+
import { existsSync as existsSync3 } from "fs";
|
|
387
|
+
import { join as join5 } from "path";
|
|
388
|
+
|
|
389
|
+
// src/memory/bm25.ts
|
|
390
|
+
var BM25Index = class _BM25Index {
|
|
391
|
+
documents = /* @__PURE__ */ new Map();
|
|
392
|
+
termFrequencies = /* @__PURE__ */ new Map();
|
|
393
|
+
documentFrequencies = /* @__PURE__ */ new Map();
|
|
394
|
+
documentLengths = /* @__PURE__ */ new Map();
|
|
395
|
+
avgDocLength = 0;
|
|
396
|
+
k1 = 1.5;
|
|
397
|
+
b = 0.75;
|
|
398
|
+
/**
|
|
399
|
+
* Add a document to the index
|
|
400
|
+
*/
|
|
401
|
+
addDocument(doc) {
|
|
402
|
+
const tokens = this.tokenize(doc.text);
|
|
403
|
+
this.documents.set(doc.id, doc);
|
|
404
|
+
this.documentLengths.set(doc.id, tokens.length);
|
|
405
|
+
const termFreq = /* @__PURE__ */ new Map();
|
|
406
|
+
const seenTerms = /* @__PURE__ */ new Set();
|
|
407
|
+
for (const token of tokens) {
|
|
408
|
+
termFreq.set(token, (termFreq.get(token) || 0) + 1);
|
|
409
|
+
if (!seenTerms.has(token)) {
|
|
410
|
+
seenTerms.add(token);
|
|
411
|
+
this.documentFrequencies.set(token, (this.documentFrequencies.get(token) || 0) + 1);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
this.termFrequencies.set(doc.id, termFreq);
|
|
415
|
+
this.updateAvgDocLength();
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Add multiple documents
|
|
419
|
+
*/
|
|
420
|
+
addDocuments(docs) {
|
|
421
|
+
for (const doc of docs) {
|
|
422
|
+
this.addDocument(doc);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Search the index
|
|
427
|
+
*/
|
|
428
|
+
search(query, limit = 10) {
|
|
429
|
+
const queryTokens = this.tokenize(query);
|
|
430
|
+
const scores = /* @__PURE__ */ new Map();
|
|
431
|
+
const N = this.documents.size;
|
|
432
|
+
for (const [docId] of this.documents) {
|
|
433
|
+
let score = 0;
|
|
434
|
+
const docLength = this.documentLengths.get(docId) || 0;
|
|
435
|
+
const termFreqs = this.termFrequencies.get(docId);
|
|
436
|
+
if (!termFreqs) continue;
|
|
437
|
+
for (const term of queryTokens) {
|
|
438
|
+
const tf = termFreqs.get(term) || 0;
|
|
439
|
+
if (tf === 0) continue;
|
|
440
|
+
const df = this.documentFrequencies.get(term) || 0;
|
|
441
|
+
const idf = Math.log((N - df + 0.5) / (df + 0.5) + 1);
|
|
442
|
+
const numerator = tf * (this.k1 + 1);
|
|
443
|
+
const denominator = tf + this.k1 * (1 - this.b + this.b * (docLength / this.avgDocLength));
|
|
444
|
+
score += idf * (numerator / denominator);
|
|
445
|
+
}
|
|
446
|
+
if (score > 0) {
|
|
447
|
+
scores.set(docId, score);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return Array.from(scores.entries()).sort((a, b) => b[1] - a[1]).slice(0, limit).map(([id, score]) => ({
|
|
451
|
+
id,
|
|
452
|
+
score,
|
|
453
|
+
metadata: this.documents.get(id)?.metadata
|
|
454
|
+
}));
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Get document count
|
|
458
|
+
*/
|
|
459
|
+
get size() {
|
|
460
|
+
return this.documents.size;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Clear the index
|
|
464
|
+
*/
|
|
465
|
+
clear() {
|
|
466
|
+
this.documents.clear();
|
|
467
|
+
this.termFrequencies.clear();
|
|
468
|
+
this.documentFrequencies.clear();
|
|
469
|
+
this.documentLengths.clear();
|
|
470
|
+
this.avgDocLength = 0;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Serialize the index to JSON
|
|
474
|
+
*/
|
|
475
|
+
serialize() {
|
|
476
|
+
return JSON.stringify({
|
|
477
|
+
documents: Array.from(this.documents.entries()),
|
|
478
|
+
termFrequencies: Array.from(this.termFrequencies.entries()).map(([k, v]) => [k, Array.from(v.entries())]),
|
|
479
|
+
documentFrequencies: Array.from(this.documentFrequencies.entries()),
|
|
480
|
+
documentLengths: Array.from(this.documentLengths.entries()),
|
|
481
|
+
avgDocLength: this.avgDocLength
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Load from serialized JSON
|
|
486
|
+
*/
|
|
487
|
+
static deserialize(json) {
|
|
488
|
+
const data = JSON.parse(json);
|
|
489
|
+
const index = new _BM25Index();
|
|
490
|
+
index.documents = new Map(data.documents);
|
|
491
|
+
index.termFrequencies = new Map(data.termFrequencies.map(([k, v]) => [k, new Map(v)]));
|
|
492
|
+
index.documentFrequencies = new Map(data.documentFrequencies);
|
|
493
|
+
index.documentLengths = new Map(data.documentLengths);
|
|
494
|
+
index.avgDocLength = data.avgDocLength;
|
|
495
|
+
return index;
|
|
496
|
+
}
|
|
497
|
+
tokenize(text) {
|
|
498
|
+
return text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((token) => token.length > 2 && !this.isStopWord(token));
|
|
499
|
+
}
|
|
500
|
+
isStopWord(word) {
|
|
501
|
+
const stopWords = /* @__PURE__ */ new Set([
|
|
502
|
+
"the",
|
|
503
|
+
"be",
|
|
504
|
+
"to",
|
|
505
|
+
"of",
|
|
506
|
+
"and",
|
|
507
|
+
"a",
|
|
508
|
+
"in",
|
|
509
|
+
"that",
|
|
510
|
+
"have",
|
|
511
|
+
"i",
|
|
512
|
+
"it",
|
|
513
|
+
"for",
|
|
514
|
+
"not",
|
|
515
|
+
"on",
|
|
516
|
+
"with",
|
|
517
|
+
"he",
|
|
518
|
+
"as",
|
|
519
|
+
"you",
|
|
520
|
+
"do",
|
|
521
|
+
"at",
|
|
522
|
+
"this",
|
|
523
|
+
"but",
|
|
524
|
+
"his",
|
|
525
|
+
"by",
|
|
526
|
+
"from",
|
|
527
|
+
"they",
|
|
528
|
+
"we",
|
|
529
|
+
"say",
|
|
530
|
+
"her",
|
|
531
|
+
"she",
|
|
532
|
+
"or",
|
|
533
|
+
"an",
|
|
534
|
+
"will",
|
|
535
|
+
"my",
|
|
536
|
+
"one",
|
|
537
|
+
"all",
|
|
538
|
+
"would",
|
|
539
|
+
"there",
|
|
540
|
+
"their",
|
|
541
|
+
"what",
|
|
542
|
+
"so",
|
|
543
|
+
"up",
|
|
544
|
+
"out",
|
|
545
|
+
"if",
|
|
546
|
+
"about",
|
|
547
|
+
"who",
|
|
548
|
+
"get",
|
|
549
|
+
"which",
|
|
550
|
+
"go",
|
|
551
|
+
"me",
|
|
552
|
+
"when",
|
|
553
|
+
"make",
|
|
554
|
+
"can",
|
|
555
|
+
"like",
|
|
556
|
+
"time",
|
|
557
|
+
"no",
|
|
558
|
+
"just",
|
|
559
|
+
"him",
|
|
560
|
+
"know",
|
|
561
|
+
"take",
|
|
562
|
+
"into",
|
|
563
|
+
"year",
|
|
564
|
+
"your",
|
|
565
|
+
"some",
|
|
566
|
+
"could",
|
|
567
|
+
"them",
|
|
568
|
+
"see",
|
|
569
|
+
"other",
|
|
570
|
+
"than",
|
|
571
|
+
"then",
|
|
572
|
+
"now",
|
|
573
|
+
"look",
|
|
574
|
+
"only",
|
|
575
|
+
"come",
|
|
576
|
+
"its",
|
|
577
|
+
"over",
|
|
578
|
+
"also",
|
|
579
|
+
"back",
|
|
580
|
+
"after",
|
|
581
|
+
"use",
|
|
582
|
+
"two",
|
|
583
|
+
"how",
|
|
584
|
+
"our",
|
|
585
|
+
"first",
|
|
586
|
+
"way",
|
|
587
|
+
"even",
|
|
588
|
+
"new",
|
|
589
|
+
"want",
|
|
590
|
+
"because",
|
|
591
|
+
"any",
|
|
592
|
+
"these",
|
|
593
|
+
"give",
|
|
594
|
+
"day",
|
|
595
|
+
"most",
|
|
596
|
+
"us",
|
|
597
|
+
"should",
|
|
598
|
+
"been",
|
|
599
|
+
"has",
|
|
600
|
+
"was",
|
|
601
|
+
"are"
|
|
602
|
+
]);
|
|
603
|
+
return stopWords.has(word);
|
|
604
|
+
}
|
|
605
|
+
updateAvgDocLength() {
|
|
606
|
+
if (this.documentLengths.size === 0) {
|
|
607
|
+
this.avgDocLength = 0;
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
const total = Array.from(this.documentLengths.values()).reduce((a, b) => a + b, 0);
|
|
611
|
+
this.avgDocLength = total / this.documentLengths.size;
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
// src/memory/compactor.ts
|
|
616
|
+
import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile4 } from "fs/promises";
|
|
617
|
+
import { existsSync as existsSync2 } from "fs";
|
|
618
|
+
import { join as join4 } from "path";
|
|
619
|
+
async function compactOldIssues(issues, options = {}) {
|
|
620
|
+
const keepDays = options.keepDays ?? 30;
|
|
621
|
+
const minIssues = options.minIssuesToCompact ?? 100;
|
|
622
|
+
const cutoffDate = /* @__PURE__ */ new Date();
|
|
623
|
+
cutoffDate.setDate(cutoffDate.getDate() - keepDays);
|
|
624
|
+
const oldIssues = issues.filter((i) => new Date(i.timestamp) < cutoffDate);
|
|
625
|
+
const recentIssues = issues.filter((i) => new Date(i.timestamp) >= cutoffDate);
|
|
626
|
+
if (oldIssues.length < minIssues) {
|
|
627
|
+
return { summary: null, remaining: issues };
|
|
628
|
+
}
|
|
629
|
+
const summary = buildSummary(oldIssues);
|
|
630
|
+
return { summary, remaining: recentIssues };
|
|
631
|
+
}
|
|
632
|
+
function buildSummary(issues) {
|
|
633
|
+
const sorted = issues.sort(
|
|
634
|
+
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
635
|
+
);
|
|
636
|
+
const bySeverity = {};
|
|
637
|
+
const byAgent = {};
|
|
638
|
+
const patternMap = /* @__PURE__ */ new Map();
|
|
639
|
+
const fileCount = /* @__PURE__ */ new Map();
|
|
640
|
+
for (const issue of issues) {
|
|
641
|
+
bySeverity[issue.severity] = (bySeverity[issue.severity] || 0) + 1;
|
|
642
|
+
byAgent[issue.agent] = (byAgent[issue.agent] || 0) + 1;
|
|
643
|
+
const patternKey = normalizePattern(issue.issue);
|
|
644
|
+
const existing = patternMap.get(patternKey);
|
|
645
|
+
if (existing) {
|
|
646
|
+
existing.count++;
|
|
647
|
+
} else {
|
|
648
|
+
patternMap.set(patternKey, { count: 1, issue });
|
|
649
|
+
}
|
|
650
|
+
const fileName = issue.file.split("/").pop() || issue.file;
|
|
651
|
+
fileCount.set(fileName, (fileCount.get(fileName) || 0) + 1);
|
|
652
|
+
}
|
|
653
|
+
const topPatterns = Array.from(patternMap.entries()).sort((a, b) => b[1].count - a[1].count).slice(0, 10).map(([pattern, data]) => ({
|
|
654
|
+
pattern: pattern.slice(0, 100),
|
|
655
|
+
count: data.count,
|
|
656
|
+
severity: data.issue.severity,
|
|
657
|
+
agent: data.issue.agent,
|
|
658
|
+
exampleFix: data.issue.fix.slice(0, 200)
|
|
659
|
+
}));
|
|
660
|
+
const hotFiles = Array.from(fileCount.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([file, count]) => ({ file, count }));
|
|
661
|
+
return {
|
|
662
|
+
period: `${sorted[0]?.timestamp.split("T")[0]} to ${sorted[sorted.length - 1]?.timestamp.split("T")[0]}`,
|
|
663
|
+
startDate: sorted[0]?.timestamp || "",
|
|
664
|
+
endDate: sorted[sorted.length - 1]?.timestamp || "",
|
|
665
|
+
totalIssues: issues.length,
|
|
666
|
+
resolvedCount: issues.filter((i) => i.resolved).length,
|
|
667
|
+
bySeverity,
|
|
668
|
+
byAgent,
|
|
669
|
+
topPatterns,
|
|
670
|
+
hotFiles,
|
|
671
|
+
compactedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
function normalizePattern(text) {
|
|
675
|
+
return text.toLowerCase().replace(/`[^`]+`/g, "CODE").replace(/\b\d+\b/g, "N").replace(/["']/g, "").replace(/\s+/g, " ").trim().slice(0, 150);
|
|
676
|
+
}
|
|
677
|
+
async function saveCompactedSummary(summary, projectDir) {
|
|
678
|
+
const memoryDir = join4(projectDir, ".trie", "memory");
|
|
679
|
+
await mkdir3(memoryDir, { recursive: true });
|
|
680
|
+
const summaryPath = join4(memoryDir, "compacted-summaries.json");
|
|
681
|
+
let summaries = [];
|
|
682
|
+
try {
|
|
683
|
+
if (existsSync2(summaryPath)) {
|
|
684
|
+
summaries = JSON.parse(await readFile4(summaryPath, "utf-8"));
|
|
685
|
+
}
|
|
686
|
+
} catch {
|
|
687
|
+
summaries = [];
|
|
688
|
+
}
|
|
689
|
+
summaries.push(summary);
|
|
690
|
+
if (summaries.length > 12) {
|
|
691
|
+
summaries = summaries.slice(-12);
|
|
692
|
+
}
|
|
693
|
+
await writeFile3(summaryPath, JSON.stringify(summaries, null, 2));
|
|
694
|
+
}
|
|
695
|
+
async function loadCompactedSummaries(projectDir) {
|
|
696
|
+
const summaryPath = join4(projectDir, ".trie", "memory", "compacted-summaries.json");
|
|
697
|
+
try {
|
|
698
|
+
if (existsSync2(summaryPath)) {
|
|
699
|
+
return JSON.parse(await readFile4(summaryPath, "utf-8"));
|
|
700
|
+
}
|
|
701
|
+
} catch {
|
|
702
|
+
}
|
|
703
|
+
return [];
|
|
704
|
+
}
|
|
705
|
+
async function getHistoricalInsights(projectDir) {
|
|
706
|
+
const summaries = await loadCompactedSummaries(projectDir);
|
|
707
|
+
if (summaries.length === 0) {
|
|
708
|
+
return {
|
|
709
|
+
totalHistoricalIssues: 0,
|
|
710
|
+
recurringPatterns: [],
|
|
711
|
+
improvementTrend: "unknown"
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
const totalHistoricalIssues = summaries.reduce((sum, s) => sum + s.totalIssues, 0);
|
|
715
|
+
const patternCounts = /* @__PURE__ */ new Map();
|
|
716
|
+
for (const summary of summaries) {
|
|
717
|
+
for (const pattern of summary.topPatterns) {
|
|
718
|
+
const key = pattern.pattern;
|
|
719
|
+
const existing = patternCounts.get(key);
|
|
720
|
+
if (existing) {
|
|
721
|
+
existing.count += pattern.count;
|
|
722
|
+
existing.appearances++;
|
|
723
|
+
} else {
|
|
724
|
+
patternCounts.set(key, { ...pattern, appearances: 1 });
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
const recurringPatterns = Array.from(patternCounts.values()).filter((p) => p.appearances >= 2).sort((a, b) => b.count - a.count).slice(0, 5);
|
|
729
|
+
let improvementTrend = "unknown";
|
|
730
|
+
if (summaries.length >= 2) {
|
|
731
|
+
const recent = summaries.slice(-2);
|
|
732
|
+
const olderCount = recent[0]?.totalIssues || 0;
|
|
733
|
+
const newerCount = recent[1]?.totalIssues || 0;
|
|
734
|
+
if (newerCount < olderCount * 0.8) {
|
|
735
|
+
improvementTrend = "improving";
|
|
736
|
+
} else if (newerCount > olderCount * 1.2) {
|
|
737
|
+
improvementTrend = "declining";
|
|
738
|
+
} else {
|
|
739
|
+
improvementTrend = "stable";
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return {
|
|
743
|
+
totalHistoricalIssues,
|
|
744
|
+
recurringPatterns,
|
|
745
|
+
improvementTrend
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// src/memory/issue-store.ts
|
|
750
|
+
async function storeIssues(issues, project, workDir) {
|
|
751
|
+
const projectDir = workDir || getWorkingDirectory(void 0, true);
|
|
752
|
+
const memoryDir = join5(projectDir, ".trie", "memory");
|
|
753
|
+
await mkdir4(memoryDir, { recursive: true });
|
|
754
|
+
const stored = [];
|
|
755
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
756
|
+
for (const issue of issues) {
|
|
757
|
+
const hash = hashIssue(issue);
|
|
758
|
+
stored.push({
|
|
759
|
+
id: issue.id,
|
|
760
|
+
hash,
|
|
761
|
+
severity: issue.severity,
|
|
762
|
+
issue: issue.issue,
|
|
763
|
+
fix: issue.fix,
|
|
764
|
+
file: issue.file,
|
|
765
|
+
line: issue.line,
|
|
766
|
+
agent: issue.agent,
|
|
767
|
+
category: issue.category,
|
|
768
|
+
timestamp: now,
|
|
769
|
+
project,
|
|
770
|
+
resolved: false
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
await appendToDailyLog(stored, projectDir);
|
|
774
|
+
await updateIssueIndex(stored, projectDir);
|
|
775
|
+
return stored.length;
|
|
776
|
+
}
|
|
777
|
+
async function searchIssues(query, options = {}) {
|
|
778
|
+
const projectDir = options.workDir || getWorkingDirectory(void 0, true);
|
|
779
|
+
const limit = options.limit || 10;
|
|
780
|
+
const allIssues = await loadIssueIndex(projectDir);
|
|
781
|
+
if (allIssues.length === 0) {
|
|
782
|
+
return [];
|
|
783
|
+
}
|
|
784
|
+
const filteredIssues = allIssues.filter((issue) => {
|
|
785
|
+
if (options.project && issue.project !== options.project) return false;
|
|
786
|
+
if (options.severity && !options.severity.includes(issue.severity)) return false;
|
|
787
|
+
if (options.agent && issue.agent !== options.agent) return false;
|
|
788
|
+
if (!options.includeResolved && issue.resolved) return false;
|
|
789
|
+
return true;
|
|
790
|
+
});
|
|
791
|
+
if (filteredIssues.length === 0) {
|
|
792
|
+
return [];
|
|
793
|
+
}
|
|
794
|
+
const bm25 = new BM25Index();
|
|
795
|
+
const issueMap = /* @__PURE__ */ new Map();
|
|
796
|
+
for (const issue of filteredIssues) {
|
|
797
|
+
const searchText = `${issue.issue} ${issue.fix} ${issue.file} ${issue.agent} ${issue.category || ""} ${issue.severity}`;
|
|
798
|
+
bm25.addDocument({
|
|
799
|
+
id: issue.id,
|
|
800
|
+
text: searchText
|
|
801
|
+
});
|
|
802
|
+
issueMap.set(issue.id, issue);
|
|
803
|
+
}
|
|
804
|
+
const bm25Results = bm25.search(query, limit);
|
|
805
|
+
return bm25Results.map((result) => ({
|
|
806
|
+
issue: issueMap.get(result.id),
|
|
807
|
+
score: result.score,
|
|
808
|
+
matchType: "bm25"
|
|
809
|
+
}));
|
|
810
|
+
}
|
|
811
|
+
async function findSimilarIssues(issue, options = {}) {
|
|
812
|
+
const query = `${issue.issue} ${issue.fix} ${issue.agent}`;
|
|
813
|
+
const results = await searchIssues(query, {
|
|
814
|
+
workDir: options.workDir,
|
|
815
|
+
limit: (options.limit || 5) + 5,
|
|
816
|
+
// Get extra to account for filtering
|
|
817
|
+
includeResolved: true
|
|
818
|
+
});
|
|
819
|
+
let filtered = results.filter((r) => r.issue.id !== issue.id);
|
|
820
|
+
if (options.excludeSameFile) {
|
|
821
|
+
filtered = filtered.filter((r) => r.issue.file !== issue.file);
|
|
822
|
+
}
|
|
823
|
+
return filtered.slice(0, options.limit || 5);
|
|
824
|
+
}
|
|
825
|
+
async function markIssueResolved(issueId, workDir) {
|
|
826
|
+
const projectDir = workDir || getWorkingDirectory(void 0, true);
|
|
827
|
+
const index = await loadIssueIndex(projectDir);
|
|
828
|
+
const issue = index.find((i) => i.id === issueId);
|
|
829
|
+
if (!issue) return false;
|
|
830
|
+
issue.resolved = true;
|
|
831
|
+
issue.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
832
|
+
await saveIssueIndex(index, projectDir);
|
|
833
|
+
return true;
|
|
834
|
+
}
|
|
835
|
+
async function getMemoryStats(workDir) {
|
|
836
|
+
const projectDir = workDir || getWorkingDirectory(void 0, true);
|
|
837
|
+
const index = await loadIssueIndex(projectDir);
|
|
838
|
+
const historical = await getHistoricalInsights(projectDir);
|
|
839
|
+
const stats = {
|
|
840
|
+
totalIssues: index.length,
|
|
841
|
+
issuesByAgent: {},
|
|
842
|
+
issuesBySeverity: {},
|
|
843
|
+
resolvedCount: 0,
|
|
844
|
+
historicalIssues: historical.totalHistoricalIssues,
|
|
845
|
+
improvementTrend: historical.improvementTrend
|
|
846
|
+
};
|
|
847
|
+
for (const issue of index) {
|
|
848
|
+
stats.issuesByAgent[issue.agent] = (stats.issuesByAgent[issue.agent] || 0) + 1;
|
|
849
|
+
stats.issuesBySeverity[issue.severity] = (stats.issuesBySeverity[issue.severity] || 0) + 1;
|
|
850
|
+
if (issue.resolved) stats.resolvedCount++;
|
|
851
|
+
}
|
|
852
|
+
if (index.length > 0) {
|
|
853
|
+
const sorted = [...index].sort(
|
|
854
|
+
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
855
|
+
);
|
|
856
|
+
stats.oldestIssue = sorted[0]?.timestamp;
|
|
857
|
+
stats.newestIssue = sorted[sorted.length - 1]?.timestamp;
|
|
858
|
+
}
|
|
859
|
+
return stats;
|
|
860
|
+
}
|
|
861
|
+
async function getRecentIssues(options = {}) {
|
|
862
|
+
const projectDir = options.workDir || getWorkingDirectory(void 0, true);
|
|
863
|
+
const index = await loadIssueIndex(projectDir);
|
|
864
|
+
const limit = options.limit || 20;
|
|
865
|
+
const daysBack = options.daysBack || 7;
|
|
866
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
867
|
+
cutoff.setDate(cutoff.getDate() - daysBack);
|
|
868
|
+
return index.filter((i) => new Date(i.timestamp) >= cutoff).sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()).slice(0, limit);
|
|
869
|
+
}
|
|
870
|
+
async function getDailyLogs(workDir) {
|
|
871
|
+
const projectDir = workDir || getWorkingDirectory(void 0, true);
|
|
872
|
+
const memoryDir = join5(projectDir, ".trie", "memory");
|
|
873
|
+
try {
|
|
874
|
+
if (!existsSync3(memoryDir)) return [];
|
|
875
|
+
const files = await readdir2(memoryDir);
|
|
876
|
+
return files.filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).sort().reverse();
|
|
877
|
+
} catch {
|
|
878
|
+
return [];
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
async function appendToDailyLog(issues, projectDir) {
|
|
882
|
+
const memoryDir = join5(projectDir, ".trie", "memory");
|
|
883
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
884
|
+
const logPath = join5(memoryDir, `${today}.md`);
|
|
885
|
+
let content = "";
|
|
886
|
+
try {
|
|
887
|
+
if (existsSync3(logPath)) {
|
|
888
|
+
content = await readFile5(logPath, "utf-8");
|
|
889
|
+
} else {
|
|
890
|
+
content = `# Issue Log: ${today}
|
|
891
|
+
|
|
892
|
+
`;
|
|
893
|
+
}
|
|
894
|
+
} catch {
|
|
895
|
+
content = `# Issue Log: ${today}
|
|
896
|
+
|
|
897
|
+
`;
|
|
898
|
+
}
|
|
899
|
+
const time = (/* @__PURE__ */ new Date()).toTimeString().split(" ")[0];
|
|
900
|
+
const newEntries = issues.map(
|
|
901
|
+
(i) => `## [${time}] ${i.severity.toUpperCase()}: ${i.issue.slice(0, 80)}${i.issue.length > 80 ? "..." : ""}
|
|
902
|
+
- **File:** \`${i.file}\`${i.line ? `:${i.line}` : ""}
|
|
903
|
+
- **Agent:** ${i.agent}
|
|
904
|
+
- **Fix:** ${i.fix.slice(0, 200)}${i.fix.length > 200 ? "..." : ""}
|
|
905
|
+
`
|
|
906
|
+
).join("\n");
|
|
907
|
+
content += newEntries + "\n";
|
|
908
|
+
await writeFile4(logPath, content);
|
|
909
|
+
}
|
|
910
|
+
async function loadIssueIndex(projectDir) {
|
|
911
|
+
const indexPath = join5(projectDir, ".trie", "memory", "issues.json");
|
|
912
|
+
try {
|
|
913
|
+
if (existsSync3(indexPath)) {
|
|
914
|
+
const content = await readFile5(indexPath, "utf-8");
|
|
915
|
+
return JSON.parse(content);
|
|
916
|
+
}
|
|
917
|
+
} catch {
|
|
918
|
+
}
|
|
919
|
+
return [];
|
|
920
|
+
}
|
|
921
|
+
async function updateIssueIndex(newIssues, projectDir) {
|
|
922
|
+
const memoryDir = join5(projectDir, ".trie", "memory");
|
|
923
|
+
const indexPath = join5(memoryDir, "issues.json");
|
|
924
|
+
await mkdir4(memoryDir, { recursive: true });
|
|
925
|
+
let existing = await loadIssueIndex(projectDir);
|
|
926
|
+
const hashSet = new Set(existing.map((i) => i.hash));
|
|
927
|
+
const toAdd = newIssues.filter((i) => !hashSet.has(i.hash));
|
|
928
|
+
existing = [...existing, ...toAdd];
|
|
929
|
+
if (existing.length > 500) {
|
|
930
|
+
const { summary, remaining } = await compactOldIssues(existing, {
|
|
931
|
+
keepDays: 30,
|
|
932
|
+
minIssuesToCompact: 100
|
|
933
|
+
});
|
|
934
|
+
if (summary) {
|
|
935
|
+
await saveCompactedSummary(summary, projectDir);
|
|
936
|
+
existing = remaining;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (existing.length > 1e3) {
|
|
940
|
+
existing = existing.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()).slice(0, 1e3);
|
|
941
|
+
}
|
|
942
|
+
await saveIssueIndex(existing, projectDir);
|
|
943
|
+
}
|
|
944
|
+
async function saveIssueIndex(issues, projectDir) {
|
|
945
|
+
const indexPath = join5(projectDir, ".trie", "memory", "issues.json");
|
|
946
|
+
await writeFile4(indexPath, JSON.stringify(issues, null, 2));
|
|
947
|
+
}
|
|
948
|
+
function hashIssue(issue) {
|
|
949
|
+
const content = `${issue.issue}|${issue.file}|${issue.severity}|${issue.agent}`;
|
|
950
|
+
let hash = 0;
|
|
951
|
+
for (let i = 0; i < content.length; i++) {
|
|
952
|
+
const char = content.charCodeAt(i);
|
|
953
|
+
hash = (hash << 5) - hash + char;
|
|
954
|
+
hash = hash & hash;
|
|
955
|
+
}
|
|
956
|
+
return Math.abs(hash).toString(36);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/memory/global-memory.ts
|
|
960
|
+
import { mkdir as mkdir5, writeFile as writeFile5, readFile as readFile6, readdir as readdir3 } from "fs/promises";
|
|
961
|
+
import { existsSync as existsSync4 } from "fs";
|
|
962
|
+
import { join as join6 } from "path";
|
|
963
|
+
import { homedir } from "os";
|
|
964
|
+
var GLOBAL_TRIE_DIR = join6(homedir(), ".trie");
|
|
965
|
+
var GLOBAL_MEMORY_DIR = join6(GLOBAL_TRIE_DIR, "memory");
|
|
966
|
+
async function recordToGlobalMemory(issues, projectName, projectPath, healthScore = 0) {
|
|
967
|
+
await mkdir5(GLOBAL_MEMORY_DIR, { recursive: true });
|
|
968
|
+
await mkdir5(join6(GLOBAL_MEMORY_DIR, "projects"), { recursive: true });
|
|
969
|
+
const patterns = await loadGlobalPatterns();
|
|
970
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
971
|
+
for (const issue of issues) {
|
|
972
|
+
const patternId = extractPatternId(issue);
|
|
973
|
+
const existing = patterns.find((p) => p.id === patternId);
|
|
974
|
+
if (existing) {
|
|
975
|
+
existing.occurrences++;
|
|
976
|
+
existing.lastSeen = now;
|
|
977
|
+
if (!existing.projects.includes(projectName)) {
|
|
978
|
+
existing.projects.push(projectName);
|
|
979
|
+
}
|
|
980
|
+
} else {
|
|
981
|
+
patterns.push({
|
|
982
|
+
id: patternId,
|
|
983
|
+
pattern: issue.issue.slice(0, 200),
|
|
984
|
+
description: issue.fix.slice(0, 200),
|
|
985
|
+
severity: issue.severity,
|
|
986
|
+
agent: issue.agent,
|
|
987
|
+
occurrences: 1,
|
|
988
|
+
projects: [projectName],
|
|
989
|
+
firstSeen: now,
|
|
990
|
+
lastSeen: now
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
await saveGlobalPatterns(patterns);
|
|
995
|
+
const summaryPath = join6(GLOBAL_MEMORY_DIR, "projects", `${sanitizeName(projectName)}.json`);
|
|
996
|
+
const summary = {
|
|
997
|
+
name: projectName,
|
|
998
|
+
path: projectPath,
|
|
999
|
+
lastScan: now,
|
|
1000
|
+
healthScore,
|
|
1001
|
+
totalIssues: issues.length,
|
|
1002
|
+
patterns: [...new Set(issues.map((i) => extractPatternId(i)))]
|
|
1003
|
+
};
|
|
1004
|
+
await writeFile5(summaryPath, JSON.stringify(summary, null, 2));
|
|
1005
|
+
}
|
|
1006
|
+
async function findCrossProjectPatterns(minOccurrences = 2) {
|
|
1007
|
+
const patterns = await loadGlobalPatterns();
|
|
1008
|
+
return patterns.filter((p) => p.projects.length >= minOccurrences).sort((a, b) => b.occurrences - a.occurrences);
|
|
1009
|
+
}
|
|
1010
|
+
async function listTrackedProjects() {
|
|
1011
|
+
const projectsDir = join6(GLOBAL_MEMORY_DIR, "projects");
|
|
1012
|
+
try {
|
|
1013
|
+
if (!existsSync4(projectsDir)) return [];
|
|
1014
|
+
const files = await readdir3(projectsDir);
|
|
1015
|
+
const summaries = [];
|
|
1016
|
+
for (const file of files) {
|
|
1017
|
+
if (!file.endsWith(".json")) continue;
|
|
1018
|
+
try {
|
|
1019
|
+
const content = await readFile6(join6(projectsDir, file), "utf-8");
|
|
1020
|
+
summaries.push(JSON.parse(content));
|
|
1021
|
+
} catch {
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return summaries.sort(
|
|
1025
|
+
(a, b) => new Date(b.lastScan).getTime() - new Date(a.lastScan).getTime()
|
|
1026
|
+
);
|
|
1027
|
+
} catch {
|
|
1028
|
+
return [];
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
async function getGlobalMemoryStats() {
|
|
1032
|
+
const patterns = await loadGlobalPatterns();
|
|
1033
|
+
const projects = await listTrackedProjects();
|
|
1034
|
+
const patternsByAgent = {};
|
|
1035
|
+
for (const pattern of patterns) {
|
|
1036
|
+
patternsByAgent[pattern.agent] = (patternsByAgent[pattern.agent] || 0) + 1;
|
|
1037
|
+
}
|
|
1038
|
+
return {
|
|
1039
|
+
totalPatterns: patterns.length,
|
|
1040
|
+
crossProjectPatterns: patterns.filter((p) => p.projects.length >= 2).length,
|
|
1041
|
+
trackedProjects: projects.length,
|
|
1042
|
+
totalOccurrences: patterns.reduce((sum, p) => sum + p.occurrences, 0),
|
|
1043
|
+
fixedPatterns: patterns.filter((p) => p.fixApplied).length,
|
|
1044
|
+
patternsByAgent
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
async function updateGlobalMemoryMd() {
|
|
1048
|
+
const patterns = await loadGlobalPatterns();
|
|
1049
|
+
const crossProject = patterns.filter((p) => p.projects.length >= 2);
|
|
1050
|
+
const projects = await listTrackedProjects();
|
|
1051
|
+
const lines = [
|
|
1052
|
+
"# Global Trie Memory",
|
|
1053
|
+
"",
|
|
1054
|
+
"> Auto-generated file tracking patterns across all your projects.",
|
|
1055
|
+
`> Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1056
|
+
"",
|
|
1057
|
+
"## Summary",
|
|
1058
|
+
"",
|
|
1059
|
+
`- **Projects tracked:** ${projects.length}`,
|
|
1060
|
+
`- **Total patterns:** ${patterns.length}`,
|
|
1061
|
+
`- **Cross-project patterns:** ${crossProject.length}`,
|
|
1062
|
+
"",
|
|
1063
|
+
"## Cross-Project Patterns",
|
|
1064
|
+
"",
|
|
1065
|
+
"These issues appear in multiple projects:",
|
|
1066
|
+
""
|
|
1067
|
+
];
|
|
1068
|
+
for (const p of crossProject.slice(0, 20)) {
|
|
1069
|
+
lines.push(
|
|
1070
|
+
`### ${p.pattern.slice(0, 60)}${p.pattern.length > 60 ? "..." : ""}`,
|
|
1071
|
+
"",
|
|
1072
|
+
`- **Severity:** ${p.severity}`,
|
|
1073
|
+
`- **Agent:** ${p.agent}`,
|
|
1074
|
+
`- **Occurrences:** ${p.occurrences} across ${p.projects.length} projects`,
|
|
1075
|
+
`- **Projects:** ${p.projects.slice(0, 5).join(", ")}${p.projects.length > 5 ? "..." : ""}`
|
|
1076
|
+
);
|
|
1077
|
+
if (p.fixApplied) {
|
|
1078
|
+
lines.push(`- **Fixed in:** ${p.fixApplied.project} on ${p.fixApplied.timestamp.split("T")[0]}`);
|
|
1079
|
+
} else {
|
|
1080
|
+
lines.push("- **Status:** Not fixed");
|
|
1081
|
+
}
|
|
1082
|
+
lines.push("");
|
|
1083
|
+
}
|
|
1084
|
+
lines.push(
|
|
1085
|
+
"## Tracked Projects",
|
|
1086
|
+
"",
|
|
1087
|
+
"| Project | Last Scan | Health | Issues |",
|
|
1088
|
+
"|---------|-----------|--------|--------|"
|
|
1089
|
+
);
|
|
1090
|
+
for (const p of projects.slice(0, 20)) {
|
|
1091
|
+
lines.push(`| ${p.name} | ${p.lastScan.split("T")[0]} | ${p.healthScore}% | ${p.totalIssues} |`);
|
|
1092
|
+
}
|
|
1093
|
+
lines.push("", "---", "", "*This file is auto-generated by Trie. Do not edit manually.*");
|
|
1094
|
+
await mkdir5(GLOBAL_MEMORY_DIR, { recursive: true });
|
|
1095
|
+
await writeFile5(join6(GLOBAL_MEMORY_DIR, "GLOBAL_MEMORY.md"), lines.join("\n"));
|
|
1096
|
+
}
|
|
1097
|
+
async function searchGlobalPatterns(query, options = {}) {
|
|
1098
|
+
const patterns = await loadGlobalPatterns();
|
|
1099
|
+
const limit = options.limit || 10;
|
|
1100
|
+
const queryTerms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
|
|
1101
|
+
const scored = patterns.filter((p) => {
|
|
1102
|
+
if (options.severity && !options.severity.includes(p.severity)) return false;
|
|
1103
|
+
if (options.agent && p.agent !== options.agent) return false;
|
|
1104
|
+
return true;
|
|
1105
|
+
}).map((p) => {
|
|
1106
|
+
const text = `${p.pattern} ${p.description} ${p.agent}`.toLowerCase();
|
|
1107
|
+
let score = 0;
|
|
1108
|
+
for (const term of queryTerms) {
|
|
1109
|
+
if (text.includes(term)) score++;
|
|
1110
|
+
}
|
|
1111
|
+
return { pattern: p, score };
|
|
1112
|
+
}).filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1113
|
+
return scored.map((s) => s.pattern);
|
|
1114
|
+
}
|
|
1115
|
+
async function loadGlobalPatterns() {
|
|
1116
|
+
const patternsPath = join6(GLOBAL_MEMORY_DIR, "global-patterns.json");
|
|
1117
|
+
try {
|
|
1118
|
+
if (existsSync4(patternsPath)) {
|
|
1119
|
+
const content = await readFile6(patternsPath, "utf-8");
|
|
1120
|
+
return JSON.parse(content);
|
|
1121
|
+
}
|
|
1122
|
+
} catch {
|
|
1123
|
+
}
|
|
1124
|
+
return [];
|
|
1125
|
+
}
|
|
1126
|
+
async function saveGlobalPatterns(patterns) {
|
|
1127
|
+
await mkdir5(GLOBAL_MEMORY_DIR, { recursive: true });
|
|
1128
|
+
const patternsPath = join6(GLOBAL_MEMORY_DIR, "global-patterns.json");
|
|
1129
|
+
const pruned = patterns.sort((a, b) => new Date(b.lastSeen).getTime() - new Date(a.lastSeen).getTime()).slice(0, 500);
|
|
1130
|
+
await writeFile5(patternsPath, JSON.stringify(pruned, null, 2));
|
|
1131
|
+
}
|
|
1132
|
+
function extractPatternId(issue) {
|
|
1133
|
+
const normalized = issue.issue.toLowerCase().replace(/`[^`]+`/g, "CODE").replace(/\b\d+\b/g, "N").replace(/['"]/g, "").slice(0, 100);
|
|
1134
|
+
let hash = 0;
|
|
1135
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
1136
|
+
const char = normalized.charCodeAt(i);
|
|
1137
|
+
hash = (hash << 5) - hash + char;
|
|
1138
|
+
hash = hash & hash;
|
|
1139
|
+
}
|
|
1140
|
+
return `${issue.agent}-${issue.severity}-${Math.abs(hash).toString(36)}`;
|
|
1141
|
+
}
|
|
1142
|
+
function sanitizeName(name) {
|
|
1143
|
+
return name.replace(/[^a-zA-Z0-9-_]/g, "-").toLowerCase();
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// src/utils/context-state.ts
|
|
1147
|
+
import { readFile as readFile7, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
|
|
1148
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1149
|
+
import { join as join7, basename } from "path";
|
|
1150
|
+
var AGENTS_MD_PATH = ".trie/AGENTS.md";
|
|
1151
|
+
var STATE_JSON_PATH = ".trie/state.json";
|
|
1152
|
+
async function loadContextState() {
|
|
1153
|
+
const workDir = getWorkingDirectory(void 0, true);
|
|
1154
|
+
const statePath = join7(workDir, STATE_JSON_PATH);
|
|
1155
|
+
const defaults = getDefaultState();
|
|
1156
|
+
try {
|
|
1157
|
+
if (existsSync5(statePath)) {
|
|
1158
|
+
const content = await readFile7(statePath, "utf-8");
|
|
1159
|
+
const loaded = JSON.parse(content);
|
|
1160
|
+
return {
|
|
1161
|
+
...defaults,
|
|
1162
|
+
...loaded,
|
|
1163
|
+
skills: loaded.skills || defaults.skills
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
} catch {
|
|
1167
|
+
}
|
|
1168
|
+
return defaults;
|
|
1169
|
+
}
|
|
1170
|
+
async function saveContextState(state) {
|
|
1171
|
+
const workDir = getWorkingDirectory(void 0, true);
|
|
1172
|
+
const trieDir = join7(workDir, ".trie");
|
|
1173
|
+
const statePath = join7(workDir, STATE_JSON_PATH);
|
|
1174
|
+
await mkdir6(trieDir, { recursive: true });
|
|
1175
|
+
await writeFile6(statePath, JSON.stringify(state, null, 2));
|
|
1176
|
+
}
|
|
1177
|
+
async function updateContextAfterScan(results, filesScanned, contextSignals, duration) {
|
|
1178
|
+
const state = await loadContextState();
|
|
1179
|
+
const workDir = getWorkingDirectory(void 0, true);
|
|
1180
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1181
|
+
const allIssues = results.flatMap((r) => r.issues);
|
|
1182
|
+
const issueCounts = {
|
|
1183
|
+
critical: allIssues.filter((i) => i.severity === "critical").length,
|
|
1184
|
+
serious: allIssues.filter((i) => i.severity === "serious").length,
|
|
1185
|
+
moderate: allIssues.filter((i) => i.severity === "moderate").length,
|
|
1186
|
+
low: allIssues.filter((i) => i.severity === "low").length,
|
|
1187
|
+
total: allIssues.length
|
|
1188
|
+
};
|
|
1189
|
+
const fileIssueMap = /* @__PURE__ */ new Map();
|
|
1190
|
+
for (const issue of allIssues) {
|
|
1191
|
+
const count = fileIssueMap.get(issue.file) || 0;
|
|
1192
|
+
fileIssueMap.set(issue.file, count + 1);
|
|
1193
|
+
}
|
|
1194
|
+
const hotFiles = Array.from(fileIssueMap.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([file, issueCount]) => ({ file, issueCount }));
|
|
1195
|
+
const scanSummary = {
|
|
1196
|
+
timestamp: now,
|
|
1197
|
+
agents: results.map((r) => r.agent),
|
|
1198
|
+
filesScanned,
|
|
1199
|
+
issues: issueCounts,
|
|
1200
|
+
duration,
|
|
1201
|
+
hotFiles
|
|
1202
|
+
};
|
|
1203
|
+
for (const result of results) {
|
|
1204
|
+
state.agentStatus[result.agent] = {
|
|
1205
|
+
lastRun: now,
|
|
1206
|
+
issuesFound: result.issues.length
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
const criticalPenalty = issueCounts.critical * 25;
|
|
1210
|
+
const seriousPenalty = issueCounts.serious * 10;
|
|
1211
|
+
const moderatePenalty = issueCounts.moderate * 3;
|
|
1212
|
+
const lowPenalty = issueCounts.low * 1;
|
|
1213
|
+
const totalPenalty = Math.min(100, criticalPenalty + seriousPenalty + moderatePenalty + lowPenalty);
|
|
1214
|
+
state.healthScore = Math.max(0, 100 - totalPenalty);
|
|
1215
|
+
state.activePriorities = generatePriorities(issueCounts, contextSignals);
|
|
1216
|
+
state.contextSignals = { ...state.contextSignals, ...contextSignals };
|
|
1217
|
+
state.scanHistory = [scanSummary, ...state.scanHistory.slice(0, 19)];
|
|
1218
|
+
state.lastScan = scanSummary;
|
|
1219
|
+
await saveContextState(state);
|
|
1220
|
+
await updateAgentsMd(state);
|
|
1221
|
+
if (allIssues.length > 0) {
|
|
1222
|
+
const projectName = basename(workDir);
|
|
1223
|
+
try {
|
|
1224
|
+
await storeIssues(allIssues, projectName, workDir);
|
|
1225
|
+
await recordToGlobalMemory(allIssues, projectName, workDir, state.healthScore);
|
|
1226
|
+
await updateGlobalMemoryMd();
|
|
1227
|
+
} catch {
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
async function updateAgentsMd(state) {
|
|
1232
|
+
const workDir = getWorkingDirectory(void 0, true);
|
|
1233
|
+
const mdPath = join7(workDir, AGENTS_MD_PATH);
|
|
1234
|
+
let content;
|
|
1235
|
+
try {
|
|
1236
|
+
content = await readFile7(mdPath, "utf-8");
|
|
1237
|
+
} catch {
|
|
1238
|
+
content = getAgentsMdTemplate();
|
|
1239
|
+
}
|
|
1240
|
+
content = updateSection(content, "Project State", generateProjectStateTable(state));
|
|
1241
|
+
content = updateSection(content, "Active Priorities", generatePrioritiesList(state));
|
|
1242
|
+
content = updateSection(content, "Agent Status", generateAgentStatusTable(state));
|
|
1243
|
+
content = updateSection(content, "Recent Scan History", generateScanHistoryTable(state));
|
|
1244
|
+
content = updateSection(content, "Context Signals Detected", generateContextSignals(state));
|
|
1245
|
+
content = updateSection(content, "Risk Assessment", generateRiskAssessment(state));
|
|
1246
|
+
content = updateSection(content, "Hot Files", generateHotFilesSection(state));
|
|
1247
|
+
content = content.replace(
|
|
1248
|
+
/Last updated:.*$/m,
|
|
1249
|
+
`Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
1250
|
+
);
|
|
1251
|
+
await writeFile6(mdPath, content);
|
|
1252
|
+
}
|
|
1253
|
+
function updateSection(content, sectionName, newContent) {
|
|
1254
|
+
const sectionRegex = new RegExp(
|
|
1255
|
+
`(### ${sectionName}[\\s\\S]*?)(?=###|---|
|
|
1256
|
+
## |$)`,
|
|
1257
|
+
"g"
|
|
1258
|
+
);
|
|
1259
|
+
const replacement = `### ${sectionName}
|
|
1260
|
+
${newContent}
|
|
1261
|
+
|
|
1262
|
+
`;
|
|
1263
|
+
if (content.match(sectionRegex)) {
|
|
1264
|
+
return content.replace(sectionRegex, replacement);
|
|
1265
|
+
}
|
|
1266
|
+
return content;
|
|
1267
|
+
}
|
|
1268
|
+
function generateProjectStateTable(state) {
|
|
1269
|
+
const lastScan = state.lastScan;
|
|
1270
|
+
const lastScanDate = lastScan ? new Date(lastScan.timestamp).toLocaleString() : "Never";
|
|
1271
|
+
const criticalCount = lastScan?.issues.critical ?? 0;
|
|
1272
|
+
const totalTasks = lastScan?.issues.total ?? 0;
|
|
1273
|
+
return `| Metric | Value | Updated |
|
|
1274
|
+
|--------|-------|---------|
|
|
1275
|
+
| Last Scan | ${lastScanDate} | ${lastScan ? "Auto" : "-"} |
|
|
1276
|
+
| Critical Issues | ${criticalCount} | ${lastScan ? "Auto" : "-"} |
|
|
1277
|
+
| Open Tasks | ${totalTasks} | ${lastScan ? "Auto" : "-"} |
|
|
1278
|
+
| Health Score | ${state.healthScore}% | ${lastScan ? "Auto" : "-"} |`;
|
|
1279
|
+
}
|
|
1280
|
+
function generatePrioritiesList(state) {
|
|
1281
|
+
if (state.activePriorities.length === 0) {
|
|
1282
|
+
return "_No active priorities. Run a scan to identify issues._";
|
|
1283
|
+
}
|
|
1284
|
+
return state.activePriorities.map((p, i) => `${i + 1}. ${p}`).join("\n");
|
|
1285
|
+
}
|
|
1286
|
+
function generatePriorities(issues, contextSignals) {
|
|
1287
|
+
const priorities = [];
|
|
1288
|
+
if (issues.critical > 0) {
|
|
1289
|
+
priorities.push(`\u{1F6A8} Fix ${issues.critical} critical security issue${issues.critical > 1 ? "s" : ""} immediately`);
|
|
1290
|
+
}
|
|
1291
|
+
if (issues.serious > 0) {
|
|
1292
|
+
priorities.push(`\u26A0\uFE0F Address ${issues.serious} serious issue${issues.serious > 1 ? "s" : ""} before deployment`);
|
|
1293
|
+
}
|
|
1294
|
+
if (contextSignals.touchesAuth && issues.critical === 0) {
|
|
1295
|
+
priorities.push("\u2705 Auth code reviewed - continue monitoring");
|
|
1296
|
+
}
|
|
1297
|
+
if (contextSignals.touchesPayments) {
|
|
1298
|
+
priorities.push("\u{1F4B3} Payment code detected - ensure PCI compliance");
|
|
1299
|
+
}
|
|
1300
|
+
if (contextSignals.touchesUserData) {
|
|
1301
|
+
priorities.push("\u{1F510} User data handling detected - verify privacy compliance");
|
|
1302
|
+
}
|
|
1303
|
+
if (issues.moderate > 5) {
|
|
1304
|
+
priorities.push(`\u{1F4CB} Schedule time to address ${issues.moderate} moderate issues`);
|
|
1305
|
+
}
|
|
1306
|
+
if (priorities.length === 0) {
|
|
1307
|
+
priorities.push("\u2728 No critical issues - focus on feature development");
|
|
1308
|
+
}
|
|
1309
|
+
return priorities.slice(0, 5);
|
|
1310
|
+
}
|
|
1311
|
+
function generateAgentStatusTable(state) {
|
|
1312
|
+
const builtInAgents = [
|
|
1313
|
+
"security",
|
|
1314
|
+
"privacy",
|
|
1315
|
+
"legal",
|
|
1316
|
+
"accessibility",
|
|
1317
|
+
"bugs",
|
|
1318
|
+
"design",
|
|
1319
|
+
"architecture",
|
|
1320
|
+
"performance",
|
|
1321
|
+
"devops",
|
|
1322
|
+
"soc2",
|
|
1323
|
+
"e2e",
|
|
1324
|
+
"typecheck",
|
|
1325
|
+
"visual-qa",
|
|
1326
|
+
"data-flow"
|
|
1327
|
+
];
|
|
1328
|
+
let table = `| Agent | Status | Last Run | Issues Found |
|
|
1329
|
+
|-------|--------|----------|--------------|`;
|
|
1330
|
+
for (const agent of builtInAgents) {
|
|
1331
|
+
const status = state.agentStatus[agent];
|
|
1332
|
+
const lastRun = status?.lastRun ? new Date(status.lastRun).toLocaleDateString() : "Never";
|
|
1333
|
+
const issues = status?.issuesFound ?? "-";
|
|
1334
|
+
const statusEmoji = status ? "\u2705" : "\u23F8\uFE0F";
|
|
1335
|
+
table += `
|
|
1336
|
+
| ${agent} | ${statusEmoji} Ready | ${lastRun} | ${issues} |`;
|
|
1337
|
+
}
|
|
1338
|
+
return table;
|
|
1339
|
+
}
|
|
1340
|
+
function generateScanHistoryTable(state) {
|
|
1341
|
+
if (state.scanHistory.length === 0) {
|
|
1342
|
+
return `| Date | Agents | Files | Issues | Duration |
|
|
1343
|
+
|------|--------|-------|--------|----------|
|
|
1344
|
+
| - | - | - | - | - |`;
|
|
1345
|
+
}
|
|
1346
|
+
let table = `| Date | Agents | Files | Issues | Duration |
|
|
1347
|
+
|------|--------|-------|--------|----------|`;
|
|
1348
|
+
for (const scan of state.scanHistory.slice(0, 10)) {
|
|
1349
|
+
const date = new Date(scan.timestamp).toLocaleDateString();
|
|
1350
|
+
const agents = scan.agents.slice(0, 3).join(", ") + (scan.agents.length > 3 ? "..." : "");
|
|
1351
|
+
const duration = `${(scan.duration / 1e3).toFixed(1)}s`;
|
|
1352
|
+
table += `
|
|
1353
|
+
| ${date} | ${agents} | ${scan.filesScanned} | ${scan.issues.total} | ${duration} |`;
|
|
1354
|
+
}
|
|
1355
|
+
return table;
|
|
1356
|
+
}
|
|
1357
|
+
function generateContextSignals(state) {
|
|
1358
|
+
const signals = [
|
|
1359
|
+
"touchesAuth",
|
|
1360
|
+
"touchesPayments",
|
|
1361
|
+
"touchesUserData",
|
|
1362
|
+
"touchesAPI",
|
|
1363
|
+
"touchesDatabase",
|
|
1364
|
+
"touchesCrypto"
|
|
1365
|
+
];
|
|
1366
|
+
return signals.map((s) => {
|
|
1367
|
+
const value = state.contextSignals[s];
|
|
1368
|
+
const emoji = value === true ? "\u2705" : value === false ? "\u274C" : "\u2753";
|
|
1369
|
+
return `- \`${s}\`: ${emoji} ${value === void 0 ? "Unknown" : value ? "Yes" : "No"}`;
|
|
1370
|
+
}).join("\n");
|
|
1371
|
+
}
|
|
1372
|
+
function generateRiskAssessment(state) {
|
|
1373
|
+
const score = state.healthScore;
|
|
1374
|
+
let riskLevel;
|
|
1375
|
+
let confidence;
|
|
1376
|
+
if (state.lastScan === null) {
|
|
1377
|
+
return `- Overall Risk: Unknown
|
|
1378
|
+
- Confidence: 0%`;
|
|
1379
|
+
}
|
|
1380
|
+
if (score >= 90) {
|
|
1381
|
+
riskLevel = "\u{1F7E2} Low";
|
|
1382
|
+
confidence = 95;
|
|
1383
|
+
} else if (score >= 70) {
|
|
1384
|
+
riskLevel = "\u{1F7E1} Medium";
|
|
1385
|
+
confidence = 85;
|
|
1386
|
+
} else if (score >= 50) {
|
|
1387
|
+
riskLevel = "\u{1F7E0} High";
|
|
1388
|
+
confidence = 80;
|
|
1389
|
+
} else {
|
|
1390
|
+
riskLevel = "\u{1F534} Critical";
|
|
1391
|
+
confidence = 90;
|
|
1392
|
+
}
|
|
1393
|
+
return `- Overall Risk: ${riskLevel}
|
|
1394
|
+
- Health Score: ${score}%
|
|
1395
|
+
- Confidence: ${confidence}%`;
|
|
1396
|
+
}
|
|
1397
|
+
function generateHotFilesSection(state) {
|
|
1398
|
+
if (!state.lastScan || state.lastScan.hotFiles.length === 0) {
|
|
1399
|
+
return "_Run a scan to identify hot files._";
|
|
1400
|
+
}
|
|
1401
|
+
return state.lastScan.hotFiles.map((f) => `- \`${f.file}\` - ${f.issueCount} issue${f.issueCount > 1 ? "s" : ""}`).join("\n");
|
|
1402
|
+
}
|
|
1403
|
+
function getDefaultState() {
|
|
1404
|
+
return {
|
|
1405
|
+
lastScan: null,
|
|
1406
|
+
healthScore: 0,
|
|
1407
|
+
activePriorities: [
|
|
1408
|
+
"Initial setup required - run first scan with `trie scan`",
|
|
1409
|
+
"Configure agents in `.trie/config.json`",
|
|
1410
|
+
"Set up CI/CD integration"
|
|
1411
|
+
],
|
|
1412
|
+
contextSignals: {},
|
|
1413
|
+
agentStatus: {},
|
|
1414
|
+
scanHistory: [],
|
|
1415
|
+
customAgents: [],
|
|
1416
|
+
skills: {},
|
|
1417
|
+
environment: detectEnvironment()
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
function detectEnvironment() {
|
|
1421
|
+
if (process.env.GITHUB_ACTIONS) return "github-actions";
|
|
1422
|
+
if (process.env.GITLAB_CI) return "gitlab-ci";
|
|
1423
|
+
if (process.env.CI) return "ci";
|
|
1424
|
+
const parent = process.env._ || "";
|
|
1425
|
+
if (parent.includes("cursor")) return "cursor";
|
|
1426
|
+
if (parent.includes("claude")) return "claude-code";
|
|
1427
|
+
return "cli";
|
|
1428
|
+
}
|
|
1429
|
+
async function recordSkillInstalled(params) {
|
|
1430
|
+
const state = await loadContextState();
|
|
1431
|
+
state.skills[params.name] = {
|
|
1432
|
+
source: params.source,
|
|
1433
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1434
|
+
timesApplied: 0,
|
|
1435
|
+
appliedBy: []
|
|
1436
|
+
};
|
|
1437
|
+
await saveContextState(state);
|
|
1438
|
+
}
|
|
1439
|
+
async function recordSkillUsage(params) {
|
|
1440
|
+
const state = await loadContextState();
|
|
1441
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1442
|
+
for (const skillName of params.skillNames) {
|
|
1443
|
+
const skillRecord = state.skills[skillName];
|
|
1444
|
+
if (skillRecord) {
|
|
1445
|
+
skillRecord.timesApplied++;
|
|
1446
|
+
skillRecord.lastApplied = now;
|
|
1447
|
+
if (!skillRecord.appliedBy.includes(params.agentName)) {
|
|
1448
|
+
skillRecord.appliedBy.push(params.agentName);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
const agentStatus = state.agentStatus[params.agentName];
|
|
1453
|
+
if (agentStatus) {
|
|
1454
|
+
agentStatus.skillsApplied = params.skillNames;
|
|
1455
|
+
}
|
|
1456
|
+
await saveContextState(state);
|
|
1457
|
+
}
|
|
1458
|
+
function getAgentsMdTemplate() {
|
|
1459
|
+
return `# Trie Agent Context
|
|
1460
|
+
|
|
1461
|
+
> **Auto-generated file** - Updated automatically when agents run.
|
|
1462
|
+
> Last updated: Never (initial state)
|
|
1463
|
+
|
|
1464
|
+
This file provides prioritized context for all AI coding assistants working with this codebase.
|
|
1465
|
+
Agents should read this file first and update it after completing scans.
|
|
1466
|
+
|
|
1467
|
+
---
|
|
1468
|
+
|
|
1469
|
+
## Quick Context (Read First)
|
|
1470
|
+
|
|
1471
|
+
### Project State
|
|
1472
|
+
| Metric | Value | Updated |
|
|
1473
|
+
|--------|-------|---------|
|
|
1474
|
+
| Last Scan | Never | - |
|
|
1475
|
+
| Critical Issues | 0 | - |
|
|
1476
|
+
| Open Tasks | 0 | - |
|
|
1477
|
+
| Health Score | Unknown | - |
|
|
1478
|
+
|
|
1479
|
+
### Active Priorities
|
|
1480
|
+
1. Initial setup required - run first scan with \`trie scan\`
|
|
1481
|
+
2. Configure agents in \`.trie/config.json\`
|
|
1482
|
+
3. Set up CI/CD integration
|
|
1483
|
+
|
|
1484
|
+
### Hot Files
|
|
1485
|
+
_Run a scan to identify hot files._
|
|
1486
|
+
|
|
1487
|
+
---
|
|
1488
|
+
|
|
1489
|
+
## Agent Status
|
|
1490
|
+
|
|
1491
|
+
### Agent Status
|
|
1492
|
+
| Agent | Status | Last Run | Issues Found |
|
|
1493
|
+
|-------|--------|----------|--------------|
|
|
1494
|
+
| security | Ready | Never | - |
|
|
1495
|
+
| privacy | Ready | Never | - |
|
|
1496
|
+
| bugs | Ready | Never | - |
|
|
1497
|
+
|
|
1498
|
+
### Recent Scan History
|
|
1499
|
+
| Date | Agents | Files | Issues | Duration |
|
|
1500
|
+
|------|--------|-------|--------|----------|
|
|
1501
|
+
| - | - | - | - | - |
|
|
1502
|
+
|
|
1503
|
+
---
|
|
1504
|
+
|
|
1505
|
+
## Context Analysis
|
|
1506
|
+
|
|
1507
|
+
### Context Signals Detected
|
|
1508
|
+
- \`touchesAuth\`: Unknown
|
|
1509
|
+
- \`touchesPayments\`: Unknown
|
|
1510
|
+
- \`touchesUserData\`: Unknown
|
|
1511
|
+
- \`touchesAPI\`: Unknown
|
|
1512
|
+
- \`touchesDatabase\`: Unknown
|
|
1513
|
+
- \`touchesCrypto\`: Unknown
|
|
1514
|
+
|
|
1515
|
+
### Risk Assessment
|
|
1516
|
+
- Overall Risk: Unknown
|
|
1517
|
+
- Confidence: 0%
|
|
1518
|
+
|
|
1519
|
+
---
|
|
1520
|
+
|
|
1521
|
+
*This file is maintained by Trie agents. Manual edits will be preserved in non-auto sections.*
|
|
1522
|
+
`;
|
|
1523
|
+
}
|
|
1524
|
+
async function getContextForAI() {
|
|
1525
|
+
const state = await loadContextState();
|
|
1526
|
+
const workDir = getWorkingDirectory(void 0, true);
|
|
1527
|
+
const lines = [];
|
|
1528
|
+
if (projectInfoExists(workDir)) {
|
|
1529
|
+
const projectInfo = await loadProjectInfo(workDir);
|
|
1530
|
+
if (projectInfo) {
|
|
1531
|
+
lines.push(projectInfo);
|
|
1532
|
+
lines.push("");
|
|
1533
|
+
lines.push("---");
|
|
1534
|
+
lines.push("");
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
lines.push(
|
|
1538
|
+
"## Trie Scan Context",
|
|
1539
|
+
"",
|
|
1540
|
+
`**Health Score:** ${state.healthScore}%`,
|
|
1541
|
+
`**Last Scan:** ${state.lastScan ? new Date(state.lastScan.timestamp).toLocaleString() : "Never"}`,
|
|
1542
|
+
"",
|
|
1543
|
+
"**Active Priorities:**",
|
|
1544
|
+
...state.activePriorities.map((p) => `- ${p}`),
|
|
1545
|
+
""
|
|
1546
|
+
);
|
|
1547
|
+
if (state.lastScan) {
|
|
1548
|
+
lines.push(
|
|
1549
|
+
"**Recent Issues:**",
|
|
1550
|
+
`- Critical: ${state.lastScan.issues.critical}`,
|
|
1551
|
+
`- Serious: ${state.lastScan.issues.serious}`,
|
|
1552
|
+
`- Moderate: ${state.lastScan.issues.moderate}`,
|
|
1553
|
+
`- Low: ${state.lastScan.issues.low}`,
|
|
1554
|
+
""
|
|
1555
|
+
);
|
|
1556
|
+
if (state.lastScan.hotFiles.length > 0) {
|
|
1557
|
+
lines.push(
|
|
1558
|
+
"**Hot Files (most issues):**",
|
|
1559
|
+
...state.lastScan.hotFiles.slice(0, 5).map((f) => `- ${f.file}: ${f.issueCount} issues`),
|
|
1560
|
+
""
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
return lines.join("\n");
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
export {
|
|
1568
|
+
parseSkillMd,
|
|
1569
|
+
installSkill,
|
|
1570
|
+
listInstalledSkills,
|
|
1571
|
+
removeSkill,
|
|
1572
|
+
projectInfoExists,
|
|
1573
|
+
loadProjectInfo,
|
|
1574
|
+
initProjectInfo,
|
|
1575
|
+
getProjectSection,
|
|
1576
|
+
updateProjectSection,
|
|
1577
|
+
appendToSection,
|
|
1578
|
+
getProjectSections,
|
|
1579
|
+
getProjectInfoStructured,
|
|
1580
|
+
getHistoricalInsights,
|
|
1581
|
+
searchIssues,
|
|
1582
|
+
findSimilarIssues,
|
|
1583
|
+
markIssueResolved,
|
|
1584
|
+
getMemoryStats,
|
|
1585
|
+
getRecentIssues,
|
|
1586
|
+
getDailyLogs,
|
|
1587
|
+
findCrossProjectPatterns,
|
|
1588
|
+
listTrackedProjects,
|
|
1589
|
+
getGlobalMemoryStats,
|
|
1590
|
+
updateGlobalMemoryMd,
|
|
1591
|
+
searchGlobalPatterns,
|
|
1592
|
+
loadContextState,
|
|
1593
|
+
updateContextAfterScan,
|
|
1594
|
+
recordSkillInstalled,
|
|
1595
|
+
recordSkillUsage,
|
|
1596
|
+
getContextForAI
|
|
1597
|
+
};
|
|
1598
|
+
//# sourceMappingURL=chunk-PZDQIFKO.js.map
|