0xray 4.1.0 → 4.1.1
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/.gitignore.default +4 -1
- package/.grok-plugin/plugin.json +1 -1
- package/.opencode/skills/orchestrator/SKILL.md +1 -7
- package/.xray/features.json +4 -1
- package/.xray/features.schema.json +8 -0
- package/AGENTS-consumer.md +1 -1
- package/AGENTS.md +2 -2
- package/README.md +2 -2
- package/dist/.gitignore.default +4 -1
- package/dist/AGENTS.md +2 -2
- package/dist/CHANGELOG.md +27 -1
- package/dist/README.md +2 -2
- package/dist/cli/commands/opencode-install.js +3 -9
- package/dist/cli/commands/skill-install.js +10 -5
- package/dist/cli/goggles-status.d.ts +2 -5
- package/dist/cli/goggles-status.js +5 -53
- package/dist/cli/index.js +2 -2
- package/dist/enforcement/enforcer-tools.d.ts +1 -1
- package/dist/enforcement/enforcer-tools.js +1 -1
- package/dist/integrations/cursor/README.md +1 -1
- package/dist/integrations/cursor/hooks/pre-tool-use.js +4 -0
- package/dist/integrations/grok/grok-cli.js +5 -7
- package/dist/integrations/grok/hooks/grok-hook-utils.js +4 -1
- package/dist/integrations/grok/hooks/pre-tool-use.js +4 -18
- package/dist/integrations/grok/hooks/session-start.js +4 -2
- package/dist/integrations/hermes-agent/bridge.mjs +4 -1
- package/dist/integrations/hooks/goggles-mcp.mjs +20 -196
- package/dist/integrations/hooks/goggles-pipeline.mjs +606 -616
- package/dist/integrations/hooks/goggles-planes.json +88 -40
- package/dist/integrations/hooks/plates.cjs +84 -0
- package/dist/integrations/hooks/pre-tool-speak.mjs +2 -4
- package/dist/integrations/hooks/skill-seek.mjs +115 -0
- package/dist/integrations/hooks/station-hook-runtime.cjs +4 -1
- package/dist/integrations/openclaw/hooks/pre-tool-gate-runtime.mjs +4 -4
- package/dist/integrations/openclaw/plugin/xray-pre-tool/index.js +10 -1
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/memory-routing/plates.d.ts +1 -1
- package/dist/memory-routing/plates.js +14 -0
- package/dist/nucleus/thin-dispatch.d.ts +1 -1
- package/dist/nucleus/thin-dispatch.js +1 -1
- package/dist/nucleus/work-freshness.mjs +10 -0
- package/dist/plugin/xray-codex-injection.js +3 -1
- package/dist/skills/orchestrator/SKILL.md +1 -7
- package/docs-site/docs/plates/asides.md +35 -0
- package/docs-site/docs/plates/confer.md +35 -0
- package/docs-site/docs/plates/consumer-mcp.md +35 -0
- package/docs-site/docs/plates/delegation-gate.md +35 -0
- package/docs-site/docs/plates/dynamo.md +35 -0
- package/docs-site/docs/plates/foundry.md +35 -0
- package/docs-site/docs/plates/goggles.md +8 -8
- package/docs-site/docs/plates/ground.md +35 -0
- package/docs-site/docs/plates/hermes.md +35 -0
- package/docs-site/docs/plates/index.md +12 -5
- package/docs-site/docs/plates/inference-cycle.md +35 -0
- package/docs-site/docs/plates/lens-gate.md +6 -6
- package/docs-site/docs/plates/openclaw.md +35 -0
- package/docs-site/docs/plates/opencode.md +35 -0
- package/docs-site/docs/plates/repertoire.md +35 -0
- package/docs-site/docs/plates/synthesis.md +35 -0
- package/docs-site/docs/plates/temperament.md +35 -0
- package/docs-site/docs/plates/trail-state.md +1 -1
- package/package.json +1 -1
- package/scripts/foundry/README.md +3 -1
- package/scripts/foundry/mint-suit.cjs +120 -2
- package/scripts/foundry/plant/skills/mill/SKILL.md +1 -1
- package/scripts/foundry/reconcile-version.mjs +10 -3
- package/scripts/foundry/release.mjs +1 -1
- package/scripts/foundry/validate-release-docs.mjs +2 -2
- package/scripts/foundry/version-manager.mjs +12 -4
- package/scripts/node/install-bridges.cjs +63 -34
- package/scripts/node/pack-tmp-suit-proof.mjs +23 -4
- package/src/integrations/openclaw/plugin/xray-pre-tool/index.js +10 -1
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/skills/orchestrator/SKILL.md +1 -7
- package/xray/features.json +4 -1
- package/xray/features.schema.json +8 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Repertoire
|
|
3
|
+
sidebar_label: Repertoire
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Repertoire
|
|
7
|
+
|
|
8
|
+
The memory organ matches named laws.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
┌────────────────────────────────────────────────────────────┐
|
|
12
|
+
│ INPUT LAYER │
|
|
13
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
14
|
+
│ │ a task and its signals │ │
|
|
15
|
+
│ └──────────────────────────┬─────────────────────────┘ │
|
|
16
|
+
└──────────────────────────────┼─────────────────────────────┘
|
|
17
|
+
v
|
|
18
|
+
┌────────────┼───────────────────────────────────────────────┐
|
|
19
|
+
│ PROCESSING LAYER │
|
|
20
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
21
|
+
│ │ match the worn laws │ │
|
|
22
|
+
│ └──────────────────────────┬─────────────────────────┘ │
|
|
23
|
+
└──────────────────────────────┼─────────────────────────────┘
|
|
24
|
+
v
|
|
25
|
+
┌──────────────────────────────┼─────────────────────────────┐
|
|
26
|
+
│ OUTPUT LAYER v │
|
|
27
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
28
|
+
│ │ one law, or none │ │
|
|
29
|
+
│ └────────────────────────────────────────────────────┘ │
|
|
30
|
+
└────────────────────────────────────────────────────────────┘
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The code is `src/memory-routing`. The worn package is `@0xray/repertoire` 0.2.9. Station is the wake ticket. This organ is the judgment. Do not pin 0.1.8.
|
|
34
|
+
|
|
35
|
+
stamped · 0xray 4.1.0
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Synthesis
|
|
3
|
+
sidebar_label: Synthesis
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Synthesis
|
|
7
|
+
|
|
8
|
+
The reflect checkpoint for this checkout.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
┌────────────────────────────────────────────────────────────┐
|
|
12
|
+
│ INPUT LAYER │
|
|
13
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
14
|
+
│ │ a gate, a turn, or a finished todo │ │
|
|
15
|
+
│ └──────────────────────────┬─────────────────────────┘ │
|
|
16
|
+
└──────────────────────────────┼─────────────────────────────┘
|
|
17
|
+
v
|
|
18
|
+
┌────────────┼───────────────────────────────────────────────┐
|
|
19
|
+
│ PROCESSING LAYER │
|
|
20
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
21
|
+
│ │ the checkpoint when enabled │ │
|
|
22
|
+
│ └──────────────────────────┬─────────────────────────┘ │
|
|
23
|
+
└──────────────────────────────┼─────────────────────────────┘
|
|
24
|
+
v
|
|
25
|
+
┌──────────────────────────────┼─────────────────────────────┐
|
|
26
|
+
│ OUTPUT LAYER v │
|
|
27
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
28
|
+
│ │ due or not due │ │
|
|
29
|
+
│ └────────────────────────────────────────────────────┘ │
|
|
30
|
+
└────────────────────────────────────────────────────────────┘
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The file is `src/nucleus/synthesis.ts`. The checkpoint is `.xray/state/synthesis-checkpoint.json`. While `synthesis.enabled` is false, a slice is not written and it does not come due.
|
|
34
|
+
|
|
35
|
+
stamped · 0xray 4.1.0
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Temperament
|
|
3
|
+
sidebar_label: Temperament
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Temperament
|
|
7
|
+
|
|
8
|
+
Ceremony scales by host.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
┌────────────────────────────────────────────────────────────┐
|
|
12
|
+
│ INPUT LAYER │
|
|
13
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
14
|
+
│ │ a host │ │
|
|
15
|
+
│ └──────────────────────────┬─────────────────────────┘ │
|
|
16
|
+
└──────────────────────────────┼─────────────────────────────┘
|
|
17
|
+
v
|
|
18
|
+
┌────────────┼───────────────────────────────────────────────┐
|
|
19
|
+
│ PROCESSING LAYER │
|
|
20
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
21
|
+
│ │ read suit_temperament │ │
|
|
22
|
+
│ └──────────────────────────┬─────────────────────────┘ │
|
|
23
|
+
└──────────────────────────────┼─────────────────────────────┘
|
|
24
|
+
v
|
|
25
|
+
┌──────────────────────────────┼─────────────────────────────┐
|
|
26
|
+
│ OUTPUT LAYER v │
|
|
27
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
28
|
+
│ │ frontier warns, guided denies │ │
|
|
29
|
+
│ └────────────────────────────────────────────────────┘ │
|
|
30
|
+
└────────────────────────────────────────────────────────────┘
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The module is `src/nucleus/suit-temperament.ts`. A missing `suit_temperament` stays guided. Frontier warns when a spawn has no plan. Guided and strict deny that spawn. Codex stays on. The guide is `docs-site/docs/guides/v3-temperament.md`.
|
|
34
|
+
|
|
35
|
+
stamped · 0xray 4.1.0
|
|
@@ -30,6 +30,6 @@ The suit writes its trail under .xray.
|
|
|
30
30
|
└────────────────────────────────────────────────────────────┘
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Session boot
|
|
33
|
+
Session boot and the station card are state. `.xray/state/LENS.md` is the plane cards. A named plane whose Entry and Exit are missing is written there, and later passes read that card. Commit `.xray/state/LENS.md`. Do not commit the rest of `.xray/state`.
|
|
34
34
|
|
|
35
35
|
stamped · 0xray 4.1.0
|
package/package.json
CHANGED
|
@@ -37,6 +37,8 @@ npx @0xray/foundry hooks
|
|
|
37
37
|
|
|
38
38
|
`release` without `--dry-run` requires `--i-mean-it` or `FOUNDRY_RELEASE=1`. It is a bump/commit/push/publish bot for the **milled cwd**, not a casual command.
|
|
39
39
|
|
|
40
|
+
On the 0xray repo, `reconcile --apply` and `stamp` refuse unless `release.mjs` called them. `npm run version:sync` refuses here too. Run `npx @0xray/foundry release [patch|minor|major] --i-mean-it`. That command publishes. A docs fix does not.
|
|
41
|
+
|
|
40
42
|
`FOUNDRY_ROOT` overrides cwd (the repo being milled).
|
|
41
43
|
|
|
42
44
|
`gate` is build + test + docs-check. `--skip-docs` fails the gate. `--prove-published` is Gate D and does not publish: npm lists the version, the annotated tag points at HEAD, and the GitHub Release page exists. A local gate pass is not Gate D. `docs-build` runs Docusaurus on the 0xray exo (`docs-site/`); stranger mills skip. `inspect` runs the six mill checks (diff, plant vs worn, receipt, CI, live tarball GET, isolated HOME) plus observational harness (Cursor/Grok hooks bound, Repertoire fastened, Station present). `--go` emits a shop-style forge GO receipt; `--require-harness=bare|suited|partial` turns that observation into a gate. Mint fails on a costume dump. Isolated HOME skips machine `~/.grok`. Shared HOME last-mile dest is project `.grok/plugins/0xray` (no last-wins machine plugin). `ci` reports GitHub Actions (no auto-push). `hooks` installs git pre/post hooks. GitHub `0xRay CI/CD` is the mill gate on `main`. `Deploy Docs` is the mill Pages put.
|
|
@@ -60,7 +62,7 @@ After mill plant is fastened, overlay their plant (defaults; remap in `foundry.j
|
|
|
60
62
|
|
|
61
63
|
**Mill-fill is law:** their keys/files win; mill names they did not plant stay unless they overlay the same name. JSON facets always merge. Skill overlay is project dirs on every TUI floor (OpenCode, Grok, Hermes, OpenClaw). YML agents stay OpenCode. CLI re-wear calls mint **once** after mill copies. Isolated HOME is passwd home, not `os.homedir()`. CI report is `.xray/foundry-ci-report.json` (`.opencode/logs` shim).
|
|
62
64
|
|
|
63
|
-
**Default mill plant:** `mill` + `inspect` (inspect AI work). Fasten is **foundry-plant/0**: builtin plant is mill+inspect only (files from this package `plant/`). Fastens a suit, not an empty one.
|
|
65
|
+
**Default mill plant:** `mill` + `inspect` (inspect AI work). Fasten is **foundry-plant/0**: builtin plant is mill+inspect only (files from this package `plant/`). Fastens a suit, not an empty one. The mill suit also wears every catalog skill and every role file. A blip or sound seat fastens that plant only. A name outside the fastened set still fails inspect. **Repertoire is preferred** (vendored, dest = laws). PPE stays worn.
|
|
64
66
|
|
|
65
67
|
**foundry-plant/0:** `foundry.json` `"plant": "@0xray/blip"` (aliases `"plant": "blip"` / `"plant": "sound"`) fastens whatever that mill package declares and ships in its own `plant/`. Organs (looker, vibe, mixer) live in the mill. Adding an organ is a mill publish, not a foundry catalog PR. CLI shims `foundry blip|sound` still exist on foundry 0.1.11+; fasten is the new cut. Foundry **0.1.12**.
|
|
66
68
|
|
|
@@ -114,12 +114,91 @@ function loadFoundryParams(targetDir) {
|
|
|
114
114
|
return params;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
/**
|
|
117
|
+
/** Exo repo, or foundry.json costume true. This flag does not gate catalog skills. */
|
|
118
118
|
function wantsCostume(targetDir) {
|
|
119
119
|
if (isExoRepo(targetDir)) return true;
|
|
120
120
|
return loadFoundryExtra(targetDir).costume === true;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
function suitPackageRoot(start) {
|
|
124
|
+
let dir = start;
|
|
125
|
+
for (let i = 0; i < 4 && dir; i += 1) {
|
|
126
|
+
if (
|
|
127
|
+
fs.existsSync(path.join(dir, "src", "skills"))
|
|
128
|
+
|| fs.existsSync(path.join(dir, "dist", "skills"))
|
|
129
|
+
|| fs.existsSync(path.join(dir, "src", "opencode", "agents"))
|
|
130
|
+
) return dir;
|
|
131
|
+
const parent = path.dirname(dir);
|
|
132
|
+
if (parent === dir) break;
|
|
133
|
+
dir = parent;
|
|
134
|
+
}
|
|
135
|
+
return start || "";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function catalogSkillDir(packageRoot) {
|
|
139
|
+
const base = suitPackageRoot(packageRoot);
|
|
140
|
+
const dirs = [path.join(base, "dist", "skills"), path.join(base, "src", "skills")];
|
|
141
|
+
return dirs.find((dir) => listSkillNamesAt(dir).length > 0) || "";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function catalogSkillNames(packageRoot) {
|
|
145
|
+
const dir = catalogSkillDir(packageRoot);
|
|
146
|
+
return dir ? listSkillNamesAt(dir) : [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function catalogAgentDirs(packageRoot) {
|
|
150
|
+
const base = suitPackageRoot(packageRoot);
|
|
151
|
+
return [
|
|
152
|
+
path.join(base, "src", "opencode", "agents"),
|
|
153
|
+
path.join(base, ".opencode", "agents"),
|
|
154
|
+
].filter((p) => fs.existsSync(p));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function catalogAgentFiles(packageRoot) {
|
|
158
|
+
const names = new Set();
|
|
159
|
+
for (const dir of catalogAgentDirs(packageRoot)) {
|
|
160
|
+
for (const file of listAgentFilesAt(dir)) names.add(file);
|
|
161
|
+
}
|
|
162
|
+
return [...names];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The mill suit wears the catalog. A blip-only or sound-only seat does not.
|
|
167
|
+
* Costume stays a separate flag and does not decide this.
|
|
168
|
+
*/
|
|
169
|
+
function suitCatalogApplies(targetDir, millPlant, packageRoot) {
|
|
170
|
+
const fromPlant = Array.isArray(millPlant?.kinds) ? millPlant.kinds : [];
|
|
171
|
+
const kinds = fromPlant.length > 0
|
|
172
|
+
? fromPlant
|
|
173
|
+
: loadFactoryPlantKinds(targetDir, packageRoot || "");
|
|
174
|
+
return kinds.includes("mill");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Every catalog skill and role file. An existing file stays. */
|
|
178
|
+
function wearCatalog(packageRoot, targetDir) {
|
|
179
|
+
const source = catalogSkillDir(packageRoot);
|
|
180
|
+
if (source) {
|
|
181
|
+
for (const dir of listProjectSkillDirs(targetDir)) {
|
|
182
|
+
for (const name of catalogSkillNames(packageRoot)) {
|
|
183
|
+
const src = path.join(source, name, "SKILL.md");
|
|
184
|
+
const dest = path.join(dir, name, "SKILL.md");
|
|
185
|
+
if (!fs.existsSync(src) || fs.existsSync(dest)) continue;
|
|
186
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
187
|
+
fs.copyFileSync(src, dest);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const agentsDest = path.join(targetDir, ".opencode", "agents");
|
|
192
|
+
for (const dir of catalogAgentDirs(packageRoot)) {
|
|
193
|
+
for (const file of listAgentFilesAt(dir)) {
|
|
194
|
+
const dest = path.join(agentsDest, file);
|
|
195
|
+
if (fs.existsSync(dest)) continue;
|
|
196
|
+
fs.mkdirSync(agentsDest, { recursive: true });
|
|
197
|
+
fs.copyFileSync(path.join(dir, file), dest);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
123
202
|
function millPlantDir(millPackageRoot) {
|
|
124
203
|
return millPlantProtocol.foundryPlantDir(millPackageRoot);
|
|
125
204
|
}
|
|
@@ -208,11 +287,21 @@ function inventoryPlantKinds(inventory) {
|
|
|
208
287
|
return millPlantProtocol.plantKindsFromInventory(inventory);
|
|
209
288
|
}
|
|
210
289
|
|
|
290
|
+
function skillDirEntry(parent, entry) {
|
|
291
|
+
if (entry.isDirectory()) return true;
|
|
292
|
+
if (!entry.isSymbolicLink()) return false;
|
|
293
|
+
try {
|
|
294
|
+
return fs.statSync(path.join(parent, entry.name)).isDirectory();
|
|
295
|
+
} catch {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
211
300
|
function listSkillNamesAt(skillsSrc) {
|
|
212
301
|
if (!isDirectory(skillsSrc)) return [];
|
|
213
302
|
const names = [];
|
|
214
303
|
for (const entry of fs.readdirSync(skillsSrc, { withFileTypes: true })) {
|
|
215
|
-
if (
|
|
304
|
+
if (entry.name.startsWith(".") || !skillDirEntry(skillsSrc, entry)) continue;
|
|
216
305
|
const skillMd = path.join(skillsSrc, entry.name, "SKILL.md");
|
|
217
306
|
if (!fs.existsSync(skillMd)) continue;
|
|
218
307
|
names.push(entry.name);
|
|
@@ -615,7 +704,10 @@ function costumeDumpExtras(targetDir, millPlant, tree) {
|
|
|
615
704
|
if (Array.isArray(spec?.agents)) plantMapAgents.push(...spec.agents);
|
|
616
705
|
}
|
|
617
706
|
}
|
|
707
|
+
const packageRoot = tree?.millPackageRoot || "";
|
|
708
|
+
const catalogOn = suitCatalogApplies(targetDir, millPlant, packageRoot);
|
|
618
709
|
const allowedSkills = new Set([
|
|
710
|
+
...(catalogOn ? catalogSkillNames(packageRoot) : []),
|
|
619
711
|
...(Array.isArray(millPlant?.skills) ? millPlant.skills : []),
|
|
620
712
|
...(Array.isArray(millPlant?.sound?.skills) ? millPlant.sound.skills : []),
|
|
621
713
|
...(Array.isArray(millPlant?.blip?.skills) ? millPlant.blip.skills : []),
|
|
@@ -627,6 +719,7 @@ function costumeDumpExtras(targetDir, millPlant, tree) {
|
|
|
627
719
|
...shopPlant,
|
|
628
720
|
]);
|
|
629
721
|
const allowedAgents = new Set([
|
|
722
|
+
...(catalogOn ? catalogAgentFiles(packageRoot) : []),
|
|
630
723
|
...(Array.isArray(millPlant?.agents) ? millPlant.agents : []),
|
|
631
724
|
...(Array.isArray(millPlant?.sound?.agents) ? millPlant.sound.agents : []),
|
|
632
725
|
...(Array.isArray(millPlant?.blip?.agents) ? millPlant.blip.agents : []),
|
|
@@ -795,12 +888,34 @@ function mintConsumerFromSsot(packageRoot, targetDir, log, tree) {
|
|
|
795
888
|
return withDna;
|
|
796
889
|
}
|
|
797
890
|
|
|
891
|
+
|
|
892
|
+
function gogglesDisabled(targetDir) {
|
|
893
|
+
const file = path.join(targetDir, ".xray", "features.json");
|
|
894
|
+
if (!fs.existsSync(file)) return false;
|
|
895
|
+
try {
|
|
896
|
+
const data = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
897
|
+
return Boolean(data && data.goggles && data.goggles.enabled === false);
|
|
898
|
+
} catch {
|
|
899
|
+
return false;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function assertGogglesOn(targetDir) {
|
|
904
|
+
if (gogglesDisabled(targetDir)) {
|
|
905
|
+
throw new Error("goggles off — the suit keeps goggles on");
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
798
909
|
function mintConsumerSuit(millPackageRoot, targetDir, log) {
|
|
910
|
+
assertGogglesOn(targetDir);
|
|
799
911
|
if (isDogfood(millPackageRoot, targetDir)) {
|
|
800
912
|
return { suit: "dogfood", skipped: true };
|
|
801
913
|
}
|
|
802
914
|
const params = loadFoundryParams(targetDir);
|
|
803
915
|
const millPlant = fastenMillPlant(millPackageRoot, targetDir, log);
|
|
916
|
+
if (suitCatalogApplies(targetDir, millPlant, millPackageRoot)) {
|
|
917
|
+
wearCatalog(millPackageRoot, targetDir);
|
|
918
|
+
}
|
|
804
919
|
const tree = overlayConsumerTree(targetDir, log, params);
|
|
805
920
|
tree.plantKinds = millPlant.kinds || loadFactoryPlantKinds(targetDir);
|
|
806
921
|
tree.millPlantSkills = millPlant.skills;
|
|
@@ -825,6 +940,7 @@ function mintConsumerSuit(millPackageRoot, targetDir, log) {
|
|
|
825
940
|
resolveInside(targetDir, params.features),
|
|
826
941
|
path.join(targetDir, ".xray", "features.json"),
|
|
827
942
|
);
|
|
943
|
+
assertGogglesOn(targetDir);
|
|
828
944
|
tree.config = overlayJsonFacet(
|
|
829
945
|
resolveInside(targetDir, params.config),
|
|
830
946
|
path.join(targetDir, ".xray", "config.json"),
|
|
@@ -866,6 +982,7 @@ module.exports = {
|
|
|
866
982
|
loadShopPlant,
|
|
867
983
|
declaredShopPlant,
|
|
868
984
|
wantsCostume,
|
|
985
|
+
suitCatalogApplies,
|
|
869
986
|
millPlantDir,
|
|
870
987
|
loadFactoryPlantKinds,
|
|
871
988
|
factoryPlantAllowlist,
|
|
@@ -884,6 +1001,7 @@ module.exports = {
|
|
|
884
1001
|
listProjectSkillDirs,
|
|
885
1002
|
mintConsumerFromSsot,
|
|
886
1003
|
mintConsumerSuit,
|
|
1004
|
+
assertGogglesOn,
|
|
887
1005
|
attachInventoryDna,
|
|
888
1006
|
inventoryDna,
|
|
889
1007
|
isDogfood,
|
|
@@ -13,7 +13,7 @@ The mill **fastens** a suit. Default mill plant is **inspect** (inspecting AI wo
|
|
|
13
13
|
|
|
14
14
|
**Factory:** **Repertoire is preferred** (vendored 0.2.9 ships on; dest = named laws). Seven generated MCP. Constitution (PPE) stays worn. Not a fifth MCP. Not 5.0.
|
|
15
15
|
|
|
16
|
-
**
|
|
16
|
+
**Skills:** the mill suit wears every catalog skill and every role file. The mill plant stays `mill` and `inspect`. A blip or sound seat fastens that plant only.
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
npx @0xray/foundry mint
|
|
@@ -20,7 +20,7 @@ import { execSync } from "child_process";
|
|
|
20
20
|
import fs from "fs";
|
|
21
21
|
import path from "path";
|
|
22
22
|
import { fileURLToPath } from "url";
|
|
23
|
-
import { readRootPackage, resolveMillRoot } from "./mill-root.mjs";
|
|
23
|
+
import { isXrayExoRepo, readRootPackage, resolveMillRoot } from "./mill-root.mjs";
|
|
24
24
|
import { packageLockVersions, syncPackageLockVersion } from "./version-manager.mjs";
|
|
25
25
|
|
|
26
26
|
const rootDir = resolveMillRoot();
|
|
@@ -103,7 +103,7 @@ function fail(msg) {
|
|
|
103
103
|
*/
|
|
104
104
|
export function publishedVersionCheckError(local, npmVersion) {
|
|
105
105
|
if (compare(local, npmVersion) < 0) {
|
|
106
|
-
return `package.json (${local}) must be >= npm (${npmVersion}).
|
|
106
|
+
return `package.json (${local}) must be >= npm (${npmVersion}). The bump is npx @0xray/foundry release patch --i-mean-it. That command publishes. A docs fix does not. Stop.`;
|
|
107
107
|
}
|
|
108
108
|
return null;
|
|
109
109
|
}
|
|
@@ -134,6 +134,13 @@ function main() {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
if (applyMode && bumpType) {
|
|
137
|
+
if (isXrayExoRepo(rootDir) && process.env.FOUNDRY_VIA_RELEASE !== "1") {
|
|
138
|
+
fail(
|
|
139
|
+
"This repo bumps only through scripts/foundry/release.mjs.\n" +
|
|
140
|
+
"Ship: node scripts/foundry/release.mjs patch --i-mean-it\n" +
|
|
141
|
+
"That command publishes. A docs fix does not. Stop.",
|
|
142
|
+
);
|
|
143
|
+
}
|
|
137
144
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
138
145
|
pkg.version = target;
|
|
139
146
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
@@ -167,7 +174,7 @@ function main() {
|
|
|
167
174
|
const lockNow = packageLockVersions(rootDir);
|
|
168
175
|
if (lockNow && (lockNow.root !== pkgVersion || lockNow.pkg !== pkgVersion)) {
|
|
169
176
|
fail(
|
|
170
|
-
`package-lock.json (${lockNow.root} / ${lockNow.pkg}) must match package.json (${pkgVersion}). The bumper
|
|
177
|
+
`package-lock.json (${lockNow.root} / ${lockNow.pkg}) must match package.json (${pkgVersion}). The bumper is npx @0xray/foundry release patch --i-mean-it. That command publishes. A docs fix does not. Stop.`,
|
|
171
178
|
);
|
|
172
179
|
}
|
|
173
180
|
|
|
@@ -410,8 +410,8 @@ function main() {
|
|
|
410
410
|
} else {
|
|
411
411
|
console.error('❌ Release docs validation FAILED:\n');
|
|
412
412
|
for (const e of result.errors) console.error(` • ${e}`);
|
|
413
|
-
console.error('\nFix docs
|
|
414
|
-
console.error('
|
|
413
|
+
console.error('\nFix the docs in the branch. On this repo the stamper runs only from scripts/foundry/release.mjs, and that command publishes. A docs fix does not publish. Stop.');
|
|
414
|
+
console.error('Re-check with: npx @0xray/foundry docs-check\n');
|
|
415
415
|
}
|
|
416
416
|
process.exit(1);
|
|
417
417
|
}
|
|
@@ -574,9 +574,16 @@ function runReleaseDocsValidation() {
|
|
|
574
574
|
|
|
575
575
|
function refuseBump(reason) {
|
|
576
576
|
process.stderr.write(`${reason}\n`);
|
|
577
|
-
process.stderr.write('
|
|
578
|
-
process.
|
|
579
|
-
|
|
577
|
+
process.stderr.write('Ship: npx @0xray/foundry release [patch|minor|major] --i-mean-it\n');
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function refuseOutsideRelease() {
|
|
582
|
+
if (!isXrayExoRepo(rootDir)) return;
|
|
583
|
+
if (process.env.FOUNDRY_VIA_RELEASE === '1') return;
|
|
584
|
+
process.stderr.write('This repo stamps only through scripts/foundry/release.mjs.\n');
|
|
585
|
+
process.stderr.write('Ship: node scripts/foundry/release.mjs patch --i-mean-it\n');
|
|
586
|
+
process.stderr.write('That command publishes. A docs fix does not. Stop.\n');
|
|
580
587
|
process.exit(1);
|
|
581
588
|
}
|
|
582
589
|
|
|
@@ -586,13 +593,14 @@ function main() {
|
|
|
586
593
|
if (args.includes('--help') || args.includes('-h')) {
|
|
587
594
|
process.stdout.write(`Current version: ${getCurrentVersion()}\n`);
|
|
588
595
|
process.stdout.write('Usage: npx @0xray/foundry stamp\n');
|
|
589
|
-
process.stdout.write('Bump is refused.
|
|
596
|
+
process.stdout.write('Bump is refused. On this repo the stamper runs only from scripts/foundry/release.mjs.\n');
|
|
590
597
|
process.stdout.write('The stamper strips every present-tense patch pin from shipped guides and shipped OP-PROC.\n');
|
|
591
598
|
process.stdout.write('It does not edit Station, NOTES, dest, or node_modules. It does not publish.\n');
|
|
592
599
|
process.exit(0);
|
|
593
600
|
}
|
|
594
601
|
|
|
595
602
|
if (args.includes('--artifacts-only')) {
|
|
603
|
+
refuseOutsideRelease();
|
|
596
604
|
updateReleaseArtifactsOnly('');
|
|
597
605
|
return;
|
|
598
606
|
}
|
|
@@ -216,7 +216,15 @@ function syncBuiltinSkills(targetSkillsDir, packageRoot) {
|
|
|
216
216
|
try {
|
|
217
217
|
if (!fs.existsSync(targetSkillsDir)) fs.mkdirSync(targetSkillsDir, { recursive: true });
|
|
218
218
|
for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
|
|
219
|
-
|
|
219
|
+
let linkedDir = false;
|
|
220
|
+
if (entry.isSymbolicLink()) {
|
|
221
|
+
try {
|
|
222
|
+
linkedDir = fs.statSync(path.join(sourceDir, entry.name)).isDirectory();
|
|
223
|
+
} catch {
|
|
224
|
+
linkedDir = false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!entry.isDirectory() && !linkedDir) continue;
|
|
220
228
|
const skillMd = path.join(sourceDir, entry.name, "SKILL.md");
|
|
221
229
|
if (!fs.existsSync(skillMd)) continue;
|
|
222
230
|
const destMd = path.join(targetSkillsDir, entry.name, "SKILL.md");
|
|
@@ -234,8 +242,45 @@ function syncBuiltinSkills(targetSkillsDir, packageRoot) {
|
|
|
234
242
|
return copied;
|
|
235
243
|
}
|
|
236
244
|
|
|
245
|
+
function plantIncludesMill(targetDir, packageRoot) {
|
|
246
|
+
const { loadFactoryPlantKinds } = require("../foundry/mint-suit.cjs");
|
|
247
|
+
return loadFactoryPlantKinds(targetDir, packageRoot).includes("mill");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function copyRoleAgentFiles(packageRoot, opencodeDest) {
|
|
251
|
+
const dirs = [
|
|
252
|
+
path.join(packageRoot, "src", "opencode", "agents"),
|
|
253
|
+
path.join(packageRoot, ".opencode", "agents"),
|
|
254
|
+
].filter((dir) => fs.existsSync(dir));
|
|
255
|
+
const destDir = path.join(opencodeDest, "agents");
|
|
256
|
+
for (const dir of dirs) {
|
|
257
|
+
let names;
|
|
258
|
+
try {
|
|
259
|
+
names = fs.readdirSync(dir);
|
|
260
|
+
} catch {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
for (const name of names) {
|
|
264
|
+
if (!name.endsWith(".yml") && !name.endsWith(".yaml")) continue;
|
|
265
|
+
const src = path.join(dir, name);
|
|
266
|
+
let st;
|
|
267
|
+
try {
|
|
268
|
+
st = fs.statSync(src);
|
|
269
|
+
} catch {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (!st.isFile()) continue;
|
|
273
|
+
const dest = path.join(destDir, name);
|
|
274
|
+
if (fs.existsSync(dest)) continue;
|
|
275
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
276
|
+
fs.copyFileSync(src, dest);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
237
281
|
function syncCostumeSkills(targetSkillsDir, packageRoot, targetDir) {
|
|
238
|
-
|
|
282
|
+
// Catalog skills belong on the mill suit. A blip or sound seat keeps its plant.
|
|
283
|
+
if (targetDir && !plantIncludesMill(targetDir, packageRoot)) return 0;
|
|
239
284
|
return syncBuiltinSkills(targetSkillsDir, packageRoot);
|
|
240
285
|
}
|
|
241
286
|
|
|
@@ -448,7 +493,7 @@ function installOpencodeBridge(targetDir, packageRoot, log) {
|
|
|
448
493
|
const opencodeSource = path.join(packageRoot, ".opencode");
|
|
449
494
|
const opencodeDest = path.join(targetDir, ".opencode");
|
|
450
495
|
const costume = wantsCostume(targetDir);
|
|
451
|
-
|
|
496
|
+
const millSuit = plantIncludesMill(targetDir, packageRoot);
|
|
452
497
|
if (costume) {
|
|
453
498
|
if (!fs.existsSync(opencodeSource)) {
|
|
454
499
|
log("opencode-bridge", "skipped", "warn", { reason: ".opencode source missing" });
|
|
@@ -460,14 +505,18 @@ function installOpencodeBridge(targetDir, packageRoot, log) {
|
|
|
460
505
|
} else {
|
|
461
506
|
log("opencode-bridge", ".opencode exists — merging skills/plugin only", "info");
|
|
462
507
|
}
|
|
463
|
-
copyOpencodePlugin(packageRoot, opencodeDest, log);
|
|
464
|
-
const copied = syncCostumeSkills(path.join(opencodeDest, "skills"), packageRoot, targetDir);
|
|
465
|
-
if (copied > 0) log("opencode-bridge", `skills synced (${copied})`, "info", { path: ".opencode/skills/" });
|
|
466
508
|
} else {
|
|
467
|
-
log("opencode-bridge", "mill plant — plugin only, not costume dump", "info");
|
|
468
509
|
fs.mkdirSync(opencodeDest, { recursive: true });
|
|
469
|
-
|
|
510
|
+
if (millSuit) {
|
|
511
|
+
copyRoleAgentFiles(packageRoot, opencodeDest);
|
|
512
|
+
log("opencode-bridge", "mill suit — role files, not the costume tree", "info");
|
|
513
|
+
} else {
|
|
514
|
+
log("opencode-bridge", "plant seat — plugin only", "info");
|
|
515
|
+
}
|
|
470
516
|
}
|
|
517
|
+
copyOpencodePlugin(packageRoot, opencodeDest, log);
|
|
518
|
+
const copied = syncCostumeSkills(path.join(opencodeDest, "skills"), packageRoot, targetDir);
|
|
519
|
+
if (copied > 0) log("opencode-bridge", `skills synced (${copied})`, "info", { path: ".opencode/skills/" });
|
|
471
520
|
|
|
472
521
|
try {
|
|
473
522
|
const count = wireOpencodeBridge(targetDir);
|
|
@@ -1947,9 +1996,11 @@ function syncSetupSkillsAndRootLinks(packageRoot, targetDir) {
|
|
|
1947
1996
|
|
|
1948
1997
|
/** A byte-identical package skill is the old mirror. A different body still throws. */
|
|
1949
1998
|
function wornSkillMatchesPackage(packageRoot, targetDir, name) {
|
|
1950
|
-
const
|
|
1951
|
-
|
|
1952
|
-
|
|
1999
|
+
const distSkills = path.join(packageRoot, "dist", "skills");
|
|
2000
|
+
const srcSkills = path.join(packageRoot, "src", "skills");
|
|
2001
|
+
const source = fs.existsSync(path.join(distSkills, name, "SKILL.md"))
|
|
2002
|
+
? distSkills
|
|
2003
|
+
: srcSkills;
|
|
1953
2004
|
const packaged = path.join(source, name, "SKILL.md");
|
|
1954
2005
|
if (!fs.existsSync(packaged)) return false;
|
|
1955
2006
|
const packagedBytes = fs.readFileSync(packaged);
|
|
@@ -1977,12 +2028,11 @@ function packageShipsConsumerSuit(packageRoot) {
|
|
|
1977
2028
|
/**
|
|
1978
2029
|
* Consumer wear files: AGENTS.md, .gitignore, repertoire link, .xray config,
|
|
1979
2030
|
* bridges, mill plant (mill + inspect), and the scripts and dist links.
|
|
1980
|
-
*
|
|
2031
|
+
* Catalog skills and role files stay worn. Costume does not strip them.
|
|
1981
2032
|
* Does not install Cursor hooks or git hooks.
|
|
1982
2033
|
* Existing user files follow the 4.0.28 guards inside those functions.
|
|
1983
2034
|
*/
|
|
1984
2035
|
|
|
1985
|
-
const ROLE_SKILL_KEEP = new Set(["mill", "inspect"]);
|
|
1986
2036
|
const ROLE_SKILL_DIRS = [
|
|
1987
2037
|
[".opencode", "skills"],
|
|
1988
2038
|
[".grok", "plugins", "0xray", "skills"],
|
|
@@ -1990,26 +2040,6 @@ const ROLE_SKILL_DIRS = [
|
|
|
1990
2040
|
[".openclaw", "skills"],
|
|
1991
2041
|
];
|
|
1992
2042
|
|
|
1993
|
-
function removeCopiedRoleSkills(packageRoot, targetDir) {
|
|
1994
|
-
const source = fs.existsSync(path.join(packageRoot, "src", "skills"))
|
|
1995
|
-
? path.join(packageRoot, "src", "skills")
|
|
1996
|
-
: path.join(packageRoot, "dist", "skills");
|
|
1997
|
-
if (!fs.existsSync(source)) return;
|
|
1998
|
-
for (const parts of ROLE_SKILL_DIRS) {
|
|
1999
|
-
const dir = path.join(targetDir, ...parts);
|
|
2000
|
-
if (!fs.existsSync(dir)) continue;
|
|
2001
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
2002
|
-
if (!entry.isDirectory() || ROLE_SKILL_KEEP.has(entry.name)) continue;
|
|
2003
|
-
const packaged = path.join(source, entry.name, "SKILL.md");
|
|
2004
|
-
const worn = path.join(dir, entry.name, "SKILL.md");
|
|
2005
|
-
if (!fs.existsSync(packaged) || !fs.existsSync(worn)) continue;
|
|
2006
|
-
const same = fs.readFileSync(packaged).equals(fs.readFileSync(worn));
|
|
2007
|
-
if (!same) continue;
|
|
2008
|
-
fs.rmSync(path.join(dir, entry.name), { recursive: true, force: true });
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
2043
|
function installConsumerProjectFiles(packageRoot, targetDir, log) {
|
|
2014
2044
|
const write = typeof log === "function" ? log : () => {};
|
|
2015
2045
|
if (!isConsumerInstall(packageRoot, targetDir) || !packageShipsConsumerSuit(packageRoot)) {
|
|
@@ -2028,7 +2058,6 @@ function installConsumerProjectFiles(packageRoot, targetDir, log) {
|
|
|
2028
2058
|
installGrokBridge(targetDir, packageRoot, write);
|
|
2029
2059
|
installHermesBridge(targetDir, packageRoot, write);
|
|
2030
2060
|
installOpenclawBridge(targetDir, packageRoot, write);
|
|
2031
|
-
removeCopiedRoleSkills(packageRoot, targetDir);
|
|
2032
2061
|
try {
|
|
2033
2062
|
mintConsumerSuit(packageRoot, targetDir, write);
|
|
2034
2063
|
} catch (err) {
|