executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
build-cli-pluginBuild a plugin or adapter for a CLI tool using the abstract base class pattern. Covers defining the contract (static fields, required methods), choosing an installation strategy (symlink, copy, append-to-file), implementing detection, install/uninstall with idempotency, listing, auditing, and registering the plugin. Use when adding support for a new framewo…
Independent GitHub snapshot · Automated classification · Original documentation governs
以抽象基類模式為 CLI 工具之可插架構加新插件或適配器。
symlink、copy、file-per-item 或 append-to-file基類立諸插件須實作之介面:
export class FrameworkAdapter {
static id = 'base'; // Unique identifier
static displayName = 'Base'; // Human-readable name
static strategy = 'symlink'; // Installation strategy
static contentTypes = ['skill']; // What this adapter handles
async detect(projectDir) { return false; }
getTargetPath(projectDir, scope) { throw new Error('Not implemented'); }
async install(item, projectDir, scope, options) { throw new Error('Not implemented'); }
async uninstall(item, projectDir, scope, options) { throw new Error('Not implemented'); }
async listInstalled(projectDir, scope) { return []; }
async audit(projectDir, scope) { return { framework: this.constructor.displayName, ok: [], warnings: [], errors: [] }; }
supports(contentType) { return this.constructor.contentTypes.includes(contentType); }
}
靜態欄位定插件之身與能:
id:用於 --framework <id> 選項與結果報告displayName:於人可讀輸出所示strategy:定內容如何至目標contentTypes:濾此適配器所接之項若基類尚不存,先創之。此模式可伸至任多插件。
預期: 含靜身欄位與抽象方法之基類。
失敗時: 若基類有方法不適於諸插件(如非諸框架皆支 audit),提預設實作返合理無操作。
| Strategy | When to use | Example |
|---|---|---|
| symlink | 目標直讀源文件。最廉、恆同步。 | Claude Code 讀 .claude/skills/<name>/ 之 symlinks |
| copy | 目標需文件於其自目錄。改不傳。 | 某些 IDE 只索其自目錄 |
| file-per-item | 目標期每項一文件,具特定格式。 | Cursor .mdc 規則文件 |
| append-to-file | 目標讀單指令文件。 | Aider CONVENTIONS.md、Codex AGENTS.md |
策略定實作之形:
symlinkSync(source, target)——處相對對絕對路徑cpSync(source, target, { recursive: true })——處覆寫writeFileSync(target, transform(content))——或需格式轉預期: 擇策略附明理,基於目標框架如何發現內容。
失敗時: 若不確,查框架之文檔察其如何發現配置或指令文件。若框架讀任意目錄,預設用 symlink。
偵測告 CLI 項目中有何框架:
// In detector.js — each rule checks for a filesystem marker
const RULES = [
{
id: 'my-framework',
displayName: 'My Framework',
check: (dir) => existsSync(resolve(dir, '.myframework/')),
marker: '.myframework/',
scope: 'project',
},
];
偵測策略:
.claude/、.cursor/、.gemini/opencode.json、.aider.conf.ymlAGENTS.md、CONVENTIONS.md~/.openclaw/、~/.hermes/恆於偵測果返標記,以使用戶可解框架何以被偵測。
預期: 偵測規則可靠辨框架而無偽陽。
失敗時: 若框架無唯一標記(泛目錄名),用標記之合或求明之 --framework 指定。
async install(item, projectDir, scope, options) {
const targetDir = this.getTargetPath(projectDir, scope);
const targetPath = resolve(targetDir, item.id);
// Idempotency: skip if already installed (unless force)
if (existsSync(targetPath) && !options.force) {
return { action: 'skipped', path: targetPath };
}
if (options.dryRun) {
return { action: 'created', path: targetPath, details: 'dry-run' };
}
// Ensure parent directory exists
mkdirSync(targetDir, { recursive: true });
// Strategy-specific installation
if (this.constructor.strategy === 'symlink') {
const relPath = relative(targetDir, item.sourceDir);
symlinkSync(relPath, targetPath);
} else if (this.constructor.strategy === 'copy') {
cpSync(item.sourceDir, targetPath, { recursive: true });
}
return { action: 'created', path: targetPath };
}
冪等之則:
--force 未設--force 已設(先刪再裝)action: 'created' 成{ action, path, details? }預期: 安裝於目標路徑建內容,既存則略,重 --force 與 --dry-run。
失敗時: 若 Windows/NTFS 上 symlink 建失,退為目錄 junction 或 copy。記此退。
async uninstall(item, projectDir, scope, options) {
const targetDir = this.getTargetPath(projectDir, scope);
const targetPath = resolve(targetDir, item.id);
if (!existsSync(targetPath)) {
return { action: 'skipped', path: targetPath };
}
if (options.dryRun) {
return { action: 'removed', path: targetPath };
}
// Remove the installed content
rmSync(targetPath, { recursive: true });
return { action: 'removed', path: targetPath };
}
清理之慮:
預期: 卸載只刪插件之內容而無他。
失敗時: 若刪失(權限、鎖文件),返錯結果而非拋。
async listInstalled(projectDir, scope) {
const targetDir = this.getTargetPath(projectDir, scope);
if (!existsSync(targetDir)) return [];
const entries = readdirSync(targetDir);
return entries.map(name => {
const fullPath = resolve(targetDir, name);
const broken = lstatSync(fullPath).isSymbolicLink()
&& !existsSync(fullPath);
return { id: name, type: 'skill', broken };
});
}
async audit(projectDir, scope) {
const items = await this.listInstalled(projectDir, scope);
const ok = items.filter(i => !i.broken);
const broken = items.filter(i => i.broken);
return {
framework: this.constructor.displayName,
ok: [`${ok.length} skills installed`],
warnings: [],
errors: broken.map(i => `Broken: ${i.id}`),
};
}
預期: 列舉返諸裝項,附破鏈偵測。審總健康。
失敗時: 若目標目錄不存,返空(非錯——框架只是無裝)。
// In adapters/index.js
import { MyFrameworkAdapter } from './my-framework.js';
register(MyFrameworkAdapter);
註冊令適配器可用於:
detectFrameworks() → getAdaptersForDetections())--framework my-framework)listAdapters())預期: 適配器於 tool detect 輸出顯,可以 --framework 為標。
失敗時: 若適配器不顯,驗 static id 合偵測規則之 id 且 register() 已呼。
describe('adapter: my-framework (dry-run)', () => {
it('targets the correct path', () => {
const out = run('install create-skill --framework my-framework --dry-run');
assert.match(out, /\.myframework/i);
});
});
至少測:乾跑路徑、偵測存、內容類型支援。
預期: 適配器專之測確安裝路徑與行為。
失敗時: 若框架於 CI 中未偵(無標記目錄),測中明用 --framework。
id、displayName、strategy、contentTypes)已設install() 冪等(存則略、重 --force)uninstall() 只刪插件所創內容listInstalled() 偵破 symlinksaudit() 準報健康tool detect 顯mkdirSync(dir, { recursive: true }) 後創內容。<!-- start:id --> / <!-- end:id -->),重裝複內容。恆包附加之內容。.config/)或配諸框架。用目錄中之專文件標記。supports() 之查:安裝器呼 supports(item.type) 後派。若 contentTypes 誤,適配器默略項。scaffold-cli-command — 建用此插件之 CLI 命令test-cli-application — CLI 工具之測模式,含適配器測design-cli-output — 安/卸載結果之終端輸出