neuronlayer 0.1.6 → 0.1.8
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.
Potentially problematic release.
This version of neuronlayer might be problematic. Click here for more details.
- package/README.md +173 -120
- package/dist/index.js +724 -134
- package/package.json +2 -2
- package/src/cli/commands.ts +12 -6
- package/src/core/engine.ts +53 -3
- package/src/core/ghost-mode.ts +53 -0
- package/src/core/project-manager.ts +5 -1
- package/src/indexing/ast.ts +356 -51
- package/src/indexing/indexer.ts +25 -0
- package/src/server/mcp.ts +1 -1
- package/src/server/tools.ts +129 -0
- package/src/storage/tier2.ts +212 -4
- package/real-benchmark.mjs +0 -322
package/dist/index.js
CHANGED
|
@@ -20821,8 +20821,8 @@ var StdioServerTransport = class {
|
|
|
20821
20821
|
};
|
|
20822
20822
|
|
|
20823
20823
|
// src/core/engine.ts
|
|
20824
|
-
import { join as
|
|
20825
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync10 } from "fs";
|
|
20824
|
+
import { join as join13, basename as basename6 } from "path";
|
|
20825
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync10, renameSync } from "fs";
|
|
20826
20826
|
|
|
20827
20827
|
// src/storage/database.ts
|
|
20828
20828
|
import Database from "better-sqlite3";
|
|
@@ -21677,9 +21677,20 @@ var Tier2Storage = class {
|
|
|
21677
21677
|
SELECT DISTINCT i.file_id as fileId, f.path as filePath
|
|
21678
21678
|
FROM imports i
|
|
21679
21679
|
JOIN files f ON i.file_id = f.id
|
|
21680
|
-
WHERE i.imported_from
|
|
21680
|
+
WHERE i.imported_from = ?
|
|
21681
|
+
OR i.imported_from LIKE ?
|
|
21682
|
+
OR i.imported_from LIKE ?
|
|
21683
|
+
OR i.imported_from LIKE ?
|
|
21681
21684
|
`);
|
|
21682
|
-
return stmt.all(
|
|
21685
|
+
return stmt.all(
|
|
21686
|
+
modulePath,
|
|
21687
|
+
`%/${modulePath}`,
|
|
21688
|
+
// ends with /modulePath
|
|
21689
|
+
`./${modulePath}`,
|
|
21690
|
+
// relative ./modulePath
|
|
21691
|
+
`../${modulePath}`
|
|
21692
|
+
// parent ../modulePath
|
|
21693
|
+
);
|
|
21683
21694
|
}
|
|
21684
21695
|
// Phase 2: Export operations
|
|
21685
21696
|
clearExports(fileId) {
|
|
@@ -21735,7 +21746,10 @@ var Tier2Storage = class {
|
|
|
21735
21746
|
const deps = [];
|
|
21736
21747
|
for (const importer of importers) {
|
|
21737
21748
|
const imports = this.getImportsByFile(importer.fileId);
|
|
21738
|
-
const relevantImport = imports.find((i) =>
|
|
21749
|
+
const relevantImport = imports.find((i) => {
|
|
21750
|
+
const importedName = i.importedFrom.split(/[/\\]/).pop()?.replace(/\.[^.]+$/, "") || "";
|
|
21751
|
+
return importedName === fileName || i.importedFrom.endsWith(`/${fileName}`) || i.importedFrom.endsWith(`./${fileName}`);
|
|
21752
|
+
});
|
|
21739
21753
|
if (relevantImport) {
|
|
21740
21754
|
deps.push({
|
|
21741
21755
|
file: importer.filePath,
|
|
@@ -21745,6 +21759,161 @@ var Tier2Storage = class {
|
|
|
21745
21759
|
}
|
|
21746
21760
|
return deps;
|
|
21747
21761
|
}
|
|
21762
|
+
/**
|
|
21763
|
+
* Get ALL files affected by a change, walking the dependency graph.
|
|
21764
|
+
* depth=1 is direct importers only. depth=3 catches ripple effects.
|
|
21765
|
+
*/
|
|
21766
|
+
getTransitiveDependents(filePath, maxDepth = 3) {
|
|
21767
|
+
const visited = /* @__PURE__ */ new Map();
|
|
21768
|
+
const queue = [{ path: filePath, depth: 0 }];
|
|
21769
|
+
while (queue.length > 0) {
|
|
21770
|
+
const current = queue.shift();
|
|
21771
|
+
if (current.depth >= maxDepth) continue;
|
|
21772
|
+
if (visited.has(current.path) && visited.get(current.path).depth <= current.depth) continue;
|
|
21773
|
+
const dependents = this.getFileDependents(current.path);
|
|
21774
|
+
for (const dep of dependents) {
|
|
21775
|
+
const existingDepth = visited.get(dep.file)?.depth ?? Infinity;
|
|
21776
|
+
const newDepth = current.depth + 1;
|
|
21777
|
+
if (newDepth < existingDepth) {
|
|
21778
|
+
visited.set(dep.file, { depth: newDepth, imports: dep.imports });
|
|
21779
|
+
queue.push({ path: dep.file, depth: newDepth });
|
|
21780
|
+
}
|
|
21781
|
+
}
|
|
21782
|
+
}
|
|
21783
|
+
visited.delete(filePath);
|
|
21784
|
+
return Array.from(visited.entries()).map(([file2, info]) => ({ file: file2, ...info })).sort((a, b) => a.depth - b.depth);
|
|
21785
|
+
}
|
|
21786
|
+
/**
|
|
21787
|
+
* Get the full import graph as an adjacency list.
|
|
21788
|
+
* Returns { file → [files it imports] } for the whole project.
|
|
21789
|
+
*/
|
|
21790
|
+
getFullDependencyGraph() {
|
|
21791
|
+
const stmt = this.db.prepare(`
|
|
21792
|
+
SELECT f.path as filePath, i.imported_from as importedFrom
|
|
21793
|
+
FROM imports i
|
|
21794
|
+
JOIN files f ON i.file_id = f.id
|
|
21795
|
+
`);
|
|
21796
|
+
const rows = stmt.all();
|
|
21797
|
+
const graph = /* @__PURE__ */ new Map();
|
|
21798
|
+
for (const row of rows) {
|
|
21799
|
+
if (!graph.has(row.filePath)) graph.set(row.filePath, []);
|
|
21800
|
+
graph.get(row.filePath).push(row.importedFrom);
|
|
21801
|
+
}
|
|
21802
|
+
return graph;
|
|
21803
|
+
}
|
|
21804
|
+
/**
|
|
21805
|
+
* Find circular dependencies in the project.
|
|
21806
|
+
* Returns arrays of file paths that form cycles.
|
|
21807
|
+
*/
|
|
21808
|
+
findCircularDependencies() {
|
|
21809
|
+
const graph = this.getFullDependencyGraph();
|
|
21810
|
+
const cycles = [];
|
|
21811
|
+
const visited = /* @__PURE__ */ new Set();
|
|
21812
|
+
const stack = /* @__PURE__ */ new Set();
|
|
21813
|
+
const dfs = (node, path) => {
|
|
21814
|
+
if (stack.has(node)) {
|
|
21815
|
+
const cycleStart = path.indexOf(node);
|
|
21816
|
+
if (cycleStart >= 0) {
|
|
21817
|
+
cycles.push(path.slice(cycleStart).concat(node));
|
|
21818
|
+
}
|
|
21819
|
+
return;
|
|
21820
|
+
}
|
|
21821
|
+
if (visited.has(node)) return;
|
|
21822
|
+
visited.add(node);
|
|
21823
|
+
stack.add(node);
|
|
21824
|
+
path.push(node);
|
|
21825
|
+
const deps = graph.get(node) || [];
|
|
21826
|
+
for (const dep of deps) {
|
|
21827
|
+
const resolved = this.resolveImportPath(node, dep);
|
|
21828
|
+
if (resolved) dfs(resolved, [...path]);
|
|
21829
|
+
}
|
|
21830
|
+
stack.delete(node);
|
|
21831
|
+
};
|
|
21832
|
+
for (const file2 of graph.keys()) {
|
|
21833
|
+
dfs(file2, []);
|
|
21834
|
+
}
|
|
21835
|
+
const uniqueCycles = [];
|
|
21836
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21837
|
+
for (const cycle of cycles) {
|
|
21838
|
+
const normalized = [...cycle].sort().join("|");
|
|
21839
|
+
if (!seen.has(normalized)) {
|
|
21840
|
+
seen.add(normalized);
|
|
21841
|
+
uniqueCycles.push(cycle);
|
|
21842
|
+
}
|
|
21843
|
+
}
|
|
21844
|
+
return uniqueCycles;
|
|
21845
|
+
}
|
|
21846
|
+
/**
|
|
21847
|
+
* Resolve a relative import path to an actual file path in the database.
|
|
21848
|
+
*/
|
|
21849
|
+
resolveImportPath(fromFile, importPath) {
|
|
21850
|
+
if (!importPath.startsWith(".") && !importPath.startsWith("/")) return null;
|
|
21851
|
+
const dir = fromFile.split(/[/\\]/).slice(0, -1).join("/");
|
|
21852
|
+
let resolved = importPath;
|
|
21853
|
+
if (importPath.startsWith("./")) {
|
|
21854
|
+
resolved = dir + "/" + importPath.slice(2);
|
|
21855
|
+
} else if (importPath.startsWith("../")) {
|
|
21856
|
+
const parts = dir.split("/");
|
|
21857
|
+
let impParts = importPath.split("/");
|
|
21858
|
+
while (impParts[0] === "..") {
|
|
21859
|
+
parts.pop();
|
|
21860
|
+
impParts.shift();
|
|
21861
|
+
}
|
|
21862
|
+
resolved = parts.join("/") + "/" + impParts.join("/");
|
|
21863
|
+
}
|
|
21864
|
+
const baseName = resolved.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
|
|
21865
|
+
const stmt = this.db.prepare(`
|
|
21866
|
+
SELECT path FROM files
|
|
21867
|
+
WHERE path = ? OR path = ? OR path = ? OR path = ?
|
|
21868
|
+
OR path = ? OR path = ?
|
|
21869
|
+
LIMIT 1
|
|
21870
|
+
`);
|
|
21871
|
+
const result = stmt.get(
|
|
21872
|
+
`${baseName}.ts`,
|
|
21873
|
+
`${baseName}.tsx`,
|
|
21874
|
+
`${baseName}.js`,
|
|
21875
|
+
`${baseName}.jsx`,
|
|
21876
|
+
`${baseName}/index.ts`,
|
|
21877
|
+
`${baseName}/index.js`
|
|
21878
|
+
);
|
|
21879
|
+
return result?.path || null;
|
|
21880
|
+
}
|
|
21881
|
+
/**
|
|
21882
|
+
* Resolve an import path to a file record in the database.
|
|
21883
|
+
* Used by indexer to build the dependencies table.
|
|
21884
|
+
*/
|
|
21885
|
+
resolveImportToFile(sourceFilePath, importPath) {
|
|
21886
|
+
if (!importPath.startsWith(".") && !importPath.startsWith("/")) return null;
|
|
21887
|
+
const sourceDir = sourceFilePath.split(/[/\\]/).slice(0, -1).join("/");
|
|
21888
|
+
let resolved = importPath;
|
|
21889
|
+
if (importPath.startsWith("./")) {
|
|
21890
|
+
resolved = sourceDir + "/" + importPath.slice(2);
|
|
21891
|
+
} else if (importPath.startsWith("../")) {
|
|
21892
|
+
const parts = sourceDir.split("/");
|
|
21893
|
+
let impParts = importPath.split("/");
|
|
21894
|
+
while (impParts[0] === "..") {
|
|
21895
|
+
parts.pop();
|
|
21896
|
+
impParts.shift();
|
|
21897
|
+
}
|
|
21898
|
+
resolved = parts.join("/") + "/" + impParts.join("/");
|
|
21899
|
+
}
|
|
21900
|
+
resolved = resolved.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
|
|
21901
|
+
const stmt = this.db.prepare(`
|
|
21902
|
+
SELECT id, path FROM files
|
|
21903
|
+
WHERE path = ? OR path = ? OR path = ? OR path = ?
|
|
21904
|
+
OR path = ? OR path = ?
|
|
21905
|
+
LIMIT 1
|
|
21906
|
+
`);
|
|
21907
|
+
const result = stmt.get(
|
|
21908
|
+
`${resolved}.ts`,
|
|
21909
|
+
`${resolved}.tsx`,
|
|
21910
|
+
`${resolved}.js`,
|
|
21911
|
+
`${resolved}.jsx`,
|
|
21912
|
+
`${resolved}/index.ts`,
|
|
21913
|
+
`${resolved}/index.js`
|
|
21914
|
+
);
|
|
21915
|
+
return result || null;
|
|
21916
|
+
}
|
|
21748
21917
|
};
|
|
21749
21918
|
|
|
21750
21919
|
// src/storage/tier3.ts
|
|
@@ -21903,8 +22072,6 @@ var EmbeddingGenerator = class {
|
|
|
21903
22072
|
};
|
|
21904
22073
|
|
|
21905
22074
|
// src/indexing/ast.ts
|
|
21906
|
-
import Parser from "web-tree-sitter";
|
|
21907
|
-
import { join as join2 } from "path";
|
|
21908
22075
|
var LANGUAGE_CONFIGS = {
|
|
21909
22076
|
typescript: {
|
|
21910
22077
|
wasmFile: "tree-sitter-typescript.wasm",
|
|
@@ -21967,6 +22134,30 @@ var LANGUAGE_CONFIGS = {
|
|
|
21967
22134
|
(import_from_statement) @import
|
|
21968
22135
|
`
|
|
21969
22136
|
}
|
|
22137
|
+
},
|
|
22138
|
+
go: {
|
|
22139
|
+
wasmFile: "tree-sitter-go.wasm",
|
|
22140
|
+
extensions: [".go"],
|
|
22141
|
+
queries: {
|
|
22142
|
+
functions: `(function_declaration name: (identifier) @name) @func`,
|
|
22143
|
+
classes: `(type_declaration (type_spec name: (type_identifier) @name type: (struct_type))) @class`
|
|
22144
|
+
}
|
|
22145
|
+
},
|
|
22146
|
+
rust: {
|
|
22147
|
+
wasmFile: "tree-sitter-rust.wasm",
|
|
22148
|
+
extensions: [".rs"],
|
|
22149
|
+
queries: {
|
|
22150
|
+
functions: `(function_item name: (identifier) @name) @func`,
|
|
22151
|
+
classes: `(struct_item name: (type_identifier) @name) @class`
|
|
22152
|
+
}
|
|
22153
|
+
},
|
|
22154
|
+
java: {
|
|
22155
|
+
wasmFile: "tree-sitter-java.wasm",
|
|
22156
|
+
extensions: [".java"],
|
|
22157
|
+
queries: {
|
|
22158
|
+
functions: `(method_declaration name: (identifier) @name) @func`,
|
|
22159
|
+
classes: `(class_declaration name: (identifier) @name) @class`
|
|
22160
|
+
}
|
|
21970
22161
|
}
|
|
21971
22162
|
};
|
|
21972
22163
|
var ASTParser = class {
|
|
@@ -21979,33 +22170,10 @@ var ASTParser = class {
|
|
|
21979
22170
|
}
|
|
21980
22171
|
async initialize() {
|
|
21981
22172
|
if (this.initialized) return;
|
|
21982
|
-
|
|
21983
|
-
await Parser.init();
|
|
21984
|
-
this.parser = new Parser();
|
|
21985
|
-
this.initialized = true;
|
|
21986
|
-
console.error("AST Parser initialized");
|
|
21987
|
-
} catch (error2) {
|
|
21988
|
-
console.error("Failed to initialize AST parser:", error2);
|
|
21989
|
-
throw error2;
|
|
21990
|
-
}
|
|
22173
|
+
this.initialized = true;
|
|
21991
22174
|
}
|
|
21992
|
-
async loadLanguage(
|
|
21993
|
-
|
|
21994
|
-
return this.languages.get(langName);
|
|
21995
|
-
}
|
|
21996
|
-
const config2 = LANGUAGE_CONFIGS[langName];
|
|
21997
|
-
if (!config2) {
|
|
21998
|
-
return null;
|
|
21999
|
-
}
|
|
22000
|
-
try {
|
|
22001
|
-
const wasmDir = join2(this.dataDir, "wasm");
|
|
22002
|
-
const wasmPath = join2(wasmDir, config2.wasmFile);
|
|
22003
|
-
console.error(`Language ${langName} WASM not available yet`);
|
|
22004
|
-
return null;
|
|
22005
|
-
} catch (error2) {
|
|
22006
|
-
console.error(`Failed to load language ${langName}:`, error2);
|
|
22007
|
-
return null;
|
|
22008
|
-
}
|
|
22175
|
+
async loadLanguage(_langName) {
|
|
22176
|
+
return null;
|
|
22009
22177
|
}
|
|
22010
22178
|
getLanguageForFile(filePath) {
|
|
22011
22179
|
const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
|
|
@@ -22022,7 +22190,9 @@ var ASTParser = class {
|
|
|
22022
22190
|
}
|
|
22023
22191
|
return this.parseWithRegex(filePath, content);
|
|
22024
22192
|
}
|
|
22025
|
-
// Regex-based parsing
|
|
22193
|
+
// Regex-based parsing for symbol extraction
|
|
22194
|
+
// Handles: functions, classes, interfaces, types, imports, exports
|
|
22195
|
+
// Supports: TypeScript, JavaScript, Python, Go, Rust, Java
|
|
22026
22196
|
parseWithRegex(filePath, content) {
|
|
22027
22197
|
const symbols = [];
|
|
22028
22198
|
const imports = [];
|
|
@@ -22033,39 +22203,62 @@ var ASTParser = class {
|
|
|
22033
22203
|
this.parseTypeScriptJS(filePath, content, lines, symbols, imports, exports);
|
|
22034
22204
|
} else if (lang === "python") {
|
|
22035
22205
|
this.parsePython(filePath, content, lines, symbols, imports, exports);
|
|
22206
|
+
} else if (lang === "go") {
|
|
22207
|
+
this.parseGo(filePath, content, lines, symbols, imports, exports);
|
|
22208
|
+
} else if (lang === "rust") {
|
|
22209
|
+
this.parseRust(filePath, content, lines, symbols, imports, exports);
|
|
22210
|
+
} else if (lang === "java") {
|
|
22211
|
+
this.parseJava(filePath, content, lines, symbols, imports, exports);
|
|
22036
22212
|
}
|
|
22037
22213
|
return { symbols, imports, exports };
|
|
22038
22214
|
}
|
|
22039
22215
|
parseTypeScriptJS(filePath, content, lines, symbols, imports, exports) {
|
|
22040
22216
|
const patterns = {
|
|
22041
|
-
// Functions: function name(),
|
|
22042
|
-
function: /^(?:export\s+)?(?:async\s+)?function\s+(\w+)/,
|
|
22043
|
-
|
|
22217
|
+
// Functions: function name(), export default function name(), const name = () =>
|
|
22218
|
+
function: /^(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+(\w+)/,
|
|
22219
|
+
// Arrow functions: handles type annotations and destructured params
|
|
22220
|
+
arrowFunc: /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?(?:\([^)]*\)|[a-zA-Z_]\w*)\s*(?::\s*[^=]+)?\s*=>/,
|
|
22044
22221
|
// Classes
|
|
22045
|
-
class: /^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/,
|
|
22222
|
+
class: /^(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+(\w+)/,
|
|
22046
22223
|
// Interfaces (TS only)
|
|
22047
22224
|
interface: /^(?:export\s+)?interface\s+(\w+)/,
|
|
22048
22225
|
// Types (TS only)
|
|
22049
|
-
type: /^(?:export\s+)?type\s+(\w+)\s*=/,
|
|
22050
|
-
// Imports
|
|
22051
|
-
import: /^import\s+(?:(\w+)(?:\s*,\s*)?)?(?:\{([^}]+)\})?\s*from\s*['"]([^'"]+)['"]/,
|
|
22052
|
-
importAll: /^import\s
|
|
22226
|
+
type: /^(?:export\s+)?type\s+(\w+)\s*(?:<[^>]*>)?\s*=/,
|
|
22227
|
+
// Imports - supports 'import type'
|
|
22228
|
+
import: /^import\s+(?:type\s+)?(?:(\w+)(?:\s*,\s*)?)?(?:\{([^}]+)\})?\s*from\s*['"]([^'"]+)['"]/,
|
|
22229
|
+
importAll: /^import\s+(?:type\s+)?\*\s+as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/,
|
|
22053
22230
|
importSideEffect: /^import\s*['"]([^'"]+)['"]/,
|
|
22054
22231
|
// Exports
|
|
22055
|
-
exportNamed: /^export\s
|
|
22232
|
+
exportNamed: /^export\s+(?:type\s+)?\{([^}]+)\}/,
|
|
22056
22233
|
exportDefault: /^export\s+default\s+(?:class|function|const|let|var)?\s*(\w+)?/,
|
|
22057
22234
|
exportDirect: /^export\s+(?:const|let|var|function|class|interface|type|enum|async\s+function)\s+(\w+)/,
|
|
22058
22235
|
// Enums (TS)
|
|
22059
22236
|
enum: /^(?:export\s+)?(?:const\s+)?enum\s+(\w+)/,
|
|
22060
|
-
// Methods inside classes
|
|
22061
|
-
method: /^\s+(?:async\s+)?(?:static\s+)?(?:private\s+|public\s+|protected\s+)?(\w+)\s
|
|
22237
|
+
// Methods inside classes - handles generics, readonly, and all modifiers
|
|
22238
|
+
method: /^\s+(?:async\s+)?(?:static\s+)?(?:readonly\s+)?(?:private\s+|public\s+|protected\s+)?(?:get\s+|set\s+)?(\w+)\s*(?:<[^>]+>)?\s*\(/
|
|
22062
22239
|
};
|
|
22063
22240
|
let currentClass = null;
|
|
22064
22241
|
let braceDepth = 0;
|
|
22242
|
+
let inBlockComment = false;
|
|
22065
22243
|
for (let i = 0; i < lines.length; i++) {
|
|
22066
22244
|
const line = lines[i] || "";
|
|
22067
22245
|
const trimmed = line.trim();
|
|
22068
22246
|
const lineNum = i + 1;
|
|
22247
|
+
if (inBlockComment) {
|
|
22248
|
+
if (trimmed.includes("*/")) {
|
|
22249
|
+
inBlockComment = false;
|
|
22250
|
+
}
|
|
22251
|
+
continue;
|
|
22252
|
+
}
|
|
22253
|
+
if (trimmed.startsWith("//")) {
|
|
22254
|
+
continue;
|
|
22255
|
+
}
|
|
22256
|
+
if (trimmed.startsWith("/*")) {
|
|
22257
|
+
if (!trimmed.includes("*/")) {
|
|
22258
|
+
inBlockComment = true;
|
|
22259
|
+
}
|
|
22260
|
+
continue;
|
|
22261
|
+
}
|
|
22069
22262
|
braceDepth += (line.match(/\{/g) || []).length;
|
|
22070
22263
|
braceDepth -= (line.match(/\}/g) || []).length;
|
|
22071
22264
|
if (currentClass && braceDepth === 0) {
|
|
@@ -22075,9 +22268,6 @@ var ASTParser = class {
|
|
|
22075
22268
|
}
|
|
22076
22269
|
currentClass = null;
|
|
22077
22270
|
}
|
|
22078
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
|
|
22079
|
-
continue;
|
|
22080
|
-
}
|
|
22081
22271
|
let match = trimmed.match(patterns.function);
|
|
22082
22272
|
if (match && match[1]) {
|
|
22083
22273
|
symbols.push({
|
|
@@ -22314,6 +22504,224 @@ var ASTParser = class {
|
|
|
22314
22504
|
}
|
|
22315
22505
|
}
|
|
22316
22506
|
}
|
|
22507
|
+
parseGo(filePath, content, lines, symbols, imports, exports) {
|
|
22508
|
+
let inImportBlock = false;
|
|
22509
|
+
for (let i = 0; i < lines.length; i++) {
|
|
22510
|
+
const line = lines[i] || "";
|
|
22511
|
+
const trimmed = line.trim();
|
|
22512
|
+
const lineNum = i + 1;
|
|
22513
|
+
if (trimmed.startsWith("//")) continue;
|
|
22514
|
+
if (trimmed === "import (") {
|
|
22515
|
+
inImportBlock = true;
|
|
22516
|
+
continue;
|
|
22517
|
+
}
|
|
22518
|
+
if (inImportBlock && trimmed === ")") {
|
|
22519
|
+
inImportBlock = false;
|
|
22520
|
+
continue;
|
|
22521
|
+
}
|
|
22522
|
+
const importMatch = inImportBlock ? trimmed.match(/^(?:(\w+)\s+)?"([^"]+)"/) : trimmed.match(/^import\s+(?:(\w+)\s+)?"([^"]+)"/);
|
|
22523
|
+
if (importMatch) {
|
|
22524
|
+
const alias = importMatch[1];
|
|
22525
|
+
const path = importMatch[2] || "";
|
|
22526
|
+
const pkg = alias || path.split("/").pop() || "";
|
|
22527
|
+
imports.push({
|
|
22528
|
+
fileId: 0,
|
|
22529
|
+
filePath,
|
|
22530
|
+
importedFrom: path,
|
|
22531
|
+
importedSymbols: [pkg],
|
|
22532
|
+
isDefault: false,
|
|
22533
|
+
isNamespace: false,
|
|
22534
|
+
lineNumber: lineNum
|
|
22535
|
+
});
|
|
22536
|
+
continue;
|
|
22537
|
+
}
|
|
22538
|
+
const funcMatch = trimmed.match(/^func\s+(?:\((\w+)\s+\*?(\w+)\)\s+)?(\w+)\s*\(/);
|
|
22539
|
+
if (funcMatch) {
|
|
22540
|
+
const receiver = funcMatch[2];
|
|
22541
|
+
const name = receiver ? `${receiver}.${funcMatch[3]}` : funcMatch[3] || "";
|
|
22542
|
+
symbols.push({
|
|
22543
|
+
fileId: 0,
|
|
22544
|
+
filePath,
|
|
22545
|
+
kind: receiver ? "method" : "function",
|
|
22546
|
+
name,
|
|
22547
|
+
lineStart: lineNum,
|
|
22548
|
+
lineEnd: this.findBlockEnd(lines, i),
|
|
22549
|
+
exported: /^[A-Z]/.test(funcMatch[3] || ""),
|
|
22550
|
+
signature: trimmed.split("{")[0]?.trim()
|
|
22551
|
+
});
|
|
22552
|
+
continue;
|
|
22553
|
+
}
|
|
22554
|
+
const structMatch = trimmed.match(/^type\s+(\w+)\s+struct\s*\{?/);
|
|
22555
|
+
if (structMatch) {
|
|
22556
|
+
symbols.push({
|
|
22557
|
+
fileId: 0,
|
|
22558
|
+
filePath,
|
|
22559
|
+
kind: "class",
|
|
22560
|
+
name: structMatch[1] || "",
|
|
22561
|
+
lineStart: lineNum,
|
|
22562
|
+
lineEnd: this.findBlockEnd(lines, i),
|
|
22563
|
+
exported: /^[A-Z]/.test(structMatch[1] || "")
|
|
22564
|
+
});
|
|
22565
|
+
continue;
|
|
22566
|
+
}
|
|
22567
|
+
const ifaceMatch = trimmed.match(/^type\s+(\w+)\s+interface\s*\{?/);
|
|
22568
|
+
if (ifaceMatch) {
|
|
22569
|
+
symbols.push({
|
|
22570
|
+
fileId: 0,
|
|
22571
|
+
filePath,
|
|
22572
|
+
kind: "interface",
|
|
22573
|
+
name: ifaceMatch[1] || "",
|
|
22574
|
+
lineStart: lineNum,
|
|
22575
|
+
lineEnd: this.findBlockEnd(lines, i),
|
|
22576
|
+
exported: /^[A-Z]/.test(ifaceMatch[1] || "")
|
|
22577
|
+
});
|
|
22578
|
+
}
|
|
22579
|
+
}
|
|
22580
|
+
}
|
|
22581
|
+
parseRust(filePath, content, lines, symbols, imports, exports) {
|
|
22582
|
+
for (let i = 0; i < lines.length; i++) {
|
|
22583
|
+
const line = lines[i] || "";
|
|
22584
|
+
const trimmed = line.trim();
|
|
22585
|
+
const lineNum = i + 1;
|
|
22586
|
+
if (trimmed.startsWith("//")) continue;
|
|
22587
|
+
const fnMatch = trimmed.match(/^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/);
|
|
22588
|
+
if (fnMatch) {
|
|
22589
|
+
symbols.push({
|
|
22590
|
+
fileId: 0,
|
|
22591
|
+
filePath,
|
|
22592
|
+
kind: "function",
|
|
22593
|
+
name: fnMatch[1] || "",
|
|
22594
|
+
lineStart: lineNum,
|
|
22595
|
+
lineEnd: this.findBlockEnd(lines, i),
|
|
22596
|
+
exported: trimmed.startsWith("pub"),
|
|
22597
|
+
signature: trimmed.split("{")[0]?.trim()
|
|
22598
|
+
});
|
|
22599
|
+
continue;
|
|
22600
|
+
}
|
|
22601
|
+
const structMatch = trimmed.match(/^(?:pub\s+)?struct\s+(\w+)/);
|
|
22602
|
+
if (structMatch) {
|
|
22603
|
+
symbols.push({
|
|
22604
|
+
fileId: 0,
|
|
22605
|
+
filePath,
|
|
22606
|
+
kind: "class",
|
|
22607
|
+
name: structMatch[1] || "",
|
|
22608
|
+
lineStart: lineNum,
|
|
22609
|
+
lineEnd: this.findBlockEnd(lines, i),
|
|
22610
|
+
exported: trimmed.startsWith("pub")
|
|
22611
|
+
});
|
|
22612
|
+
continue;
|
|
22613
|
+
}
|
|
22614
|
+
const enumMatch = trimmed.match(/^(?:pub\s+)?enum\s+(\w+)/);
|
|
22615
|
+
if (enumMatch) {
|
|
22616
|
+
symbols.push({
|
|
22617
|
+
fileId: 0,
|
|
22618
|
+
filePath,
|
|
22619
|
+
kind: "enum",
|
|
22620
|
+
name: enumMatch[1] || "",
|
|
22621
|
+
lineStart: lineNum,
|
|
22622
|
+
lineEnd: this.findBlockEnd(lines, i),
|
|
22623
|
+
exported: trimmed.startsWith("pub")
|
|
22624
|
+
});
|
|
22625
|
+
continue;
|
|
22626
|
+
}
|
|
22627
|
+
const traitMatch = trimmed.match(/^(?:pub\s+)?trait\s+(\w+)/);
|
|
22628
|
+
if (traitMatch) {
|
|
22629
|
+
symbols.push({
|
|
22630
|
+
fileId: 0,
|
|
22631
|
+
filePath,
|
|
22632
|
+
kind: "interface",
|
|
22633
|
+
name: traitMatch[1] || "",
|
|
22634
|
+
lineStart: lineNum,
|
|
22635
|
+
lineEnd: this.findBlockEnd(lines, i),
|
|
22636
|
+
exported: trimmed.startsWith("pub")
|
|
22637
|
+
});
|
|
22638
|
+
continue;
|
|
22639
|
+
}
|
|
22640
|
+
const implMatch = trimmed.match(/^impl\s+(?:<[^>]+>\s+)?(?:(\w+)\s+for\s+)?(\w+)/);
|
|
22641
|
+
if (implMatch) {
|
|
22642
|
+
const traitName = implMatch[1];
|
|
22643
|
+
const typeName = implMatch[2] || "";
|
|
22644
|
+
const name = traitName ? `${traitName} for ${typeName}` : typeName;
|
|
22645
|
+
symbols.push({
|
|
22646
|
+
fileId: 0,
|
|
22647
|
+
filePath,
|
|
22648
|
+
kind: "class",
|
|
22649
|
+
name: `impl ${name}`,
|
|
22650
|
+
lineStart: lineNum,
|
|
22651
|
+
lineEnd: this.findBlockEnd(lines, i),
|
|
22652
|
+
exported: false
|
|
22653
|
+
});
|
|
22654
|
+
continue;
|
|
22655
|
+
}
|
|
22656
|
+
const useMatch = trimmed.match(/^(?:pub\s+)?use\s+(.+);/);
|
|
22657
|
+
if (useMatch) {
|
|
22658
|
+
const path = (useMatch[1] || "").replace(/::/g, "/");
|
|
22659
|
+
imports.push({
|
|
22660
|
+
fileId: 0,
|
|
22661
|
+
filePath,
|
|
22662
|
+
importedFrom: path,
|
|
22663
|
+
importedSymbols: [path.split("/").pop()?.replace(/[{}]/g, "") || ""],
|
|
22664
|
+
isDefault: false,
|
|
22665
|
+
isNamespace: path.includes("*"),
|
|
22666
|
+
lineNumber: lineNum
|
|
22667
|
+
});
|
|
22668
|
+
}
|
|
22669
|
+
}
|
|
22670
|
+
}
|
|
22671
|
+
parseJava(filePath, content, lines, symbols, imports, exports) {
|
|
22672
|
+
let currentClass = null;
|
|
22673
|
+
for (let i = 0; i < lines.length; i++) {
|
|
22674
|
+
const line = lines[i] || "";
|
|
22675
|
+
const trimmed = line.trim();
|
|
22676
|
+
const lineNum = i + 1;
|
|
22677
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) continue;
|
|
22678
|
+
const importMatch = trimmed.match(/^import\s+(?:static\s+)?([^;]+);/);
|
|
22679
|
+
if (importMatch) {
|
|
22680
|
+
const path = importMatch[1] || "";
|
|
22681
|
+
imports.push({
|
|
22682
|
+
fileId: 0,
|
|
22683
|
+
filePath,
|
|
22684
|
+
importedFrom: path,
|
|
22685
|
+
importedSymbols: [path.split(".").pop() || ""],
|
|
22686
|
+
isDefault: false,
|
|
22687
|
+
isNamespace: path.endsWith("*"),
|
|
22688
|
+
lineNumber: lineNum
|
|
22689
|
+
});
|
|
22690
|
+
continue;
|
|
22691
|
+
}
|
|
22692
|
+
const classMatch = trimmed.match(/^(?:public\s+|private\s+|protected\s+)?(?:abstract\s+)?(?:final\s+)?(class|interface|enum)\s+(\w+)/);
|
|
22693
|
+
if (classMatch) {
|
|
22694
|
+
currentClass = classMatch[2] || "";
|
|
22695
|
+
symbols.push({
|
|
22696
|
+
fileId: 0,
|
|
22697
|
+
filePath,
|
|
22698
|
+
kind: classMatch[1] === "interface" ? "interface" : classMatch[1] === "enum" ? "enum" : "class",
|
|
22699
|
+
name: currentClass,
|
|
22700
|
+
lineStart: lineNum,
|
|
22701
|
+
lineEnd: this.findBlockEnd(lines, i),
|
|
22702
|
+
exported: trimmed.includes("public")
|
|
22703
|
+
});
|
|
22704
|
+
continue;
|
|
22705
|
+
}
|
|
22706
|
+
const methodMatch = trimmed.match(/^(?:public\s+|private\s+|protected\s+)?(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:abstract\s+)?(?:<[^>]+>\s+)?(\w+(?:<[^>]+>)?)\s+(\w+)\s*\(/);
|
|
22707
|
+
if (methodMatch && currentClass && !["if", "for", "while", "switch", "catch", "class", "interface", "enum"].includes(methodMatch[2] || "")) {
|
|
22708
|
+
const returnType = methodMatch[1];
|
|
22709
|
+
const methodName = methodMatch[2] || "";
|
|
22710
|
+
if (methodName !== currentClass) {
|
|
22711
|
+
symbols.push({
|
|
22712
|
+
fileId: 0,
|
|
22713
|
+
filePath,
|
|
22714
|
+
kind: "method",
|
|
22715
|
+
name: `${currentClass}.${methodName}`,
|
|
22716
|
+
lineStart: lineNum,
|
|
22717
|
+
lineEnd: this.findBlockEnd(lines, i),
|
|
22718
|
+
exported: trimmed.includes("public"),
|
|
22719
|
+
signature: `${returnType} ${methodName}(...)`
|
|
22720
|
+
});
|
|
22721
|
+
}
|
|
22722
|
+
}
|
|
22723
|
+
}
|
|
22724
|
+
}
|
|
22317
22725
|
findBlockEnd(lines, startIndex) {
|
|
22318
22726
|
let braceCount = 0;
|
|
22319
22727
|
let started = false;
|
|
@@ -22634,11 +23042,31 @@ var Indexer = class extends EventEmitter2 {
|
|
|
22634
23042
|
const exportsWithFileId = parsed.exports.map((e) => ({ ...e, fileId }));
|
|
22635
23043
|
this.tier2.insertExports(exportsWithFileId);
|
|
22636
23044
|
}
|
|
23045
|
+
if (parsed.imports.length > 0) {
|
|
23046
|
+
this.tier2.clearDependencies(fileId);
|
|
23047
|
+
for (const imp of parsed.imports) {
|
|
23048
|
+
const targetFile = this.tier2.resolveImportToFile(relativePath, imp.importedFrom);
|
|
23049
|
+
if (targetFile) {
|
|
23050
|
+
this.tier2.addDependency(fileId, targetFile.id, "imports");
|
|
23051
|
+
}
|
|
23052
|
+
}
|
|
23053
|
+
}
|
|
22637
23054
|
}
|
|
22638
23055
|
} catch (astError) {
|
|
22639
23056
|
console.error(`AST parsing failed for ${relativePath}:`, astError);
|
|
22640
23057
|
}
|
|
22641
23058
|
this.emit("fileIndexed", relativePath);
|
|
23059
|
+
if (!this.isIndexing) {
|
|
23060
|
+
const dependents = this.tier2.getFileDependents(relativePath);
|
|
23061
|
+
if (dependents.length > 0) {
|
|
23062
|
+
this.emit("fileImpact", {
|
|
23063
|
+
file: relativePath,
|
|
23064
|
+
affectedFiles: dependents.map((d) => d.file),
|
|
23065
|
+
affectedCount: dependents.length,
|
|
23066
|
+
imports: dependents.map((d) => ({ file: d.file, symbols: d.imports }))
|
|
23067
|
+
});
|
|
23068
|
+
}
|
|
23069
|
+
}
|
|
22642
23070
|
return true;
|
|
22643
23071
|
} catch (error2) {
|
|
22644
23072
|
console.error(`Error indexing ${absolutePath}:`, error2);
|
|
@@ -22735,7 +23163,7 @@ var Indexer = class extends EventEmitter2 {
|
|
|
22735
23163
|
};
|
|
22736
23164
|
|
|
22737
23165
|
// src/core/context.ts
|
|
22738
|
-
import { dirname as
|
|
23166
|
+
import { dirname as dirname3, basename } from "path";
|
|
22739
23167
|
|
|
22740
23168
|
// src/utils/tokens.ts
|
|
22741
23169
|
var AVG_CHARS_PER_TOKEN = 4;
|
|
@@ -22931,7 +23359,7 @@ ${result.preview}
|
|
|
22931
23359
|
const filesViewed = this.tier1.getFilesViewed();
|
|
22932
23360
|
return results.map((r) => {
|
|
22933
23361
|
let score = r.similarity;
|
|
22934
|
-
if (currentFile &&
|
|
23362
|
+
if (currentFile && dirname3(r.file) === dirname3(currentFile)) {
|
|
22935
23363
|
score *= 1.5;
|
|
22936
23364
|
}
|
|
22937
23365
|
const hoursSinceModified = (Date.now() - r.lastModified) / 36e5;
|
|
@@ -23033,7 +23461,7 @@ ${tags.join(" ")}`;
|
|
|
23033
23461
|
// src/core/decision-extractor.ts
|
|
23034
23462
|
import { execSync } from "child_process";
|
|
23035
23463
|
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
23036
|
-
import { join as
|
|
23464
|
+
import { join as join3 } from "path";
|
|
23037
23465
|
import { glob as glob2 } from "glob";
|
|
23038
23466
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
23039
23467
|
var COMMIT_PATTERNS = [
|
|
@@ -23073,7 +23501,7 @@ var DecisionExtractor = class {
|
|
|
23073
23501
|
isGitRepo;
|
|
23074
23502
|
constructor(projectPath) {
|
|
23075
23503
|
this.projectPath = projectPath;
|
|
23076
|
-
this.isGitRepo = existsSync3(
|
|
23504
|
+
this.isGitRepo = existsSync3(join3(projectPath, ".git"));
|
|
23077
23505
|
}
|
|
23078
23506
|
async extractAll() {
|
|
23079
23507
|
const decisions = [];
|
|
@@ -23898,7 +24326,7 @@ var FileSummarizer = class {
|
|
|
23898
24326
|
|
|
23899
24327
|
// src/core/project-manager.ts
|
|
23900
24328
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync2, readdirSync } from "fs";
|
|
23901
|
-
import { join as
|
|
24329
|
+
import { join as join4, basename as basename2, resolve } from "path";
|
|
23902
24330
|
import { createHash as createHash3 } from "crypto";
|
|
23903
24331
|
import { homedir } from "os";
|
|
23904
24332
|
import Database2 from "better-sqlite3";
|
|
@@ -23907,8 +24335,8 @@ var ProjectManager = class {
|
|
|
23907
24335
|
registry;
|
|
23908
24336
|
baseDataDir;
|
|
23909
24337
|
constructor() {
|
|
23910
|
-
this.baseDataDir =
|
|
23911
|
-
this.registryPath =
|
|
24338
|
+
this.baseDataDir = join4(homedir(), ".memorylayer");
|
|
24339
|
+
this.registryPath = join4(this.baseDataDir, "registry.json");
|
|
23912
24340
|
if (!existsSync4(this.baseDataDir)) {
|
|
23913
24341
|
mkdirSync3(this.baseDataDir, { recursive: true });
|
|
23914
24342
|
}
|
|
@@ -23943,7 +24371,7 @@ var ProjectManager = class {
|
|
|
23943
24371
|
getProjectDataDir(projectPath) {
|
|
23944
24372
|
const projectId = this.generateProjectId(projectPath);
|
|
23945
24373
|
const projectName = basename2(projectPath);
|
|
23946
|
-
return
|
|
24374
|
+
return join4(this.baseDataDir, "projects", `${projectName}-${projectId}`);
|
|
23947
24375
|
}
|
|
23948
24376
|
// Register a new project or update existing
|
|
23949
24377
|
registerProject(projectPath) {
|
|
@@ -24057,17 +24485,17 @@ var ProjectManager = class {
|
|
|
24057
24485
|
const discovered = [];
|
|
24058
24486
|
const homeDir = homedir();
|
|
24059
24487
|
const searchDirs = [
|
|
24060
|
-
|
|
24061
|
-
|
|
24062
|
-
|
|
24063
|
-
|
|
24064
|
-
|
|
24065
|
-
|
|
24066
|
-
|
|
24067
|
-
|
|
24068
|
-
|
|
24069
|
-
|
|
24070
|
-
|
|
24488
|
+
join4(homeDir, "projects"),
|
|
24489
|
+
join4(homeDir, "Projects"),
|
|
24490
|
+
join4(homeDir, "code"),
|
|
24491
|
+
join4(homeDir, "Code"),
|
|
24492
|
+
join4(homeDir, "dev"),
|
|
24493
|
+
join4(homeDir, "Development"),
|
|
24494
|
+
join4(homeDir, "workspace"),
|
|
24495
|
+
join4(homeDir, "repos"),
|
|
24496
|
+
join4(homeDir, "github"),
|
|
24497
|
+
join4(homeDir, "Desktop"),
|
|
24498
|
+
join4(homeDir, "Documents")
|
|
24071
24499
|
];
|
|
24072
24500
|
for (const searchDir of searchDirs) {
|
|
24073
24501
|
if (!existsSync4(searchDir)) continue;
|
|
@@ -24075,7 +24503,7 @@ var ProjectManager = class {
|
|
|
24075
24503
|
const entries = readdirSync(searchDir, { withFileTypes: true });
|
|
24076
24504
|
for (const entry of entries) {
|
|
24077
24505
|
if (!entry.isDirectory()) continue;
|
|
24078
|
-
const projectPath =
|
|
24506
|
+
const projectPath = join4(searchDir, entry.name);
|
|
24079
24507
|
const projectIndicators = [
|
|
24080
24508
|
"package.json",
|
|
24081
24509
|
"Cargo.toml",
|
|
@@ -24087,7 +24515,7 @@ var ProjectManager = class {
|
|
|
24087
24515
|
".git"
|
|
24088
24516
|
];
|
|
24089
24517
|
const isProject = projectIndicators.some(
|
|
24090
|
-
(indicator) => existsSync4(
|
|
24518
|
+
(indicator) => existsSync4(join4(projectPath, indicator))
|
|
24091
24519
|
);
|
|
24092
24520
|
if (isProject) {
|
|
24093
24521
|
discovered.push(projectPath);
|
|
@@ -24102,7 +24530,10 @@ var ProjectManager = class {
|
|
|
24102
24530
|
getProjectDatabases() {
|
|
24103
24531
|
const result = [];
|
|
24104
24532
|
for (const project of this.listProjects()) {
|
|
24105
|
-
|
|
24533
|
+
let dbPath = join4(project.dataDir, "neuronlayer.db");
|
|
24534
|
+
if (!existsSync4(dbPath)) {
|
|
24535
|
+
dbPath = join4(project.dataDir, "memorylayer.db");
|
|
24536
|
+
}
|
|
24106
24537
|
if (existsSync4(dbPath)) {
|
|
24107
24538
|
try {
|
|
24108
24539
|
const db = new Database2(dbPath, { readonly: true });
|
|
@@ -24126,7 +24557,7 @@ var ProjectManager = class {
|
|
|
24126
24557
|
|
|
24127
24558
|
// src/core/adr-exporter.ts
|
|
24128
24559
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
24129
|
-
import { join as
|
|
24560
|
+
import { join as join5 } from "path";
|
|
24130
24561
|
var ADRExporter = class {
|
|
24131
24562
|
projectPath;
|
|
24132
24563
|
constructor(projectPath) {
|
|
@@ -24134,7 +24565,7 @@ var ADRExporter = class {
|
|
|
24134
24565
|
}
|
|
24135
24566
|
// Export a single decision to ADR file
|
|
24136
24567
|
exportDecision(decision, options = {}) {
|
|
24137
|
-
const outputDir = options.outputDir ||
|
|
24568
|
+
const outputDir = options.outputDir || join5(this.projectPath, "docs", "decisions");
|
|
24138
24569
|
const format = options.format || "madr";
|
|
24139
24570
|
if (!existsSync5(outputDir)) {
|
|
24140
24571
|
mkdirSync4(outputDir, { recursive: true });
|
|
@@ -24143,7 +24574,7 @@ var ADRExporter = class {
|
|
|
24143
24574
|
const nextNumber = this.getNextADRNumber(existingFiles);
|
|
24144
24575
|
const slug = this.slugify(decision.title);
|
|
24145
24576
|
const filename = `${String(nextNumber).padStart(4, "0")}-${slug}.md`;
|
|
24146
|
-
const filePath =
|
|
24577
|
+
const filePath = join5(outputDir, filename);
|
|
24147
24578
|
let content;
|
|
24148
24579
|
switch (format) {
|
|
24149
24580
|
case "madr":
|
|
@@ -24163,7 +24594,7 @@ var ADRExporter = class {
|
|
|
24163
24594
|
// Export all decisions
|
|
24164
24595
|
exportAllDecisions(decisions, options = {}) {
|
|
24165
24596
|
const exportedFiles = [];
|
|
24166
|
-
const outputDir = options.outputDir ||
|
|
24597
|
+
const outputDir = options.outputDir || join5(this.projectPath, "docs", "decisions");
|
|
24167
24598
|
const sorted = [...decisions].sort(
|
|
24168
24599
|
(a, b) => a.createdAt.getTime() - b.createdAt.getTime()
|
|
24169
24600
|
);
|
|
@@ -24179,7 +24610,7 @@ var ADRExporter = class {
|
|
|
24179
24610
|
}
|
|
24180
24611
|
// Generate index file
|
|
24181
24612
|
generateIndex(decisions, outputDir) {
|
|
24182
|
-
const indexPath =
|
|
24613
|
+
const indexPath = join5(outputDir, "README.md");
|
|
24183
24614
|
const lines = [
|
|
24184
24615
|
"# Architecture Decision Records",
|
|
24185
24616
|
"",
|
|
@@ -24331,7 +24762,7 @@ ${decision.files.map((f) => `- \`${f}\``).join("\n")}
|
|
|
24331
24762
|
// src/core/feature-context.ts
|
|
24332
24763
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
24333
24764
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync5 } from "fs";
|
|
24334
|
-
import { dirname as
|
|
24765
|
+
import { dirname as dirname4, join as join6, basename as basename3 } from "path";
|
|
24335
24766
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
24336
24767
|
var MAX_FILES = 20;
|
|
24337
24768
|
var MAX_CHANGES = 50;
|
|
@@ -24351,7 +24782,7 @@ var FeatureContextManager = class extends EventEmitter3 {
|
|
|
24351
24782
|
super();
|
|
24352
24783
|
this.projectPath = projectPath;
|
|
24353
24784
|
this.dataDir = dataDir;
|
|
24354
|
-
this.persistPath =
|
|
24785
|
+
this.persistPath = join6(dataDir, "feature-context.json");
|
|
24355
24786
|
this.load();
|
|
24356
24787
|
this.startInactivityTimer();
|
|
24357
24788
|
}
|
|
@@ -24580,7 +25011,7 @@ var FeatureContextManager = class extends EventEmitter3 {
|
|
|
24580
25011
|
// ========== PERSISTENCE ==========
|
|
24581
25012
|
save() {
|
|
24582
25013
|
try {
|
|
24583
|
-
const dir =
|
|
25014
|
+
const dir = dirname4(this.persistPath);
|
|
24584
25015
|
if (!existsSync6(dir)) {
|
|
24585
25016
|
mkdirSync5(dir, { recursive: true });
|
|
24586
25017
|
}
|
|
@@ -24643,7 +25074,7 @@ var FeatureContextManager = class extends EventEmitter3 {
|
|
|
24643
25074
|
if (relativePath.startsWith(this.projectPath)) {
|
|
24644
25075
|
return relativePath;
|
|
24645
25076
|
}
|
|
24646
|
-
return
|
|
25077
|
+
return join6(this.projectPath, relativePath);
|
|
24647
25078
|
}
|
|
24648
25079
|
// ========== CONTEXT RESURRECTION ==========
|
|
24649
25080
|
/**
|
|
@@ -24842,7 +25273,7 @@ var FeatureContextManager = class extends EventEmitter3 {
|
|
|
24842
25273
|
|
|
24843
25274
|
// src/core/living-docs/architecture-generator.ts
|
|
24844
25275
|
import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as readdirSync2 } from "fs";
|
|
24845
|
-
import { basename as basename4, join as
|
|
25276
|
+
import { basename as basename4, join as join7 } from "path";
|
|
24846
25277
|
var ArchitectureGenerator = class _ArchitectureGenerator {
|
|
24847
25278
|
projectPath;
|
|
24848
25279
|
tier2;
|
|
@@ -24920,7 +25351,7 @@ var ArchitectureGenerator = class _ArchitectureGenerator {
|
|
|
24920
25351
|
detectLayers() {
|
|
24921
25352
|
const layers = [];
|
|
24922
25353
|
const layerMap = /* @__PURE__ */ new Map();
|
|
24923
|
-
const srcDir = existsSync7(
|
|
25354
|
+
const srcDir = existsSync7(join7(this.projectPath, "src")) ? join7(this.projectPath, "src") : this.projectPath;
|
|
24924
25355
|
try {
|
|
24925
25356
|
const entries = readdirSync2(srcDir, { withFileTypes: true });
|
|
24926
25357
|
for (const entry of entries) {
|
|
@@ -24928,7 +25359,7 @@ var ArchitectureGenerator = class _ArchitectureGenerator {
|
|
|
24928
25359
|
const dirName = entry.name.toLowerCase();
|
|
24929
25360
|
const layerName = _ArchitectureGenerator.LAYER_MAPPING[dirName];
|
|
24930
25361
|
if (layerName) {
|
|
24931
|
-
const dirPath =
|
|
25362
|
+
const dirPath = join7(srcDir, entry.name);
|
|
24932
25363
|
const relativePath = dirPath.replace(this.projectPath, "").replace(/^[/\\]/, "");
|
|
24933
25364
|
const files = this.getFilesInDirectory(relativePath);
|
|
24934
25365
|
if (files.length > 0) {
|
|
@@ -24971,12 +25402,12 @@ var ArchitectureGenerator = class _ArchitectureGenerator {
|
|
|
24971
25402
|
}
|
|
24972
25403
|
getFilesInDirectory(relativePath) {
|
|
24973
25404
|
const files = [];
|
|
24974
|
-
const absolutePath =
|
|
25405
|
+
const absolutePath = join7(this.projectPath, relativePath);
|
|
24975
25406
|
try {
|
|
24976
25407
|
const entries = readdirSync2(absolutePath, { withFileTypes: true });
|
|
24977
25408
|
for (const entry of entries) {
|
|
24978
25409
|
if (entry.isFile() && this.isCodeFile(entry.name)) {
|
|
24979
|
-
files.push(
|
|
25410
|
+
files.push(join7(relativePath, entry.name));
|
|
24980
25411
|
}
|
|
24981
25412
|
}
|
|
24982
25413
|
} catch {
|
|
@@ -25105,7 +25536,7 @@ var ArchitectureGenerator = class _ArchitectureGenerator {
|
|
|
25105
25536
|
}
|
|
25106
25537
|
getProjectDependencies() {
|
|
25107
25538
|
const deps = [];
|
|
25108
|
-
const packageJsonPath =
|
|
25539
|
+
const packageJsonPath = join7(this.projectPath, "package.json");
|
|
25109
25540
|
if (existsSync7(packageJsonPath)) {
|
|
25110
25541
|
try {
|
|
25111
25542
|
const pkg = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
|
|
@@ -25133,7 +25564,7 @@ var ArchitectureGenerator = class _ArchitectureGenerator {
|
|
|
25133
25564
|
} catch {
|
|
25134
25565
|
}
|
|
25135
25566
|
}
|
|
25136
|
-
const requirementsPath =
|
|
25567
|
+
const requirementsPath = join7(this.projectPath, "requirements.txt");
|
|
25137
25568
|
if (existsSync7(requirementsPath)) {
|
|
25138
25569
|
try {
|
|
25139
25570
|
const content = readFileSync7(requirementsPath, "utf-8");
|
|
@@ -25178,7 +25609,7 @@ var ArchitectureGenerator = class _ArchitectureGenerator {
|
|
|
25178
25609
|
// src/core/living-docs/component-generator.ts
|
|
25179
25610
|
import { execSync as execSync2 } from "child_process";
|
|
25180
25611
|
import { existsSync as existsSync8 } from "fs";
|
|
25181
|
-
import { basename as basename5, extname as extname2, join as
|
|
25612
|
+
import { basename as basename5, extname as extname2, join as join8 } from "path";
|
|
25182
25613
|
var ComponentGenerator = class {
|
|
25183
25614
|
projectPath;
|
|
25184
25615
|
tier2;
|
|
@@ -25186,7 +25617,7 @@ var ComponentGenerator = class {
|
|
|
25186
25617
|
constructor(projectPath, tier2) {
|
|
25187
25618
|
this.projectPath = projectPath;
|
|
25188
25619
|
this.tier2 = tier2;
|
|
25189
|
-
this.isGitRepo = existsSync8(
|
|
25620
|
+
this.isGitRepo = existsSync8(join8(projectPath, ".git"));
|
|
25190
25621
|
}
|
|
25191
25622
|
async generate(filePath) {
|
|
25192
25623
|
const file2 = this.tier2.getFile(filePath);
|
|
@@ -25355,7 +25786,7 @@ var ComponentGenerator = class {
|
|
|
25355
25786
|
// src/core/living-docs/changelog-generator.ts
|
|
25356
25787
|
import { execSync as execSync3 } from "child_process";
|
|
25357
25788
|
import { existsSync as existsSync9 } from "fs";
|
|
25358
|
-
import { join as
|
|
25789
|
+
import { join as join9 } from "path";
|
|
25359
25790
|
var ChangelogGenerator = class {
|
|
25360
25791
|
projectPath;
|
|
25361
25792
|
db;
|
|
@@ -25363,7 +25794,7 @@ var ChangelogGenerator = class {
|
|
|
25363
25794
|
constructor(projectPath, db) {
|
|
25364
25795
|
this.projectPath = projectPath;
|
|
25365
25796
|
this.db = db;
|
|
25366
|
-
this.isGitRepo = existsSync9(
|
|
25797
|
+
this.isGitRepo = existsSync9(join9(projectPath, ".git"));
|
|
25367
25798
|
}
|
|
25368
25799
|
async generate(options = {}) {
|
|
25369
25800
|
if (!this.isGitRepo) {
|
|
@@ -25810,7 +26241,7 @@ var DocValidator = class {
|
|
|
25810
26241
|
// src/core/living-docs/activity-tracker.ts
|
|
25811
26242
|
import { execSync as execSync4 } from "child_process";
|
|
25812
26243
|
import { existsSync as existsSync10 } from "fs";
|
|
25813
|
-
import { join as
|
|
26244
|
+
import { join as join10 } from "path";
|
|
25814
26245
|
var ActivityTracker = class {
|
|
25815
26246
|
projectPath;
|
|
25816
26247
|
db;
|
|
@@ -25820,7 +26251,7 @@ var ActivityTracker = class {
|
|
|
25820
26251
|
this.projectPath = projectPath;
|
|
25821
26252
|
this.db = db;
|
|
25822
26253
|
this.tier2 = tier2;
|
|
25823
|
-
this.isGitRepo = existsSync10(
|
|
26254
|
+
this.isGitRepo = existsSync10(join10(projectPath, ".git"));
|
|
25824
26255
|
}
|
|
25825
26256
|
async whatHappened(since, scope) {
|
|
25826
26257
|
const sinceDate = this.parseSinceString(since);
|
|
@@ -30036,7 +30467,7 @@ ${category.toUpperCase()}:`);
|
|
|
30036
30467
|
|
|
30037
30468
|
// src/core/test-awareness/test-indexer.ts
|
|
30038
30469
|
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
30039
|
-
import { join as
|
|
30470
|
+
import { join as join11 } from "path";
|
|
30040
30471
|
|
|
30041
30472
|
// src/core/test-awareness/test-parser.ts
|
|
30042
30473
|
import { createHash as createHash4 } from "crypto";
|
|
@@ -30598,7 +31029,7 @@ var TestIndexer = class {
|
|
|
30598
31029
|
this.parser = new TestParser();
|
|
30599
31030
|
}
|
|
30600
31031
|
detectFramework() {
|
|
30601
|
-
const packageJsonPath =
|
|
31032
|
+
const packageJsonPath = join11(this.projectPath, "package.json");
|
|
30602
31033
|
if (existsSync11(packageJsonPath)) {
|
|
30603
31034
|
try {
|
|
30604
31035
|
const pkg = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
|
|
@@ -30611,21 +31042,21 @@ var TestIndexer = class {
|
|
|
30611
31042
|
}
|
|
30612
31043
|
const pytestConfigs = ["pytest.ini", "pyproject.toml", "setup.cfg", "conftest.py"];
|
|
30613
31044
|
for (const config2 of pytestConfigs) {
|
|
30614
|
-
if (existsSync11(
|
|
30615
|
-
const content = readFileSync8(
|
|
31045
|
+
if (existsSync11(join11(this.projectPath, config2))) {
|
|
31046
|
+
const content = readFileSync8(join11(this.projectPath, config2), "utf-8");
|
|
30616
31047
|
if (content.includes("[pytest]") || content.includes("pytest") || config2 === "conftest.py") {
|
|
30617
31048
|
return "pytest";
|
|
30618
31049
|
}
|
|
30619
31050
|
}
|
|
30620
31051
|
}
|
|
30621
|
-
const goMod =
|
|
31052
|
+
const goMod = join11(this.projectPath, "go.mod");
|
|
30622
31053
|
if (existsSync11(goMod)) {
|
|
30623
31054
|
return "go";
|
|
30624
31055
|
}
|
|
30625
31056
|
const vitestConfigs = ["vitest.config.ts", "vitest.config.js", "vite.config.ts", "vite.config.js"];
|
|
30626
31057
|
for (const config2 of vitestConfigs) {
|
|
30627
|
-
if (existsSync11(
|
|
30628
|
-
const content = readFileSync8(
|
|
31058
|
+
if (existsSync11(join11(this.projectPath, config2))) {
|
|
31059
|
+
const content = readFileSync8(join11(this.projectPath, config2), "utf-8");
|
|
30629
31060
|
if (content.includes("vitest") || content.includes("test:")) {
|
|
30630
31061
|
return "vitest";
|
|
30631
31062
|
}
|
|
@@ -30633,7 +31064,7 @@ var TestIndexer = class {
|
|
|
30633
31064
|
}
|
|
30634
31065
|
const jestConfigs = ["jest.config.ts", "jest.config.js", "jest.config.json"];
|
|
30635
31066
|
for (const config2 of jestConfigs) {
|
|
30636
|
-
if (existsSync11(
|
|
31067
|
+
if (existsSync11(join11(this.projectPath, config2))) {
|
|
30637
31068
|
return "jest";
|
|
30638
31069
|
}
|
|
30639
31070
|
}
|
|
@@ -30672,7 +31103,7 @@ var TestIndexer = class {
|
|
|
30672
31103
|
absolute: false
|
|
30673
31104
|
});
|
|
30674
31105
|
for (const file2 of files) {
|
|
30675
|
-
const absolutePath =
|
|
31106
|
+
const absolutePath = join11(this.projectPath, file2);
|
|
30676
31107
|
const tests = this.parseTestFile(absolutePath, file2);
|
|
30677
31108
|
allTests.push(...tests);
|
|
30678
31109
|
}
|
|
@@ -30799,7 +31230,7 @@ var TestIndexer = class {
|
|
|
30799
31230
|
absolute: false
|
|
30800
31231
|
});
|
|
30801
31232
|
for (const file2 of files) {
|
|
30802
|
-
const absolutePath =
|
|
31233
|
+
const absolutePath = join11(this.projectPath, file2);
|
|
30803
31234
|
const tests = this.parseTestFile(absolutePath, file2);
|
|
30804
31235
|
allTests.push(...tests);
|
|
30805
31236
|
}
|
|
@@ -31833,6 +32264,8 @@ var TECH_PATTERNS = [
|
|
|
31833
32264
|
var GhostMode = class {
|
|
31834
32265
|
activeFiles = /* @__PURE__ */ new Map();
|
|
31835
32266
|
recentDecisions = /* @__PURE__ */ new Map();
|
|
32267
|
+
recentImpacts = /* @__PURE__ */ new Map();
|
|
32268
|
+
// Track file impacts
|
|
31836
32269
|
tier2;
|
|
31837
32270
|
embeddingGenerator;
|
|
31838
32271
|
// Ghost mode settings
|
|
@@ -31840,10 +32273,47 @@ var GhostMode = class {
|
|
|
31840
32273
|
FILE_TTL_MS = 60 * 60 * 1e3;
|
|
31841
32274
|
// 1 hour
|
|
31842
32275
|
DECISION_CACHE_SIZE = 50;
|
|
32276
|
+
IMPACT_TTL_MS = 30 * 60 * 1e3;
|
|
32277
|
+
// 30 minutes
|
|
31843
32278
|
constructor(tier2, embeddingGenerator) {
|
|
31844
32279
|
this.tier2 = tier2;
|
|
31845
32280
|
this.embeddingGenerator = embeddingGenerator;
|
|
31846
32281
|
}
|
|
32282
|
+
/**
|
|
32283
|
+
* Called when a file change impacts other files
|
|
32284
|
+
*/
|
|
32285
|
+
onFileImpact(changedFile, affectedFiles) {
|
|
32286
|
+
const impact = {
|
|
32287
|
+
changedFile,
|
|
32288
|
+
affectedFiles,
|
|
32289
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
32290
|
+
};
|
|
32291
|
+
for (const file2 of affectedFiles) {
|
|
32292
|
+
this.recentImpacts.set(file2, impact);
|
|
32293
|
+
}
|
|
32294
|
+
this.evictStaleImpacts();
|
|
32295
|
+
}
|
|
32296
|
+
/**
|
|
32297
|
+
* Check if a file was recently impacted by changes to another file
|
|
32298
|
+
*/
|
|
32299
|
+
getImpactWarning(filePath) {
|
|
32300
|
+
const impact = this.recentImpacts.get(filePath);
|
|
32301
|
+
if (!impact) return null;
|
|
32302
|
+
const age = Date.now() - impact.timestamp.getTime();
|
|
32303
|
+
if (age > this.IMPACT_TTL_MS) {
|
|
32304
|
+
this.recentImpacts.delete(filePath);
|
|
32305
|
+
return null;
|
|
32306
|
+
}
|
|
32307
|
+
return impact;
|
|
32308
|
+
}
|
|
32309
|
+
evictStaleImpacts() {
|
|
32310
|
+
const now = Date.now();
|
|
32311
|
+
for (const [file2, impact] of this.recentImpacts) {
|
|
32312
|
+
if (now - impact.timestamp.getTime() > this.IMPACT_TTL_MS) {
|
|
32313
|
+
this.recentImpacts.delete(file2);
|
|
32314
|
+
}
|
|
32315
|
+
}
|
|
32316
|
+
}
|
|
31847
32317
|
/**
|
|
31848
32318
|
* Called when any file is read - silently track and pre-fetch decisions
|
|
31849
32319
|
*/
|
|
@@ -32399,7 +32869,7 @@ var DejaVuDetector = class {
|
|
|
32399
32869
|
|
|
32400
32870
|
// src/core/code-verifier.ts
|
|
32401
32871
|
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
32402
|
-
import { join as
|
|
32872
|
+
import { join as join12, dirname as dirname6, extname as extname4 } from "path";
|
|
32403
32873
|
var SECURITY_PATTERNS2 = [
|
|
32404
32874
|
// SQL Injection
|
|
32405
32875
|
{
|
|
@@ -32644,11 +33114,11 @@ var CodeVerifier = class {
|
|
|
32644
33114
|
nodeModulesPath;
|
|
32645
33115
|
constructor(projectPath) {
|
|
32646
33116
|
this.projectPath = projectPath;
|
|
32647
|
-
this.nodeModulesPath =
|
|
33117
|
+
this.nodeModulesPath = join12(projectPath, "node_modules");
|
|
32648
33118
|
this.loadPackageJson();
|
|
32649
33119
|
}
|
|
32650
33120
|
loadPackageJson() {
|
|
32651
|
-
const packageJsonPath =
|
|
33121
|
+
const packageJsonPath = join12(this.projectPath, "package.json");
|
|
32652
33122
|
if (existsSync12(packageJsonPath)) {
|
|
32653
33123
|
try {
|
|
32654
33124
|
this.packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
|
|
@@ -32801,7 +33271,7 @@ var CodeVerifier = class {
|
|
|
32801
33271
|
}
|
|
32802
33272
|
const packageName = this.getPackageName(importPath);
|
|
32803
33273
|
if (!packages.has(packageName)) {
|
|
32804
|
-
const nodeModulePath =
|
|
33274
|
+
const nodeModulePath = join12(this.nodeModulesPath, packageName);
|
|
32805
33275
|
if (existsSync12(nodeModulePath)) {
|
|
32806
33276
|
issues.push({
|
|
32807
33277
|
package: packageName,
|
|
@@ -32844,22 +33314,22 @@ var CodeVerifier = class {
|
|
|
32844
33314
|
}
|
|
32845
33315
|
verifyRelativeImport(importPath, file2, issues) {
|
|
32846
33316
|
if (!file2) return;
|
|
32847
|
-
const baseDir =
|
|
33317
|
+
const baseDir = dirname6(join12(this.projectPath, file2));
|
|
32848
33318
|
const possibleExtensions = [".ts", ".tsx", ".js", ".jsx", ".json", ""];
|
|
32849
33319
|
const possibleIndexes = ["index.ts", "index.tsx", "index.js", "index.jsx"];
|
|
32850
33320
|
let found = false;
|
|
32851
33321
|
for (const ext of possibleExtensions) {
|
|
32852
|
-
const fullPath =
|
|
33322
|
+
const fullPath = join12(baseDir, importPath + ext);
|
|
32853
33323
|
if (existsSync12(fullPath)) {
|
|
32854
33324
|
found = true;
|
|
32855
33325
|
break;
|
|
32856
33326
|
}
|
|
32857
33327
|
}
|
|
32858
33328
|
if (!found) {
|
|
32859
|
-
const dirPath =
|
|
33329
|
+
const dirPath = join12(baseDir, importPath);
|
|
32860
33330
|
if (existsSync12(dirPath)) {
|
|
32861
33331
|
for (const indexFile of possibleIndexes) {
|
|
32862
|
-
if (existsSync12(
|
|
33332
|
+
if (existsSync12(join12(dirPath, indexFile))) {
|
|
32863
33333
|
found = true;
|
|
32864
33334
|
break;
|
|
32865
33335
|
}
|
|
@@ -32877,7 +33347,7 @@ var CodeVerifier = class {
|
|
|
32877
33347
|
}
|
|
32878
33348
|
verifyPackageImport(importPath, issues, warnings) {
|
|
32879
33349
|
const packageName = this.getPackageName(importPath);
|
|
32880
|
-
const nodeModulePath =
|
|
33350
|
+
const nodeModulePath = join12(this.nodeModulesPath, packageName);
|
|
32881
33351
|
if (!existsSync12(nodeModulePath)) {
|
|
32882
33352
|
issues.push({
|
|
32883
33353
|
import: importPath,
|
|
@@ -32889,7 +33359,7 @@ var CodeVerifier = class {
|
|
|
32889
33359
|
}
|
|
32890
33360
|
if (importPath !== packageName) {
|
|
32891
33361
|
const subpath = importPath.slice(packageName.length + 1);
|
|
32892
|
-
const subpathFull =
|
|
33362
|
+
const subpathFull = join12(nodeModulePath, subpath);
|
|
32893
33363
|
const possibleExtensions = [".js", ".json", ""];
|
|
32894
33364
|
let found = false;
|
|
32895
33365
|
for (const ext of possibleExtensions) {
|
|
@@ -32898,7 +33368,7 @@ var CodeVerifier = class {
|
|
|
32898
33368
|
break;
|
|
32899
33369
|
}
|
|
32900
33370
|
}
|
|
32901
|
-
const pkgJsonPath =
|
|
33371
|
+
const pkgJsonPath = join12(nodeModulePath, "package.json");
|
|
32902
33372
|
if (!found && existsSync12(pkgJsonPath)) {
|
|
32903
33373
|
try {
|
|
32904
33374
|
const pkgJson = JSON.parse(readFileSync9(pkgJsonPath, "utf-8"));
|
|
@@ -33323,7 +33793,17 @@ var MemoryLayerEngine = class {
|
|
|
33323
33793
|
if (!existsSync13(config2.dataDir)) {
|
|
33324
33794
|
mkdirSync6(config2.dataDir, { recursive: true });
|
|
33325
33795
|
}
|
|
33326
|
-
|
|
33796
|
+
let dbPath = join13(config2.dataDir, "neuronlayer.db");
|
|
33797
|
+
const oldDbPath = join13(config2.dataDir, "memorylayer.db");
|
|
33798
|
+
if (!existsSync13(dbPath) && existsSync13(oldDbPath)) {
|
|
33799
|
+
try {
|
|
33800
|
+
console.error("Migrating database from memorylayer.db to neuronlayer.db...");
|
|
33801
|
+
renameSync(oldDbPath, dbPath);
|
|
33802
|
+
} catch (err) {
|
|
33803
|
+
console.error("Migration skipped (file in use), using existing database");
|
|
33804
|
+
dbPath = oldDbPath;
|
|
33805
|
+
}
|
|
33806
|
+
}
|
|
33327
33807
|
this.db = initializeDatabase(dbPath);
|
|
33328
33808
|
this.tier1 = new Tier1Storage(config2.dataDir);
|
|
33329
33809
|
this.tier2 = new Tier2Storage(this.db);
|
|
@@ -33418,11 +33898,31 @@ var MemoryLayerEngine = class {
|
|
|
33418
33898
|
this.updateProjectSummary();
|
|
33419
33899
|
this.updateProjectStats();
|
|
33420
33900
|
this.extractDecisions().catch((err) => console.error("Decision extraction error:", err));
|
|
33901
|
+
try {
|
|
33902
|
+
const testResult = this.testAwareness.refreshIndex();
|
|
33903
|
+
if (testResult.testsIndexed > 0) {
|
|
33904
|
+
console.error(`Test index: ${testResult.testsIndexed} tests (${testResult.framework})`);
|
|
33905
|
+
}
|
|
33906
|
+
} catch (err) {
|
|
33907
|
+
console.error("Test indexing error:", err);
|
|
33908
|
+
}
|
|
33421
33909
|
});
|
|
33422
33910
|
this.indexer.on("fileIndexed", (path) => {
|
|
33423
33911
|
this.featureContextManager.onFileOpened(path);
|
|
33424
33912
|
this.summarizer.invalidateSummaryByPath(path);
|
|
33425
33913
|
});
|
|
33914
|
+
this.indexer.on("fileImpact", (impact) => {
|
|
33915
|
+
if (impact.affectedCount > 0) {
|
|
33916
|
+
console.error(`[Impact] ${impact.file} changed \u2192 ${impact.affectedCount} file(s) may be affected`);
|
|
33917
|
+
if (impact.affectedCount <= 5) {
|
|
33918
|
+
impact.affectedFiles.forEach((f) => console.error(` \u2192 ${f}`));
|
|
33919
|
+
} else {
|
|
33920
|
+
impact.affectedFiles.slice(0, 3).forEach((f) => console.error(` \u2192 ${f}`));
|
|
33921
|
+
console.error(` ... and ${impact.affectedCount - 3} more`);
|
|
33922
|
+
}
|
|
33923
|
+
this.ghostMode.onFileImpact(impact.file, impact.affectedFiles);
|
|
33924
|
+
}
|
|
33925
|
+
});
|
|
33426
33926
|
this.indexer.on("error", (error2) => {
|
|
33427
33927
|
console.error("Indexer error:", error2);
|
|
33428
33928
|
});
|
|
@@ -33629,7 +34129,7 @@ ${description}`,
|
|
|
33629
34129
|
}
|
|
33630
34130
|
async getFileContext(filePath) {
|
|
33631
34131
|
this.activityGate.recordActivity();
|
|
33632
|
-
const absolutePath =
|
|
34132
|
+
const absolutePath = join13(this.config.projectPath, filePath);
|
|
33633
34133
|
if (!existsSync13(absolutePath)) {
|
|
33634
34134
|
return null;
|
|
33635
34135
|
}
|
|
@@ -33716,7 +34216,7 @@ ${decision.description}`;
|
|
|
33716
34216
|
const commonDirs = ["src", "lib", "app", "pages", "components", "api", "server", "client", "core"];
|
|
33717
34217
|
const found = [];
|
|
33718
34218
|
for (const dir of commonDirs) {
|
|
33719
|
-
if (existsSync13(
|
|
34219
|
+
if (existsSync13(join13(this.config.projectPath, dir))) {
|
|
33720
34220
|
found.push(dir);
|
|
33721
34221
|
}
|
|
33722
34222
|
}
|
|
@@ -33724,7 +34224,7 @@ ${decision.description}`;
|
|
|
33724
34224
|
}
|
|
33725
34225
|
detectDependencies() {
|
|
33726
34226
|
const deps = [];
|
|
33727
|
-
const packageJsonPath =
|
|
34227
|
+
const packageJsonPath = join13(this.config.projectPath, "package.json");
|
|
33728
34228
|
if (existsSync13(packageJsonPath)) {
|
|
33729
34229
|
try {
|
|
33730
34230
|
const pkg = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
@@ -33736,7 +34236,7 @@ ${decision.description}`;
|
|
|
33736
34236
|
} catch {
|
|
33737
34237
|
}
|
|
33738
34238
|
}
|
|
33739
|
-
const requirementsPath =
|
|
34239
|
+
const requirementsPath = join13(this.config.projectPath, "requirements.txt");
|
|
33740
34240
|
if (existsSync13(requirementsPath)) {
|
|
33741
34241
|
try {
|
|
33742
34242
|
const content = readFileSync10(requirementsPath, "utf-8");
|
|
@@ -33779,6 +34279,14 @@ ${decision.description}`;
|
|
|
33779
34279
|
}));
|
|
33780
34280
|
return { imports, importedBy, symbols };
|
|
33781
34281
|
}
|
|
34282
|
+
// Find circular dependencies in the project
|
|
34283
|
+
findCircularDependencies() {
|
|
34284
|
+
return this.tier2.findCircularDependencies();
|
|
34285
|
+
}
|
|
34286
|
+
// Get transitive dependents (all files affected by changing a file)
|
|
34287
|
+
getTransitiveDependents(filePath, maxDepth = 3) {
|
|
34288
|
+
return this.tier2.getTransitiveDependents(filePath, maxDepth);
|
|
34289
|
+
}
|
|
33782
34290
|
// Phase 2: Get symbol count
|
|
33783
34291
|
getSymbolCount() {
|
|
33784
34292
|
return this.tier2.getSymbolCount();
|
|
@@ -33793,7 +34301,7 @@ ${decision.description}`;
|
|
|
33793
34301
|
let fetched = 0;
|
|
33794
34302
|
for (const filePath of predicted) {
|
|
33795
34303
|
if (!this.learningEngine.isInHotCache(filePath)) {
|
|
33796
|
-
const absolutePath =
|
|
34304
|
+
const absolutePath = join13(this.config.projectPath, filePath);
|
|
33797
34305
|
if (existsSync13(absolutePath)) {
|
|
33798
34306
|
try {
|
|
33799
34307
|
const content = readFileSync10(absolutePath, "utf-8");
|
|
@@ -36758,6 +37266,83 @@ async function handleToolCall(engine, toolName, args) {
|
|
|
36758
37266
|
symbols: result.symbols
|
|
36759
37267
|
};
|
|
36760
37268
|
}
|
|
37269
|
+
case "find_circular_deps": {
|
|
37270
|
+
const cycles = engine.findCircularDependencies();
|
|
37271
|
+
if (cycles.length === 0) {
|
|
37272
|
+
return {
|
|
37273
|
+
status: "clean",
|
|
37274
|
+
message: "No circular dependencies found",
|
|
37275
|
+
cycles: []
|
|
37276
|
+
};
|
|
37277
|
+
}
|
|
37278
|
+
return {
|
|
37279
|
+
status: "cycles_found",
|
|
37280
|
+
message: `Found ${cycles.length} circular dependency chain(s)`,
|
|
37281
|
+
cycles: cycles.map((cycle, i) => ({
|
|
37282
|
+
id: i + 1,
|
|
37283
|
+
files: cycle,
|
|
37284
|
+
length: cycle.length - 1,
|
|
37285
|
+
// -1 because last element repeats first
|
|
37286
|
+
description: cycle.join(" \u2192 ")
|
|
37287
|
+
})),
|
|
37288
|
+
recommendation: "Break cycles by extracting shared code into a separate module, using dependency injection, or restructuring imports."
|
|
37289
|
+
};
|
|
37290
|
+
}
|
|
37291
|
+
case "get_impact_analysis": {
|
|
37292
|
+
const filePath = args.file;
|
|
37293
|
+
const depth = args.depth || 3;
|
|
37294
|
+
const includeTests = args.include_tests !== false;
|
|
37295
|
+
const affected = engine.getTransitiveDependents(filePath, depth);
|
|
37296
|
+
const deps = engine.getFileDependencies(filePath);
|
|
37297
|
+
let affectedTests = [];
|
|
37298
|
+
if (includeTests) {
|
|
37299
|
+
const allAffectedFiles = [filePath, ...affected.map((a) => a.file)];
|
|
37300
|
+
const testSet = /* @__PURE__ */ new Set();
|
|
37301
|
+
for (const f of allAffectedFiles) {
|
|
37302
|
+
const tests = engine.getTestsForFile(f);
|
|
37303
|
+
for (const t of tests) {
|
|
37304
|
+
if (!testSet.has(t.id)) {
|
|
37305
|
+
testSet.add(t.id);
|
|
37306
|
+
affectedTests.push({ name: t.name, file: t.file });
|
|
37307
|
+
}
|
|
37308
|
+
}
|
|
37309
|
+
}
|
|
37310
|
+
}
|
|
37311
|
+
const allCycles = engine.findCircularDependencies();
|
|
37312
|
+
const relevantCycles = allCycles.filter((cycle) => cycle.includes(filePath));
|
|
37313
|
+
const totalAffected = affected.length;
|
|
37314
|
+
const riskLevel = totalAffected > 10 ? "HIGH" : totalAffected > 5 ? "MEDIUM" : "LOW";
|
|
37315
|
+
const riskFactors = [];
|
|
37316
|
+
if (totalAffected > 10) riskFactors.push(`${totalAffected} files depend on this`);
|
|
37317
|
+
if (relevantCycles.length > 0) riskFactors.push(`Involved in ${relevantCycles.length} circular dependency chain(s)`);
|
|
37318
|
+
if (affectedTests.length > 5) riskFactors.push(`${affectedTests.length} tests may need updates`);
|
|
37319
|
+
if (deps.imports.length > 10) riskFactors.push(`File has ${deps.imports.length} dependencies`);
|
|
37320
|
+
return {
|
|
37321
|
+
file: filePath,
|
|
37322
|
+
risk_level: riskLevel,
|
|
37323
|
+
risk_factors: riskFactors.length > 0 ? riskFactors : ["No significant risk factors detected"],
|
|
37324
|
+
summary: {
|
|
37325
|
+
total_affected_files: totalAffected,
|
|
37326
|
+
direct_dependents: affected.filter((a) => a.depth === 1).length,
|
|
37327
|
+
indirect_dependents: affected.filter((a) => a.depth > 1).length,
|
|
37328
|
+
affected_tests: affectedTests.length,
|
|
37329
|
+
circular_dependencies: relevantCycles.length
|
|
37330
|
+
},
|
|
37331
|
+
direct_dependents: affected.filter((a) => a.depth === 1).map((a) => ({
|
|
37332
|
+
file: a.file,
|
|
37333
|
+
imports: a.imports
|
|
37334
|
+
})),
|
|
37335
|
+
indirect_dependents: affected.filter((a) => a.depth > 1).map((a) => ({
|
|
37336
|
+
file: a.file,
|
|
37337
|
+
depth: a.depth,
|
|
37338
|
+
imports: a.imports
|
|
37339
|
+
})),
|
|
37340
|
+
this_file_imports: deps.imports.map((i) => i.file),
|
|
37341
|
+
affected_tests: affectedTests,
|
|
37342
|
+
circular_dependencies: relevantCycles.map((cycle) => cycle.join(" \u2192 ")),
|
|
37343
|
+
recommendation: riskLevel === "HIGH" ? "High-impact change. Consider breaking it into smaller changes and testing incrementally." : riskLevel === "MEDIUM" ? "Moderate impact. Review affected files and ensure tests cover the changes." : "Low-risk change. Standard review and testing should suffice."
|
|
37344
|
+
};
|
|
37345
|
+
}
|
|
36761
37346
|
case "get_file_summary": {
|
|
36762
37347
|
const path = args.path;
|
|
36763
37348
|
const summary = engine.getFileSummary(path);
|
|
@@ -37918,7 +38503,7 @@ var MCPServer = class {
|
|
|
37918
38503
|
this.engine = new MemoryLayerEngine(config2);
|
|
37919
38504
|
this.server = new Server(
|
|
37920
38505
|
{
|
|
37921
|
-
name: "
|
|
38506
|
+
name: "neuronlayer",
|
|
37922
38507
|
version: "0.1.0"
|
|
37923
38508
|
},
|
|
37924
38509
|
{
|
|
@@ -38021,13 +38606,13 @@ var MCPServer = class {
|
|
|
38021
38606
|
};
|
|
38022
38607
|
|
|
38023
38608
|
// src/utils/config.ts
|
|
38024
|
-
import { join as
|
|
38609
|
+
import { join as join14, resolve as resolve2 } from "path";
|
|
38025
38610
|
function getDefaultConfig(projectPath) {
|
|
38026
38611
|
const normalizedPath = resolve2(projectPath);
|
|
38027
38612
|
return {
|
|
38028
38613
|
projectPath: normalizedPath,
|
|
38029
38614
|
// Store in project directory (standard practice like .git/, .vscode/)
|
|
38030
|
-
dataDir:
|
|
38615
|
+
dataDir: join14(normalizedPath, ".memorylayer"),
|
|
38031
38616
|
maxTokens: 6e3,
|
|
38032
38617
|
embeddingModel: "Xenova/all-MiniLM-L6-v2",
|
|
38033
38618
|
// Fallback model, faster and smaller
|
|
@@ -38189,7 +38774,7 @@ function parseArgs(args) {
|
|
|
38189
38774
|
}
|
|
38190
38775
|
|
|
38191
38776
|
// src/cli/commands.ts
|
|
38192
|
-
import { join as
|
|
38777
|
+
import { join as join15 } from "path";
|
|
38193
38778
|
import { existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync5, mkdirSync as mkdirSync7 } from "fs";
|
|
38194
38779
|
import { homedir as homedir2 } from "os";
|
|
38195
38780
|
var projectManager = new ProjectManager();
|
|
@@ -38305,12 +38890,17 @@ function exportDecisions(projectPath, options = {}) {
|
|
|
38305
38890
|
message: `Project not registered: ${targetPath}. Use "memorylayer projects add ${targetPath}" first.`
|
|
38306
38891
|
};
|
|
38307
38892
|
}
|
|
38308
|
-
|
|
38893
|
+
let dbPath = join15(projectInfo.dataDir, "neuronlayer.db");
|
|
38309
38894
|
if (!existsSync14(dbPath)) {
|
|
38310
|
-
|
|
38311
|
-
|
|
38312
|
-
|
|
38313
|
-
}
|
|
38895
|
+
const oldDbPath = join15(projectInfo.dataDir, "memorylayer.db");
|
|
38896
|
+
if (existsSync14(oldDbPath)) {
|
|
38897
|
+
dbPath = oldDbPath;
|
|
38898
|
+
} else {
|
|
38899
|
+
return {
|
|
38900
|
+
success: false,
|
|
38901
|
+
message: `Project database not found. Has the project been indexed?`
|
|
38902
|
+
};
|
|
38903
|
+
}
|
|
38314
38904
|
}
|
|
38315
38905
|
const db = initializeDatabase(dbPath);
|
|
38316
38906
|
const tier2 = new Tier2Storage(db);
|
|
@@ -38330,7 +38920,7 @@ function exportDecisions(projectPath, options = {}) {
|
|
|
38330
38920
|
});
|
|
38331
38921
|
return {
|
|
38332
38922
|
success: true,
|
|
38333
|
-
message: `Exported ${exportedFiles.length} ADR files to ${options.outputDir ||
|
|
38923
|
+
message: `Exported ${exportedFiles.length} ADR files to ${options.outputDir || join15(targetPath, "docs", "decisions")}`,
|
|
38334
38924
|
data: exportedFiles
|
|
38335
38925
|
};
|
|
38336
38926
|
}
|
|
@@ -38403,11 +38993,11 @@ function initProject(projectPath) {
|
|
|
38403
38993
|
const failedClients = [];
|
|
38404
38994
|
let claudeConfigPath;
|
|
38405
38995
|
if (platform === "win32") {
|
|
38406
|
-
claudeConfigPath =
|
|
38996
|
+
claudeConfigPath = join15(homedir2(), "AppData", "Roaming", "Claude", "claude_desktop_config.json");
|
|
38407
38997
|
} else if (platform === "darwin") {
|
|
38408
|
-
claudeConfigPath =
|
|
38998
|
+
claudeConfigPath = join15(homedir2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
38409
38999
|
} else {
|
|
38410
|
-
claudeConfigPath =
|
|
39000
|
+
claudeConfigPath = join15(homedir2(), ".config", "claude", "claude_desktop_config.json");
|
|
38411
39001
|
}
|
|
38412
39002
|
const claudeResult = configureMCPClient("Claude Desktop", claudeConfigPath, serverName, targetPath);
|
|
38413
39003
|
if (claudeResult.success) {
|
|
@@ -38415,14 +39005,14 @@ function initProject(projectPath) {
|
|
|
38415
39005
|
} else {
|
|
38416
39006
|
failedClients.push(claudeResult.message);
|
|
38417
39007
|
}
|
|
38418
|
-
const openCodeConfigPath =
|
|
39008
|
+
const openCodeConfigPath = join15(homedir2(), ".opencode", "config.json");
|
|
38419
39009
|
const openCodeResult = configureMCPClient("OpenCode", openCodeConfigPath, serverName, targetPath);
|
|
38420
39010
|
if (openCodeResult.success) {
|
|
38421
39011
|
configuredClients.push(openCodeResult.message);
|
|
38422
39012
|
} else {
|
|
38423
39013
|
failedClients.push(openCodeResult.message);
|
|
38424
39014
|
}
|
|
38425
|
-
const claudeCodeConfigPath =
|
|
39015
|
+
const claudeCodeConfigPath = join15(homedir2(), ".claude.json");
|
|
38426
39016
|
const claudeCodeResult = configureMCPClient("Claude Code", claudeCodeConfigPath, serverName, targetPath);
|
|
38427
39017
|
if (claudeCodeResult.success) {
|
|
38428
39018
|
configuredClients.push(claudeCodeResult.message);
|