algo-cli-runtime 0.14.0__py3-none-any.whl

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 (237) hide show
  1. algo_cli/__init__.py +3 -0
  2. algo_cli/__main__.py +7 -0
  3. algo_cli/_internal/__init__.py +12 -0
  4. algo_cli/_internal/policy_chain.py +259 -0
  5. algo_cli/action_registry.py +1047 -0
  6. algo_cli/agent_blocks.py +550 -0
  7. algo_cli/agent_pipeline.py +1457 -0
  8. algo_cli/agent_threads.py +308 -0
  9. algo_cli/animations.py +316 -0
  10. algo_cli/cache_admission.py +209 -0
  11. algo_cli/capability_mask.py +66 -0
  12. algo_cli/chat_protocol.py +116 -0
  13. algo_cli/chatgpt_auth.py +510 -0
  14. algo_cli/chatgpt_client.py +657 -0
  15. algo_cli/code_rag.py +479 -0
  16. algo_cli/config.py +651 -0
  17. algo_cli/context_budget.py +679 -0
  18. algo_cli/credential_helpers.py +315 -0
  19. algo_cli/deliberation.py +29 -0
  20. algo_cli/display.py +1470 -0
  21. algo_cli/evals/__init__.py +21 -0
  22. algo_cli/evals/algorithm_effectiveness.py +560 -0
  23. algo_cli/evals/competitive_harness_rating.py +702 -0
  24. algo_cli/evals/cot_quality.py +220 -0
  25. algo_cli/evals/harness_retrieval_benchmark.py +401 -0
  26. algo_cli/evals/performance_regression.py +136 -0
  27. algo_cli/evals/scorecard_grading.py +308 -0
  28. algo_cli/evals/session_distribution.py +84 -0
  29. algo_cli/execution_guardrails.py +806 -0
  30. algo_cli/extensions_manifest.py +84 -0
  31. algo_cli/git_evidence.py +227 -0
  32. algo_cli/google_workspace.py +407 -0
  33. algo_cli/google_workspace_auth.py +523 -0
  34. algo_cli/harness.py +2587 -0
  35. algo_cli/identity.py +557 -0
  36. algo_cli/index_compute_lab.py +228 -0
  37. algo_cli/inference_harness.py +70 -0
  38. algo_cli/intelligence/__init__.py +1103 -0
  39. algo_cli/intelligence/acrobat_config.py +307 -0
  40. algo_cli/intelligence/acrobat_manifests.py +338 -0
  41. algo_cli/intelligence/acrobat_models.py +195 -0
  42. algo_cli/intelligence/acrobat_pipeline.py +295 -0
  43. algo_cli/intelligence/acrobat_runtime.py +302 -0
  44. algo_cli/intelligence/acrobat_security.py +261 -0
  45. algo_cli/intelligence/acrobat_workflows.py +226 -0
  46. algo_cli/intelligence/actionability.py +165 -0
  47. algo_cli/intelligence/adversarial_audit.py +136 -0
  48. algo_cli/intelligence/agent_arena.py +92 -0
  49. algo_cli/intelligence/agent_benchmark.py +236 -0
  50. algo_cli/intelligence/agent_runtime.py +171 -0
  51. algo_cli/intelligence/agents_as_tools.py +70 -0
  52. algo_cli/intelligence/artifact_binding.py +80 -0
  53. algo_cli/intelligence/autonomous_engineer.py +1976 -0
  54. algo_cli/intelligence/backpressure.py +99 -0
  55. algo_cli/intelligence/bloom_filter.py +186 -0
  56. algo_cli/intelligence/bonferroni.py +66 -0
  57. algo_cli/intelligence/boundary_compaction.py +98 -0
  58. algo_cli/intelligence/catalog_verifier.py +172 -0
  59. algo_cli/intelligence/cavecrew.py +118 -0
  60. algo_cli/intelligence/changelog.py +176 -0
  61. algo_cli/intelligence/checkpoint_resume.py +92 -0
  62. algo_cli/intelligence/circuit_breaker.py +88 -0
  63. algo_cli/intelligence/clarification_gate.py +101 -0
  64. algo_cli/intelligence/code_graph.py +180 -0
  65. algo_cli/intelligence/coderank.py +97 -0
  66. algo_cli/intelligence/consistent_hash.py +150 -0
  67. algo_cli/intelligence/consortium_synthesis.py +139 -0
  68. algo_cli/intelligence/construction/__init__.py +241 -0
  69. algo_cli/intelligence/construction/common.py +273 -0
  70. algo_cli/intelligence/construction/documents.py +496 -0
  71. algo_cli/intelligence/construction/labor_units.py +1395 -0
  72. algo_cli/intelligence/construction/payments.py +470 -0
  73. algo_cli/intelligence/construction/risk.py +784 -0
  74. algo_cli/intelligence/content_extractor.py +132 -0
  75. algo_cli/intelligence/context_adaptive.py +102 -0
  76. algo_cli/intelligence/context_ops.py +95 -0
  77. algo_cli/intelligence/count_min.py +145 -0
  78. algo_cli/intelligence/cow_state.py +103 -0
  79. algo_cli/intelligence/critic_loop.py +119 -0
  80. algo_cli/intelligence/cross_source.py +113 -0
  81. algo_cli/intelligence/daemon_mode.py +99 -0
  82. algo_cli/intelligence/dag_orchestration.py +151 -0
  83. algo_cli/intelligence/deep_research.py +155 -0
  84. algo_cli/intelligence/degenerate_detector.py +78 -0
  85. algo_cli/intelligence/delta_report.py +92 -0
  86. algo_cli/intelligence/discovery_event_log.py +92 -0
  87. algo_cli/intelligence/document_ingest.py +298 -0
  88. algo_cli/intelligence/dual_layer_validate.py +151 -0
  89. algo_cli/intelligence/echo_fidelity.py +73 -0
  90. algo_cli/intelligence/ema_tuning.py +104 -0
  91. algo_cli/intelligence/event_log.py +92 -0
  92. algo_cli/intelligence/evidence_graph.py +114 -0
  93. algo_cli/intelligence/extension_host.py +162 -0
  94. algo_cli/intelligence/extension_manifest.py +115 -0
  95. algo_cli/intelligence/falsification_suite.py +178 -0
  96. algo_cli/intelligence/finance/__init__.py +169 -0
  97. algo_cli/intelligence/finance/anomalies.py +135 -0
  98. algo_cli/intelligence/finance/ap_ar.py +351 -0
  99. algo_cli/intelligence/finance/cash.py +162 -0
  100. algo_cli/intelligence/finance/close.py +332 -0
  101. algo_cli/intelligence/finance/common.py +244 -0
  102. algo_cli/intelligence/finance/construction.py +135 -0
  103. algo_cli/intelligence/finance/controls.py +172 -0
  104. algo_cli/intelligence/finance/evidence.py +119 -0
  105. algo_cli/intelligence/finance/exceptions.py +157 -0
  106. algo_cli/intelligence/finance/reconciliations.py +254 -0
  107. algo_cli/intelligence/finance/revenue.py +109 -0
  108. algo_cli/intelligence/finance/tax.py +74 -0
  109. algo_cli/intelligence/finance/workpapers.py +111 -0
  110. algo_cli/intelligence/finding_record.py +120 -0
  111. algo_cli/intelligence/flow_dag.py +267 -0
  112. algo_cli/intelligence/gatherer.py +223 -0
  113. algo_cli/intelligence/golden_master.py +98 -0
  114. algo_cli/intelligence/graph_rag.py +195 -0
  115. algo_cli/intelligence/group_chat.py +143 -0
  116. algo_cli/intelligence/hash_dedup.py +145 -0
  117. algo_cli/intelligence/hyperloglog.py +128 -0
  118. algo_cli/intelligence/incremental_index.py +316 -0
  119. algo_cli/intelligence/index_store.py +16 -0
  120. algo_cli/intelligence/iteration_plan.py +133 -0
  121. algo_cli/intelligence/kernel_plugins.py +167 -0
  122. algo_cli/intelligence/lesson_catalog.py +135 -0
  123. algo_cli/intelligence/llm_fallback.py +169 -0
  124. algo_cli/intelligence/log2_histogram.py +267 -0
  125. algo_cli/intelligence/lsp_integration.py +147 -0
  126. algo_cli/intelligence/memory_evolution.py +117 -0
  127. algo_cli/intelligence/minhash_lsh.py +182 -0
  128. algo_cli/intelligence/multi_model_score.py +174 -0
  129. algo_cli/intelligence/multi_tier_grade.py +211 -0
  130. algo_cli/intelligence/negative_controls.py +113 -0
  131. algo_cli/intelligence/numeric_clamp.py +63 -0
  132. algo_cli/intelligence/occ_editor.py +66 -0
  133. algo_cli/intelligence/output_normalize.py +112 -0
  134. algo_cli/intelligence/parallel_delegation.py +98 -0
  135. algo_cli/intelligence/parallel_fanout.py +104 -0
  136. algo_cli/intelligence/permission_modes.py +105 -0
  137. algo_cli/intelligence/pre_push_gate.py +68 -0
  138. algo_cli/intelligence/prefetch.py +171 -0
  139. algo_cli/intelligence/process_framework.py +217 -0
  140. algo_cli/intelligence/project_graph.py +387 -0
  141. algo_cli/intelligence/query_expansion.py +146 -0
  142. algo_cli/intelligence/ralph_loop.py +117 -0
  143. algo_cli/intelligence/rate_limiter.py +153 -0
  144. algo_cli/intelligence/refactor_transaction.py +94 -0
  145. algo_cli/intelligence/research_workspace.py +108 -0
  146. algo_cli/intelligence/retraction_ledger.py +72 -0
  147. algo_cli/intelligence/saga_pattern.py +88 -0
  148. algo_cli/intelligence/session_fork.py +100 -0
  149. algo_cli/intelligence/shadow_editor.py +67 -0
  150. algo_cli/intelligence/shell_session.py +213 -0
  151. algo_cli/intelligence/source_registry.py +143 -0
  152. algo_cli/intelligence/spawn_scales.py +99 -0
  153. algo_cli/intelligence/stat_stability.py +104 -0
  154. algo_cli/intelligence/structural_validator.py +148 -0
  155. algo_cli/intelligence/subagent_spawner.py +111 -0
  156. algo_cli/intelligence/symmetric_verify.py +70 -0
  157. algo_cli/intelligence/task_classifier.py +129 -0
  158. algo_cli/intelligence/team_execution.py +122 -0
  159. algo_cli/intelligence/tiered_access.py +121 -0
  160. algo_cli/intelligence/utility_registry.py +159 -0
  161. algo_cli/intuition_engine.py +560 -0
  162. algo_cli/intuition_injector.py +82 -0
  163. algo_cli/kernels/__init__.py +5 -0
  164. algo_cli/kernels/manifest.py +763 -0
  165. algo_cli/main.py +3903 -0
  166. algo_cli/memory_candidates.py +541 -0
  167. algo_cli/memory_echo_veil.py +394 -0
  168. algo_cli/memory_runtime.py +112 -0
  169. algo_cli/model_info.py +548 -0
  170. algo_cli/model_profile.py +160 -0
  171. algo_cli/model_routing.py +74 -0
  172. algo_cli/oneshot.py +331 -0
  173. algo_cli/perf_telemetry.py +389 -0
  174. algo_cli/plugins.py +245 -0
  175. algo_cli/private_event_store.py +654 -0
  176. algo_cli/quantization/__init__.py +24 -0
  177. algo_cli/quantization/lloyd_max.py +98 -0
  178. algo_cli/quantization/turbo_quant.py +308 -0
  179. algo_cli/reasoning/__init__.py +46 -0
  180. algo_cli/reasoning/combinatorial.py +356 -0
  181. algo_cli/reasoning/graph_of_thought.py +297 -0
  182. algo_cli/reasoning/mcts.py +220 -0
  183. algo_cli/reasoning/neuro_symbolic.py +250 -0
  184. algo_cli/reasoning/react.py +246 -0
  185. algo_cli/reasoning/reflexion.py +225 -0
  186. algo_cli/reasoning/tree_of_thought.py +241 -0
  187. algo_cli/reasoning_bridge.py +150 -0
  188. algo_cli/reconciliation.py +284 -0
  189. algo_cli/reflex.py +385 -0
  190. algo_cli/resources/docs/ALGO.md +13958 -0
  191. algo_cli/resources/docs/algo-cli-algorithm-evidence-contract.md +60 -0
  192. algo_cli/resources/docs/algo-cli-execution-verification-contract.md +59 -0
  193. algo_cli/resources/docs/algo-cli-memory-lifecycle-contract.md +72 -0
  194. algo_cli/resources/docs/harness-extension-cleanup-recommendation.md +41 -0
  195. algo_cli/resources/docs/index-compute-lab-integration.md +32 -0
  196. algo_cli/resources/docs/inference-harness-loop-blueprint-2026-06.md +55 -0
  197. algo_cli/resources/docs/main-split-map.md +35 -0
  198. algo_cli/resources/docs/privacy-and-context.md +48 -0
  199. algo_cli/resources/docs/reflex-loop-v0.2.md +354 -0
  200. algo_cli/resources/skills/README.md +26 -0
  201. algo_cli/resources/skills/algo-cli.md +59 -0
  202. algo_cli/resources/skills/edit-file-precision.md +49 -0
  203. algo_cli/resources/skills/harness-search-first.md +47 -0
  204. algo_cli/resources/skills/memory-recall-ritual.md +51 -0
  205. algo_cli/resources/skills/qol-algorithms.md +224 -0
  206. algo_cli/resources/skills/smart-error-recovery.md +56 -0
  207. algo_cli/resources/skills/tool-selection-cheatsheet.md +65 -0
  208. algo_cli/retrieval_algorithms.py +127 -0
  209. algo_cli/runtime_qos.py +236 -0
  210. algo_cli/runtime_services.py +320 -0
  211. algo_cli/session_commands.py +95 -0
  212. algo_cli/session_mode.py +113 -0
  213. algo_cli/skills.py +430 -0
  214. algo_cli/slash_dispatch.py +1265 -0
  215. algo_cli/small_context.py +206 -0
  216. algo_cli/spawn_budget.py +89 -0
  217. algo_cli/task_ledger.py +84 -0
  218. algo_cli/task_router.py +197 -0
  219. algo_cli/tool_context.py +94 -0
  220. algo_cli/tool_contract.py +99 -0
  221. algo_cli/tool_policy.py +357 -0
  222. algo_cli/tool_runtime.py +647 -0
  223. algo_cli/tools.py +3056 -0
  224. algo_cli/url_scheme.py +174 -0
  225. algo_cli/verify.py +154 -0
  226. algo_cli/version_manifest.py +178 -0
  227. algo_cli/vision_screenshot_verify.py +76 -0
  228. algo_cli/workspace_resolver.py +68 -0
  229. algo_cli/x_account.py +209 -0
  230. algo_cli/xai_auth.py +374 -0
  231. algo_cli/xai_client.py +600 -0
  232. algo_cli_runtime-0.14.0.dist-info/METADATA +369 -0
  233. algo_cli_runtime-0.14.0.dist-info/RECORD +237 -0
  234. algo_cli_runtime-0.14.0.dist-info/WHEEL +4 -0
  235. algo_cli_runtime-0.14.0.dist-info/entry_points.txt +3 -0
  236. algo_cli_runtime-0.14.0.dist-info/licenses/LICENSE +21 -0
  237. ollama_cli/__init__.py +67 -0
@@ -0,0 +1,84 @@
1
+ """Extension version manifest (J14).
2
+
3
+ Sibling to ``version_manifest.py``. Inspired by macOS SystemVersion.plist and
4
+ Docker componentsVersion.json: one durable, queryable truth for plugin/helper
5
+ components instead of scattering version facts across status output.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import shutil
11
+ import time
12
+ from dataclasses import asdict, dataclass, field
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from .config import CONFIG_DIR
17
+
18
+ EXTENSIONS_VERSION_FILE = CONFIG_DIR / "extensions_version.json"
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class ExtensionComponent:
23
+ name: str
24
+ kind: str
25
+ version: str = ""
26
+ path: str = ""
27
+ status: str = "unknown"
28
+
29
+ def as_dict(self) -> dict[str, str]:
30
+ return asdict(self)
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class ExtensionsManifest:
35
+ generated_at: float
36
+ components: tuple[ExtensionComponent, ...] = field(default_factory=tuple)
37
+
38
+ def as_dict(self) -> dict[str, Any]:
39
+ return {"generated_at": self.generated_at, "components": [c.as_dict() for c in self.components]}
40
+
41
+ def to_json(self) -> str:
42
+ return json.dumps(self.as_dict(), indent=2, sort_keys=True)
43
+
44
+
45
+ def _plugin_components() -> list[ExtensionComponent]:
46
+ try:
47
+ from . import plugins
48
+ manifests = plugins.discover_plugins()
49
+ return [
50
+ ExtensionComponent(
51
+ name=m.name,
52
+ kind="plugin",
53
+ version=m.version,
54
+ path=str(m.path),
55
+ status="discovered",
56
+ )
57
+ for m in manifests
58
+ ]
59
+ except Exception:
60
+ return []
61
+
62
+
63
+ def _binary_component(name: str) -> ExtensionComponent:
64
+ found = shutil.which(name)
65
+ return ExtensionComponent(name=name, kind="binary", path=found or "", status="ready" if found else "missing")
66
+
67
+
68
+ def build_extensions_manifest() -> ExtensionsManifest:
69
+ components: list[ExtensionComponent] = []
70
+ components.extend(_plugin_components())
71
+ for binary in ("ollama", "git", "gh", "lms"):
72
+ components.append(_binary_component(binary))
73
+ return ExtensionsManifest(generated_at=time.time(), components=tuple(components))
74
+
75
+
76
+ def save_extensions_manifest(path: Path | None = None) -> ExtensionsManifest:
77
+ manifest = build_extensions_manifest()
78
+ target = path or EXTENSIONS_VERSION_FILE
79
+ target.parent.mkdir(parents=True, exist_ok=True)
80
+ target.write_text(manifest.to_json(), encoding="utf-8")
81
+ return manifest
82
+
83
+
84
+ __all__ = ["ExtensionComponent", "ExtensionsManifest", "build_extensions_manifest", "save_extensions_manifest", "EXTENSIONS_VERSION_FILE"]
@@ -0,0 +1,227 @@
1
+ """Bounded Git evidence capture for agent pipeline verification."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import subprocess
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+
10
+
11
+ MAX_STATUS_LINES = 40
12
+ MAX_TRACKED_DIFF_CHARS = 8_000
13
+ MAX_UNTRACKED_FILES = 30
14
+ _HASH_CHUNK_BYTES = 1024 * 1024
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class GitSnapshot:
19
+ """Immutable Git state with bounded display output and full-state digests."""
20
+
21
+ available: bool
22
+ error: str | None
23
+ head: str | None
24
+ status: str
25
+ tracked_diff: str
26
+ untracked_files: tuple[str, ...]
27
+ tracked_diff_digest: str = ""
28
+ untracked_digest: str = ""
29
+ untracked_total: int = 0
30
+
31
+
32
+ def _digest(text: str) -> str:
33
+ return hashlib.sha256(text.encode("utf-8", errors="replace")).hexdigest()
34
+
35
+
36
+ def _untracked_state_digest(cwd: str | None, paths: tuple[str, ...]) -> str:
37
+ """Hash untracked names and contents so edits to an existing file are visible."""
38
+
39
+ if not paths:
40
+ return _digest("")
41
+ root = Path(cwd or ".").expanduser().resolve()
42
+ digest = hashlib.sha256()
43
+ for relative in sorted(paths):
44
+ digest.update(relative.encode("utf-8", errors="surrogateescape"))
45
+ digest.update(b"\0")
46
+ candidate = (root / relative).resolve(strict=False)
47
+ try:
48
+ candidate.relative_to(root)
49
+ except ValueError:
50
+ digest.update(b"outside-workspace\0")
51
+ continue
52
+ try:
53
+ if candidate.is_symlink():
54
+ digest.update(b"symlink\0")
55
+ digest.update(candidate.readlink().as_posix().encode("utf-8", errors="surrogateescape"))
56
+ elif candidate.is_file():
57
+ digest.update(b"file\0")
58
+ with candidate.open("rb") as handle:
59
+ while chunk := handle.read(_HASH_CHUNK_BYTES):
60
+ digest.update(chunk)
61
+ else:
62
+ digest.update(b"missing-or-special\0")
63
+ except OSError as exc:
64
+ digest.update(f"unreadable:{type(exc).__name__}".encode("ascii"))
65
+ digest.update(b"\0")
66
+ return digest.hexdigest()
67
+
68
+
69
+ def _run_git(args: list[str], cwd: str | None = None, timeout: int = 20) -> tuple[int, str]:
70
+ workdir = Path(cwd or ".").expanduser().resolve()
71
+ try:
72
+ proc = subprocess.run(
73
+ ["git", *args],
74
+ cwd=workdir,
75
+ capture_output=True,
76
+ text=True,
77
+ encoding="utf-8",
78
+ errors="replace",
79
+ timeout=timeout,
80
+ )
81
+ except subprocess.TimeoutExpired:
82
+ return 124, f"git command timed out after {timeout} seconds"
83
+ except Exception as exc:
84
+ return 1, f"git command failed: {exc}"
85
+ output = (proc.stdout or proc.stderr or "").strip()
86
+ return proc.returncode, output
87
+
88
+
89
+ def _cap_lines(text: str, limit: int) -> str:
90
+ lines = text.splitlines()
91
+ if len(lines) <= limit:
92
+ return text
93
+ return "\n".join(lines[:limit]) + f"\n... ({len(lines) - limit} more lines omitted)"
94
+
95
+
96
+ def _cap_chars(text: str, limit: int) -> str:
97
+ if len(text) <= limit:
98
+ return text
99
+ return text[:limit] + f"\n... ({len(text) - limit} more characters omitted)"
100
+
101
+
102
+ def capture_git_snapshot(cwd: str | None = None) -> GitSnapshot:
103
+ """Capture repository state without allowing display caps to hide changes."""
104
+
105
+ rc, in_tree = _run_git(["rev-parse", "--is-inside-work-tree"], cwd)
106
+ if rc != 0 or in_tree.lower() != "true":
107
+ return GitSnapshot(
108
+ available=False,
109
+ error=in_tree or "not a Git repository",
110
+ head=None,
111
+ status="",
112
+ tracked_diff="",
113
+ untracked_files=(),
114
+ )
115
+
116
+ head_rc, head_output = _run_git(["rev-parse", "--verify", "HEAD"], cwd)
117
+ head = head_output if head_rc == 0 else None
118
+
119
+ status_rc, full_status = _run_git(["status", "--short", "--branch"], cwd)
120
+ diff_rc, full_diff = _run_git(["diff", "--no-ext-diff", "HEAD"], cwd)
121
+ untracked_rc, untracked_output = _run_git(["ls-files", "--others", "--exclude-standard"], cwd)
122
+ if status_rc != 0 or diff_rc != 0 or untracked_rc != 0:
123
+ error = full_status if status_rc != 0 else full_diff if diff_rc != 0 else untracked_output
124
+ return GitSnapshot(
125
+ available=False,
126
+ error=error or "unable to capture Git state",
127
+ head=head,
128
+ status="",
129
+ tracked_diff="",
130
+ untracked_files=(),
131
+ )
132
+
133
+ all_untracked = tuple(line.strip() for line in untracked_output.splitlines() if line.strip())
134
+ return GitSnapshot(
135
+ available=True,
136
+ error=None,
137
+ head=head,
138
+ status=_cap_lines(full_status, MAX_STATUS_LINES),
139
+ tracked_diff=_cap_chars(full_diff, MAX_TRACKED_DIFF_CHARS),
140
+ untracked_files=all_untracked[:MAX_UNTRACKED_FILES],
141
+ tracked_diff_digest=_digest(full_diff),
142
+ untracked_digest=_untracked_state_digest(cwd, all_untracked),
143
+ untracked_total=len(all_untracked),
144
+ )
145
+
146
+
147
+ def _state_changed(before: GitSnapshot, after: GitSnapshot) -> bool:
148
+ return (
149
+ before.tracked_diff_digest != after.tracked_diff_digest
150
+ or before.untracked_digest != after.untracked_digest
151
+ )
152
+
153
+
154
+ def _baseline_is_clean(snapshot: GitSnapshot) -> bool:
155
+ return (
156
+ snapshot.tracked_diff_digest == _digest("")
157
+ and snapshot.untracked_digest == _digest("")
158
+ )
159
+
160
+
161
+ def snapshot_is_clean(snapshot: GitSnapshot) -> bool:
162
+ """Return whether an available snapshot has no tracked or untracked changes."""
163
+
164
+ return snapshot.available and _baseline_is_clean(snapshot)
165
+
166
+
167
+ def _format_untracked(snapshot: GitSnapshot) -> str:
168
+ if not snapshot.untracked_files:
169
+ return "(none)"
170
+ shown = "\n".join(snapshot.untracked_files)
171
+ omitted = snapshot.untracked_total - len(snapshot.untracked_files)
172
+ return shown if omitted <= 0 else f"{shown}\n... ({omitted} more files omitted)"
173
+
174
+
175
+ def format_git_evidence(before: GitSnapshot, after: GitSnapshot) -> str:
176
+ """Format evidence conservatively for review/final block context."""
177
+
178
+ if not before.available or not after.available:
179
+ return f"Git evidence unavailable.\nReason: {after.error or before.error or 'unknown error'}"
180
+
181
+ if before.head != after.head:
182
+ return (
183
+ "ATTRIBUTION UNSAFE: Repository HEAD changed during block execution.\n"
184
+ f"Before HEAD: {before.head or '(none)'}\nAfter HEAD: {after.head or '(none)'}\n"
185
+ "Working-tree evidence cannot be reliably attributed to this block."
186
+ )
187
+
188
+ if not _state_changed(before, after):
189
+ if _baseline_is_clean(before):
190
+ return "No Git changes were introduced during this block (clean baseline maintained)."
191
+ return "No Git delta was introduced during this block (pre-existing dirty state unchanged)."
192
+
193
+ details = (
194
+ f"Working tree status after execution:\n{after.status or '(clean)'}\n\n"
195
+ f"Tracked diff after execution:\n{after.tracked_diff or '(none)'}\n\n"
196
+ f"Untracked files after execution:\n{_format_untracked(after)}"
197
+ )
198
+ if _baseline_is_clean(before):
199
+ return f"Verified Git state change introduced during this block.\n\n{details}"
200
+ return (
201
+ "Git state changed during this block on a previously dirty working tree.\n"
202
+ "Attribution is conservative: verify changed sections before claiming implementation.\n\n"
203
+ f"{details}"
204
+ )
205
+
206
+
207
+ def has_verified_delta(before: GitSnapshot, after: GitSnapshot) -> bool:
208
+ """Return whether a clean-baseline delta can be attributed to the block."""
209
+
210
+ return (
211
+ before.available
212
+ and after.available
213
+ and before.head == after.head
214
+ and _baseline_is_clean(before)
215
+ and _state_changed(before, after)
216
+ )
217
+
218
+
219
+ def has_observed_delta(before: GitSnapshot, after: GitSnapshot) -> bool:
220
+ """Return whether repository state changed without crossing a HEAD boundary."""
221
+
222
+ return (
223
+ before.available
224
+ and after.available
225
+ and before.head == after.head
226
+ and _state_changed(before, after)
227
+ )