0xray 4.0.23 → 4.0.25
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 +11 -1
- package/.xray/features.schema.json +17 -0
- package/AGENTS-consumer.md +1 -1
- package/AGENTS.md +1 -1
- package/README.md +2 -2
- package/SKILLS.md +1 -1
- package/dist/AGENTS.md +1 -1
- package/dist/CHANGELOG.md +43 -0
- package/dist/README.md +2 -2
- package/dist/SKILLS.md +1 -1
- package/dist/cli/commands/validate.js +3 -0
- package/dist/cli/index.js +131 -100
- package/dist/core/features-config.d.ts +4 -0
- package/dist/core/features-config.js +10 -0
- package/dist/inference/inference-accumulator.d.ts +2 -0
- package/dist/inference/inference-accumulator.js +74 -2
- package/dist/inference/inference-cycle.js +1 -0
- package/dist/inference/inference-proposal-generator.js +65 -7
- package/dist/inference/inference-run-gate.d.ts +8 -0
- package/dist/inference/inference-run-gate.js +10 -0
- package/dist/inference/session-capture.d.ts +27 -0
- package/dist/inference/session-capture.js +277 -1
- package/dist/integrations/cursor/hooks/pre-compact.js +89 -1
- package/dist/integrations/hooks/plates.cjs +165 -0
- package/dist/integrations/hooks/station-hook-runtime.cjs +29 -11
- package/dist/integrations/hooks/station-memory-ingest.mjs +109 -42
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/mcps/researcher-confidence.d.ts +5 -1
- package/dist/mcps/researcher-confidence.js +37 -0
- package/dist/mcps/researcher.server.js +2 -2
- package/dist/memory-routing/plates.d.ts +16 -0
- package/dist/memory-routing/plates.js +71 -0
- package/dist/memory-routing/types.d.ts +2 -0
- package/dist/memory-routing/types.js +2 -1
- package/dist/processors/implementations/inference-improvement-processor.d.ts +5 -0
- package/dist/processors/implementations/inference-improvement-processor.js +101 -2
- package/dist/processors/implementations/storytelling-trigger-processor.js +1 -11
- package/dist/processors/processor-manager.d.ts +1 -1
- package/dist/processors/processor-manager.js +52 -20
- package/docs-site/docs/plates/boot.md +52 -0
- package/docs-site/docs/plates/governance.md +50 -0
- package/docs-site/docs/plates/index.md +16 -0
- package/docs-site/docs/plates/memory-recall.md +49 -0
- package/docs-site/docs/plates/orchestration.md +55 -0
- package/docs-site/docs/plates/processor.md +46 -0
- package/docs-site/docs/plates/reporting.md +42 -0
- package/docs-site/docs/plates/routing.md +49 -0
- package/llms.txt +1 -1
- package/package.json +2 -1
- package/scripts/foundry/assert-packed-dist-cli.mjs +3 -0
- package/scripts/foundry/plant/skills/mill/SKILL.md +1 -1
- package/scripts/node/consumer-install-smoke.mjs +7 -4
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/mcps/researcher-confidence.ts +56 -3
- package/src/mcps/researcher.server.ts +4 -5
- package/vendor/@0xray/repertoire/README.md +3 -1
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts +23 -3
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js +119 -16
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/types.d.ts +1 -1
- package/vendor/@0xray/repertoire/dist/types.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/package.json +1 -1
- package/xray/features.json +11 -1
- package/xray/features.schema.json +17 -0
|
@@ -22,6 +22,74 @@ import {
|
|
|
22
22
|
writeCursorUsageReceipt,
|
|
23
23
|
} from './cursor-usage-receipt.js';
|
|
24
24
|
|
|
25
|
+
async function loadPlates() {
|
|
26
|
+
const { existsSync } = await import('node:fs');
|
|
27
|
+
const { dirname, join } = await import('node:path');
|
|
28
|
+
const { fileURLToPath, pathToFileURL } = await import('node:url');
|
|
29
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const candidates = [
|
|
31
|
+
join(here, '../../hooks/plates.cjs'),
|
|
32
|
+
join(here, '../../../integrations/hooks/plates.cjs'),
|
|
33
|
+
];
|
|
34
|
+
const found = candidates.find((candidate) => existsSync(candidate));
|
|
35
|
+
if (!found) return null;
|
|
36
|
+
const loaded = await import(pathToFileURL(found).href);
|
|
37
|
+
if (typeof loaded.recallPlate === 'function') return loaded;
|
|
38
|
+
if (loaded.default && typeof loaded.default.recallPlate === 'function') return loaded.default;
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function memoryRoutingModule() {
|
|
43
|
+
const { existsSync } = await import('node:fs');
|
|
44
|
+
const { dirname, join } = await import('node:path');
|
|
45
|
+
const { fileURLToPath, pathToFileURL } = await import('node:url');
|
|
46
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
47
|
+
const candidates = [
|
|
48
|
+
join(here, '../../../memory-routing/index.js'),
|
|
49
|
+
join(here, '../../../../dist/memory-routing/index.js'),
|
|
50
|
+
];
|
|
51
|
+
const found = candidates.find((candidate) => existsSync(candidate));
|
|
52
|
+
if (!found) throw new Error('memory-routing module missing');
|
|
53
|
+
return pathToFileURL(found).href;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function lessonSpeechForIntent(root, intent) {
|
|
57
|
+
if (!intent) return [];
|
|
58
|
+
try {
|
|
59
|
+
const { readFileSync, existsSync } = await import('node:fs');
|
|
60
|
+
const { join } = await import('node:path');
|
|
61
|
+
const { loadMemoryRoutingProvider } = await import(await memoryRoutingModule());
|
|
62
|
+
let routing = { enabled: false, provider: 'null' };
|
|
63
|
+
const featuresPath = join(root, '.xray', 'features.json');
|
|
64
|
+
if (existsSync(featuresPath)) {
|
|
65
|
+
try {
|
|
66
|
+
routing = JSON.parse(readFileSync(featuresPath, 'utf8')).memory_routing || routing;
|
|
67
|
+
} catch {
|
|
68
|
+
/* leftover default */
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const provider = await loadMemoryRoutingProvider(routing, root);
|
|
72
|
+
if (!provider || provider.id === 'null' || typeof provider.buildRoutingContext !== 'function') {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const context = provider.buildRoutingContext(String(intent));
|
|
76
|
+
const matched = new Set(Array.isArray(context.matchedSignals) ? context.matchedSignals : []);
|
|
77
|
+
const lines = [];
|
|
78
|
+
for (const lesson of context.lessons || []) {
|
|
79
|
+
if (!matched.has(lesson.name)) continue;
|
|
80
|
+
for (const line of lesson.lines || []) {
|
|
81
|
+
const text = String(line.text || '').trim().slice(0, 400);
|
|
82
|
+
if (text.length === 0) continue;
|
|
83
|
+
lines.push(`${lesson.name}: ${text}`);
|
|
84
|
+
if (lines.length >= 4) return lines;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return lines;
|
|
88
|
+
} catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
25
93
|
function extractIntent(event) {
|
|
26
94
|
return (
|
|
27
95
|
event.prompt ||
|
|
@@ -47,6 +115,13 @@ async function main() {
|
|
|
47
115
|
let receiptPath = null;
|
|
48
116
|
let usagePath = null;
|
|
49
117
|
let stationLine = null;
|
|
118
|
+
let plates = null;
|
|
119
|
+
let plateBody = '';
|
|
120
|
+
try {
|
|
121
|
+
plates = await loadPlates();
|
|
122
|
+
} catch {
|
|
123
|
+
plates = null;
|
|
124
|
+
}
|
|
50
125
|
for (const root of cursorHeatRoots(event)) {
|
|
51
126
|
const payload = buildSessionBootPayload(root, '0xray/cursor-compact', {
|
|
52
127
|
host: 'cursor',
|
|
@@ -77,6 +152,17 @@ async function main() {
|
|
|
77
152
|
sessionId,
|
|
78
153
|
usage: hostUsageFromPreCompactEvent(event),
|
|
79
154
|
});
|
|
155
|
+
if (plates) {
|
|
156
|
+
try {
|
|
157
|
+
const named = plates.recallPlate(payload.intent || intent || '');
|
|
158
|
+
if (named) {
|
|
159
|
+
plates.stampPlateIfMissing(root, named.id);
|
|
160
|
+
if (!plateBody) plateBody = `\n\n${String(named.body || '').trim()}`;
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
/* plate stamp is observational */
|
|
164
|
+
}
|
|
165
|
+
}
|
|
80
166
|
}
|
|
81
167
|
appendHookActivity(eventRoot, 'cursor-pre-compact', 'station-written', 'success', {
|
|
82
168
|
bootPath,
|
|
@@ -85,10 +171,12 @@ async function main() {
|
|
|
85
171
|
event_class: eventClass,
|
|
86
172
|
stationLine,
|
|
87
173
|
});
|
|
174
|
+
const lessonLines = await lessonSpeechForIntent(eventRoot, intent);
|
|
175
|
+
const lessonNote = lessonLines.length > 0 ? ` Lessons: ${lessonLines.join(' | ')}` : '';
|
|
88
176
|
console.log(
|
|
89
177
|
JSON.stringify({
|
|
90
178
|
user_message:
|
|
91
|
-
`0xRay Station written. Read .xray/state/STATION.md — do not cold-start. event_class=${eventClass}`,
|
|
179
|
+
`0xRay Station written. Read .xray/state/STATION.md — do not cold-start. event_class=${eventClass}${lessonNote}${plateBody}`,
|
|
92
180
|
}),
|
|
93
181
|
);
|
|
94
182
|
process.exit(0);
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime plate reader. One implementation.
|
|
3
|
+
* src/memory-routing/plates.ts is the typed API and delegates here so
|
|
4
|
+
* Station (CJS) and the Cursor preCompact hook (plain node) cannot drift.
|
|
5
|
+
*/
|
|
6
|
+
const { existsSync, mkdirSync, readFileSync, writeFileSync } = require("fs");
|
|
7
|
+
const { dirname, join } = require("path");
|
|
8
|
+
|
|
9
|
+
const PLATE_IDS = Object.freeze([
|
|
10
|
+
"routing",
|
|
11
|
+
"governance",
|
|
12
|
+
"boot",
|
|
13
|
+
"orchestration",
|
|
14
|
+
"processor",
|
|
15
|
+
"reporting",
|
|
16
|
+
"memory-recall",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
/** Id token, then extra phrases. Equal top scores recall nothing. */
|
|
20
|
+
const CUES = {
|
|
21
|
+
routing: [
|
|
22
|
+
[/\brouting\b/i, 5],
|
|
23
|
+
[/thindispatch/i, 4],
|
|
24
|
+
[/task-skill-router|taskskillrouter/i, 4],
|
|
25
|
+
[/scoreandroute/i, 3],
|
|
26
|
+
[/\broute(?:s|d)?\b/i, 3],
|
|
27
|
+
],
|
|
28
|
+
governance: [
|
|
29
|
+
[/\bgovernance\b/i, 5],
|
|
30
|
+
[/rule-enforcer|ruleenforcer/i, 4],
|
|
31
|
+
[/dynamo solar/i, 3],
|
|
32
|
+
],
|
|
33
|
+
boot: [
|
|
34
|
+
[/\bboot\b/i, 5],
|
|
35
|
+
[/boot-orchestrator|bootorchestrator/i, 4],
|
|
36
|
+
[/executebootsequence/i, 4],
|
|
37
|
+
],
|
|
38
|
+
orchestration: [
|
|
39
|
+
[/\borchestrat\w*/i, 5],
|
|
40
|
+
[/aside-?context/i, 4],
|
|
41
|
+
[/spawnaside|spawn aside/i, 3],
|
|
42
|
+
[/analyze-complexity/i, 2],
|
|
43
|
+
[/govern-and-apply/i, 2],
|
|
44
|
+
],
|
|
45
|
+
processor: [
|
|
46
|
+
[/\bprocessors?\b/i, 5],
|
|
47
|
+
[/pre[\s-]+processors?/i, 6],
|
|
48
|
+
[/post[\s-]+processors?/i, 4],
|
|
49
|
+
[/executepreprocessors|execute-pre-processors/i, 6],
|
|
50
|
+
[/processor-?manager/i, 4],
|
|
51
|
+
],
|
|
52
|
+
reporting: [
|
|
53
|
+
[/\breporting\b/i, 5],
|
|
54
|
+
[/framework-reporting|framework report/i, 4],
|
|
55
|
+
[/activity report/i, 4],
|
|
56
|
+
[/\breports?\b/i, 2],
|
|
57
|
+
],
|
|
58
|
+
"memory-recall": [
|
|
59
|
+
[/\bmemory-recall\b/i, 5],
|
|
60
|
+
[/memory\s+recall/i, 5],
|
|
61
|
+
[/recall(?:s|ed|ing)?\s+(?:a\s+|one\s+|the\s+)?plate/i, 5],
|
|
62
|
+
[/stamp(?:ed|ing)?\s+plates?/i, 4],
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function platesDir() {
|
|
67
|
+
let dir = __dirname;
|
|
68
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
69
|
+
const candidate = join(dir, "docs-site", "docs", "plates");
|
|
70
|
+
if (existsSync(join(candidate, "index.md"))) return candidate;
|
|
71
|
+
const parent = dirname(dir);
|
|
72
|
+
if (parent === dir) break;
|
|
73
|
+
dir = parent;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function assertPlateId(id) {
|
|
79
|
+
if (!PLATE_IDS.includes(id)) {
|
|
80
|
+
throw new Error(`unknown plate: ${id}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function plateSourcePath(id) {
|
|
85
|
+
assertPlateId(id);
|
|
86
|
+
const dir = platesDir();
|
|
87
|
+
if (!dir) throw new Error("plate docs missing");
|
|
88
|
+
const sourcePath = join(dir, `${id}.md`);
|
|
89
|
+
if (!existsSync(sourcePath)) throw new Error(`plate file missing: ${id}`);
|
|
90
|
+
return sourcePath;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function stripFrontmatter(markdown) {
|
|
94
|
+
return String(markdown).replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "").trim();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function loadPlate(id) {
|
|
98
|
+
const sourcePath = plateSourcePath(id);
|
|
99
|
+
const raw = readFileSync(sourcePath, "utf8");
|
|
100
|
+
return {
|
|
101
|
+
id,
|
|
102
|
+
body: stripFrontmatter(raw),
|
|
103
|
+
sourcePath,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function scorePlate(id, text) {
|
|
108
|
+
const cues = CUES[id] || [];
|
|
109
|
+
let score = 0;
|
|
110
|
+
for (const [pattern, weight] of cues) {
|
|
111
|
+
if (pattern.test(text)) score += weight;
|
|
112
|
+
}
|
|
113
|
+
return score;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function recallPlate(intent) {
|
|
117
|
+
const text = String(intent || "").trim();
|
|
118
|
+
if (!text || !platesDir()) return null;
|
|
119
|
+
let bestId = null;
|
|
120
|
+
let best = 0;
|
|
121
|
+
let second = 0;
|
|
122
|
+
for (const id of PLATE_IDS) {
|
|
123
|
+
const score = scorePlate(id, text);
|
|
124
|
+
if (score > best) {
|
|
125
|
+
second = best;
|
|
126
|
+
best = score;
|
|
127
|
+
bestId = id;
|
|
128
|
+
} else if (score > second) {
|
|
129
|
+
second = score;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!bestId || best === 0 || best === second) return null;
|
|
133
|
+
return loadPlate(bestId);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function wornPlatePath(projectRoot, id) {
|
|
137
|
+
assertPlateId(id);
|
|
138
|
+
return join(projectRoot, ".xray", "state", "plates", `${id}.md`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function stampPlateIfMissing(projectRoot, id) {
|
|
142
|
+
const path = wornPlatePath(projectRoot, id);
|
|
143
|
+
if (existsSync(path)) {
|
|
144
|
+
return { id, path, written: false };
|
|
145
|
+
}
|
|
146
|
+
const sourcePath = plateSourcePath(id);
|
|
147
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
148
|
+
writeFileSync(path, readFileSync(sourcePath, "utf8"));
|
|
149
|
+
return { id, path, written: true };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function plateStockLine(intent) {
|
|
153
|
+
const plate = recallPlate(intent);
|
|
154
|
+
if (!plate) return null;
|
|
155
|
+
return `Plate: ${plate.id} — .xray/state/plates/${plate.id}.md`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
PLATE_IDS,
|
|
160
|
+
loadPlate,
|
|
161
|
+
recallPlate,
|
|
162
|
+
stampPlateIfMissing,
|
|
163
|
+
plateStockLine,
|
|
164
|
+
wornPlatePath,
|
|
165
|
+
};
|
|
@@ -20,6 +20,7 @@ const {
|
|
|
20
20
|
const { join } = require("path");
|
|
21
21
|
|
|
22
22
|
const HOOKS_DIR = __dirname;
|
|
23
|
+
const { plateStockLine } = require("./plates.cjs");
|
|
23
24
|
|
|
24
25
|
const INTENT_MAX = 240;
|
|
25
26
|
|
|
@@ -673,7 +674,11 @@ function heatLiveMemory(root) {
|
|
|
673
674
|
}
|
|
674
675
|
}
|
|
675
676
|
|
|
676
|
-
/**
|
|
677
|
+
/**
|
|
678
|
+
* Station note on HEAD move. Dedup per HEAD.
|
|
679
|
+
* Commit subjects are match text for the card, not a graded lesson.
|
|
680
|
+
* Do not mint session-<date>-<short HEAD> under docs/inference.
|
|
681
|
+
*/
|
|
677
682
|
function maybeCaptureSessionOnHeadMove(root) {
|
|
678
683
|
const cfg = readSessionCaptureConfig(root);
|
|
679
684
|
if (!cfg.enabled) return null;
|
|
@@ -690,34 +695,28 @@ function maybeCaptureSessionOnHeadMove(root) {
|
|
|
690
695
|
? gitCommitSubjects(root, [`${stamp.head}..HEAD`])
|
|
691
696
|
: gitCommitSubjects(root, ["-n", String(cfg.lookback)]);
|
|
692
697
|
if (commits.length < cfg.minCommits) return null;
|
|
693
|
-
const sessionId = `session-${new Date().toISOString().slice(0, 10)}-${git.head}`;
|
|
694
698
|
const approaches = commits.map((row) => row.message).filter(Boolean);
|
|
695
699
|
const span = {
|
|
696
700
|
from: stamp && stamp.head ? stamp.head : commits[commits.length - 1] ? commits[commits.length - 1].hash : git.head,
|
|
697
701
|
to: git.head,
|
|
698
702
|
};
|
|
699
703
|
const patterns = patternsFromGit(root, commits, span);
|
|
700
|
-
const
|
|
701
|
-
sessionId,
|
|
704
|
+
const stationNote = {
|
|
702
705
|
timestamp: new Date().toISOString(),
|
|
703
706
|
span,
|
|
704
|
-
problems: [],
|
|
705
707
|
approaches,
|
|
706
|
-
wrongTurns: [],
|
|
707
|
-
solutions: [],
|
|
708
708
|
patterns,
|
|
709
709
|
matched_primitives: preferLawHits(patterns.map((row) => row && row.name)),
|
|
710
710
|
metrics: { commits: commits.length },
|
|
711
711
|
};
|
|
712
712
|
const outDir = join(root, "docs", "inference");
|
|
713
713
|
mkdirSync(outDir, { recursive: true });
|
|
714
|
-
const filePath = join(outDir,
|
|
715
|
-
writeFileSync(filePath, `${JSON.stringify(
|
|
716
|
-
writeFileSync(join(outDir, "latest-session.json"), `${JSON.stringify(session, null, 2)}\n`);
|
|
714
|
+
const filePath = join(outDir, "latest-session.json");
|
|
715
|
+
writeFileSync(filePath, `${JSON.stringify(stationNote, null, 2)}\n`);
|
|
717
716
|
mkdirSync(join(root, ".xray", "state"), { recursive: true });
|
|
718
717
|
writeFileSync(
|
|
719
718
|
sessionCaptureStampPath(root),
|
|
720
|
-
`${JSON.stringify({ head: git.head,
|
|
719
|
+
`${JSON.stringify({ head: git.head, path: filePath, updatedAt: stationNote.timestamp }, null, 2)}\n`,
|
|
721
720
|
);
|
|
722
721
|
return filePath;
|
|
723
722
|
}
|
|
@@ -1045,6 +1044,20 @@ const STOCK_STATION_PREFIXES = [
|
|
|
1045
1044
|
"git:",
|
|
1046
1045
|
"repertoire:",
|
|
1047
1046
|
"working:",
|
|
1047
|
+
"plate:",
|
|
1048
|
+
];
|
|
1049
|
+
|
|
1050
|
+
/** Stock design map. Exact lines so a later heat does not preserve a second copy. */
|
|
1051
|
+
const DESIGN_MAP_LINES = [
|
|
1052
|
+
"Subsystem map:",
|
|
1053
|
+
"+----------------------+------------------------------------------+",
|
|
1054
|
+
"| Subsystem | After compact |",
|
|
1055
|
+
"+----------------------+------------------------------------------+",
|
|
1056
|
+
"| Inference | proposals, reflection, Repertoire |",
|
|
1057
|
+
"| External Governance | Dynamo vote, Codex |",
|
|
1058
|
+
"| Autonomous Engine | thinDispatch, AsideContext |",
|
|
1059
|
+
"| Lessons | hot lines on the signal; this card |",
|
|
1060
|
+
"+----------------------+------------------------------------------+",
|
|
1048
1061
|
];
|
|
1049
1062
|
|
|
1050
1063
|
const STOCK_STATION_FOOTERS = [
|
|
@@ -1099,6 +1112,7 @@ function isStockStationLine(line) {
|
|
|
1099
1112
|
const trimmed = String(line || "").trim();
|
|
1100
1113
|
if (!trimmed) return true;
|
|
1101
1114
|
if (/^#\s+station\s*$/i.test(trimmed)) return true;
|
|
1115
|
+
if (DESIGN_MAP_LINES.includes(trimmed)) return true;
|
|
1102
1116
|
const lower = trimmed.toLowerCase();
|
|
1103
1117
|
if (STOCK_STATION_FOOTERS.includes(lower)) return true;
|
|
1104
1118
|
return STOCK_STATION_PREFIXES.some((prefix) => lower.startsWith(prefix));
|
|
@@ -1171,6 +1185,10 @@ function formatStationMarkdown(fields) {
|
|
|
1171
1185
|
if (fields.workingLine) {
|
|
1172
1186
|
lines.push(fields.workingLine);
|
|
1173
1187
|
}
|
|
1188
|
+
const plateLine = plateStockLine(fields.intent);
|
|
1189
|
+
if (plateLine) lines.push(plateLine);
|
|
1190
|
+
lines.push("");
|
|
1191
|
+
lines.push(...DESIGN_MAP_LINES);
|
|
1174
1192
|
lines.push("");
|
|
1175
1193
|
lines.push("Continue this card. Compaction and host change are the same cut. Do not cold-start.");
|
|
1176
1194
|
lines.push("Grok does not inject this file — Read it. OpenCode injects. Do not thicken the Grok exo.");
|
|
@@ -3,10 +3,24 @@
|
|
|
3
3
|
* Station organ write — compact ingest + wake grow.
|
|
4
4
|
* Grow uses the worn RepertoireService. Groover field stays off.
|
|
5
5
|
*/
|
|
6
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
-
import { join } from 'node:path';
|
|
6
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { join, resolve } from 'node:path';
|
|
8
8
|
import { pathToFileURL } from 'node:url';
|
|
9
9
|
|
|
10
|
+
/** Tail cap for diary files the kernel list does not open. Heat only. */
|
|
11
|
+
const UNREAD_DIARY_CAP = 80_000;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Suit logs with a writer and no brain reader.
|
|
15
|
+
* Graded sessions and the five kernel-diary files stay on their existing readers.
|
|
16
|
+
* latest-session.json stays a station note, not a corpus session.
|
|
17
|
+
*/
|
|
18
|
+
const UNREAD_DIARY_FILES = [
|
|
19
|
+
['logs', 'framework', 'activity-report.json'],
|
|
20
|
+
['logs', 'framework', 'plugin-tool-events.log'],
|
|
21
|
+
['.xray', 'inference', 'postprocessor-light-latest.json'],
|
|
22
|
+
];
|
|
23
|
+
|
|
10
24
|
const root = process.argv[2];
|
|
11
25
|
const mode = process.argv[3] || 'station';
|
|
12
26
|
|
|
@@ -95,6 +109,50 @@ function readCleanupDiary(projectRoot) {
|
|
|
95
109
|
return { text: parts.join('\n'), sources };
|
|
96
110
|
}
|
|
97
111
|
|
|
112
|
+
function pushMatching(dir, pattern, into) {
|
|
113
|
+
if (!existsSync(dir)) return;
|
|
114
|
+
let names = [];
|
|
115
|
+
try {
|
|
116
|
+
names = readdirSync(dir);
|
|
117
|
+
} catch {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const matched = names.filter((name) => pattern.test(name)).sort().reverse();
|
|
121
|
+
for (const name of matched) into.push(join(dir, name));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Diary, activity, and workflow files the kernel candidate list does not open.
|
|
126
|
+
* Text is heat for last_seen. It is not a lesson and not a graded session.
|
|
127
|
+
*/
|
|
128
|
+
export function collectUnreadSuitDiary(projectRoot) {
|
|
129
|
+
const files = UNREAD_DIARY_FILES.map((parts) => join(projectRoot, ...parts));
|
|
130
|
+
pushMatching(join(projectRoot, '.xray', 'inference'), /^workflow-\d+\.json$/, files);
|
|
131
|
+
pushMatching(join(projectRoot, 'logs', 'monitoring'), /^memory-monitor-.+\.log$/, files);
|
|
132
|
+
pushMatching(join(projectRoot, '.opencode', 'logs'), /^xray-plugin-.+\.log$/, files);
|
|
133
|
+
const sources = [];
|
|
134
|
+
const chunks = [];
|
|
135
|
+
let used = 0;
|
|
136
|
+
for (const file of files) {
|
|
137
|
+
const resolved = resolve(file);
|
|
138
|
+
if (!existsSync(resolved) || sources.includes(resolved)) continue;
|
|
139
|
+
let raw = '';
|
|
140
|
+
try {
|
|
141
|
+
raw = readFileSync(resolved, 'utf8');
|
|
142
|
+
} catch {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (!raw.trim()) continue;
|
|
146
|
+
const remain = UNREAD_DIARY_CAP - used;
|
|
147
|
+
if (remain <= 0) break;
|
|
148
|
+
const slice = raw.length > remain ? raw.slice(-remain) : raw;
|
|
149
|
+
chunks.push(slice);
|
|
150
|
+
sources.push(resolved);
|
|
151
|
+
used += slice.length;
|
|
152
|
+
}
|
|
153
|
+
return { text: chunks.join('\n'), sources };
|
|
154
|
+
}
|
|
155
|
+
|
|
98
156
|
async function growDest(projectRoot) {
|
|
99
157
|
const routing = readRouting(projectRoot);
|
|
100
158
|
if (isExplicitOptOut(routing)) {
|
|
@@ -123,9 +181,10 @@ async function growDest(projectRoot) {
|
|
|
123
181
|
? pathsMod.collectKernelDiaryText(projectRoot)
|
|
124
182
|
: { text: '', sources: [] };
|
|
125
183
|
const cleanup = readCleanupDiary(projectRoot);
|
|
184
|
+
const unread = collectUnreadSuitDiary(projectRoot);
|
|
126
185
|
const diary = service.heatKernelDiary({
|
|
127
|
-
text: [kernel.text, cleanup.text].filter(Boolean).join('\n'),
|
|
128
|
-
sources: [...(kernel.sources || []), ...cleanup.sources],
|
|
186
|
+
text: [kernel.text, cleanup.text, unread.text].filter(Boolean).join('\n'),
|
|
187
|
+
sources: [...(kernel.sources || []), ...cleanup.sources, ...unread.sources],
|
|
129
188
|
});
|
|
130
189
|
const after = service.signalsManager.load().signals.length;
|
|
131
190
|
const heatedNames = Array.isArray(diary.heated) ? diary.heated : [];
|
|
@@ -147,49 +206,57 @@ async function growDest(projectRoot) {
|
|
|
147
206
|
process.stdout.write(`${JSON.stringify({ skipped: 'no-organ' })}\n`);
|
|
148
207
|
}
|
|
149
208
|
|
|
150
|
-
|
|
151
|
-
process.
|
|
209
|
+
function invokedAsScript() {
|
|
210
|
+
const entry = process.argv[1];
|
|
211
|
+
if (!entry) return false;
|
|
212
|
+
return import.meta.url === pathToFileURL(entry).href;
|
|
152
213
|
}
|
|
153
214
|
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
215
|
+
if (invokedAsScript()) {
|
|
216
|
+
if (!root) {
|
|
217
|
+
process.exit(0);
|
|
218
|
+
}
|
|
158
219
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
signals = JSON.parse(process.argv[5] || '[]');
|
|
164
|
-
} catch {
|
|
165
|
-
signals = [];
|
|
166
|
-
}
|
|
167
|
-
if (!Array.isArray(signals) || signals.length === 0) {
|
|
168
|
-
process.exit(0);
|
|
169
|
-
}
|
|
220
|
+
if (mode === '--grow') {
|
|
221
|
+
await growDest(root);
|
|
222
|
+
process.exit(0);
|
|
223
|
+
}
|
|
170
224
|
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
225
|
+
const sessionId = mode;
|
|
226
|
+
const hookEvent = process.argv[4] || 'post_compact';
|
|
227
|
+
let signals = [];
|
|
174
228
|
try {
|
|
175
|
-
|
|
229
|
+
signals = JSON.parse(process.argv[5] || '[]');
|
|
176
230
|
} catch {
|
|
177
|
-
|
|
231
|
+
signals = [];
|
|
232
|
+
}
|
|
233
|
+
if (!Array.isArray(signals) || signals.length === 0) {
|
|
234
|
+
process.exit(0);
|
|
178
235
|
}
|
|
179
|
-
}
|
|
180
236
|
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
237
|
+
let routing = { enabled: false, provider: 'null' };
|
|
238
|
+
const featuresPath = join(root, '.xray', 'features.json');
|
|
239
|
+
if (existsSync(featuresPath)) {
|
|
240
|
+
try {
|
|
241
|
+
routing = JSON.parse(readFileSync(featuresPath, 'utf8')).memory_routing || routing;
|
|
242
|
+
} catch {
|
|
243
|
+
/* leftover */
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const { loadMemoryRoutingProvider } = await import('../../memory-routing/index.js');
|
|
248
|
+
const provider = await loadMemoryRoutingProvider(routing, root);
|
|
249
|
+
if (!provider || typeof provider.ingestFeedback !== 'function') process.exit(0);
|
|
250
|
+
|
|
251
|
+
provider.ingestFeedback({
|
|
252
|
+
timestamp: new Date().toISOString(),
|
|
253
|
+
sessionId,
|
|
254
|
+
taskId: `station-${hookEvent}`,
|
|
255
|
+
assignedAgent: 'station',
|
|
256
|
+
memorySignals: signals,
|
|
257
|
+
complexity: 0,
|
|
258
|
+
success: true,
|
|
259
|
+
durationMs: 0,
|
|
260
|
+
});
|
|
261
|
+
process.exit(0);
|
|
262
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type MemoryRoutingContext, type MemorySignalLessons, type MemoryTaskConfidence } from '../memory-routing/types.js';
|
|
2
2
|
export interface ResearcherMemoryContext {
|
|
3
3
|
providerId: string;
|
|
4
4
|
confidence: MemoryTaskConfidence;
|
|
5
5
|
matchedSignals: string[];
|
|
6
6
|
recommendedAgent: string | null;
|
|
7
7
|
triggeredBy: 'trap-language' | 'high-confidence-primitives';
|
|
8
|
+
/** Graded lines for signals this task already matched. */
|
|
9
|
+
lessons?: MemorySignalLessons[];
|
|
10
|
+
/** Proposal text used to recall at most one pipeline plate. */
|
|
11
|
+
proposalText?: string;
|
|
8
12
|
}
|
|
9
13
|
export declare function shouldQueryRepertoireConfidence(description: string, proposalType?: string): boolean;
|
|
10
14
|
export declare function hasHighConfidencePrimitiveMatch(routingContext: MemoryRoutingContext, minConfidence?: number): boolean;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { frameworkLogger } from '../core/framework-logger.js';
|
|
2
2
|
import { getMemoryRoutingProvider } from '../memory-routing/index.js';
|
|
3
|
+
import { recallPlate } from '../memory-routing/plates.js';
|
|
4
|
+
import { LESSON_TEXT_CAP, } from '../memory-routing/types.js';
|
|
3
5
|
const TRAP_LANGUAGE = /TYPE:\s*ontological-trap|ontological[- ]trap/i;
|
|
4
6
|
const DEFAULT_MIN_CONFIDENCE_GATE = 0.55;
|
|
5
7
|
export function shouldQueryRepertoireConfidence(description, proposalType = '') {
|
|
@@ -40,12 +42,15 @@ export async function resolveResearcherMemoryContext(input) {
|
|
|
40
42
|
const matchedSignals = confidence.matchedSignals.length > 0
|
|
41
43
|
? confidence.matchedSignals
|
|
42
44
|
: routingContext.matchedSignals;
|
|
45
|
+
const lessons = lessonsForMatchedSignals(routingContext.lessons, matchedSignals);
|
|
43
46
|
const context = {
|
|
44
47
|
providerId: provider.id,
|
|
45
48
|
confidence,
|
|
46
49
|
matchedSignals,
|
|
47
50
|
recommendedAgent: confidence.recommendedAgent,
|
|
48
51
|
triggeredBy: trapLanguageDetected ? 'trap-language' : 'high-confidence-primitives',
|
|
52
|
+
proposalText: taskText,
|
|
53
|
+
...(lessons.length > 0 ? { lessons } : {}),
|
|
49
54
|
};
|
|
50
55
|
if (confidence.highConfidenceTrapPresent) {
|
|
51
56
|
frameworkLogger.log('mcps/researcher', 'repertoire-trap-detected', 'info', {
|
|
@@ -73,6 +78,10 @@ export function buildMemoryRoutingEvidence(context) {
|
|
|
73
78
|
if (recommendedAgent) {
|
|
74
79
|
lines.push(`Recommended agent for trap handling: ${recommendedAgent}`);
|
|
75
80
|
}
|
|
81
|
+
for (const line of lessonSpeechLines(context.lessons, matchedSignals)) {
|
|
82
|
+
lines.push(`Lesson: ${line}`);
|
|
83
|
+
}
|
|
84
|
+
appendRecalledPlate(lines, context.proposalText);
|
|
76
85
|
return lines;
|
|
77
86
|
}
|
|
78
87
|
export function formatMemoryRoutingBlock(context) {
|
|
@@ -88,5 +97,33 @@ export function formatMemoryRoutingBlock(context) {
|
|
|
88
97
|
if (recommendedAgent) {
|
|
89
98
|
lines.push(` recommendedAgent: ${recommendedAgent}`);
|
|
90
99
|
}
|
|
100
|
+
for (const line of lessonSpeechLines(context.lessons, matchedSignals)) {
|
|
101
|
+
lines.push(` lesson: ${line}`);
|
|
102
|
+
}
|
|
103
|
+
appendRecalledPlate(lines, context.proposalText);
|
|
91
104
|
return lines.join('\n');
|
|
92
105
|
}
|
|
106
|
+
function appendRecalledPlate(lines, proposalText) {
|
|
107
|
+
const plate = recallPlate(proposalText);
|
|
108
|
+
if (!plate)
|
|
109
|
+
return;
|
|
110
|
+
lines.push(`Plate: ${plate.id}`);
|
|
111
|
+
lines.push(plate.body);
|
|
112
|
+
}
|
|
113
|
+
function lessonsForMatchedSignals(lessons, matchedSignals) {
|
|
114
|
+
const matched = new Set(matchedSignals);
|
|
115
|
+
return (lessons ?? []).filter((lesson) => matched.has(lesson.name));
|
|
116
|
+
}
|
|
117
|
+
/** Stored lesson text for signals this task matched. Empty text is omitted. */
|
|
118
|
+
function lessonSpeechLines(lessons, matchedSignals) {
|
|
119
|
+
const speech = [];
|
|
120
|
+
for (const lesson of lessonsForMatchedSignals(lessons, matchedSignals)) {
|
|
121
|
+
for (const line of lesson.lines) {
|
|
122
|
+
const text = line.text.trim().slice(0, LESSON_TEXT_CAP);
|
|
123
|
+
if (text.length === 0)
|
|
124
|
+
continue;
|
|
125
|
+
speech.push(`${lesson.name}: ${text}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return speech;
|
|
129
|
+
}
|
|
@@ -388,7 +388,7 @@ class XrayLibrarianServer extends XrayKnowledgeSkillBase {
|
|
|
388
388
|
proposalType,
|
|
389
389
|
});
|
|
390
390
|
const enrichedEvidence = [...(evidence || [])];
|
|
391
|
-
if (memoryContext
|
|
391
|
+
if (memoryContext) {
|
|
392
392
|
enrichedEvidence.push(...buildMemoryRoutingEvidence(memoryContext));
|
|
393
393
|
}
|
|
394
394
|
const vote = (await tryLLMGovernance("researcher", proposalTitle || "", proposalDescription || "", enrichedEvidence, proposalType || "")) ??
|
|
@@ -398,7 +398,7 @@ class XrayLibrarianServer extends XrayKnowledgeSkillBase {
|
|
|
398
398
|
highConfidenceTrapPresent: memoryContext?.confidence.highConfidenceTrapPresent,
|
|
399
399
|
recommendedAgent: memoryContext?.recommendedAgent,
|
|
400
400
|
});
|
|
401
|
-
const memoryRoutingBlock = memoryContext
|
|
401
|
+
const memoryRoutingBlock = memoryContext
|
|
402
402
|
? `\n${formatMemoryRoutingBlock(memoryContext)}`
|
|
403
403
|
: "";
|
|
404
404
|
return {
|