@kafka0102/onespec 0.1.2 → 0.1.14

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 (27) hide show
  1. package/README.md +45 -48
  2. package/assets/skills/onespec/SKILL.md +21 -13
  3. package/assets/skills/onespec/references/archive.md +191 -0
  4. package/assets/skills/{onespec-design/SKILL.md → onespec/references/design.md} +44 -42
  5. package/assets/skills/{onespec-execute/SKILL.md → onespec/references/execute.md} +81 -31
  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 +21 -12
  14. package/assets/skills-en/onespec/references/archive.md +190 -0
  15. package/assets/skills-en/{onespec-design/SKILL.md → onespec/references/design.md} +44 -42
  16. package/assets/skills-en/{onespec-execute/SKILL.md → onespec/references/execute.md} +81 -31
  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 +6 -2
  20. package/scripts/postinstall.js +3 -3
  21. package/src/cli.js +104 -87
  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-en/onespec-archive/SKILL.md +0 -199
@@ -0,0 +1,110 @@
1
+ # Fast Path
2
+
3
+ Read on demand from `onespec` and the standalone `onespec-fast` entrypoint for the `fast` path. The goal is to skip the normal proposal approval gate, complexity check, implementation-path selection, and post-implementation archive choice. Once in the fast path, use native `OpenSpec apply` for the whole implementation and archive directly.
4
+
5
+ ## 1. Intake
6
+
7
+ Recover state first:
8
+
9
+ ```bash
10
+ ONESPEC_ENV="${ONESPEC_ENV:-$(find . "$HOME"/.codex "$HOME"/.claude "$HOME"/.cursor "$HOME"/.gemini "$HOME"/.copilot "$HOME"/.agents "$HOME"/.config -path '*/onespec/scripts/onespec-env.sh' -type f -print -quit 2>/dev/null)}"
11
+ . "$ONESPEC_ENV"
12
+ "$ONESPEC_BASH" "$ONESPEC_STATE" list
13
+ ```
14
+
15
+ If a relevant change exists, run:
16
+
17
+ ```bash
18
+ "$ONESPEC_BASH" "$ONESPEC_STATE" recover <change-id>
19
+ ```
20
+
21
+ Treat `recover` output as the execution contract. Read at least `phase`, `next_skill`, `next_reference`, `next_gate`, and `allowed_actions`.
22
+
23
+ Use rules:
24
+
25
+ - Use only when the user explicitly asks for `onespec-fast`, the fast path, fast apply, automatic OpenSpec proposal/implementation/archive, or automatic end-to-end execution.
26
+ - Do not ask the user to name the change. Generate a short kebab-case `change-id`; append a numeric suffix if needed.
27
+ - Read the minimum needed context: `openspec/config.yaml`, `openspec/project.md`, relevant `openspec/specs/**`, project entry docs, current branch, and workspace state.
28
+ - Pause only when required OpenSpec context is too incomplete to produce a valid proposal, or project docs explicitly forbid automatic edits on the current branch.
29
+
30
+ ## 2. Direct Proposal
31
+
32
+ The fast path skips the normal design phase pre-proposal user confirmation.
33
+
34
+ Create the OpenSpec proposal artifacts directly:
35
+
36
+ - `openspec/changes/<change-id>/proposal.md`
37
+ - `openspec/changes/<change-id>/design.md`, only when it has real technical value
38
+ - `openspec/changes/<change-id>/tasks.md`
39
+ - required `specs/**/spec.md`
40
+
41
+ Create state and handoff:
42
+
43
+ ```bash
44
+ "$ONESPEC_BASH" "$ONESPEC_STATE" init <change-id>
45
+ "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> phase proposal-ready
46
+ "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> ambiguity low
47
+ "$ONESPEC_BASH" "$ONESPEC_HANDOFF" <change-id> proposal --write
48
+ ```
49
+
50
+ Do not show the normal proposal approval menu. `onespec-fast` means the user has authorized continuing with native `OpenSpec apply` implementation and archive.
51
+
52
+ ## 3. OpenSpec Automatic Apply and Archive
53
+
54
+ After proposal creation, do not run a complexity check and do not switch to a Superpowers plan/subagent. Record the fast path directly and start implementation:
55
+
56
+ ```bash
57
+ "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> implementation_path openspec-apply
58
+ "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> execution_method native
59
+ "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> workspace current-branch
60
+ "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> origin_branch "$(git branch --show-current || echo detached)"
61
+ "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> origin_workspace_path "$(pwd -P)"
62
+ "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> origin_workspace_mode current-branch
63
+ "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> phase approved
64
+ "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> phase implementing
65
+ ```
66
+
67
+ Implementation rules:
68
+
69
+ - Use native `OpenSpec apply`; do not create a Superpowers plan and do not dispatch subagents.
70
+ - Implement only incomplete tasks in `tasks.md`; do not expand proposal scope.
71
+ - Work in the current workspace; do not auto-create a worktree, auto-push, or auto-merge.
72
+ - If the current branch is `main`/`master`, record `origin_workspace_mode` as `main-override`, but pause only if project docs explicitly forbid direct edits on the main branch.
73
+ - Track directly modified repo-relative paths in `.onespec.yaml`; prefer:
74
+
75
+ ```bash
76
+ "$ONESPEC_BASH" "$ONESPEC_COMMIT" track <change-id> <path>...
77
+ ```
78
+
79
+ After implementation:
80
+
81
+ - Check off completed tasks in `tasks.md`.
82
+ - If implementation exposes a new design conflict, stop automatic implementation, fix OpenSpec artifacts, and stay on the OpenSpec proposal/apply path; switch to Superpowers only if the user explicitly asks.
83
+ - Run project tests.
84
+ - Run `openspec validate <change-id> --strict`.
85
+ - Write the review handoff, but do not pause for user review:
86
+
87
+ ```bash
88
+ "$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> phase review
89
+ "$ONESPEC_BASH" "$ONESPEC_HANDOFF" <change-id> review --write
90
+ ```
91
+
92
+ Then archive directly without showing the archive phase closeout menu:
93
+
94
+ ```bash
95
+ "$ONESPEC_BASH" "$ONESPEC_COMMIT" related-dirty <change-id>
96
+ "$ONESPEC_BASH" "$ONESPEC_COMMIT" commit-related <change-id> closeout
97
+ "$ONESPEC_BASH" "$ONESPEC_CLOSEOUT" run-actions <change-id> archive-only
98
+ ```
99
+
100
+ If `related-dirty` is empty, do not run `commit-related <change-id> closeout`. `run-actions` sets `phase archived` / `archive archived` and handles the post-archive commit plus runtime cleanup.
101
+
102
+ ## 4. Stop Conditions
103
+
104
+ Pause if:
105
+
106
+ - required OpenSpec context is missing and a valid proposal cannot be written
107
+ - the request clearly spans multiple changes that should be split
108
+ - tests or `openspec validate <change-id> --strict` fail and cannot be fixed inside the approved scope
109
+ - implementation reveals scope expansion, design change, or spec semantic change
110
+ - project docs explicitly forbid automatic implementation or automatic archive on the current branch
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: onespec-fast
3
+ description: Use when the user explicitly asks for the OneSpec fast path, onespec-fast, fast apply, automatic OpenSpec proposal/implementation/archive, or automatic end-to-end execution. This skill reuses `onespec/references/fast.md` and uses native OpenSpec apply throughout, with no complexity check and no Superpowers plan.
4
+ ---
5
+
6
+ # OneSpec Fast
7
+
8
+ This is the standalone entrypoint for the OneSpec fast path. It does not duplicate phase rules; it must reuse the main `onespec` skill's `references/fast.md`.
9
+
10
+ Announce at the start:
11
+
12
+ > I am using the `onespec-fast` fast path.
13
+
14
+ ## Entry Rules
15
+
16
+ - Use only when the user explicitly asks for `onespec-fast`, the fast path, fast apply, automatic OpenSpec proposal/implementation/archive, or automatic end-to-end execution.
17
+ - First read the sibling `../onespec/SKILL.md` and follow its recovery-first, shared-constraint, and reference-loading rules.
18
+ - Then read `../onespec/references/fast.md` and execute those steps.
19
+ - If the sibling path is unavailable, locate `*/onespec/references/fast.md` under the current project, `$HOME/.codex`, `$HOME/.claude`, `$HOME/.cursor`, `$HOME/.gemini`, `$HOME/.copilot`, `$HOME/.agents`, or `$HOME/.config`. If still missing, stop and ask the user to rerun `onespec init --overwrite`.
20
+ - `references/fast.md` may reuse procedure sections from `design.md`, `execute.md`, and `archive.md`, but it overrides the normal proposal approval, review pause, and closeout-menu gates.
21
+
22
+ Do not restate the fast-path steps in `onespec-fast/SKILL.md`; the full rules live only in `onespec/references/fast.md`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kafka0102/onespec",
3
- "version": "0.1.2",
4
- "description": "OpenSpec + Superpowers workflow skill installer for Codex",
3
+ "version": "0.1.14",
4
+ "description": "OpenSpec + Superpowers workflow skill installer for mainstream SKILL.md agents",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/kafka0102/onespec.git"
@@ -11,6 +11,10 @@
11
11
  "superpowers",
12
12
  "skills",
13
13
  "codex",
14
+ "claude-code",
15
+ "cursor",
16
+ "gemini-cli",
17
+ "github-copilot",
14
18
  "workflow"
15
19
  ],
16
20
  "type": "module",
@@ -16,9 +16,9 @@ function main() {
16
16
  }
17
17
 
18
18
  console.log('\nOneSpec installed. Next run:');
19
- console.log(' onespec init --scope global --yes');
20
- console.log('or for the current project:');
21
- console.log(' onespec init . --scope project --yes\n');
19
+ console.log(' onespec init');
20
+ console.log('or non-interactive:');
21
+ console.log(' onespec init . --platform codex --scope project --yes\n');
22
22
  }
23
23
 
24
24
  try {
package/src/cli.js CHANGED
@@ -1,20 +1,25 @@
1
- import { execFileSync } from 'node:child_process';
2
1
  import path from 'node:path';
3
2
  import readline from 'node:readline/promises';
4
3
  import { stdin as input, stdout as output } from 'node:process';
4
+ import { readFile } from 'node:fs/promises';
5
5
 
6
6
  import { doctorProject } from './doctor.js';
7
- import { initProject, SUPPORTED_LANGUAGES } from './init.js';
8
-
9
- const OPEN_SPEC_CLI_PACKAGE = '@fission-ai/openspec@latest';
10
- const SUPERPOWERS_PACKAGE = 'obra/superpowers';
7
+ import { SUPPORTED_LANGUAGES } from './init.js';
8
+ import { getPlatform } from './platforms.js';
9
+ import {
10
+ detectExistingOneSpecPlatforms,
11
+ detectPlatforms,
12
+ initWorkspace,
13
+ parsePlatformList,
14
+ SUPPORTED_PLATFORM_IDS,
15
+ } from './setup.js';
11
16
 
12
17
  function parseArgs(argv) {
13
18
  const args = [...argv];
14
19
  const command = args.shift() ?? 'help';
15
20
  const options = {
16
21
  targetPath: process.cwd(),
17
- platform: 'codex',
22
+ platforms: [],
18
23
  scope: undefined,
19
24
  language: undefined,
20
25
  yes: false,
@@ -43,7 +48,7 @@ function parseArgs(argv) {
43
48
  options.language = args.shift();
44
49
  break;
45
50
  case '--platform':
46
- options.platform = args.shift() ?? 'codex';
51
+ options.platforms.push(args.shift() ?? '');
47
52
  break;
48
53
  default:
49
54
  if (arg?.startsWith('-')) {
@@ -57,34 +62,43 @@ function parseArgs(argv) {
57
62
  return { command, options };
58
63
  }
59
64
 
60
- function commandExists(command) {
61
- try {
62
- const checker = process.platform === 'win32' ? 'where' : 'which';
63
- execFileSync(checker, [command], { stdio: 'ignore' });
64
- return true;
65
- } catch {
66
- return false;
67
- }
65
+ function normalizeYesNo(value) {
66
+ return ['y', 'yes', '是', '覆盖'].includes(value.trim().toLowerCase());
68
67
  }
69
68
 
70
69
  async function askInitOptions(options) {
70
+ const explicitPlatforms = parsePlatformList(options.platforms);
71
+ const detectedPlatforms = await detectPlatforms(options.targetPath);
72
+ const defaultPlatforms = explicitPlatforms.length > 0 ? explicitPlatforms : detectedPlatforms.length > 0 ? detectedPlatforms : ['codex'];
73
+
71
74
  if (options.yes) {
72
75
  return {
73
76
  ...options,
74
77
  scope: options.scope ?? 'project',
75
78
  language: options.language ?? 'zh',
76
- installOpenSpecCli: false,
77
- initOpenSpecProject: false,
78
- installSuperpowers: false,
79
+ platforms: defaultPlatforms,
79
80
  };
80
81
  }
81
82
 
82
- const preflight = await doctorProject(options.targetPath, {
83
- platform: options.platform,
84
- scope: options.scope ?? 'project',
85
- });
86
83
  const rl = readline.createInterface({ input, output });
87
84
  try {
85
+ if (explicitPlatforms.length === 0) {
86
+ console.log('可选 AI 平台:');
87
+ for (const [index, platformId] of SUPPORTED_PLATFORM_IDS.entries()) {
88
+ const platform = getPlatform(platformId);
89
+ const detectedSuffix = detectedPlatforms.includes(platformId) ? ' [detected]' : '';
90
+ console.log(` ${index + 1}. ${platform.name} (${platform.id})${detectedSuffix}`);
91
+ }
92
+ console.log('');
93
+ }
94
+
95
+ const platformAnswer =
96
+ explicitPlatforms.length > 0
97
+ ? explicitPlatforms.join(',')
98
+ : await rl.question(
99
+ `安装到哪些 AI 平台?输入编号或 id,逗号分隔(默认 ${defaultPlatforms.join(',')}):`,
100
+ );
101
+ const selectedPlatforms = resolvePlatformSelection(platformAnswer, defaultPlatforms);
88
102
  const scopeAnswer =
89
103
  options.scope ??
90
104
  (await rl.question('安装范围?输入 project 或 global(默认 project):'));
@@ -92,100 +106,103 @@ async function askInitOptions(options) {
92
106
  const languageAnswer =
93
107
  options.language ??
94
108
  (await rl.question('Skill 语言?输入 zh 或 en(默认 zh):'));
95
- const overwriteAnswer = options.overwrite
96
- ? 'yes'
97
- : await rl.question('如果 OneSpec skill 已存在,是否覆盖?输入 yes 或 no(默认 no):');
98
- const installOpenSpecCliAnswer =
99
- preflight.openspecCli.available
109
+ const existingPlatforms = await detectExistingOneSpecPlatforms(
110
+ options.targetPath,
111
+ resolvedScope,
112
+ selectedPlatforms,
113
+ );
114
+ const overwriteAnswer =
115
+ options.overwrite || existingPlatforms.length === 0
100
116
  ? 'no'
101
117
  : await rl.question(
102
- `未检测到 OpenSpec CLI。是否现在执行 npm install -g ${OPEN_SPEC_CLI_PACKAGE} ?输入 yes 或 no(默认 no):`,
118
+ `检测到这些平台已存在 OneSpec skill:${existingPlatforms.join(', ')}。是否覆盖已存在项?输入 yes 或 no(默认 no):`,
103
119
  );
104
- const initOpenSpecProjectAnswer =
105
- resolvedScope === 'project' && !preflight.hasOpenSpecProject
106
- ? await rl.question(
107
- '当前项目未初始化 OpenSpec。是否在安装 OneSpec 后执行 openspec init?输入 yes 或 no(默认 no):',
108
- )
109
- : 'no';
110
- const installSuperpowersAnswer = preflight.superpowers.available
111
- ? 'no'
112
- : await rl.question(
113
- `未检测到 Superpowers。是否现在执行 npx skills add ${SUPERPOWERS_PACKAGE} -a codex${resolvedScope === 'global' ? ' -g' : ''} -y ?输入 yes 或 no(默认 no):`,
114
- );
115
120
 
116
121
  return {
117
122
  ...options,
123
+ platforms: selectedPlatforms,
118
124
  scope: resolvedScope,
119
125
  language: languageAnswer.trim() || 'zh',
120
- overwrite: options.overwrite || ['y', 'yes', '是', '覆盖'].includes(overwriteAnswer.trim()),
121
- installOpenSpecCli: ['y', 'yes', '是'].includes(installOpenSpecCliAnswer.trim()),
122
- initOpenSpecProject: ['y', 'yes', '是'].includes(initOpenSpecProjectAnswer.trim()),
123
- installSuperpowers: ['y', 'yes', '是'].includes(installSuperpowersAnswer.trim()),
126
+ overwrite: options.overwrite || normalizeYesNo(overwriteAnswer),
124
127
  };
125
128
  } finally {
126
129
  rl.close();
127
130
  }
128
131
  }
129
132
 
130
- function runCommand(command, args, cwd = process.cwd()) {
131
- execFileSync(command, args, {
132
- cwd,
133
- stdio: 'inherit',
134
- shell: process.platform === 'win32',
135
- });
136
- }
137
-
138
- function getNpxExecutable() {
139
- return process.platform === 'win32' ? 'npx.cmd' : 'npx';
140
- }
141
-
142
- async function ensureRequestedDependencies(targetPath, options, preflight) {
143
- if (options.installOpenSpecCli && !preflight.openspecCli.available) {
144
- runCommand('npm', ['install', '-g', OPEN_SPEC_CLI_PACKAGE]);
133
+ function resolvePlatformSelection(answer, defaultPlatforms) {
134
+ const trimmed = answer.trim();
135
+ if (!trimmed) {
136
+ return defaultPlatforms;
145
137
  }
146
138
 
147
- if (options.installSuperpowers && !preflight.superpowers.available) {
148
- const args = ['skills', 'add', SUPERPOWERS_PACKAGE, '-a', 'codex', '-y'];
149
- if (options.scope === 'global') {
150
- args.push('-g');
151
- }
152
- runCommand(getNpxExecutable(), args, targetPath);
153
- }
139
+ const numbered = trimmed
140
+ .split(',')
141
+ .map((value) => value.trim())
142
+ .filter(Boolean)
143
+ .map((value) => {
144
+ if (!/^\d+$/.test(value)) {
145
+ return value;
146
+ }
154
147
 
155
- if (options.initOpenSpecProject && !preflight.hasOpenSpecProject) {
156
- runCommand('openspec', ['init', targetPath], targetPath);
157
- }
148
+ const index = Number.parseInt(value, 10) - 1;
149
+ if (index < 0 || index >= SUPPORTED_PLATFORM_IDS.length) {
150
+ throw new Error(`Unsupported platform selection: ${value}`);
151
+ }
152
+ return SUPPORTED_PLATFORM_IDS[index];
153
+ });
154
+
155
+ return parsePlatformList(numbered);
158
156
  }
159
157
 
160
158
  function printHelp() {
161
159
  console.log(`OneSpec Skill Installer
162
160
 
163
161
  用法:
164
- onespec init [path] [--yes] [--overwrite] [--scope project|global] [--language zh|en]
165
- onespec doctor [path] [--scope project|global]
162
+ onespec init [path] [--yes] [--overwrite] [--scope project|global] [--language zh|en] [--platform ${SUPPORTED_PLATFORM_IDS.join('|')}[,...]]
163
+ onespec doctor [path] [--scope project|global] [--platform ${SUPPORTED_PLATFORM_IDS.join('|')}]
166
164
 
167
165
  说明:
168
- 当前提供中英文 Skill bundle,暂仅支持 Codex 平台。
166
+ 当前提供中英文 Skill bundle,官方支持 ${SUPPORTED_PLATFORM_IDS.join(' / ')}。
167
+ init 会引导选择 agent,并自动安装 OpenSpec / Superpowers / OneSpec。
169
168
  `);
170
169
  }
171
170
 
171
+ let cachedVersion;
172
+
173
+ async function getPackageVersion() {
174
+ if (cachedVersion) {
175
+ return cachedVersion;
176
+ }
177
+
178
+ const packageJsonPath = new URL('../package.json', import.meta.url);
179
+ const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'));
180
+ cachedVersion = packageJson.version;
181
+ return cachedVersion;
182
+ }
183
+
172
184
  function printSummary(result) {
173
185
  console.log('\nOneSpec 初始化完成\n');
174
- console.log(`安装位置:${result.skillPath}`);
186
+ console.log(`目标平台:${result.platformNames.join(', ')}`);
175
187
  console.log(`安装范围:${result.scope}`);
176
188
  console.log(`Skill 语言:${result.languageName} (${result.language})`);
177
- console.log(`Skill 状态:${result.installedSkill ? '已安装/已覆盖' : '已存在,已跳过'}`);
178
- console.log(`已安装 Skills:${result.installedSkills.join(', ') || '无'}`);
179
- console.log(`已跳过 Skills:${result.skippedSkills.join(', ') || '无'}`);
180
- console.log(`工作目录:${path.join(result.projectPath, 'docs', 'superpowers')}`);
181
- console.log('\n环境检查:');
182
- console.log(`OpenSpec CLI:${commandExists('openspec') ? '已找到' : '未找到,请先安装或运行 openspec init'}`);
183
- console.log('Superpowers:请确认 Codex 可发现 brainstorming / writing-plans / using-git-worktrees 等 skills');
184
- console.log('\n开始使用:在 Codex 中输入 “使用 onespec:<你的任务描述>”。\n');
189
+ console.log(`OpenSpec CLI:${result.openspecCli.status === 'installed' ? '已自动安装' : '已存在'}`);
190
+ console.log(`OpenSpec Tools:${result.openspec.toolIds.join(', ')}`);
191
+ console.log(`Superpowers Agents:${result.superpowers.agents.join(', ')}`);
192
+ for (const platformResult of result.results) {
193
+ const platformLabel = `${platformResult.platformName} (${platformResult.platform})`;
194
+ const skillStatus = platformResult.installedSkill ? '已安装/已覆盖' : '已存在,已跳过';
195
+ console.log(`- ${platformLabel}:${skillStatus} -> ${platformResult.skillPath}`);
196
+ }
197
+ if (result.scope === 'project') {
198
+ console.log(`工作目录:${path.join(result.projectPath, 'docs', 'superpowers')}`);
199
+ }
200
+ console.log('\n开始使用:重启对应 agent 会话后,直接输入 “使用 onespec:<你的任务描述>”。\n');
185
201
  }
186
202
 
187
203
  function printDoctor(report) {
188
204
  console.log('\nOneSpec 环境检查\n');
205
+ console.log(`目标平台:${report.platformName} (${report.platform})`);
189
206
  console.log(`OneSpec Skill:${report.onespec.installed ? '已安装' : '未安装'}`);
190
207
  console.log(`OneSpec 子 Skills:${report.onespec.installedSkills.join(', ') || '无'}`);
191
208
  console.log(`缺少 OneSpec 子 Skills:${report.onespec.missingSkills.join(', ') || '无'}`);
@@ -205,6 +222,10 @@ function printDoctor(report) {
205
222
  export async function main(argv = process.argv.slice(2)) {
206
223
  const { command, options } = parseArgs(argv);
207
224
 
225
+ if (command === 'version' || command === '--version' || command === '-v') {
226
+ console.log(await getPackageVersion());
227
+ return;
228
+ }
208
229
  if (command === 'help' || command === '--help' || command === '-h') {
209
230
  printHelp();
210
231
  return;
@@ -214,8 +235,9 @@ export async function main(argv = process.argv.slice(2)) {
214
235
  }
215
236
 
216
237
  if (command === 'doctor') {
238
+ const doctorPlatforms = parsePlatformList(options.platforms);
217
239
  const report = await doctorProject(options.targetPath, {
218
- platform: options.platform,
240
+ platform: doctorPlatforms[0],
219
241
  scope: options.scope ?? 'project',
220
242
  });
221
243
  if (options.json) {
@@ -230,12 +252,7 @@ export async function main(argv = process.argv.slice(2)) {
230
252
  if (!SUPPORTED_LANGUAGES[initOptions.language]) {
231
253
  throw new Error(`Unsupported language: ${initOptions.language}`);
232
254
  }
233
- const preflight = await doctorProject(initOptions.targetPath, {
234
- platform: initOptions.platform,
235
- scope: initOptions.scope ?? 'project',
236
- });
237
- await ensureRequestedDependencies(initOptions.targetPath, initOptions, preflight);
238
- const result = await initProject(initOptions.targetPath, initOptions);
255
+ const result = await initWorkspace(initOptions.targetPath, initOptions);
239
256
  if (initOptions.json) {
240
257
  console.log(JSON.stringify(result, null, 2));
241
258
  } else {
package/src/doctor.js CHANGED
@@ -3,8 +3,8 @@ import { access, readFile } from 'node:fs/promises';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
 
6
- import { BUNDLED_ONESPEC_SKILLS } from './init.js';
7
- import { getSkillDir, PLATFORMS } from './platforms.js';
6
+ import { BUNDLED_ONESPEC_REFERENCE_FILES, BUNDLED_ONESPEC_SKILLS } from './init.js';
7
+ import { getDiscoveryRoots, getPlatform, getSkillDir } from './platforms.js';
8
8
 
9
9
  const REQUIRED_SUPERPOWERS = [
10
10
  'brainstorming',
@@ -41,8 +41,12 @@ function defaultCommandChecker(command) {
41
41
  function defaultSkillRoots(projectPath, scope, platform) {
42
42
  return [
43
43
  getSkillDir(projectPath, scope, platform),
44
- path.join(os.homedir(), '.codex', 'skills'),
44
+ ...getDiscoveryRoots(projectPath, platform),
45
+ path.join(os.homedir(), '.claude', 'skills'),
45
46
  path.join(os.homedir(), '.codex', 'superpowers', 'skills'),
47
+ path.join(os.homedir(), '.cursor', 'skills'),
48
+ path.join(os.homedir(), '.gemini', 'skills'),
49
+ path.join(os.homedir(), '.copilot', 'skills'),
46
50
  path.join(os.homedir(), '.agents', 'skills'),
47
51
  ];
48
52
  }
@@ -60,7 +64,9 @@ async function isChineseOneSpec(projectPath, scope, platform) {
60
64
  const skillsDir = getSkillDir(projectPath, scope, platform);
61
65
  const installedSkills = [];
62
66
  const missingSkills = [];
67
+ const missingFiles = [];
63
68
  const skillPaths = {};
69
+ const referencePaths = {};
64
70
 
65
71
  for (const skillName of BUNDLED_ONESPEC_SKILLS) {
66
72
  const skillPath = path.join(skillsDir, skillName, 'SKILL.md');
@@ -72,14 +78,24 @@ async function isChineseOneSpec(projectPath, scope, platform) {
72
78
  }
73
79
  }
74
80
 
81
+ for (const referenceFile of BUNDLED_ONESPEC_REFERENCE_FILES) {
82
+ const referencePath = path.join(skillsDir, 'onespec', referenceFile);
83
+ referencePaths[referenceFile] = referencePath;
84
+ if (!(await exists(referencePath))) {
85
+ missingFiles.push(path.join('onespec', referenceFile));
86
+ }
87
+ }
88
+
75
89
  const routerPath = skillPaths.onespec;
76
90
  if (!(await exists(routerPath))) {
77
91
  return {
78
92
  installed: false,
79
93
  skillPath: routerPath,
80
94
  skillPaths,
95
+ referencePaths,
81
96
  installedSkills,
82
97
  missingSkills,
98
+ missingFiles,
83
99
  chinese: false,
84
100
  };
85
101
  }
@@ -88,11 +104,13 @@ async function isChineseOneSpec(projectPath, scope, platform) {
88
104
  const chinese = content.includes('OneSpec 工作流');
89
105
  const english = content.includes('# OneSpec Workflow');
90
106
  return {
91
- installed: missingSkills.length === 0 && (chinese || english),
107
+ installed: missingSkills.length === 0 && missingFiles.length === 0 && (chinese || english),
92
108
  skillPath: routerPath,
93
109
  skillPaths,
110
+ referencePaths,
94
111
  installedSkills,
95
112
  missingSkills,
113
+ missingFiles,
96
114
  chinese,
97
115
  english,
98
116
  language: chinese ? 'zh' : english ? 'en' : 'unknown',
@@ -101,20 +119,16 @@ async function isChineseOneSpec(projectPath, scope, platform) {
101
119
 
102
120
  export async function doctorProject(projectPath, options = {}) {
103
121
  const resolvedProject = path.resolve(projectPath);
104
- const platform = options.platform ?? 'codex';
122
+ const platform = getPlatform(options.platform ?? 'codex');
105
123
  const scope = options.scope ?? 'project';
106
124
  const commandChecker = options.commandChecker ?? defaultCommandChecker;
107
125
 
108
- if (!PLATFORMS[platform]) {
109
- throw new Error(`Unsupported platform "${platform}". Currently only "codex" is supported.`);
110
- }
111
-
112
- const onespec = await isChineseOneSpec(resolvedProject, scope, platform);
126
+ const onespec = await isChineseOneSpec(resolvedProject, scope, platform.id);
113
127
  const skillRoots =
114
128
  options.skillRoots ??
115
129
  [
116
130
  ...new Set([
117
- ...defaultSkillRoots(resolvedProject, scope, platform),
131
+ ...defaultSkillRoots(resolvedProject, scope, platform.id),
118
132
  ...(options.extraSkillRoots ?? []),
119
133
  ]),
120
134
  ];
@@ -139,29 +153,41 @@ export async function doctorProject(projectPath, options = {}) {
139
153
  const nextSteps = [];
140
154
  if (onespec.missingSkills.length > 0) {
141
155
  nextSteps.push(
142
- `缺少 OneSpec Skills:${onespec.missingSkills.join(', ')}。运行 \`onespec init --overwrite\` 补齐 OneSpec Skill bundle。`,
156
+ `缺少 OneSpec Skills:${onespec.missingSkills.join(', ')}。运行 \`onespec init --platform ${platform.id} --overwrite\` 补齐 OneSpec Skill bundle。`,
157
+ );
158
+ } else if (onespec.missingFiles.length > 0) {
159
+ nextSteps.push(
160
+ `缺少 OneSpec references:${onespec.missingFiles.join(', ')}。运行 \`onespec init --platform ${platform.id} --overwrite\` 补齐 OneSpec Skill bundle。`,
143
161
  );
144
162
  } else if (!onespec.installed) {
145
- nextSteps.push('运行 `onespec init --yes` 安装 OneSpec Skill。');
163
+ nextSteps.push(`运行 \`onespec init --platform ${platform.id} --yes\` 安装 OneSpec Skill。`);
146
164
  } else if (!onespec.chinese && !onespec.english) {
147
- nextSteps.push('当前 OneSpec Skill 无法识别语言版本,运行 `onespec init --overwrite` 覆盖安装。');
165
+ nextSteps.push(
166
+ `当前 OneSpec Skill 无法识别语言版本,运行 \`onespec init --platform ${platform.id} --overwrite\` 覆盖安装。`,
167
+ );
148
168
  }
149
169
  if (!openspecCli.available) {
150
- nextSteps.push('未找到 OpenSpec CLI,请安装 OpenSpec CLI 并在目标项目运行 `openspec init`。');
170
+ nextSteps.push(
171
+ `未找到 OpenSpec CLI。运行 \`onespec init --platform ${platform.id} --scope ${scope}\` 让 OneSpec 自动安装并初始化 OpenSpec。`,
172
+ );
151
173
  } else if (scope === 'project' && !openSpecProjectInstalled) {
152
- nextSteps.push('当前项目尚未初始化 OpenSpec,请先运行 `openspec init`。');
174
+ nextSteps.push(
175
+ `当前项目尚未初始化 OpenSpec。请重新运行 \`onespec init --platform ${platform.id} --scope project\` 让 OneSpec 自动补齐。`,
176
+ );
153
177
  }
154
178
  if (!superpowers.available) {
155
- nextSteps.push(`缺少 Superpowers Skills:${missing.join(', ')}。请先安装 Superpowers。`);
179
+ nextSteps.push(
180
+ `缺少 Superpowers Skills:${missing.join(', ')}。运行 \`onespec init --platform ${platform.id} --scope ${scope}\` 让 OneSpec 自动补齐。`,
181
+ );
156
182
  }
157
183
  if (nextSteps.length === 0) {
158
- nextSteps.push('环境检查通过。可以在 Codex 中使用 `onespec` 工作流。');
184
+ nextSteps.push(`环境检查通过。可以在 ${platform.name} 中使用 \`onespec\` 工作流。`);
159
185
  }
160
186
 
161
187
  return {
162
188
  projectPath: resolvedProject,
163
- platform,
164
- platformName: PLATFORMS[platform].name,
189
+ platform: platform.id,
190
+ platformName: platform.name,
165
191
  scope,
166
192
  onespec,
167
193
  openspecCli,