0xray 3.3.0 → 3.4.1
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/codex.codex +1 -1
- package/.opencode/enforcer-config.json +1 -4
- package/dist/CHANGELOG.md +22 -0
- package/dist/analytics/routing-refiner.js +0 -2
- package/dist/core/boot-orchestrator.js +9 -1
- package/dist/core/features-config.js +0 -1
- package/dist/integrations/grok/grok-cli.js +53 -25
- package/dist/integrations/hermes-agent/bridge.mjs +11 -1
- package/dist/mcps/index.js +1 -0
- package/dist/mcps/knowledge-skills/skill-invocation.server.js +3 -2
- package/dist/mcps/knowledge-skills/storyteller.server.js +167 -0
- package/dist/mcps/orchestrator/execution/execution-planner.js +14 -2
- package/dist/mcps/registry.json +0 -1
- package/dist/mcps/researcher-confidence.js +92 -0
- package/dist/mcps/researcher.server.js +19 -3
- package/dist/memory-routing/null-provider.js +12 -0
- package/dist/orchestrator/universal-registry-bridge.js +1 -1
- package/dist/skills/registry.json +0 -1
- package/package.json +1 -1
- package/scripts/node/enforce-agents-md.mjs +0 -26
- package/scripts/node/enforce-version-compliance.sh +0 -1
- package/scripts/node/enforce-version-compliance.ts +0 -10
- package/scripts/node/install-bridges.cjs +466 -0
- package/scripts/node/postinstall.cjs +24 -83
- package/scripts/node/pre-publish-guard.js +20 -16
- package/scripts/node/setup.cjs +11 -10
- package/scripts/node/universal-version-manager.js +17 -3
- package/src/mcps/index.ts +1 -0
- package/src/mcps/knowledge-skills/skill-invocation.server.ts +3 -2
- package/src/mcps/knowledge-skills/storyteller.server.ts +206 -0
- package/src/mcps/orchestrator/execution/execution-planner.ts +14 -2
- package/src/mcps/orchestrator/types.ts +4 -0
- package/src/mcps/registry.json +0 -1
- package/src/mcps/researcher-confidence.ts +144 -0
- package/src/mcps/researcher.server.ts +28 -3
- package/src/opencode/codex.codex +1 -1
- package/src/opencode/enforcer-config.json +1 -4
- package/src/skills/registry.json +0 -1
|
@@ -2,112 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const {
|
|
5
|
+
const {
|
|
6
|
+
installAllBridges,
|
|
7
|
+
resolveConsumerTargetDir,
|
|
8
|
+
isConsumerInstall,
|
|
9
|
+
} = require("./install-bridges.cjs");
|
|
6
10
|
|
|
7
|
-
// Structured logging shim (fwLogger discipline for script context; mirrors FrameworkUsageLogger format to logs/framework/activity.log in full runs)
|
|
8
11
|
function structuredLog(component, action, status, details) {
|
|
9
12
|
const ts = new Date().toISOString();
|
|
10
|
-
const detailsPart = details ? ` | ${JSON.stringify(details)}` :
|
|
13
|
+
const detailsPart = details ? ` | ${JSON.stringify(details)}` : "";
|
|
11
14
|
console.log(`${ts} [${component}] ${action} - ${String(status).toUpperCase()}${detailsPart}`);
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
const packageRoot = path.join(__dirname, "..", "..");
|
|
15
|
-
|
|
16
|
-
let targetDir;
|
|
17
|
-
if (__dirname.includes("node_modules/xray")) {
|
|
18
|
-
// plain xray (final identity)
|
|
19
|
-
targetDir = path.join(__dirname, "..", "..", "..", "..");
|
|
20
|
-
} else {
|
|
21
|
-
targetDir = process.env.PWD || process.cwd();
|
|
22
|
-
}
|
|
23
|
-
|
|
18
|
+
const targetDir = resolveConsumerTargetDir(packageRoot, process.env.PWD || process.cwd());
|
|
24
19
|
const resolvedPackage = path.resolve(packageRoot);
|
|
25
20
|
const resolvedTarget = path.resolve(targetDir);
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
const SKIP_DIRS = new Set(["node_modules", "logs"]);
|
|
29
|
-
|
|
30
22
|
// Copy AGENTS-consumer.md → AGENTS.md
|
|
31
23
|
const agentsConsumer = path.join(packageRoot, "AGENTS-consumer.md");
|
|
32
24
|
const agentsDest = path.join(targetDir, "AGENTS.md");
|
|
33
|
-
if (fs.existsSync(agentsConsumer) && resolvedPackage
|
|
25
|
+
if (fs.existsSync(agentsConsumer) && isConsumerInstall(resolvedPackage, resolvedTarget)) {
|
|
34
26
|
fs.copyFileSync(agentsConsumer, agentsDest);
|
|
35
27
|
}
|
|
36
28
|
|
|
37
29
|
// Copy .gitignore.default → .gitignore (never overwrite existing)
|
|
38
30
|
const gitignoreSource = path.join(packageRoot, ".gitignore.default");
|
|
39
31
|
const gitignoreDest = path.join(targetDir, ".gitignore");
|
|
40
|
-
if (!fs.existsSync(gitignoreDest) && resolvedPackage
|
|
32
|
+
if (!fs.existsSync(gitignoreDest) && isConsumerInstall(resolvedPackage, resolvedTarget)) {
|
|
41
33
|
fs.copyFileSync(gitignoreSource, gitignoreDest);
|
|
42
|
-
structuredLog(
|
|
34
|
+
structuredLog("postinstall", "Created .gitignore from template", "info");
|
|
43
35
|
}
|
|
44
36
|
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
const skillsDest = path.join(targetDir, '.opencode', 'skills');
|
|
48
|
-
if (fs.existsSync(skillsSource) && resolvedPackage !== resolvedTarget) {
|
|
37
|
+
// Unified 4-platform bridge install (OpenCode, Grok, Hermes, OpenClaw)
|
|
38
|
+
if (isConsumerInstall(resolvedPackage, resolvedTarget)) {
|
|
49
39
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const skillMd = path.join(skillsSource, entry.name, 'SKILL.md');
|
|
56
|
-
if (!fs.existsSync(skillMd)) continue;
|
|
57
|
-
const destMd = path.join(skillsDest, entry.name, 'SKILL.md');
|
|
58
|
-
const destDir = path.dirname(destMd);
|
|
59
|
-
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
60
|
-
if (fs.existsSync(destMd)) {
|
|
61
|
-
if (fs.statSync(skillMd).mtime <= fs.statSync(destMd).mtime) continue;
|
|
62
|
-
}
|
|
63
|
-
fs.copyFileSync(skillMd, destMd);
|
|
64
|
-
copied++;
|
|
65
|
-
}
|
|
66
|
-
if (copied > 0) structuredLog('postinstall', `Skills: ${copied} installed to .opencode/skills/`, 'info');
|
|
40
|
+
installAllBridges({
|
|
41
|
+
targetDir,
|
|
42
|
+
packageRoot,
|
|
43
|
+
log: structuredLog,
|
|
44
|
+
});
|
|
67
45
|
} catch (e) {
|
|
68
|
-
structuredLog(
|
|
46
|
+
structuredLog("postinstall", "Bridge install failed", "warn", { error: e.message });
|
|
69
47
|
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Register MCP servers with Grok CLI (if available) using absolute paths to installed dist
|
|
73
|
-
// Full v2 surface: governance + skills + orchestrator + enforcer (additive, preserves prior behavior)
|
|
74
|
-
if (resolvedPackage !== resolvedTarget) {
|
|
75
|
-
try {
|
|
76
|
-
execSync('which grok', { stdio: 'ignore' });
|
|
77
|
-
const govServer = path.join(packageRoot, 'dist/mcps/governance.server.js');
|
|
78
|
-
const skillsServer = path.join(packageRoot, 'dist/mcps/knowledge-skills/skill-invocation.server.js');
|
|
79
|
-
const orchServer = path.join(packageRoot, 'dist/mcps/orchestrator.server.js');
|
|
80
|
-
const enforcerServer = path.join(packageRoot, 'dist/mcps/enforcer-tools.server.js');
|
|
81
|
-
const xrayRoot = targetDir;
|
|
82
48
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
execSync(
|
|
90
|
-
`grok mcp add xray-skills --command node --args "${skillsServer}" --env "XRAY_ROOT=${xrayRoot}"`,
|
|
91
|
-
{ stdio: 'pipe' }
|
|
92
|
-
);
|
|
93
|
-
structuredLog('postinstall', 'Registered xray-skills with Grok CLI', 'info');
|
|
94
|
-
|
|
95
|
-
execSync(
|
|
96
|
-
`grok mcp add xray-orchestrator --command node --args "${orchServer}" --env "XRAY_ROOT=${xrayRoot}"`,
|
|
97
|
-
{ stdio: 'pipe' }
|
|
98
|
-
);
|
|
99
|
-
structuredLog('postinstall', 'Registered xray-orchestrator with Grok CLI', 'info');
|
|
100
|
-
|
|
101
|
-
execSync(
|
|
102
|
-
`grok mcp add xray-enforcer --command node --args "${enforcerServer}" --env "XRAY_ROOT=${xrayRoot}"`,
|
|
103
|
-
{ stdio: 'pipe' }
|
|
104
|
-
);
|
|
105
|
-
structuredLog('postinstall', 'Registered xray-enforcer with Grok CLI', 'info');
|
|
106
|
-
} catch (_e) {
|
|
107
|
-
// grok not on PATH or registration failed — skip gracefully
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (resolvedPackage !== resolvedTarget) {
|
|
112
|
-
structuredLog('postinstall', 'xray v2 framework installed. Run `npx xray setup` for full configuration (hooks, Hermes, symlinks).', 'success');
|
|
113
|
-
}
|
|
49
|
+
structuredLog(
|
|
50
|
+
"postinstall",
|
|
51
|
+
"0xRay framework installed (4 bridges). Run `npx 0xray setup` for symlinks/Hermes skill extras.",
|
|
52
|
+
"success"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -92,6 +92,7 @@ function checkGitStatus() {
|
|
|
92
92
|
'logs/',
|
|
93
93
|
'backups/',
|
|
94
94
|
'Users/',
|
|
95
|
+
'.xray/inference/',
|
|
95
96
|
];
|
|
96
97
|
|
|
97
98
|
const realChanges = lines.filter(l => {
|
|
@@ -128,32 +129,35 @@ function checkGitStatus() {
|
|
|
128
129
|
|
|
129
130
|
function checkVersionSync() {
|
|
130
131
|
log('Checking version synchronization...', 'step');
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
|
|
133
|
+
const pkgPath = path.join(rootDir, 'package.json');
|
|
134
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
135
|
+
const pkgVersion = pkg.version;
|
|
136
|
+
|
|
133
137
|
const vmPath = path.join(rootDir, 'scripts/node/universal-version-manager.js');
|
|
134
138
|
const vmContent = fs.readFileSync(vmPath, 'utf-8');
|
|
135
|
-
const
|
|
136
|
-
|
|
139
|
+
const usesPackageSsot =
|
|
140
|
+
vmContent.includes('readPackageVersion()') &&
|
|
141
|
+
/version:\s*PACKAGE_VERSION/.test(vmContent);
|
|
142
|
+
|
|
143
|
+
if (usesPackageSsot) {
|
|
144
|
+
log(`Version SSOT: package.json (${pkgVersion})`, 'success');
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const versionMatch = vmContent.match(/framework:\s*\{[\s\S]*?version:\s*"([^"]+)"/);
|
|
137
149
|
if (!versionMatch) {
|
|
138
|
-
errors.push('Cannot find version in version manager');
|
|
150
|
+
errors.push('Cannot find framework version in version manager');
|
|
139
151
|
return false;
|
|
140
152
|
}
|
|
141
|
-
|
|
153
|
+
|
|
142
154
|
const vmVersion = versionMatch[1];
|
|
143
|
-
|
|
144
|
-
// Load package.json
|
|
145
|
-
const pkgPath = path.join(rootDir, 'package.json');
|
|
146
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
147
|
-
const pkgVersion = pkg.version;
|
|
148
|
-
|
|
149
155
|
if (pkgVersion !== vmVersion) {
|
|
150
|
-
// package.json is often 1 ahead of UVM after npm version patch — non-blocking
|
|
151
156
|
warnings.push(`Version mismatch: package.json (${pkgVersion}) != version manager (${vmVersion})`);
|
|
152
157
|
log(`Version mismatch: package.json=${pkgVersion}, version-manager=${vmVersion}`, 'warn');
|
|
153
|
-
|
|
158
|
+
} else {
|
|
159
|
+
log(`Versions aligned: ${pkgVersion}`, 'success');
|
|
154
160
|
}
|
|
155
|
-
|
|
156
|
-
log(`Versions aligned: ${pkgVersion}`, 'success');
|
|
157
161
|
return true;
|
|
158
162
|
}
|
|
159
163
|
|
package/scripts/node/setup.cjs
CHANGED
|
@@ -6,12 +6,8 @@ const path = require("path");
|
|
|
6
6
|
const packageRoot = path.join(__dirname, "..", "..");
|
|
7
7
|
const homeDir = require("os").homedir();
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
targetDir = path.join(__dirname, "..", "..", "..", "..");
|
|
12
|
-
} else {
|
|
13
|
-
targetDir = process.env.PWD || process.cwd();
|
|
14
|
-
}
|
|
9
|
+
const { resolveConsumerTargetDir, isConsumerInstall } = require("./install-bridges.cjs");
|
|
10
|
+
let targetDir = resolveConsumerTargetDir(packageRoot, process.env.PWD || process.cwd());
|
|
15
11
|
|
|
16
12
|
const resolvedPackage = path.resolve(packageRoot);
|
|
17
13
|
const resolvedTarget = path.resolve(targetDir);
|
|
@@ -159,8 +155,13 @@ if (fs.existsSync(distSource)) {
|
|
|
159
155
|
}
|
|
160
156
|
|
|
161
157
|
// 6. Convert MCP paths in consumer opencode.json
|
|
162
|
-
const isConsumer =
|
|
163
|
-
|
|
158
|
+
const isConsumer = isConsumerInstall(packageRoot, targetDir);
|
|
159
|
+
const consumerPkgName = isConsumer
|
|
160
|
+
? ["0xray", "xray"].find((name) =>
|
|
161
|
+
fs.existsSync(path.join(targetDir, "node_modules", name, "package.json"))
|
|
162
|
+
)
|
|
163
|
+
: null;
|
|
164
|
+
if (!hasHermes && isConsumer && consumerPkgName) {
|
|
164
165
|
const mainOpencodePath = path.join(targetDir, "opencode.json");
|
|
165
166
|
if (fs.existsSync(mainOpencodePath)) {
|
|
166
167
|
try {
|
|
@@ -171,13 +172,13 @@ if (!hasHermes && isConsumer) {
|
|
|
171
172
|
if (server.command && typeof server.command === "string") {
|
|
172
173
|
const normalized = server.command.replace(
|
|
173
174
|
/^[.]{0,2}\/dist\/mcps\//,
|
|
174
|
-
|
|
175
|
+
`node_modules/${consumerPkgName}/dist/mcps/`
|
|
175
176
|
);
|
|
176
177
|
if (normalized !== server.command) { server.command = normalized; modified = true; }
|
|
177
178
|
}
|
|
178
179
|
if (Array.isArray(server.command)) {
|
|
179
180
|
server.command = server.command.map(a =>
|
|
180
|
-
a.replace(/^[.]{0,2}\/dist\/mcps\//,
|
|
181
|
+
a.replace(/^[.]{0,2}\/dist\/mcps\//, `node_modules/${consumerPkgName}/dist/mcps/`)
|
|
181
182
|
);
|
|
182
183
|
modified = true;
|
|
183
184
|
}
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
* - Historical docs (docs/reflections, docs/archive)
|
|
18
18
|
* - Test files with assertions
|
|
19
19
|
*
|
|
20
|
-
* @version 2.0.0
|
|
21
20
|
* @since 2026-01-15
|
|
22
21
|
* @updated 2026-03-09 - Enhanced with documentation management, validation, and backup
|
|
23
22
|
*/
|
|
@@ -28,6 +27,19 @@ const require = createRequire(import.meta.url);
|
|
|
28
27
|
import fs from "fs";
|
|
29
28
|
import path from "path";
|
|
30
29
|
import { execSync } from "child_process";
|
|
30
|
+
import { fileURLToPath } from "url";
|
|
31
|
+
|
|
32
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const rootDir = path.resolve(__dirname, "../..");
|
|
34
|
+
|
|
35
|
+
function readPackageVersion() {
|
|
36
|
+
try {
|
|
37
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf-8"));
|
|
38
|
+
return pkg.version;
|
|
39
|
+
} catch {
|
|
40
|
+
return "0.0.0";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
31
43
|
|
|
32
44
|
/**
|
|
33
45
|
* Auto-calculate framework counts from filesystem
|
|
@@ -133,10 +145,12 @@ const CALCULATED_COUNTS = calculateCounts();
|
|
|
133
145
|
* Framework version information - SINGLE SOURCE OF TRUTH
|
|
134
146
|
* Version manager updates this, then propagates to all files
|
|
135
147
|
*/
|
|
148
|
+
const PACKAGE_VERSION = readPackageVersion();
|
|
149
|
+
|
|
136
150
|
const OFFICIAL_VERSIONS = {
|
|
137
|
-
// Framework version
|
|
151
|
+
// Framework version — SSOT: package.json
|
|
138
152
|
framework: {
|
|
139
|
-
version:
|
|
153
|
+
version: PACKAGE_VERSION,
|
|
140
154
|
displayName: "xray: Self-Healing AI Governance OS",
|
|
141
155
|
lastUpdated: "2026-06-17",
|
|
142
156
|
// Counts (auto-calculated, but can be overridden)
|
package/src/mcps/index.ts
CHANGED
|
@@ -50,6 +50,7 @@ export const MCP_SERVERS: McpServerEntry[] = [
|
|
|
50
50
|
{ serverName: "refactoring-strategies", sourceFile: "mcps/knowledge-skills/refactoring-strategies.server.ts", type: "knowledge-skill", registered: true, description: "Refactoring strategies" },
|
|
51
51
|
{ serverName: "security-audit", sourceFile: "mcps/knowledge-skills/security-audit.server.ts", type: "knowledge-skill", registered: true, description: "Security auditing" },
|
|
52
52
|
{ serverName: "seo-consultant", sourceFile: "mcps/knowledge-skills/seo-consultant.server.ts", type: "knowledge-skill", registered: false, description: "SEO consulting" },
|
|
53
|
+
{ serverName: "storyteller", sourceFile: "mcps/knowledge-skills/storyteller.server.ts", type: "knowledge-skill", registered: false, description: "Reflections, sagas, and technical narratives" },
|
|
53
54
|
{ serverName: "session-management", sourceFile: "mcps/knowledge-skills/session-management.server.ts", type: "knowledge-skill", registered: true, description: "Session management" },
|
|
54
55
|
{ serverName: "skill-invocation", sourceFile: "mcps/knowledge-skills/skill-invocation.server.ts", type: "knowledge-skill", registered: true, description: "Skill invocation" },
|
|
55
56
|
{ serverName: "strategist", sourceFile: "mcps/knowledge-skills/strategist.server.ts", type: "knowledge-skill", registered: false, description: "Strategic planning" },
|
|
@@ -186,7 +186,7 @@ class SkillInvocationServer extends XrayKnowledgeSkillBase {
|
|
|
186
186
|
"strategist", "skill-invocation",
|
|
187
187
|
];
|
|
188
188
|
const knowledgeSkills = [
|
|
189
|
-
"architecture-patterns", "strategist", "tech-writer",
|
|
189
|
+
"architecture-patterns", "strategist", "tech-writer", "storyteller",
|
|
190
190
|
"seo-consultant", "content-creator", "growth-strategist",
|
|
191
191
|
"bug-triage-specialist", "log-monitor",
|
|
192
192
|
"mobile-development", "git-workflow", "session-management",
|
|
@@ -236,6 +236,7 @@ class SkillInvocationServer extends XrayKnowledgeSkillBase {
|
|
|
236
236
|
"tech-writer": "documentation-generation",
|
|
237
237
|
"frontend-ui-ux-engineer": "ui-ux-design",
|
|
238
238
|
"frontend-engineer": "project-analysis",
|
|
239
|
+
"storyteller": "storyteller",
|
|
239
240
|
};
|
|
240
241
|
|
|
241
242
|
const resolvedSkill = skillAliases[skillName] || skillName;
|
|
@@ -253,7 +254,7 @@ class SkillInvocationServer extends XrayKnowledgeSkillBase {
|
|
|
253
254
|
"git-workflow", "content-creator", "ui-ux-design",
|
|
254
255
|
"multimodal-looker", "refactoring-strategies",
|
|
255
256
|
"project-analysis", "testing-best-practices",
|
|
256
|
-
"architecture-patterns",
|
|
257
|
+
"architecture-patterns", "storyteller",
|
|
257
258
|
];
|
|
258
259
|
|
|
259
260
|
if (!availableServers.includes(resolvedSkill)) {
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storyteller MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Returns storyteller SKILL.md guidance plus a session-aware writing prompt.
|
|
5
|
+
* The host LLM writes the story; this server supplies the contract.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { dirname, join } from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { XrayKnowledgeSkillBase } from '../shared/knowledge-skill-base.js';
|
|
13
|
+
|
|
14
|
+
const STORY_TYPES = ['reflection', 'saga', 'journey', 'narrative'] as const;
|
|
15
|
+
type StoryType = (typeof STORY_TYPES)[number];
|
|
16
|
+
|
|
17
|
+
interface WriteStoryArgs {
|
|
18
|
+
title?: string;
|
|
19
|
+
context?: Record<string, unknown>;
|
|
20
|
+
framework?: string;
|
|
21
|
+
output?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolveSkillMd(): string {
|
|
25
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const candidates = [
|
|
27
|
+
join(here, '../../../skills/storyteller/SKILL.md'),
|
|
28
|
+
join(here, '../../skills/storyteller/SKILL.md'),
|
|
29
|
+
join(process.cwd(), 'node_modules/0xray/dist/skills/storyteller/SKILL.md'),
|
|
30
|
+
join(process.cwd(), 'node_modules/xray/dist/skills/storyteller/SKILL.md'),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
for (const path of candidates) {
|
|
34
|
+
if (existsSync(path)) {
|
|
35
|
+
return readFileSync(path, 'utf-8');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return '# Storyteller\n\nWrite warmly candid technical reflections with scene-first openings.';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getStoryMeta(storyType: StoryType) {
|
|
43
|
+
const defaults: Record<
|
|
44
|
+
StoryType,
|
|
45
|
+
{ location: string; min_words: number; ideal_words: number; framework: string }
|
|
46
|
+
> = {
|
|
47
|
+
reflection: {
|
|
48
|
+
location: 'docs/reflections/',
|
|
49
|
+
min_words: 2000,
|
|
50
|
+
ideal_words: 5000,
|
|
51
|
+
framework: 'three_act_structure',
|
|
52
|
+
},
|
|
53
|
+
saga: {
|
|
54
|
+
location: 'docs/reflections/deep/',
|
|
55
|
+
min_words: 5000,
|
|
56
|
+
ideal_words: 15000,
|
|
57
|
+
framework: 'hero_journey',
|
|
58
|
+
},
|
|
59
|
+
journey: {
|
|
60
|
+
location: 'docs/reflections/deep/',
|
|
61
|
+
min_words: 1500,
|
|
62
|
+
ideal_words: 4000,
|
|
63
|
+
framework: 'three_act_structure',
|
|
64
|
+
},
|
|
65
|
+
narrative: {
|
|
66
|
+
location: 'docs/reflections/',
|
|
67
|
+
min_words: 1000,
|
|
68
|
+
ideal_words: 3000,
|
|
69
|
+
framework: 'three_act_structure',
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
return defaults[storyType];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function gitContext(): string {
|
|
76
|
+
try {
|
|
77
|
+
const commits = execSync('git log --oneline -20 2>/dev/null', { encoding: 'utf-8' });
|
|
78
|
+
const files = execSync(
|
|
79
|
+
'git diff --name-only HEAD~10 2>/dev/null || git ls-files 2>/dev/null | head -50',
|
|
80
|
+
{ encoding: 'utf-8' },
|
|
81
|
+
);
|
|
82
|
+
return `Recent commits:\n${commits}\n\nRecent files:\n${files}`;
|
|
83
|
+
} catch {
|
|
84
|
+
return 'No git context available in this workspace.';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildPrompt(
|
|
89
|
+
storyType: StoryType,
|
|
90
|
+
title: string,
|
|
91
|
+
meta: ReturnType<typeof getStoryMeta>,
|
|
92
|
+
extraContext?: Record<string, unknown>,
|
|
93
|
+
): string {
|
|
94
|
+
const contextBlock = extraContext
|
|
95
|
+
? `\n## Provided context\n${JSON.stringify(extraContext, null, 2)}\n`
|
|
96
|
+
: '';
|
|
97
|
+
|
|
98
|
+
return `Write a ${storyType} titled "${title}".
|
|
99
|
+
|
|
100
|
+
## Requirements
|
|
101
|
+
- Framework: ${meta.framework}
|
|
102
|
+
- Target length: ${meta.ideal_words} words (minimum: ${meta.min_words})
|
|
103
|
+
- Save to: ${meta.location}
|
|
104
|
+
- Follow storyteller SKILL.md voice (warmly candid, scene-first, no executive summary)
|
|
105
|
+
- Include Key Takeaways and What Next sections
|
|
106
|
+
|
|
107
|
+
## Session / repo context
|
|
108
|
+
${gitContext()}
|
|
109
|
+
${contextBlock}
|
|
110
|
+
|
|
111
|
+
Write the complete ${storyType} now.`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function writeStoryTool(storyType: StoryType, args: WriteStoryArgs) {
|
|
115
|
+
const meta = getStoryMeta(storyType);
|
|
116
|
+
if (args.framework) meta.framework = args.framework;
|
|
117
|
+
|
|
118
|
+
const title =
|
|
119
|
+
args.title ??
|
|
120
|
+
`${storyType.charAt(0).toUpperCase() + storyType.slice(1)} - ${new Date().toISOString().split('T')[0]}`;
|
|
121
|
+
const skillMd = resolveSkillMd();
|
|
122
|
+
const prompt = buildPrompt(storyType, title, meta, args.context);
|
|
123
|
+
const slug = title
|
|
124
|
+
.toLowerCase()
|
|
125
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
126
|
+
.replace(/^-|-$/g, '');
|
|
127
|
+
const filename = `${storyType}-${slug}-${new Date().toISOString().split('T')[0]}.md`;
|
|
128
|
+
const outputPath = args.output ?? join(meta.location, filename);
|
|
129
|
+
|
|
130
|
+
const outputDir = join(process.cwd(), dirname(outputPath));
|
|
131
|
+
if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
|
|
132
|
+
|
|
133
|
+
const fullPath = join(process.cwd(), outputPath);
|
|
134
|
+
const frontmatter = `---
|
|
135
|
+
story_type: ${storyType}
|
|
136
|
+
title: "${title}"
|
|
137
|
+
framework: ${meta.framework}
|
|
138
|
+
target_words: ${meta.ideal_words}
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
writeFileSync(fullPath, `${frontmatter}\n${prompt}\n`, 'utf-8');
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
content: [
|
|
147
|
+
{
|
|
148
|
+
type: 'text' as const,
|
|
149
|
+
text: [
|
|
150
|
+
`Storyteller ${storyType} brief prepared.`,
|
|
151
|
+
`Output template: ${outputPath}`,
|
|
152
|
+
'',
|
|
153
|
+
'--- SKILL (excerpt) ---',
|
|
154
|
+
skillMd.slice(0, 4000),
|
|
155
|
+
skillMd.length > 4000 ? '\n...(truncated; full SKILL.md on disk)' : '',
|
|
156
|
+
'',
|
|
157
|
+
'--- WRITING PROMPT ---',
|
|
158
|
+
prompt,
|
|
159
|
+
'',
|
|
160
|
+
'Host LLM: write the full story into the output path using the prompt above.',
|
|
161
|
+
].join('\n'),
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
class StorytellerServer extends XrayKnowledgeSkillBase {
|
|
168
|
+
constructor() {
|
|
169
|
+
super('storyteller', '1.0.0');
|
|
170
|
+
|
|
171
|
+
const writeSchema = {
|
|
172
|
+
type: 'object' as const,
|
|
173
|
+
properties: {
|
|
174
|
+
title: { type: 'string', description: 'Story title' },
|
|
175
|
+
context: { type: 'object', description: 'Session context (commits, changes, notes)' },
|
|
176
|
+
framework: { type: 'string', description: 'Storytelling framework override' },
|
|
177
|
+
output: { type: 'string', description: 'Relative output path' },
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
this.tools = STORY_TYPES.map((storyType) => ({
|
|
182
|
+
name: `write_${storyType}`,
|
|
183
|
+
description: `Prepare ${storyType} story brief using storyteller SKILL.md`,
|
|
184
|
+
inputSchema: writeSchema,
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
this.handlers = {
|
|
188
|
+
write_reflection: async (args) => writeStoryTool('reflection', args as WriteStoryArgs),
|
|
189
|
+
write_saga: async (args) => writeStoryTool('saga', args as WriteStoryArgs),
|
|
190
|
+
write_journey: async (args) => writeStoryTool('journey', args as WriteStoryArgs),
|
|
191
|
+
write_narrative: async (args) => writeStoryTool('narrative', args as WriteStoryArgs),
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
this.setupToolHandlers();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (import.meta.url === `file://${fileURLToPath(import.meta.url)}`) {
|
|
199
|
+
const server = new StorytellerServer();
|
|
200
|
+
server.run().catch((error) => {
|
|
201
|
+
process.stderr.write(`FATAL: ${String(error)}\n`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export { StorytellerServer };
|
|
@@ -213,15 +213,18 @@ export class ExecutionPlanner {
|
|
|
213
213
|
agentAssignments: Map<string, OrchestrationTask[]>
|
|
214
214
|
): void {
|
|
215
215
|
for (const task of tasks) {
|
|
216
|
-
const complexity = task
|
|
216
|
+
const complexity = this.calculateTaskComplexity(task);
|
|
217
217
|
const requiredCaps = [
|
|
218
218
|
task.type,
|
|
219
219
|
...(task.metadata?.memorySignals ?? []),
|
|
220
220
|
];
|
|
221
|
+
const operationDescription = task.metadata?.memoryHighConfidenceTrap
|
|
222
|
+
? `${task.description} TYPE: ontological-trap`
|
|
223
|
+
: task.description;
|
|
221
224
|
const agent = this.capabilitiesManager.selectAgentForTask(
|
|
222
225
|
requiredCaps,
|
|
223
226
|
complexity,
|
|
224
|
-
|
|
227
|
+
operationDescription,
|
|
225
228
|
) || 'orchestrator';
|
|
226
229
|
|
|
227
230
|
if (!agentAssignments.has(agent)) {
|
|
@@ -346,6 +349,15 @@ export class ExecutionPlanner {
|
|
|
346
349
|
complexity += task.dependencies.length * 5;
|
|
347
350
|
}
|
|
348
351
|
|
|
352
|
+
if (task.metadata?.memoryComplexityBoost !== undefined) {
|
|
353
|
+
complexity += task.metadata.memoryComplexityBoost;
|
|
354
|
+
} else {
|
|
355
|
+
const provider = getProvider();
|
|
356
|
+
if (provider.id !== 'null' && provider.getTaskConfidence) {
|
|
357
|
+
complexity += provider.getTaskConfidence(toMemoryTask(task)).complexityBoost;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
349
361
|
return Math.min(Math.max(Math.round(complexity), 1), 100);
|
|
350
362
|
}
|
|
351
363
|
|
|
@@ -19,6 +19,10 @@ export interface OrchestrationTaskMetadata {
|
|
|
19
19
|
synthesisContext?: string;
|
|
20
20
|
ontologicalTrapDetected?: boolean;
|
|
21
21
|
memoryProviderId?: string;
|
|
22
|
+
memorySignalConfidences?: Record<string, number>;
|
|
23
|
+
memoryAvgConfidence?: number;
|
|
24
|
+
memoryHighConfidenceTrap?: boolean;
|
|
25
|
+
memoryComplexityBoost?: number;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
export interface OrchestrationTask {
|