@gordon.gan/specflow 1.0.0 → 1.0.1
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 +54 -32
- 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 -6
- package/dist/integrations/shared/command-catalog.js +4 -3
- package/dist/integrations/shared/parity-manifest.js +12 -6
- package/dist/utils/change-metadata.d.ts +1 -1
- package/dist/utils/change-metadata.js +1 -1
- package/package.json +5 -5
- 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 +19 -19
- 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 +8 -1
- 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-scan/SKILL.md +2 -2
- package/skills/specflow-verify/SKILL.md +1 -1
- package/templates/explore.md +89 -0
- /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.
|
|
62
|
+
npm pack # 生成 gordon.gan-specflow-1.0.1.tgz
|
|
63
63
|
```
|
|
64
64
|
在目标机器上安装:
|
|
65
65
|
```bash
|
|
66
|
-
npm install -g ./specflow-0.
|
|
66
|
+
npm install -g ./gordon.gan-specflow-1.0.1.tgz
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
### 验证安装
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
specflow --version # 应输出 0.
|
|
72
|
+
specflow --version # 应输出 1.0.1
|
|
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,11 @@ specflow init
|
|
|
125
125
|
**每阶段深度思考 → 多轮迭代精化**:每个阶段一次性产出实质内容(非占位骨架),再通过内部多轮迭代把 artifact 精化到稳态,最后进入执行。
|
|
126
126
|
|
|
127
127
|
```
|
|
128
|
-
/specflow:scan [规划中 · v0.3] 扫描已有代码生成 specs 基线;当前请直接用 /specflow:
|
|
128
|
+
/specflow:scan [规划中 · v0.3] 扫描已有代码生成 specs 基线;当前请直接用 /specflow:propose 描述已有行为
|
|
129
129
|
↓
|
|
130
|
-
/specflow:
|
|
130
|
+
/specflow:explore [可选] 需求模糊时:读代码、比方案、定边界 → 产出 explore.md
|
|
131
|
+
↓
|
|
132
|
+
/specflow:propose 一次产出 4 个 artifact:proposal + delta specs + design + tasks(first-iteration 深度思考,非纯骨架)
|
|
131
133
|
↓
|
|
132
134
|
/specflow:refine 内部多轮精化循环(≥2 rounds,AI 收敛判定,不设上限)
|
|
133
135
|
每轮攻击性审查 + 4 个挑战行为:
|
|
@@ -135,7 +137,7 @@ specflow init
|
|
|
135
137
|
· 探边界 · 质疑 scope
|
|
136
138
|
可更新任意 artifact(proposal / specs / design / tasks)
|
|
137
139
|
↓
|
|
138
|
-
/specflow:
|
|
140
|
+
/specflow:apply Phase A:基于 refine 稳定后的 artifact,用 writing-plans 严格精化(rewrite)tasks.md
|
|
139
141
|
Phase B:逐任务 TDD 执行(subagent 模式)
|
|
140
142
|
design 有漏即停,回 refine 补齐
|
|
141
143
|
↓
|
|
@@ -145,7 +147,7 @@ specflow init
|
|
|
145
147
|
↓
|
|
146
148
|
/specflow:verify 双重校验(delta specs 验收 + 主 specs 回归)
|
|
147
149
|
↓
|
|
148
|
-
/specflow:
|
|
150
|
+
/specflow:archive 归档变更 → specs 合并 → git 分支清理
|
|
149
151
|
默认要求 phase=built,可用 --force 跳过
|
|
150
152
|
```
|
|
151
153
|
|
|
@@ -170,7 +172,7 @@ specflow init
|
|
|
170
172
|
|
|
171
173
|
```bash
|
|
172
174
|
# 1. 安装(任选一种)
|
|
173
|
-
npm install -g specflow
|
|
175
|
+
npm install -g @gordon.gan/specflow
|
|
174
176
|
|
|
175
177
|
# 2. 进入你的项目
|
|
176
178
|
cd my-project
|
|
@@ -178,12 +180,19 @@ cd my-project
|
|
|
178
180
|
# 3. 初始化
|
|
179
181
|
specflow init
|
|
180
182
|
|
|
181
|
-
# 4. 打开 Claude Code
|
|
182
|
-
|
|
183
|
+
# 4. 打开 Claude Code,在会话里输入(二选一):
|
|
184
|
+
|
|
185
|
+
# 需求已清晰 — 直接 plan:
|
|
186
|
+
/specflow:propose "给用户管理模块加个批量导入功能"
|
|
187
|
+
|
|
188
|
+
# 需求模糊 — 先 explore 再 propose:
|
|
189
|
+
/specflow:explore "批量导入时不确定该用 CSV 还是 Excel,也不清楚现有用户表结构"
|
|
190
|
+
# 确认 explore.md 后:
|
|
191
|
+
/specflow:propose
|
|
183
192
|
```
|
|
184
193
|
|
|
185
194
|
Claude Code 接下来会:
|
|
186
|
-
1. 读 `.claude/skills/specflow-
|
|
195
|
+
1. 读 `.claude/skills/specflow-propose/SKILL.md` 编排器
|
|
187
196
|
2. 问你几个澄清问题(一次一个)
|
|
188
197
|
3. 生成 `specflow/changes/bulk-import/proposal.md`
|
|
189
198
|
4. 展示 proposal 等你**确认**
|
|
@@ -192,16 +201,27 @@ Claude Code 接下来会:
|
|
|
192
201
|
|
|
193
202
|
### 更完整的示例(4 个场景)
|
|
194
203
|
|
|
195
|
-
#### 场景 1
|
|
204
|
+
#### 场景 1:新项目从零开始(需求已清晰)
|
|
196
205
|
|
|
197
206
|
```
|
|
198
|
-
/specflow:
|
|
207
|
+
/specflow:propose "添加用户注册和登录功能"
|
|
199
208
|
/specflow:refine # 讨论技术方案,输出 design.md
|
|
200
|
-
/specflow:
|
|
209
|
+
/specflow:apply # TDD 实现,每任务用户确认
|
|
201
210
|
/specflow:review # 代码审查
|
|
202
211
|
/specflow:test # 跑测试
|
|
203
212
|
/specflow:verify # 对照 specs 验收
|
|
204
|
-
/specflow:
|
|
213
|
+
/specflow:archive # 归档 + merge
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### 场景 1b:需求模糊,先探索再规划
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
/specflow:explore "登录有时返回 500,不确定是 token 校验还是 session 存储的问题"
|
|
220
|
+
# AI 读代码、比方案、产出 explore.md;你确认方向后:
|
|
221
|
+
/specflow:propose # 读取 explore.md,合成 proposal + specs + design + tasks
|
|
222
|
+
/specflow:refine
|
|
223
|
+
/specflow:apply
|
|
224
|
+
/specflow:archive
|
|
205
225
|
```
|
|
206
226
|
|
|
207
227
|
#### 场景 2:接手已有项目(v0.2.x 工作流)
|
|
@@ -212,12 +232,13 @@ specflow init
|
|
|
212
232
|
|
|
213
233
|
# 在 Claude Code 里:
|
|
214
234
|
# 注意:/specflow:scan 当前规划在 v0.3,未实现。
|
|
215
|
-
# v0.2.x
|
|
216
|
-
# 新增变更,让
|
|
217
|
-
/specflow:
|
|
218
|
-
/specflow:
|
|
219
|
-
/specflow:
|
|
220
|
-
/specflow:
|
|
235
|
+
# v0.2.x 的推荐做法是:需求模糊时先 /specflow:explore;或需求清晰时直接进 plan,
|
|
236
|
+
# 在 proposal Q&A 里描述已有行为 + 新增变更,让 propose 为将要改动的 capability 生成 delta spec 基线。
|
|
237
|
+
/specflow:explore "描述模糊问题或不确定的改法" # 可选
|
|
238
|
+
/specflow:propose "描述已有行为 + 你这次要改的新功能"
|
|
239
|
+
/specflow:refine # 对 propose 产出做 ≥2 轮攻击性审查
|
|
240
|
+
/specflow:apply # Phase A 重写 tasks.md → Phase B TDD
|
|
241
|
+
/specflow:archive # 归档,delta spec 合入主 specs/
|
|
221
242
|
```
|
|
222
243
|
|
|
223
244
|
#### 场景 3:紧急修 Bug
|
|
@@ -257,13 +278,14 @@ specflow init
|
|
|
257
278
|
| 命令 | 说明 |
|
|
258
279
|
|---|---|
|
|
259
280
|
| `/specflow:scan` | **[规划中 · v0.3]** 扫描已有代码库生成 specs 基线;v0.2.x 未实现,触发时 skill 会提示替代方案 |
|
|
260
|
-
| `/specflow:
|
|
281
|
+
| `/specflow:explore` | **需求模糊时** 先探索:读代码、比方案、定边界,产出 `explore.md`,再 handoff 到 propose |
|
|
282
|
+
| `/specflow:propose` | 需求规划:生成 proposal + delta specs |
|
|
261
283
|
| `/specflow:refine` | 技术方案探讨(brainstorming + design.md) |
|
|
262
|
-
| `/specflow:
|
|
284
|
+
| `/specflow:apply` | 两阶段构建:生成计划 → subagent TDD 执行 |
|
|
263
285
|
| `/specflow:review` | 代码审查(含 specs 回归检查) |
|
|
264
286
|
| `/specflow:test` | 全量测试 + 验证(单元/集成/E2E/回归) |
|
|
265
287
|
| `/specflow:verify` | 双重校验:delta specs 验收 + 主 specs 回归 |
|
|
266
|
-
| `/specflow:
|
|
288
|
+
| `/specflow:archive` | 归档变更 + specs 合并 + git 分支清理 |
|
|
267
289
|
| `/specflow:fix` | 修 Bug 快速通道(调试 → TDD → 归档) |
|
|
268
290
|
| `/specflow:snap` | 事后补档(从 git diff 反推变更记录) |
|
|
269
291
|
|
|
@@ -403,7 +425,7 @@ your-project/
|
|
|
403
425
|
|
|
404
426
|
### `specflow: command not found`
|
|
405
427
|
|
|
406
|
-
- 确认 `npm install -g` 成功
|
|
428
|
+
- 确认 `npm install -g @gordon.gan/specflow` 成功
|
|
407
429
|
- 检查 `npm config get prefix` 下的 `bin` 目录是否在 PATH 里
|
|
408
430
|
|
|
409
431
|
### `No specflow project found at or above <dir>`
|
|
@@ -422,7 +444,7 @@ SpecFlow 需要 Node ≥ 20.19.0。用 `nvm` 切版本:
|
|
|
422
444
|
```bash
|
|
423
445
|
nvm install 20
|
|
424
446
|
nvm use 20
|
|
425
|
-
npm install -g specflow
|
|
447
|
+
npm install -g @gordon.gan/specflow
|
|
426
448
|
```
|
|
427
449
|
|
|
428
450
|
### code-review-graph 安装失败
|
|
@@ -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,26 @@
|
|
|
1
1
|
import { COMMAND_IDS } from './command-catalog.js';
|
|
2
2
|
const MARKER_RULES = [
|
|
3
3
|
{ id: 'sub.scan.planned_v03_notice', skill: 'scan', includes: ['[PLANNED v0.3]', 'NOT YET FUNCTIONAL'] },
|
|
4
|
-
{ id: 'sub.
|
|
4
|
+
{ id: 'sub.explore.conversation_first', skill: 'explore', includes: ['think-before-propose', 'Do NOT write proposal'] },
|
|
5
|
+
{ id: 'sub.explore.handoff_propose', skill: 'explore', includes: ['explore.md', 'handoff'] },
|
|
6
|
+
{ id: 'sub.explore.mid_change_reexplore', skill: 'explore', includes: ['Re-explore Mid-Change', 'stuck during'] },
|
|
7
|
+
{ id: 'sub.propose.explore_handoff', skill: 'propose', includes: ['explore.md', 'Explore Handoff'] },
|
|
8
|
+
{ id: 'sub.propose.first_iteration', skill: 'propose', includes: ['first-iteration deep-analysis pass'] },
|
|
5
9
|
{ id: 'sub.refine.multi_round', skill: 'refine', includes: ['auto-multi-round loop', '## LOOP (Stages 2-4)'] },
|
|
6
10
|
{ id: 'sub.refine.challenge_scope', skill: 'refine', includes: ['Challenge Behaviors #3 and #4', 'question scope'] },
|
|
7
|
-
{ id: 'sub.
|
|
8
|
-
{ id: 'sub.
|
|
9
|
-
{ id: 'sub.
|
|
11
|
+
{ id: 'sub.apply.phase_a', skill: 'apply', includes: ['## Phase A: Task Rewrite'] },
|
|
12
|
+
{ id: 'sub.apply.phase_b', skill: 'apply', includes: ['## Phase B: Subagent TDD Execution'] },
|
|
13
|
+
{ id: 'sub.apply.gap_detection', skill: 'apply', includes: ['Gap Detection'] },
|
|
10
14
|
{ id: 'sub.review.spec_regression', skill: 'review', includes: ['regression'] },
|
|
11
15
|
{ id: 'sub.test.full_scope', skill: 'test', includes: ['unit, integration, e2e', 'verification report'] },
|
|
12
16
|
{ id: 'sub.verify.dual_validation', skill: 'verify', includes: ['Dual validation', 'delta specs'] },
|
|
13
|
-
{ id: 'sub.
|
|
17
|
+
{ id: 'sub.archive.archive_merge', skill: 'archive', includes: ['specflow change archive', 'delta specs into main specs'] },
|
|
14
18
|
{ id: 'sub.fix.urgent_mode', skill: 'fix', includes: ['--urgent', 'skip'] },
|
|
15
19
|
{ id: 'sub.snap.posthoc', skill: 'snap', includes: ['post-hoc', 'archive'] },
|
|
16
20
|
{ id: 'failure.scan.not_implemented_guard', skill: 'scan', includes: ['do NOT attempt to run `specflow scan`'] },
|
|
17
|
-
{ id: 'failure.
|
|
21
|
+
{ id: 'failure.explore.proposal_exists_redirect', skill: 'explore', includes: ['explore is too late', 'refine'] },
|
|
22
|
+
{ id: 'failure.propose.explore_draft_gate', skill: 'propose', includes: ['Status: draft', 'REFUSE to proceed'] },
|
|
23
|
+
{ id: 'failure.apply.phase_gate', skill: 'apply', includes: ['HARD GATE (prerequisite)', 'phase is not `refined`, REFUSE'] },
|
|
18
24
|
{ id: 'failure.verify.explicit_skip_marker', skill: 'verify', includes: ['Pass 2: skipped (no baseline'] },
|
|
19
25
|
];
|
|
20
26
|
export function deriveCapabilityIds(supportedCommandIds, skillContentsByCommand) {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
export const COMMAND_CATALOG = [
|
|
2
2
|
{ id: 'scan', description: 'Brownfield project scanner via code-review-graph' },
|
|
3
|
-
{ id: '
|
|
3
|
+
{ id: 'explore', description: 'Think-before-propose exploration when requirements are fuzzy' },
|
|
4
|
+
{ id: 'propose', description: 'Create proposal and spec artifacts for a change' },
|
|
4
5
|
{ id: 'refine', description: 'Refine and iterate on spec artifacts' },
|
|
5
|
-
{ id: '
|
|
6
|
+
{ id: 'apply', description: 'Implement tasks from a change spec' },
|
|
6
7
|
{ id: 'review', description: 'Review code changes against spec' },
|
|
7
8
|
{ id: 'test', description: 'Run tests and verify coverage' },
|
|
8
9
|
{ id: 'verify', description: 'Verify implementation matches spec' },
|
|
9
|
-
{ id: '
|
|
10
|
+
{ id: 'archive', description: 'Finalize and archive a completed change' },
|
|
10
11
|
{ id: 'fix', description: 'Debug and fix failing tests or builds' },
|
|
11
12
|
{ id: 'snap', description: 'Snapshot current project state' },
|
|
12
13
|
];
|
|
@@ -7,20 +7,26 @@ export const CAPABILITY_MANIFEST = [
|
|
|
7
7
|
required: true,
|
|
8
8
|
})),
|
|
9
9
|
{ id: 'sub.scan.planned_v03_notice', commandId: 'scan', level: 'sub-capability', required: true },
|
|
10
|
-
{ id: 'sub.
|
|
10
|
+
{ id: 'sub.explore.conversation_first', commandId: 'explore', level: 'sub-capability', required: true },
|
|
11
|
+
{ id: 'sub.explore.handoff_propose', commandId: 'explore', level: 'sub-capability', required: true },
|
|
12
|
+
{ id: 'sub.explore.mid_change_reexplore', commandId: 'explore', level: 'sub-capability', required: true },
|
|
13
|
+
{ id: 'sub.propose.explore_handoff', commandId: 'propose', level: 'sub-capability', required: true },
|
|
14
|
+
{ id: 'sub.propose.first_iteration', commandId: 'propose', level: 'sub-capability', required: true },
|
|
11
15
|
{ id: 'sub.refine.multi_round', commandId: 'refine', level: 'sub-capability', required: true },
|
|
12
16
|
{ id: 'sub.refine.challenge_scope', commandId: 'refine', level: 'sub-capability', required: true },
|
|
13
|
-
{ id: 'sub.
|
|
14
|
-
{ id: 'sub.
|
|
15
|
-
{ id: 'sub.
|
|
17
|
+
{ id: 'sub.apply.phase_a', commandId: 'apply', level: 'sub-capability', required: true },
|
|
18
|
+
{ id: 'sub.apply.phase_b', commandId: 'apply', level: 'sub-capability', required: true },
|
|
19
|
+
{ id: 'sub.apply.gap_detection', commandId: 'apply', level: 'sub-capability', required: true },
|
|
16
20
|
{ id: 'sub.review.spec_regression', commandId: 'review', level: 'sub-capability', required: true },
|
|
17
21
|
{ id: 'sub.test.full_scope', commandId: 'test', level: 'sub-capability', required: true },
|
|
18
22
|
{ id: 'sub.verify.dual_validation', commandId: 'verify', level: 'sub-capability', required: true },
|
|
19
|
-
{ id: 'sub.
|
|
23
|
+
{ id: 'sub.archive.archive_merge', commandId: 'archive', level: 'sub-capability', required: true },
|
|
20
24
|
{ id: 'sub.fix.urgent_mode', commandId: 'fix', level: 'sub-capability', required: true },
|
|
21
25
|
{ id: 'sub.snap.posthoc', commandId: 'snap', level: 'sub-capability', required: true },
|
|
22
26
|
{ id: 'failure.scan.not_implemented_guard', commandId: 'scan', level: 'failure-path', required: true },
|
|
23
|
-
{ id: 'failure.
|
|
27
|
+
{ id: 'failure.explore.proposal_exists_redirect', commandId: 'explore', level: 'failure-path', required: true },
|
|
28
|
+
{ id: 'failure.propose.explore_draft_gate', commandId: 'propose', level: 'failure-path', required: true },
|
|
29
|
+
{ id: 'failure.apply.phase_gate', commandId: 'apply', level: 'failure-path', required: true },
|
|
24
30
|
{ id: 'failure.verify.explicit_skip_marker', commandId: 'verify', level: 'failure-path', required: true },
|
|
25
31
|
];
|
|
26
32
|
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'];
|