0xray 4.0.36 → 4.0.38

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 +28 -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
@@ -72,24 +72,46 @@ export function hasValidSynthesisConsultReceipt(todoId, projectRoot = process.cw
72
72
  return false;
73
73
  return validateSynthesisConsultReceipt(receipt, todoId, expected);
74
74
  }
75
+ function verdictFromToken(token) {
76
+ const value = token.toLowerCase();
77
+ if (value === 'approve' || value === 'approved' || value === 'pass')
78
+ return 'PASS';
79
+ if (value === 'reject' || value === 'rejected' || value === 'fail')
80
+ return 'FAIL';
81
+ if (value === 'abstain' ||
82
+ value === 'needs_revision' ||
83
+ value === 'conditional' ||
84
+ value === 'revise') {
85
+ return 'CONDITIONAL';
86
+ }
87
+ return null;
88
+ }
89
+ /** The last single Verdict or DECISION line wins. A menu line that lists every word does not. */
90
+ function lastExplicitVerdict(text) {
91
+ let found = null;
92
+ for (const line of text.split(/\r?\n/)) {
93
+ const trimmed = line.trim().replace(/^[-*•]\s*/, '');
94
+ const match = trimmed.match(/^(?:Verdict|DECISION):\s*(\w+)/i);
95
+ const token = match?.[1];
96
+ if (!token || trimmed.includes('|'))
97
+ continue;
98
+ const verdict = verdictFromToken(token);
99
+ if (verdict)
100
+ found = verdict;
101
+ }
102
+ return found;
103
+ }
75
104
  export function parseConsultVerdictFromText(text) {
76
- const normalized = text.toUpperCase();
77
- const decisionMatch = text.match(/\bDECISION:\s*(\w+)/i);
78
- if (decisionMatch) {
79
- const decision = decisionMatch[1].toLowerCase();
80
- if (decision === 'approve' || decision === 'approved' || decision === 'pass') {
81
- return 'PASS';
82
- }
83
- if (decision === 'reject' || decision === 'rejected' || decision === 'fail') {
84
- return 'FAIL';
85
- }
86
- if (decision === 'abstain' ||
87
- decision === 'needs_revision' ||
88
- decision === 'conditional' ||
89
- decision === 'revise') {
90
- return 'CONDITIONAL';
91
- }
105
+ const explicit = lastExplicitVerdict(text);
106
+ if (explicit)
107
+ return explicit;
108
+ const decisionToken = text.match(/\bDECISION:\s*(\w+)/i)?.[1];
109
+ if (decisionToken) {
110
+ const mapped = verdictFromToken(decisionToken);
111
+ if (mapped)
112
+ return mapped;
92
113
  }
114
+ const normalized = text.toUpperCase();
93
115
  if (/\bCONDITIONAL(\s+PASS)?\b/.test(normalized))
94
116
  return 'CONDITIONAL';
95
117
  if (/\b(?:PASS|SHIP|APPROVE)\b/.test(normalized))
@@ -120,6 +120,11 @@ export declare class EnhancedMultiAgentOrchestrator {
120
120
  * Check for pending spawns that can now execute
121
121
  */
122
122
  private checkPendingSpawns;
123
+ /**
124
+ * Lookup one monitored agent without copying every spawn map.
125
+ * Precedence matches getMonitoringInterface: failed, then completed, then active.
126
+ */
127
+ getMonitoredAgent(agentId: string): SpawnedAgent | undefined;
123
128
  /**
124
129
  * Get clickable agent monitoring interface
125
130
  */
@@ -302,6 +302,15 @@ export class EnhancedMultiAgentOrchestrator {
302
302
  this.spawnAgent(spawn);
303
303
  });
304
304
  }
305
+ /**
306
+ * Lookup one monitored agent without copying every spawn map.
307
+ * Precedence matches getMonitoringInterface: failed, then completed, then active.
308
+ */
309
+ getMonitoredAgent(agentId) {
310
+ return (this.state.failedAgents.get(agentId) ??
311
+ this.state.completedAgents.get(agentId) ??
312
+ this.state.activeAgents.get(agentId));
313
+ }
305
314
  /**
306
315
  * Get clickable agent monitoring interface
307
316
  */
@@ -210,8 +210,7 @@ export class MultiAgentOrchestrationCoordinator {
210
210
  // Wait for completion
211
211
  return new Promise((resolve) => {
212
212
  const checkCompletion = () => {
213
- const monitoringData = this.enhancedOrchestrator.getMonitoringInterface();
214
- const agent = monitoringData[spawnedAgent.id];
213
+ const agent = this.enhancedOrchestrator.getMonitoredAgent(spawnedAgent.id);
215
214
  if (agent?.status === "completed") {
216
215
  resolve({
217
216
  success: true,
@@ -470,8 +470,7 @@ export class XrayOrchestrator {
470
470
  // Wait for agent completion with monitoring
471
471
  return new Promise((resolve, reject) => {
472
472
  const checkCompletion = () => {
473
- const monitoringData = enhancedMultiAgentOrchestrator.getMonitoringInterface();
474
- const agent = monitoringData[spawnedAgent.id];
473
+ const agent = enhancedMultiAgentOrchestrator.getMonitoredAgent(spawnedAgent.id);
475
474
  if (!agent) {
476
475
  reject(new Error(`Agent ${spawnedAgent.id} not found in monitoring data`));
477
476
  return;
@@ -51,7 +51,6 @@ export declare class AgentsMdValidationProcessor {
51
51
  * Calculate days since date
52
52
  */
53
53
  private calculateDaysOld;
54
- private getPackageVersion;
55
54
  private getSectionTemplate;
56
55
  appendMissing(): Promise<{
57
56
  success: boolean;
@@ -176,18 +176,7 @@ export class AgentsMdValidationProcessor {
176
176
  const now = new Date();
177
177
  return Math.ceil((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
178
178
  }
179
- getPackageVersion() {
180
- try {
181
- const pkgPath = path.join(this.projectRoot, "package.json");
182
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
183
- return pkg.version || "0.0.0";
184
- }
185
- catch {
186
- return "0.0.0";
187
- }
188
- }
189
179
  getSectionTemplate(section) {
190
- const version = this.getPackageVersion();
191
180
  const now = new Date().toISOString().split("T")[0];
192
181
  const templates = {
193
182
  "## Available Agents": [
@@ -11,7 +11,13 @@ import { frameworkLogger } from "../../core/framework-logger.js";
11
11
  // ---------------------------------------------------------------------------
12
12
  // Processor
13
13
  // ---------------------------------------------------------------------------
14
- const CONSOLE_METHODS = ["log", "warn", "error", "info", "debug"];
14
+ const CONSOLE_METHOD_PATTERNS = [
15
+ { method: "log", pattern: /\bconsole\.log\s*\(/ },
16
+ { method: "warn", pattern: /\bconsole\.warn\s*\(/ },
17
+ { method: "error", pattern: /\bconsole\.error\s*\(/ },
18
+ { method: "info", pattern: /\bconsole\.info\s*\(/ },
19
+ { method: "debug", pattern: /\bconsole\.debug\s*\(/ },
20
+ ];
15
21
  const TEST_FILE_PATTERN = /\.(test|spec)\.ts$/;
16
22
  export class ConsoleLogGuardProcessor {
17
23
  /**
@@ -34,8 +40,7 @@ export class ConsoleLogGuardProcessor {
34
40
  const line = lines[i];
35
41
  if (!line)
36
42
  continue;
37
- for (const method of CONSOLE_METHODS) {
38
- const pattern = new RegExp(`\\bconsole\\.${method}\\s*\\(`, "g");
43
+ for (const { method, pattern } of CONSOLE_METHOD_PATTERNS) {
39
44
  if (pattern.test(line)) {
40
45
  violations.push({
41
46
  line: lineNum,
@@ -58,75 +63,59 @@ export class ConsoleLogGuardProcessor {
58
63
  * Preserves line structure so line numbers remain valid.
59
64
  */
60
65
  stripComments(content) {
61
- let result = "";
66
+ const parts = [];
62
67
  let i = 0;
68
+ let chunkStart = 0;
63
69
  const len = content.length;
70
+ const flush = (end) => {
71
+ if (end > chunkStart)
72
+ parts.push(content.slice(chunkStart, end));
73
+ chunkStart = end;
74
+ };
64
75
  while (i < len) {
65
- // Single-line comment: // ... newline
66
- if (content[i] === "/" && i + 1 < len && content[i + 1] === "/") {
67
- // Skip until end of line
68
- while (i < len && content[i] !== "\n") {
69
- i++;
70
- }
71
- // Keep the newline character to preserve line numbers
72
- if (i < len && content[i] === "\n") {
73
- result += "\n";
76
+ const c = content[i];
77
+ const next = i + 1 < len ? content[i + 1] : "";
78
+ // Single-line comment: // ... newline. Newline stays in the next chunk.
79
+ if (c === "/" && next === "/") {
80
+ flush(i);
81
+ while (i < len && content[i] !== "\n")
74
82
  i++;
75
- }
83
+ chunkStart = i;
76
84
  }
77
- // Multi-line comment: /* ... */
78
- else if (content[i] === "/" && i + 1 < len && content[i + 1] === "*") {
79
- i += 2; // skip /*
85
+ // Multi-line comment: /* ... */ — keep newlines so line numbers hold.
86
+ else if (c === "/" && next === "*") {
87
+ flush(i);
88
+ i += 2;
80
89
  while (i < len) {
81
90
  if (content[i] === "*" && i + 1 < len && content[i + 1] === "/") {
82
- i += 2; // skip */
91
+ i += 2;
83
92
  break;
84
93
  }
85
- if (content[i] === "\n") {
86
- result += "\n"; // preserve line numbers
87
- }
94
+ if (content[i] === "\n")
95
+ parts.push("\n");
88
96
  i++;
89
97
  }
98
+ chunkStart = i;
90
99
  }
91
- // String literal — skip over to avoid matching // inside strings
92
- else if (content[i] === '"' || content[i] === "'" || content[i] === "`") {
93
- const quote = content[i];
94
- result += content[i];
100
+ // String literal — walk past it so comment markers inside stay code.
101
+ else if (c === '"' || c === "'" || c === "`") {
102
+ const quote = c;
95
103
  i++;
96
104
  while (i < len && content[i] !== quote) {
97
- if (content[i] === "\\" && i + 1 < len) {
98
- result += content[i];
99
- i++;
100
- result += content[i];
105
+ if (content[i] === "\\" && i + 1 < len)
106
+ i += 2;
107
+ else
101
108
  i++;
102
- }
103
- else {
104
- if (content[i] === "\n") {
105
- result += "\n";
106
- }
107
- else {
108
- result += content[i];
109
- }
110
- i++;
111
- }
112
109
  }
113
- if (i < len) {
114
- result += content[i]; // closing quote
110
+ if (i < len)
115
111
  i++;
116
- }
117
112
  }
118
- // Regular character
119
113
  else {
120
- if (content[i] === "\n") {
121
- result += "\n";
122
- }
123
- else {
124
- result += content[i];
125
- }
126
114
  i++;
127
115
  }
128
116
  }
129
- return result;
117
+ flush(len);
118
+ return parts.join("");
130
119
  }
131
120
  }
132
121
  // ---------------------------------------------------------------------------
@@ -40,10 +40,5 @@ export declare class PerformanceBudgetProcessor {
40
40
  * Does NOT require a full AST – keeps the processor zero-dependency.
41
41
  */
42
42
  private extractFunctions;
43
- /**
44
- * Measure the maximum nesting depth by tracking control-flow keywords
45
- * (if / for / while / switch / try) inside the function body.
46
- */
47
- private measureMaxNesting;
48
43
  }
49
44
  export declare function runPerformanceBudgetCheck(context: PreValidateContext): Promise<ProcessorExecutionResult>;
@@ -43,8 +43,9 @@ export class PerformanceBudgetProcessor {
43
43
  }
44
44
  // --- function-level checks ---
45
45
  const funcViolations = this.checkFunctionComplexity(content);
46
- for (const v of funcViolations) {
47
- violations.push({ ...v, filePath });
46
+ for (const violation of funcViolations) {
47
+ violation.filePath = filePath;
48
+ violations.push(violation);
48
49
  }
49
50
  return violations;
50
51
  }
@@ -67,7 +68,7 @@ export class PerformanceBudgetProcessor {
67
68
  });
68
69
  }
69
70
  // nesting depth
70
- const depth = this.measureMaxNesting(fn.body);
71
+ const depth = fn.nestingDepth;
71
72
  if (depth > this.config.maxNestingDepth) {
72
73
  violations.push({
73
74
  type: "nestingTooDeep",
@@ -123,64 +124,54 @@ export class PerformanceBudgetProcessor {
123
124
  const paramCount = params
124
125
  ? params.split(",").filter((p) => p.trim().length > 0).length
125
126
  : 0;
126
- // Collect body lines until brace depth returns to 0
127
+ // Walk the body once: brace span, line count, and nesting share the same lines.
128
+ // Blank lines are skipped, matching the previous join-then-split of kept lines.
127
129
  let depth = 0;
128
130
  let started = false;
129
- const bodyLines = [];
131
+ let bodyLineCount = 0;
132
+ let maxDepth = 0;
133
+ let currentDepth = 0;
130
134
  for (let j = i; j < lines.length; j++) {
131
135
  const l = lines[j];
132
136
  if (!l)
133
137
  continue;
134
- for (const ch of l) {
135
- if (ch === "{") {
138
+ bodyLineCount++;
139
+ for (let k = 0; k < l.length; k++) {
140
+ const ch = l.charCodeAt(k);
141
+ if (ch === 123) {
136
142
  depth++;
137
143
  started = true;
138
144
  }
139
- else if (ch === "}") {
145
+ else if (ch === 125) {
140
146
  depth--;
141
147
  }
142
148
  }
143
- bodyLines.push(l);
149
+ const trimmed = l.trim();
150
+ const openingMatches = trimmed.match(/\b(if|for|while|switch|try|catch|else)\b/g);
151
+ if (openingMatches) {
152
+ // 'else' and 'catch' partially close+reopen, so net +0 for those
153
+ const netOpen = openingMatches.length -
154
+ (trimmed.includes("else") ? 1 : 0) -
155
+ (trimmed.includes("catch") ? 1 : 0);
156
+ currentDepth += Math.max(0, netOpen);
157
+ if (currentDepth > maxDepth)
158
+ maxDepth = currentDepth;
159
+ }
160
+ const closeBraces = (trimmed.match(/}/g) || []).length;
161
+ currentDepth = Math.max(0, currentDepth - closeBraces);
144
162
  if (started && depth <= 0)
145
163
  break;
146
164
  }
147
- const body = bodyLines.join("\n");
148
165
  results.push({
149
166
  name,
150
167
  startLine: i + 1,
151
- body,
152
- bodyLineCount: bodyLines.length,
168
+ bodyLineCount,
153
169
  paramCount,
170
+ nestingDepth: maxDepth,
154
171
  });
155
172
  }
156
173
  return results;
157
174
  }
158
- /**
159
- * Measure the maximum nesting depth by tracking control-flow keywords
160
- * (if / for / while / switch / try) inside the function body.
161
- */
162
- measureMaxNesting(body) {
163
- let maxDepth = 0;
164
- let currentDepth = 0;
165
- const lines = body.split("\n");
166
- for (const line of lines) {
167
- const trimmed = line.trim();
168
- // Count opening structures that increase nesting
169
- const openingMatches = trimmed.match(/\b(if|for|while|switch|try|catch|else)\b/g);
170
- if (openingMatches) {
171
- // 'else' and 'catch' partially close+reopen, so net +0 for those
172
- const netOpen = openingMatches.length -
173
- (trimmed.includes("else") ? 1 : 0) -
174
- (trimmed.includes("catch") ? 1 : 0);
175
- currentDepth += Math.max(0, netOpen);
176
- maxDepth = Math.max(maxDepth, currentDepth);
177
- }
178
- // Simple heuristic: decrease depth when line closes braces
179
- const closeBraces = (trimmed.match(/}/g) || []).length;
180
- currentDepth = Math.max(0, currentDepth - closeBraces);
181
- }
182
- return maxDepth;
183
- }
184
175
  }
185
176
  // ---------------------------------------------------------------------------
186
177
  // Standalone runner for processor-manager integration
@@ -17,6 +17,11 @@ export declare class StorytellingTriggerProcessor extends PostProcessor {
17
17
  private extractDecisions;
18
18
  private synthesizeInferences;
19
19
  private getCommitsBetween;
20
+ /**
21
+ * One `git log --name-only` for the range. Same paths as `git diff --name-only HASH~1 HASH`
22
+ * for non-merge commits. Null means the caller should fall back per commit.
23
+ */
24
+ private fileNamesByCommit;
20
25
  private getRecentCommits;
21
26
  private summarizeCommits;
22
27
  private getLastTag;
@@ -156,8 +156,13 @@ export class StorytellingTriggerProcessor extends PostProcessor {
156
156
  if (diff.uniqueDirs.length > 0) {
157
157
  lines.push("## Areas Touched");
158
158
  lines.push("");
159
+ const touched = diff.filesAdded.concat(diff.filesModified);
159
160
  for (const dir of diff.uniqueDirs.slice(0, 15)) {
160
- const count = [...diff.filesAdded, ...diff.filesModified].filter(f => f.startsWith(dir)).length;
161
+ let count = 0;
162
+ for (const fileName of touched) {
163
+ if (fileName.startsWith(dir))
164
+ count++;
165
+ }
161
166
  lines.push(`- \`${dir}\` (${count} files)`);
162
167
  }
163
168
  lines.push("");
@@ -370,12 +375,16 @@ export class StorytellingTriggerProcessor extends PostProcessor {
370
375
  }
371
376
  return inferences;
372
377
  }
373
- getCommitsBetween(from, to) {
378
+ getCommitsBetween(from, to, limit) {
374
379
  try {
375
- const log = this.git(`git log ${from}..${to} --format="%H||%s||%an||%aI" --shortstat --no-merges`);
380
+ // Newest-first, so -n matches slice(0, limit). %x1e starts every commit, including
381
+ // one with no shortstat — a blank-line split would glue that header to the next.
382
+ const limitArg = typeof limit === "number" && limit > 0 ? `-n ${Math.floor(limit)} ` : "";
383
+ const log = this.git(`git log ${limitArg}${from}..${to} --format="%x1e%H||%s||%an||%aI" --shortstat --no-merges`);
376
384
  if (!log)
377
385
  return [];
378
- const commitBlocks = log.split(/\n{2,}/).filter((b) => b.trim().length > 0 && b.includes("||"));
386
+ const namesByHash = this.fileNamesByCommit(from, to, limit);
387
+ const commitBlocks = log.split("\x1e").filter((b) => b.includes("||"));
379
388
  return commitBlocks.map((block) => {
380
389
  const lines = block.trim().split("\n");
381
390
  const headerLine = lines.find((l) => l.includes("||")) || "";
@@ -384,15 +393,21 @@ export class StorytellingTriggerProcessor extends PostProcessor {
384
393
  const filesChanged = parseInt(statsLine.match(/(\d+) files? changed/)?.[1] || "0", 10);
385
394
  const insertions = parseInt(statsLine.match(/(\d+) insertion/)?.[1] || "0", 10);
386
395
  const deletions = parseInt(statsLine.match(/(\d+) deletion/)?.[1] || "0", 10);
396
+ const fullHash = (hash || "").trim();
387
397
  let fileNames = [];
388
- try {
389
- const shortHash = (hash || "").slice(0, 7);
390
- const namesOutput = this.git(`git diff --name-only ${shortHash}~1 ${shortHash} 2>/dev/null`);
391
- fileNames = namesOutput ? namesOutput.split("\n").filter(Boolean) : [];
398
+ if (namesByHash?.has(fullHash)) {
399
+ fileNames = namesByHash.get(fullHash) ?? [];
400
+ }
401
+ else {
402
+ try {
403
+ const shortHash = fullHash.slice(0, 7);
404
+ const namesOutput = this.git(`git diff --name-only ${shortHash}~1 ${shortHash} 2>/dev/null`);
405
+ fileNames = namesOutput ? namesOutput.split("\n").filter(Boolean) : [];
406
+ }
407
+ catch { /* empty */ }
392
408
  }
393
- catch { /* empty */ }
394
409
  return {
395
- hash: (hash || "").slice(0, 7),
410
+ hash: fullHash.slice(0, 7),
396
411
  message: message || "",
397
412
  author: author || "",
398
413
  date: date || "",
@@ -407,13 +422,44 @@ export class StorytellingTriggerProcessor extends PostProcessor {
407
422
  return [];
408
423
  }
409
424
  }
425
+ /**
426
+ * One `git log --name-only` for the range. Same paths as `git diff --name-only HASH~1 HASH`
427
+ * for non-merge commits. Null means the caller should fall back per commit.
428
+ */
429
+ fileNamesByCommit(from, to, limit) {
430
+ const limitArg = typeof limit === "number" && limit > 0 ? `-n ${Math.floor(limit)} ` : "";
431
+ const out = this.git(`git log ${limitArg}${from}..${to} --no-merges --name-only --pretty=format:%x1e%H`);
432
+ if (!out)
433
+ return null;
434
+ const names = new Map();
435
+ for (const chunk of out.split("\x1e")) {
436
+ if (chunk.trim().length === 0)
437
+ continue;
438
+ let hash = "";
439
+ const files = [];
440
+ for (const line of chunk.split("\n")) {
441
+ if (!hash) {
442
+ if (/^[0-9a-f]{40}$/.test(line))
443
+ hash = line;
444
+ continue;
445
+ }
446
+ if (line.length > 0)
447
+ files.push(line);
448
+ }
449
+ if (hash)
450
+ names.set(hash, files);
451
+ }
452
+ return names;
453
+ }
410
454
  getRecentCommits(count) {
411
455
  try {
456
+ if (!(count > 0))
457
+ return [];
412
458
  const hash = this.git("git rev-list --max-parents=0 HEAD");
413
459
  if (!hash)
414
460
  return [];
415
461
  const firstCommit = hash.split("\n")[0].trim();
416
- return this.getCommitsBetween(firstCommit, "HEAD").slice(0, count);
462
+ return this.getCommitsBetween(firstCommit, "HEAD", count);
417
463
  }
418
464
  catch {
419
465
  return [];
@@ -453,11 +499,18 @@ export class StorytellingTriggerProcessor extends PostProcessor {
453
499
  filesModified.push(file);
454
500
  }
455
501
  }
456
- const uniqueDirs = [...new Set([...filesAdded, ...filesModified, ...filesDeleted]
457
- .map((f) => {
458
- const parts = f.split("/");
459
- return parts.length > 1 ? parts.slice(0, -1).join("/") : ".";
460
- }))].sort();
502
+ const dirSet = new Set();
503
+ const addDir = (fileName) => {
504
+ const slash = fileName.lastIndexOf("/");
505
+ dirSet.add(slash >= 0 ? fileName.slice(0, slash) : ".");
506
+ };
507
+ for (const fileName of filesAdded)
508
+ addDir(fileName);
509
+ for (const fileName of filesModified)
510
+ addDir(fileName);
511
+ for (const fileName of filesDeleted)
512
+ addDir(fileName);
513
+ const uniqueDirs = [...dirSet].sort();
461
514
  return {
462
515
  totalCommits: commits.length,
463
516
  totalFilesChanged: allFiles.size,
@@ -28,55 +28,58 @@ function findRecentlyModifiedTsFile(dir, maxAgeSeconds = 60) {
28
28
  return cached.file;
29
29
  }
30
30
  try {
31
- // Recursive walk to find all .ts files
32
- const files = [];
31
+ // One stat per source file. Dirent tells files from directories; mtime is kept
32
+ // on that stat so the walk does not stat the same path again.
33
+ // Holder avoids TS narrowing the binding to null across the closure.
34
+ const mostRecent = { path: null, mtime: 0 };
35
+ const shouldWalk = (name) => !name.startsWith(".") && name !== "node_modules" && name !== "dist";
36
+ const isTsSource = (name) => name.endsWith(".ts") && !name.endsWith(".test.ts") && !name.endsWith(".spec.ts");
37
+ const consider = (fullPath, mtimeMs) => {
38
+ const relPath = path.relative(dir, fullPath);
39
+ if (mostRecent.path === null || mtimeMs > mostRecent.mtime) {
40
+ mostRecent.path = relPath;
41
+ mostRecent.mtime = mtimeMs;
42
+ }
43
+ };
33
44
  function walkSync(dirPath) {
45
+ let items;
34
46
  try {
35
- const items = fs.readdirSync(dirPath);
36
- for (const item of items) {
37
- const fullPath = path.join(dirPath, item);
38
- try {
47
+ items = fs.readdirSync(dirPath, { withFileTypes: true });
48
+ }
49
+ catch {
50
+ return;
51
+ }
52
+ for (const item of items) {
53
+ const name = item.name;
54
+ const fullPath = path.join(dirPath, name);
55
+ try {
56
+ // stat follows links, so a symlink to a directory is still walked.
57
+ if (item.isSymbolicLink()) {
39
58
  const stat = fs.statSync(fullPath);
40
59
  if (stat.isDirectory()) {
41
- // Skip node_modules, dist, etc.
42
- if (!item.startsWith(".") &&
43
- item !== "node_modules" &&
44
- item !== "dist") {
60
+ if (shouldWalk(name))
45
61
  walkSync(fullPath);
46
- }
47
62
  }
48
- else if (item.endsWith(".ts") &&
49
- !item.endsWith(".test.ts") &&
50
- !item.endsWith(".spec.ts")) {
51
- files.push(fullPath);
63
+ else if (isTsSource(name)) {
64
+ consider(fullPath, stat.mtimeMs);
52
65
  }
66
+ continue;
53
67
  }
54
- catch {
55
- // Skip files we can't stat
68
+ if (item.isDirectory()) {
69
+ if (shouldWalk(name))
70
+ walkSync(fullPath);
71
+ }
72
+ else if (isTsSource(name)) {
73
+ consider(fullPath, fs.statSync(fullPath).mtimeMs);
56
74
  }
57
75
  }
58
- }
59
- catch {
60
- // Skip directories we can't read
61
- }
62
- }
63
- walkSync(dir);
64
- // Find most recently modified
65
- let mostRecent = null;
66
- for (const filePath of files) {
67
- try {
68
- const stats = fs.statSync(filePath);
69
- // Get relative path
70
- const relPath = path.relative(dir, filePath);
71
- if (!mostRecent || stats.mtimeMs > mostRecent.mtime) {
72
- mostRecent = { path: relPath, mtime: stats.mtimeMs };
76
+ catch {
77
+ // Skip files we can't stat
73
78
  }
74
79
  }
75
- catch {
76
- // Skip files we can't stat
77
- }
78
80
  }
79
- const result = mostRecent?.path || null;
81
+ walkSync(dir);
82
+ const result = mostRecent.path;
80
83
  // Cache the result
81
84
  fileScanCache.set(cacheKey, {
82
85
  file: result,
@@ -8,6 +8,9 @@ export declare class FrameworkReportingSystem {
8
8
  getRealtimeStatus(): Promise<RealtimeStatus>;
9
9
  createCustomReport(template: CustomReportTemplate): string;
10
10
  private generateReportId;
11
+ private activityLogFile;
12
+ private currentLogStamp;
13
+ private memoryLogStamp;
11
14
  private getCachedReport;
12
15
  private isCacheValid;
13
16
  private cacheReport;