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.
- velune/__init__.py +5 -0
- velune/__main__.py +6 -0
- velune/cli/__init__.py +5 -0
- velune/cli/app.py +208 -0
- velune/cli/autocomplete.py +80 -0
- velune/cli/banner.py +60 -0
- velune/cli/commands/__init__.py +32 -0
- velune/cli/commands/ask.py +175 -0
- velune/cli/commands/base.py +16 -0
- velune/cli/commands/chat.py +228 -0
- velune/cli/commands/config.py +224 -0
- velune/cli/commands/daemon.py +88 -0
- velune/cli/commands/doctor.py +721 -0
- velune/cli/commands/init.py +170 -0
- velune/cli/commands/mcp.py +82 -0
- velune/cli/commands/memory.py +293 -0
- velune/cli/commands/models.py +683 -0
- velune/cli/commands/preflight.py +95 -0
- velune/cli/commands/run.py +270 -0
- velune/cli/commands/setup.py +184 -0
- velune/cli/commands/workspace.py +249 -0
- velune/cli/context.py +36 -0
- velune/cli/councilmodel_ui.py +199 -0
- velune/cli/display/council_view.py +254 -0
- velune/cli/display/memory_view.py +126 -0
- velune/cli/display/panels.py +35 -0
- velune/cli/display/progress.py +25 -0
- velune/cli/display/themes.py +25 -0
- velune/cli/main.py +15 -0
- velune/cli/model_selector.py +51 -0
- velune/cli/modes.py +86 -0
- velune/cli/pull_ui.py +123 -0
- velune/cli/registry.py +80 -0
- velune/cli/rendering/__init__.py +5 -0
- velune/cli/rendering/error_panel.py +79 -0
- velune/cli/rendering/markdown.py +63 -0
- velune/cli/repl.py +1855 -0
- velune/cli/session_manager.py +71 -0
- velune/cli/slash_commands.py +37 -0
- velune/cli/theme.py +8 -0
- velune/cognition/__init__.py +23 -0
- velune/cognition/agents/__init__.py +7 -0
- velune/cognition/agents/coder.py +209 -0
- velune/cognition/agents/planner.py +156 -0
- velune/cognition/agents/reviewer.py +195 -0
- velune/cognition/arbitrator.py +220 -0
- velune/cognition/architecture.py +415 -0
- velune/cognition/budget.py +65 -0
- velune/cognition/council/__init__.py +47 -0
- velune/cognition/council/base.py +217 -0
- velune/cognition/council/challenger.py +74 -0
- velune/cognition/council/coder.py +79 -0
- velune/cognition/council/critic_agent.py +43 -0
- velune/cognition/council/critic_configs.py +111 -0
- velune/cognition/council/critics.py +41 -0
- velune/cognition/council/debate.py +46 -0
- velune/cognition/council/factory.py +140 -0
- velune/cognition/council/messages.py +56 -0
- velune/cognition/council/planner.py +124 -0
- velune/cognition/council/reviewer.py +74 -0
- velune/cognition/council/synthesizer.py +67 -0
- velune/cognition/council/tiers.py +188 -0
- velune/cognition/council_orchestrator.py +282 -0
- velune/cognition/firewall.py +354 -0
- velune/cognition/module.py +46 -0
- velune/cognition/orchestrator.py +1205 -0
- velune/cognition/personality.py +238 -0
- velune/cognition/state.py +104 -0
- velune/cognition/style_resolver.py +64 -0
- velune/cognition/verification.py +205 -0
- velune/context/__init__.py +28 -0
- velune/context/assembler.py +240 -0
- velune/context/budget.py +97 -0
- velune/context/extractive.py +95 -0
- velune/context/prompt_adaptation.py +480 -0
- velune/context/sections.py +99 -0
- velune/context/token_counter.py +134 -0
- velune/context/utilization.py +33 -0
- velune/context/window.py +63 -0
- velune/core/__init__.py +89 -0
- velune/core/background.py +5 -0
- velune/core/config/__init__.py +37 -0
- velune/core/errors/__init__.py +90 -0
- velune/core/errors/catalog.py +188 -0
- velune/core/errors/execution.py +31 -0
- velune/core/errors/memory.py +25 -0
- velune/core/errors/orchestration.py +31 -0
- velune/core/errors/provider.py +37 -0
- velune/core/event_loop.py +35 -0
- velune/core/logging.py +83 -0
- velune/core/paths.py +165 -0
- velune/core/runtime.py +113 -0
- velune/core/startup_profiler.py +56 -0
- velune/core/task_registry.py +117 -0
- velune/core/trace.py +83 -0
- velune/core/types/__init__.py +48 -0
- velune/core/types/agent.py +53 -0
- velune/core/types/context.py +42 -0
- velune/core/types/inference.py +38 -0
- velune/core/types/memory.py +42 -0
- velune/core/types/model.py +70 -0
- velune/core/types/provider.py +62 -0
- velune/core/types/repository.py +38 -0
- velune/core/types/task.py +61 -0
- velune/core/types/workspace.py +28 -0
- velune/daemon/client.py +13 -0
- velune/daemon/server.py +127 -0
- velune/daemon/transport.py +179 -0
- velune/events.py +204 -0
- velune/execution/__init__.py +22 -0
- velune/execution/benchmarker.py +315 -0
- velune/execution/cancellation.py +53 -0
- velune/execution/checkpointer.py +130 -0
- velune/execution/command_spec.py +165 -0
- velune/execution/diff_preview.py +197 -0
- velune/execution/executor.py +181 -0
- velune/execution/module.py +18 -0
- velune/execution/multi_diff.py +67 -0
- velune/execution/path_guard.py +74 -0
- velune/execution/planner.py +91 -0
- velune/execution/rollback.py +89 -0
- velune/execution/sandbox.py +268 -0
- velune/execution/validator.py +115 -0
- velune/hardware/__init__.py +1 -0
- velune/hardware/detector.py +192 -0
- velune/kernel/__init__.py +55 -0
- velune/kernel/bootstrap.py +125 -0
- velune/kernel/config.py +426 -0
- velune/kernel/entrypoint.py +78 -0
- velune/kernel/health.py +54 -0
- velune/kernel/lifecycle.py +143 -0
- velune/kernel/module.py +17 -0
- velune/kernel/modules.py +23 -0
- velune/kernel/registry.py +96 -0
- velune/kernel/schemas.py +28 -0
- velune/main.py +9 -0
- velune/mcp/__init__.py +9 -0
- velune/mcp/client.py +115 -0
- velune/mcp/config.py +19 -0
- velune/mcp/server.py +624 -0
- velune/memory/__init__.py +32 -0
- velune/memory/compaction.py +506 -0
- velune/memory/embedding_pipeline.py +241 -0
- velune/memory/lifecycle.py +680 -0
- velune/memory/module.py +218 -0
- velune/memory/prioritizer.py +67 -0
- velune/memory/storage/episodic_schema.sql +53 -0
- velune/memory/storage/lancedb_store.py +282 -0
- velune/memory/storage/sqlite_manager.py +369 -0
- velune/memory/storage/sqlite_pool.py +149 -0
- velune/memory/tiers/episodic.py +588 -0
- velune/memory/tiers/graph.py +378 -0
- velune/memory/tiers/lineage.py +416 -0
- velune/memory/tiers/semantic.py +475 -0
- velune/memory/tiers/working.py +168 -0
- velune/memory/vitality.py +132 -0
- velune/models/__init__.py +15 -0
- velune/models/family.py +76 -0
- velune/models/module.py +20 -0
- velune/models/probes.py +192 -0
- velune/models/profile_cache.py +84 -0
- velune/models/profiler.py +108 -0
- velune/models/registry.py +251 -0
- velune/models/scorer.py +233 -0
- velune/models/specializations.py +205 -0
- velune/orchestration/__init__.py +19 -0
- velune/orchestration/engine.py +239 -0
- velune/orchestration/module.py +15 -0
- velune/orchestration/role_assignments.py +82 -0
- velune/orchestration/schemas.py +98 -0
- velune/plugins/__init__.py +20 -0
- velune/plugins/hooks.py +50 -0
- velune/plugins/loader.py +161 -0
- velune/plugins/registry.py +56 -0
- velune/plugins/schemas.py +21 -0
- velune/providers/__init__.py +23 -0
- velune/providers/adapters/anthropic.py +257 -0
- velune/providers/adapters/fireworks.py +115 -0
- velune/providers/adapters/google.py +234 -0
- velune/providers/adapters/groq.py +151 -0
- velune/providers/adapters/huggingface.py +210 -0
- velune/providers/adapters/llamacpp.py +208 -0
- velune/providers/adapters/lmstudio.py +175 -0
- velune/providers/adapters/ollama.py +233 -0
- velune/providers/adapters/openai.py +213 -0
- velune/providers/adapters/openrouter.py +81 -0
- velune/providers/adapters/together.py +134 -0
- velune/providers/adapters/xai.py +60 -0
- velune/providers/base.py +86 -0
- velune/providers/benchmarker.py +138 -0
- velune/providers/discovery/__init__.py +33 -0
- velune/providers/discovery/anthropic.py +79 -0
- velune/providers/discovery/benchmarks.py +44 -0
- velune/providers/discovery/classifier.py +69 -0
- velune/providers/discovery/fireworks.py +95 -0
- velune/providers/discovery/gguf.py +88 -0
- velune/providers/discovery/google.py +95 -0
- velune/providers/discovery/gpu.py +117 -0
- velune/providers/discovery/groq.py +21 -0
- velune/providers/discovery/huggingface.py +67 -0
- velune/providers/discovery/lmstudio.py +80 -0
- velune/providers/discovery/ollama.py +162 -0
- velune/providers/discovery/openai.py +96 -0
- velune/providers/discovery/openrouter.py +113 -0
- velune/providers/discovery/scanner.py +115 -0
- velune/providers/discovery/together.py +114 -0
- velune/providers/discovery/xai.py +57 -0
- velune/providers/health.py +67 -0
- velune/providers/health_monitor.py +169 -0
- velune/providers/keystore.py +142 -0
- velune/providers/local_paths.py +49 -0
- velune/providers/local_resolver.py +229 -0
- velune/providers/module.py +51 -0
- velune/providers/ollama_manager.py +193 -0
- velune/providers/registry.py +220 -0
- velune/providers/router.py +255 -0
- velune/providers/task_classifier.py +288 -0
- velune/py.typed +0 -0
- velune/repository/__init__.py +33 -0
- velune/repository/analyzer.py +127 -0
- velune/repository/ast_parser.py +822 -0
- velune/repository/blast_radius.py +298 -0
- velune/repository/boundary_classifier.py +295 -0
- velune/repository/cognition.py +316 -0
- velune/repository/grapher.py +179 -0
- velune/repository/import_graph.py +263 -0
- velune/repository/incremental_indexer.py +275 -0
- velune/repository/index_state.py +96 -0
- velune/repository/indexer.py +243 -0
- velune/repository/module.py +17 -0
- velune/repository/parser.py +474 -0
- velune/repository/project_type.py +300 -0
- velune/repository/rename_journal.py +287 -0
- velune/repository/scanner.py +193 -0
- velune/repository/schemas.py +102 -0
- velune/repository/symbol_registry.py +365 -0
- velune/repository/tracker.py +252 -0
- velune/retrieval/__init__.py +27 -0
- velune/retrieval/cache.py +110 -0
- velune/retrieval/fast_path.py +391 -0
- velune/retrieval/graph.py +124 -0
- velune/retrieval/hybrid.py +271 -0
- velune/retrieval/keyword.py +131 -0
- velune/retrieval/module.py +26 -0
- velune/retrieval/pipeline.py +303 -0
- velune/retrieval/reranker.py +102 -0
- velune/retrieval/schemas.py +59 -0
- velune/retrieval/slow_path.py +364 -0
- velune/retrieval/vector.py +203 -0
- velune/telemetry/__init__.py +59 -0
- velune/telemetry/cognition.py +267 -0
- velune/telemetry/cost_estimator.py +92 -0
- velune/telemetry/debug.py +304 -0
- velune/telemetry/doctor.py +244 -0
- velune/telemetry/logging.py +286 -0
- velune/telemetry/spans.py +277 -0
- velune/telemetry/token_tracker.py +140 -0
- velune/telemetry/usage_tracker.py +340 -0
- velune/tools/__init__.py +41 -0
- velune/tools/base/registry.py +87 -0
- velune/tools/base/tool.py +63 -0
- velune/tools/code/navigate.py +116 -0
- velune/tools/code/search.py +123 -0
- velune/tools/filesystem/read.py +75 -0
- velune/tools/filesystem/search.py +136 -0
- velune/tools/filesystem/write.py +163 -0
- velune/tools/git/history.py +177 -0
- velune/tools/git/operations.py +122 -0
- velune/tools/git/state.py +121 -0
- velune/tools/module.py +81 -0
- velune/tools/terminal/execute.py +72 -0
- velune/tools/terminal/history.py +47 -0
- velune/tools/web/fetch.py +55 -0
- velune/tools/web/validator.py +122 -0
- velune_cli-0.9.0.dist-info/METADATA +518 -0
- velune_cli-0.9.0.dist-info/RECORD +279 -0
- velune_cli-0.9.0.dist-info/WHEEL +4 -0
- velune_cli-0.9.0.dist-info/entry_points.txt +2 -0
- 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
|
+
]
|