14 Commits

  • fix: context-size /compact trigger, Codex marketplace plugin path, live README badges (#2237)
    - suggest-compact hook now reads the latest usage record from the session
      transcript and suggests /compact at a window-scaled token threshold
      (160k/200k window, 250k/1M window; COMPACT_CONTEXT_THRESHOLD and
      COMPACT_CONTEXT_INTERVAL overridable), re-firing per 60k-token growth
      bucket; tool-call count stays as the secondary signal (#2155)
    - Codex repo marketplace now points at ./plugins/ecc instead of ./ — Codex
      never discovers plugins whose local marketplace source.path is the
      marketplace root (verified on Codex CLI 0.137.0); plugins/ecc is a thin
      folder referencing root skills/.mcp.json per maintainer direction on
      #2097; docs flag plugin mode as experimental with the upstream blocker
      openai/codex#26037 linked (#2128)
    - README badges for installs/stars/forks now use shields endpoint badges
      backed by api.ecc.tools (live install count 3,712 vs the stale static
      150), which also eliminates shields' 'Unable to select next GitHub token
      from pool' render in the stars badge
    
    Closes #2155
    Closes #2128
  • fix(suggest-compact): clean up old counter temp files (#2159)
    * fix(suggest-compact): clean up old counter temp files
    
    claude-tool-count-<sessionId> files were written into the OS temp dir
    on every hook run and never removed, accumulating one orphan per
    session indefinitely.
    
    Sweep stale counter files at the top of main() before opening the
    active counter. Retention is env-tunable via COMPACT_STATE_TTL_DAYS
    (default 14 days); invalid values fall back to the default. The
    active session's counter file is preserved unconditionally even if
    its mtime is past the cutoff. Failures during the sweep are swallowed
    to preserve the always-exit-0 hook contract.
    
    Adds 7 regression tests covering the sweep, env-var validation, and
    the always-exit-0 invariant under a populated temp dir.
    
    Fixes #2156
    
    * fix(suggest-compact): preserve counter files at the TTL cutoff boundary
    
    The cleanup sweep used `mtimeMs > cutoffMs` to short-circuit, which
    matched files whose mtime sits exactly on the cutoff boundary and
    deleted them. The cleanupOldCounters docstring promises only files
    *older than* retentionDays are removed; a file at age == retentionDays
    is not older than retentionDays, so it must survive.
    
    Switch the comparison to `>=` so only strictly older files fall
    through to deletion. Add a regression test that pins boundary-aged
    files (mtimeMs sitting just past the projected cutoff) are preserved.
    
    Refs #2156
  • fix: integrate recent hook and docs PRs (#1905)
    Integrates useful changes from #1882, #1884, #1889, #1893, #1898, #1899, and #1903:
    - fix rule install docs to preserve language directories
    - correct Ruby security command examples
    - harden dev-server hook command-substitution parsing
    - add Prisma patterns skill and catalog/package surfaces
    - allow first-time protected config creation while blocking existing configs
    - read cost metrics from Stop hook transcripts
    - emit suggest-compact additionalContext on stdout
    
    Co-authored-by: Jamkris <dltmdgus1412@gmail.com>
    Co-authored-by: Levi-Evan <levishantz@gmail.com>
    Co-authored-by: gaurav0107 <gauravdubey0107@gmail.com>
    Co-authored-by: richm-spp <richard.millar@salarypackagingplus.com.au>
    Co-authored-by: zomia <zomians@outlook.jp>
    Co-authored-by: donghyeun02 <donghyeun02@gmail.com>
  • fix: port hook session and dashboard safety fixes
    Ports suggest-compact session_id isolation and dashboard terminal/document launch safety onto current main.
  • 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: 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: 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: broken cross-references, version sync, and enhanced command validator
    - Fix /build-and-fix → /build-fix in tdd.md, plan.md (+ cursor, zh-CN)
    - Fix non-existent explorer agent → planner in orchestrate.md (+ cursor, zh-CN, zh-TW)
    - Fix /python-test → /tdd in python-review.md (+ cursor, zh-CN)
    - Sync package.json version from 1.0.0 to 1.4.1 to match plugin.json
    - Enhance validate-commands.js with cross-reference checking:
      command refs, agent path refs, skill dir refs, workflow diagrams
    - Strip fenced code blocks before scanning to avoid false positives
    - Skip hypothetical "Creates:" lines in evolve.md examples
    - Add 46 new tests (suggest-compact, session-manager, utils, hooks)
  • fix: migrate hooks to stdin JSON input, fix duplicate main() calls, add threshold validation
    - Migrate session-end.js and evaluate-session.js from CLAUDE_TRANSCRIPT_PATH
      env var to stdin JSON transcript_path (correct hook input mechanism)
    - Remove duplicate main() calls that ran before stdin was read, causing
      session files to be created with empty data
    - Add range validation (1-10000) on COMPACT_THRESHOLD in suggest-compact.js
      to prevent negative or absurdly large thresholds
    - Add integration/hooks.test.js to tests/run-all.js so CI runs all 97 tests
    - Update evaluate-session.sh to parse transcript_path from stdin JSON
    - Update hooks.test.js to pass transcript_path via stdin instead of env var
    - Sync .cursor/ copies
  • 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
  • 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: 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: 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