fix(hooks): respect package manager formatter fallback#365
fix(hooks): respect package manager formatter fallback#365tsubasakong wants to merge 2 commits intoaffaan-m:mainfrom
Conversation
📝 WalkthroughWalkthroughThis PR implements Windows-aware and project-aware command resolution for formatters. It replaces hard-coded npx usage with package-manager-specific runners (npm, pnpm, yarn, bun), respecting the CLAUDE_PACKAGE_MANAGER environment variable and project configuration. Changes
Sequence DiagramsequenceDiagram
participant Hook as post-edit-format Hook
participant Resolver as getFormatterCommand()
participant RunnerFactory as getFormatterRunner()
participant PkgMgr as getPackageManager()
participant Formatter as biome/prettier
Hook->>Resolver: Get command for file
Resolver->>RunnerFactory: Build runner for project
RunnerFactory->>PkgMgr: Detect package manager<br/>(CLAUDE_PACKAGE_MANAGER or project config)
PkgMgr-->>RunnerFactory: npm | pnpm | yarn | bun
RunnerFactory->>RunnerFactory: Map to runner:<br/>npm→npx, pnpm→pnpm dlx,<br/>yarn→yarn dlx, bun→bunx
RunnerFactory->>RunnerFactory: Handle Windows .cmd suffix
RunnerFactory-->>Resolver: {bin, prefix}
Resolver->>Resolver: Construct full command<br/>with runner bin + prefix
Resolver-->>Hook: [bin, prefix, formatter, args...]
Hook->>Formatter: Execute formatter command
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
e1e84f5 to
6f06ffd
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/hooks/hooks.test.js`:
- Around line 78-90: The current createCommandShim produces a POSIX shell script
which breaks Windows CI because getRunnerBin on Windows expects .cmd
executables; update createCommandShim to detect process.platform === 'win32' and
when true create a .cmd shim (append .cmd to shimPath if needed) whose content
uses Windows batch syntax to echo the command name and each %* arg into the
JSON-stringified logFile, otherwise keep the existing POSIX script for
non-Windows; ensure the created shim has the correct filename (with .cmd on
Windows) and is written with appropriate permissions (fs.writeFileSync) so tests
invoking getRunnerBin find and execute the shim on all platforms.
- Around line 746-774: The test currently asserts the shim invocation is exactly
'bunx' but on Windows the shim will be 'bunx.cmd'; update the assertion in the
asyncTest 'uses project package manager config for formatter fallback' to accept
the Windows variant by checking process.platform === 'win32' and expecting
'bunx.cmd' (or compute expectedBin = process.platform === 'win32' ? 'bunx.cmd' :
'bunx') then assert.deepStrictEqual(logLines, [expectedBin, '@biomejs/biome',
'format', '--write', testFile]) so the test passes cross-platform; modify the
assertion near the assert.deepStrictEqual call that references logLines
accordingly.
- Around line 718-744: The test fails on Windows because the pnpm shim is
created as 'pnpm' but the process looks for 'pnpm.cmd'; update the test to use
the platform-aware runner name instead of a hardcoded 'pnpm' by creating the
shim with getRunnerBin('pnpm') (i.e., call createCommandShim(binDir,
getRunnerBin('pnpm'), logFile)) and change the assertion that checks the logged
command to compare against getRunnerBin('pnpm') (or accept both 'pnpm' and
'pnpm.cmd') so post-edit-format.js and the test both use the same platform-aware
executable name; references: createCommandShim, getRunnerBin,
post-edit-format.js, logFile, and the assertion that currently expects ['pnpm',
'dlx', 'prettier', '--write', testFile].
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7e668100-7879-4500-a854-ffe22cc1abdc
📒 Files selected for processing (2)
scripts/hooks/post-edit-format.jstests/hooks/hooks.test.js
| function createCommandShim(binDir, name, logFile) { | ||
| const shimPath = path.join(binDir, name); | ||
| const script = `#!/bin/sh | ||
| { | ||
| printf '%s\n' "$(basename "$0")" | ||
| for arg in "$@"; do | ||
| printf '%s\n' "$arg" | ||
| done | ||
| } > ${JSON.stringify(logFile)} | ||
| `; | ||
| fs.writeFileSync(shimPath, script, { mode: 0o755 }); | ||
| return shimPath; | ||
| } |
There was a problem hiding this comment.
Cross-platform issue: POSIX-only shims cause Windows CI failures.
This createCommandShim function creates POSIX shell scripts, but on Windows:
getRunnerBinreturns.cmdsuffixed binaries (e.g.,pnpm.cmd)- The shim is created without the
.cmdextension - The shim content uses POSIX shell syntax (
#!/bin/sh,printf,for arg in "$@")
This is the root cause of the pipeline failures:
ENOENT: no such file or directory, open '...\pnpm.log'
ENOENT: no such file or directory, open '...\bunx.log'
🔧 Proposed fix: Platform-aware shim creation
function createCommandShim(binDir, name, logFile) {
- const shimPath = path.join(binDir, name);
- const script = `#!/bin/sh
+ const isWindows = process.platform === 'win32';
+ const shimPath = path.join(binDir, isWindows ? `${name}.cmd` : name);
+
+ const script = isWindows
+ ? `@echo off
+echo %~n0> "${logFile}"
+:loop
+if "%~1"=="" goto done
+echo %~1>> "${logFile}"
+shift
+goto loop
+:done
+`
+ : `#!/bin/sh
{
printf '%s\\n' "$(basename "$0")"
for arg in "$@"; do
printf '%s\\n' "$arg"
done
} > ${JSON.stringify(logFile)}
`;
fs.writeFileSync(shimPath, script, { mode: 0o755 });
return shimPath;
}As per coding guidelines: "Ensure cross-platform compatibility for Windows, macOS, and Linux via Node.js scripts".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function createCommandShim(binDir, name, logFile) { | |
| const shimPath = path.join(binDir, name); | |
| const script = `#!/bin/sh | |
| { | |
| printf '%s\n' "$(basename "$0")" | |
| for arg in "$@"; do | |
| printf '%s\n' "$arg" | |
| done | |
| } > ${JSON.stringify(logFile)} | |
| `; | |
| fs.writeFileSync(shimPath, script, { mode: 0o755 }); | |
| return shimPath; | |
| } | |
| function createCommandShim(binDir, name, logFile) { | |
| const isWindows = process.platform === 'win32'; | |
| const shimPath = path.join(binDir, isWindows ? `${name}.cmd` : name); | |
| const script = isWindows | |
| ? `@echo off | |
| echo %~n0> "${logFile}" | |
| :loop | |
| if "%~1"=="" goto done | |
| echo %~1>> "${logFile}" | |
| shift | |
| goto loop | |
| :done | |
| ` | |
| : `#!/bin/sh | |
| { | |
| printf '%s\n' "$(basename "$0")" | |
| for arg in "$@"; do | |
| printf '%s\n' "$arg" | |
| done | |
| } > ${JSON.stringify(logFile)} | |
| `; | |
| fs.writeFileSync(shimPath, script, { mode: 0o755 }); | |
| return shimPath; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/hooks/hooks.test.js` around lines 78 - 90, The current
createCommandShim produces a POSIX shell script which breaks Windows CI because
getRunnerBin on Windows expects .cmd executables; update createCommandShim to
detect process.platform === 'win32' and when true create a .cmd shim (append
.cmd to shimPath if needed) whose content uses Windows batch syntax to echo the
command name and each %* arg into the JSON-stringified logFile, otherwise keep
the existing POSIX script for non-Windows; ensure the created shim has the
correct filename (with .cmd on Windows) and is written with appropriate
permissions (fs.writeFileSync) so tests invoking getRunnerBin find and execute
the shim on all platforms.
| if (await asyncTest('uses CLAUDE_PACKAGE_MANAGER runner for formatter fallback', async () => { | ||
| const testDir = createTestDir(); | ||
| const binDir = path.join(testDir, 'bin'); | ||
| const logFile = path.join(testDir, 'pnpm.log'); | ||
| fs.mkdirSync(binDir, { recursive: true }); | ||
| createCommandShim(binDir, 'pnpm', logFile); | ||
|
|
||
| const testFile = path.join(testDir, 'src', 'example.ts'); | ||
| fs.mkdirSync(path.dirname(testFile), { recursive: true }); | ||
| fs.writeFileSync(path.join(testDir, 'package.json'), JSON.stringify({ name: 'pm-env-test' })); | ||
| fs.writeFileSync(path.join(testDir, '.prettierrc'), '{}'); | ||
| fs.writeFileSync(testFile, 'const answer=42;\n'); | ||
|
|
||
| try { | ||
| const stdinJson = JSON.stringify({ tool_input: { file_path: testFile } }); | ||
| const result = await runScript(path.join(scriptsDir, 'post-edit-format.js'), stdinJson, { | ||
| CLAUDE_PACKAGE_MANAGER: 'pnpm', | ||
| PATH: `${binDir}${path.delimiter}${process.env.PATH || ''}` | ||
| }); | ||
|
|
||
| assert.strictEqual(result.code, 0, 'Should exit 0 with pnpm fallback'); | ||
| const logLines = fs.readFileSync(logFile, 'utf8').trim().split('\n'); | ||
| assert.deepStrictEqual(logLines, ['pnpm', 'dlx', 'prettier', '--write', testFile]); | ||
| } finally { | ||
| cleanupTestDir(testDir); | ||
| } | ||
| })) passed++; else failed++; |
There was a problem hiding this comment.
Test will fail on Windows due to platform-specific shim issue.
The test creates a pnpm shim, but on Windows getRunnerBin('pnpm') returns pnpm.cmd. The execFileSync call in post-edit-format.js will search for pnpm.cmd but won't find it since the shim is created as pnpm (no .cmd extension).
Additionally, the assertion at Line 740 expects ['pnpm', 'dlx', 'prettier', '--write', testFile], but on Windows the first element would be pnpm.cmd if the shim were created correctly.
🔧 Proposed fix: Handle Windows in test assertion
assert.strictEqual(result.code, 0, 'Should exit 0 with pnpm fallback');
const logLines = fs.readFileSync(logFile, 'utf8').trim().split('\n');
- assert.deepStrictEqual(logLines, ['pnpm', 'dlx', 'prettier', '--write', testFile]);
+ const expectedBin = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
+ assert.deepStrictEqual(logLines, [expectedBin, 'dlx', 'prettier', '--write', testFile]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/hooks/hooks.test.js` around lines 718 - 744, The test fails on Windows
because the pnpm shim is created as 'pnpm' but the process looks for 'pnpm.cmd';
update the test to use the platform-aware runner name instead of a hardcoded
'pnpm' by creating the shim with getRunnerBin('pnpm') (i.e., call
createCommandShim(binDir, getRunnerBin('pnpm'), logFile)) and change the
assertion that checks the logged command to compare against getRunnerBin('pnpm')
(or accept both 'pnpm' and 'pnpm.cmd') so post-edit-format.js and the test both
use the same platform-aware executable name; references: createCommandShim,
getRunnerBin, post-edit-format.js, logFile, and the assertion that currently
expects ['pnpm', 'dlx', 'prettier', '--write', testFile].
| if (await asyncTest('uses project package manager config for formatter fallback', async () => { | ||
| const testDir = createTestDir(); | ||
| const binDir = path.join(testDir, 'bin'); | ||
| const logFile = path.join(testDir, 'bunx.log'); | ||
| fs.mkdirSync(binDir, { recursive: true }); | ||
| fs.mkdirSync(path.join(testDir, '.claude'), { recursive: true }); | ||
| createCommandShim(binDir, 'bunx', logFile); | ||
|
|
||
| const testFile = path.join(testDir, 'src', 'example.ts'); | ||
| fs.mkdirSync(path.dirname(testFile), { recursive: true }); | ||
| fs.writeFileSync(path.join(testDir, 'package.json'), JSON.stringify({ name: 'pm-project-test' })); | ||
| fs.writeFileSync(path.join(testDir, 'biome.json'), '{}'); | ||
| fs.writeFileSync(path.join(testDir, '.claude', 'package-manager.json'), JSON.stringify({ packageManager: 'bun' })); | ||
| fs.writeFileSync(testFile, 'const answer=42;\n'); | ||
|
|
||
| try { | ||
| const stdinJson = JSON.stringify({ tool_input: { file_path: testFile } }); | ||
| const result = await runScript(path.join(scriptsDir, 'post-edit-format.js'), stdinJson, { | ||
| CLAUDE_PACKAGE_MANAGER: '', | ||
| PATH: `${binDir}${path.delimiter}${process.env.PATH || ''}` | ||
| }); | ||
|
|
||
| assert.strictEqual(result.code, 0, 'Should exit 0 with project-config fallback'); | ||
| const logLines = fs.readFileSync(logFile, 'utf8').trim().split('\n'); | ||
| assert.deepStrictEqual(logLines, ['bunx', '@biomejs/biome', 'format', '--write', testFile]); | ||
| } finally { | ||
| cleanupTestDir(testDir); | ||
| } | ||
| })) passed++; else failed++; |
There was a problem hiding this comment.
Same cross-platform issue affects the bunx test.
The bunx shim has the same problem—created without .cmd extension and with POSIX shell syntax. The assertion at Line 770 also needs to account for bunx.cmd on Windows.
🔧 Proposed fix: Handle Windows in test assertion
assert.strictEqual(result.code, 0, 'Should exit 0 with project-config fallback');
const logLines = fs.readFileSync(logFile, 'utf8').trim().split('\n');
- assert.deepStrictEqual(logLines, ['bunx', '@biomejs/biome', 'format', '--write', testFile]);
+ const expectedBin = process.platform === 'win32' ? 'bunx.cmd' : 'bunx';
+ assert.deepStrictEqual(logLines, [expectedBin, '@biomejs/biome', 'format', '--write', testFile]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/hooks/hooks.test.js` around lines 746 - 774, The test currently asserts
the shim invocation is exactly 'bunx' but on Windows the shim will be
'bunx.cmd'; update the assertion in the asyncTest 'uses project package manager
config for formatter fallback' to accept the Windows variant by checking
process.platform === 'win32' and expecting 'bunx.cmd' (or compute expectedBin =
process.platform === 'win32' ? 'bunx.cmd' : 'bunx') then
assert.deepStrictEqual(logLines, [expectedBin, '@biomejs/biome', 'format',
'--write', testFile]) so the test passes cross-platform; modify the assertion
near the assert.deepStrictEqual call that references logLines accordingly.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
|
Opened #371 to carry the currently-validated maintainer path for this fix set. It includes equivalent coverage here, folds in related portability/docs fixes, and has already passed local \
Validated 16 agent files ━━━ Running lib/utils.test.js ━━━ === Testing utils.js === Platform Detection: Directory Functions: Date/Time Functions: Project Name Functions: Session ID Functions: File Operations: findFiles: Edge Cases: System Functions: output() and log(): isGitRepo(): getGitModifiedFiles(): getLearnedSkillsDir(): replaceInFile (behavior): writeFile (edge cases): findFiles (regex chars): readStdinJson(): grepFile (global regex fix): commandExists Edge Cases: findFiles Edge Cases: ensureDir Edge Cases: runCommand Edge Cases: getGitModifiedFiles Edge Cases: replaceInFile Edge Cases: readStdinJson Edge Cases: readStdinJson maxSize truncation: ensureDir Error Propagation (Round 31): runCommand failure output (Round 31): runCommand Security (allowlist + metacharacters): getGitModifiedFiles empty patterns (Round 31): readStdinJson error event (Round 33): getGitModifiedFiles all-invalid patterns (Round 69): Round 71: findFiles (unreadable subdirectory in recursive scan): Round 79: countInFile (valid string pattern): Round 79: grepFile (valid string pattern): Round 84: findFiles (inner statSync catch — broken symlink): getSessionIdShort fallback (Round 85): Round 88: replaceInFile with empty replacement string (deletion): Round 88: countInFile with existing file but non-matching pattern: Round 92: countInFile (non-string non-RegExp pattern): Round 93: countInFile (case-insensitive RegExp, g flag auto-appended): Round 93: countInFile (case-insensitive RegExp, g flag preserved): Round 95: countInFile (regex alternation without g flag): Round 97: getSessionIdShort (whitespace-only session ID): Round 97: countInFile (RegExp lastIndex reuse validation): Round 98: findFiles (maxAge: -1 — negative boundary excludes all): Round 99: replaceInFile (no-match still returns true): Round 99: grepFile (CR-only line endings — classic Mac format): Round 100: findFiles (maxAge + recursive combined — untested interaction): Round 101: output() (circular reference — JSON.stringify crash): Round 103: countInFile (boolean false — explicit type guard returns 0): Round 103: grepFile (numeric 0 — implicit toString via RegExp constructor): Round 105: grepFile (sticky y flag — not stripped like g, stateful .test() bug): Round 107: grepFile (empty line matching — ^$ on split lines, trailing \n creates extra empty element): Round 107: replaceInFile (replacement contains search pattern — String.replace is single-pass): Round 106: countInFile (named capture groups — String.match(g) returns full matches only): Round 106: grepFile (multiline m flag — preserved in regex, unlike g which is stripped): Round 109: appendFile (new file creation — ensureDir creates parent, appendFileSync creates file): Round 108: grepFile (Unicode/emoji — regex matching on UTF-16 split lines): Round 110: findFiles (root directory unreadable — EACCES on readdirSync caught silently): Round 113: replaceInFile (zero-width regex /(?:)/g — matches every position): Round 114: replaceInFile (options.all silently ignored for RegExp search): Round 114: output (object containing BigInt — JSON.stringify throws): Round 115: countInFile (empty string pattern — matches at every zero-width position): Round 117: grepFile (CRLF content — trailing \r breaks anchored regex patterns): Round 116: replaceInFile (null/undefined replacement — JS coerces to string "null"/"undefined"): Round 116: ensureDir (null path — fs.existsSync(null) throws TypeError): Round 118: writeFile (non-string content — TypeError propagates uncaught): Round 119: appendFile (non-string content — TypeError propagates like writeFile): Round 120: replaceInFile (empty string search — replace vs replaceAll dramatic difference): Round 121: findFiles (? glob pattern — converted to . regex for single char match): Round 122: findFiles (dot escaping — *.txt matches file.txt but not filetxt): Round 123: countInFile (overlapping patterns — String.match(/g/) is non-overlapping): Round 123: replaceInFile ( Round 124: findFiles (* glob matches dotfiles — unlike shell globbing): Round 125: readFile (binary/non-UTF8 content — garbled, not null): Round 125: output() (undefined/NaN/Infinity — typeof checks and JSON.stringify): === Test Results === [Utils] replaceInFile failed for /var/folders/ck/y983js7n5lz1z5ls6fq8v0cm0000gn/T/utils-test-readonly-1773115953651/readonly.txt: EACCES: permission denied, open '/var/folders/ck/y983js7n5lz1z5ls6fq8v0cm0000gn/T/utils-test-readonly-1773115953651/readonly.txt' ━━━ Running lib/package-manager.test.js ━━━ === Testing package-manager.js === PACKAGE_MANAGERS Constant: detectFromLockFile: detectFromPackageJson: getAvailablePackageManagers: getPackageManager: getRunCommand: getExecCommand: getCommandPattern: getSelectionPrompt: setProjectPackageManager: setPreferredPackageManager: detectFromPackageJson (edge cases): getExecCommand (edge cases): getRunCommand (additional): DETECTION_PRIORITY: getCommandPattern (additional): getPackageManager (robustness): getRunCommand (validation): getExecCommand (validation): getPackageManager (source detection): setPreferredPackageManager (success): getCommandPattern (completeness): getRunCommand (PM-specific formats): getExecCommand (PM-specific formats): getExecCommand (args validation): getCommandPattern (regex escaping): getRunCommand (non-string input): getExecCommand (non-string binary): getCommandPattern (escapeRegex completeness): getPackageManager (global config edge cases): Round 30: getCommandPattern edge cases: setProjectPackageManager (write verification, Round 31): getExecCommand (safe argument edge cases, Round 31): Round 34: getExecCommand non-string args: Round 34: detectFromPackageJson with non-string packageManager: Round 48: detectFromPackageJson (version format edge cases): Round 69: getPackageManager (global-config success): Round 71: setPreferredPackageManager (save failure): Round 72: setProjectPackageManager (save failure): Round 80: getExecCommand (truthy non-string args): Round 86: detectFromPackageJson (empty package.json): Round 91: getCommandPattern (empty action): Round 91: detectFromPackageJson (whitespace-only packageManager): Round 92: detectFromPackageJson (empty string packageManager): Round 94: detectFromPackageJson (scoped package name @scope/pkg@version): Round 94: getPackageManager (empty string CLAUDE_PACKAGE_MANAGER env var): Round 104: detectFromLockFile (null projectDir — throws TypeError): Round 105: getExecCommand (object args — typeof bypass coerces to [object Object]): Round 109: getExecCommand (path traversal in binary — SAFE_NAME_REGEX permits ../ in binary name): Round 108: getRunCommand (path traversal — SAFE_NAME_REGEX permits ../ via allowed / and . chars): Round 111: getExecCommand (newline in args — SAFE_ARGS_REGEX \s matches \n): === Test Results === ━━━ Running lib/session-manager.test.js ━━━ === Testing session-manager.js === parseSessionFilename: parseSessionMetadata: getSessionStats: Session CRUD: getSessionSize: getSessionTitle: getAllSessions: getSessionById: parseSessionMetadata (edge cases): getSessionStats (edge cases): getSessionSize (edge cases): parseSessionFilename (additional edge cases): writeSessionContent: appendSessionContent: deleteSession: sessionExists: getAllSessions (pagination edge cases): getSessionStats (code blocks & special chars): parseSessionFilename (30-day month validation): getSessionStats (path heuristic edge cases): getAllSessions (combined filters): getSessionById (ambiguous prefix): parseSessionMetadata (edge cases): Round 43: getSessionById (default excludes content): Round 54: search filter scope and path utility: Round 66: getSessionById (noIdMatch — date-only match for old format): Round 30: datetime local-time fix: Round 30: parseSessionFilename edge cases: createdTime fallback (Round 33): getSessionStats Windows path heuristic (Round 46): parseSessionMetadata checkbox case sensitivity (Round 46): Round 69: getSessionById (missing sessions directory): Round 78: getSessionStats (actual file path → reads from disk): Round 78: getAllSessions (hasContent field): Round 75: deleteSession (unlink failure in read-only dir): Round 81: getSessionStats(null) (null input): Round 83: getAllSessions (broken symlink — statSync catch): Round 84: getSessionById (broken symlink — statSync catch): Round 88: parseSessionMetadata content lacking Date/Started/Updated fields: Round 89: getAllSessions (subdirectory skip): Round 91: getSessionStats (mixed Windows path separators): Round 92: getSessionStats (Windows UNC path): Round 93: getSessionStats (drive letter without slash — regex boundary): Round 95: getAllSessions (both negative offset and negative limit): Round 96: parseSessionFilename (Feb 30 — impossible date): Round 96: getAllSessions (limit: Infinity — pagination bypass): Round 96: getAllSessions (limit: null — destructuring default bypass): Round 97: getAllSessions (whitespace search — truthy but unmatched): Round 98: getSessionById (null sessionId — crashes at line 297): Round 98: parseSessionFilename (null input — crashes at line 30): Round 99: writeSessionContent (null path — error handling): Round 100: parseSessionMetadata (### in item text — lazy regex truncation): Round 101: getSessionStats (non-string input — type confusion crash): Round 101: appendSessionContent (null path — error handling): Round 102: getSessionStats (Unix nonexistent .tmp path — looksLikePath → null content): Round 102: parseSessionMetadata ([x] items in In Progress — regex skips checked): Round 104: parseSessionMetadata (whitespace-only notes — trim reduces to empty): Round 105: parseSessionMetadata (blank line inside section — regex stops at \n\n): Round 106: getAllSessions (array/object limit coercion — Number([5])→5, Number({})→NaN→50): Round 109: getAllSessions (non-session .tmp files — parseSessionFilename returns null → skip): Round 108: getSessionSize (exact 1024-byte boundary — < means 1024 is KB, 1023 is B): Round 110: parseSessionFilename (year 0000 — Date constructor maps 0→1900): Round 110: parseSessionFilename (uppercase ID — regex [a-z0-9]{8,} rejects [A-Z]): Round 111: parseSessionMetadata (nested Round 112: getSessionStats (newline-in-path heuristic): Round 112: appendSessionContent (read-only file): Round 113: parseSessionFilename (century leap year — 100/400 rules): Round 113: parseSessionMetadata (title with markdown formatting — raw markdown preserved): Round 115: parseSessionMetadata (CRLF line endings — \r\n vs \n in section regexes): Round 117: getSessionSize (B/KB/MB formatting at exact boundary thresholds): Round 117: parseSessionFilename (uppercase short ID — regex [a-z0-9] rejects uppercase): Round 119: parseSessionMetadata ("Context to Load" — code block extraction edge cases): Round 120: parseSessionMetadata ("Notes for Next Session" — extraction edge cases): Round 121: parseSessionMetadata (Started/Last Updated time extraction): Round 122: getSessionById (old format no-id — date-only filename match): Round 123: parseSessionMetadata (CRLF section boundaries — \n\n fails to match \r\n\r\n): Round 124: getAllSessions (invalid date format — strict !== comparison): Round 124: parseSessionMetadata (title regex edge cases — /^#\s+(.+)$/m): Results: Passed: 165, Failed: 0 [SessionManager] Error writing session: ENOENT: no such file or directory, open '/nonexistent/deep/path/session.tmp' ━━━ Running lib/session-aliases.test.js ━━━ === Testing session-aliases.js === loadAliases: setAlias: resolveAlias: listAliases: deleteAlias: renameAlias: updateAliasTitle: resolveSessionAlias: getAliasesForSession: cleanupAliases: listAliases (edge cases): setAlias (edge cases): updateAliasTitle (edge cases): saveAliases (atomic write): cleanupAliases (edge cases): renameAlias (edge cases): getAliasesForSession (edge cases): setAlias (reserved names case sensitivity): listAliases (negative limit): setAlias (undefined title): saveAliases (failure paths, Round 31): renameAlias rollback (Round 33): saveAliases backup/restore (Round 33): Round 39: atomic overwrite: Round 48: rapid sequential saves: Round 56: Windows platform atomic write path: Round 64: loadAliases version/metadata backfill: Round 67: loadAliases (empty 0-byte file): Round 67: resolveSessionAlias (null/falsy input): Round 67: loadAliases (metadata-only backfill, version present): updateAliasTitle save failure (Round 70): Round 72: deleteAlias (save failure): Round 73: cleanupAliases (save failure): Round 73: setAlias (save failure): Round 84: listAliases (NaN date fallback in sort comparator): Round 86: loadAliases (truthy non-object aliases field): Round 90: saveAliases (backup restore double failure): Round 95: renameAlias (self-rename same name): Round 100: cleanupAliases (callback returns 0 — falsy non-boolean coercion): Round 102: setAlias (title=0 — falsy coercion silently converts to null): Round 103: loadAliases (array aliases — typeof bypass): Round 104: resolveSessionAlias (path-traversal input — returned unchanged): Round 107: setAlias (whitespace-only title — truthy string stored as-is, unlike sessionPath which is trim-checked): Round 111: setAlias (128-char alias — exact boundary of > 128 check): Round 112: resolveAlias (Unicode rejection): Round 114: listAliases (non-string search — number triggers TypeError): Round 115: updateAliasTitle (empty string title — stored null, returned ""): Round 116: loadAliases (extra unknown JSON fields — preserved by loose validation): Round 118: renameAlias (same name — "already exists" because data.aliases[newAlias] is truthy): Round 118: setAlias (reserved names — case-insensitive rejection): Round 119: renameAlias (reserved newAlias name — parallel check to setAlias): Round 120: setAlias (max alias length boundary — 128 ok, 129 rejected): Round 121: setAlias (sessionPath validation — null, empty, whitespace, non-string): Round 122: listAliases (limit edge cases — 0/negative/NaN are falsy, return all): Round 125: loadAliases (proto key in JSON — safe, no prototype pollution): Results: Passed: 105, Failed: 0 [Aliases] Error parsing aliases file: Unexpected token N in JSON at position 0 ━━━ Running lib/project-detect.test.js ━━━ === Testing project-detect.js === Rule Definitions: Empty Directory: Python Detection: TypeScript/JavaScript Detection: Go Detection: Rust Detection: Ruby Detection: PHP Detection: Fullstack Detection: Dependency Readers: Elixir Detection: Edge Cases: === Results: 28 passed, 0 failed === ━━━ Running hooks/hooks.test.js ━━━ === Testing Hook Scripts === session-start.js: session-start.js (edge cases): check-console-log.js: session-end.js: pre-compact.js: suggest-compact.js: evaluate-session.js: post-edit-console-warn.js: post-edit-format.js: pre-bash-dev-server-block.js: post-edit-typecheck.js: session-end.js (extractSessionSummary): hooks.json Validation: plugin.json Validation: evaluate-session.js: suggest-compact.js: check-console-log.js (exact pass-through): post-edit-format.js (security & extension tests): post-edit-typecheck.js (security & extension tests): Shell wrapper portability: Round 23: evaluate-session.js (config & nullish coalescing): Round 23: session-end.js (update existing file path): Round 23: pre-compact.js (glob specificity): Round 23: session-end.js (extractSessionSummary edge cases): Round 24: suggest-compact.js (interval fix & fd fallback): Round 24: post-edit-format.js (edge cases): Round 24: post-edit-typecheck.js (edge cases): Round 24: session-start.js (edge cases): Round 25: post-edit-console-warn.js (pass-through fix): Round 25: check-console-log.js (edge cases): Round 29: post-edit-format.js (cwd and exit): Round 29: post-edit-typecheck.js (exit and pass-through): Round 29: post-edit-console-warn.js (extension and exit): Round 29: check-console-log.js (exclusion patterns and exit): Round 29: run-all.js test runner improvements: Round 32: post-edit-typecheck (special character paths): Round 32: check-console-log (edge cases): Round 32: post-edit-console-warn (additional edge cases): Round 32: session-end.js (empty transcript): Round 38: evaluate-session.js (tilde expansion & missing config): Round 41: pre-compact.js (multiple session files): Round 40: session-end.js (newline collapse): Round 44: session-start.js (empty session file): Round 49: post-edit-typecheck.js (extension edge cases): Round 49: session-end.js (conditional summary sections): Round 50: session-start.js (alias reporting): Round 50: pre-compact.js (parallel execution): Round 50: session-start.js (graceful degradation): Round 53: post-edit-console-warn.js (max matches truncation): Round 53: post-edit-format.js (non-existent file): Round 55: session-start.js (maxAge 7-day boundary): Round 55: session-start.js (newest session selection): Round 55: session-end.js (stdin overflow): Round 56: post-edit-typecheck.js (tsconfig in parent directory): Round 56: suggest-compact.js (counter file as directory — fallback path): Round 59: session-start.js (unreadable session file — readFile returns null): Round 59: check-console-log.js (stdin exceeding 1MB — truncation): Round 59: pre-compact.js (read-only session file — appendFile error): Round 60: session-end.js (replaceInFile returns false — timestamp update warning): Round 60: post-edit-console-warn.js (stdin exceeding 1MB — truncation): Round 60: post-edit-format.js (valid JSON without tool_input key): Round 64: post-edit-typecheck.js (valid JSON without tool_input): Round 66: session-end.js (entry.role user fallback): Round 66: session-end.js (nonexistent transcript path): Round 70: session-end.js (entry.name/entry.input fallback): Round 71: session-start.js (default source — selection prompt): Round 74: session-start.js (main catch — unrecoverable error): Round 75: pre-compact.js (main catch — unrecoverable error): Round 75: session-end.js (main catch — unrecoverable error): Round 76: evaluate-session.js (main catch — unrecoverable error): Round 76: suggest-compact.js (main catch — double-failure): Round 80: session-end.js (entry.message.role user — third OR condition): Round 81: suggest-compact.js (COMPACT_THRESHOLD > 10000): Round 81: session-end.js (user entry with non-string non-array content): Round 82: session-end.js (entry.tool_name without type=tool_use): Round 82: session-end.js (template marker present but regex no-match): Round 87: post-edit-format.js (stdin exceeding 1MB — truncation): Round 87: post-edit-typecheck.js (stdin exceeding 1MB — truncation): Round 89: post-edit-typecheck.js (TypeScript error detection path): Round 89: session-end.js (entry.name + entry.input fallback in extractSessionSummary): Round 90: readStdinJson (timeout fires when stdin stays open): Round 94: session-end.js (tools used without files modified): === Test Results === ━━━ Running hooks/evaluate-session.test.js ━━━ === Testing evaluate-session.js === Threshold boundary (default min=10): Edge cases: Config file parsing: Round 53: CLAUDE_TRANSCRIPT_PATH fallback: Round 65: regex whitespace tolerance around colon: Round 85: config parse error catch block: Round 86: config learned_skills_path override: Results: Passed: 16, Failed: 0 ━━━ Running hooks/suggest-compact.test.js ━━━ === Testing suggest-compact.js === Basic counter functionality: Threshold suggestion: Interval suggestion: Environment variable handling: Corrupted counter file: Session isolation: Exit code: Threshold boundary values: Default session ID fallback (Round 64): Results: Passed: 20, Failed: 0 ━━━ Running integration/hooks.test.js ━━━ === Hook Integration Tests === Hook Input Format Handling: Hook Output Format: Hook Exit Codes: Realistic Scenarios: Session End Transcript Parsing: Error Handling: Round 51: Timeout Enforcement: Round 51: hooks.json Schema Validation: === Test Results === ━━━ Running ci/validators.test.js ━━━ === Testing CI Validators === validate-agents.js: validate-hooks.js: validate-skills.js: validate-commands.js: validate-rules.js: validate-hooks.js (whitespace edge cases): validate-agents.js (whitespace edge cases): validate-commands.js (additional edge cases): validate-hooks.js (schema edge cases): validate-hooks.js (legacy format errors): validate-agents.js (empty directory): validate-commands.js (whitespace edge cases): validate-rules.js (mixed files): validate-hooks.js (Round 27 edge cases): validate-commands.js (Round 27 edge cases): validate-skills.js (mixed dirs): Round 30: validate-commands (skill warnings): Round 30: validate-agents (model validation): Round 32: validate-agents (empty frontmatter): Round 32: validate-rules (non-file entries): Round 32: validate-commands (agent reference with valid workflow): Round 42: validate-agents (case sensitivity): Round 42: validate-commands (missing agents dir): Round 42: validate-hooks (empty matchers array): Round 47: validate-hooks (inline JS escape sequences): Round 47: validate-agents (frontmatter lines without colon): Round 52: validate-commands (inline backtick refs): Round 52: validate-commands (workflow whitespace): Round 52: validate-rules (code-only content): Round 57: validate-skills.js (SKILL.md is a directory — readFileSync error): Round 57: validate-rules.js (broken symlink — statSync catch block): Round 57: validate-commands.js (adjacent code blocks both stripped): Round 58: validate-agents.js (unreadable agent file — readFileSync catch): Round 58: validate-agents.js (frontmatter line with colon at position 0): Round 58: validate-hooks.js (command is a plain object — not string or array): Round 63: validate-hooks.js (object-format matcher missing matcher field): Round 63: validate-commands.js (unreadable command file): Round 63: validate-commands.js (empty commands directory): Round 65: validate-rules.js (empty directory — no .md files): Round 65: validate-skills.js (empty directory — no subdirectories): Round 70: validate-commands.js (would create: skip): Round 72: validate-hooks.js (async and timeout type validation): Round 73: validate-commands.js (unreadable skill entry — statSync catch): Round 76: validate-hooks.js (invalid JSON in hooks.json): Round 78: validate-hooks.js (wrapped hooks format): Round 79: validate-commands.js (warnings count in output): Round 80: validate-hooks.js (legacy array format): Round 82: validate-hooks (Notification and SubagentStop event types): Round 83: validate-agents (whitespace-only frontmatter field value): Round 83: validate-skills (empty SKILL.md file): Results: Passed: 136, Failed: 0 ━━━ Running scripts/claw.test.js ━━━ === Testing claw.js === Storage: Context: Delegation: REPL/Meta: NanoClaw v2: Results: Passed: 19, Failed: 0 ━━━ Running scripts/setup-package-manager.test.js ━━━ === Testing setup-package-manager.js === --help: --detect: --list: --global: --project: positional argument: environment variable: --detect output completeness: --global flag validation (Round 31): --project flag validation (Round 31): --detect marker uniqueness (Round 45): --list output completeness (Round 45): --global success path (Round 62): bare PM name success (Round 62): --detect source label (Round 62): --project success path (Round 68): --list (current) marker (Round 68): Round 74: setGlobal catch (save failure): Round 74: setProject catch (save failure): Results: Passed: 31, Failed: 0 ━━━ Running scripts/skill-create-output.test.js ━━━ === Testing skill-create-output.js === SkillCreateOutput constructor: header(): analysisResults(): patterns(): instincts(): output(): nextSteps(): footer(): progressBar edge cases: empty array edge cases: box() crash prevention: box() alignment: box() content overflow: header() width alignment (Round 34): box() width accuracy (Round 35): analysisResults zero values (Round 54): demo export (Round 68): Round 85: patterns() confidence=0 nullish coalescing: Round 87: analyzePhase() async method: Results: Passed: 36, Failed: 0 ╔══════════════════════════════════════════════════════════╗ |
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: checks are failing. Please fix failures before review.
Description
pnpm dlxFixes #363
Validation:
Type of Change
fix:Bug fixfeat:New featurerefactor:Code refactoringdocs:Documentationtest:Testschore:Maintenance/toolingci:CI/CD changesChecklist
node tests/run-all.js)