diff --git a/scripts/build-extension.js b/scripts/build-extension.js index 8ea87192..6119ec12 100644 --- a/scripts/build-extension.js +++ b/scripts/build-extension.js @@ -14,6 +14,7 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; +import { ANTIPATTERNS } from '../src/detect-antipatterns.mjs'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(__dirname, '..'); @@ -54,22 +55,14 @@ console.log(`Generated ${path.relative(ROOT, DETECTOR_OUTPUT)} (${(output.length // --- 2. Extract antipatterns.json --- -const rawSource = fs.readFileSync(SOURCE, 'utf-8'); -const apMatch = rawSource.match(/const ANTIPATTERNS = \[([\s\S]*?)\n\];/); -if (apMatch) { - // Convert JS object literals to JSON. Include description so the - // devtools panel can show the full rule explanation in tooltips — - // previously this dropped description and the panel had nothing to display. - const antipatterns = new Function(`return [${apMatch[1]}]`)(); - const apJson = antipatterns.map(({ id, name, category, description }) => ({ - id, - name, - category: category || 'quality', - description: description || '', - })); - fs.writeFileSync(AP_OUTPUT, JSON.stringify(apJson, null, 2) + '\n'); - console.log(`Generated ${path.relative(ROOT, AP_OUTPUT)} (${antipatterns.length} rules)`); -} +const apJson = ANTIPATTERNS.map(({ id, name, category, description }) => ({ + id, + name, + category: category || 'quality', + description: description || '', +})); +fs.writeFileSync(AP_OUTPUT, JSON.stringify(apJson, null, 2) + '\n'); +console.log(`Generated ${path.relative(ROOT, AP_OUTPUT)} (${ANTIPATTERNS.length} rules)`); // --- 3. Zip packaging --- diff --git a/scripts/build.js b/scripts/build.js index 53f50610..e546e238 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -21,6 +21,7 @@ import { readSourceFiles, readPatterns } from './lib/utils.js'; import { createTransformer, PROVIDERS } from './lib/transformers/index.js'; import { createAllZips } from './lib/zip.js'; import { generateSubPages } from './build-sub-pages.js'; +import { ANTIPATTERNS } from '../src/detect-antipatterns.mjs'; /** * Generate authoritative counts from source data and write to public/js/generated/counts.js. @@ -112,14 +113,6 @@ function generateCounts(rootDir, skills, buildDir) { * Returns the number of validation errors. Build fails if > 0. */ function validateAntipatternRules(rootDir) { - const detectPath = path.join(rootDir, 'src/detect-antipatterns.mjs'); - const src = fs.readFileSync(detectPath, 'utf-8'); - const apMatch = src.match(/const ANTIPATTERNS = \[([\s\S]*?)\n\];/); - if (!apMatch) { - console.error(' ❌ Could not extract ANTIPATTERNS from detect-antipatterns.mjs'); - return 1; - } - const antipatterns = new Function(`return [${apMatch[1]}]`)(); const { antipatterns: skillSections } = readPatterns(rootDir); // Build section -> joined-DON'T-text lookup for substring matching. @@ -134,7 +127,7 @@ function validateAntipatternRules(rootDir) { let errors = 0; let validated = 0; - for (const rule of antipatterns) { + for (const rule of ANTIPATTERNS) { if (!rule.skillGuideline) continue; if (!rule.skillSection) { console.error(` ❌ Rule '${rule.id}' declares skillGuideline but no skillSection`); @@ -158,7 +151,7 @@ function validateAntipatternRules(rootDir) { if (errors > 0) { console.error(`\n❌ ${errors} anti-pattern rule(s) drift between src/detect-antipatterns.mjs and source/skills/impeccable/SKILL.md`); } else { - console.log(`✓ Validated ${validated}/${antipatterns.length} anti-pattern rules against impeccable SKILL.md`); + console.log(`✓ Validated ${validated}/${ANTIPATTERNS.length} anti-pattern rules against impeccable SKILL.md`); } return errors; } diff --git a/scripts/lib/sub-pages-data.js b/scripts/lib/sub-pages-data.js index 61246f93..7ad876bb 100644 --- a/scripts/lib/sub-pages-data.js +++ b/scripts/lib/sub-pages-data.js @@ -14,6 +14,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; import { readSourceFiles, parseFrontmatter } from './utils.js'; +import { ANTIPATTERNS } from '../../src/detect-antipatterns.mjs'; import { DETECTION_LAYERS, VISUAL_EXAMPLES, @@ -92,19 +93,11 @@ export const CATEGORY_DESCRIPTIONS = { }; /** - * Parse the ANTIPATTERNS array out of src/detect-antipatterns.mjs. - * Mirrors the trick in scripts/build.js validateAntipatternRules() so we - * don't have to run the browser-only module. + * Returns the ANTIPATTERNS array from src/detect-antipatterns.mjs. + * Previously extracted via regex + new Function(); now uses a direct ESM import. */ -export function readAntipatternRules(rootDir) { - const detectPath = path.join(rootDir, 'src/detect-antipatterns.mjs'); - const src = fs.readFileSync(detectPath, 'utf-8'); - const match = src.match(/const ANTIPATTERNS = \[([\s\S]*?)\n\];/); - if (!match) { - throw new Error(`Could not extract ANTIPATTERNS from ${detectPath}`); - } - // eslint-disable-next-line no-new-func - return new Function(`return [${match[1]}]`)(); +export function readAntipatternRules(_rootDir) { + return ANTIPATTERNS; } /**