@gordon.gan/specflow 1.0.0 → 1.0.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 (66) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +52 -38
  3. package/dist/cli/commands/change-phase.d.ts +1 -1
  4. package/dist/cli/commands/change-phase.js +1 -1
  5. package/dist/cli/commands/change-status.js +15 -6
  6. package/dist/core/archive.js +2 -2
  7. package/dist/core/artifact-graph/explore-status.d.ts +5 -0
  8. package/dist/core/artifact-graph/explore-status.js +21 -0
  9. package/dist/core/artifact-graph/index.d.ts +3 -1
  10. package/dist/core/artifact-graph/index.js +2 -1
  11. package/dist/core/artifact-graph/outputs.d.ts +3 -3
  12. package/dist/core/artifact-graph/outputs.js +24 -6
  13. package/dist/core/artifact-graph/state.d.ts +4 -0
  14. package/dist/core/artifact-graph/state.js +22 -0
  15. package/dist/core/templates/index.js +1 -0
  16. package/dist/core/templates/types.d.ts +1 -1
  17. package/dist/integrations/codex/adapter.js +1 -1
  18. package/dist/integrations/cursor/adapter.js +1 -1
  19. package/dist/integrations/shared/capability-evidence.js +12 -8
  20. package/dist/integrations/shared/command-catalog.js +4 -4
  21. package/dist/integrations/shared/parity-manifest.js +12 -8
  22. package/dist/utils/change-metadata.d.ts +1 -1
  23. package/dist/utils/change-metadata.js +1 -1
  24. package/package.json +5 -8
  25. package/prompts/{build → apply}/ecc-java-reviewer.md +1 -1
  26. package/prompts/{build → apply}/language-router.md +6 -6
  27. package/prompts/{build → apply}/phase-a-plan.md +14 -14
  28. package/prompts/{build → apply}/phase-b-execute.md +17 -17
  29. package/prompts/{build → apply}/phase-b-review.md +2 -2
  30. package/prompts/{build → apply}/phase-b-worktree.md +3 -3
  31. package/prompts/{build → apply}/plan-document-reviewer.md +6 -6
  32. package/prompts/{build → apply}/tdd.md +3 -3
  33. package/prompts/{done → archive}/branch-finish.md +4 -4
  34. package/prompts/explore/explore-session.md +152 -0
  35. package/prompts/fix/debug.md +2 -2
  36. package/prompts/{plan → propose}/design-draft.md +1 -1
  37. package/prompts/{plan → propose}/proposal.md +30 -0
  38. package/prompts/{plan → propose}/specs.md +1 -1
  39. package/prompts/{plan → propose}/tasks-draft.md +9 -9
  40. package/prompts/reference/specflow/example-design.md +20 -20
  41. package/prompts/reference/superpowers/anthropic-best-practices.md +6 -6
  42. package/prompts/reference/superpowers/codex-tools.md +1 -1
  43. package/prompts/refine/brainstorm.md +15 -15
  44. package/prompts/refine/design-output.md +1 -1
  45. package/prompts/refine/spec-document-reviewer.md +1 -1
  46. package/prompts/review/code-review.md +4 -4
  47. package/prompts/shared/code-reviewer-prompt.md +1 -1
  48. package/prompts/shared/executing-plans.md +12 -12
  49. package/prompts/shared/implementer-prompt.md +3 -3
  50. package/prompts/test/verification.md +1 -1
  51. package/prompts/verify/verification.md +3 -3
  52. package/schemas/specflow/schema.yaml +6 -4
  53. package/skills/{specflow-build → specflow-apply}/SKILL.md +12 -12
  54. package/skills/{specflow-done → specflow-archive}/SKILL.md +5 -5
  55. package/skills/specflow-explore/SKILL.md +102 -0
  56. package/skills/specflow-fix/SKILL.md +2 -2
  57. package/skills/{specflow-plan → specflow-propose}/SKILL.md +38 -11
  58. package/skills/specflow-refine/SKILL.md +4 -4
  59. package/skills/specflow-verify/SKILL.md +1 -1
  60. package/templates/explore.md +89 -0
  61. package/skills/specflow-scan/SKILL.md +0 -48
  62. /package/prompts/{build → apply}/ecc-go-reviewer.md +0 -0
  63. /package/prompts/{build → apply}/ecc-kotlin-reviewer.md +0 -0
  64. /package/prompts/{build → apply}/ecc-python-reviewer.md +0 -0
  65. /package/prompts/{build → apply}/ecc-rust-reviewer.md +0 -0
  66. /package/prompts/{build → apply}/ecc-typescript-reviewer.md +0 -0
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 bstzyf
3
+ Copyright (c) 2026 Gordon-Gan-Jiang
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # SpecFlow
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/specflow.svg)](https://www.npmjs.com/package/specflow)
4
- [![npm downloads](https://img.shields.io/npm/dm/specflow.svg)](https://www.npmjs.com/package/specflow)
3
+ [![npm version](https://img.shields.io/npm/v/@gordon.gan/specflow.svg)](https://www.npmjs.com/package/@gordon.gan/specflow)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@gordon.gan/specflow.svg)](https://www.npmjs.com/package/@gordon.gan/specflow)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
6
 
7
7
  **一句话:plan 想清楚,refine 打磨透,build 做到位,done 归档好。**
@@ -30,10 +30,10 @@ SpecFlow 把两个开源框架合并成一个工具:
30
30
 
31
31
  ## 安装
32
32
 
33
- ### 方式 1:从 npm 全局安装(推荐,发布后可用)
33
+ ### 方式 1:从 npm 全局安装(推荐)
34
34
 
35
35
  ```bash
36
- npm install -g specflow
36
+ npm install -g @gordon.gan/specflow
37
37
  ```
38
38
 
39
39
  ### 方式 2:从 GitHub 直接安装(发布前/最新代码)
@@ -59,25 +59,25 @@ npm link
59
59
  在有源码的机器上打包:
60
60
  ```bash
61
61
  npm run build
62
- npm pack # 生成 specflow-0.2.2.tgz
62
+ npm pack # 生成 gordon.gan-specflow-1.0.2.tgz
63
63
  ```
64
64
  在目标机器上安装:
65
65
  ```bash
66
- npm install -g ./specflow-0.2.2.tgz
66
+ npm install -g ./gordon.gan-specflow-1.0.2.tgz
67
67
  ```
68
68
 
69
69
  ### 验证安装
70
70
 
71
71
  ```bash
72
- specflow --version # 应输出 0.2.2
72
+ specflow --version # 应输出 1.0.2
73
73
  specflow --help
74
74
  ```
75
75
 
76
76
  ### 卸载
77
77
 
78
78
  ```bash
79
- npm uninstall -g specflow
80
- # 如果用的是 npm link
79
+ npm uninstall -g @gordon.gan/specflow
80
+ # 如果用的是 npm link(本地源码安装):
81
81
  npm unlink -g specflow
82
82
  ```
83
83
 
@@ -125,9 +125,9 @@ specflow init
125
125
  **每阶段深度思考 → 多轮迭代精化**:每个阶段一次性产出实质内容(非占位骨架),再通过内部多轮迭代把 artifact 精化到稳态,最后进入执行。
126
126
 
127
127
  ```
128
- /specflow:scan [规划中 · v0.3] 扫描已有代码生成 specs 基线;当前请直接用 /specflow:plan 描述已有行为
128
+ /specflow:explore [可选] 需求模糊时:读代码、比方案、定边界 产出 explore.md
129
129
 
130
- /specflow:plan 一次产出 4 个 artifact:proposal + delta specs + design + tasks(first-iteration 深度思考,非纯骨架)
130
+ /specflow:propose 一次产出 4 个 artifact:proposal + delta specs + design + tasks(first-iteration 深度思考,非纯骨架)
131
131
 
132
132
  /specflow:refine 内部多轮精化循环(≥2 rounds,AI 收敛判定,不设上限)
133
133
  每轮攻击性审查 + 4 个挑战行为:
@@ -135,7 +135,7 @@ specflow init
135
135
  · 探边界 · 质疑 scope
136
136
  可更新任意 artifact(proposal / specs / design / tasks)
137
137
 
138
- /specflow:build Phase A:基于 refine 稳定后的 artifact,用 writing-plans 严格精化(rewrite)tasks.md
138
+ /specflow:apply Phase A:基于 refine 稳定后的 artifact,用 writing-plans 严格精化(rewrite)tasks.md
139
139
  Phase B:逐任务 TDD 执行(subagent 模式)
140
140
  design 有漏即停,回 refine 补齐
141
141
 
@@ -145,7 +145,7 @@ specflow init
145
145
 
146
146
  /specflow:verify 双重校验(delta specs 验收 + 主 specs 回归)
147
147
 
148
- /specflow:done 归档变更 → specs 合并 → git 分支清理
148
+ /specflow:archive 归档变更 → specs 合并 → git 分支清理
149
149
  默认要求 phase=built,可用 --force 跳过
150
150
  ```
151
151
 
@@ -170,7 +170,7 @@ specflow init
170
170
 
171
171
  ```bash
172
172
  # 1. 安装(任选一种)
173
- npm install -g specflow
173
+ npm install -g @gordon.gan/specflow
174
174
 
175
175
  # 2. 进入你的项目
176
176
  cd my-project
@@ -178,12 +178,19 @@ cd my-project
178
178
  # 3. 初始化
179
179
  specflow init
180
180
 
181
- # 4. 打开 Claude Code,在会话里输入:
182
- /specflow:plan "给用户管理模块加个批量导入功能"
181
+ # 4. 打开 Claude Code,在会话里输入(二选一):
182
+
183
+ # 需求已清晰 — 直接 plan:
184
+ /specflow:propose "给用户管理模块加个批量导入功能"
185
+
186
+ # 需求模糊 — 先 explore 再 propose:
187
+ /specflow:explore "批量导入时不确定该用 CSV 还是 Excel,也不清楚现有用户表结构"
188
+ # 确认 explore.md 后:
189
+ /specflow:propose
183
190
  ```
184
191
 
185
192
  Claude Code 接下来会:
186
- 1. 读 `.claude/skills/specflow-plan/SKILL.md` 编排器
193
+ 1. 读 `.claude/skills/specflow-propose/SKILL.md` 编排器
187
194
  2. 问你几个澄清问题(一次一个)
188
195
  3. 生成 `specflow/changes/bulk-import/proposal.md`
189
196
  4. 展示 proposal 等你**确认**
@@ -192,16 +199,27 @@ Claude Code 接下来会:
192
199
 
193
200
  ### 更完整的示例(4 个场景)
194
201
 
195
- #### 场景 1:新项目从零开始
202
+ #### 场景 1:新项目从零开始(需求已清晰)
196
203
 
197
204
  ```
198
- /specflow:plan "添加用户注册和登录功能"
205
+ /specflow:propose "添加用户注册和登录功能"
199
206
  /specflow:refine # 讨论技术方案,输出 design.md
200
- /specflow:build # TDD 实现,每任务用户确认
207
+ /specflow:apply # TDD 实现,每任务用户确认
201
208
  /specflow:review # 代码审查
202
209
  /specflow:test # 跑测试
203
210
  /specflow:verify # 对照 specs 验收
204
- /specflow:done # 归档 + merge
211
+ /specflow:archive # 归档 + merge
212
+ ```
213
+
214
+ #### 场景 1b:需求模糊,先探索再规划
215
+
216
+ ```
217
+ /specflow:explore "登录有时返回 500,不确定是 token 校验还是 session 存储的问题"
218
+ # AI 读代码、比方案、产出 explore.md;你确认方向后:
219
+ /specflow:propose # 读取 explore.md,合成 proposal + specs + design + tasks
220
+ /specflow:refine
221
+ /specflow:apply
222
+ /specflow:archive
205
223
  ```
206
224
 
207
225
  #### 场景 2:接手已有项目(v0.2.x 工作流)
@@ -211,13 +229,13 @@ cd legacy-project
211
229
  specflow init
212
230
 
213
231
  # 在 Claude Code 里:
214
- # 注意:/specflow:scan 当前规划在 v0.3,未实现。
215
- # v0.2.x 的推荐做法是直接进 plan,在 proposal Q&A 里描述已有行为 +
216
- # 新增变更,让 plan 为将要改动的 capability 生成 delta spec 基线。
217
- /specflow:plan "描述已有行为 + 你这次要改的新功能"
218
- /specflow:refine # 对 plan 产出做 ≥2 轮攻击性审查
219
- /specflow:build # Phase A 重写 tasks.md → Phase B TDD
220
- /specflow:done # 归档,delta spec 合入主 specs/
232
+ # 棕地项目:需求模糊时先 /specflow:explore;或需求清晰时直接 /specflow:propose,
233
+ # proposal Q&A 里描述已有行为 + 新增变更,让 propose 为将要改动的 capability 生成 delta spec 基线。
234
+ /specflow:explore "描述模糊问题或不确定的改法" # 可选
235
+ /specflow:propose "描述已有行为 + 你这次要改的新功能"
236
+ /specflow:refine # 对 propose 产出做 ≥2 轮攻击性审查
237
+ /specflow:apply # Phase A 重写 tasks.md → Phase B TDD
238
+ /specflow:archive # 归档,delta spec 合入主 specs/
221
239
  ```
222
240
 
223
241
  #### 场景 3:紧急修 Bug
@@ -256,14 +274,14 @@ specflow init
256
274
 
257
275
  | 命令 | 说明 |
258
276
  |---|---|
259
- | `/specflow:scan` | **[规划中 · v0.3]** 扫描已有代码库生成 specs 基线;v0.2.x 未实现,触发时 skill 会提示替代方案 |
260
- | `/specflow:plan` | 需求规划:生成 proposal + delta specs |
277
+ | `/specflow:explore` | **需求模糊时** 先探索:读代码、比方案、定边界,产出 `explore.md`,再 handoff propose |
278
+ | `/specflow:propose` | 需求规划:生成 proposal + delta specs |
261
279
  | `/specflow:refine` | 技术方案探讨(brainstorming + design.md) |
262
- | `/specflow:build` | 两阶段构建:生成计划 → subagent TDD 执行 |
280
+ | `/specflow:apply` | 两阶段构建:生成计划 → subagent TDD 执行 |
263
281
  | `/specflow:review` | 代码审查(含 specs 回归检查) |
264
282
  | `/specflow:test` | 全量测试 + 验证(单元/集成/E2E/回归) |
265
283
  | `/specflow:verify` | 双重校验:delta specs 验收 + 主 specs 回归 |
266
- | `/specflow:done` | 归档变更 + specs 合并 + git 分支清理 |
284
+ | `/specflow:archive` | 归档变更 + specs 合并 + git 分支清理 |
267
285
  | `/specflow:fix` | 修 Bug 快速通道(调试 → TDD → 归档) |
268
286
  | `/specflow:snap` | 事后补档(从 git diff 反推变更记录) |
269
287
 
@@ -403,7 +421,7 @@ your-project/
403
421
 
404
422
  ### `specflow: command not found`
405
423
 
406
- - 确认 `npm install -g` 成功
424
+ - 确认 `npm install -g @gordon.gan/specflow` 成功
407
425
  - 检查 `npm config get prefix` 下的 `bin` 目录是否在 PATH 里
408
426
 
409
427
  ### `No specflow project found at or above <dir>`
@@ -422,13 +440,9 @@ SpecFlow 需要 Node ≥ 20.19.0。用 `nvm` 切版本:
422
440
  ```bash
423
441
  nvm install 20
424
442
  nvm use 20
425
- npm install -g specflow
443
+ npm install -g @gordon.gan/specflow
426
444
  ```
427
445
 
428
- ### code-review-graph 安装失败
429
-
430
- `/specflow:scan` 规划在 v0.3 实现时会用到 code-review-graph(当前在 optionalDependencies 里占位)。这个依赖是从 GitHub 安装的,如果网络问题导致失败,**不影响任何 v0.2.x 功能**——scan 本身在 v0.2.x 不可用,其余所有命令与此依赖无关。
431
-
432
446
  ---
433
447
 
434
448
  ## 来源与许可
@@ -28,6 +28,6 @@ export declare function setPhase(name: string, phase: string, projectRoot: strin
28
28
  *
29
29
  * Usage:
30
30
  * specflow change phase <name> # print current phase
31
- * specflow change phase <name> --set plan # update phase
31
+ * specflow change phase <name> --set propose # update phase
32
32
  */
33
33
  export declare function registerChangePhaseCommand(changeCmd: Command): void;
@@ -46,7 +46,7 @@ export async function setPhase(name, phase, projectRoot) {
46
46
  *
47
47
  * Usage:
48
48
  * specflow change phase <name> # print current phase
49
- * specflow change phase <name> --set plan # update phase
49
+ * specflow change phase <name> --set propose # update phase
50
50
  */
51
51
  export function registerChangePhaseCommand(changeCmd) {
52
52
  changeCmd
@@ -7,7 +7,7 @@ import { join } from 'node:path';
7
7
  import * as fs from 'node:fs';
8
8
  import yaml from 'js-yaml';
9
9
  import { resolveSchema } from '../../core/artifact-graph/resolver.js';
10
- import { getCompletedArtifacts } from '../../core/artifact-graph/state.js';
10
+ import { getCompletedArtifacts, getInProgressArtifacts } from '../../core/artifact-graph/state.js';
11
11
  import { formatStatus } from '../../core/artifact-graph/outputs.js';
12
12
  import { requireProjectRoot } from '../../utils/project-root.js';
13
13
  import { readChangeMetadata } from '../../utils/change-metadata.js';
@@ -49,7 +49,8 @@ export async function getChangeStatus(changeName, projectRoot) {
49
49
  // Resolve schema from project-local schemas/ directory, falling back to package schemas
50
50
  const schema = resolveSchema(schemaName, projectRoot);
51
51
  const completed = getCompletedArtifacts(changeDir, schema);
52
- const statusOutput = formatStatus(schema.artifacts, completed);
52
+ const inProgress = getInProgressArtifacts(changeDir, schema);
53
+ const statusOutput = formatStatus(schema.artifacts, completed, inProgress);
53
54
  const completedSet = new Set(completed);
54
55
  const allDone = schema.artifacts.every((a) => completedSet.has(a.id));
55
56
  return {
@@ -66,10 +67,18 @@ export async function getChangeStatus(changeName, projectRoot) {
66
67
  */
67
68
  export function formatChangeStatus(status) {
68
69
  const lines = status.artifacts.map((entry) => {
69
- const checkbox = entry.status === 'done' ? '[x]' : '[ ]';
70
- const suffix = entry.missingDeps && entry.missingDeps.length > 0
71
- ? ` (blocked by: ${entry.missingDeps.join(', ')})`
72
- : '';
70
+ const checkbox = entry.status === 'done'
71
+ ? '[x]'
72
+ : entry.status === 'in_progress'
73
+ ? '[~]'
74
+ : '[ ]';
75
+ let suffix = '';
76
+ if (entry.status === 'in_progress') {
77
+ suffix = ' (draft — confirm before propose)';
78
+ }
79
+ else if (entry.missingDeps && entry.missingDeps.length > 0) {
80
+ suffix = ` (blocked by: ${entry.missingDeps.join(', ')})`;
81
+ }
73
82
  return `${checkbox} ${entry.id}${suffix}`;
74
83
  });
75
84
  return lines.join('\n');
@@ -80,14 +80,14 @@ export async function archiveChange(changeName, projectRoot, options = {}) {
80
80
  return {
81
81
  success: false,
82
82
  errors: [
83
- `Cannot archive: change '${changeName}' is in phase '${phaseLabel}', expected 'built'. Complete '/specflow:build' first, or pass '--force' to archive anyway.`,
83
+ `Cannot archive: change '${changeName}' is in phase '${phaseLabel}', expected 'built'. Complete '/specflow:apply' first, or pass '--force' to archive anyway.`,
84
84
  ],
85
85
  };
86
86
  }
87
87
  if (options.force && currentPhase !== 'built') {
88
88
  const phaseLabel = currentPhase ?? 'unknown';
89
89
  console.warn(`Warning: archiving "${changeName}" in phase ${phaseLabel} with --force. ` +
90
- `Consider running /specflow:build first.`);
90
+ `Consider running /specflow:apply first.`);
91
91
  }
92
92
  // 1. Find delta spec files
93
93
  const deltaFiles = await listMarkdownFiles(deltaSpecsDir);
@@ -0,0 +1,5 @@
1
+ export type ExploreArtifactStatus = 'missing' | 'draft' | 'confirmed';
2
+ /**
3
+ * Reads explore.md Status field. Only `confirmed` counts as exploration complete.
4
+ */
5
+ export declare function readExploreArtifactStatus(changeDir: string): ExploreArtifactStatus;
@@ -0,0 +1,21 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ /**
4
+ * Reads explore.md Status field. Only `confirmed` counts as exploration complete.
5
+ */
6
+ export function readExploreArtifactStatus(changeDir) {
7
+ const explorePath = path.join(changeDir, 'explore.md');
8
+ let content;
9
+ try {
10
+ content = fs.readFileSync(explorePath, 'utf-8');
11
+ }
12
+ catch {
13
+ return 'missing';
14
+ }
15
+ const match = content.match(/\*\*Status\*\*:\s*(confirmed|draft)\b/i);
16
+ if (!match) {
17
+ // File exists but Status is missing or malformed — treat as in-progress draft.
18
+ return 'draft';
19
+ }
20
+ return match[1].toLowerCase();
21
+ }
@@ -3,7 +3,9 @@ export type { Artifact, ApplyPhase, SchemaYaml, CompletedSet, BlockedArtifacts,
3
3
  export { loadSchema, parseSchema, SchemaValidationError, } from './schema.js';
4
4
  export { resolveSchema, getSchemaDir, getPackageSchemasDir, getProjectSchemasDir, listSchemas, SchemaLoadError, } from './resolver.js';
5
5
  export { ArtifactGraph } from './graph.js';
6
- export { getCompletedArtifacts } from './state.js';
6
+ export { getCompletedArtifacts, getInProgressArtifacts } from './state.js';
7
+ export { readExploreArtifactStatus } from './explore-status.js';
8
+ export type { ExploreArtifactStatus } from './explore-status.js';
7
9
  export { loadInstructions } from './instruction-loader.js';
8
10
  export type { ArtifactInstructions, DependencyInfo, ProjectConfig, } from './instruction-loader.js';
9
11
  export { formatStatus, formatStatusHuman } from './outputs.js';
@@ -2,6 +2,7 @@ export { ArtifactSchema, ApplyPhaseSchema, SchemaYamlSchema, } from './types.js'
2
2
  export { loadSchema, parseSchema, SchemaValidationError, } from './schema.js';
3
3
  export { resolveSchema, getSchemaDir, getPackageSchemasDir, getProjectSchemasDir, listSchemas, SchemaLoadError, } from './resolver.js';
4
4
  export { ArtifactGraph } from './graph.js';
5
- export { getCompletedArtifacts } from './state.js';
5
+ export { getCompletedArtifacts, getInProgressArtifacts } from './state.js';
6
+ export { readExploreArtifactStatus } from './explore-status.js';
6
7
  export { loadInstructions } from './instruction-loader.js';
7
8
  export { formatStatus, formatStatusHuman } from './outputs.js';
@@ -4,7 +4,7 @@ import type { Artifact } from './types.js';
4
4
  */
5
5
  export interface ArtifactStatusEntry {
6
6
  readonly id: string;
7
- readonly status: 'done' | 'ready' | 'blocked';
7
+ readonly status: 'done' | 'ready' | 'blocked' | 'in_progress';
8
8
  readonly missingDeps?: readonly string[];
9
9
  }
10
10
  /**
@@ -20,7 +20,7 @@ export interface StatusOutput {
20
20
  * @param completed - Array of completed artifact IDs
21
21
  * @returns Structured status with each artifact's state
22
22
  */
23
- export declare function formatStatus(artifacts: readonly Artifact[], completed: readonly string[]): StatusOutput;
23
+ export declare function formatStatus(artifacts: readonly Artifact[], completed: readonly string[], inProgress?: readonly string[]): StatusOutput;
24
24
  /**
25
25
  * Formats artifact statuses as human-readable checkbox text.
26
26
  *
@@ -32,4 +32,4 @@ export declare function formatStatus(artifacts: readonly Artifact[], completed:
32
32
  * @param completed - Array of completed artifact IDs
33
33
  * @returns Checkbox-style text string
34
34
  */
35
- export declare function formatStatusHuman(artifacts: readonly Artifact[], completed: readonly string[]): string;
35
+ export declare function formatStatusHuman(artifacts: readonly Artifact[], completed: readonly string[], inProgress?: readonly string[]): string;
@@ -5,12 +5,16 @@
5
5
  * @param completed - Array of completed artifact IDs
6
6
  * @returns Structured status with each artifact's state
7
7
  */
8
- export function formatStatus(artifacts, completed) {
8
+ export function formatStatus(artifacts, completed, inProgress = []) {
9
9
  const completedSet = new Set(completed);
10
+ const inProgressSet = new Set(inProgress);
10
11
  const statuses = artifacts.map(artifact => {
11
12
  if (completedSet.has(artifact.id)) {
12
13
  return { id: artifact.id, status: 'done' };
13
14
  }
15
+ if (inProgressSet.has(artifact.id)) {
16
+ return { id: artifact.id, status: 'in_progress' };
17
+ }
14
18
  const missingDeps = artifact.requires.filter(req => !completedSet.has(req));
15
19
  if (missingDeps.length === 0) {
16
20
  return { id: artifact.id, status: 'ready' };
@@ -34,14 +38,28 @@ export function formatStatus(artifacts, completed) {
34
38
  * @param completed - Array of completed artifact IDs
35
39
  * @returns Checkbox-style text string
36
40
  */
37
- export function formatStatusHuman(artifacts, completed) {
41
+ export function formatStatusHuman(artifacts, completed, inProgress = []) {
38
42
  const completedSet = new Set(completed);
43
+ const inProgressSet = new Set(inProgress);
39
44
  const lines = artifacts.map(artifact => {
40
- const checkbox = completedSet.has(artifact.id) ? '[x]' : '[ ]';
45
+ let checkbox;
46
+ if (completedSet.has(artifact.id)) {
47
+ checkbox = '[x]';
48
+ }
49
+ else if (inProgressSet.has(artifact.id)) {
50
+ checkbox = '[~]';
51
+ }
52
+ else {
53
+ checkbox = '[ ]';
54
+ }
41
55
  const missingDeps = artifact.requires.filter(req => !completedSet.has(req));
42
- const suffix = missingDeps.length > 0
43
- ? ` (blocked by: ${missingDeps.join(', ')})`
44
- : '';
56
+ let suffix = '';
57
+ if (inProgressSet.has(artifact.id)) {
58
+ suffix = ' (draft — confirm before propose)';
59
+ }
60
+ else if (missingDeps.length > 0) {
61
+ suffix = ` (blocked by: ${missingDeps.join(', ')})`;
62
+ }
45
63
  return `${checkbox} ${artifact.id}${suffix}`;
46
64
  });
47
65
  return lines.join('\n');
@@ -7,3 +7,7 @@ import type { SchemaYaml } from './types.js';
7
7
  * @returns Array of completed artifact IDs
8
8
  */
9
9
  export declare function getCompletedArtifacts(changeDir: string, schema: SchemaYaml): string[];
10
+ /**
11
+ * Artifacts that exist but are not yet complete (e.g. explore.md with Status: draft).
12
+ */
13
+ export declare function getInProgressArtifacts(changeDir: string, schema: SchemaYaml): string[];
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
+ import { readExploreArtifactStatus } from './explore-status.js';
3
4
  /**
4
5
  * Checks if an artifact's generated file(s) exist in the change directory.
5
6
  * Supports simple file paths and basic glob patterns with "**".
@@ -85,9 +86,30 @@ export function getCompletedArtifacts(changeDir, schema) {
85
86
  }
86
87
  const completed = [];
87
88
  for (const artifact of schema.artifacts) {
89
+ if (artifact.id === 'explore') {
90
+ if (readExploreArtifactStatus(changeDir) === 'confirmed') {
91
+ completed.push('explore');
92
+ }
93
+ continue;
94
+ }
88
95
  if (artifactFileExists(changeDir, artifact.generates)) {
89
96
  completed.push(artifact.id);
90
97
  }
91
98
  }
92
99
  return completed;
93
100
  }
101
+ /**
102
+ * Artifacts that exist but are not yet complete (e.g. explore.md with Status: draft).
103
+ */
104
+ export function getInProgressArtifacts(changeDir, schema) {
105
+ if (!fs.existsSync(changeDir)) {
106
+ return [];
107
+ }
108
+ const inProgress = [];
109
+ for (const artifact of schema.artifacts) {
110
+ if (artifact.id === 'explore' && readExploreArtifactStatus(changeDir) === 'draft') {
111
+ inProgress.push('explore');
112
+ }
113
+ }
114
+ return inProgress;
115
+ }
@@ -10,6 +10,7 @@ const TEMPLATE_IDS = [
10
10
  'spec',
11
11
  'design',
12
12
  'tasks',
13
+ 'explore',
13
14
  ];
14
15
  /**
15
16
  * Error thrown when a template cannot be found or loaded.
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Valid template identifiers corresponding to markdown files in the templates/ directory.
3
3
  */
4
- export type TemplateId = 'proposal' | 'spec' | 'design' | 'tasks';
4
+ export type TemplateId = 'proposal' | 'spec' | 'design' | 'tasks' | 'explore';
@@ -36,7 +36,7 @@ function renderCodexAgentsBlock() {
36
36
  '- Respect specflow phase gates: plan -> refined -> built -> archived',
37
37
  '- Never bypass CLI validation and archive checks',
38
38
  '- Use artifacts under specflow/changes/<change>/ before coding',
39
- '- Workflow skills: $specflow-plan, $specflow-refine, $specflow-build, $specflow-review, $specflow-test, $specflow-verify, $specflow-done, $specflow-fix, $specflow-snap',
39
+ '- Workflow skills: $specflow-explore, $specflow-propose, $specflow-refine, $specflow-apply, $specflow-review, $specflow-test, $specflow-verify, $specflow-archive, $specflow-fix, $specflow-snap',
40
40
  '- Runtime prompts live under .agents/specflow/prompts/',
41
41
  '',
42
42
  ].join('\n');
@@ -26,7 +26,7 @@ function renderCursorRule() {
26
26
  return [
27
27
  '# SpecFlow Cursor Context',
28
28
  '',
29
- '- Respect specflow phase gates: plan -> refined -> built -> archived',
29
+ '- Respect specflow phase gates: propose -> refined -> built -> archived',
30
30
  '- Never bypass CLI validation and archive checks',
31
31
  '- Use artifacts under specflow/changes/<change>/ before coding',
32
32
  '',
@@ -1,20 +1,24 @@
1
1
  import { COMMAND_IDS } from './command-catalog.js';
2
2
  const MARKER_RULES = [
3
- { id: 'sub.scan.planned_v03_notice', skill: 'scan', includes: ['[PLANNED v0.3]', 'NOT YET FUNCTIONAL'] },
4
- { id: 'sub.plan.first_iteration', skill: 'plan', includes: ['first-iteration deep-analysis pass'] },
3
+ { id: 'sub.explore.conversation_first', skill: 'explore', includes: ['think-before-propose', 'Do NOT write proposal'] },
4
+ { id: 'sub.explore.handoff_propose', skill: 'explore', includes: ['explore.md', 'handoff'] },
5
+ { id: 'sub.explore.mid_change_reexplore', skill: 'explore', includes: ['Re-explore Mid-Change', 'stuck during'] },
6
+ { id: 'sub.propose.explore_handoff', skill: 'propose', includes: ['explore.md', 'Explore Handoff'] },
7
+ { id: 'sub.propose.first_iteration', skill: 'propose', includes: ['first-iteration deep-analysis pass'] },
5
8
  { id: 'sub.refine.multi_round', skill: 'refine', includes: ['auto-multi-round loop', '## LOOP (Stages 2-4)'] },
6
9
  { id: 'sub.refine.challenge_scope', skill: 'refine', includes: ['Challenge Behaviors #3 and #4', 'question scope'] },
7
- { id: 'sub.build.phase_a', skill: 'build', includes: ['## Phase A: Task Rewrite'] },
8
- { id: 'sub.build.phase_b', skill: 'build', includes: ['## Phase B: Subagent TDD Execution'] },
9
- { id: 'sub.build.gap_detection', skill: 'build', includes: ['Gap Detection'] },
10
+ { id: 'sub.apply.phase_a', skill: 'apply', includes: ['## Phase A: Task Rewrite'] },
11
+ { id: 'sub.apply.phase_b', skill: 'apply', includes: ['## Phase B: Subagent TDD Execution'] },
12
+ { id: 'sub.apply.gap_detection', skill: 'apply', includes: ['Gap Detection'] },
10
13
  { id: 'sub.review.spec_regression', skill: 'review', includes: ['regression'] },
11
14
  { id: 'sub.test.full_scope', skill: 'test', includes: ['unit, integration, e2e', 'verification report'] },
12
15
  { id: 'sub.verify.dual_validation', skill: 'verify', includes: ['Dual validation', 'delta specs'] },
13
- { id: 'sub.done.archive_merge', skill: 'done', includes: ['specflow change archive', 'delta specs into main specs'] },
16
+ { id: 'sub.archive.archive_merge', skill: 'archive', includes: ['specflow change archive', 'delta specs into main specs'] },
14
17
  { id: 'sub.fix.urgent_mode', skill: 'fix', includes: ['--urgent', 'skip'] },
15
18
  { id: 'sub.snap.posthoc', skill: 'snap', includes: ['post-hoc', 'archive'] },
16
- { id: 'failure.scan.not_implemented_guard', skill: 'scan', includes: ['do NOT attempt to run `specflow scan`'] },
17
- { id: 'failure.build.phase_gate', skill: 'build', includes: ['HARD GATE (prerequisite)', 'phase is not `refined`, REFUSE'] },
19
+ { id: 'failure.explore.proposal_exists_redirect', skill: 'explore', includes: ['explore is too late', 'refine'] },
20
+ { id: 'failure.propose.explore_draft_gate', skill: 'propose', includes: ['Status: draft', 'REFUSE to proceed'] },
21
+ { id: 'failure.apply.phase_gate', skill: 'apply', includes: ['HARD GATE (prerequisite)', 'phase is not `refined`, REFUSE'] },
18
22
  { id: 'failure.verify.explicit_skip_marker', skill: 'verify', includes: ['Pass 2: skipped (no baseline'] },
19
23
  ];
20
24
  export function deriveCapabilityIds(supportedCommandIds, skillContentsByCommand) {
@@ -1,12 +1,12 @@
1
1
  export const COMMAND_CATALOG = [
2
- { id: 'scan', description: 'Brownfield project scanner via code-review-graph' },
3
- { id: 'plan', description: 'Create proposal and spec artifacts for a change' },
2
+ { id: 'explore', description: 'Think-before-propose exploration when requirements are fuzzy' },
3
+ { id: 'propose', description: 'Create proposal and spec artifacts for a change' },
4
4
  { id: 'refine', description: 'Refine and iterate on spec artifacts' },
5
- { id: 'build', description: 'Implement tasks from a change spec' },
5
+ { id: 'apply', description: 'Implement tasks from a change spec' },
6
6
  { id: 'review', description: 'Review code changes against spec' },
7
7
  { id: 'test', description: 'Run tests and verify coverage' },
8
8
  { id: 'verify', description: 'Verify implementation matches spec' },
9
- { id: 'done', description: 'Finalize and archive a completed change' },
9
+ { id: 'archive', description: 'Finalize and archive a completed change' },
10
10
  { id: 'fix', description: 'Debug and fix failing tests or builds' },
11
11
  { id: 'snap', description: 'Snapshot current project state' },
12
12
  ];
@@ -6,21 +6,25 @@ export const CAPABILITY_MANIFEST = [
6
6
  level: 'command',
7
7
  required: true,
8
8
  })),
9
- { id: 'sub.scan.planned_v03_notice', commandId: 'scan', level: 'sub-capability', required: true },
10
- { id: 'sub.plan.first_iteration', commandId: 'plan', level: 'sub-capability', required: true },
9
+ { id: 'sub.explore.conversation_first', commandId: 'explore', level: 'sub-capability', required: true },
10
+ { id: 'sub.explore.handoff_propose', commandId: 'explore', level: 'sub-capability', required: true },
11
+ { id: 'sub.explore.mid_change_reexplore', commandId: 'explore', level: 'sub-capability', required: true },
12
+ { id: 'sub.propose.explore_handoff', commandId: 'propose', level: 'sub-capability', required: true },
13
+ { id: 'sub.propose.first_iteration', commandId: 'propose', level: 'sub-capability', required: true },
11
14
  { id: 'sub.refine.multi_round', commandId: 'refine', level: 'sub-capability', required: true },
12
15
  { id: 'sub.refine.challenge_scope', commandId: 'refine', level: 'sub-capability', required: true },
13
- { id: 'sub.build.phase_a', commandId: 'build', level: 'sub-capability', required: true },
14
- { id: 'sub.build.phase_b', commandId: 'build', level: 'sub-capability', required: true },
15
- { id: 'sub.build.gap_detection', commandId: 'build', level: 'sub-capability', required: true },
16
+ { id: 'sub.apply.phase_a', commandId: 'apply', level: 'sub-capability', required: true },
17
+ { id: 'sub.apply.phase_b', commandId: 'apply', level: 'sub-capability', required: true },
18
+ { id: 'sub.apply.gap_detection', commandId: 'apply', level: 'sub-capability', required: true },
16
19
  { id: 'sub.review.spec_regression', commandId: 'review', level: 'sub-capability', required: true },
17
20
  { id: 'sub.test.full_scope', commandId: 'test', level: 'sub-capability', required: true },
18
21
  { id: 'sub.verify.dual_validation', commandId: 'verify', level: 'sub-capability', required: true },
19
- { id: 'sub.done.archive_merge', commandId: 'done', level: 'sub-capability', required: true },
22
+ { id: 'sub.archive.archive_merge', commandId: 'archive', level: 'sub-capability', required: true },
20
23
  { id: 'sub.fix.urgent_mode', commandId: 'fix', level: 'sub-capability', required: true },
21
24
  { id: 'sub.snap.posthoc', commandId: 'snap', level: 'sub-capability', required: true },
22
- { id: 'failure.scan.not_implemented_guard', commandId: 'scan', level: 'failure-path', required: true },
23
- { id: 'failure.build.phase_gate', commandId: 'build', level: 'failure-path', required: true },
25
+ { id: 'failure.explore.proposal_exists_redirect', commandId: 'explore', level: 'failure-path', required: true },
26
+ { id: 'failure.propose.explore_draft_gate', commandId: 'propose', level: 'failure-path', required: true },
27
+ { id: 'failure.apply.phase_gate', commandId: 'apply', level: 'failure-path', required: true },
24
28
  { id: 'failure.verify.explicit_skip_marker', commandId: 'verify', level: 'failure-path', required: true },
25
29
  ];
26
30
  export const IDE_ASSET_MANIFEST = [
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * - `plan`: Initial phase, created by `specflow change new`.
5
5
  * - `refined`: After `/specflow:refine` iterative deep review completes.
6
- * - `built`: After `/specflow:build` Phase B execution finishes.
6
+ * - `built`: After `/specflow:apply` Phase B execution finishes.
7
7
  * - `archived`: Post-archive state; assigned automatically on successful archive.
8
8
  */
9
9
  export declare const CHANGE_PHASES: readonly ["plan", "refined", "built", "archived"];
@@ -8,7 +8,7 @@ const METADATA_FILENAME = '.specflow.yaml';
8
8
  *
9
9
  * - `plan`: Initial phase, created by `specflow change new`.
10
10
  * - `refined`: After `/specflow:refine` iterative deep review completes.
11
- * - `built`: After `/specflow:build` Phase B execution finishes.
11
+ * - `built`: After `/specflow:apply` Phase B execution finishes.
12
12
  * - `archived`: Post-archive state; assigned automatically on successful archive.
13
13
  */
14
14
  export const CHANGE_PHASES = ['plan', 'refined', 'built', 'archived'];