loki-mode 10.10.5 → 10.11.2

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 (88) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/dashboard/__init__.py +1 -1
  4. package/docs/CLI-REFERENCE.md +1 -1
  5. package/docs/INSTALLATION.md +1 -1
  6. package/docs/v10/BOARD.md +6 -4
  7. package/docs/v10/CP-ASK-PLAN.md +85 -0
  8. package/docs/v10/DEPS.md +3 -0
  9. package/docs/v10/FAILURE-CLASSES.md +32 -0
  10. package/docs/v10/FC21B-FC22-PLAN.md +107 -0
  11. package/docs/v10/FOUNDER-QUEUE.md +2 -0
  12. package/docs/v10/METRICS.md +1 -0
  13. package/docs/v10/PROGRESS.md +21 -0
  14. package/loki-ts/dist/loki.js +683 -678
  15. package/mcp/__init__.py +1 -1
  16. package/mcp/server.py +86 -3
  17. package/mcp/tests/_read_only_wall_probe.py +156 -0
  18. package/mcp/tests/test_read_only.py +289 -0
  19. package/memory/storage.py +41 -3
  20. package/package.json +1 -1
  21. package/packages/control-plane/dist/server.js +289 -210
  22. package/packages/control-plane/drizzle/0005_ask.sql +39 -0
  23. package/packages/control-plane/drizzle/meta/0005_snapshot.json +800 -0
  24. package/packages/control-plane/drizzle/meta/_journal.json +7 -0
  25. package/packages/control-plane/package.json +3 -1
  26. package/packages/control-plane/src/ask/parse.ts +120 -0
  27. package/packages/control-plane/src/ask/tools.ts +111 -0
  28. package/packages/control-plane/src/ask/tools_server.ts +46 -0
  29. package/packages/control-plane/src/db/schema.ts +43 -1
  30. package/packages/control-plane/src/server/routes/artifacts.ts +3 -3
  31. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  32. package/web-app/dist/assets/{AdminPage-qj6cyTT6.js → AdminPage-CmgIgBFY.js} +1 -1
  33. package/web-app/dist/assets/{Avatar-Dg4WrYYd.js → Avatar-Dm3ypJzh.js} +1 -1
  34. package/web-app/dist/assets/{Badge-DHiwSg-p.js → Badge-0siYUwAA.js} +1 -1
  35. package/web-app/dist/assets/{Button-gi8XEzIU.js → Button-EMKQXvZe.js} +1 -1
  36. package/web-app/dist/assets/{CockpitPage-B1QNjPBn.js → CockpitPage-Z60eDvJ9.js} +1 -1
  37. package/web-app/dist/assets/{ComparePage-DW-oUaDu.js → ComparePage-D4L7nViy.js} +1 -1
  38. package/web-app/dist/assets/{ErrorBoundary-BK5zjqn-.js → ErrorBoundary--v9DugrJ.js} +1 -1
  39. package/web-app/dist/assets/{EvidenceReceiptPanel-F58cuFm9.js → EvidenceReceiptPanel-CA866XsM.js} +1 -1
  40. package/web-app/dist/assets/{GitHubIssuesPanel-QVyvF19M.js → GitHubIssuesPanel-Bi6FJmR2.js} +1 -1
  41. package/web-app/dist/assets/{GitHubPRsPanel-BRFHMzwy.js → GitHubPRsPanel-CgSfcWOg.js} +1 -1
  42. package/web-app/dist/assets/{HomePage-Cpskd4yM.js → HomePage-BxQtk9Ec.js} +2 -2
  43. package/web-app/dist/assets/{LoginPage-CteYWi5M.js → LoginPage-CCSxWlFA.js} +1 -1
  44. package/web-app/dist/assets/{MagicPage-CCLor0AU.js → MagicPage-B4eYm0jJ.js} +1 -1
  45. package/web-app/dist/assets/{MetricsPage-WgsOaLoq.js → MetricsPage-Bp4Hh0p2.js} +1 -1
  46. package/web-app/dist/assets/{NotFoundPage-DXSCr9P6.js → NotFoundPage-DTXk_JK4.js} +1 -1
  47. package/web-app/dist/assets/{ProjectPage-sR0kaaXV.js → ProjectPage-6S1v0GHS.js} +3 -3
  48. package/web-app/dist/assets/{ProjectsPage-J3TNS42-.js → ProjectsPage-9foqEwG2.js} +1 -1
  49. package/web-app/dist/assets/{SettingsPage-Cil5837P.js → SettingsPage-CWOG_hhD.js} +1 -1
  50. package/web-app/dist/assets/{ShowcasePage-C3_oWYJa.js → ShowcasePage-Du9LYlao.js} +1 -1
  51. package/web-app/dist/assets/{SystemSettingsPage-Sddw1fFS.js → SystemSettingsPage-BQconaDX.js} +1 -1
  52. package/web-app/dist/assets/{TeamsPage-B0poySCt.js → TeamsPage-_NsqtVmC.js} +1 -1
  53. package/web-app/dist/assets/{TemplatesPage-pcYSxHam.js → TemplatesPage-DyTEGBpR.js} +1 -1
  54. package/web-app/dist/assets/{TerminalOutput-CRWTZM7F.js → TerminalOutput-XHOYTpky.js} +1 -1
  55. package/web-app/dist/assets/{activity-D3aR95zO.js → activity-B9KiMdEs.js} +1 -1
  56. package/web-app/dist/assets/{bell-WdWaPIBo.js → bell-Oe4lG6kK.js} +1 -1
  57. package/web-app/dist/assets/{bot-CzD3xokc.js → bot-D86Qmxfy.js} +1 -1
  58. package/web-app/dist/assets/{check-DMYJiYcm.js → check-BvW6_ni0.js} +1 -1
  59. package/web-app/dist/assets/{chevron-left-DY0FSKvI.js → chevron-left-DYA62-3g.js} +1 -1
  60. package/web-app/dist/assets/{circle-alert-DVPtG3Pe.js → circle-alert-CHW8ye4p.js} +1 -1
  61. package/web-app/dist/assets/{clock-BjjLL6uI.js → clock-QbQ2XfbM.js} +1 -1
  62. package/web-app/dist/assets/{cloud-CnUCVZhm.js → cloud-ylzrY7eL.js} +1 -1
  63. package/web-app/dist/assets/{code-xml-BPoxcFNK.js → code-xml-DdKKw2Y9.js} +1 -1
  64. package/web-app/dist/assets/{database-ClVgDBYt.js → database-BLY_OWs3.js} +1 -1
  65. package/web-app/dist/assets/{dollar-sign-Ui4BIml8.js → dollar-sign-CsFIa1e2.js} +1 -1
  66. package/web-app/dist/assets/{file-code-corner-BNYl7J1Z.js → file-code-corner-Df4nyrCW.js} +1 -1
  67. package/web-app/dist/assets/{file-plus-BxkSyRVn.js → file-plus-_MWEY2WY.js} +1 -1
  68. package/web-app/dist/assets/{globe-b2poyDKT.js → globe-Dpc7QU9m.js} +1 -1
  69. package/web-app/dist/assets/{hammer-D_0BUpBG.js → hammer-B-PldT0c.js} +1 -1
  70. package/web-app/dist/assets/{index-D8TYtITk.js → index-gq0R3j-o.js} +3 -3
  71. package/web-app/dist/assets/{layers-Co1szEIf.js → layers-BjcNwHLZ.js} +1 -1
  72. package/web-app/dist/assets/{loader-circle-DylW-nwq.js → loader-circle-CoTgcqkV.js} +1 -1
  73. package/web-app/dist/assets/{lock-D7aFdKw7.js → lock-JEI8O6TS.js} +1 -1
  74. package/web-app/dist/assets/{package-ElSsRKiN.js → package-CXWkNS40.js} +1 -1
  75. package/web-app/dist/assets/{plus-CVWgRxDX.js → plus-CRK4U5b0.js} +1 -1
  76. package/web-app/dist/assets/{refresh-cw-B2azSZHT.js → refresh-cw-CrzWr4cw.js} +1 -1
  77. package/web-app/dist/assets/{rotate-ccw-BdQHOrf0.js → rotate-ccw-DV7UZyUw.js} +1 -1
  78. package/web-app/dist/assets/{scroll-text-9m-YKf83.js → scroll-text-CXEqRrXU.js} +1 -1
  79. package/web-app/dist/assets/{server-BYq99Fe1.js → server-CDFfFP10.js} +1 -1
  80. package/web-app/dist/assets/{shield-alert-BNCiHXhR.js → shield-alert-B5EC_m6_.js} +1 -1
  81. package/web-app/dist/assets/{thumbs-up-BB_EJkat.js → thumbs-up-BeLDemd2.js} +1 -1
  82. package/web-app/dist/assets/{trash-2-BMF4kFvy.js → trash-2-B7kS86-0.js} +1 -1
  83. package/web-app/dist/assets/{trending-down-BiqiTPXx.js → trending-down-BGvuLayo.js} +1 -1
  84. package/web-app/dist/assets/{trending-up-Bq6qyKub.js → trending-up-BkPM2tiK.js} +1 -1
  85. package/web-app/dist/assets/{upload-1HzIJHHR.js → upload-CXVI0GRU.js} +1 -1
  86. package/web-app/dist/assets/{usePolling-FNwpjURt.js → usePolling-CMGMVsMA.js} +1 -1
  87. package/web-app/dist/assets/{user-DrORfQv3.js → user-DPcXNtfj.js} +1 -1
  88. package/web-app/dist/index.html +1 -1
package/memory/storage.py CHANGED
@@ -71,6 +71,19 @@ class MemoryStorage:
71
71
  Supports namespace-based project isolation for memory separation.
72
72
  """
73
73
 
74
+ # Process-wide read-only switch, set by mcp/server.py apply_read_only_mode.
75
+ # When True, constructing a store creates nothing (no directories, no
76
+ # index/timeline init, no cleanup), reads take no lock file, and any write
77
+ # raises (every public mutator calls _check_writable). A missing store
78
+ # simply reads as empty.
79
+ READ_ONLY = False
80
+
81
+ @staticmethod
82
+ def _check_writable() -> None:
83
+ """Raise PermissionError when the process-wide READ_ONLY flag is set."""
84
+ if MemoryStorage.READ_ONLY:
85
+ raise PermissionError("memory store is read-only in this process")
86
+
74
87
  VERSION = "1.1.0"
75
88
 
76
89
  def __init__(
@@ -112,9 +125,10 @@ class MemoryStorage:
112
125
  # called on the same path from nested operations in the same thread.
113
126
  self._held_locks: threading.local = threading.local()
114
127
 
115
- self._ensure_directories()
116
- self._ensure_index()
117
- self._ensure_timeline()
128
+ if not MemoryStorage.READ_ONLY:
129
+ self._ensure_directories()
130
+ self._ensure_index()
131
+ self._ensure_timeline()
118
132
 
119
133
  @property
120
134
  def namespace(self) -> Optional[str]:
@@ -316,6 +330,11 @@ class MemoryStorage:
316
330
  Yields:
317
331
  File handle with lock held
318
332
  """
333
+ if MemoryStorage.READ_ONLY:
334
+ # No lock file may be created; reads proceed unlocked.
335
+ yield
336
+ return
337
+
319
338
  lock_path = path.with_suffix(path.suffix + ".lock")
320
339
  lock_key = str(lock_path)
321
340
 
@@ -364,6 +383,8 @@ class MemoryStorage:
364
383
  path: Target file path
365
384
  data: Dictionary to serialize as JSON
366
385
  """
386
+ if MemoryStorage.READ_ONLY:
387
+ raise PermissionError("memory store is read-only in this process")
367
388
  path = Path(path)
368
389
  path.parent.mkdir(parents=True, exist_ok=True)
369
390
 
@@ -482,6 +503,7 @@ class MemoryStorage:
482
503
  Returns:
483
504
  Episode ID
484
505
  """
506
+ self._check_writable()
485
507
  # Handle both dict and object
486
508
  if hasattr(episode, "to_dict"):
487
509
  episode_data = episode.to_dict()
@@ -620,6 +642,7 @@ class MemoryStorage:
620
642
  Returns:
621
643
  True if deleted, False if not found
622
644
  """
645
+ self._check_writable()
623
646
  episodic_dir = self.base_path / "episodic"
624
647
  if not episodic_dir.exists():
625
648
  return False
@@ -690,6 +713,7 @@ class MemoryStorage:
690
713
  Returns:
691
714
  Pattern ID
692
715
  """
716
+ self._check_writable()
693
717
  # Handle both dict and object
694
718
  if hasattr(pattern, "to_dict"):
695
719
  pattern_data = pattern.to_dict()
@@ -826,6 +850,7 @@ class MemoryStorage:
826
850
  Returns:
827
851
  True if updated, False if not found
828
852
  """
853
+ self._check_writable()
829
854
  # Handle both dict and object
830
855
  if hasattr(pattern, "to_dict"):
831
856
  pattern_data = pattern.to_dict()
@@ -906,6 +931,7 @@ class MemoryStorage:
906
931
  True if the pattern was found and the merged record written, False if
907
932
  the pattern id was not present (caller should fall back to a create).
908
933
  """
934
+ self._check_writable()
909
935
  if not pattern_id:
910
936
  return False
911
937
 
@@ -968,6 +994,7 @@ class MemoryStorage:
968
994
  Returns:
969
995
  Skill ID
970
996
  """
997
+ self._check_writable()
971
998
  # Handle both dict and object
972
999
  if hasattr(skill, "to_dict"):
973
1000
  skill_data = skill.to_dict()
@@ -1053,6 +1080,7 @@ class MemoryStorage:
1053
1080
 
1054
1081
  Scans all memory stores and builds a topic index for efficient lookup.
1055
1082
  """
1083
+ self._check_writable()
1056
1084
  index = {
1057
1085
  "version": self.VERSION,
1058
1086
  "last_updated": datetime.now(timezone.utc).isoformat(),
@@ -1126,6 +1154,7 @@ class MemoryStorage:
1126
1154
  Args:
1127
1155
  action: Action dictionary with type, description, timestamp, etc.
1128
1156
  """
1157
+ self._check_writable()
1129
1158
  timeline_path = self.base_path / "timeline.json"
1130
1159
 
1131
1160
  with self._file_lock(timeline_path, exclusive=True):
@@ -1207,6 +1236,7 @@ class MemoryStorage:
1207
1236
  Args:
1208
1237
  context: Dictionary of current context variables
1209
1238
  """
1239
+ self._check_writable()
1210
1240
  timeline_path = self.base_path / "timeline.json"
1211
1241
 
1212
1242
  with self._file_lock(timeline_path, exclusive=True):
@@ -1263,6 +1293,7 @@ class MemoryStorage:
1263
1293
 
1264
1294
  def ensure_directory(self, subpath: str) -> None:
1265
1295
  """Create directory if it doesn't exist."""
1296
+ self._check_writable()
1266
1297
  path = os.path.join(self.base_path, subpath)
1267
1298
  os.makedirs(path, exist_ok=True)
1268
1299
 
@@ -1286,6 +1317,7 @@ class MemoryStorage:
1286
1317
 
1287
1318
  def write_json(self, filepath: str, data: dict) -> None:
1288
1319
  """Write JSON file atomically."""
1320
+ self._check_writable()
1289
1321
  full_path = self._resolve_path(filepath)
1290
1322
  self._atomic_write(full_path, data)
1291
1323
 
@@ -1298,6 +1330,7 @@ class MemoryStorage:
1298
1330
 
1299
1331
  def delete_file(self, filepath: str) -> bool:
1300
1332
  """Delete file, return True if deleted."""
1333
+ self._check_writable()
1301
1334
  full_path = self._resolve_path(filepath)
1302
1335
  try:
1303
1336
  os.remove(full_path)
@@ -1545,6 +1578,7 @@ class MemoryStorage:
1545
1578
  Returns:
1546
1579
  True if a record was found and persisted, False otherwise.
1547
1580
  """
1581
+ self._check_writable()
1548
1582
  memory_id = memory.get("id")
1549
1583
  if not memory_id:
1550
1584
  return False
@@ -1656,6 +1690,7 @@ class MemoryStorage:
1656
1690
  Returns:
1657
1691
  True if the pattern was found and incremented, False otherwise.
1658
1692
  """
1693
+ self._check_writable()
1659
1694
  patterns_path = self.base_path / "semantic" / "patterns.json"
1660
1695
  if not patterns_path.exists():
1661
1696
  return False
@@ -1706,6 +1741,7 @@ class MemoryStorage:
1706
1741
  Returns:
1707
1742
  Number of memories updated
1708
1743
  """
1744
+ self._check_writable()
1709
1745
  updated_count = 0
1710
1746
 
1711
1747
  collections_to_process = []
@@ -1926,6 +1962,7 @@ class MemoryStorage:
1926
1962
  Returns:
1927
1963
  Dictionary with counts of copied items
1928
1964
  """
1965
+ self._check_writable()
1929
1966
  target = self.with_namespace(target_namespace)
1930
1967
  copied = {"episodes": 0, "patterns": 0, "skills": 0}
1931
1968
 
@@ -1970,6 +2007,7 @@ class MemoryStorage:
1970
2007
  Returns:
1971
2008
  Dictionary with counts of merged items
1972
2009
  """
2010
+ self._check_writable()
1973
2011
  source = self.with_namespace(source_namespace)
1974
2012
  merged = {"episodes": 0, "patterns": 0, "skills": 0}
1975
2013
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "loki-mode",
3
3
  "mcpName": "io.github.asklokesh/loki-mode",
4
- "version": "10.10.5",
4
+ "version": "10.11.2",
5
5
  "description": "An autonomous software factory that knows what it is supposed to deliver, and proves it did.",
6
6
  "keywords": [
7
7
  "agent",