0xray 4.0.23 → 4.0.24
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 +20 -0
- package/dist/README.md +2 -2
- package/dist/SKILLS.md +1 -1
- 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/llms.txt +1 -1
- package/package.json +1 -1
- package/scripts/foundry/plant/skills/mill/SKILL.md +1 -1
- package/scripts/node/consumer-install-smoke.mjs +3 -3
- 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
|
@@ -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 {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const PLATE_IDS: readonly ["routing", "governance", "boot", "orchestration", "processor", "reporting", "memory-recall"];
|
|
2
|
+
export type PlateId = (typeof PLATE_IDS)[number];
|
|
3
|
+
export interface PlateStamp {
|
|
4
|
+
id: PlateId;
|
|
5
|
+
body: string;
|
|
6
|
+
sourcePath: string;
|
|
7
|
+
}
|
|
8
|
+
export interface PlateStampResult {
|
|
9
|
+
id: PlateId;
|
|
10
|
+
path: string;
|
|
11
|
+
written: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function loadPlate(id: PlateId): PlateStamp;
|
|
14
|
+
export declare function recallPlate(intent: string | null | undefined): PlateStamp | null;
|
|
15
|
+
export declare function stampPlateIfMissing(projectRoot: string, id: PlateId): PlateStampResult;
|
|
16
|
+
export declare function plateStockLine(intent: string | null | undefined): string | null;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed plate API. The runtime reader is plates.cjs so Station and the
|
|
3
|
+
* Cursor preCompact hook share one implementation.
|
|
4
|
+
*/
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
export const PLATE_IDS = [
|
|
9
|
+
'routing',
|
|
10
|
+
'governance',
|
|
11
|
+
'boot',
|
|
12
|
+
'orchestration',
|
|
13
|
+
'processor',
|
|
14
|
+
'reporting',
|
|
15
|
+
'memory-recall',
|
|
16
|
+
];
|
|
17
|
+
function isPlateId(value) {
|
|
18
|
+
return PLATE_IDS.includes(value);
|
|
19
|
+
}
|
|
20
|
+
function isRuntime(value) {
|
|
21
|
+
if (!value || typeof value !== 'object')
|
|
22
|
+
return false;
|
|
23
|
+
const row = value;
|
|
24
|
+
return (typeof row.loadPlate === 'function'
|
|
25
|
+
&& typeof row.recallPlate === 'function'
|
|
26
|
+
&& typeof row.stampPlateIfMissing === 'function'
|
|
27
|
+
&& typeof row.plateStockLine === 'function'
|
|
28
|
+
&& Array.isArray(row.PLATE_IDS));
|
|
29
|
+
}
|
|
30
|
+
function loadRuntime() {
|
|
31
|
+
const require = createRequire(import.meta.url);
|
|
32
|
+
const runtimePath = join(dirname(fileURLToPath(import.meta.url)), '../integrations/hooks/plates.cjs');
|
|
33
|
+
const loaded = require(runtimePath);
|
|
34
|
+
if (!isRuntime(loaded)) {
|
|
35
|
+
throw new Error('plate runtime missing');
|
|
36
|
+
}
|
|
37
|
+
return loaded;
|
|
38
|
+
}
|
|
39
|
+
const runtime = loadRuntime();
|
|
40
|
+
function asStamp(value) {
|
|
41
|
+
if (!value)
|
|
42
|
+
return null;
|
|
43
|
+
if (!isPlateId(value.id) || typeof value.body !== 'string') {
|
|
44
|
+
throw new Error('plate runtime returned an unknown stamp');
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
export function loadPlate(id) {
|
|
49
|
+
const stamp = asStamp(runtime.loadPlate(id));
|
|
50
|
+
if (!stamp)
|
|
51
|
+
throw new Error(`plate missing: ${id}`);
|
|
52
|
+
return stamp;
|
|
53
|
+
}
|
|
54
|
+
export function recallPlate(intent) {
|
|
55
|
+
return asStamp(runtime.recallPlate(intent));
|
|
56
|
+
}
|
|
57
|
+
export function stampPlateIfMissing(projectRoot, id) {
|
|
58
|
+
const result = runtime.stampPlateIfMissing(projectRoot, id);
|
|
59
|
+
if (!isPlateId(result.id) || typeof result.path !== 'string' || typeof result.written !== 'boolean') {
|
|
60
|
+
throw new Error('plate runtime returned an unknown stamp result');
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
export function plateStockLine(intent) {
|
|
65
|
+
const line = runtime.plateStockLine(intent);
|
|
66
|
+
if (line === null || line === undefined)
|
|
67
|
+
return null;
|
|
68
|
+
if (typeof line !== 'string')
|
|
69
|
+
throw new Error('plate stock line was not text');
|
|
70
|
+
return line;
|
|
71
|
+
}
|
|
@@ -33,6 +33,8 @@ export interface MemoryTaskConfidence {
|
|
|
33
33
|
complexityBoost: number;
|
|
34
34
|
recommendedAgent: string | null;
|
|
35
35
|
}
|
|
36
|
+
/** Characters kept on one graded lesson line. lessonTrace stores with this cap. */
|
|
37
|
+
export declare const LESSON_TEXT_CAP = 400;
|
|
36
38
|
export interface MemoryLessonLine {
|
|
37
39
|
taskId: string;
|
|
38
40
|
decision: 'success' | 'failure';
|
|
@@ -4,4 +4,5 @@
|
|
|
4
4
|
* Repertoire is one implementation. Any provider exposing createMemoryRoutingProvider()
|
|
5
5
|
* can be loaded via features.json memory_routing.module_path.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
/** Characters kept on one graded lesson line. lessonTrace stores with this cap. */
|
|
8
|
+
export const LESSON_TEXT_CAP = 400;
|
|
@@ -33,4 +33,9 @@ export declare class InferenceImprovementProcessor extends PostProcessor {
|
|
|
33
33
|
private generateCodeReviewerPrompt;
|
|
34
34
|
private generateEnforcerPrompt;
|
|
35
35
|
private triggerAgentWorkflow;
|
|
36
|
+
/**
|
|
37
|
+
* The operation is finished once its prompt result text is on disk.
|
|
38
|
+
* One session per operation id. Empty speech writes nothing.
|
|
39
|
+
*/
|
|
40
|
+
private captureFinishedOperationSpeech;
|
|
36
41
|
}
|