0xray 3.3.1 → 3.4.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.
- package/.opencode/enforcer-config.json +1 -4
- package/.xray/codex.json +598 -0
- package/.xray/config.json +25 -0
- package/.xray/features.json +146 -0
- package/.xray/features.schema.json +33 -0
- package/AGENTS-consumer.md +127 -10
- package/AGENTS.md +172 -56
- package/README.md +159 -82
- package/SKILLS.md +79 -53
- package/dist/AGENTS.md +172 -56
- package/dist/CHANGELOG.md +90 -51
- package/dist/README.md +159 -82
- package/dist/SKILLS.md +79 -53
- package/dist/analytics/routing-refiner.js +0 -2
- package/dist/core/boot-orchestrator.js +9 -1
- package/dist/core/features-config.js +5 -1
- package/dist/integrations/grok/grok-cli.js +47 -25
- package/dist/integrations/grok/hooks/grok-hook-activity.js +53 -0
- package/dist/integrations/grok/hooks/grok-hook-utils.js +261 -0
- package/dist/integrations/grok/hooks/pre-tool-use.js +85 -115
- package/dist/integrations/grok/hooks/session-start.js +52 -0
- package/dist/integrations/grok/plugin/0xray/hooks/hooks.json +20 -3
- package/dist/integrations/hermes-agent/bridge.mjs +11 -1
- package/dist/mcps/config/server-config-registry.js +4 -3
- package/dist/mcps/orchestrator/handlers/complexity-handler.js +33 -3
- package/dist/mcps/orchestrator/server.js +1 -1
- package/dist/mcps/registry.json +0 -1
- package/dist/mcps/researcher.server.js +3 -0
- package/dist/nucleus/autonomy-kernel.js +176 -0
- package/dist/nucleus/index.js +1 -0
- package/dist/orchestrator/universal-registry-bridge.js +1 -1
- package/dist/skills/boot-orchestrator/SKILL.md +1 -5
- package/dist/skills/orchestrator/SKILL.md +35 -12
- package/dist/skills/registry.json +0 -1
- package/dist/skills/researcher/SKILL.md +17 -4
- package/dist/skills/strategist/SKILL.md +12 -0
- package/dist/skills/testing-strategy/SKILL.md +13 -2
- package/package.json +23 -8
- package/scripts/node/consumer-install-smoke.mjs +163 -0
- package/scripts/node/enforce-agents-md.mjs +0 -26
- package/scripts/node/enforce-version-compliance.sh +0 -1
- package/scripts/node/enforce-version-compliance.ts +0 -10
- package/scripts/node/install-bridges.cjs +154 -39
- package/scripts/node/pre-publish-guard.js +108 -165
- package/scripts/node/reconcile-version.mjs +147 -0
- package/scripts/node/release-gate.mjs +66 -0
- package/scripts/node/release.js +5 -147
- package/scripts/node/release.mjs +106 -181
- package/scripts/node/setup.cjs +21 -26
- package/scripts/node/sync-versions.mjs +12 -134
- package/scripts/node/universal-version-manager.js +22 -4
- package/scripts/node/validate-release-docs.mjs +299 -0
- package/scripts/node/version-manager.mjs +225 -49
- package/src/integrations/grok/plugin/0xray/hooks/hooks.json +20 -3
- package/src/mcps/config/__tests__/server-config-registry.test.ts +9 -0
- package/src/mcps/config/server-config-registry.ts +5 -3
- package/src/mcps/orchestrator/handlers/complexity-handler.ts +54 -3
- package/src/mcps/orchestrator/server.ts +1 -1
- package/src/mcps/registry.json +0 -1
- package/src/mcps/researcher.server.ts +4 -0
- package/src/opencode/enforcer-config.json +1 -4
- package/src/skills/boot-orchestrator/SKILL.md +1 -5
- package/src/skills/orchestrator/SKILL.md +35 -12
- package/src/skills/registry.json +0 -1
- package/src/skills/researcher/SKILL.md +17 -4
- package/src/skills/strategist/SKILL.md +12 -0
- package/src/skills/testing-strategy/SKILL.md +13 -2
- package/xray/config.json +25 -0
- package/xray/features.json +146 -0
- package/xray/features.schema.json +33 -0
|
@@ -1,140 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
2
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Syncs version strings across all config and doc files to match package.json.
|
|
7
|
-
* Called automatically by the release script after `npm version patch`.
|
|
8
|
-
*
|
|
9
|
-
* Append-only for docs — never overwrites existing content, only replaces
|
|
10
|
-
* version strings in known patterns.
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* node scripts/node/sync-versions.mjs # dry run (prints changes)
|
|
14
|
-
* node scripts/node/sync-versions.mjs --apply # apply changes
|
|
3
|
+
* @deprecated Use: node scripts/node/version-manager.mjs --artifacts-only
|
|
4
|
+
* SSOT for version strings is package.json (see reconcile-version.mjs).
|
|
15
5
|
*/
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
import { join } from "path";
|
|
19
|
-
|
|
20
|
-
const root = process.cwd();
|
|
21
|
-
const apply = process.argv.includes("--apply");
|
|
22
|
-
|
|
23
|
-
function readPkgVersion() {
|
|
24
|
-
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
|
|
25
|
-
return pkg.version;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function replaceVersionInFile(filePath, patterns, newVersion) {
|
|
29
|
-
if (!existsSync(filePath)) {
|
|
30
|
-
return { file: filePath, skipped: true, reason: "not found" };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let content = readFileSync(filePath, "utf-8");
|
|
34
|
-
const oldContent = content;
|
|
35
|
-
let changes = 0;
|
|
36
|
-
|
|
37
|
-
for (const pattern of patterns) {
|
|
38
|
-
const regex = pattern(newVersion);
|
|
39
|
-
const matches = content.match(regex.regex);
|
|
40
|
-
if (matches) {
|
|
41
|
-
content = content.replace(regex.regex, regex.replacement);
|
|
42
|
-
changes += matches.length;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (content !== oldContent && apply) {
|
|
47
|
-
writeFileSync(filePath, content, "utf-8");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return { file: filePath, changes, applied: apply };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function replaceJsonVersion(filePath, newVersion) {
|
|
54
|
-
return replaceVersionInFile(filePath, [
|
|
55
|
-
(v) => ({
|
|
56
|
-
regex: /"version"\s*:\s*"\d+\.\d+\.\d+"/g,
|
|
57
|
-
replacement: `"version": "${v}"`,
|
|
58
|
-
}),
|
|
59
|
-
], newVersion);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function replaceMdVersion(filePath, newVersion) {
|
|
63
|
-
return replaceVersionInFile(filePath, [
|
|
64
|
-
(v) => ({
|
|
65
|
-
regex: /version-(\d+\.\d+\.\d+)/g,
|
|
66
|
-
replacement: `version-${v}`,
|
|
67
|
-
}),
|
|
68
|
-
(v) => ({
|
|
69
|
-
regex: /Version\*\*:\s*\d+\.\d+\.\d+/g,
|
|
70
|
-
replacement: `Version**: ${v}`,
|
|
71
|
-
}),
|
|
72
|
-
(v) => ({
|
|
73
|
-
regex: /v(\d+\.\d+\.\d+)/g,
|
|
74
|
-
replacement: `v${v}`,
|
|
75
|
-
}),
|
|
76
|
-
(v) => ({
|
|
77
|
-
regex: /\*\*Version\*\*:\s*\d+\.\d+\.\d+/g,
|
|
78
|
-
replacement: `**Version**: ${v}`,
|
|
79
|
-
}),
|
|
80
|
-
(v) => ({
|
|
81
|
-
regex: /"version":\s*"(\d+\.\d+\.\d+)"/g,
|
|
82
|
-
replacement: `"version": "${v}"`,
|
|
83
|
-
}),
|
|
84
|
-
(v) => ({
|
|
85
|
-
regex: /\d+\.\d+\.\d+(?=[^\d]*\.svg)/g,
|
|
86
|
-
replacement: v,
|
|
87
|
-
}),
|
|
88
|
-
], newVersion);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function main() {
|
|
92
|
-
const newVersion = readPkgVersion();
|
|
93
|
-
console.log(`\n=== Version Sync: ${newVersion} ===\n`);
|
|
94
|
-
console.log(apply ? "Mode: APPLY" : "Mode: DRY RUN (use --apply to write)\n");
|
|
95
|
-
|
|
96
|
-
const results = [];
|
|
97
|
-
|
|
98
|
-
// Config JSON files (source of truth in src/opencode/)
|
|
99
|
-
const configFiles = [
|
|
100
|
-
"src/opencode/config.json",
|
|
101
|
-
"src/opencode/features.json",
|
|
102
|
-
"src/opencode/codex.codex",
|
|
103
|
-
"src/opencode/integrations.json",
|
|
104
|
-
];
|
|
105
|
-
|
|
106
|
-
for (const file of configFiles) {
|
|
107
|
-
results.push(replaceJsonVersion(join(root, file), newVersion));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Doc files
|
|
111
|
-
const docFiles = [
|
|
112
|
-
"AGENTS.md",
|
|
113
|
-
"AGENTS-full.md",
|
|
114
|
-
"docs/README.md",
|
|
115
|
-
];
|
|
116
|
-
|
|
117
|
-
for (const file of docFiles) {
|
|
118
|
-
results.push(replaceMdVersion(join(root, file), newVersion));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Report
|
|
122
|
-
let totalChanges = 0;
|
|
123
|
-
for (const r of results) {
|
|
124
|
-
if (r.skipped) {
|
|
125
|
-
console.log(` SKIP ${r.file} (${r.reason})`);
|
|
126
|
-
} else if (r.changes > 0) {
|
|
127
|
-
totalChanges += r.changes;
|
|
128
|
-
console.log(` ${r.applied ? "FIX" : "DRY"} ${r.file} (${r.changes} replacements)`);
|
|
129
|
-
} else {
|
|
130
|
-
console.log(` OK ${r.file}`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
10
|
+
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
133
11
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
console.log("Run with --apply to write changes.");
|
|
137
|
-
}
|
|
138
|
-
}
|
|
12
|
+
console.warn("\n⚠️ sync-versions.mjs is deprecated.");
|
|
13
|
+
console.warn(" Use: node scripts/node/version-manager.mjs --artifacts-only\n");
|
|
139
14
|
|
|
140
|
-
|
|
15
|
+
execSync("node scripts/node/version-manager.mjs --artifacts-only", {
|
|
16
|
+
cwd: rootDir,
|
|
17
|
+
stdio: "inherit",
|
|
18
|
+
});
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
* - Historical docs (docs/reflections, docs/archive)
|
|
18
18
|
* - Test files with assertions
|
|
19
19
|
*
|
|
20
|
-
* @version 2.0.0
|
|
21
20
|
* @since 2026-01-15
|
|
22
21
|
* @updated 2026-03-09 - Enhanced with documentation management, validation, and backup
|
|
23
22
|
*/
|
|
@@ -28,6 +27,19 @@ const require = createRequire(import.meta.url);
|
|
|
28
27
|
import fs from "fs";
|
|
29
28
|
import path from "path";
|
|
30
29
|
import { execSync } from "child_process";
|
|
30
|
+
import { fileURLToPath } from "url";
|
|
31
|
+
|
|
32
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const rootDir = path.resolve(__dirname, "../..");
|
|
34
|
+
|
|
35
|
+
function readPackageVersion() {
|
|
36
|
+
try {
|
|
37
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf-8"));
|
|
38
|
+
return pkg.version;
|
|
39
|
+
} catch {
|
|
40
|
+
return "0.0.0";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
31
43
|
|
|
32
44
|
/**
|
|
33
45
|
* Auto-calculate framework counts from filesystem
|
|
@@ -133,12 +145,14 @@ const CALCULATED_COUNTS = calculateCounts();
|
|
|
133
145
|
* Framework version information - SINGLE SOURCE OF TRUTH
|
|
134
146
|
* Version manager updates this, then propagates to all files
|
|
135
147
|
*/
|
|
148
|
+
const PACKAGE_VERSION = readPackageVersion();
|
|
149
|
+
|
|
136
150
|
const OFFICIAL_VERSIONS = {
|
|
137
|
-
// Framework version
|
|
151
|
+
// Framework version — SSOT: package.json
|
|
138
152
|
framework: {
|
|
139
|
-
version:
|
|
153
|
+
version: PACKAGE_VERSION,
|
|
140
154
|
displayName: "xray: Self-Healing AI Governance OS",
|
|
141
|
-
lastUpdated: "2026-06-
|
|
155
|
+
lastUpdated: "2026-06-17",
|
|
142
156
|
// Counts (auto-calculated, but can be overridden)
|
|
143
157
|
...CALCULATED_COUNTS,
|
|
144
158
|
},
|
|
@@ -637,6 +651,10 @@ const UPDATE_PATTERNS = [
|
|
|
637
651
|
}
|
|
638
652
|
|
|
639
653
|
async function standardizeVersions() {
|
|
654
|
+
console.warn("\n⚠️ universal-version-manager.js is DEPRECATED for releases.");
|
|
655
|
+
console.warn(" Use: npm run release:patch or npm run version:sync");
|
|
656
|
+
console.warn(" (version-manager.mjs --artifacts-only). Mass JSON version replace is disabled.\n");
|
|
657
|
+
return;
|
|
640
658
|
console.log("🔧 Starting Universal Version Standardization");
|
|
641
659
|
console.log(`📋 Framework: ${OFFICIAL_VERSIONS.framework.displayName}`);
|
|
642
660
|
console.log(
|
|
@@ -0,0 +1,299 @@
|
|
|
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*\d+ agents · \d+ skills · \d+ MCP servers? · \d+ codex terms(?: · [\d,]+ tests)?/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 or stale standard header (expected: ${header})`);
|
|
49
|
+
}
|
|
50
|
+
if (!content.includes('SKILLS.md')) {
|
|
51
|
+
errors.push('missing SKILLS.md reference in postinstall/docs section');
|
|
52
|
+
}
|
|
53
|
+
if (!content.includes('install-bridges') && !content.includes('installAllBridges')) {
|
|
54
|
+
errors.push('missing install-bridges / installAllBridges reference');
|
|
55
|
+
}
|
|
56
|
+
return errors;
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
rel: 'AGENTS.md',
|
|
61
|
+
validate: (content, version, counts) => {
|
|
62
|
+
const errors = [];
|
|
63
|
+
if (!content.includes(`**v${version}**`)) {
|
|
64
|
+
errors.push(`missing version marker **v${version}**`);
|
|
65
|
+
}
|
|
66
|
+
if (!content.includes(`${counts.agents} agents`)) {
|
|
67
|
+
errors.push(`expected ${counts.agents} agents in header`);
|
|
68
|
+
}
|
|
69
|
+
if (!content.includes(`${counts.skills} skills`)) {
|
|
70
|
+
errors.push(`expected ${counts.skills} skills in header`);
|
|
71
|
+
}
|
|
72
|
+
if (!content.includes('68 codex terms')) {
|
|
73
|
+
errors.push('expected 68 codex terms in header');
|
|
74
|
+
}
|
|
75
|
+
if (!content.includes('aside-context.md') && !content.includes('AsideContext')) {
|
|
76
|
+
errors.push('missing AsideContext documentation reference');
|
|
77
|
+
}
|
|
78
|
+
if (!content.includes('SKILLS.md')) {
|
|
79
|
+
errors.push('missing SKILLS.md link');
|
|
80
|
+
}
|
|
81
|
+
return errors;
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
rel: 'AGENTS-consumer.md',
|
|
86
|
+
validate: (content, version, counts) => {
|
|
87
|
+
const errors = [];
|
|
88
|
+
const expected = `**v${version}** — ${counts.mcps} MCP servers · ${counts.skills} skills · 68 codex terms`;
|
|
89
|
+
if (!content.includes(expected)) {
|
|
90
|
+
errors.push(`consumer header stale (expected substring: ${expected})`);
|
|
91
|
+
}
|
|
92
|
+
if (!content.includes('SKILLS.md')) {
|
|
93
|
+
errors.push('missing SKILLS.md in postinstall section');
|
|
94
|
+
}
|
|
95
|
+
if (!content.includes('AsideContext')) {
|
|
96
|
+
errors.push('missing AsideContext section');
|
|
97
|
+
}
|
|
98
|
+
return errors;
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
rel: 'SKILLS.md',
|
|
103
|
+
validate: (content, version, counts) => {
|
|
104
|
+
const errors = [];
|
|
105
|
+
const expected = `**v${version}** — **${counts.skills} skills**`;
|
|
106
|
+
if (!content.includes(expected)) {
|
|
107
|
+
errors.push(`header stale (expected: ${expected})`);
|
|
108
|
+
}
|
|
109
|
+
return errors;
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
function readFile(rootDir, rel) {
|
|
115
|
+
const full = path.join(rootDir, rel);
|
|
116
|
+
if (!fs.existsSync(full)) return null;
|
|
117
|
+
return fs.readFileSync(full, 'utf-8');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getPackageVersion(rootDir) {
|
|
121
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf-8'));
|
|
122
|
+
return pkg.version;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function getCodexTermCount(rootDir) {
|
|
126
|
+
const codexPath = path.join(rootDir, '.xray/codex.json');
|
|
127
|
+
if (!fs.existsSync(codexPath)) return null;
|
|
128
|
+
const codex = JSON.parse(fs.readFileSync(codexPath, 'utf-8'));
|
|
129
|
+
return Object.keys(codex.terms || {}).length;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function validateChangelog(content, version) {
|
|
133
|
+
const errors = [];
|
|
134
|
+
const first = content.match(/^## \[([^\]]+)\]/m);
|
|
135
|
+
if (!first) {
|
|
136
|
+
errors.push('no version sections found');
|
|
137
|
+
return errors;
|
|
138
|
+
}
|
|
139
|
+
if (first[1] !== version) {
|
|
140
|
+
errors.push(`top CHANGELOG entry is [${first[1]}], expected [${version}]`);
|
|
141
|
+
}
|
|
142
|
+
if (!content.includes(`## [${version}]`)) {
|
|
143
|
+
errors.push(`missing ## [${version}] section`);
|
|
144
|
+
}
|
|
145
|
+
return errors;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function validateStandardHeader(content, expectedHeader, label) {
|
|
149
|
+
const match = content.match(STANDARD_HEADER_RE);
|
|
150
|
+
if (!match) {
|
|
151
|
+
return [`${label}: no standard docs header line found`];
|
|
152
|
+
}
|
|
153
|
+
if (match[0] !== expectedHeader) {
|
|
154
|
+
return [`${label}: header is "${match[0]}", expected "${expectedHeader}"`];
|
|
155
|
+
}
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function validateDocusaurusTagline(content, counts) {
|
|
160
|
+
const errors = [];
|
|
161
|
+
const expectedParts = [
|
|
162
|
+
`${counts.agents} agents`,
|
|
163
|
+
`${counts.skills} skills`,
|
|
164
|
+
`${counts.mcps} MCP servers`,
|
|
165
|
+
'68 codex terms',
|
|
166
|
+
];
|
|
167
|
+
for (const part of expectedParts) {
|
|
168
|
+
if (!content.includes(part)) {
|
|
169
|
+
errors.push(`docusaurus.config.ts tagline missing "${part}"`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return errors;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @param {string} [rootDir]
|
|
177
|
+
* @returns {{ ok: boolean; version: string; counts: object; errors: string[]; warnings: string[] }}
|
|
178
|
+
*/
|
|
179
|
+
export function validateReleaseDocs(rootDir = defaultRoot) {
|
|
180
|
+
const errors = [];
|
|
181
|
+
const warnings = [];
|
|
182
|
+
const version = getPackageVersion(rootDir);
|
|
183
|
+
const counts = getFrameworkCounts(rootDir);
|
|
184
|
+
const expectedHeader = buildDocsHeader(counts, version);
|
|
185
|
+
const codexTerms = getCodexTermCount(rootDir);
|
|
186
|
+
|
|
187
|
+
if (codexTerms !== null && codexTerms !== 68) {
|
|
188
|
+
warnings.push(`codex.json has ${codexTerms} terms (docs reference 68)`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const changelog = readFile(rootDir, 'CHANGELOG.md');
|
|
192
|
+
if (!changelog) {
|
|
193
|
+
errors.push('CHANGELOG.md missing');
|
|
194
|
+
} else {
|
|
195
|
+
errors.push(...validateChangelog(changelog, version).map((e) => `CHANGELOG.md: ${e}`));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
for (const check of ROOT_DOC_CHECKS) {
|
|
199
|
+
const content = readFile(rootDir, check.rel);
|
|
200
|
+
if (!content) {
|
|
201
|
+
errors.push(`${check.rel}: file missing`);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const relErrors = check.validate(content, version, counts, expectedHeader);
|
|
205
|
+
for (const e of relErrors) {
|
|
206
|
+
errors.push(`${check.rel}: ${e}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
for (const rel of DOCS_SITE_HEADER_FILES) {
|
|
211
|
+
const content = readFile(rootDir, rel);
|
|
212
|
+
if (!content) {
|
|
213
|
+
errors.push(`${rel}: file missing`);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
errors.push(...validateStandardHeader(content, expectedHeader, rel));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (const rel of REQUIRED_GUIDES) {
|
|
220
|
+
const content = readFile(rootDir, rel);
|
|
221
|
+
if (!content) {
|
|
222
|
+
errors.push(`${rel}: required guide missing`);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (content.trim().length < 200) {
|
|
226
|
+
errors.push(`${rel}: guide too short — likely stub`);
|
|
227
|
+
}
|
|
228
|
+
if (rel.includes('features-since-3.1') && !content.includes(version)) {
|
|
229
|
+
errors.push(`${rel}: must reference current version ${version}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const docusaurus = readFile(rootDir, 'docs-site/docusaurus.config.ts');
|
|
234
|
+
if (!docusaurus) {
|
|
235
|
+
errors.push('docs-site/docusaurus.config.ts: missing');
|
|
236
|
+
} else {
|
|
237
|
+
errors.push(...validateDocusaurusTagline(docusaurus, counts));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
for (const rel of getReleaseArtifactPaths()) {
|
|
241
|
+
if (!fs.existsSync(path.join(rootDir, rel))) {
|
|
242
|
+
errors.push(`release artifact path missing: ${rel}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
ok: errors.length === 0,
|
|
248
|
+
version,
|
|
249
|
+
counts,
|
|
250
|
+
errors,
|
|
251
|
+
warnings,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function main() {
|
|
256
|
+
const jsonOut = process.argv.includes('--json');
|
|
257
|
+
const result = validateReleaseDocs();
|
|
258
|
+
|
|
259
|
+
if (!jsonOut) {
|
|
260
|
+
console.log('\n╔════════════════════════════════════════════════════════╗');
|
|
261
|
+
console.log('║ 📚 Release Docs Validation ║');
|
|
262
|
+
console.log('╚════════════════════════════════════════════════════════╝');
|
|
263
|
+
console.log(`\n📌 package.json version: v${result.version}`);
|
|
264
|
+
console.log(
|
|
265
|
+
`📊 counts: ${result.counts.agents} agents · ${result.counts.skills} skills · ${result.counts.mcps} MCP servers\n`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (result.warnings.length > 0 && !jsonOut) {
|
|
270
|
+
console.log('⚠️ Warnings:');
|
|
271
|
+
for (const w of result.warnings) console.log(` - ${w}`);
|
|
272
|
+
console.log();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!result.ok) {
|
|
276
|
+
if (jsonOut) {
|
|
277
|
+
console.log(JSON.stringify(result, null, 2));
|
|
278
|
+
} else {
|
|
279
|
+
console.error('❌ Release docs validation FAILED:\n');
|
|
280
|
+
for (const e of result.errors) console.error(` • ${e}`);
|
|
281
|
+
console.error('\nFix docs then run: npm run version:sync');
|
|
282
|
+
console.error('Or: node scripts/node/validate-release-docs.mjs\n');
|
|
283
|
+
}
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (jsonOut) {
|
|
288
|
+
console.log(JSON.stringify(result, null, 2));
|
|
289
|
+
} else {
|
|
290
|
+
console.log('✅ Release docs validation passed\n');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const isMain =
|
|
295
|
+
process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
296
|
+
|
|
297
|
+
if (isMain) {
|
|
298
|
+
main();
|
|
299
|
+
}
|