Files
pbakaus_impeccable/scripts/test-suites.mjs
T
776c019041 Add inline, in-file ignore comments for the detector (#283) (#285)
* Add inline, in-file ignore comments for the detector (issue #283)

Complement config ignores with eslint-disable-style waivers that live where
they apply and travel with the file when it leaves the repo. The motivating
case is a generated/exported standalone document that legitimately uses a
first-party brand typeface (on the overused-font list) and is later scanned
without .impeccable/config.json present.

Marker is comment-syntax-agnostic (works in //, /* */, <!-- -->, #, {/* */}):

  impeccable-disable <rule>[, <rule>...] [-- reason | : reason]   whole file
  impeccable-disable-line <rule>...                               same line
  impeccable-disable-next-line <rule>...                          next line

Bare directive or * means every rule; reason is optional and discarded at
scan time. Behavior is suppression, for parity with config ignores.

Implementation:
- New pure module cli/engine/shared/inline-ignores.mjs (parser + filter, no
  Node deps). Static-HTML findings have no line number, so only whole-file
  directives apply there -- exactly the standalone-document case; the
  regex/text engine additionally honors the line-scoped forms.
- Wired into detectText and detectHtml, gated by options.inlineIgnores.
- detect CLI applies inline ignores by default; --no-inline-ignores skips
  just them, --no-config skips config and inline ignores together.

Docs: config.md (new section), detector.md, README. skill/reference/hooks.md
reversed its prior "inline comments are not supported" guidance and now points
the agent to inline waivers for the travels-with-the-file case. Changelog 3.x.

Tests: tests/inline-ignores.test.mjs (parser units, detectText/detectHtml
integration, CLI end-to-end), registered in the detector suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Reconcile design hook wording with inline ignores

Two hook-side fixes prompted by review of the new inline-ignore feature:

1. Clean-ack steer line. The old line ("Keep typography hierarchy, spacing
   rhythm, and color contrast intentional on the next change.") read as an
   odd non-sequitur after "No anti-patterns." Reworded the whole clean ack to
   say what it means: a clean scan only clears the deterministic rule set, not
   overall design quality, so keep following the design system and skill
   guidance. Now: "Design hook scanned X. No deterministic design-quality
   issues found. That does not mean the design is good: keep following the
   project design system and the impeccable skill guidance."

2. Directive footer. It still told the agent "Do not add source comments such
   as `impeccable: ignore`; those pollute the code and do not suppress hook
   findings." That is now misleading: the hook runs the same detector engine
   as the CLI, which honors inline `impeccable-disable` waivers, so they DO
   suppress hook findings (consistent with config ignores, which filterFindings
   already honors). Reworded to: don't silence a real finding to skip fixing
   it; suppress only after the user confirms intent; prefer a config ignore,
   and reach for an inline `impeccable-disable <rule>` comment only when the
   waiver must travel with a file that leaves the repo.

Added a hook test asserting an inline `impeccable-disable-line` comment makes
the hook scan the file clean (locks in the cross-cutting behavior), and updated
the clean-ack / footer assertions to the new wording.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address review on inline-ignores parser

- Case-insensitive fast-path bail-out (Cursor): the cheap substring guard was
  lowercase-only while DIRECTIVE_RE has the `i` flag, so a mixed-case marker
  like `Impeccable-Disable` skipped parsing entirely and never suppressed.
  Switched the guard to `/impeccable-disable/i.test(...)`. Added a regression
  test.
- Removed the unreachable `-->` branch from TRAILING_CLOSER_RE (Greptile):
  `--+>` already matches `-->` and any longer dash run.
- Replaced the always-truthy lazy-match + `if (sep)` reason strip with an
  explicit first-separator slice (Greptile): clearer and drops the dead branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Align inline-ignore line numbering with the detector (CRLF/CR endings)

parseInlineIgnores split lines with /\r\n|\r|\n/, but detectText numbers lines
with split('\n'). On classic `\r`-only endings the two diverged, so a
disable-line / disable-next-line directive could key a different line than the
finding it should waive (Cursor review). Split on '\n' only, matching the
detector exactly; the directive regex already excludes '\r', so a trailing '\r'
on CRLF files is never captured into the rule list. Added a CRLF regression test
through the real detectText.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 21:41:36 +09:00

330 lines
11 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
export const DEFAULT_SUITES = ['core', 'detector', 'live', 'framework'];
export const OPT_IN_SUITES = [
'cli-remote-e2e',
'live-e2e',
'live-e2e-accept-cleanup',
'skill-behavior',
'live-svelte-adapter-deepseek',
];
const COMMON_INFRA_PATTERNS = [
/^package\.json$/,
/^bun\.lock$/,
/^scripts\/run-tests\.mjs$/,
/^scripts\/test-suites\.mjs$/,
/^scripts\/ci-test-plan\.mjs$/,
/^\.github\/workflows\/ci\.yml$/,
];
export const SUITES = {
core: {
description: 'Build, provider transforms, CLI helpers, context, and storage unit tests.',
triggers: [
...COMMON_INFRA_PATTERNS,
/^scripts\/(?!benchmark-detector|build-browser-detector|build-extension)/,
/^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|context|context-signals|critique-storage|design-parser|hook|impeccable-paths|is-generated))/,
/^site\/(pages|content|components|layouts)\//,
/^README(\.npm)?\.md$/,
/^cli\/bin\//,
/^tests\/(build|cleanup-deprecated|cli-ignores|context|context-signals|critique-storage|design-parser|docs-integrity|hook|hook-build|impeccable-paths|shiki-theme|skills-cli|target-args|test-suites|windows-path-fix|zip)\.test\.(js|mjs)$/,
/^tests\/lib\//,
],
commands: [
{
runner: 'bun',
files: [
'tests/build.test.js',
'tests/cli-ignores.test.js',
'tests/windows-path-fix.test.js',
'tests/lib/provider-blocks.test.js',
'tests/lib/transformers/provider-blocks.test.js',
'tests/lib/utils.test.js',
'tests/lib/impeccable-config.test.js',
'tests/lib/transformers/factory.test.js',
'tests/lib/transformers/providers.test.js',
'tests/docs-integrity.test.js',
'tests/skills-cli.test.js',
'tests/validate-plugin-versions.test.js',
],
},
{
runner: 'node',
files: [
'tests/ci-test-plan.test.mjs',
'tests/context.test.mjs',
'tests/context-signals.test.mjs',
'tests/critique-storage.test.mjs',
'tests/design-parser.test.mjs',
'tests/hook-build.test.mjs',
'tests/hook.test.mjs',
'tests/impeccable-paths.test.mjs',
'tests/target-args.test.mjs',
'tests/shiki-theme.test.mjs',
'tests/test-suites.test.mjs',
'tests/zip.test.mjs',
],
},
],
},
detector: {
description: 'Anti-pattern detector tests across text, jsdom fixtures, and Puppeteer browser paths.',
needsPuppeteer: true,
triggers: [
...COMMON_INFRA_PATTERNS,
/^cli\/engine\//,
/^extension\/(background|content|detector|devtools|popup|manifest\.json)/,
/^scripts\/(benchmark-detector|build-browser-detector|build-extension)\.js$/,
/^site\/(pages\/detector|public\/antipattern|data\/anti-patterns-catalog\.js)/,
/^tests\/design-system\.test\.mjs$/,
/^tests\/(detect-antipatterns|inline-ignores|extension-build|fixtures\/antipatterns)/,
],
commands: [
{
runner: 'bun',
files: [
'tests/detect-antipatterns.test.js',
'tests/inline-ignores.test.mjs',
'tests/lib/detector-bundle.test.js',
],
},
{
runner: 'node',
files: [
'tests/extension-build.test.mjs',
'tests/design-system.test.mjs',
'tests/detect-antipatterns-fixtures.test.mjs',
'tests/detect-antipatterns-browser.test.mjs',
],
},
],
},
live: {
description: 'Fast live-mode unit and local-server integration tests, excluding full browser fixture sweeps.',
triggers: [
...COMMON_INFRA_PATTERNS,
/^skill\/(reference\/live\.md|scripts\/(detect-csp|lib\/is-generated|live\/|live|live-|modern-screenshot|pin|palette))/,
/^tests\/live-/,
/^tests\/live-e2e\/(agent|agents\/llm-agent|cli-options|preactions|session|steer|ui)\.mjs$/,
/^tests\/live-e2e\/agent-insert\.test\.mjs$/,
],
commands: [
{
runner: 'node',
files: [
'tests/live-accept.test.mjs',
'tests/live-accept-scrub.test.mjs',
'tests/live-browser-dom.test.mjs',
'tests/live-browser-script-parts.test.mjs',
'tests/live-browser-regression.test.mjs',
'tests/live-browser-session.test.mjs',
'tests/live-browser-source.test.mjs',
'tests/live-commit-manual-edits.test.mjs',
'tests/live-completion.test.mjs',
'tests/live-copy-edit-agent.test.mjs',
'tests/live-discard-manual-edits.test.mjs',
'tests/live-e2e-agent-output.test.mjs',
'tests/live-e2e-cli-options.test.mjs',
'tests/live-e2e-llm-agent.test.mjs',
'tests/live-e2e-steer-agent.test.mjs',
'tests/live-e2e/agent-insert.test.mjs',
'tests/live-event-validation.test.mjs',
'tests/live-inject.test.mjs',
'tests/live-insert.test.mjs',
'tests/live-insert-ui.test.mjs',
'tests/live-manual-edits-buffer.test.mjs',
'tests/live-poll.test.mjs',
'tests/live-poll-stream.test.mjs',
'tests/live-recovery-commands.test.mjs',
'tests/live-reference.test.mjs',
'tests/live-server.test.mjs',
'tests/live-session-store.test.mjs',
'tests/live-target-context.test.mjs',
'tests/live-wrap.test.mjs',
'tests/live-wrap-buffer-aware.test.mjs',
],
},
],
},
framework: {
description: 'Framework fixture coverage for live injection, CSP, generated-file detection, and wrapping.',
triggers: [
...COMMON_INFRA_PATTERNS,
/^tests\/framework-fixtures/,
/^tests\/framework-fixtures\.test\.mjs$/,
/^skill\/scripts\/(detect-csp|live-inject|live-wrap)\.mjs$/,
/^skill\/scripts\/lib\/is-generated\.mjs$/,
/^skill\/scripts\/live\/sveltekit-adapter\.mjs$/,
],
commands: [
{
runner: 'node',
files: ['tests/framework-fixtures.test.mjs'],
},
],
},
'cli-e2e': {
description: 'Deterministic CLI install/update tests against a local universal bundle.',
commands: [
{
runner: 'bun',
files: ['tests/skills-cli.test.js'],
},
],
},
'cli-remote-e2e': {
description: 'Remote CLI install/update smoke tests against impeccable.style.',
optIn: true,
triggers: [
...COMMON_INFRA_PATTERNS,
/^cli\/bin\/commands\/skills\.mjs$/,
/^tests\/skills-cli\.test\.js$/,
],
commands: [
{
runner: 'bun',
env: { IMPECCABLE_CLI_REMOTE_E2E: '1' },
files: ['tests/skills-cli.test.js'],
},
],
},
'live-e2e': {
description: 'Full Playwright live-mode click-to-accept sweep across runtime framework fixtures.',
optIn: true,
needsPlaywright: true,
triggers: [
...COMMON_INFRA_PATTERNS,
/^skill\/scripts\/live/,
/^tests\/framework-fixtures/,
/^tests\/live-e2e(\.test\.mjs|\/)/,
],
commands: [
{
runner: 'node',
timeoutMs: 600000,
forceExit: true,
files: ['tests/live-e2e.test.mjs'],
},
],
},
'live-e2e-accept-cleanup': {
description: 'Provider-backed post-accept cleanup regression.',
optIn: true,
needsPlaywright: true,
triggers: [
...COMMON_INFRA_PATTERNS,
/^skill\/scripts\/(live-accept|live-browser|live-server|live-wrap)\.mjs$/,
/^skill\/scripts\/live\/sveltekit-adapter\.mjs$/,
/^tests\/live-e2e-accept-cleanup-regression\.test\.mjs$/,
/^tests\/live-e2e\//,
],
commands: [
{
runner: 'node',
timeoutMs: 600000,
files: ['tests/live-e2e-accept-cleanup-regression.test.mjs'],
},
],
},
'live-e2e-agent': {
description: 'Focused insert-mode fake-agent helper tests.',
commands: [
{
runner: 'node',
files: ['tests/live-e2e/agent-insert.test.mjs'],
},
],
},
'skill-behavior': {
description: 'LLM-backed skill setup behavior scenarios.',
optIn: true,
triggers: [
...COMMON_INFRA_PATTERNS,
/^skill\/SKILL\.src\.md$/,
/^skill\/reference\/(init|document|brand|product|shape|craft|audit|polish|live)\.md$/,
/^skill\/scripts\/(context|context-signals|detect|detect-csp)\.mjs$/,
/^tests\/skill-behavior\//,
],
commands: [
{
runner: 'node',
timeoutMs: 300000,
files: ['tests/skill-behavior/scenarios.test.mjs'],
},
],
},
'live-svelte-adapter-deepseek': {
description: 'DeepSeek-backed Svelte adapter browser sweep.',
optIn: true,
needsPlaywright: true,
triggers: [
...COMMON_INFRA_PATTERNS,
/^skill\/scripts\/(live-server|live-wrap)\.mjs$/,
/^skill\/scripts\/live\/(sveltekit-adapter|svelte-component)\.mjs$/,
/^tests\/framework-fixtures\/vite8-sveltekit-stateful\//,
/^tests\/live-svelte-adapter-deepseek\.test\.mjs$/,
],
commands: [
{
runner: 'node',
timeoutMs: 1200000,
files: ['tests/live-svelte-adapter-deepseek.test.mjs'],
},
],
},
};
export function expandSuites(requested) {
const names = requested.length === 0 ? ['default'] : requested;
const expanded = [];
for (const name of names) {
if (name === 'default' || name === 'all-local') {
expanded.push(...DEFAULT_SUITES);
} else if (name === 'all') {
expanded.push(...DEFAULT_SUITES, ...OPT_IN_SUITES);
} else if (SUITES[name]) {
expanded.push(name);
} else {
throw new Error(`Unknown test suite "${name}". Run: node scripts/run-tests.mjs --list`);
}
}
return [...new Set(expanded)];
}
export function suiteFiles(suiteNames) {
const files = [];
for (const name of suiteNames) {
const suite = SUITES[name];
if (!suite) throw new Error(`Unknown test suite "${name}"`);
for (const command of suite.commands) {
files.push(...command.files);
}
}
return files;
}
export function findTestFiles(root = process.cwd()) {
const out = [];
const stack = [path.join(root, 'tests')];
while (stack.length) {
const dir = stack.pop();
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const abs = path.join(dir, entry.name);
if (entry.isDirectory()) {
stack.push(abs);
} else if (/\.test\.(js|mjs)$/.test(entry.name)) {
out.push(path.relative(root, abs).split(path.sep).join('/'));
}
}
}
return out.sort();
}
export function matchesSuiteTriggers(suiteName, changedFiles) {
const suite = SUITES[suiteName];
if (!suite) throw new Error(`Unknown test suite "${suiteName}"`);
return changedFiles.some((file) => suite.triggers?.some((pattern) => pattern.test(file)));
}