@lininn/openflow 0.1.7 → 0.1.8

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 CHANGED
@@ -23,10 +23,25 @@ openflow init --tools claude
23
23
  1. Detect and guide OpenSpec CLI installation
24
24
  2. Detect Superpowers and show install instructions
25
25
  3. Check if OpenSpec is initialized in the project
26
- 4. Generate openflow skills to `.claude/skills/openflow/`
26
+ 4. Generate openflow skills to the selected tools' local skill directories, such as `.claude/skills/openflow/`, `.codex/skills/openflow/`, or `.cursor/skills/openflow/`
27
27
 
28
28
  Supported tools: `claude`, `codex`, `cursor` (comma-separated, e.g. `--tools claude,codex`)
29
29
 
30
+ ### Install skills globally
31
+
32
+ ```bash
33
+ openflow init --tools claude -g
34
+ openflow init --tools claude,codex,cursor --global
35
+ ```
36
+
37
+ With `-g` / `--global`, `openflow` installs skills under the selected tools' home directories:
38
+
39
+ | Tool | Global skill path |
40
+ |------|-------------------|
41
+ | `claude` | `~/.claude/skills/openflow/` |
42
+ | `codex` | `~/.codex/skills/openflow/` |
43
+ | `cursor` | `~/.cursor/skills/openflow/` |
44
+
30
45
  ### Check status
31
46
 
32
47
  ```bash
@@ -69,7 +84,7 @@ Works without them: yes, with manual-file fallback
69
84
 
70
85
  | Layer | Mechanism | When missing |
71
86
  |-------|-----------|-------------|
72
- | **Init time** | Detect OpenSpec auto-install; Detect Superpowers show install hint | Non-blocking, skills still generated |
87
+ | **Init time** | Detect OpenSpec CLI from `PATH`; detect project OpenSpec in `./openspec/`; detect Superpowers in the selected tools' local/global skill dirs | Non-blocking, skills still generated |
73
88
  | **Runtime** | Dependency check injected into SKILL.md | Build phase falls back to manual step-by-step execution |
74
89
 
75
90
  ## Architecture
package/README.zh-CN.md CHANGED
@@ -23,10 +23,25 @@ openflow init --tools claude
23
23
  1. 检测并引导安装 OpenSpec CLI
24
24
  2. 检测 Superpowers 并提示安装方式
25
25
  3. 检测项目 OpenSpec 初始化状态
26
- 4. 生成 openflow skills 到项目 `.claude/skills/openflow/`
26
+ 4. 生成 openflow skills 到所选工具的项目级 skill 目录,如 `.claude/skills/openflow/`、`.codex/skills/openflow/` 或 `.cursor/skills/openflow/`
27
27
 
28
28
  支持的工具:`claude`、`codex`、`cursor`(逗号分隔,如 `--tools claude,codex`)
29
29
 
30
+ ### 安装到全局 skills
31
+
32
+ ```bash
33
+ openflow init --tools claude -g
34
+ openflow init --tools claude,codex,cursor --global
35
+ ```
36
+
37
+ 加 `-g` / `--global` 后,`openflow` 会把 skills 安装到所选工具的全局目录:
38
+
39
+ | 工具 | 全局 skill 路径 |
40
+ |------|-----------------|
41
+ | `claude` | `~/.claude/skills/openflow/` |
42
+ | `codex` | `~/.codex/skills/openflow/` |
43
+ | `cursor` | `~/.cursor/skills/openflow/` |
44
+
30
45
  ### 查看状态
31
46
 
32
47
  ```bash
@@ -69,7 +84,7 @@ Works without them: yes, with manual-file fallback
69
84
 
70
85
  | 层 | 机制 | 缺失时 |
71
86
  |----|------|--------|
72
- | **init 时** | 检测 OpenSpec 自动安装;检测 Superpowers 提示安装命令 | 不阻断,继续生成 skills |
87
+ | **init 时** | 从 `PATH` 检测 OpenSpec CLI;从 `./openspec/` 检测当前项目 OpenSpec;从所选工具的本地/全局 skill 目录检测 Superpowers | 不阻断,继续生成 skills |
73
88
  | **运行时** | SKILL.md 注入依赖检测段 | build 阶段降级为手动拆解步骤执行 |
74
89
 
75
90
  ## 架构
package/dist/cli/init.js CHANGED
@@ -9,15 +9,17 @@ const SUPPORTED_TOOLS = Object.keys(TOOL_PATHS);
9
9
  export const initCommand = new Command('init')
10
10
  .description('Initialize openflow skills in the current project')
11
11
  .option('-t, --tools <tools>', 'Target tools, comma-separated', 'claude')
12
+ .option('-g, --global', 'Install skills globally under home tool directories')
12
13
  .action(async (options) => {
13
14
  const cwd = process.cwd();
14
15
  const tools = options.tools.split(',').map((t) => t.trim());
16
+ const installGlobally = Boolean(options.global);
15
17
  logger.blank();
16
- logger.info('openflow init — workflow orchestrator setup');
18
+ logger.info(`openflow init — ${installGlobally ? 'global skill setup' : 'workflow orchestrator setup'}`);
17
19
  logger.blank();
18
20
  // Step 1: Check OpenSpec
19
21
  logger.step('Checking OpenSpec ...');
20
- let depStatus = checkDependencies();
22
+ let depStatus = checkDependencies({ cwd, tools });
21
23
  if (!depStatus.openspec.installed) {
22
24
  logger.warn('OpenSpec CLI not installed');
23
25
  const { installOpenSpec } = await inquirer.prompt([
@@ -30,7 +32,7 @@ export const initCommand = new Command('init')
30
32
  ]);
31
33
  if (installOpenSpec) {
32
34
  const ok = tryAutoInstall(DEPS.openspec.npmPkg);
33
- depStatus = checkDependencies(); // recheck
35
+ depStatus = checkDependencies({ cwd, tools }); // recheck
34
36
  if (ok)
35
37
  depStatus.openspec.autoInstalled = true;
36
38
  }
@@ -49,44 +51,51 @@ export const initCommand = new Command('init')
49
51
  logger.info('Re-run openflow init after installing, or build phase will use manual fallback');
50
52
  }
51
53
  else {
52
- logger.success('Superpowers installed');
54
+ logger.success(`Superpowers installed${depStatus.superpowers.path ? ` (${depStatus.superpowers.path})` : ''}`);
53
55
  }
54
- // Step 3: Check if OpenSpec is initialized in project
55
- logger.step('Checking project OpenSpec initialization ...');
56
- if (!checkOpenSpecInitialized(cwd)) {
57
- if (depStatus.openspec.installed) {
58
- const { initOpenSpec } = await inquirer.prompt([
59
- {
60
- type: 'confirm',
61
- name: 'initOpenSpec',
62
- message: 'OpenSpec not initialized in this project. Run openspec init?',
63
- default: true,
64
- },
65
- ]);
66
- if (initOpenSpec) {
67
- const toolsFlag = tools.map((t) => t).join(',');
68
- exec(`openspec init --tools ${toolsFlag}`, { stdio: 'inherit' });
69
- logger.success('OpenSpec project initialized');
56
+ if (installGlobally) {
57
+ logger.step('Skipping project OpenSpec initialization for global install');
58
+ }
59
+ else {
60
+ // Step 3: Check if OpenSpec is initialized in project
61
+ logger.step('Checking project OpenSpec initialization ...');
62
+ if (!checkOpenSpecInitialized(cwd)) {
63
+ if (depStatus.openspec.installed) {
64
+ const { initOpenSpec } = await inquirer.prompt([
65
+ {
66
+ type: 'confirm',
67
+ name: 'initOpenSpec',
68
+ message: 'OpenSpec not initialized in this project. Run openspec init?',
69
+ default: true,
70
+ },
71
+ ]);
72
+ if (initOpenSpec) {
73
+ const toolsFlag = tools.map((t) => t).join(',');
74
+ exec(`openspec init --tools ${toolsFlag}`, { stdio: 'inherit' });
75
+ logger.success('OpenSpec project initialized');
76
+ }
77
+ }
78
+ else {
79
+ logger.info('OpenSpec not initialized — directories will be auto-created on first /openflow proposal');
70
80
  }
71
81
  }
72
82
  else {
73
- logger.info('OpenSpec not initialized — directories will be auto-created on first /openflow proposal');
83
+ logger.success('OpenSpec project initialized');
74
84
  }
75
85
  }
76
- else {
77
- logger.success('OpenSpec project initialized');
78
- }
79
86
  // Step 4: Generate skills
80
87
  logger.step('Generating openflow skills ...');
81
- generateSkills({ cwd, tools, depStatus });
82
- // Step 5: Write state
83
- writeState(cwd, {
84
- openspec: depStatus.openspec.installed,
85
- superpowers: depStatus.superpowers.installed,
86
- openspecProjectInitialized: checkOpenSpecInitialized(cwd),
87
- createdAt: new Date().toISOString(),
88
- tools,
89
- });
88
+ generateSkills({ cwd, tools, depStatus, global: installGlobally });
89
+ if (!installGlobally) {
90
+ // Step 5: Write state
91
+ writeState(cwd, {
92
+ openspec: depStatus.openspec.installed,
93
+ superpowers: depStatus.superpowers.installed,
94
+ openspecProjectInitialized: checkOpenSpecInitialized(cwd),
95
+ createdAt: new Date().toISOString(),
96
+ tools,
97
+ });
98
+ }
90
99
  logger.blank();
91
100
  logger.success('openflow initialized!');
92
101
  logger.blank();
@@ -11,9 +11,10 @@ export const statusCommand = new Command('status')
11
11
  logger.blank();
12
12
  logger.info('openflow status');
13
13
  logger.blank();
14
+ const state = readState(cwd);
14
15
  // Dependencies
15
16
  logger.step('Dependencies:');
16
- const depStatus = checkDependencies();
17
+ const depStatus = checkDependencies({ cwd, tools: state?.tools });
17
18
  if (depStatus.openspec.installed) {
18
19
  logger.success(`OpenSpec CLI${depStatus.openspec.version ? ` v${depStatus.openspec.version}` : ''}`);
19
20
  }
@@ -21,7 +22,7 @@ export const statusCommand = new Command('status')
21
22
  logger.warn('OpenSpec CLI — not installed');
22
23
  }
23
24
  if (depStatus.superpowers.installed) {
24
- logger.success('Superpowers');
25
+ logger.success(`Superpowers${depStatus.superpowers.path ? ` (${depStatus.superpowers.path})` : ''}`);
25
26
  }
26
27
  else {
27
28
  logger.warn('Superpowers — not installed (build phase will use manual mode)');
@@ -29,7 +30,6 @@ export const statusCommand = new Command('status')
29
30
  logger.blank();
30
31
  // Project state
31
32
  logger.step('Project:');
32
- const state = readState(cwd);
33
33
  if (state) {
34
34
  logger.success(`Initialized (${state.tools.join(', ')})`);
35
35
  logger.info(` Created at: ${state.createdAt}`);
@@ -14,7 +14,7 @@ export const updateCommand = new Command('update')
14
14
  logger.blank();
15
15
  logger.info('openflow update — regenerating skills');
16
16
  logger.blank();
17
- const depStatus = checkDependencies();
17
+ const depStatus = checkDependencies({ cwd, tools: state.tools });
18
18
  generateSkills({
19
19
  cwd,
20
20
  tools: state.tools,
@@ -13,7 +13,7 @@ export declare const DEPS: {
13
13
  readonly superpowers: {
14
14
  readonly name: "Superpowers";
15
15
  readonly checkPath: "writing-plans/SKILL.md";
16
- readonly installHint: "请在 Claude Code 中执行: /plugin install superpowers@claude-plugins-official";
16
+ readonly installHint: "请在当前工具中安装 Superpowers writing-plans skill(Claude Code: /plugin install superpowers@claude-plugins-official";
17
17
  readonly autoInstallable: false;
18
18
  };
19
19
  };
@@ -13,7 +13,7 @@ export const DEPS = {
13
13
  superpowers: {
14
14
  name: 'Superpowers',
15
15
  checkPath: 'writing-plans/SKILL.md',
16
- installHint: '请在 Claude Code 中执行: /plugin install superpowers@claude-plugins-official',
16
+ installHint: '请在当前工具中安装 Superpowers writing-plans skill(Claude Code: /plugin install superpowers@claude-plugins-official',
17
17
  autoInstallable: false,
18
18
  },
19
19
  };
@@ -7,9 +7,15 @@ export interface DepStatus {
7
7
  superpowers: {
8
8
  installed: boolean;
9
9
  hint?: string;
10
+ path?: string;
11
+ checkedPaths: string[];
10
12
  };
11
13
  }
12
- export declare function checkDependencies(): DepStatus;
14
+ export interface CheckDependencyOptions {
15
+ cwd?: string;
16
+ tools?: string[];
17
+ }
18
+ export declare function checkDependencies(options?: CheckDependencyOptions): DepStatus;
13
19
  export declare function tryAutoInstall(pkg: string): boolean;
14
20
  export declare function checkOpenSpecInitialized(cwd: string): boolean;
15
21
  export interface InitState {
@@ -1,21 +1,24 @@
1
1
  import { execSync } from 'child_process';
2
2
  import { cmdExists, fileExists, dirExists, exec } from '../utils/shell.js';
3
- import { DEPS } from './constants.js';
3
+ import { DEPS, TOOL_PATHS } from './constants.js';
4
4
  import { logger } from '../utils/logger.js';
5
5
  import path from 'path';
6
6
  import os from 'os';
7
7
  import fs from 'fs';
8
- export function checkDependencies() {
8
+ export function checkDependencies(options = {}) {
9
9
  const home = os.homedir();
10
+ const cwd = options.cwd ?? process.cwd();
11
+ const tools = options.tools?.length ? options.tools : Object.keys(TOOL_PATHS);
10
12
  // Check OpenSpec
11
13
  const openspecInstalled = cmdExists(DEPS.openspec.cliCmd);
12
14
  let openspecVersion;
13
15
  if (openspecInstalled) {
14
16
  openspecVersion = exec('openspec --version') || undefined;
15
17
  }
16
- // Check Superpowers
17
- const superpowersSkillPath = path.join(home, '.claude', 'skills', DEPS.superpowers.checkPath);
18
- const superpowersInstalled = fileExists(superpowersSkillPath);
18
+ // Check Superpowers in the selected tools' local and global skill dirs.
19
+ const superpowersSkillPaths = getSuperpowersSkillPaths(cwd, home, tools);
20
+ const superpowersSkillPath = superpowersSkillPaths.find((candidate) => fs.existsSync(candidate));
21
+ const superpowersInstalled = Boolean(superpowersSkillPath);
19
22
  return {
20
23
  openspec: {
21
24
  installed: openspecInstalled,
@@ -24,9 +27,22 @@ export function checkDependencies() {
24
27
  superpowers: {
25
28
  installed: superpowersInstalled,
26
29
  hint: superpowersInstalled ? undefined : DEPS.superpowers.installHint,
30
+ path: superpowersSkillPath,
31
+ checkedPaths: superpowersSkillPaths,
27
32
  },
28
33
  };
29
34
  }
35
+ function getSuperpowersSkillPaths(cwd, home, tools) {
36
+ const candidates = new Set();
37
+ for (const tool of tools) {
38
+ const toolPaths = TOOL_PATHS[tool];
39
+ if (!toolPaths)
40
+ continue;
41
+ candidates.add(path.join(cwd, toolPaths.skillsDir, DEPS.superpowers.checkPath));
42
+ candidates.add(path.join(home, toolPaths.skillsDir, DEPS.superpowers.checkPath));
43
+ }
44
+ return [...candidates];
45
+ }
30
46
  export function tryAutoInstall(pkg) {
31
47
  logger.step(`Installing ${pkg} ...`);
32
48
  try {
@@ -43,21 +59,29 @@ export function checkOpenSpecInitialized(cwd) {
43
59
  return dirExists(path.join(cwd, 'openspec'));
44
60
  }
45
61
  export function readState(cwd) {
46
- const stateFile = path.join(cwd, '.claude', 'openflow-state.json');
47
- if (!fileExists(stateFile))
48
- return null;
49
- try {
50
- return JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
51
- }
52
- catch {
53
- return null;
62
+ for (const stateFile of getStateFileCandidates(cwd)) {
63
+ if (!fileExists(stateFile))
64
+ continue;
65
+ try {
66
+ return JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
67
+ }
68
+ catch {
69
+ return null;
70
+ }
54
71
  }
72
+ return null;
55
73
  }
56
74
  export function writeState(cwd, state) {
57
- const stateDir = path.join(cwd, '.claude');
75
+ const stateDir = path.join(cwd, '.openflow');
58
76
  if (!dirExists(stateDir)) {
59
77
  fs.mkdirSync(stateDir, { recursive: true });
60
78
  }
61
- const stateFile = path.join(stateDir, 'openflow-state.json');
79
+ const stateFile = path.join(stateDir, 'state.json');
62
80
  fs.writeFileSync(stateFile, JSON.stringify(state, null, 2) + '\n');
63
81
  }
82
+ function getStateFileCandidates(cwd) {
83
+ return [
84
+ path.join(cwd, '.openflow', 'state.json'),
85
+ path.join(cwd, '.claude', 'openflow-state.json'),
86
+ ];
87
+ }
@@ -3,5 +3,6 @@ export interface GenerateOptions {
3
3
  cwd: string;
4
4
  tools: string[];
5
5
  depStatus: DepStatus;
6
+ global?: boolean;
6
7
  }
7
8
  export declare function generateSkills(options: GenerateOptions): void;
@@ -1,7 +1,8 @@
1
1
  import fs from 'fs';
2
+ import os from 'os';
2
3
  import path from 'path';
3
4
  import { fileURLToPath } from 'url';
4
- import { fileExists, dirExists } from '../utils/shell.js';
5
+ import { fileExists } from '../utils/shell.js';
5
6
  import { logger } from '../utils/logger.js';
6
7
  import { SKILL_NAME, TOOL_PATHS, DEPS } from './constants.js';
7
8
  const __filename = fileURLToPath(import.meta.url);
@@ -9,16 +10,20 @@ const __dirname = path.dirname(__filename);
9
10
  // Resolve templates dir: from dist/core/ → ../../templates/
10
11
  const TEMPLATES_DIR = path.resolve(__dirname, '..', '..', 'templates');
11
12
  export function generateSkills(options) {
12
- const { cwd, tools, depStatus } = options;
13
+ const { cwd, tools, depStatus, global = false } = options;
14
+ const baseDir = global ? os.homedir() : cwd;
13
15
  for (const tool of tools) {
14
16
  const toolPaths = TOOL_PATHS[tool];
15
17
  if (!toolPaths) {
16
18
  logger.warn(`Unknown tool: ${tool}, skipping`);
17
19
  continue;
18
20
  }
19
- const skillsDir = path.join(cwd, toolPaths.skillsDir, SKILL_NAME);
20
- logger.step(`Generating ${tool} skills to ${path.relative(cwd, skillsDir)}/`);
21
- if (!dirExists(skillsDir)) {
21
+ const skillsDir = path.join(baseDir, toolPaths.skillsDir, SKILL_NAME);
22
+ const displayPath = global
23
+ ? path.join('~', toolPaths.skillsDir, SKILL_NAME)
24
+ : path.relative(cwd, skillsDir);
25
+ logger.step(`Generating ${tool} skills to ${displayPath}/`);
26
+ if (!fs.existsSync(skillsDir)) {
22
27
  fs.mkdirSync(skillsDir, { recursive: true });
23
28
  }
24
29
  // Generate main SKILL.md
@@ -61,7 +66,7 @@ function injectRuntimeDepCheck(content, depStatus) {
61
66
 
62
67
  | 依赖 | 检测方式 | 不可用时 |
63
68
  |------|----------|----------|
64
- | Superpowers writing-plans | \`~/.claude/skills/writing-plans/SKILL.md\` 是否存在 | 降级为手动拆解 plan-ready.md 中的步骤,逐条执行 |
69
+ | Superpowers writing-plans | 当前工具的本地或全局 skills 目录下是否存在 \`writing-plans/SKILL.md\` | 降级为手动拆解 plan-ready.md 中的步骤,逐条执行 |
65
70
  | OpenSpec CLI | \`openspec\` 命令是否可执行 | 不影响 build 阶段,但 close 阶段归档需手动 mv |
66
71
 
67
72
  如果 Superpowers 不可用,提示用户:
@@ -82,12 +87,12 @@ function injectRuntimeDepCheck(content, depStatus) {
82
87
  }
83
88
  function injectSpecRuntimeCheck(content, depStatus) {
84
89
  const checkNote = `
85
- > **OpenSpec 检测**:如果 \`openspec\` CLI 可用,调用 \`openspec propose\` 生成完整规格;否则手动根据 proposal.md 生成 design.md + specs/ + tasks.md
90
+ > **OpenSpec 检测**:根据 proposal.md 生成 design.md + specs/ + tasks.md;如果 \`openspec\` CLI 可用,生成后运行 \`openspec validate <变更名> --strict\` 校验。
86
91
  `;
87
92
  const lines = content.split('\n');
88
- const proposeIdx = lines.findIndex((l) => l.includes('openspec propose'));
89
- if (proposeIdx >= 0) {
90
- lines.splice(proposeIdx + 1, 0, checkNote);
93
+ const validateIdx = lines.findIndex((l) => l.includes('openspec validate'));
94
+ if (validateIdx >= 0) {
95
+ lines.splice(validateIdx, 0, checkNote);
91
96
  }
92
97
  return lines.join('\n');
93
98
  }
@@ -136,7 +141,7 @@ description: "OpenSpec + Superpowers 工作流协调器。使用 /openflow propo
136
141
 
137
142
  1. 如果用户指定了子命令(如 \`/openflow build\`),优先按指定阶段执行,但检查前置条件
138
143
  2. 如果用户只输入 \`/openflow\`,执行状态检测,自动路由到对应阶段
139
- 3. 读取阶段文件:\`\${CLAUDE_SKILL_DIR}/<阶段>.md\`
144
+ 3. 读取当前 openflow skill 目录下的阶段文件:\`<阶段>.md\`(与本 \`SKILL.md\` 同目录;不要依赖 Claude 专属环境变量)
140
145
  4. 按阶段文件中的流程执行
141
146
 
142
147
  ### 前置条件检查
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lininn/openflow",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "OpenSpec + Superpowers workflow orchestrator for Claude Code",
5
5
  "bin": {
6
6
  "openflow": "./bin/openflow.js"
@@ -41,7 +41,7 @@ description: "OpenSpec + Superpowers workflow orchestrator. Use /openflow propos
41
41
 
42
42
  1. 如果用户指定了子命令(如 `/openflow build`),优先按指定阶段执行,但检查前置条件
43
43
  2. 如果用户只输入 `/openflow`,执行状态检测,自动路由到对应阶段
44
- 3. 读取阶段文件:`${CLAUDE_SKILL_DIR}/<阶段>.md`
44
+ 3. 读取当前 openflow skill 目录下的阶段文件:`<阶段>.md`(与本 `SKILL.md` 同目录;不要依赖 Claude 专属环境变量)
45
45
  4. 按阶段文件中的流程执行
46
46
 
47
47
  ### 前置条件检查
@@ -37,22 +37,22 @@ description: Deep design — multi-round exploration to confirm architecture and
37
37
 
38
38
  > "确认的设计方向:[方案名]。核心决策:[2-3 条]。这样对吗?"
39
39
 
40
- ### 5. 创建 OpenSpec 变更
40
+ ### 5. 创建 OpenSpec 变更目录
41
41
 
42
- 用户确认后,调用 OpenSpec 创建变更:
42
+ 用户确认后,按 OpenSpec 目录约定创建变更。`<变更名>` 使用 kebab-case、动词开头(如 `add-user-login`):
43
43
 
44
44
  ```bash
45
- openspec new <变更名>
45
+ mkdir -p openspec/changes/<变更名>/specs
46
46
  ```
47
47
 
48
- 如果 OpenSpec CLI 不可用,手动创建目录结构:
48
+ 将确认的需求描述和设计方向写入 `openspec/changes/<变更名>/proposal.md`。
49
+
50
+ 如果 OpenSpec CLI 可用,可用以下命令检查当前变更列表:
49
51
 
50
52
  ```bash
51
- mkdir -p openspec/changes/<变更名>/specs
53
+ openspec list
52
54
  ```
53
55
 
54
- 将确认的需求描述和设计方向写入 `openspec/changes/<变更名>/proposal.md`。
55
-
56
56
  ### 6. 提示下一步
57
57
 
58
58
  > "需求已记录。接下来可以用 `/openflow spec` 生成完整规格。"
@@ -52,10 +52,16 @@ description: Verify implementation consistency and archive
52
52
 
53
53
  ### 5. 归档
54
54
 
55
- 全部一致(或用户接受不一致项)后,执行归档:
55
+ 全部一致(或用户接受不一致项)后,先校验变更:
56
56
 
57
57
  ```bash
58
- openspec archive <变更名>
58
+ openspec validate <变更名> --strict
59
+ ```
60
+
61
+ 校验通过后执行归档:
62
+
63
+ ```bash
64
+ openspec archive <变更名> --yes
59
65
  ```
60
66
 
61
67
  如果 OpenSpec CLI 不可用,手动归档:
@@ -27,22 +27,22 @@ description: Lightweight requirement capture — 3-5 questions to quickly conver
27
27
 
28
28
  > "我理解的需求是:[一句话概括]。具体来说:[2-3 条要点]。这样理解对吗?"
29
29
 
30
- ### 3. 创建 OpenSpec 变更
30
+ ### 3. 创建 OpenSpec 变更目录
31
31
 
32
- 用户确认后,调用 OpenSpec 创建变更:
32
+ 用户确认后,按 OpenSpec 目录约定创建变更。`<变更名>` 使用 kebab-case、动词开头(如 `add-user-login`):
33
33
 
34
34
  ```bash
35
- openspec new <变更名>
35
+ mkdir -p openspec/changes/<变更名>/specs
36
36
  ```
37
37
 
38
- 如果 OpenSpec CLI 不可用,手动创建目录结构:
38
+ 将确认的需求描述写入 `openspec/changes/<变更名>/proposal.md`。
39
+
40
+ 如果 OpenSpec CLI 可用,可用以下命令检查当前变更列表:
39
41
 
40
42
  ```bash
41
- mkdir -p openspec/changes/<变更名>/specs
43
+ openspec list
42
44
  ```
43
45
 
44
- 将确认的需求描述写入 `openspec/changes/<变更名>/proposal.md`。
45
-
46
46
  ### 4. 提示下一步
47
47
 
48
48
  > "需求已记录。接下来可以用 `/openflow spec` 生成完整规格,或继续补充细节。"
package/templates/spec.md CHANGED
@@ -26,21 +26,23 @@ description: Call OpenSpec to generate specs, auto-translate to plan-ready.md af
26
26
  如果有多个,列出并让用户选择:
27
27
  > "检测到多个活跃变更:[列表]。要对哪个生成规格?"
28
28
 
29
- ### 2. 调用 OpenSpec 生成规格
29
+ ### 2. 生成 OpenSpec 规格文件
30
30
 
31
- 调用 OpenSpec 的 propose 命令(或 OPSX 工作流)生成完整规格:
32
-
33
- ```bash
34
- openspec propose <变更名>
35
- ```
36
-
37
- 如果 OpenSpec CLI 不可用,手动根据 proposal.md 的内容生成以下文件:
31
+ 根据 proposal.md 的内容生成或补齐以下文件:
38
32
 
39
33
  - `openspec/changes/<变更名>/proposal.md` — 已存在,可补充
40
34
  - `openspec/changes/<变更名>/design.md` — 技术方案
41
35
  - `openspec/changes/<变更名>/specs/` — 具体规格变更(标记新增/修改/删除)
42
36
  - `openspec/changes/<变更名>/tasks.md` — 实现任务清单
43
37
 
38
+ 如果 OpenSpec CLI 可用,生成后运行校验:
39
+
40
+ ```bash
41
+ openspec validate <变更名> --strict
42
+ ```
43
+
44
+ 如果校验失败,根据错误修正上述文件后重新校验。
45
+
44
46
  ### 3. 与用户确认规格
45
47
 
46
48
  展示规格摘要,逐项确认: