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,316 @@
1
+ """Incremental Index — mtime-watermark based change tracking.
2
+
3
+ Borrowed from Windows Search USN Journal change tracking
4
+ (C:\\ProgramData\\Microsoft\\Search\\Data\\Applications\\Windows\\Windows-usn.db):
5
+ Windows Search uses the NTFS USN (Update Sequence Number) journal to detect
6
+ file changes in O(1) instead of scanning the entire filesystem. The
7
+ ChangeTracking table tracks moves, deletes, and byte-range changes with
8
+ batch-based processing.
9
+
10
+ Since Python cannot access the raw NTFS USN journal without ctypes bindings,
11
+ this module uses file mtime + size as a watermark — the same principle but
12
+ portable across filesystems.
13
+
14
+ Pattern: B27 in ALGO.md.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import os
20
+ import time
21
+ from dataclasses import dataclass, field
22
+ from enum import IntFlag
23
+ from pathlib import Path
24
+ from typing import Any, Iterable
25
+
26
+
27
+ class ChangeType(IntFlag):
28
+ NONE = 0
29
+ CREATED = 1
30
+ MODIFIED = 2
31
+ DELETED = 4
32
+ MOVED = 8
33
+
34
+
35
+ @dataclass
36
+ class FileWatermark:
37
+ """Per-file watermark — mtime + size + inode."""
38
+ path: str
39
+ mtime: float
40
+ size: int
41
+ inode: int = 0 # for move detection on platforms that support it
42
+
43
+ def matches(self, stat: os.stat_result) -> bool:
44
+ return (self.mtime == stat.st_mtime
45
+ and self.size == stat.st_size)
46
+
47
+
48
+ @dataclass
49
+ class ChangeRecord:
50
+ """A single detected change."""
51
+ path: str
52
+ change_type: ChangeType
53
+ old_path: str | None = None # for moves
54
+ watermark: FileWatermark | None = None
55
+ timestamp: float = field(default_factory=time.time)
56
+
57
+
58
+ @dataclass
59
+ class IncrementalIndex:
60
+ """Mtime-watermark based incremental change tracker.
61
+
62
+ Usage:
63
+ tracker = IncrementalIndex.load(watermark_file)
64
+ changes = tracker.scan(directory, glob="*.py")
65
+ for change in changes:
66
+ if change.change_type & ChangeType.MODIFIED:
67
+ re_embed(change.path)
68
+ tracker.save(watermark_file)
69
+ """
70
+
71
+ watermarks: dict[str, FileWatermark] = field(default_factory=dict)
72
+ last_scan: float = 0.0
73
+ scan_count: int = 0
74
+
75
+ # --- scan -------------------------------------------------------------
76
+
77
+ def scan_paths(self, paths: Iterable[Path]) -> list[ChangeRecord]:
78
+ """Scan an explicit iterable of files and return changes.
79
+
80
+ This is the safer companion to ``scan(directory)`` for callers that
81
+ already have a filtered source list (for example, harness roots with
82
+ include/exclude patterns). It preserves the same watermark semantics
83
+ while avoiding a broad recursive walk.
84
+ """
85
+ changes: list[ChangeRecord] = []
86
+ current_files: dict[str, FileWatermark] = {}
87
+ current_inodes: set[int] = set()
88
+
89
+ for filepath in paths:
90
+ if not filepath.is_file():
91
+ continue
92
+ path_str = str(filepath)
93
+ try:
94
+ stat = filepath.stat()
95
+ except OSError:
96
+ continue
97
+
98
+ wm = FileWatermark(
99
+ path=path_str,
100
+ mtime=stat.st_mtime,
101
+ size=stat.st_size,
102
+ inode=stat.st_ino,
103
+ )
104
+ current_files[path_str] = wm
105
+ if stat.st_ino:
106
+ current_inodes.add(stat.st_ino)
107
+
108
+ old_wm = self.watermarks.get(path_str)
109
+ if old_wm is None:
110
+ moved_from = self._find_by_inode(stat.st_ino, path_str)
111
+ if moved_from:
112
+ changes.append(ChangeRecord(
113
+ path=path_str,
114
+ change_type=ChangeType.MOVED,
115
+ old_path=moved_from,
116
+ watermark=wm,
117
+ ))
118
+ else:
119
+ changes.append(ChangeRecord(
120
+ path=path_str,
121
+ change_type=ChangeType.CREATED,
122
+ watermark=wm,
123
+ ))
124
+ elif not old_wm.matches(stat):
125
+ changes.append(ChangeRecord(
126
+ path=path_str,
127
+ change_type=ChangeType.MODIFIED,
128
+ watermark=wm,
129
+ ))
130
+
131
+ for old_path, old_wm in self.watermarks.items():
132
+ if old_path not in current_files:
133
+ if old_wm.inode and old_wm.inode in current_inodes:
134
+ continue
135
+ changes.append(ChangeRecord(
136
+ path=old_path,
137
+ change_type=ChangeType.DELETED,
138
+ ))
139
+
140
+ self.watermarks = current_files
141
+ self.last_scan = time.time()
142
+ self.scan_count += 1
143
+ return changes
144
+
145
+ def scan(
146
+ self,
147
+ directory: Path,
148
+ *,
149
+ glob: str = "**/*",
150
+ exclude_dirs: set[str] | None = None,
151
+ ) -> list[ChangeRecord]:
152
+ """Scan directory and return changes since last scan.
153
+
154
+ Detects: created, modified, deleted, moved.
155
+ """
156
+ if exclude_dirs is None:
157
+ exclude_dirs = {".git", "__pycache__", ".venv", "node_modules",
158
+ ".mypy_cache", ".pytest_cache", ".ruff_cache"}
159
+
160
+ changes: list[ChangeRecord] = []
161
+ current_files: dict[str, FileWatermark] = {}
162
+
163
+ # Walk the directory
164
+ for filepath in directory.glob(glob):
165
+ if not filepath.is_file():
166
+ continue
167
+ # Skip excluded directories
168
+ rel_parts = filepath.relative_to(directory).parts
169
+ if any(part in exclude_dirs for part in rel_parts):
170
+ continue
171
+
172
+ path_str = str(filepath)
173
+ try:
174
+ stat = filepath.stat()
175
+ except OSError:
176
+ continue
177
+
178
+ wm = FileWatermark(
179
+ path=path_str,
180
+ mtime=stat.st_mtime,
181
+ size=stat.st_size,
182
+ inode=stat.st_ino,
183
+ )
184
+ current_files[path_str] = wm
185
+
186
+ old_wm = self.watermarks.get(path_str)
187
+ if old_wm is None:
188
+ # Check if this file was moved (same inode, different path)
189
+ moved_from = self._find_by_inode(stat.st_ino, path_str)
190
+ if moved_from:
191
+ changes.append(ChangeRecord(
192
+ path=path_str,
193
+ change_type=ChangeType.MOVED,
194
+ old_path=moved_from,
195
+ watermark=wm,
196
+ ))
197
+ else:
198
+ changes.append(ChangeRecord(
199
+ path=path_str,
200
+ change_type=ChangeType.CREATED,
201
+ watermark=wm,
202
+ ))
203
+ elif not old_wm.matches(stat):
204
+ changes.append(ChangeRecord(
205
+ path=path_str,
206
+ change_type=ChangeType.MODIFIED,
207
+ watermark=wm,
208
+ ))
209
+
210
+ # Detect deletions (files in watermarks but not in current_files)
211
+ for old_path, old_wm in self.watermarks.items():
212
+ if old_path not in current_files:
213
+ # Check if it was moved (inode exists under new path)
214
+ if not self._inode_exists_elsewhere(old_wm.inode, old_path):
215
+ changes.append(ChangeRecord(
216
+ path=old_path,
217
+ change_type=ChangeType.DELETED,
218
+ ))
219
+
220
+ # Update watermarks
221
+ self.watermarks = current_files
222
+ self.last_scan = time.time()
223
+ self.scan_count += 1
224
+
225
+ return changes
226
+
227
+ def _find_by_inode(self, inode: int, exclude_path: str) -> str | None:
228
+ """Find a watermark with the same inode (for move detection)."""
229
+ if inode == 0:
230
+ return None
231
+ for path, wm in self.watermarks.items():
232
+ if path != exclude_path and wm.inode == inode:
233
+ return path
234
+ return None
235
+
236
+ def _inode_exists_elsewhere(self, inode: int, exclude_path: str) -> bool:
237
+ """Check if inode exists in current scan under a different path."""
238
+ return self._find_by_inode(inode, exclude_path) is not None
239
+
240
+ # --- query ------------------------------------------------------------
241
+
242
+ def needs_update(self, path: str) -> bool:
243
+ """Check if a specific file has changed since last scan."""
244
+ filepath = Path(path)
245
+ if not filepath.exists():
246
+ return path in self.watermarks # was deleted
247
+ try:
248
+ stat = filepath.stat()
249
+ except OSError:
250
+ return False
251
+ wm = self.watermarks.get(path)
252
+ if wm is None:
253
+ return True # new file
254
+ return not wm.matches(stat)
255
+
256
+ def get_changed_files(self, directory: Path, **kwargs: Any) -> list[str]:
257
+ """Return only the paths of changed files (convenience)."""
258
+ changes = self.scan(directory, **kwargs)
259
+ return [c.path for c in changes if c.change_type != ChangeType.NONE]
260
+
261
+ # --- serialization ----------------------------------------------------
262
+
263
+ def to_dict(self) -> dict[str, Any]:
264
+ return {
265
+ "watermarks": {
266
+ path: {"mtime": wm.mtime, "size": wm.size, "inode": wm.inode}
267
+ for path, wm in self.watermarks.items()
268
+ },
269
+ "last_scan": self.last_scan,
270
+ "scan_count": self.scan_count,
271
+ }
272
+
273
+ @classmethod
274
+ def from_dict(cls, data: dict[str, Any]) -> IncrementalIndex:
275
+ tracker = cls()
276
+ for path, wm_data in data.get("watermarks", {}).items():
277
+ tracker.watermarks[path] = FileWatermark(
278
+ path=path,
279
+ mtime=wm_data["mtime"],
280
+ size=wm_data["size"],
281
+ inode=wm_data.get("inode", 0),
282
+ )
283
+ tracker.last_scan = data.get("last_scan", 0.0)
284
+ tracker.scan_count = data.get("scan_count", 0)
285
+ return tracker
286
+
287
+ def save(self, path: Path) -> None:
288
+ """Save watermarks to a JSON file."""
289
+ path.parent.mkdir(parents=True, exist_ok=True)
290
+ path.write_text(
291
+ json.dumps(self.to_dict(), indent=2),
292
+ encoding="utf-8",
293
+ )
294
+
295
+ @classmethod
296
+ def load(cls, path: Path) -> IncrementalIndex:
297
+ """Load watermarks from a JSON file.
298
+
299
+ Returns empty tracker if file doesn't exist (first scan).
300
+ """
301
+ if not path.exists():
302
+ return cls()
303
+ try:
304
+ data = json.loads(path.read_text(encoding="utf-8"))
305
+ except (json.JSONDecodeError, OSError):
306
+ return cls()
307
+ return cls.from_dict(data)
308
+
309
+ # --- stats ------------------------------------------------------------
310
+
311
+ def stats(self) -> dict[str, Any]:
312
+ return {
313
+ "tracked_files": len(self.watermarks),
314
+ "last_scan": self.last_scan,
315
+ "scan_count": self.scan_count,
316
+ }
@@ -0,0 +1,16 @@
1
+ """Small persistence helpers for project intelligence indexes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from .project_graph import ProjectGraph, build_project_graph, load_project_graph
8
+
9
+
10
+ def ensure_project_graph(root: Path | str, *, rebuild: bool = False) -> ProjectGraph:
11
+ root_path = Path(root).resolve()
12
+ if not rebuild:
13
+ graph = load_project_graph(root_path)
14
+ if graph is not None:
15
+ return graph
16
+ return build_project_graph(root_path, persist=True)
@@ -0,0 +1,133 @@
1
+ """B39. Iteration Plan + Endgame Checklist Cadence (VS Code Project Pattern).
2
+
3
+ Manages monthly iteration plans and release endgame checklists so
4
+ development stays disciplined as features accumulate.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from datetime import datetime
11
+ from enum import Enum
12
+ from typing import Any
13
+
14
+
15
+ class CheckState(Enum):
16
+ UNCHECKED = "unchecked"
17
+ PASSED = "passed"
18
+ FAILED = "failed"
19
+ BLOCKED = "blocked"
20
+
21
+
22
+ @dataclass
23
+ class ChecklistItem:
24
+ name: str
25
+ description: str = ""
26
+ state: CheckState = CheckState.UNCHECKED
27
+ blocker: bool = False # if True, failed state blocks release
28
+
29
+
30
+ @dataclass
31
+ class IterationPlan:
32
+ month: str # "YYYY-MM"
33
+ goals: list[str] = field(default_factory=list)
34
+ work_items: list[dict[str, str]] = field(default_factory=list) # {label, kind, status}
35
+ created_at: str = field(default_factory=lambda: datetime.now().isoformat())
36
+
37
+
38
+ @dataclass
39
+ class EndgameChecklist:
40
+ release_version: str
41
+ items: list[ChecklistItem] = field(default_factory=list)
42
+ created_at: str = field(default_factory=lambda: datetime.now().isoformat())
43
+
44
+ def add_item(self, name: str, description: str = "", blocker: bool = False) -> None:
45
+ self.items.append(ChecklistItem(name=name, description=description, blocker=blocker))
46
+
47
+ def mark(self, name: str, state: CheckState) -> bool:
48
+ for item in self.items:
49
+ if item.name == name:
50
+ item.state = state
51
+ return True
52
+ return False
53
+
54
+ def release_ready(self) -> bool:
55
+ """True if no blocker item is failed or unchecked."""
56
+ for item in self.items:
57
+ if item.blocker and item.state != CheckState.PASSED:
58
+ return False
59
+ return True
60
+
61
+ def blockers(self) -> list[ChecklistItem]:
62
+ """Return unresolved blocker items."""
63
+ return [i for i in self.items if i.blocker and i.state != CheckState.PASSED]
64
+
65
+ def summary(self) -> dict[str, Any]:
66
+ total = len(self.items)
67
+ passed = sum(1 for i in self.items if i.state == CheckState.PASSED)
68
+ failed = sum(1 for i in self.items if i.state == CheckState.FAILED)
69
+ unchecked = sum(1 for i in self.items if i.state == CheckState.UNCHECKED)
70
+ blocked = sum(1 for i in self.items if i.state == CheckState.BLOCKED)
71
+ return {
72
+ "release": self.release_version,
73
+ "total": total,
74
+ "passed": passed,
75
+ "failed": failed,
76
+ "unchecked": unchecked,
77
+ "blocked": blocked,
78
+ "release_ready": self.release_ready(),
79
+ "blockers": [b.name for b in self.blockers()],
80
+ }
81
+
82
+
83
+ def default_endgame_checklist(version: str) -> EndgameChecklist:
84
+ """Create a standard release endgame checklist."""
85
+ cl = EndgameChecklist(release_version=version)
86
+ cl.add_item("tests_pass", "All tests pass (pytest -q)", blocker=True)
87
+ cl.add_item("no_skip_markers", "No new pytest.skip markers without justification", blocker=False)
88
+ cl.add_item("help_updated", "/help text updated for new commands", blocker=True)
89
+ cl.add_item("docs_updated", "ALGO.md and README updated", blocker=False)
90
+ cl.add_item("migration_notes", "Migration notes for breaking changes", blocker=True)
91
+ cl.add_item("safety_gates", "Safety gates verified (external send, delete, payments)", blocker=True)
92
+ cl.add_item("rollback_plan", "Rollback plan documented", blocker=False)
93
+ cl.add_item("changelog", "Changelog/changefiles written", blocker=False)
94
+ return cl
95
+
96
+
97
+ class IterationPlanManager:
98
+ """Manages iteration plans and endgame checklists."""
99
+
100
+ def __init__(self) -> None:
101
+ self.plans: dict[str, IterationPlan] = {}
102
+ self.checklists: dict[str, EndgameChecklist] = {}
103
+
104
+ def create_plan(self, month: str, goals: list[str] | None = None) -> IterationPlan:
105
+ plan = IterationPlan(month=month, goals=goals or [])
106
+ self.plans[month] = plan
107
+ return plan
108
+
109
+ def add_work_item(self, month: str, label: str, kind: str, status: str = "open") -> bool:
110
+ plan = self.plans.get(month)
111
+ if not plan:
112
+ return False
113
+ plan.work_items.append({"label": label, "kind": kind, "status": status})
114
+ return True
115
+
116
+ def create_checklist(self, version: str) -> EndgameChecklist:
117
+ cl = default_endgame_checklist(version)
118
+ self.checklists[version] = cl
119
+ return cl
120
+
121
+ def current_plan(self) -> IterationPlan | None:
122
+ if not self.plans:
123
+ return None
124
+ return self.plans[max(self.plans.keys())]
125
+
126
+ def roadmap_summary(self) -> dict[str, Any]:
127
+ current = self.current_plan()
128
+ return {
129
+ "current_month": current.month if current else None,
130
+ "goals": current.goals if current else [],
131
+ "open_items": sum(1 for w in (current.work_items if current else []) if w.get("status") == "open"),
132
+ "checklists": {v: cl.summary() for v, cl in self.checklists.items()},
133
+ }
@@ -0,0 +1,167 @@
1
+ """B45. Kernel Plugin Architecture with Typed Annotations (Semantic Kernel Pattern).
2
+
3
+ Tools are plain Python methods decorated with @kernel_function and typed
4
+ with Annotated[T, "description"]. The kernel introspects the function
5
+ signature and auto-generates the tool schema — no manual JSON schema
6
+ authoring. Structured outputs use Pydantic BaseModel with response_format.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import inspect
12
+ from dataclasses import dataclass, field
13
+ from typing import Any, Callable, get_type_hints
14
+
15
+
16
+ @dataclass
17
+ class ToolParam:
18
+ name: str
19
+ type: str
20
+ description: str
21
+ required: bool
22
+
23
+
24
+ @dataclass
25
+ class ToolSchema:
26
+ name: str
27
+ description: str
28
+ params: list[ToolParam] = field(default_factory=list)
29
+ return_type: str = "string"
30
+ return_description: str = ""
31
+
32
+
33
+ def kernel_function(description: str = "") -> Callable:
34
+ """Decorator that marks a method as a kernel-callable tool."""
35
+ def decorator(fn: Callable) -> Callable:
36
+ fn._kernel_description = description or fn.__doc__ or ""
37
+ fn._kernel_function = True
38
+ return fn
39
+ return decorator
40
+
41
+
42
+ def introspect_tool(fn: Callable) -> ToolSchema:
43
+ """Auto-generate tool schema from function signature + Annotated hints."""
44
+ sig = inspect.signature(fn)
45
+ try:
46
+ hints = get_type_hints(fn, include_extras=True)
47
+ except Exception:
48
+ hints = {}
49
+
50
+ params: list[ToolParam] = []
51
+ for name, param in sig.parameters.items():
52
+ if name == "self":
53
+ continue
54
+ hint = hints.get(name, str)
55
+ desc = ""
56
+ type_name = "string"
57
+
58
+ # Extract Annotated[T, "description"]
59
+ if hasattr(hint, "__metadata__"):
60
+ origin = getattr(hint, "__origin__", str)
61
+ type_name = getattr(origin, "__name__", "string")
62
+ if hint.__metadata__:
63
+ desc = str(hint.__metadata__[0])
64
+ else:
65
+ type_name = getattr(hint, "__name__", "string")
66
+
67
+ params.append(ToolParam(
68
+ name=name,
69
+ type=type_name,
70
+ description=desc,
71
+ required=param.default is inspect.Parameter.empty,
72
+ ))
73
+
74
+ # Return type
75
+ ret_hint = hints.get("return", type(None))
76
+ ret_desc = ""
77
+ if hasattr(ret_hint, "__metadata__"):
78
+ ret_desc = str(ret_hint.__metadata__[0]) if ret_hint.__metadata__ else ""
79
+ ret_origin = getattr(ret_hint, "__origin__", ret_hint)
80
+ ret_type = getattr(ret_origin, "__name__", "void")
81
+
82
+ return ToolSchema(
83
+ name=fn.__name__,
84
+ description=getattr(fn, "_kernel_description", fn.__doc__ or ""),
85
+ params=params,
86
+ return_type=ret_type,
87
+ return_description=ret_desc,
88
+ )
89
+
90
+
91
+ class Kernel:
92
+ """Central kernel that manages plugins and invokes tools."""
93
+
94
+ def __init__(self) -> None:
95
+ self._plugins: dict[str, Any] = {}
96
+ self._schemas: dict[str, ToolSchema] = {}
97
+ self._handlers: dict[str, Callable] = {}
98
+
99
+ def add_plugin(self, plugin: Any, name: str | None = None) -> None:
100
+ """Register a plugin class — all @kernel_function methods become tools."""
101
+ plugin_name = name or plugin.__class__.__name__
102
+ self._plugins[plugin_name] = plugin
103
+ for attr_name in dir(plugin):
104
+ attr = getattr(plugin, attr_name, None)
105
+ if callable(attr) and getattr(attr, "_kernel_function", False):
106
+ schema = introspect_tool(attr)
107
+ self._schemas[schema.name] = schema
108
+ self._handlers[schema.name] = attr
109
+
110
+ def list_tools(self) -> list[ToolSchema]:
111
+ return list(self._schemas.values())
112
+
113
+ def get_tool_schema(self, name: str) -> ToolSchema | None:
114
+ return self._schemas.get(name)
115
+
116
+ def invoke(self, tool_name: str, **kwargs: Any) -> str:
117
+ """Invoke a registered tool by name."""
118
+ handler = self._handlers.get(tool_name)
119
+ if not handler:
120
+ return f"Tool '{tool_name}' not found"
121
+ try:
122
+ result = handler(**kwargs)
123
+ return str(result)
124
+ except Exception as e:
125
+ return f"Tool '{tool_name}' error: {e}"
126
+
127
+ def tool_descriptions_for_prompt(self) -> str:
128
+ """Generate tool description text for inclusion in system prompt."""
129
+ lines: list[str] = []
130
+ for schema in self._schemas.values():
131
+ param_str = ", ".join(
132
+ f"{p.name}: {p.type}" + (f" — {p.description}" if p.description else "")
133
+ for p in schema.params
134
+ )
135
+ lines.append(f"- {schema.name}({param_str}): {schema.description}")
136
+ return "\n".join(lines)
137
+
138
+
139
+ # ── structured output support ─────────────────────────────────────────
140
+
141
+
142
+ def validate_structured_output(data: dict, model_class: type) -> dict:
143
+ """Validate LLM output against a Pydantic-like BaseModel.
144
+
145
+ Works with any class that has __annotations__ (dataclass or Pydantic).
146
+ """
147
+ try:
148
+ from pydantic import BaseModel
149
+ if issubclass(model_class, BaseModel):
150
+ obj = model_class(**data)
151
+ return {"valid": True, "data": obj.model_dump()}
152
+ except ImportError:
153
+ pass
154
+ except Exception as e:
155
+ return {"valid": False, "error": str(e)}
156
+
157
+ # Fallback: simple annotation-based validation
158
+ annotations = getattr(model_class, "__annotations__", {})
159
+ errors: list[str] = []
160
+ for field_name, field_type in annotations.items():
161
+ if field_name not in data:
162
+ errors.append(f"missing field: {field_name}")
163
+ elif not isinstance(data[field_name], field_type):
164
+ errors.append(f"{field_name}: expected {field_type.__name__}, got {type(data[field_name]).__name__}")
165
+ if errors:
166
+ return {"valid": False, "error": "; ".join(errors)}
167
+ return {"valid": True, "data": data}