0xray 4.0.1 → 4.0.3
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 +4 -4
- package/AGENTS.md +3 -3
- package/README.md +1 -1
- package/SKILLS.md +1 -1
- package/dist/AGENTS.md +3 -3
- package/dist/CHANGELOG.md +43 -0
- package/dist/README.md +1 -1
- package/dist/SKILLS.md +1 -1
- package/dist/cli/commands/foundry-mint-wear.d.ts +3 -0
- package/dist/cli/commands/foundry-mint-wear.js +17 -0
- package/dist/cli/commands/hermes-install.js +2 -0
- package/dist/cli/commands/openclaw-install.js +2 -0
- package/dist/cli/commands/opencode-install.js +10 -2
- package/dist/cli/commands/skill-install.js +4 -0
- package/dist/enforcement/enforcer-tools.d.ts +7 -0
- package/dist/enforcement/enforcer-tools.js +18 -109
- package/dist/integrations/grok/grok-cli.js +2 -0
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/processors/implementations/version-compliance-processor.d.ts +3 -34
- package/dist/processors/implementations/version-compliance-processor.js +8 -135
- package/package.json +15 -14
- package/scripts/foundry/CHANGELOG.md +38 -0
- package/scripts/foundry/LICENSE +21 -0
- package/scripts/foundry/README.md +69 -0
- package/scripts/foundry/ci-monitor.mjs +115 -0
- package/scripts/foundry/cli.js +2 -0
- package/scripts/foundry/cli.mjs +55 -0
- package/scripts/foundry/docs-build.mjs +38 -0
- package/scripts/foundry/hooks.mjs +36 -0
- package/scripts/foundry/mill-root.mjs +62 -0
- package/scripts/foundry/mint-suit.cjs +397 -0
- package/scripts/foundry/mint.mjs +33 -0
- package/scripts/foundry/package.json +54 -0
- package/scripts/foundry/plant/agents/mill.yml +5 -0
- package/scripts/foundry/plant/skills/mill/SKILL.md +22 -0
- package/scripts/foundry/reconcile-version.mjs +150 -0
- package/scripts/foundry/release-gate.mjs +100 -0
- package/scripts/foundry/release.mjs +266 -0
- package/scripts/foundry/validate-release-docs.mjs +316 -0
- package/scripts/foundry/version-manager.mjs +444 -0
- package/scripts/node/ci-cd-auto-fix.cjs +12 -257
- package/scripts/node/consumer-install-smoke.mjs +17 -0
- package/scripts/node/enforce-version-compliance.sh +5 -20
- package/scripts/node/enforce-version-compliance.ts +13 -113
- package/scripts/node/foundry-shim.mjs +19 -0
- package/scripts/node/github-actions-monitor.cjs +13 -18
- package/scripts/node/install-bridges.cjs +43 -29
- package/scripts/node/postinstall.cjs +36 -1
- package/scripts/node/reconcile-version.mjs +3 -142
- package/scripts/node/release-gate.mjs +3 -74
- package/scripts/node/release.mjs +3 -171
- package/scripts/node/validate-release-docs.mjs +3 -285
- package/scripts/node/version-manager.mjs +3 -738
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/xray/features.json +1 -1
- package/scripts/node/release.js +0 -17
- package/scripts/node/sync-versions.mjs +0 -18
- package/scripts/node/universal-version-manager.js +0 -1043
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const millDir = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
export function millPackageDir() {
|
|
8
|
+
return millDir;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function millScript(basename) {
|
|
12
|
+
return path.join(millDir, basename);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Repo being milled: FOUNDRY_ROOT, else cwd. Not this package's __dirname. */
|
|
16
|
+
export function resolveMillRoot() {
|
|
17
|
+
const env = process.env.FOUNDRY_ROOT;
|
|
18
|
+
if (typeof env === "string" && env.trim()) {
|
|
19
|
+
return path.resolve(env.trim());
|
|
20
|
+
}
|
|
21
|
+
return process.cwd();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Git hook installer. Dogfood is `scripts/hooks` next to `scripts/foundry`.
|
|
26
|
+
* Published mill lives in `node_modules/@0xray/foundry` — sibling `@0xray/hooks`
|
|
27
|
+
* does not exist. Use the worn 0xray garment installer instead.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveHookInstaller(rootDir = resolveMillRoot(), millPkg = millPackageDir()) {
|
|
30
|
+
const candidates = [
|
|
31
|
+
path.join(millPkg, "..", "hooks", "install-hooks.cjs"),
|
|
32
|
+
path.join(rootDir, "node_modules", "0xray", "scripts", "hooks", "install-hooks.cjs"),
|
|
33
|
+
path.join(rootDir, "scripts", "hooks", "install-hooks.cjs"),
|
|
34
|
+
];
|
|
35
|
+
return candidates.find((p) => fs.existsSync(p)) ?? null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function readRootPackage(rootDir = resolveMillRoot()) {
|
|
39
|
+
const pkgPath = path.join(rootDir, "package.json");
|
|
40
|
+
if (!fs.existsSync(pkgPath)) {
|
|
41
|
+
return { name: null, version: null, scripts: {} };
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
45
|
+
return {
|
|
46
|
+
name: typeof pkg.name === "string" ? pkg.name : null,
|
|
47
|
+
version: typeof pkg.version === "string" ? pkg.version : null,
|
|
48
|
+
scripts: pkg.scripts && typeof pkg.scripts === "object" ? pkg.scripts : {},
|
|
49
|
+
};
|
|
50
|
+
} catch {
|
|
51
|
+
return { name: null, version: null, scripts: {} };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function hasDocsSite(rootDir = resolveMillRoot()) {
|
|
56
|
+
return fs.existsSync(path.join(rootDir, "docs-site"));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function isXrayExoRepo(rootDir = resolveMillRoot()) {
|
|
60
|
+
const pkg = readRootPackage(rootDir);
|
|
61
|
+
return pkg.name === "0xray" && hasDocsSite(rootDir);
|
|
62
|
+
}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mill overlay: their plant onto the hanger. Not PPE (evaluatePreToolGate).
|
|
3
|
+
*
|
|
4
|
+
* Default SSOT (override with foundry.json or .xray/foundry.json):
|
|
5
|
+
* xray/codex.json, xray/features.json, xray/config.json,
|
|
6
|
+
* src/skills/<name>/SKILL.md, src/opencode/agents/*.yml
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
|
|
12
|
+
const DEFAULT_PARAMS = {
|
|
13
|
+
codex: "xray/codex.json",
|
|
14
|
+
features: "xray/features.json",
|
|
15
|
+
config: "xray/config.json",
|
|
16
|
+
skills: "src/skills",
|
|
17
|
+
agents: "src/opencode/agents",
|
|
18
|
+
agentsCard: "xray/AGENTS.md",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function deepMerge(src, dest) {
|
|
22
|
+
if (typeof src !== "object" || src === null) return dest !== undefined ? dest : src;
|
|
23
|
+
if (Array.isArray(src)) return Array.isArray(dest) ? dest : src;
|
|
24
|
+
const result = {};
|
|
25
|
+
for (const key of Object.keys(src)) {
|
|
26
|
+
result[key] =
|
|
27
|
+
dest && typeof dest[key] !== "undefined" ? deepMerge(src[key], dest[key]) : src[key];
|
|
28
|
+
}
|
|
29
|
+
if (dest && typeof dest === "object") {
|
|
30
|
+
for (const key of Object.keys(dest)) {
|
|
31
|
+
if (!(key in src)) result[key] = dest[key];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readPackageIdentity(pkgPath) {
|
|
38
|
+
if (!fs.existsSync(pkgPath)) return { name: null, version: null };
|
|
39
|
+
try {
|
|
40
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
41
|
+
return {
|
|
42
|
+
name: typeof pkg.name === "string" ? pkg.name : null,
|
|
43
|
+
version: typeof pkg.version === "string" ? pkg.version : null,
|
|
44
|
+
};
|
|
45
|
+
} catch {
|
|
46
|
+
return { name: null, version: null };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isExoRepo(dir) {
|
|
51
|
+
try {
|
|
52
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8"));
|
|
53
|
+
return pkg.name === "0xray" && fs.existsSync(path.join(dir, "docs-site"));
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isDogfood(millPackageRoot, targetDir) {
|
|
60
|
+
if (path.resolve(millPackageRoot) === path.resolve(targetDir)) return true;
|
|
61
|
+
return isExoRepo(targetDir);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Rel must stay inside targetDir. Absolute and `..` escapes return null. */
|
|
65
|
+
function resolveInside(targetDir, rel) {
|
|
66
|
+
if (typeof rel !== "string" || !rel.trim()) return null;
|
|
67
|
+
if (path.isAbsolute(rel)) return null;
|
|
68
|
+
const rootAbs = path.resolve(targetDir);
|
|
69
|
+
const resolved = path.resolve(rootAbs, rel);
|
|
70
|
+
const prefix = rootAbs.endsWith(path.sep) ? rootAbs : `${rootAbs}${path.sep}`;
|
|
71
|
+
if (resolved !== rootAbs && !resolved.startsWith(prefix)) return null;
|
|
72
|
+
return resolved;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function loadFoundryExtra(targetDir) {
|
|
76
|
+
const candidates = [
|
|
77
|
+
path.join(targetDir, "foundry.json"),
|
|
78
|
+
path.join(targetDir, ".xray", "foundry.json"),
|
|
79
|
+
];
|
|
80
|
+
for (const candidate of candidates) {
|
|
81
|
+
if (!fs.existsSync(candidate)) continue;
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(fs.readFileSync(candidate, "utf8"));
|
|
84
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
85
|
+
} catch {
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function loadFoundryParams(targetDir) {
|
|
93
|
+
const extra = loadFoundryExtra(targetDir);
|
|
94
|
+
const params = { ...DEFAULT_PARAMS };
|
|
95
|
+
for (const key of ["codex", "features", "config", "skills", "agents", "agentsCard"]) {
|
|
96
|
+
if (typeof extra[key] === "string" && extra[key].trim()) params[key] = extra[key].trim();
|
|
97
|
+
}
|
|
98
|
+
return params;
|
|
99
|
+
}
|
|
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 millFillMillPlant(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 skillHangers = listSkillHangers(targetDir);
|
|
147
|
+
const agentsDest = path.join(targetDir, ".opencode", "agents");
|
|
148
|
+
for (const hanger of skillHangers) {
|
|
149
|
+
for (const name of skills) {
|
|
150
|
+
const src = path.join(skillsSrc, name, "SKILL.md");
|
|
151
|
+
const destMd = path.join(hanger, 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", "Mill plant onto hangers", "info", {
|
|
168
|
+
skills: skills.length,
|
|
169
|
+
agents: agents.length,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return { skills, agents };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isDirectory(p) {
|
|
176
|
+
try {
|
|
177
|
+
return Boolean(p) && fs.existsSync(p) && fs.statSync(p).isDirectory();
|
|
178
|
+
} catch {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Project skill hangers only. */
|
|
184
|
+
function listSkillHangers(targetDir) {
|
|
185
|
+
const dests = [path.join(targetDir, ".opencode", "skills")];
|
|
186
|
+
const projectGrok = path.join(targetDir, ".grok", "plugins", "0xray", "skills");
|
|
187
|
+
if (isDirectory(projectGrok)) dests.push(projectGrok);
|
|
188
|
+
return dests;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function overlayJsonFacet(src, dest) {
|
|
192
|
+
if (!src || !fs.existsSync(src) || !fs.statSync(src).isFile()) return false;
|
|
193
|
+
if (path.resolve(src) === path.resolve(dest)) return false;
|
|
194
|
+
let theirs;
|
|
195
|
+
try {
|
|
196
|
+
theirs = JSON.parse(fs.readFileSync(src, "utf8"));
|
|
197
|
+
} catch {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
201
|
+
if (!fs.existsSync(dest)) {
|
|
202
|
+
fs.writeFileSync(dest, `${JSON.stringify(theirs, null, 2)}\n`);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const mill = JSON.parse(fs.readFileSync(dest, "utf8"));
|
|
207
|
+
const merged = deepMerge(mill, theirs);
|
|
208
|
+
fs.writeFileSync(dest, `${JSON.stringify(merged, null, 2)}\n`);
|
|
209
|
+
return true;
|
|
210
|
+
} catch {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function listConsumerSkillNames(targetDir, skillsRel) {
|
|
216
|
+
const rel = skillsRel || DEFAULT_PARAMS.skills;
|
|
217
|
+
const skillsSrc = resolveInside(targetDir, rel);
|
|
218
|
+
return listSkillNamesAt(skillsSrc);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function listConsumerAgentFiles(targetDir, agentsRel) {
|
|
222
|
+
const rel = agentsRel || DEFAULT_PARAMS.agents;
|
|
223
|
+
const agentsSrc = resolveInside(targetDir, rel);
|
|
224
|
+
return listAgentFilesAt(agentsSrc);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const MANAGED_AGENTS_MARKER = "<!-- 0xray-managed -->";
|
|
228
|
+
|
|
229
|
+
function overlayAgentsCard(targetDir, params) {
|
|
230
|
+
const src = resolveInside(targetDir, params.agentsCard || DEFAULT_PARAMS.agentsCard);
|
|
231
|
+
if (!src || !fs.existsSync(src) || !fs.statSync(src).isFile()) return false;
|
|
232
|
+
const dest = path.join(targetDir, "AGENTS.md");
|
|
233
|
+
if (path.resolve(src) === path.resolve(dest)) return false;
|
|
234
|
+
if (fs.existsSync(dest) && !fs.readFileSync(dest, "utf8").includes(MANAGED_AGENTS_MARKER)) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
fs.copyFileSync(src, dest);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function overlayConsumerTree(targetDir, log, params) {
|
|
242
|
+
const resolved = params || loadFoundryParams(targetDir);
|
|
243
|
+
const skillsRel = resolved.skills;
|
|
244
|
+
const agentsRel = resolved.agents;
|
|
245
|
+
const skills = listConsumerSkillNames(targetDir, skillsRel);
|
|
246
|
+
const agents = listConsumerAgentFiles(targetDir, agentsRel);
|
|
247
|
+
const skillsSrc = resolveInside(targetDir, skillsRel);
|
|
248
|
+
const agentsSrc = resolveInside(targetDir, agentsRel);
|
|
249
|
+
const skillHangers = listSkillHangers(targetDir);
|
|
250
|
+
const agentsDest = path.join(targetDir, ".opencode", "agents");
|
|
251
|
+
|
|
252
|
+
for (const hanger of skillHangers) {
|
|
253
|
+
for (const name of skills) {
|
|
254
|
+
const src = path.join(skillsSrc, name, "SKILL.md");
|
|
255
|
+
const destMd = path.join(hanger, name, "SKILL.md");
|
|
256
|
+
if (path.resolve(src) === path.resolve(destMd)) continue;
|
|
257
|
+
fs.mkdirSync(path.dirname(destMd), { recursive: true });
|
|
258
|
+
fs.copyFileSync(src, destMd);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
for (const file of agents) {
|
|
262
|
+
const src = path.join(agentsSrc, file);
|
|
263
|
+
const dest = path.join(agentsDest, file);
|
|
264
|
+
if (path.resolve(src) === path.resolve(dest)) continue;
|
|
265
|
+
fs.mkdirSync(agentsDest, { recursive: true });
|
|
266
|
+
fs.copyFileSync(src, dest);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (log && (skills.length > 0 || agents.length > 0)) {
|
|
270
|
+
log("foundry-mint", "Overlaid consumer skills/agents onto hangers", "info", {
|
|
271
|
+
skills: skills.length,
|
|
272
|
+
agents: agents.length,
|
|
273
|
+
skillHangers: skillHangers.length,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return { skills, agents, params: resolved };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function mintConsumerFromSsot(packageRoot, targetDir, log, tree) {
|
|
280
|
+
if (isDogfood(packageRoot, targetDir)) {
|
|
281
|
+
return { garment: "dogfood", skipped: true, consumer: readPackageIdentity(path.join(targetDir, "package.json")) };
|
|
282
|
+
}
|
|
283
|
+
const mill = readPackageIdentity(path.join(packageRoot, "package.json"));
|
|
284
|
+
const consumer = readPackageIdentity(path.join(targetDir, "package.json"));
|
|
285
|
+
const skills = Array.isArray(tree?.skills) ? tree.skills : [];
|
|
286
|
+
const agents = Array.isArray(tree?.agents) ? tree.agents : [];
|
|
287
|
+
const constitution = Boolean(tree?.codex);
|
|
288
|
+
const features = Boolean(tree?.features);
|
|
289
|
+
const config = Boolean(tree?.config);
|
|
290
|
+
const agentsCard = Boolean(tree?.agentsCard);
|
|
291
|
+
const overlayed =
|
|
292
|
+
skills.length > 0 ||
|
|
293
|
+
agents.length > 0 ||
|
|
294
|
+
constitution ||
|
|
295
|
+
features ||
|
|
296
|
+
config ||
|
|
297
|
+
agentsCard;
|
|
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 garment = overlayed
|
|
303
|
+
? "overlay"
|
|
304
|
+
: millPlanted
|
|
305
|
+
? "mill-fill"
|
|
306
|
+
: costume
|
|
307
|
+
? "copied-onto-hanger"
|
|
308
|
+
: "mill-fill";
|
|
309
|
+
const inventory = {
|
|
310
|
+
mill: { name: mill.name || "@0xray/foundry", version: mill.version },
|
|
311
|
+
consumer: { name: consumer.name, version: consumer.version },
|
|
312
|
+
garment,
|
|
313
|
+
params: tree?.params || loadFoundryParams(targetDir),
|
|
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,
|
|
320
|
+
facets: {
|
|
321
|
+
constitution,
|
|
322
|
+
features,
|
|
323
|
+
config,
|
|
324
|
+
agentsCard,
|
|
325
|
+
skills,
|
|
326
|
+
agents,
|
|
327
|
+
},
|
|
328
|
+
mintedAt: new Date().toISOString(),
|
|
329
|
+
};
|
|
330
|
+
const xrayDir = path.join(targetDir, ".xray");
|
|
331
|
+
if (!fs.existsSync(xrayDir)) fs.mkdirSync(xrayDir, { recursive: true });
|
|
332
|
+
fs.writeFileSync(
|
|
333
|
+
path.join(xrayDir, "foundry-inventory.json"),
|
|
334
|
+
`${JSON.stringify(inventory, null, 2)}\n`,
|
|
335
|
+
);
|
|
336
|
+
if (log) {
|
|
337
|
+
log("foundry-mint", "Minted foundry-inventory from consumer mill SSOT", "info", {
|
|
338
|
+
consumer: consumer.name,
|
|
339
|
+
version: consumer.version,
|
|
340
|
+
garment,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
return inventory;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function mintConsumerSuit(millPackageRoot, targetDir, log) {
|
|
347
|
+
if (isDogfood(millPackageRoot, targetDir)) {
|
|
348
|
+
return { garment: "dogfood", skipped: true };
|
|
349
|
+
}
|
|
350
|
+
const params = loadFoundryParams(targetDir);
|
|
351
|
+
const millPlant = millFillMillPlant(millPackageRoot, targetDir, log);
|
|
352
|
+
const tree = overlayConsumerTree(targetDir, log, params);
|
|
353
|
+
tree.millPlantSkills = millPlant.skills;
|
|
354
|
+
tree.millPlantAgents = millPlant.agents;
|
|
355
|
+
tree.codex = overlayJsonFacet(
|
|
356
|
+
resolveInside(targetDir, params.codex),
|
|
357
|
+
path.join(targetDir, ".xray", "codex.json"),
|
|
358
|
+
);
|
|
359
|
+
tree.features = overlayJsonFacet(
|
|
360
|
+
resolveInside(targetDir, params.features),
|
|
361
|
+
path.join(targetDir, ".xray", "features.json"),
|
|
362
|
+
);
|
|
363
|
+
tree.config = overlayJsonFacet(
|
|
364
|
+
resolveInside(targetDir, params.config),
|
|
365
|
+
path.join(targetDir, ".xray", "config.json"),
|
|
366
|
+
);
|
|
367
|
+
tree.agentsCard = overlayAgentsCard(targetDir, params);
|
|
368
|
+
if (log && (tree.codex || tree.features || tree.config)) {
|
|
369
|
+
log("foundry-mint", "Overlaid consumer suit facets onto .xray", "info", {
|
|
370
|
+
constitution: tree.codex,
|
|
371
|
+
features: tree.features,
|
|
372
|
+
config: tree.config,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
return mintConsumerFromSsot(millPackageRoot, targetDir, log, tree);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
module.exports = {
|
|
379
|
+
DEFAULT_PARAMS,
|
|
380
|
+
deepMerge,
|
|
381
|
+
loadFoundryParams,
|
|
382
|
+
loadFoundryExtra,
|
|
383
|
+
wantsCostume,
|
|
384
|
+
millPlantDir,
|
|
385
|
+
millFillMillPlant,
|
|
386
|
+
resolveInside,
|
|
387
|
+
overlayJsonFacet,
|
|
388
|
+
overlayAgentsCard,
|
|
389
|
+
overlayConsumerTree,
|
|
390
|
+
listConsumerSkillNames,
|
|
391
|
+
listConsumerAgentFiles,
|
|
392
|
+
listSkillHangers,
|
|
393
|
+
mintConsumerFromSsot,
|
|
394
|
+
mintConsumerSuit,
|
|
395
|
+
isDogfood,
|
|
396
|
+
readPackageIdentity,
|
|
397
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Overlay their mill SSOT onto the hanger (cwd / FOUNDRY_ROOT).
|
|
4
|
+
* Not a fifth 0xray CLI. Not PPE.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { millPackageDir, resolveMillRoot } from "./mill-root.mjs";
|
|
11
|
+
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const { mintConsumerSuit } = require("./mint-suit.cjs");
|
|
14
|
+
|
|
15
|
+
function main() {
|
|
16
|
+
const target = resolveMillRoot();
|
|
17
|
+
const millRoot = millPackageDir();
|
|
18
|
+
const inventory = mintConsumerSuit(millRoot, target, (component, action, status) => {
|
|
19
|
+
process.stdout.write(`${component} ${action} ${status}\n`);
|
|
20
|
+
});
|
|
21
|
+
if (inventory.skipped) {
|
|
22
|
+
process.stderr.write("mint: skipped dogfood / 0xray exo plant (not a consumer hanger)\n");
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
process.stdout.write(`${JSON.stringify(inventory, null, 2)}\n`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const isMain =
|
|
29
|
+
process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
30
|
+
|
|
31
|
+
if (isMain) {
|
|
32
|
+
main();
|
|
33
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@0xray/foundry",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "0xRay mill: mint their suit params, stamp, gate, publish, CI report, git hooks. Not the worn exo.",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/0xRayAI/xray.git",
|
|
11
|
+
"directory": "scripts/foundry"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/0xRayAI/xray/tree/main/scripts/foundry",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/0xRayAI/xray/issues"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"0xray-foundry": "cli.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"*.mjs",
|
|
25
|
+
"*.cjs",
|
|
26
|
+
"cli.js",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"CHANGELOG.md",
|
|
30
|
+
"plant"
|
|
31
|
+
],
|
|
32
|
+
"exports": {
|
|
33
|
+
"./version-manager": "./version-manager.mjs",
|
|
34
|
+
"./validate-release-docs": "./validate-release-docs.mjs",
|
|
35
|
+
"./reconcile-version": "./reconcile-version.mjs",
|
|
36
|
+
"./release": "./release.mjs",
|
|
37
|
+
"./release-gate": "./release-gate.mjs",
|
|
38
|
+
"./mill-root": "./mill-root.mjs",
|
|
39
|
+
"./mint-suit": "./mint-suit.cjs",
|
|
40
|
+
"./ci-monitor": "./ci-monitor.mjs",
|
|
41
|
+
"./hooks": "./hooks.mjs",
|
|
42
|
+
"./docs-build": "./docs-build.mjs"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"0xray",
|
|
49
|
+
"foundry",
|
|
50
|
+
"mill",
|
|
51
|
+
"release",
|
|
52
|
+
"changelog"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mill
|
|
3
|
+
description: Mill mill-fill mill plant. Their plant is the suit. Factory Repertoire. Not 45/42 costume.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Mill
|
|
7
|
+
|
|
8
|
+
The mill **fastens** their plant onto the hanger. It does not copy the 0xRay 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 mill mill-fill mill names they did not plant stay. JSON always merges.
|
|
13
|
+
|
|
14
|
+
**Factory:** Repertoire organ 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 garment. Default is empty garment + this mill mill-fill mill plant.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx @0xray/foundry mint
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Nested mill: `node node_modules/0xray/scripts/foundry/cli.js mint`.
|