velune-cli 0.9.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 (279) hide show
  1. velune/__init__.py +5 -0
  2. velune/__main__.py +6 -0
  3. velune/cli/__init__.py +5 -0
  4. velune/cli/app.py +208 -0
  5. velune/cli/autocomplete.py +80 -0
  6. velune/cli/banner.py +60 -0
  7. velune/cli/commands/__init__.py +32 -0
  8. velune/cli/commands/ask.py +175 -0
  9. velune/cli/commands/base.py +16 -0
  10. velune/cli/commands/chat.py +228 -0
  11. velune/cli/commands/config.py +224 -0
  12. velune/cli/commands/daemon.py +88 -0
  13. velune/cli/commands/doctor.py +721 -0
  14. velune/cli/commands/init.py +170 -0
  15. velune/cli/commands/mcp.py +82 -0
  16. velune/cli/commands/memory.py +293 -0
  17. velune/cli/commands/models.py +683 -0
  18. velune/cli/commands/preflight.py +95 -0
  19. velune/cli/commands/run.py +270 -0
  20. velune/cli/commands/setup.py +184 -0
  21. velune/cli/commands/workspace.py +249 -0
  22. velune/cli/context.py +36 -0
  23. velune/cli/councilmodel_ui.py +199 -0
  24. velune/cli/display/council_view.py +254 -0
  25. velune/cli/display/memory_view.py +126 -0
  26. velune/cli/display/panels.py +35 -0
  27. velune/cli/display/progress.py +25 -0
  28. velune/cli/display/themes.py +25 -0
  29. velune/cli/main.py +15 -0
  30. velune/cli/model_selector.py +51 -0
  31. velune/cli/modes.py +86 -0
  32. velune/cli/pull_ui.py +123 -0
  33. velune/cli/registry.py +80 -0
  34. velune/cli/rendering/__init__.py +5 -0
  35. velune/cli/rendering/error_panel.py +79 -0
  36. velune/cli/rendering/markdown.py +63 -0
  37. velune/cli/repl.py +1855 -0
  38. velune/cli/session_manager.py +71 -0
  39. velune/cli/slash_commands.py +37 -0
  40. velune/cli/theme.py +8 -0
  41. velune/cognition/__init__.py +23 -0
  42. velune/cognition/agents/__init__.py +7 -0
  43. velune/cognition/agents/coder.py +209 -0
  44. velune/cognition/agents/planner.py +156 -0
  45. velune/cognition/agents/reviewer.py +195 -0
  46. velune/cognition/arbitrator.py +220 -0
  47. velune/cognition/architecture.py +415 -0
  48. velune/cognition/budget.py +65 -0
  49. velune/cognition/council/__init__.py +47 -0
  50. velune/cognition/council/base.py +217 -0
  51. velune/cognition/council/challenger.py +74 -0
  52. velune/cognition/council/coder.py +79 -0
  53. velune/cognition/council/critic_agent.py +43 -0
  54. velune/cognition/council/critic_configs.py +111 -0
  55. velune/cognition/council/critics.py +41 -0
  56. velune/cognition/council/debate.py +46 -0
  57. velune/cognition/council/factory.py +140 -0
  58. velune/cognition/council/messages.py +56 -0
  59. velune/cognition/council/planner.py +124 -0
  60. velune/cognition/council/reviewer.py +74 -0
  61. velune/cognition/council/synthesizer.py +67 -0
  62. velune/cognition/council/tiers.py +188 -0
  63. velune/cognition/council_orchestrator.py +282 -0
  64. velune/cognition/firewall.py +354 -0
  65. velune/cognition/module.py +46 -0
  66. velune/cognition/orchestrator.py +1205 -0
  67. velune/cognition/personality.py +238 -0
  68. velune/cognition/state.py +104 -0
  69. velune/cognition/style_resolver.py +64 -0
  70. velune/cognition/verification.py +205 -0
  71. velune/context/__init__.py +28 -0
  72. velune/context/assembler.py +240 -0
  73. velune/context/budget.py +97 -0
  74. velune/context/extractive.py +95 -0
  75. velune/context/prompt_adaptation.py +480 -0
  76. velune/context/sections.py +99 -0
  77. velune/context/token_counter.py +134 -0
  78. velune/context/utilization.py +33 -0
  79. velune/context/window.py +63 -0
  80. velune/core/__init__.py +89 -0
  81. velune/core/background.py +5 -0
  82. velune/core/config/__init__.py +37 -0
  83. velune/core/errors/__init__.py +90 -0
  84. velune/core/errors/catalog.py +188 -0
  85. velune/core/errors/execution.py +31 -0
  86. velune/core/errors/memory.py +25 -0
  87. velune/core/errors/orchestration.py +31 -0
  88. velune/core/errors/provider.py +37 -0
  89. velune/core/event_loop.py +35 -0
  90. velune/core/logging.py +83 -0
  91. velune/core/paths.py +165 -0
  92. velune/core/runtime.py +113 -0
  93. velune/core/startup_profiler.py +56 -0
  94. velune/core/task_registry.py +117 -0
  95. velune/core/trace.py +83 -0
  96. velune/core/types/__init__.py +48 -0
  97. velune/core/types/agent.py +53 -0
  98. velune/core/types/context.py +42 -0
  99. velune/core/types/inference.py +38 -0
  100. velune/core/types/memory.py +42 -0
  101. velune/core/types/model.py +70 -0
  102. velune/core/types/provider.py +62 -0
  103. velune/core/types/repository.py +38 -0
  104. velune/core/types/task.py +61 -0
  105. velune/core/types/workspace.py +28 -0
  106. velune/daemon/client.py +13 -0
  107. velune/daemon/server.py +127 -0
  108. velune/daemon/transport.py +179 -0
  109. velune/events.py +204 -0
  110. velune/execution/__init__.py +22 -0
  111. velune/execution/benchmarker.py +315 -0
  112. velune/execution/cancellation.py +53 -0
  113. velune/execution/checkpointer.py +130 -0
  114. velune/execution/command_spec.py +165 -0
  115. velune/execution/diff_preview.py +197 -0
  116. velune/execution/executor.py +181 -0
  117. velune/execution/module.py +18 -0
  118. velune/execution/multi_diff.py +67 -0
  119. velune/execution/path_guard.py +74 -0
  120. velune/execution/planner.py +91 -0
  121. velune/execution/rollback.py +89 -0
  122. velune/execution/sandbox.py +268 -0
  123. velune/execution/validator.py +115 -0
  124. velune/hardware/__init__.py +1 -0
  125. velune/hardware/detector.py +192 -0
  126. velune/kernel/__init__.py +55 -0
  127. velune/kernel/bootstrap.py +125 -0
  128. velune/kernel/config.py +426 -0
  129. velune/kernel/entrypoint.py +78 -0
  130. velune/kernel/health.py +54 -0
  131. velune/kernel/lifecycle.py +143 -0
  132. velune/kernel/module.py +17 -0
  133. velune/kernel/modules.py +23 -0
  134. velune/kernel/registry.py +96 -0
  135. velune/kernel/schemas.py +28 -0
  136. velune/main.py +9 -0
  137. velune/mcp/__init__.py +9 -0
  138. velune/mcp/client.py +115 -0
  139. velune/mcp/config.py +19 -0
  140. velune/mcp/server.py +624 -0
  141. velune/memory/__init__.py +32 -0
  142. velune/memory/compaction.py +506 -0
  143. velune/memory/embedding_pipeline.py +241 -0
  144. velune/memory/lifecycle.py +680 -0
  145. velune/memory/module.py +218 -0
  146. velune/memory/prioritizer.py +67 -0
  147. velune/memory/storage/episodic_schema.sql +53 -0
  148. velune/memory/storage/lancedb_store.py +282 -0
  149. velune/memory/storage/sqlite_manager.py +369 -0
  150. velune/memory/storage/sqlite_pool.py +149 -0
  151. velune/memory/tiers/episodic.py +588 -0
  152. velune/memory/tiers/graph.py +378 -0
  153. velune/memory/tiers/lineage.py +416 -0
  154. velune/memory/tiers/semantic.py +475 -0
  155. velune/memory/tiers/working.py +168 -0
  156. velune/memory/vitality.py +132 -0
  157. velune/models/__init__.py +15 -0
  158. velune/models/family.py +76 -0
  159. velune/models/module.py +20 -0
  160. velune/models/probes.py +192 -0
  161. velune/models/profile_cache.py +84 -0
  162. velune/models/profiler.py +108 -0
  163. velune/models/registry.py +251 -0
  164. velune/models/scorer.py +233 -0
  165. velune/models/specializations.py +205 -0
  166. velune/orchestration/__init__.py +19 -0
  167. velune/orchestration/engine.py +239 -0
  168. velune/orchestration/module.py +15 -0
  169. velune/orchestration/role_assignments.py +82 -0
  170. velune/orchestration/schemas.py +98 -0
  171. velune/plugins/__init__.py +20 -0
  172. velune/plugins/hooks.py +50 -0
  173. velune/plugins/loader.py +161 -0
  174. velune/plugins/registry.py +56 -0
  175. velune/plugins/schemas.py +21 -0
  176. velune/providers/__init__.py +23 -0
  177. velune/providers/adapters/anthropic.py +257 -0
  178. velune/providers/adapters/fireworks.py +115 -0
  179. velune/providers/adapters/google.py +234 -0
  180. velune/providers/adapters/groq.py +151 -0
  181. velune/providers/adapters/huggingface.py +210 -0
  182. velune/providers/adapters/llamacpp.py +208 -0
  183. velune/providers/adapters/lmstudio.py +175 -0
  184. velune/providers/adapters/ollama.py +233 -0
  185. velune/providers/adapters/openai.py +213 -0
  186. velune/providers/adapters/openrouter.py +81 -0
  187. velune/providers/adapters/together.py +134 -0
  188. velune/providers/adapters/xai.py +60 -0
  189. velune/providers/base.py +86 -0
  190. velune/providers/benchmarker.py +138 -0
  191. velune/providers/discovery/__init__.py +33 -0
  192. velune/providers/discovery/anthropic.py +79 -0
  193. velune/providers/discovery/benchmarks.py +44 -0
  194. velune/providers/discovery/classifier.py +69 -0
  195. velune/providers/discovery/fireworks.py +95 -0
  196. velune/providers/discovery/gguf.py +88 -0
  197. velune/providers/discovery/google.py +95 -0
  198. velune/providers/discovery/gpu.py +117 -0
  199. velune/providers/discovery/groq.py +21 -0
  200. velune/providers/discovery/huggingface.py +67 -0
  201. velune/providers/discovery/lmstudio.py +80 -0
  202. velune/providers/discovery/ollama.py +162 -0
  203. velune/providers/discovery/openai.py +96 -0
  204. velune/providers/discovery/openrouter.py +113 -0
  205. velune/providers/discovery/scanner.py +115 -0
  206. velune/providers/discovery/together.py +114 -0
  207. velune/providers/discovery/xai.py +57 -0
  208. velune/providers/health.py +67 -0
  209. velune/providers/health_monitor.py +169 -0
  210. velune/providers/keystore.py +142 -0
  211. velune/providers/local_paths.py +49 -0
  212. velune/providers/local_resolver.py +229 -0
  213. velune/providers/module.py +51 -0
  214. velune/providers/ollama_manager.py +193 -0
  215. velune/providers/registry.py +220 -0
  216. velune/providers/router.py +255 -0
  217. velune/providers/task_classifier.py +288 -0
  218. velune/py.typed +0 -0
  219. velune/repository/__init__.py +33 -0
  220. velune/repository/analyzer.py +127 -0
  221. velune/repository/ast_parser.py +822 -0
  222. velune/repository/blast_radius.py +298 -0
  223. velune/repository/boundary_classifier.py +295 -0
  224. velune/repository/cognition.py +316 -0
  225. velune/repository/grapher.py +179 -0
  226. velune/repository/import_graph.py +263 -0
  227. velune/repository/incremental_indexer.py +275 -0
  228. velune/repository/index_state.py +96 -0
  229. velune/repository/indexer.py +243 -0
  230. velune/repository/module.py +17 -0
  231. velune/repository/parser.py +474 -0
  232. velune/repository/project_type.py +300 -0
  233. velune/repository/rename_journal.py +287 -0
  234. velune/repository/scanner.py +193 -0
  235. velune/repository/schemas.py +102 -0
  236. velune/repository/symbol_registry.py +365 -0
  237. velune/repository/tracker.py +252 -0
  238. velune/retrieval/__init__.py +27 -0
  239. velune/retrieval/cache.py +110 -0
  240. velune/retrieval/fast_path.py +391 -0
  241. velune/retrieval/graph.py +124 -0
  242. velune/retrieval/hybrid.py +271 -0
  243. velune/retrieval/keyword.py +131 -0
  244. velune/retrieval/module.py +26 -0
  245. velune/retrieval/pipeline.py +303 -0
  246. velune/retrieval/reranker.py +102 -0
  247. velune/retrieval/schemas.py +59 -0
  248. velune/retrieval/slow_path.py +364 -0
  249. velune/retrieval/vector.py +203 -0
  250. velune/telemetry/__init__.py +59 -0
  251. velune/telemetry/cognition.py +267 -0
  252. velune/telemetry/cost_estimator.py +92 -0
  253. velune/telemetry/debug.py +304 -0
  254. velune/telemetry/doctor.py +244 -0
  255. velune/telemetry/logging.py +286 -0
  256. velune/telemetry/spans.py +277 -0
  257. velune/telemetry/token_tracker.py +140 -0
  258. velune/telemetry/usage_tracker.py +340 -0
  259. velune/tools/__init__.py +41 -0
  260. velune/tools/base/registry.py +87 -0
  261. velune/tools/base/tool.py +63 -0
  262. velune/tools/code/navigate.py +116 -0
  263. velune/tools/code/search.py +123 -0
  264. velune/tools/filesystem/read.py +75 -0
  265. velune/tools/filesystem/search.py +136 -0
  266. velune/tools/filesystem/write.py +163 -0
  267. velune/tools/git/history.py +177 -0
  268. velune/tools/git/operations.py +122 -0
  269. velune/tools/git/state.py +121 -0
  270. velune/tools/module.py +81 -0
  271. velune/tools/terminal/execute.py +72 -0
  272. velune/tools/terminal/history.py +47 -0
  273. velune/tools/web/fetch.py +55 -0
  274. velune/tools/web/validator.py +122 -0
  275. velune_cli-0.9.0.dist-info/METADATA +518 -0
  276. velune_cli-0.9.0.dist-info/RECORD +279 -0
  277. velune_cli-0.9.0.dist-info/WHEEL +4 -0
  278. velune_cli-0.9.0.dist-info/entry_points.txt +2 -0
  279. velune_cli-0.9.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,416 @@
1
+ """Decision Lineage and Failed Experiment Memory Tier.
2
+
3
+ SQLite-backed persistent store for long-running cognitive continuity and
4
+ failed-approach blocking.
5
+
6
+ All I/O is async and routed through
7
+ :class:`~velune.memory.storage.sqlite_pool.SQLiteConnectionPool` so
8
+ concurrent coroutines never cause WAL write contention.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import logging
15
+ import time
16
+ from typing import Any
17
+
18
+ from velune.memory.storage.sqlite_pool import SQLiteConnectionPool
19
+
20
+ logger = logging.getLogger("velune.memory.tiers.lineage")
21
+
22
+ _SCHEMA_SQL = """
23
+ CREATE TABLE IF NOT EXISTS decision_nodes (
24
+ id TEXT PRIMARY KEY,
25
+ timestamp REAL NOT NULL,
26
+ target_subsystem TEXT NOT NULL,
27
+ rationale TEXT NOT NULL,
28
+ architectural_impact REAL DEFAULT 0.0,
29
+ consequences TEXT
30
+ );
31
+
32
+ CREATE TABLE IF NOT EXISTS design_alternatives (
33
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
34
+ decision_id TEXT NOT NULL,
35
+ option_name TEXT NOT NULL,
36
+ tradeoffs TEXT,
37
+ rejected_reason TEXT,
38
+ FOREIGN KEY(decision_id) REFERENCES decision_nodes(id) ON DELETE CASCADE
39
+ );
40
+
41
+ CREATE TABLE IF NOT EXISTS failed_experiments (
42
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
43
+ timestamp REAL NOT NULL,
44
+ target_subsystem TEXT NOT NULL,
45
+ patch TEXT NOT NULL,
46
+ error_type TEXT NOT NULL,
47
+ error_message TEXT NOT NULL
48
+ );
49
+
50
+ CREATE TABLE IF NOT EXISTS repo_personality_styles (
51
+ subsystem TEXT PRIMARY KEY,
52
+ naming_conventions TEXT NOT NULL,
53
+ type_hinting_strictness REAL NOT NULL,
54
+ preferred_constructs TEXT NOT NULL,
55
+ class_vs_functional TEXT NOT NULL,
56
+ docstring_style TEXT NOT NULL,
57
+ updated_at REAL NOT NULL
58
+ );
59
+
60
+ CREATE TABLE IF NOT EXISTS repository_evolution_timeline (
61
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
62
+ timestamp REAL NOT NULL,
63
+ subsystem TEXT NOT NULL,
64
+ lcom_average REAL NOT NULL,
65
+ coupling_ratio REAL NOT NULL,
66
+ debt_items_count INTEGER NOT NULL,
67
+ major_milestone TEXT,
68
+ rationales_summary TEXT NOT NULL
69
+ );
70
+
71
+ CREATE INDEX IF NOT EXISTS idx_decisions_subsystem ON decision_nodes(target_subsystem);
72
+ CREATE INDEX IF NOT EXISTS idx_failures_subsystem ON failed_experiments(target_subsystem);
73
+ CREATE INDEX IF NOT EXISTS idx_personality_subsystem ON repo_personality_styles(subsystem);
74
+ CREATE INDEX IF NOT EXISTS idx_evolution_subsystem ON repository_evolution_timeline(subsystem);
75
+ """
76
+
77
+
78
+ class LineageMemoryTier:
79
+ """Persistent storage tier for architectural decisions (DLS) and failed experiments (FEL).
80
+
81
+ All reads and writes are async and serialised through an
82
+ :class:`~velune.memory.storage.sqlite_pool.SQLiteConnectionPool` to
83
+ prevent file-locking contention.
84
+ """
85
+
86
+ def __init__(self, pool: SQLiteConnectionPool) -> None:
87
+ self._pool = pool
88
+
89
+ # ------------------------------------------------------------------
90
+ # Lifecycle
91
+ # ------------------------------------------------------------------
92
+
93
+ async def initialize(self) -> None:
94
+ """Create tables and indexes if they do not yet exist."""
95
+ await self._init_db()
96
+
97
+ async def _init_db(self) -> None:
98
+ async with self._pool.write() as conn:
99
+ for stmt in _SCHEMA_SQL.split(";"):
100
+ stmt = stmt.strip()
101
+ if stmt:
102
+ await conn.execute(stmt)
103
+ logger.debug("LineageMemoryTier schema initialised.")
104
+
105
+ # ------------------------------------------------------------------
106
+ # Write operations
107
+ # ------------------------------------------------------------------
108
+
109
+ async def log_decision(
110
+ self,
111
+ decision_id: str,
112
+ target_subsystem: str,
113
+ rationale: str,
114
+ architectural_impact: float = 0.0,
115
+ consequences: str | None = None,
116
+ alternatives: list[dict[str, Any]] | None = None,
117
+ ) -> None:
118
+ """Log an approved architectural decision and its design trade-offs."""
119
+ decision_sql = """
120
+ INSERT INTO decision_nodes
121
+ (id, timestamp, target_subsystem, rationale, architectural_impact, consequences)
122
+ VALUES (?, ?, ?, ?, ?, ?)
123
+ ON CONFLICT(id) DO UPDATE SET
124
+ target_subsystem=excluded.target_subsystem,
125
+ rationale=excluded.rationale,
126
+ architectural_impact=excluded.architectural_impact,
127
+ consequences=excluded.consequences
128
+ """
129
+ params = (
130
+ decision_id,
131
+ time.time(),
132
+ target_subsystem,
133
+ rationale,
134
+ architectural_impact,
135
+ consequences or "",
136
+ )
137
+ try:
138
+ async with self._pool.write() as conn:
139
+ await conn.execute(decision_sql, params)
140
+ if alternatives:
141
+ await conn.execute(
142
+ "DELETE FROM design_alternatives WHERE decision_id = ?",
143
+ (decision_id,),
144
+ )
145
+ for alt in alternatives:
146
+ await conn.execute(
147
+ """
148
+ INSERT INTO design_alternatives
149
+ (decision_id, option_name, tradeoffs, rejected_reason)
150
+ VALUES (?, ?, ?, ?)
151
+ """,
152
+ (
153
+ decision_id,
154
+ alt.get("option_name", "Option"),
155
+ json.dumps(alt.get("tradeoffs", {})),
156
+ alt.get("rejected_reason", ""),
157
+ ),
158
+ )
159
+ except Exception as exc:
160
+ logger.error("Checkpoint save failed: %s", exc)
161
+ raise
162
+
163
+ async def log_failed_experiment(
164
+ self,
165
+ target_subsystem: str,
166
+ patch: str,
167
+ error_type: str,
168
+ error_message: str,
169
+ ) -> None:
170
+ """Log a failed implementation experiment (FEL) to prevent repeating it."""
171
+ try:
172
+ async with self._pool.write() as conn:
173
+ await conn.execute(
174
+ """
175
+ INSERT INTO failed_experiments
176
+ (timestamp, target_subsystem, patch, error_type, error_message)
177
+ VALUES (?, ?, ?, ?, ?)
178
+ """,
179
+ (time.time(), target_subsystem, patch, error_type, error_message),
180
+ )
181
+ except Exception as exc:
182
+ logger.error("Failed to log experiment: %s", exc)
183
+
184
+ async def save_personality_style(
185
+ self,
186
+ subsystem: str,
187
+ naming_conventions: dict,
188
+ type_hinting_strictness: float,
189
+ preferred_constructs: list,
190
+ class_vs_functional: str,
191
+ docstring_style: str,
192
+ ) -> None:
193
+ """Persist a subsystem's repository personality and style profile."""
194
+ try:
195
+ async with self._pool.write() as conn:
196
+ await conn.execute(
197
+ """
198
+ INSERT INTO repo_personality_styles (
199
+ subsystem, naming_conventions, type_hinting_strictness,
200
+ preferred_constructs, class_vs_functional, docstring_style,
201
+ updated_at
202
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
203
+ ON CONFLICT(subsystem) DO UPDATE SET
204
+ naming_conventions=excluded.naming_conventions,
205
+ type_hinting_strictness=excluded.type_hinting_strictness,
206
+ preferred_constructs=excluded.preferred_constructs,
207
+ class_vs_functional=excluded.class_vs_functional,
208
+ docstring_style=excluded.docstring_style,
209
+ updated_at=excluded.updated_at
210
+ """,
211
+ (
212
+ subsystem,
213
+ json.dumps(naming_conventions),
214
+ type_hinting_strictness,
215
+ json.dumps(preferred_constructs),
216
+ class_vs_functional,
217
+ docstring_style,
218
+ time.time(),
219
+ ),
220
+ )
221
+ except Exception as exc:
222
+ logger.error("Failed to save personality style: %s", exc)
223
+
224
+ async def log_monthly_snapshot(
225
+ self,
226
+ subsystem: str,
227
+ lcom_average: float,
228
+ coupling_ratio: float,
229
+ debt_items_count: int,
230
+ milestone: str | None = None,
231
+ rationale_summary: str = "",
232
+ ) -> None:
233
+ """Persist a monthly architecture snapshot for the given subsystem."""
234
+ try:
235
+ async with self._pool.write() as conn:
236
+ await conn.execute(
237
+ """
238
+ INSERT INTO repository_evolution_timeline
239
+ (timestamp, subsystem, lcom_average, coupling_ratio,
240
+ debt_items_count, major_milestone, rationales_summary)
241
+ VALUES (?, ?, ?, ?, ?, ?, ?)
242
+ """,
243
+ (
244
+ time.time(),
245
+ subsystem,
246
+ round(lcom_average, 4),
247
+ round(coupling_ratio, 4),
248
+ int(debt_items_count),
249
+ milestone or "",
250
+ rationale_summary,
251
+ ),
252
+ )
253
+ except Exception as exc:
254
+ logger.error("Failed to log monthly snapshot: %s", exc)
255
+
256
+ # ------------------------------------------------------------------
257
+ # Read operations
258
+ # ------------------------------------------------------------------
259
+
260
+ async def get_subsystem_decisions(self, subsystem: str) -> list[dict[str, Any]]:
261
+ """Fetch all decisions matching a target subsystem."""
262
+ try:
263
+ async with self._pool.read() as conn:
264
+ cursor = await conn.execute(
265
+ """
266
+ SELECT id, timestamp, target_subsystem, rationale,
267
+ architectural_impact, consequences
268
+ FROM decision_nodes
269
+ WHERE target_subsystem LIKE ?
270
+ ORDER BY timestamp DESC
271
+ """,
272
+ (f"%{subsystem}%",),
273
+ )
274
+ rows = await cursor.fetchall()
275
+
276
+ decisions = []
277
+ for r in rows:
278
+ dec = dict(r)
279
+ async with self._pool.read() as conn2:
280
+ cur2 = await conn2.execute(
281
+ "SELECT option_name, tradeoffs, rejected_reason"
282
+ " FROM design_alternatives WHERE decision_id = ?",
283
+ (dec["id"],),
284
+ )
285
+ alt_rows = await cur2.fetchall()
286
+ dec["alternatives"] = []
287
+ for ar in alt_rows:
288
+ alt = dict(ar)
289
+ try:
290
+ alt["tradeoffs"] = json.loads(alt["tradeoffs"])
291
+ except Exception:
292
+ alt["tradeoffs"] = {}
293
+ dec["alternatives"].append(alt)
294
+ decisions.append(dec)
295
+ return decisions
296
+ except Exception as exc:
297
+ logger.error("Failed to query subsystem decisions: %s", exc)
298
+ return []
299
+
300
+ async def get_failed_experiments(self, subsystem: str) -> list[dict[str, Any]]:
301
+ """Fetch all failed experiment records matching a target subsystem."""
302
+ try:
303
+ async with self._pool.read() as conn:
304
+ cursor = await conn.execute(
305
+ """
306
+ SELECT id, timestamp, target_subsystem, patch,
307
+ error_type, error_message
308
+ FROM failed_experiments
309
+ WHERE target_subsystem LIKE ?
310
+ ORDER BY timestamp DESC
311
+ """,
312
+ (f"%{subsystem}%",),
313
+ )
314
+ rows = await cursor.fetchall()
315
+ return [dict(r) for r in rows]
316
+ except Exception as exc:
317
+ logger.error("Failed to query failed experiments: %s", exc)
318
+ return []
319
+
320
+ async def get_personality_style(self, subsystem: str) -> dict[str, Any] | None:
321
+ """Query the style profile for a subsystem."""
322
+ try:
323
+ async with self._pool.read() as conn:
324
+ cursor = await conn.execute(
325
+ """
326
+ SELECT subsystem, naming_conventions, type_hinting_strictness,
327
+ preferred_constructs, class_vs_functional,
328
+ docstring_style, updated_at
329
+ FROM repo_personality_styles
330
+ WHERE subsystem = ?
331
+ """,
332
+ (subsystem,),
333
+ )
334
+ row = await cursor.fetchone()
335
+ if row:
336
+ res = dict(row)
337
+ try:
338
+ res["naming_conventions"] = json.loads(res["naming_conventions"])
339
+ except Exception:
340
+ res["naming_conventions"] = {}
341
+ try:
342
+ res["preferred_constructs"] = json.loads(res["preferred_constructs"])
343
+ except Exception:
344
+ res["preferred_constructs"] = []
345
+ return res
346
+ return None
347
+ except Exception as exc:
348
+ logger.error("Failed to query repository personality style: %s", exc)
349
+ return None
350
+
351
+ async def get_evolution_timeline(self, subsystem: str, limit: int = 50) -> list[dict[str, Any]]:
352
+ """Fetch the evolution snapshots for a subsystem, most recent first."""
353
+ try:
354
+ async with self._pool.read() as conn:
355
+ cursor = await conn.execute(
356
+ """
357
+ SELECT id, timestamp, subsystem, lcom_average, coupling_ratio,
358
+ debt_items_count, major_milestone, rationales_summary
359
+ FROM repository_evolution_timeline
360
+ WHERE subsystem LIKE ?
361
+ ORDER BY timestamp DESC
362
+ LIMIT ?
363
+ """,
364
+ (f"%{subsystem}%", limit),
365
+ )
366
+ rows = await cursor.fetchall()
367
+ return [dict(r) for r in rows]
368
+ except Exception as exc:
369
+ logger.error("Failed to query evolution timeline: %s", exc)
370
+ return []
371
+
372
+ async def query_continuity_warnings(
373
+ self,
374
+ prompt: str,
375
+ repo_context: str,
376
+ ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
377
+ """Match task keywords against stored decisions and failed experiments."""
378
+ combined_text = (prompt + " " + repo_context).lower()
379
+ subsystem_keys = [
380
+ "database",
381
+ "db",
382
+ "concurrency",
383
+ "lock",
384
+ "thread",
385
+ "async",
386
+ "cache",
387
+ "sandbox",
388
+ "security",
389
+ "telemetry",
390
+ "model",
391
+ "routing",
392
+ "memory",
393
+ "graph",
394
+ "watcher",
395
+ "file",
396
+ "lifecycle",
397
+ "executor",
398
+ "bus",
399
+ "registry",
400
+ ]
401
+
402
+ matched_decisions: list[dict[str, Any]] = []
403
+ matched_failures: list[dict[str, Any]] = []
404
+
405
+ for key in subsystem_keys:
406
+ if key in combined_text:
407
+ matched_decisions.extend(await self.get_subsystem_decisions(key))
408
+ matched_failures.extend(await self.get_failed_experiments(key))
409
+
410
+ unique_decisions = {d["id"]: d for d in matched_decisions}.values()
411
+ decisions_list = sorted(unique_decisions, key=lambda x: x["timestamp"], reverse=True)[:3]
412
+
413
+ unique_failures = {f["id"]: f for f in matched_failures}.values()
414
+ failures_list = sorted(unique_failures, key=lambda x: x["timestamp"], reverse=True)[:3]
415
+
416
+ return list(decisions_list), list(failures_list)