@michaelxwb/code-flow 0.6.5 → 1.0.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.
Files changed (177) hide show
  1. package/README.md +75 -110
  2. package/bin/code-flow.js +12 -0
  3. package/docs/README.md +19 -0
  4. package/docs/USAGE.md +268 -0
  5. package/docs/v1-design.md +1858 -0
  6. package/docs/v1-implementation.md +186 -0
  7. package/lib/adapters.js +126 -0
  8. package/lib/cli.js +258 -0
  9. package/lib/config.js +85 -0
  10. package/lib/doctor.js +283 -0
  11. package/lib/frontmatter.js +42 -0
  12. package/lib/fs.js +141 -0
  13. package/lib/glob.js +23 -0
  14. package/lib/hook.js +99 -0
  15. package/lib/init.js +132 -0
  16. package/lib/learn.js +170 -0
  17. package/lib/locks.js +52 -0
  18. package/lib/markdown.js +13 -0
  19. package/lib/process.js +116 -0
  20. package/lib/render.js +115 -0
  21. package/lib/repo.js +48 -0
  22. package/lib/runs.js +155 -0
  23. package/lib/session.js +40 -0
  24. package/lib/spec.js +80 -0
  25. package/lib/task.js +258 -0
  26. package/lib/taskdoc.js +308 -0
  27. package/lib/toml.js +140 -0
  28. package/lib/verify.js +168 -0
  29. package/lib/worktree.js +930 -0
  30. package/package.json +21 -9
  31. package/templates/adapters/opencode.mjs +65 -0
  32. package/templates/config.json +39 -0
  33. package/templates/instructions.md +13 -0
  34. package/templates/launcher/bridge.cjs +295 -0
  35. package/templates/launcher/hook.cmd +15 -0
  36. package/templates/launcher/hook.ps1 +10 -0
  37. package/templates/launcher/hook.sh +20 -0
  38. package/templates/skills/cf-check.md +19 -0
  39. package/templates/skills/cf-init.md +16 -0
  40. package/templates/skills/cf-spec.md +20 -0
  41. package/templates/skills/cf-task-align.md +19 -0
  42. package/templates/skills/cf-task-archive.md +20 -0
  43. package/templates/skills/cf-task-plan.md +31 -0
  44. package/templates/skills/cf-task-prd.md +14 -0
  45. package/templates/skills/cf-task-start.md +41 -0
  46. package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-frontend.md +22 -19
  47. package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-full.md +36 -28
  48. package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-lite.md +27 -17
  49. package/{src/core/code-flow/specs/shared/prd-template.md → templates/workflow/prd.md} +1 -1
  50. package/templates/workflow/project-guidelines.md +48 -0
  51. package/templates/workflow/scenarios.md +67 -0
  52. package/templates/workflow/task.md +56 -0
  53. package/docs/migrations/spec-workflow-0.6.0.md +0 -42
  54. package/src/adapters/claude/CLAUDE.md +0 -45
  55. package/src/adapters/claude/commands/cf-init.md +0 -264
  56. package/src/adapters/claude/commands/cf-learn.md +0 -291
  57. package/src/adapters/claude/commands/cf-spec.md +0 -67
  58. package/src/adapters/claude/commands/cf-stats.md +0 -86
  59. package/src/adapters/claude/commands/cf-sync.md +0 -25
  60. package/src/adapters/claude/commands/cf-task/align.md +0 -309
  61. package/src/adapters/claude/commands/cf-task/archive.md +0 -161
  62. package/src/adapters/claude/commands/cf-task/block.md +0 -36
  63. package/src/adapters/claude/commands/cf-task/graph.md +0 -80
  64. package/src/adapters/claude/commands/cf-task/note.md +0 -236
  65. package/src/adapters/claude/commands/cf-task/plan.md +0 -363
  66. package/src/adapters/claude/commands/cf-task/prd.md +0 -162
  67. package/src/adapters/claude/commands/cf-task/start.md +0 -304
  68. package/src/adapters/claude/commands/cf-task/status.md +0 -59
  69. package/src/adapters/claude/commands/cf-task/verify-e2e.md +0 -115
  70. package/src/adapters/claude/commands/cf-validate.md +0 -75
  71. package/src/adapters/claude/settings.local.json +0 -50
  72. package/src/adapters/codex/AGENTS.md +0 -45
  73. package/src/adapters/codex/config.toml +0 -3
  74. package/src/adapters/codex/hooks.json +0 -50
  75. package/src/adapters/codex/skills/cf-init/SKILL.md +0 -273
  76. package/src/adapters/codex/skills/cf-learn/SKILL.md +0 -296
  77. package/src/adapters/codex/skills/cf-spec/SKILL.md +0 -60
  78. package/src/adapters/codex/skills/cf-stats/SKILL.md +0 -87
  79. package/src/adapters/codex/skills/cf-sync/SKILL.md +0 -30
  80. package/src/adapters/codex/skills/cf-task-align/SKILL.md +0 -310
  81. package/src/adapters/codex/skills/cf-task-archive/SKILL.md +0 -162
  82. package/src/adapters/codex/skills/cf-task-block/SKILL.md +0 -37
  83. package/src/adapters/codex/skills/cf-task-graph/SKILL.md +0 -81
  84. package/src/adapters/codex/skills/cf-task-note/SKILL.md +0 -233
  85. package/src/adapters/codex/skills/cf-task-plan/SKILL.md +0 -324
  86. package/src/adapters/codex/skills/cf-task-prd/SKILL.md +0 -163
  87. package/src/adapters/codex/skills/cf-task-start/SKILL.md +0 -305
  88. package/src/adapters/codex/skills/cf-task-status/SKILL.md +0 -60
  89. package/src/adapters/codex/skills/cf-task-verify-e2e/SKILL.md +0 -111
  90. package/src/adapters/codex/skills/cf-validate/SKILL.md +0 -76
  91. package/src/adapters/costrict/CLAUDE.md +0 -45
  92. package/src/adapters/costrict/commands/cf-init.md +0 -264
  93. package/src/adapters/costrict/commands/cf-learn.md +0 -291
  94. package/src/adapters/costrict/commands/cf-spec.md +0 -67
  95. package/src/adapters/costrict/commands/cf-stats.md +0 -86
  96. package/src/adapters/costrict/commands/cf-sync.md +0 -25
  97. package/src/adapters/costrict/commands/cf-task/align.md +0 -309
  98. package/src/adapters/costrict/commands/cf-task/archive.md +0 -161
  99. package/src/adapters/costrict/commands/cf-task/block.md +0 -36
  100. package/src/adapters/costrict/commands/cf-task/graph.md +0 -80
  101. package/src/adapters/costrict/commands/cf-task/note.md +0 -236
  102. package/src/adapters/costrict/commands/cf-task/plan.md +0 -363
  103. package/src/adapters/costrict/commands/cf-task/prd.md +0 -162
  104. package/src/adapters/costrict/commands/cf-task/start.md +0 -304
  105. package/src/adapters/costrict/commands/cf-task/status.md +0 -59
  106. package/src/adapters/costrict/commands/cf-task/verify-e2e.md +0 -115
  107. package/src/adapters/costrict/commands/cf-validate.md +0 -75
  108. package/src/adapters/costrict/settings.local.json +0 -50
  109. package/src/adapters/opencode/AGENTS.md +0 -45
  110. package/src/adapters/opencode/commands/cf-init.md +0 -281
  111. package/src/adapters/opencode/commands/cf-learn.md +0 -295
  112. package/src/adapters/opencode/commands/cf-spec.md +0 -59
  113. package/src/adapters/opencode/commands/cf-stats.md +0 -90
  114. package/src/adapters/opencode/commands/cf-sync.md +0 -29
  115. package/src/adapters/opencode/commands/cf-task/align.md +0 -314
  116. package/src/adapters/opencode/commands/cf-task/archive.md +0 -166
  117. package/src/adapters/opencode/commands/cf-task/block.md +0 -40
  118. package/src/adapters/opencode/commands/cf-task/graph.md +0 -84
  119. package/src/adapters/opencode/commands/cf-task/note.md +0 -240
  120. package/src/adapters/opencode/commands/cf-task/plan.md +0 -363
  121. package/src/adapters/opencode/commands/cf-task/prd.md +0 -167
  122. package/src/adapters/opencode/commands/cf-task/start.md +0 -309
  123. package/src/adapters/opencode/commands/cf-task/status.md +0 -63
  124. package/src/adapters/opencode/commands/cf-task/verify-e2e.md +0 -115
  125. package/src/adapters/opencode/commands/cf-validate.md +0 -79
  126. package/src/adapters/opencode/opencode.json +0 -3
  127. package/src/adapters/opencode/plugins/code-flow/index.js +0 -213
  128. package/src/adapters/opencode/plugins/code-flow/package.json +0 -6
  129. package/src/cli.js +0 -1023
  130. package/src/core/code-flow/config.yml +0 -158
  131. package/src/core/code-flow/scripts/cf_acceptance_evidence.py +0 -100
  132. package/src/core/code-flow/scripts/cf_acceptance_manifest.py +0 -294
  133. package/src/core/code-flow/scripts/cf_acceptance_runner.py +0 -192
  134. package/src/core/code-flow/scripts/cf_acceptance_schema.py +0 -83
  135. package/src/core/code-flow/scripts/cf_checks.py +0 -336
  136. package/src/core/code-flow/scripts/cf_core.py +0 -787
  137. package/src/core/code-flow/scripts/cf_exec_base.py +0 -112
  138. package/src/core/code-flow/scripts/cf_feedback.py +0 -56
  139. package/src/core/code-flow/scripts/cf_log.py +0 -154
  140. package/src/core/code-flow/scripts/cf_post_hook.py +0 -196
  141. package/src/core/code-flow/scripts/cf_pre_tool_hook.py +0 -104
  142. package/src/core/code-flow/scripts/cf_scan.py +0 -295
  143. package/src/core/code-flow/scripts/cf_session_state.py +0 -37
  144. package/src/core/code-flow/scripts/cf_spec_context.py +0 -1746
  145. package/src/core/code-flow/scripts/cf_spec_gate.py +0 -264
  146. package/src/core/code-flow/scripts/cf_spec_metadata.py +0 -384
  147. package/src/core/code-flow/scripts/cf_spec_migrate.py +0 -512
  148. package/src/core/code-flow/scripts/cf_spec_resolver.py +0 -347
  149. package/src/core/code-flow/scripts/cf_spec_router.py +0 -244
  150. package/src/core/code-flow/scripts/cf_spec_session.py +0 -119
  151. package/src/core/code-flow/scripts/cf_spec_verify.py +0 -559
  152. package/src/core/code-flow/scripts/cf_stats.py +0 -506
  153. package/src/core/code-flow/scripts/cf_stop_hook.py +0 -381
  154. package/src/core/code-flow/scripts/cf_sync.py +0 -140
  155. package/src/core/code-flow/scripts/cf_task_index.py +0 -148
  156. package/src/core/code-flow/scripts/cf_task_parallel.py +0 -394
  157. package/src/core/code-flow/scripts/cf_task_runtime.py +0 -304
  158. package/src/core/code-flow/scripts/cf_task_state.py +0 -3
  159. package/src/core/code-flow/scripts/cf_task_workflow.py +0 -205
  160. package/src/core/code-flow/scripts/cf_user_prompt_hook.py +0 -178
  161. package/src/core/code-flow/scripts/cf_validation.py +0 -81
  162. package/src/core/code-flow/scripts/cf_workflow_service.py +0 -366
  163. package/src/core/code-flow/scripts/cf_workflow_transaction.py +0 -96
  164. package/src/core/code-flow/specs/backend/_map.md +0 -56
  165. package/src/core/code-flow/specs/backend/code-quality-performance.md +0 -59
  166. package/src/core/code-flow/specs/backend/database.md +0 -51
  167. package/src/core/code-flow/specs/backend/directory-structure.md +0 -54
  168. package/src/core/code-flow/specs/backend/logging.md +0 -50
  169. package/src/core/code-flow/specs/backend/platform-rules.md +0 -55
  170. package/src/core/code-flow/specs/frontend/_map.md +0 -52
  171. package/src/core/code-flow/specs/frontend/component-specs.md +0 -68
  172. package/src/core/code-flow/specs/frontend/directory-structure.md +0 -53
  173. package/src/core/code-flow/specs/frontend/quality-standards.md +0 -58
  174. package/src/core/code-flow/specs/shared/_map.md +0 -57
  175. package/src/core/code-flow/tasks/.gitkeep +0 -0
  176. package/src/core/code-flow/validation.yml +0 -53
  177. package/src/migrate/spec-workflow.js +0 -238
@@ -1,57 +0,0 @@
1
- # Shared Specs Navigation Map
2
-
3
- > 跨项目的共享模板和规范
4
-
5
- ## Purpose
6
-
7
- 共享模板供 `cf-task:align` 和 `cf-task:prd` 命令使用,为文档生成提供规范约束。
8
-
9
- ## Templates
10
-
11
- ### PRD Templates
12
-
13
- | 文件 | 用途 | 适用场景 |
14
- |------|------|---------|
15
- | `prd-template.md` | 产品需求文档 | 需求早期阶段,在设计之前 |
16
-
17
- ### Design Templates
18
-
19
- | 文件 | 用途 | 适用场景 |
20
- |------|------|---------|
21
- | `design/design-lite.md` | 轻量设计简报 | 功能开发/CLI/Bug修复/小型重构 |
22
- | `design/design-full.md` | 完整设计文档 | 跨系统集成/性能优化/架构演进/中大型功能 |
23
-
24
- > 两档模板均覆盖:接口设计(API/CLI/函数三形态)、性能与容量设计、可执行验收(场景即 TC)。design-full 额外含方案选型 ADR 决策记录与 §6 需求追溯矩阵。
25
-
26
- ## Workflow
27
-
28
- ```
29
- 需求 → cf-task:prd → PRD (.prd.md)
30
- ↓ (align 读取 .prd.md 派生)
31
- → cf-task:align → 设计 (.design.md)
32
- ↓ (plan 读取 .design.md 拆解)
33
- → cf-task:plan → 任务
34
- ```
35
-
36
- ## Selection Guide
37
-
38
- ```
39
- 需求阶段(还未明确用户与场景):
40
- → cf-task:prd 生成 PRD
41
-
42
- 设计阶段(已有 PRD 或已明确做什么):
43
- → cf-task:align 生成设计简报
44
- - 输入 .prd.md → 派生模式(继承目标/用户/功能/范围)
45
- - 输入文本 → 新建模式(从零对话)
46
-
47
- 复杂度判断:
48
- 简单功能/脚本/Bugfix → design-lite.md
49
- 复杂系统/跨模块/性能优化 → design-full.md
50
- ```
51
-
52
- ## Usage
53
-
54
- 1. `cf-task:prd` 命令引用 `prd-template.md` 生成 PRD
55
- 2. `cf-task:align` 命令引用 `design/design-lite.md` 或 `design/design-full.md` 生成设计文档
56
- 3. PRD 的 US/FEAT ID 被 design 的功能清单"来源"列引用,形成追溯链
57
- 4. 性能敏感需求:align 在出技术方案时即按最优性能设计,落点 design-lite §3.4 / design-full §3.5,并在 §6 矩阵闭合 US→FEAT→API→TC
File without changes
@@ -1,53 +0,0 @@
1
- # validators 在每次 Stop 按“会话内编辑过的文件”匹配执行(quality_loop.stop_check)。
2
- # 全量测试 / e2e 等慢命令标 `heavy: true`:Stop 每轮跳过,任务 finish 自动执行
3
- # 一次,也可显式运行 /cf-validate;避免每个任务都重跑多遍。
4
- validators:
5
- - name: "Python 语法检查"
6
- trigger: "**/*.py"
7
- command: "python3 -m py_compile {files}"
8
- timeout: 30000
9
- on_fail: "检查语法错误"
10
-
11
- - name: "TypeScript 类型检查"
12
- trigger: "**/*.{ts,tsx}"
13
- command: "npx tsc --noEmit"
14
- timeout: 30000
15
- on_fail: "检查类型定义,参见 specs/frontend/quality-standards.md"
16
-
17
- - name: "Vue 类型检查"
18
- trigger: "**/*.vue"
19
- command: "npx vue-tsc --noEmit"
20
- timeout: 30000
21
- on_fail: "检查 Vue 组件类型定义,参见 specs/frontend/component-specs.md"
22
-
23
- - name: "ESLint"
24
- trigger: "**/*.{ts,tsx,js,jsx,vue}"
25
- command: "npx eslint {files}"
26
- timeout: 15000
27
- on_fail: "运行 npx eslint --fix 自动修复,参见 specs/frontend/quality-standards.md"
28
-
29
- - name: "Stylelint"
30
- trigger: "**/*.{css,scss,less}"
31
- command: "npx stylelint {files}"
32
- timeout: 15000
33
- on_fail: "运行 npx stylelint --fix 自动修复,参见 specs/frontend/component-specs.md"
34
-
35
- - name: "前端单元测试"
36
- trigger: "**/*.{ts,tsx,js,jsx,vue}"
37
- command: "npx vitest run --reporter=dot"
38
- timeout: 60000
39
- heavy: true
40
- on_fail: "前端测试失败,检查组件渲染和 mock 是否需要更新"
41
-
42
- - name: "Python 类型检查"
43
- trigger: "**/*.py"
44
- command: "python3 -m mypy {files}"
45
- timeout: 30000
46
- on_fail: "检查类型注解,参见 specs/backend/code-quality-performance.md"
47
-
48
- - name: "Pytest"
49
- trigger: "**/*.py"
50
- command: "python3 -m pytest --tb=short -q"
51
- timeout: 60000
52
- heavy: true
53
- on_fail: "测试失败,检查断言和 mock 是否需要更新"
@@ -1,238 +0,0 @@
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 };