mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-26 18:35:05 +00:00
Merge PR #3456: adapt manual Claude component references
This commit is contained in:
5 files changed
+123
-5
No files matched your search
@@ -2,8 +2,8 @@
|
||||
|
||||
## Available Agents
|
||||
|
||||
ECC agents ship with the `ecc@ecc` plugin, not in `~/.claude/agents/`.
|
||||
They are invoked through the Agent tool with a plugin-scoped `subagent_type`:
|
||||
ECC agents come either from the `ecc@ecc` plugin (plugin-scoped names, `ecc:<name>`) or from a manual install into `~/.claude/agents/` (bare names, `<name>`). Use the name that matches the install you have; the manual installer strips the `ecc:` prefix from copied files.
|
||||
They are invoked through the Agent tool with that `subagent_type`:
|
||||
|
||||
```text
|
||||
Agent(subagent_type: "ecc:planner", prompt: "...")
|
||||
|
||||
@@ -52,6 +52,10 @@ const {
|
||||
} = require('./excluded-paths-reconciliation');
|
||||
const { buildInstallIndex, rewriteRelativeLinks } = require('./link-rewrite');
|
||||
const { transformInstallContent } = require('./content-transform');
|
||||
const { stripPluginNamespace } = require('./plugin-namespace');
|
||||
|
||||
// Manual Claude targets expose bare component names.
|
||||
const MANUAL_CLAUDE_TARGETS = new Set(['claude', 'claude-project']);
|
||||
|
||||
function isMarkdownPath(filePath) {
|
||||
return /\.(md|mdx|markdown)$/i.test(String(filePath || ''));
|
||||
@@ -858,17 +862,20 @@ function applyInstallPlanLocked(plan, dependencies = {}, settingsLockHeld = fals
|
||||
&& operation.sourceRelativePath
|
||||
&& isMarkdownPath(operation.destinationPath)
|
||||
);
|
||||
if (operation.kind === 'copy-file' && (operation.contentTransform || needsLinkRewrite)) {
|
||||
const stripNamespace = MANUAL_CLAUDE_TARGETS.has(plan.adapter?.target)
|
||||
&& isMarkdownPath(operation.destinationPath);
|
||||
if (operation.kind === 'copy-file' && (operation.contentTransform || needsLinkRewrite || stripNamespace)) {
|
||||
const transformed = transformInstallContent(
|
||||
operation,
|
||||
fs.readFileSync(operation.sourcePath, 'utf8')
|
||||
);
|
||||
const namespaced = stripNamespace ? stripPluginNamespace(transformed) : transformed;
|
||||
const installedContent = needsLinkRewrite
|
||||
? rewriteRelativeLinks(transformed, {
|
||||
? rewriteRelativeLinks(namespaced, {
|
||||
sourceRel: operation.sourceRelativePath,
|
||||
index: linkIndex,
|
||||
})
|
||||
: transformed;
|
||||
: namespaced;
|
||||
const writeOptions = getOpenCodeActivationWriteOptions(operation, activationSnapshot);
|
||||
if (writeOptions.expectedContent) {
|
||||
writeFileNoFollow(operation.destinationPath, installedContent, {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
// The ecc@ecc plugin exposes agents, commands and skills under an `ecc:`
|
||||
// namespace (`ecc:planner`, `/ecc:plan`). A manual install
|
||||
// (install-apply --target claude|claude-project) places the same files at bare
|
||||
// names, so plugin-prefixed references in copied markdown must drop the prefix
|
||||
// or they point at agents and commands that do not exist in that layout.
|
||||
//
|
||||
// The leading guard keeps `ecc@ecc` and words that merely end in "ecc" (e.g.
|
||||
// "decc:") untouched; the lookahead requires a real identifier after the colon.
|
||||
const PLUGIN_NAMESPACE_PREFIX = /(^|[^A-Za-z0-9_@.-])ecc:(?=[a-z][a-z0-9-]*)/gm;
|
||||
|
||||
function stripPluginNamespace(content) {
|
||||
return String(content).replace(PLUGIN_NAMESPACE_PREFIX, '$1');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
stripPluginNamespace,
|
||||
};
|
||||
@@ -162,6 +162,7 @@ function writeManifestSourceFixture(root) {
|
||||
writeFile(root, path.join('src', 'stray.pyd'), 'ignored\n');
|
||||
writeFile(root, path.join('src', 'nested', 'ecc-install-state.json'), '{}\n');
|
||||
writeFile(root, path.join('rules', 'common', 'coding-style.md'), '# Common\n');
|
||||
writeFile(root, path.join('rules', 'common', 'agents.md'), 'Use the `ecc:planner` agent.\n');
|
||||
writeFile(root, path.join('skills', 'demo', 'SKILL.md'), '# Demo\n');
|
||||
writeFile(root, 'standalone.txt', 'standalone\n');
|
||||
writeFile(root, path.join('runtime', 'ecc', 'install-state.json'), '{}\n');
|
||||
@@ -684,6 +685,11 @@ function runTests() {
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'src', 'app.js')));
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'standalone.txt')));
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'plugin.json')));
|
||||
const installedAgentsRule = fs.readFileSync(
|
||||
path.join(homeDir, '.claude', 'rules', 'ecc', 'common', 'agents.md'),
|
||||
'utf8'
|
||||
);
|
||||
assert.strictEqual(installedAgentsRule, 'Use the `planner` agent.\n');
|
||||
const state = JSON.parse(fs.readFileSync(path.join(homeDir, '.claude', 'ecc', 'install-state.json'), 'utf8'));
|
||||
assert.strictEqual(state.request.profile, 'minimal');
|
||||
assert.deepStrictEqual(state.resolution.selectedModules, ['fixture-core']);
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Tests for scripts/lib/install/plugin-namespace.js — manual Claude installs
|
||||
* place agents, commands and skills at bare names, so plugin-prefixed
|
||||
* `ecc:<name>` references in copied markdown must lose the prefix.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const { stripPluginNamespace } = require('../../scripts/lib/install/plugin-namespace');
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` PASS ${name}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(` FAIL ${name}`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
console.error(error.stack || error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
console.log('Running install plugin-namespace tests...\n');
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
const record = ok => (ok ? passed++ : failed++);
|
||||
|
||||
record(test('strips the prefix from agent names in code spans, bold and tables', () => {
|
||||
const input = [
|
||||
'| ecc:planner | Planning |',
|
||||
'1. Use **ecc:code-reviewer** agent',
|
||||
'Use `ecc:tdd-guide` here',
|
||||
].join('\n');
|
||||
assert.strictEqual(stripPluginNamespace(input), [
|
||||
'| planner | Planning |',
|
||||
'1. Use **code-reviewer** agent',
|
||||
'Use `tdd-guide` here',
|
||||
].join('\n'));
|
||||
}));
|
||||
|
||||
record(test('strips the prefix from agent frontmatter and tool-call arguments', () => {
|
||||
assert.strictEqual(
|
||||
stripPluginNamespace('agent: ecc:security-reviewer'),
|
||||
'agent: security-reviewer'
|
||||
);
|
||||
assert.strictEqual(
|
||||
stripPluginNamespace('Agent(subagent_type: "ecc:planner", prompt: "...")'),
|
||||
'Agent(subagent_type: "planner", prompt: "...")'
|
||||
);
|
||||
}));
|
||||
|
||||
record(test('strips the prefix from slash commands', () => {
|
||||
assert.strictEqual(stripPluginNamespace('see `/ecc:ecc-guide`'), 'see `/ecc-guide`');
|
||||
assert.strictEqual(stripPluginNamespace('/ecc:plan "Add auth"'), '/plan "Add auth"');
|
||||
}));
|
||||
|
||||
record(test('leaves the plugin identifier ecc@ecc untouched', () => {
|
||||
const input = '`/plugin install ecc@ecc` and `ecc@ecc`';
|
||||
assert.strictEqual(stripPluginNamespace(input), input);
|
||||
}));
|
||||
|
||||
record(test('leaves words that only end in "ecc" untouched', () => {
|
||||
const input = 'the decc:thing and tecc:value';
|
||||
assert.strictEqual(stripPluginNamespace(input), input);
|
||||
}));
|
||||
|
||||
record(test('does not touch a colon with no identifier after it', () => {
|
||||
const input = 'the `ecc:` prefix in plugin mode';
|
||||
assert.strictEqual(stripPluginNamespace(input), input);
|
||||
}));
|
||||
|
||||
record(test('returns content with no prefix unchanged', () => {
|
||||
const input = '# Plain markdown\n\nNo namespace here.\n';
|
||||
assert.strictEqual(stripPluginNamespace(input), input);
|
||||
}));
|
||||
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
return failed === 0;
|
||||
}
|
||||
|
||||
const ok = runTests();
|
||||
process.exit(ok ? 0 : 1);
|
||||
Reference in new issue
Block a user