aibridge-context 1.5.2 → 2.0.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/bin/cli.js +469 -170
- package/core/briefingGenerator.js +368 -0
- package/core/codeDiff.js +304 -0
- package/core/fileSnapshot.js +178 -0
- package/core/stateManager.js +803 -1369
- package/core/watcher.js +78 -49
- package/index.js +23 -9
- package/package.json +7 -3
- package/server/routes.js +30 -34
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* fileSnapshot.js
|
|
5
|
+
* ---------------
|
|
6
|
+
* Maintains an in-memory snapshot of every tracked file's content.
|
|
7
|
+
* The watcher calls captureSnapshot(file) BEFORE a change is processed,
|
|
8
|
+
* so diffTexts() can compare old vs new.
|
|
9
|
+
*
|
|
10
|
+
* Also owns the "code_files" section of state: a catalogue of every
|
|
11
|
+
* tracked source file with its exports, imports, functions and line count.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const CODE_EXTENSIONS = new Set(['.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx', '.py', '.go', '.rs', '.java', '.rb', '.php', '.cs', '.swift']);
|
|
18
|
+
const MAX_READ_BYTES = 200_000; // 200 KB per file cap
|
|
19
|
+
|
|
20
|
+
// In-process store: absolute path → content string
|
|
21
|
+
const _snapshots = new Map();
|
|
22
|
+
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────
|
|
24
|
+
// Snapshot capture / retrieval
|
|
25
|
+
// ─────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
function captureSnapshot(absolutePath) {
|
|
28
|
+
try {
|
|
29
|
+
const content = readFileSafe(absolutePath);
|
|
30
|
+
_snapshots.set(absolutePath, content);
|
|
31
|
+
return content;
|
|
32
|
+
} catch (_) {
|
|
33
|
+
_snapshots.set(absolutePath, '');
|
|
34
|
+
return '';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getSnapshot(absolutePath) {
|
|
39
|
+
return _snapshots.has(absolutePath) ? _snapshots.get(absolutePath) : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readCurrentContent(absolutePath) {
|
|
43
|
+
try {
|
|
44
|
+
return readFileSafe(absolutePath);
|
|
45
|
+
} catch (_) {
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readFileSafe(absolutePath) {
|
|
51
|
+
const stat = fs.statSync(absolutePath);
|
|
52
|
+
if (stat.size > MAX_READ_BYTES) {
|
|
53
|
+
// Read only the first MAX_READ_BYTES for large files
|
|
54
|
+
const buf = Buffer.alloc(MAX_READ_BYTES);
|
|
55
|
+
const fd = fs.openSync(absolutePath, 'r');
|
|
56
|
+
fs.readSync(fd, buf, 0, MAX_READ_BYTES, 0);
|
|
57
|
+
fs.closeSync(fd);
|
|
58
|
+
return buf.toString('utf8') + '\n[file truncated]';
|
|
59
|
+
}
|
|
60
|
+
return fs.readFileSync(absolutePath, 'utf8');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function deleteSnapshot(absolutePath) {
|
|
64
|
+
_snapshots.delete(absolutePath);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─────────────────────────────────────────────────────────────────
|
|
68
|
+
// Code file catalogue builder
|
|
69
|
+
// ─────────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* For each tracked source file, extract:
|
|
73
|
+
* - line count
|
|
74
|
+
* - exported names (JS/TS)
|
|
75
|
+
* - imported modules
|
|
76
|
+
* - top-level function names
|
|
77
|
+
* - class names
|
|
78
|
+
*
|
|
79
|
+
* Returns an object keyed by relative file path.
|
|
80
|
+
*/
|
|
81
|
+
function buildCodeFileCatalogue(projectRoot, fileList) {
|
|
82
|
+
const resolvedRoot = path.resolve(projectRoot);
|
|
83
|
+
const catalogue = {};
|
|
84
|
+
|
|
85
|
+
for (const relPath of (fileList || [])) {
|
|
86
|
+
const ext = path.extname(relPath).toLowerCase();
|
|
87
|
+
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
88
|
+
|
|
89
|
+
const absPath = path.join(resolvedRoot, relPath);
|
|
90
|
+
let content = '';
|
|
91
|
+
try { content = readCurrentContent(absPath); } catch (_) { continue; }
|
|
92
|
+
|
|
93
|
+
catalogue[relPath] = analyseSourceFile(content, ext);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return catalogue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function analyseSourceFile(content, ext) {
|
|
100
|
+
const lines = content.split('\n');
|
|
101
|
+
const result = {
|
|
102
|
+
lines: lines.length,
|
|
103
|
+
functions: [],
|
|
104
|
+
classes: [],
|
|
105
|
+
exports: [],
|
|
106
|
+
imports: []
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (['.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx'].includes(ext)) {
|
|
110
|
+
analyseJs(content, result);
|
|
111
|
+
} else if (ext === '.py') {
|
|
112
|
+
analysePython(content, result);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function analyseJs(content, result) {
|
|
119
|
+
// Named functions
|
|
120
|
+
const fnPattern = /^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][A-Za-z0-9_$]*)/gm;
|
|
121
|
+
let m;
|
|
122
|
+
while ((m = fnPattern.exec(content)) !== null) result.functions.push(m[1]);
|
|
123
|
+
|
|
124
|
+
// Arrow / const functions
|
|
125
|
+
const arrowPattern = /^(?:export\s+)?(?:const|let)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*(?:async\s*)?\(/gm;
|
|
126
|
+
while ((m = arrowPattern.exec(content)) !== null) result.functions.push(m[1]);
|
|
127
|
+
|
|
128
|
+
// Classes
|
|
129
|
+
const classPattern = /^(?:export\s+)?class\s+([A-Za-z_$][A-Za-z0-9_$]*)/gm;
|
|
130
|
+
while ((m = classPattern.exec(content)) !== null) result.classes.push(m[1]);
|
|
131
|
+
|
|
132
|
+
// CommonJS exports
|
|
133
|
+
const cjsExportPattern = /module\.exports\s*=\s*\{([^}]+)\}/s;
|
|
134
|
+
const cjsMatch = cjsExportPattern.exec(content);
|
|
135
|
+
if (cjsMatch) {
|
|
136
|
+
const names = cjsMatch[1].match(/([A-Za-z_$][A-Za-z0-9_$]*)/g) || [];
|
|
137
|
+
result.exports.push(...names);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ES named exports
|
|
141
|
+
const esExportPattern = /^export\s+(?:const|let|function|class|async)\s+([A-Za-z_$][A-Za-z0-9_$]*)/gm;
|
|
142
|
+
while ((m = esExportPattern.exec(content)) !== null) result.exports.push(m[1]);
|
|
143
|
+
|
|
144
|
+
// require() imports
|
|
145
|
+
const requirePattern = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
146
|
+
while ((m = requirePattern.exec(content)) !== null) result.imports.push(m[1]);
|
|
147
|
+
|
|
148
|
+
// ES import
|
|
149
|
+
const importPattern = /^import\s+.*\s+from\s+['"]([^'"]+)['"]/gm;
|
|
150
|
+
while ((m = importPattern.exec(content)) !== null) result.imports.push(m[1]);
|
|
151
|
+
|
|
152
|
+
// De-dup
|
|
153
|
+
result.functions = [...new Set(result.functions)].slice(0, 40);
|
|
154
|
+
result.classes = [...new Set(result.classes)].slice(0, 20);
|
|
155
|
+
result.exports = [...new Set(result.exports)].slice(0, 40);
|
|
156
|
+
result.imports = [...new Set(result.imports)].slice(0, 30);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function analysePython(content, result) {
|
|
160
|
+
const defPattern = /^def\s+([A-Za-z_][A-Za-z0-9_]*)/gm;
|
|
161
|
+
const classPattern = /^class\s+([A-Za-z_][A-Za-z0-9_]*)/gm;
|
|
162
|
+
const importPattern = /^(?:import|from)\s+([A-Za-z_][A-Za-z0-9_.]*)/gm;
|
|
163
|
+
let m;
|
|
164
|
+
while ((m = defPattern.exec(content)) !== null) result.functions.push(m[1]);
|
|
165
|
+
while ((m = classPattern.exec(content)) !== null) result.classes.push(m[1]);
|
|
166
|
+
while ((m = importPattern.exec(content)) !== null) result.imports.push(m[1]);
|
|
167
|
+
result.functions = [...new Set(result.functions)].slice(0, 40);
|
|
168
|
+
result.classes = [...new Set(result.classes)].slice(0, 20);
|
|
169
|
+
result.imports = [...new Set(result.imports)].slice(0, 30);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
captureSnapshot,
|
|
174
|
+
deleteSnapshot,
|
|
175
|
+
getSnapshot,
|
|
176
|
+
readCurrentContent,
|
|
177
|
+
buildCodeFileCatalogue
|
|
178
|
+
};
|