superlocalmemory 3.4.19 → 3.4.22

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 (177) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +42 -34
  3. package/bin/slm +11 -0
  4. package/bin/slm.bat +12 -0
  5. package/package.json +4 -3
  6. package/pyproject.toml +4 -3
  7. package/scripts/build-slm-hook.ps1 +40 -0
  8. package/scripts/build-slm-hook.sh +45 -0
  9. package/scripts/build_entry.py +452 -0
  10. package/scripts/ci/stage5b_gate.sh +50 -0
  11. package/scripts/postinstall/validation.js +187 -0
  12. package/scripts/postinstall-interactive.js +756 -0
  13. package/scripts/postinstall_binary.js +287 -0
  14. package/scripts/release_manifest.py +273 -0
  15. package/scripts/slm-hook.spec +56 -0
  16. package/skills/slm-build-graph/SKILL.md +423 -0
  17. package/skills/slm-list-recent/SKILL.md +348 -0
  18. package/skills/slm-recall/SKILL.md +343 -0
  19. package/skills/slm-remember/SKILL.md +194 -0
  20. package/skills/slm-show-patterns/SKILL.md +224 -0
  21. package/skills/slm-status/SKILL.md +363 -0
  22. package/skills/slm-switch-profile/SKILL.md +442 -0
  23. package/src/superlocalmemory/cli/commands.py +254 -79
  24. package/src/superlocalmemory/cli/context_commands.py +192 -0
  25. package/src/superlocalmemory/cli/daemon.py +15 -1
  26. package/src/superlocalmemory/cli/db_migrate.py +80 -0
  27. package/src/superlocalmemory/cli/escape_hatch.py +220 -0
  28. package/src/superlocalmemory/cli/main.py +72 -1
  29. package/src/superlocalmemory/core/context_cache.py +397 -0
  30. package/src/superlocalmemory/core/engine.py +38 -2
  31. package/src/superlocalmemory/core/engine_wiring.py +1 -1
  32. package/src/superlocalmemory/core/ram_lock.py +111 -0
  33. package/src/superlocalmemory/core/recall_pipeline.py +433 -3
  34. package/src/superlocalmemory/core/recall_worker.py +8 -3
  35. package/src/superlocalmemory/core/security_primitives.py +635 -0
  36. package/src/superlocalmemory/core/shadow_router.py +319 -0
  37. package/src/superlocalmemory/core/slm_disabled.py +87 -0
  38. package/src/superlocalmemory/core/slmignore.py +125 -0
  39. package/src/superlocalmemory/core/topic_signature.py +143 -0
  40. package/src/superlocalmemory/core/worker_pool.py +14 -3
  41. package/src/superlocalmemory/encoding/cognitive_consolidator.py +2 -2
  42. package/src/superlocalmemory/evolution/budget.py +321 -0
  43. package/src/superlocalmemory/evolution/llm_dispatch.py +508 -0
  44. package/src/superlocalmemory/evolution/skill_evolver.py +144 -94
  45. package/src/superlocalmemory/hooks/_outcome_common.py +506 -0
  46. package/src/superlocalmemory/hooks/adapter_base.py +317 -0
  47. package/src/superlocalmemory/hooks/antigravity_adapter.py +192 -0
  48. package/src/superlocalmemory/hooks/claude_code_hooks.py +33 -1
  49. package/src/superlocalmemory/hooks/context_payload.py +312 -0
  50. package/src/superlocalmemory/hooks/copilot_adapter.py +154 -0
  51. package/src/superlocalmemory/hooks/cross_platform_connector.py +90 -0
  52. package/src/superlocalmemory/hooks/cursor_adapter.py +195 -0
  53. package/src/superlocalmemory/hooks/hook_handlers.py +109 -8
  54. package/src/superlocalmemory/hooks/ide_connector.py +25 -2
  55. package/src/superlocalmemory/hooks/post_tool_async_hook.py +165 -0
  56. package/src/superlocalmemory/hooks/post_tool_outcome_hook.py +223 -0
  57. package/src/superlocalmemory/hooks/prewarm_auth.py +170 -0
  58. package/src/superlocalmemory/hooks/session_registry.py +186 -0
  59. package/src/superlocalmemory/hooks/stop_outcome_hook.py +134 -0
  60. package/src/superlocalmemory/hooks/sync_loop.py +114 -0
  61. package/src/superlocalmemory/hooks/user_prompt_hook.py +128 -0
  62. package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +202 -0
  63. package/src/superlocalmemory/infra/backup.py +3 -3
  64. package/src/superlocalmemory/infra/cloud_backup.py +2 -2
  65. package/src/superlocalmemory/infra/event_bus.py +2 -2
  66. package/src/superlocalmemory/infra/webhook_dispatcher.py +3 -3
  67. package/src/superlocalmemory/learning/arm_catalog.py +99 -0
  68. package/src/superlocalmemory/learning/bandit.py +526 -0
  69. package/src/superlocalmemory/learning/bandit_cache.py +133 -0
  70. package/src/superlocalmemory/learning/behavioral.py +53 -1
  71. package/src/superlocalmemory/learning/consolidation_cycle.py +381 -0
  72. package/src/superlocalmemory/learning/consolidation_worker.py +188 -520
  73. package/src/superlocalmemory/learning/database.py +256 -0
  74. package/src/superlocalmemory/learning/dedup_hnsw.py +413 -0
  75. package/src/superlocalmemory/learning/ensemble.py +300 -0
  76. package/src/superlocalmemory/learning/fact_outcome_joins.py +207 -0
  77. package/src/superlocalmemory/learning/forgetting_scheduler.py +55 -0
  78. package/src/superlocalmemory/learning/hnsw_dedup.py +69 -0
  79. package/src/superlocalmemory/learning/labeler.py +87 -0
  80. package/src/superlocalmemory/learning/legacy_migration.py +277 -0
  81. package/src/superlocalmemory/learning/memory_merge.py +160 -0
  82. package/src/superlocalmemory/learning/model_cache.py +269 -0
  83. package/src/superlocalmemory/learning/model_rollback.py +278 -0
  84. package/src/superlocalmemory/learning/outcome_queue.py +284 -0
  85. package/src/superlocalmemory/learning/pattern_miner.py +415 -0
  86. package/src/superlocalmemory/learning/pattern_miner_constants.py +47 -0
  87. package/src/superlocalmemory/learning/ranker.py +225 -81
  88. package/src/superlocalmemory/learning/ranker_common.py +163 -0
  89. package/src/superlocalmemory/learning/ranker_retrain_legacy.py +202 -0
  90. package/src/superlocalmemory/learning/ranker_retrain_online.py +411 -0
  91. package/src/superlocalmemory/learning/reward.py +777 -0
  92. package/src/superlocalmemory/learning/reward_archive.py +210 -0
  93. package/src/superlocalmemory/learning/reward_boost.py +201 -0
  94. package/src/superlocalmemory/learning/reward_proxy.py +326 -0
  95. package/src/superlocalmemory/learning/shadow_test.py +524 -0
  96. package/src/superlocalmemory/learning/signal_worker.py +270 -0
  97. package/src/superlocalmemory/learning/signals.py +314 -0
  98. package/src/superlocalmemory/learning/trigram_index.py +547 -0
  99. package/src/superlocalmemory/mcp/server.py +5 -5
  100. package/src/superlocalmemory/mcp/tools_context.py +183 -0
  101. package/src/superlocalmemory/mcp/tools_core.py +92 -27
  102. package/src/superlocalmemory/parameterization/soft_prompt_generator.py +13 -0
  103. package/src/superlocalmemory/retrieval/engine.py +52 -0
  104. package/src/superlocalmemory/server/api.py +2 -2
  105. package/src/superlocalmemory/server/bandit_loops.py +140 -0
  106. package/src/superlocalmemory/server/middleware/__init__.py +11 -0
  107. package/src/superlocalmemory/server/middleware/security_headers.py +144 -0
  108. package/src/superlocalmemory/server/routes/backup.py +36 -13
  109. package/src/superlocalmemory/server/routes/behavioral.py +50 -19
  110. package/src/superlocalmemory/server/routes/brain.py +1234 -0
  111. package/src/superlocalmemory/server/routes/data_io.py +4 -4
  112. package/src/superlocalmemory/server/routes/events.py +2 -2
  113. package/src/superlocalmemory/server/routes/helpers.py +1 -1
  114. package/src/superlocalmemory/server/routes/learning.py +192 -7
  115. package/src/superlocalmemory/server/routes/memories.py +189 -1
  116. package/src/superlocalmemory/server/routes/prewarm.py +171 -0
  117. package/src/superlocalmemory/server/routes/profiles.py +3 -3
  118. package/src/superlocalmemory/server/routes/token.py +88 -0
  119. package/src/superlocalmemory/server/routes/ws.py +5 -5
  120. package/src/superlocalmemory/server/security_middleware.py +13 -7
  121. package/src/superlocalmemory/server/ui.py +2 -2
  122. package/src/superlocalmemory/server/unified_daemon.py +335 -3
  123. package/src/superlocalmemory/skills/slm-build-graph/SKILL.md +423 -0
  124. package/src/superlocalmemory/skills/slm-list-recent/SKILL.md +348 -0
  125. package/src/superlocalmemory/skills/slm-recall/SKILL.md +343 -0
  126. package/src/superlocalmemory/skills/slm-remember/SKILL.md +194 -0
  127. package/src/superlocalmemory/skills/slm-show-patterns/SKILL.md +224 -0
  128. package/src/superlocalmemory/skills/slm-status/SKILL.md +363 -0
  129. package/src/superlocalmemory/skills/slm-switch-profile/SKILL.md +442 -0
  130. package/src/superlocalmemory/storage/migration_runner.py +545 -0
  131. package/src/superlocalmemory/storage/migrations/M001_add_signal_features_columns.py +67 -0
  132. package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +132 -0
  133. package/src/superlocalmemory/storage/migrations/M003_migration_log.py +38 -0
  134. package/src/superlocalmemory/storage/migrations/M004_cross_platform_sync_log.py +46 -0
  135. package/src/superlocalmemory/storage/migrations/M005_bandit_tables.py +75 -0
  136. package/src/superlocalmemory/storage/migrations/M006_action_outcomes_reward.py +75 -0
  137. package/src/superlocalmemory/storage/migrations/M007_pending_outcomes.py +63 -0
  138. package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +54 -0
  139. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +75 -0
  140. package/src/superlocalmemory/storage/migrations/M011_archive_and_merge.py +87 -0
  141. package/src/superlocalmemory/storage/migrations/M012_shadow_observations.py +72 -0
  142. package/src/superlocalmemory/storage/migrations/M013_bi_temporal_columns.py +55 -0
  143. package/src/superlocalmemory/storage/migrations/__init__.py +81 -0
  144. package/src/superlocalmemory/storage/models.py +4 -0
  145. package/src/superlocalmemory/ui/css/brain.css +409 -0
  146. package/src/superlocalmemory/ui/css/legacy-dashboard.css +645 -0
  147. package/src/superlocalmemory/ui/index.html +459 -1345
  148. package/src/superlocalmemory/ui/js/brain.js +1321 -0
  149. package/src/superlocalmemory/ui/js/clusters.js +123 -4
  150. package/src/superlocalmemory/ui/js/init.js +48 -39
  151. package/src/superlocalmemory/ui/js/memories.js +88 -2
  152. package/src/superlocalmemory/ui/js/modal.js +71 -1
  153. package/src/superlocalmemory/ui/js/ng-shell.js +101 -88
  154. package/src/superlocalmemory/ui/js/trust-dashboard.js +168 -25
  155. package/src/superlocalmemory/ui/vendor/bootstrap-icons/bootstrap-icons.css +2018 -0
  156. package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
  157. package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
  158. package/src/superlocalmemory/ui/vendor/bootstrap.bundle.min.js +7 -0
  159. package/src/superlocalmemory/ui/vendor/bootstrap.min.css +6 -0
  160. package/src/superlocalmemory/ui/vendor/d3.v7.min.js +2 -0
  161. package/src/superlocalmemory/ui/vendor/graphology-library.min.js +2 -0
  162. package/src/superlocalmemory/ui/vendor/graphology.umd.min.js +2 -0
  163. package/src/superlocalmemory/ui/vendor/inter-ui/inter-variable.min.css +8 -0
  164. package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable-Italic.woff2 +0 -0
  165. package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable.woff2 +0 -0
  166. package/src/superlocalmemory/ui/vendor/sigma.min.js +1 -0
  167. package/src/superlocalmemory/ui/js/behavioral.js +0 -447
  168. package/src/superlocalmemory/ui/js/graph-core.js +0 -447
  169. package/src/superlocalmemory/ui/js/graph-interactions.js +0 -351
  170. package/src/superlocalmemory/ui/js/learning.js +0 -435
  171. package/src/superlocalmemory/ui/js/patterns.js +0 -93
  172. package/src/superlocalmemory.egg-info/PKG-INFO +0 -647
  173. package/src/superlocalmemory.egg-info/SOURCES.txt +0 -335
  174. package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
  175. package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
  176. package/src/superlocalmemory.egg-info/requires.txt +0 -58
  177. package/src/superlocalmemory.egg-info/top_level.txt +0 -1
@@ -0,0 +1,72 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+ # Part of SuperLocalMemory v3.4.22
4
+
5
+ """M012 — shadow_observations table (learning.db).
6
+
7
+ Persists the paired NDCG@10 observations accumulated by
8
+ ``ShadowTest`` so a daemon restart during an in-flight shadow test
9
+ does NOT throw away the 6-hour observation window. Without this
10
+ table the candidate_id was re-attached on restart (Stage 9 W4
11
+ H-ARC-01) but the observation list restarted from zero.
12
+
13
+ Schema (additive only; no existing tables touched):
14
+
15
+ shadow_observations(
16
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
17
+ profile_id TEXT NOT NULL,
18
+ candidate_id INTEGER NOT NULL,
19
+ query_id TEXT NOT NULL,
20
+ arm TEXT NOT NULL CHECK (arm IN ('active','candidate')),
21
+ ndcg_at_10 REAL NOT NULL,
22
+ recorded_at TEXT NOT NULL,
23
+ UNIQUE(candidate_id, query_id, arm)
24
+ )
25
+
26
+ The ``UNIQUE(candidate_id, query_id, arm)`` constraint means a
27
+ query_id is scored at most once per arm per candidate — if the
28
+ daemon crashes mid-write and restarts, re-ingesting the same
29
+ observation is an INSERT-OR-IGNORE no-op.
30
+
31
+ Author: Varun Pratap Bhardwaj / Qualixar
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import sqlite3
37
+
38
+ NAME = "M012_shadow_observations"
39
+ DB_TARGET = "learning"
40
+
41
+ _REQUIRED_TABLES = frozenset({"shadow_observations"})
42
+
43
+
44
+ def verify(conn: sqlite3.Connection) -> bool:
45
+ try:
46
+ names = {
47
+ r[0]
48
+ for r in conn.execute(
49
+ "SELECT name FROM sqlite_master WHERE type='table'"
50
+ ).fetchall()
51
+ }
52
+ except sqlite3.Error:
53
+ return False
54
+ return _REQUIRED_TABLES.issubset(names)
55
+
56
+
57
+ DDL = """
58
+ CREATE TABLE IF NOT EXISTS shadow_observations (
59
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
60
+ profile_id TEXT NOT NULL,
61
+ candidate_id INTEGER NOT NULL,
62
+ query_id TEXT NOT NULL,
63
+ arm TEXT NOT NULL,
64
+ ndcg_at_10 REAL NOT NULL,
65
+ recorded_at TEXT NOT NULL,
66
+ UNIQUE(candidate_id, query_id, arm)
67
+ );
68
+ CREATE INDEX IF NOT EXISTS idx_shadow_obs_candidate
69
+ ON shadow_observations(candidate_id, recorded_at);
70
+ CREATE INDEX IF NOT EXISTS idx_shadow_obs_profile
71
+ ON shadow_observations(profile_id, recorded_at);
72
+ """
@@ -0,0 +1,55 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+ # Part of SuperLocalMemory v3.4.22
4
+
5
+ """M013 — bi-temporal columns on ``atomic_facts`` (memory.db, deferred).
6
+
7
+ Adds two NULLable columns that let SLM capture fact validity windows
8
+ WITHOUT changing the retrieval path today. Wiring the retrieval
9
+ consumer is a later-cycle item; what we ship in v3.4.22 is the
10
+ data-capture surface so existing user memories start accumulating
11
+ temporal metadata on every new fact.
12
+
13
+ Columns (both nullable, no default so existing rows stay NULL):
14
+
15
+ valid_from TEXT — ISO-8601 instant from which the fact is
16
+ considered valid. NULL ⇒ "valid since
17
+ creation" (the current default semantics).
18
+ valid_until TEXT — ISO-8601 instant after which the fact is
19
+ considered superseded. NULL ⇒ still valid.
20
+
21
+ Deferred like M006 and M011 because ``atomic_facts`` is bootstrapped
22
+ at engine init, not at migration time. Daemon lifespan calls
23
+ ``apply_deferred`` right after engine init so these columns materialise
24
+ on first boot after upgrade.
25
+
26
+ Author: Varun Pratap Bhardwaj / Qualixar
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import sqlite3
32
+
33
+ NAME = "M013_bi_temporal_columns"
34
+ DB_TARGET = "memory"
35
+
36
+ _REQUIRED_COLS = frozenset({"valid_from", "valid_until"})
37
+
38
+
39
+ def verify(conn: sqlite3.Connection) -> bool:
40
+ try:
41
+ cols = {
42
+ r[1]
43
+ for r in conn.execute(
44
+ "PRAGMA table_info(atomic_facts)"
45
+ ).fetchall()
46
+ }
47
+ except sqlite3.Error:
48
+ return False
49
+ return _REQUIRED_COLS.issubset(cols)
50
+
51
+
52
+ DDL = """
53
+ ALTER TABLE atomic_facts ADD COLUMN valid_from TEXT;
54
+ ALTER TABLE atomic_facts ADD COLUMN valid_until TEXT;
55
+ """
@@ -0,0 +1,81 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+ # Part of SuperLocalMemory v3.4.22 — LLD-07
4
+
5
+ """v3.4.22 migration definitions.
6
+
7
+ One module per migration. Each exposes:
8
+ - ``NAME``: canonical migration name (used in ``migration_log``)
9
+ - ``DB_TARGET``: either ``"learning"`` or ``"memory"``
10
+ - ``DDL``: the full DDL string including ``BEGIN IMMEDIATE`` / ``COMMIT``
11
+ (where the migration warrants a transaction; see each module).
12
+
13
+ The ordered list here is the authoritative order for ``migration_runner``.
14
+ M003 must run first (bootstraps the log table); the others follow in the
15
+ order LLD-07 §4 specifies.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from . import (
21
+ M001_add_signal_features_columns,
22
+ M002_model_state_history,
23
+ M003_migration_log,
24
+ M004_cross_platform_sync_log,
25
+ M005_bandit_tables,
26
+ )
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Backward-compat shim: the legacy flat module
30
+ # ``superlocalmemory/storage/migrations.py`` shipped in v3.4.20 carried
31
+ # ``CURRENT_SCHEMA_VERSION`` / ``get_schema_version`` / ``set_schema_version``
32
+ # / ``is_v1_database`` / ``needs_migration`` / ``backup_database``. Creating
33
+ # the ``migrations/`` package in Wave 1 shadowed that flat module, so any
34
+ # caller that did ``from superlocalmemory.storage.migrations import X``
35
+ # broke. We re-load the legacy module under a distinct name and re-export
36
+ # the symbols here so pre-3.4.22 callers (and the pre-existing tests at
37
+ # ``tests/test_storage/test_migrations.py``) keep working unchanged.
38
+ # ---------------------------------------------------------------------------
39
+
40
+
41
+ def _load_legacy_module(): # pragma: no cover — trivial file loader
42
+ import importlib.util
43
+ from pathlib import Path as _Path
44
+
45
+ legacy_path = _Path(__file__).resolve().parents[1] / "migrations.py"
46
+ if not legacy_path.is_file():
47
+ return None
48
+ spec = importlib.util.spec_from_file_location(
49
+ "superlocalmemory.storage._legacy_migrations_flat",
50
+ str(legacy_path),
51
+ )
52
+ if spec is None or spec.loader is None:
53
+ return None
54
+ mod = importlib.util.module_from_spec(spec)
55
+ spec.loader.exec_module(mod)
56
+ return mod
57
+
58
+
59
+ _legacy = _load_legacy_module()
60
+ if _legacy is not None:
61
+ CURRENT_SCHEMA_VERSION = getattr(_legacy, "CURRENT_SCHEMA_VERSION", 1)
62
+ get_schema_version = getattr(_legacy, "get_schema_version", None)
63
+ set_schema_version = getattr(_legacy, "set_schema_version", None)
64
+ is_v1_database = getattr(_legacy, "is_v1_database", None)
65
+ needs_migration = getattr(_legacy, "needs_migration", None)
66
+ backup_database = getattr(_legacy, "backup_database", None)
67
+
68
+ __all__ = (
69
+ "M001_add_signal_features_columns",
70
+ "M002_model_state_history",
71
+ "M003_migration_log",
72
+ "M004_cross_platform_sync_log",
73
+ "M005_bandit_tables",
74
+ # Legacy re-exports (backward compat):
75
+ "CURRENT_SCHEMA_VERSION",
76
+ "get_schema_version",
77
+ "set_schema_version",
78
+ "is_v1_database",
79
+ "needs_migration",
80
+ "backup_database",
81
+ )
@@ -391,6 +391,10 @@ class RetrievalResult:
391
391
  confidence: float = 0.0
392
392
  evidence_chain: list[str] = field(default_factory=list)
393
393
  trust_score: float = 0.5
394
+ # LLD-00 §3 + P0.4: HMAC marker emitted during recall so post-tool hooks
395
+ # can validate that a fact_id observed in tool output really came from
396
+ # this install. Empty string by default preserves backward-compat.
397
+ marker: str = ""
394
398
 
395
399
 
396
400
  @dataclass
@@ -0,0 +1,409 @@
1
+ /*
2
+ * Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
3
+ * Licensed under AGPL-3.0-or-later - see LICENSE file
4
+ * Part of SuperLocalMemory v3.4.21 — LLD-04 §4.5
5
+ *
6
+ * Brain tab styles. Pure design-system variables — no inline styles
7
+ * anywhere in the UI, so CSP `style-src 'self'` can stay strict.
8
+ */
9
+
10
+ .brain-section {
11
+ background: var(--ng-glass-bg, rgba(255, 255, 255, 0.04));
12
+ border-radius: 12px;
13
+ padding: 20px;
14
+ margin-bottom: 16px;
15
+ }
16
+
17
+ .brain-section h4 {
18
+ font-size: 0.875rem;
19
+ text-transform: uppercase;
20
+ letter-spacing: 0.06em;
21
+ color: var(--ng-text-tertiary, rgba(148, 163, 184, 0.9));
22
+ margin-bottom: 0.75rem;
23
+ }
24
+
25
+ .brain-list {
26
+ list-style: none;
27
+ padding: 0;
28
+ margin: 0;
29
+ }
30
+
31
+ .brain-list li {
32
+ padding: 4px 0;
33
+ border-bottom: 1px solid var(--ng-border-subtle, rgba(148, 163, 184, 0.08));
34
+ }
35
+
36
+ .brain-list li.brain-empty {
37
+ color: var(--ng-text-tertiary, rgba(148, 163, 184, 0.7));
38
+ font-style: italic;
39
+ }
40
+
41
+ .brain-notice {
42
+ margin-top: 8px;
43
+ font-size: 0.8125rem;
44
+ color: var(--ng-warn, rgb(202, 138, 4));
45
+ }
46
+
47
+ .brain-honesty-badge {
48
+ display: inline-block;
49
+ font-size: 0.6875rem;
50
+ padding: 2px 8px;
51
+ border-radius: 10px;
52
+ margin-top: 6px;
53
+ }
54
+
55
+ .brain-honesty-badge.real {
56
+ background: rgba(34, 197, 94, 0.12);
57
+ color: rgb(22, 163, 74);
58
+ }
59
+
60
+ .brain-honesty-badge.counter {
61
+ background: rgba(234, 179, 8, 0.12);
62
+ color: rgb(202, 138, 4);
63
+ }
64
+
65
+ .brain-honesty-badge.soon {
66
+ background: rgba(148, 163, 184, 0.15);
67
+ color: rgb(100, 116, 139);
68
+ }
69
+
70
+ .brain-phase-pill {
71
+ display: inline-block;
72
+ padding: 4px 12px;
73
+ border-radius: 999px;
74
+ background: rgba(148, 163, 184, 0.15);
75
+ font-size: 0.8125rem;
76
+ }
77
+
78
+ .brain-phase-pill.active {
79
+ background: var(--ng-accent, rgb(59, 130, 246));
80
+ color: #fff;
81
+ }
82
+
83
+ .brain-header {
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: space-between;
87
+ margin-bottom: 16px;
88
+ }
89
+
90
+ .brain-error {
91
+ padding: 16px;
92
+ background: rgba(239, 68, 68, 0.08);
93
+ color: rgb(220, 38, 38);
94
+ border-radius: 8px;
95
+ margin-bottom: 12px;
96
+ }
97
+
98
+ .brain-subhead {
99
+ font-size: 0.75rem;
100
+ text-transform: uppercase;
101
+ letter-spacing: 0.06em;
102
+ color: var(--ng-text-tertiary, rgba(148, 163, 184, 0.85));
103
+ margin: 16px 0 8px 0;
104
+ }
105
+
106
+ .brain-help {
107
+ font-size: 0.8125rem;
108
+ color: var(--ng-text-tertiary, rgba(148, 163, 184, 0.9));
109
+ margin-bottom: 12px;
110
+ }
111
+
112
+ /* Key-value stat grid (Learning, Bandit, Cache) */
113
+ .brain-stat-grid {
114
+ display: grid;
115
+ grid-template-columns: 1fr 1fr;
116
+ gap: 8px 24px;
117
+ margin-top: 12px;
118
+ }
119
+
120
+ .brain-stat-row {
121
+ display: flex;
122
+ justify-content: space-between;
123
+ align-items: baseline;
124
+ padding: 6px 0;
125
+ border-bottom: 1px solid var(--ng-border-subtle, rgba(148, 163, 184, 0.08));
126
+ gap: 12px;
127
+ }
128
+
129
+ .brain-stat-label {
130
+ color: var(--ng-text-tertiary, rgba(148, 163, 184, 0.85));
131
+ font-size: 0.8125rem;
132
+ }
133
+
134
+ .brain-stat-value {
135
+ font-weight: 590;
136
+ font-size: 0.9375rem;
137
+ text-align: right;
138
+ word-break: break-word;
139
+ }
140
+
141
+ /* Preferences — three columns (Topics / Entities / Tech) */
142
+ .brain-pref-cols {
143
+ display: grid;
144
+ grid-template-columns: repeat(3, 1fr);
145
+ gap: 20px;
146
+ margin-top: 8px;
147
+ }
148
+
149
+ @media (max-width: 900px) {
150
+ .brain-pref-cols { grid-template-columns: 1fr; }
151
+ .brain-stat-grid { grid-template-columns: 1fr; }
152
+ }
153
+
154
+ .brain-pref-col {
155
+ min-width: 0;
156
+ }
157
+
158
+ /* Outcome tiles */
159
+ .brain-outcome-tiles {
160
+ display: grid;
161
+ grid-template-columns: repeat(4, 1fr);
162
+ gap: 12px;
163
+ margin: 12px 0;
164
+ }
165
+
166
+ .brain-outcome-tile {
167
+ padding: 14px 12px;
168
+ border-radius: 10px;
169
+ text-align: center;
170
+ background: var(--ng-glass-bg, rgba(255, 255, 255, 0.03));
171
+ border: 1px solid var(--ng-border-subtle, rgba(148, 163, 184, 0.12));
172
+ }
173
+
174
+ .brain-outcome-count {
175
+ font-size: 1.5rem;
176
+ font-weight: 700;
177
+ line-height: 1;
178
+ }
179
+
180
+ .brain-outcome-label {
181
+ font-size: 0.75rem;
182
+ text-transform: uppercase;
183
+ letter-spacing: 0.05em;
184
+ color: var(--ng-text-tertiary, rgba(148, 163, 184, 0.85));
185
+ margin-top: 6px;
186
+ }
187
+
188
+ .brain-outcome-tile.success .brain-outcome-count { color: rgb(22, 163, 74); }
189
+ .brain-outcome-tile.failure .brain-outcome-count { color: rgb(220, 38, 38); }
190
+ .brain-outcome-tile.warn .brain-outcome-count { color: rgb(202, 138, 4); }
191
+
192
+ /* Adapter tiles (Cross-platform) */
193
+ .brain-adapter-grid {
194
+ display: grid;
195
+ grid-template-columns: repeat(3, 1fr);
196
+ gap: 12px;
197
+ margin-top: 8px;
198
+ }
199
+
200
+ @media (max-width: 900px) {
201
+ .brain-adapter-grid { grid-template-columns: repeat(2, 1fr); }
202
+ .brain-outcome-tiles { grid-template-columns: repeat(2, 1fr); }
203
+ }
204
+
205
+ .brain-adapter-tile {
206
+ padding: 12px;
207
+ border-radius: 10px;
208
+ background: var(--ng-glass-bg, rgba(255, 255, 255, 0.03));
209
+ border: 1px solid var(--ng-border-subtle, rgba(148, 163, 184, 0.12));
210
+ position: relative;
211
+ }
212
+
213
+ .brain-adapter-tile.on {
214
+ border-color: rgba(34, 197, 94, 0.35);
215
+ }
216
+
217
+ .brain-adapter-name {
218
+ font-weight: 590;
219
+ font-size: 0.9375rem;
220
+ margin-right: 18px;
221
+ }
222
+
223
+ .brain-adapter-dot {
224
+ width: 8px;
225
+ height: 8px;
226
+ border-radius: 999px;
227
+ position: absolute;
228
+ top: 14px;
229
+ right: 14px;
230
+ }
231
+
232
+ .brain-adapter-dot.dot-on { background: rgb(34, 197, 94); }
233
+ .brain-adapter-dot.dot-off { background: rgba(148, 163, 184, 0.45); }
234
+
235
+ .brain-adapter-detail {
236
+ font-size: 0.8125rem;
237
+ color: var(--ng-text-tertiary, rgba(148, 163, 184, 0.9));
238
+ margin-top: 6px;
239
+ }
240
+
241
+ /* Report-outcome form */
242
+ .brain-form {
243
+ display: grid;
244
+ grid-template-columns: 1.4fr 1fr 1.2fr 1.4fr auto;
245
+ gap: 8px;
246
+ align-items: center;
247
+ }
248
+
249
+ @media (max-width: 900px) {
250
+ .brain-form { grid-template-columns: 1fr; }
251
+ }
252
+
253
+ .brain-input {
254
+ padding: 6px 10px;
255
+ border-radius: 6px;
256
+ border: 1px solid var(--ng-border-subtle, rgba(148, 163, 184, 0.18));
257
+ background: var(--ng-glass-bg, rgba(255, 255, 255, 0.04));
258
+ color: inherit;
259
+ font: inherit;
260
+ font-size: 0.875rem;
261
+ }
262
+
263
+ .brain-form-submit { white-space: nowrap; }
264
+
265
+ .brain-form-status {
266
+ grid-column: 1 / -1;
267
+ font-size: 0.8125rem;
268
+ color: var(--ng-text-tertiary, rgba(148, 163, 184, 0.9));
269
+ margin-top: 4px;
270
+ min-height: 1.2em;
271
+ }
272
+
273
+ /* Danger zone */
274
+ .brain-section.brain-danger {
275
+ border: 1px solid rgba(239, 68, 68, 0.25);
276
+ }
277
+
278
+ .brain-danger-btn { margin-right: 10px; }
279
+
280
+ /* Evolution trend chart (v3.4.21) */
281
+ .brain-chart-wrap {
282
+ width: 100%;
283
+ margin: 8px 0 10px;
284
+ overflow: hidden;
285
+ }
286
+ .brain-evolution-chart {
287
+ width: 100%;
288
+ height: auto;
289
+ display: block;
290
+ max-height: 200px;
291
+ }
292
+
293
+ /* S9-DASH-05: closed-loop tile key-value grid */
294
+ .brain-kv-grid {
295
+ display: grid;
296
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
297
+ gap: 8px;
298
+ margin: 6px 0 8px;
299
+ }
300
+ .brain-kv {
301
+ background: rgba(255, 255, 255, 0.03);
302
+ border: 1px solid rgba(255, 255, 255, 0.06);
303
+ border-radius: 6px;
304
+ padding: 6px 10px;
305
+ transition: background 0.15s ease, transform 0.15s ease;
306
+ }
307
+ .brain-kv:hover {
308
+ background: rgba(255, 255, 255, 0.06);
309
+ transform: translateY(-1px);
310
+ }
311
+ .brain-kv-label {
312
+ font-size: 10px;
313
+ color: #888;
314
+ text-transform: uppercase;
315
+ letter-spacing: 0.5px;
316
+ margin-bottom: 2px;
317
+ }
318
+ .brain-kv-value {
319
+ font-size: 15px;
320
+ font-weight: 500;
321
+ color: #eee;
322
+ }
323
+
324
+ /* S9-DASH animations — click + async feedback */
325
+ @keyframes slm-pulse {
326
+ 0% { transform: scale(1); }
327
+ 50% { transform: scale(0.96); }
328
+ 100% { transform: scale(1); }
329
+ }
330
+ @keyframes slm-flash-success {
331
+ 0% { background: rgba(40, 167, 69, 0); }
332
+ 50% { background: rgba(40, 167, 69, 0.35); }
333
+ 100% { background: rgba(40, 167, 69, 0); }
334
+ }
335
+ @keyframes slm-flash-fail {
336
+ 0% { background: rgba(220, 53, 69, 0); }
337
+ 50% { background: rgba(220, 53, 69, 0.35); }
338
+ 100% { background: rgba(220, 53, 69, 0); }
339
+ }
340
+ @keyframes slm-shake {
341
+ 0%, 100% { transform: translateX(0); }
342
+ 25% { transform: translateX(-3px); }
343
+ 75% { transform: translateX(3px); }
344
+ }
345
+ @keyframes slm-spin {
346
+ from { transform: rotate(0deg); }
347
+ to { transform: rotate(360deg); }
348
+ }
349
+
350
+ .slm-anim-click {
351
+ animation: slm-pulse 0.18s ease-out;
352
+ }
353
+ .slm-anim-success {
354
+ animation: slm-flash-success 0.8s ease-out;
355
+ }
356
+ .slm-anim-fail {
357
+ animation: slm-flash-fail 0.6s ease-out, slm-shake 0.35s ease-out;
358
+ }
359
+ .slm-anim-spin::after {
360
+ content: '';
361
+ display: inline-block;
362
+ width: 10px;
363
+ height: 10px;
364
+ margin-left: 6px;
365
+ border: 2px solid currentColor;
366
+ border-right-color: transparent;
367
+ border-radius: 50%;
368
+ animation: slm-spin 0.6s linear infinite;
369
+ vertical-align: -2px;
370
+ }
371
+
372
+ /* S9-DASH-04: auto-detected pattern rows hover */
373
+ .brain-list li {
374
+ padding: 3px 0;
375
+ transition: background 0.15s ease;
376
+ border-radius: 3px;
377
+ }
378
+ .brain-list li:hover {
379
+ background: rgba(255, 255, 255, 0.04);
380
+ }
381
+ .brain-pattern-del {
382
+ transition: transform 0.12s ease, background 0.15s ease;
383
+ }
384
+ .brain-pattern-del:hover:not([disabled]) {
385
+ transform: scale(1.15);
386
+ background: rgba(220, 53, 69, 0.15) !important;
387
+ }
388
+
389
+ /* S9-DASH-07: memory filter pills */
390
+ .mem-filter-pill {
391
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
392
+ }
393
+ .mem-filter-pill:hover {
394
+ transform: translateY(-1px);
395
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
396
+ }
397
+ .mem-filter-pill.active {
398
+ transform: scale(0.98);
399
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
400
+ }
401
+
402
+ /* S9-DASH-06: inline feedback thumbs */
403
+ .inline-feedback button {
404
+ transition: transform 0.12s ease, background 0.15s ease,
405
+ border-color 0.15s ease, color 0.15s ease;
406
+ }
407
+ .inline-feedback button:hover:not([disabled]) {
408
+ transform: scale(1.2);
409
+ }