INVARIANTS pinned only 'input validation at trust boundaries'; the other three carve-outs (data-loss, security, accessibility) could drift silently. Adds 'prevents data loss', 'security', 'accessibility' as canaries, each present verbatim in both SKILL.md and AGENTS.md. No rule text changed.
74 lines
2.7 KiB
JavaScript
74 lines
2.7 KiB
JavaScript
#!/usr/bin/env node
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const root = path.join(__dirname, '..');
|
|
|
|
function read(relPath) {
|
|
return fs.readFileSync(path.join(root, relPath), 'utf8').replace(/\r\n/g, '\n').trim();
|
|
}
|
|
|
|
function stripFrontmatter(text) {
|
|
return text.replace(/^---\n[\s\S]*?\n---\n*/, '').trim();
|
|
}
|
|
|
|
const agents = read('AGENTS.md');
|
|
const canonical = agents.replace(/\n\n\(Yes, this file also applies[\s\S]*?\)$/, '').trim();
|
|
|
|
// Compact copies: same body as AGENTS.md, host-specific frontmatter stripped.
|
|
const copies = [
|
|
['.cursor/rules/ponytail.mdc', stripFrontmatter],
|
|
['.windsurf/rules/ponytail.md', text => text.trim()],
|
|
['.clinerules/ponytail.md', text => text.trim()],
|
|
['.github/copilot-instructions.md', text => text.trim()],
|
|
['.kiro/steering/ponytail.md', stripFrontmatter],
|
|
];
|
|
|
|
let failed = false;
|
|
|
|
for (const [relPath, normalize] of copies) {
|
|
const actual = normalize(read(relPath));
|
|
if (actual !== canonical) {
|
|
console.error(`${relPath} drifted from AGENTS.md`);
|
|
failed = true;
|
|
}
|
|
}
|
|
|
|
// SKILL.md is the runtime source of truth and is longer than the compact body,
|
|
// so it cannot be byte-compared. ponytail: canary, not full equality. Assert the
|
|
// load-bearing rules survive verbatim in both the source and AGENTS.md. Changing
|
|
// a rule's wording trips this, which is the reminder to propagate it everywhere.
|
|
// Upgrade path: generate the copies from SKILL.md if this ever misses a real drift.
|
|
const INVARIANTS = [
|
|
'naive heuristic', // ceiling-comment rule
|
|
'ONE runnable check', // test reflex
|
|
'flimsier algorithm', // robust-variant rule
|
|
// the four "not lazy about" safety carve-outs: pin each so a reword in either
|
|
// file can't silently drop one. Only validation was pinned before. These are the
|
|
// continuous substrings present in both files ("prevents data loss" because the
|
|
// full "error handling that prevents data loss" wraps a line in SKILL.md).
|
|
'input validation at trust boundaries',
|
|
'prevents data loss',
|
|
'security',
|
|
'accessibility',
|
|
'Lazy code without its check is unfinished', // one-check promoted to headline
|
|
];
|
|
|
|
const skill = read('skills/ponytail/SKILL.md');
|
|
const sources = [['skills/ponytail/SKILL.md', skill], ['AGENTS.md', agents]];
|
|
for (const phrase of INVARIANTS) {
|
|
for (const [label, text] of sources) {
|
|
if (!text.includes(phrase)) {
|
|
console.error(`${label} is missing rule invariant: "${phrase}"`);
|
|
failed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (failed) {
|
|
console.error('Update the copied rule text, AGENTS.md, or SKILL.md so the shared rules match.');
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`Rule copies match AGENTS.md; ${INVARIANTS.length} rule invariants present in SKILL.md and AGENTS.md.`);
|