ltcai 11.0.1 → 11.2.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.
Files changed (140) hide show
  1. package/README.md +55 -43
  2. package/docs/CHANGELOG.md +61 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/FEATURE_AUDIT_v11.2.0.md +393 -0
  6. package/docs/LAYOUT_REBUILD_SPEC.md +9 -1
  7. package/docs/ONBOARDING.md +1 -1
  8. package/docs/OPERATIONS.md +1 -1
  9. package/docs/PERFORMANCE.md +71 -18
  10. package/docs/TRUST_MODEL.md +1 -1
  11. package/docs/WHY_LATTICE.md +1 -1
  12. package/docs/architecture.md +6 -2
  13. package/docs/kg-schema.md +1 -1
  14. package/lattice_brain/__init__.py +1 -1
  15. package/lattice_brain/embeddings.py +12 -37
  16. package/lattice_brain/gates.py +125 -0
  17. package/lattice_brain/graph/discovery_index.py +30 -32
  18. package/lattice_brain/graph/fusion.py +35 -4
  19. package/lattice_brain/graph/image_vectors.py +230 -0
  20. package/lattice_brain/graph/ingest.py +11 -5
  21. package/lattice_brain/graph/projection.py +66 -8
  22. package/lattice_brain/graph/provenance.py +27 -2
  23. package/lattice_brain/graph/retrieval.py +113 -2
  24. package/lattice_brain/graph/retrieval_docgen.py +6 -6
  25. package/lattice_brain/graph/schema.py +18 -0
  26. package/lattice_brain/graph/store.py +9 -0
  27. package/lattice_brain/graph/vector_index/selector.py +32 -2
  28. package/lattice_brain/ingestion.py +363 -10
  29. package/lattice_brain/multimodal.py +1258 -0
  30. package/lattice_brain/portability.py +169 -32
  31. package/lattice_brain/runtime/multi_agent.py +1 -1
  32. package/lattice_brain/sealed_box.py +244 -0
  33. package/lattice_brain/self_model.py +77 -22
  34. package/lattice_brain/synthesis.py +24 -1
  35. package/latticeai/__init__.py +1 -1
  36. package/latticeai/api/brain_intelligence.py +4 -0
  37. package/latticeai/api/chat.py +11 -0
  38. package/latticeai/api/chat_helpers.py +16 -3
  39. package/latticeai/api/chat_hybrid.py +32 -1
  40. package/latticeai/api/features.py +70 -0
  41. package/latticeai/api/local_files.py +102 -0
  42. package/latticeai/api/memory.py +128 -1
  43. package/latticeai/api/portability.py +39 -4
  44. package/latticeai/api/review_queue.py +126 -0
  45. package/latticeai/api/search.py +16 -2
  46. package/latticeai/core/agent.py +59 -2
  47. package/latticeai/core/agent_prompts.py +66 -0
  48. package/latticeai/core/config.py +4 -1
  49. package/latticeai/core/context_builder.py +98 -11
  50. package/latticeai/core/embedding_providers.py +528 -0
  51. package/latticeai/core/legacy_compatibility.py +1 -1
  52. package/latticeai/core/marketplace.py +1 -1
  53. package/latticeai/core/messages.py +180 -0
  54. package/latticeai/core/model_compat.py +73 -2
  55. package/latticeai/core/workspace_os.py +43 -0
  56. package/latticeai/core/workspace_os_constants.py +1 -1
  57. package/latticeai/core/workspace_reorganization.py +335 -0
  58. package/latticeai/models/model_providers.py +12 -4
  59. package/latticeai/runtime/build_phases.py +33 -2
  60. package/latticeai/runtime/chat_wiring.py +4 -0
  61. package/latticeai/runtime/feature_toggle_wiring.py +163 -0
  62. package/latticeai/runtime/persistence_runtime.py +41 -4
  63. package/latticeai/runtime/router_registration.py +11 -0
  64. package/latticeai/runtime/runtime_context.py +1 -0
  65. package/latticeai/services/app_context.py +8 -0
  66. package/latticeai/services/architecture_readiness.py +1 -1
  67. package/latticeai/services/automation_intelligence.py +22 -2
  68. package/latticeai/services/brain_intelligence.py +123 -7
  69. package/latticeai/services/change_proposals.py +50 -10
  70. package/latticeai/services/command_center.py +10 -4
  71. package/latticeai/services/feature_toggles.py +502 -0
  72. package/latticeai/services/folder_watch.py +122 -1
  73. package/latticeai/services/hybrid_chat.py +56 -5
  74. package/latticeai/services/interop_bridges.py +978 -0
  75. package/latticeai/services/memory_service.py +34 -0
  76. package/latticeai/services/model_capability_registry.py +434 -261
  77. package/latticeai/services/model_catalog.py +95 -61
  78. package/latticeai/services/model_recommendation.py +18 -11
  79. package/latticeai/services/model_runtime.py +1 -1
  80. package/latticeai/services/multimodal_ports.py +112 -0
  81. package/latticeai/services/obsidian_bridge.py +16 -25
  82. package/latticeai/services/product_readiness.py +1 -1
  83. package/latticeai/services/search_service.py +149 -2
  84. package/latticeai/services/self_model_service.py +171 -0
  85. package/latticeai/services/tool_dispatch.py +4 -0
  86. package/latticeai/services/voice_capture.py +27 -1
  87. package/latticeai/setup/auto_setup.py +27 -30
  88. package/latticeai/setup/wizard.py +77 -44
  89. package/package.json +1 -1
  90. package/scripts/check_current_release_docs.mjs +1 -1
  91. package/scripts/check_server_i18n.mjs +1 -0
  92. package/scripts/release_screen_claims.json +22 -0
  93. package/scripts/verify_hf_model_registry.py +253 -218
  94. package/src-tauri/Cargo.lock +1 -1
  95. package/src-tauri/Cargo.toml +1 -1
  96. package/src-tauri/tauri.conf.json +1 -1
  97. package/static/app/asset-manifest.json +37 -37
  98. package/static/app/assets/{Act-D4zSxFR-.js → Act-AWf0SAKp.js} +1 -1
  99. package/static/app/assets/{AdminConsole-w5jBfPt2.js → AdminConsole-D0u8Tiyj.js} +1 -1
  100. package/static/app/assets/{Brain-C2EqQg74.js → Brain-tuhI4sOC.js} +1 -1
  101. package/static/app/assets/BrainHome-Ts7G_Ila.js +2 -0
  102. package/static/app/assets/BrainSignals-jMYgQ2Ar.js +1 -0
  103. package/static/app/assets/{Capture-DPqpGK8d.js → Capture-CqOSzyPr.js} +1 -1
  104. package/static/app/assets/{CommandPalette-CNf7h5fp.js → CommandPalette-DC0Bzh-I.js} +1 -1
  105. package/static/app/assets/{Library-BN0HYOfc.js → Library-CX-bbhmK.js} +1 -1
  106. package/static/app/assets/{LivingBrain-Dfq_wEDI.js → LivingBrain-DBwhto14.js} +1 -1
  107. package/static/app/assets/{ProductFlow-B-3O0rNV.js → ProductFlow-BHA2cfKI.js} +1 -1
  108. package/static/app/assets/{ReviewCard-gZ-tdqFM.js → ReviewCard-BUhCKRNM.js} +1 -1
  109. package/static/app/assets/{System-BElUcSSw.js → System-Bu2t5hn1.js} +1 -1
  110. package/static/app/assets/arrow-left-Dzwa5zRb.js +1 -0
  111. package/static/app/assets/{bot--qYHMtkP.js → bot-Cia42c2h.js} +1 -1
  112. package/static/app/assets/brain-DJMoqrwx.js +1 -0
  113. package/static/app/assets/{button-51Z3rsuv.js → button-2j2Ijzgq.js} +1 -1
  114. package/static/app/assets/{circle-pause-CMIiMaQl.js → circle-pause-BEFeWpVW.js} +1 -1
  115. package/static/app/assets/{circle-play-DZoO_cfG.js → circle-play-ujXMcHxl.js} +1 -1
  116. package/static/app/assets/{cpu-Bs6uc9W9.js → cpu-k4awryFq.js} +1 -1
  117. package/static/app/assets/{download-G-2olkWz.js → download-DFbLJ_ig.js} +1 -1
  118. package/static/app/assets/{folder-open-CTOspnmb.js → folder-open-7y_b6xkM.js} +1 -1
  119. package/static/app/assets/{hard-drive-CewHWJhn.js → hard-drive-Bidh02Kr.js} +1 -1
  120. package/static/app/assets/{index-D7Rr-J2Y.js → index-BpYkzcVm.js} +3 -3
  121. package/static/app/assets/index-DwDl9-8Y.css +2 -0
  122. package/static/app/assets/{input-D4w_BZWl.js → input-DSlJJxRs.js} +1 -1
  123. package/static/app/assets/{permissionCopy-CosBEXAZ.js → permissionCopy-Bpb83Hx9.js} +1 -1
  124. package/static/app/assets/{primitives-d0g9pvzS.js → primitives-BCx6TvfG.js} +1 -1
  125. package/static/app/assets/search-Cgy8cCFJ.js +1 -0
  126. package/static/app/assets/{share-2-NmD7e_oV.js → share-2-BH1M-WNi.js} +1 -1
  127. package/static/app/assets/{shield-alert-CcQeMuju.js → shield-alert-BlKdBXcG.js} +1 -1
  128. package/static/app/assets/{textarea-BPAJDc-0.js → textarea-CCWbUfFB.js} +1 -1
  129. package/static/app/assets/{useFocusTrap-C7YLdTBC.js → useFocusTrap-YdHQ7pJ1.js} +1 -1
  130. package/static/app/assets/{useQuery-DRyD9opW.js → useQuery-CXQiwbVT.js} +1 -1
  131. package/static/app/assets/{utils-DG1_ExrP.js → utils-zqPZJxdx.js} +2 -2
  132. package/static/app/assets/{workspace-CWVf3gsI.js → workspace-DXTihhfU.js} +1 -1
  133. package/static/app/index.html +4 -4
  134. package/static/sw.js +1 -1
  135. package/static/app/assets/BrainHome-CvXS6XiQ.js +0 -2
  136. package/static/app/assets/BrainSignals-DOE_KhOU.js +0 -1
  137. package/static/app/assets/arrow-left-CFNIMjhv.js +0 -1
  138. package/static/app/assets/brain-DDCLjRqO.js +0 -1
  139. package/static/app/assets/index-CkzokZAj.css +0 -2
  140. package/static/app/assets/search-BLCYt75v.js +0 -1
@@ -0,0 +1,171 @@
1
+ """Self-Model service — the Brain's profile of its owner (v11.1.0 Track 4).
2
+
3
+ Router-facing façade over :mod:`lattice_brain.self_model`. It owns two things
4
+ the Brain Core deliberately does not:
5
+
6
+ * **where the collaborators come from.** The knowledge graph and the review
7
+ queue are read off the ``MemoryService`` the memory router already holds
8
+ (the same ``WorkspaceOSStore`` every other ``ReviewQueueService`` in the
9
+ process is built over), so the Self-Model routes need no extra wiring at the
10
+ composition root and proposals land in the one inbox the user reads.
11
+ * **what "unavailable" means.** A Brain with the graph disabled reports
12
+ ``available: False`` with a reason instead of pretending it has no profile,
13
+ and a write attempted without a graph raises so the API answers 409 rather
14
+ than silently doing nothing.
15
+
16
+ The consent model is the product's: extraction proposes, the user approves,
17
+ and only the *user's own* edits write directly.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from typing import Any, Dict, List, Optional
23
+
24
+ from lattice_brain.self_model import (
25
+ DEFAULT_SUMMARY_TOKENS,
26
+ KIND_ORDER,
27
+ SelfModelError,
28
+ apply_self_model_proposal,
29
+ delete_self_model_fact,
30
+ list_self_model,
31
+ propose_self_model,
32
+ self_model_summary,
33
+ upsert_self_model_fact,
34
+ )
35
+ from latticeai.core.timeutil import now_iso as _now
36
+
37
+ GRAPH_UNAVAILABLE = "self-model needs the knowledge graph"
38
+ QUEUE_UNAVAILABLE = "self-model proposals need the review queue"
39
+
40
+
41
+ class SelfModelService:
42
+ """Read/propose/apply/edit the Self-Model subgraph."""
43
+
44
+ def __init__(
45
+ self,
46
+ *,
47
+ memory_service: Any = None,
48
+ knowledge_graph: Any = None,
49
+ review_queue: Any = None,
50
+ enable_graph: bool = True,
51
+ summary_tokens: int = DEFAULT_SUMMARY_TOKENS,
52
+ ) -> None:
53
+ self._memory = memory_service
54
+ self._graph = knowledge_graph
55
+ self._queue = review_queue
56
+ self._enable_graph = bool(enable_graph)
57
+ self._summary_tokens = int(summary_tokens)
58
+
59
+ # ── collaborators ────────────────────────────────────────────────────
60
+ def _kg(self) -> Any:
61
+ """The knowledge graph, or ``None`` when this Brain has none."""
62
+ if not self._enable_graph:
63
+ return None
64
+ if self._graph is None:
65
+ self._graph = getattr(self._memory, "_kg", None)
66
+ return self._graph
67
+
68
+ def _review_queue(self) -> Any:
69
+ """The review queue proposals are written to, or ``None``."""
70
+ if self._queue is not None:
71
+ return self._queue
72
+ store = getattr(self._memory, "_store", None)
73
+ if store is None or not hasattr(store, "create_review_item"):
74
+ return None
75
+ from latticeai.services.review_queue import ReviewQueueService
76
+
77
+ self._queue = ReviewQueueService(store=store)
78
+ return self._queue
79
+
80
+ @staticmethod
81
+ def _unavailable(detail: str) -> Dict[str, Any]:
82
+ return {"available": False, "detail": detail, "generated_at": _now()}
83
+
84
+ def _require_graph(self) -> Any:
85
+ graph = self._kg()
86
+ if graph is None:
87
+ raise SelfModelError(GRAPH_UNAVAILABLE, code="graph_unavailable")
88
+ return graph
89
+
90
+ # ── reads ────────────────────────────────────────────────────────────
91
+ def profile(
92
+ self, *, user_email: Optional[str] = None, workspace_id: Optional[str] = None
93
+ ) -> Dict[str, Any]:
94
+ """Everything the Brain holds about its owner, plus the injected text."""
95
+ graph = self._kg()
96
+ if graph is None:
97
+ return {**self._unavailable(GRAPH_UNAVAILABLE), "facts": [], "summary": ""}
98
+ listing = dict(list_self_model(graph, workspace_id=workspace_id))
99
+ listing["summary"] = self_model_summary(
100
+ graph, limit_tokens=self._summary_tokens, workspace_id=workspace_id
101
+ )
102
+ listing["summary_tokens"] = self._summary_tokens
103
+ listing["user_email"] = user_email
104
+ listing["kind_options"] = list(KIND_ORDER)
105
+ return listing
106
+
107
+ def summary(self, *, workspace_id: Optional[str] = None) -> str:
108
+ """Injection-ready summary (empty string when there is nothing to say)."""
109
+ graph = self._kg()
110
+ if graph is None:
111
+ return ""
112
+ return self_model_summary(
113
+ graph, limit_tokens=self._summary_tokens, workspace_id=workspace_id
114
+ )
115
+
116
+ # ── proposal path (agents / background) ──────────────────────────────
117
+ def propose(
118
+ self,
119
+ text: str,
120
+ *,
121
+ texts: Optional[List[str]] = None,
122
+ source: Optional[str] = None,
123
+ user_email: Optional[str] = None,
124
+ workspace_id: Optional[str] = None,
125
+ max_proposals: int = 5,
126
+ ) -> Dict[str, Any]:
127
+ """Read text and raise a review proposal per new fact. Never writes."""
128
+ graph = self._kg()
129
+ if graph is None:
130
+ return self._unavailable(GRAPH_UNAVAILABLE)
131
+ queue = self._review_queue()
132
+ if queue is None:
133
+ return self._unavailable(QUEUE_UNAVAILABLE)
134
+ return propose_self_model(
135
+ graph,
136
+ queue,
137
+ text=text,
138
+ texts=texts,
139
+ source=source,
140
+ workspace_id=workspace_id,
141
+ user_email=user_email,
142
+ max_proposals=max_proposals,
143
+ )
144
+
145
+ def apply(
146
+ self, item_id: str, *, workspace_id: Optional[str] = None
147
+ ) -> Dict[str, Any]:
148
+ """Approve one Self-Model proposal and write the fact into the graph."""
149
+ graph = self._require_graph()
150
+ queue = self._review_queue()
151
+ if queue is None:
152
+ raise SelfModelError(QUEUE_UNAVAILABLE, code="queue_unavailable")
153
+ return apply_self_model_proposal(
154
+ graph, queue, item_id, workspace_id=workspace_id
155
+ )
156
+
157
+ # ── user-initiated edits (direct) ────────────────────────────────────
158
+ def upsert(
159
+ self, *, kind: str, text: str, workspace_id: Optional[str] = None
160
+ ) -> Dict[str, Any]:
161
+ """A person editing their own profile writes straight through."""
162
+ return upsert_self_model_fact(
163
+ self._require_graph(), kind=kind, text=text, workspace_id=workspace_id
164
+ )
165
+
166
+ def delete(self, node_id: str) -> Dict[str, Any]:
167
+ """Forget one fact about the user, permanently."""
168
+ return delete_self_model_fact(self._require_graph(), node_id)
169
+
170
+
171
+ __all__ = ["GRAPH_UNAVAILABLE", "QUEUE_UNAVAILABLE", "SelfModelService"]
@@ -486,6 +486,7 @@ def build_agent_runtime(
486
486
  brain_memory: Any = None,
487
487
  dispatch_service: ToolDispatchService = DEFAULT_TOOL_DISPATCH_SERVICE,
488
488
  permission_mode: Any = None,
489
+ self_model_summary: Any = None,
489
490
  ) -> SingleAgentRuntime:
490
491
  ensure_agent_root()
491
492
  mode = permission_mode if permission_mode is not None else dispatch_service.permission_mode
@@ -513,6 +514,9 @@ def build_agent_runtime(
513
514
  hooks=hooks,
514
515
  brain_memory=brain_memory,
515
516
  permission_mode=mode,
517
+ # v11.2.0: what the Brain knows about its owner reaches the agent loop.
518
+ # ``None`` keeps the pre-11.2.0 prompt bytes exactly.
519
+ self_model_summary=self_model_summary,
516
520
  )
517
521
  return SingleAgentRuntime(deps)
518
522
 
@@ -18,13 +18,23 @@ to become text. So transcription is an **injected, optional port**:
18
18
  Nothing here reaches for a cloud speech API, and nothing here installs a model
19
19
  behind the user's back — an absent transcriber is a reported state, not a
20
20
  prompt to download something.
21
+
22
+ v11.1.0: that same injected port is now the *only* transcription seam in the
23
+ product. When multi-modal ingestion is enabled, a ``.m4a`` found by a folder
24
+ scan is transcribed by whatever this service was given — see
25
+ :meth:`VoiceCaptureService.multimodal_ports`. One transcriber, one honesty
26
+ story: if a voice memo cannot become text here, a scanned recording cannot
27
+ either, and both say so the same way.
21
28
  """
22
29
 
23
30
  from __future__ import annotations
24
31
 
25
32
  import logging
26
33
  from pathlib import Path
27
- from typing import Any, Callable, Dict, Optional
34
+ from typing import TYPE_CHECKING, Any, Callable, Dict, Optional
35
+
36
+ if TYPE_CHECKING: # import-time isolation: the port is a callable, not a class
37
+ from lattice_brain.multimodal import MultimodalPorts
28
38
 
29
39
  LOGGER = logging.getLogger(__name__)
30
40
 
@@ -75,6 +85,19 @@ class VoiceCaptureService:
75
85
  ),
76
86
  }
77
87
 
88
+ def multimodal_ports(self) -> "MultimodalPorts":
89
+ """This service's transcriber, shaped for the ingestion pipeline.
90
+
91
+ The pipeline lives in Brain Core and must not import ``latticeai``, so
92
+ the capability travels as a plain callable rather than as this class.
93
+ Handing over ``None`` when no transcriber is configured is the point:
94
+ the pipeline then degrades exactly as :meth:`capture` does instead of
95
+ inventing a transcript of its own.
96
+ """
97
+ from lattice_brain.multimodal import MultimodalPorts
98
+
99
+ return MultimodalPorts(transcriber=self._transcriber)
100
+
78
101
  # ── transcription ────────────────────────────────────────────────────
79
102
  def _transcribe(self, path: Path) -> str:
80
103
  if self._transcriber is None:
@@ -163,6 +186,9 @@ class VoiceCaptureService:
163
186
  conversation_id=conversation_id,
164
187
  metadata={
165
188
  "capture": "voice",
189
+ # Same key the multi-modal ingest door writes, so a memo and a
190
+ # scanned recording are one kind of thing in the graph.
191
+ "modality": "audio",
166
192
  "audio_path": str(path),
167
193
  "audio_bytes": size,
168
194
  "transcription": transcription_state,
@@ -399,36 +399,36 @@ class Recommendation:
399
399
  return asdict(self)
400
400
 
401
401
 
402
- # 모델 카탈로그. PPT 슬라이드 16 의 "추천 모델" 열과 동기화.
402
+ # 모델 카탈로그 — RAM/VRAM 내림차순. 첫 번째로 조건을 만족하는 항목이 선택된다.
403
+ # 모든 repo id 는 2026-08-10 Hugging Face API 로 확인했다(존재·비gated·정확한 대소문자).
404
+ # OS 오버헤드(~4-6 GB) + KV 캐시 여유를 감안한 보수적 RAM 임계값.
403
405
  _MODEL_CATALOG: List[Dict[str, Any]] = [
404
- # (min_ram_mb, min_vram_mb, model_id, quant, runtime_preference)
405
- # OS 오버헤드(~4-6 GB) + KV 캐시 여유를 감안한 보수적 RAM 임계값
406
+ # 대형 (48GB+): 최상위 dense / Gemma 4 최대 모델
406
407
  {"ram": 64 * 1024, "vram": 32 * 1024,
407
408
  "id": "mlx-community/gemma-4-31b-it-4bit", "q": "4bit", "multimodal": True},
408
- {"ram": 64 * 1024, "vram": 32 * 1024,
409
- "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", "q": "q4_K_M", "multimodal": True},
410
409
  {"ram": 48 * 1024, "vram": 24 * 1024,
411
- "id": "mlx-community/gemma-4-31b-it-4bit", "q": "4bit", "multimodal": True},
410
+ "id": "mlx-community/Qwen3.6-27B-4bit", "q": "4bit", "multimodal": True},
411
+ # MoE (32GB+)
412
+ {"ram": 40 * 1024, "vram": 24 * 1024,
413
+ "id": "mlx-community/Qwen3.6-35B-A3B-4bit", "q": "4bit", "multimodal": True},
412
414
  {"ram": 32 * 1024, "vram": 16 * 1024,
413
415
  "id": "mlx-community/gemma-4-26b-a4b-it-4bit", "q": "4bit", "multimodal": True},
414
- {"ram": 48 * 1024, "vram": 24 * 1024,
415
- "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", "q": "q4_K_M", "multimodal": True},
416
- {"ram": 24 * 1024, "vram": 12 * 1024,
417
- "id": "mlx-community/Llama-4-Scout-17B-16E-Instruct-4bit", "q": "4bit", "multimodal": True},
418
- {"ram": 16 * 1024, "vram": 8 * 1024,
419
- "id": "mlx-community/gemma-4-12b-it-4bit", "q": "4bit", "multimodal": True},
420
- {"ram": 32 * 1024, "vram": 16 * 1024,
421
- "id": "Qwen/Qwen3-VL-8B-Instruct", "q": "q5_K_M", "multimodal": True},
416
+ # 범용 (24GB)
422
417
  {"ram": 24 * 1024, "vram": 12 * 1024,
423
- "id": "Qwen/Qwen3-VL-8B-Instruct", "q": "q4_K_M", "multimodal": True},
418
+ "id": "mlx-community/gpt-oss-20b-MXFP4-Q8", "q": "MXFP4-Q8", "multimodal": False},
419
+ # 중형 (16GB)
424
420
  {"ram": 16 * 1024, "vram": 8 * 1024,
425
- "id": "Qwen/Qwen3-VL-8B-Instruct", "q": "q4_K_M", "multimodal": True},
421
+ "id": "mlx-community/gemma-4-12B-it-4bit", "q": "4bit", "multimodal": True},
426
422
  {"ram": 12 * 1024, "vram": 6 * 1024,
427
- "id": "Qwen/Qwen3-VL-4B-Instruct", "q": "q4_K_M", "multimodal": True},
423
+ "id": "mlx-community/Qwen3.5-9B-MLX-4bit", "q": "4bit", "multimodal": True},
424
+ # 경량 (10GB) / 초경량 (8GB 이하)
425
+ {"ram": 10 * 1024, "vram": 6 * 1024,
426
+ "id": "mlx-community/gemma-4-e4b-it-4bit", "q": "4bit", "multimodal": True},
428
427
  {"ram": 8 * 1024, "vram": 4 * 1024,
429
- "id": "Qwen/Qwen3-VL-4B-Instruct", "q": "q4_K_M", "multimodal": True},
428
+ "id": "mlx-community/gemma-4-e2b-it-4bit", "q": "4bit", "multimodal": True},
429
+ # 최소 사양: 사진은 못 읽지만 한국어로 대답은 한다.
430
430
  {"ram": 4 * 1024, "vram": 0,
431
- "id": "Qwen/Qwen3-VL-4B-Instruct", "q": "q4_K_M", "multimodal": True},
431
+ "id": "mlx-community/LFM2.5-2.6B-4bit", "q": "4bit", "multimodal": False},
432
432
  ]
433
433
 
434
434
 
@@ -622,17 +622,14 @@ def plan(profile: SystemProfile, rec: Recommendation) -> InstallPlan:
622
622
  # 모델 가중치 풀
623
623
  model_command = ["huggingface-cli", "download", rec.model_id, "--quiet"]
624
624
  if rec.runtime == "ollama":
625
- lower = rec.model_id.lower()
626
- if "gemma-4-31b" in lower:
627
- model_command = ["ollama", "pull", "hf.co/ggml-org/gemma-4-31B-it-GGUF:Q4_K_M"]
628
- elif "gemma-4-12b" in lower:
629
- model_command = ["ollama", "pull", "hf.co/ggml-org/gemma-4-12B-it-GGUF:Q4_K_M"]
630
- elif "llama-4-scout" in lower:
631
- model_command = ["ollama", "pull", "hf.co/ggml-org/Llama-4-Scout-17B-16E-Instruct-GGUF:Q4_K_M"]
632
- elif "qwen3-vl-8b" in lower:
633
- model_command = ["ollama", "pull", "qwen3-vl:8b"]
634
- elif "qwen3-vl-4b" in lower:
635
- model_command = ["ollama", "pull", "qwen3-vl:4b"]
625
+ # Local import: this module keeps a stdlib-only import graph so it can be
626
+ # run before the app is wired. The alias table is the single source of
627
+ # truth for per-engine repo ids — the old hardcoded if/elif chain here
628
+ # drifted from it and still named Llama 4 Scout and Qwen3-VL.
629
+ from latticeai.services.model_catalog import MODEL_ENGINE_ALIASES
630
+ ollama_target = (MODEL_ENGINE_ALIASES.get(rec.model_id.lower()) or {}).get("ollama")
631
+ if ollama_target:
632
+ model_command = ["ollama", "pull", ollama_target]
636
633
  elif rec.runtime == "lmstudio":
637
634
  model_command = ["lms", "get", rec.model_id]
638
635
  steps.append(InstallStep(
@@ -562,47 +562,63 @@ def scan_environment() -> Dict[str, Any]:
562
562
 
563
563
  # ── Model Catalog ─────────────────────────────────────────────────────────────
564
564
  # (model_id, display_name, size_gb, tag, description, min_ram_gb)
565
+ # 11.2.0: 모든 repo id 를 2026-08-10 Hugging Face API 로 확인했다 — 존재 여부,
566
+ # gated 여부, 정확한 대소문자, siblings 합계 크기. 크기 값은 측정값이다.
567
+ # 삭제: Qwen3-VL 전 라인업(2025-10, Qwen3.5/3.6 이 상위 세대), Llama 4 Scout
568
+ # (vLLM/LM Studio 경로가 gated 인 meta-llama 저장소를 가리켰다).
565
569
  _MODEL_CATALOG = [
566
- ("mlx-community/Qwen3-VL-4B-Instruct-4bit", "Qwen3-VL 4B", 2.7, "VLM", "최신 Qwen 멀티모달 · 저사양", 4),
567
- ("mlx-community/gemma-4-e2b-it-4bit", "Gemma 4 E2B", 3.6, "VLM", "Gemma 4 소형 멀티모달", 8),
568
- ("mlx-community/Qwen3-VL-8B-Instruct-4bit", "Qwen3-VL 8B", 4.8, "VLM", "최신 Qwen 멀티모달 · 균형 추천", 16),
569
- ("mlx-community/gemma-4-12b-it-4bit", "Gemma 4 12B", 7.6, "VLM", "Gemma 4 기본 추천 · 4bit", 16),
570
- ("mlx-community/Llama-4-Scout-17B-16E-Instruct-4bit", "Llama 4 Scout", 11.8, "VLM", "Meta 최신 멀티모달 Scout", 24),
571
- ("mlx-community/gemma-4-26b-a4b-it-4bit", "Gemma 4 26B", 15.6, "VLM", "이미지 지원 · 대형 추천", 32),
572
- ("mlx-community/gemma-4-31b-it-4bit", "Gemma 4 31B", 18.4, "VLM+", "Gemma 4 최신 31B instruct", 48),
573
- ("mlx-community/Qwen3-VL-30B-A3B-Instruct-4bit", "Qwen3-VL 30B A3B", 18.0, "VLM+", "최신 Qwen 대형 멀티모달", 48),
570
+ ("mlx-community/LFM2.5-2.6B-4bit", "LFM2.5 2.6B", 1.5, "LLM", "가장 가벼움 · 한국어 대화 (사진은 못 읽음)", 4),
571
+ ("mlx-community/gemma-4-e2b-it-4bit", "Gemma 4 E2B", 3.6, "VLM", "사진을 읽는 가장 작은 모델", 8),
572
+ ("mlx-community/gemma-4-e4b-it-4bit", "Gemma 4 E4B", 5.2, "VLM", "E2B 보다 한 단계 위 · 여전히 가벼움", 10),
573
+ ("mlx-community/Qwen3.5-9B-MLX-4bit", "Qwen3.5 9B", 6.0, "VLM", "중형 멀티모달 · 균형 추천", 12),
574
+ ("mlx-community/gemma-4-12B-it-4bit", "Gemma 4 12B", 6.8, "VLM", "Gemma 4 기본 추천 · 4bit", 16),
575
+ ("mlx-community/gpt-oss-20b-MXFP4-Q8", "GPT-OSS 20B", 12.1, "LLM", "범용 · 다운로드 최다 (사진은 못 읽음)", 24),
576
+ ("mlx-community/gemma-4-26b-a4b-it-4bit", "Gemma 4 26B A4B", 15.4, "VLM", "MoE · 대형 추천", 32),
577
+ ("mlx-community/Qwen3.6-27B-4bit", "Qwen3.6 27B", 16.1, "VLM+", "dense 최상위 · 느리지만 일정함", 48),
578
+ ("mlx-community/gemma-4-31b-it-4bit", "Gemma 4 31B", 18.4, "VLM+", "Gemma 4 최대 모델", 48),
579
+ ("mlx-community/Qwen3.6-35B-A3B-4bit", "Qwen3.6 35B A3B", 20.4, "VLM+", "MoE 최상위", 48),
574
580
  ]
575
581
 
576
582
  _CROSS_PLATFORM_MODEL_CATALOG: Dict[str, List[Tuple[str, str, float, str, str, int]]] = {
577
583
  "ollama": [
578
- ("ollama:qwen3-vl:4b", "Qwen3-VL 4B", 2.7, "VLM", "Ollama 멀티모달 · 저사양", 4),
579
- ("ollama:qwen3-vl:8b", "Qwen3-VL 8B", 4.8, "VLM", "Ollama 멀티모달 · 균형 추천", 16),
584
+ ("ollama:hf.co/LiquidAI/LFM2.5-2.6B-GGUF:Q4_K_M", "LFM2.5 2.6B Q4", 1.7, "LLM", "가장 가벼움 · 한국어 대화", 4),
585
+ ("ollama:hf.co/ggml-org/gemma-4-E2B-it-GGUF:Q4_K_M", "Gemma 4 E2B Q4", 3.8, "VLM", "Hugging Face GGUF 기반 Gemma 4", 8),
586
+ ("ollama:hf.co/ggml-org/gemma-4-E4B-it-GGUF:Q4_K_M", "Gemma 4 E4B Q4", 5.4, "VLM", "Hugging Face GGUF 기반 Gemma 4", 10),
580
587
  ("ollama:hf.co/ggml-org/gemma-4-12B-it-GGUF:Q4_K_M", "Gemma 4 12B Q4", 7.9, "VLM", "Hugging Face GGUF 기반 Gemma 4", 16),
581
- ("ollama:hf.co/ggml-org/gemma-4-31B-it-GGUF:Q4_K_M", "Gemma 4 31B Q4", 18.7, "VLM+", "Hugging Face GGUF 기반 Gemma 4", 48),
582
- ("ollama:hf.co/ggml-org/Llama-4-Scout-17B-16E-Instruct-GGUF:Q4_K_M", "Llama 4 Scout Q4", 12.0, "VLM", "Meta 최신 멀티모달 Scout", 24),
588
+ ("ollama:hf.co/ggml-org/gpt-oss-20b-GGUF:Q4_K_M", "GPT-OSS 20B Q4", 12.5, "LLM", "범용 · 다운로드 최다", 24),
589
+ ("ollama:hf.co/ggml-org/gemma-4-26B-A4B-it-GGUF:Q4_K_M", "Gemma 4 26B Q4", 16.0, "VLM", "MoE · 대형 추천", 32),
590
+ ("ollama:hf.co/ggml-org/Qwen3.6-27B-GGUF:Q4_K_M", "Qwen3.6 27B Q4", 16.6, "VLM+", "dense 최상위", 48),
591
+ ("ollama:hf.co/ggml-org/gemma-4-31B-it-GGUF:Q4_K_M", "Gemma 4 31B Q4", 18.7, "VLM+", "Gemma 4 최대 모델", 48),
583
592
  ],
584
593
  "lmstudio": [
585
- ("lmstudio:Qwen/Qwen3-VL-4B-Instruct", "Qwen3-VL 4B", 2.7, "VLM", "LM Studio 멀티모달 · 저사양", 4),
586
- ("lmstudio:Qwen/Qwen3-VL-8B-Instruct", "Qwen3-VL 8B", 4.8, "VLM", "LM Studio 멀티모달 · 균형 추천", 16),
587
- ("lmstudio:ggml-org/gemma-4-12B-it-GGUF", "Gemma 4 12B 4-bit", 7.9, "VLM", "LM Studio GGUF Gemma 4", 16),
588
- ("lmstudio:ggml-org/gemma-4-31B-it-GGUF", "Gemma 4 31B 4-bit", 18.7, "VLM+", "LM Studio GGUF Gemma 4", 48),
589
- ("lmstudio:Qwen/Qwen3-VL-30B-A3B-Instruct", "Qwen3-VL 30B A3B", 18.0, "VLM+", "대형 Qwen 멀티모달 · 24GB+ VRAM 권장", 32),
590
- ("lmstudio:meta-llama/Llama-4-Scout-17B-16E-Instruct", "Llama 4 Scout", 12.0, "VLM", "Meta 최신 멀티모달 Scout", 24),
594
+ ("lmstudio:LiquidAI/LFM2.5-2.6B-GGUF", "LFM2.5 2.6B", 1.7, "LLM", "가장 가벼움 · 한국어 대화", 4),
595
+ ("lmstudio:ggml-org/gemma-4-E2B-it-GGUF", "Gemma 4 E2B", 3.8, "VLM", "LM Studio GGUF Gemma 4", 8),
596
+ ("lmstudio:ggml-org/gemma-4-E4B-it-GGUF", "Gemma 4 E4B", 5.4, "VLM", "LM Studio GGUF Gemma 4", 10),
597
+ ("lmstudio:ggml-org/gemma-4-12B-it-GGUF", "Gemma 4 12B", 7.9, "VLM", "LM Studio GGUF Gemma 4", 16),
598
+ ("lmstudio:ggml-org/gpt-oss-20b-GGUF", "GPT-OSS 20B", 12.5, "LLM", "범용 · 다운로드 최다", 24),
599
+ ("lmstudio:ggml-org/gemma-4-26B-A4B-it-GGUF", "Gemma 4 26B", 16.0, "VLM", "MoE · 대형 추천", 32),
600
+ ("lmstudio:ggml-org/Qwen3.6-27B-GGUF", "Qwen3.6 27B", 16.6, "VLM+", "dense 최상위", 48),
601
+ ("lmstudio:ggml-org/gemma-4-31B-it-GGUF", "Gemma 4 31B", 18.7, "VLM+", "Gemma 4 최대 모델", 48),
591
602
  ],
592
603
  "vllm": [
593
- ("vllm:Qwen/Qwen3-VL-4B-Instruct", "Qwen3-VL 4B", 2.7, "VLM", "내 컴퓨터 GPU 실행 도구 권장", 4),
594
- ("vllm:Qwen/Qwen3-VL-8B-Instruct", "Qwen3-VL 8B", 4.8, "VLM", "내 컴퓨터 NVIDIA 실행 도구 권장", 16),
595
- ("vllm:google/gemma-4-12b-it", "Gemma 4 12B", 7.6, "VLM", "Gemma 4 기본 추천 · 4bit", 16),
596
- ("vllm:Qwen/Qwen3-VL-30B-A3B-Instruct", "Qwen3-VL 30B A3B", 18.0, "VLM+", "대형 Qwen 멀티모달 · 24GB+ VRAM 권장", 32),
597
- ("vllm:suitch/gemma-4-31B-it-4bit", "Gemma 4 31B", 18.7, "VLM+", "Gemma 4 최신 31B instruct", 48),
598
- ("vllm:meta-llama/Llama-4-Scout-17B-16E-Instruct", "Llama 4 Scout", 12.0, "VLM", "Meta 최신 멀티모달 Scout", 24),
604
+ ("vllm:LiquidAI/LFM2.5-2.6B", "LFM2.5 2.6B", 5.2, "LLM", "가장 가벼움 · 한국어 대화", 8),
605
+ ("vllm:google/gemma-4-E2B-it", "Gemma 4 E2B", 6.0, "VLM", "내 컴퓨터 GPU 실행 도구 권장", 12),
606
+ ("vllm:google/gemma-4-E4B-it", "Gemma 4 E4B", 9.0, "VLM", "내 컴퓨터 GPU 실행 도구 권장", 16),
607
+ ("vllm:Qwen/Qwen3.5-9B", "Qwen3.5 9B", 18.0, "VLM", "중형 멀티모달 · 균형 추천", 24),
608
+ ("vllm:google/gemma-4-12B-it", "Gemma 4 12B", 24.0, "VLM", "Gemma 4 기본 추천", 32),
609
+ ("vllm:openai/gpt-oss-20b", "GPT-OSS 20B", 13.5, "LLM", "범용 · 다운로드 최다", 24),
610
+ ("vllm:Qwen/Qwen3.6-27B", "Qwen3.6 27B", 54.0, "VLM+", "dense 최상위 · 24GB+ VRAM 권장", 64),
611
+ ("vllm:Qwen/Qwen3.6-35B-A3B", "Qwen3.6 35B A3B", 70.0, "VLM+", "MoE 최상위 · 24GB+ VRAM 권장", 80),
599
612
  ],
600
613
  "llamacpp": [
601
- ("llamacpp:Qwen/Qwen3-VL-4B-Instruct-GGUF", "Qwen3-VL 4B GGUF", 2.7, "GGUF", "CPU/Vulkan 백업 · 멀티모달 GGUF", 4),
602
- ("llamacpp:Qwen/Qwen3-VL-8B-Instruct-GGUF", "Qwen3-VL 8B GGUF", 4.8, "GGUF", "CPU/Vulkan 백업 · 균형형", 16),
614
+ ("llamacpp:LiquidAI/LFM2.5-2.6B-GGUF", "LFM2.5 2.6B GGUF", 1.7, "GGUF", "CPU/Vulkan 백업 · 가장 가벼움", 4),
615
+ ("llamacpp:ggml-org/gemma-4-E2B-it-GGUF", "Gemma 4 E2B GGUF", 3.8, "GGUF", "Gemma 4 E2B Q4_K_M", 8),
616
+ ("llamacpp:ggml-org/gemma-4-E4B-it-GGUF", "Gemma 4 E4B GGUF", 5.4, "GGUF", "Gemma 4 E4B Q4_K_M", 10),
603
617
  ("llamacpp:ggml-org/gemma-4-12B-it-GGUF", "Gemma 4 12B GGUF", 7.9, "GGUF", "Gemma 4 12B Q4_K_M", 16),
618
+ ("llamacpp:ggml-org/gpt-oss-20b-GGUF", "GPT-OSS 20B GGUF", 12.5, "GGUF", "범용 · 다운로드 최다", 24),
619
+ ("llamacpp:ggml-org/gemma-4-26B-A4B-it-GGUF", "Gemma 4 26B GGUF", 16.0, "GGUF", "MoE · 대형 추천", 32),
620
+ ("llamacpp:ggml-org/Qwen3.6-27B-GGUF", "Qwen3.6 27B GGUF", 16.6, "GGUF", "dense 최상위", 48),
604
621
  ("llamacpp:ggml-org/gemma-4-31B-it-GGUF", "Gemma 4 31B GGUF", 18.7, "GGUF", "Gemma 4 31B Q4_K_M", 48),
605
- ("llamacpp:ggml-org/Llama-4-Scout-17B-16E-Instruct-GGUF", "Llama 4 Scout GGUF", 12.0, "GGUF", "Meta 최신 멀티모달 Scout", 24),
606
622
  ],
607
623
  }
608
624
 
@@ -612,37 +628,51 @@ _VERSIONED_MODEL_PATTERNS = (
612
628
  ("llama", re.compile(r"\bllama[-\s]?(\d+(?:\.\d+)?)", re.IGNORECASE)),
613
629
  )
614
630
 
631
+ # RAM(GB) 내림차순 티어. 첫 번째로 조건을 만족하는 항목이 "이 컴퓨터에서 가장
632
+ # 좋은 모델"이 된다. 초경량(≤8) / 경량(16) / 중형(24) / MoE(32) / 대형(48).
615
633
  _BEST_MODEL_TIERS: Dict[str, List[Tuple[int, str]]] = {
616
634
  "local_mlx": [
617
635
  (48, "mlx-community/gemma-4-31b-it-4bit"),
618
636
  (32, "mlx-community/gemma-4-26b-a4b-it-4bit"),
619
- (16, "mlx-community/gemma-4-12b-it-4bit"),
620
- (16, "mlx-community/Qwen3-VL-8B-Instruct-4bit"),
621
- (4, "mlx-community/Qwen3-VL-4B-Instruct-4bit"),
637
+ (24, "mlx-community/gemma-4-12B-it-4bit"),
638
+ (16, "mlx-community/Qwen3.5-9B-MLX-4bit"),
639
+ (8, "mlx-community/gemma-4-e2b-it-4bit"),
640
+ (4, "mlx-community/LFM2.5-2.6B-4bit"),
622
641
  ],
623
642
  "ollama": [
624
643
  (48, "ollama:hf.co/ggml-org/gemma-4-31B-it-GGUF:Q4_K_M"),
625
- (16, "ollama:hf.co/ggml-org/gemma-4-12B-it-GGUF:Q4_K_M"),
626
- (16, "ollama:qwen3-vl:8b"),
627
- (4, "ollama:qwen3-vl:4b"),
644
+ (32, "ollama:hf.co/ggml-org/gemma-4-26B-A4B-it-GGUF:Q4_K_M"),
645
+ (24, "ollama:hf.co/ggml-org/gemma-4-12B-it-GGUF:Q4_K_M"),
646
+ (16, "ollama:hf.co/ggml-org/gemma-4-E4B-it-GGUF:Q4_K_M"),
647
+ (8, "ollama:hf.co/ggml-org/gemma-4-E2B-it-GGUF:Q4_K_M"),
648
+ (4, "ollama:hf.co/LiquidAI/LFM2.5-2.6B-GGUF:Q4_K_M"),
628
649
  ],
629
650
  "lmstudio": [
630
651
  (48, "lmstudio:ggml-org/gemma-4-31B-it-GGUF"),
631
- (16, "lmstudio:ggml-org/gemma-4-12B-it-GGUF"),
632
- (16, "lmstudio:Qwen/Qwen3-VL-8B-Instruct"),
633
- (4, "lmstudio:Qwen/Qwen3-VL-4B-Instruct"),
652
+ (32, "lmstudio:ggml-org/gemma-4-26B-A4B-it-GGUF"),
653
+ (24, "lmstudio:ggml-org/gemma-4-12B-it-GGUF"),
654
+ (16, "lmstudio:ggml-org/gemma-4-E4B-it-GGUF"),
655
+ (8, "lmstudio:ggml-org/gemma-4-E2B-it-GGUF"),
656
+ (4, "lmstudio:LiquidAI/LFM2.5-2.6B-GGUF"),
634
657
  ],
658
+ # vLLM serves the upstream bf16 repos, which are 3-4x the MLX 4-bit builds
659
+ # (Qwen3.6-27B is 54GB, not 16GB), so its thresholds sit far higher than the
660
+ # local_mlx ones. Picking by RAM alone would nominate a model no consumer
661
+ # GPU can hold.
635
662
  "vllm": [
636
- (48, "vllm:suitch/gemma-4-31B-it-4bit"),
637
- (16, "vllm:google/gemma-4-12b-it"),
638
- (16, "vllm:Qwen/Qwen3-VL-8B-Instruct"),
639
- (4, "vllm:Qwen/Qwen3-VL-4B-Instruct"),
663
+ (96, "vllm:Qwen/Qwen3.6-27B"),
664
+ (32, "vllm:google/gemma-4-12B-it"),
665
+ (24, "vllm:Qwen/Qwen3.5-9B"),
666
+ (16, "vllm:google/gemma-4-E4B-it"),
667
+ (8, "vllm:LiquidAI/LFM2.5-2.6B"),
640
668
  ],
641
669
  "llamacpp": [
642
670
  (48, "llamacpp:ggml-org/gemma-4-31B-it-GGUF"),
643
- (16, "llamacpp:ggml-org/gemma-4-12B-it-GGUF"),
644
- (16, "llamacpp:Qwen/Qwen3-VL-8B-Instruct-GGUF"),
645
- (4, "llamacpp:Qwen/Qwen3-VL-4B-Instruct-GGUF"),
671
+ (32, "llamacpp:ggml-org/gemma-4-26B-A4B-it-GGUF"),
672
+ (24, "llamacpp:ggml-org/gemma-4-12B-it-GGUF"),
673
+ (16, "llamacpp:ggml-org/gemma-4-E4B-it-GGUF"),
674
+ (8, "llamacpp:ggml-org/gemma-4-E2B-it-GGUF"),
675
+ (4, "llamacpp:LiquidAI/LFM2.5-2.6B-GGUF"),
646
676
  ],
647
677
  }
648
678
 
@@ -656,7 +686,10 @@ def _catalog_row_family_version(row: Tuple[str, str, float, str, str, int]) -> T
656
686
  for family, pattern in _VERSIONED_MODEL_PATTERNS:
657
687
  match = pattern.search(text)
658
688
  if match:
659
- version = _version_tuple(match.group(1))
689
+ # Major version only — see model_catalog._model_family_version. The
690
+ # filter hides superseded *generations*; Qwen3.5 and Qwen3.6 are one
691
+ # generation filling two different RAM tiers and must coexist.
692
+ version = _version_tuple(match.group(1))[:1]
660
693
  if version:
661
694
  return family, version
662
695
  return None
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ltcai",
3
- "version": "11.0.1",
3
+ "version": "11.2.0",
4
4
  "description": "Lattice AI — local-first Digital Brain that keeps your knowledge durable across any AI model.",
5
5
  "homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
6
6
  "repository": {
@@ -6,7 +6,7 @@ const root = process.cwd();
6
6
  const pkg = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8"));
7
7
  const version = pkg.version;
8
8
  const releaseDir = `output/release/v${version}`;
9
- const releaseTheme = "Both Branches";
9
+ const releaseTheme = "All Systems On";
10
10
  const title = `${version} — ${releaseTheme}`;
11
11
  const escapedVersion = version.replaceAll(".", "\\.");
12
12
 
@@ -34,6 +34,7 @@ const LOCALIZED = [
34
34
  "chat",
35
35
  "chat_history",
36
36
  "chat_intents",
37
+ "features",
37
38
  "knowledge_graph",
38
39
  "local_files",
39
40
  "mcp",
@@ -21,6 +21,28 @@
21
21
  "specific, not less."
22
22
  ],
23
23
 
24
+ "11.2.0": {
25
+ "04-brain-chat-home.png": {
26
+ "_why": [
27
+ "The dock rail gained a fourth item (기능) opening the feature-toggle",
28
+ "drawer. The rail change itself is small; the LivingBrain animation",
29
+ "noise threshold (8%) may exceed it, so the screen is claimed with the",
30
+ "rail region only."
31
+ ],
32
+ "region": [0.0, 0.25, 0.12, 0.85],
33
+ "min_pct": 0.5
34
+ }
35
+ },
36
+
37
+ "11.1.0": {
38
+ "_why": [
39
+ "Feature release with no intentional change to the twelve captured",
40
+ "screens: the new surfaces (proposals, thumbnails) render inside",
41
+ "existing panels and the capture corpus carries no image citations.",
42
+ "All twelve measured but not gated."
43
+ ]
44
+ },
45
+
24
46
  "11.0.1": {
25
47
  "_why": [
26
48
  "Defect-fix and branch-coverage release: no intentional UI change.",