435 Commits

  • fix(hooks): scrub secrets and harden hook security (#348)
    * 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(lint): remove unnecessary escape characters in regex patterns
    - doc-file-warning.js: \/ → / inside character classes (4 occurrences)
    - project-detect.js: \[ → [ inside character classes (2 occurrences)
    
    These are pre-existing no-useless-escape errors on upstream main.
  • fix(hooks): exclude .history/ directory from doc file warning
    Incorporates the fix from #316 into the standalone script.
  • fix: resolve CI failures on main — lint, hooks validator, and test alignment
    - 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.
  • feat: automatic project type and framework detection (#293)
    Add SessionStart hook integration that auto-detects project languages
    and frameworks by inspecting marker files and dependency manifests.
    
    Supports 12 languages (Python, TypeScript, Go, Rust, Ruby, Java, C#,
    Swift, Kotlin, Elixir, PHP, JavaScript) and 25+ frameworks (Next.js,
    React, Django, FastAPI, Rails, Laravel, Spring, etc.).
    
    Detection output is injected into Claude's context as JSON, enabling
    context-aware recommendations without loading irrelevant rules.
    
    - New: scripts/lib/project-detect.js (cross-platform detection library)
    - Modified: scripts/hooks/session-start.js (integration)
    - New: tests/lib/project-detect.test.js (28 tests, all passing)
    
    Co-authored-by: Claude <noreply@anthropic.com>
  • fix(session-end): always update session summary content (#317)
    * 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>
  • fix(hooks): extract doc-warning hook to external script to fix CI
    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.
  • Merge pull request #233 from andydiaz122/nano_claw_v1
    LGTM — NanoClaw agent REPL. Safe, uses only local Claude CLI, good input validation, includes tests.
  • Merge pull request #252 from pythonstrup/feat/auto-detect-formatter
    LGTM — Auto-detect formatter hook. Safe, well-structured.
  • fix: address CodeRabbit review — deduplicate prompt, fix skill count
    - 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
  • feat: auto-detect formatter in post-edit hook (Biome/Prettier)
    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
  • feat: add NanoClaw agent REPL — persistent session-aware CLI for ECC
    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
  • fix: use nullish coalescing for confidence default + add 3 tests (round 85)
    Fix confidence=0 showing 80% instead of 0% in patterns() (|| → ??).
    Test evaluate-session.js config parse error catch, getSessionIdShort
    fallback at root CWD, and precise confidence=0 assertion.
  • fix: collapse newlines in user messages to prevent markdown list breaks in session-end
    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.
  • fix: make saveAliases atomic on Unix by skipping unnecessary unlink before rename
    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.
  • fix: correct box() off-by-one width calculation in skill-create-output
    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.
  • fix: header subtitle width mismatch in skill-create-output; add 9 tests (Round 34)
    - Fix subtitle padding 55→59 so line 94 matches 64-char border width
    - Add 4 header width alignment tests (skill-create-output)
    - Add 3 getExecCommand non-string args tests (package-manager)
    - Add 2 detectFromPackageJson non-string type tests (package-manager)
  • fix: reject flags passed as package manager names in setup-package-manager CLI
    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.
  • fix: use local-time Date constructor in session-manager to prevent timezone day shift
    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.
  • fix: add cwd to prettier hook, consistent process.exit(0), and stdout pass-through
    - post-edit-format.js: add cwd based on file directory so npx resolves
      correct local prettier binary
    - post-edit-typecheck.js, post-edit-format.js: replace console.log(data)
      with process.stdout.write(data) to avoid trailing newline corruption
    - Add process.exit(0) to 4 hooks for consistent termination
      (check-console-log, post-edit-console-warn, post-edit-format,
      post-edit-typecheck)
    - run-all.js: switch from execSync to spawnSync so stderr is visible
      on the success path (hook warnings were silently discarded)
    - Add 21 tests: cwd verification, process.exit(0) checks, exact
      stdout pass-through, extension edge cases, exclusion pattern
      matching, threshold boundary values (630 → 651)
  • fix: exact byte pass-through in post-edit-console-warn, add 7 tests
    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.
  • fix: consistent periodic interval spacing in suggest-compact, add 10 tests
    - suggest-compact.js: count % 25 → (count - threshold) % 25 for consistent
      spacing regardless of threshold value
    - Update existing periodic interval test to match corrected behavior
    - 10 new tests: interval fix regression (non-25-divisible threshold, false
      suggestion prevention), corrupted counter file, 1M boundary, malformed
      JSON pass-through, non-TS extension pass-through, empty sessions dir,
      blank template skip
  • fix: nullish coalescing in evaluate-session config, narrow pre-compact glob, add 11 tests
    - evaluate-session.js: || 10 → ?? 10 for min_session_length (0 is valid)
    - pre-compact.js: *.tmp → *-session.tmp to match only session files
    - 11 new tests: config loading (min=0, null, custom path, invalid JSON),
      session-end update path (timestamp, template replace, preserve content),
      pre-compact glob specificity, extractSessionSummary edge cases
  • fix: reject empty/invalid array commands in hooks validator, add 19 tests
    validate-hooks.js: Empty arrays [] and arrays with non-string elements
    (e.g., [123, null]) passed command validation due to JS truthiness of
    empty arrays (![] === false). Added explicit length and element type
    checks.
    
    19 new tests covering: non-array event type values, null/string matcher
    entries, string/number top-level data, empty string/array commands,
    non-string array elements, non-string type field, non-number timeout,
    timeout boundary (0), unwrapped hooks format, legacy format error paths,
    empty agent directory, whitespace-only command files, valid skill refs,
    mixed valid/invalid rules and skills.
  • fix: sanitize getExecCommand args, escape regex in getCommandPattern, clean up readStdinJson timeout, add 10 tests
    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.
  • fix: eliminate command injection in hooks, fix pass-through newline corruption, add 8 tests
    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.
  • fix: clamp getAllSessions pagination params, add cleanupAliases success field, add 10 tests
    - 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
  • fix: reject whitespace-only command/field values in CI validators, add 10 tests
    validate-hooks.js: whitespace-only command strings now fail validation
    validate-agents.js: whitespace-only model/tools values now fail validation
  • fix: clamp progressBar to prevent RangeError on overflow, add 10 tests
    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
  • fix: clamp suggest-compact counter overflow, add 9 boundary tests
    Counter file could contain huge values (e.g. 999999999999) that pass
    Number.isFinite() but cause unbounded growth. Added range clamp to
    reject values outside [1, 1000000].
    
    New tests cover:
    - Counter overflow reset (huge number, negative number)
    - COMPACT_THRESHOLD zero fallback
    - session-end empty sections (no tools/files omits headers)
    - session-end slice boundaries (10 messages, 20 tools, 30 files)
    - post-edit-console-warn 5-match limit
    - post-edit-console-warn ignores console.warn/error/debug
  • fix: greedy regex in validate-commands captures all refs per line, add 18 tests
    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)
  • fix: calendar-accurate date validation in parseSessionFilename, add 22 tests
    - Fix parseSessionFilename to reject impossible dates (Feb 31, Apr 31,
      Feb 29 non-leap) using Date constructor month/day roundtrip check
    - Add 6 session-manager tests for calendar date validation edge cases
    - Add 3 session-manager tests for code blocks/special chars in getSessionStats
    - Add 10 package-manager tests for PM-specific command formats (getRunCommand
      and getExecCommand for pnpm, yarn, bun, npm)
    - Add 3 integration tests for session-end transcript parsing (mixed JSONL
      formats, malformed lines, nested user messages)
  • fix: typecheck hook false positives, add 11 session-manager tests
    - Fix post-edit-typecheck.js error filtering: use relative/absolute path
      matching instead of basename, preventing false positives when multiple
      files share the same name (e.g., src/utils.ts vs tests/utils.ts)
    - Add writeSessionContent tests (create, overwrite, invalid path)
    - Add appendSessionContent test (append to existing file)
    - Add deleteSession tests (delete existing, non-existent)
    - Add sessionExists tests (file, non-existent, directory)
    - Add getSessionStats empty content edge case
    - Add post-edit-typecheck stdout passthrough test
    - Total: 391 → 402 tests, all passing
  • fix: add missing ReplaceInFileOptions to utils.d.ts type declaration
    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.
  • fix: grepFile global regex lastIndex bug, add 12 tests
    Fix grepFile() silently skipping matches when called with /g flag regex.
    The global flag makes .test() stateful, causing alternating match/miss
    on consecutive matching lines. Strip g flag since per-line testing
    doesn't need global state.
    
    Add first-ever tests for evaluate-session.js (5 tests: short session,
    long session, missing transcript, malformed stdin, env var fallback)
    and suggest-compact.js (5 tests: counter increment, threshold trigger,
    periodic suggestions, below-threshold silence, invalid threshold).
  • fix: add missing validation in renameAlias, add 6 tests
    renameAlias was missing length (>128), reserved name, and empty string
    validation that setAlias enforced. This inconsistency allowed renaming
    aliases to reserved names like 'list' or 'delete'.
    
    Also adds tests for:
    - renameAlias empty string, reserved name, and length limit
    - validate-skills whitespace-only SKILL.md rejection
    - validate-rules whitespace-only file and recursive subdirectory scan
  • fix: Windows compatibility for hook scripts (execFileSync + tmux) (#215)
    * fix: Windows compatibility for hook scripts
    
    - post-edit-format.js: add `shell: process.platform === 'win32'` to
      execFileSync options so npx.cmd is resolved via cmd.exe on Windows
    - post-edit-typecheck.js: same fix for tsc invocation via npx
    - hooks.json: skip tmux-dependent hooks on Windows where tmux is
      unavailable (dev-server blocker and long-running command reminder)
    
    On Windows, execFileSync('npx', ...) without shell:true fails with
    ENOENT because Node.js cannot directly execute .cmd files. These
    hooks silently fail on all Windows installations.
    
    The tmux hooks unconditionally block dev server commands (exit 2) or
    warn about tmux on Windows where tmux is not available.
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
    
    * fix: parse Claude Code JSONL transcript format correctly
    
    The session-end hook expected user messages at entry.content, but
    Claude Code's actual JSONL format nests them at entry.message.content.
    This caused all session files to be blank templates (0 user messages
    despite 136+ actual entries).
    
    - Check entry.message?.content in addition to entry.content
    - Extract tool_use blocks from assistant message.content arrays
    
    Verified with Claude Code v2.1.41 JSONL transcripts.
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: ddungan <sckim@mococo.co.kr>
    Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
  • fix: add input validation, date range checks, and security hardening
    - validate-agents.js: reject invalid model names in agent frontmatter
    - package-manager.js: validate script/binary names against shell injection
    - session-manager.js: reject impossible month/day values in filenames
    - utils.js: support options.all for replaceInFile string patterns
    - strategic-compact/SKILL.md: fix hook matcher syntax and script reference
    - install.sh: warn when overwriting existing rule customizations
    - Add 24 new tests covering all validation and edge cases
  • fix: box() off-by-one alignment, add 5 tests for readStdinJson and box alignment
    - skill-create-output.js: fix top border being 1 char narrower than
      middle/bottom lines (width - title - 5 → width - title - 4)
    - Add box alignment regression test verifying all lines have equal width
    - Add 4 readStdinJson tests via subprocess (valid JSON, invalid JSON,
      empty stdin, nested objects) — last untested exported utility function
    - All 338 tests passing