0xray 4.0.15 → 4.0.17
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/init.sh +14 -4
- package/.opencode/skills/orchestrator/SKILL.md +5 -4
- package/.xray/features.json +1 -1
- package/dist/CHANGELOG.md +56 -0
- package/dist/cli/commands/validate.d.ts +26 -0
- package/dist/cli/commands/validate.js +164 -0
- package/dist/cli/index.js +4 -12
- package/dist/integrations/cursor/hooks/cursor-hook-utils.js +109 -2
- package/dist/integrations/hooks/station-hook-runtime.cjs +45 -2
- package/dist/integrations/hooks/station-hook-runtime.mjs +3 -0
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/skills/orchestrator/SKILL.md +5 -4
- package/package.json +1 -1
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/skills/orchestrator/SKILL.md +5 -4
- package/xray/features.json +1 -1
package/.grok-plugin/plugin.json
CHANGED
package/.opencode/init.sh
CHANGED
|
@@ -35,8 +35,15 @@ XRAY_VERSION=$(node -e "console.log(require('$FRAMEWORK_ROOT/package.json').vers
|
|
|
35
35
|
# Dedup guard — prevent duplicate runs during startup
|
|
36
36
|
# Uses a TTL lockfile (10s window) since OpenCode may trigger config hook
|
|
37
37
|
# from multiple plugin copies in quick succession
|
|
38
|
-
# Key by PROJECT_ROOT
|
|
39
|
-
|
|
38
|
+
# Key by PROJECT_ROOT so all invocations in the same project share one lock.
|
|
39
|
+
# md5 is macOS; Linux has md5sum. Do not fail the boot on a leftover Mac tool.
|
|
40
|
+
if command -v md5 >/dev/null 2>&1; then
|
|
41
|
+
LOCK_KEY=$(echo -n "$PROJECT_ROOT" | md5 | cut -c1-16)
|
|
42
|
+
elif command -v md5sum >/dev/null 2>&1; then
|
|
43
|
+
LOCK_KEY=$(echo -n "$PROJECT_ROOT" | md5sum | awk '{print $1}' | cut -c1-16)
|
|
44
|
+
else
|
|
45
|
+
LOCK_KEY=$(echo -n "$PROJECT_ROOT" | cksum | awk '{print $1}')
|
|
46
|
+
fi
|
|
40
47
|
LOCK_FILE="/tmp/xray-init-${LOCK_KEY}.lock"
|
|
41
48
|
LOCK_TTL=10
|
|
42
49
|
if [ -f "$LOCK_FILE" ]; then
|
|
@@ -123,8 +130,11 @@ else
|
|
|
123
130
|
PLUGIN_STATUS="❌"
|
|
124
131
|
fi
|
|
125
132
|
|
|
126
|
-
# Framework config check
|
|
127
|
-
if [ ! -f "$PROJECT_ROOT/.opencode/enforcer-config.json" ]
|
|
133
|
+
# Framework config check — consumer wear is .xray/codex.json, not leftover enforcer-config.json
|
|
134
|
+
if [ ! -f "$PROJECT_ROOT/.opencode/enforcer-config.json" ] \
|
|
135
|
+
&& [ ! -f "$PROJECT_ROOT/.xray/codex.json" ] \
|
|
136
|
+
&& [ ! -f "$PROJECT_ROOT/node_modules/0xray/.xray/codex.json" ] \
|
|
137
|
+
&& [ ! -f "$PROJECT_ROOT/.mcp.json" ]; then
|
|
128
138
|
echo -e "${PURPLE}// ❌ Framework configuration not found //${NC}"
|
|
129
139
|
exit 1
|
|
130
140
|
fi
|
|
@@ -67,12 +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.
|
|
71
|
-
- **Publish:**
|
|
72
|
-
- **
|
|
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
|
+
- **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
|
+
- **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. **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.**
|
|
73
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`.
|
|
74
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.
|
|
75
|
-
- **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.
|
|
76
77
|
- **Board:** critic Strict comments; forge implements; lead stays main thread.
|
|
77
78
|
- **Codex 69:** rewire this skill and existing ops. Do not add a new `SKILL.md` to hold the cadence.
|
|
78
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
|
@@ -17,14 +17,70 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
|
|
|
17
17
|
- **Dispatch encode** — lead cards ticket + PR URLs + “Wear, then review.” Critic OP-PROC lives on `SEATS.md` Reviewer. When a law is spoken twice, write it to disk this wake. No new skill.
|
|
18
18
|
- **Ship decision matrix** — lead-owned. A PR+CI · B pack (`assert-packed-dist-cli` + consumer smoke) · C `npm run release:docs-check` · D `npx @0xray/foundry release --i-mean-it`. Green CI is not ship. Spend / credentials stay Ask-first. Host Auto Review may still card npm/Railway; that is not the OS decision.
|
|
19
19
|
- **Metamorphosis** — an LLM becomes OP-PROC through in-context worn seats (same critic, same dummy, four-line dispatch). Spawning more bots is the old skin.
|
|
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
|
+
- **`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
|
+
- **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.
|
|
20
28
|
|
|
21
29
|
### 🔄 Changes
|
|
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.
|
|
22
31
|
- **Hangar catalog** — mill hangar docs use Clearing `GET /v1/catalog` as the directory (listed hangars: Groover DID + pin, plus solar + live shop), not a hardcoded extract/witness/pin route table. Groover MCP `list_hangars` reads that catalog. 0xray stays 4.0.15.
|
|
23
32
|
- **foundry-plant/0** — fasten is a protocol any mill implements, not a 0xray-owned catalog of plant kinds. Builtin plant is mill+inspect only. `foundry.json` `"plant": "@0xray/blip"` (alias `"blip"`) fastens whatever that mill package declares. Organs (looker, vibe, mixer) live in the mill; adding one is a mill publish, not an xray catalog PR. Nested mill `@0xray/foundry@0.1.12`. CLI shims `foundry blip|sound` still exist on foundry 0.1.11+. 0xray stays 4.0.15.
|
|
24
33
|
- **Blip/sound mills moved to `@0xray/blip`** — 4.44s drawers (render, kapow, vibe, wow-pass listen/cel, sound bed/mixer, looker) live in the blip product mill. `@0xray/foundry@0.1.11` shims `foundry blip|sound` including `blip look`. Railway plant consumes the product mill, not a hand-vendored copy. 0xray stays 4.0.15.
|
|
25
34
|
|
|
26
35
|
---
|
|
27
36
|
|
|
37
|
+
## [4.0.17] - 2026-09-22
|
|
38
|
+
|
|
39
|
+
### 🔄 Changes
|
|
40
|
+
|
|
41
|
+
### ✨ Features
|
|
42
|
+
- feat: write session-*.json from Cursor heat when HEAD moves (363186a02)
|
|
43
|
+
- feat: persist overlay OP-PROC on repertoire-working after compact heat (8c55793e8)
|
|
44
|
+
|
|
45
|
+
### 📚 Documentation
|
|
46
|
+
- docs: ASCII gather/store/retrieve trees for Station memory (d8f2f3b89)
|
|
47
|
+
- docs: Station is always the pickup memory after compact (bec431c61)
|
|
48
|
+
- docs: Groover is not Repertoire — do not grow dest from Groover field (203147d19)
|
|
49
|
+
- docs: Subject review. Fix n ship. — dest review then leftover then D (ad4ffa7f6)
|
|
50
|
+
- docs: stop /loop when the card and WAVEBOARD are idle (60f0fd53f)
|
|
51
|
+
- docs: clean ticks every cycle — rewrite /loop prompt, do not act on stale metal (e71a19559)
|
|
52
|
+
|
|
53
|
+
### 🧪 Tests
|
|
54
|
+
- test: match Compact cut wording on Station-as-memory (7f9a4f091)
|
|
55
|
+
|
|
56
|
+
### 🔎 Other Changes
|
|
57
|
+
- Merge main — include weekday-mirror #97 in 4.0.17 tarball (86fdec7ce)
|
|
58
|
+
- Merge pull request #97 from 0xRayAI/cursor/weekday-mirror-dist-voice-9b4d (d0853aad1)
|
|
59
|
+
- docs(grok-bot): weekday mirror for Dist reply voice and CoS wake hygiene (f91233b59)
|
|
60
|
+
- Merge branch 'cursor/clean-ticks-fbe0' — clean ticks every cycle (12dd06362)
|
|
61
|
+
- Merge branch 'cursor/leftover-validate-d-fbe0' — release 4.0.16 live, leftover validate gone (8fa037212)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## [4.0.16] - 2026-09-20
|
|
66
|
+
|
|
67
|
+
### 🔄 Changes
|
|
68
|
+
|
|
69
|
+
### 🐛 Bug Fixes
|
|
70
|
+
- fix: type validate pack paths — no untyped mjs import in src/cli (ff936b276)
|
|
71
|
+
- fix: cleanse npx 0xray validate — wear check, not leftover init.sh (741cca410)
|
|
72
|
+
|
|
73
|
+
### 📚 Documentation
|
|
74
|
+
- docs: new-arch reflection after wading part of the swamp (12d1fff69)
|
|
75
|
+
- docs: persist peer-boot seat bc-49e17122 after four-line wear (5c8be98bb)
|
|
76
|
+
- docs: encode registry install as live-verify after npm view (bd32bd1c4)
|
|
77
|
+
- docs: encode npm CLI OTP latch — lead publishes, no subagent (d600d12a2)
|
|
78
|
+
|
|
79
|
+
### 🔎 Other Changes
|
|
80
|
+
- Merge branch 'cursor/npm-cli-otp-fbe0' — OTP latch, two-path live-verify, cleanse leftover validate (568797952)
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
28
84
|
## [4.0.15] - 2026-09-17
|
|
29
85
|
|
|
30
86
|
### ✨ Features
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer wear check — not leftover OpenCode init.sh.
|
|
3
|
+
*
|
|
4
|
+
* `npx 0xray validate` used to shell `.opencode/init.sh` (macOS `md5`,
|
|
5
|
+
* `.opencode/enforcer-config.json`). That is factory boot theater, not wear.
|
|
6
|
+
*/
|
|
7
|
+
/** Same paths as `REQUIRED_PACK_PATHS` in assert-packed-dist-cli.mjs — typed here so src/cli does not import untyped .mjs. */
|
|
8
|
+
export declare const VALIDATE_PACK_PATHS: readonly string[];
|
|
9
|
+
export declare const CONSUMER_WEAR_PATHS: readonly ["AGENTS.md", ".mcp.json", ".xray/codex.json", ".xray/features.json", ".cursor/hooks.json"];
|
|
10
|
+
export declare const MILL_PLANT_SKILLS: readonly ["mill", "inspect"];
|
|
11
|
+
export type ValidateCheck = {
|
|
12
|
+
id: string;
|
|
13
|
+
ok: boolean;
|
|
14
|
+
detail: string;
|
|
15
|
+
};
|
|
16
|
+
export type ValidateReport = {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
consumer: boolean;
|
|
19
|
+
packageRoot: string;
|
|
20
|
+
checks: ValidateCheck[];
|
|
21
|
+
};
|
|
22
|
+
export declare function resolveInstalledPackageRoot(cwd: string): string | null;
|
|
23
|
+
export declare function isConsumerInstall(cwd: string, packageRoot: string): boolean;
|
|
24
|
+
export declare function collectValidateReport(cwd: string): ValidateReport;
|
|
25
|
+
export declare function validateCommand(cwd?: string): Promise<ValidateReport>;
|
|
26
|
+
export default validateCommand;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer wear check — not leftover OpenCode init.sh.
|
|
3
|
+
*
|
|
4
|
+
* `npx 0xray validate` used to shell `.opencode/init.sh` (macOS `md5`,
|
|
5
|
+
* `.opencode/enforcer-config.json`). That is factory boot theater, not wear.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { frameworkLogger } from "../../core/framework-logger.js";
|
|
10
|
+
/** Same paths as `REQUIRED_PACK_PATHS` in assert-packed-dist-cli.mjs — typed here so src/cli does not import untyped .mjs. */
|
|
11
|
+
export const VALIDATE_PACK_PATHS = [
|
|
12
|
+
"dist/cli/index.js",
|
|
13
|
+
"dist/mcps/orchestrator.server.js",
|
|
14
|
+
"dist/integrations/grok/hooks/session-start.js",
|
|
15
|
+
"dist/integrations/cursor/hooks/hooks.json",
|
|
16
|
+
"dist/integrations/cursor/hooks/pre-tool-use.js",
|
|
17
|
+
"dist/integrations/cursor/hooks/pre-compact.js",
|
|
18
|
+
"dist/integrations/cursor/hooks/after-file-edit.js",
|
|
19
|
+
"dist/integrations/cursor/hooks/cursor-hook-utils.js",
|
|
20
|
+
"dist/integrations/cursor/hooks/cursor-usage-receipt.js",
|
|
21
|
+
];
|
|
22
|
+
export const CONSUMER_WEAR_PATHS = [
|
|
23
|
+
"AGENTS.md",
|
|
24
|
+
".mcp.json",
|
|
25
|
+
".xray/codex.json",
|
|
26
|
+
".xray/features.json",
|
|
27
|
+
".cursor/hooks.json",
|
|
28
|
+
];
|
|
29
|
+
export const MILL_PLANT_SKILLS = ["mill", "inspect"];
|
|
30
|
+
export function resolveInstalledPackageRoot(cwd) {
|
|
31
|
+
const nested = join(cwd, "node_modules", "0xray");
|
|
32
|
+
if (existsSync(join(nested, "package.json"))) {
|
|
33
|
+
return nested;
|
|
34
|
+
}
|
|
35
|
+
const pkgPath = join(cwd, "package.json");
|
|
36
|
+
if (!existsSync(pkgPath)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
41
|
+
if (pkg.name === "0xray") {
|
|
42
|
+
return cwd;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
export function isConsumerInstall(cwd, packageRoot) {
|
|
51
|
+
return packageRoot !== cwd;
|
|
52
|
+
}
|
|
53
|
+
function checkPath(root, rel) {
|
|
54
|
+
return existsSync(join(root, rel));
|
|
55
|
+
}
|
|
56
|
+
function stationDurableHoldsNpm(cwd) {
|
|
57
|
+
const dest = join(cwd, ".xray", "state", "STATION.md");
|
|
58
|
+
if (!existsSync(dest))
|
|
59
|
+
return false;
|
|
60
|
+
try {
|
|
61
|
+
return /\bhold\s+npm\b/i.test(readFileSync(dest, "utf8"));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function collectValidateReport(cwd) {
|
|
68
|
+
const packageRoot = resolveInstalledPackageRoot(cwd);
|
|
69
|
+
const checks = [];
|
|
70
|
+
if (!packageRoot) {
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
consumer: false,
|
|
74
|
+
packageRoot: cwd,
|
|
75
|
+
checks: [
|
|
76
|
+
{
|
|
77
|
+
id: "package",
|
|
78
|
+
ok: false,
|
|
79
|
+
detail: "0xray package not found in cwd or node_modules/0xray",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const consumer = isConsumerInstall(cwd, packageRoot);
|
|
85
|
+
checks.push({
|
|
86
|
+
id: "package",
|
|
87
|
+
ok: true,
|
|
88
|
+
detail: consumer ? `consumer node_modules/0xray at ${packageRoot}` : `exo dogfood at ${packageRoot}`,
|
|
89
|
+
});
|
|
90
|
+
const missingPack = VALIDATE_PACK_PATHS.filter((p) => !checkPath(packageRoot, p));
|
|
91
|
+
checks.push({
|
|
92
|
+
id: "pack-paths",
|
|
93
|
+
ok: missingPack.length === 0,
|
|
94
|
+
detail: missingPack.length === 0
|
|
95
|
+
? `REQUIRED_PACK_PATHS ${VALIDATE_PACK_PATHS.length}/${VALIDATE_PACK_PATHS.length}`
|
|
96
|
+
: `missing ${missingPack.join(", ")}`,
|
|
97
|
+
});
|
|
98
|
+
if (consumer) {
|
|
99
|
+
const missingWear = CONSUMER_WEAR_PATHS.filter((p) => !checkPath(cwd, p));
|
|
100
|
+
checks.push({
|
|
101
|
+
id: "consumer-wear",
|
|
102
|
+
ok: missingWear.length === 0,
|
|
103
|
+
detail: missingWear.length === 0
|
|
104
|
+
? "AGENTS.md · .mcp.json · .xray · .cursor/hooks.json"
|
|
105
|
+
: `missing ${missingWear.join(", ")}`,
|
|
106
|
+
});
|
|
107
|
+
const millRoot = join(cwd, ".opencode", "skills");
|
|
108
|
+
const missingMill = MILL_PLANT_SKILLS.filter((name) => !existsSync(join(millRoot, name, "SKILL.md")));
|
|
109
|
+
checks.push({
|
|
110
|
+
id: "mill-plant",
|
|
111
|
+
ok: missingMill.length === 0,
|
|
112
|
+
detail: missingMill.length === 0
|
|
113
|
+
? "mill + inspect fastened"
|
|
114
|
+
: `missing plant ${missingMill.join(", ")}`,
|
|
115
|
+
});
|
|
116
|
+
const repertoire = existsSync(join(cwd, "node_modules", "@0xray", "repertoire", "package.json")) ||
|
|
117
|
+
existsSync(join(packageRoot, "vendor", "@0xray", "repertoire", "package.json"));
|
|
118
|
+
checks.push({
|
|
119
|
+
id: "repertoire",
|
|
120
|
+
ok: repertoire,
|
|
121
|
+
detail: repertoire ? "repertoire organ on disk" : "missing @0xray/repertoire",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const stationPath = join(cwd, ".xray", "state", "STATION.md");
|
|
125
|
+
if (existsSync(stationPath)) {
|
|
126
|
+
const staleHold = stationDurableHoldsNpm(cwd);
|
|
127
|
+
checks.push({
|
|
128
|
+
id: "durable-hold",
|
|
129
|
+
ok: !staleHold,
|
|
130
|
+
detail: staleHold
|
|
131
|
+
? "STATION Durable still says Hold npm — heat should have stripped it"
|
|
132
|
+
: "Durable has no Hold npm",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
ok: checks.every((c) => c.ok),
|
|
137
|
+
consumer,
|
|
138
|
+
packageRoot,
|
|
139
|
+
checks,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
export async function validateCommand(cwd = process.cwd()) {
|
|
143
|
+
frameworkLogger.log("cli", "validate-start", "info", { cwd });
|
|
144
|
+
const report = collectValidateReport(cwd);
|
|
145
|
+
for (const check of report.checks) {
|
|
146
|
+
const mark = check.ok ? "✅" : "❌";
|
|
147
|
+
console.log(`${mark} ${check.id}: ${check.detail}`);
|
|
148
|
+
}
|
|
149
|
+
if (report.ok) {
|
|
150
|
+
console.log("");
|
|
151
|
+
console.log("🎉 Wear checks passed. This is not leftover init.sh.");
|
|
152
|
+
frameworkLogger.log("cli", "validate-success", "info", { consumer: report.consumer });
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.log("");
|
|
156
|
+
console.log("❌ Validation failed. Pack/wear miss — not a Mac init.sh leftover.");
|
|
157
|
+
frameworkLogger.log("cli", "validate-fail", "error", {
|
|
158
|
+
consumer: report.consumer,
|
|
159
|
+
failed: report.checks.filter((c) => !c.ok).map((c) => c.id),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return report;
|
|
163
|
+
}
|
|
164
|
+
export default validateCommand;
|
package/dist/cli/index.js
CHANGED
|
@@ -99,23 +99,15 @@ program
|
|
|
99
99
|
});
|
|
100
100
|
program
|
|
101
101
|
.command("validate")
|
|
102
|
-
.description("Validate
|
|
102
|
+
.description("Validate consumer wear (pack paths, Cursor hooks, mill plant) — not leftover init.sh")
|
|
103
103
|
.action(async () => {
|
|
104
104
|
console.log("🔬 xray CLI: Validating installation...");
|
|
105
105
|
try {
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
if (!fs.existsSync(initScript)) {
|
|
110
|
-
console.error("❌ Validation failed: init script not found:", initScript);
|
|
106
|
+
const { validateCommand } = await import("./commands/validate.js");
|
|
107
|
+
const report = await validateCommand(process.cwd());
|
|
108
|
+
if (!report.ok) {
|
|
111
109
|
process.exit(1);
|
|
112
110
|
}
|
|
113
|
-
// SECURITY: Validate script path before execution
|
|
114
|
-
validateScriptPath(initScript, "init script");
|
|
115
|
-
execSync(`bash "${initScript}"`, {
|
|
116
|
-
stdio: "inherit",
|
|
117
|
-
cwd: process.cwd(),
|
|
118
|
-
});
|
|
119
111
|
}
|
|
120
112
|
catch (error) {
|
|
121
113
|
console.error("❌ Validation failed:", error instanceof Error ? error.message : String(error));
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* sessionStart is unavailable on managed cloud; first preToolUse / afterFileEdit boots.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
7
8
|
import fs from 'fs';
|
|
8
9
|
import path from 'path';
|
|
9
10
|
import {
|
|
@@ -25,6 +26,7 @@ import { appendHookActivity } from '../../grok/hooks/grok-hook-activity.js';
|
|
|
25
26
|
import {
|
|
26
27
|
buildRepertoireResume,
|
|
27
28
|
readGitBrief,
|
|
29
|
+
stationDurableHoldsNpm,
|
|
28
30
|
} from '../../hooks/station-hook-runtime.mjs';
|
|
29
31
|
|
|
30
32
|
export const CURSOR_HOST = 'cursor';
|
|
@@ -147,15 +149,118 @@ export function cursorBootNeedsRefresh(existing, root) {
|
|
|
147
149
|
if (liveGit && liveGit.head && bootHead !== liveGit.head) return true;
|
|
148
150
|
const liveResume = buildRepertoireResume(root);
|
|
149
151
|
if (liveResume && existing.repertoireResume && liveResume !== existing.repertoireResume) return true;
|
|
152
|
+
if (stationDurableHoldsNpm(root)) return true;
|
|
150
153
|
return false;
|
|
151
154
|
}
|
|
152
155
|
|
|
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
|
+
|
|
153
255
|
export function ensureCursorSessionBoot(root, source = '0xray/cursor-pre-tool-use-boot', extra = {}) {
|
|
154
256
|
const bootPath = sessionBootPath(root);
|
|
155
257
|
if (fs.existsSync(bootPath)) {
|
|
156
258
|
try {
|
|
157
259
|
const existing = JSON.parse(fs.readFileSync(bootPath, 'utf8'));
|
|
158
|
-
if (!cursorBootNeedsRefresh(existing, root))
|
|
260
|
+
if (!cursorBootNeedsRefresh(existing, root)) {
|
|
261
|
+
maybeCaptureSessionOnHeadMove(root);
|
|
262
|
+
return bootPath;
|
|
263
|
+
}
|
|
159
264
|
} catch {
|
|
160
265
|
/* rewrite corrupt or host-mismatched boot */
|
|
161
266
|
}
|
|
@@ -164,7 +269,9 @@ export function ensureCursorSessionBoot(root, source = '0xray/cursor-pre-tool-us
|
|
|
164
269
|
host: CURSOR_HOST,
|
|
165
270
|
...extra,
|
|
166
271
|
});
|
|
167
|
-
|
|
272
|
+
const written = writeSessionBoot(root, payload) || bootPath;
|
|
273
|
+
maybeCaptureSessionOnHeadMove(root);
|
|
274
|
+
return written;
|
|
168
275
|
}
|
|
169
276
|
|
|
170
277
|
export function writeCursorPrecompactReceipt(root, fields) {
|
|
@@ -203,6 +203,25 @@ function stationSafeSignals(names) {
|
|
|
203
203
|
return out;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
/** Overlay + factory names on dest. OP-PROC lives here — not on Station.md. */
|
|
207
|
+
function readOpProcNames(root) {
|
|
208
|
+
const dest = join(root, ".xray", "state", "repertoire", "curated_signals.json");
|
|
209
|
+
if (!existsSync(dest)) return [];
|
|
210
|
+
try {
|
|
211
|
+
const data = JSON.parse(readFileSync(dest, "utf8"));
|
|
212
|
+
if (!Array.isArray(data.signals)) return [];
|
|
213
|
+
const names = [];
|
|
214
|
+
for (const signal of data.signals) {
|
|
215
|
+
const name = String(signal && signal.name ? signal.name : "").trim();
|
|
216
|
+
if (!name || name.toLowerCase().startsWith("bedrock-")) continue;
|
|
217
|
+
names.push(name);
|
|
218
|
+
}
|
|
219
|
+
return names;
|
|
220
|
+
} catch {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
206
225
|
function persistRepertoireWorking(root, snapshot) {
|
|
207
226
|
try {
|
|
208
227
|
mkdirSync(join(root, ".xray", "state"), { recursive: true });
|
|
@@ -340,6 +359,8 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
|
|
|
340
359
|
repertoireResume,
|
|
341
360
|
};
|
|
342
361
|
if (matchedSignals.length) workingSnapshot.matchedSignals = matchedSignals;
|
|
362
|
+
const opProcNames = readOpProcNames(root);
|
|
363
|
+
if (opProcNames.length) workingSnapshot.opProcNames = opProcNames;
|
|
343
364
|
const working = persistRepertoireWorking(root, workingSnapshot);
|
|
344
365
|
if (isCompactHook(extra) && matchedSignals.length) {
|
|
345
366
|
ingestCompactFeedbackSync(
|
|
@@ -383,6 +404,23 @@ function isDurableStationHeading(line) {
|
|
|
383
404
|
return /^##\s+(durable|seed)\b/i.test(String(line || "").trim());
|
|
384
405
|
}
|
|
385
406
|
|
|
407
|
+
/** Durable is identity. Hold/ship lives in mill-gate — not on the card. */
|
|
408
|
+
function isHoldNpmLine(line) {
|
|
409
|
+
return /\bhold\s+npm\b/i.test(String(line || "").trim());
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function stripHoldNpm(line) {
|
|
413
|
+
return String(line || "")
|
|
414
|
+
.replace(/\s*hold\s+npm\.?/gi, "")
|
|
415
|
+
.replace(/[ \t]+$/g, "");
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function stationDurableHoldsNpm(root) {
|
|
419
|
+
return readExistingStationMarkdown(root)
|
|
420
|
+
.split(/\r?\n/)
|
|
421
|
+
.some((line) => isHoldNpmLine(line));
|
|
422
|
+
}
|
|
423
|
+
|
|
386
424
|
function isStockStationLine(line) {
|
|
387
425
|
const trimmed = String(line || "").trim();
|
|
388
426
|
if (!trimmed) return true;
|
|
@@ -411,12 +449,14 @@ function extractPreservedStationLines(existing) {
|
|
|
411
449
|
if (otherHeading || isStockStationLine(line)) {
|
|
412
450
|
inDurable = false;
|
|
413
451
|
} else {
|
|
414
|
-
|
|
452
|
+
const durableLine = stripHoldNpm(line);
|
|
453
|
+
if (durableLine.trim()) preserved.push(durableLine);
|
|
415
454
|
continue;
|
|
416
455
|
}
|
|
417
456
|
}
|
|
418
457
|
if (isStockStationLine(line)) continue;
|
|
419
|
-
|
|
458
|
+
const kept = stripHoldNpm(line);
|
|
459
|
+
if (kept.trim()) preserved.push(kept);
|
|
420
460
|
}
|
|
421
461
|
while (preserved.length && !preserved[0].trim()) preserved.shift();
|
|
422
462
|
while (preserved.length && !preserved[preserved.length - 1].trim()) preserved.pop();
|
|
@@ -500,10 +540,13 @@ module.exports = {
|
|
|
500
540
|
isLeftoverMemoryRoutingOff,
|
|
501
541
|
persistRepertoireWorking,
|
|
502
542
|
readRepertoireWorking,
|
|
543
|
+
readOpProcNames,
|
|
503
544
|
formatWorkingLine,
|
|
504
545
|
applyStationHeat,
|
|
505
546
|
extractPreservedStationLines,
|
|
506
547
|
mergeStationMarkdown,
|
|
507
548
|
formatStationMarkdown,
|
|
508
549
|
writeStationMarkdown,
|
|
550
|
+
isHoldNpmLine,
|
|
551
|
+
stationDurableHoldsNpm,
|
|
509
552
|
};
|
|
@@ -18,9 +18,12 @@ 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;
|
|
21
22
|
export const formatWorkingLine = impl.formatWorkingLine;
|
|
22
23
|
export const applyStationHeat = impl.applyStationHeat;
|
|
23
24
|
export const extractPreservedStationLines = impl.extractPreservedStationLines;
|
|
24
25
|
export const mergeStationMarkdown = impl.mergeStationMarkdown;
|
|
25
26
|
export const formatStationMarkdown = impl.formatStationMarkdown;
|
|
26
27
|
export const writeStationMarkdown = impl.writeStationMarkdown;
|
|
28
|
+
export const isHoldNpmLine = impl.isHoldNpmLine;
|
|
29
|
+
export const stationDurableHoldsNpm = impl.stationDurableHoldsNpm;
|
|
@@ -67,12 +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.
|
|
71
|
-
- **Publish:**
|
|
72
|
-
- **
|
|
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
|
+
- **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
|
+
- **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. **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.**
|
|
73
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`.
|
|
74
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.
|
|
75
|
-
- **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.
|
|
76
77
|
- **Board:** critic Strict comments; forge implements; lead stays main thread.
|
|
77
78
|
- **Codex 69:** rewire this skill and existing ops. Do not add a new `SKILL.md` to hold the cadence.
|
|
78
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,12 +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.
|
|
71
|
-
- **Publish:**
|
|
72
|
-
- **
|
|
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
|
+
- **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
|
+
- **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. **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.**
|
|
73
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`.
|
|
74
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.
|
|
75
|
-
- **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.
|
|
76
77
|
- **Board:** critic Strict comments; forge implements; lead stays main thread.
|
|
77
78
|
- **Codex 69:** rewire this skill and existing ops. Do not add a new `SKILL.md` to hold the cadence.
|
|
78
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.
|