@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.
Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/src/adapters/claude/commands/cf-task/plan.md +40 -0
  3. package/src/adapters/claude/commands/cf-task/start.md +6 -7
  4. package/src/adapters/claude/commands/cf-task/verify-e2e.md +111 -0
  5. package/src/adapters/codex/skills/cf-task-start/SKILL.md +6 -7
  6. package/src/adapters/costrict/commands/cf-task/plan.md +40 -0
  7. package/src/adapters/costrict/commands/cf-task/start.md +6 -7
  8. package/src/adapters/costrict/commands/cf-task/verify-e2e.md +111 -0
  9. package/src/adapters/opencode/commands/cf-task/plan.md +40 -5
  10. package/src/adapters/opencode/commands/cf-task/start.md +6 -7
  11. package/src/adapters/opencode/commands/cf-task/verify-e2e.md +111 -0
  12. package/src/core/code-flow/scripts/cf_acceptance_manifest.py +199 -0
  13. package/src/core/code-flow/scripts/cf_acceptance_runner.py +107 -0
  14. package/src/core/code-flow/scripts/cf_core.py +20 -0
  15. package/src/core/code-flow/scripts/cf_post_hook.py +2 -0
  16. package/src/core/code-flow/scripts/cf_pre_tool_hook.py +5 -4
  17. package/src/core/code-flow/scripts/cf_spec_context.py +131 -16
  18. package/src/core/code-flow/scripts/cf_spec_gate.py +10 -0
  19. package/src/core/code-flow/scripts/cf_spec_metadata.py +60 -11
  20. package/src/core/code-flow/scripts/cf_spec_resolver.py +78 -7
  21. package/src/core/code-flow/scripts/cf_spec_router.py +102 -9
  22. package/src/core/code-flow/scripts/cf_spec_verify.py +118 -8
  23. package/src/core/code-flow/scripts/cf_stop_hook.py +2 -0
  24. package/src/core/code-flow/scripts/cf_task_runtime.py +48 -2
  25. 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")