0xray 3.0.14 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.opencode/hooks/post-commit +1 -1
- package/.opencode/init.sh +4 -4
- package/AGENTS.md +2 -2
- package/dist/AGENTS.md +2 -2
- package/dist/CHANGELOG.md +9 -0
- package/dist/benchmark/performance-benchmark.js +4 -2
- package/dist/cli/commands/hermes-install.js +4 -4
- package/dist/cli/commands/mcp-install.js +1 -1
- package/dist/cli/commands/openclaw-install.js +1 -1
- package/dist/cli/commands/plugin-commands.js +2 -2
- package/dist/cli/commands/publish-agent.js +5 -5
- package/dist/cli/commands/skill-install.js +1 -1
- package/dist/cli/commands/status.js +3 -3
- package/dist/cli/commands/storyteller.js +1 -1
- package/dist/cli/index.js +6 -6
- package/dist/cli/server.js +4 -4
- package/dist/core/boot-orchestrator.js +24 -24
- package/dist/core/bridge.mjs +22 -22
- package/dist/core/codex-formatter.js +2 -2
- package/dist/core/codex-injector.js +5 -5
- package/dist/core/config-loader.js +4 -2
- package/dist/core/config-paths.js +14 -14
- package/dist/core/context-loader.js +8 -5
- package/dist/core/index.js +1 -1
- package/dist/core/orchestrator.js +1 -1
- package/dist/core/system-prompt-generator.js +2 -2
- package/dist/core/xray-activation.js +42 -40
- package/dist/delegation/agent-delegator.js +2 -2
- package/dist/enforcement/enforcer-tools.js +2 -2
- package/dist/enforcement/loaders/codex-loader.js +2 -2
- package/dist/governance/llm-governance-provider.js +210 -0
- package/dist/index.js +8 -6
- package/dist/inference/deploy-verifier.js +1 -1
- package/dist/inference/inference-cycle.js +7 -7
- package/dist/integrations/hermes-agent/__init__.py +72 -72
- package/dist/integrations/hermes-agent/after-install.md +14 -14
- package/dist/integrations/hermes-agent/bridge.mjs +24 -24
- package/dist/integrations/hermes-agent/plugin.yaml +5 -5
- package/dist/integrations/hermes-agent/schemas.py +12 -12
- package/dist/integrations/hermes-agent/test_plugin.py +117 -117
- package/dist/integrations/hermes-agent/tools.py +25 -25
- package/dist/integrations/plugins/plugin-registry.js +2 -2
- package/dist/mcps/config/server-config-registry.js +10 -10
- package/dist/mcps/connection/mcp-connection.js +1 -1
- package/dist/mcps/framework-help.server.js +6 -6
- package/dist/mcps/knowledge-skills/code-review.server.js +12 -37
- package/dist/mcps/knowledge-skills/security-audit.server.js +12 -42
- package/dist/mcps/knowledge-skills/ui-ux-design.server.js +2 -2
- package/dist/mcps/lint.server.js +2 -2
- package/dist/mcps/mcp-client.js +5 -5
- package/dist/mcps/orchestrator/server.js +2 -2
- package/dist/mcps/researcher.server.js +12 -37
- package/dist/mcps/simulation/server-simulations.js +3 -3
- package/dist/orchestrator/enhanced-multi-agent-orchestrator.js +2 -2
- package/dist/orchestrator/intelligent-commit-batcher.js +3 -3
- package/dist/orchestrator/multi-agent-orchestration-coordinator.js +9 -9
- package/dist/orchestrator/orchestrator.js +10 -8
- package/dist/orchestrator/universal-registry-bridge.js +3 -3
- package/dist/plugin/xray-codex-injection.js +21 -21
- package/dist/postprocessor/PostProcessor.js +5 -5
- package/dist/postprocessor/services/RegressionAnalysisService.js +2 -2
- package/dist/postprocessor/triggers/GitHookTrigger.js +14 -14
- package/dist/processors/implementations/agents-md-validation-processor.js +2 -2
- package/dist/processors/implementations/inference-improvement-processor.js +1 -1
- package/dist/processors/implementations/log-protection-processor.js +2 -2
- package/dist/processors/implementations/publish-preflight-processor.js +3 -3
- package/dist/processors/implementations/regression-testing-processor.js +2 -2
- package/dist/processors/implementations/session-summary-processor.js +1 -1
- package/dist/processors/implementations/storytelling-trigger-processor.js +1 -1
- package/dist/reporting/report-formatter.js +1 -1
- package/dist/scripts/activate-kernel-pipeline.js +2 -2
- package/dist/services/inference-tuner.js +2 -2
- package/dist/skills/hermes-agent/SKILL.md +42 -42
- package/dist/skills/storyteller/SKILL.md +1 -1
- package/dist/state/index.js +2 -1
- package/dist/state/state-manager.js +3 -3
- package/dist/utils/token-manager.js +1 -1
- package/dist/validation/estimation-validator.js +1 -1
- package/package.json +1 -1
- package/scripts/node/pre-publish-guard.js +13 -9
- package/scripts/node/universal-version-manager.js +2 -2
- package/src/mcps/config/__tests__/server-config-registry.test.ts +12 -12
- package/src/mcps/config/server-config-registry.ts +10 -10
- package/src/mcps/connection/mcp-connection.ts +1 -1
- package/src/mcps/framework-help.server.ts +6 -6
- package/src/mcps/knowledge-skills/code-review.server.ts +19 -34
- package/src/mcps/knowledge-skills/security-audit.server.ts +19 -38
- package/src/mcps/knowledge-skills/ui-ux-design.server.ts +2 -2
- package/src/mcps/lint.server.ts +2 -2
- package/src/mcps/mcp-client.ts +5 -5
- package/src/mcps/orchestrator/server.ts +2 -2
- package/src/mcps/researcher.server.ts +19 -34
- package/src/mcps/simulation/server-simulations.ts +3 -3
- package/src/skills/hermes-agent/SKILL.md +42 -42
- package/src/skills/storyteller/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
* This is the main entry point for the 0xRay framework.
|
|
5
5
|
* It exports the core initialization function and key components.
|
|
6
6
|
*/
|
|
7
|
-
export { StringRayOrchestrator } from "./orchestrator/orchestrator.js";
|
|
8
|
-
export { StringRayStateManager } from "./state/index.js";
|
|
7
|
+
export { XrayOrchestrator, StringRayOrchestrator } from "./orchestrator/orchestrator.js";
|
|
8
|
+
export { XrayStateManager, StringRayStateManager } from "./state/index.js";
|
|
9
9
|
export { AgentDelegator } from "./delegation/index.js";
|
|
10
10
|
export { frameworkLogger } from "./core/framework-logger.js";
|
|
11
11
|
export { BUILTIN_CODEX } from "./core/codex-formatter.js";
|
|
12
|
-
import {
|
|
13
|
-
export { defaultStringRayConfig };
|
|
12
|
+
import { defaultXrayConfig } from "./core/index.js";
|
|
13
|
+
export { defaultXrayConfig, defaultXrayConfig as defaultStringRayConfig };
|
|
14
14
|
export { OpenClawIntegration, initializeOpenClawIntegration, getOpenClawIntegration, shutdownOpenClawIntegration } from "./integrations/openclaw/index.js";
|
|
15
15
|
// Main initialization function
|
|
16
|
-
export function
|
|
17
|
-
const mergedConfig = { ...
|
|
16
|
+
export function initializeXray(config = {}) {
|
|
17
|
+
const mergedConfig = { ...defaultXrayConfig, ...config };
|
|
18
18
|
// Return a standardized initialization result
|
|
19
19
|
return {
|
|
20
20
|
success: true,
|
|
@@ -22,3 +22,5 @@ export function initializeStringRay(config = {}) {
|
|
|
22
22
|
message: "0xRay framework initialized successfully",
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
+
// Backward compat alias
|
|
26
|
+
export { initializeXray as initializeStringRay };
|
|
@@ -43,7 +43,7 @@ export class DeployVerifier {
|
|
|
43
43
|
if (!packCheck.passed) {
|
|
44
44
|
return this.result(false, version, tarballPath, installDir, checks, startTime);
|
|
45
45
|
}
|
|
46
|
-
installDir = fs.mkdtempSync(path.join(os.tmpdir(), "
|
|
46
|
+
installDir = fs.mkdtempSync(path.join(os.tmpdir(), "xray-deploy-verify-"));
|
|
47
47
|
const installCheck = this.runCheck("npm-install-fresh", () => {
|
|
48
48
|
this.exec(`npm install ${tarballPath} --no-save`, installDir, 60000);
|
|
49
49
|
return "Fresh install succeeded";
|
|
@@ -3,7 +3,7 @@ import * as path from "path";
|
|
|
3
3
|
import { shouldTriggerCycle, accumulateCorpus } from "./inference-accumulator.js";
|
|
4
4
|
import { DeployVerifier } from "./deploy-verifier.js";
|
|
5
5
|
import { VotingCoordinator } from "../delegation/voting-coordinator.js";
|
|
6
|
-
import {
|
|
6
|
+
import { XrayStateManager } from "../state/state-manager.js";
|
|
7
7
|
import { frameworkLogger } from "../core/framework-logger.js";
|
|
8
8
|
import { getGovernanceIntegration } from "../integrations/governance/index.js";
|
|
9
9
|
import { featuresConfigLoader } from "../core/features-config.js";
|
|
@@ -50,7 +50,7 @@ export class InferenceCycle {
|
|
|
50
50
|
constructor(projectRoot, agentInvoker, options) {
|
|
51
51
|
this.projectRoot = projectRoot || process.cwd();
|
|
52
52
|
this.inferenceDir = path.join(this.projectRoot, "docs", "inference");
|
|
53
|
-
this.stateDir = path.join(this.projectRoot, ".
|
|
53
|
+
this.stateDir = path.join(this.projectRoot, ".xray", "inference");
|
|
54
54
|
this.agentInvoker = agentInvoker ?? null;
|
|
55
55
|
this.options = options ?? {};
|
|
56
56
|
}
|
|
@@ -353,7 +353,7 @@ export class InferenceCycle {
|
|
|
353
353
|
``,
|
|
354
354
|
`1. Read the relevant source files`,
|
|
355
355
|
`2. Add the missing guard, validation, or edge case handling`,
|
|
356
|
-
`3. If this is a codex rule, add the term to .opencode/
|
|
356
|
+
`3. If this is a codex rule, add the term to .opencode/xray/codex.json`,
|
|
357
357
|
`4. Make minimal, surgical changes`,
|
|
358
358
|
].join("\n");
|
|
359
359
|
try {
|
|
@@ -922,7 +922,7 @@ Respond with EXACTLY one of:
|
|
|
922
922
|
fs.mkdirSync(this.stateDir, { recursive: true });
|
|
923
923
|
}
|
|
924
924
|
const stateFile = path.join(this.stateDir, "governance-state.json");
|
|
925
|
-
const stateManager = new
|
|
925
|
+
const stateManager = new XrayStateManager();
|
|
926
926
|
if (fs.existsSync(stateFile)) {
|
|
927
927
|
try {
|
|
928
928
|
const data = JSON.parse(fs.readFileSync(stateFile, "utf-8"));
|
|
@@ -975,10 +975,10 @@ Respond with EXACTLY one of:
|
|
|
975
975
|
return texts.join("\n").trim();
|
|
976
976
|
}
|
|
977
977
|
resolveOpencodeRoot() {
|
|
978
|
-
// Use the provider-agnostic config path resolver (prefers .
|
|
978
|
+
// Use the provider-agnostic config path resolver (prefers .xray/, falls back to .opencode/xray/)
|
|
979
979
|
const configDir = getConfigDir(this.projectRoot);
|
|
980
|
-
// If we resolved to a .
|
|
981
|
-
if (configDir.includes(".
|
|
980
|
+
// If we resolved to a .xray or custom dir, use its parent as the "root"
|
|
981
|
+
if (configDir.includes(".xray") || configDir.includes("xray")) {
|
|
982
982
|
return path.dirname(configDir);
|
|
983
983
|
}
|
|
984
984
|
// Legacy fallback
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Xray Hermes Plugin — full framework pipeline integration.
|
|
2
2
|
|
|
3
3
|
Mirrors the OpenCode xray-codex-injection.ts behavior:
|
|
4
4
|
1. Captures ALL tool calls and logs to disk
|
|
@@ -29,24 +29,24 @@ except ImportError:
|
|
|
29
29
|
schemas = importlib.import_module("schemas")
|
|
30
30
|
tools = importlib.import_module("tools")
|
|
31
31
|
|
|
32
|
-
logger = logging.getLogger("
|
|
32
|
+
logger = logging.getLogger("xray-hermes")
|
|
33
33
|
|
|
34
34
|
# ── Paths ─────────────────────────────────────────────────────
|
|
35
35
|
|
|
36
36
|
PLUGIN_DIR = Path(__file__).resolve().parent
|
|
37
37
|
BRIDGE_PATH = PLUGIN_DIR / "bridge.mjs"
|
|
38
38
|
|
|
39
|
-
# Project root: find the
|
|
39
|
+
# Project root: find the Xray project directory
|
|
40
40
|
# The plugin lives at ~/.hermes/plugins/ which is NOT inside any project tree,
|
|
41
41
|
# so walking up from PLUGIN_DIR will never find the project. Instead:
|
|
42
|
-
# 1. Check
|
|
43
|
-
# 2. Walk up from cwd looking for node_modules/0xray
|
|
42
|
+
# 1. Check XRAY_PROJECT_ROOT env var (explicit override)
|
|
43
|
+
# 2. Walk up from cwd looking for node_modules/0xray (consumer install)
|
|
44
44
|
...
|
|
45
45
|
# node_modules/0xray — consumer install marker
|
|
46
|
-
if (d / "node_modules" / "0xray" / "package.json").exists() or (d / "node_modules" / "
|
|
46
|
+
if (d / "node_modules" / "0xray" / "package.json").exists() or (d / "node_modules" / "0xray" / "package.json").exists():
|
|
47
47
|
return d
|
|
48
|
-
# .opencode/
|
|
49
|
-
if (d / ".opencode" / "
|
|
48
|
+
# .opencode/xray — dev repo marker
|
|
49
|
+
if (d / ".opencode" / "xray" / "features.json").exists():
|
|
50
50
|
return d
|
|
51
51
|
# package.json but not home dir
|
|
52
52
|
if d != home and (d / "package.json").exists():
|
|
@@ -79,20 +79,20 @@ _TOOL_AGENT_MAP = {
|
|
|
79
79
|
"delegate_task": ("orchestrator", "delegation"),
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
# Tools where
|
|
82
|
+
# Tools where Xray has a better alternative
|
|
83
83
|
# terminal: only nudge when the command looks lint/security/search related
|
|
84
|
-
|
|
85
|
-
"search_files": "Use
|
|
84
|
+
_BETTER_WITH_XRAY = {
|
|
85
|
+
"search_files": "Use mcp_xray_researcher_search_codebase for code pattern searches",
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
# Patterns that suggest the terminal command should use an MCP tool instead
|
|
89
89
|
_TERMINAL_NUDGE_PATTERNS = {
|
|
90
|
-
"grep": "Use
|
|
91
|
-
"rg ": "Use
|
|
92
|
-
"eslint": "Use
|
|
93
|
-
"npx eslint": "Use
|
|
94
|
-
"npm audit": "Use
|
|
95
|
-
"yarn audit": "Use
|
|
90
|
+
"grep": "Use mcp_xray_researcher_search_codebase instead of grep",
|
|
91
|
+
"rg ": "Use mcp_xray_researcher_search_codebase instead of ripgrep",
|
|
92
|
+
"eslint": "Use mcp_xray_lint_lint instead of raw eslint",
|
|
93
|
+
"npx eslint": "Use mcp_xray_lint_lint instead of raw eslint",
|
|
94
|
+
"npm audit": "Use mcp_xray_security_scan_security_scan instead of npm audit",
|
|
95
|
+
"yarn audit": "Use mcp_xray_security_scan_security_scan instead of yarn audit",
|
|
96
96
|
"find ": "Use search_files(target='files') instead of find",
|
|
97
97
|
"sed ": "Use patch tool instead of sed",
|
|
98
98
|
"awk ": "Use patch tool instead of awk",
|
|
@@ -108,7 +108,7 @@ _session_stats = {
|
|
|
108
108
|
"session_id": None,
|
|
109
109
|
"code_operations": 0,
|
|
110
110
|
"total_tool_calls": 0,
|
|
111
|
-
"
|
|
111
|
+
"xray_mcp_calls": 0,
|
|
112
112
|
"native_tool_calls": 0,
|
|
113
113
|
"quality_gate_runs": 0,
|
|
114
114
|
"quality_gate_blocks": 0,
|
|
@@ -184,8 +184,8 @@ def _call_bridge(command: dict, timeout: int = 10) -> dict:
|
|
|
184
184
|
|
|
185
185
|
# ── Hook: pre_tool_call ───────────────────────────────────────
|
|
186
186
|
|
|
187
|
-
def
|
|
188
|
-
return tool_name.startswith("
|
|
187
|
+
def _is_xray_mcp(tool_name: str) -> bool:
|
|
188
|
+
return tool_name.startswith("mcp_xray_")
|
|
189
189
|
|
|
190
190
|
|
|
191
191
|
def _on_pre_tool_call(tool_name: str, args: dict, task_id: str, **kwargs):
|
|
@@ -195,17 +195,17 @@ def _on_pre_tool_call(tool_name: str, args: dict, task_id: str, **kwargs):
|
|
|
195
195
|
1. Track stats
|
|
196
196
|
2. Log tool-start event to disk
|
|
197
197
|
3. For code-producing tools: run quality gate + pre-processors via bridge
|
|
198
|
-
4. For non-code tools: nudge if
|
|
198
|
+
4. For non-code tools: nudge if Xray alternative exists
|
|
199
199
|
"""
|
|
200
200
|
_session_stats["total_tool_calls"] += 1
|
|
201
201
|
|
|
202
202
|
# Log start event
|
|
203
203
|
_log_tool_event("start", tool_name, args)
|
|
204
204
|
|
|
205
|
-
#
|
|
206
|
-
if
|
|
207
|
-
_session_stats["
|
|
208
|
-
_log_to_file("activity.log", f"[quality-gate] SKIP
|
|
205
|
+
# Xray MCP tools — track but don't interfere
|
|
206
|
+
if _is_xray_mcp(tool_name):
|
|
207
|
+
_session_stats["xray_mcp_calls"] += 1
|
|
208
|
+
_log_to_file("activity.log", f"[quality-gate] SKIP [xray-mcp]: {tool_name}")
|
|
209
209
|
return
|
|
210
210
|
|
|
211
211
|
# delegate_task: snapshot working tree so post_hook can validate changes
|
|
@@ -252,7 +252,7 @@ def _on_pre_tool_call(tool_name: str, args: dict, task_id: str, **kwargs):
|
|
|
252
252
|
_log_to_file("activity.log",
|
|
253
253
|
f"[quality-gate] BLOCKED: tool={tool_name} violations={violation_msg}")
|
|
254
254
|
logger.warning(
|
|
255
|
-
"[
|
|
255
|
+
"[xray] Quality gate BLOCKED %s: %s",
|
|
256
256
|
tool_name, violation_msg,
|
|
257
257
|
)
|
|
258
258
|
else:
|
|
@@ -277,10 +277,10 @@ def _on_pre_tool_call(tool_name: str, args: dict, task_id: str, **kwargs):
|
|
|
277
277
|
f"[bridge] ERROR in pre-process: {bridge_result.get('error', 'unknown')}")
|
|
278
278
|
return
|
|
279
279
|
|
|
280
|
-
# Non-code tools: nudge for
|
|
281
|
-
if tool_name in
|
|
282
|
-
tip =
|
|
283
|
-
logger.info("[
|
|
280
|
+
# Non-code tools: nudge for Xray alternatives
|
|
281
|
+
if tool_name in _BETTER_WITH_XRAY:
|
|
282
|
+
tip = _BETTER_WITH_XRAY[tool_name]
|
|
283
|
+
logger.info("[xray] Tip: %s — %s", tool_name, tip)
|
|
284
284
|
_log_to_file("activity.log",
|
|
285
285
|
f"[nudge] {tool_name}: {tip}")
|
|
286
286
|
|
|
@@ -290,7 +290,7 @@ def _on_pre_tool_call(tool_name: str, args: dict, task_id: str, **kwargs):
|
|
|
290
290
|
if isinstance(cmd, str):
|
|
291
291
|
for pattern, tip in _TERMINAL_NUDGE_PATTERNS.items():
|
|
292
292
|
if pattern in cmd:
|
|
293
|
-
logger.info("[
|
|
293
|
+
logger.info("[xray] Tip: %s — %s", tool_name, tip)
|
|
294
294
|
_log_to_file("activity.log",
|
|
295
295
|
f"[nudge] {tool_name}: {tip}")
|
|
296
296
|
break
|
|
@@ -378,14 +378,14 @@ def _on_post_tool_call(tool_name: str, args: dict, result, task_id: str, **kwarg
|
|
|
378
378
|
if calls - _last_tune_tool_call_count >= _INFERENCE_TUNE_INTERVAL:
|
|
379
379
|
_last_tune_tool_call_count = calls
|
|
380
380
|
logger.info(
|
|
381
|
-
"[
|
|
381
|
+
"[xray] Triggering inference tuning cycle (tool call #%d)", calls
|
|
382
382
|
)
|
|
383
383
|
_log_to_file("activity.log",
|
|
384
384
|
f"[inference-tune] auto-cycle at tool call #{calls}")
|
|
385
385
|
try:
|
|
386
386
|
_run_inference_tune()
|
|
387
387
|
except Exception as e:
|
|
388
|
-
logger.warning("[
|
|
388
|
+
logger.warning("[xray] Inference tuning failed: %s", e)
|
|
389
389
|
|
|
390
390
|
|
|
391
391
|
# ── Hook: session_start ───────────────────────────────────────
|
|
@@ -394,7 +394,7 @@ def _on_session_start(session_id: str, platform: str, **kwargs):
|
|
|
394
394
|
"""Fires when a new session starts. Resets stats, logs to disk."""
|
|
395
395
|
_session_stats["started_at"] = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
396
396
|
_session_stats["session_id"] = session_id
|
|
397
|
-
for key in ("code_operations", "total_tool_calls", "
|
|
397
|
+
for key in ("code_operations", "total_tool_calls", "xray_mcp_calls",
|
|
398
398
|
"native_tool_calls", "quality_gate_runs", "quality_gate_blocks",
|
|
399
399
|
"pre_processor_runs", "post_processor_runs",
|
|
400
400
|
"bridge_calls", "bridge_errors",
|
|
@@ -406,23 +406,23 @@ def _on_session_start(session_id: str, platform: str, **kwargs):
|
|
|
406
406
|
_ensure_log_dir()
|
|
407
407
|
_log_to_file("activity.log",
|
|
408
408
|
f"[session-start] session={session_id} platform={platform}")
|
|
409
|
-
logger.info("[
|
|
409
|
+
logger.info("[xray] Session %s started on %s", session_id, platform)
|
|
410
410
|
|
|
411
411
|
|
|
412
412
|
# ── Slash command ─────────────────────────────────────────────
|
|
413
413
|
|
|
414
|
-
def
|
|
415
|
-
"""Slash command handler: /
|
|
414
|
+
def _xray_command(args: str) -> str:
|
|
415
|
+
"""Slash command handler: /xray [status|stats|help]"""
|
|
416
416
|
cmd = (args or "status").strip().lower()
|
|
417
417
|
|
|
418
418
|
if cmd == "stats":
|
|
419
419
|
return (
|
|
420
|
-
f"
|
|
420
|
+
f"Xray Session Stats\n"
|
|
421
421
|
f" Session: {_session_stats['session_id'] or 'N/A'}\n"
|
|
422
422
|
f" Started: {_session_stats['started_at'] or 'N/A'}\n"
|
|
423
423
|
f" Tool calls: {_session_stats['total_tool_calls']}\n"
|
|
424
424
|
f" Code operations: {_session_stats['code_operations']}\n"
|
|
425
|
-
f"
|
|
425
|
+
f" Xray MCP: {_session_stats['xray_mcp_calls']}\n"
|
|
426
426
|
f" Native tools: {_session_stats['native_tool_calls']}\n"
|
|
427
427
|
f" Quality gate runs: {_session_stats['quality_gate_runs']}\n"
|
|
428
428
|
f" Quality gate blocks: {_session_stats['quality_gate_blocks']}\n"
|
|
@@ -437,19 +437,19 @@ def _strray_command(args: str) -> str:
|
|
|
437
437
|
|
|
438
438
|
if cmd == "help":
|
|
439
439
|
return (
|
|
440
|
-
"
|
|
441
|
-
" /
|
|
442
|
-
" /
|
|
443
|
-
" /
|
|
440
|
+
"Xray Commands:\n"
|
|
441
|
+
" /xray status — Plugin and framework health\n"
|
|
442
|
+
" /xray stats — Session pipeline statistics\n"
|
|
443
|
+
" /xray help — This message"
|
|
444
444
|
)
|
|
445
445
|
|
|
446
446
|
# Default: status (calls bridge health)
|
|
447
447
|
bridge_result = _call_bridge({"command": "health"}, timeout=10)
|
|
448
448
|
if "error" in bridge_result:
|
|
449
|
-
return f"
|
|
449
|
+
return f"Xray plugin loaded. Bridge: {bridge_result['error']}"
|
|
450
450
|
|
|
451
451
|
return (
|
|
452
|
-
f"
|
|
452
|
+
f"Xray Hermes Plugin Status\n"
|
|
453
453
|
f" Framework: {bridge_result.get('framework', 'unknown')}\n"
|
|
454
454
|
f" Version: {bridge_result.get('version', 'unknown')}\n"
|
|
455
455
|
f" Quality Gate: {'ready' if bridge_result.get('components', {}).get('qualityGate') else 'not loaded'}\n"
|
|
@@ -518,13 +518,13 @@ def _record_tool_outcome(tool_name: str, args: dict, success: bool):
|
|
|
518
518
|
with open(_OUTCOMES_PATH, "w") as f:
|
|
519
519
|
json.dump(outcomes, f, indent=2)
|
|
520
520
|
except Exception as e:
|
|
521
|
-
logger.debug("[
|
|
521
|
+
logger.debug("[xray] outcome recording failed: %s", e)
|
|
522
522
|
|
|
523
523
|
|
|
524
524
|
# ── Inference tuning (auto-calibration) ────────────────────────
|
|
525
525
|
|
|
526
526
|
def _run_inference_tune():
|
|
527
|
-
"""Shell out to
|
|
527
|
+
"""Shell out to 0xray inference:tuner --run-once.
|
|
528
528
|
|
|
529
529
|
Runs in a background thread so it doesn't block the tool call pipeline.
|
|
530
530
|
The tuner reads routing outcomes, runs the analytics pipeline, and
|
|
@@ -560,7 +560,7 @@ def _run_inference_tune():
|
|
|
560
560
|
# ── Registration ──────────────────────────────────────────────
|
|
561
561
|
|
|
562
562
|
# ── Subagent (delegate_task) enforcement ────────────────────
|
|
563
|
-
# Subagents bypass all
|
|
563
|
+
# Subagents bypass all Xray hooks because they run in isolated
|
|
564
564
|
# contexts. We enforce by snapshotting the working tree before dispatch
|
|
565
565
|
# and validating all changed files after return.
|
|
566
566
|
|
|
@@ -647,7 +647,7 @@ def _validate_subagent_changes(task_id: str, **kwargs):
|
|
|
647
647
|
f"[subagent-validate] BLOCKED: {rel_path} "
|
|
648
648
|
f"violations={'; '.join(str(v) for v in violations[:3])}")
|
|
649
649
|
logger.warning(
|
|
650
|
-
"[
|
|
650
|
+
"[xray] Subagent BLOCKED %s: %s",
|
|
651
651
|
rel_path, violations[:3],
|
|
652
652
|
)
|
|
653
653
|
else:
|
|
@@ -659,28 +659,28 @@ def register(ctx):
|
|
|
659
659
|
"""Wire schemas to handlers and register lifecycle hooks."""
|
|
660
660
|
# ── Register tools ────────────────────────────────────────
|
|
661
661
|
ctx.register_tool(
|
|
662
|
-
name="
|
|
663
|
-
toolset="
|
|
664
|
-
schema=schemas.
|
|
665
|
-
handler=tools.
|
|
662
|
+
name="xray_validate",
|
|
663
|
+
toolset="xray-hermes",
|
|
664
|
+
schema=schemas.XRAY_VALIDATE,
|
|
665
|
+
handler=tools.xray_validate,
|
|
666
666
|
)
|
|
667
667
|
ctx.register_tool(
|
|
668
|
-
name="
|
|
669
|
-
toolset="
|
|
670
|
-
schema=schemas.
|
|
671
|
-
handler=tools.
|
|
668
|
+
name="xray_codex_check",
|
|
669
|
+
toolset="xray-hermes",
|
|
670
|
+
schema=schemas.XRAY_CODEX_CHECK,
|
|
671
|
+
handler=tools.xray_codex_check,
|
|
672
672
|
)
|
|
673
673
|
ctx.register_tool(
|
|
674
|
-
name="
|
|
675
|
-
toolset="
|
|
676
|
-
schema=schemas.
|
|
677
|
-
handler=tools.
|
|
674
|
+
name="xray_health",
|
|
675
|
+
toolset="xray-hermes",
|
|
676
|
+
schema=schemas.XRAY_HEALTH,
|
|
677
|
+
handler=tools.xray_health,
|
|
678
678
|
)
|
|
679
679
|
ctx.register_tool(
|
|
680
|
-
name="
|
|
681
|
-
toolset="
|
|
682
|
-
schema=schemas.
|
|
683
|
-
handler=tools.
|
|
680
|
+
name="xray_hooks",
|
|
681
|
+
toolset="xray-hermes",
|
|
682
|
+
schema=schemas.XRAY_HOOKS,
|
|
683
|
+
handler=tools.xray_hooks,
|
|
684
684
|
)
|
|
685
685
|
|
|
686
686
|
# ── Register hooks ────────────────────────────────────────
|
|
@@ -691,28 +691,28 @@ def register(ctx):
|
|
|
691
691
|
try:
|
|
692
692
|
ctx.register_hook("on_session_start", _on_session_start)
|
|
693
693
|
except (AttributeError, TypeError):
|
|
694
|
-
logger.debug("[
|
|
694
|
+
logger.debug("[xray] on_session_start hook not yet available")
|
|
695
695
|
|
|
696
696
|
# ── Register slash command ────────────────────────────────
|
|
697
697
|
try:
|
|
698
698
|
ctx.register_command(
|
|
699
|
-
name="
|
|
700
|
-
handler=
|
|
701
|
-
description="
|
|
699
|
+
name="xray",
|
|
700
|
+
handler=_xray_command,
|
|
701
|
+
description="Xray status, stats, hooks, and enforcement info",
|
|
702
702
|
args_hint="[status|stats|help]",
|
|
703
703
|
aliases=("sr",),
|
|
704
704
|
)
|
|
705
705
|
except (AttributeError, TypeError):
|
|
706
|
-
logger.debug("[
|
|
706
|
+
logger.debug("[xray] Slash command registration not yet available")
|
|
707
707
|
|
|
708
708
|
# ── Bootstrap ─────────────────────────────────────────────
|
|
709
709
|
_ensure_log_dir()
|
|
710
710
|
_log_to_file("activity.log",
|
|
711
|
-
f"[plugin-loaded]
|
|
711
|
+
f"[plugin-loaded] Xray Hermes Plugin v2.2 — "
|
|
712
712
|
f"4 tools, 2 hooks, subagent enforcement, bridge={BRIDGE_PATH.exists()}")
|
|
713
713
|
|
|
714
714
|
logger.info(
|
|
715
|
-
"[
|
|
715
|
+
"[xray] Plugin v2.2 loaded: 4 tools, 2 hooks, "
|
|
716
716
|
"subagent enforcement active, bridge=%s",
|
|
717
717
|
BRIDGE_PATH.exists(),
|
|
718
718
|
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Xray Hermes Plugin Installed
|
|
2
2
|
|
|
3
3
|
**Restart your Hermes session** for the plugin to take effect.
|
|
4
4
|
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
| Component | Description |
|
|
8
8
|
|-----------|-------------|
|
|
9
|
-
| `
|
|
10
|
-
| `
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
9
|
+
| `xray_validate` | Pre-commit validation with quality gates |
|
|
10
|
+
| `xray_codex_check` | Code review against 60 Codex error-prevention rules |
|
|
11
|
+
| `xray_health` | Framework health check |
|
|
12
|
+
| `xray_hooks` | Git hooks management (install, uninstall, list, status) |
|
|
13
13
|
| `pre_tool_call` hook | Quality gates + nudges before every tool call |
|
|
14
14
|
| `post_tool_call` hook | Post-processors after every tool call |
|
|
15
15
|
|
|
@@ -27,24 +27,24 @@ The plugin can install git hooks for automated validation:
|
|
|
27
27
|
### Install Hooks
|
|
28
28
|
|
|
29
29
|
```
|
|
30
|
-
/
|
|
30
|
+
/xray hooks install
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Or use the tool directly:
|
|
34
34
|
```
|
|
35
|
-
|
|
35
|
+
xray_hooks(action="install")
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
### Check Hook Status
|
|
39
39
|
|
|
40
40
|
```
|
|
41
|
-
/
|
|
41
|
+
/xray hooks status
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
### Uninstall Hooks
|
|
45
45
|
|
|
46
46
|
```
|
|
47
|
-
/
|
|
47
|
+
/xray hooks uninstall
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
## Quick Test
|
|
@@ -52,20 +52,20 @@ strray_hooks(action="install")
|
|
|
52
52
|
After restarting Hermes, try:
|
|
53
53
|
|
|
54
54
|
```
|
|
55
|
-
/
|
|
56
|
-
/
|
|
55
|
+
/xray status
|
|
56
|
+
/xray stats
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Or use the tools directly — `
|
|
59
|
+
Or use the tools directly — `xray_health` will confirm the bridge is connected.
|
|
60
60
|
|
|
61
61
|
## MCP Servers
|
|
62
62
|
|
|
63
|
-
The plugin works alongside
|
|
63
|
+
The plugin works alongside Xray's MCP servers (if configured in your
|
|
64
64
|
Hermes config). The plugin's native tools provide offline/offline-first
|
|
65
65
|
validation, while MCP servers offer deeper framework integration.
|
|
66
66
|
|
|
67
67
|
## Files
|
|
68
68
|
|
|
69
|
-
The plugin lives at `~/.hermes/plugins/
|
|
69
|
+
The plugin lives at `~/.hermes/plugins/xray-hermes/`. It is auto-updated
|
|
70
70
|
when you run `npm install` or `npm update` in a project with `0xray`
|
|
71
71
|
as a dependency.
|