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
@@ -145,7 +145,12 @@ export class EWCConsolidator {
145
145
  for (const newPattern of newPatterns) {
146
146
  if (!newPattern.embedding || newPattern.embedding.length === 0)
147
147
  continue;
148
- const existingPattern = this.patterns.get(newPattern.id);
148
+ // Cap pattern ID length: unbounded IDs fill both the Map key and the
149
+ // modifiedPatterns/protectedPatterns result arrays without any limit.
150
+ const patternId = typeof newPattern.id === 'string'
151
+ ? newPattern.id.slice(0, 256)
152
+ : String(newPattern.id).slice(0, 256);
153
+ const existingPattern = this.patterns.get(patternId);
149
154
  if (existingPattern) {
150
155
  // Calculate EWC penalty for updating existing pattern
151
156
  const penalty = this.getPenalty(existingPattern.weights, newPattern.embedding, fisher);
@@ -153,19 +158,19 @@ export class EWCConsolidator {
153
158
  const importanceScore = this.calculateImportance(existingPattern);
154
159
  if (importanceScore > this.config.importanceThreshold && penalty > this.config.lambda) {
155
160
  // Protect high-importance patterns with high penalty
156
- result.protectedPatterns.push(newPattern.id);
161
+ result.protectedPatterns.push(patternId);
157
162
  // Apply constrained update: blend old and new based on importance
158
163
  const blendFactor = 1 - importanceScore;
159
164
  const blendedWeights = this.blendWeights(existingPattern.weights, newPattern.embedding, blendFactor, fisher);
160
165
  existingPattern.weights = blendedWeights;
161
166
  existingPattern.lastUpdated = Date.now();
162
- result.modifiedPatterns.push(newPattern.id);
167
+ result.modifiedPatterns.push(patternId);
163
168
  }
164
169
  else {
165
170
  // Low importance or low penalty: allow full update
166
171
  existingPattern.weights = newPattern.embedding.slice(0, this.config.dimensions);
167
172
  existingPattern.lastUpdated = Date.now();
168
- result.modifiedPatterns.push(newPattern.id);
173
+ result.modifiedPatterns.push(patternId);
169
174
  }
170
175
  // Update Fisher diagonal for this pattern
171
176
  existingPattern.fisherDiagonal = fisher;
@@ -174,7 +179,7 @@ export class EWCConsolidator {
174
179
  else {
175
180
  // New pattern: add directly
176
181
  const weights = {
177
- id: newPattern.id,
182
+ id: patternId,
178
183
  weights: newPattern.embedding.slice(0, this.config.dimensions),
179
184
  fisherDiagonal: fisher,
180
185
  importance: 0.5,
@@ -184,8 +189,8 @@ export class EWCConsolidator {
184
189
  type: newPattern.type,
185
190
  description: newPattern.description
186
191
  };
187
- this.patterns.set(newPattern.id, weights);
188
- result.modifiedPatterns.push(newPattern.id);
192
+ this.patterns.set(patternId, weights);
193
+ result.modifiedPatterns.push(patternId);
189
194
  }
190
195
  result.patternsConsolidated++;
191
196
  }
@@ -209,7 +214,10 @@ export class EWCConsolidator {
209
214
  return result;
210
215
  }
211
216
  catch (error) {
212
- result.error = error instanceof Error ? error.message : String(error);
217
+ // Sanitize: strip filesystem paths and cap length so internal error
218
+ // messages are not reflected verbatim into CallerResult.error.
219
+ const rawMsg = error instanceof Error ? error.message : String(error);
220
+ result.error = rawMsg.replace(/\/[^\s:]+(\/|(?=\s|:|$))/g, '<path>/').slice(0, 500);
213
221
  result.duration = performance.now() - startTime;
214
222
  return result;
215
223
  }
@@ -513,6 +521,10 @@ export class EWCConsolidator {
513
521
  if (!fs.existsSync(this.config.storagePath)) {
514
522
  throw new Error('No persisted state found');
515
523
  }
524
+ const fileSize = fs.statSync(this.config.storagePath).size;
525
+ if (fileSize > 50 * 1024 * 1024) {
526
+ throw new Error(`EWC state file too large (${fileSize} bytes); refusing to load`);
527
+ }
516
528
  const content = fs.readFileSync(this.config.storagePath, 'utf-8');
517
529
  const state = JSON.parse(content);
518
530
  // Validate version
@@ -551,8 +563,12 @@ export class EWCConsolidator {
551
563
  this.patterns.set(id, pattern);
552
564
  }
553
565
  }
554
- // Restore history
555
- this.consolidationHistory = Array.isArray(state.consolidationHistory) ? state.consolidationHistory : [];
566
+ // Restore history — cap to last 100 entries so a crafted state file cannot
567
+ // bloat the in-memory array (each entry is a small object, but the array is
568
+ // summed on every getConsolidationStats() call which is O(n)).
569
+ this.consolidationHistory = Array.isArray(state.consolidationHistory)
570
+ ? state.consolidationHistory.slice(-100)
571
+ : [];
556
572
  // Update config from persisted values, clamped to a sensible range to
557
573
  // prevent negative/NaN lambda from inverting the regularization sign.
558
574
  if (state.config && typeof state.config.lambda === 'number'
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * @module v1/cli/intelligence
12
12
  */
13
- import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
13
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, renameSync } from 'node:fs';
14
14
  import { homedir } from 'node:os';
15
15
  import { join } from 'node:path';
16
16
  // ============================================================================
@@ -332,7 +332,7 @@ class LocalReasoningBank {
332
332
  loadFromDisk() {
333
333
  try {
334
334
  const path = getPatternsPath();
335
- if (existsSync(path)) {
335
+ if (existsSync(path) && statSync(path).size <= 50 * 1024 * 1024) {
336
336
  const data = JSON.parse(readFileSync(path, 'utf-8'));
337
337
  if (Array.isArray(data)) {
338
338
  // Validate each persisted pattern. The patterns file is part of the
@@ -393,9 +393,10 @@ class LocalReasoningBank {
393
393
  renameSync(tmp, path);
394
394
  this.dirty = false;
395
395
  }
396
- catch (error) {
396
+ catch {
397
397
  // Log but don't throw - persistence failures shouldn't break training
398
- console.error('Failed to persist patterns:', error);
398
+ // Do not reflect raw error to avoid leaking internal paths
399
+ console.error('Failed to persist patterns');
399
400
  }
400
401
  }
401
402
  /**
@@ -548,7 +549,7 @@ let globalStats = {
548
549
  function loadPersistedStats() {
549
550
  try {
550
551
  const path = getStatsPath();
551
- if (existsSync(path)) {
552
+ if (existsSync(path) && statSync(path).size <= 10 * 1024 * 1024) {
552
553
  const data = JSON.parse(readFileSync(path, 'utf-8'));
553
554
  if (data && typeof data === 'object') {
554
555
  globalStats.trajectoriesRecorded = data.trajectoriesRecorded ?? 0;
@@ -599,7 +600,7 @@ async function _doInitializeIntelligence(config) {
599
600
  // Seed neural learned patterns from @monomind/neural's LearningBridge flush.
600
601
  // This is the A→B bridge reader: connects the automatic learning loop to routing.
601
602
  const neuralPatternsPath = join(getDataDir(), 'patterns.json');
602
- if (existsSync(neuralPatternsPath)) {
603
+ if (existsSync(neuralPatternsPath) && statSync(neuralPatternsPath).size <= 50 * 1024 * 1024) {
603
604
  try {
604
605
  const { generateEmbedding: genEmb } = await import('./memory-initializer.js').catch(() => ({ generateEmbedding: null }));
605
606
  const raw = readFileSync(neuralPatternsPath, 'utf-8');
@@ -804,6 +805,10 @@ export async function recordTrajectory(steps, verdict) {
804
805
  }
805
806
  }
806
807
  export async function findSimilarPatterns(query, options) {
808
+ // Cap query length to prevent OOM via embedding generation on unbounded input
809
+ if (typeof query === 'string' && query.length > 2000) {
810
+ query = query.slice(0, 2000);
811
+ }
807
812
  if (!reasoningBank) {
808
813
  const init = await initializeIntelligence();
809
814
  if (!init.success)
@@ -943,9 +948,11 @@ export function benchmarkAdaptation(iterations = 1000) {
943
948
  if (!sonaCoordinator) {
944
949
  return { totalMs: 0, avgMs: 0, minMs: 0, maxMs: 0, targetMet: false };
945
950
  }
951
+ // Cap iterations to prevent OOM/CPU exhaustion from unbounded caller input
952
+ const safeIterations = Math.min(Math.max(1, iterations >>> 0), 100_000);
946
953
  const times = [];
947
954
  const testEmbedding = Array.from({ length: 384 }, () => Math.random());
948
- for (let i = 0; i < iterations; i++) {
955
+ for (let i = 0; i < safeIterations; i++) {
949
956
  const start = performance.now();
950
957
  sonaCoordinator.recordSignal({
951
958
  type: 'test',
@@ -956,7 +963,7 @@ export function benchmarkAdaptation(iterations = 1000) {
956
963
  times.push(performance.now() - start);
957
964
  }
958
965
  const totalMs = times.reduce((a, b) => a + b, 0);
959
- const avgMs = totalMs / iterations;
966
+ const avgMs = totalMs / safeIterations;
960
967
  const minMs = Math.min(...times);
961
968
  const maxMs = Math.max(...times);
962
969
  return {
@@ -980,21 +987,75 @@ function loadSonaRoutingPatterns() {
980
987
  const sonaPath = join(process.cwd(), '.swarm', 'sona-patterns.json');
981
988
  if (!existsSync(sonaPath))
982
989
  return [];
990
+ if (statSync(sonaPath).size > 10 * 1024 * 1024)
991
+ return [];
983
992
  const raw = JSON.parse(readFileSync(sonaPath, 'utf-8'));
984
993
  const persisted = raw;
985
- if (!persisted.patterns || typeof persisted.patterns !== 'object')
994
+ if (!persisted.patterns || typeof persisted.patterns !== 'object' || Array.isArray(persisted.patterns))
986
995
  return [];
987
996
  const now = Date.now();
988
- return Object.entries(persisted.patterns).map(([key, p]) => ({
989
- id: `sona:${key}`,
990
- type: p.agent ?? 'routing',
991
- content: (p.keywords ?? [key]).join(' '),
992
- confidence: p.confidence ?? 0.5,
993
- usageCount: (p.successCount ?? 0) + (p.failureCount ?? 0),
994
- embedding: [],
995
- createdAt: p.createdAt ?? now,
996
- lastUsedAt: now,
997
- }));
997
+ const results = [];
998
+ // Cap total entries to prevent DoS via an unbounded patterns map.
999
+ // sona-patterns.json is written by the SONA optimizer but could be
1000
+ // replaced by a malicious IPFS bundle — validate every field before use.
1001
+ const MAX_SONA_ENTRIES = 500;
1002
+ let entryCount = 0;
1003
+ for (const [key, p] of Object.entries(persisted.patterns)) {
1004
+ // Prototype pollution guard — skip __proto__ / constructor / prototype keys.
1005
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype')
1006
+ continue;
1007
+ if (typeof key !== 'string' || key.length === 0 || key.length > 256)
1008
+ continue;
1009
+ if (entryCount++ >= MAX_SONA_ENTRIES)
1010
+ break;
1011
+ if (!p || typeof p !== 'object')
1012
+ continue;
1013
+ // Validate keywords: must be a bounded array of short strings.
1014
+ const rawKw = p.keywords;
1015
+ let keywords;
1016
+ if (rawKw === undefined) {
1017
+ keywords = [key];
1018
+ }
1019
+ else if (Array.isArray(rawKw) && rawKw.length <= 64
1020
+ && rawKw.every(k => typeof k === 'string' && k.length > 0 && k.length <= 128)) {
1021
+ keywords = rawKw;
1022
+ }
1023
+ else {
1024
+ continue; // malformed keywords — skip entry
1025
+ }
1026
+ // Validate agent string.
1027
+ const agent = p.agent;
1028
+ if (agent !== undefined && (typeof agent !== 'string' || agent.length === 0 || agent.length > 128))
1029
+ continue;
1030
+ // Validate confidence is a finite number in [0, 1].
1031
+ const rawConf = p.confidence;
1032
+ const confidence = rawConf !== undefined ? rawConf : 0.5;
1033
+ if (typeof confidence !== 'number' || !Number.isFinite(confidence) || confidence < 0 || confidence > 1)
1034
+ continue;
1035
+ // Validate usage counts are safe integers.
1036
+ const sc = p.successCount ?? 0;
1037
+ const fc = p.failureCount ?? 0;
1038
+ if (typeof sc !== 'number' || !Number.isFinite(sc) || sc < 0 || sc > 1e9)
1039
+ continue;
1040
+ if (typeof fc !== 'number' || !Number.isFinite(fc) || fc < 0 || fc > 1e9)
1041
+ continue;
1042
+ // Validate createdAt is a reasonable epoch ms value.
1043
+ const rawTs = p.createdAt;
1044
+ const createdAt = (rawTs !== undefined && typeof rawTs === 'number' && Number.isFinite(rawTs) && rawTs >= 0 && rawTs <= 9.9e12)
1045
+ ? rawTs
1046
+ : now;
1047
+ results.push({
1048
+ id: `sona:${key}`,
1049
+ type: agent ?? 'routing',
1050
+ content: keywords.join(' '),
1051
+ confidence,
1052
+ usageCount: sc + fc,
1053
+ embedding: [],
1054
+ createdAt,
1055
+ lastUsedAt: now,
1056
+ });
1057
+ }
1058
+ return results;
998
1059
  }
999
1060
  catch {
1000
1061
  return [];
@@ -404,8 +404,12 @@ async function getOrBuildHnswIndex(db) {
404
404
  _hnswIndexBuilt = true; // Lock prevents concurrent re-build
405
405
  try {
406
406
  const memPkg = await import('@monoes/memory');
407
- if (!memPkg?.HNSWIndex)
407
+ if (!memPkg?.HNSWIndex) {
408
+ // Release the lock so a later retry can succeed if the package becomes available.
409
+ _hnswIndexBuilt = false;
410
+ _hnswBuildFailedAt = Date.now();
408
411
  return null;
412
+ }
409
413
  const rows = db.prepare(`SELECT id, embedding FROM memory_entries WHERE status = 'active' AND (expires_at IS NULL OR expires_at > ?) AND embedding IS NOT NULL`).all(Date.now());
410
414
  const valid = [];
411
415
  for (const row of rows) {
@@ -447,7 +451,22 @@ export async function bridgeStoreEntry(options) {
447
451
  if (!ctx)
448
452
  return null;
449
453
  try {
450
- const { key, value, namespace = 'default', tags: rawTags = [], ttl } = options;
454
+ const rawKey = options.key;
455
+ const rawValue = options.value;
456
+ // SECURITY: defensive caps so no caller — current or future — can pass an
457
+ // unbounded string directly into embedder.embed (hash fallback is O(n)) or
458
+ // inflate the AgentDB row beyond practical limits.
459
+ // Memory-tools already validates to 1 MB; these caps are a last-resort
460
+ // backstop for any internal caller that forgets to pre-truncate.
461
+ const BRIDGE_MAX_KEY_LEN = 4 * 1024; // 4 KB — generous for any realistic key
462
+ const BRIDGE_MAX_VALUE_LEN = 1024 * 1024; // 1 MB — matches memory-tools validator
463
+ const key = typeof rawKey === 'string' && rawKey.length > BRIDGE_MAX_KEY_LEN
464
+ ? rawKey.slice(0, BRIDGE_MAX_KEY_LEN) : rawKey;
465
+ const value = typeof rawValue === 'string' && rawValue.length > BRIDGE_MAX_VALUE_LEN
466
+ ? rawValue.slice(0, BRIDGE_MAX_VALUE_LEN) : rawValue;
467
+ const namespace = options.namespace ?? 'default';
468
+ const rawTags = options.tags ?? [];
469
+ const ttl = options.ttl;
451
470
  // SECURITY: cap tags array length and per-tag length. Without these, any
452
471
  // memory_store caller (every spawned agent) could submit
453
472
  // tags: new Array(1e5).fill("x".repeat(1e4)) → ~1GB persisted blob,
@@ -11,6 +11,8 @@
11
11
  import * as fs from 'fs';
12
12
  import * as path from 'path';
13
13
  import { safeParseEmbedding } from './memory-bridge.js';
14
+ /** Maximum SQLite database file size accepted before read (256 MB). */
15
+ const MAX_DB_FILE_BYTES = 256 * 1024 * 1024;
14
16
  // ADR-053: Lazy import of AgentDB v1 bridge
15
17
  let _bridge;
16
18
  async function getBridge() {
@@ -733,6 +735,11 @@ export async function ensureSchemaColumns(dbPath) {
733
735
  }
734
736
  const initSqlJs = (await import('sql.js')).default;
735
737
  const SQL = await initSqlJs();
738
+ // Guard against excessively large DB files to prevent OOM.
739
+ const ensureStat = fs.statSync(dbPath);
740
+ if (ensureStat.size > MAX_DB_FILE_BYTES) {
741
+ return { success: false, columnsAdded, error: `Database file too large: ${ensureStat.size} bytes` };
742
+ }
736
743
  const fileBuffer = fs.readFileSync(dbPath);
737
744
  const db = new SQL.Database(fileBuffer);
738
745
  // Get current columns in memory_entries
@@ -802,6 +809,14 @@ export async function checkAndMigrateLegacy(options) {
802
809
  try {
803
810
  const initSqlJs = (await import('sql.js')).default;
804
811
  const SQL = await initSqlJs();
812
+ // Guard against excessively large legacy DB files to prevent OOM.
813
+ const legacyStat = fs.statSync(legacyPath);
814
+ if (legacyStat.size > MAX_DB_FILE_BYTES) {
815
+ if (verbose) {
816
+ console.warn(`[memory] Skipping legacy DB at ${legacyPath}: file too large (${legacyStat.size} bytes)`);
817
+ }
818
+ continue;
819
+ }
805
820
  const legacyBuffer = fs.readFileSync(legacyPath);
806
821
  const legacyDb = new SQL.Database(legacyBuffer);
807
822
  // Check if it has data
@@ -1080,6 +1095,11 @@ export async function checkMemoryInitialization(dbPath) {
1080
1095
  // Try to load with sql.js
1081
1096
  const initSqlJs = (await import('sql.js')).default;
1082
1097
  const SQL = await initSqlJs();
1098
+ // Guard against excessively large DB files to prevent OOM.
1099
+ const checkStat = fs.statSync(path_);
1100
+ if (checkStat.size > MAX_DB_FILE_BYTES) {
1101
+ return { initialized: false };
1102
+ }
1083
1103
  const fileBuffer = fs.readFileSync(path_);
1084
1104
  const db = new SQL.Database(fileBuffer);
1085
1105
  // Check for metadata table
@@ -1125,6 +1145,11 @@ export async function applyTemporalDecay(dbPath) {
1125
1145
  try {
1126
1146
  const initSqlJs = (await import('sql.js')).default;
1127
1147
  const SQL = await initSqlJs();
1148
+ // Guard against excessively large DB files to prevent OOM.
1149
+ const decayStat = fs.statSync(path_);
1150
+ if (decayStat.size > MAX_DB_FILE_BYTES) {
1151
+ return { success: false, patternsDecayed: 0, error: `Database file too large: ${decayStat.size} bytes` };
1152
+ }
1128
1153
  const fileBuffer = fs.readFileSync(path_);
1129
1154
  const db = new SQL.Database(fileBuffer);
1130
1155
  // Apply decay: confidence *= exp(-decay_rate * days_since_last_use)
@@ -1445,6 +1470,11 @@ export async function verifyMemoryInit(dbPath, options) {
1445
1470
  const initSqlJs = (await import('sql.js')).default;
1446
1471
  const SQL = await initSqlJs();
1447
1472
  const fs = await import('fs');
1473
+ // Guard against excessively large DB files to prevent OOM.
1474
+ const verifyStat = fs.statSync(dbPath);
1475
+ if (verifyStat.size > MAX_DB_FILE_BYTES) {
1476
+ return { success: false, tests: [{ name: 'Database access', passed: false, details: `File too large: ${verifyStat.size} bytes` }], summary: { passed: 0, failed: 1, total: 1 } };
1477
+ }
1448
1478
  // Load database
1449
1479
  const fileBuffer = fs.readFileSync(dbPath);
1450
1480
  const db = new SQL.Database(fileBuffer);
@@ -1631,6 +1661,11 @@ export async function storeEntry(options) {
1631
1661
  await ensureSchemaColumns(dbPath);
1632
1662
  const initSqlJs = (await import('sql.js')).default;
1633
1663
  const SQL = await initSqlJs();
1664
+ // Guard against excessively large DB files to prevent OOM.
1665
+ const storeStat = fs.statSync(dbPath);
1666
+ if (storeStat.size > MAX_DB_FILE_BYTES) {
1667
+ return { success: false, id: '', error: `Database file too large: ${storeStat.size} bytes` };
1668
+ }
1634
1669
  const fileBuffer = fs.readFileSync(dbPath);
1635
1670
  const db = new SQL.Database(fileBuffer);
1636
1671
  const id = `entry_${Date.now()}_${Math.random().toString(36).substring(7)}`;
@@ -1727,6 +1762,11 @@ export async function searchEntries(options) {
1727
1762
  }
1728
1763
  // Ensure schema has all required columns (migration for older DBs)
1729
1764
  await ensureSchemaColumns(dbPath);
1765
+ // Guard against excessively large DB files to prevent OOM.
1766
+ const searchStat = fs.statSync(dbPath);
1767
+ if (searchStat.size > MAX_DB_FILE_BYTES) {
1768
+ return { success: false, results: [], searchTime: 0, error: `Database file too large: ${searchStat.size} bytes` };
1769
+ }
1730
1770
  // Generate query embedding
1731
1771
  const queryEmb = await generateEmbedding(query);
1732
1772
  const queryEmbedding = queryEmb.embedding;
@@ -1744,6 +1784,11 @@ export async function searchEntries(options) {
1744
1784
  // Fall back to brute-force SQLite search
1745
1785
  const initSqlJs = (await import('sql.js')).default;
1746
1786
  const SQL = await initSqlJs();
1787
+ // Guard against excessively large DB files to prevent OOM.
1788
+ const searchFbStat = fs.statSync(dbPath);
1789
+ if (searchFbStat.size > MAX_DB_FILE_BYTES) {
1790
+ return { success: false, results: [], searchTime: Date.now() - startTime, error: `Database file too large: ${searchFbStat.size} bytes` };
1791
+ }
1747
1792
  const fileBuffer = fs.readFileSync(dbPath);
1748
1793
  const db = new SQL.Database(fileBuffer);
1749
1794
  // Get entries with embeddings
@@ -1854,6 +1899,11 @@ export async function listEntries(options) {
1854
1899
  await ensureSchemaColumns(dbPath);
1855
1900
  const initSqlJs = (await import('sql.js')).default;
1856
1901
  const SQL = await initSqlJs();
1902
+ // Guard against excessively large DB files to prevent OOM.
1903
+ const listStat = fs.statSync(dbPath);
1904
+ if (listStat.size > MAX_DB_FILE_BYTES) {
1905
+ return { success: false, entries: [], total: 0, error: `Database file too large: ${listStat.size} bytes` };
1906
+ }
1857
1907
  const fileBuffer = fs.readFileSync(dbPath);
1858
1908
  const db = new SQL.Database(fileBuffer);
1859
1909
  // Get total count
@@ -1870,9 +1920,13 @@ export async function listEntries(options) {
1870
1920
  countStmt.free();
1871
1921
  const countResult = countRows.length > 0 ? [{ values: countRows }] : [];
1872
1922
  const total = countResult[0]?.values?.[0]?.[0] || 0;
1873
- // Get entries
1874
- const safeLimit = parseInt(String(limit), 10) || 100;
1875
- const safeOffset = parseInt(String(offset), 10) || 0;
1923
+ // Get entries — cap limit to 10 000 to prevent full-table loads that OOM
1924
+ // the sql.js in-memory database on large datasets.
1925
+ const MAX_LIST_LIMIT = 10_000;
1926
+ const rawLimit = parseInt(String(limit), 10);
1927
+ const safeLimit = Number.isFinite(rawLimit) && rawLimit > 0 ? Math.min(rawLimit, MAX_LIST_LIMIT) : 100;
1928
+ const rawOffset = parseInt(String(offset), 10);
1929
+ const safeOffset = Number.isFinite(rawOffset) && rawOffset >= 0 ? rawOffset : 0;
1876
1930
  const listStmt = namespace
1877
1931
  ? db.prepare(`SELECT id, key, namespace, content, embedding, access_count, created_at, updated_at FROM memory_entries WHERE status = 'active' AND namespace = ? ORDER BY updated_at DESC LIMIT ? OFFSET ?`)
1878
1932
  : db.prepare(`SELECT id, key, namespace, content, embedding, access_count, created_at, updated_at FROM memory_entries WHERE status = 'active' ORDER BY updated_at DESC LIMIT ? OFFSET ?`);
@@ -1939,6 +1993,11 @@ export async function getEntry(options) {
1939
1993
  await ensureSchemaColumns(dbPath);
1940
1994
  const initSqlJs = (await import('sql.js')).default;
1941
1995
  const SQL = await initSqlJs();
1996
+ // Guard against excessively large DB files to prevent OOM.
1997
+ const getStat = fs.statSync(dbPath);
1998
+ if (getStat.size > MAX_DB_FILE_BYTES) {
1999
+ return { success: false, found: false, error: `Database file too large: ${getStat.size} bytes` };
2000
+ }
1942
2001
  const fileBuffer = fs.readFileSync(dbPath);
1943
2002
  const db = new SQL.Database(fileBuffer);
1944
2003
  // Find entry by key
@@ -2041,6 +2100,11 @@ export async function deleteEntry(options) {
2041
2100
  await ensureSchemaColumns(dbPath);
2042
2101
  const initSqlJs = (await import('sql.js')).default;
2043
2102
  const SQL = await initSqlJs();
2103
+ // Guard against excessively large DB files to prevent OOM.
2104
+ const deleteStat = fs.statSync(dbPath);
2105
+ if (deleteStat.size > MAX_DB_FILE_BYTES) {
2106
+ return { success: false, deleted: false, key, namespace, remainingEntries: 0, error: `Database file too large: ${deleteStat.size} bytes` };
2107
+ }
2044
2108
  const fileBuffer = fs.readFileSync(dbPath);
2045
2109
  const db = new SQL.Database(fileBuffer);
2046
2110
  // Check if entry exists first
@@ -11,7 +11,7 @@
11
11
  * - Persists patterns to .swarm/sona-patterns.json
12
12
  * @module v1/cli/memory/sona-optimizer
13
13
  */
14
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'fs';
14
+ import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'fs';
15
15
  import { dirname, join } from 'path';
16
16
  // ============================================================================
17
17
  // Constants
@@ -473,6 +473,8 @@ export class SONAOptimizer {
473
473
  if (!existsSync(fullPath)) {
474
474
  return false;
475
475
  }
476
+ if (statSync(fullPath).size > 50 * 1024 * 1024)
477
+ return false;
476
478
  const data = readFileSync(fullPath, 'utf-8');
477
479
  const state = JSON.parse(data);
478
480
  // Validate version
@@ -480,9 +482,13 @@ export class SONAOptimizer {
480
482
  console.error('[SONA] Incompatible state version, starting fresh');
481
483
  return false;
482
484
  }
483
- // Load patterns
485
+ // Load patterns — also cap key length so a crafted state file cannot
486
+ // store arbitrarily long keys that bloat the in-memory Map. The key is
487
+ // an agent:keyword string; 512 chars is ample for any real value.
484
488
  this.patterns.clear();
485
489
  for (const [key, pattern] of Object.entries(state.patterns)) {
490
+ if (typeof key !== 'string' || key.length > 512)
491
+ continue;
486
492
  if (this.validatePattern(pattern)) {
487
493
  this.patterns.set(key, pattern);
488
494
  }
@@ -497,7 +503,9 @@ export class SONAOptimizer {
497
503
  return true;
498
504
  }
499
505
  catch (err) {
500
- console.error(`[SONA] Failed to load state: ${err}`);
506
+ // Strip filesystem paths from error before logging to prevent path disclosure
507
+ const msg = err instanceof Error ? err.message : String(err);
508
+ console.error(`[SONA] Failed to load state: ${msg.replace(/\/[^\s:]+(\/|(?=\s|:|$))/g, '<path>/').slice(0, 200)}`);
501
509
  return false;
502
510
  }
503
511
  }
@@ -532,7 +540,9 @@ export class SONAOptimizer {
532
540
  return true;
533
541
  }
534
542
  catch (err) {
535
- console.error(`[SONA] Failed to save state: ${err}`);
543
+ // Strip filesystem paths from error before logging to prevent path disclosure
544
+ const msg = err instanceof Error ? err.message : String(err);
545
+ console.error(`[SONA] Failed to save state: ${msg.replace(/\/[^\s:]+(\/|(?=\s|:|$))/g, '<path>/').slice(0, 200)}`);
536
546
  return false;
537
547
  }
538
548
  }
@@ -3,19 +3,43 @@
3
3
  * success signal from real command exit codes, instead of trusting a
4
4
  * caller-supplied --success flag.
5
5
  */
6
- import { promises as fs } from 'node:fs';
6
+ import { promises as fs, statSync } from 'node:fs';
7
7
  import { join } from 'node:path';
8
+ /** Refuse to read files larger than this to prevent OOM. */
9
+ const MAX_FILE_BYTES = 50 * 1024 * 1024; // 50 MB
10
+ /** Cap command string length to prevent file bloat. */
11
+ const MAX_COMMAND_LEN = 500;
8
12
  function storePath(baseDir) {
9
13
  return join(baseDir, 'command-outcomes.jsonl');
10
14
  }
15
+ /** Maximum number of command-outcome records to keep.
16
+ * deriveRecentSuccess only uses records within a 5-minute window (typically < 50), so
17
+ * anything older is dead weight. 500 gives a comfortable buffer. */
18
+ const MAX_COMMAND_RECORDS = 500;
11
19
  /** Append a command outcome. Non-fatal on error. */
12
20
  export async function recordCommand(baseDir, cmd) {
13
21
  try {
14
22
  await fs.mkdir(baseDir, { recursive: true });
15
- const rec = { ts: cmd.ts, command: cmd.command, exitCode: cmd.exitCode, success: cmd.exitCode === 0 };
16
- await fs.appendFile(storePath(baseDir), JSON.stringify(rec) + '\n', 'utf8');
17
- // Opportunistic trim: keep file bounded (last 500 lines) to avoid unbounded growth
18
- // (cheap: only rewrite when it gets large)
23
+ const path = storePath(baseDir);
24
+ // Cap command length to prevent individual records from bloating the file.
25
+ const safeCommand = cmd.command.length > MAX_COMMAND_LEN ? cmd.command.slice(0, MAX_COMMAND_LEN) : cmd.command;
26
+ const rec = { ts: cmd.ts, command: safeCommand, exitCode: cmd.exitCode, success: cmd.exitCode === 0 };
27
+ await fs.appendFile(path, JSON.stringify(rec) + '\n', 'utf8');
28
+ // Opportunistic trim: rewrite only when the file exceeds the cap.
29
+ // Avoids an extra stat() on every call by catching the overcount lazily.
30
+ // Guard with size check first to prevent OOM on unexpectedly large files.
31
+ try {
32
+ if (statSync(path).size > MAX_FILE_BYTES)
33
+ return;
34
+ }
35
+ catch {
36
+ return;
37
+ }
38
+ const content = await fs.readFile(path, 'utf8').catch(() => '');
39
+ const lines = content.trim().split('\n').filter(Boolean);
40
+ if (lines.length > MAX_COMMAND_RECORDS) {
41
+ await fs.writeFile(path, lines.slice(-MAX_COMMAND_RECORDS).join('\n') + '\n', 'utf8');
42
+ }
19
43
  }
20
44
  catch { /* non-fatal */ }
21
45
  }
@@ -42,7 +66,13 @@ export async function recordCommand(baseDir, cmd) {
42
66
  */
43
67
  export async function deriveRecentSuccess(baseDir, windowMs = 300_000) {
44
68
  try {
45
- const content = await fs.readFile(storePath(baseDir), 'utf8').catch(() => '');
69
+ const p = storePath(baseDir);
70
+ try {
71
+ if (statSync(p).size > MAX_FILE_BYTES)
72
+ return null;
73
+ }
74
+ catch { /* file absent */ }
75
+ const content = await fs.readFile(p, 'utf8').catch(() => '');
46
76
  if (!content)
47
77
  return null;
48
78
  const now = Date.now();
@@ -67,7 +97,13 @@ export async function deriveRecentSuccess(baseDir, windowMs = 300_000) {
67
97
  /** Read recent command outcomes (for diagnostics). */
68
98
  export async function readCommandOutcomes(baseDir, windowMs = 300_000) {
69
99
  try {
70
- const content = await fs.readFile(storePath(baseDir), 'utf8').catch(() => '');
100
+ const p = storePath(baseDir);
101
+ try {
102
+ if (statSync(p).size > MAX_FILE_BYTES)
103
+ return [];
104
+ }
105
+ catch { /* file absent */ }
106
+ const content = await fs.readFile(p, 'utf8').catch(() => '');
71
107
  if (!content)
72
108
  return [];
73
109
  const now = Date.now();
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * @module @monomind/cli/monovector/coverage-router
12
12
  */
13
- import { existsSync, readFileSync } from 'fs';
13
+ import { existsSync, readFileSync, statSync } from 'fs';
14
14
  import { join } from 'path';
15
15
  import { getProjectCwd } from '../mcp-tools/types.js';
16
16
  // ============================================================================
@@ -28,6 +28,10 @@ const EMPTY = {
28
28
  overallStatementCoverage: 0,
29
29
  },
30
30
  };
31
+ /** Maximum bytes for a JSON coverage summary (20 MB). */
32
+ const MAX_COVERAGE_JSON_BYTES = 20 * 1024 * 1024;
33
+ /** Maximum bytes for an lcov.info file (50 MB). */
34
+ const MAX_COVERAGE_LCOV_BYTES = 50 * 1024 * 1024;
31
35
  /**
32
36
  * Read coverage data from disk. Checks, in order:
33
37
  * 1. coverage/coverage-summary.json (Jest/Istanbul)
@@ -39,7 +43,7 @@ const EMPTY = {
39
43
  export function readCoverage(cwd = getProjectCwd()) {
40
44
  for (const rel of ['coverage/coverage-summary.json', 'coverage-summary.json']) {
41
45
  const p = join(cwd, rel);
42
- if (existsSync(p)) {
46
+ if (existsSync(p) && statSync(p).size <= MAX_COVERAGE_JSON_BYTES) {
43
47
  try {
44
48
  return parseSummaryJson(JSON.parse(readFileSync(p, 'utf-8')), rel);
45
49
  }
@@ -50,7 +54,7 @@ export function readCoverage(cwd = getProjectCwd()) {
50
54
  }
51
55
  for (const rel of ['coverage/lcov.info', 'lcov.info']) {
52
56
  const p = join(cwd, rel);
53
- if (existsSync(p)) {
57
+ if (existsSync(p) && statSync(p).size <= MAX_COVERAGE_LCOV_BYTES) {
54
58
  try {
55
59
  return parseLcov(readFileSync(p, 'utf-8'), rel);
56
60
  }
@@ -60,7 +64,7 @@ export function readCoverage(cwd = getProjectCwd()) {
60
64
  }
61
65
  }
62
66
  const nyc = join(cwd, '.nyc_output', 'out.json');
63
- if (existsSync(nyc)) {
67
+ if (existsSync(nyc) && statSync(nyc).size <= MAX_COVERAGE_JSON_BYTES) {
64
68
  try {
65
69
  return parseSummaryJson(JSON.parse(readFileSync(nyc, 'utf-8')), '.nyc_output/out.json');
66
70
  }