Round 80: Three previously untested conditional branches:
- session-end.js: entry.message?.role === 'user' third OR condition
(fires when type is not 'user' but message.role is)
- package-manager.js: getExecCommand with truthy non-string args
(typeof check short-circuits, value still appended via ternary)
- validate-hooks.js: legacy array format parsing path (lines 115-135)
with 'Hook N' error labels instead of 'EventType[N]'
- evaluate-session: main().catch when HOME is non-directory (ENOTDIR)
- suggest-compact: main().catch double-failure when TMPDIR is non-directory
- validate-hooks: invalid JSON in hooks.json triggers error exit
Total tests: 831 → 834
- session-manager: deleteSession returns false when dir is read-only (EACCES)
- pre-compact: main().catch handler when HOME is non-directory (ENOTDIR)
- session-end: main().catch handler when HOME is non-directory (ENOTDIR)
Total tests: 828 → 831
- setup-package-manager: setGlobal catch when HOME is non-directory (ENOTDIR)
- setup-package-manager: setProject catch when CWD is read-only (EACCES)
- session-start: main().catch handler when ensureDir throws (exit 0, don't block)
Total tests: 825 → 828
Round 73: Add 3 tests for genuine untested code paths:
- session-aliases cleanupAliases returns failure when save blocked after removing aliases
- session-aliases setAlias returns failure when save blocked on new alias creation
- validate-commands silently skips broken symlinks in skill directory scanning
- session-end.js: entry.name/entry.input fallback in direct tool_use entries
- validate-commands.js: "would create:" regex alternation skip line
- session-aliases.js: updateAliasTitle save failure with read-only dir
- session-manager.js: getSessionById with date-only string exercises the
noIdMatch path for old-format sessions (2026-02-10 → 2026-02-10-session.tmp)
- session-end.js: extract user messages from role-only JSONL format
({"role":"user",...} without type field) exercises line 48 fallback
- session-end.js: nonexistent transcript_path triggers "Transcript not found"
log path (lines 153-155), creates session with blank template
Total: 803 tests, all passing
- evaluate-session.js: verify regex whitespace tolerance around colon
matches "type" : "user" (with spaces), not just compact JSON
- validate-rules.js: empty directory with no .md files yields Validated 0
- validate-skills.js: directory with only files, no subdirectories yields
Validated 0
Total: 800 tests, all passing
- suggest-compact: 'default' session ID fallback when CLAUDE_SESSION_ID empty
- session-aliases: loadAliases backfills missing version and metadata fields
- post-edit-typecheck: valid JSON without tool_input passes through unchanged
Total: 797 tests, all passing
- setup-package-manager.test.js: --global npm writes config and exits 0
- setup-package-manager.test.js: bare PM name sets global preference
- setup-package-manager.test.js: --detect with env var shows source 'environment'
791 tests total, all passing.
- utils.test.js: replaceInFile returns false on read-only file (catch block)
- session-manager.test.js: getAllSessions returns empty when sessions dir missing
- package-manager.test.js: getPackageManager falls through corrupted global config to npm default
788 tests total, all passing.
- getAllSessions search matches only shortId, not title/content
- getSessionPath returns absolute path with correct directory structure
- analysisResults handles zero values for all data fields without crash
Round 53: Adds 3 hook tests — validates evaluate-session.js
falls back to CLAUDE_TRANSCRIPT_PATH env var when stdin JSON
is invalid, post-edit-console-warn.js truncates output to max
5 matches, and post-edit-format.js passes through data when
the target .tsx file doesn't exist.
Round 46: verify getSessionStats recognises C:/ and D:\ as file
paths but not bare C: without slash; verify parseSessionMetadata
only matches lowercase [x] checkboxes (not uppercase [X]).
User messages containing newline characters were being added as-is to
markdown list items in buildSummarySection(), breaking the list format.
Now newlines are replaced with spaces before backtick escaping.
On Unix/macOS, rename(2) atomically replaces the destination file.
The previous code ran unlinkSync before renameSync on all platforms,
creating an unnecessary non-atomic window where a crash could lose
data. Now the delete-before-rename is gated behind process.platform
=== 'win32', where rename cannot overwrite an existing file.
The .cursor copy had diverged from scripts/hooks/suggest-compact.js:
- Fixed interval calculation: count % 25 → (count - threshold) % 25
so suggestions fire relative to the configured threshold
- Added upper bound clamp (<=1000000) to prevent counter corruption
from large values converting to scientific notation strings
- Removed unreliable String(process.ppid) fallback for session ID
The box() helper produced lines that were width+1 characters instead of
the requested width. Adjusted all three formulas (top border, middle
content, bottom border) by -1 each. Added 4 tests verifying box width
accuracy across instincts(), analysisResults(), and nextSteps() output.
Cover createdTime/birthtime fallback in session-manager, readStdinJson
error event settled-flag guard in utils, renameAlias rollback on naming
conflict in session-aliases, and saveAliases backup preservation on
serialization failure. Total: 713 tests.