2 Commits

  • feat: worktree-lifecycle service (deterministic conflict prediction + safe GC) (#2164)
    * feat: add worktree-lifecycle service (ecc.worktree-lifecycle.v1)
    
    The "unowned moat" from the orchestrator landscape research: no existing
    tool ships deterministic merge-conflict prediction or a safe worktree GC.
    
    - scripts/lib/worktree-lifecycle/git.js: injectable, hermetic git layer.
      Predicts merge conflicts WITHOUT touching the working tree via
      `git merge-tree`. Strips inherited GIT_* env so it is safe inside hooks.
    - scripts/lib/worktree-lifecycle/lifecycle.js: deterministic state machine
      (main/dirty/conflict/merge-ready/merged/stale/idle) + planCleanup that
      buckets worktrees into remove / salvage / keep. Only fully-merged trees
      are auto-removable; stale (unmerged+inactive) => salvage, never deleted.
    - scripts/worktree-lifecycle.js: CLI (--json/--conflicts/--stale/
      --cleanup-plan/--base/--stale-days/--repo).
    - tests/lib/worktree-lifecycle.test.js: 11 tests (fake-git + real-git).
    
    Safety model mirrors the reference-arch salvage rule, validated by the
    2026-06-05 MacBook->Mac Mini consolidation. Tests: 11/0.
    
    * fix: hermetic git env in session adapters + mcp-inventory lint
    
    - session adapters (codex-worktree, opencode): resolveGitBranch stripped
      no git env, so the "outside a repo" path returned the host branch when
      run inside a git hook (GIT_DIR set). Strip GIT_* before rev-parse.
    - mcp-inventory: fix eslint no-unused-vars (signatures) and a stale
      eslint-disable directive in the merged code.
    
    * test: run each test with inherited git env stripped (hermetic runner)
    
    When the suite runs inside a git hook (pre-push), git sets GIT_DIR/
    GIT_WORK_TREE, which hijack 'git -C <dir>' calls in tests that exercise
    real git, making them operate on the host repo. Strip GIT_* before
    spawning each test so the suite is isolated from ambient git state.
    
    ---------
    
    Co-authored-by: ECC Test <ecc@example.test>
  • feat: MCP inventory (ecc.mcp.v1) — unified cross-harness MCP config view (#2146)
    * feat: add MCP inventory (ecc.mcp.v1) across harnesses
    
    Read-only MCP-gateway groundwork: discover MCP server configs across
    every installed harness, normalize to a canonical ecc.mcp.v1 inventory,
    redact secrets, and report which servers are configured in 2+ harnesses
    (the configure-N-times pain). The read+dedup side of a unified gateway,
    mirroring how the session-adapter layer started read-only.
    
    Readers (per-harness config formats):
    - claude-code: ~/.claude.json mcpServers + project .mcp.json
    - codex: ~/.codex/config.toml [mcp_servers.*] TOML via @iarna/toml
    - opencode: ~/.config/opencode/opencode.json mcp block (command ARRAY)
    
    canonical-mcp.js:
    - normalize transport labels (local=>stdio, remote=>http) to stdio/http/sse
    - merge servers by name across harnesses; flag DRIFT when signatures differ
    - fragmentation report + aggregates
    - SECRET REDACTION: env values stripped to key names; secrets in args
      (--modelApiKey sk-ant-...), inline --flag=secret, and URL userinfo/token
      query params all redacted before storage AND before the dedup signature.
    
    scripts/mcp-inventory.js: CLI (--json, --fragmented, --help).
    tests/lib/mcp-inventory.test.js: 12 tests incl. a regression for the
    real arg-carried-secret leak found while smoke-testing on live configs.
    
    Tests: 12/0. Real-data smoke: 33 servers across 3 harnesses, 21
    configured in 2+ harnesses (7 drift); secret-leak audit clean.
    
    * test: cover reader error paths, collect skip-logic, and CLI main() for mcp-inventory
    
    Lift global branch coverage past the 80% gate (was 79.86%). Adds 6
    tests exercising: missing-file/malformed-JSON/missing-block reader
    fallbacks, codex no-parser path, collect skipping non-function readers
    and swallowing reader errors, CLI usage()/main() help+json+human paths,
    and formatHumanReport no-fragmentation + fragmented-only branches.
    
    Also scrub a real API-key fragment that had leaked into a test fixture;
    all secret-like fixtures are now obviously-fake FAKE... tokens.
    
    mcp-inventory.js branch 30%->93%, collect.js ->100%. Global branch 80.33%.