Commit Graph

209 Commits

  • test: cover error/fallback branches for codex-worktree + opencode adapters
    Lift global branch coverage past the 80% gate (was 79.53%). Adds error
    and fallback path tests: missing-session/unknown-id throws, findRolloutById/
    findSessionInfoById, direct file targets, objective truncation, model
    fallbacks, corrupt-line skip, mtime activity fallback, and the real
    resolveGitBranch path outside a repo.
    
    codex-worktree.js branch 52.8%->78.3%; global branch 80.04%.
  • feat: add opencode session adapter + allow empty intent objective
    Adds the fourth session adapter (after dmux-tmux, claude-history,
    codex-worktree), normalizing OpenCode sessions into ecc.session.v1.
    
    Reads ~/.local/share/opencode/storage: session/<project>/ses_*.json
    for metadata (id, directory, title, version, projectID, time) and
    message/<session>/msg_*.json to extract the model (modelID/providerID
    from the first assistant message). Derives objective from the session
    title, treating the auto-generated "New session - <date>" title as no
    objective. Recency-based active/recorded state.
    
    Schema: relax intent.objective from non-empty to allow empty string
    (ensureStringAllowEmpty). Sessions legitimately have no objective yet
    (fresh/auto-titled), and claude-history already emitted "" via
    metadata.title fallback. This fixes a latent over-strict validation.
    
    - scripts/lib/session-adapters/opencode.js: adapter + storage parser
    - canonical-session.js: normalizeOpencodeSession + ensureStringAllowEmpty
    - registry.js: register adapter + opencode target type
    - tests/lib/session-adapters-opencode.test.js: 5 tests
    
    Tests: opencode 5/0, codex 4/0, session-adapters 14/0,
    control-pane-state 10/0, session-inspect 8/0, control-pane 12/0.
    Smoke-tested on a real OpenCode session (140 messages, gpt-5.3-codex).
  • feat: add codex-worktree session adapter
    Adds the third session adapter (after dmux-tmux and claude-history),
    normalizing Codex rollout sessions into the harness-neutral
    ecc.session.v1 snapshot. Reads ~/.codex/sessions rollout JSONL,
    derives objective (skipping the AGENTS.md preamble + leading message
    UUID), model, originator, worktree cwd, and best-effort git branch.
    
    This is step 1 of ECC-2.0-SESSION-ADAPTER-DISCOVERY (move the
    abstraction beyond tmux + Claude-history) and supports the
    wrap/adapt control-pane strategy: ECC reads sessions from any
    harness rather than owning one UX.
    
    - scripts/lib/session-adapters/codex-worktree.js: adapter + rollout parser
    - canonical-session.js: normalizeCodexWorktreeSession
    - registry.js: register adapter, codex/codex-worktree target types
    - tests/lib/session-adapters-codex.test.js: 4 tests (unit + registry routing)
  • feat: add dynamic workflow team orchestration surface
    Adds dynamic workflow/team orchestration skills, the content pack, and control-pane work-item/Kanban state DB support. Includes reviewer hardening for state-db CLI validation, optional state DB failure handling, and mergeStateStatus projection.
  • feat: add ECC2 local control pane (#2131)
    * feat: add ECC2 local control pane
    
    * fix: refresh control pane package locks
    
    * test: harden control pane coverage
    
    * test: allow portable control pane shutdown
    
    * test: retry local control pane fetches
    
    * fix: harden control pane error handling
    
    * fix: wrap control pane metadata
  • docs(i18n): add German localization scout (#2029)
    Adds de-DE docs, installer wiring, and locale tests. Pre-validated on current main with install manifest checks, markdownlint, locale-install tests, and ECC 2.0 release-surface tests.
  • fix(install-targets): validate compiled OpenCode plugin before install (#2041)
    Fail fast when the OpenCode home install is attempted from a source checkout without the compiled .opencode/dist payload. PR had the full CI matrix green.
  • test(install-targets): add positive rules assertion to claude-project foreign-path test
    Addresses CodeRabbit review: the negative-only assertions could have
    passed on an empty plan. Add a positive assertion that the non-foreign
    'rules' path is still planned under .claude/rules/ecc so regression to
    zero ops would fail loudly.
  • feat(install-targets): add claude-project (per-project Claude Code) adapter
    Completes the install-target matrix for Claude Code. Until now, ECC's
    Claude support was home-scope only (~/.claude/) via the `claude` target.
    This adds a project-scope counterpart (./.claude/) via a new
    `claude-project` target so teams can install ECC per-repo without
    contaminating ~/.claude/ — matching the existing project-scope adapters
    for Cursor, Antigravity, Gemini, CodeBuddy, Joycode, and Zed.
    
    Symmetric with `claude`:
    - Same namespace under rules/ecc and skills/ecc
    - Same docs/<locale> handling for --locale
    - Same hooks placeholder substitution for hooks.json
    - Reuses claude-home's destination-mapping logic 1:1
    
    Use cases:
    - Monorepos with multiple Flow-managed projects
    - Teams that want ECC scoped per-project without touching ~/.claude/
    - Per-project skill/rule isolation when global install isn't desirable
    
    No breaking change: existing --target claude continues to route to
    claude-home (user-scope) unchanged. New target is opt-in.
    
    Tests
    -----
    - 4 new tests in tests/lib/install-targets.test.js
      (root resolution, lookup-by-id, plan parity with claude, foreign-path filtering)
    - All install-target regression guards (schema enum / SUPPORTED_INSTALL_TARGETS)
      still pass
    - End-to-end smoke: `--target claude-project --profile minimal --dry-run`
      emits 359 ops with destinations rooted at <projectRoot>/.claude/ (parity
      with --target claude which emits 359 ops rooted at ~/.claude/)
  • fix(hooks): avoid escaped quotes in plugin bootstrap
    Generate the inline hook root resolver with single-quoted JavaScript literals so Windows Git Bash does not choke on nested escaped double quotes before Node starts. Refresh hooks.json and add regression coverage for parsed hook commands and installed hook manifests.
  • test(lib): make concurrent-write test actually concurrent + use regex matcher for assert.throws
    Two round-1 review findings in `tests/lib/session-bridge.test.js`,
    both about test correctness rather than the underlying fix:
    
    1. **greptile P1 + coderabbitai Major + cubic P2 (all three): concurrent-write test ran sequentially.**
    
       The test spawned two child processes with two consecutive
       `spawnSync` calls. Because `spawnSync` blocks until the child
       exits, the second writer started *after* the first finished —
       the two writers never overlapped, so the rename race the fix
       targets was never actually exercised. The test would have passed
       with the old broken `${target}.tmp` suffix.
    
       Fix: introduce a one-off "race runner" helper that runs inside
       its own subprocess and uses async `spawn` to start both writers
       simultaneously. The runner waits for both to exit (the event
       loop is local to the runner subprocess, so this stays compatible
       with the synchronous test harness used elsewhere in this file)
       and reports both exit codes plus stderrs on stdout. The test
       then calls the runner via `spawnSync` and parses the result.
       Both writer children now overlap for the duration of their 200
       `writeBridgeAtomic` calls each, which is enough wall time to
       reliably trigger the rename race against the pre-fix code.
    
       Verified: with the fixed `${target}.${pid}.${nonce}.tmp` suffix,
       the test passes; with the old fixed `${target}.tmp` suffix
       reintroduced, it fails as expected (one writer hits ENOENT on
       roughly half its rename calls).
    
    2. **greptile P2 + cubic P3: `assert.throws` used a string as the second argument.**
    
       Node deprecated passing a string as the second argument to
       `assert.throws` years ago: the string is silently treated as
       the assertion failure message (what to print when the function
       does *not* throw) rather than as an error matcher. The check
       passed for any thrown error, not just the rename failure.
    
       Fix: pass a regex matcher as the second arg and keep the
       explanatory text as the third. The regex matches `EISDIR`,
       `EPERM`, `ENOTDIR`, or `ENOENT` because `renameSync` of a
       regular tmp file onto an existing directory raises different
       codes on Linux / macOS / BSD — making the matcher portable
       across CI runners.
    
    Test count unchanged at 14; `npm test` green; `npm run lint` clean.
    
    The two helper files (`tests/__tmp_bridge_writer.js`,
    `tests/__tmp_bridge_race_runner.js`) are written and unlinked
    inside the test's try/finally so they never persist beyond the
    test run.
  • test(lib): concurrent writeBridgeAtomic + tmp-cleanup regression
    Two regression tests pin down the previous two commits' atomic-rename
    fixes:
    
    1. **concurrent writes don't throw ENOENT or corrupt the file** —
       spawns two child Node processes (`tests/__tmp_bridge_writer.js`
       created in-test, cleaned up in finally) that each call
       `writeBridgeAtomic(sid, …)` 200 times against the same session
       ID with independent payloads. Asserts both subprocesses exit 0
       (the previous implementation produced ENOENT on roughly 50% of
       rename calls, all swallowed by the in-test catch) and the final
       bridge file is parseable JSON belonging to one of the two writers
       (last-writer-wins is fine; the contract is *no corruption* and
       *no rename ENOENT*, not data preservation).
    
    2. **tmp file cleanup on rename failure** — pre-creates a directory
       at the target bridge path so `renameSync(tmp, target)` fails,
       calls `writeBridgeAtomic`, asserts the call throws AND that no
       tmp file with the writer's `pid.<nonce>.tmp` prefix is left
       behind in `os.tmpdir()`. The previous code had no cleanup; the
       fix's `try/catch + unlinkSync` keeps tmpdir from accumulating
       orphan files across repeated rename failures.
    
    The first test deliberately writes independent payloads from each
    subprocess so this regression doesn't try to claim a property the
    fix doesn't actually deliver (read-modify-write race in the caller
    is a separate issue and out of scope per PR body).
    
    Test count: 12 → 14 in `tests/lib/session-bridge.test.js`;
    `npm test` green; `npm run lint` clean.
  • feat: add ECC statusline observability hooks
    Salvages the useful statusline/context monitor work from stale PR #1504 while preserving the current continuous-learning hook runner wiring.
    
    Adds the metrics bridge, context monitor, statusline script, shared cost/session bridge utilities, and tests. Fixes the reviewed false loop-detection hash collision for non-file tools, avoids default-session cost inflation, sanitizes statusline task lookup, and records hook payload session IDs in cost-tracker.
  • fix: port continuous-learning observer fixes
    Ports continuous-learning observer signal, storage, remote normalization, and v1 deprecation fixes onto current main.
  • fix: install native Cursor hook and MCP config (#1543)
    * fix: install native cursor hook and MCP config
    
    * fix: avoid false healthy stdio mcp probes