monomind 2.2.0 → 2.3.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.
Files changed (90) hide show
  1. package/package.json +2 -2
  2. package/packages/@monomind/cli/.claude/commands/mastermind/createorg.md +24 -16
  3. package/packages/@monomind/cli/.claude/commands/mastermind/master.md +23 -7
  4. package/packages/@monomind/cli/.claude/commands/mastermind/runorg.md +3 -1
  5. package/packages/@monomind/cli/.claude/commands/mastermind/stoporg.md +6 -3
  6. package/packages/@monomind/cli/.claude/skills/mastermind-skills/code-quality-reviewer-prompt.md +3 -3
  7. package/packages/@monomind/cli/.claude/skills/mastermind-skills/createorg.md +19 -24
  8. package/packages/@monomind/cli/.claude/skills/mastermind-skills/debug.md +49 -4
  9. package/packages/@monomind/cli/.claude/skills/mastermind-skills/design.md +11 -10
  10. package/packages/@monomind/cli/.claude/skills/mastermind-skills/final-reviewer-prompt.md +144 -0
  11. package/packages/@monomind/cli/.claude/skills/mastermind-skills/implementer-prompt.md +9 -4
  12. package/packages/@monomind/cli/.claude/skills/mastermind-skills/org-settings.md +20 -2
  13. package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgs.md +40 -19
  14. package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgstatus.md +90 -41
  15. package/packages/@monomind/cli/.claude/skills/mastermind-skills/plan.md +19 -0
  16. package/packages/@monomind/cli/.claude/skills/mastermind-skills/skill-builder.md +171 -0
  17. package/packages/@monomind/cli/.claude/skills/mastermind-skills/spec-reviewer-prompt.md +19 -2
  18. package/packages/@monomind/cli/.claude/skills/mastermind-skills/stoporg.md +7 -5
  19. package/packages/@monomind/cli/.claude/skills/mastermind-skills/taskdev.md +83 -15
  20. package/packages/@monomind/cli/.claude/skills/mastermind-skills/tdd.md +30 -0
  21. package/packages/@monomind/cli/dist/src/autopilot-state.js +6 -4
  22. package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +4 -1
  23. package/packages/@monomind/cli/dist/src/capabilities/manager.js +3 -1
  24. package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.js +26 -0
  25. package/packages/@monomind/cli/dist/src/commands/agent-ops.js +28 -46
  26. package/packages/@monomind/cli/dist/src/commands/cleanup.d.ts +18 -0
  27. package/packages/@monomind/cli/dist/src/commands/cleanup.js +120 -0
  28. package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +13 -4
  29. package/packages/@monomind/cli/dist/src/commands/hooks-routing-commands.js +34 -18
  30. package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +8 -2
  31. package/packages/@monomind/cli/dist/src/commands/hooks.js +1 -1
  32. package/packages/@monomind/cli/dist/src/commands/mcp.js +4 -1
  33. package/packages/@monomind/cli/dist/src/commands/memory-admin.js +7 -1
  34. package/packages/@monomind/cli/dist/src/commands/org-observe.d.ts +18 -0
  35. package/packages/@monomind/cli/dist/src/commands/org-observe.js +295 -0
  36. package/packages/@monomind/cli/dist/src/commands/org.d.ts +9 -1
  37. package/packages/@monomind/cli/dist/src/commands/org.js +191 -9
  38. package/packages/@monomind/cli/dist/src/commands/performance.js +4 -1
  39. package/packages/@monomind/cli/dist/src/commands/platforms.js +4 -1
  40. package/packages/@monomind/cli/dist/src/commands/security-scan.js +8 -2
  41. package/packages/@monomind/cli/dist/src/commands/start.js +4 -1
  42. package/packages/@monomind/cli/dist/src/commands/swarm.js +3 -2
  43. package/packages/@monomind/cli/dist/src/index.js +13 -3
  44. package/packages/@monomind/cli/dist/src/init/executor.js +19 -5
  45. package/packages/@monomind/cli/dist/src/knowledge/document-pipeline.js +3 -2
  46. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +1 -0
  47. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +63 -20
  48. package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +3 -1
  49. package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +3 -2
  50. package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +3 -2
  51. package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +6 -2
  52. package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +35 -10
  53. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +24 -7
  54. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-embedding.js +13 -3
  55. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-intelligence.js +9 -3
  56. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.d.ts +1 -0
  57. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.js +112 -10
  58. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +2 -1
  59. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +8 -2
  60. package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +3 -2
  61. package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +15 -5
  62. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +4 -1
  63. package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +6 -4
  64. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.d.ts +20 -0
  65. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +38 -16
  66. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.d.ts +2 -0
  67. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +41 -24
  68. package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +6 -2
  69. package/packages/@monomind/cli/dist/src/memory/intelligence.js +32 -10
  70. package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +16 -4
  71. package/packages/@monomind/cli/dist/src/memory/memory-crud.js +26 -2
  72. package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +3 -2
  73. package/packages/@monomind/cli/dist/src/memory/memory-migrations.js +2 -0
  74. package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +4 -1
  75. package/packages/@monomind/cli/dist/src/orgrt/bus.js +4 -1
  76. package/packages/@monomind/cli/dist/src/orgrt/daemon.js +74 -19
  77. package/packages/@monomind/cli/dist/src/orgrt/inbox.js +3 -1
  78. package/packages/@monomind/cli/dist/src/orgrt/reporting.d.ts +40 -0
  79. package/packages/@monomind/cli/dist/src/orgrt/reporting.js +127 -0
  80. package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +2 -0
  81. package/packages/@monomind/cli/dist/src/orgrt/session.js +10 -2
  82. package/packages/@monomind/cli/dist/src/orgrt/templates.d.ts +16 -0
  83. package/packages/@monomind/cli/dist/src/orgrt/templates.js +57 -0
  84. package/packages/@monomind/cli/dist/src/services/config-file-manager.js +10 -1
  85. package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +6 -2
  86. package/packages/@monomind/cli/dist/src/ui/orgs.html +32 -0
  87. package/packages/@monomind/cli/dist/src/ui/server.mjs +25 -0
  88. package/packages/@monomind/cli/dist/src/update/executor.js +3 -1
  89. package/packages/@monomind/cli/dist/src/update/rate-limiter.js +3 -1
  90. package/packages/@monomind/cli/package.json +2 -2
@@ -4,13 +4,13 @@ import { join, resolve } from 'node:path';
4
4
  import { output } from '../output.js';
5
5
  import { OrgDaemon } from '../orgrt/daemon.js';
6
6
  import { startOrgServer } from '../orgrt/server.js';
7
- import { ORG_DIR } from '../orgrt/types.js';
7
+ import { ORG_DIR, OrgDefSchema } from '../orgrt/types.js';
8
8
  const log = (text) => { console.log(text); };
9
9
  /** Org names are used to build filesystem paths under .monomind/orgs — reject
10
10
  * anything that isn't a plain identifier to prevent path traversal (e.g.
11
11
  * `monomind org stop '../../../../tmp/x'`). */
12
12
  const ORG_NAME_RE = /^[a-z0-9][a-z0-9_-]*$/i;
13
- function validateOrgName(name) {
13
+ export function validateOrgName(name) {
14
14
  if (!name)
15
15
  return { ok: false, result: { success: false, message: 'org name required' } };
16
16
  if (!ORG_NAME_RE.test(name)) {
@@ -30,7 +30,7 @@ const ORG_ARTIFACT_SUFFIXES = [
30
30
  '-plugins', '-adapters', '-join-requests', '-bootstrap', '-project-workspaces',
31
31
  '-approval-comments', '-skills',
32
32
  ];
33
- function listOrgConfigFiles(orgsDir) {
33
+ export function listOrgConfigFiles(orgsDir) {
34
34
  return readdirSync(orgsDir)
35
35
  .filter(f => f.endsWith('.json') && !f.startsWith('._') && !ORG_ARTIFACT_SUFFIXES.some(suf => f.includes(suf)));
36
36
  }
@@ -53,6 +53,32 @@ const runAction = async (ctx) => {
53
53
  const taskFlag = ctx.flags['task'];
54
54
  if (Array.isArray(taskFlag))
55
55
  return { success: false, message: '--task was passed more than once — pass it exactly once' };
56
+ // Fail before any side effects (inbox server) when the org doesn't exist.
57
+ const orgsDir = join(ctx.cwd, ORG_DIR);
58
+ if (!existsSync(join(orgsDir, `${name}.json`))) {
59
+ const known = existsSync(orgsDir) ? listOrgConfigFiles(orgsDir).map(f => f.replace(/\.json$/, '')) : [];
60
+ log(output.error(`Org not found: ${name}${known.length ? ` — available: ${known.join(', ')}` : ' — create one with /mastermind:createorg'}`));
61
+ return { success: false, message: 'org not found' };
62
+ }
63
+ if (ctx.flags['dryRun'] === true) {
64
+ // Validate + preview each role's actual briefing without spawning sessions.
65
+ try {
66
+ const def = OrgDefSchema.parse(JSON.parse(readFileSync(join(orgsDir, `${name}.json`), 'utf8')));
67
+ const { buildRolePrompt } = await import('../orgrt/session.js');
68
+ const roster = def.roles.map(r => r.id);
69
+ const perRole = Math.floor((def.run_config.budget_tokens ?? 1_000_000) / def.roles.length);
70
+ log(output.info(`DRY RUN — org ${name}: ${def.roles.length} roles, ${perRole} tokens each, goal: ${taskFlag ?? def.goal}`));
71
+ for (const role of def.roles) {
72
+ log(output.info(`\n─── ${role.id} (${role.title || role.type})${role.adapter_config?.model ? ` [${role.adapter_config.model}]` : ''} ───`));
73
+ log(buildRolePrompt(role, { name: def.name, goal: taskFlag ?? def.goal }, roster));
74
+ }
75
+ return { success: true, message: 'dry run complete — no sessions started' };
76
+ }
77
+ catch (err) {
78
+ log(output.error(`Config invalid: ${err instanceof Error ? err.message : String(err)}`));
79
+ return { success: false, message: 'invalid org config' };
80
+ }
81
+ }
56
82
  const crossProcess = ctx.flags['crossProcess'] !== false;
57
83
  const daemon = new OrgDaemon(ctx.cwd, { crossProcess });
58
84
  let srv;
@@ -60,7 +86,20 @@ const runAction = async (ctx) => {
60
86
  srv = await startOrgServer(daemon, 0);
61
87
  daemon.setInboxUrl(`http://127.0.0.1:${srv.port}`);
62
88
  }
63
- const running = await daemon.startOrg(name, taskFlag);
89
+ let running;
90
+ try {
91
+ running = await daemon.startOrg(name, taskFlag);
92
+ }
93
+ catch (err) {
94
+ // Don't leave the inbox server holding the event loop open on a failed start.
95
+ srv?.close();
96
+ await daemon.stopAll().catch(() => { });
97
+ const detail = err instanceof Error ? err.message : String(err);
98
+ const hint = err instanceof Error && err.name === 'ZodError'
99
+ ? ` — run "monomind org validate ${name}" for details` : '';
100
+ log(output.error(`Could not start org ${name}: ${detail}${hint}`));
101
+ return { success: false, message: 'org start failed' };
102
+ }
64
103
  log(output.info(`org ${name} running (${running.def.roles.length} agents, run ${running.run}) — Ctrl-C or "monomind org stop ${name}" to stop`));
65
104
  // stopfile poll lets `org stop` work from another terminal;
66
105
  // clear any stale stopfile from a previous run before polling
@@ -79,18 +118,41 @@ const runAction = async (ctx) => {
79
118
  srv?.close();
80
119
  return { success: true, message: `org ${name} stopped` };
81
120
  };
121
+ /** True when runtime.json records a running org whose recorded pid is still alive. */
122
+ const isOrgRunning = (cwd, name) => {
123
+ try {
124
+ const rt = JSON.parse(readFileSync(join(cwd, ORG_DIR, name, 'runtime.json'), 'utf8'));
125
+ if (rt.status !== 'running' || !rt.pid)
126
+ return false;
127
+ process.kill(rt.pid, 0); // throws if the pid is gone (crashed daemon left a stale file)
128
+ return true;
129
+ }
130
+ catch {
131
+ return false;
132
+ }
133
+ };
82
134
  const stopAction = async (ctx) => {
83
135
  const validated = validateOrgName(ctx.args[0]);
84
136
  if (!validated.ok)
85
137
  return validated.result;
86
138
  const name = validated.name;
139
+ if (!existsSync(join(ctx.cwd, ORG_DIR, `${name}.json`))) {
140
+ log(output.error(`Org not found: ${name}`));
141
+ return { success: false, message: 'org not found' };
142
+ }
87
143
  const { writeFileSync, mkdirSync } = await import('node:fs');
88
144
  mkdirSync(join(ctx.cwd, ORG_DIR, name), { recursive: true });
89
145
  writeFileSync(join(ctx.cwd, ORG_DIR, name, 'stop'), new Date().toISOString());
90
146
  return { success: true, message: `stop requested for ${name} (daemon exits within 2s)` };
91
147
  };
92
148
  const statusAction = async (ctx) => {
93
- const name = ctx.args[0];
149
+ let name;
150
+ if (ctx.args[0]) {
151
+ const validated = validateOrgName(ctx.args[0]);
152
+ if (!validated.ok)
153
+ return validated.result;
154
+ name = validated.name;
155
+ }
94
156
  const orgDir = join(ctx.cwd, ORG_DIR);
95
157
  const targets = name ? [name] : (existsSync(orgDir)
96
158
  ? listOrgConfigFiles(orgDir).map(f => f.replace(/\.json$/, ''))
@@ -107,6 +169,17 @@ const statusAction = async (ctx) => {
107
169
  continue;
108
170
  }
109
171
  }
172
+ // A "running" record whose pid is gone means the daemon died without its
173
+ // stopOrg cleanup — surface that instead of reporting it as still running.
174
+ if (state.status === 'running' && state.pid) {
175
+ try {
176
+ process.kill(state.pid, 0);
177
+ }
178
+ catch {
179
+ log(output.warning(`${t}: crashed (runtime.json says running but pid ${state.pid} is gone)${state.run ? ` — run ${state.run}` : ''} — close it out with "monomind org mark-complete ${t}"`));
180
+ continue;
181
+ }
182
+ }
110
183
  log(output.info(`${t}: ${state.status}${state.run ? ` (run ${state.run}, pid ${state.pid})` : ''}`));
111
184
  }
112
185
  return { success: true };
@@ -198,8 +271,27 @@ const listAction = async (ctx) => {
198
271
  return { success: true };
199
272
  }
200
273
  log(output.info(`Found ${configs.length} org(s):`));
201
- for (const f of configs)
202
- log(output.info(` • ${f.replace('.json', '')}`));
274
+ for (const f of configs) {
275
+ const stem = f.replace(/\.json$/, '');
276
+ let detail = '';
277
+ try {
278
+ const def = JSON.parse(readFileSync(join(orgsDir, f), 'utf8'));
279
+ const roles = Array.isArray(def.roles) ? def.roles.length : 0;
280
+ const sched = def.schedule ? `every ${def.schedule}` : 'manual';
281
+ let status = 'never run';
282
+ try {
283
+ status = JSON.parse(readFileSync(join(orgsDir, stem, 'runtime.json'), 'utf8')).status ?? status;
284
+ }
285
+ catch { /* no runtime state yet */ }
286
+ const goal = typeof def.goal === 'string' && def.goal
287
+ ? ` — ${def.goal.length > 60 ? `${def.goal.slice(0, 57)}...` : def.goal}` : '';
288
+ detail = ` (${roles} role${roles === 1 ? '' : 's'}, ${sched}, ${status})${goal}`;
289
+ }
290
+ catch {
291
+ detail = ' (unreadable config — run `monomind org validate`)';
292
+ }
293
+ log(output.info(` • ${stem}${detail}`));
294
+ }
203
295
  return { success: true };
204
296
  };
205
297
  const deleteAction = async (ctx) => {
@@ -225,6 +317,10 @@ const deleteAction = async (ctx) => {
225
317
  log(output.error(`Org not found: ${orgName}`));
226
318
  return { success: false, message: 'org not found' };
227
319
  }
320
+ if (isOrgRunning(cwd, orgName) && ctx.flags['force'] !== true) {
321
+ log(output.error(`Org "${orgName}" is currently running — stop it first (monomind org stop ${orgName}) or pass --force.`));
322
+ return { success: false, message: 'org is running' };
323
+ }
228
324
  let removed = 0;
229
325
  for (const suf of ['', ...ORG_ARTIFACT_SUFFIXES]) {
230
326
  for (const ext of ['.json', '.jsonl']) {
@@ -298,6 +394,7 @@ export const orgCommand = {
298
394
  options: [
299
395
  { name: 'task', description: 'Override the org goal for this run', type: 'string' },
300
396
  { name: 'cross-process', description: 'Discover and message orgs hosted by other monomind processes on this machine (default true)', type: 'boolean', default: true },
397
+ { name: 'dry-run', description: 'Validate and print each role\'s briefing without starting any agent sessions', type: 'boolean' },
301
398
  ],
302
399
  examples: [{ command: 'monomind org run growth --task "weekly report"', description: 'Run the growth org once with a task' }],
303
400
  action: runAction,
@@ -316,10 +413,95 @@ export const orgCommand = {
316
413
  options: [{ name: 'times', short: 'n', description: 'Iterations', type: 'number', default: 5 }],
317
414
  action: testLoopAction,
318
415
  },
416
+ {
417
+ name: 'logs', description: 'Show (or follow) the formatted event log of an org run',
418
+ options: [
419
+ { name: 'run', description: 'Run id (default: latest)', type: 'string' },
420
+ { name: 'role', description: 'Only events from/to this role', type: 'string' },
421
+ { name: 'follow', short: 'f', description: 'Keep tailing until Ctrl-C', type: 'boolean' },
422
+ ],
423
+ examples: [{ command: 'monomind org logs growth --follow', description: 'Live-tail the latest run' }],
424
+ action: async (ctx) => {
425
+ const v = validateOrgName(ctx.args[0]);
426
+ if (!v.ok)
427
+ return v.result;
428
+ const { logsAction } = await import('./org-observe.js');
429
+ return logsAction(ctx, v.name);
430
+ },
431
+ },
432
+ {
433
+ name: 'report', description: 'Summarize an org run: outcome, per-role activity, tokens, assets, crashes',
434
+ options: [
435
+ { name: 'run', description: 'Run id (default: latest)', type: 'string' },
436
+ { name: 'all', description: 'List all recorded runs from history', type: 'boolean' },
437
+ ],
438
+ examples: [{ command: 'monomind org report growth', description: 'Report on the latest run' }],
439
+ action: async (ctx) => {
440
+ const v = validateOrgName(ctx.args[0]);
441
+ if (!v.ok)
442
+ return v.result;
443
+ const { reportAction } = await import('./org-observe.js');
444
+ return reportAction(ctx, v.name);
445
+ },
446
+ },
447
+ {
448
+ name: 'questions', description: 'List pending ask_human questions from an org\'s agents',
449
+ options: [{ name: 'all', description: 'Include answered questions', type: 'boolean' }],
450
+ examples: [{ command: 'monomind org questions growth', description: 'Show unanswered questions' }],
451
+ action: async (ctx) => {
452
+ const v = validateOrgName(ctx.args[0]);
453
+ if (!v.ok)
454
+ return v.result;
455
+ const { questionsAction } = await import('./org-observe.js');
456
+ return questionsAction(ctx, v.name);
457
+ },
458
+ },
459
+ {
460
+ name: 'answer', description: 'Answer a pending ask_human question (live if the org is running, queued otherwise)',
461
+ examples: [{ command: 'monomind org answer growth q-123-ab "yes, ship it"', description: 'Answer question q-123-ab' }],
462
+ action: async (ctx) => {
463
+ const v = validateOrgName(ctx.args[0]);
464
+ if (!v.ok)
465
+ return v.result;
466
+ const { answerAction } = await import('./org-observe.js');
467
+ return answerAction(ctx, v.name);
468
+ },
469
+ },
470
+ {
471
+ name: 'create', description: 'Scaffold an org config from a starter template',
472
+ options: [
473
+ { name: 'template', description: 'content-team | dev-team | research-pod', type: 'string' },
474
+ { name: 'goal', description: 'Org goal (defaults to the template\'s placeholder)', type: 'string' },
475
+ { name: 'schedule', description: 'Daemon schedule, e.g. 30m or 2h', type: 'string' },
476
+ { name: 'force', description: 'Overwrite an existing org config', type: 'boolean' },
477
+ ],
478
+ examples: [{ command: 'monomind org create blog --template content-team --goal "3 posts/week"', description: 'Create a content org' }],
479
+ action: async (ctx) => {
480
+ const v = validateOrgName(ctx.args[0]);
481
+ if (!v.ok)
482
+ return v.result;
483
+ const { createAction } = await import('./org-observe.js');
484
+ return createAction(ctx, v.name);
485
+ },
486
+ },
487
+ {
488
+ name: 'validate', description: 'Validate org config(s) against the runtime schema and structural invariants',
489
+ examples: [
490
+ { command: 'monomind org validate growth', description: 'Validate one org config' },
491
+ { command: 'monomind org validate', description: 'Validate every org config in the project' },
492
+ ],
493
+ action: async (ctx) => {
494
+ const { validateAction } = await import('./org-observe.js');
495
+ return validateAction(ctx);
496
+ },
497
+ },
319
498
  { name: 'list', description: 'List all orgs in the current project', action: listAction },
320
499
  {
321
500
  name: 'delete', description: 'Delete an org and all its data',
322
- options: [{ name: 'yes', short: 'y', description: 'Skip confirmation', type: 'boolean' }],
501
+ options: [
502
+ { name: 'yes', short: 'y', description: 'Skip confirmation', type: 'boolean' },
503
+ { name: 'force', description: 'Delete even if the org appears to be running', type: 'boolean' },
504
+ ],
323
505
  action: deleteAction,
324
506
  },
325
507
  { name: 'mark-complete', description: 'Manually close a stale/crashed run', action: markCompleteAction },
@@ -329,7 +511,7 @@ export const orgCommand = {
329
511
  // index.ts's dispatcher never prints result.message on a failed action —
330
512
  // it only exits with result.exitCode — so this must log itself or bare
331
513
  // `monomind org` exits silently with code 1 and zero output.
332
- const message = 'usage: monomind org <run|stop|status|serve|test-loop|list|delete|mark-complete>';
514
+ const message = 'usage: monomind org <run|stop|status|serve|test-loop|logs|report|questions|answer|create|validate|list|delete|mark-complete>';
333
515
  log(output.error(message));
334
516
  return { success: false, message };
335
517
  },
@@ -535,7 +535,10 @@ const bottleneckCommand = {
535
535
  try {
536
536
  config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
537
537
  }
538
- catch { /* no config */ }
538
+ catch (e) {
539
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
540
+ console.error('[performance-bottleneck] failed to parse monomind.config.json:', e);
541
+ }
539
542
  const perf = (config.performance ?? {});
540
543
  const hnsw = (perf.hnsw ?? {});
541
544
  if (!hnsw.quantization) {
@@ -224,7 +224,10 @@ function setupCursor(activateScriptPath, repoPath) {
224
224
  try {
225
225
  settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
226
226
  }
227
- catch { /* keep empty */ }
227
+ catch (e) {
228
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
229
+ console.error('[setupCursor] failed to parse existing .cursor/settings.json, overwriting with empty:', e);
230
+ }
228
231
  }
229
232
  const hooks = settings['hooks'] ?? {};
230
233
  const sessionStart = hooks['SessionStart'] ?? [];
@@ -141,10 +141,16 @@ export const scanCommand = {
141
141
  }
142
142
  }
143
143
  }
144
- catch { /* JSON parse failed */ }
144
+ catch (e) { /* JSON parse failed */
145
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
146
+ console.error('[security-scan] failed to parse npm audit output:', e);
147
+ }
145
148
  }
146
149
  }
147
- catch { /* npm audit failed */ }
150
+ catch (e) { /* npm audit failed */
151
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
152
+ console.error('[security-scan] dependency check failed:', e);
153
+ }
148
154
  }
149
155
  if (scanType === 'all' || scanType === 'code') {
150
156
  spinner.setText('Scanning for hardcoded secrets...');
@@ -90,7 +90,10 @@ function loadConfig(cwd) {
90
90
  const content = fs.readFileSync(configPath, 'utf-8');
91
91
  return parseSimpleYaml(content);
92
92
  }
93
- catch {
93
+ catch (e) {
94
+ // Caller falls back to hardcoded defaults (topology/maxAgents) as if no config existed
95
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
96
+ console.error('[loadConfig] failed to read/parse .monomind/config.yaml, falling back to defaults:', e);
94
97
  return null;
95
98
  }
96
99
  }
@@ -339,8 +339,9 @@ const initCommand = {
339
339
  };
340
340
  writeJsonFileAtomic(stateFile, store);
341
341
  }
342
- catch {
343
- // Ignore errors writing state file
342
+ catch (e) {
343
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
344
+ console.error('[swarm-init] failed to persist swarm state file:', e);
344
345
  }
345
346
  if (ctx.flags.format === 'json') {
346
347
  output.printJson(result);
@@ -25,7 +25,9 @@ function getPackageVersion() {
25
25
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
26
26
  return pkg.version || '3.0.0';
27
27
  }
28
- catch {
28
+ catch (e) {
29
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
30
+ console.error('[index] getPackageVersion failed, using fallback:', e);
29
31
  return '3.0.0';
30
32
  }
31
33
  }
@@ -472,7 +474,11 @@ export class CLI {
472
474
  });
473
475
  void _swarmCheckpointer;
474
476
  }
475
- catch { /* optional — monomind/memory may not be installed */ }
477
+ catch (e) {
478
+ // optional — monomind/memory may not be installed
479
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
480
+ console.error('[index] SwarmCheckpointer init failed:', e);
481
+ }
476
482
  // Task 30: Build unified agent registry — extras (canonical) first, dev copies second.
477
483
  // Deduplication is slug-based; agency-agents wins on conflict.
478
484
  // Extra paths are read from MONOMIND_EXTRA_AGENT_PATHS env var (colon-separated)
@@ -486,7 +492,11 @@ export class CLI {
486
492
  mkdirSync(outDir, { recursive: true });
487
493
  buildUnifiedRegistry(roots, join(outDir, 'registry.json'));
488
494
  }
489
- catch { /* optional — registry build failures must never block startup */ }
495
+ catch (e) {
496
+ // optional — registry build failures must never block startup
497
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
498
+ console.error('[index] agent registry build failed:', e);
499
+ }
490
500
  // Task 04: CapabilityMetadata validation moved to `monomind doctor -c registry`
491
501
  // (see doctor-project-checks.ts:checkAgentRegistry). Printing this from a
492
502
  // fire-and-forget startup task raced process exit — short-lived commands
@@ -926,8 +926,10 @@ async function writeSettings(targetDir, options, result) {
926
926
  result.skipped.push('.claude/settings.json');
927
927
  }
928
928
  }
929
- catch {
929
+ catch (e) {
930
930
  // Existing file is corrupt — overwrite
931
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
932
+ console.error('[writeSettings] existing settings.json unparseable, overwriting with generated defaults:', e);
931
933
  atomicWriteFile(settingsPath, JSON.stringify(generated, null, 2));
932
934
  result.created.files.push('.claude/settings.json');
933
935
  }
@@ -2000,8 +2002,10 @@ async function writeClaudeMd(targetDir, options, result) {
2000
2002
  result.created.files.push('~/.claude/CLAUDE.md');
2001
2003
  }
2002
2004
  }
2003
- catch {
2005
+ catch (e) {
2004
2006
  // Non-critical — global CLAUDE.md is best-effort
2007
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
2008
+ console.error('[writeClaudeMd] failed to write/append ~/.claude/CLAUDE.md:', e);
2005
2009
  }
2006
2010
  // Also inject the token-display hook into ~/.claude/settings.json
2007
2011
  const globalSettingsPath = path.join(globalClaudeDir, 'settings.json');
@@ -2014,7 +2018,11 @@ async function writeClaudeMd(targetDir, options, result) {
2014
2018
  try {
2015
2019
  globalSettings = JSON.parse(fs.readFileSync(globalSettingsPath, 'utf-8'));
2016
2020
  }
2017
- catch { /* malformed JSON — start fresh */ }
2021
+ catch (e) {
2022
+ // malformed JSON — start fresh
2023
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
2024
+ console.error('[writeClaudeMd] ~/.claude/settings.json unparseable, starting fresh:', e);
2025
+ }
2018
2026
  }
2019
2027
  // Inject SessionStart token hook if not already present
2020
2028
  const hooks = globalSettings.hooks ?? {};
@@ -2031,8 +2039,10 @@ async function writeClaudeMd(targetDir, options, result) {
2031
2039
  result.created.files.push('~/.claude/settings.json (added token hook)');
2032
2040
  }
2033
2041
  }
2034
- catch {
2042
+ catch (e) {
2035
2043
  // Non-critical — global settings hook is best-effort
2044
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
2045
+ console.error('[writeClaudeMd] failed to inject token hook into ~/.claude/settings.json:', e);
2036
2046
  }
2037
2047
  }
2038
2048
  }
@@ -2163,7 +2173,11 @@ function _registerMonomindProject(dir) {
2163
2173
  reg = JSON.parse(fs.readFileSync(registryPath, 'utf-8'));
2164
2174
  }
2165
2175
  }
2166
- catch { }
2176
+ catch (e) {
2177
+ // Unparseable registry — proceeding with an empty list will overwrite it below, losing previously registered projects
2178
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
2179
+ console.error('[_registerMonomindProject] ~/.monomind-projects.json unparseable, resetting:', e);
2180
+ }
2167
2181
  if (!Array.isArray(reg.projects))
2168
2182
  reg.projects = [];
2169
2183
  const abs = path.resolve(dir);
@@ -159,8 +159,9 @@ export async function ingestDocument(filePath, scope = 'shared', rootDir = proce
159
159
  if (storeResult?.success)
160
160
  indexed++;
161
161
  }
162
- catch {
163
- // chunk not stored — don't count as indexed
162
+ catch (e) {
163
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
164
+ console.error(`[ingestDocument] failed to store chunk ${chunk.chunkIndex} of ${resolved}:`, e);
164
165
  }
165
166
  }
166
167
  }
@@ -24,6 +24,7 @@ interface AgentStore {
24
24
  version: string;
25
25
  }
26
26
  export declare function loadAgentStore(): AgentStore;
27
+ export declare function loadAgentStoreOrNull(): AgentStore | null;
27
28
  export declare const agentTools: MCPTool[];
28
29
  export {};
29
30
  //# sourceMappingURL=agent-tools.d.ts.map
@@ -30,21 +30,35 @@ const MAX_AGENT_STORE_BYTES = 50 * 1024 * 1024;
30
30
  // (e.g. hive-mind-tools.ts) can reuse this hardened loader instead of
31
31
  // maintaining their own weaker copy (missing the size cap / __proto__ guard).
32
32
  export function loadAgentStore() {
33
+ return loadAgentStoreOrNull() ?? { agents: {}, version: '3.0.0' };
34
+ }
35
+ // Like loadAgentStore(), but distinguishes "file doesn't exist yet" (returns
36
+ // the empty default store — safe to build on and save) from "file exists but
37
+ // failed to read/parse, or is corrupt/oversized" (returns null). Handlers
38
+ // that are about to mutate-and-save MUST use this instead of loadAgentStore()
39
+ // and bail out on null, or a transient read failure silently wipes every
40
+ // previously-spawned agent on the next save (the exact bug found and fixed
41
+ // in task-tools.ts's task_assign earlier this session — same pattern here).
42
+ // Exported for other tool modules with their own mutate-and-save paths
43
+ // against the same agent store file — see hive-mind-tools.ts.
44
+ export function loadAgentStoreOrNull() {
33
45
  try {
34
46
  const path = getAgentPath();
35
47
  migrateLegacyStoreFile(path, join(AGENT_DIR, AGENT_FILE));
36
48
  if (existsSync(path)) {
37
49
  if (statSync(path).size > MAX_AGENT_STORE_BYTES)
38
- return { agents: {}, version: '3.0.0' };
50
+ return null;
39
51
  const data = readFileSync(path, 'utf-8');
40
52
  const parsed = JSON.parse(data);
41
53
  if (parsed && typeof parsed === 'object' && Object.prototype.hasOwnProperty.call(parsed, '__proto__'))
42
- return { agents: {}, version: '3.0.0' };
54
+ return null;
43
55
  return parsed;
44
56
  }
45
57
  }
46
- catch {
47
- // Return empty store on error
58
+ catch (e) {
59
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
60
+ console.error('[loadAgentStore] failed to read/parse agent store — refusing to proceed to avoid overwriting it with an empty one:', e);
61
+ return null;
48
62
  }
49
63
  return { agents: {}, version: '3.0.0' };
50
64
  }
@@ -126,7 +140,10 @@ export const agentTools = [
126
140
  required: ['agentType'],
127
141
  },
128
142
  handler: async (input) => {
129
- const store = loadAgentStore();
143
+ const store = loadAgentStoreOrNull();
144
+ if (!store) {
145
+ return { success: false, error: 'Agent store is unreadable/corrupt — refusing to spawn to avoid overwriting real agent data. Retry, or check the store file if this persists.' };
146
+ }
130
147
  // Cap agentId: used as the JSON object key in store.agents[agentId].
131
148
  // An oversized key inflates the on-disk store for every spawned agent.
132
149
  // Cap agentType/domain: persisted as AgentRecord field values.
@@ -215,7 +232,10 @@ export const agentTools = [
215
232
  if (!agentId || typeof agentId !== 'string' || ['__proto__', 'constructor', 'prototype'].includes(agentId)) {
216
233
  return { success: false, agentId, error: 'Invalid agent ID' };
217
234
  }
218
- const store = loadAgentStore();
235
+ const store = loadAgentStoreOrNull();
236
+ if (!store) {
237
+ return { success: false, agentId, error: 'Agent store is unreadable/corrupt — refusing to terminate to avoid overwriting real agent data.' };
238
+ }
219
239
  if (Object.hasOwn(store.agents, agentId)) {
220
240
  store.agents[agentId].status = 'terminated';
221
241
  saveAgentStore(store);
@@ -277,25 +297,35 @@ export const agentTools = [
277
297
  inputSchema: {
278
298
  type: 'object',
279
299
  properties: {
280
- status: { type: 'string', description: 'Filter by status' },
300
+ status: { type: 'string', description: 'Filter by status (pass "all" or omit to include every status)' },
281
301
  domain: { type: 'string', description: 'Filter by domain' },
302
+ agentType: { type: 'string', description: 'Filter by agent type' },
282
303
  includeTerminated: { type: 'boolean', description: 'Include terminated agents' },
283
304
  },
284
305
  },
285
306
  handler: async (input) => {
286
307
  const store = loadAgentStore();
287
308
  let agents = Object.values(store.agents);
288
- // Filter by status
289
- if (input.status) {
309
+ // Filter by status. 'all' (the CLI's `--all` flag sends this literal
310
+ // string) means "no status filter" — it used to be treated as a real
311
+ // status value to match against, which no agent ever has, so `--all`
312
+ // silently returned zero agents instead of every agent.
313
+ if (input.status && input.status !== 'all') {
290
314
  agents = agents.filter(a => a.status === input.status);
291
315
  }
292
- else if (!input.includeTerminated) {
316
+ else if (input.status !== 'all' && !input.includeTerminated) {
293
317
  agents = agents.filter(a => a.status !== 'terminated');
294
318
  }
295
319
  // Filter by domain
296
320
  if (input.domain) {
297
321
  agents = agents.filter(a => a.domain === input.domain);
298
322
  }
323
+ // Filter by agent type — the CLI's `--type` flag has sent this since
324
+ // it was added, but this handler never read it, so `--type` was a
325
+ // silent no-op that returned every agent regardless of the filter.
326
+ if (input.agentType) {
327
+ agents = agents.filter(a => a.agentType === input.agentType);
328
+ }
299
329
  return {
300
330
  agents: agents.map(a => ({
301
331
  agentId: a.agentId,
@@ -310,6 +340,7 @@ export const agentTools = [
310
340
  filters: {
311
341
  status: input.status,
312
342
  domain: input.domain,
343
+ agentType: input.agentType,
313
344
  includeTerminated: input.includeTerminated,
314
345
  },
315
346
  };
@@ -329,9 +360,15 @@ export const agentTools = [
329
360
  required: ['action'],
330
361
  },
331
362
  handler: async (input) => {
332
- const store = loadAgentStore();
333
- const agents = Object.values(store.agents).filter(a => a.status !== 'terminated');
334
363
  const action = input.action || 'status'; // Default to status
364
+ const store = loadAgentStoreOrNull();
365
+ if (!store) {
366
+ // 'scale'/'drain' would otherwise build on an empty store and save it,
367
+ // wiping every real agent; 'status' reporting all-zeros on a corrupt
368
+ // store would also be misleading, so all three branches bail here.
369
+ return { action, error: 'Agent store is unreadable/corrupt — refusing to proceed to avoid overwriting real agent data.' };
370
+ }
371
+ const agents = Object.values(store.agents).filter(a => a.status !== 'terminated');
335
372
  if (action === 'status') {
336
373
  const byType = {};
337
374
  const byStatus = {};
@@ -404,13 +441,16 @@ export const agentTools = [
404
441
  }
405
442
  if (action === 'drain') {
406
443
  const agentType = input.agentType;
444
+ // Scope "remaining" to the same population drain operated over — when
445
+ // filtered by agentType, agents.length (all non-terminated agents,
446
+ // any type) minus drained (only that type's count) mixed two
447
+ // different populations and produced a meaningless total.
448
+ const scoped = agentType ? agents.filter(a => a.agentType === agentType) : agents;
407
449
  let drained = 0;
408
- for (const agent of agents) {
409
- if (!agentType || agent.agentType === agentType) {
410
- if (agent.status === 'idle') {
411
- store.agents[agent.agentId].status = 'terminated';
412
- drained++;
413
- }
450
+ for (const agent of scoped) {
451
+ if (agent.status === 'idle') {
452
+ store.agents[agent.agentId].status = 'terminated';
453
+ drained++;
414
454
  }
415
455
  }
416
456
  saveAgentStore(store);
@@ -418,7 +458,7 @@ export const agentTools = [
418
458
  action,
419
459
  agentType: agentType || 'all',
420
460
  drained,
421
- remaining: agents.length - drained,
461
+ remaining: scoped.length - drained,
422
462
  };
423
463
  }
424
464
  return { action, error: 'Unknown action' };
@@ -521,7 +561,10 @@ export const agentTools = [
521
561
  if (!agentId || typeof agentId !== 'string' || ['__proto__', 'constructor', 'prototype'].includes(agentId)) {
522
562
  return { success: false, agentId, error: 'Invalid agent ID' };
523
563
  }
524
- const store = loadAgentStore();
564
+ const store = loadAgentStoreOrNull();
565
+ if (!store) {
566
+ return { success: false, agentId, error: 'Agent store is unreadable/corrupt — refusing to update to avoid overwriting real agent data.' };
567
+ }
525
568
  const agent = Object.hasOwn(store.agents, agentId) ? store.agents[agentId] : undefined;
526
569
  if (agent) {
527
570
  if (input.status)