0xray 4.0.16 → 4.0.18
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/.opencode/skills/orchestrator/SKILL.md +3 -3
- package/.xray/features.json +1 -1
- package/dist/CHANGELOG.md +49 -0
- package/dist/integrations/cursor/hooks/cursor-hook-utils.js +10 -2
- package/dist/integrations/hooks/station-hook-runtime.cjs +287 -14
- package/dist/integrations/hooks/station-hook-runtime.mjs +4 -0
- package/dist/integrations/hooks/station-memory-match.mjs +49 -3
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/skills/orchestrator/SKILL.md +3 -3
- package/package.json +1 -1
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/skills/orchestrator/SKILL.md +3 -3
- package/xray/features.json +1 -1
package/.grok-plugin/plugin.json
CHANGED
|
@@ -67,13 +67,13 @@ The seven rules are the engine. The cadence is the time signature. Disk SSOT: `g
|
|
|
67
67
|
- **Peer test:** another instance with only worn 0xRay continues the ticket. No command stream.
|
|
68
68
|
- **Wear first:** Read `.xray/state/STATION.md`. Same cloud. Same critic (resume only). Dummy Reads Station.
|
|
69
69
|
- **Dummy tests `SKILLS.md`:** a suited dummy proves the worn catalog. Same-sess bodies keep context and hold a Station card.
|
|
70
|
-
- **Ship track:** always branch + worktree/aside + PR. Docs as you go. Builds. Rollback = close the PR.
|
|
70
|
+
- **Ship track:** always branch + worktree/aside + PR. Docs as you go. Builds. Rollback = close the PR. **Subject review. Fix n ship.** After critic PASS, review dest/domain, close leftovers, then mill-gate D. PASS is not ship. **Groover is not Repertoire.**
|
|
71
71
|
- **Publish:** stay logged in (do not `npm logout`). Every time run `npm publish --access public` in the live TTY. Paste the clickable `auth/cli` URL. No Enter, no VM browser, no subagent, no 6-digit code in chat. Poll `npm view` until live. Then prove **fresh** and **upgrade** registry installs. `npx 0xray validate` is the wear check (not leftover init.sh). `npm view` is not an install.
|
|
72
72
|
- **Peer boot:** four-line dispatch. They Read Station + this cadence and install without a command novel. Not a twin critic.
|
|
73
|
-
- **Live tick, not pacer:** `/loop` runs work now. Keep workstreams moving. Do not idle-wait 12 minutes.
|
|
73
|
+
- **Live tick, not pacer:** `/loop` runs work now. Keep workstreams moving. Do not idle-wait 12 minutes. **Clean ticks every cycle** — unsubscribe then resubscribe so the prompt matches metal. Name-dedupe does not rewrite. A stale prompt (waiting URL after live, Hold after strip) is dirty: rewrite, do not act. **If the card and WAVEBOARD are idle, unsubscribe and do not resubscribe.**
|
|
74
74
|
- **Board clock:** PR events + resume the same critic. Not two idle pacers. Merge after PASS+CI (gate A). Publish only after mill-gate D + `foundry gate`.
|
|
75
75
|
- **Dispatch:** ticket + PR URLs + “Wear, then review.” The critic Reads `SEATS.md` Reviewer. Do not paste a command novel. When a law is spoken twice, encode it this wake.
|
|
76
|
-
- **Close:** loop until Station is done. Compact: Station survives the cut.
|
|
76
|
+
- **Close:** loop until Station is done. Compact: Station survives the cut. Overlay dest names are OP-PROC — reload `repertoire-working.json` `opProcNames`, not Station.
|
|
77
77
|
- **Board:** critic Strict comments; forge implements; lead stays main thread.
|
|
78
78
|
- **Codex 69:** rewire this skill and existing ops. Do not add a new `SKILL.md` to hold the cadence.
|
|
79
79
|
- **Ship matrix:** `LEAD-CADENCE.md` Decision matrix — A PR+CI · B pack · C `release:docs-check` · D `foundry release --i-mean-it`. Green CI is not ship. Spend / credentials stay Ask-first.
|
package/.xray/features.json
CHANGED
package/dist/CHANGELOG.md
CHANGED
|
@@ -20,6 +20,11 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
|
|
|
20
20
|
- **Publish OTP latch** — stay logged in (do not `npm logout`). Every time run `npm publish --access public` in the live TTY (no `--auth-type=web`, no `--otp=` from chat, no `tee`). Paste the clickable `auth/cli` URL. Do not ask for a 6-digit authenticator code in chat. Do not press Enter. Do not hand publish to a browser subagent. Poll `npm view` until the version is live. Then prove **fresh** and **upgrade** registry installs. `npm view` is not an install.
|
|
21
21
|
- **`npx 0xray validate` cleansed** — no longer shells leftover `.opencode/init.sh` (macOS `md5` + `enforcer-config.json`). Wear check: `REQUIRED_PACK_PATHS`, Cursor hooks, mill+inspect, repertoire. `init.sh` still boots OpenCode but accepts Linux `md5sum` and consumer `.xray/codex.json`.
|
|
22
22
|
- **Swamp reflection (new arch)** — `docs/reflections/swamp-new-arch-reflection-2026-09-20.md`. Card, organ, hook. Lying metal. Live tarball lag.
|
|
23
|
+
- **Clean ticks every cycle** — `/loop` prompt is rewritten at the end of every live tick. `subscribe_timer` name-dedupe does not update the prompt. Unsubscribe then resubscribe. A tick that contradicts metal is dirty: rewrite, do not act. Not on Station.
|
|
24
|
+
- **Idle `/loop` stop** — if Station `Working` and the WAVEBOARD (open ship PR, live OTP, unfinished close) are idle, unsubscribe and do not resubscribe. A parked mill is not an active wave.
|
|
25
|
+
- **Subject review. Fix n ship.** After board PASS, review the worn dest / domain (not only CI), close leftovers, then mill-gate D. PASS is not ship. On `LEAD-CADENCE.md` Ship track + `SEATS.md` Reviewer. Not on Station.
|
|
26
|
+
- **OP-PROC reload after compact** — overlay dest names *are* OP-PROC. Heat writes them to `.xray/state/repertoire-working.json` `opProcNames`. Station stays a ticket (count + four working matches). Not a new skill.
|
|
27
|
+
- **Groover is not Repertoire.** Groover was a broken experiment of the organ. Do not grow dest from Groover field as if that is the KB. On `LEAD-CADENCE.md` Heads from tails. Not on Station.
|
|
23
28
|
|
|
24
29
|
### 🔄 Changes
|
|
25
30
|
- **Durable Hold npm** — heat + `cursorBootNeedsRefresh` + `npx 0xray validate` drop/fail `Hold npm` on Station. Identity stays. Mill-gate holds the ship latch. Not a new skill.
|
|
@@ -29,6 +34,50 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
|
|
|
29
34
|
|
|
30
35
|
---
|
|
31
36
|
|
|
37
|
+
## [4.0.18] - 2026-09-22
|
|
38
|
+
|
|
39
|
+
### 🔄 Changes
|
|
40
|
+
|
|
41
|
+
### ✨ Features
|
|
42
|
+
- feat: wake heat joins bookmark, index, and mind (ad596aae2)
|
|
43
|
+
|
|
44
|
+
### 🐛 Bug Fixes
|
|
45
|
+
- fix: pin subject hits on wake rematch (65de9d37a)
|
|
46
|
+
- fix: wake match loads 0.2.5 and keeps subject hits (80b61236d)
|
|
47
|
+
|
|
48
|
+
### 📚 Documentation
|
|
49
|
+
- docs: memory-wake join — BRAIN-003 (1c128e69c)
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## [4.0.17] - 2026-09-22
|
|
54
|
+
|
|
55
|
+
### 🔄 Changes
|
|
56
|
+
|
|
57
|
+
### ✨ Features
|
|
58
|
+
- feat: write session-*.json from Cursor heat when HEAD moves (363186a02)
|
|
59
|
+
- feat: persist overlay OP-PROC on repertoire-working after compact heat (8c55793e8)
|
|
60
|
+
|
|
61
|
+
### 📚 Documentation
|
|
62
|
+
- docs: ASCII gather/store/retrieve trees for Station memory (d8f2f3b89)
|
|
63
|
+
- docs: Station is always the pickup memory after compact (bec431c61)
|
|
64
|
+
- docs: Groover is not Repertoire — do not grow dest from Groover field (203147d19)
|
|
65
|
+
- docs: Subject review. Fix n ship. — dest review then leftover then D (ad4ffa7f6)
|
|
66
|
+
- docs: stop /loop when the card and WAVEBOARD are idle (60f0fd53f)
|
|
67
|
+
- docs: clean ticks every cycle — rewrite /loop prompt, do not act on stale metal (e71a19559)
|
|
68
|
+
|
|
69
|
+
### 🧪 Tests
|
|
70
|
+
- test: match Compact cut wording on Station-as-memory (7f9a4f091)
|
|
71
|
+
|
|
72
|
+
### 🔎 Other Changes
|
|
73
|
+
- Merge main — include weekday-mirror #97 in 4.0.17 tarball (86fdec7ce)
|
|
74
|
+
- Merge pull request #97 from 0xRayAI/cursor/weekday-mirror-dist-voice-9b4d (d0853aad1)
|
|
75
|
+
- docs(grok-bot): weekday mirror for Dist reply voice and CoS wake hygiene (f91233b59)
|
|
76
|
+
- Merge branch 'cursor/clean-ticks-fbe0' — clean ticks every cycle (12dd06362)
|
|
77
|
+
- Merge branch 'cursor/leftover-validate-d-fbe0' — release 4.0.16 live, leftover validate gone (8fa037212)
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
32
81
|
## [4.0.16] - 2026-09-20
|
|
33
82
|
|
|
34
83
|
### 🔄 Changes
|
|
@@ -24,10 +24,13 @@ import {
|
|
|
24
24
|
import { appendHookActivity } from '../../grok/hooks/grok-hook-activity.js';
|
|
25
25
|
import {
|
|
26
26
|
buildRepertoireResume,
|
|
27
|
+
maybeCaptureSessionOnHeadMove,
|
|
27
28
|
readGitBrief,
|
|
28
29
|
stationDurableHoldsNpm,
|
|
29
30
|
} from '../../hooks/station-hook-runtime.mjs';
|
|
30
31
|
|
|
32
|
+
export { maybeCaptureSessionOnHeadMove };
|
|
33
|
+
|
|
31
34
|
export const CURSOR_HOST = 'cursor';
|
|
32
35
|
export const EVENT_CLASS_HOST = 'cursor-host-precompact';
|
|
33
36
|
export const EVENT_CLASS_SYNTHETIC = 'cursor-precompact-synthetic';
|
|
@@ -157,7 +160,10 @@ export function ensureCursorSessionBoot(root, source = '0xray/cursor-pre-tool-us
|
|
|
157
160
|
if (fs.existsSync(bootPath)) {
|
|
158
161
|
try {
|
|
159
162
|
const existing = JSON.parse(fs.readFileSync(bootPath, 'utf8'));
|
|
160
|
-
if (!cursorBootNeedsRefresh(existing, root))
|
|
163
|
+
if (!cursorBootNeedsRefresh(existing, root)) {
|
|
164
|
+
maybeCaptureSessionOnHeadMove(root);
|
|
165
|
+
return bootPath;
|
|
166
|
+
}
|
|
161
167
|
} catch {
|
|
162
168
|
/* rewrite corrupt or host-mismatched boot */
|
|
163
169
|
}
|
|
@@ -166,7 +172,9 @@ export function ensureCursorSessionBoot(root, source = '0xray/cursor-pre-tool-us
|
|
|
166
172
|
host: CURSOR_HOST,
|
|
167
173
|
...extra,
|
|
168
174
|
});
|
|
169
|
-
|
|
175
|
+
const written = writeSessionBoot(root, payload) || bootPath;
|
|
176
|
+
maybeCaptureSessionOnHeadMove(root);
|
|
177
|
+
return written;
|
|
170
178
|
}
|
|
171
179
|
|
|
172
180
|
export function writeCursorPrecompactReceipt(root, fields) {
|
|
@@ -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,6 +203,248 @@ function stationSafeSignals(names) {
|
|
|
203
203
|
return out;
|
|
204
204
|
}
|
|
205
205
|
|
|
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, "..", "repertoire"),
|
|
233
|
+
join(root, "vendor", "@0xray", "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. */
|
|
425
|
+
function readOpProcNames(root) {
|
|
426
|
+
const dest = destSignalsPath(root);
|
|
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;
|
|
431
|
+
try {
|
|
432
|
+
const data = JSON.parse(readFileSync(dest, "utf8"));
|
|
433
|
+
if (!Array.isArray(data.signals)) return [];
|
|
434
|
+
const names = [];
|
|
435
|
+
for (const signal of data.signals) {
|
|
436
|
+
const name = String(signal && signal.name ? signal.name : "").trim();
|
|
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;
|
|
440
|
+
names.push(name);
|
|
441
|
+
}
|
|
442
|
+
return names;
|
|
443
|
+
} catch {
|
|
444
|
+
return [];
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
206
448
|
function persistRepertoireWorking(root, snapshot) {
|
|
207
449
|
try {
|
|
208
450
|
mkdirSync(join(root, ".xray", "state"), { recursive: true });
|
|
@@ -276,8 +518,9 @@ function buildRepertoireResume(root) {
|
|
|
276
518
|
if (!modulePath) {
|
|
277
519
|
return "Repertoire: not installed (memory_routing stays off)";
|
|
278
520
|
}
|
|
279
|
-
|
|
280
|
-
|
|
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)) {
|
|
281
524
|
signalsPath = join(root, signalsPath);
|
|
282
525
|
}
|
|
283
526
|
if (!signalsPath) {
|
|
@@ -292,6 +535,10 @@ function buildRepertoireResume(root) {
|
|
|
292
535
|
}
|
|
293
536
|
|
|
294
537
|
function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
538
|
+
const hydrate = hydrateDestOnWake(root);
|
|
539
|
+
const captured = maybeCaptureSessionOnHeadMove(root);
|
|
540
|
+
const pickup = readNotesPickup(root);
|
|
541
|
+
const approaches = readLatestSessionApproaches(root);
|
|
295
542
|
const prevHost = typeof existing.host === "string" ? existing.host : null;
|
|
296
543
|
const nextSwap = prevHost && host && prevHost !== host ? { from: prevHost, to: host } : null;
|
|
297
544
|
const keptSwap =
|
|
@@ -302,9 +549,12 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
|
302
549
|
? existing.hotSwap
|
|
303
550
|
: null;
|
|
304
551
|
const hotSwap = nextSwap || keptSwap;
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
552
|
+
const incomingIntent = clipIntent(
|
|
553
|
+
extra.intent || extra.prompt || extra.userMessage || extra.user_prompt,
|
|
554
|
+
);
|
|
555
|
+
const intent = incomingIntent || (typeof existing.intent === "string" ? existing.intent : null);
|
|
556
|
+
const matchText = clipIntent([intent, pickup, approaches].filter(Boolean).join(" "));
|
|
557
|
+
const rematch = Boolean(incomingIntent);
|
|
308
558
|
const git = readGitBrief(root);
|
|
309
559
|
const planLine = readPlanLine(root);
|
|
310
560
|
const repertoireResume =
|
|
@@ -313,18 +563,27 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
|
313
563
|
const intentBit = intent ? `intent: ${intent}` : "intent: (none yet)";
|
|
314
564
|
const gitBit = git ? `git ${git.branch}@${git.head}` : "git: n/a";
|
|
315
565
|
const planBit = planLine ? `plan: ${planLine}` : "plan: (none)";
|
|
316
|
-
let matchedSignals =
|
|
566
|
+
let matchedSignals = [];
|
|
567
|
+
if (Array.isArray(extra.matchedSignals) && extra.matchedSignals.length) {
|
|
568
|
+
matchedSignals = preferSubjectHits(extra.matchedSignals, 8);
|
|
569
|
+
}
|
|
317
570
|
const priorWorking = readRepertoireWorking(root);
|
|
318
|
-
|
|
571
|
+
const destCount = hydrate.destCount || (existsSync(destSignalsPath(root)) ? countCuratedSignals(destSignalsPath(root)) : 0);
|
|
572
|
+
if (!matchedSignals.length && matchText) {
|
|
319
573
|
if (
|
|
574
|
+
!rematch &&
|
|
320
575
|
priorWorking &&
|
|
321
|
-
priorWorking.
|
|
576
|
+
priorWorking.matchText === matchText &&
|
|
577
|
+
priorWorking.destCount === destCount &&
|
|
322
578
|
Array.isArray(priorWorking.matchedSignals) &&
|
|
323
579
|
priorWorking.matchedSignals.length
|
|
324
580
|
) {
|
|
325
|
-
matchedSignals =
|
|
581
|
+
matchedSignals = preferSubjectHits(priorWorking.matchedSignals, 8);
|
|
326
582
|
} else {
|
|
327
|
-
matchedSignals =
|
|
583
|
+
matchedSignals = preferSubjectHits(
|
|
584
|
+
matchStationSignalsSync(root, rematch && incomingIntent ? incomingIntent : matchText),
|
|
585
|
+
8,
|
|
586
|
+
);
|
|
328
587
|
}
|
|
329
588
|
}
|
|
330
589
|
if (!matchedSignals.length && priorWorking) {
|
|
@@ -338,14 +597,24 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
|
338
597
|
hookEvent: extra.hookEvent || extra.source || null,
|
|
339
598
|
memoryRouting: repertoireResume.startsWith("Repertoire: on") ? "on" : "off",
|
|
340
599
|
repertoireResume,
|
|
600
|
+
destCount,
|
|
341
601
|
};
|
|
342
|
-
if (
|
|
602
|
+
if (pickup) workingSnapshot.pickup = pickup;
|
|
603
|
+
if (matchText) workingSnapshot.matchText = matchText;
|
|
604
|
+
if (captured) workingSnapshot.sessionCapture = captured;
|
|
605
|
+
if (matchedSignals.length) workingSnapshot.matchedSignals = matchedSignals.slice(0, 8);
|
|
606
|
+
const opProcNames = readOpProcNames(root);
|
|
607
|
+
if (opProcNames.length) workingSnapshot.opProcNames = opProcNames;
|
|
343
608
|
const working = persistRepertoireWorking(root, workingSnapshot);
|
|
344
|
-
|
|
609
|
+
const pickupChanged = Boolean(pickup && (!priorWorking || priorWorking.pickup !== pickup));
|
|
610
|
+
if (
|
|
611
|
+
matchedSignals.length &&
|
|
612
|
+
(isCompactHook(extra) || hydrate.added > 0 || pickupChanged)
|
|
613
|
+
) {
|
|
345
614
|
ingestCompactFeedbackSync(
|
|
346
615
|
root,
|
|
347
616
|
typeof extra.sessionId === "string" ? extra.sessionId : "station",
|
|
348
|
-
extra.hookEvent || extra.source || "post_compact",
|
|
617
|
+
extra.hookEvent || extra.source || (isCompactHook(extra) ? "post_compact" : "wake"),
|
|
349
618
|
matchedSignals,
|
|
350
619
|
);
|
|
351
620
|
}
|
|
@@ -519,6 +788,10 @@ module.exports = {
|
|
|
519
788
|
isLeftoverMemoryRoutingOff,
|
|
520
789
|
persistRepertoireWorking,
|
|
521
790
|
readRepertoireWorking,
|
|
791
|
+
readOpProcNames,
|
|
792
|
+
hydrateDestOnWake,
|
|
793
|
+
readNotesPickup,
|
|
794
|
+
maybeCaptureSessionOnHeadMove,
|
|
522
795
|
formatWorkingLine,
|
|
523
796
|
applyStationHeat,
|
|
524
797
|
extractPreservedStationLines,
|
|
@@ -18,6 +18,10 @@ export const readPlanLine = impl.readPlanLine;
|
|
|
18
18
|
export const buildRepertoireResume = impl.buildRepertoireResume;
|
|
19
19
|
export const persistRepertoireWorking = impl.persistRepertoireWorking;
|
|
20
20
|
export const readRepertoireWorking = impl.readRepertoireWorking;
|
|
21
|
+
export const readOpProcNames = impl.readOpProcNames;
|
|
22
|
+
export const hydrateDestOnWake = impl.hydrateDestOnWake;
|
|
23
|
+
export const readNotesPickup = impl.readNotesPickup;
|
|
24
|
+
export const maybeCaptureSessionOnHeadMove = impl.maybeCaptureSessionOnHeadMove;
|
|
21
25
|
export const formatWorkingLine = impl.formatWorkingLine;
|
|
22
26
|
export const applyStationHeat = impl.applyStationHeat;
|
|
23
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
|
+
join(root, '..', 'repertoire', 'dist', 'provider', 'memory-routing-provider.js'),
|
|
36
|
+
join(root, 'node_modules', '@0xray', 'repertoire', 'dist', 'provider', 'memory-routing-provider.js'),
|
|
37
|
+
configured,
|
|
38
|
+
join(root, 'vendor', '@0xray', 'repertoire', 'dist', 'provider', 'memory-routing-provider.js'),
|
|
39
|
+
].filter(Boolean);
|
|
40
|
+
const withSubject = [];
|
|
41
|
+
const rest = [];
|
|
42
|
+
for (const dest of raw) {
|
|
43
|
+
const overlay = dest.replace(/dist\/provider\/memory-routing-provider\.js$/, 'data/subject-overlay.json');
|
|
44
|
+
if (existsSync(overlay)) withSubject.push(dest);
|
|
45
|
+
else rest.push(dest);
|
|
46
|
+
}
|
|
47
|
+
return [...withSubject, ...rest];
|
|
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);
|
|
@@ -67,13 +67,13 @@ The seven rules are the engine. The cadence is the time signature. Disk SSOT: `g
|
|
|
67
67
|
- **Peer test:** another instance with only worn 0xRay continues the ticket. No command stream.
|
|
68
68
|
- **Wear first:** Read `.xray/state/STATION.md`. Same cloud. Same critic (resume only). Dummy Reads Station.
|
|
69
69
|
- **Dummy tests `SKILLS.md`:** a suited dummy proves the worn catalog. Same-sess bodies keep context and hold a Station card.
|
|
70
|
-
- **Ship track:** always branch + worktree/aside + PR. Docs as you go. Builds. Rollback = close the PR.
|
|
70
|
+
- **Ship track:** always branch + worktree/aside + PR. Docs as you go. Builds. Rollback = close the PR. **Subject review. Fix n ship.** After critic PASS, review dest/domain, close leftovers, then mill-gate D. PASS is not ship. **Groover is not Repertoire.**
|
|
71
71
|
- **Publish:** stay logged in (do not `npm logout`). Every time run `npm publish --access public` in the live TTY. Paste the clickable `auth/cli` URL. No Enter, no VM browser, no subagent, no 6-digit code in chat. Poll `npm view` until live. Then prove **fresh** and **upgrade** registry installs. `npx 0xray validate` is the wear check (not leftover init.sh). `npm view` is not an install.
|
|
72
72
|
- **Peer boot:** four-line dispatch. They Read Station + this cadence and install without a command novel. Not a twin critic.
|
|
73
|
-
- **Live tick, not pacer:** `/loop` runs work now. Keep workstreams moving. Do not idle-wait 12 minutes.
|
|
73
|
+
- **Live tick, not pacer:** `/loop` runs work now. Keep workstreams moving. Do not idle-wait 12 minutes. **Clean ticks every cycle** — unsubscribe then resubscribe so the prompt matches metal. Name-dedupe does not rewrite. A stale prompt (waiting URL after live, Hold after strip) is dirty: rewrite, do not act. **If the card and WAVEBOARD are idle, unsubscribe and do not resubscribe.**
|
|
74
74
|
- **Board clock:** PR events + resume the same critic. Not two idle pacers. Merge after PASS+CI (gate A). Publish only after mill-gate D + `foundry gate`.
|
|
75
75
|
- **Dispatch:** ticket + PR URLs + “Wear, then review.” The critic Reads `SEATS.md` Reviewer. Do not paste a command novel. When a law is spoken twice, encode it this wake.
|
|
76
|
-
- **Close:** loop until Station is done. Compact: Station survives the cut.
|
|
76
|
+
- **Close:** loop until Station is done. Compact: Station survives the cut. Overlay dest names are OP-PROC — reload `repertoire-working.json` `opProcNames`, not Station.
|
|
77
77
|
- **Board:** critic Strict comments; forge implements; lead stays main thread.
|
|
78
78
|
- **Codex 69:** rewire this skill and existing ops. Do not add a new `SKILL.md` to hold the cadence.
|
|
79
79
|
- **Ship matrix:** `LEAD-CADENCE.md` Decision matrix — A PR+CI · B pack · C `release:docs-check` · D `foundry release --i-mean-it`. Green CI is not ship. Spend / credentials stay Ask-first.
|
package/package.json
CHANGED
|
@@ -67,13 +67,13 @@ The seven rules are the engine. The cadence is the time signature. Disk SSOT: `g
|
|
|
67
67
|
- **Peer test:** another instance with only worn 0xRay continues the ticket. No command stream.
|
|
68
68
|
- **Wear first:** Read `.xray/state/STATION.md`. Same cloud. Same critic (resume only). Dummy Reads Station.
|
|
69
69
|
- **Dummy tests `SKILLS.md`:** a suited dummy proves the worn catalog. Same-sess bodies keep context and hold a Station card.
|
|
70
|
-
- **Ship track:** always branch + worktree/aside + PR. Docs as you go. Builds. Rollback = close the PR.
|
|
70
|
+
- **Ship track:** always branch + worktree/aside + PR. Docs as you go. Builds. Rollback = close the PR. **Subject review. Fix n ship.** After critic PASS, review dest/domain, close leftovers, then mill-gate D. PASS is not ship. **Groover is not Repertoire.**
|
|
71
71
|
- **Publish:** stay logged in (do not `npm logout`). Every time run `npm publish --access public` in the live TTY. Paste the clickable `auth/cli` URL. No Enter, no VM browser, no subagent, no 6-digit code in chat. Poll `npm view` until live. Then prove **fresh** and **upgrade** registry installs. `npx 0xray validate` is the wear check (not leftover init.sh). `npm view` is not an install.
|
|
72
72
|
- **Peer boot:** four-line dispatch. They Read Station + this cadence and install without a command novel. Not a twin critic.
|
|
73
|
-
- **Live tick, not pacer:** `/loop` runs work now. Keep workstreams moving. Do not idle-wait 12 minutes.
|
|
73
|
+
- **Live tick, not pacer:** `/loop` runs work now. Keep workstreams moving. Do not idle-wait 12 minutes. **Clean ticks every cycle** — unsubscribe then resubscribe so the prompt matches metal. Name-dedupe does not rewrite. A stale prompt (waiting URL after live, Hold after strip) is dirty: rewrite, do not act. **If the card and WAVEBOARD are idle, unsubscribe and do not resubscribe.**
|
|
74
74
|
- **Board clock:** PR events + resume the same critic. Not two idle pacers. Merge after PASS+CI (gate A). Publish only after mill-gate D + `foundry gate`.
|
|
75
75
|
- **Dispatch:** ticket + PR URLs + “Wear, then review.” The critic Reads `SEATS.md` Reviewer. Do not paste a command novel. When a law is spoken twice, encode it this wake.
|
|
76
|
-
- **Close:** loop until Station is done. Compact: Station survives the cut.
|
|
76
|
+
- **Close:** loop until Station is done. Compact: Station survives the cut. Overlay dest names are OP-PROC — reload `repertoire-working.json` `opProcNames`, not Station.
|
|
77
77
|
- **Board:** critic Strict comments; forge implements; lead stays main thread.
|
|
78
78
|
- **Codex 69:** rewire this skill and existing ops. Do not add a new `SKILL.md` to hold the cadence.
|
|
79
79
|
- **Ship matrix:** `LEAD-CADENCE.md` Decision matrix — A PR+CI · B pack · C `release:docs-check` · D `foundry release --i-mean-it`. Green CI is not ship. Spend / credentials stay Ask-first.
|