@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.
- package/LICENSE +1 -1
- package/README.md +52 -38
- package/dist/cli/commands/change-phase.d.ts +1 -1
- package/dist/cli/commands/change-phase.js +1 -1
- package/dist/cli/commands/change-status.js +15 -6
- package/dist/core/archive.js +2 -2
- package/dist/core/artifact-graph/explore-status.d.ts +5 -0
- package/dist/core/artifact-graph/explore-status.js +21 -0
- package/dist/core/artifact-graph/index.d.ts +3 -1
- package/dist/core/artifact-graph/index.js +2 -1
- package/dist/core/artifact-graph/outputs.d.ts +3 -3
- package/dist/core/artifact-graph/outputs.js +24 -6
- package/dist/core/artifact-graph/state.d.ts +4 -0
- package/dist/core/artifact-graph/state.js +22 -0
- package/dist/core/templates/index.js +1 -0
- package/dist/core/templates/types.d.ts +1 -1
- package/dist/integrations/codex/adapter.js +1 -1
- package/dist/integrations/cursor/adapter.js +1 -1
- package/dist/integrations/shared/capability-evidence.js +12 -8
- package/dist/integrations/shared/command-catalog.js +4 -4
- package/dist/integrations/shared/parity-manifest.js +12 -8
- package/dist/utils/change-metadata.d.ts +1 -1
- package/dist/utils/change-metadata.js +1 -1
- package/package.json +5 -8
- package/prompts/{build → apply}/ecc-java-reviewer.md +1 -1
- package/prompts/{build → apply}/language-router.md +6 -6
- package/prompts/{build → apply}/phase-a-plan.md +14 -14
- package/prompts/{build → apply}/phase-b-execute.md +17 -17
- package/prompts/{build → apply}/phase-b-review.md +2 -2
- package/prompts/{build → apply}/phase-b-worktree.md +3 -3
- package/prompts/{build → apply}/plan-document-reviewer.md +6 -6
- package/prompts/{build → apply}/tdd.md +3 -3
- package/prompts/{done → archive}/branch-finish.md +4 -4
- package/prompts/explore/explore-session.md +152 -0
- package/prompts/fix/debug.md +2 -2
- package/prompts/{plan → propose}/design-draft.md +1 -1
- package/prompts/{plan → propose}/proposal.md +30 -0
- package/prompts/{plan → propose}/specs.md +1 -1
- package/prompts/{plan → propose}/tasks-draft.md +9 -9
- package/prompts/reference/specflow/example-design.md +20 -20
- package/prompts/reference/superpowers/anthropic-best-practices.md +6 -6
- package/prompts/reference/superpowers/codex-tools.md +1 -1
- package/prompts/refine/brainstorm.md +15 -15
- package/prompts/refine/design-output.md +1 -1
- package/prompts/refine/spec-document-reviewer.md +1 -1
- package/prompts/review/code-review.md +4 -4
- package/prompts/shared/code-reviewer-prompt.md +1 -1
- package/prompts/shared/executing-plans.md +12 -12
- package/prompts/shared/implementer-prompt.md +3 -3
- package/prompts/test/verification.md +1 -1
- package/prompts/verify/verification.md +3 -3
- package/schemas/specflow/schema.yaml +6 -4
- package/skills/{specflow-build → specflow-apply}/SKILL.md +12 -12
- package/skills/{specflow-done → specflow-archive}/SKILL.md +5 -5
- package/skills/specflow-explore/SKILL.md +102 -0
- package/skills/specflow-fix/SKILL.md +2 -2
- package/skills/{specflow-plan → specflow-propose}/SKILL.md +38 -11
- package/skills/specflow-refine/SKILL.md +4 -4
- package/skills/specflow-verify/SKILL.md +1 -1
- package/templates/explore.md +89 -0
- package/skills/specflow-scan/SKILL.md +0 -48
- /package/prompts/{build → apply}/ecc-go-reviewer.md +0 -0
- /package/prompts/{build → apply}/ecc-kotlin-reviewer.md +0 -0
- /package/prompts/{build → apply}/ecc-python-reviewer.md +0 -0
- /package/prompts/{build → apply}/ecc-rust-reviewer.md +0 -0
- /package/prompts/{build → apply}/ecc-typescript-reviewer.md +0 -0
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# SpecFlow
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/specflow)
|
|
4
|
-
[](https://www.npmjs.com/package/specflow)
|
|
3
|
+
[](https://www.npmjs.com/package/@gordon.gan/specflow)
|
|
4
|
+
[](https://www.npmjs.com/package/@gordon.gan/specflow)
|
|
5
5
|
[](./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.
|
|
62
|
+
npm pack # 生成 gordon.gan-specflow-1.0.2.tgz
|
|
63
63
|
```
|
|
64
64
|
在目标机器上安装:
|
|
65
65
|
```bash
|
|
66
|
-
npm install -g ./specflow-0.2.
|
|
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
|
|
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:
|
|
128
|
+
/specflow:explore [可选] 需求模糊时:读代码、比方案、定边界 → 产出 explore.md
|
|
129
129
|
↓
|
|
130
|
-
/specflow:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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-
|
|
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:
|
|
205
|
+
/specflow:propose "添加用户注册和登录功能"
|
|
199
206
|
/specflow:refine # 讨论技术方案,输出 design.md
|
|
200
|
-
/specflow:
|
|
207
|
+
/specflow:apply # TDD 实现,每任务用户确认
|
|
201
208
|
/specflow:review # 代码审查
|
|
202
209
|
/specflow:test # 跑测试
|
|
203
210
|
/specflow:verify # 对照 specs 验收
|
|
204
|
-
/specflow:
|
|
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
|
-
#
|
|
215
|
-
#
|
|
216
|
-
#
|
|
217
|
-
/specflow:
|
|
218
|
-
/specflow:refine # 对
|
|
219
|
-
/specflow:
|
|
220
|
-
/specflow:
|
|
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:
|
|
260
|
-
| `/specflow:
|
|
277
|
+
| `/specflow:explore` | **需求模糊时** 先探索:读代码、比方案、定边界,产出 `explore.md`,再 handoff 到 propose |
|
|
278
|
+
| `/specflow:propose` | 需求规划:生成 proposal + delta specs |
|
|
261
279
|
| `/specflow:refine` | 技术方案探讨(brainstorming + design.md) |
|
|
262
|
-
| `/specflow:
|
|
280
|
+
| `/specflow:apply` | 两阶段构建:生成计划 → subagent TDD 执行 |
|
|
263
281
|
| `/specflow:review` | 代码审查(含 specs 回归检查) |
|
|
264
282
|
| `/specflow:test` | 全量测试 + 验证(单元/集成/E2E/回归) |
|
|
265
283
|
| `/specflow:verify` | 双重校验:delta specs 验收 + 主 specs 回归 |
|
|
266
|
-
| `/specflow:
|
|
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
|
|
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
|
|
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
|
|
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'
|
|
70
|
-
|
|
71
|
-
|
|
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');
|
package/dist/core/archive.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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,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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
+
}
|
|
@@ -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-
|
|
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:
|
|
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.
|
|
4
|
-
{ id: 'sub.
|
|
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.
|
|
8
|
-
{ id: 'sub.
|
|
9
|
-
{ id: 'sub.
|
|
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.
|
|
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.
|
|
17
|
-
{ id: 'failure.
|
|
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: '
|
|
3
|
-
{ id: '
|
|
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: '
|
|
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: '
|
|
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.
|
|
10
|
-
{ id: 'sub.
|
|
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.
|
|
14
|
-
{ id: 'sub.
|
|
15
|
-
{ id: 'sub.
|
|
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.
|
|
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.
|
|
23
|
-
{ id: 'failure.
|
|
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:
|
|
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:
|
|
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'];
|