211 Commits

  • docs+chore: add README Security section; fix lint regressions on main
    - README: add a visible ## Security section (official sources, vuln reporting via SECURITY.md, GateGuard/IOC/AgentShield guardrails, security guide); make stats line a plain paragraph to clear MD028
    - eslint: empty catch comment in run-with-flags.js; drop unneeded escape in github-coordination/parsing.js; remove unused execFileSync import in its test (#2236 follow-ups)
    - markdownlint: wrap bare URLs in rules/vue/*.md (#2250 follow-up)
    
    npm run lint green; full suite 2836/2836.
  • Merge pull request #2236 from Victor-Casado/feat/github-native-coordination
    feat: add github-native coordination (epic-* commands + scripts + tests). Command registry + catalog reconciled.
  • Finalize and enhance SLSA generic generator workflow (#2197)
    * Add SLSA generic generator workflow
    
    * ci: finalize SLSA generator and fix bun test timeout
    
    - Harden SLSA workflow with persist-credentials: false and pinned actions
    - Update SLSA workflow to build real npm artifacts and fix digest outputs
    - Increase trae-install test timeout to prevent ETIMEDOUT under Bun
    - Fix Validate Components security violation in SLSA workflow
    
    * ci: finalize SLSA generator and fix bun test timeout
    
    - Harden SLSA workflow with persist-credentials: false and pinned actions
    - Update SLSA workflow to build real npm artifacts and fix digest outputs
    - Rename workflow to "SLSA generic generator workflow #1"
    - Increase trae-install test timeout to prevent ETIMEDOUT under Bun
    - Fix Validate Components security violation in SLSA workflow
    
    * Update generator-generic-ossf-slsa3-publish.yml
    
    Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
    
    * generator-generic-ossf-slsa3-publish.yml
    
    * .github/workflows/generator-generic-ossf-slsa3-publish.yml
    
    Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
    
    * Update .github/workflows/generator-generic-ossf-slsa3-publish.yml
    
    Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
    
    ---------
    
    Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
    Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
  • fix: add plugin cache health check (#2249)
    * fix: add plugin cache health check
    
    * fix: harden plugin cache diagnostics
    
    * fix: reject escaping plugin cache refs
    
    * test: remove unused plugin cache fixture
  • feat: add web capabilities dashboard (#2100)
    * feat: add web capabilities dashboard with agents, skills, commands, MCPs, rules, and hooks
    
    * fix: address code review - XSS, env exposure, port validation, error handling, packaging
    
    * add tests for dashboard
  • fix(security): add host/origin allowlist + validate git refs + quote workflow input (#2185)
    Three defense-in-depth fixes around untrusted input flowing to subprocess execution:
    
    1. **Control-pane HTTP server (scripts/lib/control-pane/server.js)**
       The local control-pane API binds to 127.0.0.1 but had no Host or Origin
       validation, so a DNS-rebinding attack from a malicious website could pivot
       into the loopback endpoints — including POST /api/actions/:id, which spawns
       'cargo run -- graph ...' with caller-supplied query strings. Add a hostname
       allowlist (loopback variants plus the explicitly configured --host) and
       reject mismatched Host (421) or non-loopback Origin (403) before any route
       handler runs.
    
    2. **OpenCode git-summary tool (.opencode/tools/git-summary.ts)**
       The tool was building 'git diff ${baseBranch}...HEAD --stat' with execSync
       and a raw model-supplied baseBranch string. Switch run() to execFileSync
       with an args array (no shell), validate baseBranch against a conservative
       git-ref allowlist (rejects shell metacharacters, leading -, embedded ..),
       and clamp the depth arg to a small positive integer before interpolating
       into 'git log --oneline -<N>'.
    
    3. **Reusable test workflow (.github/workflows/reusable-test.yml)**
       The 'Install dependencies' step interpolated ${{ inputs.package-manager }}
       directly into a bash 'case' and into an echo, so a downstream caller that
       forwarded attacker-controllable input could inject into the runner. Move
       the input into a PACKAGE_MANAGER env var and reference $PACKAGE_MANAGER
       inside the script per the GitHub script-injection guidance.
    
    Detected by Aeon + semgrep p/security-audit (host check via threat-model
    manual-review axis; git-summary via detect-child-process; workflow via
    run-shell-injection).
    
    Verification: node tests/run-all.js — 2686/2687 pre-existing tests pass; the
    one failure (observe.sh legacy output fallback) reproduces on main without
    this branch applied. Added 2 new control-pane tests covering the allowlist
    classifier and the DNS-rebinding-gate behavior end-to-end.
    
    ---
    Filed by [Aeon](https://github.com/aaronjmars/aeon-aaron).
    
    Co-authored-by: aeonframework <aeon@aaronjmars.com>
  • 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
  • refactor: apply code-review findings to github-native coordination
    scripts/github-coordination.js:
    - parseArgs: replace 13-entry if/else chain with BOOL_FLAGS/VALUE_FLAGS
      lookup maps; shrinks from 119 to ~45 lines
    - Extract dispatchCommand(options, ctx) and formatOutput(payload, options)
      from main(); main() shrinks to ~20 lines
    
    scripts/lib/github-coordination.js:
    - Split 1041-line monolith into 6 focused sub-modules under
      scripts/lib/github-coordination/ (policy, parsing, gh-api, state,
      actions, store); index becomes a thin re-export (~55 lines)
    - Document ECC_GH_SHIM trust boundary in runGh() (gh-api.js)
    - Document applyClaim() read→check→write race condition (actions.js)
    
    tests/lib/github-coordination.test.js:
    - Refactor runTests() to data-driven DESCRIPTORS array + runGroup()
      helper; runTests() shrinks to ~10 lines
    - Add 5 new edge-case tests: normalizeRepo('') and normalizeRepo('   ')
      throw, desiredLabelsForState for blocked/ready statuses, and
      buildIssueStateFromAction for validate action (15 → 20 tests)
    
    tests/scripts/github-coordination.test.js:
    - Replace console.log in test runner with process.stdout.write
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
  • feat: add github-native coordination (epic-* commands + scripts + tests)
    Adds a GitHub-native coordination layer on top of ECC:
    
    Commands (7 new slash commands):
    - epic-claim, epic-sync, epic-validate, epic-publish
    - epic-review, epic-unblock, epic-decompose
    
    Scripts:
    - scripts/github-coordination.js  — CLI entry point
    - scripts/lib/github-coordination.js  — core library (state machine, gh API wrappers)
    - scripts/status.js  — coordination status reporter
    
    Config:
    - config/github-native-coordination.json  — labels, review policy, validation gates
    
    Tests:
    - tests/lib/github-coordination.test.js  — 15 unit tests for pure functions
    - tests/scripts/github-coordination.test.js  — integration/CLI test suite
    
    Registry:
    - docs/COMMAND-REGISTRY.json  — adds 7 epic-* entries, totalCommands 84 → 91
    
    No encoding changes, no prp-* modifications, no Windows shims.
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
  • fix: stability batch — hook stdin truncation, Codex exa TOML, Stop hook JSON, GateGuard repetition (#2227)
    * fix(hooks): fail open on oversized stdin instead of echoing truncated JSON (#2222)
    
    run-with-flags.js capped stdin at 1MB but every fallthrough path still
    echoed the truncated string to stdout. The harness parses hook stdout as
    JSON, got a document cut mid-stream, and blocked the tool call — so any
    Edit/Write with a >1MB hook payload was permanently blocked by every
    registered pre-write hook, before ECC_HOOK_PROFILE / ECC_DISABLED_HOOKS
    gating could run.
    
    - Exit 0 with empty stdout (no opinion) when the stdin cap trips, before
      any echo or gating logic.
    - Flush stdout via write callback before process.exit: exiting right
      after stdout.write() dropped everything past the ~64KB pipe buffer,
      cutting even sub-cap pass-through payloads mid-JSON.
    
    Regression tests cover the enabled, disabled, and missing-arg paths for
    oversized payloads plus full echo of sub-cap >64KB payloads.
    
    * fix(codex): stop emitting invalid exa url entry, align merge with connector policy (#2224)
    
    The Codex MCP merge declared exa with a url key, but Codex's
    [mcp_servers.*] TOML schema is stdio-only — the url key makes the
    entire config.toml fail to load, bricking both the codex CLI and the
    desktop app. Every install/update re-injected the line because the
    urlEntry branch treated the broken entry as present.
    
    - ECC_SERVERS now emits only the current default set per
      docs/MCP-CONNECTOR-POLICY.md: chrome-devtools (stdio, command/args).
      Retired servers (supabase, playwright, context7, exa, github, memory,
      sequential-thinking) are never re-emitted; existing user-managed
      entries are untouched.
    - The merge now repairs the exact ECC-emitted broken form (url-only
      exa entry) on every run so re-running the installer fixes broken
      configs instead of preserving them. User stdio exa entries
      (command + mcp-remote) are left alone.
    - check-codex-global-state.sh requires chrome-devtools instead of the
      retired set, and flags url-only exa entries with a repair hint.
    
    Tests cover repair, re-run idempotence, stdio-entry preservation, and
    no-retired-server emission in add, update, dry-run, and disabled modes.
    
    * fix(hooks): never echo truncated stdin from Stop hooks (#2090)
    
    Stop hooks follow the ECC pass-through convention (echo stdin on
    stdout), but every echoing Stop hook capped stdin and echoed the capped
    string. The Stop payload carries last_assistant_message, so a long
    final assistant message produced a JSON document cut mid-stream on
    stdout, which the harness reports as 'Stop hook error: JSON validation
    failed' across the whole Stop chain.
    
    Reproduced: a Stop payload with a >64KB last_assistant_message run
    through run-with-flags + cost-tracker emitted exactly 65536 bytes of
    invalid JSON (cost-tracker capped stdin at 64KB — far below realistic
    Stop payloads).
    
    - cost-tracker: raise the cap to 1MB (matching all other hooks) and
      suppress the pass-through echo when stdin was truncated.
    - check-console-log, stop-format-typecheck, desktop-notify: suppress
      the echo when stdin was truncated; flush stdout before process.exit
      so sub-cap payloads are not cut at the ~64KB pipe buffer.
    - All hooks keep exiting 0 (fail-open); diagnostics go to stderr.
    
    New stop-hooks-stdout test asserts the contract for every registered
    Stop hook: stdout is empty or valid JSON, exit code 0 — for realistic
    100KB payloads and oversized >1MB payloads, via the production runner
    and via direct invocation. Updated the old hooks.test.js case that
    codified the truncated-echo behavior.
    
    * fix(hooks): dampen GateGuard fact-force repetition in long sessions (#2142)
    
    In long autonomous sessions the fact-force gate produced 10+
    near-identical 'state facts -> blocked -> restate -> retry' blocks in
    one context window, which measurably raises the odds of the model
    collapsing into a degenerate single-token repetition loop.
    
    - Track a per-session fact_force_denials counter in GateGuard state
      (merged max across concurrent writers, reset with the session, robust
      to malformed on-disk values).
    - The first GATEGUARD_FACT_FORCE_FULL_DENIALS denials (default 3) keep
      the full four-fact block; later denials emit a condensed single-line
      message that carries the denial ordinal, so consecutive denials are
      structurally different and never textually identical.
    - True retries of the same target remain allowed without re-prompting
      (unchanged). Destructive-Bash and routine-Bash gates are unchanged,
      as are the ECC_GATEGUARD=off / ECC_DISABLED_HOOKS escape hatches.
    
    Eight new tests cover budget counting, condensed format, ordinal
    advancement, retry pass-through, env tuning, malformed state, MultiEdit
    dampening, and destructive-gate exemption.
    
    * fix(hooks): keep security hooks able to block on oversized stdin (#2222)
    
    Refine the truncation fail-open: instead of skipping the hook entirely,
    the runner now suppresses only its own raw-echo when stdin was
    truncated. The hook still executes and receives the truncated flag
    (run() context / ECC_HOOK_INPUT_TRUNCATED), so config-protection keeps
    blocking truncated protected-config payloads (its test requires exit 2)
    while pass-through hooks fail open with empty stdout as before.
    
    * style: apply repo formatter to touched hook files
  • feat(mcp): single-connector default set + connector policy (#2219)
    Reduce the default .mcp.json to one connector (chrome-devtools) per the
    new policy in docs/MCP-CONNECTOR-POLICY.md: a default earns its slot only
    if it is universal AND MCP beats a CLI/API wrapped in a skill. June 2026
    audit verdicts: github -> gh via github-ops skill; context7 -> REST via
    documentation-lookup; exa -> harness-native search (+ exa-search skill);
    memory -> native harness memory + instincts; playwright -> playwright CLI
    skills (vendor moved agent flows off MCP); sequential-thinking -> native
    extended thinking. All six remain opt-in in mcp-configs/mcp-servers.json.
    Tests updated: plugin-manifest policy assertions + install-apply Cursor
    expectations.
    
    Co-authored-by: ECC Test <ecc@example.test>
  • fix: surface legacy data warning in instinct-cli status (#2127)
    * fix: surface legacy data warning in instinct-cli status (#2036)
    
    When the data directory moved from ~/.claude/homunculus/ to the
    XDG-compliant ~/.local/share/ecc-homunculus/, legacy installs with data
    still in the old path saw "No instincts found" with no explanation.
    
    Add _warn_legacy_data() to cmd_status so users get a clear, actionable
    warning pointing them to the migration script or the CLV2_HOMUNCULUS_DIR
    override. Wrap the directory scan in try/except to handle permission
    errors gracefully.
    
    Closes #2036
    
    Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
    
    * fix: address review feedback — drop unused f-strings, resolve absolute migrate path
    
    Remove extraneous f-prefix from strings without interpolation (ruff F541).
    Resolve migrate-homunculus.sh path relative to instinct-cli.py instead of
    hard-coding a repo-relative path that only works from the repo root.
    
    Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
    
    * fix: quote migrate script path to handle spaces
    
    Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: kky <lingmu141592@gmail.com>
    Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
  • fix: retire rules/zh from the always-loaded default rules install (#2170)
    rules/zh shipped ~17KB of Chinese rule text into the auto-loaded rules tree
    of every default install (rules-core installs the bare 'rules' path with
    defaultInstall: true), with no paths: frontmatter gating. The content had
    also drifted behind both rules/common and the maintained translations in
    docs/zh-CN/rules/common (e.g. zh/coding-style.md 48 lines vs the 52-line
    docs/zh-CN copy), and 'zh' was already dropped from the installer's language
    help in favor of the gated docs-zh-cn locale module (--locale zh-CN).
    
    - move rules/zh/code-review.md to docs/zh-CN/rules/common/code-review.md:
      the only file with no counterpart in the maintained locale tree (fills a
      zh-CN parity gap with rules/common/code-review.md)
    - delete the remaining 10 rules/zh files, all older duplicates of
      docs/zh-CN/rules/common content
    - update trae-install test to assert the rules tree via rules/web instead
    
    Not addressed here: rules/README.md (~5.5KB of installer docs) still ships
    into the auto-loaded tree via the bare 'rules' module path; filtering README
    files from rule-tree expansion is a separate decision
  • fix: close install manifest packaging gaps (#2172)
    - commands-core now ships scripts/harness-audit.js and scripts/skills-health.js:
      the module installs the whole commands/ dir, so /harness-audit and
      /skill-health were installed without their backing engines on
      manifest-driven installs (the original 1.10.0 failure mode)
    - agentic-patterns now ships scripts/claw.js: the module installs the
      nanoclaw-repl skill, whose workflow operates scripts/claw.js
    - package.json files array gains scripts/skills-health.js so the npm publish
      surface stays aligned with the module graph (claw.js and harness-audit.js
      were already listed)
    - orchestration drops commands/multi-workflow.md and commands/sessions.md
      from its explicit paths: both are already shipped by commands-core, which
      is a declared dependency of the module, so the duplicate ownership produced
      two copy operations per destination in install-state. The two scripts/lib
      entries are kept because hooks-runtime is NOT a declared dependency and a
      standalone orchestration install still needs them
  • 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
  • Sync Marketplace Pro readback release gate (#2019)
    * docs: sync marketplace pro readback gate
    
    * docs: refresh operator dashboard after readback sync
    
    * docs: sanitize marketplace readback summary
    
    * docs: refresh operator dashboard after marketplace readback
  • feat: extend harness audit integration scoring (#1990)
    Salvages the useful harness-audit scoring work from #1989 while preserving the current hook registry and newer plugin install detection. Adds GitHub integration checks, conditional deploy-provider categories, dynamic applicable category metadata, and CODEOWNERS coverage.
  • 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(ci): regression coverage for newly-covered invisible code points
    9 new test cases pin down the two previous commits' denylist
    extensions. Each verifies both detection (validator exit non-zero +
    the expected `dangerous-invisible U+<HEX>` line on stderr) and,
    where applicable, `--write` sanitization.
    
    Coverage:
    
    Tag block (commit 1):
    - U+E0041 TAG LATIN CAPITAL LETTER A — the range's printable ASCII
      shadow; this is the byte sequence demonstrated in published ASCII
      smuggling proofs of concept.
    - U+E007F CANCEL TAG — the range end.
    
    Other invisibles (commit 2):
    - U+180E MONGOLIAN VOWEL SEPARATOR
    - U+115F HANGUL CHOSEONG FILLER
    - U+1160 HANGUL JUNGSEONG FILLER
    - U+2061 FUNCTION APPLICATION (range start)
    - U+2064 INVISIBLE PLUS (range end)
    - U+3164 HANGUL FILLER
    
    Detection table is data-driven (one loop, one assertion per row) so
    adding the next invisible to the denylist also gets a paired
    regression test by simply appending to NEWLY_COVERED_RANGES.
    
    Plus a `--write` integration test:
    - writes a markdown file containing both Tag block (5 chars) and
      U+180E, runs `--write`, asserts both removed and surrounding text
      preserved character-for-character ('# Title\n\nBenigntext.\n').
    - re-runs the validator without `--write` and asserts exit 0,
      confirming the sanitizer's output is idempotent under the
      extended denylist.
    
    Test count: 5 → 14 in this file; full `yarn test` green; `yarn lint`
    clean.