@michaelxwb/code-flow 0.5.2 → 0.6.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/docs/migrations/spec-workflow-0.6.0.md +42 -0
- package/package.json +4 -2
- package/src/adapters/claude/CLAUDE.md +11 -17
- package/src/adapters/claude/commands/cf-init.md +5 -4
- package/src/adapters/claude/commands/cf-spec.md +63 -0
- package/src/adapters/claude/commands/cf-sync.md +25 -0
- package/src/adapters/claude/commands/cf-task/align.md +23 -1
- package/src/adapters/claude/commands/cf-task/archive.md +7 -0
- package/src/adapters/claude/commands/cf-task/plan.md +16 -0
- package/src/adapters/claude/commands/cf-task/prd.md +24 -0
- package/src/adapters/claude/commands/cf-task/start.md +13 -10
- package/src/adapters/claude/settings.local.json +1 -11
- package/src/adapters/codex/AGENTS.md +11 -17
- package/src/adapters/codex/hooks.json +6 -4
- package/src/adapters/codex/skills/cf-init/SKILL.md +5 -4
- package/src/adapters/codex/skills/cf-spec/SKILL.md +56 -0
- package/src/adapters/codex/skills/cf-sync/SKILL.md +30 -0
- package/src/adapters/codex/skills/cf-task-align/SKILL.md +23 -1
- package/src/adapters/codex/skills/cf-task-archive/SKILL.md +7 -0
- package/src/adapters/codex/skills/cf-task-plan/SKILL.md +16 -0
- package/src/adapters/codex/skills/cf-task-prd/SKILL.md +24 -0
- package/src/adapters/codex/skills/cf-task-start/SKILL.md +13 -10
- package/src/adapters/costrict/CLAUDE.md +11 -17
- package/src/adapters/costrict/commands/cf-init.md +5 -4
- package/src/adapters/costrict/commands/cf-spec.md +63 -0
- package/src/adapters/costrict/commands/cf-sync.md +25 -0
- package/src/adapters/costrict/commands/cf-task/align.md +23 -1
- package/src/adapters/costrict/commands/cf-task/archive.md +7 -0
- package/src/adapters/costrict/commands/cf-task/plan.md +16 -0
- package/src/adapters/costrict/commands/cf-task/prd.md +24 -0
- package/src/adapters/costrict/commands/cf-task/start.md +13 -10
- package/src/adapters/costrict/settings.local.json +1 -11
- package/src/adapters/opencode/AGENTS.md +11 -17
- package/src/adapters/opencode/commands/cf-init.md +4 -3
- package/src/adapters/opencode/commands/cf-spec.md +55 -0
- package/src/adapters/opencode/commands/cf-sync.md +29 -0
- package/src/adapters/opencode/commands/cf-task/align.md +24 -1
- package/src/adapters/opencode/commands/cf-task/archive.md +8 -0
- package/src/adapters/opencode/commands/cf-task/plan.md +17 -0
- package/src/adapters/opencode/commands/cf-task/prd.md +25 -0
- package/src/adapters/opencode/commands/cf-task/start.md +14 -10
- package/src/adapters/opencode/plugins/code-flow/index.js +3 -4
- package/src/cli.js +51 -2
- package/src/core/code-flow/config.yml +14 -16
- package/src/core/code-flow/scripts/cf_checks.py +30 -0
- package/src/core/code-flow/scripts/cf_core.py +36 -256
- package/src/core/code-flow/scripts/cf_log.py +8 -0
- package/src/core/code-flow/scripts/cf_post_hook.py +6 -9
- package/src/core/code-flow/scripts/cf_pre_tool_hook.py +88 -0
- package/src/core/code-flow/scripts/cf_session_state.py +37 -0
- package/src/core/code-flow/scripts/cf_spec_context.py +1470 -0
- package/src/core/code-flow/scripts/cf_spec_gate.py +230 -0
- package/src/core/code-flow/scripts/cf_spec_metadata.py +304 -0
- package/src/core/code-flow/scripts/cf_spec_migrate.py +512 -0
- package/src/core/code-flow/scripts/cf_spec_resolver.py +271 -0
- package/src/core/code-flow/scripts/cf_spec_router.py +149 -0
- package/src/core/code-flow/scripts/cf_spec_session.py +118 -0
- package/src/core/code-flow/scripts/cf_spec_verify.py +340 -0
- package/src/core/code-flow/scripts/cf_stats.py +40 -5
- package/src/core/code-flow/scripts/cf_stop_hook.py +43 -2
- package/src/core/code-flow/scripts/cf_sync.py +140 -0
- package/src/core/code-flow/scripts/cf_task_runtime.py +184 -0
- package/src/core/code-flow/scripts/cf_user_prompt_hook.py +115 -345
- package/src/core/code-flow/specs/backend/code-quality-performance.md +13 -1
- package/src/core/code-flow/specs/backend/database.md +13 -1
- package/src/core/code-flow/specs/backend/directory-structure.md +13 -1
- package/src/core/code-flow/specs/backend/logging.md +13 -1
- package/src/core/code-flow/specs/backend/platform-rules.md +13 -1
- package/src/core/code-flow/specs/frontend/component-specs.md +13 -1
- package/src/core/code-flow/specs/frontend/directory-structure.md +13 -1
- package/src/core/code-flow/specs/frontend/quality-standards.md +13 -1
- package/src/core/code-flow/specs/shared/design/design-frontend.md +10 -0
- package/src/core/code-flow/specs/shared/design/design-full.md +10 -0
- package/src/core/code-flow/specs/shared/design/design-lite.md +10 -0
- package/src/core/code-flow/specs/shared/prd-template.md +14 -3
- package/src/migrate/spec-workflow.js +238 -0
- package/src/adapters/claude/commands/cf-inject.md +0 -55
- package/src/adapters/codex/skills/cf-inject/SKILL.md +0 -56
- package/src/adapters/costrict/commands/cf-inject.md +0 -55
- package/src/adapters/opencode/commands/cf-inject.md +0 -59
- package/src/core/code-flow/scripts/cf_inject_hook.py +0 -178
- package/src/core/code-flow/scripts/cf_session_hook.py +0 -42
|
@@ -43,10 +43,11 @@ description: 一键初始化项目规范体系,检测技术栈,生成 spec
|
|
|
43
43
|
`code-flow init` 已从 `src/core/code-flow/config.yml` 复制当前核心模板。cf-init 不要复制或手写旧版 YAML;应该读取现有 `.code-flow/config.yml`,只做保守修补。
|
|
44
44
|
|
|
45
45
|
必须保持或补齐这些核心能力:
|
|
46
|
-
- `
|
|
47
|
-
- `
|
|
46
|
+
- `spec_workflow.schema_version: 1`
|
|
47
|
+
- `spec_workflow.catalog.dedup_window: 5`
|
|
48
|
+
- `quality_loop.compress: true`
|
|
48
49
|
- 完整 `skip_extensions`,包括图片、压缩包、PDF 等非代码文件
|
|
49
|
-
- `skip_paths` 中的平台目录:`.claude/**`、`.codex/**`、`.costrict/**`
|
|
50
|
+
- `skip_paths` 中的平台目录:`.claude/**`、`.codex/**`、`.costrict/**`、`.opencode/**`
|
|
50
51
|
- `path_mapping.shared`,包括 `shared/_map.md`、`shared/prd-template.md`、`shared/design/design-lite.md`、`shared/design/design-full.md`、`shared/design/design-frontend.md`
|
|
51
52
|
- backend patterns 中同时包含 `*.py` 与 `**/*.py`
|
|
52
53
|
- 按检测到的前端框架动态补 `frontend.patterns`:Vue→`**/*.vue`、Svelte→`**/*.svelte`、Next→`app/**` 等(保守追加,按检测结果,不预塞所有框架)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 管理 Spec Context、漂移恢复与一次性迁移
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# cf-spec
|
|
6
|
+
|
|
7
|
+
管理 schema v1 Spec Context 与一次性迁移。用法:
|
|
8
|
+
|
|
9
|
+
- `cf-spec migrate --plan <migration-plan.yml>`
|
|
10
|
+
- `cf-spec context [需求目录]`
|
|
11
|
+
- `cf-spec refresh [需求目录]`
|
|
12
|
+
- `cf-spec status [需求目录]`
|
|
13
|
+
- `cf-spec doctor [需求目录]`
|
|
14
|
+
|
|
15
|
+
## 通用硬门禁
|
|
16
|
+
|
|
17
|
+
1. 先读取 `.code-flow/config.yml`;`spec_workflow.schema_version` 不是 `1` 时,只有 `migrate --plan` 可继续。
|
|
18
|
+
2. 不存在有效 Context 时,不得伪造 binding、Rule 状态、Evidence 或用户确认。
|
|
19
|
+
3. stdout JSON 由 `cf_*.py` 产生;本命令不得手写成功结果。
|
|
20
|
+
|
|
21
|
+
## migrate --plan
|
|
22
|
+
|
|
23
|
+
只处理 `code-flow migrate --spec-workflow --prepare` 已产生的 prepared plan:
|
|
24
|
+
|
|
25
|
+
1. 校验 plan 位于当前项目 `.code-flow/migrations/<id>/`,状态为 `prepared_blocked` 或 `prepared`,source hashes 未漂移。
|
|
26
|
+
2. 逐条展示 `unresolved` 的来源、候选和影响;读取关联 Spec 与活跃需求文档后给出建议。
|
|
27
|
+
3. 每项必须由用户明确选择。写入 `confirmed_by`、`confirmed_at`、`source`、`reason`;Agent 不能代确认,不能批量 N/A。
|
|
28
|
+
4. 只修改 prepared plan,不修改项目目标、backup、staging、journal 或 `.version`。
|
|
29
|
+
5. unresolved 清零后提示执行:
|
|
30
|
+
`code-flow migrate --spec-workflow --apply --plan <migration-plan.yml>`
|
|
31
|
+
|
|
32
|
+
## context
|
|
33
|
+
|
|
34
|
+
定位显式需求目录;省略时只可使用有效 `.active-task.json` 的 `task_dir`。
|
|
35
|
+
|
|
36
|
+
1. 读取并展示 `spec-context.yml` 的 bindings、Rule stage status、artifact refs、Evidence 与 drift。
|
|
37
|
+
2. 执行:
|
|
38
|
+
`python3 .code-flow/scripts/cf_spec_context.py validate --task-dir <目录> --json`
|
|
39
|
+
3. Context 缺失、schema/hash 无效时 fail-closed,不回退 Catalog。
|
|
40
|
+
|
|
41
|
+
## refresh
|
|
42
|
+
|
|
43
|
+
执行:
|
|
44
|
+
|
|
45
|
+
`python3 .code-flow/scripts/cf_spec_context.py refresh --task-dir <目录> --root "$PWD" --json`
|
|
46
|
+
|
|
47
|
+
- changed required Rule 标为 stale,关联 stage Gate 必须阻断。
|
|
48
|
+
- missing/conflict 不自动降级;回 Align 或 Plan 更新承接后再继续。
|
|
49
|
+
- refresh 不替用户做 N/A/waiver 决策。
|
|
50
|
+
|
|
51
|
+
## doctor
|
|
52
|
+
|
|
53
|
+
1. 校验 config schema、active marker、lock、Context hash、task/status、migration journal 和 legacy residue。
|
|
54
|
+
2. active marker 存在时执行 `cf_spec_context.py active doctor`,传入可证明的 Context hash;无法证明时保持 `recovery_required`。
|
|
55
|
+
3. 输出明确修复命令。不得删除损坏 marker、越过 required Gate 或静默切换到无任务模式。
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 双副本同步:检查漂移并一键部署 canonical 源
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# cf-sync
|
|
6
|
+
|
|
7
|
+
一键同步双副本(canonical 源 → 部署副本),并检查漂移。用法:
|
|
8
|
+
|
|
9
|
+
- `python3 .code-flow/scripts/cf_sync.py check` — 检查全部配对是否一致(默认)
|
|
10
|
+
- `python3 .code-flow/scripts/cf_sync.py sync` — 把 canonical 源复制到部署副本
|
|
11
|
+
- `python3 .code-flow/scripts/cf_sync.py check --verbose` — 额外列出部署侧独有文件
|
|
12
|
+
|
|
13
|
+
## 同步配对
|
|
14
|
+
|
|
15
|
+
| canonical 源 | 部署副本 | 同步内容 |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| `src/core/code-flow` | `.code-flow` | `scripts/`、`.version`、`.gitignore` |
|
|
18
|
+
| `src/adapters/claude` | `.claude` | `commands/` |
|
|
19
|
+
| `src/adapters/codex` | `.codex` | `hooks.json` |
|
|
20
|
+
| `src/adapters/codex/skills` | `.agents/skills` | 全部 |
|
|
21
|
+
| `src/adapters/costrict` | `.costrict` | `commands/` |
|
|
22
|
+
| `src/adapters/opencode` | `.opencode` | `commands/`、`plugins/` |
|
|
23
|
+
|
|
24
|
+
## 规则
|
|
25
|
+
|
|
26
|
+
1. 项目自有内容(`specs/`、`tasks/`、`config.yml`、`validation.yml`、`settings.local.json`)不在同步范围内,不会被动覆盖。
|
|
27
|
+
2. 部署侧独有文件不会被删除(避免误删本地产物)。
|
|
28
|
+
3. 只改一侧 = 测试通过但 live 行为不变;改完 canonical 源后运行 `cf-sync sync` 部署,提交时双副本一起提交。
|
|
29
|
+
4. 每对 `check` 全绿后提交,保证四平台命令与运行副本同步。
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
description: 从需求出发通过结构化对话产出设计简报(.design.md)
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
# cf-task:align
|
|
6
7
|
|
|
7
8
|
从需求出发,通过结构化对话产出设计简报(`.design.md`),为 `cf-task:plan` 提供足够丰富的输入。支持三种输入:纯文本需求、`.prd.md` 产品需求文档、`.design.md` 草稿。
|
|
@@ -114,6 +115,18 @@ PRD 已覆盖的维度如无需调整,将直接使用。
|
|
|
114
115
|
- Step 3 中已从 PRD 获得充分信息的维度**不重复提问**,除非用户主动修改
|
|
115
116
|
- design 中 FEAT 的"来源"列必须引用 PRD 中对应的 US-XX
|
|
116
117
|
|
|
118
|
+
### 2.7. 继承并刷新 Spec Context(硬门禁)
|
|
119
|
+
|
|
120
|
+
进入技术讨论前先定位需求目录中的 `spec-context.yml`:
|
|
121
|
+
|
|
122
|
+
1. PRD 派生/恢复模式必须执行 `python3 .code-flow/scripts/cf_spec_context.py refresh --task-dir <需求目录> --root "$PWD" --json`;missing/stale/conflict 必须先处理。
|
|
123
|
+
2. 已有 binding 必须从 PRD Context 原样继承,**不得重新选择**、降级 enforcement 或清空 PRD 阶段 refs/decision/evidence。根据预计实现路径运行 `catalog --stage design`,只对新增 design-stage 候选做增量 bind,并记录 reason。
|
|
124
|
+
3. 为每条 design-stage required Rule 预建 `Spec Compliance Matrix` 行:`Spec/Rule`、enforcement、设计影响、具体设计落点、验证场景/verifier、状态或 N/A 理由。
|
|
125
|
+
4. required Rule 没有具体 heading/item 和验证方式时不得进入可评审状态;只写“遵循规范”无效。
|
|
126
|
+
5. N/A/waiver 必须逐 Rule 请求用户确认并调用 `decision`;`batch` 必须为 false,Agent 不得代确认。无有效确认来源时 fail-closed。
|
|
127
|
+
|
|
128
|
+
> 无 PRD 的新建模式也必须先 catalog/bind 并创建 Context,再写 Design;不能因为缺 PRD 跳过 Specs。
|
|
129
|
+
|
|
117
130
|
### 3. 交互式细化
|
|
118
131
|
|
|
119
132
|
围绕选中的维度逐步推进,**每轮提 2-3 个问题**(不要一次性抛 10 个问题):
|
|
@@ -194,7 +207,8 @@ CLI 场景:
|
|
|
194
207
|
3. **只包含适用的章节**,模板中有但需求不涉及的章节跳过
|
|
195
208
|
4. 用 `{}` 占位符替换为实际内容
|
|
196
209
|
5. PRD 派生模式:功能清单的"来源"列填 PRD 中对应的 US-XX
|
|
197
|
-
6.
|
|
210
|
+
6. 生成 `Spec Compliance Matrix`,每条 required Rule 的验证场景必须引用 S-/E-/B- 或说明仅可人工验证的外部边界
|
|
211
|
+
7. 展示生成的设计简报
|
|
198
212
|
|
|
199
213
|
**章节映射**(Lite 模板,对应 `design-lite.md` 实际章节):
|
|
200
214
|
|
|
@@ -271,6 +285,14 @@ CLI 场景:
|
|
|
271
285
|
- **全栈需求**:前后端各写一份到同一需求目录
|
|
272
286
|
4. 用 Write 写入最终路径
|
|
273
287
|
|
|
288
|
+
写入后,对 Matrix 每一行调用 `bind --stage design` 的 `applications` 回填 design artifact、`section_id`、稳定 `item_id` 和脚本计算的 hash;随后执行:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
python3 .code-flow/scripts/cf_spec_gate.py --task-dir <需求目录> --stage design --json
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
只有 Design Gate `decision=pass` 才能输出 Plan 下一步。缺 Matrix 行、artifact ref、验证方式、用户确认或发生 drift 时保留草稿并修复,不得推迟到 Coding/Done 才发现。
|
|
295
|
+
|
|
274
296
|
### 6. 输出摘要
|
|
275
297
|
|
|
276
298
|
```
|
|
@@ -282,6 +304,7 @@ CLI 场景:
|
|
|
282
304
|
- <按实际包含的章节列出>
|
|
283
305
|
- 验收标准: N 项
|
|
284
306
|
- 追溯自检: 每个 FEAT 有来源(US/需求)✓、有验收场景✓;每个场景有测试层级和关键真实边界✓;RULE/高影响 RISK 有验证场景✓;Full 模板 §6 矩阵闭合
|
|
307
|
+
- Spec Compliance: required Rules N/N 已落点;Design Gate: pass
|
|
285
308
|
- 性能自检: 标记性能敏感则方案须体现最优性能设计依据(§3.1/§3.5 或 lite §3.4),否则提示补全或显式声明"无性能敏感点"
|
|
286
309
|
|
|
287
310
|
下一步:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
description: 归档已完成的 task 文件
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
# cf-task:archive
|
|
6
7
|
|
|
7
8
|
归档已完成的整个 task 文件。归档前执行四维校验,归档后提示更新 specs。
|
|
@@ -36,6 +37,13 @@ description: 归档已完成的 task 文件
|
|
|
36
37
|
|
|
37
38
|
### 2. 归档前校验(Verify)
|
|
38
39
|
|
|
40
|
+
**Spec Context / Code Gate(先于四维校验)**:
|
|
41
|
+
|
|
42
|
+
- 需求目录必须存在 `spec-context.yml`;执行 `python3 .code-flow/scripts/cf_spec_context.py refresh --task-dir <需求目录> --root "$PWD" --json`
|
|
43
|
+
- 执行 `python3 .code-flow/scripts/cf_spec_gate.py --task-dir <需求目录> --stage code --json`;required Rule 的 stale/pending/conflict/unverified 任一存在即 FAIL
|
|
44
|
+
- 若 `.code-flow/.active-task.json` 仍指向本需求,必须先完成对应 TASK 的 Done Gate,并以 `{"gate_passed": true}` 调用 `active complete`;不得通过归档绕过 active 状态
|
|
45
|
+
- Context 与 Evidence 随需求目录一并归档,`_session` 仅是可重建投影,不是事实源
|
|
46
|
+
|
|
39
47
|
所有子任务 done 后,执行四维校验:
|
|
40
48
|
|
|
41
49
|
**完整性**:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
description: 从设计文档拆解需求,生成结构化任务文件
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
# cf-task:plan
|
|
6
7
|
|
|
7
8
|
从设计文档或设计简报拆解需求,生成结构化任务文件。
|
|
@@ -166,6 +167,13 @@ AI 从设计文档中识别关键缺口,输出结构化分析并与用户交
|
|
|
166
167
|
5. Checklist 禁止使用泛化的“编写测试”。必须写成 `[场景ID][测试层级] + 真实边界 + 关键断言`,并为新功能/缺陷修复安排先写测试和 RED 记录。
|
|
167
168
|
6. 如果场景缺测试层级、真实边界或 RULE/高影响 RISK 没有验证场景,先以 `#NOTES` 列为设计缺口;未解决前不得开始编码。
|
|
168
169
|
|
|
170
|
+
**Spec 责任拆解(硬约束)**:
|
|
171
|
+
|
|
172
|
+
1. 先执行 Context `refresh`,读取 Design `Spec Compliance Matrix` 与每个 design-stage/plan-stage Rule 的 `verifier_ref`,继承已有 applied refs,禁止重新选择或降级。
|
|
173
|
+
2. 每条 plan-stage required Rule 必须有且仅有一个责任 TASK;该 TASK 的 `Spec-Refs` 写完整 `{spec-id}#{rule-ref}`,Checklist 写具体 verifier 命令/输入,Acceptance Contract 写测试层级、不得 Mock 的真实边界和关键断言。
|
|
174
|
+
3. Rule 可被其他 TASK 引用为依赖,但 Acceptance Coverage 只能有一个最终负责人。E2E 层级**不得降级**;manual 仍仅限经用户确认的外部边界。
|
|
175
|
+
4. 缺 `verifier_ref`、责任 TASK 为 0/多个、Checklist/Contract 不含 Rule-specific 验证责任时,任务文件只能保留为缺口草稿,不能启动。
|
|
176
|
+
|
|
169
177
|
### 4. 生成任务文件方案
|
|
170
178
|
|
|
171
179
|
将拆解结果按以下格式组织,展示给用户确认:
|
|
@@ -213,6 +221,7 @@ AI 从设计文档中识别关键缺口,输出结构化分析并与用户交
|
|
|
213
221
|
- **Priority**: P0
|
|
214
222
|
- **Depends**:
|
|
215
223
|
- **Source**: docs/xxx.md#§3.1 数据模型(L83-L110)
|
|
224
|
+
- **Spec-Refs**: product-rules#RULE-product-001
|
|
216
225
|
- **Acceptance-Refs**: S-01, E-01, RULE-01
|
|
217
226
|
|
|
218
227
|
### Description
|
|
@@ -288,6 +297,14 @@ TASK-002: <标题> [P1]
|
|
|
288
297
|
3. 如果输入为 docs/ 设计文档(无需求目录)→ 按当前日期创建需求目录 `.code-flow/tasks/<YYYY-MM-DD>/<需求>/` 并写入其中
|
|
289
298
|
4. 用 Write 写入
|
|
290
299
|
|
|
300
|
+
写入后用 `bind --stage plan` 的 `applications` 将每条 required Rule 指向任务文件内唯一 TASK item,并执行:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
python3 .code-flow/scripts/cf_spec_gate.py --task-dir <需求目录> --stage plan --artifact <任务文件> --json
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
只有 Context Plan 状态与任务结构校验都 `decision=pass` 才输出 Start 下一步;缺唯一 owner、`verifier_ref`、测试层级或真实边界时必须回到拆解修复。
|
|
307
|
+
|
|
291
308
|
### 6. 输出摘要
|
|
292
309
|
|
|
293
310
|
```
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
description: 从一句话需求出发产生产品需求文档(PRD)
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
# cf-task:prd
|
|
6
7
|
|
|
7
8
|
从一句话需求出发,通过结构化对话产出产品需求文档(PRD),为 `cf-task:align` 提供足够丰富的输入。
|
|
@@ -38,6 +39,22 @@ description: 从一句话需求出发产生产品需求文档(PRD)
|
|
|
38
39
|
|
|
39
40
|
目的:**让 PRD 基于实际业务背景**,而非泛泛的通用描述。本步只读不提问,所有提问统一放到 Step 3。
|
|
40
41
|
|
|
42
|
+
### 2.1 先解析并绑定 Existing Specs(硬门禁)
|
|
43
|
+
|
|
44
|
+
在开始写需求内容前,先确定 Step 5 的需求目录和预计涉及路径,再执行:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python3 .code-flow/scripts/cf_spec_context.py catalog --stage prd --root "$PWD" --paths <预计路径...> --json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
1. 必须检查完整 `candidates`,不得按展示条数截断;只消费 resolver 返回的 `stages` 含 `prd` 的产品、安全、兼容、业务约束,`code-only` Spec 不得进入 PRD。
|
|
51
|
+
2. 对每个适用候选记录 `selected_by` 和具体 `reason`,立即用 `bind --stage prd` 写入需求目录的 `spec-context.yml`(首次 `applications` 为空),然后再进入需求细化。
|
|
52
|
+
3. required Rule 必须进入 PRD 的 `Existing Spec Constraints`,并明确影响哪个范围项、FEAT 验收或 NFR;只复制一句“遵循规范”不算承接。
|
|
53
|
+
4. 不适用的 required Rule 必须逐 Rule 说明具体理由并请求用户确认,再调用 `decision` 保存 `not_applicable`;禁止批量 N/A,**Agent 不得代确认**。
|
|
54
|
+
5. Catalog/Context 失败、同级 required 冲突或 required Rule 尚未决定时 fail-closed:保留草稿并修复,不得绕过后继续产出可批准 PRD。
|
|
55
|
+
|
|
56
|
+
> 预计路径尚不明确时仍须以空 `--paths` 运行,确保 global/task-stage 规则先进入 Context;后续范围明确时重新 catalog,并补充绑定新增候选。
|
|
57
|
+
|
|
41
58
|
### 3. 交互式细化
|
|
42
59
|
|
|
43
60
|
围绕 PRD 各要素逐步推进,**每轮提 2-3 个问题**:
|
|
@@ -98,6 +115,7 @@ description: 从一句话需求出发产生产品需求文档(PRD)
|
|
|
98
115
|
| 5. 非功能需求 | Step 3 的"非功能需求"(按需,分配 NFR 编号) |
|
|
99
116
|
| 6. 范围与边界 | Step 3 的"范围与边界" |
|
|
100
117
|
| 7. 依赖与风险 | Step 3 的"依赖与风险"(按需) |
|
|
118
|
+
| 8. Existing Spec Constraints | Step 2.1 的 prd-stage Rule;逐条关联范围/FEAT 验收/NFR |
|
|
101
119
|
|
|
102
120
|
**编号与追溯(硬约束,`cf-task:align` 派生的基础)**:
|
|
103
121
|
- 用户故事按出现顺序分配 `US-01`、`US-02`…,写入 §3.2 用户故事表
|
|
@@ -120,6 +138,12 @@ description: 从一句话需求出发产生产品需求文档(PRD)
|
|
|
120
138
|
3. **重名检查**:需求目录已存在且非本次草稿时,目录名追加序号 `<name>-2`、`<name>-3`… 避免覆盖;恢复模式复用原目录、不改名
|
|
121
139
|
4. 用 Write 写入 `.code-flow/tasks/<YYYY-MM-DD>/<name>/<name>.prd.md`
|
|
122
140
|
|
|
141
|
+
写入后必须完成 Context 回填和 PRD Gate:
|
|
142
|
+
|
|
143
|
+
1. 再次调用 `bind --stage prd`,`selections` 可为空,`applications` 为每条已承接 Rule 写入 `artifact`、`section_id`、`item_id`;artifact 必须指向本需求目录内 PRD,由脚本计算 `artifact_sha256`。
|
|
144
|
+
2. 执行 `python3 .code-flow/scripts/cf_spec_gate.py --task-dir <需求目录> --stage prd --json`。
|
|
145
|
+
3. 只有 `decision=pass` 才能把 PRD 标为可评审并输出下一步;pending/conflict/stale、缺少范围/验收落点或无有效用户确认的 N/A 均保持阻断。
|
|
146
|
+
|
|
123
147
|
**文件位置说明**:PRD、设计简报(`<name>.frontend.design.md` / `<name>.backend.design.md`)、任务文件(`<name>.md`)同放**需求目录** `.code-flow/tasks/<日期>/<name>/`,`cf-task:archive` 按整个需求目录归档。
|
|
124
148
|
|
|
125
149
|
### 6. 输出摘要
|
|
@@ -132,6 +156,7 @@ description: 从一句话需求出发产生产品需求文档(PRD)
|
|
|
132
156
|
- 用户故事: N 个
|
|
133
157
|
- 功能: N 个(P0: X, P1: Y)
|
|
134
158
|
- 追溯自检: FEAT↔US 全部关联 ✓ | 有缺口则列出「未关联 FEAT-XX / 未覆盖 US-XX」
|
|
159
|
+
- Existing Specs: N 个 / required Rules: N 条 / PRD Gate: pass
|
|
135
160
|
|
|
136
161
|
> 自检发现缺口(FEAT 无来源 US 或 US 无 FEAT 覆盖)时,在摘要显式标注并提示用户补 US 或降级,不要静默放过。
|
|
137
162
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
description: 激活子任务并开始编码
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
# cf-task:start
|
|
6
7
|
|
|
7
8
|
激活子任务并开始编码。支持单任务模式和整文件模式。
|
|
@@ -62,12 +63,15 @@ description: 激活子任务并开始编码
|
|
|
62
63
|
|
|
63
64
|
### 3. 激活并准备验收测试
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
在改状态或生产代码前,顺序固定且不得跳步:
|
|
67
|
+
|
|
68
|
+
1. 调用 `cf_spec_context.py refresh --task-dir ...`,执行 Start Gate;stale/pending/conflict 或依赖未闭合均不得继续。
|
|
69
|
+
2. 重新读取 refresh 后的 Context hash,再调用 `cf_spec_context.py active start`,传 task/context hash 和逐路径确认的 pre-existing ownership;已有/损坏 marker、未归属 diff 或 hash 不一致立即阻断。禁止先 start 再 refresh,避免 active marker 在编码前自行漂移。
|
|
70
|
+
3. 调用 `cf_spec_session.py`,只根据当前 TASK 的 `Spec-Refs`、Source 与 Acceptance Contract 覆盖写入 `_session/task-<name>.md`。禁止重新 catalog 或猜测规则。
|
|
71
|
+
4. 只有前三步全部成功,才用 Edit 更新子任务 Status 为 `in-progress`、追加 started log 并更新文件头日期。
|
|
72
|
+
5. 在修改任何生产代码前,为每个 Acceptance-Ref 填写测试文件、包含场景 ID 的测试用例名和可单独执行的命令
|
|
73
|
+
6. 先编写验收测试。E2E 测试必须经过契约声明的真实边界,不得用 mock 绕过 Store、Resolver、Builder、Renderer、Browser 等指定组件
|
|
74
|
+
7. 新功能或缺陷修复先执行一次测试并记录 RED:失败命令、失败用例和与预期缺陷对应的失败原因。纯重构或已有行为补测无法 RED 时,记录原因,不得伪造失败
|
|
71
75
|
|
|
72
76
|
RED 证据写入 `Acceptance Evidence`:
|
|
73
77
|
|
|
@@ -91,15 +95,15 @@ RED 证据写入 `Acceptance Evidence`:
|
|
|
91
95
|
4. 负责该场景的任务验证完成后,将全局 `Acceptance Coverage` 对应行改为 `verified`
|
|
92
96
|
5. 测试文件存在但未被测试框架收集、命令未实际执行、只有场景 ID 没有关键断言,都视为未验证
|
|
93
97
|
|
|
94
|
-
### 3.5
|
|
98
|
+
### 3.5 TASK-bound Spec Session
|
|
95
99
|
|
|
96
100
|
激活后,若任务文件头 Source 指向的 design 文档含验收条件章节(如 §2.5 验收条件 / 验收标准):
|
|
97
101
|
|
|
98
|
-
1. 只提取当前任务 `Acceptance-Refs`
|
|
102
|
+
1. 只提取当前任务 `Spec-Refs` 与 `Acceptance-Refs` 对应的 Rule hash、verifier、artifact refs、场景、测试层级和真实边界,生成 `.code-flow/specs/_session/task-<name>.md`:
|
|
99
103
|
- frontmatter:`description: 当前任务 <name> 的验收约束(cf-task:start 生成,archive 清理)`
|
|
100
104
|
- 正文:验收场景与约束的精简列表(≤300 token);必须保留全部引用 ID、层级、边界和预期结果,不能为压缩而删除场景
|
|
101
|
-
2. Spec Catalog
|
|
102
|
-
3.
|
|
105
|
+
2. 任务模式直接读取这个投影,禁止经 Spec Catalog 二次选择;同名文件已存在则原子覆盖。
|
|
106
|
+
3. 50 Rule 等超预算任务按 required/当前阶段优先输出受控摘要,完整 Context 保留不丢失,并明确提示拆 TASK;不得用截断隐藏 required 缺口。
|
|
103
107
|
|
|
104
108
|
> `_session/` 不入库(.gitignore 模板已覆盖)、不参与规范审计与预算。
|
|
105
109
|
|
|
@@ -21,13 +21,13 @@ function pythonPath(projectRoot, script) {
|
|
|
21
21
|
return join(projectRoot, SCRIPT_DIR, script);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function callHook(projectRoot, script, input) {
|
|
24
|
+
function callHook(projectRoot, script, input, timeout = 5000) {
|
|
25
25
|
try {
|
|
26
26
|
const proc = spawnSync("python3", [pythonPath(projectRoot, script)], {
|
|
27
27
|
cwd: projectRoot,
|
|
28
28
|
input: JSON.stringify(input),
|
|
29
29
|
encoding: "utf-8",
|
|
30
|
-
timeout
|
|
30
|
+
timeout,
|
|
31
31
|
maxBuffer: 1024 * 1024,
|
|
32
32
|
});
|
|
33
33
|
if (proc.error || proc.status !== 0) {
|
|
@@ -60,13 +60,12 @@ export const CodeFlow = async (ctx) => {
|
|
|
60
60
|
const sid = input.event?.properties?.info?.id || "";
|
|
61
61
|
debugLog(projectRoot, `session.created sid=${sid}`);
|
|
62
62
|
if (sid) sessionContext.delete(sid);
|
|
63
|
-
callHook(projectRoot, "cf_session_hook.py", { session_id: sid });
|
|
64
63
|
}
|
|
65
64
|
if (input.event?.type === "session.idle") {
|
|
66
65
|
const sid =
|
|
67
66
|
input.event?.properties?.sessionID ||
|
|
68
67
|
input.event?.properties?.info?.id || "";
|
|
69
|
-
const result = callHook(projectRoot, "cf_stop_hook.py", { session_id: sid });
|
|
68
|
+
const result = callHook(projectRoot, "cf_stop_hook.py", { session_id: sid }, 35000);
|
|
70
69
|
if (result?.reason) {
|
|
71
70
|
// idle 无法阻断,校验失败排队到下一轮 system prompt
|
|
72
71
|
const pending = sessionContext.get(sid);
|
package/src/cli.js
CHANGED
|
@@ -7,9 +7,11 @@ const path = require('path');
|
|
|
7
7
|
const { spawnSync } = require('child_process');
|
|
8
8
|
|
|
9
9
|
const pkg = require('../package.json');
|
|
10
|
+
const specWorkflowMigration = require('./migrate/spec-workflow');
|
|
10
11
|
|
|
11
12
|
const usage = [
|
|
12
13
|
'Usage: code-flow init [--force] [--platform=<claude|codex|costrict|opencode>]',
|
|
14
|
+
' code-flow migrate --spec-workflow <--dry-run|--prepare|--apply --plan <path>|--rollback <id>>',
|
|
13
15
|
' code-flow -v | --version',
|
|
14
16
|
' code-flow -h | --help'
|
|
15
17
|
].join('\n');
|
|
@@ -469,15 +471,19 @@ function parsePlatform(args) {
|
|
|
469
471
|
// --- Main init ---
|
|
470
472
|
|
|
471
473
|
function runInit(force, platform) {
|
|
474
|
+
const cwd = process.cwd();
|
|
475
|
+
const installedVersion = readInstalledVersion(cwd);
|
|
476
|
+
if (installedVersion && compareVersions(installedVersion, '0.4.2') >= 0 && compareVersions(installedVersion, '0.6.0') < 0) {
|
|
477
|
+
process.stdout.write(`${JSON.stringify({ status: 'migration_required', version: installedVersion, command: 'code-flow migrate --spec-workflow --dry-run' })}\n`);
|
|
478
|
+
process.exit(3);
|
|
479
|
+
}
|
|
472
480
|
ensurePython3();
|
|
473
481
|
|
|
474
|
-
const cwd = process.cwd();
|
|
475
482
|
const baseDir = __dirname;
|
|
476
483
|
const coreDir = path.join(baseDir, 'core');
|
|
477
484
|
const adaptersDir = path.join(baseDir, 'adapters');
|
|
478
485
|
|
|
479
486
|
// Determine mode
|
|
480
|
-
const installedVersion = readInstalledVersion(cwd);
|
|
481
487
|
let mode;
|
|
482
488
|
if (force) {
|
|
483
489
|
mode = 'force';
|
|
@@ -778,6 +784,45 @@ function runInit(force, platform) {
|
|
|
778
784
|
|
|
779
785
|
const args = process.argv.slice(2);
|
|
780
786
|
|
|
787
|
+
function argumentValue(values, name) {
|
|
788
|
+
const index = values.indexOf(name);
|
|
789
|
+
return index >= 0 ? values[index + 1] || '' : '';
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function migrationExitCode(result) {
|
|
793
|
+
if (result.status === 'rolled_back') return 4;
|
|
794
|
+
if (result.status === 'recovery_required') return 5;
|
|
795
|
+
if (result.status === 'prepared_blocked') return 3;
|
|
796
|
+
return 0;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function runMigrate(values) {
|
|
800
|
+
if (!values.includes('--spec-workflow')) fail('Error: migrate requires --spec-workflow.');
|
|
801
|
+
const actions = ['--dry-run', '--prepare', '--apply', '--rollback'].filter(action => values.includes(action));
|
|
802
|
+
if (actions.length !== 1) fail('Error: choose exactly one migrate action.');
|
|
803
|
+
let result;
|
|
804
|
+
try {
|
|
805
|
+
if (actions[0] === '--dry-run') result = specWorkflowMigration.preview(process.cwd());
|
|
806
|
+
if (actions[0] === '--prepare') result = specWorkflowMigration.prepare(process.cwd());
|
|
807
|
+
if (actions[0] === '--apply') {
|
|
808
|
+
const plan = argumentValue(values, '--plan');
|
|
809
|
+
if (!plan) fail('Error: --apply requires --plan <path>.');
|
|
810
|
+
result = specWorkflowMigration.apply(path.resolve(plan));
|
|
811
|
+
}
|
|
812
|
+
if (actions[0] === '--rollback') {
|
|
813
|
+
const id = argumentValue(values, '--rollback');
|
|
814
|
+
if (!id) fail('Error: --rollback requires a migration id.');
|
|
815
|
+
const plan = path.join(process.cwd(), '.code-flow', 'migrations', id, 'migration-plan.json');
|
|
816
|
+
result = specWorkflowMigration.rollback(plan);
|
|
817
|
+
}
|
|
818
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
819
|
+
process.exit(migrationExitCode(result));
|
|
820
|
+
} catch (error) {
|
|
821
|
+
process.stdout.write(`${JSON.stringify({ status: 'unresolved', error: String(error.message || error) })}\n`);
|
|
822
|
+
process.exit(3);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
781
826
|
if (args.includes('-v') || args.includes('--version')) {
|
|
782
827
|
process.stdout.write(`${pkg.version}\n`);
|
|
783
828
|
process.exit(0);
|
|
@@ -799,6 +844,10 @@ if (args[0] === 'init') {
|
|
|
799
844
|
runInit(force, platform);
|
|
800
845
|
}
|
|
801
846
|
|
|
847
|
+
if (args[0] === 'migrate') {
|
|
848
|
+
runMigrate(args.slice(1));
|
|
849
|
+
}
|
|
850
|
+
|
|
802
851
|
if (args.length === 0) {
|
|
803
852
|
fail('Error: missing command.');
|
|
804
853
|
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
version: 1
|
|
2
2
|
|
|
3
|
+
spec_workflow:
|
|
4
|
+
schema_version: 1
|
|
5
|
+
enforcement: required # required=全门禁 | warn=门禁仅记录不阻断 | inject=只注入不门禁
|
|
6
|
+
drift_check: true
|
|
7
|
+
scope_expansion_check: true
|
|
8
|
+
metrics: true
|
|
9
|
+
catalog:
|
|
10
|
+
dedup_window: 5
|
|
11
|
+
|
|
3
12
|
budget:
|
|
4
13
|
total: 2500
|
|
5
14
|
l0_max: 800
|
|
@@ -8,17 +17,6 @@ budget:
|
|
|
8
17
|
# catalog 模式下 spec 目录的 token 上限(超出按 Tier1 优先截断并告警)
|
|
9
18
|
catalog_max: 200
|
|
10
19
|
|
|
11
|
-
inject:
|
|
12
|
-
auto: true
|
|
13
|
-
# 注入模式:
|
|
14
|
-
# catalog — UserPromptSubmit 仅注入极小 spec 目录(每 spec 一行适用场景),
|
|
15
|
-
# 由模型按语义自行 Read 相关 spec 全文;prompt 含明确文件路径且
|
|
16
|
-
# 命中 domain 时仍直接注入完整约束。PreToolUse 不受影响(编辑时
|
|
17
|
-
# 始终强制注入完整约束兜底)。
|
|
18
|
-
# full — 旧行为:UserPromptSubmit 按 tag 词表匹配注入完整 spec。
|
|
19
|
-
# 仅 literal "catalog" 启用目录模式;缺失/其他值一律 full(升级用户行为不变)。
|
|
20
|
-
mode: catalog
|
|
21
|
-
|
|
22
20
|
# 质量闭环(v0.5):合规反馈 + 学习采集。
|
|
23
21
|
# enabled 仅 literal true 启用(缺失即关闭,升级用户行为不变);
|
|
24
22
|
# 子开关缺省跟随 enabled,仅 literal false 单独关闭。
|
|
@@ -31,11 +29,7 @@ quality_loop:
|
|
|
31
29
|
# 注入时对 spec 内容做保守无损压缩(去行尾空白、折叠多空行、剥 HTML 注释、去重 bullet)
|
|
32
30
|
# 缺省视为 true;仅显式 false 关闭
|
|
33
31
|
compress: true
|
|
34
|
-
#
|
|
35
|
-
# 0 或负数 关闭去重(每次都注入,等同旧行为);缺省 5。
|
|
36
|
-
# 窗口"限速"而非"永久跳过",超过 N 个 prompt 后会再次注入,保证 auto-compaction
|
|
37
|
-
# 丢掉早期上下文后仍能恢复 spec。
|
|
38
|
-
dedup_window: 5
|
|
32
|
+
compress_reminder: true # 会话过长每 N 轮提醒 /compact(per-session 计数,缺省 true)
|
|
39
33
|
code_extensions:
|
|
40
34
|
- ".py"
|
|
41
35
|
- ".ts"
|
|
@@ -73,7 +67,9 @@ quality_loop:
|
|
|
73
67
|
- "*.config.*"
|
|
74
68
|
- ".code-flow/**"
|
|
75
69
|
- ".claude/**"
|
|
70
|
+
- ".codex/**"
|
|
76
71
|
- ".costrict/**"
|
|
72
|
+
- ".opencode/**"
|
|
77
73
|
- "node_modules/**"
|
|
78
74
|
- "dist/**"
|
|
79
75
|
- "build/**"
|
|
@@ -112,6 +108,8 @@ path_mapping:
|
|
|
112
108
|
- "src/components/**"
|
|
113
109
|
- "src/pages/**"
|
|
114
110
|
- "src/hooks/**"
|
|
111
|
+
- "src/composables/**"
|
|
112
|
+
- "src/services/**"
|
|
115
113
|
- "src/styles/**"
|
|
116
114
|
- "**/*.tsx"
|
|
117
115
|
- "**/*.jsx"
|
|
@@ -16,6 +16,7 @@ import fnmatch
|
|
|
16
16
|
import os
|
|
17
17
|
import re
|
|
18
18
|
import sys
|
|
19
|
+
from typing import Mapping
|
|
19
20
|
from concurrent.futures import ThreadPoolExecutor
|
|
20
21
|
from concurrent.futures import TimeoutError as FutureTimeout
|
|
21
22
|
|
|
@@ -304,3 +305,32 @@ def run_checks(
|
|
|
304
305
|
"severity": check["severity"],
|
|
305
306
|
})
|
|
306
307
|
return violations, skipped
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def run_regex_verifier(
|
|
311
|
+
pattern: str,
|
|
312
|
+
files: str,
|
|
313
|
+
contents: Mapping[str, str],
|
|
314
|
+
timeout: float = CHECK_TIMEOUT_SECONDS,
|
|
315
|
+
) -> tuple[list[dict[str, object]], list[dict[str, object]]]:
|
|
316
|
+
"""Execute one verifier regex over explicit file contents."""
|
|
317
|
+
try:
|
|
318
|
+
compiled = re.compile(pattern)
|
|
319
|
+
except re.error as exc:
|
|
320
|
+
return [], [{"check_id": "verifier", "reason": f"invalid_regex:{exc}"}]
|
|
321
|
+
check = {
|
|
322
|
+
"id": "verifier",
|
|
323
|
+
"type": "regex",
|
|
324
|
+
"pattern": pattern,
|
|
325
|
+
"regex": compiled,
|
|
326
|
+
"files": files,
|
|
327
|
+
"message": "Rule verifier matched forbidden content",
|
|
328
|
+
"severity": "warn",
|
|
329
|
+
}
|
|
330
|
+
violations: list[dict[str, object]] = []
|
|
331
|
+
skipped: list[dict[str, object]] = []
|
|
332
|
+
for relative, content in contents.items():
|
|
333
|
+
file_violations, file_skipped = run_checks([check], relative, content, timeout=timeout)
|
|
334
|
+
violations.extend(file_violations)
|
|
335
|
+
skipped.extend(file_skipped)
|
|
336
|
+
return violations, skipped
|