@gepeiyu/smart 0.1.1
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/LICENSE +21 -0
- package/README.md +94 -0
- package/SKILL.md +269 -0
- package/bin/smart.js +3 -0
- package/package.json +39 -0
- package/reference/file-structure.md +61 -0
- package/reference/issue-lifecycle.md +96 -0
- package/reference/smart-yaml-fields.md +66 -0
- package/scripts/smart-archive.sh +138 -0
- package/scripts/smart-env.sh +145 -0
- package/scripts/smart-guard.sh +214 -0
- package/scripts/smart-handoff.sh +127 -0
- package/scripts/smart-state.sh +365 -0
- package/smart-archive/SKILL.md +102 -0
- package/smart-build/SKILL.md +317 -0
- package/smart-design/SKILL.md +264 -0
- package/smart-hotfix/SKILL.md +200 -0
- package/smart-issue/SKILL.md +259 -0
- package/smart-tweak/SKILL.md +176 -0
- package/smart-verify/SKILL.md +232 -0
- package/src/deploy.js +47 -0
- package/src/index.js +54 -0
- package/src/init.js +80 -0
- package/src/platforms.js +20 -0
- package/src/status.js +31 -0
- package/src/uninstall.js +31 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: smart-verify
|
|
3
|
+
description: "Smart 阶段 4:验证与收尾。用 /smart-verify 调用。验证实现符合设计,处理开发分支。"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Smart 阶段 4:验证与收尾(Verify)
|
|
7
|
+
|
|
8
|
+
## 前置条件
|
|
9
|
+
|
|
10
|
+
- 代码已提交(阶段 3 完成)
|
|
11
|
+
- tasks.md 全部任务已完成
|
|
12
|
+
|
|
13
|
+
## 步骤
|
|
14
|
+
|
|
15
|
+
### 0a. 输出语言约束
|
|
16
|
+
|
|
17
|
+
验证报告和分支处理说明必须使用触发本次工作流的用户请求语言。
|
|
18
|
+
|
|
19
|
+
### 0b. 入口状态验证(Entry Check)
|
|
20
|
+
|
|
21
|
+
执行入口验证:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
SMART_ENV="${SMART_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/smart/scripts/smart-env.sh' -type f -print -quit 2>/dev/null)}"
|
|
25
|
+
if [ -z "$SMART_ENV" ]; then
|
|
26
|
+
echo "ERROR: smart-env.sh not found. Ensure the smart skill is installed." >&2
|
|
27
|
+
return 1
|
|
28
|
+
fi
|
|
29
|
+
. "$SMART_ENV"
|
|
30
|
+
"$SMART_BASH" "$SMART_STATE" check <change-name> verify
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
验证通过后继续 Step 1。验证失败时脚本会输出具体失败原因。
|
|
34
|
+
|
|
35
|
+
**幂等性**:verify 阶段所有检查可安全重复执行。如 `verify_result` 已为 `pass` 且 `branch_status` 已为 `handled`,说明验证已完成,直接执行 guard 流转。如 `verify_result` 为 `pending`,从头开始验证。
|
|
36
|
+
|
|
37
|
+
### 1. 改动规模评估
|
|
38
|
+
|
|
39
|
+
执行规模评估:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
"$SMART_BASH" "$SMART_STATE" scale <change-name>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
脚本自动统计任务数、增量规格数、变更文件数,判断使用 light 或 full 验证模式,并设置 verify_mode 字段。判定规则(满足任一即 full):任务数 > 3、delta spec 能力数 > 1、变更文件数 > 4。
|
|
46
|
+
|
|
47
|
+
验证开始前,按 `smart/reference/dirty-worktree.md` 协议检查并处理未提交改动。verify 阶段的特殊处理:
|
|
48
|
+
|
|
49
|
+
1. 若 dirty diff 属于当前 change 且涉及实现、测试、tasks、delta spec 或 design doc 变更,不在 verify 阶段直接修复或提交;报告失败项并进入 Step 1b 的验证失败决策阻塞点
|
|
50
|
+
2. 若 dirty diff 只是 verify 本阶段产物(例如验证报告草稿、分支处理记录),可继续在 verify 阶段完成并记录状态
|
|
51
|
+
3. 若 dirty diff 已实现但 tasks.md 未勾选,视为 build 状态滞后;报告失败项并进入 Step 1b,由用户决定回退修复或接受偏差
|
|
52
|
+
|
|
53
|
+
用户选择修复后,才允许回退到 build 阶段:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# 仅在用户确认修复后执行
|
|
57
|
+
"$SMART_BASH" "$SMART_STATE" transition <change-name> verify-fail
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
注意:verify-fail 回退到 build 时 `branch_status` 不会被重置。如果首次 verify 已完成分支处理,修复后再次进入 verify 时跳过已完成的分支处理步骤,直接使用 `"$SMART_BASH" "$SMART_STATE" set <change-name> branch_status handled` 保留原有分支处理结果。
|
|
61
|
+
|
|
62
|
+
注意:如果 build 阶段每个任务都已提交,脚本基于工作区 diff 的文件数可能低估改动规模。此时必须读取 plan 文件头的 `base-ref` 并用提交区间复核:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
PLAN=$("$SMART_BASH" "$SMART_STATE" get <change-name> plan)
|
|
66
|
+
BASE_REF=$(grep '^base-ref:' "$PLAN" 2>/dev/null | head -1 | sed 's/^base-ref: *//')
|
|
67
|
+
git diff --stat "$BASE_REF"...HEAD
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
若提交区间显示改动超过轻量阈值(> 4 个文件、跨模块协调、或 delta spec 超过 1 个 capability),手动设置为完整验证:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
"$SMART_BASH" "$SMART_STATE" set <change-name> verify_mode full
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**覆盖机制**:如 agent 或用户认为自动评估结果不合适,可随时通过 `"$SMART_BASH" "$SMART_STATE" set <change-name> verify_mode <light|full>` 手动覆盖。
|
|
77
|
+
|
|
78
|
+
### 1b. 验证失败决策(阻塞点)
|
|
79
|
+
|
|
80
|
+
验证不通过时**必须按 `smart/reference/decision-point.md` 的协议暂停并等待用户决定修复或接受偏差**。不得自动运行 `"$SMART_BASH" "$SMART_STATE" transition <change-name> verify-fail`,也不得自动调用 `/smart-build`。
|
|
81
|
+
|
|
82
|
+
暂停时必须列出:
|
|
83
|
+
- 失败项
|
|
84
|
+
- 是否属于 CRITICAL 或 IMPORTANT(构建失败、测试失败、安全问题、核心验收场景失败、简化代码审查发现的正确性/安全/边界问题)
|
|
85
|
+
- 推荐处理方式
|
|
86
|
+
|
|
87
|
+
**不确定性原则**:无法确定严重程度时,降级处理(SUGGESTION > WARNING > CRITICAL)。仅对构建失败、测试失败、安全问题使用 CRITICAL;模糊或不确定的问题标为 WARNING 或 SUGGESTION。
|
|
88
|
+
|
|
89
|
+
用户选择后按以下方式继续:
|
|
90
|
+
- **全部修复**:运行 `"$SMART_BASH" "$SMART_STATE" transition <change-name> verify-fail`,然后调用 `/smart-build` 修复
|
|
91
|
+
- **逐项处理**:CRITICAL 或 IMPORTANT 失败项必须修复;WARNING/SUGGESTION 失败项可选择接受偏差,但必须在验证报告中记录接受原因和影响范围。若存在任何 CRITICAL 或 IMPORTANT 失败项,不允许跳过修复直接全部接受
|
|
92
|
+
|
|
93
|
+
### 2. 产物上下文加载(Hash 按需读)
|
|
94
|
+
|
|
95
|
+
验证需要读取 OpenSpec 产物时,先检查产物是否自 design 阶段以来发生变化:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
RECORDED_HASH=$("$SMART_BASH" "$SMART_STATE" get <change-name> handoff_hash)
|
|
99
|
+
CURRENT_HASH=$("$SMART_HANDOFF" <change-name> --hash-only 2>/dev/null || echo "")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- 若 `RECORDED_HASH` = `CURRENT_HASH` 且均非空且均非 `null`:OpenSpec 产物未变化,**tasks.md 无需重新读取全文**(用 `grep -c '\- \[ \]' tasks.md` 确认完成数即可)。proposal.md、design.md、delta spec 仍需读取用于对照检查。
|
|
103
|
+
- 若 `RECORDED_HASH` 为空、为 `null`、或与 `CURRENT_HASH` 不一致:产物已变化或 hash 未记录,正常读取所有所需文件全文。
|
|
104
|
+
|
|
105
|
+
此优化仅跳过 tasks.md 的重复全文读取。proposal.md 和 design.md 包含验证检查项所需的完整上下文,不得因 hash 匹配而跳过。
|
|
106
|
+
|
|
107
|
+
**立即执行:** 使用 Skill 工具加载 Superpowers `verification-before-completion` 技能。禁止跳过此步骤。
|
|
108
|
+
|
|
109
|
+
技能加载后,按 verify_mode 分支执行:
|
|
110
|
+
|
|
111
|
+
### 2a. 轻量验证(小改动)
|
|
112
|
+
|
|
113
|
+
按以下 6 项进行检查:
|
|
114
|
+
|
|
115
|
+
1. tasks.md 全部任务已完成 `[x]`
|
|
116
|
+
2. 改动文件与 tasks.md 描述一致(`git diff --stat` / `git diff --cached --stat` / `git diff --stat <base-ref>...HEAD` 对照 tasks 内容)
|
|
117
|
+
3. 编译通过(执行项目对应的构建命令,如 `npm run build`、`mvn compile`、`cargo build` 等)
|
|
118
|
+
4. 相关测试通过
|
|
119
|
+
5. 无明显安全问题(无硬编码密钥、无新增 unsafe 操作)
|
|
120
|
+
6. 代码审查策略:当 `review_mode: standard` 或 `thorough` 时,必须使用 Skill 工具加载 Superpowers `requesting-code-review` 技能,请求只检查正确性、安全、边界条件的轻量代码审查;当 `review_mode: off` 时跳过自动代码审查,并在验证报告中记录跳过原因
|
|
121
|
+
|
|
122
|
+
简化代码审查的输入应限定为本次改动 diff、tasks.md 和必要的测试结果;审查范围只覆盖实现正确性、安全风险和边界条件,不执行 spec 覆盖率、Design Doc 一致性或漂移检查。若审查发现 CRITICAL 或 IMPORTANT 问题,按验证失败处理并进入 Step 1b。`review_mode: off` 只跳过自动 code review,不跳过构建、测试、安全检查或异常调试协议。
|
|
123
|
+
|
|
124
|
+
**通过标准**:6 项全部 OK,无 CRITICAL 或 IMPORTANT 问题。
|
|
125
|
+
|
|
126
|
+
**不通过时**:报告失败项,进入 Step 1b 的验证失败决策阻塞点。用户选择修复后,才执行以下命令记录失败并回退到 build 阶段,然后调用 `/smart-build` 修复:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# 仅在用户确认修复后执行
|
|
130
|
+
"$SMART_BASH" "$SMART_STATE" transition <change-name> verify-fail
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**报告格式**:简表列出 6 项检查结果 + PASS/FAIL。
|
|
134
|
+
|
|
135
|
+
**跳过项**(不在轻量验证中检查):
|
|
136
|
+
- spec scenario 覆盖率
|
|
137
|
+
- design doc 一致性深度比对
|
|
138
|
+
- 不影响正确性、安全、边界条件的 code pattern consistency 建议
|
|
139
|
+
- delta spec 与 design doc 漂移检测
|
|
140
|
+
|
|
141
|
+
### 2b. 完整验证(大改动)
|
|
142
|
+
|
|
143
|
+
当规模评估结果为"大"时:
|
|
144
|
+
|
|
145
|
+
**立即执行:** 使用 Skill 工具加载 `openspec-verify-change` 技能。禁止跳过此步骤。
|
|
146
|
+
|
|
147
|
+
技能加载后,按其指引验证。检查项:
|
|
148
|
+
1. tasks.md 全部任务已完成(`[x]`)
|
|
149
|
+
2. 实现符合 `openspec/changes/<name>/design.md` 高层设计决策
|
|
150
|
+
3. 实现符合 Design Doc(`docs/superpowers/specs/` 下的技术设计文档)
|
|
151
|
+
4. 能力规格场景全部通过
|
|
152
|
+
5. proposal.md 目标已满足
|
|
153
|
+
6. delta spec 与 design doc 无矛盾(若 Build 阶段有增量修改 spec,检查 design doc 是否有对应记录)
|
|
154
|
+
7. `docs/superpowers/specs/` 关联的设计文档可定位(文件存在且与当前 change 相关)
|
|
155
|
+
|
|
156
|
+
验证不通过时:报告缺失项,进入 Step 1b 的验证失败决策阻塞点。用户选择修复后,才执行以下命令记录失败并回退到 build 阶段,然后调用 `/smart-build` 补充:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# 仅在用户确认修复后执行
|
|
160
|
+
"$SMART_BASH" "$SMART_STATE" transition <change-name> verify-fail
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Spec 漂移处理**(用户决策点):
|
|
164
|
+
- 若检查项 6 发现矛盾(delta spec 有内容但 design doc 未体现),**必须使用当前平台可用的用户输入/确认机制以单选题形式暂停并等待用户选择处理方式**,不得自动选择。选项:
|
|
165
|
+
- 选项 A:在 design doc 追加 "Implementation Divergence" 节记录偏差原因。选项 A 属于 verify 阶段允许产物;写入后不得因该 design doc 变更再次触发 Step 1b dirty-worktree 决策
|
|
166
|
+
- 选项 B:用户选择 B 后,运行 `"$SMART_BASH" "$SMART_STATE" transition <change-name> verify-fail`,然后调用 `/smart-build`;由 `/smart-build` 的 Spec 增量更新规则加载 Superpowers `brainstorming` 更新 Design Doc + delta spec
|
|
167
|
+
- 选项 C:确认偏差可接受,继续验证(归档时 design doc 将标记为 `superseded-by-main-spec`)
|
|
168
|
+
|
|
169
|
+
### 3. 收尾(Superpowers)
|
|
170
|
+
|
|
171
|
+
**立即执行:** 使用 Skill 工具加载 Superpowers `finishing-a-development-branch` 技能。禁止跳过此步骤。
|
|
172
|
+
|
|
173
|
+
如 Superpowers `finishing-a-development-branch` 技能不可用,停止流程并提示安装或启用 Superpowers 技能,不要用普通对话替代该步骤。
|
|
174
|
+
|
|
175
|
+
技能加载后,按其指引收尾。分支处理选项:
|
|
176
|
+
1. 本地合并到主分支
|
|
177
|
+
2. 推送并创建 PR
|
|
178
|
+
3. 保持分支(稍后处理)
|
|
179
|
+
4. 丢弃工作
|
|
180
|
+
|
|
181
|
+
这是用户决策点。**必须按 `smart/reference/decision-point.md` 的协议暂停并等待用户选择分支处理方式**,不得根据推荐、默认值或当前分支状态自行选择。只有在用户完成选择且对应操作完成后,才允许写入 `branch_status: handled`。
|
|
182
|
+
|
|
183
|
+
**确认项**:
|
|
184
|
+
- 全部测试通过
|
|
185
|
+
- 无硬编码密钥或安全问题
|
|
186
|
+
|
|
187
|
+
### 4. 记录验证证据
|
|
188
|
+
|
|
189
|
+
验证报告必须落盘,并在 `.smart.yaml` 中记录;分支处理完成后也必须写入状态字段。不要手动设置 `verify_result: pass`,由阶段守卫 `--apply` 推进。
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
mkdir -p docs/superpowers/reports
|
|
193
|
+
# 将本次验证结论写入报告文件,例如:
|
|
194
|
+
# docs/superpowers/reports/YYYY-MM-DD-<change-name>-verify.md
|
|
195
|
+
|
|
196
|
+
"$SMART_BASH" "$SMART_STATE" set <change-name> verification_report docs/superpowers/reports/YYYY-MM-DD-<change-name>-verify.md
|
|
197
|
+
"$SMART_BASH" "$SMART_STATE" set <change-name> branch_status handled
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## 退出条件
|
|
201
|
+
|
|
202
|
+
- 验证报告通过
|
|
203
|
+
- 分支已处理
|
|
204
|
+
- `.smart.yaml` 中 `verification_report` 指向已存在的验证报告文件
|
|
205
|
+
- `.smart.yaml` 中 `branch_status: handled`
|
|
206
|
+
- **阶段守卫**:运行 `"$SMART_BASH" "$SMART_GUARD" <change-name> verify --apply`,全部 PASS 后由守卫通过 `smart-state transition verify-pass` 推进到 `phase: archive`(此步骤更新 `phase` 字段,与 `auto_transition` 无关)
|
|
207
|
+
|
|
208
|
+
验证和分支处理均完成后,运行阶段守卫推进 phase(此步骤与 `auto_transition` 无关):
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
"$SMART_BASH" "$SMART_GUARD" <change-name> verify --apply
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
状态文件自动更新为 `phase: archive`、`verify_result: pass`、`verified_at: YYYY-MM-DD`。
|
|
215
|
+
|
|
216
|
+
## 上下文压缩恢复
|
|
217
|
+
|
|
218
|
+
按 `smart/reference/context-recovery.md` 执行,phase 参数为 `verify`。
|
|
219
|
+
|
|
220
|
+
## 自动衔接下一阶段
|
|
221
|
+
|
|
222
|
+
按 `smart/reference/auto-transition.md` 执行。关键命令:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
"$SMART_BASH" "$SMART_STATE" next <change-name>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- `NEXT: auto` → 调用 `SKILL` 指向的 skill 进入下一阶段
|
|
229
|
+
- `NEXT: manual` → 不要调用下一 skill,按 `HINT` 提示用户手动运行 `/<SKILL>`
|
|
230
|
+
- `NEXT: done` → 流程已完成,无需继续
|
|
231
|
+
|
|
232
|
+
注意:无论 `NEXT` 为 `auto` 还是 `manual`,`smart-archive` 进入后必须先执行归档前最终确认阻塞点,等待用户明确选择「确认归档」后才允许运行归档脚本。不得因为验证已通过就自动归档。
|
package/src/deploy.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { cpSync, mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { SUBSKILLS } from './platforms.js';
|
|
5
|
+
|
|
6
|
+
// package root: skill files (SKILL.md, smart-*/, scripts/, reference/) live at the package root
|
|
7
|
+
const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
+
|
|
9
|
+
function stripFrontmatter(s) {
|
|
10
|
+
if (s.startsWith('---\n')) {
|
|
11
|
+
const e = s.indexOf('\n---\n', 4);
|
|
12
|
+
if (e !== -1) return s.slice(e + 5);
|
|
13
|
+
}
|
|
14
|
+
return s;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function genCommand(cmd, skill, src) {
|
|
18
|
+
const body = stripFrontmatter(readFileSync(src, 'utf8'));
|
|
19
|
+
return `---\ndescription: Run the ${skill} Smart workflow\n---\n\nEquivalent Smart skill: \`${skill}\`\nCommand name: \`/${cmd}\`\n\nUse the invocation arguments below as the user input for this workflow:\n\n\`\`\`text\n$ARGUMENTS\n\`\`\`\n\n${body}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function deployToPlatform(p, cwd) {
|
|
23
|
+
const toolDir = join(cwd, p.dir);
|
|
24
|
+
const smartDir = join(toolDir, p.skillsSubdir, 'smart');
|
|
25
|
+
mkdirSync(join(smartDir, 'scripts'), { recursive: true });
|
|
26
|
+
mkdirSync(join(smartDir, 'reference'), { recursive: true });
|
|
27
|
+
cpSync(join(PKG_ROOT, 'SKILL.md'), join(smartDir, 'SKILL.md'));
|
|
28
|
+
cpSync(join(PKG_ROOT, 'scripts'), join(smartDir, 'scripts'), { recursive: true });
|
|
29
|
+
cpSync(join(PKG_ROOT, 'reference'), join(smartDir, 'reference'), { recursive: true });
|
|
30
|
+
for (const sub of SUBSKILLS) {
|
|
31
|
+
const src = join(PKG_ROOT, sub, 'SKILL.md');
|
|
32
|
+
if (existsSync(src)) {
|
|
33
|
+
const d = join(toolDir, p.skillsSubdir, sub);
|
|
34
|
+
mkdirSync(d, { recursive: true });
|
|
35
|
+
cpSync(src, join(d, 'SKILL.md'));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (p.commandsDir) {
|
|
39
|
+
const cmds = join(toolDir, p.commandsDir);
|
|
40
|
+
mkdirSync(cmds, { recursive: true });
|
|
41
|
+
writeFileSync(join(cmds, 'smart.md'), genCommand('smart', 'smart', join(PKG_ROOT, 'SKILL.md')));
|
|
42
|
+
for (const sub of SUBSKILLS) {
|
|
43
|
+
const src = join(PKG_ROOT, sub, 'SKILL.md');
|
|
44
|
+
if (existsSync(src)) writeFileSync(join(cmds, `${sub}.md`), genCommand(sub, sub, src));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { cmdInit } from './init.js';
|
|
2
|
+
import { cmdStatus } from './status.js';
|
|
3
|
+
import { cmdUninstall } from './uninstall.js';
|
|
4
|
+
|
|
5
|
+
export const VERSION = '0.1.0';
|
|
6
|
+
|
|
7
|
+
export const HELP = `Smart — GitHub Issue 驱动的 OpenSpec 变更流程 agent skill
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
smart init [path] 初始化(检测平台/部署 skill/装依赖/建工作目录)
|
|
11
|
+
smart status [path] 显示活跃 change 与下一步
|
|
12
|
+
smart uninstall [path] 卸载 Smart skill
|
|
13
|
+
smart --version | -v
|
|
14
|
+
smart --help | -h
|
|
15
|
+
|
|
16
|
+
Init options:
|
|
17
|
+
--yes 非交互模式(部署到所有已检测平台)
|
|
18
|
+
--scope <project|global> 安装范围(v1 仅 project)
|
|
19
|
+
--skip-existing 跳过已安装组件
|
|
20
|
+
--overwrite 覆盖已安装组件
|
|
21
|
+
--no-deps 跳过依赖安装
|
|
22
|
+
|
|
23
|
+
Uninstall options:
|
|
24
|
+
--force 跳过确认
|
|
25
|
+
|
|
26
|
+
前置:Node.js 20+、gh CLI 已登录、Bash(Windows 用 Git Bash)。`;
|
|
27
|
+
|
|
28
|
+
export function run() {
|
|
29
|
+
const argv = process.argv.slice(2);
|
|
30
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') { console.log(HELP); return; }
|
|
31
|
+
if (argv[0] === '-v' || argv[0] === '--version') { console.log(VERSION); return; }
|
|
32
|
+
const sub = argv[0];
|
|
33
|
+
const rest = argv.slice(1);
|
|
34
|
+
const opts = { yes: false, scope: null, skipExisting: false, overwrite: false, force: false, noDeps: false };
|
|
35
|
+
const positional = [];
|
|
36
|
+
for (let i = 0; i < rest.length; i++) {
|
|
37
|
+
const a = rest[i];
|
|
38
|
+
if (a === '--yes') opts.yes = true;
|
|
39
|
+
else if (a === '--skip-existing') opts.skipExisting = true;
|
|
40
|
+
else if (a === '--overwrite') opts.overwrite = true;
|
|
41
|
+
else if (a === '--force') opts.force = true;
|
|
42
|
+
else if (a === '--no-deps') opts.noDeps = true;
|
|
43
|
+
else if (a === '--scope') opts.scope = rest[++i];
|
|
44
|
+
else if (a.startsWith('--scope=')) opts.scope = a.slice(8);
|
|
45
|
+
else positional.push(a);
|
|
46
|
+
}
|
|
47
|
+
const path = positional[0] || process.cwd();
|
|
48
|
+
switch (sub) {
|
|
49
|
+
case 'init': return cmdInit(path, opts);
|
|
50
|
+
case 'status': return cmdStatus(path, opts);
|
|
51
|
+
case 'uninstall': return cmdUninstall(path, opts);
|
|
52
|
+
default: console.error(`未知命令: ${sub}\n\n${HELP}`); process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/init.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { mkdirSync, existsSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createInterface } from 'node:readline/promises';
|
|
4
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import { detectPlatforms, PLATFORMS } from './platforms.js';
|
|
7
|
+
import { deployToPlatform } from './deploy.js';
|
|
8
|
+
|
|
9
|
+
const rl = () => createInterface({ input, output });
|
|
10
|
+
const ask = async (r, q) => (await r.question(q)).trim();
|
|
11
|
+
|
|
12
|
+
function runCmd(cmd, args, cwd) {
|
|
13
|
+
return new Promise(resolve => {
|
|
14
|
+
const c = spawn(cmd, args, { cwd, stdio: 'inherit', shell: true });
|
|
15
|
+
c.on('close', code => resolve(code === 0));
|
|
16
|
+
c.on('error', () => resolve(false));
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function cmdInit(targetPath, opts) {
|
|
21
|
+
const cwd = targetPath || process.cwd();
|
|
22
|
+
console.log(`Smart init — ${cwd}\n`);
|
|
23
|
+
|
|
24
|
+
// 1. detect platforms
|
|
25
|
+
let platforms = detectPlatforms(cwd);
|
|
26
|
+
if (platforms.length === 0) {
|
|
27
|
+
console.log('未检测到 AI 平台目录。可选平台:');
|
|
28
|
+
PLATFORMS.forEach((p, i) => console.log(` ${i + 1}. ${p.name} (${p.dir})`));
|
|
29
|
+
const r = rl();
|
|
30
|
+
const ans = opts.yes ? '' : await ask(r, '\n选择要初始化的平台编号(逗号分隔,回车=全部):');
|
|
31
|
+
r.close();
|
|
32
|
+
if (ans === '') platforms = [...PLATFORMS];
|
|
33
|
+
else {
|
|
34
|
+
const idxs = ans.split(',').map(s => parseInt(s.trim(), 10) - 1).filter(i => i >= 0 && i < PLATFORMS.length);
|
|
35
|
+
platforms = idxs.map(i => PLATFORMS[i]);
|
|
36
|
+
}
|
|
37
|
+
for (const p of platforms) mkdirSync(join(cwd, p.dir), { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
if (platforms.length === 0) { console.log('未选择平台,退出。'); return; }
|
|
40
|
+
console.log('目标平台:' + platforms.map(p => p.name).join(', ') + '\n');
|
|
41
|
+
|
|
42
|
+
// 2. deploy skills
|
|
43
|
+
for (const p of platforms) {
|
|
44
|
+
deployToPlatform(p, cwd);
|
|
45
|
+
console.log(` ✓ ${p.name}: skill 部署${p.commandsDir ? ' + commands' : ''}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. install deps (openspec + superpowers)
|
|
49
|
+
let installDeps = true;
|
|
50
|
+
if (opts.noDeps) {
|
|
51
|
+
installDeps = false;
|
|
52
|
+
} else {
|
|
53
|
+
const r = rl();
|
|
54
|
+
installDeps = opts.yes ? true : (await ask(r, '\n安装依赖 OpenSpec + Superpowers?[Y/n] ')).toLowerCase() !== 'n';
|
|
55
|
+
r.close();
|
|
56
|
+
}
|
|
57
|
+
if (installDeps) {
|
|
58
|
+
console.log('\n安装 OpenSpec CLI...');
|
|
59
|
+
if (await runCmd('npm', ['install', '--save-dev', '@fission-ai/openspec'], cwd)) console.log(' ✓ OpenSpec');
|
|
60
|
+
else console.log(' ! 失败,请手动: npm i -D @fission-ai/openspec');
|
|
61
|
+
console.log('安装 Superpowers (npx skills add obra/superpowers)...');
|
|
62
|
+
if (await runCmd('npx', ['--yes', 'skills', 'add', 'obra/superpowers'], cwd)) console.log(' ✓ Superpowers');
|
|
63
|
+
else console.log(' ! 失败,请手动: npx skills add obra/superpowers');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 4. work dirs + project config
|
|
67
|
+
mkdirSync(join(cwd, 'docs/superpowers/specs'), { recursive: true });
|
|
68
|
+
mkdirSync(join(cwd, 'docs/superpowers/plans'), { recursive: true });
|
|
69
|
+
mkdirSync(join(cwd, 'docs/superpowers/reports'), { recursive: true });
|
|
70
|
+
const cfg = join(cwd, '.smart/config.yaml');
|
|
71
|
+
if (!existsSync(cfg)) {
|
|
72
|
+
mkdirSync(join(cwd, '.smart'), { recursive: true });
|
|
73
|
+
writeFileSync(cfg, 'context_compression: off\nreview_mode: off\nauto_transition: true\n');
|
|
74
|
+
}
|
|
75
|
+
console.log(' ✓ 工作目录 docs/superpowers/{specs,plans,reports} + .smart/config.yaml');
|
|
76
|
+
|
|
77
|
+
console.log('\nSmart 初始化完成。');
|
|
78
|
+
console.log('下一步:在目标平台用 /smart-issue <描述> 或 /smart-issue #<id> 开始一条 change。');
|
|
79
|
+
console.log('前置:gh CLI 已登录(gh auth status)、Bash(Windows 用 Git Bash)。');
|
|
80
|
+
}
|
package/src/platforms.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
// v1: 8 主流平台(comet 支持 29,后续按需扩展)
|
|
5
|
+
export const PLATFORMS = [
|
|
6
|
+
{ id: 'opencode', name: 'OpenCode', dir: '.opencode', skillsSubdir: 'skills', commandsDir: 'commands' },
|
|
7
|
+
{ id: 'claude', name: 'Claude Code', dir: '.claude', skillsSubdir: 'skills', commandsDir: null },
|
|
8
|
+
{ id: 'codex', name: 'Codex', dir: '.codex', skillsSubdir: 'skills', commandsDir: null },
|
|
9
|
+
{ id: 'cursor', name: 'Cursor', dir: '.cursor', skillsSubdir: 'skills', commandsDir: null },
|
|
10
|
+
{ id: 'windsurf', name: 'Windsurf', dir: '.windsurf', skillsSubdir: 'skills', commandsDir: null },
|
|
11
|
+
{ id: 'cline', name: 'Cline', dir: '.cline', skillsSubdir: 'skills', commandsDir: null },
|
|
12
|
+
{ id: 'roo', name: 'RooCode', dir: '.roo', skillsSubdir: 'skills', commandsDir: null },
|
|
13
|
+
{ id: 'gemini', name: 'Gemini CLI', dir: '.gemini', skillsSubdir: 'skills', commandsDir: null },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export const SUBSKILLS = ['smart-issue', 'smart-design', 'smart-build', 'smart-verify', 'smart-archive', 'smart-hotfix', 'smart-tweak'];
|
|
17
|
+
|
|
18
|
+
export function detectPlatforms(cwd) {
|
|
19
|
+
return PLATFORMS.filter(p => existsSync(join(cwd, p.dir)));
|
|
20
|
+
}
|
package/src/status.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
function getField(file, field) {
|
|
5
|
+
const line = readFileSync(file, 'utf8').split('\n').find(l => l.startsWith(field + ':'));
|
|
6
|
+
return line ? line.replace(new RegExp('^' + field + ': *'), '').trim() : '';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function cmdStatus(targetPath) {
|
|
10
|
+
const cwd = targetPath || process.cwd();
|
|
11
|
+
const dir = join(cwd, 'openspec/changes');
|
|
12
|
+
if (!existsSync(dir)) {
|
|
13
|
+
console.log('无 openspec/changes/。先 smart init 并用 /smart-issue 创建 change。');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const active = readdirSync(dir, { withFileTypes: true })
|
|
17
|
+
.filter(d => d.isDirectory() && d.name !== 'archive' && existsSync(join(dir, d.name, '.smart.yaml')))
|
|
18
|
+
.map(d => d.name);
|
|
19
|
+
if (active.length === 0) {
|
|
20
|
+
console.log('无活跃 change。用 /smart-issue <描述> 或 /smart-issue #<id> 开始。');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
for (const name of active) {
|
|
24
|
+
const s = join(dir, name, '.smart.yaml');
|
|
25
|
+
const phase = getField(s, 'phase');
|
|
26
|
+
const wf = getField(s, 'workflow');
|
|
27
|
+
const issue = getField(s, 'issue_number');
|
|
28
|
+
console.log(`\n● ${name} [phase: ${phase || '?'} | workflow: ${wf || '?'} | issue: ${issue || 'null'}]`);
|
|
29
|
+
}
|
|
30
|
+
console.log('\n下一步:在平台用 /smart 继续(自动检测阶段并 dispatch)。');
|
|
31
|
+
}
|
package/src/uninstall.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { rmSync, existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createInterface } from 'node:readline/promises';
|
|
4
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
5
|
+
import { PLATFORMS, SUBSKILLS } from './platforms.js';
|
|
6
|
+
|
|
7
|
+
export async function cmdUninstall(targetPath, opts) {
|
|
8
|
+
const cwd = targetPath || process.cwd();
|
|
9
|
+
if (!opts.force) {
|
|
10
|
+
const r = createInterface({ input, output });
|
|
11
|
+
const ans = (await r.question('将移除所有平台的 Smart skill(保留 openspec/changes、docs、.smart 等用户数据)。继续?[y/N] ')).toLowerCase();
|
|
12
|
+
r.close();
|
|
13
|
+
if (ans !== 'y') { console.log('已取消。'); return; }
|
|
14
|
+
}
|
|
15
|
+
for (const p of PLATFORMS) {
|
|
16
|
+
const toolDir = join(cwd, p.dir);
|
|
17
|
+
if (!existsSync(toolDir)) continue;
|
|
18
|
+
rmSync(join(toolDir, p.skillsSubdir, 'smart'), { recursive: true, force: true });
|
|
19
|
+
for (const sub of SUBSKILLS) rmSync(join(toolDir, p.skillsSubdir, sub), { recursive: true, force: true });
|
|
20
|
+
if (p.commandsDir) {
|
|
21
|
+
const cmds = join(toolDir, p.commandsDir);
|
|
22
|
+
if (existsSync(cmds)) {
|
|
23
|
+
for (const f of readdirSync(cmds).filter(f => f.startsWith('smart') && f.endsWith('.md'))) {
|
|
24
|
+
rmSync(join(cmds, f));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
console.log(` ✓ ${p.name}: Smart skill 已移除`);
|
|
29
|
+
}
|
|
30
|
+
console.log('\nSmart skill 已卸载。openspec/changes/、docs/、.smart/ 等用户数据保留。');
|
|
31
|
+
}
|