sinapse-ai 1.6.1 → 1.8.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 (131) hide show
  1. package/.claude/CLAUDE.md +5 -11
  2. package/.claude/hooks/README.md +14 -1
  3. package/.claude/hooks/code-intel-pretool.cjs +115 -0
  4. package/.claude/hooks/enforce-delegation.cjs +31 -3
  5. package/.claude/hooks/enforce-framework-boundary.cjs +324 -0
  6. package/.claude/hooks/enforce-permission-mode.cjs +249 -0
  7. package/.claude/hooks/secret-scanning.cjs +34 -43
  8. package/.claude/hooks/synapse-engine.cjs +23 -23
  9. package/.claude/hooks/telemetry-post-tool.cjs +128 -0
  10. package/.claude/hooks/telemetry-stop.cjs +132 -0
  11. package/.claude/hooks/verify-packages.cjs +9 -2
  12. package/.claude/rules/documentation-first.md +1 -1
  13. package/.claude/rules/hook-governance.md +2 -0
  14. package/.sinapse-ai/cli/commands/health/index.js +24 -0
  15. package/.sinapse-ai/core/README.md +11 -0
  16. package/.sinapse-ai/core/config/config-loader.js +19 -0
  17. package/.sinapse-ai/core/config/merge-utils.js +8 -0
  18. package/.sinapse-ai/core/errors/constants.js +147 -0
  19. package/.sinapse-ai/core/errors/error-registry.js +176 -0
  20. package/.sinapse-ai/core/errors/index.js +50 -0
  21. package/.sinapse-ai/core/errors/serializer.js +147 -0
  22. package/.sinapse-ai/core/errors/sinapse-error.js +144 -0
  23. package/.sinapse-ai/core/errors/utils.js +187 -0
  24. package/.sinapse-ai/core/execution/build-orchestrator.js +47 -49
  25. package/.sinapse-ai/core/execution/build-state-manager.js +183 -31
  26. package/.sinapse-ai/core/execution/parallel-executor.js +7 -1
  27. package/.sinapse-ai/core/execution/semantic-merge-engine.js +26 -14
  28. package/.sinapse-ai/core/execution/subagent-dispatcher.js +201 -60
  29. package/.sinapse-ai/core/execution/wave-executor.js +4 -1
  30. package/.sinapse-ai/core/grounding/README.md +71 -11
  31. package/.sinapse-ai/core/health-check/checks/project/framework-config.js +38 -2
  32. package/.sinapse-ai/core/health-check/checks/project/package-json.js +47 -3
  33. package/.sinapse-ai/core/health-check/checks/services/gemini-cli.js +117 -0
  34. package/.sinapse-ai/core/health-check/checks/services/index.js +2 -0
  35. package/.sinapse-ai/core/health-check/healers/index.js +40 -3
  36. package/.sinapse-ai/core/ideation/ideation-engine.js +212 -107
  37. package/.sinapse-ai/core/ids/gate-evaluator.js +318 -0
  38. package/.sinapse-ai/core/ids/gates/g5-semantic-handshake.js +190 -0
  39. package/.sinapse-ai/core/ids/gates/g6-ci-integrity.js +162 -0
  40. package/.sinapse-ai/core/ids/index.js +30 -0
  41. package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +11 -0
  42. package/.sinapse-ai/core/memory/gotchas-memory.js +37 -2
  43. package/.sinapse-ai/core/orchestration/agent-invoker.js +29 -6
  44. package/.sinapse-ai/core/orchestration/brownfield-handler.js +36 -3
  45. package/.sinapse-ai/core/orchestration/condition-evaluator.js +57 -0
  46. package/.sinapse-ai/core/orchestration/executors/epic-3-executor.js +76 -5
  47. package/.sinapse-ai/core/orchestration/executors/epic-4-executor.js +63 -17
  48. package/.sinapse-ai/core/orchestration/executors/epic-6-executor.js +153 -41
  49. package/.sinapse-ai/core/orchestration/executors/epic-executor.js +40 -0
  50. package/.sinapse-ai/core/orchestration/greenfield-handler.js +87 -3
  51. package/.sinapse-ai/core/orchestration/master-orchestrator.js +150 -10
  52. package/.sinapse-ai/core/orchestration/parallel-executor.js +6 -1
  53. package/.sinapse-ai/core/orchestration/recovery-handler.js +81 -8
  54. package/.sinapse-ai/core/orchestration/workflow-executor.js +41 -0
  55. package/.sinapse-ai/core/registry/registry-loader.js +71 -5
  56. package/.sinapse-ai/core/registry/squad-agent-resolver.js +253 -0
  57. package/.sinapse-ai/core/synapse/context/context-tracker.js +104 -9
  58. package/.sinapse-ai/core/synapse/context/index.js +19 -0
  59. package/.sinapse-ai/core/synapse/context/semantic-handshake-engine.js +555 -0
  60. package/.sinapse-ai/core/synapse/diagnostics/collectors/pipeline-collector.js +4 -2
  61. package/.sinapse-ai/core/synapse/engine.js +43 -3
  62. package/.sinapse-ai/core/telemetry/ids-sink.js +188 -0
  63. package/.sinapse-ai/core/utils/output-formatter.js +8 -290
  64. package/.sinapse-ai/core/utils/spawn-safe.js +186 -0
  65. package/.sinapse-ai/core-config.yaml +68 -1
  66. package/.sinapse-ai/data/entity-registry.yaml +15082 -13618
  67. package/.sinapse-ai/data/registry-update-log.jsonl +143 -0
  68. package/.sinapse-ai/development/agents/developer.md +2 -0
  69. package/.sinapse-ai/development/agents/devops.md +9 -0
  70. package/.sinapse-ai/development/external-executors/README.md +18 -0
  71. package/.sinapse-ai/development/external-executors/codex.md +56 -0
  72. package/.sinapse-ai/development/scripts/populate-entity-registry.js +65 -9
  73. package/.sinapse-ai/development/scripts/squad/squad-downloader.js +169 -14
  74. package/.sinapse-ai/development/tasks/delegate-to-external-executor.md +152 -0
  75. package/.sinapse-ai/development/tasks/github-devops-pre-push-quality-gate.md +46 -29
  76. package/.sinapse-ai/development/tasks/update-sinapse.md +3 -3
  77. package/.sinapse-ai/hooks/sinapse-brand-grounding.cjs +4 -7
  78. package/.sinapse-ai/hooks/sinapse-ds-grounding.cjs +5 -8
  79. package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +6 -9
  80. package/.sinapse-ai/infrastructure/integrations/ai-providers/ai-provider-factory.js +4 -1
  81. package/.sinapse-ai/infrastructure/integrations/ai-providers/claude-provider.js +57 -55
  82. package/.sinapse-ai/infrastructure/integrations/pm-adapters/github-adapter.js +9 -7
  83. package/.sinapse-ai/infrastructure/scripts/ide-sync/gemini-commands.js +298 -0
  84. package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +127 -6
  85. package/.sinapse-ai/infrastructure/scripts/ide-sync/persona-renderer.js +97 -0
  86. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/antigravity.js +121 -0
  87. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/cursor.js +119 -0
  88. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/github-copilot.js +191 -0
  89. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/kimi.js +448 -0
  90. package/.sinapse-ai/install-manifest.yaml +218 -114
  91. package/.sinapse-ai/product/templates/engine/renderer.js +20 -1
  92. package/.sinapse-ai/scripts/pm.sh +18 -6
  93. package/bin/cli.js +17 -0
  94. package/bin/commands/agents.js +96 -0
  95. package/bin/commands/doctor.js +15 -0
  96. package/bin/commands/ideate.js +129 -0
  97. package/bin/commands/uninstall.js +40 -0
  98. package/bin/postinstall.js +50 -4
  99. package/bin/sinapse.js +146 -2
  100. package/bin/utils/secret-scanner-core.js +253 -0
  101. package/bin/utils/staged-secret-scan.js +106 -40
  102. package/docs/framework/collaboration-autonomy-plan.md +18 -18
  103. package/docs/guides/parallel-workflow.md +6 -6
  104. package/package.json +22 -5
  105. package/packages/installer/src/installer/git-hooks-installer.js +384 -0
  106. package/packages/installer/src/installer/sinapse-ai-installer.js +16 -0
  107. package/packages/installer/src/wizard/ide-config-generator.js +23 -0
  108. package/packages/installer/src/wizard/validators.js +38 -1
  109. package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +5 -1
  110. package/packages/installer/tests/unit/doctor/doctor-checks.test.js +44 -22
  111. package/packages/installer/tests/unit/git-hooks-installer.test.js +262 -0
  112. package/scripts/eval-runner.js +422 -0
  113. package/scripts/generate-install-manifest.js +13 -9
  114. package/scripts/generate-synapse-runtime.js +51 -0
  115. package/scripts/regenerate-orqx-stubs.ps1 +6 -5
  116. package/scripts/validate-all.js +1 -0
  117. package/scripts/validate-evals.js +466 -0
  118. package/scripts/validate-schemas.js +539 -0
  119. package/scripts/validate-squad-orqx.js +9 -2
  120. package/squads/claude-code-mastery/knowledge-base/memory-systems-reference.md +1 -1
  121. package/squads/squad-brand/templates/client-delivery-template.md +1 -1
  122. package/squads/squad-content/knowledge-base/social-compression-framework.md +1 -1
  123. package/squads/squad-council/knowledge-base/brand-strategy-models.md +1 -1
  124. package/.sinapse-ai/development/scripts/elicitation-engine.js +0 -385
  125. package/.sinapse-ai/development/scripts/elicitation-session-manager.js +0 -300
  126. package/.sinapse-ai/development/tasks/test-validation-task.md +0 -172
  127. package/docs/chrome-brain-upgrade-plan.md +0 -624
  128. package/docs/constitution-compliance.md +0 -87
  129. package/docs/mega-upgrade-orchestration-plan.md +0 -71
  130. package/docs/research-synthesis-for-upgrade.md +0 -511
  131. package/docs/security-audit-report.md +0 -306
@@ -23,6 +23,11 @@
23
23
  const fs = require('fs');
24
24
  const path = require('path');
25
25
  const crypto = require('crypto');
26
+ const {
27
+ normalizeError,
28
+ sanitizeValue: sanitizeErrorValue,
29
+ serializeError,
30
+ } = require('../errors');
26
31
 
27
32
  // Optional dependencies with graceful fallback
28
33
  let chalk;
@@ -87,6 +92,66 @@ const NotificationType = {
87
92
  ABANDONED: 'abandoned',
88
93
  };
89
94
 
95
+ /**
96
+ * Converts arbitrary values into stable, JSON-safe log data.
97
+ *
98
+ * @param {*} value - Value to sanitize.
99
+ * @param {WeakSet<object>} [seen=new WeakSet()] - Objects in the current path.
100
+ * @returns {*} Data-only representation that JSON.stringify can serialize.
101
+ */
102
+ function sanitizeLogValue(value, seen = new WeakSet()) {
103
+ return sanitizeErrorValue(value, seen, {
104
+ includeStack: shouldExposeLogErrorStack(),
105
+ });
106
+ }
107
+
108
+ /**
109
+ * Checks whether persisted attempt logs may include raw error stack traces.
110
+ *
111
+ * @returns {boolean} True when stack trace logging is explicitly enabled.
112
+ */
113
+ function shouldExposeLogErrorStack() {
114
+ const stackFlag = process.env.DEBUG_ERROR_STACKS || process.env.DEBUG_STACKS || '';
115
+ return ['1', 'true', 'yes', 'on'].includes(String(stackFlag).toLowerCase());
116
+ }
117
+
118
+ /**
119
+ * Stringifies attempt log details without allowing log formatting to throw.
120
+ *
121
+ * @param {*} value - Value to stringify.
122
+ * @returns {string} JSON string or a fallback marker.
123
+ */
124
+ function stringifyLogDetails(value) {
125
+ try {
126
+ return JSON.stringify(sanitizeLogValue(value));
127
+ } catch (error) {
128
+ return JSON.stringify(`[Unserializable: ${error.message}]`);
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Normalize a failed build attempt into legacy message plus canonical details.
134
+ *
135
+ * @param {*} error - Raw failure error/value.
136
+ * @param {object} context - Build/subtask context for metadata.
137
+ * @returns {{ message: string, details: object }} Normalized failure payload.
138
+ */
139
+ function normalizeFailureError(error, context = {}) {
140
+ const normalized = normalizeError(error || 'Unknown error', {
141
+ code: 'SNPS_EXECUTION_FAILED',
142
+ metadata: {
143
+ buildState: context,
144
+ },
145
+ });
146
+
147
+ return {
148
+ message: normalized.message,
149
+ details: serializeError(normalized, {
150
+ includeStack: shouldExposeLogErrorStack(),
151
+ }),
152
+ };
153
+ }
154
+
90
155
  // ═══════════════════════════════════════════════════════════════════════════════════
91
156
  // SCHEMA VALIDATION
92
157
  // ═══════════════════════════════════════════════════════════════════════════════════
@@ -185,6 +250,8 @@ class BuildStateManager {
185
250
  // Internal state
186
251
  this._state = null;
187
252
  this._logBuffer = [];
253
+ this._persistenceAvailable = true;
254
+ this._persistenceError = null;
188
255
  }
189
256
 
190
257
  // ─────────────────────────────────────────────────────────────────────────────────
@@ -244,14 +311,24 @@ class BuildStateManager {
244
311
  * @returns {Object|null} Loaded state or null if not exists
245
312
  */
246
313
  loadState() {
314
+ if (!this._persistenceAvailable) {
315
+ return this._state;
316
+ }
317
+
247
318
  if (!fs.existsSync(this.stateFilePath)) {
248
319
  return null;
249
320
  }
250
321
 
322
+ let content;
251
323
  try {
252
- const content = fs.readFileSync(this.stateFilePath, 'utf-8');
253
- const state = JSON.parse(content);
324
+ content = fs.readFileSync(this.stateFilePath, 'utf-8');
325
+ } catch (error) {
326
+ this._markPersistenceUnavailable(error);
327
+ return this._state;
328
+ }
254
329
 
330
+ try {
331
+ const state = JSON.parse(content);
255
332
  // Validate
256
333
  const validation = validateBuildState(state);
257
334
  if (!validation.valid) {
@@ -277,11 +354,6 @@ class BuildStateManager {
277
354
  throw new Error('No state to save. Call createState() or loadState() first.');
278
355
  }
279
356
 
280
- // Ensure directory exists
281
- if (!fs.existsSync(this.planDir)) {
282
- fs.mkdirSync(this.planDir, { recursive: true });
283
- }
284
-
285
357
  // Only update timestamp if explicitly requested (via saveCheckpoint)
286
358
  if (options.updateCheckpoint) {
287
359
  this._state.lastCheckpoint = new Date().toISOString();
@@ -293,11 +365,24 @@ class BuildStateManager {
293
365
  throw new Error(`Invalid state: ${validation.errors.join(', ')}`);
294
366
  }
295
367
 
296
- // Write state file
297
- fs.writeFileSync(this.stateFilePath, JSON.stringify(this._state, null, 2), 'utf-8');
368
+ if (!this._persistenceAvailable) {
369
+ return this._state;
370
+ }
371
+
372
+ try {
373
+ // Ensure directory exists
374
+ if (!fs.existsSync(this.planDir)) {
375
+ fs.mkdirSync(this.planDir, { recursive: true });
376
+ }
377
+
378
+ // Write state file
379
+ fs.writeFileSync(this.stateFilePath, JSON.stringify(this._state, null, 2), 'utf-8');
298
380
 
299
- // Flush log buffer
300
- this._flushLogBuffer();
381
+ // Flush log buffer
382
+ this._flushLogBuffer();
383
+ } catch (error) {
384
+ this._markPersistenceUnavailable(error);
385
+ }
301
386
 
302
387
  return this._state;
303
388
  }
@@ -341,11 +426,6 @@ class BuildStateManager {
341
426
  throw new Error('No state loaded');
342
427
  }
343
428
 
344
- // Ensure checkpoint directory exists
345
- if (!fs.existsSync(this.checkpointDir)) {
346
- fs.mkdirSync(this.checkpointDir, { recursive: true });
347
- }
348
-
349
429
  const checkpointId = this._generateCheckpointId();
350
430
  const now = new Date().toISOString();
351
431
 
@@ -376,8 +456,19 @@ class BuildStateManager {
376
456
  this._updateMetrics(checkpoint);
377
457
 
378
458
  // Save checkpoint file
379
- const checkpointPath = path.join(this.checkpointDir, `${checkpointId}.json`);
380
- fs.writeFileSync(checkpointPath, JSON.stringify(checkpoint, null, 2), 'utf-8');
459
+ if (this._persistenceAvailable) {
460
+ try {
461
+ // Ensure checkpoint directory exists
462
+ if (!fs.existsSync(this.checkpointDir)) {
463
+ fs.mkdirSync(this.checkpointDir, { recursive: true });
464
+ }
465
+
466
+ const checkpointPath = path.join(this.checkpointDir, `${checkpointId}.json`);
467
+ fs.writeFileSync(checkpointPath, JSON.stringify(checkpoint, null, 2), 'utf-8');
468
+ } catch (error) {
469
+ this._markPersistenceUnavailable(error);
470
+ }
471
+ }
381
472
 
382
473
  // Save main state with checkpoint timestamp update
383
474
  this.saveState({ updateCheckpoint: true });
@@ -781,12 +872,21 @@ class BuildStateManager {
781
872
  throw new Error('No state loaded');
782
873
  }
783
874
 
875
+ const attempt =
876
+ options.attempt ||
877
+ this._state.failedAttempts.filter((f) => f.subtaskId === subtaskId).length + 1;
878
+
879
+ const normalizedFailure = normalizeFailureError(options.error, {
880
+ storyId: this.storyId,
881
+ subtaskId,
882
+ attempt,
883
+ });
884
+
784
885
  const failure = {
785
886
  subtaskId,
786
- attempt:
787
- options.attempt ||
788
- this._state.failedAttempts.filter((f) => f.subtaskId === subtaskId).length + 1,
789
- error: options.error || 'Unknown error',
887
+ attempt,
888
+ error: normalizedFailure.message,
889
+ errorDetails: normalizedFailure.details,
790
890
  timestamp: new Date().toISOString(),
791
891
  approach: options.approach || null,
792
892
  duration: options.duration || null,
@@ -808,6 +908,7 @@ class BuildStateManager {
808
908
  this._logAttempt(subtaskId, 'failure', {
809
909
  attempt: failure.attempt,
810
910
  error: failure.error,
911
+ errorDetails: failure.errorDetails,
811
912
  isStuck: isStuck.stuck,
812
913
  });
813
914
 
@@ -918,6 +1019,8 @@ class BuildStateManager {
918
1019
  * @private
919
1020
  */
920
1021
  _logAttempt(subtaskId, action, details = {}) {
1022
+ if (!this._persistenceAvailable) return;
1023
+
921
1024
  const entry = {
922
1025
  timestamp: new Date().toISOString(),
923
1026
  storyId: this.storyId,
@@ -927,7 +1030,7 @@ class BuildStateManager {
927
1030
  };
928
1031
 
929
1032
  // Format log line
930
- const logLine = `[${entry.timestamp}] [${this.storyId}] [${subtaskId}] ${action}: ${JSON.stringify(details)}\n`;
1033
+ const logLine = `[${entry.timestamp}] [${this.storyId}] [${subtaskId}] ${action}: ${stringifyLogDetails(details)}\n`;
931
1034
 
932
1035
  this._logBuffer.push(logLine);
933
1036
 
@@ -943,15 +1046,20 @@ class BuildStateManager {
943
1046
  */
944
1047
  _flushLogBuffer() {
945
1048
  if (this._logBuffer.length === 0) return;
1049
+ if (!this._persistenceAvailable) return;
946
1050
 
947
- // Ensure directory exists
948
- if (!fs.existsSync(this.planDir)) {
949
- fs.mkdirSync(this.planDir, { recursive: true });
950
- }
1051
+ try {
1052
+ // Ensure directory exists
1053
+ if (!fs.existsSync(this.planDir)) {
1054
+ fs.mkdirSync(this.planDir, { recursive: true });
1055
+ }
951
1056
 
952
- // Append to log file
953
- fs.appendFileSync(this.logFilePath, this._logBuffer.join(''), 'utf-8');
954
- this._logBuffer = [];
1057
+ // Append to log file
1058
+ fs.appendFileSync(this.logFilePath, this._logBuffer.join(''), 'utf-8');
1059
+ this._logBuffer = [];
1060
+ } catch (error) {
1061
+ this._markPersistenceUnavailable(error);
1062
+ }
955
1063
  }
956
1064
 
957
1065
  /**
@@ -961,11 +1069,22 @@ class BuildStateManager {
961
1069
  * @returns {string[]} Log lines
962
1070
  */
963
1071
  getAttemptLog(options = {}) {
1072
+ if (!this._persistenceAvailable) {
1073
+ return [];
1074
+ }
1075
+
964
1076
  if (!fs.existsSync(this.logFilePath)) {
965
1077
  return [];
966
1078
  }
967
1079
 
968
- const content = fs.readFileSync(this.logFilePath, 'utf-8');
1080
+ let content;
1081
+ try {
1082
+ content = fs.readFileSync(this.logFilePath, 'utf-8');
1083
+ } catch (error) {
1084
+ this._markPersistenceUnavailable(error);
1085
+ return [];
1086
+ }
1087
+
969
1088
  let lines = content.split('\n').filter((l) => l.trim());
970
1089
 
971
1090
  // Filter by subtask if specified
@@ -1143,6 +1262,39 @@ class BuildStateManager {
1143
1262
  // Silent by default - can be overridden
1144
1263
  }
1145
1264
 
1265
+ /**
1266
+ * Check if file-backed persistence is still available.
1267
+ *
1268
+ * @returns {boolean} True when state/checkpoint/log writes are available.
1269
+ */
1270
+ isPersistenceAvailable() {
1271
+ return this._persistenceAvailable;
1272
+ }
1273
+
1274
+ /**
1275
+ * Get the first persistence error that disabled file-backed writes.
1276
+ *
1277
+ * @returns {Error|null} Persistence error or null when persistence is available.
1278
+ */
1279
+ getPersistenceError() {
1280
+ return this._persistenceError;
1281
+ }
1282
+
1283
+ /**
1284
+ * Disable file-backed persistence after an I/O failure.
1285
+ *
1286
+ * @param {Error} error - Underlying persistence error.
1287
+ * @private
1288
+ */
1289
+ _markPersistenceUnavailable(error) {
1290
+ if (!this._persistenceAvailable) return;
1291
+
1292
+ this._persistenceAvailable = false;
1293
+ this._persistenceError = error instanceof Error ? error : new Error(String(error));
1294
+ this._logBuffer = [];
1295
+ this._log(`Persistence unavailable: ${this._persistenceError.message}`);
1296
+ }
1297
+
1146
1298
  // ─────────────────────────────────────────────────────────────────────────────────
1147
1299
  // CLI FORMATTING
1148
1300
  // ─────────────────────────────────────────────────────────────────────────────────
@@ -1,8 +1,14 @@
1
1
  /**
2
- * Parallel Executor
2
+ * Parallel Executor (PROVIDER-level)
3
3
  * Story GEMINI-INT.17 - Multi-Agent Parallel Execution
4
4
  *
5
5
  * Executes Claude and Gemini in parallel for improved quality and reliability.
6
+ *
7
+ * NOT a duplicate of `core/orchestration/parallel-executor.js` (audit 2026-06-11
8
+ * flagged them as duplicate — they are NOT). This one races/merges AI PROVIDERS
9
+ * (RACE/CONSENSUS/BEST_OF/MERGE/FALLBACK). The orchestration one runs independent
10
+ * WORKFLOW PHASES concurrently. Different concerns; do not merge (a merge would
11
+ * lose one capability).
6
12
  */
7
13
 
8
14
  const EventEmitter = require('events');
@@ -16,7 +16,7 @@
16
16
  * Based on Auto-Claude's merge system architecture.
17
17
  */
18
18
 
19
- const { execSync } = require('child_process');
19
+ const { execSync, execFileSync } = require('child_process');
20
20
  const fs = require('fs');
21
21
  const path = require('path');
22
22
  const EventEmitter = require('events');
@@ -180,17 +180,29 @@ class SemanticAnalyzer {
180
180
  }));
181
181
 
182
182
  // Extract functions
183
+ // DATA-003: the jsFunction regex can degrade to quadratic on pathological
184
+ // inputs (very large files with lots of whitespace). Cap the analyzed size
185
+ // and the iteration count so a single oversized file can't stall the engine.
186
+ // Normal files (well under the limits) are unaffected.
187
+ const MAX_FUNC_SCAN_BYTES = 200_000;
188
+ const MAX_FUNC_MATCHES = 10_000;
183
189
  let match;
184
- const funcRegex = new RegExp(this.patterns.jsFunction.source, 'gm');
185
- while ((match = funcRegex.exec(content)) !== null) {
186
- const name = match[1] || match[2] || match[3];
187
- if (name) {
188
- elements.functions.push({
189
- type: 'function',
190
- name,
191
- content: this.extractFunctionBody(content, match.index),
192
- location: this.getLocation(content, match.index),
193
- });
190
+ if (content.length <= MAX_FUNC_SCAN_BYTES) {
191
+ const funcRegex = new RegExp(this.patterns.jsFunction.source, 'gm');
192
+ let matchCount = 0;
193
+ while ((match = funcRegex.exec(content)) !== null) {
194
+ if (++matchCount > MAX_FUNC_MATCHES) break;
195
+ const name = match[1] || match[2] || match[3];
196
+ if (name) {
197
+ elements.functions.push({
198
+ type: 'function',
199
+ name,
200
+ content: this.extractFunctionBody(content, match.index),
201
+ location: this.getLocation(content, match.index),
202
+ });
203
+ }
204
+ // Guard against zero-width matches causing an infinite loop.
205
+ if (match.index === funcRegex.lastIndex) funcRegex.lastIndex++;
194
206
  }
195
207
  }
196
208
 
@@ -1521,7 +1533,7 @@ class SemanticMergeEngine extends EventEmitter {
1521
1533
 
1522
1534
  try {
1523
1535
  // Get list of files
1524
- const fileList = execSync(`git ls-tree -r --name-only ${branch}`, {
1536
+ const fileList = execFileSync('git', ['ls-tree', '-r', '--name-only', branch], {
1525
1537
  cwd: this.rootPath,
1526
1538
  encoding: 'utf8',
1527
1539
  })
@@ -1531,7 +1543,7 @@ class SemanticMergeEngine extends EventEmitter {
1531
1543
  for (const filePath of fileList) {
1532
1544
  if (this.shouldProcessFile(filePath)) {
1533
1545
  try {
1534
- const content = execSync(`git show ${branch}:${filePath}`, {
1546
+ const content = execFileSync('git', ['show', `${branch}:${filePath}`], {
1535
1547
  cwd: this.rootPath,
1536
1548
  encoding: 'utf8',
1537
1549
  });
@@ -1564,7 +1576,7 @@ class SemanticMergeEngine extends EventEmitter {
1564
1576
 
1565
1577
  try {
1566
1578
  // Get modified files in this task
1567
- const diffOutput = execSync(`git diff --name-only ${branch || 'main'}...HEAD`, {
1579
+ const diffOutput = execFileSync('git', ['diff', '--name-only', `${branch || 'main'}...HEAD`], {
1568
1580
  cwd: workDir,
1569
1581
  encoding: 'utf8',
1570
1582
  }).trim();