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.
- algo_cli/__init__.py +3 -0
- algo_cli/__main__.py +7 -0
- algo_cli/_internal/__init__.py +12 -0
- algo_cli/_internal/policy_chain.py +259 -0
- algo_cli/action_registry.py +1047 -0
- algo_cli/agent_blocks.py +550 -0
- algo_cli/agent_pipeline.py +1457 -0
- algo_cli/agent_threads.py +308 -0
- algo_cli/animations.py +316 -0
- algo_cli/cache_admission.py +209 -0
- algo_cli/capability_mask.py +66 -0
- algo_cli/chat_protocol.py +116 -0
- algo_cli/chatgpt_auth.py +510 -0
- algo_cli/chatgpt_client.py +657 -0
- algo_cli/code_rag.py +479 -0
- algo_cli/config.py +651 -0
- algo_cli/context_budget.py +679 -0
- algo_cli/credential_helpers.py +315 -0
- algo_cli/deliberation.py +29 -0
- algo_cli/display.py +1470 -0
- algo_cli/evals/__init__.py +21 -0
- algo_cli/evals/algorithm_effectiveness.py +560 -0
- algo_cli/evals/competitive_harness_rating.py +702 -0
- algo_cli/evals/cot_quality.py +220 -0
- algo_cli/evals/harness_retrieval_benchmark.py +401 -0
- algo_cli/evals/performance_regression.py +136 -0
- algo_cli/evals/scorecard_grading.py +308 -0
- algo_cli/evals/session_distribution.py +84 -0
- algo_cli/execution_guardrails.py +806 -0
- algo_cli/extensions_manifest.py +84 -0
- algo_cli/git_evidence.py +227 -0
- algo_cli/google_workspace.py +407 -0
- algo_cli/google_workspace_auth.py +523 -0
- algo_cli/harness.py +2587 -0
- algo_cli/identity.py +557 -0
- algo_cli/index_compute_lab.py +228 -0
- algo_cli/inference_harness.py +70 -0
- algo_cli/intelligence/__init__.py +1103 -0
- algo_cli/intelligence/acrobat_config.py +307 -0
- algo_cli/intelligence/acrobat_manifests.py +338 -0
- algo_cli/intelligence/acrobat_models.py +195 -0
- algo_cli/intelligence/acrobat_pipeline.py +295 -0
- algo_cli/intelligence/acrobat_runtime.py +302 -0
- algo_cli/intelligence/acrobat_security.py +261 -0
- algo_cli/intelligence/acrobat_workflows.py +226 -0
- algo_cli/intelligence/actionability.py +165 -0
- algo_cli/intelligence/adversarial_audit.py +136 -0
- algo_cli/intelligence/agent_arena.py +92 -0
- algo_cli/intelligence/agent_benchmark.py +236 -0
- algo_cli/intelligence/agent_runtime.py +171 -0
- algo_cli/intelligence/agents_as_tools.py +70 -0
- algo_cli/intelligence/artifact_binding.py +80 -0
- algo_cli/intelligence/autonomous_engineer.py +1976 -0
- algo_cli/intelligence/backpressure.py +99 -0
- algo_cli/intelligence/bloom_filter.py +186 -0
- algo_cli/intelligence/bonferroni.py +66 -0
- algo_cli/intelligence/boundary_compaction.py +98 -0
- algo_cli/intelligence/catalog_verifier.py +172 -0
- algo_cli/intelligence/cavecrew.py +118 -0
- algo_cli/intelligence/changelog.py +176 -0
- algo_cli/intelligence/checkpoint_resume.py +92 -0
- algo_cli/intelligence/circuit_breaker.py +88 -0
- algo_cli/intelligence/clarification_gate.py +101 -0
- algo_cli/intelligence/code_graph.py +180 -0
- algo_cli/intelligence/coderank.py +97 -0
- algo_cli/intelligence/consistent_hash.py +150 -0
- algo_cli/intelligence/consortium_synthesis.py +139 -0
- algo_cli/intelligence/construction/__init__.py +241 -0
- algo_cli/intelligence/construction/common.py +273 -0
- algo_cli/intelligence/construction/documents.py +496 -0
- algo_cli/intelligence/construction/labor_units.py +1395 -0
- algo_cli/intelligence/construction/payments.py +470 -0
- algo_cli/intelligence/construction/risk.py +784 -0
- algo_cli/intelligence/content_extractor.py +132 -0
- algo_cli/intelligence/context_adaptive.py +102 -0
- algo_cli/intelligence/context_ops.py +95 -0
- algo_cli/intelligence/count_min.py +145 -0
- algo_cli/intelligence/cow_state.py +103 -0
- algo_cli/intelligence/critic_loop.py +119 -0
- algo_cli/intelligence/cross_source.py +113 -0
- algo_cli/intelligence/daemon_mode.py +99 -0
- algo_cli/intelligence/dag_orchestration.py +151 -0
- algo_cli/intelligence/deep_research.py +155 -0
- algo_cli/intelligence/degenerate_detector.py +78 -0
- algo_cli/intelligence/delta_report.py +92 -0
- algo_cli/intelligence/discovery_event_log.py +92 -0
- algo_cli/intelligence/document_ingest.py +298 -0
- algo_cli/intelligence/dual_layer_validate.py +151 -0
- algo_cli/intelligence/echo_fidelity.py +73 -0
- algo_cli/intelligence/ema_tuning.py +104 -0
- algo_cli/intelligence/event_log.py +92 -0
- algo_cli/intelligence/evidence_graph.py +114 -0
- algo_cli/intelligence/extension_host.py +162 -0
- algo_cli/intelligence/extension_manifest.py +115 -0
- algo_cli/intelligence/falsification_suite.py +178 -0
- algo_cli/intelligence/finance/__init__.py +169 -0
- algo_cli/intelligence/finance/anomalies.py +135 -0
- algo_cli/intelligence/finance/ap_ar.py +351 -0
- algo_cli/intelligence/finance/cash.py +162 -0
- algo_cli/intelligence/finance/close.py +332 -0
- algo_cli/intelligence/finance/common.py +244 -0
- algo_cli/intelligence/finance/construction.py +135 -0
- algo_cli/intelligence/finance/controls.py +172 -0
- algo_cli/intelligence/finance/evidence.py +119 -0
- algo_cli/intelligence/finance/exceptions.py +157 -0
- algo_cli/intelligence/finance/reconciliations.py +254 -0
- algo_cli/intelligence/finance/revenue.py +109 -0
- algo_cli/intelligence/finance/tax.py +74 -0
- algo_cli/intelligence/finance/workpapers.py +111 -0
- algo_cli/intelligence/finding_record.py +120 -0
- algo_cli/intelligence/flow_dag.py +267 -0
- algo_cli/intelligence/gatherer.py +223 -0
- algo_cli/intelligence/golden_master.py +98 -0
- algo_cli/intelligence/graph_rag.py +195 -0
- algo_cli/intelligence/group_chat.py +143 -0
- algo_cli/intelligence/hash_dedup.py +145 -0
- algo_cli/intelligence/hyperloglog.py +128 -0
- algo_cli/intelligence/incremental_index.py +316 -0
- algo_cli/intelligence/index_store.py +16 -0
- algo_cli/intelligence/iteration_plan.py +133 -0
- algo_cli/intelligence/kernel_plugins.py +167 -0
- algo_cli/intelligence/lesson_catalog.py +135 -0
- algo_cli/intelligence/llm_fallback.py +169 -0
- algo_cli/intelligence/log2_histogram.py +267 -0
- algo_cli/intelligence/lsp_integration.py +147 -0
- algo_cli/intelligence/memory_evolution.py +117 -0
- algo_cli/intelligence/minhash_lsh.py +182 -0
- algo_cli/intelligence/multi_model_score.py +174 -0
- algo_cli/intelligence/multi_tier_grade.py +211 -0
- algo_cli/intelligence/negative_controls.py +113 -0
- algo_cli/intelligence/numeric_clamp.py +63 -0
- algo_cli/intelligence/occ_editor.py +66 -0
- algo_cli/intelligence/output_normalize.py +112 -0
- algo_cli/intelligence/parallel_delegation.py +98 -0
- algo_cli/intelligence/parallel_fanout.py +104 -0
- algo_cli/intelligence/permission_modes.py +105 -0
- algo_cli/intelligence/pre_push_gate.py +68 -0
- algo_cli/intelligence/prefetch.py +171 -0
- algo_cli/intelligence/process_framework.py +217 -0
- algo_cli/intelligence/project_graph.py +387 -0
- algo_cli/intelligence/query_expansion.py +146 -0
- algo_cli/intelligence/ralph_loop.py +117 -0
- algo_cli/intelligence/rate_limiter.py +153 -0
- algo_cli/intelligence/refactor_transaction.py +94 -0
- algo_cli/intelligence/research_workspace.py +108 -0
- algo_cli/intelligence/retraction_ledger.py +72 -0
- algo_cli/intelligence/saga_pattern.py +88 -0
- algo_cli/intelligence/session_fork.py +100 -0
- algo_cli/intelligence/shadow_editor.py +67 -0
- algo_cli/intelligence/shell_session.py +213 -0
- algo_cli/intelligence/source_registry.py +143 -0
- algo_cli/intelligence/spawn_scales.py +99 -0
- algo_cli/intelligence/stat_stability.py +104 -0
- algo_cli/intelligence/structural_validator.py +148 -0
- algo_cli/intelligence/subagent_spawner.py +111 -0
- algo_cli/intelligence/symmetric_verify.py +70 -0
- algo_cli/intelligence/task_classifier.py +129 -0
- algo_cli/intelligence/team_execution.py +122 -0
- algo_cli/intelligence/tiered_access.py +121 -0
- algo_cli/intelligence/utility_registry.py +159 -0
- algo_cli/intuition_engine.py +560 -0
- algo_cli/intuition_injector.py +82 -0
- algo_cli/kernels/__init__.py +5 -0
- algo_cli/kernels/manifest.py +763 -0
- algo_cli/main.py +3903 -0
- algo_cli/memory_candidates.py +541 -0
- algo_cli/memory_echo_veil.py +394 -0
- algo_cli/memory_runtime.py +112 -0
- algo_cli/model_info.py +548 -0
- algo_cli/model_profile.py +160 -0
- algo_cli/model_routing.py +74 -0
- algo_cli/oneshot.py +331 -0
- algo_cli/perf_telemetry.py +389 -0
- algo_cli/plugins.py +245 -0
- algo_cli/private_event_store.py +654 -0
- algo_cli/quantization/__init__.py +24 -0
- algo_cli/quantization/lloyd_max.py +98 -0
- algo_cli/quantization/turbo_quant.py +308 -0
- algo_cli/reasoning/__init__.py +46 -0
- algo_cli/reasoning/combinatorial.py +356 -0
- algo_cli/reasoning/graph_of_thought.py +297 -0
- algo_cli/reasoning/mcts.py +220 -0
- algo_cli/reasoning/neuro_symbolic.py +250 -0
- algo_cli/reasoning/react.py +246 -0
- algo_cli/reasoning/reflexion.py +225 -0
- algo_cli/reasoning/tree_of_thought.py +241 -0
- algo_cli/reasoning_bridge.py +150 -0
- algo_cli/reconciliation.py +284 -0
- algo_cli/reflex.py +385 -0
- algo_cli/resources/docs/ALGO.md +13958 -0
- algo_cli/resources/docs/algo-cli-algorithm-evidence-contract.md +60 -0
- algo_cli/resources/docs/algo-cli-execution-verification-contract.md +59 -0
- algo_cli/resources/docs/algo-cli-memory-lifecycle-contract.md +72 -0
- algo_cli/resources/docs/harness-extension-cleanup-recommendation.md +41 -0
- algo_cli/resources/docs/index-compute-lab-integration.md +32 -0
- algo_cli/resources/docs/inference-harness-loop-blueprint-2026-06.md +55 -0
- algo_cli/resources/docs/main-split-map.md +35 -0
- algo_cli/resources/docs/privacy-and-context.md +48 -0
- algo_cli/resources/docs/reflex-loop-v0.2.md +354 -0
- algo_cli/resources/skills/README.md +26 -0
- algo_cli/resources/skills/algo-cli.md +59 -0
- algo_cli/resources/skills/edit-file-precision.md +49 -0
- algo_cli/resources/skills/harness-search-first.md +47 -0
- algo_cli/resources/skills/memory-recall-ritual.md +51 -0
- algo_cli/resources/skills/qol-algorithms.md +224 -0
- algo_cli/resources/skills/smart-error-recovery.md +56 -0
- algo_cli/resources/skills/tool-selection-cheatsheet.md +65 -0
- algo_cli/retrieval_algorithms.py +127 -0
- algo_cli/runtime_qos.py +236 -0
- algo_cli/runtime_services.py +320 -0
- algo_cli/session_commands.py +95 -0
- algo_cli/session_mode.py +113 -0
- algo_cli/skills.py +430 -0
- algo_cli/slash_dispatch.py +1265 -0
- algo_cli/small_context.py +206 -0
- algo_cli/spawn_budget.py +89 -0
- algo_cli/task_ledger.py +84 -0
- algo_cli/task_router.py +197 -0
- algo_cli/tool_context.py +94 -0
- algo_cli/tool_contract.py +99 -0
- algo_cli/tool_policy.py +357 -0
- algo_cli/tool_runtime.py +647 -0
- algo_cli/tools.py +3056 -0
- algo_cli/url_scheme.py +174 -0
- algo_cli/verify.py +154 -0
- algo_cli/version_manifest.py +178 -0
- algo_cli/vision_screenshot_verify.py +76 -0
- algo_cli/workspace_resolver.py +68 -0
- algo_cli/x_account.py +209 -0
- algo_cli/xai_auth.py +374 -0
- algo_cli/xai_client.py +600 -0
- algo_cli_runtime-0.14.0.dist-info/METADATA +369 -0
- algo_cli_runtime-0.14.0.dist-info/RECORD +237 -0
- algo_cli_runtime-0.14.0.dist-info/WHEEL +4 -0
- algo_cli_runtime-0.14.0.dist-info/entry_points.txt +3 -0
- algo_cli_runtime-0.14.0.dist-info/licenses/LICENSE +21 -0
- 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"]
|
algo_cli/git_evidence.py
ADDED
|
@@ -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
|
+
)
|