0xray 4.0.35 → 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 (173) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.xray/features.json +1 -1
  3. package/dist/CHANGELOG.md +25 -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/grok/plugin/0xray/hooks/hooks.json +6 -6
  41. package/dist/integrations/hooks/goggles-mcp.mjs +10 -3
  42. package/dist/integrations/hooks/goggles-pipeline.mjs +221 -44
  43. package/dist/integrations/hooks/goggles-planes.json +172 -8
  44. package/dist/integrations/hooks/plates.cjs +138 -0
  45. package/dist/integrations/hooks/station-hook-runtime.cjs +63 -5
  46. package/dist/integrations/hooks/write-home.cjs +59 -0
  47. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  48. package/dist/mcps/architect-tools.server.d.ts +6 -0
  49. package/dist/mcps/architect-tools.server.js +19 -18
  50. package/dist/mcps/orchestrator/server.d.ts +4 -3
  51. package/dist/mcps/orchestrator/server.js +29 -17
  52. package/dist/memory-routing/lookup-cache.d.ts +8 -0
  53. package/dist/memory-routing/lookup-cache.js +72 -0
  54. package/dist/memory-routing/plates.d.ts +1 -1
  55. package/dist/memory-routing/plates.js +26 -0
  56. package/dist/memory-routing/provider-loader.js +3 -0
  57. package/dist/metrics/agent-metrics.d.ts +14 -5
  58. package/dist/metrics/agent-metrics.js +362 -224
  59. package/dist/nucleus/synthesis-consult-receipt.js +38 -16
  60. package/dist/orchestrator/enhanced-multi-agent-orchestrator.d.ts +5 -0
  61. package/dist/orchestrator/enhanced-multi-agent-orchestrator.js +9 -0
  62. package/dist/orchestrator/multi-agent-orchestration-coordinator.js +1 -2
  63. package/dist/orchestrator/orchestrator.js +1 -2
  64. package/dist/processors/implementations/agents-md-validation-processor.d.ts +0 -1
  65. package/dist/processors/implementations/agents-md-validation-processor.js +0 -11
  66. package/dist/processors/implementations/console-log-guard-processor.js +39 -50
  67. package/dist/processors/implementations/performance-budget-processor.d.ts +0 -5
  68. package/dist/processors/implementations/performance-budget-processor.js +29 -38
  69. package/dist/processors/implementations/storytelling-trigger-processor.d.ts +5 -0
  70. package/dist/processors/implementations/storytelling-trigger-processor.js +69 -16
  71. package/dist/processors/implementations/test-auto-creation-processor.js +39 -36
  72. package/dist/reporting/framework-reporting-system.d.ts +3 -0
  73. package/dist/reporting/framework-reporting-system.js +61 -8
  74. package/dist/reporting/log-parser.js +188 -105
  75. package/dist/reporting/metrics.js +42 -18
  76. package/dist/security/prompt-security-validator.js +8 -7
  77. package/dist/security/security-auditor.d.ts +4 -0
  78. package/dist/security/security-auditor.js +132 -57
  79. package/dist/security/security-hardener.d.ts +2 -0
  80. package/dist/security/security-hardener.js +31 -6
  81. package/dist/security/security-headers.d.ts +2 -0
  82. package/dist/security/security-headers.js +23 -21
  83. package/dist/security/security-scanner.js +18 -4
  84. package/dist/session/session-cleanup-manager.d.ts +2 -0
  85. package/dist/session/session-cleanup-manager.js +29 -8
  86. package/dist/session/session-monitor.d.ts +14 -4
  87. package/dist/session/session-monitor.js +208 -106
  88. package/dist/session/session-state-manager.d.ts +11 -0
  89. package/dist/session/session-state-manager.js +90 -8
  90. package/docs-site/docs/plates/activity-log.md +38 -0
  91. package/docs-site/docs/plates/cursor-compact.md +35 -0
  92. package/docs-site/docs/plates/grok-compact.md +35 -0
  93. package/docs-site/docs/plates/index.md +25 -3
  94. package/docs-site/docs/plates/inference-files.md +35 -0
  95. package/docs-site/docs/plates/kept-line.md +35 -0
  96. package/docs-site/docs/plates/lens-gate.md +35 -0
  97. package/docs-site/docs/plates/lens-page.md +35 -0
  98. package/docs-site/docs/plates/notes-page.md +35 -0
  99. package/docs-site/docs/plates/package-face.md +35 -0
  100. package/docs-site/docs/plates/payload-heat.md +35 -0
  101. package/docs-site/docs/plates/pickup-stamp.md +35 -0
  102. package/docs-site/docs/plates/pre-tool.md +35 -0
  103. package/docs-site/docs/plates/record-map.md +41 -0
  104. package/docs-site/docs/plates/reflection-page.md +35 -0
  105. package/docs-site/docs/plates/review.md +57 -0
  106. package/docs-site/docs/plates/session-capture.md +38 -0
  107. package/docs-site/docs/plates/site-manual.md +35 -0
  108. package/docs-site/docs/plates/stamp-plate.md +35 -0
  109. package/docs-site/docs/plates/station-card.md +35 -0
  110. package/docs-site/docs/plates/station-heat.md +35 -0
  111. package/docs-site/docs/plates/suit-organs.md +38 -0
  112. package/docs-site/docs/plates/suit-settings.md +35 -0
  113. package/docs-site/docs/plates/suit-wear.md +37 -0
  114. package/docs-site/docs/plates/trail-state.md +35 -0
  115. package/docs-site/docs/plates/work-fresh.md +35 -0
  116. package/docs-site/docs/plates/write-home.md +38 -0
  117. package/package.json +4 -2
  118. package/scripts/mjs/run-grok-hook.mjs +50 -0
  119. package/scripts/node/install-bridges.cjs +305 -81
  120. package/scripts/node/postinstall.cjs +14 -3
  121. package/src/integrations/grok/plugin/0xray/hooks/hooks.json +6 -6
  122. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  123. package/src/mcps/architect-tools.server.ts +25 -20
  124. package/src/mcps/orchestrator/server.ts +33 -19
  125. package/xray/features.json +1 -1
  126. package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts.map +0 -1
  127. package/vendor/@0xray/repertoire/dist/RepertoireService.js.map +0 -1
  128. package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.d.ts.map +0 -1
  129. package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.js.map +0 -1
  130. package/vendor/@0xray/repertoire/dist/index.d.ts.map +0 -1
  131. package/vendor/@0xray/repertoire/dist/index.js.map +0 -1
  132. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.d.ts.map +0 -1
  133. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.js.map +0 -1
  134. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.d.ts.map +0 -1
  135. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.js.map +0 -1
  136. package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.d.ts.map +0 -1
  137. package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.js.map +0 -1
  138. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.d.ts.map +0 -1
  139. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js.map +0 -1
  140. package/vendor/@0xray/repertoire/dist/mcp/server.d.ts.map +0 -1
  141. package/vendor/@0xray/repertoire/dist/mcp/server.js.map +0 -1
  142. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.d.ts.map +0 -1
  143. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.js.map +0 -1
  144. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.d.ts.map +0 -1
  145. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.js.map +0 -1
  146. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.d.ts.map +0 -1
  147. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js.map +0 -1
  148. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.d.ts.map +0 -1
  149. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.js.map +0 -1
  150. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.d.ts.map +0 -1
  151. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js.map +0 -1
  152. package/vendor/@0xray/repertoire/dist/paths.d.ts.map +0 -1
  153. package/vendor/@0xray/repertoire/dist/paths.js.map +0 -1
  154. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts.map +0 -1
  155. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js.map +0 -1
  156. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts.map +0 -1
  157. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js.map +0 -1
  158. package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.d.ts.map +0 -1
  159. package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.js.map +0 -1
  160. package/vendor/@0xray/repertoire/dist/registry/confidence-decay.d.ts.map +0 -1
  161. package/vendor/@0xray/repertoire/dist/registry/confidence-decay.js.map +0 -1
  162. package/vendor/@0xray/repertoire/dist/registry/signal-prune.d.ts.map +0 -1
  163. package/vendor/@0xray/repertoire/dist/registry/signal-prune.js.map +0 -1
  164. package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.d.ts.map +0 -1
  165. package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.js.map +0 -1
  166. package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.d.ts.map +0 -1
  167. package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.js.map +0 -1
  168. package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.d.ts.map +0 -1
  169. package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.js.map +0 -1
  170. package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.d.ts.map +0 -1
  171. package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.js.map +0 -1
  172. package/vendor/@0xray/repertoire/dist/types.d.ts.map +0 -1
  173. package/vendor/@0xray/repertoire/dist/types.js.map +0 -1
@@ -0,0 +1,270 @@
1
+ /**
2
+ * One line from streams the suit already writes.
3
+ * Not a doctor, not a second memory.
4
+ */
5
+ import { createRequire } from "node:module";
6
+ import { closeSync, existsSync, openSync, readFileSync, readSync, statSync, } from "node:fs";
7
+ import { dirname, join } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ const require = createRequire(import.meta.url);
10
+ const ACTIVITY_LINE = /^\S+\s+(?:\[[^\]]*\]\s+)?\[[^\]]+\]\s+(\S+)\s+-\s+\S+(?:\s+\|\s+(\{.*\}))?\s*$/;
11
+ function asRecord(value) {
12
+ if (!value || typeof value !== "object" || Array.isArray(value))
13
+ return null;
14
+ return value;
15
+ }
16
+ function rawText(value) {
17
+ if (typeof value !== "string")
18
+ return null;
19
+ const trimmed = value.trim();
20
+ return trimmed ? trimmed : null;
21
+ }
22
+ function textField(value) {
23
+ const text = rawText(value);
24
+ return text ? text.toLowerCase() : null;
25
+ }
26
+ function readVersion(pkgPath) {
27
+ if (!existsSync(pkgPath))
28
+ return null;
29
+ try {
30
+ const parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
31
+ const version = asRecord(parsed)?.version;
32
+ return typeof version === "string" && version.trim() ? version.trim() : null;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ /** Directory named by the fastened Grok hook, or null when the file names none. */
39
+ function hookLaunchDir(root) {
40
+ const file = join(root, ".grok", "plugins", "0xray", "hooks", "hooks.json");
41
+ if (!existsSync(file))
42
+ return null;
43
+ let parsed;
44
+ try {
45
+ parsed = JSON.parse(readFileSync(file, "utf8"));
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ const pre = asRecord(asRecord(parsed)?.hooks)?.PreToolUse;
51
+ if (!Array.isArray(pre))
52
+ return null;
53
+ for (const group of pre) {
54
+ const hooks = asRecord(group)?.hooks;
55
+ if (!Array.isArray(hooks))
56
+ continue;
57
+ for (const hook of hooks) {
58
+ const body = asRecord(hook);
59
+ if (!body)
60
+ continue;
61
+ const envPath = rawText(asRecord(body.env)?.XRAY_AI_PATH);
62
+ const fromEnv = envPath ? packageDir(envPath) : null;
63
+ if (fromEnv)
64
+ return fromEnv;
65
+ const blobs = [];
66
+ if (typeof body.command === "string")
67
+ blobs.push(body.command);
68
+ if (Array.isArray(body.args)) {
69
+ for (const arg of body.args) {
70
+ if (typeof arg === "string")
71
+ blobs.push(arg);
72
+ }
73
+ }
74
+ for (const blob of blobs) {
75
+ const found = packageDirFromText(blob);
76
+ if (found)
77
+ return found;
78
+ }
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+ function packageDir(spec) {
84
+ if (!spec.startsWith("/") || spec.includes("$"))
85
+ return null;
86
+ const marker = "/node_modules/0xray";
87
+ const at = spec.indexOf(marker);
88
+ if (at >= 0)
89
+ return spec.slice(0, at + marker.length);
90
+ return spec;
91
+ }
92
+ function packageDirFromText(text) {
93
+ const matches = text.match(/\/[^\s"']+/g);
94
+ if (!matches)
95
+ return null;
96
+ for (const match of matches) {
97
+ const dir = packageDir(match);
98
+ if (dir)
99
+ return dir;
100
+ }
101
+ return null;
102
+ }
103
+ function resolveWornDir(root, opts) {
104
+ if (opts.wornDir)
105
+ return opts.wornDir;
106
+ const hooked = hookLaunchDir(root);
107
+ if (hooked)
108
+ return hooked;
109
+ const local = join(root, "node_modules", "0xray");
110
+ return existsSync(local) ? local : null;
111
+ }
112
+ function readWorn(root, opts) {
113
+ const directory = resolveWornDir(root, opts);
114
+ if (!directory || !existsSync(directory))
115
+ return null;
116
+ const version = readVersion(join(directory, "package.json"));
117
+ if (!version)
118
+ return null;
119
+ return { version, directory };
120
+ }
121
+ function repertoireVersion(root) {
122
+ return readVersion(join(root, "node_modules", "@0xray", "repertoire", "package.json"));
123
+ }
124
+ function repertoireIndex(root) {
125
+ const here = dirname(fileURLToPath(import.meta.url));
126
+ const candidates = [
127
+ join(root, "node_modules", "@0xray", "repertoire", "dist", "index.js"),
128
+ join(root, "vendor", "@0xray", "repertoire", "dist", "index.js"),
129
+ join(root, "node_modules", "0xray", "vendor", "@0xray", "repertoire", "dist", "index.js"),
130
+ join(here, "..", "..", "vendor", "@0xray", "repertoire", "dist", "index.js"),
131
+ join(here, "..", "..", "..", "vendor", "@0xray", "repertoire", "dist", "index.js"),
132
+ ];
133
+ for (const candidate of candidates) {
134
+ if (existsSync(candidate))
135
+ return candidate;
136
+ }
137
+ return null;
138
+ }
139
+ /** Whole seconds since InferenceState.lastRun. A missing or null run is none. */
140
+ function secondsSinceLastRun(lastRun, now) {
141
+ if (typeof lastRun !== "string")
142
+ return null;
143
+ const parsed = Date.parse(lastRun);
144
+ if (!Number.isFinite(parsed))
145
+ return null;
146
+ return Math.max(0, Math.floor((now - parsed) / 1000));
147
+ }
148
+ function inferenceAge(root, now) {
149
+ const index = repertoireIndex(root);
150
+ if (!index)
151
+ return null;
152
+ const file = join(root, ".xray", "state", "repertoire", "inference-state.json");
153
+ try {
154
+ const loaded = require(index);
155
+ const Manager = loaded.InferenceStateManager;
156
+ if (typeof Manager !== "function")
157
+ return null;
158
+ const state = asRecord(new Manager(file).load());
159
+ return secondsSinceLastRun(state?.lastRun, now);
160
+ }
161
+ catch {
162
+ return null;
163
+ }
164
+ }
165
+ function readTail(file, maxBytes = 65536) {
166
+ const size = statSync(file).size;
167
+ const start = Math.max(0, size - maxBytes);
168
+ const fd = openSync(file, "r");
169
+ try {
170
+ const buf = Buffer.alloc(size - start);
171
+ readSync(fd, buf, 0, buf.length, start);
172
+ let text = buf.toString("utf8");
173
+ if (start > 0) {
174
+ const cut = text.indexOf("\n");
175
+ if (cut >= 0)
176
+ text = text.slice(cut + 1);
177
+ }
178
+ return text;
179
+ }
180
+ finally {
181
+ closeSync(fd);
182
+ }
183
+ }
184
+ function parseActivityLine(line) {
185
+ const match = ACTIVITY_LINE.exec(line.trim());
186
+ const action = match?.[1];
187
+ if (!action)
188
+ return null;
189
+ const decision = action.toLowerCase();
190
+ let gate = null;
191
+ let tool = null;
192
+ const detailsJson = match?.[2];
193
+ if (detailsJson) {
194
+ try {
195
+ const details = asRecord(JSON.parse(detailsJson));
196
+ gate = textField(details?.gate);
197
+ tool = textField(details?.tool);
198
+ }
199
+ catch {
200
+ /* the action still stands */
201
+ }
202
+ }
203
+ return { gate, decision, tool };
204
+ }
205
+ function lastActivity(root) {
206
+ const file = join(root, "logs", "framework", "activity.log");
207
+ if (!existsSync(file))
208
+ return null;
209
+ let text = "";
210
+ try {
211
+ text = readTail(file);
212
+ }
213
+ catch {
214
+ return null;
215
+ }
216
+ const lines = text.split("\n");
217
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
218
+ const line = lines[i]?.trim();
219
+ if (!line)
220
+ continue;
221
+ const parsed = parseActivityLine(line);
222
+ if (parsed)
223
+ return parsed;
224
+ }
225
+ return null;
226
+ }
227
+ /** absent → none. Same shape as the goggles read pass. */
228
+ function lensState(root) {
229
+ const file = join(root, ".xray", "state", "goggles-lens-pass.json");
230
+ if (!existsSync(file))
231
+ return "none";
232
+ try {
233
+ const data = asRecord(JSON.parse(readFileSync(file, "utf8")));
234
+ if (!data || data.read !== true)
235
+ return "none";
236
+ return data.spent === true ? "spent" : "armed";
237
+ }
238
+ catch {
239
+ return "none";
240
+ }
241
+ }
242
+ /** node_modules/0xray/package.json. Seat doctor already owns mill+inspect. */
243
+ function plantPresent(root) {
244
+ return existsSync(join(root, "node_modules", "0xray", "package.json"));
245
+ }
246
+ function activityToken(activity) {
247
+ if (!activity)
248
+ return "none";
249
+ const parts = [activity.gate, activity.decision, activity.tool].filter((part) => Boolean(part));
250
+ return parts.length ? parts.join(":") : "none";
251
+ }
252
+ export function readSuitPulse(root, opts = {}) {
253
+ const now = opts.now ?? Date.now();
254
+ const worn = readWorn(root, opts);
255
+ const repertoire = repertoireVersion(root);
256
+ const inferenceSeconds = inferenceAge(root, now);
257
+ const activity = lastActivity(root);
258
+ const lens = lensState(root);
259
+ const plant = plantPresent(root);
260
+ const head = worn ? `WORN ${worn.version}` : "BARE";
261
+ const line = [
262
+ head,
263
+ `repertoire ${repertoire ?? "missing"}`,
264
+ `inference ${inferenceSeconds === null ? "none" : `${inferenceSeconds}s`}`,
265
+ `activity ${activityToken(activity)}`,
266
+ `lens ${lens}`,
267
+ `plant ${plant ? "yes" : "no"}`,
268
+ ].join(" ");
269
+ return { worn, repertoire, inferenceSeconds, activity, lens, plant, line };
270
+ }
@@ -101,10 +101,12 @@ export declare class AgentDelegator {
101
101
  private agentMetrics;
102
102
  /** Minimum confidence for a learned mapping to override hardcoded routing. */
103
103
  private static readonly MAPPING_CONFIDENCE_THRESHOLD;
104
+ /** Parsed routing file. Identity is path, mtime, size, and a hash of the bytes. */
105
+ private routingMappingsCache;
104
106
  constructor(stateManager: XrayStateManager, configLoader: typeof xrayConfigLoader);
105
107
  /**
106
- * Load routing-mappings.json from disk (fresh each call — picks up tuner writes).
107
- * Same path resolution as inference-tuner.ts.
108
+ * Load routing-mappings.json. First file among .xray/, xray/, then cwd.
109
+ * Same path, mtime, and size still re-reads when the bytes change.
108
110
  */
109
111
  private loadRoutingMappings;
110
112
  /**
@@ -8,6 +8,7 @@
8
8
  *
9
9
  * @since 2026-01-07
10
10
  */
11
+ import { createHash } from "crypto";
11
12
  import * as fs from "fs";
12
13
  import * as path from "path";
13
14
  import * as nodePath from "path";
@@ -31,6 +32,8 @@ export class AgentDelegator {
31
32
  agentMetrics;
32
33
  /** Minimum confidence for a learned mapping to override hardcoded routing. */
33
34
  static MAPPING_CONFIDENCE_THRESHOLD = 0.7;
35
+ /** Parsed routing file. Identity is path, mtime, size, and a hash of the bytes. */
36
+ routingMappingsCache = null;
34
37
  constructor(stateManager, configLoader) {
35
38
  this.stateManager = stateManager;
36
39
  this.configLoader = configLoader;
@@ -39,43 +42,57 @@ export class AgentDelegator {
39
42
  this.agentMetrics = initializeAgentMetrics(stateManager);
40
43
  }
41
44
  /**
42
- * Load routing-mappings.json from disk (fresh each call — picks up tuner writes).
43
- * Same path resolution as inference-tuner.ts.
45
+ * Load routing-mappings.json. First file among .xray/, xray/, then cwd.
46
+ * Same path, mtime, and size still re-reads when the bytes change.
44
47
  */
45
48
  loadRoutingMappings() {
46
49
  const candidates = [
47
50
  path.resolve(process.cwd(), ".xray/routing-mappings.json"),
48
51
  path.resolve(process.cwd(), "xray/routing-mappings.json"),
49
- path.resolve(process.cwd(), ".xray/routing-mappings.json"),
50
52
  path.resolve(process.cwd(), "routing-mappings.json"),
51
53
  ];
52
- for (const p of candidates) {
54
+ for (const candidate of candidates) {
53
55
  try {
54
- if (fs.existsSync(p)) {
55
- const data = fs.readFileSync(p, "utf-8");
56
- const parsed = JSON.parse(data);
57
- if (!Array.isArray(parsed))
58
- continue;
59
- const valid = [];
60
- for (const entry of parsed) {
61
- if (entry.autoGenerated === true) {
62
- frameworkLogger.log("agent-delegator", "routing-filtered", "warning", {
63
- reason: "autoGenerated flag",
64
- agent: entry.agent,
65
- });
66
- continue;
67
- }
68
- if (entry.agent && !isAllowedAgent(entry.agent)) {
69
- frameworkLogger.log("agent-delegator", "routing-filtered", "warning", {
70
- reason: "agent not in registry",
71
- agent: entry.agent,
72
- });
73
- continue;
74
- }
56
+ const stat = fs.statSync(candidate);
57
+ if (!stat.isFile())
58
+ continue;
59
+ const bytes = fs.readFileSync(candidate);
60
+ const hash = createHash("sha256").update(bytes).digest("hex");
61
+ const key = `${candidate}\0${stat.mtimeMs}\0${stat.size}\0${hash}`;
62
+ if (this.routingMappingsCache?.key === key) {
63
+ return this.routingMappingsCache.mappings;
64
+ }
65
+ const parsed = JSON.parse(bytes.toString("utf-8"));
66
+ if (!Array.isArray(parsed))
67
+ continue;
68
+ const valid = [];
69
+ for (const entry of parsed) {
70
+ if (entry === null || entry === undefined) {
71
+ throw new TypeError("routing mapping entry is not an object");
72
+ }
73
+ if (typeof entry !== "object") {
75
74
  valid.push(entry);
75
+ continue;
76
+ }
77
+ const record = entry;
78
+ if (record.autoGenerated === true) {
79
+ frameworkLogger.log("agent-delegator", "routing-filtered", "warning", {
80
+ reason: "autoGenerated flag",
81
+ agent: record.agent,
82
+ });
83
+ continue;
84
+ }
85
+ if (record.agent && !isAllowedAgent(record.agent)) {
86
+ frameworkLogger.log("agent-delegator", "routing-filtered", "warning", {
87
+ reason: "agent not in registry",
88
+ agent: record.agent,
89
+ });
90
+ continue;
76
91
  }
77
- return valid;
92
+ valid.push(record);
78
93
  }
94
+ this.routingMappingsCache = { key, mappings: valid };
95
+ return valid;
79
96
  }
80
97
  catch {
81
98
  // try next candidate
@@ -422,9 +439,10 @@ export class AgentDelegator {
422
439
  * Record analysis results for learning system
423
440
  */
424
441
  recordAnalysisForLearning(request, analysis) {
442
+ const outcomes = [];
425
443
  for (const agentName of analysis.agents) {
426
444
  const agentDetail = analysis.agentDetails.find(a => a.name === agentName);
427
- routingOutcomeTracker.recordOutcome({
445
+ outcomes.push({
428
446
  taskId: request.sessionId || `analysis-${Date.now()}`,
429
447
  taskDescription: request.description,
430
448
  routedAgent: agentName,
@@ -433,6 +451,7 @@ export class AgentDelegator {
433
451
  success: true,
434
452
  });
435
453
  }
454
+ routingOutcomeTracker.recordOutcomes(outcomes);
436
455
  }
437
456
  determineConflictResolution(metrics, complexityScore) {
438
457
  if (complexityScore.level === "enterprise" ||
@@ -144,8 +144,9 @@ export function getVotingWeight(agentName) {
144
144
  const level = getAgentExpertiseLevel(agentName);
145
145
  return level * 10;
146
146
  }
147
+ const EXPERTISE_ROSTER = Object.values(AGENT_EXPERTISE_LEVELS);
147
148
  export function getAgentsWithExpertiseDomain(domain) {
148
- return Object.values(AGENT_EXPERTISE_LEVELS).filter((agent) => agent.domain === domain || agent.specialties.some((s) => domain.includes(s)));
149
+ return EXPERTISE_ROSTER.filter((agent) => agent.domain === domain || agent.specialties.some((s) => domain.includes(s)));
149
150
  }
150
151
  export function getTopExpertsForDomain(domain, count = 3) {
151
152
  const domainExperts = getAgentsWithExpertiseDomain(domain);
@@ -48,6 +48,12 @@ export declare class RoutingOutcomeTracker {
48
48
  * @param outcome The outcome to record (timestamp will be added)
49
49
  */
50
50
  recordOutcome(outcome: Omit<RoutingOutcome, 'timestamp'>): void;
51
+ /**
52
+ * Record several outcomes and stringify the buffer once.
53
+ * Same retained rows as calling recordOutcome per item.
54
+ */
55
+ recordOutcomes(outcomes: Array<Omit<RoutingOutcome, 'timestamp'>>): void;
56
+ private appendOutcome;
51
57
  /**
52
58
  * Update an existing outcome by taskId
53
59
  * @param taskId The task ID to update
@@ -109,13 +109,27 @@ export class RoutingOutcomeTracker {
109
109
  recordOutcome(outcome) {
110
110
  if (!this.maxOutcomes)
111
111
  return;
112
+ this.appendOutcome(outcome);
113
+ // Persist to disk immediately (not debounced for better tracking during tests/CI)
114
+ this.saveToDisk();
115
+ }
116
+ /**
117
+ * Record several outcomes and stringify the buffer once.
118
+ * Same retained rows as calling recordOutcome per item.
119
+ */
120
+ recordOutcomes(outcomes) {
121
+ if (!this.maxOutcomes || outcomes.length === 0)
122
+ return;
123
+ for (const outcome of outcomes) {
124
+ this.appendOutcome(outcome);
125
+ }
126
+ this.saveToDisk();
127
+ }
128
+ appendOutcome(outcome) {
112
129
  this.outcomes.push({ ...outcome, timestamp: new Date() });
113
- // Keep only recent outcomes (circular buffer)
114
130
  if (this.outcomes.length > this.maxOutcomes) {
115
131
  this.outcomes = this.outcomes.slice(-this.maxOutcomes);
116
132
  }
117
- // Persist to disk immediately (not debounced for better tracking during tests/CI)
118
- this.saveToDisk();
119
133
  }
120
134
  /**
121
135
  * Update an existing outcome by taskId
@@ -102,6 +102,9 @@ export interface CodeMetrics {
102
102
  export declare class ASTCodeParser {
103
103
  private astGrepPath;
104
104
  private astGrepAvailable;
105
+ private compiledPatterns;
106
+ private newlineFor;
107
+ private newlineStarts;
105
108
  private patterns;
106
109
  constructor(astGrepPath?: string);
107
110
  /**
@@ -167,6 +170,8 @@ export declare class ASTCodeParser {
167
170
  private calculateCyclomaticComplexity;
168
171
  private calculateCognitiveComplexity;
169
172
  private calculateMaxNesting;
173
+ private newlineIndex;
174
+ private lineNumberAt;
170
175
  private getLineNumber;
171
176
  private calculateMaintainabilityIndex;
172
177
  private findDuplicateImports;
@@ -10,9 +10,22 @@ import * as fs from "fs";
10
10
  import * as path from "path";
11
11
  import { execSync } from "child_process";
12
12
  import { frameworkLogger } from "../core/framework-logger.js";
13
+ const CONTROL_FLOW_RE = /\b(?:if|else|for|while|do|switch|case|catch)\b/g;
14
+ const AMP_RE = /\b&&\b/g;
15
+ const QMARK_RE = /\b\?\b/g;
16
+ function countPattern(re, content) {
17
+ re.lastIndex = 0;
18
+ let count = 0;
19
+ while (re.exec(content) !== null)
20
+ count++;
21
+ return count;
22
+ }
13
23
  export class ASTCodeParser {
14
24
  astGrepPath = null;
15
25
  astGrepAvailable = false;
26
+ compiledPatterns = new Map();
27
+ newlineFor = null;
28
+ newlineStarts = null;
16
29
  patterns = [
17
30
  // TypeScript/JavaScript patterns - direct regex for fallback
18
31
  {
@@ -403,9 +416,12 @@ export class ASTCodeParser {
403
416
  * Regex-based pattern matching (fallback when ast-grep not available)
404
417
  */
405
418
  regexBasedMatching(content, pattern) {
406
- // For fallback, patterns are already regex strings
407
- const regexPattern = pattern;
408
- const regex = new RegExp(regexPattern, "g");
419
+ let regex = this.compiledPatterns.get(pattern);
420
+ if (!regex) {
421
+ regex = new RegExp(pattern, "g");
422
+ this.compiledPatterns.set(pattern, regex);
423
+ }
424
+ regex.lastIndex = 0;
409
425
  const matches = [];
410
426
  let match;
411
427
  while ((match = regex.exec(content)) !== null) {
@@ -720,28 +736,7 @@ export class ASTCodeParser {
720
736
  });
721
737
  }
722
738
  calculateFunctionComplexity(content) {
723
- let complexity = 1; // Base complexity
724
- // +1 for each control flow statement
725
- const controlFlowKeywords = [
726
- "if",
727
- "else",
728
- "for",
729
- "while",
730
- "do",
731
- "switch",
732
- "case",
733
- "catch",
734
- ];
735
- for (const keyword of controlFlowKeywords) {
736
- if (!keyword)
737
- continue; // Skip empty keywords
738
- const regex = new RegExp(`\\b${keyword}\\b`, "g");
739
- const matches = content.match(regex);
740
- if (matches) {
741
- complexity += matches.length;
742
- }
743
- }
744
- return complexity;
739
+ return 1 + countPattern(CONTROL_FLOW_RE, content);
745
740
  }
746
741
  parseImportNames(importStr) {
747
742
  if (!importStr.trim())
@@ -780,28 +775,13 @@ export class ASTCodeParser {
780
775
  return "variable";
781
776
  }
782
777
  calculateCyclomaticComplexity(content) {
783
- let complexity = 1;
784
- const patterns = [
785
- "if",
786
- "else",
787
- "for",
788
- "while",
789
- "do",
790
- "switch",
791
- "case",
792
- "catch",
793
- "&&",
794
- "||",
795
- "\\?",
796
- ];
797
- for (const pattern of patterns) {
798
- const regex = new RegExp(`\\b${pattern}\\b`, "g");
799
- const matches = content.match(regex);
800
- if (matches) {
801
- complexity += matches.length;
802
- }
803
- }
804
- return complexity;
778
+ // `||` is interpolated into `\b||\b`, an empty alternation: one match per index.
779
+ return (1 +
780
+ countPattern(CONTROL_FLOW_RE, content) +
781
+ countPattern(AMP_RE, content) +
782
+ content.length +
783
+ 1 +
784
+ countPattern(QMARK_RE, content));
805
785
  }
806
786
  calculateCognitiveComplexity(functions) {
807
787
  return functions.reduce((sum, func) => sum + func.complexity, 0);
@@ -839,8 +819,33 @@ export class ASTCodeParser {
839
819
  }
840
820
  return maxNesting;
841
821
  }
822
+ newlineIndex(content) {
823
+ if (this.newlineFor === content && this.newlineStarts)
824
+ return this.newlineStarts;
825
+ const starts = [0];
826
+ for (let i = 0; i < content.length; i++) {
827
+ if (content.charCodeAt(i) === 10)
828
+ starts.push(i + 1);
829
+ }
830
+ this.newlineFor = content;
831
+ this.newlineStarts = starts;
832
+ return starts;
833
+ }
834
+ lineNumberAt(starts, index) {
835
+ let lo = 0;
836
+ let hi = starts.length - 1;
837
+ while (lo < hi) {
838
+ const mid = (lo + hi + 1) >> 1;
839
+ const start = starts[mid];
840
+ if (start !== undefined && start <= index)
841
+ lo = mid;
842
+ else
843
+ hi = mid - 1;
844
+ }
845
+ return lo + 1;
846
+ }
842
847
  getLineNumber(content, index) {
843
- return content.substring(0, index).split("\n").length;
848
+ return this.lineNumberAt(this.newlineIndex(content), index);
844
849
  }
845
850
  calculateMaintainabilityIndex(structure, linesOfCode) {
846
851
  // Simplified maintainability index calculation
@@ -71,6 +71,8 @@ export interface CodebaseAnalysisCacheData {
71
71
  exports: string[];
72
72
  content?: string;
73
73
  lastModified?: Date;
74
+ size?: number;
75
+ contentHash?: string;
74
76
  timestamp: number;
75
77
  }
76
78
  export declare class CodebaseContextAnalyzer {
@@ -148,16 +150,10 @@ export declare class CodebaseContextAnalyzer {
148
150
  * Identify potential risks and issues
149
151
  */
150
152
  private identifyRisks;
151
- private isModuleDirectory;
153
+ private isModuleNames;
152
154
  private isConfigFile;
153
155
  private classifyModule;
154
156
  private findEntryPoint;
155
- private hasFramework;
156
- private detectMVCPattern;
157
- private detectRepositoryPattern;
158
- private detectObserverPattern;
159
- private detectFactoryPattern;
160
- private hasMicroservicesIndicators;
161
157
  private isEntryPoint;
162
158
  private detectCircularDependencies;
163
159
  }