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

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

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

|
||||

|
||||

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

|
||||
|
||||
[](https://github.com/affaan-m/ECC/stargazers)
|
||||
[](https://github.com/affaan-m/ECC/network/members)
|
||||
[](https://github.com/affaan-m/ECC)
|
||||
[](https://github.com/affaan-m/ECC/forks)
|
||||
[](https://github.com/affaan-m/ECC/graphs/contributors)
|
||||
[](https://www.npmjs.com/package/ecc-universal)
|
||||
[](https://www.npmjs.com/package/ecc-agentshield)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 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**
|
||||
|
||||
对于独立操作使用并行执行 — 同时启动多个智能体。
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
## 最新动态
|
||||
|
||||
### v2.2.1 — 引导式多 Harness 安装(2026年8月)
|
||||
### v2.2.2 — 引导式多 Harness 安装(2026年8月)
|
||||
|
||||
新增可审查的 Claude Code、Codex 与 Kimi Code 多 Harness 安装流程,并提供同步的 npm 命令入口。
|
||||
|
||||
@@ -1292,7 +1292,7 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
|
||||
| **上下文文件** | CLAUDE.md + AGENTS.md | AGENTS.md | AGENTS.md | AGENTS.md |
|
||||
| **秘密检测** | 基于钩子 | beforeSubmitPrompt 钩子 | 基于沙箱 | 基于钩子 |
|
||||
| **自动格式化** | PostToolUse 钩子 | afterFileEdit 钩子 | N/A | file.edited 钩子 |
|
||||
| **版本** | 插件 | 插件 | 参考配置 | 2.2.1 |
|
||||
| **版本** | 插件 | 插件 | 参考配置 | 2.2.2 |
|
||||
|
||||
**关键架构决策:**
|
||||
|
||||
|
||||
@@ -2,29 +2,36 @@
|
||||
|
||||
## 可用智能体
|
||||
|
||||
位于 `~/.claude/agents/` 中:
|
||||
ECC 智能体随 `ecc@ecc` 插件一起分发,不在 `~/.claude/agents/` 目录中。
|
||||
它们通过 Agent 工具以插件作用域的 `subagent_type` 调用:
|
||||
|
||||
```text
|
||||
Agent(subagent_type: "ecc:planner", prompt: "...")
|
||||
```
|
||||
|
||||
| 代理 | 用途 | 使用时机 |
|
||||
|-------|---------|-------------|
|
||||
| planner | 实现规划 | 复杂功能、重构 |
|
||||
| architect | 系统设计 | 架构决策 |
|
||||
| tdd-guide | 测试驱动开发 | 新功能、错误修复 |
|
||||
| code-reviewer | 代码审查 | 编写代码后 |
|
||||
| security-reviewer | 安全分析 | 提交前 |
|
||||
| build-error-resolver | 修复构建错误 | 构建失败时 |
|
||||
| e2e-runner | 端到端测试 | 关键用户流程 |
|
||||
| refactor-cleaner | 清理死代码 | 代码维护 |
|
||||
| doc-updater | 文档 | 更新文档 |
|
||||
| rust-reviewer | Rust 代码审查 | Rust 项目 |
|
||||
| ecc:planner | 实现规划 | 复杂功能、重构 |
|
||||
| ecc:architect | 系统设计 | 架构决策 |
|
||||
| ecc:tdd-guide | 测试驱动开发 | 新功能、错误修复 |
|
||||
| ecc:code-reviewer | 代码审查 | 编写代码后 |
|
||||
| ecc:security-reviewer | 安全分析 | 提交前 |
|
||||
| ecc:build-error-resolver | 修复构建错误 | 构建失败时 |
|
||||
| ecc:e2e-runner | 端到端测试 | 关键用户流程 |
|
||||
| ecc:refactor-cleaner | 清理死代码 | 代码维护 |
|
||||
| ecc:doc-updater | 文档 | 更新文档 |
|
||||
| ecc:rust-reviewer | Rust 代码审查 | Rust 项目 |
|
||||
|
||||
完整 68 个智能体的清单参见 `/ecc:ecc-guide`。
|
||||
|
||||
## 即时智能体使用
|
||||
|
||||
无需用户提示:
|
||||
|
||||
1. 复杂的功能请求 - 使用 **planner** 智能体
|
||||
2. 刚编写/修改的代码 - 使用 **code-reviewer** 智能体
|
||||
3. 错误修复或新功能 - 使用 **tdd-guide** 智能体
|
||||
4. 架构决策 - 使用 **architect** 智能体
|
||||
1. 复杂的功能请求 - 使用 **ecc:planner** 智能体
|
||||
2. 刚编写/修改的代码 - 使用 **ecc:code-reviewer** 智能体
|
||||
3. 错误修复或新功能 - 使用 **ecc:tdd-guide** 智能体
|
||||
4. 架构决策 - 使用 **ecc:architect** 智能体
|
||||
|
||||
## 并行任务执行
|
||||
|
||||
|
||||
@@ -421,7 +421,7 @@ affoon:~ ctx:65% Opus 4.5 19:52
|
||||
* [交互模式](https://code.claude.com/docs/en/interactive-mode)
|
||||
* [记忆系统](https://code.claude.com/docs/en/memory)
|
||||
* [子代理](https://code.claude.com/docs/en/sub-agents)
|
||||
* [MCP 概述](https://code.claude.com/docs/en/mcp-overview)
|
||||
* [MCP 概述](https://code.claude.com/docs/en/mcp)
|
||||
|
||||
***
|
||||
|
||||
|
||||
Generated
+8
-8
@@ -2375,9 +2375,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.1.4+spec-1.1.0"
|
||||
version = "1.1.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
|
||||
checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
@@ -2528,9 +2528,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "3.4.0"
|
||||
version = "3.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d"
|
||||
checksum = "af5546be8f5378d5414f83733f5c9a2526f4645829edbc1c41790aeef1b38e8b"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"cookie_store",
|
||||
@@ -2548,9 +2548,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ureq-proto"
|
||||
version = "0.6.1"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613"
|
||||
checksum = "5b0809a01d1ca5a51ca70db32bb2a19157582a526505ef3c19e3b343a59aa5ad"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"http",
|
||||
@@ -2590,9 +2590,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.26.0"
|
||||
version = "1.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
|
||||
checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
|
||||
dependencies = [
|
||||
"atomic",
|
||||
"getrandom 0.4.2",
|
||||
|
||||
@@ -14,6 +14,12 @@ It is usable as an alpha for local experimentation, but it is **not** the finish
|
||||
- worktree-aware session scaffolding
|
||||
- basic multi-session state and output tracking
|
||||
|
||||
Dashboard output is hydrated from SQLite at startup and after recovery, then
|
||||
synchronized with a monotonic database cursor. Because session runners are
|
||||
separate processes, the database remains the cross-process source of truth
|
||||
while steady-state refreshes read only the rows appended since the previous
|
||||
dashboard tick.
|
||||
|
||||
## What This Is For
|
||||
|
||||
ECC 2.0 is the layer above individual harness installs.
|
||||
|
||||
@@ -5,6 +5,8 @@ use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
pub const OUTPUT_BUFFER_LIMIT: usize = 1000;
|
||||
/// Maximum number of cross-process output rows applied during one dashboard refresh.
|
||||
pub const OUTPUT_DELTA_BATCH_LIMIT: usize = 4096;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum OutputStream {
|
||||
@@ -113,16 +115,6 @@ impl SessionOutputStore {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn replace_lines(&self, session_id: &str, lines: Vec<OutputLine>) {
|
||||
let mut buffer: VecDeque<OutputLine> = lines.into_iter().collect();
|
||||
|
||||
while buffer.len() > self.capacity {
|
||||
let _ = buffer.pop_front();
|
||||
}
|
||||
|
||||
self.lock_buffers().insert(session_id.to_string(), buffer);
|
||||
}
|
||||
|
||||
pub fn lines(&self, session_id: &str) -> Vec<OutputLine> {
|
||||
self.lock_buffers()
|
||||
.get(session_id)
|
||||
|
||||
@@ -28,6 +28,31 @@ pub struct StateStore {
|
||||
conn: Connection,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct SessionOutputRecord {
|
||||
pub id: i64,
|
||||
pub session_id: String,
|
||||
pub line: OutputLine,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct SessionOutputBatch {
|
||||
pub cursor: i64,
|
||||
pub records: Vec<SessionOutputRecord>,
|
||||
}
|
||||
|
||||
/// Converts one persisted output row into the dashboard's typed record.
|
||||
fn output_record_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<SessionOutputRecord> {
|
||||
let stream: String = row.get(2)?;
|
||||
let text: String = row.get(3)?;
|
||||
let timestamp: String = row.get(4)?;
|
||||
Ok(SessionOutputRecord {
|
||||
id: row.get(0)?,
|
||||
session_id: row.get(1)?,
|
||||
line: OutputLine::new(OutputStream::from_db_value(&stream), text, timestamp),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct HarnessAuditEntry {
|
||||
pub id: i64,
|
||||
@@ -4000,6 +4025,53 @@ impl StateStore {
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
/// Returns a bounded recent-output snapshot and its highest persisted row ID.
|
||||
pub(crate) fn get_output_snapshot(
|
||||
&self,
|
||||
limit_per_session: usize,
|
||||
) -> Result<SessionOutputBatch> {
|
||||
let limit_per_session = i64::try_from(limit_per_session.max(1)).unwrap_or(i64::MAX);
|
||||
let mut stmt = self.conn.prepare(
|
||||
"SELECT id, session_id, stream, line, timestamp
|
||||
FROM (
|
||||
SELECT id, session_id, stream, line, timestamp,
|
||||
ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY id DESC) AS row_num
|
||||
FROM session_output
|
||||
)
|
||||
WHERE row_num <= ?1
|
||||
ORDER BY id ASC",
|
||||
)?;
|
||||
let records = stmt
|
||||
.query_map(rusqlite::params![limit_per_session], output_record_from_row)?
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let cursor = records.last().map(|record| record.id).unwrap_or(0);
|
||||
|
||||
Ok(SessionOutputBatch { cursor, records })
|
||||
}
|
||||
|
||||
/// Returns at most `limit` output rows newer than `cursor` in insertion order.
|
||||
pub(crate) fn get_output_since(
|
||||
&self,
|
||||
cursor: i64,
|
||||
limit: usize,
|
||||
) -> Result<SessionOutputBatch> {
|
||||
let cursor = cursor.max(0);
|
||||
let limit = i64::try_from(limit.max(1)).unwrap_or(i64::MAX);
|
||||
let mut stmt = self.conn.prepare(
|
||||
"SELECT id, session_id, stream, line, timestamp
|
||||
FROM session_output
|
||||
WHERE id > ?1
|
||||
ORDER BY id ASC
|
||||
LIMIT ?2",
|
||||
)?;
|
||||
let records = stmt
|
||||
.query_map(rusqlite::params![cursor, limit], output_record_from_row)?
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let cursor = records.last().map(|record| record.id).unwrap_or(cursor);
|
||||
|
||||
Ok(SessionOutputBatch { cursor, records })
|
||||
}
|
||||
|
||||
pub fn insert_tool_log(
|
||||
&self,
|
||||
session_id: &str,
|
||||
@@ -7382,6 +7454,69 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_cursor_reads_a_bounded_snapshot_then_only_new_rows() -> Result<()> {
|
||||
let tempdir = TestDir::new("store-output-cursor")?;
|
||||
let db = StateStore::open(&tempdir.path().join("state.db"))?;
|
||||
|
||||
db.insert_session(&build_session("session-1", SessionState::Running))?;
|
||||
db.insert_session(&build_session("session-2", SessionState::Running))?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "one-a")?;
|
||||
db.append_output_line("session-2", OutputStream::Stderr, "two-a")?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "one-b")?;
|
||||
db.append_output_line("session-2", OutputStream::Stdout, "two-b")?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "one-c")?;
|
||||
|
||||
let snapshot = db.get_output_snapshot(2)?;
|
||||
assert_eq!(snapshot.cursor, 5);
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.records
|
||||
.iter()
|
||||
.map(|record| (record.session_id.as_str(), record.line.text.as_str()))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
("session-2", "two-a"),
|
||||
("session-1", "one-b"),
|
||||
("session-2", "two-b"),
|
||||
("session-1", "one-c"),
|
||||
]
|
||||
);
|
||||
|
||||
db.append_output_line("session-2", OutputStream::Stderr, "two-c")?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "one-d")?;
|
||||
let delta = db.get_output_since(snapshot.cursor, 1)?;
|
||||
assert_eq!(delta.cursor, 6);
|
||||
assert_eq!(delta.records.len(), 1);
|
||||
assert_eq!(delta.records[0].session_id, "session-2");
|
||||
assert_eq!(delta.records[0].line.text, "two-c");
|
||||
|
||||
let next = db.get_output_since(delta.cursor, 1)?;
|
||||
assert_eq!(next.cursor, 7);
|
||||
assert_eq!(next.records.len(), 1);
|
||||
assert_eq!(next.records[0].session_id, "session-1");
|
||||
assert_eq!(next.records[0].line.text, "one-d");
|
||||
|
||||
let empty = db.get_output_since(next.cursor, 1)?;
|
||||
assert_eq!(empty.cursor, next.cursor);
|
||||
assert!(empty.records.is_empty());
|
||||
|
||||
let query_plan = db
|
||||
.conn
|
||||
.prepare(
|
||||
"EXPLAIN QUERY PLAN SELECT id FROM session_output WHERE id > ?1 ORDER BY id ASC",
|
||||
)?
|
||||
.query_map(rusqlite::params![snapshot.cursor], |row| {
|
||||
row.get::<_, String>(3)
|
||||
})?
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
assert!(query_plan
|
||||
.iter()
|
||||
.any(|detail| detail.contains("INTEGER PRIMARY KEY") && detail.contains("rowid>?")));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_round_trip_tracks_unread_counts_and_read_state() -> Result<()> {
|
||||
let tempdir = TestDir::new("store-messages")?;
|
||||
|
||||
+343
-48
@@ -10,7 +10,6 @@ use ratatui::{
|
||||
use regex::Regex;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
|
||||
use std::time::UNIX_EPOCH;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use super::widgets::{budget_state, format_currency, format_token_count, BudgetState, TokenMeter};
|
||||
use crate::comms;
|
||||
@@ -19,12 +18,12 @@ use crate::notifications::{DesktopNotifier, NotificationEvent, WebhookNotifier};
|
||||
use crate::observability::ToolLogEntry;
|
||||
use crate::session::manager;
|
||||
use crate::session::output::{
|
||||
OutputEvent, OutputLine, OutputStream, SessionOutputStore, OUTPUT_BUFFER_LIMIT,
|
||||
OutputLine, OutputStream, OUTPUT_BUFFER_LIMIT, OUTPUT_DELTA_BATCH_LIMIT,
|
||||
};
|
||||
use crate::session::store::{DaemonActivity, FileActivityOverlap, StateStore};
|
||||
use crate::session::store::{DaemonActivity, FileActivityOverlap, SessionOutputRecord, StateStore};
|
||||
use crate::session::{
|
||||
ContextObservationPriority, DecisionLogEntry, FileActivityEntry, Session, SessionGrouping,
|
||||
SessionBoardMeta, SessionHarnessInfo, SessionMessage, SessionState,
|
||||
ContextObservationPriority, DecisionLogEntry, FileActivityEntry, Session, SessionBoardMeta,
|
||||
SessionGrouping, SessionHarnessInfo, SessionMessage, SessionState,
|
||||
};
|
||||
use crate::worktree;
|
||||
|
||||
@@ -79,16 +78,42 @@ struct TestRunSummary {
|
||||
passed: usize,
|
||||
}
|
||||
|
||||
/// Consumes an output cache and returns a new bounded cache with `records` appended.
|
||||
fn append_output_records(
|
||||
mut cache: HashMap<String, Vec<OutputLine>>,
|
||||
records: Vec<SessionOutputRecord>,
|
||||
) -> HashMap<String, Vec<OutputLine>> {
|
||||
let mut touched_sessions = HashSet::new();
|
||||
for record in records {
|
||||
cache
|
||||
.entry(record.session_id.clone())
|
||||
.or_default()
|
||||
.push(record.line);
|
||||
touched_sessions.insert(record.session_id);
|
||||
}
|
||||
|
||||
for session_id in touched_sessions {
|
||||
if let Some(lines) = cache.get_mut(&session_id) {
|
||||
let overflow = lines.len().saturating_sub(OUTPUT_BUFFER_LIMIT);
|
||||
if overflow > 0 {
|
||||
lines.drain(..overflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache
|
||||
}
|
||||
|
||||
pub struct Dashboard {
|
||||
db: StateStore,
|
||||
cfg: Config,
|
||||
output_store: SessionOutputStore,
|
||||
output_rx: broadcast::Receiver<OutputEvent>,
|
||||
notifier: DesktopNotifier,
|
||||
webhook_notifier: WebhookNotifier,
|
||||
sessions: Vec<Session>,
|
||||
session_harnesses: HashMap<String, SessionHarnessInfo>,
|
||||
session_output_cache: HashMap<String, Vec<OutputLine>>,
|
||||
session_output_generations: HashMap<String, chrono::DateTime<Utc>>,
|
||||
output_cursor: Option<i64>,
|
||||
unread_message_counts: HashMap<String, usize>,
|
||||
approval_queue_counts: HashMap<String, usize>,
|
||||
approval_queue_preview: Vec<SessionMessage>,
|
||||
@@ -502,15 +527,8 @@ fn load_session_harnesses(
|
||||
}
|
||||
|
||||
impl Dashboard {
|
||||
/// Builds the dashboard and hydrates its initial bounded output snapshot.
|
||||
pub fn new(db: StateStore, cfg: Config) -> Self {
|
||||
Self::with_output_store(db, cfg, SessionOutputStore::default())
|
||||
}
|
||||
|
||||
pub fn with_output_store(
|
||||
db: StateStore,
|
||||
cfg: Config,
|
||||
output_store: SessionOutputStore,
|
||||
) -> Self {
|
||||
let pane_size_percent = configured_pane_size(&cfg, cfg.pane_layout);
|
||||
let initial_cost_metrics_signature = metrics_file_signature(&cfg.cost_metrics_path());
|
||||
let initial_tool_activity_signature =
|
||||
@@ -528,12 +546,15 @@ impl Dashboard {
|
||||
.iter()
|
||||
.map(|session| (session.id.clone(), session.state.clone()))
|
||||
.collect();
|
||||
let session_output_generations = sessions
|
||||
.iter()
|
||||
.map(|session| (session.id.clone(), session.created_at))
|
||||
.collect();
|
||||
let initial_approval_message_id = db
|
||||
.latest_unread_approval_message()
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|message| message.id);
|
||||
let output_rx = output_store.subscribe();
|
||||
let notifier = DesktopNotifier::new(cfg.desktop_notifications.clone());
|
||||
let webhook_notifier = WebhookNotifier::new(cfg.webhook_notifications.clone());
|
||||
let mut session_table_state = TableState::default();
|
||||
@@ -544,13 +565,13 @@ impl Dashboard {
|
||||
let mut dashboard = Self {
|
||||
db,
|
||||
cfg,
|
||||
output_store,
|
||||
output_rx,
|
||||
notifier,
|
||||
webhook_notifier,
|
||||
sessions,
|
||||
session_harnesses,
|
||||
session_output_cache: HashMap::new(),
|
||||
session_output_generations,
|
||||
output_cursor: None,
|
||||
unread_message_counts: HashMap::new(),
|
||||
approval_queue_counts: HashMap::new(),
|
||||
approval_queue_preview: Vec::new(),
|
||||
@@ -624,6 +645,7 @@ impl Dashboard {
|
||||
dashboard.sync_handoff_backlog_counts();
|
||||
dashboard.sync_board_meta();
|
||||
dashboard.sync_global_handoff_backlog();
|
||||
dashboard.sync_output_cache();
|
||||
dashboard.sync_selected_output();
|
||||
dashboard.sync_selected_diff();
|
||||
dashboard.sync_selected_messages();
|
||||
@@ -3211,6 +3233,7 @@ impl Dashboard {
|
||||
));
|
||||
}
|
||||
|
||||
/// Refreshes persisted dashboard state while preserving the output cursor.
|
||||
pub fn refresh(&mut self) {
|
||||
self.sync_from_store();
|
||||
}
|
||||
@@ -3993,15 +4016,6 @@ impl Dashboard {
|
||||
}
|
||||
|
||||
pub async fn tick(&mut self) {
|
||||
loop {
|
||||
match self.output_rx.try_recv() {
|
||||
Ok(_event) => {}
|
||||
Err(broadcast::error::TryRecvError::Empty) => break,
|
||||
Err(broadcast::error::TryRecvError::Lagged(_)) => continue,
|
||||
Err(broadcast::error::TryRecvError::Closed) => break,
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(error) = manager::activate_pending_worktree_sessions(&self.db, &self.cfg).await {
|
||||
tracing::warn!("Failed to activate queued worktree sessions: {error}");
|
||||
}
|
||||
@@ -4073,18 +4087,22 @@ impl Dashboard {
|
||||
)
|
||||
}
|
||||
|
||||
/// Synchronizes dashboard state, deferring output recovery until sessions load.
|
||||
fn sync_from_store(&mut self) {
|
||||
let (heartbeat_enforcement, budget_enforcement, conflict_enforcement) =
|
||||
self.sync_runtime_metrics();
|
||||
let selected_id = self.selected_session_id().map(ToOwned::to_owned);
|
||||
self.sessions = match self.db.list_sessions() {
|
||||
let sessions_refreshed = match self.db.list_sessions() {
|
||||
Ok(mut sessions) => {
|
||||
sort_sessions_for_display(&mut sessions);
|
||||
sessions
|
||||
self.sessions = sessions;
|
||||
true
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!("Failed to refresh sessions: {error}");
|
||||
Vec::new()
|
||||
self.output_cursor = None;
|
||||
self.sessions.clear();
|
||||
false
|
||||
}
|
||||
};
|
||||
self.session_harnesses = load_session_harnesses(&self.db, &self.cfg, &self.sessions);
|
||||
@@ -4103,7 +4121,9 @@ impl Dashboard {
|
||||
self.sync_approval_notifications();
|
||||
self.sync_global_handoff_backlog();
|
||||
self.sync_daemon_activity();
|
||||
self.sync_output_cache();
|
||||
if sessions_refreshed {
|
||||
self.sync_output_cache();
|
||||
}
|
||||
self.sync_selection_by_id(selected_id.as_deref());
|
||||
self.ensure_selected_pane_visible();
|
||||
self.sync_selected_output();
|
||||
@@ -4481,25 +4501,43 @@ impl Dashboard {
|
||||
}
|
||||
|
||||
fn sync_output_cache(&mut self) {
|
||||
let active_session_ids: HashSet<_> = self
|
||||
let active_session_generations: HashMap<_, _> = self
|
||||
.sessions
|
||||
.iter()
|
||||
.map(|session| session.id.as_str())
|
||||
.map(|session| (session.id.clone(), session.created_at))
|
||||
.collect();
|
||||
self.session_output_cache
|
||||
.retain(|session_id, _| active_session_ids.contains(session_id.as_str()));
|
||||
let cached_generations = &self.session_output_generations;
|
||||
self.session_output_cache = std::mem::take(&mut self.session_output_cache)
|
||||
.into_iter()
|
||||
.filter(|(session_id, _)| {
|
||||
active_session_generations.get(session_id) == cached_generations.get(session_id)
|
||||
})
|
||||
.collect();
|
||||
self.session_output_generations = active_session_generations;
|
||||
|
||||
for session in &self.sessions {
|
||||
match self.db.get_output_lines(&session.id, OUTPUT_BUFFER_LIMIT) {
|
||||
Ok(lines) => {
|
||||
self.output_store.replace_lines(&session.id, lines.clone());
|
||||
self.session_output_cache.insert(session.id.clone(), lines);
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!("Failed to load session output for {}: {error}", session.id);
|
||||
}
|
||||
let batch = match self.output_cursor {
|
||||
Some(cursor) => self
|
||||
.db
|
||||
.get_output_since(cursor, OUTPUT_DELTA_BATCH_LIMIT),
|
||||
None => self.db.get_output_snapshot(OUTPUT_BUFFER_LIMIT),
|
||||
};
|
||||
let batch = match batch {
|
||||
Ok(batch) => batch,
|
||||
Err(error) => {
|
||||
tracing::warn!("Failed to refresh session output cache: {error}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if self.output_cursor.is_none() {
|
||||
self.session_output_cache = HashMap::new();
|
||||
}
|
||||
self.output_cursor = Some(batch.cursor);
|
||||
|
||||
self.session_output_cache = append_output_records(
|
||||
std::mem::take(&mut self.session_output_cache),
|
||||
batch.records,
|
||||
);
|
||||
}
|
||||
|
||||
fn ensure_selected_pane_visible(&mut self) {
|
||||
@@ -5212,6 +5250,7 @@ impl Dashboard {
|
||||
.map(|session| session.id.as_str())
|
||||
}
|
||||
|
||||
/// Returns the selected session's currently cached output window.
|
||||
fn selected_output_lines(&self) -> &[OutputLine] {
|
||||
self.selected_session_id()
|
||||
.and_then(|session_id| self.session_output_cache.get(session_id))
|
||||
@@ -13147,6 +13186,260 @@ diff --git a/src/lib.rs b/src/lib.rs
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_cache_appends_rows_written_by_another_process_without_rehydrating() -> Result<()> {
|
||||
let db_path =
|
||||
std::env::temp_dir().join(format!("ecc2-output-cursor-{}.db", Uuid::new_v4()));
|
||||
let db = StateStore::open(&db_path)?;
|
||||
let session = sample_session("session-1", "claude", SessionState::Running, None, 0, 0);
|
||||
db.insert_session(&session)?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "persisted-before-open")?;
|
||||
|
||||
let mut dashboard = Dashboard::new(db, Config::default());
|
||||
assert!(dashboard
|
||||
.selected_output_text()
|
||||
.contains("persisted-before-open"));
|
||||
dashboard
|
||||
.session_output_cache
|
||||
.entry("session-1".to_string())
|
||||
.or_default()
|
||||
.push(test_output_line(OutputStream::Stdout, "cache-only"));
|
||||
|
||||
let child = Command::new(std::env::current_exe()?)
|
||||
.args([
|
||||
"--exact",
|
||||
"tui::dashboard::tests::output_cursor_child_writer",
|
||||
"--ignored",
|
||||
"--nocapture",
|
||||
])
|
||||
.env("ECC2_OUTPUT_CURSOR_CHILD_DB", &db_path)
|
||||
.status()?;
|
||||
assert!(child.success(), "child output writer should succeed");
|
||||
dashboard.refresh();
|
||||
|
||||
let text = dashboard.selected_output_text();
|
||||
assert!(text.contains("persisted-before-open"));
|
||||
assert!(text.contains("cache-only"));
|
||||
assert!(text.contains("persisted-after-open"));
|
||||
|
||||
dashboard.sync_output_cache();
|
||||
assert_eq!(
|
||||
dashboard
|
||||
.selected_output_lines()
|
||||
.iter()
|
||||
.filter(|line| line.text == "persisted-after-open")
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_file(db_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "helper invoked by output cursor cross-process test"]
|
||||
fn output_cursor_child_writer() -> Result<()> {
|
||||
let Some(db_path) = std::env::var_os("ECC2_OUTPUT_CURSOR_CHILD_DB") else {
|
||||
return Ok(());
|
||||
};
|
||||
StateStore::open(Path::new(&db_path))?.append_output_line(
|
||||
"session-1",
|
||||
OutputStream::Stderr,
|
||||
"persisted-after-open",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_cache_rehydrates_after_transient_session_list_failure() -> Result<()> {
|
||||
let db_path =
|
||||
std::env::temp_dir().join(format!("ecc2-output-recovery-{}.db", Uuid::new_v4()));
|
||||
let db = StateStore::open(&db_path)?;
|
||||
let session = sample_session("session-1", "claude", SessionState::Running, None, 0, 0);
|
||||
db.insert_session(&session)?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "persisted-output")?;
|
||||
|
||||
let mut dashboard = Dashboard::new(db, Config::default());
|
||||
assert!(dashboard
|
||||
.selected_output_text()
|
||||
.contains("persisted-output"));
|
||||
dashboard
|
||||
.session_output_cache
|
||||
.entry("session-1".to_string())
|
||||
.or_default()
|
||||
.push(test_output_line(OutputStream::Stdout, "cache-only"));
|
||||
|
||||
let schema = rusqlite::Connection::open(&db_path)?;
|
||||
schema.execute("ALTER TABLE sessions RENAME TO unavailable_sessions", [])?;
|
||||
dashboard.sync_from_store();
|
||||
assert!(dashboard.sessions.is_empty());
|
||||
assert!(dashboard.session_output_cache["session-1"]
|
||||
.iter()
|
||||
.any(|line| line.text == "cache-only"));
|
||||
assert!(dashboard.output_cursor.is_none());
|
||||
|
||||
dashboard.sync_from_store();
|
||||
assert!(dashboard.session_output_cache["session-1"]
|
||||
.iter()
|
||||
.any(|line| line.text == "cache-only"));
|
||||
assert!(dashboard.output_cursor.is_none());
|
||||
|
||||
schema.execute("ALTER TABLE unavailable_sessions RENAME TO sessions", [])?;
|
||||
dashboard.sync_from_store();
|
||||
|
||||
assert_eq!(dashboard.sessions.len(), 1);
|
||||
assert!(dashboard
|
||||
.selected_output_text()
|
||||
.contains("persisted-output"));
|
||||
assert!(!dashboard.selected_output_text().contains("cache-only"));
|
||||
|
||||
let _ = std::fs::remove_file(db_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_cache_tracks_session_add_delete_and_same_id_recreation() -> Result<()> {
|
||||
let db_path =
|
||||
std::env::temp_dir().join(format!("ecc2-output-lifecycle-{}.db", Uuid::new_v4()));
|
||||
let db = StateStore::open(&db_path)?;
|
||||
db.insert_session(&sample_session(
|
||||
"session-1",
|
||||
"claude",
|
||||
SessionState::Running,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
))?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "first-session")?;
|
||||
|
||||
let mut dashboard = Dashboard::new(db, Config::default());
|
||||
let external = StateStore::open(&db_path)?;
|
||||
external.insert_session(&sample_session(
|
||||
"session-2",
|
||||
"codex",
|
||||
SessionState::Running,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
))?;
|
||||
external.append_output_line("session-2", OutputStream::Stderr, "new-session")?;
|
||||
dashboard.sync_from_store();
|
||||
|
||||
assert!(dashboard
|
||||
.sessions
|
||||
.iter()
|
||||
.any(|session| session.id == "session-2"));
|
||||
assert_eq!(
|
||||
dashboard.session_output_cache["session-2"][0].text,
|
||||
"new-session"
|
||||
);
|
||||
|
||||
external.delete_session("session-2")?;
|
||||
let replacement_time = Utc::now() + chrono::Duration::seconds(1);
|
||||
external.insert_session(&Session {
|
||||
created_at: replacement_time,
|
||||
updated_at: replacement_time,
|
||||
last_heartbeat_at: replacement_time,
|
||||
..sample_session("session-2", "codex", SessionState::Running, None, 0, 0)
|
||||
})?;
|
||||
external.append_output_line("session-2", OutputStream::Stdout, "replacement-session")?;
|
||||
dashboard.sync_from_store();
|
||||
|
||||
let replacement = &dashboard.session_output_cache["session-2"];
|
||||
assert_eq!(replacement.len(), 1);
|
||||
assert_eq!(replacement[0].text, "replacement-session");
|
||||
|
||||
let _ = std::fs::remove_file(db_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_cache_retries_delta_after_transient_output_query_failure() -> Result<()> {
|
||||
let db_path =
|
||||
std::env::temp_dir().join(format!("ecc2-output-query-retry-{}.db", Uuid::new_v4()));
|
||||
let db = StateStore::open(&db_path)?;
|
||||
db.insert_session(&sample_session(
|
||||
"session-1",
|
||||
"claude",
|
||||
SessionState::Running,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
))?;
|
||||
db.append_output_line("session-1", OutputStream::Stdout, "persisted-before")?;
|
||||
|
||||
let mut dashboard = Dashboard::new(db, Config::default());
|
||||
dashboard
|
||||
.session_output_cache
|
||||
.get_mut("session-1")
|
||||
.expect("hydrated output")
|
||||
.push(test_output_line(OutputStream::Stdout, "cache-only"));
|
||||
let cursor = dashboard.output_cursor;
|
||||
|
||||
let schema = rusqlite::Connection::open(&db_path)?;
|
||||
schema.execute(
|
||||
"ALTER TABLE session_output RENAME TO unavailable_session_output",
|
||||
[],
|
||||
)?;
|
||||
dashboard.sync_output_cache();
|
||||
assert_eq!(dashboard.output_cursor, cursor);
|
||||
assert!(dashboard.session_output_cache["session-1"]
|
||||
.iter()
|
||||
.any(|line| line.text == "cache-only"));
|
||||
|
||||
schema.execute(
|
||||
"ALTER TABLE unavailable_session_output RENAME TO session_output",
|
||||
[],
|
||||
)?;
|
||||
StateStore::open(&db_path)?.append_output_line(
|
||||
"session-1",
|
||||
OutputStream::Stderr,
|
||||
"persisted-after",
|
||||
)?;
|
||||
dashboard.sync_output_cache();
|
||||
|
||||
let output = &dashboard.session_output_cache["session-1"];
|
||||
assert!(output.iter().any(|line| line.text == "cache-only"));
|
||||
assert_eq!(
|
||||
output
|
||||
.iter()
|
||||
.filter(|line| line.text == "persisted-after")
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_file(db_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_output_records_bounds_each_session_to_the_latest_window() {
|
||||
let mut cache = HashMap::from([(
|
||||
"session-2".to_string(),
|
||||
vec![test_output_line(OutputStream::Stderr, "other-session")],
|
||||
)]);
|
||||
let records = (0..(OUTPUT_BUFFER_LIMIT + 5))
|
||||
.map(|index| crate::session::store::SessionOutputRecord {
|
||||
id: index as i64 + 1,
|
||||
session_id: "session-1".to_string(),
|
||||
line: test_output_line(OutputStream::Stdout, &format!("line-{index}")),
|
||||
})
|
||||
.collect();
|
||||
|
||||
cache = append_output_records(cache, records);
|
||||
|
||||
let session_lines = cache.get("session-1").expect("session output");
|
||||
assert_eq!(session_lines.len(), OUTPUT_BUFFER_LIMIT);
|
||||
assert_eq!(
|
||||
session_lines.first().map(|line| line.text.as_str()),
|
||||
Some("line-5")
|
||||
);
|
||||
assert_eq!(
|
||||
session_lines.last().map(|line| line.text.as_str()),
|
||||
Some(format!("line-{}", OUTPUT_BUFFER_LIMIT + 4).as_str())
|
||||
);
|
||||
assert_eq!(cache["session-2"][0].text, "other-session");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_search_tracks_matches_and_sets_navigation_note() {
|
||||
let mut dashboard = test_dashboard(
|
||||
@@ -14917,8 +15210,10 @@ diff --git a/src/lib.rs b/src/lib.rs
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let output_store = SessionOutputStore::default();
|
||||
let output_rx = output_store.subscribe();
|
||||
let session_output_generations = sessions
|
||||
.iter()
|
||||
.map(|session| (session.id.clone(), session.created_at))
|
||||
.collect();
|
||||
let mut session_table_state = TableState::default();
|
||||
if !sessions.is_empty() {
|
||||
session_table_state.select(Some(selected_session));
|
||||
@@ -14928,13 +15223,13 @@ diff --git a/src/lib.rs b/src/lib.rs
|
||||
db: StateStore::open(Path::new(":memory:")).expect("open test db"),
|
||||
pane_size_percent: configured_pane_size(&cfg, cfg.pane_layout),
|
||||
cfg,
|
||||
output_store,
|
||||
output_rx,
|
||||
notifier,
|
||||
webhook_notifier,
|
||||
sessions,
|
||||
session_harnesses,
|
||||
session_output_cache: HashMap::new(),
|
||||
session_output_generations,
|
||||
output_cursor: None,
|
||||
unread_message_counts: HashMap::new(),
|
||||
approval_queue_counts: HashMap::new(),
|
||||
approval_queue_preview: Vec::new(),
|
||||
|
||||
+2
-1
@@ -35,12 +35,13 @@ $scriptDir = Split-Path -Parent $scriptPath
|
||||
$installerScript = Join-Path -Path (Join-Path -Path $scriptDir -ChildPath 'scripts') -ChildPath 'install-apply.js'
|
||||
|
||||
# Auto-install Node dependencies when running from a git clone
|
||||
# SECURITY: --ignore-scripts blocks preinstall/postinstall RCE from a compromised dependency.
|
||||
$nodeModules = Join-Path -Path $scriptDir -ChildPath 'node_modules'
|
||||
if (-not (Test-Path -LiteralPath $nodeModules)) {
|
||||
Write-Host '[ECC] Installing dependencies...'
|
||||
Push-Location $scriptDir
|
||||
try {
|
||||
& npm install --no-audit --no-fund --loglevel=error
|
||||
& npm install --ignore-scripts --no-audit --no-fund --loglevel=error
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "npm install failed with exit code $LASTEXITCODE"
|
||||
exit $LASTEXITCODE
|
||||
|
||||
+4
-2
@@ -14,10 +14,12 @@ while [ -L "$SCRIPT_PATH" ]; do
|
||||
done
|
||||
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
||||
|
||||
# Auto-install Node dependencies when running from a git clone
|
||||
# Auto-install Node dependencies when running from a git clone.
|
||||
# SECURITY: --ignore-scripts blocks preinstall/postinstall RCE from a
|
||||
# compromised dependency. ECC deps are pure JS (no native build step).
|
||||
if [ ! -d "$SCRIPT_DIR/node_modules" ]; then
|
||||
echo "[ECC] Installing dependencies..."
|
||||
(cd "$SCRIPT_DIR" && npm install --no-audit --no-fund --loglevel=error)
|
||||
(cd "$SCRIPT_DIR" && npm install --ignore-scripts --no-audit --no-fund --loglevel=error)
|
||||
fi
|
||||
|
||||
# On MSYS2/Git Bash, convert the POSIX path to a Windows path so Node.js
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "commonjs"
|
||||
}
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ecc-universal",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ecc-universal",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "2.2.5",
|
||||
|
||||
+13
-1
@@ -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,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
@@ -20,7 +20,7 @@ classifiers = [
|
||||
|
||||
dependencies = [
|
||||
"anthropic>=0.120.2",
|
||||
"openai>=1.30.0",
|
||||
"openai>=2.34.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
+23
-16
@@ -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
@@ -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
Reference in New Issue
Block a user