@reconcrap/people-network-memory 0.1.0
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.
- package/README.md +476 -0
- package/docs/mcp_tools.md +138 -0
- package/harness_adapters/openclaw/mcp.managed.unix.template.json +25 -0
- package/harness_adapters/openclaw/mcp.managed.windows.template.json +26 -0
- package/harness_adapters/openclaw/mcp.template.json +14 -0
- package/harness_adapters/openclaw/ppl/SKILL.md +114 -0
- package/package.json +30 -0
- package/pyproject.toml +26 -0
- package/scripts/install_windows.ps1 +92 -0
- package/scripts/npm/people-memory.js +276 -0
- package/scripts/people_memory_bootstrap.py +247 -0
- package/scripts/run_graphiti_live_from_liepin.ps1 +87 -0
- package/scripts/run_tests_with_artifacts.ps1 +307 -0
- package/src/people_network_memory/__init__.py +6 -0
- package/src/people_network_memory/application/__init__.py +16 -0
- package/src/people_network_memory/application/normalization.py +1441 -0
- package/src/people_network_memory/application/services.py +921 -0
- package/src/people_network_memory/cli.py +1212 -0
- package/src/people_network_memory/config.py +268 -0
- package/src/people_network_memory/domain/__init__.py +55 -0
- package/src/people_network_memory/domain/identity.py +77 -0
- package/src/people_network_memory/domain/models.py +355 -0
- package/src/people_network_memory/fixtures/__init__.py +6 -0
- package/src/people_network_memory/fixtures/eval.py +398 -0
- package/src/people_network_memory/fixtures/extractor_eval.py +364 -0
- package/src/people_network_memory/fixtures/generator.py +290 -0
- package/src/people_network_memory/fixtures/report.py +252 -0
- package/src/people_network_memory/graphiti_adapter/__init__.py +9 -0
- package/src/people_network_memory/graphiti_adapter/episode_formatter.py +70 -0
- package/src/people_network_memory/graphiti_adapter/graphiti_store.py +655 -0
- package/src/people_network_memory/graphiti_adapter/indexer.py +194 -0
- package/src/people_network_memory/graphiti_adapter/ontology.py +68 -0
- package/src/people_network_memory/harness_adapters/__init__.py +2 -0
- package/src/people_network_memory/harness_adapters/openclaw/__init__.py +9 -0
- package/src/people_network_memory/harness_adapters/openclaw/installer.py +577 -0
- package/src/people_network_memory/harness_adapters/openclaw/integration_eval.py +508 -0
- package/src/people_network_memory/harness_adapters/openclaw/smoke.py +292 -0
- package/src/people_network_memory/infrastructure/__init__.py +2 -0
- package/src/people_network_memory/infrastructure/archive_backup.py +171 -0
- package/src/people_network_memory/infrastructure/diagnostics.py +171 -0
- package/src/people_network_memory/infrastructure/embeddings.py +155 -0
- package/src/people_network_memory/infrastructure/file_store.py +129 -0
- package/src/people_network_memory/infrastructure/graphiti_promotion.py +212 -0
- package/src/people_network_memory/infrastructure/id_generator.py +40 -0
- package/src/people_network_memory/infrastructure/in_memory_store.py +1008 -0
- package/src/people_network_memory/infrastructure/llm_extractor.py +476 -0
- package/src/people_network_memory/infrastructure/llm_identity_advisor.py +200 -0
- package/src/people_network_memory/infrastructure/llm_judge.py +162 -0
- package/src/people_network_memory/infrastructure/redaction.py +21 -0
- package/src/people_network_memory/infrastructure/release_check.py +186 -0
- package/src/people_network_memory/infrastructure/retrieval_intent.py +98 -0
- package/src/people_network_memory/infrastructure/semantic_index.py +262 -0
- package/src/people_network_memory/mcp_server/__init__.py +2 -0
- package/src/people_network_memory/mcp_server/contracts.py +85 -0
- package/src/people_network_memory/mcp_server/runtime.py +133 -0
- package/src/people_network_memory/mcp_server/tools.py +588 -0
- package/src/people_network_memory/ports/__init__.py +2 -0
- package/src/people_network_memory/ports/errors.py +25 -0
- package/src/people_network_memory/ports/interfaces.py +103 -0
- package/src/people_network_memory/projection/__init__.py +6 -0
- package/src/people_network_memory/projection/builders.py +46 -0
|
@@ -0,0 +1,1212 @@
|
|
|
1
|
+
"""Command line entrypoint for people-memory."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
9
|
+
from dataclasses import replace
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from people_network_memory.config import PeopleMemoryConfig
|
|
13
|
+
from people_network_memory.fixtures.eval import (
|
|
14
|
+
evaluate_dataset,
|
|
15
|
+
evaluate_retrieval_service,
|
|
16
|
+
evaluate_services,
|
|
17
|
+
)
|
|
18
|
+
from people_network_memory.fixtures.extractor_eval import evaluate_extractor
|
|
19
|
+
from people_network_memory.fixtures.generator import generate_mock_dataset
|
|
20
|
+
from people_network_memory.fixtures.report import write_eval_markdown_report
|
|
21
|
+
from people_network_memory.harness_adapters.openclaw import (
|
|
22
|
+
install_openclaw_adapter,
|
|
23
|
+
openclaw_checks,
|
|
24
|
+
)
|
|
25
|
+
from people_network_memory.harness_adapters.openclaw.integration_eval import (
|
|
26
|
+
run_harness_memory_integration_eval,
|
|
27
|
+
)
|
|
28
|
+
from people_network_memory.harness_adapters.openclaw.smoke import run_openclaw_adapter_smoke
|
|
29
|
+
from people_network_memory.infrastructure.archive_backup import (
|
|
30
|
+
create_archive_backup,
|
|
31
|
+
restore_archive_backup,
|
|
32
|
+
)
|
|
33
|
+
from people_network_memory.infrastructure.diagnostics import graphiti_spike_checks
|
|
34
|
+
from people_network_memory.infrastructure.embeddings import check_embedding_provider
|
|
35
|
+
from people_network_memory.infrastructure.file_store import JsonPeopleStore, local_json_path
|
|
36
|
+
from people_network_memory.infrastructure.graphiti_promotion import run_graphiti_promotion_gate
|
|
37
|
+
from people_network_memory.infrastructure.redaction import redact_sensitive_text
|
|
38
|
+
from people_network_memory.infrastructure.release_check import run_local_release_check
|
|
39
|
+
from people_network_memory.mcp_server.contracts import public_tool_contracts
|
|
40
|
+
from people_network_memory.mcp_server.runtime import build_runtime
|
|
41
|
+
from people_network_memory.ports.errors import PeopleMemoryError
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _print_json(payload: object) -> None:
|
|
45
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _write_json_output(payload: dict[str, object], output: str | None) -> None:
|
|
49
|
+
if not output:
|
|
50
|
+
_print_json(payload)
|
|
51
|
+
return
|
|
52
|
+
path = Path(output)
|
|
53
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8")
|
|
55
|
+
_print_json(
|
|
56
|
+
{
|
|
57
|
+
"ok": payload.get("ok", True),
|
|
58
|
+
"output": str(path),
|
|
59
|
+
"checked": payload.get("checked"),
|
|
60
|
+
"passes_v1_thresholds": payload.get("passes_v1_thresholds"),
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _config_from_args(args: argparse.Namespace, *, default_test_mode: bool = False) -> PeopleMemoryConfig:
|
|
66
|
+
config = PeopleMemoryConfig.from_env(
|
|
67
|
+
test_mode=bool(getattr(args, "test_mode", default_test_mode))
|
|
68
|
+
)
|
|
69
|
+
updates = {}
|
|
70
|
+
if getattr(args, "backend", None):
|
|
71
|
+
updates["backend"] = args.backend
|
|
72
|
+
if getattr(args, "data_path", None):
|
|
73
|
+
updates["data_path"] = args.data_path
|
|
74
|
+
if getattr(args, "graphiti_kuzu_path", None):
|
|
75
|
+
updates["graphiti_kuzu_path"] = args.graphiti_kuzu_path
|
|
76
|
+
if getattr(args, "retrieval_judge", None):
|
|
77
|
+
updates["retrieval_judge"] = args.retrieval_judge
|
|
78
|
+
if getattr(args, "ingestion_extractor", None):
|
|
79
|
+
updates["ingestion_extractor"] = args.ingestion_extractor
|
|
80
|
+
if getattr(args, "identity_advisor", None):
|
|
81
|
+
updates["identity_advisor"] = args.identity_advisor
|
|
82
|
+
if updates:
|
|
83
|
+
config = replace(config, **updates)
|
|
84
|
+
return config
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _build_optional_retrieval_judge(config: PeopleMemoryConfig) -> object | None:
|
|
88
|
+
if config.retrieval_judge != "llm":
|
|
89
|
+
return None
|
|
90
|
+
from people_network_memory.infrastructure.llm_judge import OpenAICompatibleRetrievalJudge
|
|
91
|
+
|
|
92
|
+
return OpenAICompatibleRetrievalJudge.from_config(config)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _read_json_file(path: str) -> dict[str, object]:
|
|
96
|
+
return json.loads(Path(path).read_text(encoding="utf-8"))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _cmd_start(args: argparse.Namespace) -> int:
|
|
100
|
+
config = _config_from_args(args)
|
|
101
|
+
runtime = build_runtime(config)
|
|
102
|
+
if args.once:
|
|
103
|
+
_print_json(
|
|
104
|
+
{
|
|
105
|
+
"status": "ok",
|
|
106
|
+
"service": "people-network-memory",
|
|
107
|
+
"backend": config.backend,
|
|
108
|
+
"test_mode": config.test_mode,
|
|
109
|
+
"tools": sorted(runtime.tools.tool_names()),
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
return 0
|
|
113
|
+
runtime.run_stdio()
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _cmd_doctor(args: argparse.Namespace) -> int:
|
|
118
|
+
config = _config_from_args(args)
|
|
119
|
+
checks = []
|
|
120
|
+
try:
|
|
121
|
+
config.validate_runtime()
|
|
122
|
+
checks.append({"name": "config", "ok": True})
|
|
123
|
+
except PeopleMemoryError as exc:
|
|
124
|
+
checks.append({"name": "config", "ok": False, "error": str(exc)})
|
|
125
|
+
checks.append(
|
|
126
|
+
{
|
|
127
|
+
"name": "telemetry",
|
|
128
|
+
"ok": not config.telemetry_enabled,
|
|
129
|
+
"detail": "GRAPHITI_TELEMETRY_ENABLED should be false for local personal data.",
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
data_path_check = {"name": "data_path", "ok": bool(config.data_path), "path": config.data_path}
|
|
133
|
+
if config.backend == "local_json":
|
|
134
|
+
target = local_json_path(config)
|
|
135
|
+
nearest = target.parent
|
|
136
|
+
while not nearest.exists() and nearest.parent != nearest:
|
|
137
|
+
nearest = nearest.parent
|
|
138
|
+
data_path_check["target"] = str(target)
|
|
139
|
+
data_path_check["nearest_existing_parent"] = str(nearest)
|
|
140
|
+
data_path_check["ok"] = bool(config.data_path) and nearest.exists()
|
|
141
|
+
checks.append(data_path_check)
|
|
142
|
+
if config.backend == "graphiti":
|
|
143
|
+
checks.extend(check.to_json() for check in graphiti_spike_checks(config))
|
|
144
|
+
if getattr(args, "agent", None) == "openclaw":
|
|
145
|
+
checks.extend(
|
|
146
|
+
check.to_json()
|
|
147
|
+
for check in openclaw_checks(
|
|
148
|
+
getattr(args, "openclaw_home", None) or "~/.openclaw"
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
ok = all(check["ok"] for check in checks)
|
|
152
|
+
_print_json({"ok": ok, "checks": checks})
|
|
153
|
+
return 0 if ok else 2
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _cmd_load_fixtures(args: argparse.Namespace) -> int:
|
|
157
|
+
dataset = generate_mock_dataset(seed=args.seed)
|
|
158
|
+
payload = dataset.model_dump(mode="json")
|
|
159
|
+
if args.summary:
|
|
160
|
+
_print_json(dataset.summary())
|
|
161
|
+
return 0
|
|
162
|
+
if args.output:
|
|
163
|
+
path = Path(args.output)
|
|
164
|
+
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
165
|
+
_print_json({"ok": True, "output": str(path), "summary": dataset.summary()})
|
|
166
|
+
return 0
|
|
167
|
+
_print_json(payload)
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _cmd_eval_fixtures(args: argparse.Namespace) -> int:
|
|
172
|
+
dataset = generate_mock_dataset(seed=args.seed)
|
|
173
|
+
result = evaluate_dataset(
|
|
174
|
+
dataset,
|
|
175
|
+
max_interactions=args.max_interactions,
|
|
176
|
+
max_queries=args.max_queries,
|
|
177
|
+
only_answerable=not args.all_queries,
|
|
178
|
+
)
|
|
179
|
+
payload = result.to_json(
|
|
180
|
+
include_cases=args.include_cases or args.failures_only,
|
|
181
|
+
failures_only=args.failures_only,
|
|
182
|
+
)
|
|
183
|
+
_write_json_output(payload, args.output)
|
|
184
|
+
return 0 if payload["passes_v1_thresholds"] else 2
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _cmd_spike_graphiti(args: argparse.Namespace) -> int:
|
|
188
|
+
config = _config_from_args(args)
|
|
189
|
+
checks = graphiti_spike_checks(config)
|
|
190
|
+
ok = all(check.ok for check in checks)
|
|
191
|
+
_print_json(
|
|
192
|
+
{
|
|
193
|
+
"ok": ok,
|
|
194
|
+
"gate": "graphiti_spike",
|
|
195
|
+
"checks": [check.to_json() for check in checks],
|
|
196
|
+
"next_step": (
|
|
197
|
+
"Run live Graphiti integration tests."
|
|
198
|
+
if ok
|
|
199
|
+
else "Resolve failed checks before committing to Graphiti as the substrate."
|
|
200
|
+
),
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
return 0 if ok else 2
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _cmd_check_embedding(args: argparse.Namespace) -> int:
|
|
207
|
+
config = _config_from_args(args)
|
|
208
|
+
result = check_embedding_provider(config, args.sample)
|
|
209
|
+
_print_json(result.to_json())
|
|
210
|
+
return 0 if result.ok else 2
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _cmd_smoke_graphiti(args: argparse.Namespace) -> int:
|
|
214
|
+
config = _config_from_args(args)
|
|
215
|
+
if config.backend != "graphiti":
|
|
216
|
+
config = replace(config, backend="graphiti")
|
|
217
|
+
if args.isolated:
|
|
218
|
+
temp_dir = tempfile.TemporaryDirectory()
|
|
219
|
+
config = replace(
|
|
220
|
+
config,
|
|
221
|
+
data_path=temp_dir.name,
|
|
222
|
+
graphiti_kuzu_path=str(Path(temp_dir.name) / "graphiti.kuzu"),
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
temp_dir = None
|
|
226
|
+
runtime = None
|
|
227
|
+
try:
|
|
228
|
+
runtime = build_runtime(config)
|
|
229
|
+
record_result = runtime.tools.record_interaction(
|
|
230
|
+
{
|
|
231
|
+
"source_text": (
|
|
232
|
+
"Met Alice Zhang at Blue Bottle Coffee. We discussed robotics hiring. "
|
|
233
|
+
"Alice mentioned Bob Li may be useful for founder intros."
|
|
234
|
+
),
|
|
235
|
+
"interaction_type": "coffee",
|
|
236
|
+
"place": "Blue Bottle Coffee",
|
|
237
|
+
"participants": [
|
|
238
|
+
{"person": {"label": "Alice Zhang", "person_id": "alice_zhang"}}
|
|
239
|
+
],
|
|
240
|
+
"mentioned_people": [
|
|
241
|
+
{
|
|
242
|
+
"person": {"label": "Bob Li", "person_id": "bob_li"},
|
|
243
|
+
"mentioned_by": {"label": "Alice Zhang", "person_id": "alice_zhang"},
|
|
244
|
+
"context": "founder intros",
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
"topics": ["robotics hiring", "founder intros"],
|
|
248
|
+
"attributed_claims": [
|
|
249
|
+
{
|
|
250
|
+
"speaker": {"label": "Alice Zhang", "person_id": "alice_zhang"},
|
|
251
|
+
"subject": {"label": "Bob Li", "person_id": "bob_li"},
|
|
252
|
+
"claim_text": "Alice Zhang said Bob Li may be useful for founder intros.",
|
|
253
|
+
}
|
|
254
|
+
],
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
search_result = runtime.tools.retrieve_network_context(
|
|
258
|
+
{"query": "robotics person from Blue Bottle", "limit": 5}
|
|
259
|
+
)
|
|
260
|
+
payload = {
|
|
261
|
+
"ok": bool(search_result.get("results")),
|
|
262
|
+
"record_interaction": record_result,
|
|
263
|
+
"search_result_count": len(search_result.get("results", [])),
|
|
264
|
+
"top_results": search_result.get("results", [])[:3],
|
|
265
|
+
}
|
|
266
|
+
_print_json(payload)
|
|
267
|
+
return 0 if payload["ok"] else 2
|
|
268
|
+
finally:
|
|
269
|
+
if runtime is not None:
|
|
270
|
+
runtime.close()
|
|
271
|
+
if temp_dir is not None:
|
|
272
|
+
temp_dir.cleanup()
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _cmd_eval_graphiti(args: argparse.Namespace) -> int:
|
|
276
|
+
config = _config_from_args(args)
|
|
277
|
+
if config.backend != "graphiti":
|
|
278
|
+
config = replace(config, backend="graphiti")
|
|
279
|
+
if args.isolated:
|
|
280
|
+
temp_dir = tempfile.TemporaryDirectory()
|
|
281
|
+
config = replace(
|
|
282
|
+
config,
|
|
283
|
+
data_path=temp_dir.name,
|
|
284
|
+
graphiti_kuzu_path=str(Path(temp_dir.name) / "graphiti.kuzu"),
|
|
285
|
+
)
|
|
286
|
+
else:
|
|
287
|
+
temp_dir = None
|
|
288
|
+
runtime = None
|
|
289
|
+
try:
|
|
290
|
+
dataset = generate_mock_dataset(seed=args.seed)
|
|
291
|
+
runtime = build_runtime(config)
|
|
292
|
+
result = evaluate_services(
|
|
293
|
+
dataset,
|
|
294
|
+
record_service=runtime.record_service,
|
|
295
|
+
retrieve_service=runtime.retrieve_service,
|
|
296
|
+
max_interactions=args.max_interactions,
|
|
297
|
+
max_queries=args.max_queries,
|
|
298
|
+
only_answerable=not args.all_queries,
|
|
299
|
+
)
|
|
300
|
+
payload = result.to_json(
|
|
301
|
+
include_cases=not args.no_cases or args.failures_only,
|
|
302
|
+
failures_only=args.failures_only,
|
|
303
|
+
)
|
|
304
|
+
payload.update(
|
|
305
|
+
{
|
|
306
|
+
"backend": "graphiti",
|
|
307
|
+
"isolated": args.isolated,
|
|
308
|
+
"fixture_summary": dataset.summary(),
|
|
309
|
+
"enforced_thresholds": args.enforce_thresholds,
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
_write_json_output(payload, args.output)
|
|
313
|
+
return 0 if (not args.enforce_thresholds or payload["passes_v1_thresholds"]) else 2
|
|
314
|
+
finally:
|
|
315
|
+
if runtime is not None:
|
|
316
|
+
runtime.close()
|
|
317
|
+
if temp_dir is not None:
|
|
318
|
+
temp_dir.cleanup()
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _cmd_eval_graphiti_search(args: argparse.Namespace) -> int:
|
|
322
|
+
from people_network_memory.application.services import RetrieveContextService
|
|
323
|
+
from people_network_memory.graphiti_adapter.graphiti_store import GraphitiGraphStore
|
|
324
|
+
|
|
325
|
+
config = _config_from_args(args)
|
|
326
|
+
if config.backend != "graphiti":
|
|
327
|
+
config = replace(config, backend="graphiti")
|
|
328
|
+
graph_store = GraphitiGraphStore.from_config(config)
|
|
329
|
+
try:
|
|
330
|
+
dataset = generate_mock_dataset(seed=args.seed)
|
|
331
|
+
retrieve = RetrieveContextService(
|
|
332
|
+
graph_search=graph_store,
|
|
333
|
+
review_queue=graph_store,
|
|
334
|
+
default_sensitivity_policy=config.sensitivity_policy,
|
|
335
|
+
retrieval_judge=_build_optional_retrieval_judge(config),
|
|
336
|
+
)
|
|
337
|
+
indexed_interactions = (
|
|
338
|
+
args.indexed_interactions
|
|
339
|
+
if args.indexed_interactions is not None
|
|
340
|
+
else len(dataset.interactions)
|
|
341
|
+
)
|
|
342
|
+
result = evaluate_retrieval_service(
|
|
343
|
+
dataset,
|
|
344
|
+
retrieve_service=retrieve,
|
|
345
|
+
indexed_interactions=indexed_interactions,
|
|
346
|
+
max_queries=args.max_queries,
|
|
347
|
+
only_answerable=not args.all_queries,
|
|
348
|
+
)
|
|
349
|
+
payload = result.to_json(
|
|
350
|
+
include_cases=not args.no_cases or args.failures_only,
|
|
351
|
+
failures_only=args.failures_only,
|
|
352
|
+
)
|
|
353
|
+
payload.update(
|
|
354
|
+
{
|
|
355
|
+
"backend": "graphiti",
|
|
356
|
+
"mode": "search_existing_graph",
|
|
357
|
+
"seed": args.seed,
|
|
358
|
+
"graphiti_kuzu_path": config.graphiti_kuzu_path,
|
|
359
|
+
"indexed_interactions": indexed_interactions,
|
|
360
|
+
"retrieval_judge": config.retrieval_judge,
|
|
361
|
+
"enforced_thresholds": args.enforce_thresholds,
|
|
362
|
+
}
|
|
363
|
+
)
|
|
364
|
+
_write_json_output(payload, args.output)
|
|
365
|
+
if args.report_output:
|
|
366
|
+
write_eval_markdown_report(
|
|
367
|
+
payload,
|
|
368
|
+
args.report_output,
|
|
369
|
+
title=(
|
|
370
|
+
"Graphiti Semantic Hybrid Retrieval Report"
|
|
371
|
+
if config.retrieval_judge == "off"
|
|
372
|
+
else "Graphiti Semantic Hybrid Retrieval Report With LLM Judge"
|
|
373
|
+
),
|
|
374
|
+
compare_payload=_read_json_file(args.compare_report_input)
|
|
375
|
+
if args.compare_report_input
|
|
376
|
+
else None,
|
|
377
|
+
)
|
|
378
|
+
return 0 if (not args.enforce_thresholds or payload["passes_v1_thresholds"]) else 2
|
|
379
|
+
finally:
|
|
380
|
+
graph_store.close()
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _cmd_report_eval(args: argparse.Namespace) -> int:
|
|
384
|
+
payload = _read_json_file(args.input)
|
|
385
|
+
compare_payload = _read_json_file(args.compare_input) if args.compare_input else None
|
|
386
|
+
write_eval_markdown_report(
|
|
387
|
+
payload,
|
|
388
|
+
args.output,
|
|
389
|
+
title=args.title,
|
|
390
|
+
compare_payload=compare_payload,
|
|
391
|
+
)
|
|
392
|
+
_print_json(
|
|
393
|
+
{
|
|
394
|
+
"ok": True,
|
|
395
|
+
"input": args.input,
|
|
396
|
+
"compare_input": args.compare_input,
|
|
397
|
+
"output": args.output,
|
|
398
|
+
"checked": payload.get("checked"),
|
|
399
|
+
}
|
|
400
|
+
)
|
|
401
|
+
return 0
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _cmd_graphiti_gate(args: argparse.Namespace) -> int:
|
|
405
|
+
config = _config_from_args(args)
|
|
406
|
+
payload = run_graphiti_promotion_gate(
|
|
407
|
+
config,
|
|
408
|
+
seed=args.seed,
|
|
409
|
+
max_interactions=args.max_interactions,
|
|
410
|
+
max_queries=args.max_queries,
|
|
411
|
+
isolated=args.isolated,
|
|
412
|
+
include_cases=args.include_cases,
|
|
413
|
+
failures_only=args.failures_only,
|
|
414
|
+
skip_live=args.skip_live,
|
|
415
|
+
skip_embedding_check=args.skip_embedding_check,
|
|
416
|
+
)
|
|
417
|
+
_write_json_output(payload, args.output)
|
|
418
|
+
return 0 if payload["ok"] else 2
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _cmd_index_graphiti(args: argparse.Namespace) -> int:
|
|
422
|
+
from people_network_memory.graphiti_adapter.indexer import index_local_json_to_graphiti
|
|
423
|
+
|
|
424
|
+
config = _config_from_args(args)
|
|
425
|
+
if config.backend != "graphiti":
|
|
426
|
+
config = replace(config, backend="graphiti")
|
|
427
|
+
payload = index_local_json_to_graphiti(
|
|
428
|
+
config,
|
|
429
|
+
source_data_path=args.source_data_path,
|
|
430
|
+
state_path=args.state_path,
|
|
431
|
+
limit=args.limit,
|
|
432
|
+
resume=not args.no_resume,
|
|
433
|
+
reset_state=args.reset_state,
|
|
434
|
+
continue_on_error=args.continue_on_error,
|
|
435
|
+
)
|
|
436
|
+
_write_json_output(payload, args.output)
|
|
437
|
+
return 0 if payload["ok"] else 2
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _cmd_hydrate_graphiti_cache(args: argparse.Namespace) -> int:
|
|
441
|
+
from people_network_memory.application.services import RecordInteractionService
|
|
442
|
+
from people_network_memory.infrastructure.id_generator import SequentialIdGenerator
|
|
443
|
+
|
|
444
|
+
config = _config_from_args(args)
|
|
445
|
+
cache_path = Path(config.data_path).expanduser() / "people-memory.graphiti-cache.json"
|
|
446
|
+
cache = JsonPeopleStore(cache_path)
|
|
447
|
+
if args.fixture_seed is not None:
|
|
448
|
+
interactions = generate_mock_dataset(seed=args.fixture_seed).interactions
|
|
449
|
+
source = f"fixture-seed-{args.fixture_seed}"
|
|
450
|
+
elif args.source_data_path:
|
|
451
|
+
source_path = Path(args.source_data_path).expanduser()
|
|
452
|
+
interactions = list(JsonPeopleStore(source_path).interactions.values())
|
|
453
|
+
source = str(source_path)
|
|
454
|
+
else:
|
|
455
|
+
_print_json(
|
|
456
|
+
{
|
|
457
|
+
"ok": False,
|
|
458
|
+
"error": "hydrate-graphiti-cache requires --fixture-seed or --source-data-path",
|
|
459
|
+
}
|
|
460
|
+
)
|
|
461
|
+
return 2
|
|
462
|
+
if args.limit is not None:
|
|
463
|
+
interactions = interactions[: args.limit]
|
|
464
|
+
record = RecordInteractionService(
|
|
465
|
+
memory_store=cache,
|
|
466
|
+
identity_index=cache,
|
|
467
|
+
review_queue=cache,
|
|
468
|
+
id_generator=SequentialIdGenerator(),
|
|
469
|
+
)
|
|
470
|
+
cached = 0
|
|
471
|
+
skipped_existing = 0
|
|
472
|
+
entries: list[dict[str, object]] = []
|
|
473
|
+
existing = {
|
|
474
|
+
(interaction.source_text, interaction.occurred_at)
|
|
475
|
+
for interaction in cache.interactions.values()
|
|
476
|
+
}
|
|
477
|
+
for index, interaction in enumerate(interactions):
|
|
478
|
+
key = (interaction.source_text, interaction.occurred_at)
|
|
479
|
+
if key in existing:
|
|
480
|
+
skipped_existing += 1
|
|
481
|
+
entries.append(
|
|
482
|
+
{
|
|
483
|
+
"interaction_index": index,
|
|
484
|
+
"ok": True,
|
|
485
|
+
"cached": False,
|
|
486
|
+
"reason": "already_cached",
|
|
487
|
+
"source_text": interaction.source_text,
|
|
488
|
+
}
|
|
489
|
+
)
|
|
490
|
+
continue
|
|
491
|
+
result = record.record(interaction)
|
|
492
|
+
existing.add(key)
|
|
493
|
+
cached += 1
|
|
494
|
+
entries.append(
|
|
495
|
+
{
|
|
496
|
+
"interaction_index": index,
|
|
497
|
+
"ok": True,
|
|
498
|
+
"cached": True,
|
|
499
|
+
"source_text": interaction.source_text,
|
|
500
|
+
"created_people": result.created_people,
|
|
501
|
+
"updated_people": result.updated_people,
|
|
502
|
+
}
|
|
503
|
+
)
|
|
504
|
+
payload = {
|
|
505
|
+
"ok": True,
|
|
506
|
+
"source": source,
|
|
507
|
+
"target_cache": str(cache_path),
|
|
508
|
+
"attempted": len(interactions),
|
|
509
|
+
"cached": cached,
|
|
510
|
+
"skipped_existing": skipped_existing,
|
|
511
|
+
"people": len(cache.people),
|
|
512
|
+
"interactions": len(cache.interactions),
|
|
513
|
+
"entries": entries,
|
|
514
|
+
}
|
|
515
|
+
_write_json_output(payload, args.output)
|
|
516
|
+
return 0
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _cmd_build_semantic_cache(args: argparse.Namespace) -> int:
|
|
520
|
+
from people_network_memory.graphiti_adapter.graphiti_store import semantic_index_path
|
|
521
|
+
from people_network_memory.infrastructure.embeddings import (
|
|
522
|
+
EmbeddingSettings,
|
|
523
|
+
OpenAICompatibleEmbeddingClient,
|
|
524
|
+
)
|
|
525
|
+
from people_network_memory.infrastructure.semantic_index import SemanticProjectionIndex
|
|
526
|
+
|
|
527
|
+
config = _config_from_args(args)
|
|
528
|
+
source_path = (
|
|
529
|
+
Path(args.source_data_path).expanduser()
|
|
530
|
+
if args.source_data_path
|
|
531
|
+
else Path(config.data_path).expanduser() / "people-memory.graphiti-cache.json"
|
|
532
|
+
)
|
|
533
|
+
if not source_path.exists():
|
|
534
|
+
_print_json({"ok": False, "error": f"projection cache does not exist: {source_path}"})
|
|
535
|
+
return 2
|
|
536
|
+
source_store = JsonPeopleStore(source_path)
|
|
537
|
+
interactions = list(source_store.interactions.values())
|
|
538
|
+
if args.limit is not None:
|
|
539
|
+
interactions = interactions[: args.limit]
|
|
540
|
+
settings = EmbeddingSettings.from_config(config)
|
|
541
|
+
client = OpenAICompatibleEmbeddingClient(settings)
|
|
542
|
+
index = SemanticProjectionIndex(
|
|
543
|
+
Path(args.index_path).expanduser() if args.index_path else semantic_index_path(config)
|
|
544
|
+
)
|
|
545
|
+
payload = index.build_from_interactions(
|
|
546
|
+
interactions,
|
|
547
|
+
embed_texts=client.embed,
|
|
548
|
+
batch_size=args.batch_size,
|
|
549
|
+
reset=args.reset,
|
|
550
|
+
)
|
|
551
|
+
payload.update(
|
|
552
|
+
{
|
|
553
|
+
"source_cache": str(source_path),
|
|
554
|
+
"provider": settings.provider,
|
|
555
|
+
"model": settings.model,
|
|
556
|
+
"dimension": settings.dimension,
|
|
557
|
+
}
|
|
558
|
+
)
|
|
559
|
+
_write_json_output(payload, args.output)
|
|
560
|
+
return 0
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def _cmd_graphiti_ladder(args: argparse.Namespace) -> int:
|
|
564
|
+
from people_network_memory.graphiti_adapter.indexer import index_interactions
|
|
565
|
+
from people_network_memory.graphiti_adapter.graphiti_store import GraphitiGraphStore
|
|
566
|
+
|
|
567
|
+
config = _config_from_args(args)
|
|
568
|
+
if config.backend != "graphiti":
|
|
569
|
+
config = replace(config, backend="graphiti")
|
|
570
|
+
dataset = generate_mock_dataset(seed=args.seed)
|
|
571
|
+
steps = [int(item) for item in args.steps.split(",") if item.strip()]
|
|
572
|
+
output_dir = Path(args.output_dir)
|
|
573
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
574
|
+
results: list[dict[str, object]] = []
|
|
575
|
+
cumulative_config: PeopleMemoryConfig | None = None
|
|
576
|
+
if args.cumulative:
|
|
577
|
+
if args.isolated:
|
|
578
|
+
cumulative_root = output_dir / "cumulative-store"
|
|
579
|
+
cumulative_config = replace(
|
|
580
|
+
config,
|
|
581
|
+
data_path=str(cumulative_root / "projection"),
|
|
582
|
+
graphiti_kuzu_path=str(cumulative_root / "graphiti.kuzu"),
|
|
583
|
+
)
|
|
584
|
+
else:
|
|
585
|
+
cumulative_config = config
|
|
586
|
+
for step in steps:
|
|
587
|
+
if args.cumulative:
|
|
588
|
+
run_config = cumulative_config or config
|
|
589
|
+
temp_dir = None
|
|
590
|
+
elif args.isolated:
|
|
591
|
+
temp_dir = tempfile.TemporaryDirectory()
|
|
592
|
+
run_config = replace(
|
|
593
|
+
config,
|
|
594
|
+
data_path=temp_dir.name,
|
|
595
|
+
graphiti_kuzu_path=str(Path(temp_dir.name) / "graphiti.kuzu"),
|
|
596
|
+
)
|
|
597
|
+
else:
|
|
598
|
+
temp_dir = None
|
|
599
|
+
run_config = config
|
|
600
|
+
graph_store = None
|
|
601
|
+
try:
|
|
602
|
+
graph_store = GraphitiGraphStore.from_config(run_config)
|
|
603
|
+
interactions = (
|
|
604
|
+
dataset.interactions[:step] if args.cumulative else dataset.interactions
|
|
605
|
+
)
|
|
606
|
+
step_payload = index_interactions(
|
|
607
|
+
interactions,
|
|
608
|
+
state_path=(
|
|
609
|
+
output_dir / "ladder-cumulative-state.json"
|
|
610
|
+
if args.cumulative
|
|
611
|
+
else output_dir / f"ladder-{step:03d}-state.json"
|
|
612
|
+
),
|
|
613
|
+
source_path=Path(f"fixture-seed-{args.seed}.json"),
|
|
614
|
+
graphiti_kuzu_path=Path(run_config.graphiti_kuzu_path).expanduser(),
|
|
615
|
+
limit=None if args.cumulative else step,
|
|
616
|
+
resume=args.cumulative,
|
|
617
|
+
reset_state=args.cumulative
|
|
618
|
+
and step == steps[0]
|
|
619
|
+
and not args.resume_cumulative,
|
|
620
|
+
continue_on_error=args.continue_on_error,
|
|
621
|
+
index_one=graph_store.index_interaction_episode,
|
|
622
|
+
cache_one=graph_store.cache_interaction_projection,
|
|
623
|
+
)
|
|
624
|
+
if args.cumulative:
|
|
625
|
+
step_payload["target_interactions"] = step
|
|
626
|
+
except Exception as exc: # pragma: no cover - live provider defensive path
|
|
627
|
+
step_payload = {
|
|
628
|
+
"ok": False,
|
|
629
|
+
"step": step,
|
|
630
|
+
"error": str(exc),
|
|
631
|
+
}
|
|
632
|
+
finally:
|
|
633
|
+
if graph_store is not None:
|
|
634
|
+
graph_store.close()
|
|
635
|
+
if temp_dir is not None:
|
|
636
|
+
temp_dir.cleanup()
|
|
637
|
+
step_payload["step"] = step
|
|
638
|
+
step_file = output_dir / f"ladder-{step:03d}.json"
|
|
639
|
+
step_file.write_text(
|
|
640
|
+
json.dumps(step_payload, ensure_ascii=False, indent=2, sort_keys=True),
|
|
641
|
+
encoding="utf-8",
|
|
642
|
+
)
|
|
643
|
+
results.append({**step_payload, "artifact": str(step_file)})
|
|
644
|
+
if not step_payload.get("ok") and not args.continue_ladder:
|
|
645
|
+
break
|
|
646
|
+
summary: dict[str, object] = {
|
|
647
|
+
"ok": all(bool(result.get("ok")) for result in results),
|
|
648
|
+
"gate": "graphiti_ingestion_ladder",
|
|
649
|
+
"seed": args.seed,
|
|
650
|
+
"steps_requested": steps,
|
|
651
|
+
"steps_completed": len(results),
|
|
652
|
+
"output_dir": str(output_dir),
|
|
653
|
+
"cumulative": args.cumulative,
|
|
654
|
+
"resume_cumulative": args.resume_cumulative,
|
|
655
|
+
"results": results,
|
|
656
|
+
}
|
|
657
|
+
summary_file = output_dir / "ladder-summary.json"
|
|
658
|
+
summary_file.write_text(
|
|
659
|
+
json.dumps(summary, ensure_ascii=False, indent=2, sort_keys=True),
|
|
660
|
+
encoding="utf-8",
|
|
661
|
+
)
|
|
662
|
+
_print_json(summary)
|
|
663
|
+
return 0 if summary["ok"] else 2
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def _cmd_export(args: argparse.Namespace) -> int:
|
|
667
|
+
config = _config_from_args(args, default_test_mode=True)
|
|
668
|
+
runtime = build_runtime(config)
|
|
669
|
+
payload = runtime.store.export_data()
|
|
670
|
+
if args.output:
|
|
671
|
+
Path(args.output).write_text(
|
|
672
|
+
json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8"
|
|
673
|
+
)
|
|
674
|
+
_print_json({"ok": True, "output": args.output})
|
|
675
|
+
return 0
|
|
676
|
+
_print_json(payload)
|
|
677
|
+
return 0
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def _cmd_list_reviews(args: argparse.Namespace) -> int:
|
|
681
|
+
config = _config_from_args(args)
|
|
682
|
+
runtime = build_runtime(config)
|
|
683
|
+
reviews = runtime.review_workflow.list_reviews(status=args.status)
|
|
684
|
+
_print_json(
|
|
685
|
+
{
|
|
686
|
+
"ok": True,
|
|
687
|
+
"count": len(reviews),
|
|
688
|
+
"reviews": [item.model_dump(mode="json") for item in reviews],
|
|
689
|
+
}
|
|
690
|
+
)
|
|
691
|
+
return 0
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def _cmd_resolve_review(args: argparse.Namespace) -> int:
|
|
695
|
+
config = _config_from_args(args)
|
|
696
|
+
runtime = build_runtime(config)
|
|
697
|
+
item = runtime.review_workflow.resolve_identity_review(
|
|
698
|
+
review_id=args.review_id,
|
|
699
|
+
source_person_id=args.source_person_id,
|
|
700
|
+
target_person_id=args.target_person_id,
|
|
701
|
+
note=args.note,
|
|
702
|
+
)
|
|
703
|
+
_print_json({"ok": True, "review": item.model_dump(mode="json")})
|
|
704
|
+
return 0
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def _cmd_dismiss_review(args: argparse.Namespace) -> int:
|
|
708
|
+
config = _config_from_args(args)
|
|
709
|
+
runtime = build_runtime(config)
|
|
710
|
+
item = runtime.review_workflow.dismiss_review(review_id=args.review_id, note=args.note)
|
|
711
|
+
_print_json({"ok": True, "review": item.model_dump(mode="json")})
|
|
712
|
+
return 0
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def _cmd_backup(args: argparse.Namespace) -> int:
|
|
716
|
+
if not args.output:
|
|
717
|
+
_print_json({"ok": False, "error": "backup requires --output"})
|
|
718
|
+
return 2
|
|
719
|
+
config = _config_from_args(args)
|
|
720
|
+
runtime = build_runtime(config)
|
|
721
|
+
payload = runtime.store.export_data()
|
|
722
|
+
output = Path(args.output)
|
|
723
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
724
|
+
output.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
725
|
+
_print_json({"ok": True, "output": str(output), "people": len(payload.get("people", []))})
|
|
726
|
+
return 0
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def _cmd_backup_archive(args: argparse.Namespace) -> int:
|
|
730
|
+
config = _config_from_args(args)
|
|
731
|
+
if config.backend == "inmemory":
|
|
732
|
+
config = replace(config, backend="local_json")
|
|
733
|
+
payload = create_archive_backup(config, args.output)
|
|
734
|
+
_print_json(payload)
|
|
735
|
+
return 0
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def _cmd_restore_archive(args: argparse.Namespace) -> int:
|
|
739
|
+
config = _config_from_args(args)
|
|
740
|
+
if config.backend == "inmemory":
|
|
741
|
+
config = replace(config, backend="local_json")
|
|
742
|
+
payload = restore_archive_backup(config, args.input, confirm=args.confirm)
|
|
743
|
+
_print_json(payload)
|
|
744
|
+
return 0
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def _cmd_restore(args: argparse.Namespace) -> int:
|
|
748
|
+
config = _config_from_args(args)
|
|
749
|
+
if config.backend != "local_json":
|
|
750
|
+
_print_json({"ok": False, "error": "restore currently supports backend=local_json only"})
|
|
751
|
+
return 2
|
|
752
|
+
source = Path(args.input)
|
|
753
|
+
if not source.exists():
|
|
754
|
+
_print_json({"ok": False, "error": f"restore input does not exist: {source}"})
|
|
755
|
+
return 2
|
|
756
|
+
payload = json.loads(source.read_text(encoding="utf-8"))
|
|
757
|
+
target = local_json_path(config)
|
|
758
|
+
JsonPeopleStore.restore_to_path(target, payload)
|
|
759
|
+
_print_json({"ok": True, "restored_to": str(target), "people": len(payload.get("people", []))})
|
|
760
|
+
return 0
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def _cmd_reset(args: argparse.Namespace) -> int:
|
|
764
|
+
if args.confirm != "DELETE":
|
|
765
|
+
_print_json(
|
|
766
|
+
{
|
|
767
|
+
"ok": False,
|
|
768
|
+
"error": "reset requires --confirm DELETE; no user data was deleted",
|
|
769
|
+
}
|
|
770
|
+
)
|
|
771
|
+
return 2
|
|
772
|
+
config = _config_from_args(args)
|
|
773
|
+
if config.backend == "local_json":
|
|
774
|
+
target = local_json_path(config)
|
|
775
|
+
if target.exists():
|
|
776
|
+
target.unlink()
|
|
777
|
+
detail = f"deleted {target}"
|
|
778
|
+
else:
|
|
779
|
+
detail = f"no local data file existed at {target}"
|
|
780
|
+
_print_json({"ok": True, "detail": detail})
|
|
781
|
+
return 0
|
|
782
|
+
_print_json({"ok": True, "detail": f"reset completed for backend={config.backend}"})
|
|
783
|
+
return 0
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def _cmd_redact(args: argparse.Namespace) -> int:
|
|
787
|
+
_print_json({"redacted": redact_sensitive_text(args.text)})
|
|
788
|
+
return 0
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def _cmd_install_openclaw(args: argparse.Namespace) -> int:
|
|
792
|
+
result = install_openclaw_adapter(
|
|
793
|
+
home=args.home,
|
|
794
|
+
command=args.command,
|
|
795
|
+
command_args=args.command_arg,
|
|
796
|
+
backend=args.backend,
|
|
797
|
+
ingestion_extractor=args.ingestion_extractor,
|
|
798
|
+
identity_advisor=args.identity_advisor,
|
|
799
|
+
managed_bootstrap=args.managed_bootstrap,
|
|
800
|
+
auto_update=args.auto_update,
|
|
801
|
+
dry_run=args.dry_run,
|
|
802
|
+
)
|
|
803
|
+
_print_json(result.to_json())
|
|
804
|
+
return 0
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def _cmd_smoke_openclaw(args: argparse.Namespace) -> int:
|
|
808
|
+
payload = run_openclaw_adapter_smoke(
|
|
809
|
+
home=args.home,
|
|
810
|
+
install=not args.no_install,
|
|
811
|
+
backend=args.backend,
|
|
812
|
+
)
|
|
813
|
+
_print_json(payload)
|
|
814
|
+
return 0 if payload["ok"] else 2
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
def _cmd_eval_harness_integration(args: argparse.Namespace) -> int:
|
|
818
|
+
payload = run_harness_memory_integration_eval()
|
|
819
|
+
_write_json_output(payload, args.output)
|
|
820
|
+
return 0 if payload["ok"] else 2
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def _cmd_eval_extractor(args: argparse.Namespace) -> int:
|
|
824
|
+
from people_network_memory.infrastructure.llm_extractor import (
|
|
825
|
+
OpenAICompatibleInteractionExtractor,
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
config = _config_from_args(args)
|
|
829
|
+
extractor = OpenAICompatibleInteractionExtractor.from_config(config)
|
|
830
|
+
payload = evaluate_extractor(extractor, max_cases=args.max_cases)
|
|
831
|
+
payload["min_pass_rate"] = args.min_pass_rate
|
|
832
|
+
payload["ok"] = bool(payload["pass_rate"] >= args.min_pass_rate)
|
|
833
|
+
_write_json_output(payload, args.output)
|
|
834
|
+
return 0 if payload["ok"] else 2
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
def _cmd_tool_schemas(args: argparse.Namespace) -> int:
|
|
838
|
+
_print_json(
|
|
839
|
+
{
|
|
840
|
+
"ok": True,
|
|
841
|
+
"contract_version": "v1",
|
|
842
|
+
"tools": public_tool_contracts(),
|
|
843
|
+
}
|
|
844
|
+
)
|
|
845
|
+
return 0
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
def _cmd_release_check(args: argparse.Namespace) -> int:
|
|
849
|
+
payload = run_local_release_check()
|
|
850
|
+
_write_json_output(payload, args.output)
|
|
851
|
+
return 0 if payload["ok"] else 2
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
855
|
+
parser = argparse.ArgumentParser(prog="people-memory")
|
|
856
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
857
|
+
|
|
858
|
+
start = sub.add_parser("start")
|
|
859
|
+
start.add_argument("--test-mode", action="store_true")
|
|
860
|
+
start.add_argument("--backend", choices=["inmemory", "local_json", "graphiti"])
|
|
861
|
+
start.add_argument("--data-path")
|
|
862
|
+
start.add_argument("--ingestion-extractor", choices=["off", "llm"])
|
|
863
|
+
start.add_argument("--identity-advisor", choices=["off", "llm"])
|
|
864
|
+
start.add_argument("--once", action="store_true")
|
|
865
|
+
start.set_defaults(func=_cmd_start)
|
|
866
|
+
|
|
867
|
+
doctor = sub.add_parser("doctor")
|
|
868
|
+
doctor.add_argument("--test-mode", action="store_true")
|
|
869
|
+
doctor.add_argument("--backend", choices=["inmemory", "local_json", "graphiti"])
|
|
870
|
+
doctor.add_argument("--data-path")
|
|
871
|
+
doctor.add_argument("--ingestion-extractor", choices=["off", "llm"])
|
|
872
|
+
doctor.add_argument("--identity-advisor", choices=["off", "llm"])
|
|
873
|
+
doctor.add_argument("--agent", choices=["openclaw"])
|
|
874
|
+
doctor.add_argument("--openclaw-home", default="~/.openclaw")
|
|
875
|
+
doctor.set_defaults(func=_cmd_doctor)
|
|
876
|
+
|
|
877
|
+
fixtures = sub.add_parser("load-fixtures")
|
|
878
|
+
fixtures.add_argument("--seed", type=int, default=42)
|
|
879
|
+
fixtures.add_argument("--summary", action="store_true")
|
|
880
|
+
fixtures.add_argument("--output")
|
|
881
|
+
fixtures.set_defaults(func=_cmd_load_fixtures)
|
|
882
|
+
|
|
883
|
+
eval_fixtures = sub.add_parser("eval-fixtures")
|
|
884
|
+
eval_fixtures.add_argument("--seed", type=int, default=42)
|
|
885
|
+
eval_fixtures.add_argument("--max-interactions", type=int)
|
|
886
|
+
eval_fixtures.add_argument("--max-queries", type=int)
|
|
887
|
+
eval_fixtures.add_argument("--include-cases", action="store_true")
|
|
888
|
+
eval_fixtures.add_argument("--failures-only", action="store_true")
|
|
889
|
+
eval_fixtures.add_argument(
|
|
890
|
+
"--all-queries",
|
|
891
|
+
action="store_true",
|
|
892
|
+
help="Evaluate selected queries even if max-interactions makes some targets unanswerable.",
|
|
893
|
+
)
|
|
894
|
+
eval_fixtures.add_argument("--output")
|
|
895
|
+
eval_fixtures.set_defaults(func=_cmd_eval_fixtures)
|
|
896
|
+
|
|
897
|
+
graphiti = sub.add_parser("spike-graphiti")
|
|
898
|
+
graphiti.add_argument("--test-mode", action="store_true")
|
|
899
|
+
graphiti.add_argument("--backend", choices=["inmemory", "local_json", "graphiti"])
|
|
900
|
+
graphiti.add_argument("--data-path")
|
|
901
|
+
graphiti.set_defaults(func=_cmd_spike_graphiti)
|
|
902
|
+
|
|
903
|
+
embedding = sub.add_parser("check-embedding")
|
|
904
|
+
embedding.add_argument("--test-mode", action="store_true")
|
|
905
|
+
embedding.add_argument("--backend", choices=["inmemory", "local_json", "graphiti"])
|
|
906
|
+
embedding.add_argument("--data-path")
|
|
907
|
+
embedding.add_argument("--sample", default="Alice likes robotics and coffee.")
|
|
908
|
+
embedding.set_defaults(func=_cmd_check_embedding)
|
|
909
|
+
|
|
910
|
+
smoke_graphiti = sub.add_parser("smoke-graphiti")
|
|
911
|
+
smoke_graphiti.add_argument("--backend", choices=["graphiti"], default="graphiti")
|
|
912
|
+
smoke_graphiti.add_argument("--data-path")
|
|
913
|
+
smoke_graphiti.add_argument("--isolated", action="store_true")
|
|
914
|
+
smoke_graphiti.set_defaults(func=_cmd_smoke_graphiti)
|
|
915
|
+
|
|
916
|
+
eval_graphiti = sub.add_parser("eval-graphiti")
|
|
917
|
+
eval_graphiti.add_argument("--backend", choices=["graphiti"], default="graphiti")
|
|
918
|
+
eval_graphiti.add_argument("--data-path")
|
|
919
|
+
eval_graphiti.add_argument("--isolated", action="store_true")
|
|
920
|
+
eval_graphiti.add_argument("--seed", type=int, default=42)
|
|
921
|
+
eval_graphiti.add_argument("--max-interactions", type=int)
|
|
922
|
+
eval_graphiti.add_argument("--max-queries", type=int)
|
|
923
|
+
eval_graphiti.add_argument("--no-cases", action="store_true")
|
|
924
|
+
eval_graphiti.add_argument("--failures-only", action="store_true")
|
|
925
|
+
eval_graphiti.add_argument(
|
|
926
|
+
"--all-queries",
|
|
927
|
+
action="store_true",
|
|
928
|
+
help="Evaluate selected queries even if max-interactions makes some targets unanswerable.",
|
|
929
|
+
)
|
|
930
|
+
eval_graphiti.add_argument("--output")
|
|
931
|
+
eval_graphiti.add_argument("--enforce-thresholds", action="store_true")
|
|
932
|
+
eval_graphiti.set_defaults(func=_cmd_eval_graphiti)
|
|
933
|
+
|
|
934
|
+
eval_graphiti_search = sub.add_parser("eval-graphiti-search")
|
|
935
|
+
eval_graphiti_search.add_argument("--backend", choices=["graphiti"], default="graphiti")
|
|
936
|
+
eval_graphiti_search.add_argument("--data-path")
|
|
937
|
+
eval_graphiti_search.add_argument("--graphiti-kuzu-path")
|
|
938
|
+
eval_graphiti_search.add_argument("--retrieval-judge", choices=["off", "llm"])
|
|
939
|
+
eval_graphiti_search.add_argument("--seed", type=int, default=42)
|
|
940
|
+
eval_graphiti_search.add_argument("--indexed-interactions", type=int)
|
|
941
|
+
eval_graphiti_search.add_argument("--max-queries", type=int)
|
|
942
|
+
eval_graphiti_search.add_argument("--no-cases", action="store_true")
|
|
943
|
+
eval_graphiti_search.add_argument("--failures-only", action="store_true")
|
|
944
|
+
eval_graphiti_search.add_argument(
|
|
945
|
+
"--all-queries",
|
|
946
|
+
action="store_true",
|
|
947
|
+
help="Evaluate selected queries even if indexed-interactions makes some targets unanswerable.",
|
|
948
|
+
)
|
|
949
|
+
eval_graphiti_search.add_argument("--output")
|
|
950
|
+
eval_graphiti_search.add_argument("--report-output")
|
|
951
|
+
eval_graphiti_search.add_argument(
|
|
952
|
+
"--compare-report-input",
|
|
953
|
+
help="Optional baseline eval JSON to compare in the generated Markdown report.",
|
|
954
|
+
)
|
|
955
|
+
eval_graphiti_search.add_argument("--enforce-thresholds", action="store_true")
|
|
956
|
+
eval_graphiti_search.set_defaults(func=_cmd_eval_graphiti_search)
|
|
957
|
+
|
|
958
|
+
report_eval = sub.add_parser("report-eval")
|
|
959
|
+
report_eval.add_argument("--input", required=True)
|
|
960
|
+
report_eval.add_argument("--output", required=True)
|
|
961
|
+
report_eval.add_argument("--compare-input")
|
|
962
|
+
report_eval.add_argument(
|
|
963
|
+
"--title",
|
|
964
|
+
default="Personal Network Memory Retrieval Test Report",
|
|
965
|
+
)
|
|
966
|
+
report_eval.set_defaults(func=_cmd_report_eval)
|
|
967
|
+
|
|
968
|
+
graphiti_gate = sub.add_parser("graphiti-gate")
|
|
969
|
+
graphiti_gate.add_argument("--backend", choices=["graphiti"], default="graphiti")
|
|
970
|
+
graphiti_gate.add_argument("--data-path")
|
|
971
|
+
graphiti_gate.add_argument("--isolated", action="store_true", default=True)
|
|
972
|
+
graphiti_gate.add_argument(
|
|
973
|
+
"--no-isolated",
|
|
974
|
+
action="store_false",
|
|
975
|
+
dest="isolated",
|
|
976
|
+
help="Use the configured Graphiti/Kuzu paths instead of a temporary isolated database.",
|
|
977
|
+
)
|
|
978
|
+
graphiti_gate.add_argument("--seed", type=int, default=42)
|
|
979
|
+
graphiti_gate.add_argument("--max-interactions", type=int)
|
|
980
|
+
graphiti_gate.add_argument("--max-queries", type=int)
|
|
981
|
+
graphiti_gate.add_argument("--include-cases", action="store_true")
|
|
982
|
+
graphiti_gate.add_argument("--failures-only", action="store_true", default=True)
|
|
983
|
+
graphiti_gate.add_argument(
|
|
984
|
+
"--all-cases",
|
|
985
|
+
action="store_false",
|
|
986
|
+
dest="failures_only",
|
|
987
|
+
help="When cases are included, write every case instead of failures only.",
|
|
988
|
+
)
|
|
989
|
+
graphiti_gate.add_argument(
|
|
990
|
+
"--skip-live",
|
|
991
|
+
action="store_true",
|
|
992
|
+
help="Only render static gate structure; promotion will remain blocked.",
|
|
993
|
+
)
|
|
994
|
+
graphiti_gate.add_argument(
|
|
995
|
+
"--skip-embedding-check",
|
|
996
|
+
action="store_true",
|
|
997
|
+
help="Skip the live embedding ping; promotion will remain blocked.",
|
|
998
|
+
)
|
|
999
|
+
graphiti_gate.add_argument("--output")
|
|
1000
|
+
graphiti_gate.set_defaults(func=_cmd_graphiti_gate)
|
|
1001
|
+
|
|
1002
|
+
index_graphiti = sub.add_parser("index-graphiti")
|
|
1003
|
+
index_graphiti.add_argument("--backend", choices=["graphiti"], default="graphiti")
|
|
1004
|
+
index_graphiti.add_argument("--data-path")
|
|
1005
|
+
index_graphiti.add_argument("--source-data-path")
|
|
1006
|
+
index_graphiti.add_argument("--state-path")
|
|
1007
|
+
index_graphiti.add_argument("--limit", type=int)
|
|
1008
|
+
index_graphiti.add_argument("--output")
|
|
1009
|
+
index_graphiti.add_argument("--no-resume", action="store_true")
|
|
1010
|
+
index_graphiti.add_argument("--reset-state", action="store_true")
|
|
1011
|
+
index_graphiti.add_argument("--continue-on-error", action="store_true")
|
|
1012
|
+
index_graphiti.set_defaults(func=_cmd_index_graphiti)
|
|
1013
|
+
|
|
1014
|
+
hydrate_graphiti_cache = sub.add_parser("hydrate-graphiti-cache")
|
|
1015
|
+
hydrate_graphiti_cache.add_argument("--backend", choices=["graphiti"], default="graphiti")
|
|
1016
|
+
hydrate_graphiti_cache.add_argument("--data-path", required=True)
|
|
1017
|
+
hydrate_graphiti_cache.add_argument("--source-data-path")
|
|
1018
|
+
hydrate_graphiti_cache.add_argument("--fixture-seed", type=int)
|
|
1019
|
+
hydrate_graphiti_cache.add_argument("--limit", type=int)
|
|
1020
|
+
hydrate_graphiti_cache.add_argument("--output")
|
|
1021
|
+
hydrate_graphiti_cache.set_defaults(func=_cmd_hydrate_graphiti_cache)
|
|
1022
|
+
|
|
1023
|
+
build_semantic_cache = sub.add_parser("build-semantic-cache")
|
|
1024
|
+
build_semantic_cache.add_argument("--backend", choices=["graphiti"], default="graphiti")
|
|
1025
|
+
build_semantic_cache.add_argument("--data-path", required=True)
|
|
1026
|
+
build_semantic_cache.add_argument("--source-data-path")
|
|
1027
|
+
build_semantic_cache.add_argument("--index-path")
|
|
1028
|
+
build_semantic_cache.add_argument("--limit", type=int)
|
|
1029
|
+
build_semantic_cache.add_argument("--batch-size", type=int, default=16)
|
|
1030
|
+
build_semantic_cache.add_argument("--reset", action="store_true")
|
|
1031
|
+
build_semantic_cache.add_argument("--output")
|
|
1032
|
+
build_semantic_cache.set_defaults(func=_cmd_build_semantic_cache)
|
|
1033
|
+
|
|
1034
|
+
graphiti_ladder = sub.add_parser("graphiti-ladder")
|
|
1035
|
+
graphiti_ladder.add_argument("--backend", choices=["graphiti"], default="graphiti")
|
|
1036
|
+
graphiti_ladder.add_argument("--data-path")
|
|
1037
|
+
graphiti_ladder.add_argument("--seed", type=int, default=42)
|
|
1038
|
+
graphiti_ladder.add_argument("--steps", default="1,3,10,20,40,90")
|
|
1039
|
+
graphiti_ladder.add_argument(
|
|
1040
|
+
"--output-dir",
|
|
1041
|
+
default=".people-network-memory/test-artifacts/graphiti-ladder",
|
|
1042
|
+
)
|
|
1043
|
+
graphiti_ladder.add_argument("--isolated", action="store_true", default=True)
|
|
1044
|
+
graphiti_ladder.add_argument(
|
|
1045
|
+
"--no-isolated",
|
|
1046
|
+
action="store_false",
|
|
1047
|
+
dest="isolated",
|
|
1048
|
+
help="Reuse configured Graphiti/Kuzu paths instead of one isolated database per step.",
|
|
1049
|
+
)
|
|
1050
|
+
graphiti_ladder.add_argument("--continue-on-error", action="store_true")
|
|
1051
|
+
graphiti_ladder.add_argument("--continue-ladder", action="store_true")
|
|
1052
|
+
graphiti_ladder.add_argument(
|
|
1053
|
+
"--cumulative",
|
|
1054
|
+
action="store_true",
|
|
1055
|
+
help=(
|
|
1056
|
+
"Reuse one artifact-local Graphiti/Kuzu store and state file across steps, "
|
|
1057
|
+
"so larger rungs only index the additional fixture interactions."
|
|
1058
|
+
),
|
|
1059
|
+
)
|
|
1060
|
+
graphiti_ladder.add_argument(
|
|
1061
|
+
"--resume-cumulative",
|
|
1062
|
+
action="store_true",
|
|
1063
|
+
help="With --cumulative, preserve an existing ladder-cumulative-state.json.",
|
|
1064
|
+
)
|
|
1065
|
+
graphiti_ladder.set_defaults(func=_cmd_graphiti_ladder)
|
|
1066
|
+
|
|
1067
|
+
export = sub.add_parser("export")
|
|
1068
|
+
export.add_argument("--test-mode", action="store_true")
|
|
1069
|
+
export.add_argument("--backend", choices=["inmemory", "local_json", "graphiti"])
|
|
1070
|
+
export.add_argument("--data-path")
|
|
1071
|
+
export.add_argument("--output")
|
|
1072
|
+
export.set_defaults(func=_cmd_export)
|
|
1073
|
+
|
|
1074
|
+
list_reviews = sub.add_parser("list-reviews")
|
|
1075
|
+
list_reviews.add_argument("--test-mode", action="store_true")
|
|
1076
|
+
list_reviews.add_argument("--backend", choices=["inmemory", "local_json", "graphiti"])
|
|
1077
|
+
list_reviews.add_argument("--data-path")
|
|
1078
|
+
list_reviews.add_argument(
|
|
1079
|
+
"--status", choices=["open", "resolved", "dismissed", "all"], default="open"
|
|
1080
|
+
)
|
|
1081
|
+
list_reviews.set_defaults(func=_cmd_list_reviews)
|
|
1082
|
+
|
|
1083
|
+
resolve_review = sub.add_parser("resolve-review")
|
|
1084
|
+
resolve_review.add_argument("--test-mode", action="store_true")
|
|
1085
|
+
resolve_review.add_argument("--backend", choices=["local_json", "graphiti"])
|
|
1086
|
+
resolve_review.add_argument("--data-path")
|
|
1087
|
+
resolve_review.add_argument("--review-id", required=True)
|
|
1088
|
+
resolve_review.add_argument("--source-person-id", required=True)
|
|
1089
|
+
resolve_review.add_argument("--target-person-id", required=True)
|
|
1090
|
+
resolve_review.add_argument("--note")
|
|
1091
|
+
resolve_review.set_defaults(func=_cmd_resolve_review)
|
|
1092
|
+
|
|
1093
|
+
dismiss_review = sub.add_parser("dismiss-review")
|
|
1094
|
+
dismiss_review.add_argument("--test-mode", action="store_true")
|
|
1095
|
+
dismiss_review.add_argument("--backend", choices=["local_json", "graphiti"])
|
|
1096
|
+
dismiss_review.add_argument("--data-path")
|
|
1097
|
+
dismiss_review.add_argument("--review-id", required=True)
|
|
1098
|
+
dismiss_review.add_argument("--note")
|
|
1099
|
+
dismiss_review.set_defaults(func=_cmd_dismiss_review)
|
|
1100
|
+
|
|
1101
|
+
backup = sub.add_parser("backup")
|
|
1102
|
+
backup.add_argument("--backend", choices=["inmemory", "local_json", "graphiti"])
|
|
1103
|
+
backup.add_argument("--data-path")
|
|
1104
|
+
backup.add_argument("--output", required=True)
|
|
1105
|
+
backup.set_defaults(func=_cmd_backup)
|
|
1106
|
+
|
|
1107
|
+
backup_archive = sub.add_parser("backup-archive")
|
|
1108
|
+
backup_archive.add_argument("--test-mode", action="store_true")
|
|
1109
|
+
backup_archive.add_argument("--backend", choices=["local_json", "graphiti"], default="local_json")
|
|
1110
|
+
backup_archive.add_argument("--data-path")
|
|
1111
|
+
backup_archive.add_argument("--output", required=True)
|
|
1112
|
+
backup_archive.set_defaults(func=_cmd_backup_archive)
|
|
1113
|
+
|
|
1114
|
+
restore_archive = sub.add_parser("restore-archive")
|
|
1115
|
+
restore_archive.add_argument("--test-mode", action="store_true")
|
|
1116
|
+
restore_archive.add_argument("--backend", choices=["local_json", "graphiti"], default="local_json")
|
|
1117
|
+
restore_archive.add_argument("--data-path")
|
|
1118
|
+
restore_archive.add_argument("--input", required=True)
|
|
1119
|
+
restore_archive.add_argument("--confirm", default="")
|
|
1120
|
+
restore_archive.set_defaults(func=_cmd_restore_archive)
|
|
1121
|
+
|
|
1122
|
+
restore = sub.add_parser("restore")
|
|
1123
|
+
restore.add_argument("--backend", choices=["local_json"], default="local_json")
|
|
1124
|
+
restore.add_argument("--data-path")
|
|
1125
|
+
restore.add_argument("--input", required=True)
|
|
1126
|
+
restore.set_defaults(func=_cmd_restore)
|
|
1127
|
+
|
|
1128
|
+
reset = sub.add_parser("reset")
|
|
1129
|
+
reset.add_argument("--backend", choices=["inmemory", "local_json", "graphiti"])
|
|
1130
|
+
reset.add_argument("--data-path")
|
|
1131
|
+
reset.add_argument("--confirm", default="")
|
|
1132
|
+
reset.set_defaults(func=_cmd_reset)
|
|
1133
|
+
|
|
1134
|
+
redact = sub.add_parser("redact")
|
|
1135
|
+
redact.add_argument("text")
|
|
1136
|
+
redact.set_defaults(func=_cmd_redact)
|
|
1137
|
+
|
|
1138
|
+
install_openclaw = sub.add_parser("install-openclaw")
|
|
1139
|
+
install_openclaw.add_argument("--home", default="~/.openclaw")
|
|
1140
|
+
install_openclaw.add_argument(
|
|
1141
|
+
"--command",
|
|
1142
|
+
help=(
|
|
1143
|
+
"Command to run. Defaults to the active Python executable and "
|
|
1144
|
+
"`-m people_network_memory.cli start`."
|
|
1145
|
+
),
|
|
1146
|
+
)
|
|
1147
|
+
install_openclaw.add_argument(
|
|
1148
|
+
"--command-arg",
|
|
1149
|
+
action="append",
|
|
1150
|
+
help="Override MCP command args. Repeat for each arg.",
|
|
1151
|
+
)
|
|
1152
|
+
install_openclaw.add_argument(
|
|
1153
|
+
"--backend", choices=["local_json", "inmemory", "graphiti"], default="local_json"
|
|
1154
|
+
)
|
|
1155
|
+
install_openclaw.add_argument("--ingestion-extractor", choices=["off", "llm"], default="llm")
|
|
1156
|
+
install_openclaw.add_argument("--identity-advisor", choices=["off", "llm"], default="llm")
|
|
1157
|
+
install_openclaw.add_argument(
|
|
1158
|
+
"--managed-bootstrap",
|
|
1159
|
+
action="store_true",
|
|
1160
|
+
help=(
|
|
1161
|
+
"Install a self-healing MCP command that creates/updates the local "
|
|
1162
|
+
"venv before launching the server."
|
|
1163
|
+
),
|
|
1164
|
+
)
|
|
1165
|
+
install_openclaw.add_argument(
|
|
1166
|
+
"--auto-update",
|
|
1167
|
+
action="store_true",
|
|
1168
|
+
help="With --managed-bootstrap, run git pull --ff-only before dependency checks.",
|
|
1169
|
+
)
|
|
1170
|
+
install_openclaw.add_argument("--dry-run", action="store_true")
|
|
1171
|
+
install_openclaw.set_defaults(func=_cmd_install_openclaw)
|
|
1172
|
+
|
|
1173
|
+
smoke_openclaw = sub.add_parser("smoke-openclaw")
|
|
1174
|
+
smoke_openclaw.add_argument("--home")
|
|
1175
|
+
smoke_openclaw.add_argument(
|
|
1176
|
+
"--backend", choices=["local_json", "inmemory", "graphiti"], default="local_json"
|
|
1177
|
+
)
|
|
1178
|
+
smoke_openclaw.add_argument("--no-install", action="store_true")
|
|
1179
|
+
smoke_openclaw.set_defaults(func=_cmd_smoke_openclaw)
|
|
1180
|
+
|
|
1181
|
+
eval_harness = sub.add_parser("eval-harness-integration")
|
|
1182
|
+
eval_harness.add_argument("--output")
|
|
1183
|
+
eval_harness.set_defaults(func=_cmd_eval_harness_integration)
|
|
1184
|
+
|
|
1185
|
+
eval_extractor = sub.add_parser("eval-extractor")
|
|
1186
|
+
eval_extractor.add_argument("--ingestion-extractor", choices=["llm"], default="llm")
|
|
1187
|
+
eval_extractor.add_argument("--max-cases", type=int)
|
|
1188
|
+
eval_extractor.add_argument("--min-pass-rate", type=float, default=0.8)
|
|
1189
|
+
eval_extractor.add_argument("--output")
|
|
1190
|
+
eval_extractor.set_defaults(func=_cmd_eval_extractor)
|
|
1191
|
+
|
|
1192
|
+
release_check = sub.add_parser("release-check")
|
|
1193
|
+
release_check.add_argument("--output")
|
|
1194
|
+
release_check.set_defaults(func=_cmd_release_check)
|
|
1195
|
+
|
|
1196
|
+
tool_schemas = sub.add_parser("tool-schemas")
|
|
1197
|
+
tool_schemas.set_defaults(func=_cmd_tool_schemas)
|
|
1198
|
+
return parser
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
def main(argv: list[str] | None = None) -> int:
|
|
1202
|
+
parser = build_parser()
|
|
1203
|
+
args = parser.parse_args(argv)
|
|
1204
|
+
try:
|
|
1205
|
+
return int(args.func(args))
|
|
1206
|
+
except PeopleMemoryError as exc:
|
|
1207
|
+
print(str(exc), file=sys.stderr)
|
|
1208
|
+
return 2
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
if __name__ == "__main__":
|
|
1212
|
+
raise SystemExit(main())
|