monomind 2.1.9 → 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 (93) 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/init-wizard.js +2 -2
  33. package/packages/@monomind/cli/dist/src/commands/init.js +54 -10
  34. package/packages/@monomind/cli/dist/src/commands/mcp.js +4 -1
  35. package/packages/@monomind/cli/dist/src/commands/memory-admin.js +7 -1
  36. package/packages/@monomind/cli/dist/src/commands/org-observe.d.ts +18 -0
  37. package/packages/@monomind/cli/dist/src/commands/org-observe.js +295 -0
  38. package/packages/@monomind/cli/dist/src/commands/org.d.ts +9 -1
  39. package/packages/@monomind/cli/dist/src/commands/org.js +191 -9
  40. package/packages/@monomind/cli/dist/src/commands/performance.js +4 -1
  41. package/packages/@monomind/cli/dist/src/commands/platforms.js +4 -1
  42. package/packages/@monomind/cli/dist/src/commands/security-scan.js +8 -2
  43. package/packages/@monomind/cli/dist/src/commands/start.js +4 -1
  44. package/packages/@monomind/cli/dist/src/commands/swarm.js +5 -4
  45. package/packages/@monomind/cli/dist/src/index.js +13 -3
  46. package/packages/@monomind/cli/dist/src/init/executor.js +19 -5
  47. package/packages/@monomind/cli/dist/src/init/settings-generator.js +8 -1
  48. package/packages/@monomind/cli/dist/src/knowledge/document-pipeline.js +3 -2
  49. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +1 -0
  50. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +63 -20
  51. package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +3 -1
  52. package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +3 -2
  53. package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +3 -2
  54. package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +6 -2
  55. package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +35 -10
  56. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +24 -7
  57. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-embedding.js +13 -3
  58. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-intelligence.js +9 -3
  59. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.d.ts +1 -0
  60. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.js +112 -10
  61. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +2 -1
  62. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +8 -2
  63. package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +3 -2
  64. package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +15 -5
  65. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +4 -1
  66. package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +6 -4
  67. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.d.ts +20 -0
  68. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +38 -16
  69. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.d.ts +2 -0
  70. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +68 -24
  71. package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +6 -2
  72. package/packages/@monomind/cli/dist/src/memory/intelligence.js +32 -10
  73. package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +16 -4
  74. package/packages/@monomind/cli/dist/src/memory/memory-crud.js +26 -2
  75. package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +3 -2
  76. package/packages/@monomind/cli/dist/src/memory/memory-migrations.js +2 -0
  77. package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +4 -1
  78. package/packages/@monomind/cli/dist/src/orgrt/bus.js +4 -1
  79. package/packages/@monomind/cli/dist/src/orgrt/daemon.js +74 -19
  80. package/packages/@monomind/cli/dist/src/orgrt/inbox.js +3 -1
  81. package/packages/@monomind/cli/dist/src/orgrt/reporting.d.ts +40 -0
  82. package/packages/@monomind/cli/dist/src/orgrt/reporting.js +127 -0
  83. package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +2 -0
  84. package/packages/@monomind/cli/dist/src/orgrt/session.js +10 -2
  85. package/packages/@monomind/cli/dist/src/orgrt/templates.d.ts +16 -0
  86. package/packages/@monomind/cli/dist/src/orgrt/templates.js +57 -0
  87. package/packages/@monomind/cli/dist/src/services/config-file-manager.js +10 -1
  88. package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +6 -2
  89. package/packages/@monomind/cli/dist/src/ui/orgs.html +32 -0
  90. package/packages/@monomind/cli/dist/src/ui/server.mjs +25 -0
  91. package/packages/@monomind/cli/dist/src/update/executor.js +3 -1
  92. package/packages/@monomind/cli/dist/src/update/rate-limiter.js +3 -1
  93. package/packages/@monomind/cli/package.json +2 -2
@@ -59,21 +59,21 @@ export const routeCommand = {
59
59
  output.writeln();
60
60
  output.printBox([
61
61
  `Task: ${result.task}`,
62
- `Top Agent: ${output.highlight(result.topAgent)}`,
63
- `Confidence: ${(result.confidence * 100).toFixed(1)}%`,
62
+ `Top Agent: ${output.highlight(result.primaryAgent.type)}`,
63
+ `Confidence: ${(result.primaryAgent.confidence * 100).toFixed(1)}%`,
64
64
  `Method: ${result.routing?.method ?? 'keyword'}`
65
65
  ].join('\n'), 'Routing Decision');
66
- if (result.recommendations.length > 0) {
66
+ const recommendations = [result.primaryAgent, ...(result.alternativeAgents || [])];
67
+ if (recommendations.length > 0) {
67
68
  output.writeln();
68
69
  output.writeln(output.bold('Agent Recommendations'));
69
70
  output.printTable({
70
71
  columns: [
71
- { key: 'agent', header: 'Agent', width: 20 },
72
+ { key: 'type', header: 'Agent', width: 20 },
72
73
  { key: 'confidence', header: 'Confidence', width: 12, align: 'right', format: (v) => `${(Number(v) * 100).toFixed(1)}%` },
73
- { key: 'reason', header: 'Reason', width: 35 },
74
- { key: 'estimatedTime', header: 'Est. Time', width: 12, format: (v) => v ? String(v) : 'N/A' }
74
+ { key: 'reason', header: 'Reason', width: 35 }
75
75
  ],
76
- data: result.recommendations.slice(0, topK)
76
+ data: recommendations.slice(0, topK)
77
77
  });
78
78
  }
79
79
  return { success: true, data: result };
@@ -447,7 +447,12 @@ export const metricsCommand = {
447
447
  output.writeln(output.bold(`Learning Metrics Dashboard (${period})`));
448
448
  output.writeln();
449
449
  try {
450
- // Call MCP tool for metrics
450
+ // Call MCP tool for metrics. The real handler (hooks-routing.ts) only
451
+ // returns a subset of these fields depending on whether any memory
452
+ // entries exist yet — patterns/agents/commands/performance are all
453
+ // Partial, not fully populated objects. Fields that aren't tracked yet
454
+ // are genuinely absent (not present as null), each with a `_note`
455
+ // string explaining what would populate them.
451
456
  const result = await callMCPTool('hooks_metrics', {
452
457
  period,
453
458
  includev1: v1Dashboard,
@@ -457,6 +462,11 @@ export const metricsCommand = {
457
462
  output.printJson(result);
458
463
  return { success: true, data: result };
459
464
  }
465
+ const pct = (v) => typeof v === 'number' ? `${(v * 100).toFixed(1)}%` : 'N/A';
466
+ if (result._note) {
467
+ output.writeln(output.dim(result._note));
468
+ output.writeln();
469
+ }
460
470
  // Patterns section
461
471
  output.writeln(output.bold('📊 Pattern Learning'));
462
472
  output.printTable({
@@ -465,12 +475,14 @@ export const metricsCommand = {
465
475
  { key: 'value', header: 'Value', width: 20, align: 'right' }
466
476
  ],
467
477
  data: [
468
- { metric: 'Total Patterns', value: result.patterns.total },
469
- { metric: 'Successful', value: output.success(String(result.patterns.successful)) },
470
- { metric: 'Failed', value: output.error(String(result.patterns.failed)) },
471
- { metric: 'Avg Confidence', value: `${(result.patterns.avgConfidence * 100).toFixed(1)}%` }
478
+ { metric: 'Total Patterns', value: result.patterns.total ?? 0 },
479
+ { metric: 'Successful', value: typeof result.patterns.successful === 'number' ? output.success(String(result.patterns.successful)) : 'N/A' },
480
+ { metric: 'Failed', value: typeof result.patterns.failed === 'number' ? output.error(String(result.patterns.failed)) : 'N/A' },
481
+ { metric: 'Avg Confidence', value: pct(result.patterns.avgConfidence) }
472
482
  ]
473
483
  });
484
+ if (result.patterns._note)
485
+ output.writeln(output.dim(` ${result.patterns._note}`));
474
486
  output.writeln();
475
487
  // Agent routing section
476
488
  output.writeln(output.bold('🤖 Agent Routing'));
@@ -480,11 +492,13 @@ export const metricsCommand = {
480
492
  { key: 'value', header: 'Value', width: 20, align: 'right' }
481
493
  ],
482
494
  data: [
483
- { metric: 'Routing Accuracy', value: `${(result.agents.routingAccuracy * 100).toFixed(1)}%` },
484
- { metric: 'Total Routes', value: result.agents.totalRoutes },
485
- { metric: 'Top Agent', value: output.highlight(result.agents.topAgent) }
495
+ { metric: 'Routing Accuracy', value: pct(result.agents.routingAccuracy) },
496
+ { metric: 'Total Routes', value: result.agents.totalRoutes ?? 0 },
497
+ { metric: 'Top Agent', value: result.agents.topAgent ? output.highlight(result.agents.topAgent) : 'N/A' }
486
498
  ]
487
499
  });
500
+ if (result.agents._note)
501
+ output.writeln(output.dim(` ${result.agents._note}`));
488
502
  output.writeln();
489
503
  // Command execution section
490
504
  output.writeln(output.bold('⚡ Command Execution'));
@@ -494,11 +508,13 @@ export const metricsCommand = {
494
508
  { key: 'value', header: 'Value', width: 20, align: 'right' }
495
509
  ],
496
510
  data: [
497
- { metric: 'Total Executed', value: result.commands.totalExecuted },
498
- { metric: 'Success Rate', value: `${(result.commands.successRate * 100).toFixed(1)}%` },
499
- { metric: 'Avg Risk Score', value: result.commands.avgRiskScore.toFixed(2) }
511
+ { metric: 'Total Executed', value: result.commands.totalExecuted ?? 0 },
512
+ { metric: 'Success Rate', value: pct(result.commands.successRate) },
513
+ { metric: 'Avg Risk Score', value: typeof result.commands.avgRiskScore === 'number' ? result.commands.avgRiskScore.toFixed(2) : 'N/A' }
500
514
  ]
501
515
  });
516
+ if (result.commands._note)
517
+ output.writeln(output.dim(` ${result.commands._note}`));
502
518
  if (v1Dashboard && result.performance) {
503
519
  const p = result.performance;
504
520
  output.writeln();
@@ -146,7 +146,10 @@ export const intelligenceCommand = {
146
146
  patternsFileEntries = pData.length;
147
147
  }
148
148
  }
149
- catch { /* ignore */ }
149
+ catch (e) {
150
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
151
+ console.error('[hooks-intelligence] patterns.json read/parse failed:', e);
152
+ }
150
153
  }
151
154
  // Read stats.json for trajectory data
152
155
  let trajectoriesFromDisk = 0;
@@ -160,7 +163,10 @@ export const intelligenceCommand = {
160
163
  lastAdaptationFromDisk = sData?.lastAdaptation ?? null;
161
164
  }
162
165
  }
163
- catch { /* ignore */ }
166
+ catch (e) {
167
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
168
+ console.error('[hooks-intelligence] stats.json read/parse failed:', e);
169
+ }
164
170
  }
165
171
  // Merge local stats with any we can get from MCP
166
172
  let mcpResult = null;
@@ -313,7 +313,7 @@ const sessionRestoreCommand = {
313
313
  data: [
314
314
  { item: 'Tasks', count: result.restoredState.tasksRestored },
315
315
  { item: 'Agents', count: result.restoredState.agentsRestored },
316
- { item: 'Memory Entries', count: result.restoredState.memoryRestored }
316
+ { item: 'Memory Bridge', count: result.restoredState.memoryBridgeInitialized ? 'ready' : 'unavailable' }
317
317
  ]
318
318
  });
319
319
  if (result.warnings && result.warnings.length > 0) {
@@ -179,7 +179,7 @@ export const wizardCommand = {
179
179
  }
180
180
  const { execFileSync } = await import('child_process');
181
181
  try {
182
- execFileSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['@monomind/cli@latest', 'embeddings', 'init', '--model', embeddingModel, '--no-download', '--force'], {
182
+ execFileSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['monomind@latest', 'embeddings', 'init', '--model', embeddingModel, '--no-download', '--force'], {
183
183
  stdio: 'pipe',
184
184
  cwd: ctx.cwd,
185
185
  timeout: 30000
@@ -199,7 +199,7 @@ export const wizardCommand = {
199
199
  if (enableGates) {
200
200
  try {
201
201
  const { execFileSync } = await import('child_process');
202
- execFileSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['@monomind/cli@latest', 'guidance', 'setup', '--project-dir', ctx.cwd], { stdio: 'pipe', cwd: ctx.cwd, timeout: 10000 });
202
+ execFileSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['monomind@latest', 'guidance', 'setup', '--project-dir', ctx.cwd], { stdio: 'pipe', cwd: ctx.cwd, timeout: 10000 });
203
203
  gatesEnabled = true;
204
204
  output.writeln(output.success(' ✓ Enforcement gates wired'));
205
205
  }
@@ -10,6 +10,7 @@ import { executeInit, DEFAULT_INIT_OPTIONS, MINIMAL_INIT_OPTIONS, FULL_INIT_OPTI
10
10
  import { wizardCommand } from './init-wizard.js';
11
11
  import { upgradeCommand } from './init-upgrade.js';
12
12
  import { checkCommand, skillsCommand, hooksCommand } from './init-subcommands.js';
13
+ import { initializeMemoryDatabase } from '../memory/memory-initializer.js';
13
14
  function isInitialized(cwd) {
14
15
  const claudePath = path.join(cwd, '.claude', 'settings.json');
15
16
  const monomindPath = path.join(cwd, '.monomind', 'config.yaml');
@@ -172,23 +173,30 @@ const initAction = async (ctx) => {
172
173
  output.printInfo('Starting services...');
173
174
  const { execSync } = await import('child_process');
174
175
  if (startAll) {
176
+ // In-process, not a subprocess: `npx @monomind/cli@latest memory init`
177
+ // (the previous approach) shelled out to a package name that has
178
+ // never been published — every fresh init silently 404'd here and
179
+ // fell into the catch block's "already exists" message even when no
180
+ // database existed at all. initializeMemoryDatabase() is the same
181
+ // function `monomind memory init` itself calls, run directly.
175
182
  try {
176
183
  output.writeln(output.dim(' Initializing memory database...'));
177
- execSync('npx @monomind/cli@latest memory init', {
178
- stdio: 'pipe',
179
- cwd: ctx.cwd,
180
- timeout: 30000
181
- });
182
- output.writeln(output.success(' Memory initialized'));
184
+ const memResult = await initializeMemoryDatabase({ dbPath: path.join(ctx.cwd, '.swarm', 'memory.db') });
185
+ if (memResult.success) {
186
+ output.writeln(output.success(' ✓ Memory initialized'));
187
+ }
188
+ else {
189
+ output.writeln(output.dim(` Memory database init skipped (${memResult.error || 'unknown reason'})`));
190
+ }
183
191
  }
184
- catch {
185
- output.writeln(output.dim(' Memory database already exists'));
192
+ catch (e) {
193
+ output.writeln(output.dim(` Memory database init skipped (${e instanceof Error ? e.message : String(e)})`));
186
194
  }
187
195
  }
188
196
  if (startAll) {
189
197
  try {
190
198
  output.writeln(output.dim(' Initializing swarm...'));
191
- execSync('npx @monomind/cli@latest swarm init --topology hierarchical', {
199
+ execSync('npx monomind@latest swarm init --topology hierarchical', {
192
200
  stdio: 'pipe',
193
201
  cwd: ctx.cwd,
194
202
  timeout: 30000
@@ -199,6 +207,42 @@ const initAction = async (ctx) => {
199
207
  output.writeln(output.dim(' Swarm initialization skipped'));
200
208
  }
201
209
  }
210
+ if (startAll) {
211
+ // Seed .monomind/metrics/ immediately instead of waiting for the
212
+ // first Claude Code session-restore hook to run these workers —
213
+ // running `monomind doctor` right after `init` (before ever opening
214
+ // Claude Code) otherwise always shows "Worker Metrics"/"Security
215
+ // Audit" as unconfigured, even though nothing is actually broken.
216
+ try {
217
+ output.writeln(output.dim(' Seeding worker metrics...'));
218
+ const hooksMod = await import('@monomind/hooks').catch(() => null);
219
+ if (hooksMod && hooksMod.createWorkerManager) {
220
+ const manager = hooksMod.createWorkerManager(ctx.cwd);
221
+ await manager.ensureMetricsDir();
222
+ const seeded = [];
223
+ for (const workerName of ['map', 'audit']) {
224
+ try {
225
+ const r = await manager.runWorker(workerName);
226
+ if (r.success)
227
+ seeded.push(workerName);
228
+ }
229
+ catch { /* best-effort — doctor will report if this stays missing */ }
230
+ }
231
+ if (seeded.length > 0) {
232
+ output.writeln(output.success(` ✓ Worker metrics seeded (${seeded.join(', ')})`));
233
+ }
234
+ else {
235
+ output.writeln(output.dim(' Worker metrics seeding skipped'));
236
+ }
237
+ }
238
+ else {
239
+ output.writeln(output.dim(' Worker metrics seeding skipped (@monomind/hooks unavailable)'));
240
+ }
241
+ }
242
+ catch (e) {
243
+ output.writeln(output.dim(` Worker metrics seeding skipped (${e instanceof Error ? e.message : String(e)})`));
244
+ }
245
+ }
202
246
  output.writeln();
203
247
  output.printSuccess('All services started');
204
248
  }
@@ -216,7 +260,7 @@ const initAction = async (ctx) => {
216
260
  try {
217
261
  output.writeln(output.dim(` Model: ${embeddingModel}`));
218
262
  output.writeln(output.dim(' Hyperbolic: Enabled (Poincaré ball)'));
219
- execFileSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['@monomind/cli@latest', 'embeddings', 'init', '--model', embeddingModel, '--no-download', '--force'], {
263
+ execFileSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['monomind@latest', 'embeddings', 'init', '--model', embeddingModel, '--no-download', '--force'], {
220
264
  stdio: 'pipe',
221
265
  cwd: ctx.cwd,
222
266
  timeout: 30000
@@ -466,7 +466,10 @@ const toggleCommand = {
466
466
  try {
467
467
  disabled = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
468
468
  }
469
- catch { /* fresh */ }
469
+ catch (e) {
470
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
471
+ console.error('[mcp] failed to load mcp-disabled-tools.json, treating as fresh:', e);
472
+ }
470
473
  const enableArg = ctx.flags.enable;
471
474
  const disableArg = ctx.flags.disable;
472
475
  if (!enableArg && !disableArg) {
@@ -330,7 +330,13 @@ export const configureCommand = {
330
330
  ]
331
331
  });
332
332
  output.writeln();
333
- configManager.set(ctx.cwd, 'memory', config);
333
+ try {
334
+ configManager.set(ctx.cwd, 'memory', config);
335
+ }
336
+ catch (error) {
337
+ output.printError(error instanceof Error ? error.message : String(error));
338
+ return { success: false, exitCode: 1 };
339
+ }
334
340
  output.printSuccess('Memory configuration updated');
335
341
  return { success: true, data: config };
336
342
  }
@@ -0,0 +1,18 @@
1
+ import type { CommandContext, CommandResult } from '../types.js';
2
+ /** Validate org config(s) against OrgDefSchema — the exact parse `org run`/`org serve`
3
+ * perform — plus the structural invariants the runtime assumes but the schema can't
4
+ * express (single root role, resolvable reports_to, unique ids, parseable schedule). */
5
+ export declare const validateAction: (ctx: CommandContext) => Promise<CommandResult>;
6
+ /** `org logs <name> [--run id] [--role r] [--follow]` — formatted bus.jsonl tail. */
7
+ export declare const logsAction: (ctx: CommandContext, name: string) => Promise<CommandResult>;
8
+ /** `org report <name> [--run id] [--all]` — summarize a run (or list run history). */
9
+ export declare const reportAction: (ctx: CommandContext, name: string) => Promise<CommandResult>;
10
+ /** `org questions <name> [--all]` — list pending (or all) ask_human questions. */
11
+ export declare const questionsAction: (ctx: CommandContext, name: string) => Promise<CommandResult>;
12
+ /** `org answer <name> <question-id> <answer...>` — answer a pending ask_human question.
13
+ * Delivers live via the hosting daemon's /api/answer-question when the org is running
14
+ * (broker lookup); otherwise records the answer and queues it for the next run. */
15
+ export declare const answerAction: (ctx: CommandContext, name: string) => Promise<CommandResult>;
16
+ /** `org create <name> --template <t> [--goal g] [--schedule s]` — scaffold a config from a template. */
17
+ export declare const createAction: (ctx: CommandContext, name: string) => Promise<CommandResult>;
18
+ //# sourceMappingURL=org-observe.d.ts.map
@@ -0,0 +1,295 @@
1
+ // packages/@monomind/cli/src/commands/org-observe.ts
2
+ // Read-side org subcommands (logs / report) + template scaffolding (create).
3
+ // Kept out of org.ts to respect the 500-line file ceiling.
4
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { output } from '../output.js';
7
+ import { ORG_DIR, OrgDefSchema } from '../orgrt/types.js';
8
+ import { formatEvent, listRunDirs, readHistory, readRunEvents, summarizeRun } from '../orgrt/reporting.js';
9
+ import { ORG_TEMPLATES, buildFromTemplate } from '../orgrt/templates.js';
10
+ import { parseSchedule } from '../orgrt/scheduler.js';
11
+ import { listOrgConfigFiles, validateOrgName } from './org.js';
12
+ const log = (text) => { console.log(text); };
13
+ /** Validate org config(s) against OrgDefSchema — the exact parse `org run`/`org serve`
14
+ * perform — plus the structural invariants the runtime assumes but the schema can't
15
+ * express (single root role, resolvable reports_to, unique ids, parseable schedule). */
16
+ export const validateAction = async (ctx) => {
17
+ const orgsDir = join(ctx.cwd || process.cwd(), ORG_DIR);
18
+ let files;
19
+ if (ctx.args[0]) {
20
+ const validated = validateOrgName(ctx.args[0]);
21
+ if (!validated.ok)
22
+ return validated.result;
23
+ files = [`${validated.name}.json`];
24
+ }
25
+ else {
26
+ if (!existsSync(orgsDir))
27
+ return { success: false, message: 'no orgs directory — create an org first with /mastermind:createorg' };
28
+ files = listOrgConfigFiles(orgsDir);
29
+ if (!files.length)
30
+ return { success: false, message: 'no org configs found' };
31
+ }
32
+ let failed = 0;
33
+ for (const f of files) {
34
+ const stem = f.replace(/\.json$/, '');
35
+ const path = join(orgsDir, f);
36
+ const errors = [];
37
+ const warnings = [];
38
+ if (!existsSync(path)) {
39
+ log(output.error(`${stem}: not found (${path})`));
40
+ failed++;
41
+ continue;
42
+ }
43
+ try {
44
+ const def = OrgDefSchema.parse(JSON.parse(readFileSync(path, 'utf8')));
45
+ const ids = def.roles.map(r => r.id);
46
+ const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
47
+ if (dupes.length)
48
+ errors.push(`duplicate role id(s): ${[...new Set(dupes)].join(', ')}`);
49
+ const roots = def.roles.filter(r => r.reports_to === null);
50
+ if (roots.length === 0)
51
+ errors.push('no root role — exactly one role must have reports_to: null');
52
+ if (roots.length > 1)
53
+ errors.push(`multiple root roles (${roots.map(r => r.id).join(', ')}) — exactly one may have reports_to: null`);
54
+ for (const r of def.roles) {
55
+ if (r.reports_to !== null && !ids.includes(r.reports_to))
56
+ errors.push(`role "${r.id}": reports_to "${r.reports_to}" matches no role id`);
57
+ if (r.reports_to === r.id)
58
+ errors.push(`role "${r.id}" reports to itself`);
59
+ }
60
+ if (def.schedule != null && parseSchedule(def.schedule) === null)
61
+ errors.push(`schedule "${def.schedule}" is not parseable — use "<N>s", "<N>m", or "<N>h"`);
62
+ if (def.name !== stem)
63
+ warnings.push(`def.name "${def.name}" differs from filename — the runtime addresses this org as "${stem}"`);
64
+ }
65
+ catch (err) {
66
+ errors.push(err instanceof Error ? err.message : String(err));
67
+ }
68
+ for (const w of warnings)
69
+ log(output.warning(`${stem}: ${w}`));
70
+ if (errors.length) {
71
+ failed++;
72
+ for (const e of errors)
73
+ log(output.error(`${stem}: ${e}`));
74
+ }
75
+ else {
76
+ log(output.success(`${stem}: valid${warnings.length ? ` (${warnings.length} warning(s))` : ''}`));
77
+ }
78
+ }
79
+ return failed
80
+ ? { success: false, message: `${failed} of ${files.length} org config(s) failed validation` }
81
+ : { success: true, message: `${files.length} org config(s) valid` };
82
+ };
83
+ const resolveRun = (cwd, name, runFlag) => {
84
+ if (typeof runFlag === 'string' && runFlag)
85
+ return runFlag;
86
+ return listRunDirs(cwd, name)[0] ?? null;
87
+ };
88
+ /** `org logs <name> [--run id] [--role r] [--follow]` — formatted bus.jsonl tail. */
89
+ export const logsAction = async (ctx, name) => {
90
+ const run = resolveRun(ctx.cwd, name, ctx.flags['run']);
91
+ if (!run)
92
+ return { success: false, message: `no runs found for org ${name} — start one with: monomind org run ${name}` };
93
+ const file = join(ctx.cwd, ORG_DIR, name, run, 'bus.jsonl');
94
+ const roleFilter = typeof ctx.flags['role'] === 'string' ? ctx.flags['role'] : null;
95
+ const show = (e) => {
96
+ if (roleFilter && e.from !== roleFilter && e.to !== roleFilter)
97
+ return;
98
+ log(formatEvent(e));
99
+ };
100
+ log(output.info(`org ${name} — ${run}${roleFilter ? ` (role: ${roleFilter})` : ''}`));
101
+ let seenLines = 0;
102
+ const drain = () => {
103
+ if (!existsSync(file))
104
+ return;
105
+ const lines = readFileSync(file, 'utf8').split('\n').filter(Boolean);
106
+ for (const l of lines.slice(seenLines)) {
107
+ try {
108
+ show(JSON.parse(l));
109
+ seenLines++;
110
+ }
111
+ catch {
112
+ break;
113
+ } // partial line mid-append — retry it next tick
114
+ }
115
+ };
116
+ drain();
117
+ if (ctx.flags['follow'] !== true)
118
+ return { success: true };
119
+ log(output.info('following — Ctrl-C to stop'));
120
+ await new Promise(resolve => {
121
+ const iv = setInterval(drain, 500);
122
+ process.once('SIGINT', () => { clearInterval(iv); resolve(); });
123
+ process.once('SIGTERM', () => { clearInterval(iv); resolve(); });
124
+ });
125
+ return { success: true };
126
+ };
127
+ /** `org report <name> [--run id] [--all]` — summarize a run (or list run history). */
128
+ export const reportAction = async (ctx, name) => {
129
+ if (ctx.flags['all'] === true) {
130
+ const history = readHistory(ctx.cwd, name);
131
+ if (!history.length)
132
+ return { success: false, message: `no run history for org ${name}` };
133
+ log(output.info(`org ${name} — ${history.length} recorded run(s):`));
134
+ for (const h of history) {
135
+ const dur = h.durationMs != null ? `${Math.round(h.durationMs / 1000)}s` : '?';
136
+ const outcome = h.outcome ? `${h.outcome.status}: ${h.outcome.summary.slice(0, 60)}` : 'no outcome recorded';
137
+ log(output.info(` • ${h.run} ${dur} ${h.totalTokens} tokens ${h.messages} msgs — ${outcome}`));
138
+ }
139
+ return { success: true };
140
+ }
141
+ const run = resolveRun(ctx.cwd, name, ctx.flags['run']);
142
+ if (!run)
143
+ return { success: false, message: `no runs found for org ${name}` };
144
+ const events = readRunEvents(ctx.cwd, name, run);
145
+ if (!events.length)
146
+ return { success: false, message: `run ${run} has no recorded events` };
147
+ const s = summarizeRun(events);
148
+ // Per-role budget ceiling: same split the daemon applies (budget ÷ role count),
149
+ // with any explicit policy.maxTokens override. Missing/unreadable config → no ceilings.
150
+ let perRoleBudget = null;
151
+ const roleCeiling = new Map();
152
+ try {
153
+ const def = OrgDefSchema.parse(JSON.parse(readFileSync(join(ctx.cwd, ORG_DIR, `${name}.json`), 'utf8')));
154
+ perRoleBudget = Math.floor((def.run_config.budget_tokens ?? 1_000_000) / def.roles.length);
155
+ for (const r of def.roles) {
156
+ const max = r.policy?.maxTokens;
157
+ roleCeiling.set(r.id, max ?? perRoleBudget);
158
+ }
159
+ }
160
+ catch { /* config gone or invalid — report without budget context */ }
161
+ const budgetNote = (id, tokens) => {
162
+ const cap = roleCeiling.get(id);
163
+ if (!cap)
164
+ return '';
165
+ const pct = Math.round((tokens / cap) * 100);
166
+ return ` (${pct}% of ${cap}${pct >= 100 ? ' — EXHAUSTED' : pct >= 80 ? ' — near limit' : ''})`;
167
+ };
168
+ log(output.info(`ORG REPORT — ${name} / ${run}`));
169
+ log(output.info(` Duration: ${s.durationMs != null ? `${Math.round(s.durationMs / 1000)}s` : '?'} Events: ${s.events} Messages: ${s.messages}${s.xorgMessages ? ` (+${s.xorgMessages} cross-org)` : ''}`));
170
+ log(output.info(` Tokens: ${s.totalTokens}${perRoleBudget ? ` (budget: ${perRoleBudget}/role)` : ''}${s.totalCostUsd ? ` Cost: $${s.totalCostUsd.toFixed(4)}` : ''}`));
171
+ if (s.outcome)
172
+ log(output.success(` Outcome: ${s.outcome.status} (by ${s.outcome.by}) — ${s.outcome.summary}`));
173
+ else
174
+ log(output.warning(' Outcome: not recorded (coordinator never called org_complete)'));
175
+ log(output.info(' Roles:'));
176
+ for (const [id, r] of Object.entries(s.roles)) {
177
+ log(output.info(` ${r.crashed ? '✗' : '•'} ${id}: ${r.messagesSent} msgs, ${r.toolsAllowed} tools${r.toolsDenied ? ` (${r.toolsDenied} denied)` : ''}, ${r.tokens} tokens${budgetNote(id, r.tokens)}${r.crashed ? ' — CRASHED' : ''}`));
178
+ }
179
+ if (s.assets.length) {
180
+ log(output.info(` Assets (${s.assets.length}):`));
181
+ for (const a of s.assets.slice(0, 20))
182
+ log(output.info(` 📄 ${a}`));
183
+ if (s.assets.length > 20)
184
+ log(output.info(` … and ${s.assets.length - 20} more`));
185
+ }
186
+ return { success: true };
187
+ };
188
+ const readQuestions = (cwd, name) => {
189
+ try {
190
+ return JSON.parse(readFileSync(join(cwd, ORG_DIR, name, 'questions.json'), 'utf8')).questions ?? [];
191
+ }
192
+ catch {
193
+ return [];
194
+ }
195
+ };
196
+ /** `org questions <name> [--all]` — list pending (or all) ask_human questions. */
197
+ export const questionsAction = async (ctx, name) => {
198
+ const all = readQuestions(ctx.cwd, name);
199
+ const shown = ctx.flags['all'] === true ? all : all.filter(q => q.answer === null);
200
+ if (!shown.length) {
201
+ log(output.info(all.length ? `No pending questions for org ${name} (${all.length} answered — use --all).` : `No questions recorded for org ${name}.`));
202
+ return { success: true };
203
+ }
204
+ for (const q of shown) {
205
+ const when = new Date(q.ts).toISOString().replace('T', ' ').slice(0, 16);
206
+ log(output.info(`${q.answer === null ? '❓' : '✓'} [${q.questionId}] ${when} ${q.role}: ${q.question}`));
207
+ if (q.answer !== null)
208
+ log(output.info(` ↳ ${q.answer}`));
209
+ }
210
+ if (shown.some(q => q.answer === null))
211
+ log(output.info(`\nAnswer with: monomind org answer ${name} <question-id> "your answer"`));
212
+ return { success: true };
213
+ };
214
+ /** `org answer <name> <question-id> <answer...>` — answer a pending ask_human question.
215
+ * Delivers live via the hosting daemon's /api/answer-question when the org is running
216
+ * (broker lookup); otherwise records the answer and queues it for the next run. */
217
+ export const answerAction = async (ctx, name) => {
218
+ const questionId = ctx.args[1];
219
+ const answer = ctx.args.slice(2).join(' ').trim();
220
+ if (!questionId || !answer)
221
+ return { success: false, message: `usage: monomind org answer ${name} <question-id> "answer text"` };
222
+ const questions = readQuestions(ctx.cwd, name);
223
+ const q = questions.find(x => x.questionId === questionId);
224
+ if (!q) {
225
+ log(output.error(`Question "${questionId}" not found for org ${name} — list with: monomind org questions ${name}`));
226
+ return { success: false, message: 'question not found' };
227
+ }
228
+ if (q.answer !== null)
229
+ return { success: false, message: `question "${questionId}" was already answered` };
230
+ // Live path: the hosting daemon updates questions.json and pushes into the role's mailbox.
231
+ const { lookupOrg } = await import('../orgrt/broker.js');
232
+ const remote = lookupOrg(name);
233
+ if (remote) {
234
+ try {
235
+ const res = await fetch(`${remote.url}/api/answer-question`, {
236
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
237
+ body: JSON.stringify({ org: name, role: q.role, questionId, answer }),
238
+ signal: AbortSignal.timeout(10_000),
239
+ });
240
+ const data = await res.json().catch(() => ({}));
241
+ if (res.ok && data.ok) {
242
+ log(output.success(`Answer delivered to ${name}:${q.role} (live).`));
243
+ return { success: true };
244
+ }
245
+ log(output.warning(`Live delivery rejected (${data.error ?? res.status}) — falling back to offline queue.`));
246
+ }
247
+ catch (err) {
248
+ log(output.warning(`Hosting daemon unreachable (${err instanceof Error ? err.message : 'error'}) — falling back to offline queue.`));
249
+ }
250
+ }
251
+ // Offline path: mirror daemon.answerQuestion's org-not-running branch.
252
+ const updated = questions.map(x => x.questionId === questionId ? { ...x, answer, answeredAt: Date.now() } : x);
253
+ const dest = join(ctx.cwd, ORG_DIR, name, 'questions.json');
254
+ const tmp = `${dest}.${process.pid}.tmp`;
255
+ writeFileSync(tmp, JSON.stringify({ questions: updated }, null, 2));
256
+ const { renameSync } = await import('node:fs');
257
+ renameSync(tmp, dest);
258
+ const { queueMessage } = await import('../orgrt/inbox.js');
259
+ queueMessage(ctx.cwd, name, {
260
+ fromQualified: 'human', toRole: q.role,
261
+ subject: `answer:${questionId}`,
262
+ body: `question: ${q.question}\n\nanswer: ${answer}`,
263
+ ts: Date.now(),
264
+ });
265
+ log(output.success(`Answer recorded — ${name}:${q.role} receives it when the org next runs.`));
266
+ return { success: true };
267
+ };
268
+ /** `org create <name> --template <t> [--goal g] [--schedule s]` — scaffold a config from a template. */
269
+ export const createAction = async (ctx, name) => {
270
+ const templateName = typeof ctx.flags['template'] === 'string' ? ctx.flags['template'] : '';
271
+ if (!templateName) {
272
+ log(output.info(`Available templates: ${Object.keys(ORG_TEMPLATES).join(', ')}`));
273
+ return { success: false, message: 'usage: monomind org create <name> --template <template> [--goal "..."] [--schedule 30m]' };
274
+ }
275
+ const def = buildFromTemplate(templateName, name, typeof ctx.flags['goal'] === 'string' ? ctx.flags['goal'] : undefined);
276
+ if (!def) {
277
+ log(output.error(`Unknown template "${templateName}" — available: ${Object.keys(ORG_TEMPLATES).join(', ')}`));
278
+ return { success: false, message: 'unknown template' };
279
+ }
280
+ if (typeof ctx.flags['schedule'] === 'string')
281
+ def.schedule = ctx.flags['schedule'];
282
+ const file = join(ctx.cwd, ORG_DIR, `${name}.json`);
283
+ if (existsSync(file) && ctx.flags['force'] !== true) {
284
+ log(output.error(`Org "${name}" already exists — pass --force to overwrite.`));
285
+ return { success: false, message: 'org exists' };
286
+ }
287
+ OrgDefSchema.parse(def); // templates must always produce a runnable config
288
+ const { mkdirSync } = await import('node:fs');
289
+ mkdirSync(join(ctx.cwd, ORG_DIR), { recursive: true });
290
+ writeFileSync(file, JSON.stringify(def, null, 2) + '\n', 'utf8');
291
+ log(output.success(`Org "${name}" created from template "${templateName}" (${def.roles.length} roles).`));
292
+ log(output.info(` Edit the goal/roles in ${file}, then: monomind org run ${name}`));
293
+ return { success: true };
294
+ };
295
+ //# sourceMappingURL=org-observe.js.map
@@ -1,4 +1,12 @@
1
- import type { Command } from '../types.js';
1
+ import type { Command, CommandResult } from '../types.js';
2
+ export declare function validateOrgName(name: string | undefined): {
3
+ ok: true;
4
+ name: string;
5
+ } | {
6
+ ok: false;
7
+ result: CommandResult;
8
+ };
9
+ export declare function listOrgConfigFiles(orgsDir: string): string[];
2
10
  /** Remove a lingering stopfile so a fresh `org run` doesn't self-terminate. */
3
11
  export declare const clearStopfile: (cwd: string, name: string) => void;
4
12
  export declare const orgCommand: Command;