@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.
@@ -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;AAMxD,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;AAED,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,CAmaxB"}
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"}
@@ -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
- // No proposals found — done
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
- // Filter, dedup, score, create tickets
34
- const result = await filterAndCreateTickets(run, db, rawProposals);
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, result }, null, 2));
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 from ${rawProposals.length} proposals (${result.rejected.length} rejected)`,
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 ${rawProposals.length} proposals rejected: ${result.rejected.map(r => r.reason).join('; ')}`,
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');