0xray 3.3.0 → 3.3.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.
@@ -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.estimatedComplexity || 30;
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
- task.description,
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 {
@@ -0,0 +1,144 @@
1
+ import { frameworkLogger } from '../core/framework-logger.js';
2
+ import { getMemoryRoutingProviderSync } from '../memory-routing/index.js';
3
+ import type {
4
+ MemoryRoutingContext,
5
+ MemoryTaskConfidence,
6
+ } from '../memory-routing/types.js';
7
+
8
+ const TRAP_LANGUAGE = /TYPE:\s*ontological-trap|ontological[- ]trap/i;
9
+ const DEFAULT_MIN_CONFIDENCE_GATE = 0.55;
10
+
11
+ export interface ResearcherMemoryContext {
12
+ providerId: string;
13
+ confidence: MemoryTaskConfidence;
14
+ matchedSignals: string[];
15
+ recommendedAgent: string | null;
16
+ triggeredBy: 'trap-language' | 'high-confidence-primitives';
17
+ }
18
+
19
+ export function shouldQueryRepertoireConfidence(
20
+ description: string,
21
+ proposalType = '',
22
+ ): boolean {
23
+ const text = `${description} ${proposalType}`.trim();
24
+ return TRAP_LANGUAGE.test(text);
25
+ }
26
+
27
+ export function hasHighConfidencePrimitiveMatch(
28
+ routingContext: MemoryRoutingContext,
29
+ minConfidence = DEFAULT_MIN_CONFIDENCE_GATE,
30
+ ): boolean {
31
+ const confidences = routingContext.signalConfidences ?? {};
32
+ return Object.values(confidences).some((confidence) => confidence >= minConfidence);
33
+ }
34
+
35
+ function buildTaskText(
36
+ proposalTitle: string,
37
+ proposalDescription: string,
38
+ proposalType: string,
39
+ ): string {
40
+ return [proposalTitle, proposalDescription, proposalType].filter(Boolean).join('\n');
41
+ }
42
+
43
+ /**
44
+ * Resolve Repertoire task confidence for researcher proposal analysis.
45
+ * Triggered by ontological-trap language or high-confidence primitive matches.
46
+ */
47
+ export function resolveResearcherMemoryContext(input: {
48
+ proposalTitle?: string;
49
+ proposalDescription?: string;
50
+ proposalType?: string;
51
+ }): ResearcherMemoryContext | null {
52
+ const proposalTitle = input.proposalTitle ?? '';
53
+ const proposalDescription = input.proposalDescription ?? '';
54
+ const proposalType = input.proposalType ?? '';
55
+ const taskText = buildTaskText(proposalTitle, proposalDescription, proposalType);
56
+
57
+ const provider = getMemoryRoutingProviderSync();
58
+ if (provider.id === 'null' || !provider.getTaskConfidence) {
59
+ return null;
60
+ }
61
+
62
+ const trapLanguageDetected = shouldQueryRepertoireConfidence(
63
+ proposalDescription,
64
+ proposalType,
65
+ );
66
+ const routingContext = provider.buildRoutingContext(taskText);
67
+ const primitiveMatchDetected = hasHighConfidencePrimitiveMatch(routingContext);
68
+
69
+ if (!trapLanguageDetected && !primitiveMatchDetected) {
70
+ return null;
71
+ }
72
+
73
+ const confidence = provider.getTaskConfidence({
74
+ id: 'researcher-proposal',
75
+ description: taskText,
76
+ type: proposalType || 'governance',
77
+ });
78
+
79
+ const matchedSignals =
80
+ confidence.matchedSignals.length > 0
81
+ ? confidence.matchedSignals
82
+ : routingContext.matchedSignals;
83
+
84
+ const context: ResearcherMemoryContext = {
85
+ providerId: provider.id,
86
+ confidence,
87
+ matchedSignals,
88
+ recommendedAgent: confidence.recommendedAgent,
89
+ triggeredBy: trapLanguageDetected ? 'trap-language' : 'high-confidence-primitives',
90
+ };
91
+
92
+ if (confidence.highConfidenceTrapPresent) {
93
+ frameworkLogger.log('mcps/researcher', 'repertoire-trap-detected', 'info', {
94
+ providerId: provider.id,
95
+ matchedSignals,
96
+ complexityBoost: confidence.complexityBoost,
97
+ recommendedAgent: confidence.recommendedAgent,
98
+ triggeredBy: context.triggeredBy,
99
+ });
100
+ }
101
+
102
+ return context;
103
+ }
104
+
105
+ export function buildMemoryRoutingEvidence(
106
+ context: ResearcherMemoryContext,
107
+ ): string[] {
108
+ const { confidence, matchedSignals, recommendedAgent } = context;
109
+ const signalSummary = confidence.signals
110
+ .map((entry) => `${entry.name} (${entry.confidence.toFixed(2)})`)
111
+ .join(', ');
112
+
113
+ const lines = [
114
+ `Repertoire memory routing (${context.providerId}, trigger: ${context.triggeredBy})`,
115
+ `Matched signals: ${matchedSignals.join(', ') || 'none'}`,
116
+ `Signal confidences: ${signalSummary || 'none'}`,
117
+ `High-confidence trap present: ${confidence.highConfidenceTrapPresent}`,
118
+ `Complexity boost: ${confidence.complexityBoost}`,
119
+ ];
120
+
121
+ if (recommendedAgent) {
122
+ lines.push(`Recommended agent for trap handling: ${recommendedAgent}`);
123
+ }
124
+
125
+ return lines;
126
+ }
127
+
128
+ export function formatMemoryRoutingBlock(context: ResearcherMemoryContext): string {
129
+ const { confidence, matchedSignals, recommendedAgent } = context;
130
+ const lines = [
131
+ 'MEMORY_ROUTING:',
132
+ ` provider: ${context.providerId}`,
133
+ ` trigger: ${context.triggeredBy}`,
134
+ ` matchedSignals: ${matchedSignals.join(', ') || 'none'}`,
135
+ ` highConfidenceTrapPresent: ${confidence.highConfidenceTrapPresent}`,
136
+ ` complexityBoost: ${confidence.complexityBoost}`,
137
+ ];
138
+
139
+ if (recommendedAgent) {
140
+ lines.push(` recommendedAgent: ${recommendedAgent}`);
141
+ }
142
+
143
+ return lines.join('\n');
144
+ }
@@ -13,6 +13,11 @@ import * as fs from "fs";
13
13
  import * as path from "path";
14
14
  import { frameworkLogger } from "../core/framework-logger.js";
15
15
  import { tryLLMGovernance } from "../governance/llm-governance-provider.js";
16
+ import {
17
+ buildMemoryRoutingEvidence,
18
+ formatMemoryRoutingBlock,
19
+ resolveResearcherMemoryContext,
20
+ } from "./researcher-confidence.js";
16
21
  import { XrayKnowledgeSkillBase } from "./shared/knowledge-skill-base.js";
17
22
 
18
23
  interface SearchResult {
@@ -452,20 +457,40 @@ class XrayLibrarianServer extends XrayKnowledgeSkillBase {
452
457
  async analyzeProposal(args: AnalyzeProposalArgs): Promise<CallToolResult> {
453
458
  const { proposalTitle = "", proposalDescription = "", evidence = [], proposalType = "" } = args || {};
454
459
 
460
+ const memoryContext = resolveResearcherMemoryContext({
461
+ proposalTitle,
462
+ proposalDescription,
463
+ proposalType,
464
+ });
465
+
466
+ const enrichedEvidence = [...(evidence || [])];
467
+ if (memoryContext?.confidence.highConfidenceTrapPresent) {
468
+ enrichedEvidence.push(...buildMemoryRoutingEvidence(memoryContext));
469
+ }
470
+
455
471
  const vote = await tryLLMGovernance(
456
472
  "researcher",
457
473
  proposalTitle || "",
458
474
  proposalDescription || "",
459
- evidence || [],
475
+ enrichedEvidence,
460
476
  proposalType || "",
461
477
  );
462
478
 
479
+ const memoryRoutingBlock =
480
+ memoryContext?.confidence.highConfidenceTrapPresent
481
+ ? `\n${formatMemoryRoutingBlock(memoryContext)}`
482
+ : "";
483
+
463
484
  if (!vote) {
485
+ const abstainReason = memoryContext?.confidence.highConfidenceTrapPresent
486
+ ? "No LLM governance provider configured. Repertoire detected a high-confidence ontological trap — route to the recommended agent before proceeding."
487
+ : "No LLM governance provider configured. Set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT, or install Hermes and run: hermes auth add xai-oauth --no-browser";
488
+
464
489
  return {
465
490
  content: [
466
491
  {
467
492
  type: "text",
468
- text: "DECISION: abstain\nCONFIDENCE: 0.50\nREASONING: No LLM governance provider configured. Set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT, or install Hermes and run: hermes auth add xai-oauth --no-browser",
493
+ text: `DECISION: abstain\nCONFIDENCE: 0.50\nREASONING: ${abstainReason}${memoryRoutingBlock}`,
469
494
  },
470
495
  ],
471
496
  };
@@ -475,7 +500,7 @@ class XrayLibrarianServer extends XrayKnowledgeSkillBase {
475
500
  content: [
476
501
  {
477
502
  type: "text",
478
- text: `DECISION: ${vote.decision}\nCONFIDENCE: ${vote.confidence.toFixed(2)}\nREASONING: ${vote.reasoning}`,
503
+ text: `DECISION: ${vote.decision}\nCONFIDENCE: ${vote.confidence.toFixed(2)}\nREASONING: ${vote.reasoning}${memoryRoutingBlock}`,
479
504
  },
480
505
  ],
481
506
  };
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.3.0",
2
+ "version": "3.3.1",
3
3
  "terms": [
4
4
  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60
5
5
  ],