@michaelxwb/code-flow 0.5.2 → 0.6.0
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 +50 -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 +55 -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 +50 -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 +54 -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 +0 -1
- package/src/cli.js +51 -2
- package/src/core/code-flow/config.yml +13 -16
- package/src/core/code-flow/scripts/cf_checks.py +30 -0
- package/src/core/code-flow/scripts/cf_core.py +12 -255
- package/src/core/code-flow/scripts/cf_log.py +8 -0
- package/src/core/code-flow/scripts/cf_post_hook.py +33 -9
- package/src/core/code-flow/scripts/cf_pre_tool_hook.py +65 -0
- package/src/core/code-flow/scripts/cf_spec_context.py +1233 -0
- package/src/core/code-flow/scripts/cf_spec_gate.py +224 -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 +145 -0
- package/src/core/code-flow/scripts/cf_spec_session.py +122 -0
- package/src/core/code-flow/scripts/cf_spec_verify.py +293 -0
- package/src/core/code-flow/scripts/cf_stats.py +40 -5
- package/src/core/code-flow/scripts/cf_stop_hook.py +22 -2
- package/src/core/code-flow/scripts/cf_task_runtime.py +142 -0
- package/src/core/code-flow/scripts/cf_user_prompt_hook.py +34 -360
- 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
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
+
id: frontend-quality-standards
|
|
2
3
|
description: 写前端代码时适用:类型、lint、错误处理、测试、状态管理质量约束
|
|
4
|
+
stages: [design, plan, code, review]
|
|
5
|
+
enforcement: required
|
|
6
|
+
verifiers:
|
|
7
|
+
- rule: RULE-frontend-quality-001
|
|
8
|
+
type: manual
|
|
9
|
+
config:
|
|
10
|
+
checklist: Confirm all Guidance and Avoid items for this Spec.
|
|
11
|
+
owner: project-owner
|
|
3
12
|
---
|
|
4
13
|
|
|
5
14
|
# Frontend Quality Standards
|
|
@@ -27,6 +36,9 @@ render(data);
|
|
|
27
36
|
```
|
|
28
37
|
|
|
29
38
|
## Rules
|
|
39
|
+
- [RULE-frontend-quality-001] The implementation must satisfy every applicable item in Guidance and avoid every item in Avoid.
|
|
40
|
+
|
|
41
|
+
## Guidance
|
|
30
42
|
- TypeScript 项目禁止使用 `any`,未知类型用 `unknown` 并显式收敛
|
|
31
43
|
- 组件统一使用函数组件(React)/ 组合式 API(Vue 3),禁止类组件新增
|
|
32
44
|
- 关键交互(提交 / 删除 / 支付)必须有错误提示与 loading 状态
|
|
@@ -39,7 +51,7 @@ render(data);
|
|
|
39
51
|
- 网络请求统一处理 401 / 403 / 5xx,避免每个调用方重复
|
|
40
52
|
- 关键路径补端到端测试或组件交互测试
|
|
41
53
|
|
|
42
|
-
##
|
|
54
|
+
## Avoid
|
|
43
55
|
- 禁止在 render / setup 中发起未受控的副作用(用 `useEffect` / `onMounted`)
|
|
44
56
|
- 禁止把后端错误直接抛给用户(如 SQL / stack trace)
|
|
45
57
|
- 禁止用 `// @ts-ignore` 绕过类型错误,必须修复或改为 `@ts-expect-error` 加注释
|
|
@@ -233,6 +233,16 @@
|
|
|
233
233
|
|
|
234
234
|
---
|
|
235
235
|
|
|
236
|
+
## Spec Compliance Matrix
|
|
237
|
+
|
|
238
|
+
> 从需求目录 `spec-context.yml` 继承并逐 Rule 回填。required Rule 必须有具体设计落点和 verifier/验收场景;N/A 只接受逐项用户确认。
|
|
239
|
+
|
|
240
|
+
| Spec/Rule | enforcement | 设计影响 | 设计落点 | 验证场景 | 状态/N/A 理由 |
|
|
241
|
+
|-----------|-------------|---------|---------|---------|----------------|
|
|
242
|
+
| `{spec-id}#RULE-{domain}-001` | required / advisory | <对方案/交互的影响> | §<heading> / <item-id> | S-/E-/B- + verifier | applied / confirmed N/A |
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
236
246
|
## 附录:术语表
|
|
237
247
|
|
|
238
248
|
| 术语 | 定义 |
|
|
@@ -475,6 +475,16 @@ flowchart TD
|
|
|
475
475
|
|
|
476
476
|
---
|
|
477
477
|
|
|
478
|
+
## Spec Compliance Matrix
|
|
479
|
+
|
|
480
|
+
> 从需求目录 `spec-context.yml` 继承并逐 Rule 回填。required Rule 必须有具体设计落点和 verifier/验收场景;N/A 只接受逐项用户确认。
|
|
481
|
+
|
|
482
|
+
| Spec/Rule | enforcement | 设计影响 | 设计落点 | 验证场景 | 状态/N/A 理由 |
|
|
483
|
+
|-----------|-------------|---------|---------|---------|----------------|
|
|
484
|
+
| `{spec-id}#RULE-{domain}-001` | required / advisory | <对方案的影响> | §<heading> / <item-id> | S-/E-/B- + verifier | applied / confirmed N/A |
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
478
488
|
## 附录:术语表
|
|
479
489
|
|
|
480
490
|
| 术语 | 定义 |
|
|
@@ -282,6 +282,16 @@ flowchart TD
|
|
|
282
282
|
|
|
283
283
|
---
|
|
284
284
|
|
|
285
|
+
## Spec Compliance Matrix
|
|
286
|
+
|
|
287
|
+
> 从需求目录 `spec-context.yml` 继承并逐 Rule 回填。required Rule 必须有具体设计落点和 verifier/验收场景;N/A 只接受逐项用户确认。
|
|
288
|
+
|
|
289
|
+
| Spec/Rule | enforcement | 设计影响 | 设计落点 | 验证场景 | 状态/N/A 理由 |
|
|
290
|
+
|-----------|-------------|---------|---------|---------|----------------|
|
|
291
|
+
| `{spec-id}#RULE-{domain}-001` | required / advisory | <对方案的影响> | §<heading> / <item-id> | S-/E-/B- + verifier | applied / confirmed N/A |
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
285
295
|
## 附录:术语表
|
|
286
296
|
|
|
287
297
|
| 术语 | 定义 |
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
> **状态**: 草稿 / 评审中 / 已批准 / 开发中 / 已上线
|
|
8
8
|
|
|
9
9
|
**评审边界说明**:
|
|
10
|
-
- **需求基线**: 第 2-
|
|
11
|
-
- **设计输入**: 第 3-
|
|
10
|
+
- **需求基线**: 第 2-8 章(背景/用户/功能/非功能/范围/依赖/已有规范约束)→ 通过后锁定
|
|
11
|
+
- **设计输入**: 第 3-8 章 → 为 `cf-task:align` 提供 US / FEAT / NFR / 范围 / Spec refs
|
|
12
12
|
|
|
13
|
-
**ID 体系**: US(用户故事)、FEAT(功能)、NFR
|
|
13
|
+
**ID 体系**: US(用户故事)、FEAT(功能)、NFR(非功能指标)、Spec/Rule(已有规范引用)
|
|
14
14
|
|
|
15
15
|
**适用场景**: 需求早期阶段,在设计之前
|
|
16
16
|
**不适用**: 已有明确技术方案(请直接用 cf-task:align)
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
- [5. 非功能需求](#5-非功能需求-可选)
|
|
35
35
|
- [6. 范围与边界](#6-范围与边界-必填)
|
|
36
36
|
- [7. 依赖与风险](#7-依赖与风险-可选)
|
|
37
|
+
- [8. Existing Spec Constraints](#8-existing-spec-constraints)
|
|
37
38
|
- [附录:术语表](#附录术语表)
|
|
38
39
|
|
|
39
40
|
---
|
|
@@ -172,6 +173,16 @@
|
|
|
172
173
|
|
|
173
174
|
---
|
|
174
175
|
|
|
176
|
+
## 8. Existing Spec Constraints
|
|
177
|
+
|
|
178
|
+
> 仅列出 `stages` 含 `prd` 且已绑定到 `spec-context.yml` 的 Rule。required Rule 必须明确落到范围、FEAT 验收或 NFR;不适用必须逐条记录用户确认,不能由 Agent 自行填写 N/A。
|
|
179
|
+
|
|
180
|
+
| Spec/Rule | 约束 | 对范围/验收的影响 | 状态 |
|
|
181
|
+
|-----------|------|------------------|------|
|
|
182
|
+
| `{spec-id}#RULE-{domain}-001` | <规则摘要> | <In/Out Scope、FEAT-XX 验收或 NFR-XX> | applied / confirmed N/A |
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
175
186
|
## 附录:术语表
|
|
176
187
|
|
|
177
188
|
| 术语 | 定义 |
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const childProcess = require("node:child_process");
|
|
4
|
+
const crypto = require("node:crypto");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const path = require("node:path");
|
|
7
|
+
|
|
8
|
+
const PREFLIGHT = path.join(__dirname, "../core/code-flow/scripts/cf_spec_migrate.py");
|
|
9
|
+
|
|
10
|
+
function walkFiles(root, directory = root) {
|
|
11
|
+
const files = [];
|
|
12
|
+
if (!fs.existsSync(directory)) return files;
|
|
13
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
14
|
+
const full = path.join(directory, entry.name);
|
|
15
|
+
const relative = path.relative(root, full);
|
|
16
|
+
if (entry.isDirectory() && relative === path.join(".code-flow", "migrations")) continue;
|
|
17
|
+
if (entry.isDirectory()) files.push(...walkFiles(root, full));
|
|
18
|
+
else files.push(relative);
|
|
19
|
+
}
|
|
20
|
+
return files.sort();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function migrationFiles(root) {
|
|
24
|
+
const entries = [".code-flow", ".claude", ".codex", ".costrict", ".opencode", ".agents", "AGENTS.md", "CLAUDE.md"];
|
|
25
|
+
const files = [];
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const target = path.join(root, entry);
|
|
28
|
+
if (!fs.existsSync(target)) continue;
|
|
29
|
+
if (fs.statSync(target).isDirectory()) files.push(...walkFiles(root, target));
|
|
30
|
+
else files.push(entry);
|
|
31
|
+
}
|
|
32
|
+
return [...new Set(files)].sort();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function hash(data) {
|
|
36
|
+
return crypto.createHash("sha256").update(data).digest("hex");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function sourceHashes(root) {
|
|
40
|
+
const result = {};
|
|
41
|
+
for (const relative of migrationFiles(root)) {
|
|
42
|
+
result[relative] = hash(fs.readFileSync(path.join(root, relative)));
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function pythonCommand() {
|
|
48
|
+
return process.env.PYTHON || "python3";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function preview(root) {
|
|
52
|
+
const project = fs.realpathSync(root);
|
|
53
|
+
const result = childProcess.spawnSync(
|
|
54
|
+
pythonCommand(), [PREFLIGHT, "preflight", "--root", project, "--json"],
|
|
55
|
+
{ encoding: "utf8", shell: false }
|
|
56
|
+
);
|
|
57
|
+
if (result.status !== 0) throw new Error(result.stderr || result.stdout || "preflight failed");
|
|
58
|
+
return JSON.parse(result.stdout);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function migrationId(root, hashes) {
|
|
62
|
+
const seed = `${root}\0${JSON.stringify(hashes)}\0${Date.now()}`;
|
|
63
|
+
return `spec-workflow-${hash(seed).slice(0, 12)}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function copyBackup(root, backup, hashes) {
|
|
67
|
+
for (const relative of Object.keys(hashes)) {
|
|
68
|
+
const target = path.join(backup, relative);
|
|
69
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
70
|
+
fs.copyFileSync(path.join(root, relative), target);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function writeJson(target, value) {
|
|
75
|
+
fs.writeFileSync(target, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function prepare(root) {
|
|
79
|
+
const project = fs.realpathSync(root);
|
|
80
|
+
const plan = preview(project);
|
|
81
|
+
const hashes = sourceHashes(project);
|
|
82
|
+
const id = migrationId(project, hashes);
|
|
83
|
+
const workspace = path.join(project, ".code-flow", "migrations", id);
|
|
84
|
+
const backup = path.join(workspace, "backup");
|
|
85
|
+
const staging = path.join(workspace, "staging");
|
|
86
|
+
fs.mkdirSync(backup, { recursive: true });
|
|
87
|
+
fs.mkdirSync(staging, { recursive: true });
|
|
88
|
+
copyBackup(project, backup, hashes);
|
|
89
|
+
const preparedPlan = { ...plan, migration_id: id, project_root: project, source_hashes: hashes };
|
|
90
|
+
const status = plan.unresolved.length ? "prepared_blocked" : "prepared";
|
|
91
|
+
writeJson(path.join(workspace, "migration-plan.json"), preparedPlan);
|
|
92
|
+
writeJson(path.join(workspace, "journal.json"), { version: 1, migration_id: id, status, operations: [] });
|
|
93
|
+
return { migration_id: id, status, workspace, plan: path.join(workspace, "migration-plan.json") };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readJson(target) {
|
|
97
|
+
return JSON.parse(fs.readFileSync(target, "utf8"));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function journalPaths(planPath) {
|
|
101
|
+
const workspace = path.dirname(planPath);
|
|
102
|
+
return { workspace, journal: path.join(workspace, "journal.json"), staging: path.join(workspace, "staging"), backup: path.join(workspace, "backup") };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function sameHashes(first, second) {
|
|
106
|
+
return JSON.stringify(first) === JSON.stringify(second);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function runStaging(planPath, locations) {
|
|
110
|
+
const result = childProcess.spawnSync(
|
|
111
|
+
pythonCommand(), [PREFLIGHT, "stage", "--plan", planPath, "--staging", locations.staging, "--json"],
|
|
112
|
+
{ encoding: "utf8", shell: false }
|
|
113
|
+
);
|
|
114
|
+
if (result.status !== 0) throw new Error(result.stdout || result.stderr || "staging failed");
|
|
115
|
+
return JSON.parse(result.stdout);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function verifyStaging(staging, manifest) {
|
|
119
|
+
for (const item of manifest.targets) {
|
|
120
|
+
const target = path.join(staging, item.path);
|
|
121
|
+
if (!fs.existsSync(target) || hash(fs.readFileSync(target)) !== item.sha256) {
|
|
122
|
+
throw new Error(`staging hash mismatch: ${item.path}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function persistJournal(target, journal, status) {
|
|
128
|
+
journal.status = status;
|
|
129
|
+
writeJson(target, journal);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function targetEntries(manifest) {
|
|
133
|
+
return manifest.targets.filter((item) => item.path !== ".code-flow/.version");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function commitEntry(project, staging, item, journal, journalPath) {
|
|
137
|
+
const source = path.join(staging, item.path);
|
|
138
|
+
const target = path.join(project, item.path);
|
|
139
|
+
journal.operations.push({ path: item.path, status: "renaming" });
|
|
140
|
+
writeJson(journalPath, journal);
|
|
141
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
142
|
+
if (fs.existsSync(target)) fs.rmSync(target, { recursive: true, force: true });
|
|
143
|
+
fs.renameSync(source, target);
|
|
144
|
+
journal.operations[journal.operations.length - 1].status = "committed";
|
|
145
|
+
writeJson(journalPath, journal);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function obsoletePaths(plan, manifest) {
|
|
149
|
+
const targets = new Set(manifest.targets.map((item) => item.path));
|
|
150
|
+
return Object.keys(plan.source_hashes).filter((item) => item.startsWith(".code-flow/") && item !== ".code-flow/.version" && !targets.has(item));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function removeObsolete(project, paths) {
|
|
154
|
+
for (const relative of paths) fs.rmSync(path.join(project, relative), { recursive: true, force: true });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function restoreBackup(plan, locations, manifest) {
|
|
158
|
+
for (const item of manifest.targets || []) {
|
|
159
|
+
if (item.path !== ".code-flow/.version") fs.rmSync(path.join(plan.project_root, item.path), { recursive: true, force: true });
|
|
160
|
+
}
|
|
161
|
+
for (const relative of Object.keys(plan.source_hashes)) {
|
|
162
|
+
const source = path.join(locations.backup, relative);
|
|
163
|
+
const target = path.join(plan.project_root, relative);
|
|
164
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
165
|
+
fs.copyFileSync(source, target);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function rollback(planPath, error = "requested") {
|
|
170
|
+
const plan = readJson(planPath);
|
|
171
|
+
const locations = journalPaths(planPath);
|
|
172
|
+
const journal = readJson(locations.journal);
|
|
173
|
+
try {
|
|
174
|
+
restoreBackup(plan, locations, journal.manifest || { targets: [] });
|
|
175
|
+
journal.error = String(error);
|
|
176
|
+
persistJournal(locations.journal, journal, "rolled_back");
|
|
177
|
+
return { status: "rolled_back", migration_id: plan.migration_id };
|
|
178
|
+
} catch (restoreError) {
|
|
179
|
+
journal.error = String(restoreError);
|
|
180
|
+
persistJournal(locations.journal, journal, "recovery_required");
|
|
181
|
+
return { status: "recovery_required", migration_id: plan.migration_id };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function targetsMatch(project, manifest) {
|
|
186
|
+
return targetEntries(manifest).every((item) => {
|
|
187
|
+
const target = path.join(project, item.path);
|
|
188
|
+
return fs.existsSync(target) && hash(fs.readFileSync(target)) === item.sha256;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function finishVersion(plan, journal, journalPath) {
|
|
193
|
+
fs.writeFileSync(path.join(plan.project_root, ".code-flow/.version"), "0.6.0", "utf8");
|
|
194
|
+
persistJournal(journalPath, journal, "committed");
|
|
195
|
+
return { status: "committed", migration_id: plan.migration_id };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function recoverCommitting(plan, journal, locations, options) {
|
|
199
|
+
if (!targetsMatch(plan.project_root, journal.manifest)) return rollback(path.join(locations.workspace, "migration-plan.json"), "target hash mismatch");
|
|
200
|
+
if (options.interruptBeforeVersion) return { status: "committing", migration_id: plan.migration_id };
|
|
201
|
+
return finishVersion(plan, journal, locations.journal);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function apply(planPath, options = {}) {
|
|
205
|
+
const plan = readJson(planPath);
|
|
206
|
+
const locations = journalPaths(planPath);
|
|
207
|
+
const versionPath = path.join(plan.project_root, ".code-flow/.version");
|
|
208
|
+
if (fs.existsSync(versionPath) && fs.readFileSync(versionPath, "utf8").trim() === "0.6.0") return { status: "already_migrated", migration_id: plan.migration_id };
|
|
209
|
+
const journal = readJson(locations.journal);
|
|
210
|
+
if (journal.status === "committing") return recoverCommitting(plan, journal, locations, options);
|
|
211
|
+
if (plan.unresolved.length) throw new Error("migration plan has unresolved items");
|
|
212
|
+
if (!sameHashes(sourceHashes(plan.project_root), plan.source_hashes)) throw new Error("source hash drift");
|
|
213
|
+
try {
|
|
214
|
+
const manifest = runStaging(planPath, locations);
|
|
215
|
+
verifyStaging(locations.staging, manifest);
|
|
216
|
+
journal.manifest = manifest;
|
|
217
|
+
persistJournal(locations.journal, journal, "committing");
|
|
218
|
+
let completed = 0;
|
|
219
|
+
for (const item of targetEntries(manifest)) {
|
|
220
|
+
if (options.failAfter === completed) throw new Error(`injected rename failure ${completed}`);
|
|
221
|
+
if (options.exdevAfter === completed) {
|
|
222
|
+
const exdev = new Error("injected EXDEV");
|
|
223
|
+
exdev.code = "EXDEV";
|
|
224
|
+
throw exdev;
|
|
225
|
+
}
|
|
226
|
+
commitEntry(plan.project_root, locations.staging, item, journal, locations.journal);
|
|
227
|
+
completed += 1;
|
|
228
|
+
}
|
|
229
|
+
removeObsolete(plan.project_root, obsoletePaths(plan, manifest));
|
|
230
|
+
removeObsolete(plan.project_root, manifest.deletions || []);
|
|
231
|
+
if (options.interruptBeforeVersion) return { status: "committing", migration_id: plan.migration_id };
|
|
232
|
+
return finishVersion(plan, journal, locations.journal);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
return rollback(planPath, error);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
module.exports = { apply, preview, prepare, rollback, sourceHashes, walkFiles };
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# cf-inject
|
|
2
|
-
|
|
3
|
-
强制重新注入指定领域的编码规范(通常不需要手动调用)。
|
|
4
|
-
|
|
5
|
-
## 自动注入机制
|
|
6
|
-
|
|
7
|
-
本项目的规范注入是**全自动**的,通过两层机制保障:
|
|
8
|
-
|
|
9
|
-
1. **CLAUDE.md 指令**:Claude 根据问题自动判断领域,读取 `_map.md` 导航地图理解项目结构
|
|
10
|
-
2. **PreToolUse Hook**:编辑代码文件时根据文件路径标签自动注入匹配的约束 specs
|
|
11
|
-
|
|
12
|
-
两层职责分离:CLAUDE.md 负责导航(Tier 0),Hook 负责约束注入(Tier 1)。
|
|
13
|
-
|
|
14
|
-
正常情况下**无需手动调用**此命令。
|
|
15
|
-
|
|
16
|
-
## 何时使用
|
|
17
|
-
|
|
18
|
-
- 需要强制刷新已注入的规范(如 spec 文件刚被修改)
|
|
19
|
-
- 想要预览某个领域的完整规范内容
|
|
20
|
-
- 自动注入未生效时的排查手段
|
|
21
|
-
- Hook 标签匹配遗漏了某些 spec,需要手动补充
|
|
22
|
-
|
|
23
|
-
## 输入
|
|
24
|
-
|
|
25
|
-
- `/project:cf-inject frontend` — 强制加载前端全部 specs
|
|
26
|
-
- `/project:cf-inject backend` — 强制加载后端全部 specs
|
|
27
|
-
|
|
28
|
-
## 执行步骤
|
|
29
|
-
|
|
30
|
-
1. 用 Read 读取 `.code-flow/config.yml`,获取指定领域的 `specs` 列表
|
|
31
|
-
2. 提取所有 spec 的 `path` 字段(兼容新旧格式)
|
|
32
|
-
3. 按 tier 排序:tier 0(`_map.md`)在前,tier 1 按列表顺序
|
|
33
|
-
4. 用 Read 逐个读取 `.code-flow/specs/` 下的匹配文件
|
|
34
|
-
5. 将规范内容直接输出到对话中,格式:
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
## Active Specs (manual inject)
|
|
38
|
-
|
|
39
|
-
### Navigation (Retrieval Map)
|
|
40
|
-
|
|
41
|
-
#### [domain]/_map.md
|
|
42
|
-
[导航地图内容]
|
|
43
|
-
|
|
44
|
-
### Constraints (all specs for domain)
|
|
45
|
-
|
|
46
|
-
#### [spec-path]
|
|
47
|
-
[spec 内容]
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
以上规范是本次开发的约束条件,生成代码必须遵循。
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
6. 用 Write 更新 `.code-flow/.inject-state`,将该域所有 spec 路径标记为已注入,防止 Hook 重复注入
|
|
54
|
-
|
|
55
|
-
> 注:手动 inject 会加载该域的**全部** spec,不做标签过滤。这是与 Hook 自动注入的区别——Hook 按标签精准匹配,手动注入用于全量预览或强制刷新。
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cf-inject
|
|
3
|
-
description: Manually force-load all specs for a domain. Use when auto-injection hasn't triggered, you want to preview a domain's complete spec content, spec files were recently edited and need refresh, or hook tag matching missed some specs.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## 自动注入机制
|
|
7
|
-
|
|
8
|
-
本项目的规范注入是**全自动**的,通过两层机制保障:
|
|
9
|
-
|
|
10
|
-
1. **AGENTS.md 指令**:根据问题自动判断领域,读取 `_map.md` 导航地图理解项目结构
|
|
11
|
-
2. **UserPromptSubmit Hook**:每轮对话开始时,根据 prompt 中引用的文件路径标签自动注入匹配的约束 specs
|
|
12
|
-
|
|
13
|
-
两层职责分离:AGENTS.md 负责导航(Tier 0),Hook 负责约束注入(Tier 1)。
|
|
14
|
-
|
|
15
|
-
正常情况下**无需手动调用**此命令。
|
|
16
|
-
|
|
17
|
-
## 何时使用
|
|
18
|
-
|
|
19
|
-
- 需要强制刷新已注入的规范(如 spec 文件刚被修改)
|
|
20
|
-
- 想要预览某个领域的完整规范内容
|
|
21
|
-
- 自动注入未生效时的排查手段(例如 prompt 中未明确引用文件路径)
|
|
22
|
-
- Hook 标签匹配遗漏了某些 spec,需要手动补充
|
|
23
|
-
|
|
24
|
-
## 输入
|
|
25
|
-
|
|
26
|
-
- `cf-inject frontend` — 强制加载前端全部 specs
|
|
27
|
-
- `cf-inject backend` — 强制加载后端全部 specs
|
|
28
|
-
|
|
29
|
-
## 执行步骤
|
|
30
|
-
|
|
31
|
-
1. 读取 `.code-flow/config.yml`,获取指定领域的 `specs` 列表
|
|
32
|
-
2. 提取所有 spec 的 `path` 字段(兼容新旧格式)
|
|
33
|
-
3. 按 tier 排序:tier 0(`_map.md`)在前,tier 1 按列表顺序
|
|
34
|
-
4. 逐个读取 `.code-flow/specs/` 下的匹配文件
|
|
35
|
-
5. 将规范内容直接输出到对话中,格式:
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
## Active Specs (manual inject)
|
|
39
|
-
|
|
40
|
-
### Navigation (Retrieval Map)
|
|
41
|
-
|
|
42
|
-
#### [domain]/_map.md
|
|
43
|
-
[导航地图内容]
|
|
44
|
-
|
|
45
|
-
### Constraints (all specs for domain)
|
|
46
|
-
|
|
47
|
-
#### [spec-path]
|
|
48
|
-
[spec 内容]
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
以上规范是本次开发的约束条件,生成代码必须遵循。
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
6. 用 apply_patch 更新 `.code-flow/.inject-state`,将该域所有 spec 路径标记为已注入,防止 Hook 重复注入
|
|
55
|
-
|
|
56
|
-
> 注:手动 inject 会加载该域的**全部** spec,不做标签过滤。这是与 Hook 自动注入的区别——UserPromptSubmit Hook 按 prompt 中的文件路径推断标签精准匹配,手动注入用于全量预览或强制刷新。
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# cf-inject
|
|
2
|
-
|
|
3
|
-
强制重新注入指定领域的编码规范(通常不需要手动调用)。
|
|
4
|
-
|
|
5
|
-
## 自动注入机制
|
|
6
|
-
|
|
7
|
-
本项目的规范注入是**全自动**的,通过两层机制保障:
|
|
8
|
-
|
|
9
|
-
1. **CLAUDE.md 指令**:根据问题自动判断领域,读取 `_map.md` 导航地图理解项目结构
|
|
10
|
-
2. **PreToolUse Hook**:编辑代码文件时根据文件路径标签自动注入匹配的约束 specs
|
|
11
|
-
|
|
12
|
-
两层职责分离:CLAUDE.md 负责导航(Tier 0),Hook 负责约束注入(Tier 1)。
|
|
13
|
-
|
|
14
|
-
正常情况下**无需手动调用**此命令。
|
|
15
|
-
|
|
16
|
-
## 何时使用
|
|
17
|
-
|
|
18
|
-
- 需要强制刷新已注入的规范(如 spec 文件刚被修改)
|
|
19
|
-
- 想要预览某个领域的完整规范内容
|
|
20
|
-
- 自动注入未生效时的排查手段
|
|
21
|
-
- Hook 标签匹配遗漏了某些 spec,需要手动补充
|
|
22
|
-
|
|
23
|
-
## 输入
|
|
24
|
-
|
|
25
|
-
- `/project:cf-inject frontend` — 强制加载前端全部 specs
|
|
26
|
-
- `/project:cf-inject backend` — 强制加载后端全部 specs
|
|
27
|
-
|
|
28
|
-
## 执行步骤
|
|
29
|
-
|
|
30
|
-
1. 用 Read 读取 `.code-flow/config.yml`,获取指定领域的 `specs` 列表
|
|
31
|
-
2. 提取所有 spec 的 `path` 字段(兼容新旧格式)
|
|
32
|
-
3. 按 tier 排序:tier 0(`_map.md`)在前,tier 1 按列表顺序
|
|
33
|
-
4. 用 Read 逐个读取 `.code-flow/specs/` 下的匹配文件
|
|
34
|
-
5. 将规范内容直接输出到对话中,格式:
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
## Active Specs (manual inject)
|
|
38
|
-
|
|
39
|
-
### Navigation (Retrieval Map)
|
|
40
|
-
|
|
41
|
-
#### [domain]/_map.md
|
|
42
|
-
[导航地图内容]
|
|
43
|
-
|
|
44
|
-
### Constraints (all specs for domain)
|
|
45
|
-
|
|
46
|
-
#### [spec-path]
|
|
47
|
-
[spec 内容]
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
以上规范是本次开发的约束条件,生成代码必须遵循。
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
6. 用 Write 更新 `.code-flow/.inject-state`,将该域所有 spec 路径标记为已注入,防止 Hook 重复注入
|
|
54
|
-
|
|
55
|
-
> 注:手动 inject 会加载该域的**全部** spec,不做标签过滤。这是与 Hook 自动注入的区别——Hook 按标签精准匹配,手动注入用于全量预览或强制刷新。
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: 强制重新注入指定领域的编码规范
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# cf-inject
|
|
6
|
-
|
|
7
|
-
强制重新注入指定领域的编码规范(通常不需要手动调用)。
|
|
8
|
-
|
|
9
|
-
## 自动注入机制
|
|
10
|
-
|
|
11
|
-
本项目的规范注入是**全自动**的,通过两层机制保障:
|
|
12
|
-
|
|
13
|
-
1. **AGENTS.md 指令**:AI 根据问题自动判断领域,读取 `_map.md` 导航地图理解项目结构
|
|
14
|
-
2. **code-flow 插件**:在你发送消息时根据引用的文件自动注入匹配的约束 specs
|
|
15
|
-
|
|
16
|
-
两层职责分离:AGENTS.md 负责导航(Tier 0),插件负责约束注入(Tier 1)。
|
|
17
|
-
|
|
18
|
-
正常情况下**无需手动调用**此命令。
|
|
19
|
-
|
|
20
|
-
## 何时使用
|
|
21
|
-
|
|
22
|
-
- 需要强制刷新已注入的规范(如 spec 文件刚被修改)
|
|
23
|
-
- 想要预览某个领域的完整规范内容
|
|
24
|
-
- 自动注入未生效时的排查手段
|
|
25
|
-
- 插件标签匹配遗漏了某些 spec,需要手动补充
|
|
26
|
-
|
|
27
|
-
## 输入
|
|
28
|
-
|
|
29
|
-
- `/project:cf-inject frontend` — 强制加载前端全部 specs
|
|
30
|
-
- `/project:cf-inject backend` — 强制加载后端全部 specs
|
|
31
|
-
|
|
32
|
-
## 执行步骤
|
|
33
|
-
|
|
34
|
-
1. 读取 `.code-flow/config.yml`,获取指定领域的 `specs` 列表
|
|
35
|
-
2. 提取所有 spec 的 `path` 字段(兼容新旧格式)
|
|
36
|
-
3. 按 tier 排序:tier 0(`_map.md`)在前,tier 1 按列表顺序
|
|
37
|
-
4. 逐个读取 `.code-flow/specs/` 下的匹配文件
|
|
38
|
-
5. 将规范内容直接输出到对话中,格式:
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
## Active Specs (manual inject)
|
|
42
|
-
|
|
43
|
-
### Navigation (Retrieval Map)
|
|
44
|
-
|
|
45
|
-
#### [domain]/_map.md
|
|
46
|
-
[导航地图内容]
|
|
47
|
-
|
|
48
|
-
### Constraints (all specs for domain)
|
|
49
|
-
|
|
50
|
-
#### [spec-path]
|
|
51
|
-
[spec 内容]
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
以上规范是本次开发的约束条件,生成代码必须遵循。
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
6. 更新 `.code-flow/.inject-state`,将该域所有 spec 路径标记为已注入,防止插件重复注入
|
|
58
|
-
|
|
59
|
-
> 注:手动 inject 会加载该域的**全部** spec,不做标签过滤。这是与插件自动注入的区别——插件按标签精准匹配,手动注入用于全量预览或强制刷新。
|