adspecs 0.1.27 → 0.1.29

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.
@@ -10,7 +10,7 @@
10
10
  {
11
11
  "name": "adspecs",
12
12
  "description": "AI Coding研发协同开发插件",
13
- "version": "0.1.27",
13
+ "version": "0.1.29",
14
14
  "source": "./",
15
15
  "author": {
16
16
  "name": "Qingwen Chen",
@@ -2,7 +2,7 @@
2
2
  "name": "adspecs",
3
3
  "displayName": "adspecs AI Plugin",
4
4
  "description": "AI first工程规范驱动开发插件",
5
- "version": "0.1.27",
5
+ "version": "0.1.29",
6
6
  "author": {
7
7
  "name": "Qingwen Chen",
8
8
  "email": "cqinwn@qq.com",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "adspecs",
3
3
  "displayName": "adspecs AI 工程规范驱动开发插件",
4
- "version": "0.1.27",
4
+ "version": "0.1.29",
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-saeplugin/SKILL.md` as agent context (no-op if skill doesn't exist) |
104
- | `SessionStart` | `wiki-queue.js init` | Initialize wiki update queue file in `.claude/` |
105
- | `SessionStart` | `commit-queue.js init` | Initialize commit candidates queue file in `.claude/` |
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 Claude to write log |
108
- | `PostToolUse` (Write/Edit on code/doc files) | `wiki-queue.js enqueue` | Track changed files for wiki sync |
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
- Hook scripts use `__dirname` for path resolution (not CWD). Queue files stored in `.claude/` (`.commit-candidates`, `.wiki-update-queue`, `.session-save-marker`).
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
- ### 2. 安装 AdSpecs 插件
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,45 @@ claude plugins update adspecs --scope project
129
177
  - **团队协作,需要统一插件版本** → 选**项目级**,插件随代码版本控制
130
178
  - **同时存在两种需求** → 两种可同时安装,项目级优先于用户级(就近原则)
131
179
 
132
- > **注意**:安装完成后,建议重启 Claude Code 以确保所有 Skill 被正确加载。如遇到 Skill 未生效,可执行 `/plugins` 查看当前已加载的插件列表。
180
+ > **注意**:安装完成后,建议重启 AI 平台(Claude Code / Qoder)以确保所有 Skill 被正确加载。如遇到 Skill 未生效,可执行 `/plugins` 查看当前已加载的插件列表。
133
181
 
134
182
  ---
183
+
184
+ ## 跨平台支持
185
+
186
+ ### 平台自动检测
187
+
188
+ adspecs 通过 `hooks/platform.js` 模块自动检测运行环境,无需手动配置:
189
+
190
+ | 检测方式 | 说明 |
191
+ |----------|------|
192
+ | `ADSPECS_PLATFORM` 环境变量 | 显式指定平台(优先级最高) |
193
+ | `CLAUDE_PLUGIN_ROOT` 环境变量 | Claude Code 自动注入 |
194
+ | `QODER_PLUGIN_ROOT` 环境变量 | Qoder 自动注入 |
195
+
196
+ ### 环境变量
197
+
198
+ | 环境变量 | 说明 | 可选值 |
199
+ |----------|------|--------|
200
+ | `ADSPECS_PLATFORM` | 显式指定运行平台 | `claude` / `qoder` |
201
+ | `CLAUDE_PLUGIN_ROOT` | Claude Code 注入的插件根路径 | 自动 |
202
+ | `QODER_PLUGIN_ROOT` | Qoder 注入的插件根路径 | 自动 |
203
+
204
+ 通常无需手动设置,平台会自动注入。若 hooks 执行异常,可尝试设置 `ADSPECS_PLATFORM=qoder`。
205
+
206
+ ### Hooks 系统
207
+
208
+ adspecs 的 hooks 脚本(`hooks/*.js`)已适配双平台,通过统一的 `platform.js` 模块获取:
209
+
210
+ - 平台类型(`claude` / `qoder`)
211
+ - 状态目录(`.claude/` / `.qoder/`)
212
+ - 工具输出文件路径
213
+
214
+ hooks 配置位于 `hooks/hooks.json`,支持以下事件:
215
+
216
+ | 事件 | 说明 |
217
+ |------|------|
218
+ | `SessionStart` | 会话启动时注入上下文 |
219
+ | `PreCompact` | 上下文压缩前保存提醒 |
220
+ | `PostToolUse` | 文件变更后追踪队列 |
221
+ | `Stop` | 会话结束时建议操作 |
package/README.md CHANGED
@@ -2,20 +2,24 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/adspecs)](https://www.npmjs.com/package/adspecs)
4
4
  [![npm downloads](https://img.shields.io/npm/dt/adspecs)](https://www.npmjs.com/package/adspecs)
5
+ [![Platform](https://img.shields.io/badge/platform-Claude%20Code%20%7C%20Qoder-blueviolet)](#安装)
5
6
 
6
7
  ---
7
8
 
8
9
  ## 简介
9
10
 
10
- AdSpecs 是一个基于 Claude Code AI 工程研发协同插件,提供:
11
+ AdSpecs 是一个支持 **Claude Code** **Qoder** 双平台的 AI 工程研发协同插件,提供:
11
12
 
12
13
  - **21 个 Skill**(Slash 命令) — 覆盖需求分析、架构规划、系统设计、任务拆解、单元测试、代码审查、文档导出、Wiki 同步
13
14
  - **CLI 工具** — `adspecs init` 一键脚手架、`adspecs plugin install` 插件注册、`adspecs doctor` 环境检查
14
15
  - **21 个标准文档模板** — 需求 / 设计 / 任务 / 测试 / 评审 / 架构全覆盖
15
- - **多技术栈参考** — ECP Java 后端、Vue 3 前端、Ant Design React 前端、Python 后端等 11 套参考工程
16
+ - **多技术栈参考** — Java 后端、Vue 3 前端、Ant Design React 前端、Python 后端等 11 套参考工程
17
+ - **双平台支持** — 原生兼容 Claude Code 和 Qoder,自动检测运行环境
16
18
  - **npm 分发** — `npm install -g adspecs` 即装即用
17
19
 
18
- 本插件需配合 [`superpowers`](https://github.com/anthropics/claude-plugins-official) 插件(提供 TDD、子代理驱动开发等通用 Skill)一起使用。
20
+ > **平台依赖说明**:
21
+ > - **Claude Code**:需配合 [`superpowers`](https://github.com/anthropics/claude-plugins-official) 插件(提供 TDD、子代理驱动开发等通用 Skill)一起使用
22
+ > - **Qoder**:adspecs 的 Skills 可独立运行,无需额外依赖
19
23
 
20
24
  ---
21
25
 
@@ -344,12 +348,25 @@ Skill 支持通过 `.adspecs/paths.json` 自定义输出目录:
344
348
 
345
349
  详细安装说明见 [INSTALL.md](./INSTALL.md)。
346
350
 
351
+ ### Claude Code
352
+
353
+ ```bash
354
+ npm install -g adspecs
355
+ adspecs plugin install # 用户级(默认)
356
+ adspecs plugin install --scope project # 项目级
357
+ ```
358
+
359
+ ### Qoder
360
+
347
361
  ```bash
348
- # 安装
349
362
  npm install -g adspecs
350
- adspecs plugin install # 用户级(默认)
351
- adspecs plugin install --scope project # 项目级
363
+ adspecs plugin install --platform qoder # 用户级
364
+ adspecs plugin install --platform qoder --scope project # 项目级
365
+ ```
366
+
367
+ ### 通用命令
352
368
 
369
+ ```bash
353
370
  # 升级
354
371
  npm install -g adspecs@latest
355
372
 
@@ -363,15 +380,20 @@ adspecs plugin uninstall --scope project
363
380
  ## 常见问题
364
381
 
365
382
  **Q1:adspecs 与 superpowers 的关系?**
366
- adspecs 提供领域专属 Skill(需求、设计、模板、规范),superpowers 提供通用 AI 工程 Skill(TDD、子代理、调试)。两者分工协作,**编码阶段必须同时安装**。
383
+ adspecs 提供领域专属 Skill(需求、设计、模板、规范),superpowers 提供通用 AI 工程 Skill(TDD、子代理、调试)。
384
+ - **Claude Code**:两者分工协作,编码阶段必须同时安装
385
+ - **Qoder**:adspecs 可独立运行,无需 superpowers
367
386
 
368
387
  **Q2:用户级和项目级安装可以共存吗?**
369
388
  可以,项目级优先于用户级(就近原则)。个人开发选用户级,团队协作选项目级。
370
389
 
371
- **Q3:安装后 Skill 未生效?**
390
+ **Q3:Claude Code 和 Qoder 可以同时使用吗?**
391
+ 可以。adspecs 通过 `.claude-plugin/` 和 `.qoder-plugin/` 分别提供两个平台的插件配置,互不干扰。使用 `--platform` 参数指定目标平台即可。
392
+
393
+ **Q4:安装后 Skill 未生效?**
372
394
  1. 执行 `/plugins` 查看 adspecs 是否在已加载列表
373
- 2. 重启 Claude Code
374
- 3. 检查安装 scope 对应的目录是否存在
395
+ 2. 重启 Claude Code 或 Qoder
396
+ 3. 检查安装 scope 对应的目录是否存在(`.claude-plugin/` 或 `.qoder-plugin/`)
375
397
 
376
398
  **Q4:如何升级?**
377
399
  ```bash
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 为 Claude Code 插件')
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('从 Claude Code 中卸载 adspecs 插件')
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 # 注册为 Claude Code 插件(默认 user 范围)
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 # 升级到最新版本
@@ -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
- // 队列文件放在目标项目根目录的 .claude/ 下。
21
- // 使用 process.cwd() 获取目标项目根目录(Claude Code 运行 hook 时 CWD 即为目标项目)。
22
- // 注意:不能用 __dirname,因为 __dirname 指向的是插件安装目录,而非用户的目标项目。
23
- const PROJECT_ROOT = process.cwd();
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
- // CLAUDE_TOOL_OUTPUT_FILES Claude Code Hook 运行时注入为环境变量
234
- enqueueFromManifests(process.env.CLAUDE_TOOL_OUTPUT_FILES || '');
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
+ };
@@ -4,7 +4,8 @@
4
4
  * Session Logger — AI 会话日志自动保存 Hook
5
5
  *
6
6
  * 在 SessionStart / PreCompact / Stop 三个事件中管理会话保存状态,
7
- * 通过 additionalContext 引导 Claude 自行生成并写入结构化日志文件。
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 = process.cwd();
21
- const CLAUDE_DIR = path.join(PROJECT_ROOT, '.claude');
22
- const MARKER_FILE = path.join(CLAUDE_DIR, '.session-save-marker');
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(CLAUDE_DIR, { recursive: true });
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(供 Claude 读取) */
140
+ /** 输出 additionalContext 到 stdout(供 AI 平台读取,使用平台统一协议) */
139
141
  function outputContext(eventName, contextText) {
140
- var output = {
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 时间戳之间的分钟数 */
@@ -4,18 +4,19 @@
4
4
  * SessionStart Hook — 注入 using-adspecs 技能引导内容
5
5
  *
6
6
  * 在每次会话启动时,读取 using-adspecs/SKILL.md 并输出为
7
- * Claude Code hook additionalContext,确保 Agent 在任何任务开始前
7
+ * hook additionalContext,确保 Agent 在任何任务开始前
8
8
  * 都知道需要检查并使用 ADSpecs 技能。
9
9
  *
10
- * 机制参考:Superpowers hooks/session-start
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
- // hooks/ 在项目根下,__dirname 总是指向 hooks/ 目录
18
- const PLUGIN_ROOT = path.join(__dirname, '..');
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
- // Claude Code hook command 输出 JSON 到 stdout,
36
- // hookSpecificOutput.additionalContext 会被注入到 Agent 上下文
37
- var output = {
38
- hookSpecificOutput: {
39
- hookEventName: 'SessionStart',
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
+ );
@@ -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
- // 队列文件放在目标项目根目录的 .claude/ 下。
20
- // 使用 process.cwd() 获取目标项目根目录(Claude Code 运行 hook 时 CWD 即为目标项目)。
21
- // 注意:不能用 __dirname,因为 __dirname 指向的是插件安装目录,而非用户的目标项目。
22
- const PROJECT_ROOT = process.cwd();
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
- // CLAUDE_TOOL_OUTPUT_FILES Claude Code Hook 运行时注入为环境变量
116
- appendToQueue(process.env.CLAUDE_TOOL_OUTPUT_FILES || '');
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.27",
3
+ "version": "0.1.29",
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",
@@ -2,7 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  /**
5
- * npm 安装/更新后自动刷新 Claude Code 插件注册
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
- * - 检查 claude CLI 是否可用
14
- * - 检查 adspecs 插件是否已在 Claude Code 中注册
15
- * - 若已注册 → 静默重新安装以刷新版本(让 Claude Code 感知新版本)
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. 检查 claude CLI 是否可用
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} — Claude Code 插件注册已自动刷新\n`
103
+ `\n🔄 adspecs@${pkg.version} — AI 平台插件注册已自动刷新\n`
101
104
  );
102
105
  }
103
106
  } catch {
@@ -3,6 +3,7 @@
3
3
  const { execSync } = require('child_process');
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
+ const os = require('os');
6
7
  const chalk = require('chalk');
7
8
 
8
9
  /**
@@ -25,42 +26,326 @@ function checkClaudeCli() {
25
26
  }
26
27
 
27
28
  /**
28
- * 读取 plugin.json 获取插件名
29
+ * 读取 plugin.json 获取插件名(根据平台选择对应清单)
30
+ * @param {string} platform - 'claude' | 'qoder'
29
31
  */
30
- function getPluginName() {
31
- const pluginJsonPath = path.join(PLUGIN_DIR, '.claude-plugin', 'plugin.json');
32
+ function getPluginName(platform) {
33
+ var manifestDir = platform === 'qoder' ? '.qoder-plugin' : '.claude-plugin';
34
+ const pluginJsonPath = path.join(PLUGIN_DIR, manifestDir, 'plugin.json');
32
35
  if (!fs.existsSync(pluginJsonPath)) {
33
- throw new Error(`未找到插件清单文件: ${pluginJsonPath}`);
36
+ // 回退到 .claude-plugin
37
+ const fallbackPath = path.join(PLUGIN_DIR, '.claude-plugin', 'plugin.json');
38
+ if (!fs.existsSync(fallbackPath)) {
39
+ throw new Error(`未找到插件清单文件: ${pluginJsonPath}`);
40
+ }
41
+ const pluginJson = JSON.parse(fs.readFileSync(fallbackPath, 'utf8'));
42
+ return pluginJson.name || 'adspecs';
34
43
  }
35
44
  const pluginJson = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf8'));
36
45
  return pluginJson.name || 'adspecs';
37
46
  }
38
47
 
48
+ /**
49
+ * 检测当前可用的 AI 平台
50
+ * @returns {'claude'|'qoder'}
51
+ */
52
+ function detectInstalledPlatform() {
53
+ // 显式环境变量优先
54
+ var explicit = (process.env.ADSPECS_PLATFORM || '').toLowerCase();
55
+ if (explicit === 'qoder' || explicit === 'claude') {
56
+ return explicit;
57
+ }
58
+ // 检测 claude CLI
59
+ if (checkClaudeCli()) {
60
+ return 'claude';
61
+ }
62
+ // 默认回退 qoder(当前运行环境)
63
+ return 'qoder';
64
+ }
65
+
39
66
  /**
40
67
  * 安装(注册)插件到 Claude Code
41
68
  *
42
- * Claude Code 的 marketplace 命令期望路径指向包含 `.claude-plugin/marketplace.json`
43
- * 的目录,因此传入 PLUGIN_DIR(而非 PLUGIN_DIR/.claude-plugin)。
44
- *
45
69
  * @param {object} options
46
70
  * @param {string} options.scope - 安装范围: user|project
71
+ * @param {string} options.platform - 目标平台: claude|qoder(可选,自动检测)
47
72
  */
48
73
  exports.install = async function install(options) {
49
74
  const scope = options.scope || 'user';
50
- const pluginName = getPluginName();
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
- }
75
+ const platform = options.platform || detectInstalledPlatform();
76
+ const pluginName = getPluginName(platform);
57
77
 
58
78
  console.log(chalk.cyan('🔌 adspecs 插件注册'));
59
79
  console.log('');
60
80
  console.log(` 插件目录: ${PLUGIN_DIR}`);
61
81
  console.log(` 插件名称: ${pluginName}`);
62
82
  console.log(` 安装范围: ${scope}`);
83
+ console.log(` 目标平台: ${platform}`);
84
+ console.log('');
85
+
86
+ if (platform === 'qoder') {
87
+ await installForQoder(pluginName, scope);
88
+ } else {
89
+ await installForClaude(pluginName, scope);
90
+ }
91
+ };
92
+
93
+ /**
94
+ * Qoder 平台安装
95
+ * 自动注册到 Qoder 插件系统:
96
+ * - 用户级:创建 junction 链接 + 写入注册表 + 启用插件
97
+ * - 项目级:在项目目录创建 .qoder-plugin 链接
98
+ */
99
+ async function installForQoder(pluginName, scope) {
100
+ const qoderManifest = path.join(PLUGIN_DIR, '.qoder-plugin', 'plugin.json');
101
+ if (!fs.existsSync(qoderManifest)) {
102
+ throw new Error(`未找到 Qoder 插件清单: ${qoderManifest}`);
103
+ }
104
+
105
+ if (scope === 'project') {
106
+ await installQoderProject(pluginName, qoderManifest);
107
+ } else {
108
+ await installQoderUser(pluginName, qoderManifest);
109
+ }
110
+
111
+ console.log('');
112
+ console.log(chalk.green.bold(`✅ adspecs 插件已配置到 Qoder (scope=${scope})`));
113
+ console.log('');
114
+ printSkillList();
115
+ }
116
+
117
+ /**
118
+ * Qoder 用户级安装 — 自动注册到 ~/.qoder/plugins/
119
+ */
120
+ async function installQoderUser(pluginName, qoderManifest) {
121
+ const qoderHome = path.join(os.homedir(), '.qoder');
122
+ const pluginsDir = path.join(qoderHome, 'plugins');
123
+ const cacheDir = path.join(pluginsDir, 'cache', 'local');
124
+ const linkPath = path.join(cacheDir, pluginName);
125
+ const registryKey = `${pluginName}@local`;
126
+
127
+ console.log(chalk.gray(' → Qoder 用户级插件自动注册...'));
128
+ console.log('');
129
+
130
+ // 1. 确保目录结构存在
131
+ fs.mkdirSync(cacheDir, { recursive: true });
132
+
133
+ // 2. 创建 junction 链接(已存在则先移除重建,确保指向最新路径)
134
+ if (fs.existsSync(linkPath)) {
135
+ try {
136
+ fs.rmSync(linkPath, { recursive: true, force: true });
137
+ } catch (_err) {
138
+ // junction 可能无法用 rmSync 删除,尝试 unlink
139
+ try { fs.unlinkSync(linkPath); } catch (_e) { /* ignore */ }
140
+ }
141
+ }
142
+
143
+ try {
144
+ fs.symlinkSync(PLUGIN_DIR, linkPath, 'junction');
145
+ console.log(chalk.green(` ✅ 已创建插件链接: ${linkPath}`));
146
+ console.log(chalk.gray(` → ${PLUGIN_DIR}`));
147
+ } catch (err) {
148
+ // junction 创建失败(权限不足等),回退为复制关键文件
149
+ console.log(chalk.yellow(` ⚠ 链接创建失败(${err.message}),回退为文件复制...`));
150
+ fs.mkdirSync(linkPath, { recursive: true });
151
+ copyDirRecursive(PLUGIN_DIR, linkPath, [
152
+ 'node_modules', '.git', '.claude', 'assets'
153
+ ]);
154
+ console.log(chalk.green(` ✅ 已复制插件文件到: ${linkPath}`));
155
+ }
156
+
157
+ // 3. 读取插件版本
158
+ const pluginJson = JSON.parse(fs.readFileSync(qoderManifest, 'utf8'));
159
+ const version = pluginJson.version || '0.0.0';
160
+ const now = new Date().toISOString();
161
+
162
+ // 4. 写入 installed_plugins_v2.json
163
+ const v2Path = path.join(pluginsDir, 'installed_plugins_v2.json');
164
+ let v2Data = { version: 2, plugins: {} };
165
+ if (fs.existsSync(v2Path)) {
166
+ try { v2Data = JSON.parse(fs.readFileSync(v2Path, 'utf8')); } catch (_e) { /* use default */ }
167
+ }
168
+ if (!v2Data.plugins) v2Data.plugins = {};
169
+
170
+ v2Data.plugins[registryKey] = [{
171
+ scope: 'user',
172
+ installPath: linkPath,
173
+ version: version,
174
+ source: 'local',
175
+ installedAt: (v2Data.plugins[registryKey] && v2Data.plugins[registryKey][0] && v2Data.plugins[registryKey][0].installedAt) || now,
176
+ lastUpdated: now,
177
+ enabled: true
178
+ }];
179
+
180
+ fs.writeFileSync(v2Path, JSON.stringify(v2Data, null, 2) + '\n', 'utf8');
181
+ console.log(chalk.green(' ✅ 已写入插件注册表 (installed_plugins_v2.json)'));
182
+
183
+ // 5. 写入 installed_plugins.json(v1 兼容)
184
+ const v1Path = path.join(pluginsDir, 'installed_plugins.json');
185
+ let v1Data = { plugins: {} };
186
+ if (fs.existsSync(v1Path)) {
187
+ try { v1Data = JSON.parse(fs.readFileSync(v1Path, 'utf8')); } catch (_e) { /* use default */ }
188
+ }
189
+ if (!v1Data.plugins) v1Data.plugins = {};
190
+
191
+ v1Data.plugins[registryKey] = { installPath: linkPath };
192
+
193
+ fs.writeFileSync(v1Path, JSON.stringify(v1Data, null, 2) + '\n', 'utf8');
194
+ console.log(chalk.green(' ✅ 已写入插件注册表 (installed_plugins.json)'));
195
+
196
+ // 6. 在 settings.json 中启用插件
197
+ const settingsPath = path.join(qoderHome, 'settings.json');
198
+ let settings = {};
199
+ if (fs.existsSync(settingsPath)) {
200
+ try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_e) { /* use default */ }
201
+ }
202
+ if (!settings.enabledPlugins) settings.enabledPlugins = {};
203
+
204
+ settings.enabledPlugins[registryKey] = true;
205
+
206
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
207
+ console.log(chalk.green(' ✅ 已在 settings.json 中启用插件'));
208
+
63
209
  console.log('');
210
+ console.log(chalk.gray(' 请重启 Qoder 使插件生效。'));
211
+ }
212
+
213
+ /**
214
+ * Qoder 项目级安装
215
+ */
216
+ async function installQoderProject(pluginName, qoderManifest) {
217
+ // 项目级:在当前项目创建 .qoder-plugin 符号链接或复制
218
+ const targetDir = path.join(process.cwd(), '.qoder-plugin');
219
+ if (fs.existsSync(targetDir)) {
220
+ console.log(chalk.yellow(' ⏭ 当前项目已存在 .qoder-plugin/,跳过'));
221
+ } else {
222
+ try {
223
+ fs.symlinkSync(
224
+ path.join(PLUGIN_DIR, '.qoder-plugin'),
225
+ targetDir,
226
+ 'junction'
227
+ );
228
+ console.log(chalk.green(' ✅ 已创建 .qoder-plugin 符号链接'));
229
+ } catch (_err) {
230
+ fs.mkdirSync(targetDir, { recursive: true });
231
+ fs.copyFileSync(qoderManifest, path.join(targetDir, 'plugin.json'));
232
+ console.log(chalk.green(' ✅ 已复制 .qoder-plugin/plugin.json 到项目'));
233
+ }
234
+ }
235
+
236
+ // 确保 skills 可被访问
237
+ const targetSkills = path.join(process.cwd(), 'skills');
238
+ if (!fs.existsSync(targetSkills)) {
239
+ try {
240
+ fs.symlinkSync(path.join(PLUGIN_DIR, 'skills'), targetSkills, 'junction');
241
+ console.log(chalk.green(' ✅ 已创建 skills/ 符号链接'));
242
+ } catch (_err) {
243
+ console.log(chalk.yellow(' ⚠ skills/ 链接创建失败,请手动确保 Qoder 可访问插件 skills 目录'));
244
+ }
245
+ }
246
+
247
+ // 确保 hooks 可被访问
248
+ const targetHooks = path.join(process.cwd(), 'hooks');
249
+ if (!fs.existsSync(targetHooks)) {
250
+ try {
251
+ fs.symlinkSync(path.join(PLUGIN_DIR, 'hooks'), targetHooks, 'junction');
252
+ console.log(chalk.green(' ✅ 已创建 hooks/ 符号链接'));
253
+ } catch (_err) {
254
+ console.log(chalk.yellow(' ⚠ hooks/ 链接创建失败,请手动确保 Qoder 可访问插件 hooks 目录'));
255
+ }
256
+ }
257
+ }
258
+
259
+ /**
260
+ * 递归复制目录(排除指定目录名)
261
+ */
262
+ function copyDirRecursive(src, dest, excludeDirs) {
263
+ fs.mkdirSync(dest, { recursive: true });
264
+ const entries = fs.readdirSync(src, { withFileTypes: true });
265
+ for (const entry of entries) {
266
+ if (excludeDirs && excludeDirs.includes(entry.name)) continue;
267
+ const srcPath = path.join(src, entry.name);
268
+ const destPath = path.join(dest, entry.name);
269
+ if (entry.isDirectory()) {
270
+ copyDirRecursive(srcPath, destPath, excludeDirs);
271
+ } else {
272
+ fs.copyFileSync(srcPath, destPath);
273
+ }
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Qoder 用户级卸载 — 移除链接 + 注册表条目 + 启用配置
279
+ */
280
+ function uninstallQoderUser(pluginName) {
281
+ const qoderHome = path.join(os.homedir(), '.qoder');
282
+ const pluginsDir = path.join(qoderHome, 'plugins');
283
+ const linkPath = path.join(pluginsDir, 'cache', 'local', pluginName);
284
+ const registryKey = `${pluginName}@local`;
285
+
286
+ // 1. 移除 junction 链接 / 复制的目录
287
+ if (fs.existsSync(linkPath)) {
288
+ try {
289
+ fs.rmSync(linkPath, { recursive: true, force: true });
290
+ console.log(chalk.green(` ✅ 已移除插件目录: ${linkPath}`));
291
+ } catch (err) {
292
+ console.log(chalk.yellow(` ⚠ 移除插件目录失败: ${err.message}`));
293
+ }
294
+ } else {
295
+ console.log(chalk.yellow(' ⏭ 插件目录不存在,跳过'));
296
+ }
297
+
298
+ // 2. 从 installed_plugins_v2.json 移除
299
+ const v2Path = path.join(pluginsDir, 'installed_plugins_v2.json');
300
+ if (fs.existsSync(v2Path)) {
301
+ try {
302
+ const v2Data = JSON.parse(fs.readFileSync(v2Path, 'utf8'));
303
+ if (v2Data.plugins && v2Data.plugins[registryKey]) {
304
+ delete v2Data.plugins[registryKey];
305
+ fs.writeFileSync(v2Path, JSON.stringify(v2Data, null, 2) + '\n', 'utf8');
306
+ console.log(chalk.green(' ✅ 已从注册表移除 (installed_plugins_v2.json)'));
307
+ }
308
+ } catch (_err) { /* ignore */ }
309
+ }
310
+
311
+ // 3. 从 installed_plugins.json 移除
312
+ const v1Path = path.join(pluginsDir, 'installed_plugins.json');
313
+ if (fs.existsSync(v1Path)) {
314
+ try {
315
+ const v1Data = JSON.parse(fs.readFileSync(v1Path, 'utf8'));
316
+ if (v1Data.plugins && v1Data.plugins[registryKey]) {
317
+ delete v1Data.plugins[registryKey];
318
+ fs.writeFileSync(v1Path, JSON.stringify(v1Data, null, 2) + '\n', 'utf8');
319
+ console.log(chalk.green(' ✅ 已从注册表移除 (installed_plugins.json)'));
320
+ }
321
+ } catch (_err) { /* ignore */ }
322
+ }
323
+
324
+ // 4. 从 settings.json 禁用
325
+ const settingsPath = path.join(qoderHome, 'settings.json');
326
+ if (fs.existsSync(settingsPath)) {
327
+ try {
328
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
329
+ if (settings.enabledPlugins && settings.enabledPlugins[registryKey] !== undefined) {
330
+ delete settings.enabledPlugins[registryKey];
331
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
332
+ console.log(chalk.green(' ✅ 已从 settings.json 移除启用配置'));
333
+ }
334
+ } catch (_err) { /* ignore */ }
335
+ }
336
+
337
+ console.log(chalk.gray(' 请重启 Qoder 使变更生效。'));
338
+ }
339
+
340
+ /**
341
+ * Claude Code 平台安装(原有逻辑)
342
+ */
343
+ async function installForClaude(pluginName, scope) {
344
+ // marketplace.json 必须存在,否则无法注册
345
+ const marketplaceJson = path.join(PLUGIN_DIR, '.claude-plugin', 'marketplace.json');
346
+ if (!fs.existsSync(marketplaceJson)) {
347
+ throw new Error(`未找到 marketplace 清单: ${marketplaceJson}`);
348
+ }
64
349
 
65
350
  // 1. 检查 claude CLI
66
351
  if (!checkClaudeCli()) {
@@ -74,7 +359,6 @@ exports.install = async function install(options) {
74
359
  }
75
360
 
76
361
  // 2. 注册 marketplace
77
- // 注意:传入 PLUGIN_DIR,claude 会去找 <PLUGIN_DIR>/.claude-plugin/marketplace.json
78
362
  console.log(chalk.gray(' → 注册 marketplace...'));
79
363
  let marketplaceRegistered = false;
80
364
  try {
@@ -90,15 +374,13 @@ exports.install = async function install(options) {
90
374
  const combined = stderr + stdout;
91
375
  if (combined.includes('already exists') || combined.includes('already registered')) {
92
376
  console.log(chalk.yellow(' ⏭ marketplace 已存在,跳过'));
93
- marketplaceRegistered = true; // 已存在也视为可用
377
+ marketplaceRegistered = true;
94
378
  } else {
95
- // 把真实错误输出,方便排查,不再静默吞掉
96
379
  console.log(chalk.red(` ❌ marketplace 注册失败: ${combined.trim() || err.message}`));
97
380
  }
98
381
  }
99
382
 
100
383
  // 3. 安装插件
101
- // 只有 marketplace 注册成功(或已存在)时,才能按名称安装
102
384
  console.log(chalk.gray(' → 安装插件...'));
103
385
  if (!marketplaceRegistered) {
104
386
  throw new Error('marketplace 未注册成功,无法按名称安装插件,请检查上方错误');
@@ -122,28 +404,63 @@ exports.install = async function install(options) {
122
404
  console.log('');
123
405
  console.log(chalk.green.bold(`✅ adspecs 插件已注册到 Claude Code (scope=${scope})`));
124
406
  console.log('');
125
- console.log(' 现在可以在 Claude Code 中使用以下技能:');
407
+ printSkillList();
408
+ }
409
+
410
+ /**
411
+ * 打印可用技能列表
412
+ */
413
+ function printSkillList() {
414
+ console.log(' 现在可以使用以下技能:');
126
415
  console.log(' /adspecs-prd — 生成 PRD');
127
416
  console.log(' /adspecs-plan — 后端系统设计');
128
417
  console.log(' /adspecs-tasks — TDD 任务清单');
129
418
  console.log(' /adspecs-utest — 生成单元测试');
130
- console.log(' ...(共 19 个技能)');
419
+ console.log(' ...(共 30 个技能)');
131
420
  console.log('');
132
- };
421
+ }
133
422
 
134
423
  /**
135
424
  * 卸载插件
136
425
  *
137
426
  * @param {object} options
138
427
  * @param {string} options.scope - 卸载范围: user|project
428
+ * @param {string} options.platform - 目标平台: claude|qoder(可选,自动检测)
139
429
  */
140
430
  exports.uninstall = async function uninstall(options) {
141
431
  const scope = options.scope || 'user';
142
- const pluginName = getPluginName();
432
+ const platform = options.platform || detectInstalledPlatform();
433
+ const pluginName = getPluginName(platform);
143
434
 
144
435
  console.log(chalk.cyan('🔌 adspecs 插件卸载'));
145
436
  console.log('');
437
+ console.log(` 目标平台: ${platform}`);
438
+ console.log('');
439
+
440
+ if (platform === 'qoder') {
441
+ if (scope === 'project') {
442
+ // Qoder 项目级卸载:移除项目链接
443
+ const targetDir = path.join(process.cwd(), '.qoder-plugin');
444
+ if (fs.existsSync(targetDir)) {
445
+ try {
446
+ fs.rmSync(targetDir, { recursive: true, force: true });
447
+ console.log(chalk.green(' ✅ 已移除项目 .qoder-plugin/'));
448
+ } catch (err) {
449
+ throw new Error(`移除失败: ${err.message}`);
450
+ }
451
+ } else {
452
+ console.log(chalk.yellow(' ⏭ 项目未安装 .qoder-plugin/,无需卸载'));
453
+ }
454
+ } else {
455
+ // Qoder 用户级卸载:移除链接 + 注册表 + 启用配置
456
+ uninstallQoderUser(pluginName);
457
+ }
458
+ console.log('');
459
+ console.log(chalk.green(`✅ ${pluginName} 已从 Qoder 卸载 (scope=${scope})`));
460
+ return;
461
+ }
146
462
 
463
+ // Claude Code 卸载
147
464
  if (!checkClaudeCli()) {
148
465
  console.error(chalk.red('❌ 未检测到 claude CLI'));
149
466
  process.exit(1);