openpencil/tools/dump-diagnostics-golden.ts
Fini 8cff5abaf5 feat(ai): implement op-design-lint Rust crate (S1)
Port the pen-ai-skills diagnostics layer to a new pure Rust crate
`op-design-lint`: 14 design-lint detectors, the detect_all aggregator,
apply_fixes / detect_and_fix, and golden parity tests against the TS
oracle. Wire it into op-mcp as the read-only debug_validation_report
tool, gated by OPENPENCIL_DEBUG_TOOLS=1.

Detectors: empty_paths, unexpected_rotation, excessive_frame_effects,
invisible_containers, text_explicit_heights, text_effect,
text_corner_radius, text_stroke, text_bg_contrast, edge_section_padding,
stacked_horizontal_padding, sibling_inconsistencies (+ check_consistency),
detect_all.

Also includes: node_util shared helpers + pen-core color/visibility
ports, node_mut field accessors, set_property issue->node mutation
dispatch, golden fixture corpus + TS dump script, structural-parity
test, a CI golden-drift guard, and the gitignore fix so the fixture
docs/ dir is tracked.

This branch's per-commit history was squashed: the original 28 commits
carried fabricated timestamps and could not be honestly reconstructed,
so the work is recorded as a single commit at its real completion time.
2026-05-23 18:39:08 +08:00

60 lines
2.3 KiB
TypeScript

/**
* Golden parity dump — S1 `op-design-lint` parity oracle.
*
* Reads every hand-authored `PenDocument` fixture under
* `crates/op-design-lint/tests/fixtures/docs/`, runs the TS `detectAllIssues`
* (the parity oracle), and writes the resulting `Issue[]` as pretty JSON to
* `crates/op-design-lint/tests/fixtures/golden/<same-name>.json`.
*
* The Rust `tests/parity.rs` then asserts `op_design_lint::detect_all`
* structurally matches each committed golden file. A CI drift-guard job
* re-runs this script and fails if the regenerated golden differs from the
* committed copy (spec §8, Risk R4).
*
* Run: bun run tools/dump-diagnostics-golden.ts
*
* Each fixture is a single-page `PenDocument`; its lone root node
* (`doc.children[0]`) is the subtree `detectAllIssues` walks — matching how
* the Rust parity test feeds `detect_all`.
*/
import { readdirSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { detectAllIssues } from '@zseven-w/pen-ai-skills';
import type { PenDocument, PenNode } from '@zseven-w/pen-types';
const here = dirname(fileURLToPath(import.meta.url));
const fixturesRoot = join(here, '..', 'crates', 'op-design-lint', 'tests', 'fixtures');
const docsDir = join(fixturesRoot, 'docs');
const goldenDir = join(fixturesRoot, 'golden');
mkdirSync(goldenDir, { recursive: true });
const fixtureFiles = readdirSync(docsDir)
.filter((name) => name.endsWith('.json'))
.sort();
if (fixtureFiles.length === 0) {
throw new Error(`no fixture docs found under ${docsDir}`);
}
let total = 0;
for (const file of fixtureFiles) {
const docPath = join(docsDir, file);
const doc = JSON.parse(readFileSync(docPath, 'utf8')) as PenDocument;
const root = doc.children?.[0] as PenNode | undefined;
if (!root) {
throw new Error(`fixture ${file} has no root node in doc.children[0]`);
}
const issues = detectAllIssues(root, doc);
const goldenPath = join(goldenDir, file);
// Trailing newline so the file is POSIX-clean and `git diff` stays quiet.
writeFileSync(goldenPath, JSON.stringify(issues, null, 2) + '\n', 'utf8');
total += issues.length;
console.log(`${file}: ${issues.length} issue(s)`);
}
console.log(`\nwrote ${fixtureFiles.length} golden file(s), ${total} issue(s) total`);