[codex] lint craft references in guard (#4239)

* chore: lint craft references

* fix: validate bundled plugin craft references

---------

Co-authored-by: Jeshua09090 <jeshuaelpro@outlok.com>
This commit is contained in:
Jeshua:D
2026-06-14 08:53:54 +00:00
committed by GitHub
co-authored by Jeshua09090
parent 1a1b4ecc05
commit a0beba92f9
7 changed files with 330 additions and 1 deletions
+9
View File
@@ -0,0 +1,9 @@
# Future Craft Sections
These slugs are intentionally referenced by skills before their matching
`craft/<slug>.md` sections ship. `pnpm lint:craft` treats them as planned
forward references while still failing on unlisted typos.
- motion-discipline
- pixel-discipline
- typographic-rhythm
+6
View File
@@ -41,6 +41,12 @@ od:
Allowed values match the file names in this directory minus the `.md`
extension. Unknown values are silently ignored (forward-compatible).
Run `pnpm lint:craft` after adding or changing `od.craft.requires`. The
repository guard reports unresolved slugs with their manifest paths, so typos
cannot silently drop a craft section from the runtime prompt. If a slug is an
intentional forward reference, list it in `craft/FUTURE_SECTIONS.md` until the
matching `craft/<slug>.md` file ships.
### Why silent fallback instead of fail-fast?
A skeptical reader will ask: "If a skill requests a planned-but-not-yet-vendored
+2 -1
View File
@@ -15,7 +15,8 @@
"tools-pack": "pnpm exec tools-pack",
"tools-serve": "pnpm exec tools-serve",
"nix:update-hash": "node --experimental-strip-types ./scripts/update-nix-pnpm-deps-hash.ts",
"guard": "tsx ./scripts/guard.ts && tsx ./scripts/check-ui-p0-path-parity.ts && node --import tsx --test scripts/style-policy.test.ts scripts/product-neutrality.test.ts scripts/web-import-isolation.test.ts scripts/check-cross-app-imports.test.ts scripts/approve-fork-pr-workflows.test.ts scripts/postinstall.test.ts",
"guard": "tsx ./scripts/guard.ts && tsx ./scripts/check-ui-p0-path-parity.ts && node --import tsx --test scripts/style-policy.test.ts scripts/product-neutrality.test.ts scripts/web-import-isolation.test.ts scripts/check-cross-app-imports.test.ts scripts/approve-fork-pr-workflows.test.ts scripts/postinstall.test.ts scripts/lint-craft-references.test.ts",
"lint:craft": "tsx ./scripts/lint-craft-references.ts",
"i18n:check": "tsx ./scripts/i18n-check.ts",
"i18n:coverage": "tsx ./scripts/i18n-coverage-report.ts",
"sync:community-pets": "node --experimental-strip-types scripts/sync-community-pets.ts",
@@ -0,0 +1,7 @@
{
"od": {
"context": {
"craft": ["typograpy"]
}
}
}
+2
View File
@@ -17,6 +17,7 @@ import {
checkDesignSystemTokenFixtureSync,
checkDesignSystemUnknownTokens,
} from "./check-tokens-fixture-sync.ts";
import { checkCraftReferences } from "./lint-craft-references.ts";
import { collectCssHardcodedColorMatches, cssWideAndSpecialColorKeywords, realNamedColors } from "./style-policy.ts";
const repoRoot = path.resolve(import.meta.dirname, "..");
@@ -1241,6 +1242,7 @@ const checks: GuardCheck[] = [
{ name: "web import isolation", run: checkWebImportIsolation },
{ name: "tools layout", run: checkToolsLayout },
{ name: "style policy", run: checkStylePolicy },
{ name: "craft references", run: checkCraftReferences },
{ name: "design system manifests", run: checkDesignSystemManifests },
{ name: "design system package quality", run: checkDesignSystemPackageQuality },
{ name: "design system component fixture report", run: checkDesignSystemComponentFixtureReport },
+76
View File
@@ -0,0 +1,76 @@
import assert from "node:assert/strict";
import test from "node:test";
import { fileURLToPath } from "node:url";
import {
collectCraftReferences,
extractCraftRequiresSlugs,
findCraftReferenceViolations,
} from "./lint-craft-references.ts";
test("craft reference parser reads only od.craft.requires and preserves malformed entries", () => {
const inlineSource = `---
od:
design_system:
requires: true
craft:
requires: [typography, typo_graphy, 42]
---
`;
const blockSource = `---
od:
craft:
requires:
- form-validation
- state_coverage
---
`;
assert.deepEqual(extractCraftRequiresSlugs(inlineSource), ["typography", "typo_graphy", 42]);
assert.deepEqual(extractCraftRequiresSlugs(blockSource), ["form-validation", "state_coverage"]);
});
test("craft reference violations allow present and planned slugs while reporting typos", () => {
const violations = findCraftReferenceViolations(
[
{ manifestPath: "skills/example/SKILL.md", slug: "typography" },
{ manifestPath: "skills/example/SKILL.md", slug: "motion-discipline" },
{ manifestPath: "skills/example/SKILL.md", slug: "typograpy" },
{ manifestPath: "skills/example/SKILL.md", slug: "typo_graphy" },
{ manifestPath: "skills/example/SKILL.md", slug: 42 },
],
new Set(["typography"]),
new Set(["motion-discipline"]),
);
assert.deepEqual(violations, [
{
kind: "unresolved",
manifestPath: "skills/example/SKILL.md",
slug: "typograpy",
},
{
kind: "invalid",
manifestPath: "skills/example/SKILL.md",
slug: "typo_graphy",
},
{
kind: "invalid",
manifestPath: "skills/example/SKILL.md",
slug: 42,
},
]);
});
test("bad bundled scenario manifest craft references are reported", async () => {
const fixtureRoot = fileURLToPath(new URL("./fixtures/lint-craft-references", import.meta.url));
const references = await collectCraftReferences(fixtureRoot);
assert.deepEqual(findCraftReferenceViolations(references, new Set(), new Set()), [
{
kind: "unresolved",
manifestPath: "plugins/_official/scenarios/bad-scenario/open-design.json",
slug: "typograpy",
},
]);
});
+228
View File
@@ -0,0 +1,228 @@
import { readFile, readdir, stat } from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { parseFrontmatter } from "../packages/plugin-runtime/src/parsers/frontmatter.ts";
const repoRoot = path.resolve(import.meta.dirname, "..");
const craftRoot = path.join(repoRoot, "craft");
const futureSectionsPath = path.join(craftRoot, "FUTURE_SECTIONS.md");
const skillManifestRoots = [
"skills",
"design-templates",
"plugins/_official/examples",
"docs/examples",
];
const pluginManifestRoot = "plugins/_official";
const slugPattern = /^[a-z0-9][a-z0-9-]*$/;
export type CraftReference = {
manifestPath: string;
slug: unknown;
};
export type CraftReferenceViolation = CraftReference & {
kind: "invalid" | "unresolved";
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isAbsenceError(error: unknown): boolean {
return isRecord(error) && (error["code"] === "ENOENT" || error["code"] === "ENOTDIR");
}
async function pathExists(filePath: string): Promise<boolean> {
try {
await stat(filePath);
return true;
} catch (error) {
if (isAbsenceError(error)) return false;
throw error;
}
}
function toRepositoryPath(root: string, filePath: string): string {
return path.relative(root, filePath).split(path.sep).join("/");
}
export function extractCraftRequiresSlugs(source: string): unknown[] {
const { data } = parseFrontmatter(source);
const od = data["od"];
if (!isRecord(od)) return [];
const craft = od["craft"];
if (!isRecord(craft)) return [];
const requires = craft["requires"];
return Array.isArray(requires) ? [...requires] : [];
}
export function extractPluginManifestContextCraftSlugs(source: string): unknown[] {
const manifest: unknown = JSON.parse(source);
if (!isRecord(manifest)) return [];
const od = manifest["od"];
if (!isRecord(od)) return [];
const context = od["context"];
if (!isRecord(context)) return [];
const craft = context["craft"];
return Array.isArray(craft) ? [...craft] : [];
}
export function findCraftReferenceViolations(
references: CraftReference[],
existingSlugs: ReadonlySet<string>,
futureSlugs: ReadonlySet<string>,
): CraftReferenceViolation[] {
const violations: CraftReferenceViolation[] = [];
for (const reference of references) {
if (typeof reference.slug !== "string" || !slugPattern.test(reference.slug)) {
violations.push({ ...reference, kind: "invalid" });
continue;
}
if (!existingSlugs.has(reference.slug) && !futureSlugs.has(reference.slug)) {
violations.push({ ...reference, kind: "unresolved" });
}
}
return violations;
}
async function collectNamedManifests(directory: string, fileName: string): Promise<string[]> {
if (!(await pathExists(directory))) return [];
const entries = await readdir(directory, { withFileTypes: true });
const manifests: string[] = [];
for (const entry of entries) {
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) {
manifests.push(...(await collectNamedManifests(fullPath, fileName)));
} else if (entry.isFile() && entry.name === fileName) {
manifests.push(fullPath);
}
}
return manifests;
}
export async function collectCraftReferences(root: string = repoRoot): Promise<CraftReference[]> {
const skillManifests = (
await Promise.all(
skillManifestRoots.map((manifestRoot) =>
collectNamedManifests(path.join(root, manifestRoot), "SKILL.md"),
),
)
).flat();
const pluginManifests = await collectNamedManifests(
path.join(root, pluginManifestRoot),
"open-design.json",
);
const references: CraftReference[] = [];
for (const manifestPath of skillManifests) {
const source = await readFile(manifestPath, "utf8");
for (const slug of extractCraftRequiresSlugs(source)) {
references.push({
manifestPath: toRepositoryPath(root, manifestPath),
slug,
});
}
}
for (const manifestPath of pluginManifests) {
const source = await readFile(manifestPath, "utf8");
for (const slug of extractPluginManifestContextCraftSlugs(source)) {
references.push({
manifestPath: toRepositoryPath(root, manifestPath),
slug,
});
}
}
return references.sort((left, right) => left.manifestPath.localeCompare(right.manifestPath));
}
async function collectExistingCraftSlugs(): Promise<Set<string>> {
const entries = await readdir(craftRoot, { withFileTypes: true });
const slugs = new Set<string>();
for (const entry of entries) {
if (!entry.isFile() || path.extname(entry.name) !== ".md") continue;
const slug = path.basename(entry.name, ".md");
if (slug === "README" || slug === "FUTURE_SECTIONS") continue;
if (slugPattern.test(slug)) slugs.add(slug);
}
return slugs;
}
function extractFutureCraftSlugs(source: string): Set<string> {
const slugs = new Set<string>();
for (const line of source.split(/\r?\n/)) {
const match = /^\s*[-*]\s+`?([a-z0-9][a-z0-9-]*)`?\s*$/.exec(line);
if (match?.[1]) slugs.add(match[1]);
}
return slugs;
}
async function collectFutureCraftSlugs(): Promise<Set<string>> {
if (!(await pathExists(futureSectionsPath))) return new Set();
return extractFutureCraftSlugs(await readFile(futureSectionsPath, "utf8"));
}
function formatSlug(slug: unknown): string {
return typeof slug === "string" ? `'${slug}'` : JSON.stringify(slug);
}
function printViolations(violations: CraftReferenceViolation[]): void {
const invalid = violations.filter((violation) => violation.kind === "invalid");
const unresolved = violations.filter((violation) => violation.kind === "unresolved");
if (invalid.length > 0) {
console.error("Invalid craft reference entries:");
for (const violation of invalid) {
console.error(`- ${violation.manifestPath}: ${formatSlug(violation.slug)}`);
}
console.error("Craft slugs must be strings containing lowercase letters, digits, and hyphens only.");
}
if (unresolved.length > 0) {
console.error("Unresolved craft reference slugs:");
for (const violation of unresolved) {
console.error(`- ${violation.manifestPath}: ${formatSlug(violation.slug)}`);
}
console.error("Add craft/<slug>.md, fix the typo, or list an intentional forward reference in craft/FUTURE_SECTIONS.md.");
}
}
export async function checkCraftReferences(): Promise<boolean> {
const references = await collectCraftReferences();
const existingSlugs = await collectExistingCraftSlugs();
const futureSlugs = await collectFutureCraftSlugs();
const violations = findCraftReferenceViolations(references, existingSlugs, futureSlugs);
const manifestCount = new Set(references.map((reference) => reference.manifestPath)).size;
if (violations.length > 0) {
printViolations(violations);
return false;
}
console.log(
`Craft reference check passed: ${references.length} references across ${manifestCount} manifests resolve or are explicitly planned.`,
);
return true;
}
const isMain = process.argv[1] ? import.meta.url === pathToFileURL(process.argv[1]).href : false;
if (isMain && !(await checkCraftReferences())) {
process.exitCode = 1;
}