@triedotdev/mcp 1.0.124 β 1.0.126
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 +11 -0
- package/dist/{chunk-7J4ZOOAD.js β chunk-KCVXTYHO.js} +152 -56
- package/dist/chunk-KCVXTYHO.js.map +1 -0
- package/dist/cli/yolo-daemon.js +1 -1
- package/dist/codebase-index-ECJYAEZJ.js +197 -0
- package/dist/codebase-index-ECJYAEZJ.js.map +1 -0
- package/dist/goal-validator-4RA64F37.js +259 -0
- package/dist/goal-validator-4RA64F37.js.map +1 -0
- package/dist/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-7J4ZOOAD.js.map +0 -1
- package/dist/goal-validator-CKFKJ46J.js +0 -188
- package/dist/goal-validator-CKFKJ46J.js.map +0 -1
package/dist/cli/yolo-daemon.js
CHANGED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Trie
|
|
3
|
+
} from "./chunk-6NLHFIYA.js";
|
|
4
|
+
import {
|
|
5
|
+
atomicWriteJSON
|
|
6
|
+
} from "./chunk-43X6JBEM.js";
|
|
7
|
+
import {
|
|
8
|
+
getTrieDirectory
|
|
9
|
+
} from "./chunk-45Y5TLQZ.js";
|
|
10
|
+
import "./chunk-APMV77PU.js";
|
|
11
|
+
import {
|
|
12
|
+
__require
|
|
13
|
+
} from "./chunk-DGUM43GV.js";
|
|
14
|
+
|
|
15
|
+
// src/context/codebase-index.ts
|
|
16
|
+
import { readFile, stat } from "fs/promises";
|
|
17
|
+
import { createHash } from "crypto";
|
|
18
|
+
import { join } from "path";
|
|
19
|
+
import { existsSync } from "fs";
|
|
20
|
+
var CodebaseIndex = class {
|
|
21
|
+
trie = new Trie();
|
|
22
|
+
projectPath;
|
|
23
|
+
indexPath;
|
|
24
|
+
constructor(projectPath) {
|
|
25
|
+
this.projectPath = projectPath;
|
|
26
|
+
this.indexPath = join(getTrieDirectory(projectPath), "codebase-index.json");
|
|
27
|
+
this.load();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Load index from disk if it exists
|
|
31
|
+
*/
|
|
32
|
+
load() {
|
|
33
|
+
if (!existsSync(this.indexPath)) return;
|
|
34
|
+
try {
|
|
35
|
+
const raw = JSON.parse(__require("fs").readFileSync(this.indexPath, "utf-8"));
|
|
36
|
+
if (raw.trie) {
|
|
37
|
+
this.trie = Trie.fromJSON(raw.trie);
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error("Failed to load codebase index:", error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Save index to disk
|
|
45
|
+
*/
|
|
46
|
+
async save() {
|
|
47
|
+
const data = {
|
|
48
|
+
version: 1,
|
|
49
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
50
|
+
fileCount: this.trie.getWithPrefix("").length,
|
|
51
|
+
trie: this.trie.toJSON()
|
|
52
|
+
};
|
|
53
|
+
await atomicWriteJSON(this.indexPath, data);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Add or update a file in the index
|
|
57
|
+
*/
|
|
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;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
this.trie.insert(filePath, metadata);
|
|
78
|
+
return metadata;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get file metadata from index
|
|
82
|
+
*/
|
|
83
|
+
getFile(filePath) {
|
|
84
|
+
const result = this.trie.search(filePath);
|
|
85
|
+
return result.found && result.value ? result.value : null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if file has changed since last index
|
|
89
|
+
*/
|
|
90
|
+
async hasChanged(filePath) {
|
|
91
|
+
const metadata = this.getFile(filePath);
|
|
92
|
+
if (!metadata) return true;
|
|
93
|
+
const absolutePath = join(this.projectPath, filePath);
|
|
94
|
+
try {
|
|
95
|
+
const stats = await stat(absolutePath);
|
|
96
|
+
if (stats.mtimeMs !== metadata.lastModified) return true;
|
|
97
|
+
const content = await readFile(absolutePath, "utf-8");
|
|
98
|
+
const hash = createHash("sha256").update(content).digest("hex");
|
|
99
|
+
return hash !== metadata.hash;
|
|
100
|
+
} catch {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get files in a directory
|
|
106
|
+
*/
|
|
107
|
+
getDirectoryFiles(directoryPath) {
|
|
108
|
+
const prefix = directoryPath.endsWith("/") ? directoryPath : `${directoryPath}/`;
|
|
109
|
+
const matches = this.trie.getWithPrefix(prefix);
|
|
110
|
+
return matches.map((m) => m.value).filter((v) => v !== null);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get files that match a pattern (uses Trie prefix matching)
|
|
114
|
+
*/
|
|
115
|
+
getFilesWithPrefix(prefix) {
|
|
116
|
+
const matches = this.trie.getWithPrefix(prefix);
|
|
117
|
+
return matches.map((m) => m.value).filter((v) => v !== null);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get files by type (extension)
|
|
121
|
+
*/
|
|
122
|
+
getFilesByType(type) {
|
|
123
|
+
const allFiles = this.trie.getWithPrefix("");
|
|
124
|
+
return allFiles.map((m) => m.value).filter((v) => v !== null && v.type === type);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Record goal violation scan result for a file
|
|
128
|
+
*/
|
|
129
|
+
recordViolation(filePath, goalId, goalDescription, found, details, confidence) {
|
|
130
|
+
const metadata = this.getFile(filePath);
|
|
131
|
+
if (!metadata) return;
|
|
132
|
+
metadata.lastScanned = Date.now();
|
|
133
|
+
if (!metadata.violations) metadata.violations = [];
|
|
134
|
+
metadata.violations = metadata.violations.filter((v) => v.goalId !== goalId);
|
|
135
|
+
metadata.violations.push({
|
|
136
|
+
goalId,
|
|
137
|
+
goalDescription,
|
|
138
|
+
found,
|
|
139
|
+
details,
|
|
140
|
+
confidence,
|
|
141
|
+
timestamp: Date.now()
|
|
142
|
+
});
|
|
143
|
+
this.trie.insert(filePath, metadata);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get cached violation results for a file
|
|
147
|
+
*/
|
|
148
|
+
getCachedViolations(filePath, goalId) {
|
|
149
|
+
const metadata = this.getFile(filePath);
|
|
150
|
+
if (!metadata || !metadata.violations) return void 0;
|
|
151
|
+
if (goalId) {
|
|
152
|
+
return metadata.violations.filter((v) => v.goalId === goalId);
|
|
153
|
+
}
|
|
154
|
+
return metadata.violations;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get statistics about the index
|
|
158
|
+
*/
|
|
159
|
+
getStats() {
|
|
160
|
+
const allFiles = this.trie.getWithPrefix("").map((m) => m.value).filter((v) => v !== null);
|
|
161
|
+
const stats = {
|
|
162
|
+
totalFiles: allFiles.length,
|
|
163
|
+
totalSize: allFiles.reduce((sum, f) => sum + f.size, 0),
|
|
164
|
+
filesByType: {},
|
|
165
|
+
scannedFiles: allFiles.filter((f) => f.lastScanned).length,
|
|
166
|
+
filesWithViolations: allFiles.filter((f) => f.violations && f.violations.some((v) => v.found)).length
|
|
167
|
+
};
|
|
168
|
+
for (const file of allFiles) {
|
|
169
|
+
stats.filesByType[file.type] = (stats.filesByType[file.type] || 0) + 1;
|
|
170
|
+
}
|
|
171
|
+
return stats;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Clear stale cached results (older than maxAgeMs)
|
|
175
|
+
*/
|
|
176
|
+
clearStaleCache(maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
|
|
177
|
+
const allFiles = this.trie.getWithPrefix("").map((m) => m.value).filter((v) => v !== null);
|
|
178
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
179
|
+
let cleared = 0;
|
|
180
|
+
for (const file of allFiles) {
|
|
181
|
+
if (file.violations) {
|
|
182
|
+
const before = file.violations.length;
|
|
183
|
+
file.violations = file.violations.filter((v) => v.timestamp > cutoff);
|
|
184
|
+
cleared += before - file.violations.length;
|
|
185
|
+
if (file.violations.length === 0) {
|
|
186
|
+
file.lastScanned = void 0;
|
|
187
|
+
}
|
|
188
|
+
this.trie.insert(file.path, file);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return cleared;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
export {
|
|
195
|
+
CodebaseIndex
|
|
196
|
+
};
|
|
197
|
+
//# sourceMappingURL=codebase-index-ECJYAEZJ.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 */\n async indexFile(filePath: string): Promise<FileMetadata> {\n const absolutePath = join(this.projectPath, filePath);\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 }\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 const metadata = this.getFile(filePath);\n if (!metadata) return true; // Not indexed = changed\n\n const absolutePath = 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 * 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":"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","names":[]}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getGuardianState
|
|
3
|
+
} from "./chunk-UHMMANC2.js";
|
|
4
|
+
import "./chunk-WS6OA7H6.js";
|
|
5
|
+
import "./chunk-43X6JBEM.js";
|
|
6
|
+
import "./chunk-45Y5TLQZ.js";
|
|
7
|
+
import "./chunk-APMV77PU.js";
|
|
8
|
+
import "./chunk-DGUM43GV.js";
|
|
9
|
+
|
|
10
|
+
// src/guardian/goal-validator.ts
|
|
11
|
+
async function getActiveGoals(projectPath) {
|
|
12
|
+
const guardianState = getGuardianState(projectPath);
|
|
13
|
+
await guardianState.load();
|
|
14
|
+
return guardianState.getAllGoals().filter((g) => g.status === "active");
|
|
15
|
+
}
|
|
16
|
+
async function recordGoalViolationCaught(goal, file, projectPath) {
|
|
17
|
+
const guardianState = getGuardianState(projectPath);
|
|
18
|
+
await guardianState.load();
|
|
19
|
+
const metadata = goal.metadata || {};
|
|
20
|
+
const caughtCount = (metadata.caughtCount || 0) + 1;
|
|
21
|
+
await guardianState.updateGoal(goal.id, {
|
|
22
|
+
metadata: {
|
|
23
|
+
...metadata,
|
|
24
|
+
caughtCount,
|
|
25
|
+
lastCaught: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26
|
+
lastCaughtFile: file
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function recordGoalViolationFixed(goal, file, projectPath) {
|
|
31
|
+
const guardianState = getGuardianState(projectPath);
|
|
32
|
+
await guardianState.load();
|
|
33
|
+
const metadata = goal.metadata || {};
|
|
34
|
+
const caughtCount = (metadata.caughtCount || 0) + 1;
|
|
35
|
+
const fixedCount = (metadata.fixedCount || 0) + 1;
|
|
36
|
+
await guardianState.updateGoal(goal.id, {
|
|
37
|
+
metadata: {
|
|
38
|
+
...metadata,
|
|
39
|
+
caughtCount,
|
|
40
|
+
fixedCount,
|
|
41
|
+
lastFixed: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42
|
+
lastFixedFile: file
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
if (goal.type === "reduction") {
|
|
46
|
+
const newValue = Math.max(0, goal.currentValue - 1);
|
|
47
|
+
await guardianState.updateGoal(goal.id, {
|
|
48
|
+
currentValue: newValue
|
|
49
|
+
});
|
|
50
|
+
if (newValue <= goal.target) {
|
|
51
|
+
await guardianState.updateGoal(goal.id, {
|
|
52
|
+
status: "achieved",
|
|
53
|
+
achievedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function checkFilesForGoalViolations(goals, projectPath, filesToCheck) {
|
|
59
|
+
const { isAIAvailable, runAIAnalysis } = await import("./client-EWP4SIG3.js");
|
|
60
|
+
const { CodebaseIndex } = await import("./codebase-index-ECJYAEZJ.js");
|
|
61
|
+
if (!isAIAvailable()) {
|
|
62
|
+
throw new Error("AI not available - ANTHROPIC_API_KEY not set");
|
|
63
|
+
}
|
|
64
|
+
const codebaseIndex = new CodebaseIndex(projectPath);
|
|
65
|
+
let files = [];
|
|
66
|
+
if (filesToCheck && filesToCheck.length > 0) {
|
|
67
|
+
files = filesToCheck;
|
|
68
|
+
} else {
|
|
69
|
+
const { glob } = await import("glob");
|
|
70
|
+
const pattern = `${projectPath}/**/*.{ts,tsx,js,jsx,py,go,rs,java,c,cpp,h,hpp,cs,rb,php,css,scss,html,vue,svelte}`;
|
|
71
|
+
const allFiles = await glob(pattern, {
|
|
72
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/.trie/**", "**/coverage/**"],
|
|
73
|
+
nodir: true
|
|
74
|
+
});
|
|
75
|
+
const { stat } = await import("fs/promises");
|
|
76
|
+
const withStats = await Promise.all(
|
|
77
|
+
allFiles.map(async (f) => {
|
|
78
|
+
try {
|
|
79
|
+
const stats = await stat(f);
|
|
80
|
+
return { file: f, mtime: stats.mtime.getTime() };
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
files = withStats.filter((f) => f !== null).sort((a, b) => b.mtime - a.mtime).slice(0, 100).map((f) => f.file);
|
|
87
|
+
}
|
|
88
|
+
if (files.length === 0) {
|
|
89
|
+
throw new Error("No files found to check");
|
|
90
|
+
}
|
|
91
|
+
const filesToScan = [];
|
|
92
|
+
const cachedViolations = [];
|
|
93
|
+
for (const filePath of files) {
|
|
94
|
+
const relativePath = filePath.replace(projectPath + "/", "");
|
|
95
|
+
const hasChanged = await codebaseIndex.hasChanged(relativePath);
|
|
96
|
+
if (hasChanged) {
|
|
97
|
+
filesToScan.push(filePath);
|
|
98
|
+
await codebaseIndex.indexFile(relativePath);
|
|
99
|
+
} else {
|
|
100
|
+
for (const goal of goals) {
|
|
101
|
+
const cached = codebaseIndex.getCachedViolations(relativePath, goal.id);
|
|
102
|
+
if (cached && cached.length > 0) {
|
|
103
|
+
const violation = cached[0];
|
|
104
|
+
if (violation && violation.found) {
|
|
105
|
+
cachedViolations.push({
|
|
106
|
+
file: relativePath,
|
|
107
|
+
message: `Goal "${goal.description}" violated in ${relativePath}: ${violation.details || "Violation found"} [${violation.confidence || 90}% confidence] (cached)`,
|
|
108
|
+
severity: "warning"
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
if (!filesToScan.includes(filePath)) {
|
|
113
|
+
filesToScan.push(filePath);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
console.error(`[Goal Check] ${files.length} files total, ${filesToScan.length} need scanning, ${files.length - filesToScan.length} using cache`);
|
|
120
|
+
const allViolations = [...cachedViolations];
|
|
121
|
+
if (filesToScan.length === 0) {
|
|
122
|
+
await codebaseIndex.save();
|
|
123
|
+
return allViolations;
|
|
124
|
+
}
|
|
125
|
+
const BATCH_SIZE = 25;
|
|
126
|
+
for (let batchStart = 0; batchStart < filesToScan.length; batchStart += BATCH_SIZE) {
|
|
127
|
+
const batchFiles = filesToScan.slice(batchStart, batchStart + BATCH_SIZE);
|
|
128
|
+
const { readFile } = await import("fs/promises");
|
|
129
|
+
const fileContents = await Promise.all(
|
|
130
|
+
batchFiles.map(async (filePath) => {
|
|
131
|
+
try {
|
|
132
|
+
const content = await readFile(filePath, "utf-8");
|
|
133
|
+
const relativePath = filePath.replace(projectPath + "/", "");
|
|
134
|
+
return {
|
|
135
|
+
path: relativePath,
|
|
136
|
+
content: content.slice(0, 1e4)
|
|
137
|
+
// Increased limit for manual checks
|
|
138
|
+
};
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
const validFiles = fileContents.filter((f) => f !== null);
|
|
145
|
+
if (validFiles.length === 0) continue;
|
|
146
|
+
const filesBlock = validFiles.map((f) => `--- ${f.path} ---
|
|
147
|
+
${f.content}`).join("\n\n");
|
|
148
|
+
const goalsSection = `
|
|
149
|
+
USER-DEFINED GOALS (check EVERY file against ALL goals):
|
|
150
|
+
${goals.map((g, i) => ` ${i + 1}. "${g.description}"`).join("\n")}
|
|
151
|
+
|
|
152
|
+
This is a MANUAL CHECK requested by the user. Report ALL goal violations you find.
|
|
153
|
+
For emoji detection, look for Unicode emoji characters.
|
|
154
|
+
`;
|
|
155
|
+
const result = await runAIAnalysis({
|
|
156
|
+
systemPrompt: `You are checking code for GOAL VIOLATIONS ONLY.
|
|
157
|
+
${goalsSection}
|
|
158
|
+
Reply ONLY with a JSON array. Each element must have:
|
|
159
|
+
- "file": relative file path
|
|
160
|
+
- "severity": "critical" | "major" | "minor"
|
|
161
|
+
- "description": 1-sentence description of the goal violation with specific examples
|
|
162
|
+
- "confidence": number 0-100, how confident you are this is a violation
|
|
163
|
+
- "isGoalViolation": true (always true for this scan)
|
|
164
|
+
- "goalIndex": 0-based index of the violated goal
|
|
165
|
+
|
|
166
|
+
CRITICAL DETECTION RULES:
|
|
167
|
+
|
|
168
|
+
**EMOJIS**: Any Unicode emoji characters including but not limited to:
|
|
169
|
+
- Emoticons: \u{1F600}\u{1F603}\u{1F604}\u{1F60A}\u{1F642}\u{1F643}\u{1F609}\u{1F607}\u{1F970}\u{1F60D}\u{1F929}\u{1F618}\u{1F617}\u263A\uFE0F\u{1F61A}\u{1F619}\u{1F972}
|
|
170
|
+
- Symbols: \u26A1\uFE0F\u26A0\uFE0F\u2705\u274C\u279C\u2192\u2190\u2191\u2193\u25BA\u25C4\u25B2\u25BC\u2605\u2606\u25CF\u25CB\u25C6\u25C7\u25A0\u25A1\u25AA\uFE0F\u25AB\uFE0F
|
|
171
|
+
- Objects: \u{1F4CA}\u{1F4C8}\u{1F4C9}\u{1F4BB}\u{1F5A5}\uFE0F\u{1F4F1}\u2328\uFE0F\u{1F5B1}\uFE0F\u{1F4BE}\u{1F4BF}\u{1F4C0}\u{1F527}\u{1F528}\u2699\uFE0F\u{1F6E0}\uFE0F
|
|
172
|
+
- Actions: \u{1F525}\u{1F4AA}\u{1F44D}\u{1F44E}\u{1F44F}\u{1F64C}\u{1F91D}\u270A\u{1F44A}\u{1F3AF}\u{1F389}\u{1F38A}\u{1F680}
|
|
173
|
+
- Weather: \u2600\uFE0F\u{1F324}\uFE0F\u26C5\u2601\uFE0F\u{1F326}\uFE0F\u{1F327}\uFE0F\u26C8\uFE0F\u{1F329}\uFE0F\u{1F328}\uFE0F\u2744\uFE0F
|
|
174
|
+
- ALL OTHER Unicode emoji in ranges U+1F300-U+1F9FF, U+2600-U+27BF, U+2B00-U+2BFF
|
|
175
|
+
|
|
176
|
+
**COLORS**: For "purple" or "gradient" goals, check:
|
|
177
|
+
- CSS: purple, #purple, hsl(purple), rgb(purple), violet, #8B00FF, #9B59D6, etc.
|
|
178
|
+
- Gradients: linear-gradient, radial-gradient, conic-gradient, background-image with gradients
|
|
179
|
+
- Tailwind: purple-*, violet-*, bg-gradient-*
|
|
180
|
+
- Styled components or CSS-in-JS with purple/violet/gradient
|
|
181
|
+
|
|
182
|
+
Be EXTREMELY thorough. Check:
|
|
183
|
+
1. String literals and template literals
|
|
184
|
+
2. JSX/HTML content
|
|
185
|
+
3. CSS files and style blocks
|
|
186
|
+
4. Comments (emojis in comments still violate "no emojis")
|
|
187
|
+
5. console.log statements
|
|
188
|
+
6. Component names, variable names (if they contain emojis)
|
|
189
|
+
|
|
190
|
+
If a goal says "no emojis" and you see ANY emoji ANYWHERE in the file, report it.
|
|
191
|
+
If a goal says "no console.log" and you see console.log ANYWHERE, report it.
|
|
192
|
+
If a goal says "no purple/gradient" and you see ANY purple color or gradient, report it.
|
|
193
|
+
|
|
194
|
+
If no violations found, reply with: []
|
|
195
|
+
Output ONLY the JSON array, no markdown fences, no commentary.`,
|
|
196
|
+
userPrompt: `Check these ${validFiles.length} files for goal violations:
|
|
197
|
+
|
|
198
|
+
${filesBlock}`,
|
|
199
|
+
maxTokens: 8192,
|
|
200
|
+
// Increased for larger codebases
|
|
201
|
+
temperature: 0.1
|
|
202
|
+
});
|
|
203
|
+
let issues = [];
|
|
204
|
+
try {
|
|
205
|
+
const cleaned = result.content.replace(/```json?\n?|\n?```/g, "").trim();
|
|
206
|
+
issues = JSON.parse(cleaned);
|
|
207
|
+
if (!Array.isArray(issues)) issues = [];
|
|
208
|
+
} catch {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
for (const issue of issues) {
|
|
212
|
+
if (!issue.isGoalViolation || issue.confidence < 50) continue;
|
|
213
|
+
if (issue.goalIndex == null || issue.goalIndex < 0 || issue.goalIndex >= goals.length) continue;
|
|
214
|
+
const goal = goals[issue.goalIndex];
|
|
215
|
+
const severity = issue.severity === "critical" ? "critical" : "warning";
|
|
216
|
+
const message = `Goal "${goal.description}" violated in ${issue.file}: ${issue.description} [${issue.confidence}% confidence]`;
|
|
217
|
+
allViolations.push({
|
|
218
|
+
file: issue.file,
|
|
219
|
+
message,
|
|
220
|
+
severity
|
|
221
|
+
});
|
|
222
|
+
codebaseIndex.recordViolation(
|
|
223
|
+
issue.file,
|
|
224
|
+
goal.id,
|
|
225
|
+
goal.description,
|
|
226
|
+
true,
|
|
227
|
+
// found
|
|
228
|
+
issue.description,
|
|
229
|
+
issue.confidence
|
|
230
|
+
);
|
|
231
|
+
await recordGoalViolationCaught(goal, issue.file, projectPath);
|
|
232
|
+
}
|
|
233
|
+
for (const file of validFiles) {
|
|
234
|
+
for (const goal of goals) {
|
|
235
|
+
const hasViolation = issues.some((i) => i.file === file.path && i.goalIndex === goals.indexOf(goal));
|
|
236
|
+
if (!hasViolation) {
|
|
237
|
+
codebaseIndex.recordViolation(
|
|
238
|
+
file.path,
|
|
239
|
+
goal.id,
|
|
240
|
+
goal.description,
|
|
241
|
+
false,
|
|
242
|
+
// not found
|
|
243
|
+
void 0,
|
|
244
|
+
100
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
await codebaseIndex.save();
|
|
251
|
+
return allViolations;
|
|
252
|
+
}
|
|
253
|
+
export {
|
|
254
|
+
checkFilesForGoalViolations,
|
|
255
|
+
getActiveGoals,
|
|
256
|
+
recordGoalViolationCaught,
|
|
257
|
+
recordGoalViolationFixed
|
|
258
|
+
};
|
|
259
|
+
//# sourceMappingURL=goal-validator-4RA64F37.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 // 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 const relativePath = filePath.replace(projectPath + '/', '');\n const hasChanged = await codebaseIndex.hasChanged(relativePath);\n \n if (hasChanged) {\n // File changed or not indexed - needs scanning\n filesToScan.push(filePath);\n await codebaseIndex.indexFile(relativePath);\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
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
getPrompt,
|
|
39
39
|
getSystemPrompt,
|
|
40
40
|
handleCheckpointTool
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-KCVXTYHO.js";
|
|
42
42
|
import "./chunk-D2CGMX7K.js";
|
|
43
43
|
import "./chunk-DFPVUMVE.js";
|
|
44
44
|
import "./chunk-TRIJC5MW.js";
|
|
@@ -1638,7 +1638,7 @@ ${f.content.slice(0, 1e3)}`
|
|
|
1638
1638
|
this.state.lastAutoScan = now;
|
|
1639
1639
|
try {
|
|
1640
1640
|
const graph = new ContextGraph(projectPath);
|
|
1641
|
-
const { getActiveGoals, recordGoalViolationCaught } = await import("./goal-validator-
|
|
1641
|
+
const { getActiveGoals, recordGoalViolationCaught } = await import("./goal-validator-4RA64F37.js");
|
|
1642
1642
|
const { appendIssuesToLedger } = await import("./ledger-JMPGJGLB.js");
|
|
1643
1643
|
console.debug("[AI Watcher] Loading active goals...");
|
|
1644
1644
|
const activeGoals = await getActiveGoals(projectPath);
|
|
@@ -1945,7 +1945,7 @@ ${filesBlock}`,
|
|
|
1945
1945
|
const projectPath = this.watchedDirectory || getWorkingDirectory(void 0, true);
|
|
1946
1946
|
console.debug("[Initial Scan] Starting initial goal compliance scan", { projectPath });
|
|
1947
1947
|
try {
|
|
1948
|
-
const { getActiveGoals, recordGoalViolationCaught } = await import("./goal-validator-
|
|
1948
|
+
const { getActiveGoals, recordGoalViolationCaught } = await import("./goal-validator-4RA64F37.js");
|
|
1949
1949
|
const activeGoals = await getActiveGoals(projectPath);
|
|
1950
1950
|
console.debug("[Initial Scan] Loaded goals for initial scan:", {
|
|
1951
1951
|
goalCount: activeGoals.length,
|