@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.
- package/README.md +75 -110
- package/bin/code-flow.js +12 -0
- package/docs/README.md +19 -0
- package/docs/USAGE.md +268 -0
- package/docs/v1-design.md +1858 -0
- package/docs/v1-implementation.md +186 -0
- package/lib/adapters.js +126 -0
- package/lib/cli.js +258 -0
- package/lib/config.js +85 -0
- package/lib/doctor.js +283 -0
- package/lib/frontmatter.js +42 -0
- package/lib/fs.js +141 -0
- package/lib/glob.js +23 -0
- package/lib/hook.js +99 -0
- package/lib/init.js +132 -0
- package/lib/learn.js +170 -0
- package/lib/locks.js +52 -0
- package/lib/markdown.js +13 -0
- package/lib/process.js +116 -0
- package/lib/render.js +115 -0
- package/lib/repo.js +48 -0
- package/lib/runs.js +155 -0
- package/lib/session.js +40 -0
- package/lib/spec.js +80 -0
- package/lib/task.js +258 -0
- package/lib/taskdoc.js +308 -0
- package/lib/toml.js +140 -0
- package/lib/verify.js +168 -0
- package/lib/worktree.js +930 -0
- package/package.json +21 -9
- package/templates/adapters/opencode.mjs +65 -0
- package/templates/config.json +39 -0
- package/templates/instructions.md +13 -0
- package/templates/launcher/bridge.cjs +295 -0
- package/templates/launcher/hook.cmd +15 -0
- package/templates/launcher/hook.ps1 +10 -0
- package/templates/launcher/hook.sh +20 -0
- package/templates/skills/cf-check.md +19 -0
- package/templates/skills/cf-init.md +16 -0
- package/templates/skills/cf-spec.md +20 -0
- package/templates/skills/cf-task-align.md +19 -0
- package/templates/skills/cf-task-archive.md +20 -0
- package/templates/skills/cf-task-plan.md +31 -0
- package/templates/skills/cf-task-prd.md +14 -0
- package/templates/skills/cf-task-start.md +41 -0
- package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-frontend.md +22 -19
- package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-full.md +36 -28
- package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-lite.md +27 -17
- package/{src/core/code-flow/specs/shared/prd-template.md → templates/workflow/prd.md} +1 -1
- package/templates/workflow/project-guidelines.md +48 -0
- package/templates/workflow/scenarios.md +67 -0
- package/templates/workflow/task.md +56 -0
- package/docs/migrations/spec-workflow-0.6.0.md +0 -42
- package/src/adapters/claude/CLAUDE.md +0 -45
- package/src/adapters/claude/commands/cf-init.md +0 -264
- package/src/adapters/claude/commands/cf-learn.md +0 -291
- package/src/adapters/claude/commands/cf-spec.md +0 -67
- package/src/adapters/claude/commands/cf-stats.md +0 -86
- package/src/adapters/claude/commands/cf-sync.md +0 -25
- package/src/adapters/claude/commands/cf-task/align.md +0 -309
- package/src/adapters/claude/commands/cf-task/archive.md +0 -161
- package/src/adapters/claude/commands/cf-task/block.md +0 -36
- package/src/adapters/claude/commands/cf-task/graph.md +0 -80
- package/src/adapters/claude/commands/cf-task/note.md +0 -236
- package/src/adapters/claude/commands/cf-task/plan.md +0 -363
- package/src/adapters/claude/commands/cf-task/prd.md +0 -162
- package/src/adapters/claude/commands/cf-task/start.md +0 -304
- package/src/adapters/claude/commands/cf-task/status.md +0 -59
- package/src/adapters/claude/commands/cf-task/verify-e2e.md +0 -115
- package/src/adapters/claude/commands/cf-validate.md +0 -75
- package/src/adapters/claude/settings.local.json +0 -50
- package/src/adapters/codex/AGENTS.md +0 -45
- package/src/adapters/codex/config.toml +0 -3
- package/src/adapters/codex/hooks.json +0 -50
- package/src/adapters/codex/skills/cf-init/SKILL.md +0 -273
- package/src/adapters/codex/skills/cf-learn/SKILL.md +0 -296
- package/src/adapters/codex/skills/cf-spec/SKILL.md +0 -60
- package/src/adapters/codex/skills/cf-stats/SKILL.md +0 -87
- package/src/adapters/codex/skills/cf-sync/SKILL.md +0 -30
- package/src/adapters/codex/skills/cf-task-align/SKILL.md +0 -310
- package/src/adapters/codex/skills/cf-task-archive/SKILL.md +0 -162
- package/src/adapters/codex/skills/cf-task-block/SKILL.md +0 -37
- package/src/adapters/codex/skills/cf-task-graph/SKILL.md +0 -81
- package/src/adapters/codex/skills/cf-task-note/SKILL.md +0 -233
- package/src/adapters/codex/skills/cf-task-plan/SKILL.md +0 -324
- package/src/adapters/codex/skills/cf-task-prd/SKILL.md +0 -163
- package/src/adapters/codex/skills/cf-task-start/SKILL.md +0 -305
- package/src/adapters/codex/skills/cf-task-status/SKILL.md +0 -60
- package/src/adapters/codex/skills/cf-task-verify-e2e/SKILL.md +0 -111
- package/src/adapters/codex/skills/cf-validate/SKILL.md +0 -76
- package/src/adapters/costrict/CLAUDE.md +0 -45
- package/src/adapters/costrict/commands/cf-init.md +0 -264
- package/src/adapters/costrict/commands/cf-learn.md +0 -291
- package/src/adapters/costrict/commands/cf-spec.md +0 -67
- package/src/adapters/costrict/commands/cf-stats.md +0 -86
- package/src/adapters/costrict/commands/cf-sync.md +0 -25
- package/src/adapters/costrict/commands/cf-task/align.md +0 -309
- package/src/adapters/costrict/commands/cf-task/archive.md +0 -161
- package/src/adapters/costrict/commands/cf-task/block.md +0 -36
- package/src/adapters/costrict/commands/cf-task/graph.md +0 -80
- package/src/adapters/costrict/commands/cf-task/note.md +0 -236
- package/src/adapters/costrict/commands/cf-task/plan.md +0 -363
- package/src/adapters/costrict/commands/cf-task/prd.md +0 -162
- package/src/adapters/costrict/commands/cf-task/start.md +0 -304
- package/src/adapters/costrict/commands/cf-task/status.md +0 -59
- package/src/adapters/costrict/commands/cf-task/verify-e2e.md +0 -115
- package/src/adapters/costrict/commands/cf-validate.md +0 -75
- package/src/adapters/costrict/settings.local.json +0 -50
- package/src/adapters/opencode/AGENTS.md +0 -45
- package/src/adapters/opencode/commands/cf-init.md +0 -281
- package/src/adapters/opencode/commands/cf-learn.md +0 -295
- package/src/adapters/opencode/commands/cf-spec.md +0 -59
- package/src/adapters/opencode/commands/cf-stats.md +0 -90
- package/src/adapters/opencode/commands/cf-sync.md +0 -29
- package/src/adapters/opencode/commands/cf-task/align.md +0 -314
- package/src/adapters/opencode/commands/cf-task/archive.md +0 -166
- package/src/adapters/opencode/commands/cf-task/block.md +0 -40
- package/src/adapters/opencode/commands/cf-task/graph.md +0 -84
- package/src/adapters/opencode/commands/cf-task/note.md +0 -240
- package/src/adapters/opencode/commands/cf-task/plan.md +0 -363
- package/src/adapters/opencode/commands/cf-task/prd.md +0 -167
- package/src/adapters/opencode/commands/cf-task/start.md +0 -309
- package/src/adapters/opencode/commands/cf-task/status.md +0 -63
- package/src/adapters/opencode/commands/cf-task/verify-e2e.md +0 -115
- package/src/adapters/opencode/commands/cf-validate.md +0 -79
- package/src/adapters/opencode/opencode.json +0 -3
- package/src/adapters/opencode/plugins/code-flow/index.js +0 -213
- package/src/adapters/opencode/plugins/code-flow/package.json +0 -6
- package/src/cli.js +0 -1023
- package/src/core/code-flow/config.yml +0 -158
- package/src/core/code-flow/scripts/cf_acceptance_evidence.py +0 -100
- package/src/core/code-flow/scripts/cf_acceptance_manifest.py +0 -294
- package/src/core/code-flow/scripts/cf_acceptance_runner.py +0 -192
- package/src/core/code-flow/scripts/cf_acceptance_schema.py +0 -83
- package/src/core/code-flow/scripts/cf_checks.py +0 -336
- package/src/core/code-flow/scripts/cf_core.py +0 -787
- package/src/core/code-flow/scripts/cf_exec_base.py +0 -112
- package/src/core/code-flow/scripts/cf_feedback.py +0 -56
- package/src/core/code-flow/scripts/cf_log.py +0 -154
- package/src/core/code-flow/scripts/cf_post_hook.py +0 -196
- package/src/core/code-flow/scripts/cf_pre_tool_hook.py +0 -104
- package/src/core/code-flow/scripts/cf_scan.py +0 -295
- package/src/core/code-flow/scripts/cf_session_state.py +0 -37
- package/src/core/code-flow/scripts/cf_spec_context.py +0 -1746
- package/src/core/code-flow/scripts/cf_spec_gate.py +0 -264
- package/src/core/code-flow/scripts/cf_spec_metadata.py +0 -384
- package/src/core/code-flow/scripts/cf_spec_migrate.py +0 -512
- package/src/core/code-flow/scripts/cf_spec_resolver.py +0 -347
- package/src/core/code-flow/scripts/cf_spec_router.py +0 -244
- package/src/core/code-flow/scripts/cf_spec_session.py +0 -119
- package/src/core/code-flow/scripts/cf_spec_verify.py +0 -559
- package/src/core/code-flow/scripts/cf_stats.py +0 -506
- package/src/core/code-flow/scripts/cf_stop_hook.py +0 -381
- package/src/core/code-flow/scripts/cf_sync.py +0 -140
- package/src/core/code-flow/scripts/cf_task_index.py +0 -148
- package/src/core/code-flow/scripts/cf_task_parallel.py +0 -394
- package/src/core/code-flow/scripts/cf_task_runtime.py +0 -304
- package/src/core/code-flow/scripts/cf_task_state.py +0 -3
- package/src/core/code-flow/scripts/cf_task_workflow.py +0 -205
- package/src/core/code-flow/scripts/cf_user_prompt_hook.py +0 -178
- package/src/core/code-flow/scripts/cf_validation.py +0 -81
- package/src/core/code-flow/scripts/cf_workflow_service.py +0 -366
- package/src/core/code-flow/scripts/cf_workflow_transaction.py +0 -96
- package/src/core/code-flow/specs/backend/_map.md +0 -56
- package/src/core/code-flow/specs/backend/code-quality-performance.md +0 -59
- package/src/core/code-flow/specs/backend/database.md +0 -51
- package/src/core/code-flow/specs/backend/directory-structure.md +0 -54
- package/src/core/code-flow/specs/backend/logging.md +0 -50
- package/src/core/code-flow/specs/backend/platform-rules.md +0 -55
- package/src/core/code-flow/specs/frontend/_map.md +0 -52
- package/src/core/code-flow/specs/frontend/component-specs.md +0 -68
- package/src/core/code-flow/specs/frontend/directory-structure.md +0 -53
- package/src/core/code-flow/specs/frontend/quality-standards.md +0 -58
- package/src/core/code-flow/specs/shared/_map.md +0 -57
- package/src/core/code-flow/tasks/.gitkeep +0 -0
- package/src/core/code-flow/validation.yml +0 -53
- 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 };
|