From ee4969dffa9d9d1d9fe98d4840a063f8a81d9596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sat, 16 May 2026 08:31:55 +0300 Subject: [PATCH] feat(regression): 12-check registry (quick=6, full=12) Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/skills/regression/run.mjs | 59 ++++++++++++++++++++++++++ .claude/skills/regression/run.test.mjs | 24 +++++++++++ 2 files changed, 83 insertions(+) diff --git a/.claude/skills/regression/run.mjs b/.claude/skills/regression/run.mjs index e28b7519..59bcc6b9 100644 --- a/.claude/skills/regression/run.mjs +++ b/.claude/skills/regression/run.mjs @@ -100,3 +100,62 @@ export function verdictLine(v, total) { } return `🔴 RED — ${v.failed.length}/${total} failed: ${v.failed.join(', ')}`; } + +// ── data: checks registry ────────────────────────────────────────── +// Script-based checks carry `cmd`; binary-based checks carry `bin` + `argv`. +// `parse(combinedOutput, exitCode)` → canonical token. `cwd`: '.' = repo root, +// 'app' = the Laravel app. Execution order: quick checks first, then heavy. +export const CHECKS = [ + { + id: 'pint', label: 'Pint', tiers: ['quick', 'full'], cwd: 'app', + cmd: 'composer pint:test', parse: (_o, c) => parseExit('Pint', c), + }, + { + id: 'eslint', label: 'ESLint', tiers: ['quick', 'full'], cwd: 'app', + cmd: 'npm run lint:vue', parse: (_o, c) => parseExit('ESLint', c), + }, + { + id: 'prettier', label: 'Prettier', tiers: ['quick', 'full'], cwd: 'app', + cmd: 'npm run format:check', parse: (_o, c) => parseExit('Prettier', c), + }, + { + id: 'vue-tsc', label: 'vue-tsc', tiers: ['quick', 'full'], cwd: 'app', + cmd: 'npm run type-check', parse: (_o, c) => parseExit('vue-tsc', c), + }, + { + id: 'markdownlint', label: 'markdownlint', tiers: ['quick', 'full'], cwd: '.', + cmd: 'npm run lint:md', parse: (_o, c) => parseExit('markdownlint', c), + }, + { + id: 'cspell', label: 'cspell', tiers: ['quick', 'full'], cwd: '.', + cmd: 'npm run spell', parse: (_o, c) => parseExit('cspell', c), + }, + { + id: 'larastan', label: 'Larastan', tiers: ['full'], cwd: 'app', + cmd: 'composer stan', parse: (o) => parseLarastan(o), + }, + { + id: 'pest', label: 'Pest', tiers: ['full'], cwd: 'app', + cmd: 'composer test:parallel', parse: (o) => parsePest(o), + }, + { + id: 'vitest', label: 'Vitest', tiers: ['full'], cwd: 'app', + cmd: 'npm run test:vue', parse: (o) => parseVitest(o), + }, + { + id: 'vite-build', label: 'Vite build', tiers: ['full'], cwd: 'app', + cmd: 'npm run build', parse: (o) => parseViteBuild(o), + }, + { + id: 'lychee', label: 'lychee', tiers: ['full'], cwd: '.', + bin: 'lychee', + argv: ['--config', '.lychee.toml', 'docs/**/*.md', 'db/**/*.md', '*.md'], + parse: (o) => parseLychee(o), + }, + { + id: 'gitleaks', label: 'gitleaks', tiers: ['full'], cwd: '.', + bin: 'gitleaks', + argv: ['detect', '--source', '.', '--no-banner', '--config', '.gitleaks.toml', '--redact'], + parse: (o, c) => parseGitleaks(o, c), + }, +]; diff --git a/.claude/skills/regression/run.test.mjs b/.claude/skills/regression/run.test.mjs index 7b80a508..2dc13530 100644 --- a/.claude/skills/regression/run.test.mjs +++ b/.claude/skills/regression/run.test.mjs @@ -6,6 +6,7 @@ import { parseViteBuild, parseLarastan, parseGitleaks, parseLychee, computeVerdict, buildCanonicalLine, formatRow, verdictLine, + CHECKS, } from './run.mjs'; test('resolveBinary: win32 → bin\\.exe', () => { @@ -155,3 +156,26 @@ test('verdictLine: RED-INCOMPLETE lists skipped checks', () => { assert.ok(line.includes('🟠 RED-INCOMPLETE')); assert.ok(line.includes('gitleaks')); }); + +test('CHECKS: quick tier has exactly 6 checks', () => { + assert.equal(CHECKS.filter((c) => c.tiers.includes('quick')).length, 6); +}); +test('CHECKS: full tier has exactly 12 checks', () => { + assert.equal(CHECKS.filter((c) => c.tiers.includes('full')).length, 12); +}); +test('CHECKS: quick is a strict subset of full', () => { + const full = new Set(CHECKS.filter((c) => c.tiers.includes('full')).map((c) => c.id)); + for (const c of CHECKS.filter((c) => c.tiers.includes('quick'))) { + assert.ok(full.has(c.id), `${c.id} in quick must also be in full`); + } +}); +test('CHECKS: every check has id, label, cwd, parse, and a command source', () => { + for (const c of CHECKS) { + assert.ok(c.id && c.label && c.cwd, `${c.id}: id/label/cwd`); + assert.equal(typeof c.parse, 'function', `${c.id}: parse is a function`); + assert.ok(c.cmd || (c.bin && Array.isArray(c.argv)), `${c.id}: has cmd or bin+argv`); + } +}); +test('CHECKS: ids are unique', () => { + assert.equal(new Set(CHECKS.map((c) => c.id)).size, CHECKS.length); +});