mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-11 02:33:10 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c888d2b73f | ||
|
|
ff768db363 |
@@ -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
|
||||||
|
|
||||||
|
|||||||
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"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# 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
|
## 2.0.0 - 2026-06-09
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ ECC v2.0.0 adds the public Hermes operator story on top of that reusable layer:
|
|||||||
<sub><strong>Business sponsors</strong></sub><br />
|
<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.coderabbit.ai"><img src="assets/images/sponsors/coderabbit.png" width="72" alt="CodeRabbit logo" /></a>
|
||||||
|
|
||||||
<a href="https://greptile.com"><img src="assets/images/sponsors/greptile.png" width="72" alt="Greptile logo" /></a>
|
<a href="https://www.greptile.com/go/ecc"><img src="assets/images/sponsors/greptile.png" width="72" alt="Greptile logo" /></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -982,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.
|
||||||
@@ -1774,7 +1776,7 @@ ECC stays free because paid sponsors fund the work. Featured README placement is
|
|||||||
<div align="center">
|
<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.coderabbit.ai"><img src="assets/images/sponsors/coderabbit.png" width="80" alt="CodeRabbit logo" /></a>
|
||||||
|
|
||||||
<a href="https://greptile.com"><img src="assets/images/sponsors/greptile.png" width="80" alt="Greptile logo" /></a>
|
<a href="https://www.greptile.com/go/ecc"><img src="assets/images/sponsors/greptile.png" width="80" alt="Greptile logo" /></a>
|
||||||
<br />
|
<br />
|
||||||
<sub><strong>CodeRabbit</strong> · <strong>Greptile</strong></sub>
|
<sub><strong>CodeRabbit</strong> · <strong>Greptile</strong></sub>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Thank you to everyone funding ECC's open-source work. Your sponsorship is what l
|
|||||||
| Sponsor | Logo | Since |
|
| Sponsor | Logo | Since |
|
||||||
|---------|------|-------|
|
|---------|------|-------|
|
||||||
| [**CodeRabbit**](https://www.coderabbit.ai) | <img src="assets/images/sponsors/coderabbit.png" width="60" alt="CodeRabbit logo" /> | 2026 |
|
| [**CodeRabbit**](https://www.coderabbit.ai) | <img src="assets/images/sponsors/coderabbit.png" width="60" alt="CodeRabbit logo" /> | 2026 |
|
||||||
| [**Greptile**](https://greptile.com) | <img src="assets/images/sponsors/greptile.png" width="60" alt="Greptile 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 get README sponsor placement + SPONSORS.md listing. No seats, SLA, custom development, or preferential technical placement is bundled unless separately agreed.*
|
*[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.*
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.6 KiB 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.
|
||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user