monomind 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -5
- package/packages/@monomind/cli/.claude/commands/mastermind/runorg.md +4 -0
- package/packages/@monomind/cli/.claude/helpers/control-start.cjs +50 -5
- package/packages/@monomind/cli/.claude/helpers/event-logger.cjs +109 -6
- package/packages/@monomind/cli/.claude/helpers/graphify-freshen.cjs +19 -17
- package/packages/@monomind/cli/.claude/helpers/handlers/agent-start-handler.cjs +60 -28
- package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +75 -18
- package/packages/@monomind/cli/.claude/helpers/handlers/edit-handler.cjs +27 -9
- package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +16 -7
- package/packages/@monomind/cli/.claude/helpers/handlers/loops-status-handler.cjs +1 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +4 -4
- package/packages/@monomind/cli/.claude/helpers/handlers/session-handler.cjs +40 -22
- package/packages/@monomind/cli/.claude/helpers/handlers/session-restore-handler.cjs +0 -12
- package/packages/@monomind/cli/.claude/helpers/handlers/task-handler.cjs +53 -22
- package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +54 -4
- package/packages/@monomind/cli/.claude/helpers/intelligence.cjs +94 -15
- package/packages/@monomind/cli/.claude/helpers/session.cjs +6 -1
- package/packages/@monomind/cli/.claude/helpers/statusline.cjs +10 -10
- package/packages/@monomind/cli/.claude/helpers/token-tracker.cjs +3 -3
- package/packages/@monomind/cli/.claude/helpers/utils/agent-registrations.cjs +41 -0
- package/packages/@monomind/cli/.claude/helpers/utils/fs-helpers.cjs +183 -0
- package/packages/@monomind/cli/.claude/helpers/utils/micro-agents.cjs +10 -6
- package/packages/@monomind/cli/.claude/helpers/utils/telemetry.cjs +35 -19
- package/packages/@monomind/cli/bin/cli.js +10 -1
- package/packages/@monomind/cli/dist/src/commands/agent-ops.js +9 -15
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +20 -4
- package/packages/@monomind/cli/dist/src/commands/completions.js +66 -261
- package/packages/@monomind/cli/dist/src/commands/doctor.js +7 -2
- package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +7 -0
- package/packages/@monomind/cli/dist/src/commands/index.js +3 -0
- package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -6
- package/packages/@monomind/cli/dist/src/commands/memory-admin.js +12 -6
- package/packages/@monomind/cli/dist/src/commands/memory-transfer.js +6 -13
- package/packages/@monomind/cli/dist/src/commands/monograph.js +3 -3
- package/packages/@monomind/cli/dist/src/commands/org.d.ts +3 -1
- package/packages/@monomind/cli/dist/src/commands/org.js +306 -129
- package/packages/@monomind/cli/dist/src/commands/start.js +142 -37
- package/packages/@monomind/cli/dist/src/commands/tokens.js +21 -3
- package/packages/@monomind/cli/dist/src/config-adapter.js +37 -17
- package/packages/@monomind/cli/dist/src/index.js +64 -28
- package/packages/@monomind/cli/dist/src/init/claudemd-generator.js +44 -11
- package/packages/@monomind/cli/dist/src/init/executor.js +63 -49
- package/packages/@monomind/cli/dist/src/mcp-client.d.ts +8 -3
- package/packages/@monomind/cli/dist/src/mcp-client.js +37 -3
- package/packages/@monomind/cli/dist/src/mcp-server.js +8 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +10 -5
- package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +30 -8
- package/packages/@monomind/cli/dist/src/mcp-tools/graphify-tools.js +11 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +51 -34
- package/packages/@monomind/cli/dist/src/mcp-tools/knowledge-tools.js +9 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +56 -14
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +65 -43
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +6 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +4 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/types.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/types.js +36 -1
- package/packages/@monomind/cli/dist/src/memory/hnsw-operations.d.ts +13 -1
- package/packages/@monomind/cli/dist/src/memory/hnsw-operations.js +66 -14
- package/packages/@monomind/cli/dist/src/memory/intelligence.js +99 -3
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +20 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.d.ts +24 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.js +68 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +30 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +96 -7
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.d.ts +10 -5
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +105 -8
- package/packages/@monomind/cli/dist/src/orgrt/live.html +56 -0
- package/packages/@monomind/cli/dist/src/orgrt/provider.js +1 -0
- package/packages/@monomind/cli/dist/src/orgrt/scheduler.d.ts +13 -0
- package/packages/@monomind/cli/dist/src/orgrt/scheduler.js +48 -0
- package/packages/@monomind/cli/dist/src/orgrt/server.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/orgrt/server.js +72 -0
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.js +108 -0
- package/packages/@monomind/cli/dist/src/output.js +12 -5
- package/packages/@monomind/cli/dist/src/parser.d.ts +32 -0
- package/packages/@monomind/cli/dist/src/parser.js +130 -5
- package/packages/@monomind/cli/dist/src/routing/embed-worker.js +13 -1
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +31 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.d.ts +21 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +60 -7
- package/packages/@monomind/cli/dist/src/services/crash-reporter.js +2 -1
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +40 -40
- package/packages/@monomind/cli/dist/src/ui/orgs.html +110 -11
- package/packages/@monomind/cli/dist/src/ui/server.mjs +304 -133
- package/packages/@monomind/cli/dist/src/utils/input-guards.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/utils/input-guards.js +39 -5
- package/packages/@monomind/cli/package.json +6 -2
|
@@ -7,6 +7,19 @@ import { confirm } from '../prompt.js';
|
|
|
7
7
|
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import * as path from 'path';
|
|
10
|
+
import { spawn } from 'child_process';
|
|
11
|
+
/** Check liveness of a pid via a zero-signal, matching the pattern used elsewhere (e.g. .claude/helpers/control-start.cjs). */
|
|
12
|
+
function isPidAlive(pid) {
|
|
13
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
14
|
+
return false;
|
|
15
|
+
try {
|
|
16
|
+
process.kill(pid, 0);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
10
23
|
// Default configuration
|
|
11
24
|
const DEFAULT_TOPOLOGY = 'hierarchical-mesh';
|
|
12
25
|
const DEFAULT_MAX_AGENTS = 15;
|
|
@@ -92,6 +105,60 @@ const startAction = async (ctx) => {
|
|
|
92
105
|
output.printInfo('Run "monomind init" first to initialize');
|
|
93
106
|
return { success: false, exitCode: 1 };
|
|
94
107
|
}
|
|
108
|
+
// Daemon mode, parent side: a Node process cannot truly self-daemonize —
|
|
109
|
+
// every handle (streams, intervals) would need to be unref'd, which leaves
|
|
110
|
+
// nothing keeping the event loop alive and the process exits almost
|
|
111
|
+
// immediately. Instead, spawn a genuine DETACHED CHILD process that
|
|
112
|
+
// re-invokes this same command with an internal marker flag; the parent's
|
|
113
|
+
// only job is to confirm the child came up and then exit. The child (see
|
|
114
|
+
// the `--foreground-worker-internal` branch below) is the one that actually
|
|
115
|
+
// stays running, with a real ref'd keep-alive.
|
|
116
|
+
const isDaemonChild = Boolean(ctx.flags['foreground-worker-internal']);
|
|
117
|
+
if (daemon && !isDaemonChild) {
|
|
118
|
+
output.writeln();
|
|
119
|
+
output.writeln(output.bold('Starting Monomind (daemon)'));
|
|
120
|
+
output.writeln();
|
|
121
|
+
const daemonPidPath = path.join(cwd, '.monomind', 'daemon.pid');
|
|
122
|
+
if (fs.existsSync(daemonPidPath)) {
|
|
123
|
+
const stalePid = Number(fs.readFileSync(daemonPidPath, 'utf-8').trim());
|
|
124
|
+
if (isPidAlive(stalePid)) {
|
|
125
|
+
output.printError(`Daemon already running (pid ${stalePid}). Run "monomind stop" first.`);
|
|
126
|
+
return { success: false, exitCode: 1 };
|
|
127
|
+
}
|
|
128
|
+
// Stale pid file — the prior daemon is dead, safe to remove.
|
|
129
|
+
fs.unlinkSync(daemonPidPath);
|
|
130
|
+
}
|
|
131
|
+
// Reconstruct the child's argv explicitly rather than reusing process.argv —
|
|
132
|
+
// startAction can be reached via `start`, `start quick`, or `restart`
|
|
133
|
+
// (which calls startAction directly in-process), so process.argv may not
|
|
134
|
+
// even contain the `start` subcommand. Building the args from ctx.flags
|
|
135
|
+
// guarantees the respawned child always dispatches to a valid daemon start.
|
|
136
|
+
const entry = process.argv[1];
|
|
137
|
+
const childArgs = ['start', '--daemon', '--foreground-worker-internal'];
|
|
138
|
+
if (topology)
|
|
139
|
+
childArgs.push('--topology', topology);
|
|
140
|
+
const child = spawn(process.execPath, [entry, ...childArgs], {
|
|
141
|
+
detached: true,
|
|
142
|
+
stdio: 'ignore',
|
|
143
|
+
cwd,
|
|
144
|
+
});
|
|
145
|
+
child.unref();
|
|
146
|
+
// Give the child a moment to actually start before trusting its pid.
|
|
147
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
148
|
+
if (!child.pid || !isPidAlive(child.pid)) {
|
|
149
|
+
output.printError('Daemon child process failed to start');
|
|
150
|
+
return { success: false, exitCode: 1 };
|
|
151
|
+
}
|
|
152
|
+
fs.mkdirSync(path.dirname(daemonPidPath), { recursive: true });
|
|
153
|
+
fs.writeFileSync(daemonPidPath, String(child.pid), { mode: 0o600 });
|
|
154
|
+
output.printSuccess(`Monomind daemon started (pid ${child.pid})`);
|
|
155
|
+
output.printInfo('Use "monomind stop" to stop it.');
|
|
156
|
+
const daemonStartResult = { daemon: true, pid: child.pid, startedAt: new Date().toISOString() };
|
|
157
|
+
if (ctx.flags.format === 'json') {
|
|
158
|
+
output.printJson(daemonStartResult);
|
|
159
|
+
}
|
|
160
|
+
return { success: true, data: daemonStartResult };
|
|
161
|
+
}
|
|
95
162
|
// Load configuration
|
|
96
163
|
const config = loadConfig(cwd);
|
|
97
164
|
const swarmConfig = config?.swarm || {};
|
|
@@ -147,48 +214,28 @@ const startAction = async (ctx) => {
|
|
|
147
214
|
`${output.highlight('monomind swarm status')} - View swarm details`,
|
|
148
215
|
`${output.highlight('monomind stop')} - Stop the system`
|
|
149
216
|
]);
|
|
150
|
-
// Daemon mode
|
|
151
|
-
|
|
217
|
+
// Daemon mode: this branch only runs inside the detached child spawned by
|
|
218
|
+
// the parent-side block above (isDaemonChild === true — the parent never
|
|
219
|
+
// reaches here, it already returned after confirming the child is alive).
|
|
220
|
+
// The pid file was already written by the parent with the CHILD's pid, so
|
|
221
|
+
// just confirm it's in place and start the real, ref'd keep-alive that
|
|
222
|
+
// actually holds the event loop open.
|
|
223
|
+
if (daemon && isDaemonChild) {
|
|
152
224
|
output.writeln();
|
|
153
225
|
output.printInfo('Running in daemon mode. Use "monomind stop" to stop.');
|
|
154
|
-
// Store PID for daemon management.
|
|
155
|
-
// wx flag refuses to follow a pre-staged symlink at the path; mode 0o600
|
|
156
|
-
// keeps the file from being world-readable on multi-user systems.
|
|
157
226
|
const daemonPidPath = path.join(cwd, '.monomind', 'daemon.pid');
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
catch (e) {
|
|
162
|
-
if (e.code === 'EEXIST') {
|
|
163
|
-
// Stale PID file — caller should have verified the prior daemon is dead
|
|
164
|
-
fs.unlinkSync(daemonPidPath);
|
|
165
|
-
fs.writeFileSync(daemonPidPath, String(process.pid), { flag: 'wx', mode: 0o600 });
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
throw e;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
// Detach from parent process for true daemon behavior
|
|
172
|
-
if (process.platform !== 'win32') {
|
|
173
|
-
// Unix-like systems: create new session
|
|
174
|
-
try {
|
|
175
|
-
process.stdin.unref?.();
|
|
176
|
-
process.stdout.unref?.();
|
|
177
|
-
process.stderr.unref?.();
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
// Ignore errors if streams can't be unref'd
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// Keep process alive in daemon mode
|
|
227
|
+
// Keep the process alive with a REAL (ref'd) interval — this is what
|
|
228
|
+
// actually keeps Node's event loop from exiting, since stdio was
|
|
229
|
+
// already set to 'ignore' by the parent's spawn() call.
|
|
184
230
|
const keepAlive = setInterval(() => {
|
|
185
|
-
// Heartbeat -
|
|
231
|
+
// Heartbeat - exit if our pid file has been removed (e.g. by `stop`)
|
|
186
232
|
if (!fs.existsSync(daemonPidPath)) {
|
|
187
233
|
clearInterval(keepAlive);
|
|
188
234
|
process.exit(0);
|
|
189
235
|
}
|
|
190
236
|
}, 5000);
|
|
191
|
-
|
|
237
|
+
// Deliberately NOT unref'd — this is the one handle that must keep the
|
|
238
|
+
// daemon child's event loop alive indefinitely.
|
|
192
239
|
}
|
|
193
240
|
const result = {
|
|
194
241
|
swarmId: swarmResult.swarmId,
|
|
@@ -267,16 +314,74 @@ const stopCommand = {
|
|
|
267
314
|
catch {
|
|
268
315
|
spinner.fail('Swarm was not running');
|
|
269
316
|
}
|
|
270
|
-
//
|
|
317
|
+
// Stop the daemon process itself: read its real pid, verify liveness,
|
|
318
|
+
// send a real termination signal, then wait and confirm it's actually
|
|
319
|
+
// dead before reporting success — rather than unconditionally deleting
|
|
320
|
+
// the pid file and claiming success regardless of reality.
|
|
271
321
|
const daemonPidPath = path.join(ctx.cwd, '.monomind', 'daemon.pid');
|
|
322
|
+
let daemonWasRunning = false;
|
|
323
|
+
let daemonStopped = true;
|
|
324
|
+
let daemonPid = null;
|
|
272
325
|
if (fs.existsSync(daemonPidPath)) {
|
|
273
|
-
fs.
|
|
326
|
+
const pidStr = fs.readFileSync(daemonPidPath, 'utf-8').trim();
|
|
327
|
+
const pid = Number(pidStr);
|
|
328
|
+
daemonPid = Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
329
|
+
if (daemonPid && isPidAlive(daemonPid)) {
|
|
330
|
+
daemonWasRunning = true;
|
|
331
|
+
spinner.setText(`Stopping daemon (pid ${daemonPid})...`);
|
|
332
|
+
spinner.start();
|
|
333
|
+
try {
|
|
334
|
+
process.kill(daemonPid, force ? 'SIGKILL' : 'SIGTERM');
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
// Process may have exited between the liveness check and the signal
|
|
338
|
+
}
|
|
339
|
+
// Wait and confirm the process actually exited before claiming success.
|
|
340
|
+
const waitMs = Math.min(timeout, 10) * 1000;
|
|
341
|
+
const deadline = Date.now() + waitMs;
|
|
342
|
+
while (Date.now() < deadline && isPidAlive(daemonPid)) {
|
|
343
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
344
|
+
}
|
|
345
|
+
if (isPidAlive(daemonPid) && force) {
|
|
346
|
+
try {
|
|
347
|
+
process.kill(daemonPid, 'SIGKILL');
|
|
348
|
+
}
|
|
349
|
+
catch { /* already gone */ }
|
|
350
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
351
|
+
}
|
|
352
|
+
daemonStopped = !isPidAlive(daemonPid);
|
|
353
|
+
if (daemonStopped) {
|
|
354
|
+
spinner.succeed(`Daemon stopped (pid ${daemonPid})`);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
spinner.fail(`Daemon (pid ${daemonPid}) did not stop within ${waitMs}ms`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Only clean up the pid file once we've confirmed the process is gone
|
|
361
|
+
// (or it was already stale) — never delete it while a live daemon
|
|
362
|
+
// still owns it.
|
|
363
|
+
if (daemonStopped) {
|
|
364
|
+
fs.unlinkSync(daemonPidPath);
|
|
365
|
+
}
|
|
274
366
|
}
|
|
275
367
|
output.writeln();
|
|
276
|
-
|
|
368
|
+
if (!daemonWasRunning) {
|
|
369
|
+
output.printInfo('No daemon was running.');
|
|
370
|
+
}
|
|
371
|
+
else if (daemonStopped) {
|
|
372
|
+
output.printSuccess(`MonoMind daemon stopped successfully (pid ${daemonPid})`);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
output.printError(`Failed to stop daemon (pid ${daemonPid}) — it did not exit in time`);
|
|
376
|
+
return {
|
|
377
|
+
success: false,
|
|
378
|
+
exitCode: 1,
|
|
379
|
+
data: { stopped: false, force, pid: daemonPid }
|
|
380
|
+
};
|
|
381
|
+
}
|
|
277
382
|
return {
|
|
278
383
|
success: true,
|
|
279
|
-
data: { stopped:
|
|
384
|
+
data: { stopped: daemonWasRunning ? daemonStopped : null, force, pid: daemonPid, stoppedAt: new Date().toISOString() }
|
|
280
385
|
};
|
|
281
386
|
}
|
|
282
387
|
catch (error) {
|
|
@@ -6,14 +6,32 @@ import { output } from '../output.js';
|
|
|
6
6
|
import { createRequire } from 'module';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { dirname, join } from 'path';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
9
10
|
function getTrackerPath() {
|
|
10
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
//
|
|
12
|
-
|
|
12
|
+
// The CLI package ships its own copy of token-tracker.cjs at the package
|
|
13
|
+
// root: from dist/src/commands/tokens.js, that's 3 levels up
|
|
14
|
+
// (dist/src/commands -> dist/src -> dist -> package root), then
|
|
15
|
+
// .claude/helpers/token-tracker.cjs. Verified empirically — do not "fix"
|
|
16
|
+
// this by guessing a different depth without re-checking the build output.
|
|
17
|
+
const bundled = join(__dirname, '..', '..', '..', '.claude', 'helpers', 'token-tracker.cjs');
|
|
18
|
+
if (existsSync(bundled))
|
|
19
|
+
return bundled;
|
|
20
|
+
// Fall back to a copy in the user's own project (e.g. a stripped install
|
|
21
|
+
// that dropped .claude/helpers, or a monorepo checkout like this one where
|
|
22
|
+
// the user's cwd has its own tracker copy).
|
|
23
|
+
const cwdCopy = join(process.cwd(), '.claude', 'helpers', 'token-tracker.cjs');
|
|
24
|
+
if (existsSync(cwdCopy))
|
|
25
|
+
return cwdCopy;
|
|
26
|
+
return null;
|
|
13
27
|
}
|
|
14
28
|
function loadTracker() {
|
|
29
|
+
const trackerPath = getTrackerPath();
|
|
30
|
+
if (!trackerPath) {
|
|
31
|
+
throw new Error('token-tracker.cjs not found (checked bundled package copy and cwd/.claude/helpers)');
|
|
32
|
+
}
|
|
15
33
|
const require = createRequire(import.meta.url);
|
|
16
|
-
return require(
|
|
34
|
+
return require(trackerPath);
|
|
17
35
|
}
|
|
18
36
|
const VALID_PERIODS = new Set(['today', 'week', '30days', 'month']);
|
|
19
37
|
function validatePeriod(raw) {
|
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
* Configuration Adapter
|
|
3
3
|
* Converts between SystemConfig and MonomindConfig types
|
|
4
4
|
*/
|
|
5
|
+
// Share fallback values with config-file-manager.ts's own DEFAULT_CONFIG so
|
|
6
|
+
// a config that round-trips through this adapter doesn't silently diverge
|
|
7
|
+
// from what `monomind config create`/`reset` would produce (e.g. maxAgents
|
|
8
|
+
// 8 vs a separately-hardcoded 15, hooks.enabled true vs false).
|
|
9
|
+
import { DEFAULT_CONFIG } from './services/config-file-manager.js';
|
|
10
|
+
// structuredClone so nested arrays/objects (e.g. hooks.hooks) returned by
|
|
11
|
+
// systemConfigToMonomindConfig() below are never the live DEFAULT_CONFIG
|
|
12
|
+
// reference — matches config-file-manager.ts's own cloneDefaultConfig() fix,
|
|
13
|
+
// which this file's direct DEFAULT_CONFIG import would otherwise bypass.
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
const AGENTS_DEFAULTS = structuredClone(DEFAULT_CONFIG.agents);
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
const SWARM_DEFAULTS = structuredClone(DEFAULT_CONFIG.swarm);
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
const CLI_DEFAULTS = structuredClone(DEFAULT_CONFIG.cli);
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
const HOOKS_DEFAULTS = structuredClone(DEFAULT_CONFIG.hooks);
|
|
5
22
|
/**
|
|
6
23
|
* Convert SystemConfig to MonomindConfig (CLI-specific format)
|
|
7
24
|
*/
|
|
@@ -11,19 +28,19 @@ export function systemConfigToMonomindConfig(systemConfig) {
|
|
|
11
28
|
projectRoot: systemConfig.orchestrator?.session?.dataDir || process.cwd(),
|
|
12
29
|
// Agent configuration
|
|
13
30
|
agents: {
|
|
14
|
-
defaultType:
|
|
15
|
-
autoSpawn:
|
|
16
|
-
maxConcurrent: systemConfig.orchestrator?.lifecycle?.maxConcurrentAgents ??
|
|
17
|
-
timeout: systemConfig.orchestrator?.lifecycle?.spawnTimeout ??
|
|
31
|
+
defaultType: systemConfig.agents?.defaultType ?? AGENTS_DEFAULTS.defaultType,
|
|
32
|
+
autoSpawn: systemConfig.agents?.autoSpawn ?? AGENTS_DEFAULTS.autoSpawn,
|
|
33
|
+
maxConcurrent: systemConfig.orchestrator?.lifecycle?.maxConcurrentAgents ?? AGENTS_DEFAULTS.maxConcurrent,
|
|
34
|
+
timeout: systemConfig.orchestrator?.lifecycle?.spawnTimeout ?? AGENTS_DEFAULTS.timeout,
|
|
18
35
|
providers: [],
|
|
19
36
|
},
|
|
20
37
|
// Swarm configuration
|
|
21
38
|
swarm: {
|
|
22
39
|
topology: normalizeTopology(systemConfig.swarm?.topology),
|
|
23
|
-
maxAgents: systemConfig.swarm?.maxAgents ??
|
|
24
|
-
autoScale: systemConfig.swarm?.autoScale?.enabled ??
|
|
40
|
+
maxAgents: systemConfig.swarm?.maxAgents ?? SWARM_DEFAULTS.maxAgents,
|
|
41
|
+
autoScale: systemConfig.swarm?.autoScale?.enabled ?? SWARM_DEFAULTS.autoScale,
|
|
25
42
|
coordinationStrategy: systemConfig.swarm?.coordination?.consensusRequired ? 'consensus' : 'leader',
|
|
26
|
-
healthCheckInterval: systemConfig.swarm?.coordination?.timeoutMs ??
|
|
43
|
+
healthCheckInterval: systemConfig.swarm?.coordination?.timeoutMs ?? SWARM_DEFAULTS.healthCheckInterval,
|
|
27
44
|
},
|
|
28
45
|
// Memory configuration
|
|
29
46
|
memory: {
|
|
@@ -41,19 +58,22 @@ export function systemConfigToMonomindConfig(systemConfig) {
|
|
|
41
58
|
transportType: systemConfig.mcp?.transport?.type || 'stdio',
|
|
42
59
|
tools: [], // Not in SystemConfig
|
|
43
60
|
},
|
|
44
|
-
// CLI preferences
|
|
61
|
+
// CLI preferences — read from SystemConfig.cli when present, falling
|
|
62
|
+
// back to the shared DEFAULT_CONFIG.cli values (not separately
|
|
63
|
+
// hardcoded numbers that could silently drop user-configured values).
|
|
45
64
|
cli: {
|
|
46
|
-
colorOutput:
|
|
47
|
-
interactive:
|
|
48
|
-
verbosity:
|
|
49
|
-
outputFormat:
|
|
50
|
-
progressStyle:
|
|
65
|
+
colorOutput: systemConfig.cli?.colorOutput ?? CLI_DEFAULTS.colorOutput,
|
|
66
|
+
interactive: systemConfig.cli?.interactive ?? CLI_DEFAULTS.interactive,
|
|
67
|
+
verbosity: systemConfig.cli?.verbosity ?? CLI_DEFAULTS.verbosity,
|
|
68
|
+
outputFormat: systemConfig.cli?.outputFormat ?? CLI_DEFAULTS.outputFormat,
|
|
69
|
+
progressStyle: systemConfig.cli?.progressStyle ?? CLI_DEFAULTS.progressStyle,
|
|
51
70
|
},
|
|
52
|
-
// Hooks configuration
|
|
71
|
+
// Hooks configuration — likewise read from SystemConfig.hooks when
|
|
72
|
+
// present, falling back to the shared DEFAULT_CONFIG.hooks values.
|
|
53
73
|
hooks: {
|
|
54
|
-
enabled:
|
|
55
|
-
autoExecute:
|
|
56
|
-
hooks:
|
|
74
|
+
enabled: systemConfig.hooks?.enabled ?? HOOKS_DEFAULTS.enabled,
|
|
75
|
+
autoExecute: systemConfig.hooks?.autoExecute ?? HOOKS_DEFAULTS.autoExecute,
|
|
76
|
+
hooks: systemConfig.hooks?.hooks ?? HOOKS_DEFAULTS.hooks,
|
|
57
77
|
},
|
|
58
78
|
// Neural pattern-learning configuration
|
|
59
79
|
neural: {
|
|
@@ -82,12 +82,16 @@ export class CLI {
|
|
|
82
82
|
this.output.printDebug(`Flags: ${JSON.stringify(Object.fromEntries(Object.entries(flags).filter(([k]) => k !== '_')))}`);
|
|
83
83
|
this.output.printDebug(`CWD: ${process.cwd()}`);
|
|
84
84
|
}
|
|
85
|
-
// Run startup update check (non-blocking, silent on skip)
|
|
86
|
-
|
|
85
|
+
// Run startup update check (non-blocking, silent on skip).
|
|
86
|
+
// `update` is now a properly-declared global boolean option (default
|
|
87
|
+
// true) — `--no-update` negates it via the parser's standard boolean
|
|
88
|
+
// negation path rather than relying on the generic --no-X fallback
|
|
89
|
+
// (which checked the flag name against the GLOBAL pool of every
|
|
90
|
+
// command's boolean options, so an unrelated command declaring its
|
|
91
|
+
// own `update` boolean flag could hijack `--no-update`'s meaning).
|
|
92
|
+
if (flags.update !== false && commandPath[0] !== 'update') {
|
|
87
93
|
this.checkForUpdatesOnStartup().catch(() => { });
|
|
88
94
|
}
|
|
89
|
-
// Initialize optional subsystems (non-blocking — never delay CLI startup)
|
|
90
|
-
this.initSubsystems().catch(() => { });
|
|
91
95
|
// Handle lazy-loaded commands that weren't recognized by the parser
|
|
92
96
|
// If commandPath is empty but positional has a command name, check if it's lazy-loadable
|
|
93
97
|
if (commandPath.length === 0 && positional.length > 0 && !positional[0].startsWith('-')) {
|
|
@@ -101,8 +105,11 @@ export class CLI {
|
|
|
101
105
|
// No command - show help or suggest correction
|
|
102
106
|
if (commandPath.length === 0 || flags.help || flags.h) {
|
|
103
107
|
if (commandPath.length > 0) {
|
|
104
|
-
// Show
|
|
105
|
-
|
|
108
|
+
// Show help for the fully-resolved (sub)command — walking the same
|
|
109
|
+
// path the dispatcher below resolves — not just the top-level
|
|
110
|
+
// parent's subcommand list. `monomind memory store --help` should
|
|
111
|
+
// show `store`'s own options, not memory's list of subcommands.
|
|
112
|
+
await this.showCommandHelp(commandPath);
|
|
106
113
|
}
|
|
107
114
|
else if (positional.length > 0 && !positional[0].startsWith('-')) {
|
|
108
115
|
// First positional looks like an attempted command - suggest correction
|
|
@@ -135,6 +142,13 @@ export class CLI {
|
|
|
135
142
|
this.output.writeln(this.output.dim(` ${message}`));
|
|
136
143
|
process.exit(1);
|
|
137
144
|
}
|
|
145
|
+
// Initialize optional subsystems (non-blocking — never delay CLI
|
|
146
|
+
// startup). Deliberately placed AFTER the --help/--version/unknown-
|
|
147
|
+
// command short-circuits above: those paths don't touch project state,
|
|
148
|
+
// so running this here means `monomind --help` (or any invocation in a
|
|
149
|
+
// directory that's never been a monomind project) no longer creates
|
|
150
|
+
// .monomind/registry.json as a side effect of just asking for help.
|
|
151
|
+
this.initSubsystems().catch(() => { });
|
|
138
152
|
// Handle subcommand (supports nested subcommands)
|
|
139
153
|
let targetCommand = command;
|
|
140
154
|
let subcommandArgs = positional;
|
|
@@ -208,8 +222,8 @@ export class CLI {
|
|
|
208
222
|
}
|
|
209
223
|
}
|
|
210
224
|
else {
|
|
211
|
-
// No action - show command
|
|
212
|
-
await this.showCommandHelp(commandName);
|
|
225
|
+
// No action - show help for the resolved (sub)command path
|
|
226
|
+
await this.showCommandHelp(commandPath.length > 0 ? commandPath : [commandName]);
|
|
213
227
|
}
|
|
214
228
|
}
|
|
215
229
|
catch (error) {
|
|
@@ -308,24 +322,40 @@ export class CLI {
|
|
|
308
322
|
/**
|
|
309
323
|
* Show command-specific help
|
|
310
324
|
*/
|
|
311
|
-
async showCommandHelp(
|
|
325
|
+
async showCommandHelp(commandPath) {
|
|
326
|
+
const path = Array.isArray(commandPath) ? commandPath : [commandPath];
|
|
327
|
+
const topName = path[0];
|
|
312
328
|
// Try sync first, then lazy load
|
|
313
|
-
let command = getCommand(
|
|
314
|
-
if (!command && hasCommand(
|
|
315
|
-
command = await getCommandAsync(
|
|
329
|
+
let command = getCommand(topName);
|
|
330
|
+
if (!command && hasCommand(topName)) {
|
|
331
|
+
command = await getCommandAsync(topName);
|
|
316
332
|
}
|
|
317
333
|
if (!command) {
|
|
318
|
-
this.output.printError(`Unknown command: ${
|
|
334
|
+
this.output.printError(`Unknown command: ${topName}`);
|
|
319
335
|
return;
|
|
320
336
|
}
|
|
337
|
+
// Walk the remaining path segments through subcommands/nested
|
|
338
|
+
// subcommands — mirrors the resolution the dispatcher uses in run() —
|
|
339
|
+
// so `monomind <command> <subcommand> --help` shows the actual target
|
|
340
|
+
// (sub)command's own options and examples, not just the parent's list
|
|
341
|
+
// of subcommands.
|
|
342
|
+
let target = command;
|
|
343
|
+
const resolvedNames = [command.name];
|
|
344
|
+
for (const segment of path.slice(1)) {
|
|
345
|
+
const next = target.subcommands?.find(sc => sc.name === segment || sc.aliases?.includes(segment));
|
|
346
|
+
if (!next)
|
|
347
|
+
break;
|
|
348
|
+
target = next;
|
|
349
|
+
resolvedNames.push(next.name);
|
|
350
|
+
}
|
|
321
351
|
this.output.writeln();
|
|
322
|
-
this.output.writeln(this.output.bold(`${this.name} ${
|
|
323
|
-
this.output.writeln(
|
|
352
|
+
this.output.writeln(this.output.bold(`${this.name} ${resolvedNames.join(' ')}`));
|
|
353
|
+
this.output.writeln(target.description);
|
|
324
354
|
this.output.writeln();
|
|
325
355
|
// Subcommands
|
|
326
|
-
if (
|
|
356
|
+
if (target.subcommands && target.subcommands.length > 0) {
|
|
327
357
|
this.output.writeln(this.output.bold('SUBCOMMANDS:'));
|
|
328
|
-
for (const sub of
|
|
358
|
+
for (const sub of target.subcommands) {
|
|
329
359
|
if (sub.hidden)
|
|
330
360
|
continue;
|
|
331
361
|
const name = sub.name.padEnd(15);
|
|
@@ -335,9 +365,9 @@ export class CLI {
|
|
|
335
365
|
this.output.writeln();
|
|
336
366
|
}
|
|
337
367
|
// Options
|
|
338
|
-
if (
|
|
368
|
+
if (target.options && target.options.length > 0) {
|
|
339
369
|
this.output.writeln(this.output.bold('OPTIONS:'));
|
|
340
|
-
for (const opt of
|
|
370
|
+
for (const opt of target.options) {
|
|
341
371
|
const flags = opt.short ? `-${opt.short}, --${opt.name}` : ` --${opt.name}`;
|
|
342
372
|
const required = opt.required ? this.output.error(' (required)') : '';
|
|
343
373
|
const defaultVal = opt.default !== undefined ? this.output.dim(` [default: ${opt.default}]`) : '';
|
|
@@ -346,9 +376,9 @@ export class CLI {
|
|
|
346
376
|
this.output.writeln();
|
|
347
377
|
}
|
|
348
378
|
// Examples
|
|
349
|
-
if (
|
|
379
|
+
if (target.examples && target.examples.length > 0) {
|
|
350
380
|
this.output.writeln(this.output.bold('EXAMPLES:'));
|
|
351
|
-
for (const example of
|
|
381
|
+
for (const example of target.examples) {
|
|
352
382
|
this.output.writeln(` ${this.output.dim('$')} ${example.command}`);
|
|
353
383
|
this.output.writeln(` ${this.output.dim(example.description)}`);
|
|
354
384
|
}
|
|
@@ -392,16 +422,22 @@ export class CLI {
|
|
|
392
422
|
* Load configuration file
|
|
393
423
|
*/
|
|
394
424
|
async loadConfig(configPath) {
|
|
425
|
+
const { configManager } = await import('./services/config-file-manager.js');
|
|
426
|
+
// An explicit --config/-c path names an EXACT file — load it directly
|
|
427
|
+
// instead of directory-searching from its dirname (which previously
|
|
428
|
+
// discarded the filename the user gave and either loaded an unrelated
|
|
429
|
+
// monomind.config.json from that directory or found nothing). Failure
|
|
430
|
+
// to find/parse an explicitly-named config file is a loud error, not a
|
|
431
|
+
// silent fallback to defaults.
|
|
432
|
+
if (configPath) {
|
|
433
|
+
const raw = configManager.loadExact(configPath);
|
|
434
|
+
return raw;
|
|
435
|
+
}
|
|
395
436
|
try {
|
|
396
|
-
const
|
|
397
|
-
const cwd = configPath
|
|
398
|
-
? (await import('path')).dirname(configPath)
|
|
399
|
-
: process.cwd();
|
|
400
|
-
const raw = configManager.load(cwd);
|
|
437
|
+
const raw = configManager.load(process.cwd());
|
|
401
438
|
if (!raw)
|
|
402
439
|
return undefined;
|
|
403
|
-
|
|
404
|
-
return config;
|
|
440
|
+
return raw;
|
|
405
441
|
}
|
|
406
442
|
catch (error) {
|
|
407
443
|
// Config loading is optional - don't fail if it doesn't exist
|
|
@@ -6,6 +6,32 @@
|
|
|
6
6
|
* Templates: minimal | standard | full | security | performance | solo
|
|
7
7
|
* All templates use bullet-format rules with imperative keywords for enforceability.
|
|
8
8
|
*/
|
|
9
|
+
import { createRequire } from 'module';
|
|
10
|
+
let _availabilityCache = null;
|
|
11
|
+
function detectOptionalPackages() {
|
|
12
|
+
if (_availabilityCache)
|
|
13
|
+
return _availabilityCache;
|
|
14
|
+
const req = createRequire(import.meta.url);
|
|
15
|
+
const resolvable = (pkg) => {
|
|
16
|
+
try {
|
|
17
|
+
req.resolve(pkg);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
_availabilityCache = {
|
|
25
|
+
hooks: resolvable('@monomind/hooks'),
|
|
26
|
+
mcp: resolvable('@monomind/mcp'),
|
|
27
|
+
routing: resolvable('@monomind/routing'),
|
|
28
|
+
monofence: resolvable('monofence-ai'),
|
|
29
|
+
};
|
|
30
|
+
return _availabilityCache;
|
|
31
|
+
}
|
|
32
|
+
function unavailNote(available) {
|
|
33
|
+
return available ? '' : ' _(unavailable in this install)_';
|
|
34
|
+
}
|
|
9
35
|
// --- Section Generators (each returns enforceable markdown) ---
|
|
10
36
|
function behavioralRules() {
|
|
11
37
|
return `## Behavioral Rules (Always Enforced)
|
|
@@ -156,20 +182,25 @@ function executionRules() {
|
|
|
156
182
|
- When agent results arrive, review ALL results before proceeding`;
|
|
157
183
|
}
|
|
158
184
|
function cliCommandsTable() {
|
|
159
|
-
|
|
185
|
+
const avail = detectOptionalPackages();
|
|
186
|
+
return `## CLI Commands
|
|
160
187
|
|
|
161
188
|
### Core Commands
|
|
162
189
|
|
|
163
190
|
| Command | Subcommands | Description |
|
|
164
191
|
|---------|-------------|-------------|
|
|
165
|
-
| \`init\` |
|
|
166
|
-
| \`agent\` |
|
|
192
|
+
| \`init\` | 5 | Project initialization |
|
|
193
|
+
| \`agent\` | 7 | Agent lifecycle management |
|
|
167
194
|
| \`swarm\` | 6 | Multi-agent swarm coordination |
|
|
168
|
-
| \`memory\` |
|
|
169
|
-
| \`task\` |
|
|
170
|
-
| \`session\` |
|
|
171
|
-
| \`hooks\` |
|
|
172
|
-
|
|
195
|
+
| \`memory\` | 12 | LanceDB memory with ANN search |
|
|
196
|
+
| \`task\` | 5 | Task creation and lifecycle |
|
|
197
|
+
| \`session\` | 6 | Session state management |
|
|
198
|
+
| \`hooks\` | 29 | Self-learning hooks + 15 background workers${unavailNote(avail.hooks)} |
|
|
199
|
+
|
|
200
|
+
> Note: there is no \`hive-mind\` or \`neural\` CLI command. Hive-mind
|
|
201
|
+
> consensus (byzantine/raft/quorum) is available exclusively via MCP tools
|
|
202
|
+
> (\`hive-mind_*\`), not the CLI. Neural pattern learning was merged into
|
|
203
|
+
> \`hooks intelligence\`.
|
|
173
204
|
|
|
174
205
|
### Quick CLI Examples
|
|
175
206
|
|
|
@@ -197,7 +228,8 @@ function agentTypes() {
|
|
|
197
228
|
\`pr-manager\`, \`code-review-swarm\`, \`issue-tracker\`, \`release-manager\``;
|
|
198
229
|
}
|
|
199
230
|
function hooksSystem() {
|
|
200
|
-
|
|
231
|
+
const avail = detectOptionalPackages();
|
|
232
|
+
return `## Hooks System (29 Hook Subcommands + 15 Background Workers)
|
|
201
233
|
|
|
202
234
|
### Essential Hooks
|
|
203
235
|
|
|
@@ -210,7 +242,7 @@ function hooksSystem() {
|
|
|
210
242
|
| \`intelligence\` | Pattern-learning intelligence system |
|
|
211
243
|
| \`worker\` | Background worker management |
|
|
212
244
|
|
|
213
|
-
### Background Workers (@monomind/hooks, run in-process)
|
|
245
|
+
### Background Workers (@monomind/hooks, run in-process)${unavailNote(avail.hooks)}
|
|
214
246
|
|
|
215
247
|
| Worker | Priority | Description |
|
|
216
248
|
|--------|----------|-------------|
|
|
@@ -220,9 +252,10 @@ function hooksSystem() {
|
|
|
220
252
|
| \`consolidate\` | low | Memory consolidation |
|
|
221
253
|
| \`ddd\` | low | DDD progress tracking |
|
|
222
254
|
| \`security\` | high | Secret/vulnerability scan |
|
|
255
|
+
| \`performance\`, \`health\`, \`swarm\`, \`git\`, \`learning\`, \`adr\`, \`patterns\`, \`cache\`, \`progress\` | various | See \`hooks worker list\` for the full 15 |
|
|
223
256
|
|
|
224
257
|
Metrics-producing workers refresh at session start when output is >6h old.
|
|
225
|
-
|
|
258
|
+
${avail.hooks ? '' : '\n> \\@monomind/hooks is not resolvable in this install — background workers will fail to load (see `hooks worker list`). This is an install/publish gap, not a project misconfiguration.\n'}
|
|
226
259
|
\`\`\`bash
|
|
227
260
|
npx monomind@latest hooks pre-task --description "[task]"
|
|
228
261
|
npx monomind@latest hooks post-task --task-id "[id]" --success true
|