Extract project-root discovery, formatter detection, and binary
resolution into a reusable module. Caches results per-process to
avoid redundant filesystem lookups on every Edit hook invocation.
This is the foundation for eliminating npx overhead in format hooks.
* fix: auto-start development servers in tmux instead of blocking
Replace blocking PreToolUse hook that used process.exit(2) with an auto-transform hook that:
- Detects development server commands
- Wraps them in tmux with directory-based session names
- Runs server detached so Claude Code is not blocked
- Provides confirmation message with log viewing instructions
Benefits:
- Development servers no longer block Claude Code execution
- Each project gets its own tmux session (allows multiple projects)
- Logs remain accessible via 'tmux capture-pane -t <session>'
- Non-blocking: if tmux unavailable, command still runs (graceful fallback)
Implementation:
- Created scripts/hooks/auto-tmux-dev.js with transform logic
- Updated hooks.json to reference the script instead of inline node command
- Applied same fix to cached plugin version (1.4.1) for immediate effect
* fix: resolve PR #344 code review issues in auto-tmux-dev.js
Critical fixes:
- Fix variable scope: declare 'input' before try block, not inside
- Fix shell injection: sanitize sessionName and escape cmd for shell
- Replace unused execFileSync import with spawnSync
Improvements:
- Add real Windows support using cmd /k window launcher
- Add tmux availability check with graceful fallback
- Update header comment to accurately describe platform support
Test coverage:
- Valid JSON input: transforms command for respective platform
- Invalid JSON: passes through raw data unchanged
- Unsupported tools: gracefully falls back to original command
- Shell metacharacters: sanitized in sessionName, escaped in cmd
* fix: correct cmd.exe escape sequence for double quotes on Windows
Use double-quote doubling ('""') instead of backslash-escape ('\\\") for cmd.exe syntax.
Backslash escaping is Unix convention and not recognized by cmd.exe. This fixes quoted
arguments in dev server commands on Windows (e.g., 'npm run dev --filter="my-app"').
* fix(hooks): scrub secrets and harden hook security
- Scrub common secret patterns (api_key, token, password, etc.) from
observation logs before persisting to JSONL (observe.sh)
- Auto-purge observation files older than 30 days (observe.sh)
- Strip embedded credentials from git remote URLs before saving to
projects.json (detect-project.sh)
- Add command prefix allowlist to runCommand — only git, node, npx,
which, where are permitted (utils.js)
- Sanitize CLAUDE_SESSION_ID in temp file paths to prevent path
traversal (suggest-compact.js)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(hooks): address review feedback from CodeRabbit and Cubic
- Reject shell command-chaining operators (;|&`) in runCommand, strip
quoted sections before checking to avoid false positives (utils.js)
- Remove command string from blocked error message to avoid leaking
secrets (utils.js)
- Fix Python regex quoting: switch outer shell string from double to
single quotes so regex compiles correctly (observe.sh)
- Add optional auth scheme match (Bearer, Basic) to secret scrubber
regex (observe.sh)
- Scope auto-purge to current project dir and match only archived
files (observations-*.jsonl), not live queue (observe.sh)
- Add second fallback after session ID sanitization to prevent empty
string (suggest-compact.js)
- Preserve backward compatibility when credential stripping changes
project hash — detect and migrate legacy directories
(detect-project.sh)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(hooks): block $() substitution, fix Bearer redaction, add security tests
- Add $ and \n to blocked shell metacharacters in runCommand to prevent
command substitution via $(cmd) and newline injection (utils.js)
- Make auth scheme group capturing so Bearer/Basic is preserved in
redacted output instead of being silently dropped (observe.sh)
- Add 10 unit tests covering runCommand allowlist blocking (rm, curl,
bash prefixes) and metacharacter rejection (;|&`$ chaining), plus
error message leak prevention (utils.test.js)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(hooks): scrub parse-error fallback, strengthen security tests
Address remaining reviewer feedback from CodeRabbit and Cubic:
- Scrub secrets in observe.sh parse-error fallback path (was writing
raw unsanitized input to observations file)
- Remove redundant re.IGNORECASE flag ((?i) inline flag already set)
- Add inline comment documenting quote-stripping limitation trade-off
- Fix misleading test name for error-output test
- Add 5 new security tests: single-quote passthrough, mixed
quoted+unquoted metacharacters, prefix boundary (no trailing space),
npx acceptance, and newline injection
- Improve existing quoted-metacharacter test to actually exercise
quote-stripping logic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): block $() and backtick inside quotes in runCommand
Shell evaluates $() and backticks inside double quotes, so checking
only the unquoted portion was insufficient. Now $ and ` are rejected
anywhere in the command string, while ; | & remain quote-aware.
Addresses CodeRabbit and Cubic review feedback on PR #348.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Fix MD012 trailing blank lines in commands/projects.md and commands/promote.md
- Fix MD050 strong-style in continuous-learning-v2 (escape __tests__ as inline code)
- Extract doc-file-warning hook to standalone script to fix hooks validator regex parsing
- Update session-end test to match #317 behavior (always update summary content)
- Allow shell script hooks in integration test format validation
All 992 tests passing.
* fix(session-end): always update session summary content
Previously, session-end.js would only write content to session files
on first creation. Subsequent sessions would only update the timestamp,
causing stale content (e.g., old tasks, resolved issues) to persist
indefinitely.
This fix ensures that every session end updates the summary section
with fresh content from the current transcript, keeping cross-session
context accurate and relevant.
Fixes: #187 (partially - addresses stale content issue)
Changes:
- Remove the blank-template-only check
- Replace entire Session Summary section on every session end
- Keep timestamp update separate from content update
* fix(session-end): match both summary headers and prevent duplicate stats
Fixes two issues identified in PR #317 code review:
1. CodeRabbit: Updated regex to match both `## Session Summary` and
`## Current State` headers, ensuring files created from blank template
can be updated with fresh summaries.
2. Cubic: Changed regex lookahead `(?=### Stats|$)` to end-of-string `$`
to prevent duplicate `### Stats` sections. The old pattern stopped before
`### Stats` without consuming it, but buildSummarySection() also emits
a `### Stats` block, causing duplication on each session update.
Changes:
- Regex now: `/## (?:Session Summary|Current State)[\s\S]*?$/`
- Matches both header variants used in blank template and populated sessions
- Matches to end-of-string to cleanly replace entire summary section
---------
Co-authored-by: will <will@192.168.5.31>
The inline JS in the Write PreToolUse hook had a multi-layer escaping
bug: the regex [\\/\\] collapsed to [\/\] after the validator's
unescape chain, producing an invalid regex (Unmatched ')').
Fix: move the doc-file-warning hook to scripts/hooks/pre-write-doc-warn.js,
eliminating the inline escaping problem entirely. All 992 tests now pass.
Closes the 991/992 CI failure on main.
- Swap loadHistory/appendTurn order to prevent user message appearing
twice in the prompt (once in history, once as USER MESSAGE)
- Calculate actual loaded skill count via fs.existsSync instead of
counting requested skill names (banner now reflects reality)
- Add err.stack to test harness error output for better debugging
This script scans the current working directory and generates architectural codemap documentation in the specified output directory. It classifies files into areas such as frontend, backend, database, integrations, and workers, and creates markdown files for each area along with an index.
The post-edit-format hook was hardcoded to use Prettier. Projects using
Biome had their code reformatted with Prettier defaults (e.g. double
quotes overwriting single quotes).
Now the hook walks up from the edited file to find the project root,
then checks for config files:
- biome.json / biome.jsonc → runs Biome
- .prettierrc / prettier.config.* → runs Prettier
- Neither found → skips formatting silently
Implements a barebones agent loop that delegates to `claude -p` with
markdown-as-database session persistence and ECC skill context loading.
Zero external dependencies, ~264 lines of pure Node.js CommonJS.
- scripts/claw.js: core module (storage, context, delegation, REPL)
- commands/claw.md: slash command definition with usage docs
- tests/scripts/claw.test.js: 14 unit tests covering all modules
- package.json: add claw script and files entry
- tests/run-all.js: register claw tests in test manifest
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 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.
When --global or --project was followed by another flag (e.g., --global --project),
the flag was treated as a package manager name. Added pmName.startsWith('-') check
to both handlers. Added 20 tests across 4 test files covering argument validation,
ensureDir error propagation, runCommand stderr handling, and saveAliases failure paths.
new Date('YYYY-MM-DD') creates UTC midnight, which in negative UTC offset
timezones (e.g., Hawaii) causes getDate() to return the previous day.
Replaced with new Date(year, month - 1, day) for correct local-time behavior.
Added 15 tests: session-manager datetime verification and edge cases (7),
package-manager getCommandPattern special characters (4), and
validators model/skill-reference validation (4). Tests: 651 → 666.
Replace console.log(data) with process.stdout.write(data) in both
pass-through paths to prevent appending a trailing newline that
corrupts the hook output. Add 7 tests covering exact byte fidelity,
malformed JSON, missing file_path, non-existent files, exclusion
patterns in check-console-log, non-git repo handling, and empty stdin.
Validate args parameter in getExecCommand() against SAFE_ARGS_REGEX to
prevent command injection when returned string is passed to a shell.
Escape regex metacharacters in getCommandPattern() generic action branch
to prevent malformed patterns and unintended matching. Clean up stdin
listeners in readStdinJson() timeout path to prevent process hanging.
Replace shell: true with npx.cmd on Windows in post-edit-format.js and
post-edit-typecheck.js to prevent command injection via crafted file paths.
Replace console.log(data) with process.stdout.write(data) in
check-console-log.js to avoid appending extra newlines to pass-through data.
- session-manager: clamp offset/limit to safe non-negative integers to
prevent negative offset counting from end and NaN returning empty results
- session-aliases: add success field to cleanupAliases return value for
API contract consistency with setAlias/deleteAlias/renameAlias
progressBar() in skill-create-output.js could crash with RangeError when
percent > 100 because repeat() received a negative count. Fixed by
clamping filled to [0, width].
New tests:
- progressBar edge cases: 0%, 100%, and >100% confidence
- Empty patterns/instincts arrays
- post-edit-format: null tool_input, missing file_path, prettier failure
- setup-package-manager: --detect output completeness, current marker
The command cross-reference regex /^.*`\/(...)`.*$/gm only captured the
LAST command ref per line due to greedy .* consuming earlier refs.
Replaced with line-by-line processing using non-anchored regex to
capture ALL command references.
New tests:
- 4 validate-commands multi-ref-per-line tests (regression)
- 8 evaluate-session threshold boundary tests (new file)
- 6 session-aliases edge case tests (cleanup, rename, path matching)
The replaceInFile function in utils.js accepts an optional `options`
parameter with `{ all?: boolean }` for replacing all occurrences, but
the .d.ts type declaration was missing this parameter entirely.