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
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor — system/environment health checks
|
|
3
|
+
* Node, npm, git, disk, TypeScript, Claude CLI, version freshness
|
|
4
|
+
*/
|
|
5
|
+
export declare const MAX_DOCTOR_PKG_BYTES: number;
|
|
6
|
+
export declare const MAX_DOCTOR_CONFIG_BYTES: number;
|
|
7
|
+
export declare const MAX_DOCTOR_GITIGNORE_BYTES: number;
|
|
8
|
+
export declare const MAX_DOCTOR_PID_BYTES = 64;
|
|
9
|
+
export declare const MAX_DOCTOR_HELPER_BYTES: number;
|
|
10
|
+
export interface HealthCheck {
|
|
11
|
+
name: string;
|
|
12
|
+
status: 'pass' | 'warn' | 'fail';
|
|
13
|
+
message: string;
|
|
14
|
+
fix?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function runCommand(command: string, timeoutMs?: number): Promise<string>;
|
|
17
|
+
export declare function checkNodeVersion(): Promise<HealthCheck>;
|
|
18
|
+
export declare function checkNpmVersion(): Promise<HealthCheck>;
|
|
19
|
+
export declare function checkGit(): Promise<HealthCheck>;
|
|
20
|
+
export declare function checkGitRepo(): Promise<HealthCheck>;
|
|
21
|
+
export declare function checkDiskSpace(): Promise<HealthCheck>;
|
|
22
|
+
export declare function checkBuildTools(): Promise<HealthCheck>;
|
|
23
|
+
export declare function checkVersionFreshness(): Promise<HealthCheck>;
|
|
24
|
+
export declare function checkClaudeCode(): Promise<HealthCheck>;
|
|
25
|
+
export declare function installClaudeCode(): Promise<boolean>;
|
|
26
|
+
//# sourceMappingURL=doctor-env-checks.d.ts.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor — system/environment health checks
|
|
3
|
+
* Node, npm, git, disk, TypeScript, Claude CLI, version freshness
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync, statSync } from 'fs';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { execSync, exec } from 'child_process';
|
|
9
|
+
import { promisify } from 'util';
|
|
10
|
+
import { output } from '../output.js';
|
|
11
|
+
export const MAX_DOCTOR_PKG_BYTES = 1024 * 1024; // 1 MB
|
|
12
|
+
export const MAX_DOCTOR_CONFIG_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
13
|
+
export const MAX_DOCTOR_GITIGNORE_BYTES = 512 * 1024; // 512 KB
|
|
14
|
+
export const MAX_DOCTOR_PID_BYTES = 64; // 64 bytes
|
|
15
|
+
export const MAX_DOCTOR_HELPER_BYTES = 2 * 1024 * 1024; // 2 MB
|
|
16
|
+
const execAsync = promisify(exec);
|
|
17
|
+
export async function runCommand(command, timeoutMs = 5000) {
|
|
18
|
+
const { stdout } = await execAsync(command, {
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
timeout: timeoutMs,
|
|
21
|
+
shell: process.platform === 'win32' ? 'cmd.exe' : '/bin/sh',
|
|
22
|
+
env: { ...process.env },
|
|
23
|
+
windowsHide: true,
|
|
24
|
+
});
|
|
25
|
+
return stdout.trim();
|
|
26
|
+
}
|
|
27
|
+
export async function checkNodeVersion() {
|
|
28
|
+
const requiredMajor = 20;
|
|
29
|
+
const version = process.version;
|
|
30
|
+
const major = parseInt(version.slice(1).split('.')[0], 10);
|
|
31
|
+
if (major >= requiredMajor) {
|
|
32
|
+
return { name: 'Node.js Version', status: 'pass', message: `${version} (>= ${requiredMajor} required)` };
|
|
33
|
+
}
|
|
34
|
+
else if (major >= 18) {
|
|
35
|
+
return { name: 'Node.js Version', status: 'warn', message: `${version} (>= ${requiredMajor} recommended)`, fix: 'nvm install 20 && nvm use 20' };
|
|
36
|
+
}
|
|
37
|
+
return { name: 'Node.js Version', status: 'fail', message: `${version} (>= ${requiredMajor} required)`, fix: 'nvm install 20 && nvm use 20' };
|
|
38
|
+
}
|
|
39
|
+
export async function checkNpmVersion() {
|
|
40
|
+
try {
|
|
41
|
+
const version = await runCommand('npm --version');
|
|
42
|
+
const major = parseInt(version.split('.')[0], 10);
|
|
43
|
+
if (major >= 9)
|
|
44
|
+
return { name: 'npm Version', status: 'pass', message: `v${version}` };
|
|
45
|
+
return { name: 'npm Version', status: 'warn', message: `v${version} (>= 9 recommended)`, fix: 'npm install -g npm@latest' };
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { name: 'npm Version', status: 'fail', message: 'npm not found', fix: 'Install Node.js from https://nodejs.org' };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function checkGit() {
|
|
52
|
+
try {
|
|
53
|
+
const version = await runCommand('git --version');
|
|
54
|
+
return { name: 'Git', status: 'pass', message: version.replace('git version ', 'v') };
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return { name: 'Git', status: 'warn', message: 'Not installed', fix: 'Install git from https://git-scm.com' };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function checkGitRepo() {
|
|
61
|
+
try {
|
|
62
|
+
await runCommand('git rev-parse --git-dir');
|
|
63
|
+
return { name: 'Git Repository', status: 'pass', message: 'In a git repository' };
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return { name: 'Git Repository', status: 'warn', message: 'Not a git repository', fix: 'git init' };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export async function checkDiskSpace() {
|
|
70
|
+
try {
|
|
71
|
+
if (process.platform === 'win32')
|
|
72
|
+
return { name: 'Disk Space', status: 'pass', message: 'Check skipped on Windows' };
|
|
73
|
+
const output_str = await runCommand('df -Ph . | tail -1');
|
|
74
|
+
const parts = output_str.split(/\s+/);
|
|
75
|
+
const available = parts[3];
|
|
76
|
+
const usePercent = parseInt(parts[4]?.replace('%', '') || '0', 10);
|
|
77
|
+
if (isNaN(usePercent))
|
|
78
|
+
return { name: 'Disk Space', status: 'warn', message: `${available || 'unknown'} available (unable to parse usage)` };
|
|
79
|
+
if (usePercent > 90)
|
|
80
|
+
return { name: 'Disk Space', status: 'fail', message: `${available} available (${usePercent}% used)`, fix: 'Free up disk space' };
|
|
81
|
+
if (usePercent > 80)
|
|
82
|
+
return { name: 'Disk Space', status: 'warn', message: `${available} available (${usePercent}% used)` };
|
|
83
|
+
return { name: 'Disk Space', status: 'pass', message: `${available} available` };
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return { name: 'Disk Space', status: 'warn', message: 'Unable to check' };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export async function checkBuildTools() {
|
|
90
|
+
try {
|
|
91
|
+
const tscVersion = await runCommand('npx tsc --version', 10000);
|
|
92
|
+
if (!tscVersion || tscVersion.includes('not found')) {
|
|
93
|
+
return { name: 'TypeScript', status: 'warn', message: 'Not installed locally', fix: 'npm install -D typescript' };
|
|
94
|
+
}
|
|
95
|
+
return { name: 'TypeScript', status: 'pass', message: tscVersion.replace('Version ', 'v') };
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return { name: 'TypeScript', status: 'warn', message: 'Not installed locally', fix: 'npm install -D typescript' };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export async function checkVersionFreshness() {
|
|
102
|
+
try {
|
|
103
|
+
let currentVersion = '0.0.0';
|
|
104
|
+
try {
|
|
105
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
106
|
+
let dir = dirname(thisFile);
|
|
107
|
+
for (;;) {
|
|
108
|
+
const candidate = join(dir, 'package.json');
|
|
109
|
+
try {
|
|
110
|
+
if (existsSync(candidate) && statSync(candidate).size <= MAX_DOCTOR_PKG_BYTES) {
|
|
111
|
+
const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
112
|
+
if (pkg.version && typeof pkg.name === 'string' &&
|
|
113
|
+
(pkg.name === '@monomind/cli' || pkg.name === 'monomind' || pkg.name === '@monoes/monomindcli')) {
|
|
114
|
+
currentVersion = pkg.version;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch { /* keep walking */ }
|
|
120
|
+
const parent = dirname(dir);
|
|
121
|
+
if (parent === dir)
|
|
122
|
+
break;
|
|
123
|
+
dir = parent;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
currentVersion = '0.0.0';
|
|
128
|
+
}
|
|
129
|
+
const isNpx = process.argv[1]?.includes('_npx') ||
|
|
130
|
+
process.env.npm_execpath?.includes('npx') ||
|
|
131
|
+
process.cwd().includes('_npx');
|
|
132
|
+
let latestVersion = currentVersion;
|
|
133
|
+
try {
|
|
134
|
+
latestVersion = (await runCommand('npm view monomind version', 5000)).trim();
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return { name: 'Version Freshness', status: 'warn', message: `v${currentVersion} (cannot check registry)` };
|
|
138
|
+
}
|
|
139
|
+
const parseVer = (v) => {
|
|
140
|
+
const m = v.match(/^(\d+)\.(\d+)\.(\d+)(?:-[a-zA-Z]+\.(\d+))?/);
|
|
141
|
+
if (!m)
|
|
142
|
+
return { major: 0, minor: 0, patch: 0, pre: 0 };
|
|
143
|
+
return { major: +m[1], minor: +m[2], patch: +m[3], pre: +(m[4] ?? 0) };
|
|
144
|
+
};
|
|
145
|
+
const cur = parseVer(currentVersion);
|
|
146
|
+
const lat = parseVer(latestVersion);
|
|
147
|
+
const outdated = lat.major > cur.major || (lat.major === cur.major && lat.minor > cur.minor) ||
|
|
148
|
+
(lat.major === cur.major && lat.minor === cur.minor && lat.patch > cur.patch) ||
|
|
149
|
+
(lat.major === cur.major && lat.minor === cur.minor && lat.patch === cur.patch && lat.pre > cur.pre);
|
|
150
|
+
if (outdated) {
|
|
151
|
+
return {
|
|
152
|
+
name: 'Version Freshness',
|
|
153
|
+
status: 'warn',
|
|
154
|
+
message: `v${currentVersion} (latest: v${latestVersion})${isNpx ? ' [npx cache stale]' : ''}`,
|
|
155
|
+
fix: isNpx ? 'rm -rf ~/.npm/_npx/* && npx -y monomind@latest doctor' : 'npm update -g monomind',
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return { name: 'Version Freshness', status: 'pass', message: `v${currentVersion} (up to date)` };
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return { name: 'Version Freshness', status: 'warn', message: 'Unable to check version freshness' };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export async function checkClaudeCode() {
|
|
165
|
+
try {
|
|
166
|
+
const version = await runCommand('claude --version');
|
|
167
|
+
const m = version.match(/v?(\d+\.\d+\.\d+)/);
|
|
168
|
+
return { name: 'Claude Code CLI', status: 'pass', message: m ? `v${m[1]}` : version };
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return { name: 'Claude Code CLI', status: 'warn', message: 'Not installed', fix: 'npm install -g @anthropic-ai/claude-code' };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export async function installClaudeCode() {
|
|
175
|
+
try {
|
|
176
|
+
output.writeln();
|
|
177
|
+
output.writeln(output.bold('Installing Claude Code CLI...'));
|
|
178
|
+
execSync('npm install -g @anthropic-ai/claude-code', { encoding: 'utf8', stdio: 'inherit' });
|
|
179
|
+
output.writeln(output.success('Claude Code CLI installed successfully!'));
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
output.writeln(output.error('Failed to install Claude Code CLI'));
|
|
184
|
+
if (error instanceof Error)
|
|
185
|
+
output.writeln(output.dim(error.message));
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=doctor-env-checks.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor — project/monomind health checks
|
|
3
|
+
* Config, daemon, memory, API keys, MCP, monograph, helpers, routing, gates, gitignore
|
|
4
|
+
*/
|
|
5
|
+
import type { HealthCheck } from './doctor-env-checks.js';
|
|
6
|
+
export type { HealthCheck };
|
|
7
|
+
export declare function checkConfigFile(): Promise<HealthCheck>;
|
|
8
|
+
export declare function checkDaemonStatus(): Promise<HealthCheck>;
|
|
9
|
+
export declare function checkMemoryDatabase(): Promise<HealthCheck>;
|
|
10
|
+
export declare function checkApiKeys(): Promise<HealthCheck>;
|
|
11
|
+
export declare function checkMcpServers(): Promise<HealthCheck>;
|
|
12
|
+
export declare function checkMonograph(): Promise<HealthCheck>;
|
|
13
|
+
export declare function checkMonographFreshness(): Promise<HealthCheck>;
|
|
14
|
+
export declare function checkMonoesMemory(): Promise<HealthCheck>;
|
|
15
|
+
export declare function checkHelpersFresh(): Promise<HealthCheck>;
|
|
16
|
+
export declare function checkMonoesIntegration(): Promise<HealthCheck>;
|
|
17
|
+
export declare function checkGitignoreCoverage(): Promise<HealthCheck>;
|
|
18
|
+
export declare function checkGuidanceGates(): Promise<HealthCheck>;
|
|
19
|
+
//# sourceMappingURL=doctor-project-checks.d.ts.map
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor — project/monomind health checks
|
|
3
|
+
* Config, daemon, memory, API keys, MCP, monograph, helpers, routing, gates, gitignore
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync, statSync } from 'fs';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { MAX_DOCTOR_PKG_BYTES, MAX_DOCTOR_CONFIG_BYTES, MAX_DOCTOR_GITIGNORE_BYTES, MAX_DOCTOR_PID_BYTES, MAX_DOCTOR_HELPER_BYTES, } from './doctor-env-checks.js';
|
|
11
|
+
export async function checkConfigFile() {
|
|
12
|
+
const jsonPaths = ['.monomind/config.json', 'monomind.config.json', '.monomind.json'];
|
|
13
|
+
for (const configPath of jsonPaths) {
|
|
14
|
+
if (existsSync(configPath) && statSync(configPath).size <= MAX_DOCTOR_CONFIG_BYTES) {
|
|
15
|
+
try {
|
|
16
|
+
JSON.parse(readFileSync(configPath, 'utf8'));
|
|
17
|
+
return { name: 'Config File', status: 'pass', message: `Found: ${configPath}` };
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { name: 'Config File', status: 'fail', message: `Invalid JSON: ${configPath}`, fix: 'Fix JSON syntax in config file' };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const yamlPaths = ['.monomind/config.yaml', '.monomind/config.yml', 'monomind.config.yaml'];
|
|
25
|
+
for (const configPath of yamlPaths) {
|
|
26
|
+
if (existsSync(configPath))
|
|
27
|
+
return { name: 'Config File', status: 'pass', message: `Found: ${configPath}` };
|
|
28
|
+
}
|
|
29
|
+
return { name: 'Config File', status: 'warn', message: 'No config file (using defaults)', fix: 'monomind config init' };
|
|
30
|
+
}
|
|
31
|
+
export async function checkDaemonStatus() {
|
|
32
|
+
try {
|
|
33
|
+
const pidFile = '.monomind/daemon.pid';
|
|
34
|
+
if (existsSync(pidFile) && statSync(pidFile).size <= MAX_DOCTOR_PID_BYTES) {
|
|
35
|
+
const pid = readFileSync(pidFile, 'utf8').trim();
|
|
36
|
+
try {
|
|
37
|
+
process.kill(parseInt(pid, 10), 0);
|
|
38
|
+
return { name: 'Daemon Status', status: 'pass', message: `Running (PID: ${pid})` };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return { name: 'Daemon Status', status: 'warn', message: 'Stale PID file', fix: 'rm .monomind/daemon.pid && monomind daemon start' };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { name: 'Daemon Status', status: 'warn', message: 'Not running', fix: 'monomind daemon start' };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return { name: 'Daemon Status', status: 'warn', message: 'Unable to check', fix: 'monomind daemon status' };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export async function checkMemoryDatabase() {
|
|
51
|
+
const dbPaths = ['.monomind/memory.db', '.swarm/memory.db', 'data/memory.db'];
|
|
52
|
+
for (const dbPath of dbPaths) {
|
|
53
|
+
if (existsSync(dbPath)) {
|
|
54
|
+
try {
|
|
55
|
+
const sizeMB = (statSync(dbPath).size / 1024 / 1024).toFixed(2);
|
|
56
|
+
return { name: 'Memory Database', status: 'pass', message: `${dbPath} (${sizeMB} MB)` };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { name: 'Memory Database', status: 'warn', message: `${dbPath} (unable to stat)` };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { name: 'Memory Database', status: 'warn', message: 'Not initialized', fix: 'monomind memory configure --backend hybrid' };
|
|
64
|
+
}
|
|
65
|
+
export async function checkApiKeys() {
|
|
66
|
+
const keys = ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY', 'OPENAI_API_KEY'];
|
|
67
|
+
const found = keys.filter(k => process.env[k]);
|
|
68
|
+
const inClaudeCode = !!(process.env.CLAUDE_CODE || process.env.CLAUDE_PROJECT_DIR || process.env.MCP_SESSION_ID);
|
|
69
|
+
let claudeCliAvailable = false;
|
|
70
|
+
try {
|
|
71
|
+
execSync('claude --version', { encoding: 'utf-8', stdio: 'pipe', timeout: 5000, windowsHide: true });
|
|
72
|
+
claudeCliAvailable = true;
|
|
73
|
+
}
|
|
74
|
+
catch { /* not on PATH */ }
|
|
75
|
+
if (found.includes('ANTHROPIC_API_KEY') || found.includes('CLAUDE_API_KEY')) {
|
|
76
|
+
return { name: 'API Keys', status: 'pass', message: `Found: ${found.join(', ')}` };
|
|
77
|
+
}
|
|
78
|
+
else if (inClaudeCode) {
|
|
79
|
+
return { name: 'API Keys', status: 'pass', message: 'Claude Code manages auth (no direct API key needed)' };
|
|
80
|
+
}
|
|
81
|
+
else if (claudeCliAvailable) {
|
|
82
|
+
return { name: 'API Keys', status: 'pass', message: 'Using Claude Code CLI auth (no direct API key needed)' };
|
|
83
|
+
}
|
|
84
|
+
else if (found.length > 0) {
|
|
85
|
+
return { name: 'API Keys', status: 'warn', message: `Found: ${found.join(', ')} (no Claude key)`, fix: 'export ANTHROPIC_API_KEY=your_key' };
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
name: 'API Keys',
|
|
89
|
+
status: 'warn',
|
|
90
|
+
message: 'Claude Code CLI not found — monomind works best on top of Claude Code',
|
|
91
|
+
fix: 'npm install -g @anthropic-ai/claude-code # then: claude login',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export async function checkMcpServers() {
|
|
95
|
+
const mcpConfigPaths = [
|
|
96
|
+
join(homedir(), '.claude/claude_desktop_config.json'),
|
|
97
|
+
join(homedir(), '.config/claude/mcp.json'),
|
|
98
|
+
'.mcp.json',
|
|
99
|
+
'.claude/settings.json',
|
|
100
|
+
'.claude/settings.local.json',
|
|
101
|
+
join(homedir(), '.claude/settings.json'),
|
|
102
|
+
];
|
|
103
|
+
for (const configPath of mcpConfigPaths) {
|
|
104
|
+
if (existsSync(configPath) && statSync(configPath).size <= MAX_DOCTOR_CONFIG_BYTES) {
|
|
105
|
+
try {
|
|
106
|
+
const content = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
107
|
+
const servers = content.mcpServers || content.servers || {};
|
|
108
|
+
const count = Object.keys(servers).length;
|
|
109
|
+
const hasMonomind = 'monomind' in servers || 'monomind_alpha' in servers;
|
|
110
|
+
if (hasMonomind)
|
|
111
|
+
return { name: 'MCP Servers', status: 'pass', message: `${count} servers (monomind configured)` };
|
|
112
|
+
return { name: 'MCP Servers', status: 'warn', message: `${count} servers (monomind not found)`, fix: 'claude mcp add monomind -- npx -y monomind@latest mcp start' };
|
|
113
|
+
}
|
|
114
|
+
catch { /* try next */ }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { name: 'MCP Servers', status: 'warn', message: 'No MCP config found', fix: 'claude mcp add monomind -- npx -y monomind@latest mcp start' };
|
|
118
|
+
}
|
|
119
|
+
export async function checkMonograph() {
|
|
120
|
+
try {
|
|
121
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
122
|
+
const _base = dirname(__filename);
|
|
123
|
+
let _globalRoot = '';
|
|
124
|
+
try {
|
|
125
|
+
_globalRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
126
|
+
}
|
|
127
|
+
catch { /* no npm */ }
|
|
128
|
+
const candidates = [
|
|
129
|
+
join(_base, '..', '..', 'node_modules', '@monomind', 'monograph', 'package.json'),
|
|
130
|
+
join(_base, '..', '..', '..', '..', 'node_modules', '@monomind', 'monograph', 'package.json'),
|
|
131
|
+
join(_base, '..', '..', 'node_modules', '@monoes', 'monograph', 'package.json'),
|
|
132
|
+
join(_base, '..', '..', '..', '..', 'node_modules', '@monoes', 'monograph', 'package.json'),
|
|
133
|
+
...(_globalRoot ? [
|
|
134
|
+
join(_globalRoot, '@monomind', 'monograph', 'package.json'),
|
|
135
|
+
join(_globalRoot, '@monoes', 'monograph', 'package.json'),
|
|
136
|
+
] : []),
|
|
137
|
+
];
|
|
138
|
+
const found = candidates.find(p => existsSync(p) && statSync(p).size <= MAX_DOCTOR_PKG_BYTES);
|
|
139
|
+
if (found) {
|
|
140
|
+
try {
|
|
141
|
+
const pkg = JSON.parse(readFileSync(found, 'utf-8'));
|
|
142
|
+
return { name: 'Monograph', status: 'pass', message: `v${pkg.version || '?'} available (knowledge graph engine)` };
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return { name: 'Monograph', status: 'pass', message: 'available (knowledge graph engine)' };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { name: 'Monograph', status: 'warn', message: 'Package not found (knowledge graph disabled)', fix: 'npm install -g monomind@latest' };
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return { name: 'Monograph', status: 'warn', message: 'Package check failed', fix: 'npm install -g monomind@latest' };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export async function checkMonographFreshness() {
|
|
155
|
+
try {
|
|
156
|
+
const cwd = process.cwd();
|
|
157
|
+
const dbPath = join(cwd, '.monomind', 'monograph.db');
|
|
158
|
+
const lockPath = join(cwd, '.monomind', 'graph', '.rebuild-lock');
|
|
159
|
+
const statsPath = join(cwd, '.monomind', 'graph', 'stats.json');
|
|
160
|
+
const hasDb = existsSync(dbPath);
|
|
161
|
+
if (!hasDb && !existsSync(statsPath)) {
|
|
162
|
+
return { name: 'Graph freshness', status: 'warn', message: 'No monograph graph built yet', fix: 'mcp__monomind__monograph_build codeOnly:true' };
|
|
163
|
+
}
|
|
164
|
+
let buildMs = 0;
|
|
165
|
+
if (hasDb) {
|
|
166
|
+
try {
|
|
167
|
+
buildMs = Math.max(buildMs, statSync(dbPath).mtimeMs);
|
|
168
|
+
}
|
|
169
|
+
catch { /* ignore */ }
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
buildMs = Math.max(buildMs, statSync(lockPath).mtimeMs);
|
|
173
|
+
}
|
|
174
|
+
catch { /* ignore */ }
|
|
175
|
+
try {
|
|
176
|
+
buildMs = Math.max(buildMs, statSync(statsPath).mtimeMs);
|
|
177
|
+
}
|
|
178
|
+
catch { /* ignore */ }
|
|
179
|
+
if (buildMs === 0)
|
|
180
|
+
return { name: 'Graph freshness', status: 'warn', message: 'Graph exists but build time unknown' };
|
|
181
|
+
const buildIso = new Date(buildMs).toISOString();
|
|
182
|
+
let commitsBehind = 0;
|
|
183
|
+
try {
|
|
184
|
+
const out = execSync(`git rev-list --count --since='${buildIso}' HEAD 2>/dev/null`, { encoding: 'utf8', timeout: 2000, cwd }).trim();
|
|
185
|
+
commitsBehind = parseInt(out, 10) || 0;
|
|
186
|
+
}
|
|
187
|
+
catch { /* git unavailable */ }
|
|
188
|
+
const ageMinutes = Math.floor((Date.now() - buildMs) / 60000);
|
|
189
|
+
const ageStr = ageMinutes < 60 ? `${ageMinutes}m ago` : `${Math.floor(ageMinutes / 60)}h ago`;
|
|
190
|
+
if (commitsBehind === 0)
|
|
191
|
+
return { name: 'Graph freshness', status: 'pass', message: `FRESH — built ${ageStr}, 0 commits behind` };
|
|
192
|
+
if (commitsBehind <= 5)
|
|
193
|
+
return { name: 'Graph freshness', status: 'warn', message: `${commitsBehind} commit(s) behind — built ${ageStr}`, fix: 'mcp__monomind__monograph_build codeOnly:true' };
|
|
194
|
+
return { name: 'Graph freshness', status: 'fail', message: `STALE — ${commitsBehind} commits behind (built ${ageStr})`, fix: 'mcp__monomind__monograph_build codeOnly:true' };
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return { name: 'Graph freshness', status: 'warn', message: 'Could not check graph freshness' };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
export async function checkMonoesMemory() {
|
|
201
|
+
try {
|
|
202
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
203
|
+
const _base = dirname(__filename);
|
|
204
|
+
let _globalRoot = '';
|
|
205
|
+
try {
|
|
206
|
+
_globalRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
207
|
+
}
|
|
208
|
+
catch { /* no npm */ }
|
|
209
|
+
const candidates = [
|
|
210
|
+
join(_base, '..', '..', 'node_modules', '@monoes', 'memory', 'package.json'),
|
|
211
|
+
join(_base, '..', '..', '..', '..', 'node_modules', '@monoes', 'memory', 'package.json'),
|
|
212
|
+
...(_globalRoot ? [join(_globalRoot, '@monoes', 'memory', 'package.json')] : []),
|
|
213
|
+
];
|
|
214
|
+
const found = candidates.find(p => existsSync(p) && statSync(p).size <= MAX_DOCTOR_PKG_BYTES);
|
|
215
|
+
if (found) {
|
|
216
|
+
try {
|
|
217
|
+
const pkg = JSON.parse(readFileSync(found, 'utf-8'));
|
|
218
|
+
return { name: 'Vector Memory', status: 'pass', message: `@monoes/memory v${pkg.version || '?'} (HNSW search enabled)` };
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return { name: 'Vector Memory', status: 'pass', message: '@monoes/memory available (HNSW search enabled)' };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return { name: 'Vector Memory', status: 'warn', message: '@monoes/memory not installed (vector search disabled)', fix: 'npm install @monoes/memory' };
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return { name: 'Vector Memory', status: 'warn', message: 'Vector memory check failed' };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function _resolveBundledHelper(relativePath) {
|
|
231
|
+
try {
|
|
232
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
233
|
+
let dir = dirname(thisFile);
|
|
234
|
+
for (;;) {
|
|
235
|
+
const candidate = join(dir, 'package.json');
|
|
236
|
+
if (existsSync(candidate) && statSync(candidate).size <= MAX_DOCTOR_PKG_BYTES) {
|
|
237
|
+
try {
|
|
238
|
+
const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
239
|
+
if (pkg.name === '@monomind/cli' || pkg.name === 'monomind' || pkg.name === '@monoes/monomindcli') {
|
|
240
|
+
const helperPath = join(dir, relativePath);
|
|
241
|
+
return existsSync(helperPath) ? helperPath : null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch { /* keep walking */ }
|
|
245
|
+
}
|
|
246
|
+
const parent = dirname(dir);
|
|
247
|
+
if (parent === dir)
|
|
248
|
+
return null;
|
|
249
|
+
dir = parent;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async function _detectStaleHelpers() {
|
|
257
|
+
const stale = [];
|
|
258
|
+
const missing = [];
|
|
259
|
+
const helpers = ['hook-handler.cjs', 'statusline.cjs', 'router.cjs', 'graphify-freshen.cjs'];
|
|
260
|
+
const crypto = await import('node:crypto');
|
|
261
|
+
for (const name of helpers) {
|
|
262
|
+
const local = join(process.cwd(), '.claude', 'helpers', name);
|
|
263
|
+
if (!existsSync(local) || statSync(local).size > MAX_DOCTOR_HELPER_BYTES)
|
|
264
|
+
continue;
|
|
265
|
+
const bundled = _resolveBundledHelper(join('.claude', 'helpers', name));
|
|
266
|
+
if (!bundled) {
|
|
267
|
+
missing.push(name);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (statSync(bundled).size > MAX_DOCTOR_HELPER_BYTES)
|
|
271
|
+
continue;
|
|
272
|
+
try {
|
|
273
|
+
const hashLocal = crypto.createHash('sha256').update(readFileSync(local)).digest('hex');
|
|
274
|
+
const hashBundled = crypto.createHash('sha256').update(readFileSync(bundled)).digest('hex');
|
|
275
|
+
if (hashLocal !== hashBundled)
|
|
276
|
+
stale.push(name);
|
|
277
|
+
}
|
|
278
|
+
catch { /* skip */ }
|
|
279
|
+
}
|
|
280
|
+
return { stale, missing };
|
|
281
|
+
}
|
|
282
|
+
export async function checkHelpersFresh() {
|
|
283
|
+
try {
|
|
284
|
+
const { stale, missing } = await _detectStaleHelpers();
|
|
285
|
+
if (stale.length === 0 && missing.length === 0) {
|
|
286
|
+
return { name: 'Helper Files', status: 'pass', message: 'Project helpers match bundled version' };
|
|
287
|
+
}
|
|
288
|
+
if (stale.length > 0) {
|
|
289
|
+
return { name: 'Helper Files', status: 'warn', message: `${stale.length} stale helper(s): ${stale.join(', ')}`, fix: 'monomind init upgrade' };
|
|
290
|
+
}
|
|
291
|
+
return { name: 'Helper Files', status: 'warn', message: `Could not locate bundled copies of: ${missing.join(', ')}`, fix: 'Reinstall monomind or run `monomind init upgrade`' };
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
return { name: 'Helper Files', status: 'warn', message: `check failed: ${e instanceof Error ? e.message : 'unknown'}` };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function fmtPct(v) {
|
|
298
|
+
return v === null ? 'n/a' : `${Math.round(v * 100)}%`;
|
|
299
|
+
}
|
|
300
|
+
async function routingAccuracyLine() {
|
|
301
|
+
try {
|
|
302
|
+
const { computeRoutingAccuracy, computeAdherence } = await import('../monovector/route-outcomes.js');
|
|
303
|
+
const baseDir = join(process.cwd(), '.monomind');
|
|
304
|
+
const acc = await computeRoutingAccuracy(baseDir, 100);
|
|
305
|
+
const adh = await computeAdherence(baseDir);
|
|
306
|
+
const adhStr = ` | adherence ${fmtPct(adh.adherence)} (n=${adh.sample})`;
|
|
307
|
+
if (acc.accuracy === null)
|
|
308
|
+
return `routing accuracy (last 100): no outcome data yet${adhStr}`;
|
|
309
|
+
const trend = acc.recentVsPrior === null ? '' : ` trend ${acc.recentVsPrior >= 0 ? '+' : ''}${Math.round(acc.recentVsPrior * 100)}%`;
|
|
310
|
+
return `routing accuracy (last ${acc.window}): ${fmtPct(acc.accuracy)} [native ${fmtPct(acc.byMode.native)} / js ${fmtPct(acc.byMode.js)}]${trend}${adhStr}`;
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
return 'routing accuracy (last 100): no outcome data yet';
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
export async function checkMonoesIntegration() {
|
|
317
|
+
try {
|
|
318
|
+
return { name: 'Routing Learning', status: 'pass', message: await routingAccuracyLine() };
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
return { name: 'Routing Learning', status: 'warn', message: `Could not compute routing accuracy: ${err instanceof Error ? err.message : String(err)}` };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const REQUIRED_GITIGNORE_PATTERNS = [
|
|
325
|
+
{ pattern: '.monomind/sessions/', reason: 'session files contain cwd and machine paths' },
|
|
326
|
+
{ pattern: '.monomind/data/', reason: 'intelligence data with edit file paths' },
|
|
327
|
+
{ pattern: '.monomind/metrics/', reason: 'metrics with file path references' },
|
|
328
|
+
{ pattern: '.monomind/knowledge/', reason: 'knowledge chunks with local file content' },
|
|
329
|
+
{ pattern: '.monomind/*.json', reason: 'root-level runtime JSON (control, registry, routing)' },
|
|
330
|
+
{ pattern: '.monomind/*.jsonl', reason: 'root-level event logs (decisions, routing-feedback)' },
|
|
331
|
+
{ pattern: '**/.monomind/sessions/', reason: 'nested session files in sub-packages' },
|
|
332
|
+
{ pattern: '**/.monomind/*.json', reason: 'nested runtime JSON in sub-packages' },
|
|
333
|
+
{ pattern: 'data/sessions/', reason: 'session files with machine paths' },
|
|
334
|
+
{ pattern: 'data/mastermind-*.json', reason: 'mastermind session data' },
|
|
335
|
+
{ pattern: 'data/mastermind-*.jsonl', reason: 'mastermind event logs' },
|
|
336
|
+
{ pattern: '**/.claude-flow/', reason: 'claude-flow runtime data with paths' },
|
|
337
|
+
{ pattern: '.hive-mind/', reason: 'hive-mind state with session info' },
|
|
338
|
+
{ pattern: '.swarm/', reason: 'swarm state files' },
|
|
339
|
+
];
|
|
340
|
+
export async function checkGitignoreCoverage() {
|
|
341
|
+
const gitignorePath = join(process.cwd(), '.gitignore');
|
|
342
|
+
if (!existsSync(gitignorePath)) {
|
|
343
|
+
return { name: 'Gitignore Coverage', status: 'warn', message: 'No .gitignore found — all monomind runtime paths are unprotected', fix: 'echo ".monomind/\\n**/.monomind/" >> .gitignore' };
|
|
344
|
+
}
|
|
345
|
+
if (statSync(gitignorePath).size > MAX_DOCTOR_GITIGNORE_BYTES) {
|
|
346
|
+
return { name: 'Gitignore Coverage', status: 'warn', message: '.gitignore too large to parse' };
|
|
347
|
+
}
|
|
348
|
+
const lines = readFileSync(gitignorePath, 'utf-8').split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
|
|
349
|
+
const missing = REQUIRED_GITIGNORE_PATTERNS.filter(({ pattern }) => {
|
|
350
|
+
const base = pattern.replace(/\*\*\//g, '').replace(/\*/g, '');
|
|
351
|
+
return !lines.some(l => l === pattern || l === pattern.replace(/\/$/, '') ||
|
|
352
|
+
(l.includes('**') && base && l.replace(/\*\*\//g, '').replace(/\*/g, '') === base));
|
|
353
|
+
});
|
|
354
|
+
if (missing.length === 0)
|
|
355
|
+
return { name: 'Gitignore Coverage', status: 'pass', message: 'All monomind runtime paths are gitignored' };
|
|
356
|
+
const missingList = missing.map(m => m.pattern).join(', ');
|
|
357
|
+
return { name: 'Gitignore Coverage', status: 'warn', message: `${missing.length} runtime path(s) not in .gitignore: ${missingList}`, fix: `printf "${missing.map(m => m.pattern).join('\\n')}\\n" >> .gitignore` };
|
|
358
|
+
}
|
|
359
|
+
export async function checkGuidanceGates() {
|
|
360
|
+
const settingsPath = join(process.cwd(), '.claude', 'settings.json');
|
|
361
|
+
const gatesHandlerPath = join(process.cwd(), '.claude', 'helpers', 'handlers', 'gates-handler.cjs');
|
|
362
|
+
if (!existsSync(gatesHandlerPath)) {
|
|
363
|
+
return { name: 'Guidance Gates', status: 'warn', message: 'gates-handler.cjs not found — enforcement gates not installed', fix: 'monomind init (then monomind guidance setup)' };
|
|
364
|
+
}
|
|
365
|
+
if (!existsSync(settingsPath)) {
|
|
366
|
+
return { name: 'Guidance Gates', status: 'warn', message: 'gates-handler.cjs present but .claude/settings.json missing', fix: 'monomind guidance setup' };
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
if (statSync(settingsPath).size > MAX_DOCTOR_CONFIG_BYTES) {
|
|
370
|
+
return { name: 'Guidance Gates', status: 'warn', message: 'settings.json too large to parse' };
|
|
371
|
+
}
|
|
372
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
373
|
+
const preToolUse = settings?.hooks?.PreToolUse ?? [];
|
|
374
|
+
const hasPreWrite = preToolUse.some(e => e.matcher === 'Write|Edit|MultiEdit' && e.hooks.some(h => h.command?.includes('pre-write')));
|
|
375
|
+
const hasPreBash = preToolUse.some(e => e.matcher === 'Bash' && e.hooks.some(h => h.command?.includes('pre-bash')));
|
|
376
|
+
if (!hasPreWrite && !hasPreBash)
|
|
377
|
+
return { name: 'Guidance Gates', status: 'warn', message: 'gates-handler.cjs present but no gates registered', fix: 'monomind guidance setup' };
|
|
378
|
+
if (!hasPreWrite)
|
|
379
|
+
return { name: 'Guidance Gates', status: 'warn', message: 'pre-write hook not registered — secrets gate inactive', fix: 'monomind guidance setup' };
|
|
380
|
+
if (!hasPreBash)
|
|
381
|
+
return { name: 'Guidance Gates', status: 'warn', message: 'pre-bash hook not registered — destructive-ops gate inactive', fix: 'monomind guidance setup' };
|
|
382
|
+
return { name: 'Guidance Gates', status: 'pass', message: 'destructive-ops (pre-bash) + secrets (pre-write) gates active' };
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return { name: 'Guidance Gates', status: 'warn', message: 'Could not parse .claude/settings.json', fix: 'monomind guidance setup --force' };
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
//# sourceMappingURL=doctor-project-checks.js.map
|