@kafka0102/onespec 0.1.2 → 0.2.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.
Files changed (29) hide show
  1. package/README.md +45 -48
  2. package/assets/skills/onespec/SKILL.md +22 -14
  3. package/assets/skills/onespec/references/archive.md +214 -0
  4. package/assets/skills/{onespec-design/SKILL.md → onespec/references/design.md} +55 -51
  5. package/assets/skills/onespec/references/execute.md +291 -0
  6. package/assets/skills/onespec/references/fast.md +110 -0
  7. package/assets/skills/onespec/scripts/onespec-closeout.sh +238 -77
  8. package/assets/skills/onespec/scripts/onespec-commit.sh +191 -11
  9. package/assets/skills/onespec/scripts/onespec-handoff.sh +19 -6
  10. package/assets/skills/onespec/scripts/onespec-state.sh +157 -18
  11. package/assets/skills/onespec-fast/SKILL.md +22 -0
  12. package/assets/skills/onespec-fast/agents/openai.yaml +4 -0
  13. package/assets/skills-en/onespec/SKILL.md +22 -13
  14. package/assets/skills-en/onespec/references/archive.md +213 -0
  15. package/assets/skills-en/{onespec-design/SKILL.md → onespec/references/design.md} +58 -43
  16. package/assets/skills-en/onespec/references/execute.md +291 -0
  17. package/assets/skills-en/onespec/references/fast.md +110 -0
  18. package/assets/skills-en/onespec-fast/SKILL.md +22 -0
  19. package/package.json +10 -3
  20. package/scripts/postinstall.js +3 -3
  21. package/src/cli.js +120 -110
  22. package/src/doctor.js +46 -20
  23. package/src/init.js +24 -10
  24. package/src/platforms.js +88 -8
  25. package/src/setup.js +211 -0
  26. package/assets/skills/onespec-archive/SKILL.md +0 -202
  27. package/assets/skills/onespec-execute/SKILL.md +0 -219
  28. package/assets/skills-en/onespec-archive/SKILL.md +0 -199
  29. package/assets/skills-en/onespec-execute/SKILL.md +0 -219
package/src/setup.js ADDED
@@ -0,0 +1,211 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { access } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+
6
+ import { initProject, SUPPORTED_LANGUAGES } from './init.js';
7
+ import { getGlobalSkillDir, getPlatform, getProjectSkillDir, PLATFORMS } from './platforms.js';
8
+
9
+ const OPEN_SPEC_CLI_PACKAGE = '@fission-ai/openspec@latest';
10
+ const SUPERPOWERS_PACKAGE = 'obra/superpowers';
11
+
12
+ export const SUPPORTED_PLATFORM_IDS = Object.keys(PLATFORMS);
13
+
14
+ async function exists(filePath) {
15
+ try {
16
+ await access(filePath);
17
+ return true;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ export function commandExists(command) {
24
+ try {
25
+ const checker = process.platform === 'win32' ? 'where' : 'which';
26
+ execFileSync(checker, [command], { stdio: 'ignore' });
27
+ return true;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ export function getNpxExecutable(platform = process.platform) {
34
+ return platform === 'win32' ? 'npx.cmd' : 'npx';
35
+ }
36
+
37
+ export function getNpmExecutable(platform = process.platform) {
38
+ return platform === 'win32' ? 'npm.cmd' : 'npm';
39
+ }
40
+
41
+ export function parsePlatformList(values) {
42
+ const rawValues = Array.isArray(values) ? values : values ? [values] : [];
43
+ const selected = [];
44
+
45
+ for (const rawValue of rawValues) {
46
+ for (const part of String(rawValue).split(',')) {
47
+ const token = part.trim();
48
+ if (!token) {
49
+ continue;
50
+ }
51
+ selected.push(getPlatform(token).id);
52
+ }
53
+ }
54
+
55
+ return [...new Set(selected)];
56
+ }
57
+
58
+ export async function detectPlatforms(projectPath) {
59
+ const resolvedProject = path.resolve(projectPath);
60
+ const detected = [];
61
+
62
+ for (const platformId of SUPPORTED_PLATFORM_IDS) {
63
+ const projectSkillDir = getProjectSkillDir(resolvedProject, platformId);
64
+ const globalSkillDir = getGlobalSkillDir(platformId);
65
+ if ((await exists(projectSkillDir)) || (await exists(globalSkillDir))) {
66
+ detected.push(platformId);
67
+ }
68
+ }
69
+
70
+ return detected;
71
+ }
72
+
73
+ export async function detectExistingOneSpecPlatforms(projectPath, scope, platformIds) {
74
+ const resolvedProject = path.resolve(projectPath);
75
+ const existing = [];
76
+
77
+ for (const platformId of platformIds) {
78
+ const skillDir =
79
+ scope === 'global'
80
+ ? getGlobalSkillDir(platformId)
81
+ : getProjectSkillDir(resolvedProject, platformId);
82
+ if (await exists(path.join(skillDir, 'onespec', 'SKILL.md'))) {
83
+ existing.push(platformId);
84
+ }
85
+ }
86
+
87
+ return existing;
88
+ }
89
+
90
+ export function buildOpenSpecCliInstallCommand() {
91
+ return {
92
+ command: getNpmExecutable(),
93
+ args: ['install', '-g', OPEN_SPEC_CLI_PACKAGE],
94
+ };
95
+ }
96
+
97
+ export function buildOpenSpecInitCommand(targetPath, platformIds, scope, homeDir = os.homedir()) {
98
+ const resolvedPlatforms = parsePlatformList(platformIds);
99
+ if (resolvedPlatforms.length === 0) {
100
+ throw new Error('At least one platform must be selected.');
101
+ }
102
+
103
+ const openSpecToolIds = resolvedPlatforms.map((platformId) => getPlatform(platformId).openspecToolId);
104
+ return {
105
+ command: 'openspec',
106
+ args: ['init', scope === 'global' ? homeDir : targetPath, '--tools', openSpecToolIds.join(',')],
107
+ toolIds: openSpecToolIds,
108
+ targetPath: scope === 'global' ? homeDir : targetPath,
109
+ };
110
+ }
111
+
112
+ export function buildSuperpowersInstallCommand(platformIds, scope) {
113
+ const resolvedPlatforms = parsePlatformList(platformIds);
114
+ if (resolvedPlatforms.length === 0) {
115
+ throw new Error('At least one platform must be selected.');
116
+ }
117
+
118
+ const args = ['skills', 'add', SUPERPOWERS_PACKAGE, '-y'];
119
+ if (scope === 'global') {
120
+ args.push('-g');
121
+ }
122
+ for (const platformId of resolvedPlatforms) {
123
+ args.push('--agent', getPlatform(platformId).id);
124
+ }
125
+
126
+ return {
127
+ command: getNpxExecutable(),
128
+ args,
129
+ agents: resolvedPlatforms,
130
+ };
131
+ }
132
+
133
+ function runCommand(command, args, cwd = process.cwd()) {
134
+ execFileSync(command, args, {
135
+ cwd,
136
+ stdio: 'inherit',
137
+ shell: process.platform === 'win32',
138
+ });
139
+ }
140
+
141
+ export async function initWorkspace(targetPath, options = {}, dependencies = {}) {
142
+ const projectPath = path.resolve(targetPath);
143
+ const scope = options.scope ?? 'project';
144
+ const language = options.language ?? 'zh';
145
+ const platformIds = parsePlatformList(options.platforms ?? []);
146
+ const selectedPlatformIds = platformIds.length > 0 ? platformIds : ['codex'];
147
+
148
+ if (!['project', 'global'].includes(scope)) {
149
+ throw new Error(`Unsupported scope "${scope}". Use "project" or "global".`);
150
+ }
151
+ if (!SUPPORTED_LANGUAGES[language]) {
152
+ throw new Error(
153
+ `Unsupported language "${language}". Use one of: ${Object.keys(SUPPORTED_LANGUAGES).join(', ')}.`,
154
+ );
155
+ }
156
+
157
+ const commandChecker = dependencies.commandExists ?? commandExists;
158
+ const commandRunner = dependencies.runCommand ?? runCommand;
159
+ const projectInitializer = dependencies.initProject ?? initProject;
160
+ const homeDir = dependencies.homeDir ?? os.homedir();
161
+
162
+ let openspecCliStatus = 'present';
163
+ if (!commandChecker('openspec')) {
164
+ const cliInstall = buildOpenSpecCliInstallCommand();
165
+ commandRunner(cliInstall.command, cliInstall.args, projectPath);
166
+ openspecCliStatus = 'installed';
167
+ if (!commandChecker('openspec')) {
168
+ throw new Error(`OpenSpec CLI installation completed but \`openspec\` is still unavailable.`);
169
+ }
170
+ }
171
+
172
+ const openSpecSetup = buildOpenSpecInitCommand(projectPath, selectedPlatformIds, scope, homeDir);
173
+ commandRunner(openSpecSetup.command, openSpecSetup.args, projectPath);
174
+
175
+ const superpowersSetup = buildSuperpowersInstallCommand(selectedPlatformIds, scope);
176
+ commandRunner(superpowersSetup.command, superpowersSetup.args, projectPath);
177
+
178
+ const results = [];
179
+ for (const platformId of selectedPlatformIds) {
180
+ results.push(
181
+ await projectInitializer(projectPath, {
182
+ ...options,
183
+ platform: platformId,
184
+ scope,
185
+ language,
186
+ }),
187
+ );
188
+ }
189
+
190
+ return {
191
+ projectPath,
192
+ scope,
193
+ language,
194
+ languageName: SUPPORTED_LANGUAGES[language].name,
195
+ platforms: selectedPlatformIds,
196
+ platformNames: selectedPlatformIds.map((platformId) => getPlatform(platformId).name),
197
+ openspecCli: {
198
+ status: openspecCliStatus,
199
+ package: OPEN_SPEC_CLI_PACKAGE,
200
+ },
201
+ openspec: {
202
+ targetPath: openSpecSetup.targetPath,
203
+ toolIds: openSpecSetup.toolIds,
204
+ },
205
+ superpowers: {
206
+ package: SUPERPOWERS_PACKAGE,
207
+ agents: superpowersSetup.agents,
208
+ },
209
+ results,
210
+ };
211
+ }
@@ -1,202 +0,0 @@
1
- ---
2
- name: onespec-archive
3
- description: 当用户需要对 OneSpec change 做最终评审、处理反馈、删除 worktree 或执行 OpenSpec archive 时使用。
4
- ---
5
-
6
- # OneSpec Archive
7
-
8
- 用于 OneSpec 的评审、收尾与归档阶段。目标是在用户确认后执行删除 worktree 与 OpenSpec archive,不默认执行有后果的操作。
9
-
10
- 开始时说明:
11
-
12
- > 我正在使用 `onespec-archive` 处理 review / closeout 阶段。
13
-
14
- ## 1. 评审入口
15
-
16
- 先恢复状态:
17
-
18
- ```bash
19
- ONESPEC_ENV="${ONESPEC_ENV:-$(find . "$HOME"/.codex "$HOME"/.agents "$HOME"/.config -path '*/onespec/scripts/onespec-env.sh' -type f -print -quit 2>/dev/null)}"
20
- . "$ONESPEC_ENV"
21
- "$ONESPEC_BASH" "$ONESPEC_STATE" list
22
- ```
23
-
24
- 如果发现相关 change,必须继续执行:
25
-
26
- ```bash
27
- "$ONESPEC_BASH" "$ONESPEC_STATE" recover <change-id>
28
- ```
29
-
30
- `recover` 的输出是当前阶段合同,不是参考信息。至少先读取 `phase`、`next_skill`、`next_gate` 与 `allowed_actions`,再决定是否继续收尾阶段动作。
31
-
32
- 读取最少必要上下文:
33
-
34
- - `openspec/changes/<change-id>/proposal.md`
35
- - `openspec/changes/<change-id>/tasks.md`
36
- - `openspec/changes/<change-id>/design.md`,如果存在
37
- - 相关 `openspec/specs/**`
38
- - 最新测试结果与 `openspec validate <change-id> --strict` 结果
39
- - 当前分支、worktree 和工作区状态
40
- - `origin_branch`、`origin_workspace_path`、`origin_workspace_mode`
41
-
42
- 如果状态尚未到 `review`,先说明缺少什么:未实现、未验证、未回填 `tasks.md`,或 proposal 尚未批准。
43
-
44
- 入口校验:如果 phase 已到 `review`,但 `.onespec.yaml` 里的 `handoff_purpose` 不是 `review`,或 `handoff_hash` 为空,说明 execute gate 可能未正常完成。此时必须告诉用户:"执行阶段的 review handoff 状态未写回,建议先回到执行阶段补充汇报(回复 `补充汇报` 或重新触发 execute gate)。" 不允许静默跳过。
45
-
46
- ## 2. 用户评审
47
-
48
- 实现完成后让用户评审。若用户指出问题,继续修改并重新验证。
49
-
50
- 开发完成后不需要再次让用户确认是否 review,也不需要展示常规“继续评审 / 保留分支”类选项。只需询问是否进行归档;如果用户回复任意非编号内容,默认视为“继续修改当前实现”,直接回到代码处理环节。
51
-
52
- 不要让用户自己猜“下一步该输入什么”。进入 `onespec-archive` 时,必须给出可直接回复的编号选项;如果支持多动作组合,允许用户回复逗号分隔的数字,例如 `1,3`。
53
-
54
- 进入收尾选择前,必须显式向用户汇报:
55
-
56
- - 当前分支名
57
- - 当前工作区路径
58
- - 最初开始这次 change 时记录的 `origin_branch` 与 `origin_workspace_path`
59
- - 当前是否仍在原始分支/原始工作区
60
-
61
- 如果当前分支或工作区不同于 `origin_*`,必须明确说明“你当前看到的是临时实现分支或临时 worktree”。此时默认展示删除 worktree / 归档组合选项;如果用户改为输入任意非编号内容,则表示当前功能还有问题,需要继续修改。
62
-
63
- 可选收尾路径只围绕两件事展开:
64
-
65
- - 删除 worktree
66
- - 执行归档
67
-
68
- 不要默认自动删除 worktree。删除与归档都是有后果的操作,必须来自用户选择。
69
-
70
- ## 2.1 Superpowers Worktree 优先规则
71
-
72
- 如果 `origin_workspace_mode=worktree`,或当前路径是实现期新建的临时 worktree,收尾时必须把“回收到原始分支/工作区”的动作提到最前面说明。
73
-
74
- 必须显式告诉用户:
75
-
76
- - 当前实现位于临时 worktree
77
- - 原始分支是 `origin_branch`
78
- - 原始工作区是 `origin_workspace_path`
79
- - 收尾后是否会删除本地临时 branch 与 worktree
80
-
81
- 默认推荐顺序:
82
-
83
- 1. 先在临时 worktree 完成 review。
84
- 2. 如果无需继续修改,优先给出“删除 worktree 并归档”作为推荐收尾组合。
85
- 3. 如果用户只想清理本地环境,则允许“仅删除 worktree”。
86
- 4. 如果代码已经真正位于目标分支,则允许“仅归档”。
87
-
88
- ## 2.2 多选收尾组合
89
-
90
- 收尾选项不要再只做单选。动作本身就应围绕组合展开;组合输入使用编号列表,例如 `1,3`:
91
-
92
- - `删除 worktree`
93
- - `执行归档`
94
-
95
- 推荐的组合校验逻辑:
96
-
97
- - `{删除 worktree 并归档}`:等价于 `{删除 worktree, 执行归档}`。
98
- - `{删除 worktree, 执行归档}`:合法。适用于临时 worktree 已完成使命,需要直接清理并归档。
99
- - `{仅删除 worktree}`:等价于 `{删除 worktree}`。
100
- - `{删除 worktree}`:合法。适用于只清理本地临时 worktree,稍后再归档。
101
- - `{仅归档}`:等价于 `{执行归档}`。
102
- - `{执行归档}`:仅当代码已经位于目标分支时合法;如果当前还在临时 branch / worktree,默认不合法。
103
-
104
- 如果用户勾选了非法组合,必须明确指出冲突原因,不要替用户猜测执行顺序。
105
-
106
- 默认推荐组合:
107
-
108
- - 当前在 Superpowers 临时 worktree:推荐 `{删除 worktree, 执行归档}`。
109
- - 当前在临时 worktree,但用户只想先清理本地环境:推荐 `{删除 worktree}`。
110
- - 当前不在临时 worktree,且代码已真正位于目标分支:推荐 `{执行归档}`。
111
-
112
- 推荐向用户展示的收尾选项至少包含:
113
-
114
- 1. 删除 worktree 并归档
115
- 2. 仅删除 worktree
116
- 3. 仅归档
117
- 其他:如果意图不在以上选项里,允许用户直接补充说明;任意非编号内容视为继续修改当前实现
118
-
119
- 菜单解释规则:
120
-
121
- - 用户回复 `1`:执行“删除 worktree 并归档”。
122
- - 用户回复 `2`:仅删除 worktree。
123
- - 用户回复 `3`:仅当代码已合并且归档前置条件满足时,执行 archive;否则先说明阻塞条件。
124
- - 用户回复多项编号,如 `1,3`:按组合规则校验。合法时按安全执行顺序运行;不合法时明确指出冲突原因。
125
- - 用户输入数字外的自由文本:默认视为继续修改当前实现,直接回到代码处理环节;只有意图不清晰时才补一个最短澄清问题。
126
-
127
- ## 3. 归档规则
128
-
129
- 进入 archive 或删除 worktree 的最终收尾前,必须检查当前是否仍有“与本次 change 相关的未提交代码”:
130
-
131
- ```bash
132
- "$ONESPEC_BASH" "$ONESPEC_COMMIT" related-dirty <change-id>
133
- ```
134
-
135
- - 如果结果为空,继续后续收尾。
136
- - 如果结果为空,即使工作区里还有无关未跟踪目录,也不要阻塞收尾;例如未记录到 `.onespec.yaml` tracked file 列表里的 `.superpowers/` 可以明确说明“未纳入本次提交”,但不应视为本次 change 的阻塞项。
137
- - 如果结果非空,先向用户明确提示这些文件尚未提交,并暂停归档。
138
- - 若用户要求现在提交,只能 stage 本次 change 相关文件:
139
-
140
- ```bash
141
- "$ONESPEC_BASH" "$ONESPEC_COMMIT" stage-related <change-id>
142
- ```
143
-
144
- - 提交信息优先遵循项目自身指定的 Git 提交策略。先探测项目内文档和配置:
145
-
146
- ```bash
147
- "$ONESPEC_BASH" "$ONESPEC_COMMIT" detect-policy <change-id>
148
- ```
149
-
150
- - 如果项目里存在明确规范,按项目要求处理 commit message 的格式、scope 和语言。
151
- - 如果项目里没有明确规范,回退到通用 Conventional Commits:`<type>(<scope>): <简要描述>`。
152
- - 只能提交 `.onespec.yaml` 中记录的 tracked files 与当前脏文件的交集;如果 `.onespec.yaml` 本身是脏的,也应一并提交,不允许把无关改动一并提交。
153
- - 例外:位于 `openspec/changes/<change-id>/` 下、专属于本次 change 的临时压缩包、导出包或交接工件,也视为本次 change 相关文件;自动提交时要一并带上,这样 archive 后仍能保留在 change 历史里。
154
- - 如果代码已合并到目标分支且用户选择归档,直接执行 OpenSpec archive,并将状态设为 `archived`。
155
- - 如果用户当前只删除 worktree、不归档,将状态设为 `done`,并提示之后可再运行归档;此时不要删除 `.onespec.yaml`。
156
- - 只有真正执行 archive 后,才删除运行时状态文件:
157
-
158
- ```bash
159
- "$ONESPEC_BASH" "$ONESPEC_CLOSEOUT" cleanup-runtime <change-id>
160
- ```
161
-
162
- 用户一旦通过收尾菜单明确选择了归档或组合归档动作,就把这次选择视为唯一确认;不要再追加第二次确认。
163
-
164
- 实际执行收尾动作时,优先使用:
165
-
166
- ```bash
167
- "$ONESPEC_BASH" "$ONESPEC_CLOSEOUT" run-actions <change-id> [delete-worktree] [archive]
168
- ```
169
-
170
- ```bash
171
- "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> phase done
172
- "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> archive <skipped|archived>
173
- ```
174
-
175
- 归档前必须确认:
176
-
177
- - `tasks.md` 已按实际完成项勾选
178
- - 项目测试已通过,或未通过项已明确说明
179
- - `openspec validate <change-id> --strict` 已通过
180
- - 用户明确选择了删除 worktree、归档或它们的组合策略
181
- - 没有未处理的用户评审反馈
182
-
183
- ## 4. 汇报
184
-
185
- 收尾汇报必须覆盖:
186
-
187
- - 用户评审结果
188
- - 选择的收尾路径:删除 worktree、归档或组合执行
189
- - 分支/worktree 的最终状态
190
- - 当前分支与 `origin_branch` 的关系,以及是否仍保留临时 worktree
191
- - `tasks.md`、测试与 OpenSpec validate 状态
192
- - archive 字段:`skipped` 或 `archived`
193
-
194
- ## 5. 停止条件
195
-
196
- 以下情况必须暂停并向用户说明:
197
-
198
- - 用户还没有完成最终评审
199
- - 用户没有明确选择收尾路径
200
- - 用户没有明确确认删除 worktree 或 OpenSpec archive
201
- - 代码未合并到目标分支且也未选择允许的删除 worktree 组合时,却要求单独 archive
202
- - 测试或 `openspec validate <change-id> --strict` 未通过且用户未明确接受风险
@@ -1,219 +0,0 @@
1
- ---
2
- name: onespec-execute
3
- description: 当用户需要执行已批准 OpenSpec change、继续实现、生成 Superpowers plan、运行 OpenSpec apply、回填 tasks 或验证实现时使用。
4
- ---
5
-
6
- # OneSpec Execute
7
-
8
- 用于 OneSpec 的执行阶段。目标是只在已批准范围内实现,并把实现结果回填 OpenSpec 状态。
9
-
10
- 开始时说明:
11
-
12
- > 我正在使用 `onespec-execute` 处理 apply / implement 阶段。
13
-
14
- ## 1. Apply 路由
15
-
16
- 先恢复状态:
17
-
18
- ```bash
19
- ONESPEC_ENV="${ONESPEC_ENV:-$(find . "$HOME"/.codex "$HOME"/.agents "$HOME"/.config -path '*/onespec/scripts/onespec-env.sh' -type f -print -quit 2>/dev/null)}"
20
- . "$ONESPEC_ENV"
21
- "$ONESPEC_BASH" "$ONESPEC_STATE" list
22
- ```
23
-
24
- 如果发现相关 change,必须继续执行:
25
-
26
- ```bash
27
- "$ONESPEC_BASH" "$ONESPEC_STATE" recover <change-id>
28
- ```
29
-
30
- `recover` 的输出是当前阶段合同,不是参考信息。至少先读取 `phase`、`next_skill`、`next_gate` 与 `allowed_actions`,再决定是否继续执行阶段动作。
31
-
32
- apply 前至少读取:
33
-
34
- - `openspec/changes/<change-id>/proposal.md`
35
- - `openspec/changes/<change-id>/tasks.md`
36
- - `openspec/changes/<change-id>/design.md`,如果存在
37
- - 相关 `openspec/specs/**`
38
- - 相关 `docs/**`
39
-
40
- 默认意图映射:
41
-
42
- - 用户说“开始实现”、“执行这个 change”、“apply 这个 proposal / change”、“继续做这个 change”、“开始 coding / 开发”、“make plan”时,默认解释为进入已批准 change 的 Superpowers 实现准备路径,而不是直接原生 `openspec apply`。
43
- - 只有用户明确说“不用 Superpowers plan”、“不用 subagent”或“直接按 OpenSpec apply”时,才允许走原生 OpenSpec apply。
44
- - 如果 proposal 阶段已经确认过实现路线,后续 apply 优先遵循该确认结果,不要用默认映射覆盖用户已确认的路线。
45
-
46
- 如果 proposal 尚未批准,直接停止,不要开始实现。
47
-
48
- 如果 proposal 阶段已经确认实现路径:
49
-
50
- - 用户确认 `Superpowers`:继续 Superpowers Make Plan。
51
- - 用户确认原生 `OpenSpec apply`:切换到原生 OpenSpec apply。
52
- - 用户尚未确认:提醒当前推荐路线,并用编号菜单要求用户选择,不要直接开始实现。
53
-
54
- 如果必须在执行阶段补做路线确认,使用下面的编号菜单,用户回复数字即可:
55
-
56
- 1. 按推荐路线继续
57
- 2. 改成 `Superpowers`
58
- 3. 改成原生 `OpenSpec apply`
59
- 4. 先不要开始实现,我要先回去修改 proposal / design / tasks
60
- 其他:如果意图不在以上选项里,允许用户直接补充说明
61
-
62
- 菜单解释规则:
63
-
64
- - 用户回复 `1`:采用当前推荐路线。
65
- - 用户回复 `2`:切换到 `Superpowers`,并继续用编号菜单确认 `subagent/local` 与 `worktree/current-branch`。
66
- - 用户回复 `3`:切换到原生 `OpenSpec apply`。
67
- - 用户回复 `4`:停止执行,返回设计修订路径。
68
- - 用户输入数字外的自由文本:如果意图清晰,按用户自定义意图处理;若不清晰,只补一个最短澄清问题。
69
-
70
- ## 2. Superpowers Make Plan 与实现
71
-
72
- 在 Superpowers 路径下,apply 默认不是“直接实现”,而是先把已批准的 OpenSpec change 翻译成 Superpowers 可执行计划。
73
-
74
- 必须执行:
75
-
76
- - 读取并总结 `proposal.md`、`design.md`、`tasks.md`、相关 spec delta 与相关项目文档。
77
- - 从 `tasks.md` 中提取未完成 task,作为 planning scope。
78
- - 使用 `writing-plans` 或 `superpowers:writing-plans` 生成计划,保存到 `docs/superpowers/plans/YYYY-MM-DD-<change-id>.md`。
79
- - 计划必须覆盖每个未完成 OpenSpec task,可拆细,但不得遗漏或扩大范围。
80
- - 如果已有对应 plan,先检查它是否仍覆盖当前未完成 tasks;不覆盖就更新或重写。
81
- - 如果 plan 与 OpenSpec artifacts 冲突,先修正 OpenSpec artifacts,再重写 plan。
82
- - 从开始实现到进入 review 之前,为当前 change 维护 `openspec/changes/<change-id>/.onespec.yaml` 这个唯一运行时状态文件;本次直接修改的仓库相对路径写入其中的 `touched_files_b64`。优先使用:
83
-
84
- ```bash
85
- "$ONESPEC_BASH" "$ONESPEC_COMMIT" track <change-id> <path>...
86
- ```
87
-
88
- - 不要把用户原本就存在但不属于本次 change 的脏文件写入这个 tracked file 列表。
89
- - 如果在 `openspec/changes/<change-id>/` 下生成了临时压缩包、导出包或其他仅服务于本次 change 的工件,也要视为本次 change 的一部分保留到 archive;它们不要求单独写进 `touched_files_b64`,但后续自动提交必须把这些工件与 `.onespec.yaml` 一并提交。
90
- - 后续如果进入自动提交,`.onespec.yaml` 本身也应随当前 change 一起提交;它不是要提前删除的中间产物,而是 review / archive 前的恢复依据。
91
-
92
- 记录计划并创建交接包:
93
-
94
- ```bash
95
- "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> plan <plan-path>
96
- "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> phase plan-ready
97
- "$ONESPEC_BASH" "$ONESPEC_HANDOFF" <change-id> plan --write
98
- ```
99
-
100
- 在真正开始写代码、运行原生 apply 或派发子任务前,必须先把状态切到 `implementing`:
101
-
102
- ```bash
103
- "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> phase implementing
104
- ```
105
-
106
- 如果还停留在 `approved` 或 `plan-ready`,说明实现尚未正式开始;此时不允许把中途代码编辑误报为“继续实现”。
107
-
108
- 如果 `origin_branch` 或 `origin_workspace_path` 仍是 `unknown`,则在真正创建 worktree、切换 branch 或开始实现前立即补记当前上下文:
109
-
110
- ```bash
111
- "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> origin_branch "$(git branch --show-current || echo detached)"
112
- "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> origin_workspace_path "$(pwd -P)"
113
- "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> origin_workspace_mode "$( "$ONESPEC_BASH" "$ONESPEC_STATE" get <change-id> workspace )"
114
- ```
115
-
116
- 默认执行路径:
117
-
118
- - 优先使用 `subagent-driven-development`。
119
- - 子 agent 按 task 执行时,强制遵守 `test-driven-development`。
120
- - controller 在每个任务后做规格符合性评审和代码质量评审,再进入下一个任务。
121
- - 如果用户明确要求不用 subagent,或任务强耦合到不适合逐 task 派发,说明原因后改用 `executing-plans`。
122
- - 需要隔离时使用 `using-git-worktrees`,不要手写绕过它的安全检查。
123
-
124
- 实现完成后必须回填 OpenSpec artifacts:
125
-
126
- - 在 `tasks.md` 中勾选本次完成的任务。
127
- - 如果 Superpowers plan 将一个 OpenSpec task 拆成多步,只有对应实现、测试和必要 review 完成后,才允许回填该 OpenSpec task。
128
- - 如果实现改变了已批准事实,先同步更新 `design.md`、`proposal.md` 或 spec delta,再继续。
129
- - 不允许实现结果与已批准 OpenSpec 范围静默漂移。
130
- - 运行项目测试和 `openspec validate <change-id> --strict`。
131
-
132
- ## 3. 原生 OpenSpec Apply
133
-
134
- 只有当用户选择 `OpenSpec apply`、接受低复杂度推荐,或明确拒绝 Superpowers 时,才允许走原生 OpenSpec apply。
135
-
136
- 原生 apply 执行后同样要:
137
-
138
- - 勾选 `tasks.md`
139
- - 如实现中暴露新的歧义或设计冲突,先暂停并修正 OpenSpec artifacts,必要时回到 brainstorming
140
- - 运行项目测试
141
- - 运行 `openspec validate <change-id> --strict`
142
- - 进入用户评审,并使用与 5.3 一致的数字菜单暂停等待后续动作
143
- - 将状态置为 `review`
144
-
145
- ## 4. 停止条件
146
-
147
- 以下情况必须暂停并向用户说明:
148
-
149
- - proposal 未批准但用户要求直接实现
150
- - Superpowers plan 与已批准 OpenSpec artifacts 发生冲突
151
- - `tasks.md` 尚未被翻译成可执行 Superpowers plan,但模型试图直接编码
152
- - 实现过程中发现会改变 scope、design 或 spec 的新需求
153
- - 测试或 `openspec validate <change-id> --strict` 未通过且尚未修复
154
-
155
- ## 5. 实现完成 Gate(强制暂停)
156
-
157
- > ⚠️ 这是强制 gate。如果 gate 未通过,不允许输出任何总结或收尾建议,不允许进入下一阶段。
158
-
159
- 完成实现与验证后,必须明确暂停,不允许直接继续做 merge、删除 worktree、归档或“顺手收尾”。这里的目标是进入用户评审 / `review-closeout` 等待态;开发完成后只需询问是否进行归档,不需要再要求用户先确认 review。
160
-
161
- ### 5.1 强制脚本调用
162
-
163
- 回填 artifacts 完成且测试通过后,必须执行以下两条命令:
164
-
165
- ```bash
166
- "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> phase review
167
- "$ONESPEC_BASH" "$ONESPEC_HANDOFF" <change-id> review --write
168
- ```
169
-
170
- **如果这两条命令未执行,则 gate 未通过。** 不允许跳过这一步直接输出完成汇报。
171
-
172
- ### 5.2 强制汇报 checklist
173
-
174
- 执行完上述脚本后,必须向用户输出汇报。汇报 MUST 覆盖以下全部条目,缺一不可:
175
-
176
- 1. 当前分支名
177
- 2. 当前工作区路径
178
- 3. `origin_branch` 与 `origin_workspace_path`(是否与当前一致)
179
- 4. 使用了哪一个 Superpowers plan 文件
180
- 5. 本次完成了哪些 OpenSpec task
181
- 6. `tasks.md` 回填情况
182
- 7. 是否更新了 `proposal.md`、`design.md` 或 spec delta
183
- 8. 测试结果
184
- 9. `openspec validate <change-id> --strict` 结果
185
- 10. 下一步编号选项(必须给出可直接回复的数字菜单,并明确任意非编号内容视为继续修改当前实现)
186
-
187
- ### 5.3 下一步编号菜单模板
188
-
189
- 汇报结尾必须包含以下格式的编号提示(可微调措辞,但结构和编号语义不可省略):
190
-
191
- ```
192
- ---
193
- ✅ 实现与验证已完成。
194
-
195
- 📍 当前分支: `<branch>`
196
- 📍 当前工作区: `<path>`
197
- 📍 origin: `<origin_branch>` @ `<origin_workspace_path>`
198
-
199
- 1. 进入 `onespec-archive`,选择删除 worktree / 归档相关操作
200
- 2. 保持当前分支 / worktree 不变,先停在这里,稍后再继续
201
- 其他:任意非编号内容视为继续修改当前实现;如果意图不在以上选项里,也可以直接补充说明
202
- ---
203
- ```
204
-
205
- 如果当前分支或工作区不同于 `origin_*`,还必须额外说明:"当前实现位于临时分支或临时 worktree;若你直接回复非编号内容,我会按继续修改处理。"
206
-
207
- 不要只停在“下一步应进入 `onespec-archive`”这种抽象提示,也不要只说“做 `review-closeout`”。必须同时给出用户可直接回复的编号选项。
208
-
209
- ### 5.4 反模式(NEVER)
210
-
211
- 以下行为构成 gate 违规,绝不允许:
212
-
213
- - 实现完成后直接输出"已完成"总结,而未执行 5.1 的脚本
214
- - 汇报中缺少当前分支/工作区信息(checklist 第 1-3 项)
215
- - 未给出明确的下一步编号选项
216
- - 将 archive / merge / worktree 删除操作混入实现完成汇报
217
- - 在用户未回复前自行进入 `onespec-archive` 阶段
218
- - 用"下一步应进入 onespec-archive"这种抽象描述替代具体编号菜单
219
- - 在用户完成 review 并明确要求收尾前,擅自删除临时 worktree