mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-17 00:26:41 +03:00
* Release: skill v4.1.0, CLI v3.6.0, extension v1.3.2 Skill 4.1.0: the build path becomes a recorded setting with a per-round toggle, the direction round routes challengers by verdict, surface rounds deal structure, and critique delivers its report and its close. CLI 3.6.0: contrast findings stop assuming white when the ground cannot be read, waivers scope to the element that carries them, and Hermes Agent and Antigravity install natively. Extension 1.3.2: no source change, but the bundled engine is rebuilt at release, so the same 59 rules ship with the false-positive work behind them. Chrome and Firefox from the one manifest. Harness output regenerated with build:release, which is what the version validator checks against the manifests. Written with AI assistance (Claude Code). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Bound release-note extraction to the entry it names Every v4.0.x skill release shipped v4.0.0's notes. The extractor took the first `<ul class="cf-items">` after the version header with no upper bound, and the v4.0.1 through v4.0.4 entries wrote their bullets in a `cf-entry-list` instead, so the search ran past all four and landed in v4.0.0. Nothing failed, because finding a list somewhere was treated as success. The search now stops at the entry's own `</article>` and fails with the reason when the entry has no readable list, which is the case the old code silently published its way through. The changelog side is fixed in impeccable-site, where those five entries now use `cf-items` like the other 46: `cf-entry-list` also had no CSS at all, so their bullets were rendering unstyled on the changelog page. Written with AI assistance (Claude Code). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
73 lines
2.4 KiB
JavaScript
73 lines
2.4 KiB
JavaScript
/**
|
|
* Decide whether a given file is "generated" (regenerated by a build step,
|
|
* unsafe to write variants into) or "source" (safe to edit, changes persist).
|
|
*
|
|
* Why this matters: when the user picks an element on a page whose underlying
|
|
* file is regenerated by a build step (e.g. `scripts/build-sub-pages.js`
|
|
* rewriting `public/docs/*.html`), writing variants or accepted changes into
|
|
* that file is silent data loss — the next build wipes them.
|
|
*
|
|
* Signals, in order of reliability:
|
|
* 1. Git check-ignore: gitignored files are assumed generated.
|
|
* 2. File-header markers ("GENERATED", "DO NOT EDIT", "AUTO-GENERATED")
|
|
* within the first ~300 characters — catches non-git projects.
|
|
*/
|
|
|
|
import { execFileSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
const HEADER_SCAN_BYTES = 300;
|
|
const HEADER_MARKERS = [
|
|
/@generated\b/i,
|
|
/\bGENERATED\s+FILE\b/,
|
|
/\bAUTO-?GENERATED\b/i,
|
|
/\bDO\s+NOT\s+EDIT\b/i,
|
|
];
|
|
|
|
/**
|
|
* @param {string} filePath - absolute or cwd-relative path
|
|
* @param {object} [options]
|
|
* @param {string} [options.cwd] - project root (defaults to process.cwd())
|
|
*/
|
|
export function isGeneratedFile(filePath, options = {}) {
|
|
const cwd = options.cwd || process.cwd();
|
|
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
|
|
|
|
if (isGitIgnored(absPath, cwd)) return true;
|
|
if (hasGeneratedHeader(absPath)) return true;
|
|
return false;
|
|
}
|
|
|
|
function isGitIgnored(absPath, cwd) {
|
|
try {
|
|
// argv form, never a shell: this runs on every file the live-mode source
|
|
// walk reaches, so a hostile filename embedding $(...) or backticks must
|
|
// not be interpretable (issue #476). JSON.stringify is not shell quoting.
|
|
execFileSync('git', ['check-ignore', '--quiet', absPath], {
|
|
cwd,
|
|
stdio: 'ignore',
|
|
});
|
|
return true; // exit 0 = ignored
|
|
} catch (err) {
|
|
// Exit code 1 = not ignored. Exit code 128 = not a git repo or other error.
|
|
// In both cases, treat as "not known to be ignored."
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function hasGeneratedHeader(absPath) {
|
|
let fd;
|
|
try {
|
|
fd = fs.openSync(absPath, 'r');
|
|
const buf = Buffer.alloc(HEADER_SCAN_BYTES);
|
|
const bytesRead = fs.readSync(fd, buf, 0, HEADER_SCAN_BYTES, 0);
|
|
const head = buf.slice(0, bytesRead).toString('utf-8');
|
|
return HEADER_MARKERS.some((re) => re.test(head));
|
|
} catch {
|
|
return false;
|
|
} finally {
|
|
if (fd !== undefined) { try { fs.closeSync(fd); } catch {} }
|
|
}
|
|
}
|