0xray 4.0.17 → 4.0.19
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 +1 -1
- package/dist/CHANGELOG.md +28 -0
- package/dist/integrations/cursor/hooks/cursor-hook-utils.js +3 -100
- package/dist/integrations/hooks/station-hook-runtime.cjs +270 -16
- package/dist/integrations/hooks/station-hook-runtime.mjs +3 -0
- package/dist/integrations/hooks/station-memory-match.mjs +49 -3
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/package.json +1 -1
- package/scripts/node/consumer-install-smoke.mjs +3 -3
- package/scripts/node/wear-vendored-repertoire.cjs +39 -1
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/vendor/@0xray/repertoire/AGENTS.md +8 -2
- package/vendor/@0xray/repertoire/README.md +35 -8
- package/vendor/@0xray/repertoire/data/stack-overlay.json +750 -0
- package/vendor/@0xray/repertoire/data/subject-overlay.json +297 -0
- package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts +36 -1
- package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/RepertoireService.js +108 -18
- package/vendor/@0xray/repertoire/dist/RepertoireService.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/index.d.ts +3 -1
- package/vendor/@0xray/repertoire/dist/index.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/index.js +3 -1
- package/vendor/@0xray/repertoire/dist/index.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.d.ts +4 -0
- package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.js +15 -1
- package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.d.ts +22 -8
- package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js +56 -5
- package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/mcp/server.js +8 -13
- package/vendor/@0xray/repertoire/dist/mcp/server.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js +24 -7
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js +2 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/paths.d.ts +65 -2
- package/vendor/@0xray/repertoire/dist/paths.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/paths.js +377 -6
- package/vendor/@0xray/repertoire/dist/paths.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js +6 -8
- package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts +20 -2
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js +121 -8
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.d.ts +2 -1
- package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.js +21 -3
- package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/registry/confidence-decay.d.ts +40 -0
- package/vendor/@0xray/repertoire/dist/registry/confidence-decay.d.ts.map +1 -0
- package/vendor/@0xray/repertoire/dist/registry/confidence-decay.js +89 -0
- package/vendor/@0xray/repertoire/dist/registry/confidence-decay.js.map +1 -0
- package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.js +1 -0
- package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/types.d.ts +5 -0
- package/vendor/@0xray/repertoire/dist/types.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/package.json +4 -2
- package/vendor/@0xray/repertoire/scripts/verify-synthesis-dogfood.mjs +2 -2
- package/xray/features.json +1 -1
package/.grok-plugin/plugin.json
CHANGED
package/.xray/features.json
CHANGED
package/dist/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,7 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
9
|
### ✨ Features
|
|
10
|
+
- **Vendored Repertoire 0.2.5** — `vendor/@0xray/repertoire` is the published 0.2.5 package (overlays included). Wear replaces an older leftover in `node_modules`. A newer user-installed copy is left alone. Boot reads worn `node_modules`, then vendor, then a sibling checkout last. Repertoire stays optional (`enabled: false` + `provider: repertoire`); Station is the memory when it is off.
|
|
10
11
|
- **Cursor fifth wear** — `install-bridges.cjs` fastens project `.cursor/hooks.json` (preToolUse / preCompact / afterFileEdit) so Cursor Cloud heats Station and gates tools. Leaves an existing file alone. Not a fifth chat TUI. No new MCP/skill surface. Pack gate (`REQUIRED_PACK_PATHS`) now requires `dist/integrations/cursor/hooks/{hooks.json,pre-tool-use.js,pre-compact.js,after-file-edit.js,cursor-hook-utils.js,cursor-usage-receipt.js}` so a published tarball cannot ship a dead consumer command. `preToolUse` / `afterFileEdit` rewrite the card when git HEAD or Repertoire count diverges — Ticket / Durable / Seed stay. OP-PROC stays in the hook, not on Station.
|
|
11
12
|
|
|
12
13
|
### 📚 Documentation
|
|
@@ -34,6 +35,33 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
|
|
|
34
35
|
|
|
35
36
|
---
|
|
36
37
|
|
|
38
|
+
## [4.0.19] - 2026-09-22
|
|
39
|
+
|
|
40
|
+
### ✨ Features
|
|
41
|
+
- **Vendored Repertoire 0.2.5** — published 0.2.5 lives in `vendor/@0xray/repertoire`. Wear replaces older leftovers. Optional; Station is the memory when it is off.
|
|
42
|
+
|
|
43
|
+
### 🔎 Other Changes
|
|
44
|
+
- Load worn Repertoire first; sibling checkout last. (f8a90b531)
|
|
45
|
+
- Put published Repertoire 0.2.5 inside 0xray vendor and wear it. (a90b0a7a4)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## [4.0.18] - 2026-09-22
|
|
50
|
+
|
|
51
|
+
### 🔄 Changes
|
|
52
|
+
|
|
53
|
+
### ✨ Features
|
|
54
|
+
- feat: wake heat joins bookmark, index, and mind (ad596aae2)
|
|
55
|
+
|
|
56
|
+
### 🐛 Bug Fixes
|
|
57
|
+
- fix: pin subject hits on wake rematch (65de9d37a)
|
|
58
|
+
- fix: wake match loads 0.2.5 and keeps subject hits (80b61236d)
|
|
59
|
+
|
|
60
|
+
### 📚 Documentation
|
|
61
|
+
- docs: memory-wake join — BRAIN-003 (1c128e69c)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
37
65
|
## [4.0.17] - 2026-09-22
|
|
38
66
|
|
|
39
67
|
### 🔄 Changes
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* sessionStart is unavailable on managed cloud; first preToolUse / afterFileEdit boots.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { execFileSync } from 'node:child_process';
|
|
8
7
|
import fs from 'fs';
|
|
9
8
|
import path from 'path';
|
|
10
9
|
import {
|
|
@@ -25,10 +24,13 @@ import {
|
|
|
25
24
|
import { appendHookActivity } from '../../grok/hooks/grok-hook-activity.js';
|
|
26
25
|
import {
|
|
27
26
|
buildRepertoireResume,
|
|
27
|
+
maybeCaptureSessionOnHeadMove,
|
|
28
28
|
readGitBrief,
|
|
29
29
|
stationDurableHoldsNpm,
|
|
30
30
|
} from '../../hooks/station-hook-runtime.mjs';
|
|
31
31
|
|
|
32
|
+
export { maybeCaptureSessionOnHeadMove };
|
|
33
|
+
|
|
32
34
|
export const CURSOR_HOST = 'cursor';
|
|
33
35
|
export const EVENT_CLASS_HOST = 'cursor-host-precompact';
|
|
34
36
|
export const EVENT_CLASS_SYNTHETIC = 'cursor-precompact-synthetic';
|
|
@@ -153,105 +155,6 @@ export function cursorBootNeedsRefresh(existing, root) {
|
|
|
153
155
|
return false;
|
|
154
156
|
}
|
|
155
157
|
|
|
156
|
-
function sessionCaptureStampPath(root) {
|
|
157
|
-
return path.join(root, '.xray', 'state', 'session-capture-stamp.json');
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function readSessionCaptureConfig(root) {
|
|
161
|
-
let cfg = {};
|
|
162
|
-
try {
|
|
163
|
-
const raw = JSON.parse(fs.readFileSync(path.join(root, '.xray', 'features.json'), 'utf8'));
|
|
164
|
-
cfg = raw.inference_session_capture || {};
|
|
165
|
-
} catch {
|
|
166
|
-
cfg = {};
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
enabled: cfg.enabled === true,
|
|
170
|
-
minCommits: typeof cfg.min_commits === 'number' && cfg.min_commits > 0 ? cfg.min_commits : 3,
|
|
171
|
-
lookback: typeof cfg.lookback_commits === 'number' && cfg.lookback_commits > 0 ? cfg.lookback_commits : 20,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function readSessionCaptureStamp(root) {
|
|
176
|
-
try {
|
|
177
|
-
return JSON.parse(fs.readFileSync(sessionCaptureStampPath(root), 'utf8'));
|
|
178
|
-
} catch {
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function gitCommitSubjects(root, args) {
|
|
184
|
-
try {
|
|
185
|
-
const out = execFileSync('git', ['log', '--format=%h||%s', '--no-merges', ...args], {
|
|
186
|
-
cwd: root,
|
|
187
|
-
encoding: 'utf8',
|
|
188
|
-
timeout: 5000,
|
|
189
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
190
|
-
});
|
|
191
|
-
return String(out)
|
|
192
|
-
.split('\n')
|
|
193
|
-
.map((line) => line.trim())
|
|
194
|
-
.filter(Boolean)
|
|
195
|
-
.map((line) => {
|
|
196
|
-
const idx = line.indexOf('||');
|
|
197
|
-
return {
|
|
198
|
-
hash: idx === -1 ? line : line.slice(0, idx),
|
|
199
|
-
message: idx === -1 ? '' : line.slice(idx + 2),
|
|
200
|
-
};
|
|
201
|
-
});
|
|
202
|
-
} catch {
|
|
203
|
-
return [];
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Cursor never runs the git post-commit hook. When HEAD moves, write the same
|
|
209
|
-
* session-*.json the kernel already knows how to ingest. Dedup per HEAD.
|
|
210
|
-
*/
|
|
211
|
-
export function maybeCaptureSessionOnHeadMove(root) {
|
|
212
|
-
const cfg = readSessionCaptureConfig(root);
|
|
213
|
-
if (!cfg.enabled) return null;
|
|
214
|
-
const git = readGitBrief(root);
|
|
215
|
-
if (!git || !git.head) return null;
|
|
216
|
-
const stamp = readSessionCaptureStamp(root);
|
|
217
|
-
if (stamp && stamp.head === git.head) return null;
|
|
218
|
-
|
|
219
|
-
const commits = stamp && stamp.head
|
|
220
|
-
? gitCommitSubjects(root, [`${stamp.head}..HEAD`])
|
|
221
|
-
: gitCommitSubjects(root, ['-n', String(cfg.lookback)]);
|
|
222
|
-
if (commits.length < cfg.minCommits) return null;
|
|
223
|
-
|
|
224
|
-
const sessionId = `session-${new Date().toISOString().slice(0, 10)}-${git.head}`;
|
|
225
|
-
const approaches = commits.map((c) => c.message).filter(Boolean);
|
|
226
|
-
const session = {
|
|
227
|
-
sessionId,
|
|
228
|
-
timestamp: new Date().toISOString(),
|
|
229
|
-
span: {
|
|
230
|
-
from: stamp && stamp.head ? stamp.head : `HEAD~${cfg.lookback}`,
|
|
231
|
-
to: git.head,
|
|
232
|
-
},
|
|
233
|
-
problems: [],
|
|
234
|
-
approaches,
|
|
235
|
-
wrongTurns: [],
|
|
236
|
-
solutions: [],
|
|
237
|
-
patterns: [],
|
|
238
|
-
matched_primitives: [],
|
|
239
|
-
metrics: { commits: commits.length },
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
const outDir = path.join(root, 'docs', 'inference');
|
|
243
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
244
|
-
const filePath = path.join(outDir, `session-${sessionId.replace(/^session-/, '')}.json`);
|
|
245
|
-
fs.writeFileSync(filePath, `${JSON.stringify(session, null, 2)}\n`);
|
|
246
|
-
fs.writeFileSync(path.join(outDir, 'latest-session.json'), `${JSON.stringify(session, null, 2)}\n`);
|
|
247
|
-
fs.mkdirSync(path.dirname(sessionCaptureStampPath(root)), { recursive: true });
|
|
248
|
-
fs.writeFileSync(
|
|
249
|
-
sessionCaptureStampPath(root),
|
|
250
|
-
`${JSON.stringify({ head: git.head, sessionId, path: filePath, updatedAt: session.timestamp }, null, 2)}\n`,
|
|
251
|
-
);
|
|
252
|
-
return filePath;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
158
|
export function ensureCursorSessionBoot(root, source = '0xray/cursor-pre-tool-use-boot', extra = {}) {
|
|
256
159
|
const bootPath = sessionBootPath(root);
|
|
257
160
|
if (fs.existsSync(bootPath)) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Heat writers merge stock fields; unknown keys and ## Durable / ## Seed survive.
|
|
6
6
|
*/
|
|
7
7
|
const { execFileSync } = require("child_process");
|
|
8
|
-
const { existsSync, mkdirSync, readFileSync, writeFileSync } = require("fs");
|
|
8
|
+
const { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } = require("fs");
|
|
9
9
|
const { join } = require("path");
|
|
10
10
|
|
|
11
11
|
const HOOKS_DIR = __dirname;
|
|
@@ -203,10 +203,231 @@ function stationSafeSignals(names) {
|
|
|
203
203
|
return out;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
/**
|
|
206
|
+
/** Keep subject repo-* in the short list the card and working file hold. */
|
|
207
|
+
function preferSubjectHits(names, cap = 8) {
|
|
208
|
+
if (!Array.isArray(names)) return [];
|
|
209
|
+
const unique = [];
|
|
210
|
+
for (const raw of names) {
|
|
211
|
+
const name = String(raw || "").trim();
|
|
212
|
+
if (!name || name.toLowerCase().startsWith("bedrock-")) continue;
|
|
213
|
+
if (unique.includes(name)) continue;
|
|
214
|
+
unique.push(name);
|
|
215
|
+
}
|
|
216
|
+
const subject = unique.filter((name) => name.startsWith("repo-"));
|
|
217
|
+
const rest = unique.filter((name) => !name.startsWith("repo-"));
|
|
218
|
+
return [...subject, ...rest].slice(0, cap);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function destSignalsPath(root) {
|
|
222
|
+
return join(root, ".xray", "state", "repertoire", "curated_signals.json");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function notesPath(root) {
|
|
226
|
+
return join(root, ".xray", "state", "NOTES.md");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function repertoirePackageRoots(root) {
|
|
230
|
+
const candidates = [
|
|
231
|
+
join(root, "node_modules", "@0xray", "repertoire"),
|
|
232
|
+
join(root, "vendor", "@0xray", "repertoire"),
|
|
233
|
+
join(root, "..", "repertoire"),
|
|
234
|
+
];
|
|
235
|
+
const out = [];
|
|
236
|
+
for (const pkgRoot of candidates) {
|
|
237
|
+
const pkgFile = join(pkgRoot, "package.json");
|
|
238
|
+
if (!existsSync(pkgFile)) continue;
|
|
239
|
+
try {
|
|
240
|
+
const pkg = JSON.parse(readFileSync(pkgFile, "utf8"));
|
|
241
|
+
if (pkg.name === "@0xray/repertoire" || pkg.name === "repertoire") out.push(pkgRoot);
|
|
242
|
+
} catch {
|
|
243
|
+
/* skip */
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function repertoireDataFile(root, fileName) {
|
|
250
|
+
for (const pkgRoot of repertoirePackageRoots(root)) {
|
|
251
|
+
const dest = join(pkgRoot, "data", fileName);
|
|
252
|
+
if (existsSync(dest)) return dest;
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function readSignalRecords(filePath) {
|
|
258
|
+
if (!filePath || !existsSync(filePath)) return [];
|
|
259
|
+
try {
|
|
260
|
+
const data = JSON.parse(readFileSync(filePath, "utf8"));
|
|
261
|
+
const signals = Array.isArray(data.signals) ? data.signals : [];
|
|
262
|
+
return signals.filter((signal) => signal && String(signal.name || "").trim());
|
|
263
|
+
} catch {
|
|
264
|
+
return [];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function signalNameSet(filePath) {
|
|
269
|
+
return new Set(readSignalRecords(filePath).map((signal) => String(signal.name).trim()));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function mergeMissingSignals(destPath, incoming) {
|
|
273
|
+
if (!incoming.length || !existsSync(destPath)) return 0;
|
|
274
|
+
let data;
|
|
275
|
+
try {
|
|
276
|
+
data = JSON.parse(readFileSync(destPath, "utf8"));
|
|
277
|
+
} catch {
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
if (!Array.isArray(data.signals)) return 0;
|
|
281
|
+
const have = new Set(
|
|
282
|
+
data.signals.map((signal) => String((signal && signal.name) || "").trim()).filter(Boolean),
|
|
283
|
+
);
|
|
284
|
+
let added = 0;
|
|
285
|
+
for (const signal of incoming) {
|
|
286
|
+
const name = String(signal.name).trim();
|
|
287
|
+
if (!name || have.has(name)) continue;
|
|
288
|
+
data.signals.push(signal);
|
|
289
|
+
have.add(name);
|
|
290
|
+
added += 1;
|
|
291
|
+
}
|
|
292
|
+
if (added) writeFileSync(destPath, `${JSON.stringify(data, null, 2)}\n`);
|
|
293
|
+
return added;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Hydrate project dest from seed + stack + subject overlays. Never write the tarball. */
|
|
297
|
+
function hydrateDestOnWake(root) {
|
|
298
|
+
const dest = destSignalsPath(root);
|
|
299
|
+
const seed = repertoireDataFile(root, "curated_signals.json");
|
|
300
|
+
if (!existsSync(dest) && seed) {
|
|
301
|
+
mkdirSync(join(root, ".xray", "state", "repertoire"), { recursive: true });
|
|
302
|
+
copyFileSync(seed, dest);
|
|
303
|
+
}
|
|
304
|
+
if (!existsSync(dest)) return { dest: null, added: 0, destCount: 0 };
|
|
305
|
+
const added =
|
|
306
|
+
mergeMissingSignals(dest, readSignalRecords(repertoireDataFile(root, "stack-overlay.json"))) +
|
|
307
|
+
mergeMissingSignals(dest, readSignalRecords(repertoireDataFile(root, "subject-overlay.json")));
|
|
308
|
+
return { dest, added, destCount: countCuratedSignals(dest) || 0 };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function readNotesPickup(root) {
|
|
312
|
+
const dest = notesPath(root);
|
|
313
|
+
if (!existsSync(dest)) return null;
|
|
314
|
+
try {
|
|
315
|
+
const text = readFileSync(dest, "utf8");
|
|
316
|
+
const match = text.match(/\*\*Pickup line:\*\*\s*([^\n]+)/);
|
|
317
|
+
return clipIntent(match && match[1] ? match[1] : null);
|
|
318
|
+
} catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function readLatestSessionApproaches(root) {
|
|
324
|
+
const latest = join(root, "docs", "inference", "latest-session.json");
|
|
325
|
+
if (!existsSync(latest)) return null;
|
|
326
|
+
try {
|
|
327
|
+
const data = JSON.parse(readFileSync(latest, "utf8"));
|
|
328
|
+
const approaches = Array.isArray(data.approaches) ? data.approaches : [];
|
|
329
|
+
return clipIntent(approaches.filter(Boolean).join(" "));
|
|
330
|
+
} catch {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function sessionCaptureStampPath(root) {
|
|
336
|
+
return join(root, ".xray", "state", "session-capture-stamp.json");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function readSessionCaptureConfig(root) {
|
|
340
|
+
let cfg = {};
|
|
341
|
+
try {
|
|
342
|
+
cfg = JSON.parse(readFileSync(join(root, ".xray", "features.json"), "utf8")).inference_session_capture || {};
|
|
343
|
+
} catch {
|
|
344
|
+
cfg = {};
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
enabled: cfg.enabled === true,
|
|
348
|
+
minCommits: typeof cfg.min_commits === "number" && cfg.min_commits > 0 ? cfg.min_commits : 3,
|
|
349
|
+
lookback: typeof cfg.lookback_commits === "number" && cfg.lookback_commits > 0 ? cfg.lookback_commits : 20,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function gitCommitSubjects(root, args) {
|
|
354
|
+
try {
|
|
355
|
+
const out = execFileSync("git", ["log", "--format=%h||%s", "--no-merges", ...args], {
|
|
356
|
+
cwd: root,
|
|
357
|
+
encoding: "utf8",
|
|
358
|
+
timeout: 5000,
|
|
359
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
360
|
+
});
|
|
361
|
+
return String(out)
|
|
362
|
+
.split("\n")
|
|
363
|
+
.map((line) => line.trim())
|
|
364
|
+
.filter(Boolean)
|
|
365
|
+
.map((line) => {
|
|
366
|
+
const idx = line.indexOf("||");
|
|
367
|
+
return {
|
|
368
|
+
hash: idx === -1 ? line : line.slice(0, idx),
|
|
369
|
+
message: idx === -1 ? "" : line.slice(idx + 2),
|
|
370
|
+
};
|
|
371
|
+
});
|
|
372
|
+
} catch {
|
|
373
|
+
return [];
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Every floor that heats. Dedup per HEAD. Cursor-only copy was the leftover. */
|
|
378
|
+
function maybeCaptureSessionOnHeadMove(root) {
|
|
379
|
+
const cfg = readSessionCaptureConfig(root);
|
|
380
|
+
if (!cfg.enabled) return null;
|
|
381
|
+
const git = readGitBrief(root);
|
|
382
|
+
if (!git || !git.head) return null;
|
|
383
|
+
let stamp = null;
|
|
384
|
+
try {
|
|
385
|
+
stamp = JSON.parse(readFileSync(sessionCaptureStampPath(root), "utf8"));
|
|
386
|
+
} catch {
|
|
387
|
+
stamp = null;
|
|
388
|
+
}
|
|
389
|
+
if (stamp && stamp.head === git.head) return null;
|
|
390
|
+
const commits = stamp && stamp.head
|
|
391
|
+
? gitCommitSubjects(root, [`${stamp.head}..HEAD`])
|
|
392
|
+
: gitCommitSubjects(root, ["-n", String(cfg.lookback)]);
|
|
393
|
+
if (commits.length < cfg.minCommits) return null;
|
|
394
|
+
const sessionId = `session-${new Date().toISOString().slice(0, 10)}-${git.head}`;
|
|
395
|
+
const approaches = commits.map((row) => row.message).filter(Boolean);
|
|
396
|
+
const session = {
|
|
397
|
+
sessionId,
|
|
398
|
+
timestamp: new Date().toISOString(),
|
|
399
|
+
span: {
|
|
400
|
+
from: stamp && stamp.head ? stamp.head : `HEAD~${cfg.lookback}`,
|
|
401
|
+
to: git.head,
|
|
402
|
+
},
|
|
403
|
+
problems: [],
|
|
404
|
+
approaches,
|
|
405
|
+
wrongTurns: [],
|
|
406
|
+
solutions: [],
|
|
407
|
+
patterns: [],
|
|
408
|
+
matched_primitives: [],
|
|
409
|
+
metrics: { commits: commits.length },
|
|
410
|
+
};
|
|
411
|
+
const outDir = join(root, "docs", "inference");
|
|
412
|
+
mkdirSync(outDir, { recursive: true });
|
|
413
|
+
const filePath = join(outDir, `session-${sessionId.replace(/^session-/, "")}.json`);
|
|
414
|
+
writeFileSync(filePath, `${JSON.stringify(session, null, 2)}\n`);
|
|
415
|
+
writeFileSync(join(outDir, "latest-session.json"), `${JSON.stringify(session, null, 2)}\n`);
|
|
416
|
+
mkdirSync(join(root, ".xray", "state"), { recursive: true });
|
|
417
|
+
writeFileSync(
|
|
418
|
+
sessionCaptureStampPath(root),
|
|
419
|
+
`${JSON.stringify({ head: git.head, sessionId, path: filePath, updatedAt: session.timestamp }, null, 2)}\n`,
|
|
420
|
+
);
|
|
421
|
+
return filePath;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** Overlay + factory names on dest. Subject repo-* stay off this list. */
|
|
207
425
|
function readOpProcNames(root) {
|
|
208
|
-
const dest =
|
|
426
|
+
const dest = destSignalsPath(root);
|
|
209
427
|
if (!existsSync(dest)) return [];
|
|
428
|
+
const factory = signalNameSet(repertoireDataFile(root, "curated_signals.json"));
|
|
429
|
+
const stack = signalNameSet(repertoireDataFile(root, "stack-overlay.json"));
|
|
430
|
+
const filter = factory.size || stack.size;
|
|
210
431
|
try {
|
|
211
432
|
const data = JSON.parse(readFileSync(dest, "utf8"));
|
|
212
433
|
if (!Array.isArray(data.signals)) return [];
|
|
@@ -214,6 +435,8 @@ function readOpProcNames(root) {
|
|
|
214
435
|
for (const signal of data.signals) {
|
|
215
436
|
const name = String(signal && signal.name ? signal.name : "").trim();
|
|
216
437
|
if (!name || name.toLowerCase().startsWith("bedrock-")) continue;
|
|
438
|
+
if (name.toLowerCase().startsWith("repo-")) continue;
|
|
439
|
+
if (filter && !factory.has(name) && !stack.has(name)) continue;
|
|
217
440
|
names.push(name);
|
|
218
441
|
}
|
|
219
442
|
return names;
|
|
@@ -295,8 +518,9 @@ function buildRepertoireResume(root) {
|
|
|
295
518
|
if (!modulePath) {
|
|
296
519
|
return "Repertoire: not installed (memory_routing stays off)";
|
|
297
520
|
}
|
|
298
|
-
|
|
299
|
-
|
|
521
|
+
const destPath = destSignalsPath(root);
|
|
522
|
+
let signalsPath = existsSync(destPath) ? destPath : mr.config && mr.config.signalsPath;
|
|
523
|
+
if (signalsPath && signalsPath !== destPath && !signalsPath.startsWith("/") && !/^[A-Za-z]:[\\/]/.test(signalsPath)) {
|
|
300
524
|
signalsPath = join(root, signalsPath);
|
|
301
525
|
}
|
|
302
526
|
if (!signalsPath) {
|
|
@@ -311,6 +535,12 @@ function buildRepertoireResume(root) {
|
|
|
311
535
|
}
|
|
312
536
|
|
|
313
537
|
function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
538
|
+
const mr = readMemoryRoutingConfig(root);
|
|
539
|
+
const memoryOff = isExplicitMemoryRoutingOptOut(mr);
|
|
540
|
+
const hydrate = memoryOff ? { dest: null, added: 0, destCount: 0 } : hydrateDestOnWake(root);
|
|
541
|
+
const captured = maybeCaptureSessionOnHeadMove(root);
|
|
542
|
+
const pickup = readNotesPickup(root);
|
|
543
|
+
const approaches = readLatestSessionApproaches(root);
|
|
314
544
|
const prevHost = typeof existing.host === "string" ? existing.host : null;
|
|
315
545
|
const nextSwap = prevHost && host && prevHost !== host ? { from: prevHost, to: host } : null;
|
|
316
546
|
const keptSwap =
|
|
@@ -321,9 +551,12 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
|
321
551
|
? existing.hotSwap
|
|
322
552
|
: null;
|
|
323
553
|
const hotSwap = nextSwap || keptSwap;
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
554
|
+
const incomingIntent = clipIntent(
|
|
555
|
+
extra.intent || extra.prompt || extra.userMessage || extra.user_prompt,
|
|
556
|
+
);
|
|
557
|
+
const intent = incomingIntent || (typeof existing.intent === "string" ? existing.intent : null);
|
|
558
|
+
const matchText = clipIntent([intent, pickup, approaches].filter(Boolean).join(" "));
|
|
559
|
+
const rematch = Boolean(incomingIntent);
|
|
327
560
|
const git = readGitBrief(root);
|
|
328
561
|
const planLine = readPlanLine(root);
|
|
329
562
|
const repertoireResume =
|
|
@@ -332,18 +565,27 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
|
332
565
|
const intentBit = intent ? `intent: ${intent}` : "intent: (none yet)";
|
|
333
566
|
const gitBit = git ? `git ${git.branch}@${git.head}` : "git: n/a";
|
|
334
567
|
const planBit = planLine ? `plan: ${planLine}` : "plan: (none)";
|
|
335
|
-
let matchedSignals =
|
|
568
|
+
let matchedSignals = [];
|
|
569
|
+
if (Array.isArray(extra.matchedSignals) && extra.matchedSignals.length) {
|
|
570
|
+
matchedSignals = preferSubjectHits(extra.matchedSignals, 8);
|
|
571
|
+
}
|
|
336
572
|
const priorWorking = readRepertoireWorking(root);
|
|
337
|
-
|
|
573
|
+
const destCount = hydrate.destCount || (existsSync(destSignalsPath(root)) ? countCuratedSignals(destSignalsPath(root)) : 0);
|
|
574
|
+
if (!memoryOff && !matchedSignals.length && matchText) {
|
|
338
575
|
if (
|
|
576
|
+
!rematch &&
|
|
339
577
|
priorWorking &&
|
|
340
|
-
priorWorking.
|
|
578
|
+
priorWorking.matchText === matchText &&
|
|
579
|
+
priorWorking.destCount === destCount &&
|
|
341
580
|
Array.isArray(priorWorking.matchedSignals) &&
|
|
342
581
|
priorWorking.matchedSignals.length
|
|
343
582
|
) {
|
|
344
|
-
matchedSignals =
|
|
583
|
+
matchedSignals = preferSubjectHits(priorWorking.matchedSignals, 8);
|
|
345
584
|
} else {
|
|
346
|
-
matchedSignals =
|
|
585
|
+
matchedSignals = preferSubjectHits(
|
|
586
|
+
matchStationSignalsSync(root, rematch && incomingIntent ? incomingIntent : matchText),
|
|
587
|
+
8,
|
|
588
|
+
);
|
|
347
589
|
}
|
|
348
590
|
}
|
|
349
591
|
if (!matchedSignals.length && priorWorking) {
|
|
@@ -357,16 +599,25 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
|
357
599
|
hookEvent: extra.hookEvent || extra.source || null,
|
|
358
600
|
memoryRouting: repertoireResume.startsWith("Repertoire: on") ? "on" : "off",
|
|
359
601
|
repertoireResume,
|
|
602
|
+
destCount,
|
|
360
603
|
};
|
|
361
|
-
if (
|
|
604
|
+
if (pickup) workingSnapshot.pickup = pickup;
|
|
605
|
+
if (matchText) workingSnapshot.matchText = matchText;
|
|
606
|
+
if (captured) workingSnapshot.sessionCapture = captured;
|
|
607
|
+
if (matchedSignals.length) workingSnapshot.matchedSignals = matchedSignals.slice(0, 8);
|
|
362
608
|
const opProcNames = readOpProcNames(root);
|
|
363
609
|
if (opProcNames.length) workingSnapshot.opProcNames = opProcNames;
|
|
364
610
|
const working = persistRepertoireWorking(root, workingSnapshot);
|
|
365
|
-
|
|
611
|
+
const pickupChanged = Boolean(pickup && (!priorWorking || priorWorking.pickup !== pickup));
|
|
612
|
+
if (
|
|
613
|
+
!memoryOff &&
|
|
614
|
+
matchedSignals.length &&
|
|
615
|
+
(isCompactHook(extra) || hydrate.added > 0 || pickupChanged)
|
|
616
|
+
) {
|
|
366
617
|
ingestCompactFeedbackSync(
|
|
367
618
|
root,
|
|
368
619
|
typeof extra.sessionId === "string" ? extra.sessionId : "station",
|
|
369
|
-
extra.hookEvent || extra.source || "post_compact",
|
|
620
|
+
extra.hookEvent || extra.source || (isCompactHook(extra) ? "post_compact" : "wake"),
|
|
370
621
|
matchedSignals,
|
|
371
622
|
);
|
|
372
623
|
}
|
|
@@ -541,6 +792,9 @@ module.exports = {
|
|
|
541
792
|
persistRepertoireWorking,
|
|
542
793
|
readRepertoireWorking,
|
|
543
794
|
readOpProcNames,
|
|
795
|
+
hydrateDestOnWake,
|
|
796
|
+
readNotesPickup,
|
|
797
|
+
maybeCaptureSessionOnHeadMove,
|
|
544
798
|
formatWorkingLine,
|
|
545
799
|
applyStationHeat,
|
|
546
800
|
extractPreservedStationLines,
|
|
@@ -19,6 +19,9 @@ export const buildRepertoireResume = impl.buildRepertoireResume;
|
|
|
19
19
|
export const persistRepertoireWorking = impl.persistRepertoireWorking;
|
|
20
20
|
export const readRepertoireWorking = impl.readRepertoireWorking;
|
|
21
21
|
export const readOpProcNames = impl.readOpProcNames;
|
|
22
|
+
export const hydrateDestOnWake = impl.hydrateDestOnWake;
|
|
23
|
+
export const readNotesPickup = impl.readNotesPickup;
|
|
24
|
+
export const maybeCaptureSessionOnHeadMove = impl.maybeCaptureSessionOnHeadMove;
|
|
22
25
|
export const formatWorkingLine = impl.formatWorkingLine;
|
|
23
26
|
export const applyStationHeat = impl.applyStationHeat;
|
|
24
27
|
export const extractPreservedStationLines = impl.extractPreservedStationLines;
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Sync helper for station-hook-runtime.cjs — match Repertoire signals for an intent.
|
|
4
4
|
* Prints a JSON string array. Used on every floor's station heat, not only Grok.
|
|
5
|
+
* Loads the worn provider from dest, not xray src TypeScript.
|
|
5
6
|
*/
|
|
6
7
|
import { existsSync, readFileSync } from 'node:fs';
|
|
7
8
|
import { join } from 'node:path';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
8
10
|
|
|
9
11
|
const root = process.argv[2];
|
|
10
12
|
const intent = process.argv[3] || '';
|
|
@@ -23,8 +25,52 @@ if (existsSync(featuresPath)) {
|
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
+
function providerCandidates() {
|
|
29
|
+
const configured = routing.module_path
|
|
30
|
+
? routing.module_path.startsWith('/') || /^[A-Za-z]:[\\/]/.test(routing.module_path)
|
|
31
|
+
? routing.module_path
|
|
32
|
+
: join(root, routing.module_path)
|
|
33
|
+
: null;
|
|
34
|
+
const raw = [
|
|
35
|
+
configured,
|
|
36
|
+
join(root, 'node_modules', '@0xray', 'repertoire', 'dist', 'provider', 'memory-routing-provider.js'),
|
|
37
|
+
join(root, 'vendor', '@0xray', 'repertoire', 'dist', 'provider', 'memory-routing-provider.js'),
|
|
38
|
+
join(root, '..', 'repertoire', 'dist', 'provider', 'memory-routing-provider.js'),
|
|
39
|
+
].filter(Boolean);
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const dest of raw) {
|
|
43
|
+
if (seen.has(dest)) continue;
|
|
44
|
+
seen.add(dest);
|
|
45
|
+
out.push(dest);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function loadProvider() {
|
|
51
|
+
for (const dest of providerCandidates()) {
|
|
52
|
+
if (!existsSync(dest)) continue;
|
|
53
|
+
try {
|
|
54
|
+
const mod = await import(pathToFileURL(dest).href);
|
|
55
|
+
if (typeof mod.createMemoryRoutingProvider === 'function') {
|
|
56
|
+
return mod.createMemoryRoutingProvider({
|
|
57
|
+
projectRoot: root,
|
|
58
|
+
signalsPath: routing.config && routing.config.signalsPath,
|
|
59
|
+
statePath: routing.config && routing.config.statePath,
|
|
60
|
+
feedbackDir: routing.config && routing.config.feedbackDir,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (typeof mod.loadMemoryRoutingProvider === 'function') {
|
|
64
|
+
return mod.loadMemoryRoutingProvider(routing, root);
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
/* try next */
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const provider = await loadProvider();
|
|
28
74
|
if (!provider || provider.id === 'null' || typeof provider.getTaskConfidence !== 'function') {
|
|
29
75
|
process.stdout.write('[]\n');
|
|
30
76
|
process.exit(0);
|
|
@@ -41,7 +87,7 @@ for (const signalName of names) {
|
|
|
41
87
|
const name = String(signalName || '').trim();
|
|
42
88
|
if (!name || name.toLowerCase().startsWith('bedrock-')) continue;
|
|
43
89
|
safe.push(name);
|
|
44
|
-
if (safe.length >=
|
|
90
|
+
if (safe.length >= 12) break;
|
|
45
91
|
}
|
|
46
92
|
process.stdout.write(`${JSON.stringify(safe)}\n`);
|
|
47
93
|
process.exit(0);
|
package/package.json
CHANGED
|
@@ -92,7 +92,7 @@ function assertOrganRequire(tmpRoot) {
|
|
|
92
92
|
if (organPkg.name !== "@0xray/repertoire") {
|
|
93
93
|
throw new Error(`required organ name ${organPkg.name}`);
|
|
94
94
|
}
|
|
95
|
-
if (organPkg.version !== "0.2.
|
|
95
|
+
if (organPkg.version !== "0.2.5") {
|
|
96
96
|
throw new Error(`required organ version ${organPkg.version}`);
|
|
97
97
|
}
|
|
98
98
|
if (fs.existsSync(path.join(tmpRoot, "vendor"))) {
|
|
@@ -165,8 +165,8 @@ function assertFreshInstallDefaults(tmpRoot, version) {
|
|
|
165
165
|
throw new Error("vendored @0xray/repertoire missing from fresh tarball install");
|
|
166
166
|
}
|
|
167
167
|
const organ = JSON.parse(fs.readFileSync(organPkg, "utf-8"));
|
|
168
|
-
if (organ.version !== "0.2.
|
|
169
|
-
throw new Error(`expected organ 0.2.
|
|
168
|
+
if (organ.version !== "0.2.5") {
|
|
169
|
+
throw new Error(`expected organ 0.2.5, got ${organ.version}`);
|
|
170
170
|
}
|
|
171
171
|
console.log(" ✅ .xray/features.json fresh-install defaults (opt-in off)");
|
|
172
172
|
return { consumerFeaturesPath, features };
|