31 Commits

  • 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: 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: 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: 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: 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: 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: 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: add event type enum to hooks schema and avoid shared RegExp state
    - hooks.schema.json: add enum constraint for hook event types
      (PreToolUse, PostToolUse, PreCompact, SessionStart, SessionEnd,
      Stop, Notification, SubagentStop) — enables IDE autocompletion
      and compile-time validation
    - utils.js countInFile: always create fresh RegExp to avoid shared
      lastIndex state when reusing global regex instances
    - README: update AgentShield stats (751 tests, 73 rules)
  • fix: 3 bugs fixed, stdin encoding hardened, 37 CI validator tests added
    Bug fixes:
    - utils.js: glob-to-regex conversion now escapes all regex special chars
      (+, ^, $, |, (), {}, [], \) before converting * and ? wildcards
    - validate-hooks.js: escape sequence processing order corrected —
      \\\\ now processed before \\n and \\t to prevent double-processing
    - 6 hooks: added process.stdin.setEncoding('utf8') to prevent
      multi-byte UTF-8 character corruption at chunk boundaries
      (check-console-log, post-edit-format, post-edit-typecheck,
      post-edit-console-warn, session-end, evaluate-session)
    
    New tests (37):
    - CI validator test suite (tests/ci/validators.test.js):
      - validate-agents: 9 tests (real project, frontmatter parsing,
        BOM/CRLF, colons in values, missing fields, non-md skip)
      - validate-hooks: 13 tests (real project, invalid JSON, invalid
        event types, missing fields, async/timeout validation, inline JS
        syntax, array commands, legacy format)
      - validate-skills: 6 tests (real project, missing SKILL.md, empty
        files, non-directory entries)
      - validate-commands: 5 tests (real project, empty files, non-md skip)
      - validate-rules: 4 tests (real project, empty files)
    
    Total test count: 228 (up from 191)
  • fix: Windows path support, error handling, and dedup in validators
    - session-manager.js: fix getSessionStats path detection to handle
      Windows paths (C:\...) in addition to Unix paths (/)
    - package-manager.js: add try-catch to setPreferredPackageManager for
      consistent error handling with setProjectPackageManager
    - validate-hooks.js: extract duplicated hook entry validation into
      reusable validateHookEntry() helper
    - Update .d.ts JSDoc for both fixes
  • fix: 6 bugs fixed, 67 tests added for session-manager and session-aliases
    Bug fixes:
    - utils.js: prevent duplicate 'g' flag in countInFile regex construction
    - validate-agents.js: handle CRLF line endings in frontmatter parsing
    - validate-hooks.js: handle \t and \\ escape sequences in inline JS validation
    - session-aliases.js: prevent NaN in date sort when timestamps are missing
    - session-aliases.js: persist rollback on rename failure instead of silent loss
    - session-manager.js: require absolute paths in getSessionStats to prevent
      content strings ending with .tmp from being treated as file paths
    
    New tests (164 total, up from 97):
    - session-manager.test.js: 27 tests covering parseSessionFilename,
      parseSessionMetadata, getSessionStats, CRUD operations, getSessionSize,
      getSessionTitle, edge cases (null input, non-existent files, directories)
    - session-aliases.test.js: 40 tests covering loadAliases (corrupted JSON,
      invalid structure), setAlias (validation, reserved names), resolveAlias,
      listAliases (sort, search, limit), deleteAlias, renameAlias, updateAliasTitle,
      resolveSessionAlias, getAliasesForSession, cleanupAliases, atomic write
    
    Also includes hook-generated improvements:
    - utils.d.ts: document that readStdinJson never rejects
    - session-aliases.d.ts: fix updateAliasTitle type to accept null
    - package-manager.js: add try-catch to setProjectPackageManager writeFile
  • fix: use readFile utility in hooks and add pattern type safety
    - Replace raw fs.readFileSync with readFile() from utils in
      check-console-log.js and post-edit-console-warn.js to eliminate
      TOCTOU race conditions (file deleted between existsSync and read)
    - Remove redundant existsSync in post-edit-format.js (exec already
      handles missing files via its catch block)
    - Resolve path upfront in post-edit-typecheck.js before tsconfig walk
    - Add type guard in getGitModifiedFiles() to skip non-string and
      empty patterns before regex compilation
  • fix: harden utils.js edge cases and add input validation
    - Guard findFiles() against null/undefined dir and pattern parameters
      (previously crashed with TypeError on .replace() or fs.existsSync())
    - Wrap countInFile() and grepFile() regex construction in try-catch to
      handle invalid regex strings like '(unclosed' (previously crashed with
      SyntaxError: Invalid regular expression)
    - Add try-catch to replaceInFile() with descriptive error logging
    - Add 1MB size limit to readStdinJson() matching the PostToolUse hooks
      (previously had unbounded stdin accumulation)
    - Improve ensureDir() error message to include the directory path
    - Add 128-char length limit to setAlias() to prevent oversized alias
      names from inflating the JSON store
    - Update utils.d.ts with new maxSize option on ReadStdinJsonOptions
  • fix: renameAlias data corruption, empty sessionId match, NaN threshold
    - Fix renameAlias() leaving orphaned newAlias key on save failure,
      causing in-memory data corruption with both old and new keys present
    - Add sessionPath validation to setAlias() to reject empty/null paths
    - Guard getSessionById() against empty string matching all sessions
      (startsWith('') is always true in JavaScript)
    - Fix suggest-compact.js NaN comparison when COMPACT_THRESHOLD env var
      is set to a non-numeric value — falls back to 50 instead of silently
      disabling the threshold check
    - Sync suggest-compact.js to .cursor/ copy
  • feat: add TypeScript declaration files for all core libraries
    Add .d.ts type definitions for all four library modules:
    - utils.d.ts: Platform detection, file ops, hook I/O, git helpers
    - package-manager.d.ts: PM detection with PackageManagerName union type,
      DetectionSource union, and typed config interfaces
    - session-manager.d.ts: Session CRUD with Session, SessionMetadata,
      SessionStats, and SessionListResult interfaces
    - session-aliases.d.ts: Alias management with typed result interfaces
      for set, delete, rename, and cleanup operations
    
    These provide IDE autocomplete and type-checking for TypeScript
    consumers of the npm package without converting the source to TS.
  • fix: remove dead export, harden session-aliases, sync .cursor scripts
    - Remove duplicate getAliasesPath() from utils.js (only used in
      session-aliases.js which has its own copy)
    - session-aliases.js: validate cleanupAliases param is a function,
      check saveAliases return value, guard resolveAlias against empty input
    - Sync .cursor/skills/strategic-compact/suggest-compact.sh with the
      fixed main version (CLAUDE_SESSION_ID instead of $$)
  • fix: harden error handling, fix TOCTOU races, and improve test accuracy
    Core library fixes:
    - session-manager.js: wrap all statSync calls in try-catch to prevent
      TOCTOU crashes when files are deleted between readdir and stat
    - session-manager.js: use birthtime||ctime fallback for Linux compat
    - session-manager.js: remove redundant existsSync before readFile
    - utils.js: fix findFiles TOCTOU race on statSync inside readdir loop
    
    Hook improvements:
    - Add 1MB stdin buffer limits to all PostToolUse hooks to prevent
      unbounded memory growth from large payloads
    - suggest-compact.js: use fd-based atomic read+write for counter file
      to reduce race window between concurrent invocations
    - session-end.js: log when transcript file is missing, check
      replaceInFile return value for failed timestamp updates
    - start-observer.sh: log claude CLI failures instead of silently
      swallowing them, check observations file exists before analysis
    
    Test fixes:
    - Fix blocking hook tests to send matching input (dev server command)
      and expect correct exit code 2 instead of 1
  • fix: improve error handling, fix bugs, and optimize core libraries
    utils.js:
    - Fix countInFile: enforce global flag on regex to prevent silent
      under-counting (match() without /g returns only first match)
    - Add 5s timeout to readStdinJson to prevent hooks hanging forever
    - Handle EEXIST race condition in ensureDir
    - Pre-compile regex patterns in getGitModifiedFiles to avoid N*M
      compilations and catch invalid patterns before filtering
    - Add JSDoc documentation to all improved functions
    
    session-manager.js:
    - Fix getSessionById triple file read: pass pre-read content to
      getSessionStats instead of re-reading from disk
    - Allow getSessionStats to accept content string directly
    
    session-aliases.js:
    - Wrap temp file cleanup in try/catch to prevent cascading errors
    
    check-console-log.js:
    - Refactor to use shared utils (isGitRepo, getGitModifiedFiles, log)
      instead of raw execSync calls
    - Add exclusion patterns for test files, config files, and scripts/
      where console.log is intentional
    
    session-end.js:
    - Log count of skipped unparseable transcript lines for diagnostics
    
    suggest-compact.js:
    - Guard against NaN from corrupted counter files
    
    package-manager.js:
    - Remove dead fallbackOrder parameter (unused after #162 fix)
  • fix: eliminate child process spawns during session startup (#162)
    getAvailablePackageManagers() spawned where.exe/which for each package
    manager (npm, pnpm, yarn, bun). During SessionStart hooks, these 4+
    child processes combined with Bun's own initialization exceeded the
    spawn limit on Windows, freezing the terminal.
    
    Fix: Remove process spawning from the hot path. Steps 1-5 of detection
    (env var, project config, package.json, lock file, global config) already
    cover all file-based detection. If none match, default to npm without
    spawning. Also fix getSelectionPrompt() to list supported PMs without
    checking availability.
  • fix: sync plugin.json version with latest tag (#171)
    Sync plugin.json version to 1.4.1, add CI check to verify versions match on release, and add release.sh script. Fixes #170.
  • feat: add /sessions command for session history management (#142)
    Add a new /sessions command to manage Claude Code session history with
    alias support for quick access to previous sessions.
    
    Features:
    - List sessions with pagination and filtering (by date, ID)
    - Load and view session content and metadata
    - Create memorable aliases for sessions
    - Remove aliases
    - Display session statistics (lines, items, size)
    - List all aliases
    
    New libraries:
    - scripts/lib/session-manager.js - Core session CRUD operations
    - scripts/lib/session-aliases.js - Alias management with atomic saves
    
    New command:
    - commands/sessions.md - Complete command with embedded scripts
    
    Modified:
    - scripts/lib/utils.js - Add getAliasesPath() export
    - scripts/hooks/session-start.js - Show available aliases on session start
    
    Session format support:
    - Old: YYYY-MM-DD-session.tmp
    - New: YYYY-MM-DD-<short-id>-session.tmp
    
    Aliases are stored in ~/.claude/session-aliases.json with Windows-
    compatible atomic writes and backup support.
    
    Co-authored-by: 王志坚 <wangzhijian10@bgyfw.com>
    Co-authored-by: Claude <noreply@anthropic.com>
  • fix: resolve ESLint errors and update tests for project-name fallback
    - Fix 16 ESLint no-unused-vars errors across hook scripts and tests
    - Add eslint-disable comment for intentional control-regex in ANSI stripper
    - Update session file test to use getSessionIdShort() instead of hardcoded 'default'
      (reflects PR #110's project-name fallback behavior)
    - Add marketing/ to .gitignore (local drafts)
    - Add skill-create-output.js (terminal output formatter)
    
    All 69 tests now pass. CI should be green.
  • feat: use project name as session filename fallback
    Fixes #99. Falls back to git repo name or directory name when CLAUDE_SESSION_ID is unavailable.
  • feat: v1.1.0 release - session ID tracking, async hooks, new skills
    - Add session ID to session filenames (Issue #62)
    - Add getSessionIdShort() helper for unique per-session tracking
    - Add async hooks documentation with example
    - Create iterative-retrieval skill for progressive context refinement
    - Add continuous-learning-v2 skill with instinct-based learning
    - Add ecc.tools ecosystem section to README
    - Update skills list in README
    
    All 67 tests passing.
  • fix: security and documentation fixes
    - fix(utils.js): prevent command injection in commandExists() by using
      spawnSync instead of execSync with string interpolation, and validate
      input to only allow alphanumeric chars, dash, underscore, dot (#42)
    
    - fix(utils.js): add security documentation to runCommand() warning
      against passing user-controlled input
    
    - fix(setup-package-manager.js): replace <script> and <binary> with
      [script-name] and [binary-name] to avoid XSS scanner false positives (#43)
    
    - fix(doc-updater.md): replace invalid 'npx ts-morph' with correct
      'npx tsx scripts/codemaps/generate.ts' since ts-morph is a library,
      not a CLI tool (#51)
    
    Fixes #42, #43, #51
  • feat: cross-platform support with Node.js scripts
    - Rewrite all bash hooks to Node.js for Windows/macOS/Linux compatibility
    - Add package manager auto-detection (npm, pnpm, yarn, bun)
    - Add scripts/lib/ with cross-platform utilities
    - Add /setup-pm command for package manager configuration
    - Add comprehensive test suite (62 tests)
    
    Co-authored-by: zerx-lab