agdi 2.5.1 → 2.6.1
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/README.md +184 -23
- package/dist/chunk-NVMD3WL3.js +349 -0
- package/dist/index.js +533 -207
- package/dist/rag-6HO4ZLBN.js +16 -0
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
[](https://nodejs.org/)
|
|
20
20
|
[](https://www.typescriptlang.org/)
|
|
21
21
|
|
|
22
|
-
**Build full-stack apps from natural language •
|
|
22
|
+
**Build full-stack apps from natural language • Multi-Agent AI • Codebase RAG • Enterprise Security**
|
|
23
23
|
|
|
24
24
|
[Website](https://agdi-dev.vercel.app) · [npm](https://www.npmjs.com/package/agdi) · [GitHub](https://github.com/anassagd432/Agdi-dev)
|
|
25
25
|
|
|
@@ -27,6 +27,71 @@
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
+
## 🚀 What's New in v2.6
|
|
31
|
+
|
|
32
|
+
<table>
|
|
33
|
+
<tr>
|
|
34
|
+
<td width="50%">
|
|
35
|
+
|
|
36
|
+
### 🤖 Multi-Agent System
|
|
37
|
+
Planner → Coder → Reviewer pipeline. Your code is reviewed by AI before delivery.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
→ /agent
|
|
41
|
+
🤖 Multi-agent mode: ON
|
|
42
|
+
Planner → Coder → Reviewer pipeline enabled
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
</td>
|
|
46
|
+
<td width="50%">
|
|
47
|
+
|
|
48
|
+
### 🔍 Codebase RAG
|
|
49
|
+
Index and search your entire project semantically. The AI understands your code.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
→ /index
|
|
53
|
+
✓ Indexed 47 files, 312 chunks
|
|
54
|
+
|
|
55
|
+
→ /search authentication
|
|
56
|
+
🔍 src/auth/login.ts:15 (0.87)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
</td>
|
|
60
|
+
</tr>
|
|
61
|
+
<tr>
|
|
62
|
+
<td width="50%">
|
|
63
|
+
|
|
64
|
+
### 🔧 MCP Tool Integration
|
|
65
|
+
Model Context Protocol for standardized AI tool calls.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
→ /tools
|
|
69
|
+
🔧 Available Tools
|
|
70
|
+
read_file [filesystem]
|
|
71
|
+
list_dir [filesystem]
|
|
72
|
+
search_code [code]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
</td>
|
|
76
|
+
<td width="50%">
|
|
77
|
+
|
|
78
|
+
### 🧠 Persistent Memory
|
|
79
|
+
AI remembers your preferences and project context across sessions.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
→ /memory
|
|
83
|
+
🧠 Memory Stats
|
|
84
|
+
Total entries: 12
|
|
85
|
+
Projects: 3
|
|
86
|
+
Preferences: 5
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
</td>
|
|
90
|
+
</tr>
|
|
91
|
+
</table>
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
30
95
|
## ⚡ Quick Start
|
|
31
96
|
|
|
32
97
|
```bash
|
|
@@ -50,13 +115,25 @@ $ agdi
|
|
|
50
115
|
|
|
51
116
|
Model: gemini-2.5-flash
|
|
52
117
|
Workspace: ~/projects/my-app
|
|
53
|
-
Commands: /status, /diff, /commit, /build, /
|
|
118
|
+
Commands: /status, /diff, /commit, /build, /index, /search, /agent, /help, /exit
|
|
54
119
|
|
|
55
120
|
──────────────────────────────────────────────────
|
|
56
121
|
|
|
122
|
+
→ /agent
|
|
123
|
+
🤖 Multi-agent mode: ON
|
|
124
|
+
|
|
57
125
|
→ create a todo app with dark mode and local storage
|
|
58
126
|
|
|
59
|
-
|
|
127
|
+
📋 Phase 1: Planning...
|
|
128
|
+
✓ Plan created
|
|
129
|
+
|
|
130
|
+
💻 Phase 2: Coding...
|
|
131
|
+
✓ Code generated
|
|
132
|
+
|
|
133
|
+
🔍 Phase 3: Review (iteration 1)...
|
|
134
|
+
✓ Code approved!
|
|
135
|
+
|
|
136
|
+
✅ Multi-Agent Pipeline Complete
|
|
60
137
|
|
|
61
138
|
📋 Action Plan - todo-app
|
|
62
139
|
─────────────────────────
|
|
@@ -80,7 +157,8 @@ Generating action plan...
|
|
|
80
157
|
### 🤖 AI-Powered Development
|
|
81
158
|
- **Natural language to code** — Describe what you want, get production-ready apps
|
|
82
159
|
- **Conversation memory** — AI remembers context across messages
|
|
83
|
-
- **Multi-
|
|
160
|
+
- **Multi-agent mode** — Planner, Coder, Reviewer pipeline
|
|
161
|
+
- **Codebase RAG** — AI understands your entire project
|
|
84
162
|
|
|
85
163
|
</td>
|
|
86
164
|
<td width="50%">
|
|
@@ -107,9 +185,10 @@ Generating action plan...
|
|
|
107
185
|
### 🌐 Multi-Provider AI
|
|
108
186
|
- **Gemini** — Free tier, fast responses
|
|
109
187
|
- **OpenRouter** — 400+ models, one API
|
|
110
|
-
- **OpenAI** — GPT-4o,
|
|
111
|
-
- **Anthropic** — Claude
|
|
188
|
+
- **OpenAI** — GPT-4o, o3 reasoning
|
|
189
|
+
- **Anthropic** — Claude 4.5 Sonnet
|
|
112
190
|
- **DeepSeek** — R1, V3 models
|
|
191
|
+
- **Puter** — 100% free, no key needed
|
|
113
192
|
|
|
114
193
|
</td>
|
|
115
194
|
</tr>
|
|
@@ -131,6 +210,11 @@ Inside the session:
|
|
|
131
210
|
|---------|-------------|
|
|
132
211
|
| `/build <prompt>` | Generate & execute an app from description |
|
|
133
212
|
| `/edit <file>` | AI-powered surgical file editing |
|
|
213
|
+
| `/index` | 🆕 Index current project for RAG search |
|
|
214
|
+
| `/search <query>` | 🆕 Semantic code search |
|
|
215
|
+
| `/tools` | 🆕 List available MCP tools |
|
|
216
|
+
| `/agent` | 🆕 Toggle multi-agent mode |
|
|
217
|
+
| `/memory` | 🆕 View persistent memory stats |
|
|
134
218
|
| `/status` | AI analysis of git status |
|
|
135
219
|
| `/diff` | AI explanation of current changes |
|
|
136
220
|
| `/commit` | Auto-generate & run git commit |
|
|
@@ -153,6 +237,70 @@ agdi run [dir] # Run a generated project
|
|
|
153
237
|
|
|
154
238
|
---
|
|
155
239
|
|
|
240
|
+
## 🤖 Multi-Agent Architecture (v2.6)
|
|
241
|
+
|
|
242
|
+
Enable with `/agent` for higher quality code:
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
┌──────────────────────────────────────────────────────────┐
|
|
246
|
+
│ User Request │
|
|
247
|
+
└─────────────────────────┬────────────────────────────────┘
|
|
248
|
+
▼
|
|
249
|
+
┌──────────────────────────────────────────────────────────┐
|
|
250
|
+
│ 📋 PLANNER AGENT │
|
|
251
|
+
│ Analyzes request, creates step-by-step implementation │
|
|
252
|
+
│ plan, identifies files to create/modify │
|
|
253
|
+
└─────────────────────────┬────────────────────────────────┘
|
|
254
|
+
▼
|
|
255
|
+
┌──────────────────────────────────────────────────────────┐
|
|
256
|
+
│ 💻 CODER AGENT │
|
|
257
|
+
│ Writes production-ready TypeScript/React code │
|
|
258
|
+
│ following the plan │
|
|
259
|
+
└─────────────────────────┬────────────────────────────────┘
|
|
260
|
+
▼
|
|
261
|
+
┌──────────────────────────────────────────────────────────┐
|
|
262
|
+
│ 🔍 REVIEWER AGENT │
|
|
263
|
+
│ Reviews for: │
|
|
264
|
+
│ • Correctness • Security • Performance • Completeness │
|
|
265
|
+
│ │
|
|
266
|
+
│ → APPROVED: Code delivered to user │
|
|
267
|
+
│ → NEEDS_CHANGES: Sent back to CODER (max 3 loops) │
|
|
268
|
+
└─────────────────────────┬────────────────────────────────┘
|
|
269
|
+
▼
|
|
270
|
+
┌──────────────────────────────────────────────────────────┐
|
|
271
|
+
│ ✅ High-Quality, Reviewed Code │
|
|
272
|
+
└──────────────────────────────────────────────────────────┘
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## 🔍 Codebase RAG (v2.6)
|
|
278
|
+
|
|
279
|
+
Index your project so AI can understand all your code:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
# Index your project (creates searchable chunks)
|
|
283
|
+
→ /index
|
|
284
|
+
✓ Indexed 47 files, 312 chunks
|
|
285
|
+
|
|
286
|
+
# Semantic search
|
|
287
|
+
→ /search user authentication
|
|
288
|
+
🔍 Search Results
|
|
289
|
+
src/auth/login.ts:15 (0.87)
|
|
290
|
+
export async function authenticateUser...
|
|
291
|
+
src/middleware/auth.ts:1 (0.65)
|
|
292
|
+
import { verifyToken } from...
|
|
293
|
+
src/hooks/useAuth.ts:5 (0.52)
|
|
294
|
+
const AuthContext = createContext...
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**How it works:**
|
|
298
|
+
1. **Indexer** walks your project, chunks code by functions/classes
|
|
299
|
+
2. **Retriever** uses TF-IDF semantic search (no external vector DB)
|
|
300
|
+
3. **Context** is automatically injected into AI prompts
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
156
304
|
## 🛡️ Security Architecture
|
|
157
305
|
|
|
158
306
|
Agdi includes **zero-trust security** designed for safe AI-assisted development:
|
|
@@ -195,7 +343,7 @@ Real-time scanning detects:
|
|
|
195
343
|
- **22+ secret patterns** — AWS, GitHub, OpenAI, Anthropic, Stripe, etc.
|
|
196
344
|
- **Shell injection** — Dangerous command patterns
|
|
197
345
|
- **Prompt injection** — LLM manipulation attempts
|
|
198
|
-
- **
|
|
346
|
+
- **Network exfiltration** — Suspicious external requests
|
|
199
347
|
|
|
200
348
|
### 📊 Audit Logging
|
|
201
349
|
|
|
@@ -220,7 +368,6 @@ All decisions logged to `~/.agdi/audit.jsonl`:
|
|
|
220
368
|
| Kimi K2 | `moonshotai/kimi-k2:free` | General purpose |
|
|
221
369
|
| Gemma 3N E2B | `google/gemma-3n-e2b-it:free` | Lightweight |
|
|
222
370
|
| LFM Thinking | `liquid/lfm-2.5-1.2b-thinking:free` | Agentic tasks |
|
|
223
|
-
| Devstral ⚠️ | `mistralai/devstral-2512:free` | Ends Jan 27 |
|
|
224
371
|
|
|
225
372
|
### Image Generation
|
|
226
373
|
|
|
@@ -243,9 +390,9 @@ All decisions logged to `~/.agdi/audit.jsonl`:
|
|
|
243
390
|
<td>
|
|
244
391
|
|
|
245
392
|
**OpenRouter** 🌐
|
|
246
|
-
- Claude
|
|
247
|
-
- GPT-
|
|
248
|
-
- Llama
|
|
393
|
+
- Claude 4.5 Sonnet
|
|
394
|
+
- GPT-5 / GPT-4o
|
|
395
|
+
- Llama 4 Maverick
|
|
249
396
|
- DeepSeek R1
|
|
250
397
|
- 400+ more
|
|
251
398
|
|
|
@@ -253,10 +400,10 @@ All decisions logged to `~/.agdi/audit.jsonl`:
|
|
|
253
400
|
<td>
|
|
254
401
|
|
|
255
402
|
**OpenAI** 🧠
|
|
403
|
+
- GPT-5
|
|
256
404
|
- GPT-4o
|
|
257
|
-
-
|
|
405
|
+
- o3
|
|
258
406
|
- o1
|
|
259
|
-
- o1-mini
|
|
260
407
|
|
|
261
408
|
</td>
|
|
262
409
|
</tr>
|
|
@@ -264,15 +411,15 @@ All decisions logged to `~/.agdi/audit.jsonl`:
|
|
|
264
411
|
<td>
|
|
265
412
|
|
|
266
413
|
**Anthropic** 💜
|
|
414
|
+
- Claude 4.5 Sonnet
|
|
415
|
+
- Claude 4.5 Opus
|
|
267
416
|
- Claude 3.5 Sonnet
|
|
268
|
-
- Claude 3.5 Haiku
|
|
269
|
-
- Claude 3 Opus
|
|
270
417
|
|
|
271
418
|
</td>
|
|
272
419
|
<td>
|
|
273
420
|
|
|
274
421
|
**DeepSeek** 🌊
|
|
275
|
-
- DeepSeek V3
|
|
422
|
+
- DeepSeek V3.2
|
|
276
423
|
- DeepSeek R1 (Reasoning)
|
|
277
424
|
|
|
278
425
|
</td>
|
|
@@ -300,6 +447,9 @@ Config stored in `~/.agdi/`:
|
|
|
300
447
|
├── config.json # API keys & settings
|
|
301
448
|
├── rules.json # Permission rules
|
|
302
449
|
├── trusted-workspaces.json # Trusted directories
|
|
450
|
+
├── memory/ # 🆕 Persistent memory
|
|
451
|
+
│ └── store.json
|
|
452
|
+
├── indexes/ # 🆕 RAG indexes
|
|
303
453
|
└── audit.jsonl # Audit log
|
|
304
454
|
```
|
|
305
455
|
|
|
@@ -325,10 +475,14 @@ agdi auth --status
|
|
|
325
475
|
<p>TypeScript Errors</p>
|
|
326
476
|
</td>
|
|
327
477
|
<td align="center">
|
|
328
|
-
<h3>
|
|
478
|
+
<h3>60</h3>
|
|
329
479
|
<p>Tests Passing</p>
|
|
330
480
|
</td>
|
|
331
481
|
<td align="center">
|
|
482
|
+
<h3>148 KB</h3>
|
|
483
|
+
<p>Build Size</p>
|
|
484
|
+
</td>
|
|
485
|
+
<td align="center">
|
|
332
486
|
<h3>22+</h3>
|
|
333
487
|
<p>Security Patterns</p>
|
|
334
488
|
</td>
|
|
@@ -344,14 +498,20 @@ agdi auth --status
|
|
|
344
498
|
## 🚀 Examples
|
|
345
499
|
|
|
346
500
|
```bash
|
|
347
|
-
# Create a full-stack app
|
|
348
|
-
agdi
|
|
501
|
+
# Create a full-stack app (multi-agent recommended)
|
|
502
|
+
agdi
|
|
503
|
+
→ /agent
|
|
504
|
+
→ Create a blog with authentication and markdown support
|
|
349
505
|
|
|
350
|
-
#
|
|
351
|
-
agdi
|
|
506
|
+
# Semantic search your codebase
|
|
507
|
+
agdi
|
|
508
|
+
→ /index
|
|
509
|
+
→ /search payment processing
|
|
352
510
|
|
|
353
|
-
#
|
|
354
|
-
agdi
|
|
511
|
+
# AI-powered file editing
|
|
512
|
+
agdi
|
|
513
|
+
→ /edit src/components/Header.tsx
|
|
514
|
+
"Add a dark mode toggle button"
|
|
355
515
|
|
|
356
516
|
# Interactive development
|
|
357
517
|
agdi
|
|
@@ -371,6 +531,7 @@ agdi
|
|
|
371
531
|
- **Styling**: Chalk, Ora
|
|
372
532
|
- **Build**: tsup
|
|
373
533
|
- **Testing**: Vitest
|
|
534
|
+
- **AI**: Multi-agent, RAG, Token Optimization
|
|
374
535
|
|
|
375
536
|
---
|
|
376
537
|
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/core/rag/indexer.ts
|
|
9
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync, mkdirSync } from "fs";
|
|
10
|
+
import { join, extname, relative } from "path";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
import { createHash } from "crypto";
|
|
13
|
+
var INDEX_DIR = join(homedir(), ".agdi", "indexes");
|
|
14
|
+
var CHUNK_SIZE = 50;
|
|
15
|
+
var OVERLAP = 10;
|
|
16
|
+
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
17
|
+
".ts",
|
|
18
|
+
".tsx",
|
|
19
|
+
".js",
|
|
20
|
+
".jsx",
|
|
21
|
+
".mjs",
|
|
22
|
+
".cjs",
|
|
23
|
+
".py",
|
|
24
|
+
".rb",
|
|
25
|
+
".go",
|
|
26
|
+
".rs",
|
|
27
|
+
".java",
|
|
28
|
+
".kt",
|
|
29
|
+
".c",
|
|
30
|
+
".cpp",
|
|
31
|
+
".h",
|
|
32
|
+
".hpp",
|
|
33
|
+
".cs",
|
|
34
|
+
".json",
|
|
35
|
+
".yaml",
|
|
36
|
+
".yml",
|
|
37
|
+
".toml",
|
|
38
|
+
".md",
|
|
39
|
+
".txt",
|
|
40
|
+
".css",
|
|
41
|
+
".scss",
|
|
42
|
+
".html"
|
|
43
|
+
]);
|
|
44
|
+
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
45
|
+
"node_modules",
|
|
46
|
+
".git",
|
|
47
|
+
"dist",
|
|
48
|
+
"build",
|
|
49
|
+
".next",
|
|
50
|
+
"__pycache__",
|
|
51
|
+
".venv",
|
|
52
|
+
"venv",
|
|
53
|
+
"target",
|
|
54
|
+
".idea",
|
|
55
|
+
".vscode",
|
|
56
|
+
"coverage",
|
|
57
|
+
".nyc_output"
|
|
58
|
+
]);
|
|
59
|
+
var IGNORE_FILES = /* @__PURE__ */ new Set([
|
|
60
|
+
"package-lock.json",
|
|
61
|
+
"yarn.lock",
|
|
62
|
+
"pnpm-lock.yaml",
|
|
63
|
+
".DS_Store",
|
|
64
|
+
"Thumbs.db"
|
|
65
|
+
]);
|
|
66
|
+
function getLanguage(ext) {
|
|
67
|
+
const langMap = {
|
|
68
|
+
".ts": "typescript",
|
|
69
|
+
".tsx": "typescript",
|
|
70
|
+
".js": "javascript",
|
|
71
|
+
".jsx": "javascript",
|
|
72
|
+
".mjs": "javascript",
|
|
73
|
+
".py": "python",
|
|
74
|
+
".rb": "ruby",
|
|
75
|
+
".go": "go",
|
|
76
|
+
".rs": "rust",
|
|
77
|
+
".java": "java",
|
|
78
|
+
".kt": "kotlin",
|
|
79
|
+
".c": "c",
|
|
80
|
+
".cpp": "cpp",
|
|
81
|
+
".cs": "csharp",
|
|
82
|
+
".json": "json",
|
|
83
|
+
".yaml": "yaml",
|
|
84
|
+
".yml": "yaml",
|
|
85
|
+
".md": "markdown",
|
|
86
|
+
".css": "css",
|
|
87
|
+
".html": "html"
|
|
88
|
+
};
|
|
89
|
+
return langMap[ext] || "text";
|
|
90
|
+
}
|
|
91
|
+
function generateId(filePath, startLine) {
|
|
92
|
+
const hash = createHash("md5").update(`${filePath}:${startLine}`).digest("hex").slice(0, 8);
|
|
93
|
+
return `chunk_${hash}`;
|
|
94
|
+
}
|
|
95
|
+
function hashContent(content) {
|
|
96
|
+
return createHash("md5").update(content).digest("hex").slice(0, 12);
|
|
97
|
+
}
|
|
98
|
+
function detectChunkType(content, language) {
|
|
99
|
+
const firstLine = content.split("\n")[0].trim();
|
|
100
|
+
if (language === "typescript" || language === "javascript") {
|
|
101
|
+
if (/^(export\s+)?(async\s+)?function\s/.test(firstLine)) return "function";
|
|
102
|
+
if (/^(export\s+)?(abstract\s+)?class\s/.test(firstLine)) return "class";
|
|
103
|
+
if (/^(import|from)\s/.test(firstLine)) return "import";
|
|
104
|
+
if (/^export\s/.test(firstLine)) return "export";
|
|
105
|
+
}
|
|
106
|
+
if (language === "python") {
|
|
107
|
+
if (/^(async\s+)?def\s/.test(firstLine)) return "function";
|
|
108
|
+
if (/^class\s/.test(firstLine)) return "class";
|
|
109
|
+
if (/^(from|import)\s/.test(firstLine)) return "import";
|
|
110
|
+
}
|
|
111
|
+
if (/^(\/\/|\/\*|#|""")/.test(firstLine)) return "comment";
|
|
112
|
+
return "other";
|
|
113
|
+
}
|
|
114
|
+
function* walkDirectory(dir, basePath) {
|
|
115
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
const fullPath = join(dir, entry.name);
|
|
118
|
+
if (entry.isDirectory()) {
|
|
119
|
+
if (!IGNORE_DIRS.has(entry.name)) {
|
|
120
|
+
yield* walkDirectory(fullPath, basePath);
|
|
121
|
+
}
|
|
122
|
+
} else if (entry.isFile()) {
|
|
123
|
+
if (!IGNORE_FILES.has(entry.name)) {
|
|
124
|
+
const ext = extname(entry.name).toLowerCase();
|
|
125
|
+
if (SUPPORTED_EXTENSIONS.has(ext)) {
|
|
126
|
+
yield fullPath;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function chunkFile(filePath, basePath) {
|
|
133
|
+
const content = readFileSync(filePath, "utf-8");
|
|
134
|
+
const lines = content.split("\n");
|
|
135
|
+
const ext = extname(filePath).toLowerCase();
|
|
136
|
+
const language = getLanguage(ext);
|
|
137
|
+
const relativePath = relative(basePath, filePath);
|
|
138
|
+
const chunks = [];
|
|
139
|
+
for (let i = 0; i < lines.length; i += CHUNK_SIZE - OVERLAP) {
|
|
140
|
+
const chunkLines = lines.slice(i, i + CHUNK_SIZE);
|
|
141
|
+
const chunkContent = chunkLines.join("\n");
|
|
142
|
+
if (chunkContent.trim().length === 0) continue;
|
|
143
|
+
chunks.push({
|
|
144
|
+
id: generateId(filePath, i),
|
|
145
|
+
filePath,
|
|
146
|
+
relativePath,
|
|
147
|
+
content: chunkContent,
|
|
148
|
+
startLine: i + 1,
|
|
149
|
+
endLine: Math.min(i + CHUNK_SIZE, lines.length),
|
|
150
|
+
type: detectChunkType(chunkContent, language),
|
|
151
|
+
language,
|
|
152
|
+
hash: hashContent(chunkContent)
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return chunks;
|
|
156
|
+
}
|
|
157
|
+
function indexProject(projectPath) {
|
|
158
|
+
const startTime = Date.now();
|
|
159
|
+
const allChunks = [];
|
|
160
|
+
let fileCount = 0;
|
|
161
|
+
for (const filePath of walkDirectory(projectPath, projectPath)) {
|
|
162
|
+
try {
|
|
163
|
+
const stat = statSync(filePath);
|
|
164
|
+
if (stat.size > 1024 * 1024) continue;
|
|
165
|
+
const chunks = chunkFile(filePath, projectPath);
|
|
166
|
+
allChunks.push(...chunks);
|
|
167
|
+
fileCount++;
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const index = {
|
|
172
|
+
version: 1,
|
|
173
|
+
projectPath,
|
|
174
|
+
indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
175
|
+
fileCount,
|
|
176
|
+
chunkCount: allChunks.length,
|
|
177
|
+
chunks: allChunks
|
|
178
|
+
};
|
|
179
|
+
saveIndex(projectPath, index);
|
|
180
|
+
console.log(`Indexed ${fileCount} files, ${allChunks.length} chunks in ${Date.now() - startTime}ms`);
|
|
181
|
+
return index;
|
|
182
|
+
}
|
|
183
|
+
function getIndexPath(projectPath) {
|
|
184
|
+
const hash = createHash("md5").update(projectPath).digest("hex").slice(0, 12);
|
|
185
|
+
return join(INDEX_DIR, `${hash}.json`);
|
|
186
|
+
}
|
|
187
|
+
function saveIndex(projectPath, index) {
|
|
188
|
+
if (!existsSync(INDEX_DIR)) {
|
|
189
|
+
mkdirSync(INDEX_DIR, { recursive: true });
|
|
190
|
+
}
|
|
191
|
+
writeFileSync(getIndexPath(projectPath), JSON.stringify(index));
|
|
192
|
+
}
|
|
193
|
+
function loadIndex(projectPath) {
|
|
194
|
+
const indexPath = getIndexPath(projectPath);
|
|
195
|
+
if (!existsSync(indexPath)) return null;
|
|
196
|
+
try {
|
|
197
|
+
const data = readFileSync(indexPath, "utf-8");
|
|
198
|
+
return JSON.parse(data);
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function isIndexStale(index) {
|
|
204
|
+
const indexedAt = new Date(index.indexedAt).getTime();
|
|
205
|
+
const hourAgo = Date.now() - 60 * 60 * 1e3;
|
|
206
|
+
return indexedAt < hourAgo;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/core/rag/retriever.ts
|
|
210
|
+
function tokenize(text) {
|
|
211
|
+
return text.toLowerCase().replace(/[^a-z0-9_]/g, " ").split(/\s+/).filter((t) => t.length > 2);
|
|
212
|
+
}
|
|
213
|
+
function calculateTF(tokens) {
|
|
214
|
+
const tf = /* @__PURE__ */ new Map();
|
|
215
|
+
for (const token of tokens) {
|
|
216
|
+
tf.set(token, (tf.get(token) || 0) + 1);
|
|
217
|
+
}
|
|
218
|
+
const max = Math.max(...tf.values());
|
|
219
|
+
for (const [token, count] of tf) {
|
|
220
|
+
tf.set(token, count / max);
|
|
221
|
+
}
|
|
222
|
+
return tf;
|
|
223
|
+
}
|
|
224
|
+
function scoreChunk(chunk, queryTokens, queryTF) {
|
|
225
|
+
const chunkTokens = tokenize(chunk.content);
|
|
226
|
+
const chunkTF = calculateTF(chunkTokens);
|
|
227
|
+
let score = 0;
|
|
228
|
+
const matchedTokens = /* @__PURE__ */ new Set();
|
|
229
|
+
for (const queryToken of queryTokens) {
|
|
230
|
+
if (chunkTF.has(queryToken)) {
|
|
231
|
+
score += chunkTF.get(queryToken) * (queryTF.get(queryToken) || 1);
|
|
232
|
+
matchedTokens.add(queryToken);
|
|
233
|
+
}
|
|
234
|
+
for (const chunkToken of chunkTokens) {
|
|
235
|
+
if (chunkToken.startsWith(queryToken) || queryToken.startsWith(chunkToken)) {
|
|
236
|
+
if (!matchedTokens.has(queryToken)) {
|
|
237
|
+
score += 0.5 * (queryTF.get(queryToken) || 1);
|
|
238
|
+
matchedTokens.add(queryToken);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const pathTokens = tokenize(chunk.relativePath);
|
|
244
|
+
for (const queryToken of queryTokens) {
|
|
245
|
+
if (pathTokens.includes(queryToken)) {
|
|
246
|
+
score += 0.5;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (chunk.type === "function" || chunk.type === "class") {
|
|
250
|
+
score *= 1.2;
|
|
251
|
+
}
|
|
252
|
+
score = score / Math.sqrt(queryTokens.length);
|
|
253
|
+
return score;
|
|
254
|
+
}
|
|
255
|
+
function extractHighlights(chunk, queryTokens) {
|
|
256
|
+
const lines = chunk.content.split("\n");
|
|
257
|
+
const highlights = [];
|
|
258
|
+
for (const line of lines) {
|
|
259
|
+
const lineLower = line.toLowerCase();
|
|
260
|
+
for (const token of queryTokens) {
|
|
261
|
+
if (lineLower.includes(token)) {
|
|
262
|
+
const trimmed = line.trim();
|
|
263
|
+
if (trimmed.length > 0 && !highlights.includes(trimmed)) {
|
|
264
|
+
highlights.push(trimmed.slice(0, 100));
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (highlights.length >= 3) break;
|
|
270
|
+
}
|
|
271
|
+
return highlights;
|
|
272
|
+
}
|
|
273
|
+
function searchCodebase(projectPath, query, options = {}) {
|
|
274
|
+
const limit = options.limit ?? 10;
|
|
275
|
+
const minScore = options.minScore ?? 0.1;
|
|
276
|
+
const index = loadIndex(projectPath);
|
|
277
|
+
if (!index) {
|
|
278
|
+
console.warn("No index found. Run /index first.");
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
const queryTokens = tokenize(query);
|
|
282
|
+
if (queryTokens.length === 0) {
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
285
|
+
const queryTF = calculateTF(queryTokens);
|
|
286
|
+
const results = [];
|
|
287
|
+
for (const chunk of index.chunks) {
|
|
288
|
+
if (options.fileTypes && !options.fileTypes.includes(chunk.language)) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (options.chunkTypes && !options.chunkTypes.includes(chunk.type)) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const score = scoreChunk(chunk, queryTokens, queryTF);
|
|
295
|
+
if (score >= minScore) {
|
|
296
|
+
results.push({
|
|
297
|
+
chunk,
|
|
298
|
+
score,
|
|
299
|
+
highlights: extractHighlights(chunk, queryTokens)
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
results.sort((a, b) => b.score - a.score);
|
|
304
|
+
return results.slice(0, limit);
|
|
305
|
+
}
|
|
306
|
+
function getRelevantContext(projectPath, prompt, maxChunks = 5) {
|
|
307
|
+
const results = searchCodebase(projectPath, prompt, { limit: maxChunks });
|
|
308
|
+
if (results.length === 0) {
|
|
309
|
+
return "";
|
|
310
|
+
}
|
|
311
|
+
const parts = ["[Relevant Code Context]"];
|
|
312
|
+
for (const result of results) {
|
|
313
|
+
parts.push(`
|
|
314
|
+
// ${result.chunk.relativePath}:${result.chunk.startLine}-${result.chunk.endLine}`);
|
|
315
|
+
parts.push("```" + result.chunk.language);
|
|
316
|
+
parts.push(result.chunk.content.slice(0, 500));
|
|
317
|
+
if (result.chunk.content.length > 500) {
|
|
318
|
+
parts.push("// ... (truncated)");
|
|
319
|
+
}
|
|
320
|
+
parts.push("```");
|
|
321
|
+
}
|
|
322
|
+
parts.push("[/Relevant Code Context]\n");
|
|
323
|
+
return parts.join("\n");
|
|
324
|
+
}
|
|
325
|
+
function findFiles(projectPath, pattern) {
|
|
326
|
+
const index = loadIndex(projectPath);
|
|
327
|
+
if (!index) return [];
|
|
328
|
+
const patternLower = pattern.toLowerCase();
|
|
329
|
+
const seen = /* @__PURE__ */ new Set();
|
|
330
|
+
const results = [];
|
|
331
|
+
for (const chunk of index.chunks) {
|
|
332
|
+
if (seen.has(chunk.filePath)) continue;
|
|
333
|
+
if (chunk.relativePath.toLowerCase().includes(patternLower)) {
|
|
334
|
+
seen.add(chunk.filePath);
|
|
335
|
+
results.push(chunk);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return results;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export {
|
|
342
|
+
__require,
|
|
343
|
+
indexProject,
|
|
344
|
+
loadIndex,
|
|
345
|
+
isIndexStale,
|
|
346
|
+
searchCodebase,
|
|
347
|
+
getRelevantContext,
|
|
348
|
+
findFiles
|
|
349
|
+
};
|