25 Commits

  • fix: port continuous-learning observer fixes
    Ports continuous-learning observer signal, storage, remote normalization, and v1 deprecation fixes onto current main.
  • fix(observe): skip Windows AppInstallerPythonRedirector.exe in resolve_python_cmd (#1511)
    On Windows 10/11 without Python installed from the Microsoft Store, the
    "App Execution Alias" stubs at %LOCALAPPDATA%\Microsoft\WindowsApps\python.exe
    and python3.exe are symlinks to AppInstallerPythonRedirector.exe. These
    stubs neither launch Python nor honor `-c`; calls print a bare "Python "
    line and exit, silently breaking every JSON-parsing step in observe.sh.
    
    Net effect: observations.jsonl is never written, CLV2 appears installed
    correctly, and the only residual artifact is `.last-purge`.
    
    This commit:
      1. Adds `_is_windows_app_installer_stub` helper that detects the stub
         via `command -v` output and optional `readlink -f` resolution.
      2. Teaches `resolve_python_cmd` to skip stub candidates and fall
         through to the next real interpreter (typically C:\...\Python3xx\python.exe).
      3. Exports the stub-aware CLV2_PYTHON_CMD before sourcing
         detect-project.sh, which already honors an already-set value,
         so the shared helper does not re-resolve and re-select the stub.
    
    POSIX-compatible. No behavior change on macOS / Linux / WSL where no
    such stub exists.
    
    Refs: observations.jsonl empty on Windows Claude Desktop users.
  • fix(observe): allow sdk-ts entrypoint in observation hook (#614)
    Clean surgical fix allowing sdk-ts entrypoint in observe hook for Agent SDK sessions. Has APPROVED review.
  • fix: observer memory explosion with throttling, re-entrancy guard, and tail sampling (#536)
    Three fixes for the positive feedback loop causing runaway memory usage:
    
    1. SIGUSR1 throttling in observe.sh: Signal observer only every 20
       observations (configurable via ECC_OBSERVER_SIGNAL_EVERY_N) instead
       of on every tool call. Uses a counter file to track invocations.
    
    2. Re-entrancy guard in observer-loop.sh on_usr1(): ANALYZING flag
       prevents parallel Claude analysis processes from spawning when
       signals arrive while analysis is already running.
    
    3. Cooldown + tail-based sampling in observer-loop.sh:
       - 60s cooldown between analyses (ECC_OBSERVER_ANALYSIS_COOLDOWN)
       - Only last 500 lines sent to LLM (ECC_OBSERVER_MAX_ANALYSIS_LINES)
         instead of the entire observations file
    
    Closes #521
  • fix(continuous-learning-v2): add lazy-start observer logic (#508)
    * feat(continuous-learning-v2): add lazy-start observer logic
    
    Auto-starts observer when observer.enabled: true in config and no .observer.pid exists.
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
    
    * fix(continuous-learning-v2): address PR review concerns
    
    - Use flock for atomic check-then-act to prevent race conditions
    - Check both project-scoped AND global PID files before starting
    - Support CLV2_CONFIG override for config file path
    - Check disabled file in lazy-start logic
    - Use double-check pattern after acquiring lock
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
    
    * fix(observe.sh): address PR review comments
    
    - Add stale PID cleanup via _CHECK_OBSERVER_RUNNING function
    - Add macOS fallback using lockfile when flock unavailable
    - Fix CLV2_CONFIG override: use EFFECTIVE_CONFIG for both check and read
    - Use proper Python context manager (with open() as f)
    - Deduplicate signaled PIDs to avoid duplicate USR1 signals
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
    
    * fix(observe.sh): wrap macOS lockfile fallback in subshell with trap
    
    - Wrap lockfile block in subshell so exit 0 only terminates that block
    - Add trap for EXIT to clean up lock file on script interruption
    - Add -l 30 (30 second expiry) to prevent permanent lock file stuck
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
    
    * fix(observe.sh): address remaining PR review comments
    
    - Validate PID is a positive integer before kill calls to prevent
      signaling invalid targets (e.g. -1 could signal all processes)
    - Pass config path via env var instead of interpolating shell variable
      into Python -c string to prevent injection/breakage on special paths
    - Check CLV2_CONFIG-derived directory for disabled file so disable
      guard respects the same config source as lazy-start
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
  • fix: read tool_response field in observe.sh (#377)
    Claude Code sends tool output as `tool_response` in PostToolUse hook
    payloads, but observe.sh only checked for `tool_output` and `output`.
    This caused all observations to have empty output fields, making the
    observer pipeline blind to tool results.
    
    Adds `tool_response` as the primary field to check, with backward-
    compatible fallback to the existing `tool_output` and `output` fields.
  • 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>
  • feat: project-scoped instinct isolation
    * feat: add project-scoped instinct isolation
    
    * fix(continuous-learning-v2): harden instinct loading and promotion safety; sync v2.1 command docs
    
    * fix(ci): make copilot-setup-steps a valid GitHub Actions workflow
    
    * fix(hooks): stabilize docs warning inline JS regex parsing
  • address review: remove .cursor/ duplicate, use is not None checks
    Changes based on CodeRabbit review feedback:
    
    1. Remove entire .cursor/ directory — it was an identical copy of the
       main skills/commands/agents/rules, causing maintenance drift.
       Users of Cursor can reference the canonical files directly.
    
    2. Use explicit `is not None` checks instead of truthiness for
       parsed['input'] and parsed['output']. Empty strings or empty
       dicts are valid values that should be preserved.
  • fix: use CLI argument for hook phase detection in observe.sh
    The observe.sh script receives "pre" or "post" as $1 from the hook
    config, but the Python code was looking for a "hook_type" field in
    the stdin JSON. Claude Code does NOT include "hook_type" in the
    JSON payload passed to hooks, so it always defaulted to "unknown",
    causing all observations to be recorded as "tool_complete" —
    PreToolUse events were never distinguished from PostToolUse.
    
    Fix: capture $1 as HOOK_PHASE and pass it to Python via env var.
    This also fixes TIMESTAMP export in the .cursor copy where inline
    `VAR=val cmd` syntax didn't propagate to the python subprocess.
  • fix: correct TIMESTAMP environment variable syntax in observe.sh
    The inline environment variable syntax `TIMESTAMP="$timestamp" echo ...` 
    does not work correctly because:
    1. The pipe creates a subshell that doesn't inherit the variable
    2. The environment variable is set for echo, not for the piped python
    
    Fixed by using `export` and separating the commands:
    - export TIMESTAMP="$timestamp"
    - echo "$INPUT_JSON" | python3 -c "..."
    
    This ensures the TIMESTAMP variable is available to the python subprocess.
    
    Fixes #227
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
  • fix: harden CI validators, shell scripts, and expand test suite
    - Add try-catch around readFileSync in validate-agents, validate-commands,
      validate-skills to handle TOCTOU races and file read errors
    - Add validate-hooks.js and all test suites to package.json test script
      (was only running 4/5 validators and 0/4 test files)
    - Fix shell variable injection in observe.sh: use os.environ instead of
      interpolating $timestamp/$OBSERVATIONS_FILE into Python string literals
    - Fix $? always being 0 in start-observer.sh: capture exit code before
      conditional since `if !` inverts the status
    - Add OLD_VERSION validation in release.sh and use pipe delimiter in sed
      to avoid issues with slash-containing values
    - Add jq dependency check in evaluate-session.sh before parsing config
    - Sync .cursor/ copies of all modified shell scripts
  • fix: resolve multiple reported issues (#205, #182, #188, #172, #173) (#207)
    * fix: resolve multiple reported issues (#205, #182, #188, #172, #173)
    
    - fix(observe.sh): replace triple-quote JSON parsing with stdin pipe to
      prevent ~49% parse failures on payloads with quotes/backslashes/unicode
    - fix(hooks.json): correct matcher syntax to use simple tool name regexes
      instead of unsupported logical expressions; move command/path filtering
      into hook scripts; use exit code 2 for blocking hooks
    - fix(skills): quote YAML descriptions containing colons in 3 skill files
      and add missing frontmatter to 2 skill files for Codex CLI compatibility
    - feat(rules): add paths: filters to all 15 language-specific rule files
      so they only load when working on matching file types
    - fix(agents): align model fields with CONTRIBUTING.md recommendations
      (opus for planner/architect, sonnet for reviewers/workers, haiku for
      doc-updater)
    
    * ci: use AgentShield GitHub Action instead of npx
    
    Switch from npx ecc-agentshield to uses: affaan-m/agentshield@v1
    for proper GitHub Action demo and marketplace visibility.
  • fix: use CLAUDE_PLUGIN_ROOT for continuous-learning-v2 paths
    Fixes #113
    
    The instinct commands referenced hardcoded paths that only work with
    manual installation (~/.claude/skills/...). When installed as a plugin,
    files are at ~/.claude/plugins/cache/.../skills/...
    
    Changes:
    - Updated instinct-status, instinct-import, evolve commands to use
      ${CLAUDE_PLUGIN_ROOT} with fallback to manual path
    - Updated observe.sh hook documentation with both install methods
    - Updated SKILL.md with plugin vs manual installation instructions
    - Removed duplicate commands from skills/continuous-learning-v2/commands/
      (already exist in commands/)
    
    Users should use ${CLAUDE_PLUGIN_ROOT} in their hooks config when
    installed as a plugin.
  • 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.