mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-30 16:45:48 +08:00
fix: namespace cursor agent installs
This commit is contained in:
parent
5881554a1c
commit
e1d6d853f7
12
README.md
12
README.md
@ -1133,7 +1133,7 @@ These are not bundled with ECC and are not audited by this repo, but they are wo
|
|||||||
|
|
||||||
## Cursor IDE Support
|
## Cursor IDE Support
|
||||||
|
|
||||||
ECC provides **full Cursor IDE support** with hooks, rules, agents, skills, commands, and MCP configs adapted for Cursor's native format.
|
ECC provides Cursor IDE support with hooks, rules, agents, skills, commands, and MCP configs adapted for Cursor's project layout.
|
||||||
|
|
||||||
### Quick Start (Cursor)
|
### Quick Start (Cursor)
|
||||||
|
|
||||||
@ -1156,11 +1156,17 @@ ECC provides **full Cursor IDE support** with hooks, rules, agents, skills, comm
|
|||||||
| Hook Events | 15 | sessionStart, beforeShellExecution, afterFileEdit, beforeMCPExecution, beforeSubmitPrompt, and 10 more |
|
| Hook Events | 15 | sessionStart, beforeShellExecution, afterFileEdit, beforeMCPExecution, beforeSubmitPrompt, and 10 more |
|
||||||
| Hook Scripts | 16 | Thin Node.js scripts delegating to `scripts/hooks/` via shared adapter |
|
| Hook Scripts | 16 | Thin Node.js scripts delegating to `scripts/hooks/` via shared adapter |
|
||||||
| Rules | 34 | 9 common (alwaysApply) + 25 language-specific (TypeScript, Python, Go, Swift, PHP) |
|
| Rules | 34 | 9 common (alwaysApply) + 25 language-specific (TypeScript, Python, Go, Swift, PHP) |
|
||||||
| Agents | Shared | Via AGENTS.md at root (read by Cursor natively) |
|
| Agents | 48 | `.cursor/agents/ecc-*.md` when installed; prefixed to avoid collisions with user or marketplace agents |
|
||||||
| Skills | Shared + Bundled | Via AGENTS.md at root and `.cursor/skills/` for translated additions |
|
| Skills | Shared + Bundled | `.cursor/skills/` for translated additions |
|
||||||
| Commands | Shared | `.cursor/commands/` if installed |
|
| Commands | Shared | `.cursor/commands/` if installed |
|
||||||
| MCP Config | Shared | `.cursor/mcp.json` if installed |
|
| MCP Config | Shared | `.cursor/mcp.json` if installed |
|
||||||
|
|
||||||
|
### Cursor Loading Notes
|
||||||
|
|
||||||
|
ECC does not install root `AGENTS.md` into `.cursor/`. Cursor treats nested `AGENTS.md` files as directory context, so copying ECC's repo identity into a host project would pollute that project.
|
||||||
|
|
||||||
|
Cursor-native loading behavior can vary by Cursor build. ECC installs agents as `.cursor/agents/ecc-*.md`; if your Cursor build does not expose project agents, those files still work as explicit reference definitions instead of hidden global prompt context.
|
||||||
|
|
||||||
### Hook Architecture (DRY Adapter Pattern)
|
### Hook Architecture (DRY Adapter Pattern)
|
||||||
|
|
||||||
Cursor has **more hook events than Claude Code** (20 vs 8). The `.cursor/hooks/adapter.js` module transforms Cursor's stdin JSON to Claude Code's format, allowing existing `scripts/hooks/*.js` to be reused without duplication.
|
Cursor has **more hook events than Claude Code** (20 vs 8). The `.cursor/hooks/adapter.js` module transforms Cursor's stdin JSON to Claude Code's format, allowing existing `scripts/hooks/*.js` to be reused without duplication.
|
||||||
|
|||||||
26
scripts/lib/cursor-agent-names.js
Normal file
26
scripts/lib/cursor-agent-names.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function toCursorAgentFileName(fileName) {
|
||||||
|
if (!fileName || fileName.startsWith('ecc-')) {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `ecc-${fileName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toCursorAgentRelativePath(relativePath) {
|
||||||
|
const segments = String(relativePath || '').split(/[\\/]+/).filter(Boolean);
|
||||||
|
if (segments.length === 0) {
|
||||||
|
return relativePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = segments.pop();
|
||||||
|
return path.join(...segments, toCursorAgentFileName(fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
toCursorAgentFileName,
|
||||||
|
toCursorAgentRelativePath,
|
||||||
|
};
|
||||||
@ -3,6 +3,7 @@ const os = require('os');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { execFileSync } = require('child_process');
|
const { execFileSync } = require('child_process');
|
||||||
|
|
||||||
|
const { toCursorAgentRelativePath } = require('./cursor-agent-names');
|
||||||
const { LEGACY_INSTALL_TARGETS, parseInstallArgs } = require('./install/request');
|
const { LEGACY_INSTALL_TARGETS, parseInstallArgs } = require('./install/request');
|
||||||
const {
|
const {
|
||||||
SUPPORTED_INSTALL_TARGETS,
|
SUPPORTED_INSTALL_TARGETS,
|
||||||
@ -154,7 +155,13 @@ function addRecursiveCopyOperations(operations, options) {
|
|||||||
for (const relativeFile of relativeFiles) {
|
for (const relativeFile of relativeFiles) {
|
||||||
const sourceRelativePath = path.join(options.sourceRelativeDir, relativeFile);
|
const sourceRelativePath = path.join(options.sourceRelativeDir, relativeFile);
|
||||||
const sourcePath = path.join(options.sourceRoot, sourceRelativePath);
|
const sourcePath = path.join(options.sourceRoot, sourceRelativePath);
|
||||||
const destinationPath = path.join(options.destinationDir, relativeFile);
|
const destinationRelativePath = typeof options.destinationRelativePathTransform === 'function'
|
||||||
|
? options.destinationRelativePathTransform(relativeFile, sourceRelativePath)
|
||||||
|
: relativeFile;
|
||||||
|
if (!destinationRelativePath) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const destinationPath = path.join(options.destinationDir, destinationRelativePath);
|
||||||
operations.push(buildCopyFileOperation({
|
operations.push(buildCopyFileOperation({
|
||||||
moduleId: options.moduleId,
|
moduleId: options.moduleId,
|
||||||
sourcePath,
|
sourcePath,
|
||||||
@ -351,6 +358,7 @@ function planCursorLegacyInstall(context) {
|
|||||||
sourceRoot: context.sourceRoot,
|
sourceRoot: context.sourceRoot,
|
||||||
sourceRelativeDir: path.join('.cursor', 'agents'),
|
sourceRelativeDir: path.join('.cursor', 'agents'),
|
||||||
destinationDir: path.join(targetRoot, 'agents'),
|
destinationDir: path.join(targetRoot, 'agents'),
|
||||||
|
destinationRelativePathTransform: toCursorAgentRelativePath,
|
||||||
});
|
});
|
||||||
addRecursiveCopyOperations(operations, {
|
addRecursiveCopyOperations(operations, {
|
||||||
moduleId: 'legacy-cursor-install',
|
moduleId: 'legacy-cursor-install',
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const { toCursorAgentFileName } = require('../cursor-agent-names');
|
||||||
const {
|
const {
|
||||||
|
createFlatFileOperations,
|
||||||
createFlatRuleOperations,
|
createFlatRuleOperations,
|
||||||
createInstallTargetAdapter,
|
createInstallTargetAdapter,
|
||||||
createManagedOperation,
|
createManagedOperation,
|
||||||
@ -149,6 +151,16 @@ module.exports = createInstallTargetAdapter({
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sourceRelativePath === 'agents') {
|
||||||
|
return takeUniqueOperations(createFlatFileOperations({
|
||||||
|
moduleId: module.id,
|
||||||
|
repoRoot,
|
||||||
|
sourceRelativePath,
|
||||||
|
destinationDir: path.join(targetRoot, 'agents'),
|
||||||
|
destinationNameTransform: toCursorAgentFileName,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
if (sourceRelativePath === '.cursor') {
|
if (sourceRelativePath === '.cursor') {
|
||||||
const cursorRoot = path.join(repoRoot, '.cursor');
|
const cursorRoot = path.join(repoRoot, '.cursor');
|
||||||
if (!fs.existsSync(cursorRoot) || !fs.statSync(cursorRoot).isDirectory()) {
|
if (!fs.existsSync(cursorRoot) || !fs.statSync(cursorRoot).isDirectory()) {
|
||||||
|
|||||||
@ -181,7 +181,7 @@ function createNamespacedFlatRuleOperations(adapter, moduleId, sourceRelativePat
|
|||||||
return operations;
|
return operations;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFlatRuleOperations({
|
function createFlatFileOperations({
|
||||||
moduleId,
|
moduleId,
|
||||||
repoRoot,
|
repoRoot,
|
||||||
sourceRelativePath,
|
sourceRelativePath,
|
||||||
@ -242,6 +242,10 @@ function createFlatRuleOperations({
|
|||||||
return operations;
|
return operations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createFlatRuleOperations(options) {
|
||||||
|
return createFlatFileOperations(options);
|
||||||
|
}
|
||||||
|
|
||||||
function createInstallTargetAdapter(config) {
|
function createInstallTargetAdapter(config) {
|
||||||
const adapter = {
|
const adapter = {
|
||||||
id: config.id,
|
id: config.id,
|
||||||
@ -342,6 +346,7 @@ function createInstallTargetAdapter(config) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
buildValidationIssue,
|
buildValidationIssue,
|
||||||
|
createFlatFileOperations,
|
||||||
createFlatRuleOperations,
|
createFlatRuleOperations,
|
||||||
createInstallTargetAdapter,
|
createInstallTargetAdapter,
|
||||||
createManagedOperation,
|
createManagedOperation,
|
||||||
|
|||||||
@ -213,7 +213,10 @@ function runTests() {
|
|||||||
assert.strictEqual(plan.installRoot, targetRoot);
|
assert.strictEqual(plan.installRoot, targetRoot);
|
||||||
assert.ok(operationFor(plan, path.join('.cursor', 'rules', 'common-style.md')));
|
assert.ok(operationFor(plan, path.join('.cursor', 'rules', 'common-style.md')));
|
||||||
assert.ok(operationFor(plan, path.join('.cursor', 'rules', 'typescript-style.md')));
|
assert.ok(operationFor(plan, path.join('.cursor', 'rules', 'typescript-style.md')));
|
||||||
assert.ok(operationFor(plan, path.join('.cursor', 'agents', 'planner.md')));
|
assert.ok(operationFor(plan, path.join('.cursor', 'agents', 'ecc-planner.md')));
|
||||||
|
assert.ok(!plan.operations.some(operation => (
|
||||||
|
operation.destinationPath.endsWith(path.join('.cursor', 'agents', 'planner.md'))
|
||||||
|
)));
|
||||||
assert.ok(operationFor(plan, path.join('.cursor', 'skills', 'demo', 'SKILL.md')));
|
assert.ok(operationFor(plan, path.join('.cursor', 'skills', 'demo', 'SKILL.md')));
|
||||||
assert.ok(operationFor(plan, path.join('.cursor', 'commands', 'plan.md')));
|
assert.ok(operationFor(plan, path.join('.cursor', 'commands', 'plan.md')));
|
||||||
assert.ok(operationFor(plan, path.join('.cursor', 'hooks', 'hook.js')));
|
assert.ok(operationFor(plan, path.join('.cursor', 'hooks', 'hook.js')));
|
||||||
|
|||||||
@ -202,6 +202,44 @@ function runTests() {
|
|||||||
);
|
);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('plans cursor agents with ecc-prefixed filenames to avoid agent collisions', () => {
|
||||||
|
const repoRoot = path.join(__dirname, '..', '..');
|
||||||
|
const projectRoot = '/workspace/app';
|
||||||
|
|
||||||
|
const plan = planInstallTargetScaffold({
|
||||||
|
target: 'cursor',
|
||||||
|
repoRoot,
|
||||||
|
projectRoot,
|
||||||
|
modules: [
|
||||||
|
{
|
||||||
|
id: 'agents-core',
|
||||||
|
paths: ['agents'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
plan.operations.some(operation => (
|
||||||
|
normalizedRelativePath(operation.sourceRelativePath) === 'agents/architect.md'
|
||||||
|
&& operation.destinationPath === path.join(projectRoot, '.cursor', 'agents', 'ecc-architect.md')
|
||||||
|
)),
|
||||||
|
'Should prefix Cursor agent files with ecc-'
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
!plan.operations.some(operation => (
|
||||||
|
operation.destinationPath === path.join(projectRoot, '.cursor', 'agents', 'architect.md')
|
||||||
|
)),
|
||||||
|
'Should not write bare Cursor agent filenames'
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
!plan.operations.some(operation => (
|
||||||
|
normalizedRelativePath(operation.sourceRelativePath) === 'agents'
|
||||||
|
&& operation.destinationPath === path.join(projectRoot, '.cursor', 'agents')
|
||||||
|
)),
|
||||||
|
'Should not plan a whole-directory Cursor agent copy'
|
||||||
|
);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (test('plans cursor platform rule files as .mdc and excludes rule README docs', () => {
|
if (test('plans cursor platform rule files as .mdc and excludes rule README docs', () => {
|
||||||
const repoRoot = path.join(__dirname, '..', '..');
|
const repoRoot = path.join(__dirname, '..', '..');
|
||||||
const projectRoot = '/workspace/app';
|
const projectRoot = '/workspace/app';
|
||||||
|
|||||||
@ -136,7 +136,8 @@ function runTests() {
|
|||||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-agents.mdc')));
|
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-agents.mdc')));
|
||||||
assert.ok(!fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-agents.md')));
|
assert.ok(!fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-agents.md')));
|
||||||
assert.ok(!fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'README.mdc')));
|
assert.ok(!fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'README.mdc')));
|
||||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'agents', 'architect.md')));
|
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'agents', 'ecc-architect.md')));
|
||||||
|
assert.ok(!fs.existsSync(path.join(projectDir, '.cursor', 'agents', 'architect.md')));
|
||||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'commands', 'plan.md')));
|
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'commands', 'plan.md')));
|
||||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'hooks.json')));
|
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'hooks.json')));
|
||||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'mcp.json')));
|
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'mcp.json')));
|
||||||
|
|||||||
@ -93,6 +93,21 @@ function runTests() {
|
|||||||
);
|
);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('README documents Cursor agent namespace and loading caveat', () => {
|
||||||
|
assert.ok(
|
||||||
|
readme.includes('`.cursor/agents/ecc-*.md`'),
|
||||||
|
'README should document the Cursor agent namespace'
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
readme.includes('Cursor-native loading behavior can vary by Cursor build.'),
|
||||||
|
'README should avoid overclaiming Cursor agent loading semantics'
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
readme.includes('ECC does not install root `AGENTS.md` into `.cursor/`.'),
|
||||||
|
'README should explain why root AGENTS.md is not copied into Cursor context'
|
||||||
|
);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (test('README explains plugin-path cleanup and rules scoping', () => {
|
if (test('README explains plugin-path cleanup and rules scoping', () => {
|
||||||
assert.ok(
|
assert.ok(
|
||||||
readme.includes('remove the plugin from Claude Code'),
|
readme.includes('remove the plugin from Claude Code'),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user