mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
The detector is open source. The rules it ships were already public in this repo's git history and in every npm tarball of the JS engine, so a closed binary bought nothing it could keep; the moat is the service (the catalog, the labs, the review pipeline), not the check functions. Keeping them behind a prebuilt archive cost a C-ABI, an exact toolchain pin, a build-time download, a second release to order ahead of every engine release, and a serde layer that had to serve two encodings. Deleted - crates/core/src/ffi.rs, crates/core/build.rs, crates/core/tests/boundary.rs and the shim modules under src/checks and src/browser. - crates/foundation/src/boundary.rs and the postcard dependency. - DETECTOR_VERSION, scripts/check-detector-release.mjs and its test, the check:detector-release script, the detector gate and IMPECCABLE_SKIP_DETECTOR_CHECK in scripts/release.mjs. - scripts/lib/detector-bundle.mjs and tests/detector-bundle.test.mjs (the vendoring path for the closed browser bundle). - scripts/build-browser-detector.js and the build:browser script (a stub since the JS engine left the tree). - xtask's detector-archive subcommand and its public-repo lookup. Came back - crates/core is now the rule logic itself: every check_* / scan_*, the browser adapters, the visual-contrast decisions. It re-exports foundation as before, so no consumer changed. Its vectors dispatcher is the union of both id tables again, and tests/vectors.rs replays the frozen vectors straight through it. - crates/wasm and crates/xtask join the workspace. cargo xtask bundle builds the in-page bundle from browser-bundle/ plus the wasm core, writes dist/, refreshes the tracked crates/live/assets/detect-antipatterns- browser.js, and writes extension/detector/. bun run build:extension runs it instead of downloading. - crates/live/assets/detect-antipatterns-browser.js is tracked again; live mode embeds it and serves it as /detect.js. - Serde is back to plain derives: no is_human_readable branch in js::json_number, derived Serialize for Rgba and BrowserFinding with their skip_serializing_if attributes. - profile.release has lto = "fat" again; rust-toolchain.toml is plain stable plus the wasm32 target. The rust, rust-windows and oracle CI jobs lose continue-on-error and can be required. Verified - cargo build --workspace --all-targets: clean, no warnings. - cargo test --workspace: 346 pass, 0 fail (the 8 boundary tests are gone with the boundary). - cargo build -p impeccable-wasm --target wasm32-unknown-unknown --release: ok. - cargo xtask bundle && cargo xtask bundle --check: reproducible; the regenerated bundle is committed (it differs from the archived one, which was built with a pinned rustc and lto = false). - cargo build --release -p impeccable: no linker warnings, 12.5 MB (the same source at lto = false is 13.1 MB). - oracle: 795 pass, 0 fail, 0 accepted deltas, 0 missing goldens. - bun run build, bun run build:extension, web-ext lint (0 errors, 8 warnings), bun run test: 363 + 80 + 1 + 1 + 133 + 180 + 4 pass, 0 fail. - impeccable detect --no-config --json tests/fixtures/antipatterns: 128.7 ms median of 5. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
126 lines
4.4 KiB
JavaScript
126 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from 'node:fs';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { DEFAULT_SUITES, matchesSuiteTriggers } from './test-suites.mjs';
|
|
|
|
const eventName = process.env.GITHUB_EVENT_NAME || '';
|
|
const localNoChanges = !eventName && !process.env.CI_CHANGED_FILES;
|
|
// The nightly schedule exists for exactly one thing: the full live-e2e
|
|
// matrix. A schedule event has no diff base, so the change-detection path
|
|
// degenerates to "everything changed"; without this guard that would flip on
|
|
// every file-triggered opt-in suite, including the ones that bill LLM APIs
|
|
// (skill-behavior, accept-cleanup, deepseek), every single night.
|
|
const isSchedule = eventName === 'schedule';
|
|
const changedFiles = localNoChanges || isSchedule ? [] : getChangedFiles();
|
|
const forceDeterministic = localNoChanges || isSchedule || eventName === 'push' || eventName === 'workflow_dispatch';
|
|
const forceOptIn = eventName === 'workflow_dispatch';
|
|
// The Rust workspace (the engine) builds and tests when its own inputs move.
|
|
// tests/oracle is included: the goldens are the engine's behavior gate and
|
|
// the oracle job replays them against a source build.
|
|
const RUST_PATTERNS = [
|
|
/^crates\//,
|
|
/^Cargo\.(toml|lock)$/,
|
|
/^rust-toolchain\.toml$/,
|
|
/^browser-bundle\//,
|
|
/^tests\/oracle\//,
|
|
/^\.github\/workflows\/ci\.yml$/,
|
|
];
|
|
const rustChanged = changedFiles.some((file) => RUST_PATTERNS.some((re) => re.test(file)));
|
|
|
|
const plan = isSchedule
|
|
? {
|
|
core: true,
|
|
oracle: true,
|
|
rust: true,
|
|
detector: true,
|
|
live: true,
|
|
framework: true,
|
|
cli_remote_e2e: false,
|
|
live_e2e: true,
|
|
live_e2e_accept_cleanup: false,
|
|
skill_behavior: false,
|
|
live_svelte_adapter_deepseek: false,
|
|
}
|
|
: {
|
|
core: true,
|
|
oracle: forceDeterministic || matchesSuiteTriggers('oracle', changedFiles),
|
|
rust: forceDeterministic || rustChanged,
|
|
detector: forceDeterministic || matchesSuiteTriggers('detector', changedFiles),
|
|
live: forceDeterministic || matchesSuiteTriggers('live', changedFiles),
|
|
framework: forceDeterministic || matchesSuiteTriggers('framework', changedFiles),
|
|
cli_remote_e2e: forceOptIn,
|
|
live_e2e: forceOptIn || matchesSuiteTriggers('live-e2e', changedFiles),
|
|
live_e2e_accept_cleanup: forceOptIn || matchesSuiteTriggers('live-e2e-accept-cleanup', changedFiles),
|
|
skill_behavior: forceOptIn || matchesSuiteTriggers('skill-behavior', changedFiles),
|
|
live_svelte_adapter_deepseek: forceOptIn || matchesSuiteTriggers('live-svelte-adapter-deepseek', changedFiles),
|
|
};
|
|
|
|
writeGithubOutputs(plan);
|
|
printSummary(plan, changedFiles);
|
|
|
|
function getChangedFiles() {
|
|
if (process.env.CI_CHANGED_FILES) {
|
|
return process.env.CI_CHANGED_FILES
|
|
.split(/\r?\n/)
|
|
.map((file) => file.trim())
|
|
.filter(Boolean);
|
|
}
|
|
|
|
const event = process.env.GITHUB_EVENT_NAME || '';
|
|
const sha = process.env.GITHUB_SHA || 'HEAD';
|
|
|
|
if (event === 'pull_request' && process.env.GITHUB_BASE_REF) {
|
|
const base = `origin/${process.env.GITHUB_BASE_REF}`;
|
|
return gitDiffNames(`${base}...${sha}`) || gitDiffNames(`${base}...HEAD`) || allChanged();
|
|
}
|
|
|
|
const before = process.env.GITHUB_EVENT_BEFORE;
|
|
if (before && !/^0+$/.test(before)) {
|
|
return gitDiffNames(`${before}..${sha}`) || allChanged();
|
|
}
|
|
|
|
return allChanged();
|
|
}
|
|
|
|
function allChanged() {
|
|
return git(['ls-files']).split(/\r?\n/).filter(Boolean);
|
|
}
|
|
|
|
function gitDiffNames(range) {
|
|
try {
|
|
return git(['diff', '--name-only', range]).split(/\r?\n/).filter(Boolean);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function git(args) {
|
|
return execFileSync('git', args, { encoding: 'utf-8' });
|
|
}
|
|
|
|
function writeGithubOutputs(outputs) {
|
|
const outputPath = process.env.GITHUB_OUTPUT;
|
|
if (!outputPath) return;
|
|
const lines = [];
|
|
for (const [key, value] of Object.entries(outputs)) {
|
|
lines.push(`${key}=${value ? 'true' : 'false'}`);
|
|
}
|
|
fs.appendFileSync(outputPath, lines.join('\n') + '\n');
|
|
}
|
|
|
|
function printSummary(outputs, files) {
|
|
const deterministic = DEFAULT_SUITES.map((name) => `${name}=${outputs[name]}`).join(' ');
|
|
console.log(`Event: ${eventName || 'local'}`);
|
|
console.log(`Changed files: ${files.length}`);
|
|
console.log(`Deterministic suites: ${deterministic} rust=${outputs.rust}`);
|
|
console.log(
|
|
[
|
|
`cli_remote_e2e=${outputs.cli_remote_e2e}`,
|
|
`live_e2e=${outputs.live_e2e}`,
|
|
`live_e2e_accept_cleanup=${outputs.live_e2e_accept_cleanup}`,
|
|
`skill_behavior=${outputs.skill_behavior}`,
|
|
`deepseek=${outputs.live_svelte_adapter_deepseek}`,
|
|
].join(' '),
|
|
);
|
|
}
|