adspecs 0.1.26 → 0.1.28
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/.qoder-plugin/plugin.json +1 -2
- package/CLAUDE.md +19 -11
- package/INSTALL.md +63 -3
- package/bin/adspecs.js +8 -5
- package/hooks/commit-queue.js +8 -7
- package/hooks/hooks.json +4 -3
- package/hooks/platform.js +141 -0
- package/hooks/session-logger.js +9 -13
- package/hooks/session-start.js +10 -14
- package/hooks/wiki-queue.js +8 -7
- package/package.json +2 -1
- package/scripts/postinstall.js +14 -11
- package/src/commands/plugin.js +162 -22
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adspecs",
|
|
3
3
|
"displayName": "adspecs AI 工程规范驱动开发插件",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.28",
|
|
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": {
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
"documentation"
|
|
26
26
|
],
|
|
27
27
|
"skills": "./skills/",
|
|
28
|
-
"rules": "./rules/",
|
|
29
28
|
"hooks": "./hooks/hooks.json",
|
|
30
29
|
"preserveUpstreamMetadata": true
|
|
31
30
|
}
|
package/CLAUDE.md
CHANGED
|
@@ -96,22 +96,27 @@ Skills check for `.adspecs/extensions.yml` in the target project root, supportin
|
|
|
96
96
|
|
|
97
97
|
## Hooks System
|
|
98
98
|
|
|
99
|
-
Hooks are configured in `hooks/hooks.json` and run as Node.js scripts:
|
|
99
|
+
Hooks are configured in `hooks/hooks.json` and run as Node.js scripts. A shared platform detection module (`hooks/platform.js`) provides cross-platform compatibility between Claude Code and Qoder:
|
|
100
100
|
|
|
101
101
|
| Event | Script | Purpose |
|
|
102
102
|
| ------------------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------- |
|
|
103
|
-
| `SessionStart` | `session-start.js` | Injects `skills/using-
|
|
104
|
-
| `SessionStart` | `wiki-queue.js init` | Initialize wiki update queue file in
|
|
105
|
-
| `SessionStart` | `commit-queue.js init` | Initialize commit candidates queue file in
|
|
103
|
+
| `SessionStart` | `session-start.js` | Injects `skills/using-adspecs/SKILL.md` as agent context (no-op if skill doesn't exist) |
|
|
104
|
+
| `SessionStart` | `wiki-queue.js init` | Initialize wiki update queue file in state dir |
|
|
105
|
+
| `SessionStart` | `commit-queue.js init` | Initialize commit candidates queue file in state dir |
|
|
106
106
|
| `SessionStart` | `session-logger.js init` | Detect unsaved previous session → inject recovery reminder or normal guidance |
|
|
107
|
-
| `PreCompact` | `session-logger.js mark` | Context compaction imminent → inject urgent save reminder for
|
|
108
|
-
| `PostToolUse` (Write/Edit on code/doc files)
|
|
109
|
-
| `PostToolUse` (Write on `pipeline-manifest.json`) | `commit-queue.js enqueue` | Track completed features for commit
|
|
107
|
+
| `PreCompact` | `session-logger.js mark` | Context compaction imminent → inject urgent save reminder for agent to write log |
|
|
108
|
+
| `PostToolUse` (Write/Edit/SearchReplace on code/doc files) | `wiki-queue.js enqueue` | Track changed files for wiki sync |
|
|
109
|
+
| `PostToolUse` (Write/SearchReplace on `pipeline-manifest.json`) | `commit-queue.js enqueue` | Track completed features for commit |
|
|
110
110
|
| `Stop` | `wiki-queue.js report` | Suggest wiki sync at session end |
|
|
111
111
|
| `Stop` | `commit-queue.js report` | Suggest commit at session end |
|
|
112
112
|
| `Stop` | `session-logger.js report`| Mark session as missed if log not saved → recovery at next SessionStart |
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
### Platform Detection (`hooks/platform.js`)
|
|
115
|
+
|
|
116
|
+
- Detects platform via `ADSPECS_PLATFORM` env var, or platform-specific env vars (`CLAUDE_PLUGIN_ROOT` / `QODER_PLUGIN_ROOT`)
|
|
117
|
+
- State directory: `.claude/` for Claude Code, `.qoder/` for Qoder
|
|
118
|
+
- Tool output files: reads `CLAUDE_TOOL_OUTPUT_FILES` or `QODER_TOOL_OUTPUT_FILES`
|
|
119
|
+
- Output protocol: unified `hookSpecificOutput.additionalContext` JSON stdout format
|
|
115
120
|
|
|
116
121
|
## Path Configuration
|
|
117
122
|
|
|
@@ -209,9 +214,12 @@ npm publish --registry=https://registry.npmjs.org
|
|
|
209
214
|
## Installation & Distribution
|
|
210
215
|
|
|
211
216
|
```bash
|
|
212
|
-
# Register marketplace
|
|
217
|
+
# Claude Code: Register marketplace + install
|
|
213
218
|
claude plugins marketplace add <path-to>/.claude-plugin --scope user|project
|
|
214
|
-
|
|
215
|
-
# Install plugin
|
|
216
219
|
claude plugins install adspecs --scope user|project
|
|
220
|
+
|
|
221
|
+
# Qoder: CLI install
|
|
222
|
+
adspecs plugin install --platform qoder --scope user|project
|
|
223
|
+
|
|
224
|
+
# Qoder: Manual — ensure .qoder-plugin/plugin.json is accessible to Qoder
|
|
217
225
|
```
|
package/INSTALL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## 环境准备
|
|
4
4
|
|
|
5
|
-
### 1. 安装 superpowers
|
|
5
|
+
### 1. 安装 superpowers 插件(前置依赖,仅 Claude Code)
|
|
6
6
|
|
|
7
7
|
adspecs 的编码阶段依赖 `superpowers` 插件提供的 `subagent-driven-development`、`test-driven-development` 等通用 Skill,必须先安装:
|
|
8
8
|
|
|
@@ -22,7 +22,55 @@ adspecs 的编码阶段依赖 `superpowers` 插件提供的 `subagent-driven-dev
|
|
|
22
22
|
/plugin install superpowers@superpowers-marketplace
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
> **注意**:Qoder 平台无需安装 superpowers,adspecs 的 Skills 在 Qoder 中可独立运行。
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 在 Qoder 中安装
|
|
30
|
+
|
|
31
|
+
adspecs 通过 `.qoder-plugin/plugin.json` 提供 Qoder 原生插件支持。
|
|
32
|
+
|
|
33
|
+
### 方式 A:CLI 自动安装(推荐)
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# 全局安装 adspecs CLI
|
|
37
|
+
npm install -g adspecs
|
|
38
|
+
|
|
39
|
+
# 注册为 Qoder 插件(自动检测平台)
|
|
40
|
+
adspecs plugin install --platform qoder
|
|
41
|
+
|
|
42
|
+
# 或指定项目级安装
|
|
43
|
+
adspecs plugin install --platform qoder --scope project
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 方式 B:手动安装
|
|
47
|
+
|
|
48
|
+
1. 将 adspecs 插件目录放置到 Qoder 可访问的路径
|
|
49
|
+
2. 确保目录中包含 `.qoder-plugin/plugin.json`
|
|
50
|
+
3. 在 Qoder 插件设置中添加该目录路径
|
|
51
|
+
4. 重启 Qoder 使插件生效
|
|
52
|
+
|
|
53
|
+
### 方式 C:项目级安装(团队协作)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# 进入项目根目录
|
|
57
|
+
cd /path/to/your/project
|
|
58
|
+
|
|
59
|
+
# 注册为项目级 Qoder 插件
|
|
60
|
+
adspecs plugin install --platform qoder --scope project
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
此命令会在项目中创建 `.qoder-plugin/` 链接,使插件随项目版本控制。
|
|
64
|
+
|
|
65
|
+
### 卸载
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
adspecs plugin uninstall --platform qoder --scope project
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 在 Claude Code 中安装
|
|
26
74
|
|
|
27
75
|
adspecs 插件提供**用户级**和**项目级**两种安装方式,按需选择:
|
|
28
76
|
|
|
@@ -129,6 +177,18 @@ claude plugins update adspecs --scope project
|
|
|
129
177
|
- **团队协作,需要统一插件版本** → 选**项目级**,插件随代码版本控制
|
|
130
178
|
- **同时存在两种需求** → 两种可同时安装,项目级优先于用户级(就近原则)
|
|
131
179
|
|
|
132
|
-
> **注意**:安装完成后,建议重启 Claude Code
|
|
180
|
+
> **注意**:安装完成后,建议重启 AI 平台(Claude Code / Qoder)以确保所有 Skill 被正确加载。如遇到 Skill 未生效,可执行 `/plugins` 查看当前已加载的插件列表。
|
|
133
181
|
|
|
134
182
|
---
|
|
183
|
+
|
|
184
|
+
## 跨平台环境变量
|
|
185
|
+
|
|
186
|
+
adspecs hooks 支持通过环境变量控制平台行为:
|
|
187
|
+
|
|
188
|
+
| 环境变量 | 说明 | 可选值 |
|
|
189
|
+
|----------|------|--------|
|
|
190
|
+
| `ADSPECS_PLATFORM` | 显式指定运行平台 | `claude` / `qoder` |
|
|
191
|
+
| `CLAUDE_PLUGIN_ROOT` | Claude Code 注入的插件根路径 | 自动 |
|
|
192
|
+
| `QODER_PLUGIN_ROOT` | Qoder 注入的插件根路径(如适用) | 自动 |
|
|
193
|
+
|
|
194
|
+
通常无需手动设置,平台会自动注入。若 hooks 执行异常,可尝试设置 `ADSPECS_PLATFORM=qoder`。
|
package/bin/adspecs.js
CHANGED
|
@@ -44,12 +44,13 @@ program
|
|
|
44
44
|
// ============ plugin 命令组 ============
|
|
45
45
|
const pluginCmd = program
|
|
46
46
|
.command('plugin')
|
|
47
|
-
.description('Claude Code
|
|
47
|
+
.description('AI 平台插件管理(支持 Claude Code / Qoder)');
|
|
48
48
|
|
|
49
49
|
pluginCmd
|
|
50
50
|
.command('install')
|
|
51
|
-
.description('注册当前 adspecs 为
|
|
51
|
+
.description('注册当前 adspecs 为 AI 平台插件')
|
|
52
52
|
.option('--scope <scope>', '安装范围: user|project', 'user')
|
|
53
|
+
.option('--platform <platform>', '目标平台: claude|qoder(默认自动检测)')
|
|
53
54
|
.action(async (options) => {
|
|
54
55
|
try {
|
|
55
56
|
const pluginCmd = require('../src/commands/plugin');
|
|
@@ -63,8 +64,9 @@ pluginCmd
|
|
|
63
64
|
|
|
64
65
|
pluginCmd
|
|
65
66
|
.command('uninstall')
|
|
66
|
-
.description('从
|
|
67
|
+
.description('从 AI 平台中卸载 adspecs 插件')
|
|
67
68
|
.option('--scope <scope>', '卸载范围: user|project', 'user')
|
|
69
|
+
.option('--platform <platform>', '目标平台: claude|qoder(默认自动检测)')
|
|
68
70
|
.action(async (options) => {
|
|
69
71
|
try {
|
|
70
72
|
const pluginCmd = require('../src/commands/plugin');
|
|
@@ -114,9 +116,10 @@ program.addHelpText('after', `
|
|
|
114
116
|
$ adspecs init --mode simple # 快速初始化(交互选择其余参数)
|
|
115
117
|
$ adspecs init --non-interactive # 完全非交互(使用默认值)
|
|
116
118
|
$ adspecs init --project-name "我的项目" --mode full --front-demo antd
|
|
117
|
-
$ adspecs plugin install # 注册为
|
|
119
|
+
$ adspecs plugin install # 注册为 AI 平台插件(自动检测平台)
|
|
120
|
+
$ adspecs plugin install --platform qoder # 注册为 Qoder 插件
|
|
121
|
+
$ adspecs plugin install --platform claude # 注册为 Claude Code 插件
|
|
118
122
|
$ adspecs plugin install --scope project # 仅注册到当前项目
|
|
119
|
-
$ adspecs plugin install --scope user # 注册到当前用户(全局可用)
|
|
120
123
|
$ adspecs plugin uninstall --scope project # 从当前项目卸载
|
|
121
124
|
$ adspecs doctor # 检查环境依赖
|
|
122
125
|
$ adspecs update # 升级到最新版本
|
package/hooks/commit-queue.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* 在 SessionStart / PostToolUse / Stop 三个 Hook 事件中管理 .commit-candidates 队列文件。
|
|
7
7
|
* 当检测到 pipeline-manifest.json 写入(且 status=completed + mvn_compile=pass),
|
|
8
8
|
* 自动将 feature 信息加入提交候选队列,Stop 时提示用户执行提交。
|
|
9
|
+
* 支持 Claude Code 和 Qoder 双平台。
|
|
9
10
|
*
|
|
10
11
|
* 用法:
|
|
11
12
|
* node hooks/commit-queue.js init — SessionStart: 创建队列文件
|
|
@@ -15,13 +16,13 @@
|
|
|
15
16
|
|
|
16
17
|
const fs = require('fs');
|
|
17
18
|
const path = require('path');
|
|
19
|
+
const platform = require('./platform');
|
|
18
20
|
|
|
19
21
|
// ── 配置 ──────────────────────────────────────────────
|
|
20
|
-
//
|
|
21
|
-
// 使用 process.cwd()
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const QUEUE_DIR = path.join(PROJECT_ROOT, '.claude');
|
|
22
|
+
// 队列文件放在目标项目根目录的平台状态目录下。
|
|
23
|
+
// 使用 process.cwd() 获取目标项目根目录(平台运行 hook 时 CWD 即为目标项目)。
|
|
24
|
+
const PROJECT_ROOT = platform.getProjectRoot();
|
|
25
|
+
const QUEUE_DIR = platform.getStateDir(PROJECT_ROOT);
|
|
25
26
|
const QUEUE_FILE = path.join(QUEUE_DIR, '.commit-candidates');
|
|
26
27
|
|
|
27
28
|
// ── 工具函数 ──────────────────────────────────────────
|
|
@@ -230,8 +231,8 @@ switch (action) {
|
|
|
230
231
|
break;
|
|
231
232
|
|
|
232
233
|
case 'enqueue':
|
|
233
|
-
//
|
|
234
|
-
enqueueFromManifests(
|
|
234
|
+
// 工具输出文件由平台 Hook 运行时注入为环境变量(兼容 Claude Code / Qoder)
|
|
235
|
+
enqueueFromManifests(platform.getToolOutputFiles());
|
|
235
236
|
break;
|
|
236
237
|
|
|
237
238
|
case 'report':
|
package/hooks/hooks.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"_comment": "跨平台 Hook 配置 — 同时支持 Claude Code 和 Qoder。${CLAUDE_PLUGIN_ROOT} 由平台在加载插件时注入(Qoder 兼容此变量或设置 ADSPECS_PLATFORM=qoder)。",
|
|
2
3
|
"hooks": {
|
|
3
4
|
"SessionStart": [
|
|
4
5
|
{
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
"PreToolUse": [],
|
|
36
37
|
"PostToolUse": [
|
|
37
38
|
{
|
|
38
|
-
"matcher": "Write|Edit",
|
|
39
|
+
"matcher": "Write|Edit|SearchReplace",
|
|
39
40
|
"hooks": [
|
|
40
41
|
{
|
|
41
42
|
"type": "command",
|
|
@@ -43,11 +44,11 @@
|
|
|
43
44
|
}
|
|
44
45
|
],
|
|
45
46
|
"conditions": {
|
|
46
|
-
"filePattern": "\\.(java|py|ts|js|go|rs|cs|md)$"
|
|
47
|
+
"filePattern": "\\.(java|py|ts|js|go|rs|cs|md|vue|tsx|jsx)$"
|
|
47
48
|
}
|
|
48
49
|
},
|
|
49
50
|
{
|
|
50
|
-
"matcher": "Write",
|
|
51
|
+
"matcher": "Write|SearchReplace",
|
|
51
52
|
"hooks": [
|
|
52
53
|
{
|
|
53
54
|
"type": "command",
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Platform Detection — 跨 AI 平台兼容工具模块
|
|
5
|
+
*
|
|
6
|
+
* 为 hooks 脚本提供统一的平台检测、路径解析和输出协议,
|
|
7
|
+
* 同时支持 Claude Code 和 Qoder 两种 AI 编码平台。
|
|
8
|
+
*
|
|
9
|
+
* 检测优先级:
|
|
10
|
+
* 1. 环境变量 ADSPECS_PLATFORM 显式指定('claude' | 'qoder')
|
|
11
|
+
* 2. Qoder 特征环境变量(QODER_PLUGIN_ROOT 等)
|
|
12
|
+
* 3. Claude Code 特征环境变量(CLAUDE_PLUGIN_ROOT 等)
|
|
13
|
+
* 4. 默认回退到 claude(向后兼容)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
// ── 平台检测 ──────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 检测当前运行的 AI 平台
|
|
22
|
+
* @returns {'claude'|'qoder'}
|
|
23
|
+
*/
|
|
24
|
+
function detectPlatform() {
|
|
25
|
+
// 1. 显式指定
|
|
26
|
+
var explicit = (process.env.ADSPECS_PLATFORM || '').toLowerCase();
|
|
27
|
+
if (explicit === 'qoder' || explicit === 'claude') {
|
|
28
|
+
return explicit;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 2. Qoder 特征
|
|
32
|
+
if (process.env.QODER_PLUGIN_ROOT || process.env.QODER_SESSION_ID) {
|
|
33
|
+
return 'qoder';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 3. Claude Code 特征
|
|
37
|
+
if (process.env.CLAUDE_PLUGIN_ROOT || process.env.CLAUDE_SESSION_ID) {
|
|
38
|
+
return 'claude';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 4. 默认回退
|
|
42
|
+
return 'claude';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 当前平台(模块加载时确定) */
|
|
46
|
+
var PLATFORM = detectPlatform();
|
|
47
|
+
|
|
48
|
+
// ── 路径解析 ──────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 获取插件根目录
|
|
52
|
+
* 优先使用平台环境变量,回退到 __dirname 相对路径推导
|
|
53
|
+
*
|
|
54
|
+
* @param {string} hooksDir - 调用方的 __dirname(即 hooks/ 目录绝对路径)
|
|
55
|
+
* @returns {string} 插件根目录绝对路径
|
|
56
|
+
*/
|
|
57
|
+
function getPluginRoot(hooksDir) {
|
|
58
|
+
// 平台环境变量优先
|
|
59
|
+
var envRoot = process.env.CLAUDE_PLUGIN_ROOT || process.env.QODER_PLUGIN_ROOT;
|
|
60
|
+
if (envRoot) {
|
|
61
|
+
return path.resolve(envRoot);
|
|
62
|
+
}
|
|
63
|
+
// 回退:hooks/ 的上级目录即插件根
|
|
64
|
+
return path.resolve(hooksDir, '..');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 获取目标项目根目录
|
|
69
|
+
* 两种平台均使用 process.cwd()(运行时 CWD 即为目标项目)
|
|
70
|
+
*
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
function getProjectRoot() {
|
|
74
|
+
return process.cwd();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 获取项目内的插件状态目录(用于存放队列文件、marker 等)
|
|
79
|
+
* - Claude Code: {project}/.claude/
|
|
80
|
+
* - Qoder: {project}/.qoder/(若不存在则回退 .claude/ 保持兼容)
|
|
81
|
+
*
|
|
82
|
+
* @param {string} projectRoot - 项目根目录
|
|
83
|
+
* @returns {string}
|
|
84
|
+
*/
|
|
85
|
+
function getStateDir(projectRoot) {
|
|
86
|
+
if (PLATFORM === 'qoder') {
|
|
87
|
+
return path.join(projectRoot, '.qoder');
|
|
88
|
+
}
|
|
89
|
+
return path.join(projectRoot, '.claude');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── 环境变量适配 ──────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 获取工具输出文件列表(PostToolUse 时由平台注入)
|
|
96
|
+
* - Claude Code: CLAUDE_TOOL_OUTPUT_FILES
|
|
97
|
+
* - Qoder: QODER_TOOL_OUTPUT_FILES 或 CLAUDE_TOOL_OUTPUT_FILES(兼容)
|
|
98
|
+
*
|
|
99
|
+
* @returns {string} 换行分隔的文件路径列表
|
|
100
|
+
*/
|
|
101
|
+
function getToolOutputFiles() {
|
|
102
|
+
return (
|
|
103
|
+
process.env.QODER_TOOL_OUTPUT_FILES ||
|
|
104
|
+
process.env.CLAUDE_TOOL_OUTPUT_FILES ||
|
|
105
|
+
''
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── 输出协议 ──────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 输出 Hook 上下文到 stdout(供 AI 平台读取注入 Agent 上下文)
|
|
113
|
+
*
|
|
114
|
+
* 两种平台当前均使用相同的 JSON stdout 协议:
|
|
115
|
+
* { "hookSpecificOutput": { "hookEventName": "...", "additionalContext": "..." } }
|
|
116
|
+
*
|
|
117
|
+
* @param {string} eventName - Hook 事件名称
|
|
118
|
+
* @param {string} contextText - 要注入的上下文文本
|
|
119
|
+
*/
|
|
120
|
+
function outputContext(eventName, contextText) {
|
|
121
|
+
var output = {
|
|
122
|
+
hookSpecificOutput: {
|
|
123
|
+
hookEventName: eventName,
|
|
124
|
+
additionalContext: contextText,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
process.stdout.write(JSON.stringify(output));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── 导出 ──────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
/** 当前检测到的平台: 'claude' | 'qoder' */
|
|
134
|
+
platform: PLATFORM,
|
|
135
|
+
detectPlatform: detectPlatform,
|
|
136
|
+
getPluginRoot: getPluginRoot,
|
|
137
|
+
getProjectRoot: getProjectRoot,
|
|
138
|
+
getStateDir: getStateDir,
|
|
139
|
+
getToolOutputFiles: getToolOutputFiles,
|
|
140
|
+
outputContext: outputContext,
|
|
141
|
+
};
|
package/hooks/session-logger.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Session Logger — AI 会话日志自动保存 Hook
|
|
5
5
|
*
|
|
6
6
|
* 在 SessionStart / PreCompact / Stop 三个事件中管理会话保存状态,
|
|
7
|
-
* 通过 additionalContext 引导
|
|
7
|
+
* 通过 additionalContext 引导 AI Agent 自行生成并写入结构化日志文件。
|
|
8
|
+
* 支持 Claude Code 和 Qoder 双平台。
|
|
8
9
|
*
|
|
9
10
|
* 用法:
|
|
10
11
|
* node hooks/session-logger.js init — SessionStart: 检测恢复 / 注入引导
|
|
@@ -14,12 +15,13 @@
|
|
|
14
15
|
|
|
15
16
|
const fs = require('fs');
|
|
16
17
|
const path = require('path');
|
|
18
|
+
const platform = require('./platform');
|
|
17
19
|
|
|
18
20
|
// ── 路径解析 ──────────────────────────────────────────────
|
|
19
21
|
// 使用 process.cwd() 定位目标项目(不用 __dirname,因为 __dirname 指向插件目录)
|
|
20
|
-
const PROJECT_ROOT =
|
|
21
|
-
const
|
|
22
|
-
const MARKER_FILE = path.join(
|
|
22
|
+
const PROJECT_ROOT = platform.getProjectRoot();
|
|
23
|
+
const STATE_DIR = platform.getStateDir(PROJECT_ROOT);
|
|
24
|
+
const MARKER_FILE = path.join(STATE_DIR, '.session-save-marker');
|
|
23
25
|
const CONFIG_FILE = path.join(PROJECT_ROOT, '.adspecs', 'session-logger.json');
|
|
24
26
|
const PATHS_FILE = path.join(PROJECT_ROOT, '.adspecs', 'paths.json');
|
|
25
27
|
const FEATURE_FILE = path.join(PROJECT_ROOT, '.adspecs', 'feature.json');
|
|
@@ -112,7 +114,7 @@ function readMarker() {
|
|
|
112
114
|
|
|
113
115
|
/** 写入 marker 文件(覆盖写,单行最新态) */
|
|
114
116
|
function writeMarker(data) {
|
|
115
|
-
fs.mkdirSync(
|
|
117
|
+
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
116
118
|
fs.writeFileSync(MARKER_FILE, JSON.stringify(data) + '\n', 'utf8');
|
|
117
119
|
}
|
|
118
120
|
|
|
@@ -135,15 +137,9 @@ function formatTimestamp(date) {
|
|
|
135
137
|
return yyyy + MM + dd + '-' + HH + mm + ss;
|
|
136
138
|
}
|
|
137
139
|
|
|
138
|
-
/** 输出 additionalContext 到 stdout(供
|
|
140
|
+
/** 输出 additionalContext 到 stdout(供 AI 平台读取,使用平台统一协议) */
|
|
139
141
|
function outputContext(eventName, contextText) {
|
|
140
|
-
|
|
141
|
-
hookSpecificOutput: {
|
|
142
|
-
hookEventName: eventName,
|
|
143
|
-
additionalContext: contextText,
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
process.stdout.write(JSON.stringify(output));
|
|
142
|
+
platform.outputContext(eventName, contextText);
|
|
147
143
|
}
|
|
148
144
|
|
|
149
145
|
/** 计算两个 ISO 时间戳之间的分钟数 */
|
package/hooks/session-start.js
CHANGED
|
@@ -4,18 +4,19 @@
|
|
|
4
4
|
* SessionStart Hook — 注入 using-adspecs 技能引导内容
|
|
5
5
|
*
|
|
6
6
|
* 在每次会话启动时,读取 using-adspecs/SKILL.md 并输出为
|
|
7
|
-
*
|
|
7
|
+
* hook additionalContext,确保 Agent 在任何任务开始前
|
|
8
8
|
* 都知道需要检查并使用 ADSpecs 技能。
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* 跨平台兼容:Claude Code / Qoder
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
const path = require('path');
|
|
15
|
+
const platform = require('./platform');
|
|
15
16
|
|
|
16
17
|
// ── 路径解析 ──────────────────────────────────────────
|
|
17
|
-
//
|
|
18
|
-
const PLUGIN_ROOT =
|
|
18
|
+
// 使用平台工具解析插件根目录(兼容环境变量和 __dirname 回退)
|
|
19
|
+
const PLUGIN_ROOT = platform.getPluginRoot(__dirname);
|
|
19
20
|
const SKILL_PATH = path.join(PLUGIN_ROOT, 'skills', 'using-adspecs', 'SKILL.md');
|
|
20
21
|
|
|
21
22
|
// ── 读取技能内容 ──────────────────────────────────────
|
|
@@ -32,13 +33,8 @@ if (!skillContent || skillContent.trim() === '') {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
// ── 输出 Hook Context ─────────────────────────────────
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
additionalContext: '<EXTREMELY-IMPORTANT>\n' + skillContent + '\n</EXTREMELY-IMPORTANT>'
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
process.stdout.write(JSON.stringify(output));
|
|
36
|
+
// 使用平台统一输出协议(hookSpecificOutput.additionalContext)
|
|
37
|
+
platform.outputContext(
|
|
38
|
+
'SessionStart',
|
|
39
|
+
'<EXTREMELY-IMPORTANT>\n' + skillContent + '\n</EXTREMELY-IMPORTANT>'
|
|
40
|
+
);
|
package/hooks/wiki-queue.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 在 SessionStart / PostToolUse / Stop 三个 Hook 事件中管理 .wiki-update-queue 队列文件。
|
|
7
7
|
* 使用 Node.js(而非平台特定 Shell)确保 Windows / macOS / Linux 三平台兼容。
|
|
8
|
+
* 支持 Claude Code 和 Qoder 双平台。
|
|
8
9
|
*
|
|
9
10
|
* 用法:
|
|
10
11
|
* node hooks/wiki-queue.js init — SessionStart: 创建队列文件
|
|
@@ -14,13 +15,13 @@
|
|
|
14
15
|
|
|
15
16
|
const fs = require('fs');
|
|
16
17
|
const path = require('path');
|
|
18
|
+
const platform = require('./platform');
|
|
17
19
|
|
|
18
20
|
// ── 配置 ──────────────────────────────────────────────
|
|
19
|
-
//
|
|
20
|
-
// 使用 process.cwd()
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
const QUEUE_DIR = path.join(PROJECT_ROOT, '.claude');
|
|
21
|
+
// 队列文件放在目标项目根目录的平台状态目录下。
|
|
22
|
+
// 使用 process.cwd() 获取目标项目根目录(平台运行 hook 时 CWD 即为目标项目)。
|
|
23
|
+
const PROJECT_ROOT = platform.getProjectRoot();
|
|
24
|
+
const QUEUE_DIR = platform.getStateDir(PROJECT_ROOT);
|
|
24
25
|
const QUEUE_FILE = path.join(QUEUE_DIR, '.wiki-update-queue');
|
|
25
26
|
|
|
26
27
|
// ── 工具函数 ──────────────────────────────────────────
|
|
@@ -112,8 +113,8 @@ switch (action) {
|
|
|
112
113
|
break;
|
|
113
114
|
|
|
114
115
|
case 'enqueue':
|
|
115
|
-
//
|
|
116
|
-
appendToQueue(
|
|
116
|
+
// 工具输出文件由平台 Hook 运行时注入为环境变量(兼容 Claude Code / Qoder)
|
|
117
|
+
appendToQueue(platform.getToolOutputFiles());
|
|
117
118
|
break;
|
|
118
119
|
|
|
119
120
|
case 'report':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adspecs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "AI first 工程规范驱动开发插件 — 支持 npm 安装和 CLI 初始化",
|
|
5
5
|
"bin": {
|
|
6
6
|
"adspecs": "bin/adspecs.js"
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"keywords": [
|
|
43
43
|
"adspecs",
|
|
44
44
|
"claude-code",
|
|
45
|
+
"qoder",
|
|
45
46
|
"plugin",
|
|
46
47
|
"scaffold",
|
|
47
48
|
"cli",
|
package/scripts/postinstall.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* npm 安装/更新后自动刷新
|
|
5
|
+
* npm 安装/更新后自动刷新 AI 平台插件注册
|
|
6
6
|
*
|
|
7
7
|
* 触发时机:
|
|
8
8
|
* - `npm install -g adspecs` → 首次安装
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* - `npm install adspecs` (本地) → 作为项目依赖安装
|
|
11
11
|
*
|
|
12
12
|
* 行为:
|
|
13
|
-
* - 检查
|
|
14
|
-
* - 检查 adspecs
|
|
15
|
-
* - 若已注册 →
|
|
13
|
+
* - 检查 AI 平台 CLI 是否可用(Claude Code / Qoder)
|
|
14
|
+
* - 检查 adspecs 插件是否已在平台中注册
|
|
15
|
+
* - 若已注册 → 静默重新安装以刷新版本
|
|
16
16
|
* - 若未注册 → 不做任何事(首次安装应由用户主动 `adspecs plugin install`)
|
|
17
17
|
*
|
|
18
18
|
* 关键约束:
|
|
@@ -33,17 +33,22 @@ const timer = setTimeout(() => {
|
|
|
33
33
|
if (timer.unref) timer.unref();
|
|
34
34
|
|
|
35
35
|
try {
|
|
36
|
-
// 1.
|
|
36
|
+
// 1. 检测可用的 AI 平台
|
|
37
|
+
var hasClaudeCli = false;
|
|
37
38
|
try {
|
|
38
39
|
execSync('claude --version', { stdio: 'ignore', timeout: 1000 });
|
|
40
|
+
hasClaudeCli = true;
|
|
39
41
|
} catch {
|
|
40
|
-
// claude CLI 未安装
|
|
42
|
+
// claude CLI 未安装
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 如果没有任何平台 CLI 可用,静默退出
|
|
46
|
+
// Qoder 作为 IDE 插件不需要 CLI 刷新(通过 .qoder-plugin/ 自动发现)
|
|
47
|
+
if (!hasClaudeCli) {
|
|
41
48
|
process.exit(0);
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
// 2. 判断是否是全局安装
|
|
45
|
-
// npm 在全局安装时会设置 npm_config_global 或 initInstalled 等环境变量
|
|
46
|
-
// 也可通过检查安装路径是否包含 node_modules/adspecs 判断
|
|
47
52
|
const installDir = path.resolve(__dirname, '..');
|
|
48
53
|
const isGlobal =
|
|
49
54
|
process.env.npm_config_global === 'true' ||
|
|
@@ -75,8 +80,6 @@ try {
|
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
// 4. 已注册 → 重新安装以刷新版本
|
|
78
|
-
// 使用异步 spawn 避免阻塞 npm install 流程
|
|
79
|
-
const pluginDir = installDir;
|
|
80
83
|
const child = spawn(
|
|
81
84
|
'claude',
|
|
82
85
|
['plugins', 'install', 'adspecs', '--scope', 'user'],
|
|
@@ -97,7 +100,7 @@ try {
|
|
|
97
100
|
fs.readFileSync(path.join(installDir, 'package.json'), 'utf8')
|
|
98
101
|
);
|
|
99
102
|
console.log(
|
|
100
|
-
`\n🔄 adspecs@${pkg.version} —
|
|
103
|
+
`\n🔄 adspecs@${pkg.version} — AI 平台插件注册已自动刷新\n`
|
|
101
104
|
);
|
|
102
105
|
}
|
|
103
106
|
} catch {
|
package/src/commands/plugin.js
CHANGED
|
@@ -25,42 +25,151 @@ function checkClaudeCli() {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* 读取 plugin.json
|
|
28
|
+
* 读取 plugin.json 获取插件名(根据平台选择对应清单)
|
|
29
|
+
* @param {string} platform - 'claude' | 'qoder'
|
|
29
30
|
*/
|
|
30
|
-
function getPluginName() {
|
|
31
|
-
|
|
31
|
+
function getPluginName(platform) {
|
|
32
|
+
var manifestDir = platform === 'qoder' ? '.qoder-plugin' : '.claude-plugin';
|
|
33
|
+
const pluginJsonPath = path.join(PLUGIN_DIR, manifestDir, 'plugin.json');
|
|
32
34
|
if (!fs.existsSync(pluginJsonPath)) {
|
|
33
|
-
|
|
35
|
+
// 回退到 .claude-plugin
|
|
36
|
+
const fallbackPath = path.join(PLUGIN_DIR, '.claude-plugin', 'plugin.json');
|
|
37
|
+
if (!fs.existsSync(fallbackPath)) {
|
|
38
|
+
throw new Error(`未找到插件清单文件: ${pluginJsonPath}`);
|
|
39
|
+
}
|
|
40
|
+
const pluginJson = JSON.parse(fs.readFileSync(fallbackPath, 'utf8'));
|
|
41
|
+
return pluginJson.name || 'adspecs';
|
|
34
42
|
}
|
|
35
43
|
const pluginJson = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf8'));
|
|
36
44
|
return pluginJson.name || 'adspecs';
|
|
37
45
|
}
|
|
38
46
|
|
|
47
|
+
/**
|
|
48
|
+
* 检测当前可用的 AI 平台
|
|
49
|
+
* @returns {'claude'|'qoder'}
|
|
50
|
+
*/
|
|
51
|
+
function detectInstalledPlatform() {
|
|
52
|
+
// 显式环境变量优先
|
|
53
|
+
var explicit = (process.env.ADSPECS_PLATFORM || '').toLowerCase();
|
|
54
|
+
if (explicit === 'qoder' || explicit === 'claude') {
|
|
55
|
+
return explicit;
|
|
56
|
+
}
|
|
57
|
+
// 检测 claude CLI
|
|
58
|
+
if (checkClaudeCli()) {
|
|
59
|
+
return 'claude';
|
|
60
|
+
}
|
|
61
|
+
// 默认回退 qoder(当前运行环境)
|
|
62
|
+
return 'qoder';
|
|
63
|
+
}
|
|
64
|
+
|
|
39
65
|
/**
|
|
40
66
|
* 安装(注册)插件到 Claude Code
|
|
41
67
|
*
|
|
42
|
-
* Claude Code 的 marketplace 命令期望路径指向包含 `.claude-plugin/marketplace.json`
|
|
43
|
-
* 的目录,因此传入 PLUGIN_DIR(而非 PLUGIN_DIR/.claude-plugin)。
|
|
44
|
-
*
|
|
45
68
|
* @param {object} options
|
|
46
69
|
* @param {string} options.scope - 安装范围: user|project
|
|
70
|
+
* @param {string} options.platform - 目标平台: claude|qoder(可选,自动检测)
|
|
47
71
|
*/
|
|
48
72
|
exports.install = async function install(options) {
|
|
49
73
|
const scope = options.scope || 'user';
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
// marketplace.json 必须存在,否则无法注册
|
|
53
|
-
const marketplaceJson = path.join(PLUGIN_DIR, '.claude-plugin', 'marketplace.json');
|
|
54
|
-
if (!fs.existsSync(marketplaceJson)) {
|
|
55
|
-
throw new Error(`未找到 marketplace 清单: ${marketplaceJson}`);
|
|
56
|
-
}
|
|
74
|
+
const platform = options.platform || detectInstalledPlatform();
|
|
75
|
+
const pluginName = getPluginName(platform);
|
|
57
76
|
|
|
58
77
|
console.log(chalk.cyan('🔌 adspecs 插件注册'));
|
|
59
78
|
console.log('');
|
|
60
79
|
console.log(` 插件目录: ${PLUGIN_DIR}`);
|
|
61
80
|
console.log(` 插件名称: ${pluginName}`);
|
|
62
81
|
console.log(` 安装范围: ${scope}`);
|
|
82
|
+
console.log(` 目标平台: ${platform}`);
|
|
83
|
+
console.log('');
|
|
84
|
+
|
|
85
|
+
if (platform === 'qoder') {
|
|
86
|
+
await installForQoder(pluginName, scope);
|
|
87
|
+
} else {
|
|
88
|
+
await installForClaude(pluginName, scope);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Qoder 平台安装
|
|
94
|
+
* Qoder 通过 .qoder-plugin/plugin.json 自动发现插件,
|
|
95
|
+
* 安装即确保插件目录可被 Qoder 访问。
|
|
96
|
+
*/
|
|
97
|
+
async function installForQoder(pluginName, scope) {
|
|
98
|
+
const qoderManifest = path.join(PLUGIN_DIR, '.qoder-plugin', 'plugin.json');
|
|
99
|
+
if (!fs.existsSync(qoderManifest)) {
|
|
100
|
+
throw new Error(`未找到 Qoder 插件清单: ${qoderManifest}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (scope === 'project') {
|
|
104
|
+
// 项目级:在当前项目创建 .qoder-plugin 符号链接或复制
|
|
105
|
+
const targetDir = path.join(process.cwd(), '.qoder-plugin');
|
|
106
|
+
if (fs.existsSync(targetDir)) {
|
|
107
|
+
console.log(chalk.yellow(' ⏭ 当前项目已存在 .qoder-plugin/,跳过'));
|
|
108
|
+
} else {
|
|
109
|
+
try {
|
|
110
|
+
// 尝试创建符号链接(节省空间)
|
|
111
|
+
fs.symlinkSync(
|
|
112
|
+
path.join(PLUGIN_DIR, '.qoder-plugin'),
|
|
113
|
+
targetDir,
|
|
114
|
+
'junction' // Windows 使用 junction
|
|
115
|
+
);
|
|
116
|
+
console.log(chalk.green(' ✅ 已创建 .qoder-plugin 符号链接'));
|
|
117
|
+
} catch (_err) {
|
|
118
|
+
// 符号链接失败,复制清单文件
|
|
119
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
120
|
+
fs.copyFileSync(qoderManifest, path.join(targetDir, 'plugin.json'));
|
|
121
|
+
console.log(chalk.green(' ✅ 已复制 .qoder-plugin/plugin.json 到项目'));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 确保 skills 可被访问
|
|
126
|
+
const targetSkills = path.join(process.cwd(), 'skills');
|
|
127
|
+
if (!fs.existsSync(targetSkills)) {
|
|
128
|
+
try {
|
|
129
|
+
fs.symlinkSync(path.join(PLUGIN_DIR, 'skills'), targetSkills, 'junction');
|
|
130
|
+
console.log(chalk.green(' ✅ 已创建 skills/ 符号链接'));
|
|
131
|
+
} catch (_err) {
|
|
132
|
+
console.log(chalk.yellow(' ⚠ skills/ 链接创建失败,请手动确保 Qoder 可访问插件 skills 目录'));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 确保 hooks 可被访问
|
|
137
|
+
const targetHooks = path.join(process.cwd(), 'hooks');
|
|
138
|
+
if (!fs.existsSync(targetHooks)) {
|
|
139
|
+
try {
|
|
140
|
+
fs.symlinkSync(path.join(PLUGIN_DIR, 'hooks'), targetHooks, 'junction');
|
|
141
|
+
console.log(chalk.green(' ✅ 已创建 hooks/ 符号链接'));
|
|
142
|
+
} catch (_err) {
|
|
143
|
+
console.log(chalk.yellow(' ⚠ hooks/ 链接创建失败,请手动确保 Qoder 可访问插件 hooks 目录'));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
// 用户级:输出指引
|
|
148
|
+
console.log(chalk.gray(' → Qoder 用户级插件安装...'));
|
|
149
|
+
console.log('');
|
|
150
|
+
console.log(chalk.green(' ✅ Qoder 插件清单已就绪: ' + qoderManifest));
|
|
151
|
+
console.log('');
|
|
152
|
+
console.log(' Qoder 用户级安装方式:');
|
|
153
|
+
console.log(' 1. 将本插件目录路径添加到 Qoder 插件搜索路径');
|
|
154
|
+
console.log(' 2. 或在 Qoder 设置中配置插件目录指向: ' + PLUGIN_DIR);
|
|
155
|
+
console.log(' 3. 重启 Qoder 使插件生效');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log('');
|
|
159
|
+
console.log(chalk.green.bold(`✅ adspecs 插件已配置到 Qoder (scope=${scope})`));
|
|
63
160
|
console.log('');
|
|
161
|
+
printSkillList();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Claude Code 平台安装(原有逻辑)
|
|
166
|
+
*/
|
|
167
|
+
async function installForClaude(pluginName, scope) {
|
|
168
|
+
// marketplace.json 必须存在,否则无法注册
|
|
169
|
+
const marketplaceJson = path.join(PLUGIN_DIR, '.claude-plugin', 'marketplace.json');
|
|
170
|
+
if (!fs.existsSync(marketplaceJson)) {
|
|
171
|
+
throw new Error(`未找到 marketplace 清单: ${marketplaceJson}`);
|
|
172
|
+
}
|
|
64
173
|
|
|
65
174
|
// 1. 检查 claude CLI
|
|
66
175
|
if (!checkClaudeCli()) {
|
|
@@ -74,7 +183,6 @@ exports.install = async function install(options) {
|
|
|
74
183
|
}
|
|
75
184
|
|
|
76
185
|
// 2. 注册 marketplace
|
|
77
|
-
// 注意:传入 PLUGIN_DIR,claude 会去找 <PLUGIN_DIR>/.claude-plugin/marketplace.json
|
|
78
186
|
console.log(chalk.gray(' → 注册 marketplace...'));
|
|
79
187
|
let marketplaceRegistered = false;
|
|
80
188
|
try {
|
|
@@ -90,15 +198,13 @@ exports.install = async function install(options) {
|
|
|
90
198
|
const combined = stderr + stdout;
|
|
91
199
|
if (combined.includes('already exists') || combined.includes('already registered')) {
|
|
92
200
|
console.log(chalk.yellow(' ⏭ marketplace 已存在,跳过'));
|
|
93
|
-
marketplaceRegistered = true;
|
|
201
|
+
marketplaceRegistered = true;
|
|
94
202
|
} else {
|
|
95
|
-
// 把真实错误输出,方便排查,不再静默吞掉
|
|
96
203
|
console.log(chalk.red(` ❌ marketplace 注册失败: ${combined.trim() || err.message}`));
|
|
97
204
|
}
|
|
98
205
|
}
|
|
99
206
|
|
|
100
207
|
// 3. 安装插件
|
|
101
|
-
// 只有 marketplace 注册成功(或已存在)时,才能按名称安装
|
|
102
208
|
console.log(chalk.gray(' → 安装插件...'));
|
|
103
209
|
if (!marketplaceRegistered) {
|
|
104
210
|
throw new Error('marketplace 未注册成功,无法按名称安装插件,请检查上方错误');
|
|
@@ -122,28 +228,62 @@ exports.install = async function install(options) {
|
|
|
122
228
|
console.log('');
|
|
123
229
|
console.log(chalk.green.bold(`✅ adspecs 插件已注册到 Claude Code (scope=${scope})`));
|
|
124
230
|
console.log('');
|
|
125
|
-
|
|
231
|
+
printSkillList();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 打印可用技能列表
|
|
236
|
+
*/
|
|
237
|
+
function printSkillList() {
|
|
238
|
+
console.log(' 现在可以使用以下技能:');
|
|
126
239
|
console.log(' /adspecs-prd — 生成 PRD');
|
|
127
240
|
console.log(' /adspecs-plan — 后端系统设计');
|
|
128
241
|
console.log(' /adspecs-tasks — TDD 任务清单');
|
|
129
242
|
console.log(' /adspecs-utest — 生成单元测试');
|
|
130
|
-
console.log(' ...(共
|
|
243
|
+
console.log(' ...(共 30 个技能)');
|
|
131
244
|
console.log('');
|
|
132
|
-
}
|
|
245
|
+
}
|
|
133
246
|
|
|
134
247
|
/**
|
|
135
248
|
* 卸载插件
|
|
136
249
|
*
|
|
137
250
|
* @param {object} options
|
|
138
251
|
* @param {string} options.scope - 卸载范围: user|project
|
|
252
|
+
* @param {string} options.platform - 目标平台: claude|qoder(可选,自动检测)
|
|
139
253
|
*/
|
|
140
254
|
exports.uninstall = async function uninstall(options) {
|
|
141
255
|
const scope = options.scope || 'user';
|
|
142
|
-
const
|
|
256
|
+
const platform = options.platform || detectInstalledPlatform();
|
|
257
|
+
const pluginName = getPluginName(platform);
|
|
143
258
|
|
|
144
259
|
console.log(chalk.cyan('🔌 adspecs 插件卸载'));
|
|
145
260
|
console.log('');
|
|
261
|
+
console.log(` 目标平台: ${platform}`);
|
|
262
|
+
console.log('');
|
|
263
|
+
|
|
264
|
+
if (platform === 'qoder') {
|
|
265
|
+
// Qoder 卸载:移除项目级链接
|
|
266
|
+
if (scope === 'project') {
|
|
267
|
+
const targetDir = path.join(process.cwd(), '.qoder-plugin');
|
|
268
|
+
if (fs.existsSync(targetDir)) {
|
|
269
|
+
try {
|
|
270
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
271
|
+
console.log(chalk.green(' ✅ 已移除项目 .qoder-plugin/'));
|
|
272
|
+
} catch (err) {
|
|
273
|
+
throw new Error(`移除失败: ${err.message}`);
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
console.log(chalk.yellow(' ⏭ 项目未安装 .qoder-plugin/,无需卸载'));
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
console.log(chalk.yellow(' 用户级卸载请从 Qoder 插件设置中移除本插件路径'));
|
|
280
|
+
}
|
|
281
|
+
console.log('');
|
|
282
|
+
console.log(chalk.green(`✅ ${pluginName} 已从 Qoder 卸载 (scope=${scope})`));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
146
285
|
|
|
286
|
+
// Claude Code 卸载
|
|
147
287
|
if (!checkClaudeCli()) {
|
|
148
288
|
console.error(chalk.red('❌ 未检测到 claude CLI'));
|
|
149
289
|
process.exit(1);
|