@michaelxwb/code-flow 0.6.1 → 0.6.2
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/plan.md +40 -0
- package/src/adapters/claude/commands/cf-task/start.md +6 -7
- package/src/adapters/claude/commands/cf-task/verify-e2e.md +111 -0
- package/src/adapters/codex/skills/cf-task-start/SKILL.md +6 -7
- package/src/adapters/costrict/commands/cf-task/plan.md +40 -0
- package/src/adapters/costrict/commands/cf-task/start.md +6 -7
- package/src/adapters/costrict/commands/cf-task/verify-e2e.md +111 -0
- package/src/adapters/opencode/commands/cf-task/plan.md +40 -5
- package/src/adapters/opencode/commands/cf-task/start.md +6 -7
- package/src/adapters/opencode/commands/cf-task/verify-e2e.md +111 -0
- package/src/core/code-flow/scripts/cf_acceptance_manifest.py +199 -0
- package/src/core/code-flow/scripts/cf_acceptance_runner.py +107 -0
- package/src/core/code-flow/scripts/cf_core.py +20 -0
- package/src/core/code-flow/scripts/cf_post_hook.py +2 -0
- package/src/core/code-flow/scripts/cf_pre_tool_hook.py +5 -4
- package/src/core/code-flow/scripts/cf_spec_context.py +131 -16
- 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 +78 -7
- package/src/core/code-flow/scripts/cf_spec_router.py +102 -9
- package/src/core/code-flow/scripts/cf_spec_verify.py +118 -8
- package/src/core/code-flow/scripts/cf_stop_hook.py +2 -0
- package/src/core/code-flow/scripts/cf_task_runtime.py +48 -2
- package/src/core/code-flow/scripts/cf_user_prompt_hook.py +2 -0
|
@@ -28,6 +28,7 @@ from cf_spec_metadata import load_spec_metadata
|
|
|
28
28
|
from cf_spec_resolver import resolve_candidates, SpecCandidate
|
|
29
29
|
from cf_spec_session import context_sha256
|
|
30
30
|
from cf_spec_verify import VerificationEvidence, VerificationScope, run_all_verifiers
|
|
31
|
+
from cf_core import phase_timing
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
@dataclass(frozen=True)
|
|
@@ -50,6 +51,24 @@ def _context_path(task_dir: str) -> str:
|
|
|
50
51
|
return str(Path(task_dir) / "spec-context.yml")
|
|
51
52
|
|
|
52
53
|
|
|
54
|
+
def _manual_manifest_issue(task_dir: str) -> str:
|
|
55
|
+
path = Path(task_dir) / ".acceptance-manifest.json"
|
|
56
|
+
if not path.is_file():
|
|
57
|
+
return ""
|
|
58
|
+
try:
|
|
59
|
+
import json
|
|
60
|
+
|
|
61
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
62
|
+
pending = [
|
|
63
|
+
item.get("id", "unknown")
|
|
64
|
+
for item in data.get("scenarios", [])
|
|
65
|
+
if isinstance(item, dict) and item.get("kind") == "manual" and item.get("status") != "verified"
|
|
66
|
+
]
|
|
67
|
+
return f"manual 场景未完成: {', '.join(pending)}" if pending else ""
|
|
68
|
+
except (OSError, ValueError, TypeError):
|
|
69
|
+
return "acceptance manifest invalid"
|
|
70
|
+
|
|
71
|
+
|
|
53
72
|
def _required_candidate(candidate: SpecCandidate) -> bool:
|
|
54
73
|
metadata = candidate.metadata
|
|
55
74
|
return metadata.enforcement == "required" and any(
|
|
@@ -157,15 +176,40 @@ def _rule_manual_confirmation(rule: RuleBinding) -> Optional[Mapping[str, object
|
|
|
157
176
|
}
|
|
158
177
|
|
|
159
178
|
|
|
160
|
-
def run_done_gate(root: str, task_dir: str, cheap: bool = False, budget: Optional[float] = None) -> DoneResult:
|
|
179
|
+
def run_done_gate(root: str, task_dir: str, cheap: bool = False, budget: Optional[float] = None, include_e2e: bool = False) -> DoneResult:
|
|
180
|
+
started = time.monotonic()
|
|
181
|
+
phase_started = time.monotonic()
|
|
161
182
|
scope_result = evaluate_scope(root, task_dir)
|
|
183
|
+
phase_timing("done.evaluate_scope", phase_started)
|
|
162
184
|
if scope_result.decision == "pause":
|
|
163
185
|
return DoneResult("block", scope_result.files, (), scope_result.message)
|
|
186
|
+
manual_issue = _manual_manifest_issue(task_dir)
|
|
187
|
+
if manual_issue:
|
|
188
|
+
return DoneResult("block", scope_result.files, (), manual_issue)
|
|
189
|
+
manifest_path = Path(task_dir) / ".acceptance-manifest.json"
|
|
190
|
+
if manifest_path.is_file():
|
|
191
|
+
try:
|
|
192
|
+
import json
|
|
193
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
194
|
+
has_commands = any(
|
|
195
|
+
isinstance(item, dict) and isinstance(item.get("command"), list)
|
|
196
|
+
for item in manifest.get("scenarios", [])
|
|
197
|
+
)
|
|
198
|
+
except (OSError, ValueError, TypeError):
|
|
199
|
+
return DoneResult("block", scope_result.files, (), "acceptance manifest invalid")
|
|
200
|
+
if has_commands:
|
|
201
|
+
from cf_acceptance_runner import run_manifest
|
|
202
|
+
|
|
203
|
+
scenario_result = run_manifest(str(manifest_path), root, write_evidence=True, include_e2e=include_e2e)
|
|
204
|
+
if scenario_result["decision"] == "block":
|
|
205
|
+
return DoneResult("block", scope_result.files, (), "acceptance scenario failed")
|
|
206
|
+
phase_started = time.monotonic()
|
|
164
207
|
context = load_context(_context_path(task_dir))
|
|
165
208
|
diff_hash = _diff_hash(root, scope_result.files)
|
|
209
|
+
phase_timing("done.load_context_and_diff", phase_started)
|
|
166
210
|
all_evidence: list[Mapping[str, object]] = []
|
|
167
|
-
started = time.monotonic()
|
|
168
211
|
for binding in context.bindings:
|
|
212
|
+
phase_started = time.monotonic()
|
|
169
213
|
metadata = load_spec_metadata(str(Path(root) / ".code-flow/specs" / binding.path))
|
|
170
214
|
confirmations: dict[str, Mapping[str, object]] = {}
|
|
171
215
|
for rule in binding.rules:
|
|
@@ -176,9 +220,11 @@ def run_done_gate(root: str, task_dir: str, cheap: bool = False, budget: Optiona
|
|
|
176
220
|
result = run_all_verifiers(
|
|
177
221
|
metadata, VerificationScope(root, scope_result.files, diff_hash), confirmations, cheap, remaining
|
|
178
222
|
)
|
|
223
|
+
phase_timing(f"done.verify.{binding.spec_id}", phase_started)
|
|
179
224
|
all_evidence.extend(_evidence_data(item) for item in result.evidence)
|
|
180
225
|
updated = _apply_evidence(context, tuple(all_evidence))
|
|
181
226
|
if updated != context:
|
|
182
227
|
save_context(_context_path(task_dir), updated)
|
|
183
228
|
gate = validate_stage(updated, "code", diff_sha256=diff_hash)
|
|
229
|
+
phase_timing("done.total", started)
|
|
184
230
|
return DoneResult(gate.decision, scope_result.files, tuple(all_evidence))
|
|
@@ -16,6 +16,7 @@ from cf_core import (
|
|
|
16
16
|
resolve_enforcement,
|
|
17
17
|
resolve_quality_loop,
|
|
18
18
|
resolve_session_id,
|
|
19
|
+
timing_log,
|
|
19
20
|
)
|
|
20
21
|
from cf_session_state import load_session_state, save_session_state
|
|
21
22
|
from cf_spec_router import RouterError, route_prompt
|
|
@@ -170,3 +171,4 @@ def main() -> None:
|
|
|
170
171
|
|
|
171
172
|
if __name__ == "__main__":
|
|
172
173
|
main()
|
|
174
|
+
timing_log("cf_user_prompt_hook")
|