Coverage for src \ truenex_memory \ mcp \ tools.py: 71%
49 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-19 10:21 +0200
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-19 10:21 +0200
1"""Local MCP-compatible tool functions."""
3from __future__ import annotations
5from pathlib import Path
7from truenex_memory.core.memory_service import MemoryService
8from truenex_memory.ingestion.global_context import build_project_context
9from truenex_memory.ingestion.global_status import build_global_status
10from truenex_memory.retrieval.result import search_payload
13def memory_search(query: str, top_k: int = 5, *, project_root: Path | str = ".") -> dict[str, object]:
14 """Search local memory using the stable MCP result shape."""
16 service = MemoryService(project_root)
17 results = service.search(query, top_k=top_k)
18 return search_payload(query, results, trace_id=service.last_trace_id)
21def memory_add(
22 content: str,
23 memory_type: str = "note",
24 *,
25 project_root: Path | str = ".",
26) -> dict[str, object]:
27 """Add a local memory node."""
29 service = MemoryService(project_root)
30 memory_id = service.add(content, memory_type=memory_type)
31 return {"id": memory_id, "status": "active", "memory_type": memory_type}
34def global_status(
35 home: str | Path | None = None,
36 catalog: str | Path | None = None,
37 db: str | Path | None = None,
38) -> dict[str, object]:
39 """Read-only global status report for the Truenex Memory global store."""
41 _home = Path(home) if home else Path.home()
42 catalog_path = Path(catalog) if catalog else _home / ".truenex-memory" / "sources.json"
43 db_path = Path(db) if db else _home / ".truenex-memory" / "truenex_memory.db"
45 report = build_global_status(catalog_path=catalog_path, db_path=db_path)
46 return report.to_dict()
49def global_project_context(
50 project: str,
51 home: str | Path | None = None,
52 catalog: str | Path | None = None,
53 db: str | Path | None = None,
54 limit: int = 20,
55) -> dict[str, object]:
56 """Read-only project context report for a project in the global store."""
58 if not isinstance(project, str) or not project.strip():
59 raise ValueError("project must be a non-empty string")
60 if not isinstance(limit, int) or limit < 1 or limit > 100:
61 raise ValueError("limit must be an integer between 1 and 100")
63 _home = Path(home) if home else Path.home()
64 catalog_path = Path(catalog) if catalog else _home / ".truenex-memory" / "sources.json"
65 db_path = Path(db) if db else _home / ".truenex-memory" / "truenex_memory.db"
67 report = build_project_context(
68 project_query=project,
69 catalog_path=catalog_path,
70 db_path=db_path,
71 limit=limit,
72 )
73 return report.to_dict()
76from truenex_memory.store.task_store import TaskStore, TASK_TYPES, BRAIN_JUDGMENTS
79def _default_task_store(db: str | None = None) -> TaskStore:
80 db_path = Path(db) if db else Path.home() / ".truenex-memory" / "truenex_memory.db"
81 return TaskStore(db_path)
84def task_open(
85 title: str,
86 task_type: str = "feature",
87 *,
88 project: str | None = None,
89 agent_session_id: str | None = None,
90 db: str | None = None,
91) -> dict[str, object]:
92 """Open a new task record in the adaptive pipeline."""
93 if task_type not in TASK_TYPES:
94 raise ValueError(f"task_type must be one of {sorted(TASK_TYPES)}")
95 task_id = _default_task_store(db).task_open(title, task_type, project=project, agent_session_id=agent_session_id)
96 return {"task_id": task_id, "status": "open"}
99def task_step_add(
100 task_id: str,
101 *,
102 prompt_used: str | None = None,
103 output: str | None = None,
104 brain_judgment: str | None = None,
105 tokens_used: int | None = None,
106 duration_s: float | None = None,
107 model_used: str | None = None,
108 db: str | None = None,
109) -> dict[str, object]:
110 """Add a step record to an open task."""
111 if brain_judgment is not None and brain_judgment not in BRAIN_JUDGMENTS:
112 raise ValueError(f"brain_judgment must be one of {sorted(BRAIN_JUDGMENTS)}")
113 step_id = _default_task_store(db).step_add(
114 task_id, prompt_used=prompt_used, output=output, brain_judgment=brain_judgment,
115 tokens_used=tokens_used, duration_s=duration_s, model_used=model_used,
116 )
117 return {"step_id": step_id, "task_id": task_id}
120def task_close(
121 task_id: str,
122 *,
123 human_outcome: int | None = None,
124 human_comment: str | None = None,
125 db: str | None = None,
126) -> dict[str, object]:
127 """Close a task. Provide human_outcome (1/0/-1) or omit for unrated."""
128 if human_outcome is not None and human_outcome not in (1, 0, -1):
129 raise ValueError("human_outcome must be 1, 0, or -1")
130 record = _default_task_store(db).task_close(task_id, human_outcome=human_outcome, human_comment=human_comment)
131 return {"task_id": record.task_id, "status": record.status, "human_outcome": record.human_outcome, "closed_at": record.closed_at}