superlocalmemory 4.0.9 → 4.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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +245 -0
- package/README.md +7 -7
- package/package.json +4 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +4 -4
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +2 -2
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +5 -5
- package/plugin/skills/slm-recall/SKILL.md +102 -15
- package/plugin/skills/slm-remember/SKILL.md +35 -3
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +29 -3
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +16 -8
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-governance/SKILL.md +1 -1
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-loop/SKILL.md +2 -2
- package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
- package/plugin-src/skills/slm-profile/SKILL.md +5 -5
- package/plugin-src/skills/slm-recall/SKILL.md +102 -15
- package/plugin-src/skills/slm-remember/SKILL.md +35 -3
- package/plugin-src/skills/slm-scope/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +29 -3
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +308 -20
- package/src/superlocalmemory/cli/daemon.py +30 -0
- package/src/superlocalmemory/cli/db_migrate.py +71 -1
- package/src/superlocalmemory/cli/gdpr_cmd.py +15 -2
- package/src/superlocalmemory/cli/main.py +26 -4
- package/src/superlocalmemory/code_graph/bridge/maintenance.py +8 -0
- package/src/superlocalmemory/code_graph/database.py +44 -0
- package/src/superlocalmemory/compliance/gdpr.py +449 -39
- package/src/superlocalmemory/core/admission.py +231 -11
- package/src/superlocalmemory/core/backend_orchestrator.py +190 -84
- package/src/superlocalmemory/core/config.py +90 -11
- package/src/superlocalmemory/core/consolidation_engine.py +34 -0
- package/src/superlocalmemory/core/engine.py +140 -11
- package/src/superlocalmemory/core/fact_consolidator.py +316 -125
- package/src/superlocalmemory/core/graph_analyzer.py +76 -112
- package/src/superlocalmemory/core/graph_metrics.py +597 -0
- package/src/superlocalmemory/core/graph_pruner.py +121 -0
- package/src/superlocalmemory/core/maintenance.py +44 -6
- package/src/superlocalmemory/core/maintenance_scheduler.py +205 -0
- package/src/superlocalmemory/core/memory_health.py +266 -0
- package/src/superlocalmemory/core/mode_capability.py +111 -0
- package/src/superlocalmemory/core/ollama_validator.py +315 -0
- package/src/superlocalmemory/core/operation_policy_registry.py +1 -1
- package/src/superlocalmemory/core/operation_request.py +1 -1
- package/src/superlocalmemory/core/ops_remediation.py +2 -2
- package/src/superlocalmemory/core/projection_drain.py +380 -0
- package/src/superlocalmemory/core/recall_pipeline.py +390 -3
- package/src/superlocalmemory/core/recall_worker.py +6 -3
- package/src/superlocalmemory/core/scale_autopromote.py +196 -0
- package/src/superlocalmemory/core/scale_engine.py +16 -2
- package/src/superlocalmemory/core/score_contract.py +21 -1
- package/src/superlocalmemory/core/session_identity.py +85 -0
- package/src/superlocalmemory/core/status_contract.py +108 -0
- package/src/superlocalmemory/core/store_pipeline.py +78 -3
- package/src/superlocalmemory/core/worker_pool.py +4 -4
- package/src/superlocalmemory/core/working_memory.py +288 -0
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +51 -7
- package/src/superlocalmemory/encoding/context_generator.py +1 -1
- package/src/superlocalmemory/encoding/entity_resolver.py +38 -0
- package/src/superlocalmemory/encoding/fact_extractor.py +18 -14
- package/src/superlocalmemory/encoding/prospective_markers.py +262 -0
- package/src/superlocalmemory/encoding/type_router.py +12 -12
- package/src/superlocalmemory/evolution/mutation_generator.py +30 -4
- package/src/superlocalmemory/graph/cozo_adjacency.py +122 -0
- package/src/superlocalmemory/graph/cozo_backend.py +103 -138
- package/src/superlocalmemory/hooks/portable_kit.py +10 -2
- package/src/superlocalmemory/learning/bandit.py +43 -0
- package/src/superlocalmemory/learning/consolidation_worker.py +54 -0
- package/src/superlocalmemory/learning/database.py +60 -3
- package/src/superlocalmemory/learning/entity_compiler.py +21 -58
- package/src/superlocalmemory/learning/feedback.py +3 -1
- package/src/superlocalmemory/learning/outcomes.py +47 -16
- package/src/superlocalmemory/learning/pattern_miner.py +28 -3
- package/src/superlocalmemory/learning/pattern_miner_constants.py +43 -0
- package/src/superlocalmemory/learning/pcos.py +291 -0
- package/src/superlocalmemory/learning/reward_from_outcomes.py +365 -0
- package/src/superlocalmemory/learning/reward_proxy.py +100 -10
- package/src/superlocalmemory/learning/signal_kinds.py +79 -0
- package/src/superlocalmemory/mcp/profiles.py +14 -2
- package/src/superlocalmemory/mcp/server.py +1 -1
- package/src/superlocalmemory/mcp/session_binding.py +92 -0
- package/src/superlocalmemory/mcp/tools_active.py +2 -1
- package/src/superlocalmemory/mcp/tools_core.py +71 -42
- package/src/superlocalmemory/mcp/tools_ops.py +2 -2
- package/src/superlocalmemory/mcp/tools_v28.py +20 -1
- package/src/superlocalmemory/parameterization/pattern_extractor.py +14 -1
- package/src/superlocalmemory/parameterization/soft_prompt_generator.py +98 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +68 -11
- package/src/superlocalmemory/retrieval/channel_status.py +117 -0
- package/src/superlocalmemory/retrieval/engine.py +106 -11
- package/src/superlocalmemory/retrieval/entity_channel.py +217 -257
- package/src/superlocalmemory/retrieval/graph_adjacency.py +219 -0
- package/src/superlocalmemory/retrieval/scope_policy.py +42 -1
- package/src/superlocalmemory/retrieval/semantic_channel.py +47 -5
- package/src/superlocalmemory/retrieval/spreading.py +288 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +13 -1
- package/src/superlocalmemory/retrieval/vector_store.py +63 -0
- package/src/superlocalmemory/server/api.py +26 -2
- package/src/superlocalmemory/server/asset_versions.py +171 -0
- package/src/superlocalmemory/server/bandit_loops.py +17 -1
- package/src/superlocalmemory/server/rbac_enforce.py +26 -6
- package/src/superlocalmemory/server/recall_serializer.py +9 -0
- package/src/superlocalmemory/server/routes/abstraction.py +201 -0
- package/src/superlocalmemory/server/routes/behavioral.py +75 -10
- package/src/superlocalmemory/server/routes/compliance.py +98 -18
- package/src/superlocalmemory/server/routes/config_api.py +186 -4
- package/src/superlocalmemory/server/routes/data_io.py +29 -1
- package/src/superlocalmemory/server/routes/entity.py +13 -1
- package/src/superlocalmemory/server/routes/evolution.py +178 -0
- package/src/superlocalmemory/server/routes/ingest.py +8 -0
- package/src/superlocalmemory/server/routes/learning_telemetry.py +2 -1
- package/src/superlocalmemory/server/routes/memories.py +49 -7
- package/src/superlocalmemory/server/routes/mesh.py +1 -1
- package/src/superlocalmemory/server/routes/timeline.py +4 -0
- package/src/superlocalmemory/server/routes/v3_api.py +193 -17
- package/src/superlocalmemory/server/ui.py +24 -1
- package/src/superlocalmemory/server/unified_daemon.py +292 -9
- package/src/superlocalmemory/storage/_migration_internals.py +35 -0
- package/src/superlocalmemory/storage/_schema_version.py +24 -3
- package/src/superlocalmemory/storage/database.py +598 -82
- package/src/superlocalmemory/storage/embedding_codec.py +71 -0
- package/src/superlocalmemory/storage/lineage_retention.py +236 -0
- package/src/superlocalmemory/storage/logical_edges.py +43 -2
- package/src/superlocalmemory/storage/migration_runner.py +130 -0
- package/src/superlocalmemory/storage/migrations/M043_quarantine_display_summaries.py +488 -0
- package/src/superlocalmemory/storage/migrations/M044_play_carries_its_own_evidence.py +127 -0
- package/src/superlocalmemory/storage/migrations/M045_fact_outcome_score.py +158 -0
- package/src/superlocalmemory/storage/migrations/M046_prospective_memory_has_its_own_name.py +620 -0
- package/src/superlocalmemory/storage/migrations/M047_fisher_vectors_are_stored_like_every_other_vector.py +306 -0
- package/src/superlocalmemory/storage/migrations/M048_upcoming_holds_only_what_is_upcoming.py +207 -0
- package/src/superlocalmemory/storage/migrations/M049_a_schema_version_marker_is_one_row.py +201 -0
- package/src/superlocalmemory/storage/migrations.py +18 -2
- package/src/superlocalmemory/storage/models.py +40 -1
- package/src/superlocalmemory/storage/projection_outbox.py +346 -0
- package/src/superlocalmemory/storage/retention_policy.py +860 -0
- package/src/superlocalmemory/storage/schema.py +110 -1
- package/src/superlocalmemory/storage/write_coordinator.py +19 -2
- package/src/superlocalmemory/summaries/base.py +1 -1
- package/src/superlocalmemory/summaries/non_answer.py +223 -0
- package/src/superlocalmemory/trust/scorer.py +43 -1
- package/src/superlocalmemory/ui/index.html +10 -19
- package/src/superlocalmemory/ui/js/event-delegation.js +12 -1
- package/src/superlocalmemory/ui/js/od-health.js +28 -6
- package/src/superlocalmemory/ui/js/od-memories.js +209 -1
- package/src/superlocalmemory/ui/js/od-ops-health.js +1 -1
- package/src/superlocalmemory/ui/js/od-settings.js +87 -1
- package/src/superlocalmemory/ui/js/recall-lab.js +78 -3
|
@@ -35,6 +35,59 @@ def _resolve_paths(args: Namespace) -> tuple[Path, Path]:
|
|
|
35
35
|
return Path(learning), Path(memory)
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
def _end_state_disagreements(learning_db, memory_db) -> dict[str, str]:
|
|
39
|
+
"""Migrations recorded complete whose own verification no longer passes.
|
|
40
|
+
|
|
41
|
+
``--status`` reads ``migration_log``, which records what was *done*. The
|
|
42
|
+
daemon re-runs each completed migration's ``verify()`` on every start, which
|
|
43
|
+
checks whether what was done still *holds*. When those two answers differ
|
|
44
|
+
the log says ``complete`` and the health endpoint says the migration failed,
|
|
45
|
+
and until now nothing on any surface showed the two were even asking
|
|
46
|
+
different questions -- so the only available reading was that one of them
|
|
47
|
+
was lying. Reported as #125.
|
|
48
|
+
|
|
49
|
+
Read-only and fail-quiet: this is a diagnostic printed beside a status line,
|
|
50
|
+
and it must never be the reason a status command exits non-zero.
|
|
51
|
+
"""
|
|
52
|
+
import sqlite3
|
|
53
|
+
|
|
54
|
+
from superlocalmemory.storage._migration_internals import _MODULES
|
|
55
|
+
from superlocalmemory.storage.migration_runner import MIGRATIONS
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
from superlocalmemory.storage.migration_runner import DEFERRED_MIGRATIONS
|
|
59
|
+
except ImportError: # pragma: no cover — older layouts
|
|
60
|
+
DEFERRED_MIGRATIONS = ()
|
|
61
|
+
|
|
62
|
+
out: dict[str, str] = {}
|
|
63
|
+
for migration in list(MIGRATIONS) + list(DEFERRED_MIGRATIONS):
|
|
64
|
+
verify_fn = getattr(_MODULES.get(migration.name), "verify", None)
|
|
65
|
+
if not callable(verify_fn):
|
|
66
|
+
continue
|
|
67
|
+
db_path = memory_db if migration.db_target != "learning" else learning_db
|
|
68
|
+
try:
|
|
69
|
+
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
|
70
|
+
except sqlite3.Error:
|
|
71
|
+
continue
|
|
72
|
+
try:
|
|
73
|
+
row = conn.execute(
|
|
74
|
+
"SELECT status FROM migration_log WHERE name = ? LIMIT 1",
|
|
75
|
+
(migration.name,),
|
|
76
|
+
).fetchone()
|
|
77
|
+
if not row or row[0] != "complete":
|
|
78
|
+
continue
|
|
79
|
+
if not bool(verify_fn(conn)):
|
|
80
|
+
out[migration.name] = " <- recorded complete, end-state no longer holds"
|
|
81
|
+
except Exception: # noqa: BLE001 — a diagnostic must not break status
|
|
82
|
+
pass
|
|
83
|
+
finally:
|
|
84
|
+
try:
|
|
85
|
+
conn.close()
|
|
86
|
+
except sqlite3.Error: # pragma: no cover
|
|
87
|
+
pass
|
|
88
|
+
return out
|
|
89
|
+
|
|
90
|
+
|
|
38
91
|
def cmd_db_migrate(args: Namespace) -> int:
|
|
39
92
|
"""Apply pending migrations or report status.
|
|
40
93
|
|
|
@@ -57,8 +110,25 @@ def cmd_db_migrate(args: Namespace) -> int:
|
|
|
57
110
|
if not report:
|
|
58
111
|
print("(no migrations registered)")
|
|
59
112
|
else:
|
|
113
|
+
disagreements = _end_state_disagreements(learning_db, memory_db)
|
|
60
114
|
for name, state in report.items():
|
|
61
|
-
|
|
115
|
+
note = disagreements.get(name, "")
|
|
116
|
+
print(f" {name}: {state}{note}")
|
|
117
|
+
if disagreements:
|
|
118
|
+
print()
|
|
119
|
+
print(
|
|
120
|
+
" A migration marked complete is re-checked on every start "
|
|
121
|
+
"by its own\n"
|
|
122
|
+
" verification. The ones flagged above are recorded as done "
|
|
123
|
+
"and their\n"
|
|
124
|
+
" end-state no longer holds, which is what the daemon "
|
|
125
|
+
"reports as a\n"
|
|
126
|
+
" migration failure while this log still reads complete. "
|
|
127
|
+
"Run `slm db\n"
|
|
128
|
+
" migrate` to let each one try to repair itself, and see "
|
|
129
|
+
"`migration_failure_reasons`\n"
|
|
130
|
+
" in `slm health --json` for what specifically did not hold."
|
|
131
|
+
)
|
|
62
132
|
return 0
|
|
63
133
|
|
|
64
134
|
dry_run = bool(getattr(args, "dry_run", False))
|
|
@@ -427,9 +427,14 @@ def _cmd_gdpr_erase(args: Namespace) -> None:
|
|
|
427
427
|
sys.exit(1)
|
|
428
428
|
|
|
429
429
|
complete = counts.get("erasure_complete", 0)
|
|
430
|
+
# Whether it can be SHOWN to have happened is a separate answer, and
|
|
431
|
+
# printing COMPLETE while the tamper-evident receipt failed to persist is
|
|
432
|
+
# how the command line came to disagree with the API about the same erasure.
|
|
433
|
+
provable = counts.get("erasure_provable", complete)
|
|
430
434
|
result_data = {
|
|
431
435
|
"profile": profile,
|
|
432
436
|
"erasure_complete": bool(complete),
|
|
437
|
+
"erasure_provable": bool(provable),
|
|
433
438
|
"counts": counts,
|
|
434
439
|
"note": (
|
|
435
440
|
"Erasure recorded in audit_chain.db. "
|
|
@@ -441,7 +446,15 @@ def _cmd_gdpr_erase(args: Namespace) -> None:
|
|
|
441
446
|
if use_json:
|
|
442
447
|
_print_json(_json_envelope("gdpr-erase", data=result_data))
|
|
443
448
|
else:
|
|
444
|
-
|
|
449
|
+
if not complete:
|
|
450
|
+
status_str = "INCOMPLETE (check counts)"
|
|
451
|
+
elif not provable:
|
|
452
|
+
status_str = (
|
|
453
|
+
"COMPLETE, BUT NOT PROVABLE — the data is gone and the "
|
|
454
|
+
"tamper-evident receipt did not persist"
|
|
455
|
+
)
|
|
456
|
+
else:
|
|
457
|
+
status_str = "COMPLETE"
|
|
445
458
|
print(f"Art.17 erasure for profile '{profile}': {status_str}")
|
|
446
459
|
for k, v in counts.items():
|
|
447
460
|
print(f" {k}: {v}")
|
|
@@ -449,7 +462,7 @@ def _cmd_gdpr_erase(args: Namespace) -> None:
|
|
|
449
462
|
print(" Backups/ contain outstanding obligation — see C1 gap.")
|
|
450
463
|
print(" Verify receipts: slm gdpr verify --profile", profile)
|
|
451
464
|
|
|
452
|
-
if not complete:
|
|
465
|
+
if not complete or not provable:
|
|
453
466
|
sys.exit(1)
|
|
454
467
|
|
|
455
468
|
|
|
@@ -24,7 +24,10 @@ _os.environ.setdefault('TORCH_DEVICE', 'cpu')
|
|
|
24
24
|
import argparse
|
|
25
25
|
import sys
|
|
26
26
|
|
|
27
|
-
from superlocalmemory.core.config import
|
|
27
|
+
from superlocalmemory.core.config import (
|
|
28
|
+
CANONICAL_LIST_LIMIT,
|
|
29
|
+
CANONICAL_RECALL_LIMIT,
|
|
30
|
+
)
|
|
28
31
|
|
|
29
32
|
_HELP_EPILOG = """\
|
|
30
33
|
operating modes:
|
|
@@ -344,6 +347,19 @@ def main() -> None:
|
|
|
344
347
|
db_scale_p.add_argument("--stage-id", help="Stage identifier required by verify/promote")
|
|
345
348
|
db_scale_p.add_argument("--backup-id", help="Backup identifier required by rollback")
|
|
346
349
|
|
|
350
|
+
db_regraph_p = db_sub.add_parser(
|
|
351
|
+
"regraph",
|
|
352
|
+
help="Re-derive the graph copy of your memories from the store",
|
|
353
|
+
)
|
|
354
|
+
db_regraph_p.add_argument(
|
|
355
|
+
"--check", action="store_true",
|
|
356
|
+
help="Report how far the graph copy has drifted; change nothing",
|
|
357
|
+
)
|
|
358
|
+
db_regraph_p.add_argument(
|
|
359
|
+
"--profile", default="",
|
|
360
|
+
help="Workspace to re-derive. Default: all of them",
|
|
361
|
+
)
|
|
362
|
+
|
|
347
363
|
db_reembed_p = db_sub.add_parser(
|
|
348
364
|
"reembed",
|
|
349
365
|
help="Backfill NULL embeddings (facts never embedded)",
|
|
@@ -503,7 +519,8 @@ def main() -> None:
|
|
|
503
519
|
|
|
504
520
|
list_p = sub.add_parser("list", help="List recent memories chronologically (shows IDs for delete/update)")
|
|
505
521
|
list_p.add_argument(
|
|
506
|
-
"--limit", "-n", type=int, default=
|
|
522
|
+
"--limit", "-n", type=int, default=CANONICAL_LIST_LIMIT,
|
|
523
|
+
help=f"Number of entries (default {CANONICAL_LIST_LIMIT})",
|
|
507
524
|
)
|
|
508
525
|
list_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
509
526
|
|
|
@@ -530,6 +547,11 @@ def main() -> None:
|
|
|
530
547
|
"--quick", action="store_true",
|
|
531
548
|
help="Run only the fast checks (deps + config); skip daemon/embedding probes",
|
|
532
549
|
)
|
|
550
|
+
doctor_p.add_argument(
|
|
551
|
+
"--deep", action="store_true",
|
|
552
|
+
help="Read every database page (PRAGMA integrity_check) instead of the "
|
|
553
|
+
"structural check; slow on a large store",
|
|
554
|
+
)
|
|
533
555
|
doctor_p.add_argument(
|
|
534
556
|
"--fix", action="store_true",
|
|
535
557
|
help="Auto-repair fixable components (re-download missing models, "
|
|
@@ -977,7 +999,7 @@ def main() -> None:
|
|
|
977
999
|
|
|
978
1000
|
register_summary_parser(sub)
|
|
979
1001
|
|
|
980
|
-
#
|
|
1002
|
+
# V4.0.6: GDPR subject-rights CLI (Art.15/17/20)
|
|
981
1003
|
gdpr_p = sub.add_parser(
|
|
982
1004
|
"gdpr",
|
|
983
1005
|
help="GDPR subject rights: status, export (Art.15/20), erase (Art.17), verify",
|
|
@@ -1051,7 +1073,7 @@ def main() -> None:
|
|
|
1051
1073
|
"--json", action="store_true", help="Output structured JSON",
|
|
1052
1074
|
)
|
|
1053
1075
|
|
|
1054
|
-
#
|
|
1076
|
+
# Operational recovery & admin remediation
|
|
1055
1077
|
ops_p = sub.add_parser(
|
|
1056
1078
|
"ops",
|
|
1057
1079
|
help="Operational recovery: list failures, resolve stuck ops, check status",
|
|
@@ -103,6 +103,14 @@ def _fact_rows(
|
|
|
103
103
|
# the pass linked happily in tests and would have linked NOTHING for any
|
|
104
104
|
# actual user — 3,608 of 3,608 facts filtered out on this machine.
|
|
105
105
|
sql += "AND COALESCE(archive_status, 'live') != 'archived' "
|
|
106
|
+
# Same reasoning for withheld rows: a model's non-answer has no code
|
|
107
|
+
# entities worth resolving, and a link from one would surface it in the
|
|
108
|
+
# bridge's enrichment text.
|
|
109
|
+
try:
|
|
110
|
+
if memory_db._has_quarantine_column():
|
|
111
|
+
sql += "AND COALESCE(quarantined, 0) = 0 "
|
|
112
|
+
except Exception: # pragma: no cover - helper absent on an unusual manager
|
|
113
|
+
pass
|
|
106
114
|
|
|
107
115
|
params: list[Any] = [profile_id]
|
|
108
116
|
if since:
|
|
@@ -37,6 +37,49 @@ _MAX_RETRIES = 5
|
|
|
37
37
|
_RETRY_BASE_SECONDS = 0.1
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
def _load_vector_extension(conn: sqlite3.Connection) -> bool:
|
|
41
|
+
"""Make vector search available on this connection. Returns whether it loaded.
|
|
42
|
+
|
|
43
|
+
Without this the vector table cannot be created, so every semantic lookup
|
|
44
|
+
over the code graph returned nothing — not an error, just an empty result,
|
|
45
|
+
with one line in the maintenance log saying embeddings were disabled. The
|
|
46
|
+
extension was installed and working the whole time; no connection ever asked
|
|
47
|
+
for it.
|
|
48
|
+
|
|
49
|
+
Loaded per connection because that is the only scope SQLite offers: an
|
|
50
|
+
extension is bound to a connection, not to a database file, and this class
|
|
51
|
+
opens a fresh connection per statement.
|
|
52
|
+
|
|
53
|
+
The import stays inside the function. At module level it would make the
|
|
54
|
+
whole code graph unimportable wherever the wheel is unavailable, which is a
|
|
55
|
+
platform detail and not a reason to lose the rest of the feature.
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
import sqlite_vec
|
|
59
|
+
except Exception as exc: # ImportError, or a broken native build
|
|
60
|
+
logger.debug("vector search unavailable: %s", exc)
|
|
61
|
+
return False
|
|
62
|
+
try:
|
|
63
|
+
conn.enable_load_extension(True)
|
|
64
|
+
except AttributeError as exc:
|
|
65
|
+
# Python built against a SQLite without extension support.
|
|
66
|
+
logger.debug("this SQLite cannot load extensions: %s", exc)
|
|
67
|
+
return False
|
|
68
|
+
try:
|
|
69
|
+
sqlite_vec.load(conn)
|
|
70
|
+
return True
|
|
71
|
+
except Exception as exc:
|
|
72
|
+
logger.debug("vector extension failed to load: %s", exc)
|
|
73
|
+
return False
|
|
74
|
+
finally:
|
|
75
|
+
# Closed again either way. Leaving extension loading enabled on a
|
|
76
|
+
# connection widens what any later SQL on it is allowed to do.
|
|
77
|
+
try:
|
|
78
|
+
conn.enable_load_extension(False)
|
|
79
|
+
except Exception: # pragma: no cover — defensive
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
|
|
40
83
|
class CodeGraphDatabase:
|
|
41
84
|
"""SQLite database manager for code_graph.db.
|
|
42
85
|
|
|
@@ -70,6 +113,7 @@ class CodeGraphDatabase:
|
|
|
70
113
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
71
114
|
conn.execute("PRAGMA foreign_keys=ON")
|
|
72
115
|
conn.execute("PRAGMA synchronous=NORMAL")
|
|
116
|
+
_load_vector_extension(conn)
|
|
73
117
|
return conn
|
|
74
118
|
|
|
75
119
|
def execute(
|