fix(critique-storage): make CLI entry-point check Windows-safe (#155)

The `import.meta.url === \`file://\${process.argv[1]}\`` guard at the
bottom of critique-storage.mjs silently failed on Windows: Node sets
import.meta.url to file:///D:/... (forward slashes) but process.argv[1]
is D:\... (backslashes), so the string compare returns false, main()
never runs, and the script exits 0 with no output. The OpenCode reporter
saw "/impeccable critique" skip the snapshot save with no error.

Switch to pathToFileURL(process.argv[1]).href, the standard cross-
platform pattern already used everywhere else in the repo.

Adds three CLI subprocess tests so future regressions of this guard
are caught even on macOS/Linux CI.

Fixes #155.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-05-14 15:48:27 -07:00
co-authored by Claude Opus 4.7
parent e493504496
commit 5f15163c2b
15 changed files with 119 additions and 15 deletions
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
+6 -1
View File
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { getCritiqueDir } from './impeccable-paths.mjs';
const SLUG_MAX = 50;
@@ -221,6 +222,10 @@ function main(argv) {
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
// Why pathToFileURL: on Windows, import.meta.url is file:///D:/... (forward
// slashes) while process.argv[1] is D:\... (backslashes), so the naive
// `file://${process.argv[1]}` compare fails and main() never runs — the
// script silently exits 0. pathToFileURL normalizes both. (issue #155)
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main(process.argv.slice(2));
}
+35 -1
View File
@@ -6,8 +6,12 @@
import { describe, it, beforeEach, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { join, resolve } from 'node:path';
import { tmpdir } from 'node:os';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const SCRIPT = fileURLToPath(new URL('../skill/scripts/critique-storage.mjs', import.meta.url));
import {
slugFromTarget,
@@ -148,6 +152,36 @@ describe('writeSnapshot + readLatestSnapshot', () => {
});
});
describe('CLI entry point', () => {
// Why a subprocess test: the CLI guard at the bottom of the script
// previously compared import.meta.url to `file://${process.argv[1]}`,
// which silently broke on Windows (forward vs back slashes) — exit 0,
// no output, save skipped. The exported functions kept passing because
// tests never spawned the script as a process. See issue #155.
it('slug subcommand prints a slug and exits 0', () => {
const r = spawnSync(process.execPath, [SCRIPT, 'slug', 'site/pages/index.astro'], {
cwd,
encoding: 'utf-8',
});
assert.equal(r.status, 0, `stderr: ${r.stderr}`);
assert.equal(r.stdout.trim(), 'site-pages-index-astro');
});
it('slug subcommand exits 1 with a message for empty input', () => {
const r = spawnSync(process.execPath, [SCRIPT, 'slug', ''], { cwd, encoding: 'utf-8' });
assert.equal(r.status, 1);
assert.match(r.stderr, /no stable slug/);
});
it('latest subcommand exits 2 when no snapshot exists', () => {
const r = spawnSync(process.execPath, [SCRIPT, 'latest', 'never-written'], {
cwd,
encoding: 'utf-8',
});
assert.equal(r.status, 2);
});
});
describe('readTrend', () => {
it('returns last N entries oldest → newest, filtered by slug', () => {
for (let i = 0; i < 6; i++) {