@culeo/specx 0.1.0 → 0.1.2
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/README.md +5 -5
- package/package.json +2 -2
- package/skills/specx-archive/SKILL.md +13 -13
- package/skills/specx-clarify/SKILL.md +6 -6
- package/skills/specx-create-rule/SKILL.md +10 -10
- package/skills/specx-demystify/SKILL.md +21 -21
- package/skills/specx-fix/SKILL.md +313 -0
- package/{specx.js → src/specx.js} +192 -9
package/README.md
CHANGED
|
@@ -88,20 +88,20 @@ specx 是一套企业级研发流程 skill 系列,核心理念是**先签合
|
|
|
88
88
|
docs/specx/
|
|
89
89
|
├── spaces/ # 需求空间(每个需求独立目录)
|
|
90
90
|
│ └── {yyyyMMdd}-{需求摘要}/
|
|
91
|
-
│ ├──
|
|
91
|
+
│ ├── raws/ # 原始需求来源(多源分类)
|
|
92
92
|
│ │ ├── P001-{需求名称}.md # 产品需求文档
|
|
93
93
|
│ │ ├── U001-{用户反馈}.md # 用户输入/语料
|
|
94
94
|
│ │ └── ...
|
|
95
|
-
│ ├──
|
|
95
|
+
│ ├── t-01-{子需求名}/
|
|
96
96
|
│ │ ├── 00-子需求.md # 子需求原始描述(由 demystify 产出)
|
|
97
97
|
│ │ ├── 01-clarify.md # 澄清记录
|
|
98
98
|
│ │ ├── 02-spec.md # 规格说明书
|
|
99
99
|
│ │ ├── 03-design.md # 设计方案
|
|
100
100
|
│ │ └── 04-tasks.md # 任务清单
|
|
101
|
-
│ └──
|
|
101
|
+
│ └── t-02-{子需求名}/
|
|
102
102
|
│ └── ...
|
|
103
103
|
├── wiki/ # 项目知识库(归档沉淀)
|
|
104
|
-
├──
|
|
104
|
+
├── rules/ # 框架规范
|
|
105
105
|
└── templates/ # 项目专属模板
|
|
106
106
|
```
|
|
107
107
|
|
|
@@ -111,7 +111,7 @@ docs/specx/
|
|
|
111
111
|
|
|
112
112
|
- **文件名规范**:`00-子需求.md` / `01-clarify.md` / `02-spec.md` / `03-design.md` / `04-tasks.md`
|
|
113
113
|
- **wiki 知识沉淀**:`docs/specx/wiki/`,由 `specx-archive` 维护
|
|
114
|
-
- **框架规范**:`docs/specx/
|
|
114
|
+
- **框架规范**:`docs/specx/rules/`,clarify 时跳过已有规范,不重复提问
|
|
115
115
|
- **项目模板**:`docs/specx/templates/`,首次 design 时生成
|
|
116
116
|
|
|
117
117
|
---
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@culeo/specx",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Install specx skills to AI coding agents (Claude Code, Codex, etc.)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"specx": "./specx.js"
|
|
7
|
+
"specx": "./src/specx.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"commander": "^12.0.0"
|
|
@@ -11,7 +11,7 @@ tags: [specx, archive, wiki, knowledge]
|
|
|
11
11
|
|
|
12
12
|
当一个子需求完成(clarify → spec → design → code 全部走完)后,用户说"归档"、"沉淀到 wiki"、"完成这个需求"时使用。
|
|
13
13
|
|
|
14
|
-
**前置判断:** 子需求目录 `
|
|
14
|
+
**前置判断:** 子需求目录 `t-xx/` 下已有 `clarify.md`、`spec.md`、`design.md`(及可选 `code` 相关文档)。
|
|
15
15
|
|
|
16
16
|
**不是触发的情况:**
|
|
17
17
|
- 需求还在中途(未完成 code)
|
|
@@ -21,11 +21,11 @@ tags: [specx, archive, wiki, knowledge]
|
|
|
21
21
|
|
|
22
22
|
## 输入
|
|
23
23
|
|
|
24
|
-
- `
|
|
25
|
-
- `
|
|
26
|
-
- `
|
|
27
|
-
- `
|
|
28
|
-
- `
|
|
24
|
+
- `t-xx/00-子需求.md` — 原始需求
|
|
25
|
+
- `t-xx/01-clarify.md` — 澄清后的需求
|
|
26
|
+
- `t-xx/02-spec.md` — 规格说明书
|
|
27
|
+
- `t-xx/03-design.md` — 设计方案
|
|
28
|
+
- `t-xx/04-tasks.md` — 任务清单(已完成的)
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
@@ -54,8 +54,8 @@ docs/specx/wiki/
|
|
|
54
54
|
|
|
55
55
|
### 命名规则
|
|
56
56
|
|
|
57
|
-
- 文件名:`{
|
|
58
|
-
- 示例:`
|
|
57
|
+
- 文件名:`{t-xx}-{简短英文名}.md`,小写,连字符分隔
|
|
58
|
+
- 示例:`t01-stock-card.md`、`t03-redis-cache.md`
|
|
59
59
|
|
|
60
60
|
> 首次归档时由本 skill 创建 `docs/specx/wiki/` 目录和 `README.md`。
|
|
61
61
|
|
|
@@ -137,7 +137,7 @@ docs/specx/wiki/
|
|
|
137
137
|
**分类:** {entities/features/decisions/components}
|
|
138
138
|
**最后更新时间:** {YYYY-MM-DD}
|
|
139
139
|
**关联需求:**
|
|
140
|
-
- [
|
|
140
|
+
- [t-xx](../{需求目录}/t-xx/00-子需求.md)
|
|
141
141
|
|
|
142
142
|
---
|
|
143
143
|
|
|
@@ -199,15 +199,15 @@ history:
|
|
|
199
199
|
|
|
200
200
|
## Step 6️⃣ 更新子需求目录状态
|
|
201
201
|
|
|
202
|
-
在 `
|
|
202
|
+
在 `t-xx/` 下的 `README.md`(如果没有就创建)标注归档状态:
|
|
203
203
|
|
|
204
204
|
```markdown
|
|
205
|
-
#
|
|
205
|
+
# t-{xx}-{子需求名称}
|
|
206
206
|
|
|
207
207
|
**状态:** ✅ 已归档
|
|
208
208
|
**最后更新时间:** {YYYY-MM-DD}
|
|
209
209
|
**Wiki 沉淀位置:**
|
|
210
|
-
- [
|
|
210
|
+
- [t-xx-{slug}](../wiki/{分类}/t-xx-{slug}.md)
|
|
211
211
|
```
|
|
212
212
|
|
|
213
213
|
---
|
|
@@ -218,7 +218,7 @@ history:
|
|
|
218
218
|
[ ] 已读取全部子需求文档(00-子需求/clarify/spec/design/tasks)
|
|
219
219
|
[ ] 判断了正确的 wiki 分类(entities/features/decisions/components)
|
|
220
220
|
[ ] 每个提炼内容写成独立文件
|
|
221
|
-
[ ] 文件名符合命名规则 {
|
|
221
|
+
[ ] 文件名符合命名规则 {t-xx}-{slug}.md
|
|
222
222
|
[ ] 更新了 README.md 索引(顶部插入新行)
|
|
223
223
|
[ ] 标注了子需求目录为已归档
|
|
224
224
|
[ ] 未污染原始文档(clarify/spec/design.md 未被修改)
|
|
@@ -159,7 +159,7 @@ history:
|
|
|
159
159
|
# {子需求名称}
|
|
160
160
|
|
|
161
161
|
**所属原始需求:** {需求名称}
|
|
162
|
-
**子需求目录:**
|
|
162
|
+
**子需求目录:** t-{序号}-{名称}
|
|
163
163
|
|
|
164
164
|
---
|
|
165
165
|
|
|
@@ -207,16 +207,16 @@ history:
|
|
|
207
207
|
|
|
208
208
|
### Step 2.5️⃣ 框架对齐(跳过已定义的)
|
|
209
209
|
|
|
210
|
-
> ⚠️ 如果 `docs/specx/
|
|
210
|
+
> ⚠️ 如果 `docs/specx/rules/` 下已有规范,只clarify 框架未定义的部分
|
|
211
211
|
|
|
212
212
|
**动作:**
|
|
213
|
-
1. 扫描 `docs/specx/
|
|
213
|
+
1. 扫描 `docs/specx/rules/` 目录下的规范文件
|
|
214
214
|
2. 在 clarify-prd 中标注哪些是框架定义的(直接引用,不重复问)
|
|
215
215
|
3. 只对「框架未定义」的部分提问
|
|
216
216
|
|
|
217
217
|
**扫描范围:**
|
|
218
218
|
```
|
|
219
|
-
docs/specx/
|
|
219
|
+
docs/specx/rules/ → 项目框架规范(由项目自己组织子目录结构)
|
|
220
220
|
```
|
|
221
221
|
|
|
222
222
|
**标注示例:**
|
|
@@ -252,7 +252,7 @@ history:
|
|
|
252
252
|
|
|
253
253
|
# {子需求名称} — 规格说明书
|
|
254
254
|
|
|
255
|
-
**所属子需求:**
|
|
255
|
+
**所属子需求:** t-{序号}-{名称}
|
|
256
256
|
|
|
257
257
|
---
|
|
258
258
|
|
|
@@ -505,7 +505,7 @@ history:
|
|
|
505
505
|
- **两个文件同步更新** — clarify-prd 和 spec 是一体的
|
|
506
506
|
- **clarify 迭代次数不限** — 可能是 1 次,可能是 10 次,都正常
|
|
507
507
|
- **文档状态** — 新建时 `draft`,完成时 `review`
|
|
508
|
-
- **框架已定义的不重复问** — 先扫描 `docs/specx/
|
|
508
|
+
- **框架已定义的不重复问** — 先扫描 `docs/specx/rules/`,直接引用,只clarify 未定义的部分
|
|
509
509
|
- **避免污染上下文** — 框架规范在 rule/ 目录维护,不进clarify过程
|
|
510
510
|
|
|
511
511
|
---
|
|
@@ -36,7 +36,7 @@ tags: [specx, rule, convention, knowledge]
|
|
|
36
36
|
| **替换** | 现有规则方向正确但表述不对 | 替换原文 |
|
|
37
37
|
|
|
38
38
|
**判断步骤:**
|
|
39
|
-
1. 读取 `docs/specx/
|
|
39
|
+
1. 读取 `docs/specx/rules/` 下所有现有规则
|
|
40
40
|
2. 与新规则对比:是否同类?是否矛盾?是否重复?
|
|
41
41
|
3. 确定类型后,再执行对应动作
|
|
42
42
|
|
|
@@ -44,9 +44,9 @@ tags: [specx, rule, convention, knowledge]
|
|
|
44
44
|
|
|
45
45
|
## 规则发现路径
|
|
46
46
|
|
|
47
|
-
### 路径 1:项目初始化时(docs/specx/
|
|
47
|
+
### 路径 1:项目初始化时(docs/specx/rules 不存在)
|
|
48
48
|
|
|
49
|
-
**前置判断:** `docs/specx/
|
|
49
|
+
**前置判断:** `docs/specx/rules/` 目录不存在
|
|
50
50
|
|
|
51
51
|
**扫描步骤:**
|
|
52
52
|
|
|
@@ -56,7 +56,7 @@ tags: [specx, rule, convention, knowledge]
|
|
|
56
56
|
- 目录结构约定
|
|
57
57
|
- 常用工具库
|
|
58
58
|
- 分层架构(MVVM / Clean / DDD)
|
|
59
|
-
3. **推断规则** — 从扫描结果推断项目规范,写入 `docs/specx/
|
|
59
|
+
3. **推断规则** — 从扫描结果推断项目规范,写入 `docs/specx/rules/`
|
|
60
60
|
|
|
61
61
|
### 路径 2:对话中途纠正(上下文沉淀)
|
|
62
62
|
|
|
@@ -65,16 +65,16 @@ tags: [specx, rule, convention, knowledge]
|
|
|
65
65
|
**动作:**
|
|
66
66
|
1. 理解用户纠正的本质(表面修改 vs 原则变更)
|
|
67
67
|
2. 询问用户:"是否需要把这个规则固化到项目的规范文档里?"
|
|
68
|
-
3. 如用户确认,从纠正内容中提炼规则,写入 `docs/specx/
|
|
68
|
+
3. 如用户确认,从纠正内容中提炼规则,写入 `docs/specx/rules/`
|
|
69
69
|
|
|
70
70
|
### 路径 3:用户明确提出规则
|
|
71
71
|
|
|
72
72
|
**前置判断:** 用户说"加上 X 规则"、"规定 Y 这样做"等
|
|
73
73
|
|
|
74
74
|
**动作:**
|
|
75
|
-
1. **先判断** — 读取 `docs/specx/
|
|
75
|
+
1. **先判断** — 读取 `docs/specx/rules/` 下现有规则,确认是否已有相关规则
|
|
76
76
|
2. 按判断结果执行(新增/冲突/过时/细化/替换)
|
|
77
|
-
3. 更新 `docs/specx/
|
|
77
|
+
3. 更新 `docs/specx/rules/README.md`
|
|
78
78
|
|
|
79
79
|
### 路径 4:意图不明确
|
|
80
80
|
|
|
@@ -88,10 +88,10 @@ tags: [specx, rule, convention, knowledge]
|
|
|
88
88
|
|
|
89
89
|
## 输出结构
|
|
90
90
|
|
|
91
|
-
### docs/specx/
|
|
91
|
+
### docs/specx/rules/ 目录结构
|
|
92
92
|
|
|
93
93
|
```
|
|
94
|
-
docs/specx/
|
|
94
|
+
docs/specx/rules/
|
|
95
95
|
├── README.md # 规则总览 & 索引
|
|
96
96
|
├── naming.md # 命名规范
|
|
97
97
|
├── workflow.md # 开发流程规范
|
|
@@ -99,7 +99,7 @@ docs/specx/rule/
|
|
|
99
99
|
└── architecture.md # 架构约定
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
### docs/specx/
|
|
102
|
+
### docs/specx/rules/README.md 模板
|
|
103
103
|
|
|
104
104
|
```markdown
|
|
105
105
|
# 项目规范
|
|
@@ -22,12 +22,12 @@ history:
|
|
|
22
22
|
|
|
23
23
|
| 输入 | 说明 |
|
|
24
24
|
|------|------|
|
|
25
|
-
| 原始需求来源 | 一个或多个文件,放入 `docs/specx/spaces/{yyyyMMdd}-{摘要}/
|
|
25
|
+
| 原始需求来源 | 一个或多个文件,放入 `docs/specx/spaces/{yyyyMMdd}-{摘要}/raws/` 目录,只读不修改 |
|
|
26
26
|
| 项目上下文 | 技术栈(iOS / KMP / Android)、现有框架规范、架构约定 |
|
|
27
27
|
|
|
28
|
-
###
|
|
28
|
+
### raws/ 目录规范
|
|
29
29
|
|
|
30
|
-
所有原始来源文件(PRD、用户反馈、聊天记录、参考文献等)统一放入 `
|
|
30
|
+
所有原始来源文件(PRD、用户反馈、聊天记录、参考文献等)统一放入 `raws/` 子目录,按分类前缀命名:
|
|
31
31
|
|
|
32
32
|
| 分类 | 前缀 | 说明 |
|
|
33
33
|
|------|------|------|
|
|
@@ -51,7 +51,7 @@ Raws/
|
|
|
51
51
|
|
|
52
52
|
| 输出 | 格式 | 说明 |
|
|
53
53
|
|------|------|------|
|
|
54
|
-
| 子需求目录集合 | `docs/specx/spaces/{yyyyMMdd}-{摘要}/
|
|
54
|
+
| 子需求目录集合 | `docs/specx/spaces/{yyyyMMdd}-{摘要}/t-{编号}-{名称}/00-子需求.md` | 每个子需求独立目录,含独立分析 |
|
|
55
55
|
| 拆解决策记录 | 同一 docs 目录下的 `00-拆解策略.md`(可选) | 记录选择策略 A/B 的理由 |
|
|
56
56
|
|
|
57
57
|
## 4. 拆解策略(二选一)
|
|
@@ -90,7 +90,7 @@ Raws/
|
|
|
90
90
|
- 检查项目是否已有数据模型规范、状态机模式、数据流约定(如已有项目的 Entity 定义、API 规范层)
|
|
91
91
|
- 对已有抽象层:**不重复拆,直接引用**已有规范的路径/文档
|
|
92
92
|
- 只拆「新增部分」——当前需求引入的新实体、新状态、新数据流
|
|
93
|
-
- 示例:项目已有「商品数据模型」规范 →
|
|
93
|
+
- 示例:项目已有「商品数据模型」规范 → t-01 不再定义商品模型,改为「按项目现有商品模型扩展新字段」
|
|
94
94
|
|
|
95
95
|
**Step 1:识别新增抽象层**
|
|
96
96
|
- 梳理需求涉及的所有「实体」(Entity)—— 有唯一标识、有生命周期的东西
|
|
@@ -107,24 +107,24 @@ Raws/
|
|
|
107
107
|
|
|
108
108
|
```
|
|
109
109
|
抽象层:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
t-01: 订单数据模型(Order Entity + 商品线 Item Value Object)
|
|
111
|
+
t-02: 订单状态机(待支付→已支付→已发货→已完成→已取消,含流转条件)
|
|
112
|
+
t-03: 订单数据加载/缓存/刷新机制(列表+详情的数据流)
|
|
113
113
|
|
|
114
114
|
实现层:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
t-04: 订单列表页(依赖 t-01, t-03)
|
|
116
|
+
t-05: 订单详情页(依赖 t-01, t-02, t-03)
|
|
117
|
+
t-06: 退款处理流程(依赖 t-01 订单状态机扩展)
|
|
118
|
+
t-07: 数据统计看板(依赖 t-01, t-03)
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
#### 依赖关系与执行顺序
|
|
122
122
|
|
|
123
123
|
```
|
|
124
124
|
执行顺序:
|
|
125
|
-
Phase I(独立可并行):
|
|
126
|
-
Phase II(依赖 Phase I):
|
|
127
|
-
Phase III(可选):
|
|
125
|
+
Phase I(独立可并行):t-01 → t-02 → t-03
|
|
126
|
+
Phase II(依赖 Phase I):t-04, t-05, t-06(可并行)
|
|
127
|
+
Phase III(可选):t-07
|
|
128
128
|
```
|
|
129
129
|
|
|
130
130
|
#### 🎯 策略 B:按业务模块拆
|
|
@@ -149,10 +149,10 @@ Raws/
|
|
|
149
149
|
|
|
150
150
|
## 6. 输出规范
|
|
151
151
|
|
|
152
|
-
每个子需求目录 `/
|
|
152
|
+
每个子需求目录 `/t-{编号}-{名称}/` 下:
|
|
153
153
|
|
|
154
154
|
```
|
|
155
|
-
|
|
155
|
+
t-001-订单数据模型/
|
|
156
156
|
├── 00-子需求.md # 从原始需求摘录,可独立分析
|
|
157
157
|
└── (后续 clarify/design/writing-plans 产物)
|
|
158
158
|
```
|
|
@@ -160,10 +160,10 @@ T-001-订单数据模型/
|
|
|
160
160
|
`00-子需求.md` 模板:
|
|
161
161
|
|
|
162
162
|
```markdown
|
|
163
|
-
##
|
|
163
|
+
## t-{编号}: {子需求名称}
|
|
164
164
|
|
|
165
165
|
### 来源
|
|
166
|
-
> 摘录自 `
|
|
166
|
+
> 摘录自 `raws/{文件名}`
|
|
167
167
|
|
|
168
168
|
{从原始需求中摘录相关段落}
|
|
169
169
|
|
|
@@ -171,8 +171,8 @@ T-001-订单数据模型/
|
|
|
171
171
|
{明确本子需求包含什么、不包含什么}
|
|
172
172
|
|
|
173
173
|
### 依赖
|
|
174
|
-
- 依赖:
|
|
175
|
-
- 被依赖:
|
|
174
|
+
- 依赖:t-{编号1}, t-{编号2}
|
|
175
|
+
- 被依赖:t-{编号3}, t-{编号4}
|
|
176
176
|
|
|
177
177
|
### 类型
|
|
178
178
|
- [ ] 抽象层(数据模型/状态机/数据流/领域规则)
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specx-fix
|
|
3
|
+
description: specx 流程的 bug 修复 skill — 自动判断验收 bug(场景 A)和独立 bug(场景 B),提供根因分析、修复方案、归档沉淀
|
|
4
|
+
category: software-development
|
|
5
|
+
tags: [specx, bugfix, debugging]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# specx-fix:Bug 修复
|
|
9
|
+
|
|
10
|
+
## 触发条件
|
|
11
|
+
|
|
12
|
+
当用户反馈 bug 相关问题时触发本 skill。不需要用户说特定的关键词,通过上下文自动判断走哪条路径。
|
|
13
|
+
|
|
14
|
+
## 场景判断(自动)
|
|
15
|
+
|
|
16
|
+
| 信号 | 场景 A(验收 bug) | 场景 B(独立 bug) |
|
|
17
|
+
|------|-------------------|-------------------|
|
|
18
|
+
| 当前有活跃子需求 `t-xxx/` | ✅ | — |
|
|
19
|
+
| 当前 git 分支是 feature / 需求分支(如 `t-xxx-*`) | ✅ | — |
|
|
20
|
+
| 问题在最近修改的代码范围内 | ✅ | — |
|
|
21
|
+
| 问题在当前子需求的新功能范围内 | ✅ | — |
|
|
22
|
+
| 当前 git 分支是 `fix/*` 或 `hotfix/*` | — | ✅ |
|
|
23
|
+
| 当前 git 分支是 `main` / `master` / `maintenance` | — | ✅ |
|
|
24
|
+
| 没有进行中的子需求 | — | ✅ |
|
|
25
|
+
| 问题在完全不同的模块 | — | ✅ |
|
|
26
|
+
|
|
27
|
+
**判断逻辑:**
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
IF 有活跃子需求 t-xxx/ AND
|
|
31
|
+
(问题在最近改动范围内 OR git 分支是需求分支 OR 问题在新功能范围内):
|
|
32
|
+
→ 场景 A(验收 bug)
|
|
33
|
+
ELSE:
|
|
34
|
+
→ 场景 B(独立 bug)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 场景 A:验收 Bug(开发过程)
|
|
40
|
+
|
|
41
|
+
### 适用条件
|
|
42
|
+
|
|
43
|
+
当前正在执行 specx 流程,子需求已实现但验收时发现问题。根因可能在 spec、design 或代码实现中。
|
|
44
|
+
|
|
45
|
+
### 流程
|
|
46
|
+
|
|
47
|
+
#### Step A1️⃣ 定位根因环节
|
|
48
|
+
|
|
49
|
+
判断问题出在哪个环节:
|
|
50
|
+
|
|
51
|
+
| 根因位置 | 判断依据 | 修复动作 |
|
|
52
|
+
|----------|---------|---------|
|
|
53
|
+
| **spec 遗漏** | 需求定义本身就没覆盖这个场景 / 边界条件 | 更新 `02-spec.md`,然后补 design,重新执行 |
|
|
54
|
+
| **design 遗漏** | spec 定义了但设计方案没覆盖 | 更新 `03-design.md`(或模板定义的文件),重新执行 |
|
|
55
|
+
| **代码实现错** | spec 和 design 都对,代码写错了 | 直接修代码 |
|
|
56
|
+
|
|
57
|
+
#### Step A2️⃣ 修复
|
|
58
|
+
|
|
59
|
+
1. 根据根因回退到对应环节修复
|
|
60
|
+
2. 更新下游文档(如果上游改了,下游必须同步)
|
|
61
|
+
3. 在 `04-tasks.md` 末尾追加验收 bug 记录:
|
|
62
|
+
|
|
63
|
+
```markdown
|
|
64
|
+
### 验收 bug 记录
|
|
65
|
+
|
|
66
|
+
| 时间 | 描述 | 根因环节 | 修复内容 | 状态 |
|
|
67
|
+
|------|------|---------|---------|------|
|
|
68
|
+
| {YYYY-MM-DD} | {bug描述} | spec / design / 代码 | {修复了什么} | ✅ |
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> 如果 `04-tasks.md` 不存在,创建 `t-xxx/99-fix.md`
|
|
72
|
+
|
|
73
|
+
#### Step A3️⃣ 更新文档状态
|
|
74
|
+
|
|
75
|
+
涉及修改的文档,状态回退为 `draft`:
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
---
|
|
79
|
+
status: draft
|
|
80
|
+
updated: {YYYY-MM-DD}
|
|
81
|
+
history:
|
|
82
|
+
- "v{n}: 验收修复: {摘要}"
|
|
83
|
+
---
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Step A4️⃣ 回到执行
|
|
87
|
+
|
|
88
|
+
修复完成后,回到 `specx-executing-plans` 继续执行修改后的任务。
|
|
89
|
+
|
|
90
|
+
### 场景 A 不做什么
|
|
91
|
+
|
|
92
|
+
- 不创建新目录
|
|
93
|
+
- 不走独立 bug 的完整文档流程
|
|
94
|
+
- 不归档到 wiki(如果值得归档,等子需求完成时走 archive 流程统一沉淀)
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 场景 B:独立 Bug(线上/历史/日常)
|
|
99
|
+
|
|
100
|
+
### 适用条件
|
|
101
|
+
|
|
102
|
+
- 无当前需求上下文
|
|
103
|
+
- 线上报障、历史代码问题、日常发现
|
|
104
|
+
- 与当前进行中的需求无关
|
|
105
|
+
|
|
106
|
+
### 流程
|
|
107
|
+
|
|
108
|
+
#### Step B1️⃣ 创建 bug 工作空间
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
docs/specx/fixes/
|
|
112
|
+
└── {yyyyMMdd}-{简短描述}/
|
|
113
|
+
├── 00-bug-report.md # bug 描述 & 复现步骤
|
|
114
|
+
├── 01-root-cause.md # 根因分析
|
|
115
|
+
├── 02-fix-plan.md # 修复方案
|
|
116
|
+
└── 03-fix-done.md # 修复验证 & 归档
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**命名规则:** `{yyyyMMdd}-{3-5单词描述}`,全小写连字符
|
|
120
|
+
|
|
121
|
+
#### Step B2️⃣ 00-bug-report.md:Bug 描述
|
|
122
|
+
|
|
123
|
+
```markdown
|
|
124
|
+
---
|
|
125
|
+
status: draft
|
|
126
|
+
updated: {YYYY-MM-DD}
|
|
127
|
+
history:
|
|
128
|
+
- "v1: 初始创建"
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
# {Bug 描述}
|
|
132
|
+
|
|
133
|
+
## 发现时间
|
|
134
|
+
{YYYY-MM-DD}
|
|
135
|
+
|
|
136
|
+
## 发现场景
|
|
137
|
+
{在哪发现的,如:线上报障、QA 提测、日常使用}
|
|
138
|
+
|
|
139
|
+
## 复现步骤
|
|
140
|
+
1. {步骤1}
|
|
141
|
+
2. {步骤2}
|
|
142
|
+
3. {步骤3}
|
|
143
|
+
|
|
144
|
+
## 预期行为
|
|
145
|
+
{应该是什么}
|
|
146
|
+
|
|
147
|
+
## 实际行为
|
|
148
|
+
{实际是什么}
|
|
149
|
+
|
|
150
|
+
## 影响范围
|
|
151
|
+
- [ ] 阻塞(无临时方案,必须修复)
|
|
152
|
+
- [ ] 重要(有临时方案但影响体验)
|
|
153
|
+
- [ ] 轻微(不影响主要流程)
|
|
154
|
+
|
|
155
|
+
## 截图/日志(如有)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### Step B3️⃣ 01-root-cause.md:根因分析
|
|
159
|
+
|
|
160
|
+
```markdown
|
|
161
|
+
---
|
|
162
|
+
status: draft
|
|
163
|
+
updated: {YYYY-MM-DD}
|
|
164
|
+
history:
|
|
165
|
+
- "v1: 初始创建"
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
# 根因分析
|
|
169
|
+
|
|
170
|
+
## 复现验证
|
|
171
|
+
- [ ] 已确认复现
|
|
172
|
+
|
|
173
|
+
## 定位过程
|
|
174
|
+
{排查步骤记录}
|
|
175
|
+
|
|
176
|
+
## 根因
|
|
177
|
+
|
|
178
|
+
### 直接原因
|
|
179
|
+
{最接近表象的原因,如:空指针、类型不匹配、边界条件缺失}
|
|
180
|
+
|
|
181
|
+
### 根本原因
|
|
182
|
+
{为什么会引入这个 bug,如:spec 未定义边界情况、对 API 行为假设错误、重构遗漏}
|
|
183
|
+
|
|
184
|
+
## 涉及的代码位置
|
|
185
|
+
- {文件路径}:{行号} — {说明}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### Step B4️⃣ 02-fix-plan.md:修复方案
|
|
189
|
+
|
|
190
|
+
```markdown
|
|
191
|
+
---
|
|
192
|
+
status: draft
|
|
193
|
+
updated: {YYYY-MM-DD}
|
|
194
|
+
history:
|
|
195
|
+
- "v1: 初始创建"
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
# 修复方案
|
|
199
|
+
|
|
200
|
+
## 修复策略
|
|
201
|
+
{描述修复方式}
|
|
202
|
+
|
|
203
|
+
## 文件变更
|
|
204
|
+
| 文件 | 操作 | 说明 |
|
|
205
|
+
|------|------|------|
|
|
206
|
+
| {路径} | 修改/新增 | {说明} |
|
|
207
|
+
|
|
208
|
+
## 影响分析
|
|
209
|
+
- [ ] 修复会影响其他模块?哪些?
|
|
210
|
+
- [ ] 需要补充单元测试?
|
|
211
|
+
- [ ] 需要补充集成测试?
|
|
212
|
+
|
|
213
|
+
## 回滚方案
|
|
214
|
+
{如果修复有问题,如何回滚}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### Step B5️⃣ 修复执行
|
|
218
|
+
|
|
219
|
+
1. 按修复方案修改代码
|
|
220
|
+
2. 验证修复
|
|
221
|
+
3. 提交代码(建议使用 `fix/` 分支)
|
|
222
|
+
|
|
223
|
+
#### Step B6️⃣ 03-fix-done.md:修复验证 & 归档
|
|
224
|
+
|
|
225
|
+
```markdown
|
|
226
|
+
---
|
|
227
|
+
status: archived
|
|
228
|
+
updated: {YYYY-MM-DD}
|
|
229
|
+
history:
|
|
230
|
+
- "v1: 初始创建"
|
|
231
|
+
- "v2: 修复完成"
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
# 修复验证
|
|
235
|
+
|
|
236
|
+
## 修复内容
|
|
237
|
+
{描述实际改了哪些代码}
|
|
238
|
+
|
|
239
|
+
## 验证结果
|
|
240
|
+
- [ ] 复现步骤不再触发
|
|
241
|
+
- [ ] 相关测试通过
|
|
242
|
+
|
|
243
|
+
## 归档到 wiki
|
|
244
|
+
|
|
245
|
+
### wiki 条目
|
|
246
|
+
docs/specx/wiki/fixes/{slug}.md
|
|
247
|
+
|
|
248
|
+
```markdown
|
|
249
|
+
# {Bug 描述}
|
|
250
|
+
|
|
251
|
+
**发现时间:** {YYYY-MM-DD}
|
|
252
|
+
**根因类型:** {逻辑错误 / 边界遗漏 / 类型不匹配 / 并发问题 / 性能 / 其他}
|
|
253
|
+
|
|
254
|
+
## 根因
|
|
255
|
+
{一句话描述根因}
|
|
256
|
+
|
|
257
|
+
## 修复方式
|
|
258
|
+
{一句话描述修复方式}
|
|
259
|
+
|
|
260
|
+
## 涉及文件
|
|
261
|
+
- {文件}:{行号}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 自检清单
|
|
265
|
+
- [ ] bug-report 和 root-cause 已提炼为 wiki 知识
|
|
266
|
+
- [ ] fix-done 状态为 `archived`
|
|
267
|
+
- [ ] 相关文档状态已更新
|
|
268
|
+
- [ ] 代码已提交
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### Step B7️⃣ 更新 fixes 目录索引
|
|
272
|
+
|
|
273
|
+
首次使用 `fixes/` 时,在 `docs/specx/fixes/README.md` 创建索引:
|
|
274
|
+
|
|
275
|
+
```markdown
|
|
276
|
+
# Bug 修复记录
|
|
277
|
+
|
|
278
|
+
| 日期 | 描述 | 根因类型 | 影响范围 | 状态 |
|
|
279
|
+
|------|------|---------|---------|------|
|
|
280
|
+
| {YYYY-MM-DD} | {描述} | {类型} | {阻塞/重要/轻微} | ✅ |
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
后续每次修复完成,在表格顶部插入新行。
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## 公共检查清单
|
|
288
|
+
|
|
289
|
+
修复完成后确认:
|
|
290
|
+
|
|
291
|
+
- [ ] bug 已修复并验证
|
|
292
|
+
- [ ] 根因已明确记录(区分直接原因和根本原因)
|
|
293
|
+
- [ ] 修复不会引入新的问题
|
|
294
|
+
- [ ] 相关测试已补充(如有必要)
|
|
295
|
+
- [ ] 场景 B 已归档到 wiki
|
|
296
|
+
|
|
297
|
+
## 与现有 specx skill 的关系
|
|
298
|
+
|
|
299
|
+
| 当前流程 | 对应动作 |
|
|
300
|
+
|----------|---------|
|
|
301
|
+
| executing-plans 发现代码实现错 → 场景 A 走代码修复 | 直接在当前子需求修复 |
|
|
302
|
+
| 验收发现 spec 遗漏 → 场景 A 走 spec 回退 | 更新 clarify/spec/design |
|
|
303
|
+
| 验收发现 design 遗漏 → 场景 A 走 design 回退 | 更新 design 文档,重新执行 |
|
|
304
|
+
| 用户报线上 bug → 场景 B | 完整流程 |
|
|
305
|
+
| 日常发现历史代码 bug → 场景 B | 完整流程 |
|
|
306
|
+
|
|
307
|
+
## 重要提示
|
|
308
|
+
|
|
309
|
+
- **场景 A 不要过度流程化** — 验收中发现的代码小 bug,定位到根因后快速修掉,不需要走完整 fixes/ 目录。记录到当前子需求即可
|
|
310
|
+
- **场景 B 必须归档到 wiki** — 独立 bug 是项目知识的重要组成部分
|
|
311
|
+
- **区分直接原因和根本原因** — 直接原因是"表象"(空指针),根本原因是"为什么会引入"(spec 未定义边界)
|
|
312
|
+
- **频繁出现同类 bug** → 考虑用 specx-create-rule 建立编码规范
|
|
313
|
+
- **场景判断有歧义时** → 走场景 B(独立 bug 流程更完整,多走流程比漏走好)
|
|
@@ -6,12 +6,13 @@ import path from "path";
|
|
|
6
6
|
import fs from "fs";
|
|
7
7
|
import os from "os";
|
|
8
8
|
import readline from "readline";
|
|
9
|
+
import { execSync } from "child_process";
|
|
9
10
|
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
12
13
|
|
|
13
14
|
// ─── Paths ───────────────────────────────────────────────────────
|
|
14
|
-
const SPECX_HOME = process.env.SPECX_HOME || path.resolve(__dirname);
|
|
15
|
+
const SPECX_HOME = process.env.SPECX_HOME || path.resolve(__dirname, "..");
|
|
15
16
|
const SKILLS_DIR = path.join(SPECX_HOME, "skills");
|
|
16
17
|
|
|
17
18
|
// ─── Registered CLI Targets ──────────────────────────────────────
|
|
@@ -28,6 +29,72 @@ const CLI_TARGETS = [
|
|
|
28
29
|
},
|
|
29
30
|
];
|
|
30
31
|
|
|
32
|
+
// ─── Helpers ──────────────────────────────────────────────────────
|
|
33
|
+
function getPackageName() {
|
|
34
|
+
try {
|
|
35
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(SPECX_HOME, "package.json"), "utf-8"));
|
|
36
|
+
return pkg.name;
|
|
37
|
+
} catch {
|
|
38
|
+
return "@culeo/specx";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Detect installed specx skills in a target dir ───────────────
|
|
43
|
+
function findSpecxSkillsInDir(dir) {
|
|
44
|
+
if (!fs.existsSync(dir)) return [];
|
|
45
|
+
return fs.readdirSync(dir)
|
|
46
|
+
.filter((name) => {
|
|
47
|
+
const skillDir = path.join(dir, name);
|
|
48
|
+
return fs.statSync(skillDir).isDirectory() && fs.existsSync(path.join(skillDir, "SKILL.md")) && name.startsWith("specx-");
|
|
49
|
+
})
|
|
50
|
+
.sort();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── Detect project-level .{claude,codex}/skills/ ────────────────
|
|
54
|
+
function findProjectSpecxDirs(cwd) {
|
|
55
|
+
const results = [];
|
|
56
|
+
for (const target of CLI_TARGETS) {
|
|
57
|
+
const projectDir = path.join(cwd, `.${target.id}`, "skills");
|
|
58
|
+
const skills = findSpecxSkillsInDir(projectDir);
|
|
59
|
+
if (skills.length > 0) {
|
|
60
|
+
results.push({ targetId: target.id, label: target.label, dir: projectDir, skills });
|
|
61
|
+
} else if (fs.existsSync(projectDir)) {
|
|
62
|
+
// Dir exists but no specx skills — still a candidate for coverage
|
|
63
|
+
results.push({ targetId: target.id, label: target.label, dir: projectDir, skills: [] });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── Copy skills from source to a destination ────────────────────
|
|
70
|
+
function copySkillsToDir(srcDir, destDir) {
|
|
71
|
+
const skills = getAvailableSkills();
|
|
72
|
+
let installCount = 0;
|
|
73
|
+
let updateCount = 0;
|
|
74
|
+
for (const skillName of skills) {
|
|
75
|
+
const skillSrc = path.join(srcDir, skillName);
|
|
76
|
+
const skillDest = path.join(destDir, skillName);
|
|
77
|
+
if (!fs.existsSync(skillSrc)) {
|
|
78
|
+
console.log(` ✖ ${skillName}: source not found, skipping`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const existed = fs.existsSync(skillDest);
|
|
82
|
+
if (existed) {
|
|
83
|
+
fs.rmSync(skillDest, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
86
|
+
copyDirSync(skillSrc, skillDest);
|
|
87
|
+
if (existed) {
|
|
88
|
+
console.log(` ↻ ${skillName} updated`);
|
|
89
|
+
updateCount++;
|
|
90
|
+
} else {
|
|
91
|
+
console.log(` ✓ ${skillName} installed`);
|
|
92
|
+
installCount++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { installCount, updateCount };
|
|
96
|
+
}
|
|
97
|
+
|
|
31
98
|
// ─── Interactive Multi-Select ────────────────────────────────────
|
|
32
99
|
function isTTY() {
|
|
33
100
|
return process.stdin.isTTY;
|
|
@@ -205,27 +272,143 @@ program
|
|
|
205
272
|
const finalDir = getFinalDir(targetId, isProject, projectDir);
|
|
206
273
|
|
|
207
274
|
let installCount = 0;
|
|
275
|
+
let updateCount = 0;
|
|
208
276
|
for (const skillName of skillsToInstall) {
|
|
209
277
|
const destDir = path.join(finalDir, skillName);
|
|
210
|
-
if (fs.existsSync(destDir)) {
|
|
211
|
-
console.log(` ○ ${target.label}: ${skillName} already installed`);
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
278
|
const srcDir = path.join(SKILLS_DIR, skillName);
|
|
215
279
|
if (!fs.existsSync(srcDir)) {
|
|
216
280
|
console.log(` ✖ ${skillName}: source not found, skipping`);
|
|
217
281
|
continue;
|
|
218
282
|
}
|
|
283
|
+
const existed = fs.existsSync(destDir);
|
|
284
|
+
if (existed) {
|
|
285
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
286
|
+
}
|
|
219
287
|
fs.mkdirSync(finalDir, { recursive: true });
|
|
220
288
|
copyDirSync(srcDir, destDir);
|
|
221
|
-
|
|
222
|
-
|
|
289
|
+
if (existed) {
|
|
290
|
+
console.log(` ↻ ${target.label}: ${skillName} updated`);
|
|
291
|
+
updateCount++;
|
|
292
|
+
} else {
|
|
293
|
+
console.log(` ✓ ${target.label}: ${skillName} installed`);
|
|
294
|
+
installCount++;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (installCount > 0 || updateCount > 0) {
|
|
299
|
+
const parts = [];
|
|
300
|
+
if (installCount > 0) parts.push(`${installCount} installed`);
|
|
301
|
+
if (updateCount > 0) parts.push(`${updateCount} updated`);
|
|
302
|
+
console.log(`\n✓ ${parts.join(", ")} for ${target.label} (${isProject ? "project" : "user"} level)`);
|
|
223
303
|
}
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// ─── Update Command ──────────────────────────────────────────────
|
|
308
|
+
program
|
|
309
|
+
.command("update")
|
|
310
|
+
.description("Update specx CLI (npm) and re-install skills to all installed locations")
|
|
311
|
+
.action(async () => {
|
|
312
|
+
console.log("🔄 Updating specx CLI...");
|
|
313
|
+
|
|
314
|
+
// Step 1: Update the CLI itself via npm
|
|
315
|
+
const pkgName = getPackageName();
|
|
316
|
+
console.log(` Running: npm update -g ${pkgName}...`);
|
|
317
|
+
try {
|
|
318
|
+
execSync(`npm update -g ${pkgName}`, { stdio: "inherit" });
|
|
319
|
+
console.log(" ✓ npm update complete");
|
|
320
|
+
} catch (e) {
|
|
321
|
+
console.error(" ✖ npm update failed. Try with sudo or check your npm setup.");
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
console.log("\n🔁 Re-installing specx skills...");
|
|
326
|
+
|
|
327
|
+
// Step 2: Re-install to all user-level targets that have specx skills installed
|
|
328
|
+
const userTargets = [];
|
|
329
|
+
for (const target of CLI_TARGETS) {
|
|
330
|
+
const targetDir = target.dir();
|
|
331
|
+
const installedSkills = findSpecxSkillsInDir(targetDir);
|
|
332
|
+
if (installedSkills.length > 0) {
|
|
333
|
+
userTargets.push({ target, skills: installedSkills });
|
|
334
|
+
} else {
|
|
335
|
+
console.log(` ○ ${target.label}: no specx skills installed at user level, skipping`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
224
338
|
|
|
225
|
-
|
|
226
|
-
|
|
339
|
+
if (userTargets.length > 0) {
|
|
340
|
+
console.log("\n📦 User-level targets found:");
|
|
341
|
+
for (const ut of userTargets) {
|
|
342
|
+
console.log(` ${ut.target.label} (${ut.skills.length} skills)`);
|
|
343
|
+
}
|
|
344
|
+
console.log("");
|
|
345
|
+
|
|
346
|
+
for (const ut of userTargets) {
|
|
347
|
+
const destDir = ut.target.dir();
|
|
348
|
+
console.log(`→ ${ut.target.label}...`);
|
|
349
|
+
const result = copySkillsToDir(SKILLS_DIR, destDir);
|
|
350
|
+
const parts = [];
|
|
351
|
+
if (result.installCount > 0) parts.push(`${result.installCount} installed`);
|
|
352
|
+
if (result.updateCount > 0) parts.push(`${result.updateCount} updated`);
|
|
353
|
+
console.log(` ✓ ${parts.join(", ")} for ${ut.target.label} (user level)`);
|
|
227
354
|
}
|
|
355
|
+
} else {
|
|
356
|
+
console.log("\n No user-level specx skills found to update.");
|
|
228
357
|
}
|
|
358
|
+
|
|
359
|
+
// Step 3: Check current working directory for project-level installations
|
|
360
|
+
const cwd = process.cwd();
|
|
361
|
+
const projectTargets = findProjectSpecxDirs(cwd);
|
|
362
|
+
|
|
363
|
+
if (projectTargets.length > 0) {
|
|
364
|
+
console.log(`\n📁 Project-level specx skills detected in ${cwd}:`);
|
|
365
|
+
for (const pt of projectTargets) {
|
|
366
|
+
console.log(` ${pt.label} (${pt.skills.length} skills) — ${pt.dir}`);
|
|
367
|
+
}
|
|
368
|
+
console.log("");
|
|
369
|
+
|
|
370
|
+
if (isTTY()) {
|
|
371
|
+
// Interactive: let user choose which project locations to update
|
|
372
|
+
const items = projectTargets.map((pt) => ({
|
|
373
|
+
id: pt.targetId,
|
|
374
|
+
label: `${pt.label} (${pt.skills.length || 0} skills installed)`,
|
|
375
|
+
detected: true,
|
|
376
|
+
}));
|
|
377
|
+
const selected = await interactiveSelect(
|
|
378
|
+
items,
|
|
379
|
+
"📁 Select project-level locations to update:",
|
|
380
|
+
projectTargets.map((pt) => pt.targetId),
|
|
381
|
+
false
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
for (const pt of projectTargets) {
|
|
385
|
+
if (!selected.includes(pt.targetId)) {
|
|
386
|
+
console.log(` ○ ${pt.label}: skipped`);
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
console.log(`→ ${pt.label}...`);
|
|
390
|
+
const result = copySkillsToDir(SKILLS_DIR, pt.dir);
|
|
391
|
+
const parts = [];
|
|
392
|
+
if (result.installCount > 0) parts.push(`${result.installCount} installed`);
|
|
393
|
+
if (result.updateCount > 0) parts.push(`${result.updateCount} updated`);
|
|
394
|
+
console.log(` ✓ ${parts.join(", ")} for ${pt.label} (project level)`);
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
// Non-interactive: update all
|
|
398
|
+
for (const pt of projectTargets) {
|
|
399
|
+
console.log(`→ ${pt.label}...`);
|
|
400
|
+
const result = copySkillsToDir(SKILLS_DIR, pt.dir);
|
|
401
|
+
const parts = [];
|
|
402
|
+
if (result.installCount > 0) parts.push(`${result.installCount} installed`);
|
|
403
|
+
if (result.updateCount > 0) parts.push(`${result.updateCount} updated`);
|
|
404
|
+
console.log(` ✓ ${parts.join(", ")} for ${pt.label} (project level)`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
console.log("\n No project-level specx skills detected in current directory.");
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
console.log("\n✓ Update complete!");
|
|
229
412
|
});
|
|
230
413
|
|
|
231
414
|
// ─── Uninstall Command ───────────────────────────────────────────
|