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
algo_cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Algo CLI agent runtime."""
2
+
3
+ __version__ = "0.14.0"
algo_cli/__main__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Run Algo CLI with ``python -m algo_cli``."""
2
+
3
+ from .main import main
4
+
5
+
6
+ if __name__ == "__main__":
7
+ main()
@@ -0,0 +1,12 @@
1
+ """algo_cli/_internal — Internal helpers and runtime modules.
2
+
3
+ This subpackage is for code that the user does not import directly. It is
4
+ imported by algo_cli and its public API. Importing from this package
5
+ directly may emit a DeprecationWarning in a future version.
6
+
7
+ Modules:
8
+ - policy_chain: J10 — PAM-style policy chain (required/sufficient/requisite/include)
9
+ """
10
+ from __future__ import annotations
11
+
12
+ __all__: list[str] = []
@@ -0,0 +1,259 @@
1
+ """PAM-style policy chain (J10).
2
+
3
+ A chain of named, composable checks with control flags
4
+ (required / sufficient / requisite / include), modeled on the
5
+ macOS PAM stack (/etc/pam.d/*).
6
+
7
+ Provenance: ALGO.md J10 (PAM-style policy chain), J5 (SHAuthorizationRight),
8
+ J13 (flat-text policy file). The chain is the upgrade path for
9
+ algo_cli/tool_policy.py from a single boolean to a composable,
10
+ audit-trail-preserving decision model.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass, field
15
+ from enum import Enum
16
+ from typing import Any, Callable, Sequence
17
+
18
+
19
+ class Control(str, Enum):
20
+ """PAM-style control flags.
21
+
22
+ - REQUIRED: must pass; failure is recorded but the chain continues
23
+ to gather full evidence (PAM "required" semantics).
24
+ - SUFFICIENT: pass short-circuits the chain to PASS (PAM "sufficient").
25
+ - REQUISITE: must pass; failure aborts the chain immediately
26
+ (PAM "requisite" semantics — fail-fast).
27
+ - INCLUDE: recursively evaluate a named chain. The chain name is
28
+ read from the check's `name` field (PAM "include" semantics).
29
+ """
30
+
31
+ REQUIRED = "required"
32
+ SUFFICIENT = "sufficient"
33
+ REQUISITE = "requisite"
34
+ INCLUDE = "include"
35
+
36
+
37
+ @dataclass
38
+ class CheckResult:
39
+ """Outcome of one check inside a chain."""
40
+
41
+ name: str
42
+ passed: bool
43
+ reason: str = ""
44
+ control: Control = Control.REQUIRED
45
+ duration_ms: float = 0.0
46
+
47
+ def to_dict(self) -> dict:
48
+ return {
49
+ "name": self.name,
50
+ "passed": self.passed,
51
+ "reason": self.reason,
52
+ "control": self.control.value,
53
+ "duration_ms": round(self.duration_ms, 3),
54
+ }
55
+
56
+
57
+ @dataclass
58
+ class ChainDecision:
59
+ """Aggregate outcome of evaluating one named chain."""
60
+
61
+ chain: str
62
+ passed: bool
63
+ results: tuple[CheckResult, ...] = field(default_factory=tuple)
64
+ reasons: tuple[str, ...] = field(default_factory=tuple)
65
+ abort_reason: str = ""
66
+
67
+ def to_dict(self) -> dict:
68
+ return {
69
+ "chain": self.chain,
70
+ "passed": self.passed,
71
+ "abort_reason": self.abort_reason,
72
+ "results": [r.to_dict() for r in self.results],
73
+ "reasons": list(self.reasons),
74
+ }
75
+
76
+ def fired_rules(self) -> list[str]:
77
+ """Return the names of checks that failed (or the `sufficient` that short-circuited)."""
78
+ return [r.name for r in self.results if not r.passed or r.control == Control.SUFFICIENT]
79
+
80
+
81
+ # A Check is a callable that returns a CheckResult.
82
+ Check = Callable[[dict[str, Any], dict[str, Any]], CheckResult]
83
+
84
+ # A Chain is an ordered list of (control, check) pairs.
85
+ Chain = Sequence[tuple[Control, Check]]
86
+
87
+ # get_chain resolves an include reference to its chain definition.
88
+ ChainRegistry = Callable[[str], Chain | None]
89
+
90
+
91
+ def evaluate_chain(
92
+ chain_name: str,
93
+ checks: Chain,
94
+ tool_call: dict[str, Any],
95
+ context: dict[str, Any] | None = None,
96
+ *,
97
+ registry: ChainRegistry | None = None,
98
+ ) -> ChainDecision:
99
+ """Evaluate a chain of checks against a tool call.
100
+
101
+ Args:
102
+ chain_name: Display name for this chain.
103
+ checks: Ordered (control, check) pairs.
104
+ tool_call: The tool invocation being gated. Shape:
105
+ {"tool": "algo_cli.shell.write", "args": {...}, "tier": "tier2", ...}
106
+ context: Session-level state (e.g. cwd, env, user).
107
+ registry: Optional callback that returns a named chain for INCLUDE.
108
+
109
+ Returns:
110
+ A ChainDecision with per-check results, the overall verdict, and
111
+ structured reasons suitable for the audit log.
112
+ """
113
+ ctx = context or {}
114
+ results: list[CheckResult] = []
115
+ abort_reason = ""
116
+
117
+ for control, check in checks:
118
+ # INCLUDE — recursively evaluate a registered chain.
119
+ if control == Control.INCLUDE:
120
+ if registry is None:
121
+ results.append(CheckResult(
122
+ name=getattr(check, "__name__", "<include>"),
123
+ passed=False,
124
+ reason="include: no registry provided",
125
+ control=Control.INCLUDE,
126
+ ))
127
+ continue
128
+ sub_chain = registry(getattr(check, "__name__", ""))
129
+ if sub_chain is None:
130
+ results.append(CheckResult(
131
+ name=getattr(check, "__name__", "<include>"),
132
+ passed=False,
133
+ reason="include: chain not registered",
134
+ control=Control.INCLUDE,
135
+ ))
136
+ continue
137
+ sub = evaluate_chain(
138
+ getattr(check, "__name__", "include"),
139
+ sub_chain,
140
+ tool_call,
141
+ ctx,
142
+ registry=registry,
143
+ )
144
+ results.extend(sub.results)
145
+ # The include chain must itself pass; if it doesn't, propagate
146
+ # the failure but continue to the next check.
147
+ if not sub.passed:
148
+ results.append(CheckResult(
149
+ name=getattr(check, "__name__", "include"),
150
+ passed=False,
151
+ reason=f"included chain failed: {sub.abort_reason or 'see results'}",
152
+ control=Control.INCLUDE,
153
+ ))
154
+ continue
155
+
156
+ # Regular check.
157
+ try:
158
+ result = check(tool_call, ctx)
159
+ except Exception as exc: # noqa: BLE001
160
+ result = CheckResult(
161
+ name=getattr(check, "__name__", "<check>"),
162
+ passed=False,
163
+ reason=f"check raised: {type(exc).__name__}: {exc}",
164
+ control=control,
165
+ )
166
+ result.control = control
167
+ results.append(result)
168
+
169
+ # REQUISITE — abort on failure.
170
+ if not result.passed and control == Control.REQUISITE:
171
+ abort_reason = f"requisite failed: {result.name}: {result.reason}"
172
+ break
173
+
174
+ # SUFFICIENT — pass short-circuits the chain.
175
+ if result.passed and control == Control.SUFFICIENT:
176
+ break
177
+
178
+ passed = (not abort_reason) and all(r.passed for r in results)
179
+ reasons = tuple(r.reason for r in results if r.reason)
180
+ return ChainDecision(
181
+ chain=chain_name,
182
+ passed=passed,
183
+ results=tuple(results),
184
+ reasons=reasons,
185
+ abort_reason=abort_reason,
186
+ )
187
+
188
+
189
+ # --- Built-in checks -------------------------------------------------------
190
+
191
+ def tier_check(min_tier: str) -> Check:
192
+ """Check that the call's tier is at least `min_tier`."""
193
+ tier_rank = {"tier0": 0, "tier1": 1, "tier2": 2, "tier3": 3}
194
+ min_rank = tier_rank.get(min_tier, 0)
195
+
196
+ def _check(tool_call: dict[str, Any], _ctx: dict[str, Any]) -> CheckResult:
197
+ actual = tool_call.get("tier", "tier0")
198
+ actual_rank = tier_rank.get(actual, 0)
199
+ passed = actual_rank >= min_rank
200
+ return CheckResult(
201
+ name=f"tier(min={min_tier})",
202
+ passed=passed,
203
+ reason="" if passed else f"call tier {actual} < required {min_tier}",
204
+ )
205
+
206
+ _check.__name__ = f"tier_{min_tier}"
207
+ return _check
208
+
209
+
210
+ def path_allowlist_check(allow: list[str] | tuple[str, ...], block: list[str] | tuple[str, ...] = ()) -> Check:
211
+ """Check that the call's path is in the allowlist and not in the blocklist."""
212
+ allow_t = tuple(allow)
213
+ block_t = tuple(block)
214
+
215
+ def _check(tool_call: dict[str, Any], _ctx: dict[str, Any]) -> CheckResult:
216
+ path = (tool_call.get("path") or tool_call.get("args", {}).get("path") or "").strip()
217
+ if not path:
218
+ return CheckResult(name="path_allowlist", passed=False, reason="no path in call")
219
+ if any(path.startswith(b) for b in block_t):
220
+ return CheckResult(name="path_allowlist", passed=False, reason=f"path {path!r} in blocklist")
221
+ if any(path.startswith(a) for a in allow_t):
222
+ return CheckResult(name="path_allowlist", passed=True)
223
+ return CheckResult(name="path_allowlist", passed=False, reason=f"path {path!r} not in allowlist")
224
+
225
+ _check.__name__ = "path_allowlist"
226
+ return _check
227
+
228
+
229
+ def command_grep_check(deny: list[str] | tuple[str, ...]) -> Check:
230
+ """Check that the call's command does not contain any denied substring."""
231
+ deny_t = tuple(deny)
232
+
233
+ def _check(tool_call: dict[str, Any], _ctx: dict[str, Any]) -> CheckResult:
234
+ cmd = (tool_call.get("command") or tool_call.get("args", {}).get("command") or "").lower()
235
+ for needle in deny_t:
236
+ if needle.lower() in cmd:
237
+ return CheckResult(
238
+ name="command_grep",
239
+ passed=False,
240
+ reason=f"command contains denied substring {needle!r}",
241
+ )
242
+ return CheckResult(name="command_grep", passed=True)
243
+
244
+ _check.__name__ = "command_grep"
245
+ return _check
246
+
247
+
248
+ __all__ = [
249
+ "Chain",
250
+ "ChainDecision",
251
+ "ChainRegistry",
252
+ "Check",
253
+ "CheckResult",
254
+ "Control",
255
+ "command_grep_check",
256
+ "evaluate_chain",
257
+ "path_allowlist_check",
258
+ "tier_check",
259
+ ]