monomind 1.11.14 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/generated/channel-intelligence-director.md +87 -0
- package/.claude/agents/generated/chief-growth-officer.md +88 -0
- package/.claude/agents/generated/content-seo-strategist.md +90 -0
- package/.claude/agents/generated/developer-community-strategist.md +91 -0
- package/.claude/agents/generated/outreach-partnership-strategist.md +90 -0
- package/.claude/agents/generated/social-media-strategist.md +91 -0
- package/.claude/agents/generated/video-visual-strategist.md +90 -0
- package/.claude/commands/mastermind/master.md +1 -1
- package/.claude/helpers/auto-memory-hook.mjs +13 -4
- package/.claude/helpers/control-start.cjs +5 -0
- package/.claude/helpers/event-logger.cjs +114 -0
- package/.claude/helpers/handlers/adr-draft-handler.cjs +19 -5
- package/.claude/helpers/handlers/agent-start-handler.cjs +13 -4
- package/.claude/helpers/handlers/compact-handler.cjs +2 -0
- package/.claude/helpers/handlers/edit-handler.cjs +1 -1
- package/.claude/helpers/handlers/gates-handler.cjs +3 -0
- package/.claude/helpers/handlers/graph-status-handler.cjs +14 -8
- package/.claude/helpers/handlers/loops-status-handler.cjs +5 -2
- package/.claude/helpers/handlers/route-handler.cjs +24 -10
- package/.claude/helpers/handlers/session-handler.cjs +11 -4
- package/.claude/helpers/handlers/session-restore-handler.cjs +35 -19
- package/.claude/helpers/handlers/task-handler.cjs +13 -5
- package/.claude/helpers/hook-handler.cjs +40 -0
- package/.claude/helpers/intelligence.cjs +130 -53
- package/.claude/helpers/loop-tracker.cjs +15 -3
- package/.claude/helpers/memory-palace.cjs +461 -0
- package/.claude/helpers/memory.cjs +138 -14
- package/.claude/helpers/metrics-db.mjs +87 -0
- package/.claude/helpers/router.cjs +300 -42
- package/.claude/helpers/session.cjs +89 -30
- package/.claude/helpers/statusline.cjs +148 -4
- package/.claude/helpers/toggle-statusline.cjs +73 -0
- package/.claude/helpers/token-tracker.cjs +934 -0
- package/.claude/helpers/utils/micro-agents.cjs +20 -4
- package/.claude/helpers/utils/monograph.cjs +39 -4
- package/.claude/helpers/utils/telemetry.cjs +3 -3
- package/.claude/scheduled_tasks.lock +1 -1
- package/.claude/settings.json +92 -1
- package/.claude/skills/mastermind/_protocol.md +25 -15
- package/.claude/skills/mastermind/architect.md +3 -3
- package/.claude/skills/mastermind/autodev.md +4 -2
- package/.claude/skills/mastermind/idea.md +10 -0
- package/.claude/skills/mastermind/ops.md +3 -3
- package/.claude/skills/mastermind/runorg.md +153 -86
- package/package.json +20 -3
- package/packages/@monomind/cli/dist/src/agents/registry-builder.js +2 -0
- package/packages/@monomind/cli/dist/src/autopilot-state.js +10 -5
- package/packages/@monomind/cli/dist/src/benchmarks/benchmark-runner.js +13 -0
- package/packages/@monomind/cli/dist/src/benchmarks/metric-evaluators.js +20 -9
- package/packages/@monomind/cli/dist/src/browser/actions.js +10 -3
- package/packages/@monomind/cli/dist/src/browser/browser.js +12 -2
- package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -3
- package/packages/@monomind/cli/dist/src/browser/har.js +27 -5
- package/packages/@monomind/cli/dist/src/commands/agent.js +11 -8
- package/packages/@monomind/cli/dist/src/commands/analyze.js +36 -21
- package/packages/@monomind/cli/dist/src/commands/autopilot.js +12 -4
- package/packages/@monomind/cli/dist/src/commands/benchmark.js +51 -8
- package/packages/@monomind/cli/dist/src/commands/browse.js +5 -2
- package/packages/@monomind/cli/dist/src/commands/claims.js +29 -11
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +25 -5
- package/packages/@monomind/cli/dist/src/commands/config.js +15 -7
- package/packages/@monomind/cli/dist/src/commands/daemon.js +6 -0
- package/packages/@monomind/cli/dist/src/commands/deployment.js +34 -19
- package/packages/@monomind/cli/dist/src/commands/doctor.js +192 -23
- package/packages/@monomind/cli/dist/src/commands/guidance.js +15 -2
- package/packages/@monomind/cli/dist/src/commands/hive-mind.js +37 -14
- package/packages/@monomind/cli/dist/src/commands/hooks.js +42 -25
- package/packages/@monomind/cli/dist/src/commands/init.js +9 -4
- package/packages/@monomind/cli/dist/src/commands/issues.js +29 -26
- package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -5
- package/packages/@monomind/cli/dist/src/commands/memory.js +10 -0
- package/packages/@monomind/cli/dist/src/commands/migrate.js +5 -5
- package/packages/@monomind/cli/dist/src/commands/monograph.js +18 -5
- package/packages/@monomind/cli/dist/src/commands/monovector/backup.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/monovector/benchmark.js +20 -7
- package/packages/@monomind/cli/dist/src/commands/monovector/import.js +15 -0
- package/packages/@monomind/cli/dist/src/commands/monovector/migrate.js +4 -1
- package/packages/@monomind/cli/dist/src/commands/monovector/optimize.js +11 -0
- package/packages/@monomind/cli/dist/src/commands/monovector/setup.js +11 -1
- package/packages/@monomind/cli/dist/src/commands/neural.js +1 -1
- package/packages/@monomind/cli/dist/src/commands/performance.js +20 -7
- package/packages/@monomind/cli/dist/src/commands/platforms.js +90 -8
- package/packages/@monomind/cli/dist/src/commands/plugins.js +12 -5
- package/packages/@monomind/cli/dist/src/commands/process.js +33 -10
- package/packages/@monomind/cli/dist/src/commands/progress.js +5 -3
- package/packages/@monomind/cli/dist/src/commands/providers.js +5 -5
- package/packages/@monomind/cli/dist/src/commands/replay.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/route.js +27 -7
- package/packages/@monomind/cli/dist/src/commands/security.js +4 -0
- package/packages/@monomind/cli/dist/src/commands/session.js +12 -1
- package/packages/@monomind/cli/dist/src/commands/start.js +11 -4
- package/packages/@monomind/cli/dist/src/commands/status.js +7 -4
- package/packages/@monomind/cli/dist/src/commands/swarm.js +27 -13
- package/packages/@monomind/cli/dist/src/commands/task.js +26 -11
- package/packages/@monomind/cli/dist/src/commands/tokens.js +7 -2
- package/packages/@monomind/cli/dist/src/commands/transfer-store.js +36 -22
- package/packages/@monomind/cli/dist/src/commands/update.js +15 -3
- package/packages/@monomind/cli/dist/src/commands/workflow.js +39 -6
- package/packages/@monomind/cli/dist/src/consensus/audit-writer.js +18 -7
- package/packages/@monomind/cli/dist/src/consensus/vote-signer.js +25 -8
- package/packages/@monomind/cli/dist/src/index.js +7 -3
- package/packages/@monomind/cli/dist/src/init/executor.js +14 -11
- package/packages/@monomind/cli/dist/src/init/shared-instructions-generator.js +20 -4
- package/packages/@monomind/cli/dist/src/init/statusline-generator.js +36 -15
- package/packages/@monomind/cli/dist/src/mcp-tools/a2a-tools.js +98 -13
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +16 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/analyze-tools.js +80 -17
- package/packages/@monomind/cli/dist/src/mcp-tools/browser-tools.js +84 -22
- package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +35 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +82 -17
- package/packages/@monomind/cli/dist/src/mcp-tools/coordination-tools.js +37 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +49 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +45 -18
- package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +75 -25
- package/packages/@monomind/cli/dist/src/mcp-tools/guidance-tools.js +32 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +91 -20
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +188 -29
- package/packages/@monomind/cli/dist/src/mcp-tools/memory-tools.js +25 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +11 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +476 -62
- package/packages/@monomind/cli/dist/src/mcp-tools/neural-tools.js +44 -9
- package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +45 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/progress-tools.js +7 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/request-tracker.js +15 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/security-tools.js +61 -9
- package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +45 -14
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +15 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +14 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +52 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +40 -6
- package/packages/@monomind/cli/dist/src/mcp-tools/transfer-tools.js +37 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/workflow-tools.js +29 -6
- package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +26 -10
- package/packages/@monomind/cli/dist/src/memory/intelligence.js +80 -19
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +21 -2
- package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +67 -3
- package/packages/@monomind/cli/dist/src/memory/sona-optimizer.js +14 -4
- package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +43 -7
- package/packages/@monomind/cli/dist/src/monovector/coverage-router.js +8 -4
- package/packages/@monomind/cli/dist/src/monovector/coverage-tools.js +6 -3
- package/packages/@monomind/cli/dist/src/monovector/diff-classifier.js +13 -0
- package/packages/@monomind/cli/dist/src/monovector/route-outcomes.d.ts +2 -1
- package/packages/@monomind/cli/dist/src/monovector/route-outcomes.js +46 -4
- package/packages/@monomind/cli/dist/src/plugins/manager.js +8 -3
- package/packages/@monomind/cli/dist/src/plugins/store/discovery.js +46 -2
- package/packages/@monomind/cli/dist/src/plugins/store/search.js +5 -4
- package/packages/@monomind/cli/dist/src/production/circuit-breaker.js +17 -3
- package/packages/@monomind/cli/dist/src/production/error-handler.js +3 -0
- package/packages/@monomind/cli/dist/src/production/monitoring.js +20 -3
- package/packages/@monomind/cli/dist/src/production/rate-limiter.js +13 -4
- package/packages/@monomind/cli/dist/src/production/retry.js +17 -9
- package/packages/@monomind/cli/dist/src/routing/embed-worker.js +6 -2
- package/packages/@monomind/cli/dist/src/routing/embedder.js +0 -0
- package/packages/@monomind/cli/dist/src/routing/llm-caller.js +13 -2
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +18 -3
- package/packages/@monomind/cli/dist/src/services/claim-service.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/services/claim-service.js +8 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +14 -2
- package/packages/@monomind/cli/dist/src/services/headless-worker-executor.js +18 -2
- package/packages/@monomind/cli/dist/src/services/worker-daemon.js +348 -17
- package/packages/@monomind/cli/dist/src/transfer/anonymization/index.d.ts +0 -3
- package/packages/@monomind/cli/dist/src/transfer/anonymization/index.js +16 -1
- package/packages/@monomind/cli/dist/src/transfer/export.js +8 -0
- package/packages/@monomind/cli/dist/src/transfer/ipfs/upload.js +33 -3
- package/packages/@monomind/cli/dist/src/transfer/serialization/cfp.js +8 -2
- package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +37 -3
- package/packages/@monomind/cli/dist/src/transfer/store/discovery.js +45 -3
- package/packages/@monomind/cli/dist/src/transfer/store/download.js +5 -0
- package/packages/@monomind/cli/dist/src/transfer/store/publish.js +13 -1
- package/packages/@monomind/cli/dist/src/transfer/store/registry.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/transfer/store/registry.js +30 -5
- package/packages/@monomind/cli/dist/src/transfer/store/search.js +20 -5
- package/packages/@monomind/cli/dist/src/update/checker.js +59 -7
- package/packages/@monomind/cli/dist/src/update/executor.js +50 -3
- package/packages/@monomind/cli/dist/src/update/index.js +18 -1
- package/packages/@monomind/cli/dist/src/update/rate-limiter.d.ts +6 -0
- package/packages/@monomind/cli/dist/src/update/rate-limiter.js +79 -7
- package/packages/@monomind/cli/dist/src/update/validator.js +52 -1
- package/packages/@monomind/cli/package.json +2 -3
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* github.com/monoes/monomind
|
|
6
6
|
*/
|
|
7
7
|
import { output } from '../output.js';
|
|
8
|
-
import { existsSync,
|
|
8
|
+
import { existsSync, lstatSync, rmSync, readdirSync } from 'fs';
|
|
9
9
|
import { join } from 'path';
|
|
10
10
|
/**
|
|
11
11
|
* Artifact directories and files that monomind/monomind may create
|
|
@@ -30,11 +30,27 @@ const KEEP_CONFIG_PATHS = [
|
|
|
30
30
|
join('.claude', 'settings.json'),
|
|
31
31
|
];
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* Maximum directory recursion depth for size calculation.
|
|
34
|
+
* Prevents stack overflow on deeply-nested or circular-symlink trees.
|
|
34
35
|
*/
|
|
35
|
-
|
|
36
|
+
const MAX_SIZE_DEPTH = 20;
|
|
37
|
+
/**
|
|
38
|
+
* Calculate the total size of a path (file or directory) in bytes.
|
|
39
|
+
*
|
|
40
|
+
* Uses lstatSync (not statSync) so that symlinks are never followed:
|
|
41
|
+
* a symlink counts only the size of the link itself, not its target.
|
|
42
|
+
* This prevents a crafted symlink (e.g. .claude -> /) from causing
|
|
43
|
+
* the cleanup command to recursively traverse the entire filesystem.
|
|
44
|
+
*/
|
|
45
|
+
function getSize(fullPath, depth = 0) {
|
|
46
|
+
if (depth > MAX_SIZE_DEPTH)
|
|
47
|
+
return 0;
|
|
36
48
|
try {
|
|
37
|
-
const stat =
|
|
49
|
+
const stat = lstatSync(fullPath);
|
|
50
|
+
if (stat.isSymbolicLink()) {
|
|
51
|
+
// Count only the symlink entry itself; never traverse the target.
|
|
52
|
+
return stat.size;
|
|
53
|
+
}
|
|
38
54
|
if (stat.isFile()) {
|
|
39
55
|
return stat.size;
|
|
40
56
|
}
|
|
@@ -42,7 +58,11 @@ function getSize(fullPath) {
|
|
|
42
58
|
let total = 0;
|
|
43
59
|
const entries = readdirSync(fullPath, { withFileTypes: true });
|
|
44
60
|
for (const entry of entries) {
|
|
45
|
-
|
|
61
|
+
// Skip symlinks at the entry level too — lstatSync below will still
|
|
62
|
+
// catch them, but checking here avoids unnecessary path joins.
|
|
63
|
+
if (!entry.isSymbolicLink()) {
|
|
64
|
+
total += getSize(join(fullPath, entry.name), depth + 1);
|
|
65
|
+
}
|
|
46
66
|
}
|
|
47
67
|
return total;
|
|
48
68
|
}
|
|
@@ -69,7 +69,7 @@ const getCommand = {
|
|
|
69
69
|
{ command: 'monomind config get -k memory.backend', description: 'Get memory backend' }
|
|
70
70
|
],
|
|
71
71
|
action: async (ctx) => {
|
|
72
|
-
const key = ctx.flags.key || ctx.args[0];
|
|
72
|
+
const key = (ctx.flags.key || ctx.args[0] || '').slice(0, 256);
|
|
73
73
|
if (!key) {
|
|
74
74
|
// Show all config from actual config file (fall back to defaults)
|
|
75
75
|
const config = configManager.getConfig(ctx.cwd);
|
|
@@ -102,6 +102,14 @@ const getCommand = {
|
|
|
102
102
|
});
|
|
103
103
|
return { success: true, data: flatEntries };
|
|
104
104
|
}
|
|
105
|
+
// Prototype pollution guard — mirrors the same check in setCommand.
|
|
106
|
+
const FORBIDDEN_KEY_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
107
|
+
for (const seg of key.split('.')) {
|
|
108
|
+
if (FORBIDDEN_KEY_SEGMENTS.has(seg)) {
|
|
109
|
+
output.printError(`Forbidden config key segment: "${seg}"`);
|
|
110
|
+
return { success: false, exitCode: 1 };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
105
113
|
const value = configManager.get(ctx.cwd, key);
|
|
106
114
|
if (value === undefined) {
|
|
107
115
|
output.printError(`Configuration key not found: ${key}`);
|
|
@@ -141,8 +149,8 @@ const setCommand = {
|
|
|
141
149
|
{ command: 'monomind config set -k memory.backend -v agentdb', description: 'Set memory backend' }
|
|
142
150
|
],
|
|
143
151
|
action: async (ctx) => {
|
|
144
|
-
const key = ctx.flags.key || ctx.args[0];
|
|
145
|
-
const value = ctx.flags.value
|
|
152
|
+
const key = (ctx.flags.key || ctx.args[0] || '').slice(0, 256);
|
|
153
|
+
const value = (ctx.flags.value ?? ctx.args[1] ?? '');
|
|
146
154
|
if (!key || value === undefined) {
|
|
147
155
|
output.printError('Both key and value are required');
|
|
148
156
|
return { success: false, exitCode: 1 };
|
|
@@ -203,10 +211,10 @@ const providersCommand = {
|
|
|
203
211
|
{ name: 'gemini', model: 'gemini-2.0-flash', priority: 4, enabled: false, status: 'Disabled' }
|
|
204
212
|
];
|
|
205
213
|
// Handle mutation flags
|
|
206
|
-
const addProvider = ctx.flags.add;
|
|
207
|
-
const removeProvider = ctx.flags.remove;
|
|
208
|
-
const enableProvider = ctx.flags.enable;
|
|
209
|
-
const disableProvider = ctx.flags.disable;
|
|
214
|
+
const addProvider = ctx.flags.add?.slice(0, 64);
|
|
215
|
+
const removeProvider = ctx.flags.remove?.slice(0, 64);
|
|
216
|
+
const enableProvider = ctx.flags.enable?.slice(0, 64);
|
|
217
|
+
const disableProvider = ctx.flags.disable?.slice(0, 64);
|
|
210
218
|
if (addProvider || removeProvider || enableProvider || disableProvider) {
|
|
211
219
|
// Read current providers from config
|
|
212
220
|
let currentProviders = configManager.get(ctx.cwd, 'providers') || [];
|
|
@@ -327,6 +327,10 @@ async function killBackgroundDaemon(projectRoot) {
|
|
|
327
327
|
return false;
|
|
328
328
|
}
|
|
329
329
|
try {
|
|
330
|
+
if (fs.statSync(pidFile).size > 32) {
|
|
331
|
+
fs.unlinkSync(pidFile);
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
330
334
|
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
331
335
|
if (isNaN(pid)) {
|
|
332
336
|
fs.unlinkSync(pidFile);
|
|
@@ -376,6 +380,8 @@ function getBackgroundDaemonPid(projectRoot) {
|
|
|
376
380
|
return null;
|
|
377
381
|
}
|
|
378
382
|
try {
|
|
383
|
+
if (fs.statSync(pidFile).size > 32)
|
|
384
|
+
return null;
|
|
379
385
|
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
380
386
|
return isNaN(pid) ? null : pid;
|
|
381
387
|
}
|
|
@@ -19,11 +19,23 @@ function getStatePath(cwd) {
|
|
|
19
19
|
function emptyState() {
|
|
20
20
|
return { environments: {}, history: [], activeDeployment: undefined };
|
|
21
21
|
}
|
|
22
|
+
const MAX_DEPLOYMENT_STATE_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
23
|
+
// Input length caps to prevent DoS via unbounded strings stored to disk
|
|
24
|
+
const MAX_ENV_NAME_LEN = 128;
|
|
25
|
+
const MAX_VERSION_LEN = 64;
|
|
26
|
+
const MAX_DESCRIPTION_LEN = 1024;
|
|
27
|
+
const MAX_URL_LEN = 2048;
|
|
28
|
+
const MAX_ENV_TYPE_LEN = 64;
|
|
29
|
+
const MAX_HISTORY_LIMIT = 1000;
|
|
30
|
+
const MAX_LOGS_LIMIT = 1000;
|
|
22
31
|
function loadDeploymentState(cwd) {
|
|
23
32
|
const filePath = getStatePath(cwd);
|
|
24
33
|
if (!fs.existsSync(filePath)) {
|
|
25
34
|
return emptyState();
|
|
26
35
|
}
|
|
36
|
+
if (fs.statSync(filePath).size > MAX_DEPLOYMENT_STATE_BYTES) {
|
|
37
|
+
return emptyState();
|
|
38
|
+
}
|
|
27
39
|
try {
|
|
28
40
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
29
41
|
const parsed = JSON.parse(raw);
|
|
@@ -66,6 +78,9 @@ function readProjectVersion(cwd) {
|
|
|
66
78
|
if (!fs.existsSync(pkgPath)) {
|
|
67
79
|
return null;
|
|
68
80
|
}
|
|
81
|
+
if (fs.statSync(pkgPath).size > 1024 * 1024) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
69
84
|
try {
|
|
70
85
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
71
86
|
return pkg.version ?? null;
|
|
@@ -92,10 +107,10 @@ const deployCommand = {
|
|
|
92
107
|
],
|
|
93
108
|
action: async (ctx) => {
|
|
94
109
|
try {
|
|
95
|
-
const envName = String(ctx.flags['env'] || 'staging');
|
|
110
|
+
const envName = String(ctx.flags['env'] || 'staging').slice(0, MAX_ENV_NAME_LEN);
|
|
96
111
|
const dryRun = Boolean(ctx.flags['dry-run']);
|
|
97
|
-
const description = ctx.flags['description'] ? String(ctx.flags['description']) : undefined;
|
|
98
|
-
let version = ctx.flags['version'] ? String(ctx.flags['version']) : null;
|
|
112
|
+
const description = ctx.flags['description'] ? String(ctx.flags['description']).slice(0, MAX_DESCRIPTION_LEN) : undefined;
|
|
113
|
+
let version = ctx.flags['version'] ? String(ctx.flags['version']).slice(0, MAX_VERSION_LEN) : null;
|
|
99
114
|
if (!version) {
|
|
100
115
|
version = readProjectVersion(ctx.cwd) || '0.0.0';
|
|
101
116
|
}
|
|
@@ -181,7 +196,7 @@ const statusCommand = {
|
|
|
181
196
|
action: async (ctx) => {
|
|
182
197
|
try {
|
|
183
198
|
const state = loadDeploymentState(ctx.cwd);
|
|
184
|
-
const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']) : null;
|
|
199
|
+
const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']).slice(0, MAX_ENV_NAME_LEN) : null;
|
|
185
200
|
output.writeln();
|
|
186
201
|
output.writeln(output.bold('Deployment Status'));
|
|
187
202
|
output.writeln();
|
|
@@ -277,13 +292,13 @@ const rollbackCommand = {
|
|
|
277
292
|
],
|
|
278
293
|
action: async (ctx) => {
|
|
279
294
|
try {
|
|
280
|
-
const envName = String(ctx.flags['env'] || '');
|
|
295
|
+
const envName = String(ctx.flags['env'] || '').slice(0, MAX_ENV_NAME_LEN);
|
|
281
296
|
if (!envName) {
|
|
282
297
|
output.printError('Environment is required', 'Use --env or -e to specify');
|
|
283
298
|
return { success: false, exitCode: 1 };
|
|
284
299
|
}
|
|
285
|
-
const targetVersion = ctx.flags['version'] ? String(ctx.flags['version']) : null;
|
|
286
|
-
const steps = parseInt(ctx.flags.steps || '1', 10);
|
|
300
|
+
const targetVersion = ctx.flags['version'] ? String(ctx.flags['version']).slice(0, MAX_VERSION_LEN) : null;
|
|
301
|
+
const steps = Math.min(Math.max(parseInt(ctx.flags.steps || '1', 10), 1), 100);
|
|
287
302
|
if (steps > 1) {
|
|
288
303
|
output.printWarning(`Multi-step rollback (--steps ${steps}) is not yet implemented. Rolling back 1 step only.`);
|
|
289
304
|
}
|
|
@@ -370,8 +385,8 @@ const historyCommand = {
|
|
|
370
385
|
action: async (ctx) => {
|
|
371
386
|
try {
|
|
372
387
|
const state = loadDeploymentState(ctx.cwd);
|
|
373
|
-
const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']) : null;
|
|
374
|
-
const limit = Number(ctx.flags['limit']) || 10;
|
|
388
|
+
const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']).slice(0, MAX_ENV_NAME_LEN) : null;
|
|
389
|
+
const limit = Math.min(Math.max(Number(ctx.flags['limit']) || 10, 1), MAX_HISTORY_LIMIT);
|
|
375
390
|
let records = [...state.history].reverse();
|
|
376
391
|
if (filterEnv) {
|
|
377
392
|
records = records.filter(r => r.environment === filterEnv);
|
|
@@ -455,7 +470,7 @@ const environmentsCommand = {
|
|
|
455
470
|
return { success: true };
|
|
456
471
|
}
|
|
457
472
|
if (action === 'add') {
|
|
458
|
-
const name = ctx.flags['name'] ? String(ctx.flags['name']) : null;
|
|
473
|
+
const name = ctx.flags['name'] ? String(ctx.flags['name']).slice(0, MAX_ENV_NAME_LEN) : null;
|
|
459
474
|
if (!name) {
|
|
460
475
|
output.printError('Environment name is required', 'Use --name or -n to specify');
|
|
461
476
|
return { success: false, exitCode: 1 };
|
|
@@ -464,8 +479,8 @@ const environmentsCommand = {
|
|
|
464
479
|
output.printWarning(`Environment '${name}' already exists`);
|
|
465
480
|
return { success: false, exitCode: 1 };
|
|
466
481
|
}
|
|
467
|
-
const envType = String(ctx.flags['type'] || 'local');
|
|
468
|
-
const url = ctx.flags['url'] ? String(ctx.flags['url']) : undefined;
|
|
482
|
+
const envType = String(ctx.flags['type'] || 'local').slice(0, MAX_ENV_TYPE_LEN);
|
|
483
|
+
const url = ctx.flags['url'] ? String(ctx.flags['url']).slice(0, MAX_URL_LEN) : undefined;
|
|
469
484
|
state.environments[name] = {
|
|
470
485
|
name,
|
|
471
486
|
type: envType,
|
|
@@ -481,7 +496,7 @@ const environmentsCommand = {
|
|
|
481
496
|
return { success: true };
|
|
482
497
|
}
|
|
483
498
|
if (action === 'remove') {
|
|
484
|
-
const name = ctx.flags['name'] ? String(ctx.flags['name']) : null;
|
|
499
|
+
const name = ctx.flags['name'] ? String(ctx.flags['name']).slice(0, MAX_ENV_NAME_LEN) : null;
|
|
485
500
|
if (!name) {
|
|
486
501
|
output.printError('Environment name is required', 'Use --name or -n to specify');
|
|
487
502
|
return { success: false, exitCode: 1 };
|
|
@@ -524,9 +539,9 @@ const logsCommand = {
|
|
|
524
539
|
action: async (ctx) => {
|
|
525
540
|
try {
|
|
526
541
|
const state = loadDeploymentState(ctx.cwd);
|
|
527
|
-
const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']) : null;
|
|
528
|
-
const deploymentId = ctx.flags['deployment'] ? String(ctx.flags['deployment']) : null;
|
|
529
|
-
const limit = Number(ctx.flags['lines']) || 50;
|
|
542
|
+
const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']).slice(0, MAX_ENV_NAME_LEN) : null;
|
|
543
|
+
const deploymentId = ctx.flags['deployment'] ? String(ctx.flags['deployment']).slice(0, 64) : null;
|
|
544
|
+
const limit = Math.min(Math.max(Number(ctx.flags['lines']) || 50, 1), MAX_LOGS_LIMIT);
|
|
530
545
|
output.writeln();
|
|
531
546
|
output.writeln(output.bold('Deployment Logs'));
|
|
532
547
|
output.writeln();
|
|
@@ -588,9 +603,9 @@ const releaseCommand = {
|
|
|
588
603
|
],
|
|
589
604
|
action: async (ctx) => {
|
|
590
605
|
try {
|
|
591
|
-
const envName = String(ctx.flags['env'] || 'production');
|
|
592
|
-
const description = ctx.flags['description'] ? String(ctx.flags['description']) : undefined;
|
|
593
|
-
let version = ctx.flags['version'] ? String(ctx.flags['version']) : null;
|
|
606
|
+
const envName = String(ctx.flags['env'] || 'production').slice(0, MAX_ENV_NAME_LEN);
|
|
607
|
+
const description = ctx.flags['description'] ? String(ctx.flags['description']).slice(0, MAX_DESCRIPTION_LEN) : undefined;
|
|
608
|
+
let version = ctx.flags['version'] ? String(ctx.flags['version']).slice(0, MAX_VERSION_LEN) : null;
|
|
594
609
|
if (!version) {
|
|
595
610
|
const pkgVersion = readProjectVersion(ctx.cwd);
|
|
596
611
|
if (!pkgVersion) {
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
import { output } from '../output.js';
|
|
8
8
|
import { existsSync, readFileSync, statSync } from 'fs';
|
|
9
9
|
import { join, dirname } from 'path';
|
|
10
|
+
const MAX_DOCTOR_PKG_BYTES = 1024 * 1024; // 1 MB — package.json / settings.json
|
|
11
|
+
const MAX_DOCTOR_CONFIG_BYTES = 10 * 1024 * 1024; // 10 MB — monomind.config.json / MCP configs
|
|
12
|
+
const MAX_DOCTOR_GITIGNORE_BYTES = 512 * 1024; // 512 KB — .gitignore
|
|
13
|
+
const MAX_DOCTOR_PID_BYTES = 64; // 64 bytes — daemon PID file
|
|
14
|
+
const MAX_DOCTOR_HELPER_BYTES = 2 * 1024 * 1024; // 2 MB — hook helper .cjs bundles
|
|
10
15
|
import { fileURLToPath } from 'url';
|
|
11
16
|
import { execSync, exec } from 'child_process';
|
|
12
17
|
import { homedir } from 'os';
|
|
@@ -67,7 +72,7 @@ async function checkConfigFile() {
|
|
|
67
72
|
'.monomind.json'
|
|
68
73
|
];
|
|
69
74
|
for (const configPath of jsonPaths) {
|
|
70
|
-
if (existsSync(configPath)) {
|
|
75
|
+
if (existsSync(configPath) && statSync(configPath).size <= MAX_DOCTOR_CONFIG_BYTES) {
|
|
71
76
|
try {
|
|
72
77
|
const content = readFileSync(configPath, 'utf8');
|
|
73
78
|
JSON.parse(content);
|
|
@@ -95,7 +100,7 @@ async function checkConfigFile() {
|
|
|
95
100
|
async function checkDaemonStatus() {
|
|
96
101
|
try {
|
|
97
102
|
const pidFile = '.monomind/daemon.pid';
|
|
98
|
-
if (existsSync(pidFile)) {
|
|
103
|
+
if (existsSync(pidFile) && statSync(pidFile).size <= MAX_DOCTOR_PID_BYTES) {
|
|
99
104
|
const pid = readFileSync(pidFile, 'utf8').trim();
|
|
100
105
|
try {
|
|
101
106
|
process.kill(parseInt(pid, 10), 0); // Check if process exists
|
|
@@ -184,7 +189,7 @@ async function checkMcpServers() {
|
|
|
184
189
|
'.mcp.json'
|
|
185
190
|
];
|
|
186
191
|
for (const configPath of mcpConfigPaths) {
|
|
187
|
-
if (existsSync(configPath)) {
|
|
192
|
+
if (existsSync(configPath) && statSync(configPath).size <= MAX_DOCTOR_CONFIG_BYTES) {
|
|
188
193
|
try {
|
|
189
194
|
const content = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
190
195
|
const servers = content.mcpServers || content.servers || {};
|
|
@@ -202,7 +207,7 @@ async function checkMcpServers() {
|
|
|
202
207
|
}
|
|
203
208
|
}
|
|
204
209
|
}
|
|
205
|
-
return { name: 'MCP Servers', status: 'warn', message: 'No MCP config found', fix: 'claude mcp add monomind npx
|
|
210
|
+
return { name: 'MCP Servers', status: 'warn', message: 'No MCP config found', fix: 'claude mcp add monomind -- npx -y monomind@latest mcp start' };
|
|
206
211
|
}
|
|
207
212
|
// Check disk space (async with proper env inheritance)
|
|
208
213
|
async function checkDiskSpace() {
|
|
@@ -260,11 +265,11 @@ async function checkVersionFreshness() {
|
|
|
260
265
|
for (;;) {
|
|
261
266
|
const candidate = join(dir, 'package.json');
|
|
262
267
|
try {
|
|
263
|
-
if (existsSync(candidate)) {
|
|
268
|
+
if (existsSync(candidate) && statSync(candidate).size <= MAX_DOCTOR_PKG_BYTES) {
|
|
264
269
|
const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
265
270
|
if (pkg.version &&
|
|
266
271
|
typeof pkg.name === 'string' &&
|
|
267
|
-
(pkg.name === '@monomind/cli' || pkg.name === 'monomind')) {
|
|
272
|
+
(pkg.name === '@monomind/cli' || pkg.name === 'monomind' || pkg.name === '@monoes/monomindcli')) {
|
|
268
273
|
currentVersion = pkg.version;
|
|
269
274
|
break;
|
|
270
275
|
}
|
|
@@ -287,10 +292,10 @@ async function checkVersionFreshness() {
|
|
|
287
292
|
const isNpx = process.argv[1]?.includes('_npx') ||
|
|
288
293
|
process.env.npm_execpath?.includes('npx') ||
|
|
289
294
|
process.cwd().includes('_npx');
|
|
290
|
-
// Query npm for latest version
|
|
295
|
+
// Query npm for latest version of the published umbrella package
|
|
291
296
|
let latestVersion = currentVersion;
|
|
292
297
|
try {
|
|
293
|
-
const npmInfo = await runCommand('npm view
|
|
298
|
+
const npmInfo = await runCommand('npm view monomind version', 5000);
|
|
294
299
|
latestVersion = npmInfo.trim();
|
|
295
300
|
}
|
|
296
301
|
catch {
|
|
@@ -322,8 +327,8 @@ async function checkVersionFreshness() {
|
|
|
322
327
|
(latest.major === current.major && latest.minor === current.minor && latest.patch === current.patch && latest.prerelease > current.prerelease));
|
|
323
328
|
if (isOutdated) {
|
|
324
329
|
const fix = isNpx
|
|
325
|
-
? 'rm -rf ~/.npm/_npx/* && npx -y
|
|
326
|
-
: 'npm update
|
|
330
|
+
? 'rm -rf ~/.npm/_npx/* && npx -y monomind@latest doctor'
|
|
331
|
+
: 'npm update -g monomind';
|
|
327
332
|
return {
|
|
328
333
|
name: 'Version Freshness',
|
|
329
334
|
status: 'warn',
|
|
@@ -387,18 +392,39 @@ async function installClaudeCode() {
|
|
|
387
392
|
async function checkMonograph() {
|
|
388
393
|
try {
|
|
389
394
|
const __filename = fileURLToPath(import.meta.url);
|
|
395
|
+
const _base = dirname(__filename);
|
|
396
|
+
let _globalRoot = '';
|
|
397
|
+
try {
|
|
398
|
+
_globalRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
399
|
+
}
|
|
400
|
+
catch { /* no npm */ }
|
|
390
401
|
const candidates = [
|
|
391
|
-
|
|
392
|
-
join(
|
|
402
|
+
// local dev monorepo paths (both old @monomind and published @monoes scope)
|
|
403
|
+
join(_base, '..', '..', 'node_modules', '@monomind', 'monograph', 'package.json'),
|
|
404
|
+
join(_base, '..', '..', '..', '..', 'node_modules', '@monomind', 'monograph', 'package.json'),
|
|
405
|
+
join(_base, '..', '..', 'node_modules', '@monoes', 'monograph', 'package.json'),
|
|
406
|
+
join(_base, '..', '..', '..', '..', 'node_modules', '@monoes', 'monograph', 'package.json'),
|
|
407
|
+
// global install paths
|
|
408
|
+
...(_globalRoot ? [
|
|
409
|
+
join(_globalRoot, '@monomind', 'monograph', 'package.json'),
|
|
410
|
+
join(_globalRoot, '@monoes', 'monograph', 'package.json'),
|
|
411
|
+
] : []),
|
|
393
412
|
];
|
|
394
|
-
|
|
395
|
-
|
|
413
|
+
const found = candidates.find(p => existsSync(p) && statSync(p).size <= MAX_DOCTOR_PKG_BYTES);
|
|
414
|
+
if (found) {
|
|
415
|
+
try {
|
|
416
|
+
const pkg = JSON.parse(readFileSync(found, 'utf-8'));
|
|
417
|
+
return { name: 'Monograph', status: 'pass', message: `v${pkg.version || '?'} available (knowledge graph engine)` };
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
return { name: 'Monograph', status: 'pass', message: 'available (knowledge graph engine)' };
|
|
421
|
+
}
|
|
396
422
|
}
|
|
397
423
|
return {
|
|
398
424
|
name: 'Monograph',
|
|
399
425
|
status: 'warn',
|
|
400
426
|
message: 'Package not found (knowledge graph disabled)',
|
|
401
|
-
fix: 'npm install
|
|
427
|
+
fix: 'npm install -g monomind@latest # reinstall to get @monoes/monograph'
|
|
402
428
|
};
|
|
403
429
|
}
|
|
404
430
|
catch {
|
|
@@ -406,10 +432,124 @@ async function checkMonograph() {
|
|
|
406
432
|
name: 'Monograph',
|
|
407
433
|
status: 'warn',
|
|
408
434
|
message: 'Package check failed (knowledge graph may be unavailable)',
|
|
409
|
-
fix: 'npm install
|
|
435
|
+
fix: 'npm install -g monomind@latest'
|
|
410
436
|
};
|
|
411
437
|
}
|
|
412
438
|
}
|
|
439
|
+
// Check monograph graph freshness (is the graph built? how stale?)
|
|
440
|
+
async function checkMonographFreshness() {
|
|
441
|
+
try {
|
|
442
|
+
const cwd = process.cwd();
|
|
443
|
+
const dbPath = join(cwd, '.monomind', 'monograph.db');
|
|
444
|
+
const lockPath = join(cwd, '.monomind', 'graph', '.rebuild-lock');
|
|
445
|
+
const statsPath = join(cwd, '.monomind', 'graph', 'stats.json');
|
|
446
|
+
// Check if graph exists at all
|
|
447
|
+
const hasDb = existsSync(dbPath);
|
|
448
|
+
const hasLock = existsSync(lockPath);
|
|
449
|
+
const hasStats = existsSync(statsPath);
|
|
450
|
+
if (!hasDb && !hasStats) {
|
|
451
|
+
return {
|
|
452
|
+
name: 'Graph freshness',
|
|
453
|
+
status: 'warn',
|
|
454
|
+
message: 'No monograph graph built yet',
|
|
455
|
+
fix: 'mcp__monomind__monograph_build codeOnly:true — or run npx monomind@latest hooks graph-status',
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
// Determine last build time
|
|
459
|
+
let buildMs = 0;
|
|
460
|
+
if (hasDb) {
|
|
461
|
+
try {
|
|
462
|
+
buildMs = Math.max(buildMs, statSync(dbPath).mtimeMs);
|
|
463
|
+
}
|
|
464
|
+
catch { /* ignore */ }
|
|
465
|
+
}
|
|
466
|
+
if (hasLock) {
|
|
467
|
+
try {
|
|
468
|
+
buildMs = Math.max(buildMs, statSync(lockPath).mtimeMs);
|
|
469
|
+
}
|
|
470
|
+
catch { /* ignore */ }
|
|
471
|
+
}
|
|
472
|
+
if (hasStats) {
|
|
473
|
+
try {
|
|
474
|
+
buildMs = Math.max(buildMs, statSync(statsPath).mtimeMs);
|
|
475
|
+
}
|
|
476
|
+
catch { /* ignore */ }
|
|
477
|
+
}
|
|
478
|
+
if (buildMs === 0) {
|
|
479
|
+
return { name: 'Graph freshness', status: 'warn', message: 'Graph exists but build time unknown' };
|
|
480
|
+
}
|
|
481
|
+
// Count commits since last build
|
|
482
|
+
const buildIso = new Date(buildMs).toISOString();
|
|
483
|
+
let commitsBehind = 0;
|
|
484
|
+
try {
|
|
485
|
+
const out = execSync(`git rev-list --count --since='${buildIso}' HEAD 2>/dev/null`, {
|
|
486
|
+
encoding: 'utf8', timeout: 2000, cwd,
|
|
487
|
+
}).trim();
|
|
488
|
+
commitsBehind = parseInt(out, 10) || 0;
|
|
489
|
+
}
|
|
490
|
+
catch { /* git not available or not a git repo */ }
|
|
491
|
+
const ageMinutes = Math.floor((Date.now() - buildMs) / 60000);
|
|
492
|
+
const ageStr = ageMinutes < 60 ? `${ageMinutes}m ago` : `${Math.floor(ageMinutes / 60)}h ago`;
|
|
493
|
+
if (commitsBehind === 0) {
|
|
494
|
+
return { name: 'Graph freshness', status: 'pass', message: `FRESH — built ${ageStr}, 0 commits behind` };
|
|
495
|
+
}
|
|
496
|
+
else if (commitsBehind <= 5) {
|
|
497
|
+
return {
|
|
498
|
+
name: 'Graph freshness',
|
|
499
|
+
status: 'warn',
|
|
500
|
+
message: `${commitsBehind} commit(s) behind — built ${ageStr}`,
|
|
501
|
+
fix: 'mcp__monomind__monograph_build codeOnly:true',
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
return {
|
|
506
|
+
name: 'Graph freshness',
|
|
507
|
+
status: 'fail',
|
|
508
|
+
message: `STALE — ${commitsBehind} commits behind (built ${ageStr})`,
|
|
509
|
+
fix: 'mcp__monomind__monograph_build codeOnly:true',
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
return { name: 'Graph freshness', status: 'warn', message: 'Could not check graph freshness' };
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// Check @monoes/memory (optional HNSW vector search package)
|
|
518
|
+
async function checkMonoesMemory() {
|
|
519
|
+
try {
|
|
520
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
521
|
+
const _base = dirname(__filename);
|
|
522
|
+
let _globalRoot = '';
|
|
523
|
+
try {
|
|
524
|
+
_globalRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
525
|
+
}
|
|
526
|
+
catch { /* no npm */ }
|
|
527
|
+
const candidates = [
|
|
528
|
+
join(_base, '..', '..', 'node_modules', '@monoes', 'memory', 'package.json'),
|
|
529
|
+
join(_base, '..', '..', '..', '..', 'node_modules', '@monoes', 'memory', 'package.json'),
|
|
530
|
+
...(_globalRoot ? [join(_globalRoot, '@monoes', 'memory', 'package.json')] : []),
|
|
531
|
+
];
|
|
532
|
+
const found = candidates.find(p => existsSync(p) && statSync(p).size <= MAX_DOCTOR_PKG_BYTES);
|
|
533
|
+
if (found) {
|
|
534
|
+
try {
|
|
535
|
+
const pkg = JSON.parse(readFileSync(found, 'utf-8'));
|
|
536
|
+
return { name: 'Vector Memory', status: 'pass', message: `@monoes/memory v${pkg.version || '?'} (HNSW search enabled)` };
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
return { name: 'Vector Memory', status: 'pass', message: '@monoes/memory available (HNSW search enabled)' };
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
name: 'Vector Memory',
|
|
544
|
+
status: 'warn',
|
|
545
|
+
message: '@monoes/memory not installed (vector search disabled — using fallback)',
|
|
546
|
+
fix: 'npm install @monoes/memory'
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
return { name: 'Vector Memory', status: 'warn', message: 'Vector memory check failed' };
|
|
551
|
+
}
|
|
552
|
+
}
|
|
413
553
|
// Check agentic-flow v1 integration (filesystem-based to avoid slow WASM/DB init)
|
|
414
554
|
// Resolve the path to the bundled (npm-installed) copy of a helper file.
|
|
415
555
|
// Walks up from this module's location to find the package root, then joins
|
|
@@ -420,10 +560,10 @@ function _resolveBundledHelper(relativePath) {
|
|
|
420
560
|
let dir = dirname(thisFile);
|
|
421
561
|
for (;;) {
|
|
422
562
|
const candidate = join(dir, 'package.json');
|
|
423
|
-
if (existsSync(candidate)) {
|
|
563
|
+
if (existsSync(candidate) && statSync(candidate).size <= MAX_DOCTOR_PKG_BYTES) {
|
|
424
564
|
try {
|
|
425
565
|
const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
426
|
-
if (pkg.name === '@monomind/cli' || pkg.name === 'monomind') {
|
|
566
|
+
if (pkg.name === '@monomind/cli' || pkg.name === 'monomind' || pkg.name === '@monoes/monomindcli') {
|
|
427
567
|
const helperPath = join(dir, relativePath);
|
|
428
568
|
return existsSync(helperPath) ? helperPath : null;
|
|
429
569
|
}
|
|
@@ -451,11 +591,15 @@ async function _detectStaleHelpers() {
|
|
|
451
591
|
const local = join(process.cwd(), '.claude', 'helpers', name);
|
|
452
592
|
if (!existsSync(local))
|
|
453
593
|
continue;
|
|
594
|
+
if (statSync(local).size > MAX_DOCTOR_HELPER_BYTES)
|
|
595
|
+
continue; // skip oversized helper
|
|
454
596
|
const bundled = _resolveBundledHelper(join('.claude', 'helpers', name));
|
|
455
597
|
if (!bundled) {
|
|
456
598
|
missing.push(name);
|
|
457
599
|
continue;
|
|
458
600
|
}
|
|
601
|
+
if (statSync(bundled).size > MAX_DOCTOR_HELPER_BYTES)
|
|
602
|
+
continue; // skip oversized bundled
|
|
459
603
|
try {
|
|
460
604
|
const hashLocal = crypto.createHash('sha256').update(readFileSync(local)).digest('hex');
|
|
461
605
|
const hashBundled = crypto.createHash('sha256').update(readFileSync(bundled)).digest('hex');
|
|
@@ -513,6 +657,9 @@ async function checkAgenticFlow() {
|
|
|
513
657
|
fix: 'npm install agentic-flow@latest'
|
|
514
658
|
};
|
|
515
659
|
}
|
|
660
|
+
if (statSync(pkgJsonPath).size > MAX_DOCTOR_PKG_BYTES) {
|
|
661
|
+
return { name: 'agentic-flow', status: 'warn', message: 'package.json too large to parse' };
|
|
662
|
+
}
|
|
516
663
|
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
517
664
|
const version = pkg.version || 'unknown';
|
|
518
665
|
const exports = pkg.exports || {};
|
|
@@ -524,7 +671,7 @@ async function checkAgenticFlow() {
|
|
|
524
671
|
return {
|
|
525
672
|
name: 'agentic-flow',
|
|
526
673
|
status: 'pass',
|
|
527
|
-
message: `v${version}
|
|
674
|
+
message: `v${version}${features.length ? ' (' + features.join(', ') + ')' : ' (installed)'}`
|
|
528
675
|
};
|
|
529
676
|
}
|
|
530
677
|
catch {
|
|
@@ -609,6 +756,9 @@ async function checkGitignoreCoverage() {
|
|
|
609
756
|
fix: 'echo ".monomind/\\n**/.monomind/" >> .gitignore',
|
|
610
757
|
};
|
|
611
758
|
}
|
|
759
|
+
if (statSync(gitignorePath).size > MAX_DOCTOR_GITIGNORE_BYTES) {
|
|
760
|
+
return { name: 'Gitignore Coverage', status: 'warn', message: '.gitignore too large to parse' };
|
|
761
|
+
}
|
|
612
762
|
const content = readFileSync(gitignorePath, 'utf-8');
|
|
613
763
|
const lines = content.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
|
|
614
764
|
const missing = REQUIRED_GITIGNORE_PATTERNS.filter(({ pattern }) => {
|
|
@@ -651,6 +801,9 @@ async function checkGuidanceGates() {
|
|
|
651
801
|
};
|
|
652
802
|
}
|
|
653
803
|
try {
|
|
804
|
+
if (statSync(settingsPath).size > MAX_DOCTOR_CONFIG_BYTES) {
|
|
805
|
+
return { name: 'Guidance Gates', status: 'warn', message: 'settings.json too large to parse' };
|
|
806
|
+
}
|
|
654
807
|
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
655
808
|
const preToolUse = settings?.hooks?.PreToolUse ?? [];
|
|
656
809
|
const hasPreWrite = preToolUse.some(e => e.matcher === 'Write|Edit|MultiEdit' && e.hooks.some(h => h.command?.includes('pre-write')));
|
|
@@ -723,7 +876,7 @@ export const doctorCommand = {
|
|
|
723
876
|
{
|
|
724
877
|
name: 'component',
|
|
725
878
|
short: 'c',
|
|
726
|
-
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, claude, disk, typescript, monograph, helpers, monoes, gates, gitignore)',
|
|
879
|
+
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, claude, disk, typescript, monograph, graph-freshness, memory-pkg, helpers, agentic-flow, monoes, gates, gitignore)',
|
|
727
880
|
type: 'string'
|
|
728
881
|
},
|
|
729
882
|
{
|
|
@@ -766,6 +919,8 @@ export const doctorCommand = {
|
|
|
766
919
|
checkDiskSpace,
|
|
767
920
|
checkBuildTools,
|
|
768
921
|
checkMonograph,
|
|
922
|
+
checkMonographFreshness,
|
|
923
|
+
checkMonoesMemory,
|
|
769
924
|
checkHelpersFresh,
|
|
770
925
|
checkAgenticFlow,
|
|
771
926
|
checkMonoesIntegration,
|
|
@@ -787,6 +942,8 @@ export const doctorCommand = {
|
|
|
787
942
|
'disk': checkDiskSpace,
|
|
788
943
|
'typescript': checkBuildTools,
|
|
789
944
|
'monograph': checkMonograph,
|
|
945
|
+
'graph-freshness': checkMonographFreshness,
|
|
946
|
+
'memory-pkg': checkMonoesMemory,
|
|
790
947
|
'helpers': checkHelpersFresh,
|
|
791
948
|
'agentic-flow': checkAgenticFlow,
|
|
792
949
|
'monoes': checkMonoesIntegration,
|
|
@@ -812,6 +969,14 @@ export const doctorCommand = {
|
|
|
812
969
|
const result = settledResult.value;
|
|
813
970
|
results.push(result);
|
|
814
971
|
output.writeln(formatCheck(result));
|
|
972
|
+
if (result.fix && result.status === 'fail') {
|
|
973
|
+
// Always show fix inline for failures — no flag needed
|
|
974
|
+
output.writeln(output.dim(` Fix: ${result.fix}`));
|
|
975
|
+
}
|
|
976
|
+
else if (result.fix && result.status === 'warn') {
|
|
977
|
+
// Show fix inline for warnings too, so users don't need --fix for common issues
|
|
978
|
+
output.writeln(output.dim(` Hint: ${result.fix}`));
|
|
979
|
+
}
|
|
815
980
|
if (result.fix && (result.status === 'fail' || result.status === 'warn')) {
|
|
816
981
|
fixes.push(`${result.name}: ${result.fix}`);
|
|
817
982
|
}
|
|
@@ -872,9 +1037,13 @@ export const doctorCommand = {
|
|
|
872
1037
|
output.writeln(output.dim(` ${fix}`));
|
|
873
1038
|
}
|
|
874
1039
|
}
|
|
875
|
-
else if (
|
|
876
|
-
|
|
877
|
-
|
|
1040
|
+
else if (!showFix) {
|
|
1041
|
+
// Only nudge about --fix for warnings (failures already showed their fix inline)
|
|
1042
|
+
const warnFixes = results.filter(r => r.status === 'warn' && r.fix).length;
|
|
1043
|
+
if (warnFixes > 0) {
|
|
1044
|
+
output.writeln();
|
|
1045
|
+
output.writeln(output.dim(`Run with --fix to see ${warnFixes} suggested fix${warnFixes > 1 ? 'es' : ''} for warnings`));
|
|
1046
|
+
}
|
|
878
1047
|
}
|
|
879
1048
|
// Overall result
|
|
880
1049
|
if (failed > 0) {
|