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
@@ -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) {
@@ -16891,204 +16933,11 @@ function dropSet(repoDir, staged, preexistingDirty, env = process.env) {
16891
16933
  import { basename as basename9, dirname as dirname7 } from "path";
16892
16934
 
16893
16935
  // ../../loki-ts/src/engine10/testmap.ts
16894
- import { basename as basename8, extname, join as join24, relative as relative2, sep as sep9 } from "path";
16895
- var SKIP_DIRS = new Set([
16896
- ".git",
16897
- "node_modules",
16898
- "dist",
16899
- "build",
16900
- "target",
16901
- "coverage",
16902
- ".venv",
16903
- "venv",
16904
- "__pycache__",
16905
- ".tox",
16906
- ".pytest_cache",
16907
- ".loki",
16908
- ".next"
16909
- ]);
16910
- var JS_TEST_RE = /\.(test|spec)\.[cm]?[jt]sx?$/;
16911
- var PY_TEST_RE = /^(test_.+|.+_test)\.py$/;
16912
- var GO_TEST_RE = /_test\.go$/;
16913
- function isTestFile(rel) {
16914
- const name = basename8(rel);
16915
- return JS_TEST_RE.test(name) || PY_TEST_RE.test(name) || GO_TEST_RE.test(name);
16916
- }
16917
-
16918
- // ../../loki-ts/src/features/speed/unit_mode.ts
16919
- import { closeSync as closeSync6, constants, fstatSync as fstatSync4, lstatSync as lstatSync4, openSync as openSync6, readSync as readSync4, realpathSync as realpathSync12 } from "fs";
16920
- var MAX_SPEC_BYTES = 1024 * 1024;
16921
- var MAX_TOKEN_BUDGET = 1e9;
16922
- var norm2 = (p) => p.replace(/^\.\//, "");
16923
- var CTRL = /[\x00-\x1f\x7f]/;
16924
- var strs = (v) => Array.isArray(v) && v.every((x) => typeof x === "string" && !CTRL.test(x)) ? v.map(norm2) : null;
16925
- function readBounded(path) {
16926
- const l = lstatSync4(path);
16927
- if (l.isSymbolicLink() || !l.isFile() || l.size > MAX_SPEC_BYTES)
16928
- return null;
16929
- const fd = openSync6(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
16930
- try {
16931
- const st = fstatSync4(fd);
16932
- if (!st.isFile() || st.size > MAX_SPEC_BYTES)
16933
- return null;
16934
- const buf = Buffer.alloc(st.size + 1);
16935
- let n2 = 0;
16936
- for (;; ) {
16937
- const r = readSync4(fd, buf, n2, buf.length - n2, null);
16938
- if (r <= 0)
16939
- break;
16940
- n2 += r;
16941
- if (n2 >= buf.length)
16942
- return null;
16943
- }
16944
- return buf.subarray(0, n2).toString("utf8");
16945
- } finally {
16946
- closeSync6(fd);
16947
- }
16948
- }
16949
- function parseText(txt) {
16950
- try {
16951
- const j = JSON.parse(txt), ws = strs(j.writeSet), pack = strs(j.pack), tb = j.tokenBudget;
16952
- if (typeof j.id !== "string" || !j.id || !ws || ws.length === 0 || !pack || typeof tb !== "number" || !Number.isFinite(tb) || !(tb > 0) || tb > MAX_TOKEN_BUDGET)
16953
- return null;
16954
- return { id: j.id, writeSet: ws, pack, tokenBudget: tb };
16955
- } catch {
16956
- return null;
16957
- }
16958
- }
16959
- function parseSpec(path) {
16960
- try {
16961
- const txt = readBounded(path);
16962
- return txt === null ? null : parseText(txt);
16963
- } catch {
16964
- return null;
16965
- }
16966
- }
16967
- var memo = new Map;
16968
- function unitSpec(env = process.env) {
16969
- const path = env["LOKI_UNIT_SPEC"], frozen = env["LOKI_UNIT_SPEC_FROZEN"];
16970
- if (env["LOKI_SPEED"] === "0" || !path)
16971
- return null;
16972
- if (frozen !== undefined)
16973
- return frozen.length <= MAX_SPEC_BYTES ? parseText(frozen) : null;
16974
- if (!memo.has(path))
16975
- memo.set(path, parseSpec(path));
16976
- return memo.get(path) ?? null;
16977
- }
16978
- var inWriteSet = (spec, f) => spec.writeSet.some((w) => w.endsWith("/") ? f.startsWith(w) : f === w);
16979
- function unitFlags(staged, env = process.env, pre = {}) {
16980
- const s = unitSpec(env);
16981
- return s ? staged.filter(({ f }) => !inWriteSet(s, f) && !Object.hasOwn(pre, f)).map(({ f }) => f) : null;
16982
- }
16983
-
16984
- // ../../loki-ts/src/e10ext/scope.ts
16985
- var SCOPE_UNDETERMINED = "scope not determined; all edits committed";
16986
- var outsideNote = (f) => `outside stated scope: ${f}`;
16987
- var esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16988
- var mentions = (text2, name) => new RegExp(`(?<![\\w./-])${esc(name)}(?![\\w-]|\\.\\w)`).test(text2);
16989
- var STOP = new Set(["the", "and", "for", "with", "that", "this", "all", "any", "src", "from", "into", "each", "across", "apply", "add", "use", "make"]);
16990
- var stem = (w) => w.length > 3 && w.endsWith("s") ? w.slice(0, -1) : w;
16991
- var surfaceTokens = (text2) => new Set((text2.toLowerCase().match(/[a-z][a-z0-9_-]{2,}/g) ?? []).filter((w) => !STOP.has(w)).map(stem));
16992
- var dirSegs = (f) => f.split("/").slice(0, -1).map((d) => stem(d.toLowerCase()));
16993
- function contractText(o) {
16994
- const c = o.intake?.contract_snapshot?.contract;
16995
- return Array.isArray(c?.criteria) ? c.criteria.map((x) => typeof x?.text === "string" ? x.text : "").join(`
16996
- `) : "";
16997
- }
16998
- function outsideScope(o, staged) {
16999
- const rel = Array.isArray(o.plan?.relevant_files) ? o.plan.relevant_files.filter((x) => typeof x === "string") : [];
17000
- const plan = typeof o.plan?.plan === "string" ? o.plan.plan : "";
17001
- if (rel.length === 0 && plan.trim() === "")
17002
- return null;
17003
- const text2 = `${plan}
17004
- ${typeof o.intake?.task === "string" ? o.intake.task : ""}
17005
- ${contractText(o)}`, pre = o.intake?.preexisting_dirty ?? {};
17006
- const toks = surfaceTokens(text2), plannedDirs = new Set(rel.map((r) => dirname7(r)).filter((d) => d !== "."));
17007
- return staged.filter(({ st, f }) => st !== "A" && !isTestFile(f) && !Object.hasOwn(pre, f) && !rel.includes(f) && !mentions(text2, f) && !mentions(text2, basename9(f)) && !plannedDirs.has(dirname7(f)) && !dirSegs(f).some((d) => toks.has(d))).map(({ f }) => f);
17008
- }
17009
- function flagOutsideScope(o, staged) {
17010
- const unit = unitFlags(staged, process.env, o.intake?.preexisting_dirty ?? {});
17011
- const un = outsideScope(o, staged), seen = new Set((unit ?? []).map(outsideNote));
17012
- return [...(unit ?? []).map(outsideNote), ...un ? un.map(outsideNote).filter((n2) => !seen.has(n2)) : staged.length > 0 ? [SCOPE_UNDETERMINED] : []];
17013
- }
17014
-
17015
- // ../../loki-ts/src/util/receipt_signer.ts
17016
- var RECEIPT_SIGNER_BASENAME = "receipt-ed25519.pem";
17017
-
17018
- // ../../loki-ts/src/util/shell.ts
17019
- var MAX_STDOUT_BYTES = 16 * 1024 * 1024;
17020
- async function readStreamCapped(stream, maxBytes = MAX_STDOUT_BYTES) {
17021
- const reader = stream.getReader();
17022
- const decoder = new TextDecoder;
17023
- let out2 = "";
17024
- let total = 0;
17025
- try {
17026
- while (total < maxBytes) {
17027
- const { done, value } = await reader.read();
17028
- if (done)
17029
- break;
17030
- if (!value)
17031
- continue;
17032
- total += value.byteLength;
17033
- if (total > maxBytes) {
17034
- const keep = value.byteLength - (total - maxBytes);
17035
- out2 += decoder.decode(value.subarray(0, keep), { stream: true });
17036
- break;
17037
- }
17038
- out2 += decoder.decode(value, { stream: true });
17039
- }
17040
- out2 += decoder.decode();
17041
- } finally {
17042
- try {
17043
- await reader.cancel();
17044
- } catch {}
17045
- reader.releaseLock();
17046
- }
17047
- return out2;
17048
- }
17049
- async function run(argv, opts = {}) {
17050
- const proc = Bun.spawn({
17051
- cmd: [...argv],
17052
- stdout: "pipe",
17053
- stderr: "pipe",
17054
- env: opts.env ? { ...process.env, ...opts.env } : process.env,
17055
- cwd: opts.cwd
17056
- });
17057
- let timer;
17058
- let killTimer;
17059
- if (opts.timeoutMs && opts.timeoutMs > 0) {
17060
- timer = setTimeout(() => {
17061
- try {
17062
- proc.kill("SIGTERM");
17063
- } catch {}
17064
- killTimer = setTimeout(() => {
17065
- try {
17066
- proc.kill("SIGKILL");
17067
- } catch {}
17068
- }, 2000);
17069
- }, opts.timeoutMs);
17070
- }
17071
- try {
17072
- const [stdout, stderr, exitCode] = await Promise.all([
17073
- readStreamCapped(proc.stdout),
17074
- new Response(proc.stderr).text(),
17075
- proc.exited
17076
- ]);
17077
- return { stdout, stderr, exitCode };
17078
- } finally {
17079
- if (timer)
17080
- clearTimeout(timer);
17081
- if (killTimer)
17082
- clearTimeout(killTimer);
17083
- }
17084
- }
17085
-
17086
- // ../../loki-ts/src/features/visual_evidence.ts
17087
- import { spawn as spawn5 } from "child_process";
16936
+ import { basename as basename8, extname, join as join26, relative as relative2, sep as sep10 } from "path";
17088
16937
 
17089
16938
  // ../../loki-ts/src/project_model/discover.ts
17090
16939
  import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync19, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "fs";
17091
- import { join as join26 } from "path";
16940
+ import { join as join25 } from "path";
17092
16941
 
17093
16942
  // ../../loki-ts/src/engine10/types.ts
17094
16943
  var STAGE_BUDGETS = {
@@ -17105,23 +16954,23 @@ var STAGE_BUDGETS = {
17105
16954
  };
17106
16955
 
17107
16956
  // ../../loki-ts/src/project_model/schema.ts
17108
- import { existsSync as existsSync13, lstatSync as lstatSync5, realpathSync as realpathSync13, statSync as statSync10 } from "fs";
17109
- import { isAbsolute, join as join25, normalize, sep as sep10 } from "path";
16957
+ import { existsSync as existsSync13, lstatSync as lstatSync4, realpathSync as realpathSync12, statSync as statSync10 } from "fs";
16958
+ import { isAbsolute, join as join24, normalize, sep as sep9 } from "path";
17110
16959
  var PROJECT_MODEL_SCHEMA = "loki.v10.project/1";
17111
16960
  var COMMAND_KINDS = ["test", "lint", "build", "start"];
17112
16961
  var isRec = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
17113
16962
  var isStr = (v) => typeof v === "string" && v.trim() !== "";
17114
16963
  function safeRel(p) {
17115
- const n2 = normalize(p.trim()).split(sep10).join("/").replace(/\/$/, "");
16964
+ const n2 = normalize(p.trim()).split(sep9).join("/").replace(/\/$/, "");
17116
16965
  if (n2 === "" || isAbsolute(n2) || n2 === ".." || n2.startsWith("../"))
17117
16966
  return null;
17118
16967
  return n2;
17119
16968
  }
17120
16969
  function insideRepo(repoDir, rel) {
17121
16970
  try {
17122
- const real2 = realpathSync13(join25(repoDir, rel));
17123
- const base = realpathSync13(repoDir);
17124
- return real2 === base || real2.startsWith(base + sep10);
16971
+ const real2 = realpathSync12(join24(repoDir, rel));
16972
+ const base = realpathSync12(repoDir);
16973
+ return real2 === base || real2.startsWith(base + sep9);
17125
16974
  } catch {
17126
16975
  return false;
17127
16976
  }
@@ -17132,7 +16981,7 @@ function checkCitation(repoDir, raw2) {
17132
16981
  const rel = safeRel(raw2.replace(/:\d+(?:-\d+)?$/, ""));
17133
16982
  if (rel === null)
17134
16983
  return `citation "${raw2}" is not a repo-relative path`;
17135
- const abs = join25(repoDir, rel);
16984
+ const abs = join24(repoDir, rel);
17136
16985
  if (!existsSync13(abs) || !statSync10(abs).isFile() || !insideRepo(repoDir, rel))
17137
16986
  return `citation "${raw2}" names a file that does not exist`;
17138
16987
  return null;
@@ -17144,7 +16993,7 @@ function checkFingerprint(repoDir, raw2) {
17144
16993
  if (rel === null)
17145
16994
  return `fingerprint "${raw2}" is not a repo-relative path`;
17146
16995
  try {
17147
- if (!lstatSync5(join25(repoDir, rel)).isFile() || !insideRepo(repoDir, rel))
16996
+ if (!lstatSync4(join24(repoDir, rel)).isFile() || !insideRepo(repoDir, rel))
17148
16997
  return `fingerprint "${raw2}" is not a regular file inside the repo`;
17149
16998
  } catch {
17150
16999
  return `fingerprint "${raw2}" names a file that does not exist`;
@@ -17172,7 +17021,7 @@ function checkDir(repoDir, v, where, errs) {
17172
17021
  errs.push(`${where}: must be a repo-relative directory ("." for the repo root)`);
17173
17022
  return ".";
17174
17023
  }
17175
- const abs = join25(repoDir, rel);
17024
+ const abs = join24(repoDir, rel);
17176
17025
  if (!existsSync13(abs) || !statSync10(abs).isDirectory() || !insideRepo(repoDir, rel))
17177
17026
  errs.push(`${where}: directory "${rel}" does not exist`);
17178
17027
  return rel;
@@ -17187,6 +17036,21 @@ function checkCommand(repoDir, v, where, errs) {
17187
17036
  const cwd = checkDir(repoDir, v.cwd, `${where}.cwd`, errs);
17188
17037
  return { cmd: v.cmd.trim(), cwd, cite: checkCites(repoDir, v.cite, `${where}.cite`, errs) };
17189
17038
  }
17039
+ function rawDeps(v, where, errs) {
17040
+ if (!Array.isArray(v)) {
17041
+ errs.push(`${where}: must be an array of package roots`);
17042
+ return [];
17043
+ }
17044
+ const out2 = [];
17045
+ for (const d of v) {
17046
+ const rel = isStr(d) ? safeRel(d) : null;
17047
+ if (rel === null)
17048
+ errs.push(`${where}: "${String(d)}" is not a repo-relative package root`);
17049
+ else if (!out2.includes(rel))
17050
+ out2.push(rel);
17051
+ }
17052
+ return out2;
17053
+ }
17190
17054
  function validateAnswer(repoDir, raw2) {
17191
17055
  const errs = [];
17192
17056
  if (!isRec(raw2))
@@ -17219,9 +17083,16 @@ function validateAnswer(repoDir, raw2) {
17219
17083
  runner: isStr(p.runner) ? p.runner.trim() : null,
17220
17084
  commands,
17221
17085
  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)
17086
+ cite: checkCites(repoDir, p.cite, `${w}.cite`, errs),
17087
+ ...p.install === undefined ? {} : { install: checkCommand(repoDir, p.install, `${w}.install`, errs) },
17088
+ ...p.dependsOn === undefined ? {} : { dependsOn: rawDeps(p.dependsOn, `${w}.dependsOn`, errs) }
17223
17089
  });
17224
17090
  }
17091
+ const roots = new Set(packages.map((p) => p.root));
17092
+ for (const p of packages)
17093
+ for (const d of p.dependsOn ?? [])
17094
+ if (!roots.has(d))
17095
+ errs.push(`package "${p.name}".dependsOn: "${d}" is not the root of any listed package`);
17225
17096
  if (!Array.isArray(raw2.fingerprintFiles))
17226
17097
  errs.push("fingerprintFiles: required array of the manifest and lockfile paths");
17227
17098
  const fingerprintFiles = (Array.isArray(raw2.fingerprintFiles) ? raw2.fingerprintFiles : []).filter((f) => {
@@ -17267,7 +17138,7 @@ var PROJECT_FILE = ".loki/project.json";
17267
17138
  var UNKNOWN_TTL_MS = 60 * 60 * 1000;
17268
17139
  function loadCached(repoDir, now = Date.now()) {
17269
17140
  try {
17270
- return parseCached(repoDir, JSON.parse(readFileSync19(join26(repoDir, PROJECT_FILE), "utf8")), now);
17141
+ return parseCached(repoDir, JSON.parse(readFileSync19(join25(repoDir, PROJECT_FILE), "utf8")), now);
17271
17142
  } catch {
17272
17143
  return null;
17273
17144
  }
@@ -17305,6 +17176,14 @@ function projectApi(model) {
17305
17176
  return p ? p.root === "." ? clean(file) : clean(file).slice(p.root.length + 1) : null;
17306
17177
  },
17307
17178
  commandFor: (pkg, kind) => (packages.find((p) => p.root === clean(pkg) || p.name === pkg) ?? null)?.commands?.[kind] ?? null,
17179
+ dependsOn: (pkg) => {
17180
+ const d = (packages.find((p) => p.root === clean(pkg) || p.name === pkg) ?? null)?.dependsOn;
17181
+ return Array.isArray(d) ? d.filter((x) => typeof x === "string") : null;
17182
+ },
17183
+ installFor: (pkg) => {
17184
+ const i = (packages.find((p) => p.root === clean(pkg) || p.name === pkg) ?? null)?.install;
17185
+ return isRec2(i) && typeof i.cmd === "string" ? i : null;
17186
+ },
17308
17187
  hasUI: () => packages.some((p) => isRec2(p.ui) && p.ui.present === true),
17309
17188
  uiBoot: () => {
17310
17189
  for (const p of packages)
@@ -17331,7 +17210,205 @@ function isMultiRoot(api) {
17331
17210
  return !!api && api.known() && api.packages().some((p) => p.root !== ".");
17332
17211
  }
17333
17212
 
17213
+ // ../../loki-ts/src/project_model/scope.ts
17214
+ var apis = new WeakMap;
17215
+ var imports = new WeakMap;
17216
+ var dropped = new WeakMap;
17217
+
17218
+ // ../../loki-ts/src/engine10/testmap.ts
17219
+ var SKIP_DIRS = new Set([
17220
+ ".git",
17221
+ "node_modules",
17222
+ "dist",
17223
+ "build",
17224
+ "target",
17225
+ "coverage",
17226
+ ".venv",
17227
+ "venv",
17228
+ "__pycache__",
17229
+ ".tox",
17230
+ ".pytest_cache",
17231
+ ".loki",
17232
+ ".next"
17233
+ ]);
17234
+ var JS_TEST_RE = /\.(test|spec)\.[cm]?[jt]sx?$/;
17235
+ var PY_TEST_RE = /^(test_.+|.+_test)\.py$/;
17236
+ var GO_TEST_RE = /_test\.go$/;
17237
+ function isTestFile(rel) {
17238
+ const name = basename8(rel);
17239
+ return JS_TEST_RE.test(name) || PY_TEST_RE.test(name) || GO_TEST_RE.test(name);
17240
+ }
17241
+
17242
+ // ../../loki-ts/src/features/speed/unit_mode.ts
17243
+ import { closeSync as closeSync6, constants, fstatSync as fstatSync4, lstatSync as lstatSync5, openSync as openSync6, readSync as readSync4, realpathSync as realpathSync13 } from "fs";
17244
+ var MAX_SPEC_BYTES = 1024 * 1024;
17245
+ var MAX_TOKEN_BUDGET = 1e9;
17246
+ var norm2 = (p) => p.replace(/^\.\//, "");
17247
+ var CTRL = /[\x00-\x1f\x7f]/;
17248
+ var strs = (v) => Array.isArray(v) && v.every((x) => typeof x === "string" && !CTRL.test(x)) ? v.map(norm2) : null;
17249
+ function readBounded(path) {
17250
+ const l = lstatSync5(path);
17251
+ if (l.isSymbolicLink() || !l.isFile() || l.size > MAX_SPEC_BYTES)
17252
+ return null;
17253
+ const fd = openSync6(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
17254
+ try {
17255
+ const st = fstatSync4(fd);
17256
+ if (!st.isFile() || st.size > MAX_SPEC_BYTES)
17257
+ return null;
17258
+ const buf = Buffer.alloc(st.size + 1);
17259
+ let n2 = 0;
17260
+ for (;; ) {
17261
+ const r = readSync4(fd, buf, n2, buf.length - n2, null);
17262
+ if (r <= 0)
17263
+ break;
17264
+ n2 += r;
17265
+ if (n2 >= buf.length)
17266
+ return null;
17267
+ }
17268
+ return buf.subarray(0, n2).toString("utf8");
17269
+ } finally {
17270
+ closeSync6(fd);
17271
+ }
17272
+ }
17273
+ function parseText(txt) {
17274
+ try {
17275
+ const j = JSON.parse(txt), ws = strs(j.writeSet), pack = strs(j.pack), tb = j.tokenBudget;
17276
+ if (typeof j.id !== "string" || !j.id || !ws || ws.length === 0 || !pack || typeof tb !== "number" || !Number.isFinite(tb) || !(tb > 0) || tb > MAX_TOKEN_BUDGET)
17277
+ return null;
17278
+ return { id: j.id, writeSet: ws, pack, tokenBudget: tb };
17279
+ } catch {
17280
+ return null;
17281
+ }
17282
+ }
17283
+ function parseSpec(path) {
17284
+ try {
17285
+ const txt = readBounded(path);
17286
+ return txt === null ? null : parseText(txt);
17287
+ } catch {
17288
+ return null;
17289
+ }
17290
+ }
17291
+ var memo = new Map;
17292
+ function unitSpec(env = process.env) {
17293
+ const path = env["LOKI_UNIT_SPEC"], frozen = env["LOKI_UNIT_SPEC_FROZEN"];
17294
+ if (env["LOKI_SPEED"] === "0" || !path)
17295
+ return null;
17296
+ if (frozen !== undefined)
17297
+ return frozen.length <= MAX_SPEC_BYTES ? parseText(frozen) : null;
17298
+ if (!memo.has(path))
17299
+ memo.set(path, parseSpec(path));
17300
+ return memo.get(path) ?? null;
17301
+ }
17302
+ var inWriteSet = (spec, f) => spec.writeSet.some((w) => w.endsWith("/") ? f.startsWith(w) : f === w);
17303
+ function unitFlags(staged, env = process.env, pre = {}) {
17304
+ const s = unitSpec(env);
17305
+ return s ? staged.filter(({ f }) => !inWriteSet(s, f) && !Object.hasOwn(pre, f)).map(({ f }) => f) : null;
17306
+ }
17307
+
17308
+ // ../../loki-ts/src/e10ext/scope.ts
17309
+ var SCOPE_UNDETERMINED = "scope not determined; all edits committed";
17310
+ var outsideNote = (f) => `outside stated scope: ${f}`;
17311
+ var esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17312
+ var mentions = (text2, name) => new RegExp(`(?<![\\w./-])${esc(name)}(?![\\w-]|\\.\\w)`).test(text2);
17313
+ var STOP = new Set(["the", "and", "for", "with", "that", "this", "all", "any", "src", "from", "into", "each", "across", "apply", "add", "use", "make"]);
17314
+ var stem = (w) => w.length > 3 && w.endsWith("s") ? w.slice(0, -1) : w;
17315
+ var surfaceTokens = (text2) => new Set((text2.toLowerCase().match(/[a-z][a-z0-9_-]{2,}/g) ?? []).filter((w) => !STOP.has(w)).map(stem));
17316
+ var dirSegs = (f) => f.split("/").slice(0, -1).map((d) => stem(d.toLowerCase()));
17317
+ function contractText(o) {
17318
+ const c = o.intake?.contract_snapshot?.contract;
17319
+ return Array.isArray(c?.criteria) ? c.criteria.map((x) => typeof x?.text === "string" ? x.text : "").join(`
17320
+ `) : "";
17321
+ }
17322
+ function outsideScope(o, staged) {
17323
+ const rel = Array.isArray(o.plan?.relevant_files) ? o.plan.relevant_files.filter((x) => typeof x === "string") : [];
17324
+ const plan = typeof o.plan?.plan === "string" ? o.plan.plan : "";
17325
+ if (rel.length === 0 && plan.trim() === "")
17326
+ return null;
17327
+ const text2 = `${plan}
17328
+ ${typeof o.intake?.task === "string" ? o.intake.task : ""}
17329
+ ${contractText(o)}`, pre = o.intake?.preexisting_dirty ?? {};
17330
+ const toks = surfaceTokens(text2), plannedDirs = new Set(rel.map((r) => dirname7(r)).filter((d) => d !== "."));
17331
+ return staged.filter(({ st, f }) => st !== "A" && !isTestFile(f) && !Object.hasOwn(pre, f) && !rel.includes(f) && !mentions(text2, f) && !mentions(text2, basename9(f)) && !plannedDirs.has(dirname7(f)) && !dirSegs(f).some((d) => toks.has(d))).map(({ f }) => f);
17332
+ }
17333
+ function flagOutsideScope(o, staged) {
17334
+ const unit = unitFlags(staged, process.env, o.intake?.preexisting_dirty ?? {});
17335
+ const un = outsideScope(o, staged), seen = new Set((unit ?? []).map(outsideNote));
17336
+ return [...(unit ?? []).map(outsideNote), ...un ? un.map(outsideNote).filter((n2) => !seen.has(n2)) : staged.length > 0 ? [SCOPE_UNDETERMINED] : []];
17337
+ }
17338
+
17339
+ // ../../loki-ts/src/util/receipt_signer.ts
17340
+ var RECEIPT_SIGNER_BASENAME = "receipt-ed25519.pem";
17341
+
17342
+ // ../../loki-ts/src/util/shell.ts
17343
+ var MAX_STDOUT_BYTES = 16 * 1024 * 1024;
17344
+ async function readStreamCapped(stream, maxBytes = MAX_STDOUT_BYTES) {
17345
+ const reader = stream.getReader();
17346
+ const decoder = new TextDecoder;
17347
+ let out2 = "";
17348
+ let total = 0;
17349
+ try {
17350
+ while (total < maxBytes) {
17351
+ const { done, value } = await reader.read();
17352
+ if (done)
17353
+ break;
17354
+ if (!value)
17355
+ continue;
17356
+ total += value.byteLength;
17357
+ if (total > maxBytes) {
17358
+ const keep = value.byteLength - (total - maxBytes);
17359
+ out2 += decoder.decode(value.subarray(0, keep), { stream: true });
17360
+ break;
17361
+ }
17362
+ out2 += decoder.decode(value, { stream: true });
17363
+ }
17364
+ out2 += decoder.decode();
17365
+ } finally {
17366
+ try {
17367
+ await reader.cancel();
17368
+ } catch {}
17369
+ reader.releaseLock();
17370
+ }
17371
+ return out2;
17372
+ }
17373
+ async function run(argv, opts = {}) {
17374
+ const proc = Bun.spawn({
17375
+ cmd: [...argv],
17376
+ stdout: "pipe",
17377
+ stderr: "pipe",
17378
+ env: opts.env ? { ...process.env, ...opts.env } : process.env,
17379
+ cwd: opts.cwd
17380
+ });
17381
+ let timer;
17382
+ let killTimer;
17383
+ if (opts.timeoutMs && opts.timeoutMs > 0) {
17384
+ timer = setTimeout(() => {
17385
+ try {
17386
+ proc.kill("SIGTERM");
17387
+ } catch {}
17388
+ killTimer = setTimeout(() => {
17389
+ try {
17390
+ proc.kill("SIGKILL");
17391
+ } catch {}
17392
+ }, 2000);
17393
+ }, opts.timeoutMs);
17394
+ }
17395
+ try {
17396
+ const [stdout, stderr, exitCode] = await Promise.all([
17397
+ readStreamCapped(proc.stdout),
17398
+ new Response(proc.stderr).text(),
17399
+ proc.exited
17400
+ ]);
17401
+ return { stdout, stderr, exitCode };
17402
+ } finally {
17403
+ if (timer)
17404
+ clearTimeout(timer);
17405
+ if (killTimer)
17406
+ clearTimeout(killTimer);
17407
+ }
17408
+ }
17409
+
17334
17410
  // ../../loki-ts/src/features/visual_evidence.ts
17411
+ import { spawn as spawn5 } from "child_process";
17335
17412
  import { createHash as createHash6, randomBytes as randomBytes2 } from "crypto";
17336
17413
  import { existsSync as existsSync15, lstatSync as lstatSync6, mkdirSync as mkdirSync7, readdirSync as readdirSync9, readFileSync as readFileSync20, writeFileSync as writeFileSync8 } from "fs";
17337
17414
  import { createServer } from "net";
@@ -18015,7 +18092,7 @@ var commitStage = {
18015
18092
  return sat;
18016
18093
  if (drop.length > 0 && (await git(ctx, ["--literal-pathspecs", "reset", "-q", ctx.baseSha, "--", ...drop.map(({ f }) => f)])).code !== 0)
18017
18094
  return { status: "failed", data: {}, reason: "git reset failed" };
18018
- const dropped = new Set(drop.map(({ f }) => f)), notes = flagOutsideScope(ctx.outputs(), staged.filter(({ f }) => !dropped.has(f)));
18095
+ const dropped2 = new Set(drop.map(({ f }) => f)), notes = flagOutsideScope(ctx.outputs(), staged.filter(({ f }) => !dropped2.has(f)));
18019
18096
  if ((await git(ctx, ["diff", "--cached", "--quiet"])).code === 0)
18020
18097
  return { status: "completed", data: { committed: false, scope_notes: notes } };
18021
18098
  const title = (str3(ctx.outputs().intake?.title) ?? `run ${ctx.runId}`).split(`
@@ -18105,6 +18182,8 @@ var sealStage = {
18105
18182
  }
18106
18183
  if (wallNotRun > 0)
18107
18184
  notProven.add(`wall base run not_run: ${wallNotRun}`);
18185
+ for (const d of Array.isArray(o.wall?.discarded) ? o.wall.discarded : [])
18186
+ notProven.add(`wall test discarded: ${String(d.file)} (${String(d.reason)})`);
18108
18187
  if (wallNotRun > 0) {
18109
18188
  try {
18110
18189
  const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) => basename10(String(f.path))));
@@ -18115,7 +18194,7 @@ var sealStage = {
18115
18194
  }
18116
18195
  if (!diffOk)
18117
18196
  notProven.add("diff not computed (git diff-tree failed)");
18118
- const dropped = new Set;
18197
+ const dropped2 = new Set;
18119
18198
  const tcs = o.verify?.["test_counts"];
18120
18199
  const cnt = (x) => {
18121
18200
  const c = x;
@@ -18130,7 +18209,7 @@ var sealStage = {
18130
18209
  const mine = assertDeltaNotes(ctx.repoDir, ctx.baseSha, head, t, str3(o.intake?.task) ?? "", cnt(tc?.b), cnt(tc?.h));
18131
18210
  if (!mine || mine.length !== vl.length || mine.some((n2) => !vl.includes(n2)))
18132
18211
  for (const v of vl)
18133
- dropped.add(v);
18212
+ dropped2.add(v);
18134
18213
  }
18135
18214
  for (const c of checks)
18136
18215
  if (c.result === "not_run")
@@ -18138,7 +18217,7 @@ var sealStage = {
18138
18217
  for (const f of strs2(o.verify?.flaky))
18139
18218
  notProven.add(`flaky test: ${f}`);
18140
18219
  for (const n2 of verifyNotProven)
18141
- if (!dropped.has(n2))
18220
+ if (!dropped2.has(n2))
18142
18221
  notProven.add(n2);
18143
18222
  for (const n2 of [...strs2(o.commit?.not_proven), ...strs2(o.commit?.scope_notes)])
18144
18223
  notProven.add(n2);
@@ -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`);