code-graph-llm 1.6.0 → 2.1.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/AGENT_RULES.md +20 -0
- package/PROJECT_REFLECTIONS.md +14 -0
- package/README.md +35 -49
- package/index.js +352 -293
- package/llm-code-graph.md +8 -5
- package/package.json +1 -1
- package/test/index.test.js +51 -16
package/AGENT_RULES.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# AGENT OPERATIONAL PROTOCOL (STRICT)
|
|
2
|
+
> This protocol is MANDATORY for all LLM agents. Failure to update memory is a failure of the task.
|
|
3
|
+
## 🧠 THE REFLECTION CYCLE
|
|
4
|
+
Every execution SHOULD follow this cycle:
|
|
5
|
+
1. **PRE-TASK:** Read \`PROJECT_REFLECTIONS.md\` to identify existing pitfalls.
|
|
6
|
+
2. **EXECUTION:** Monitor for "Learned Moments" (failures, unexpected behaviors, or non-obvious fixes).
|
|
7
|
+
3. **POST-TASK:** If a significant lesson was learned, run \`code-graph reflect <CAT> <LESSON>\`.
|
|
8
|
+
- **NOTE:** Routine, expected changes do not require reflections. Focus on high-signal context.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## ⚠️ CRITICAL MANDATES
|
|
12
|
+
- **OS [win32]:** Always prefix npm commands with `cmd /c`.
|
|
13
|
+
- **FILE [index.js]:** This is the core engine. Any change here requires immediate `npm test` validation.
|
|
14
|
+
- **DOCS:** `llm-code-graph.md` is the "Source of Truth" for your context. Keep it accurate.
|
|
15
|
+
|
|
16
|
+
## 📝 REFLECTION CATEGORIES
|
|
17
|
+
- `LOGIC`: Code bugs, better patterns, or complex regex pitfalls.
|
|
18
|
+
- `ENV`: OS compatibility, shell behaviors, or CI/CD issues.
|
|
19
|
+
- `DEP`: Library bugs, version incompatibilities, or deprecations.
|
|
20
|
+
- `STYLE`: Naming conventions or project-specific architectural rules.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# PROJECT_REFLECTIONS & LESSONS LEARNED
|
|
2
|
+
> LLM AGENT MEMORY: READ BEFORE STARTING TASKS. UPDATE ON FAILURES.
|
|
3
|
+
|
|
4
|
+
## ⚠️ CRITICAL PITFALLS
|
|
5
|
+
- [OS: win32] PowerShell script execution is disabled by default; use `cmd /c npm` for all npm commands to ensure compatibility.
|
|
6
|
+
- [LOGIC: index.js] `extractSymbolsAndInheritance` regex matches itself if TODO/FIXME strings are present in the code; ensure filters are in place.
|
|
7
|
+
|
|
8
|
+
## 📦 DEPENDENCIES & ENVIRONMENT
|
|
9
|
+
- [DEP: chokidar] Watcher should be debounced by at least 500ms to avoid race conditions during rapid file saves.
|
|
10
|
+
|
|
11
|
+
## ✅ BEST PRACTICES
|
|
12
|
+
- [STYLE] Always include dependency counts (↑N ↓M) in `llm-code-graph.md` to help prioritize architectural understanding.
|
|
13
|
+
|
|
14
|
+
- [TOOLING: 2026-04-15] Added reflect command to simplify LLM memory updates.
|
package/README.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
# CODE-GRAPH
|
|
1
|
+
# CODE-GRAPH (v2.0.0)
|
|
2
2
|
|
|
3
|
-
A language-agnostic, ultra-compact codebase mapper designed specifically for LLM agents
|
|
3
|
+
A language-agnostic, ultra-compact codebase mapper and **agent memory system** designed specifically for LLM agents. It optimizes context and token usage while enabling agents to learn from their own mistakes across sessions.
|
|
4
|
+
|
|
5
|
+
## 🚀 New in v2.0: Self-Learning Memory
|
|
6
|
+
- **Reflection System:** Agents can now persist "Lessons Learned" via `code-graph reflect`.
|
|
7
|
+
- **Operational Protocol:** Standardized `AGENT_RULES.md` ensures agents follow strict "Reflect-Act-Verify" cycles.
|
|
8
|
+
- **Architectural Weight:** Project maps now include dependency counts (↑N ↓M) and CORE entry-point markers.
|
|
9
|
+
- **Production Refactor:** Class-based service architecture with full `fs/promises` async support.
|
|
4
10
|
|
|
5
11
|
## Features
|
|
6
|
-
- **Structural Knowledge Graph:** Captures
|
|
7
|
-
- **Dependencies:** Tracks `imports`, `requires`, and `includes` across files.
|
|
8
|
-
- **Inheritance:** Maps `extends`, `implements`, and class hierarchies.
|
|
12
|
+
- **Structural Knowledge Graph:** Captures `imports`, `requires`, `extends`, and `implements`.
|
|
9
13
|
- **Smart Context Extraction:** Captures JSDoc, Python docstrings, and preceding comments.
|
|
10
14
|
- **Signature Fallback:** Extracts function signatures (parameters/types) if documentation is missing.
|
|
11
15
|
- **Recursive .gitignore Support:** Deeply respects both root and nested `.gitignore` files.
|
|
@@ -16,33 +20,44 @@ A language-agnostic, ultra-compact codebase mapper designed specifically for LLM
|
|
|
16
20
|
|
|
17
21
|
### 1. Install via NPM
|
|
18
22
|
```bash
|
|
19
|
-
# Global installation for CLI use
|
|
20
23
|
npm install -g code-graph-llm
|
|
21
|
-
|
|
22
|
-
# Local project dependency
|
|
24
|
+
# OR
|
|
23
25
|
npm install --save-dev code-graph-llm
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
### 2.
|
|
28
|
+
### 2. Core Commands
|
|
27
29
|
```bash
|
|
30
|
+
# Initialize Agent Rules and Reflection files (Scaffolding)
|
|
31
|
+
code-graph init
|
|
32
|
+
|
|
28
33
|
# Generate the llm-code-graph.md map
|
|
29
34
|
code-graph generate
|
|
30
35
|
|
|
36
|
+
# Record a project reflection (Memory)
|
|
37
|
+
code-graph reflect <CATEGORY> "Lesson learned"
|
|
38
|
+
# Example: code-graph reflect ENV "Always use 'cmd /c npm' on Windows."
|
|
39
|
+
|
|
31
40
|
# Start the live watcher for real-time updates
|
|
32
41
|
code-graph watch
|
|
33
42
|
|
|
34
|
-
# Install the Git pre-commit hook
|
|
43
|
+
# Install the Git pre-commit hook (Enforces Map & Memory sync)
|
|
35
44
|
code-graph install-hook
|
|
36
45
|
```
|
|
37
46
|
|
|
38
|
-
## LLM
|
|
47
|
+
## 🧠 LLM Agent Strategy
|
|
48
|
+
|
|
49
|
+
### 1. The Mandatory Protocol
|
|
50
|
+
Instruct your agent to follow the **STRICT AGENT PROTOCOL** in `AGENT_RULES.md`. This ensures the agent:
|
|
51
|
+
1. Reads `PROJECT_REFLECTIONS.md` before starting any task.
|
|
52
|
+
2. Updates reflections after any failure or "learned moment."
|
|
53
|
+
3. Regenerates the project map (`llm-code-graph.md`) after structural changes.
|
|
39
54
|
|
|
40
|
-
### The "Read First" Strategy
|
|
41
|
-
|
|
55
|
+
### 2. The "Read First" Strategy
|
|
56
|
+
The `llm-code-graph.md` file provides a high-level map and structural graph for relational reasoning:
|
|
42
57
|
|
|
43
58
|
**Example Map Entry:**
|
|
44
59
|
```markdown
|
|
45
|
-
- src/auth.js | desc: Handles user authentication.
|
|
60
|
+
- [CORE] src/auth.js (↑3 ↓5) [TODO: Add JWT rotation] | desc: Handles user authentication.
|
|
46
61
|
- syms: [login [ (username, password) ], validateToken [ (token: string) ]]
|
|
47
62
|
|
|
48
63
|
## GRAPH EDGES
|
|
@@ -50,41 +65,12 @@ Instruct your LLM agent to read `llm-code-graph.md` as its first step. The file
|
|
|
50
65
|
[AdminUser] -> [inherits] -> [BaseUser]
|
|
51
66
|
```
|
|
52
67
|
|
|
53
|
-
|
|
54
|
-
> "Before acting, read `llm-code-graph.md`.
|
|
55
|
-
|
|
56
|
-
## Build Phase Integration
|
|
57
|
-
|
|
58
|
-
### 1. Java/Kotlin (Maven/Gradle)
|
|
59
|
-
**Gradle (Groovy):**
|
|
60
|
-
```groovy
|
|
61
|
-
task generateCodeGraph(type: Exec) {
|
|
62
|
-
commandLine 'code-graph', 'generate'
|
|
63
|
-
}
|
|
64
|
-
compileJava.dependsOn generateCodeGraph
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### 2. Python
|
|
68
|
-
**Makefile:**
|
|
69
|
-
```makefile
|
|
70
|
-
map:
|
|
71
|
-
code-graph generate
|
|
72
|
-
test: map
|
|
73
|
-
pytest
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### 3. Rust (build.rs)
|
|
77
|
-
```rust
|
|
78
|
-
use std::process::Command;
|
|
79
|
-
fn main() {
|
|
80
|
-
Command::new("code-graph").arg("generate").status().unwrap();
|
|
81
|
-
}
|
|
82
|
-
```
|
|
68
|
+
### 3. Example System Prompt
|
|
69
|
+
> "Before acting, read `llm-code-graph.md`. Follow the protocol in `AGENT_RULES.md`. If you encounter a bug or an environment quirk, use the `code-graph reflect` tool to record the lesson in `PROJECT_REFLECTIONS.md`."
|
|
83
70
|
|
|
84
71
|
## How it works
|
|
85
|
-
1. **File Scanning:** Recursively walks the directory, ignoring patterns in `.gitignore
|
|
72
|
+
1. **File Scanning:** Recursively walks the directory, ignoring patterns in `.gitignore`.
|
|
86
73
|
2. **Context Extraction:** Scans for classes, functions, and variables while ignoring matches in comments.
|
|
87
|
-
3. **Graph Extraction:** Identifies `imports`, `requires`, `extends`, and `implements
|
|
88
|
-
4. **
|
|
89
|
-
5. **
|
|
90
|
-
6. **Compilation:** Writes a single, minified `llm-code-graph.md` file with a dedicated `## GRAPH EDGES` section.
|
|
74
|
+
3. **Graph Extraction:** Identifies `imports`, `requires`, `extends`, and `implements`.
|
|
75
|
+
4. **Reflection Management:** Deduplicates and persists agent learning into a standardized Markdown format.
|
|
76
|
+
5. **Compilation:** Writes a single, minified `llm-code-graph.md` file with a dedicated `## GRAPH EDGES` section.
|
package/index.js
CHANGED
|
@@ -1,334 +1,393 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @file index.js
|
|
5
|
+
* @description Compact, language-agnostic codebase mapper for LLM token efficiency.
|
|
6
|
+
* Features: Symbol extraction, dependency mapping, reflection logging, and git hooks.
|
|
7
|
+
*/
|
|
8
|
+
|
|
3
9
|
import fs from 'fs';
|
|
10
|
+
import { promises as fsp } from 'fs';
|
|
4
11
|
import path from 'path';
|
|
5
12
|
import { fileURLToPath } from 'url';
|
|
6
13
|
import chokidar from 'chokidar';
|
|
7
14
|
import ignore from 'ignore';
|
|
8
15
|
|
|
16
|
+
// --- Constants & Configuration ---
|
|
17
|
+
|
|
9
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
]
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
19
|
+
export const CONFIG = {
|
|
20
|
+
IGNORE_FILE: '.gitignore',
|
|
21
|
+
MAP_FILE: 'llm-code-graph.md',
|
|
22
|
+
REFLECTIONS_FILE: 'PROJECT_REFLECTIONS.md',
|
|
23
|
+
RULES_FILE: 'AGENT_RULES.md',
|
|
24
|
+
SUPPORTED_EXTENSIONS: [
|
|
25
|
+
'.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.rs', '.java',
|
|
26
|
+
'.cpp', '.c', '.h', '.hpp', '.cc', '.rb', '.php', '.swift',
|
|
27
|
+
'.kt', '.cs', '.dart', '.scala', '.m', '.mm'
|
|
28
|
+
],
|
|
29
|
+
DEFAULT_IGNORES: [
|
|
30
|
+
'.git/', 'node_modules/', 'package-lock.json', '.idea/',
|
|
31
|
+
'build/', 'dist/', 'bin/', 'obj/', '.dart_tool/', '.pub-cache/', '.pub/'
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const SUPPORTED_EXTENSIONS = CONFIG.SUPPORTED_EXTENSIONS;
|
|
36
|
+
|
|
37
|
+
const REGEX = {
|
|
38
|
+
SYMBOLS: [
|
|
39
|
+
// Types, Classes, Interfaces with Inheritance
|
|
40
|
+
/\b(?:class|interface|type|struct|enum|protocol|extension|trait|module|namespace|object)\s+([a-zA-Z_]\w*)(?:[^\n\S]*(?:extends|implements|:|(?:\())[^\n\S]*([a-zA-Z_]\w*(?:[^\n\S]*,\s*[a-zA-Z_]\w*)*)\)?)?/g,
|
|
41
|
+
// Functions
|
|
42
|
+
/\b(?:function|def|fn|func|fun|method|procedure|sub|routine)\s+([a-zA-Z_]\w*)/g,
|
|
43
|
+
// Method/Var Declarations (C-style, Java)
|
|
44
|
+
/\b(?:void|async|public|private|protected|static|final|native|synchronized|abstract|transient|volatile)\s+(?:[\w<>[\]]+\s+)?([a-zA-Z_]\w*)(?=\s*\([^)]*\)\s*(?:\{|=>|;|=))/g,
|
|
45
|
+
// Annotations
|
|
46
|
+
/(@[a-zA-Z_]\w*(?:\([^)]*\))?)\s*(?:(?:public|private|protected|static|final|abstract|class|interface|enum|void|[\w<>[\]]+)\s+)+([a-zA-Z_]\w*)/g,
|
|
47
|
+
// Exports
|
|
48
|
+
/\bexport\s+(?:default\s+)?(?:const|let|var|function|class|type|interface|enum|async|val)\s+([a-zA-Z_]\w*)/g
|
|
49
|
+
],
|
|
50
|
+
EDGES: [
|
|
51
|
+
/\b(?:import|from|include|require|using)\s*(?:[\(\s])\s*['"]?([@\w\.\/\-]+)['"]?/g,
|
|
52
|
+
/#include\s+[<"]([\w\.\/\-]+)[>"]/g
|
|
53
|
+
],
|
|
54
|
+
TAGS: /\b(TODO|FIXME|BUG|DEPRECATED):?\s*(.*)/i,
|
|
55
|
+
KEYWORDS: new Set(['if', 'for', 'while', 'switch', 'return', 'await', 'yield', 'const', 'new', 'let', 'var', 'class', 'void', 'public', 'private', 'protected'])
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// --- Core Services ---
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Handles extraction of symbols, edges, and metadata from source code.
|
|
62
|
+
*/
|
|
63
|
+
class CodeParser {
|
|
64
|
+
static extract(content) {
|
|
65
|
+
const noComments = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
|
|
66
|
+
const cleanContent = noComments.replace(/['"`](?:\\.|[^'"`])*['"`]/g, ''); // Remove strings for symbol extraction
|
|
67
|
+
|
|
68
|
+
const { symbols, inheritance } = this.extractSymbols(content, cleanContent);
|
|
69
|
+
const edges = this.extractEdges(noComments); // Edges need strings (import paths)
|
|
70
|
+
const tags = this.extractTags(content);
|
|
71
|
+
|
|
72
|
+
return { symbols, inheritance, edges, tags };
|
|
54
73
|
}
|
|
55
|
-
if (additionalLines.length > 0) {
|
|
56
|
-
ig.add(additionalLines);
|
|
57
|
-
}
|
|
58
|
-
return ig;
|
|
59
|
-
}
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
regex.lastIndex = 0;
|
|
73
|
-
while ((match = regex.exec(cleanContent)) !== null) {
|
|
74
|
-
if (match[1]) {
|
|
75
|
-
// Handle the Annotation regex separately (it has 2 groups)
|
|
76
|
-
let symbolName = match[1];
|
|
77
|
-
let annotation = '';
|
|
75
|
+
static extractSymbols(original, clean) {
|
|
76
|
+
const symbols = new Set();
|
|
77
|
+
const inheritance = [];
|
|
78
|
+
|
|
79
|
+
for (const regex of REGEX.SYMBOLS) {
|
|
80
|
+
let match;
|
|
81
|
+
regex.lastIndex = 0;
|
|
82
|
+
while ((match = regex.exec(clean)) !== null) {
|
|
83
|
+
let name = match[1];
|
|
84
|
+
let annotation = name.startsWith('@') ? name : '';
|
|
85
|
+
if (annotation) name = match[2] || '';
|
|
78
86
|
|
|
79
|
-
if (
|
|
80
|
-
annotation = symbolName;
|
|
81
|
-
symbolName = match[2] || '';
|
|
82
|
-
if (!symbolName) continue;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (['if', 'for', 'while', 'switch', 'return', 'await', 'yield', 'const', 'new', 'let', 'var', 'class', 'void', 'public', 'private', 'protected'].includes(symbolName)) continue;
|
|
87
|
+
if (!name || REGEX.KEYWORDS.has(name)) continue;
|
|
86
88
|
|
|
87
|
-
// Capture inheritance if present (match[2] only for non-annotation regex)
|
|
88
89
|
if (!annotation && match[2]) {
|
|
89
|
-
|
|
90
|
-
parents.forEach(parent => {
|
|
91
|
-
inheritance.push({ child: symbolName, parent });
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// To find the comment, we need to find the position in the ORIGINAL content
|
|
96
|
-
const escapedName = symbolName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
97
|
-
const posRegex = new RegExp(`\\b${escapedName}\\b`, 'g');
|
|
98
|
-
let posMatch = posRegex.exec(content);
|
|
99
|
-
if (!posMatch) continue;
|
|
100
|
-
|
|
101
|
-
const linesBefore = content.substring(0, posMatch.index).split('\n');
|
|
102
|
-
let comment = '';
|
|
103
|
-
// Skip the current line where the symbol is defined
|
|
104
|
-
for (let i = linesBefore.length - 2; i >= 0; i--) {
|
|
105
|
-
const line = linesBefore[i].trim();
|
|
106
|
-
if (line.startsWith('//') || line.startsWith('*') || line.startsWith('"""') || line.startsWith('#') || line.startsWith('/*')) {
|
|
107
|
-
const clean = line.replace(/[\/*#"]/g, '').trim();
|
|
108
|
-
if (clean) comment = clean + (comment ? ' ' + comment : '');
|
|
109
|
-
if (comment.length > 100) break;
|
|
110
|
-
} else if (line === '' && comment === '') continue;
|
|
111
|
-
else if (line.startsWith('@')) continue; // Skip annotations in comment search
|
|
112
|
-
else break;
|
|
90
|
+
match[2].split(',').forEach(p => inheritance.push({ child: name, parent: p.trim() }));
|
|
113
91
|
}
|
|
114
92
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const sigMatch = remainingLine.match(/^\s*(\([^)]*\)|[^\n{;]*)/);
|
|
119
|
-
if (sigMatch && sigMatch[1].trim()) {
|
|
120
|
-
context = sigMatch[1].trim();
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const displaySymbol = annotation ? `${annotation} ${symbolName}` : symbolName;
|
|
125
|
-
symbols.push(context ? `${displaySymbol} [${context}]` : displaySymbol);
|
|
93
|
+
const context = this.findSymbolContext(original, name);
|
|
94
|
+
const display = annotation ? `${annotation} ${name}` : name;
|
|
95
|
+
symbols.add(context ? `${display} [${context}]` : display);
|
|
126
96
|
}
|
|
127
97
|
}
|
|
98
|
+
return { symbols: Array.from(symbols).sort(), inheritance };
|
|
128
99
|
}
|
|
129
|
-
return {
|
|
130
|
-
symbols: Array.from(new Set(symbols)).sort(),
|
|
131
|
-
inheritance: Array.from(new Set(inheritance.map(JSON.stringify))).map(JSON.parse)
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
100
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
|
|
101
|
+
static findSymbolContext(content, name) {
|
|
102
|
+
const pos = content.search(new RegExp(`\\b${name}\\b`));
|
|
103
|
+
if (pos === -1) return '';
|
|
104
|
+
|
|
105
|
+
const linesBefore = content.substring(0, pos).split('\n');
|
|
106
|
+
let comment = '';
|
|
107
|
+
for (let i = linesBefore.length - 2; i >= 0; i--) {
|
|
108
|
+
const line = linesBefore[i].trim();
|
|
109
|
+
if (/^(\/\/|\*|"""|#|\/\*)/.test(line)) {
|
|
110
|
+
const clean = line.replace(/[\/*#"]/g, '').trim();
|
|
111
|
+
if (clean) comment = clean + (comment ? ' ' + comment : '');
|
|
112
|
+
if (comment.length > 100) break;
|
|
113
|
+
} else if (line !== '' && !line.startsWith('@')) break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!comment) {
|
|
117
|
+
const remaining = content.substring(pos + name.length);
|
|
118
|
+
const sigMatch = remaining.match(/^\s*(\([^)]*\)|[^\n{;]*)/);
|
|
119
|
+
return sigMatch?.[1].trim() || '';
|
|
120
|
+
}
|
|
121
|
+
return comment;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static extractEdges(clean) {
|
|
125
|
+
const deps = new Set();
|
|
126
|
+
for (const regex of REGEX.EDGES) {
|
|
127
|
+
let match;
|
|
128
|
+
regex.lastIndex = 0;
|
|
129
|
+
while ((match = regex.exec(clean)) !== null) {
|
|
130
|
+
if (match[1] && match[1].length > 1) deps.add(match[1]);
|
|
148
131
|
}
|
|
149
132
|
}
|
|
133
|
+
return Array.from(deps).sort();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static extractTags(content) {
|
|
137
|
+
const tags = new Set();
|
|
138
|
+
content.split('\n').forEach(line => {
|
|
139
|
+
const match = line.match(REGEX.TAGS);
|
|
140
|
+
if (match) tags.add(`${match[1]}: ${match[2].substring(0, 50).trim()}`);
|
|
141
|
+
});
|
|
142
|
+
return Array.from(tags);
|
|
150
143
|
}
|
|
151
|
-
return Array.from(dependencies).sort();
|
|
152
144
|
}
|
|
153
145
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
localIg = ignore().add(ig).add(lines);
|
|
146
|
+
/**
|
|
147
|
+
* Manages the project mapping and file generation.
|
|
148
|
+
*/
|
|
149
|
+
class ProjectMapper {
|
|
150
|
+
constructor(cwd) {
|
|
151
|
+
this.cwd = cwd;
|
|
152
|
+
this.files = [];
|
|
153
|
+
this.allEdges = [];
|
|
154
|
+
this.incomingEdges = {};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
getIgnores(dir, baseIg) {
|
|
158
|
+
const ig = ignore().add(baseIg);
|
|
159
|
+
const ignorePath = path.join(dir, CONFIG.IGNORE_FILE);
|
|
160
|
+
if (fs.existsSync(ignorePath)) {
|
|
161
|
+
ig.add(fs.readFileSync(ignorePath, 'utf8'));
|
|
171
162
|
}
|
|
163
|
+
return ig;
|
|
164
|
+
}
|
|
172
165
|
|
|
173
|
-
|
|
166
|
+
async walk(dir, ig) {
|
|
167
|
+
const entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
168
|
+
|
|
174
169
|
for (const entry of entries) {
|
|
175
170
|
const fullPath = path.join(dir, entry.name);
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (!fileDesc.trim() && symbols.length > 0) fileDesc = `Contains ${symbols.length} symbols.`;
|
|
217
|
-
|
|
218
|
-
// Identify core entry points
|
|
219
|
-
const isCore = /^(index|main|app|server|cli)\.(js|ts|py|go|rs|java|cpp)$/i.test(entry.name);
|
|
220
|
-
|
|
221
|
-
const fileObj = {
|
|
222
|
-
path: normalizedPath,
|
|
223
|
-
desc: fileDesc.trim(),
|
|
224
|
-
symbols,
|
|
225
|
-
tags: Array.from(new Set(tags)),
|
|
226
|
-
isCore,
|
|
227
|
-
outCount: dependencies.length
|
|
228
|
-
};
|
|
229
|
-
files.push(fileObj);
|
|
230
|
-
|
|
231
|
-
// Collect Edges and track incoming
|
|
232
|
-
dependencies.forEach(dep => {
|
|
233
|
-
let target = dep;
|
|
234
|
-
if (dep.startsWith('.')) {
|
|
235
|
-
// Resolve relative path to project-relative
|
|
236
|
-
const resolved = path.normalize(path.join(path.dirname(normalizedPath), dep));
|
|
237
|
-
target = resolved.replace(/\\/g, '/');
|
|
238
|
-
// Basic extension guesser
|
|
239
|
-
if (!path.extname(target)) {
|
|
240
|
-
for (const ex of SUPPORTED_EXTENSIONS) {
|
|
241
|
-
if (fs.existsSync(path.join(cwd, target + ex))) {
|
|
242
|
-
target += ex;
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
allEdges.push(`[${normalizedPath}] -> [imports] -> [${target}]`);
|
|
249
|
-
incomingEdges[target] = (incomingEdges[target] || 0) + 1;
|
|
250
|
-
});
|
|
251
|
-
inheritance.forEach(inh => {
|
|
252
|
-
allEdges.push(`[${inh.child}] -> [inherits] -> [${inh.parent}]`);
|
|
253
|
-
});
|
|
254
|
-
}
|
|
171
|
+
const relPath = path.relative(this.cwd, fullPath).replace(/\\/g, '/');
|
|
172
|
+
const checkPath = entry.isDirectory() ? `${relPath}/` : relPath;
|
|
173
|
+
|
|
174
|
+
if (ig.ignores(checkPath)) continue;
|
|
175
|
+
|
|
176
|
+
if (entry.isDirectory()) {
|
|
177
|
+
await this.walk(fullPath, this.getIgnores(fullPath, ig));
|
|
178
|
+
} else if (CONFIG.SUPPORTED_EXTENSIONS.includes(path.extname(entry.name))) {
|
|
179
|
+
await this.processFile(fullPath, relPath);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async processFile(fullPath, relPath) {
|
|
185
|
+
const content = await fsp.readFile(fullPath, 'utf8');
|
|
186
|
+
const { symbols, inheritance, edges, tags } = CodeParser.extract(content);
|
|
187
|
+
|
|
188
|
+
const isCore = /^(index|main|app|server|cli)\./i.test(path.basename(relPath));
|
|
189
|
+
const fileObj = { path: relPath, symbols, tags, isCore, outCount: edges.length, desc: this.extractFileDesc(content, symbols.length) };
|
|
190
|
+
|
|
191
|
+
this.files.push(fileObj);
|
|
192
|
+
this.processEdges(relPath, edges, inheritance);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
extractFileDesc(content, symCount) {
|
|
196
|
+
const lines = content.split('\n').slice(0, 10);
|
|
197
|
+
let desc = '';
|
|
198
|
+
for (const line of lines) {
|
|
199
|
+
if (/^(\/\/|#|\/\*)/.test(line.trim())) desc += line.replace(/[\/*#]/g, '').trim() + ' ';
|
|
200
|
+
else if (desc) break;
|
|
201
|
+
}
|
|
202
|
+
return desc.trim() || (symCount > 0 ? `Contains ${symCount} symbols.` : '');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
processEdges(relPath, edges, inheritance) {
|
|
206
|
+
edges.forEach(dep => {
|
|
207
|
+
let target = dep;
|
|
208
|
+
if (dep.startsWith('.')) {
|
|
209
|
+
const resolved = path.normalize(path.join(path.dirname(relPath), dep)).replace(/\\/g, '/');
|
|
210
|
+
target = this.resolveExtension(resolved);
|
|
255
211
|
}
|
|
212
|
+
this.allEdges.push(`[${relPath}] -> [imports] -> [${target}]`);
|
|
213
|
+
this.incomingEdges[target] = (this.incomingEdges[target] || 0) + 1;
|
|
214
|
+
});
|
|
215
|
+
inheritance.forEach(inh => this.allEdges.push(`[${inh.child}] -> [inherits] -> [${inh.parent}]`));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
resolveExtension(target) {
|
|
219
|
+
if (path.extname(target)) return target;
|
|
220
|
+
for (const ext of CONFIG.SUPPORTED_EXTENSIONS) {
|
|
221
|
+
if (fs.existsSync(path.join(this.cwd, target + ext))) return target + ext;
|
|
256
222
|
}
|
|
223
|
+
return target;
|
|
257
224
|
}
|
|
258
225
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const edgesOutput = allEdges.length > 0
|
|
281
|
-
? `\n\n## GRAPH EDGES\n${Array.from(new Set(allEdges)).sort().join('\n')}`
|
|
282
|
-
: '';
|
|
283
|
-
|
|
284
|
-
const header = `# CODE_GRAPH_MAP\n> LLM_ONLY: DO NOT EDIT. COMPACT PROJECT MAP.\n> Legend: [CORE] Entry Point, (↑N) Outgoing Deps, (↓M) Incoming Dependents\n> Notation: syms: [Name [Signature/Context]], desc: File Summary, [TAG: Context] Actionable items\n\n`;
|
|
285
|
-
fs.writeFileSync(path.join(cwd, DEFAULT_MAP_FILE), header + nodesOutput + edgesOutput);
|
|
286
|
-
console.log(`[Code-Graph] Updated ${DEFAULT_MAP_FILE}`);
|
|
226
|
+
async generate() {
|
|
227
|
+
console.log(`[Code-Graph] Mapping ${this.cwd}...`);
|
|
228
|
+
await this.walk(this.cwd, this.getIgnores(this.cwd, CONFIG.DEFAULT_IGNORES));
|
|
229
|
+
|
|
230
|
+
this.files.sort((a, b) => (b.isCore - a.isCore) || ((this.incomingEdges[b.path] || 0) - (this.incomingEdges[a.path] || 0)));
|
|
231
|
+
|
|
232
|
+
const output = this.formatOutput();
|
|
233
|
+
await fsp.writeFile(path.join(this.cwd, CONFIG.MAP_FILE), output);
|
|
234
|
+
console.log(`[Code-Graph] Updated ${CONFIG.MAP_FILE}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
formatOutput() {
|
|
238
|
+
const header = `# CODE_GRAPH_MAP\n> MISSION: COMPACT PROJECT MAP FOR LLM AGENTS.\n> PROTOCOL: Follow AGENT_RULES.md | MEMORY: See PROJECT_REFLECTIONS.md\n> Legend: [CORE] Entry Point, (↑N) Outgoing Deps, (↓M) Incoming Dependents\n> Notation: syms: [Name [Signature/Context]], desc: File Summary, [TAG: Context]\n\n`;
|
|
239
|
+
const nodes = this.files.map(f => {
|
|
240
|
+
const inCount = this.incomingEdges[f.path] || 0;
|
|
241
|
+
const tags = f.tags.length ? ` [${f.tags.join(', ')}]` : '';
|
|
242
|
+
return `- ${f.isCore ? '[CORE] ' : ''}${f.path} (↑${f.outCount} ↓${inCount})${tags} | desc: ${f.desc.substring(0, 100)}\n - syms: [${f.symbols.join(', ')}]`;
|
|
243
|
+
}).join('\n');
|
|
244
|
+
const edges = this.allEdges.length ? `\n\n## GRAPH EDGES\n${Array.from(new Set(this.allEdges)).sort().join('\n')}` : '';
|
|
245
|
+
return header + nodes + edges;
|
|
246
|
+
}
|
|
287
247
|
}
|
|
288
248
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
249
|
+
/**
|
|
250
|
+
* Manages project reflections and lessons learned.
|
|
251
|
+
*/
|
|
252
|
+
class ReflectionManager {
|
|
253
|
+
static async add(category, lesson) {
|
|
254
|
+
if (!lesson) return console.error('[Code-Graph] Usage: reflect <cat> <lesson>');
|
|
255
|
+
|
|
256
|
+
const filePath = path.join(process.cwd(), CONFIG.REFLECTIONS_FILE);
|
|
257
|
+
const header = `# PROJECT_REFLECTIONS & LESSONS LEARNED\n> LLM AGENT MEMORY: READ BEFORE STARTING TASKS. UPDATE ON FAILURES.\n`;
|
|
258
|
+
const entry = `- [${category.toUpperCase()}: ${new Date().toISOString().split('T')[0]}] ${lesson}`;
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
let content = fs.existsSync(filePath) ? await fsp.readFile(filePath, 'utf8') : header;
|
|
262
|
+
if (!fs.existsSync(filePath)) await fsp.writeFile(filePath, header);
|
|
263
|
+
|
|
264
|
+
if (content.toLowerCase().includes(lesson.toLowerCase().trim())) {
|
|
265
|
+
return console.log('[Code-Graph] Reflection already exists.');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
await fsp.appendFile(filePath, `\n${entry}`);
|
|
269
|
+
console.log(`[Code-Graph] Recorded reflection: ${lesson}`);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
console.error(`[Code-Graph] Reflection failed: ${err.message}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Scaffolds the initial agent-agnostic rule and reflection files.
|
|
278
|
+
*/
|
|
279
|
+
class ProjectInitializer {
|
|
280
|
+
static async init(cwd) {
|
|
281
|
+
const rulesPath = path.join(cwd, CONFIG.RULES_FILE);
|
|
282
|
+
const reflectPath = path.join(cwd, CONFIG.REFLECTIONS_FILE);
|
|
283
|
+
|
|
284
|
+
const rulesContent = `# AGENT OPERATIONAL PROTOCOL (STRICT)
|
|
285
|
+
> This protocol is MANDATORY for all LLM agents. Failure to update memory is a failure of the task.
|
|
286
|
+
|
|
287
|
+
## 🧠 THE REFLECTION CYCLE
|
|
288
|
+
1. **PRE-TASK:** Read \`${CONFIG.REFLECTIONS_FILE}\` to identify existing pitfalls.
|
|
289
|
+
2. **EXECUTION:** Monitor for "Learned Moments" (failures, corrections, or discoveries).
|
|
290
|
+
3. **POST-TASK:** Immediately run \`code-graph reflect <CAT> <LESSON>\` for any discovery.
|
|
291
|
+
|
|
292
|
+
## 📝 REFLECTION CATEGORIES
|
|
293
|
+
- \`LOGIC\`: Code bugs or complex regex pitfalls.
|
|
294
|
+
- \`ENV\`: OS compatibility or shell behaviors.
|
|
295
|
+
- \`DEP\`: Library bugs or version deprecations.
|
|
296
|
+
- \`STYLE\`: Project-specific architectural rules.
|
|
297
|
+
`;
|
|
298
|
+
|
|
299
|
+
const reflectContent = `# PROJECT_REFLECTIONS & LESSONS LEARNED\n> LLM AGENT MEMORY: READ BEFORE STARTING TASKS. UPDATE ON FAILURES.\n`;
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
if (!fs.existsSync(rulesPath)) {
|
|
303
|
+
await fsp.writeFile(rulesPath, rulesContent);
|
|
304
|
+
console.log(`[Code-Graph] Initialized ${CONFIG.RULES_FILE}`);
|
|
305
|
+
}
|
|
306
|
+
if (!fs.existsSync(reflectPath)) {
|
|
307
|
+
await fsp.writeFile(reflectPath, reflectContent);
|
|
308
|
+
console.log(`[Code-Graph] Initialized ${CONFIG.REFLECTIONS_FILE}`);
|
|
309
|
+
}
|
|
310
|
+
} catch (err) {
|
|
311
|
+
console.error(`[Code-Graph] Initialization failed: ${err.message}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
313
314
|
}
|
|
314
315
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
316
|
+
// --- CLI Entry Point ---
|
|
317
|
+
|
|
318
|
+
async function main() {
|
|
319
|
+
const [command, ...args] = process.argv.slice(2);
|
|
320
|
+
const cwd = process.cwd();
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
switch (command || 'generate') {
|
|
324
|
+
case 'generate':
|
|
325
|
+
await new ProjectMapper(cwd).generate();
|
|
326
|
+
break;
|
|
327
|
+
case 'init':
|
|
328
|
+
await ProjectInitializer.init(cwd);
|
|
329
|
+
break;
|
|
330
|
+
case 'reflect':
|
|
331
|
+
await ReflectionManager.add(args[0], args.slice(1).join(' '));
|
|
332
|
+
break;
|
|
333
|
+
case 'install-hook':
|
|
334
|
+
await ProjectInitializer.init(cwd);
|
|
335
|
+
await installGitHook(cwd);
|
|
336
|
+
break;
|
|
337
|
+
case 'watch':
|
|
338
|
+
startWatcher(cwd);
|
|
339
|
+
break;
|
|
340
|
+
default:
|
|
341
|
+
console.log('Usage: code-graph [generate|init|reflect|install-hook|watch]');
|
|
342
|
+
}
|
|
343
|
+
} catch (err) {
|
|
344
|
+
console.error(`[Code-Graph] Critical Error: ${err.message}`);
|
|
345
|
+
process.exit(1);
|
|
320
346
|
}
|
|
321
|
-
const hookPath = path.join(hooksDir, 'pre-commit');
|
|
322
|
-
const hookContent = `#!/bin/sh\n# Code-Graph pre-commit hook\nnode "${__filename}" generate\ngit add "${DEFAULT_MAP_FILE}"\n`;
|
|
323
|
-
fs.writeFileSync(hookPath, hookContent, { mode: 0o755 });
|
|
324
|
-
console.log('[Code-Graph] Installed pre-commit hook.');
|
|
325
347
|
}
|
|
326
348
|
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
349
|
+
async function installGitHook(cwd) {
|
|
350
|
+
const hookPath = path.join(cwd, '.git', 'hooks', 'pre-commit');
|
|
351
|
+
if (!fs.existsSync(path.dirname(hookPath))) return console.error('[Code-Graph] No .git directory found.');
|
|
352
|
+
|
|
353
|
+
const content = `#!/bin/sh
|
|
354
|
+
# Code-Graph Advisory: Map Sync & Reflection Reminder
|
|
355
|
+
echo "[Code-Graph] Validating commit..."
|
|
356
|
+
|
|
357
|
+
# 1. Regenerate Map
|
|
358
|
+
node "${__filename}" generate
|
|
359
|
+
git add "${CONFIG.MAP_FILE}"
|
|
360
|
+
|
|
361
|
+
# 2. Reflection Advisory
|
|
362
|
+
# Notify if code changed but reflections didn't (Soft Nudge)
|
|
363
|
+
CODE_CHANGES=$(git diff --cached --name-only | grep -E "\\.(${CONFIG.SUPPORTED_EXTENSIONS.map(e => e.slice(1)).join('|')})$")
|
|
364
|
+
REFLECT_CHANGES=$(git diff --cached --name-only | grep "${CONFIG.REFLECTIONS_FILE}")
|
|
365
|
+
|
|
366
|
+
if [ ! -z "$CODE_CHANGES" ] && [ -z "$REFLECT_CHANGES" ]; then
|
|
367
|
+
echo "--------------------------------------------------------"
|
|
368
|
+
echo "ℹ️ [Code-Graph] ADVISORY: Reflection Check"
|
|
369
|
+
echo "Significant code changes detected without a reflection."
|
|
370
|
+
echo "If you learned something new or fixed a non-obvious bug,"
|
|
371
|
+
echo "run 'code-graph reflect LOGIC <lesson>' before committing."
|
|
372
|
+
echo "--------------------------------------------------------"
|
|
373
|
+
fi
|
|
374
|
+
`;
|
|
375
|
+
await fsp.writeFile(hookPath, content, { mode: 0o755 });
|
|
376
|
+
console.log('[Code-Graph] Pre-commit Advisory installed (Soft Enforcement).');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function startWatcher(cwd) {
|
|
380
|
+
console.log(`[Code-Graph] Watching ${cwd}...`);
|
|
381
|
+
let timer;
|
|
382
|
+
chokidar.watch(cwd, { ignoreInitial: true, ignored: [/node_modules/, /\.git/, new RegExp(CONFIG.MAP_FILE)] })
|
|
383
|
+
.on('all', () => {
|
|
384
|
+
clearTimeout(timer);
|
|
385
|
+
timer = setTimeout(() => new ProjectMapper(cwd).generate(), 1000);
|
|
386
|
+
});
|
|
334
387
|
}
|
|
388
|
+
|
|
389
|
+
if (process.argv[1] && (process.argv[1] === __filename || process.argv[1].endsWith('index.js'))) {
|
|
390
|
+
main();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export { CodeParser, ProjectMapper, ReflectionManager };
|
package/llm-code-graph.md
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
# CODE_GRAPH_MAP
|
|
2
|
-
>
|
|
2
|
+
> MISSION: COMPACT PROJECT MAP FOR LLM AGENTS.
|
|
3
|
+
> PROTOCOL: Follow AGENT_RULES.md | MEMORY: See PROJECT_REFLECTIONS.md
|
|
3
4
|
> Legend: [CORE] Entry Point, (↑N) Outgoing Deps, (↓M) Incoming Dependents
|
|
4
|
-
> Notation: syms: [Name [Signature/Context]], desc: File Summary, [TAG: Context]
|
|
5
|
+
> Notation: syms: [Name [Signature/Context]], desc: File Summary, [TAG: Context]
|
|
5
6
|
|
|
6
|
-
- [CORE] index.js (↑
|
|
7
|
-
- syms: [
|
|
8
|
-
- test/index.test.js (↑10 ↓0)
|
|
7
|
+
- [CORE] index.js (↑6 ↓1) [TODO: |FIXME|BUG|DEPRECATED):?\s*(.*)/i,, bug: s or complex regex pitfalls., bug: s or version deprecations., bug: ,"] | desc: !usrbinenv node
|
|
8
|
+
- syms: [CONFIG [=], CodeParser [--- Core Services --- Handles extraction of symbols, edges, and metadata from source code.], ProjectInitializer [Scaffolds the initial agent-agnostic rule and reflection files.], ProjectMapper [Manages the project mapping and file generation.], ReflectionManager [Manages project reflections and lessons learned.], SUPPORTED_EXTENSIONS [: [], add [(context ? `${display} [${context}]` : display)], extract [(content)], init [(cwd)], installGitHook [(cwd)], main [|app|server|cli)\./i.test(path.basename(relPath))], processFile [(fullPath, relPath)], walk [(dir, ig)]]
|
|
9
|
+
- [CORE] test/index.test.js (↑10 ↓0) | desc:
|
|
10
|
+
- syms: []
|
|
9
11
|
|
|
10
12
|
## GRAPH EDGES
|
|
11
13
|
[index.js] -> [imports] -> [chokidar]
|
|
12
14
|
[index.js] -> [imports] -> [fs]
|
|
13
15
|
[index.js] -> [imports] -> [ignore]
|
|
14
16
|
[index.js] -> [imports] -> [path]
|
|
17
|
+
[index.js] -> [imports] -> [symbols]
|
|
15
18
|
[index.js] -> [imports] -> [url]
|
|
16
19
|
[test/index.test.js] -> [imports] -> [assert]
|
|
17
20
|
[test/index.test.js] -> [imports] -> [fs]
|
package/package.json
CHANGED
package/test/index.test.js
CHANGED
|
@@ -4,11 +4,11 @@ import fs from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
CodeParser,
|
|
8
|
+
ProjectMapper,
|
|
9
|
+
ReflectionManager,
|
|
10
10
|
SUPPORTED_EXTENSIONS,
|
|
11
|
-
|
|
11
|
+
CONFIG
|
|
12
12
|
} from '../index.js';
|
|
13
13
|
|
|
14
14
|
test('extractSymbols - JS/TS Docstrings', () => {
|
|
@@ -18,7 +18,7 @@ test('extractSymbols - JS/TS Docstrings', () => {
|
|
|
18
18
|
*/
|
|
19
19
|
function testFunc(a, b) {}
|
|
20
20
|
`;
|
|
21
|
-
const { symbols } =
|
|
21
|
+
const { symbols } = CodeParser.extract(code);
|
|
22
22
|
assert.ok(symbols.some(s => s.includes('testFunc') && s.includes('This is a test function')));
|
|
23
23
|
});
|
|
24
24
|
|
|
@@ -28,8 +28,7 @@ test('extractSymbols - Signature Fallback', () => {
|
|
|
28
28
|
return true;
|
|
29
29
|
}
|
|
30
30
|
`;
|
|
31
|
-
const { symbols } =
|
|
32
|
-
// Matches "noDocFunc [ (arg1: string, arg2: number)]"
|
|
31
|
+
const { symbols } = CodeParser.extract(code);
|
|
33
32
|
assert.ok(symbols.some(s => s.includes('noDocFunc') && s.includes('arg1: string, arg2: number')));
|
|
34
33
|
});
|
|
35
34
|
|
|
@@ -38,7 +37,7 @@ test('extractSymbols - Flutter/Dart Noise Reduction', () => {
|
|
|
38
37
|
const SizedBox(height: 10);
|
|
39
38
|
void realFunction() {}
|
|
40
39
|
`;
|
|
41
|
-
const { symbols } =
|
|
40
|
+
const { symbols } = CodeParser.extract(code);
|
|
42
41
|
assert.ok(symbols.some(s => s.includes('realFunction')));
|
|
43
42
|
assert.ok(!symbols.some(s => s.includes('SizedBox')));
|
|
44
43
|
});
|
|
@@ -49,7 +48,7 @@ test('extractInheritance - Class relationships', () => {
|
|
|
49
48
|
interface IRepository implements IBase {}
|
|
50
49
|
class MyWidget : StatelessWidget {}
|
|
51
50
|
`;
|
|
52
|
-
const { inheritance } =
|
|
51
|
+
const { inheritance } = CodeParser.extract(code);
|
|
53
52
|
assert.ok(inheritance.some(i => i.child === 'AdminUser' && i.parent === 'BaseUser'));
|
|
54
53
|
assert.ok(inheritance.some(i => i.child === 'IRepository' && i.parent === 'IBase'));
|
|
55
54
|
assert.ok(inheritance.some(i => i.child === 'MyWidget' && i.parent === 'StatelessWidget'));
|
|
@@ -61,7 +60,7 @@ test('extractEdges - Imports and includes', () => {
|
|
|
61
60
|
const other = require('other-module');
|
|
62
61
|
#include "header.h"
|
|
63
62
|
`;
|
|
64
|
-
const edges =
|
|
63
|
+
const { edges } = CodeParser.extract(code);
|
|
65
64
|
assert.ok(edges.includes('./local-file'));
|
|
66
65
|
assert.ok(edges.includes('other-module'));
|
|
67
66
|
assert.ok(edges.includes('header.h'));
|
|
@@ -75,20 +74,57 @@ test('extractSymbols - Java/Spring Annotations', () => {
|
|
|
75
74
|
public String hello() { return "hi"; }
|
|
76
75
|
}
|
|
77
76
|
`;
|
|
78
|
-
const { symbols } =
|
|
77
|
+
const { symbols } = CodeParser.extract(code);
|
|
79
78
|
assert.ok(symbols.some(s => s.includes('@RestController MyController')));
|
|
80
|
-
// Note: Strings are stripped during extraction to avoid false positives
|
|
81
79
|
assert.ok(symbols.some(s => s.includes('@GetMapping() hello')));
|
|
82
80
|
});
|
|
83
81
|
|
|
84
82
|
test('getIgnores - Default Patterns', () => {
|
|
85
|
-
const
|
|
83
|
+
const mapper = new ProjectMapper(process.cwd());
|
|
84
|
+
const ig = mapper.getIgnores(process.cwd(), CONFIG.DEFAULT_IGNORES);
|
|
86
85
|
assert.strictEqual(ig.ignores('.git/'), true);
|
|
87
86
|
assert.strictEqual(ig.ignores('node_modules/'), true);
|
|
88
87
|
assert.strictEqual(ig.ignores('.idea/'), true);
|
|
89
88
|
assert.strictEqual(ig.ignores('.dart_tool/'), true);
|
|
90
89
|
});
|
|
91
90
|
|
|
91
|
+
test('ReflectionManager - Add and Deduplicate', async () => {
|
|
92
|
+
const tempReflectFile = path.join(process.cwd(), CONFIG.REFLECTIONS_FILE);
|
|
93
|
+
const backupExists = fs.existsSync(tempReflectFile);
|
|
94
|
+
let backupContent = '';
|
|
95
|
+
if (backupExists) backupContent = fs.readFileSync(tempReflectFile, 'utf8');
|
|
96
|
+
|
|
97
|
+
// Test Initial Add
|
|
98
|
+
const lesson = "Unique test lesson for reflection";
|
|
99
|
+
await ReflectionManager.add('TEST', lesson);
|
|
100
|
+
const content = fs.readFileSync(tempReflectFile, 'utf8');
|
|
101
|
+
assert.ok(content.includes(lesson));
|
|
102
|
+
|
|
103
|
+
// Test Deduplication
|
|
104
|
+
const logSpy = [];
|
|
105
|
+
const originalLog = console.log;
|
|
106
|
+
console.log = (msg) => logSpy.push(msg);
|
|
107
|
+
|
|
108
|
+
await ReflectionManager.add('TEST', lesson);
|
|
109
|
+
|
|
110
|
+
console.log = originalLog;
|
|
111
|
+
assert.ok(logSpy.includes('[Code-Graph] Reflection already exists.'));
|
|
112
|
+
|
|
113
|
+
// Restore
|
|
114
|
+
if (backupExists) fs.writeFileSync(tempReflectFile, backupContent);
|
|
115
|
+
else fs.unlinkSync(tempReflectFile);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('ProjectMapper - Format Output Header', () => {
|
|
119
|
+
const mapper = new ProjectMapper(process.cwd());
|
|
120
|
+
mapper.files = [{ path: 'test.js', symbols: [], tags: [], isCore: true, outCount: 0, desc: 'test' }];
|
|
121
|
+
const output = mapper.formatOutput();
|
|
122
|
+
|
|
123
|
+
assert.ok(output.includes('MISSION: COMPACT PROJECT MAP FOR LLM AGENTS.'));
|
|
124
|
+
assert.ok(output.includes('PROTOCOL: Follow AGENT_RULES.md'));
|
|
125
|
+
assert.ok(output.includes('MEMORY: See PROJECT_REFLECTIONS.md'));
|
|
126
|
+
});
|
|
127
|
+
|
|
92
128
|
test('Recursive Ignore Simulation (Logic Check)', async () => {
|
|
93
129
|
const tempDir = path.join(process.cwd(), 'temp_test_dir');
|
|
94
130
|
if (fs.existsSync(tempDir)) fs.rmSync(tempDir, { recursive: true });
|
|
@@ -97,14 +133,13 @@ test('Recursive Ignore Simulation (Logic Check)', async () => {
|
|
|
97
133
|
const subDir = path.join(tempDir, 'subdir');
|
|
98
134
|
fs.mkdirSync(subDir);
|
|
99
135
|
|
|
100
|
-
// Create a file that should be ignored by subdir/.gitignore
|
|
101
136
|
fs.writeFileSync(path.join(subDir, 'ignored.js'), 'function ignored() {}');
|
|
102
137
|
fs.writeFileSync(path.join(subDir, 'included.js'), 'function included() {}');
|
|
103
138
|
fs.writeFileSync(path.join(subDir, '.gitignore'), 'ignored.js');
|
|
104
139
|
|
|
105
|
-
await
|
|
140
|
+
await new ProjectMapper(tempDir).generate();
|
|
106
141
|
|
|
107
|
-
const mapPath = path.join(tempDir,
|
|
142
|
+
const mapPath = path.join(tempDir, CONFIG.MAP_FILE);
|
|
108
143
|
const mapContent = fs.readFileSync(mapPath, 'utf8');
|
|
109
144
|
|
|
110
145
|
assert.ok(mapContent.includes('included.js'));
|