adspecs 0.1.30 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +24 -0
- package/.qoder-plugin/plugin.json +1 -1
- package/.workbuddy-plugin/plugin.json +1 -1
- package/CLAUDE.md +3 -2
- package/bin/adspecs.js +1 -1
- package/package.json +3 -2
- package/scripts/postinstall.js +16 -0
- package/scripts/sync-version.js +11 -1
- package/src/commands/plugin.js +67 -47
- package/src/commands/update.js +2 -2
- package/src/utils.js +133 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "adspecs",
|
|
3
|
+
"displayName": "adspecs AI 工程规范驱动开发插件",
|
|
4
|
+
"version": "0.1.32",
|
|
5
|
+
"description": "AI-first specification-driven development plugin for ECP platform full-lifecycle development",
|
|
6
|
+
"descriptionZh": "AI优先的工程规范驱动开发插件,覆盖需求分析、PRD、系统设计、任务拆解、TDD、代码评审、Wiki同步全流程",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Qingwen Chen",
|
|
9
|
+
"email": "cqinwn@qq.com"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://gitee.com/yuebon/adspecs",
|
|
12
|
+
"repository": "https://gitee.com/yuebon/adspecs",
|
|
13
|
+
"logo": "./assets/avatar.svg",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"codebuddy-plugin",
|
|
16
|
+
"adspecs",
|
|
17
|
+
"spec-driven",
|
|
18
|
+
"tdd"
|
|
19
|
+
],
|
|
20
|
+
"category": "developer-tools",
|
|
21
|
+
"skills": "./skills/",
|
|
22
|
+
"hooks": "./hooks/hooks.json",
|
|
23
|
+
"defaultEnabled": true
|
|
24
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adspecs",
|
|
3
3
|
"displayName": "adspecs AI 工程规范驱动开发插件",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.32",
|
|
5
5
|
"description": "AI-first specification-driven development plugin for ECP platform full-lifecycle development",
|
|
6
6
|
"descriptionZh": "AI优先的工程规范驱动开发插件,覆盖需求分析、PRD、系统设计、任务拆解、TDD、代码评审、Wiki同步全流程",
|
|
7
7
|
"author": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adspecs",
|
|
3
3
|
"displayName": "adspecs AI 工程规范驱动开发插件",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.32",
|
|
5
5
|
"description": "AI-first specification-driven development plugin for ECP platform full-lifecycle development",
|
|
6
6
|
"descriptionZh": "AI优先的工程规范驱动开发插件,覆盖需求分析、PRD、系统设计、任务拆解、TDD、代码评审、Wiki同步全流程",
|
|
7
7
|
"author": {
|
package/CLAUDE.md
CHANGED
|
@@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
6
6
|
|
|
7
7
|
**adspecs AI Plugin** — a Claude Code / Qoder / WorkBuddy plugin for ECP (Enterprise Cloud Platform) full-lifecycle AI-driven development. Provides 30 skills (slash commands) covering requirements, SOW authoring, system design, task breakdown, unit testing, code review, feature status tracking, and wiki sync.
|
|
8
8
|
|
|
9
|
-
- **Plugin name**: `adspecs` (see `.claude-plugin/plugin.json` / `.qoder-plugin/plugin.json` / `.workbuddy-plugin/plugin.json`)
|
|
9
|
+
- **Plugin name**: `adspecs` (see `.claude-plugin/plugin.json` / `.qoder-plugin/plugin.json` / `.codebuddy-plugin/plugin.json` / `.workbuddy-plugin/plugin.json`)
|
|
10
10
|
- **Author**: Qingwen Chen
|
|
11
11
|
- **License**: Apache 2.0
|
|
12
12
|
- **Language**: Chinese (all documentation, templates, and skill instructions are in Chinese)
|
|
@@ -18,7 +18,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
18
18
|
adspecs/
|
|
19
19
|
├── .claude-plugin/ # Plugin metadata for Claude Code
|
|
20
20
|
├── .qoder-plugin/ # Plugin metadata for Qoder
|
|
21
|
-
├── .
|
|
21
|
+
├── .codebuddy-plugin/ # Plugin metadata for WorkBuddy (CodeBuddy format)
|
|
22
|
+
├── .workbuddy-plugin/ # Plugin metadata for WorkBuddy (legacy, backward compat)
|
|
22
23
|
├── .adspecs/ # Plugin runtime config, templates, scripts & extensions (copied to target projects)
|
|
23
24
|
│ ├── templates/ # 21 standard document templates (PRD, system design, etc.)
|
|
24
25
|
│ ├── extensions/ # Extension configs (git hooks, etc.)
|
package/bin/adspecs.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adspecs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"description": "AI first 工程规范驱动开发插件 — 支持 npm 安装和 CLI 初始化",
|
|
5
5
|
"bin": {
|
|
6
6
|
"adspecs": "bin/adspecs.js"
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"src/",
|
|
12
12
|
".claude-plugin/",
|
|
13
13
|
".qoder-plugin/",
|
|
14
|
+
".codebuddy-plugin/",
|
|
14
15
|
".workbuddy-plugin/",
|
|
15
16
|
".adspecs/",
|
|
16
17
|
"skills/",
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
"test": "node --test src/**/*.test.js",
|
|
32
33
|
"postinstall": "node scripts/postinstall.js",
|
|
33
34
|
"prepublishOnly": "node scripts/prepublish-check.js",
|
|
34
|
-
"version": "node scripts/sync-version.js && git add .claude-plugin/plugin.json .claude-plugin/marketplace.json .qoder-plugin/plugin.json .workbuddy-plugin/plugin.json",
|
|
35
|
+
"version": "node scripts/sync-version.js && git add .claude-plugin/plugin.json .claude-plugin/marketplace.json .qoder-plugin/plugin.json .codebuddy-plugin/plugin.json .workbuddy-plugin/plugin.json",
|
|
35
36
|
"sync-version": "node scripts/sync-version.js"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
package/scripts/postinstall.js
CHANGED
|
@@ -137,6 +137,22 @@ try {
|
|
|
137
137
|
} catch {
|
|
138
138
|
// codebuddy plugin list 失败 → 静默跳过
|
|
139
139
|
}
|
|
140
|
+
} else {
|
|
141
|
+
// 5. WorkBuddy — 无 codebuddy CLI 时的回退方案
|
|
142
|
+
// 手工注册:创建 marketplace 目录 → junction 链接 → known_marketplaces.json → enabledPlugins
|
|
143
|
+
try {
|
|
144
|
+
var wkHome = path.join(require('os').homedir(), '.workbuddy');
|
|
145
|
+
if (fs.existsSync(wkHome)) {
|
|
146
|
+
var utilsPath = path.join(installDir, 'src', 'utils');
|
|
147
|
+
var { registerWorkBuddyPlugin } = require(utilsPath);
|
|
148
|
+
var wbResult = registerWorkBuddyPlugin(installDir, 'adspecs');
|
|
149
|
+
if (wbResult.success) {
|
|
150
|
+
refreshedPlatforms.push('WorkBuddy');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
// 注册失败 → 静默跳过(非 WorkBuddy 用户无需处理)
|
|
155
|
+
}
|
|
140
156
|
}
|
|
141
157
|
|
|
142
158
|
// 输出一行提示(仅当有平台被刷新)
|
package/scripts/sync-version.js
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* 版本同步脚本
|
|
6
6
|
*
|
|
7
7
|
* 在 `npm version <type>` 时自动执行(通过 preversion 钩子)。
|
|
8
|
-
* 将 package.json
|
|
8
|
+
* 将 package.json 的新版本同步到五套插件清单文件:
|
|
9
9
|
* - .claude-plugin/plugin.json
|
|
10
10
|
* - .claude-plugin/marketplace.json
|
|
11
11
|
* - .qoder-plugin/plugin.json
|
|
12
|
+
* - .codebuddy-plugin/plugin.json
|
|
12
13
|
* - .workbuddy-plugin/plugin.json
|
|
13
14
|
*
|
|
14
15
|
* 也可手动运行:`npm run sync-version`
|
|
@@ -87,6 +88,15 @@ function main() {
|
|
|
87
88
|
updated.push(`.workbuddy-plugin/plugin.json (${old} → ${newVersion})`);
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
// 5. .codebuddy-plugin/plugin.json — 始终与 package.json 保持版本一致
|
|
92
|
+
const cbPlugin = readJson('.codebuddy-plugin/plugin.json');
|
|
93
|
+
if (cbPlugin && cbPlugin.version !== newVersion) {
|
|
94
|
+
const old = cbPlugin.version;
|
|
95
|
+
cbPlugin.version = newVersion;
|
|
96
|
+
writeJson('.codebuddy-plugin/plugin.json', cbPlugin);
|
|
97
|
+
updated.push(`.codebuddy-plugin/plugin.json (${old} → ${newVersion})`);
|
|
98
|
+
}
|
|
99
|
+
|
|
90
100
|
if (updated.length === 0) {
|
|
91
101
|
console.log('✅ 所有清单版本已一致,无需更新');
|
|
92
102
|
} else {
|
package/src/commands/plugin.js
CHANGED
|
@@ -34,7 +34,7 @@ function getPluginName(platform) {
|
|
|
34
34
|
if (platform === 'qoder') {
|
|
35
35
|
manifestDir = '.qoder-plugin';
|
|
36
36
|
} else if (platform === 'workbuddy') {
|
|
37
|
-
manifestDir = '.
|
|
37
|
+
manifestDir = '.codebuddy-plugin';
|
|
38
38
|
} else {
|
|
39
39
|
manifestDir = '.claude-plugin';
|
|
40
40
|
}
|
|
@@ -121,7 +121,7 @@ function checkCodebuddyCli() {
|
|
|
121
121
|
* 若 CLI 不可用则回退为手工配置 enabledPlugins
|
|
122
122
|
*/
|
|
123
123
|
async function installForWorkBuddy(pluginName, scope) {
|
|
124
|
-
const workbuddyManifest = path.join(PLUGIN_DIR, '.
|
|
124
|
+
const workbuddyManifest = path.join(PLUGIN_DIR, '.codebuddy-plugin', 'plugin.json');
|
|
125
125
|
if (!fs.existsSync(workbuddyManifest)) {
|
|
126
126
|
throw new Error(`未找到 WorkBuddy 插件清单: ${workbuddyManifest}`);
|
|
127
127
|
}
|
|
@@ -147,34 +147,23 @@ async function installForWorkBuddy(pluginName, scope) {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
} else {
|
|
150
|
-
// 方式 B
|
|
150
|
+
// 方式 B:回退为手工配置(无 CLI 时)
|
|
151
|
+
// 完整注册:创建 marketplace 目录 → junction 链接 → known_marketplaces.json → enabledPlugins
|
|
151
152
|
console.log(chalk.yellow(' ⚠ 未检测到 codebuddy CLI,使用手工配置方式'));
|
|
152
|
-
|
|
153
|
-
const settingsPath = path.join(workbuddyHome, 'settings.json');
|
|
154
|
-
|
|
155
|
-
// 确保 .workbuddy 目录存在
|
|
156
|
-
fs.mkdirSync(workbuddyHome, { recursive: true });
|
|
153
|
+
console.log('');
|
|
157
154
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (fs.existsSync(settingsPath)) {
|
|
161
|
-
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_e) { /* use default */ }
|
|
162
|
-
}
|
|
163
|
-
if (!settings.enabledPlugins) settings.enabledPlugins = {};
|
|
155
|
+
const { registerWorkBuddyPlugin } = require('../utils');
|
|
156
|
+
const result = registerWorkBuddyPlugin(PLUGIN_DIR, pluginName);
|
|
164
157
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
158
|
+
if (result.success) {
|
|
159
|
+
console.log(chalk.green(' ✅ 已完成 WorkBuddy 插件注册'));
|
|
160
|
+
console.log(chalk.gray(' 📁 插件目录:'));
|
|
161
|
+
console.log(chalk.gray(` ${PLUGIN_DIR}`));
|
|
162
|
+
console.log(chalk.gray(' 📁 marketplace:'));
|
|
163
|
+
console.log(chalk.gray(` ~/.workbuddy/plugins/marketplaces/local/`));
|
|
168
164
|
} else {
|
|
169
|
-
|
|
165
|
+
throw new Error(`WorkBuddy 插件注册失败: ${result.reason}`);
|
|
170
166
|
}
|
|
171
|
-
|
|
172
|
-
settings.enabledPlugins[registryKey] = true;
|
|
173
|
-
|
|
174
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
175
|
-
console.log(chalk.green(` ✅ 已在 settings.json 中启用插件 (${registryKey})`));
|
|
176
|
-
console.log(chalk.gray(' 📁 请确保 WorkBuddy 可访问以下目录:'));
|
|
177
|
-
console.log(chalk.gray(` ${PLUGIN_DIR}`));
|
|
178
167
|
console.log(chalk.gray(' 请重启 WorkBuddy 使 skills 与 hooks 生效。'));
|
|
179
168
|
}
|
|
180
169
|
}
|
|
@@ -345,23 +334,7 @@ async function installQoderProject(pluginName, qoderManifest) {
|
|
|
345
334
|
}
|
|
346
335
|
}
|
|
347
336
|
|
|
348
|
-
|
|
349
|
-
* 递归复制目录(排除指定目录名)
|
|
350
|
-
*/
|
|
351
|
-
function copyDirRecursive(src, dest, excludeDirs) {
|
|
352
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
353
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
354
|
-
for (const entry of entries) {
|
|
355
|
-
if (excludeDirs && excludeDirs.includes(entry.name)) continue;
|
|
356
|
-
const srcPath = path.join(src, entry.name);
|
|
357
|
-
const destPath = path.join(dest, entry.name);
|
|
358
|
-
if (entry.isDirectory()) {
|
|
359
|
-
copyDirRecursive(srcPath, destPath, excludeDirs);
|
|
360
|
-
} else {
|
|
361
|
-
fs.copyFileSync(srcPath, destPath);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
337
|
+
const { copyDirRecursive } = require('../utils');
|
|
365
338
|
|
|
366
339
|
/**
|
|
367
340
|
* Qoder 用户级卸载 — 移除链接 + 注册表条目 + 启用配置
|
|
@@ -530,13 +503,43 @@ async function uninstallWorkBuddy(pluginName, scope) {
|
|
|
530
503
|
}
|
|
531
504
|
}
|
|
532
505
|
} else {
|
|
533
|
-
// 回退:手工清除 enabledPlugins 条目
|
|
534
|
-
const
|
|
506
|
+
// 回退:手工清除 marketplace 注册 + junction 链接 + enabledPlugins 条目
|
|
507
|
+
const workbuddyHome = path.join(os.homedir(), '.workbuddy');
|
|
508
|
+
const localMarketplaceDir = path.join(workbuddyHome, 'plugins', 'marketplaces', 'local');
|
|
509
|
+
const pluginLinkPath = path.join(localMarketplaceDir, pluginName);
|
|
510
|
+
const settingsPath = path.join(workbuddyHome, 'settings.json');
|
|
511
|
+
const knownMarketplacesPath = path.join(workbuddyHome, 'plugins', 'known_marketplaces.json');
|
|
512
|
+
|
|
513
|
+
let cleaned = false;
|
|
514
|
+
|
|
515
|
+
// 1. 移除 junction / 目录
|
|
516
|
+
if (fs.existsSync(pluginLinkPath)) {
|
|
517
|
+
try {
|
|
518
|
+
fs.rmSync(pluginLinkPath, { recursive: true, force: true });
|
|
519
|
+
console.log(chalk.green(' ✅ 已移除插件链接'));
|
|
520
|
+
cleaned = true;
|
|
521
|
+
} catch (err) {
|
|
522
|
+
console.log(chalk.yellow(` ⚠ 移除插件链接失败: ${err.message}`));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// 2. 移除 marketplace 目录级清单 (.codebuddy-plugin/marketplace.json)
|
|
527
|
+
const catalogDir = path.join(localMarketplaceDir, '.codebuddy-plugin');
|
|
528
|
+
if (fs.existsSync(catalogDir)) {
|
|
529
|
+
try {
|
|
530
|
+
fs.rmSync(catalogDir, { recursive: true, force: true });
|
|
531
|
+
console.log(chalk.green(' ✅ 已移除 marketplace 清单'));
|
|
532
|
+
cleaned = true;
|
|
533
|
+
} catch (err) {
|
|
534
|
+
console.log(chalk.yellow(` ⚠ 移除 marketplace 清单失败: ${err.message}`));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// 3. 从 settings.json 移除
|
|
535
539
|
if (fs.existsSync(settingsPath)) {
|
|
536
540
|
let settings = {};
|
|
537
541
|
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_e) { /* use default */ }
|
|
538
542
|
if (settings.enabledPlugins) {
|
|
539
|
-
// 移除所有 adspecs 相关条目
|
|
540
543
|
const keysBefore = Object.keys(settings.enabledPlugins).length;
|
|
541
544
|
for (var key in settings.enabledPlugins) {
|
|
542
545
|
if (key.startsWith(pluginName + '@')) {
|
|
@@ -546,12 +549,29 @@ async function uninstallWorkBuddy(pluginName, scope) {
|
|
|
546
549
|
const keysAfter = Object.keys(settings.enabledPlugins).length;
|
|
547
550
|
if (keysBefore > keysAfter) {
|
|
548
551
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
549
|
-
console.log(chalk.green(
|
|
552
|
+
console.log(chalk.green(' ✅ 已从 settings.json 移除插件配置'));
|
|
553
|
+
cleaned = true;
|
|
550
554
|
} else {
|
|
551
|
-
console.log(chalk.yellow(
|
|
555
|
+
console.log(chalk.yellow(' ⏭ 未在 settings.json 中找到插件配置'));
|
|
552
556
|
}
|
|
553
557
|
}
|
|
554
558
|
}
|
|
559
|
+
|
|
560
|
+
// 4. 从 known_marketplaces.json 移除 local marketplace
|
|
561
|
+
if (fs.existsSync(knownMarketplacesPath)) {
|
|
562
|
+
let knownMarketplaces = {};
|
|
563
|
+
try { knownMarketplaces = JSON.parse(fs.readFileSync(knownMarketplacesPath, 'utf8')); } catch (_e) { /* use default */ }
|
|
564
|
+
if (knownMarketplaces.local) {
|
|
565
|
+
delete knownMarketplaces.local;
|
|
566
|
+
fs.writeFileSync(knownMarketplacesPath, JSON.stringify(knownMarketplaces, null, 4) + '\n', 'utf8');
|
|
567
|
+
console.log(chalk.green(' ✅ 已从 known_marketplaces.json 移除 local marketplace'));
|
|
568
|
+
cleaned = true;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (!cleaned) {
|
|
573
|
+
console.log(chalk.yellow(' ⏭ 未找到需要清理的插件配置'));
|
|
574
|
+
}
|
|
555
575
|
}
|
|
556
576
|
console.log(chalk.gray(' 请重启 WorkBuddy 使变更生效。'));
|
|
557
577
|
}
|
package/src/commands/update.js
CHANGED
|
@@ -83,8 +83,8 @@ module.exports = async function update(options) {
|
|
|
83
83
|
console.log('');
|
|
84
84
|
console.log(' 🔄 插件注册刷新:');
|
|
85
85
|
console.log(' Claude Code — postinstall 钩子已自动刷新命令行插件注册');
|
|
86
|
-
console.log(' Qoder
|
|
87
|
-
console.log('
|
|
86
|
+
console.log(' Qoder / CodeBuddy / WorkBuddy — postinstall 钩子已自动刷新插件注册');
|
|
87
|
+
console.log(' 如未自动刷新,请执行 adspecs plugin install --platform <platform>');
|
|
88
88
|
console.log('');
|
|
89
89
|
console.log(` 验证: ${chalk.gray('adspecs --version')}`);
|
|
90
90
|
console.log('');
|
package/src/utils.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const fs = require('fs');
|
|
12
|
+
const os = require('os');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* 获取插件根目录(即 adspecs/ 目录)
|
|
@@ -70,6 +71,136 @@ function safeWriteJson(filePath, data) {
|
|
|
70
71
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
/**
|
|
75
|
+
* 递归复制目录(排除指定目录名)
|
|
76
|
+
* @param {string} src
|
|
77
|
+
* @param {string} dest
|
|
78
|
+
* @param {string[]} excludeDirs
|
|
79
|
+
*/
|
|
80
|
+
function copyDirRecursive(src, dest, excludeDirs) {
|
|
81
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
82
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
if (excludeDirs && excludeDirs.includes(entry.name)) continue;
|
|
85
|
+
const srcPath = path.join(src, entry.name);
|
|
86
|
+
const destPath = path.join(dest, entry.name);
|
|
87
|
+
if (entry.isDirectory()) {
|
|
88
|
+
copyDirRecursive(srcPath, destPath, excludeDirs);
|
|
89
|
+
} else {
|
|
90
|
+
fs.copyFileSync(srcPath, destPath);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 注册 WorkBuddy 本地插件 marketplace
|
|
97
|
+
*
|
|
98
|
+
* 手工方式完成完整注册,无需 codebuddy CLI:
|
|
99
|
+
* 1. 创建 ~/.workbuddy/plugins/marketplaces/local/ 目录
|
|
100
|
+
* 2. 创建 junction 链接(或回退复制)指向插件目录
|
|
101
|
+
* 3. 生成 marketplace 目录级清单 (.codebuddy-plugin/marketplace.json)
|
|
102
|
+
* 4. 在 known_marketplaces.json 中注册 local marketplace
|
|
103
|
+
* 5. 在 settings.json 的 enabledPlugins 中启用插件
|
|
104
|
+
*
|
|
105
|
+
* @param {string} pluginDir - adspecs 插件根目录
|
|
106
|
+
* @param {string} [pluginName='adspecs'] - 插件名称
|
|
107
|
+
* @returns {{success: boolean, reason?: string}}
|
|
108
|
+
*/
|
|
109
|
+
function registerWorkBuddyPlugin(pluginDir, pluginName) {
|
|
110
|
+
pluginName = pluginName || 'adspecs';
|
|
111
|
+
|
|
112
|
+
const workbuddyHome = path.join(os.homedir(), '.workbuddy');
|
|
113
|
+
|
|
114
|
+
// 检查 WorkBuddy 配置目录是否存在
|
|
115
|
+
if (!fs.existsSync(workbuddyHome)) {
|
|
116
|
+
return { success: false, reason: '未检测到 WorkBuddy 配置目录 (~/.workbuddy)' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const localMarketplaceDir = path.join(workbuddyHome, 'plugins', 'marketplaces', 'local');
|
|
120
|
+
const pluginLinkPath = path.join(localMarketplaceDir, pluginName);
|
|
121
|
+
const settingsPath = path.join(workbuddyHome, 'settings.json');
|
|
122
|
+
const knownMarketplacesPath = path.join(workbuddyHome, 'plugins', 'known_marketplaces.json');
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
// 1. 创建 local marketplace 目录
|
|
126
|
+
fs.mkdirSync(localMarketplaceDir, { recursive: true });
|
|
127
|
+
|
|
128
|
+
// 2. 创建 junction / symlink(已存在则先移除重建)
|
|
129
|
+
if (fs.existsSync(pluginLinkPath)) {
|
|
130
|
+
try { fs.rmSync(pluginLinkPath, { recursive: true, force: true }); } catch (_err) {
|
|
131
|
+
try { fs.unlinkSync(pluginLinkPath); } catch (_e) { /* ignore */ }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
fs.symlinkSync(pluginDir, pluginLinkPath, 'junction');
|
|
136
|
+
} catch (_symlinkErr) {
|
|
137
|
+
// junction 创建失败 → 回退为复制关键目录
|
|
138
|
+
fs.mkdirSync(pluginLinkPath, { recursive: true });
|
|
139
|
+
const entriesToCopy = ['.codebuddy-plugin', '.workbuddy-plugin', 'skills', 'hooks', 'references', 'commands'];
|
|
140
|
+
for (const entry of entriesToCopy) {
|
|
141
|
+
const srcPath = path.join(pluginDir, entry);
|
|
142
|
+
if (!fs.existsSync(srcPath)) continue;
|
|
143
|
+
const destPath = path.join(pluginLinkPath, entry);
|
|
144
|
+
if (fs.lstatSync(srcPath).isDirectory()) {
|
|
145
|
+
copyDirRecursive(srcPath, destPath, ['node_modules']);
|
|
146
|
+
} else {
|
|
147
|
+
fs.copyFileSync(srcPath, destPath);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// 同步复制根目录关键文件
|
|
151
|
+
for (const file of ['package.json', 'CLAUDE.md', 'README.md']) {
|
|
152
|
+
const srcFile = path.join(pluginDir, file);
|
|
153
|
+
if (fs.existsSync(srcFile)) {
|
|
154
|
+
fs.copyFileSync(srcFile, path.join(pluginLinkPath, file));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 3. 生成 marketplace 目录级清单(.codebuddy-plugin/marketplace.json)
|
|
160
|
+
// WorkBuddy 扫描器需要此文件才能发现 marketplace 中的插件
|
|
161
|
+
const catalogDir = path.join(localMarketplaceDir, '.codebuddy-plugin');
|
|
162
|
+
fs.mkdirSync(catalogDir, { recursive: true });
|
|
163
|
+
const pkg = getPkg();
|
|
164
|
+
safeWriteJson(path.join(catalogDir, 'marketplace.json'), {
|
|
165
|
+
name: 'local',
|
|
166
|
+
description: 'Local plugins',
|
|
167
|
+
plugins: [{
|
|
168
|
+
name: pluginName,
|
|
169
|
+
source: './' + pluginName,
|
|
170
|
+
version: pkg.version,
|
|
171
|
+
description: pkg.description
|
|
172
|
+
}]
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// 4. 注册 local marketplace
|
|
176
|
+
let knownMarketplaces = safeReadJson(knownMarketplacesPath, {});
|
|
177
|
+
const now = new Date().toISOString();
|
|
178
|
+
|
|
179
|
+
knownMarketplaces.local = {
|
|
180
|
+
type: 'directory',
|
|
181
|
+
source: {
|
|
182
|
+
source: 'directory',
|
|
183
|
+
path: localMarketplaceDir
|
|
184
|
+
},
|
|
185
|
+
installLocation: localMarketplaceDir,
|
|
186
|
+
autoUpdate: false,
|
|
187
|
+
description: 'Local plugins',
|
|
188
|
+
lastUpdated: now
|
|
189
|
+
};
|
|
190
|
+
safeWriteJson(knownMarketplacesPath, knownMarketplaces);
|
|
191
|
+
|
|
192
|
+
// 5. 启用插件
|
|
193
|
+
let settings = safeReadJson(settingsPath, {});
|
|
194
|
+
if (!settings.enabledPlugins) settings.enabledPlugins = {};
|
|
195
|
+
settings.enabledPlugins[`${pluginName}@local`] = true;
|
|
196
|
+
safeWriteJson(settingsPath, settings);
|
|
197
|
+
|
|
198
|
+
return { success: true };
|
|
199
|
+
} catch (err) {
|
|
200
|
+
return { success: false, reason: err.message };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
73
204
|
module.exports = {
|
|
74
205
|
getPluginDir,
|
|
75
206
|
getPkg,
|
|
@@ -77,4 +208,6 @@ module.exports = {
|
|
|
77
208
|
isInsidePlugin,
|
|
78
209
|
safeReadJson,
|
|
79
210
|
safeWriteJson,
|
|
211
|
+
copyDirRecursive,
|
|
212
|
+
registerWorkBuddyPlugin,
|
|
80
213
|
};
|