monomind 1.17.0 → 1.17.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.
Files changed (91) hide show
  1. package/.claude/agents/engineering/engineering-security-engineer.md +1 -1
  2. package/.claude/commands/mastermind/_repeat.md +4 -0
  3. package/.claude/commands/mastermind/master.md +52 -1
  4. package/.claude/scheduled_tasks.lock +1 -1
  5. package/.claude/skills/mastermind/_repeat.md +2 -0
  6. package/package.json +1 -1
  7. package/packages/@monomind/cli/.claude/agents/engineering/engineering-security-engineer.md +1 -1
  8. package/packages/@monomind/cli/.claude/commands/mastermind/_repeat.md +4 -0
  9. package/packages/@monomind/cli/.claude/commands/mastermind/master.md +52 -1
  10. package/packages/@monomind/cli/.claude/skills/mastermind/_repeat.md +2 -0
  11. package/packages/@monomind/cli/dist/src/__tests__/browse-analyzer.test.js +42 -59
  12. package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +18 -0
  13. package/packages/@monomind/cli/dist/src/browser/dashboard/ui.html +37 -125
  14. package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.d.ts +17 -0
  15. package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.js +320 -0
  16. package/packages/@monomind/cli/dist/src/commands/agent-ops.d.ts +9 -0
  17. package/packages/@monomind/cli/dist/src/commands/agent-ops.js +329 -0
  18. package/packages/@monomind/cli/dist/src/commands/agent.js +5 -907
  19. package/packages/@monomind/cli/dist/src/commands/analyze-ast.d.ts +26 -0
  20. package/packages/@monomind/cli/dist/src/commands/analyze-ast.js +284 -0
  21. package/packages/@monomind/cli/dist/src/commands/analyze-boundaries.d.ts +14 -0
  22. package/packages/@monomind/cli/dist/src/commands/analyze-boundaries.js +295 -0
  23. package/packages/@monomind/cli/dist/src/commands/analyze-diff.d.ts +8 -0
  24. package/packages/@monomind/cli/dist/src/commands/analyze-diff.js +395 -0
  25. package/packages/@monomind/cli/dist/src/commands/analyze-graph.d.ts +14 -0
  26. package/packages/@monomind/cli/dist/src/commands/analyze-graph.js +304 -0
  27. package/packages/@monomind/cli/dist/src/commands/analyze-imports.d.ts +11 -0
  28. package/packages/@monomind/cli/dist/src/commands/analyze-imports.js +287 -0
  29. package/packages/@monomind/cli/dist/src/commands/analyze-symbols.d.ts +14 -0
  30. package/packages/@monomind/cli/dist/src/commands/analyze-symbols.js +302 -0
  31. package/packages/@monomind/cli/dist/src/commands/analyze.d.ts +38 -0
  32. package/packages/@monomind/cli/dist/src/commands/analyze.js +12 -1827
  33. package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.d.ts +26 -0
  34. package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.js +189 -0
  35. package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.d.ts +19 -0
  36. package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +388 -0
  37. package/packages/@monomind/cli/dist/src/commands/doctor.js +51 -942
  38. package/packages/@monomind/cli/dist/src/commands/hive-mind-comms.d.ts +11 -0
  39. package/packages/@monomind/cli/dist/src/commands/hive-mind-comms.js +242 -0
  40. package/packages/@monomind/cli/dist/src/commands/hive-mind-helpers.d.ts +35 -0
  41. package/packages/@monomind/cli/dist/src/commands/hive-mind-helpers.js +203 -0
  42. package/packages/@monomind/cli/dist/src/commands/hive-mind-ops.d.ts +8 -0
  43. package/packages/@monomind/cli/dist/src/commands/hive-mind-ops.js +233 -0
  44. package/packages/@monomind/cli/dist/src/commands/hive-mind-spawn.d.ts +12 -0
  45. package/packages/@monomind/cli/dist/src/commands/hive-mind-spawn.js +274 -0
  46. package/packages/@monomind/cli/dist/src/commands/hive-mind.js +10 -1129
  47. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-commands.d.ts +4 -4
  48. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-commands.js +19 -819
  49. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-gaps.d.ts +7 -0
  50. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-gaps.js +334 -0
  51. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-routing.d.ts +7 -0
  52. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-routing.js +399 -0
  53. package/packages/@monomind/cli/dist/src/commands/init-subcommands.d.ts +8 -0
  54. package/packages/@monomind/cli/dist/src/commands/init-subcommands.js +156 -0
  55. package/packages/@monomind/cli/dist/src/commands/init-upgrade.d.ts +6 -0
  56. package/packages/@monomind/cli/dist/src/commands/init-upgrade.js +203 -0
  57. package/packages/@monomind/cli/dist/src/commands/init-wizard.d.ts +6 -0
  58. package/packages/@monomind/cli/dist/src/commands/init-wizard.js +246 -0
  59. package/packages/@monomind/cli/dist/src/commands/init.js +6 -623
  60. package/packages/@monomind/cli/dist/src/commands/memory-admin.d.ts +10 -0
  61. package/packages/@monomind/cli/dist/src/commands/memory-admin.js +433 -0
  62. package/packages/@monomind/cli/dist/src/commands/memory-crud.d.ts +9 -0
  63. package/packages/@monomind/cli/dist/src/commands/memory-crud.js +342 -0
  64. package/packages/@monomind/cli/dist/src/commands/memory-list.d.ts +10 -0
  65. package/packages/@monomind/cli/dist/src/commands/memory-list.js +321 -0
  66. package/packages/@monomind/cli/dist/src/commands/memory-transfer.d.ts +9 -0
  67. package/packages/@monomind/cli/dist/src/commands/memory-transfer.js +372 -0
  68. package/packages/@monomind/cli/dist/src/commands/memory.d.ts +6 -0
  69. package/packages/@monomind/cli/dist/src/commands/memory.js +10 -1441
  70. package/packages/@monomind/cli/dist/src/commands/neural-core.d.ts +8 -0
  71. package/packages/@monomind/cli/dist/src/commands/neural-core.js +274 -0
  72. package/packages/@monomind/cli/dist/src/commands/neural-optimize.d.ts +7 -0
  73. package/packages/@monomind/cli/dist/src/commands/neural-optimize.js +332 -0
  74. package/packages/@monomind/cli/dist/src/commands/neural-registry.d.ts +7 -0
  75. package/packages/@monomind/cli/dist/src/commands/neural-registry.js +290 -0
  76. package/packages/@monomind/cli/dist/src/commands/neural.js +3 -974
  77. package/packages/@monomind/cli/dist/src/commands/platforms.js +327 -7
  78. package/packages/@monomind/cli/dist/src/commands/security-cve.d.ts +6 -0
  79. package/packages/@monomind/cli/dist/src/commands/security-cve.js +310 -0
  80. package/packages/@monomind/cli/dist/src/commands/security-misc.d.ts +9 -0
  81. package/packages/@monomind/cli/dist/src/commands/security-misc.js +293 -0
  82. package/packages/@monomind/cli/dist/src/commands/security-scan.d.ts +18 -0
  83. package/packages/@monomind/cli/dist/src/commands/security-scan.js +328 -0
  84. package/packages/@monomind/cli/dist/src/commands/security.js +3 -958
  85. package/packages/@monomind/cli/dist/src/commands/session.js +1 -1
  86. package/packages/@monomind/cli/dist/src/commands/swarm.js +23 -17
  87. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +77 -0
  88. package/packages/@monomind/cli/dist/src/parser.js +11 -6
  89. package/packages/@monomind/cli/dist/src/routing/llm-caller.js +1 -2
  90. package/packages/@monomind/cli/package.json +2 -3
  91. package/packages/@monomind/cli/scripts/understand-analyze.mjs +1 -1
@@ -5,990 +5,107 @@
5
5
  * github.com/monoes/monomind
6
6
  */
7
7
  import { output } from '../output.js';
8
- import { existsSync, readFileSync, statSync } from 'fs';
9
- import { join, dirname } from 'path';
10
- const MAX_DOCTOR_PKG_BYTES = 1024 * 1024; // 1 MB — package.json / settings.json
11
- const MAX_DOCTOR_CONFIG_BYTES = 10 * 1024 * 1024; // 10 MB — monomind.config.json / MCP configs
12
- const MAX_DOCTOR_GITIGNORE_BYTES = 512 * 1024; // 512 KB — .gitignore
13
- const MAX_DOCTOR_PID_BYTES = 64; // 64 bytes — daemon PID file
14
- const MAX_DOCTOR_HELPER_BYTES = 2 * 1024 * 1024; // 2 MB — hook helper .cjs bundles
15
- import { fileURLToPath } from 'url';
16
- import { execSync, exec } from 'child_process';
17
- import { homedir } from 'os';
18
- import { promisify } from 'util';
19
- // Promisified exec with proper shell and env inheritance for cross-platform support
20
- const execAsync = promisify(exec);
21
- /**
22
- * Execute command asynchronously with proper environment inheritance
23
- * Critical for Windows where PATH may not be inherited properly
24
- */
25
- async function runCommand(command, timeoutMs = 5000) {
26
- const { stdout } = await execAsync(command, {
27
- encoding: 'utf8',
28
- timeout: timeoutMs,
29
- shell: process.platform === 'win32' ? 'cmd.exe' : '/bin/sh', // Use proper shell per platform
30
- env: { ...process.env }, // Explicitly inherit full environment
31
- windowsHide: true, // Hide window on Windows
32
- });
33
- return stdout.trim();
34
- }
35
- // Check Node.js version
36
- async function checkNodeVersion() {
37
- const requiredMajor = 20;
38
- const version = process.version;
39
- const major = parseInt(version.slice(1).split('.')[0], 10);
40
- if (major >= requiredMajor) {
41
- return { name: 'Node.js Version', status: 'pass', message: `${version} (>= ${requiredMajor} required)` };
42
- }
43
- else if (major >= 18) {
44
- return { name: 'Node.js Version', status: 'warn', message: `${version} (>= ${requiredMajor} recommended)`, fix: 'nvm install 20 && nvm use 20' };
45
- }
46
- else {
47
- return { name: 'Node.js Version', status: 'fail', message: `${version} (>= ${requiredMajor} required)`, fix: 'nvm install 20 && nvm use 20' };
48
- }
49
- }
50
- // Check npm version (async with proper env inheritance)
51
- async function checkNpmVersion() {
52
- try {
53
- const version = await runCommand('npm --version');
54
- const major = parseInt(version.split('.')[0], 10);
55
- if (major >= 9) {
56
- return { name: 'npm Version', status: 'pass', message: `v${version}` };
57
- }
58
- else {
59
- return { name: 'npm Version', status: 'warn', message: `v${version} (>= 9 recommended)`, fix: 'npm install -g npm@latest' };
60
- }
61
- }
62
- catch {
63
- return { name: 'npm Version', status: 'fail', message: 'npm not found', fix: 'Install Node.js from https://nodejs.org' };
64
- }
65
- }
66
- // Check config file
67
- async function checkConfigFile() {
68
- // JSON configs (parse-validated)
69
- const jsonPaths = [
70
- '.monomind/config.json',
71
- 'monomind.config.json',
72
- '.monomind.json'
73
- ];
74
- for (const configPath of jsonPaths) {
75
- if (existsSync(configPath) && statSync(configPath).size <= MAX_DOCTOR_CONFIG_BYTES) {
76
- try {
77
- const content = readFileSync(configPath, 'utf8');
78
- JSON.parse(content);
79
- return { name: 'Config File', status: 'pass', message: `Found: ${configPath}` };
80
- }
81
- catch (e) {
82
- return { name: 'Config File', status: 'fail', message: `Invalid JSON: ${configPath}`, fix: 'Fix JSON syntax in config file' };
83
- }
84
- }
85
- }
86
- // YAML configs (existence-checked only — no heavy yaml parser dependency)
87
- const yamlPaths = [
88
- '.monomind/config.yaml',
89
- '.monomind/config.yml',
90
- 'monomind.config.yaml'
91
- ];
92
- for (const configPath of yamlPaths) {
93
- if (existsSync(configPath)) {
94
- return { name: 'Config File', status: 'pass', message: `Found: ${configPath}` };
95
- }
96
- }
97
- return { name: 'Config File', status: 'warn', message: 'No config file (using defaults)', fix: 'monomind config init' };
98
- }
99
- // Check daemon status
100
- async function checkDaemonStatus() {
101
- try {
102
- const pidFile = '.monomind/daemon.pid';
103
- if (existsSync(pidFile) && statSync(pidFile).size <= MAX_DOCTOR_PID_BYTES) {
104
- const pid = readFileSync(pidFile, 'utf8').trim();
105
- try {
106
- process.kill(parseInt(pid, 10), 0); // Check if process exists
107
- return { name: 'Daemon Status', status: 'pass', message: `Running (PID: ${pid})` };
108
- }
109
- catch {
110
- return { name: 'Daemon Status', status: 'warn', message: 'Stale PID file', fix: 'rm .monomind/daemon.pid && monomind daemon start' };
111
- }
112
- }
113
- return { name: 'Daemon Status', status: 'warn', message: 'Not running', fix: 'monomind daemon start' };
114
- }
115
- catch {
116
- return { name: 'Daemon Status', status: 'warn', message: 'Unable to check', fix: 'monomind daemon status' };
117
- }
118
- }
119
- // Check memory database
120
- async function checkMemoryDatabase() {
121
- const dbPaths = [
122
- '.monomind/memory.db',
123
- '.swarm/memory.db',
124
- 'data/memory.db'
125
- ];
126
- for (const dbPath of dbPaths) {
127
- if (existsSync(dbPath)) {
128
- try {
129
- const stats = statSync(dbPath);
130
- const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
131
- return { name: 'Memory Database', status: 'pass', message: `${dbPath} (${sizeMB} MB)` };
132
- }
133
- catch {
134
- return { name: 'Memory Database', status: 'warn', message: `${dbPath} (unable to stat)` };
135
- }
136
- }
137
- }
138
- return { name: 'Memory Database', status: 'warn', message: 'Not initialized', fix: 'monomind memory configure --backend hybrid' };
139
- }
140
- // Check API keys
141
- async function checkApiKeys() {
142
- const keys = ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY', 'OPENAI_API_KEY'];
143
- const found = [];
144
- for (const key of keys) {
145
- if (process.env[key]) {
146
- found.push(key);
147
- }
148
- }
149
- // Detect Claude Code environment — API keys are managed internally
150
- const inClaudeCode = !!(process.env.CLAUDE_CODE || process.env.CLAUDE_PROJECT_DIR || process.env.MCP_SESSION_ID);
151
- // Detect claude CLI on PATH — monomind uses claude --print, no direct API key needed
152
- let claudeCliAvailable = false;
153
- try {
154
- execSync('claude --version', { encoding: 'utf-8', stdio: 'pipe', timeout: 5000, windowsHide: true });
155
- claudeCliAvailable = true;
156
- }
157
- catch { /* not on PATH */ }
158
- if (found.includes('ANTHROPIC_API_KEY') || found.includes('CLAUDE_API_KEY')) {
159
- return { name: 'API Keys', status: 'pass', message: `Found: ${found.join(', ')}` };
160
- }
161
- else if (inClaudeCode) {
162
- return { name: 'API Keys', status: 'pass', message: 'Claude Code manages auth (no direct API key needed)' };
163
- }
164
- else if (claudeCliAvailable) {
165
- return { name: 'API Keys', status: 'pass', message: 'Using Claude Code CLI auth (no direct API key needed)' };
166
- }
167
- else if (found.length > 0) {
168
- return { name: 'API Keys', status: 'warn', message: `Found: ${found.join(', ')} (no Claude key)`, fix: 'export ANTHROPIC_API_KEY=your_key' };
169
- }
170
- else {
171
- return {
172
- name: 'API Keys',
173
- status: 'warn',
174
- message: 'Claude Code CLI not found — monomind works best on top of Claude Code',
175
- fix: 'npm install -g @anthropic-ai/claude-code # then: claude login',
176
- };
177
- }
178
- }
179
- // Check git (async with proper env inheritance)
180
- async function checkGit() {
181
- try {
182
- const version = await runCommand('git --version');
183
- return { name: 'Git', status: 'pass', message: version.replace('git version ', 'v') };
184
- }
185
- catch {
186
- return { name: 'Git', status: 'warn', message: 'Not installed', fix: 'Install git from https://git-scm.com' };
187
- }
188
- }
189
- // Check if in git repo (async with proper env inheritance)
190
- async function checkGitRepo() {
191
- try {
192
- await runCommand('git rev-parse --git-dir');
193
- return { name: 'Git Repository', status: 'pass', message: 'In a git repository' };
194
- }
195
- catch {
196
- return { name: 'Git Repository', status: 'warn', message: 'Not a git repository', fix: 'git init' };
197
- }
198
- }
199
- // Check MCP servers
200
- async function checkMcpServers() {
201
- const mcpConfigPaths = [
202
- join(homedir(), '.claude/claude_desktop_config.json'),
203
- join(homedir(), '.config/claude/mcp.json'),
204
- '.mcp.json',
205
- // Claude Code local/project scope stores MCP servers in settings files
206
- '.claude/settings.json',
207
- '.claude/settings.local.json',
208
- join(homedir(), '.claude/settings.json'),
209
- ];
210
- for (const configPath of mcpConfigPaths) {
211
- if (existsSync(configPath) && statSync(configPath).size <= MAX_DOCTOR_CONFIG_BYTES) {
212
- try {
213
- const content = JSON.parse(readFileSync(configPath, 'utf8'));
214
- const servers = content.mcpServers || content.servers || {};
215
- const count = Object.keys(servers).length;
216
- const hasMonomind = 'monomind' in servers || 'monomind_alpha' in servers;
217
- if (hasMonomind) {
218
- return { name: 'MCP Servers', status: 'pass', message: `${count} servers (monomind configured)` };
219
- }
220
- else {
221
- return { name: 'MCP Servers', status: 'warn', message: `${count} servers (monomind not found)`, fix: 'claude mcp add monomind -- npx -y monomind@latest mcp start' };
222
- }
223
- }
224
- catch {
225
- // continue to next path
226
- }
227
- }
228
- }
229
- return { name: 'MCP Servers', status: 'warn', message: 'No MCP config found', fix: 'claude mcp add monomind -- npx -y monomind@latest mcp start' };
230
- }
231
- // Check disk space (async with proper env inheritance)
232
- async function checkDiskSpace() {
233
- try {
234
- if (process.platform === 'win32') {
235
- return { name: 'Disk Space', status: 'pass', message: 'Check skipped on Windows' };
236
- }
237
- // Use df -Ph for POSIX mode (guarantees single-line output even with long device names)
238
- const output_str = await runCommand('df -Ph . | tail -1');
239
- const parts = output_str.split(/\s+/);
240
- // POSIX format: Filesystem Size Used Avail Capacity Mounted
241
- const available = parts[3];
242
- const usePercent = parseInt(parts[4]?.replace('%', '') || '0', 10);
243
- if (isNaN(usePercent)) {
244
- return { name: 'Disk Space', status: 'warn', message: `${available || 'unknown'} available (unable to parse usage)` };
245
- }
246
- if (usePercent > 90) {
247
- return { name: 'Disk Space', status: 'fail', message: `${available} available (${usePercent}% used)`, fix: 'Free up disk space' };
248
- }
249
- else if (usePercent > 80) {
250
- return { name: 'Disk Space', status: 'warn', message: `${available} available (${usePercent}% used)` };
251
- }
252
- return { name: 'Disk Space', status: 'pass', message: `${available} available` };
253
- }
254
- catch {
255
- return { name: 'Disk Space', status: 'warn', message: 'Unable to check' };
256
- }
257
- }
258
- // Check TypeScript/build (async with proper env inheritance)
259
- async function checkBuildTools() {
260
- try {
261
- const tscVersion = await runCommand('npx tsc --version', 10000); // tsc can be slow
262
- if (!tscVersion || tscVersion.includes('not found')) {
263
- return { name: 'TypeScript', status: 'warn', message: 'Not installed locally', fix: 'npm install -D typescript' };
264
- }
265
- return { name: 'TypeScript', status: 'pass', message: tscVersion.replace('Version ', 'v') };
266
- }
267
- catch {
268
- return { name: 'TypeScript', status: 'warn', message: 'Not installed locally', fix: 'npm install -D typescript' };
269
- }
270
- }
271
- // Check for stale npx cache (version freshness)
272
- async function checkVersionFreshness() {
273
- try {
274
- // Get current CLI version from package.json
275
- // Use import.meta.url to reliably locate our own package.json,
276
- // regardless of how deep the compiled file sits (e.g. dist/src/commands/).
277
- let currentVersion = '0.0.0';
278
- try {
279
- const thisFile = fileURLToPath(import.meta.url);
280
- let dir = dirname(thisFile);
281
- // Walk up from the current file's directory until we find the
282
- // package.json that belongs to @monomind/cli (or monomind/cli).
283
- // Walk until dirname(dir) === dir (filesystem root on any platform).
284
- for (;;) {
285
- const candidate = join(dir, 'package.json');
286
- try {
287
- if (existsSync(candidate) && statSync(candidate).size <= MAX_DOCTOR_PKG_BYTES) {
288
- const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
289
- if (pkg.version &&
290
- typeof pkg.name === 'string' &&
291
- (pkg.name === '@monomind/cli' || pkg.name === 'monomind' || pkg.name === '@monoes/monomindcli')) {
292
- currentVersion = pkg.version;
293
- break;
294
- }
295
- }
296
- }
297
- catch {
298
- // Unreadable/invalid JSON -- skip and keep walking up
299
- }
300
- const parent = dirname(dir);
301
- if (parent === dir)
302
- break; // reached root
303
- dir = parent;
304
- }
305
- }
306
- catch {
307
- // Fall back to a default
308
- currentVersion = '0.0.0';
309
- }
310
- // Check if running via npx (look for _npx in process path or argv)
311
- const isNpx = process.argv[1]?.includes('_npx') ||
312
- process.env.npm_execpath?.includes('npx') ||
313
- process.cwd().includes('_npx');
314
- // Query npm for latest version of the published umbrella package
315
- let latestVersion = currentVersion;
316
- try {
317
- const npmInfo = await runCommand('npm view monomind version', 5000);
318
- latestVersion = npmInfo.trim();
319
- }
320
- catch {
321
- // Can't reach npm registry - skip check
322
- return {
323
- name: 'Version Freshness',
324
- status: 'warn',
325
- message: `v${currentVersion} (cannot check registry)`
326
- };
327
- }
328
- // Parse version numbers for comparison (handle prerelease like 3.0.0-alpha.84)
329
- const parseVersion = (v) => {
330
- const match = v.match(/^(\d+)\.(\d+)\.(\d+)(?:-[a-zA-Z]+\.(\d+))?/);
331
- if (!match)
332
- return { major: 0, minor: 0, patch: 0, prerelease: 0 };
333
- return {
334
- major: parseInt(match[1], 10) || 0,
335
- minor: parseInt(match[2], 10) || 0,
336
- patch: parseInt(match[3], 10) || 0,
337
- prerelease: parseInt(match[4], 10) || 0
338
- };
339
- };
340
- const current = parseVersion(currentVersion);
341
- const latest = parseVersion(latestVersion);
342
- // Compare versions (including prerelease number)
343
- const isOutdated = (latest.major > current.major ||
344
- (latest.major === current.major && latest.minor > current.minor) ||
345
- (latest.major === current.major && latest.minor === current.minor && latest.patch > current.patch) ||
346
- (latest.major === current.major && latest.minor === current.minor && latest.patch === current.patch && latest.prerelease > current.prerelease));
347
- if (isOutdated) {
348
- const fix = isNpx
349
- ? 'rm -rf ~/.npm/_npx/* && npx -y monomind@latest doctor'
350
- : 'npm update -g monomind';
351
- return {
352
- name: 'Version Freshness',
353
- status: 'warn',
354
- message: `v${currentVersion} (latest: v${latestVersion})${isNpx ? ' [npx cache stale]' : ''}`,
355
- fix
356
- };
357
- }
358
- return {
359
- name: 'Version Freshness',
360
- status: 'pass',
361
- message: `v${currentVersion} (up to date)`
362
- };
363
- }
364
- catch (error) {
365
- return {
366
- name: 'Version Freshness',
367
- status: 'warn',
368
- message: 'Unable to check version freshness'
369
- };
370
- }
371
- }
372
- // Check Claude Code CLI (async with proper env inheritance)
373
- async function checkClaudeCode() {
374
- try {
375
- const version = await runCommand('claude --version');
376
- // Parse version from output like "claude 1.0.0" or "Claude Code v1.0.0"
377
- const versionMatch = version.match(/v?(\d+\.\d+\.\d+)/);
378
- const versionStr = versionMatch ? `v${versionMatch[1]}` : version;
379
- return { name: 'Claude Code CLI', status: 'pass', message: versionStr };
380
- }
381
- catch {
382
- return {
383
- name: 'Claude Code CLI',
384
- status: 'warn',
385
- message: 'Not installed',
386
- fix: 'npm install -g @anthropic-ai/claude-code'
387
- };
388
- }
389
- }
390
- // Install Claude Code CLI
391
- async function installClaudeCode() {
392
- try {
393
- output.writeln();
394
- output.writeln(output.bold('Installing Claude Code CLI...'));
395
- execSync('npm install -g @anthropic-ai/claude-code', {
396
- encoding: 'utf8',
397
- stdio: 'inherit'
398
- });
399
- output.writeln(output.success('Claude Code CLI installed successfully!'));
400
- return true;
401
- }
402
- catch (error) {
403
- output.writeln(output.error('Failed to install Claude Code CLI'));
404
- if (error instanceof Error) {
405
- output.writeln(output.dim(error.message));
406
- }
407
- return false;
408
- }
409
- }
410
- // Check monograph (TypeScript knowledge graph engine, bundled with CLI)
411
- async function checkMonograph() {
412
- try {
413
- const __filename = fileURLToPath(import.meta.url);
414
- const _base = dirname(__filename);
415
- let _globalRoot = '';
416
- try {
417
- _globalRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 3000 }).trim();
418
- }
419
- catch { /* no npm */ }
420
- const candidates = [
421
- // local dev monorepo paths (both old @monomind and published @monoes scope)
422
- join(_base, '..', '..', 'node_modules', '@monomind', 'monograph', 'package.json'),
423
- join(_base, '..', '..', '..', '..', 'node_modules', '@monomind', 'monograph', 'package.json'),
424
- join(_base, '..', '..', 'node_modules', '@monoes', 'monograph', 'package.json'),
425
- join(_base, '..', '..', '..', '..', 'node_modules', '@monoes', 'monograph', 'package.json'),
426
- // global install paths
427
- ...(_globalRoot ? [
428
- join(_globalRoot, '@monomind', 'monograph', 'package.json'),
429
- join(_globalRoot, '@monoes', 'monograph', 'package.json'),
430
- ] : []),
431
- ];
432
- const found = candidates.find(p => existsSync(p) && statSync(p).size <= MAX_DOCTOR_PKG_BYTES);
433
- if (found) {
434
- try {
435
- const pkg = JSON.parse(readFileSync(found, 'utf-8'));
436
- return { name: 'Monograph', status: 'pass', message: `v${pkg.version || '?'} available (knowledge graph engine)` };
437
- }
438
- catch {
439
- return { name: 'Monograph', status: 'pass', message: 'available (knowledge graph engine)' };
440
- }
441
- }
442
- return {
443
- name: 'Monograph',
444
- status: 'warn',
445
- message: 'Package not found (knowledge graph disabled)',
446
- fix: 'npm install -g monomind@latest # reinstall to get @monoes/monograph'
447
- };
448
- }
449
- catch {
450
- return {
451
- name: 'Monograph',
452
- status: 'warn',
453
- message: 'Package check failed (knowledge graph may be unavailable)',
454
- fix: 'npm install -g monomind@latest'
455
- };
456
- }
457
- }
458
- // Check monograph graph freshness (is the graph built? how stale?)
459
- async function checkMonographFreshness() {
460
- try {
461
- const cwd = process.cwd();
462
- const dbPath = join(cwd, '.monomind', 'monograph.db');
463
- const lockPath = join(cwd, '.monomind', 'graph', '.rebuild-lock');
464
- const statsPath = join(cwd, '.monomind', 'graph', 'stats.json');
465
- // Check if graph exists at all
466
- const hasDb = existsSync(dbPath);
467
- const hasLock = existsSync(lockPath);
468
- const hasStats = existsSync(statsPath);
469
- if (!hasDb && !hasStats) {
470
- return {
471
- name: 'Graph freshness',
472
- status: 'warn',
473
- message: 'No monograph graph built yet',
474
- fix: 'mcp__monomind__monograph_build codeOnly:true — or run npx monomind@latest hooks graph-status',
475
- };
476
- }
477
- // Determine last build time
478
- let buildMs = 0;
479
- if (hasDb) {
480
- try {
481
- buildMs = Math.max(buildMs, statSync(dbPath).mtimeMs);
482
- }
483
- catch { /* ignore */ }
484
- }
485
- if (hasLock) {
486
- try {
487
- buildMs = Math.max(buildMs, statSync(lockPath).mtimeMs);
488
- }
489
- catch { /* ignore */ }
490
- }
491
- if (hasStats) {
492
- try {
493
- buildMs = Math.max(buildMs, statSync(statsPath).mtimeMs);
494
- }
495
- catch { /* ignore */ }
496
- }
497
- if (buildMs === 0) {
498
- return { name: 'Graph freshness', status: 'warn', message: 'Graph exists but build time unknown' };
499
- }
500
- // Count commits since last build
501
- const buildIso = new Date(buildMs).toISOString();
502
- let commitsBehind = 0;
503
- try {
504
- const out = execSync(`git rev-list --count --since='${buildIso}' HEAD 2>/dev/null`, {
505
- encoding: 'utf8', timeout: 2000, cwd,
506
- }).trim();
507
- commitsBehind = parseInt(out, 10) || 0;
508
- }
509
- catch { /* git not available or not a git repo */ }
510
- const ageMinutes = Math.floor((Date.now() - buildMs) / 60000);
511
- const ageStr = ageMinutes < 60 ? `${ageMinutes}m ago` : `${Math.floor(ageMinutes / 60)}h ago`;
512
- if (commitsBehind === 0) {
513
- return { name: 'Graph freshness', status: 'pass', message: `FRESH — built ${ageStr}, 0 commits behind` };
514
- }
515
- else if (commitsBehind <= 5) {
516
- return {
517
- name: 'Graph freshness',
518
- status: 'warn',
519
- message: `${commitsBehind} commit(s) behind — built ${ageStr}`,
520
- fix: 'mcp__monomind__monograph_build codeOnly:true',
521
- };
522
- }
523
- else {
524
- return {
525
- name: 'Graph freshness',
526
- status: 'fail',
527
- message: `STALE — ${commitsBehind} commits behind (built ${ageStr})`,
528
- fix: 'mcp__monomind__monograph_build codeOnly:true',
529
- };
530
- }
531
- }
532
- catch {
533
- return { name: 'Graph freshness', status: 'warn', message: 'Could not check graph freshness' };
534
- }
535
- }
536
- // Check @monoes/memory (optional HNSW vector search package)
537
- async function checkMonoesMemory() {
538
- try {
539
- const __filename = fileURLToPath(import.meta.url);
540
- const _base = dirname(__filename);
541
- let _globalRoot = '';
542
- try {
543
- _globalRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 3000 }).trim();
544
- }
545
- catch { /* no npm */ }
546
- const candidates = [
547
- join(_base, '..', '..', 'node_modules', '@monoes', 'memory', 'package.json'),
548
- join(_base, '..', '..', '..', '..', 'node_modules', '@monoes', 'memory', 'package.json'),
549
- ...(_globalRoot ? [join(_globalRoot, '@monoes', 'memory', 'package.json')] : []),
550
- ];
551
- const found = candidates.find(p => existsSync(p) && statSync(p).size <= MAX_DOCTOR_PKG_BYTES);
552
- if (found) {
553
- try {
554
- const pkg = JSON.parse(readFileSync(found, 'utf-8'));
555
- return { name: 'Vector Memory', status: 'pass', message: `@monoes/memory v${pkg.version || '?'} (HNSW search enabled)` };
556
- }
557
- catch {
558
- return { name: 'Vector Memory', status: 'pass', message: '@monoes/memory available (HNSW search enabled)' };
559
- }
560
- }
561
- return {
562
- name: 'Vector Memory',
563
- status: 'warn',
564
- message: '@monoes/memory not installed (vector search disabled — using fallback)',
565
- fix: 'npm install @monoes/memory'
566
- };
567
- }
568
- catch {
569
- return { name: 'Vector Memory', status: 'warn', message: 'Vector memory check failed' };
570
- }
571
- }
572
- // Resolve the path to the bundled (npm-installed) copy of a helper file.
573
- // Walks up from this module's location to find the package root, then joins
574
- // the relative path. Returns null if not found.
575
- function _resolveBundledHelper(relativePath) {
576
- try {
577
- const thisFile = fileURLToPath(import.meta.url);
578
- let dir = dirname(thisFile);
579
- for (;;) {
580
- const candidate = join(dir, 'package.json');
581
- if (existsSync(candidate) && statSync(candidate).size <= MAX_DOCTOR_PKG_BYTES) {
582
- try {
583
- const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
584
- if (pkg.name === '@monomind/cli' || pkg.name === 'monomind' || pkg.name === '@monoes/monomindcli') {
585
- const helperPath = join(dir, relativePath);
586
- return existsSync(helperPath) ? helperPath : null;
587
- }
588
- }
589
- catch { /* keep walking */ }
590
- }
591
- const parent = dirname(dir);
592
- if (parent === dir)
593
- return null;
594
- dir = parent;
595
- }
596
- }
597
- catch {
598
- return null;
599
- }
600
- }
601
- // Compare per-project helper file content vs the bundled (npm) version.
602
- // Returns the list of files that drifted.
603
- async function _detectStaleHelpers() {
604
- const stale = [];
605
- const missing = [];
606
- const helpers = ['hook-handler.cjs', 'statusline.cjs', 'router.cjs', 'graphify-freshen.cjs'];
607
- const crypto = await import('node:crypto');
608
- for (const name of helpers) {
609
- const local = join(process.cwd(), '.claude', 'helpers', name);
610
- if (!existsSync(local))
611
- continue;
612
- if (statSync(local).size > MAX_DOCTOR_HELPER_BYTES)
613
- continue; // skip oversized helper
614
- const bundled = _resolveBundledHelper(join('.claude', 'helpers', name));
615
- if (!bundled) {
616
- missing.push(name);
617
- continue;
618
- }
619
- if (statSync(bundled).size > MAX_DOCTOR_HELPER_BYTES)
620
- continue; // skip oversized bundled
621
- try {
622
- const hashLocal = crypto.createHash('sha256').update(readFileSync(local)).digest('hex');
623
- const hashBundled = crypto.createHash('sha256').update(readFileSync(bundled)).digest('hex');
624
- if (hashLocal !== hashBundled)
625
- stale.push(name);
626
- }
627
- catch { /* skip */ }
628
- }
629
- return { stale, missing };
630
- }
631
- async function checkHelpersFresh() {
632
- try {
633
- const { stale, missing } = await _detectStaleHelpers();
634
- if (stale.length === 0 && missing.length === 0) {
635
- return { name: 'Helper Files', status: 'pass', message: 'Project helpers match bundled version' };
636
- }
637
- if (stale.length > 0) {
638
- return {
639
- name: 'Helper Files',
640
- status: 'warn',
641
- message: `${stale.length} stale helper(s) in .claude/helpers/: ${stale.join(', ')}`,
642
- fix: 'monomind init upgrade',
643
- };
644
- }
645
- return {
646
- name: 'Helper Files',
647
- status: 'warn',
648
- message: `Could not locate bundled copies of: ${missing.join(', ')}`,
649
- fix: 'Reinstall monomind or run `monomind init upgrade`',
650
- };
651
- }
652
- catch (e) {
653
- return { name: 'Helper Files', status: 'warn', message: `check failed: ${e instanceof Error ? e.message : 'unknown'}` };
654
- }
655
- }
656
- // Check @monoes native acceleration integration (sona/router/attention/learning-wasm)
657
- // Format a 0..1 accuracy as a whole-percent string, or 'n/a' when null.
658
- function fmtPct(v) {
659
- return v === null ? 'n/a' : `${Math.round(v * 100)}%`;
660
- }
661
- // Render the windowed routing-accuracy metric (C1) as a one-line summary.
662
- // Tells an operator whether routing learning is actually helping.
663
- async function routingAccuracyLine() {
664
- try {
665
- const { computeRoutingAccuracy, computeAdherence } = await import('../monovector/route-outcomes.js');
666
- const baseDir = join(process.cwd(), '.monomind');
667
- const acc = await computeRoutingAccuracy(baseDir, 100);
668
- const adh = await computeAdherence(baseDir);
669
- const adhStr = ` | adherence ${fmtPct(adh.adherence)} (n=${adh.sample})`;
670
- if (acc.accuracy === null) {
671
- return `routing accuracy (last 100): no outcome data yet${adhStr}`;
672
- }
673
- const trend = acc.recentVsPrior === null
674
- ? ''
675
- : ` trend ${acc.recentVsPrior >= 0 ? '+' : ''}${Math.round(acc.recentVsPrior * 100)}%`;
676
- return `routing accuracy (last ${acc.window}): ${fmtPct(acc.accuracy)} ` +
677
- `[native ${fmtPct(acc.byMode.native)} / js ${fmtPct(acc.byMode.js)}]${trend}${adhStr}`;
678
- }
679
- catch {
680
- return 'routing accuracy (last 100): no outcome data yet';
681
- }
682
- }
683
- // Lean teardown: the native @monoes acceleration matrix (sona/router/attention/
684
- // learning-wasm) has been removed. This check now reports only the honest routing
685
- // learning metric — the windowed recommendation→outcome accuracy and adherence,
686
- // which is the lean system's measurable signal.
687
- async function checkMonoesIntegration() {
688
- try {
689
- const accLine = await routingAccuracyLine();
690
- return {
691
- name: 'Routing Learning',
692
- status: 'pass',
693
- message: accLine,
694
- };
695
- }
696
- catch (err) {
697
- return {
698
- name: 'Routing Learning',
699
- status: 'warn',
700
- message: `Could not compute routing accuracy: ${err instanceof Error ? err.message : String(err)}`,
701
- };
702
- }
703
- }
704
- // Patterns that must be covered by .gitignore to prevent leaking session data / machine paths.
705
- // Uses the surgical approach: ignore specific sensitive subdirs and file globs inside .monomind/
706
- // rather than the entire directory, so safe content (orgs/, test-fixtures/) can still be tracked.
707
- const REQUIRED_GITIGNORE_PATTERNS = [
708
- { pattern: '.monomind/sessions/', reason: 'session files contain cwd and machine paths' },
709
- { pattern: '.monomind/data/', reason: 'intelligence data with edit file paths' },
710
- { pattern: '.monomind/metrics/', reason: 'metrics with file path references' },
711
- { pattern: '.monomind/knowledge/', reason: 'knowledge chunks with local file content' },
712
- { pattern: '.monomind/*.json', reason: 'root-level runtime JSON (control, registry, routing)' },
713
- { pattern: '.monomind/*.jsonl', reason: 'root-level event logs (decisions, routing-feedback)' },
714
- { pattern: '**/.monomind/sessions/', reason: 'nested session files in sub-packages' },
715
- { pattern: '**/.monomind/*.json', reason: 'nested runtime JSON in sub-packages' },
716
- { pattern: 'data/sessions/', reason: 'session files with machine paths' },
717
- { pattern: 'data/mastermind-*.json', reason: 'mastermind session data' },
718
- { pattern: 'data/mastermind-*.jsonl', reason: 'mastermind event logs' },
719
- { pattern: '**/.claude-flow/', reason: 'claude-flow runtime data with paths' },
720
- { pattern: '.hive-mind/', reason: 'hive-mind state with session info' },
721
- { pattern: '.swarm/', reason: 'swarm state files' },
722
- ];
723
- // Check whether a gitignore file covers all required monomind runtime patterns
724
- async function checkGitignoreCoverage() {
725
- const gitignorePath = join(process.cwd(), '.gitignore');
726
- if (!existsSync(gitignorePath)) {
727
- return {
728
- name: 'Gitignore Coverage',
729
- status: 'warn',
730
- message: 'No .gitignore found — all monomind runtime paths are unprotected',
731
- fix: 'echo ".monomind/\\n**/.monomind/" >> .gitignore',
732
- };
733
- }
734
- if (statSync(gitignorePath).size > MAX_DOCTOR_GITIGNORE_BYTES) {
735
- return { name: 'Gitignore Coverage', status: 'warn', message: '.gitignore too large to parse' };
736
- }
737
- const content = readFileSync(gitignorePath, 'utf-8');
738
- const lines = content.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
739
- const missing = REQUIRED_GITIGNORE_PATTERNS.filter(({ pattern }) => {
740
- // A pattern is "covered" if the gitignore contains it exactly, or a parent glob covers it
741
- const base = pattern.replace(/\*\*\//g, '').replace(/\*/g, '');
742
- return !lines.some(l => l === pattern ||
743
- l === pattern.replace(/\/$/, '') ||
744
- // e.g. "**/.monomind/" covers ".monomind/"
745
- (l.includes('**') && base && l.replace(/\*\*\//g, '').replace(/\*/g, '') === base));
746
- });
747
- if (missing.length === 0) {
748
- return { name: 'Gitignore Coverage', status: 'pass', message: 'All monomind runtime paths are gitignored' };
749
- }
750
- const missingList = missing.map(m => m.pattern).join(', ');
751
- const fixLines = missing.map(m => m.pattern).join('\\n');
752
- return {
753
- name: 'Gitignore Coverage',
754
- status: 'warn',
755
- message: `${missing.length} runtime path(s) not in .gitignore: ${missingList}`,
756
- fix: `printf "${fixLines}\\n" >> .gitignore`,
757
- };
758
- }
759
- async function checkGuidanceGates() {
760
- const settingsPath = join(process.cwd(), '.claude', 'settings.json');
761
- const gatesHandlerPath = join(process.cwd(), '.claude', 'helpers', 'handlers', 'gates-handler.cjs');
762
- if (!existsSync(gatesHandlerPath)) {
763
- return {
764
- name: 'Guidance Gates',
765
- status: 'warn',
766
- message: 'gates-handler.cjs not found — enforcement gates not installed',
767
- fix: 'monomind init (then monomind guidance setup)',
768
- };
769
- }
770
- if (!existsSync(settingsPath)) {
771
- return {
772
- name: 'Guidance Gates',
773
- status: 'warn',
774
- message: 'gates-handler.cjs present but .claude/settings.json missing — pre-write hook not registered',
775
- fix: 'monomind guidance setup',
776
- };
777
- }
778
- try {
779
- if (statSync(settingsPath).size > MAX_DOCTOR_CONFIG_BYTES) {
780
- return { name: 'Guidance Gates', status: 'warn', message: 'settings.json too large to parse' };
781
- }
782
- const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
783
- const preToolUse = settings?.hooks?.PreToolUse ?? [];
784
- const hasPreWrite = preToolUse.some(e => e.matcher === 'Write|Edit|MultiEdit' && e.hooks.some(h => h.command?.includes('pre-write')));
785
- const hasPreBash = preToolUse.some(e => e.matcher === 'Bash' && e.hooks.some(h => h.command?.includes('pre-bash')));
786
- if (!hasPreWrite && !hasPreBash) {
787
- return {
788
- name: 'Guidance Gates',
789
- status: 'warn',
790
- message: 'gates-handler.cjs present but neither pre-bash nor pre-write hooks registered — no gates active',
791
- fix: 'monomind guidance setup',
792
- };
793
- }
794
- if (!hasPreWrite) {
795
- return {
796
- name: 'Guidance Gates',
797
- status: 'warn',
798
- message: 'gates-handler.cjs present but pre-write hook not in settings.json — secrets gate inactive',
799
- fix: 'monomind guidance setup',
800
- };
801
- }
802
- if (!hasPreBash) {
803
- return {
804
- name: 'Guidance Gates',
805
- status: 'warn',
806
- message: 'gates-handler.cjs present but pre-bash hook not in settings.json — destructive-ops gate inactive',
807
- fix: 'monomind guidance setup',
808
- };
809
- }
810
- return {
811
- name: 'Guidance Gates',
812
- status: 'pass',
813
- message: 'destructive-ops (pre-bash) + secrets (pre-write) gates active',
814
- };
815
- }
816
- catch {
817
- return {
818
- name: 'Guidance Gates',
819
- status: 'warn',
820
- message: 'Could not parse .claude/settings.json',
821
- fix: 'monomind guidance setup --force',
822
- };
823
- }
824
- }
825
- // Format health check result
8
+ import { checkNodeVersion, checkNpmVersion, checkGit, checkGitRepo, checkDiskSpace, checkBuildTools, checkVersionFreshness, checkClaudeCode, installClaudeCode, } from './doctor-env-checks.js';
9
+ import { checkConfigFile, checkDaemonStatus, checkMemoryDatabase, checkApiKeys, checkMcpServers, checkMonograph, checkMonographFreshness, checkMonoesMemory, checkHelpersFresh, checkMonoesIntegration, checkGuidanceGates, checkGitignoreCoverage, } from './doctor-project-checks.js';
826
10
  function formatCheck(check) {
827
11
  const icon = check.status === 'pass' ? output.success('✓') :
828
12
  check.status === 'warn' ? output.warning('⚠') :
829
13
  output.error('✗');
830
14
  return `${icon} ${check.name}: ${check.message}`;
831
15
  }
832
- // Main doctor command
833
16
  export const doctorCommand = {
834
17
  name: 'doctor',
835
18
  description: 'System diagnostics and health checks',
836
19
  options: [
20
+ { name: 'fix', short: 'f', description: 'Show fix commands for issues', type: 'boolean', default: false },
21
+ { name: 'install', short: 'i', description: 'Auto-install missing dependencies (Claude Code CLI)', type: 'boolean', default: false },
837
22
  {
838
- name: 'fix',
839
- short: 'f',
840
- description: 'Show fix commands for issues',
841
- type: 'boolean',
842
- default: false
843
- },
844
- {
845
- name: 'install',
846
- short: 'i',
847
- description: 'Auto-install missing dependencies (Claude Code CLI)',
848
- type: 'boolean',
849
- default: false
850
- },
851
- {
852
- name: 'component',
853
- short: 'c',
23
+ name: 'component', short: 'c',
854
24
  description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, claude, disk, typescript, monograph, graph-freshness, memory-pkg, helpers, monoes, gates, gitignore)',
855
- type: 'string'
25
+ type: 'string',
856
26
  },
857
- {
858
- name: 'verbose',
859
- short: 'v',
860
- description: 'Verbose output',
861
- type: 'boolean',
862
- default: false
863
- }
27
+ { name: 'verbose', short: 'v', description: 'Verbose output', type: 'boolean', default: false },
864
28
  ],
865
29
  examples: [
866
30
  { command: 'monomind doctor', description: 'Run full health check' },
867
31
  { command: 'monomind doctor --fix', description: 'Show fixes for issues' },
868
32
  { command: 'monomind doctor --install', description: 'Auto-install missing dependencies' },
869
33
  { command: 'monomind doctor -c version', description: 'Check for stale npx cache' },
870
- { command: 'monomind doctor -c claude', description: 'Check Claude Code CLI only' }
34
+ { command: 'monomind doctor -c claude', description: 'Check Claude Code CLI only' },
871
35
  ],
872
36
  action: async (ctx) => {
873
37
  const showFix = ctx.flags.fix;
874
38
  const autoInstall = ctx.flags.install;
875
39
  const component = ctx.flags.component;
876
- const verbose = ctx.flags.verbose;
877
40
  output.writeln();
878
41
  output.writeln(output.bold('MonoMind Doctor'));
879
42
  output.writeln(output.dim('System diagnostics and health check'));
880
43
  output.writeln(output.dim('─'.repeat(50)));
881
44
  output.writeln();
882
45
  const allChecks = [
883
- checkVersionFreshness,
884
- checkNodeVersion,
885
- checkNpmVersion,
886
- checkClaudeCode,
887
- checkGit,
888
- checkGitRepo,
889
- checkConfigFile,
890
- checkDaemonStatus,
891
- checkMemoryDatabase,
892
- checkApiKeys,
893
- checkMcpServers,
894
- checkDiskSpace,
895
- checkBuildTools,
896
- checkMonograph,
897
- checkMonographFreshness,
898
- checkMonoesMemory,
899
- checkHelpersFresh,
900
- checkMonoesIntegration,
901
- checkGuidanceGates,
902
- checkGitignoreCoverage,
46
+ checkVersionFreshness, checkNodeVersion, checkNpmVersion, checkClaudeCode,
47
+ checkGit, checkGitRepo, checkConfigFile, checkDaemonStatus, checkMemoryDatabase,
48
+ checkApiKeys, checkMcpServers, checkDiskSpace, checkBuildTools,
49
+ checkMonograph, checkMonographFreshness, checkMonoesMemory,
50
+ checkHelpersFresh, checkMonoesIntegration, checkGuidanceGates, checkGitignoreCoverage,
903
51
  ];
904
52
  const componentMap = {
905
- 'version': checkVersionFreshness,
906
- 'freshness': checkVersionFreshness,
907
- 'node': checkNodeVersion,
908
- 'npm': checkNpmVersion,
909
- 'claude': checkClaudeCode,
910
- 'config': checkConfigFile,
911
- 'daemon': checkDaemonStatus,
912
- 'memory': checkMemoryDatabase,
913
- 'api': checkApiKeys,
914
- 'git': checkGit,
915
- 'mcp': checkMcpServers,
916
- 'disk': checkDiskSpace,
917
- 'typescript': checkBuildTools,
918
- 'monograph': checkMonograph,
919
- 'graph-freshness': checkMonographFreshness,
920
- 'memory-pkg': checkMonoesMemory,
921
- 'helpers': checkHelpersFresh,
922
- 'monoes': checkMonoesIntegration,
923
- 'gates': checkGuidanceGates,
924
- 'gitignore': checkGitignoreCoverage,
925
- };
926
- let checksToRun = allChecks;
927
- if (component && componentMap[component]) {
928
- checksToRun = [componentMap[component]];
929
- }
53
+ version: checkVersionFreshness, freshness: checkVersionFreshness,
54
+ node: checkNodeVersion, npm: checkNpmVersion, claude: checkClaudeCode,
55
+ config: checkConfigFile, daemon: checkDaemonStatus, memory: checkMemoryDatabase,
56
+ api: checkApiKeys, git: checkGit, mcp: checkMcpServers, disk: checkDiskSpace,
57
+ typescript: checkBuildTools, monograph: checkMonograph,
58
+ 'graph-freshness': checkMonographFreshness, 'memory-pkg': checkMonoesMemory,
59
+ helpers: checkHelpersFresh, monoes: checkMonoesIntegration,
60
+ gates: checkGuidanceGates, gitignore: checkGitignoreCoverage,
61
+ };
62
+ const checksToRun = (component && componentMap[component]) ? [componentMap[component]] : allChecks;
930
63
  const results = [];
931
64
  const fixes = [];
932
- // OPTIMIZATION: Run all checks in parallel for 3-5x faster execution
933
65
  const spinner = output.createSpinner({ text: 'Running health checks in parallel...', spinner: 'dots' });
934
66
  spinner.start();
935
67
  try {
936
- // Execute all checks concurrently
937
- const checkResults = await Promise.allSettled(checksToRun.map(check => check()));
68
+ const settled = await Promise.allSettled(checksToRun.map(check => check()));
938
69
  spinner.stop();
939
- // Process results in order
940
- for (const settledResult of checkResults) {
941
- if (settledResult.status === 'fulfilled') {
942
- const result = settledResult.value;
943
- results.push(result);
944
- output.writeln(formatCheck(result));
945
- if (result.fix && result.status === 'fail') {
946
- // Always show fix inline for failures — no flag needed
947
- output.writeln(output.dim(` Fix: ${result.fix}`));
948
- }
949
- else if (result.fix && result.status === 'warn') {
950
- // Show fix inline for warnings too, so users don't need --fix for common issues
951
- output.writeln(output.dim(` Hint: ${result.fix}`));
952
- }
953
- if (result.fix && (result.status === 'fail' || result.status === 'warn')) {
954
- fixes.push(`${result.name}: ${result.fix}`);
955
- }
70
+ for (const result of settled) {
71
+ if (result.status === 'fulfilled') {
72
+ const r = result.value;
73
+ results.push(r);
74
+ output.writeln(formatCheck(r));
75
+ if (r.fix && r.status === 'fail')
76
+ output.writeln(output.dim(` Fix: ${r.fix}`));
77
+ else if (r.fix && r.status === 'warn')
78
+ output.writeln(output.dim(` Hint: ${r.fix}`));
79
+ if (r.fix && (r.status === 'fail' || r.status === 'warn'))
80
+ fixes.push(`${r.name}: ${r.fix}`);
956
81
  }
957
82
  else {
958
- const errorResult = {
959
- name: 'Check',
960
- status: 'fail',
961
- message: settledResult.reason?.message || 'Unknown error'
962
- };
963
- results.push(errorResult);
964
- output.writeln(formatCheck(errorResult));
83
+ const err = { name: 'Check', status: 'fail', message: result.reason?.message || 'Unknown error' };
84
+ results.push(err);
85
+ output.writeln(formatCheck(err));
965
86
  }
966
87
  }
967
88
  }
968
- catch (error) {
89
+ catch {
969
90
  spinner.stop();
970
91
  output.writeln(output.error('Failed to run health checks'));
971
92
  }
972
- // Auto-install missing dependencies if requested
973
93
  if (autoInstall) {
974
- const claudeCodeResult = results.find(r => r.name === 'Claude Code CLI');
975
- if (claudeCodeResult && claudeCodeResult.status !== 'pass') {
976
- const installed = await installClaudeCode();
977
- if (installed) {
94
+ const claudeResult = results.find(r => r.name === 'Claude Code CLI');
95
+ if (claudeResult && claudeResult.status !== 'pass') {
96
+ if (await installClaudeCode()) {
978
97
  const newCheck = await checkClaudeCode();
979
98
  const idx = results.findIndex(r => r.name === 'Claude Code CLI');
980
99
  if (idx !== -1) {
981
100
  results[idx] = newCheck;
982
101
  const fixIdx = fixes.findIndex(f => f.startsWith('Claude Code CLI:'));
983
- if (fixIdx !== -1 && newCheck.status === 'pass') {
102
+ if (fixIdx !== -1 && newCheck.status === 'pass')
984
103
  fixes.splice(fixIdx, 1);
985
- }
986
104
  }
987
105
  output.writeln(formatCheck(newCheck));
988
106
  }
989
107
  }
990
108
  }
991
- // Summary
992
109
  const passed = results.filter(r => r.status === 'pass').length;
993
110
  const warnings = results.filter(r => r.status === 'warn').length;
994
111
  const failed = results.filter(r => r.status === 'fail').length;
@@ -998,27 +115,21 @@ export const doctorCommand = {
998
115
  const summaryParts = [
999
116
  output.success(`${passed} passed`),
1000
117
  warnings > 0 ? output.warning(`${warnings} warnings`) : null,
1001
- failed > 0 ? output.error(`${failed} failed`) : null
118
+ failed > 0 ? output.error(`${failed} failed`) : null,
1002
119
  ].filter(Boolean);
1003
120
  output.writeln(`Summary: ${summaryParts.join(', ')}`);
1004
- // Show fixes
1005
121
  if (showFix && fixes.length > 0) {
1006
122
  output.writeln();
1007
123
  output.writeln(output.bold('Suggested Fixes:'));
1008
124
  output.writeln();
1009
- for (const fix of fixes) {
125
+ for (const fix of fixes)
1010
126
  output.writeln(output.dim(` ${fix}`));
1011
- }
1012
127
  }
1013
128
  else if (!showFix) {
1014
- // Only nudge about --fix for warnings (failures already showed their fix inline)
1015
129
  const warnFixes = results.filter(r => r.status === 'warn' && r.fix).length;
1016
- if (warnFixes > 0) {
1017
- output.writeln();
1018
- output.writeln(output.dim(`Run with --fix to see ${warnFixes} suggested fix${warnFixes > 1 ? 'es' : ''} for warnings`));
1019
- }
130
+ if (warnFixes > 0)
131
+ output.writeln(output.dim(`\nRun with --fix to see ${warnFixes} suggested fix${warnFixes > 1 ? 'es' : ''} for warnings`));
1020
132
  }
1021
- // Overall result
1022
133
  if (failed > 0) {
1023
134
  output.writeln();
1024
135
  output.writeln(output.error('Some checks failed. Please address the issues above.'));
@@ -1029,12 +140,10 @@ export const doctorCommand = {
1029
140
  output.writeln(output.warning('All checks passed with some warnings.'));
1030
141
  return { success: true, data: { passed, warnings, failed, results } };
1031
142
  }
1032
- else {
1033
- output.writeln();
1034
- output.writeln(output.success('All checks passed! System is healthy.'));
1035
- return { success: true, data: { passed, warnings, failed, results } };
1036
- }
1037
- }
143
+ output.writeln();
144
+ output.writeln(output.success('All checks passed! System is healthy.'));
145
+ return { success: true, data: { passed, warnings, failed, results } };
146
+ },
1038
147
  };
1039
148
  export default doctorCommand;
1040
149
  //# sourceMappingURL=doctor.js.map