mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
docs(skill): apply STYLE.md to source/skills/impeccable (#135)
Follow-up to #134, which scoped validateProse to user-facing copy and left the LLM-facing skill files alone. Bring those to the same bar, phased so hardening repetition stays intact. - Em dashes: 419 → 0 across SKILL.md and 35 reference files. Each replacement picks the right relationship (colon, semicolon, period, or parens) instead of letting the dash hide the choice. - Closer cleanup: deleted or rewrote the "Remember:" sermonettes that were pure adjective chants (bolder/quieter/clarify/delight/extract/ colorize/layout/typeset/audit/adapt). Survivors that load-bear an instruction now hand off to /impeccable polish instead of summarizing. - Opener taglines: rewrote the "[Verb] [object] to [outcome]" brochure openers in 12 older files to lead with the failure mode, the strongest claim, or a directive. Newer files (live, brand, product, audit, critique, harden) kept their existing openers. - data-driven: rephrased the two technical hits in live.md so the validator can stay strict on this term. - validateSkillProse: narrow validator scoped to source/skills/impeccable/. Em-dash check + the small denylist of phrases with no technical reading. Hardening repetition and structural-prose rules are deliberately not enforced — those need human judgment. Test failure on detectUrl is pre-existing (puppeteer needs --no-sandbox when running as root); unrelated to these changes. https://claude.ai/code/session_013zZY6rbB1bS8z3D63rX5hW Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
+93
-1
@@ -233,6 +233,94 @@ function validateProse(rootDir) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrow prose check for the impeccable skill source.
|
||||
*
|
||||
* The full validateProse rules don't fit LLM-facing reference instructions:
|
||||
* the hardening repetition and triadic checklists those files use exist on
|
||||
* purpose, and the structural-prose rules in STYLE.md require human judgment.
|
||||
* This validator only enforces the mechanical wins: em dashes (which are
|
||||
* pure punctuation laziness regardless of audience) and the small handful
|
||||
* of denylisted phrases that have no technical reading. Em-dash creep is the
|
||||
* only thing likely to come back at scale once humans stop watching.
|
||||
*
|
||||
* Returns the number of occurrences found. Build fails if > 0.
|
||||
*/
|
||||
function validateSkillProse(rootDir) {
|
||||
const target = 'source/skills/impeccable';
|
||||
const extensions = new Set(['.md']);
|
||||
const emDashPatterns = [/—/g, /—/gi, /—/gi, /—/gi];
|
||||
// Tighter than validateProse: only the rules that have no technical reading.
|
||||
// Skipping `data-driven` here would be a mistake (it slipped through twice
|
||||
// in live.md before this pass); but `seamless`, `robust`, etc. have
|
||||
// legitimate technical uses elsewhere we may want to allow.
|
||||
const phraseRules = [
|
||||
{ re: /\bload-bearing\b/i, rationale: 'AI tell. Name what the thing actually does.' },
|
||||
{ re: /\bhighest-leverage\b/i, rationale: 'AI tell. Say what specifically pays off.' },
|
||||
{ re: /\bbiggest unlock\b/i, rationale: 'Marketing-speak. Describe the actual change.' },
|
||||
{ re: /\breflex defaults?\b/i, rationale: 'Internal jargon. Say "instincts" or "first guesses".' },
|
||||
{ re: /\bcollapses? into monoculture\b/i, rationale: 'Eval-speak. Describe what actually went wrong.' },
|
||||
{ re: /\bdata-driven\b/i, rationale: 'Empty marketing adjective. Cite the data instead.' },
|
||||
{ re: /\bdelves?\b|\bdelved\b|\bdelving\b/i, rationale: 'Top AI tell. Use "explore" or "look at".' },
|
||||
{ re: /\btapestry\b/i, rationale: 'AI scenery noun. Cut.' },
|
||||
{ re: /\bin today's\b/i, rationale: 'Throat-clearing opener. Start at the point.' },
|
||||
{ re: /\bgone are the days\b/i, rationale: 'Throat-clearing. Make the point directly.' },
|
||||
{ re: /\blet's dive in\b/i, rationale: 'Throat-clearing. Just start.' },
|
||||
{ re: /\bin summary\b|\bin conclusion\b/i, rationale: 'Summarizing closer. End on the strongest sentence.' },
|
||||
];
|
||||
let errors = 0;
|
||||
|
||||
const checkLine = (line, rel, lineNum) => {
|
||||
for (const re of emDashPatterns) {
|
||||
if (re.test(line)) {
|
||||
console.error(` ❌ ${rel}:${lineNum}: em dash → ${line.trim().slice(0, 120)}`);
|
||||
console.error(` Use commas, colons, semicolons, periods, or parentheses.`);
|
||||
errors++;
|
||||
re.lastIndex = 0;
|
||||
break;
|
||||
}
|
||||
re.lastIndex = 0;
|
||||
}
|
||||
if (/ -- /.test(line)) {
|
||||
console.error(` ❌ ${rel}:${lineNum}: \` -- \` em-dash substitute → ${line.trim().slice(0, 120)}`);
|
||||
console.error(` Worse than the em dash. Pick real punctuation.`);
|
||||
errors++;
|
||||
}
|
||||
for (const rule of phraseRules) {
|
||||
if (rule.re.test(line)) {
|
||||
const matched = line.match(rule.re)?.[0] ?? '';
|
||||
console.error(` ❌ ${rel}:${lineNum}: "${matched}" → ${line.trim().slice(0, 120)}`);
|
||||
console.error(` ${rule.rationale}`);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const scan = (absPath, rel) => {
|
||||
const stat = fs.statSync(absPath);
|
||||
if (stat.isDirectory()) {
|
||||
for (const entry of fs.readdirSync(absPath)) {
|
||||
scan(path.join(absPath, entry), path.join(rel, entry));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!extensions.has(path.extname(absPath))) return;
|
||||
const src = fs.readFileSync(absPath, 'utf-8');
|
||||
const lines = src.split('\n');
|
||||
lines.forEach((line, i) => checkLine(line, rel, i + 1));
|
||||
};
|
||||
|
||||
const full = path.join(rootDir, target);
|
||||
if (fs.existsSync(full)) scan(full, target);
|
||||
|
||||
if (errors === 0) {
|
||||
console.log(`✓ Skill prose validator: source/skills/impeccable/ is clean`);
|
||||
} else {
|
||||
console.error(`\n❌ ${errors} prose issue(s) in source/skills/impeccable/. See STYLE.md.`);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that every hand-authored HTML page carries the shared site header.
|
||||
* The partial is stamped with `<!-- site-header v1 -->` so drift is loud.
|
||||
@@ -727,7 +815,11 @@ async function build() {
|
||||
// Scan user-facing copy for AI tells (em dashes, marketing fluff, denylisted phrases)
|
||||
const proseErrors = validateProse(ROOT_DIR);
|
||||
|
||||
if (countErrors > 0 || headerErrors > 0 || proseErrors > 0) {
|
||||
// Narrow scan of LLM-facing skill instructions: em dashes + a tighter denylist
|
||||
// that has no technical reading. Hardening repetition is intentionally allowed.
|
||||
const skillProseErrors = validateSkillProse(ROOT_DIR);
|
||||
|
||||
if (countErrors > 0 || headerErrors > 0 || proseErrors > 0 || skillProseErrors > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user