merge: reconcile Polish locale with current main

This commit is contained in:
Dante
2026-09-22 12:23:52 +08:00
170 changed files with 8472 additions and 1263 deletions
+1
View File
@@ -20,3 +20,4 @@ bash ./install.sh --target adal --profile minimal
- The `adal` target installs into the project-level `./.adal/` directory.
- AdaL's own config (`~/.adal/settings.json`, MCP servers, plugins) is **not** touched by ECC install.
- Use `npx ecc-universal doctor --target adal` to check install health.
- use an installed
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -12,7 +12,7 @@
"name": "ecc",
"source": "./",
"description": "Harness-native ECC operator layer - 68 agents, 292 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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ecc",
"version": "2.2.1",
"version": "2.2.2",
"description": "Harness-native ECC plugin for engineering teams - 68 agents, 292 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 -1
View File
@@ -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",
+6 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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"
+2 -2
View File
@@ -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 -1
View File
@@ -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",
+23 -12
View File
@@ -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]
}
},
+2 -2
View File
@@ -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"
+3 -3
View File
@@ -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. " +
+8 -8
View File
@@ -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"
+2 -1
View File
@@ -15,7 +15,8 @@
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true,
"types": ["node"]
},
"include": [
+44 -3
View File
@@ -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}`)
}
}
+49
View File
@@ -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.
+13 -13
View File
@@ -2,7 +2,7 @@
This is a **production-ready AI coding plugin** providing 68 specialized agents, 292 skills, 94 commands, and automated hook workflows for software development.
**Version:** 2.2.1
**Version:** 2.2.2
## Core Principles
@@ -52,15 +52,15 @@ This is a **production-ready AI coding plugin** providing 68 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.
@@ -114,9 +114,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
View File
@@ -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
+51 -26
View File
@@ -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>
@@ -153,8 +153,8 @@ Access to 68 agents, 292 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>
@@ -171,9 +171,34 @@ Access to 68 agents, 292 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
```
#### Windows first-time walkthrough
If you are new to command-line tools, use this copy-and-paste path:
1. Install Node.js 18 or newer, Git, and Claude Code.
2. Open **PowerShell** from the Windows Start menu.
3. Confirm that each prerequisite is available:
```powershell
node --version
git --version
claude --version
```
4. Run the guided installer:
```powershell
npx ecc-universal@2.2.2 setup
```
5. For a typical personal setup, choose **Global user**, choose **Standard** hooks, and confirm.
6. Start a new Claude Code session and run `/plugin list` to verify that `ecc@ecc` is enabled.
This path does not require cloning the repository. If any prerequisite command is not found, install or repair that prerequisite before rerunning ECC setup.
If npm reports a version or cache error, confirm the registry version before retrying:
```bash
@@ -184,12 +209,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.
@@ -198,7 +223,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.
@@ -212,7 +237,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
@@ -221,16 +246,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.
@@ -402,7 +427,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:
@@ -587,11 +612,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:
@@ -780,7 +805,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.
@@ -1197,7 +1222,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
@@ -1592,7 +1617,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
View File
@@ -80,7 +80,7 @@
## 最新动态
### v2.2.1 — 引导式多 Harness 安装(2026年8月)
### v2.2.2 — 引导式多 Harness 安装(2026年8月)
新增可审查的 Claude Code、Codex 与 Kimi Code 多 Harness 安装流程,并提供同步的 npm 命令入口。
+1 -1
View File
@@ -1 +1 @@
2.2.1
2.2.2
+1 -1
View File
@@ -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
-30
View File
@@ -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 &#183; first 40,000 stars</text>
<text x="70" y="390" fill="#8b949e" font-size="11">Jan 18, 2026 &#8211; Feb 7, 2026 &#183; source: GitHub stargazers API</text>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

-30
View File
@@ -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 &#183; first 40,000 stars</text>
<text x="70" y="390" fill="#59636e" font-size="11">Jan 18, 2026 &#8211; Feb 7, 2026 &#183; source: GitHub stargazers API</text>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

+26
View File
@@ -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.
+19
View File
@@ -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.
+1 -1
View File
@@ -703,7 +703,7 @@ Suggested payload:
"skippedModules": []
},
"source": {
"repoVersion": "2.2.1",
"repoVersion": "2.2.2",
"repoCommit": "git-sha",
"manifestVersion": 1
},
+2 -2
View File
@@ -4,8 +4,8 @@
![ECC - das Harness-native Operator-System für agentische Arbeit](../../assets/hero.png)
[![Stars](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.ecc.tools%2Fbadge%2Fstars&style=flat)](https://github.com/affaan-m/ECC/stargazers)
[![Forks](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.ecc.tools%2Fbadge%2Fforks&style=flat)](https://github.com/affaan-m/ECC/network/members)
[![GitHub-Sterne](https://img.shields.io/github/stars/affaan-m/ECC?style=flat)](https://github.com/affaan-m/ECC)
[![GitHub-Forks](https://img.shields.io/github/forks/affaan-m/ECC?style=flat)](https://github.com/affaan-m/ECC/forks)
[![Contributors](https://img.shields.io/github/contributors/affaan-m/ECC?style=flat)](https://github.com/affaan-m/ECC/graphs/contributors)
[![npm ecc-universal](https://img.shields.io/npm/dw/ecc-universal?label=ecc-universal%20weekly%20downloads&logo=npm)](https://www.npmjs.com/package/ecc-universal)
[![npm ecc-agentshield](https://img.shields.io/npm/dw/ecc-agentshield?label=ecc-agentshield%20weekly%20downloads&logo=npm)](https://www.npmjs.com/package/ecc-agentshield)
+7 -7
View File
@@ -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
View File
@@ -4,13 +4,13 @@
![ECC - el sistema operativo nativo del harness para trabajo agentivo](../../assets/hero.png)
[![Stars](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.ecc.tools%2Fbadge%2Fstars&style=flat)](https://github.com/affaan-m/ECC/stargazers)
[![Forks](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.ecc.tools%2Fbadge%2Fforks&style=flat)](https://github.com/affaan-m/ECC/network/members)
[![Estrellas de GitHub](https://img.shields.io/github/stars/affaan-m/ECC?style=flat)](https://github.com/affaan-m/ECC)
[![Forks de GitHub](https://img.shields.io/github/forks/affaan-m/ECC?style=flat)](https://github.com/affaan-m/ECC/forks)
[![Contributors](https://img.shields.io/github/contributors/affaan-m/ECC?style=flat)](https://github.com/affaan-m/ECC/graphs/contributors)
[![npm ecc-universal](https://img.shields.io/npm/dw/ecc-universal?label=ecc-universal%20weekly%20downloads&logo=npm)](https://www.npmjs.com/package/ecc-universal)
[![npm ecc-agentshield](https://img.shields.io/npm/dw/ecc-agentshield?label=ecc-agentshield%20weekly%20downloads&logo=npm)](https://www.npmjs.com/package/ecc-agentshield)
[![GitHub App Install](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.ecc.tools%2Fbadge%2Finstalls&logo=github)](https://github.com/marketplace/ecc-tools)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](../../LICENSE)
![Shell](https://img.shields.io/badge/-Shell-4EAA25?logo=gnu-bash&logoColor=white)
![TypeScript](https://img.shields.io/badge/-TypeScript-3178C6?logo=typescript&logoColor=white)
![Python](https://img.shields.io/badge/-Python-3776AB?logo=python&logoColor=white)
+23 -16
View File
@@ -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
+7 -7
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+21 -14
View File
@@ -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 を使用
## 並列タスク実行
+1 -1
View File
@@ -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
View File
@@ -2,7 +2,7 @@
Bu, yazılım geliştirme için 68 özel agent, 292 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 68 özel agent, 292 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
View File
@@ -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.
+22 -15
View File
@@ -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
+1 -1
View File
@@ -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)
---
+2 -2
View File
@@ -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>
+2 -2
View File
@@ -4,8 +4,8 @@
![ECC - ایجنٹک کام کے لیے ہارنس-نیٹو آپریٹر سسٹم](../../assets/hero.png)
[![Stars](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.ecc.tools%2Fbadge%2Fstars&style=flat)](https://github.com/affaan-m/ECC/stargazers)
[![Forks](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.ecc.tools%2Fbadge%2Fforks&style=flat)](https://github.com/affaan-m/ECC/network/members)
[![GitHub stars](https://img.shields.io/github/stars/affaan-m/ECC?style=flat)](https://github.com/affaan-m/ECC)
[![GitHub forks](https://img.shields.io/github/forks/affaan-m/ECC?style=flat)](https://github.com/affaan-m/ECC/forks)
[![Contributors](https://img.shields.io/github/contributors/affaan-m/ECC?style=flat)](https://github.com/affaan-m/ECC/graphs/contributors)
[![npm ecc-universal](https://img.shields.io/npm/dw/ecc-universal?label=ecc-universal%20weekly%20downloads&logo=npm)](https://www.npmjs.com/package/ecc-universal)
[![npm ecc-agentshield](https://img.shields.io/npm/dw/ecc-agentshield?label=ecc-agentshield%20weekly%20downloads&logo=npm)](https://www.npmjs.com/package/ecc-agentshield)
+9 -9
View File
@@ -2,7 +2,7 @@
这是一个**生产就绪的 AI 编码插件**,提供 68 个专业代理、292 项技能、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**
对于独立操作使用并行执行 — 同时启动多个智能体。
+2 -2
View File
@@ -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 |
**关键架构决策:**
+22 -15
View File
@@ -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** 智能体
## 并行任务执行
+1 -1
View File
@@ -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)
***
+8 -8
View File
@@ -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",
+6
View File
@@ -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.
+2 -10
View File
@@ -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)
+135
View File
@@ -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
View File
@@ -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(),
+2 -1
View File
@@ -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
View File
@@ -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"
}
+2 -2
View File
@@ -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",
+13 -1
View File
@@ -1,7 +1,18 @@
{
"name": "ecc-universal",
"version": "2.2.1",
"version": "2.2.2",
"description": "Harness-native agent operating system for Codex, OpenCode, Cursor, Gemini, Claude Code, and terminal workflows - skills, hooks, rules, MCP conventions, and operator control-plane patterns",
"main": ".opencode/dist/index.js",
"types": ".opencode/dist/index.d.ts",
"exports": {
".": {
"types": "./.opencode/dist/index.d.ts",
"import": "./.opencode/dist/index.js",
"default": "./.opencode/dist/index.js"
},
"./package.json": "./package.json",
"./*": "./*"
},
"publishConfig": {
"access": "public"
},
@@ -51,6 +62,7 @@
".hermes/",
".kimi/",
".opencode/",
".opencode/dist/",
".pi/",
".openclaw/",
".qwen/",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -20,7 +20,7 @@ classifiers = [
dependencies = [
"anthropic>=0.120.2",
"openai>=1.30.0",
"openai>=2.34.0",
]
[project.optional-dependencies]
+23 -16
View File
@@ -2,29 +2,36 @@
## Available Agents
Located in `~/.claude/agents/`:
ECC agents ship with the `ecc@ecc` plugin, not in `~/.claude/agents/`.
They are invoked through the Agent tool with a plugin-scoped `subagent_type`:
```text
Agent(subagent_type: "ecc:planner", prompt: "...")
```
| Agent | Purpose | When to Use |
|-------|---------|-------------|
| planner | Implementation planning | Complex features, refactoring |
| architect | System design | Architectural decisions |
| tdd-guide | Test-driven development | New features, bug fixes |
| code-reviewer | Code review | After writing code |
| security-reviewer | Security analysis | Before commits |
| build-error-resolver | Fix build errors | When build fails |
| e2e-runner | E2E testing | Critical user flows |
| refactor-cleaner | Dead code cleanup | Code maintenance |
| doc-updater | Documentation | Updating docs |
| rust-reviewer | Rust code review | Rust projects |
| harmonyos-app-resolver | HarmonyOS app development | HarmonyOS/ArkTS projects |
| ecc:planner | Implementation planning | Complex features, refactoring |
| ecc:architect | System design | Architectural decisions |
| ecc:tdd-guide | Test-driven development | New features, bug fixes |
| ecc:code-reviewer | Code review | After writing code |
| ecc:security-reviewer | Security analysis | Before commits |
| ecc:build-error-resolver | Fix build errors | When build fails |
| ecc:e2e-runner | E2E testing | Critical user flows |
| ecc:refactor-cleaner | Dead code cleanup | Code maintenance |
| ecc:doc-updater | Documentation | Updating docs |
| ecc:rust-reviewer | Rust code review | Rust projects |
| ecc:harmonyos-app-resolver | HarmonyOS app development | HarmonyOS/ArkTS projects |
For the full roster of 68 agents, see `/ecc:ecc-guide`.
## Immediate Agent Usage
No user prompt needed:
1. Complex feature requests - Use **planner** agent
2. Code just written/modified - Use **code-reviewer** agent
3. Bug fix or new feature - Use **tdd-guide** agent
4. Architectural decision - Use **architect** agent
1. Complex feature requests - Use **ecc:planner** agent
2. Code just written/modified - Use **ecc:code-reviewer** agent
3. Bug fix or new feature - Use **ecc:tdd-guide** agent
4. Architectural decision - Use **ecc:architect** agent
## Parallel Task Execution
+43 -10
View File
@@ -95,21 +95,54 @@ function askClaude(systemPrompt, history, userMessage, model) {
}
args.push('-p');
// On Windows the `claude` binary installed via npm is `claude.cmd`/`claude.ps1`,
// and Node's spawn() cannot resolve those wrappers via PATH without shell: true.
// But shell mode concatenates args *unescaped*, so a multi-line prompt passed as
// an arg gets mangled (newlines and the `===` section markers truncate it, and
// claude receives an empty prompt). Fix: send the prompt over stdin via `input`
// and keep only the short, safe flags (`--model`, `-p`) as args.
// 'claude' is a hardcoded literal here (not user input), so shell mode is safe.
const result = spawnSync('claude', args, {
// SECURITY: a model value like `x & calc &` breaks out when Node
// concatenates command+args unquoted under cmd.exe (DEP0190), so the model
// token is validated and only fixed flags reach the command line.
if (model && !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/.test(model)) {
return `[Error: invalid model name]`;
}
// On Windows the `claude` binary is usually a .cmd shim, which Node
// >=18.20/20.12 refuses to spawn directly (CVE-2024-27980 mitigation), and
// .ps1 shims are not directly executable at all. Resolve a natively
// executable target first; only .cmd/.bat go through cmd.exe, using the
// same quoted-command-line pattern as scripts/hooks/mcp-health-check.js so
// space-containing paths survive as single tokens. .ps1 is never executed
// directly — fall through to bare `claude` (pre-change behavior) instead.
// cmd.exe expands %NAME% even inside double-quoted strings, so reject
// percent-delimited executable paths rather than route them through the shell.
function quoteWinToken(token) {
if (/%/.test(token)) return null;
return /[\s"&|<>^();]/.test(token) ? '"' + token.replace(/"/g, '""') + '"' : token;
}
let bin = 'claude';
let useShell = false;
if (process.platform === 'win32') {
const { spawnSync: spawnWhere } = require('child_process');
for (const ext of ['.exe', '.cmd', '.bat']) {
let found = null;
try {
found = spawnWhere('where', [`claude${ext}`], { encoding: 'utf8' });
} catch { /* ignore */ }
if (found && found.status === 0 && found.stdout && found.stdout.trim()) {
bin = found.stdout.trim().split(/\r?\n/)[0];
useShell = /\.(cmd|bat)$/i.test(bin);
break;
}
}
if (useShell && quoteWinToken(bin) === null) {
useShell = false;
}
}
const spawnOpts = {
input: fullPrompt,
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, CLAUDECODE: '' },
timeout: 300000,
shell: process.platform === 'win32'
});
};
const result = useShell
? spawnSync([bin, ...args].map(quoteWinToken).join(' '), { ...spawnOpts, shell: true })
: spawnSync(bin, args, { ...spawnOpts, shell: false });
if (result.error) {
return `[Error: ${result.error.message}]`;

Some files were not shown because too many files have changed in this diff Show More