@michaelxwb/code-flow 0.6.1 → 0.6.3
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/package.json +1 -1
- package/src/adapters/claude/commands/cf-task/archive.md +5 -5
- package/src/adapters/claude/commands/cf-task/block.md +15 -9
- package/src/adapters/claude/commands/cf-task/graph.md +8 -1
- package/src/adapters/claude/commands/cf-task/note.md +1 -4
- package/src/adapters/claude/commands/cf-task/plan.md +40 -0
- package/src/adapters/claude/commands/cf-task/start.md +22 -22
- package/src/adapters/claude/commands/cf-task/verify-e2e.md +110 -0
- package/src/adapters/claude/commands/cf-validate.md +12 -13
- package/src/adapters/codex/skills/cf-task-archive/SKILL.md +5 -5
- package/src/adapters/codex/skills/cf-task-block/SKILL.md +15 -9
- package/src/adapters/codex/skills/cf-task-graph/SKILL.md +8 -1
- package/src/adapters/codex/skills/cf-task-note/SKILL.md +1 -4
- package/src/adapters/codex/skills/cf-task-start/SKILL.md +22 -22
- package/src/adapters/codex/skills/cf-task-verify-e2e/SKILL.md +106 -0
- package/src/adapters/codex/skills/cf-validate/SKILL.md +12 -13
- package/src/adapters/costrict/commands/cf-task/archive.md +5 -5
- package/src/adapters/costrict/commands/cf-task/block.md +15 -9
- package/src/adapters/costrict/commands/cf-task/graph.md +8 -1
- package/src/adapters/costrict/commands/cf-task/note.md +1 -4
- package/src/adapters/costrict/commands/cf-task/plan.md +40 -0
- package/src/adapters/costrict/commands/cf-task/start.md +22 -22
- package/src/adapters/costrict/commands/cf-task/verify-e2e.md +110 -0
- package/src/adapters/costrict/commands/cf-validate.md +12 -13
- package/src/adapters/opencode/commands/cf-task/archive.md +5 -5
- package/src/adapters/opencode/commands/cf-task/block.md +15 -9
- package/src/adapters/opencode/commands/cf-task/graph.md +8 -1
- package/src/adapters/opencode/commands/cf-task/note.md +1 -4
- package/src/adapters/opencode/commands/cf-task/plan.md +40 -5
- package/src/adapters/opencode/commands/cf-task/start.md +22 -22
- package/src/adapters/opencode/commands/cf-task/verify-e2e.md +110 -0
- package/src/adapters/opencode/commands/cf-validate.md +12 -13
- package/src/adapters/opencode/plugins/code-flow/index.js +50 -28
- package/src/cli.js +177 -63
- package/src/core/code-flow/scripts/cf_acceptance_evidence.py +100 -0
- package/src/core/code-flow/scripts/cf_acceptance_manifest.py +248 -0
- package/src/core/code-flow/scripts/cf_acceptance_runner.py +192 -0
- package/src/core/code-flow/scripts/cf_acceptance_schema.py +83 -0
- package/src/core/code-flow/scripts/cf_core.py +26 -0
- package/src/core/code-flow/scripts/cf_exec_base.py +112 -0
- package/src/core/code-flow/scripts/cf_post_hook.py +2 -0
- package/src/core/code-flow/scripts/cf_pre_tool_hook.py +13 -8
- package/src/core/code-flow/scripts/cf_scan.py +8 -6
- package/src/core/code-flow/scripts/cf_spec_context.py +267 -28
- package/src/core/code-flow/scripts/cf_spec_gate.py +10 -0
- package/src/core/code-flow/scripts/cf_spec_metadata.py +60 -11
- package/src/core/code-flow/scripts/cf_spec_resolver.py +85 -9
- package/src/core/code-flow/scripts/cf_spec_router.py +104 -9
- package/src/core/code-flow/scripts/cf_spec_session.py +3 -2
- package/src/core/code-flow/scripts/cf_spec_verify.py +159 -38
- package/src/core/code-flow/scripts/cf_stats.py +41 -9
- package/src/core/code-flow/scripts/cf_stop_hook.py +100 -59
- package/src/core/code-flow/scripts/cf_task_index.py +147 -0
- package/src/core/code-flow/scripts/cf_task_runtime.py +84 -3
- package/src/core/code-flow/scripts/cf_task_state.py +3 -0
- package/src/core/code-flow/scripts/cf_task_workflow.py +107 -0
- package/src/core/code-flow/scripts/cf_user_prompt_hook.py +11 -5
- package/src/core/code-flow/scripts/cf_validation.py +81 -0
- package/src/core/code-flow/scripts/cf_workflow_service.py +366 -0
- package/src/core/code-flow/scripts/cf_workflow_transaction.py +96 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Recoverable two-file workflow commits under the active-task lock."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from cf_spec_context import _acquire_active_lock, _atomic_text, _release_active_lock
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _read(path: Path) -> Optional[str]:
|
|
12
|
+
try:
|
|
13
|
+
return path.read_text(encoding="utf-8")
|
|
14
|
+
except FileNotFoundError:
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _write(path: Path, value: Optional[str]) -> None:
|
|
19
|
+
if _read(path) == value:
|
|
20
|
+
return
|
|
21
|
+
if value is None:
|
|
22
|
+
path.unlink()
|
|
23
|
+
else:
|
|
24
|
+
_atomic_text(path, value)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _journal_path(root: str) -> Path:
|
|
28
|
+
return Path(root) / ".code-flow/.workflow-transition.json"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _records(root: str, data: object) -> list[dict[str, object]]:
|
|
32
|
+
if not isinstance(data, dict) or data.get("version") != 1:
|
|
33
|
+
raise ValueError("workflow recovery: invalid journal")
|
|
34
|
+
rows = data.get("files")
|
|
35
|
+
if not isinstance(rows, list) or len(rows) != 2:
|
|
36
|
+
raise ValueError("workflow recovery: invalid files")
|
|
37
|
+
base = Path(root).resolve()
|
|
38
|
+
marker = base / ".code-flow/.active-task.json"
|
|
39
|
+
for row in rows:
|
|
40
|
+
if not isinstance(row, dict) or not isinstance(row.get("path"), str):
|
|
41
|
+
raise ValueError("workflow recovery: invalid record")
|
|
42
|
+
path = Path(row["path"]).resolve()
|
|
43
|
+
if base not in path.parents or (path != marker and path.suffix != ".md"):
|
|
44
|
+
raise ValueError("workflow recovery: out-of-scope path")
|
|
45
|
+
for field in ("before", "after"):
|
|
46
|
+
if field not in row or (row[field] is not None and not isinstance(row[field], str)):
|
|
47
|
+
raise ValueError("workflow recovery: invalid contents")
|
|
48
|
+
if _read(path) not in (row["before"], row["after"]):
|
|
49
|
+
raise ValueError(f"workflow recovery: concurrent change to {path}")
|
|
50
|
+
if Path(rows[1]["path"]).resolve() != marker or rows[0]["path"] == rows[1]["path"]:
|
|
51
|
+
raise ValueError("workflow recovery: marker must be last")
|
|
52
|
+
return rows
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def recover_transition(root: str) -> bool:
|
|
56
|
+
journal = _journal_path(root)
|
|
57
|
+
if not journal.exists():
|
|
58
|
+
return False
|
|
59
|
+
lock = _acquire_active_lock(root)
|
|
60
|
+
try:
|
|
61
|
+
if not journal.exists():
|
|
62
|
+
return False
|
|
63
|
+
rows = _records(root, json.loads(journal.read_text(encoding="utf-8")))
|
|
64
|
+
for row in rows:
|
|
65
|
+
_write(Path(row["path"]), row["after"])
|
|
66
|
+
journal.unlink()
|
|
67
|
+
return True
|
|
68
|
+
finally:
|
|
69
|
+
_release_active_lock(lock)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def commit_transition(root: str, task: Path, before: str, after: str,
|
|
73
|
+
marker_before: Optional[str], marker_after: Optional[str]) -> None:
|
|
74
|
+
marker = Path(root).resolve() / ".code-flow/.active-task.json"
|
|
75
|
+
rows = [{"path": str(task.resolve()), "before": before, "after": after},
|
|
76
|
+
{"path": str(marker), "before": marker_before, "after": marker_after}]
|
|
77
|
+
journal = _journal_path(root)
|
|
78
|
+
lock = _acquire_active_lock(root)
|
|
79
|
+
try:
|
|
80
|
+
if journal.exists():
|
|
81
|
+
raise ValueError("workflow recovery required before transition")
|
|
82
|
+
_records(root, {"version": 1, "files": rows})
|
|
83
|
+
if any(_read(Path(row["path"])) != row["before"] for row in rows):
|
|
84
|
+
raise ValueError("workflow concurrent change; retry transition")
|
|
85
|
+
_atomic_text(journal, json.dumps({"version": 1, "files": rows}, ensure_ascii=False))
|
|
86
|
+
try:
|
|
87
|
+
for row in rows:
|
|
88
|
+
_write(Path(row["path"]), row["after"])
|
|
89
|
+
except (OSError, ValueError):
|
|
90
|
+
for row in reversed(rows):
|
|
91
|
+
_write(Path(row["path"]), row["before"])
|
|
92
|
+
journal.unlink()
|
|
93
|
+
raise
|
|
94
|
+
journal.unlink()
|
|
95
|
+
finally:
|
|
96
|
+
_release_active_lock(lock)
|