concierge-graph 3.8.2__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.
- agents/__init__.py +14 -0
- agents/revisor_critico.py +610 -0
- concierge_graph-3.8.2.dist-info/METADATA +327 -0
- concierge_graph-3.8.2.dist-info/RECORD +37 -0
- concierge_graph-3.8.2.dist-info/WHEEL +5 -0
- concierge_graph-3.8.2.dist-info/entry_points.txt +3 -0
- concierge_graph-3.8.2.dist-info/licenses/LICENSE +21 -0
- concierge_graph-3.8.2.dist-info/top_level.txt +8 -0
- core/__init__.py +23 -0
- core/config.py +192 -0
- core/hybrid_search.py +288 -0
- core/memory_extractor.py +245 -0
- core/middleware.py +723 -0
- core/probabilistic_retriever.py +99 -0
- core/project_index.py +316 -0
- core/vector_backend.py +471 -0
- ingestion/__init__.py +25 -0
- ingestion/crawler.py +722 -0
- ingestion/orchestrator.py +920 -0
- ingestion/parser.py +984 -0
- ingestion/summarizer.py +948 -0
- interface/__init__.py +16 -0
- interface/action_hooks.py +261 -0
- interface/cli.py +391 -0
- interface/mcp_server.py +1737 -0
- main.py +281 -0
- scripts/bootstrap_core_memory.py +93 -0
- services/__init__.py +13 -0
- services/janitor.py +762 -0
- storage/__init__.py +31 -0
- storage/base_backend.py +241 -0
- storage/connection.py +310 -0
- storage/logic.py +703 -0
- storage/schema.py +390 -0
- storage/semantic_logic.py +125 -0
- storage/store.py +802 -0
- storage/vector_store.py +759 -0
interface/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
interface/ - External Interface of Grafo Concierge v3.8.0
|
|
3
|
+
|
|
4
|
+
Modules:
|
|
5
|
+
mcp_server.py -> FastMCP server with 7 tools (mine, search, commit, wakeup, resume, load, status)
|
|
6
|
+
action_hooks.py -> Reactive lifecycle triggers (on_planning, on_execution, on_done)
|
|
7
|
+
cli.py -> Terminal interface (argparse) with 9 commands
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from interface.mcp_server import GrafoConciergeServer
|
|
11
|
+
from interface.action_hooks import ActionHooks
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"GrafoConciergeServer",
|
|
15
|
+
"ActionHooks",
|
|
16
|
+
]
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""
|
|
2
|
+
interface/action_hooks.py — Grafo Concierge v3.8.0 (Absolute Solidity)
|
|
3
|
+
|
|
4
|
+
Reactive Lifecycle Triggers for Operational Modules.
|
|
5
|
+
|
|
6
|
+
This module exposes decorators and functions that allow any
|
|
7
|
+
Operational Module (Cursor Skills, n8n Agents, Automations)
|
|
8
|
+
to connect to Grafo Concierge at key lifecycle moments.
|
|
9
|
+
|
|
10
|
+
Triggers:
|
|
11
|
+
on_planning() -> Reactivates consciousness + searches relevant drawers
|
|
12
|
+
on_execution() -> Focused search + lazy load of specific nodes
|
|
13
|
+
on_done() -> Audited commit + trajectory decay
|
|
14
|
+
|
|
15
|
+
Typical flow of an Operational Module:
|
|
16
|
+
1. Module receives user task
|
|
17
|
+
2. Calls hooks.on_planning() -> receives Compass + context
|
|
18
|
+
3. Executes task using hooks.on_execution() to fetch code
|
|
19
|
+
4. Finishes with hooks.on_done() -> writes result to graph
|
|
20
|
+
|
|
21
|
+
Integration:
|
|
22
|
+
- Consumes core.middleware.GrafoConcierge as sole dependency
|
|
23
|
+
- Consumes agents.revisor_critico.RevisorCritico for auditing
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import logging
|
|
29
|
+
from typing import Any, Optional
|
|
30
|
+
|
|
31
|
+
from core.middleware import GrafoConcierge
|
|
32
|
+
from agents.revisor_critico import RevisorCritico
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger("grafo-concierge.hooks")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ActionHooks:
|
|
38
|
+
"""Reactive triggers for Operational Modules.
|
|
39
|
+
|
|
40
|
+
Centralizes contact points between Operational Modules and
|
|
41
|
+
Grafo Concierge. Each method corresponds to a phase of
|
|
42
|
+
a task's lifecycle.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
concierge: Central Facade instance.
|
|
46
|
+
revisor: Critical Reviewer instance (optional).
|
|
47
|
+
If None, commits do not go through LLM auditing.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
concierge: GrafoConcierge,
|
|
53
|
+
revisor: Optional[RevisorCritico] = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
self._gc = concierge
|
|
56
|
+
self._revisor = revisor or RevisorCritico()
|
|
57
|
+
|
|
58
|
+
logger.info(
|
|
59
|
+
"ActionHooks initialized: reviewer=%s",
|
|
60
|
+
"LLM" if revisor and revisor._llm else "heuristic",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# ===================================================================
|
|
64
|
+
# ON_PLANNING — Consciousness reactivation
|
|
65
|
+
# ===================================================================
|
|
66
|
+
|
|
67
|
+
def on_planning(
|
|
68
|
+
self,
|
|
69
|
+
project_uuid: str,
|
|
70
|
+
task: str,
|
|
71
|
+
top_k: int = 5,
|
|
72
|
+
node_type: Optional[str] = None,
|
|
73
|
+
) -> dict:
|
|
74
|
+
"""Planning trigger — prepares the context for the task.
|
|
75
|
+
|
|
76
|
+
Flow:
|
|
77
|
+
1. Wake-up -> loads Compass + Reference Wings + commits
|
|
78
|
+
2. Hybrid Search -> finds relevant drawers for the task
|
|
79
|
+
3. Returns full context package
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
project_uuid: UUID of the project.
|
|
83
|
+
task: Description of the task to be executed.
|
|
84
|
+
top_k: Maximum number of search results.
|
|
85
|
+
node_type: Optional surgical filter.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Dict with:
|
|
89
|
+
{
|
|
90
|
+
"wake_up": dict (Compass + Wings + commits),
|
|
91
|
+
"relevant_nodes": list[dict] (search results),
|
|
92
|
+
"task": str,
|
|
93
|
+
}
|
|
94
|
+
"""
|
|
95
|
+
logger.info("on_planning: project=%s, task='%.50s...'", project_uuid, task)
|
|
96
|
+
|
|
97
|
+
# 1. Wake-up
|
|
98
|
+
wake_data = self._gc.wake_up(project_uuid)
|
|
99
|
+
|
|
100
|
+
# 2. Hybrid Search
|
|
101
|
+
search_results = self._gc.hybrid_search(
|
|
102
|
+
query=task,
|
|
103
|
+
project_uuid=project_uuid,
|
|
104
|
+
top_k=top_k,
|
|
105
|
+
node_type=node_type,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
result = {
|
|
109
|
+
"wake_up": wake_data,
|
|
110
|
+
"relevant_nodes": search_results,
|
|
111
|
+
"task": task,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
logger.info(
|
|
115
|
+
"on_planning OK: wake_up with %d commits, %d relevant nodes.",
|
|
116
|
+
len(wake_data.get("recent_commits", [])),
|
|
117
|
+
len(search_results),
|
|
118
|
+
)
|
|
119
|
+
return result
|
|
120
|
+
|
|
121
|
+
# ===================================================================
|
|
122
|
+
# ON_EXECUTION — Focused search during execution
|
|
123
|
+
# ===================================================================
|
|
124
|
+
|
|
125
|
+
def on_execution(
|
|
126
|
+
self,
|
|
127
|
+
project_uuid: str,
|
|
128
|
+
task: str,
|
|
129
|
+
top_k: int = 10,
|
|
130
|
+
node_type: Optional[str] = None,
|
|
131
|
+
include_references: bool = False,
|
|
132
|
+
rerank: bool = True,
|
|
133
|
+
) -> list[dict]:
|
|
134
|
+
"""Execution trigger — searches and filters drawers by relevance.
|
|
135
|
+
|
|
136
|
+
Flow:
|
|
137
|
+
1. Hybrid Search v4 with Strict Scoping
|
|
138
|
+
2. If rerank=True, Critical Reviewer filters top-5
|
|
139
|
+
3. Returns relevant drawers for the task
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
project_uuid: UUID of the project.
|
|
143
|
+
task: Description of the current task.
|
|
144
|
+
top_k: Maximum number of initial search results.
|
|
145
|
+
node_type: Surgical filter (FACT, SKILL, INSIGHT, etc.).
|
|
146
|
+
include_references: Include Reference Wings.
|
|
147
|
+
rerank: Apply Critical Reviewer Reranking.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
List of dicts with filtered results.
|
|
151
|
+
"""
|
|
152
|
+
logger.info("on_execution: project=%s, task='%.50s...'", project_uuid, task)
|
|
153
|
+
|
|
154
|
+
# 1. Hybrid Search
|
|
155
|
+
search_results = self._gc.hybrid_search(
|
|
156
|
+
query=task,
|
|
157
|
+
project_uuid=project_uuid,
|
|
158
|
+
top_k=top_k,
|
|
159
|
+
include_references=include_references,
|
|
160
|
+
node_type=node_type,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# 2. Reranking (if active and there are results)
|
|
164
|
+
if rerank and search_results:
|
|
165
|
+
search_results = self._revisor.rerank(
|
|
166
|
+
candidates=search_results,
|
|
167
|
+
task_context=task,
|
|
168
|
+
max_results=5,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
logger.info(
|
|
172
|
+
"on_execution OK: %d results (rerank=%s).",
|
|
173
|
+
len(search_results), rerank,
|
|
174
|
+
)
|
|
175
|
+
return search_results
|
|
176
|
+
|
|
177
|
+
# ===================================================================
|
|
178
|
+
# ON_DONE — Audited commit post-task
|
|
179
|
+
# ===================================================================
|
|
180
|
+
|
|
181
|
+
def on_done(
|
|
182
|
+
self,
|
|
183
|
+
project_uuid: str,
|
|
184
|
+
outcome: dict,
|
|
185
|
+
) -> dict:
|
|
186
|
+
"""Conclusion trigger — registers result in the graph.
|
|
187
|
+
|
|
188
|
+
Flow:
|
|
189
|
+
1. Critical Reviewer audits the draft
|
|
190
|
+
2. If approved, writes commit to the graph
|
|
191
|
+
3. If there is an error/solution, registers episodic trajectory
|
|
192
|
+
4. Returns operation result
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
project_uuid: UUID of the project.
|
|
196
|
+
outcome: Dict with task outcome:
|
|
197
|
+
- phase (str): Phase (planning, build, done, review)
|
|
198
|
+
- technical_changes (str): Technical changes
|
|
199
|
+
- updated_pointers (list[str]): Updated pointers
|
|
200
|
+
- node_ids (list[int], optional): Affected nodes
|
|
201
|
+
- erro_encontrado (str, optional): Error occurred
|
|
202
|
+
- solucao_aplicada (str, optional): Solution applied
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Dict with:
|
|
206
|
+
{
|
|
207
|
+
"commit_id": int,
|
|
208
|
+
"audit": AuditResult.to_dict(),
|
|
209
|
+
"trajectory_id": int (if there was an error),
|
|
210
|
+
}
|
|
211
|
+
"""
|
|
212
|
+
logger.info("on_done: project=%s, phase='%s'", project_uuid, outcome.get("phase", "?"))
|
|
213
|
+
|
|
214
|
+
result: dict[str, Any] = {}
|
|
215
|
+
|
|
216
|
+
# 1. Auditoria do commit
|
|
217
|
+
audit = self._revisor.audit({
|
|
218
|
+
"phase": outcome.get("phase", "done"),
|
|
219
|
+
"technical_changes": outcome.get("technical_changes", ""),
|
|
220
|
+
"updated_pointers": outcome.get("updated_pointers", []),
|
|
221
|
+
"source_wing": "primary",
|
|
222
|
+
})
|
|
223
|
+
result["audit"] = audit.to_dict()
|
|
224
|
+
|
|
225
|
+
# 2. Grava commit (mesmo se partial_audit, para não perder dados)
|
|
226
|
+
if audit.approved:
|
|
227
|
+
commit_id = self._gc.commit_memory(
|
|
228
|
+
project_uuid=project_uuid,
|
|
229
|
+
phase=outcome.get("phase", "done"),
|
|
230
|
+
technical_changes=audit.technical_changes,
|
|
231
|
+
updated_pointers=audit.updated_pointers,
|
|
232
|
+
node_ids=outcome.get("node_ids"),
|
|
233
|
+
)
|
|
234
|
+
result["commit_id"] = commit_id
|
|
235
|
+
else:
|
|
236
|
+
result["commit_id"] = None
|
|
237
|
+
logger.warning("on_done: commit REJEITADO — %s", audit.reason)
|
|
238
|
+
|
|
239
|
+
# 3. Registra trajetória episódica (se houve erro)
|
|
240
|
+
if outcome.get("erro_encontrado"):
|
|
241
|
+
try:
|
|
242
|
+
trajectory_id = self._gc.store.create_trajectory(
|
|
243
|
+
project_uuid=project_uuid,
|
|
244
|
+
prompt_origem=outcome.get("task", ""),
|
|
245
|
+
tentativa_execucao=outcome.get("technical_changes", ""),
|
|
246
|
+
erro_encontrado=outcome.get("erro_encontrado"),
|
|
247
|
+
solucao_aplicada=outcome.get("solucao_aplicada"),
|
|
248
|
+
)
|
|
249
|
+
result["trajectory_id"] = trajectory_id
|
|
250
|
+
logger.info(
|
|
251
|
+
"Trajetória episódica registrada: id=%d", trajectory_id,
|
|
252
|
+
)
|
|
253
|
+
except Exception as e:
|
|
254
|
+
logger.error("Falha ao registrar trajetória: %s", e)
|
|
255
|
+
result["trajectory_id"] = None
|
|
256
|
+
|
|
257
|
+
logger.info(
|
|
258
|
+
"on_done OK: commit_id=%s, audit_approved=%s, partial=%s",
|
|
259
|
+
result.get("commit_id"), audit.approved, audit.partial_audit,
|
|
260
|
+
)
|
|
261
|
+
return result
|
interface/cli.py
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"""
|
|
2
|
+
interface/cli.py — Grafo Concierge v3.8.0 (Absolute Solidity)
|
|
3
|
+
|
|
4
|
+
Terminal Interface (CLI) to manage Grafo Concierge
|
|
5
|
+
via Bash/PowerShell.
|
|
6
|
+
|
|
7
|
+
Commands:
|
|
8
|
+
grafo-concierge register → Registers a new project
|
|
9
|
+
grafo-concierge mine → Directory ingestion
|
|
10
|
+
grafo-concierge search → Hybrid Search v4
|
|
11
|
+
grafo-concierge wakeup → Consciousness reactivation
|
|
12
|
+
grafo-concierge resume → Context Compass
|
|
13
|
+
grafo-concierge commit → Changelog registration
|
|
14
|
+
grafo-concierge load → Node Lazy Load
|
|
15
|
+
grafo-concierge status → System health status
|
|
16
|
+
grafo-concierge projects → Lists projects
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
python -m interface.cli mine --path /projects/vortex --name vortex-pro
|
|
20
|
+
python -m interface.cli search --query "JWT authentication" --project abc123
|
|
21
|
+
python -m interface.cli wakeup --project abc123
|
|
22
|
+
|
|
23
|
+
Integration:
|
|
24
|
+
Consumes core.middleware.GrafoConcierge as sole dependency.
|
|
25
|
+
Bootstrap is automatically performed via _bootstrap_concierge().
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
import json
|
|
32
|
+
import logging
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Optional
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger("grafo-concierge.cli")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Bootstrap — Initializes the GrafoConcierge for CLI use
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
def _bootstrap_concierge():
|
|
46
|
+
"""Initializes the Central Facade for CLI usage.
|
|
47
|
+
|
|
48
|
+
Uses the same environment variables as main.py.
|
|
49
|
+
Returns a ready-to-use GrafoConcierge instance.
|
|
50
|
+
"""
|
|
51
|
+
from dotenv import load_dotenv
|
|
52
|
+
load_dotenv()
|
|
53
|
+
|
|
54
|
+
from storage import SqliteStore, EmbeddingManager, EmbeddingTier, ChromaVectorStore
|
|
55
|
+
from ingestion import IngestionManager, ZoomSummarizer
|
|
56
|
+
from ingestion.summarizer import LLMAdapter
|
|
57
|
+
from core.middleware import GrafoConcierge
|
|
58
|
+
|
|
59
|
+
# Dynamic anchor: interface/ → project root
|
|
60
|
+
_project_root = Path(__file__).parent.parent.resolve()
|
|
61
|
+
|
|
62
|
+
def resolve_project_path(env_value: str, default_rel: str) -> str:
|
|
63
|
+
val = env_value or default_rel
|
|
64
|
+
path = Path(val)
|
|
65
|
+
if path.is_absolute():
|
|
66
|
+
return str(path)
|
|
67
|
+
return str((_project_root / path).resolve())
|
|
68
|
+
|
|
69
|
+
db_path = resolve_project_path(os.environ.get("GRAFO_DB_PATH", ""), "data/concierge.db")
|
|
70
|
+
chroma_path = resolve_project_path(os.environ.get("GRAFO_CHROMA_PATH", ""), "data/chroma")
|
|
71
|
+
chroma_collection = os.environ.get("GRAFO_CHROMA_COLLECTION", "grafo_concierge")
|
|
72
|
+
llm_model = os.environ.get("GRAFO_LLM_MODEL", "gemini-2.0-flash")
|
|
73
|
+
llm_api_key = os.environ.get("GRAFO_LLM_API_KEY", "")
|
|
74
|
+
llm_base_url = os.environ.get("GRAFO_LLM_BASE_URL", "")
|
|
75
|
+
|
|
76
|
+
os.makedirs(os.path.dirname(db_path) or ".", exist_ok=True)
|
|
77
|
+
os.makedirs(chroma_path, exist_ok=True)
|
|
78
|
+
|
|
79
|
+
store = SqliteStore(db_path)
|
|
80
|
+
embedder = EmbeddingManager(tier=EmbeddingTier.FLASH)
|
|
81
|
+
|
|
82
|
+
vector_backend = os.environ.get("GRAFO_VECTOR_BACKEND", "chroma").lower()
|
|
83
|
+
if vector_backend == "qdrant":
|
|
84
|
+
qdrant_url = os.environ.get("GRAFO_QDRANT_URL", "http://localhost:6333")
|
|
85
|
+
qdrant_key = os.environ.get("GRAFO_QDRANT_API_KEY", "") or None
|
|
86
|
+
from core.vector_backend import QdrantVectorStore
|
|
87
|
+
vector_store = QdrantVectorStore(
|
|
88
|
+
url=qdrant_url,
|
|
89
|
+
api_key=qdrant_key,
|
|
90
|
+
collection_name=chroma_collection,
|
|
91
|
+
embedding_dimensions=embedder.dimensions,
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
vector_store = ChromaVectorStore(
|
|
95
|
+
persist_dir=chroma_path,
|
|
96
|
+
collection_name=chroma_collection,
|
|
97
|
+
embedding_manager=embedder,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
llm_adapter = LLMAdapter(
|
|
101
|
+
model_name=llm_model,
|
|
102
|
+
api_key=llm_api_key or None,
|
|
103
|
+
base_url=llm_base_url or None,
|
|
104
|
+
)
|
|
105
|
+
summarizer = ZoomSummarizer(llm_adapter=llm_adapter, sqlite_store=store)
|
|
106
|
+
|
|
107
|
+
ingestion_manager = IngestionManager(
|
|
108
|
+
sqlite_store=store,
|
|
109
|
+
vector_store=vector_store,
|
|
110
|
+
embedding_manager=embedder,
|
|
111
|
+
summarizer=summarizer,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
gc = GrafoConcierge(
|
|
115
|
+
sqlite_store=store,
|
|
116
|
+
vector_store=vector_store,
|
|
117
|
+
embedding_manager=embedder,
|
|
118
|
+
ingestion_manager=ingestion_manager,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return gc, store
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _print_json(data: dict) -> None:
|
|
125
|
+
"""Prints result formatted as JSON."""
|
|
126
|
+
print(json.dumps(data, indent=2, ensure_ascii=False, default=str))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# CLI Commands
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
def cmd_register(args, gc, store):
|
|
134
|
+
"""Registers a new project."""
|
|
135
|
+
uuid = gc.register_project(
|
|
136
|
+
folder_name=args.name,
|
|
137
|
+
wing=args.wing,
|
|
138
|
+
privacy_level=args.privacy or "PUBLIC",
|
|
139
|
+
summary=args.summary,
|
|
140
|
+
)
|
|
141
|
+
print(f"Project registered: {args.name} → {uuid}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def cmd_mine(args, gc, store):
|
|
145
|
+
"""Executes directory ingestion."""
|
|
146
|
+
uuid = gc.register_project(folder_name=args.name)
|
|
147
|
+
result = gc.mine(uuid, args.path, auto_tag=not args.no_tag)
|
|
148
|
+
_print_json(result)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def cmd_search(args, gc, store):
|
|
152
|
+
"""Hybrid Search v4."""
|
|
153
|
+
results = gc.hybrid_search(
|
|
154
|
+
query=args.query,
|
|
155
|
+
project_uuid=args.project,
|
|
156
|
+
top_k=args.top_k,
|
|
157
|
+
node_type=args.node_type,
|
|
158
|
+
include_references=args.refs,
|
|
159
|
+
all_wings=args.all_wings,
|
|
160
|
+
)
|
|
161
|
+
_print_json({"results_count": len(results), "results": results})
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def cmd_wakeup(args, gc, store):
|
|
165
|
+
"""Consciousness reactivation."""
|
|
166
|
+
result = gc.wake_up(args.project)
|
|
167
|
+
_print_json(result)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def cmd_resume(args, gc, store):
|
|
171
|
+
"""Context Compass."""
|
|
172
|
+
resume = gc.get_resume(args.project)
|
|
173
|
+
print(resume)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def cmd_commit(args, gc, store):
|
|
177
|
+
"""Commit registration."""
|
|
178
|
+
pointers = args.pointers.split(",") if args.pointers else []
|
|
179
|
+
commit_id = gc.commit_memory(
|
|
180
|
+
project_uuid=args.project,
|
|
181
|
+
phase=args.phase,
|
|
182
|
+
technical_changes=args.changes,
|
|
183
|
+
updated_pointers=pointers,
|
|
184
|
+
)
|
|
185
|
+
print(f"Commit registered: id={commit_id}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def cmd_load(args, gc, store):
|
|
189
|
+
"""Lazy Load of a node."""
|
|
190
|
+
result = gc.lazy_load(args.node_id)
|
|
191
|
+
_print_json(result)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def cmd_status(args, gc, store):
|
|
195
|
+
"""System status."""
|
|
196
|
+
if args.project:
|
|
197
|
+
result = gc.status(args.project)
|
|
198
|
+
else:
|
|
199
|
+
projects = store.list_projects()
|
|
200
|
+
result = {
|
|
201
|
+
"system": "Grafo Concierge v3.8.0",
|
|
202
|
+
"total_projects": len(projects),
|
|
203
|
+
"projects": [
|
|
204
|
+
{"uuid": p["uuid"], "name": p.get("folder_name", ""), "wing": p.get("primary_wing", "geral")}
|
|
205
|
+
for p in projects
|
|
206
|
+
],
|
|
207
|
+
}
|
|
208
|
+
_print_json(result)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def cmd_projects(args, gc, store):
|
|
212
|
+
"""Lists all projects."""
|
|
213
|
+
projects = store.list_projects()
|
|
214
|
+
if not projects:
|
|
215
|
+
print("No projects registered.")
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
print(f"{'UUID':<38} {'Name':<25} {'Wing':<20} {'Privacy':<12}")
|
|
219
|
+
print("-" * 95)
|
|
220
|
+
for p in projects:
|
|
221
|
+
print(
|
|
222
|
+
f"{p['uuid']:<38} {p.get('folder_name', ''):<25} "
|
|
223
|
+
f"{p.get('primary_wing', 'general'):<20} {p.get('privacy_level', 'PUBLIC'):<12}"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def cmd_sync_vector(args, gc, store):
|
|
228
|
+
"""Synchronizes and reconciles missing embeddings in the active vector store manually."""
|
|
229
|
+
from services.janitor import JanitorService, MaintenanceReport
|
|
230
|
+
|
|
231
|
+
# Initializes local JanitorService
|
|
232
|
+
janitor = JanitorService(
|
|
233
|
+
sqlite_store=store,
|
|
234
|
+
vector_store=gc._vector,
|
|
235
|
+
ingestion_manager=gc._ingestion,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
projects_to_sync = []
|
|
239
|
+
if args.project:
|
|
240
|
+
projects_to_sync.append(args.project)
|
|
241
|
+
else:
|
|
242
|
+
all_projects = store.list_projects()
|
|
243
|
+
projects_to_sync = [p["uuid"] for p in all_projects]
|
|
244
|
+
|
|
245
|
+
if not projects_to_sync:
|
|
246
|
+
print("No registered projects found to synchronize.")
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
print(f"Starting manual batch vector reconciliation for {len(projects_to_sync)} projects...")
|
|
250
|
+
for p_uuid in projects_to_sync:
|
|
251
|
+
p_name = next((p["folder_name"] for p in store.list_projects() if p["uuid"] == p_uuid), p_uuid)
|
|
252
|
+
print(f"-> Synchronizing project: {p_name} ({p_uuid})...")
|
|
253
|
+
report = MaintenanceReport()
|
|
254
|
+
# Executes bidirectional reconciliation (deletes orphans and generates missing)
|
|
255
|
+
janitor._sync_vectors(p_uuid, report)
|
|
256
|
+
if report.errors:
|
|
257
|
+
print(f" [ERROR] {report.errors[0]}")
|
|
258
|
+
else:
|
|
259
|
+
print(f" [OK] Vector reconciliation completed successfully.")
|
|
260
|
+
print("Reconciliation and synchronization finished!")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
# Argument parser
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
268
|
+
"""Builds the CLI argument parser."""
|
|
269
|
+
|
|
270
|
+
parser = argparse.ArgumentParser(
|
|
271
|
+
prog="grafo-concierge",
|
|
272
|
+
description="Grafo Concierge v3.8.0 — Sovereign Memory for AI Agents",
|
|
273
|
+
)
|
|
274
|
+
subparsers = parser.add_subparsers(dest="command", help="Available command")
|
|
275
|
+
|
|
276
|
+
# --- register ---
|
|
277
|
+
p_register = subparsers.add_parser("register", help="Registers a new project")
|
|
278
|
+
p_register.add_argument("--name", required=True, help="Project name")
|
|
279
|
+
p_register.add_argument("--wing", default=None, help="Primary Wing")
|
|
280
|
+
p_register.add_argument("--privacy", default="PUBLIC", help="Privacy level")
|
|
281
|
+
p_register.add_argument("--summary", default=None, help="Project description")
|
|
282
|
+
|
|
283
|
+
# --- mine ---
|
|
284
|
+
p_mine = subparsers.add_parser("mine", help="Directory ingestion")
|
|
285
|
+
p_mine.add_argument("--path", required=True, help="Directory path")
|
|
286
|
+
p_mine.add_argument("--name", required=True, help="Project name")
|
|
287
|
+
p_mine.add_argument("--no-tag", action="store_true", help="Disables auto-tag")
|
|
288
|
+
|
|
289
|
+
# --- search ---
|
|
290
|
+
p_search = subparsers.add_parser("search", help="Hybrid Search v4")
|
|
291
|
+
p_search.add_argument("--query", required=True, help="Search text")
|
|
292
|
+
p_search.add_argument("--project", required=True, help="Project UUID")
|
|
293
|
+
p_search.add_argument("--top-k", type=int, default=10, help="Maximum results")
|
|
294
|
+
p_search.add_argument("--node-type", default=None, help="Node type filter")
|
|
295
|
+
p_search.add_argument("--refs", action="store_true", help="Include Reference Wings")
|
|
296
|
+
p_search.add_argument("--all-wings", action="store_true", help="Search in all wings")
|
|
297
|
+
|
|
298
|
+
# --- wakeup ---
|
|
299
|
+
p_wakeup = subparsers.add_parser("wakeup", help="Consciousness reactivation")
|
|
300
|
+
p_wakeup.add_argument("--project", required=True, help="Project UUID")
|
|
301
|
+
|
|
302
|
+
# --- resume ---
|
|
303
|
+
p_resume = subparsers.add_parser("resume", help="Context Compass")
|
|
304
|
+
p_resume.add_argument("--project", required=True, help="Project UUID")
|
|
305
|
+
|
|
306
|
+
# --- commit ---
|
|
307
|
+
p_commit = subparsers.add_parser("commit", help="Registers a memory commit")
|
|
308
|
+
p_commit.add_argument("--project", required=True, help="Project UUID")
|
|
309
|
+
p_commit.add_argument("--phase", required=True, help="Current phase")
|
|
310
|
+
p_commit.add_argument("--changes", required=True, help="Technical changes")
|
|
311
|
+
p_commit.add_argument("--pointers", required=True, help="Pointers (comma-separated)")
|
|
312
|
+
|
|
313
|
+
# --- load ---
|
|
314
|
+
p_load = subparsers.add_parser("load", help="Lazy Load of a node")
|
|
315
|
+
p_load.add_argument("--node-id", type=int, required=True, help="Node ID")
|
|
316
|
+
|
|
317
|
+
# --- status ---
|
|
318
|
+
p_status = subparsers.add_parser("status", help="System status")
|
|
319
|
+
p_status.add_argument("--project", default=None, help="Project UUID (optional)")
|
|
320
|
+
|
|
321
|
+
# --- projects ---
|
|
322
|
+
subparsers.add_parser("projects", help="Lists all projects")
|
|
323
|
+
|
|
324
|
+
# --- sync-vector ---
|
|
325
|
+
p_sync_vector = subparsers.add_parser("sync-vector", help="Synchronizes and reconciles missing embeddings in the active vector store manually")
|
|
326
|
+
p_sync_vector.add_argument("--project", default=None, help="UUID of a specific project to synchronize (optional)")
|
|
327
|
+
|
|
328
|
+
return parser
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
# ---------------------------------------------------------------------------
|
|
332
|
+
# Main
|
|
333
|
+
# ---------------------------------------------------------------------------
|
|
334
|
+
|
|
335
|
+
COMMAND_MAP = {
|
|
336
|
+
"register": cmd_register,
|
|
337
|
+
"mine": cmd_mine,
|
|
338
|
+
"search": cmd_search,
|
|
339
|
+
"wakeup": cmd_wakeup,
|
|
340
|
+
"resume": cmd_resume,
|
|
341
|
+
"commit": cmd_commit,
|
|
342
|
+
"load": cmd_load,
|
|
343
|
+
"status": cmd_status,
|
|
344
|
+
"projects": cmd_projects,
|
|
345
|
+
"sync-vector": cmd_sync_vector,
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def main():
|
|
350
|
+
"""CLI entry point."""
|
|
351
|
+
parser = build_parser()
|
|
352
|
+
args = parser.parse_args()
|
|
353
|
+
|
|
354
|
+
if not args.command:
|
|
355
|
+
parser.print_help()
|
|
356
|
+
sys.exit(1)
|
|
357
|
+
|
|
358
|
+
# Setup minimal logging for CLI
|
|
359
|
+
logging.basicConfig(
|
|
360
|
+
level=logging.WARNING,
|
|
361
|
+
format="%(levelname)s: %(message)s",
|
|
362
|
+
stream=sys.stderr,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Bootstrap
|
|
366
|
+
try:
|
|
367
|
+
gc, store = _bootstrap_concierge()
|
|
368
|
+
except Exception as e:
|
|
369
|
+
print(f"Error initializing Grafo Concierge: {e}", file=sys.stderr)
|
|
370
|
+
sys.exit(1)
|
|
371
|
+
|
|
372
|
+
# Executes command
|
|
373
|
+
handler = COMMAND_MAP.get(args.command)
|
|
374
|
+
if handler:
|
|
375
|
+
try:
|
|
376
|
+
handler(args, gc, store)
|
|
377
|
+
except Exception as e:
|
|
378
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
379
|
+
sys.exit(1)
|
|
380
|
+
finally:
|
|
381
|
+
try:
|
|
382
|
+
store.close()
|
|
383
|
+
except Exception:
|
|
384
|
+
pass
|
|
385
|
+
else:
|
|
386
|
+
parser.print_help()
|
|
387
|
+
sys.exit(1)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
if __name__ == "__main__":
|
|
391
|
+
main()
|