loki-mode 10.10.3 → 10.11.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 (84) 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 +8 -0
  7. package/docs/v10/CP-ASK-PLAN.md +85 -0
  8. package/docs/v10/DEPS.md +2 -0
  9. package/docs/v10/FAILURE-CLASSES.md +61 -16
  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 +10 -0
  13. package/docs/v10/PROGRESS.md +23 -0
  14. package/loki-ts/dist/loki.js +681 -660
  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 +91 -5
  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/src/ask/parse.ts +120 -0
  26. package/packages/control-plane/src/db/schema.ts +43 -1
  27. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  28. package/web-app/dist/assets/{AdminPage-Dyx5OHmP.js → AdminPage-qTfCHm93.js} +1 -1
  29. package/web-app/dist/assets/{Avatar-WVxt-kYj.js → Avatar-V5Wfzf34.js} +1 -1
  30. package/web-app/dist/assets/{Badge-16vWJ-6u.js → Badge-B7fNko8Z.js} +1 -1
  31. package/web-app/dist/assets/{Button-CWVPIMTW.js → Button-C_3eInAe.js} +1 -1
  32. package/web-app/dist/assets/{CockpitPage-D5WhogJK.js → CockpitPage-Ir2wag2q.js} +1 -1
  33. package/web-app/dist/assets/{ComparePage-CjJfjxcT.js → ComparePage-qbC63b0U.js} +1 -1
  34. package/web-app/dist/assets/{ErrorBoundary-CNnObh81.js → ErrorBoundary-BTEqFTCi.js} +1 -1
  35. package/web-app/dist/assets/{EvidenceReceiptPanel-Cn1PE_JV.js → EvidenceReceiptPanel-CcICm1gj.js} +1 -1
  36. package/web-app/dist/assets/{GitHubIssuesPanel-CcH9neSU.js → GitHubIssuesPanel-CXCWYQqX.js} +1 -1
  37. package/web-app/dist/assets/{GitHubPRsPanel-C-n6inCN.js → GitHubPRsPanel-DmzhZYpS.js} +1 -1
  38. package/web-app/dist/assets/{HomePage-BZ7PCPFR.js → HomePage-BRPspBy7.js} +2 -2
  39. package/web-app/dist/assets/{LoginPage-CJsigIUa.js → LoginPage-C1Nb7Vf9.js} +1 -1
  40. package/web-app/dist/assets/{MagicPage-ClZaKmXT.js → MagicPage-CeXc60VN.js} +1 -1
  41. package/web-app/dist/assets/{MetricsPage-ELQn9-cf.js → MetricsPage-MuDDdqNu.js} +1 -1
  42. package/web-app/dist/assets/{NotFoundPage-Dc_zp3RF.js → NotFoundPage-8iOaj85A.js} +1 -1
  43. package/web-app/dist/assets/{ProjectPage-B5K1NDuH.js → ProjectPage-DOKcPhxr.js} +3 -3
  44. package/web-app/dist/assets/{ProjectsPage-C2l5SiIN.js → ProjectsPage-CNKaqvuO.js} +1 -1
  45. package/web-app/dist/assets/{SettingsPage-DkDNp6Pd.js → SettingsPage-uZT6dOT1.js} +1 -1
  46. package/web-app/dist/assets/{ShowcasePage-1zasFSKz.js → ShowcasePage-CHzrwMZ3.js} +1 -1
  47. package/web-app/dist/assets/{SystemSettingsPage-hIm_4NUx.js → SystemSettingsPage-DMHj5Xub.js} +1 -1
  48. package/web-app/dist/assets/{TeamsPage-DUb_TA22.js → TeamsPage-IGY1oZ2o.js} +1 -1
  49. package/web-app/dist/assets/{TemplatesPage-B1mj-eA4.js → TemplatesPage-Byxc8XyZ.js} +1 -1
  50. package/web-app/dist/assets/{TerminalOutput-BX1UwelI.js → TerminalOutput-CRamR4sR.js} +1 -1
  51. package/web-app/dist/assets/{activity-B8APKMuh.js → activity-DQfL1H8b.js} +1 -1
  52. package/web-app/dist/assets/{bell-BS8WL-tI.js → bell-CVksERF2.js} +1 -1
  53. package/web-app/dist/assets/{bot-e9I6yy12.js → bot-jSx_C-35.js} +1 -1
  54. package/web-app/dist/assets/{check-BbgR7Ovw.js → check-o3bOhMvE.js} +1 -1
  55. package/web-app/dist/assets/{chevron-left-BcDqv7aX.js → chevron-left-DBhM8Y2E.js} +1 -1
  56. package/web-app/dist/assets/{circle-alert-BnDT8wxe.js → circle-alert-vCzO_mB8.js} +1 -1
  57. package/web-app/dist/assets/{clock-LPz79lks.js → clock-Bj125ZaJ.js} +1 -1
  58. package/web-app/dist/assets/{cloud-BcgBi5T1.js → cloud-DpLr6uQJ.js} +1 -1
  59. package/web-app/dist/assets/{code-xml-5zcykgva.js → code-xml-VuYrHYvT.js} +1 -1
  60. package/web-app/dist/assets/{database-D9KLqhHq.js → database-DBmPbwaG.js} +1 -1
  61. package/web-app/dist/assets/{dollar-sign-CrINoZ4o.js → dollar-sign-DEajFwH1.js} +1 -1
  62. package/web-app/dist/assets/{file-code-corner-CA7G72ip.js → file-code-corner-BidTyjV_.js} +1 -1
  63. package/web-app/dist/assets/{file-plus-D8tHdHn_.js → file-plus-DJ4dSvxw.js} +1 -1
  64. package/web-app/dist/assets/{globe-CT0Ts5bO.js → globe-BnGEwsOq.js} +1 -1
  65. package/web-app/dist/assets/{hammer-tUpu0LJk.js → hammer-KCPviXRE.js} +1 -1
  66. package/web-app/dist/assets/{index-DdjeiWUI.js → index-CdK6NQG3.js} +3 -3
  67. package/web-app/dist/assets/{layers-BzzJ7w3C.js → layers-7G4hsaDk.js} +1 -1
  68. package/web-app/dist/assets/{loader-circle-BBdMdxIV.js → loader-circle-CWVxvRmJ.js} +1 -1
  69. package/web-app/dist/assets/{lock-Cg9DkEh7.js → lock-BEH3dNM0.js} +1 -1
  70. package/web-app/dist/assets/{package-DHs8IEJR.js → package-DvijfmPC.js} +1 -1
  71. package/web-app/dist/assets/{plus-70JSAqB1.js → plus-CEwJzXGx.js} +1 -1
  72. package/web-app/dist/assets/{refresh-cw-DjggkboT.js → refresh-cw-Ct7rMsNS.js} +1 -1
  73. package/web-app/dist/assets/{rotate-ccw-c1NNyyL5.js → rotate-ccw-CoTyya_B.js} +1 -1
  74. package/web-app/dist/assets/{scroll-text-B49wQrJ2.js → scroll-text-CbsOnRXr.js} +1 -1
  75. package/web-app/dist/assets/{server-BHKG-dDi.js → server-MUPVBbag.js} +1 -1
  76. package/web-app/dist/assets/{shield-alert-Dx9MoBGm.js → shield-alert-CWwdqRUv.js} +1 -1
  77. package/web-app/dist/assets/{thumbs-up-frEbS59j.js → thumbs-up-CPVKKyLS.js} +1 -1
  78. package/web-app/dist/assets/{trash-2-1Gd8pXFG.js → trash-2-BqzCzljk.js} +1 -1
  79. package/web-app/dist/assets/{trending-down-BpUeO-FP.js → trending-down-DAo5VuPc.js} +1 -1
  80. package/web-app/dist/assets/{trending-up-CvhWp8qO.js → trending-up-BCUggli9.js} +1 -1
  81. package/web-app/dist/assets/{upload-DZuMugqd.js → upload-iQijlCY2.js} +1 -1
  82. package/web-app/dist/assets/{usePolling-D5KUSkfs.js → usePolling-B6kojZR_.js} +1 -1
  83. package/web-app/dist/assets/{user-BF9vws84.js → user-DR3XkyHZ.js} +1 -1
  84. 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.3",
4
+ "version": "10.11.0",
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",
@@ -10473,6 +10473,9 @@ class Index {
10473
10473
  function index(name) {
10474
10474
  return new IndexBuilderOn(name, false);
10475
10475
  }
10476
+ function uniqueIndex(name) {
10477
+ return new IndexBuilderOn(name, true);
10478
+ }
10476
10479
 
10477
10480
  // node_modules/drizzle-orm/sqlite-core/primary-keys.js
10478
10481
  function primaryKey(...config) {
@@ -12521,6 +12524,9 @@ __export(exports_schema, {
12521
12524
  localRepos: () => localRepos,
12522
12525
  events: () => events,
12523
12526
  audit: () => audit2,
12527
+ askThreads: () => askThreads,
12528
+ askMessages: () => askMessages,
12529
+ askEvents: () => askEvents,
12524
12530
  actions: () => actions
12525
12531
  });
12526
12532
  var sources = sqliteTable("sources", {
@@ -12601,6 +12607,42 @@ var actions = sqliteTable("actions", {
12601
12607
  result: text("result").notNull(),
12602
12608
  detail: text("detail")
12603
12609
  }, (t) => [index("actions_ts").on(t.ts)]);
12610
+ var askThreads = sqliteTable("ask_threads", {
12611
+ id: text("id").primaryKey(),
12612
+ createdAt: text("created_at").notNull(),
12613
+ updatedAt: text("updated_at").notNull(),
12614
+ repo: text("repo"),
12615
+ provider: text("provider").notNull(),
12616
+ model: text("model"),
12617
+ title: text("title")
12618
+ });
12619
+ var askMessages = sqliteTable("ask_messages", {
12620
+ id: text("id").primaryKey(),
12621
+ threadId: text("thread_id").notNull().references(() => askThreads.id),
12622
+ seq: integer("seq").notNull(),
12623
+ role: text("role").notNull(),
12624
+ text: text("text").notNull(),
12625
+ status: text("status").notNull(),
12626
+ workerPid: integer("worker_pid"),
12627
+ pgid: integer("pgid"),
12628
+ costUsd: real("cost_usd"),
12629
+ error: text("error"),
12630
+ startedAt: text("started_at"),
12631
+ finishedAt: text("finished_at")
12632
+ }, (t) => [
12633
+ uniqueIndex("ask_messages_thread_seq").on(t.threadId, t.seq),
12634
+ index("ask_messages_status").on(t.status)
12635
+ ]);
12636
+ var askEvents = sqliteTable("ask_events", {
12637
+ id: text("id").primaryKey(),
12638
+ messageId: text("message_id").notNull().references(() => askMessages.id),
12639
+ seq: integer("seq").notNull(),
12640
+ kind: text("kind").notNull(),
12641
+ payload: text("payload").notNull(),
12642
+ ts: text("ts").notNull()
12643
+ }, (t) => [
12644
+ uniqueIndex("ask_events_message_seq").on(t.messageId, t.seq)
12645
+ ]);
12604
12646
 
12605
12647
  // src/db/migrate.ts
12606
12648
  function openDb(path) {
@@ -17187,6 +17229,21 @@ function checkCommand(repoDir, v, where, errs) {
17187
17229
  const cwd = checkDir(repoDir, v.cwd, `${where}.cwd`, errs);
17188
17230
  return { cmd: v.cmd.trim(), cwd, cite: checkCites(repoDir, v.cite, `${where}.cite`, errs) };
17189
17231
  }
17232
+ function rawDeps(v, where, errs) {
17233
+ if (!Array.isArray(v)) {
17234
+ errs.push(`${where}: must be an array of package roots`);
17235
+ return [];
17236
+ }
17237
+ const out2 = [];
17238
+ for (const d of v) {
17239
+ const rel = isStr(d) ? safeRel(d) : null;
17240
+ if (rel === null)
17241
+ errs.push(`${where}: "${String(d)}" is not a repo-relative package root`);
17242
+ else if (!out2.includes(rel))
17243
+ out2.push(rel);
17244
+ }
17245
+ return out2;
17246
+ }
17190
17247
  function validateAnswer(repoDir, raw2) {
17191
17248
  const errs = [];
17192
17249
  if (!isRec(raw2))
@@ -17219,9 +17276,16 @@ function validateAnswer(repoDir, raw2) {
17219
17276
  runner: isStr(p.runner) ? p.runner.trim() : null,
17220
17277
  commands,
17221
17278
  ui: { present: ui.present === true, boot: checkCommand(repoDir, ui.boot, `${w}.ui.boot`, errs), cite: checkCites(repoDir, ui.cite, `${w}.ui.cite`, errs) },
17222
- cite: checkCites(repoDir, p.cite, `${w}.cite`, errs)
17279
+ cite: checkCites(repoDir, p.cite, `${w}.cite`, errs),
17280
+ ...p.install === undefined ? {} : { install: checkCommand(repoDir, p.install, `${w}.install`, errs) },
17281
+ ...p.dependsOn === undefined ? {} : { dependsOn: rawDeps(p.dependsOn, `${w}.dependsOn`, errs) }
17223
17282
  });
17224
17283
  }
17284
+ const roots = new Set(packages.map((p) => p.root));
17285
+ for (const p of packages)
17286
+ for (const d of p.dependsOn ?? [])
17287
+ if (!roots.has(d))
17288
+ errs.push(`package "${p.name}".dependsOn: "${d}" is not the root of any listed package`);
17225
17289
  if (!Array.isArray(raw2.fingerprintFiles))
17226
17290
  errs.push("fingerprintFiles: required array of the manifest and lockfile paths");
17227
17291
  const fingerprintFiles = (Array.isArray(raw2.fingerprintFiles) ? raw2.fingerprintFiles : []).filter((f) => {
@@ -17305,6 +17369,14 @@ function projectApi(model) {
17305
17369
  return p ? p.root === "." ? clean(file) : clean(file).slice(p.root.length + 1) : null;
17306
17370
  },
17307
17371
  commandFor: (pkg, kind) => (packages.find((p) => p.root === clean(pkg) || p.name === pkg) ?? null)?.commands?.[kind] ?? null,
17372
+ dependsOn: (pkg) => {
17373
+ const d = (packages.find((p) => p.root === clean(pkg) || p.name === pkg) ?? null)?.dependsOn;
17374
+ return Array.isArray(d) ? d.filter((x) => typeof x === "string") : null;
17375
+ },
17376
+ installFor: (pkg) => {
17377
+ const i = (packages.find((p) => p.root === clean(pkg) || p.name === pkg) ?? null)?.install;
17378
+ return isRec2(i) && typeof i.cmd === "string" ? i : null;
17379
+ },
17308
17380
  hasUI: () => packages.some((p) => isRec2(p.ui) && p.ui.present === true),
17309
17381
  uiBoot: () => {
17310
17382
  for (const p of packages)
@@ -17654,6 +17726,13 @@ function receiptScreensProblem(receiptPath, receipt) {
17654
17726
  return receipt["evidence_screens"] === undefined ? null : checkScreens(dirname8(receiptPath), receipt["evidence_screens"]);
17655
17727
  }
17656
17728
 
17729
+ // ../../loki-ts/src/util/check_result.ts
17730
+ var NO_TESTS_REASON = "no tests executed";
17731
+ var UNCONFIRMED_REASON = "test count could not be confirmed";
17732
+ var UNMEASURED_REASON = "executed count unmeasured (Loki could not parse the runner summary, harness-owned)";
17733
+ var isExecutedProof = (c) => c.result === "pass" && typeof c.n === "number" && c.n > 0;
17734
+ var hasExecutedProof = (checks) => checks.some(isExecutedProof);
17735
+
17657
17736
  // ../../loki-ts/src/features/contract.ts
17658
17737
  import { existsSync as existsSync16, lstatSync as lstatSync7, mkdirSync as mkdirSync8, readFileSync as readFileSync21, writeFileSync as writeFileSync9 } from "fs";
17659
17738
  import { dirname as dirname9, join as join28 } from "path";
@@ -18019,10 +18098,10 @@ var commitStage = {
18019
18098
  return { status: "completed", data: { committed: true, head_sha: (await git(ctx, ["rev-parse", "HEAD"])).out.trim(), scope_notes: notes } };
18020
18099
  }
18021
18100
  };
18022
- function verdictOf2(o, checks, emptyDiff, verifyNotProven, wallGreenOnBase) {
18101
+ function verdictOf2(o, checks, emptyDiff, verifyNotProven, wallGreenOnBase, proof) {
18023
18102
  const exit = o.implement?.exit;
18024
18103
  if (o.commit?.failed !== true && strs2(o.commit?.not_proven).length === 0 && (o.intake?.already_satisfied === true || wallGreenOnBase || exit === "already_done"))
18025
- return "ALREADY_SATISFIED";
18104
+ return proof ? "ALREADY_SATISFIED" : "PARTIAL";
18026
18105
  if (exit === "spec_conflict")
18027
18106
  return "SPEC_CONFLICT";
18028
18107
  if (o.commit?.failed === true || strs2(o.commit?.not_proven).length > 0)
@@ -18031,7 +18110,7 @@ function verdictOf2(o, checks, emptyDiff, verifyNotProven, wallGreenOnBase) {
18031
18110
  return "FAILED";
18032
18111
  if (checks.some((c) => c.result === "fail"))
18033
18112
  return "FAILED";
18034
- if (exit === "killed" || checks.length === 0 || checks.some((c) => c.result !== "pass") || verifyNotProven)
18113
+ if (exit === "killed" || checks.length === 0 || checks.some((c) => c.result !== "pass") || verifyNotProven || !proof)
18035
18114
  return "PARTIAL";
18036
18115
  return "VERIFIED";
18037
18116
  }
@@ -18089,10 +18168,17 @@ var sealStage = {
18089
18168
  if ((rawDiff[i].trim().split(" ").pop() ?? "") !== "A" && isTestFile(rawDiff[i + 1]))
18090
18169
  weakTests.push(rawDiff[i + 1]);
18091
18170
  const grp = sealGroup(ctx.runDir, receiptSha256);
18092
- const verdict = capGroupVerdict(verdictOf2(o, checks.filter((c) => !(c.result === "fail" && preRedChecks.includes(c.name))), !diffOk || diff.stdout === "", verifyNotProven.length > 0 || weakTests.length > 0, wallGreenOnBase), grp);
18171
+ const proof = wallGreenOnBase || hasExecutedProof(Array.isArray(o.verify?.checks) ? o.verify.checks : []);
18172
+ const verdict = capGroupVerdict(verdictOf2(o, checks.filter((c) => !(c.result === "fail" && preRedChecks.includes(c.name))), !diffOk || diff.stdout === "", verifyNotProven.length > 0 || weakTests.length > 0, wallGreenOnBase, proof), grp);
18093
18173
  const notProven = new Set([...DEEP_NOT_PROVEN, ...grp.notProven]);
18174
+ if (!proof && (verdict === "PARTIAL" || verdict === "VERIFIED" || verdict === "ALREADY_SATISFIED")) {
18175
+ const vc = Array.isArray(o.verify?.checks) ? o.verify.checks : [];
18176
+ notProven.add(vc.length > 0 && vc.every((c) => c.n !== 0 && String(c.reason ?? "").startsWith(UNMEASURED_REASON)) ? UNMEASURED_REASON : vc.length > 0 && vc.every((c) => c.n !== 0 && String(c.reason ?? "").startsWith(UNCONFIRMED_REASON)) ? UNCONFIRMED_REASON : NO_TESTS_REASON);
18177
+ }
18094
18178
  if (wallNotRun > 0)
18095
18179
  notProven.add(`wall base run not_run: ${wallNotRun}`);
18180
+ for (const d of Array.isArray(o.wall?.discarded) ? o.wall.discarded : [])
18181
+ notProven.add(`wall test discarded: ${String(d.file)} (${String(d.reason)})`);
18096
18182
  if (wallNotRun > 0) {
18097
18183
  try {
18098
18184
  const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) => basename10(String(f.path))));
@@ -0,0 +1,39 @@
1
+ CREATE TABLE IF NOT EXISTS `ask_threads` (
2
+ `id` text PRIMARY KEY NOT NULL,
3
+ `created_at` text NOT NULL,
4
+ `updated_at` text NOT NULL,
5
+ `repo` text,
6
+ `provider` text NOT NULL,
7
+ `model` text,
8
+ `title` text
9
+ );
10
+ --> statement-breakpoint
11
+ CREATE TABLE IF NOT EXISTS `ask_messages` (
12
+ `id` text PRIMARY KEY NOT NULL,
13
+ `thread_id` text NOT NULL,
14
+ `seq` integer NOT NULL,
15
+ `role` text NOT NULL,
16
+ `text` text NOT NULL,
17
+ `status` text NOT NULL,
18
+ `worker_pid` integer,
19
+ `pgid` integer,
20
+ `cost_usd` real,
21
+ `error` text,
22
+ `started_at` text,
23
+ `finished_at` text,
24
+ FOREIGN KEY (`thread_id`) REFERENCES `ask_threads`(`id`) ON UPDATE no action ON DELETE no action
25
+ );
26
+ --> statement-breakpoint
27
+ CREATE UNIQUE INDEX IF NOT EXISTS `ask_messages_thread_seq` ON `ask_messages` (`thread_id`,`seq`);--> statement-breakpoint
28
+ CREATE INDEX IF NOT EXISTS `ask_messages_status` ON `ask_messages` (`status`);--> statement-breakpoint
29
+ CREATE TABLE IF NOT EXISTS `ask_events` (
30
+ `id` text PRIMARY KEY NOT NULL,
31
+ `message_id` text NOT NULL,
32
+ `seq` integer NOT NULL,
33
+ `kind` text NOT NULL,
34
+ `payload` text NOT NULL,
35
+ `ts` text NOT NULL,
36
+ FOREIGN KEY (`message_id`) REFERENCES `ask_messages`(`id`) ON UPDATE no action ON DELETE no action
37
+ );
38
+ --> statement-breakpoint
39
+ CREATE UNIQUE INDEX IF NOT EXISTS `ask_events_message_seq` ON `ask_events` (`message_id`,`seq`);