0xray 4.0.0 → 4.0.2

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.
Files changed (59) hide show
  1. package/.mcp.json +15 -55
  2. package/.xray/features.json +2 -2
  3. package/AGENTS-consumer.md +6 -6
  4. package/AGENTS.md +9 -9
  5. package/README.md +19 -19
  6. package/SKILLS.md +2 -2
  7. package/dist/AGENTS.md +9 -9
  8. package/dist/CHANGELOG.md +54 -2
  9. package/dist/README.md +19 -19
  10. package/dist/SKILLS.md +2 -2
  11. package/dist/cli/commands/foundry-mint-wear.d.ts +2 -0
  12. package/dist/cli/commands/foundry-mint-wear.js +14 -0
  13. package/dist/cli/commands/hermes-install.js +2 -0
  14. package/dist/cli/commands/openclaw-install.js +2 -0
  15. package/dist/cli/commands/opencode-install.js +2 -0
  16. package/dist/cli/index.js +10 -1
  17. package/dist/enforcement/enforcer-tools.d.ts +7 -0
  18. package/dist/enforcement/enforcer-tools.js +18 -109
  19. package/dist/integrations/grok/grok-cli.js +2 -0
  20. package/dist/integrations/hermes-agent/bridge.mjs +7 -0
  21. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +4 -2
  22. package/dist/processors/implementations/version-compliance-processor.d.ts +3 -34
  23. package/dist/processors/implementations/version-compliance-processor.js +8 -135
  24. package/opencode.json +0 -8
  25. package/package.json +15 -14
  26. package/scripts/foundry/CHANGELOG.md +34 -0
  27. package/scripts/foundry/LICENSE +21 -0
  28. package/scripts/foundry/README.md +67 -0
  29. package/scripts/foundry/ci-monitor.mjs +115 -0
  30. package/scripts/foundry/cli.js +2 -0
  31. package/scripts/foundry/cli.mjs +55 -0
  32. package/scripts/foundry/docs-build.mjs +38 -0
  33. package/scripts/foundry/hooks.mjs +36 -0
  34. package/scripts/foundry/mill-root.mjs +62 -0
  35. package/scripts/foundry/mint-suit.cjs +313 -0
  36. package/scripts/foundry/mint.mjs +33 -0
  37. package/scripts/foundry/package.json +53 -0
  38. package/scripts/foundry/reconcile-version.mjs +150 -0
  39. package/scripts/foundry/release-gate.mjs +100 -0
  40. package/scripts/foundry/release.mjs +266 -0
  41. package/scripts/foundry/validate-release-docs.mjs +316 -0
  42. package/scripts/foundry/version-manager.mjs +444 -0
  43. package/scripts/node/ci-cd-auto-fix.cjs +12 -257
  44. package/scripts/node/consumer-install-smoke.mjs +57 -2
  45. package/scripts/node/enforce-version-compliance.sh +5 -20
  46. package/scripts/node/enforce-version-compliance.ts +13 -113
  47. package/scripts/node/foundry-shim.mjs +19 -0
  48. package/scripts/node/github-actions-monitor.cjs +13 -18
  49. package/scripts/node/postinstall.cjs +36 -1
  50. package/scripts/node/reconcile-version.mjs +3 -146
  51. package/scripts/node/release-gate.mjs +3 -74
  52. package/scripts/node/release.mjs +3 -171
  53. package/scripts/node/validate-release-docs.mjs +3 -285
  54. package/scripts/node/version-manager.mjs +3 -708
  55. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +4 -2
  56. package/xray/features.json +2 -2
  57. package/scripts/node/release.js +0 -17
  58. package/scripts/node/sync-versions.mjs +0 -18
  59. package/scripts/node/universal-version-manager.js +0 -1043
@@ -1,147 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * reconcile-version.mjs — Single version truth before bump/tag/publish.
4
- *
5
- * Sources: npm registry, package.json, latest git tag.
6
- *
7
- * Usage:
8
- * node scripts/node/reconcile-version.mjs # report
9
- * node scripts/node/reconcile-version.mjs --check # fail if not publish-ready
10
- * node scripts/node/reconcile-version.mjs patch # print next patch target
11
- * node scripts/node/reconcile-version.mjs patch --apply # write package.json
12
- */
13
-
14
- import { execSync } from "child_process";
15
- import fs from "fs";
16
- import path from "path";
17
- import { fileURLToPath } from "url";
18
-
19
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
- const rootDir = path.resolve(__dirname, "../..");
21
- const pkgPath = path.join(rootDir, "package.json");
22
-
23
- const args = process.argv.slice(2);
24
- const checkMode = args.includes("--check");
25
- const applyMode = args.includes("--apply");
26
- const printTarget = args.includes("--print-target");
27
- const bumpType = args.find((a) => ["major", "minor", "patch"].includes(a)) || null;
28
-
29
- function run(cmd) {
30
- try {
31
- return execSync(cmd, { cwd: rootDir, encoding: "utf-8" }).trim();
32
- } catch {
33
- return null;
34
- }
35
- }
36
-
37
- function readLocalVersion() {
38
- return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
39
- }
40
-
41
- function readPublishedVersion() {
42
- const v = run("npm view 0xray version");
43
- return v || "0.0.0";
44
- }
45
-
46
- function readLatestTag() {
47
- const tag = run('git describe --tags --abbrev=0 2>/dev/null');
48
- if (!tag) return null;
49
- return tag.replace(/^v/, "");
50
- }
51
-
52
- function parseVersion(version) {
53
- const [major, minor, patch] = version.split(".").map((n) => parseInt(n, 10));
54
- return { major, minor, patch };
55
- }
56
-
57
- function bumpVersion(version, type) {
58
- const v = parseVersion(version);
59
- switch (type) {
60
- case "major":
61
- return `${v.major + 1}.0.0`;
62
- case "minor":
63
- return `${v.major}.${v.minor + 1}.0`;
64
- case "patch":
65
- return `${v.major}.${v.minor}.${v.patch + 1}`;
66
- default:
67
- return version;
68
- }
69
- }
70
-
71
- function compare(a, b) {
72
- const va = parseVersion(a);
73
- const vb = parseVersion(b);
74
- if (va.major !== vb.major) return va.major - vb.major;
75
- if (va.minor !== vb.minor) return va.minor - vb.minor;
76
- return va.patch - vb.patch;
77
- }
78
-
79
- function maxVersion(...versions) {
80
- return versions.reduce((best, cur) => (compare(cur, best) > 0 ? cur : best));
81
- }
82
-
83
- function fail(msg) {
84
- console.error(`❌ ${msg}`);
85
- process.exit(1);
86
- }
87
-
88
- function main() {
89
- const local = readLocalVersion();
90
- const npm = readPublishedVersion();
91
- const tag = readLatestTag();
92
-
93
- const baseline = maxVersion(npm, tag || "0.0.0");
94
- const target = bumpType ? bumpVersion(baseline, bumpType) : local;
95
-
96
- if (printTarget && bumpType) {
97
- console.log(target);
98
- return;
99
- }
100
-
101
- console.log("\n=== Version Reconcile ===\n");
102
- console.log(` npm registry : ${npm}`);
103
- console.log(` package.json : ${local}`);
104
- console.log(` latest tag : ${tag ? `v${tag}` : "(none)"}`);
105
- if (bumpType) {
106
- console.log(` bump ${bumpType} : ${target} (from baseline ${baseline})`);
107
- }
108
-
109
- if (applyMode && bumpType) {
110
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
111
- pkg.version = target;
112
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
113
- console.log(`\n✅ Updated package.json → ${target}`);
114
- }
115
-
116
- if (!checkMode) {
117
- if (compare(local, npm) <= 0 && !bumpType) {
118
- console.log("\n⚠️ Local version is not ahead of npm — bump before publishing.");
119
- } else {
120
- console.log("\n✅ Reconcile report complete.");
121
- }
122
- return;
123
- }
124
-
125
- // Strict checks for release gate / pre-tag
126
- if (compare(local, npm) <= 0) {
127
- fail(`package.json (${local}) must be > npm (${npm}). Run: node scripts/node/reconcile-version.mjs patch --apply`);
128
- }
129
-
130
- if (tag && compare(local, tag) < 0) {
131
- fail(`package.json (${local}) is behind latest tag v${tag}`);
132
- }
133
-
134
- if (tag && compare(local, tag) === 0 && compare(npm, local) < 0) {
135
- fail(`tag v${tag} exists but npm is only ${npm} — publish or retag`);
136
- }
137
-
138
- if (tag && compare(local, tag) > 0 && compare(npm, tag) === 0) {
139
- console.log(`✅ Ready to tag v${local} (npm at ${npm})`);
140
- } else if (compare(local, npm) > 0) {
141
- console.log(`✅ Ready to publish ${local} (npm at ${npm})`);
142
- } else {
143
- console.log("✅ Version reconcile OK");
144
- }
145
- }
146
-
147
- main();
2
+ export * from "../foundry/reconcile-version.mjs";
3
+ import { millMain } from "./foundry-shim.mjs";
4
+ millMain(import.meta.url, "reconcile-version.mjs");
@@ -1,75 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * release-gate.mjs — Single release checkpoint.
4
- *
5
- * Modes:
6
- * (default) build + test + consumer smoke (fresh + upgrade merge)
7
- * --verify-only git + reconcile + release docs + consumer smoke — after push, before publish
8
- * --skip-smoke skip consumer install smoke
9
- * --skip-docs skip release docs validation (not recommended for publish)
10
- *
11
- * Usage:
12
- * node scripts/node/release-gate.mjs
13
- * node scripts/node/release-gate.mjs --verify-only
14
- */
15
-
16
- import { execSync } from "child_process";
17
- import path from "path";
18
- import { fileURLToPath } from "url";
19
-
20
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
- const rootDir = path.resolve(__dirname, "../..");
22
- const verifyOnly = process.argv.includes("--verify-only");
23
- const skipSmoke = process.argv.includes("--skip-smoke");
24
- const skipDocs = process.argv.includes("--skip-docs");
25
-
26
- function step(label, cmd) {
27
- console.log(`\n${"─".repeat(60)}\n🔄 ${label}\n${"─".repeat(60)}\n`);
28
- execSync(cmd, { cwd: rootDir, stdio: "inherit" });
29
- }
30
-
31
- function main() {
32
- const mode = verifyOnly ? "verify" : "full";
33
- console.log("\n╔════════════════════════════════════════════════════════╗");
34
- console.log(`║ 🛡️ 0xRay Release Gate (${mode})`.padEnd(57) + "║");
35
- console.log("╚════════════════════════════════════════════════════════╝");
36
-
37
- try {
38
- if (verifyOnly) {
39
- step("1/5 Git + reconcile", "node scripts/node/pre-publish-guard.js --verify-only");
40
- if (!skipDocs) {
41
- step("2/5 Release docs", "node scripts/node/validate-release-docs.mjs");
42
- }
43
- step(
44
- "3/5 Plugin infrastructure",
45
- "node scripts/test/validate-plugins-e2e.cjs --structural-only",
46
- );
47
- if (!skipSmoke) {
48
- step("4/5 Consumer install smoke", "node scripts/node/consumer-install-smoke.mjs");
49
- }
50
- } else {
51
- step("1/6 Build", "npm run build");
52
- step("2/6 Tests", "npm test");
53
- step(
54
- "3/6 Consumer hook verifiers",
55
- "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",
56
- );
57
- step(
58
- "4/6 Plugin infrastructure",
59
- "node scripts/test/validate-plugins-e2e.cjs --structural-only",
60
- );
61
- if (!skipSmoke) {
62
- step("5/6 Consumer install smoke (fresh + upgrade)", "node scripts/node/consumer-install-smoke.mjs");
63
- }
64
- }
65
-
66
- console.log("\n╔════════════════════════════════════════════════════════╗");
67
- console.log("║ ✅ RELEASE GATE PASSED ║");
68
- console.log("╚════════════════════════════════════════════════════════╝\n");
69
- } catch {
70
- console.error("\n❌ RELEASE GATE FAILED — do not tag or publish\n");
71
- process.exit(1);
72
- }
73
- }
74
-
75
- main();
2
+ export * from "../foundry/release-gate.mjs";
3
+ import { millMain } from "./foundry-shim.mjs";
4
+ millMain(import.meta.url, "release-gate.mjs");
@@ -1,172 +1,4 @@
1
1
  #!/usr/bin/env node
2
-
3
- /**
4
- * Canonical 0xRay release script.
5
- *
6
- * 1. Bump version from npm registry baseline (reconcile-version)
7
- * 2. Release artifacts (CHANGELOG / README / AGENTS / docs) — before gate so docs tests pass
8
- * 3. Release gate (build + test + consumer smoke)
9
- * 4. Commit release artifacts → push
10
- * 5. Verify gate (reconcile + git + release docs + smoke)
11
- * 6. npm publish (idempotent — skip if version already on registry)
12
- * 7. Tag → push tag (only after successful publish)
13
- *
14
- * Usage:
15
- * npm run release:patch
16
- * node scripts/node/release.mjs patch --dry-run
17
- */
18
-
19
- import { execSync } from "child_process";
20
- import fs from "fs";
21
- import path from "path";
22
- import { getReleaseArtifactPaths } from "./version-manager.mjs";
23
-
24
- const rootDir = process.cwd();
25
- const args = process.argv.slice(2);
26
- const dryRun = args.includes("--dry-run");
27
- const releaseType = args.find((a) => ["major", "minor", "patch"].includes(a));
28
-
29
- function run(cmd, label) {
30
- console.log(`\n> ${cmd}`);
31
- if (dryRun) {
32
- console.log(` (dry-run skip: ${label})`);
33
- return;
34
- }
35
- execSync(cmd, { cwd: rootDir, stdio: "inherit", encoding: "utf-8" });
36
- }
37
-
38
- function readVersion() {
39
- return JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf-8")).version;
40
- }
41
-
42
- function currentBranch() {
43
- return execSync("git rev-parse --abbrev-ref HEAD", { cwd: rootDir, encoding: "utf-8" }).trim();
44
- }
45
-
46
- function npmVersionPublished(version) {
47
- try {
48
- const out = execSync(`npm view 0xray@${version} version`, {
49
- cwd: rootDir,
50
- encoding: "utf-8",
51
- stdio: ["pipe", "pipe", "pipe"],
52
- }).trim();
53
- return out === version;
54
- } catch {
55
- return false;
56
- }
57
- }
58
-
59
- function publishIdempotent(version) {
60
- if (dryRun) {
61
- console.log(` would: npm publish --access public (check 0xray@${version} first)`);
62
- return;
63
- }
64
- if (npmVersionPublished(version)) {
65
- console.log(`ℹ️ 0xray@${version} already on npm — skipping publish`);
66
- return;
67
- }
68
- execSync("npm publish --access public", { cwd: rootDir, stdio: "inherit", encoding: "utf-8" });
69
- console.log(`✅ Published 0xray@${version}`);
70
- }
71
-
72
- async function main() {
73
- if (!releaseType || !["major", "minor", "patch"].includes(releaseType)) {
74
- console.error("Usage: node scripts/node/release.mjs [patch|minor|major] [--dry-run]");
75
- process.exit(1);
76
- }
77
-
78
- console.log("╔════════════════════════════════════════════════════════╗");
79
- console.log("║ 🚀 0xRay Release (canonical) ║");
80
- console.log("╚════════════════════════════════════════════════════════╝");
81
- if (dryRun) console.log("\n⚠️ DRY RUN — no writes, commits, tags, or publish\n");
82
-
83
- // 1. Version bump from npm SSOT
84
- console.log(`\n📦 Step 1: Bump ${releaseType} from npm baseline`);
85
- if (dryRun) {
86
- execSync(`node scripts/node/reconcile-version.mjs ${releaseType}`, { cwd: rootDir, stdio: "inherit" });
87
- } else {
88
- run(`node scripts/node/reconcile-version.mjs ${releaseType} --apply`, "version bump");
89
- }
90
- const newVersion = dryRun
91
- ? execSync(`node scripts/node/reconcile-version.mjs ${releaseType} --print-target`, {
92
- cwd: rootDir,
93
- encoding: "utf-8",
94
- }).trim()
95
- : readVersion();
96
- console.log(`📌 Release version: ${newVersion}`);
97
-
98
- // 2. Artifacts BEFORE gate (docs vitest matches package.json version)
99
- console.log("\n📦 Step 2: Release artifacts (CHANGELOG, README, AGENTS, docs)");
100
- run("node scripts/node/version-manager.mjs --artifacts-only", "release artifacts");
101
-
102
- // 3. Gate
103
- console.log("\n📦 Step 3: Release gate (build + test + smoke)");
104
- run("node scripts/node/release-gate.mjs", "release gate");
105
-
106
- // 4. Commit + push
107
- console.log("\n📦 Step 4: Commit & push");
108
- const releaseFiles = getReleaseArtifactPaths();
109
- if (!dryRun) {
110
- run(`git add ${releaseFiles.join(" ")}`, "git add");
111
- try {
112
- execSync(`git commit --no-verify -m "release: v${newVersion}"`, { cwd: rootDir, stdio: "inherit" });
113
- } catch (e) {
114
- const out = String(e.stdout || e.stderr || "");
115
- if (!out.includes("nothing to commit")) throw e;
116
- console.log("ℹ️ Nothing to commit (release files unchanged)");
117
- }
118
- } else {
119
- console.log(` would: git add ${releaseFiles.join(" ")} && git commit -m "release: v${newVersion}"`);
120
- }
121
-
122
- const branch = currentBranch();
123
- run(`git push origin ${branch}`, "git push");
124
-
125
- // 5. Post-push verify before publish
126
- console.log("\n📦 Step 5: Verify gate (reconcile + git + docs + smoke)");
127
- run("node scripts/node/release-gate.mjs --verify-only", "verify gate");
128
-
129
- // 6. Publish (idempotent)
130
- console.log("\n📦 Step 6: npm publish");
131
- publishIdempotent(newVersion);
132
-
133
- // 7. Tag after publish
134
- console.log("\n📦 Step 7: Tag");
135
- if (!dryRun) {
136
- try {
137
- execSync(`git tag -a v${newVersion} -m "Release v${newVersion}"`, { cwd: rootDir, stdio: "inherit" });
138
- console.log(`✅ Created tag v${newVersion}`);
139
- } catch (e) {
140
- const msg = String(e.stderr || e.message || "");
141
- if (msg.includes("already exists")) {
142
- console.log(`ℹ️ Tag v${newVersion} already exists`);
143
- } else {
144
- throw e;
145
- }
146
- }
147
- } else {
148
- console.log(` would: git tag -a v${newVersion}`);
149
- }
150
- run(`git push origin v${newVersion}`, "push tag");
151
-
152
- console.log("\n╔════════════════════════════════════════════════════════╗");
153
- console.log("║ ✅ Release Complete! ║");
154
- console.log("╚════════════════════════════════════════════════════════╝");
155
- if (!dryRun) {
156
- console.log(`\n📦 0xray@${newVersion} 🏷 v${newVersion}\n`);
157
- const tweetPath = path.join(rootDir, "tweets", `v${newVersion}.md`);
158
- if (!fs.existsSync(tweetPath)) {
159
- fs.mkdirSync(path.join(rootDir, "tweets"), { recursive: true });
160
- fs.writeFileSync(
161
- tweetPath,
162
- `🎉 0xRay v${newVersion} is LIVE - consumer-safe upgrades!\n...\n\`\`\`\nnpm install 0xray@latest\n\`\`\`\n`,
163
- );
164
- console.log(`📝 Tweet template: tweets/v${newVersion}.md`);
165
- }
166
- }
167
- }
168
-
169
- main().catch((err) => {
170
- console.error("\n❌ Release failed:", err.message);
171
- process.exit(1);
172
- });
2
+ export * from "../foundry/release.mjs";
3
+ import { millMain } from "./foundry-shim.mjs";
4
+ millMain(import.meta.url, "release.mjs");
@@ -1,286 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * validate-release-docs.mjs — Blocking guard for release doc freshness.
4
- *
5
- * Ensures README, AGENTS, SKILLS, CHANGELOG, Docusaurus headers, and required
6
- * guides match package.json version and live framework counts before tag/publish.
7
- *
8
- * Usage:
9
- * node scripts/node/validate-release-docs.mjs
10
- * node scripts/node/validate-release-docs.mjs --json
11
- */
12
-
13
- import fs from 'fs';
14
- import path from 'path';
15
- import { fileURLToPath } from 'url';
16
- import {
17
- buildDocsHeader,
18
- DOCS_SITE_HEADER_FILES,
19
- getFrameworkCounts,
20
- getReleaseArtifactPaths,
21
- } from './version-manager.mjs';
22
-
23
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
- const defaultRoot = path.resolve(__dirname, '../..');
25
-
26
- const STANDARD_HEADER_RE =
27
- /^\*{0,2}v[\d.]+\*{0,2}\s*—\s*a suit that survives the context window/m;
28
-
29
- /** Guides that must exist and reference the current release (body or header). */
30
- const REQUIRED_GUIDES = [
31
- 'docs-site/docs/guides/features-since-3.1.md',
32
- 'docs-site/docs/guides/features-json.md',
33
- 'docs-site/docs/guides/memory-routing.md',
34
- 'docs-site/docs/guides/aside-context.md',
35
- 'docs-site/docs/guides/repertoire.md',
36
- 'docs-site/docs/guides/integrations.md',
37
- 'docs-site/docs/guides/consumer-migration.md',
38
- 'docs-site/docs/mcp/README.md',
39
- 'docs-site/docs/agents/README.md',
40
- ];
41
-
42
- const ROOT_DOC_CHECKS = [
43
- {
44
- rel: 'README.md',
45
- validate: (content, version, counts, header) => {
46
- const errors = [];
47
- if (!content.includes(header)) {
48
- errors.push(`missing kernel header (expected substring: ${header})`);
49
- }
50
- if (!content.includes('survive the context window') && !content.includes('survives the context window')) {
51
- errors.push('missing context-window / exo kernel copy');
52
- }
53
- if (!content.includes('SKILLS.md')) {
54
- errors.push('missing SKILLS.md reference in postinstall/docs section');
55
- }
56
- if (!content.includes('install-bridges') && !content.includes('installAllBridges')) {
57
- errors.push('missing install-bridges / installAllBridges reference');
58
- }
59
- return errors;
60
- },
61
- },
62
- {
63
- rel: 'AGENTS.md',
64
- validate: (content, version, counts) => {
65
- const errors = [];
66
- if (!content.includes(`**v${version}**`)) {
67
- errors.push(`missing version marker **v${version}**`);
68
- }
69
- if (!content.includes('survive the context window') && !content.includes('survives the context window') && !content.includes('Exo, not catalog')) {
70
- errors.push('missing exo / context-window kernel copy');
71
- }
72
- if (!content.includes('aside-context.md') && !content.includes('AsideContext')) {
73
- errors.push('missing AsideContext documentation reference');
74
- }
75
- if (!content.includes('SKILLS.md')) {
76
- errors.push('missing SKILLS.md link');
77
- }
78
- return errors;
79
- },
80
- },
81
- {
82
- rel: 'AGENTS-consumer.md',
83
- validate: (content, version, counts) => {
84
- const errors = [];
85
- if (!content.includes(`**v${version}**`)) {
86
- errors.push(`missing version marker **v${version}**`);
87
- }
88
- if (!content.includes('survive the context window') && !content.includes('survives the context window')) {
89
- errors.push('consumer header missing context-window kernel copy');
90
- }
91
- if (!content.includes('SKILLS.md')) {
92
- errors.push('missing SKILLS.md in postinstall section');
93
- }
94
- if (!content.includes('AsideContext')) {
95
- errors.push('missing AsideContext section');
96
- }
97
- return errors;
98
- },
99
- },
100
- {
101
- rel: 'SKILLS.md',
102
- validate: (content, version, counts) => {
103
- const errors = [];
104
- const expected = `**v${version}** — **${counts.skills} skills**`;
105
- if (!content.includes(expected)) {
106
- errors.push(`header stale (expected: ${expected})`);
107
- }
108
- return errors;
109
- },
110
- },
111
- ];
112
-
113
- function readFile(rootDir, rel) {
114
- const full = path.join(rootDir, rel);
115
- if (!fs.existsSync(full)) return null;
116
- return fs.readFileSync(full, 'utf-8');
117
- }
118
-
119
- function getPackageVersion(rootDir) {
120
- const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf-8'));
121
- return pkg.version;
122
- }
123
-
124
- function getCodexTermCount(rootDir) {
125
- const codexPath = path.join(rootDir, '.xray/codex.json');
126
- if (!fs.existsSync(codexPath)) return null;
127
- const codex = JSON.parse(fs.readFileSync(codexPath, 'utf-8'));
128
- return Object.keys(codex.terms || {}).length;
129
- }
130
-
131
- function validateChangelog(content, version) {
132
- const errors = [];
133
- const first = content.match(/^## \[([^\]]+)\]/m);
134
- if (!first) {
135
- errors.push('no version sections found');
136
- return errors;
137
- }
138
- if (first[1] !== version) {
139
- errors.push(`top CHANGELOG entry is [${first[1]}], expected [${version}]`);
140
- }
141
- if (!content.includes(`## [${version}]`)) {
142
- errors.push(`missing ## [${version}] section`);
143
- }
144
- return errors;
145
- }
146
-
147
- function validateStandardHeader(content, expectedHeader, label) {
148
- if (!content.includes(expectedHeader) && !STANDARD_HEADER_RE.test(content)) {
149
- return [`${label}: missing kernel header (expected substring: ${expectedHeader})`];
150
- }
151
- return [];
152
- }
153
-
154
- function validateDocusaurusTagline(content) {
155
- const errors = [];
156
- if (!content.includes('survive the context window') && !content.includes('survives the context window')) {
157
- errors.push('docusaurus.config.ts tagline missing context-window kernel copy');
158
- }
159
- return errors;
160
- }
161
-
162
- /**
163
- * @param {string} [rootDir]
164
- * @returns {{ ok: boolean; version: string; counts: object; errors: string[]; warnings: string[] }}
165
- */
166
- export function validateReleaseDocs(rootDir = defaultRoot) {
167
- const errors = [];
168
- const warnings = [];
169
- const version = getPackageVersion(rootDir);
170
- const counts = getFrameworkCounts(rootDir);
171
- const expectedHeader = buildDocsHeader(counts, version);
172
- const codexTerms = getCodexTermCount(rootDir);
173
-
174
- if (codexTerms !== null && counts.codexTerms && codexTerms !== counts.codexTerms) {
175
- warnings.push(`codex.json has ${codexTerms} terms (counts.codexTerms is ${counts.codexTerms})`);
176
- }
177
-
178
- const changelog = readFile(rootDir, 'CHANGELOG.md');
179
- if (!changelog) {
180
- errors.push('CHANGELOG.md missing');
181
- } else {
182
- errors.push(...validateChangelog(changelog, version).map((e) => `CHANGELOG.md: ${e}`));
183
- }
184
-
185
- for (const check of ROOT_DOC_CHECKS) {
186
- const content = readFile(rootDir, check.rel);
187
- if (!content) {
188
- errors.push(`${check.rel}: file missing`);
189
- continue;
190
- }
191
- const relErrors = check.validate(content, version, counts, expectedHeader);
192
- for (const e of relErrors) {
193
- errors.push(`${check.rel}: ${e}`);
194
- }
195
- }
196
-
197
- for (const rel of DOCS_SITE_HEADER_FILES) {
198
- const content = readFile(rootDir, rel);
199
- if (!content) {
200
- errors.push(`${rel}: file missing`);
201
- continue;
202
- }
203
- errors.push(...validateStandardHeader(content, expectedHeader, rel));
204
- }
205
-
206
- for (const rel of REQUIRED_GUIDES) {
207
- const content = readFile(rootDir, rel);
208
- if (!content) {
209
- errors.push(`${rel}: required guide missing`);
210
- continue;
211
- }
212
- if (content.trim().length < 200) {
213
- errors.push(`${rel}: guide too short — likely stub`);
214
- }
215
- if (rel.includes('features-since-3.1') && !content.includes(version)) {
216
- errors.push(`${rel}: must reference current version ${version}`);
217
- }
218
- }
219
-
220
- const docusaurus = readFile(rootDir, 'docs-site/docusaurus.config.ts');
221
- if (!docusaurus) {
222
- errors.push('docs-site/docusaurus.config.ts: missing');
223
- } else {
224
- errors.push(...validateDocusaurusTagline(docusaurus));
225
- }
226
-
227
- for (const rel of getReleaseArtifactPaths()) {
228
- if (!fs.existsSync(path.join(rootDir, rel))) {
229
- errors.push(`release artifact path missing: ${rel}`);
230
- }
231
- }
232
-
233
- return {
234
- ok: errors.length === 0,
235
- version,
236
- counts,
237
- errors,
238
- warnings,
239
- };
240
- }
241
-
242
- function main() {
243
- const jsonOut = process.argv.includes('--json');
244
- const result = validateReleaseDocs();
245
-
246
- if (!jsonOut) {
247
- console.log('\n╔════════════════════════════════════════════════════════╗');
248
- console.log('║ 📚 Release Docs Validation ║');
249
- console.log('╚════════════════════════════════════════════════════════╝');
250
- console.log(`\n📌 package.json version: v${result.version}`);
251
- console.log(
252
- `📊 counts: ${result.counts.agents} agents · ${result.counts.skills} skills · ${result.counts.mcps} MCP servers\n`,
253
- );
254
- }
255
-
256
- if (result.warnings.length > 0 && !jsonOut) {
257
- console.log('⚠️ Warnings:');
258
- for (const w of result.warnings) console.log(` - ${w}`);
259
- console.log();
260
- }
261
-
262
- if (!result.ok) {
263
- if (jsonOut) {
264
- console.log(JSON.stringify(result, null, 2));
265
- } else {
266
- console.error('❌ Release docs validation FAILED:\n');
267
- for (const e of result.errors) console.error(` • ${e}`);
268
- console.error('\nFix docs then run: npm run version:sync');
269
- console.error('Or: node scripts/node/validate-release-docs.mjs\n');
270
- }
271
- process.exit(1);
272
- }
273
-
274
- if (jsonOut) {
275
- console.log(JSON.stringify(result, null, 2));
276
- } else {
277
- console.log('✅ Release docs validation passed\n');
278
- }
279
- }
280
-
281
- const isMain =
282
- process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
283
-
284
- if (isMain) {
285
- main();
286
- }
2
+ export * from "../foundry/validate-release-docs.mjs";
3
+ import { millMain } from "./foundry-shim.mjs";
4
+ millMain(import.meta.url, "validate-release-docs.mjs");