diff --git a/Taskfile.yml b/Taskfile.yml index cbd2c56e..cc39d2a7 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -81,6 +81,8 @@ tasks: - '../../pkg/compose/spec.go' - 'check-pages.mjs' - 'render-pages.mjs' + - 'schema-coverage.mjs' + - 'schema-coverage.test.mjs' - 'package.json' - 'package-lock.json' generates: diff --git a/tools/genpages/check-pages.mjs b/tools/genpages/check-pages.mjs index 5f977d87..679e545a 100644 --- a/tools/genpages/check-pages.mjs +++ b/tools/genpages/check-pages.mjs @@ -2,6 +2,8 @@ import { readFile, readdir, stat } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { documentedYAMLSchemaFields } from "./schema-coverage.mjs"; + const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const repositoryRoot = path.resolve(scriptDir, "../.."); const pagesDir = path.join(repositoryRoot, "docs", "pages"); @@ -62,11 +64,7 @@ async function checkEnglishManuals() { async function checkYAMLSchemaCoverage() { const schema = await readFile(schemaPath, "utf8"); - const fields = new Set( - [...schema.matchAll(/yaml:"([^",]+)/g)] - .map((match) => match[1]) - .filter((field) => field !== "-"), - ); + const fields = documentedYAMLSchemaFields(schema); for (const relativePath of [ "agent-compose-yaml-manual.md", diff --git a/tools/genpages/package.json b/tools/genpages/package.json index cb70d91d..3485741f 100644 --- a/tools/genpages/package.json +++ b/tools/genpages/package.json @@ -3,8 +3,8 @@ "private": true, "type": "module", "scripts": { - "build": "node render-pages.mjs && node check-pages.mjs", - "test": "node render-pages.mjs && node check-pages.mjs" + "build": "node --test && node render-pages.mjs && node check-pages.mjs", + "test": "node --test" }, "dependencies": { "marked": "15.0.12" diff --git a/tools/genpages/schema-coverage.mjs b/tools/genpages/schema-coverage.mjs new file mode 100644 index 00000000..958f3157 --- /dev/null +++ b/tools/genpages/schema-coverage.mjs @@ -0,0 +1,24 @@ +// These fields preserve v1 presentation metadata during migration. They are +// intentionally absent from the v2 authoring manual and will leave with the +// compatibility path. +const v1MigrationCompatibilityFields = new Set([ + "AgentSpec.description", + "AgentSpec.display_name", + "SchedulerSpec.description", + "SchedulerSpec.display_name", + "WorkspaceSpec.commit", +]); + +export function documentedYAMLSchemaFields(schema) { + const fields = new Set(); + for (const definition of schema.matchAll(/type\s+(\w+)\s+struct\s*\{([\s\S]*?)\n\}/g)) { + const [, typeName, body] = definition; + for (const tag of body.matchAll(/yaml:"([^",]+)/g)) { + const field = tag[1]; + if (field !== "-" && !v1MigrationCompatibilityFields.has(`${typeName}.${field}`)) { + fields.add(field); + } + } + } + return fields; +} diff --git a/tools/genpages/schema-coverage.test.mjs b/tools/genpages/schema-coverage.test.mjs new file mode 100644 index 00000000..65f69526 --- /dev/null +++ b/tools/genpages/schema-coverage.test.mjs @@ -0,0 +1,28 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { documentedYAMLSchemaFields } from "./schema-coverage.mjs"; + +test("excludes temporary v1 migration fields from documentation coverage", () => { + const schema = ` +type AgentSpec struct { + Name string \`yaml:"name,omitempty"\` + DisplayName string \`yaml:"display_name,omitempty"\` + Description string \`yaml:"description,omitempty"\` +} + +type WorkspaceSpec struct { + Commit string \`yaml:"commit,omitempty"\` + Internal string \`yaml:"-"\` +} + +type ReleaseSpec struct { + Commit string \`yaml:"commit,omitempty"\` +} +`; + + assert.deepEqual( + [...documentedYAMLSchemaFields(schema)].sort(), + ["commit", "name"], + ); +});