@blockspool/mcp 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advance.d.ts.map +1 -1
- package/dist/advance.js +182 -9
- package/dist/advance.js.map +1 -1
- package/dist/codebase-index.d.ts +44 -0
- package/dist/codebase-index.d.ts.map +1 -0
- package/dist/codebase-index.js +427 -0
- package/dist/codebase-index.js.map +1 -0
- package/dist/event-processor.d.ts.map +1 -1
- package/dist/event-processor.js +136 -8
- package/dist/event-processor.js.map +1 -1
- package/dist/learnings.d.ts +65 -0
- package/dist/learnings.d.ts.map +1 -0
- package/dist/learnings.js +220 -0
- package/dist/learnings.js.map +1 -0
- package/dist/proposals.d.ts +1 -0
- package/dist/proposals.d.ts.map +1 -1
- package/dist/proposals.js +25 -0
- package/dist/proposals.js.map +1 -1
- package/dist/run-manager.d.ts +2 -0
- package/dist/run-manager.d.ts.map +1 -1
- package/dist/run-manager.js +36 -0
- package/dist/run-manager.js.map +1 -1
- package/dist/server.js +1 -1
- package/dist/ticket-worker.d.ts.map +1 -1
- package/dist/ticket-worker.js +1 -0
- package/dist/ticket-worker.js.map +1 -1
- package/dist/tools/session.d.ts.map +1 -1
- package/dist/tools/session.js +6 -0
- package/dist/tools/session.js.map +1 -1
- package/dist/types.d.ts +16 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-Scout Codebase Index — lightweight structural map built at session start.
|
|
3
|
+
*
|
|
4
|
+
* Walks directories 2 levels deep using `fs` only. No AST parsing, no heavy deps.
|
|
5
|
+
* Provides module map, dependency edges, test gaps, complexity hotspots, and entrypoints.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Source file extensions
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
13
|
+
'.ts', '.js', '.py', '.rs', '.go', '.rb', '.java', '.cs', '.ex', '.php', '.swift',
|
|
14
|
+
]);
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Purpose inference from directory name
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
const PURPOSE_MAP = {
|
|
19
|
+
api: 'api',
|
|
20
|
+
apis: 'api',
|
|
21
|
+
routes: 'api',
|
|
22
|
+
handlers: 'api',
|
|
23
|
+
controllers: 'api',
|
|
24
|
+
endpoints: 'api',
|
|
25
|
+
services: 'services',
|
|
26
|
+
service: 'services',
|
|
27
|
+
lib: 'services',
|
|
28
|
+
core: 'services',
|
|
29
|
+
test: 'tests',
|
|
30
|
+
tests: 'tests',
|
|
31
|
+
__tests__: 'tests',
|
|
32
|
+
spec: 'tests',
|
|
33
|
+
specs: 'tests',
|
|
34
|
+
ui: 'ui',
|
|
35
|
+
components: 'ui',
|
|
36
|
+
views: 'ui',
|
|
37
|
+
pages: 'ui',
|
|
38
|
+
screens: 'ui',
|
|
39
|
+
utils: 'utils',
|
|
40
|
+
util: 'utils',
|
|
41
|
+
helpers: 'utils',
|
|
42
|
+
shared: 'utils',
|
|
43
|
+
common: 'utils',
|
|
44
|
+
config: 'config',
|
|
45
|
+
configs: 'config',
|
|
46
|
+
configuration: 'config',
|
|
47
|
+
settings: 'config',
|
|
48
|
+
};
|
|
49
|
+
function inferPurpose(dirName) {
|
|
50
|
+
const lower = dirName.toLowerCase();
|
|
51
|
+
return PURPOSE_MAP[lower] ?? 'unknown';
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Import regex patterns
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// JS/TS: import ... from '...' or require('...')
|
|
57
|
+
const JS_IMPORT_RE = /(?:import\s+.*?\s+from\s+['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\))/g;
|
|
58
|
+
// Python: from X import ... or import X
|
|
59
|
+
const PY_IMPORT_RE = /(?:from\s+([\w.]+)\s+import|^import\s+([\w.]+))/gm;
|
|
60
|
+
// Go: import "..."
|
|
61
|
+
const GO_IMPORT_RE = /import\s+"([^"]+)"/g;
|
|
62
|
+
function extractImports(content, filePath) {
|
|
63
|
+
const ext = path.extname(filePath);
|
|
64
|
+
const imports = [];
|
|
65
|
+
if (ext === '.ts' || ext === '.js') {
|
|
66
|
+
for (const m of content.matchAll(JS_IMPORT_RE)) {
|
|
67
|
+
const spec = m[1] ?? m[2];
|
|
68
|
+
if (spec)
|
|
69
|
+
imports.push(spec);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (ext === '.py') {
|
|
73
|
+
for (const m of content.matchAll(PY_IMPORT_RE)) {
|
|
74
|
+
const spec = m[1] ?? m[2];
|
|
75
|
+
if (spec)
|
|
76
|
+
imports.push(spec);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (ext === '.go') {
|
|
80
|
+
for (const m of content.matchAll(GO_IMPORT_RE)) {
|
|
81
|
+
if (m[1])
|
|
82
|
+
imports.push(m[1]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return imports;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolve a relative import specifier to a module path relative to projectRoot.
|
|
89
|
+
* Returns null for non-relative (package) imports.
|
|
90
|
+
*/
|
|
91
|
+
function resolveImportToModule(specifier, sourceFile, projectRoot, modulePaths) {
|
|
92
|
+
// Only resolve relative imports
|
|
93
|
+
if (!specifier.startsWith('.'))
|
|
94
|
+
return null;
|
|
95
|
+
const sourceDir = path.dirname(sourceFile);
|
|
96
|
+
const resolved = path.resolve(sourceDir, specifier);
|
|
97
|
+
const relative = path.relative(projectRoot, resolved);
|
|
98
|
+
// Find which module this resolved path falls under
|
|
99
|
+
for (const mod of modulePaths) {
|
|
100
|
+
if (relative === mod || relative.startsWith(mod + '/') || relative.startsWith(mod + path.sep)) {
|
|
101
|
+
return mod;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// buildCodebaseIndex
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
export function buildCodebaseIndex(projectRoot, excludeDirs = []) {
|
|
110
|
+
const excludeSet = new Set(excludeDirs.map(d => d.toLowerCase()));
|
|
111
|
+
// Step 1: Module map — walk dirs 2 levels deep
|
|
112
|
+
const modules = [];
|
|
113
|
+
const sourceFilesByModule = new Map();
|
|
114
|
+
function shouldExclude(name) {
|
|
115
|
+
return excludeSet.has(name.toLowerCase()) || name.startsWith('.');
|
|
116
|
+
}
|
|
117
|
+
function walkForModules(dir, depth) {
|
|
118
|
+
if (modules.length >= 50)
|
|
119
|
+
return;
|
|
120
|
+
let entries;
|
|
121
|
+
try {
|
|
122
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const sourceFiles = [];
|
|
128
|
+
const subdirs = [];
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
if (entry.isFile() && SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
|
|
131
|
+
sourceFiles.push(path.join(dir, entry.name));
|
|
132
|
+
}
|
|
133
|
+
else if (entry.isDirectory() && !shouldExclude(entry.name)) {
|
|
134
|
+
subdirs.push(entry);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Register this dir as a module if it has source files
|
|
138
|
+
if (sourceFiles.length > 0 && depth > 0) {
|
|
139
|
+
const relPath = path.relative(projectRoot, dir);
|
|
140
|
+
if (relPath && modules.length < 50) {
|
|
141
|
+
modules.push({
|
|
142
|
+
path: relPath,
|
|
143
|
+
file_count: sourceFiles.length,
|
|
144
|
+
purpose: inferPurpose(path.basename(dir)),
|
|
145
|
+
});
|
|
146
|
+
sourceFilesByModule.set(relPath, sourceFiles);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Recurse into subdirs (up to depth 2)
|
|
150
|
+
if (depth < 2) {
|
|
151
|
+
for (const sub of subdirs) {
|
|
152
|
+
if (modules.length >= 50)
|
|
153
|
+
break;
|
|
154
|
+
walkForModules(path.join(dir, sub.name), depth + 1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
walkForModules(projectRoot, 0);
|
|
159
|
+
// Also count source files at root level for modules registered at depth > 0
|
|
160
|
+
// (root files don't form a module — they become entrypoints)
|
|
161
|
+
const modulePaths = modules.map(m => m.path);
|
|
162
|
+
// Step 2: Import scanning — build dependency_edges + record file mtimes
|
|
163
|
+
const dependencyEdges = {};
|
|
164
|
+
const sampledFileMtimes = {};
|
|
165
|
+
for (const mod of modules) {
|
|
166
|
+
const files = sourceFilesByModule.get(mod.path) ?? [];
|
|
167
|
+
const deps = new Set();
|
|
168
|
+
// Read first 50 lines of up to 5 files
|
|
169
|
+
const filesToScan = files.slice(0, 5);
|
|
170
|
+
for (const filePath of filesToScan) {
|
|
171
|
+
try {
|
|
172
|
+
// Record mtime for change detection
|
|
173
|
+
const relFile = path.relative(projectRoot, filePath);
|
|
174
|
+
sampledFileMtimes[relFile] = fs.statSync(filePath).mtimeMs;
|
|
175
|
+
const fd = fs.openSync(filePath, 'r');
|
|
176
|
+
const buf = Buffer.alloc(4096); // ~50 lines worth
|
|
177
|
+
const bytesRead = fs.readSync(fd, buf, 0, 4096, 0);
|
|
178
|
+
fs.closeSync(fd);
|
|
179
|
+
const content = buf.toString('utf8', 0, bytesRead);
|
|
180
|
+
// Trim to ~50 lines
|
|
181
|
+
const lines = content.split('\n').slice(0, 50).join('\n');
|
|
182
|
+
const imports = extractImports(lines, filePath);
|
|
183
|
+
for (const spec of imports) {
|
|
184
|
+
const resolved = resolveImportToModule(spec, filePath, projectRoot, modulePaths);
|
|
185
|
+
if (resolved && resolved !== mod.path) {
|
|
186
|
+
deps.add(resolved);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// skip unreadable files
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (deps.size > 0) {
|
|
195
|
+
dependencyEdges[mod.path] = [...deps];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Step 3: Test coverage — find untested modules
|
|
199
|
+
const untestedModules = [];
|
|
200
|
+
for (const mod of modules) {
|
|
201
|
+
if (mod.purpose === 'tests')
|
|
202
|
+
continue;
|
|
203
|
+
const modAbsPath = path.join(projectRoot, mod.path);
|
|
204
|
+
const modParent = path.dirname(modAbsPath);
|
|
205
|
+
const modName = path.basename(modAbsPath);
|
|
206
|
+
let hasTesting = false;
|
|
207
|
+
// Check for __tests__/ sibling
|
|
208
|
+
if (existsDir(path.join(modAbsPath, '__tests__'))) {
|
|
209
|
+
hasTesting = true;
|
|
210
|
+
}
|
|
211
|
+
// Check for parallel test/ or tests/ dir at same level
|
|
212
|
+
if (!hasTesting && (existsDir(path.join(modParent, 'test')) || existsDir(path.join(modParent, 'tests')))) {
|
|
213
|
+
hasTesting = true;
|
|
214
|
+
}
|
|
215
|
+
// Check for *.test.* or *.spec.* files within the module
|
|
216
|
+
if (!hasTesting) {
|
|
217
|
+
const files = sourceFilesByModule.get(mod.path) ?? [];
|
|
218
|
+
hasTesting = files.some(f => {
|
|
219
|
+
const base = path.basename(f);
|
|
220
|
+
return base.includes('.test.') || base.includes('.spec.');
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
if (!hasTesting) {
|
|
224
|
+
untestedModules.push(mod.path);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Step 4: Large files — stat.size / 40 heuristic for LOC
|
|
228
|
+
const largeFiles = [];
|
|
229
|
+
for (const mod of modules) {
|
|
230
|
+
const files = sourceFilesByModule.get(mod.path) ?? [];
|
|
231
|
+
for (const filePath of files) {
|
|
232
|
+
if (largeFiles.length >= 20)
|
|
233
|
+
break;
|
|
234
|
+
try {
|
|
235
|
+
const stat = fs.statSync(filePath);
|
|
236
|
+
const estimatedLines = Math.round(stat.size / 40);
|
|
237
|
+
if (estimatedLines > 300) {
|
|
238
|
+
largeFiles.push({
|
|
239
|
+
path: path.relative(projectRoot, filePath),
|
|
240
|
+
lines: estimatedLines,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// skip
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (largeFiles.length >= 20)
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
// Step 5: Entrypoints
|
|
252
|
+
const entrypoints = [];
|
|
253
|
+
const entrypointNames = [
|
|
254
|
+
'index.ts', 'index.js', 'main.ts', 'main.js',
|
|
255
|
+
'app.ts', 'app.js', 'server.ts', 'server.js',
|
|
256
|
+
'main.py', 'app.py', 'main.go', 'main.rs',
|
|
257
|
+
'index.php', 'main.swift', 'main.rb', 'main.ex',
|
|
258
|
+
];
|
|
259
|
+
const searchDirs = [projectRoot, path.join(projectRoot, 'src')];
|
|
260
|
+
for (const dir of searchDirs) {
|
|
261
|
+
for (const name of entrypointNames) {
|
|
262
|
+
if (entrypoints.length >= 10)
|
|
263
|
+
break;
|
|
264
|
+
const full = path.join(dir, name);
|
|
265
|
+
try {
|
|
266
|
+
if (fs.statSync(full).isFile()) {
|
|
267
|
+
entrypoints.push(path.relative(projectRoot, full));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// doesn't exist
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
built_at: new Date().toISOString(),
|
|
277
|
+
modules,
|
|
278
|
+
dependency_edges: dependencyEdges,
|
|
279
|
+
untested_modules: untestedModules,
|
|
280
|
+
large_files: largeFiles,
|
|
281
|
+
entrypoints,
|
|
282
|
+
sampled_file_mtimes: sampledFileMtimes,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
// refreshCodebaseIndex — incremental reindex, only re-scans changed modules
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
/**
|
|
289
|
+
* Incrementally refresh the codebase index. Re-walks the directory tree (cheap)
|
|
290
|
+
* but only re-scans imports for modules whose file count changed or that are new.
|
|
291
|
+
* Unchanged modules keep their existing dependency edges.
|
|
292
|
+
*/
|
|
293
|
+
export function refreshCodebaseIndex(existing, projectRoot, excludeDirs = []) {
|
|
294
|
+
const fresh = buildCodebaseIndex(projectRoot, excludeDirs);
|
|
295
|
+
// Build lookup of old modules by path
|
|
296
|
+
const oldByPath = new Map(existing.modules.map(m => [m.path, m]));
|
|
297
|
+
// For unchanged modules (same path + same file_count), keep old dependency edges
|
|
298
|
+
// to avoid re-reading file contents. New/changed modules get fresh edges.
|
|
299
|
+
const mergedEdges = {};
|
|
300
|
+
for (const mod of fresh.modules) {
|
|
301
|
+
const old = oldByPath.get(mod.path);
|
|
302
|
+
if (old && old.file_count === mod.file_count) {
|
|
303
|
+
// Module unchanged — reuse existing edges
|
|
304
|
+
const oldEdges = existing.dependency_edges[mod.path];
|
|
305
|
+
if (oldEdges) {
|
|
306
|
+
mergedEdges[mod.path] = oldEdges;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
// New or changed module — use freshly scanned edges
|
|
311
|
+
const freshEdges = fresh.dependency_edges[mod.path];
|
|
312
|
+
if (freshEdges) {
|
|
313
|
+
mergedEdges[mod.path] = freshEdges;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
fresh.dependency_edges = mergedEdges;
|
|
318
|
+
return fresh;
|
|
319
|
+
}
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
// hasStructuralChanges — cheap mtime check to detect external modifications
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
/**
|
|
324
|
+
* Check if the codebase structure has changed since the last index build.
|
|
325
|
+
*
|
|
326
|
+
* Two-layer detection:
|
|
327
|
+
* 1. Directory mtimes — catches file additions/deletions, new dirs, git pulls, branch switches.
|
|
328
|
+
* 2. Sampled file mtimes — catches content edits to files we scanned for imports (dependency edges).
|
|
329
|
+
*
|
|
330
|
+
* Cost: ≤60 dir statSync + ≤250 file statSync. Zero file reads, zero tokens.
|
|
331
|
+
*/
|
|
332
|
+
export function hasStructuralChanges(index, projectRoot) {
|
|
333
|
+
const builtAt = new Date(index.built_at).getTime();
|
|
334
|
+
// Layer 1: Check directory mtimes (structural changes — add/delete/rename)
|
|
335
|
+
const dirsToCheck = new Set();
|
|
336
|
+
for (const mod of index.modules) {
|
|
337
|
+
const absPath = path.join(projectRoot, mod.path);
|
|
338
|
+
dirsToCheck.add(absPath);
|
|
339
|
+
// Also check parent dir (catches new sibling modules)
|
|
340
|
+
dirsToCheck.add(path.dirname(absPath));
|
|
341
|
+
}
|
|
342
|
+
// Always check root and src/ for new top-level dirs
|
|
343
|
+
dirsToCheck.add(projectRoot);
|
|
344
|
+
dirsToCheck.add(path.join(projectRoot, 'src'));
|
|
345
|
+
for (const dir of dirsToCheck) {
|
|
346
|
+
try {
|
|
347
|
+
if (fs.statSync(dir).mtimeMs > builtAt) {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// Dir was removed — structural change if it was a known module
|
|
353
|
+
const rel = path.relative(projectRoot, dir);
|
|
354
|
+
if (index.modules.some(m => m.path === rel)) {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// Layer 2: Check sampled file mtimes (content edits that may change imports)
|
|
360
|
+
for (const [relFile, oldMtime] of Object.entries(index.sampled_file_mtimes)) {
|
|
361
|
+
try {
|
|
362
|
+
const currentMtime = fs.statSync(path.join(projectRoot, relFile)).mtimeMs;
|
|
363
|
+
if (currentMtime !== oldMtime) {
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
// File was deleted — structural change
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
function existsDir(p) {
|
|
375
|
+
try {
|
|
376
|
+
return fs.statSync(p).isDirectory();
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
// formatIndexForPrompt — chunked rendering
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
const CHUNK_SIZE = 15;
|
|
386
|
+
export function formatIndexForPrompt(index, scoutCycle) {
|
|
387
|
+
const { modules, dependency_edges, untested_modules, large_files, entrypoints } = index;
|
|
388
|
+
if (modules.length === 0) {
|
|
389
|
+
return '## Codebase Structure\n\nNo modules detected.';
|
|
390
|
+
}
|
|
391
|
+
const totalChunks = Math.max(1, Math.ceil(modules.length / CHUNK_SIZE));
|
|
392
|
+
const chunkIndex = scoutCycle % totalChunks;
|
|
393
|
+
const offset = chunkIndex * CHUNK_SIZE;
|
|
394
|
+
const focusModules = modules.slice(offset, offset + CHUNK_SIZE);
|
|
395
|
+
const otherModules = modules.filter((_, i) => i < offset || i >= offset + CHUNK_SIZE);
|
|
396
|
+
const parts = [];
|
|
397
|
+
parts.push(`## Codebase Structure (chunk ${chunkIndex + 1}/${totalChunks})`);
|
|
398
|
+
parts.push('');
|
|
399
|
+
parts.push('### Modules in Focus This Cycle');
|
|
400
|
+
for (const mod of focusModules) {
|
|
401
|
+
const deps = dependency_edges[mod.path];
|
|
402
|
+
const depStr = deps ? ` → imports: ${deps.join(', ')}` : '';
|
|
403
|
+
parts.push(`${mod.path}/ — ${mod.file_count} files (${mod.purpose})${depStr}`);
|
|
404
|
+
}
|
|
405
|
+
if (otherModules.length > 0) {
|
|
406
|
+
parts.push('');
|
|
407
|
+
parts.push('### Other Modules (not in focus — available for future cycles)');
|
|
408
|
+
parts.push(otherModules.map(m => m.path + '/').join(', '));
|
|
409
|
+
}
|
|
410
|
+
if (untested_modules.length > 0) {
|
|
411
|
+
parts.push('');
|
|
412
|
+
parts.push('### Untested Modules (high-value scout targets)');
|
|
413
|
+
parts.push(untested_modules.map(m => m + '/').join(', '));
|
|
414
|
+
}
|
|
415
|
+
if (large_files.length > 0) {
|
|
416
|
+
parts.push('');
|
|
417
|
+
parts.push('### Complexity Hotspots (>300 LOC)');
|
|
418
|
+
parts.push(large_files.map(f => `${f.path} (${f.lines})`).join(', '));
|
|
419
|
+
}
|
|
420
|
+
if (entrypoints.length > 0) {
|
|
421
|
+
parts.push('');
|
|
422
|
+
parts.push('### Entrypoints');
|
|
423
|
+
parts.push(entrypoints.join(', '));
|
|
424
|
+
}
|
|
425
|
+
return parts.join('\n');
|
|
426
|
+
}
|
|
427
|
+
//# sourceMappingURL=codebase-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codebase-index.js","sourceRoot":"","sources":["../src/codebase-index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-processor.d.ts","sourceRoot":"","sources":["../src/event-processor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"event-processor.d.ts","sourceRoot":"","sources":["../src/event-processor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,YAAY,CAAC;AAOxD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,wBAAsB,YAAY,CAChC,GAAG,EAAE,UAAU,EACf,EAAE,EAAE,eAAe,EACnB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC,aAAa,CAAC,CAmjBxB"}
|
package/dist/event-processor.js
CHANGED
|
@@ -8,6 +8,8 @@ import { repos } from '@blockspool/core';
|
|
|
8
8
|
import { filterAndCreateTickets } from './proposals.js';
|
|
9
9
|
import { deriveScopePolicy, validatePlanScope } from './scope-policy.js';
|
|
10
10
|
import { recordDiff, recordCommandFailure, recordPlanHash } from './spindle.js';
|
|
11
|
+
import { addLearning, confirmLearning, extractTags } from './learnings.js';
|
|
12
|
+
const MAX_SCOUT_RETRIES = 2;
|
|
11
13
|
export async function processEvent(run, db, type, payload) {
|
|
12
14
|
const s = run.require();
|
|
13
15
|
switch (type) {
|
|
@@ -18,38 +20,125 @@ export async function processEvent(run, db, type, payload) {
|
|
|
18
20
|
if (s.phase !== 'SCOUT') {
|
|
19
21
|
return { processed: true, phase_changed: false, message: 'Scout output outside SCOUT phase, ignored' };
|
|
20
22
|
}
|
|
23
|
+
// Track explored directories for rotation across cycles
|
|
24
|
+
const exploredDirs = (payload['explored_dirs'] ?? []);
|
|
25
|
+
if (exploredDirs.length > 0) {
|
|
26
|
+
for (const dir of exploredDirs) {
|
|
27
|
+
if (!s.scouted_dirs.includes(dir)) {
|
|
28
|
+
s.scouted_dirs.push(dir);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
21
32
|
// Extract proposals from payload
|
|
22
33
|
const rawProposals = (payload['proposals'] ?? []);
|
|
34
|
+
// Build exploration log entry (before empty-check so retries also get logged)
|
|
35
|
+
const explorationSummary = (payload['exploration_summary'] ?? '');
|
|
36
|
+
const logEntry = explorationSummary
|
|
37
|
+
? `Attempt ${s.scout_retries + 1}: Explored ${exploredDirs.join(', ') || '(unknown)'}. Found ${rawProposals.length} proposals. ${explorationSummary}`
|
|
38
|
+
: `Attempt ${s.scout_retries + 1}: Explored ${exploredDirs.join(', ') || '(unknown)'}. Found ${rawProposals.length} proposals.`;
|
|
39
|
+
s.scout_exploration_log.push(logEntry);
|
|
23
40
|
if (rawProposals.length === 0) {
|
|
24
|
-
|
|
41
|
+
if (s.scout_retries < MAX_SCOUT_RETRIES) {
|
|
42
|
+
s.scout_retries++;
|
|
43
|
+
// Stay in SCOUT phase — advance() will return an escalated prompt
|
|
44
|
+
return {
|
|
45
|
+
processed: true,
|
|
46
|
+
phase_changed: false,
|
|
47
|
+
message: `No proposals found (attempt ${s.scout_retries}/${MAX_SCOUT_RETRIES + 1}). Retrying with deeper analysis.`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Exhausted retries — genuinely no work
|
|
25
51
|
run.setPhase('DONE');
|
|
26
52
|
return {
|
|
27
53
|
processed: true,
|
|
28
54
|
phase_changed: true,
|
|
29
55
|
new_phase: 'DONE',
|
|
30
|
-
message: 'No proposals in scout output, transitioning to DONE',
|
|
56
|
+
message: 'No proposals in scout output after all retries, transitioning to DONE',
|
|
31
57
|
};
|
|
32
58
|
}
|
|
33
|
-
//
|
|
34
|
-
|
|
59
|
+
// Store proposals as pending for adversarial review (instead of creating tickets immediately)
|
|
60
|
+
s.pending_proposals = rawProposals;
|
|
35
61
|
// Save proposals artifact
|
|
36
|
-
run.saveArtifact(`${s.step_count}-scout-proposals.json`, JSON.stringify({ raw: rawProposals,
|
|
62
|
+
run.saveArtifact(`${s.step_count}-scout-proposals.json`, JSON.stringify({ raw: rawProposals, pending_review: true }, null, 2));
|
|
63
|
+
return {
|
|
64
|
+
processed: true,
|
|
65
|
+
phase_changed: false,
|
|
66
|
+
message: `${rawProposals.length} proposals pending adversarial review`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// -----------------------------------------------------------------
|
|
70
|
+
// Proposal review (adversarial critique pass)
|
|
71
|
+
// -----------------------------------------------------------------
|
|
72
|
+
case 'PROPOSALS_REVIEWED': {
|
|
73
|
+
if (s.phase !== 'SCOUT') {
|
|
74
|
+
return { processed: true, phase_changed: false, message: 'PROPOSALS_REVIEWED outside SCOUT phase, ignored' };
|
|
75
|
+
}
|
|
76
|
+
const pendingProposals = s.pending_proposals;
|
|
77
|
+
if (!pendingProposals || pendingProposals.length === 0) {
|
|
78
|
+
return { processed: true, phase_changed: false, message: 'No pending proposals to review' };
|
|
79
|
+
}
|
|
80
|
+
// Apply revised scores from review
|
|
81
|
+
const reviewedItems = (payload['reviewed_proposals'] ?? []);
|
|
82
|
+
// Merge reviewed scores back into pending proposals
|
|
83
|
+
for (const reviewed of reviewedItems) {
|
|
84
|
+
if (!reviewed.title)
|
|
85
|
+
continue;
|
|
86
|
+
const match = pendingProposals.find(p => p.title === reviewed.title);
|
|
87
|
+
if (match) {
|
|
88
|
+
// Record learning if confidence lowered >20 pts
|
|
89
|
+
if (s.learnings_enabled && typeof reviewed.confidence === 'number' && typeof match.confidence === 'number') {
|
|
90
|
+
const drop = match.confidence - reviewed.confidence;
|
|
91
|
+
if (drop > 20) {
|
|
92
|
+
addLearning(run.rootPath, {
|
|
93
|
+
text: `Proposal "${reviewed.title}" had inflated confidence (${match.confidence}→${reviewed.confidence})`,
|
|
94
|
+
category: 'warning',
|
|
95
|
+
source: { type: 'review_downgrade', detail: reviewed.review_note },
|
|
96
|
+
tags: extractTags(match.files ?? match.allowed_paths ?? [], []),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (typeof reviewed.confidence === 'number')
|
|
101
|
+
match.confidence = reviewed.confidence;
|
|
102
|
+
if (typeof reviewed.impact_score === 'number')
|
|
103
|
+
match.impact_score = reviewed.impact_score;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Clear pending
|
|
107
|
+
s.pending_proposals = null;
|
|
108
|
+
// Now filter and create tickets with revised scores
|
|
109
|
+
const result = await filterAndCreateTickets(run, db, pendingProposals);
|
|
110
|
+
// Update exploration log with rejection info
|
|
111
|
+
const lastIdx = s.scout_exploration_log.length - 1;
|
|
112
|
+
if (lastIdx >= 0) {
|
|
113
|
+
s.scout_exploration_log[lastIdx] += ` ${result.accepted.length} accepted, ${result.rejected.length} rejected (${result.rejected.map(r => r.reason).slice(0, 3).join('; ')}).`;
|
|
114
|
+
}
|
|
115
|
+
// Save reviewed artifact
|
|
116
|
+
run.saveArtifact(`${s.step_count}-scout-proposals-reviewed.json`, JSON.stringify({ reviewed: reviewedItems, result }, null, 2));
|
|
37
117
|
if (result.created_ticket_ids.length > 0) {
|
|
118
|
+
s.scout_retries = 0;
|
|
38
119
|
run.setPhase('NEXT_TICKET');
|
|
39
120
|
return {
|
|
40
121
|
processed: true,
|
|
41
122
|
phase_changed: true,
|
|
42
123
|
new_phase: 'NEXT_TICKET',
|
|
43
|
-
message: `Created ${result.created_ticket_ids.length} tickets
|
|
124
|
+
message: `Created ${result.created_ticket_ids.length} tickets after review (${result.rejected.length} rejected)`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// All proposals rejected after review
|
|
128
|
+
if (s.scout_retries < MAX_SCOUT_RETRIES) {
|
|
129
|
+
s.scout_retries++;
|
|
130
|
+
return {
|
|
131
|
+
processed: true,
|
|
132
|
+
phase_changed: false,
|
|
133
|
+
message: `All proposals rejected after review (attempt ${s.scout_retries}/${MAX_SCOUT_RETRIES + 1}). Retrying.`,
|
|
44
134
|
};
|
|
45
135
|
}
|
|
46
|
-
// All proposals rejected — done
|
|
47
136
|
run.setPhase('DONE');
|
|
48
137
|
return {
|
|
49
138
|
processed: true,
|
|
50
139
|
phase_changed: true,
|
|
51
140
|
new_phase: 'DONE',
|
|
52
|
-
message: `All
|
|
141
|
+
message: `All proposals rejected after review and all retries: ${result.rejected.map(r => r.reason).join('; ')}`,
|
|
53
142
|
};
|
|
54
143
|
}
|
|
55
144
|
case 'PROPOSALS_FILTERED': {
|
|
@@ -118,6 +207,15 @@ export async function processEvent(run, db, type, payload) {
|
|
|
118
207
|
if (!scopeResult.valid) {
|
|
119
208
|
s.plan_rejections++;
|
|
120
209
|
run.appendEvent('PLAN_REJECTED', { reason: scopeResult.reason, attempt: s.plan_rejections });
|
|
210
|
+
// Record learning on plan rejection
|
|
211
|
+
if (s.learnings_enabled) {
|
|
212
|
+
addLearning(run.rootPath, {
|
|
213
|
+
text: `Plan rejected: ${scopeResult.reason}`.slice(0, 200),
|
|
214
|
+
category: 'gotcha',
|
|
215
|
+
source: { type: 'plan_rejection', detail: scopeResult.reason ?? undefined },
|
|
216
|
+
tags: extractTags(plan.files_to_touch.map(f => f.path), []),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
121
219
|
return {
|
|
122
220
|
processed: true,
|
|
123
221
|
phase_changed: false,
|
|
@@ -211,6 +309,17 @@ export async function processEvent(run, db, type, payload) {
|
|
|
211
309
|
};
|
|
212
310
|
}
|
|
213
311
|
if (status === 'failed') {
|
|
312
|
+
// Record learning on ticket failure
|
|
313
|
+
if (s.learnings_enabled) {
|
|
314
|
+
const reason = payload['reason'] ?? 'Execution failed';
|
|
315
|
+
const ticket = s.current_ticket_id ? await repos.tickets.getById(db, s.current_ticket_id) : null;
|
|
316
|
+
addLearning(run.rootPath, {
|
|
317
|
+
text: `Ticket failed on ${ticket?.title ?? 'unknown'} — ${reason}`.slice(0, 200),
|
|
318
|
+
category: 'warning',
|
|
319
|
+
source: { type: 'ticket_failure', detail: reason },
|
|
320
|
+
tags: extractTags(ticket?.allowedPaths ?? [], ticket?.verificationCommands ?? []),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
214
323
|
// Fail the ticket, move to next
|
|
215
324
|
if (s.current_ticket_id) {
|
|
216
325
|
await repos.tickets.updateStatus(db, s.current_ticket_id, 'blocked');
|
|
@@ -253,6 +362,13 @@ export async function processEvent(run, db, type, payload) {
|
|
|
253
362
|
if (s.phase !== 'QA') {
|
|
254
363
|
return { processed: true, phase_changed: false, message: 'QA passed outside QA phase' };
|
|
255
364
|
}
|
|
365
|
+
// Confirm injected learnings on success
|
|
366
|
+
if (s.learnings_enabled && s.injected_learning_ids.length > 0) {
|
|
367
|
+
for (const id of s.injected_learning_ids) {
|
|
368
|
+
confirmLearning(run.rootPath, id);
|
|
369
|
+
}
|
|
370
|
+
s.injected_learning_ids = [];
|
|
371
|
+
}
|
|
256
372
|
// Mark ticket done in DB
|
|
257
373
|
if (s.current_ticket_id) {
|
|
258
374
|
await repos.tickets.updateStatus(db, s.current_ticket_id, 'done');
|
|
@@ -287,6 +403,18 @@ export async function processEvent(run, db, type, payload) {
|
|
|
287
403
|
}, null, 2));
|
|
288
404
|
s.qa_retries++;
|
|
289
405
|
if (s.qa_retries >= 3) {
|
|
406
|
+
// Record learning on final QA failure
|
|
407
|
+
if (s.learnings_enabled) {
|
|
408
|
+
const failedCmds = (payload['failed_commands'] ?? payload['command'] ?? '');
|
|
409
|
+
const errorSummary = (payload['error'] ?? payload['output'] ?? '').slice(0, 100);
|
|
410
|
+
const ticket = s.current_ticket_id ? await repos.tickets.getById(db, s.current_ticket_id) : null;
|
|
411
|
+
addLearning(run.rootPath, {
|
|
412
|
+
text: `QA fails on ${ticket?.title ?? 'unknown'} — ${errorSummary || failedCmds}`.slice(0, 200),
|
|
413
|
+
category: 'gotcha',
|
|
414
|
+
source: { type: 'qa_failure', detail: failedCmds },
|
|
415
|
+
tags: extractTags(ticket?.allowedPaths ?? [], ticket?.verificationCommands ?? []),
|
|
416
|
+
});
|
|
417
|
+
}
|
|
290
418
|
// Give up on this ticket
|
|
291
419
|
if (s.current_ticket_id) {
|
|
292
420
|
await repos.tickets.updateStatus(db, s.current_ticket_id, 'blocked');
|