dev-mcp-server 0.0.2

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/.env.example ADDED
@@ -0,0 +1,68 @@
1
+ # ═══════════════════════════════════════════════════════════════
2
+ # Dev MCP Server — environment configuration
3
+ # Copy this file to .env and fill in your values.
4
+ # ═══════════════════════════════════════════════════════════════
5
+
6
+ # ── LLM Provider ───────────────────────────────────────────────
7
+ # Which LLM backend to use.
8
+ # Options: anthropic | ollama | azure
9
+ # Default: anthropic
10
+ LLM_PROVIDER=anthropic
11
+
12
+ # Model / deployment name override.
13
+ # If unset, the default per provider is used:
14
+ # anthropic → claude-opus-4-5
15
+ # ollama → llama3
16
+ # azure → MUST be set (matches your Azure deployment name)
17
+ # LLM_MODEL=claude-opus-4-5
18
+
19
+
20
+ # ── Anthropic ──────────────────────────────────────────────────
21
+ # Required when LLM_PROVIDER=anthropic
22
+ ANTHROPIC_API_KEY=sk-ant-your-key-here
23
+
24
+
25
+ # ── Ollama ─────────────────────────────────────────────────────
26
+ # Required when LLM_PROVIDER=ollama
27
+ # Ensure Ollama is running: https://ollama.com
28
+ # Pull a model first: ollama pull llama3
29
+ #
30
+ # OLLAMA_BASE_URL=http://localhost:11434
31
+
32
+
33
+ # ── Azure OpenAI ───────────────────────────────────────────────
34
+ # Required when LLM_PROVIDER=azure
35
+ #
36
+ # Your Azure OpenAI resource endpoint:
37
+ # AZURE_OPENAI_ENDPOINT=https://<your-resource>.openai.azure.com
38
+ #
39
+ # API key from Azure portal → Keys and Endpoint:
40
+ # AZURE_OPENAI_API_KEY=your-azure-key-here
41
+ #
42
+ # The name of the deployment you created in Azure AI Studio
43
+ # (this is also used as the model parameter):
44
+ # AZURE_OPENAI_DEPLOYMENT=gpt-4o
45
+ #
46
+ # API version — default is fine unless you need a specific one:
47
+ # AZURE_OPENAI_API_VERSION=2024-05-01-preview
48
+
49
+
50
+ # ── Ingestion — ignore rules ────────────────────────────────────
51
+ # Whether to read the project's .gitignore and add its patterns
52
+ # to the ingest ignore list. Set to "false" to disable.
53
+ # Default: true
54
+ # INGEST_USE_GITIGNORE=true
55
+
56
+ # Extra glob patterns to ignore during ingest (comma-separated).
57
+ # Added on top of the built-in baseline and .gitignore patterns.
58
+ # Example: INGEST_EXTRA_IGNORE=**/fixtures/**,**/__snapshots__/**
59
+ # INGEST_EXTRA_IGNORE=
60
+
61
+
62
+ # ── Server ─────────────────────────────────────────────────────
63
+ PORT=3000
64
+
65
+
66
+ # ── Logging ────────────────────────────────────────────────────
67
+ # Options: error | warn | info | debug
68
+ LOG_LEVEL=info
package/README.md ADDED
@@ -0,0 +1,333 @@
1
+ # 🧠 Dev MCP Server — Model Context Platform
2
+
3
+ > AI that understands **your** codebase, not just the internet.
4
+
5
+ Inspired by *"How I Built an MCP Server That Made Developers Faster and Work Easier"* — a full implementation of the **Model Context Platform** concept: instead of generic AI answers, every response is grounded in your actual code, error logs, API behavior, and bug history.
6
+
7
+ ---
8
+
9
+ ## The Problem It Solves
10
+
11
+ Every team has this invisible tax:
12
+ - Debugging code you didn't write, with zero context
13
+ - Explaining things that are already written *somewhere*
14
+ - Digging through 10 files to understand one API
15
+ - Answering the same question for the third time this week
16
+
17
+ The root cause isn't bad code. It's a **context problem** — knowledge scattered across services, logs, configs, and people's heads.
18
+
19
+ ---
20
+
21
+ ## What It Does
22
+
23
+ Before answering any question, the AI looks up your **actual system**. It knows:
24
+
25
+ - Your data models and DTOs
26
+ - Your naming conventions and code patterns
27
+ - Your most common bugs and how you fixed them
28
+ - Your API behaviour — including weird edge cases
29
+ - How your modules connect to each other
30
+
31
+ ---
32
+
33
+ ## The 3 Core Queries
34
+
35
+ | Query | Endpoint | Example |
36
+ | ------------------------------------ | ------------------------ | ---------------------------------------------------- |
37
+ | 🐛 **Why is this failing?** | `POST /api/query/debug` | `"Why is ClassCastException thrown in UserService?"` |
38
+ | 🔍 **Where is this used?** | `POST /api/query/usage` | `"Where is getUserById called?"` |
39
+ | 💥 **If I change this, what breaks?** | `POST /api/query/impact` | `"If I change the User model, what breaks?"` |
40
+
41
+ ---
42
+
43
+ ## Quick Start
44
+
45
+ ### Option A — via npx (no install required)
46
+
47
+ ```bash
48
+ # In your project root (where your .env lives):
49
+ npx dev-mcp-server ingest ./src
50
+ npx dev-mcp-server query "Why is getUserById throwing?"
51
+ npx dev-mcp-server query -i # interactive REPL
52
+ ```
53
+
54
+ > **Note:** `npx` will look for `.env` in the directory you run the command from,
55
+ > so make sure your credentials are there before running.
56
+
57
+ ### Option B — local install
58
+
59
+ ```bash
60
+ git clone <repo>
61
+ cd dev-mcp-server
62
+ npm install
63
+ cp .env.example .env
64
+ # Edit .env — choose your LLM provider and add credentials
65
+ ```
66
+
67
+ ```bash
68
+ # Ingest your codebase
69
+ node cli.js ingest ./src
70
+
71
+ # Ask questions
72
+ node cli.js query -i # interactive REPL
73
+ node cli.js query "Why is getUserById failing?"
74
+ node cli.js debug "ClassCastException" --stack "at UserService:45"
75
+ node cli.js stats
76
+ ```
77
+
78
+ ### Option C — REST API server
79
+
80
+ ```bash
81
+ npm start
82
+ # Runs at http://localhost:3000
83
+ ```
84
+
85
+ ---
86
+
87
+ ## LLM Providers
88
+
89
+ The server supports three backends. Switch between them with a single environment variable — no code changes needed.
90
+
91
+ ### Anthropic (default)
92
+
93
+ ```env
94
+ LLM_PROVIDER=anthropic
95
+ ANTHROPIC_API_KEY=sk-ant-your-key-here
96
+ LLM_MODEL=claude-opus-4-5 # optional, this is the default
97
+ ```
98
+
99
+ ### Ollama (local / self-hosted)
100
+
101
+ Run any model locally — no API key needed.
102
+
103
+ ```bash
104
+ # Install Ollama: https://ollama.com
105
+ ollama pull llama3 # or mistral, codellama, phi3, etc.
106
+ ```
107
+
108
+ ```env
109
+ LLM_PROVIDER=ollama
110
+ OLLAMA_BASE_URL=http://localhost:11434 # optional, this is the default
111
+ LLM_MODEL=llama3 # optional, this is the default
112
+ ```
113
+
114
+ ### Azure OpenAI
115
+
116
+ ```env
117
+ LLM_PROVIDER=azure
118
+ AZURE_OPENAI_ENDPOINT=https://<your-resource>.openai.azure.com
119
+ AZURE_OPENAI_API_KEY=your-azure-key-here
120
+ AZURE_OPENAI_DEPLOYMENT=gpt-4o # your deployment name in Azure AI Studio
121
+ AZURE_OPENAI_API_VERSION=2024-05-01-preview # optional, has a sensible default
122
+ ```
123
+
124
+ > The deployment name is also used as `LLM_MODEL`. If you want to override the model
125
+ > label independently, set `LLM_MODEL` explicitly.
126
+
127
+ ---
128
+
129
+ ## Ingest & Ignore Rules
130
+
131
+ ### Default ignore list
132
+
133
+ The following patterns are always excluded, regardless of any other configuration:
134
+
135
+ ```
136
+ **/node_modules/** **/.git/** **/dist/**
137
+ **/build/** **/coverage/** **/*.min.js
138
+ **/package-lock.json **/yarn.lock
139
+ ```
140
+
141
+ ### .gitignore integration
142
+
143
+ By default the server reads the `.gitignore` in the directory being ingested and adds those patterns on top of the baseline. This means anything your team already ignores in git is also ignored during ingestion — no duplicate config.
144
+
145
+ ```env
146
+ # Disable .gitignore integration (enabled by default):
147
+ INGEST_USE_GITIGNORE=false
148
+ ```
149
+
150
+ ### Extra ignore patterns
151
+
152
+ Add any additional glob patterns via a comma-separated env var:
153
+
154
+ ```env
155
+ INGEST_EXTRA_IGNORE=**/fixtures/**,**/__snapshots__/**,**/test-data/**
156
+ ```
157
+
158
+ All three sources (baseline + `.gitignore` + `INGEST_EXTRA_IGNORE`) are merged and deduplicated before each directory ingest. The log output tells you exactly what was applied:
159
+
160
+ ```
161
+ Ignore sources: baseline, .gitignore (12 patterns), INGEST_EXTRA_IGNORE (2 patterns)
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Configuration Reference
167
+
168
+ Copy `.env.example` to `.env` and fill in the relevant section for your chosen provider.
169
+
170
+ | Variable | Default | Description |
171
+ | -------------------------- | ------------------------ | ----------------------------------------------- |
172
+ | `LLM_PROVIDER` | `anthropic` | LLM backend: `anthropic` \| `ollama` \| `azure` |
173
+ | `LLM_MODEL` | *(per provider)* | Model or deployment name override |
174
+ | `ANTHROPIC_API_KEY` | — | Required when `LLM_PROVIDER=anthropic` |
175
+ | `OLLAMA_BASE_URL` | `http://localhost:11434` | Ollama server URL |
176
+ | `AZURE_OPENAI_ENDPOINT` | — | Required when `LLM_PROVIDER=azure` |
177
+ | `AZURE_OPENAI_API_KEY` | — | Required when `LLM_PROVIDER=azure` |
178
+ | `AZURE_OPENAI_DEPLOYMENT` | — | Required when `LLM_PROVIDER=azure` |
179
+ | `AZURE_OPENAI_API_VERSION` | `2024-05-01-preview` | Azure API version |
180
+ | `INGEST_USE_GITIGNORE` | `true` | Read `.gitignore` during ingest |
181
+ | `INGEST_EXTRA_IGNORE` | — | Comma-separated extra glob patterns to ignore |
182
+ | `PORT` | `3000` | HTTP server port |
183
+ | `LOG_LEVEL` | `info` | `error` \| `warn` \| `info` \| `debug` |
184
+
185
+ ---
186
+
187
+ ## API Reference
188
+
189
+ ### Ingest
190
+
191
+ ```bash
192
+ # Ingest a file
193
+ curl -X POST http://localhost:3000/api/ingest/file \
194
+ -H "Content-Type: application/json" \
195
+ -d '{"filePath": "./src/services/UserService.js"}'
196
+
197
+ # Ingest a directory
198
+ curl -X POST http://localhost:3000/api/ingest/directory \
199
+ -H "Content-Type: application/json" \
200
+ -d '{"dirPath": "./src"}'
201
+
202
+ # Ingest raw text (paste an error log, bug description, etc.)
203
+ curl -X POST http://localhost:3000/api/ingest/raw \
204
+ -H "Content-Type: application/json" \
205
+ -d '{
206
+ "content": "ClassCastException at UserService line 45: Mongoose doc passed to UserDTO. Fix: call .toObject() first.",
207
+ "kind": "log",
208
+ "label": "production-bug-2024-03-15"
209
+ }'
210
+ ```
211
+
212
+ ### Query
213
+
214
+ ```bash
215
+ # General question — auto-detects debug / usage / impact mode
216
+ curl -X POST http://localhost:3000/api/query \
217
+ -H "Content-Type: application/json" \
218
+ -d '{"question": "Why does getUserById sometimes throw ClassCastException?"}'
219
+
220
+ # Force debug mode
221
+ curl -X POST http://localhost:3000/api/query/debug \
222
+ -H "Content-Type: application/json" \
223
+ -d '{"error": "ClassCastException", "stackTrace": "at UserService.getUserById:45"}'
224
+
225
+ # Usage search
226
+ curl -X POST http://localhost:3000/api/query/usage \
227
+ -H "Content-Type: application/json" \
228
+ -d '{"symbol": "getUserById"}'
229
+
230
+ # Impact analysis
231
+ curl -X POST http://localhost:3000/api/query/impact \
232
+ -H "Content-Type: application/json" \
233
+ -d '{"target": "UserDTO", "changeDescription": "add a new required field"}'
234
+
235
+ # Streaming (Server-Sent Events)
236
+ curl -X POST http://localhost:3000/api/query/stream \
237
+ -H "Content-Type: application/json" \
238
+ -d '{"question": "How does user status update work end to end?"}'
239
+ ```
240
+
241
+ ### Knowledge Base
242
+
243
+ ```bash
244
+ curl http://localhost:3000/api/knowledge/stats
245
+ curl "http://localhost:3000/api/knowledge/search?q=ClassCastException&topK=5"
246
+ curl http://localhost:3000/api/knowledge/files
247
+ curl -X POST http://localhost:3000/api/knowledge/rebuild
248
+ curl -X DELETE http://localhost:3000/api/ingest/clear
249
+ ```
250
+
251
+ ---
252
+
253
+ ## Supported File Types
254
+
255
+ | Category | Extensions |
256
+ | -------- | ------------------------------------------------------------------------------------------------ |
257
+ | Code | `.js` `.ts` `.jsx` `.tsx` `.mjs` `.cjs` `.py` `.java` `.go` `.rb` `.php` `.cs` `.cpp` `.c` `.rs` |
258
+ | Config | `.json` `.yaml` `.yml` `.env` `.toml` `.xml` |
259
+ | Docs | `.md` `.txt` |
260
+ | Logs | `.log` |
261
+ | Schema | `.sql` `.graphql` `.gql` |
262
+ | Scripts | `.sh` `.bash` |
263
+
264
+ ---
265
+
266
+ ## What to Ingest
267
+
268
+ The key insight: **ingest real stuff, not clean summaries**.
269
+
270
+ ```bash
271
+ node cli.js ingest ./src # actual source code
272
+ node cli.js ingest ./logs # real error logs — the ugly ones
273
+ node cli.js ingest ./config # environment configs and schemas
274
+ node cli.js ingest ./docs # ADRs, runbooks, onboarding notes
275
+ ```
276
+
277
+ Paste knowledge directly in the interactive REPL:
278
+
279
+ ```
280
+ ❯ node cli.js query -i
281
+ ❯ We fixed a bug last week where the Mongoose document wasn't being converted
282
+ to a plain object before passing to UserDTO. Always call .toObject() first.
283
+ ```
284
+
285
+ > *"Docs lie. Or rather, docs go stale. Code doesn't."*
286
+
287
+ ---
288
+
289
+ ## Architecture
290
+
291
+ ```
292
+ ┌─────────────────────────────────────────────────────────────┐
293
+ │ Dev MCP Server │
294
+ │ │
295
+ │ ┌──────────┐ ┌──────────┐ ┌────────────────────────┐ │
296
+ │ │ Ingester │───▶│ Store │──▶│ Indexer │ │
297
+ │ │ │ │ (JSON) │ │ (TF-IDF Search) │ │
298
+ │ └──────────┘ └──────────┘ └────────────────────────┘ │
299
+ │ │ │ │
300
+ │ ▼ ▼ │
301
+ │ ┌──────────┐ ┌───────────────────────────┐ │
302
+ │ │ CLI │ │ Query Engine │ │
303
+ │ │ (REPL) │ │ Retrieval + LLM Client │ │
304
+ │ └──────────┘ └───────────────────────────┘ │
305
+ │ │ │
306
+ │ ┌┴──────────────┐│
307
+ │ │ Anthropic / ││
308
+ │ │ Ollama / ││
309
+ │ │ Azure OpenAI ││
310
+ │ └───────────────┘│
311
+ │ │
312
+ │ ┌────────────────────────────────────────────────────────┐ │
313
+ │ │ Express REST API │ │
314
+ │ └────────────────────────────────────────────────────────┘ │
315
+ └─────────────────────────────────────────────────────────────┘
316
+ ```
317
+
318
+ **How it works:**
319
+ 1. **Ingest** — Feed your codebase in (files, directories, raw text)
320
+ 2. **Index** — TF-IDF search index built over all chunks
321
+ 3. **Query** — Question arrives → relevant context retrieved → LLM answers based on *your* code
322
+
323
+ ---
324
+
325
+ ## Key Design Decisions
326
+
327
+ **Data quality beats model quality.** The retrieval step (TF-IDF over your actual files) matters more than which AI model you use. A focused, well-curated knowledge base with a smaller model will outperform a bloated one with GPT-4.
328
+
329
+ **No embeddings, no vector DB.** TF-IDF is deterministic, fast, and requires zero infrastructure. For most codebases (< 50k files) it's entirely sufficient.
330
+
331
+ **Provider-agnostic by design.** The `llmClient` abstraction means you can switch from Anthropic to a local Ollama model to Azure OpenAI by changing one line in `.env` — useful for cost control, data residency requirements, or offline usage.
332
+
333
+ **Ingest real artefacts.** Error logs, not summaries of error logs. Actual API responses, not docs about API responses. The messier the better — the system is built to handle it.
package/cli.js ADDED
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require('path');
4
+ require('dotenv').config({ path: path.resolve(process.cwd(), '.env') });
5
+
6
+ const { Command } = require('commander');
7
+ const chalk = require('chalk');
8
+ const ora = require('ora');
9
+ const readline = require('readline');
10
+
11
+ const ingester = require('./src/core/ingester');
12
+ const indexer = require('./src/core/indexer');
13
+ const { QueryEngine, detectMode, QUERY_MODES } = require('./src/core/queryEngine');
14
+ const store = require('./src/storage/store');
15
+
16
+ const program = new Command();
17
+
18
+ const banner = chalk.cyan(`
19
+ ╔══════════════════════════════════════════════════════╗
20
+ ║ Dev MCP Server — Model Context Platform ║
21
+ ║ AI that understands YOUR codebase ║
22
+ ╚══════════════════════════════════════════════════════╝
23
+ `);
24
+
25
+ program
26
+ .command('ingest <path>')
27
+ .description('Ingest a file or directory into the knowledge base')
28
+ .option('-t, --type <type>', 'Force type: code | config | documentation | log | schema')
29
+ .action(async (inputPath, opts) => {
30
+ console.log(banner);
31
+ const fs = require('fs');
32
+ const stat = fs.statSync(inputPath);
33
+ const spinner = ora();
34
+
35
+ if (stat.isDirectory()) {
36
+ spinner.start(chalk.blue(`Scanning directory: ${inputPath}`));
37
+ try {
38
+ const result = await ingester.ingestDirectory(inputPath);
39
+ spinner.succeed(chalk.green('Ingestion complete'));
40
+ console.log('\n' + chalk.bold('Results:'));
41
+ console.log(` ${chalk.green('✓')} Ingested: ${result.ingested} files`);
42
+ console.log(` ${chalk.yellow('⚠')} Skipped: ${result.skipped} files`);
43
+ console.log(` ${chalk.red('✗')} Failed: ${result.failed} files`);
44
+ console.log(` ${chalk.cyan('◈')} Chunks: ${result.totalChunks} total`);
45
+ if (result.errors.length > 0) {
46
+ console.log('\n' + chalk.red('Errors:'));
47
+ result.errors.slice(0, 5).forEach(e =>
48
+ console.log(` ${e.file}: ${e.error}`)
49
+ );
50
+ }
51
+ } catch (err) {
52
+ spinner.fail(chalk.red(`Failed: ${err.message}`));
53
+ process.exit(1);
54
+ }
55
+ } else {
56
+ spinner.start(chalk.blue(`Ingesting file: ${inputPath}`));
57
+ try {
58
+ const result = await ingester.ingestFile(inputPath);
59
+ indexer.build();
60
+ spinner.succeed(chalk.green(`Ingested: ${result.chunks} chunks`));
61
+ } catch (err) {
62
+ spinner.fail(chalk.red(`Failed: ${err.message}`));
63
+ process.exit(1);
64
+ }
65
+ }
66
+ });
67
+
68
+ program
69
+ .command('query [question]')
70
+ .description('Ask a question about your codebase')
71
+ .option('-m, --mode <mode>', 'Force mode: debug | usage | impact | general')
72
+ .option('-k, --top-k <n>', 'Number of context chunks', '8')
73
+ .option('-i, --interactive', 'Start interactive REPL session')
74
+ .action(async (question, opts) => {
75
+ console.log(banner);
76
+
77
+ const stats = store.getStats();
78
+ if (stats.totalDocs === 0) {
79
+ console.log(chalk.yellow('⚠ Knowledge base is empty!'));
80
+ console.log(chalk.gray(' Run: node cli.js ingest <path>'));
81
+ process.exit(1);
82
+ }
83
+
84
+ console.log(chalk.gray(`📚 Knowledge base: ${stats.totalDocs} docs from ${stats.totalFiles} files\n`));
85
+
86
+ if (opts.interactive || !question) {
87
+ await startRepl();
88
+ return;
89
+ }
90
+
91
+ await askQuestion(question, opts);
92
+ });
93
+
94
+ async function askQuestion(question, opts = {}) {
95
+ const mode = opts.mode || detectMode(question);
96
+ const topK = parseInt(opts.topK || 8);
97
+
98
+ const modeColors = {
99
+ [QUERY_MODES.DEBUG]: chalk.red,
100
+ [QUERY_MODES.USAGE]: chalk.blue,
101
+ [QUERY_MODES.IMPACT]: chalk.yellow,
102
+ [QUERY_MODES.GENERAL]: chalk.cyan,
103
+ };
104
+
105
+ const modeEmoji = {
106
+ [QUERY_MODES.DEBUG]: '🐛',
107
+ [QUERY_MODES.USAGE]: '🔍',
108
+ [QUERY_MODES.IMPACT]: '💥',
109
+ [QUERY_MODES.GENERAL]: '💬',
110
+ };
111
+
112
+ const colorFn = modeColors[mode] || chalk.cyan;
113
+ console.log(colorFn(`${modeEmoji[mode]} Mode: ${mode.toUpperCase()}`));
114
+ console.log(chalk.bold(`\nQ: ${question}\n`));
115
+
116
+ const spinner = ora('Retrieving context and thinking...').start();
117
+
118
+ try {
119
+ const result = await QueryEngine.query(question, { mode, topK });
120
+ spinner.stop();
121
+
122
+ console.log(chalk.gray('─'.repeat(60)));
123
+ console.log(chalk.gray('Sources used:'));
124
+ result.sources.forEach((s, i) => {
125
+ console.log(chalk.gray(` [${i + 1}] ${s.file} (${s.kind}) — score: ${s.relevanceScore}`));
126
+ });
127
+ console.log(chalk.gray('─'.repeat(60)));
128
+
129
+ console.log('\n' + chalk.bold('Answer:\n'));
130
+ console.log(result.answer);
131
+ console.log(chalk.gray(`\n[Tokens: ${result.usage.inputTokens} in / ${result.usage.outputTokens} out]`));
132
+
133
+ } catch (err) {
134
+ spinner.fail(chalk.red(`Error: ${err.message}`));
135
+ }
136
+ }
137
+
138
+ async function startRepl() {
139
+ console.log(chalk.cyan('Starting interactive session. Type "exit" to quit, "help" for tips.\n'));
140
+
141
+ const rl = readline.createInterface({
142
+ input: process.stdin,
143
+ output: process.stdout,
144
+ });
145
+
146
+ const prompt = () => {
147
+ rl.question(chalk.bold.cyan('\n❯ '), async (input) => {
148
+ const trimmed = input.trim();
149
+
150
+ if (!trimmed) {
151
+ prompt();
152
+ return;
153
+ }
154
+
155
+ if (trimmed.toLowerCase() === 'exit' || trimmed.toLowerCase() === 'quit') {
156
+ console.log(chalk.cyan('\nGoodbye!\n'));
157
+ rl.close();
158
+ process.exit(0);
159
+ }
160
+
161
+ if (trimmed.toLowerCase() === 'help') {
162
+ console.log(chalk.cyan(`
163
+ Tips:
164
+ 🐛 Debug: "Why is ClassCastException happening in UserService?"
165
+ 🔍 Usage: "Where is getUserById used?"
166
+ 💥 Impact: "If I change the User model, what breaks?"
167
+ 💬 General: Any question about your codebase
168
+ `));
169
+ prompt();
170
+ return;
171
+ }
172
+
173
+ if (trimmed.toLowerCase() === 'stats') {
174
+ const stats = store.getStats();
175
+ console.log(chalk.cyan(JSON.stringify(stats, null, 2)));
176
+ prompt();
177
+ return;
178
+ }
179
+
180
+ await askQuestion(trimmed);
181
+ prompt();
182
+ });
183
+ };
184
+
185
+ prompt();
186
+ }
187
+
188
+ program
189
+ .command('stats')
190
+ .description('Show knowledge base statistics')
191
+ .action(() => {
192
+ const stats = store.getStats();
193
+ const files = store.getIngestedFiles();
194
+
195
+ console.log(banner);
196
+ console.log(chalk.bold('Knowledge Base Stats:'));
197
+ console.log(` Total documents: ${chalk.green(stats.totalDocs)}`);
198
+ console.log(` Total files: ${chalk.green(stats.totalFiles)}`);
199
+ console.log(` Last ingested: ${chalk.gray(stats.lastIngested || 'Never')}`);
200
+ console.log('\n' + chalk.bold('By type:'));
201
+ Object.entries(stats.fileTypes || {}).forEach(([type, count]) => {
202
+ console.log(` ${type.padEnd(15)} ${chalk.cyan(count)} docs`);
203
+ });
204
+
205
+ if (files.length > 0) {
206
+ console.log('\n' + chalk.bold(`Ingested files (${files.length}):`));
207
+ files.slice(0, 20).forEach(f => console.log(` ${chalk.gray(f)}`));
208
+ if (files.length > 20) {
209
+ console.log(chalk.gray(` ... and ${files.length - 20} more`));
210
+ }
211
+ }
212
+ });
213
+
214
+ program
215
+ .command('clear')
216
+ .description('Clear the entire knowledge base')
217
+ .option('-y, --yes', 'Skip confirmation')
218
+ .action(async (opts) => {
219
+ if (!opts.yes) {
220
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
221
+ rl.question(chalk.red('⚠ This will delete all indexed data. Continue? (y/N) '), (answer) => {
222
+ rl.close();
223
+ if (answer.toLowerCase() === 'y') {
224
+ store.clear();
225
+ indexer.invalidate();
226
+ console.log(chalk.green('✓ Knowledge base cleared'));
227
+ } else {
228
+ console.log('Cancelled.');
229
+ }
230
+ process.exit(0);
231
+ });
232
+ } else {
233
+ store.clear();
234
+ indexer.invalidate();
235
+ console.log(chalk.green('✓ Knowledge base cleared'));
236
+ }
237
+ });
238
+
239
+ program
240
+ .command('debug <error>')
241
+ .description('Quick debug: explain an error in context of your codebase')
242
+ .option('-s, --stack <trace>', 'Stack trace')
243
+ .action(async (error, opts) => {
244
+ console.log(banner);
245
+ await askQuestion(`Why is this error happening and how do I fix it?\nError: ${error}${opts.stack ? '\nStack:\n' + opts.stack : ''}`, { mode: QUERY_MODES.DEBUG });
246
+ });
247
+
248
+ program.parse(process.argv);