monomind 1.11.14 → 1.12.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 (172) hide show
  1. package/.claude/agents/generated/channel-intelligence-director.md +87 -0
  2. package/.claude/agents/generated/chief-growth-officer.md +88 -0
  3. package/.claude/agents/generated/content-seo-strategist.md +90 -0
  4. package/.claude/agents/generated/developer-community-strategist.md +91 -0
  5. package/.claude/agents/generated/outreach-partnership-strategist.md +90 -0
  6. package/.claude/agents/generated/social-media-strategist.md +91 -0
  7. package/.claude/agents/generated/video-visual-strategist.md +90 -0
  8. package/.claude/commands/mastermind/master.md +1 -1
  9. package/.claude/helpers/auto-memory-hook.mjs +13 -4
  10. package/.claude/helpers/control-start.cjs +5 -0
  11. package/.claude/helpers/event-logger.cjs +114 -0
  12. package/.claude/helpers/handlers/adr-draft-handler.cjs +19 -5
  13. package/.claude/helpers/handlers/agent-start-handler.cjs +13 -4
  14. package/.claude/helpers/handlers/compact-handler.cjs +2 -0
  15. package/.claude/helpers/handlers/edit-handler.cjs +1 -1
  16. package/.claude/helpers/handlers/gates-handler.cjs +3 -0
  17. package/.claude/helpers/handlers/graph-status-handler.cjs +14 -8
  18. package/.claude/helpers/handlers/loops-status-handler.cjs +5 -2
  19. package/.claude/helpers/handlers/route-handler.cjs +13 -6
  20. package/.claude/helpers/handlers/session-handler.cjs +11 -4
  21. package/.claude/helpers/handlers/session-restore-handler.cjs +21 -11
  22. package/.claude/helpers/handlers/task-handler.cjs +13 -5
  23. package/.claude/helpers/intelligence.cjs +7 -2
  24. package/.claude/helpers/loop-tracker.cjs +15 -3
  25. package/.claude/helpers/memory.cjs +6 -1
  26. package/.claude/helpers/router.cjs +5 -2
  27. package/.claude/helpers/session.cjs +2 -0
  28. package/.claude/helpers/statusline.cjs +10 -2
  29. package/.claude/helpers/utils/micro-agents.cjs +20 -4
  30. package/.claude/scheduled_tasks.lock +1 -1
  31. package/.claude/settings.json +92 -1
  32. package/.claude/skills/mastermind/_protocol.md +25 -15
  33. package/.claude/skills/mastermind/architect.md +3 -3
  34. package/.claude/skills/mastermind/autodev.md +4 -2
  35. package/.claude/skills/mastermind/idea.md +10 -0
  36. package/.claude/skills/mastermind/ops.md +3 -3
  37. package/.claude/skills/mastermind/runorg.md +153 -86
  38. package/package.json +20 -3
  39. package/packages/@monomind/cli/dist/src/agents/registry-builder.js +2 -0
  40. package/packages/@monomind/cli/dist/src/autopilot-state.js +10 -5
  41. package/packages/@monomind/cli/dist/src/benchmarks/benchmark-runner.js +13 -0
  42. package/packages/@monomind/cli/dist/src/benchmarks/metric-evaluators.js +20 -9
  43. package/packages/@monomind/cli/dist/src/browser/actions.js +10 -3
  44. package/packages/@monomind/cli/dist/src/browser/browser.js +12 -2
  45. package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -3
  46. package/packages/@monomind/cli/dist/src/browser/har.js +27 -5
  47. package/packages/@monomind/cli/dist/src/commands/agent.js +11 -8
  48. package/packages/@monomind/cli/dist/src/commands/analyze.js +36 -21
  49. package/packages/@monomind/cli/dist/src/commands/autopilot.js +12 -4
  50. package/packages/@monomind/cli/dist/src/commands/benchmark.js +51 -8
  51. package/packages/@monomind/cli/dist/src/commands/browse.js +5 -2
  52. package/packages/@monomind/cli/dist/src/commands/claims.js +29 -11
  53. package/packages/@monomind/cli/dist/src/commands/cleanup.js +25 -5
  54. package/packages/@monomind/cli/dist/src/commands/config.js +15 -7
  55. package/packages/@monomind/cli/dist/src/commands/daemon.js +6 -0
  56. package/packages/@monomind/cli/dist/src/commands/deployment.js +34 -19
  57. package/packages/@monomind/cli/dist/src/commands/doctor.js +97 -20
  58. package/packages/@monomind/cli/dist/src/commands/guidance.js +15 -2
  59. package/packages/@monomind/cli/dist/src/commands/hive-mind.js +37 -14
  60. package/packages/@monomind/cli/dist/src/commands/hooks.js +42 -25
  61. package/packages/@monomind/cli/dist/src/commands/init.js +9 -4
  62. package/packages/@monomind/cli/dist/src/commands/issues.js +29 -26
  63. package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -5
  64. package/packages/@monomind/cli/dist/src/commands/memory.js +10 -0
  65. package/packages/@monomind/cli/dist/src/commands/migrate.js +5 -5
  66. package/packages/@monomind/cli/dist/src/commands/monograph.js +18 -5
  67. package/packages/@monomind/cli/dist/src/commands/monovector/backup.js +8 -2
  68. package/packages/@monomind/cli/dist/src/commands/monovector/benchmark.js +20 -7
  69. package/packages/@monomind/cli/dist/src/commands/monovector/import.js +15 -0
  70. package/packages/@monomind/cli/dist/src/commands/monovector/migrate.js +4 -1
  71. package/packages/@monomind/cli/dist/src/commands/monovector/optimize.js +11 -0
  72. package/packages/@monomind/cli/dist/src/commands/monovector/setup.js +11 -1
  73. package/packages/@monomind/cli/dist/src/commands/neural.js +1 -1
  74. package/packages/@monomind/cli/dist/src/commands/performance.js +20 -7
  75. package/packages/@monomind/cli/dist/src/commands/platforms.js +90 -8
  76. package/packages/@monomind/cli/dist/src/commands/plugins.js +12 -5
  77. package/packages/@monomind/cli/dist/src/commands/process.js +33 -10
  78. package/packages/@monomind/cli/dist/src/commands/progress.js +5 -3
  79. package/packages/@monomind/cli/dist/src/commands/providers.js +5 -5
  80. package/packages/@monomind/cli/dist/src/commands/replay.js +8 -2
  81. package/packages/@monomind/cli/dist/src/commands/route.js +27 -7
  82. package/packages/@monomind/cli/dist/src/commands/security.js +4 -0
  83. package/packages/@monomind/cli/dist/src/commands/session.js +12 -1
  84. package/packages/@monomind/cli/dist/src/commands/start.js +11 -4
  85. package/packages/@monomind/cli/dist/src/commands/status.js +7 -4
  86. package/packages/@monomind/cli/dist/src/commands/swarm.js +27 -13
  87. package/packages/@monomind/cli/dist/src/commands/task.js +26 -11
  88. package/packages/@monomind/cli/dist/src/commands/tokens.js +7 -2
  89. package/packages/@monomind/cli/dist/src/commands/transfer-store.js +36 -22
  90. package/packages/@monomind/cli/dist/src/commands/update.js +15 -3
  91. package/packages/@monomind/cli/dist/src/commands/workflow.js +39 -6
  92. package/packages/@monomind/cli/dist/src/consensus/audit-writer.js +18 -7
  93. package/packages/@monomind/cli/dist/src/consensus/vote-signer.js +25 -8
  94. package/packages/@monomind/cli/dist/src/index.js +7 -3
  95. package/packages/@monomind/cli/dist/src/init/executor.js +14 -11
  96. package/packages/@monomind/cli/dist/src/init/shared-instructions-generator.js +20 -4
  97. package/packages/@monomind/cli/dist/src/init/statusline-generator.js +36 -15
  98. package/packages/@monomind/cli/dist/src/mcp-tools/a2a-tools.js +98 -13
  99. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +16 -3
  100. package/packages/@monomind/cli/dist/src/mcp-tools/analyze-tools.js +80 -17
  101. package/packages/@monomind/cli/dist/src/mcp-tools/browser-tools.js +84 -22
  102. package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +35 -7
  103. package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +82 -17
  104. package/packages/@monomind/cli/dist/src/mcp-tools/coordination-tools.js +37 -4
  105. package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +49 -7
  106. package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +45 -18
  107. package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +75 -25
  108. package/packages/@monomind/cli/dist/src/mcp-tools/guidance-tools.js +32 -10
  109. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +91 -20
  110. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +188 -29
  111. package/packages/@monomind/cli/dist/src/mcp-tools/memory-tools.js +25 -7
  112. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +11 -2
  113. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +148 -26
  114. package/packages/@monomind/cli/dist/src/mcp-tools/neural-tools.js +44 -9
  115. package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +45 -10
  116. package/packages/@monomind/cli/dist/src/mcp-tools/progress-tools.js +7 -4
  117. package/packages/@monomind/cli/dist/src/mcp-tools/request-tracker.js +15 -1
  118. package/packages/@monomind/cli/dist/src/mcp-tools/security-tools.js +61 -9
  119. package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +45 -14
  120. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +15 -3
  121. package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +14 -7
  122. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +52 -10
  123. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +40 -6
  124. package/packages/@monomind/cli/dist/src/mcp-tools/transfer-tools.js +37 -4
  125. package/packages/@monomind/cli/dist/src/mcp-tools/workflow-tools.js +29 -6
  126. package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +26 -10
  127. package/packages/@monomind/cli/dist/src/memory/intelligence.js +80 -19
  128. package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +21 -2
  129. package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +67 -3
  130. package/packages/@monomind/cli/dist/src/memory/sona-optimizer.js +14 -4
  131. package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +43 -7
  132. package/packages/@monomind/cli/dist/src/monovector/coverage-router.js +8 -4
  133. package/packages/@monomind/cli/dist/src/monovector/coverage-tools.js +6 -3
  134. package/packages/@monomind/cli/dist/src/monovector/diff-classifier.js +13 -0
  135. package/packages/@monomind/cli/dist/src/monovector/route-outcomes.d.ts +2 -1
  136. package/packages/@monomind/cli/dist/src/monovector/route-outcomes.js +46 -4
  137. package/packages/@monomind/cli/dist/src/plugins/manager.js +8 -3
  138. package/packages/@monomind/cli/dist/src/plugins/store/discovery.js +46 -2
  139. package/packages/@monomind/cli/dist/src/plugins/store/search.js +5 -4
  140. package/packages/@monomind/cli/dist/src/production/circuit-breaker.js +17 -3
  141. package/packages/@monomind/cli/dist/src/production/error-handler.js +3 -0
  142. package/packages/@monomind/cli/dist/src/production/monitoring.js +20 -3
  143. package/packages/@monomind/cli/dist/src/production/rate-limiter.js +13 -4
  144. package/packages/@monomind/cli/dist/src/production/retry.js +17 -9
  145. package/packages/@monomind/cli/dist/src/routing/embed-worker.js +6 -2
  146. package/packages/@monomind/cli/dist/src/routing/embedder.js +0 -0
  147. package/packages/@monomind/cli/dist/src/routing/llm-caller.js +13 -2
  148. package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +18 -3
  149. package/packages/@monomind/cli/dist/src/services/claim-service.d.ts +1 -0
  150. package/packages/@monomind/cli/dist/src/services/claim-service.js +8 -0
  151. package/packages/@monomind/cli/dist/src/services/config-file-manager.js +14 -2
  152. package/packages/@monomind/cli/dist/src/services/headless-worker-executor.js +18 -2
  153. package/packages/@monomind/cli/dist/src/services/worker-daemon.js +53 -12
  154. package/packages/@monomind/cli/dist/src/transfer/anonymization/index.d.ts +0 -3
  155. package/packages/@monomind/cli/dist/src/transfer/anonymization/index.js +16 -1
  156. package/packages/@monomind/cli/dist/src/transfer/export.js +8 -0
  157. package/packages/@monomind/cli/dist/src/transfer/ipfs/upload.js +33 -3
  158. package/packages/@monomind/cli/dist/src/transfer/serialization/cfp.js +9 -3
  159. package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +37 -3
  160. package/packages/@monomind/cli/dist/src/transfer/store/discovery.js +45 -3
  161. package/packages/@monomind/cli/dist/src/transfer/store/download.js +5 -0
  162. package/packages/@monomind/cli/dist/src/transfer/store/publish.js +13 -1
  163. package/packages/@monomind/cli/dist/src/transfer/store/registry.d.ts +8 -0
  164. package/packages/@monomind/cli/dist/src/transfer/store/registry.js +30 -5
  165. package/packages/@monomind/cli/dist/src/transfer/store/search.js +20 -5
  166. package/packages/@monomind/cli/dist/src/update/checker.js +59 -7
  167. package/packages/@monomind/cli/dist/src/update/executor.js +50 -3
  168. package/packages/@monomind/cli/dist/src/update/index.js +18 -1
  169. package/packages/@monomind/cli/dist/src/update/rate-limiter.d.ts +6 -0
  170. package/packages/@monomind/cli/dist/src/update/rate-limiter.js +79 -7
  171. package/packages/@monomind/cli/dist/src/update/validator.js +52 -1
  172. package/packages/@monomind/cli/package.json +2 -3
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * github.com/monoes/monomind
6
6
  */
7
- import { readFileSync } from 'fs';
7
+ import { readFileSync, statSync } from 'fs';
8
8
  import { fileURLToPath } from 'url';
9
9
  import { dirname, join } from 'path';
10
10
  import { commandParser } from './parser.js';
@@ -19,6 +19,9 @@ function getPackageVersion() {
19
19
  const __dirname = dirname(__filename);
20
20
  // Navigate from dist/src to package root
21
21
  const pkgPath = join(__dirname, '..', '..', 'package.json');
22
+ // Guard: skip if package.json is unexpectedly large (> 1 MB)
23
+ if (statSync(pkgPath).size > 1024 * 1024)
24
+ return '3.0.0';
22
25
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
23
26
  return pkg.version || '3.0.0';
24
27
  }
@@ -458,10 +461,11 @@ export class CLI {
458
461
  catch { /* optional — registry build failures must never block startup */ }
459
462
  // Task 04: CapabilityMetadata — validate agent registry at startup
460
463
  try {
461
- const { readFileSync, existsSync } = await import('fs');
464
+ const { readFileSync, existsSync, statSync: statSyncReg } = await import('fs');
462
465
  const { join: pathJoin } = await import('path');
463
466
  const registryPath = pathJoin(process.cwd(), '.monomind', 'registry.json');
464
- if (existsSync(registryPath)) {
467
+ const MAX_REGISTRY_BYTES = 10 * 1024 * 1024; // 10 MB
468
+ if (existsSync(registryPath) && statSyncReg(registryPath).size <= MAX_REGISTRY_BYTES) {
465
469
  const registry = JSON.parse(readFileSync(registryPath, 'utf-8'));
466
470
  const entries = registry.agents ?? [];
467
471
  const issues = [];
@@ -10,6 +10,7 @@ import { dirname } from 'path';
10
10
  // ESM-compatible __dirname
11
11
  const __filename = fileURLToPath(import.meta.url);
12
12
  const __dirname = dirname(__filename);
13
+ const MAX_EXEC_FILE_BYTES = 10 * 1024 * 1024; // 10 MB
13
14
  /**
14
15
  * Atomic write helper — writes to a sibling .tmp file then renames into place.
15
16
  * SIGINT or crash during a partial write would otherwise corrupt user-critical
@@ -205,7 +206,7 @@ function cleanupLegacyTools(targetDir) {
205
206
  }
206
207
  // Clean ruv-swarm from .mcp.json and fix old MCP package name
207
208
  const mcpJsonPath = path.join(targetDir, '.mcp.json');
208
- if (fs.existsSync(mcpJsonPath)) {
209
+ if (fs.existsSync(mcpJsonPath) && fs.statSync(mcpJsonPath).size <= MAX_EXEC_FILE_BYTES) {
209
210
  try {
210
211
  const mcp = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf-8'));
211
212
  let mcpChanged = false;
@@ -226,7 +227,7 @@ function cleanupLegacyTools(targetDir) {
226
227
  }
227
228
  // Clean ruv-swarm from .claude/settings.json hooks and fix MCP package name
228
229
  const settingsPath = path.join(targetDir, '.claude', 'settings.json');
229
- if (fs.existsSync(settingsPath)) {
230
+ if (fs.existsSync(settingsPath) && fs.statSync(settingsPath).size <= MAX_EXEC_FILE_BYTES) {
230
231
  try {
231
232
  const raw = fs.readFileSync(settingsPath, 'utf-8');
232
233
  const settings = JSON.parse(raw);
@@ -449,8 +450,8 @@ async function startDaemonBackground(targetDir, result) {
449
450
  const { execSync } = await import('child_process');
450
451
  // Check if daemon is already running
451
452
  const pidFile = path.join(targetDir, '.monomind', 'daemon.pid');
452
- const { existsSync, readFileSync } = await import('fs');
453
- if (existsSync(pidFile)) {
453
+ const { existsSync, readFileSync, statSync: statSyncPid } = await import('fs');
454
+ if (existsSync(pidFile) && statSyncPid(pidFile).size <= 1024) {
454
455
  const pid = parseInt(readFileSync(pidFile, 'utf8').trim(), 10);
455
456
  try {
456
457
  process.kill(pid, 0);
@@ -798,7 +799,7 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
798
799
  // 3. Merge settings if requested
799
800
  if (upgradeSettings) {
800
801
  const settingsPath = path.join(targetDir, '.claude', 'settings.json');
801
- if (fs.existsSync(settingsPath)) {
802
+ if (fs.existsSync(settingsPath) && fs.statSync(settingsPath).size <= MAX_EXEC_FILE_BYTES) {
802
803
  try {
803
804
  const existingSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
804
805
  const mergedSettings = mergeSettingsForUpgrade(existingSettings);
@@ -951,7 +952,7 @@ async function createDirectories(targetDir, options, result) {
951
952
  async function writeSettings(targetDir, options, result) {
952
953
  const settingsPath = path.join(targetDir, '.claude', 'settings.json');
953
954
  const generated = JSON.parse(generateSettingsJson(options));
954
- if (fs.existsSync(settingsPath) && !options.force) {
955
+ if (fs.existsSync(settingsPath) && !options.force && fs.statSync(settingsPath).size <= MAX_EXEC_FILE_BYTES) {
955
956
  // Merge hooks/env/permissions into existing settings instead of skipping
956
957
  try {
957
958
  const existing = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
@@ -1493,7 +1494,7 @@ daemon.pid
1493
1494
  // A blanket ignore prevents config, metrics, and knowledge graph from being committed.
1494
1495
  // We remove any bare `.monomind/` or `**/.monomind/` lines and add specific excludes instead.
1495
1496
  const projectGitignorePath = path.join(targetDir, '.gitignore');
1496
- if (fs.existsSync(projectGitignorePath)) {
1497
+ if (fs.existsSync(projectGitignorePath) && fs.statSync(projectGitignorePath).size <= MAX_EXEC_FILE_BYTES) {
1497
1498
  const existing = fs.readFileSync(projectGitignorePath, 'utf-8');
1498
1499
  const blanketPattern = /^(\*\*\/)?\.monomind\/?\s*$/gm;
1499
1500
  if (blanketPattern.test(existing)) {
@@ -2060,7 +2061,7 @@ async function writeClaudeMd(targetDir, options, result) {
2060
2061
  if (!fs.existsSync(globalClaudeDir)) {
2061
2062
  fs.mkdirSync(globalClaudeDir, { recursive: true });
2062
2063
  }
2063
- if (fs.existsSync(globalClaudeMd)) {
2064
+ if (fs.existsSync(globalClaudeMd) && fs.statSync(globalClaudeMd).size <= MAX_EXEC_FILE_BYTES) {
2064
2065
  const existing = fs.readFileSync(globalClaudeMd, 'utf-8');
2065
2066
  if (!existing.includes('Monomind Integration')) {
2066
2067
  fs.appendFileSync(globalClaudeMd, monomindBlock);
@@ -2082,7 +2083,7 @@ async function writeClaudeMd(targetDir, options, result) {
2082
2083
  fs.mkdirSync(globalClaudeDir, { recursive: true });
2083
2084
  }
2084
2085
  let globalSettings = {};
2085
- if (fs.existsSync(globalSettingsPath)) {
2086
+ if (fs.existsSync(globalSettingsPath) && fs.statSync(globalSettingsPath).size <= MAX_EXEC_FILE_BYTES) {
2086
2087
  try {
2087
2088
  globalSettings = JSON.parse(fs.readFileSync(globalSettingsPath, 'utf-8'));
2088
2089
  }
@@ -2227,7 +2228,9 @@ function _registerMonomindProject(dir) {
2227
2228
  const registryPath = path.join(os.homedir(), '.monomind-projects.json');
2228
2229
  let reg = { projects: [] };
2229
2230
  try {
2230
- reg = JSON.parse(fs.readFileSync(registryPath, 'utf-8'));
2231
+ if (fs.existsSync(registryPath) && fs.statSync(registryPath).size <= MAX_EXEC_FILE_BYTES) {
2232
+ reg = JSON.parse(fs.readFileSync(registryPath, 'utf-8'));
2233
+ }
2231
2234
  }
2232
2235
  catch { }
2233
2236
  if (!Array.isArray(reg.projects))
@@ -2260,7 +2263,7 @@ export function findMonomindProjects(maxDepth = 3) {
2260
2263
  ].filter(r => fs.existsSync(r));
2261
2264
  // Also check known-projects registry if it exists
2262
2265
  const registryPath = path.join(home, '.monomind-projects.json');
2263
- if (fs.existsSync(registryPath)) {
2266
+ if (fs.existsSync(registryPath) && fs.statSync(registryPath).size <= MAX_EXEC_FILE_BYTES) {
2264
2267
  try {
2265
2268
  const reg = JSON.parse(fs.readFileSync(registryPath, 'utf-8'));
2266
2269
  if (Array.isArray(reg.projects)) {
@@ -8,8 +8,12 @@
8
8
  import * as fs from 'node:fs';
9
9
  import * as path from 'node:path';
10
10
  import { execFileSync } from 'node:child_process';
11
+ const MAX_JSON_READ_BYTES = 10 * 1024 * 1024; // 10 MB
12
+ const MAX_TEXT_READ_BYTES = 2 * 1024 * 1024; // 2 MB for plain-text config files
11
13
  function readJSON(p) {
12
14
  try {
15
+ if (!fs.existsSync(p) || fs.statSync(p).size > MAX_JSON_READ_BYTES)
16
+ return null;
13
17
  return JSON.parse(fs.readFileSync(p, 'utf-8'));
14
18
  }
15
19
  catch {
@@ -166,7 +170,10 @@ export function detectProjectProfile(cwd) {
166
170
  profile.language = 'rust';
167
171
  profile.packageManager = 'cargo';
168
172
  try {
169
- const cargo = fs.readFileSync(path.join(cwd, 'Cargo.toml'), 'utf-8');
173
+ const cargoPath = path.join(cwd, 'Cargo.toml');
174
+ if (fs.statSync(cargoPath).size > MAX_TEXT_READ_BYTES)
175
+ throw new Error('too large');
176
+ const cargo = fs.readFileSync(cargoPath, 'utf-8');
170
177
  const nameM = cargo.match(/^name\s*=\s*"([^"]+)"/m);
171
178
  if (nameM)
172
179
  profile.name = nameM[1];
@@ -191,7 +198,10 @@ export function detectProjectProfile(cwd) {
191
198
  else
192
199
  profile.packageManager = 'pip';
193
200
  try {
194
- const pp = fs.readFileSync(path.join(cwd, 'pyproject.toml'), 'utf-8');
201
+ const ppPath = path.join(cwd, 'pyproject.toml');
202
+ if (fs.existsSync(ppPath) && fs.statSync(ppPath).size > MAX_TEXT_READ_BYTES)
203
+ throw new Error('too large');
204
+ const pp = fs.readFileSync(ppPath, 'utf-8');
195
205
  if (pp.includes('fastapi') || pp.includes('FastAPI'))
196
206
  profile.framework.push('fastapi');
197
207
  if (pp.includes('django'))
@@ -210,7 +220,10 @@ export function detectProjectProfile(cwd) {
210
220
  profile.language = 'go';
211
221
  profile.packageManager = 'unknown'; // go mod doesn't have a separate PM
212
222
  try {
213
- const gomod = fs.readFileSync(path.join(cwd, 'go.mod'), 'utf-8');
223
+ const gomodPath = path.join(cwd, 'go.mod');
224
+ if (fs.statSync(gomodPath).size > MAX_TEXT_READ_BYTES)
225
+ throw new Error('too large');
226
+ const gomod = fs.readFileSync(gomodPath, 'utf-8');
214
227
  if (gomod.includes('gin-gonic/gin'))
215
228
  profile.framework.push('gin');
216
229
  if (gomod.includes('labstack/echo'))
@@ -236,7 +249,10 @@ export function detectProjectProfile(cwd) {
236
249
  }
237
250
  // ── CLAUDE.md conventions extraction ──────────────────────────────────────
238
251
  try {
239
- const claudeMd = fs.readFileSync(path.join(cwd, 'CLAUDE.md'), 'utf-8');
252
+ const claudeMdPath = path.join(cwd, 'CLAUDE.md');
253
+ if (fs.existsSync(claudeMdPath) && fs.statSync(claudeMdPath).size > MAX_TEXT_READ_BYTES)
254
+ throw new Error('too large');
255
+ const claudeMd = fs.readFileSync(claudeMdPath, 'utf-8');
240
256
  profile.maxFileLines = extractMaxFileLines(claudeMd);
241
257
  }
242
258
  catch { /* skip */ }
@@ -62,6 +62,8 @@ function getVersion() {
62
62
  ];
63
63
  for (const p of walkCandidates) {
64
64
  try {
65
+ const pkgStat = safeStat(p);
66
+ if (!pkgStat || pkgStat.size > 1024 * 1024) continue;
65
67
  const pkg = JSON.parse(fs.readFileSync(p, 'utf-8'));
66
68
  if (pkg.version && (pkg.name === 'monomind' || pkg.name === '@monomind/cli' || (pkg.name || '').startsWith('@monomind'))) {
67
69
  return \`v\${pkg.version}\`;
@@ -72,7 +74,10 @@ function getVersion() {
72
74
  try {
73
75
  const { execSync } = require('child_process');
74
76
  const prefix = execSync('npm config get prefix', { encoding: 'utf-8', timeout: 2000 }).trim();
75
- const pkg = JSON.parse(fs.readFileSync(path.join(prefix, 'lib', 'node_modules', 'monomind', 'package.json'), 'utf-8'));
77
+ const globalPkgPath = path.join(prefix, 'lib', 'node_modules', 'monomind', 'package.json');
78
+ const globalPkgStat = safeStat(globalPkgPath);
79
+ if (!globalPkgStat || globalPkgStat.size > 1024 * 1024) throw new Error('too large');
80
+ const pkg = JSON.parse(fs.readFileSync(globalPkgPath, 'utf-8'));
76
81
  if (pkg.version) return \`v\${pkg.version}\`;
77
82
  } catch { /* ignore */ }
78
83
  return 'v1.0.6';
@@ -113,9 +118,11 @@ function safeExec(cmd, timeoutMs = 2000) {
113
118
  }
114
119
 
115
120
  // Safe JSON file reader (returns null on failure)
121
+ // Refuses to load files > 10 MB to prevent OOM on corrupted/oversized stores.
122
+ const MAX_JSON_READ_BYTES = 10 * 1024 * 1024;
116
123
  function readJSON(filePath) {
117
124
  try {
118
- if (fs.existsSync(filePath)) {
125
+ if (fs.existsSync(filePath) && fs.statSync(filePath).size <= MAX_JSON_READ_BYTES) {
119
126
  return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
120
127
  }
121
128
  } catch { /* ignore */ }
@@ -227,6 +234,8 @@ function getModelFromSessionJSONL() {
227
234
  if (files.length === 0) return null;
228
235
 
229
236
  const sessionFile = path.join(projectsDir, files[0].f);
237
+ const sessionStat = safeStat(sessionFile);
238
+ if (!sessionStat || sessionStat.size > 50 * 1024 * 1024) return null; // skip > 50 MB
230
239
  const raw = fs.readFileSync(sessionFile, 'utf-8');
231
240
  const lines = raw.split('\\n').filter(Boolean);
232
241
 
@@ -557,7 +566,8 @@ function getHooksStatus() {
557
566
  function getActiveAgent() {
558
567
  const routeFile = path.join(CWD, '.monomind', 'last-route.json');
559
568
  try {
560
- if (!fs.existsSync(routeFile)) return null;
569
+ const routeStat = safeStat(routeFile);
570
+ if (!routeStat || routeStat.size > MAX_JSON_READ_BYTES) return null;
561
571
  const data = JSON.parse(fs.readFileSync(routeFile, 'utf-8'));
562
572
  if (!data || !data.agent) return null;
563
573
 
@@ -590,7 +600,7 @@ function getAgentDBStats() {
590
600
  // 1. Count real entries from auto-memory-store.json
591
601
  const storePath = path.join(CWD, '.monomind', 'data', 'auto-memory-store.json');
592
602
  const storeStat = safeStat(storePath);
593
- if (storeStat) {
603
+ if (storeStat && storeStat.size <= MAX_JSON_READ_BYTES) {
594
604
  dbSizeKB += storeStat.size / 1024;
595
605
  try {
596
606
  const store = JSON.parse(fs.readFileSync(storePath, 'utf-8'));
@@ -804,12 +814,14 @@ function getKnowledgeStats() {
804
814
  const skillsPath = path.join(CWD, '.monomind', 'skills.jsonl');
805
815
  let chunks = 0, skills = 0;
806
816
  try {
807
- if (fs.existsSync(chunksPath)) {
817
+ const chunksStat = safeStat(chunksPath);
818
+ if (chunksStat && chunksStat.size <= MAX_JSON_READ_BYTES) {
808
819
  chunks = fs.readFileSync(chunksPath, 'utf-8').split('\\n').filter(Boolean).length;
809
820
  }
810
821
  } catch { /* ignore */ }
811
822
  try {
812
- if (fs.existsSync(skillsPath)) {
823
+ const skillsStat = safeStat(skillsPath);
824
+ if (skillsStat && skillsStat.size <= MAX_JSON_READ_BYTES) {
813
825
  skills = fs.readFileSync(skillsPath, 'utf-8').split('\\n').filter(Boolean).length;
814
826
  }
815
827
  } catch { /* ignore */ }
@@ -819,7 +831,8 @@ function getKnowledgeStats() {
819
831
  function getTriggerStats() {
820
832
  const indexPath = path.join(CWD, '.monomind', 'trigger-index.json');
821
833
  try {
822
- if (!fs.existsSync(indexPath)) return { triggers: 0, agents: 0 };
834
+ const idxStat = safeStat(indexPath);
835
+ if (!idxStat || idxStat.size > MAX_JSON_READ_BYTES) return { triggers: 0, agents: 0 };
823
836
  const raw = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
824
837
  const idx = raw.index || raw;
825
838
  const triggers = Object.keys(idx).length;
@@ -832,7 +845,8 @@ function getTriggerStats() {
832
845
  function getHookLatency() {
833
846
  const p = path.join(CWD, '.monomind', 'metrics', 'hook-latency.json');
834
847
  try {
835
- if (!fs.existsSync(p)) return null;
848
+ const latStat = safeStat(p);
849
+ if (!latStat || latStat.size > MAX_JSON_READ_BYTES) return null;
836
850
  const d = JSON.parse(fs.readFileSync(p, 'utf-8'));
837
851
  const perPrompt = ['route'];
838
852
  let totalMs = 0; let count = 0;
@@ -854,7 +868,8 @@ function getHookLatency() {
854
868
  function getGraphUsage() {
855
869
  const usagePath = path.join(CWD, '.monomind', 'metrics', 'graph-usage.json');
856
870
  try {
857
- if (!fs.existsSync(usagePath)) return null;
871
+ const usageStat = safeStat(usagePath);
872
+ if (!usageStat || usageStat.size > MAX_JSON_READ_BYTES) return null;
858
873
  const d = JSON.parse(fs.readFileSync(usagePath, 'utf-8'));
859
874
  const graphWins = (d.monograph_call || 0) + (d.preresolve_hit || 0)
860
875
  + (d.graph_assist_search || 0) + (d.graph_assist_neighbors || 0);
@@ -919,7 +934,10 @@ function getHILPending() {
919
934
  const files = fs.readdirSync(loopsDir).filter(f => f.endsWith('-hil.md'));
920
935
  for (const f of files) {
921
936
  try {
922
- const txt = fs.readFileSync(path.join(loopsDir, f), 'utf-8');
937
+ const hilPath = path.join(loopsDir, f);
938
+ const hilStat = safeStat(hilPath);
939
+ if (!hilStat || hilStat.size > 512 * 1024) continue; // skip > 512 KB
940
+ const txt = fs.readFileSync(hilPath, 'utf-8');
923
941
  const answered = /^[ \\t]*>[ \\t]+\\S/m.test(txt);
924
942
  if (!answered) pending++;
925
943
  } catch { /* ignore */ }
@@ -969,7 +987,9 @@ function getSIBudget() {
969
987
  const SI_LIMIT = 1500;
970
988
  const siPath = path.join(CWD, '.agents', 'shared_instructions.md');
971
989
  try {
972
- if (!fs.existsSync(siPath)) return null;
990
+ const siStat = safeStat(siPath);
991
+ if (!siStat) return null;
992
+ if (siStat.size > 2 * 1024 * 1024) return null; // skip > 2 MB
973
993
  const len = fs.readFileSync(siPath, 'utf-8').length;
974
994
  return { len, pct: Math.round((len / SI_LIMIT) * 100), limit: SI_LIMIT };
975
995
  } catch { return null; }
@@ -1149,10 +1169,10 @@ function generateDashboard() {
1149
1169
 
1150
1170
  const hil = getHILPending();
1151
1171
  const hilStr = hil.pending > 0
1152
- ? \`\${x.coral}✨ \${x.bold}\${hil.pending}\${x.reset}\${x.coral} HIL pending\${x.reset}\`
1153
- : \`\${x.slate}✨ no pending HIL\${x.reset}\`;
1172
+ ? \` \${DIV} \${x.coral}✨ \${x.bold}\${hil.pending}\${x.reset}\${x.coral} HIL pending\${x.reset}\`
1173
+ : \`\`;
1154
1174
 
1155
- lines.push(\`\${x.teal}🧠 CONTEXT\${x.reset} \${graphStr} \${DIV} \${hilStr}\`);
1175
+ lines.push(\`\${x.teal}🧠 CONTEXT\${x.reset} \${graphStr}\${hilStr}\`);
1156
1176
 
1157
1177
  return lines.join('\\n');
1158
1178
  }
@@ -1180,7 +1200,8 @@ const MODE_FILE = path.join(CWD, '.monomind', 'statusline-mode.txt');
1180
1200
 
1181
1201
  function readMode() {
1182
1202
  try {
1183
- if (fs.existsSync(MODE_FILE)) {
1203
+ const modeStat = safeStat(MODE_FILE);
1204
+ if (modeStat && modeStat.size <= 1024) {
1184
1205
  return fs.readFileSync(MODE_FILE, 'utf-8').trim();
1185
1206
  }
1186
1207
  } catch { /* ignore */ }
@@ -9,6 +9,57 @@
9
9
  *
10
10
  * @module v1/cli/mcp-tools/a2a-tools
11
11
  */
12
+ // ===== Input validation helpers =====
13
+ const MAX_AGENT_TYPE_LEN = 64; // slug length — catalogue keys are all < 30 chars
14
+ const MAX_BASE_URL_LEN = 2048; // typical browser URL limit
15
+ const MAX_TASK_ID_LEN = 256;
16
+ const MAX_SESSION_ID_LEN = 256;
17
+ // Allowlist of URL schemes that are safe to embed in returned Agent Card urls.
18
+ // Prevents javascript:, data:, file:// etc. from being reflected back to callers.
19
+ const ALLOWED_URL_SCHEMES = new Set(['http:', 'https:']);
20
+ /**
21
+ * Validate and return an agent type slug.
22
+ * Returns null when the value is not a string or exceeds the max length.
23
+ */
24
+ function validateAgentType(value) {
25
+ if (typeof value !== 'string' || value.length === 0 || value.length > MAX_AGENT_TYPE_LEN)
26
+ return null;
27
+ return value;
28
+ }
29
+ /**
30
+ * Validate and return a base URL.
31
+ * Accepts only http/https schemes and caps length to prevent oversized reflected values.
32
+ * Falls back to the default localhost URL on failure.
33
+ */
34
+ function validateBaseUrl(value, fallback = 'http://localhost:3000') {
35
+ if (typeof value !== 'string' || value.length === 0)
36
+ return fallback;
37
+ if (value.length > MAX_BASE_URL_LEN)
38
+ return fallback;
39
+ try {
40
+ const parsed = new URL(value);
41
+ if (!ALLOWED_URL_SCHEMES.has(parsed.protocol))
42
+ return fallback;
43
+ // Reject URLs with credentials — they should never appear in Agent Card endpoints
44
+ if (parsed.username || parsed.password)
45
+ return fallback;
46
+ }
47
+ catch {
48
+ return fallback;
49
+ }
50
+ return value;
51
+ }
52
+ /**
53
+ * Validate an optional string ID (taskId / sessionId).
54
+ * Returns undefined when the value is absent; null when it is present but invalid.
55
+ */
56
+ function validateOptionalId(value, maxLen) {
57
+ if (value === undefined || value === null)
58
+ return undefined;
59
+ if (typeof value !== 'string' || value.length === 0 || value.length > maxLen)
60
+ return null;
61
+ return value;
62
+ }
12
63
  // ===== Agent type catalogue =====
13
64
  /** Well-known agent types and their A2A card metadata */
14
65
  const AGENT_CARD_CATALOGUE = {
@@ -116,13 +167,16 @@ export const a2aTools = [
116
167
  required: ['agentType'],
117
168
  },
118
169
  handler: async (input) => {
119
- const agentType = input.agentType;
120
- const baseUrl = input.baseUrl || 'http://localhost:3000';
170
+ const agentType = validateAgentType(input.agentType);
171
+ if (!agentType) {
172
+ return { success: false, error: 'agentType is required (non-empty string, max 64 chars)' };
173
+ }
174
+ const baseUrl = validateBaseUrl(input.baseUrl);
121
175
  const card = buildAgentCard(agentType, baseUrl);
122
176
  if (!card) {
123
177
  return {
124
178
  success: false,
125
- error: `Unknown agent type: "${agentType}". Available: ${Object.keys(AGENT_CARD_CATALOGUE).join(', ')}`,
179
+ error: `Unknown agent type. Available: ${Object.keys(AGENT_CARD_CATALOGUE).join(', ')}`,
126
180
  };
127
181
  }
128
182
  return { success: true, agentCard: card, protocol: 'a2a', source: 'https://a2a-protocol.org' };
@@ -146,8 +200,19 @@ export const a2aTools = [
146
200
  },
147
201
  },
148
202
  handler: async (input) => {
149
- const baseUrl = input.baseUrl || 'http://localhost:3000';
150
- const capabilityFilter = input.capabilities;
203
+ const baseUrl = validateBaseUrl(input.baseUrl);
204
+ // Only accept a plain object capability filter to prevent prototype pollution.
205
+ // Object.create(null) maps and class instances are intentionally rejected.
206
+ const rawCap = input.capabilities;
207
+ const capabilityFilter = rawCap !== null &&
208
+ typeof rawCap === 'object' &&
209
+ !Array.isArray(rawCap) &&
210
+ Object.getPrototypeOf(rawCap) === Object.prototype
211
+ ? rawCap
212
+ : undefined;
213
+ const KNOWN_CAPABILITY_KEYS = new Set([
214
+ 'streaming', 'pushNotifications', 'stateTransitionHistory',
215
+ ]);
151
216
  const cards = Object.keys(AGENT_CARD_CATALOGUE)
152
217
  .map(type => buildAgentCard(type, baseUrl))
153
218
  .filter((card) => {
@@ -155,9 +220,12 @@ export const a2aTools = [
155
220
  return false;
156
221
  if (!capabilityFilter)
157
222
  return true;
158
- // Apply capability filter
159
- for (const [key, value] of Object.entries(capabilityFilter)) {
160
- if (card.capabilities[key] !== value)
223
+ // Apply capability filter — only iterate known keys to prevent
224
+ // prototype-chain access via attacker-controlled property names.
225
+ for (const key of KNOWN_CAPABILITY_KEYS) {
226
+ if (!(key in capabilityFilter))
227
+ continue;
228
+ if (card.capabilities[key] !== capabilityFilter[key])
161
229
  return false;
162
230
  }
163
231
  return true;
@@ -203,15 +271,32 @@ export const a2aTools = [
203
271
  required: ['agentType', 'message'],
204
272
  },
205
273
  handler: async (input) => {
206
- const agentType = input.agentType;
274
+ const agentType = validateAgentType(input.agentType);
275
+ if (!agentType) {
276
+ return { success: false, error: 'agentType is required (non-empty string, max 64 chars)' };
277
+ }
207
278
  const message = input.message;
208
- const taskId = input.taskId || `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
209
- const sessionId = input.sessionId;
210
- const metadata = input.metadata || {};
279
+ const rawTaskId = validateOptionalId(input.taskId, MAX_TASK_ID_LEN);
280
+ if (rawTaskId === null) {
281
+ return { success: false, error: `taskId must be a non-empty string up to ${MAX_TASK_ID_LEN} chars` };
282
+ }
283
+ const taskId = rawTaskId ?? `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
284
+ const rawSessionId = validateOptionalId(input.sessionId, MAX_SESSION_ID_LEN);
285
+ if (rawSessionId === null) {
286
+ return { success: false, error: `sessionId must be a non-empty string up to ${MAX_SESSION_ID_LEN} chars` };
287
+ }
288
+ const sessionId = rawSessionId;
289
+ // Only accept plain objects for metadata to prevent prototype pollution
290
+ const metadata = input.metadata !== null &&
291
+ typeof input.metadata === 'object' &&
292
+ !Array.isArray(input.metadata) &&
293
+ Object.getPrototypeOf(input.metadata) === Object.prototype
294
+ ? input.metadata
295
+ : {};
211
296
  if (!AGENT_CARD_CATALOGUE[agentType]) {
212
297
  return {
213
298
  success: false,
214
- error: `Unknown agent type: "${agentType}". Run a2a_discover to see available agents.`,
299
+ error: `Unknown agent type. Run a2a_discover to see available agents.`,
215
300
  };
216
301
  }
217
302
  // Return the A2A-compliant task submission envelope
@@ -122,8 +122,18 @@ export const agentTools = [
122
122
  },
123
123
  handler: async (input) => {
124
124
  const store = loadAgentStore();
125
- const agentId = input.agentId || `agent-${Date.now()}-${randomBytes(4).toString('hex')}`;
126
- const agentType = input.agentType;
125
+ // Cap agentId: used as the JSON object key in store.agents[agentId].
126
+ // An oversized key inflates the on-disk store for every spawned agent.
127
+ // Cap agentType/domain: persisted as AgentRecord field values.
128
+ const MAX_AGENT_ID_LEN = 256;
129
+ const MAX_AGENT_TYPE_LEN = 128;
130
+ const MAX_AGENT_DOMAIN_LEN = 256;
131
+ const rawAgentId = input.agentId || `agent-${Date.now()}-${randomBytes(4).toString('hex')}`;
132
+ const agentId = typeof rawAgentId === 'string' && rawAgentId.length > MAX_AGENT_ID_LEN
133
+ ? rawAgentId.slice(0, MAX_AGENT_ID_LEN) : rawAgentId;
134
+ const rawAgentType = input.agentType;
135
+ const agentType = typeof rawAgentType === 'string' && rawAgentType.length > MAX_AGENT_TYPE_LEN
136
+ ? rawAgentType.slice(0, MAX_AGENT_TYPE_LEN) : rawAgentType;
127
137
  if (['__proto__', 'constructor', 'prototype'].includes(agentId)) {
128
138
  return { success: false, agentId, error: 'Forbidden agent ID' };
129
139
  }
@@ -141,6 +151,9 @@ export const agentTools = [
141
151
  }
142
152
  // Get task from either top-level or config (CLI passes it in config.task)
143
153
  const task = input.task || config.task || undefined;
154
+ const rawDomain = input.domain;
155
+ const domain = typeof rawDomain === 'string' && rawDomain.length > MAX_AGENT_DOMAIN_LEN
156
+ ? rawDomain.slice(0, MAX_AGENT_DOMAIN_LEN) : rawDomain;
144
157
  // Determine model using ADR-026 3-tier routing logic
145
158
  const routingResult = await determineAgentModel(agentType, config, task);
146
159
  const agent = {
@@ -151,7 +164,7 @@ export const agentTools = [
151
164
  taskCount: 0,
152
165
  config,
153
166
  createdAt: new Date().toISOString(),
154
- domain: input.domain,
167
+ domain,
155
168
  model: routingResult.model,
156
169
  modelRoutedBy: routingResult.routedBy,
157
170
  };