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.
- package/.claude/agents/engineering/engineering-security-engineer.md +1 -1
- package/.claude/commands/mastermind/_repeat.md +4 -0
- package/.claude/commands/mastermind/master.md +52 -1
- package/.claude/scheduled_tasks.lock +1 -1
- package/.claude/skills/mastermind/_repeat.md +2 -0
- package/package.json +1 -1
- package/packages/@monomind/cli/.claude/agents/engineering/engineering-security-engineer.md +1 -1
- package/packages/@monomind/cli/.claude/commands/mastermind/_repeat.md +4 -0
- package/packages/@monomind/cli/.claude/commands/mastermind/master.md +52 -1
- package/packages/@monomind/cli/.claude/skills/mastermind/_repeat.md +2 -0
- package/packages/@monomind/cli/dist/src/__tests__/browse-analyzer.test.js +42 -59
- package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +18 -0
- package/packages/@monomind/cli/dist/src/browser/dashboard/ui.html +37 -125
- package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.d.ts +17 -0
- package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.js +320 -0
- package/packages/@monomind/cli/dist/src/commands/agent-ops.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/commands/agent-ops.js +329 -0
- package/packages/@monomind/cli/dist/src/commands/agent.js +5 -907
- package/packages/@monomind/cli/dist/src/commands/analyze-ast.d.ts +26 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-ast.js +284 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-boundaries.d.ts +14 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-boundaries.js +295 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-diff.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-diff.js +395 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-graph.d.ts +14 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-graph.js +304 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-imports.d.ts +11 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-imports.js +287 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-symbols.d.ts +14 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-symbols.js +302 -0
- package/packages/@monomind/cli/dist/src/commands/analyze.d.ts +38 -0
- package/packages/@monomind/cli/dist/src/commands/analyze.js +12 -1827
- package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.d.ts +26 -0
- package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.js +189 -0
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.d.ts +19 -0
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +388 -0
- package/packages/@monomind/cli/dist/src/commands/doctor.js +51 -942
- package/packages/@monomind/cli/dist/src/commands/hive-mind-comms.d.ts +11 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-comms.js +242 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-helpers.d.ts +35 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-helpers.js +203 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-ops.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-ops.js +233 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-spawn.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-spawn.js +274 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind.js +10 -1129
- package/packages/@monomind/cli/dist/src/commands/hooks-coverage-commands.d.ts +4 -4
- package/packages/@monomind/cli/dist/src/commands/hooks-coverage-commands.js +19 -819
- package/packages/@monomind/cli/dist/src/commands/hooks-coverage-gaps.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/commands/hooks-coverage-gaps.js +334 -0
- package/packages/@monomind/cli/dist/src/commands/hooks-coverage-routing.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/commands/hooks-coverage-routing.js +399 -0
- package/packages/@monomind/cli/dist/src/commands/init-subcommands.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/commands/init-subcommands.js +156 -0
- package/packages/@monomind/cli/dist/src/commands/init-upgrade.d.ts +6 -0
- package/packages/@monomind/cli/dist/src/commands/init-upgrade.js +203 -0
- package/packages/@monomind/cli/dist/src/commands/init-wizard.d.ts +6 -0
- package/packages/@monomind/cli/dist/src/commands/init-wizard.js +246 -0
- package/packages/@monomind/cli/dist/src/commands/init.js +6 -623
- package/packages/@monomind/cli/dist/src/commands/memory-admin.d.ts +10 -0
- package/packages/@monomind/cli/dist/src/commands/memory-admin.js +433 -0
- package/packages/@monomind/cli/dist/src/commands/memory-crud.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/commands/memory-crud.js +342 -0
- package/packages/@monomind/cli/dist/src/commands/memory-list.d.ts +10 -0
- package/packages/@monomind/cli/dist/src/commands/memory-list.js +321 -0
- package/packages/@monomind/cli/dist/src/commands/memory-transfer.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/commands/memory-transfer.js +372 -0
- package/packages/@monomind/cli/dist/src/commands/memory.d.ts +6 -0
- package/packages/@monomind/cli/dist/src/commands/memory.js +10 -1441
- package/packages/@monomind/cli/dist/src/commands/neural-core.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/commands/neural-core.js +274 -0
- package/packages/@monomind/cli/dist/src/commands/neural-optimize.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/commands/neural-optimize.js +332 -0
- package/packages/@monomind/cli/dist/src/commands/neural-registry.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/commands/neural-registry.js +290 -0
- package/packages/@monomind/cli/dist/src/commands/neural.js +3 -974
- package/packages/@monomind/cli/dist/src/commands/platforms.js +327 -7
- package/packages/@monomind/cli/dist/src/commands/security-cve.d.ts +6 -0
- package/packages/@monomind/cli/dist/src/commands/security-cve.js +310 -0
- package/packages/@monomind/cli/dist/src/commands/security-misc.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/commands/security-misc.js +293 -0
- package/packages/@monomind/cli/dist/src/commands/security-scan.d.ts +18 -0
- package/packages/@monomind/cli/dist/src/commands/security-scan.js +328 -0
- package/packages/@monomind/cli/dist/src/commands/security.js +3 -958
- package/packages/@monomind/cli/dist/src/commands/session.js +1 -1
- package/packages/@monomind/cli/dist/src/commands/swarm.js +23 -17
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +77 -0
- package/packages/@monomind/cli/dist/src/parser.js +11 -6
- package/packages/@monomind/cli/dist/src/routing/llm-caller.js +1 -2
- package/packages/@monomind/cli/package.json +2 -3
- 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 {
|
|
9
|
-
import {
|
|
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: '
|
|
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
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
'
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
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
|
-
|
|
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
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
output.writeln(output.dim(`
|
|
948
|
-
|
|
949
|
-
|
|
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
|
|
959
|
-
|
|
960
|
-
|
|
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
|
|
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
|
|
975
|
-
if (
|
|
976
|
-
|
|
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
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
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
|