@triedotdev/mcp 1.0.126 → 1.0.128

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.
@@ -7,7 +7,6 @@ import {
7
7
  import {
8
8
  getTrieDirectory
9
9
  } from "./chunk-45Y5TLQZ.js";
10
- import "./chunk-APMV77PU.js";
11
10
  import {
12
11
  __require
13
12
  } from "./chunk-DGUM43GV.js";
@@ -54,28 +53,38 @@ var CodebaseIndex = class {
54
53
  }
55
54
  /**
56
55
  * Add or update a file in the index
56
+ * Returns null if the file doesn't exist or can't be read
57
57
  */
58
58
  async indexFile(filePath) {
59
- const absolutePath = join(this.projectPath, filePath);
60
- const content = await readFile(absolutePath, "utf-8");
61
- const stats = await stat(absolutePath);
62
- const hash = createHash("sha256").update(content).digest("hex");
63
- const metadata = {
64
- path: filePath,
65
- hash,
66
- size: stats.size,
67
- lastModified: stats.mtimeMs,
68
- type: filePath.split(".").pop() || ""
69
- };
70
- const existing = this.trie.search(filePath);
71
- if (existing.found && existing.value) {
72
- if (existing.value.hash === hash) {
73
- metadata.violations = existing.value.violations;
74
- metadata.lastScanned = existing.value.lastScanned;
59
+ if (filePath.startsWith("/")) {
60
+ if (!filePath.toLowerCase().startsWith(this.projectPath.toLowerCase())) {
61
+ return null;
75
62
  }
76
63
  }
77
- this.trie.insert(filePath, metadata);
78
- return metadata;
64
+ const absolutePath = filePath.startsWith("/") ? filePath : join(this.projectPath, filePath);
65
+ try {
66
+ const content = await readFile(absolutePath, "utf-8");
67
+ const stats = await stat(absolutePath);
68
+ const hash = createHash("sha256").update(content).digest("hex");
69
+ const metadata = {
70
+ path: filePath,
71
+ hash,
72
+ size: stats.size,
73
+ lastModified: stats.mtimeMs,
74
+ type: filePath.split(".").pop() || ""
75
+ };
76
+ const existing = this.trie.search(filePath);
77
+ if (existing.found && existing.value) {
78
+ if (existing.value.hash === hash) {
79
+ metadata.violations = existing.value.violations;
80
+ metadata.lastScanned = existing.value.lastScanned;
81
+ }
82
+ }
83
+ this.trie.insert(filePath, metadata);
84
+ return metadata;
85
+ } catch (error) {
86
+ return null;
87
+ }
79
88
  }
80
89
  /**
81
90
  * Get file metadata from index
@@ -88,9 +97,12 @@ var CodebaseIndex = class {
88
97
  * Check if file has changed since last index
89
98
  */
90
99
  async hasChanged(filePath) {
100
+ if (filePath.startsWith("/") && !filePath.toLowerCase().startsWith(this.projectPath.toLowerCase())) {
101
+ return true;
102
+ }
91
103
  const metadata = this.getFile(filePath);
92
104
  if (!metadata) return true;
93
- const absolutePath = join(this.projectPath, filePath);
105
+ const absolutePath = filePath.startsWith("/") ? filePath : join(this.projectPath, filePath);
94
106
  try {
95
107
  const stats = await stat(absolutePath);
96
108
  if (stats.mtimeMs !== metadata.lastModified) return true;
@@ -153,6 +165,12 @@ var CodebaseIndex = class {
153
165
  }
154
166
  return metadata.violations;
155
167
  }
168
+ /**
169
+ * Check if the index is empty (needs initial indexing)
170
+ */
171
+ isEmpty() {
172
+ return this.trie.getWithPrefix("").length === 0;
173
+ }
156
174
  /**
157
175
  * Get statistics about the index
158
176
  */
@@ -191,7 +209,8 @@ var CodebaseIndex = class {
191
209
  return cleared;
192
210
  }
193
211
  };
212
+
194
213
  export {
195
214
  CodebaseIndex
196
215
  };
197
- //# sourceMappingURL=codebase-index-ECJYAEZJ.js.map
216
+ //# sourceMappingURL=chunk-6VIMBFUZ.js.map
@@ -0,0 +1 @@
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 } 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\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(require('fs').readFileSync(this.indexPath, 'utf-8'));\n if (raw.trie) {\n this.trie = Trie.fromJSON<FileMetadata>(raw.trie);\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 const data: CodebaseIndexData = {\n version: 1,\n lastUpdated: new Date().toISOString(),\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 metadata.violations = existing.value.violations;\n 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 metadata.violations.push({\n goalId,\n goalDescription,\n found,\n details,\n confidence,\n timestamp: Date.now(),\n });\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 } {\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 };\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 file.lastScanned = undefined;\n }\n this.trie.insert(file.path, file);\n }\n }\n\n return cleared;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,SAAS,UAAU,YAAY;AAC/B,SAAS,kBAAkB;AAC3B,SAAS,YAAY;AACrB,SAAS,kBAAkB;AA6BpB,IAAM,gBAAN,MAAoB;AAAA,EACjB,OAA2B,IAAI,KAAK;AAAA,EACpC;AAAA,EACA;AAAA,EAER,YAAY,aAAqB;AAC/B,SAAK,cAAc;AACnB,SAAK,YAAY,KAAK,iBAAiB,WAAW,GAAG,qBAAqB;AAC1E,SAAK,KAAK;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA,EAKQ,OAAa;AACnB,QAAI,CAAC,WAAW,KAAK,SAAS,EAAG;AAEjC,QAAI;AACF,YAAM,MAAM,KAAK,MAAM,UAAQ,IAAI,EAAE,aAAa,KAAK,WAAW,OAAO,CAAC;AAC1E,UAAI,IAAI,MAAM;AACZ,aAAK,OAAO,KAAK,SAAuB,IAAI,IAAI;AAAA,MAClD;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,kCAAkC,KAAK;AAAA,IACvD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,UAAM,OAA0B;AAAA,MAC9B,SAAS;AAAA,MACT,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,WAAW,KAAK,KAAK,cAAc,EAAE,EAAE;AAAA,MACvC,MAAM,KAAK,KAAK,OAAO;AAAA,IACzB;AAEA,UAAM,gBAAgB,KAAK,WAAW,IAAI;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UAAU,UAAgD;AAG9D,QAAI,SAAS,WAAW,GAAG,GAAG;AAE5B,UAAI,CAAC,SAAS,YAAY,EAAE,WAAW,KAAK,YAAY,YAAY,CAAC,GAAG;AACtE,eAAO;AAAA,MACT;AAAA,IAEF;AAIA,UAAM,eAAe,SAAS,WAAW,GAAG,IAAI,WAAW,KAAK,KAAK,aAAa,QAAQ;AAE1F,QAAI;AACF,YAAM,UAAU,MAAM,SAAS,cAAc,OAAO;AACpD,YAAM,QAAQ,MAAM,KAAK,YAAY;AACrC,YAAM,OAAO,WAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAE9D,YAAM,WAAyB;AAAA,QAC7B,MAAM;AAAA,QACN;AAAA,QACA,MAAM,MAAM;AAAA,QACZ,cAAc,MAAM;AAAA,QACpB,MAAM,SAAS,MAAM,GAAG,EAAE,IAAI,KAAK;AAAA,MACrC;AAGA,YAAM,WAAW,KAAK,KAAK,OAAO,QAAQ;AAC1C,UAAI,SAAS,SAAS,SAAS,OAAO;AAEpC,YAAI,SAAS,MAAM,SAAS,MAAM;AAChC,mBAAS,aAAa,SAAS,MAAM;AACrC,mBAAS,cAAc,SAAS,MAAM;AAAA,QACxC;AAAA,MACF;AAEA,WAAK,KAAK,OAAO,UAAU,QAAQ;AACnC,aAAO;AAAA,IACT,SAAS,OAAO;AAEd,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,UAAuC;AAC7C,UAAM,SAAS,KAAK,KAAK,OAAO,QAAQ;AACxC,WAAO,OAAO,SAAS,OAAO,QAAQ,OAAO,QAAQ;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,UAAoC;AAEnD,QAAI,SAAS,WAAW,GAAG,KAAK,CAAC,SAAS,YAAY,EAAE,WAAW,KAAK,YAAY,YAAY,CAAC,GAAG;AAClG,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,KAAK,QAAQ,QAAQ;AACtC,QAAI,CAAC,SAAU,QAAO;AAGtB,UAAM,eAAe,SAAS,WAAW,GAAG,IAAI,WAAW,KAAK,KAAK,aAAa,QAAQ;AAC1F,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,YAAY;AACrC,UAAI,MAAM,YAAY,SAAS,aAAc,QAAO;AAGpD,YAAM,UAAU,MAAM,SAAS,cAAc,OAAO;AACpD,YAAM,OAAO,WAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAC9D,aAAO,SAAS,SAAS;AAAA,IAC3B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,eAAuC;AACvD,UAAM,SAAS,cAAc,SAAS,GAAG,IAAI,gBAAgB,GAAG,aAAa;AAC7E,UAAM,UAAU,KAAK,KAAK,cAAc,MAAM;AAC9C,WAAO,QAAQ,IAAI,OAAK,EAAE,KAAK,EAAE,OAAO,CAAC,MAAyB,MAAM,IAAI;AAAA,EAC9E;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,QAAgC;AACjD,UAAM,UAAU,KAAK,KAAK,cAAc,MAAM;AAC9C,WAAO,QAAQ,IAAI,OAAK,EAAE,KAAK,EAAE,OAAO,CAAC,MAAyB,MAAM,IAAI;AAAA,EAC9E;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,MAA8B;AAC3C,UAAM,WAAW,KAAK,KAAK,cAAc,EAAE;AAC3C,WAAO,SACJ,IAAI,OAAK,EAAE,KAAK,EAChB,OAAO,CAAC,MAAyB,MAAM,QAAQ,EAAE,SAAS,IAAI;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA,EAKA,gBACE,UACA,QACA,iBACA,OACA,SACA,YACM;AACN,UAAM,WAAW,KAAK,QAAQ,QAAQ;AACtC,QAAI,CAAC,SAAU;AAEf,aAAS,cAAc,KAAK,IAAI;AAChC,QAAI,CAAC,SAAS,WAAY,UAAS,aAAa,CAAC;AAGjD,aAAS,aAAa,SAAS,WAAW,OAAO,OAAK,EAAE,WAAW,MAAM;AAGzE,aAAS,WAAW,KAAK;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,IACtB,CAAC;AAED,SAAK,KAAK,OAAO,UAAU,QAAQ;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,UAAkB,QAA6C;AACjF,UAAM,WAAW,KAAK,QAAQ,QAAQ;AACtC,QAAI,CAAC,YAAY,CAAC,SAAS,WAAY,QAAO;AAE9C,QAAI,QAAQ;AACV,aAAO,SAAS,WAAW,OAAO,OAAK,EAAE,WAAW,MAAM;AAAA,IAC5D;AAEA,WAAO,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAmB;AACjB,WAAO,KAAK,KAAK,cAAc,EAAE,EAAE,WAAW;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,WAME;AACA,UAAM,WAAW,KAAK,KAAK,cAAc,EAAE,EAAE,IAAI,OAAK,EAAE,KAAK,EAAE,OAAO,CAAC,MAAyB,MAAM,IAAI;AAE1G,UAAM,QAAQ;AAAA,MACZ,YAAY,SAAS;AAAA,MACrB,WAAW,SAAS,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,MAAM,CAAC;AAAA,MACtD,aAAa,CAAC;AAAA,MACd,cAAc,SAAS,OAAO,OAAK,EAAE,WAAW,EAAE;AAAA,MAClD,qBAAqB,SAAS,OAAO,OAAK,EAAE,cAAc,EAAE,WAAW,KAAK,OAAK,EAAE,KAAK,CAAC,EAAE;AAAA,IAC7F;AAEA,eAAW,QAAQ,UAAU;AAC3B,YAAM,YAAY,KAAK,IAAI,KAAK,MAAM,YAAY,KAAK,IAAI,KAAK,KAAK;AAAA,IACvE;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,WAAmB,IAAI,KAAK,KAAK,KAAK,KAAc;AAClE,UAAM,WAAW,KAAK,KAAK,cAAc,EAAE,EAAE,IAAI,OAAK,EAAE,KAAK,EAAE,OAAO,CAAC,MAAyB,MAAM,IAAI;AAC1G,UAAM,SAAS,KAAK,IAAI,IAAI;AAC5B,QAAI,UAAU;AAEd,eAAW,QAAQ,UAAU;AAC3B,UAAI,KAAK,YAAY;AACnB,cAAM,SAAS,KAAK,WAAW;AAC/B,aAAK,aAAa,KAAK,WAAW,OAAO,OAAK,EAAE,YAAY,MAAM;AAClE,mBAAW,SAAS,KAAK,WAAW;AACpC,YAAI,KAAK,WAAW,WAAW,GAAG;AAChC,eAAK,cAAc;AAAA,QACrB;AACA,aAAK,KAAK,OAAO,KAAK,MAAM,IAAI;AAAA,MAClC;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  InteractiveDashboard,
4
4
  StreamingManager,
5
5
  TrieScanTool
6
- } from "../chunk-KCVXTYHO.js";
6
+ } from "../chunk-4GPN2QZ4.js";
7
7
  import "../chunk-D2CGMX7K.js";
8
8
  import "../chunk-DFPVUMVE.js";
9
9
  import "../chunk-TRIJC5MW.js";
@@ -0,0 +1,12 @@
1
+ import {
2
+ CodebaseIndex
3
+ } from "./chunk-6VIMBFUZ.js";
4
+ import "./chunk-6NLHFIYA.js";
5
+ import "./chunk-43X6JBEM.js";
6
+ import "./chunk-45Y5TLQZ.js";
7
+ import "./chunk-APMV77PU.js";
8
+ import "./chunk-DGUM43GV.js";
9
+ export {
10
+ CodebaseIndex
11
+ };
12
+ //# sourceMappingURL=codebase-index-CR6Q2HEI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -57,11 +57,36 @@ async function recordGoalViolationFixed(goal, file, projectPath) {
57
57
  }
58
58
  async function checkFilesForGoalViolations(goals, projectPath, filesToCheck) {
59
59
  const { isAIAvailable, runAIAnalysis } = await import("./client-EWP4SIG3.js");
60
- const { CodebaseIndex } = await import("./codebase-index-ECJYAEZJ.js");
60
+ const { CodebaseIndex } = await import("./codebase-index-CR6Q2HEI.js");
61
61
  if (!isAIAvailable()) {
62
62
  throw new Error("AI not available - ANTHROPIC_API_KEY not set");
63
63
  }
64
64
  const codebaseIndex = new CodebaseIndex(projectPath);
65
+ if (codebaseIndex.isEmpty()) {
66
+ console.error("[Goal Check] Codebase index is empty - building index first...");
67
+ console.error("[Goal Check] This is a one-time operation. Future checks will be much faster.");
68
+ const { glob } = await import("glob");
69
+ const indexPattern = `${projectPath}/**/*.{ts,tsx,js,jsx,mjs,vue,svelte,astro,py,go,rs,java,c,cpp,h,hpp,cs,rb,php,css,scss,html}`;
70
+ const indexFiles = await glob(indexPattern, {
71
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/.trie/**", "**/coverage/**"],
72
+ nodir: true
73
+ });
74
+ let indexed = 0;
75
+ const total = Math.min(indexFiles.length, 500);
76
+ for (const filePath of indexFiles.slice(0, 500)) {
77
+ let relativePath = filePath;
78
+ if (filePath.toLowerCase().startsWith(projectPath.toLowerCase() + "/")) {
79
+ relativePath = filePath.slice(projectPath.length + 1);
80
+ }
81
+ const result = await codebaseIndex.indexFile(relativePath);
82
+ if (result) indexed++;
83
+ if (indexed % 50 === 0) {
84
+ console.error(`[Goal Check] Indexed ${indexed}/${total} files...`);
85
+ }
86
+ }
87
+ await codebaseIndex.save();
88
+ console.error(`[Goal Check] Index built: ${indexed} files indexed`);
89
+ }
65
90
  let files = [];
66
91
  if (filesToCheck && filesToCheck.length > 0) {
67
92
  files = filesToCheck;
@@ -91,11 +116,18 @@ async function checkFilesForGoalViolations(goals, projectPath, filesToCheck) {
91
116
  const filesToScan = [];
92
117
  const cachedViolations = [];
93
118
  for (const filePath of files) {
94
- const relativePath = filePath.replace(projectPath + "/", "");
119
+ let relativePath = filePath;
120
+ if (filePath.toLowerCase().startsWith(projectPath.toLowerCase() + "/")) {
121
+ relativePath = filePath.slice(projectPath.length + 1);
122
+ } else if (filePath.startsWith("/")) {
123
+ continue;
124
+ }
95
125
  const hasChanged = await codebaseIndex.hasChanged(relativePath);
96
126
  if (hasChanged) {
97
- filesToScan.push(filePath);
98
- await codebaseIndex.indexFile(relativePath);
127
+ const indexed = await codebaseIndex.indexFile(relativePath);
128
+ if (indexed) {
129
+ filesToScan.push(filePath);
130
+ }
99
131
  } else {
100
132
  for (const goal of goals) {
101
133
  const cached = codebaseIndex.getCachedViolations(relativePath, goal.id);
@@ -256,4 +288,4 @@ export {
256
288
  recordGoalViolationCaught,
257
289
  recordGoalViolationFixed
258
290
  };
259
- //# sourceMappingURL=goal-validator-4RA64F37.js.map
291
+ //# sourceMappingURL=goal-validator-NLOJJ7FF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/guardian/goal-validator.ts"],"sourcesContent":["/**\n * Goal Validator - Helpers for goal tracking\n * \n * Goal detection is handled entirely by the AI watcher in watch.ts.\n * The AI (Anthropic) checks every changed file against user-defined goals,\n * using the Trie's priority scoring for cost control.\n * \n * This module provides:\n * - getActiveGoals: loads active goals for the AI prompt\n * - recordGoalViolationCaught: tracks when a violation is detected\n * - recordGoalViolationFixed: tracks when a violation is auto-fixed\n */\n\nimport type { Goal } from './guardian-state.js';\nimport { getGuardianState } from './guardian-state.js';\n\n/**\n * Get active goals for a project (used by the AI watcher)\n */\nexport async function getActiveGoals(projectPath: string): Promise<Goal[]> {\n const guardianState = getGuardianState(projectPath);\n await guardianState.load();\n return guardianState.getAllGoals().filter(g => g.status === 'active');\n}\n\nexport async function recordGoalViolationCaught(\n goal: Goal,\n file: string,\n projectPath: string\n): Promise<void> {\n const guardianState = getGuardianState(projectPath);\n await guardianState.load();\n \n const metadata = goal.metadata || {};\n const caughtCount = (metadata.caughtCount || 0) + 1;\n \n await guardianState.updateGoal(goal.id, {\n metadata: {\n ...metadata,\n caughtCount,\n lastCaught: new Date().toISOString(),\n lastCaughtFile: file,\n },\n });\n}\n\nexport async function recordGoalViolationFixed(\n goal: Goal,\n file: string,\n projectPath: string\n): Promise<void> {\n const guardianState = getGuardianState(projectPath);\n await guardianState.load();\n \n const metadata = goal.metadata || {};\n const caughtCount = (metadata.caughtCount || 0) + 1;\n const fixedCount = (metadata.fixedCount || 0) + 1;\n \n await guardianState.updateGoal(goal.id, {\n metadata: {\n ...metadata,\n caughtCount,\n fixedCount,\n lastFixed: new Date().toISOString(),\n lastFixedFile: file,\n },\n });\n \n if (goal.type === 'reduction') {\n const newValue = Math.max(0, goal.currentValue - 1);\n await guardianState.updateGoal(goal.id, {\n currentValue: newValue,\n });\n \n if (newValue <= goal.target) {\n await guardianState.updateGoal(goal.id, {\n status: 'achieved',\n achievedAt: new Date().toISOString(),\n });\n }\n }\n}\n\n/**\n * Manually check files against goals\n * Returns violations found\n * \n * NOW WITH CACHING: Uses CodebaseIndex to avoid re-scanning unchanged files\n */\nexport async function checkFilesForGoalViolations(\n goals: Goal[],\n projectPath: string,\n filesToCheck?: string[]\n): Promise<Array<{ file: string; message: string; severity: 'critical' | 'warning' | 'info' }>> {\n // Import AI client and codebase index\n const { isAIAvailable, runAIAnalysis } = await import('../ai/client.js');\n const { CodebaseIndex } = await import('../context/codebase-index.js');\n \n if (!isAIAvailable()) {\n throw new Error('AI not available - ANTHROPIC_API_KEY not set');\n }\n \n // Initialize codebase index for caching\n const codebaseIndex = new CodebaseIndex(projectPath);\n \n // Check if index needs to be built\n if (codebaseIndex.isEmpty()) {\n console.error('[Goal Check] Codebase index is empty - building index first...');\n console.error('[Goal Check] This is a one-time operation. Future checks will be much faster.');\n \n // Auto-index the codebase\n const { glob } = await import('glob');\n const indexPattern = `${projectPath}/**/*.{ts,tsx,js,jsx,mjs,vue,svelte,astro,py,go,rs,java,c,cpp,h,hpp,cs,rb,php,css,scss,html}`;\n const indexFiles = await glob(indexPattern, {\n ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**', '**/.trie/**', '**/coverage/**'],\n nodir: true,\n });\n \n let indexed = 0;\n const total = Math.min(indexFiles.length, 500); // Cap at 500 files for initial index\n for (const filePath of indexFiles.slice(0, 500)) {\n let relativePath = filePath;\n if (filePath.toLowerCase().startsWith(projectPath.toLowerCase() + '/')) {\n relativePath = filePath.slice(projectPath.length + 1);\n }\n const result = await codebaseIndex.indexFile(relativePath);\n if (result) indexed++;\n \n // Progress indicator every 50 files\n if (indexed % 50 === 0) {\n console.error(`[Goal Check] Indexed ${indexed}/${total} files...`);\n }\n }\n \n await codebaseIndex.save();\n console.error(`[Goal Check] Index built: ${indexed} files indexed`);\n }\n \n // Get files to check\n let files: string[] = [];\n if (filesToCheck && filesToCheck.length > 0) {\n files = filesToCheck;\n } else {\n // MANUAL CHECK MODE: Scan ALL relevant files, not just recently modified\n const { glob } = await import('glob');\n \n const pattern = `${projectPath}/**/*.{ts,tsx,js,jsx,py,go,rs,java,c,cpp,h,hpp,cs,rb,php,css,scss,html,vue,svelte}`;\n const allFiles = await glob(pattern, {\n ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**', '**/.trie/**', '**/coverage/**'],\n nodir: true,\n });\n \n // For manual checks, use more files but still reasonable limit\n // Sort by modification time to prioritize recently modified\n const { stat } = await import('fs/promises');\n const withStats = await Promise.all(\n allFiles.map(async (f) => {\n try {\n const stats = await stat(f);\n return { file: f, mtime: stats.mtime.getTime() };\n } catch {\n return null;\n }\n })\n );\n \n files = withStats\n .filter((f): f is { file: string; mtime: number } => f !== null)\n .sort((a, b) => b.mtime - a.mtime)\n .slice(0, 100) // Check up to 100 files for manual scans\n .map(f => f.file);\n }\n \n if (files.length === 0) {\n throw new Error('No files found to check');\n }\n \n // Check which files need scanning vs can use cached results\n const filesToScan: string[] = [];\n const cachedViolations: Array<{ file: string; message: string; severity: 'critical' | 'warning' | 'info' }> = [];\n \n for (const filePath of files) {\n // Handle case-insensitive filesystems (macOS) and ensure we get a proper relative path\n let relativePath = filePath;\n if (filePath.toLowerCase().startsWith(projectPath.toLowerCase() + '/')) {\n relativePath = filePath.slice(projectPath.length + 1);\n } else if (filePath.startsWith('/')) {\n // Already absolute but doesn't match projectPath - skip this file\n // This can happen with stale cache entries from different projects\n continue;\n }\n const hasChanged = await codebaseIndex.hasChanged(relativePath);\n \n if (hasChanged) {\n // File changed or not indexed - needs scanning\n // Try to index the file first; skip if it doesn't exist\n const indexed = await codebaseIndex.indexFile(relativePath);\n if (indexed) {\n filesToScan.push(filePath);\n }\n // If indexFile returns null, file doesn't exist - skip it silently\n } else {\n // File unchanged - check cache for each goal\n for (const goal of goals) {\n const cached = codebaseIndex.getCachedViolations(relativePath, goal.id);\n if (cached && cached.length > 0) {\n const violation = cached[0];\n if (violation && violation.found) {\n cachedViolations.push({\n file: relativePath,\n message: `Goal \"${goal.description}\" violated in ${relativePath}: ${violation.details || 'Violation found'} [${violation.confidence || 90}% confidence] (cached)`,\n severity: 'warning',\n });\n }\n } else {\n // No cache for this goal - needs scanning\n if (!filesToScan.includes(filePath)) {\n filesToScan.push(filePath);\n }\n }\n }\n }\n }\n \n console.error(`[Goal Check] ${files.length} files total, ${filesToScan.length} need scanning, ${files.length - filesToScan.length} using cache`);\n \n // Process files that need scanning in batches\n const allViolations: Array<{ file: string; message: string; severity: 'critical' | 'warning' | 'info' }> = [...cachedViolations];\n \n if (filesToScan.length === 0) {\n // All results from cache!\n await codebaseIndex.save();\n return allViolations;\n }\n \n // For large file sets, process in batches to avoid token limits\n const BATCH_SIZE = 25; // Process 25 files at a time\n \n for (let batchStart = 0; batchStart < filesToScan.length; batchStart += BATCH_SIZE) {\n const batchFiles = filesToScan.slice(batchStart, batchStart + BATCH_SIZE);\n \n // Read file contents for this batch\n const { readFile } = await import('fs/promises');\n const fileContents = await Promise.all(\n batchFiles.map(async (filePath) => {\n try {\n const content = await readFile(filePath, 'utf-8');\n const relativePath = filePath.replace(projectPath + '/', '');\n return {\n path: relativePath,\n content: content.slice(0, 10000), // Increased limit for manual checks\n };\n } catch {\n return null;\n }\n })\n );\n \n const validFiles = fileContents.filter((f): f is { path: string; content: string } => f !== null);\n \n if (validFiles.length === 0) continue;\n \n // Build files block for AI\n const filesBlock = validFiles\n .map(f => `--- ${f.path} ---\\n${f.content}`)\n .join('\\n\\n');\n \n // Build goals section\n const goalsSection = `\nUSER-DEFINED GOALS (check EVERY file against ALL goals):\n${goals.map((g, i) => ` ${i + 1}. \"${g.description}\"`).join('\\n')}\n\nThis is a MANUAL CHECK requested by the user. Report ALL goal violations you find.\nFor emoji detection, look for Unicode emoji characters.\n`;\n \n // Run AI analysis\n const result = await runAIAnalysis({\n systemPrompt: `You are checking code for GOAL VIOLATIONS ONLY.\n${goalsSection}\nReply ONLY with a JSON array. Each element must have:\n- \"file\": relative file path\n- \"severity\": \"critical\" | \"major\" | \"minor\"\n- \"description\": 1-sentence description of the goal violation with specific examples\n- \"confidence\": number 0-100, how confident you are this is a violation\n- \"isGoalViolation\": true (always true for this scan)\n- \"goalIndex\": 0-based index of the violated goal\n\nCRITICAL DETECTION RULES:\n\n**EMOJIS**: Any Unicode emoji characters including but not limited to:\n- Emoticons: 😀😃😄😊🙂🙃😉😇🥰😍🤩😘😗☺️😚😙🥲\n- Symbols: ⚡️⚠️✅❌➜→←↑↓►◄▲▼★☆●○◆◇■□▪️▫️\n- Objects: 📊📈📉💻🖥️📱⌨️🖱️💾💿📀🔧🔨⚙️🛠️\n- Actions: 🔥💪👍👎👏🙌🤝✊👊🎯🎉🎊🚀\n- Weather: ☀️🌤️⛅☁️🌦️🌧️⛈️🌩️🌨️❄️\n- ALL OTHER Unicode emoji in ranges U+1F300-U+1F9FF, U+2600-U+27BF, U+2B00-U+2BFF\n\n**COLORS**: For \"purple\" or \"gradient\" goals, check:\n- CSS: purple, #purple, hsl(purple), rgb(purple), violet, #8B00FF, #9B59D6, etc.\n- Gradients: linear-gradient, radial-gradient, conic-gradient, background-image with gradients\n- Tailwind: purple-*, violet-*, bg-gradient-*\n- Styled components or CSS-in-JS with purple/violet/gradient\n\nBe EXTREMELY thorough. Check:\n1. String literals and template literals\n2. JSX/HTML content\n3. CSS files and style blocks\n4. Comments (emojis in comments still violate \"no emojis\")\n5. console.log statements\n6. Component names, variable names (if they contain emojis)\n\nIf a goal says \"no emojis\" and you see ANY emoji ANYWHERE in the file, report it.\nIf a goal says \"no console.log\" and you see console.log ANYWHERE, report it.\nIf a goal says \"no purple/gradient\" and you see ANY purple color or gradient, report it.\n\nIf no violations found, reply with: []\nOutput ONLY the JSON array, no markdown fences, no commentary.`,\n userPrompt: `Check these ${validFiles.length} files for goal violations:\\n\\n${filesBlock}`,\n maxTokens: 8192, // Increased for larger codebases\n temperature: 0.1,\n });\n \n // Parse response\n let issues: Array<{\n file: string;\n severity: 'critical' | 'major' | 'minor';\n description: string;\n confidence: number;\n isGoalViolation: boolean;\n goalIndex?: number;\n }> = [];\n \n try {\n const cleaned = result.content.replace(/```json?\\n?|\\n?```/g, '').trim();\n issues = JSON.parse(cleaned);\n if (!Array.isArray(issues)) issues = [];\n } catch {\n // Parse failed - continue to next batch\n continue;\n }\n \n // Convert to violation format and record\n for (const issue of issues) {\n if (!issue.isGoalViolation || issue.confidence < 50) continue;\n if (issue.goalIndex == null || issue.goalIndex < 0 || issue.goalIndex >= goals.length) continue;\n \n const goal = goals[issue.goalIndex]!;\n const severity = issue.severity === 'critical' ? 'critical' : 'warning';\n const message = `Goal \"${goal.description}\" violated in ${issue.file}: ${issue.description} [${issue.confidence}% confidence]`;\n \n allViolations.push({\n file: issue.file,\n message,\n severity,\n });\n \n // Cache the result in codebase index\n codebaseIndex.recordViolation(\n issue.file,\n goal.id,\n goal.description,\n true, // found\n issue.description,\n issue.confidence\n );\n \n // Record violation\n await recordGoalViolationCaught(goal, issue.file, projectPath);\n }\n \n // Also record \"no violation\" for scanned files\n for (const file of validFiles) {\n for (const goal of goals) {\n const hasViolation = issues.some(i => i.file === file.path && i.goalIndex === goals.indexOf(goal));\n if (!hasViolation) {\n codebaseIndex.recordViolation(\n file.path,\n goal.id,\n goal.description,\n false, // not found\n undefined,\n 100\n );\n }\n }\n }\n }\n \n // Save updated index\n await codebaseIndex.save();\n \n return allViolations;\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ CodebaseIndex
4
+ } from "./chunk-6VIMBFUZ.js";
2
5
  import {
3
6
  LinearIngester,
4
7
  appendToSection,
@@ -38,7 +41,7 @@ import {
38
41
  getPrompt,
39
42
  getSystemPrompt,
40
43
  handleCheckpointTool
41
- } from "./chunk-KCVXTYHO.js";
44
+ } from "./chunk-4GPN2QZ4.js";
42
45
  import "./chunk-D2CGMX7K.js";
43
46
  import "./chunk-DFPVUMVE.js";
44
47
  import "./chunk-TRIJC5MW.js";
@@ -1141,6 +1144,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
1141
1144
  var TrieWatchTool = class _TrieWatchTool {
1142
1145
  extractionPipeline = null;
1143
1146
  watchedDirectory = "";
1147
+ codebaseIndex = null;
1144
1148
  state = {
1145
1149
  isRunning: false,
1146
1150
  lastScan: /* @__PURE__ */ new Map(),
@@ -1173,7 +1177,7 @@ var TrieWatchTool = class _TrieWatchTool {
1173
1177
  case "start":
1174
1178
  return this.startWatching(getWorkingDirectory(directory), debounceMs);
1175
1179
  case "stop":
1176
- return this.stopWatching();
1180
+ return await this.stopWatching();
1177
1181
  case "status":
1178
1182
  return await this.getStatus();
1179
1183
  case "issues":
@@ -1214,6 +1218,7 @@ var TrieWatchTool = class _TrieWatchTool {
1214
1218
  });
1215
1219
  await this.extractionPipeline.initialize();
1216
1220
  }
1221
+ this.codebaseIndex = new CodebaseIndex(directory);
1217
1222
  this.state.isRunning = true;
1218
1223
  this.watchedDirectory = directory;
1219
1224
  this.state.issueCache.clear();
@@ -1255,6 +1260,7 @@ var TrieWatchTool = class _TrieWatchTool {
1255
1260
  void this.initialGoalComplianceScan();
1256
1261
  void this.initialHypothesisGeneration();
1257
1262
  }, 1e3);
1263
+ const indexStatus = this.codebaseIndex?.isEmpty() ? "BUILDING (one-time, speeds up goal checks)" : "READY";
1258
1264
  return {
1259
1265
  content: [{
1260
1266
  type: "text",
@@ -1265,6 +1271,7 @@ Your Trie agent is now autonomously watching and learning from your codebase.
1265
1271
  **Watching:** \`${directory}\`
1266
1272
  **Debounce:** ${debounceMs}ms (waits for you to stop typing)
1267
1273
  **Signal Extraction:** ${process.env.ANTHROPIC_API_KEY ? "ENABLED" : "LIMITED (set ANTHROPIC_API_KEY for full extraction)"}
1274
+ **Codebase Index:** ${indexStatus}
1268
1275
 
1269
1276
  ### How the agent works:
1270
1277
  1. You write/edit code
@@ -1280,7 +1287,8 @@ Your Trie agent is now autonomously watching and learning from your codebase.
1280
1287
 
1281
1288
  ### Commands:
1282
1289
  - \`trie_watch status\` - See agent status
1283
- - \`trie_watch stop\` - Stop the agent`
1290
+ - \`trie_watch stop\` - Stop the agent
1291
+ - \`trie_index status\` - Check codebase index`
1284
1292
  }]
1285
1293
  };
1286
1294
  }
@@ -1403,6 +1411,12 @@ ${f.content.slice(0, 1e3)}`
1403
1411
  this.state.filesScanned += files.length;
1404
1412
  for (const file of files) {
1405
1413
  this.state.lastScan.set(file, Date.now());
1414
+ if (this.codebaseIndex) {
1415
+ const relativePath = file.replace(projectPath + "/", "");
1416
+ void this.codebaseIndex.indexFile(relativePath).then(() => {
1417
+ void this.codebaseIndex?.save();
1418
+ });
1419
+ }
1406
1420
  }
1407
1421
  } catch (error) {
1408
1422
  if (!isInteractiveMode()) {
@@ -1638,7 +1652,7 @@ ${f.content.slice(0, 1e3)}`
1638
1652
  this.state.lastAutoScan = now;
1639
1653
  try {
1640
1654
  const graph = new ContextGraph(projectPath);
1641
- const { getActiveGoals, recordGoalViolationCaught } = await import("./goal-validator-4RA64F37.js");
1655
+ const { getActiveGoals, recordGoalViolationCaught } = await import("./goal-validator-NLOJJ7FF.js");
1642
1656
  const { appendIssuesToLedger } = await import("./ledger-JMPGJGLB.js");
1643
1657
  console.debug("[AI Watcher] Loading active goals...");
1644
1658
  const activeGoals = await getActiveGoals(projectPath);
@@ -1945,7 +1959,7 @@ ${filesBlock}`,
1945
1959
  const projectPath = this.watchedDirectory || getWorkingDirectory(void 0, true);
1946
1960
  console.debug("[Initial Scan] Starting initial goal compliance scan", { projectPath });
1947
1961
  try {
1948
- const { getActiveGoals, recordGoalViolationCaught } = await import("./goal-validator-4RA64F37.js");
1962
+ const { getActiveGoals, recordGoalViolationCaught } = await import("./goal-validator-NLOJJ7FF.js");
1949
1963
  const activeGoals = await getActiveGoals(projectPath);
1950
1964
  console.debug("[Initial Scan] Loaded goals for initial scan:", {
1951
1965
  goalCount: activeGoals.length,
@@ -2091,7 +2105,7 @@ ${filesBlock}`,
2091
2105
  }
2092
2106
  }
2093
2107
  }
2094
- stopWatching() {
2108
+ async stopWatching() {
2095
2109
  if (!this.state.isRunning) {
2096
2110
  return {
2097
2111
  content: [{
@@ -2108,6 +2122,10 @@ ${filesBlock}`,
2108
2122
  this.extractionPipeline.close();
2109
2123
  this.extractionPipeline = null;
2110
2124
  }
2125
+ if (this.codebaseIndex) {
2126
+ await this.codebaseIndex.save();
2127
+ this.codebaseIndex = null;
2128
+ }
2111
2129
  if (this.state.scanDebounceTimer) {
2112
2130
  clearTimeout(this.state.scanDebounceTimer);
2113
2131
  }
@@ -3372,6 +3390,120 @@ var LinearSyncTool = class {
3372
3390
  }
3373
3391
  };
3374
3392
 
3393
+ // src/tools/index-codebase.ts
3394
+ import { glob } from "glob";
3395
+ var INDEXABLE_EXTENSIONS = [
3396
+ "ts",
3397
+ "tsx",
3398
+ "js",
3399
+ "jsx",
3400
+ "mjs",
3401
+ "vue",
3402
+ "svelte",
3403
+ "astro",
3404
+ "py",
3405
+ "go",
3406
+ "rs",
3407
+ "java",
3408
+ "c",
3409
+ "cpp",
3410
+ "h",
3411
+ "hpp",
3412
+ "cs",
3413
+ "rb",
3414
+ "php",
3415
+ "css",
3416
+ "scss",
3417
+ "html"
3418
+ ];
3419
+ var TrieIndexTool = class {
3420
+ async execute(params) {
3421
+ const action = params.action || "full";
3422
+ const workDir = getWorkingDirectory(params.directory, true);
3423
+ if (!isTrieInitialized(workDir)) {
3424
+ return "Trie is not initialized for this project. Run `trie init` first.";
3425
+ }
3426
+ const codebaseIndex = new CodebaseIndex(workDir);
3427
+ if (action === "status") {
3428
+ const stats2 = codebaseIndex.getStats();
3429
+ return [
3430
+ "# Codebase Index Status",
3431
+ "",
3432
+ `**Total files indexed:** ${stats2.totalFiles}`,
3433
+ `**Total size:** ${(stats2.totalSize / 1024 / 1024).toFixed(2)} MB`,
3434
+ `**Files scanned for goals:** ${stats2.scannedFiles}`,
3435
+ `**Files with violations:** ${stats2.filesWithViolations}`,
3436
+ "",
3437
+ "## Files by Type",
3438
+ "",
3439
+ ...Object.entries(stats2.filesByType).sort(([, a], [, b]) => b - a).slice(0, 10).map(([type, count]) => `- .${type}: ${count} files`),
3440
+ "",
3441
+ "---",
3442
+ "",
3443
+ 'Run `trie_index` with action="full" to re-index the codebase.',
3444
+ 'Run `trie_index` with action="clear" to clear stale cache entries.'
3445
+ ].join("\n");
3446
+ }
3447
+ if (action === "clear") {
3448
+ const cleared = codebaseIndex.clearStaleCache();
3449
+ await codebaseIndex.save();
3450
+ return `Cleared ${cleared} stale cache entries.`;
3451
+ }
3452
+ const pattern = `${workDir}/**/*.{${INDEXABLE_EXTENSIONS.join(",")}}`;
3453
+ const allFiles = await glob(pattern, {
3454
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/.trie/**", "**/coverage/**"],
3455
+ nodir: true
3456
+ });
3457
+ let indexed = 0;
3458
+ let failed = 0;
3459
+ const startTime = Date.now();
3460
+ const BATCH_SIZE = 50;
3461
+ for (let i = 0; i < allFiles.length; i += BATCH_SIZE) {
3462
+ const batch = allFiles.slice(i, i + BATCH_SIZE);
3463
+ await Promise.all(batch.map(async (filePath) => {
3464
+ try {
3465
+ let relativePath = filePath;
3466
+ if (filePath.toLowerCase().startsWith(workDir.toLowerCase() + "/")) {
3467
+ relativePath = filePath.slice(workDir.length + 1);
3468
+ }
3469
+ const result = await codebaseIndex.indexFile(relativePath);
3470
+ if (result) {
3471
+ indexed++;
3472
+ } else {
3473
+ failed++;
3474
+ }
3475
+ } catch {
3476
+ failed++;
3477
+ }
3478
+ }));
3479
+ }
3480
+ await codebaseIndex.save();
3481
+ const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
3482
+ const stats = codebaseIndex.getStats();
3483
+ return [
3484
+ "# Codebase Indexing Complete",
3485
+ "",
3486
+ `**Files indexed:** ${indexed}`,
3487
+ `**Files failed:** ${failed}`,
3488
+ `**Duration:** ${duration}s`,
3489
+ "",
3490
+ "## Index Statistics",
3491
+ "",
3492
+ `- Total files: ${stats.totalFiles}`,
3493
+ `- Total size: ${(stats.totalSize / 1024 / 1024).toFixed(2)} MB`,
3494
+ "",
3495
+ "## Files by Type",
3496
+ "",
3497
+ ...Object.entries(stats.filesByType).sort(([, a], [, b]) => b - a).slice(0, 10).map(([type, count]) => `- .${type}: ${count} files`),
3498
+ "",
3499
+ "---",
3500
+ "",
3501
+ "The index will be automatically updated when files change during watch mode.",
3502
+ "Goal checks will now use cached results for unchanged files."
3503
+ ].join("\n");
3504
+ }
3505
+ };
3506
+
3375
3507
  // src/server/tool-registry.ts
3376
3508
  var TrieCheckpointTool = class {
3377
3509
  async execute(input) {
@@ -3410,6 +3542,7 @@ var ToolRegistry = class {
3410
3542
  this.tools.set("ok", new TrieFeedbackTool());
3411
3543
  this.tools.set("bad", new TrieFeedbackTool());
3412
3544
  this.tools.set("linear_sync", new LinearSyncTool());
3545
+ this.tools.set("index", new TrieIndexTool());
3413
3546
  this.tools.set("get_decisions", new TrieGetDecisionsTool());
3414
3547
  this.tools.set("get_blockers", new TrieGetBlockersTool());
3415
3548
  this.tools.set("get_related_decisions", new TrieGetRelatedDecisionsTool());
@@ -3816,6 +3949,24 @@ var ToolRegistry = class {
3816
3949
  required: ["action"]
3817
3950
  }
3818
3951
  },
3952
+ {
3953
+ name: "trie_index",
3954
+ description: "Index codebase for fast goal checking and file lookups. Index is auto-updated during watch mode. Use this for initial indexing or to rebuild the index.",
3955
+ inputSchema: {
3956
+ type: "object",
3957
+ properties: {
3958
+ action: {
3959
+ type: "string",
3960
+ enum: ["full", "status", "clear"],
3961
+ description: "Action: full (index all files), status (show index stats), clear (remove stale cache)"
3962
+ },
3963
+ directory: {
3964
+ type: "string",
3965
+ description: "Project directory (defaults to current workspace)"
3966
+ }
3967
+ }
3968
+ }
3969
+ },
3819
3970
  // Add remaining tool definitions...
3820
3971
  this.createSpecialAgentDefinitions()
3821
3972
  ].flat();