0xray 4.0.2 → 4.0.4
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/.xray/features.json +1 -1
- package/AGENTS-consumer.md +1 -1
- package/dist/CHANGELOG.md +16 -0
- package/dist/cli/commands/foundry-mint-wear.d.ts +1 -0
- package/dist/cli/commands/foundry-mint-wear.js +5 -2
- package/dist/cli/commands/opencode-install.js +9 -3
- package/dist/cli/commands/skill-install.js +4 -0
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/package.json +1 -1
- package/scripts/foundry/CHANGELOG.md +11 -1
- package/scripts/foundry/README.md +6 -4
- package/scripts/foundry/cli.mjs +1 -1
- package/scripts/foundry/mill-root.mjs +1 -1
- package/scripts/foundry/mint-suit.cjs +120 -36
- package/scripts/foundry/mint.mjs +2 -2
- package/scripts/foundry/package.json +3 -2
- package/scripts/foundry/plant/agents/inspect.yml +5 -0
- package/scripts/foundry/plant/agents/mill.yml +5 -0
- package/scripts/foundry/plant/skills/inspect/SKILL.md +31 -0
- package/scripts/foundry/plant/skills/mill/SKILL.md +20 -0
- package/scripts/node/consumer-install-smoke.mjs +15 -0
- package/scripts/node/install-bridges.cjs +43 -29
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/xray/features.json +1 -1
package/.xray/features.json
CHANGED
package/AGENTS-consumer.md
CHANGED
|
@@ -19,7 +19,7 @@ Before other work, Read `.xray/state/STATION.md`. Compaction and host change are
|
|
|
19
19
|
`npm install 0xray` runs `install-bridges.cjs` and:
|
|
20
20
|
|
|
21
21
|
- Copies this file → **`AGENTS.md`** in your project root
|
|
22
|
-
-
|
|
22
|
+
- Fastens mill plant (`mill` + `inspect`) then overlays **your** `src/skills` (same name wins). Inspect AI work is the mill discipline. Does **not** dump 45/42 costume unless `foundry.json` `"costume": true`. Does **not** write root **`SKILLS.md`**
|
|
23
23
|
- Seeds **`.gitignore`** (from template, if absent)
|
|
24
24
|
- Deploys **`.xray/`** (`codex.json`, `features.json`, `config.json`) then overlays **your** mill SSOT (`xray/codex.json`, `xray/features.json`, `src/skills`, `src/opencode/agents`; remap with `foundry.json`)
|
|
25
25
|
- Writes **`.mcp.json`** with 7 MCPs servers (`npx -y 0xray mcp …`)
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -26,6 +26,22 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
|
|
|
26
26
|
- Mill CI report: `mill-monitor.yml` (fresh workflow ID) listens for mill CI completion. Report only.
|
|
27
27
|
- Published mill `hooks` uses the 0xray garment installer (`node_modules/0xray/scripts/hooks`), not `node_modules/@0xray/hooks`. Mill tree 0.1.4 (not a 0xray bump).
|
|
28
28
|
- Nested mill in the 0xray tarball (`scripts/foundry/`). Wear no longer needs a second `npm i @0xray/foundry` for mint-on-install. 4.0.2.
|
|
29
|
+
- Empty garment: mill plant (`mill` skill) is the hanger. Not 45/42 costume unless `foundry.json` `"costume": true`. Mill 0.1.5.
|
|
30
|
+
- Mill plant fastens **inspect** (inspect AI work) plus `mill`. Not empty. Mill 0.1.6.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## [4.0.4] - 2026-09-04
|
|
35
|
+
|
|
36
|
+
Mill **fastens** an inspect suit: mill plant is `mill` + `inspect` (inspect AI work — diffs, traces, mill receipts, CI). Not empty. Not 45/42 costume unless `foundry.json` `"costume": true`. Factory Repertoire + PPE stay. Mill **0.1.6**. Not 5.0.0.
|
|
37
|
+
|
|
38
|
+
Naming: inventory `suit` (`fastened` / `overlay` / `costume`). Not garment. Not hanger. Costume is the 45/42 dump flag only.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## [4.0.3] - 2026-09-04
|
|
43
|
+
|
|
44
|
+
Empty garment. Their plant is the suit. Mill plant (`mill` skill) is the factory discipline on the hanger. Not 45/42 costume unless `foundry.json` `"costume": true`. Factory Repertoire still enable-when-resolves. PPE stays worn. Mill **0.1.5**. Not 5.0.0.
|
|
29
45
|
|
|
30
46
|
---
|
|
31
47
|
|
|
@@ -4,10 +4,13 @@ import { fileURLToPath } from "url";
|
|
|
4
4
|
import { frameworkLogger } from "../../core/framework-logger.js";
|
|
5
5
|
const require = createRequire(import.meta.url);
|
|
6
6
|
const packageRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
7
|
+
const millSuit = require(path.join(packageRoot, "scripts/foundry/mint-suit.cjs"));
|
|
8
|
+
export function wantsCostume(targetDir) {
|
|
9
|
+
return millSuit.wantsCostume(targetDir);
|
|
10
|
+
}
|
|
7
11
|
/** Re-apply mill overlay after CLI re-wear copies mill skills. */
|
|
8
12
|
export function mintAfterWear(targetDir) {
|
|
9
|
-
|
|
10
|
-
mintConsumerSuit(packageRoot, targetDir, (component, action, status, details) => {
|
|
13
|
+
millSuit.mintConsumerSuit(packageRoot, targetDir, (component, action, status, details) => {
|
|
11
14
|
const level = status === "error" ? "error" : "info";
|
|
12
15
|
frameworkLogger.log(component, action, level, details);
|
|
13
16
|
});
|
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import { frameworkLogger } from '../../core/framework-logger.js';
|
|
5
5
|
import { syncBuiltinSkills } from './skill-install.js';
|
|
6
|
-
import { mintAfterWear } from './foundry-mint-wear.js';
|
|
6
|
+
import { mintAfterWear, wantsCostume } from './foundry-mint-wear.js';
|
|
7
7
|
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
8
8
|
const require = createRequire(import.meta.url);
|
|
9
9
|
const wiring = require(path.join(__dirname, '..', '..', '..', 'scripts', 'node', 'bridge-mcp-wiring.cjs'));
|
|
@@ -105,8 +105,14 @@ async function installForOpencode(options = {}) {
|
|
|
105
105
|
console.log('Use --force to reinstall.');
|
|
106
106
|
}
|
|
107
107
|
else {
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
if (wantsCostume(process.cwd())) {
|
|
109
|
+
copyDir(opencodeSource, opencodeDest);
|
|
110
|
+
frameworkLogger.log('opencode-integration', 'opencode-copied', 'info', { destination: opencodeDest });
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
fs.mkdirSync(opencodeDest, { recursive: true });
|
|
114
|
+
frameworkLogger.log('opencode-integration', 'mill-plant-plugin', 'info', { destination: opencodeDest });
|
|
115
|
+
}
|
|
110
116
|
const pluginSource = path.join(__dirname, '..', '..', '..', 'dist', 'plugin', 'xray-codex-injection.js');
|
|
111
117
|
const pluginDest = path.join(opencodeDest, 'plugin', 'xray-codex-injection.js');
|
|
112
118
|
if (fs.existsSync(pluginSource)) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, cpSync, rmSync, statSync, unlinkSync, copyFileSync } from "fs";
|
|
2
2
|
import { join, basename, dirname } from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
+
import { createRequire } from "module";
|
|
4
5
|
import { execSync } from "child_process";
|
|
5
6
|
function getLocalRegistryPath() {
|
|
6
7
|
return join(process.cwd(), ".xray", "skill-registry.json");
|
|
@@ -484,6 +485,9 @@ export async function skillRegistryCommand(action, args) {
|
|
|
484
485
|
// Each platform's install command calls this to copy framework skills.
|
|
485
486
|
export function syncBuiltinSkills(targetSkillsDir, packageRoot) {
|
|
486
487
|
const root = packageRoot ?? join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
488
|
+
const millSuit = createRequire(import.meta.url)(join(root, "scripts/foundry/mint-suit.cjs"));
|
|
489
|
+
if (!millSuit.wantsCostume(process.cwd()))
|
|
490
|
+
return 0;
|
|
487
491
|
const candidateDirs = [
|
|
488
492
|
join(root, "dist", "skills"),
|
|
489
493
|
join(root, "src", "skills"),
|
package/package.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.6] - 2026-09-04
|
|
4
|
+
|
|
5
|
+
Mill plant fastens an **inspect** suit: inspect AI work (diffs, traces, mill receipts, CI). Not empty. `mill` + `inspect` skills. Costume dump still opt-in.
|
|
6
|
+
|
|
7
|
+
Inventory field is `suit` (`fastened` / `overlay` / `costume` / `dogfood`). Not `garment`. `foundry.json` `"costume": true` is the 45/42 dump.
|
|
8
|
+
|
|
9
|
+
## [0.1.5] - 2026-09-04
|
|
10
|
+
|
|
11
|
+
Mill plant (`plant/skills/mill`) fastened by default, not 45/42 costume. `foundry.json` `"costume": true` restores the dump. Plant overlay still mill-fill.
|
|
12
|
+
|
|
3
13
|
## [0.1.4] - 2026-09-04
|
|
4
14
|
|
|
5
|
-
`npx @0xray/foundry hooks` on a published mill uses the worn
|
|
15
|
+
`npx @0xray/foundry hooks` on a published mill uses the worn 0xray installer
|
|
6
16
|
`node_modules/0xray/scripts/hooks/install-hooks.cjs`. Dogfood still uses
|
|
7
17
|
`scripts/hooks` next to `scripts/foundry`. Missing installer fails honestly
|
|
8
18
|
(no spawn of `node_modules/@0xray/hooks/...`).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The **mill**, not the worn exo. Reconcile, stamp JSON/CHANGELOG, verify docs, gate, publish, overlay their suit params.
|
|
4
4
|
|
|
5
|
-
PPE (`evaluatePreToolGate`) stays worn. The mill
|
|
5
|
+
PPE (`evaluatePreToolGate`) stays worn. The mill **fastens** their constitution, features, skills, and agents as the suit. It is not a fifth MCP. The suit is [`0xray`](https://www.npmjs.com/package/0xray).
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -35,11 +35,11 @@ npx @0xray/foundry hooks
|
|
|
35
35
|
|
|
36
36
|
`docs-check` and `gate` run **full** 0xRay corpus only when the milled `package.json` name is `0xray` and `docs-site/` exists. Otherwise **light**: `package.json` + CHANGELOG.
|
|
37
37
|
|
|
38
|
-
`hooks` installs git hooks via `scripts/hooks/install-hooks.cjs` (dogfood) or `node_modules/0xray/scripts/hooks/install-hooks.cjs` (published mill + worn
|
|
38
|
+
`hooks` installs git hooks via `scripts/hooks/install-hooks.cjs` (dogfood) or `node_modules/0xray/scripts/hooks/install-hooks.cjs` (published mill + worn suit). It does not look for `@0xray/hooks`.
|
|
39
39
|
|
|
40
40
|
## Suit params (`mint`)
|
|
41
41
|
|
|
42
|
-
After
|
|
42
|
+
After mill plant is fastened, overlay their plant (defaults; remap in `foundry.json` or `.xray/foundry.json`):
|
|
43
43
|
|
|
44
44
|
| Facet | Default plant | Hanger |
|
|
45
45
|
|---|---|---|
|
|
@@ -50,7 +50,9 @@ After the garment is on the hanger, overlay their plant (defaults; remap in `fou
|
|
|
50
50
|
| Agents | `src/opencode/agents/*.yml` | `.opencode/agents/` (OpenCode hangar; other floors do not read mill YML agents) |
|
|
51
51
|
| Agents card | `xray/AGENTS.md` | `AGENTS.md` if mill-managed |
|
|
52
52
|
|
|
53
|
-
**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
|
|
53
|
+
**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 only (`.opencode/skills`, project Grok plugin skills). CLI re-wear calls mint **once** after mill copies.
|
|
54
|
+
|
|
55
|
+
**Default mill plant:** `mill` + `inspect` (inspect AI work). Fastens a suit, not an empty one. Not 45/42 costume. `foundry.json` `"costume": true` copies that dump. Factory Repertoire still enable-when-resolves. PPE stays worn.
|
|
54
56
|
|
|
55
57
|
## Publish this mill
|
|
56
58
|
|
package/scripts/foundry/cli.mjs
CHANGED
|
@@ -25,7 +25,7 @@ const HELP =
|
|
|
25
25
|
"\n" +
|
|
26
26
|
"Mill, not exo. FOUNDRY_ROOT overrides cwd (the repo being milled).\n" +
|
|
27
27
|
"release bumps/commits/pushes/publishes the milled cwd — pass --dry-run or --i-mean-it (or FOUNDRY_RELEASE=1).\n" +
|
|
28
|
-
"Mint
|
|
28
|
+
"Mint fastens mill plant then overlays their plant (mill-fill JSON). PPE stays worn.\n" +
|
|
29
29
|
"gate is build+test. docs-build is Docusaurus on the 0xray exo. ci reports GitHub Actions (no auto-push). hooks installs git pre/post hooks.\n";
|
|
30
30
|
|
|
31
31
|
const cmd = process.argv[2];
|
|
@@ -24,7 +24,7 @@ export function resolveMillRoot() {
|
|
|
24
24
|
/**
|
|
25
25
|
* Git hook installer. Dogfood is `scripts/hooks` next to `scripts/foundry`.
|
|
26
26
|
* Published mill lives in `node_modules/@0xray/foundry` — sibling `@0xray/hooks`
|
|
27
|
-
* does not exist. Use the worn 0xray
|
|
27
|
+
* does not exist. Use the worn 0xray installer instead.
|
|
28
28
|
*/
|
|
29
29
|
export function resolveHookInstaller(rootDir = resolveMillRoot(), millPkg = millPackageDir()) {
|
|
30
30
|
const candidates = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mill overlay: their plant
|
|
2
|
+
* Mill overlay: fasten their plant as the suit. Not PPE (evaluatePreToolGate).
|
|
3
3
|
*
|
|
4
4
|
* Default SSOT (override with foundry.json or .xray/foundry.json):
|
|
5
5
|
* xray/codex.json, xray/features.json, xray/config.json,
|
|
@@ -72,22 +72,25 @@ function resolveInside(targetDir, rel) {
|
|
|
72
72
|
return resolved;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
function
|
|
75
|
+
function loadFoundryExtra(targetDir) {
|
|
76
76
|
const candidates = [
|
|
77
77
|
path.join(targetDir, "foundry.json"),
|
|
78
78
|
path.join(targetDir, ".xray", "foundry.json"),
|
|
79
79
|
];
|
|
80
|
-
let extra = {};
|
|
81
80
|
for (const candidate of candidates) {
|
|
82
81
|
if (!fs.existsSync(candidate)) continue;
|
|
83
82
|
try {
|
|
84
83
|
const parsed = JSON.parse(fs.readFileSync(candidate, "utf8"));
|
|
85
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
84
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
86
85
|
} catch {
|
|
87
|
-
|
|
86
|
+
return {};
|
|
88
87
|
}
|
|
89
|
-
break;
|
|
90
88
|
}
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function loadFoundryParams(targetDir) {
|
|
93
|
+
const extra = loadFoundryExtra(targetDir);
|
|
91
94
|
const params = { ...DEFAULT_PARAMS };
|
|
92
95
|
for (const key of ["codex", "features", "config", "skills", "agents", "agentsCard"]) {
|
|
93
96
|
if (typeof extra[key] === "string" && extra[key].trim()) params[key] = extra[key].trim();
|
|
@@ -95,6 +98,80 @@ function loadFoundryParams(targetDir) {
|
|
|
95
98
|
return params;
|
|
96
99
|
}
|
|
97
100
|
|
|
101
|
+
/** 45/42 costume dump. Default false on consumers. Exo mill keeps the costume. */
|
|
102
|
+
function wantsCostume(targetDir) {
|
|
103
|
+
if (isExoRepo(targetDir)) return true;
|
|
104
|
+
return loadFoundryExtra(targetDir).costume === true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function millPlantDir(millPackageRoot) {
|
|
108
|
+
const nested = path.join(millPackageRoot, "scripts", "foundry", "plant");
|
|
109
|
+
if (isDirectory(nested)) return nested;
|
|
110
|
+
const packed = path.join(millPackageRoot, "plant");
|
|
111
|
+
if (isDirectory(packed)) return packed;
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function listSkillNamesAt(skillsSrc) {
|
|
116
|
+
if (!isDirectory(skillsSrc)) return [];
|
|
117
|
+
const names = [];
|
|
118
|
+
for (const entry of fs.readdirSync(skillsSrc, { withFileTypes: true })) {
|
|
119
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
120
|
+
const skillMd = path.join(skillsSrc, entry.name, "SKILL.md");
|
|
121
|
+
if (!fs.existsSync(skillMd)) continue;
|
|
122
|
+
names.push(entry.name);
|
|
123
|
+
}
|
|
124
|
+
return names;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function listAgentFilesAt(agentsSrc) {
|
|
128
|
+
if (!isDirectory(agentsSrc)) return [];
|
|
129
|
+
return fs
|
|
130
|
+
.readdirSync(agentsSrc, { withFileTypes: true })
|
|
131
|
+
.filter(
|
|
132
|
+
(entry) =>
|
|
133
|
+
entry.isFile() && (entry.name.endsWith(".yml") || entry.name.endsWith(".yaml")),
|
|
134
|
+
)
|
|
135
|
+
.map((entry) => entry.name);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Mill mill-fill mill plant: mill mill mill mill-fill mill names they did not plant stay. */
|
|
139
|
+
function fastenMillPlant(millPackageRoot, targetDir, log) {
|
|
140
|
+
const plant = millPlantDir(millPackageRoot);
|
|
141
|
+
if (!plant) return { skills: [], agents: [] };
|
|
142
|
+
const skillsSrc = path.join(plant, "skills");
|
|
143
|
+
const agentsSrc = path.join(plant, "agents");
|
|
144
|
+
const skills = listSkillNamesAt(skillsSrc);
|
|
145
|
+
const agents = listAgentFilesAt(agentsSrc);
|
|
146
|
+
const skillDirs = listProjectSkillDirs(targetDir);
|
|
147
|
+
const agentsDest = path.join(targetDir, ".opencode", "agents");
|
|
148
|
+
for (const dir of skillDirs) {
|
|
149
|
+
for (const name of skills) {
|
|
150
|
+
const src = path.join(skillsSrc, name, "SKILL.md");
|
|
151
|
+
const destMd = path.join(dir, name, "SKILL.md");
|
|
152
|
+
if (path.resolve(src) === path.resolve(destMd)) continue;
|
|
153
|
+
if (fs.existsSync(destMd)) continue;
|
|
154
|
+
fs.mkdirSync(path.dirname(destMd), { recursive: true });
|
|
155
|
+
fs.copyFileSync(src, destMd);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const file of agents) {
|
|
159
|
+
const src = path.join(agentsSrc, file);
|
|
160
|
+
const dest = path.join(agentsDest, file);
|
|
161
|
+
if (path.resolve(src) === path.resolve(dest)) continue;
|
|
162
|
+
if (fs.existsSync(dest)) continue;
|
|
163
|
+
fs.mkdirSync(agentsDest, { recursive: true });
|
|
164
|
+
fs.copyFileSync(src, dest);
|
|
165
|
+
}
|
|
166
|
+
if (log && (skills.length > 0 || agents.length > 0)) {
|
|
167
|
+
log("foundry-mint", "Fastened mill plant", "info", {
|
|
168
|
+
skills: skills.length,
|
|
169
|
+
agents: agents.length,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return { skills, agents };
|
|
173
|
+
}
|
|
174
|
+
|
|
98
175
|
function isDirectory(p) {
|
|
99
176
|
try {
|
|
100
177
|
return Boolean(p) && fs.existsSync(p) && fs.statSync(p).isDirectory();
|
|
@@ -103,8 +180,8 @@ function isDirectory(p) {
|
|
|
103
180
|
}
|
|
104
181
|
}
|
|
105
182
|
|
|
106
|
-
/** Project skill
|
|
107
|
-
function
|
|
183
|
+
/** Project skill dirs only (.opencode/skills, project Grok plugin skills). */
|
|
184
|
+
function listProjectSkillDirs(targetDir) {
|
|
108
185
|
const dests = [path.join(targetDir, ".opencode", "skills")];
|
|
109
186
|
const projectGrok = path.join(targetDir, ".grok", "plugins", "0xray", "skills");
|
|
110
187
|
if (isDirectory(projectGrok)) dests.push(projectGrok);
|
|
@@ -138,28 +215,13 @@ function overlayJsonFacet(src, dest) {
|
|
|
138
215
|
function listConsumerSkillNames(targetDir, skillsRel) {
|
|
139
216
|
const rel = skillsRel || DEFAULT_PARAMS.skills;
|
|
140
217
|
const skillsSrc = resolveInside(targetDir, rel);
|
|
141
|
-
|
|
142
|
-
const names = [];
|
|
143
|
-
for (const entry of fs.readdirSync(skillsSrc, { withFileTypes: true })) {
|
|
144
|
-
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
145
|
-
const skillMd = path.join(skillsSrc, entry.name, "SKILL.md");
|
|
146
|
-
if (!fs.existsSync(skillMd)) continue;
|
|
147
|
-
names.push(entry.name);
|
|
148
|
-
}
|
|
149
|
-
return names;
|
|
218
|
+
return listSkillNamesAt(skillsSrc);
|
|
150
219
|
}
|
|
151
220
|
|
|
152
221
|
function listConsumerAgentFiles(targetDir, agentsRel) {
|
|
153
222
|
const rel = agentsRel || DEFAULT_PARAMS.agents;
|
|
154
223
|
const agentsSrc = resolveInside(targetDir, rel);
|
|
155
|
-
|
|
156
|
-
return fs
|
|
157
|
-
.readdirSync(agentsSrc, { withFileTypes: true })
|
|
158
|
-
.filter(
|
|
159
|
-
(entry) =>
|
|
160
|
-
entry.isFile() && (entry.name.endsWith(".yml") || entry.name.endsWith(".yaml")),
|
|
161
|
-
)
|
|
162
|
-
.map((entry) => entry.name);
|
|
224
|
+
return listAgentFilesAt(agentsSrc);
|
|
163
225
|
}
|
|
164
226
|
|
|
165
227
|
const MANAGED_AGENTS_MARKER = "<!-- 0xray-managed -->";
|
|
@@ -184,13 +246,13 @@ function overlayConsumerTree(targetDir, log, params) {
|
|
|
184
246
|
const agents = listConsumerAgentFiles(targetDir, agentsRel);
|
|
185
247
|
const skillsSrc = resolveInside(targetDir, skillsRel);
|
|
186
248
|
const agentsSrc = resolveInside(targetDir, agentsRel);
|
|
187
|
-
const
|
|
249
|
+
const skillDirs = listProjectSkillDirs(targetDir);
|
|
188
250
|
const agentsDest = path.join(targetDir, ".opencode", "agents");
|
|
189
251
|
|
|
190
|
-
for (const
|
|
252
|
+
for (const dir of skillDirs) {
|
|
191
253
|
for (const name of skills) {
|
|
192
254
|
const src = path.join(skillsSrc, name, "SKILL.md");
|
|
193
|
-
const destMd = path.join(
|
|
255
|
+
const destMd = path.join(dir, name, "SKILL.md");
|
|
194
256
|
if (path.resolve(src) === path.resolve(destMd)) continue;
|
|
195
257
|
fs.mkdirSync(path.dirname(destMd), { recursive: true });
|
|
196
258
|
fs.copyFileSync(src, destMd);
|
|
@@ -205,10 +267,10 @@ function overlayConsumerTree(targetDir, log, params) {
|
|
|
205
267
|
}
|
|
206
268
|
|
|
207
269
|
if (log && (skills.length > 0 || agents.length > 0)) {
|
|
208
|
-
log("foundry-mint", "Overlaid consumer skills/agents
|
|
270
|
+
log("foundry-mint", "Overlaid consumer skills/agents", "info", {
|
|
209
271
|
skills: skills.length,
|
|
210
272
|
agents: agents.length,
|
|
211
|
-
|
|
273
|
+
skillDirs: skillDirs.length,
|
|
212
274
|
});
|
|
213
275
|
}
|
|
214
276
|
return { skills, agents, params: resolved };
|
|
@@ -216,7 +278,7 @@ function overlayConsumerTree(targetDir, log, params) {
|
|
|
216
278
|
|
|
217
279
|
function mintConsumerFromSsot(packageRoot, targetDir, log, tree) {
|
|
218
280
|
if (isDogfood(packageRoot, targetDir)) {
|
|
219
|
-
return {
|
|
281
|
+
return { suit: "dogfood", skipped: true, consumer: readPackageIdentity(path.join(targetDir, "package.json")) };
|
|
220
282
|
}
|
|
221
283
|
const mill = readPackageIdentity(path.join(packageRoot, "package.json"));
|
|
222
284
|
const consumer = readPackageIdentity(path.join(targetDir, "package.json"));
|
|
@@ -233,13 +295,28 @@ function mintConsumerFromSsot(packageRoot, targetDir, log, tree) {
|
|
|
233
295
|
features ||
|
|
234
296
|
config ||
|
|
235
297
|
agentsCard;
|
|
236
|
-
const
|
|
298
|
+
const millPlanted =
|
|
299
|
+
(Array.isArray(tree?.millPlantSkills) && tree.millPlantSkills.length > 0) ||
|
|
300
|
+
(Array.isArray(tree?.millPlantAgents) && tree.millPlantAgents.length > 0);
|
|
301
|
+
const costume = wantsCostume(targetDir);
|
|
302
|
+
const suit = overlayed
|
|
303
|
+
? "overlay"
|
|
304
|
+
: millPlanted
|
|
305
|
+
? "fastened"
|
|
306
|
+
: costume
|
|
307
|
+
? "costume"
|
|
308
|
+
: "fastened";
|
|
237
309
|
const inventory = {
|
|
238
310
|
mill: { name: mill.name || "@0xray/foundry", version: mill.version },
|
|
239
311
|
consumer: { name: consumer.name, version: consumer.version },
|
|
240
|
-
|
|
312
|
+
suit,
|
|
241
313
|
params: tree?.params || loadFoundryParams(targetDir),
|
|
242
314
|
tree: { skills, agents },
|
|
315
|
+
millPlant: {
|
|
316
|
+
skills: Array.isArray(tree?.millPlantSkills) ? tree.millPlantSkills : [],
|
|
317
|
+
agents: Array.isArray(tree?.millPlantAgents) ? tree.millPlantAgents : [],
|
|
318
|
+
},
|
|
319
|
+
costume,
|
|
243
320
|
facets: {
|
|
244
321
|
constitution,
|
|
245
322
|
features,
|
|
@@ -260,7 +337,7 @@ function mintConsumerFromSsot(packageRoot, targetDir, log, tree) {
|
|
|
260
337
|
log("foundry-mint", "Minted foundry-inventory from consumer mill SSOT", "info", {
|
|
261
338
|
consumer: consumer.name,
|
|
262
339
|
version: consumer.version,
|
|
263
|
-
|
|
340
|
+
suit,
|
|
264
341
|
});
|
|
265
342
|
}
|
|
266
343
|
return inventory;
|
|
@@ -268,10 +345,13 @@ function mintConsumerFromSsot(packageRoot, targetDir, log, tree) {
|
|
|
268
345
|
|
|
269
346
|
function mintConsumerSuit(millPackageRoot, targetDir, log) {
|
|
270
347
|
if (isDogfood(millPackageRoot, targetDir)) {
|
|
271
|
-
return {
|
|
348
|
+
return { suit: "dogfood", skipped: true };
|
|
272
349
|
}
|
|
273
350
|
const params = loadFoundryParams(targetDir);
|
|
351
|
+
const millPlant = fastenMillPlant(millPackageRoot, targetDir, log);
|
|
274
352
|
const tree = overlayConsumerTree(targetDir, log, params);
|
|
353
|
+
tree.millPlantSkills = millPlant.skills;
|
|
354
|
+
tree.millPlantAgents = millPlant.agents;
|
|
275
355
|
tree.codex = overlayJsonFacet(
|
|
276
356
|
resolveInside(targetDir, params.codex),
|
|
277
357
|
path.join(targetDir, ".xray", "codex.json"),
|
|
@@ -299,13 +379,17 @@ module.exports = {
|
|
|
299
379
|
DEFAULT_PARAMS,
|
|
300
380
|
deepMerge,
|
|
301
381
|
loadFoundryParams,
|
|
382
|
+
loadFoundryExtra,
|
|
383
|
+
wantsCostume,
|
|
384
|
+
millPlantDir,
|
|
385
|
+
fastenMillPlant,
|
|
302
386
|
resolveInside,
|
|
303
387
|
overlayJsonFacet,
|
|
304
388
|
overlayAgentsCard,
|
|
305
389
|
overlayConsumerTree,
|
|
306
390
|
listConsumerSkillNames,
|
|
307
391
|
listConsumerAgentFiles,
|
|
308
|
-
|
|
392
|
+
listProjectSkillDirs,
|
|
309
393
|
mintConsumerFromSsot,
|
|
310
394
|
mintConsumerSuit,
|
|
311
395
|
isDogfood,
|
package/scripts/foundry/mint.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Overlay their mill SSOT onto the
|
|
3
|
+
* Overlay their mill SSOT onto the project (cwd / FOUNDRY_ROOT).
|
|
4
4
|
* Not a fifth 0xray CLI. Not PPE.
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -19,7 +19,7 @@ function main() {
|
|
|
19
19
|
process.stdout.write(`${component} ${action} ${status}\n`);
|
|
20
20
|
});
|
|
21
21
|
if (inventory.skipped) {
|
|
22
|
-
process.stderr.write("mint: skipped dogfood / 0xray exo plant (not a consumer
|
|
22
|
+
process.stderr.write("mint: skipped dogfood / 0xray exo plant (not a consumer project)\n");
|
|
23
23
|
process.exit(0);
|
|
24
24
|
}
|
|
25
25
|
process.stdout.write(`${JSON.stringify(inventory, null, 2)}\n`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xray/foundry",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "0xRay mill: mint their suit params, stamp, gate, publish, CI report, git hooks. Not the worn exo.",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"cli.js",
|
|
27
27
|
"README.md",
|
|
28
28
|
"LICENSE",
|
|
29
|
-
"CHANGELOG.md"
|
|
29
|
+
"CHANGELOG.md",
|
|
30
|
+
"plant"
|
|
30
31
|
],
|
|
31
32
|
"exports": {
|
|
32
33
|
"./version-manager": "./version-manager.mjs",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: inspect
|
|
3
|
+
description: Inspect AI work — diffs, tool traces, mill receipts, CI. Factory mill plant. Not 45/42 costume.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Inspect
|
|
7
|
+
|
|
8
|
+
This mill **fastens** a suit for **inspecting AI work**. Not an empty suit. Not a copy of 0xRay's 45/42 costume.
|
|
9
|
+
|
|
10
|
+
Use this when an agent (or a mill) just wrote, minted, or shipped something. Read the work. Do not trust the receipt until you have opened the files.
|
|
11
|
+
|
|
12
|
+
## What to inspect
|
|
13
|
+
|
|
14
|
+
1. **Diff.** What actually changed. Ignore the agent's summary if the diff disagrees.
|
|
15
|
+
2. **Plant vs worn.** `src/skills`, `src/opencode/agents`, `xray/*.json` vs `.opencode/skills`, `.opencode/agents`, `.xray/`. Same name: plant should win. Costume dump (45 skills / 42 agents they did not plant) is a fail unless `foundry.json` has `"costume": true`.
|
|
16
|
+
3. **Mill receipt.** `.xray/foundry-inventory.json` — mill name/version, `suit` (`fastened` / `overlay` / `costume`), `millPlant.skills` should include `mill` and `inspect`.
|
|
17
|
+
4. **CI.** GitHub mill CI and mill-monitor report, not just a green check in chat.
|
|
18
|
+
5. **Live PUT.** `npm view <pkg> version` and HTTP 200 on the `.tgz`. Metadata without a tarball is not shipped.
|
|
19
|
+
6. **Machine hangar.** Isolated HOME last-mile must not clobber `~/.grok/plugins/0xray`.
|
|
20
|
+
|
|
21
|
+
## How to work
|
|
22
|
+
|
|
23
|
+
- Open the files. Quote paths. Do not invent a pass.
|
|
24
|
+
- Constitution stays on (Codex 11 / 69 / destructive). Inspecting is not a license to add an 8th MCP.
|
|
25
|
+
- Factory Repertoire is the organ, not the inspection.
|
|
26
|
+
- Fasten their plant on top; do not dump our costume.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx @0xray/foundry mint
|
|
30
|
+
npx @0xray/foundry ci --report
|
|
31
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mill
|
|
3
|
+
description: Fasten their plant as the suit. Factory Repertoire. Inspect is the mill discipline. Not 45/42 costume.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Mill
|
|
7
|
+
|
|
8
|
+
The mill **fastens** a suit. Default mill plant is **inspect** (inspecting AI work) plus this mill card. Not empty. Not 0xRay's 45-skill / 42-agent costume unless they opt in.
|
|
9
|
+
|
|
10
|
+
**Plant (their SSOT):** `xray/codex.json`, `xray/features.json`, `xray/config.json`, `src/skills/<name>/SKILL.md`, `src/opencode/agents/*.yml`. Remap with `foundry.json`.
|
|
11
|
+
|
|
12
|
+
**Mill-fill:** their keys/files win. Mill plant names they did not overlay stay. JSON always merges.
|
|
13
|
+
|
|
14
|
+
**Factory:** Repertoire enable-when-resolves. Seven generated MCP. Constitution (PPE) stays worn. Not a fifth MCP. Not 5.0.
|
|
15
|
+
|
|
16
|
+
**Costume:** `foundry.json` `"costume": true` copies the 45/42 mill costume.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx @0xray/foundry mint
|
|
20
|
+
```
|
|
@@ -210,6 +210,7 @@ function main() {
|
|
|
210
210
|
"scripts/node/postinstall.cjs",
|
|
211
211
|
"scripts/foundry/mint-suit.cjs",
|
|
212
212
|
"scripts/foundry/hooks.mjs",
|
|
213
|
+
"scripts/foundry/plant/skills/mill/SKILL.md",
|
|
213
214
|
"package.json",
|
|
214
215
|
"xray/features.json",
|
|
215
216
|
"xray/features.schema.json",
|
|
@@ -223,6 +224,20 @@ function main() {
|
|
|
223
224
|
console.log(` ✅ ${rel}`);
|
|
224
225
|
}
|
|
225
226
|
|
|
227
|
+
const millSkillHanger = path.join(tmpRoot, ".opencode", "skills", "mill", "SKILL.md");
|
|
228
|
+
if (!fs.existsSync(millSkillHanger)) {
|
|
229
|
+
throw new Error("mill plant missing mill skill");
|
|
230
|
+
}
|
|
231
|
+
const wornSkills = path.join(tmpRoot, ".opencode", "skills");
|
|
232
|
+
const wornNames = fs.existsSync(wornSkills)
|
|
233
|
+
? fs.readdirSync(wornSkills).filter((n) =>
|
|
234
|
+
fs.existsSync(path.join(wornSkills, n, "SKILL.md")),
|
|
235
|
+
)
|
|
236
|
+
: [];
|
|
237
|
+
if (wornNames.length > 3) {
|
|
238
|
+
throw new Error(`costume dump: ${wornNames.join(",")}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
226
241
|
const installedPkg = JSON.parse(fs.readFileSync(path.join(nmRoot, "package.json"), "utf-8"));
|
|
227
242
|
if (installedPkg.version !== version) {
|
|
228
243
|
throw new Error(`Installed version ${installedPkg.version} != packed ${version}`);
|
|
@@ -8,6 +8,7 @@ const fs = require("fs");
|
|
|
8
8
|
const path = require("path");
|
|
9
9
|
const os = require("os");
|
|
10
10
|
const { execSync } = require("child_process");
|
|
11
|
+
const { wantsCostume } = require("../foundry/mint-suit.cjs");
|
|
11
12
|
const {
|
|
12
13
|
wireHermesBridge,
|
|
13
14
|
wireOpencodeBridge,
|
|
@@ -92,6 +93,11 @@ function syncBuiltinSkills(targetSkillsDir, packageRoot) {
|
|
|
92
93
|
return copied;
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
function syncCostumeSkills(targetSkillsDir, packageRoot, targetDir) {
|
|
97
|
+
if (targetDir && !wantsCostume(targetDir)) return 0;
|
|
98
|
+
return syncBuiltinSkills(targetSkillsDir, packageRoot);
|
|
99
|
+
}
|
|
100
|
+
|
|
95
101
|
function copyTree(src, dest, relPath = "") {
|
|
96
102
|
if (!fs.existsSync(src)) return;
|
|
97
103
|
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
@@ -267,38 +273,46 @@ function writeGrokDiscoveredHooks(targetDir, hooksPath, log, label) {
|
|
|
267
273
|
}
|
|
268
274
|
}
|
|
269
275
|
|
|
276
|
+
function copyOpencodePlugin(packageRoot, opencodeDest, log) {
|
|
277
|
+
const pluginSource = path.join(packageRoot, "dist", "plugin", "xray-codex-injection.js");
|
|
278
|
+
const pluginDest = path.join(opencodeDest, "plugin", "xray-codex-injection.js");
|
|
279
|
+
if (!fs.existsSync(pluginSource)) return;
|
|
280
|
+
const pluginDestDir = path.dirname(pluginDest);
|
|
281
|
+
if (!fs.existsSync(pluginDestDir)) fs.mkdirSync(pluginDestDir, { recursive: true });
|
|
282
|
+
const shouldCopy =
|
|
283
|
+
!fs.existsSync(pluginDest) ||
|
|
284
|
+
fs.statSync(pluginSource).mtime > fs.statSync(pluginDest).mtime;
|
|
285
|
+
if (shouldCopy) {
|
|
286
|
+
fs.copyFileSync(pluginSource, pluginDest);
|
|
287
|
+
log("opencode-bridge", "plugin updated", "info");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
270
291
|
function installOpencodeBridge(targetDir, packageRoot, log) {
|
|
271
292
|
const opencodeSource = path.join(packageRoot, ".opencode");
|
|
272
293
|
const opencodeDest = path.join(targetDir, ".opencode");
|
|
273
|
-
|
|
274
|
-
log("opencode-bridge", "skipped", "warn", { reason: ".opencode source missing" });
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
294
|
+
const costume = wantsCostume(targetDir);
|
|
277
295
|
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
log("opencode-bridge", ".opencode exists — merging skills/plugin only", "info");
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const pluginSource = path.join(packageRoot, "dist", "plugin", "xray-codex-injection.js");
|
|
286
|
-
const pluginDest = path.join(opencodeDest, "plugin", "xray-codex-injection.js");
|
|
287
|
-
if (fs.existsSync(pluginSource)) {
|
|
288
|
-
const pluginDestDir = path.dirname(pluginDest);
|
|
289
|
-
if (!fs.existsSync(pluginDestDir)) fs.mkdirSync(pluginDestDir, { recursive: true });
|
|
290
|
-
const shouldCopy =
|
|
291
|
-
!fs.existsSync(pluginDest) ||
|
|
292
|
-
fs.statSync(pluginSource).mtime > fs.statSync(pluginDest).mtime;
|
|
293
|
-
if (shouldCopy) {
|
|
294
|
-
fs.copyFileSync(pluginSource, pluginDest);
|
|
295
|
-
log("opencode-bridge", "plugin updated", "info");
|
|
296
|
+
if (costume) {
|
|
297
|
+
if (!fs.existsSync(opencodeSource)) {
|
|
298
|
+
log("opencode-bridge", "skipped", "warn", { reason: ".opencode source missing" });
|
|
299
|
+
return;
|
|
296
300
|
}
|
|
301
|
+
if (!fs.existsSync(opencodeDest)) {
|
|
302
|
+
copyTree(opencodeSource, opencodeDest);
|
|
303
|
+
log("opencode-bridge", "copied .opencode tree", "info");
|
|
304
|
+
} else {
|
|
305
|
+
log("opencode-bridge", ".opencode exists — merging skills/plugin only", "info");
|
|
306
|
+
}
|
|
307
|
+
copyOpencodePlugin(packageRoot, opencodeDest, log);
|
|
308
|
+
const copied = syncCostumeSkills(path.join(opencodeDest, "skills"), packageRoot, targetDir);
|
|
309
|
+
if (copied > 0) log("opencode-bridge", `skills synced (${copied})`, "info", { path: ".opencode/skills/" });
|
|
310
|
+
} else {
|
|
311
|
+
log("opencode-bridge", "mill plant — plugin only, not costume dump", "info");
|
|
312
|
+
fs.mkdirSync(opencodeDest, { recursive: true });
|
|
313
|
+
copyOpencodePlugin(packageRoot, opencodeDest, log);
|
|
297
314
|
}
|
|
298
315
|
|
|
299
|
-
const copied = syncBuiltinSkills(path.join(opencodeDest, "skills"), packageRoot);
|
|
300
|
-
if (copied > 0) log("opencode-bridge", `skills synced (${copied})`, "info", { path: ".opencode/skills/" });
|
|
301
|
-
|
|
302
316
|
try {
|
|
303
317
|
const count = wireOpencodeBridge(targetDir);
|
|
304
318
|
log("opencode-bridge", `opencode.json mcp wired (${count} servers)`, "info");
|
|
@@ -376,14 +390,14 @@ function installGrokBridge(targetDir, packageRoot, log) {
|
|
|
376
390
|
log("grok-bridge", "plugin copied", "info", { path: rel || dest });
|
|
377
391
|
writePluginMcpJson(dest, targetDir, log, "grok-bridge");
|
|
378
392
|
patchGrokHooks(dest, packageRoot, targetDir, log, "grok-bridge");
|
|
379
|
-
const copied =
|
|
393
|
+
const copied = syncCostumeSkills(path.join(dest, "skills"), packageRoot, targetDir);
|
|
380
394
|
if (copied > 0) log("grok-bridge", `plugin skills synced (${copied})`, "info", { path: rel });
|
|
381
395
|
}
|
|
382
396
|
}
|
|
383
397
|
|
|
384
398
|
if (!ephemeral) {
|
|
385
399
|
const grokGlobalSkills = path.join(home, ".grok", "skills");
|
|
386
|
-
const globalCopied =
|
|
400
|
+
const globalCopied = syncCostumeSkills(grokGlobalSkills, packageRoot, targetDir);
|
|
387
401
|
if (globalCopied > 0) {
|
|
388
402
|
log("grok-bridge", `global skills synced (${globalCopied})`, "info", { path: "~/.grok/skills/" });
|
|
389
403
|
}
|
|
@@ -421,7 +435,7 @@ function installHermesBridge(targetDir, packageRoot, log) {
|
|
|
421
435
|
|
|
422
436
|
writePluginMcpJson(targetPluginDir, targetDir, log, "hermes-bridge");
|
|
423
437
|
|
|
424
|
-
const copied =
|
|
438
|
+
const copied = syncCostumeSkills(path.join(targetPluginDir, "skills"), packageRoot, targetDir);
|
|
425
439
|
if (copied > 0) log("hermes-bridge", `skills synced (${copied})`, "info");
|
|
426
440
|
|
|
427
441
|
const rootMarker = path.join(targetPluginDir, "xray-consumer-root.txt");
|
|
@@ -473,7 +487,7 @@ function installOpenclawBridge(targetDir, packageRoot, log) {
|
|
|
473
487
|
log("openclaw-bridge", "config created", "info", { path: ".xray/config/openclaw.json" });
|
|
474
488
|
}
|
|
475
489
|
|
|
476
|
-
const copied =
|
|
490
|
+
const copied = syncCostumeSkills(path.join(os.homedir(), ".openclaw", "skills"), packageRoot, targetDir);
|
|
477
491
|
if (copied > 0) log("openclaw-bridge", `skills synced (${copied})`, "info", { path: "~/.openclaw/skills/" });
|
|
478
492
|
|
|
479
493
|
try {
|