feat(#1733): normalize-path-in-content production AST rule + fix Windows agent-skills content leak (Phase 5) (#1736)
* feat(#1733): normalize-path-in-content production AST rule (Phase 5) ADR-1703 Phase 5 — the first production-code rule. local/normalize-path-in-content (src/**/*.cts, @typescript-eslint/parser): flags a path-returning fn result (path.basename excluded — returns a separator-less filename) interpolated into an @-reference / config-dir markdown body without .replace(/\\/g,'/') normalization, per RULESET.CONTENT-PATH-NORMALIZATION / DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT. Build-and-assess found the canonical defect site (computePathPrefix) already compliant and only 1 src/ hit — a false positive (path.basename in a status message) — eliminated by narrowing (exclude basename; require a real @-ref/ config-dir marker, not bare .md). 0 src/ violations: clean forward-prevention. The out-of-band disable-ban now scans src/**/*.cts too (typescript-estree) so the production rule also cannot be eslint-disabled. Registered (error) + PROTECTED_RULES; CONTEXT.md predicates + how-to doc updated. - RuleTester suite (26 cases) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(#1733): add changeset for Windows agent-skills path-leak fix Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: harden mutation-matrix.cjs stdin read against EAGAIN on non-blocking pipe scripts/mutation-matrix.cjs read piped stdin via readFileSync(process.stdin.fd). On macOS libuv marks the stdin pipe fd non-blocking, so a synchronous read can throw EAGAIN before the writer fills the pipe — intermittently, under heavy CI shard load — aborting the script (status 2) and flaking mutation-matrix-ratchet. Replace with readStdinSync(): an fs.readSync loop that retries on EAGAIN (1ms synchronous Atomics.wait yield), stops on 0-byte/EOF, and rethrows other errors. Deterministic regression test injects EAGAIN via an fs.readSync monkeypatch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: re-run golden-install-parity on src/lib + installer changes (close drift guard) golden-install-parity hashes every installed bin/lib/*.cjs per runtime, so it must re-run whenever the built lib could change. ci-test-scope selected it for neither src/** nor installer changes, so a source-only edit (e.g. #1691's milestone.cts/roadmap.cts) recompiled bin/lib and silently drifted the golden fixtures past the scoped lane. Add golden-install-parity.test.cjs to both the 'TS runtime sources' and 'installer and package layout' selection rules, with behavioral regression tests for each. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: review-bot <review-bot@gsd>
This commit is contained in:
@@ -118,6 +118,7 @@ const RULES = [
|
||||
tests: [
|
||||
'tests/semver-compare.test.cjs',
|
||||
'tests/bug-10-semver-policy-consolidation.test.cjs',
|
||||
'tests/golden-install-parity.test.cjs', // any src/installer change can alter emitted install artifacts → re-verify golden install parity (drift guard)
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -134,6 +135,7 @@ const RULES = [
|
||||
'tests/install-path-detection.test.cjs',
|
||||
'tests/release-tarball-smoke.install.test.cjs',
|
||||
'tests/runtime-artifact-layout.test.cjs',
|
||||
'tests/golden-install-parity.test.cjs', // any src/installer change can alter emitted install artifacts → re-verify golden install parity (drift guard)
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -30,10 +30,52 @@
|
||||
*/
|
||||
|
||||
const { execFileSync } = require('child_process');
|
||||
const { readFileSync } = require('fs');
|
||||
const fs = require('fs');
|
||||
|
||||
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
||||
|
||||
// ── Resilient stdin reader ────────────────────────────────────────────────────
|
||||
// On macOS, libuv sets the stdin pipe fd to non-blocking mode. A synchronous
|
||||
// readFileSync(process.stdin.fd) can therefore throw EAGAIN ("resource
|
||||
// temporarily unavailable") when the writer hasn't yet filled the pipe — this
|
||||
// is intermittent under heavy CI shard load and causes a spurious status 2
|
||||
// exit. We work around it by calling fs.readSync in a loop and retrying on
|
||||
// EAGAIN with a 1 ms synchronous pause (Atomics.wait on a fresh SharedArrayBuffer
|
||||
// — no hot spin, no real-clock dependency, works under --experimental-vm-modules).
|
||||
/**
|
||||
* Read all of stdin synchronously, retrying on EAGAIN.
|
||||
*
|
||||
* @returns {string} UTF-8 decoded full stdin content.
|
||||
*/
|
||||
function readStdinSync() {
|
||||
const BUF_SIZE = 64 * 1024; // 64 KB chunks
|
||||
const buf = Buffer.allocUnsafe(BUF_SIZE);
|
||||
const chunks = [];
|
||||
|
||||
for (;;) {
|
||||
let bytesRead;
|
||||
try {
|
||||
bytesRead = fs.readSync(process.stdin.fd, buf, 0, BUF_SIZE, null);
|
||||
} catch (err) {
|
||||
if (err.code === 'EAGAIN') {
|
||||
// Non-blocking pipe not yet ready — yield for ~1 ms then retry.
|
||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1);
|
||||
continue;
|
||||
}
|
||||
if (err.code === 'EOF') {
|
||||
break;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
if (bytesRead === 0) {
|
||||
break; // Clean EOF
|
||||
}
|
||||
chunks.push(Buffer.from(buf.slice(0, bytesRead)));
|
||||
}
|
||||
|
||||
return Buffer.concat(chunks).toString('utf8');
|
||||
}
|
||||
|
||||
// ── Per-module mutation score ratchet ─────────────────────────────────────────
|
||||
// ADR-456 / issue #1187: every covered module declares a minScore floor.
|
||||
//
|
||||
@@ -195,7 +237,7 @@ function resolveChangedFiles(args) {
|
||||
// When --base is absent AND stdin is not a TTY (isTTY is falsy / undefined),
|
||||
// read a newline-delimited file list from stdin.
|
||||
if (!args.base && process.stdin.isTTY !== true) {
|
||||
const raw = readFileSync(process.stdin.fd, 'utf8');
|
||||
const raw = readStdinSync();
|
||||
return raw.split('\n').map(l => l.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -318,6 +360,6 @@ function resolveMutationBreak(raw) {
|
||||
|
||||
// Export internals for programmatic use (tests/mutation-matrix-ratchet.test.cjs).
|
||||
// The require.main guard prevents main() from running when this file is require()d.
|
||||
module.exports = { COVERED, TARGET_MUTATION_SCORE, resolveMutationBreak };
|
||||
module.exports = { COVERED, TARGET_MUTATION_SCORE, resolveMutationBreak, readStdinSync };
|
||||
|
||||
if (require.main === module) runMain(main);
|
||||
|
||||
Reference in New Issue
Block a user