0xray 4.0.36 → 4.0.37

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 (169) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.xray/features.json +1 -1
  3. package/dist/CHANGELOG.md +15 -0
  4. package/dist/analytics/emerging-pattern-detector.d.ts +12 -1
  5. package/dist/analytics/emerging-pattern-detector.js +37 -12
  6. package/dist/analytics/pattern-learning-engine.js +1 -9
  7. package/dist/analytics/predictive-analytics.js +51 -33
  8. package/dist/analytics/prompt-pattern-analyzer.d.ts +1 -0
  9. package/dist/analytics/prompt-pattern-analyzer.js +39 -20
  10. package/dist/analytics/routing-performance-analyzer.d.ts +2 -0
  11. package/dist/analytics/routing-performance-analyzer.js +89 -37
  12. package/dist/analytics/simple-pattern-analyzer.d.ts +4 -0
  13. package/dist/analytics/simple-pattern-analyzer.js +66 -28
  14. package/dist/architect/architect-tools.d.ts +3 -0
  15. package/dist/architect/architect-tools.js +22 -0
  16. package/dist/cli/commands/grok-install.js +1 -1
  17. package/dist/cli/commands/hermes-install.js +11 -5
  18. package/dist/cli/commands/openclaw-install.js +13 -7
  19. package/dist/cli/commands/opencode-install.js +7 -4
  20. package/dist/cli/commands/suit-monitor.d.ts +30 -0
  21. package/dist/cli/commands/suit-monitor.js +128 -0
  22. package/dist/cli/index.js +52 -15
  23. package/dist/cli/suit-pulse.d.ts +29 -0
  24. package/dist/cli/suit-pulse.js +270 -0
  25. package/dist/delegation/agent-delegator.d.ts +4 -2
  26. package/dist/delegation/agent-delegator.js +46 -27
  27. package/dist/delegation/agent-expertise.js +2 -1
  28. package/dist/delegation/analytics/outcome-tracker.d.ts +6 -0
  29. package/dist/delegation/analytics/outcome-tracker.js +17 -3
  30. package/dist/delegation/ast-code-parser.d.ts +5 -0
  31. package/dist/delegation/ast-code-parser.js +53 -48
  32. package/dist/delegation/codebase-context-analyzer.d.ts +3 -7
  33. package/dist/delegation/codebase-context-analyzer.js +188 -159
  34. package/dist/delegation/session-coordinator.js +3 -2
  35. package/dist/enforcement/loaders/codex-loader.js +18 -14
  36. package/dist/enforcement/validators/architecture-validators.js +115 -9
  37. package/dist/governance/codex-policy.service.d.ts +12 -2
  38. package/dist/governance/codex-policy.service.js +91 -16
  39. package/dist/integrations/grok/hooks/session-start.js +6 -6
  40. package/dist/integrations/hooks/goggles-pipeline.mjs +169 -28
  41. package/dist/integrations/hooks/goggles-planes.json +172 -8
  42. package/dist/integrations/hooks/plates.cjs +138 -0
  43. package/dist/integrations/hooks/station-hook-runtime.cjs +63 -5
  44. package/dist/integrations/hooks/write-home.cjs +59 -0
  45. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  46. package/dist/mcps/architect-tools.server.d.ts +6 -0
  47. package/dist/mcps/architect-tools.server.js +19 -18
  48. package/dist/mcps/orchestrator/server.d.ts +4 -3
  49. package/dist/mcps/orchestrator/server.js +29 -17
  50. package/dist/memory-routing/lookup-cache.d.ts +8 -0
  51. package/dist/memory-routing/lookup-cache.js +72 -0
  52. package/dist/memory-routing/plates.d.ts +1 -1
  53. package/dist/memory-routing/plates.js +26 -0
  54. package/dist/memory-routing/provider-loader.js +3 -0
  55. package/dist/metrics/agent-metrics.d.ts +14 -5
  56. package/dist/metrics/agent-metrics.js +362 -224
  57. package/dist/nucleus/synthesis-consult-receipt.js +38 -16
  58. package/dist/orchestrator/enhanced-multi-agent-orchestrator.d.ts +5 -0
  59. package/dist/orchestrator/enhanced-multi-agent-orchestrator.js +9 -0
  60. package/dist/orchestrator/multi-agent-orchestration-coordinator.js +1 -2
  61. package/dist/orchestrator/orchestrator.js +1 -2
  62. package/dist/processors/implementations/agents-md-validation-processor.d.ts +0 -1
  63. package/dist/processors/implementations/agents-md-validation-processor.js +0 -11
  64. package/dist/processors/implementations/console-log-guard-processor.js +39 -50
  65. package/dist/processors/implementations/performance-budget-processor.d.ts +0 -5
  66. package/dist/processors/implementations/performance-budget-processor.js +29 -38
  67. package/dist/processors/implementations/storytelling-trigger-processor.d.ts +5 -0
  68. package/dist/processors/implementations/storytelling-trigger-processor.js +69 -16
  69. package/dist/processors/implementations/test-auto-creation-processor.js +39 -36
  70. package/dist/reporting/framework-reporting-system.d.ts +3 -0
  71. package/dist/reporting/framework-reporting-system.js +61 -8
  72. package/dist/reporting/log-parser.js +188 -105
  73. package/dist/reporting/metrics.js +42 -18
  74. package/dist/security/prompt-security-validator.js +8 -7
  75. package/dist/security/security-auditor.d.ts +4 -0
  76. package/dist/security/security-auditor.js +132 -57
  77. package/dist/security/security-hardener.d.ts +2 -0
  78. package/dist/security/security-hardener.js +31 -6
  79. package/dist/security/security-headers.d.ts +2 -0
  80. package/dist/security/security-headers.js +23 -21
  81. package/dist/security/security-scanner.js +18 -4
  82. package/dist/session/session-cleanup-manager.d.ts +2 -0
  83. package/dist/session/session-cleanup-manager.js +29 -8
  84. package/dist/session/session-monitor.d.ts +14 -4
  85. package/dist/session/session-monitor.js +208 -106
  86. package/dist/session/session-state-manager.d.ts +11 -0
  87. package/dist/session/session-state-manager.js +90 -8
  88. package/docs-site/docs/plates/activity-log.md +38 -0
  89. package/docs-site/docs/plates/cursor-compact.md +35 -0
  90. package/docs-site/docs/plates/grok-compact.md +35 -0
  91. package/docs-site/docs/plates/index.md +25 -3
  92. package/docs-site/docs/plates/inference-files.md +35 -0
  93. package/docs-site/docs/plates/kept-line.md +35 -0
  94. package/docs-site/docs/plates/lens-gate.md +35 -0
  95. package/docs-site/docs/plates/lens-page.md +35 -0
  96. package/docs-site/docs/plates/notes-page.md +35 -0
  97. package/docs-site/docs/plates/package-face.md +35 -0
  98. package/docs-site/docs/plates/payload-heat.md +35 -0
  99. package/docs-site/docs/plates/pickup-stamp.md +35 -0
  100. package/docs-site/docs/plates/pre-tool.md +35 -0
  101. package/docs-site/docs/plates/record-map.md +41 -0
  102. package/docs-site/docs/plates/reflection-page.md +35 -0
  103. package/docs-site/docs/plates/review.md +57 -0
  104. package/docs-site/docs/plates/session-capture.md +38 -0
  105. package/docs-site/docs/plates/site-manual.md +35 -0
  106. package/docs-site/docs/plates/stamp-plate.md +35 -0
  107. package/docs-site/docs/plates/station-card.md +35 -0
  108. package/docs-site/docs/plates/station-heat.md +35 -0
  109. package/docs-site/docs/plates/suit-organs.md +38 -0
  110. package/docs-site/docs/plates/suit-settings.md +35 -0
  111. package/docs-site/docs/plates/suit-wear.md +37 -0
  112. package/docs-site/docs/plates/trail-state.md +35 -0
  113. package/docs-site/docs/plates/work-fresh.md +35 -0
  114. package/docs-site/docs/plates/write-home.md +38 -0
  115. package/package.json +3 -2
  116. package/scripts/node/install-bridges.cjs +302 -78
  117. package/scripts/node/postinstall.cjs +14 -3
  118. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  119. package/src/mcps/architect-tools.server.ts +25 -20
  120. package/src/mcps/orchestrator/server.ts +33 -19
  121. package/xray/features.json +1 -1
  122. package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts.map +0 -1
  123. package/vendor/@0xray/repertoire/dist/RepertoireService.js.map +0 -1
  124. package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.d.ts.map +0 -1
  125. package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.js.map +0 -1
  126. package/vendor/@0xray/repertoire/dist/index.d.ts.map +0 -1
  127. package/vendor/@0xray/repertoire/dist/index.js.map +0 -1
  128. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.d.ts.map +0 -1
  129. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.js.map +0 -1
  130. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.d.ts.map +0 -1
  131. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.js.map +0 -1
  132. package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.d.ts.map +0 -1
  133. package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.js.map +0 -1
  134. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.d.ts.map +0 -1
  135. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js.map +0 -1
  136. package/vendor/@0xray/repertoire/dist/mcp/server.d.ts.map +0 -1
  137. package/vendor/@0xray/repertoire/dist/mcp/server.js.map +0 -1
  138. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.d.ts.map +0 -1
  139. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.js.map +0 -1
  140. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.d.ts.map +0 -1
  141. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.js.map +0 -1
  142. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.d.ts.map +0 -1
  143. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js.map +0 -1
  144. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.d.ts.map +0 -1
  145. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.js.map +0 -1
  146. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.d.ts.map +0 -1
  147. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js.map +0 -1
  148. package/vendor/@0xray/repertoire/dist/paths.d.ts.map +0 -1
  149. package/vendor/@0xray/repertoire/dist/paths.js.map +0 -1
  150. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts.map +0 -1
  151. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js.map +0 -1
  152. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts.map +0 -1
  153. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js.map +0 -1
  154. package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.d.ts.map +0 -1
  155. package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.js.map +0 -1
  156. package/vendor/@0xray/repertoire/dist/registry/confidence-decay.d.ts.map +0 -1
  157. package/vendor/@0xray/repertoire/dist/registry/confidence-decay.js.map +0 -1
  158. package/vendor/@0xray/repertoire/dist/registry/signal-prune.d.ts.map +0 -1
  159. package/vendor/@0xray/repertoire/dist/registry/signal-prune.js.map +0 -1
  160. package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.d.ts.map +0 -1
  161. package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.js.map +0 -1
  162. package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.d.ts.map +0 -1
  163. package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.js.map +0 -1
  164. package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.d.ts.map +0 -1
  165. package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.js.map +0 -1
  166. package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.d.ts.map +0 -1
  167. package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.js.map +0 -1
  168. package/vendor/@0xray/repertoire/dist/types.d.ts.map +0 -1
  169. package/vendor/@0xray/repertoire/dist/types.js.map +0 -1
@@ -1,6 +1,60 @@
1
+ import { createHash } from "crypto";
1
2
  import * as fs from "fs";
2
3
  import * as path from "path";
3
4
  import { frameworkLogger } from "../core/framework-logger.js";
5
+ function bytesHash(bytes) {
6
+ return createHash("sha256").update(bytes).digest("hex");
7
+ }
8
+ const JS_IMPORT_PATTERNS = [
9
+ /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g,
10
+ /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
11
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
12
+ ];
13
+ const JS_EXPORT_PATTERNS = [
14
+ /export\s+(?:const|let|var|function|class|interface|type)\s+(\w+)/g,
15
+ /export\s+{\s*([^}]+)\s*}/g,
16
+ /export\s+default/g,
17
+ ];
18
+ const PY_IMPORT_PATTERNS = [/from\s+([^\s]+)\s+import/g, /import\s+([^\s]+)/g];
19
+ const JAVA_IMPORT_PATTERN = /^import\s+([^;]+);/gm;
20
+ const JAVA_EXPORT_PATTERN = /(?:public\s+)?(?:class|interface|enum)\s+(\w+)/g;
21
+ const CONFIG_FILE_PATTERNS = [
22
+ /package\.json$/,
23
+ /tsconfig\.json$/,
24
+ /webpack\.config\./,
25
+ /\.eslintrc/,
26
+ /\.prettierrc/,
27
+ /Dockerfile/,
28
+ /docker-compose\.yml/,
29
+ /\.env/,
30
+ ];
31
+ const IMPORT_EXTENSIONS = ["", ".ts", ".tsx", ".js", ".jsx", ".py", ".java"];
32
+ const FRAMEWORK_NEEDLES = [
33
+ ["react", "React"],
34
+ ["vue", "Vue.js"],
35
+ ["angular", "Angular"],
36
+ ["svelte", "Svelte"],
37
+ ["express", "Express.js"],
38
+ ["nestjs", "NestJS"],
39
+ ["nextjs", "Next.js"],
40
+ ["nuxt", "Nuxt.js"],
41
+ ];
42
+ function countLines(content) {
43
+ let lines = 1;
44
+ for (let i = 0; i < content.length; i++) {
45
+ if (content.charCodeAt(i) === 10)
46
+ lines++;
47
+ }
48
+ return lines;
49
+ }
50
+ function resetAndCollect(pattern, content, into) {
51
+ pattern.lastIndex = 0;
52
+ let match;
53
+ while ((match = pattern.exec(content)) !== null) {
54
+ if (match[1])
55
+ into.push(match[1]);
56
+ }
57
+ }
4
58
  export class CodebaseContextAnalyzer {
5
59
  projectRoot;
6
60
  memoryConfig;
@@ -135,9 +189,18 @@ export class CodebaseContextAnalyzer {
135
189
  if (filePath) {
136
190
  try {
137
191
  const currentStats = fs.statSync(filePath);
138
- const cachedMtime = cached.lastModified?.getTime() || 0;
139
- if (currentStats.mtime.getTime() !== cachedMtime) {
140
- // File has changed, invalidate cache
192
+ const cachedMtime = cached.lastModified?.getTime();
193
+ const sameMeta = cachedMtime !== undefined &&
194
+ currentStats.mtime.getTime() === cachedMtime &&
195
+ cached.size !== undefined &&
196
+ currentStats.size === cached.size;
197
+ if (!sameMeta) {
198
+ this.analysisCache.delete(cacheKey);
199
+ return null;
200
+ }
201
+ // mtime and size collided. Identity includes the bytes.
202
+ const hash = bytesHash(fs.readFileSync(filePath, "utf8"));
203
+ if (cached.contentHash !== hash) {
141
204
  this.analysisCache.delete(cacheKey);
142
205
  return null;
143
206
  }
@@ -172,29 +235,37 @@ export class CodebaseContextAnalyzer {
172
235
  * Extract file path from cache key for validation
173
236
  */
174
237
  extractFilePathFromCacheKey(cacheKey) {
175
- const match = cacheKey.match(/^file:(.+?:\d+)$/);
176
- if (match && match[1]) {
177
- const parts = match[1].split(":");
178
- return parts[0] || null;
179
- }
180
- return null;
238
+ if (!cacheKey.startsWith("file:"))
239
+ return null;
240
+ const rest = cacheKey.slice("file:".length);
241
+ const colon = rest.lastIndexOf(":");
242
+ if (colon <= 0)
243
+ return null;
244
+ const mtime = rest.slice(colon + 1);
245
+ if (!/^\d+$/.test(mtime))
246
+ return null;
247
+ return rest.slice(0, colon);
181
248
  }
182
249
  /**
183
250
  * Stream file content for large files to reduce memory usage
184
251
  */
185
252
  async streamFileContent(filePath) {
186
253
  return new Promise((resolve, reject) => {
187
- let content = "";
254
+ const chunks = [];
255
+ let size = 0;
188
256
  const stream = fs.createReadStream(filePath, { encoding: "utf8" });
189
257
  stream.on("data", (chunk) => {
190
- content += chunk;
258
+ const text = typeof chunk === "string" ? chunk : String(chunk);
259
+ size += text.length;
191
260
  // Prevent excessive memory usage during streaming
192
- if (content.length > this.memoryConfig.maxFileSizeBytes) {
261
+ if (size > this.memoryConfig.maxFileSizeBytes) {
193
262
  stream.destroy();
194
263
  reject(new Error("File too large for streaming"));
264
+ return;
195
265
  }
266
+ chunks.push(text);
196
267
  });
197
- stream.on("end", () => resolve(content));
268
+ stream.on("end", () => resolve(chunks.join("")));
198
269
  stream.on("error", reject);
199
270
  });
200
271
  }
@@ -206,9 +277,15 @@ export class CodebaseContextAnalyzer {
206
277
  const modules = new Map();
207
278
  const dependencyGraph = new Map();
208
279
  const languages = new Map();
209
- const scanDirectory = async (dirPath, relativePath = "") => {
280
+ const scanDirectory = async (dirPath, relativePath = "", checkModule = false) => {
210
281
  try {
211
282
  const entries = fs.readdirSync(dirPath, { withFileTypes: true });
283
+ // Names decide module vs tree. Do not walk this snapshot.
284
+ if (checkModule && this.isModuleNames(entries.map((entry) => entry.name))) {
285
+ const moduleInfo = await this.analyzeModule(dirPath, relativePath, jobId);
286
+ modules.set(relativePath, moduleInfo);
287
+ return;
288
+ }
212
289
  // Process files in batches to control memory usage
213
290
  const fileEntries = [];
214
291
  const dirEntries = [];
@@ -221,11 +298,9 @@ export class CodebaseContextAnalyzer {
221
298
  continue;
222
299
  }
223
300
  if (entry.isDirectory()) {
224
- const isModule = this.isModuleDirectory(entryPath);
225
301
  dirEntries.push({
226
302
  path: entryPath,
227
303
  relativePath: entryRelativePath,
228
- isModule,
229
304
  });
230
305
  }
231
306
  else if (entry.isFile()) {
@@ -237,13 +312,7 @@ export class CodebaseContextAnalyzer {
237
312
  }
238
313
  // Process directories first (may contain modules)
239
314
  for (const dirEntry of dirEntries) {
240
- if (dirEntry.isModule) {
241
- const moduleInfo = await this.analyzeModule(dirEntry.path, dirEntry.relativePath, jobId);
242
- modules.set(dirEntry.relativePath, moduleInfo);
243
- }
244
- else {
245
- await scanDirectory(dirEntry.path, dirEntry.relativePath);
246
- }
315
+ await scanDirectory(dirEntry.path, dirEntry.relativePath, true);
247
316
  }
248
317
  // Process files with concurrent processing for better performance
249
318
  const concurrencyLimit = this.memoryConfig.enableConcurrentProcessing
@@ -341,8 +410,8 @@ export class CodebaseContextAnalyzer {
341
410
  let imports = [];
342
411
  let exports = [];
343
412
  try {
344
- // Check cache first for performance
345
- const cacheKey = `file:${relativePath}:${stats.mtime.getTime()}`;
413
+ // Same path and mtime still miss when size or bytes change.
414
+ const cacheKey = `file:${filePath}:${stats.mtime.getTime()}`;
346
415
  const cached = this.getCachedAnalysis(cacheKey);
347
416
  if (cached) {
348
417
  return {
@@ -365,7 +434,7 @@ export class CodebaseContextAnalyzer {
365
434
  this.memoryConfig.enableStreaming && stats.size > 100 * 1024
366
435
  ? await this.streamFileContent(filePath)
367
436
  : fs.readFileSync(filePath, "utf8");
368
- linesOfCode = content.split("\n").length;
437
+ linesOfCode = countLines(content);
369
438
  const importExportData = await this.extractImportsExports(content, language, jobId);
370
439
  imports = importExportData.imports;
371
440
  exports = importExportData.exports;
@@ -376,6 +445,9 @@ export class CodebaseContextAnalyzer {
376
445
  imports,
377
446
  exports,
378
447
  content,
448
+ lastModified: stats.mtime,
449
+ size: stats.size,
450
+ contentHash: bytesHash(content),
379
451
  timestamp: Date.now(),
380
452
  });
381
453
  }
@@ -427,6 +499,7 @@ export class CodebaseContextAnalyzer {
427
499
  const dependents = new Set();
428
500
  const scanModuleFiles = async (modulePath, moduleRelativePath) => {
429
501
  try {
502
+ // List at the walk so a file created after classification is included.
430
503
  const entries = fs.readdirSync(modulePath, { withFileTypes: true });
431
504
  for (const entry of entries) {
432
505
  const entryPath = path.join(modulePath, entry.name);
@@ -499,27 +572,13 @@ export class CodebaseContextAnalyzer {
499
572
  extractJsTsImportsExports(content) {
500
573
  const imports = [];
501
574
  const exports = [];
502
- // Import patterns
503
- const importPatterns = [
504
- /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g,
505
- /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
506
- /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
507
- ];
508
- importPatterns.forEach((pattern) => {
509
- let match;
510
- while ((match = pattern.exec(content)) !== null) {
511
- if (match[1]) {
512
- imports.push(match[1]);
513
- }
514
- }
515
- });
575
+ for (const pattern of JS_IMPORT_PATTERNS) {
576
+ resetAndCollect(pattern, content, imports);
577
+ }
516
578
  // Export patterns
517
- const exportPatterns = [
518
- /export\s+(?:const|let|var|function|class|interface|type)\s+(\w+)/g,
519
- /export\s+{\s*([^}]+)\s*}/g,
520
- /export\s+default/g,
521
- ];
579
+ const exportPatterns = JS_EXPORT_PATTERNS;
522
580
  exportPatterns.forEach((pattern) => {
581
+ pattern.lastIndex = 0;
523
582
  let match;
524
583
  while ((match = pattern.exec(content)) !== null) {
525
584
  if (match[1]) {
@@ -546,16 +605,9 @@ export class CodebaseContextAnalyzer {
546
605
  extractPythonImportsExports(content) {
547
606
  const imports = [];
548
607
  const exports = [];
549
- // Import patterns
550
- const importPatterns = [/from\s+([^\s]+)\s+import/g, /import\s+([^\s]+)/g];
551
- importPatterns.forEach((pattern) => {
552
- let match;
553
- while ((match = pattern.exec(content)) !== null) {
554
- if (match[1]) {
555
- imports.push(match[1]);
556
- }
557
- }
558
- });
608
+ for (const pattern of PY_IMPORT_PATTERNS) {
609
+ resetAndCollect(pattern, content, imports);
610
+ }
559
611
  // Export patterns (Python doesn't have explicit exports, but __all__ can indicate public API)
560
612
  const allMatch = content.match(/__all__\s*=\s*\[([^\]]+)\]/);
561
613
  if (allMatch && allMatch[1]) {
@@ -573,21 +625,8 @@ export class CodebaseContextAnalyzer {
573
625
  extractJavaImportsExports(content) {
574
626
  const imports = [];
575
627
  const exports = [];
576
- // Import patterns
577
- const importPattern = /^import\s+([^;]+);/gm;
578
- let match;
579
- while ((match = importPattern.exec(content)) !== null) {
580
- if (match[1]) {
581
- imports.push(match[1]);
582
- }
583
- }
584
- // Export patterns (public classes/interfaces)
585
- const exportPattern = /(?:public\s+)?(?:class|interface|enum)\s+(\w+)/g;
586
- while ((match = exportPattern.exec(content)) !== null) {
587
- if (match[1]) {
588
- exports.push(match[1]);
589
- }
590
- }
628
+ resetAndCollect(JAVA_IMPORT_PATTERN, content, imports);
629
+ resetAndCollect(JAVA_EXPORT_PATTERN, content, exports);
591
630
  return {
592
631
  imports: Array.from(new Set(imports)),
593
632
  exports: Array.from(new Set(exports)),
@@ -597,7 +636,16 @@ export class CodebaseContextAnalyzer {
597
636
  * Build dependency graph between files
598
637
  */
599
638
  async buildDependencyGraph(fileGraph, dependencyGraph) {
600
- for (const [filePath, fileInfo] of Array.from(fileGraph)) {
639
+ const existsCache = new Map();
640
+ const fileExists = (absPath) => {
641
+ const cached = existsCache.get(absPath);
642
+ if (cached !== undefined)
643
+ return cached;
644
+ const found = fs.existsSync(absPath);
645
+ existsCache.set(absPath, found);
646
+ return found;
647
+ };
648
+ for (const [filePath, fileInfo] of fileGraph) {
601
649
  const dependencies = new Set();
602
650
  for (const importPath of fileInfo.imports) {
603
651
  // Resolve relative imports
@@ -605,21 +653,11 @@ export class CodebaseContextAnalyzer {
605
653
  try {
606
654
  const resolvedPath = path.resolve(path.dirname(fileInfo.path), importPath);
607
655
  const resolvedRelative = path.relative(this.projectRoot, resolvedPath);
608
- // Check for file extensions
609
- const possibleExtensions = [
610
- "",
611
- ".ts",
612
- ".tsx",
613
- ".js",
614
- ".jsx",
615
- ".py",
616
- ".java",
617
- ];
618
656
  let foundFile = null;
619
- for (const ext of possibleExtensions) {
657
+ for (const ext of IMPORT_EXTENSIONS) {
620
658
  const testPath = resolvedRelative + ext;
621
659
  if (fileGraph.has(testPath) ||
622
- fs.existsSync(path.join(this.projectRoot, testPath))) {
660
+ fileExists(path.join(this.projectRoot, testPath))) {
623
661
  foundFile = testPath;
624
662
  break;
625
663
  }
@@ -648,34 +686,61 @@ export class CodebaseContextAnalyzer {
648
686
  const patterns = [];
649
687
  let structure = "monolithic";
650
688
  const entryPoints = [];
651
- // Framework detection
652
- if (this.hasFramework(fileGraph, "react"))
653
- frameworks.push("React");
654
- if (this.hasFramework(fileGraph, "vue"))
655
- frameworks.push("Vue.js");
656
- if (this.hasFramework(fileGraph, "angular"))
657
- frameworks.push("Angular");
658
- if (this.hasFramework(fileGraph, "svelte"))
659
- frameworks.push("Svelte");
660
- if (this.hasFramework(fileGraph, "express"))
661
- frameworks.push("Express.js");
662
- if (this.hasFramework(fileGraph, "nestjs"))
663
- frameworks.push("NestJS");
664
- if (this.hasFramework(fileGraph, "nextjs"))
665
- frameworks.push("Next.js");
666
- if (this.hasFramework(fileGraph, "nuxt"))
667
- frameworks.push("Nuxt.js");
668
- // Pattern detection
669
- if (this.detectMVCPattern(fileGraph))
689
+ const foundFrameworks = new Set();
690
+ let hasControllers = false;
691
+ let hasModels = false;
692
+ let hasViews = false;
693
+ let hasRepository = false;
694
+ let hasFactory = false;
695
+ let hasObserver = false;
696
+ let hasMicroservices = false;
697
+ // One pass over paths and file text. Same booleans as the per-needle scans.
698
+ for (const [filePath, fileInfo] of fileGraph) {
699
+ if (!hasControllers && filePath.includes("controller"))
700
+ hasControllers = true;
701
+ if (!hasModels && filePath.includes("model"))
702
+ hasModels = true;
703
+ if (!hasViews && filePath.includes("view"))
704
+ hasViews = true;
705
+ if (!hasRepository && filePath.includes("repository"))
706
+ hasRepository = true;
707
+ if (!hasFactory && filePath.includes("factory"))
708
+ hasFactory = true;
709
+ if (!hasMicroservices &&
710
+ (filePath.includes("docker") ||
711
+ filePath.includes("kubernetes") ||
712
+ filePath.includes("service"))) {
713
+ hasMicroservices = true;
714
+ }
715
+ const content = fileInfo.content;
716
+ if (!content)
717
+ continue;
718
+ if (!hasObserver && (content.includes("subscribe") || content.includes("observe"))) {
719
+ hasObserver = true;
720
+ }
721
+ if (foundFrameworks.size === FRAMEWORK_NEEDLES.length)
722
+ continue;
723
+ const lower = content.toLowerCase();
724
+ for (const [needle, label] of FRAMEWORK_NEEDLES) {
725
+ if (!foundFrameworks.has(label) && lower.includes(needle)) {
726
+ foundFrameworks.add(label);
727
+ }
728
+ }
729
+ }
730
+ for (const [, label] of FRAMEWORK_NEEDLES) {
731
+ if (foundFrameworks.has(label))
732
+ frameworks.push(label);
733
+ }
734
+ if (hasControllers && hasModels && hasViews)
670
735
  patterns.push("MVC");
671
- if (this.detectRepositoryPattern(fileGraph))
736
+ if (hasRepository)
672
737
  patterns.push("Repository");
673
- if (this.detectObserverPattern(fileGraph))
738
+ if (hasObserver)
674
739
  patterns.push("Observer");
675
- if (this.detectFactoryPattern(fileGraph))
740
+ if (hasFactory)
676
741
  patterns.push("Factory");
677
742
  // Structure detection
678
- if (modules.size > 5 && this.hasMicroservicesIndicators(fileGraph)) {
743
+ if (modules.size > 5 && hasMicroservices) {
679
744
  structure = "microservices";
680
745
  }
681
746
  else if (modules.size > 0) {
@@ -895,33 +960,26 @@ export class CodebaseContextAnalyzer {
895
960
  return risks;
896
961
  }
897
962
  // Helper methods
898
- isModuleDirectory(dirPath) {
899
- try {
900
- const entries = fs.readdirSync(dirPath);
901
- return (entries.some((entry) => {
902
- const ext = path.extname(entry);
903
- return this.supportedLanguages[ext];
904
- }) &&
905
- entries.some((entry) => entry === "package.json" ||
906
- entry === "index.ts" ||
907
- entry === "index.js"));
908
- }
909
- catch {
910
- return false;
963
+ isModuleNames(names) {
964
+ let hasSource = false;
965
+ let hasMarker = false;
966
+ for (const entry of names) {
967
+ const ext = path.extname(entry);
968
+ if (this.supportedLanguages[ext]) {
969
+ hasSource = true;
970
+ }
971
+ if (entry === "package.json" ||
972
+ entry === "index.ts" ||
973
+ entry === "index.js") {
974
+ hasMarker = true;
975
+ }
976
+ if (hasSource && hasMarker)
977
+ return true;
911
978
  }
979
+ return false;
912
980
  }
913
981
  isConfigFile(filePath) {
914
- const configPatterns = [
915
- /package\.json$/,
916
- /tsconfig\.json$/,
917
- /webpack\.config\./,
918
- /\.eslintrc/,
919
- /\.prettierrc/,
920
- /Dockerfile/,
921
- /docker-compose\.yml/,
922
- /\.env/,
923
- ];
924
- return configPatterns.some((pattern) => pattern.test(filePath));
982
+ return CONFIG_FILE_PATTERNS.some((pattern) => pattern.test(filePath));
925
983
  }
926
984
  classifyModule(relativePath, files) {
927
985
  if (relativePath.includes("test") || relativePath.includes("spec")) {
@@ -957,35 +1015,6 @@ export class CodebaseContextAnalyzer {
957
1015
  const sourceFile = files.find((f) => f.isSourceCode);
958
1016
  return sourceFile?.relativePath;
959
1017
  }
960
- hasFramework(fileGraph, framework) {
961
- for (const file of Array.from(fileGraph.values())) {
962
- if (file.content?.toLowerCase().includes(framework.toLowerCase())) {
963
- return true;
964
- }
965
- }
966
- return false;
967
- }
968
- detectMVCPattern(fileGraph) {
969
- const hasControllers = Array.from(fileGraph.keys()).some((path) => path.includes("controller"));
970
- const hasModels = Array.from(fileGraph.keys()).some((path) => path.includes("model"));
971
- const hasViews = Array.from(fileGraph.keys()).some((path) => path.includes("view"));
972
- return hasControllers && hasModels && hasViews;
973
- }
974
- detectRepositoryPattern(fileGraph) {
975
- return Array.from(fileGraph.keys()).some((path) => path.includes("repository"));
976
- }
977
- detectObserverPattern(fileGraph) {
978
- return Array.from(fileGraph.values()).some((file) => file.content?.includes("subscribe") ||
979
- file.content?.includes("observe"));
980
- }
981
- detectFactoryPattern(fileGraph) {
982
- return Array.from(fileGraph.keys()).some((path) => path.includes("factory"));
983
- }
984
- hasMicroservicesIndicators(fileGraph) {
985
- return Array.from(fileGraph.keys()).some((path) => path.includes("docker") ||
986
- path.includes("kubernetes") ||
987
- path.includes("service"));
988
- }
989
1018
  isEntryPoint(filePath, fileInfo) {
990
1019
  const entryIndicators = [
991
1020
  "index.ts",
@@ -296,9 +296,10 @@ export class SessionCoordinator {
296
296
  resolved = expertItem ? expertItem.value : sharedData[0]?.value;
297
297
  break;
298
298
  case "consensus":
299
- // All values must be the same
299
+ // All values must be the same. Stringify the anchor once.
300
300
  const firstValue = sharedData[0]?.value;
301
- const allSame = sharedData.every((item) => JSON.stringify(item.value) === JSON.stringify(firstValue));
301
+ const firstKey = JSON.stringify(firstValue);
302
+ const allSame = sharedData.every((item) => JSON.stringify(item.value) === firstKey);
302
303
  resolved = allSame ? firstValue : undefined;
303
304
  break;
304
305
  }
@@ -323,21 +323,25 @@ export class CodexLoader extends BaseLoader {
323
323
  }
324
324
  // Term 19: Small, Focused Functions - check function size
325
325
  if (termNum === 19) {
326
- // Match actual function declarations and arrow functions (not variable declarations)
326
+ // Match actual function declarations and arrow functions (not variable declarations).
327
+ // exec indexes the first hit of each match text; indexOf(text) is that same span,
328
+ // and a repeated text rechecks it. Do not copy the tail on every hit.
327
329
  const functionRegex = /(?:^|\n)(?:function\s+\w+|async\s+function\s+\w+|(?:\([^)]*\)|[^=])=>\s*\{)/g;
328
- const matches = newCode.match(functionRegex);
329
- if (matches) {
330
- for (const match of matches) {
331
- const startIdx = newCode.indexOf(match);
332
- const afterMatch = newCode.substring(startIdx + match.length);
333
- const closingIdx = afterMatch.indexOf('}');
334
- if (closingIdx > 0) {
335
- const functionBody = afterMatch.substring(0, closingIdx);
336
- const lines = functionBody.split('\n').filter(l => l.trim().length > 0);
337
- if (lines.length > 30) {
338
- violations.push(`Function with ${lines.length} lines - consider splitting into smaller functions (max 30)`);
339
- break;
340
- }
330
+ const seen = new Set();
331
+ let match;
332
+ while ((match = functionRegex.exec(newCode)) !== null) {
333
+ const text = match[0];
334
+ if (seen.has(text))
335
+ continue;
336
+ seen.add(text);
337
+ const bodyStart = match.index + text.length;
338
+ const closingIdx = newCode.indexOf('}', bodyStart);
339
+ if (closingIdx > bodyStart) {
340
+ const functionBody = newCode.substring(bodyStart, closingIdx);
341
+ const lines = functionBody.split('\n').filter(l => l.trim().length > 0);
342
+ if (lines.length > 30) {
343
+ violations.push(`Function with ${lines.length} lines - consider splitting into smaller functions (max 30)`);
344
+ break;
341
345
  }
342
346
  }
343
347
  }