mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-16 16:36:53 +08:00
Merge branch 'main' into feat-ml-adoption
This commit is contained in:
commit
515c275b30
@ -6,7 +6,7 @@
|
|||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "ecc",
|
"name": "ecc",
|
||||||
"version": "2.0.0-rc.1",
|
"version": "2.0.0",
|
||||||
"source": {
|
"source": {
|
||||||
"source": "local",
|
"source": "local",
|
||||||
"path": "./"
|
"path": "./"
|
||||||
|
|||||||
@ -13,6 +13,8 @@
|
|||||||
"source": "./",
|
"source": "./",
|
||||||
"description": "Harness-native ECC operator layer - 64 agents, 262 skills, 84 legacy command shims, reusable hooks, rules, selective install profiles, and production-ready workflows for Claude Code, Codex, OpenCode, Cursor, and related agent harnesses",
|
"description": "Harness-native ECC operator layer - 64 agents, 262 skills, 84 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.0.0-rc.1",
|
"version": "2.0.0-rc.1",
|
||||||
|
"description": "Harness-native ECC operator layer - 64 agents, 261 skills, 84 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.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Affaan Mustafa",
|
"name": "Affaan Mustafa",
|
||||||
"email": "me@affaanmustafa.com"
|
"email": "me@affaanmustafa.com"
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
"name": "ecc",
|
"name": "ecc",
|
||||||
"version": "2.0.0-rc.1",
|
"version": "2.0.0-rc.1",
|
||||||
"description": "Harness-native ECC plugin for engineering teams - 64 agents, 262 skills, 84 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
|
"description": "Harness-native ECC plugin for engineering teams - 64 agents, 262 skills, 84 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"description": "Harness-native ECC plugin for engineering teams - 64 agents, 261 skills, 84 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Affaan Mustafa",
|
"name": "Affaan Mustafa",
|
||||||
"url": "https://x.com/affaanmustafa"
|
"url": "https://x.com/affaanmustafa"
|
||||||
|
|||||||
36
.coderabbit.yaml
Normal file
36
.coderabbit.yaml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||||
|
language: "en-US"
|
||||||
|
early_access: false
|
||||||
|
tone_instructions: "Be direct, concise, and evidence-led. Prioritize actionable findings over praise."
|
||||||
|
|
||||||
|
reviews:
|
||||||
|
profile: "assertive"
|
||||||
|
request_changes_workflow: false
|
||||||
|
high_level_summary: true
|
||||||
|
high_level_summary_in_walkthrough: true
|
||||||
|
review_status: true
|
||||||
|
review_details: true
|
||||||
|
commit_status: true
|
||||||
|
fail_commit_status: true
|
||||||
|
auto_review:
|
||||||
|
enabled: true
|
||||||
|
drafts: false
|
||||||
|
path_instructions:
|
||||||
|
- path: ".github/workflows/**"
|
||||||
|
instructions: |
|
||||||
|
Treat workflow changes as security-sensitive. Flag unpinned third-party actions, broad write permissions, persisted checkout credentials in write-token jobs, pull_request_target misuse, and untrusted GitHub context interpolated into shell commands.
|
||||||
|
- path: "{scripts,bin}/**"
|
||||||
|
instructions: |
|
||||||
|
Focus on command injection, unsafe subprocess usage, path traversal, SSRF, secret exposure, and missing tests for new CLI behavior.
|
||||||
|
- path: "skills/**/scripts/**"
|
||||||
|
instructions: |
|
||||||
|
Review generated or imported scripts as untrusted-input tooling. Flag RCE, path traversal, network fetches without validation, and writes outside the expected workspace.
|
||||||
|
- path: "{skills,commands,agents,rules}/**"
|
||||||
|
instructions: |
|
||||||
|
Focus on prompt-injection resilience, tool-permission scope, destructive action guards, and secret exfiltration risks.
|
||||||
|
- path: "{SECURITY.md,docs/security/**}"
|
||||||
|
instructions: |
|
||||||
|
Check that official distribution surfaces, disclosure guidance, and supply-chain rules stay accurate and do not endorse unofficial packages.
|
||||||
|
|
||||||
|
chat:
|
||||||
|
auto_reply: true
|
||||||
@ -49,12 +49,9 @@ stay below provider length limits.
|
|||||||
|
|
||||||
| Server | Purpose |
|
| Server | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `github` | GitHub API access |
|
| `chrome-devtools` | Interactive browser debugging via Chrome DevTools (CDP sessions, performance traces, console/network inspection) |
|
||||||
| `context7` | Live documentation lookup |
|
|
||||||
| `exa` | Neural web search |
|
The former defaults (`github`, `context7`, `exa`, `memory`, `playwright`, `sequential-thinking`) were retired in the June 2026 connector audit — their jobs are covered by skills wrapping CLIs/REST APIs or by harness-native features. They remain available as opt-in entries in `mcp-configs/mcp-servers.json`. See `docs/MCP-CONNECTOR-POLICY.md` for the policy and the per-connector rationale.
|
||||||
| `memory` | Persistent memory across sessions |
|
|
||||||
| `playwright` | Browser automation & E2E testing |
|
|
||||||
| `sequential-thinking` | Step-by-step reasoning |
|
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ecc",
|
"name": "ecc",
|
||||||
"version": "2.0.0-rc.1",
|
"version": "2.0.0",
|
||||||
"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.",
|
"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": {
|
"author": {
|
||||||
"name": "Affaan Mustafa",
|
"name": "Affaan Mustafa",
|
||||||
|
|||||||
4
.github/copilot-instructions.md
vendored
4
.github/copilot-instructions.md
vendored
@ -83,10 +83,11 @@ Use AAA structure (Arrange / Act / Assert) and descriptive test names that expla
|
|||||||
|
|
||||||
Types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`
|
Types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`
|
||||||
|
|
||||||
PR checklist before requesting review:
|
PR checklist before requesting sponsored review:
|
||||||
- CI passing, merge conflicts resolved, branch up to date with target
|
- CI passing, merge conflicts resolved, branch up to date with target
|
||||||
- Full diff reviewed (`git diff [base-branch]...HEAD`)
|
- Full diff reviewed (`git diff [base-branch]...HEAD`)
|
||||||
- Test plan included in PR description
|
- Test plan included in PR description
|
||||||
|
- Code review is handled by CodeRabbit and Greptile. Do not add or route PR code review through Copilot, Claude, Codex, or other reviewer bots.
|
||||||
|
|
||||||
## Code Quality Checklist
|
## Code Quality Checklist
|
||||||
|
|
||||||
@ -107,7 +108,6 @@ Use these prompts in Copilot Chat for deeper workflows:
|
|||||||
|--------|-------------|---------|
|
|--------|-------------|---------|
|
||||||
| `/plan` | Complex feature | Phased implementation plan |
|
| `/plan` | Complex feature | Phased implementation plan |
|
||||||
| `/tdd` | New feature or bug fix | Test-driven development cycle |
|
| `/tdd` | New feature or bug fix | Test-driven development cycle |
|
||||||
| `/code-review` | After writing code | Quality and security review |
|
|
||||||
| `/security-review` | Before a release | Deep security analysis |
|
| `/security-review` | Before a release | Deep security analysis |
|
||||||
| `/build-fix` | Build/CI failure | Systematic error resolution |
|
| `/build-fix` | Build/CI failure | Systematic error resolution |
|
||||||
| `/refactor` | Code maintenance | Dead code cleanup and simplification |
|
| `/refactor` | Code maintenance | Dead code cleanup and simplification |
|
||||||
|
|||||||
83
.github/dependabot.yml
vendored
83
.github/dependabot.yml
vendored
@ -4,18 +4,99 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
labels:
|
labels:
|
||||||
- "dependencies"
|
- "dependencies"
|
||||||
|
- "npm"
|
||||||
groups:
|
groups:
|
||||||
minor-and-patch:
|
npm-minor-and-patch:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
update-types:
|
update-types:
|
||||||
- "minor"
|
- "minor"
|
||||||
- "patch"
|
- "patch"
|
||||||
|
npm-security:
|
||||||
|
applies-to: "security-updates"
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
labels:
|
labels:
|
||||||
- "dependencies"
|
- "dependencies"
|
||||||
- "ci"
|
- "ci"
|
||||||
|
groups:
|
||||||
|
actions-security:
|
||||||
|
applies-to: "security-updates"
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
actions-minor-and-patch:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
|
||||||
|
- package-ecosystem: "pip"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "python"
|
||||||
|
groups:
|
||||||
|
pip-security:
|
||||||
|
applies-to: "security-updates"
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
pip-minor-and-patch:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
|
||||||
|
- package-ecosystem: "pip"
|
||||||
|
directory: "/skills/skill-comply"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "python"
|
||||||
|
groups:
|
||||||
|
skill-comply-pip-security:
|
||||||
|
applies-to: "security-updates"
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
skill-comply-pip-minor-and-patch:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
|
||||||
|
- package-ecosystem: "cargo"
|
||||||
|
directory: "/ecc2"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "rust"
|
||||||
|
groups:
|
||||||
|
cargo-security:
|
||||||
|
applies-to: "security-updates"
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
cargo-minor-and-patch:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
|
|||||||
56
.github/prompts/code-review.prompt.md
vendored
56
.github/prompts/code-review.prompt.md
vendored
@ -1,56 +0,0 @@
|
|||||||
---
|
|
||||||
agent: agent
|
|
||||||
description: Comprehensive code quality and security review of the selected code or recent changes
|
|
||||||
---
|
|
||||||
|
|
||||||
# Code Review
|
|
||||||
|
|
||||||
Review the selected code (or the current diff if nothing is selected) across four dimensions. Only report issues you are **confident about** — flag uncertainty explicitly rather than guessing.
|
|
||||||
|
|
||||||
## Dimensions
|
|
||||||
|
|
||||||
### 1. Security (CRITICAL — block ship if found)
|
|
||||||
- Hardcoded secrets, tokens, API keys, passwords
|
|
||||||
- Missing input validation or sanitization at system boundaries
|
|
||||||
- SQL/NoSQL injection risk (string interpolation in queries)
|
|
||||||
- XSS risk (unsanitized HTML output)
|
|
||||||
- Auth/authz checks missing or client-side only
|
|
||||||
- Sensitive data in logs or error messages exposed to clients
|
|
||||||
- Missing rate limiting on public endpoints
|
|
||||||
|
|
||||||
### 2. Code Quality (HIGH)
|
|
||||||
- Mutation of existing state instead of creating new objects
|
|
||||||
- Functions over 50 lines or files over 800 lines
|
|
||||||
- Nesting deeper than 4 levels
|
|
||||||
- Duplicated logic that should be extracted
|
|
||||||
- Misleading or non-descriptive names
|
|
||||||
|
|
||||||
### 3. Error Handling (HIGH)
|
|
||||||
- Silently swallowed errors (`catch {}`, empty catch blocks)
|
|
||||||
- Missing error handling at async boundaries
|
|
||||||
- Errors returned but not checked by callers
|
|
||||||
- User-facing error messages leaking internal details
|
|
||||||
|
|
||||||
### 4. Test Coverage (MEDIUM)
|
|
||||||
- Missing tests for new logic
|
|
||||||
- Tests that only test happy paths (missing error/edge cases)
|
|
||||||
- Assertions that always pass
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
For each issue found:
|
|
||||||
|
|
||||||
```
|
|
||||||
**[CRITICAL|HIGH|MEDIUM|LOW]** — [File:Line if known]
|
|
||||||
Issue: [What is wrong]
|
|
||||||
Fix: [Concrete suggestion]
|
|
||||||
```
|
|
||||||
|
|
||||||
End with a summary:
|
|
||||||
```
|
|
||||||
## Summary
|
|
||||||
- Critical: N
|
|
||||||
- High: N
|
|
||||||
- Medium: N
|
|
||||||
- Approved to ship: yes / no (fix CRITICAL and HIGH first)
|
|
||||||
```
|
|
||||||
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@ -36,6 +36,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Node.js ${{ matrix.node }}
|
- name: Setup Node.js ${{ matrix.node }}
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
@ -114,6 +116,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
@ -175,6 +179,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
@ -200,6 +206,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
@ -227,6 +235,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
|
|||||||
29
.github/workflows/release-announce.yml
vendored
Normal file
29
.github/workflows/release-announce.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
name: Release Announce
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
discussions: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
announce:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Announce release to Discord + Discussions
|
||||||
|
run: node scripts/discord/release-announce.mjs
|
||||||
|
env:
|
||||||
|
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
|
||||||
|
DISCORD_ANNOUNCE_CHANNEL_ID: ${{ secrets.DISCORD_ANNOUNCE_CHANNEL_ID }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
|
RELEASE_NAME: ${{ github.event.release.name }}
|
||||||
|
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||||
|
RELEASE_URL: ${{ github.event.release.html_url }}
|
||||||
|
RELEASE_BODY: ${{ github.event.release.body }}
|
||||||
2
.github/workflows/reusable-test.yml
vendored
2
.github/workflows/reusable-test.yml
vendored
@ -28,6 +28,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
|
|||||||
2
.github/workflows/reusable-validate.yml
vendored
2
.github/workflows/reusable-validate.yml
vendored
@ -18,6 +18,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
|
|||||||
24
.mcp.json
24
.mcp.json
@ -1,28 +1,8 @@
|
|||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"github": {
|
"chrome-devtools": {
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "@modelcontextprotocol/server-github@2025.4.8"]
|
"args": ["-y", "chrome-devtools-mcp@latest"]
|
||||||
},
|
|
||||||
"context7": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": ["-y", "@upstash/context7-mcp@2.1.4"]
|
|
||||||
},
|
|
||||||
"exa": {
|
|
||||||
"type": "http",
|
|
||||||
"url": "https://mcp.exa.ai/mcp"
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": ["-y", "@modelcontextprotocol/server-memory@2026.1.26"]
|
|
||||||
},
|
|
||||||
"playwright": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": ["-y", "@playwright/mcp@0.0.69", "--extension"]
|
|
||||||
},
|
|
||||||
"sequential-thinking": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": ["-y", "@modelcontextprotocol/server-sequential-thinking@2025.12.18"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
.opencode/package-lock.json
generated
4
.opencode/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ecc-universal",
|
"name": "ecc-universal",
|
||||||
"version": "2.0.0-rc.1",
|
"version": "2.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ecc-universal",
|
"name": "ecc-universal",
|
||||||
"version": "2.0.0-rc.1",
|
"version": "2.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@opencode-ai/plugin": "^1.4.3",
|
"@opencode-ai/plugin": "^1.4.3",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ecc-universal",
|
"name": "ecc-universal",
|
||||||
"version": "2.0.0-rc.1",
|
"version": "2.0.0",
|
||||||
"description": "ECC plugin for OpenCode - agents, commands, hooks, and skills",
|
"description": "ECC plugin for OpenCode - agents, commands, hooks, and skills",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@ -453,7 +453,7 @@ export const ECCHooksPlugin: ECCHooksPluginFn = async ({
|
|||||||
const contextBlock = [
|
const contextBlock = [
|
||||||
"# ECC Context (preserve across compaction)",
|
"# ECC Context (preserve across compaction)",
|
||||||
"",
|
"",
|
||||||
"## Active Plugin: ECC v2.0.0-rc.1",
|
"## Active Plugin: ECC v2.0.0",
|
||||||
"- Hooks: file.edited, tool.execute.before/after, session.created/idle/deleted, shell.env, compacting, permission.ask",
|
"- 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",
|
"- 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)",
|
"- 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)",
|
||||||
|
|||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -7,10 +7,6 @@
|
|||||||
{ "file": ".github/copilot-instructions.md" },
|
{ "file": ".github/copilot-instructions.md" },
|
||||||
{ "text": "Always write tests before implementation (TDD). Use Arrange-Act-Assert structure. Target 80%+ coverage. Write descriptive test names that explain the behavior under test, not just the function name." }
|
{ "text": "Always write tests before implementation (TDD). Use Arrange-Act-Assert structure. Target 80%+ coverage. Write descriptive test names that explain the behavior under test, not just the function name." }
|
||||||
],
|
],
|
||||||
"github.copilot.chat.reviewSelection.instructions": [
|
|
||||||
{ "file": ".github/copilot-instructions.md" },
|
|
||||||
{ "text": "Review for: (1) security issues — hardcoded secrets, missing input validation, injection risks, (2) code quality — mutation, deep nesting, large functions, (3) error handling — swallowed errors, missing boundary validation, (4) test coverage gaps." }
|
|
||||||
],
|
|
||||||
"github.copilot.chat.commitMessageGeneration.instructions": [
|
"github.copilot.chat.commitMessageGeneration.instructions": [
|
||||||
{ "text": "Use conventional commit format: <type>: <description>. Types: feat, fix, refactor, docs, test, chore, perf, ci. Keep the subject line under 72 characters. Focus on WHY the change was made, not WHAT changed." }
|
{ "text": "Use conventional commit format: <type>: <description>. Types: feat, fix, refactor, docs, test, chore, perf, ci. Keep the subject line under 72 characters. Focus on WHY the change was made, not WHAT changed." }
|
||||||
]
|
]
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This is a **production-ready AI coding plugin** providing 64 specialized agents, 262 skills, 84 commands, and automated hook workflows for software development.
|
This is a **production-ready AI coding plugin** providing 64 specialized agents, 262 skills, 84 commands, and automated hook workflows for software development.
|
||||||
|
|
||||||
**Version:** 2.0.0-rc.1
|
**Version:** 2.0.0
|
||||||
|
|
||||||
## Core Principles
|
## Core Principles
|
||||||
|
|
||||||
|
|||||||
25
CHANGELOG.md
25
CHANGELOG.md
@ -1,5 +1,30 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Default MCP connector set reduced to a single connector (`chrome-devtools`) per the new connector policy (`docs/MCP-CONNECTOR-POLICY.md`). The six previous defaults (`github`, `context7`, `exa`, `memory`, `playwright`, `sequential-thinking`) were retired after the June 2026 audit: their jobs are covered by skills wrapping CLIs/REST APIs (`github-ops`, `documentation-lookup`, `exa-search`, e2e skills) or by harness-native features (memory, extended thinking, web search). All six remain opt-in via `mcp-configs/mcp-servers.json`.
|
||||||
|
|
||||||
|
## 2.0.0 - 2026-06-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Discord community launch: server + GitHub PR/issue/release feed, `release-announce.yml` workflow (announce + pin + Discussions cross-post), and a dependency-free community bot (`scripts/discord/ecc-bot.mjs`) with `/ecc`, `/help`, `/skill`, `/docs`, `/release`.
|
||||||
|
- `orch-*` orchestrator skill family and dynamic workflow team orchestration.
|
||||||
|
- `kubernetes-patterns` skill, worktree-lifecycle service, MCP inventory (`ecc.mcp.v1`), codex-worktree and opencode session adapters.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Plugin hooks silently no-oped on Node 21+ (`require.main` undefined under `node -e`).
|
||||||
|
- Windows reliability: `CLAUDE_PLUGIN_ROOT` normalization, stdin prompt passing, symlink/chmod test guards.
|
||||||
|
- Session-end `$`-sequence corruption, project-detect boundary matching, install manifest gaps, corrupted legacy shim truncation.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Version graduated to 2.0.0 stable across package, plugin, marketplace, OpenCode, and agent metadata.
|
||||||
|
- Smaller default OpenCode install surface; `rules/zh` removed from the always-loaded default install.
|
||||||
|
|
||||||
## 2.0.0-rc.1 - 2026-04-28
|
## 2.0.0-rc.1 - 2026-04-28
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|||||||
93
README.md
93
README.md
@ -19,7 +19,7 @@
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
> **182K+ stars** | **28K+ forks** | **170+ contributors** | **12+ language ecosystems** | **Cross-harness agent workflows**
|
> **211.9K+ stars** | **32.5K+ forks** | **230+ contributors** | **12+ language ecosystems** | **Cross-harness agent workflows**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ Not just configs. A complete system: skills, instincts, memory optimization, con
|
|||||||
|
|
||||||
Works across **Codex**, **Claude Code**, **Cursor**, **OpenCode**, **Gemini**, **Zed**, **GitHub Copilot**, and other AI agent harnesses.
|
Works across **Codex**, **Claude Code**, **Cursor**, **OpenCode**, **Gemini**, **Zed**, **GitHub Copilot**, and other AI agent harnesses.
|
||||||
|
|
||||||
ECC v2.0.0-rc.1 adds the public Hermes operator story on top of that reusable layer: start with the [Hermes setup guide](docs/HERMES-SETUP.md), then review the [rc.1 release notes](docs/releases/2.0.0-rc.1/release-notes.md) and [cross-harness architecture](docs/architecture/cross-harness.md).
|
ECC v2.0.0 adds the public Hermes operator story on top of that reusable layer: start with the [Hermes setup guide](docs/HERMES-SETUP.md), then review the [2.0.0 release notes](docs/releases/2.0.0/release-notes.md) and [cross-harness architecture](docs/architecture/cross-harness.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -76,6 +76,13 @@ ECC v2.0.0-rc.1 adds the public Hermes operator story on top of that reusable la
|
|||||||
|
|
||||||
<sub>**OSS stays free.** This repo is MIT-licensed forever. ECC Pro is the hosted GitHub App for private repos. <a href="https://github.com/sponsors/affaan-m">Sponsors</a> and <a href="https://ecc.tools/pricing">Pro subscribers</a> fund the work — that's why a single maintainer ships weekly across 7 harnesses.</sub>
|
<sub>**OSS stays free.** This repo is MIT-licensed forever. ECC Pro is the hosted GitHub App for private repos. <a href="https://github.com/sponsors/affaan-m">Sponsors</a> and <a href="https://ecc.tools/pricing">Pro subscribers</a> fund the work — that's why a single maintainer ships weekly across 7 harnesses.</sub>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<sub><strong>Business sponsors</strong></sub><br />
|
||||||
|
<a href="https://www.coderabbit.ai"><img src="assets/images/sponsors/coderabbit.png" width="72" alt="CodeRabbit logo" /></a>
|
||||||
|
|
||||||
|
<a href="https://www.greptile.com/go/ecc"><img src="assets/images/sponsors/greptile.png" width="72" alt="Greptile logo" /></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The Guides
|
## The Guides
|
||||||
@ -85,17 +92,17 @@ This repo is the raw code only. The guides explain everything.
|
|||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="33%">
|
<td width="33%">
|
||||||
<a href="https://x.com/affaanmustafa/status/2012378465664745795">
|
<a href="https://x.com/affaan/status/2012378465664745795">
|
||||||
<img src="./assets/images/guides/shorthand-guide.png" alt="The Shorthand Guide to ECC" />
|
<img src="./assets/images/guides/shorthand-guide.png" alt="The Shorthand Guide to ECC" />
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td width="33%">
|
<td width="33%">
|
||||||
<a href="https://x.com/affaanmustafa/status/2014040193557471352">
|
<a href="https://x.com/affaan/status/2014040193557471352">
|
||||||
<img src="./assets/images/guides/longform-guide.png" alt="The Longform Guide to ECC" />
|
<img src="./assets/images/guides/longform-guide.png" alt="The Longform Guide to ECC" />
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td width="33%">
|
<td width="33%">
|
||||||
<a href="https://x.com/affaanmustafa/status/2033263813387223421">
|
<a href="https://x.com/affaan/status/2033263813387223421">
|
||||||
<img src="./assets/images/security/security-guide-header.png" alt="The Shorthand Guide to Everything Agentic Security" />
|
<img src="./assets/images/security/security-guide-header.png" alt="The Shorthand Guide to Everything Agentic Security" />
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@ -120,6 +127,10 @@ This repo is the raw code only. The guides explain everything.
|
|||||||
|
|
||||||
## What's New
|
## What's New
|
||||||
|
|
||||||
|
### v2.0.0 — The Agent Harness Operating System (Jun 2026)
|
||||||
|
|
||||||
|
Stable graduation of the 2.0 line: 261 skills, the control-pane substrate (session adapters + MCP inventory), the worktree-lifecycle service, the `orch-*` orchestrator family, and the launch of the [ECC Discord community](https://discord.gg/36yGMHGFbR). Full notes: [docs/releases/2.0.0/release-notes.md](docs/releases/2.0.0/release-notes.md).
|
||||||
|
|
||||||
### v2.0.0-rc.1 — Surface Refresh, Operator Workflows, and ECC 2.0 Alpha (Apr 2026)
|
### v2.0.0-rc.1 — Surface Refresh, Operator Workflows, and ECC 2.0 Alpha (Apr 2026)
|
||||||
|
|
||||||
- **Dashboard GUI** — New Tkinter-based desktop application (`ecc_dashboard.py` or `npm run dashboard`) with dark/light theme toggle, font customization, and project logo in header and taskbar.
|
- **Dashboard GUI** — New Tkinter-based desktop application (`ecc_dashboard.py` or `npm run dashboard`) with dark/light theme toggle, font customization, and project logo in header and taskbar.
|
||||||
@ -755,17 +766,17 @@ This analyzes your git history locally and generates SKILL.md files.
|
|||||||
|
|
||||||
For advanced features (10k+ commits, auto-PRs, team sharing):
|
For advanced features (10k+ commits, auto-PRs, team sharing):
|
||||||
|
|
||||||
[Install GitHub App](https://github.com/apps/skill-creator) | [ecc.tools](https://ecc.tools)
|
[Install ECC Tools GitHub App](https://github.com/apps/ecc-tools) | [ecc.tools](https://ecc.tools)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Comment on any issue:
|
# Comment on any issue:
|
||||||
/skill-creator analyze
|
/ecc-tools analyze
|
||||||
|
|
||||||
# Or auto-triggers on push to default branch
|
# Or run against a repo from the hosted app
|
||||||
```
|
```
|
||||||
|
|
||||||
Both options create:
|
Both options create:
|
||||||
- **SKILL.md files** - Ready-to-use skills for Claude Code
|
- **SKILL.md files** - Ready-to-use skills for the active harness
|
||||||
- **Instinct collections** - For continuous-learning-v2
|
- **Instinct collections** - For continuous-learning-v2
|
||||||
- **Pattern extraction** - Learns from your commit history
|
- **Pattern extraction** - Learns from your commit history
|
||||||
|
|
||||||
@ -971,10 +982,12 @@ Use Claude Code's `/mcp` command or CLI-managed MCP setup for live Claude Code s
|
|||||||
|
|
||||||
For repo-local MCP access, copy desired MCP server definitions from `mcp-configs/mcp-servers.json` into a project-scoped `.mcp.json`.
|
For repo-local MCP access, copy desired MCP server definitions from `mcp-configs/mcp-servers.json` into a project-scoped `.mcp.json`.
|
||||||
|
|
||||||
|
ECC ships exactly one default connector (`chrome-devtools`); everything else is a skill wrapping a CLI/REST API or an opt-in catalog entry. The rule and the June 2026 audit that retired the previous six defaults live in [docs/MCP-CONNECTOR-POLICY.md](docs/MCP-CONNECTOR-POLICY.md).
|
||||||
|
|
||||||
If you already run your own copies of ECC-bundled MCPs, set:
|
If you already run your own copies of ECC-bundled MCPs, set:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export ECC_DISABLED_MCPS="github,context7,exa,playwright,sequential-thinking,memory"
|
export ECC_DISABLED_MCPS="chrome-devtools"
|
||||||
```
|
```
|
||||||
|
|
||||||
ECC-managed install and Codex sync flows will skip or remove those bundled servers instead of re-adding duplicates. `ECC_DISABLED_MCPS` is an ECC install/sync filter, not a live Claude Code toggle.
|
ECC-managed install and Codex sync flows will skip or remove those bundled servers instead of re-adding duplicates. `ECC_DISABLED_MCPS` is an ECC install/sync filter, not a live Claude Code toggle.
|
||||||
@ -1233,14 +1246,6 @@ Please contribute! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|||||||
- Testing strategies (different frameworks, visual regression)
|
- Testing strategies (different frameworks, visual regression)
|
||||||
- Domain-specific knowledge (ML, data engineering, mobile)
|
- Domain-specific knowledge (ML, data engineering, mobile)
|
||||||
|
|
||||||
### Community Ecosystem Notes
|
|
||||||
|
|
||||||
These are not bundled with ECC and are not audited by this repo, but they are worth knowing about if you are exploring the broader Claude Code skills ecosystem:
|
|
||||||
|
|
||||||
- [claude-seo](https://github.com/AgriciDaniel/claude-seo) — SEO-focused skill and agent collection
|
|
||||||
- [claude-ads](https://github.com/AgriciDaniel/claude-ads) — Ad-audit and paid-growth workflow collection
|
|
||||||
- [claude-cybersecurity](https://github.com/AgriciDaniel/claude-cybersecurity) — Security-oriented skill and agent collection
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Cursor IDE Support
|
## Cursor IDE Support
|
||||||
@ -1579,10 +1584,9 @@ ECC provides **GitHub Copilot support** for VS Code via Copilot Chat's native in
|
|||||||
| Component | File | Purpose |
|
| Component | File | Purpose |
|
||||||
|-----------|------|---------|
|
|-----------|------|---------|
|
||||||
| Core instructions | `.github/copilot-instructions.md` | Always-loaded rules: coding style, security, testing, git workflow |
|
| Core instructions | `.github/copilot-instructions.md` | Always-loaded rules: coding style, security, testing, git workflow |
|
||||||
| VS Code settings | `.vscode/settings.json` | Per-task instruction files for code gen, test gen, review, and commit messages |
|
| VS Code settings | `.vscode/settings.json` | Per-task instruction files for code gen, test gen, and commit messages |
|
||||||
| Plan prompt | `.github/prompts/plan.prompt.md` | Phased implementation planning |
|
| Plan prompt | `.github/prompts/plan.prompt.md` | Phased implementation planning |
|
||||||
| TDD prompt | `.github/prompts/tdd.prompt.md` | Red-Green-Improve cycle |
|
| TDD prompt | `.github/prompts/tdd.prompt.md` | Red-Green-Improve cycle |
|
||||||
| Code review prompt | `.github/prompts/code-review.prompt.md` | Quality and security review |
|
|
||||||
| Security review prompt | `.github/prompts/security-review.prompt.md` | Deep OWASP-aligned security analysis |
|
| Security review prompt | `.github/prompts/security-review.prompt.md` | Deep OWASP-aligned security analysis |
|
||||||
| Build fix prompt | `.github/prompts/build-fix.prompt.md` | Systematic build and CI error resolution |
|
| Build fix prompt | `.github/prompts/build-fix.prompt.md` | Systematic build and CI error resolution |
|
||||||
| Refactor prompt | `.github/prompts/refactor.prompt.md` | Dead code cleanup and simplification |
|
| Refactor prompt | `.github/prompts/refactor.prompt.md` | Dead code cleanup and simplification |
|
||||||
@ -1595,16 +1599,16 @@ The committed `.vscode/settings.json` enables `chat.promptFiles` so VS Code can
|
|||||||
To use the workflow prompts in Copilot Chat:
|
To use the workflow prompts in Copilot Chat:
|
||||||
1. Open the Copilot Chat panel in VS Code.
|
1. Open the Copilot Chat panel in VS Code.
|
||||||
2. Click the **paperclip / attach** icon and select **Prompt...**, or type `/` and choose a prompt.
|
2. Click the **paperclip / attach** icon and select **Prompt...**, or type `/` and choose a prompt.
|
||||||
3. Select the prompt (e.g. `plan`, `tdd`, `code-review`).
|
3. Select the prompt (e.g. `plan`, `tdd`, `security-review`).
|
||||||
|
|
||||||
### How It Works
|
### How It Works
|
||||||
|
|
||||||
GitHub Copilot in VS Code reads two types of files automatically:
|
GitHub Copilot in VS Code reads two types of files automatically:
|
||||||
|
|
||||||
- **`.github/copilot-instructions.md`** — repository-level instructions, always injected into every Copilot Chat request. Contains ECC's core coding standards, security checklist, testing requirements, and git workflow.
|
- **`.github/copilot-instructions.md`** — repository-level instructions, always injected into every Copilot Chat request. Contains ECC's core coding standards, security checklist, testing requirements, and git workflow.
|
||||||
- **`.github/prompts/*.prompt.md`** — reusable prompt files users invoke on demand. Each prompt walks Copilot through a specific ECC workflow (plan → TDD → review → ship).
|
- **`.github/prompts/*.prompt.md`** — reusable prompt files users invoke on demand. Each prompt walks Copilot through a specific ECC workflow such as planning, TDD, security review, build-fix, or refactor.
|
||||||
|
|
||||||
The **`.vscode/settings.json`** adds per-task instruction overlays so Copilot receives the right context depending on whether you are generating code, writing tests, reviewing a selection, or drafting a commit message.
|
The **`.vscode/settings.json`** adds per-task instruction overlays so Copilot receives the right context for code generation, test generation, and commit message drafting.
|
||||||
|
|
||||||
### Feature Coverage
|
### Feature Coverage
|
||||||
|
|
||||||
@ -1614,7 +1618,7 @@ The **`.vscode/settings.json`** adds per-task instruction overlays so Copilot re
|
|||||||
| Security checklist | Always-on + `security-review` prompt |
|
| Security checklist | Always-on + `security-review` prompt |
|
||||||
| Testing / TDD | Always-on + `tdd` prompt |
|
| Testing / TDD | Always-on + `tdd` prompt |
|
||||||
| Implementation planning | `plan` prompt |
|
| Implementation planning | `plan` prompt |
|
||||||
| Code review | `code-review` prompt |
|
| Code review | External PR review via CodeRabbit + Greptile |
|
||||||
| Build error resolution | `build-fix` prompt |
|
| Build error resolution | `build-fix` prompt |
|
||||||
| Refactoring | `refactor` prompt |
|
| Refactoring | `refactor` prompt |
|
||||||
| Commit message format | Per-task instruction in `settings.json` |
|
| Commit message format | Per-task instruction in `settings.json` |
|
||||||
@ -1636,6 +1640,8 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e
|
|||||||
| **Agents** | 64 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 | N/A |
|
| **Agents** | 64 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 | N/A |
|
||||||
| **Commands** | 84 | Shared | Instruction-based | 35 | 6 prompts |
|
| **Commands** | 84 | Shared | Instruction-based | 35 | 6 prompts |
|
||||||
| **Skills** | 262 | Shared | 10 (native format) | 37 | Via instructions |
|
| **Skills** | 262 | Shared | 10 (native format) | 37 | Via instructions |
|
||||||
|
| **Commands** | 84 | Shared | Instruction-based | 35 | 5 prompts |
|
||||||
|
| **Skills** | 261 | Shared | 10 (native format) | 37 | Via instructions |
|
||||||
| **Hook Events** | 8 types | 15 types | None yet | 11 types | None |
|
| **Hook Events** | 8 types | 15 types | None yet | 11 types | None |
|
||||||
| **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks | N/A |
|
| **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks | N/A |
|
||||||
| **Rules** | 34 (common + lang) | 34 (YAML frontmatter) | Instruction-based | 13 instructions | 1 always-on file |
|
| **Rules** | 34 (common + lang) | 34 (YAML frontmatter) | Instruction-based | 13 instructions | 1 always-on file |
|
||||||
@ -1645,7 +1651,7 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e
|
|||||||
| **Context File** | CLAUDE.md + AGENTS.md | AGENTS.md | AGENTS.md | AGENTS.md | copilot-instructions.md |
|
| **Context File** | CLAUDE.md + AGENTS.md | AGENTS.md | AGENTS.md | AGENTS.md | copilot-instructions.md |
|
||||||
| **Secret Detection** | Hook-based | beforeSubmitPrompt hook | Sandbox-based | Hook-based | Instruction-based |
|
| **Secret Detection** | Hook-based | beforeSubmitPrompt hook | Sandbox-based | Hook-based | Instruction-based |
|
||||||
| **Auto-Format** | PostToolUse hook | afterFileEdit hook | N/A | file.edited hook | N/A |
|
| **Auto-Format** | PostToolUse hook | afterFileEdit hook | N/A | file.edited hook | N/A |
|
||||||
| **Version** | Plugin | Plugin | Reference config | 2.0.0-rc.1 | Instruction layer |
|
| **Version** | Plugin | Plugin | Reference config | 2.0.0 | Instruction layer |
|
||||||
|
|
||||||
**Key architectural decisions:**
|
**Key architectural decisions:**
|
||||||
- **AGENTS.md** at root is the universal cross-tool file (read by Claude Code, Cursor, Codex, and OpenCode — GitHub Copilot uses `.github/copilot-instructions.md` instead)
|
- **AGENTS.md** at root is the universal cross-tool file (read by Claude Code, Cursor, Codex, and OpenCode — GitHub Copilot uses `.github/copilot-instructions.md` instead)
|
||||||
@ -1765,39 +1771,28 @@ These configs work for my workflow. You should:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Community Projects
|
|
||||||
|
|
||||||
Projects built on or inspired by ECC:
|
|
||||||
|
|
||||||
| Project | Description |
|
|
||||||
|---------|-------------|
|
|
||||||
| [EVC](https://github.com/SaigonXIII/evc) | Marketing agent workspace — 42 commands for content operators, brand governance, and multi-channel publishing. [Visual overview](https://saigonxiii.github.io/evc). |
|
|
||||||
| [trading-skills](https://github.com/VictorVVedtion/trading-skills) | 68 trading-themed Claude Code skills with pre-trade review prompts and risk gates inspired by market operators. |
|
|
||||||
|
|
||||||
Built something with ECC? Open a PR to add it here.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
This project is free and open source. Sponsors help keep it maintained and growing.
|
ECC stays free because paid sponsors fund the work. Featured README placement is reserved for active sponsors.
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://www.coderabbit.ai"><img src="assets/images/sponsors/coderabbit.png" width="80" alt="CodeRabbit logo" /></a>
|
||||||
|
|
||||||
|
<a href="https://www.greptile.com/go/ecc"><img src="assets/images/sponsors/greptile.png" width="80" alt="Greptile logo" /></a>
|
||||||
|
<br />
|
||||||
|
<sub><strong>CodeRabbit</strong> · <strong>Greptile</strong></sub>
|
||||||
|
</div>
|
||||||
|
|
||||||
[**Become a Sponsor**](https://github.com/sponsors/affaan-m) | [Sponsor Tiers](SPONSORS.md) | [Sponsorship Program](SPONSORING.md)
|
[**Become a Sponsor**](https://github.com/sponsors/affaan-m) | [Sponsor Tiers](SPONSORS.md) | [Sponsorship Program](SPONSORING.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Star History
|
|
||||||
|
|
||||||
[](https://star-history.com/#affaan-m/ECC&Date)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
- **Shorthand Guide (Start Here):** [The Shorthand Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2012378465664745795)
|
- **Shorthand Guide (Start Here):** [The Shorthand Guide to ECC](https://x.com/affaan/status/2012378465664745795)
|
||||||
- **Longform Guide (Advanced):** [The Longform Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2014040193557471352)
|
- **Longform Guide (Advanced):** [The Longform Guide to ECC](https://x.com/affaan/status/2014040193557471352)
|
||||||
- **Security Guide:** [Security Guide](./the-security-guide.md) | [Thread](https://x.com/affaanmustafa/status/2033263813387223421)
|
- **Security Guide:** [Security Guide](./the-security-guide.md) | [Thread](https://x.com/affaan/status/2033263813387223421)
|
||||||
- **Follow:** [@affaanmustafa](https://x.com/affaanmustafa)
|
- **Follow:** [@affaan](https://x.com/affaan)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -80,6 +80,10 @@
|
|||||||
|
|
||||||
## 最新动态
|
## 最新动态
|
||||||
|
|
||||||
|
### v2.0.0 — 智能体 Harness 操作系统(2026年6月)
|
||||||
|
|
||||||
|
2.0 主线稳定版:261 个技能、control-pane 基底(会话适配器 + MCP 清单)、worktree 生命周期服务,以及 [ECC Discord 社区](https://discord.gg/36yGMHGFbR)。
|
||||||
|
|
||||||
### v2.0.0-rc.1 — 表面同步、运营工作流与 ECC 2.0 Alpha(2026年4月)
|
### v2.0.0-rc.1 — 表面同步、运营工作流与 ECC 2.0 Alpha(2026年4月)
|
||||||
|
|
||||||
- **公共表面已与真实仓库同步** —— 元数据、目录数量、插件清单以及安装文档现在都与实际开源表面保持一致。
|
- **公共表面已与真实仓库同步** —— 元数据、目录数量、插件清单以及安装文档现在都与实际开源表面保持一致。
|
||||||
|
|||||||
145
SECURITY.md
145
SECURITY.md
@ -2,100 +2,155 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| --- | --- |
|
||||||
| 1.9.x | :white_check_mark: |
|
| 2.x / rc builds | :white_check_mark: |
|
||||||
| 1.8.x | :white_check_mark: |
|
| 1.10.x | :white_check_mark: |
|
||||||
| < 1.8 | :x: |
|
| 1.9.x | Critical fixes only |
|
||||||
|
| < 1.9 | :x: |
|
||||||
|
|
||||||
|
Security fixes land on `main` first. Backports are best-effort and only for currently supported release lines.
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
If you discover a security vulnerability in ECC, please report it responsibly.
|
Use GitHub private vulnerability reporting whenever possible:
|
||||||
|
|
||||||
**Do not open a public GitHub issue for security vulnerabilities.**
|
- <https://github.com/affaan-m/ECC/security/advisories/new>
|
||||||
|
|
||||||
Instead, email **<security@ecc.tools>** with:
|
You can also email **<security@ecc.tools>**.
|
||||||
|
|
||||||
- A description of the vulnerability
|
Do **not** open a public GitHub issue for security vulnerabilities.
|
||||||
- Steps to reproduce
|
|
||||||
- The affected version(s)
|
|
||||||
- Any potential impact assessment
|
|
||||||
|
|
||||||
You can expect:
|
Include:
|
||||||
|
|
||||||
- **Acknowledgment** within 48 hours
|
- affected file, package, version, commit, and install path
|
||||||
- **Status update** within 7 days
|
- steps to reproduce from a clean checkout
|
||||||
- **Fix or mitigation** within 30 days for critical issues
|
- expected impact and affected trust boundary
|
||||||
|
- whether exploitation requires local shell access, a malicious repo, a malicious package, a remote unauthenticated actor, or maintainer credentials
|
||||||
|
- any PoC logs with tokens, keys, local paths, and private data redacted
|
||||||
|
|
||||||
If the vulnerability is accepted, we will:
|
Expected response:
|
||||||
|
|
||||||
- Credit you in the release notes (unless you prefer anonymity)
|
- **Acknowledgment:** within 48 hours
|
||||||
- Fix the issue in a timely manner
|
- **Initial assessment:** within 7 days
|
||||||
- Coordinate disclosure timing with you
|
- **Critical fix or mitigation target:** within 14 days when the report affects a supported release and crosses a real trust boundary
|
||||||
|
- **Coordinated disclosure:** before public advisory publication
|
||||||
|
|
||||||
If the vulnerability is declined, we will explain why and provide guidance on whether it should be reported elsewhere.
|
If a report is declined, we will explain whether it is not reproducible, out of scope, already fixed, or needs a stronger attack path.
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
This policy covers:
|
This policy covers:
|
||||||
|
|
||||||
- The ECC plugin and all scripts in this repository
|
- the `affaan-m/ECC` repository
|
||||||
- Hook scripts that execute on your machine
|
- the `ecc-universal` npm package
|
||||||
- Install/uninstall/repair lifecycle scripts
|
- ECC plugin, install, repair, dashboard, hook, rule, skill, MCP, and command surfaces shipped from this repository
|
||||||
- MCP configurations shipped with ECC
|
- GitHub Actions workflows and release automation in this repository
|
||||||
- The AgentShield security scanner ([github.com/affaan-m/agentshield](https://github.com/affaan-m/agentshield))
|
- the ECC Tools GitHub App integration points documented by this repository
|
||||||
|
- AgentShield usage docs when they are embedded here. AgentShield code issues belong in <https://github.com/affaan-m/agentshield>
|
||||||
|
|
||||||
|
## Official Distribution Surfaces
|
||||||
|
|
||||||
|
Official ECC surfaces are:
|
||||||
|
|
||||||
|
- GitHub repo: <https://github.com/affaan-m/ECC>
|
||||||
|
- npm package: `ecc-universal`
|
||||||
|
- GitHub App: <https://github.com/apps/ecc-tools>
|
||||||
|
- marketplace/plugin slug: `ecc@ecc`
|
||||||
|
- website: <https://ecc.tools>
|
||||||
|
|
||||||
|
Official AgentShield surface:
|
||||||
|
|
||||||
|
- npm package: `ecc-agentshield`
|
||||||
|
- GitHub repo: <https://github.com/affaan-m/agentshield>
|
||||||
|
|
||||||
|
The following packages have been observed using ECC repository metadata but are **not maintained by ECC**:
|
||||||
|
|
||||||
|
- `@chil_ntl/ecc-cli`
|
||||||
|
- `ecc-100xprompt-plugin`
|
||||||
|
|
||||||
|
Treat any package not listed under official surfaces as unofficial until verified. Do not install packages named `opencode-ecc`, `everything-claude-code`, or other ECC-like aliases unless this repository explicitly documents them as official.
|
||||||
|
|
||||||
|
GitHub dependency graph may also show Go module aliases such as `github.com/affaan-m/ecc` or historical repository paths. ECC is not currently distributed as a supported Go module.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
Reports are usually out of scope when they only show:
|
||||||
|
|
||||||
|
- local command execution where the user already controls the local shell and no higher-privilege trust boundary is crossed
|
||||||
|
- screenshots, stale line numbers, or reports against `affaan-m/everything-claude-code` that do not reproduce on current `affaan-m/ECC`
|
||||||
|
- self-XSS or social engineering with no repository-controlled exploit path
|
||||||
|
- dependency graph/package metadata confusion without an install path to an official ECC package
|
||||||
|
- vulnerabilities in third-party packages unless ECC pins, installs, or executes them in a way that creates extra impact
|
||||||
|
|
||||||
|
Local developer tools can still be valid security issues when untrusted repository content, package installation, generated hooks, or CI automation can trigger execution without clear user intent. Show that trust boundary in the report.
|
||||||
|
|
||||||
|
## Supply-Chain Rules
|
||||||
|
|
||||||
|
ECC treats supply-chain exposure as a first-class security surface.
|
||||||
|
|
||||||
|
- GitHub Actions must use pinned commit SHAs for third-party actions.
|
||||||
|
- Workflows must avoid shelling untrusted GitHub context directly into `run:` blocks.
|
||||||
|
- Release and install docs must point only to official packages.
|
||||||
|
- Package metadata should point at `affaan-m/ECC`, not historical repo paths.
|
||||||
|
- Private vulnerability reports are triaged privately before public disclosure.
|
||||||
|
- Security advisories are published only when a supported release is affected and coordinated disclosure is appropriate.
|
||||||
|
|
||||||
## Operational Guidance
|
## Operational Guidance
|
||||||
|
|
||||||
### Secrets Handling
|
### Secrets Handling
|
||||||
|
|
||||||
`mcp-configs/mcp-servers.json` is a **template**. All `YOUR_*_HERE` values must be replaced at install time from env-vars or a secrets manager. Never commit real credentials. If a secret is accidentally committed, rotate it immediately and rewrite history; do not rely on a plain revert.
|
`mcp-configs/mcp-servers.json` is a **template**. All `YOUR_*_HERE` values must be replaced at install time from env-vars or a secrets manager. Never commit real credentials. If a secret is accidentally committed, rotate it immediately and rewrite history. Do not rely on a plain revert.
|
||||||
|
|
||||||
The same rule applies to your user-scope Claude Code config (`~/.claude/settings.json` or `%USERPROFILE%\.claude\settings.json`). That file is outside this repository, but it is commonly shared via `claude doctor` output, screenshots, or bug reports. Do not hardcode PATs, API keys, or OAuth tokens into its `mcpServers[*].env` blocks; resolve them at spawn time from the OS keychain or env-vars your MCP server already supports. A quick audit:
|
The same rule applies to user-scope Claude Code config (`~/.claude/settings.json` or `%USERPROFILE%\.claude\settings.json`). That file is outside this repository, but it is commonly shared through `claude doctor` output, screenshots, and bug reports. Do not hardcode PATs, API keys, or OAuth tokens into `mcpServers[*].env` blocks. Resolve them at spawn time from the OS keychain or env-vars your MCP server already supports.
|
||||||
|
|
||||||
|
Quick audit:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# macOS / Linux
|
# macOS / Linux
|
||||||
grep -EnH '(TOKEN|SECRET|KEY|PASSWORD)\s*"\s*:\s*"[A-Za-z0-9_-]{16,}"' ~/.claude/settings.json
|
grep -EnH '(TOKEN|SECRET|KEY|PASSWORD)\s*"\s*:\s*"[A-Za-z0-9_-]{16,}"' ~/.claude/settings.json
|
||||||
|
|
||||||
# Windows PowerShell
|
# Windows PowerShell
|
||||||
Select-String -Path "$env:USERPROFILE\.claude\settings.json" -Pattern '(TOKEN|SECRET|KEY|PASSWORD)"\s*:\s*"[A-Za-z0-9_-]{16,}"'
|
Select-String -Path "$env:USERPROFILE\.claude\settings.json" -Pattern '(TOKEN|SECRET|KEY|PASSWORD)"\s*:\s*"[A-Za-z0-9_-]{16,}"'
|
||||||
```
|
```
|
||||||
|
|
||||||
If the audit matches, rotate the secret at the issuing provider, then move it out of the file (per-provider env-var or `credentialHelper` for servers that support it).
|
If the audit matches, rotate the secret at the issuing provider, then move it out of the file.
|
||||||
|
|
||||||
### Local MCP Ports
|
### Local MCP Ports
|
||||||
|
|
||||||
Some bundled MCP servers connect over plain HTTP to a localhost port (e.g. `devfleet` to `http://localhost:18801/mcp`). Before first use, verify the listening process:
|
Some bundled MCP servers connect over plain HTTP to a localhost port. Before first use, verify the listening process:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Windows
|
# Windows
|
||||||
netstat -ano | findstr :18801
|
netstat -ano | findstr :18801
|
||||||
|
|
||||||
# macOS / Linux
|
# macOS / Linux
|
||||||
lsof -iTCP:18801 -sTCP:LISTEN
|
lsof -iTCP:18801 -sTCP:LISTEN
|
||||||
```
|
```
|
||||||
|
|
||||||
Compare the PID against the expected devfleet binary. Any other process on that port can intercept MCP traffic.
|
Compare the PID against the expected binary. Any other process on that port can intercept MCP traffic.
|
||||||
|
|
||||||
## Triage: suspicious `<system-reminder>` blocks
|
## Triage: suspicious `<system-reminder>` blocks
|
||||||
|
|
||||||
ECC runs inside Claude Code, which injects **ephemeral client-side system reminders** into the model's input on every turn (TodoWrite nudges, date-changed notices, file-modified notices, etc.). These blocks:
|
ECC runs inside agent harnesses that may inject ephemeral client-side system reminders into the model input on every turn. These blocks are not automatically repository-carried payloads.
|
||||||
|
|
||||||
- typically end with phrasing like *"ignore if not applicable"* or *"NEVER mention this reminder to the user"* / *"Don't tell the user this, since they are already aware"*; that wording is Anthropic's own prompt, not a malicious tail;
|
Before treating one as an attack, verify:
|
||||||
- are added by the CLI per turn and are **not persisted** in the session transcript at `~/.claude/projects/<slug>/<sessionId>.jsonl`.
|
|
||||||
|
|
||||||
That combination makes them easy to mistake for a prompt-injection appended to a tool result. Before treating one as an attack, verify:
|
1. Is the block actually in a file under this repo?
|
||||||
|
|
||||||
1. Is the block actually in a file under this repo? `grep -rEn "system-reminder|NEVER mention|DO NOT mention" .`; if nothing, it is not carried by the repo.
|
```bash
|
||||||
2. Is the block stored in the transcript? Inspect the current session's `.jsonl`; if the exact text does not appear inside a `tool_result` body there, it is a client-injected ephemeral reminder, not a payload from any tool.
|
grep -rEn "system-reminder|NEVER mention|DO NOT mention" .
|
||||||
3. Is the content contextually consistent with Anthropic's known reminders (TodoWrite nudge, date-changed, file-modified notice)? If yes, it is the ephemeral-reminder mechanism and no action is needed.
|
```
|
||||||
|
|
||||||
Escalate to Anthropic only if a block is **both** (a) present in the transcript inside a `tool_result` **and** (b) not attributable to the file or URL that was actually read. Minimal report: a fresh session, a read of a clean local file, the exact text observed, and the transcript excerpt. Send to <https://github.com/anthropics/claude-code/issues> (non-sensitive) or <mailto:security@anthropic.com> (embargo-class).
|
2. Is the block stored in the session transcript as part of a tool result?
|
||||||
|
3. Is it consistent with known client reminders such as TodoWrite nudges, date notices, or file-modified notices?
|
||||||
|
|
||||||
Do not sanitize repo files in response to ephemeral reminders; they are not the carrier.
|
Escalate upstream only when the block is present inside a tool result or repository file and is not attributable to the file, URL, or command that was actually read.
|
||||||
|
|
||||||
## Security Resources
|
## Security Resources
|
||||||
|
|
||||||
- **AgentShield**: Scan your agent config for vulnerabilities — `npx ecc-agentshield scan`
|
- **AgentShield:** `npx ecc-agentshield scan`
|
||||||
- **Security Guide**: [The Shorthand Guide to Everything Agentic Security](./the-security-guide.md)
|
- **Security Guide:** [The Shorthand Guide to Everything Agentic Security](./the-security-guide.md)
|
||||||
- **Supply-chain incident response**: [npm/GitHub Actions package-registry playbook](./docs/security/supply-chain-incident-response.md)
|
- **Supply-chain incident response:** [npm/GitHub Actions package-registry playbook](./docs/security/supply-chain-incident-response.md)
|
||||||
- **OWASP MCP Top 10**: [owasp.org/www-project-mcp-top-10](https://owasp.org/www-project-mcp-top-10/)
|
- **OWASP MCP Top 10:** <https://owasp.org/www-project-mcp-top-10/>
|
||||||
- **OWASP Agentic Applications Top 10**: [genai.owasp.org](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/)
|
- **OWASP Agentic Applications Top 10:** <https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/>
|
||||||
|
|||||||
35
SPONSORS.md
35
SPONSORS.md
@ -2,17 +2,18 @@
|
|||||||
|
|
||||||
Thank you to everyone funding ECC's open-source work. Your sponsorship is what lets the OSS layer stay free while the GitHub App, hosted security scans, and continuous improvements ship every week.
|
Thank you to everyone funding ECC's open-source work. Your sponsorship is what lets the OSS layer stay free while the GitHub App, hosted security scans, and continuous improvements ship every week.
|
||||||
|
|
||||||
## Enterprise Sponsors — $2,500/mo
|
## Strategic Sponsors — $2,500/mo
|
||||||
|
|
||||||
*Become an [Enterprise sponsor](https://github.com/sponsors/affaan-m) to be featured here.*
|
*Become a [Strategic sponsor](https://github.com/sponsors/affaan-m) to be featured here.*
|
||||||
|
|
||||||
## Business Sponsors — $500/mo
|
## Business Sponsors — $500/mo
|
||||||
|
|
||||||
| Sponsor | Logo | Since |
|
| Sponsor | Logo | Since |
|
||||||
|---------|------|-------|
|
|---------|------|-------|
|
||||||
| [**CodeRabbit**](https://coderabbit.ai) | <img src="https://avatars.githubusercontent.com/u/132028505?s=120" width="60" alt="CodeRabbit" /> | 2026 |
|
| [**CodeRabbit**](https://www.coderabbit.ai) | <img src="assets/images/sponsors/coderabbit.png" width="60" alt="CodeRabbit logo" /> | 2026 |
|
||||||
|
| [**Greptile**](https://www.greptile.com/go/ecc) | <img src="assets/images/sponsors/greptile.png" width="60" alt="Greptile logo" /> | 2026 |
|
||||||
|
|
||||||
*[Become a Business sponsor](https://github.com/sponsors/affaan-m) to be featured here with logo placement in the main README hero and a quarterly case study.*
|
*[Become a Business sponsor](https://github.com/sponsors/affaan-m) to get README sponsor placement + SPONSORS.md listing. No seats, SLA, custom development, or preferential technical placement is bundled unless separately agreed.*
|
||||||
|
|
||||||
## Team Sponsors — $200/mo
|
## Team Sponsors — $200/mo
|
||||||
|
|
||||||
@ -20,11 +21,11 @@ Thank you to everyone funding ECC's open-source work. Your sponsorship is what l
|
|||||||
|---------|-------|
|
|---------|-------|
|
||||||
| [Mike Morgan](https://github.com/mikejmorgan-ai) | 2026 |
|
| [Mike Morgan](https://github.com/mikejmorgan-ai) | 2026 |
|
||||||
|
|
||||||
*[Become a Team sponsor](https://github.com/sponsors/affaan-m) to get small logo placement and 5 ECC Pro seats.*
|
*[Become a Team sponsor](https://github.com/sponsors/affaan-m) to be listed in SPONSORS.md.*
|
||||||
|
|
||||||
## Pro Sponsors — $50/mo
|
## Pro Sponsors — $50/mo
|
||||||
|
|
||||||
*[Become a Pro sponsor](https://github.com/sponsors/affaan-m) to be listed here with your name in the main README sponsor row.*
|
*[Become a Pro sponsor](https://github.com/sponsors/affaan-m) to support the project and be listed here.*
|
||||||
|
|
||||||
## Builder Sponsors — $25/mo
|
## Builder Sponsors — $25/mo
|
||||||
|
|
||||||
@ -33,11 +34,11 @@ Thank you to everyone funding ECC's open-source work. Your sponsorship is what l
|
|||||||
- @massimotodaro (grandfathered at $10)
|
- @massimotodaro (grandfathered at $10)
|
||||||
- @meadmccabe (grandfathered at $10)
|
- @meadmccabe (grandfathered at $10)
|
||||||
|
|
||||||
*[Become a Builder sponsor](https://github.com/sponsors/affaan-m) to support the project and get your name in this list + a private monthly progress note.*
|
*[Become a Builder sponsor](https://github.com/sponsors/affaan-m) to support the project and get your name in this list.*
|
||||||
|
|
||||||
## Supporters — $5/mo
|
## Supporters — $5/mo
|
||||||
|
|
||||||
*[Become a Supporter](https://github.com/sponsors/affaan-m) to back the project with a profile badge and a thank-you in our release notes.*
|
*[Become a Supporter](https://github.com/sponsors/affaan-m) to back the project with a profile badge and a thank-you in release notes.*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -45,16 +46,16 @@ Thank you to everyone funding ECC's open-source work. Your sponsorship is what l
|
|||||||
|
|
||||||
| Tier | Monthly | Perks |
|
| Tier | Monthly | Perks |
|
||||||
|------|--------:|-------|
|
|------|--------:|-------|
|
||||||
| Supporter | $5 | Sponsor badge on profile, thank-you in release notes |
|
| Supporter | $5 | Sponsor badge on profile, thank-you in release notes |
|
||||||
| Builder | $25 | Above + name in SPONSORS.md + private monthly progress note |
|
| Builder | $25 | Above + name in SPONSORS.md |
|
||||||
| Pro Sponsor | $50 | Above + name in main README + 1 quarterly roadmap vote |
|
| Pro Sponsor | $50 | Above + listed in SPONSORS.md |
|
||||||
| Team | $200 | Above + small org logo in README + 5 ECC Pro seats |
|
| Team Sponsor | $200 | SPONSORS.md listing |
|
||||||
| Business | $500 | Above + featured logo in README hero + quarterly case study + Discord sponsors-lounge access |
|
| Business Sponsor | $500 | README sponsor placement + SPONSORS.md listing |
|
||||||
| Enterprise | $2,500 | Above + unlimited Pro seats + 30 min/mo founder time + SLA + dedicated channel |
|
| Strategic Sponsor | $2,500 | Premium sponsor placement + custom partnership discussion |
|
||||||
|
|
||||||
[**Become a Sponsor →**](https://github.com/sponsors/affaan-m)
|
[**Become a Sponsor →**](https://github.com/sponsors/affaan-m)
|
||||||
|
|
||||||
For corporate sponsorship inquiries, custom partnerships, or PR integrations, email **[affaan@ecc.tools](mailto:affaan@ecc.tools)** with your company name and intended tier. We'll move fast — most agreements close within 48 hours.
|
For corporate sponsorship inquiries, custom partnerships, or PR integrations, email **[affaan@ecc.tools](mailto:affaan@ecc.tools)** with your company name and intended tier.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ Your sponsorship directly funds:
|
|||||||
- **OSS work that stays free** — the core repo, AgentShield, install scripts, and skills library remain MIT
|
- **OSS work that stays free** — the core repo, AgentShield, install scripts, and skills library remain MIT
|
||||||
- **Weekly releases** — full-time work on the harness, not a side project
|
- **Weekly releases** — full-time work on the harness, not a side project
|
||||||
- **Independent maintenance** — no acquisition pressure, no rug pulls, no enshittification
|
- **Independent maintenance** — no acquisition pressure, no rug pulls, no enshittification
|
||||||
- **Sponsor-driven roadmap** — Pro+ sponsors vote on direction, Business+ get case studies and integration support
|
- **Sponsor-funded roadmap** — paid sponsors fund ongoing work without turning unpaid README placement into a supply-chain risk
|
||||||
|
|
||||||
## Existing Sponsors Are Grandfathered
|
## Existing Sponsors Are Grandfathered
|
||||||
|
|
||||||
@ -73,4 +74,4 @@ If you sponsored before May 2026, you keep your original perks at your original
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Auto-updated by Hermes on every release. Last sync: 2026-05-14*
|
*Updated by Hermes. Last sync: 2026-06-09*
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
spec_version: "0.1.0"
|
spec_version: "0.1.0"
|
||||||
name: ecc
|
name: ecc
|
||||||
version: 2.0.0-rc.1
|
version: 2.0.0
|
||||||
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."
|
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
|
author: affaan-m
|
||||||
license: MIT
|
license: MIT
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 676 KiB After Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 514 KiB After Width: | Height: | Size: 37 KiB |
BIN
assets/images/sponsors/coderabbit.png
Normal file
BIN
assets/images/sponsors/coderabbit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/images/sponsors/greptile.png
Normal file
BIN
assets/images/sponsors/greptile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
43
docs/MCP-CONNECTOR-POLICY.md
Normal file
43
docs/MCP-CONNECTOR-POLICY.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# MCP Connector Policy
|
||||||
|
|
||||||
|
ECC ships exactly one default MCP connector. Everything else is a skill wrapping a CLI or REST API, or an opt-in entry in `mcp-configs/mcp-servers.json`.
|
||||||
|
|
||||||
|
## The rule
|
||||||
|
|
||||||
|
A default connector earns its slot only if both hold:
|
||||||
|
|
||||||
|
1. **Universal** — it applies to essentially every user of a coding agent, on every harness ECC targets.
|
||||||
|
2. **MCP beats a CLI/API wrapped in a skill** — the job genuinely needs what MCP provides: interactive session state, streaming, an auth handshake, or structured browsing. Stateless request/response work is a skill, not a server. Tool schemas load into every session; each default connector taxes every user's context window whether they use it or not.
|
||||||
|
|
||||||
|
The default set stays well under ten. In practice the 2026 field default across serious harnesses is zero to two connectors plus native built-ins.
|
||||||
|
|
||||||
|
## Current default set
|
||||||
|
|
||||||
|
| Server | Why it passes |
|
||||||
|
|---|---|
|
||||||
|
| `chrome-devtools` | Google's official DevTools MCP. Interactive CDP sessions — live debugging, performance traces, console and network inspection on a stateful browser. This is the textbook case where MCP beats a CLI: the value is the held-open session, not a one-shot command. Keyless. |
|
||||||
|
|
||||||
|
## The six it replaced (June 2026 audit)
|
||||||
|
|
||||||
|
| Former default | Verdict | Replacement |
|
||||||
|
|---|---|---|
|
||||||
|
| `github` | drop for skill | `gh` CLI via the `github-ops` skill. `gh` is in every model's training data, composes one-shot commands with minimal token overhead, and auths once via `gh auth login`. The MCP server's ~30 tool schemas taxed every session. |
|
||||||
|
| `context7` | drop for skill | The `documentation-lookup` skill targeting Context7's public REST API (`/api/v2/libs/search`, `/api/v2/context`). Two stateless calls with a bearer key — no session state to justify a server. |
|
||||||
|
| `exa` | drop for skill | Harness-native search (Claude Code WebSearch, Codex web_search, Cursor @Web) by default; the `exa-search` skill remains for API-key holders. Also required an API key, which fails the universality test for a default. |
|
||||||
|
| `memory` | drop entirely | Native harness memory (Claude Code auto-memory directories, Cursor memories, AGENTS.md conventions) plus ECC's instinct/continuous-learning system. The knowledge-graph server solved a 2024 problem harnesses have since absorbed. |
|
||||||
|
| `playwright` | drop for skill | Microsoft's own `@playwright/cli` agent surface — the vendor itself moved agent workflows off MCP because returning full accessibility trees per step burns context. ECC's e2e skills already drive the CLI. Browser *debugging* (the interactive case) is covered by `chrome-devtools`. |
|
||||||
|
| `sequential-thinking` | drop entirely | Native extended thinking in every modern harness. The server wrapped no external system — a prompting pattern dressed as a connector. |
|
||||||
|
|
||||||
|
All six remain available as opt-in entries in `mcp-configs/mcp-servers.json` for users who want them.
|
||||||
|
|
||||||
|
## Opt-out
|
||||||
|
|
||||||
|
`ECC_DISABLED_MCPS` filters ECC-generated MCP configs at install/sync time:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export ECC_DISABLED_MCPS="chrome-devtools"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding a connector
|
||||||
|
|
||||||
|
Open a PR that argues both prongs of the rule explicitly. "Popular" is not an argument; "the job is stateful and universal" is.
|
||||||
@ -703,7 +703,7 @@ Suggested payload:
|
|||||||
"skippedModules": []
|
"skippedModules": []
|
||||||
},
|
},
|
||||||
"source": {
|
"source": {
|
||||||
"repoVersion": "2.0.0-rc.1",
|
"repoVersion": "2.0.0",
|
||||||
"repoCommit": "git-sha",
|
"repoCommit": "git-sha",
|
||||||
"manifestVersion": 1
|
"manifestVersion": 1
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1533,10 +1533,9 @@ ECC bietet **GitHub-Copilot-Unterstützung** für VS Code über das native Instr
|
|||||||
| Komponente | Datei | Zweck |
|
| Komponente | Datei | Zweck |
|
||||||
|-----------|------|---------|
|
|-----------|------|---------|
|
||||||
| Kern-Instructions | `.github/copilot-instructions.md` | Stets geladene Rules: Coding-Style, Sicherheit, Testing, Git-Workflow |
|
| Kern-Instructions | `.github/copilot-instructions.md` | Stets geladene Rules: Coding-Style, Sicherheit, Testing, Git-Workflow |
|
||||||
| VS-Code-Einstellungen | `.vscode/settings.json` | Aufgabenspezifische Instruction-Dateien für Codegenerierung, Testgenerierung, Review und Commit-Nachrichten |
|
| VS-Code-Einstellungen | `.vscode/settings.json` | Aufgabenspezifische Instruction-Dateien für Codegenerierung, Testgenerierung und Commit-Nachrichten |
|
||||||
| Plan-Prompt | `.github/prompts/plan.prompt.md` | Phasenweise Implementierungsplanung |
|
| Plan-Prompt | `.github/prompts/plan.prompt.md` | Phasenweise Implementierungsplanung |
|
||||||
| TDD-Prompt | `.github/prompts/tdd.prompt.md` | Red-Green-Improve-Zyklus |
|
| TDD-Prompt | `.github/prompts/tdd.prompt.md` | Red-Green-Improve-Zyklus |
|
||||||
| Code-Review-Prompt | `.github/prompts/code-review.prompt.md` | Qualitäts- und Sicherheitsreview |
|
|
||||||
| Security-Review-Prompt | `.github/prompts/security-review.prompt.md` | Tiefe, OWASP-orientierte Sicherheitsanalyse |
|
| Security-Review-Prompt | `.github/prompts/security-review.prompt.md` | Tiefe, OWASP-orientierte Sicherheitsanalyse |
|
||||||
| Build-Fix-Prompt | `.github/prompts/build-fix.prompt.md` | Systematische Behebung von Build- und CI-Fehlern |
|
| Build-Fix-Prompt | `.github/prompts/build-fix.prompt.md` | Systematische Behebung von Build- und CI-Fehlern |
|
||||||
| Refactor-Prompt | `.github/prompts/refactor.prompt.md` | Beseitigung von totem Code und Vereinfachung |
|
| Refactor-Prompt | `.github/prompts/refactor.prompt.md` | Beseitigung von totem Code und Vereinfachung |
|
||||||
@ -1549,16 +1548,16 @@ Die eingecheckte `.vscode/settings.json` aktiviert `chat.promptFiles`, sodass VS
|
|||||||
So verwendest du die Workflow-Prompts in Copilot Chat:
|
So verwendest du die Workflow-Prompts in Copilot Chat:
|
||||||
1. Öffne das Copilot-Chat-Panel in VS Code.
|
1. Öffne das Copilot-Chat-Panel in VS Code.
|
||||||
2. Klicke auf das **Büroklammer-/Anhängen-Symbol** und wähle **Prompt...**, oder tippe `/` und wähle einen Prompt.
|
2. Klicke auf das **Büroklammer-/Anhängen-Symbol** und wähle **Prompt...**, oder tippe `/` und wähle einen Prompt.
|
||||||
3. Wähle den Prompt aus (z. B. `plan`, `tdd`, `code-review`).
|
3. Wähle den Prompt aus (z. B. `plan`, `tdd`, `security-review`).
|
||||||
|
|
||||||
### Wie es funktioniert
|
### Wie es funktioniert
|
||||||
|
|
||||||
GitHub Copilot in VS Code liest zwei Dateitypen automatisch:
|
GitHub Copilot in VS Code liest zwei Dateitypen automatisch:
|
||||||
|
|
||||||
- **`.github/copilot-instructions.md`** — Instructions auf Repository-Ebene, die in jede Copilot-Chat-Anfrage injiziert werden. Enthält ECCs Kern-Coding-Standards, Sicherheits-Checkliste, Testanforderungen und Git-Workflow.
|
- **`.github/copilot-instructions.md`** — Instructions auf Repository-Ebene, die in jede Copilot-Chat-Anfrage injiziert werden. Enthält ECCs Kern-Coding-Standards, Sicherheits-Checkliste, Testanforderungen und Git-Workflow.
|
||||||
- **`.github/prompts/*.prompt.md`** — wiederverwendbare Prompt-Dateien, die Nutzer bei Bedarf aufrufen. Jeder Prompt führt Copilot durch einen bestimmten ECC-Workflow (plan → TDD → review → ship).
|
- **`.github/prompts/*.prompt.md`** — wiederverwendbare Prompt-Dateien, die Nutzer bei Bedarf aufrufen. Jeder Prompt führt Copilot durch einen bestimmten ECC-Workflow wie Planung, TDD, Security-Review, Build-Fix oder Refactor.
|
||||||
|
|
||||||
Die **`.vscode/settings.json`** fügt aufgabenspezifische Instruction-Overlays hinzu, sodass Copilot je nachdem, ob du Code generierst, Tests schreibst, eine Auswahl reviewst oder eine Commit-Nachricht entwirfst, den richtigen Kontext erhält.
|
Die **`.vscode/settings.json`** fügt aufgabenspezifische Instruction-Overlays hinzu, sodass Copilot für Codegenerierung, Testgenerierung und Commit-Nachrichten den richtigen Kontext erhält.
|
||||||
|
|
||||||
### Feature-Abdeckung
|
### Feature-Abdeckung
|
||||||
|
|
||||||
@ -1568,7 +1567,7 @@ Die **`.vscode/settings.json`** fügt aufgabenspezifische Instruction-Overlays h
|
|||||||
| Sicherheits-Checkliste | Stets aktiv + `security-review`-Prompt |
|
| Sicherheits-Checkliste | Stets aktiv + `security-review`-Prompt |
|
||||||
| Testing / TDD | Stets aktiv + `tdd`-Prompt |
|
| Testing / TDD | Stets aktiv + `tdd`-Prompt |
|
||||||
| Implementierungsplanung | `plan`-Prompt |
|
| Implementierungsplanung | `plan`-Prompt |
|
||||||
| Code-Review | `code-review`-Prompt |
|
| Code-Review | Externes PR-Review über CodeRabbit + Greptile |
|
||||||
| Behebung von Build-Fehlern | `build-fix`-Prompt |
|
| Behebung von Build-Fehlern | `build-fix`-Prompt |
|
||||||
| Refactoring | `refactor`-Prompt |
|
| Refactoring | `refactor`-Prompt |
|
||||||
| Commit-Nachrichten-Format | Aufgabenspezifische Instruction in `settings.json` |
|
| Commit-Nachrichten-Format | Aufgabenspezifische Instruction in `settings.json` |
|
||||||
@ -1588,7 +1587,7 @@ ECC ist das **erste Plugin, das jedes große KI-Coding-Tool ausreizt**. So vergl
|
|||||||
| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode | GitHub Copilot |
|
| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode | GitHub Copilot |
|
||||||
|---------|------------|------------|-----------|----------|----------------|
|
|---------|------------|------------|-----------|----------|----------------|
|
||||||
| **Agents** | 60 | Gemeinsam (AGENTS.md) | Gemeinsam (AGENTS.md) | 12 | Nicht verfügbar |
|
| **Agents** | 60 | Gemeinsam (AGENTS.md) | Gemeinsam (AGENTS.md) | 12 | Nicht verfügbar |
|
||||||
| **Commands** | 75 | Gemeinsam | Instruction-basiert | 35 | 6 Prompts |
|
| **Commands** | 75 | Gemeinsam | Instruction-basiert | 35 | 5 Prompts |
|
||||||
| **Skills** | 232 | Gemeinsam | 10 (natives Format) | 37 | Über Instructions |
|
| **Skills** | 232 | Gemeinsam | 10 (natives Format) | 37 | Über Instructions |
|
||||||
| **Hook-Events** | 8 Typen | 15 Typen | Noch keine | 11 Typen | Keine |
|
| **Hook-Events** | 8 Typen | 15 Typen | Noch keine | 11 Typen | Keine |
|
||||||
| **Hook-Skripte** | 20+ Skripte | 16 Skripte (DRY-Adapter) | Nicht verfügbar | Plugin-Hooks | Nicht verfügbar |
|
| **Hook-Skripte** | 20+ Skripte | 16 Skripte (DRY-Adapter) | Nicht verfügbar | Plugin-Hooks | Nicht verfügbar |
|
||||||
|
|||||||
@ -1215,10 +1215,9 @@ ECC proporciona **soporte para GitHub Copilot** para VS Code mediante el sistema
|
|||||||
| Componente | Archivo | Propósito |
|
| Componente | Archivo | Propósito |
|
||||||
|------------|---------|-----------|
|
|------------|---------|-----------|
|
||||||
| Instrucciones principales | `.github/copilot-instructions.md` | Reglas siempre cargadas: estilo de código, seguridad, pruebas, flujo de git |
|
| Instrucciones principales | `.github/copilot-instructions.md` | Reglas siempre cargadas: estilo de código, seguridad, pruebas, flujo de git |
|
||||||
| Configuración de VS Code | `.vscode/settings.json` | Archivos de instrucciones por tarea para generación de código, pruebas, revisión y mensajes de commit |
|
| Configuración de VS Code | `.vscode/settings.json` | Archivos de instrucciones por tarea para generación de código, pruebas y mensajes de commit |
|
||||||
| Prompt de plan | `.github/prompts/plan.prompt.md` | Planificación de implementación por fases |
|
| Prompt de plan | `.github/prompts/plan.prompt.md` | Planificación de implementación por fases |
|
||||||
| Prompt de TDD | `.github/prompts/tdd.prompt.md` | Ciclo Rojo-Verde-Mejorar |
|
| Prompt de TDD | `.github/prompts/tdd.prompt.md` | Ciclo Rojo-Verde-Mejorar |
|
||||||
| Prompt de revisión de código | `.github/prompts/code-review.prompt.md` | Revisión de calidad y seguridad |
|
|
||||||
| Prompt de revisión de seguridad | `.github/prompts/security-review.prompt.md` | Análisis de seguridad profundo alineado con OWASP |
|
| Prompt de revisión de seguridad | `.github/prompts/security-review.prompt.md` | Análisis de seguridad profundo alineado con OWASP |
|
||||||
| Prompt de corrección de build | `.github/prompts/build-fix.prompt.md` | Resolución sistemática de errores de build y CI |
|
| Prompt de corrección de build | `.github/prompts/build-fix.prompt.md` | Resolución sistemática de errores de build y CI |
|
||||||
| Prompt de refactorización | `.github/prompts/refactor.prompt.md` | Limpieza de código muerto y simplificación |
|
| Prompt de refactorización | `.github/prompts/refactor.prompt.md` | Limpieza de código muerto y simplificación |
|
||||||
@ -1231,7 +1230,7 @@ El `.vscode/settings.json` confirmado habilita `chat.promptFiles` para que VS Co
|
|||||||
Para usar los prompts de flujo de trabajo en Copilot Chat:
|
Para usar los prompts de flujo de trabajo en Copilot Chat:
|
||||||
1. Abre el panel de Copilot Chat en VS Code.
|
1. Abre el panel de Copilot Chat en VS Code.
|
||||||
2. Haz clic en el icono de **clip / adjuntar** y selecciona **Prompt...**, o escribe `/` y elige un prompt.
|
2. Haz clic en el icono de **clip / adjuntar** y selecciona **Prompt...**, o escribe `/` y elige un prompt.
|
||||||
3. Selecciona el prompt (por ejemplo, `plan`, `tdd`, `code-review`).
|
3. Selecciona el prompt (por ejemplo, `plan`, `tdd`, `security-review`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -1242,7 +1241,7 @@ ECC es el **primer plugin que maximiza todas las principales herramientas de cod
|
|||||||
| Característica | Claude Code | Cursor IDE | Codex CLI | OpenCode | GitHub Copilot |
|
| Característica | Claude Code | Cursor IDE | Codex CLI | OpenCode | GitHub Copilot |
|
||||||
|----------------|-----------------------|------------|-----------|----------|----------------|
|
|----------------|-----------------------|------------|-----------|----------|----------------|
|
||||||
| **Agentes** | 63 | Compartidos (AGENTS.md) | Compartidos (AGENTS.md) | 12 | N/A |
|
| **Agentes** | 63 | Compartidos (AGENTS.md) | Compartidos (AGENTS.md) | 12 | N/A |
|
||||||
| **Comandos** | 79 | Compartidos | Basados en instrucciones | 35 | 6 prompts |
|
| **Comandos** | 79 | Compartidos | Basados en instrucciones | 35 | 5 prompts |
|
||||||
| **Skills** | 249 | Compartidas | 10 (formato nativo) | 37 | Mediante instrucciones |
|
| **Skills** | 249 | Compartidas | 10 (formato nativo) | 37 | Mediante instrucciones |
|
||||||
| **Eventos de Hook** | 8 tipos | 15 tipos | Ninguno aún | 11 tipos | Ninguno |
|
| **Eventos de Hook** | 8 tipos | 15 tipos | Ninguno aún | 11 tipos | Ninguno |
|
||||||
| **Scripts de Hook** | 20+ scripts | 16 scripts (adaptador DRY) | N/A | Hooks de plugin | N/A |
|
| **Scripts de Hook** | 20+ scripts | 16 scripts (adaptador DRY) | N/A | Hooks de plugin | N/A |
|
||||||
|
|||||||
@ -80,6 +80,10 @@ Este repositório contém apenas o código. Os guias explicam tudo.
|
|||||||
|
|
||||||
## O Que Há de Novo
|
## O Que Há de Novo
|
||||||
|
|
||||||
|
### v2.0.0 — O Sistema Operacional do Harness de Agentes (Jun 2026)
|
||||||
|
|
||||||
|
Graduação estável da linha 2.0: 261 skills, substrato de control-pane, inventário MCP, serviço de ciclo de vida de worktrees e a comunidade no [Discord](https://discord.gg/36yGMHGFbR).
|
||||||
|
|
||||||
### v2.0.0-rc.1 — Sincronização de Superfície, Fluxos Operacionais e ECC 2.0 Alpha (Abr 2026)
|
### v2.0.0-rc.1 — Sincronização de Superfície, Fluxos Operacionais e ECC 2.0 Alpha (Abr 2026)
|
||||||
|
|
||||||
- **Superfície pública sincronizada com o repositório real** — metadados, contagens de catálogo, manifests de plugin e documentação de instalação agora refletem a superfície OSS que realmente é entregue.
|
- **Superfície pública sincronizada com o repositório real** — metadados, contagens de catálogo, manifests de plugin e documentação de instalação agora refletem a superfície OSS que realmente é entregue.
|
||||||
|
|||||||
43
docs/releases/2.0.0/release-notes.md
Normal file
43
docs/releases/2.0.0/release-notes.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# ECC 2.0.0 — The Agent Harness Operating System
|
||||||
|
|
||||||
|
ECC 2.0.0 is the stable graduation of the 2.0 line: ECC as a cross-harness operating system for agentic work. Claude Code stays first-class; Codex, OpenCode, Cursor, Gemini, Zed, and terminal-only workflows share the same skills, rules, hooks, MCP conventions, release gates, and operator workflows.
|
||||||
|
|
||||||
|
## Highlights
|
||||||
|
|
||||||
|
- 261 public skills across coding, research, security, media, enterprise ops, and agent workflows.
|
||||||
|
- ECC 2.0 control-pane substrate: harness-neutral session adapters (`ecc.session.v1`) covering Claude Code, Codex, OpenCode, and dmux.
|
||||||
|
- MCP inventory (`ecc.mcp.v1`): one normalized view of MCP server configs across harnesses, with fragmentation and drift detection and secret redaction.
|
||||||
|
- Worktree-lifecycle service: deterministic conflict prediction and safe garbage collection for parallel agent worktrees.
|
||||||
|
- `orch-*` orchestrator skill family plus dynamic workflow team orchestration.
|
||||||
|
- Rollout-derived optimization pack: `parallel-execution-optimizer`, `benchmark-optimization-loop`, `data-throughput-accelerator`, `latency-critical-systems`, `recursive-decision-ledger`.
|
||||||
|
|
||||||
|
## Hardening since rc.1
|
||||||
|
|
||||||
|
Roughly thirty PRs of fixes landed between rc.1 and stable. The ones worth knowing about:
|
||||||
|
|
||||||
|
- **Plugin hooks were silently no-ops on Node 21+** (#2184). The hook runner depended on `require.main` under `node -e`, which newer Node leaves undefined — every plugin hook exited cleanly without running. If you are on Node 21 or newer, update now.
|
||||||
|
- Windows reliability: `CLAUDE_PLUGIN_ROOT` path normalization (#2139), prompts passed via stdin so the shell does not mangle them (#2174), broken-symlink and chmod test guards (#2171, #2176).
|
||||||
|
- Security: curl credentials kept out of argv (#2175), gateguard now gates force/path checkouts as destructive (#2158) with env knobs for routine-command gating (#2161), advisory intake hardening.
|
||||||
|
- Correctness: session-end summaries no longer corrupt `$`-sequences in user messages (#2180), project detection matches package keys on boundaries so `preact` no longer reads as `react` (#2181), install manifest packaging gaps closed (#2172), corrupted legacy command shims truncated safely (#2167).
|
||||||
|
- Slimmer defaults: smaller OpenCode install surface with gated hooks-runtime (#2140), `rules/zh` out of the always-loaded default install (#2170).
|
||||||
|
- New surfaces: `kubernetes-patterns` skill (#2178), worktree-lifecycle service (#2164), MCP inventory (#2146), codex-worktree and opencode session adapters (#2145), the `orch-*` family (#2153).
|
||||||
|
|
||||||
|
## Community launch
|
||||||
|
|
||||||
|
The ECC Discord is live: <https://discord.gg/36yGMHGFbR>
|
||||||
|
|
||||||
|
- Release news lands in #announcements, auto-posted and pinned by the release workflow shipped in this very release (#2201).
|
||||||
|
- A live PR and issue feed runs in #pr-and-issues.
|
||||||
|
- The ECC bot answers `/skill`, `/docs`, and `/release` lookups in-server.
|
||||||
|
- #feedback and #feature-requests are read directly by the maintainer and shape the roadmap.
|
||||||
|
|
||||||
|
## Install or upgrade
|
||||||
|
|
||||||
|
```
|
||||||
|
/plugin marketplace add https://github.com/affaan-m/ECC
|
||||||
|
/plugin install ecc
|
||||||
|
```
|
||||||
|
|
||||||
|
Existing installs: `/plugin update ecc`
|
||||||
|
|
||||||
|
Full changelog: <https://github.com/affaan-m/ECC/compare/v2.0.0-rc.1...v2.0.0>
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Bu, yazılım geliştirme için 28 özel agent, 116 skill, 59 command ve otomatik hook iş akışları sağlayan **üretime hazır bir AI kodlama eklentisidir**.
|
Bu, yazılım geliştirme için 28 özel agent, 116 skill, 59 command ve otomatik hook iş akışları sağlayan **üretime hazır bir AI kodlama eklentisidir**.
|
||||||
|
|
||||||
**Sürüm:** 2.0.0-rc.1
|
**Sürüm:** 2.0.0
|
||||||
|
|
||||||
## Temel İlkeler
|
## Temel İlkeler
|
||||||
|
|
||||||
|
|||||||
@ -79,6 +79,10 @@ Bu repository yalnızca ham kodu içerir. Rehberler her şeyi açıklıyor.
|
|||||||
|
|
||||||
## Yenilikler
|
## Yenilikler
|
||||||
|
|
||||||
|
### v2.0.0 — Ajan Harness İşletim Sistemi (Haz 2026)
|
||||||
|
|
||||||
|
2.0 hattının kararlı sürümü: 261 skill, control-pane altyapısı, MCP envanteri, worktree yaşam döngüsü servisi ve [Discord topluluğu](https://discord.gg/36yGMHGFbR).
|
||||||
|
|
||||||
### v2.0.0-rc.1 — Surface Sync, Operatör İş Akışları ve ECC 2.0 Alpha (Nis 2026)
|
### v2.0.0-rc.1 — Surface Sync, Operatör İş Akışları ve ECC 2.0 Alpha (Nis 2026)
|
||||||
|
|
||||||
- **Public surface canlı repo ile senkronlandı** — metadata, katalog sayıları, plugin manifest'leri ve kurulum odaklı dokümanlar artık gerçek OSS yüzeyiyle eşleşiyor.
|
- **Public surface canlı repo ile senkronlandı** — metadata, katalog sayıları, plugin manifest'leri ve kurulum odaklı dokümanlar artık gerçek OSS yüzeyiyle eşleşiyor.
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
这是一个**生产就绪的 AI 编码插件**,提供 64 个专业代理、262 项技能、84 条命令以及自动化钩子工作流,用于软件开发。
|
这是一个**生产就绪的 AI 编码插件**,提供 64 个专业代理、262 项技能、84 条命令以及自动化钩子工作流,用于软件开发。
|
||||||
|
|
||||||
**版本:** 2.0.0-rc.1
|
**版本:** 2.0.0
|
||||||
|
|
||||||
## 核心原则
|
## 核心原则
|
||||||
|
|
||||||
|
|||||||
@ -81,6 +81,10 @@
|
|||||||
|
|
||||||
## 最新动态
|
## 最新动态
|
||||||
|
|
||||||
|
### v2.0.0 — 智能体 Harness 操作系统(2026年6月)
|
||||||
|
|
||||||
|
2.0 主线稳定版:261 个技能、control-pane 基底(会话适配器 + MCP 清单)、worktree 生命周期服务,以及 [ECC Discord 社区](https://discord.gg/36yGMHGFbR)。
|
||||||
|
|
||||||
### v2.0.0-rc.1 — 表面同步、运营工作流与 ECC 2.0 Alpha(2026年4月)
|
### v2.0.0-rc.1 — 表面同步、运营工作流与 ECC 2.0 Alpha(2026年4月)
|
||||||
|
|
||||||
* **公共表面已与真实仓库同步** —— 元数据、目录数量、插件清单以及安装文档现在都与实际开源表面保持一致。
|
* **公共表面已与真实仓库同步** —— 元数据、目录数量、插件清单以及安装文档现在都与实际开源表面保持一致。
|
||||||
@ -1256,7 +1260,7 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
|
|||||||
| **上下文文件** | CLAUDE.md + AGENTS.md | AGENTS.md | AGENTS.md | AGENTS.md |
|
| **上下文文件** | CLAUDE.md + AGENTS.md | AGENTS.md | AGENTS.md | AGENTS.md |
|
||||||
| **秘密检测** | 基于钩子 | beforeSubmitPrompt 钩子 | 基于沙箱 | 基于钩子 |
|
| **秘密检测** | 基于钩子 | beforeSubmitPrompt 钩子 | 基于沙箱 | 基于钩子 |
|
||||||
| **自动格式化** | PostToolUse 钩子 | afterFileEdit 钩子 | N/A | file.edited 钩子 |
|
| **自动格式化** | PostToolUse 钩子 | afterFileEdit 钩子 | N/A | file.edited 钩子 |
|
||||||
| **版本** | 插件 | 插件 | 参考配置 | 2.0.0-rc.1 |
|
| **版本** | 插件 | 插件 | 参考配置 | 2.0.0 |
|
||||||
|
|
||||||
**关键架构决策:**
|
**关键架构决策:**
|
||||||
|
|
||||||
|
|||||||
67
greptile.json
Normal file
67
greptile.json
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"strictness": 2,
|
||||||
|
"commentTypes": ["logic", "syntax", "style"],
|
||||||
|
"triggerOnUpdates": true,
|
||||||
|
"triggerOnDrafts": false,
|
||||||
|
"shouldUpdateDescription": false,
|
||||||
|
"updateExistingSummaryComment": true,
|
||||||
|
"statusCheck": true,
|
||||||
|
"statusCommentsEnabled": true,
|
||||||
|
"disabledLabels": ["no-review", "skip-review", "wip"],
|
||||||
|
"excludeBranches": ["dependabot/**"],
|
||||||
|
"fileChangeLimit": 80,
|
||||||
|
"ignoreKeywords": "no-review\nskip-review\nmechanical-format-only",
|
||||||
|
"ignorePatterns": "node_modules/**\ndist/**\nbuild/**\ncoverage/**\n.vite/**\n.next/**\n.cache/**\n*.lock\npackage-lock.json\nyarn.lock\npnpm-lock.yaml\nassets/**/*.png\nassets/**/*.jpg\nassets/**/*.jpeg\nassets/**/*.gif\nassets/**/*.webp\n**/*.generated.*",
|
||||||
|
"summarySection": {
|
||||||
|
"included": true,
|
||||||
|
"collapsible": true,
|
||||||
|
"defaultOpen": true
|
||||||
|
},
|
||||||
|
"issuesTableSection": {
|
||||||
|
"included": true,
|
||||||
|
"collapsible": false,
|
||||||
|
"defaultOpen": true
|
||||||
|
},
|
||||||
|
"confidenceScoreSection": {
|
||||||
|
"included": true,
|
||||||
|
"collapsible": true,
|
||||||
|
"defaultOpen": false
|
||||||
|
},
|
||||||
|
"sequenceDiagramSection": {
|
||||||
|
"included": true,
|
||||||
|
"collapsible": true,
|
||||||
|
"defaultOpen": false
|
||||||
|
},
|
||||||
|
"customContext": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"scope": [".github/workflows/**"],
|
||||||
|
"rule": "Flag unpinned third-party GitHub Actions, broad write permissions, persisted checkout credentials in write-token jobs, pull_request_target misuse, and untrusted GitHub context inside shell commands."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": ["scripts/**", "bin/**", "skills/**/scripts/**"],
|
||||||
|
"rule": "Treat CLI inputs, URLs, file paths, and subprocess arguments as untrusted. Flag RCE, SSRF, path traversal, unsafe shell usage, and missing regression tests."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": ["skills/**", "commands/**", "agents/**", "rules/**"],
|
||||||
|
"rule": "Review for prompt injection, tool-permission creep, destructive-action ambiguity, hidden persistence, and secret exfiltration risks."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": ["SECURITY.md", "docs/security/**", "README.md"],
|
||||||
|
"rule": "Ensure package and distribution claims only list official ECC surfaces. Flag unofficial npm packages, stale historical repo paths, and misleading install instructions."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"scope": ["**/*"],
|
||||||
|
"path": "SECURITY.md",
|
||||||
|
"description": "ECC disclosure policy, official package surfaces, out-of-scope rules, and supply-chain guardrails."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": ["**/*"],
|
||||||
|
"path": "AGENTS.md",
|
||||||
|
"description": "Workspace-level agent routing and operating constraints."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ecc-universal",
|
"name": "ecc-universal",
|
||||||
"version": "2.0.0-rc.1",
|
"version": "2.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ecc-universal",
|
"name": "ecc-universal",
|
||||||
"version": "2.0.0-rc.1",
|
"version": "2.0.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ecc-universal",
|
"name": "ecc-universal",
|
||||||
"version": "2.0.0-rc.1",
|
"version": "2.0.0",
|
||||||
"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",
|
"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",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
@ -28,7 +28,7 @@
|
|||||||
],
|
],
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Affaan Mustafa",
|
"name": "Affaan Mustafa",
|
||||||
"url": "https://x.com/affaanmustafa"
|
"url": "https://x.com/affaan"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
222
scripts/discord/ecc-bot.mjs
Normal file
222
scripts/discord/ecc-bot.mjs
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// ECC community Discord bot — dependency-free (Node 22+ native WebSocket).
|
||||||
|
// Slash commands: /ecc /help /skill /docs /release
|
||||||
|
//
|
||||||
|
// Env: DISCORD_BOT_TOKEN (required), DISCORD_APP_ID (required),
|
||||||
|
// ECC_REPO (path to local clone, default ~/GitHub/ECC/everything-claude-code),
|
||||||
|
// DISCORD_INVITE (optional, shown in /ecc)
|
||||||
|
//
|
||||||
|
// Crash-only design: any gateway close, error, or missed heartbeat ack exits
|
||||||
|
// the process; the launchd/pm2 supervisor restarts it with a fresh identify.
|
||||||
|
// Register commands first: node scripts/discord/register-commands.mjs
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { homedir } from 'node:os';
|
||||||
|
|
||||||
|
const TOKEN = process.env.DISCORD_BOT_TOKEN;
|
||||||
|
const APP_ID = process.env.DISCORD_APP_ID;
|
||||||
|
if (!TOKEN || !APP_ID) {
|
||||||
|
console.error('missing DISCORD_BOT_TOKEN / DISCORD_APP_ID');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const REPO = process.env.ECC_REPO || join(homedir(), 'GitHub/ECC/everything-claude-code');
|
||||||
|
const REPO_URL = 'https://github.com/affaan-m/ECC';
|
||||||
|
const INVITE = process.env.DISCORD_INVITE || '';
|
||||||
|
const API = 'https://discord.com/api/v10';
|
||||||
|
|
||||||
|
const log = (...a) => console.log(new Date().toISOString(), ...a);
|
||||||
|
|
||||||
|
// ---------- skill + docs lookup (local clone as the data source) ----------
|
||||||
|
|
||||||
|
function parseFrontmatter(text) {
|
||||||
|
const m = text.match(/^---\n([\s\S]*?)\n---/);
|
||||||
|
if (!m) return {};
|
||||||
|
const out = {};
|
||||||
|
for (const line of m[1].split('\n')) {
|
||||||
|
const kv = line.match(/^(\w[\w-]*):\s*(.+)$/);
|
||||||
|
if (kv) out[kv[1]] = kv[2].replace(/^["']|["']$/g, '');
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSkills() {
|
||||||
|
const dir = join(REPO, 'skills');
|
||||||
|
if (!existsSync(dir)) return [];
|
||||||
|
const skills = [];
|
||||||
|
for (const name of readdirSync(dir)) {
|
||||||
|
const md = join(dir, name, 'SKILL.md');
|
||||||
|
if (!existsSync(md)) continue;
|
||||||
|
try {
|
||||||
|
const fm = parseFrontmatter(readFileSync(md, 'utf8'));
|
||||||
|
skills.push({ name, description: fm.description || '(no description)' });
|
||||||
|
} catch { /* unreadable skill dirs are skipped, not fatal */ }
|
||||||
|
}
|
||||||
|
return skills;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSkill(query) {
|
||||||
|
const q = query.toLowerCase().trim().replace(/\s+/g, '-');
|
||||||
|
const skills = loadSkills();
|
||||||
|
const exact = skills.find(s => s.name === q);
|
||||||
|
const ranked = exact
|
||||||
|
? [exact, ...skills.filter(s => s !== exact && s.name.includes(q))]
|
||||||
|
: skills.filter(s => s.name.includes(q) || s.description.toLowerCase().includes(query.toLowerCase()));
|
||||||
|
return ranked.slice(0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchDocs(query) {
|
||||||
|
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
||||||
|
const hits = [];
|
||||||
|
const roots = ['docs', 'README.md'];
|
||||||
|
const walk = rel => {
|
||||||
|
const abs = join(REPO, rel);
|
||||||
|
if (!existsSync(abs)) return;
|
||||||
|
if (statSync(abs).isDirectory()) {
|
||||||
|
for (const f of readdirSync(abs)) walk(join(rel, f));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!rel.endsWith('.md')) return;
|
||||||
|
const nameScore = terms.filter(t => rel.toLowerCase().includes(t)).length;
|
||||||
|
let score = nameScore * 3;
|
||||||
|
if (nameScore < terms.length) {
|
||||||
|
try {
|
||||||
|
const head = readFileSync(abs, 'utf8').slice(0, 4000).toLowerCase();
|
||||||
|
score += terms.filter(t => head.includes(t)).length;
|
||||||
|
} catch { /* skip unreadable */ }
|
||||||
|
}
|
||||||
|
if (score > 0) hits.push({ rel, score });
|
||||||
|
};
|
||||||
|
for (const r of roots) walk(r);
|
||||||
|
return hits.sort((a, b) => b.score - a.score).slice(0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- command handlers ----------
|
||||||
|
|
||||||
|
const HELP = [
|
||||||
|
'**ECC bot commands**',
|
||||||
|
'- `/ecc` — what ECC is + all the links',
|
||||||
|
'- `/skill name:<query>` — look up an ECC skill',
|
||||||
|
'- `/docs query:<terms>` — search the ECC docs',
|
||||||
|
'- `/release` — latest ECC release',
|
||||||
|
'- `/help` — this message',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const handlers = {
|
||||||
|
ecc: () => [
|
||||||
|
'**Everything Claude Code (ECC)** — the agent harness performance system.',
|
||||||
|
'Skills, agents, rules, hooks, MCP conventions, and operator workflows that move across Claude Code, Codex, OpenCode, Cursor, Gemini, and Zed.',
|
||||||
|
'',
|
||||||
|
`- repo: ${REPO_URL}`,
|
||||||
|
'- site: https://ecc.tools',
|
||||||
|
`- install: \`/plugin marketplace add affaan-m/everything-claude-code\` then \`/plugin install ecc\``,
|
||||||
|
INVITE ? `- invite a friend: ${INVITE}` : '',
|
||||||
|
].filter(Boolean).join('\n'),
|
||||||
|
|
||||||
|
help: () => HELP,
|
||||||
|
|
||||||
|
skill: (options) => {
|
||||||
|
const query = options.find(o => o.name === 'name')?.value || '';
|
||||||
|
const found = findSkill(query);
|
||||||
|
if (!found.length) return `no skill matching \`${query}\` — browse all: ${REPO_URL}/tree/main/skills`;
|
||||||
|
const [top, ...rest] = found;
|
||||||
|
return [
|
||||||
|
`**${top.name}** — ${top.description}`,
|
||||||
|
`${REPO_URL}/tree/main/skills/${top.name}`,
|
||||||
|
rest.length ? `\nalso close: ${rest.map(s => `\`${s.name}\``).join(', ')}` : '',
|
||||||
|
].filter(Boolean).join('\n');
|
||||||
|
},
|
||||||
|
|
||||||
|
docs: (options) => {
|
||||||
|
const query = options.find(o => o.name === 'query')?.value || '';
|
||||||
|
const hits = searchDocs(query);
|
||||||
|
if (!hits.length) return `nothing found for \`${query}\` — try ${REPO_URL}/tree/main/docs`;
|
||||||
|
return [`**docs matching \`${query}\`:**`, ...hits.map(h => `- ${REPO_URL}/blob/main/${h.rel.replace(/\\/g, '/')}`)].join('\n');
|
||||||
|
},
|
||||||
|
|
||||||
|
release: async () => {
|
||||||
|
const res = await fetch('https://api.github.com/repos/affaan-m/ECC/releases/latest', {
|
||||||
|
headers: { 'User-Agent': 'ecc-discord-bot' },
|
||||||
|
});
|
||||||
|
if (!res.ok) return `couldn't reach GitHub (${res.status}) — ${REPO_URL}/releases`;
|
||||||
|
const r = await res.json();
|
||||||
|
return `**${r.name || r.tag_name}**\n${r.html_url}`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function respond(interaction) {
|
||||||
|
const name = interaction.data?.name;
|
||||||
|
const handler = handlers[name];
|
||||||
|
const url = `${API}/interactions/${interaction.id}/${interaction.token}/callback`;
|
||||||
|
if (!handler) {
|
||||||
|
await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ type: 4, data: { content: `unknown command \`${name}\`` } }),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const content = await handler(interaction.data?.options || []);
|
||||||
|
await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ type: 4, data: { content: String(content).slice(0, 1990) } }),
|
||||||
|
});
|
||||||
|
log('handled', `/${name}`);
|
||||||
|
} catch (err) {
|
||||||
|
log('handler error', name, err.message);
|
||||||
|
await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ type: 4, data: { content: 'something broke handling that — try again in a minute' } }),
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- gateway (crash-only: exit on any failure, supervisor restarts) ----------
|
||||||
|
|
||||||
|
let seq = null;
|
||||||
|
let acked = true;
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const gw = await fetch(`${API}/gateway/bot`, { headers: { Authorization: `Bot ${TOKEN}` } }).then(r => r.json());
|
||||||
|
if (!gw.url) { console.error('gateway discovery failed:', JSON.stringify(gw).slice(0, 200)); process.exit(1); }
|
||||||
|
const ws = new WebSocket(`${gw.url}?v=10&encoding=json`);
|
||||||
|
const send = payload => ws.send(JSON.stringify(payload));
|
||||||
|
const die = reason => { log('exiting:', reason); process.exit(1); };
|
||||||
|
|
||||||
|
ws.onmessage = ev => {
|
||||||
|
const msg = JSON.parse(ev.data);
|
||||||
|
if (msg.s) seq = msg.s;
|
||||||
|
switch (msg.op) {
|
||||||
|
case 10: { // HELLO
|
||||||
|
const interval = msg.d.heartbeat_interval;
|
||||||
|
setTimeout(() => {
|
||||||
|
send({ op: 1, d: seq });
|
||||||
|
setInterval(() => {
|
||||||
|
if (!acked) die('missed heartbeat ack');
|
||||||
|
acked = false;
|
||||||
|
send({ op: 1, d: seq });
|
||||||
|
}, interval);
|
||||||
|
}, interval * Math.random());
|
||||||
|
send({ op: 2, d: { token: TOKEN, intents: 1, properties: { os: 'darwin', browser: 'ecc-bot', device: 'ecc-bot' } } });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 11: acked = true; break; // HEARTBEAT_ACK
|
||||||
|
case 1: send({ op: 1, d: seq }); break; // server-requested heartbeat
|
||||||
|
case 7: die('server requested reconnect'); break;
|
||||||
|
case 9: die('invalid session'); break;
|
||||||
|
case 0:
|
||||||
|
if (msg.t === 'READY') log(`READY as ${msg.d.user.username}#${msg.d.user.discriminator}`);
|
||||||
|
if (msg.t === 'INTERACTION_CREATE' && msg.d.type === 2) respond(msg.d);
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ws.onclose = ev => die(`gateway closed (${ev.code})`);
|
||||||
|
ws.onerror = () => die('gateway error');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => { console.error('fatal:', err.message); process.exit(1); });
|
||||||
38
scripts/discord/register-commands.mjs
Normal file
38
scripts/discord/register-commands.mjs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Registers the ECC bot's guild slash commands (bulk overwrite, instant).
|
||||||
|
// Env: DISCORD_BOT_TOKEN, DISCORD_APP_ID, DISCORD_GUILD_ID
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { DISCORD_BOT_TOKEN: TOKEN, DISCORD_APP_ID: APP_ID, DISCORD_GUILD_ID: GUILD } = process.env;
|
||||||
|
if (!TOKEN || !APP_ID || !GUILD) {
|
||||||
|
console.error('missing DISCORD_BOT_TOKEN / DISCORD_APP_ID / DISCORD_GUILD_ID');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const COMMANDS = [
|
||||||
|
{ name: 'ecc', description: 'What ECC is + all the links' },
|
||||||
|
{ name: 'help', description: 'List ECC bot commands' },
|
||||||
|
{
|
||||||
|
name: 'skill',
|
||||||
|
description: 'Look up an ECC skill by name',
|
||||||
|
options: [{ type: 3, name: 'name', description: 'skill name or keyword', required: true }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'docs',
|
||||||
|
description: 'Search the ECC docs',
|
||||||
|
options: [{ type: 3, name: 'query', description: 'search terms', required: true }],
|
||||||
|
},
|
||||||
|
{ name: 'release', description: 'Latest ECC release' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const res = await fetch(`https://discord.com/api/v10/applications/${APP_ID}/guilds/${GUILD}/commands`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { Authorization: `Bot ${TOKEN}`, 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(COMMANDS),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error('registration failed:', res.status, (await res.text()).slice(0, 300));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const registered = await res.json();
|
||||||
|
console.log('registered:', registered.map(c => `/${c.name}`).join(' '));
|
||||||
106
scripts/discord/release-announce.mjs
Normal file
106
scripts/discord/release-announce.mjs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Posts a published GitHub release to the Discord #announcements channel,
|
||||||
|
// pins it, and cross-posts to GitHub Discussions (Announcements category).
|
||||||
|
// Dependency-free (Node 18+ fetch). Runs from the release-announce workflow.
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {
|
||||||
|
DISCORD_BOT_TOKEN,
|
||||||
|
DISCORD_ANNOUNCE_CHANNEL_ID,
|
||||||
|
RELEASE_NAME,
|
||||||
|
RELEASE_TAG,
|
||||||
|
RELEASE_URL,
|
||||||
|
RELEASE_BODY,
|
||||||
|
GITHUB_TOKEN,
|
||||||
|
GITHUB_REPOSITORY,
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||||
|
|
||||||
|
async function discord(method, path, body) {
|
||||||
|
const res = await fetch(`https://discord.com/api/v10${path}`, {
|
||||||
|
method,
|
||||||
|
headers: { Authorization: `Bot ${DISCORD_BOT_TOKEN}`, 'Content-Type': 'application/json' },
|
||||||
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
|
});
|
||||||
|
if (res.status === 429) {
|
||||||
|
const j = await res.json().catch(() => ({ retry_after: 1 }));
|
||||||
|
await sleep((j.retry_after || 1) * 1000 + 250);
|
||||||
|
return discord(method, path, body);
|
||||||
|
}
|
||||||
|
if (!res.ok) throw new Error(`${method} ${path} -> ${res.status} ${(await res.text()).slice(0, 200)}`);
|
||||||
|
return res.status === 204 ? null : res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMessage() {
|
||||||
|
const title = (RELEASE_NAME && RELEASE_NAME.trim()) || RELEASE_TAG || 'New release';
|
||||||
|
const body = (RELEASE_BODY || '').trim();
|
||||||
|
// Discord message cap is 2000 chars; leave room for header + link.
|
||||||
|
const maxBody = 1600;
|
||||||
|
const trimmed = body.length > maxBody ? `${body.slice(0, maxBody)}\n...` : body;
|
||||||
|
const parts = [`# ${title} is out`, ''];
|
||||||
|
if (trimmed) parts.push(trimmed, '');
|
||||||
|
if (RELEASE_URL) parts.push(`full release notes: ${RELEASE_URL}`);
|
||||||
|
return parts.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function postAndPinToDiscord() {
|
||||||
|
if (!DISCORD_BOT_TOKEN || !DISCORD_ANNOUNCE_CHANNEL_ID) {
|
||||||
|
console.log('skip discord: missing DISCORD_BOT_TOKEN / DISCORD_ANNOUNCE_CHANNEL_ID');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const msg = await discord('POST', `/channels/${DISCORD_ANNOUNCE_CHANNEL_ID}/messages`, { content: buildMessage() });
|
||||||
|
console.log('posted release to #announcements:', msg.id);
|
||||||
|
try {
|
||||||
|
await discord('PUT', `/channels/${DISCORD_ANNOUNCE_CHANNEL_ID}/pins/${msg.id}`);
|
||||||
|
console.log('pinned announcement');
|
||||||
|
} catch (e) {
|
||||||
|
console.log('pin skipped:', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function graphql(query, variables) {
|
||||||
|
const res = await fetch('https://api.github.com/graphql', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { Authorization: `Bearer ${GITHUB_TOKEN}`, 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ query, variables }),
|
||||||
|
});
|
||||||
|
const j = await res.json();
|
||||||
|
if (j.errors) throw new Error(JSON.stringify(j.errors).slice(0, 300));
|
||||||
|
return j.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function crossPostToDiscussions() {
|
||||||
|
if (!GITHUB_TOKEN || !GITHUB_REPOSITORY) {
|
||||||
|
console.log('skip discussions: missing GITHUB_TOKEN / GITHUB_REPOSITORY');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [owner, name] = GITHUB_REPOSITORY.split('/');
|
||||||
|
try {
|
||||||
|
const data = await graphql(
|
||||||
|
`query($owner:String!,$name:String!){repository(owner:$owner,name:$name){id discussionCategories(first:25){nodes{id name}}}}`,
|
||||||
|
{ owner, name }
|
||||||
|
);
|
||||||
|
const repo = data.repository;
|
||||||
|
const cat = repo.discussionCategories.nodes.find(c => /announcement/i.test(c.name))
|
||||||
|
|| repo.discussionCategories.nodes[0];
|
||||||
|
if (!cat) { console.log('skip discussions: no category found'); return; }
|
||||||
|
const title = `${(RELEASE_NAME && RELEASE_NAME.trim()) || RELEASE_TAG} release`;
|
||||||
|
const bodyParts = [(RELEASE_BODY || '').trim(), '', RELEASE_URL ? `Release: ${RELEASE_URL}` : ''].filter(Boolean);
|
||||||
|
const created = await graphql(
|
||||||
|
`mutation($repo:ID!,$cat:ID!,$title:String!,$body:String!){createDiscussion(input:{repositoryId:$repo,categoryId:$cat,title:$title,body:$body}){discussion{url}}}`,
|
||||||
|
{ repo: repo.id, cat: cat.id, title, body: bodyParts.join('\n') || title }
|
||||||
|
);
|
||||||
|
console.log('created discussion:', created.createDiscussion.discussion.url);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('discussions cross-post skipped:', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await postAndPinToDiscord();
|
||||||
|
await crossPostToDiscussions();
|
||||||
|
console.log('release-announce done');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(e => { console.error('release-announce FAILED:', e.message); process.exit(1); });
|
||||||
@ -23,6 +23,9 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
import ipaddress
|
||||||
|
import socket
|
||||||
|
import urllib.parse
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
@ -181,6 +184,55 @@ def _validate_instinct_id(instinct_id: str) -> bool:
|
|||||||
return bool(re.match(r"^[A-Za-z0-9][A-Za-z0-9._-]*$", instinct_id))
|
return bool(re.match(r"^[A-Za-z0-9][A-Za-z0-9._-]*$", instinct_id))
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_import_url(source: str) -> str:
|
||||||
|
"""Validate remote instinct imports before opening a network connection."""
|
||||||
|
parsed = urllib.parse.urlparse(source)
|
||||||
|
if parsed.scheme != "https":
|
||||||
|
raise ValueError("remote instinct imports require https URLs")
|
||||||
|
if not parsed.hostname:
|
||||||
|
raise ValueError("remote import URL is missing a hostname")
|
||||||
|
|
||||||
|
try:
|
||||||
|
addr_infos = socket.getaddrinfo(parsed.hostname, parsed.port or 443, type=socket.SOCK_STREAM)
|
||||||
|
except socket.gaierror as exc:
|
||||||
|
raise ValueError(f"remote import host could not be resolved: {parsed.hostname}") from exc
|
||||||
|
|
||||||
|
for family, _, _, _, sockaddr in addr_infos:
|
||||||
|
host = sockaddr[0]
|
||||||
|
try:
|
||||||
|
ip = ipaddress.ip_address(host)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
if (
|
||||||
|
ip.is_private
|
||||||
|
or ip.is_loopback
|
||||||
|
or ip.is_link_local
|
||||||
|
or ip.is_multicast
|
||||||
|
or ip.is_reserved
|
||||||
|
or ip.is_unspecified
|
||||||
|
):
|
||||||
|
raise ValueError(f"remote import host resolves to a non-public address: {host}")
|
||||||
|
|
||||||
|
return urllib.parse.urlunparse(parsed)
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_import_url(source: str, *, max_bytes: int = 2 * 1024 * 1024) -> str:
|
||||||
|
"""Fetch a validated remote instinct file with bounded size and timeout."""
|
||||||
|
url = _validate_import_url(source)
|
||||||
|
req = urllib.request.Request(url, headers={"User-Agent": "ECC-instinct-import/2"})
|
||||||
|
with urllib.request.urlopen(req, timeout=15) as response:
|
||||||
|
content_type = response.headers.get("Content-Type", "")
|
||||||
|
if content_type and not any(
|
||||||
|
allowed in content_type.lower()
|
||||||
|
for allowed in ("text/", "markdown", "yaml", "json", "octet-stream")
|
||||||
|
):
|
||||||
|
raise ValueError(f"unsupported remote content type: {content_type}")
|
||||||
|
data = response.read(max_bytes + 1)
|
||||||
|
if len(data) > max_bytes:
|
||||||
|
raise ValueError(f"remote import exceeds {max_bytes} bytes")
|
||||||
|
return data.decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
def _yaml_quote(value: str) -> str:
|
def _yaml_quote(value: str) -> str:
|
||||||
"""Quote a string for safe YAML frontmatter serialization.
|
"""Quote a string for safe YAML frontmatter serialization.
|
||||||
|
|
||||||
@ -794,8 +846,7 @@ def cmd_import(args) -> int:
|
|||||||
if source.startswith('http://') or source.startswith('https://'):
|
if source.startswith('http://') or source.startswith('https://'):
|
||||||
print(f"Fetching from URL: {source}")
|
print(f"Fetching from URL: {source}")
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(source) as response:
|
content = _fetch_import_url(source)
|
||||||
content = response.read().decode('utf-8')
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error fetching URL: {e}", file=sys.stderr)
|
print(f"Error fetching URL: {e}", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@ -44,6 +44,7 @@ _promote_auto = _mod._promote_auto
|
|||||||
_find_cross_project_instincts = _mod._find_cross_project_instincts
|
_find_cross_project_instincts = _mod._find_cross_project_instincts
|
||||||
load_registry = _mod.load_registry
|
load_registry = _mod.load_registry
|
||||||
_validate_instinct_id = _mod._validate_instinct_id
|
_validate_instinct_id = _mod._validate_instinct_id
|
||||||
|
_validate_import_url = _mod._validate_import_url
|
||||||
_update_registry = _mod._update_registry
|
_update_registry = _mod._update_registry
|
||||||
_confidence_bar = _mod._confidence_bar
|
_confidence_bar = _mod._confidence_bar
|
||||||
|
|
||||||
@ -326,6 +327,32 @@ def test_validate_relative_path(tmp_path, monkeypatch):
|
|||||||
assert result == test_file.resolve()
|
assert result == test_file.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_import_url_rejects_http():
|
||||||
|
"""Remote imports should not downgrade to plaintext HTTP."""
|
||||||
|
with pytest.raises(ValueError, match="require https"):
|
||||||
|
_validate_import_url("http://example.com/instincts.yaml")
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_import_url_rejects_private_hosts(monkeypatch):
|
||||||
|
"""Remote imports should not resolve to private or loopback addresses."""
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_mod.socket,
|
||||||
|
"getaddrinfo",
|
||||||
|
lambda *args, **kwargs: [(None, None, None, None, ("127.0.0.1", 443))],
|
||||||
|
)
|
||||||
|
with pytest.raises(ValueError, match="non-public address"):
|
||||||
|
_validate_import_url("https://example.com/instincts.yaml")
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_import_url_allows_public_https(monkeypatch):
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_mod.socket,
|
||||||
|
"getaddrinfo",
|
||||||
|
lambda *args, **kwargs: [(None, None, None, None, ("93.184.216.34", 443))],
|
||||||
|
)
|
||||||
|
assert _validate_import_url("https://example.com/instincts.yaml") == "https://example.com/instincts.yaml"
|
||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────
|
# ─────────────────────────────────────────────
|
||||||
# detect_project tests
|
# detect_project tests
|
||||||
# ─────────────────────────────────────────────
|
# ─────────────────────────────────────────────
|
||||||
|
|||||||
@ -46,21 +46,16 @@ console.log('\n=== Testing GitHub Copilot support surface ===\n');
|
|||||||
test('VS Code settings enable Copilot prompt files', () => {
|
test('VS Code settings enable Copilot prompt files', () => {
|
||||||
const settings = JSON.parse(read('.vscode/settings.json'));
|
const settings = JSON.parse(read('.vscode/settings.json'));
|
||||||
assert.strictEqual(settings['chat.promptFiles'], true);
|
assert.strictEqual(settings['chat.promptFiles'], true);
|
||||||
|
assert.ok(!Object.prototype.hasOwnProperty.call(settings, 'github.copilot.chat.reviewSelection.instructions'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Copilot prompt files use current VS Code frontmatter', () => {
|
test('Copilot prompt files use current VS Code frontmatter', () => {
|
||||||
const promptFiles = fs.readdirSync(promptDir)
|
const promptFiles = fs
|
||||||
|
.readdirSync(promptDir)
|
||||||
.filter(file => file.endsWith('.prompt.md'))
|
.filter(file => file.endsWith('.prompt.md'))
|
||||||
.sort();
|
.sort();
|
||||||
|
|
||||||
assert.deepStrictEqual(promptFiles, [
|
assert.deepStrictEqual(promptFiles, ['build-fix.prompt.md', 'plan.prompt.md', 'refactor.prompt.md', 'security-review.prompt.md', 'tdd.prompt.md']);
|
||||||
'build-fix.prompt.md',
|
|
||||||
'code-review.prompt.md',
|
|
||||||
'plan.prompt.md',
|
|
||||||
'refactor.prompt.md',
|
|
||||||
'security-review.prompt.md',
|
|
||||||
'tdd.prompt.md',
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (const file of promptFiles) {
|
for (const file of promptFiles) {
|
||||||
const relativePath = `.github/prompts/${file}`;
|
const relativePath = `.github/prompts/${file}`;
|
||||||
@ -74,18 +69,15 @@ test('Copilot prompt files use current VS Code frontmatter', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Copilot docs advertise slash prompt invocation instead of hash commands', () => {
|
test('Copilot docs advertise slash prompt invocation instead of hash commands', () => {
|
||||||
const sources = [
|
const sources = ['.github/copilot-instructions.md', 'README.md'].map(read).join('\n');
|
||||||
'.github/copilot-instructions.md',
|
|
||||||
'README.md',
|
|
||||||
].map(read).join('\n');
|
|
||||||
|
|
||||||
for (const command of ['plan', 'tdd', 'code-review', 'security-review', 'build-fix', 'refactor']) {
|
for (const command of ['plan', 'tdd', 'security-review', 'build-fix', 'refactor']) {
|
||||||
assert.ok(!sources.includes(`#${command}`), `Expected no stale #${command} command syntax`);
|
assert.ok(!sources.includes(`#${command}`), `Expected no stale #${command} command syntax`);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.ok(sources.includes('/plan'));
|
assert.ok(sources.includes('/plan'));
|
||||||
assert.ok(sources.includes('/tdd'));
|
assert.ok(sources.includes('/tdd'));
|
||||||
assert.ok(sources.includes('/code-review'));
|
assert.ok(sources.includes('/security-review'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Copilot instructions include a prompt defense baseline', () => {
|
test('Copilot instructions include a prompt defense baseline', () => {
|
||||||
|
|||||||
@ -55,7 +55,7 @@ const expectedReleaseFiles = [
|
|||||||
'video-suite-production.md',
|
'video-suite-production.md',
|
||||||
'partner-sponsor-talks-pack.md',
|
'partner-sponsor-talks-pack.md',
|
||||||
'owner-approval-packet-2026-05-19.md',
|
'owner-approval-packet-2026-05-19.md',
|
||||||
'release-name-plugin-publication-checklist-2026-05-18.md',
|
'release-name-plugin-publication-checklist-2026-05-18.md'
|
||||||
];
|
];
|
||||||
|
|
||||||
test('release candidate directory includes the public launch pack', () => {
|
test('release candidate directory includes the public launch pack', () => {
|
||||||
@ -64,10 +64,10 @@ test('release candidate directory includes the public launch pack', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('README links to Hermes setup and rc.1 release notes', () => {
|
test('README links to Hermes setup and current release notes', () => {
|
||||||
const readme = read('README.md');
|
const readme = read('README.md');
|
||||||
assert.ok(readme.includes('docs/HERMES-SETUP.md'), 'README must link to Hermes setup');
|
assert.ok(readme.includes('docs/HERMES-SETUP.md'), 'README must link to Hermes setup');
|
||||||
assert.ok(readme.includes('docs/releases/2.0.0-rc.1/release-notes.md'), 'README must link to rc.1 release notes');
|
assert.ok(readme.includes('docs/releases/2.0.0/release-notes.md'), 'README must link to the 2.0.0 release notes');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cross-harness architecture doc exists and names core harnesses', () => {
|
test('cross-harness architecture doc exists and names core harnesses', () => {
|
||||||
@ -109,37 +109,19 @@ test('release docs do not contain unresolved public-link placeholders', () => {
|
|||||||
test('business launch copy stays aligned with the rc.1 public surface', () => {
|
test('business launch copy stays aligned with the rc.1 public surface', () => {
|
||||||
const source = read('docs/business/social-launch-copy.md');
|
const source = read('docs/business/social-launch-copy.md');
|
||||||
assert.ok(source.includes('ECC v2.0.0-rc.1'), 'business launch copy should use the rc.1 release');
|
assert.ok(source.includes('ECC v2.0.0-rc.1'), 'business launch copy should use the rc.1 release');
|
||||||
assert.ok(
|
assert.ok(source.includes('preview pack is ready for final release review'), 'business launch copy should stay pre-publication until release URLs exist');
|
||||||
source.includes('preview pack is ready for final release review'),
|
assert.ok(source.includes('https://github.com/affaan-m/ECC'), 'business launch copy should include the public repo URL');
|
||||||
'business launch copy should stay pre-publication until release URLs exist'
|
assert.ok(source.includes('https://github.com/affaan-m/ECC/blob/main/docs/releases/2.0.0-rc.1/release-notes.md'), 'business launch copy should link to the rc.1 release notes');
|
||||||
);
|
|
||||||
assert.ok(
|
|
||||||
source.includes('https://github.com/affaan-m/ECC'),
|
|
||||||
'business launch copy should include the public repo URL'
|
|
||||||
);
|
|
||||||
assert.ok(
|
|
||||||
source.includes(
|
|
||||||
'https://github.com/affaan-m/ECC/blob/main/docs/releases/2.0.0-rc.1/release-notes.md'
|
|
||||||
),
|
|
||||||
'business launch copy should link to the rc.1 release notes'
|
|
||||||
);
|
|
||||||
assert.ok(!source.includes('<repo-link>'), 'business launch copy should not contain repo placeholders');
|
assert.ok(!source.includes('<repo-link>'), 'business launch copy should not contain repo placeholders');
|
||||||
assert.ok(!source.includes('v1.8.0'), 'business launch copy should not stay pinned to v1.8.0');
|
assert.ok(!source.includes('v1.8.0'), 'business launch copy should not stay pinned to v1.8.0');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('announcement drafts avoid live-release claims before publication', () => {
|
test('announcement drafts avoid live-release claims before publication', () => {
|
||||||
const announcementFiles = [
|
const announcementFiles = ['docs/releases/2.0.0-rc.1/linkedin-post.md', 'docs/releases/2.0.0-rc.1/partner-sponsor-talks-pack.md', 'docs/business/social-launch-copy.md'];
|
||||||
'docs/releases/2.0.0-rc.1/linkedin-post.md',
|
|
||||||
'docs/releases/2.0.0-rc.1/partner-sponsor-talks-pack.md',
|
|
||||||
'docs/business/social-launch-copy.md',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const relativePath of announcementFiles) {
|
for (const relativePath of announcementFiles) {
|
||||||
const source = read(relativePath);
|
const source = read(relativePath);
|
||||||
assert.ok(
|
assert.ok(!/ECC v2\.0\.0-rc\.1 is live\./.test(source), `${relativePath} must not claim rc.1 is live before the release gate completes`);
|
||||||
!/ECC v2\.0\.0-rc\.1 is live\./.test(source),
|
|
||||||
`${relativePath} must not claim rc.1 is live before the release gate completes`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -184,7 +166,7 @@ test('preview pack manifest assembles release, Hermes, and publication gates', (
|
|||||||
'docs/releases/2.0.0-rc.1/owner-approval-packet-2026-05-19.md',
|
'docs/releases/2.0.0-rc.1/owner-approval-packet-2026-05-19.md',
|
||||||
'docs/releases/2.0.0-rc.1/video-suite-production.md',
|
'docs/releases/2.0.0-rc.1/video-suite-production.md',
|
||||||
'docs/releases/2.0.0-rc.1/publication-evidence-2026-05-19.md',
|
'docs/releases/2.0.0-rc.1/publication-evidence-2026-05-19.md',
|
||||||
'docs/releases/2.0.0-rc.1/release-name-plugin-publication-checklist-2026-05-18.md',
|
'docs/releases/2.0.0-rc.1/release-name-plugin-publication-checklist-2026-05-18.md'
|
||||||
]) {
|
]) {
|
||||||
assert.ok(manifest.includes(artifact), `preview pack manifest missing ${artifact}`);
|
assert.ok(manifest.includes(artifact), `preview pack manifest missing ${artifact}`);
|
||||||
}
|
}
|
||||||
@ -194,7 +176,7 @@ test('preview pack manifest assembles release, Hermes, and publication gates', (
|
|||||||
'npm `ecc-universal@2.0.0-rc.1`',
|
'npm `ecc-universal@2.0.0-rc.1`',
|
||||||
'Claude plugin tag',
|
'Claude plugin tag',
|
||||||
'Codex repo-marketplace distribution evidence',
|
'Codex repo-marketplace distribution evidence',
|
||||||
'ECC Tools billing/product readiness',
|
'ECC Tools billing/product readiness'
|
||||||
]) {
|
]) {
|
||||||
assert.ok(manifest.includes(blocker), `preview pack manifest missing blocker ${blocker}`);
|
assert.ok(manifest.includes(blocker), `preview pack manifest missing blocker ${blocker}`);
|
||||||
}
|
}
|
||||||
@ -223,7 +205,7 @@ test('owner approval packet consolidates the final gated decisions', () => {
|
|||||||
'Video upload',
|
'Video upload',
|
||||||
'Final URL Fill-In',
|
'Final URL Fill-In',
|
||||||
'Do Not Approve If',
|
'Do Not Approve If',
|
||||||
'No outbound email, personal-account post, package publish, plugin tag, or billing announcement is authorized by this packet alone.',
|
'No outbound email, personal-account post, package publish, plugin tag, or billing announcement is authorized by this packet alone.'
|
||||||
]) {
|
]) {
|
||||||
assert.ok(packet.includes(marker), `owner approval packet missing ${marker}`);
|
assert.ok(packet.includes(marker), `owner approval packet missing ${marker}`);
|
||||||
}
|
}
|
||||||
@ -233,18 +215,12 @@ test('owner approval packet consolidates the final gated decisions', () => {
|
|||||||
'npm run preview-pack:smoke -- --format json',
|
'npm run preview-pack:smoke -- --format json',
|
||||||
'npm run release:approval-gate -- --format json',
|
'npm run release:approval-gate -- --format json',
|
||||||
'npm run release:video-suite -- --format json',
|
'npm run release:video-suite -- --format json',
|
||||||
'node tests/run-all.js',
|
'node tests/run-all.js'
|
||||||
]) {
|
]) {
|
||||||
assert.ok(packet.includes(command), `owner approval packet missing command ${command}`);
|
assert.ok(packet.includes(command), `owner approval packet missing command ${command}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const urlSurface of [
|
for (const urlSurface of ['GitHub prerelease URL', 'npm rc package URL', 'Claude plugin tag URL', 'Primary launch video URL', 'ECC Tools billing/readiness URL']) {
|
||||||
'GitHub prerelease URL',
|
|
||||||
'npm rc package URL',
|
|
||||||
'Claude plugin tag URL',
|
|
||||||
'Primary launch video URL',
|
|
||||||
'ECC Tools billing/readiness URL',
|
|
||||||
]) {
|
|
||||||
assert.ok(packet.includes(urlSurface), `owner approval packet missing ${urlSurface}`);
|
assert.ok(packet.includes(urlSurface), `owner approval packet missing ${urlSurface}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +248,7 @@ test('GA roadmap mirrors the current May 19 release evidence', () => {
|
|||||||
'467d148a-712a-4777-aad9-95593e9f1739',
|
'467d148a-712a-4777-aad9-95593e9f1739',
|
||||||
'7642ee9c-3107-400c-a229-53e2895a8914',
|
'7642ee9c-3107-400c-a229-53e2895a8914',
|
||||||
'ecc-may-19-post-pr-2002-sync-64cef8f668e0',
|
'ecc-may-19-post-pr-2002-sync-64cef8f668e0',
|
||||||
'owner approval packet',
|
'owner approval packet'
|
||||||
]) {
|
]) {
|
||||||
assert.ok(roadmap.includes(marker), `GA roadmap missing current evidence marker ${marker}`);
|
assert.ok(roadmap.includes(marker), `GA roadmap missing current evidence marker ${marker}`);
|
||||||
}
|
}
|
||||||
@ -331,19 +307,12 @@ test('release video suite manifest gates the content launch lane', () => {
|
|||||||
'Do Not Publish If',
|
'Do Not Publish If',
|
||||||
'renders/ecc-2-primary-launch-rough-v1.mp4',
|
'renders/ecc-2-primary-launch-rough-v1.mp4',
|
||||||
'timelines/primary-launch-v1.timeline.json',
|
'timelines/primary-launch-v1.timeline.json',
|
||||||
'Primary launch video',
|
'Primary launch video'
|
||||||
]) {
|
]) {
|
||||||
assert.ok(videoManifest.includes(marker), `video suite manifest missing ${marker}`);
|
assert.ok(videoManifest.includes(marker), `video suite manifest missing ${marker}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const asset of [
|
for (const asset of ['longform-full-wide.mp4', 'sf-thread-2-whatisecc.mp4', 'thread-2-ghapp-money.mp4', 'coverage-montage-wide.mp4', 'star_history.png', 'x_analytics.png']) {
|
||||||
'longform-full-wide.mp4',
|
|
||||||
'sf-thread-2-whatisecc.mp4',
|
|
||||||
'thread-2-ghapp-money.mp4',
|
|
||||||
'coverage-montage-wide.mp4',
|
|
||||||
'star_history.png',
|
|
||||||
'x_analytics.png',
|
|
||||||
]) {
|
|
||||||
assert.ok(videoManifest.includes(asset), `video suite manifest missing asset ${asset}`);
|
assert.ok(videoManifest.includes(asset), `video suite manifest missing asset ${asset}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,7 +334,7 @@ test('release approval gate blocks publication until owner decisions and URLs ar
|
|||||||
'owner-decisions-approved',
|
'owner-decisions-approved',
|
||||||
'release-url-ledger-finalized',
|
'release-url-ledger-finalized',
|
||||||
'announcement-copy-finalized',
|
'announcement-copy-finalized',
|
||||||
'No outbound email, personal-account post, package publish, plugin tag, or billing announcement',
|
'No outbound email, personal-account post, package publish, plugin tag, or billing announcement'
|
||||||
]) {
|
]) {
|
||||||
assert.ok(script.includes(marker), `release approval gate missing ${marker}`);
|
assert.ok(script.includes(marker), `release approval gate missing ${marker}`);
|
||||||
}
|
}
|
||||||
@ -402,7 +371,7 @@ test('partner sponsor talks pack gates the hypergrowth outbound lane', () => {
|
|||||||
'GitHub Discussion Announcement',
|
'GitHub Discussion Announcement',
|
||||||
'Video CTA Hooks',
|
'Video CTA Hooks',
|
||||||
'Do Not Send Or Publish If',
|
'Do Not Send Or Publish If',
|
||||||
'The user has not approved outbound sponsor, partner, consulting, or media',
|
'The user has not approved outbound sponsor, partner, consulting, or media'
|
||||||
]) {
|
]) {
|
||||||
assert.ok(partnerPack.includes(marker), `partner pack missing ${marker}`);
|
assert.ok(partnerPack.includes(marker), `partner pack missing ${marker}`);
|
||||||
}
|
}
|
||||||
@ -416,10 +385,7 @@ test('partner sponsor talks pack gates the hypergrowth outbound lane', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('release video suite public docs do not expose private media paths', () => {
|
test('release video suite public docs do not expose private media paths', () => {
|
||||||
const releaseVideoDocs = [
|
const releaseVideoDocs = ['docs/releases/2.0.0-rc.1/video-suite-production.md', 'docs/releases/2.0.0/ecc-2-hypergrowth-release-command-center.md'];
|
||||||
'docs/releases/2.0.0-rc.1/video-suite-production.md',
|
|
||||||
'docs/releases/2.0.0/ecc-2-hypergrowth-release-command-center.md',
|
|
||||||
];
|
|
||||||
|
|
||||||
const offenders = [];
|
const offenders = [];
|
||||||
for (const relativePath of releaseVideoDocs) {
|
for (const relativePath of releaseVideoDocs) {
|
||||||
@ -437,37 +403,15 @@ test('publication readiness checklist gates public release actions on evidence',
|
|||||||
const may15Evidence = read('docs/releases/2.0.0-rc.1/publication-evidence-2026-05-15.md');
|
const may15Evidence = read('docs/releases/2.0.0-rc.1/publication-evidence-2026-05-15.md');
|
||||||
const discussionPlaybook = read('docs/architecture/discussion-response-playbook.md');
|
const discussionPlaybook = read('docs/architecture/discussion-response-playbook.md');
|
||||||
|
|
||||||
for (const section of [
|
for (const section of ['## Release Identity Matrix', '## Publication Gates', '## Required Command Evidence', '## Do Not Publish If', '## Announcement Order']) {
|
||||||
'## Release Identity Matrix',
|
|
||||||
'## Publication Gates',
|
|
||||||
'## Required Command Evidence',
|
|
||||||
'## Do Not Publish If',
|
|
||||||
'## Announcement Order',
|
|
||||||
]) {
|
|
||||||
assert.ok(source.includes(section), `publication readiness missing ${section}`);
|
assert.ok(source.includes(section), `publication readiness missing ${section}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const field of [
|
for (const field of ['Fresh check', 'Evidence artifact', 'Owner', 'Status', 'Blocker field', 'Recorded output']) {
|
||||||
'Fresh check',
|
|
||||||
'Evidence artifact',
|
|
||||||
'Owner',
|
|
||||||
'Status',
|
|
||||||
'Blocker field',
|
|
||||||
'Recorded output',
|
|
||||||
]) {
|
|
||||||
assert.ok(source.includes(field), `publication readiness missing ${field}`);
|
assert.ok(source.includes(field), `publication readiness missing ${field}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const surface of [
|
for (const surface of ['GitHub release', 'npm package', 'Claude plugin', 'Codex plugin', 'Codex repo marketplace', 'OpenCode package', 'ECC Tools billing reference', 'Announcement copy']) {
|
||||||
'GitHub release',
|
|
||||||
'npm package',
|
|
||||||
'Claude plugin',
|
|
||||||
'Codex plugin',
|
|
||||||
'Codex repo marketplace',
|
|
||||||
'OpenCode package',
|
|
||||||
'ECC Tools billing reference',
|
|
||||||
'Announcement copy',
|
|
||||||
]) {
|
|
||||||
assert.ok(source.includes(surface), `publication readiness missing ${surface}`);
|
assert.ok(source.includes(surface), `publication readiness missing ${surface}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -502,14 +446,7 @@ test('publication readiness checklist gates public release actions on evidence',
|
|||||||
assert.ok(source.includes('platform audit sampled 59 trunk discussions'));
|
assert.ok(source.includes('platform audit sampled 59 trunk discussions'));
|
||||||
assert.ok(source.includes('0 needing maintainer touch'));
|
assert.ok(source.includes('0 needing maintainer touch'));
|
||||||
assert.ok(source.includes('discussion-response-playbook.md'));
|
assert.ok(source.includes('discussion-response-playbook.md'));
|
||||||
for (const expected of [
|
for (const expected of ['Public Support', 'Maintainer Coordination', 'Stale Or Concluded', 'Release Announcement', 'Security Escalation', 'classified as informational']) {
|
||||||
'Public Support',
|
|
||||||
'Maintainer Coordination',
|
|
||||||
'Stale Or Concluded',
|
|
||||||
'Release Announcement',
|
|
||||||
'Security Escalation',
|
|
||||||
'classified as informational',
|
|
||||||
]) {
|
|
||||||
assert.ok(discussionPlaybook.includes(expected), `discussion playbook missing ${expected}`);
|
assert.ok(discussionPlaybook.includes(expected), `discussion playbook missing ${expected}`);
|
||||||
}
|
}
|
||||||
assert.ok(may15Evidence.includes('env -u GITHUB_TOKEN'));
|
assert.ok(may15Evidence.includes('env -u GITHUB_TOKEN'));
|
||||||
@ -518,9 +455,7 @@ test('publication readiness checklist gates public release actions on evidence',
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('release name and plugin publication checklist freezes rc.1 surfaces', () => {
|
test('release name and plugin publication checklist freezes rc.1 surfaces', () => {
|
||||||
const checklist = read(
|
const checklist = read('docs/releases/2.0.0-rc.1/release-name-plugin-publication-checklist-2026-05-18.md');
|
||||||
'docs/releases/2.0.0-rc.1/release-name-plugin-publication-checklist-2026-05-18.md'
|
|
||||||
);
|
|
||||||
const launchChecklist = read('docs/releases/2.0.0-rc.1/launch-checklist.md');
|
const launchChecklist = read('docs/releases/2.0.0-rc.1/launch-checklist.md');
|
||||||
const referenceArchitecture = read('docs/ECC-2.0-REFERENCE-ARCHITECTURE.md');
|
const referenceArchitecture = read('docs/ECC-2.0-REFERENCE-ARCHITECTURE.md');
|
||||||
|
|
||||||
@ -534,7 +469,7 @@ test('release name and plugin publication checklist freezes rc.1 surfaces', () =
|
|||||||
'Codex plugin',
|
'Codex plugin',
|
||||||
'do not claim official directory listing until OpenAI publishing path is available',
|
'do not claim official directory listing until OpenAI publishing path is available',
|
||||||
'Do not rename the npm package until rc.1 is published',
|
'Do not rename the npm package until rc.1 is published',
|
||||||
'Do not announce billing, Marketplace, or native payments',
|
'Do not announce billing, Marketplace, or native payments'
|
||||||
]) {
|
]) {
|
||||||
assert.ok(checklist.includes(value), `release name/plugin checklist missing ${value}`);
|
assert.ok(checklist.includes(value), `release name/plugin checklist missing ${value}`);
|
||||||
}
|
}
|
||||||
@ -545,7 +480,7 @@ test('release name and plugin publication checklist freezes rc.1 surfaces', () =
|
|||||||
'codex plugin marketplace add --help',
|
'codex plugin marketplace add --help',
|
||||||
'npm publish --tag next --dry-run',
|
'npm publish --tag next --dry-run',
|
||||||
'npm run preview-pack:smoke',
|
'npm run preview-pack:smoke',
|
||||||
'npm run release:approval-gate -- --format json',
|
'npm run release:approval-gate -- --format json'
|
||||||
]) {
|
]) {
|
||||||
assert.ok(checklist.includes(command), `release name/plugin checklist missing command ${command}`);
|
assert.ok(checklist.includes(command), `release name/plugin checklist missing command ${command}`);
|
||||||
}
|
}
|
||||||
@ -569,7 +504,7 @@ test('active release identity surfaces use canonical ECC repo URLs', () => {
|
|||||||
'docs/releases/2.0.0-rc.1/release-url-ledger-2026-05-19.md',
|
'docs/releases/2.0.0-rc.1/release-url-ledger-2026-05-19.md',
|
||||||
'ecc2/Cargo.toml',
|
'ecc2/Cargo.toml',
|
||||||
'scripts/platform-audit.js',
|
'scripts/platform-audit.js',
|
||||||
'scripts/discussion-audit.js',
|
'scripts/discussion-audit.js'
|
||||||
];
|
];
|
||||||
|
|
||||||
const offenders = [];
|
const offenders = [];
|
||||||
|
|||||||
@ -61,10 +61,7 @@ function loadJsonObject(filePath, label) {
|
|||||||
assert.fail(`Expected ${label} to contain valid JSON: ${error.message}`);
|
assert.fail(`Expected ${label} to contain valid JSON: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(parsed && typeof parsed === 'object' && !Array.isArray(parsed), `Expected ${label} to contain a JSON object`);
|
||||||
parsed && typeof parsed === 'object' && !Array.isArray(parsed),
|
|
||||||
`Expected ${label} to contain a JSON object`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
@ -161,34 +158,22 @@ test('.opencode/plugins/ecc-hooks.ts active plugin banner matches package.json',
|
|||||||
|
|
||||||
test('docs/pt-BR/README.md latest release heading matches package.json', () => {
|
test('docs/pt-BR/README.md latest release heading matches package.json', () => {
|
||||||
const source = fs.readFileSync(ptBrReadmePath, 'utf8');
|
const source = fs.readFileSync(ptBrReadmePath, 'utf8');
|
||||||
assert.ok(
|
assert.ok(source.includes(`### v${expectedVersion} `), 'Expected docs/pt-BR/README.md to advertise the current release heading');
|
||||||
source.includes(`### v${expectedVersion} `),
|
|
||||||
'Expected docs/pt-BR/README.md to advertise the current release heading',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('docs/tr/README.md latest release heading matches package.json', () => {
|
test('docs/tr/README.md latest release heading matches package.json', () => {
|
||||||
const source = fs.readFileSync(trReadmePath, 'utf8');
|
const source = fs.readFileSync(trReadmePath, 'utf8');
|
||||||
assert.ok(
|
assert.ok(source.includes(`### v${expectedVersion} `), 'Expected docs/tr/README.md to advertise the current release heading');
|
||||||
source.includes(`### v${expectedVersion} `),
|
|
||||||
'Expected docs/tr/README.md to advertise the current release heading',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('README.zh-CN.md latest release heading matches package.json', () => {
|
test('README.zh-CN.md latest release heading matches package.json', () => {
|
||||||
const source = fs.readFileSync(rootZhCnReadmePath, 'utf8');
|
const source = fs.readFileSync(rootZhCnReadmePath, 'utf8');
|
||||||
assert.ok(
|
assert.ok(source.includes(`### v${expectedVersion} `), 'Expected README.zh-CN.md to advertise the current release heading');
|
||||||
source.includes(`### v${expectedVersion} `),
|
|
||||||
'Expected README.zh-CN.md to advertise the current release heading',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('docs/zh-CN/README.md latest release heading matches package.json', () => {
|
test('docs/zh-CN/README.md latest release heading matches package.json', () => {
|
||||||
const source = fs.readFileSync(zhCnReadmePath, 'utf8');
|
const source = fs.readFileSync(zhCnReadmePath, 'utf8');
|
||||||
assert.ok(
|
assert.ok(source.includes(`### v${expectedVersion} `), 'Expected docs/zh-CN/README.md to advertise the current release heading');
|
||||||
source.includes(`### v${expectedVersion} `),
|
|
||||||
'Expected docs/zh-CN/README.md to advertise the current release heading',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Claude plugin manifest ────────────────────────────────────────────────────
|
// ── Claude plugin manifest ────────────────────────────────────────────────────
|
||||||
@ -216,10 +201,7 @@ test('claude plugin.json uses short plugin slug', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('claude plugin.json does NOT have agents field (unsupported by Claude Code validator)', () => {
|
test('claude plugin.json does NOT have agents field (unsupported by Claude Code validator)', () => {
|
||||||
assert.ok(
|
assert.ok(!('agents' in claudePlugin), 'agents field must NOT be declared — Claude Code plugin validator rejects it');
|
||||||
!('agents' in claudePlugin),
|
|
||||||
'agents field must NOT be declared — Claude Code plugin validator rejects it',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('claude plugin.json skills is an array', () => {
|
test('claude plugin.json skills is an array', () => {
|
||||||
@ -234,26 +216,13 @@ test('claude plugin.json disables bundled MCP servers for provider tool-name com
|
|||||||
const legacyPluginName = 'everything-claude-code';
|
const legacyPluginName = 'everything-claude-code';
|
||||||
const reportedOverlongToolName = `mcp__plugin_${legacyPluginName}_github__create_pull_request_review`;
|
const reportedOverlongToolName = `mcp__plugin_${legacyPluginName}_github__create_pull_request_review`;
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(reportedOverlongToolName.length > 64, 'Expected the reported GitHub MCP tool name to exceed strict provider limits without the MCP opt-out');
|
||||||
reportedOverlongToolName.length > 64,
|
assert.ok(Object.prototype.hasOwnProperty.call(claudePlugin, 'mcpServers'), 'Expected mcpServers to be explicitly declared so Claude Code does not auto-load root .mcp.json');
|
||||||
'Expected the reported GitHub MCP tool name to exceed strict provider limits without the MCP opt-out',
|
assert.deepStrictEqual(claudePlugin.mcpServers, {}, 'Claude plugin installs must not auto-bundle root MCP servers; document/manual MCP install remains supported');
|
||||||
);
|
|
||||||
assert.ok(
|
|
||||||
Object.prototype.hasOwnProperty.call(claudePlugin, 'mcpServers'),
|
|
||||||
'Expected mcpServers to be explicitly declared so Claude Code does not auto-load root .mcp.json',
|
|
||||||
);
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
claudePlugin.mcpServers,
|
|
||||||
{},
|
|
||||||
'Claude plugin installs must not auto-bundle root MCP servers; document/manual MCP install remains supported',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('claude plugin.json does NOT have explicit hooks declaration', () => {
|
test('claude plugin.json does NOT have explicit hooks declaration', () => {
|
||||||
assert.ok(
|
assert.ok(!('hooks' in claudePlugin), 'hooks field must NOT be declared — Claude Code v2.1+ auto-loads hooks/hooks.json by convention');
|
||||||
!('hooks' in claudePlugin),
|
|
||||||
'hooks field must NOT be declared — Claude Code v2.1+ auto-loads hooks/hooks.json by convention',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('\n=== .claude-plugin/marketplace.json ===\n');
|
console.log('\n=== .claude-plugin/marketplace.json ===\n');
|
||||||
@ -267,10 +236,7 @@ const claudeMarketplace = loadJsonObject(claudeMarketplacePath, '.claude-plugin/
|
|||||||
test('claude marketplace.json keeps only Claude-supported top-level keys', () => {
|
test('claude marketplace.json keeps only Claude-supported top-level keys', () => {
|
||||||
const unsupportedTopLevelKeys = ['$schema', 'description'];
|
const unsupportedTopLevelKeys = ['$schema', 'description'];
|
||||||
for (const key of unsupportedTopLevelKeys) {
|
for (const key of unsupportedTopLevelKeys) {
|
||||||
assert.ok(
|
assert.ok(!(key in claudeMarketplace), `.claude-plugin/marketplace.json must not declare unsupported top-level key "${key}"`);
|
||||||
!(key in claudeMarketplace),
|
|
||||||
`.claude-plugin/marketplace.json must not declare unsupported top-level key "${key}"`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -316,39 +282,21 @@ test('codex plugin.json version matches package.json', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('codex plugin.json skills is a string (not array) per official spec', () => {
|
test('codex plugin.json skills is a string (not array) per official spec', () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(typeof codexPlugin.skills, 'string', 'skills must be a string path per Codex official docs, not an array');
|
||||||
typeof codexPlugin.skills,
|
|
||||||
'string',
|
|
||||||
'skills must be a string path per Codex official docs, not an array',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('codex plugin.json mcpServers is a string path (not array) per official spec', () => {
|
test('codex plugin.json mcpServers is a string path (not array) per official spec', () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(typeof codexPlugin.mcpServers, 'string', 'mcpServers must be a string path per Codex official docs');
|
||||||
typeof codexPlugin.mcpServers,
|
|
||||||
'string',
|
|
||||||
'mcpServers must be a string path per Codex official docs',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('codex plugin.json mcpServers exactly matches "./.mcp.json"', () => {
|
test('codex plugin.json mcpServers exactly matches "./.mcp.json"', () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(codexPlugin.mcpServers, './.mcp.json', 'mcpServers must point exactly to "./.mcp.json" per official docs');
|
||||||
codexPlugin.mcpServers,
|
|
||||||
'./.mcp.json',
|
|
||||||
'mcpServers must point exactly to "./.mcp.json" per official docs',
|
|
||||||
);
|
|
||||||
const mcpPath = path.join(repoRoot, codexPlugin.mcpServers.replace(/^\.\//, ''));
|
const mcpPath = path.join(repoRoot, codexPlugin.mcpServers.replace(/^\.\//, ''));
|
||||||
assert.ok(
|
assert.ok(fs.existsSync(mcpPath), `mcpServers file missing at plugin root: ${codexPlugin.mcpServers}`);
|
||||||
fs.existsSync(mcpPath),
|
|
||||||
`mcpServers file missing at plugin root: ${codexPlugin.mcpServers}`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('codex plugin.json has interface.displayName', () => {
|
test('codex plugin.json has interface.displayName', () => {
|
||||||
assert.ok(
|
assert.ok(codexPlugin.interface && codexPlugin.interface.displayName, 'Expected interface.displayName for plugin directory presentation');
|
||||||
codexPlugin.interface && codexPlugin.interface.displayName,
|
|
||||||
'Expected interface.displayName for plugin directory presentation',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('codex plugin.json uses canonical ECC repo and display name', () => {
|
test('codex plugin.json uses canonical ECC repo and display name', () => {
|
||||||
@ -363,20 +311,11 @@ test('codex plugin presentation assets exist and ship in npm package', () => {
|
|||||||
for (const field of ['composerIcon', 'logo']) {
|
for (const field of ['composerIcon', 'logo']) {
|
||||||
const assetPath = codexPlugin.interface[field];
|
const assetPath = codexPlugin.interface[field];
|
||||||
assert.ok(assetPath, `Expected interface.${field}`);
|
assert.ok(assetPath, `Expected interface.${field}`);
|
||||||
assert.ok(
|
assert.ok(assetPath.startsWith('./assets/'), `Expected interface.${field} to point at a root assets path, got ${assetPath}`);
|
||||||
assetPath.startsWith('./assets/'),
|
|
||||||
`Expected interface.${field} to point at a root assets path, got ${assetPath}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const packagePath = assetPath.replace(/^\.\//, '');
|
const packagePath = assetPath.replace(/^\.\//, '');
|
||||||
assert.ok(
|
assert.ok(fs.existsSync(path.join(repoRoot, packagePath)), `Expected interface.${field} asset to exist: ${packagePath}`);
|
||||||
fs.existsSync(path.join(repoRoot, packagePath)),
|
assert.ok(packageFiles.has(packagePath), `Expected package.json files to include interface.${field} asset: ${packagePath}`);
|
||||||
`Expected interface.${field} asset to exist: ${packagePath}`,
|
|
||||||
);
|
|
||||||
assert.ok(
|
|
||||||
packageFiles.has(packagePath),
|
|
||||||
`Expected package.json files to include interface.${field} asset: ${packagePath}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -388,31 +327,27 @@ const mcpJsonPath = path.join(repoRoot, '.mcp.json');
|
|||||||
|
|
||||||
test('.mcp.json exists at plugin root (not inside .codex-plugin/)', () => {
|
test('.mcp.json exists at plugin root (not inside .codex-plugin/)', () => {
|
||||||
assert.ok(fs.existsSync(mcpJsonPath), 'Expected .mcp.json at repo root (plugin root)');
|
assert.ok(fs.existsSync(mcpJsonPath), 'Expected .mcp.json at repo root (plugin root)');
|
||||||
assert.ok(
|
assert.ok(!fs.existsSync(path.join(repoRoot, '.codex-plugin', '.mcp.json')), '.mcp.json must NOT be inside .codex-plugin/ — only plugin.json belongs there');
|
||||||
!fs.existsSync(path.join(repoRoot, '.codex-plugin', '.mcp.json')),
|
|
||||||
'.mcp.json must NOT be inside .codex-plugin/ — only plugin.json belongs there',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mcpConfig = loadJsonObject(mcpJsonPath, '.mcp.json');
|
const mcpConfig = loadJsonObject(mcpJsonPath, '.mcp.json');
|
||||||
|
|
||||||
test('.mcp.json has mcpServers object', () => {
|
test('.mcp.json has mcpServers object', () => {
|
||||||
assert.ok(
|
assert.ok(mcpConfig.mcpServers && typeof mcpConfig.mcpServers === 'object', 'Expected mcpServers object');
|
||||||
mcpConfig.mcpServers && typeof mcpConfig.mcpServers === 'object',
|
|
||||||
'Expected mcpServers object',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('.mcp.json includes at least github, context7, and exa servers', () => {
|
test('.mcp.json default set follows the connector policy', () => {
|
||||||
const servers = Object.keys(mcpConfig.mcpServers);
|
const servers = Object.keys(mcpConfig.mcpServers);
|
||||||
assert.ok(servers.includes('github'), 'Expected github MCP server');
|
assert.ok(servers.includes('chrome-devtools'), 'Expected chrome-devtools as the default browser connector');
|
||||||
assert.ok(servers.includes('context7'), 'Expected context7 MCP server');
|
assert.ok(servers.length <= 2, `Default connector set must stay minimal per docs/MCP-CONNECTOR-POLICY.md (found ${servers.length})`);
|
||||||
assert.ok(servers.includes('exa'), 'Expected exa MCP server');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('.mcp.json declares exa as an http MCP server', () => {
|
test('.mcp.json does not reintroduce retired default connectors', () => {
|
||||||
assert.strictEqual(mcpConfig.mcpServers.exa.type, 'http', 'Expected exa MCP server to declare type=http');
|
const retired = ['github', 'context7', 'exa', 'memory', 'playwright', 'sequential-thinking'];
|
||||||
assert.strictEqual(mcpConfig.mcpServers.exa.url, 'https://mcp.exa.ai/mcp', 'Expected exa MCP server URL to remain unchanged');
|
const servers = Object.keys(mcpConfig.mcpServers);
|
||||||
|
for (const name of retired) {
|
||||||
|
assert.ok(!servers.includes(name), `${name} was retired from the default set (June 2026 audit) — it lives in mcp-configs/mcp-servers.json as opt-in; see docs/MCP-CONNECTOR-POLICY.md`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Codex marketplace file ────────────────────────────────────────────────────
|
// ── Codex marketplace file ────────────────────────────────────────────────────
|
||||||
@ -422,10 +357,7 @@ console.log('\n=== .agents/plugins/marketplace.json ===\n');
|
|||||||
const marketplacePath = path.join(repoRoot, '.agents', 'plugins', 'marketplace.json');
|
const marketplacePath = path.join(repoRoot, '.agents', 'plugins', 'marketplace.json');
|
||||||
|
|
||||||
test('marketplace.json exists at .agents/plugins/', () => {
|
test('marketplace.json exists at .agents/plugins/', () => {
|
||||||
assert.ok(
|
assert.ok(fs.existsSync(marketplacePath), 'Expected .agents/plugins/marketplace.json for Codex repo marketplace discovery');
|
||||||
fs.existsSync(marketplacePath),
|
|
||||||
'Expected .agents/plugins/marketplace.json for Codex repo marketplace discovery',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const marketplace = loadJsonObject(marketplacePath, '.agents/plugins/marketplace.json');
|
const marketplace = loadJsonObject(marketplacePath, '.agents/plugins/marketplace.json');
|
||||||
@ -467,24 +399,11 @@ test('marketplace local plugin path resolves to the repo-root Codex bundle', ()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(plugin.source.path.startsWith('./'), `Codex marketplace source.path must be ./-prefixed: ${plugin.source.path}`);
|
||||||
plugin.source.path.startsWith('./'),
|
|
||||||
`Codex marketplace source.path must be ./-prefixed: ${plugin.source.path}`,
|
|
||||||
);
|
|
||||||
const resolvedRoot = path.resolve(repoRoot, plugin.source.path);
|
const resolvedRoot = path.resolve(repoRoot, plugin.source.path);
|
||||||
assert.strictEqual(
|
assert.strictEqual(resolvedRoot, repoRoot, `Expected local marketplace path to resolve to repo root from marketplace root, got: ${plugin.source.path}`);
|
||||||
resolvedRoot,
|
assert.ok(fs.existsSync(path.join(resolvedRoot, '.codex-plugin', 'plugin.json')), `Codex plugin manifest missing under resolved marketplace root: ${plugin.source.path}`);
|
||||||
repoRoot,
|
assert.ok(fs.existsSync(path.join(resolvedRoot, '.mcp.json')), `Root MCP config missing under resolved marketplace root: ${plugin.source.path}`);
|
||||||
`Expected local marketplace path to resolve to repo root from marketplace root, got: ${plugin.source.path}`,
|
|
||||||
);
|
|
||||||
assert.ok(
|
|
||||||
fs.existsSync(path.join(resolvedRoot, '.codex-plugin', 'plugin.json')),
|
|
||||||
`Codex plugin manifest missing under resolved marketplace root: ${plugin.source.path}`,
|
|
||||||
);
|
|
||||||
assert.ok(
|
|
||||||
fs.existsSync(path.join(resolvedRoot, '.mcp.json')),
|
|
||||||
`Root MCP config missing under resolved marketplace root: ${plugin.source.path}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -510,7 +429,7 @@ test('user-facing docs do not use overlong legacy marketplace install commands',
|
|||||||
path.join(repoRoot, 'README.md'),
|
path.join(repoRoot, 'README.md'),
|
||||||
path.join(repoRoot, 'README.zh-CN.md'),
|
path.join(repoRoot, 'README.zh-CN.md'),
|
||||||
path.join(repoRoot, 'skills', 'configure-ecc', 'SKILL.md'),
|
path.join(repoRoot, 'skills', 'configure-ecc', 'SKILL.md'),
|
||||||
...collectMarkdownFiles(path.join(repoRoot, 'docs')),
|
...collectMarkdownFiles(path.join(repoRoot, 'docs'))
|
||||||
].filter(filePath => !path.relative(repoRoot, filePath).startsWith(`docs${path.sep}drafts${path.sep}`));
|
].filter(filePath => !path.relative(repoRoot, filePath).startsWith(`docs${path.sep}drafts${path.sep}`));
|
||||||
|
|
||||||
const offenders = [];
|
const offenders = [];
|
||||||
@ -521,19 +440,11 @@ test('user-facing docs do not use overlong legacy marketplace install commands',
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(offenders, [], `Overlong legacy install commands must not appear in user-facing docs: ${offenders.join(', ')}`);
|
||||||
offenders,
|
|
||||||
[],
|
|
||||||
`Overlong legacy install commands must not appear in user-facing docs: ${offenders.join(', ')}`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('user-facing docs do not use the legacy non-URL marketplace add form', () => {
|
test('user-facing docs do not use the legacy non-URL marketplace add form', () => {
|
||||||
const markdownFiles = [
|
const markdownFiles = [path.join(repoRoot, 'README.md'), path.join(repoRoot, 'README.zh-CN.md'), ...collectMarkdownFiles(path.join(repoRoot, 'docs'))];
|
||||||
path.join(repoRoot, 'README.md'),
|
|
||||||
path.join(repoRoot, 'README.zh-CN.md'),
|
|
||||||
...collectMarkdownFiles(path.join(repoRoot, 'docs')),
|
|
||||||
];
|
|
||||||
|
|
||||||
const offenders = [];
|
const offenders = [];
|
||||||
for (const filePath of markdownFiles) {
|
for (const filePath of markdownFiles) {
|
||||||
@ -543,31 +454,15 @@ test('user-facing docs do not use the legacy non-URL marketplace add form', () =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(offenders, [], `Legacy non-URL marketplace add form must not appear in user-facing docs: ${offenders.join(', ')}`);
|
||||||
offenders,
|
|
||||||
[],
|
|
||||||
`Legacy non-URL marketplace add form must not appear in user-facing docs: ${offenders.join(', ')}`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('.codex-plugin README uses current marketplace add flow', () => {
|
test('.codex-plugin README uses current marketplace add flow', () => {
|
||||||
const readme = fs.readFileSync(path.join(repoRoot, '.codex-plugin', 'README.md'), 'utf8');
|
const readme = fs.readFileSync(path.join(repoRoot, '.codex-plugin', 'README.md'), 'utf8');
|
||||||
assert.ok(
|
assert.ok(readme.includes('codex plugin marketplace add'), 'Expected .codex-plugin README to document codex plugin marketplace add');
|
||||||
readme.includes('codex plugin marketplace add'),
|
assert.ok(readme.includes('codex plugin marketplace add affaan-m/ECC'), 'Expected .codex-plugin README to document the canonical ECC repo marketplace source');
|
||||||
'Expected .codex-plugin README to document codex plugin marketplace add',
|
assert.ok(readme.includes('Official Plugin Directory publishing is coming soon'), 'Expected .codex-plugin README to document current official directory status');
|
||||||
);
|
assert.ok(!/\bcodex plugin install\b/.test(readme), 'codex plugin install is not a current Codex CLI command');
|
||||||
assert.ok(
|
|
||||||
readme.includes('codex plugin marketplace add affaan-m/ECC'),
|
|
||||||
'Expected .codex-plugin README to document the canonical ECC repo marketplace source',
|
|
||||||
);
|
|
||||||
assert.ok(
|
|
||||||
readme.includes('Official Plugin Directory publishing is coming soon'),
|
|
||||||
'Expected .codex-plugin README to document current official directory status',
|
|
||||||
);
|
|
||||||
assert.ok(
|
|
||||||
!/\bcodex plugin install\b/.test(readme),
|
|
||||||
'codex plugin install is not a current Codex CLI command',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('docs/zh-CN/README.md version row matches package.json', () => {
|
test('docs/zh-CN/README.md version row matches package.json', () => {
|
||||||
|
|||||||
@ -150,8 +150,7 @@ function runTests() {
|
|||||||
const mcpConfig = readJson(path.join(projectDir, '.cursor', 'mcp.json'));
|
const mcpConfig = readJson(path.join(projectDir, '.cursor', 'mcp.json'));
|
||||||
assert.strictEqual(hooksConfig.version, 1);
|
assert.strictEqual(hooksConfig.version, 1);
|
||||||
assert.ok(hooksConfig.hooks.sessionStart, 'Should keep Cursor sessionStart hooks');
|
assert.ok(hooksConfig.hooks.sessionStart, 'Should keep Cursor sessionStart hooks');
|
||||||
assert.ok(mcpConfig.mcpServers.github, 'Should install shared MCP servers into Cursor');
|
assert.ok(mcpConfig.mcpServers['chrome-devtools'], 'Should install shared MCP servers into Cursor');
|
||||||
assert.ok(mcpConfig.mcpServers.context7, 'Should include bundled documentation MCPs');
|
|
||||||
|
|
||||||
const statePath = path.join(projectDir, '.cursor', 'ecc-install-state.json');
|
const statePath = path.join(projectDir, '.cursor', 'ecc-install-state.json');
|
||||||
const state = readJson(statePath);
|
const state = readJson(statePath);
|
||||||
@ -194,8 +193,7 @@ function runTests() {
|
|||||||
|
|
||||||
const mcpConfig = readJson(path.join(projectDir, '.cursor', 'mcp.json'));
|
const mcpConfig = readJson(path.join(projectDir, '.cursor', 'mcp.json'));
|
||||||
assert.ok(mcpConfig.mcpServers.custom, 'Should preserve existing custom Cursor MCP servers');
|
assert.ok(mcpConfig.mcpServers.custom, 'Should preserve existing custom Cursor MCP servers');
|
||||||
assert.ok(mcpConfig.mcpServers.github, 'Should merge bundled GitHub MCP server');
|
assert.ok(mcpConfig.mcpServers['chrome-devtools'], 'Should merge the bundled chrome-devtools MCP server');
|
||||||
assert.ok(mcpConfig.mcpServers.playwright, 'Should merge bundled Playwright MCP server');
|
|
||||||
} finally {
|
} finally {
|
||||||
cleanup(homeDir);
|
cleanup(homeDir);
|
||||||
cleanup(projectDir);
|
cleanup(projectDir);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user