@michaelxwb/code-flow 0.6.3 → 0.6.5
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 +2 -2
- package/package.json +1 -1
- package/src/adapters/claude/commands/cf-init.md +1 -0
- package/src/adapters/claude/commands/cf-spec.md +7 -3
- package/src/adapters/claude/commands/cf-task/archive.md +2 -0
- package/src/adapters/claude/commands/cf-task/graph.md +1 -1
- package/src/adapters/claude/commands/cf-task/plan.md +2 -2
- package/src/adapters/claude/commands/cf-task/start.md +99 -10
- package/src/adapters/claude/commands/cf-task/verify-e2e.md +6 -1
- package/src/adapters/claude/commands/cf-validate.md +3 -0
- package/src/adapters/codex/skills/cf-init/SKILL.md +1 -0
- package/src/adapters/codex/skills/cf-spec/SKILL.md +6 -2
- package/src/adapters/codex/skills/cf-task-archive/SKILL.md +2 -0
- package/src/adapters/codex/skills/cf-task-graph/SKILL.md +1 -1
- package/src/adapters/codex/skills/cf-task-plan/SKILL.md +2 -2
- package/src/adapters/codex/skills/cf-task-start/SKILL.md +99 -10
- package/src/adapters/codex/skills/cf-task-verify-e2e/SKILL.md +6 -1
- package/src/adapters/codex/skills/cf-validate/SKILL.md +3 -0
- package/src/adapters/costrict/commands/cf-init.md +1 -0
- package/src/adapters/costrict/commands/cf-spec.md +7 -3
- package/src/adapters/costrict/commands/cf-task/archive.md +2 -0
- package/src/adapters/costrict/commands/cf-task/graph.md +1 -1
- package/src/adapters/costrict/commands/cf-task/plan.md +2 -2
- package/src/adapters/costrict/commands/cf-task/start.md +99 -10
- package/src/adapters/costrict/commands/cf-task/verify-e2e.md +6 -1
- package/src/adapters/costrict/commands/cf-validate.md +3 -0
- package/src/adapters/opencode/commands/cf-init.md +9 -4
- package/src/adapters/opencode/commands/cf-spec.md +6 -2
- package/src/adapters/opencode/commands/cf-task/archive.md +2 -0
- package/src/adapters/opencode/commands/cf-task/graph.md +1 -1
- package/src/adapters/opencode/commands/cf-task/plan.md +2 -2
- package/src/adapters/opencode/commands/cf-task/start.md +99 -10
- package/src/adapters/opencode/commands/cf-task/verify-e2e.md +6 -1
- package/src/adapters/opencode/commands/cf-validate.md +3 -0
- package/src/adapters/opencode/opencode.json +1 -2
- package/src/adapters/opencode/plugins/code-flow/index.js +138 -78
- package/src/adapters/opencode/plugins/code-flow/package.json +1 -1
- package/src/cli.js +51 -1
- package/src/core/code-flow/config.yml +2 -1
- package/src/core/code-flow/scripts/cf_acceptance_manifest.py +54 -8
- package/src/core/code-flow/scripts/cf_core.py +28 -1
- package/src/core/code-flow/scripts/cf_post_hook.py +5 -4
- package/src/core/code-flow/scripts/cf_pre_tool_hook.py +14 -3
- package/src/core/code-flow/scripts/cf_spec_context.py +53 -16
- package/src/core/code-flow/scripts/cf_spec_gate.py +28 -4
- package/src/core/code-flow/scripts/cf_spec_metadata.py +34 -3
- package/src/core/code-flow/scripts/cf_spec_router.py +1 -1
- package/src/core/code-flow/scripts/cf_spec_verify.py +107 -9
- package/src/core/code-flow/scripts/cf_stop_hook.py +31 -3
- package/src/core/code-flow/scripts/cf_task_index.py +5 -4
- package/src/core/code-flow/scripts/cf_task_parallel.py +394 -0
- package/src/core/code-flow/scripts/cf_task_runtime.py +49 -10
- package/src/core/code-flow/scripts/cf_task_workflow.py +111 -13
- package/src/core/code-flow/validation.yml +5 -0
|
@@ -11,8 +11,12 @@ import sys
|
|
|
11
11
|
|
|
12
12
|
from cf_acceptance_runner import run_manifest
|
|
13
13
|
from cf_acceptance_schema import load_manifest, validate_execution_baseline, verified_evidence
|
|
14
|
+
from cf_spec_context import ContextError, _git_changes, _run_git, load_context, save_context
|
|
15
|
+
from cf_spec_gate import validate_stage
|
|
16
|
+
from cf_spec_metadata import load_spec_metadata
|
|
17
|
+
from cf_spec_verify import VerificationScope, _git_tracked_files, run_all_verifiers
|
|
14
18
|
from cf_task_index import parse_task_file
|
|
15
|
-
from cf_task_runtime import run_done_gate
|
|
19
|
+
from cf_task_runtime import _apply_evidence, _evidence_data, run_done_gate
|
|
16
20
|
from cf_workflow_service import (FINISHED_STATUSES, _require_marker_task,
|
|
17
21
|
_sync_markdown, block_task, complete_task, locate_task_file, resume_task)
|
|
18
22
|
from cf_workflow_transaction import recover_transition
|
|
@@ -23,7 +27,10 @@ def finish_task(root: str, directory: str, task_file: str, task_id: str) -> dict
|
|
|
23
27
|
gate = run_done_gate(root, directory, task_id=task_id)
|
|
24
28
|
if gate.decision != "pass":
|
|
25
29
|
return {"decision": "block", "reason": gate.message, "evidence": gate.evidence}
|
|
26
|
-
|
|
30
|
+
result = {"decision": "pass", "deferred_review": gate.deferred_review, **complete_task(root, directory, task_file, task_id, True)}
|
|
31
|
+
if gate.deferred_review:
|
|
32
|
+
result["deferred_hint"] = f"{gate.deferred_review} review-layer verifier(s) deferred; run cf-task:verify-e2e for the requirement directory"
|
|
33
|
+
return result
|
|
27
34
|
|
|
28
35
|
|
|
29
36
|
def _demand_tasks(directory: Path) -> list[tuple[Path, str, str]]:
|
|
@@ -44,6 +51,79 @@ def _demand_tasks(directory: Path) -> list[tuple[Path, str, str]]:
|
|
|
44
51
|
return tasks
|
|
45
52
|
|
|
46
53
|
|
|
54
|
+
def _head_token(root: str) -> str:
|
|
55
|
+
try:
|
|
56
|
+
return "head:" + _run_git(root, ("rev-parse", "HEAD")).strip()
|
|
57
|
+
except ContextError:
|
|
58
|
+
return "head:none"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _review_scope(root: str) -> VerificationScope:
|
|
62
|
+
files = set(_git_tracked_files(root))
|
|
63
|
+
try:
|
|
64
|
+
files.update(_git_changes(root))
|
|
65
|
+
except ContextError:
|
|
66
|
+
pass
|
|
67
|
+
return VerificationScope(root, tuple(sorted(files)), _head_token(root))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def collect_review_bindings(directory: Path) -> dict[str, dict[str, object]]:
|
|
71
|
+
"""收集需求目录下全部 task context 的 review 层绑定。
|
|
72
|
+
|
|
73
|
+
返回 spec_id -> {"path": spec 相对路径, "rules": {rule_ref: [(context_path, context), ...]}}。
|
|
74
|
+
读取失败 fail-closed(报告文件与原因),不允许静默跳过。
|
|
75
|
+
"""
|
|
76
|
+
targets: dict[str, dict[str, object]] = {}
|
|
77
|
+
for ctx_path in sorted(directory.rglob("spec-context.yml")):
|
|
78
|
+
try:
|
|
79
|
+
context = load_context(str(ctx_path))
|
|
80
|
+
except (ContextError, OSError, ValueError) as exc:
|
|
81
|
+
raise ValueError(f"review_context_unreadable: {ctx_path}: {exc}") from exc
|
|
82
|
+
for binding in context.bindings:
|
|
83
|
+
for rule in binding.rules:
|
|
84
|
+
if rule.verifier_stage != "review":
|
|
85
|
+
continue
|
|
86
|
+
entry = targets.setdefault(binding.spec_id, {"path": binding.path, "rules": {}})
|
|
87
|
+
entry["rules"].setdefault(rule.ref, []).append((ctx_path, context))
|
|
88
|
+
return targets
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _run_review_targets(root: str, targets: dict[str, dict[str, object]]) -> dict[str, object]:
|
|
92
|
+
"""执行 review 层 verifier(按 spec/rule 去重),证据写回全部相关 context。"""
|
|
93
|
+
executed = reused = 0
|
|
94
|
+
failures: list[str] = []
|
|
95
|
+
context_paths: set[Path] = set()
|
|
96
|
+
scope = _review_scope(root)
|
|
97
|
+
for spec_id in sorted(targets):
|
|
98
|
+
info = targets[spec_id]
|
|
99
|
+
metadata = load_spec_metadata(str(Path(root) / ".code-flow/specs" / str(info["path"])))
|
|
100
|
+
result = run_all_verifiers(metadata, scope, stage="review")
|
|
101
|
+
by_rule: dict[str, object] = {}
|
|
102
|
+
for evidence in result.evidence:
|
|
103
|
+
rule_ref = evidence.verifier_ref.split("#", 1)[-1]
|
|
104
|
+
by_rule[rule_ref] = evidence
|
|
105
|
+
if evidence.status != "verified":
|
|
106
|
+
failures.append(evidence.verifier_ref)
|
|
107
|
+
elif (evidence.details or {}).get("cache_reused"):
|
|
108
|
+
reused += 1
|
|
109
|
+
else:
|
|
110
|
+
executed += 1
|
|
111
|
+
for rule_ref, entries in info["rules"].items():
|
|
112
|
+
evidence = by_rule.get(rule_ref)
|
|
113
|
+
if evidence is None:
|
|
114
|
+
continue
|
|
115
|
+
payload = (_evidence_data(evidence),)
|
|
116
|
+
for ctx_path, context in entries:
|
|
117
|
+
context_paths.add(ctx_path)
|
|
118
|
+
updated = _apply_evidence(context, payload)
|
|
119
|
+
if updated != context:
|
|
120
|
+
save_context(str(ctx_path), updated)
|
|
121
|
+
for ctx_path in sorted(context_paths):
|
|
122
|
+
gate = validate_stage(load_context(str(ctx_path)), "review", diff_sha256=scope.diff_sha256)
|
|
123
|
+
failures.extend(f"{issue.spec_id}#{issue.rule_ref}" for issue in gate.errors)
|
|
124
|
+
return {"executed": executed, "reused": reused, "failed": sorted(set(failures))}
|
|
125
|
+
|
|
126
|
+
|
|
47
127
|
def verify_e2e(root: str, directory: str) -> dict[str, object]:
|
|
48
128
|
recover_transition(root)
|
|
49
129
|
tasks = _demand_tasks(Path(directory))
|
|
@@ -52,18 +132,36 @@ def verify_e2e(root: str, directory: str) -> dict[str, object]:
|
|
|
52
132
|
return {"decision": "block", "reason": "unfinished_tasks", "tasks": unfinished}
|
|
53
133
|
if (Path(root) / ".code-flow/.active-task.json").exists():
|
|
54
134
|
return {"decision": "block", "reason": "finish_active_task_first"}
|
|
135
|
+
try:
|
|
136
|
+
targets = collect_review_bindings(Path(directory))
|
|
137
|
+
except ValueError as exc:
|
|
138
|
+
return {"decision": "block", "reason": "review_context_unreadable", "detail": str(exc)}
|
|
55
139
|
manifest = Path(directory) / ".acceptance-manifest.json"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
140
|
+
if not targets and not manifest.exists():
|
|
141
|
+
return {"decision": "pass", "executed": 0, "reused": 0, "failed": [], "reason": "nothing_to_verify"}
|
|
142
|
+
review = _run_review_targets(root, targets) if targets else {"executed": 0, "reused": 0, "failed": []}
|
|
143
|
+
acceptance: dict[str, object] = {"decision": "pass", "results": []}
|
|
144
|
+
if manifest.exists():
|
|
145
|
+
data = load_manifest(manifest)
|
|
146
|
+
validate_execution_baseline(manifest, data)
|
|
147
|
+
bound_task = (manifest.parent / str(data.get("task_file", ""))).resolve()
|
|
148
|
+
if {task.resolve() for task, _, _ in tasks} != {bound_task}:
|
|
149
|
+
return {"decision": "block", "reason": "acceptance_manifest_task_mismatch"}
|
|
150
|
+
required = [row["id"] for row in data["scenarios"] if row.get("kind", "functional") != "e2e"
|
|
151
|
+
and not verified_evidence(row)]
|
|
152
|
+
if required:
|
|
153
|
+
return {"decision": "block", "reason": "functional_or_manual_evidence_missing", "scenarios": required}
|
|
154
|
+
acceptance = run_manifest(str(manifest), root, write_evidence=True, only_e2e=True)
|
|
155
|
+
decision = "pass" if not review["failed"] and acceptance.get("decision") == "pass" else "block"
|
|
156
|
+
result = {
|
|
157
|
+
**acceptance,
|
|
158
|
+
"decision": decision,
|
|
159
|
+
"executed": review["executed"],
|
|
160
|
+
"reused": review["reused"],
|
|
161
|
+
"failed": review["failed"],
|
|
162
|
+
"review": review,
|
|
163
|
+
}
|
|
164
|
+
if decision == "pass":
|
|
67
165
|
for task_file, task_id, _ in tasks:
|
|
68
166
|
_sync_markdown(str(task_file), task_id, "verified")
|
|
69
167
|
return result
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# validators 在每次 Stop 按“会话内编辑过的文件”匹配执行(quality_loop.stop_check)。
|
|
2
|
+
# 全量测试 / e2e 等慢命令标 `heavy: true`:Stop 每轮跳过,任务 finish 自动执行
|
|
3
|
+
# 一次,也可显式运行 /cf-validate;避免每个任务都重跑多遍。
|
|
1
4
|
validators:
|
|
2
5
|
- name: "Python 语法检查"
|
|
3
6
|
trigger: "**/*.py"
|
|
@@ -33,6 +36,7 @@ validators:
|
|
|
33
36
|
trigger: "**/*.{ts,tsx,js,jsx,vue}"
|
|
34
37
|
command: "npx vitest run --reporter=dot"
|
|
35
38
|
timeout: 60000
|
|
39
|
+
heavy: true
|
|
36
40
|
on_fail: "前端测试失败,检查组件渲染和 mock 是否需要更新"
|
|
37
41
|
|
|
38
42
|
- name: "Python 类型检查"
|
|
@@ -45,4 +49,5 @@ validators:
|
|
|
45
49
|
trigger: "**/*.py"
|
|
46
50
|
command: "python3 -m pytest --tb=short -q"
|
|
47
51
|
timeout: 60000
|
|
52
|
+
heavy: true
|
|
48
53
|
on_fail: "测试失败,检查断言和 mock 是否需要更新"
|