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,104 @@
1
+ """H13 — Feedback-Driven Parameter Tuning (EMA).
2
+
3
+ Exponential moving average tuning: user feedback (binary ratings) updates
4
+ an EMA that adjusts harness parameters over time. Includes sample-count
5
+ gating (no adjustments until minimum samples collected) and bounded history.
6
+
7
+ Source: G0DM0D3 AutoTune.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from collections import deque
12
+ from dataclasses import dataclass
13
+ from typing import Any
14
+
15
+
16
+ MAX_HISTORY = 500
17
+ MIN_SAMPLES_TO_APPLY = 3
18
+
19
+
20
+ @dataclass
21
+ class TuningState:
22
+ """Snapshot of the EMA tuner state."""
23
+
24
+ ema: float
25
+ samples: int
26
+ applied: bool
27
+ history_size: int
28
+
29
+
30
+ class EMATuner:
31
+ """EMA-based parameter tuner with sample-count gating."""
32
+
33
+ def __init__(
34
+ self,
35
+ param_name: str,
36
+ initial_value: float = 0.5,
37
+ alpha: float = 0.3,
38
+ min_samples: int = MIN_SAMPLES_TO_APPLY,
39
+ max_history: int = MAX_HISTORY,
40
+ ) -> None:
41
+ if not 0.0 < alpha <= 1.0:
42
+ raise ValueError(f"alpha must be in (0, 1], got {alpha}")
43
+ self.param_name = param_name
44
+ self.alpha = alpha
45
+ self.min_samples = min_samples
46
+ self.max_history = max_history
47
+ self._ema = initial_value
48
+ self._samples = 0
49
+ self._history: deque[float] = deque(maxlen=max_history)
50
+
51
+ def update(self, rating: float) -> float:
52
+ """Update EMA with a new rating (0.0 to 1.0).
53
+
54
+ Returns the updated EMA value.
55
+ """
56
+ if not 0.0 <= rating <= 1.0:
57
+ raise ValueError(f"rating must be in [0, 1], got {rating}")
58
+ self._samples += 1
59
+ self._history.append(rating)
60
+ self._ema = self.alpha * rating + (1 - self.alpha) * self._ema
61
+ return self._ema
62
+
63
+ def update_binary(self, positive: bool) -> float:
64
+ """Update with a binary rating (True=1.0, False=0.0)."""
65
+ return self.update(1.0 if positive else 0.0)
66
+
67
+ @property
68
+ def ema(self) -> float:
69
+ return self._ema
70
+
71
+ @property
72
+ def samples(self) -> int:
73
+ return self._samples
74
+
75
+ @property
76
+ def can_apply(self) -> bool:
77
+ """True when enough samples have been collected to apply tuning."""
78
+ return self._samples >= self.min_samples
79
+
80
+ def apply(self, params: dict[str, Any]) -> dict[str, Any]:
81
+ """Apply the tuned value to a params dict if enough samples exist.
82
+
83
+ Returns a new dict with the parameter updated.
84
+ """
85
+ if not self.can_apply:
86
+ return params
87
+ result = dict(params)
88
+ result[self.param_name] = self._ema
89
+ return result
90
+
91
+ def state(self) -> TuningState:
92
+ """Return a snapshot of the current state."""
93
+ return TuningState(
94
+ ema=round(self._ema, 6),
95
+ samples=self._samples,
96
+ applied=self.can_apply,
97
+ history_size=len(self._history),
98
+ )
99
+
100
+ def reset(self) -> None:
101
+ """Reset to initial state."""
102
+ self._ema = 0.5
103
+ self._samples = 0
104
+ self._history.clear()
@@ -0,0 +1,92 @@
1
+ """H4 — Harness Discovery Event Log.
2
+
3
+ Structured events for the full discovery lifecycle.
4
+ Mined from T3MP3ST EventEmitter (finding:discovered, finding:retracted, etc.).
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import time
9
+ from dataclasses import dataclass, field
10
+ from enum import Enum
11
+ from typing import Any, Callable
12
+
13
+
14
+ class EventType(str, Enum):
15
+ DISCOVERED = "discovered"
16
+ PROPOSED = "proposed"
17
+ VERIFIED = "verified"
18
+ RETRACTED = "retracted"
19
+ DEPRECATED = "deprecated"
20
+ PROMOTED = "promoted"
21
+
22
+
23
+ @dataclass
24
+ class Event:
25
+ """A single discovery event."""
26
+
27
+ event_type: EventType
28
+ target_id: str
29
+ timestamp: float = field(default_factory=time.time)
30
+ metadata: dict[str, Any] = field(default_factory=dict)
31
+
32
+ def to_dict(self) -> dict[str, Any]:
33
+ return {
34
+ "event_type": self.event_type.value,
35
+ "target_id": self.target_id,
36
+ "timestamp": self.timestamp,
37
+ "metadata": dict(self.metadata),
38
+ }
39
+
40
+
41
+ class EventLogger:
42
+ """Append-only event log with subscription support."""
43
+
44
+ def __init__(self) -> None:
45
+ self._events: list[Event] = []
46
+ self._subscribers: list[tuple[Callable[[Event], None], EventType | None]] = []
47
+
48
+ def emit(
49
+ self,
50
+ event_type: EventType,
51
+ target_id: str,
52
+ metadata: dict[str, Any] | None = None,
53
+ ) -> Event:
54
+ """Emit an event and notify subscribers."""
55
+ event = Event(
56
+ event_type=event_type,
57
+ target_id=target_id,
58
+ metadata=metadata or {},
59
+ )
60
+ self._events.append(event)
61
+ for callback, filter_type in self._subscribers:
62
+ if filter_type is None or filter_type == event_type:
63
+ callback(event)
64
+ return event
65
+
66
+ def subscribe(
67
+ self,
68
+ callback: Callable[[Event], None],
69
+ event_type: EventType | None = None,
70
+ ) -> None:
71
+ """Subscribe to events. Optionally filter by event type."""
72
+ self._subscribers.append((callback, event_type))
73
+
74
+ def query(
75
+ self,
76
+ event_type: EventType | None = None,
77
+ target_id: str | None = None,
78
+ ) -> list[Event]:
79
+ """Query events by type and/or target."""
80
+ results = self._events
81
+ if event_type is not None:
82
+ results = [e for e in results if e.event_type == event_type]
83
+ if target_id is not None:
84
+ results = [e for e in results if e.target_id == target_id]
85
+ return list(results)
86
+
87
+ def count(self) -> int:
88
+ return len(self._events)
89
+
90
+ def clear(self) -> None:
91
+ self._events.clear()
92
+ self._subscribers.clear()
@@ -0,0 +1,114 @@
1
+ """B63. Evidence Graph for Provenance.
2
+
3
+ Link every claim to source text snippets with traceable citations.
4
+ Source: Deep-Research-Agent pattern.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass, field
9
+
10
+
11
+ @dataclass
12
+ class Source:
13
+ id: str
14
+ title: str
15
+ url: str = ""
16
+ content: str = ""
17
+ retrieved_at: str = ""
18
+ trust_score: float = 0.5
19
+
20
+
21
+ @dataclass
22
+ class Claim:
23
+ id: str
24
+ text: str
25
+ source_ids: list[str] = field(default_factory=list)
26
+ evidence_spans: list[dict] = field(default_factory=list)
27
+ confidence: float = 0.0
28
+ contradictions: list[str] = field(default_factory=list)
29
+
30
+
31
+ @dataclass
32
+ class EvidenceEdge:
33
+ claim_id: str
34
+ source_id: str
35
+ span_start: int = 0
36
+ span_end: int = 0
37
+ relevance: float = 0.0
38
+
39
+
40
+ class EvidenceGraph:
41
+ """Graph linking claims to source evidence for provenance."""
42
+
43
+ def __init__(self) -> None:
44
+ self._sources: dict[str, Source] = {}
45
+ self._claims: dict[str, Claim] = {}
46
+ self._edges: list[EvidenceEdge] = []
47
+
48
+ def add_source(self, source: Source) -> None:
49
+ self._sources[source.id] = source
50
+
51
+ def add_claim(self, claim: Claim) -> None:
52
+ self._claims[claim.id] = claim
53
+ for sid in claim.source_ids:
54
+ if sid in self._sources:
55
+ self._edges.append(EvidenceEdge(claim_id=claim.id, source_id=sid))
56
+
57
+ def link_evidence(self, claim_id: str, source_id: str,
58
+ span_start: int = 0, span_end: int = 0,
59
+ relevance: float = 1.0) -> None:
60
+ self._edges.append(EvidenceEdge(
61
+ claim_id=claim_id, source_id=source_id,
62
+ span_start=span_start, span_end=span_end, relevance=relevance,
63
+ ))
64
+ claim = self._claims.get(claim_id)
65
+ if claim and source_id not in claim.source_ids:
66
+ claim.source_ids.append(source_id)
67
+
68
+ def get_evidence(self, claim_id: str) -> list[tuple[Source, EvidenceEdge]]:
69
+ """Get all source evidence for a claim."""
70
+ result: list[tuple[Source, EvidenceEdge]] = []
71
+ for edge in self._edges:
72
+ if edge.claim_id == claim_id:
73
+ source = self._sources.get(edge.source_id)
74
+ if source:
75
+ result.append((source, edge))
76
+ return sorted(result, key=lambda x: x[1].relevance, reverse=True)
77
+
78
+ def find_contradictions(self) -> list[tuple[str, str, str]]:
79
+ """Find claims that contradict each other."""
80
+ contradictions: list[tuple[str, str, str]] = []
81
+ claim_list = list(self._claims.values())
82
+ for i, a in enumerate(claim_list):
83
+ for b in claim_list[i + 1:]:
84
+ if a.source_ids and b.source_ids:
85
+ shared = set(a.source_ids) & set(b.source_ids)
86
+ if shared and a.text != b.text:
87
+ # Simple heuristic: if same source but different claims
88
+ contradictions.append((a.id, b.id, "same source, different claims"))
89
+ return contradictions
90
+
91
+ def render_citations(self, claim_id: str) -> str:
92
+ """Render citations for a claim in [1], [2] format."""
93
+ evidence = self.get_evidence(claim_id)
94
+ if not evidence:
95
+ return ""
96
+ citations: list[str] = []
97
+ for i, (source, edge) in enumerate(evidence, 1):
98
+ span_text = ""
99
+ if edge.span_end > edge.span_start:
100
+ span_text = source.content[edge.span_start:edge.span_end]
101
+ citations.append(f"[{i}] {source.title} — {span_text[:100]}")
102
+ return "\n".join(citations)
103
+
104
+ @property
105
+ def source_count(self) -> int:
106
+ return len(self._sources)
107
+
108
+ @property
109
+ def claim_count(self) -> int:
110
+ return len(self._claims)
111
+
112
+ @property
113
+ def edge_count(self) -> int:
114
+ return len(self._edges)
@@ -0,0 +1,162 @@
1
+ """B38. Extension Host Isolation + Declarative Contribution Points (VS Code Pattern).
2
+
3
+ Extensions declare what they contribute (commands, tools, converters,
4
+ maintenance checks) via a manifest. Activation events control when an
5
+ extension's code is loaded. Extensions run behind a boundary and cannot
6
+ directly mutate files or send externally.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ from typing import Any, Callable
13
+
14
+
15
+ # ── contribution schema ───────────────────────────────────────────────
16
+
17
+
18
+ @dataclass
19
+ class CommandContribution:
20
+ id: str
21
+ title: str
22
+ handler: Callable[[], str] | None = None
23
+
24
+
25
+ @dataclass
26
+ class ToolContribution:
27
+ id: str
28
+ description: str
29
+ handler: Callable[[dict], str] | None = None
30
+
31
+
32
+ @dataclass
33
+ class ConverterContribution:
34
+ id: str
35
+ suffixes: list[str]
36
+ handler: Callable[[str], str] | None = None
37
+
38
+
39
+ @dataclass
40
+ class MaintenanceCheckContribution:
41
+ id: str
42
+ name: str
43
+ handler: Callable[[], dict] | None = None
44
+
45
+
46
+ @dataclass
47
+ class ExtensionManifest:
48
+ """Declarative manifest for an Algo CLI extension."""
49
+ id: str
50
+ name: str
51
+ version: str = "0.1.0"
52
+ enabled: bool = True
53
+ activation_events: list[str] = field(default_factory=list)
54
+ contributes_commands: list[CommandContribution] = field(default_factory=list)
55
+ contributes_tools: list[ToolContribution] = field(default_factory=list)
56
+ contributes_converters: list[ConverterContribution] = field(default_factory=list)
57
+ contributes_maintenance_checks: list[MaintenanceCheckContribution] = field(default_factory=list)
58
+
59
+
60
+ # ── activation events ─────────────────────────────────────────────────
61
+
62
+
63
+ def matches_activation(event: str, context: dict[str, Any]) -> bool:
64
+ """Check if an activation event matches the current context."""
65
+ if event == "*":
66
+ return True
67
+ if event.startswith("onCommand:"):
68
+ return context.get("command") == event.split(":", 1)[1]
69
+ if event.startswith("onFileType:"):
70
+ return context.get("file_suffix") == event.split(":", 1)[1]
71
+ if event.startswith("onProject:"):
72
+ return context.get("project") == event.split(":", 1)[1]
73
+ if event.startswith("onSchedule:"):
74
+ return context.get("schedule") == event.split(":", 1)[1]
75
+ return False
76
+
77
+
78
+ # ── extension host ────────────────────────────────────────────────────
79
+
80
+
81
+ class ExtensionHost:
82
+ """Manages extensions with activation and contribution resolution."""
83
+
84
+ def __init__(self) -> None:
85
+ self._manifests: dict[str, ExtensionManifest] = {}
86
+ self._activated: set[str] = set()
87
+
88
+ def register(self, manifest: ExtensionManifest) -> None:
89
+ self._manifests[manifest.id] = manifest
90
+
91
+ def activate(self, ext_id: str, context: dict[str, Any] | None = None) -> bool:
92
+ """Activate an extension if its activation events match."""
93
+ manifest = self._manifests.get(ext_id)
94
+ if not manifest or not manifest.enabled:
95
+ return False
96
+ ctx = context or {}
97
+ if any(matches_activation(ev, ctx) for ev in manifest.activation_events):
98
+ self._activated.add(ext_id)
99
+ return True
100
+ return False
101
+
102
+ def is_activated(self, ext_id: str) -> bool:
103
+ return ext_id in self._activated
104
+
105
+ def disable(self, ext_id: str) -> None:
106
+ manifest = self._manifests.get(ext_id)
107
+ if manifest:
108
+ manifest.enabled = False
109
+ self._activated.discard(ext_id)
110
+
111
+ def all_commands(self) -> list[CommandContribution]:
112
+ """Get commands from all activated extensions."""
113
+ commands: list[CommandContribution] = []
114
+ for ext_id in self._activated:
115
+ manifest = self._manifests[ext_id]
116
+ commands.extend(manifest.contributes_commands)
117
+ return commands
118
+
119
+ def all_tools(self) -> list[ToolContribution]:
120
+ tools: list[ToolContribution] = []
121
+ for ext_id in self._activated:
122
+ manifest = self._manifests[ext_id]
123
+ tools.extend(manifest.contributes_tools)
124
+ return tools
125
+
126
+ def all_converters(self) -> list[ConverterContribution]:
127
+ converters: list[ConverterContribution] = []
128
+ for ext_id in self._activated:
129
+ manifest = self._manifests[ext_id]
130
+ converters.extend(manifest.contributes_converters)
131
+ return converters
132
+
133
+ def all_maintenance_checks(self) -> list[MaintenanceCheckContribution]:
134
+ checks: list[MaintenanceCheckContribution] = []
135
+ for ext_id in self._activated:
136
+ manifest = self._manifests[ext_id]
137
+ checks.extend(manifest.contributes_maintenance_checks)
138
+ return checks
139
+
140
+ def execute_command(self, command_id: str) -> str:
141
+ """Execute a contributed command (gated by activation)."""
142
+ for ext_id in self._activated:
143
+ manifest = self._manifests[ext_id]
144
+ for cmd in manifest.contributes_commands:
145
+ if cmd.id == command_id and cmd.handler:
146
+ return cmd.handler()
147
+ return f"Command {command_id} not found or not activated"
148
+
149
+ def validate_manifest(self, ext_id: str) -> list[str]:
150
+ """Validate a manifest and return list of issues."""
151
+ manifest = self._manifests.get(ext_id)
152
+ if not manifest:
153
+ return ["manifest not found"]
154
+ issues: list[str] = []
155
+ if not manifest.id:
156
+ issues.append("missing id")
157
+ if not manifest.name:
158
+ issues.append("missing name")
159
+ for cmd in manifest.contributes_commands:
160
+ if not cmd.id:
161
+ issues.append("command missing id")
162
+ return issues
@@ -0,0 +1,115 @@
1
+ """B57. Extension Manifest Schema + Catalog.
2
+
3
+ YAML manifest with commands/config/hooks. Extension registry with
4
+ multi-source catalog and priority. Source: spec-kit pattern.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass, field
9
+ from enum import Enum, auto
10
+ from typing import Any
11
+
12
+
13
+ class ExtensionStatus(Enum):
14
+ DISCOVERED = auto()
15
+ INSTALLED = auto()
16
+ ENABLED = auto()
17
+ DISABLED = auto()
18
+ ERROR = auto()
19
+
20
+
21
+ @dataclass
22
+ class ExtensionContribution:
23
+ type: str # "command", "tool", "hook", "contextProvider", "fileConverter"
24
+ id: str
25
+ handler: str = "" # module:function or path
26
+ config: dict[str, Any] = field(default_factory=dict)
27
+
28
+
29
+ @dataclass
30
+ class ExtensionManifest:
31
+ id: str
32
+ name: str
33
+ version: str
34
+ description: str = ""
35
+ author: str = ""
36
+ priority: int = 100 # lower = higher priority
37
+ activation_events: list[str] = field(default_factory=list)
38
+ contributes: list[ExtensionContribution] = field(default_factory=list)
39
+ config_schema: dict[str, Any] = field(default_factory=dict)
40
+ status: ExtensionStatus = ExtensionStatus.DISCOVERED
41
+
42
+ def to_dict(self) -> dict:
43
+ return {
44
+ "id": self.id,
45
+ "name": self.name,
46
+ "version": self.version,
47
+ "description": self.description,
48
+ "author": self.author,
49
+ "priority": self.priority,
50
+ "activation_events": self.activation_events,
51
+ "contributes": [
52
+ {"type": c.type, "id": c.id, "handler": c.handler, "config": c.config}
53
+ for c in self.contributes
54
+ ],
55
+ }
56
+
57
+
58
+ @dataclass
59
+ class CatalogSource:
60
+ name: str
61
+ url: str = ""
62
+ trust_level: str = "local" # local, trusted, community
63
+ priority: int = 100
64
+ enabled: bool = True
65
+
66
+
67
+ class ExtensionCatalog:
68
+ """Multi-source extension catalog with priority-based resolution."""
69
+
70
+ def __init__(self) -> None:
71
+ self._sources: dict[str, CatalogSource] = {}
72
+ self._extensions: dict[str, ExtensionManifest] = {}
73
+
74
+ def add_source(self, source: CatalogSource) -> None:
75
+ self._sources[source.name] = source
76
+
77
+ def register(self, manifest: ExtensionManifest, source_name: str = "local") -> None:
78
+ self._extensions[manifest.id] = manifest
79
+
80
+ def get(self, ext_id: str) -> ExtensionManifest | None:
81
+ return self._extensions.get(ext_id)
82
+
83
+ def list_extensions(self, status: ExtensionStatus | None = None) -> list[ExtensionManifest]:
84
+ exts = list(self._extensions.values())
85
+ if status:
86
+ exts = [e for e in exts if e.status == status]
87
+ return sorted(exts, key=lambda e: e.priority)
88
+
89
+ def find_contribution(self, contrib_type: str, contrib_id: str) -> ExtensionManifest | None:
90
+ for ext in self._extensions.values():
91
+ for c in ext.contributes:
92
+ if c.type == contrib_type and c.id == contrib_id:
93
+ return ext
94
+ return None
95
+
96
+ def enable(self, ext_id: str) -> bool:
97
+ ext = self._extensions.get(ext_id)
98
+ if ext:
99
+ ext.status = ExtensionStatus.ENABLED
100
+ return True
101
+ return False
102
+
103
+ def disable(self, ext_id: str) -> bool:
104
+ ext = self._extensions.get(ext_id)
105
+ if ext:
106
+ ext.status = ExtensionStatus.DISABLED
107
+ return True
108
+ return False
109
+
110
+ def match_activation(self, event: str) -> list[ExtensionManifest]:
111
+ """Find extensions whose activation events match."""
112
+ return [
113
+ e for e in self._extensions.values()
114
+ if e.status == ExtensionStatus.ENABLED and event in e.activation_events
115
+ ]