@blockspool/cli 0.4.3 → 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/bin/blockspool.js +1 -1
- package/dist/lib/codebase-index.d.ts +46 -0
- package/dist/lib/codebase-index.d.ts.map +1 -0
- package/dist/lib/codebase-index.js +427 -0
- package/dist/lib/codebase-index.js.map +1 -0
- package/dist/lib/learnings.d.ts +68 -0
- package/dist/lib/learnings.d.ts.map +1 -0
- package/dist/lib/learnings.js +248 -0
- package/dist/lib/learnings.js.map +1 -0
- package/dist/lib/project-metadata.d.ts +34 -0
- package/dist/lib/project-metadata.d.ts.map +1 -0
- package/dist/lib/project-metadata.js +504 -0
- package/dist/lib/project-metadata.js.map +1 -0
- package/dist/lib/proposal-review.d.ts +26 -0
- package/dist/lib/proposal-review.d.ts.map +1 -0
- package/dist/lib/proposal-review.js +89 -0
- package/dist/lib/proposal-review.js.map +1 -0
- package/dist/lib/solo-auto.d.ts.map +1 -1
- package/dist/lib/solo-auto.js +183 -2
- package/dist/lib/solo-auto.js.map +1 -1
- package/dist/lib/solo-ci.d.ts +1 -1
- package/dist/lib/solo-ci.d.ts.map +1 -1
- package/dist/lib/solo-ci.js +6 -0
- package/dist/lib/solo-ci.js.map +1 -1
- package/dist/lib/solo-config.d.ts +8 -0
- package/dist/lib/solo-config.d.ts.map +1 -1
- package/dist/lib/solo-config.js +4 -0
- package/dist/lib/solo-config.js.map +1 -1
- package/dist/lib/solo-ticket.d.ts +6 -2
- package/dist/lib/solo-ticket.d.ts.map +1 -1
- package/dist/lib/solo-ticket.js +29 -3
- package/dist/lib/solo-ticket.js.map +1 -1
- package/dist/lib/spindle.d.ts +21 -1
- package/dist/lib/spindle.d.ts.map +1 -1
- package/dist/lib/spindle.js +152 -3
- package/dist/lib/spindle.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/blockspool.js
CHANGED
|
@@ -18,7 +18,7 @@ const program = new Command();
|
|
|
18
18
|
program
|
|
19
19
|
.name('blockspool')
|
|
20
20
|
.description('Continuous codebase improvement tool')
|
|
21
|
-
.version('0.
|
|
21
|
+
.version('0.5.0');
|
|
22
22
|
// `blockspool solo <cmd>` — backwards compat
|
|
23
23
|
program.addCommand(soloCommand);
|
|
24
24
|
// Detect if argv[2] is a known solo subcommand name — if so, lift it.
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
* Copied from packages/mcp/src/codebase-index.ts (no cross-package deps).
|
|
8
|
+
*/
|
|
9
|
+
export interface CodebaseIndex {
|
|
10
|
+
built_at: string;
|
|
11
|
+
modules: ModuleEntry[];
|
|
12
|
+
dependency_edges: Record<string, string[]>;
|
|
13
|
+
untested_modules: string[];
|
|
14
|
+
large_files: LargeFileEntry[];
|
|
15
|
+
entrypoints: string[];
|
|
16
|
+
/** mtimes of files sampled for import scanning — used for change detection. Not included in prompt. */
|
|
17
|
+
sampled_file_mtimes: Record<string, number>;
|
|
18
|
+
}
|
|
19
|
+
export interface ModuleEntry {
|
|
20
|
+
path: string;
|
|
21
|
+
file_count: number;
|
|
22
|
+
purpose: string;
|
|
23
|
+
}
|
|
24
|
+
export interface LargeFileEntry {
|
|
25
|
+
path: string;
|
|
26
|
+
lines: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function buildCodebaseIndex(projectRoot: string, excludeDirs?: string[]): CodebaseIndex;
|
|
29
|
+
/**
|
|
30
|
+
* Incrementally refresh the codebase index. Re-walks the directory tree (cheap)
|
|
31
|
+
* but only re-scans imports for modules whose file count changed or that are new.
|
|
32
|
+
* Unchanged modules keep their existing dependency edges.
|
|
33
|
+
*/
|
|
34
|
+
export declare function refreshCodebaseIndex(existing: CodebaseIndex, projectRoot: string, excludeDirs?: string[]): CodebaseIndex;
|
|
35
|
+
/**
|
|
36
|
+
* Check if the codebase structure has changed since the last index build.
|
|
37
|
+
*
|
|
38
|
+
* Two-layer detection:
|
|
39
|
+
* 1. Directory mtimes — catches file additions/deletions, new dirs, git pulls, branch switches.
|
|
40
|
+
* 2. Sampled file mtimes — catches content edits to files we scanned for imports (dependency edges).
|
|
41
|
+
*
|
|
42
|
+
* Cost: ≤60 dir statSync + ≤250 file statSync. Zero file reads, zero tokens.
|
|
43
|
+
*/
|
|
44
|
+
export declare function hasStructuralChanges(index: CodebaseIndex, projectRoot: string): boolean;
|
|
45
|
+
export declare function formatIndexForPrompt(index: CodebaseIndex, scoutCycle: number): string;
|
|
46
|
+
//# sourceMappingURL=codebase-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codebase-index.d.ts","sourceRoot":"","sources":["../../src/lib/codebase-index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3C,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,uGAAuG;IACvG,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAmHD,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,WAAW,GAAE,MAAM,EAAO,GACzB,aAAa,CA+Lf;AAMD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,aAAa,EACvB,WAAW,EAAE,MAAM,EACnB,WAAW,GAAE,MAAM,EAAO,GACzB,aAAa,CA6Bf;AAMD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,EACpB,WAAW,EAAE,MAAM,GAClB,OAAO,CA6CT;AAgBD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAkDrF"}
|
|
@@ -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
|
+
* Copied from packages/mcp/src/codebase-index.ts (no cross-package deps).
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Source file extensions
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
15
|
+
'.ts', '.js', '.py', '.rs', '.go', '.rb', '.java', '.cs', '.ex', '.php', '.swift',
|
|
16
|
+
]);
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Purpose inference from directory name
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const PURPOSE_MAP = {
|
|
21
|
+
api: 'api',
|
|
22
|
+
apis: 'api',
|
|
23
|
+
routes: 'api',
|
|
24
|
+
handlers: 'api',
|
|
25
|
+
controllers: 'api',
|
|
26
|
+
endpoints: 'api',
|
|
27
|
+
services: 'services',
|
|
28
|
+
service: 'services',
|
|
29
|
+
lib: 'services',
|
|
30
|
+
core: 'services',
|
|
31
|
+
test: 'tests',
|
|
32
|
+
tests: 'tests',
|
|
33
|
+
__tests__: 'tests',
|
|
34
|
+
spec: 'tests',
|
|
35
|
+
specs: 'tests',
|
|
36
|
+
ui: 'ui',
|
|
37
|
+
components: 'ui',
|
|
38
|
+
views: 'ui',
|
|
39
|
+
pages: 'ui',
|
|
40
|
+
screens: 'ui',
|
|
41
|
+
utils: 'utils',
|
|
42
|
+
util: 'utils',
|
|
43
|
+
helpers: 'utils',
|
|
44
|
+
shared: 'utils',
|
|
45
|
+
common: 'utils',
|
|
46
|
+
config: 'config',
|
|
47
|
+
configs: 'config',
|
|
48
|
+
configuration: 'config',
|
|
49
|
+
settings: 'config',
|
|
50
|
+
};
|
|
51
|
+
function inferPurpose(dirName) {
|
|
52
|
+
const lower = dirName.toLowerCase();
|
|
53
|
+
return PURPOSE_MAP[lower] ?? 'unknown';
|
|
54
|
+
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Import regex patterns
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// JS/TS: import ... from '...' or require('...')
|
|
59
|
+
const JS_IMPORT_RE = /(?:import\s+.*?\s+from\s+['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\))/g;
|
|
60
|
+
// Python: from X import ... or import X
|
|
61
|
+
const PY_IMPORT_RE = /(?:from\s+([\w.]+)\s+import|^import\s+([\w.]+))/gm;
|
|
62
|
+
// Go: import "..."
|
|
63
|
+
const GO_IMPORT_RE = /import\s+"([^"]+)"/g;
|
|
64
|
+
function extractImports(content, filePath) {
|
|
65
|
+
const ext = path.extname(filePath);
|
|
66
|
+
const imports = [];
|
|
67
|
+
if (ext === '.ts' || ext === '.js') {
|
|
68
|
+
for (const m of content.matchAll(JS_IMPORT_RE)) {
|
|
69
|
+
const spec = m[1] ?? m[2];
|
|
70
|
+
if (spec)
|
|
71
|
+
imports.push(spec);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if (ext === '.py') {
|
|
75
|
+
for (const m of content.matchAll(PY_IMPORT_RE)) {
|
|
76
|
+
const spec = m[1] ?? m[2];
|
|
77
|
+
if (spec)
|
|
78
|
+
imports.push(spec);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (ext === '.go') {
|
|
82
|
+
for (const m of content.matchAll(GO_IMPORT_RE)) {
|
|
83
|
+
if (m[1])
|
|
84
|
+
imports.push(m[1]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return imports;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolve a relative import specifier to a module path relative to projectRoot.
|
|
91
|
+
* Returns null for non-relative (package) imports.
|
|
92
|
+
*/
|
|
93
|
+
function resolveImportToModule(specifier, sourceFile, projectRoot, modulePaths) {
|
|
94
|
+
// Only resolve relative imports
|
|
95
|
+
if (!specifier.startsWith('.'))
|
|
96
|
+
return null;
|
|
97
|
+
const sourceDir = path.dirname(sourceFile);
|
|
98
|
+
const resolved = path.resolve(sourceDir, specifier);
|
|
99
|
+
const relative = path.relative(projectRoot, resolved);
|
|
100
|
+
// Find which module this resolved path falls under
|
|
101
|
+
for (const mod of modulePaths) {
|
|
102
|
+
if (relative === mod || relative.startsWith(mod + '/') || relative.startsWith(mod + path.sep)) {
|
|
103
|
+
return mod;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// buildCodebaseIndex
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
export function buildCodebaseIndex(projectRoot, excludeDirs = []) {
|
|
112
|
+
const excludeSet = new Set(excludeDirs.map(d => d.toLowerCase()));
|
|
113
|
+
// Step 1: Module map — walk dirs 2 levels deep
|
|
114
|
+
const modules = [];
|
|
115
|
+
const sourceFilesByModule = new Map();
|
|
116
|
+
function shouldExclude(name) {
|
|
117
|
+
return excludeSet.has(name.toLowerCase()) || name.startsWith('.');
|
|
118
|
+
}
|
|
119
|
+
function walkForModules(dir, depth) {
|
|
120
|
+
if (modules.length >= 50)
|
|
121
|
+
return;
|
|
122
|
+
let entries;
|
|
123
|
+
try {
|
|
124
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const sourceFiles = [];
|
|
130
|
+
const subdirs = [];
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
if (entry.isFile() && SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
|
|
133
|
+
sourceFiles.push(path.join(dir, entry.name));
|
|
134
|
+
}
|
|
135
|
+
else if (entry.isDirectory() && !shouldExclude(entry.name)) {
|
|
136
|
+
subdirs.push(entry);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Register this dir as a module if it has source files
|
|
140
|
+
if (sourceFiles.length > 0 && depth > 0) {
|
|
141
|
+
const relPath = path.relative(projectRoot, dir);
|
|
142
|
+
if (relPath && modules.length < 50) {
|
|
143
|
+
modules.push({
|
|
144
|
+
path: relPath,
|
|
145
|
+
file_count: sourceFiles.length,
|
|
146
|
+
purpose: inferPurpose(path.basename(dir)),
|
|
147
|
+
});
|
|
148
|
+
sourceFilesByModule.set(relPath, sourceFiles);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Recurse into subdirs (up to depth 2)
|
|
152
|
+
if (depth < 2) {
|
|
153
|
+
for (const sub of subdirs) {
|
|
154
|
+
if (modules.length >= 50)
|
|
155
|
+
break;
|
|
156
|
+
walkForModules(path.join(dir, sub.name), depth + 1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
walkForModules(projectRoot, 0);
|
|
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/lib/codebase-index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-run learning mechanism.
|
|
3
|
+
*
|
|
4
|
+
* Persists learnings to `.blockspool/learnings.json` and provides
|
|
5
|
+
* decay, consolidation, relevance scoring, and prompt formatting.
|
|
6
|
+
*
|
|
7
|
+
* Copied from packages/mcp/src/learnings.ts with titleSimilarity
|
|
8
|
+
* inlined to avoid cross-package dependency.
|
|
9
|
+
*/
|
|
10
|
+
export interface Learning {
|
|
11
|
+
id: string;
|
|
12
|
+
text: string;
|
|
13
|
+
category: 'gotcha' | 'pattern' | 'warning' | 'context';
|
|
14
|
+
source: {
|
|
15
|
+
type: 'qa_failure' | 'ticket_failure' | 'ticket_success' | 'review_downgrade' | 'plan_rejection' | 'scope_violation' | 'manual';
|
|
16
|
+
detail?: string;
|
|
17
|
+
};
|
|
18
|
+
tags: string[];
|
|
19
|
+
weight: number;
|
|
20
|
+
created_at: string;
|
|
21
|
+
last_confirmed_at: string;
|
|
22
|
+
access_count: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Load learnings, apply decay, prune dead entries, write back.
|
|
26
|
+
* Called once per session start.
|
|
27
|
+
*/
|
|
28
|
+
export declare function loadLearnings(projectRoot: string, decayRate?: number): Learning[];
|
|
29
|
+
/**
|
|
30
|
+
* Add a new learning with sensible defaults.
|
|
31
|
+
*/
|
|
32
|
+
export declare function addLearning(projectRoot: string, input: {
|
|
33
|
+
text: string;
|
|
34
|
+
category: Learning['category'];
|
|
35
|
+
source: Learning['source'];
|
|
36
|
+
tags?: string[];
|
|
37
|
+
}): Learning;
|
|
38
|
+
/**
|
|
39
|
+
* Confirm a learning: bump weight +10 and update last_confirmed_at.
|
|
40
|
+
*/
|
|
41
|
+
export declare function confirmLearning(projectRoot: string, id: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Record access: increment access_count for each id.
|
|
44
|
+
*/
|
|
45
|
+
export declare function recordAccess(projectRoot: string, ids: string[]): void;
|
|
46
|
+
/**
|
|
47
|
+
* Consolidate near-duplicate learnings (>70% text similarity).
|
|
48
|
+
* Keeps the higher weight entry, sums access counts.
|
|
49
|
+
*/
|
|
50
|
+
export declare function consolidateLearnings(projectRoot: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* Format learnings for prompt injection.
|
|
53
|
+
* Sorts by weight descending, respects char budget.
|
|
54
|
+
*/
|
|
55
|
+
export declare function formatLearningsForPrompt(learnings: Learning[], budget?: number): string;
|
|
56
|
+
/**
|
|
57
|
+
* Select learnings relevant to the current context.
|
|
58
|
+
* Scores by tag overlap × weight.
|
|
59
|
+
*/
|
|
60
|
+
export declare function selectRelevant(learnings: Learning[], context: {
|
|
61
|
+
paths?: string[];
|
|
62
|
+
commands?: string[];
|
|
63
|
+
}): Learning[];
|
|
64
|
+
/**
|
|
65
|
+
* Extract tags from paths and commands for tag matching.
|
|
66
|
+
*/
|
|
67
|
+
export declare function extractTags(paths: string[], commands: string[]): string[];
|
|
68
|
+
//# sourceMappingURL=learnings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings.d.ts","sourceRoot":"","sources":["../../src/lib/learnings.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAuCH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IACvD,MAAM,EAAE;QACN,IAAI,EACA,YAAY,GACZ,gBAAgB,GAChB,gBAAgB,GAChB,kBAAkB,GAClB,gBAAgB,GAChB,iBAAiB,GACjB,QAAQ,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAgDD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,GAAE,MAA2B,GAAG,QAAQ,EAAE,CA4BrG;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE;IACL,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC/B,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,GACA,QAAQ,CAiBV;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,CAUrE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CA0B9D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,MAAM,GAAE,MAAuB,GAAG,MAAM,CAqBvG;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,QAAQ,EAAE,CAgBpH;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAWzE"}
|