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.
60 lines
2.3 KiB
TypeScript
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`);
|