ltcai 10.3.0 → 10.4.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 +37 -36
- package/docs/CHANGELOG.md +77 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/MYPY_BACKLOG.md +59 -92
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +1 -1
- package/docs/SURFACE_PARITY.md +33 -6
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/kg-schema.md +1 -1
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/context.py +23 -3
- package/lattice_brain/graph/_kg_common.py +164 -9
- package/lattice_brain/graph/_kg_contract.py +223 -0
- package/lattice_brain/graph/_kg_fsutil.py +1 -1
- package/lattice_brain/graph/curator.py +4 -4
- package/lattice_brain/graph/discovery.py +12 -1
- package/lattice_brain/graph/discovery_index.py +12 -1
- package/lattice_brain/graph/documents.py +17 -2
- package/lattice_brain/graph/ingest.py +12 -1
- package/lattice_brain/graph/proactive.py +9 -5
- package/lattice_brain/graph/projection.py +12 -1
- package/lattice_brain/graph/provenance.py +12 -1
- package/lattice_brain/graph/retrieval.py +12 -1
- package/lattice_brain/graph/retrieval_docgen.py +12 -1
- package/lattice_brain/graph/retrieval_reads.py +12 -1
- package/lattice_brain/graph/retrieval_vector.py +12 -1
- package/lattice_brain/graph/schema.py +6 -2
- package/lattice_brain/graph/write_master.py +12 -1
- package/lattice_brain/ingestion.py +1 -1
- package/lattice_brain/quality.py +5 -5
- package/lattice_brain/runtime/contracts.py +2 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/lattice_brain/workflow.py +10 -7
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +2 -2
- package/latticeai/api/chat.py +19 -15
- package/latticeai/api/chat_agent_http.py +19 -7
- package/latticeai/api/chat_helpers.py +9 -8
- package/latticeai/api/chat_stream.py +2 -2
- package/latticeai/api/computer_use.py +2 -2
- package/latticeai/api/hooks.py +4 -2
- package/latticeai/api/local_files.py +8 -4
- package/latticeai/api/mcp.py +2 -2
- package/latticeai/api/models.py +5 -5
- package/latticeai/api/permissions.py +2 -2
- package/latticeai/api/search.py +5 -3
- package/latticeai/api/security_dashboard.py +3 -5
- package/latticeai/api/setup.py +3 -3
- package/latticeai/api/static_routes.py +7 -2
- package/latticeai/api/tools.py +9 -4
- package/latticeai/api/workspace.py +25 -22
- package/latticeai/app_factory.py +132 -1322
- package/latticeai/cli/entrypoint.py +1 -1
- package/latticeai/core/agent.py +28 -14
- package/latticeai/core/audit.py +5 -3
- package/latticeai/core/embedding_providers.py +19 -6
- package/latticeai/core/legacy_compatibility.py +1 -1
- package/latticeai/core/marketplace.py +2 -2
- package/latticeai/core/mcp_catalog.py +3 -1
- package/latticeai/core/model_compat.py +1 -1
- package/latticeai/core/plugins.py +1 -1
- package/latticeai/core/project_sessions.py +1 -1
- package/latticeai/core/run_explain.py +6 -4
- package/latticeai/core/workspace_os.py +0 -4
- package/latticeai/core/workspace_os_constants.py +1 -1
- package/latticeai/core/workspace_os_state.py +1 -1
- package/latticeai/core/workspace_snapshots.py +3 -3
- package/latticeai/integrations/telegram_bot.py +52 -3
- package/latticeai/models/router.py +61 -34
- package/latticeai/runtime/access_runtime.py +3 -1
- package/latticeai/runtime/build_phases.py +1417 -0
- package/latticeai/runtime/review_wiring.py +6 -1
- package/latticeai/runtime/runtime_context.py +276 -0
- package/latticeai/runtime/sso_config_runtime.py +1 -1
- package/latticeai/runtime/stages.py +8 -2
- package/latticeai/services/app_context.py +23 -0
- package/latticeai/services/architecture_readiness.py +1 -1
- package/latticeai/services/brain_intelligence.py +4 -2
- package/latticeai/services/cloud_streaming.py +5 -3
- package/latticeai/services/command_center.py +1 -1
- package/latticeai/services/evidence_actions.py +2 -1
- package/latticeai/services/hybrid_chat.py +1 -1
- package/latticeai/services/local_knowledge.py +25 -9
- package/latticeai/services/memory_service.py +8 -4
- package/latticeai/services/model_catalog.py +12 -12
- package/latticeai/services/model_engines.py +15 -17
- package/latticeai/services/model_loading.py +1 -1
- package/latticeai/services/model_runtime.py +31 -33
- package/latticeai/services/multimodal_streaming.py +10 -4
- package/latticeai/services/openai_compatible_adapter.py +4 -3
- package/latticeai/services/product_readiness.py +1 -1
- package/latticeai/services/run_executor.py +12 -3
- package/latticeai/services/search_service.py +1 -1
- package/latticeai/services/triggers.py +1 -1
- package/latticeai/setup/auto_setup.py +1 -1
- package/latticeai/setup/wizard.py +12 -3
- package/latticeai/tools/computer.py +3 -1
- package/latticeai/tools/documents.py +2 -2
- package/package.json +1 -1
- package/scripts/check_current_release_docs.mjs +1 -1
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +36 -36
- package/static/app/assets/{Act-DrKIGZeJ.js → Act-BK__Y5VE.js} +1 -1
- package/static/app/assets/{AdminConsole-CEV1VefA.js → AdminConsole-ZrTyN8_F.js} +1 -1
- package/static/app/assets/{Brain-BZSXnwWU.js → Brain-CYHDXpPB.js} +1 -1
- package/static/app/assets/{BrainHome-CEBd86k6.js → BrainHome-bbJulb5g.js} +1 -1
- package/static/app/assets/{BrainSignals-O4s6R7av.js → BrainSignals-CesSPtUP.js} +1 -1
- package/static/app/assets/{Capture-AGbOxsg-.js → Capture-p5upXmz2.js} +1 -1
- package/static/app/assets/{CommandPalette-CeqBjCn2.js → CommandPalette-0zkyC0RY.js} +1 -1
- package/static/app/assets/{Library-CXnbNiQW.js → Library-Dt-vABI2.js} +1 -1
- package/static/app/assets/{LivingBrain-D9Iei2ZJ.js → LivingBrain-Bz-h6LBH.js} +1 -1
- package/static/app/assets/{ProductFlow-JlXiDfMM.js → ProductFlow-DAZ7DJCE.js} +1 -1
- package/static/app/assets/{ReviewCard-QQly2bTV.js → ReviewCard-Bv0WGbY6.js} +1 -1
- package/static/app/assets/{System-Bp9ZY4ER.js → System-BhrJQSD_.js} +1 -1
- package/static/app/assets/{activity-CxRPOQ2F.js → activity-AolpZJCH.js} +1 -1
- package/static/app/assets/{bot-1bcsWB2s.js → bot-CsVsT2nd.js} +1 -1
- package/static/app/assets/{brain-CxaeKCRM.js → brain-vuppco5y.js} +1 -1
- package/static/app/assets/{button-ZYFqGan7.js → button-1BP1PJZ3.js} +1 -1
- package/static/app/assets/{circle-pause-B5NOIqvk.js → circle-pause-5paeCOtq.js} +1 -1
- package/static/app/assets/{circle-play-BiJVFXf7.js → circle-play-By8mFYRV.js} +1 -1
- package/static/app/assets/{cpu-Vvq0OKTr.js → cpu-DvUQxdKe.js} +1 -1
- package/static/app/assets/{download-SEcXzkx1.js → download-CFqYG90a.js} +1 -1
- package/static/app/assets/{folder-open-Dw1ff_Qa.js → folder-open-CVSnp-Dp.js} +1 -1
- package/static/app/assets/{hard-drive-B00fR7_r.js → hard-drive-Ckr4Vd6-.js} +1 -1
- package/static/app/assets/{index-Dm9VXl1o.js → index--0zKF5Ow.js} +3 -3
- package/static/app/assets/{input-DtdWYQEa.js → input-D-JKquXU.js} +1 -1
- package/static/app/assets/{network-CpjBu0kI.js → network-DB88FZ2T.js} +1 -1
- package/static/app/assets/{primitives-CwsudBwc.js → primitives-D8U9OaIo.js} +1 -1
- package/static/app/assets/search-B8jsVTw_.js +1 -0
- package/static/app/assets/{shield-alert-CpjBc-gQ.js → shield-alert-DnsvO79P.js} +1 -1
- package/static/app/assets/{textarea-DUPyjHQs.js → textarea-4RchwbTa.js} +1 -1
- package/static/app/assets/{useFocusTrap-BWcMtCWH.js → useFocusTrap-CFJjy7cS.js} +1 -1
- package/static/app/assets/{useQuery-Dg5fPqTS.js → useQuery-3Reu-A4a.js} +1 -1
- package/static/app/assets/{users-BgcdTQGM.js → users-BTxYksxa.js} +1 -1
- package/static/app/assets/{utils-a3YXFtEO.js → utils-D02Vf-fw.js} +1 -1
- package/static/app/assets/{workspace-BMUEiXUm.js → workspace-CSJNgX-C.js} +1 -1
- package/static/app/index.html +3 -3
- package/static/sw.js +1 -1
- package/static/app/assets/search-CwEJWBEA.js +0 -1
|
@@ -29,9 +29,9 @@ from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple
|
|
|
29
29
|
try:
|
|
30
30
|
from .schema import EdgeType, KGStoreV2, NodeType, _exec_script
|
|
31
31
|
except Exception: # pragma: no cover - v2 schema is optional at import time
|
|
32
|
-
KGStoreV2 = None # type: ignore[assignment]
|
|
33
|
-
NodeType = None # type: ignore[assignment]
|
|
34
|
-
EdgeType = None # type: ignore[assignment]
|
|
32
|
+
KGStoreV2 = None # type: ignore[assignment,misc]
|
|
33
|
+
NodeType = None # type: ignore[assignment,misc]
|
|
34
|
+
EdgeType = None # type: ignore[assignment,misc]
|
|
35
35
|
_exec_script = None # type: ignore[assignment]
|
|
36
36
|
|
|
37
37
|
from ..embeddings import LocalEmbeddingModel
|
|
@@ -69,9 +69,33 @@ from ._kg_constants import ( # noqa: E402
|
|
|
69
69
|
WINDOWS_EXCLUDED_NAMES,
|
|
70
70
|
)
|
|
71
71
|
|
|
72
|
-
# Pure fs/path/hash/classification helpers → ._kg_fsutil
|
|
73
|
-
#
|
|
74
|
-
|
|
72
|
+
# Pure fs/path/hash/classification helpers → ._kg_fsutil, re-exported so the
|
|
73
|
+
# static __all__ below forwards them to the graph mixins. Listed explicitly
|
|
74
|
+
# rather than star-imported: a star import here made every name in this
|
|
75
|
+
# module unverifiable to both ruff and mypy.
|
|
76
|
+
from ._kg_fsutil import ( # noqa: E402,F401
|
|
77
|
+
_current_os_type,
|
|
78
|
+
_drive_id_for_path,
|
|
79
|
+
_excluded_directory_reason,
|
|
80
|
+
_file_category,
|
|
81
|
+
_is_hidden_path,
|
|
82
|
+
_is_relative_to,
|
|
83
|
+
_node_type_for_category,
|
|
84
|
+
_now,
|
|
85
|
+
_parse_iso,
|
|
86
|
+
_parser_type_for_category,
|
|
87
|
+
_path_fingerprint,
|
|
88
|
+
_path_parts_lower,
|
|
89
|
+
_recency_score,
|
|
90
|
+
_root_warning,
|
|
91
|
+
_safe_iso_from_stat_mtime,
|
|
92
|
+
_sample_file,
|
|
93
|
+
_sensitive_file_reason,
|
|
94
|
+
_sha256_bytes,
|
|
95
|
+
_sha256_text,
|
|
96
|
+
_size_limit_for_category,
|
|
97
|
+
_slug,
|
|
98
|
+
)
|
|
75
99
|
|
|
76
100
|
|
|
77
101
|
def _clean_text(text: str) -> str:
|
|
@@ -613,9 +637,11 @@ def _llm_extract_concepts(text: str, limit: int = 12) -> Optional[List[str]]:
|
|
|
613
637
|
return None
|
|
614
638
|
|
|
615
639
|
|
|
640
|
+
# Triples carry a numeric ``weight``/``confidence`` alongside string fields,
|
|
641
|
+
# so the value type is Any rather than str.
|
|
616
642
|
def _llm_extract_triples(
|
|
617
643
|
text: str, concepts: List[str], limit: int = 20
|
|
618
|
-
) -> Optional[List[Dict[str,
|
|
644
|
+
) -> Optional[List[Dict[str, Any]]]:
|
|
619
645
|
router = get_llm_router()
|
|
620
646
|
if not ENABLE_LLM_EXTRACTION or not router:
|
|
621
647
|
return None
|
|
@@ -647,7 +673,7 @@ def _llm_extract_triples(
|
|
|
647
673
|
raw = re.sub(r"\s*```$", "", raw)
|
|
648
674
|
parsed = json.loads(raw)
|
|
649
675
|
if isinstance(parsed, list):
|
|
650
|
-
triples = []
|
|
676
|
+
triples: List[Dict[str, Any]] = []
|
|
651
677
|
for item in parsed[:limit]:
|
|
652
678
|
if isinstance(item, dict) and "subject" in item and "object" in item:
|
|
653
679
|
relation = str(item.get("relation", "관련됨"))
|
|
@@ -1173,4 +1199,133 @@ def _topic_candidates(text: str, limit: int = 8) -> List[str]:
|
|
|
1173
1199
|
return list(seen.values())[:limit]
|
|
1174
1200
|
|
|
1175
1201
|
|
|
1176
|
-
|
|
1202
|
+
# Static export list. This used to be `[name for name in globals() if not
|
|
1203
|
+
# name.startswith("__")]`, which is invisible to a type checker: every
|
|
1204
|
+
# `from ._kg_common import *` consumer then had *no* resolvable names, and
|
|
1205
|
+
# mypy reported ~750 spurious `name-defined` errors across the graph
|
|
1206
|
+
# package. `tests/unit/test_kg_common_exports.py` asserts this list still
|
|
1207
|
+
# equals what the computed expression would produce, so it cannot drift.
|
|
1208
|
+
__all__ = [
|
|
1209
|
+
"Any",
|
|
1210
|
+
"COMMON_EXCLUDED_DIRS",
|
|
1211
|
+
"COMMON_EXCLUDED_FILE_NAMES",
|
|
1212
|
+
"COMMON_EXCLUDED_FILE_SUFFIXES",
|
|
1213
|
+
"COOCCURRENCE_CONCEPT_LIMIT",
|
|
1214
|
+
"COOCCURRENCE_EDGE_WEIGHT",
|
|
1215
|
+
"Counter",
|
|
1216
|
+
"Dict",
|
|
1217
|
+
"EDGE_VERB",
|
|
1218
|
+
"ENABLE_LLM_EXTRACTION",
|
|
1219
|
+
"EdgeType",
|
|
1220
|
+
"GRAPH_SCHEMA_VERSION",
|
|
1221
|
+
"Iterable",
|
|
1222
|
+
"Iterator",
|
|
1223
|
+
"KGStoreV2",
|
|
1224
|
+
"LINUX_EXCLUDED_PREFIXES",
|
|
1225
|
+
"LOCAL_CODE_EXTENSIONS",
|
|
1226
|
+
"LOCAL_DOCUMENT_EXTENSIONS",
|
|
1227
|
+
"LOCAL_IMAGE_EXTENSIONS",
|
|
1228
|
+
"LOCAL_SIZE_LIMITS",
|
|
1229
|
+
"LOCAL_SLIDE_EXTENSIONS",
|
|
1230
|
+
"LOCAL_SPREADSHEET_EXTENSIONS",
|
|
1231
|
+
"LOCAL_SUPPORTED_EXTENSIONS",
|
|
1232
|
+
"LOCAL_TEXT_EXTENSIONS",
|
|
1233
|
+
"List",
|
|
1234
|
+
"LocalEmbeddingModel",
|
|
1235
|
+
"MACOS_EXCLUDED_PREFIXES",
|
|
1236
|
+
"NodeType",
|
|
1237
|
+
"Optional",
|
|
1238
|
+
"Path",
|
|
1239
|
+
"SENSITIVE_PATH_KEYWORDS",
|
|
1240
|
+
"Tuple",
|
|
1241
|
+
"VERB_EDGE_WEIGHT",
|
|
1242
|
+
"WINDOWS_EXCLUDED_NAMES",
|
|
1243
|
+
"_CHUNK_STRATEGIES",
|
|
1244
|
+
"_CODE_BLANK_RUN_RE",
|
|
1245
|
+
"_CODE_BOUNDARY_LINE_RE",
|
|
1246
|
+
"_CODE_CHUNK_EXTENSIONS",
|
|
1247
|
+
"_CONCEPT_STOP",
|
|
1248
|
+
"_KG_DB_FORMAT_KEY",
|
|
1249
|
+
"_KG_DB_FORMAT_VERSION",
|
|
1250
|
+
"_LLM_EXTRACT_CONCEPT_PROMPT",
|
|
1251
|
+
"_LLM_EXTRACT_TRIPLE_PROMPT",
|
|
1252
|
+
"_MARKDOWN_CHUNK_EXTENSIONS",
|
|
1253
|
+
"_MARKDOWN_HEADING_RE",
|
|
1254
|
+
"_MARKDOWN_MIN_SECTION_CHARS",
|
|
1255
|
+
"_NOT_PERSON_WORDS",
|
|
1256
|
+
"_PROJECTION_VERSION",
|
|
1257
|
+
"_PROSE_CHUNK_EXTENSIONS",
|
|
1258
|
+
"_PROSE_MIN_SPAN_RATIO",
|
|
1259
|
+
"_PROSE_STRONG_BOUNDARY_RE",
|
|
1260
|
+
"_PROSE_WEAK_BOUNDARY_RE",
|
|
1261
|
+
"_READ_FROM_V2_DEFAULT",
|
|
1262
|
+
"_V2_WRITE_MASTER_KEY",
|
|
1263
|
+
"_chunks",
|
|
1264
|
+
"_classify_node_type",
|
|
1265
|
+
"_clean_text",
|
|
1266
|
+
"_code_chunks",
|
|
1267
|
+
"_code_segment_spans",
|
|
1268
|
+
"_current_os_type",
|
|
1269
|
+
"_drive_id_for_path",
|
|
1270
|
+
"_excluded_directory_reason",
|
|
1271
|
+
"_exec_script",
|
|
1272
|
+
"_extract_concepts",
|
|
1273
|
+
"_extract_concepts_rules",
|
|
1274
|
+
"_extract_triples",
|
|
1275
|
+
"_extract_triples_rules",
|
|
1276
|
+
"_file_category",
|
|
1277
|
+
"_infer_edge",
|
|
1278
|
+
"_is_hidden_path",
|
|
1279
|
+
"_is_relative_to",
|
|
1280
|
+
"_json",
|
|
1281
|
+
"_last_boundary",
|
|
1282
|
+
"_llm_extract_concepts",
|
|
1283
|
+
"_llm_extract_triples",
|
|
1284
|
+
"_markdown_chunks",
|
|
1285
|
+
"_markdown_section_spans",
|
|
1286
|
+
"_merge_small_sections",
|
|
1287
|
+
"_node_type_for_category",
|
|
1288
|
+
"_now",
|
|
1289
|
+
"_parse_iso",
|
|
1290
|
+
"_parser_type_for_category",
|
|
1291
|
+
"_path_fingerprint",
|
|
1292
|
+
"_path_parts_lower",
|
|
1293
|
+
"_plain_windows",
|
|
1294
|
+
"_prose_chunks",
|
|
1295
|
+
"_recency_score",
|
|
1296
|
+
"_root_warning",
|
|
1297
|
+
"_safe_iso_from_stat_mtime",
|
|
1298
|
+
"_safe_loads",
|
|
1299
|
+
"_sample_file",
|
|
1300
|
+
"_semantic_items",
|
|
1301
|
+
"_sensitive_file_reason",
|
|
1302
|
+
"_sha256_bytes",
|
|
1303
|
+
"_sha256_text",
|
|
1304
|
+
"_size_limit_for_category",
|
|
1305
|
+
"_slug",
|
|
1306
|
+
"_topic_candidates",
|
|
1307
|
+
"asyncio",
|
|
1308
|
+
"chunk_strategy_for",
|
|
1309
|
+
"citation_locator",
|
|
1310
|
+
"contextmanager",
|
|
1311
|
+
"datetime",
|
|
1312
|
+
"get_llm_router",
|
|
1313
|
+
"hashlib",
|
|
1314
|
+
"infer_edge_relation",
|
|
1315
|
+
"json",
|
|
1316
|
+
"logging",
|
|
1317
|
+
"math",
|
|
1318
|
+
"os",
|
|
1319
|
+
"page_for_offset",
|
|
1320
|
+
"pdf_page_offsets",
|
|
1321
|
+
"platform",
|
|
1322
|
+
"quiet",
|
|
1323
|
+
"re",
|
|
1324
|
+
"set_llm_router",
|
|
1325
|
+
"shutil",
|
|
1326
|
+
"sqlite3",
|
|
1327
|
+
"time",
|
|
1328
|
+
"typed_chunk_meta_fields",
|
|
1329
|
+
"typed_chunks",
|
|
1330
|
+
"zipfile",
|
|
1331
|
+
]
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""The seam eleven graph mixins share.
|
|
2
|
+
|
|
3
|
+
``KnowledgeGraphStore`` is assembled from eleven mixins that freely call each
|
|
4
|
+
other's methods — ``ingest`` calls ``_upsert_node`` from ``write_master``,
|
|
5
|
+
``write_master`` calls ``_v2_project_node`` from ``projection``, everything
|
|
6
|
+
calls ``_connect`` from the store itself. That contract existed, but nowhere in
|
|
7
|
+
the code: a reader had to grep eleven files to learn what a mixin may assume,
|
|
8
|
+
and a type checker reported 229 ``attr-defined`` errors because from *its*
|
|
9
|
+
position each mixin is a bare class calling methods it does not have.
|
|
10
|
+
|
|
11
|
+
This module writes the contract down. It is **typing-only** — the mixins alias
|
|
12
|
+
it to ``object`` at runtime, so the MRO and every method resolution are
|
|
13
|
+
byte-for-byte what they were. What changes is that the shared surface is now 23
|
|
14
|
+
declared names in one file instead of an unwritten convention, and adding a
|
|
15
|
+
cross-mixin call without declaring it here is a type error.
|
|
16
|
+
|
|
17
|
+
Signatures mirror the real implementations; ``tests/unit/test_kg_contract.py``
|
|
18
|
+
asserts the concrete store still provides every member.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import sqlite3
|
|
24
|
+
from contextlib import AbstractContextManager
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class KnowledgeGraphCore:
|
|
30
|
+
"""What any graph mixin may assume about ``self``.
|
|
31
|
+
|
|
32
|
+
Never instantiated and never inherited at runtime — see the module
|
|
33
|
+
docstring. Members are declared, not implemented, on purpose: the concrete
|
|
34
|
+
implementation lives in whichever mixin owns it.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# ── State owned by KnowledgeGraphStore.__init__ ──────────────────────────
|
|
38
|
+
db_path: Path
|
|
39
|
+
blob_dir: Path
|
|
40
|
+
storage_engine: Any
|
|
41
|
+
_embedding_model: Any
|
|
42
|
+
_read_from_v2: bool
|
|
43
|
+
_v2_projection_available: bool
|
|
44
|
+
|
|
45
|
+
# ── store.py: connection + read-table resolution ─────────────────────────
|
|
46
|
+
def _connect(self) -> AbstractContextManager[sqlite3.Connection]:
|
|
47
|
+
"""Transactional connection that is *closed* when the block exits."""
|
|
48
|
+
raise NotImplementedError
|
|
49
|
+
|
|
50
|
+
def _read_tables(self) -> Tuple[str, ...]:
|
|
51
|
+
"""``(nodes_table, edges_table)`` — legacy tables or the v2 views."""
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
54
|
+
# ── write_master.py: the single write door ───────────────────────────────
|
|
55
|
+
def _upsert_node(
|
|
56
|
+
self,
|
|
57
|
+
conn: sqlite3.Connection,
|
|
58
|
+
node_id: str,
|
|
59
|
+
node_type: str,
|
|
60
|
+
title: str,
|
|
61
|
+
summary: str = "",
|
|
62
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
63
|
+
raw: Optional[Dict[str, Any]] = None,
|
|
64
|
+
owner: Optional[str] = None,
|
|
65
|
+
workspace_id: Optional[str] = None,
|
|
66
|
+
visibility: Optional[str] = None,
|
|
67
|
+
) -> str:
|
|
68
|
+
raise NotImplementedError
|
|
69
|
+
|
|
70
|
+
def _upsert_edge(
|
|
71
|
+
self,
|
|
72
|
+
conn: sqlite3.Connection,
|
|
73
|
+
from_node: str,
|
|
74
|
+
to_node: str,
|
|
75
|
+
edge_type: str,
|
|
76
|
+
weight: float = 1.0,
|
|
77
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
78
|
+
*,
|
|
79
|
+
legacy_label: Optional[str] = None,
|
|
80
|
+
) -> str:
|
|
81
|
+
raise NotImplementedError
|
|
82
|
+
|
|
83
|
+
def _upsert_chunk(
|
|
84
|
+
self,
|
|
85
|
+
conn: sqlite3.Connection,
|
|
86
|
+
*,
|
|
87
|
+
chunk_id: str,
|
|
88
|
+
source_node: str,
|
|
89
|
+
text: str,
|
|
90
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
91
|
+
) -> None:
|
|
92
|
+
raise NotImplementedError
|
|
93
|
+
|
|
94
|
+
def _upsert_vector_item(
|
|
95
|
+
self,
|
|
96
|
+
conn: sqlite3.Connection,
|
|
97
|
+
*,
|
|
98
|
+
item_id: str,
|
|
99
|
+
item_type: str,
|
|
100
|
+
source_node: str,
|
|
101
|
+
text: str,
|
|
102
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
103
|
+
) -> bool:
|
|
104
|
+
raise NotImplementedError
|
|
105
|
+
|
|
106
|
+
def _vector_text_for_node(
|
|
107
|
+
self,
|
|
108
|
+
*,
|
|
109
|
+
title: str,
|
|
110
|
+
summary: str = "",
|
|
111
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
112
|
+
) -> str:
|
|
113
|
+
raise NotImplementedError
|
|
114
|
+
|
|
115
|
+
# ── projection.py: legacy ↔ v2 projection + keyword index ────────────────
|
|
116
|
+
def _v2_project_node(
|
|
117
|
+
self,
|
|
118
|
+
conn: sqlite3.Connection,
|
|
119
|
+
node_id: str,
|
|
120
|
+
node_type: str,
|
|
121
|
+
title: str,
|
|
122
|
+
summary: Optional[str],
|
|
123
|
+
metadata_json: Optional[str],
|
|
124
|
+
*,
|
|
125
|
+
created_at: Optional[str] = None,
|
|
126
|
+
updated_at: Optional[str] = None,
|
|
127
|
+
owner: Optional[str] = None,
|
|
128
|
+
workspace_id: Optional[str] = None,
|
|
129
|
+
visibility: Optional[str] = None,
|
|
130
|
+
strict: bool = False,
|
|
131
|
+
) -> None:
|
|
132
|
+
raise NotImplementedError
|
|
133
|
+
|
|
134
|
+
def _v2_project_edge(
|
|
135
|
+
self,
|
|
136
|
+
conn: sqlite3.Connection,
|
|
137
|
+
from_node: str,
|
|
138
|
+
to_node: str,
|
|
139
|
+
edge_type: str,
|
|
140
|
+
weight: float,
|
|
141
|
+
metadata_json: Optional[str],
|
|
142
|
+
*,
|
|
143
|
+
edge_id: Optional[str] = None,
|
|
144
|
+
created_at: Optional[str] = None,
|
|
145
|
+
strict: bool = False,
|
|
146
|
+
legacy_type: Optional[str] = None,
|
|
147
|
+
) -> None:
|
|
148
|
+
raise NotImplementedError
|
|
149
|
+
|
|
150
|
+
def _v2_delete_nodes(self, conn: sqlite3.Connection, ids: Any) -> None:
|
|
151
|
+
raise NotImplementedError
|
|
152
|
+
|
|
153
|
+
def _v2_delete_edges_from(self, conn: sqlite3.Connection, node_id: str) -> None:
|
|
154
|
+
raise NotImplementedError
|
|
155
|
+
|
|
156
|
+
def _fts_match_ids(
|
|
157
|
+
self, conn: sqlite3.Connection, query: str, limit: int
|
|
158
|
+
) -> List[str]:
|
|
159
|
+
raise NotImplementedError
|
|
160
|
+
|
|
161
|
+
# ── retrieval_reads.py: workspace scoping ────────────────────────────────
|
|
162
|
+
def filter_scoped_nodes(
|
|
163
|
+
self,
|
|
164
|
+
items: Any,
|
|
165
|
+
allowed_workspaces: Any,
|
|
166
|
+
*,
|
|
167
|
+
id_key: str = "id",
|
|
168
|
+
include_legacy_global: bool = False,
|
|
169
|
+
) -> Any:
|
|
170
|
+
raise NotImplementedError
|
|
171
|
+
|
|
172
|
+
# ── documents.py: structured-document ingestion ──────────────────────────
|
|
173
|
+
def _document_structure(self, path: Path, ext: str) -> Dict[str, Any]:
|
|
174
|
+
raise NotImplementedError
|
|
175
|
+
|
|
176
|
+
def _ingest_structure_nodes(
|
|
177
|
+
self,
|
|
178
|
+
conn: sqlite3.Connection,
|
|
179
|
+
file_id: str,
|
|
180
|
+
filename: str,
|
|
181
|
+
structure: Dict[str, Any],
|
|
182
|
+
*,
|
|
183
|
+
owner: Optional[str] = None,
|
|
184
|
+
workspace_id: Optional[str] = None,
|
|
185
|
+
) -> None:
|
|
186
|
+
raise NotImplementedError
|
|
187
|
+
|
|
188
|
+
# ── discovery.py / discovery_index.py: local filesystem indexing ─────────
|
|
189
|
+
def _iter_local_scan_entries(
|
|
190
|
+
self, root: Path, *, max_files: int
|
|
191
|
+
) -> Iterable[Dict[str, Any]]:
|
|
192
|
+
raise NotImplementedError
|
|
193
|
+
|
|
194
|
+
def _local_file_decision(
|
|
195
|
+
self, path: Path, root: Path, stat: Any
|
|
196
|
+
) -> Dict[str, Any]:
|
|
197
|
+
raise NotImplementedError
|
|
198
|
+
|
|
199
|
+
def _delete_local_file_graph(
|
|
200
|
+
self, conn: sqlite3.Connection, file_node_id: Optional[str]
|
|
201
|
+
) -> None:
|
|
202
|
+
raise NotImplementedError
|
|
203
|
+
|
|
204
|
+
def _cleanup_local_graph_orphans(
|
|
205
|
+
self, conn: sqlite3.Connection, source_id: str
|
|
206
|
+
) -> None:
|
|
207
|
+
raise NotImplementedError
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
#: Every name a mixin may reach for through ``self``. Kept in sync by
|
|
211
|
+
#: ``tests/unit/test_kg_contract.py`` — the test derives it from the class, so
|
|
212
|
+
#: this constant cannot drift from the declarations above.
|
|
213
|
+
KG_CORE_MEMBERS: Sequence[str] = tuple(
|
|
214
|
+
sorted(
|
|
215
|
+
name
|
|
216
|
+
for name in vars(KnowledgeGraphCore)
|
|
217
|
+
if not name.startswith("__")
|
|
218
|
+
)
|
|
219
|
+
+ sorted(getattr(KnowledgeGraphCore, "__annotations__", {}))
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
__all__ = ["KG_CORE_MEMBERS", "KnowledgeGraphCore"]
|
|
@@ -192,7 +192,7 @@ def _excluded_directory_reason(
|
|
|
192
192
|
if os_type == "macos":
|
|
193
193
|
home_library = Path.home() / "Library"
|
|
194
194
|
try:
|
|
195
|
-
root_is_library =
|
|
195
|
+
root_is_library = root is not None and _is_relative_to(
|
|
196
196
|
root.expanduser().resolve(), home_library.expanduser().resolve()
|
|
197
197
|
)
|
|
198
198
|
if (
|
|
@@ -525,7 +525,7 @@ def plan_concept_noise_reduction(
|
|
|
525
525
|
remove: List[Dict[str, Any]] = []
|
|
526
526
|
keep: List[Dict[str, Any]] = []
|
|
527
527
|
for concept in concepts:
|
|
528
|
-
entry = {
|
|
528
|
+
entry: Dict[str, Any] = {
|
|
529
529
|
"id": str(concept.get("id") or ""),
|
|
530
530
|
"label": concept.get("label"),
|
|
531
531
|
"df": int(concept.get("df") or 0),
|
|
@@ -534,7 +534,7 @@ def plan_concept_noise_reduction(
|
|
|
534
534
|
if not entry["heuristic"]:
|
|
535
535
|
keep.append({**entry, "reason": "user_created_protected"})
|
|
536
536
|
continue
|
|
537
|
-
df = entry["df"]
|
|
537
|
+
df = int(entry["df"])
|
|
538
538
|
if df < int(min_doc_frequency):
|
|
539
539
|
df_ratio = (df / total) if total else 0.0
|
|
540
540
|
remove.append({**entry, "df_ratio": round(df_ratio, 4), "reason": "below_frequency_floor"})
|
|
@@ -578,7 +578,7 @@ def plan_relation_noise_reduction(
|
|
|
578
578
|
keep: List[Dict[str, Any]] = []
|
|
579
579
|
demote: List[Dict[str, Any]] = []
|
|
580
580
|
for edge in edges:
|
|
581
|
-
entry = {
|
|
581
|
+
entry: Dict[str, Any] = {
|
|
582
582
|
"id": str(edge.get("id") or ""),
|
|
583
583
|
"from": edge.get("from"),
|
|
584
584
|
"to": edge.get("to"),
|
|
@@ -600,7 +600,7 @@ def plan_relation_noise_reduction(
|
|
|
600
600
|
if not entry["evidence"]:
|
|
601
601
|
keep.append({**entry, "reason": "unknown_evidence"})
|
|
602
602
|
continue
|
|
603
|
-
if entry["weight"] < float(min_cooccurrence_weight):
|
|
603
|
+
if float(entry["weight"]) < float(min_cooccurrence_weight):
|
|
604
604
|
demote.append({**entry, "reason": "weak_cooccurrence"})
|
|
605
605
|
continue
|
|
606
606
|
if degree > int(max_cooccurrence_degree):
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
3
5
|
from ..quiet import quiet
|
|
4
6
|
|
|
5
7
|
# ruff: noqa: F403,F405
|
|
6
8
|
from ._kg_common import * # noqa: F403,F401
|
|
7
9
|
|
|
10
|
+
# The cross-mixin surface (`_connect`, `_upsert_node`, …) is declared in
|
|
11
|
+
# `_kg_contract.KnowledgeGraphCore`. It is a typing-only base: at runtime this
|
|
12
|
+
# is `object`, so the MRO of `KnowledgeGraphStore` is unchanged.
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ._kg_contract import KnowledgeGraphCore as _Core
|
|
15
|
+
else:
|
|
16
|
+
_Core = object
|
|
17
|
+
|
|
18
|
+
|
|
8
19
|
|
|
9
|
-
class KnowledgeGraphDiscoveryMixin:
|
|
20
|
+
class KnowledgeGraphDiscoveryMixin(_Core):
|
|
10
21
|
def discover_local_roots(self) -> Dict[str, Any]:
|
|
11
22
|
"""Return safe, cross-platform starting points for structure browsing."""
|
|
12
23
|
os_type = _current_os_type()
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
3
5
|
# ruff: noqa: F403,F405
|
|
4
6
|
from ._kg_common import * # noqa: F403,F401
|
|
5
7
|
|
|
8
|
+
# The cross-mixin surface (`_connect`, `_upsert_node`, …) is declared in
|
|
9
|
+
# `_kg_contract.KnowledgeGraphCore`. It is a typing-only base: at runtime this
|
|
10
|
+
# is `object`, so the MRO of `KnowledgeGraphStore` is unchanged.
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ._kg_contract import KnowledgeGraphCore as _Core
|
|
13
|
+
else:
|
|
14
|
+
_Core = object
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
|
|
7
18
|
def _local_scoped_slug(prefix: str, value: str, workspace_id: Optional[str]) -> str:
|
|
8
19
|
"""Preserve legacy IDs while isolating newly workspace-scoped nodes."""
|
|
@@ -13,7 +24,7 @@ def _local_scoped_slug(prefix: str, value: str, workspace_id: Optional[str]) ->
|
|
|
13
24
|
return f"{prefix}:{scope}:{slug}"
|
|
14
25
|
|
|
15
26
|
|
|
16
|
-
class KnowledgeGraphLocalIndexMixin:
|
|
27
|
+
class KnowledgeGraphLocalIndexMixin(_Core):
|
|
17
28
|
"""Local file → graph indexing (text extraction, node/index upserts,
|
|
18
29
|
graph-node deletion, orphan cleanup, and the index_local_folder driver),
|
|
19
30
|
split out of discovery. Composed into KnowledgeGraphStore alongside
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
3
5
|
from ..quiet import quiet
|
|
4
6
|
|
|
5
7
|
# ruff: noqa: F403,F405
|
|
6
8
|
from ._kg_common import * # noqa: F403,F401
|
|
7
9
|
|
|
10
|
+
# The cross-mixin surface (`_connect`, `_upsert_node`, …) is declared in
|
|
11
|
+
# `_kg_contract.KnowledgeGraphCore`. It is a typing-only base: at runtime this
|
|
12
|
+
# is `object`, so the MRO of `KnowledgeGraphStore` is unchanged.
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ._kg_contract import KnowledgeGraphCore as _Core
|
|
15
|
+
else:
|
|
16
|
+
_Core = object
|
|
17
|
+
|
|
18
|
+
|
|
8
19
|
|
|
9
|
-
class KnowledgeGraphDocumentsMixin:
|
|
20
|
+
class KnowledgeGraphDocumentsMixin(_Core):
|
|
10
21
|
def find_documents_by_uri_prefix(self, prefix: str) -> List[Dict[str, Any]]:
|
|
11
22
|
"""Content nodes whose ``metadata.source_uri`` starts with ``prefix``.
|
|
12
23
|
|
|
@@ -246,7 +257,11 @@ class KnowledgeGraphDocumentsMixin:
|
|
|
246
257
|
|
|
247
258
|
prs = Presentation(str(path))
|
|
248
259
|
for slide_index, slide in enumerate(prs.slides, start=1):
|
|
249
|
-
slide_info
|
|
260
|
+
slide_info: Dict[str, Any] = {
|
|
261
|
+
"index": slide_index,
|
|
262
|
+
"shapes": [],
|
|
263
|
+
"texts": [],
|
|
264
|
+
}
|
|
250
265
|
for shape_index, shape in enumerate(slide.shapes, start=1):
|
|
251
266
|
shape_info = {
|
|
252
267
|
"index": shape_index,
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
3
5
|
from ..quiet import quiet
|
|
4
6
|
from ..sensitivity import stamp_sensitivity
|
|
5
7
|
|
|
6
8
|
# ruff: noqa: F403,F405
|
|
7
9
|
from ._kg_common import * # noqa: F403,F401
|
|
8
10
|
|
|
11
|
+
# The cross-mixin surface (`_connect`, `_upsert_node`, …) is declared in
|
|
12
|
+
# `_kg_contract.KnowledgeGraphCore`. It is a typing-only base: at runtime this
|
|
13
|
+
# is `object`, so the MRO of `KnowledgeGraphStore` is unchanged.
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ._kg_contract import KnowledgeGraphCore as _Core
|
|
16
|
+
else:
|
|
17
|
+
_Core = object
|
|
18
|
+
|
|
19
|
+
|
|
9
20
|
|
|
10
21
|
def _scoped_slug_id(prefix: str, value: str, workspace_id: Optional[str]) -> str:
|
|
11
22
|
"""Keep legacy IDs unchanged while isolating every newly scoped identity."""
|
|
@@ -42,7 +53,7 @@ def _triple_edge_metadata(triple: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
42
53
|
return metadata
|
|
43
54
|
|
|
44
55
|
|
|
45
|
-
class KnowledgeGraphIngestMixin:
|
|
56
|
+
class KnowledgeGraphIngestMixin(_Core):
|
|
46
57
|
def ingest_message(
|
|
47
58
|
self,
|
|
48
59
|
role: str,
|
|
@@ -191,11 +191,13 @@ class ProactiveBrain:
|
|
|
191
191
|
for indices in token_index.values():
|
|
192
192
|
if len(indices) < 2 or len(indices) > 50: # skip too-common tokens
|
|
193
193
|
continue
|
|
194
|
-
for i,
|
|
195
|
-
for
|
|
196
|
-
cooccur[(
|
|
194
|
+
for i, left_idx in enumerate(indices):
|
|
195
|
+
for right_idx in indices[i + 1:]:
|
|
196
|
+
cooccur[(left_idx, right_idx)] = (
|
|
197
|
+
cooccur.get((left_idx, right_idx), 0) + 1
|
|
198
|
+
)
|
|
197
199
|
|
|
198
|
-
near_pairs = []
|
|
200
|
+
near_pairs: List[Dict[str, Any]] = []
|
|
199
201
|
for (li, ri), shared in sorted(cooccur.items(), key=lambda kv: -kv[1]):
|
|
200
202
|
if shared < 3:
|
|
201
203
|
continue
|
|
@@ -219,7 +221,7 @@ class ProactiveBrain:
|
|
|
219
221
|
)
|
|
220
222
|
if len(near_pairs) >= max_pairs:
|
|
221
223
|
break
|
|
222
|
-
near_pairs.sort(key=lambda p: p["similarity"], reverse=True)
|
|
224
|
+
near_pairs.sort(key=lambda p: float(p["similarity"]), reverse=True)
|
|
223
225
|
|
|
224
226
|
return {
|
|
225
227
|
"nodes_scanned": len(nodes),
|
|
@@ -467,6 +469,8 @@ class ProactiveBrain:
|
|
|
467
469
|
mode = "applied"
|
|
468
470
|
for group in groups:
|
|
469
471
|
try:
|
|
472
|
+
if merge_fn is None: # guarded by apply_supported above
|
|
473
|
+
raise RuntimeError("store has no merge_nodes")
|
|
470
474
|
result = merge_fn(group["keep"], group["remove"])
|
|
471
475
|
applied.append({"keep": group["keep"], "result": result})
|
|
472
476
|
except Exception as exc: # keep going; report per-group failure
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
3
5
|
from ..quiet import quiet
|
|
4
6
|
|
|
5
7
|
# ruff: noqa: F403,F405
|
|
6
8
|
from ._kg_common import * # noqa: F403,F401
|
|
7
9
|
|
|
10
|
+
# The cross-mixin surface (`_connect`, `_upsert_node`, …) is declared in
|
|
11
|
+
# `_kg_contract.KnowledgeGraphCore`. It is a typing-only base: at runtime this
|
|
12
|
+
# is `object`, so the MRO of `KnowledgeGraphStore` is unchanged.
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ._kg_contract import KnowledgeGraphCore as _Core
|
|
15
|
+
else:
|
|
16
|
+
_Core = object
|
|
17
|
+
|
|
18
|
+
|
|
8
19
|
# ── promotion review mode (review 2026-07-25 Wave 4) ─────────────────────────
|
|
9
20
|
# When enabled, curate() parks would-be Topic promotions in graph_meta for a
|
|
10
21
|
# human decision instead of writing them immediately. Explicit review_mode=
|
|
@@ -22,7 +33,7 @@ def _promotion_review_default() -> bool:
|
|
|
22
33
|
return os.getenv(_PROMOTION_REVIEW_ENV, "").strip().lower() in ("1", "true", "yes")
|
|
23
34
|
|
|
24
35
|
|
|
25
|
-
class KnowledgeGraphProjectionMixin:
|
|
36
|
+
class KnowledgeGraphProjectionMixin(_Core):
|
|
26
37
|
_FTS_SQL = """
|
|
27
38
|
CREATE VIRTUAL TABLE IF NOT EXISTS node_fts USING fts5(
|
|
28
39
|
node_id UNINDEXED, title, summary, metadata, tokenize='trigram'
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
3
5
|
# ruff: noqa: F403,F405
|
|
4
6
|
from ._kg_common import * # noqa: F403,F401
|
|
5
7
|
|
|
8
|
+
# The cross-mixin surface (`_connect`, `_upsert_node`, …) is declared in
|
|
9
|
+
# `_kg_contract.KnowledgeGraphCore`. It is a typing-only base: at runtime this
|
|
10
|
+
# is `object`, so the MRO of `KnowledgeGraphStore` is unchanged.
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ._kg_contract import KnowledgeGraphCore as _Core
|
|
13
|
+
else:
|
|
14
|
+
_Core = object
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
|
|
7
|
-
class KnowledgeGraphProvenanceMixin:
|
|
18
|
+
class KnowledgeGraphProvenanceMixin(_Core):
|
|
8
19
|
def record_provenance(
|
|
9
20
|
self,
|
|
10
21
|
*,
|