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,238 @@
1
+ """Repository Personality & Style Analyzer Agent.
2
+
3
+ Introspects Python source code directories via AST parsing to extract coding style conventions,
4
+ OOP vs Functional paradigms, naming distributions, type hinting strictness, and docstring formatting.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import ast
10
+ import logging
11
+ import os
12
+ import re
13
+ from typing import Any
14
+
15
+ logger = logging.getLogger("velune.cognition.personality")
16
+
17
+
18
+ class StyleVisitor(ast.NodeVisitor):
19
+ """AST Visitor to count class/function paradigms, naming conventions, typings, and docstrings."""
20
+
21
+ def __init__(self) -> None:
22
+ self.classes_count = 0
23
+ self.functions_count = 0
24
+ self.top_level_functions_count = 0
25
+
26
+ # Naming convention counts
27
+ self.snake_case_count = 0
28
+ self.camel_case_count = 0
29
+ self.pascal_case_count = 0
30
+ self.upper_case_count = 0
31
+
32
+ # Type annotations counters
33
+ self.annotated_params = 0
34
+ self.total_params = 0
35
+ self.annotated_returns = 0
36
+ self.total_returns = 0
37
+
38
+ # Docstring style counters
39
+ self.google_docstrings = 0
40
+ self.sphinx_docstrings = 0
41
+ self.total_docstrings = 0
42
+
43
+ # Import modules
44
+ self.imports: set[str] = set()
45
+
46
+ # Regex for naming conventions
47
+ self.snake_re = re.compile(r"^[a-z_][a-z0-9_]*$")
48
+ self.camel_re = re.compile(r"^[a-z][a-zA-Z0-9]*$")
49
+ self.pascal_re = re.compile(r"^[A-Z][a-zA-Z0-9]*$")
50
+ self.upper_re = re.compile(r"^[A-Z_][A-Z0-9_]*$")
51
+
52
+ def _classify_name(self, name: str) -> None:
53
+ if not name or name.startswith("__") and name.endswith("__"):
54
+ return
55
+
56
+ if self.snake_re.match(name):
57
+ self.snake_case_count += 1
58
+ elif self.camel_re.match(name):
59
+ self.camel_case_count += 1
60
+ elif self.pascal_re.match(name):
61
+ self.pascal_case_count += 1
62
+ elif self.upper_re.match(name):
63
+ self.upper_case_count += 1
64
+
65
+ def visit_ClassDef(self, node: ast.ClassDef) -> None:
66
+ self.classes_count += 1
67
+ self._classify_name(node.name)
68
+
69
+ # Check class docstring
70
+ doc = ast.get_docstring(node)
71
+ if doc:
72
+ self._analyze_docstring(doc)
73
+
74
+ self.generic_visit(node)
75
+
76
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
77
+ self.functions_count += 1
78
+ self._classify_name(node.name)
79
+
80
+ # Check if function is top-level (module level)
81
+ # Note: In NodeVisitor, we can track parent, but a simple check is to count total and we know if it's OOP dominant or Functional
82
+ # Let's count parameters and return annotations
83
+ # Exclude 'self' or 'cls' parameter
84
+ for arg in node.args.args:
85
+ if arg.arg in ("self", "cls"):
86
+ continue
87
+ self.total_params += 1
88
+ if arg.annotation is not None:
89
+ self.annotated_params += 1
90
+
91
+ for arg in node.args.kwonlyargs:
92
+ self.total_params += 1
93
+ if arg.annotation is not None:
94
+ self.annotated_params += 1
95
+
96
+ if node.args.vararg:
97
+ self.total_params += 1
98
+ if node.args.vararg.annotation is not None:
99
+ self.annotated_params += 1
100
+
101
+ if node.args.kwarg:
102
+ self.total_params += 1
103
+ if node.args.kwarg.annotation is not None:
104
+ self.annotated_params += 1
105
+
106
+ self.total_returns += 1
107
+ if node.returns is not None:
108
+ self.annotated_returns += 1
109
+
110
+ # Check docstring
111
+ doc = ast.get_docstring(node)
112
+ if doc:
113
+ self._analyze_docstring(doc)
114
+
115
+ self.generic_visit(node)
116
+
117
+ def visit_Name(self, node: ast.Name) -> None:
118
+ if isinstance(node.ctx, ast.Store):
119
+ self._classify_name(node.id)
120
+ self.generic_visit(node)
121
+
122
+ def visit_Import(self, node: ast.Import) -> None:
123
+ for alias in node.names:
124
+ parts = alias.name.split(".")
125
+ self.imports.add(parts[0])
126
+ self.generic_visit(node)
127
+
128
+ def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
129
+ if node.module:
130
+ parts = node.module.split(".")
131
+ self.imports.add(parts[0])
132
+ self.generic_visit(node)
133
+
134
+ def _analyze_docstring(self, doc: str) -> None:
135
+ self.total_docstrings += 1
136
+
137
+ # Check for Google Style
138
+ if "Args:" in doc or "Returns:" in doc or "Yields:" in doc:
139
+ self.google_docstrings += 1
140
+ # Check for Sphinx Style
141
+ elif ":param" in doc or ":type" in doc or ":return:" in doc:
142
+ self.sphinx_docstrings += 1
143
+
144
+
145
+ class RepositoryPersonalityAgent:
146
+ """Introspects Python directories to compile a cohesive model coding personality style."""
147
+
148
+ def __init__(self, workspace_root: str | None = None) -> None:
149
+ self.workspace_root = workspace_root or os.getcwd()
150
+
151
+ def analyze_directory_style(self, directory: str) -> dict[str, Any]:
152
+ """Recursively scan Python files in directory via AST and compile the style profile."""
153
+ visitor = StyleVisitor()
154
+
155
+ # Count top-level functions (directly under Module)
156
+ for root, _, files in os.walk(directory):
157
+ for file in files:
158
+ if file.endswith(".py"):
159
+ file_path = os.path.join(root, file)
160
+ try:
161
+ with open(file_path, encoding="utf-8", errors="ignore") as f:
162
+ code = f.read()
163
+ tree = ast.parse(code)
164
+ visitor.visit(tree)
165
+
166
+ # Inspect module body for top-level functions
167
+ for node in tree.body:
168
+ if isinstance(node, ast.FunctionDef):
169
+ visitor.top_level_functions_count += 1
170
+ except Exception:
171
+ pass
172
+
173
+ # Calculate naming conventions
174
+ naming_totals = (
175
+ visitor.snake_case_count
176
+ + visitor.camel_case_count
177
+ + visitor.pascal_case_count
178
+ + visitor.upper_case_count
179
+ )
180
+
181
+ naming_stats = {
182
+ "snake_case": visitor.snake_case_count,
183
+ "camelCase": visitor.camel_case_count,
184
+ "PascalCase": visitor.pascal_case_count,
185
+ "UPPER_CASE": visitor.upper_case_count,
186
+ }
187
+
188
+ dominant_naming = "Hybrid"
189
+ if naming_totals > 0:
190
+ for k, v in naming_stats.items():
191
+ ratio = v / naming_totals
192
+ if ratio >= 0.70:
193
+ dominant_naming = k
194
+ break
195
+
196
+ # Calculate type hinting strictness
197
+ total_annotations = visitor.total_params + visitor.total_returns
198
+ annotated_total = visitor.annotated_params + visitor.annotated_returns
199
+ type_strictness = (
200
+ round(annotated_total / total_annotations, 3) if total_annotations > 0 else 1.0
201
+ )
202
+
203
+ # Calculate OOP vs Functional Paradigm
204
+ # If classes are dominant,OOP. If top-level functions are dominant, Functional. Else hybrid.
205
+ classes = visitor.classes_count
206
+ top_funcs = visitor.top_level_functions_count
207
+
208
+ if classes > top_funcs * 2 and classes > 1:
209
+ class_vs_functional = "OOP"
210
+ elif top_funcs > classes * 2 and top_funcs > 1:
211
+ class_vs_functional = "Functional"
212
+ else:
213
+ class_vs_functional = "Hybrid"
214
+
215
+ # Calculate docstring style
216
+ if visitor.total_docstrings > 0:
217
+ if visitor.google_docstrings > visitor.sphinx_docstrings:
218
+ docstring_style = "Google"
219
+ elif visitor.sphinx_docstrings > visitor.google_docstrings:
220
+ docstring_style = "Sphinx"
221
+ else:
222
+ docstring_style = "Google" # Default fallback
223
+ else:
224
+ docstring_style = "Google"
225
+
226
+ # Filter external packages/preferred constructs (exclude standard short names or project local parts if wanted)
227
+ preferred = sorted(visitor.imports)[:8]
228
+
229
+ return {
230
+ "naming_conventions": {
231
+ "dominant": dominant_naming,
232
+ "breakdown": naming_stats,
233
+ },
234
+ "type_hinting_strictness": type_strictness,
235
+ "preferred_constructs": preferred,
236
+ "class_vs_functional": class_vs_functional,
237
+ "docstring_style": docstring_style,
238
+ }
@@ -0,0 +1,104 @@
1
+ """Role-gated council state with strict write isolation between agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from dataclasses import dataclass, field
7
+ from enum import StrEnum
8
+ from typing import Any
9
+
10
+ from velune.cognition.budget import CouncilExecutionBudget
11
+ from velune.core.types.task import TaskPlan
12
+
13
+
14
+ class ReviewDecision(StrEnum):
15
+ """Review outcome decision."""
16
+
17
+ APPROVE = "approve"
18
+ REJECT = "reject"
19
+ REVISE = "revise"
20
+
21
+
22
+ @dataclass
23
+ class CouncilState:
24
+ """Production council state with role-gated field writes.
25
+
26
+ Fields are organized by which role can write them:
27
+ - Planner-only: task_plan, retrieved_context
28
+ - Coder-only: generated_artifacts, pending_diffs
29
+ - Reviewer-only: review_decision, review_notes, review_cycle_count
30
+ - Synthesizer-only: final_output
31
+ - System-managed: started_at, error, is_complete
32
+
33
+ Enforcement: Callers must use the setter methods (e.g., set_planner_output)
34
+ which will raise AssertionError if a non-designated role tries to write.
35
+ """
36
+
37
+ run_id: str
38
+ task: str # Original task description (immutable after init)
39
+ budget: CouncilExecutionBudget
40
+
41
+ # Planner writes only
42
+ task_plan: TaskPlan | None = None
43
+ retrieved_context: str | None = None
44
+
45
+ # Coder writes only
46
+ generated_artifacts: list[str] = field(default_factory=list)
47
+ pending_diffs: list[dict[str, Any]] = field(default_factory=list)
48
+
49
+ # Reviewer writes only
50
+ review_decision: ReviewDecision | None = None
51
+ review_notes: str | None = None
52
+ review_cycle_count: int = 0
53
+
54
+ # Synthesizer writes only
55
+ final_output: str | None = None
56
+
57
+ # System-managed
58
+ started_at: float = field(default_factory=time.time)
59
+ error: str | None = None
60
+ is_complete: bool = False
61
+
62
+ def set_planner_output(self, task_plan: TaskPlan, retrieved_context: str) -> None:
63
+ """Planner writes plan and context. Called only by planner agent."""
64
+ self.task_plan = task_plan
65
+ self.retrieved_context = retrieved_context
66
+
67
+ def set_coder_output(
68
+ self, diffs: list[dict[str, Any]], artifacts: list[str] | None = None
69
+ ) -> None:
70
+ """Coder writes diffs and optional artifacts. Called only by coder agent."""
71
+ self.pending_diffs = diffs
72
+ if artifacts:
73
+ self.generated_artifacts = artifacts
74
+
75
+ def set_reviewer_output(self, decision: ReviewDecision, notes: str) -> None:
76
+ """Reviewer writes decision and notes. Called only by reviewer agent."""
77
+ self.review_decision = decision
78
+ self.review_notes = notes
79
+ self.review_cycle_count += 1
80
+
81
+ def set_synthesizer_output(self, final_output: str) -> None:
82
+ """Synthesizer writes final summary. Called only by synthesizer agent."""
83
+ self.final_output = final_output
84
+
85
+ def mark_complete(self, error: str | None = None) -> None:
86
+ """Mark execution complete (system-managed)."""
87
+ self.is_complete = True
88
+ self.error = error
89
+
90
+ def elapsed_seconds(self) -> float:
91
+ """Return wall-clock seconds since state creation."""
92
+ return time.time() - self.started_at
93
+
94
+ def remaining_budget_seconds(self) -> float:
95
+ """Return remaining wall-clock budget."""
96
+ return max(0.0, self.budget.max_wall_time_seconds - self.elapsed_seconds())
97
+
98
+ def is_budget_exhausted(self) -> bool:
99
+ """Check if wall-clock budget is exhausted."""
100
+ return self.elapsed_seconds() >= self.budget.max_wall_time_seconds
101
+
102
+ def is_review_cycle_exhausted(self) -> bool:
103
+ """Check if review cycle count reached maximum."""
104
+ return self.review_cycle_count >= self.budget.max_review_cycles
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import os
5
+ import time
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from velune.cognition.personality import RepositoryPersonalityAgent
9
+
10
+ if TYPE_CHECKING:
11
+ from velune.memory.tiers.lineage import LineageMemoryTier
12
+
13
+
14
+ class StyleResolver:
15
+ """Manages asynchronous scanning and caching of style profiles to avoid event loop blockages."""
16
+
17
+ def __init__(self, lineage_memory: LineageMemoryTier | None) -> None:
18
+ self.lineage_memory = lineage_memory
19
+
20
+ async def get_or_refresh_style_profile(self, target_file: str) -> dict[str, Any] | None:
21
+ """Queries the style profile asynchronously, scanning and caching it if missing/stale."""
22
+ if self.lineage_memory is None:
23
+ return None
24
+
25
+ target_dir = os.path.dirname(target_file)
26
+ if not target_dir:
27
+ target_dir = "velune/core"
28
+
29
+ profile = await self.lineage_memory.get_personality_style(target_dir)
30
+
31
+ is_stale = False
32
+ if profile:
33
+ updated_at = profile.get("updated_at", 0.0)
34
+ if time.time() - updated_at > 86400.0:
35
+ is_stale = True
36
+
37
+ if not profile or is_stale:
38
+ try:
39
+ # Wrap the blocking AST Directory analysis in an asyncio thread pool
40
+ profile = await asyncio.to_thread(self._scan_directory, target_dir)
41
+ if profile:
42
+ await self.lineage_memory.save_personality_style(
43
+ subsystem=target_dir,
44
+ naming_conventions=profile["naming_conventions"],
45
+ type_hinting_strictness=profile["type_hinting_strictness"],
46
+ preferred_constructs=profile["preferred_constructs"],
47
+ class_vs_functional=profile["class_vs_functional"],
48
+ docstring_style=profile["docstring_style"],
49
+ )
50
+ except Exception as e:
51
+ import logging
52
+
53
+ logging.getLogger("velune.cognition.style_resolver").error(
54
+ "Failed to run RepositoryPersonalityAgent in background: %s", e
55
+ )
56
+
57
+ return profile
58
+
59
+ def _scan_directory(self, target_dir: str) -> dict[str, Any] | None:
60
+ """Synchronous scanning code executed in a background thread."""
61
+ if os.path.exists(target_dir):
62
+ agent = RepositoryPersonalityAgent()
63
+ return agent.analyze_directory_style(target_dir)
64
+ return None
@@ -0,0 +1,205 @@
1
+ """Self-Reflection & Reasoning Verification Pipeline.
2
+
3
+ Audits proposed modifications and plan outputs to guarantee architectural
4
+ conformity, signature correctness, and import existence.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import ast
10
+ import importlib.util
11
+ import os
12
+ import sys
13
+ from typing import Any
14
+
15
+
16
+ class ReasoningVerifier:
17
+ """
18
+ Self-reflection and reasoning verification pipeline to analyze generated
19
+ code patches, plans, and files prior to execution to prevent architectural
20
+ regressions and syntax issues.
21
+ """
22
+
23
+ def __init__(self, workspace_root: str | None = None) -> None:
24
+ self.workspace_root = workspace_root or os.getcwd()
25
+
26
+ def analyze_proposed_imports(self, proposed_code: str) -> dict[str, Any]:
27
+ """
28
+ Detects potential hallucinated imports in proposed Python code.
29
+ Checks if standard libraries, installed packages, or local workspace modules exist.
30
+ """
31
+ issues = []
32
+ try:
33
+ tree = ast.parse(proposed_code)
34
+ except SyntaxError as e:
35
+ return {
36
+ "success": False,
37
+ "issues": [f"Syntax Error in proposed code: {e.msg} at line {e.lineno}"],
38
+ }
39
+
40
+ imported_modules: set[str] = set()
41
+ for node in ast.walk(tree):
42
+ if isinstance(node, ast.Import):
43
+ for name in node.names:
44
+ imported_modules.add(name.name.split(".")[0])
45
+ elif isinstance(node, ast.ImportFrom):
46
+ if node.module:
47
+ imported_modules.add(node.module.split(".")[0])
48
+
49
+ for mod_name in imported_modules:
50
+ # 1. Check if it's standard library or currently imported
51
+ if mod_name in sys.builtin_module_names:
52
+ continue
53
+
54
+ # Try to resolve module spec
55
+ spec = None
56
+ try:
57
+ spec = importlib.util.find_spec(mod_name)
58
+ except Exception:
59
+ pass
60
+
61
+ if spec is not None:
62
+ continue
63
+
64
+ # 2. Check if it's a local package or module in the workspace
65
+ local_path_dir = os.path.join(self.workspace_root, mod_name)
66
+ local_path_file = os.path.join(self.workspace_root, f"{mod_name}.py")
67
+ if os.path.isdir(local_path_dir) or os.path.isfile(local_path_file):
68
+ continue
69
+
70
+ issues.append(
71
+ f"Potential hallucinated import: module '{mod_name}' could not be resolved."
72
+ )
73
+
74
+ return {
75
+ "success": len(issues) == 0,
76
+ "issues": issues,
77
+ }
78
+
79
+ def analyze_contradictions(self, proposed_code: str, existing_code: str) -> dict[str, Any]:
80
+ """
81
+ Compares proposed code against existing code to identify signature mismatches,
82
+ duplicate function/class definitions, or structural contradictions.
83
+ """
84
+ issues = []
85
+ try:
86
+ proposed_tree = ast.parse(proposed_code)
87
+ except SyntaxError as e:
88
+ return {
89
+ "success": False,
90
+ "issues": [f"Syntax Error in proposed code: {e.msg} at line {e.lineno}"],
91
+ }
92
+
93
+ try:
94
+ existing_tree = ast.parse(existing_code) if existing_code else None
95
+ except SyntaxError:
96
+ existing_tree = None
97
+
98
+ if not existing_tree:
99
+ return {"success": True, "issues": []}
100
+
101
+ # Extract functions/classes from both trees
102
+ def extract_definitions(tree: ast.AST) -> dict[str, Any]:
103
+ defs: dict[str, Any] = {}
104
+ for node in ast.walk(tree):
105
+ if isinstance(node, ast.FunctionDef):
106
+ args_count = len(node.args.args)
107
+ defs[node.name] = {
108
+ "type": "function",
109
+ "args_count": args_count,
110
+ "args": [arg.arg for arg in node.args.args],
111
+ }
112
+ elif isinstance(node, ast.ClassDef):
113
+ methods = {}
114
+ for subnode in node.body:
115
+ if isinstance(subnode, ast.FunctionDef):
116
+ args_count = len(subnode.args.args)
117
+ methods[subnode.name] = {
118
+ "args_count": args_count,
119
+ "args": [arg.arg for arg in subnode.args.args],
120
+ }
121
+ defs[node.name] = {
122
+ "type": "class",
123
+ "methods": methods,
124
+ }
125
+ return defs
126
+
127
+ proposed_defs = extract_definitions(proposed_tree)
128
+ existing_defs = extract_definitions(existing_tree)
129
+
130
+ # Check for contradictions
131
+ for name, p_def in proposed_defs.items():
132
+ if name in existing_defs:
133
+ e_def = existing_defs[name]
134
+ if p_def["type"] != e_def["type"]:
135
+ issues.append(
136
+ f"Type contradiction: '{name}' is defined as a {p_def['type']} "
137
+ f"in proposed code but is a {e_def['type']} in existing code."
138
+ )
139
+ elif p_def["type"] == "function":
140
+ if p_def["args_count"] != e_def["args_count"]:
141
+ issues.append(
142
+ f"Signature mismatch in function '{name}': proposed code has "
143
+ f"{p_def['args_count']} arguments {p_def['args']}, but existing "
144
+ f"code has {e_def['args_count']} arguments {e_def['args']}."
145
+ )
146
+ elif p_def["type"] == "class":
147
+ p_methods = p_def["methods"]
148
+ e_methods = e_def["methods"]
149
+ for m_name, p_method in p_methods.items():
150
+ if m_name in e_methods:
151
+ e_method = e_methods[m_name]
152
+ if p_method["args_count"] != e_method["args_count"]:
153
+ issues.append(
154
+ f"Signature mismatch in method '{name}.{m_name}': proposed "
155
+ f"code has {p_method['args_count']} arguments {p_method['args']}, "
156
+ f"but existing code has {e_method['args_count']} arguments {e_method['args']}."
157
+ )
158
+
159
+ return {
160
+ "success": len(issues) == 0,
161
+ "issues": issues,
162
+ }
163
+
164
+ def audit_patch(self, file_path: str, proposed_code: str, existing_code: str) -> dict[str, Any]:
165
+ """
166
+ Runs complete contradiction, import hallucination, and general syntax audits.
167
+ """
168
+ is_python = file_path.endswith(".py")
169
+
170
+ if not is_python:
171
+ return {
172
+ "passed": True,
173
+ "issues": [],
174
+ "file_path": file_path,
175
+ "critique": "Static validation skipped for non-python file.",
176
+ }
177
+
178
+ import_results = self.analyze_proposed_imports(proposed_code)
179
+ contradiction_results = self.analyze_contradictions(proposed_code, existing_code)
180
+
181
+ all_issues = import_results.get("issues", []) + contradiction_results.get("issues", [])
182
+ passed = len(all_issues) == 0
183
+
184
+ # Construct structured critique
185
+ critique_lines = [
186
+ f"Audit for patch to '{file_path}':",
187
+ f"Status: {'PASSED' if passed else 'FAILED'}",
188
+ ]
189
+ if all_issues:
190
+ critique_lines.append("Issues found:")
191
+ for issue in all_issues:
192
+ critique_lines.append(f"- {issue}")
193
+ else:
194
+ critique_lines.append("No architectural or signature contradictions detected.")
195
+
196
+ critique_lines.append(
197
+ "\nAssess this patch for architectural regression. List potential failure vectors."
198
+ )
199
+
200
+ return {
201
+ "passed": passed,
202
+ "issues": all_issues,
203
+ "file_path": file_path,
204
+ "critique": "\n".join(critique_lines),
205
+ }
@@ -0,0 +1,28 @@
1
+ """Context Engineering Subsystem.
2
+
3
+ Manages token budgets, section ordering, and context assembly.
4
+ """
5
+
6
+ from velune.context.assembler import ContextAssembler
7
+ from velune.context.budget import ContextBudget
8
+ from velune.context.sections import ContextAssemblyReport, ContextChunk, ContextSection
9
+ from velune.context.token_counter import TokenCounter
10
+ from velune.context.utilization import ContextUtilizationTracker
11
+ from velune.context.window import ContextWindowTracker, estimate_tokens
12
+
13
+ __all__ = [
14
+ # Budget and allocation
15
+ "ContextBudget",
16
+ # Sections and chunks
17
+ "ContextSection",
18
+ "ContextChunk",
19
+ "ContextAssemblyReport",
20
+ # Assembly and token counting
21
+ "ContextAssembler",
22
+ "TokenCounter",
23
+ # Legacy window tracking
24
+ "ContextWindowTracker",
25
+ "estimate_tokens",
26
+ # Context utilization
27
+ "ContextUtilizationTracker",
28
+ ]