ai-mind-map 1.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/LICENSE +21 -0
- package/README.md +554 -0
- package/dist/change-tracker/change-log.d.ts +160 -0
- package/dist/change-tracker/change-log.d.ts.map +1 -0
- package/dist/change-tracker/change-log.js +507 -0
- package/dist/change-tracker/change-log.js.map +1 -0
- package/dist/change-tracker/diff-engine.d.ts +149 -0
- package/dist/change-tracker/diff-engine.d.ts.map +1 -0
- package/dist/change-tracker/diff-engine.js +530 -0
- package/dist/change-tracker/diff-engine.js.map +1 -0
- package/dist/change-tracker/watcher.d.ts +137 -0
- package/dist/change-tracker/watcher.d.ts.map +1 -0
- package/dist/change-tracker/watcher.js +300 -0
- package/dist/change-tracker/watcher.js.map +1 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +937 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +222 -0
- package/dist/config.js.map +1 -0
- package/dist/context/compressor.d.ts +49 -0
- package/dist/context/compressor.d.ts.map +1 -0
- package/dist/context/compressor.js +769 -0
- package/dist/context/compressor.js.map +1 -0
- package/dist/context/progressive-disclosure.d.ts +71 -0
- package/dist/context/progressive-disclosure.d.ts.map +1 -0
- package/dist/context/progressive-disclosure.js +470 -0
- package/dist/context/progressive-disclosure.js.map +1 -0
- package/dist/context/token-budget.d.ts +121 -0
- package/dist/context/token-budget.d.ts.map +1 -0
- package/dist/context/token-budget.js +282 -0
- package/dist/context/token-budget.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +944 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +66 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +946 -0
- package/dist/install.js.map +1 -0
- package/dist/knowledge-graph/architecture.d.ts +213 -0
- package/dist/knowledge-graph/architecture.d.ts.map +1 -0
- package/dist/knowledge-graph/architecture.js +585 -0
- package/dist/knowledge-graph/architecture.js.map +1 -0
- package/dist/knowledge-graph/cypher.d.ts +113 -0
- package/dist/knowledge-graph/cypher.d.ts.map +1 -0
- package/dist/knowledge-graph/cypher.js +1051 -0
- package/dist/knowledge-graph/cypher.js.map +1 -0
- package/dist/knowledge-graph/dead-code.d.ts +121 -0
- package/dist/knowledge-graph/dead-code.d.ts.map +1 -0
- package/dist/knowledge-graph/dead-code.js +331 -0
- package/dist/knowledge-graph/dead-code.js.map +1 -0
- package/dist/knowledge-graph/flow-analyzer.d.ts +167 -0
- package/dist/knowledge-graph/flow-analyzer.d.ts.map +1 -0
- package/dist/knowledge-graph/flow-analyzer.js +739 -0
- package/dist/knowledge-graph/flow-analyzer.js.map +1 -0
- package/dist/knowledge-graph/graph.d.ts +291 -0
- package/dist/knowledge-graph/graph.d.ts.map +1 -0
- package/dist/knowledge-graph/graph.js +978 -0
- package/dist/knowledge-graph/graph.js.map +1 -0
- package/dist/knowledge-graph/index.d.ts +17 -0
- package/dist/knowledge-graph/index.d.ts.map +1 -0
- package/dist/knowledge-graph/index.js +14 -0
- package/dist/knowledge-graph/index.js.map +1 -0
- package/dist/knowledge-graph/indexer.d.ts +112 -0
- package/dist/knowledge-graph/indexer.d.ts.map +1 -0
- package/dist/knowledge-graph/indexer.js +506 -0
- package/dist/knowledge-graph/indexer.js.map +1 -0
- package/dist/knowledge-graph/pagerank.d.ts +141 -0
- package/dist/knowledge-graph/pagerank.d.ts.map +1 -0
- package/dist/knowledge-graph/pagerank.js +493 -0
- package/dist/knowledge-graph/pagerank.js.map +1 -0
- package/dist/knowledge-graph/parser.d.ts +55 -0
- package/dist/knowledge-graph/parser.d.ts.map +1 -0
- package/dist/knowledge-graph/parser.js +1090 -0
- package/dist/knowledge-graph/parser.js.map +1 -0
- package/dist/knowledge-graph/snapshot.d.ts +107 -0
- package/dist/knowledge-graph/snapshot.d.ts.map +1 -0
- package/dist/knowledge-graph/snapshot.js +435 -0
- package/dist/knowledge-graph/snapshot.js.map +1 -0
- package/dist/memory/decision-log.d.ts +151 -0
- package/dist/memory/decision-log.d.ts.map +1 -0
- package/dist/memory/decision-log.js +482 -0
- package/dist/memory/decision-log.js.map +1 -0
- package/dist/memory/persistent-memory.d.ts +182 -0
- package/dist/memory/persistent-memory.d.ts.map +1 -0
- package/dist/memory/persistent-memory.js +579 -0
- package/dist/memory/persistent-memory.js.map +1 -0
- package/dist/memory/session-memory.d.ts +165 -0
- package/dist/memory/session-memory.d.ts.map +1 -0
- package/dist/memory/session-memory.js +382 -0
- package/dist/memory/session-memory.js.map +1 -0
- package/dist/stress-test.d.ts +10 -0
- package/dist/stress-test.d.ts.map +1 -0
- package/dist/stress-test.js +258 -0
- package/dist/stress-test.js.map +1 -0
- package/dist/tools/advanced-tools.d.ts +32 -0
- package/dist/tools/advanced-tools.d.ts.map +1 -0
- package/dist/tools/advanced-tools.js +480 -0
- package/dist/tools/advanced-tools.js.map +1 -0
- package/dist/tools/change-tools.d.ts +76 -0
- package/dist/tools/change-tools.d.ts.map +1 -0
- package/dist/tools/change-tools.js +93 -0
- package/dist/tools/change-tools.js.map +1 -0
- package/dist/tools/context-tools.d.ts +68 -0
- package/dist/tools/context-tools.d.ts.map +1 -0
- package/dist/tools/context-tools.js +141 -0
- package/dist/tools/context-tools.js.map +1 -0
- package/dist/tools/debug-tools.d.ts +25 -0
- package/dist/tools/debug-tools.d.ts.map +1 -0
- package/dist/tools/debug-tools.js +286 -0
- package/dist/tools/debug-tools.js.map +1 -0
- package/dist/tools/evolving-tools.d.ts +23 -0
- package/dist/tools/evolving-tools.d.ts.map +1 -0
- package/dist/tools/evolving-tools.js +207 -0
- package/dist/tools/evolving-tools.js.map +1 -0
- package/dist/tools/flow-tools.d.ts +24 -0
- package/dist/tools/flow-tools.d.ts.map +1 -0
- package/dist/tools/flow-tools.js +265 -0
- package/dist/tools/flow-tools.js.map +1 -0
- package/dist/tools/graph-tools.d.ts +71 -0
- package/dist/tools/graph-tools.d.ts.map +1 -0
- package/dist/tools/graph-tools.js +165 -0
- package/dist/tools/graph-tools.js.map +1 -0
- package/dist/tools/memory-tools.d.ts +62 -0
- package/dist/tools/memory-tools.d.ts.map +1 -0
- package/dist/tools/memory-tools.js +195 -0
- package/dist/tools/memory-tools.js.map +1 -0
- package/dist/tools/smart-tools.d.ts +23 -0
- package/dist/tools/smart-tools.d.ts.map +1 -0
- package/dist/tools/smart-tools.js +482 -0
- package/dist/tools/smart-tools.js.map +1 -0
- package/dist/tools/snapshot-tools.d.ts +19 -0
- package/dist/tools/snapshot-tools.d.ts.map +1 -0
- package/dist/tools/snapshot-tools.js +149 -0
- package/dist/tools/snapshot-tools.js.map +1 -0
- package/dist/types.d.ts +181 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +45 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/logger.d.ts +59 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +142 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/token-counter.d.ts +51 -0
- package/dist/utils/token-counter.d.ts.map +1 -0
- package/dist/utils/token-counter.js +181 -0
- package/dist/utils/token-counter.js.map +1 -0
- package/install.ps1 +321 -0
- package/install.sh +345 -0
- package/package.json +94 -0
- package/setup.bat +62 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,937 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AI Mind Map — CLI Interface
|
|
4
|
+
*
|
|
5
|
+
* Full command-line interface inspired by codebase-memory-mcp's CLI mode.
|
|
6
|
+
* Parses process.argv manually (no external CLI library). Each command
|
|
7
|
+
* initialises only the components it needs — no full MCP server boot for
|
|
8
|
+
* simple queries.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* ai-mind-map <command> [options]
|
|
12
|
+
*
|
|
13
|
+
* @module cli
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import process from 'node:process';
|
|
18
|
+
import Database from 'better-sqlite3';
|
|
19
|
+
import { loadConfig } from './config.js';
|
|
20
|
+
import { DEFAULT_CONFIG } from './types.js';
|
|
21
|
+
import { KnowledgeGraph } from './knowledge-graph/graph.js';
|
|
22
|
+
import { Indexer } from './knowledge-graph/indexer.js';
|
|
23
|
+
import { PersistentMemory } from './memory/persistent-memory.js';
|
|
24
|
+
import { DecisionLog } from './memory/decision-log.js';
|
|
25
|
+
import { ChangeLog } from './change-tracker/change-log.js';
|
|
26
|
+
import { installAgents, uninstallAgents, runDoctor, } from './install.js';
|
|
27
|
+
// ============================================================
|
|
28
|
+
// ANSI Color Helpers
|
|
29
|
+
// ============================================================
|
|
30
|
+
const supportsColor = process.stdout.isTTY !== false;
|
|
31
|
+
/** ANSI escape codes for terminal coloring */
|
|
32
|
+
const c = {
|
|
33
|
+
reset: supportsColor ? '\x1b[0m' : '',
|
|
34
|
+
bold: supportsColor ? '\x1b[1m' : '',
|
|
35
|
+
dim: supportsColor ? '\x1b[2m' : '',
|
|
36
|
+
underline: supportsColor ? '\x1b[4m' : '',
|
|
37
|
+
red: supportsColor ? '\x1b[31m' : '',
|
|
38
|
+
green: supportsColor ? '\x1b[32m' : '',
|
|
39
|
+
yellow: supportsColor ? '\x1b[33m' : '',
|
|
40
|
+
blue: supportsColor ? '\x1b[34m' : '',
|
|
41
|
+
magenta: supportsColor ? '\x1b[35m' : '',
|
|
42
|
+
cyan: supportsColor ? '\x1b[36m' : '',
|
|
43
|
+
white: supportsColor ? '\x1b[37m' : '',
|
|
44
|
+
bgRed: supportsColor ? '\x1b[41m' : '',
|
|
45
|
+
bgGreen: supportsColor ? '\x1b[42m' : '',
|
|
46
|
+
bgBlue: supportsColor ? '\x1b[44m' : '',
|
|
47
|
+
gray: supportsColor ? '\x1b[90m' : '',
|
|
48
|
+
};
|
|
49
|
+
function success(msg) {
|
|
50
|
+
console.log(`${c.green}✔${c.reset} ${msg}`);
|
|
51
|
+
}
|
|
52
|
+
function error(msg) {
|
|
53
|
+
console.log(`${c.red}✖${c.reset} ${msg}`);
|
|
54
|
+
}
|
|
55
|
+
function info(msg) {
|
|
56
|
+
console.log(`${c.blue}ℹ${c.reset} ${msg}`);
|
|
57
|
+
}
|
|
58
|
+
function warn(msg) {
|
|
59
|
+
console.log(`${c.yellow}⚠${c.reset} ${msg}`);
|
|
60
|
+
}
|
|
61
|
+
function heading(msg) {
|
|
62
|
+
console.log(`\n${c.bold}${c.cyan}${msg}${c.reset}`);
|
|
63
|
+
}
|
|
64
|
+
function divider() {
|
|
65
|
+
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
66
|
+
}
|
|
67
|
+
/** Pad a string to a fixed width */
|
|
68
|
+
function pad(str, width) {
|
|
69
|
+
return str.length >= width ? str : str + ' '.repeat(width - str.length);
|
|
70
|
+
}
|
|
71
|
+
/** Format a number with commas */
|
|
72
|
+
function formatNum(n) {
|
|
73
|
+
return n.toLocaleString();
|
|
74
|
+
}
|
|
75
|
+
/** Format bytes as human-readable */
|
|
76
|
+
function formatBytes(bytes) {
|
|
77
|
+
if (bytes < 1024)
|
|
78
|
+
return `${bytes} B`;
|
|
79
|
+
if (bytes < 1024 * 1024)
|
|
80
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
81
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
82
|
+
}
|
|
83
|
+
/** Format epoch timestamp as readable date */
|
|
84
|
+
function formatDate(epoch) {
|
|
85
|
+
if (!epoch)
|
|
86
|
+
return 'Never';
|
|
87
|
+
return new Date(epoch).toLocaleString();
|
|
88
|
+
}
|
|
89
|
+
/** Truncate a string with ellipsis */
|
|
90
|
+
function truncate(str, maxLen) {
|
|
91
|
+
if (str.length <= maxLen)
|
|
92
|
+
return str;
|
|
93
|
+
return str.slice(0, maxLen - 1) + '…';
|
|
94
|
+
}
|
|
95
|
+
// ============================================================
|
|
96
|
+
// Package Version
|
|
97
|
+
// ============================================================
|
|
98
|
+
function getVersion() {
|
|
99
|
+
try {
|
|
100
|
+
const pkgPath = path.resolve(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1')), '..', 'package.json');
|
|
101
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
102
|
+
return pkg.version ?? '0.0.0';
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return '1.0.0';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Parse process.argv into structured command, positional args, and flags.
|
|
110
|
+
* Flags can be `--key value` or `--flag` (boolean).
|
|
111
|
+
*/
|
|
112
|
+
function parseArgs(argv) {
|
|
113
|
+
const args = argv.slice(2); // strip node + script path
|
|
114
|
+
const command = (args[0] && !args[0].startsWith('-')) ? args[0] : '';
|
|
115
|
+
const positional = [];
|
|
116
|
+
const flags = {};
|
|
117
|
+
let i = command ? 1 : 0;
|
|
118
|
+
while (i < args.length) {
|
|
119
|
+
const arg = args[i];
|
|
120
|
+
if (arg.startsWith('--')) {
|
|
121
|
+
const key = arg.slice(2);
|
|
122
|
+
const next = args[i + 1];
|
|
123
|
+
if (next && !next.startsWith('-')) {
|
|
124
|
+
flags[key] = next;
|
|
125
|
+
i += 2;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
flags[key] = true;
|
|
129
|
+
i += 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else if (arg.startsWith('-') && arg.length === 2) {
|
|
133
|
+
const key = arg.slice(1);
|
|
134
|
+
const next = args[i + 1];
|
|
135
|
+
if (next && !next.startsWith('-')) {
|
|
136
|
+
flags[key] = next;
|
|
137
|
+
i += 2;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
flags[key] = true;
|
|
141
|
+
i += 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
positional.push(arg);
|
|
146
|
+
i += 1;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { command, positional, flags };
|
|
150
|
+
}
|
|
151
|
+
// ============================================================
|
|
152
|
+
// Component Initialisation Helpers
|
|
153
|
+
// ============================================================
|
|
154
|
+
/**
|
|
155
|
+
* Load config for a given project path.
|
|
156
|
+
* Resolves path and ensures DB directory exists.
|
|
157
|
+
*/
|
|
158
|
+
async function resolveConfig(projectPath) {
|
|
159
|
+
const root = projectPath
|
|
160
|
+
? path.resolve(projectPath)
|
|
161
|
+
: process.cwd();
|
|
162
|
+
const config = await loadConfig({ projectRoot: root, logLevel: 'error' });
|
|
163
|
+
// Ensure DB directory exists
|
|
164
|
+
const dbDir = path.dirname(config.dbPath);
|
|
165
|
+
if (!existsSync(dbDir)) {
|
|
166
|
+
mkdirSync(dbDir, { recursive: true });
|
|
167
|
+
}
|
|
168
|
+
return config;
|
|
169
|
+
}
|
|
170
|
+
/** Open the knowledge graph database */
|
|
171
|
+
function openGraph(config) {
|
|
172
|
+
return new KnowledgeGraph(config.dbPath);
|
|
173
|
+
}
|
|
174
|
+
/** Open a shared SQLite database for memory/decisions */
|
|
175
|
+
function openMemoryDb(config) {
|
|
176
|
+
const db = new Database(config.dbPath);
|
|
177
|
+
db.pragma('journal_mode = WAL');
|
|
178
|
+
return db;
|
|
179
|
+
}
|
|
180
|
+
// ============================================================
|
|
181
|
+
// Command Implementations
|
|
182
|
+
// ============================================================
|
|
183
|
+
/** ai-mind-map serve — Start MCP server (default) */
|
|
184
|
+
async function cmdServe() {
|
|
185
|
+
info('Starting AI Mind Map MCP server...');
|
|
186
|
+
// Dynamic import triggers the MCP server startup as a side effect.
|
|
187
|
+
// index.ts self-executes when imported (registers tools + connects transport).
|
|
188
|
+
await import('./index.js');
|
|
189
|
+
}
|
|
190
|
+
/** ai-mind-map index <project-path> — Index a project */
|
|
191
|
+
async function cmdIndex(args) {
|
|
192
|
+
const projectPath = args.positional[0] || process.cwd();
|
|
193
|
+
heading('🗂️ Indexing Project');
|
|
194
|
+
info(`Project: ${path.resolve(projectPath)}`);
|
|
195
|
+
divider();
|
|
196
|
+
const config = await resolveConfig(projectPath);
|
|
197
|
+
const graph = openGraph(config);
|
|
198
|
+
try {
|
|
199
|
+
const indexer = new Indexer(graph, config);
|
|
200
|
+
const startTime = Date.now();
|
|
201
|
+
const stats = await indexer.fullIndex((progress) => {
|
|
202
|
+
if (progress.phase === 'scanning') {
|
|
203
|
+
process.stdout.write(`\r${c.dim}Scanning files...${c.reset}`);
|
|
204
|
+
}
|
|
205
|
+
else if (progress.phase === 'parsing') {
|
|
206
|
+
const pct = progress.total > 0
|
|
207
|
+
? Math.round((progress.current / progress.total) * 100)
|
|
208
|
+
: 0;
|
|
209
|
+
process.stdout.write(`\r${c.dim}Parsing: ${progress.current}/${progress.total} (${pct}%)${c.reset} `);
|
|
210
|
+
}
|
|
211
|
+
else if (progress.phase === 'storing') {
|
|
212
|
+
process.stdout.write(`\r${c.dim}Storing: ${progress.current}/${progress.total}${c.reset} `);
|
|
213
|
+
}
|
|
214
|
+
else if (progress.phase === 'complete') {
|
|
215
|
+
process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
// Clear the progress line
|
|
219
|
+
process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
220
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
221
|
+
heading('📊 Index Results');
|
|
222
|
+
divider();
|
|
223
|
+
console.log(` ${pad('Files scanned:', 20)} ${c.bold}${formatNum(stats.filesScanned)}${c.reset}`);
|
|
224
|
+
console.log(` ${pad('Files parsed:', 20)} ${c.bold}${formatNum(stats.filesParsed)}${c.reset}`);
|
|
225
|
+
console.log(` ${pad('Files skipped:', 20)} ${formatNum(stats.filesSkipped)}`);
|
|
226
|
+
console.log(` ${pad('Nodes created:', 20)} ${c.green}${formatNum(stats.nodesCreated)}${c.reset}`);
|
|
227
|
+
console.log(` ${pad('Edges created:', 20)} ${c.green}${formatNum(stats.edgesCreated)}${c.reset}`);
|
|
228
|
+
console.log(` ${pad('Parse errors:', 20)} ${stats.parseErrors > 0 ? c.yellow : ''}${formatNum(stats.parseErrors)}${c.reset}`);
|
|
229
|
+
console.log(` ${pad('Duration:', 20)} ${elapsed}s`);
|
|
230
|
+
if (Object.keys(stats.languages).length > 0) {
|
|
231
|
+
console.log(` ${pad('Languages:', 20)}`);
|
|
232
|
+
for (const [lang, count] of Object.entries(stats.languages).sort((a, b) => b[1] - a[1])) {
|
|
233
|
+
console.log(` ${c.cyan}${pad(lang, 16)}${c.reset} ${formatNum(count)} files`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
divider();
|
|
237
|
+
success(`Indexing complete in ${elapsed}s`);
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
graph.close();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/** ai-mind-map search <query> [--type ...] [--limit N] — Search the knowledge graph */
|
|
244
|
+
async function cmdSearch(args) {
|
|
245
|
+
const query = args.positional.join(' ');
|
|
246
|
+
if (!query) {
|
|
247
|
+
error('Usage: ai-mind-map search <query> [--type func|class|...] [--limit N]');
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
const typeFilter = typeof args.flags['type'] === 'string' ? args.flags['type'] : undefined;
|
|
251
|
+
const limit = typeof args.flags['limit'] === 'string' ? parseInt(args.flags['limit'], 10) : 20;
|
|
252
|
+
const config = await resolveConfig();
|
|
253
|
+
const graph = openGraph(config);
|
|
254
|
+
try {
|
|
255
|
+
let results = graph.search(query, limit * 2); // Over-fetch for type filtering
|
|
256
|
+
if (typeFilter) {
|
|
257
|
+
results = results.filter((n) => n.type === typeFilter);
|
|
258
|
+
}
|
|
259
|
+
results = results.slice(0, limit);
|
|
260
|
+
heading(`🔍 Search Results for "${query}"`);
|
|
261
|
+
if (typeFilter)
|
|
262
|
+
info(`Type filter: ${typeFilter}`);
|
|
263
|
+
divider();
|
|
264
|
+
if (results.length === 0) {
|
|
265
|
+
warn('No results found.');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
for (const node of results) {
|
|
269
|
+
const typeColor = getTypeColor(node.type);
|
|
270
|
+
console.log(` ${typeColor}${pad(node.type, 12)}${c.reset} ` +
|
|
271
|
+
`${c.bold}${node.name}${c.reset}` +
|
|
272
|
+
`${c.dim} (${truncate(node.filePath, 40)}:${node.startLine})${c.reset}`);
|
|
273
|
+
if (node.signature) {
|
|
274
|
+
console.log(`${c.dim} ${truncate(node.signature, 60)}${c.reset}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
divider();
|
|
278
|
+
info(`${results.length} result(s) found`);
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
graph.close();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/** Color a node type for display */
|
|
285
|
+
function getTypeColor(type) {
|
|
286
|
+
switch (type) {
|
|
287
|
+
case 'function': return c.yellow;
|
|
288
|
+
case 'class': return c.magenta;
|
|
289
|
+
case 'method': return c.cyan;
|
|
290
|
+
case 'interface': return c.blue;
|
|
291
|
+
case 'type_alias': return c.blue;
|
|
292
|
+
case 'variable': return c.green;
|
|
293
|
+
case 'constant': return c.green;
|
|
294
|
+
case 'file': return c.gray;
|
|
295
|
+
case 'module': return c.gray;
|
|
296
|
+
case 'component': return c.magenta;
|
|
297
|
+
case 'hook': return c.cyan;
|
|
298
|
+
case 'test': return c.yellow;
|
|
299
|
+
default: return c.white;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/** ai-mind-map trace <symbol> [--direction both|callers|callees] [--depth N] */
|
|
303
|
+
async function cmdTrace(args) {
|
|
304
|
+
const symbolName = args.positional[0];
|
|
305
|
+
if (!symbolName) {
|
|
306
|
+
error('Usage: ai-mind-map trace <symbol-name> [--direction both|callers|callees] [--depth N]');
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
const direction = (typeof args.flags['direction'] === 'string' ? args.flags['direction'] : 'both');
|
|
310
|
+
const depth = typeof args.flags['depth'] === 'string' ? parseInt(args.flags['depth'], 10) : 3;
|
|
311
|
+
const config = await resolveConfig();
|
|
312
|
+
const graph = openGraph(config);
|
|
313
|
+
try {
|
|
314
|
+
// Find the symbol node(s)
|
|
315
|
+
const nodes = graph.getNodesByName(symbolName);
|
|
316
|
+
if (nodes.length === 0) {
|
|
317
|
+
warn(`Symbol "${symbolName}" not found in the knowledge graph.`);
|
|
318
|
+
info('Try running "ai-mind-map index" first, or search with "ai-mind-map search".');
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
heading(`🔗 Trace: ${symbolName}`);
|
|
322
|
+
info(`Direction: ${direction} | Depth: ${depth}`);
|
|
323
|
+
divider();
|
|
324
|
+
for (const node of nodes) {
|
|
325
|
+
console.log(`\n ${c.bold}${node.qualifiedName}${c.reset} ` +
|
|
326
|
+
`${c.dim}(${node.type} in ${truncate(node.filePath, 40)}:${node.startLine})${c.reset}`);
|
|
327
|
+
if (direction === 'callers' || direction === 'both') {
|
|
328
|
+
const callers = graph.findCallers(node.id);
|
|
329
|
+
console.log(`\n ${c.cyan}↑ Callers${c.reset} (${callers.length}):`);
|
|
330
|
+
if (callers.length === 0) {
|
|
331
|
+
console.log(` ${c.dim}(none)${c.reset}`);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
for (const caller of callers.slice(0, depth * 5)) {
|
|
335
|
+
console.log(` ${c.green}←${c.reset} ${caller.qualifiedName} ` +
|
|
336
|
+
`${c.dim}(${caller.type}, ${truncate(caller.filePath, 30)}:${caller.startLine})${c.reset}`);
|
|
337
|
+
}
|
|
338
|
+
if (callers.length > depth * 5) {
|
|
339
|
+
console.log(` ${c.dim}… and ${callers.length - depth * 5} more${c.reset}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (direction === 'callees' || direction === 'both') {
|
|
344
|
+
const callees = graph.findCallees(node.id);
|
|
345
|
+
console.log(`\n ${c.cyan}↓ Callees${c.reset} (${callees.length}):`);
|
|
346
|
+
if (callees.length === 0) {
|
|
347
|
+
console.log(` ${c.dim}(none)${c.reset}`);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
for (const callee of callees.slice(0, depth * 5)) {
|
|
351
|
+
console.log(` ${c.green}→${c.reset} ${callee.qualifiedName} ` +
|
|
352
|
+
`${c.dim}(${callee.type}, ${truncate(callee.filePath, 30)}:${callee.startLine})${c.reset}`);
|
|
353
|
+
}
|
|
354
|
+
if (callees.length > depth * 5) {
|
|
355
|
+
console.log(` ${c.dim}… and ${callees.length - depth * 5} more${c.reset}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// Blast radius
|
|
360
|
+
const blast = graph.blastRadius(node.id, depth);
|
|
361
|
+
if (blast.length > 0) {
|
|
362
|
+
console.log(`\n ${c.red}💥 Blast Radius${c.reset} (${blast.length} affected nodes):`);
|
|
363
|
+
for (const affected of blast.slice(0, 10)) {
|
|
364
|
+
console.log(` ${c.yellow}⚡${c.reset} ${affected.qualifiedName} ` +
|
|
365
|
+
`${c.dim}(${affected.type})${c.reset}`);
|
|
366
|
+
}
|
|
367
|
+
if (blast.length > 10) {
|
|
368
|
+
console.log(` ${c.dim}… and ${blast.length - 10} more${c.reset}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
divider();
|
|
373
|
+
}
|
|
374
|
+
finally {
|
|
375
|
+
graph.close();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/** ai-mind-map structure [<project-path>] — Show project structure */
|
|
379
|
+
async function cmdStructure(args) {
|
|
380
|
+
const projectPath = args.positional[0];
|
|
381
|
+
const config = await resolveConfig(projectPath);
|
|
382
|
+
const graph = openGraph(config);
|
|
383
|
+
try {
|
|
384
|
+
const overview = graph.getProjectOverview();
|
|
385
|
+
heading('🏗️ Project Structure');
|
|
386
|
+
info(`Project: ${config.projectRoot}`);
|
|
387
|
+
divider();
|
|
388
|
+
if (overview.size === 0) {
|
|
389
|
+
warn('No indexed files found. Run "ai-mind-map index" first.');
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
for (const [filePath, symbols] of overview) {
|
|
393
|
+
const relPath = path.relative(config.projectRoot, filePath);
|
|
394
|
+
console.log(`\n ${c.bold}${relPath}${c.reset}`);
|
|
395
|
+
if (symbols.length === 0) {
|
|
396
|
+
console.log(` ${c.dim}(no exported symbols)${c.reset}`);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
for (const sym of symbols) {
|
|
400
|
+
const typeColor = getTypeColor(sym.type);
|
|
401
|
+
const asyncMark = sym.isAsync ? `${c.yellow}async ${c.reset}` : '';
|
|
402
|
+
const exportMark = sym.isExported ? `${c.green}↗${c.reset} ` : ' ';
|
|
403
|
+
console.log(` ${exportMark}${typeColor}${pad(sym.type, 10)}${c.reset} ` +
|
|
404
|
+
`${asyncMark}${c.bold}${sym.name}${c.reset}` +
|
|
405
|
+
(sym.signature ? ` ${c.dim}${truncate(sym.signature, 45)}${c.reset}` : ''));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
divider();
|
|
410
|
+
info(`${overview.size} file(s) indexed`);
|
|
411
|
+
}
|
|
412
|
+
finally {
|
|
413
|
+
graph.close();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/** ai-mind-map status [<project-path>] — Show index stats */
|
|
417
|
+
async function cmdStatus(args) {
|
|
418
|
+
const projectPath = args.positional[0];
|
|
419
|
+
const config = await resolveConfig(projectPath);
|
|
420
|
+
heading('📈 AI Mind Map Status');
|
|
421
|
+
info(`Project: ${config.projectRoot}`);
|
|
422
|
+
info(`Database: ${config.dbPath}`);
|
|
423
|
+
divider();
|
|
424
|
+
if (!existsSync(config.dbPath)) {
|
|
425
|
+
warn('Database not found. Run "ai-mind-map index" first.');
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const graph = openGraph(config);
|
|
429
|
+
const memDb = openMemoryDb(config);
|
|
430
|
+
try {
|
|
431
|
+
// Graph stats
|
|
432
|
+
const graphStats = graph.getStats();
|
|
433
|
+
console.log(`\n ${c.bold}Knowledge Graph${c.reset}`);
|
|
434
|
+
console.log(` ${pad('Files:', 18)} ${c.bold}${formatNum(graphStats.totalFiles)}${c.reset}`);
|
|
435
|
+
console.log(` ${pad('Nodes:', 18)} ${c.bold}${formatNum(graphStats.totalNodes)}${c.reset}`);
|
|
436
|
+
console.log(` ${pad('Edges:', 18)} ${c.bold}${formatNum(graphStats.totalEdges)}${c.reset}`);
|
|
437
|
+
if (Object.keys(graphStats.nodesByType).length > 0) {
|
|
438
|
+
console.log(` ${pad('By type:', 18)}`);
|
|
439
|
+
for (const [type, count] of Object.entries(graphStats.nodesByType).sort((a, b) => b[1] - a[1])) {
|
|
440
|
+
const typeColor = getTypeColor(type);
|
|
441
|
+
console.log(` ${typeColor}${pad(type, 14)}${c.reset} ${formatNum(count)}`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (Object.keys(graphStats.languageBreakdown).length > 0) {
|
|
445
|
+
console.log(` ${pad('Languages:', 18)}`);
|
|
446
|
+
for (const [lang, count] of Object.entries(graphStats.languageBreakdown).sort((a, b) => b[1] - a[1])) {
|
|
447
|
+
console.log(` ${c.cyan}${pad(lang, 14)}${c.reset} ${formatNum(count)} files`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// Memory stats
|
|
451
|
+
try {
|
|
452
|
+
const memory = new PersistentMemory(memDb, config.memory);
|
|
453
|
+
const memStats = memory.getStats();
|
|
454
|
+
console.log(`\n ${c.bold}Memories${c.reset}`);
|
|
455
|
+
console.log(` ${pad('Total:', 18)} ${formatNum(memStats.totalMemories)}`);
|
|
456
|
+
console.log(` ${pad('Avg importance:', 18)} ${memStats.averageImportance.toFixed(2)}`);
|
|
457
|
+
console.log(` ${pad('Total accesses:', 18)} ${formatNum(memStats.totalAccessCount)}`);
|
|
458
|
+
if (Object.keys(memStats.byCategory).length > 0) {
|
|
459
|
+
console.log(` ${pad('By category:', 18)}`);
|
|
460
|
+
for (const [cat, cnt] of Object.entries(memStats.byCategory).sort((a, b) => b[1] - a[1])) {
|
|
461
|
+
console.log(` ${c.magenta}${pad(cat, 16)}${c.reset} ${formatNum(cnt)}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
console.log(`\n ${c.bold}Memories${c.reset} ${c.dim}(not initialised)${c.reset}`);
|
|
467
|
+
}
|
|
468
|
+
// Decision stats
|
|
469
|
+
try {
|
|
470
|
+
const decisions = new DecisionLog(memDb, config.memory);
|
|
471
|
+
const active = decisions.getActiveDecisions();
|
|
472
|
+
const all = decisions.queryDecisions({});
|
|
473
|
+
console.log(`\n ${c.bold}Decisions${c.reset}`);
|
|
474
|
+
console.log(` ${pad('Total:', 18)} ${formatNum(all.length)}`);
|
|
475
|
+
console.log(` ${pad('Active:', 18)} ${c.green}${formatNum(active.length)}${c.reset}`);
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
console.log(`\n ${c.bold}Decisions${c.reset} ${c.dim}(not initialised)${c.reset}`);
|
|
479
|
+
}
|
|
480
|
+
// DB file size
|
|
481
|
+
try {
|
|
482
|
+
const dbStat = statSync(config.dbPath);
|
|
483
|
+
console.log(`\n ${c.bold}Storage${c.reset}`);
|
|
484
|
+
console.log(` ${pad('DB size:', 18)} ${formatBytes(dbStat.size)}`);
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
// skip
|
|
488
|
+
}
|
|
489
|
+
divider();
|
|
490
|
+
}
|
|
491
|
+
finally {
|
|
492
|
+
graph.close();
|
|
493
|
+
memDb.close();
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/** ai-mind-map recall <query> — Search memories */
|
|
497
|
+
async function cmdRecall(args) {
|
|
498
|
+
const query = args.positional.join(' ');
|
|
499
|
+
if (!query) {
|
|
500
|
+
error('Usage: ai-mind-map recall <query>');
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
const limit = typeof args.flags['limit'] === 'string' ? parseInt(args.flags['limit'], 10) : 10;
|
|
504
|
+
const config = await resolveConfig();
|
|
505
|
+
const db = openMemoryDb(config);
|
|
506
|
+
try {
|
|
507
|
+
const memory = new PersistentMemory(db, config.memory);
|
|
508
|
+
const results = memory.queryMemories({ text: query, limit });
|
|
509
|
+
heading(`🧠 Memory Recall: "${query}"`);
|
|
510
|
+
divider();
|
|
511
|
+
if (results.length === 0) {
|
|
512
|
+
warn('No memories found matching your query.');
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
for (const mem of results) {
|
|
516
|
+
const catColor = getCategoryColor(mem.category);
|
|
517
|
+
console.log(`\n ${c.bold}#${mem.id}${c.reset} ` +
|
|
518
|
+
`${catColor}[${mem.category}]${c.reset} ` +
|
|
519
|
+
`${c.dim}importance: ${mem.importance.toFixed(2)} | ` +
|
|
520
|
+
`accessed: ${mem.accessCount}x${c.reset}`);
|
|
521
|
+
console.log(` ${truncate(mem.content, 120)}`);
|
|
522
|
+
if (mem.tags.length > 0) {
|
|
523
|
+
console.log(` ${c.dim}Tags: ${mem.tags.join(', ')}${c.reset}`);
|
|
524
|
+
}
|
|
525
|
+
if (mem.relatedFiles.length > 0) {
|
|
526
|
+
console.log(` ${c.dim}Files: ${mem.relatedFiles.map((f) => truncate(f, 30)).join(', ')}${c.reset}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
divider();
|
|
530
|
+
info(`${results.length} memor${results.length === 1 ? 'y' : 'ies'} found`);
|
|
531
|
+
}
|
|
532
|
+
finally {
|
|
533
|
+
db.close();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
/** Color a memory category for display */
|
|
537
|
+
function getCategoryColor(cat) {
|
|
538
|
+
switch (cat) {
|
|
539
|
+
case 'architecture': return c.magenta;
|
|
540
|
+
case 'convention': return c.cyan;
|
|
541
|
+
case 'decision': return c.blue;
|
|
542
|
+
case 'gotcha': return c.red;
|
|
543
|
+
case 'dependency': return c.yellow;
|
|
544
|
+
case 'workflow': return c.green;
|
|
545
|
+
case 'context': return c.white;
|
|
546
|
+
case 'preference': return c.gray;
|
|
547
|
+
case 'lesson_learned': return c.yellow;
|
|
548
|
+
case 'todo': return c.cyan;
|
|
549
|
+
default: return c.white;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/** ai-mind-map remember <content> --category <cat> — Store a memory */
|
|
553
|
+
async function cmdRemember(args) {
|
|
554
|
+
const content = args.positional.join(' ');
|
|
555
|
+
if (!content) {
|
|
556
|
+
error('Usage: ai-mind-map remember <content> --category <category>');
|
|
557
|
+
process.exit(1);
|
|
558
|
+
}
|
|
559
|
+
const category = (typeof args.flags['category'] === 'string'
|
|
560
|
+
? args.flags['category']
|
|
561
|
+
: 'convention');
|
|
562
|
+
const validCategories = [
|
|
563
|
+
'architecture', 'convention', 'decision', 'gotcha', 'dependency',
|
|
564
|
+
'workflow', 'context', 'preference', 'lesson_learned', 'todo',
|
|
565
|
+
];
|
|
566
|
+
if (!validCategories.includes(category)) {
|
|
567
|
+
error(`Invalid category: "${category}". Valid: ${validCategories.join(', ')}`);
|
|
568
|
+
process.exit(1);
|
|
569
|
+
}
|
|
570
|
+
const tagsRaw = typeof args.flags['tags'] === 'string' ? args.flags['tags'] : '';
|
|
571
|
+
const tags = tagsRaw ? tagsRaw.split(',').map((t) => t.trim()) : [];
|
|
572
|
+
const config = await resolveConfig();
|
|
573
|
+
const db = openMemoryDb(config);
|
|
574
|
+
try {
|
|
575
|
+
const memory = new PersistentMemory(db, config.memory);
|
|
576
|
+
const created = memory.createMemory({
|
|
577
|
+
category,
|
|
578
|
+
content,
|
|
579
|
+
tags,
|
|
580
|
+
source: 'user',
|
|
581
|
+
});
|
|
582
|
+
heading('💾 Memory Stored');
|
|
583
|
+
divider();
|
|
584
|
+
console.log(` ${pad('ID:', 14)} ${c.bold}#${created.id}${c.reset}`);
|
|
585
|
+
console.log(` ${pad('Category:', 14)} ${getCategoryColor(created.category)}${created.category}${c.reset}`);
|
|
586
|
+
console.log(` ${pad('Importance:', 14)} ${created.importance.toFixed(2)}`);
|
|
587
|
+
console.log(` ${pad('Content:', 14)} ${truncate(created.content, 80)}`);
|
|
588
|
+
if (created.tags.length > 0) {
|
|
589
|
+
console.log(` ${pad('Tags:', 14)} ${created.tags.join(', ')}`);
|
|
590
|
+
}
|
|
591
|
+
divider();
|
|
592
|
+
success('Memory saved successfully');
|
|
593
|
+
}
|
|
594
|
+
finally {
|
|
595
|
+
db.close();
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
/** ai-mind-map decisions [--status active] — List decisions */
|
|
599
|
+
async function cmdDecisions(args) {
|
|
600
|
+
const statusFilter = typeof args.flags['status'] === 'string'
|
|
601
|
+
? args.flags['status']
|
|
602
|
+
: undefined;
|
|
603
|
+
const config = await resolveConfig();
|
|
604
|
+
const db = openMemoryDb(config);
|
|
605
|
+
try {
|
|
606
|
+
const decisionLog = new DecisionLog(db, config.memory);
|
|
607
|
+
const decisions = decisionLog.queryDecisions({
|
|
608
|
+
status: statusFilter,
|
|
609
|
+
limit: 50,
|
|
610
|
+
});
|
|
611
|
+
heading('📋 Decision Log');
|
|
612
|
+
if (statusFilter)
|
|
613
|
+
info(`Filter: status = ${statusFilter}`);
|
|
614
|
+
divider();
|
|
615
|
+
if (decisions.length === 0) {
|
|
616
|
+
warn('No decisions found.');
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
for (const d of decisions) {
|
|
620
|
+
const statusColor = d.status === 'active' ? c.green
|
|
621
|
+
: d.status === 'superseded' ? c.yellow
|
|
622
|
+
: c.red;
|
|
623
|
+
console.log(`\n ${c.bold}#${d.id}${c.reset} ${statusColor}[${d.status}]${c.reset} ${c.bold}${d.title}${c.reset}`);
|
|
624
|
+
console.log(` ${c.dim}${truncate(d.description, 100)}${c.reset}`);
|
|
625
|
+
console.log(` ${c.dim}Rationale: ${truncate(d.rationale, 80)}${c.reset}`);
|
|
626
|
+
console.log(` ${c.dim}Decided: ${formatDate(d.decidedAt)} by ${d.decidedBy}${c.reset}`);
|
|
627
|
+
if (d.tags.length > 0) {
|
|
628
|
+
console.log(` ${c.dim}Tags: ${d.tags.join(', ')}${c.reset}`);
|
|
629
|
+
}
|
|
630
|
+
if (d.alternatives.length > 0) {
|
|
631
|
+
console.log(` ${c.dim}Alternatives: ${d.alternatives.join('; ')}${c.reset}`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
divider();
|
|
635
|
+
info(`${decisions.length} decision(s) total`);
|
|
636
|
+
}
|
|
637
|
+
finally {
|
|
638
|
+
db.close();
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
/** ai-mind-map changes [--since last_session|<ms>] — Show what changed */
|
|
642
|
+
async function cmdChanges(args) {
|
|
643
|
+
const sinceRaw = typeof args.flags['since'] === 'string' ? args.flags['since'] : undefined;
|
|
644
|
+
const limit = typeof args.flags['limit'] === 'string' ? parseInt(args.flags['limit'], 10) : 30;
|
|
645
|
+
const config = await resolveConfig();
|
|
646
|
+
heading('📝 Change History');
|
|
647
|
+
divider();
|
|
648
|
+
if (!existsSync(config.dbPath)) {
|
|
649
|
+
warn('Database not found. Run "ai-mind-map index" first.');
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
try {
|
|
653
|
+
const changeLog = new ChangeLog({ dbPath: config.dbPath });
|
|
654
|
+
let since;
|
|
655
|
+
if (sinceRaw === 'last_session') {
|
|
656
|
+
const latestSession = changeLog.getLatestSession();
|
|
657
|
+
if (latestSession) {
|
|
658
|
+
since = latestSession.startedAt;
|
|
659
|
+
info(`Since last session: ${formatDate(since)}`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
else if (sinceRaw && !isNaN(Number(sinceRaw))) {
|
|
663
|
+
since = Number(sinceRaw);
|
|
664
|
+
}
|
|
665
|
+
const changes = changeLog.queryChanges({ since, limit });
|
|
666
|
+
if (changes.length === 0) {
|
|
667
|
+
warn('No changes recorded.');
|
|
668
|
+
changeLog.close();
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
for (const change of changes) {
|
|
672
|
+
const typeIcon = change.changeType === 'created' ? `${c.green}+`
|
|
673
|
+
: change.changeType === 'modified' ? `${c.yellow}~`
|
|
674
|
+
: change.changeType === 'deleted' ? `${c.red}-`
|
|
675
|
+
: `${c.blue}→`;
|
|
676
|
+
console.log(` ${typeIcon}${c.reset} ${c.bold}${truncate(change.filePath, 50)}${c.reset} ` +
|
|
677
|
+
`${c.dim}${formatDate(change.timestamp)}${c.reset}`);
|
|
678
|
+
if (change.summary) {
|
|
679
|
+
console.log(` ${c.dim}${truncate(change.summary, 80)}${c.reset}`);
|
|
680
|
+
}
|
|
681
|
+
if (change.symbolsAffected.length > 0) {
|
|
682
|
+
console.log(` ${c.dim}Symbols: ${change.symbolsAffected.join(', ')}${c.reset}`);
|
|
683
|
+
}
|
|
684
|
+
if (change.linesAdded > 0 || change.linesRemoved > 0) {
|
|
685
|
+
console.log(` ${c.green}+${change.linesAdded}${c.reset} ` +
|
|
686
|
+
`${c.red}-${change.linesRemoved}${c.reset}`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
// Summary stats
|
|
690
|
+
const stats = changeLog.getStats();
|
|
691
|
+
divider();
|
|
692
|
+
console.log(` Total: ${formatNum(stats.totalChanges)} changes across ` +
|
|
693
|
+
`${formatNum(stats.totalSessions)} sessions`);
|
|
694
|
+
console.log(` All-time: ${c.green}+${formatNum(stats.linesAddedAllTime)}${c.reset} / ` +
|
|
695
|
+
`${c.red}-${formatNum(stats.linesRemovedAllTime)}${c.reset} lines`);
|
|
696
|
+
changeLog.close();
|
|
697
|
+
}
|
|
698
|
+
catch (err) {
|
|
699
|
+
error(`Failed to read change log: ${err instanceof Error ? err.message : String(err)}`);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
/** ai-mind-map config list | set <key> <value> | reset <key> */
|
|
703
|
+
async function cmdConfig(args) {
|
|
704
|
+
const subCommand = args.positional[0] || 'list';
|
|
705
|
+
if (subCommand === 'list') {
|
|
706
|
+
const config = await resolveConfig();
|
|
707
|
+
heading('⚙️ Configuration');
|
|
708
|
+
divider();
|
|
709
|
+
printConfigRecursive(config, '');
|
|
710
|
+
divider();
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
if (subCommand === 'set') {
|
|
714
|
+
const key = args.positional[1];
|
|
715
|
+
const value = args.positional[2];
|
|
716
|
+
if (!key || !value) {
|
|
717
|
+
error('Usage: ai-mind-map config set <key> <value>');
|
|
718
|
+
process.exit(1);
|
|
719
|
+
}
|
|
720
|
+
info(`Setting ${key} = ${value}`);
|
|
721
|
+
info('Note: Runtime config changes are not persisted. Edit .mindmap.json instead.');
|
|
722
|
+
warn(`To persist: add {"${key}": ${JSON.stringify(value)}} to .mindmap.json`);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (subCommand === 'reset') {
|
|
726
|
+
const key = args.positional[1];
|
|
727
|
+
if (!key) {
|
|
728
|
+
error('Usage: ai-mind-map config reset <key>');
|
|
729
|
+
process.exit(1);
|
|
730
|
+
}
|
|
731
|
+
const defaultVal = getNestedValue(DEFAULT_CONFIG, key);
|
|
732
|
+
info(`Default value for "${key}": ${JSON.stringify(defaultVal)}`);
|
|
733
|
+
info('Remove the key from .mindmap.json to reset to default.');
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
error(`Unknown config subcommand: ${subCommand}. Use: list, set, reset`);
|
|
737
|
+
process.exit(1);
|
|
738
|
+
}
|
|
739
|
+
/** Recursively print config key-value pairs */
|
|
740
|
+
function printConfigRecursive(obj, prefix) {
|
|
741
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
742
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
743
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
744
|
+
printConfigRecursive(value, fullKey);
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
const displayValue = Array.isArray(value) && value.length > 5
|
|
748
|
+
? `[${value.slice(0, 5).join(', ')}, … +${value.length - 5}]`
|
|
749
|
+
: JSON.stringify(value);
|
|
750
|
+
console.log(` ${c.cyan}${pad(fullKey, 30)}${c.reset} ${displayValue}`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
/** Get a nested value from an object using dot notation */
|
|
755
|
+
function getNestedValue(obj, path) {
|
|
756
|
+
const parts = path.split('.');
|
|
757
|
+
let current = obj;
|
|
758
|
+
for (const part of parts) {
|
|
759
|
+
if (current === null || typeof current !== 'object')
|
|
760
|
+
return undefined;
|
|
761
|
+
current = current[part];
|
|
762
|
+
}
|
|
763
|
+
return current;
|
|
764
|
+
}
|
|
765
|
+
/** ai-mind-map update — Check for updates */
|
|
766
|
+
async function cmdUpdate() {
|
|
767
|
+
heading('🔄 Update Check');
|
|
768
|
+
divider();
|
|
769
|
+
const currentVersion = getVersion();
|
|
770
|
+
info(`Current version: ${c.bold}${currentVersion}${c.reset}`);
|
|
771
|
+
info('To update, run:');
|
|
772
|
+
console.log(`\n ${c.bold}npm update -g ai-mind-map${c.reset}`);
|
|
773
|
+
console.log(` ${c.dim}or${c.reset}`);
|
|
774
|
+
console.log(` ${c.bold}npm install -g ai-mind-map@latest${c.reset}\n`);
|
|
775
|
+
divider();
|
|
776
|
+
}
|
|
777
|
+
/** ai-mind-map --help — Show help text */
|
|
778
|
+
function showHelp() {
|
|
779
|
+
const version = getVersion();
|
|
780
|
+
console.log(`
|
|
781
|
+
${c.bold}${c.cyan}AI Mind Map${c.reset} ${c.dim}v${version}${c.reset}
|
|
782
|
+
${c.dim}MCP server that reduces AI coding agent token usage by 80-99%.${c.reset}
|
|
783
|
+
|
|
784
|
+
${c.bold}USAGE${c.reset}
|
|
785
|
+
${c.cyan}ai-mind-map${c.reset} <command> [options]
|
|
786
|
+
|
|
787
|
+
${c.bold}COMMANDS${c.reset}
|
|
788
|
+
${c.cyan}serve${c.reset} Start MCP server (default)
|
|
789
|
+
${c.cyan}index${c.reset} <project-path> Index a project's codebase
|
|
790
|
+
${c.cyan}search${c.reset} <query> Search the knowledge graph
|
|
791
|
+
${c.dim}--type <type> Filter: func, class, method, etc.${c.reset}
|
|
792
|
+
${c.dim}--limit <N> Max results (default: 20)${c.reset}
|
|
793
|
+
${c.cyan}trace${c.reset} <symbol-name> Trace symbol dependencies
|
|
794
|
+
${c.dim}--direction <dir> both, callers, or callees (default: both)${c.reset}
|
|
795
|
+
${c.dim}--depth <N> Traversal depth (default: 3)${c.reset}
|
|
796
|
+
${c.cyan}structure${c.reset} [<project-path>] Show project structure overview
|
|
797
|
+
${c.cyan}status${c.reset} [<project-path>] Show index and memory stats
|
|
798
|
+
${c.cyan}recall${c.reset} <query> Search stored memories
|
|
799
|
+
${c.cyan}remember${c.reset} <content> Store a new memory
|
|
800
|
+
${c.dim}--category <cat> Category (default: convention)${c.reset}
|
|
801
|
+
${c.dim}--tags <tag1,tag2> Comma-separated tags${c.reset}
|
|
802
|
+
${c.cyan}decisions${c.reset} List architectural decisions
|
|
803
|
+
${c.dim}--status <status> Filter: active, superseded, reversed${c.reset}
|
|
804
|
+
${c.cyan}changes${c.reset} Show change history
|
|
805
|
+
${c.dim}--since <last_session|epoch> Filter changes since timestamp${c.reset}
|
|
806
|
+
${c.dim}--limit <N> Max results (default: 30)${c.reset}
|
|
807
|
+
|
|
808
|
+
${c.bold}AGENT MANAGEMENT${c.reset}
|
|
809
|
+
${c.cyan}install${c.reset} Auto-detect and configure AI agents
|
|
810
|
+
${c.cyan}uninstall${c.reset} Remove agent configurations
|
|
811
|
+
${c.cyan}doctor${c.reset} Run diagnostics check
|
|
812
|
+
|
|
813
|
+
${c.bold}CONFIGURATION${c.reset}
|
|
814
|
+
${c.cyan}config list${c.reset} Show current configuration
|
|
815
|
+
${c.cyan}config set${c.reset} <key> <value> Set a config value
|
|
816
|
+
${c.cyan}config reset${c.reset} <key> Reset a key to default
|
|
817
|
+
|
|
818
|
+
${c.bold}OTHER${c.reset}
|
|
819
|
+
${c.cyan}update${c.reset} Check for updates
|
|
820
|
+
${c.dim}--help, -h${c.reset} Show this help message
|
|
821
|
+
${c.dim}--version, -v${c.reset} Show version
|
|
822
|
+
|
|
823
|
+
${c.bold}EXAMPLES${c.reset}
|
|
824
|
+
${c.dim}# Index current directory${c.reset}
|
|
825
|
+
${c.cyan}ai-mind-map index .${c.reset}
|
|
826
|
+
|
|
827
|
+
${c.dim}# Search for a function${c.reset}
|
|
828
|
+
${c.cyan}ai-mind-map search "handleAuth" --type function${c.reset}
|
|
829
|
+
|
|
830
|
+
${c.dim}# Trace who calls a function${c.reset}
|
|
831
|
+
${c.cyan}ai-mind-map trace parseConfig --direction callers${c.reset}
|
|
832
|
+
|
|
833
|
+
${c.dim}# Store a convention${c.reset}
|
|
834
|
+
${c.cyan}ai-mind-map remember "Always use snake_case for DB columns" --category convention${c.reset}
|
|
835
|
+
|
|
836
|
+
${c.dim}# Install MCP config for all detected agents${c.reset}
|
|
837
|
+
${c.cyan}ai-mind-map install${c.reset}
|
|
838
|
+
|
|
839
|
+
${c.dim}# Run diagnostics${c.reset}
|
|
840
|
+
${c.cyan}ai-mind-map doctor${c.reset}
|
|
841
|
+
`);
|
|
842
|
+
}
|
|
843
|
+
// ============================================================
|
|
844
|
+
// Main Router
|
|
845
|
+
// ============================================================
|
|
846
|
+
/**
|
|
847
|
+
* Main CLI entry point. Parses arguments and dispatches to the
|
|
848
|
+
* appropriate command handler.
|
|
849
|
+
*/
|
|
850
|
+
export async function main(argv = process.argv) {
|
|
851
|
+
const args = parseArgs(argv);
|
|
852
|
+
// Version flag
|
|
853
|
+
if (args.flags['version'] || args.flags['v']) {
|
|
854
|
+
console.log(getVersion());
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
// Help flag
|
|
858
|
+
if (args.flags['help'] || args.flags['h'] || args.command === 'help') {
|
|
859
|
+
showHelp();
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
try {
|
|
863
|
+
switch (args.command) {
|
|
864
|
+
case '':
|
|
865
|
+
case 'serve':
|
|
866
|
+
await cmdServe();
|
|
867
|
+
break;
|
|
868
|
+
case 'index':
|
|
869
|
+
await cmdIndex(args);
|
|
870
|
+
break;
|
|
871
|
+
case 'search':
|
|
872
|
+
await cmdSearch(args);
|
|
873
|
+
break;
|
|
874
|
+
case 'trace':
|
|
875
|
+
await cmdTrace(args);
|
|
876
|
+
break;
|
|
877
|
+
case 'structure':
|
|
878
|
+
await cmdStructure(args);
|
|
879
|
+
break;
|
|
880
|
+
case 'status':
|
|
881
|
+
await cmdStatus(args);
|
|
882
|
+
break;
|
|
883
|
+
case 'recall':
|
|
884
|
+
await cmdRecall(args);
|
|
885
|
+
break;
|
|
886
|
+
case 'remember':
|
|
887
|
+
await cmdRemember(args);
|
|
888
|
+
break;
|
|
889
|
+
case 'decisions':
|
|
890
|
+
await cmdDecisions(args);
|
|
891
|
+
break;
|
|
892
|
+
case 'changes':
|
|
893
|
+
await cmdChanges(args);
|
|
894
|
+
break;
|
|
895
|
+
case 'install':
|
|
896
|
+
await installAgents();
|
|
897
|
+
break;
|
|
898
|
+
case 'uninstall':
|
|
899
|
+
await uninstallAgents();
|
|
900
|
+
break;
|
|
901
|
+
case 'update':
|
|
902
|
+
await cmdUpdate();
|
|
903
|
+
break;
|
|
904
|
+
case 'config':
|
|
905
|
+
await cmdConfig(args);
|
|
906
|
+
break;
|
|
907
|
+
case 'doctor':
|
|
908
|
+
await runDoctor();
|
|
909
|
+
break;
|
|
910
|
+
default:
|
|
911
|
+
error(`Unknown command: "${args.command}"`);
|
|
912
|
+
info('Run "ai-mind-map --help" to see available commands.');
|
|
913
|
+
process.exit(1);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
catch (err) {
|
|
917
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
918
|
+
error(`Command failed: ${message}`);
|
|
919
|
+
if (err instanceof Error && err.stack && process.env.DEBUG) {
|
|
920
|
+
console.log(`${c.dim}${err.stack}${c.reset}`);
|
|
921
|
+
}
|
|
922
|
+
process.exit(1);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
// ============================================================
|
|
926
|
+
// Auto-run when executed directly
|
|
927
|
+
// ============================================================
|
|
928
|
+
// Detect if this file is being run as the main entry point
|
|
929
|
+
const isMainModule = process.argv[1] &&
|
|
930
|
+
(process.argv[1].endsWith('cli.js') || process.argv[1].endsWith('cli.ts'));
|
|
931
|
+
if (isMainModule) {
|
|
932
|
+
main().catch((err) => {
|
|
933
|
+
console.error('Fatal error:', err);
|
|
934
|
+
process.exit(1);
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
//# sourceMappingURL=cli.js.map
|