@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.
- package/README.md +45 -48
- package/assets/skills/onespec/SKILL.md +21 -13
- package/assets/skills/onespec/references/archive.md +191 -0
- package/assets/skills/{onespec-design/SKILL.md → onespec/references/design.md} +44 -42
- package/assets/skills/{onespec-execute/SKILL.md → onespec/references/execute.md} +81 -31
- package/assets/skills/onespec/references/fast.md +110 -0
- package/assets/skills/onespec/scripts/onespec-closeout.sh +238 -77
- package/assets/skills/onespec/scripts/onespec-commit.sh +191 -11
- package/assets/skills/onespec/scripts/onespec-handoff.sh +19 -6
- package/assets/skills/onespec/scripts/onespec-state.sh +157 -18
- package/assets/skills/onespec-fast/SKILL.md +22 -0
- package/assets/skills/onespec-fast/agents/openai.yaml +4 -0
- package/assets/skills-en/onespec/SKILL.md +21 -12
- package/assets/skills-en/onespec/references/archive.md +190 -0
- package/assets/skills-en/{onespec-design/SKILL.md → onespec/references/design.md} +44 -42
- package/assets/skills-en/{onespec-execute/SKILL.md → onespec/references/execute.md} +81 -31
- package/assets/skills-en/onespec/references/fast.md +110 -0
- package/assets/skills-en/onespec-fast/SKILL.md +22 -0
- package/package.json +6 -2
- package/scripts/postinstall.js +3 -3
- package/src/cli.js +104 -87
- package/src/doctor.js +46 -20
- package/src/init.js +24 -10
- package/src/platforms.js +88 -8
- package/src/setup.js +211 -0
- package/assets/skills/onespec-archive/SKILL.md +0 -202
- 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.
|
|
4
|
-
"description": "OpenSpec + Superpowers workflow skill installer for
|
|
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",
|
package/scripts/postinstall.js
CHANGED
|
@@ -16,9 +16,9 @@ function main() {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
console.log('\nOneSpec installed. Next run:');
|
|
19
|
-
console.log(' onespec init
|
|
20
|
-
console.log('or
|
|
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 {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
61
|
-
|
|
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
|
-
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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 ||
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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
|
|
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(
|
|
186
|
+
console.log(`目标平台:${result.platformNames.join(', ')}`);
|
|
175
187
|
console.log(`安装范围:${result.scope}`);
|
|
176
188
|
console.log(`Skill 语言:${result.languageName} (${result.language})`);
|
|
177
|
-
console.log(`
|
|
178
|
-
console.log(
|
|
179
|
-
console.log(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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:
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
163
|
+
nextSteps.push(`运行 \`onespec init --platform ${platform.id} --yes\` 安装 OneSpec Skill。`);
|
|
146
164
|
} else if (!onespec.chinese && !onespec.english) {
|
|
147
|
-
nextSteps.push(
|
|
165
|
+
nextSteps.push(
|
|
166
|
+
`当前 OneSpec Skill 无法识别语言版本,运行 \`onespec init --platform ${platform.id} --overwrite\` 覆盖安装。`,
|
|
167
|
+
);
|
|
148
168
|
}
|
|
149
169
|
if (!openspecCli.available) {
|
|
150
|
-
nextSteps.push(
|
|
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(
|
|
174
|
+
nextSteps.push(
|
|
175
|
+
`当前项目尚未初始化 OpenSpec。请重新运行 \`onespec init --platform ${platform.id} --scope project\` 让 OneSpec 自动补齐。`,
|
|
176
|
+
);
|
|
153
177
|
}
|
|
154
178
|
if (!superpowers.available) {
|
|
155
|
-
nextSteps.push(
|
|
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(
|
|
184
|
+
nextSteps.push(`环境检查通过。可以在 ${platform.name} 中使用 \`onespec\` 工作流。`);
|
|
159
185
|
}
|
|
160
186
|
|
|
161
187
|
return {
|
|
162
188
|
projectPath: resolvedProject,
|
|
163
|
-
platform,
|
|
164
|
-
platformName:
|
|
189
|
+
platform: platform.id,
|
|
190
|
+
platformName: platform.name,
|
|
165
191
|
scope,
|
|
166
192
|
onespec,
|
|
167
193
|
openspecCli,
|