mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-18 22:13:45 +02:00
chore: merge main into Bengali integration
Resolve the 3 conflicts from 104 upstream commits (2.2.1 -> 2.2.2). Merge resolution: - .claude-plugin/plugin.json, .claude-plugin/marketplace.json: take the upstream 2.2.2 version bump, keep the catalog counts at 69 agents / 293 skills. Upstream released 2.2.2 still describing 68/292, so neither side could be taken whole. - docs/ja-JP/README.md: upstream restructured the page, moving the hero above the fold and replacing both markdown language selectors with a single centered HTML block that also gained Español. Take that structure and re-add বাংলা to the new selector. - rules/common/agents.md: merged clean, but the roster line main added reads "68 agents". Resync to 69 alongside the other catalog counts. Verified: catalog.js reports 69 agents / 293 skills and confirms the documentation counts match the repository catalog; the nine CI validators and the command-registry check pass; bengali-language-surface.test.js is 7/7, including the selector and locale-parity assertions. Co-Authored-By: Kishor Morol <kishormorol@gmail.com>
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
"plugins": [
|
||||
{
|
||||
"name": "ecc",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.2",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: agent-introspection-debugging
|
||||
description: Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Agent Introspection Debugging
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: agent-sort
|
||||
description: Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Agent Sort
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: api-design
|
||||
description: REST API design patterns including resource naming, status codes, pagination, filtering, error responses, versioning, and rate limiting for production APIs. Use when designing or reviewing REST endpoints, resource names, status codes, pagination, or versioning.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# API Design Patterns
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: article-writing
|
||||
description: Write articles, guides, blog posts, tutorials, newsletter issues, and other long-form content in a distinctive voice derived from supplied examples or brand guidance. Use when the user wants polished written content longer than a paragraph, especially when voice consistency, structure, and credibility matter.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Article Writing
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: backend-patterns
|
||||
description: Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes. Use when building or reviewing Node.js, Express, or Next.js API routes and their data access.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Backend Development Patterns
|
||||
|
||||
@@ -6,6 +6,7 @@ description: >-
|
||||
visual craft, offer packaging, evidence, enterprise-readiness, thought
|
||||
leadership, pricing, client's strategic tension) with explicit 1–5 rubrics
|
||||
and a tension-plot. Precedes competitive-report-structure.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Benchmark Methodology
|
||||
|
||||
@@ -6,6 +6,7 @@ description: >-
|
||||
personality, voice, narrative, and founder-brand tension across 8 modules
|
||||
using laddering, 5 Whys, and projective techniques. Produces a resumable
|
||||
session with disk-persisted state and a master brandbook (90_SYNTHESIS.md).
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Brand Discovery
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: brand-voice
|
||||
description: Build a source-derived writing style profile from real posts, essays, launch notes, docs, or site copy, then reuse that profile across content, outreach, and social workflows. Use when the user wants voice consistency without generic AI writing tropes.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Brand Voice
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: bun-runtime
|
||||
description: Bun as runtime, package manager, bundler, and test runner. When to choose Bun vs Node, migration notes, and Vercel support.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Bun Runtime
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: coding-standards
|
||||
description: Baseline cross-project coding conventions for naming, readability, immutability, and code-quality review. Use detailed frontend or backend skills for framework-specific patterns. Use when reviewing code quality or naming with no framework-specific skill that applies.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Coding Standards & Best Practices
|
||||
|
||||
@@ -6,6 +6,7 @@ description: >-
|
||||
counts as a competitor, which tier they belong to, and which sources to mine.
|
||||
First step in the three-skill competitive pipeline; precedes
|
||||
benchmark-methodology.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Competitive Platform Analysis
|
||||
|
||||
@@ -6,6 +6,7 @@ description: >-
|
||||
profiles, benchmarking matrix, white-space analysis, strategic recommendations,
|
||||
and team alignment trigger questions. Final step in the three-skill competitive
|
||||
pipeline.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Competitive Report Structure
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: content-engine
|
||||
description: Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Content Engine
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: crosspost
|
||||
description: Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Crosspost
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: deep-research
|
||||
description: Multi-source deep research using firecrawl and exa MCPs. Searches the web, synthesizes findings, and delivers cited reports with source attribution. Use when the user wants thorough research on any topic with evidence and citations.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Deep Research
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: dmux-workflows
|
||||
description: Multi-agent orchestration using dmux (tmux pane manager for AI agents). Patterns for parallel agent workflows across Claude Code, Codex, OpenCode, and other harnesses. Use when running multiple agent sessions in parallel or coordinating multi-agent development workflows.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# dmux Workflows
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: documentation-lookup
|
||||
description: Use up-to-date library and framework docs via Context7 MCP instead of training data. Activates for setup questions, API references, code examples, or when the user names a framework (e.g. React, Next.js, Prisma).
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Documentation Lookup (Context7)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: e2e-testing
|
||||
description: Playwright E2E testing patterns, Page Object Model, configuration, CI/CD integration, artifact management, and flaky test strategies. Use when writing Playwright tests, structuring page objects, or fixing flaky E2E runs in CI.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# E2E Testing Patterns
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
name: eval-harness
|
||||
description: Formal evaluation framework for Claude Code sessions implementing eval-driven development (EDD) principles. Use when a Claude Code workflow needs a formal eval before it is trusted or changed.
|
||||
allowed-tools: Read, Write, Edit, Bash, Grep, Glob
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Eval Harness Skill
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: everything-claude-code
|
||||
description: Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Everything Claude Code Conventions
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: exa-search
|
||||
description: Neural search via Exa MCP for web, code, and company research. Use when the user needs web search, code examples, company intel, people lookup, or AI-powered deep research with Exa's neural search engine.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Exa Search
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: fal-ai-media
|
||||
description: Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# fal.ai Media Generation
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: frontend-patterns
|
||||
description: Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices. Use when building or reviewing React or Next.js components, state, or render performance.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Frontend Development Patterns
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: frontend-slides
|
||||
description: Create stunning, animation-rich HTML presentations from scratch or by converting PowerPoint files. Use when the user wants to build a presentation, convert a PPT/PPTX to web, or create slides for a talk/pitch. Helps non-designers discover their aesthetic through visual exploration rather than abstract choices.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Frontend Slides
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: investor-materials
|
||||
description: Create and update pitch decks, one-pagers, investor memos, accelerator applications, financial models, and fundraising materials. Use when the user needs investor-facing documents, projections, use-of-funds tables, milestone plans, or materials that must stay internally consistent across multiple fundraising assets.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Investor Materials
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: investor-outreach
|
||||
description: Draft cold emails, warm intro blurbs, follow-ups, update emails, and investor communications for fundraising. Use when the user wants outreach to angels, VCs, strategic investors, or accelerators and needs concise, personalized, investor-facing messaging.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Investor Outreach
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: market-research
|
||||
description: Conduct market research, competitive analysis, investor due diligence, and industry intelligence with source attribution and decision-oriented summaries. Use when the user wants market sizing, competitor comparisons, fund research, technology scans, or research that informs business decisions.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Market Research
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: mcp-server-patterns
|
||||
description: Build MCP servers with Node/TypeScript SDK — tools, resources, prompts, Zod validation, stdio vs Streamable HTTP. Use Context7 or official MCP docs for latest API. Use when building or debugging an MCP server — tools, resources, prompts, validation, or transport choice.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# MCP Server Patterns
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
name: mle-workflow
|
||||
description: Production machine-learning engineering workflow for data contracts, reproducible training, model evaluation, deployment, monitoring, and rollback. Use when building, reviewing, or hardening ML systems beyond one-off notebooks.
|
||||
allowed-tools: Read, Write, Edit, Bash, Grep, Glob
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Machine Learning Engineering Workflow
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: nextjs-turbopack
|
||||
description: Next.js 16+ and Turbopack — incremental bundling, FS caching, dev speed, and when to use Turbopack vs webpack.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Next.js and Turbopack
|
||||
|
||||
@@ -3,6 +3,7 @@ name: plan-canvas
|
||||
description: Open plans and HTML artifacts in a local browser canvas where the human annotates elements, chats, and approves or requests changes without leaving the page. Use when presenting a plan for review, or when feedback like "move this, change that" is easier pointed at than typed.
|
||||
metadata:
|
||||
origin: ECC
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Plan Canvas
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: product-capability
|
||||
description: Translate PRD intent, roadmap asks, or product discussions into an implementation-ready capability plan that exposes constraints, invariants, interfaces, and unresolved decisions before multi-service work starts. Use when the user needs an ECC-native PRD-to-SRS lane instead of vague planning prose.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Product Capability
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: security-review
|
||||
description: Use this skill when adding authentication, handling user input, working with secrets, creating API endpoints, or implementing payment/sensitive features. Provides comprehensive security checklist and patterns.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Security Review Skill
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: strategic-compact
|
||||
description: Suggests manual context compaction at logical intervals to preserve context through task phases rather than arbitrary auto-compaction. Use when a session is approaching a context limit and a task phase is a natural place to compact.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Strategic Compact Skill
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: tdd-workflow
|
||||
description: Use this skill when writing new features, fixing bugs, or refactoring code. Enforces test-driven development with 80%+ coverage including unit, integration, and E2E tests.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Test-Driven Development Workflow
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: unified-memory
|
||||
description: Share durable, inspectable context and handoffs between Claude, Codex, Hermes, Cursor, OpenCode, and other agents through the local ECC Memory Vault. Use when an agent must save work state, transfer context, resume another agent's task, or search shared project knowledge.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Unified Memory
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: verification-loop
|
||||
description: "A comprehensive verification system for Claude Code sessions. Use when verifying a Claude Code session's work before claiming it is complete."
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Verification Loop Skill
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: video-editing
|
||||
description: AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Video Editing
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: x-api
|
||||
description: X/Twitter API integration for posting tweets, threads, reading timelines, search, and analytics. Covers OAuth auth patterns, rate limits, and platform-native content posting. Use when the user wants to interact with X programmatically.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# X API
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"name": "ecc",
|
||||
"source": "./",
|
||||
"description": "Harness-native ECC operator layer - 69 agents, 293 skills, 94 legacy command shims, reusable hooks, rules, selective install profiles, and production-ready workflows for Claude Code, Codex, OpenCode, Cursor, and related agent harnesses",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.2",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
"email": "me@affaanmustafa.com"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ecc",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.2",
|
||||
"description": "Harness-native ECC plugin for engineering teams - 69 agents, 293 skills, 94 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ecc",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.2",
|
||||
"description": "Harness-native ECC workflows for Codex: shared skills, production-ready MCP configs, and selective-install-aligned conventions for TDD, security scanning, code review, and autonomous development.",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
# Package manager setup
|
||||
- name: Setup pnpm
|
||||
if: matrix.pm == 'pnpm' && matrix.node != '18.x'
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
with:
|
||||
# Keep an explicit pnpm major because this repo's packageManager is Yarn.
|
||||
version: 10
|
||||
@@ -267,6 +267,11 @@ jobs:
|
||||
- name: Run Python tests
|
||||
run: python -m pytest tests/test_*.py -m "not integration"
|
||||
|
||||
- name: Test minimum supported OpenAI SDK
|
||||
run: |
|
||||
python -m pip install 'openai==2.34.0'
|
||||
python -m pytest tests/test_provider_tools.py tests/test_atlas_provider.py tests/test_astraflow_provider.py tests/test_resolver.py
|
||||
|
||||
security:
|
||||
name: Security Scan
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- name: Setup pnpm
|
||||
if: inputs.package-manager == 'pnpm' && inputs.node-version != '18.x'
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
with:
|
||||
# Keep an explicit pnpm major because this repo's packageManager is Yarn.
|
||||
version: 10
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
+1
-1
@@ -37,4 +37,4 @@
|
||||
// Export the main plugin
|
||||
// opencode's legacy plugin loader iterates every module export and throws if
|
||||
// any is not a plugin function, so only the plugin function may be exported.
|
||||
export { default } from "./plugins/index.js"
|
||||
export { default } from "./plugins/index.ts"
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ecc-universal",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ecc-universal",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.2",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@opencode-ai/plugin": "^1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ecc-universal",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.2",
|
||||
"description": "ECC plugin for OpenCode - agents, commands, hooks, and skills",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import changedFilesTool from "../tools/changed-files.js"
|
||||
import dependencyAnalyzerTool from "../tools/dependency-analyzer.js"
|
||||
import changedFilesTool from "../tools/changed-files.ts"
|
||||
import dependencyAnalyzerTool from "../tools/dependency-analyzer.ts"
|
||||
|
||||
/**
|
||||
* Type definitions for better type safety
|
||||
@@ -111,9 +111,9 @@ export const ECCHooksPlugin: ECCHooksPluginFn = async ({
|
||||
// This plugin is OpenCode's startup entry point, so a static import
|
||||
// failure here previously crashed the whole plugin -- and with it, the
|
||||
// entire OpenCode session -- before any hooks could load (see #2530).
|
||||
let changedFilesStore: typeof import("./lib/changed-files-store.js") | undefined
|
||||
let changedFilesStore: typeof import("./lib/changed-files-store.ts") | undefined
|
||||
try {
|
||||
const store = await import("./lib/changed-files-store.js")
|
||||
const store = await import("./lib/changed-files-store.ts")
|
||||
store.initStore(worktreePath)
|
||||
changedFilesStore = store
|
||||
} catch {
|
||||
@@ -481,7 +481,7 @@ export const ECCHooksPlugin: ECCHooksPluginFn = async ({
|
||||
* Triggers: Before shell command execution
|
||||
* Action: Sets PROJECT_ROOT, PACKAGE_MANAGER, DETECTED_LANGUAGES, ECC_VERSION
|
||||
*/
|
||||
"shell.env": async () => {
|
||||
"shell.env": async (_input: { cwd: string }, output: { env: Record<string, string> }) => {
|
||||
const env: Record<string, string> = {
|
||||
ECC_VERSION: getECCVersion(),
|
||||
ECC_PLUGIN: "true",
|
||||
@@ -523,7 +523,8 @@ export const ECCHooksPlugin: ECCHooksPluginFn = async ({
|
||||
env.PRIMARY_LANGUAGE = detected[0]
|
||||
}
|
||||
|
||||
return env
|
||||
// OpenCode reads the supplied output object and ignores callback return values.
|
||||
output.env = { ...output.env, ...env }
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -531,13 +532,16 @@ export const ECCHooksPlugin: ECCHooksPluginFn = async ({
|
||||
* OpenCode-specific: Control context compaction behavior
|
||||
*
|
||||
* Triggers: Before context compaction
|
||||
* Action: Push ECC context block and custom compaction prompt
|
||||
* Action: Push ECC context block and compaction guidance
|
||||
*/
|
||||
"experimental.session.compacting": async () => {
|
||||
"experimental.session.compacting": async (
|
||||
_input: { sessionID: string },
|
||||
output: { context: string[]; prompt?: string }
|
||||
) => {
|
||||
const contextBlock = [
|
||||
"# ECC Context (preserve across compaction)",
|
||||
"",
|
||||
"## Active Plugin: ECC v2.2.1",
|
||||
"## Active Plugin: ECC v2.2.2",
|
||||
"- Hooks: file.edited, tool.execute.before/after, session.created/idle/deleted, shell.env, compacting, permission.ask",
|
||||
"- Tools: run-tests, check-coverage, security-audit, format-code, lint-check, git-summary, changed-files",
|
||||
"- Agents: 13 specialized (planner, architect, tdd-guide, code-reviewer, security-reviewer, build-error-resolver, e2e-runner, refactor-cleaner, doc-updater, go-reviewer, go-build-resolver, database-reviewer, python-reviewer)",
|
||||
@@ -558,9 +562,16 @@ export const ECCHooksPlugin: ECCHooksPluginFn = async ({
|
||||
contextBlock.push("")
|
||||
}
|
||||
|
||||
return {
|
||||
context: contextBlock.join("\n"),
|
||||
compaction_prompt: "Focus on preserving: 1) Current task status and progress, 2) Key decisions made, 3) Files created/modified, 4) Remaining work items, 5) Any security concerns flagged. Discard: verbose tool outputs, intermediate exploration, redundant file listings.",
|
||||
const eccContext = [
|
||||
contextBlock.join("\n"),
|
||||
"Focus on preserving: 1) Current task status and progress, 2) Key decisions made, 3) Files created/modified, 4) Remaining work items, 5) Any security concerns flagged. Discard: verbose tool outputs, intermediate exploration, redundant file listings.",
|
||||
]
|
||||
|
||||
// OpenCode requires output assignment and skips context when a prompt is set.
|
||||
if (output.prompt !== undefined) {
|
||||
output.prompt = [output.prompt, ...eccContext].join("\n\n")
|
||||
} else {
|
||||
output.context = [...output.context, ...eccContext]
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* while taking advantage of OpenCode's more sophisticated 20+ event types.
|
||||
*/
|
||||
|
||||
export { ECCHooksPlugin, default } from "./ecc-hooks.js"
|
||||
export { ECCHooksPlugin, default } from "./ecc-hooks.ts"
|
||||
|
||||
// Re-export for named imports
|
||||
export * from "./ecc-hooks.js"
|
||||
export * from "./ecc-hooks.ts"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
|
||||
import type { ChangeType, TreeNode } from "../plugins/lib/changed-files-store.js"
|
||||
import type { ChangeType, TreeNode } from "../plugins/lib/changed-files-store.ts"
|
||||
|
||||
const INDICATORS: Record<ChangeType, string> = {
|
||||
added: "+",
|
||||
@@ -27,12 +27,12 @@ function renderTree(nodes: TreeNode[], indent: string): string {
|
||||
// file, so a static import failure here previously took down the entire
|
||||
// tools module -- and with it, the whole OpenCode session -- on the very
|
||||
// first tool-loading pass (see #2530).
|
||||
type ChangedFilesStore = typeof import("../plugins/lib/changed-files-store.js")
|
||||
type ChangedFilesStore = typeof import("../plugins/lib/changed-files-store.ts")
|
||||
let changedFilesStorePromise: Promise<ChangedFilesStore> | undefined
|
||||
|
||||
async function loadChangedFilesStore(): Promise<ChangedFilesStore> {
|
||||
if (!changedFilesStorePromise) {
|
||||
changedFilesStorePromise = import("../plugins/lib/changed-files-store.js").catch(() => {
|
||||
changedFilesStorePromise = import("../plugins/lib/changed-files-store.ts").catch(() => {
|
||||
changedFilesStorePromise = undefined
|
||||
throw new Error(
|
||||
"changed-files tool: could not load the changed-files store. " +
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
*/
|
||||
|
||||
// Re-export all tools
|
||||
export { default as runTests } from "./run-tests.js"
|
||||
export { default as checkCoverage } from "./check-coverage.js"
|
||||
export { default as securityAudit } from "./security-audit.js"
|
||||
export { default as formatCode } from "./format-code.js"
|
||||
export { default as lintCheck } from "./lint-check.js"
|
||||
export { default as gitSummary } from "./git-summary.js"
|
||||
export { default as changedFiles } from "./changed-files.js"
|
||||
export { default as dependencyAnalyzer } from "./dependency-analyzer.js"
|
||||
export { default as runTests } from "./run-tests.ts"
|
||||
export { default as checkCoverage } from "./check-coverage.ts"
|
||||
export { default as securityAudit } from "./security-audit.ts"
|
||||
export { default as formatCode } from "./format-code.ts"
|
||||
export { default as lintCheck } from "./lint-check.ts"
|
||||
export { default as gitSummary } from "./git-summary.ts"
|
||||
export { default as changedFiles } from "./changed-files.ts"
|
||||
export { default as dependencyAnalyzer } from "./dependency-analyzer.ts"
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": [
|
||||
|
||||
+44
-3
@@ -141,6 +141,10 @@ const DISABLED_VALUES = new Set(["0", "false", "off", "none", "disabled"])
|
||||
/**
|
||||
* Optional Pi companion packages. ECC works without every one of these; they
|
||||
* are reported by `/ecc-doctor` so users can see which extras are available.
|
||||
*
|
||||
* These are capability names, not exact install specs. See
|
||||
* `findInstalledCompanion` for how an entry is matched against what Pi has
|
||||
* actually installed.
|
||||
*/
|
||||
const COMPANION_PACKAGES = [
|
||||
"pi-subagents",
|
||||
@@ -475,6 +479,41 @@ function normalizePiPackageName(entry: unknown): string | undefined {
|
||||
return versionAt > 0 ? spec.slice(0, versionAt) : spec
|
||||
}
|
||||
|
||||
/**
|
||||
* The installed package satisfying a companion entry, or undefined if none is.
|
||||
*
|
||||
* An exact name match is the ordinary case. An UNSCOPED companion entry is
|
||||
* also satisfied by a scoped package with the same bare name --
|
||||
* `@tintinweb/pi-subagents` satisfies `pi-subagents`. The subagents capability
|
||||
* is published to npm by more than one maintainer under that same bare name,
|
||||
* and a user running a scoped fork has the capability installed by any
|
||||
* meaning of the word; reporting "not installed" at them while its tools are
|
||||
* live in their session is a false negative, and the suggested
|
||||
* `pi install npm:pi-subagents` would push them into installing a second
|
||||
* extension that registers the same tool names.
|
||||
*
|
||||
* A SCOPED companion entry is matched exactly, because there the scope is
|
||||
* part of the identity the entry names, not incidental packaging.
|
||||
*/
|
||||
function findInstalledCompanion(companion: string, installed: Set<string>): string | undefined {
|
||||
if (installed.has(companion)) {
|
||||
return companion
|
||||
}
|
||||
|
||||
if (companion.startsWith("@")) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const scopedSuffix = `/${companion}`
|
||||
for (const name of installed) {
|
||||
if (name.startsWith("@") && name.endsWith(scopedSuffix)) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function countDirectories(dir: string): number {
|
||||
try {
|
||||
return fs.readdirSync(dir, { withFileTypes: true }).filter(entry => entry.isDirectory()).length
|
||||
@@ -547,10 +586,12 @@ function buildDoctorReport(ctx: ExtensionContext): string {
|
||||
|
||||
const installed = listInstalledPiPackages(ctx.cwd)
|
||||
for (const name of COMPANION_PACKAGES) {
|
||||
const present = installed.has(name)
|
||||
lines.push(` ${present ? "installed " : "not installed"} ${name}`)
|
||||
if (!present) {
|
||||
const match = findInstalledCompanion(name, installed)
|
||||
lines.push(` ${match ? "installed " : "not installed"} ${name}`)
|
||||
if (!match) {
|
||||
lines.push(` install with: pi install npm:${name}`)
|
||||
} else if (match !== name) {
|
||||
lines.push(` satisfied by: ${match}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Security Evidence — PR #3172 / #3171
|
||||
|
||||
Commit under review: observe.sh Layer-1 allowlist adds `sdk-cli`.
|
||||
|
||||
## Changed security-sensitive surface
|
||||
- `skills/continuous-learning-v2/hooks/observe.sh` (agent hook entrypoint allowlist)
|
||||
|
||||
## Threat model (bounded)
|
||||
- **Risk if missing `sdk-cli`**: interactive Agent SDK CLI sessions never observe (availability/coverage gap).
|
||||
- **Risk if allowlist too broad**: non-interactive bots could start the observer. Mitigated by Layers 2–5 (`ECC_HOOK_PROFILE=minimal`, `ECC_SKIP_OBSERVE=1`, `agent_id`, path exclusions) — unchanged by this PR.
|
||||
- **No secrets / auth tokens / billing / webhook handlers** were modified.
|
||||
|
||||
## Security-focused validation artifacts (this PR)
|
||||
1. **Focused security regression test** (new): `tests/hooks/observe-entrypoint-security.test.js`
|
||||
- Asserts source allowlist includes `sdk-cli`
|
||||
- Asserts Layer-1 allows: `cli`, `sdk-ts`, `sdk-cli`, `claude-desktop`, `claude-vscode`
|
||||
- Asserts Layer-1 rejects: `unknown-bot`, `ci-bot`
|
||||
2. **Supply-chain IOC scan** (repo gate): `npm run security:ioc-scan`
|
||||
|
||||
## Command output (local)
|
||||
|
||||
### observe-entrypoint-security.test.js
|
||||
```text
|
||||
|
||||
=== observe.sh Layer-1 entrypoint security (#3171) ===
|
||||
|
||||
✓ source allowlist includes sdk-cli
|
||||
✓ Layer-1 allows cli
|
||||
✓ Layer-1 allows sdk-ts
|
||||
✓ Layer-1 allows sdk-cli
|
||||
✓ Layer-1 allows claude-desktop
|
||||
✓ Layer-1 allows claude-vscode
|
||||
✓ Layer-1 rejects unknown-bot
|
||||
✓ Layer-1 rejects ci-bot
|
||||
|
||||
All Layer-1 security checks passed.
|
||||
```
|
||||
|
||||
### npm run security:ioc-scan
|
||||
```text
|
||||
|
||||
> ecc-universal@2.2.1 security:ioc-scan
|
||||
> node scripts/ci/scan-supply-chain-iocs.js
|
||||
|
||||
Supply-chain IOC scan passed for /workspace/pr-work/ECC-3171 (12 files inspected)
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
Allowlist change is covered by a dedicated security regression test plus the repository IOC scan. Unknown entrypoints remain denied at Layer-1.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This is a **production-ready AI coding plugin** providing 69 specialized agents, 293 skills, 94 commands, and automated hook workflows for software development.
|
||||
|
||||
**Version:** 2.2.1
|
||||
**Version:** 2.2.2
|
||||
|
||||
## Core Principles
|
||||
|
||||
@@ -53,15 +53,15 @@ This is a **production-ready AI coding plugin** providing 69 specialized agents,
|
||||
## Agent Orchestration
|
||||
|
||||
Use agents proactively without user prompt:
|
||||
- Complex feature requests → **planner**
|
||||
- Code just written/modified → **code-reviewer**
|
||||
- Bug fix or new feature → **tdd-guide**
|
||||
- Architectural decision → **architect**
|
||||
- Security-sensitive code → **security-reviewer**
|
||||
- Brownfield project onboarding → **spec-miner**
|
||||
- Autonomous loops / loop monitoring → **loop-operator**
|
||||
- Harness config reliability and cost → **harness-optimizer**
|
||||
- RAG/retrieval pipeline changes → **rag-pipeline-reviewer**
|
||||
- Complex feature requests → **ecc:planner**
|
||||
- Code just written/modified → **ecc:code-reviewer**
|
||||
- Bug fix or new feature → **ecc:tdd-guide**
|
||||
- Architectural decision → **ecc:architect**
|
||||
- Security-sensitive code → **ecc:security-reviewer**
|
||||
- Brownfield project onboarding → **ecc:spec-miner**
|
||||
- Autonomous loops / loop monitoring → **ecc:loop-operator**
|
||||
- Harness config reliability and cost → **ecc:harness-optimizer**
|
||||
- RAG/retrieval pipeline changes → **ecc:rag-pipeline-reviewer**
|
||||
|
||||
Use parallel execution for independent operations — launch multiple agents simultaneously.
|
||||
|
||||
@@ -115,9 +115,9 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Plan** — Use planner agent, identify dependencies and risks, break into phases
|
||||
2. **TDD** — Use tdd-guide agent, write tests first, implement, refactor
|
||||
3. **Review** — Use code-reviewer agent immediately, address CRITICAL/HIGH issues
|
||||
1. **Plan** — Use ecc:planner agent, identify dependencies and risks, break into phases
|
||||
2. **TDD** — Use ecc:tdd-guide agent, write tests first, implement, refactor
|
||||
3. **Review** — Use ecc:code-reviewer agent immediately, address CRITICAL/HIGH issues
|
||||
4. **Capture knowledge in the right place**
|
||||
- Personal debugging notes, preferences, and temporary context → auto memory
|
||||
- Team/project knowledge (architecture decisions, API changes, runbooks) → the project's existing docs structure
|
||||
|
||||
+30
-2
@@ -1,10 +1,38 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
## 2.2.2 - 2026-09-15
|
||||
|
||||
### Fixed
|
||||
|
||||
- Claude settings updates now tolerate a missing Windows device ID while retaining full-precision inode checks and strict matching when both device IDs are available.
|
||||
#### Packaging
|
||||
|
||||
- Explicitly include the compiled OpenCode payload in the npm package and verify that packing builds it from a clean state with lifecycle scripts enabled.
|
||||
|
||||
#### Memory and MCP
|
||||
|
||||
- Distinguish incomplete memory reads from missing records and classify directory traversal failures (`90ef62cb`, `8321021c`).
|
||||
- Accept the reserved `_meta` parameter on memory MCP ping requests (`380f4b35`).
|
||||
|
||||
#### Hooks and Windows compatibility
|
||||
|
||||
- Keep `hooks.json` within Claude Code's schema by moving stable hook metadata into a validated sidecar (`1ac07903`).
|
||||
- Handle stuck optional values and long-option prefixes in the no-verify guard (`4f373874`).
|
||||
- Support Windows linter paths and ESLint 9 (`2083c983`).
|
||||
- Tolerate missing Windows device IDs in settings updates while retaining full-precision inode checks and strict matching when both device IDs are available (`d3af582b`).
|
||||
|
||||
#### Workflow guidance and catalog
|
||||
|
||||
- Filter epic sync issues by label (`3033436d`).
|
||||
- Remove instructions to auto-merge dependency bumps and synchronize localized merge authority (`22d7ed51`, `678c6dea`).
|
||||
- Keep common naming and Boolean guidance language-neutral (`072e4684`, `a0ecb793`, `013ed0a8`).
|
||||
- Distinguish the `prp-pr` command alias (`cc91c24f`).
|
||||
- Correct Rails skill discovery, invoice tax calculation order, and framework documentation (`b6ddd13a`).
|
||||
- Remove Serply and Squish catalog entries (`c4904e3f`).
|
||||
|
||||
#### Dependency security
|
||||
|
||||
- Update `lru` to 0.18.2 for RUSTSEC-2026-0253 (`4fc950c4`).
|
||||
- Update `js-yaml` to 4.3.2 for GHSA-2883-xcg3-v3hh (`549c1469`).
|
||||
|
||||
## 2.2.0 - 2026-08-25
|
||||
|
||||
|
||||
@@ -43,8 +43,8 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/affaan-m/ECC/stargazers"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.ecc.tools%2Fbadge%2Fstars&style=flat" alt="Stars" /></a>
|
||||
<a href="https://github.com/affaan-m/ECC/network/members"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.ecc.tools%2Fbadge%2Fforks&style=flat" alt="Forks" /></a>
|
||||
<a href="https://github.com/affaan-m/ECC"><img src="https://img.shields.io/github/stars/affaan-m/ECC?style=flat" alt="GitHub stars" /></a>
|
||||
<a href="https://github.com/affaan-m/ECC/forks"><img src="https://img.shields.io/github/forks/affaan-m/ECC?style=flat" alt="GitHub forks" /></a>
|
||||
<a href="https://github.com/affaan-m/ECC/graphs/contributors"><img src="https://img.shields.io/github/contributors/affaan-m/ECC?style=flat" alt="Contributors" /></a>
|
||||
<a href="https://github.com/marketplace/ecc-tools"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.ecc.tools%2Fbadge%2Finstalls&logo=github" alt="GitHub App installs" /></a>
|
||||
</p>
|
||||
@@ -110,11 +110,13 @@ Use the [guided setup](#install-ecc) or [native plugin commands](#claude-code-de
|
||||
<p align="center" aria-label="Partners and sponsors">
|
||||
<a href="https://www.coderabbit.ai" title="CodeRabbit"><img src="assets/images/sponsors/coderabbit.png" height="54" alt="CodeRabbit" /></a>
|
||||
<a href="https://www.greptile.com/go/ecc" title="Greptile"><img src="assets/images/sponsors/greptile.png" height="54" alt="Greptile" /></a>
|
||||
<a href="https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=ECC" title="Atlas Cloud"><picture><source media="(prefers-color-scheme: dark)" srcset="assets/images/sponsors/atlascloud-dark.svg" /><img src="assets/images/sponsors/atlascloud.svg" width="154" alt="Atlas Cloud" /></picture></a>
|
||||
<a href="https://platform.kimi.ai?aff=ecc" title="Moonshot AI - Kimi"><picture><source media="(prefers-color-scheme: dark)" srcset="assets/images/sponsors/moonshot-dark.png" /><img src="assets/images/sponsors/moonshot.png" width="132" alt="Moonshot AI - Kimi" /></picture></a>
|
||||
<a href="https://compute.itomarkets.com" title="Itô Markets"><picture><source media="(prefers-color-scheme: light)" srcset="assets/images/sponsors/ito-transparent-light.png" /><img src="assets/images/sponsors/ito-transparent.png" width="96" alt="Itô Markets" /></picture></a>
|
||||
<a href="https://compute.itomarkets.com" title="Itô Markets"><picture><source media="(prefers-color-scheme: light)" srcset="assets/images/sponsors/ito-transparent-light.png" /><img src="assets/images/sponsors/ito-transparent.png" width="96" alt="Itô Markets" /></picture></a>
|
||||
<a href="https://serpapi.com/github-ecc" title="SerpApi: Web Search API"><picture><source media="(prefers-color-scheme: dark)" srcset="assets/images/sponsors/serpapi-logo-dark-mode.svg" /><img src="assets/images/sponsors/serpapi-logo-light-mode.svg" width="200" alt="SerpApi: Web Search API" /></picture></a>
|
||||
</p>
|
||||
|
||||
<sub><strong>Past sponsors:</strong> <a href="https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=ECC">Atlas Cloud</a></sub>
|
||||
|
||||
<sub><strong>Community sponsors:</strong> <a href="https://github.com/mikejmorgan-ai">Mike Morgan</a> · <a href="https://github.com/jasonwu513">@jasonwu513</a> · <a href="https://github.com/1anter">@1anter</a> · <a href="https://github.com/massimotodaro">@massimotodaro</a> · <a href="https://github.com/meadmccabe">@meadmccabe</a></sub>
|
||||
|
||||
<sub><a href="https://github.com/sponsors/affaan-m"><strong>Become a Sponsor</strong></a> · <a href="SPONSORS.md">Sponsor Tiers</a> · <a href="SPONSORING.md">Sponsorship Program</a></sub>
|
||||
@@ -151,8 +153,8 @@ Access to 69 agents, 293 skills, and 94 legacy command shims, plus hooks, rules,
|
||||
<p align="center">
|
||||
<a href="https://www.star-history.com/affaan-m/ecc">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="assets/star-history-dark.svg" />
|
||||
<img src="assets/star-history-light.svg" alt="ECC star history: first 40,000 stars, January 18 to February 7, 2026" width="100%" />
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=affaan-m/ECC&type=Date&theme=dark" />
|
||||
<img src="https://api.star-history.com/svg?repos=affaan-m/ECC&type=Date" alt="Live star history chart for affaan-m/ECC" width="100%" />
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
@@ -169,7 +171,7 @@ Access to 69 agents, 293 skills, and 94 legacy command shims, plus hooks, rules,
|
||||
For Claude Code plugin setup, updates, scope changes, and hook-profile changes:
|
||||
|
||||
```bash
|
||||
npx ecc-universal@2.2.1 setup
|
||||
npx ecc-universal@2.2.2 setup
|
||||
```
|
||||
|
||||
If npm reports a version or cache error, confirm the registry version before retrying:
|
||||
@@ -182,12 +184,12 @@ ECC 2.2 supports the same guided setup through modern package runners:
|
||||
|
||||
| Package runner | Guided setup command |
|
||||
|---|---|
|
||||
| npm / npx | `npx ecc-universal@2.2.1 setup` |
|
||||
| pnpm | `pnpm dlx ecc-universal@2.2.1 setup` |
|
||||
| Yarn 2+ | `yarn dlx ecc-universal@2.2.1 setup` |
|
||||
| Bun | `bunx ecc-universal@2.2.1 setup` |
|
||||
| npm / npx | `npx ecc-universal@2.2.2 setup` |
|
||||
| pnpm | `pnpm dlx ecc-universal@2.2.2 setup` |
|
||||
| Yarn 2+ | `yarn dlx ecc-universal@2.2.2 setup` |
|
||||
| Bun | `bunx ecc-universal@2.2.2 setup` |
|
||||
|
||||
The examples select [the published ECC 2.2.1 release](https://www.npmjs.com/package/ecc-universal/v/2.2.1), matching this repository's release version. A version pin is not a security audit or an integrity check. Review the release source and registry integrity before running package code; use a reviewed checkout for unreleased changes.
|
||||
The examples select [the published ECC 2.2.2 release](https://www.npmjs.com/package/ecc-universal/v/2.2.2), matching this repository's release version. A version pin is not a security audit or an integrity check. Review the release source and registry integrity before running package code; use a reviewed checkout for unreleased changes.
|
||||
|
||||
Yarn Classic 1 does not provide `yarn dlx`; use `npx`, install the package globally, or upgrade Yarn for a temporary one-shot run.
|
||||
|
||||
@@ -196,7 +198,7 @@ The wizard inventories the official marketplace and every native Claude install
|
||||
To configure more than one coding agent in one reviewed flow, use the multi-harness wizard:
|
||||
|
||||
```bash
|
||||
npx ecc-universal@2.2.1 install --guided
|
||||
npx ecc-universal@2.2.2 install --guided
|
||||
```
|
||||
|
||||
It lets you select any combination of Claude Code, Codex, and Kimi Code, shows each install channel and destination, preflights every selection before the first write, and asks for one final confirmation.
|
||||
@@ -210,7 +212,7 @@ It lets you select any combination of Claude Code, Codex, and Kimi Code, shows e
|
||||
For automation, make every provider-specific choice explicit:
|
||||
|
||||
```bash
|
||||
npx ecc-universal@2.2.1 install --guided \
|
||||
npx ecc-universal@2.2.2 install --guided \
|
||||
--harness claude --harness codex --harness kimi \
|
||||
--claude-scope local --claude-hooks standard \
|
||||
--profile core --yes
|
||||
@@ -219,16 +221,16 @@ npx ecc-universal@2.2.1 install --guided \
|
||||
Verify the native guided Codex path and managed Kimi path without writing first:
|
||||
|
||||
```bash
|
||||
npx ecc-universal@2.2.1 install --guided --harness codex --dry-run
|
||||
npx ecc-universal@2.2.1 install --profile core --target kimi --dry-run
|
||||
npx ecc-universal@2.2.2 install --guided --harness codex --dry-run
|
||||
npx ecc-universal@2.2.2 install --profile core --target kimi --dry-run
|
||||
```
|
||||
|
||||
Additional package-name commands are also available through the 2.2 alias:
|
||||
|
||||
```bash
|
||||
npx ecc-universal@2.2.1 consult "security reviews" --target claude
|
||||
npx ecc-universal@2.2.1 install --profile minimal --target claude --with capability:machine-learning
|
||||
npx ecc-universal@2.2.1 doctor --target kimi
|
||||
npx ecc-universal@2.2.2 consult "security reviews" --target claude
|
||||
npx ecc-universal@2.2.2 install --profile minimal --target claude --with capability:machine-learning
|
||||
npx ecc-universal@2.2.2 doctor --target kimi
|
||||
```
|
||||
|
||||
Do not use `npx ecc-install --profile minimal --target claude`: `ecc-install` is a binary name inside `ecc-universal`, not a separately published npm package.
|
||||
@@ -400,7 +402,7 @@ Deep per-harness notes (feature parity, hook adapters, limitations) live in [Pla
|
||||
Use this when you want ECC's rules, agents, commands, platform config, and core workflows without runtime hooks:
|
||||
|
||||
```bash
|
||||
npx ecc-universal@2.2.1 install --profile minimal --target claude
|
||||
npx ecc-universal@2.2.2 install --profile minimal --target claude
|
||||
```
|
||||
|
||||
From a source checkout, the equivalent command is:
|
||||
@@ -585,11 +587,11 @@ If you installed from the universal package, run these commands from the same
|
||||
project directory used for installation:
|
||||
|
||||
```bash
|
||||
npx ecc-universal@2.2.1 list-installed
|
||||
npx ecc-universal@2.2.1 doctor
|
||||
npx ecc-universal@2.2.1 repair
|
||||
npx ecc-universal@2.2.1 uninstall --dry-run
|
||||
npx ecc-universal@2.2.1 uninstall
|
||||
npx ecc-universal@2.2.2 list-installed
|
||||
npx ecc-universal@2.2.2 doctor
|
||||
npx ecc-universal@2.2.2 repair
|
||||
npx ecc-universal@2.2.2 uninstall --dry-run
|
||||
npx ecc-universal@2.2.2 uninstall
|
||||
```
|
||||
|
||||
From a source checkout, inspect the managed state before reinstalling:
|
||||
@@ -778,7 +780,7 @@ The `ito-compute-cli` package is currently unpublished. Build it locally from th
|
||||
|
||||
## What's New
|
||||
|
||||
Current release: **2.2.1** (2026-08-31). Highlights of the 2.2 line:
|
||||
Current release: **2.2.2** (2026-08-31). Highlights of the 2.2 line:
|
||||
|
||||
- Guided, manifest-driven setup across Claude Code, Codex, and Kimi Code, with install-state ownership, doctor, repair, and uninstall.
|
||||
- Native Antigravity install, a thin Pi adapter, and the packed-artifact release gate tested on Linux, macOS, and Windows.
|
||||
@@ -1195,7 +1197,7 @@ ECC's Memory Vault gives Claude, Codex, Hermes, OpenClaw, Kimi, and other harnes
|
||||
Skill-only, minimal, manual, and Claude plugin installs do not put the Memory Vault runtime on `PATH`. Install the npm runtime separately before using the CLI or optional MCP server:
|
||||
|
||||
```bash
|
||||
npm install -g ecc-universal@2.2.1
|
||||
npm install -g ecc-universal@2.2.2
|
||||
ecc memory init --scope project
|
||||
ecc memory search "authentication migration" --target-harness codex
|
||||
ecc memory doctor
|
||||
@@ -1590,7 +1592,7 @@ opencode
|
||||
|
||||
**Option 2: Install as npm package**
|
||||
```bash
|
||||
npm install ecc-universal@2.2.1
|
||||
npm install ecc-universal@2.2.2
|
||||
```
|
||||
|
||||
Then add to your `opencode.json`:
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@
|
||||
|
||||
## 最新动态
|
||||
|
||||
### v2.2.1 — 引导式多 Harness 安装(2026年8月)
|
||||
### v2.2.2 — 引导式多 Harness 安装(2026年8月)
|
||||
|
||||
新增可审查的 Claude Code、Codex 与 Kimi Code 多 Harness 安装流程,并提供同步的 npm 命令入口。
|
||||
|
||||
|
||||
+7
-1
@@ -12,14 +12,20 @@ Thank you to everyone funding ECC's open-source work. Your sponsorship is what l
|
||||
|---------|------|-------|
|
||||
| [**CodeRabbit**](https://www.coderabbit.ai) | <img src="assets/images/sponsors/coderabbit.png" width="60" alt="CodeRabbit logo" /> | 2026 |
|
||||
| [**Greptile**](https://www.greptile.com/go/ecc) | <img src="assets/images/sponsors/greptile.png" width="60" alt="Greptile logo" /> | 2026 |
|
||||
| [**Atlas Cloud**](https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=ECC) | <picture><source media="(prefers-color-scheme: dark)" srcset="assets/images/sponsors/atlascloud-dark.svg" /><img src="assets/images/sponsors/atlascloud.svg" width="120" alt="Atlas Cloud logo" /></picture> | 2026 |
|
||||
| [**Moonshot AI (Kimi)**](https://www.moonshot.ai) | <picture><source media="(prefers-color-scheme: dark)" srcset="assets/images/sponsors/moonshot-dark.png" /><img src="assets/images/sponsors/moonshot.png" width="100" alt="Moonshot AI Kimi logo" /></picture> | 2026 |
|
||||
| [**Itô**](https://compute.itomarkets.com) | <picture><source media="(prefers-color-scheme: light)" srcset="assets/images/sponsors/ito-transparent-light.png" /><img src="assets/images/sponsors/ito-transparent.png" width="88" alt="Itô Markets logo" /></picture> | 2026 |
|
||||
| [**SerpApi**](https://serpapi.com/github-ecc) | <picture><source media="(prefers-color-scheme: dark)" srcset="assets/images/sponsors/serpapi-logo-dark-mode.svg" /><img src="assets/images/sponsors/serpapi-logo-light-mode.svg" width="200" alt="SerpApi: Web Search API" /></picture> | 2026 |
|
||||
|
||||
*[Become a Business sponsor](https://github.com/sponsors/affaan-m) to get README sponsor placement + SPONSORS.md listing. Current Business tier is $800/mo. No seats, SLA, custom development, or preferential technical placement is bundled unless separately agreed.*
|
||||
|
||||
Run or self-host any open-source model. Itô partners with ECC on compute, while ECC remains provider-agnostic and any GPU provider works. The [Itô dashboard](https://compute.itomarkets.com) sponsorship link is passive: it does not invoke an RFQ, reserve capacity, provision compute, or configure serving. Separately, the opt-in `ecc ito find` bridge invokes the explicitly configured canonical Itô CLI and submits a live authenticated RFQ; it does not reserve capacity. Managed inference through Itô is not live yet.
|
||||
|
||||
## Past Sponsors
|
||||
|
||||
| Sponsor | Active period |
|
||||
|---------|---------------|
|
||||
| [**Atlas Cloud**](https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=ECC) | 2026 |
|
||||
|
||||
## Team Sponsors — $200/mo
|
||||
|
||||
| Sponsor | Since |
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
spec_version: "0.1.0"
|
||||
name: ecc
|
||||
version: 2.2.1
|
||||
version: 2.2.2
|
||||
description: "Initial gitagent export surface for ECC's shared skill catalog, governance, and identity. Native agents, commands, and hooks remain authoritative in the repository while manifest coverage expands."
|
||||
author: affaan-m
|
||||
license: MIT
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.8 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.5 KiB |
@@ -1,30 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 400" width="800" height="400" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif">
|
||||
<rect width="800" height="400" fill="#0d1117"/>
|
||||
<line x1="70" y1="348.0" x2="772" y2="348.0" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="60" y="352.0" fill="#8b949e" font-size="12" text-anchor="end">0</text>
|
||||
<line x1="70" y1="284.4" x2="772" y2="284.4" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="60" y="288.4" fill="#8b949e" font-size="12" text-anchor="end">10k</text>
|
||||
<line x1="70" y1="220.8" x2="772" y2="220.8" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="60" y="224.8" fill="#8b949e" font-size="12" text-anchor="end">20k</text>
|
||||
<line x1="70" y1="157.2" x2="772" y2="157.2" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="60" y="161.2" fill="#8b949e" font-size="12" text-anchor="end">30k</text>
|
||||
<line x1="70" y1="93.6" x2="772" y2="93.6" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="60" y="97.6" fill="#8b949e" font-size="12" text-anchor="end">40k</text>
|
||||
<line x1="70" y1="30.0" x2="772" y2="30.0" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="60" y="34.0" fill="#8b949e" font-size="12" text-anchor="end">50k</text>
|
||||
<line x1="70.0" y1="30" x2="70.0" y2="348" stroke="#30363d" stroke-width="1" stroke-dasharray="2 4"/>
|
||||
<text x="70.0" y="370" fill="#8b949e" font-size="12" text-anchor="middle">Jan 18</text>
|
||||
<line x1="238.7" y1="30" x2="238.7" y2="348" stroke="#30363d" stroke-width="1" stroke-dasharray="2 4"/>
|
||||
<text x="238.7" y="370" fill="#8b949e" font-size="12" text-anchor="middle">Jan 23</text>
|
||||
<line x1="407.3" y1="30" x2="407.3" y2="348" stroke="#30363d" stroke-width="1" stroke-dasharray="2 4"/>
|
||||
<text x="407.3" y="370" fill="#8b949e" font-size="12" text-anchor="middle">Jan 28</text>
|
||||
<line x1="576.0" y1="30" x2="576.0" y2="348" stroke="#30363d" stroke-width="1" stroke-dasharray="2 4"/>
|
||||
<text x="576.0" y="370" fill="#8b949e" font-size="12" text-anchor="middle">Feb 2</text>
|
||||
<line x1="744.7" y1="30" x2="744.7" y2="348" stroke="#30363d" stroke-width="1" stroke-dasharray="2 4"/>
|
||||
<text x="744.7" y="370" fill="#8b949e" font-size="12" text-anchor="middle">Feb 7</text>
|
||||
<polygon points="70,348 70.0,348.0 123.9,332.7 137.9,327.6 149.4,322.6 171.9,307.3 177.3,302.2 187.8,292.0 192.2,286.9 198.6,281.8 212.6,266.6 222.0,256.4 226.3,251.3 234.5,246.2 239.6,241.1 244.2,236.1 247.5,231.0 251.4,225.9 274.2,210.6 282.2,205.5 290.0,200.4 300.7,195.4 312.3,190.3 320.2,185.2 355.4,164.8 373.7,159.7 409.0,149.6 430.4,144.5 458.7,139.4 492.0,134.3 526.5,129.2 559.4,124.1 618.6,113.9 685.6,103.8 718.9,98.7 772.0,93.6 772,348" fill="#2ea04326"/>
|
||||
<polyline points="70.0,348.0 123.9,332.7 137.9,327.6 149.4,322.6 171.9,307.3 177.3,302.2 187.8,292.0 192.2,286.9 198.6,281.8 212.6,266.6 222.0,256.4 226.3,251.3 234.5,246.2 239.6,241.1 244.2,236.1 247.5,231.0 251.4,225.9 274.2,210.6 282.2,205.5 290.0,200.4 300.7,195.4 312.3,190.3 320.2,185.2 355.4,164.8 373.7,159.7 409.0,149.6 430.4,144.5 458.7,139.4 492.0,134.3 526.5,129.2 559.4,124.1 618.6,113.9 685.6,103.8 718.9,98.7 772.0,93.6" fill="none" stroke="#2ea043" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="772.0" cy="93.6" r="4" fill="#2ea043"/>
|
||||
<text x="400.0" y="20" fill="#e6edf3" font-size="14" font-weight="600" text-anchor="middle">affaan-m/ECC · first 40,000 stars</text>
|
||||
<text x="70" y="390" fill="#8b949e" font-size="11">Jan 18, 2026 – Feb 7, 2026 · source: GitHub stargazers API</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -1,30 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 400" width="800" height="400" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif">
|
||||
<rect width="800" height="400" fill="#ffffff"/>
|
||||
<line x1="70" y1="348.0" x2="772" y2="348.0" stroke="#d8dee4" stroke-width="1"/>
|
||||
<text x="60" y="352.0" fill="#59636e" font-size="12" text-anchor="end">0</text>
|
||||
<line x1="70" y1="284.4" x2="772" y2="284.4" stroke="#d8dee4" stroke-width="1"/>
|
||||
<text x="60" y="288.4" fill="#59636e" font-size="12" text-anchor="end">10k</text>
|
||||
<line x1="70" y1="220.8" x2="772" y2="220.8" stroke="#d8dee4" stroke-width="1"/>
|
||||
<text x="60" y="224.8" fill="#59636e" font-size="12" text-anchor="end">20k</text>
|
||||
<line x1="70" y1="157.2" x2="772" y2="157.2" stroke="#d8dee4" stroke-width="1"/>
|
||||
<text x="60" y="161.2" fill="#59636e" font-size="12" text-anchor="end">30k</text>
|
||||
<line x1="70" y1="93.6" x2="772" y2="93.6" stroke="#d8dee4" stroke-width="1"/>
|
||||
<text x="60" y="97.6" fill="#59636e" font-size="12" text-anchor="end">40k</text>
|
||||
<line x1="70" y1="30.0" x2="772" y2="30.0" stroke="#d8dee4" stroke-width="1"/>
|
||||
<text x="60" y="34.0" fill="#59636e" font-size="12" text-anchor="end">50k</text>
|
||||
<line x1="70.0" y1="30" x2="70.0" y2="348" stroke="#d8dee4" stroke-width="1" stroke-dasharray="2 4"/>
|
||||
<text x="70.0" y="370" fill="#59636e" font-size="12" text-anchor="middle">Jan 18</text>
|
||||
<line x1="238.7" y1="30" x2="238.7" y2="348" stroke="#d8dee4" stroke-width="1" stroke-dasharray="2 4"/>
|
||||
<text x="238.7" y="370" fill="#59636e" font-size="12" text-anchor="middle">Jan 23</text>
|
||||
<line x1="407.3" y1="30" x2="407.3" y2="348" stroke="#d8dee4" stroke-width="1" stroke-dasharray="2 4"/>
|
||||
<text x="407.3" y="370" fill="#59636e" font-size="12" text-anchor="middle">Jan 28</text>
|
||||
<line x1="576.0" y1="30" x2="576.0" y2="348" stroke="#d8dee4" stroke-width="1" stroke-dasharray="2 4"/>
|
||||
<text x="576.0" y="370" fill="#59636e" font-size="12" text-anchor="middle">Feb 2</text>
|
||||
<line x1="744.7" y1="30" x2="744.7" y2="348" stroke="#d8dee4" stroke-width="1" stroke-dasharray="2 4"/>
|
||||
<text x="744.7" y="370" fill="#59636e" font-size="12" text-anchor="middle">Feb 7</text>
|
||||
<polygon points="70,348 70.0,348.0 123.9,332.7 137.9,327.6 149.4,322.6 171.9,307.3 177.3,302.2 187.8,292.0 192.2,286.9 198.6,281.8 212.6,266.6 222.0,256.4 226.3,251.3 234.5,246.2 239.6,241.1 244.2,236.1 247.5,231.0 251.4,225.9 274.2,210.6 282.2,205.5 290.0,200.4 300.7,195.4 312.3,190.3 320.2,185.2 355.4,164.8 373.7,159.7 409.0,149.6 430.4,144.5 458.7,139.4 492.0,134.3 526.5,129.2 559.4,124.1 618.6,113.9 685.6,103.8 718.9,98.7 772.0,93.6 772,348" fill="#1a7f3720"/>
|
||||
<polyline points="70.0,348.0 123.9,332.7 137.9,327.6 149.4,322.6 171.9,307.3 177.3,302.2 187.8,292.0 192.2,286.9 198.6,281.8 212.6,266.6 222.0,256.4 226.3,251.3 234.5,246.2 239.6,241.1 244.2,236.1 247.5,231.0 251.4,225.9 274.2,210.6 282.2,205.5 290.0,200.4 300.7,195.4 312.3,190.3 320.2,185.2 355.4,164.8 373.7,159.7 409.0,149.6 430.4,144.5 458.7,139.4 492.0,134.3 526.5,129.2 559.4,124.1 618.6,113.9 685.6,103.8 718.9,98.7 772.0,93.6" fill="none" stroke="#1a7f37" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="772.0" cy="93.6" r="4" fill="#1a7f37"/>
|
||||
<text x="400.0" y="20" fill="#1f2328" font-size="14" font-weight="600" text-anchor="middle">affaan-m/ECC · first 40,000 stars</text>
|
||||
<text x="70" y="390" fill="#59636e" font-size="11">Jan 18, 2026 – Feb 7, 2026 · source: GitHub stargazers API</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,26 @@
|
||||
# ECC and the Ito desk
|
||||
|
||||
ECC is the public agentic-engineering toolkit; the Ito desk is Affaan's
|
||||
private ops system. The connection surface in this repo is the set of
|
||||
public `ito-*` skills (`skills/ito-baskets`, `skills/ito-compute`,
|
||||
`skills/ito-inference`, `skills/ito-training`). Each of them is a thin
|
||||
pointer: it names the supported boundary and hands real work to the
|
||||
separately installed canonical CLI or MCP server. ECC itself implements no
|
||||
compute booking, inference serving, training stack or basket trading, and
|
||||
nothing here may claim those capabilities exist inside this repo.
|
||||
|
||||
Desk-side work that touches ECC runs as bounded lane tasks. The lane-worker
|
||||
doctrine (see `docs/LANE-RULES.md`) is: one worker, one task, one branch,
|
||||
one PR or one receipt; real work only, meaning code edits, tests, commits
|
||||
and a PR, with the final message as the receipt; no self-review loops, no
|
||||
receipt ledgers, no merging to main, no publishing, no deployments, no
|
||||
messages; blocked means naming exactly who or what unblocks. The doctrine
|
||||
exists because unbounded agent loops were the dominant failure mode of the
|
||||
desk's earlier automation.
|
||||
|
||||
The merge rule for anything desk-related in this repo: fixes and tests
|
||||
merge freely. Anything that adds a third-party tool, a vendor-named skill,
|
||||
or an external link waits for Affaan's explicit yes, recorded before merge.
|
||||
The living desk plan is `docs/PLAN.md` in `Ito-Markets/ito-desk`; task
|
||||
schemas and the spec book live under `docs/spec/` in the same repo. This
|
||||
file only describes the relationship; the plan repo is the source of truth.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Lane rules
|
||||
|
||||
These are the working rules for bounded lane workers (human or agent) that
|
||||
execute tasks against this repository from the Ito workstream system. They
|
||||
are copied verbatim from the lane registry
|
||||
(`lanes/RULES.md` in the Ito workstream system on the ops mini,
|
||||
2026-09-16) so a worker reading only this repo sees the same contract.
|
||||
One task, one branch, one PR or one receipt, then stop.
|
||||
|
||||
---
|
||||
|
||||
## Lane rules (every codex exec brief starts by reading this)
|
||||
You are one bounded worker. One task, one branch, one PR or one receipt, then stop.
|
||||
- Real work only: edit code, run the tests, commit, push, open the PR. No receipts about receipts, no independent review of your own output, no hashing manifests, no ledgers, no acceptance JSONs, no skill self-patching. Your final message is the receipt (under 300 words: what changed, PR link, test command and result, what is blocked and on whom).
|
||||
- Never merge to main, never publish to npm, never deploy, never send email or messages, never change Hermes profiles or launchd on the mini unless the brief says so explicitly.
|
||||
- Commits: plain messages, no Co-Authored-By or generated-with trailers, no em dashes anywhere.
|
||||
- Worktrees and caches go under ~/GitHub/ECC-worktrees or ~/GitHub on the Pro, /Volumes/Agent-Runtime/workspaces on the mini, never on the mini root disk.
|
||||
- If blocked (missing credential, approval needed, conflicting work), stop and say exactly what is needed. Do not wait, poll, or sleep.
|
||||
- Time box: finish in one pass. Do not spawn subagents.
|
||||
@@ -703,7 +703,7 @@ Suggested payload:
|
||||
"skippedModules": []
|
||||
},
|
||||
"source": {
|
||||
"repoVersion": "2.2.1",
|
||||
"repoVersion": "2.2.2",
|
||||
"repoCommit": "git-sha",
|
||||
"manifestVersion": 1
|
||||
},
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||

|
||||
|
||||
[](https://github.com/affaan-m/ECC/stargazers)
|
||||
[](https://github.com/affaan-m/ECC/network/members)
|
||||
[](https://github.com/affaan-m/ECC)
|
||||
[](https://github.com/affaan-m/ECC/forks)
|
||||
[](https://github.com/affaan-m/ECC/graphs/contributors)
|
||||
[](https://www.npmjs.com/package/ecc-universal)
|
||||
[](https://www.npmjs.com/package/ecc-agentshield)
|
||||
|
||||
+7
-7
@@ -50,13 +50,13 @@ Este es un **plugin de IA para codificación listo para producción** que propor
|
||||
## Orquestación de Agentes
|
||||
|
||||
Usa agentes proactivamente sin prompt del usuario:
|
||||
- Solicitudes de features complejas → **planner**
|
||||
- Código recién escrito/modificado → **code-reviewer**
|
||||
- Corrección de bug o nueva feature → **tdd-guide**
|
||||
- Decisión arquitectónica → **architect**
|
||||
- Código sensible a la seguridad → **security-reviewer**
|
||||
- Bucles autónomos / monitoreo de bucles → **loop-operator**
|
||||
- Confiabilidad y costo de la configuración del harness → **harness-optimizer**
|
||||
- Solicitudes de features complejas → **ecc:planner**
|
||||
- Código recién escrito/modificado → **ecc:code-reviewer**
|
||||
- Corrección de bug o nueva feature → **ecc:tdd-guide**
|
||||
- Decisión arquitectónica → **ecc:architect**
|
||||
- Código sensible a la seguridad → **ecc:security-reviewer**
|
||||
- Bucles autónomos / monitoreo de bucles → **ecc:loop-operator**
|
||||
- Confiabilidad y costo de la configuración del harness → **ecc:harness-optimizer**
|
||||
|
||||
Usa ejecución paralela para operaciones independientes — lanza múltiples agentes simultáneamente.
|
||||
|
||||
|
||||
+3
-3
@@ -4,13 +4,13 @@
|
||||
|
||||

|
||||
|
||||
[](https://github.com/affaan-m/ECC/stargazers)
|
||||
[](https://github.com/affaan-m/ECC/network/members)
|
||||
[](https://github.com/affaan-m/ECC)
|
||||
[](https://github.com/affaan-m/ECC/forks)
|
||||
[](https://github.com/affaan-m/ECC/graphs/contributors)
|
||||
[](https://www.npmjs.com/package/ecc-universal)
|
||||
[](https://www.npmjs.com/package/ecc-agentshield)
|
||||
[](https://github.com/marketplace/ecc-tools)
|
||||
[](LICENSE)
|
||||
[](../../LICENSE)
|
||||

|
||||

|
||||

|
||||
|
||||
@@ -2,29 +2,36 @@
|
||||
|
||||
## Agentes Disponibles
|
||||
|
||||
Ubicados en `~/.claude/agents/`:
|
||||
Los agentes de ECC se distribuyen con el plugin `ecc@ecc`, no en `~/.claude/agents/`.
|
||||
Se invocan a través de la herramienta Agent con un `subagent_type` con ámbito de plugin:
|
||||
|
||||
```text
|
||||
Agent(subagent_type: "ecc:planner", prompt: "...")
|
||||
```
|
||||
|
||||
| Agente | Propósito | Cuándo Usar |
|
||||
|--------|-----------|-------------|
|
||||
| planner | Planificación de implementación | Features complejas, refactoring |
|
||||
| architect | Diseño de sistemas | Decisiones arquitectónicas |
|
||||
| tdd-guide | Desarrollo guiado por pruebas | Nuevas features, corrección de bugs |
|
||||
| code-reviewer | Revisión de código | Después de escribir código |
|
||||
| security-reviewer | Análisis de seguridad | Antes de los commits |
|
||||
| build-error-resolver | Corrección de errores de build | Cuando el build falla |
|
||||
| e2e-runner | Testing E2E | Flujos de usuario críticos |
|
||||
| refactor-cleaner | Limpieza de código muerto | Mantenimiento de código |
|
||||
| doc-updater | Documentación | Actualización de docs |
|
||||
| rust-reviewer | Revisión de código Rust | Proyectos Rust |
|
||||
| harmonyos-app-resolver | Desarrollo de apps HarmonyOS | Proyectos HarmonyOS/ArkTS |
|
||||
| ecc:planner | Planificación de implementación | Features complejas, refactoring |
|
||||
| ecc:architect | Diseño de sistemas | Decisiones arquitectónicas |
|
||||
| ecc:tdd-guide | Desarrollo guiado por pruebas | Nuevas features, corrección de bugs |
|
||||
| ecc:code-reviewer | Revisión de código | Después de escribir código |
|
||||
| ecc:security-reviewer | Análisis de seguridad | Antes de los commits |
|
||||
| ecc:build-error-resolver | Corrección de errores de build | Cuando el build falla |
|
||||
| ecc:e2e-runner | Testing E2E | Flujos de usuario críticos |
|
||||
| ecc:refactor-cleaner | Limpieza de código muerto | Mantenimiento de código |
|
||||
| ecc:doc-updater | Documentación | Actualización de docs |
|
||||
| ecc:rust-reviewer | Revisión de código Rust | Proyectos Rust |
|
||||
| ecc:harmonyos-app-resolver | Desarrollo de apps HarmonyOS | Proyectos HarmonyOS/ArkTS |
|
||||
|
||||
Para el roster completo de 68 agentes, ver `/ecc:ecc-guide`.
|
||||
|
||||
## Uso Inmediato de Agentes
|
||||
|
||||
Sin necesidad de prompt del usuario:
|
||||
1. Solicitudes de features complejas - Usar el agente **planner**
|
||||
2. Código recién escrito/modificado - Usar el agente **code-reviewer**
|
||||
3. Corrección de bug o nueva feature - Usar el agente **tdd-guide**
|
||||
4. Decisión arquitectónica - Usar el agente **architect**
|
||||
1. Solicitudes de features complejas - Usar el agente **ecc:planner**
|
||||
2. Código recién escrito/modificado - Usar el agente **ecc:code-reviewer**
|
||||
3. Corrección de bug o nueva feature - Usar el agente **ecc:tdd-guide**
|
||||
4. Decisión arquitectónica - Usar el agente **ecc:architect**
|
||||
|
||||
## Ejecución Paralela de Tareas
|
||||
|
||||
|
||||
@@ -50,13 +50,13 @@
|
||||
## エージェントオーケストレーション
|
||||
|
||||
ユーザーのプロンプトなしで積極的にエージェントを使用する:
|
||||
- 複雑な機能リクエスト → **planner**
|
||||
- コードの作成/変更直後 → **code-reviewer**
|
||||
- バグ修正または新機能 → **tdd-guide**
|
||||
- アーキテクチャの意思決定 → **architect**
|
||||
- セキュリティに関わるコード → **security-reviewer**
|
||||
- 自律ループ / ループ監視 → **loop-operator**
|
||||
- ハーネス設定の信頼性とコスト → **harness-optimizer**
|
||||
- 複雑な機能リクエスト → **ecc:planner**
|
||||
- コードの作成/変更直後 → **ecc:code-reviewer**
|
||||
- バグ修正または新機能 → **ecc:tdd-guide**
|
||||
- アーキテクチャの意思決定 → **ecc:architect**
|
||||
- セキュリティに関わるコード → **ecc:security-reviewer**
|
||||
- 自律ループ / ループ監視 → **ecc:loop-operator**
|
||||
- ハーネス設定の信頼性とコスト → **ecc:harness-optimizer**
|
||||
|
||||
独立した操作には並列実行を使用する — 複数のエージェントを同時に起動する。
|
||||
|
||||
|
||||
+1868
-653
File diff suppressed because it is too large
Load Diff
@@ -2,27 +2,34 @@
|
||||
|
||||
## 利用可能な Agent
|
||||
|
||||
`~/.claude/agents/` に配置:
|
||||
ECC の Agent は `ecc@ecc` プラグインに同梱されており、`~/.claude/agents/` には配置されません。
|
||||
Agent ツールではプラグインスコープの `subagent_type` で呼び出します:
|
||||
|
||||
```text
|
||||
Agent(subagent_type: "ecc:planner", prompt: "...")
|
||||
```
|
||||
|
||||
| Agent | 目的 | 使用タイミング |
|
||||
|-------|---------|-------------|
|
||||
| planner | 実装計画 | 複雑な機能、リファクタリング |
|
||||
| architect | システム設計 | アーキテクチャの意思決定 |
|
||||
| tdd-guide | テスト駆動開発 | 新機能、バグ修正 |
|
||||
| code-reviewer | コードレビュー | コード記述後 |
|
||||
| security-reviewer | セキュリティ分析 | コミット前 |
|
||||
| build-error-resolver | ビルドエラー修正 | ビルド失敗時 |
|
||||
| e2e-runner | E2Eテスト | 重要なユーザーフロー |
|
||||
| refactor-cleaner | デッドコードクリーンアップ | コードメンテナンス |
|
||||
| doc-updater | ドキュメント | ドキュメント更新 |
|
||||
| ecc:planner | 実装計画 | 複雑な機能、リファクタリング |
|
||||
| ecc:architect | システム設計 | アーキテクチャの意思決定 |
|
||||
| ecc:tdd-guide | テスト駆動開発 | 新機能、バグ修正 |
|
||||
| ecc:code-reviewer | コードレビュー | コード記述後 |
|
||||
| ecc:security-reviewer | セキュリティ分析 | コミット前 |
|
||||
| ecc:build-error-resolver | ビルドエラー修正 | ビルド失敗時 |
|
||||
| ecc:e2e-runner | E2Eテスト | 重要なユーザーフロー |
|
||||
| ecc:refactor-cleaner | デッドコードクリーンアップ | コードメンテナンス |
|
||||
| ecc:doc-updater | ドキュメント | ドキュメント更新 |
|
||||
|
||||
全 68 Agent の一覧は `/ecc:ecc-guide` を参照。
|
||||
|
||||
## Agent の即座の使用
|
||||
|
||||
ユーザープロンプト不要:
|
||||
1. 複雑な機能リクエスト - **planner** agent を使用
|
||||
2. コード作成/変更直後 - **code-reviewer** agent を使用
|
||||
3. バグ修正または新機能 - **tdd-guide** agent を使用
|
||||
4. アーキテクチャの意思決定 - **architect** agent を使用
|
||||
1. 複雑な機能リクエスト - **ecc:planner** agent を使用
|
||||
2. コード作成/変更直後 - **ecc:code-reviewer** agent を使用
|
||||
3. バグ修正または新機能 - **ecc:tdd-guide** agent を使用
|
||||
4. アーキテクチャの意思決定 - **ecc:architect** agent を使用
|
||||
|
||||
## 並列タスク実行
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ Este repositório contém apenas o código. Os guias explicam tudo.
|
||||
|
||||
## O Que Há de Novo
|
||||
|
||||
### v2.2.1 — Instalação Guiada para Múltiplos Harnesses (Ago 2026)
|
||||
### v2.2.2 — Instalação Guiada para Múltiplos Harnesses (Ago 2026)
|
||||
|
||||
Adiciona uma instalação revisável para Claude Code, Codex e Kimi Code, com uma entrada de comando npm sincronizada.
|
||||
|
||||
|
||||
+9
-9
@@ -2,7 +2,7 @@
|
||||
|
||||
Bu, yazılım geliştirme için 69 özel agent, 293 skill, 94 command ve otomatik hook iş akışları sağlayan **üretime hazır bir AI kodlama eklentisidir**.
|
||||
|
||||
**Sürüm:** 2.2.1
|
||||
**Sürüm:** 2.2.2
|
||||
|
||||
## Temel İlkeler
|
||||
|
||||
@@ -47,14 +47,14 @@ Bu, yazılım geliştirme için 69 özel agent, 293 skill, 94 command ve otomati
|
||||
## Agent Orkestrasyonu
|
||||
|
||||
Agentları kullanıcı istemi olmadan proaktif olarak kullanın:
|
||||
- Karmaşık özellik istekleri → **planner**
|
||||
- Yeni yazılan/değiştirilen kod → **code-reviewer**
|
||||
- Hata düzeltme veya yeni özellik → **tdd-guide**
|
||||
- Mimari karar → **architect**
|
||||
- Güvenlik açısından hassas kod → **security-reviewer**
|
||||
- Çok kanallı iletişim önceliklendirme → **chief-of-staff**
|
||||
- Otonom döngüler / döngü izleme → **loop-operator**
|
||||
- Harness yapılandırma güvenilirliği ve maliyeti → **harness-optimizer**
|
||||
- Karmaşık özellik istekleri → **ecc:planner**
|
||||
- Yeni yazılan/değiştirilen kod → **ecc:code-reviewer**
|
||||
- Hata düzeltme veya yeni özellik → **ecc:tdd-guide**
|
||||
- Mimari karar → **ecc:architect**
|
||||
- Güvenlik açısından hassas kod → **ecc:security-reviewer**
|
||||
- Çok kanallı iletişim önceliklendirme → **ecc:chief-of-staff**
|
||||
- Otonom döngüler / döngü izleme → **ecc:loop-operator**
|
||||
- Harness yapılandırma güvenilirliği ve maliyeti → **ecc:harness-optimizer**
|
||||
|
||||
Bağımsız işlemler için paralel yürütme kullanın — birden fazla agenti aynı anda başlatın.
|
||||
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ Bu repository yalnızca ham kodu içerir. Rehberler her şeyi açıklıyor.
|
||||
|
||||
## Yenilikler
|
||||
|
||||
### v2.2.1 — Rehberli Çoklu Harness Kurulumu (Ağu 2026)
|
||||
### v2.2.2 — Rehberli Çoklu Harness Kurulumu (Ağu 2026)
|
||||
|
||||
Claude Code, Codex ve Kimi Code için incelenebilir çoklu harness kurulumu ve eşitlenmiş npm komut girişi eklendi.
|
||||
|
||||
|
||||
@@ -2,28 +2,35 @@
|
||||
|
||||
## Mevcut Agent'lar
|
||||
|
||||
`~/.claude/agents/` dizininde bulunur:
|
||||
ECC agent'ları `ecc@ecc` eklentisiyle birlikte gelir, `~/.claude/agents/` dizininde bulunmaz.
|
||||
Agent aracıyla eklenti kapsamlı bir `subagent_type` ile çağrılır:
|
||||
|
||||
```text
|
||||
Agent(subagent_type: "ecc:planner", prompt: "...")
|
||||
```
|
||||
|
||||
| Agent | Amaç | Ne Zaman Kullanılır |
|
||||
|-------|---------|-------------|
|
||||
| planner | Uygulama planlaması | Karmaşık özellikler, refactoring |
|
||||
| architect | Sistem tasarımı | Mimari kararlar |
|
||||
| tdd-guide | Test odaklı geliştirme | Yeni özellikler, hata düzeltmeleri |
|
||||
| code-reviewer | Kod incelemesi | Kod yazdıktan sonra |
|
||||
| security-reviewer | Güvenlik analizi | Commit'lerden önce |
|
||||
| build-error-resolver | Build hatalarını düzeltme | Build başarısız olduğunda |
|
||||
| e2e-runner | E2E testleri | Kritik kullanıcı akışları |
|
||||
| refactor-cleaner | Ölü kod temizliği | Kod bakımı |
|
||||
| doc-updater | Dokümantasyon | Dokümanları güncelleme |
|
||||
| rust-reviewer | Rust kod incelemesi | Rust projeleri |
|
||||
| ecc:planner | Uygulama planlaması | Karmaşık özellikler, refactoring |
|
||||
| ecc:architect | Sistem tasarımı | Mimari kararlar |
|
||||
| ecc:tdd-guide | Test odaklı geliştirme | Yeni özellikler, hata düzeltmeleri |
|
||||
| ecc:code-reviewer | Kod incelemesi | Kod yazdıktan sonra |
|
||||
| ecc:security-reviewer | Güvenlik analizi | Commit'lerden önce |
|
||||
| ecc:build-error-resolver | Build hatalarını düzeltme | Build başarısız olduğunda |
|
||||
| ecc:e2e-runner | E2E testleri | Kritik kullanıcı akışları |
|
||||
| ecc:refactor-cleaner | Ölü kod temizliği | Kod bakımı |
|
||||
| ecc:doc-updater | Dokümantasyon | Dokümanları güncelleme |
|
||||
| ecc:rust-reviewer | Rust kod incelemesi | Rust projeleri |
|
||||
|
||||
68 agent'ın tam listesi için `/ecc:ecc-guide` bölümüne bakın.
|
||||
|
||||
## Anlık Agent Kullanımı
|
||||
|
||||
Kullanıcı istemi gerekmez:
|
||||
1. Karmaşık özellik istekleri - **planner** agent kullan
|
||||
2. Kod yeni yazıldı/değiştirildi - **code-reviewer** agent kullan
|
||||
3. Hata düzeltmesi veya yeni özellik - **tdd-guide** agent kullan
|
||||
4. Mimari karar - **architect** agent kullan
|
||||
1. Karmaşık özellik istekleri - **ecc:planner** agent kullan
|
||||
2. Kod yeni yazıldı/değiştirildi - **ecc:code-reviewer** agent kullan
|
||||
3. Hata düzeltmesi veya yeni özellik - **ecc:tdd-guide** agent kullan
|
||||
4. Mimari karar - **ecc:architect** agent kullan
|
||||
|
||||
## Paralel Görev Yürütme
|
||||
|
||||
|
||||
@@ -420,7 +420,7 @@ affoon:~ ctx:65% Opus 4.5 19:52
|
||||
- [Interactive Mode](https://code.claude.com/docs/en/interactive-mode)
|
||||
- [Memory Sistemi](https://code.claude.com/docs/en/memory)
|
||||
- [Subagent'lar](https://code.claude.com/docs/en/sub-agents)
|
||||
- [MCP Genel Bakış](https://code.claude.com/docs/en/mcp-overview)
|
||||
- [MCP Genel Bakış](https://code.claude.com/docs/en/mcp)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/affaan-m/ECC/stargazers"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.ecc.tools%2Fbadge%2Fstars&style=flat" alt="Stars" /></a>
|
||||
<a href="https://github.com/affaan-m/ECC/network/members"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.ecc.tools%2Fbadge%2Fforks&style=flat" alt="Forks" /></a>
|
||||
<a href="https://github.com/affaan-m/ECC"><img src="https://img.shields.io/github/stars/affaan-m/ECC?style=flat" alt="GitHub stars" /></a>
|
||||
<a href="https://github.com/affaan-m/ECC/forks"><img src="https://img.shields.io/github/forks/affaan-m/ECC?style=flat" alt="GitHub forks" /></a>
|
||||
<a href="https://github.com/affaan-m/ECC/graphs/contributors"><img src="https://img.shields.io/github/contributors/affaan-m/ECC?style=flat" alt="Contributors" /></a>
|
||||
<a href="https://github.com/marketplace/ecc-tools"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.ecc.tools%2Fbadge%2Finstalls&logo=github" alt="GitHub App installs" /></a>
|
||||
</p>
|
||||
@@ -106,7 +106,8 @@
|
||||
<a href="https://www.greptile.com/go/ecc" title="Greptile"><img src="../../assets/images/sponsors/greptile.png" height="54" alt="Greptile" /></a>
|
||||
<a href="https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=ECC" title="Atlas Cloud"><picture><source media="(prefers-color-scheme: dark)" srcset="../../assets/images/sponsors/atlascloud-dark.svg" /><img src="../../assets/images/sponsors/atlascloud.svg" width="154" alt="Atlas Cloud" /></picture></a>
|
||||
<a href="https://www.moonshot.ai" title="Moonshot AI - Kimi"><picture><source media="(prefers-color-scheme: dark)" srcset="../../assets/images/sponsors/moonshot-dark.png" /><img src="../../assets/images/sponsors/moonshot.png" width="132" alt="Moonshot AI - Kimi" /></picture></a>
|
||||
<a href="https://compute.itomarkets.com" title="Itô Markets"><picture><source media="(prefers-color-scheme: light)" srcset="../../assets/images/sponsors/ito-transparent-light.png" /><img src="../../assets/images/sponsors/ito-transparent.png" width="96" alt="Itô Markets" /></picture></a>
|
||||
<a href="https://compute.itomarkets.com" title="Itô Markets"><picture><source media="(prefers-color-scheme: light)" srcset="../../assets/images/sponsors/ito-transparent-light.png" /><img src="../../assets/images/sponsors/ito-transparent.png" width="96" alt="Itô Markets" /></picture></a>
|
||||
<a href="https://serpapi.com/github-ecc" title="SerpApi: Web Search API"><picture><source media="(prefers-color-scheme: dark)" srcset="../../assets/images/sponsors/serpapi-logo-dark-mode.svg" /><img src="../../assets/images/sponsors/serpapi-logo-light-mode.svg" width="200" alt="SerpApi: Web Search API" /></picture></a>
|
||||
</p>
|
||||
|
||||
<sub><strong>Спонсори спільноти:</strong> <a href="https://github.com/mikejmorgan-ai">Mike Morgan</a> · <a href="https://github.com/jasonwu513">@jasonwu513</a> · <a href="https://github.com/1anter">@1anter</a> · <a href="https://github.com/massimotodaro">@massimotodaro</a> · <a href="https://github.com/meadmccabe">@meadmccabe</a></sub>
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@
|
||||
|
||||

|
||||
|
||||
[](https://github.com/affaan-m/ECC/stargazers)
|
||||
[](https://github.com/affaan-m/ECC/network/members)
|
||||
[](https://github.com/affaan-m/ECC)
|
||||
[](https://github.com/affaan-m/ECC/forks)
|
||||
[](https://github.com/affaan-m/ECC/graphs/contributors)
|
||||
[](https://www.npmjs.com/package/ecc-universal)
|
||||
[](https://www.npmjs.com/package/ecc-agentshield)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 69 个专业代理、293 项技能、94 条命令以及自动化钩子工作流,用于软件开发。
|
||||
|
||||
**版本:** 2.2.1
|
||||
**版本:** 2.2.2
|
||||
|
||||
## 核心原则
|
||||
|
||||
@@ -48,14 +48,14 @@
|
||||
|
||||
主动使用智能体,无需用户提示:
|
||||
|
||||
* 复杂功能请求 → **planner**
|
||||
* 刚编写/修改的代码 → **code-reviewer**
|
||||
* 错误修复或新功能 → **tdd-guide**
|
||||
* 架构决策 → **architect**
|
||||
* 安全敏感代码 → **security-reviewer**
|
||||
* 多渠道沟通分流 → **chief-of-staff**
|
||||
* 自主循环 / 循环监控 → **loop-operator**
|
||||
* 线束配置可靠性及成本 → **harness-optimizer**
|
||||
* 复杂功能请求 → **ecc:planner**
|
||||
* 刚编写/修改的代码 → **ecc:code-reviewer**
|
||||
* 错误修复或新功能 → **ecc:tdd-guide**
|
||||
* 架构决策 → **ecc:architect**
|
||||
* 安全敏感代码 → **ecc:security-reviewer**
|
||||
* 多渠道沟通分流 → **ecc:chief-of-staff**
|
||||
* 自主循环 / 循环监控 → **ecc:loop-operator**
|
||||
* 线束配置可靠性及成本 → **ecc:harness-optimizer**
|
||||
|
||||
对于独立操作使用并行执行 — 同时启动多个智能体。
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
## 最新动态
|
||||
|
||||
### v2.2.1 — 引导式多 Harness 安装(2026年8月)
|
||||
### v2.2.2 — 引导式多 Harness 安装(2026年8月)
|
||||
|
||||
新增可审查的 Claude Code、Codex 与 Kimi Code 多 Harness 安装流程,并提供同步的 npm 命令入口。
|
||||
|
||||
@@ -1292,7 +1292,7 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
|
||||
| **上下文文件** | CLAUDE.md + AGENTS.md | AGENTS.md | AGENTS.md | AGENTS.md |
|
||||
| **秘密检测** | 基于钩子 | beforeSubmitPrompt 钩子 | 基于沙箱 | 基于钩子 |
|
||||
| **自动格式化** | PostToolUse 钩子 | afterFileEdit 钩子 | N/A | file.edited 钩子 |
|
||||
| **版本** | 插件 | 插件 | 参考配置 | 2.2.1 |
|
||||
| **版本** | 插件 | 插件 | 参考配置 | 2.2.2 |
|
||||
|
||||
**关键架构决策:**
|
||||
|
||||
|
||||
@@ -2,29 +2,36 @@
|
||||
|
||||
## 可用智能体
|
||||
|
||||
位于 `~/.claude/agents/` 中:
|
||||
ECC 智能体随 `ecc@ecc` 插件一起分发,不在 `~/.claude/agents/` 目录中。
|
||||
它们通过 Agent 工具以插件作用域的 `subagent_type` 调用:
|
||||
|
||||
```text
|
||||
Agent(subagent_type: "ecc:planner", prompt: "...")
|
||||
```
|
||||
|
||||
| 代理 | 用途 | 使用时机 |
|
||||
|-------|---------|-------------|
|
||||
| planner | 实现规划 | 复杂功能、重构 |
|
||||
| architect | 系统设计 | 架构决策 |
|
||||
| tdd-guide | 测试驱动开发 | 新功能、错误修复 |
|
||||
| code-reviewer | 代码审查 | 编写代码后 |
|
||||
| security-reviewer | 安全分析 | 提交前 |
|
||||
| build-error-resolver | 修复构建错误 | 构建失败时 |
|
||||
| e2e-runner | 端到端测试 | 关键用户流程 |
|
||||
| refactor-cleaner | 清理死代码 | 代码维护 |
|
||||
| doc-updater | 文档 | 更新文档 |
|
||||
| rust-reviewer | Rust 代码审查 | Rust 项目 |
|
||||
| ecc:planner | 实现规划 | 复杂功能、重构 |
|
||||
| ecc:architect | 系统设计 | 架构决策 |
|
||||
| ecc:tdd-guide | 测试驱动开发 | 新功能、错误修复 |
|
||||
| ecc:code-reviewer | 代码审查 | 编写代码后 |
|
||||
| ecc:security-reviewer | 安全分析 | 提交前 |
|
||||
| ecc:build-error-resolver | 修复构建错误 | 构建失败时 |
|
||||
| ecc:e2e-runner | 端到端测试 | 关键用户流程 |
|
||||
| ecc:refactor-cleaner | 清理死代码 | 代码维护 |
|
||||
| ecc:doc-updater | 文档 | 更新文档 |
|
||||
| ecc:rust-reviewer | Rust 代码审查 | Rust 项目 |
|
||||
|
||||
完整 68 个智能体的清单参见 `/ecc:ecc-guide`。
|
||||
|
||||
## 即时智能体使用
|
||||
|
||||
无需用户提示:
|
||||
|
||||
1. 复杂的功能请求 - 使用 **planner** 智能体
|
||||
2. 刚编写/修改的代码 - 使用 **code-reviewer** 智能体
|
||||
3. 错误修复或新功能 - 使用 **tdd-guide** 智能体
|
||||
4. 架构决策 - 使用 **architect** 智能体
|
||||
1. 复杂的功能请求 - 使用 **ecc:planner** 智能体
|
||||
2. 刚编写/修改的代码 - 使用 **ecc:code-reviewer** 智能体
|
||||
3. 错误修复或新功能 - 使用 **ecc:tdd-guide** 智能体
|
||||
4. 架构决策 - 使用 **ecc:architect** 智能体
|
||||
|
||||
## 并行任务执行
|
||||
|
||||
|
||||
@@ -421,7 +421,7 @@ affoon:~ ctx:65% Opus 4.5 19:52
|
||||
* [交互模式](https://code.claude.com/docs/en/interactive-mode)
|
||||
* [记忆系统](https://code.claude.com/docs/en/memory)
|
||||
* [子代理](https://code.claude.com/docs/en/sub-agents)
|
||||
* [MCP 概述](https://code.claude.com/docs/en/mcp-overview)
|
||||
* [MCP 概述](https://code.claude.com/docs/en/mcp)
|
||||
|
||||
***
|
||||
|
||||
|
||||
Generated
+8
-8
@@ -2375,9 +2375,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.1.4+spec-1.1.0"
|
||||
version = "1.1.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
|
||||
checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
@@ -2528,9 +2528,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "3.4.0"
|
||||
version = "3.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d"
|
||||
checksum = "af5546be8f5378d5414f83733f5c9a2526f4645829edbc1c41790aeef1b38e8b"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"cookie_store",
|
||||
@@ -2548,9 +2548,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ureq-proto"
|
||||
version = "0.6.1"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613"
|
||||
checksum = "5b0809a01d1ca5a51ca70db32bb2a19157582a526505ef3c19e3b343a59aa5ad"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"http",
|
||||
@@ -2590,9 +2590,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.26.0"
|
||||
version = "1.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
|
||||
checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
|
||||
dependencies = [
|
||||
"atomic",
|
||||
"getrandom 0.4.2",
|
||||
|
||||
@@ -14,6 +14,12 @@ It is usable as an alpha for local experimentation, but it is **not** the finish
|
||||
- worktree-aware session scaffolding
|
||||
- basic multi-session state and output tracking
|
||||
|
||||
Dashboard output is hydrated from SQLite at startup and after recovery, then
|
||||
synchronized with a monotonic database cursor. Because session runners are
|
||||
separate processes, the database remains the cross-process source of truth
|
||||
while steady-state refreshes read only the rows appended since the previous
|
||||
dashboard tick.
|
||||
|
||||
## What This Is For
|
||||
|
||||
ECC 2.0 is the layer above individual harness installs.
|
||||
|
||||
@@ -5,6 +5,8 @@ use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
pub const OUTPUT_BUFFER_LIMIT: usize = 1000;
|
||||
/// Maximum number of cross-process output rows applied during one dashboard refresh.
|
||||
pub const OUTPUT_DELTA_BATCH_LIMIT: usize = 4096;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum OutputStream {
|
||||
@@ -113,16 +115,6 @@ impl SessionOutputStore {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn replace_lines(&self, session_id: &str, lines: Vec<OutputLine>) {
|
||||
let mut buffer: VecDeque<OutputLine> = lines.into_iter().collect();
|
||||
|
||||
while buffer.len() > self.capacity {
|
||||
let _ = buffer.pop_front();
|
||||
}
|
||||
|
||||
self.lock_buffers().insert(session_id.to_string(), buffer);
|
||||
}
|
||||
|
||||
pub fn lines(&self, session_id: &str) -> Vec<OutputLine> {
|
||||
self.lock_buffers()
|
||||
.get(session_id)
|
||||
|
||||
@@ -28,6 +28,31 @@ pub struct StateStore {
|
||||
conn: Connection,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct SessionOutputRecord {
|
||||
pub id: i64,
|
||||
pub session_id: String,
|
||||
pub line: OutputLine,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct SessionOutputBatch {
|
||||
pub cursor: i64,
|
||||
pub records: Vec<SessionOutputRecord>,
|
||||
}
|
||||
|
||||
/// Converts one persisted output row into the dashboard's typed record.
|
||||
fn output_record_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<SessionOutputRecord> {
|
||||
let stream: String = row.get(2)?;
|
||||
let text: String = row.get(3)?;
|
||||
let timestamp: String = row.get(4)?;
|
||||
Ok(SessionOutputRecord {
|
||||
id: row.get(0)?,
|
||||
session_id: row.get(1)?,
|
||||
line: OutputLine::new(OutputStream::from_db_value(&stream), text, timestamp),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct HarnessAuditEntry {
|
||||
pub id: i64,
|
||||
@@ -4000,6 +4025,53 @@ impl StateStore {
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
/// Returns a bounded recent-output snapshot and its highest persisted row ID.
|
||||
pub(crate) fn get_output_snapshot(
|
||||
&self,
|
||||
limit_per_session: usize,
|
||||
) -> Result<SessionOutputBatch> {
|
||||
let limit_per_session = i64::try_from(limit_per_session.max(1)).unwrap_or(i64::MAX);
|
||||
let mut stmt = self.conn.prepare(
|
||||
"SELECT id, session_id, stream, line, timestamp
|
||||
FROM (
|
||||
SELECT id, session_id, stream, line, timestamp,
|
||||
ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY id DESC) AS row_num
|
||||
FROM session_output
|
||||
)
|
||||
WHERE row_num <= ?1
|
||||
ORDER BY id ASC",
|
||||
)?;
|
||||
let records = stmt
|
||||
.query_map(rusqlite::params![limit_per_session], output_record_from_row)?
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let cursor = records.last().map(|record| record.id).unwrap_or(0);
|
||||
|
||||
Ok(SessionOutputBatch { cursor, records })
|
||||
}
|
||||
|
||||
/// Returns at most `limit` output rows newer than `cursor` in insertion order.
|
||||
pub(crate) fn get_output_since(
|
||||
&self,
|
||||
cursor: i64,
|
||||
limit: usize,
|
||||
) -> Result<SessionOutputBatch> {
|
||||
let cursor = cursor.max(0);
|
||||
let limit = i64::try_from(limit.max(1)).unwrap_or(i64::MAX);
|
||||
let mut stmt = self.conn.prepare(
|
||||
"SELECT id, session_id, stream, line, timestamp
|
||||
FROM session_output
|
||||
WHERE id > ?1
|
||||
ORDER BY id ASC
|
||||
LIMIT ?2",
|
||||
)?;
|
||||
let records = stmt
|
||||
.query_map(rusqlite::params![cursor, limit], output_record_from_row)?
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let cursor = records.last().map(|record| record.id).unwrap_or(cursor);
|
||||
|
||||
Ok(SessionOutputBatch { cursor, records })
|
||||
}
|
||||
|
||||
pub fn insert_tool_log(
|
||||
&self,
|
||||
session_id: &str,
|
||||
@@ -7382,6 +7454,69 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_cursor_reads_a_bounded_snapshot_then_only_new_rows() -> Result<()> {
|
||||
let tempdir = TestDir::new("store-output-cursor")?;
|
||||
let db = StateStore::open(&tempdir.path().join("state.db"))?;
|
||||
|
||||
db.insert_session(&build_session("session-1", SessionState::Running))?;
|
||||
db.insert_session(&build_session("session-2", SessionState::Running))?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "one-a")?;
|
||||
db.append_output_line("session-2", OutputStream::Stderr, "two-a")?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "one-b")?;
|
||||
db.append_output_line("session-2", OutputStream::Stdout, "two-b")?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "one-c")?;
|
||||
|
||||
let snapshot = db.get_output_snapshot(2)?;
|
||||
assert_eq!(snapshot.cursor, 5);
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.records
|
||||
.iter()
|
||||
.map(|record| (record.session_id.as_str(), record.line.text.as_str()))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
("session-2", "two-a"),
|
||||
("session-1", "one-b"),
|
||||
("session-2", "two-b"),
|
||||
("session-1", "one-c"),
|
||||
]
|
||||
);
|
||||
|
||||
db.append_output_line("session-2", OutputStream::Stderr, "two-c")?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "one-d")?;
|
||||
let delta = db.get_output_since(snapshot.cursor, 1)?;
|
||||
assert_eq!(delta.cursor, 6);
|
||||
assert_eq!(delta.records.len(), 1);
|
||||
assert_eq!(delta.records[0].session_id, "session-2");
|
||||
assert_eq!(delta.records[0].line.text, "two-c");
|
||||
|
||||
let next = db.get_output_since(delta.cursor, 1)?;
|
||||
assert_eq!(next.cursor, 7);
|
||||
assert_eq!(next.records.len(), 1);
|
||||
assert_eq!(next.records[0].session_id, "session-1");
|
||||
assert_eq!(next.records[0].line.text, "one-d");
|
||||
|
||||
let empty = db.get_output_since(next.cursor, 1)?;
|
||||
assert_eq!(empty.cursor, next.cursor);
|
||||
assert!(empty.records.is_empty());
|
||||
|
||||
let query_plan = db
|
||||
.conn
|
||||
.prepare(
|
||||
"EXPLAIN QUERY PLAN SELECT id FROM session_output WHERE id > ?1 ORDER BY id ASC",
|
||||
)?
|
||||
.query_map(rusqlite::params![snapshot.cursor], |row| {
|
||||
row.get::<_, String>(3)
|
||||
})?
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
assert!(query_plan
|
||||
.iter()
|
||||
.any(|detail| detail.contains("INTEGER PRIMARY KEY") && detail.contains("rowid>?")));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_round_trip_tracks_unread_counts_and_read_state() -> Result<()> {
|
||||
let tempdir = TestDir::new("store-messages")?;
|
||||
|
||||
+343
-48
@@ -10,7 +10,6 @@ use ratatui::{
|
||||
use regex::Regex;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
|
||||
use std::time::UNIX_EPOCH;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use super::widgets::{budget_state, format_currency, format_token_count, BudgetState, TokenMeter};
|
||||
use crate::comms;
|
||||
@@ -19,12 +18,12 @@ use crate::notifications::{DesktopNotifier, NotificationEvent, WebhookNotifier};
|
||||
use crate::observability::ToolLogEntry;
|
||||
use crate::session::manager;
|
||||
use crate::session::output::{
|
||||
OutputEvent, OutputLine, OutputStream, SessionOutputStore, OUTPUT_BUFFER_LIMIT,
|
||||
OutputLine, OutputStream, OUTPUT_BUFFER_LIMIT, OUTPUT_DELTA_BATCH_LIMIT,
|
||||
};
|
||||
use crate::session::store::{DaemonActivity, FileActivityOverlap, StateStore};
|
||||
use crate::session::store::{DaemonActivity, FileActivityOverlap, SessionOutputRecord, StateStore};
|
||||
use crate::session::{
|
||||
ContextObservationPriority, DecisionLogEntry, FileActivityEntry, Session, SessionGrouping,
|
||||
SessionBoardMeta, SessionHarnessInfo, SessionMessage, SessionState,
|
||||
ContextObservationPriority, DecisionLogEntry, FileActivityEntry, Session, SessionBoardMeta,
|
||||
SessionGrouping, SessionHarnessInfo, SessionMessage, SessionState,
|
||||
};
|
||||
use crate::worktree;
|
||||
|
||||
@@ -79,16 +78,42 @@ struct TestRunSummary {
|
||||
passed: usize,
|
||||
}
|
||||
|
||||
/// Consumes an output cache and returns a new bounded cache with `records` appended.
|
||||
fn append_output_records(
|
||||
mut cache: HashMap<String, Vec<OutputLine>>,
|
||||
records: Vec<SessionOutputRecord>,
|
||||
) -> HashMap<String, Vec<OutputLine>> {
|
||||
let mut touched_sessions = HashSet::new();
|
||||
for record in records {
|
||||
cache
|
||||
.entry(record.session_id.clone())
|
||||
.or_default()
|
||||
.push(record.line);
|
||||
touched_sessions.insert(record.session_id);
|
||||
}
|
||||
|
||||
for session_id in touched_sessions {
|
||||
if let Some(lines) = cache.get_mut(&session_id) {
|
||||
let overflow = lines.len().saturating_sub(OUTPUT_BUFFER_LIMIT);
|
||||
if overflow > 0 {
|
||||
lines.drain(..overflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache
|
||||
}
|
||||
|
||||
pub struct Dashboard {
|
||||
db: StateStore,
|
||||
cfg: Config,
|
||||
output_store: SessionOutputStore,
|
||||
output_rx: broadcast::Receiver<OutputEvent>,
|
||||
notifier: DesktopNotifier,
|
||||
webhook_notifier: WebhookNotifier,
|
||||
sessions: Vec<Session>,
|
||||
session_harnesses: HashMap<String, SessionHarnessInfo>,
|
||||
session_output_cache: HashMap<String, Vec<OutputLine>>,
|
||||
session_output_generations: HashMap<String, chrono::DateTime<Utc>>,
|
||||
output_cursor: Option<i64>,
|
||||
unread_message_counts: HashMap<String, usize>,
|
||||
approval_queue_counts: HashMap<String, usize>,
|
||||
approval_queue_preview: Vec<SessionMessage>,
|
||||
@@ -502,15 +527,8 @@ fn load_session_harnesses(
|
||||
}
|
||||
|
||||
impl Dashboard {
|
||||
/// Builds the dashboard and hydrates its initial bounded output snapshot.
|
||||
pub fn new(db: StateStore, cfg: Config) -> Self {
|
||||
Self::with_output_store(db, cfg, SessionOutputStore::default())
|
||||
}
|
||||
|
||||
pub fn with_output_store(
|
||||
db: StateStore,
|
||||
cfg: Config,
|
||||
output_store: SessionOutputStore,
|
||||
) -> Self {
|
||||
let pane_size_percent = configured_pane_size(&cfg, cfg.pane_layout);
|
||||
let initial_cost_metrics_signature = metrics_file_signature(&cfg.cost_metrics_path());
|
||||
let initial_tool_activity_signature =
|
||||
@@ -528,12 +546,15 @@ impl Dashboard {
|
||||
.iter()
|
||||
.map(|session| (session.id.clone(), session.state.clone()))
|
||||
.collect();
|
||||
let session_output_generations = sessions
|
||||
.iter()
|
||||
.map(|session| (session.id.clone(), session.created_at))
|
||||
.collect();
|
||||
let initial_approval_message_id = db
|
||||
.latest_unread_approval_message()
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|message| message.id);
|
||||
let output_rx = output_store.subscribe();
|
||||
let notifier = DesktopNotifier::new(cfg.desktop_notifications.clone());
|
||||
let webhook_notifier = WebhookNotifier::new(cfg.webhook_notifications.clone());
|
||||
let mut session_table_state = TableState::default();
|
||||
@@ -544,13 +565,13 @@ impl Dashboard {
|
||||
let mut dashboard = Self {
|
||||
db,
|
||||
cfg,
|
||||
output_store,
|
||||
output_rx,
|
||||
notifier,
|
||||
webhook_notifier,
|
||||
sessions,
|
||||
session_harnesses,
|
||||
session_output_cache: HashMap::new(),
|
||||
session_output_generations,
|
||||
output_cursor: None,
|
||||
unread_message_counts: HashMap::new(),
|
||||
approval_queue_counts: HashMap::new(),
|
||||
approval_queue_preview: Vec::new(),
|
||||
@@ -624,6 +645,7 @@ impl Dashboard {
|
||||
dashboard.sync_handoff_backlog_counts();
|
||||
dashboard.sync_board_meta();
|
||||
dashboard.sync_global_handoff_backlog();
|
||||
dashboard.sync_output_cache();
|
||||
dashboard.sync_selected_output();
|
||||
dashboard.sync_selected_diff();
|
||||
dashboard.sync_selected_messages();
|
||||
@@ -3211,6 +3233,7 @@ impl Dashboard {
|
||||
));
|
||||
}
|
||||
|
||||
/// Refreshes persisted dashboard state while preserving the output cursor.
|
||||
pub fn refresh(&mut self) {
|
||||
self.sync_from_store();
|
||||
}
|
||||
@@ -3993,15 +4016,6 @@ impl Dashboard {
|
||||
}
|
||||
|
||||
pub async fn tick(&mut self) {
|
||||
loop {
|
||||
match self.output_rx.try_recv() {
|
||||
Ok(_event) => {}
|
||||
Err(broadcast::error::TryRecvError::Empty) => break,
|
||||
Err(broadcast::error::TryRecvError::Lagged(_)) => continue,
|
||||
Err(broadcast::error::TryRecvError::Closed) => break,
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(error) = manager::activate_pending_worktree_sessions(&self.db, &self.cfg).await {
|
||||
tracing::warn!("Failed to activate queued worktree sessions: {error}");
|
||||
}
|
||||
@@ -4073,18 +4087,22 @@ impl Dashboard {
|
||||
)
|
||||
}
|
||||
|
||||
/// Synchronizes dashboard state, deferring output recovery until sessions load.
|
||||
fn sync_from_store(&mut self) {
|
||||
let (heartbeat_enforcement, budget_enforcement, conflict_enforcement) =
|
||||
self.sync_runtime_metrics();
|
||||
let selected_id = self.selected_session_id().map(ToOwned::to_owned);
|
||||
self.sessions = match self.db.list_sessions() {
|
||||
let sessions_refreshed = match self.db.list_sessions() {
|
||||
Ok(mut sessions) => {
|
||||
sort_sessions_for_display(&mut sessions);
|
||||
sessions
|
||||
self.sessions = sessions;
|
||||
true
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!("Failed to refresh sessions: {error}");
|
||||
Vec::new()
|
||||
self.output_cursor = None;
|
||||
self.sessions.clear();
|
||||
false
|
||||
}
|
||||
};
|
||||
self.session_harnesses = load_session_harnesses(&self.db, &self.cfg, &self.sessions);
|
||||
@@ -4103,7 +4121,9 @@ impl Dashboard {
|
||||
self.sync_approval_notifications();
|
||||
self.sync_global_handoff_backlog();
|
||||
self.sync_daemon_activity();
|
||||
self.sync_output_cache();
|
||||
if sessions_refreshed {
|
||||
self.sync_output_cache();
|
||||
}
|
||||
self.sync_selection_by_id(selected_id.as_deref());
|
||||
self.ensure_selected_pane_visible();
|
||||
self.sync_selected_output();
|
||||
@@ -4481,25 +4501,43 @@ impl Dashboard {
|
||||
}
|
||||
|
||||
fn sync_output_cache(&mut self) {
|
||||
let active_session_ids: HashSet<_> = self
|
||||
let active_session_generations: HashMap<_, _> = self
|
||||
.sessions
|
||||
.iter()
|
||||
.map(|session| session.id.as_str())
|
||||
.map(|session| (session.id.clone(), session.created_at))
|
||||
.collect();
|
||||
self.session_output_cache
|
||||
.retain(|session_id, _| active_session_ids.contains(session_id.as_str()));
|
||||
let cached_generations = &self.session_output_generations;
|
||||
self.session_output_cache = std::mem::take(&mut self.session_output_cache)
|
||||
.into_iter()
|
||||
.filter(|(session_id, _)| {
|
||||
active_session_generations.get(session_id) == cached_generations.get(session_id)
|
||||
})
|
||||
.collect();
|
||||
self.session_output_generations = active_session_generations;
|
||||
|
||||
for session in &self.sessions {
|
||||
match self.db.get_output_lines(&session.id, OUTPUT_BUFFER_LIMIT) {
|
||||
Ok(lines) => {
|
||||
self.output_store.replace_lines(&session.id, lines.clone());
|
||||
self.session_output_cache.insert(session.id.clone(), lines);
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!("Failed to load session output for {}: {error}", session.id);
|
||||
}
|
||||
let batch = match self.output_cursor {
|
||||
Some(cursor) => self
|
||||
.db
|
||||
.get_output_since(cursor, OUTPUT_DELTA_BATCH_LIMIT),
|
||||
None => self.db.get_output_snapshot(OUTPUT_BUFFER_LIMIT),
|
||||
};
|
||||
let batch = match batch {
|
||||
Ok(batch) => batch,
|
||||
Err(error) => {
|
||||
tracing::warn!("Failed to refresh session output cache: {error}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if self.output_cursor.is_none() {
|
||||
self.session_output_cache = HashMap::new();
|
||||
}
|
||||
self.output_cursor = Some(batch.cursor);
|
||||
|
||||
self.session_output_cache = append_output_records(
|
||||
std::mem::take(&mut self.session_output_cache),
|
||||
batch.records,
|
||||
);
|
||||
}
|
||||
|
||||
fn ensure_selected_pane_visible(&mut self) {
|
||||
@@ -5212,6 +5250,7 @@ impl Dashboard {
|
||||
.map(|session| session.id.as_str())
|
||||
}
|
||||
|
||||
/// Returns the selected session's currently cached output window.
|
||||
fn selected_output_lines(&self) -> &[OutputLine] {
|
||||
self.selected_session_id()
|
||||
.and_then(|session_id| self.session_output_cache.get(session_id))
|
||||
@@ -13147,6 +13186,260 @@ diff --git a/src/lib.rs b/src/lib.rs
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_cache_appends_rows_written_by_another_process_without_rehydrating() -> Result<()> {
|
||||
let db_path =
|
||||
std::env::temp_dir().join(format!("ecc2-output-cursor-{}.db", Uuid::new_v4()));
|
||||
let db = StateStore::open(&db_path)?;
|
||||
let session = sample_session("session-1", "claude", SessionState::Running, None, 0, 0);
|
||||
db.insert_session(&session)?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "persisted-before-open")?;
|
||||
|
||||
let mut dashboard = Dashboard::new(db, Config::default());
|
||||
assert!(dashboard
|
||||
.selected_output_text()
|
||||
.contains("persisted-before-open"));
|
||||
dashboard
|
||||
.session_output_cache
|
||||
.entry("session-1".to_string())
|
||||
.or_default()
|
||||
.push(test_output_line(OutputStream::Stdout, "cache-only"));
|
||||
|
||||
let child = Command::new(std::env::current_exe()?)
|
||||
.args([
|
||||
"--exact",
|
||||
"tui::dashboard::tests::output_cursor_child_writer",
|
||||
"--ignored",
|
||||
"--nocapture",
|
||||
])
|
||||
.env("ECC2_OUTPUT_CURSOR_CHILD_DB", &db_path)
|
||||
.status()?;
|
||||
assert!(child.success(), "child output writer should succeed");
|
||||
dashboard.refresh();
|
||||
|
||||
let text = dashboard.selected_output_text();
|
||||
assert!(text.contains("persisted-before-open"));
|
||||
assert!(text.contains("cache-only"));
|
||||
assert!(text.contains("persisted-after-open"));
|
||||
|
||||
dashboard.sync_output_cache();
|
||||
assert_eq!(
|
||||
dashboard
|
||||
.selected_output_lines()
|
||||
.iter()
|
||||
.filter(|line| line.text == "persisted-after-open")
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_file(db_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "helper invoked by output cursor cross-process test"]
|
||||
fn output_cursor_child_writer() -> Result<()> {
|
||||
let Some(db_path) = std::env::var_os("ECC2_OUTPUT_CURSOR_CHILD_DB") else {
|
||||
return Ok(());
|
||||
};
|
||||
StateStore::open(Path::new(&db_path))?.append_output_line(
|
||||
"session-1",
|
||||
OutputStream::Stderr,
|
||||
"persisted-after-open",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_cache_rehydrates_after_transient_session_list_failure() -> Result<()> {
|
||||
let db_path =
|
||||
std::env::temp_dir().join(format!("ecc2-output-recovery-{}.db", Uuid::new_v4()));
|
||||
let db = StateStore::open(&db_path)?;
|
||||
let session = sample_session("session-1", "claude", SessionState::Running, None, 0, 0);
|
||||
db.insert_session(&session)?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "persisted-output")?;
|
||||
|
||||
let mut dashboard = Dashboard::new(db, Config::default());
|
||||
assert!(dashboard
|
||||
.selected_output_text()
|
||||
.contains("persisted-output"));
|
||||
dashboard
|
||||
.session_output_cache
|
||||
.entry("session-1".to_string())
|
||||
.or_default()
|
||||
.push(test_output_line(OutputStream::Stdout, "cache-only"));
|
||||
|
||||
let schema = rusqlite::Connection::open(&db_path)?;
|
||||
schema.execute("ALTER TABLE sessions RENAME TO unavailable_sessions", [])?;
|
||||
dashboard.sync_from_store();
|
||||
assert!(dashboard.sessions.is_empty());
|
||||
assert!(dashboard.session_output_cache["session-1"]
|
||||
.iter()
|
||||
.any(|line| line.text == "cache-only"));
|
||||
assert!(dashboard.output_cursor.is_none());
|
||||
|
||||
dashboard.sync_from_store();
|
||||
assert!(dashboard.session_output_cache["session-1"]
|
||||
.iter()
|
||||
.any(|line| line.text == "cache-only"));
|
||||
assert!(dashboard.output_cursor.is_none());
|
||||
|
||||
schema.execute("ALTER TABLE unavailable_sessions RENAME TO sessions", [])?;
|
||||
dashboard.sync_from_store();
|
||||
|
||||
assert_eq!(dashboard.sessions.len(), 1);
|
||||
assert!(dashboard
|
||||
.selected_output_text()
|
||||
.contains("persisted-output"));
|
||||
assert!(!dashboard.selected_output_text().contains("cache-only"));
|
||||
|
||||
let _ = std::fs::remove_file(db_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_cache_tracks_session_add_delete_and_same_id_recreation() -> Result<()> {
|
||||
let db_path =
|
||||
std::env::temp_dir().join(format!("ecc2-output-lifecycle-{}.db", Uuid::new_v4()));
|
||||
let db = StateStore::open(&db_path)?;
|
||||
db.insert_session(&sample_session(
|
||||
"session-1",
|
||||
"claude",
|
||||
SessionState::Running,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
))?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "first-session")?;
|
||||
|
||||
let mut dashboard = Dashboard::new(db, Config::default());
|
||||
let external = StateStore::open(&db_path)?;
|
||||
external.insert_session(&sample_session(
|
||||
"session-2",
|
||||
"codex",
|
||||
SessionState::Running,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
))?;
|
||||
external.append_output_line("session-2", OutputStream::Stderr, "new-session")?;
|
||||
dashboard.sync_from_store();
|
||||
|
||||
assert!(dashboard
|
||||
.sessions
|
||||
.iter()
|
||||
.any(|session| session.id == "session-2"));
|
||||
assert_eq!(
|
||||
dashboard.session_output_cache["session-2"][0].text,
|
||||
"new-session"
|
||||
);
|
||||
|
||||
external.delete_session("session-2")?;
|
||||
let replacement_time = Utc::now() + chrono::Duration::seconds(1);
|
||||
external.insert_session(&Session {
|
||||
created_at: replacement_time,
|
||||
updated_at: replacement_time,
|
||||
last_heartbeat_at: replacement_time,
|
||||
..sample_session("session-2", "codex", SessionState::Running, None, 0, 0)
|
||||
})?;
|
||||
external.append_output_line("session-2", OutputStream::Stdout, "replacement-session")?;
|
||||
dashboard.sync_from_store();
|
||||
|
||||
let replacement = &dashboard.session_output_cache["session-2"];
|
||||
assert_eq!(replacement.len(), 1);
|
||||
assert_eq!(replacement[0].text, "replacement-session");
|
||||
|
||||
let _ = std::fs::remove_file(db_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_cache_retries_delta_after_transient_output_query_failure() -> Result<()> {
|
||||
let db_path =
|
||||
std::env::temp_dir().join(format!("ecc2-output-query-retry-{}.db", Uuid::new_v4()));
|
||||
let db = StateStore::open(&db_path)?;
|
||||
db.insert_session(&sample_session(
|
||||
"session-1",
|
||||
"claude",
|
||||
SessionState::Running,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
))?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "persisted-before")?;
|
||||
|
||||
let mut dashboard = Dashboard::new(db, Config::default());
|
||||
dashboard
|
||||
.session_output_cache
|
||||
.get_mut("session-1")
|
||||
.expect("hydrated output")
|
||||
.push(test_output_line(OutputStream::Stdout, "cache-only"));
|
||||
let cursor = dashboard.output_cursor;
|
||||
|
||||
let schema = rusqlite::Connection::open(&db_path)?;
|
||||
schema.execute(
|
||||
"ALTER TABLE session_output RENAME TO unavailable_session_output",
|
||||
[],
|
||||
)?;
|
||||
dashboard.sync_output_cache();
|
||||
assert_eq!(dashboard.output_cursor, cursor);
|
||||
assert!(dashboard.session_output_cache["session-1"]
|
||||
.iter()
|
||||
.any(|line| line.text == "cache-only"));
|
||||
|
||||
schema.execute(
|
||||
"ALTER TABLE unavailable_session_output RENAME TO session_output",
|
||||
[],
|
||||
)?;
|
||||
StateStore::open(&db_path)?.append_output_line(
|
||||
"session-1",
|
||||
OutputStream::Stderr,
|
||||
"persisted-after",
|
||||
)?;
|
||||
dashboard.sync_output_cache();
|
||||
|
||||
let output = &dashboard.session_output_cache["session-1"];
|
||||
assert!(output.iter().any(|line| line.text == "cache-only"));
|
||||
assert_eq!(
|
||||
output
|
||||
.iter()
|
||||
.filter(|line| line.text == "persisted-after")
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_file(db_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_output_records_bounds_each_session_to_the_latest_window() {
|
||||
let mut cache = HashMap::from([(
|
||||
"session-2".to_string(),
|
||||
vec![test_output_line(OutputStream::Stderr, "other-session")],
|
||||
)]);
|
||||
let records = (0..(OUTPUT_BUFFER_LIMIT + 5))
|
||||
.map(|index| crate::session::store::SessionOutputRecord {
|
||||
id: index as i64 + 1,
|
||||
session_id: "session-1".to_string(),
|
||||
line: test_output_line(OutputStream::Stdout, &format!("line-{index}")),
|
||||
})
|
||||
.collect();
|
||||
|
||||
cache = append_output_records(cache, records);
|
||||
|
||||
let session_lines = cache.get("session-1").expect("session output");
|
||||
assert_eq!(session_lines.len(), OUTPUT_BUFFER_LIMIT);
|
||||
assert_eq!(
|
||||
session_lines.first().map(|line| line.text.as_str()),
|
||||
Some("line-5")
|
||||
);
|
||||
assert_eq!(
|
||||
session_lines.last().map(|line| line.text.as_str()),
|
||||
Some(format!("line-{}", OUTPUT_BUFFER_LIMIT + 4).as_str())
|
||||
);
|
||||
assert_eq!(cache["session-2"][0].text, "other-session");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_search_tracks_matches_and_sets_navigation_note() {
|
||||
let mut dashboard = test_dashboard(
|
||||
@@ -14917,8 +15210,10 @@ diff --git a/src/lib.rs b/src/lib.rs
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let output_store = SessionOutputStore::default();
|
||||
let output_rx = output_store.subscribe();
|
||||
let session_output_generations = sessions
|
||||
.iter()
|
||||
.map(|session| (session.id.clone(), session.created_at))
|
||||
.collect();
|
||||
let mut session_table_state = TableState::default();
|
||||
if !sessions.is_empty() {
|
||||
session_table_state.select(Some(selected_session));
|
||||
@@ -14928,13 +15223,13 @@ diff --git a/src/lib.rs b/src/lib.rs
|
||||
db: StateStore::open(Path::new(":memory:")).expect("open test db"),
|
||||
pane_size_percent: configured_pane_size(&cfg, cfg.pane_layout),
|
||||
cfg,
|
||||
output_store,
|
||||
output_rx,
|
||||
notifier,
|
||||
webhook_notifier,
|
||||
sessions,
|
||||
session_harnesses,
|
||||
session_output_cache: HashMap::new(),
|
||||
session_output_generations,
|
||||
output_cursor: None,
|
||||
unread_message_counts: HashMap::new(),
|
||||
approval_queue_counts: HashMap::new(),
|
||||
approval_queue_preview: Vec::new(),
|
||||
|
||||
+22
-8
@@ -19,7 +19,7 @@ User request → Claude picks a tool → PreToolUse hook runs → Tool executes
|
||||
Memory persistence lifecycle definitions live in `hooks/memory-persistence/`.
|
||||
The executable hook graph remains `hooks/hooks.json`; the memory persistence directory is the stable contract for SessionStart, PreCompact, observation, activity tracking, and SessionEnd behavior.
|
||||
|
||||
Stable hook IDs and descriptions live in `hooks/hooks.metadata.json`, aligned by event and index with `hooks/hooks.json`. Claude Code validates a plugin's `hooks.json` against its own schema and reports any other key (`$schema`, `id`, `description`) as unknown at load time, so `hooks.json` carries only what the harness accepts. ECC's installer, validator, and dashboard merge the sidecar back in through `scripts/lib/hooks-config.js`; `node scripts/ci/validate-hooks.js` fails if the two files drift apart.
|
||||
Stable hook IDs and descriptions live in `hooks/hooks.metadata.json`, aligned by event and index with `hooks/hooks.json`. Claude Code validates a plugin's `hooks.json` against its own schema and reports any other key (`$schema`, `id`, `description`) as unknown at load time, so `hooks.json` carries only what the harness accepts. ECC's installer, validator, and dashboard merge the sidecar back in through `scripts/lib/hooks-config.js`; `node scripts/ci/validate-hooks.js` fails if the two files drift apart, and `node scripts/ci/check-hooks-schema-keys.js` fails if `hooks.json` or `hooks/codex-hooks.json` carry any key outside their loader's documented set.
|
||||
|
||||
Each sidecar entry also carries a `fingerprint` of the matcher entry it describes (matcher plus hook commands), so reordering `hooks.json` without reordering the sidecar, or editing a command without updating the sidecar, is caught rather than silently swapping IDs. When reordering hooks, move the matching sidecar entries first. Then run `node scripts/ci/validate-hooks.js --update-fingerprints` to refresh changed commands and commit both files. The updater rejects known fingerprints at different positions and writes only after validation succeeds.
|
||||
|
||||
@@ -114,6 +114,18 @@ export ECC_HOOK_PROFILE=standard
|
||||
# Disable specific hook IDs (comma-separated)
|
||||
export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,post:edit:typecheck"
|
||||
|
||||
# Lower the hook input cap in bytes (default and maximum: 1048576).
|
||||
# run-with-flags.js adds runner-level fail-closed handling for
|
||||
# pre:edit-write:gateguard-fact-force and pre:mcp-health-check because they
|
||||
# cannot inspect the complete request. Other safety hooks, including the Bash
|
||||
# dispatcher and config protection, retain their own fail-closed behavior.
|
||||
# If a trusted tool call legitimately exceeds the cap, retry with a smaller
|
||||
# input or temporarily set ECC_GATEGUARD=off (or GATEGUARD_DISABLED=1) for
|
||||
# GateGuard, or ECC_MCP_HEALTH_FAIL_OPEN=yes for MCP health, then restore it.
|
||||
# These switches reduce only the named protection while enabled; they do not
|
||||
# bypass the Bash dispatcher or config-protection checks.
|
||||
export ECC_HOOK_INPUT_MAX_BYTES=524288
|
||||
|
||||
# Disable only GateGuard during setup or recovery
|
||||
export ECC_GATEGUARD=off
|
||||
|
||||
@@ -147,7 +159,10 @@ update the plugin and change those preferences.
|
||||
|
||||
### Writing Your Own Hook
|
||||
|
||||
Hooks are shell commands that receive tool input as JSON on stdin and must output JSON on stdout.
|
||||
Hooks are shell commands that receive tool input as JSON on stdin. A hook with
|
||||
no decision or context to return should leave stdout empty. Only explicit hook
|
||||
output, such as a deny decision or `additionalContext`, should be written to
|
||||
stdout; the input payload must not be echoed as a no-op response.
|
||||
|
||||
**Basic structure:**
|
||||
|
||||
@@ -169,8 +184,7 @@ process.stdin.on('end', () => {
|
||||
// Block (PreToolUse only): exit with code 2
|
||||
// process.exit(2);
|
||||
|
||||
// Always output the original data to stdout
|
||||
console.log(data);
|
||||
// No opinion: leave stdout empty.
|
||||
});
|
||||
```
|
||||
|
||||
@@ -221,7 +235,7 @@ Async hooks run in the background. They cannot block tool execution.
|
||||
"matcher": "Edit",
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const ns=i.tool_input?.new_string||'';if(/TODO|FIXME|HACK/.test(ns)){console.error('[Hook] New TODO/FIXME added - consider creating an issue')}console.log(d)})\""
|
||||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const ns=i.tool_input?.new_string||'';if(/TODO|FIXME|HACK/.test(ns)){console.error('[Hook] New TODO/FIXME added - consider creating an issue')}})\""
|
||||
}],
|
||||
"description": "Warn when adding TODO/FIXME comments"
|
||||
}
|
||||
@@ -234,7 +248,7 @@ Async hooks run in the background. They cannot block tool execution.
|
||||
"matcher": "Write",
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const c=i.tool_input?.content||'';const lines=c.split('\\n').length;if(lines>800){console.error('[Hook] BLOCKED: File exceeds 800 lines ('+lines+' lines)');console.error('[Hook] Split into smaller, focused modules');process.exit(2)}console.log(d)})\""
|
||||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const c=i.tool_input?.content||'';const lines=c.split('\\n').length;if(lines>800){console.error('[Hook] BLOCKED: File exceeds 800 lines ('+lines+' lines)');console.error('[Hook] Split into smaller, focused modules');process.exit(2)}})\""
|
||||
}],
|
||||
"description": "Block creation of files larger than 800 lines"
|
||||
}
|
||||
@@ -247,7 +261,7 @@ Async hooks run in the background. They cannot block tool execution.
|
||||
"matcher": "Edit",
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/\\.py$/.test(p)){const{execFileSync}=require('child_process');try{execFileSync('ruff',['format',p],{stdio:'pipe'})}catch(e){}}console.log(d)})\""
|
||||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/\\.py$/.test(p)){const{execFileSync}=require('child_process');try{execFileSync('ruff',['format',p],{stdio:'pipe'})}catch(e){}}})\""
|
||||
}],
|
||||
"description": "Auto-format Python files with ruff after edits"
|
||||
}
|
||||
@@ -260,7 +274,7 @@ Async hooks run in the background. They cannot block tool execution.
|
||||
"matcher": "Write",
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "node -e \"const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/src\\/.*\\.(ts|js)$/.test(p)&&!/\\.test\\.|\\.spec\\./.test(p)){const testPath=p.replace(/\\.(ts|js)$/,'.test.$1');if(!fs.existsSync(testPath)){console.error('[Hook] No test file found for: '+p);console.error('[Hook] Expected: '+testPath);console.error('[Hook] Consider writing tests first (/tdd)')}}console.log(d)})\""
|
||||
"command": "node -e \"const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/src\\/.*\\.(ts|js)$/.test(p)&&!/\\.test\\.|\\.spec\\./.test(p)){const testPath=p.replace(/\\.(ts|js)$/,'.test.$1');if(!fs.existsSync(testPath)){console.error('[Hook] No test file found for: '+p);console.error('[Hook] Expected: '+testPath);console.error('[Hook] Consider writing tests first (/tdd)')}}})\""
|
||||
}],
|
||||
"description": "Remind to create tests when adding new source files"
|
||||
}
|
||||
|
||||
+10
-10
@@ -126,7 +126,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/posttooluse-dispatcher.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.env.ECC_POSTTOOLUSE_PASSTHROUGH='1';process.argv.splice(1,0,s);require(s).cli()\" sync",
|
||||
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/posttooluse-dispatcher.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s).cli()\" sync",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
@@ -136,7 +136,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/posttooluse-dispatcher.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.env.ECC_POSTTOOLUSE_PASSTHROUGH='1';process.argv.splice(1,0,s);require(s).cli()\" async",
|
||||
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/posttooluse-dispatcher.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s).cli()\" async",
|
||||
"async": true,
|
||||
"timeout": 45
|
||||
}
|
||||
@@ -169,7 +169,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:plan-canvas-pending','scripts/hooks/plan-canvas-pending.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\""
|
||||
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const n=process.platform==='win32'&&r.charAt(0)==='/'&&/[a-zA-Z]/.test(r.charAt(1))&&(r.length===2||r.charAt(2)==='/')?r.charAt(1).toUpperCase()+':/'+r.slice(3):r;const s=p.join(n,'scripts/hooks/lifecycle-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=n;if(require('fs').existsSync(s)){process.argv.splice(1,0,s);require(s).cli()}else{process.stderr.write('[Hook] lifecycle bootstrap unavailable; skipping hook'+String.fromCharCode(10))}\" stop:plan-canvas-pending scripts/hooks/plan-canvas-pending.js minimal,standard,strict 30000"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -178,7 +178,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:format-typecheck','scripts/hooks/stop-format-typecheck.js','standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:300000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
|
||||
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const n=process.platform==='win32'&&r.charAt(0)==='/'&&/[a-zA-Z]/.test(r.charAt(1))&&(r.length===2||r.charAt(2)==='/')?r.charAt(1).toUpperCase()+':/'+r.slice(3):r;const s=p.join(n,'scripts/hooks/lifecycle-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=n;if(require('fs').existsSync(s)){process.argv.splice(1,0,s);require(s).cli()}else{process.stderr.write('[Hook] lifecycle bootstrap unavailable; skipping hook'+String.fromCharCode(10))}\" stop:format-typecheck scripts/hooks/stop-format-typecheck.js standard,strict 300000",
|
||||
"timeout": 300
|
||||
}
|
||||
]
|
||||
@@ -188,7 +188,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:check-console-log','scripts/hooks/check-console-log.js','standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\""
|
||||
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const n=process.platform==='win32'&&r.charAt(0)==='/'&&/[a-zA-Z]/.test(r.charAt(1))&&(r.length===2||r.charAt(2)==='/')?r.charAt(1).toUpperCase()+':/'+r.slice(3):r;const s=p.join(n,'scripts/hooks/lifecycle-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=n;if(require('fs').existsSync(s)){process.argv.splice(1,0,s);require(s).cli()}else{process.stderr.write('[Hook] lifecycle bootstrap unavailable; skipping hook'+String.fromCharCode(10))}\" stop:check-console-log scripts/hooks/check-console-log.js standard,strict 30000"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -197,7 +197,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:session-end','scripts/hooks/session-end.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
|
||||
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const n=process.platform==='win32'&&r.charAt(0)==='/'&&/[a-zA-Z]/.test(r.charAt(1))&&(r.length===2||r.charAt(2)==='/')?r.charAt(1).toUpperCase()+':/'+r.slice(3):r;const s=p.join(n,'scripts/hooks/lifecycle-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=n;if(require('fs').existsSync(s)){process.argv.splice(1,0,s);require(s).cli()}else{process.stderr.write('[Hook] lifecycle bootstrap unavailable; skipping hook'+String.fromCharCode(10))}\" stop:session-end scripts/hooks/session-end.js minimal,standard,strict 30000",
|
||||
"async": true,
|
||||
"timeout": 10
|
||||
}
|
||||
@@ -208,7 +208,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:evaluate-session','scripts/hooks/evaluate-session.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
|
||||
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const n=process.platform==='win32'&&r.charAt(0)==='/'&&/[a-zA-Z]/.test(r.charAt(1))&&(r.length===2||r.charAt(2)==='/')?r.charAt(1).toUpperCase()+':/'+r.slice(3):r;const s=p.join(n,'scripts/hooks/lifecycle-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=n;if(require('fs').existsSync(s)){process.argv.splice(1,0,s);require(s).cli()}else{process.stderr.write('[Hook] lifecycle bootstrap unavailable; skipping hook'+String.fromCharCode(10))}\" stop:evaluate-session scripts/hooks/evaluate-session.js minimal,standard,strict 30000",
|
||||
"async": true,
|
||||
"timeout": 10
|
||||
}
|
||||
@@ -219,7 +219,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:cost-tracker','scripts/hooks/cost-tracker.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
|
||||
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const n=process.platform==='win32'&&r.charAt(0)==='/'&&/[a-zA-Z]/.test(r.charAt(1))&&(r.length===2||r.charAt(2)==='/')?r.charAt(1).toUpperCase()+':/'+r.slice(3):r;const s=p.join(n,'scripts/hooks/lifecycle-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=n;if(require('fs').existsSync(s)){process.argv.splice(1,0,s);require(s).cli()}else{process.stderr.write('[Hook] lifecycle bootstrap unavailable; skipping hook'+String.fromCharCode(10))}\" stop:cost-tracker scripts/hooks/cost-tracker.js minimal,standard,strict 30000",
|
||||
"async": true,
|
||||
"timeout": 10
|
||||
}
|
||||
@@ -230,7 +230,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:desktop-notify','scripts/hooks/desktop-notify.js','standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
|
||||
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const n=process.platform==='win32'&&r.charAt(0)==='/'&&/[a-zA-Z]/.test(r.charAt(1))&&(r.length===2||r.charAt(2)==='/')?r.charAt(1).toUpperCase()+':/'+r.slice(3):r;const s=p.join(n,'scripts/hooks/lifecycle-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=n;if(require('fs').existsSync(s)){process.argv.splice(1,0,s);require(s).cli()}else{process.stderr.write('[Hook] lifecycle bootstrap unavailable; skipping hook'+String.fromCharCode(10))}\" stop:desktop-notify scripts/hooks/desktop-notify.js standard,strict 30000",
|
||||
"async": true,
|
||||
"timeout": 10
|
||||
}
|
||||
@@ -243,7 +243,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'session:end:marker','scripts/hooks/session-end-marker.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});const stdout=typeof result.stdout==='string'?result.stdout:'';if(stdout)process.stdout.write(stdout);else process.stdout.write(raw);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[SessionEnd] ERROR: hook runner failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);}process.stderr.write('[SessionEnd] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);\"",
|
||||
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const n=process.platform==='win32'&&r.charAt(0)==='/'&&/[a-zA-Z]/.test(r.charAt(1))&&(r.length===2||r.charAt(2)==='/')?r.charAt(1).toUpperCase()+':/'+r.slice(3):r;const s=p.join(n,'scripts/hooks/lifecycle-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=n;if(require('fs').existsSync(s)){process.argv.splice(1,0,s);require(s).cli()}else{process.stderr.write('[Hook] lifecycle bootstrap unavailable; skipping hook'+String.fromCharCode(10))}\" session:end:marker scripts/hooks/session-end-marker.js minimal,standard,strict 30000",
|
||||
"async": true,
|
||||
"timeout": 10
|
||||
}
|
||||
|
||||
+10
-10
@@ -71,12 +71,12 @@
|
||||
{
|
||||
"id": "post:dispatcher:sync",
|
||||
"description": "Run synchronous PostToolUse hooks in one process while preserving per-hook controls",
|
||||
"fingerprint": "cc868baab727"
|
||||
"fingerprint": "69422cb651aa"
|
||||
},
|
||||
{
|
||||
"id": "post:dispatcher:async",
|
||||
"description": "Run background PostToolUse hooks in one process while preserving per-hook controls",
|
||||
"fingerprint": "5e256d15db44"
|
||||
"fingerprint": "01af98da6841"
|
||||
}
|
||||
],
|
||||
"PostToolUseFailure": [
|
||||
@@ -95,44 +95,44 @@
|
||||
{
|
||||
"id": "stop:plan-canvas-pending",
|
||||
"description": "Deliver undelivered Plan Canvas browser feedback before the agent stops",
|
||||
"fingerprint": "e1a0fd79c26f"
|
||||
"fingerprint": "5953e0fed81c"
|
||||
},
|
||||
{
|
||||
"id": "stop:format-typecheck",
|
||||
"description": "Batch format (Biome/Prettier) and typecheck (tsc) all JS/TS files edited this response — runs once at Stop instead of after every Edit",
|
||||
"fingerprint": "9836d01e962e"
|
||||
"fingerprint": "a9d9bb04e060"
|
||||
},
|
||||
{
|
||||
"id": "stop:check-console-log",
|
||||
"description": "Check for console.log in modified files after each response",
|
||||
"fingerprint": "235c7f182b76"
|
||||
"fingerprint": "a675a517c549"
|
||||
},
|
||||
{
|
||||
"id": "stop:session-end",
|
||||
"description": "Persist session state after each response (Stop carries transcript_path)",
|
||||
"fingerprint": "981212c32849"
|
||||
"fingerprint": "d094692bee01"
|
||||
},
|
||||
{
|
||||
"id": "stop:evaluate-session",
|
||||
"description": "Evaluate session for extractable patterns",
|
||||
"fingerprint": "d874ecf69ef7"
|
||||
"fingerprint": "664eec4bb68f"
|
||||
},
|
||||
{
|
||||
"id": "stop:cost-tracker",
|
||||
"description": "Track token and cost metrics per session",
|
||||
"fingerprint": "57d255146fc0"
|
||||
"fingerprint": "5c5fe7253e20"
|
||||
},
|
||||
{
|
||||
"id": "stop:desktop-notify",
|
||||
"description": "Send desktop notification (macOS/WSL) with task summary when Claude responds",
|
||||
"fingerprint": "668cdbae027d"
|
||||
"fingerprint": "7a492e898bf6"
|
||||
}
|
||||
],
|
||||
"SessionEnd": [
|
||||
{
|
||||
"id": "session:end:marker",
|
||||
"description": "Session end lifecycle marker (non-blocking)",
|
||||
"fingerprint": "23a3832480e1"
|
||||
"fingerprint": "9270863f2935"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+2
-1
@@ -35,12 +35,13 @@ $scriptDir = Split-Path -Parent $scriptPath
|
||||
$installerScript = Join-Path -Path (Join-Path -Path $scriptDir -ChildPath 'scripts') -ChildPath 'install-apply.js'
|
||||
|
||||
# Auto-install Node dependencies when running from a git clone
|
||||
# SECURITY: --ignore-scripts blocks preinstall/postinstall RCE from a compromised dependency.
|
||||
$nodeModules = Join-Path -Path $scriptDir -ChildPath 'node_modules'
|
||||
if (-not (Test-Path -LiteralPath $nodeModules)) {
|
||||
Write-Host '[ECC] Installing dependencies...'
|
||||
Push-Location $scriptDir
|
||||
try {
|
||||
& npm install --no-audit --no-fund --loglevel=error
|
||||
& npm install --ignore-scripts --no-audit --no-fund --loglevel=error
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "npm install failed with exit code $LASTEXITCODE"
|
||||
exit $LASTEXITCODE
|
||||
|
||||
+4
-2
@@ -14,10 +14,12 @@ while [ -L "$SCRIPT_PATH" ]; do
|
||||
done
|
||||
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
||||
|
||||
# Auto-install Node dependencies when running from a git clone
|
||||
# Auto-install Node dependencies when running from a git clone.
|
||||
# SECURITY: --ignore-scripts blocks preinstall/postinstall RCE from a
|
||||
# compromised dependency. ECC deps are pure JS (no native build step).
|
||||
if [ ! -d "$SCRIPT_DIR/node_modules" ]; then
|
||||
echo "[ECC] Installing dependencies..."
|
||||
(cd "$SCRIPT_DIR" && npm install --no-audit --no-fund --loglevel=error)
|
||||
(cd "$SCRIPT_DIR" && npm install --ignore-scripts --no-audit --no-fund --loglevel=error)
|
||||
fi
|
||||
|
||||
# On MSYS2/Git Bash, convert the POSIX path to a Windows path so Node.js
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "commonjs"
|
||||
}
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ecc-universal",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ecc-universal",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "2.2.5",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user