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,378 @@
|
|
|
1
|
+
"""Graph Memory Tier (Tier 4).
|
|
2
|
+
|
|
3
|
+
Lightweight SQLite-backed Knowledge Graph store for indexing entities
|
|
4
|
+
(files, functions, concepts) and their semantic edge relationships.
|
|
5
|
+
|
|
6
|
+
All I/O is async and routed through
|
|
7
|
+
:class:`~velune.memory.storage.sqlite_pool.SQLiteConnectionPool`.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
import time
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel, Field
|
|
18
|
+
|
|
19
|
+
from velune.memory.storage.sqlite_pool import SQLiteConnectionPool
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger("velune.memory.tiers.graph")
|
|
22
|
+
|
|
23
|
+
_SCHEMA_SQL = """
|
|
24
|
+
CREATE TABLE IF NOT EXISTS graph_nodes (
|
|
25
|
+
id TEXT PRIMARY KEY,
|
|
26
|
+
node_type TEXT NOT NULL,
|
|
27
|
+
properties TEXT NOT NULL
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
CREATE TABLE IF NOT EXISTS graph_edges (
|
|
31
|
+
source TEXT NOT NULL,
|
|
32
|
+
target TEXT NOT NULL,
|
|
33
|
+
relation_type TEXT NOT NULL,
|
|
34
|
+
properties TEXT NOT NULL,
|
|
35
|
+
PRIMARY KEY (source, target, relation_type),
|
|
36
|
+
FOREIGN KEY (source) REFERENCES graph_nodes(id) ON DELETE CASCADE,
|
|
37
|
+
FOREIGN KEY (target) REFERENCES graph_nodes(id) ON DELETE CASCADE
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE TABLE IF NOT EXISTS execution_nodes (
|
|
41
|
+
id TEXT PRIMARY KEY,
|
|
42
|
+
task_id TEXT NOT NULL,
|
|
43
|
+
node_type TEXT NOT NULL,
|
|
44
|
+
status TEXT NOT NULL,
|
|
45
|
+
parameters TEXT NOT NULL,
|
|
46
|
+
outcome TEXT,
|
|
47
|
+
timestamp REAL NOT NULL
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
CREATE TABLE IF NOT EXISTS execution_edges (
|
|
51
|
+
source TEXT NOT NULL,
|
|
52
|
+
target TEXT NOT NULL,
|
|
53
|
+
relation_type TEXT NOT NULL,
|
|
54
|
+
PRIMARY KEY (source, target, relation_type),
|
|
55
|
+
FOREIGN KEY (source) REFERENCES execution_nodes(id) ON DELETE CASCADE,
|
|
56
|
+
FOREIGN KEY (target) REFERENCES execution_nodes(id) ON DELETE CASCADE
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON graph_edges(source);
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON graph_edges(target);
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_exec_nodes_task ON execution_nodes(task_id);
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class GraphNode(BaseModel):
|
|
66
|
+
"""A single entity node in the Knowledge Graph."""
|
|
67
|
+
|
|
68
|
+
id: str
|
|
69
|
+
node_type: str
|
|
70
|
+
properties: dict[str, Any] = Field(default_factory=dict)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class GraphEdge(BaseModel):
|
|
74
|
+
"""A directed edge connecting two entities in the Knowledge Graph."""
|
|
75
|
+
|
|
76
|
+
source: str
|
|
77
|
+
target: str
|
|
78
|
+
relation_type: str
|
|
79
|
+
properties: dict[str, Any] = Field(default_factory=dict)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class GraphMemoryTier:
|
|
83
|
+
"""Tier 4: Structured entity-relationship store for codebase and cognitive dependencies."""
|
|
84
|
+
|
|
85
|
+
def __init__(self, pool: SQLiteConnectionPool) -> None:
|
|
86
|
+
self._pool = pool
|
|
87
|
+
|
|
88
|
+
# ------------------------------------------------------------------
|
|
89
|
+
# Lifecycle
|
|
90
|
+
# ------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
async def initialize(self) -> None:
|
|
93
|
+
"""Create tables and indexes if they do not yet exist."""
|
|
94
|
+
await self._init_db()
|
|
95
|
+
|
|
96
|
+
async def _init_db(self) -> None:
|
|
97
|
+
async with self._pool.write() as conn:
|
|
98
|
+
for stmt in _SCHEMA_SQL.split(";"):
|
|
99
|
+
stmt = stmt.strip()
|
|
100
|
+
if stmt:
|
|
101
|
+
await conn.execute(stmt)
|
|
102
|
+
logger.debug("GraphMemoryTier schema initialised.")
|
|
103
|
+
|
|
104
|
+
# ------------------------------------------------------------------
|
|
105
|
+
# Write operations
|
|
106
|
+
# ------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
async def add_node(
|
|
109
|
+
self,
|
|
110
|
+
node_id: str,
|
|
111
|
+
node_type: str,
|
|
112
|
+
properties: dict[str, Any] | None = None,
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Insert or upsert a node."""
|
|
115
|
+
props_str = json.dumps(properties or {})
|
|
116
|
+
try:
|
|
117
|
+
async with self._pool.write() as conn:
|
|
118
|
+
await conn.execute(
|
|
119
|
+
"""
|
|
120
|
+
INSERT INTO graph_nodes (id, node_type, properties)
|
|
121
|
+
VALUES (?, ?, ?)
|
|
122
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
123
|
+
node_type=excluded.node_type,
|
|
124
|
+
properties=excluded.properties
|
|
125
|
+
""",
|
|
126
|
+
(node_id, node_type, props_str),
|
|
127
|
+
)
|
|
128
|
+
except Exception as exc:
|
|
129
|
+
logger.error("Failed to add node %s: %s", node_id, exc)
|
|
130
|
+
|
|
131
|
+
async def add_edge(
|
|
132
|
+
self,
|
|
133
|
+
source_id: str,
|
|
134
|
+
target_id: str,
|
|
135
|
+
relation_type: str,
|
|
136
|
+
properties: dict[str, Any] | None = None,
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Create or update a directed edge."""
|
|
139
|
+
props_str = json.dumps(properties or {})
|
|
140
|
+
try:
|
|
141
|
+
async with self._pool.write() as conn:
|
|
142
|
+
await conn.execute(
|
|
143
|
+
"""
|
|
144
|
+
INSERT INTO graph_edges (source, target, relation_type, properties)
|
|
145
|
+
VALUES (?, ?, ?, ?)
|
|
146
|
+
ON CONFLICT(source, target, relation_type) DO UPDATE SET
|
|
147
|
+
properties=excluded.properties
|
|
148
|
+
""",
|
|
149
|
+
(source_id, target_id, relation_type, props_str),
|
|
150
|
+
)
|
|
151
|
+
except Exception as exc:
|
|
152
|
+
logger.error("Failed to add edge %s→%s: %s", source_id, target_id, exc)
|
|
153
|
+
|
|
154
|
+
async def upsert_entity(self, entity_id: str, entity_type: str, **properties: Any) -> None:
|
|
155
|
+
"""Upsert a node (entity) in the knowledge graph."""
|
|
156
|
+
await self.add_node(node_id=entity_id, node_type=entity_type, properties=properties)
|
|
157
|
+
|
|
158
|
+
async def upsert_relationship(
|
|
159
|
+
self,
|
|
160
|
+
source_id: str,
|
|
161
|
+
target_id: str,
|
|
162
|
+
relation_type: str,
|
|
163
|
+
**properties: Any,
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Upsert a directed edge (relationship) between two existing nodes."""
|
|
166
|
+
await self.add_edge(
|
|
167
|
+
source_id=source_id,
|
|
168
|
+
target_id=target_id,
|
|
169
|
+
relation_type=relation_type,
|
|
170
|
+
properties=properties,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
async def record_execution_node(
|
|
174
|
+
self,
|
|
175
|
+
node_id: str,
|
|
176
|
+
task_id: str,
|
|
177
|
+
node_type: str,
|
|
178
|
+
status: str,
|
|
179
|
+
parameters: dict[str, Any],
|
|
180
|
+
outcome: str | None = None,
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Record a step in the execution lineage graph."""
|
|
183
|
+
params_str = json.dumps(parameters)
|
|
184
|
+
try:
|
|
185
|
+
async with self._pool.write() as conn:
|
|
186
|
+
await conn.execute(
|
|
187
|
+
"""
|
|
188
|
+
INSERT INTO execution_nodes
|
|
189
|
+
(id, task_id, node_type, status, parameters, outcome, timestamp)
|
|
190
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
191
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
192
|
+
status=excluded.status,
|
|
193
|
+
parameters=excluded.parameters,
|
|
194
|
+
outcome=excluded.outcome,
|
|
195
|
+
timestamp=excluded.timestamp
|
|
196
|
+
""",
|
|
197
|
+
(node_id, task_id, node_type, status, params_str, outcome, time.time()),
|
|
198
|
+
)
|
|
199
|
+
except Exception as exc:
|
|
200
|
+
logger.error("Failed to record execution node %s: %s", node_id, exc)
|
|
201
|
+
|
|
202
|
+
async def record_execution_edge(
|
|
203
|
+
self,
|
|
204
|
+
source_id: str,
|
|
205
|
+
target_id: str,
|
|
206
|
+
relation_type: str,
|
|
207
|
+
) -> None:
|
|
208
|
+
"""Record a transition edge between execution steps."""
|
|
209
|
+
try:
|
|
210
|
+
async with self._pool.write() as conn:
|
|
211
|
+
await conn.execute(
|
|
212
|
+
"""
|
|
213
|
+
INSERT INTO execution_edges (source, target, relation_type)
|
|
214
|
+
VALUES (?, ?, ?)
|
|
215
|
+
ON CONFLICT(source, target, relation_type) DO NOTHING
|
|
216
|
+
""",
|
|
217
|
+
(source_id, target_id, relation_type),
|
|
218
|
+
)
|
|
219
|
+
except Exception as exc:
|
|
220
|
+
logger.error("Failed to record execution edge %s→%s: %s", source_id, target_id, exc)
|
|
221
|
+
|
|
222
|
+
# ------------------------------------------------------------------
|
|
223
|
+
# Read operations
|
|
224
|
+
# ------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
async def get_all_nodes(self) -> list[GraphNode]:
|
|
227
|
+
"""Retrieve all nodes from the knowledge graph."""
|
|
228
|
+
nodes = []
|
|
229
|
+
try:
|
|
230
|
+
async with self._pool.read() as conn:
|
|
231
|
+
cursor = await conn.execute("SELECT id, node_type, properties FROM graph_nodes")
|
|
232
|
+
rows = await cursor.fetchall()
|
|
233
|
+
for row in rows:
|
|
234
|
+
nodes.append(
|
|
235
|
+
GraphNode(
|
|
236
|
+
id=row["id"],
|
|
237
|
+
node_type=row["node_type"],
|
|
238
|
+
properties=json.loads(row["properties"]),
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
except Exception as exc:
|
|
242
|
+
logger.error("Failed to retrieve all graph nodes: %s", exc)
|
|
243
|
+
return nodes
|
|
244
|
+
|
|
245
|
+
async def get_all_edges(self) -> list[GraphEdge]:
|
|
246
|
+
"""Retrieve all edges from the knowledge graph."""
|
|
247
|
+
edges = []
|
|
248
|
+
try:
|
|
249
|
+
async with self._pool.read() as conn:
|
|
250
|
+
cursor = await conn.execute(
|
|
251
|
+
"SELECT source, target, relation_type, properties FROM graph_edges"
|
|
252
|
+
)
|
|
253
|
+
rows = await cursor.fetchall()
|
|
254
|
+
for row in rows:
|
|
255
|
+
edges.append(
|
|
256
|
+
GraphEdge(
|
|
257
|
+
source=row["source"],
|
|
258
|
+
target=row["target"],
|
|
259
|
+
relation_type=row["relation_type"],
|
|
260
|
+
properties=json.loads(row["properties"]),
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
except Exception as exc:
|
|
264
|
+
logger.error("Failed to retrieve all graph edges: %s", exc)
|
|
265
|
+
return edges
|
|
266
|
+
|
|
267
|
+
async def get_node(self, node_id: str) -> GraphNode | None:
|
|
268
|
+
"""Fetch a specific node by its identifier."""
|
|
269
|
+
try:
|
|
270
|
+
async with self._pool.read() as conn:
|
|
271
|
+
cursor = await conn.execute(
|
|
272
|
+
"SELECT id, node_type, properties FROM graph_nodes WHERE id = ?",
|
|
273
|
+
(node_id,),
|
|
274
|
+
)
|
|
275
|
+
row = await cursor.fetchone()
|
|
276
|
+
if row:
|
|
277
|
+
return GraphNode(
|
|
278
|
+
id=row["id"],
|
|
279
|
+
node_type=row["node_type"],
|
|
280
|
+
properties=json.loads(row["properties"]),
|
|
281
|
+
)
|
|
282
|
+
except Exception as exc:
|
|
283
|
+
logger.error("Failed to query node %s: %s", node_id, exc)
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
async def get_neighbors(self, node_id: str) -> list[tuple[GraphNode, str, GraphEdge]]:
|
|
287
|
+
"""Find all neighbouring nodes and their edge relations."""
|
|
288
|
+
neighbors: list[tuple[GraphNode, str, GraphEdge]] = []
|
|
289
|
+
try:
|
|
290
|
+
async with self._pool.read() as conn:
|
|
291
|
+
cursor = await conn.execute(
|
|
292
|
+
"""
|
|
293
|
+
SELECT e.relation_type, e.properties as edge_props,
|
|
294
|
+
n.id, n.node_type, n.properties as node_props
|
|
295
|
+
FROM graph_edges e
|
|
296
|
+
JOIN graph_nodes n ON e.target = n.id
|
|
297
|
+
WHERE e.source = ?
|
|
298
|
+
""",
|
|
299
|
+
(node_id,),
|
|
300
|
+
)
|
|
301
|
+
rows = await cursor.fetchall()
|
|
302
|
+
for row in rows:
|
|
303
|
+
target_node = GraphNode(
|
|
304
|
+
id=row["id"],
|
|
305
|
+
node_type=row["node_type"],
|
|
306
|
+
properties=json.loads(row["node_props"]),
|
|
307
|
+
)
|
|
308
|
+
edge = GraphEdge(
|
|
309
|
+
source=node_id,
|
|
310
|
+
target=row["id"],
|
|
311
|
+
relation_type=row["relation_type"],
|
|
312
|
+
properties=json.loads(row["edge_props"]),
|
|
313
|
+
)
|
|
314
|
+
neighbors.append((target_node, "outgoing", edge))
|
|
315
|
+
except Exception as exc:
|
|
316
|
+
logger.error("Failed to query graph neighbours for %s: %s", node_id, exc)
|
|
317
|
+
return neighbors
|
|
318
|
+
|
|
319
|
+
async def find_shortest_path(
|
|
320
|
+
self,
|
|
321
|
+
start_id: str,
|
|
322
|
+
end_id: str,
|
|
323
|
+
max_depth: int = 4,
|
|
324
|
+
) -> list[str] | None:
|
|
325
|
+
"""BFS to find the shortest relationship path between two nodes."""
|
|
326
|
+
if start_id == end_id:
|
|
327
|
+
return [start_id]
|
|
328
|
+
|
|
329
|
+
queue: list[list[str]] = [[start_id]]
|
|
330
|
+
visited = {start_id}
|
|
331
|
+
|
|
332
|
+
while queue:
|
|
333
|
+
path = queue.pop(0)
|
|
334
|
+
node = path[-1]
|
|
335
|
+
|
|
336
|
+
if len(path) > max_depth:
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
for neighbor_node, _, _ in await self.get_neighbors(node):
|
|
340
|
+
n_id = neighbor_node.id
|
|
341
|
+
if n_id == end_id:
|
|
342
|
+
return path + [end_id]
|
|
343
|
+
if n_id not in visited:
|
|
344
|
+
visited.add(n_id)
|
|
345
|
+
queue.append(path + [n_id])
|
|
346
|
+
|
|
347
|
+
return None
|
|
348
|
+
|
|
349
|
+
async def query_execution_lineage(self, file_path: str) -> list[dict[str, Any]]:
|
|
350
|
+
"""Query historical execution lineage for a file path."""
|
|
351
|
+
attempts = []
|
|
352
|
+
try:
|
|
353
|
+
async with self._pool.read() as conn:
|
|
354
|
+
cursor = await conn.execute(
|
|
355
|
+
"""
|
|
356
|
+
SELECT id, task_id, node_type, status, parameters, outcome, timestamp
|
|
357
|
+
FROM execution_nodes
|
|
358
|
+
WHERE parameters LIKE ?
|
|
359
|
+
ORDER BY timestamp DESC
|
|
360
|
+
""",
|
|
361
|
+
(f"%{file_path}%",),
|
|
362
|
+
)
|
|
363
|
+
rows = await cursor.fetchall()
|
|
364
|
+
for row in rows:
|
|
365
|
+
attempts.append(
|
|
366
|
+
{
|
|
367
|
+
"id": row["id"],
|
|
368
|
+
"task_id": row["task_id"],
|
|
369
|
+
"node_type": row["node_type"],
|
|
370
|
+
"status": row["status"],
|
|
371
|
+
"parameters": json.loads(row["parameters"]),
|
|
372
|
+
"outcome": row["outcome"],
|
|
373
|
+
"timestamp": row["timestamp"],
|
|
374
|
+
}
|
|
375
|
+
)
|
|
376
|
+
except Exception as exc:
|
|
377
|
+
logger.error("Failed to query execution lineage for %s: %s", file_path, exc)
|
|
378
|
+
return attempts
|