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.
- package/.grok-plugin/plugin.json +1 -1
- package/.xray/features.json +1 -1
- package/dist/CHANGELOG.md +25 -0
- package/dist/analytics/emerging-pattern-detector.d.ts +12 -1
- package/dist/analytics/emerging-pattern-detector.js +37 -12
- package/dist/analytics/pattern-learning-engine.js +1 -9
- package/dist/analytics/predictive-analytics.js +51 -33
- package/dist/analytics/prompt-pattern-analyzer.d.ts +1 -0
- package/dist/analytics/prompt-pattern-analyzer.js +39 -20
- package/dist/analytics/routing-performance-analyzer.d.ts +2 -0
- package/dist/analytics/routing-performance-analyzer.js +89 -37
- package/dist/analytics/simple-pattern-analyzer.d.ts +4 -0
- package/dist/analytics/simple-pattern-analyzer.js +66 -28
- package/dist/architect/architect-tools.d.ts +3 -0
- package/dist/architect/architect-tools.js +22 -0
- package/dist/cli/commands/grok-install.js +1 -1
- package/dist/cli/commands/hermes-install.js +11 -5
- package/dist/cli/commands/openclaw-install.js +13 -7
- package/dist/cli/commands/opencode-install.js +7 -4
- package/dist/cli/commands/suit-monitor.d.ts +30 -0
- package/dist/cli/commands/suit-monitor.js +128 -0
- package/dist/cli/index.js +52 -15
- package/dist/cli/suit-pulse.d.ts +29 -0
- package/dist/cli/suit-pulse.js +270 -0
- package/dist/delegation/agent-delegator.d.ts +4 -2
- package/dist/delegation/agent-delegator.js +46 -27
- package/dist/delegation/agent-expertise.js +2 -1
- package/dist/delegation/analytics/outcome-tracker.d.ts +6 -0
- package/dist/delegation/analytics/outcome-tracker.js +17 -3
- package/dist/delegation/ast-code-parser.d.ts +5 -0
- package/dist/delegation/ast-code-parser.js +53 -48
- package/dist/delegation/codebase-context-analyzer.d.ts +3 -7
- package/dist/delegation/codebase-context-analyzer.js +188 -159
- package/dist/delegation/session-coordinator.js +3 -2
- package/dist/enforcement/loaders/codex-loader.js +18 -14
- package/dist/enforcement/validators/architecture-validators.js +115 -9
- package/dist/governance/codex-policy.service.d.ts +12 -2
- package/dist/governance/codex-policy.service.js +91 -16
- package/dist/integrations/grok/hooks/session-start.js +6 -6
- package/dist/integrations/grok/plugin/0xray/hooks/hooks.json +6 -6
- package/dist/integrations/hooks/goggles-mcp.mjs +10 -3
- package/dist/integrations/hooks/goggles-pipeline.mjs +221 -44
- package/dist/integrations/hooks/goggles-planes.json +172 -8
- package/dist/integrations/hooks/plates.cjs +138 -0
- package/dist/integrations/hooks/station-hook-runtime.cjs +63 -5
- package/dist/integrations/hooks/write-home.cjs +59 -0
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/mcps/architect-tools.server.d.ts +6 -0
- package/dist/mcps/architect-tools.server.js +19 -18
- package/dist/mcps/orchestrator/server.d.ts +4 -3
- package/dist/mcps/orchestrator/server.js +29 -17
- package/dist/memory-routing/lookup-cache.d.ts +8 -0
- package/dist/memory-routing/lookup-cache.js +72 -0
- package/dist/memory-routing/plates.d.ts +1 -1
- package/dist/memory-routing/plates.js +26 -0
- package/dist/memory-routing/provider-loader.js +3 -0
- package/dist/metrics/agent-metrics.d.ts +14 -5
- package/dist/metrics/agent-metrics.js +362 -224
- package/dist/nucleus/synthesis-consult-receipt.js +38 -16
- package/dist/orchestrator/enhanced-multi-agent-orchestrator.d.ts +5 -0
- package/dist/orchestrator/enhanced-multi-agent-orchestrator.js +9 -0
- package/dist/orchestrator/multi-agent-orchestration-coordinator.js +1 -2
- package/dist/orchestrator/orchestrator.js +1 -2
- package/dist/processors/implementations/agents-md-validation-processor.d.ts +0 -1
- package/dist/processors/implementations/agents-md-validation-processor.js +0 -11
- package/dist/processors/implementations/console-log-guard-processor.js +39 -50
- package/dist/processors/implementations/performance-budget-processor.d.ts +0 -5
- package/dist/processors/implementations/performance-budget-processor.js +29 -38
- package/dist/processors/implementations/storytelling-trigger-processor.d.ts +5 -0
- package/dist/processors/implementations/storytelling-trigger-processor.js +69 -16
- package/dist/processors/implementations/test-auto-creation-processor.js +39 -36
- package/dist/reporting/framework-reporting-system.d.ts +3 -0
- package/dist/reporting/framework-reporting-system.js +61 -8
- package/dist/reporting/log-parser.js +188 -105
- package/dist/reporting/metrics.js +42 -18
- package/dist/security/prompt-security-validator.js +8 -7
- package/dist/security/security-auditor.d.ts +4 -0
- package/dist/security/security-auditor.js +132 -57
- package/dist/security/security-hardener.d.ts +2 -0
- package/dist/security/security-hardener.js +31 -6
- package/dist/security/security-headers.d.ts +2 -0
- package/dist/security/security-headers.js +23 -21
- package/dist/security/security-scanner.js +18 -4
- package/dist/session/session-cleanup-manager.d.ts +2 -0
- package/dist/session/session-cleanup-manager.js +29 -8
- package/dist/session/session-monitor.d.ts +14 -4
- package/dist/session/session-monitor.js +208 -106
- package/dist/session/session-state-manager.d.ts +11 -0
- package/dist/session/session-state-manager.js +90 -8
- package/docs-site/docs/plates/activity-log.md +38 -0
- package/docs-site/docs/plates/cursor-compact.md +35 -0
- package/docs-site/docs/plates/grok-compact.md +35 -0
- package/docs-site/docs/plates/index.md +25 -3
- package/docs-site/docs/plates/inference-files.md +35 -0
- package/docs-site/docs/plates/kept-line.md +35 -0
- package/docs-site/docs/plates/lens-gate.md +35 -0
- package/docs-site/docs/plates/lens-page.md +35 -0
- package/docs-site/docs/plates/notes-page.md +35 -0
- package/docs-site/docs/plates/package-face.md +35 -0
- package/docs-site/docs/plates/payload-heat.md +35 -0
- package/docs-site/docs/plates/pickup-stamp.md +35 -0
- package/docs-site/docs/plates/pre-tool.md +35 -0
- package/docs-site/docs/plates/record-map.md +41 -0
- package/docs-site/docs/plates/reflection-page.md +35 -0
- package/docs-site/docs/plates/review.md +57 -0
- package/docs-site/docs/plates/session-capture.md +38 -0
- package/docs-site/docs/plates/site-manual.md +35 -0
- package/docs-site/docs/plates/stamp-plate.md +35 -0
- package/docs-site/docs/plates/station-card.md +35 -0
- package/docs-site/docs/plates/station-heat.md +35 -0
- package/docs-site/docs/plates/suit-organs.md +38 -0
- package/docs-site/docs/plates/suit-settings.md +35 -0
- package/docs-site/docs/plates/suit-wear.md +37 -0
- package/docs-site/docs/plates/trail-state.md +35 -0
- package/docs-site/docs/plates/work-fresh.md +35 -0
- package/docs-site/docs/plates/write-home.md +38 -0
- package/package.json +4 -2
- package/scripts/mjs/run-grok-hook.mjs +50 -0
- package/scripts/node/install-bridges.cjs +305 -81
- package/scripts/node/postinstall.cjs +14 -3
- package/src/integrations/grok/plugin/0xray/hooks/hooks.json +6 -6
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/mcps/architect-tools.server.ts +25 -20
- package/src/mcps/orchestrator/server.ts +33 -19
- package/xray/features.json +1 -1
- package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/RepertoireService.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/index.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/index.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/mcp/server.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/mcp/server.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/paths.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/paths.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/confidence-decay.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/confidence-decay.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/signal-prune.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/signal-prune.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/types.d.ts.map +0 -1
- 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
|
|
107
|
-
* Same path
|
|
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
|
|
43
|
-
* Same path
|
|
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
|
|
54
|
+
for (const candidate of candidates) {
|
|
53
55
|
try {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
|
|
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
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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
|
|
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
|
|
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
|
}
|