0xray 4.0.42 → 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/.plugin/plugin.json +1 -1
- package/.xray/features.json +8 -5
- package/.xray/features.schema.json +8 -0
- package/AGENTS-consumer.md +9 -7
- package/AGENTS.md +19 -10
- package/LICENSE +1 -1
- package/README.md +15 -9
- package/SKILLS.md +3 -1
- package/dist/.gitignore.default +4 -1
- package/dist/AGENTS.md +19 -10
- package/dist/CHANGELOG.md +109 -11
- package/dist/LICENSE +1 -1
- package/dist/README.md +15 -9
- package/dist/SKILLS.md +3 -1
- package/dist/cli/commands/opencode-install.js +3 -9
- package/dist/cli/commands/plugin-commands.d.ts +4 -0
- package/dist/cli/commands/plugin-commands.js +84 -41
- 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/core/config-loader.js +4 -4
- package/dist/core/features-config.d.ts +1 -1
- package/dist/core/features-config.js +3 -3
- 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/hooks.json +6 -3
- package/dist/integrations/cursor/hooks/pre-tool-use.js +104 -37
- package/dist/integrations/cursor/hooks/xray-cloud-hook.sh +9 -1
- 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 +6 -76
- package/dist/integrations/grok/hooks/session-start.js +4 -2
- package/dist/integrations/hermes-agent/bridge.mjs +17 -1
- package/dist/integrations/hooks/delegation-gate-runtime.mjs +3 -2
- package/dist/integrations/hooks/goggles-mcp.mjs +23 -181
- package/dist/integrations/hooks/goggles-pipeline.mjs +606 -571
- package/dist/integrations/hooks/goggles-planes.json +95 -46
- package/dist/integrations/hooks/plates.cjs +84 -0
- package/dist/integrations/hooks/pre-tool-speak.mjs +74 -0
- 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 +24 -7
- package/dist/integrations/openclaw/plugin/xray-pre-tool/index.js +26 -2
- 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/monitoring/{nudge-watchdog.d.ts → nudge-monitor.d.ts} +14 -5
- package/dist/monitoring/{nudge-watchdog.js → nudge-monitor.js} +42 -17
- 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 +20 -0
- package/dist/processors/implementations/nudge-processor.d.ts +1 -1
- package/dist/processors/implementations/nudge-processor.js +4 -4
- package/dist/skills/orchestrator/SKILL.md +1 -7
- package/docs-site/docs/plates/activity-log.md +1 -1
- package/docs-site/docs/plates/asides.md +35 -0
- package/docs-site/docs/plates/bifrost.md +48 -0
- package/docs-site/docs/plates/boot.md +2 -2
- package/docs-site/docs/plates/centrifuge.md +1 -1
- package/docs-site/docs/plates/confer.md +35 -0
- package/docs-site/docs/plates/consumer-mcp.md +35 -0
- package/docs-site/docs/plates/cursor-compact.md +1 -1
- 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/glossary.md +7 -3
- package/docs-site/docs/plates/goggles.md +12 -1
- package/docs-site/docs/plates/governance.md +1 -1
- package/docs-site/docs/plates/grok-compact.md +1 -1
- package/docs-site/docs/plates/grokbot.md +3 -1
- package/docs-site/docs/plates/ground.md +35 -0
- package/docs-site/docs/plates/hermes.md +35 -0
- package/docs-site/docs/plates/host-pack.md +1 -1
- package/docs-site/docs/plates/house.md +1 -1
- package/docs-site/docs/plates/index.md +16 -7
- package/docs-site/docs/plates/inference-cycle.md +35 -0
- package/docs-site/docs/plates/inference-files.md +1 -1
- package/docs-site/docs/plates/kept-line.md +1 -1
- package/docs-site/docs/plates/kits.md +1 -1
- package/docs-site/docs/plates/lens-gate.md +8 -6
- package/docs-site/docs/plates/lens-page.md +1 -1
- package/docs-site/docs/plates/memory-recall.md +1 -1
- package/docs-site/docs/plates/notes-page.md +2 -2
- package/docs-site/docs/plates/openclaw.md +35 -0
- package/docs-site/docs/plates/opencode.md +35 -0
- package/docs-site/docs/plates/orchestration.md +1 -1
- package/docs-site/docs/plates/package-face.md +1 -1
- package/docs-site/docs/plates/payload-heat.md +1 -1
- package/docs-site/docs/plates/pickup-stamp.md +1 -1
- package/docs-site/docs/plates/pre-tool.md +1 -1
- package/docs-site/docs/plates/processor.md +1 -1
- package/docs-site/docs/plates/record-map.md +1 -1
- package/docs-site/docs/plates/reflection-page.md +1 -1
- package/docs-site/docs/plates/repertoire.md +35 -0
- package/docs-site/docs/plates/reporting.md +1 -1
- package/docs-site/docs/plates/review.md +1 -1
- package/docs-site/docs/plates/routing.md +1 -1
- package/docs-site/docs/plates/session-capture.md +1 -1
- package/docs-site/docs/plates/site-manual.md +1 -1
- package/docs-site/docs/plates/stamp-plate.md +1 -1
- package/docs-site/docs/plates/station-card.md +1 -1
- package/docs-site/docs/plates/station-heat.md +1 -1
- package/docs-site/docs/plates/suit-organs.md +1 -1
- package/docs-site/docs/plates/suit-settings.md +1 -1
- package/docs-site/docs/plates/suit-wear.md +1 -1
- package/docs-site/docs/plates/suit.md +3 -3
- 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 +2 -2
- package/docs-site/docs/plates/work-fresh.md +1 -1
- package/docs-site/docs/plates/write-home.md +1 -1
- package/llms.txt +14 -6
- package/package.json +3 -3
- package/scripts/foundry/README.md +5 -3
- 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-gate.mjs +111 -15
- package/scripts/foundry/release.mjs +9 -1
- package/scripts/foundry/validate-release-docs.mjs +2 -2
- package/scripts/foundry/version-manager.mjs +12 -4
- package/scripts/mjs/verify-pipeline-facets.mjs +1 -1
- package/scripts/node/install-bridges.cjs +91 -46
- package/scripts/node/pack-tmp-suit-proof.mjs +37 -10
- package/src/integrations/openclaw/plugin/xray-pre-tool/index.js +26 -2
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/skills/orchestrator/SKILL.md +1 -7
- package/vendor/@0xray/repertoire/README.md +1 -1
- package/xray/agents_template.md +2 -2
- package/xray/features.json +8 -5
- package/xray/features.schema.json +8 -0
- package/docs-site/docs/plates/live-mesh.md +0 -41
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
* release-gate.mjs — Single release checkpoint.
|
|
4
4
|
*
|
|
5
5
|
* Modes:
|
|
6
|
-
* (default) build + test + consumer smoke (fresh + upgrade merge)
|
|
6
|
+
* (default) build + test + docs + consumer smoke (fresh + upgrade merge)
|
|
7
7
|
* --verify-only git + reconcile + release docs + consumer smoke — after push, before publish
|
|
8
8
|
* --skip-smoke skip consumer install smoke
|
|
9
|
-
* --
|
|
9
|
+
* --prove-published Gate D only: npm lists this version, the annotated tag points at HEAD,
|
|
10
|
+
* and the GitHub Release exists. This command does not publish.
|
|
11
|
+
* --skip-docs refused. Gate C runs on every gate.
|
|
10
12
|
*
|
|
11
13
|
* Usage:
|
|
12
14
|
* npx @0xray/foundry gate
|
|
@@ -21,7 +23,7 @@ import { isXrayExoRepo, millScript, readRootPackage, resolveMillRoot } from "./m
|
|
|
21
23
|
const rootDir = resolveMillRoot();
|
|
22
24
|
const verifyOnly = process.argv.includes("--verify-only");
|
|
23
25
|
const skipSmoke = process.argv.includes("--skip-smoke");
|
|
24
|
-
const
|
|
26
|
+
const provePublished = process.argv.includes("--prove-published");
|
|
25
27
|
|
|
26
28
|
function step(label, cmd) {
|
|
27
29
|
console.log(`\n${"─".repeat(60)}\n🔄 ${label}\n${"─".repeat(60)}\n`);
|
|
@@ -32,18 +34,114 @@ function docsCheckCmd() {
|
|
|
32
34
|
return `${JSON.stringify(process.execPath)} ${JSON.stringify(millScript("validate-release-docs.mjs"))}`;
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
|
|
38
|
+
/** Gate C is the docs check. A ship gate that skips it is not a pass. */
|
|
39
|
+
export function gateCRefusal(argv = process.argv) {
|
|
40
|
+
if (argv.includes("--skip-docs")) {
|
|
41
|
+
return "Gate C cannot be skipped. Remove --skip-docs. The docs check runs on every gate.";
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Gate D facts. listed, tagAnnotated, and tagMatchesHead are booleans.
|
|
48
|
+
* releaseUrl is the GitHub Release page, or empty when that page is missing.
|
|
49
|
+
*/
|
|
50
|
+
export function provePublishedRelease(proof) {
|
|
51
|
+
const version = proof.version;
|
|
52
|
+
const errors = [];
|
|
53
|
+
if (proof.listed !== true) errors.push(`npm does not list ${version}`);
|
|
54
|
+
if (proof.tagAnnotated !== true) errors.push(`annotated tag v${version} is missing`);
|
|
55
|
+
if (proof.tagMatchesHead !== true) errors.push(`annotated tag v${version} does not point at HEAD`);
|
|
56
|
+
if (typeof proof.releaseUrl !== "string" || proof.releaseUrl.length === 0) {
|
|
57
|
+
errors.push(`GitHub Release v${version} is missing`);
|
|
58
|
+
}
|
|
59
|
+
return errors;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function gitOut(cmd) {
|
|
63
|
+
return execSync(cmd, { cwd: rootDir, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function originSlug() {
|
|
67
|
+
const url = gitOut("git remote get-url origin");
|
|
68
|
+
const match = url.match(/github\.com[:/]([^/]+)\/([^/.]+)(?:\.git)?$/);
|
|
69
|
+
if (!match) throw new Error(`origin is not a GitHub repo: ${url}`);
|
|
70
|
+
return { owner: match[1], repo: match[2] };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function collectPublishedProof() {
|
|
74
|
+
const pkg = readRootPackage(rootDir);
|
|
75
|
+
const version = pkg.version;
|
|
76
|
+
const name = pkg.name;
|
|
77
|
+
let listed = false;
|
|
78
|
+
try {
|
|
79
|
+
const view = gitOut(`npm view ${name}@${version} version`);
|
|
80
|
+
listed = view.split(/\s+/).includes(version);
|
|
81
|
+
} catch {
|
|
82
|
+
listed = false;
|
|
83
|
+
}
|
|
84
|
+
let tagAnnotated = false;
|
|
85
|
+
let tagMatchesHead = false;
|
|
86
|
+
try {
|
|
87
|
+
tagAnnotated = gitOut(`git cat-file -t v${version}`) === "tag";
|
|
88
|
+
const tagged = gitOut(`git rev-parse v${version}^{}`);
|
|
89
|
+
const head = gitOut("git rev-parse HEAD");
|
|
90
|
+
tagMatchesHead = tagAnnotated && tagged === head;
|
|
91
|
+
} catch {
|
|
92
|
+
tagAnnotated = false;
|
|
93
|
+
tagMatchesHead = false;
|
|
94
|
+
}
|
|
95
|
+
let releaseUrl = "";
|
|
96
|
+
try {
|
|
97
|
+
const { owner, repo } = originSlug();
|
|
98
|
+
const body = gitOut(
|
|
99
|
+
`curl -fsS -H "Accept: application/vnd.github+json" -H "User-Agent: 0xray-release-gate" https://api.github.com/repos/${owner}/${repo}/releases/tags/v${version}`,
|
|
100
|
+
);
|
|
101
|
+
const parsed = JSON.parse(body);
|
|
102
|
+
releaseUrl = typeof parsed.html_url === "string" ? parsed.html_url : "";
|
|
103
|
+
} catch {
|
|
104
|
+
releaseUrl = "";
|
|
105
|
+
}
|
|
106
|
+
return { version, name, listed, tagAnnotated, tagMatchesHead, releaseUrl };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function runProvePublished() {
|
|
110
|
+
const proof = collectPublishedProof();
|
|
111
|
+
const errors = provePublishedRelease(proof);
|
|
112
|
+
process.stdout.write(`\nGate D — ${proof.name}@${proof.version}\n`);
|
|
113
|
+
if (errors.length) {
|
|
114
|
+
for (const error of errors) process.stderr.write(`❌ ${error}\n`);
|
|
115
|
+
process.stderr.write("\n❌ GATE D FAILED — npm listing, annotated tag at HEAD, and GitHub Release are all required\n\n");
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
process.stdout.write(`✅ npm lists ${proof.version}\n`);
|
|
119
|
+
process.stdout.write(`✅ annotated tag v${proof.version} points at HEAD\n`);
|
|
120
|
+
process.stdout.write(`✅ GitHub Release ${proof.releaseUrl}\n`);
|
|
121
|
+
process.stdout.write("\n✅ GATE D PROVEN\n\n");
|
|
122
|
+
}
|
|
123
|
+
|
|
35
124
|
function runLightGate() {
|
|
36
125
|
const pkg = readRootPackage(rootDir);
|
|
37
126
|
if (verifyOnly) {
|
|
38
|
-
|
|
127
|
+
step("1/1 Release docs (light)", docsCheckCmd());
|
|
39
128
|
return;
|
|
40
129
|
}
|
|
41
130
|
if (pkg.scripts.build) step("1/3 Build", "npm run build");
|
|
42
131
|
if (pkg.scripts.test) step("2/3 Tests", "npm test");
|
|
43
|
-
|
|
132
|
+
step("3/3 Release docs (light)", docsCheckCmd());
|
|
44
133
|
}
|
|
45
134
|
|
|
46
135
|
function main() {
|
|
136
|
+
const refused = gateCRefusal();
|
|
137
|
+
if (refused) {
|
|
138
|
+
process.stderr.write(`\n❌ ${refused}\n\n`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
if (provePublished) {
|
|
142
|
+
runProvePublished();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
47
145
|
const mode = verifyOnly ? "verify" : "full";
|
|
48
146
|
console.log("\n╔════════════════════════════════════════════════════════╗");
|
|
49
147
|
console.log(`║ 🛡️ 0xRay Release Gate (${mode})`.padEnd(57) + "║");
|
|
@@ -54,9 +152,7 @@ function main() {
|
|
|
54
152
|
runLightGate();
|
|
55
153
|
} else if (verifyOnly) {
|
|
56
154
|
step("1/6 Git + reconcile", "node scripts/node/pre-publish-guard.js --verify-only");
|
|
57
|
-
|
|
58
|
-
step("2/6 Release docs", docsCheckCmd());
|
|
59
|
-
}
|
|
155
|
+
step("2/6 Release docs", docsCheckCmd());
|
|
60
156
|
step(
|
|
61
157
|
"3/6 Plugin infrastructure",
|
|
62
158
|
"node scripts/test/validate-plugins-e2e.cjs --structural-only",
|
|
@@ -75,9 +171,7 @@ function main() {
|
|
|
75
171
|
`${JSON.stringify(process.execPath)} ${JSON.stringify(millScript("assert-packed-dist-cli.mjs"))}`,
|
|
76
172
|
);
|
|
77
173
|
step("3/8 Tests", "npm run test:comprehensive");
|
|
78
|
-
|
|
79
|
-
step("4/8 Release docs", docsCheckCmd());
|
|
80
|
-
}
|
|
174
|
+
step("4/8 Release docs", docsCheckCmd());
|
|
81
175
|
step(
|
|
82
176
|
"5/8 Consumer hook verifiers",
|
|
83
177
|
"npm run verify:pre-commit-diff && npm run verify:pre-push-diff && node scripts/mjs/verify-delegation-gate-core.mjs --host=grok && node scripts/mjs/verify-delegation-gate-core.mjs --host=hermes && node scripts/mjs/verify-delegation-gate-core.mjs --host=opencode && node scripts/mjs/verify-hermes-session-start.mjs && node scripts/mjs/verify-confer-core.mjs && npm run verify:user-aside && npm run build && node scripts/mjs/verify-pipeline-facets.mjs --package-only",
|
|
@@ -91,9 +185,11 @@ function main() {
|
|
|
91
185
|
}
|
|
92
186
|
}
|
|
93
187
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
188
|
+
process.stdout.write("\n╔════════════════════════════════════════════════════════╗\n");
|
|
189
|
+
process.stdout.write("║ ✅ GATE A–C LOCAL PASS ║\n");
|
|
190
|
+
process.stdout.write("║ Gate D is not proven by this command. ║\n");
|
|
191
|
+
process.stdout.write("╚════════════════════════════════════════════════════════╝\n");
|
|
192
|
+
process.stdout.write("Prove D after publish: npx @0xray/foundry gate --prove-published\n\n");
|
|
97
193
|
} catch {
|
|
98
194
|
console.error("\n❌ RELEASE GATE FAILED — do not tag or publish\n");
|
|
99
195
|
process.exit(1);
|
|
@@ -105,4 +201,4 @@ const isMain =
|
|
|
105
201
|
|
|
106
202
|
if (isMain) {
|
|
107
203
|
main();
|
|
108
|
-
}
|
|
204
|
+
}
|
|
@@ -57,7 +57,7 @@ function run(cmd, label) {
|
|
|
57
57
|
cwd: rootDir,
|
|
58
58
|
stdio: "inherit",
|
|
59
59
|
encoding: "utf-8",
|
|
60
|
-
env: { ...process.env, FOUNDRY_ROOT: rootDir },
|
|
60
|
+
env: { ...process.env, FOUNDRY_ROOT: rootDir, FOUNDRY_VIA_RELEASE: "1" },
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -191,6 +191,8 @@ async function main() {
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
run(`git push origin v${version}`, "push tag");
|
|
194
|
+
say("Step 8: Gate D");
|
|
195
|
+
runMill("release-gate.mjs", ["--prove-published"], "gate D");
|
|
194
196
|
return;
|
|
195
197
|
}
|
|
196
198
|
|
|
@@ -281,6 +283,12 @@ async function main() {
|
|
|
281
283
|
console.log(` would: git tag -a v${newVersion}`);
|
|
282
284
|
}
|
|
283
285
|
run(`git push origin v${newVersion}`, "push tag");
|
|
286
|
+
say("Step 8: Gate D");
|
|
287
|
+
runMill("release-gate.mjs", ["--prove-published"], "gate D");
|
|
288
|
+
if (dryRun) {
|
|
289
|
+
say("Dry run did not prove Gate D.");
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
284
292
|
|
|
285
293
|
console.log("\n╔════════════════════════════════════════════════════════╗");
|
|
286
294
|
console.log("║ ✅ Release Complete! ║");
|
|
@@ -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
|
}
|
|
@@ -110,7 +110,7 @@ if (packageOnly) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// P1 — autonomous reporting
|
|
113
|
-
if (features?.autonomous_reporting?.enabled ===
|
|
113
|
+
if (features?.autonomous_reporting?.enabled === false) {
|
|
114
114
|
pass('autonomous_reporting.enabled');
|
|
115
115
|
} else {
|
|
116
116
|
fail('autonomous_reporting.enabled');
|
|
@@ -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);
|
|
@@ -1016,16 +1065,18 @@ const CURSOR_HOOK_EVENTS = [
|
|
|
1016
1065
|
["beforeReadFile", "before-read-file.sh"],
|
|
1017
1066
|
];
|
|
1018
1067
|
|
|
1068
|
+
const CURSOR_GATE_EVENTS = new Set(["preToolUse", "beforeShellExecution", "beforeReadFile"]);
|
|
1069
|
+
|
|
1019
1070
|
const CURSOR_WEAR_BACKUP = "hooks.json.xray-before";
|
|
1020
1071
|
|
|
1021
1072
|
const CLOUD_SAFE_CURSOR_HOOKS = {
|
|
1022
1073
|
version: 1,
|
|
1023
1074
|
hooks: {
|
|
1024
|
-
preToolUse: [{ command: ".cursor/hooks/pre-tool-use.sh" }],
|
|
1075
|
+
preToolUse: [{ command: ".cursor/hooks/pre-tool-use.sh", failClosed: true }],
|
|
1025
1076
|
preCompact: [{ command: ".cursor/hooks/pre-compact.sh" }],
|
|
1026
1077
|
afterFileEdit: [{ command: ".cursor/hooks/after-file-edit.sh" }],
|
|
1027
|
-
beforeShellExecution: [{ command: ".cursor/hooks/before-shell-execution.sh" }],
|
|
1028
|
-
beforeReadFile: [{ command: ".cursor/hooks/before-read-file.sh" }],
|
|
1078
|
+
beforeShellExecution: [{ command: ".cursor/hooks/before-shell-execution.sh", failClosed: true }],
|
|
1079
|
+
beforeReadFile: [{ command: ".cursor/hooks/before-read-file.sh", failClosed: true }],
|
|
1029
1080
|
},
|
|
1030
1081
|
};
|
|
1031
1082
|
|
|
@@ -1071,6 +1122,9 @@ function mergeCloudSafeCursorHooks(existing) {
|
|
|
1071
1122
|
!Array.isArray(current) || !current.some((h) => isRelativeCursorHookCommand(h && h.command));
|
|
1072
1123
|
if (!current || leftover || missingRelative) {
|
|
1073
1124
|
next.hooks[name] = entries;
|
|
1125
|
+
} else if (entries[0].failClosed) {
|
|
1126
|
+
// Gate events fail closed: our relative entry gets failClosed even on an existing hooks.json (re-wear upgrades it).
|
|
1127
|
+
next.hooks[name] = current.map((h) => (h && isRelativeCursorHookCommand(h.command) ? { ...h, failClosed: true } : h));
|
|
1074
1128
|
}
|
|
1075
1129
|
}
|
|
1076
1130
|
return next;
|
|
@@ -1279,7 +1333,10 @@ function installedHookCommand(fromDir, packageRoot, scriptName) {
|
|
|
1279
1333
|
function installedCursorHookEntries(fromDir, packageRoot) {
|
|
1280
1334
|
const hooks = {};
|
|
1281
1335
|
for (const [event, script] of CURSOR_HOOK_EVENTS) {
|
|
1282
|
-
|
|
1336
|
+
const entry = { command: installedHookCommand(fromDir, packageRoot, script) };
|
|
1337
|
+
// Gate events fail closed: Cursor denies when the hook crashes or prints no JSON.
|
|
1338
|
+
if (CURSOR_GATE_EVENTS.has(event)) entry.failClosed = true;
|
|
1339
|
+
hooks[event] = [entry];
|
|
1283
1340
|
}
|
|
1284
1341
|
return hooks;
|
|
1285
1342
|
}
|
|
@@ -1610,8 +1667,10 @@ function ownedDeletionEdits(tokens, elements) {
|
|
|
1610
1667
|
return edits;
|
|
1611
1668
|
}
|
|
1612
1669
|
|
|
1613
|
-
function renderEventEntry(command) {
|
|
1614
|
-
return
|
|
1670
|
+
function renderEventEntry(command, failClosed) {
|
|
1671
|
+
return failClosed
|
|
1672
|
+
? `{ "command": ${JSON.stringify(command)}, "failClosed": true }`
|
|
1673
|
+
: `{ "command": ${JSON.stringify(command)} }`;
|
|
1615
1674
|
}
|
|
1616
1675
|
|
|
1617
1676
|
function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
@@ -1627,7 +1686,7 @@ function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
|
1627
1686
|
const hooksPair = root.pairs.find((pair) => pair.key === "hooks");
|
|
1628
1687
|
if (!hooksPair) {
|
|
1629
1688
|
const inner = Object.entries(xrayByEvent)
|
|
1630
|
-
.map(([event, entries]) => `"${event}": [${renderEventEntry(entries[0].command)}]`)
|
|
1689
|
+
.map(([event, entries]) => `"${event}": [${renderEventEntry(entries[0].command, entries[0].failClosed)}]`)
|
|
1631
1690
|
.join(", ");
|
|
1632
1691
|
const needsComma = root.pairs.length > 0;
|
|
1633
1692
|
edits.push({
|
|
@@ -1642,9 +1701,10 @@ function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
|
1642
1701
|
const missing = [];
|
|
1643
1702
|
for (const [event, entries] of Object.entries(xrayByEvent)) {
|
|
1644
1703
|
const command = entries[0].command;
|
|
1704
|
+
const failClosed = entries[0].failClosed === true;
|
|
1645
1705
|
const pair = hooksObj.pairs.find((item) => item.key === event);
|
|
1646
1706
|
if (!pair) {
|
|
1647
|
-
missing.push([event, command]);
|
|
1707
|
+
missing.push([event, command, failClosed]);
|
|
1648
1708
|
continue;
|
|
1649
1709
|
}
|
|
1650
1710
|
if (tokens[pair.valIdx].type !== "[") throw new Error(`${event} is not an array`);
|
|
@@ -1666,7 +1726,7 @@ function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
|
1666
1726
|
edits.push({
|
|
1667
1727
|
start: tokens[pair.valEnd].start,
|
|
1668
1728
|
end: tokens[pair.valEnd].start,
|
|
1669
|
-
insert: `${elements.length > 0 ? ", " : ""}${renderEventEntry(command)}`,
|
|
1729
|
+
insert: `${elements.length > 0 ? ", " : ""}${renderEventEntry(command, failClosed)}`,
|
|
1670
1730
|
});
|
|
1671
1731
|
continue;
|
|
1672
1732
|
}
|
|
@@ -1678,13 +1738,17 @@ function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
|
1678
1738
|
insert: JSON.stringify(command),
|
|
1679
1739
|
});
|
|
1680
1740
|
}
|
|
1741
|
+
if (failClosed && !objectPairs(tokens, first.el.startIdx).pairs.some((p) => p.key === "failClosed")) {
|
|
1742
|
+
// Re-wear upgrades an older entry: gate events fail closed.
|
|
1743
|
+
edits.push({ start: first.cmd.token.end, end: first.cmd.token.end, insert: ', "failClosed": true' });
|
|
1744
|
+
}
|
|
1681
1745
|
if (owned.length > 1) {
|
|
1682
1746
|
edits.push(...ownedDeletionEdits(tokens, owned.slice(1).map((item) => item.el)));
|
|
1683
1747
|
}
|
|
1684
1748
|
}
|
|
1685
1749
|
if (missing.length > 0) {
|
|
1686
1750
|
const rendered = missing
|
|
1687
|
-
.map(([event, command]) => `"${event}": [${renderEventEntry(command)}]`)
|
|
1751
|
+
.map(([event, command, failClosed]) => `"${event}": [${renderEventEntry(command, failClosed)}]`)
|
|
1688
1752
|
.join(", ");
|
|
1689
1753
|
edits.push({
|
|
1690
1754
|
start: tokens[hooksObj.closeIdx].start,
|
|
@@ -1778,20 +1842,21 @@ function mergeInstalledCursorHooks(existing, xrayByEvent) {
|
|
|
1778
1842
|
continue;
|
|
1779
1843
|
}
|
|
1780
1844
|
const command = shipped[0].command;
|
|
1845
|
+
const extra = shipped[0].failClosed ? { failClosed: true } : {};
|
|
1781
1846
|
let replaced = false;
|
|
1782
1847
|
const next = [];
|
|
1783
1848
|
for (const entry of current) {
|
|
1784
1849
|
const cmd = entry && typeof entry === "object" ? entry.command : "";
|
|
1785
1850
|
if (isXrayHookCommand(cmd)) {
|
|
1786
1851
|
if (!replaced) {
|
|
1787
|
-
next.push({ ...entry, command });
|
|
1852
|
+
next.push({ ...entry, command, ...extra });
|
|
1788
1853
|
replaced = true;
|
|
1789
1854
|
}
|
|
1790
1855
|
continue;
|
|
1791
1856
|
}
|
|
1792
1857
|
next.push(entry);
|
|
1793
1858
|
}
|
|
1794
|
-
if (!replaced) next.push({ command });
|
|
1859
|
+
if (!replaced) next.push({ command, ...extra });
|
|
1795
1860
|
hooks[name] = next;
|
|
1796
1861
|
}
|
|
1797
1862
|
const doc = {};
|
|
@@ -1931,9 +1996,11 @@ function syncSetupSkillsAndRootLinks(packageRoot, targetDir) {
|
|
|
1931
1996
|
|
|
1932
1997
|
/** A byte-identical package skill is the old mirror. A different body still throws. */
|
|
1933
1998
|
function wornSkillMatchesPackage(packageRoot, targetDir, name) {
|
|
1934
|
-
const
|
|
1935
|
-
|
|
1936
|
-
|
|
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;
|
|
1937
2004
|
const packaged = path.join(source, name, "SKILL.md");
|
|
1938
2005
|
if (!fs.existsSync(packaged)) return false;
|
|
1939
2006
|
const packagedBytes = fs.readFileSync(packaged);
|
|
@@ -1961,12 +2028,11 @@ function packageShipsConsumerSuit(packageRoot) {
|
|
|
1961
2028
|
/**
|
|
1962
2029
|
* Consumer wear files: AGENTS.md, .gitignore, repertoire link, .xray config,
|
|
1963
2030
|
* bridges, mill plant (mill + inspect), and the scripts and dist links.
|
|
1964
|
-
*
|
|
2031
|
+
* Catalog skills and role files stay worn. Costume does not strip them.
|
|
1965
2032
|
* Does not install Cursor hooks or git hooks.
|
|
1966
2033
|
* Existing user files follow the 4.0.28 guards inside those functions.
|
|
1967
2034
|
*/
|
|
1968
2035
|
|
|
1969
|
-
const ROLE_SKILL_KEEP = new Set(["mill", "inspect"]);
|
|
1970
2036
|
const ROLE_SKILL_DIRS = [
|
|
1971
2037
|
[".opencode", "skills"],
|
|
1972
2038
|
[".grok", "plugins", "0xray", "skills"],
|
|
@@ -1974,26 +2040,6 @@ const ROLE_SKILL_DIRS = [
|
|
|
1974
2040
|
[".openclaw", "skills"],
|
|
1975
2041
|
];
|
|
1976
2042
|
|
|
1977
|
-
function removeCopiedRoleSkills(packageRoot, targetDir) {
|
|
1978
|
-
const source = fs.existsSync(path.join(packageRoot, "src", "skills"))
|
|
1979
|
-
? path.join(packageRoot, "src", "skills")
|
|
1980
|
-
: path.join(packageRoot, "dist", "skills");
|
|
1981
|
-
if (!fs.existsSync(source)) return;
|
|
1982
|
-
for (const parts of ROLE_SKILL_DIRS) {
|
|
1983
|
-
const dir = path.join(targetDir, ...parts);
|
|
1984
|
-
if (!fs.existsSync(dir)) continue;
|
|
1985
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
1986
|
-
if (!entry.isDirectory() || ROLE_SKILL_KEEP.has(entry.name)) continue;
|
|
1987
|
-
const packaged = path.join(source, entry.name, "SKILL.md");
|
|
1988
|
-
const worn = path.join(dir, entry.name, "SKILL.md");
|
|
1989
|
-
if (!fs.existsSync(packaged) || !fs.existsSync(worn)) continue;
|
|
1990
|
-
const same = fs.readFileSync(packaged).equals(fs.readFileSync(worn));
|
|
1991
|
-
if (!same) continue;
|
|
1992
|
-
fs.rmSync(path.join(dir, entry.name), { recursive: true, force: true });
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
2043
|
function installConsumerProjectFiles(packageRoot, targetDir, log) {
|
|
1998
2044
|
const write = typeof log === "function" ? log : () => {};
|
|
1999
2045
|
if (!isConsumerInstall(packageRoot, targetDir) || !packageShipsConsumerSuit(packageRoot)) {
|
|
@@ -2012,7 +2058,6 @@ function installConsumerProjectFiles(packageRoot, targetDir, log) {
|
|
|
2012
2058
|
installGrokBridge(targetDir, packageRoot, write);
|
|
2013
2059
|
installHermesBridge(targetDir, packageRoot, write);
|
|
2014
2060
|
installOpenclawBridge(targetDir, packageRoot, write);
|
|
2015
|
-
removeCopiedRoleSkills(packageRoot, targetDir);
|
|
2016
2061
|
try {
|
|
2017
2062
|
mintConsumerSuit(packageRoot, targetDir, write);
|
|
2018
2063
|
} catch (err) {
|
|
@@ -39,17 +39,25 @@ export function foundryCliPath(millRoot) {
|
|
|
39
39
|
return path.join(millRoot, "cli.js");
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
/** Clean-room foundry env. The machine home is empty so a plugin on the real machine cannot fail the seat inspect. */
|
|
43
|
+
export function proofFoundryEnv(consumerRoot, baseEnv = process.env) {
|
|
43
44
|
const isolatedHome = path.join(consumerRoot, ".proof-home");
|
|
45
|
+
const machineHome = path.join(consumerRoot, ".proof-machine");
|
|
44
46
|
fs.mkdirSync(isolatedHome, { recursive: true });
|
|
47
|
+
fs.mkdirSync(machineHome, { recursive: true });
|
|
48
|
+
return {
|
|
49
|
+
...baseEnv,
|
|
50
|
+
FOUNDRY_ROOT: consumerRoot,
|
|
51
|
+
HOME: isolatedHome,
|
|
52
|
+
FOUNDRY_MACHINE_HOME: machineHome,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function runFoundryCli(millRoot, consumerRoot, args) {
|
|
45
57
|
return spawnSync(process.execPath, [foundryCliPath(millRoot), ...args], {
|
|
46
58
|
cwd: consumerRoot,
|
|
47
59
|
encoding: "utf8",
|
|
48
|
-
env:
|
|
49
|
-
...process.env,
|
|
50
|
-
FOUNDRY_ROOT: consumerRoot,
|
|
51
|
-
HOME: isolatedHome,
|
|
52
|
-
},
|
|
60
|
+
env: proofFoundryEnv(consumerRoot),
|
|
53
61
|
});
|
|
54
62
|
}
|
|
55
63
|
|
|
@@ -92,7 +100,25 @@ export function wornSkillNames(consumerRoot) {
|
|
|
92
100
|
);
|
|
93
101
|
}
|
|
94
102
|
|
|
95
|
-
|
|
103
|
+
function catalogSkillNamesNear(millRoot) {
|
|
104
|
+
let dir = millRoot;
|
|
105
|
+
for (let i = 0; i < 4 && dir; i += 1) {
|
|
106
|
+
for (const rel of ["dist/skills", "src/skills"]) {
|
|
107
|
+
const skills = path.join(dir, rel);
|
|
108
|
+
if (!fs.existsSync(skills)) continue;
|
|
109
|
+
const names = fs.readdirSync(skills).filter((name) =>
|
|
110
|
+
fs.existsSync(path.join(skills, name, "SKILL.md")),
|
|
111
|
+
);
|
|
112
|
+
if (names.length > 0) return names;
|
|
113
|
+
}
|
|
114
|
+
const parent = path.dirname(dir);
|
|
115
|
+
if (parent === dir) break;
|
|
116
|
+
dir = parent;
|
|
117
|
+
}
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function assertFastenedMillPlant(consumerRoot, millRoot = consumerRoot) {
|
|
96
122
|
const inventoryPath = path.join(consumerRoot, ".xray", "foundry-inventory.json");
|
|
97
123
|
if (!fs.existsSync(inventoryPath)) {
|
|
98
124
|
throw new Error("foundry-inventory.json missing — mill did not fasten");
|
|
@@ -118,8 +144,9 @@ export function assertFastenedMillPlant(consumerRoot) {
|
|
|
118
144
|
}
|
|
119
145
|
}
|
|
120
146
|
const worn = wornSkillNames(consumerRoot);
|
|
147
|
+
const catalog = catalogSkillNamesNear(millRoot);
|
|
121
148
|
const extras = worn.filter(
|
|
122
|
-
(name) => !MILL_PLANT_SKILLS.includes(name) && !FACTORY_SHOP_SKILLS.includes(name),
|
|
149
|
+
(name) => !MILL_PLANT_SKILLS.includes(name) && !FACTORY_SHOP_SKILLS.includes(name) && !catalog.includes(name),
|
|
123
150
|
);
|
|
124
151
|
if (extras.length > 0) {
|
|
125
152
|
throw new Error(`costume dump extras before hangar plant: ${extras.join(",")}`);
|
|
@@ -137,7 +164,7 @@ export function proveSuitAfterInstall(consumerRoot, millRoot) {
|
|
|
137
164
|
if (mint.status !== 0) {
|
|
138
165
|
throw new Error(`foundry mint --skip-live failed:\n${mint.stdout}\n${mint.stderr}`);
|
|
139
166
|
}
|
|
140
|
-
const inventory = assertFastenedMillPlant(consumerRoot);
|
|
167
|
+
const inventory = assertFastenedMillPlant(consumerRoot, millRoot);
|
|
141
168
|
if (inventory.suit !== "fastened" && inventory.suit !== "overlay") {
|
|
142
169
|
throw new Error(`expected fastened/overlay suit, got ${inventory.suit}`);
|
|
143
170
|
}
|
|
@@ -172,7 +199,7 @@ export function proveSuitAfterInstall(consumerRoot, millRoot) {
|
|
|
172
199
|
if (Array.isArray(plantVsWorn.extraSkills) && plantVsWorn.extraSkills.length > 0) {
|
|
173
200
|
throw new Error(`inspect extraSkills after hangar plant: ${plantVsWorn.extraSkills.join(",")}`);
|
|
174
201
|
}
|
|
175
|
-
const after = assertFastenedMillPlant(consumerRoot);
|
|
202
|
+
const after = assertFastenedMillPlant(consumerRoot, millRoot);
|
|
176
203
|
if (after.costume === true) {
|
|
177
204
|
throw new Error("costume flipped true after hangar plant");
|
|
178
205
|
}
|