Coverage for src/keel/contracts.py: 100%

285 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-16 18:07 +0000

1"""Structured command contracts for adapters and parity tests. 

2 

3The contract is intentionally plain JSON-compatible data. Agent adapters can read it before 

4mutating work starts, compare required capabilities with the current runtime, and execute the 

5same command graph without re-deriving keel behavior from prose. 

6""" 

7 

8from __future__ import annotations 

9 

10import re 

11from dataclasses import asdict 

12from pathlib import Path 

13from typing import Any 

14 

15from . import ( 

16 artifacts, 

17 capture, 

18 checkpoint, 

19 closure, 

20 consent, 

21 evidence, 

22 gates, 

23 github_transport, 

24 install, 

25 intake, 

26 ledger, 

27 lock, 

28 model, 

29 orchestrator, 

30 provenance, 

31 runcontrols, 

32 runtime, 

33 stepverifier, 

34 workblock, 

35 workcreation, 

36) 

37from . import config as cfg 

38from . import ship as ship_decisions 

39from .extensions import Extension 

40from .project_commands import get_project_command, list_project_commands 

41 

42SCHEMA_VERSION = "keel.command-contract.v1" 

43 

44_COMPOUND_OVERRIDES: dict[str, str] = { 

45 "s4": "compound", 

46 "s7": "compound", 

47 "s9": "compound", 

48 "s11": "compound", 

49} 

50 

51_STEP_RE = re.compile( 

52 r"^#{2,3}\s+(?P<id>(?:Step\s+[0-9A-Za-z.]+|s\d+))\s*(?:[\u2014-]\s*)?" 

53 r"(?P<name>.*)$" 

54) 

55 

56_BASE_SIDE_EFFECTS: dict[str, tuple[str, ...]] = { 

57 "ship": ("git_worktree", "git_branch", "file_edit", "git_push", "pull_request", "comments", 

58 "reviews", "merge", 

59 "issue_close", "capture"), 

60 "pr-loop": ("file_edit", "git_commit", "git_push", "comments", "reviews", "check_runs"), 

61 "review-cycle": ("file_edit", "comments", "reviews", "git_commit", "git_push"), 

62 "morning": ("issue_read", "pr_read", "report_write"), 

63 "wrap": ("git_commit", "git_push", "pull_request", "session_recap"), 

64 "work-block": ("git_branch", "git_push", "pull_request", "comments", "reviews", "merge", 

65 "deferral_queue", "session_report"), 

66 "overnight": ("git_branch", "git_push", "pull_request", "comments", "reviews", "merge", 

67 "deferral_queue", "session_report"), 

68 "implement": ("git_worktree", "git_branch", "file_edit", "git_commit", "git_push", 

69 "pull_request", "comments"), 

70 "ci-check": ("check_runs",), 

71 "triage": ("labels", "comments"), 

72 "stale-prs": ("comments", "git_checkout", "git_push"), 

73 "regression": ("git_worktree", "issue_write", "labels"), 

74 "review-all-day": ("issue_write", "labels"), 

75 "coverage": ("git_worktree", "git_checkout", "comments", "labels", "issue_write"), 

76 "deps-audit": ("comments", "issue_write"), 

77 "flake-audit": ("issue_write", "comments"), 

78} 

79 

80_DEFAULT_FEEDBACK_WORKFLOWS: dict[str, dict[str, Any]] = { 

81 "pr-loop": { 

82 "posting_mode": "summary", 

83 "posting_owner": "orchestrator", 

84 "reviewer_isolation": { 

85 "shared_with_ship": True, 

86 "codename_prefix": "PR-LOOP", 

87 "no_cross_reading": True, 

88 }, 

89 "inputs": { 

90 "auto_detect_current_branch": True, 

91 "explicit_pr_targets": True, 

92 "reads_review_comments": True, 

93 "reads_issue_conversation_comments": True, 

94 }, 

95 "ci": { 

96 "recheck_after_push": True, 

97 "green_required_to_exit": True, 

98 "degrade_when_logs_unavailable": True, 

99 }, 

100 "fix_loop": { 

101 "budget": 3, 

102 "self_review_before_push": True, 

103 "reviewer_fanout_after_each_push": True, 

104 }, 

105 "completion": { 

106 "marker": None, 

107 "merge": "handoff", 

108 "summary_comment": True, 

109 }, 

110 }, 

111 "review-cycle": { 

112 "posting_mode": "inline", 

113 "posting_owner": "orchestrator", 

114 "reviewer_isolation": { 

115 "shared_with_ship": True, 

116 "codename_prefix": "REVIEW-CYCLE", 

117 "no_cross_reading": True, 

118 }, 

119 "inputs": { 

120 "multi_pr": True, 

121 "sequential_pr_processing": True, 

122 }, 

123 "review": { 

124 "parallel_reviewers_within_pr": True, 

125 "partial_reviewer_failures_degrade": True, 

126 "severity_histogram_source_of_truth": True, 

127 }, 

128 "fix_loop": { 

129 "budget": 3, 

130 "enabled": True, 

131 }, 

132 "completion": { 

133 "marker": "review-cycle-complete", 

134 "marker_after_summary": True, 

135 "merge": "never", 

136 "formal_approval": "never", 

137 }, 

138 }, 

139} 

140 

141_REPORTING_COMMANDS = {"coverage", "deps-audit", "flake-audit"} 

142 

143 

144def available_commands() -> tuple[str, ...]: 

145 """Every packaged adapter command that can expose a structured contract.""" 

146 return tuple(name.removesuffix(".md") for name in install.adapter_names()) 

147 

148 

149def command_graph(command: str, *, profile: str = "standard") -> list[dict[str, Any]]: 

150 """Return the command's step graph as JSON-compatible records. 

151 

152 ``ship`` uses the fixed keel backbone as the canonical graph. When the ``compound`` 

153 profile is selected, the s4/s7/s9/s11 steps are marked as compound overrides. Other 

154 commands expose their adapter step headings, so adapters can still reason about their 

155 command-local sequence without parsing Markdown themselves. 

156 """ 

157 if command == "ship": 

158 compound = profile == "compound" 

159 return [ 

160 { 

161 "step_id": step.id, 

162 "step_name": step.name, 

163 "agentic": step.agentic, 

164 "slot": step.slot, 

165 "source": "backbone", 

166 "profile_step": _COMPOUND_OVERRIDES.get(step.id, "standard") 

167 if compound else "standard", 

168 } 

169 for step in model.BACKBONE 

170 ] 

171 steps = _adapter_steps(command) 

172 return steps if steps else [] 

173 

174 

175def build_command_contract( 

176 *, 

177 command: str, 

178 profile: str = "standard", 

179 config: cfg.ProjectConfig, 

180 loaded: dict[str, list[Extension]], 

181 plan: tuple[orchestrator.PlanItem, ...], 

182 requirement: runtime.CapabilityRequirement, 

183 evaluation: runtime.CapabilityEvaluation, 

184 transport: github_transport.GitHubTransport, 

185 extension_problems: tuple[str, ...] = (), 

186 dry_run: bool = True, 

187 approved_consent_scopes: tuple[str, ...] = (), 

188 consent_approval_source: str = "flag", 

189 consent_mode: str = "explicit", 

190 operator: str | None = None, 

191 target: str | None = None, 

192 reviewer_override: int | None = None, 

193 review_tier: int | None = None, 

194 review_comments: str = "inline", 

195 jury: bool = False, 

196 no_jury: bool = False, 

197 jury_advisory: bool = False, 

198 issue_title: str | None = None, 

199 issue_body: str | None = None, 

200 issue_labels: tuple[str, ...] = (), 

201) -> dict[str, Any]: 

202 """Build the stable adapter contract shared by ``plan --json`` and dry-run commands.""" 

203 declared_side_effects = command_side_effects(command, config, requirement, loaded) 

204 graph = command_graph(command, profile=profile) 

205 if not graph and (project_command := get_project_command(config, command)): 

206 graph = [{ 

207 "step_id": f"project-command:{project_command.name}", 

208 "step_name": project_command.name, 

209 "agentic": bool(project_command.agent_role), 

210 "slot": None, 

211 "source": "project_command", 

212 }] 

213 contract = { 

214 "schema_version": SCHEMA_VERSION, 

215 "command": command, 

216 "mode": "dry-run" if dry_run else "live", 

217 "dry_run": dry_run, 

218 "no_mutations": dry_run, 

219 "project": project_as_dict(config), 

220 "workflow_profile": workflow_profile(command, profile=profile), 

221 "graph": graph, 

222 "backbone_plan": orchestrator.plan_as_dict(plan), 

223 "gates": [gate_as_dict(spec) for spec in gates.plan_gates(config, loaded)], 

224 "project_commands": [command.as_dict() for command in list_project_commands(config)], 

225 "extension_hooks": extension_hooks_as_dict(config, loaded), 

226 "extension_problems": list(extension_problems), 

227 "required_capabilities": list(requirement.required), 

228 "optional_capabilities": list(requirement.optional), 

229 "capabilities": evaluation.as_dict(), 

230 "github_transport": transport.as_dict(), 

231 "checkpoint": checkpoint.checkpoint_contract_as_dict(config), 

232 "capture": capture.contract_as_dict(config), 

233 "run_ledger": ledger.ledger_contract_as_dict(config), 

234 "resource_claims": lock.contract_as_dict(), 

235 "side_effects": { 

236 "declared": list(declared_side_effects), 

237 "mutates_in_dry_run": False, 

238 }, 

239 "operator_consent": consent.build_consent_contract( 

240 command=command, 

241 side_effects=declared_side_effects, 

242 dry_run=dry_run, 

243 approved_scopes=approved_consent_scopes, 

244 approval_source=consent_approval_source, 

245 mode=consent_mode, 

246 operator=operator, 

247 target=target, 

248 ), 

249 "agent_output_provenance": provenance.contract_as_dict(), 

250 } 

251 if command == "morning": 

252 contract["morning_contract"] = morning_contract_as_dict( 

253 config=config, 

254 evaluation=evaluation, 

255 transport=transport, 

256 ) 

257 if command in _REPORTING_COMMANDS: 

258 contract["reporting_contract"] = reporting_contract_as_dict( 

259 command=command, 

260 config=config, 

261 transport=transport, 

262 ) 

263 if command in {"wrap", "work-block", "overnight"}: 

264 contract["session_contract"] = session_contract_as_dict( 

265 command=command, 

266 config=config, 

267 transport=transport, 

268 ) 

269 if command in {"regression", "review-all-day"}: 

270 contract["scan_contract"] = scan_contract_as_dict( 

271 command=command, 

272 config=config, 

273 transport=transport, 

274 ) 

275 if command in {"ship", "pr-loop", "review-cycle", "work-block", "overnight"}: 

276 contract["review_merge_contract"] = ship_decisions.resolve_review_contract( 

277 tier=review_tier, 

278 reviewer_override=reviewer_override, 

279 review_comments=review_comments, 

280 gates=config.gates, 

281 policy_pack=config.policy_pack, 

282 jury=jury, 

283 no_jury=no_jury, 

284 jury_advisory=jury_advisory, 

285 ) 

286 if command == "ship": 

287 contract["evidence"] = evidence.contract_as_dict( 

288 contract["review_merge_contract"], 

289 dry_run=dry_run, 

290 ) 

291 contract["step_verification"] = stepverifier.contract_as_dict( 

292 contract["review_merge_contract"], 

293 dry_run=dry_run, 

294 ) 

295 contract["run_controls"] = runcontrols.contract_as_dict() 

296 if command == "ship": 

297 contract["closure_comment"] = closure.contract_as_dict() 

298 contract["artifact_renderers"] = artifacts.contract_as_dict() 

299 if command in {"ship", "implement", "overnight"}: 

300 contract["issue_intake"] = intake.assess_issue( 

301 title=issue_title, 

302 body=issue_body, 

303 labels=issue_labels, 

304 ) 

305 if command in {"pr-loop", "review-cycle"}: 

306 contract["feedback_workflow"] = feedback_workflow_as_dict(config, command) 

307 return contract 

308 

309 

310def workflow_profile(command: str, *, profile: str = "standard") -> dict[str, Any]: 

311 """First-class workflow profile metadata for command variants. 

312 

313 ``ship`` carries the ``standard`` profile by default; selecting the ``compound`` 

314 profile swaps the s4/s7/s9/s11 steps to compound step overrides without forking the 

315 backbone. 

316 """ 

317 if command == "ship" and profile == "compound": 

318 return { 

319 "name": "ship", 

320 "profile": "compound", 

321 "inherits": "ship", 

322 "first_class_variant": True, 

323 "shared_primitives": [ 

324 "select", 

325 "branch", 

326 "worktree", 

327 "guard", 

328 "classify", 

329 "ci", 

330 "test", 

331 "merge_window", 

332 "merge_lock", 

333 "merge", 

334 "capture_marker", 

335 "close", 

336 ], 

337 "step_overrides": { 

338 "s4": { 

339 "step": "implement", 

340 "mode": "compound", 

341 "reason": "compound implement and PR-quality pass", 

342 }, 

343 "s7": { 

344 "step": "review", 

345 "mode": "compound", 

346 "reason": "persona and diff-aware reviewer fan-out", 

347 }, 

348 "s9": { 

349 "step": "fixloop", 

350 "mode": "compound", 

351 "reason": "structured PR-feedback resolution", 

352 }, 

353 "s11": { 

354 "step": "capture", 

355 "mode": "compound", 

356 "reason": "durable-learning capture", 

357 }, 

358 }, 

359 } 

360 if command == "pr-loop": 

361 return { 

362 "name": "pr-loop", 

363 "profile": "feedback-loop", 

364 "inherits": "ship.s6-s9", 

365 "first_class_variant": True, 

366 "shared_primitives": [ 

367 "linked_worktree_preflight", 

368 "github_transport", 

369 "reviewer_isolation", 

370 "ci_recheck", 

371 "fixloop", 

372 "summary_comment", 

373 "operator_consent", 

374 ], 

375 "step_overrides": { 

376 "merge": { 

377 "mode": "handoff", 

378 "reason": "pr-loop exits after feedback and CI are satisfied.", 

379 }, 

380 }, 

381 } 

382 if command == "review-cycle": 

383 return { 

384 "name": "review-cycle", 

385 "profile": "review-feedback", 

386 "inherits": "ship.s7-s9", 

387 "first_class_variant": True, 

388 "shared_primitives": [ 

389 "multi_pr_targets", 

390 "reviewer_isolation", 

391 "posting_mode", 

392 "severity_histogram", 

393 "fixloop", 

394 "completion_marker", 

395 "operator_consent", 

396 ], 

397 "step_overrides": { 

398 "merge": { 

399 "mode": "never", 

400 "reason": "review-cycle never merges or posts formal approval.", 

401 }, 

402 }, 

403 } 

404 if command == "implement": 

405 return { 

406 "name": "implement", 

407 "profile": "standalone-step", 

408 "inherits": "ship.s4", 

409 "first_class_variant": True, 

410 "shared_primitives": [ 

411 "issue_target", 

412 "branch", 

413 "worktree", 

414 "implementer_routing", 

415 "operator_consent", 

416 "handoff", 

417 ], 

418 "step_overrides": {}, 

419 } 

420 if command == "ci-check": 

421 return { 

422 "name": "ci-check", 

423 "profile": "standalone-diagnostic", 

424 "inherits": None, 

425 "first_class_variant": True, 

426 "shared_primitives": [ 

427 "github_transport", 

428 "check_runs", 

429 "latest_run_context", 

430 "log_diagnostics", 

431 "read_only", 

432 "routing_recommendation", 

433 ], 

434 "step_overrides": {}, 

435 } 

436 if command == "morning": 

437 return { 

438 "name": "morning", 

439 "profile": "daily-brief", 

440 "inherits": None, 

441 "first_class_variant": True, 

442 "shared_primitives": [ 

443 "date_window", 

444 "deferral_queue", 

445 "shipped_since", 

446 "github_summary", 

447 "health_providers", 

448 "priority_sources", 

449 "ranked_focus", 

450 "report_output", 

451 ], 

452 "step_overrides": {}, 

453 } 

454 if command == "wrap": 

455 return { 

456 "name": "wrap", 

457 "profile": "session-wrap", 

458 "inherits": None, 

459 "first_class_variant": True, 

460 "shared_primitives": [ 

461 "linked_worktree_preflight", 

462 "base_branch_guard", 

463 "configured_gates", 

464 "conventional_commit", 

465 "ready_pr_create", 

466 "session_recap", 

467 "deferral_queue", 

468 "operator_consent", 

469 ], 

470 "step_overrides": {}, 

471 } 

472 if command == "overnight": 

473 return { 

474 "name": "overnight", 

475 "profile": "session-overnight", 

476 "inherits": "ship", 

477 "first_class_variant": True, 

478 "shared_primitives": [ 

479 "work_block", 

480 "merge_window", 

481 "ship_handoff", 

482 "priority_queue", 

483 "per_issue_worktree", 

484 "no_night_merge", 

485 "blocker_policy", 

486 "session_report", 

487 "deferral_queue", 

488 "stop_conditions", 

489 "operator_consent", 

490 ], 

491 "step_overrides": {}, 

492 } 

493 if command == "work-block": 

494 return { 

495 "name": "work-block", 

496 "profile": "session-work-block-daytime", 

497 "inherits": "ship", 

498 "first_class_variant": True, 

499 "shared_primitives": [ 

500 "work_block", 

501 "queue_snapshot", 

502 "readiness_refresh", 

503 "ship_handoff", 

504 "per_issue_worktree", 

505 "operator_between_item_control", 

506 "progress_snapshot", 

507 "session_report", 

508 "deferral_queue", 

509 "stop_conditions", 

510 "operator_consent", 

511 ], 

512 "step_overrides": {}, 

513 } 

514 if command in _REPORTING_COMMANDS: 

515 return { 

516 "name": command, 

517 "profile": "reporting", 

518 "inherits": None, 

519 "first_class_variant": True, 

520 "shared_primitives": [ 

521 "project_policy", 

522 "github_transport", 

523 "codename_anchor", 

524 "dedupe", 

525 "dry_run_no_mutations", 

526 "ship_handoff", 

527 "operator_consent", 

528 ], 

529 "step_overrides": {}, 

530 } 

531 if command == "regression": 

532 return { 

533 "name": "regression", 

534 "profile": "scan-and-file", 

535 "inherits": None, 

536 "first_class_variant": True, 

537 "shared_primitives": [ 

538 "canonical_base_scan", 

539 "clean_tree_preflight", 

540 "read_only_worktree", 

541 "area_fanout", 

542 "reviewer_isolation", 

543 "confidence_filter", 

544 "dedupe", 

545 "issue_lock", 

546 "issue_create", 

547 "ship_handoff", 

548 "final_report", 

549 "operator_consent", 

550 ], 

551 "step_overrides": {}, 

552 } 

553 if command == "review-all-day": 

554 return { 

555 "name": "review-all-day", 

556 "profile": "time-window-scan", 

557 "inherits": None, 

558 "first_class_variant": True, 

559 "shared_primitives": [ 

560 "merge_window_span", 

561 "remote_ref_scope", 

562 "batch_or_fanout", 

563 "reviewer_isolation", 

564 "diff_truncation", 

565 "finding_filter", 

566 "dedupe", 

567 "issue_prefix", 

568 "issue_create", 

569 "final_report", 

570 "operator_consent", 

571 ], 

572 "step_overrides": {}, 

573 } 

574 if command == "ship": 

575 return { 

576 "name": "ship", 

577 "profile": "standard", 

578 "inherits": None, 

579 "first_class_variant": True, 

580 "shared_primitives": [ 

581 "select", 

582 "branch", 

583 "guard", 

584 "implement", 

585 "classify", 

586 "ci", 

587 "review", 

588 "test", 

589 "fixloop", 

590 "merge", 

591 "capture", 

592 "close", 

593 ], 

594 "step_overrides": {}, 

595 } 

596 return { 

597 "name": command, 

598 "profile": "adapter", 

599 "inherits": None, 

600 "first_class_variant": False, 

601 "shared_primitives": [], 

602 "step_overrides": {}, 

603 } 

604 

605 

606def command_side_effects( 

607 command: str, 

608 config: cfg.ProjectConfig, 

609 requirement: runtime.CapabilityRequirement, 

610 loaded: dict[str, list[Extension]], 

611) -> tuple[str, ...]: 

612 """Return command side effects plus project capability-derived consent effects.""" 

613 effects: list[str] = list(_BASE_SIDE_EFFECTS.get(command, ())) 

614 if project_command := get_project_command(config, command): 

615 effects.extend(project_command.side_effects) 

616 effects.extend(consent.capability_side_effects(requirement.required)) 

617 effects.extend(consent.capability_side_effects(requirement.optional)) 

618 for extensions in loaded.values(): 

619 for ext in extensions: 

620 effects.extend(consent.capability_side_effects(ext.required_capabilities)) 

621 effects.extend(consent.capability_side_effects(ext.optional_capabilities)) 

622 return tuple(dict.fromkeys(effects)) 

623 

624 

625def reporting_contract_as_dict( 

626 *, 

627 command: str, 

628 config: cfg.ProjectConfig, 

629 transport: github_transport.GitHubTransport | None = None, 

630) -> dict[str, Any]: 

631 """Project-neutral reporting parity contract for audit/report adapters.""" 

632 github = transport.as_dict() if transport is not None else {} 

633 base = { 

634 "command": command, 

635 "base_branch": config.base_branch, 

636 "timezone": config.timezone, 

637 "github_transport": github, 

638 "policy_source": "policy_pack + adapter arguments", 

639 "dry_run": { 

640 "mutates": False, 

641 "prints_planned_writes": True, 

642 }, 

643 "handoff": { 

644 "fixes_route_to": "ship", 

645 "auto_applies_fixes": False, 

646 }, 

647 "work_creation_policy": workcreation.contract_as_dict(), 

648 } 

649 if command == "coverage": 

650 return { 

651 **base, 

652 "target": "pull_request", 

653 "codename_prefix": "COVERAGE-<PR>-", 

654 "idempotency": { 

655 "scope": "one-comment-per-pr", 

656 "find_by_first_line_prefix": "COVERAGE-<PR>-", 

657 "existing_comment": "update-in-place", 

658 "update_unavailable": "do-not-post-duplicate", 

659 }, 

660 "labels": { 

661 "regression": "coverage-regression", 

662 "operation": "idempotent-add-or-remove", 

663 }, 

664 "degradation": { 

665 "unwired_tool": "skip-area", 

666 "coverage_command_failure": "fatal", 

667 }, 

668 "arguments": ["pr", "--base", "--threshold", "--changed", "--open-issues", 

669 "--dry-run"], 

670 } 

671 if command == "deps-audit": 

672 return { 

673 **base, 

674 "target": "daily_tracking_issue", 

675 "tracking_issue_title": "deps-audit: <DATE>", 

676 "codename_prefix": "DEPS-AUDIT-<DATE>-", 

677 "idempotency": { 

678 "scope": "append-per-run", 

679 "find_tracking_issue_by_exact_title": "deps-audit: <DATE>", 

680 "find_latest_run_by_first_line_prefix": "DEPS-AUDIT-<DATE>-", 

681 "existing_comment": "append-fresh-run-comment", 

682 }, 

683 "degradation": { 

684 "per_ecosystem_failure": "skipped-section", 

685 "argument_failure": "fatal", 

686 }, 

687 "arguments": ["ecosystem", "--severity", "--security-only", "--open-issues", 

688 "--dry-run"], 

689 } 

690 if command == "flake-audit": 

691 return { 

692 **base, 

693 "target": "ci_history_or_local_runs", 

694 "codename_prefix": "FLAKE-AUDIT-<DATE>-", 

695 "idempotency": { 

696 "scope": "one-issue-per-flake", 

697 "dedupe_issue_title": "flaky test: <fully.qualified.name>", 

698 "find_run_by_first_line_prefix": "FLAKE-AUDIT-<DATE>-", 

699 }, 

700 "classification": { 

701 "rule": "across-run-disagreement-only", 

702 "minimum_failures": 3, 

703 "consistent_failures": "real-bug-not-flake", 

704 }, 

705 "degradation": { 

706 "artifact_unavailable": "run-level-limitations-section", 

707 "no_ci_or_local_gate": "clean-exit", 

708 }, 

709 "arguments": ["--days", "--runs", "--threshold", "--open-issues", "--dry-run"], 

710 } 

711 return base 

712 

713 

714def project_as_dict(config: cfg.ProjectConfig) -> dict[str, Any]: 

715 """Resolved project config summary safe for adapter planning.""" 

716 return { 

717 "config_hash": cfg.config_hash(config), 

718 "extends": config.extends, 

719 "core_version": config.core_version, 

720 "base_branch": config.base_branch, 

721 "owner": config.owner, 

722 "repo": config.repo, 

723 "platform": config.platform, 

724 "timezone": config.timezone, 

725 "merge_window": config.merge_window, 

726 "merge_window_mode": config.merge_window_mode, 

727 "extensions_dir": config.extensions_dir, 

728 "gates": list(config.gates), 

729 "extensions": {slot: list(files) for slot, files in sorted(config.extensions.items())}, 

730 "policy_pack": config.policy_pack, 

731 "knobs": { 

732 "build_gate_cmd": config.knobs.build_gate_cmd, 

733 "lint_cmd": config.knobs.lint_cmd, 

734 "implementer_agents": dict(sorted(config.knobs.implementer_agents.items())), 

735 "tier3_globs": list(config.knobs.tier3_globs), 

736 "ci_workflows": dict(sorted(config.knobs.ci_workflows.items())), 

737 "docs_gate_paths": list(config.knobs.docs_gate_paths), 

738 "docs_only_allowlist": list(config.knobs.docs_only_allowlist), 

739 "sot_doc": config.knobs.sot_doc, 

740 "required_capabilities": list(config.knobs.required_capabilities), 

741 "optional_capabilities": list(config.knobs.optional_capabilities), 

742 }, 

743 } 

744 

745 

746def gate_as_dict(spec: gates.GateSpec) -> dict[str, Any]: 

747 """Render a planned gate without losing its capability declarations.""" 

748 return asdict(spec) 

749 

750 

751def extension_hooks_as_dict( 

752 config: cfg.ProjectConfig, loaded: dict[str, list[Extension]] 

753) -> dict[str, list[dict[str, Any]]]: 

754 """Render loaded extension hooks grouped by backbone slot.""" 

755 return { 

756 slot: [ 

757 { 

758 "id": ext.id, 

759 "slot": ext.slot, 

760 "kind": ext.kind, 

761 "mode": ext.mode, 

762 "agent": ext.agent, 

763 "on_fail": ext.on_fail, 

764 "anchorable": ext.anchorable, 

765 "source": ext.source, 

766 "has_run": ext.run is not None, 

767 "has_prompt": ext.prompt is not None or bool(ext.body.strip()), 

768 "required_capabilities": list(ext.required_capabilities), 

769 "optional_capabilities": list(ext.optional_capabilities), 

770 } 

771 for ext in loaded.get(slot, []) 

772 ] 

773 for slot in model.SLOTS 

774 } 

775 

776 

777def ship_result_as_dict( 

778 *, 

779 changed_files: list[str], 

780 outcomes: list[gates.GateOutcome], 

781 verdict, 

782 assessment, 

783 issue_intake: dict[str, Any] | None = None, 

784 run_ledger: dict[str, Any] | None = None, 

785) -> dict[str, Any]: 

786 """Normalized deterministic result record for ``keel ship --json``.""" 

787 closure_comment = None 

788 issue_number = None 

789 pr_number = None 

790 head_sha = None 

791 if isinstance(run_ledger, dict): 

792 record = run_ledger.get("record") 

793 if isinstance(record, dict): 

794 closure_comment = closure.render_closure_comment(record) 

795 issue = record.get("issue") 

796 pull_request = record.get("pull_request") 

797 issue_number = issue.get("number") if isinstance(issue, dict) else None 

798 pr_number = pull_request.get("number") if isinstance(pull_request, dict) else None 

799 head_sha = record.get("head_sha") 

800 head_sha = head_sha if isinstance(head_sha, str) else None 

801 finding_dicts = [_finding_as_dict(finding) for finding in verdict.findings] 

802 testing = _testing_summary(outcomes) 

803 artifact_bodies = { 

804 "pr_body": artifacts.render_pr_body( 

805 issue_number=issue_number, 

806 issue_intake=issue_intake, 

807 changed_files=changed_files, 

808 testing=testing, 

809 docs_impact=_docs_impact(changed_files), 

810 ), 

811 "issue_update": artifacts.render_issue_update( 

812 issue_number=issue_number, 

813 pull_request=pr_number, 

814 status="ready-for-merge" if not verdict.blocked else "blocked", 

815 summary=assessment.merge.reason, 

816 next_step="Merge when CI and evidence are green." 

817 if not verdict.blocked else "Resolve blocking findings before merge.", 

818 ), 

819 "review_verdict_template": artifacts.render_review_verdict( 

820 reviewer="reviewer", 

821 head_sha=head_sha, 

822 verdict="REQUEST_CHANGES" if verdict.blocked else "LGTM", 

823 scope="Full changed-file diff and keel command contract.", 

824 findings=finding_dicts, 

825 testing="; ".join(testing) if testing else "See PR Testing section.", 

826 ), 

827 "jury_verdict_template": artifacts.render_jury_verdict( 

828 head_sha=head_sha, 

829 participants=("reviewer-a", "reviewer-b", "reviewer-c", "orchestrator"), 

830 verdict="REQUEST_CHANGES" if verdict.blocked else "LGTM", 

831 findings_summary=_finding_summaries(finding_dicts), 

832 remaining_risks="blocking findings present" if verdict.blocked else "none identified", 

833 ), 

834 "extension_result_template": artifacts.render_extension_result( 

835 slot="<slot>", 

836 extension_id="<extension-id>", 

837 status="not-run", 

838 mode="advisory", 

839 summary="Extension result summary goes here.", 

840 ), 

841 } 

842 return { 

843 "changed_files": list(changed_files), 

844 "changed_file_count": len(changed_files), 

845 "issue_intake": issue_intake, 

846 "run_ledger": run_ledger, 

847 "closure_comment": closure_comment, 

848 "artifact_bodies": artifact_bodies, 

849 "gate_outcomes": [ 

850 { 

851 "gate": outcome.gate, 

852 "ok": outcome.ok, 

853 "skipped": outcome.skipped, 

854 "error": outcome.error, 

855 "findings": [_finding_as_dict(finding) for finding in outcome.findings], 

856 } 

857 for outcome in outcomes 

858 ], 

859 "verdict": { 

860 "blocked": verdict.blocked, 

861 "counts": dict(verdict.counts), 

862 "findings": [_finding_as_dict(finding) for finding in verdict.findings], 

863 }, 

864 "assessment": { 

865 "tier": assessment.tier, 

866 "reviewers": assessment.reviewers, 

867 "window_open": assessment.window_open, 

868 "ci_ok": assessment.ci_ok, 

869 "merge": { 

870 "action": assessment.merge.action, 

871 "reason": assessment.merge.reason, 

872 }, 

873 "halted": assessment.halted, 

874 "bypassed_window": assessment.bypassed_window, 

875 "review_merge_contract": assessment.review_contract, 

876 }, 

877 } 

878 

879 

880def _testing_summary(outcomes: list[gates.GateOutcome]) -> list[str]: 

881 if not outcomes: 

882 return [] 

883 lines: list[str] = [] 

884 for outcome in outcomes: 

885 if outcome.skipped: 

886 state = "skipped" 

887 elif outcome.ok: 

888 state = "passed" 

889 else: 

890 state = "failed" 

891 suffix = f" ({outcome.error})" if outcome.error else "" 

892 lines.append(f"{outcome.gate}: {state}{suffix}") 

893 return lines 

894 

895 

896def _docs_impact(changed_files: list[str]) -> str: 

897 docs = [file for file in changed_files if _is_doc_path(file)] 

898 if docs: 

899 return "Updated docs: " + ", ".join(f"`{file}`" for file in docs) 

900 return "Docs Impact: none — no documentation files changed." 

901 

902 

903def _is_doc_path(file: str) -> bool: 

904 lowered = file.lower() 

905 return ( 

906 "/docs/" in f"/{lowered}" 

907 or lowered.startswith("docs/") 

908 or lowered.endswith((".md", ".mdx", ".rst", ".adoc")) 

909 ) 

910 

911 

912def _finding_summaries(findings: list[dict[str, Any]]) -> list[str]: 

913 return [ 

914 f"{finding['severity']}: {finding['message']}" 

915 for finding in findings 

916 if isinstance(finding.get("severity"), str) and isinstance(finding.get("message"), str) 

917 ] 

918 

919 

920def standalone_result_as_dict( 

921 *, 

922 command: str, 

923 config: cfg.ProjectConfig, 

924 target: str | None = None, 

925 delegate: str | None = None, 

926 transport: github_transport.GitHubTransport | None = None, 

927 evaluation: runtime.CapabilityEvaluation | None = None, 

928) -> dict[str, Any]: 

929 """Deterministic dry-run result records for standalone non-ship commands.""" 

930 if command == "implement": 

931 issue_id = _target_identifier(target) 

932 return { 

933 "command": command, 

934 "target": target, 

935 "base_branch": config.base_branch, 

936 "branch_pattern": f"feature/issue-{issue_id}-<slug>", 

937 "worktree_path_pattern": f"worktrees/issue-{issue_id}", 

938 "implementer": { 

939 "source": "delegate" if delegate else "project-routing-or-host", 

940 "selected": delegate, 

941 "routing_keys": sorted(config.knobs.implementer_agents), 

942 }, 

943 "handoff": { 

944 "opens_pr": True, 

945 "merges": False, 

946 "next_commands": ["ship", "pr-loop"], 

947 }, 

948 } 

949 if command == "ci-check": 

950 resolved = transport.as_dict() if transport is not None else {} 

951 return { 

952 "command": command, 

953 "target": target, 

954 "base_branch": config.base_branch, 

955 "ci_workflows": dict(sorted(config.knobs.ci_workflows.items())), 

956 "latest_run_context": { 

957 "limit": 3, 

958 "selected": "newest available run", 

959 "history": "previous runs used for flake or infra classification", 

960 }, 

961 "diagnostics": { 

962 "read_only": True, 

963 "log_tail": "available when the selected GitHub transport exposes logs", 

964 "classifications": ["real-failure", "flake", "infra-or-quota"], 

965 "proposed_fix_count": 1, 

966 }, 

967 "github_transport": resolved, 

968 "routing": { 

969 "never_direct_merge": True, 

970 "recommendations": ["review-cycle", "pr-loop", "ship", "flake-audit"], 

971 }, 

972 } 

973 if command == "morning": 

974 return { 

975 "command": command, 

976 "target": target, 

977 "base_branch": config.base_branch, 

978 "brief": morning_contract_as_dict( 

979 config=config, 

980 evaluation=evaluation, 

981 transport=transport, 

982 ), 

983 "execution": { 

984 "runs_project_health_commands": False, 

985 "writes_reports": False, 

986 "live_work_owner": "adapter-or-extension-after-consent", 

987 }, 

988 } 

989 if command in {"wrap", "work-block", "overnight"}: 

990 return { 

991 "command": command, 

992 "target": target, 

993 "base_branch": config.base_branch, 

994 "session": session_contract_as_dict( 

995 command=command, 

996 config=config, 

997 transport=transport, 

998 ), 

999 "execution": { 

1000 "runs_gates": False, 

1001 "creates_prs": False, 

1002 "merges": False, 

1003 "writes_reports": False, 

1004 "live_work_owner": "adapter-after-consent", 

1005 }, 

1006 } 

1007 if command in {"pr-loop", "review-cycle"}: 

1008 workflow = feedback_workflow_as_dict(config, command) 

1009 return { 

1010 "command": command, 

1011 "target": target, 

1012 "base_branch": config.base_branch, 

1013 "feedback_workflow": workflow, 

1014 "execution": { 

1015 "commits": False, 

1016 "pushes": False, 

1017 "posts_comments": False, 

1018 "merges": False, 

1019 "live_work_owner": "adapter-after-consent", 

1020 }, 

1021 } 

1022 if command in {"regression", "review-all-day"}: 

1023 return { 

1024 "command": command, 

1025 "target": target, 

1026 "base_branch": config.base_branch, 

1027 "scan": scan_contract_as_dict( 

1028 command=command, 

1029 config=config, 

1030 transport=transport, 

1031 ), 

1032 "execution": { 

1033 "edits_code": False, 

1034 "pushes": False, 

1035 "merges": False, 

1036 "writes_issues": False, 

1037 "live_work_owner": "adapter-after-consent", 

1038 }, 

1039 } 

1040 return {"command": command, "target": target} 

1041 

1042 

1043def feedback_workflow_as_dict(config: cfg.ProjectConfig, command: str) -> dict[str, Any]: 

1044 """Return command-specific feedback-loop policy with project overrides applied.""" 

1045 defaults = _DEFAULT_FEEDBACK_WORKFLOWS.get(command, {}) 

1046 policy = _feedback_workflow_policy(config).get(command, {}) 

1047 return _deep_merge(defaults, policy) 

1048 

1049 

1050def scan_contract_as_dict( 

1051 *, 

1052 command: str, 

1053 config: cfg.ProjectConfig, 

1054 transport: github_transport.GitHubTransport | None = None, 

1055) -> dict[str, Any]: 

1056 """Project-neutral scan-and-file contract for regression and review-all-day.""" 

1057 pack = config.policy_pack or {} 

1058 reports = pack.get("reports") if isinstance(pack.get("reports"), dict) else {} 

1059 scan = pack.get("scan") if isinstance(pack.get("scan"), dict) else {} 

1060 areas = scan.get("areas") if isinstance(scan.get("areas"), dict) else {} 

1061 issue_labels = scan.get("issue_labels") if isinstance(scan.get("issue_labels"), dict) else {} 

1062 github = transport.as_dict() if transport is not None else {} 

1063 base = { 

1064 "timezone": config.timezone, 

1065 "merge_window": config.merge_window, 

1066 "base_branch": config.base_branch, 

1067 "github_transport": github, 

1068 "reports": _report_destinations(reports), 

1069 "project_policy_sources": { 

1070 "scan": "policy_pack.scan", 

1071 "areas": "policy_pack.scan.areas", 

1072 "active_branch_patterns": "policy_pack.scan.active_branch_patterns", 

1073 "risk_globs": "knobs.tier3_globs", 

1074 "labels": "policy_pack.labels + policy_pack.scan.issue_labels", 

1075 "ci_workflows": "knobs.ci_workflows", 

1076 }, 

1077 "write_safety": { 

1078 "dry_run_no_writes": True, 

1079 "orchestrator_only_writes": True, 

1080 "code_mutation": False, 

1081 "pr_mutation": False, 

1082 "issue_write_requires_consent": True, 

1083 }, 

1084 "dedupe": { 

1085 "source": "policy_pack.scan.dedupe + canonical defaults", 

1086 "path_token_boundary": True, 

1087 "type_must_match": True, 

1088 "near_text_similarity": _scan_float(scan, "near_text_similarity", 0.6), 

1089 "open_duplicate": "skip", 

1090 "closed_duplicate": "promote-regression-of", 

1091 "lock": "mkdir", 

1092 }, 

1093 "reviewer_isolation": { 

1094 "parallel": True, 

1095 "no_cross_reading": True, 

1096 "orchestrator_collects_findings": True, 

1097 }, 

1098 "areas": [ 

1099 {"name": name, "paths": list(paths)} 

1100 for name, paths in sorted(areas.items()) 

1101 if isinstance(paths, list) 

1102 ], 

1103 "risk_globs": list(config.knobs.tier3_globs), 

1104 "issue_labels": { 

1105 key: list(value) 

1106 for key, value in sorted(issue_labels.items()) 

1107 if isinstance(value, list) 

1108 }, 

1109 "work_creation_policy": workcreation.contract_as_dict(), 

1110 } 

1111 if command == "regression": 

1112 base["regression"] = { 

1113 "scan_target": { 

1114 "source": "canonical base head", 

1115 "base_branch": config.base_branch, 

1116 "read_only_worktree": True, 

1117 "clean_tree_preflight": True, 

1118 }, 

1119 "scope": { 

1120 "default": "full", 

1121 "supported": ["full", "changed", "since"], 

1122 }, 

1123 "confidence_filter": { 

1124 "drop": ["low"], 

1125 "downgrade_blocker_when": "medium-confidence", 

1126 "file_only_when": ["high-confidence", "medium-security"], 

1127 }, 

1128 "issue_creation": { 

1129 "one_issue_per_finding": True, 

1130 "route_to": "ship", 

1131 "regression_of_line": "regression-of: #N", 

1132 "labels": issue_labels.get("regression", []), 

1133 }, 

1134 "final_report": [ 

1135 "raw_findings", 

1136 "after_confidence_filter", 

1137 "duplicates_skipped", 

1138 "promoted_regressions", 

1139 "issues_opened", 

1140 "ship_handoffs", 

1141 ], 

1142 } 

1143 elif command == "review-all-day": 

1144 base["review_all_day"] = { 

1145 "span": { 

1146 "timezone": config.timezone, 

1147 "merge_window": config.merge_window, 

1148 "days_are_inclusive_calendar_days": True, 

1149 "n_days_argument_covers_calendar_days": "N+1", 

1150 }, 

1151 "ref_scope": { 

1152 "branches": ["trunk", "active-work-branches"], 

1153 "active_branch_patterns": list(scan.get("active_branch_patterns") or ()), 

1154 "remote_refs_default": True, 

1155 "warn_on_stale_fetch": True, 

1156 }, 

1157 "strategy": { 

1158 "batch_threshold": _scan_int(scan, "batch_threshold", 5), 

1159 "fanout_when_commit_count_gt": _scan_int(scan, "batch_threshold", 5), 

1160 }, 

1161 "diff_truncation": { 

1162 "max_bytes": _scan_int(scan, "large_diff_max_bytes", 200000), 

1163 "boundary": "file", 

1164 }, 

1165 "finding_filter": { 

1166 "skip_minor": True, 

1167 "keep_minor_categories": ["security"], 

1168 "file_categories": ["bug-insert", "regression", "security", "config", 

1169 "test-coverage"], 

1170 }, 

1171 "issue_creation": { 

1172 "title_prefix": "[review-all-day] ", 

1173 "one_issue_per_serious_finding": True, 

1174 "labels": issue_labels.get("review-all-day", []), 

1175 }, 

1176 "final_report": [ 

1177 "commit_range", 

1178 "reviewers", 

1179 "findings", 

1180 "duplicates_skipped", 

1181 "issues_opened", 

1182 ], 

1183 } 

1184 return base 

1185 

1186 

1187def session_contract_as_dict( 

1188 *, 

1189 command: str, 

1190 config: cfg.ProjectConfig, 

1191 transport: github_transport.GitHubTransport | None = None, 

1192) -> dict[str, Any]: 

1193 """Project-neutral session workflow contract for session/work-block commands.""" 

1194 pack = config.policy_pack or {} 

1195 reports = pack.get("reports") if isinstance(pack.get("reports"), dict) else {} 

1196 github = transport.as_dict() if transport is not None else {} 

1197 base = { 

1198 "timezone": config.timezone, 

1199 "merge_window": config.merge_window, 

1200 "merge_window_mode": config.merge_window_mode, 

1201 "base_branch": config.base_branch, 

1202 "github_transport": github, 

1203 "reports": _report_destinations(reports), 

1204 "deferral_queue": _deferral_queue_as_dict(reports), 

1205 "run_ledger": ledger.ledger_contract_as_dict(config), 

1206 "project_policy_sources": { 

1207 "gates": list(config.gates), 

1208 "extensions": {slot: list(files) for slot, files in sorted(config.extensions.items())}, 

1209 "risk_rules": [rule.get("id") for rule in pack.get("risk_rules", []) 

1210 if isinstance(rule, dict)], 

1211 "source_of_truth_doc": config.knobs.sot_doc, 

1212 }, 

1213 } 

1214 if command in {"work-block", "overnight"}: 

1215 base["work_block"] = workblock.contract_as_dict( 

1216 config=config, 

1217 mode="overnight" if command == "overnight" else "daytime", 

1218 transport=github, 

1219 ) 

1220 if command == "wrap": 

1221 base["wrap"] = { 

1222 "workspace_preflight": { 

1223 "must_run_from_linked_worktree": True, 

1224 "abort_on_base_branch": True, 

1225 "git_dir_rule": "main worktree returns .git; linked worktree uses .git/worktrees", 

1226 }, 

1227 "quality_gates": { 

1228 "runner": "keel run-gates", 

1229 "changed_file_policy_source": "policy_pack + extension hooks", 

1230 }, 

1231 "commit": { 

1232 "format": "conventional-commits", 

1233 "supports_closes_issue": True, 

1234 }, 

1235 "pull_request": { 

1236 "ready_not_draft": True, 

1237 "base_branch": config.base_branch, 

1238 "requires_pr_write": True, 

1239 "body_sections": ["Summary", "Docs Impact", "Test Plan"], 

1240 }, 

1241 "recap": { 

1242 "report_key": "session", 

1243 "path": reports.get("session") or reports.get("wrap"), 

1244 "status": "configured" if reports.get("session") or reports.get("wrap") 

1245 else "unconfigured", 

1246 }, 

1247 } 

1248 elif command == "work-block": 

1249 base["daytime"] = { 

1250 "mode_source": { 

1251 "command": "keel work-block", 

1252 "shared_with_overnight": True, 

1253 }, 

1254 "queue": { 

1255 "source": "explicit issue numbers or project queue selector", 

1256 "deterministic_order": ( 

1257 "explicit numbers keep their provided order; selectors sort by policy" 

1258 ), 

1259 }, 

1260 "ship_handoff": { 

1261 "command": "ship", 

1262 "passes_operator_consent_scope": True, 

1263 "per_issue_worktree": True, 

1264 "refreshes_readiness_between_issues": True, 

1265 }, 

1266 "operator_control": { 

1267 "between_items": True, 

1268 "stop_on_consent_gap": True, 

1269 "stop_on_needs_input": True, 

1270 }, 

1271 "report": { 

1272 "day_key": "session", 

1273 "day_path": reports.get("session"), 

1274 "status": "configured" if reports.get("session") else "unconfigured", 

1275 }, 

1276 } 

1277 elif command == "overnight": 

1278 base["overnight"] = { 

1279 "mode_source": { 

1280 "command": "keel window", 

1281 "shared_with_ship": True, 

1282 "timezone": config.timezone, 

1283 "merge_window": config.merge_window, 

1284 }, 

1285 "merge_policy": { 

1286 "day": "ship may merge reviewed CI-green PRs inside the configured window", 

1287 "night": "no merge outside the configured window except true blockers", 

1288 "blocker_source": "ship blocker heuristics + project policy extensions", 

1289 "cannot_weaken_core_window": True, 

1290 }, 

1291 "queue": { 

1292 "source": "project policy or adapter query", 

1293 "tiers": ["T0-blocker", "T1-open-prs", "T2-coverage", "T3-ci-quality", 

1294 "T4-modernization", "T5-docs-backlog"], 

1295 }, 

1296 "ship_handoff": { 

1297 "command": "ship", 

1298 "passes_operator_consent_scope": True, 

1299 "per_issue_worktree": True, 

1300 "shared_work_block_contract": True, 

1301 }, 

1302 "report": { 

1303 "night_key": "overnight", 

1304 "day_key": "session", 

1305 "night_path": reports.get("overnight") or reports.get("morning"), 

1306 "day_path": reports.get("session"), 

1307 "status": "configured" if any( 

1308 reports.get(key) for key in ("overnight", "morning", "session") 

1309 ) else "unconfigured", 

1310 }, 

1311 "stop_conditions": [ 

1312 "merge-window-close", 

1313 "time-budget-exhausted", 

1314 "max-items-reached", 

1315 "hard-blocker", 

1316 "three-consecutive-unresolved-ci-failures", 

1317 "user-cancelled", 

1318 ], 

1319 } 

1320 return base 

1321 

1322 

1323def morning_contract_as_dict( 

1324 *, 

1325 config: cfg.ProjectConfig, 

1326 evaluation: runtime.CapabilityEvaluation | None = None, 

1327 transport: github_transport.GitHubTransport | None = None, 

1328) -> dict[str, Any]: 

1329 """Project-neutral morning briefing contract for adapters and dry-run output.""" 

1330 pack = config.policy_pack or {} 

1331 reports = pack.get("reports") if isinstance(pack.get("reports"), dict) else {} 

1332 health = pack.get("health_providers") if isinstance(pack.get("health_providers"), dict) else {} 

1333 github = transport.as_dict() if transport is not None else {} 

1334 github_status = "available" if github.get("transport") not in {None, "none"} else "degraded" 

1335 return { 

1336 "timezone": config.timezone, 

1337 "merge_window": config.merge_window, 

1338 "base_branch": config.base_branch, 

1339 "sections": [ 

1340 { 

1341 "id": "deferrals", 

1342 "source": "shared_queue", 

1343 "status": "configured" if reports.get("deferrals") else "unconfigured", 

1344 "path": reports.get("deferrals"), 

1345 }, 

1346 { 

1347 "id": "shipped_since_last_brief", 

1348 "source": "github", 

1349 "transport": github.get("transport"), 

1350 "status": github_status, 

1351 }, 

1352 { 

1353 "id": "github_status", 

1354 "source": "github", 

1355 "transport": github.get("transport"), 

1356 "status": github_status, 

1357 }, 

1358 { 

1359 "id": "project_health", 

1360 "source": "policy_pack.health_providers", 

1361 "status": "configured" if health else "unconfigured", 

1362 }, 

1363 { 

1364 "id": "ranked_focus", 

1365 "source": "policy_pack + github", 

1366 "status": "configured" if _priority_sources(config, reports) else "unconfigured", 

1367 }, 

1368 ], 

1369 "health_providers": [ 

1370 _health_provider_as_dict(name, provider, evaluation) 

1371 for name, provider in sorted(health.items()) 

1372 if isinstance(provider, dict) 

1373 ], 

1374 "priority_sources": _priority_sources(config, reports), 

1375 "reports": _report_destinations(reports), 

1376 "deferral_queue": _deferral_queue_as_dict(reports), 

1377 "run_ledger": ledger.ledger_contract_as_dict(config), 

1378 "missing_optional_policy": "unavailable-not-success", 

1379 } 

1380 

1381 

1382def _health_provider_as_dict( 

1383 name: str, 

1384 provider: dict[str, Any], 

1385 evaluation: runtime.CapabilityEvaluation | None, 

1386) -> dict[str, Any]: 

1387 required = tuple(provider.get("required_capabilities") or ()) 

1388 optional = tuple(provider.get("optional_capabilities") or ()) 

1389 missing_required = _missing_capabilities(required, evaluation) 

1390 missing_optional = _missing_capabilities(optional, evaluation) 

1391 status = "available" 

1392 if missing_required: 

1393 status = "blocked" 

1394 elif missing_optional: 

1395 status = "unavailable" 

1396 elif provider.get("command") is None and provider.get("kind") == "project-command": 

1397 status = "unavailable" 

1398 return { 

1399 "name": name, 

1400 "kind": provider.get("kind"), 

1401 "command": provider.get("command"), 

1402 "reports": list(provider.get("reports") or ()), 

1403 "required_capabilities": list(required), 

1404 "optional_capabilities": list(optional), 

1405 "missing_required_capabilities": list(missing_required), 

1406 "missing_optional_capabilities": list(missing_optional), 

1407 "status": status, 

1408 } 

1409 

1410 

1411def _missing_capabilities( 

1412 names: tuple[str, ...], 

1413 evaluation: runtime.CapabilityEvaluation | None, 

1414) -> tuple[str, ...]: 

1415 if evaluation is None: 

1416 return () 

1417 missing = set(evaluation.missing_required) | set(evaluation.missing_optional) 

1418 return tuple(name for name in names if name in missing) 

1419 

1420 

1421def _priority_sources(config: cfg.ProjectConfig, reports: dict[str, Any]) -> list[dict[str, Any]]: 

1422 sources: list[dict[str, Any]] = [] 

1423 if config.knobs.sot_doc: 

1424 sources.append({"id": "source_of_truth", "path": config.knobs.sot_doc}) 

1425 if reports.get("priorities"): 

1426 sources.append({"id": "priorities_report", "path": reports["priorities"]}) 

1427 if config.knobs.ci_workflows: 

1428 sources.append({"id": "ci_workflows", "names": sorted(config.knobs.ci_workflows)}) 

1429 return sources 

1430 

1431 

1432def _report_destinations(reports: dict[str, Any]) -> dict[str, dict[str, Any]]: 

1433 return { 

1434 key: { 

1435 "path": value, 

1436 "write_status": "skipped-in-dry-run", 

1437 } 

1438 for key, value in sorted(reports.items()) 

1439 } 

1440 

1441 

1442def _deferral_queue_as_dict(reports: dict[str, Any]) -> dict[str, Any]: 

1443 return { 

1444 "source": "policy_pack.reports.deferrals", 

1445 "path": reports.get("deferrals"), 

1446 "status": "configured" if reports.get("deferrals") else "unconfigured", 

1447 "shared_with": ["ship", "overnight", "wrap", "morning"], 

1448 } 

1449 

1450 

1451def _feedback_workflow_policy(config: cfg.ProjectConfig) -> dict[str, dict[str, Any]]: 

1452 pack = config.policy_pack or {} 

1453 policy = pack.get("workflow_policies") 

1454 if not isinstance(policy, dict): 

1455 return {} 

1456 return { 

1457 name: value 

1458 for name, value in policy.items() 

1459 if isinstance(name, str) and isinstance(value, dict) 

1460 } 

1461 

1462 

1463def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]: 

1464 merged: dict[str, Any] = {} 

1465 for key, value in base.items(): 

1466 if isinstance(value, dict): 

1467 merged[key] = _deep_merge(value, {}) 

1468 elif isinstance(value, list): 

1469 merged[key] = list(value) 

1470 else: 

1471 merged[key] = value 

1472 for key, value in override.items(): 

1473 if isinstance(value, dict) and isinstance(merged.get(key), dict): 

1474 merged[key] = _deep_merge(merged[key], value) 

1475 elif isinstance(value, list): 

1476 merged[key] = list(value) 

1477 else: 

1478 merged[key] = value 

1479 return merged 

1480 

1481 

1482def _scan_int(scan: dict[str, Any], key: str, default: int) -> int: 

1483 value = scan.get(key) 

1484 return value if isinstance(value, int) else default 

1485 

1486 

1487def _scan_float(scan: dict[str, Any], key: str, default: float) -> float: 

1488 value = scan.get(key) 

1489 return value if isinstance(value, int | float) else default 

1490 

1491 

1492def _target_identifier(target: str | None) -> str: 

1493 if not target: 

1494 return "selected-issue" 

1495 match = re.search(r"#?(\d+)", target) 

1496 if match: 

1497 return match.group(1) 

1498 return re.sub(r"[^a-z0-9]+", "-", target.lower()).strip("-") or "selected-issue" 

1499 

1500 

1501def _adapter_steps(command: str) -> list[dict[str, Any]]: 

1502 path = Path(install.ADAPTERS) / f"{command}.md" 

1503 if not path.exists(): 

1504 return [] 

1505 steps: list[dict[str, Any]] = [] 

1506 for line in path.read_text(encoding="utf-8").splitlines(): 

1507 match = _STEP_RE.match(line) 

1508 if not match: 

1509 continue 

1510 raw_id = match.group("id").strip() 

1511 step_id = raw_id.lower().replace(" ", "-").replace(".", "-") 

1512 steps.append({ 

1513 "step_id": step_id, 

1514 "step_name": match.group("name").strip() or raw_id, 

1515 "agentic": "agent" in match.group("name").lower(), 

1516 "slot": None, 

1517 "source": "adapter", 

1518 }) 

1519 return steps 

1520 

1521 

1522def _finding_as_dict(finding) -> dict[str, Any]: 

1523 tag = provenance.normalize_tag( 

1524 getattr(finding, "provenance", None), 

1525 fallback_agent=finding.source, 

1526 step_id="finding", 

1527 ) 

1528 return { 

1529 "severity": finding.severity, 

1530 "message": finding.message, 

1531 "source": finding.source, 

1532 "path": finding.path, 

1533 "line": finding.line, 

1534 "anchorable": finding.anchorable, 

1535 "provenance": tag, 

1536 }