loki-mode 10.8.0 → 10.9.1

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 (147) hide show
  1. package/README.md +2 -2
  2. package/SKILL.md +2 -2
  3. package/VERSION +1 -1
  4. package/autonomy/loki +16 -31
  5. package/autonomy/tui.sh +5 -1
  6. package/dashboard/__init__.py +1 -1
  7. package/dashboard/api_phases.py +1 -1
  8. package/dashboard/api_start.py +0 -1
  9. package/dashboard/server.py +22 -138
  10. package/docs/COCKPIT-SPEC.md +3 -1
  11. package/docs/DASHBOARD-9.12-EVIDENCE.md +1 -1
  12. package/docs/DASHBOARD-ARCHITECTURE.md +13 -11
  13. package/docs/DASHBOARD_V2_CHECKLIST.md +2 -0
  14. package/docs/INSTALLATION.md +12 -11
  15. package/docs/MERGE-DEDUP-MAP.md +1 -1
  16. package/docs/MERGE-ROUTE-MAP.md +1 -1
  17. package/docs/MERGE3-PLAN.md +2 -2
  18. package/docs/R3-COST-OBSERVABILITY-DESIGN.md +2 -2
  19. package/docs/R4-TRUST-TRAJECTORY-DESIGN.md +1 -1
  20. package/docs/R5-AUTO-WIKI-DESIGN.md +1 -1
  21. package/docs/R6-ROLLBACK-CHECKPOINT-PLAN.md +1 -1
  22. package/docs/RARV-C-100X-PLAN.md +3 -3
  23. package/docs/SONNET5-DEFAULT-PLAN.md +2 -2
  24. package/docs/SYNERGY-TASKS.md +1 -1
  25. package/docs/TOOL-INTEGRATION.md +1 -1
  26. package/docs/TOP-100-BACKLOG.md +1 -1
  27. package/docs/alternative-installations.md +1 -1
  28. package/docs/architecture/ADR-001-runtime-migration.md +1 -1
  29. package/docs/architecture/DASHBOARD_V2_ARCHITECTURE.md +2 -0
  30. package/docs/audit-logging.md +2 -0
  31. package/docs/authentication.md +2 -0
  32. package/docs/authorization.md +2 -0
  33. package/docs/certification/02-enterprise-features/lab.md +2 -0
  34. package/docs/certification/02-enterprise-features/lesson.md +2 -0
  35. package/docs/certification/04-production-deployment/lab.md +2 -0
  36. package/docs/certification/04-production-deployment/lesson.md +2 -0
  37. package/docs/control-plane-migration.md +97 -0
  38. package/docs/dashboard-guide.md +6 -7
  39. package/docs/dev/release-checklist.md +9 -11
  40. package/docs/enterprise/integration-cookbook.md +2 -0
  41. package/docs/enterprise/migration.md +1 -1
  42. package/docs/enterprise/performance.md +3 -1
  43. package/docs/enterprise/sdk-guide.md +2 -0
  44. package/docs/enterprise/security.md +2 -0
  45. package/docs/plans/FORGE-AUTONOMOUS-QUEUE.md +2 -2
  46. package/docs/research-2026-07/OBSERVABILITY-TOOL-SPEC.md +1 -1
  47. package/docs/retrospectives/v7.5.15-honesty-audit.md +2 -2
  48. package/docs/test-scenarios/enterprise-scenarios.md +2 -2
  49. package/docs/v10/ARCHITECT-CUTS.md +9 -9
  50. package/docs/v10/BACKLOG.md +7 -7
  51. package/docs/v10/BOARD.md +25 -25
  52. package/docs/v10/CONTROL-PLANE.md +4 -4
  53. package/docs/v10/CP-ENTERPRISE-UI.md +66 -66
  54. package/docs/v10/D51-PHASE-B.md +1 -1
  55. package/docs/v10/DECISIONS.md +3 -3
  56. package/docs/v10/DEPS.md +10 -10
  57. package/docs/v10/FAILURE-CLASSES.md +45 -1
  58. package/docs/v10/LEGACY-REMOVAL.md +2 -2
  59. package/docs/v10/SWARM.md +2 -2
  60. package/loki-ts/dist/cockpit.js +11 -11
  61. package/loki-ts/dist/loki.js +596 -579
  62. package/magic/core/design_tokens.py +1 -2
  63. package/magic/core/generator.py +2 -2
  64. package/magic/tokens/README.md +1 -3
  65. package/mcp/__init__.py +1 -1
  66. package/package.json +2 -7
  67. package/packages/control-plane/dist/server.js +1683 -843
  68. package/packages/control-plane/src/server/legacy/routes-audit.ts +50 -0
  69. package/packages/control-plane/src/server/legacy/routes-control.ts +26 -0
  70. package/packages/control-plane/src/server/legacy/routes-memory.ts +30 -0
  71. package/packages/control-plane/src/server/legacy/routes.ts +58 -58
  72. package/packages/control-plane/src/server/legacy/shim.ts +10 -4
  73. package/packages/control-plane/src/server/routes/checkpoints.ts +171 -0
  74. package/packages/control-plane/src/server/routes/index.ts +4 -1
  75. package/packages/control-plane/src/server/routes/memory.ts +219 -0
  76. package/packages/control-plane/src/server/routes/session_control.ts +155 -0
  77. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  78. package/references/magic-modules-patterns.md +2 -2
  79. package/skills/factory-operations.md +3 -3
  80. package/skills/magic-modules.md +1 -1
  81. package/skills/release-cadence.md +1 -1
  82. package/tools/audit-docs.py +1 -1
  83. package/tools/index-codebase.py +1 -1
  84. package/web-app/dist/assets/{AdminPage-DDDvUz0N.js → AdminPage-DqMnfZrH.js} +1 -1
  85. package/web-app/dist/assets/{Avatar-Dcdz46i6.js → Avatar-BEof29du.js} +1 -1
  86. package/web-app/dist/assets/{Badge-BB24cCpK.js → Badge-WD53OL5m.js} +1 -1
  87. package/web-app/dist/assets/{Button-D0-DmPZL.js → Button-Rzk5flhU.js} +1 -1
  88. package/web-app/dist/assets/{CockpitPage-CdjpU2JW.js → CockpitPage-Bqz3PCMh.js} +1 -1
  89. package/web-app/dist/assets/{ComparePage-DT5U0y0d.js → ComparePage-Byo2NzAZ.js} +1 -1
  90. package/web-app/dist/assets/{ErrorBoundary-lEHcl0EO.js → ErrorBoundary-BuE0kH3W.js} +1 -1
  91. package/web-app/dist/assets/{EvidenceReceiptPanel-GpnWvdCr.js → EvidenceReceiptPanel-CKFHOPBx.js} +1 -1
  92. package/web-app/dist/assets/{GitHubIssuesPanel-C7VOh1B7.js → GitHubIssuesPanel-Df_kTMqI.js} +1 -1
  93. package/web-app/dist/assets/{GitHubPRsPanel-G_kOuINv.js → GitHubPRsPanel-CouvcKrZ.js} +1 -1
  94. package/web-app/dist/assets/{HomePage-PqRisOny.js → HomePage-dEpLzj0x.js} +2 -2
  95. package/web-app/dist/assets/{LoginPage-DU7pFAkf.js → LoginPage-6DVjUND5.js} +1 -1
  96. package/web-app/dist/assets/{MagicPage-C5JWIWMk.js → MagicPage-BDQ9MOfY.js} +1 -1
  97. package/web-app/dist/assets/{MetricsPage-CbIGS_qo.js → MetricsPage-CXiJ04G_.js} +1 -1
  98. package/web-app/dist/assets/{NotFoundPage-vU1pvdSt.js → NotFoundPage-BaXhZvyj.js} +1 -1
  99. package/web-app/dist/assets/{ProjectPage-SsixIPPv.js → ProjectPage-BzE4V7Ti.js} +3 -3
  100. package/web-app/dist/assets/{ProjectsPage-BfQKSR9k.js → ProjectsPage-ipf6TLMX.js} +1 -1
  101. package/web-app/dist/assets/{SettingsPage-B8YfXAom.js → SettingsPage-BDxkUKN6.js} +1 -1
  102. package/web-app/dist/assets/{ShowcasePage-Ba407f2-.js → ShowcasePage-YQXD72SM.js} +1 -1
  103. package/web-app/dist/assets/{SystemSettingsPage-DzNQhhBS.js → SystemSettingsPage-Dou7zHS0.js} +1 -1
  104. package/web-app/dist/assets/{TeamsPage-tqUnpZ15.js → TeamsPage-CQarfcYr.js} +1 -1
  105. package/web-app/dist/assets/{TemplatesPage-ClB5EpI3.js → TemplatesPage-CPi1R8Xj.js} +1 -1
  106. package/web-app/dist/assets/{TerminalOutput-CBvpYfEu.js → TerminalOutput-WiGhWdYD.js} +1 -1
  107. package/web-app/dist/assets/{activity-C1s0L_X9.js → activity-DOa7ejQA.js} +1 -1
  108. package/web-app/dist/assets/{bell-DMEiXr73.js → bell-CX-VAlYN.js} +1 -1
  109. package/web-app/dist/assets/{bot-BF_xIOxu.js → bot-EDXaZmHc.js} +1 -1
  110. package/web-app/dist/assets/{check-Dv1Ynvrs.js → check-CnBdKk3L.js} +1 -1
  111. package/web-app/dist/assets/{chevron-left-Ddhsod7S.js → chevron-left-BJ_ydRQW.js} +1 -1
  112. package/web-app/dist/assets/{circle-alert-OATHUkBZ.js → circle-alert-tz8ufhc_.js} +1 -1
  113. package/web-app/dist/assets/{clock-C1Ltva07.js → clock-DFBZPSsH.js} +1 -1
  114. package/web-app/dist/assets/{cloud-B-S4oOy9.js → cloud-uy3p_5Q7.js} +1 -1
  115. package/web-app/dist/assets/{code-xml-D3Femn_F.js → code-xml-3CbYD9YL.js} +1 -1
  116. package/web-app/dist/assets/{database-DBSeFgO3.js → database-YiNBXc9c.js} +1 -1
  117. package/web-app/dist/assets/{dollar-sign-sgDgNuGE.js → dollar-sign-D0wBa4Cv.js} +1 -1
  118. package/web-app/dist/assets/{file-code-corner-C-MbdZEc.js → file-code-corner-DDdZVZ5v.js} +1 -1
  119. package/web-app/dist/assets/{file-plus-CCB6yqXv.js → file-plus-D09LTBqt.js} +1 -1
  120. package/web-app/dist/assets/{globe-DlbrNAj1.js → globe-BirTPwff.js} +1 -1
  121. package/web-app/dist/assets/{hammer-B-DWm695.js → hammer-DaE5qgnd.js} +1 -1
  122. package/web-app/dist/assets/{index-Dvny-1SU.js → index-D8t7KnQf.js} +3 -3
  123. package/web-app/dist/assets/{layers-99qsKpOT.js → layers-DVVrckRY.js} +1 -1
  124. package/web-app/dist/assets/{loader-circle-ZvZyZl3J.js → loader-circle-2-nMGmcb.js} +1 -1
  125. package/web-app/dist/assets/{lock-DosGjcxP.js → lock-D0Xj43pC.js} +1 -1
  126. package/web-app/dist/assets/{package-C-PumesN.js → package-BB7S3egm.js} +1 -1
  127. package/web-app/dist/assets/{plus-CTYYskTB.js → plus-BLhoMrn3.js} +1 -1
  128. package/web-app/dist/assets/{refresh-cw-qW2ztTFS.js → refresh-cw-DMc2uPLw.js} +1 -1
  129. package/web-app/dist/assets/{rotate-ccw-ZIKrf-vO.js → rotate-ccw-DM3IP9x7.js} +1 -1
  130. package/web-app/dist/assets/{scroll-text-BamlXjAT.js → scroll-text-lH2uW7rB.js} +1 -1
  131. package/web-app/dist/assets/{server-BljXb79I.js → server-BkerZcdR.js} +1 -1
  132. package/web-app/dist/assets/{shield-alert-Dr6eT9c_.js → shield-alert-B8ohcUfu.js} +1 -1
  133. package/web-app/dist/assets/{thumbs-up-v3oBXZgU.js → thumbs-up-CTIxLzPD.js} +1 -1
  134. package/web-app/dist/assets/{trash-2-BOuIrCpi.js → trash-2-C3zIpsH5.js} +1 -1
  135. package/web-app/dist/assets/{trending-down-Dp7jMCgH.js → trending-down-BTT7wV4Y.js} +1 -1
  136. package/web-app/dist/assets/{trending-up-CDiur-nP.js → trending-up-BzHdCDvF.js} +1 -1
  137. package/web-app/dist/assets/{upload-DxIHXhNE.js → upload-HxR34-A8.js} +1 -1
  138. package/web-app/dist/assets/{usePolling-D2Uu-YZb.js → usePolling-DDj6Fupy.js} +1 -1
  139. package/web-app/dist/assets/{user-BVWCUAfF.js → user-qH5y9yNy.js} +1 -1
  140. package/web-app/dist/index.html +1 -1
  141. package/dashboard/static/assets/mermaid.min.js +0 -2030
  142. package/dashboard/static/cost.html +0 -344
  143. package/dashboard/static/favicon.svg +0 -5
  144. package/dashboard/static/index.html +0 -15577
  145. package/dashboard/static/proofs.html +0 -194
  146. package/dashboard/static/start.html +0 -131
  147. package/dashboard/static/trust.html +0 -325
@@ -879,16 +879,16 @@ var require_Collection = __commonJS((exports) => {
879
879
 
880
880
  // node_modules/yaml/dist/stringify/stringifyComment.js
881
881
  var require_stringifyComment = __commonJS((exports) => {
882
- var stringifyComment = (str2) => str2.replace(/^(?!$)(?: $)?/gm, "#");
882
+ var stringifyComment = (str3) => str3.replace(/^(?!$)(?: $)?/gm, "#");
883
883
  function indentComment(comment, indent) {
884
884
  if (/^\n+$/.test(comment))
885
885
  return comment.substring(1);
886
886
  return indent ? comment.replace(/^(?! *$)/gm, indent) : comment;
887
887
  }
888
- var lineComment = (str2, indent, comment) => str2.endsWith(`
888
+ var lineComment = (str3, indent, comment) => str3.endsWith(`
889
889
  `) ? indentComment(comment, indent) : comment.includes(`
890
890
  `) ? `
891
- ` + indentComment(comment, indent) : (str2.endsWith(" ") ? "" : " ") + comment;
891
+ ` + indentComment(comment, indent) : (str3.endsWith(" ") ? "" : " ") + comment;
892
892
  exports.indentComment = indentComment;
893
893
  exports.lineComment = lineComment;
894
894
  exports.stringifyComment = stringifyComment;
@@ -1040,16 +1040,16 @@ var require_stringifyString = __commonJS((exports) => {
1040
1040
  lineWidth: ctx.options.lineWidth,
1041
1041
  minContentWidth: ctx.options.minContentWidth
1042
1042
  });
1043
- var containsDocumentMarker = (str2) => /^(%|---|\.\.\.)/m.test(str2);
1044
- function lineLengthOverLimit(str2, lineWidth, indentLength) {
1043
+ var containsDocumentMarker = (str3) => /^(%|---|\.\.\.)/m.test(str3);
1044
+ function lineLengthOverLimit(str3, lineWidth, indentLength) {
1045
1045
  if (!lineWidth || lineWidth < 0)
1046
1046
  return false;
1047
1047
  const limit = lineWidth - indentLength;
1048
- const strLen = str2.length;
1048
+ const strLen = str3.length;
1049
1049
  if (strLen <= limit)
1050
1050
  return false;
1051
1051
  for (let i = 0, start = 0;i < strLen; ++i) {
1052
- if (str2[i] === `
1052
+ if (str3[i] === `
1053
1053
  `) {
1054
1054
  if (i - start > limit)
1055
1055
  return true;
@@ -1067,11 +1067,11 @@ var require_stringifyString = __commonJS((exports) => {
1067
1067
  const { implicitKey } = ctx;
1068
1068
  const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength;
1069
1069
  const indent = ctx.indent || (containsDocumentMarker(value) ? " " : "");
1070
- let str2 = "";
1070
+ let str3 = "";
1071
1071
  let start = 0;
1072
1072
  for (let i = 0, ch = json[i];ch; ch = json[++i]) {
1073
1073
  if (ch === " " && json[i + 1] === "\\" && json[i + 2] === "n") {
1074
- str2 += json.slice(start, i) + "\\ ";
1074
+ str3 += json.slice(start, i) + "\\ ";
1075
1075
  i += 1;
1076
1076
  start = i;
1077
1077
  ch = "\\";
@@ -1080,38 +1080,38 @@ var require_stringifyString = __commonJS((exports) => {
1080
1080
  switch (json[i + 1]) {
1081
1081
  case "u":
1082
1082
  {
1083
- str2 += json.slice(start, i);
1083
+ str3 += json.slice(start, i);
1084
1084
  const code = json.substr(i + 2, 4);
1085
1085
  switch (code) {
1086
1086
  case "0000":
1087
- str2 += "\\0";
1087
+ str3 += "\\0";
1088
1088
  break;
1089
1089
  case "0007":
1090
- str2 += "\\a";
1090
+ str3 += "\\a";
1091
1091
  break;
1092
1092
  case "000b":
1093
- str2 += "\\v";
1093
+ str3 += "\\v";
1094
1094
  break;
1095
1095
  case "001b":
1096
- str2 += "\\e";
1096
+ str3 += "\\e";
1097
1097
  break;
1098
1098
  case "0085":
1099
- str2 += "\\N";
1099
+ str3 += "\\N";
1100
1100
  break;
1101
1101
  case "00a0":
1102
- str2 += "\\_";
1102
+ str3 += "\\_";
1103
1103
  break;
1104
1104
  case "2028":
1105
- str2 += "\\L";
1105
+ str3 += "\\L";
1106
1106
  break;
1107
1107
  case "2029":
1108
- str2 += "\\P";
1108
+ str3 += "\\P";
1109
1109
  break;
1110
1110
  default:
1111
1111
  if (code.substr(0, 2) === "00")
1112
- str2 += "\\x" + code.substr(2);
1112
+ str3 += "\\x" + code.substr(2);
1113
1113
  else
1114
- str2 += json.substr(i, 6);
1114
+ str3 += json.substr(i, 6);
1115
1115
  }
1116
1116
  i += 5;
1117
1117
  start = i + 1;
@@ -1121,17 +1121,17 @@ var require_stringifyString = __commonJS((exports) => {
1121
1121
  if (implicitKey || json[i + 2] === '"' || json.length < minMultiLineLength) {
1122
1122
  i += 1;
1123
1123
  } else {
1124
- str2 += json.slice(start, i) + `
1124
+ str3 += json.slice(start, i) + `
1125
1125
 
1126
1126
  `;
1127
1127
  while (json[i + 2] === "\\" && json[i + 3] === "n" && json[i + 4] !== '"') {
1128
- str2 += `
1128
+ str3 += `
1129
1129
  `;
1130
1130
  i += 2;
1131
1131
  }
1132
- str2 += indent;
1132
+ str3 += indent;
1133
1133
  if (json[i + 2] === " ")
1134
- str2 += "\\";
1134
+ str3 += "\\";
1135
1135
  i += 1;
1136
1136
  start = i + 1;
1137
1137
  }
@@ -1140,8 +1140,8 @@ var require_stringifyString = __commonJS((exports) => {
1140
1140
  i += 1;
1141
1141
  }
1142
1142
  }
1143
- str2 = start ? str2 + json.slice(start) : json;
1144
- return implicitKey ? str2 : foldFlowLines.foldFlowLines(str2, indent, foldFlowLines.FOLD_QUOTED, getFoldOptions(ctx, false));
1143
+ str3 = start ? str3 + json.slice(start) : json;
1144
+ return implicitKey ? str3 : foldFlowLines.foldFlowLines(str3, indent, foldFlowLines.FOLD_QUOTED, getFoldOptions(ctx, false));
1145
1145
  }
1146
1146
  function singleQuotedString(value, ctx) {
1147
1147
  if (ctx.options.singleQuote === false || ctx.implicitKey && value.includes(`
@@ -1283,15 +1283,15 @@ ${indent}${start}${value}${end}`;
1283
1283
  return quotedString(value, ctx);
1284
1284
  }
1285
1285
  }
1286
- const str2 = value.replace(/\n+/g, `$&
1286
+ const str3 = value.replace(/\n+/g, `$&
1287
1287
  ${indent}`);
1288
1288
  if (actualString) {
1289
- const test = (tag) => tag.default && tag.tag !== "tag:yaml.org,2002:str" && tag.test?.test(str2);
1289
+ const test = (tag) => tag.default && tag.tag !== "tag:yaml.org,2002:str" && tag.test?.test(str3);
1290
1290
  const { compat, tags } = ctx.doc.schema;
1291
1291
  if (tags.some(test) || compat?.some(test))
1292
1292
  return quotedString(value, ctx);
1293
1293
  }
1294
- return implicitKey ? str2 : foldFlowLines.foldFlowLines(str2, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false));
1294
+ return implicitKey ? str3 : foldFlowLines.foldFlowLines(str3, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false));
1295
1295
  }
1296
1296
  function stringifyString(item, ctx, onComment, onChompKeep) {
1297
1297
  const { implicitKey, inFlow } = ctx;
@@ -1440,11 +1440,11 @@ var require_stringify = __commonJS((exports) => {
1440
1440
  const props = stringifyProps(node, tagObj, ctx);
1441
1441
  if (props.length > 0)
1442
1442
  ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1;
1443
- const str2 = typeof tagObj.stringify === "function" ? tagObj.stringify(node, ctx, onComment, onChompKeep) : identity.isScalar(node) ? stringifyString.stringifyString(node, ctx, onComment, onChompKeep) : node.toString(ctx, onComment, onChompKeep);
1443
+ const str3 = typeof tagObj.stringify === "function" ? tagObj.stringify(node, ctx, onComment, onChompKeep) : identity.isScalar(node) ? stringifyString.stringifyString(node, ctx, onComment, onChompKeep) : node.toString(ctx, onComment, onChompKeep);
1444
1444
  if (!props)
1445
- return str2;
1446
- return identity.isScalar(node) || str2[0] === "{" || str2[0] === "[" ? `${props} ${str2}` : `${props}
1447
- ${ctx.indent}${str2}`;
1445
+ return str3;
1446
+ return identity.isScalar(node) || str3[0] === "{" || str3[0] === "[" ? `${props} ${str3}` : `${props}
1447
+ ${ctx.indent}${str3}`;
1448
1448
  }
1449
1449
  exports.createStringifyContext = createStringifyContext;
1450
1450
  exports.stringify = stringify;
@@ -1476,8 +1476,8 @@ var require_stringifyPair = __commonJS((exports) => {
1476
1476
  });
1477
1477
  let keyCommentDone = false;
1478
1478
  let chompKeep = false;
1479
- let str2 = stringify.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
1480
- if (!explicitKey && !ctx.inFlow && str2.length > 1024) {
1479
+ let str3 = stringify.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
1480
+ if (!explicitKey && !ctx.inFlow && str3.length > 1024) {
1481
1481
  if (simpleKeys)
1482
1482
  throw new Error("With simple keys, single line scalar must not span more than 1024 characters");
1483
1483
  explicitKey = true;
@@ -1486,27 +1486,27 @@ var require_stringifyPair = __commonJS((exports) => {
1486
1486
  if (allNullValues || value == null) {
1487
1487
  if (keyCommentDone && onComment)
1488
1488
  onComment();
1489
- return str2 === "" ? "?" : explicitKey ? `? ${str2}` : str2;
1489
+ return str3 === "" ? "?" : explicitKey ? `? ${str3}` : str3;
1490
1490
  }
1491
1491
  } else if (allNullValues && !simpleKeys || value == null && explicitKey) {
1492
- str2 = `? ${str2}`;
1492
+ str3 = `? ${str3}`;
1493
1493
  if (keyComment && !keyCommentDone) {
1494
- str2 += stringifyComment.lineComment(str2, ctx.indent, commentString(keyComment));
1494
+ str3 += stringifyComment.lineComment(str3, ctx.indent, commentString(keyComment));
1495
1495
  } else if (chompKeep && onChompKeep)
1496
1496
  onChompKeep();
1497
- return str2;
1497
+ return str3;
1498
1498
  }
1499
1499
  if (keyCommentDone)
1500
1500
  keyComment = null;
1501
1501
  if (explicitKey) {
1502
1502
  if (keyComment)
1503
- str2 += stringifyComment.lineComment(str2, ctx.indent, commentString(keyComment));
1504
- str2 = `? ${str2}
1503
+ str3 += stringifyComment.lineComment(str3, ctx.indent, commentString(keyComment));
1504
+ str3 = `? ${str3}
1505
1505
  ${indent}:`;
1506
1506
  } else {
1507
- str2 = `${str2}:`;
1507
+ str3 = `${str3}:`;
1508
1508
  if (keyComment)
1509
- str2 += stringifyComment.lineComment(str2, ctx.indent, commentString(keyComment));
1509
+ str3 += stringifyComment.lineComment(str3, ctx.indent, commentString(keyComment));
1510
1510
  }
1511
1511
  let vsb, vcb, valueComment;
1512
1512
  if (identity.isNode(value)) {
@@ -1522,7 +1522,7 @@ ${indent}:`;
1522
1522
  }
1523
1523
  ctx.implicitKey = false;
1524
1524
  if (!explicitKey && !keyComment && identity.isScalar(value))
1525
- ctx.indentAtStart = str2.length + 1;
1525
+ ctx.indentAtStart = str3.length + 1;
1526
1526
  chompKeep = false;
1527
1527
  if (!indentSeq && indentStep.length >= 2 && !ctx.inFlow && !explicitKey && identity.isSeq(value) && !value.flow && !value.tag && !value.anchor) {
1528
1528
  ctx.indent = ctx.indent.substring(2);
@@ -1572,16 +1572,16 @@ ${ctx.indent}`;
1572
1572
  `) {
1573
1573
  ws = "";
1574
1574
  }
1575
- str2 += ws + valueStr;
1575
+ str3 += ws + valueStr;
1576
1576
  if (ctx.inFlow) {
1577
1577
  if (valueCommentDone && onComment)
1578
1578
  onComment();
1579
1579
  } else if (valueComment && !valueCommentDone) {
1580
- str2 += stringifyComment.lineComment(str2, ctx.indent, commentString(valueComment));
1580
+ str3 += stringifyComment.lineComment(str3, ctx.indent, commentString(valueComment));
1581
1581
  } else if (chompKeep && onChompKeep) {
1582
1582
  onChompKeep();
1583
1583
  }
1584
- return str2;
1584
+ return str3;
1585
1585
  }
1586
1586
  exports.stringifyPair = stringifyPair;
1587
1587
  });
@@ -1794,33 +1794,33 @@ var require_stringifyCollection = __commonJS((exports) => {
1794
1794
  }
1795
1795
  }
1796
1796
  chompKeep = false;
1797
- let str3 = stringify.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
1797
+ let str4 = stringify.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
1798
1798
  if (comment2)
1799
- str3 += stringifyComment.lineComment(str3, itemIndent, commentString(comment2));
1799
+ str4 += stringifyComment.lineComment(str4, itemIndent, commentString(comment2));
1800
1800
  if (chompKeep && comment2)
1801
1801
  chompKeep = false;
1802
- lines.push(blockItemPrefix + str3);
1802
+ lines.push(blockItemPrefix + str4);
1803
1803
  }
1804
- let str2;
1804
+ let str3;
1805
1805
  if (lines.length === 0) {
1806
- str2 = flowChars.start + flowChars.end;
1806
+ str3 = flowChars.start + flowChars.end;
1807
1807
  } else {
1808
- str2 = lines[0];
1808
+ str3 = lines[0];
1809
1809
  for (let i = 1;i < lines.length; ++i) {
1810
1810
  const line2 = lines[i];
1811
- str2 += line2 ? `
1811
+ str3 += line2 ? `
1812
1812
  ${indent}${line2}` : `
1813
1813
  `;
1814
1814
  }
1815
1815
  }
1816
1816
  if (comment) {
1817
- str2 += `
1817
+ str3 += `
1818
1818
  ` + stringifyComment.indentComment(commentString(comment), indent);
1819
1819
  if (onComment)
1820
1820
  onComment();
1821
1821
  } else if (chompKeep && onChompKeep)
1822
1822
  onChompKeep();
1823
- return str2;
1823
+ return str3;
1824
1824
  }
1825
1825
  function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) {
1826
1826
  const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx;
@@ -1863,22 +1863,22 @@ ${indent}${line2}` : `
1863
1863
  }
1864
1864
  if (comment)
1865
1865
  reqNewline = true;
1866
- let str2 = stringify.stringify(item, itemCtx, () => comment = null);
1867
- reqNewline || (reqNewline = lines.length > linesAtValue || str2.includes(`
1866
+ let str3 = stringify.stringify(item, itemCtx, () => comment = null);
1867
+ reqNewline || (reqNewline = lines.length > linesAtValue || str3.includes(`
1868
1868
  `));
1869
1869
  if (i < items.length - 1) {
1870
- str2 += ",";
1870
+ str3 += ",";
1871
1871
  } else if (ctx.options.trailingComma) {
1872
1872
  if (ctx.options.lineWidth > 0) {
1873
- reqNewline || (reqNewline = lines.reduce((sum, line2) => sum + line2.length + 2, 2) + (str2.length + 2) > ctx.options.lineWidth);
1873
+ reqNewline || (reqNewline = lines.reduce((sum, line2) => sum + line2.length + 2, 2) + (str3.length + 2) > ctx.options.lineWidth);
1874
1874
  }
1875
1875
  if (reqNewline) {
1876
- str2 += ",";
1876
+ str3 += ",";
1877
1877
  }
1878
1878
  }
1879
1879
  if (comment)
1880
- str2 += stringifyComment.lineComment(str2, itemIndent, commentString(comment));
1881
- lines.push(str2);
1880
+ str3 += stringifyComment.lineComment(str3, itemIndent, commentString(comment));
1881
+ lines.push(str3);
1882
1882
  linesAtValue = lines.length;
1883
1883
  }
1884
1884
  const { start, end } = flowChars;
@@ -1890,12 +1890,12 @@ ${indent}${line2}` : `
1890
1890
  reqNewline = ctx.options.lineWidth > 0 && len > ctx.options.lineWidth;
1891
1891
  }
1892
1892
  if (reqNewline) {
1893
- let str2 = start;
1893
+ let str3 = start;
1894
1894
  for (const line2 of lines)
1895
- str2 += line2 ? `
1895
+ str3 += line2 ? `
1896
1896
  ${indentStep}${indent}${line2}` : `
1897
1897
  `;
1898
- return `${str2}
1898
+ return `${str3}
1899
1899
  ${indent}${end}`;
1900
1900
  } else {
1901
1901
  return `${start}${fcPadding}${lines.join(" ")}${fcPadding}${end}`;
@@ -2178,7 +2178,7 @@ var require_string = __commonJS((exports) => {
2178
2178
  identify: (value) => typeof value === "string",
2179
2179
  default: true,
2180
2180
  tag: "tag:yaml.org,2002:str",
2181
- resolve: (str2) => str2,
2181
+ resolve: (str3) => str3,
2182
2182
  stringify(item, ctx, onComment, onChompKeep) {
2183
2183
  ctx = Object.assign({ actualString: true }, ctx);
2184
2184
  return stringifyString.stringifyString(item, ctx, onComment, onChompKeep);
@@ -2210,7 +2210,7 @@ var require_bool = __commonJS((exports) => {
2210
2210
  default: true,
2211
2211
  tag: "tag:yaml.org,2002:bool",
2212
2212
  test: /^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/,
2213
- resolve: (str2) => new Scalar.Scalar(str2[0] === "t" || str2[0] === "T"),
2213
+ resolve: (str3) => new Scalar.Scalar(str3[0] === "t" || str3[0] === "T"),
2214
2214
  stringify({ source, value }, ctx) {
2215
2215
  if (source && boolTag.test.test(source)) {
2216
2216
  const sv = source[0] === "t" || source[0] === "T";
@@ -2256,7 +2256,7 @@ var require_float = __commonJS((exports) => {
2256
2256
  default: true,
2257
2257
  tag: "tag:yaml.org,2002:float",
2258
2258
  test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/,
2259
- resolve: (str2) => str2.slice(-3).toLowerCase() === "nan" ? NaN : str2[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
2259
+ resolve: (str3) => str3.slice(-3).toLowerCase() === "nan" ? NaN : str3[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
2260
2260
  stringify: stringifyNumber.stringifyNumber
2261
2261
  };
2262
2262
  var floatExp = {
@@ -2265,7 +2265,7 @@ var require_float = __commonJS((exports) => {
2265
2265
  tag: "tag:yaml.org,2002:float",
2266
2266
  format: "EXP",
2267
2267
  test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/,
2268
- resolve: (str2) => parseFloat(str2),
2268
+ resolve: (str3) => parseFloat(str3),
2269
2269
  stringify(node) {
2270
2270
  const num = Number(node.value);
2271
2271
  return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node);
@@ -2276,11 +2276,11 @@ var require_float = __commonJS((exports) => {
2276
2276
  default: true,
2277
2277
  tag: "tag:yaml.org,2002:float",
2278
2278
  test: /^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/,
2279
- resolve(str2) {
2280
- const node = new Scalar.Scalar(parseFloat(str2));
2281
- const dot = str2.indexOf(".");
2282
- if (dot !== -1 && str2[str2.length - 1] === "0")
2283
- node.minFractionDigits = str2.length - dot - 1;
2279
+ resolve(str3) {
2280
+ const node = new Scalar.Scalar(parseFloat(str3));
2281
+ const dot = str3.indexOf(".");
2282
+ if (dot !== -1 && str3[str3.length - 1] === "0")
2283
+ node.minFractionDigits = str3.length - dot - 1;
2284
2284
  return node;
2285
2285
  },
2286
2286
  stringify: stringifyNumber.stringifyNumber
@@ -2294,7 +2294,7 @@ var require_float = __commonJS((exports) => {
2294
2294
  var require_int = __commonJS((exports) => {
2295
2295
  var stringifyNumber = require_stringifyNumber();
2296
2296
  var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
2297
- var intResolve = (str2, offset, radix, { intAsBigInt }) => intAsBigInt ? BigInt(str2) : parseInt(str2.substring(offset), radix);
2297
+ var intResolve = (str3, offset, radix, { intAsBigInt }) => intAsBigInt ? BigInt(str3) : parseInt(str3.substring(offset), radix);
2298
2298
  function intStringify(node, radix, prefix) {
2299
2299
  const { value } = node;
2300
2300
  if (intIdentify(value) && value >= 0)
@@ -2307,7 +2307,7 @@ var require_int = __commonJS((exports) => {
2307
2307
  tag: "tag:yaml.org,2002:int",
2308
2308
  format: "OCT",
2309
2309
  test: /^0o[0-7]+$/,
2310
- resolve: (str2, _onError, opt) => intResolve(str2, 2, 8, opt),
2310
+ resolve: (str3, _onError, opt) => intResolve(str3, 2, 8, opt),
2311
2311
  stringify: (node) => intStringify(node, 8, "0o")
2312
2312
  };
2313
2313
  var int = {
@@ -2315,7 +2315,7 @@ var require_int = __commonJS((exports) => {
2315
2315
  default: true,
2316
2316
  tag: "tag:yaml.org,2002:int",
2317
2317
  test: /^[-+]?[0-9]+$/,
2318
- resolve: (str2, _onError, opt) => intResolve(str2, 0, 10, opt),
2318
+ resolve: (str3, _onError, opt) => intResolve(str3, 0, 10, opt),
2319
2319
  stringify: stringifyNumber.stringifyNumber
2320
2320
  };
2321
2321
  var intHex = {
@@ -2324,7 +2324,7 @@ var require_int = __commonJS((exports) => {
2324
2324
  tag: "tag:yaml.org,2002:int",
2325
2325
  format: "HEX",
2326
2326
  test: /^0x[0-9a-fA-F]+$/,
2327
- resolve: (str2, _onError, opt) => intResolve(str2, 2, 16, opt),
2327
+ resolve: (str3, _onError, opt) => intResolve(str3, 2, 16, opt),
2328
2328
  stringify: (node) => intStringify(node, 16, "0x")
2329
2329
  };
2330
2330
  exports.int = int;
@@ -2371,7 +2371,7 @@ var require_schema2 = __commonJS((exports) => {
2371
2371
  identify: (value) => typeof value === "string",
2372
2372
  default: true,
2373
2373
  tag: "tag:yaml.org,2002:str",
2374
- resolve: (str2) => str2,
2374
+ resolve: (str3) => str3,
2375
2375
  stringify: stringifyJSON
2376
2376
  },
2377
2377
  {
@@ -2388,7 +2388,7 @@ var require_schema2 = __commonJS((exports) => {
2388
2388
  default: true,
2389
2389
  tag: "tag:yaml.org,2002:bool",
2390
2390
  test: /^true$|^false$/,
2391
- resolve: (str2) => str2 === "true",
2391
+ resolve: (str3) => str3 === "true",
2392
2392
  stringify: stringifyJSON
2393
2393
  },
2394
2394
  {
@@ -2396,7 +2396,7 @@ var require_schema2 = __commonJS((exports) => {
2396
2396
  default: true,
2397
2397
  tag: "tag:yaml.org,2002:int",
2398
2398
  test: /^-?(?:0|[1-9][0-9]*)$/,
2399
- resolve: (str2, _onError, { intAsBigInt }) => intAsBigInt ? BigInt(str2) : parseInt(str2, 10),
2399
+ resolve: (str3, _onError, { intAsBigInt }) => intAsBigInt ? BigInt(str3) : parseInt(str3, 10),
2400
2400
  stringify: ({ value }) => intIdentify(value) ? value.toString() : JSON.stringify(value)
2401
2401
  },
2402
2402
  {
@@ -2404,7 +2404,7 @@ var require_schema2 = __commonJS((exports) => {
2404
2404
  default: true,
2405
2405
  tag: "tag:yaml.org,2002:float",
2406
2406
  test: /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/,
2407
- resolve: (str2) => parseFloat(str2),
2407
+ resolve: (str3) => parseFloat(str3),
2408
2408
  stringify: stringifyJSON
2409
2409
  }
2410
2410
  ];
@@ -2412,9 +2412,9 @@ var require_schema2 = __commonJS((exports) => {
2412
2412
  default: true,
2413
2413
  tag: "",
2414
2414
  test: /^/,
2415
- resolve(str2, onError) {
2416
- onError(`Unresolved plain scalar ${JSON.stringify(str2)}`);
2417
- return str2;
2415
+ resolve(str3, onError) {
2416
+ onError(`Unresolved plain scalar ${JSON.stringify(str3)}`);
2417
+ return str3;
2418
2418
  }
2419
2419
  };
2420
2420
  var schema = [map.map, seq.seq].concat(jsonScalars, jsonError);
@@ -2434,10 +2434,10 @@ var require_binary = __commonJS((exports) => {
2434
2434
  if (typeof node_buffer.Buffer === "function") {
2435
2435
  return node_buffer.Buffer.from(src, "base64");
2436
2436
  } else if (typeof atob === "function") {
2437
- const str2 = atob(src.replace(/[\n\r]/g, ""));
2438
- const buffer = new Uint8Array(str2.length);
2439
- for (let i = 0;i < str2.length; ++i)
2440
- buffer[i] = str2.charCodeAt(i);
2437
+ const str3 = atob(src.replace(/[\n\r]/g, ""));
2438
+ const buffer = new Uint8Array(str3.length);
2439
+ for (let i = 0;i < str3.length; ++i)
2440
+ buffer[i] = str3.charCodeAt(i);
2441
2441
  return buffer;
2442
2442
  } else {
2443
2443
  onError("This environment does not support reading binary tags; either Buffer or atob is required");
@@ -2448,29 +2448,29 @@ var require_binary = __commonJS((exports) => {
2448
2448
  if (!value)
2449
2449
  return "";
2450
2450
  const buf = value;
2451
- let str2;
2451
+ let str3;
2452
2452
  if (typeof node_buffer.Buffer === "function") {
2453
- str2 = buf instanceof node_buffer.Buffer ? buf.toString("base64") : node_buffer.Buffer.from(buf.buffer).toString("base64");
2453
+ str3 = buf instanceof node_buffer.Buffer ? buf.toString("base64") : node_buffer.Buffer.from(buf.buffer).toString("base64");
2454
2454
  } else if (typeof btoa === "function") {
2455
2455
  let s = "";
2456
2456
  for (let i = 0;i < buf.length; ++i)
2457
2457
  s += String.fromCharCode(buf[i]);
2458
- str2 = btoa(s);
2458
+ str3 = btoa(s);
2459
2459
  } else {
2460
2460
  throw new Error("This environment does not support writing binary tags; either Buffer or btoa is required");
2461
2461
  }
2462
2462
  type ?? (type = Scalar.Scalar.BLOCK_LITERAL);
2463
2463
  if (type !== Scalar.Scalar.QUOTE_DOUBLE) {
2464
2464
  const lineWidth = Math.max(ctx.options.lineWidth - ctx.indent.length, ctx.options.minContentWidth);
2465
- const n2 = Math.ceil(str2.length / lineWidth);
2465
+ const n2 = Math.ceil(str3.length / lineWidth);
2466
2466
  const lines = new Array(n2);
2467
2467
  for (let i = 0, o = 0;i < n2; ++i, o += lineWidth) {
2468
- lines[i] = str2.substr(o, lineWidth);
2468
+ lines[i] = str3.substr(o, lineWidth);
2469
2469
  }
2470
- str2 = lines.join(type === Scalar.Scalar.BLOCK_LITERAL ? `
2470
+ str3 = lines.join(type === Scalar.Scalar.BLOCK_LITERAL ? `
2471
2471
  ` : " ");
2472
2472
  }
2473
- return stringifyString.stringifyString({ comment, type, value: str2 }, ctx, onComment, onChompKeep);
2473
+ return stringifyString.stringifyString({ comment, type, value: str3 }, ctx, onComment, onChompKeep);
2474
2474
  }
2475
2475
  };
2476
2476
  exports.binary = binary;
@@ -2661,7 +2661,7 @@ var require_float2 = __commonJS((exports) => {
2661
2661
  default: true,
2662
2662
  tag: "tag:yaml.org,2002:float",
2663
2663
  test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/,
2664
- resolve: (str2) => str2.slice(-3).toLowerCase() === "nan" ? NaN : str2[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
2664
+ resolve: (str3) => str3.slice(-3).toLowerCase() === "nan" ? NaN : str3[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
2665
2665
  stringify: stringifyNumber.stringifyNumber
2666
2666
  };
2667
2667
  var floatExp = {
@@ -2670,7 +2670,7 @@ var require_float2 = __commonJS((exports) => {
2670
2670
  tag: "tag:yaml.org,2002:float",
2671
2671
  format: "EXP",
2672
2672
  test: /^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/,
2673
- resolve: (str2) => parseFloat(str2.replace(/_/g, "")),
2673
+ resolve: (str3) => parseFloat(str3.replace(/_/g, "")),
2674
2674
  stringify(node) {
2675
2675
  const num = Number(node.value);
2676
2676
  return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node);
@@ -2681,11 +2681,11 @@ var require_float2 = __commonJS((exports) => {
2681
2681
  default: true,
2682
2682
  tag: "tag:yaml.org,2002:float",
2683
2683
  test: /^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/,
2684
- resolve(str2) {
2685
- const node = new Scalar.Scalar(parseFloat(str2.replace(/_/g, "")));
2686
- const dot = str2.indexOf(".");
2684
+ resolve(str3) {
2685
+ const node = new Scalar.Scalar(parseFloat(str3.replace(/_/g, "")));
2686
+ const dot = str3.indexOf(".");
2687
2687
  if (dot !== -1) {
2688
- const f = str2.substring(dot + 1).replace(/_/g, "");
2688
+ const f = str3.substring(dot + 1).replace(/_/g, "");
2689
2689
  if (f[f.length - 1] === "0")
2690
2690
  node.minFractionDigits = f.length;
2691
2691
  }
@@ -2702,34 +2702,34 @@ var require_float2 = __commonJS((exports) => {
2702
2702
  var require_int2 = __commonJS((exports) => {
2703
2703
  var stringifyNumber = require_stringifyNumber();
2704
2704
  var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
2705
- function intResolve(str2, offset, radix, { intAsBigInt }) {
2706
- const sign = str2[0];
2705
+ function intResolve(str3, offset, radix, { intAsBigInt }) {
2706
+ const sign = str3[0];
2707
2707
  if (sign === "-" || sign === "+")
2708
2708
  offset += 1;
2709
- str2 = str2.substring(offset).replace(/_/g, "");
2709
+ str3 = str3.substring(offset).replace(/_/g, "");
2710
2710
  if (intAsBigInt) {
2711
2711
  switch (radix) {
2712
2712
  case 2:
2713
- str2 = `0b${str2}`;
2713
+ str3 = `0b${str3}`;
2714
2714
  break;
2715
2715
  case 8:
2716
- str2 = `0o${str2}`;
2716
+ str3 = `0o${str3}`;
2717
2717
  break;
2718
2718
  case 16:
2719
- str2 = `0x${str2}`;
2719
+ str3 = `0x${str3}`;
2720
2720
  break;
2721
2721
  }
2722
- const n3 = BigInt(str2);
2722
+ const n3 = BigInt(str3);
2723
2723
  return sign === "-" ? BigInt(-1) * n3 : n3;
2724
2724
  }
2725
- const n2 = parseInt(str2, radix);
2725
+ const n2 = parseInt(str3, radix);
2726
2726
  return sign === "-" ? -1 * n2 : n2;
2727
2727
  }
2728
2728
  function intStringify(node, radix, prefix) {
2729
2729
  const { value } = node;
2730
2730
  if (intIdentify(value)) {
2731
- const str2 = value.toString(radix);
2732
- return value < 0 ? "-" + prefix + str2.substr(1) : prefix + str2;
2731
+ const str3 = value.toString(radix);
2732
+ return value < 0 ? "-" + prefix + str3.substr(1) : prefix + str3;
2733
2733
  }
2734
2734
  return stringifyNumber.stringifyNumber(node);
2735
2735
  }
@@ -2739,7 +2739,7 @@ var require_int2 = __commonJS((exports) => {
2739
2739
  tag: "tag:yaml.org,2002:int",
2740
2740
  format: "BIN",
2741
2741
  test: /^[-+]?0b[0-1_]+$/,
2742
- resolve: (str2, _onError, opt) => intResolve(str2, 2, 2, opt),
2742
+ resolve: (str3, _onError, opt) => intResolve(str3, 2, 2, opt),
2743
2743
  stringify: (node) => intStringify(node, 2, "0b")
2744
2744
  };
2745
2745
  var intOct = {
@@ -2748,7 +2748,7 @@ var require_int2 = __commonJS((exports) => {
2748
2748
  tag: "tag:yaml.org,2002:int",
2749
2749
  format: "OCT",
2750
2750
  test: /^[-+]?0[0-7_]+$/,
2751
- resolve: (str2, _onError, opt) => intResolve(str2, 1, 8, opt),
2751
+ resolve: (str3, _onError, opt) => intResolve(str3, 1, 8, opt),
2752
2752
  stringify: (node) => intStringify(node, 8, "0")
2753
2753
  };
2754
2754
  var int = {
@@ -2756,7 +2756,7 @@ var require_int2 = __commonJS((exports) => {
2756
2756
  default: true,
2757
2757
  tag: "tag:yaml.org,2002:int",
2758
2758
  test: /^[-+]?[0-9][0-9_]*$/,
2759
- resolve: (str2, _onError, opt) => intResolve(str2, 0, 10, opt),
2759
+ resolve: (str3, _onError, opt) => intResolve(str3, 0, 10, opt),
2760
2760
  stringify: stringifyNumber.stringifyNumber
2761
2761
  };
2762
2762
  var intHex = {
@@ -2765,7 +2765,7 @@ var require_int2 = __commonJS((exports) => {
2765
2765
  tag: "tag:yaml.org,2002:int",
2766
2766
  format: "HEX",
2767
2767
  test: /^[-+]?0x[0-9a-fA-F_]+$/,
2768
- resolve: (str2, _onError, opt) => intResolve(str2, 2, 16, opt),
2768
+ resolve: (str3, _onError, opt) => intResolve(str3, 2, 16, opt),
2769
2769
  stringify: (node) => intStringify(node, 16, "0x")
2770
2770
  };
2771
2771
  exports.int = int;
@@ -2860,9 +2860,9 @@ var require_set = __commonJS((exports) => {
2860
2860
  // node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
2861
2861
  var require_timestamp = __commonJS((exports) => {
2862
2862
  var stringifyNumber = require_stringifyNumber();
2863
- function parseSexagesimal(str2, asBigInt) {
2864
- const sign = str2[0];
2865
- const parts = sign === "-" || sign === "+" ? str2.substring(1) : str2;
2863
+ function parseSexagesimal(str3, asBigInt) {
2864
+ const sign = str3[0];
2865
+ const parts = sign === "-" || sign === "+" ? str3.substring(1) : str3;
2866
2866
  const num = (n2) => asBigInt ? BigInt(n2) : Number(n2);
2867
2867
  const res = parts.replace(/_/g, "").split(":").reduce((res2, p) => res2 * num(60) + num(p), num(0));
2868
2868
  return sign === "-" ? num(-1) * res : res;
@@ -2899,7 +2899,7 @@ var require_timestamp = __commonJS((exports) => {
2899
2899
  tag: "tag:yaml.org,2002:int",
2900
2900
  format: "TIME",
2901
2901
  test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/,
2902
- resolve: (str2, _onError, { intAsBigInt }) => parseSexagesimal(str2, intAsBigInt),
2902
+ resolve: (str3, _onError, { intAsBigInt }) => parseSexagesimal(str3, intAsBigInt),
2903
2903
  stringify: stringifySexagesimal
2904
2904
  };
2905
2905
  var floatTime = {
@@ -2908,7 +2908,7 @@ var require_timestamp = __commonJS((exports) => {
2908
2908
  tag: "tag:yaml.org,2002:float",
2909
2909
  format: "TIME",
2910
2910
  test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/,
2911
- resolve: (str2) => parseSexagesimal(str2, false),
2911
+ resolve: (str3) => parseSexagesimal(str3, false),
2912
2912
  stringify: stringifySexagesimal
2913
2913
  };
2914
2914
  var timestamp = {
@@ -2916,8 +2916,8 @@ var require_timestamp = __commonJS((exports) => {
2916
2916
  default: true,
2917
2917
  tag: "tag:yaml.org,2002:timestamp",
2918
2918
  test: RegExp("^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})" + "(?:" + "(?:t|T|[ \\t]+)" + "([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)" + "(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?" + ")?$"),
2919
- resolve(str2) {
2920
- const match2 = str2.match(timestamp.test);
2919
+ resolve(str3) {
2920
+ const match2 = str3.match(timestamp.test);
2921
2921
  if (!match2)
2922
2922
  throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");
2923
2923
  const [, year, month, day, hour, minute, second] = match2.map(Number);
@@ -3696,10 +3696,10 @@ var require_resolve_block_map = __commonJS((exports) => {
3696
3696
  let offset = bm.offset;
3697
3697
  let commentEnd = null;
3698
3698
  for (const collItem of bm.items) {
3699
- const { start, key, sep: sep2, value } = collItem;
3699
+ const { start, key, sep: sep3, value } = collItem;
3700
3700
  const keyProps = resolveProps.resolveProps(start, {
3701
3701
  indicator: "explicit-key-ind",
3702
- next: key ?? sep2?.[0],
3702
+ next: key ?? sep3?.[0],
3703
3703
  offset,
3704
3704
  onError,
3705
3705
  parentIndent: bm.indent,
@@ -3713,7 +3713,7 @@ var require_resolve_block_map = __commonJS((exports) => {
3713
3713
  else if ("indent" in key && key.indent !== bm.indent)
3714
3714
  onError(offset, "BAD_INDENT", startColMsg);
3715
3715
  }
3716
- if (!keyProps.anchor && !keyProps.tag && !sep2) {
3716
+ if (!keyProps.anchor && !keyProps.tag && !sep3) {
3717
3717
  commentEnd = keyProps.end;
3718
3718
  if (keyProps.comment) {
3719
3719
  if (map.comment)
@@ -3738,7 +3738,7 @@ var require_resolve_block_map = __commonJS((exports) => {
3738
3738
  ctx.atKey = false;
3739
3739
  if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
3740
3740
  onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
3741
- const valueProps = resolveProps.resolveProps(sep2 ?? [], {
3741
+ const valueProps = resolveProps.resolveProps(sep3 ?? [], {
3742
3742
  indicator: "map-value-ind",
3743
3743
  next: value,
3744
3744
  offset: keyNode.range[2],
@@ -3754,7 +3754,7 @@ var require_resolve_block_map = __commonJS((exports) => {
3754
3754
  if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
3755
3755
  onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
3756
3756
  }
3757
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep2, null, valueProps, onError);
3757
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep3, null, valueProps, onError);
3758
3758
  if (ctx.schema.compat)
3759
3759
  utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
3760
3760
  offset = valueNode.range[2];
@@ -3840,7 +3840,7 @@ var require_resolve_end = __commonJS((exports) => {
3840
3840
  let comment = "";
3841
3841
  if (end) {
3842
3842
  let hasSpace = false;
3843
- let sep2 = "";
3843
+ let sep3 = "";
3844
3844
  for (const token of end) {
3845
3845
  const { source, type } = token;
3846
3846
  switch (type) {
@@ -3854,13 +3854,13 @@ var require_resolve_end = __commonJS((exports) => {
3854
3854
  if (!comment)
3855
3855
  comment = cb;
3856
3856
  else
3857
- comment += sep2 + cb;
3858
- sep2 = "";
3857
+ comment += sep3 + cb;
3858
+ sep3 = "";
3859
3859
  break;
3860
3860
  }
3861
3861
  case "newline":
3862
3862
  if (comment)
3863
- sep2 += source;
3863
+ sep3 += source;
3864
3864
  hasSpace = true;
3865
3865
  break;
3866
3866
  default:
@@ -3900,18 +3900,18 @@ var require_resolve_flow_collection = __commonJS((exports) => {
3900
3900
  let offset = fc.offset + fc.start.source.length;
3901
3901
  for (let i = 0;i < fc.items.length; ++i) {
3902
3902
  const collItem = fc.items[i];
3903
- const { start, key, sep: sep2, value } = collItem;
3903
+ const { start, key, sep: sep3, value } = collItem;
3904
3904
  const props = resolveProps.resolveProps(start, {
3905
3905
  flow: fcName,
3906
3906
  indicator: "explicit-key-ind",
3907
- next: key ?? sep2?.[0],
3907
+ next: key ?? sep3?.[0],
3908
3908
  offset,
3909
3909
  onError,
3910
3910
  parentIndent: fc.indent,
3911
3911
  startOnNewline: false
3912
3912
  });
3913
3913
  if (!props.found) {
3914
- if (!props.anchor && !props.tag && !sep2 && !value) {
3914
+ if (!props.anchor && !props.tag && !sep3 && !value) {
3915
3915
  if (i === 0 && props.comma)
3916
3916
  onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
3917
3917
  else if (i < fc.items.length - 1)
@@ -3963,8 +3963,8 @@ var require_resolve_flow_collection = __commonJS((exports) => {
3963
3963
  }
3964
3964
  }
3965
3965
  }
3966
- if (!isMap && !sep2 && !props.found) {
3967
- const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep2, null, props, onError);
3966
+ if (!isMap && !sep3 && !props.found) {
3967
+ const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep3, null, props, onError);
3968
3968
  coll.items.push(valueNode);
3969
3969
  offset = valueNode.range[2];
3970
3970
  if (isBlock(value))
@@ -3976,7 +3976,7 @@ var require_resolve_flow_collection = __commonJS((exports) => {
3976
3976
  if (isBlock(key))
3977
3977
  onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
3978
3978
  ctx.atKey = false;
3979
- const valueProps = resolveProps.resolveProps(sep2 ?? [], {
3979
+ const valueProps = resolveProps.resolveProps(sep3 ?? [], {
3980
3980
  flow: fcName,
3981
3981
  indicator: "map-value-ind",
3982
3982
  next: value,
@@ -3987,8 +3987,8 @@ var require_resolve_flow_collection = __commonJS((exports) => {
3987
3987
  });
3988
3988
  if (valueProps.found) {
3989
3989
  if (!isMap && !props.found && ctx.options.strict) {
3990
- if (sep2)
3991
- for (const st of sep2) {
3990
+ if (sep3)
3991
+ for (const st of sep3) {
3992
3992
  if (st === valueProps.found)
3993
3993
  break;
3994
3994
  if (st.type === "newline") {
@@ -4005,7 +4005,7 @@ var require_resolve_flow_collection = __commonJS((exports) => {
4005
4005
  else
4006
4006
  onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
4007
4007
  }
4008
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep2, null, valueProps, onError) : null;
4008
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep3, null, valueProps, onError) : null;
4009
4009
  if (valueNode) {
4010
4010
  if (isBlock(value))
4011
4011
  onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
@@ -4182,7 +4182,7 @@ var require_resolve_block_scalar = __commonJS((exports) => {
4182
4182
  chompStart = i + 1;
4183
4183
  }
4184
4184
  let value = "";
4185
- let sep2 = "";
4185
+ let sep3 = "";
4186
4186
  let prevMoreIndented = false;
4187
4187
  for (let i = 0;i < contentStart; ++i)
4188
4188
  value += lines[i][0].slice(trimIndent) + `
@@ -4200,33 +4200,33 @@ var require_resolve_block_scalar = __commonJS((exports) => {
4200
4200
  indent = "";
4201
4201
  }
4202
4202
  if (type === Scalar.Scalar.BLOCK_LITERAL) {
4203
- value += sep2 + indent.slice(trimIndent) + content;
4204
- sep2 = `
4203
+ value += sep3 + indent.slice(trimIndent) + content;
4204
+ sep3 = `
4205
4205
  `;
4206
4206
  } else if (indent.length > trimIndent || content[0] === "\t") {
4207
- if (sep2 === " ")
4208
- sep2 = `
4207
+ if (sep3 === " ")
4208
+ sep3 = `
4209
4209
  `;
4210
- else if (!prevMoreIndented && sep2 === `
4210
+ else if (!prevMoreIndented && sep3 === `
4211
4211
  `)
4212
- sep2 = `
4212
+ sep3 = `
4213
4213
 
4214
4214
  `;
4215
- value += sep2 + indent.slice(trimIndent) + content;
4216
- sep2 = `
4215
+ value += sep3 + indent.slice(trimIndent) + content;
4216
+ sep3 = `
4217
4217
  `;
4218
4218
  prevMoreIndented = true;
4219
4219
  } else if (content === "") {
4220
- if (sep2 === `
4220
+ if (sep3 === `
4221
4221
  `)
4222
4222
  value += `
4223
4223
  `;
4224
4224
  else
4225
- sep2 = `
4225
+ sep3 = `
4226
4226
  `;
4227
4227
  } else {
4228
- value += sep2 + content;
4229
- sep2 = " ";
4228
+ value += sep3 + content;
4229
+ sep3 = " ";
4230
4230
  prevMoreIndented = false;
4231
4231
  }
4232
4232
  }
@@ -4406,27 +4406,27 @@ var require_resolve_flow_scalar = __commonJS((exports) => {
4406
4406
  trimBoth = /^[ \t]+|[ \t]+$/g;
4407
4407
  }
4408
4408
  let res = match2[1].replace(trimEnd, "");
4409
- let sep2 = " ";
4409
+ let sep3 = " ";
4410
4410
  let pos = line2.lastIndex;
4411
4411
  while (match2 = line2.exec(source)) {
4412
4412
  const lm = match2[1].replace(trimBoth, "");
4413
4413
  if (lm === "") {
4414
- if (sep2 === `
4414
+ if (sep3 === `
4415
4415
  `)
4416
- res += sep2;
4416
+ res += sep3;
4417
4417
  else
4418
- sep2 = `
4418
+ sep3 = `
4419
4419
  `;
4420
4420
  } else {
4421
- res += sep2 + lm;
4422
- sep2 = " ";
4421
+ res += sep3 + lm;
4422
+ sep3 = " ";
4423
4423
  }
4424
4424
  pos = line2.lastIndex;
4425
4425
  }
4426
4426
  const last = /[ \t]*(.*)/sy;
4427
4427
  last.lastIndex = pos;
4428
4428
  match2 = last.exec(source);
4429
- return res + sep2 + (match2?.[1] ?? "");
4429
+ return res + sep3 + (match2?.[1] ?? "");
4430
4430
  }
4431
4431
  function doubleQuotedValue(source, onError) {
4432
4432
  let res = "";
@@ -5207,14 +5207,14 @@ var require_cst_stringify = __commonJS((exports) => {
5207
5207
  }
5208
5208
  }
5209
5209
  }
5210
- function stringifyItem({ start, key, sep: sep2, value }) {
5210
+ function stringifyItem({ start, key, sep: sep3, value }) {
5211
5211
  let res = "";
5212
5212
  for (const st of start)
5213
5213
  res += st.source;
5214
5214
  if (key)
5215
5215
  res += stringifyToken(key);
5216
- if (sep2)
5217
- for (const st of sep2)
5216
+ if (sep3)
5217
+ for (const st of sep3)
5218
5218
  res += st.source;
5219
5219
  if (value)
5220
5220
  res += stringifyToken(value);
@@ -6362,18 +6362,18 @@ var require_parser = __commonJS((exports) => {
6362
6362
  if (this.type === "map-value-ind") {
6363
6363
  const prev = getPrevProps(this.peek(2));
6364
6364
  const start = getFirstKeyStartProps(prev);
6365
- let sep2;
6365
+ let sep3;
6366
6366
  if (scalar.end) {
6367
- sep2 = scalar.end;
6368
- sep2.push(this.sourceToken);
6367
+ sep3 = scalar.end;
6368
+ sep3.push(this.sourceToken);
6369
6369
  delete scalar.end;
6370
6370
  } else
6371
- sep2 = [this.sourceToken];
6371
+ sep3 = [this.sourceToken];
6372
6372
  const map = {
6373
6373
  type: "block-map",
6374
6374
  offset: scalar.offset,
6375
6375
  indent: scalar.indent,
6376
- items: [{ start, key: scalar, sep: sep2 }]
6376
+ items: [{ start, key: scalar, sep: sep3 }]
6377
6377
  };
6378
6378
  this.onKeyLine = true;
6379
6379
  this.stack[this.stack.length - 1] = map;
@@ -6527,15 +6527,15 @@ var require_parser = __commonJS((exports) => {
6527
6527
  } else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
6528
6528
  const start2 = getFirstKeyStartProps(it.start);
6529
6529
  const key = it.key;
6530
- const sep2 = it.sep;
6531
- sep2.push(this.sourceToken);
6530
+ const sep3 = it.sep;
6531
+ sep3.push(this.sourceToken);
6532
6532
  delete it.key;
6533
6533
  delete it.sep;
6534
6534
  this.stack.push({
6535
6535
  type: "block-map",
6536
6536
  offset: this.offset,
6537
6537
  indent: this.indent,
6538
- items: [{ start: start2, key, sep: sep2 }]
6538
+ items: [{ start: start2, key, sep: sep3 }]
6539
6539
  });
6540
6540
  } else if (start.length > 0) {
6541
6541
  it.sep = it.sep.concat(start, this.sourceToken);
@@ -6729,13 +6729,13 @@ var require_parser = __commonJS((exports) => {
6729
6729
  const prev = getPrevProps(parent);
6730
6730
  const start = getFirstKeyStartProps(prev);
6731
6731
  fixFlowSeqItems(fc);
6732
- const sep2 = fc.end.splice(1, fc.end.length);
6733
- sep2.push(this.sourceToken);
6732
+ const sep3 = fc.end.splice(1, fc.end.length);
6733
+ sep3.push(this.sourceToken);
6734
6734
  const map = {
6735
6735
  type: "block-map",
6736
6736
  offset: fc.offset,
6737
6737
  indent: fc.indent,
6738
- items: [{ start, key: fc, sep: sep2 }]
6738
+ items: [{ start, key: fc, sep: sep3 }]
6739
6739
  };
6740
6740
  this.onKeyLine = true;
6741
6741
  this.stack[this.stack.length - 1] = map;
@@ -6959,8 +6959,8 @@ var require_public_api = __commonJS((exports) => {
6959
6959
  });
6960
6960
 
6961
6961
  // src/server/app.ts
6962
- import { existsSync as existsSync16, statSync as statSync11 } from "fs";
6963
- import { join as join27, resolve as resolve6, sep as sep8 } from "path";
6962
+ import { existsSync as existsSync19, statSync as statSync13 } from "fs";
6963
+ import { join as join30, resolve as resolve6, sep as sep10 } from "path";
6964
6964
 
6965
6965
  // node_modules/hono/dist/request/constants.js
6966
6966
  var GET_MATCH_RESULT = Symbol();
@@ -13476,10 +13476,530 @@ function mount2(ctx) {
13476
13476
  });
13477
13477
  }
13478
13478
 
13479
+ // src/server/routes/checkpoints.ts
13480
+ import { execFileSync } from "child_process";
13481
+ import { appendFileSync, copyFileSync, cpSync, existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync7, realpathSync as realpathSync4, rmSync, statSync as statSync4, writeFileSync as writeFileSync3 } from "fs";
13482
+ import { join as join9, sep as sep2 } from "path";
13483
+
13484
+ // src/server/audit.ts
13485
+ var clip = (s, n2) => s === undefined ? null : s.slice(0, n2);
13486
+ function audit3(db, e) {
13487
+ try {
13488
+ db.insert(actions).values({ ts: new Date().toISOString(), actor: e.actor ?? "local", kind: e.kind, target: clip(e.target, 500), result: e.result, detail: clip(e.detail, 2000) }).run();
13489
+ return true;
13490
+ } catch {
13491
+ return false;
13492
+ }
13493
+ }
13494
+
13495
+ // src/server/routes/cost_ledger.ts
13496
+ import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
13497
+ import { join as join8 } from "path";
13498
+ var isNum = (v) => typeof v === "number" && Number.isFinite(v);
13499
+ var isObj2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
13500
+ var r6 = (n2) => Math.round(n2 * 1e6) / 1e6;
13501
+ var MEASURED_FIELDS = ["cost_usd", "input_tokens", "output_tokens", "cache_read_tokens", "cache_creation_tokens"];
13502
+ var BUDGET_WARN_FRACTION = 0.8;
13503
+ var NOT_MEASURED = "not measured";
13504
+ function readJson(path) {
13505
+ try {
13506
+ return JSON.parse(readFileSync6(path, "utf8"));
13507
+ } catch {
13508
+ return null;
13509
+ }
13510
+ }
13511
+ function recordIsMeasured(rec, skip) {
13512
+ if (!isObj2(rec))
13513
+ return false;
13514
+ return MEASURED_FIELDS.some((k) => k !== skip && isNum(rec[k]) && rec[k] !== 0);
13515
+ }
13516
+ var DEFAULT_PRICING = {
13517
+ fable: { input: 10, output: 50 },
13518
+ "claude-fable-5": { input: 10, output: 50 },
13519
+ opus: { input: 5, output: 25 },
13520
+ sonnet: { input: 2, output: 10 },
13521
+ haiku: { input: 1, output: 5 },
13522
+ "gpt-5.3-codex": { input: 1.5, output: 12 },
13523
+ "gpt-5.6-sol": { input: 2.5, output: 20 },
13524
+ "gpt-5.6-terra": { input: 1.5, output: 12 },
13525
+ "gpt-5.6-luna": { input: 0.5, output: 4 }
13526
+ };
13527
+ function pricing(lokiDir) {
13528
+ const d = readJson(join8(lokiDir, "pricing.json"));
13529
+ const models = isObj2(d) && isObj2(d.models) ? d.models : {};
13530
+ return { ...DEFAULT_PRICING, ...models };
13531
+ }
13532
+ function modelCost(table, model, inp, out, cr = 0, cw = 0) {
13533
+ const p = table[model.toLowerCase()] ?? table.sonnet ?? {};
13534
+ const ir = isNum(p.input) ? p.input : 3;
13535
+ const or2 = isNum(p.output) ? p.output : 15;
13536
+ const crr = isNum(p.cache_read) ? p.cache_read : ir * 0.1;
13537
+ const cwr = isNum(p.cache_write) ? p.cache_write : ir * 1.25;
13538
+ return r6((inp * ir + out * or2 + cr * crr + cw * cwr) / 1e6);
13539
+ }
13540
+ function iterationRecords(lokiDir) {
13541
+ const dir = join8(lokiDir, "metrics", "efficiency");
13542
+ let names = [];
13543
+ try {
13544
+ names = readdirSync3(dir).filter((n2) => /^iteration-.*\.json$/.test(n2)).sort();
13545
+ } catch {
13546
+ return [];
13547
+ }
13548
+ const out = [];
13549
+ for (const n2 of names) {
13550
+ const d = readJson(join8(dir, n2));
13551
+ if (isObj2(d))
13552
+ out.push(d);
13553
+ }
13554
+ return out;
13555
+ }
13556
+ var num0 = (v) => isNum(v) ? v : 0;
13557
+ var priced = (table, d) => d.cost_usd === null || d.cost_usd === undefined ? modelCost(table, String(d.model ?? "sonnet").toLowerCase(), num0(d.input_tokens), num0(d.output_tokens), num0(d.cache_read_tokens), num0(d.cache_creation_tokens)) : num0(d.cost_usd);
13558
+ function costSnapshot(lokiDir) {
13559
+ const table = pricing(lokiDir);
13560
+ let tIn = 0, tOut = 0, tCr = 0, tCw = 0, est = 0;
13561
+ let recorded = false, unknownUsd = false;
13562
+ const byPhase = {}, byModel = {};
13563
+ for (const d of iterationRecords(lokiDir)) {
13564
+ const measured = recordIsMeasured(d);
13565
+ if (measured)
13566
+ recorded = true;
13567
+ const tokMeasured = recordIsMeasured(d, "cost_usd");
13568
+ const inp = num0(d.input_tokens), out = num0(d.output_tokens), cr = num0(d.cache_read_tokens), cw = num0(d.cache_creation_tokens);
13569
+ const model = String(d.model ?? "sonnet").toLowerCase();
13570
+ const phase = String(d.phase ?? "unknown");
13571
+ tIn += inp;
13572
+ tOut += out;
13573
+ tCr += cr;
13574
+ tCw += cw;
13575
+ const cost = priced(table, d);
13576
+ est += cost;
13577
+ for (const [bucket, name] of [[byPhase, phase], [byModel, model]]) {
13578
+ const b = bucket[name] ??= { input_tokens: 0, output_tokens: 0, cost_usd: 0, measured: false, tokens_measured: false };
13579
+ b.input_tokens += inp;
13580
+ b.output_tokens += out;
13581
+ b.cost_usd += cost;
13582
+ b.measured ||= measured;
13583
+ b.tokens_measured ||= tokMeasured;
13584
+ }
13585
+ }
13586
+ if (tIn === 0 && tOut === 0) {
13587
+ const ctx = readJson(join8(lokiDir, "context", "tracking.json"));
13588
+ const totals = isObj2(ctx) && isObj2(ctx.totals) ? ctx.totals : {};
13589
+ tIn = num0(totals.total_input);
13590
+ tOut = num0(totals.total_output);
13591
+ if (tIn > 0 || tOut > 0) {
13592
+ recorded = true;
13593
+ if (isNum(totals.total_cost_usd))
13594
+ est = totals.total_cost_usd;
13595
+ else
13596
+ unknownUsd = true;
13597
+ const model = String(isObj2(ctx) && ctx.provider || "unknown").toLowerCase();
13598
+ const per = isObj2(ctx) && Array.isArray(ctx.per_iteration) ? ctx.per_iteration : [];
13599
+ for (const it of per) {
13600
+ if (!isObj2(it))
13601
+ continue;
13602
+ const b = byModel[model] ??= { input_tokens: 0, output_tokens: 0, cost_usd: 0, measured: false, tokens_measured: true };
13603
+ b.input_tokens += num0(it.input_tokens);
13604
+ b.output_tokens += num0(it.output_tokens);
13605
+ if (isNum(it.cost_usd)) {
13606
+ b.cost_usd += it.cost_usd;
13607
+ b.measured = true;
13608
+ }
13609
+ }
13610
+ }
13611
+ }
13612
+ let limit = null, used = null, remaining = null;
13613
+ const bd = readJson(join8(lokiDir, "metrics", "budget.json"));
13614
+ if (isObj2(bd) && isNum(bd.limit)) {
13615
+ limit = bd.limit;
13616
+ if (recorded && !unknownUsd) {
13617
+ used = est;
13618
+ remaining = Math.max(0, limit - used);
13619
+ }
13620
+ }
13621
+ const readIn = tIn + tCr;
13622
+ const fmt = (m) => Object.fromEntries(Object.entries(m).map(([k, v]) => [k, {
13623
+ input_tokens: v.tokens_measured ? v.input_tokens : null,
13624
+ output_tokens: v.tokens_measured ? v.output_tokens : null,
13625
+ cost_usd: v.measured ? r6(v.cost_usd) : null
13626
+ }]));
13627
+ return {
13628
+ total_input_tokens: recorded ? tIn : null,
13629
+ total_output_tokens: recorded ? tOut : null,
13630
+ total_cache_read_tokens: recorded ? tCr : null,
13631
+ total_cache_creation_tokens: recorded ? tCw : null,
13632
+ total_tokens: recorded ? tIn + tOut + tCr + tCw : null,
13633
+ cache_hit_ratio: readIn > 0 ? Math.round(tCr / readIn * 1e4) / 1e4 : null,
13634
+ estimated_cost_usd: recorded && !unknownUsd ? r6(est) : null,
13635
+ cost_recorded: recorded,
13636
+ by_phase: fmt(byPhase),
13637
+ by_model: fmt(byModel),
13638
+ budget_limit: limit,
13639
+ budget_used: limit !== null && used !== null ? r6(used) : null,
13640
+ budget_remaining: remaining !== null ? r6(remaining) : null,
13641
+ ...recorded ? {} : { not_measured: NOT_MEASURED }
13642
+ };
13643
+ }
13644
+ function budgetStatus(used, limit) {
13645
+ if (limit === null || limit <= 0)
13646
+ return "none";
13647
+ if (used === null)
13648
+ return "unknown";
13649
+ if (used >= limit)
13650
+ return "exceeded";
13651
+ return used >= BUDGET_WARN_FRACTION * limit ? "warn" : "ok";
13652
+ }
13653
+ function budgetSnapshot(lokiDir) {
13654
+ const table = pricing(lokiDir);
13655
+ let total = 0, recorded = false, unmeasured = 0;
13656
+ for (const d of iterationRecords(lokiDir)) {
13657
+ if (recordIsMeasured(d))
13658
+ recorded = true;
13659
+ else
13660
+ unmeasured++;
13661
+ total += priced(table, d);
13662
+ }
13663
+ let limit = null;
13664
+ const bd = readJson(join8(lokiDir, "metrics", "budget.json"));
13665
+ if (isObj2(bd)) {
13666
+ const l = bd.limit || bd.budget_limit;
13667
+ limit = isNum(l) ? l : null;
13668
+ }
13669
+ if (limit === null && process.env.LOKI_BUDGET_LIMIT) {
13670
+ const e = Number(process.env.LOKI_BUDGET_LIMIT);
13671
+ if (Number.isFinite(e))
13672
+ limit = e;
13673
+ }
13674
+ const used = recorded ? r6(total) : null;
13675
+ const ok = limit !== null && limit > 0 && used !== null;
13676
+ const status = budgetStatus(used, limit);
13677
+ return {
13678
+ limit,
13679
+ used,
13680
+ remaining: ok ? r6(Math.max(0, limit - used)) : null,
13681
+ percent_used: ok ? Math.round(used / limit * 1e4) / 100 : null,
13682
+ status,
13683
+ warn_threshold_percent: Math.round(BUDGET_WARN_FRACTION * 100),
13684
+ exceeded: status === "exceeded",
13685
+ partial: recorded && unmeasured > 0
13686
+ };
13687
+ }
13688
+ function costTimeline(lokiDir) {
13689
+ const table = pricing(lokiDir);
13690
+ const recs = iterationRecords(lokiDir).map((d, i) => ({ d, i })).sort((a, b) => num0(Number(a.d.iteration)) - num0(Number(b.d.iteration)) || a.i - b.i).map((x) => x.d);
13691
+ const iterations = [];
13692
+ let cum = 0, recorded = false, unmeasured = 0;
13693
+ for (const d of recs) {
13694
+ const measured = recordIsMeasured(d);
13695
+ if (measured)
13696
+ recorded = true;
13697
+ else
13698
+ unmeasured++;
13699
+ const model = String(d.model ?? "sonnet").toLowerCase();
13700
+ cum += priced(table, d);
13701
+ iterations.push({
13702
+ iteration: d.iteration ?? null,
13703
+ timestamp: d.timestamp ?? null,
13704
+ model: d.model ? model : null,
13705
+ phase: d.phase ?? "unknown",
13706
+ provider: d.provider ?? null,
13707
+ input_tokens: measured ? num0(d.input_tokens) : null,
13708
+ output_tokens: measured ? num0(d.output_tokens) : null,
13709
+ cost_usd: measured ? r6(priced(table, d)) : null,
13710
+ cumulative_usd: measured ? r6(cum) : null
13711
+ });
13712
+ }
13713
+ const runs2 = [];
13714
+ let projectTotal = 0, runsMeasured = 0;
13715
+ const proofs = join8(lokiDir, "proofs");
13716
+ let entries = [];
13717
+ try {
13718
+ entries = readdirSync3(proofs).sort();
13719
+ } catch {}
13720
+ for (const e of entries) {
13721
+ try {
13722
+ if (!statSync3(join8(proofs, e)).isDirectory())
13723
+ continue;
13724
+ } catch {
13725
+ continue;
13726
+ }
13727
+ const p = readJson(join8(proofs, e, "proof.json"));
13728
+ if (!isObj2(p))
13729
+ continue;
13730
+ const cost = isObj2(p.cost) ? p.cost : {};
13731
+ const usd = isNum(cost.usd) ? cost.usd : null;
13732
+ if (usd !== null) {
13733
+ projectTotal += usd;
13734
+ runsMeasured++;
13735
+ }
13736
+ runs2.push({
13737
+ run_id: p.run_id ?? e,
13738
+ generated_at: p.generated_at ?? null,
13739
+ model: isObj2(p.provider) ? p.provider.model ?? null : null,
13740
+ cost_usd: usd !== null ? r6(usd) : null,
13741
+ cost_partial: cost.cost_partial === true,
13742
+ files_changed: isObj2(p.files_changed) ? p.files_changed.count ?? null : null,
13743
+ final_verdict: isObj2(p.council) ? p.council.final_verdict ?? null : null
13744
+ });
13745
+ }
13746
+ runs2.sort((a, b) => String(b.generated_at ?? "").localeCompare(String(a.generated_at ?? "")));
13747
+ return {
13748
+ current_run: { iterations, total_usd: recorded ? r6(cum) : null, cost_recorded: recorded, partial: recorded && unmeasured > 0, unmeasured_iterations: unmeasured },
13749
+ runs: runs2,
13750
+ runs_count: runs2.length,
13751
+ project_total_usd: runsMeasured ? r6(projectTotal) : null,
13752
+ project_total_partial: runsMeasured > 0 && runsMeasured < runs2.length,
13753
+ budget: budgetSnapshot(lokiDir),
13754
+ ...recorded || runsMeasured ? {} : { not_measured: NOT_MEASURED }
13755
+ };
13756
+ }
13757
+ var lokiDirOf = (repoDir) => join8(repoDir, ".loki");
13758
+ var lokiDirExists = (repoDir) => existsSync4(lokiDirOf(repoDir));
13759
+ function mount3(ctx) {
13760
+ const deny = { error: "loopback only without a token" };
13761
+ ctx.app.get("/v1/cost/snapshot", (c) => probeAllowed(ctx, c) ? c.json(costSnapshot(lokiDirOf(ctx.repoDir))) : c.json(deny, 403));
13762
+ ctx.app.get("/v1/cost/timeline", (c) => probeAllowed(ctx, c) ? c.json(costTimeline(lokiDirOf(ctx.repoDir))) : c.json(deny, 403));
13763
+ }
13764
+
13765
+ // src/server/routes/checkpoints.ts
13766
+ var isObj3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
13767
+ var str2 = (v) => typeof v === "string" && v !== "" ? v : null;
13768
+ var SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
13769
+ var MAX_CHECKPOINTS = 50;
13770
+ var validCheckpointId = (id) => SAFE_ID.test(id);
13771
+ var storeOf = (repoDir) => join9(repoDir, ".loki", "state", "checkpoints");
13772
+ var lokiOf = (repoDir) => join9(repoDir, ".loki");
13773
+ var readObj = (f) => {
13774
+ try {
13775
+ const d = JSON.parse(readFileSync7(f, "utf8"));
13776
+ return isObj3(d) ? d : null;
13777
+ } catch {
13778
+ return null;
13779
+ }
13780
+ };
13781
+ var stamp = (d) => d.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "").replace("T", "-");
13782
+ function listCheckpoints(repoDir, limit = 20) {
13783
+ const store = storeOf(repoDir);
13784
+ let lines = [];
13785
+ try {
13786
+ lines = readFileSync7(join9(store, "index.jsonl"), "utf8").split(`
13787
+ `);
13788
+ } catch {
13789
+ return [];
13790
+ }
13791
+ const out = [];
13792
+ for (const line2 of lines) {
13793
+ if (!line2.trim())
13794
+ continue;
13795
+ let raw2;
13796
+ try {
13797
+ raw2 = JSON.parse(line2);
13798
+ } catch {
13799
+ continue;
13800
+ }
13801
+ if (!isObj3(raw2))
13802
+ continue;
13803
+ const id = String(raw2.id ?? "");
13804
+ if (!validCheckpointId(id))
13805
+ continue;
13806
+ const it = raw2.iter ?? raw2.iteration;
13807
+ const cp = {
13808
+ id,
13809
+ created_at: str2(raw2.ts) ?? str2(raw2.created_at) ?? str2(raw2.timestamp),
13810
+ git_sha: str2(raw2.sha) ?? str2(raw2.git_sha),
13811
+ message: str2(raw2.task) ?? str2(raw2.message) ?? str2(raw2.task_description),
13812
+ iteration: Number.isInteger(it) ? it : null
13813
+ };
13814
+ const meta = readObj(join9(store, id, "metadata.json"));
13815
+ if (meta) {
13816
+ cp.git_branch = str2(meta.git_branch);
13817
+ cp.provider = str2(meta.provider);
13818
+ cp.phase = str2(meta.phase);
13819
+ cp.message ??= str2(meta.task_description);
13820
+ cp.git_sha ??= str2(meta.git_sha);
13821
+ cp.created_at ??= str2(meta.timestamp);
13822
+ cp.files_count = countFiles(join9(store, id));
13823
+ }
13824
+ out.push(cp);
13825
+ }
13826
+ return out.reverse().slice(0, limit);
13827
+ }
13828
+ function countFiles(dir) {
13829
+ let n2 = 0;
13830
+ const walk = (d, top) => {
13831
+ for (const e of readdirSync4(d, { withFileTypes: true })) {
13832
+ if (e.isDirectory())
13833
+ walk(join9(d, e.name), false);
13834
+ else if (e.isFile() && !(top && e.name === "metadata.json"))
13835
+ n2++;
13836
+ }
13837
+ };
13838
+ try {
13839
+ walk(dir, true);
13840
+ } catch {}
13841
+ return n2;
13842
+ }
13843
+ function getCheckpoint(repoDir, id) {
13844
+ return validCheckpointId(id) ? readObj(join9(storeOf(repoDir), id, "metadata.json")) : null;
13845
+ }
13846
+ var gitHead = (repoDir) => {
13847
+ try {
13848
+ return execFileSync("git", ["rev-parse", "HEAD"], { cwd: repoDir, timeout: 5000, stdio: ["ignore", "pipe", "ignore"] }).toString().trim() || null;
13849
+ } catch {
13850
+ return null;
13851
+ }
13852
+ };
13853
+ function createCheckpoint(repoDir, message, now = new Date) {
13854
+ const store = storeOf(repoDir), loki = lokiOf(repoDir);
13855
+ mkdirSync3(store, { recursive: true });
13856
+ const id = `chk-${stamp(now)}`, dir = join9(store, id);
13857
+ mkdirSync3(dir, { recursive: true });
13858
+ for (const f of ["dashboard-state.json", "session.json"]) {
13859
+ try {
13860
+ if (existsSync5(join9(loki, f)))
13861
+ copyFileSync(join9(loki, f), join9(dir, f));
13862
+ } catch {}
13863
+ }
13864
+ try {
13865
+ if (existsSync5(join9(loki, "queue")))
13866
+ cpSync(join9(loki, "queue"), join9(dir, "queue"), { recursive: true });
13867
+ } catch {}
13868
+ const meta = { id, created_at: now.toISOString(), git_sha: gitHead(repoDir), message: message || null, files: readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name) };
13869
+ writeFileSync3(join9(dir, "metadata.json"), JSON.stringify(meta, null, 2));
13870
+ appendFileSync(join9(store, "index.jsonl"), `${JSON.stringify(meta)}
13871
+ `);
13872
+ const mine = readdirSync4(store, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("chk-")).map((e) => e.name).sort();
13873
+ while (mine.length > MAX_CHECKPOINTS)
13874
+ rmSync(join9(store, mine.shift()), { recursive: true, force: true });
13875
+ return meta;
13876
+ }
13877
+ function rollbackCheckpoint(repoDir, id, now = new Date) {
13878
+ const store = storeOf(repoDir), loki = lokiOf(repoDir);
13879
+ let real2 = "", realStore = "";
13880
+ try {
13881
+ real2 = realpathSync4(join9(store, id));
13882
+ realStore = realpathSync4(store);
13883
+ } catch {
13884
+ return null;
13885
+ }
13886
+ if (!real2.startsWith(`${realStore}${sep2}`) || !statSync4(real2).isDirectory())
13887
+ return null;
13888
+ const preId = `rb-pre-${stamp(now)}`, pre = join9(store, preId);
13889
+ mkdirSync3(pre, { recursive: true });
13890
+ const notStore = (src) => src !== store;
13891
+ for (const n2 of ["session.json", "dashboard-state.json", "CONTINUITY.md", "autonomy-state.json"]) {
13892
+ try {
13893
+ if (existsSync5(join9(loki, n2)))
13894
+ copyFileSync(join9(loki, n2), join9(pre, n2));
13895
+ } catch {}
13896
+ }
13897
+ for (const d of ["state", "queue"]) {
13898
+ try {
13899
+ if (existsSync5(join9(loki, d)))
13900
+ cpSync(join9(loki, d), join9(pre, d), { recursive: true, filter: notStore });
13901
+ } catch {}
13902
+ }
13903
+ const preMeta = { id: preId, created_at: now.toISOString(), message: `pre-rollback snapshot (before restoring ${id})`, created_by: "control plane rollback" };
13904
+ writeFileSync3(join9(pre, "metadata.json"), JSON.stringify(preMeta, null, 2));
13905
+ appendFileSync(join9(store, "index.jsonl"), `${JSON.stringify(preMeta)}
13906
+ `);
13907
+ let restored = 0;
13908
+ const errors = [];
13909
+ for (const e of readdirSync4(real2, { withFileTypes: true })) {
13910
+ if (e.name === "metadata.json" || e.name === "worktree-snapshot.txt")
13911
+ continue;
13912
+ const src = join9(real2, e.name);
13913
+ try {
13914
+ if (lstatSync(src).isSymbolicLink()) {
13915
+ errors.push(`${e.name}: symlink skipped`);
13916
+ continue;
13917
+ }
13918
+ cpSync(src, join9(loki, e.name), { recursive: true, filter: (s) => s !== join9(real2, "state", "checkpoints") });
13919
+ restored++;
13920
+ } catch (err) {
13921
+ errors.push(`${e.name}: ${err.message}`);
13922
+ }
13923
+ }
13924
+ return { id, restored, pre_rollback_snapshot: preId, errors };
13925
+ }
13926
+ function auditSummary(db, days, now = Date.now()) {
13927
+ const since = new Date(now - days * 86400000).toISOString();
13928
+ const where = gte(audit.ts, since);
13929
+ const total = db.select({ n: sql`count(*)` }).from(audit).where(where).get()?.n ?? 0;
13930
+ const by = (col) => Object.fromEntries(db.select({ k: col, n: sql`count(*)` }).from(audit).where(where).groupBy(col).all().map((r) => [r.k, r.n]));
13931
+ return {
13932
+ period_days: days,
13933
+ total_events: total,
13934
+ by_action: by(audit.action),
13935
+ by_user: by(audit.actor),
13936
+ successful_events: null,
13937
+ failed_events: null,
13938
+ by_resource_type: null,
13939
+ recent_failures: null,
13940
+ not_measured: NOT_MEASURED
13941
+ };
13942
+ }
13943
+ var parseDays = (v) => {
13944
+ const n2 = Number.parseInt(v ?? "7", 10);
13945
+ return Number.isFinite(n2) && n2 >= 1 && n2 <= 3650 ? n2 : 7;
13946
+ };
13947
+ var parseLimit = (v) => {
13948
+ const n2 = Number.parseInt(v ?? "20", 10);
13949
+ return Number.isFinite(n2) ? Math.min(Math.max(n2, 1), 200) : 20;
13950
+ };
13951
+ function mount4(ctx) {
13952
+ const deny = { error: "loopback only without a token" };
13953
+ const { app, act, db, repoDir } = ctx;
13954
+ app.get("/v1/checkpoints", (c) => probeAllowed(ctx, c) ? c.json({ checkpoints: listCheckpoints(repoDir, parseLimit(c.req.query("limit"))) }) : c.json(deny, 403));
13955
+ app.get("/v1/checkpoints/:id", (c) => {
13956
+ if (!probeAllowed(ctx, c))
13957
+ return c.json(deny, 403);
13958
+ const id = c.req.param("id");
13959
+ if (!validCheckpointId(id))
13960
+ return c.json({ error: "invalid checkpoint id" }, 400);
13961
+ const m = getCheckpoint(repoDir, id);
13962
+ return m ? c.json(m) : c.json({ error: "checkpoint not found" }, 404);
13963
+ });
13964
+ app.get("/v1/audit/summary", (c) => probeAllowed(ctx, c) ? c.json(auditSummary(db, parseDays(c.req.query("days")))) : c.json(deny, 403));
13965
+ act.post("/v1/checkpoints", async (c) => {
13966
+ if (!ctx.local(c))
13967
+ return c.json({ error: "loopback JSON requests only" }, 403);
13968
+ const body = await c.req.json().catch(() => ({}));
13969
+ const message = typeof body.message === "string" ? body.message : "";
13970
+ if (message.length > 500)
13971
+ return c.json({ error: "message must be at most 500 characters" }, 400);
13972
+ try {
13973
+ const meta = createCheckpoint(repoDir, message);
13974
+ audit3(db, { kind: "checkpoint.create", target: String(meta.id), result: "ok" });
13975
+ return c.json(meta, 201);
13976
+ } catch (e) {
13977
+ audit3(db, { kind: "checkpoint.create", result: "error", detail: e.message });
13978
+ return c.json({ error: "could not create the checkpoint" }, 500);
13979
+ }
13980
+ });
13981
+ act.post("/v1/checkpoints/:id/rollback", (c) => {
13982
+ if (!ctx.local(c))
13983
+ return c.json({ error: "loopback JSON requests only" }, 403);
13984
+ const id = c.req.param("id");
13985
+ if (!validCheckpointId(id)) {
13986
+ audit3(db, { kind: "checkpoint.rollback", target: id.slice(0, 128), result: "refused", detail: "invalid id" });
13987
+ return c.json({ error: "invalid checkpoint id" }, 400);
13988
+ }
13989
+ const r = rollbackCheckpoint(repoDir, id);
13990
+ if (!r) {
13991
+ audit3(db, { kind: "checkpoint.rollback", target: id, result: "refused", detail: "not found" });
13992
+ return c.json({ error: "checkpoint not found" }, 404);
13993
+ }
13994
+ audit3(db, { kind: "checkpoint.rollback", target: id, result: r.errors.length ? "partial" : "ok", detail: `restored ${r.restored}; pre-rollback snapshot ${r.pre_rollback_snapshot}` });
13995
+ return c.json({ ...r, message: `Restored ${r.restored} item(s) from ${id}. Prior state saved as ${r.pre_rollback_snapshot} (undo this rollback by restoring it).` });
13996
+ });
13997
+ }
13998
+
13479
13999
  // src/server/routes/config.ts
13480
14000
  import { createHash as createHash5, randomBytes } from "crypto";
13481
- import { closeSync as closeSync3, fchmodSync, fsyncSync, lstatSync, openSync as openSync3, readFileSync as readFileSync7, realpathSync as realpathSync4, renameSync as renameSync2, statSync as statSync4, unlinkSync, writeSync as writeSync2 } from "fs";
13482
- import { basename as basename3, dirname as dirname2, join as join9, sep as sep2 } from "path";
14001
+ import { closeSync as closeSync3, fchmodSync, fsyncSync, lstatSync as lstatSync2, openSync as openSync3, readFileSync as readFileSync9, realpathSync as realpathSync5, renameSync as renameSync2, statSync as statSync6, unlinkSync, writeSync as writeSync2 } from "fs";
14002
+ import { basename as basename3, dirname as dirname2, join as join11, sep as sep3 } from "path";
13483
14003
 
13484
14004
  // node_modules/yaml/dist/index.js
13485
14005
  var composer = require_composer();
@@ -13527,22 +14047,11 @@ var $stringify = publicApi.stringify;
13527
14047
  var $visit = visit.visit;
13528
14048
  var $visitAsync = visit.visitAsync;
13529
14049
 
13530
- // src/server/audit.ts
13531
- var clip = (s, n2) => s === undefined ? null : s.slice(0, n2);
13532
- function audit3(db, e) {
13533
- try {
13534
- db.insert(actions).values({ ts: new Date().toISOString(), actor: e.actor ?? "local", kind: e.kind, target: clip(e.target, 500), result: e.result, detail: clip(e.detail, 2000) }).run();
13535
- return true;
13536
- } catch {
13537
- return false;
13538
- }
13539
- }
13540
-
13541
14050
  // src/server/spawn.ts
13542
14051
  import { spawn } from "child_process";
13543
- import { existsSync as existsSync4, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
14052
+ import { existsSync as existsSync6, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
13544
14053
  import { homedir as homedir3 } from "os";
13545
- import { basename as basename2, join as join8, resolve as resolve2 } from "path";
14054
+ import { basename as basename2, join as join10, resolve as resolve2 } from "path";
13546
14055
  var ISSUE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}\/[A-Za-z0-9._-]{1,100}#[1-9][0-9]{0,8}$/;
13547
14056
  var TASK = /^[A-Za-z0-9][A-Za-z0-9 .,_/#@+=-]{0,499}$/;
13548
14057
  var hasDotDot = (t) => t.split(/[\\/ ]/).some((seg) => seg.startsWith(".."));
@@ -13558,8 +14067,8 @@ var BUDGET = /^(?:[1-9][0-9]{0,5}|0)(?:\.[0-9]{1,2})?$/;
13558
14067
  var WORKSPACE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
13559
14068
  function registryRepos2(env = process.env) {
13560
14069
  try {
13561
- const j = JSON.parse(readFileSync6(join8(env.HOME || homedir3(), ".loki", "dashboard", "projects.json"), "utf8"));
13562
- return Object.values(j.projects ?? {}).map((p) => typeof p?.path === "string" ? p.path : "").filter((p) => p && existsSync4(p) && statSync3(p).isDirectory());
14070
+ const j = JSON.parse(readFileSync8(join10(env.HOME || homedir3(), ".loki", "dashboard", "projects.json"), "utf8"));
14071
+ return Object.values(j.projects ?? {}).map((p) => typeof p?.path === "string" ? p.path : "").filter((p) => p && existsSync6(p) && statSync5(p).isDirectory());
13563
14072
  } catch {
13564
14073
  return [];
13565
14074
  }
@@ -13635,7 +14144,7 @@ function originOk(origin) {
13635
14144
  return false;
13636
14145
  }
13637
14146
  }
13638
- function mount3(ctx) {
14147
+ function mount5(ctx) {
13639
14148
  const { act, db, repoDir, local } = ctx;
13640
14149
  const inflight = new Set;
13641
14150
  const handler = async (c) => {
@@ -13803,19 +14312,19 @@ var loki_yaml_schema_default = {
13803
14312
  // src/server/routes/config.ts
13804
14313
  var MAX_BODY = 1e5;
13805
14314
  var MAX_FILE = 1e6;
13806
- var isObj2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
14315
+ var isObj4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
13807
14316
  function validateConfig(value, s = loki_yaml_schema_default, path = "") {
13808
14317
  const at = path || "(root)";
13809
14318
  const errs = [];
13810
14319
  const join22 = (k) => path ? `${path}.${k}` : k;
13811
14320
  if (s.type === "object") {
13812
- if (!isObj2(value))
14321
+ if (!isObj4(value))
13813
14322
  return [`${at}: must be an object`];
13814
14323
  for (const [k, v] of Object.entries(value)) {
13815
14324
  const sub = Object.hasOwn(s.properties ?? {}, k) ? s.properties[k] : undefined;
13816
14325
  if (sub)
13817
14326
  errs.push(...validateConfig(v, sub, join22(k)));
13818
- else if (isObj2(s.additionalProperties))
14327
+ else if (isObj4(s.additionalProperties))
13819
14328
  errs.push(...validateConfig(v, s.additionalProperties, join22(k)));
13820
14329
  else if (s.additionalProperties === false)
13821
14330
  errs.push(`${join22(k)}: unknown key`);
@@ -13884,7 +14393,7 @@ function findSecrets(v, path = "") {
13884
14393
  return looksLikeSecret(v) ? [path || "(root)"] : [];
13885
14394
  if (Array.isArray(v))
13886
14395
  return v.flatMap((x, i) => findSecrets(x, `${path}[${i}]`));
13887
- if (isObj2(v)) {
14396
+ if (isObj4(v)) {
13888
14397
  return Object.entries(v).flatMap(([k, x]) => [
13889
14398
  ...looksLikeSecret(k) ? [`${path ? `${path}.` : ""}(key)`] : [],
13890
14399
  ...findSecrets(x, path ? `${path}.${k}` : k)
@@ -13893,10 +14402,10 @@ function findSecrets(v, path = "") {
13893
14402
  return [];
13894
14403
  }
13895
14404
  var sha = (b) => createHash5("sha256").update(b).digest("hex");
13896
- var canon = (v) => Array.isArray(v) ? v.map(canon) : isObj2(v) ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, canon(v[k])])) : v;
14405
+ var canon = (v) => Array.isArray(v) ? v.map(canon) : isObj4(v) ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, canon(v[k])])) : v;
13897
14406
  var deepEq = (a, b) => JSON.stringify(canon(a)) === JSON.stringify(canon(b));
13898
14407
  function sync(doc, path, prev, next) {
13899
- if (isObj2(next) && isObj2(prev)) {
14408
+ if (isObj4(next) && isObj4(prev)) {
13900
14409
  for (const k of Object.keys(prev))
13901
14410
  if (!Object.hasOwn(next, k))
13902
14411
  doc.deleteIn([...path, k]);
@@ -13922,10 +14431,10 @@ function tooDeep(v, max = MAX_DEPTH) {
13922
14431
  }
13923
14432
  return false;
13924
14433
  }
13925
- var hasShell = (w) => isObj2(w) && (isObj2(w.integration) && ("command" in w.integration) || Array.isArray(w.repos) && w.repos.some((r) => isObj2(r) && ("setup" in r)));
14434
+ var hasShell = (w) => isObj4(w) && (isObj4(w.integration) && ("command" in w.integration) || Array.isArray(w.repos) && w.repos.some((r) => isObj4(r) && ("setup" in r)));
13926
14435
  function shellLocked(prev, next) {
13927
- const pw = isObj2(prev) && isObj2(prev.workspaces) ? prev.workspaces : {};
13928
- const nw = isObj2(next) && isObj2(next.workspaces) ? next.workspaces : {};
14436
+ const pw = isObj4(prev) && isObj4(prev.workspaces) ? prev.workspaces : {};
14437
+ const nw = isObj4(next) && isObj4(next.workspaces) ? next.workspaces : {};
13929
14438
  for (const name of new Set([...Object.keys(pw), ...Object.keys(nw)])) {
13930
14439
  const a = Object.hasOwn(pw, name) ? pw[name] : undefined;
13931
14440
  const b = Object.hasOwn(nw, name) ? nw[name] : undefined;
@@ -13935,16 +14444,16 @@ function shellLocked(prev, next) {
13935
14444
  return false;
13936
14445
  }
13937
14446
  function traversalPaths(cfg, prev = {}) {
13938
- const pw = isObj2(prev) && isObj2(prev.workspaces) ? prev.workspaces : {};
14447
+ const pw = isObj4(prev) && isObj4(prev.workspaces) ? prev.workspaces : {};
13939
14448
  const out = [];
13940
- const ws = isObj2(cfg) && isObj2(cfg.workspaces) ? cfg.workspaces : {};
14449
+ const ws = isObj4(cfg) && isObj4(cfg.workspaces) ? cfg.workspaces : {};
13941
14450
  for (const [name, w] of Object.entries(ws)) {
13942
- if (!isObj2(w) || !Array.isArray(w.repos))
14451
+ if (!isObj4(w) || !Array.isArray(w.repos))
13943
14452
  continue;
13944
14453
  w.repos.forEach((r, i) => {
13945
- if (isObj2(r) && typeof r.path === "string" && r.path.split(/[\\/]+/).includes("..")) {
13946
- const old = Object.hasOwn(pw, name) && isObj2(pw[name]) && Array.isArray(pw[name].repos) ? pw[name].repos[i] : undefined;
13947
- if (!(isObj2(old) && old.path === r.path))
14454
+ if (isObj4(r) && typeof r.path === "string" && r.path.split(/[\\/]+/).includes("..")) {
14455
+ const old = Object.hasOwn(pw, name) && isObj4(pw[name]) && Array.isArray(pw[name].repos) ? pw[name].repos[i] : undefined;
14456
+ if (!(isObj4(old) && old.path === r.path))
13948
14457
  out.push(`workspaces.${name}.repos[${i}].path`);
13949
14458
  }
13950
14459
  });
@@ -13958,7 +14467,7 @@ function mergeKeyPaths(v) {
13958
14467
  const [x, p] = stack.pop();
13959
14468
  if (Array.isArray(x))
13960
14469
  x.forEach((y, i) => stack.push([y, `${p}[${i}]`]));
13961
- else if (isObj2(x))
14470
+ else if (isObj4(x))
13962
14471
  for (const [k, y] of Object.entries(x)) {
13963
14472
  if (k === "<<")
13964
14473
  out.push(p || "(root)");
@@ -14003,27 +14512,27 @@ function renderVerified(doc, next, quote = needsQuote) {
14003
14512
  function resolveConfigFile(repoDir) {
14004
14513
  let root;
14005
14514
  try {
14006
- root = realpathSync4(repoDir);
14515
+ root = realpathSync5(repoDir);
14007
14516
  } catch {
14008
14517
  return { ok: false, status: 400, error: "repo directory not found" };
14009
14518
  }
14010
- const p = join9(root, "loki.yaml");
14519
+ const p = join11(root, "loki.yaml");
14011
14520
  let st;
14012
14521
  try {
14013
- st = lstatSync(p);
14522
+ st = lstatSync2(p);
14014
14523
  } catch {
14015
14524
  return { ok: true, file: p, exists: false };
14016
14525
  }
14017
14526
  if (st.isSymbolicLink()) {
14018
14527
  let real2;
14019
14528
  try {
14020
- real2 = realpathSync4(p);
14529
+ real2 = realpathSync5(p);
14021
14530
  } catch {
14022
14531
  return { ok: false, status: 400, error: "loki.yaml is a broken symlink" };
14023
14532
  }
14024
- if (!real2.startsWith(root + sep2))
14533
+ if (!real2.startsWith(root + sep3))
14025
14534
  return { ok: false, status: 400, error: "loki.yaml is a symlink that resolves outside the repo" };
14026
- if (!statSync4(real2).isFile())
14535
+ if (!statSync6(real2).isFile())
14027
14536
  return { ok: false, status: 422, error: "loki.yaml is not a regular file" };
14028
14537
  return { ok: true, file: real2, exists: true };
14029
14538
  }
@@ -14032,7 +14541,7 @@ function resolveConfigFile(repoDir) {
14032
14541
  return { ok: true, file: p, exists: true };
14033
14542
  }
14034
14543
  function atomicWrite(file, text2, mode) {
14035
- const tmp = join9(dirname2(file), `.${basename3(file)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
14544
+ const tmp = join11(dirname2(file), `.${basename3(file)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
14036
14545
  const fd = openSync3(tmp, "wx", mode);
14037
14546
  try {
14038
14547
  writeSync2(fd, text2);
@@ -14057,7 +14566,7 @@ function atomicWrite(file, text2, mode) {
14057
14566
  }
14058
14567
  var etagOf = (raw2) => `"${sha(raw2)}"`;
14059
14568
  var norm = (h) => h ? h.trim().replace(/^W\//, "").replace(/^"|"$/g, "") : null;
14060
- function mount4(ctx) {
14569
+ function mount6(ctx) {
14061
14570
  const { act, db, repoDir, local } = ctx;
14062
14571
  const deny = (c, kind, status, error, extra = {}) => {
14063
14572
  audit3(db, { kind, target: "loki.yaml", result: status === 409 ? "conflict" : status === 500 ? "error" : "refused", detail: error });
@@ -14072,9 +14581,9 @@ function mount4(ctx) {
14072
14581
  return deny(c, "config.read", r.status, r.error);
14073
14582
  let raw2 = "";
14074
14583
  if (r.exists) {
14075
- if (statSync4(r.file).size > MAX_FILE)
14584
+ if (statSync6(r.file).size > MAX_FILE)
14076
14585
  return deny(c, "config.read", 413, "loki.yaml is too large");
14077
- raw2 = readFileSync7(r.file, "utf8");
14586
+ raw2 = readFileSync9(r.file, "utf8");
14078
14587
  }
14079
14588
  const doc = $parseDocument(raw2);
14080
14589
  let config = {};
@@ -14117,13 +14626,13 @@ function mount4(ctx) {
14117
14626
  }
14118
14627
  if (tooDeep(body))
14119
14628
  return refuse(422, "config is nested too deeply");
14120
- const next = isObj2(body) ? body.config : undefined;
14121
- if (!isObj2(next))
14629
+ const next = isObj4(body) ? body.config : undefined;
14630
+ if (!isObj4(next))
14122
14631
  return refuse(400, "body must be {config: object}");
14123
14632
  const merge = mergeKeyPaths(next);
14124
14633
  if (merge.length)
14125
14634
  return refuse(422, "the YAML merge key << is not allowed", { paths: merge });
14126
- const badNames = Object.keys(isObj2(next.workspaces) ? next.workspaces : {}).filter((n2) => !/^[A-Za-z0-9_.-]+$/.test(n2) || n2 === "." || n2 === "..");
14635
+ const badNames = Object.keys(isObj4(next.workspaces) ? next.workspaces : {}).filter((n2) => !/^[A-Za-z0-9_.-]+$/.test(n2) || n2 === "." || n2 === "..");
14127
14636
  if (badNames.length)
14128
14637
  return refuse(422, "workspace names may only use letters, digits, _ . and -", { paths: badNames.map((_n, i) => `workspaces.(name ${i + 1})`) });
14129
14638
  const secrets = findSecrets(next);
@@ -14137,9 +14646,9 @@ function mount4(ctx) {
14137
14646
  return refuse(r.status, r.error);
14138
14647
  let raw2 = "";
14139
14648
  if (r.exists) {
14140
- if (statSync4(r.file).size > MAX_FILE)
14649
+ if (statSync6(r.file).size > MAX_FILE)
14141
14650
  return refuse(400, "loki.yaml is too large");
14142
- raw2 = readFileSync7(r.file, "utf8");
14651
+ raw2 = readFileSync9(r.file, "utf8");
14143
14652
  }
14144
14653
  if (!(ifMatch === "*" ? r.exists : sha(raw2) === ifMatch))
14145
14654
  return refuse(409, "loki.yaml changed since it was read", { etag: etagOf(raw2) });
@@ -14164,7 +14673,7 @@ function mount4(ctx) {
14164
14673
  return refuse(400, "could not render the config safely");
14165
14674
  const changed = [...new Set([...Object.keys(prev), ...Object.keys(next)])].filter((k) => !deepEq(prev[k], next[k]));
14166
14675
  try {
14167
- const mode = r.exists ? statSync4(r.file).mode & 511 : 420;
14676
+ const mode = r.exists ? statSync6(r.file).mode & 511 : 420;
14168
14677
  if (r.exists)
14169
14678
  atomicWrite(`${r.file}.bak`, raw2, 384);
14170
14679
  atomicWrite(r.file, out, mode);
@@ -14182,19 +14691,19 @@ function mount4(ctx) {
14182
14691
  }
14183
14692
 
14184
14693
  // src/server/routes/control.ts
14185
- import { existsSync as existsSync5, realpathSync as realpathSync5 } from "fs";
14186
- import { join as join11, resolve as resolve3, sep as sep3 } from "path";
14694
+ import { existsSync as existsSync7, realpathSync as realpathSync6 } from "fs";
14695
+ import { join as join13, resolve as resolve3, sep as sep4 } from "path";
14187
14696
 
14188
14697
  // ../../loki-ts/src/util/run_pid.ts
14189
- import { execFileSync } from "child_process";
14190
- import { chmodSync as chmodSync2, readFileSync as readFileSync8, renameSync as renameSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
14191
- import { join as join10 } from "path";
14192
- var runPidPath = (runDir) => join10(runDir, "run.pid");
14698
+ import { execFileSync as execFileSync2 } from "child_process";
14699
+ import { chmodSync as chmodSync2, readFileSync as readFileSync10, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
14700
+ import { join as join12 } from "path";
14701
+ var runPidPath = (runDir) => join12(runDir, "run.pid");
14193
14702
  function processStartTime(pid) {
14194
14703
  if (!Number.isInteger(pid) || pid <= 1)
14195
14704
  return null;
14196
14705
  try {
14197
- const out = execFileSync("ps", ["-o", "lstart=", "-p", String(pid)], { encoding: "utf8", timeout: 5000, env: { ...process.env, LC_ALL: "C" }, stdio: ["ignore", "pipe", "ignore"] });
14706
+ const out = execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], { encoding: "utf8", timeout: 5000, env: { ...process.env, LC_ALL: "C" }, stdio: ["ignore", "pipe", "ignore"] });
14198
14707
  return out.trim().replace(/\s+/g, " ") || null;
14199
14708
  } catch {
14200
14709
  return null;
@@ -14202,7 +14711,7 @@ function processStartTime(pid) {
14202
14711
  }
14203
14712
  function readRunPid(runDir) {
14204
14713
  try {
14205
- const j = JSON.parse(readFileSync8(runPidPath(runDir), "utf8"));
14714
+ const j = JSON.parse(readFileSync10(runPidPath(runDir), "utf8"));
14206
14715
  if (!Number.isInteger(j.pid) || j.pid <= 1 || typeof j.start !== "string" || typeof j.run_id !== "string" || !Array.isArray(j.argv))
14207
14716
  return null;
14208
14717
  return { pid: j.pid, start: j.start, argv: j.argv.map(String), run_id: j.run_id };
@@ -14229,7 +14738,7 @@ function verifyRunPid(runDir, runId, startOf = processStartTime) {
14229
14738
  // src/server/routes/control.ts
14230
14739
  var ID3 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
14231
14740
  var busy = new Set;
14232
- function mount5(ctx) {
14741
+ function mount7(ctx) {
14233
14742
  const { act, db } = ctx;
14234
14743
  const bin = ctx.startBin ?? "loki", spawnIt = ctx.spawnImpl ?? spawnStart, answerDir = ctx.answerDir ?? defaultAnswerDir();
14235
14744
  const guarded = (kind, handler) => act.post(`/v1/runs/:source/:run/${kind}`, async (c) => {
@@ -14245,14 +14754,14 @@ function mount5(ctx) {
14245
14754
  const repoRow = db.select().from(localRepos).where(eq(localRepos.sourceId, source)).get();
14246
14755
  let repo = "";
14247
14756
  try {
14248
- repo = repoRow ? realpathSync5(repoRow.realpath) : "";
14757
+ repo = repoRow ? realpathSync6(repoRow.realpath) : "";
14249
14758
  } catch {
14250
14759
  repo = "";
14251
14760
  }
14252
14761
  if (!repo)
14253
14762
  return done(404, "refused", { error: "this run's repo is not known on this machine" });
14254
- const runsRoot = join11(repo, ".loki", "runs"), runDir = resolve3(runsRoot, id);
14255
- if (!runDir.startsWith(runsRoot + sep3))
14763
+ const runsRoot = join13(repo, ".loki", "runs"), runDir = resolve3(runsRoot, id);
14764
+ if (!runDir.startsWith(runsRoot + sep4))
14256
14765
  return done(400, "refused", { error: "invalid run id" }, "path escape");
14257
14766
  return handler({ source, run: id, repo, runDir }, done);
14258
14767
  });
@@ -14299,7 +14808,7 @@ function mount5(ctx) {
14299
14808
  return done(404, "refused", { error: "run not found" });
14300
14809
  if (!blockedQuestion(loadEvents(db, a.source, a.run), row.verdict))
14301
14810
  return done(409, "refused", { error: "run is not BLOCKED, so there is nothing to resume" });
14302
- if (!existsSync5(join11(answerDir, a.source, `${a.run}.answer.txt`)))
14811
+ if (!existsSync7(join13(answerDir, a.source, `${a.run}.answer.txt`)))
14303
14812
  return done(409, "refused", { error: "no answer recorded for this run; answer it first" });
14304
14813
  return launch(a, [bin, "answer", a.run], done);
14305
14814
  });
@@ -14326,333 +14835,63 @@ var add = (a, r) => {
14326
14835
  var dimVal = (d, r) => d === "day" ? r.startedAt ? r.startedAt.slice(0, 10) : NONE : d === "model" ? r.model ?? NONE : d === "provider" ? r.provider ?? NONE : r.originRepo ?? NONE;
14327
14836
  var round = (n2) => Math.round(n2 * 1e6) / 1e6;
14328
14837
  var out = (a) => ({ ...a.key, runs: a.runs, measured_runs: a.measured_runs, partial_runs: a.partial_runs, unmeasured_runs: a.unmeasured_runs, measured_usd: round(a.measured_usd), partial_usd: round(a.partial_usd), input_tokens: a.input_tokens, output_tokens: a.output_tokens });
14329
- function mount6(ctx) {
14838
+ function mount8(ctx) {
14330
14839
  ctx.app.get("/v1/stats/cost", (c) => {
14331
14840
  const dims = (c.req.query("group") ?? "day").split(",").map((s) => s.trim()).filter(Boolean);
14332
14841
  if (dims.length === 0 || dims.some((d) => !COST_DIMS.includes(d))) {
14333
14842
  return c.json({ error: `group must be a comma list of ${COST_DIMS.join(", ")}` }, 400);
14334
14843
  }
14335
- const since = c.req.query("since");
14336
- if (since !== undefined && Number.isNaN(Date.parse(since)))
14337
- return c.json({ error: "since must be an ISO date" }, 400);
14338
- const q = ctx.db.select().from(runs);
14339
- const all = since ? q.where(gte(runs.startedAt, since)).all() : q.all();
14340
- const groups = new Map;
14341
- const total = mk({});
14342
- let priced = 0, unpriced = 0;
14343
- for (const r of all) {
14344
- const key = {};
14345
- for (const d of dims)
14346
- key[d] = dimVal(d, r);
14347
- const id = dims.map((d) => key[d]).join("\x00");
14348
- let g = groups.get(id);
14349
- if (!g) {
14350
- g = mk(key);
14351
- groups.set(id, g);
14352
- }
14353
- add(g, r);
14354
- add(total, r);
14355
- if (r.measuredSessions > 0)
14356
- priced++;
14357
- else
14358
- unpriced++;
14359
- }
14360
- const rows = [...groups.values()].map(out).sort((a, b) => {
14361
- for (const d of dims) {
14362
- const x = a[d], y = b[d];
14363
- if (x !== y)
14364
- return d === "day" ? y.localeCompare(x) : x.localeCompare(y);
14365
- }
14366
- return 0;
14367
- });
14368
- const { runs: n2, ...rest } = out(total);
14369
- return c.json({
14370
- group: dims,
14371
- since: since ?? null,
14372
- rows,
14373
- totals: { runs: n2, ...rest },
14374
- budget: { api_key_default_cap_usd: DEFAULT_API_KEY_CAP_USD, subscription_cap: null, api_key_runs: priced, subscription_runs: unpriced }
14375
- });
14376
- });
14377
- }
14378
-
14379
- // src/server/routes/cost_ledger.ts
14380
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync5 } from "fs";
14381
- import { join as join12 } from "path";
14382
- var isNum = (v) => typeof v === "number" && Number.isFinite(v);
14383
- var isObj3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
14384
- var r6 = (n2) => Math.round(n2 * 1e6) / 1e6;
14385
- var MEASURED_FIELDS = ["cost_usd", "input_tokens", "output_tokens", "cache_read_tokens", "cache_creation_tokens"];
14386
- var BUDGET_WARN_FRACTION = 0.8;
14387
- var NOT_MEASURED = "not measured";
14388
- function readJson(path) {
14389
- try {
14390
- return JSON.parse(readFileSync9(path, "utf8"));
14391
- } catch {
14392
- return null;
14393
- }
14394
- }
14395
- function recordIsMeasured(rec, skip) {
14396
- if (!isObj3(rec))
14397
- return false;
14398
- return MEASURED_FIELDS.some((k) => k !== skip && isNum(rec[k]) && rec[k] !== 0);
14399
- }
14400
- var DEFAULT_PRICING = {
14401
- fable: { input: 10, output: 50 },
14402
- "claude-fable-5": { input: 10, output: 50 },
14403
- opus: { input: 5, output: 25 },
14404
- sonnet: { input: 2, output: 10 },
14405
- haiku: { input: 1, output: 5 },
14406
- "gpt-5.3-codex": { input: 1.5, output: 12 },
14407
- "gpt-5.6-sol": { input: 2.5, output: 20 },
14408
- "gpt-5.6-terra": { input: 1.5, output: 12 },
14409
- "gpt-5.6-luna": { input: 0.5, output: 4 }
14410
- };
14411
- function pricing(lokiDir) {
14412
- const d = readJson(join12(lokiDir, "pricing.json"));
14413
- const models = isObj3(d) && isObj3(d.models) ? d.models : {};
14414
- return { ...DEFAULT_PRICING, ...models };
14415
- }
14416
- function modelCost(table, model, inp, out2, cr = 0, cw = 0) {
14417
- const p = table[model.toLowerCase()] ?? table.sonnet ?? {};
14418
- const ir = isNum(p.input) ? p.input : 3;
14419
- const or2 = isNum(p.output) ? p.output : 15;
14420
- const crr = isNum(p.cache_read) ? p.cache_read : ir * 0.1;
14421
- const cwr = isNum(p.cache_write) ? p.cache_write : ir * 1.25;
14422
- return r6((inp * ir + out2 * or2 + cr * crr + cw * cwr) / 1e6);
14423
- }
14424
- function iterationRecords(lokiDir) {
14425
- const dir = join12(lokiDir, "metrics", "efficiency");
14426
- let names = [];
14427
- try {
14428
- names = readdirSync3(dir).filter((n2) => /^iteration-.*\.json$/.test(n2)).sort();
14429
- } catch {
14430
- return [];
14431
- }
14432
- const out2 = [];
14433
- for (const n2 of names) {
14434
- const d = readJson(join12(dir, n2));
14435
- if (isObj3(d))
14436
- out2.push(d);
14437
- }
14438
- return out2;
14439
- }
14440
- var num0 = (v) => isNum(v) ? v : 0;
14441
- var priced = (table, d) => d.cost_usd === null || d.cost_usd === undefined ? modelCost(table, String(d.model ?? "sonnet").toLowerCase(), num0(d.input_tokens), num0(d.output_tokens), num0(d.cache_read_tokens), num0(d.cache_creation_tokens)) : num0(d.cost_usd);
14442
- function costSnapshot(lokiDir) {
14443
- const table = pricing(lokiDir);
14444
- let tIn = 0, tOut = 0, tCr = 0, tCw = 0, est = 0;
14445
- let recorded = false, unknownUsd = false;
14446
- const byPhase = {}, byModel = {};
14447
- for (const d of iterationRecords(lokiDir)) {
14448
- const measured = recordIsMeasured(d);
14449
- if (measured)
14450
- recorded = true;
14451
- const tokMeasured = recordIsMeasured(d, "cost_usd");
14452
- const inp = num0(d.input_tokens), out2 = num0(d.output_tokens), cr = num0(d.cache_read_tokens), cw = num0(d.cache_creation_tokens);
14453
- const model = String(d.model ?? "sonnet").toLowerCase();
14454
- const phase = String(d.phase ?? "unknown");
14455
- tIn += inp;
14456
- tOut += out2;
14457
- tCr += cr;
14458
- tCw += cw;
14459
- const cost = priced(table, d);
14460
- est += cost;
14461
- for (const [bucket, name] of [[byPhase, phase], [byModel, model]]) {
14462
- const b = bucket[name] ??= { input_tokens: 0, output_tokens: 0, cost_usd: 0, measured: false, tokens_measured: false };
14463
- b.input_tokens += inp;
14464
- b.output_tokens += out2;
14465
- b.cost_usd += cost;
14466
- b.measured ||= measured;
14467
- b.tokens_measured ||= tokMeasured;
14468
- }
14469
- }
14470
- if (tIn === 0 && tOut === 0) {
14471
- const ctx = readJson(join12(lokiDir, "context", "tracking.json"));
14472
- const totals = isObj3(ctx) && isObj3(ctx.totals) ? ctx.totals : {};
14473
- tIn = num0(totals.total_input);
14474
- tOut = num0(totals.total_output);
14475
- if (tIn > 0 || tOut > 0) {
14476
- recorded = true;
14477
- if (isNum(totals.total_cost_usd))
14478
- est = totals.total_cost_usd;
14479
- else
14480
- unknownUsd = true;
14481
- const model = String(isObj3(ctx) && ctx.provider || "unknown").toLowerCase();
14482
- const per = isObj3(ctx) && Array.isArray(ctx.per_iteration) ? ctx.per_iteration : [];
14483
- for (const it of per) {
14484
- if (!isObj3(it))
14485
- continue;
14486
- const b = byModel[model] ??= { input_tokens: 0, output_tokens: 0, cost_usd: 0, measured: false, tokens_measured: true };
14487
- b.input_tokens += num0(it.input_tokens);
14488
- b.output_tokens += num0(it.output_tokens);
14489
- if (isNum(it.cost_usd)) {
14490
- b.cost_usd += it.cost_usd;
14491
- b.measured = true;
14492
- }
14493
- }
14494
- }
14495
- }
14496
- let limit = null, used = null, remaining = null;
14497
- const bd = readJson(join12(lokiDir, "metrics", "budget.json"));
14498
- if (isObj3(bd) && isNum(bd.limit)) {
14499
- limit = bd.limit;
14500
- if (recorded && !unknownUsd) {
14501
- used = est;
14502
- remaining = Math.max(0, limit - used);
14503
- }
14504
- }
14505
- const readIn = tIn + tCr;
14506
- const fmt = (m) => Object.fromEntries(Object.entries(m).map(([k, v]) => [k, {
14507
- input_tokens: v.tokens_measured ? v.input_tokens : null,
14508
- output_tokens: v.tokens_measured ? v.output_tokens : null,
14509
- cost_usd: v.measured ? r6(v.cost_usd) : null
14510
- }]));
14511
- return {
14512
- total_input_tokens: recorded ? tIn : null,
14513
- total_output_tokens: recorded ? tOut : null,
14514
- total_cache_read_tokens: recorded ? tCr : null,
14515
- total_cache_creation_tokens: recorded ? tCw : null,
14516
- total_tokens: recorded ? tIn + tOut + tCr + tCw : null,
14517
- cache_hit_ratio: readIn > 0 ? Math.round(tCr / readIn * 1e4) / 1e4 : null,
14518
- estimated_cost_usd: recorded && !unknownUsd ? r6(est) : null,
14519
- cost_recorded: recorded,
14520
- by_phase: fmt(byPhase),
14521
- by_model: fmt(byModel),
14522
- budget_limit: limit,
14523
- budget_used: limit !== null && used !== null ? r6(used) : null,
14524
- budget_remaining: remaining !== null ? r6(remaining) : null,
14525
- ...recorded ? {} : { not_measured: NOT_MEASURED }
14526
- };
14527
- }
14528
- function budgetStatus(used, limit) {
14529
- if (limit === null || limit <= 0)
14530
- return "none";
14531
- if (used === null)
14532
- return "unknown";
14533
- if (used >= limit)
14534
- return "exceeded";
14535
- return used >= BUDGET_WARN_FRACTION * limit ? "warn" : "ok";
14536
- }
14537
- function budgetSnapshot(lokiDir) {
14538
- const table = pricing(lokiDir);
14539
- let total = 0, recorded = false, unmeasured = 0;
14540
- for (const d of iterationRecords(lokiDir)) {
14541
- if (recordIsMeasured(d))
14542
- recorded = true;
14543
- else
14544
- unmeasured++;
14545
- total += priced(table, d);
14546
- }
14547
- let limit = null;
14548
- const bd = readJson(join12(lokiDir, "metrics", "budget.json"));
14549
- if (isObj3(bd)) {
14550
- const l = bd.limit || bd.budget_limit;
14551
- limit = isNum(l) ? l : null;
14552
- }
14553
- if (limit === null && process.env.LOKI_BUDGET_LIMIT) {
14554
- const e = Number(process.env.LOKI_BUDGET_LIMIT);
14555
- if (Number.isFinite(e))
14556
- limit = e;
14557
- }
14558
- const used = recorded ? r6(total) : null;
14559
- const ok = limit !== null && limit > 0 && used !== null;
14560
- const status = budgetStatus(used, limit);
14561
- return {
14562
- limit,
14563
- used,
14564
- remaining: ok ? r6(Math.max(0, limit - used)) : null,
14565
- percent_used: ok ? Math.round(used / limit * 1e4) / 100 : null,
14566
- status,
14567
- warn_threshold_percent: Math.round(BUDGET_WARN_FRACTION * 100),
14568
- exceeded: status === "exceeded",
14569
- partial: recorded && unmeasured > 0
14570
- };
14571
- }
14572
- function costTimeline(lokiDir) {
14573
- const table = pricing(lokiDir);
14574
- const recs = iterationRecords(lokiDir).map((d, i) => ({ d, i })).sort((a, b) => num0(Number(a.d.iteration)) - num0(Number(b.d.iteration)) || a.i - b.i).map((x) => x.d);
14575
- const iterations = [];
14576
- let cum = 0, recorded = false, unmeasured = 0;
14577
- for (const d of recs) {
14578
- const measured = recordIsMeasured(d);
14579
- if (measured)
14580
- recorded = true;
14581
- else
14582
- unmeasured++;
14583
- const model = String(d.model ?? "sonnet").toLowerCase();
14584
- cum += priced(table, d);
14585
- iterations.push({
14586
- iteration: d.iteration ?? null,
14587
- timestamp: d.timestamp ?? null,
14588
- model: d.model ? model : null,
14589
- phase: d.phase ?? "unknown",
14590
- provider: d.provider ?? null,
14591
- input_tokens: measured ? num0(d.input_tokens) : null,
14592
- output_tokens: measured ? num0(d.output_tokens) : null,
14593
- cost_usd: measured ? r6(priced(table, d)) : null,
14594
- cumulative_usd: measured ? r6(cum) : null
14595
- });
14596
- }
14597
- const runs2 = [];
14598
- let projectTotal = 0, runsMeasured = 0;
14599
- const proofs = join12(lokiDir, "proofs");
14600
- let entries = [];
14601
- try {
14602
- entries = readdirSync3(proofs).sort();
14603
- } catch {}
14604
- for (const e of entries) {
14605
- try {
14606
- if (!statSync5(join12(proofs, e)).isDirectory())
14607
- continue;
14608
- } catch {
14609
- continue;
14610
- }
14611
- const p = readJson(join12(proofs, e, "proof.json"));
14612
- if (!isObj3(p))
14613
- continue;
14614
- const cost = isObj3(p.cost) ? p.cost : {};
14615
- const usd = isNum(cost.usd) ? cost.usd : null;
14616
- if (usd !== null) {
14617
- projectTotal += usd;
14618
- runsMeasured++;
14619
- }
14620
- runs2.push({
14621
- run_id: p.run_id ?? e,
14622
- generated_at: p.generated_at ?? null,
14623
- model: isObj3(p.provider) ? p.provider.model ?? null : null,
14624
- cost_usd: usd !== null ? r6(usd) : null,
14625
- cost_partial: cost.cost_partial === true,
14626
- files_changed: isObj3(p.files_changed) ? p.files_changed.count ?? null : null,
14627
- final_verdict: isObj3(p.council) ? p.council.final_verdict ?? null : null
14844
+ const since = c.req.query("since");
14845
+ if (since !== undefined && Number.isNaN(Date.parse(since)))
14846
+ return c.json({ error: "since must be an ISO date" }, 400);
14847
+ const q = ctx.db.select().from(runs);
14848
+ const all = since ? q.where(gte(runs.startedAt, since)).all() : q.all();
14849
+ const groups = new Map;
14850
+ const total = mk({});
14851
+ let priced2 = 0, unpriced = 0;
14852
+ for (const r of all) {
14853
+ const key = {};
14854
+ for (const d of dims)
14855
+ key[d] = dimVal(d, r);
14856
+ const id = dims.map((d) => key[d]).join("\x00");
14857
+ let g = groups.get(id);
14858
+ if (!g) {
14859
+ g = mk(key);
14860
+ groups.set(id, g);
14861
+ }
14862
+ add(g, r);
14863
+ add(total, r);
14864
+ if (r.measuredSessions > 0)
14865
+ priced2++;
14866
+ else
14867
+ unpriced++;
14868
+ }
14869
+ const rows = [...groups.values()].map(out).sort((a, b) => {
14870
+ for (const d of dims) {
14871
+ const x = a[d], y = b[d];
14872
+ if (x !== y)
14873
+ return d === "day" ? y.localeCompare(x) : x.localeCompare(y);
14874
+ }
14875
+ return 0;
14628
14876
  });
14629
- }
14630
- runs2.sort((a, b) => String(b.generated_at ?? "").localeCompare(String(a.generated_at ?? "")));
14631
- return {
14632
- current_run: { iterations, total_usd: recorded ? r6(cum) : null, cost_recorded: recorded, partial: recorded && unmeasured > 0, unmeasured_iterations: unmeasured },
14633
- runs: runs2,
14634
- runs_count: runs2.length,
14635
- project_total_usd: runsMeasured ? r6(projectTotal) : null,
14636
- project_total_partial: runsMeasured > 0 && runsMeasured < runs2.length,
14637
- budget: budgetSnapshot(lokiDir),
14638
- ...recorded || runsMeasured ? {} : { not_measured: NOT_MEASURED }
14639
- };
14640
- }
14641
- var lokiDirOf = (repoDir) => join12(repoDir, ".loki");
14642
- var lokiDirExists = (repoDir) => existsSync6(lokiDirOf(repoDir));
14643
- function mount7(ctx) {
14644
- const deny = { error: "loopback only without a token" };
14645
- ctx.app.get("/v1/cost/snapshot", (c) => probeAllowed(ctx, c) ? c.json(costSnapshot(lokiDirOf(ctx.repoDir))) : c.json(deny, 403));
14646
- ctx.app.get("/v1/cost/timeline", (c) => probeAllowed(ctx, c) ? c.json(costTimeline(lokiDirOf(ctx.repoDir))) : c.json(deny, 403));
14877
+ const { runs: n2, ...rest } = out(total);
14878
+ return c.json({
14879
+ group: dims,
14880
+ since: since ?? null,
14881
+ rows,
14882
+ totals: { runs: n2, ...rest },
14883
+ budget: { api_key_default_cap_usd: DEFAULT_API_KEY_CAP_USD, subscription_cap: null, api_key_runs: priced2, subscription_runs: unpriced }
14884
+ });
14885
+ });
14647
14886
  }
14648
14887
 
14649
14888
  // src/server/routes/fleet.ts
14650
- import { readdirSync as readdirSync4, readFileSync as readFileSync10 } from "fs";
14889
+ import { readdirSync as readdirSync5, readFileSync as readFileSync11 } from "fs";
14651
14890
  import { homedir as homedir4 } from "os";
14652
- import { basename as basename4, join as join13 } from "path";
14891
+ import { basename as basename4, join as join14 } from "path";
14653
14892
  var isNum2 = (v) => typeof v === "number" && Number.isFinite(v);
14654
- var isObj4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
14655
- var registryFile = () => join13(process.env.HOME ?? homedir4(), ".loki", "dashboard", "projects.json");
14893
+ var isObj5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
14894
+ var registryFile = () => join14(process.env.HOME ?? homedir4(), ".loki", "dashboard", "projects.json");
14656
14895
  function pidAlive(pid) {
14657
14896
  if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0)
14658
14897
  return false;
@@ -14667,34 +14906,34 @@ function projectSnapshot(path) {
14667
14906
  const snap = { phase: "", iteration: null, cost_usd: null, started_at: null, ended_at: null };
14668
14907
  if (!path)
14669
14908
  return snap;
14670
- const loki = join13(path, ".loki");
14671
- const st = readJson(join13(loki, "dashboard-state.json"));
14672
- if (isObj4(st)) {
14909
+ const loki = join14(path, ".loki");
14910
+ const st = readJson(join14(loki, "dashboard-state.json"));
14911
+ if (isObj5(st)) {
14673
14912
  snap.phase = typeof st.phase === "string" ? st.phase : "";
14674
14913
  snap.iteration = Number.isInteger(st.iteration) ? st.iteration : null;
14675
14914
  }
14676
- const ss = readJson(join13(loki, "session.json"));
14677
- if (isObj4(ss)) {
14915
+ const ss = readJson(join14(loki, "session.json"));
14916
+ if (isObj5(ss)) {
14678
14917
  const a = ss.startedAt || ss.started_at, b = ss.endedAt || ss.ended_at;
14679
14918
  snap.started_at = typeof a === "string" ? a : null;
14680
14919
  snap.ended_at = typeof b === "string" ? b : null;
14681
14920
  }
14682
14921
  let cost = 0, found = false;
14683
- const eff = join13(loki, "metrics", "efficiency");
14922
+ const eff = join14(loki, "metrics", "efficiency");
14684
14923
  let names = [];
14685
14924
  try {
14686
- names = readdirSync4(eff).filter((n2) => n2.endsWith(".json"));
14925
+ names = readdirSync5(eff).filter((n2) => n2.endsWith(".json"));
14687
14926
  } catch {}
14688
14927
  for (const n2 of names) {
14689
- const d = readJson(join13(eff, n2));
14690
- if (isObj4(d) && isNum2(d.cost_usd) && recordIsMeasured(d)) {
14928
+ const d = readJson(join14(eff, n2));
14929
+ if (isObj5(d) && isNum2(d.cost_usd) && recordIsMeasured(d)) {
14691
14930
  cost += d.cost_usd;
14692
14931
  found = true;
14693
14932
  }
14694
14933
  }
14695
14934
  if (!found) {
14696
- const t = readJson(join13(loki, "context", "tracking.json"));
14697
- const totals = isObj4(t) && isObj4(t.totals) ? t.totals : null;
14935
+ const t = readJson(join14(loki, "context", "tracking.json"));
14936
+ const totals = isObj5(t) && isObj5(t.totals) ? t.totals : null;
14698
14937
  if (totals && isNum2(totals.total_cost_usd) && recordIsMeasured({ cost_usd: totals.total_cost_usd, input_tokens: totals.total_input, output_tokens: totals.total_output })) {
14699
14938
  cost = totals.total_cost_usd;
14700
14939
  found = true;
@@ -14706,9 +14945,9 @@ function projectSnapshot(path) {
14706
14945
  function fleetRuns(includeInactive = true, file = registryFile()) {
14707
14946
  let projects = [];
14708
14947
  try {
14709
- const reg = JSON.parse(readFileSync10(file, "utf8"));
14710
- if (isObj4(reg) && isObj4(reg.projects))
14711
- projects = Object.values(reg.projects).filter(isObj4);
14948
+ const reg = JSON.parse(readFileSync11(file, "utf8"));
14949
+ if (isObj5(reg) && isObj5(reg.projects))
14950
+ projects = Object.values(reg.projects).filter(isObj5);
14712
14951
  } catch {
14713
14952
  return [];
14714
14953
  }
@@ -14759,16 +14998,294 @@ function fleetSummary(includeInactive = true, file = registryFile()) {
14759
14998
  };
14760
14999
  }
14761
15000
  var parseInactive = (v) => !["false", "0", "no"].includes((v ?? "true").toLowerCase());
14762
- function mount8(ctx) {
15001
+ function mount9(ctx) {
14763
15002
  const deny = { error: "loopback only without a token" };
14764
15003
  ctx.app.get("/v1/fleet/runs", (c) => probeAllowed(ctx, c) ? c.json(fleetRuns(parseInactive(c.req.query("include_inactive"))).map(({ path: _p, ...r }) => r)) : c.json(deny, 403));
14765
15004
  ctx.app.get("/v1/fleet/summary", (c) => probeAllowed(ctx, c) ? c.json(fleetSummary(parseInactive(c.req.query("include_inactive")))) : c.json(deny, 403));
14766
15005
  }
14767
15006
 
15007
+ // src/server/routes/memory.ts
15008
+ import { existsSync as existsSync8, readFileSync as readFileSync12, readdirSync as readdirSync6, realpathSync as realpathSync7, statSync as statSync7 } from "fs";
15009
+ import { join as join15, sep as sep5 } from "path";
15010
+ var isObj6 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
15011
+ var nonEmpty = (v) => !(v === undefined || v === null || v === "" || Array.isArray(v) && v.length === 0 || isObj6(v) && Object.keys(v).length === 0);
15012
+ var focus = { dir: null };
15013
+ var lokiFor = (repoDir) => lokiDirOf(focus.dir ?? repoDir);
15014
+ function mutationAllowed(c) {
15015
+ if (!peerIsLoopback(c) || !isLoopbackHost(c.req.header("host")))
15016
+ return false;
15017
+ if (!(c.req.header("content-type") ?? "").toLowerCase().startsWith("application/json"))
15018
+ return false;
15019
+ const origin = c.req.header("origin");
15020
+ if (origin === undefined)
15021
+ return true;
15022
+ try {
15023
+ return new URL(origin).host.toLowerCase() === (c.req.header("host") ?? "").toLowerCase();
15024
+ } catch {
15025
+ return false;
15026
+ }
15027
+ }
15028
+ function jsonFiles(dir, cap = Infinity) {
15029
+ const out2 = [];
15030
+ const walk = (d) => {
15031
+ let ents = [];
15032
+ try {
15033
+ ents = readdirSync6(d, { withFileTypes: true });
15034
+ } catch {
15035
+ return;
15036
+ }
15037
+ for (const e of ents.sort((a, b) => a.name < b.name ? -1 : 1)) {
15038
+ if (out2.length >= cap)
15039
+ return;
15040
+ const p = join15(d, e.name);
15041
+ if (e.isDirectory())
15042
+ walk(p);
15043
+ else if (e.isFile() && e.name.endsWith(".json"))
15044
+ out2.push(p);
15045
+ }
15046
+ };
15047
+ walk(dir);
15048
+ return out2;
15049
+ }
15050
+ function focusInfo(repoDir) {
15051
+ return { project_dir: focus.dir, loki_dir: lokiFor(repoDir) };
15052
+ }
15053
+ function setFocus(repoDir, raw2, registry = registryFile()) {
15054
+ const s = typeof raw2 === "string" ? raw2.trim() : "";
15055
+ if (!s)
15056
+ return { ok: false, error: "project_dir must not be empty" };
15057
+ if (!s.startsWith("/") || s.split(/[\\/]/).includes(".."))
15058
+ return { ok: false, error: "project_dir must be an absolute path without .. segments" };
15059
+ let real2 = "";
15060
+ try {
15061
+ real2 = realpathSync7(s);
15062
+ if (!statSync7(real2).isDirectory())
15063
+ throw new Error("not a dir");
15064
+ } catch {
15065
+ return { ok: false, error: `Directory does not exist: ${s}` };
15066
+ }
15067
+ if (!existsSync8(join15(real2, ".loki")) || !statSync7(join15(real2, ".loki")).isDirectory())
15068
+ return { ok: false, error: `Directory does not contain a .loki/ subdirectory: ${s}` };
15069
+ const allowed = new Set;
15070
+ try {
15071
+ allowed.add(realpathSync7(repoDir));
15072
+ } catch {}
15073
+ const reg = readJson(registry);
15074
+ if (isObj6(reg) && isObj6(reg.projects))
15075
+ for (const p of Object.values(reg.projects)) {
15076
+ if (isObj6(p) && typeof p.path === "string") {
15077
+ try {
15078
+ allowed.add(realpathSync7(p.path));
15079
+ } catch {}
15080
+ }
15081
+ }
15082
+ if (!allowed.has(real2))
15083
+ return { ok: false, error: "project_dir is not this checkout or a registered project" };
15084
+ focus.dir = real2;
15085
+ return { ok: true };
15086
+ }
15087
+ var GROUPS = [["pending", "pending"], ["inProgress", "in_progress"], ["review", "review"], ["completed", "done"], ["failed", "done"]];
15088
+ var QUEUES = [["pending.json", "pending"], ["in-progress.json", "in_progress"], ["completed.json", "done"], ["failed.json", "done"], ["dead-letter.json", "done"]];
15089
+ var RANK = { done: 3, in_progress: 2, review: 1, pending: 0 };
15090
+ function tasksList(loki, status) {
15091
+ const all = [];
15092
+ const st = readJson(join15(loki, "dashboard-state.json"));
15093
+ const groups = isObj6(st) && isObj6(st.tasks) ? st.tasks : {};
15094
+ for (const [key, mapped] of GROUPS) {
15095
+ const g = groups[key];
15096
+ if (!Array.isArray(g))
15097
+ continue;
15098
+ g.forEach((t, i) => {
15099
+ if (!isObj6(t))
15100
+ return;
15101
+ const payload = isObj6(t.payload) ? t.payload : {};
15102
+ const e = { id: t.id ?? `${key}-${i}`, title: t.title ?? payload.action ?? t.type ?? "Task", description: t.description ?? payload.description ?? "", status: mapped, priority: t.priority ?? payload.priority ?? "medium", type: t.type ?? "task", position: i };
15103
+ for (const f of ["acceptance_criteria", "notes", "logs", "user_story", "project", "source", "specification", "provider", "startedAt", "full_content", "completedAt", "phase", "exitCode"])
15104
+ if (nonEmpty(t[f]))
15105
+ e[f] = t[f];
15106
+ if (t.exitCode === 0)
15107
+ e.exitCode = 0;
15108
+ if (key === "completed" || key === "failed")
15109
+ e._terminal_outcome = key;
15110
+ all.push(e);
15111
+ });
15112
+ }
15113
+ for (const [file, qs] of QUEUES) {
15114
+ const raw2 = readJson(join15(loki, "queue", file));
15115
+ const items = isObj6(raw2) ? raw2.tasks : raw2;
15116
+ if (!Array.isArray(items))
15117
+ continue;
15118
+ const term = file === "completed.json" ? "completed" : file === "failed.json" || file === "dead-letter.json" ? "failed" : null;
15119
+ items.forEach((it, i) => {
15120
+ if (!isObj6(it))
15121
+ return;
15122
+ const id = it.id ?? `q-${qs}-${i}`;
15123
+ if (term === null && all.some((t) => t.id === id))
15124
+ return;
15125
+ const e = { id, title: it.title ?? it.action ?? "Task", description: it.description ?? "", status: qs, priority: it.priority ?? "medium", type: it.type ?? "task", position: i };
15126
+ for (const f of ["acceptance_criteria", "user_story", "project", "source", "provider", "startedAt", "completedAt", "phase"])
15127
+ if (nonEmpty(it[f]))
15128
+ e[f] = it[f];
15129
+ for (const f of ["notes", "logs"])
15130
+ if (Array.isArray(it[f]))
15131
+ e[f] = it[f];
15132
+ if (Number.isInteger(it.exitCode))
15133
+ e.exitCode = it.exitCode;
15134
+ if (term)
15135
+ e._terminal_outcome = term;
15136
+ all.push(e);
15137
+ });
15138
+ }
15139
+ const auth = (t) => [RANK[t.status] ?? -1, t._terminal_outcome === "failed" ? 1 : 0];
15140
+ const better = (a, b) => {
15141
+ const x = auth(a), y = auth(b);
15142
+ return x[0] !== y[0] ? x[0] > y[0] : x[1] > y[1];
15143
+ };
15144
+ const byId = new Map, order2 = [];
15145
+ for (const t of all) {
15146
+ if (t.id === undefined || t.id === null) {
15147
+ order2.push(t);
15148
+ continue;
15149
+ }
15150
+ const prev = byId.get(t.id);
15151
+ if (!prev) {
15152
+ byId.set(t.id, t);
15153
+ order2.push({ ref: t.id });
15154
+ } else if (better(t, prev))
15155
+ byId.set(t.id, t);
15156
+ }
15157
+ const out2 = order2.map((o) => ("ref" in o) ? byId.get(o.ref) : o);
15158
+ for (const t of out2) {
15159
+ if (t.type === "iteration" && !t.description) {
15160
+ const ec = t.exitCode, failed = t._terminal_outcome === "failed";
15161
+ const o = Number.isInteger(ec) && ec === 0 && !failed ? "completed cleanly (exit 0)" : Number.isInteger(ec) && ec !== 0 ? `failed (exit ${ec})` : failed ? "failed" : t._terminal_outcome === "completed" ? "completed" : t.status === "done" ? "finished" : null;
15162
+ if (o)
15163
+ t.description = `Iteration ${o}${t.provider ? `, built by ${t.provider}` : ""}.`;
15164
+ }
15165
+ }
15166
+ return status ? out2.filter((t) => t.status === status) : out2;
15167
+ }
15168
+ var memDir = (loki, sub) => join15(loki, "memory", sub);
15169
+ function memoryEpisodes(loki, limit = 50) {
15170
+ const eps = jsonFiles(memDir(loki, "episodic")).map(readJson).filter(isObj6);
15171
+ eps.sort((a, b) => String(b.timestamp ?? "").localeCompare(String(a.timestamp ?? "")));
15172
+ return eps.slice(0, limit);
15173
+ }
15174
+ function memoryEpisode(loki, id) {
15175
+ for (const f of jsonFiles(memDir(loki, "episodic"))) {
15176
+ const d = readJson(f);
15177
+ if (isObj6(d) && (d.id === id || f.split(sep5).pop() === `${id}.json`))
15178
+ return d;
15179
+ }
15180
+ return null;
15181
+ }
15182
+ function memoryPatterns(loki) {
15183
+ const d = readJson(join15(memDir(loki, "semantic"), "patterns.json"));
15184
+ const list = Array.isArray(d) ? d : isObj6(d) && Array.isArray(d.patterns) ? d.patterns : [];
15185
+ return list.filter(isObj6);
15186
+ }
15187
+ var memorySkills = (loki) => jsonFiles(memDir(loki, "skills")).map(readJson).filter(isObj6);
15188
+ function memoryEconomics(loki) {
15189
+ const rawV = readJson(join15(loki, "memory", "token_economics.json"));
15190
+ const raw2 = isObj6(rawV) ? rawV : {};
15191
+ const m = isObj6(raw2.metrics) ? raw2.metrics : {};
15192
+ const n2 = (v) => typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : 0;
15193
+ const hits = n2(m.cache_hits), misses = n2(m.cache_misses), total = hits + misses;
15194
+ const recorded = Object.keys(m).length > 0;
15195
+ const disc = recorded ? n2(m.discovery_tokens) : null, read = recorded ? n2(m.read_tokens) : null;
15196
+ const cands = [];
15197
+ for (const kind of ["episodic", "semantic"]) {
15198
+ for (const f of jsonFiles(memDir(loki, kind), 300)) {
15199
+ const d = readJson(f);
15200
+ if (!isObj6(d))
15201
+ continue;
15202
+ const ctx = isObj6(d.context) ? d.context : {};
15203
+ cands.push({ id: d.id ?? "", kind, access_count: n2(d.access_count), importance: Number(d.importance) || 0, summary: String(d.summary || d.pattern || ctx.goal || "").slice(0, 160) });
15204
+ }
15205
+ }
15206
+ cands.sort((a, b) => b.access_count - a.access_count || b.importance - a.importance);
15207
+ return {
15208
+ session_id: raw2.session_id ?? null,
15209
+ discovery_tokens: disc,
15210
+ read_tokens: read,
15211
+ total_tokens: recorded ? disc + read : null,
15212
+ cache_hits: hits,
15213
+ cache_misses: misses,
15214
+ hit_rate: total > 0 ? Math.round(hits / total * 1e4) / 1e4 : null,
15215
+ ratio: raw2.ratio ?? null,
15216
+ savings_percent: raw2.savings_percent ?? null,
15217
+ top_patterns: cands.slice(0, 10).filter((p) => p.access_count > 0),
15218
+ discoveryTokens: disc,
15219
+ readTokens: read,
15220
+ savingsPercent: raw2.savings_percent ?? null,
15221
+ raw: raw2
15222
+ };
15223
+ }
15224
+ function contextTracking(loki) {
15225
+ const f = join15(loki, "context", "tracking.json");
15226
+ if (!existsSync8(f)) {
15227
+ return { status: 200, body: {
15228
+ session_id: null,
15229
+ updated_at: null,
15230
+ current: { input_tokens: null, output_tokens: null, cache_read_tokens: null, cache_creation_tokens: null, total_tokens: null, context_window_pct: null, estimated_cost_usd: null },
15231
+ compactions: [],
15232
+ per_iteration: [],
15233
+ totals: { total_input: null, total_output: null, total_cost_usd: null, compaction_count: null, iterations_tracked: null }
15234
+ } };
15235
+ }
15236
+ try {
15237
+ return { status: 200, body: JSON.parse(readFileSync12(f, "utf8")) };
15238
+ } catch {
15239
+ return { status: 500, body: { error: "Failed to read context tracking data" } };
15240
+ }
15241
+ }
15242
+ function mount10(ctx) {
15243
+ const deny = { error: "loopback only without a token" };
15244
+ const read = (path, fn) => ctx.app.get(path, (c) => probeAllowed(ctx, c) ? fn(c, lokiFor(ctx.repoDir)) : c.json(deny, 403));
15245
+ read("/v1/tasks", (c, l) => {
15246
+ if (c.req.query("project_id") !== undefined)
15247
+ return c.json({ error: "project_id filter is not supported by the Control Plane" }, 400);
15248
+ return c.json(tasksList(l, c.req.query("status")));
15249
+ });
15250
+ read("/v1/memory/episodes", (c, l) => {
15251
+ const lim = c.req.query("limit") === undefined ? 50 : Number(c.req.query("limit"));
15252
+ return Number.isInteger(lim) && lim >= 1 && lim <= 1000 ? c.json(memoryEpisodes(l, lim)) : c.json({ error: "limit must be 1..1000" }, 422);
15253
+ });
15254
+ read("/v1/memory/episodes/:id", (c, l) => {
15255
+ const e = memoryEpisode(l, c.req.param("id"));
15256
+ return e ? c.json(e) : c.json({ error: "Episode not found" }, 404);
15257
+ });
15258
+ read("/v1/memory/patterns", (c, l) => c.json(memoryPatterns(l)));
15259
+ read("/v1/memory/patterns/:id", (c, l) => {
15260
+ const p = memoryPatterns(l).find((x) => x.id === c.req.param("id"));
15261
+ return p ? c.json(p) : c.json({ error: "Pattern not found" }, 404);
15262
+ });
15263
+ read("/v1/memory/skills", (c, l) => c.json(memorySkills(l)));
15264
+ read("/v1/memory/economics", (c, l) => c.json(memoryEconomics(l)));
15265
+ read("/v1/context", (c, l) => {
15266
+ const r = contextTracking(l);
15267
+ return c.json(r.body, r.status);
15268
+ });
15269
+ read("/v1/focus", (c) => c.json(focusInfo(ctx.repoDir)));
15270
+ ctx.act.post("/v1/focus", async (c) => {
15271
+ if (!mutationAllowed(c))
15272
+ return c.json({ error: "loopback JSON requests only" }, 403);
15273
+ const body = await c.req.json().catch(() => null);
15274
+ const r = setFocus(ctx.repoDir, isObj6(body) ? body.project_dir : undefined);
15275
+ return r.ok ? c.json(focusInfo(ctx.repoDir)) : c.json({ error: r.error }, 400);
15276
+ });
15277
+ ctx.act.delete("/v1/focus", (c) => {
15278
+ if (!mutationAllowed(c))
15279
+ return c.json({ error: "loopback JSON requests only" }, 403);
15280
+ focus.dir = null;
15281
+ return c.json(focusInfo(ctx.repoDir));
15282
+ });
15283
+ }
15284
+
14768
15285
  // src/server/routes/metrics.ts
14769
- import { readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync6 } from "fs";
14770
- import { join as join14 } from "path";
14771
- var isObj5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
15286
+ import { readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
15287
+ import { join as join16 } from "path";
15288
+ var isObj7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
14772
15289
  var arr = (v) => Array.isArray(v) ? v : [];
14773
15290
  function buildMetricsText(repoDir, now = Date.now()) {
14774
15291
  if (!lokiDirExists(repoDir))
@@ -14784,8 +15301,8 @@ function buildMetricsText(repoDir, now = Date.now()) {
14784
15301
  L.push(...samples);
14785
15302
  L.push("");
14786
15303
  };
14787
- const stRaw = readJson(join14(loki, "dashboard-state.json"));
14788
- const state = isObj5(stRaw) ? stRaw : null;
15304
+ const stRaw = readJson(join16(loki, "dashboard-state.json"));
15305
+ const state = isObj7(stRaw) ? stRaw : null;
14789
15306
  let status = 0;
14790
15307
  if (state?.mode === "paused")
14791
15308
  status = 2;
@@ -14793,7 +15310,7 @@ function buildMetricsText(repoDir, now = Date.now()) {
14793
15310
  status = 1;
14794
15311
  else {
14795
15312
  try {
14796
- if (pidAlive(parseInt(readFileSync11(join14(loki, "loki.pid"), "utf8").trim(), 10)))
15313
+ if (pidAlive(parseInt(readFileSync13(join16(loki, "loki.pid"), "utf8").trim(), 10)))
14797
15314
  status = 1;
14798
15315
  } catch {}
14799
15316
  }
@@ -14801,7 +15318,7 @@ function buildMetricsText(repoDir, now = Date.now()) {
14801
15318
  gauge("loki_iteration_current", "Current iteration number", "gauge", typeof state?.iteration === "number" ? [`loki_iteration_current ${state.iteration}`] : null);
14802
15319
  const maxIt = Number.parseInt(process.env.LOKI_MAX_ITERATIONS ?? "", 10);
14803
15320
  gauge("loki_iteration_max", "Maximum configured iterations", "gauge", [`loki_iteration_max ${Number.isFinite(maxIt) ? maxIt : 1000}`]);
14804
- const tasks = state && isObj5(state.tasks) ? state.tasks : null;
15321
+ const tasks = state && isObj7(state.tasks) ? state.tasks : null;
14805
15322
  gauge("loki_tasks_total", "Number of tasks by status", "gauge", tasks ? [
14806
15323
  `loki_tasks_total{status="pending"} ${arr(tasks.pending).length}`,
14807
15324
  `loki_tasks_total{status="in_progress"} ${arr(tasks.inProgress).length}`,
@@ -14809,19 +15326,19 @@ function buildMetricsText(repoDir, now = Date.now()) {
14809
15326
  `loki_tasks_total{status="failed"} ${arr(tasks.failed).length}`
14810
15327
  ] : null);
14811
15328
  let agents = null;
14812
- const af = readJson(join14(loki, "state", "agents.json"));
15329
+ const af = readJson(join16(loki, "state", "agents.json"));
14813
15330
  if (Array.isArray(af) && af.length)
14814
15331
  agents = af;
14815
15332
  else if (state && Array.isArray(state.agents) && state.agents.length)
14816
15333
  agents = state.agents;
14817
- const active = (agents ?? []).filter((a) => isObj5(a) && a.status === "active").length;
15334
+ const active = (agents ?? []).filter((a) => isObj7(a) && a.status === "active").length;
14818
15335
  gauge("loki_agents_active", "Number of currently active agents", "gauge", agents ? [`loki_agents_active ${active}`] : null);
14819
15336
  gauge("loki_agents_total", "Total number of agents registered", "gauge", agents ? [`loki_agents_total ${agents.length}`] : null);
14820
15337
  const cost = costSnapshot(loki);
14821
15338
  gauge("loki_cost_usd", "Estimated total cost in USD", "gauge", cost.estimated_cost_usd !== null ? [`loki_cost_usd ${cost.estimated_cost_usd}`] : null);
14822
15339
  let events2 = null;
14823
15340
  try {
14824
- events2 = readFileSync11(join14(loki, "events.jsonl"), "utf8").split(`
15341
+ events2 = readFileSync13(join16(loki, "events.jsonl"), "utf8").split(`
14825
15342
  `).filter((l) => l.trim()).length;
14826
15343
  } catch {}
14827
15344
  gauge("loki_events_total", "Total number of events recorded", "counter", events2 !== null ? [`loki_events_total ${events2}`] : null);
@@ -14833,23 +15350,23 @@ function buildMetricsText(repoDir, now = Date.now()) {
14833
15350
  }
14834
15351
  gauge("loki_uptime_seconds", "Seconds since session started", "gauge", uptime !== null ? [`loki_uptime_seconds ${uptime}`] : null);
14835
15352
  let total = 0, ver = 0, gaps = 0, notver = 0, unk = 0, att = 0, sig = 0;
14836
- const pdir = join14(loki, "proofs");
15353
+ const pdir = join16(loki, "proofs");
14837
15354
  let entries = [];
14838
15355
  try {
14839
- entries = readdirSync5(pdir).sort();
15356
+ entries = readdirSync7(pdir).sort();
14840
15357
  } catch {}
14841
15358
  for (const e of entries) {
14842
15359
  try {
14843
- if (!statSync6(join14(pdir, e)).isDirectory())
15360
+ if (!statSync8(join16(pdir, e)).isDirectory())
14844
15361
  continue;
14845
15362
  } catch {
14846
15363
  continue;
14847
15364
  }
14848
- const d = readJson(join14(pdir, e, "proof.json"));
14849
- if (!isObj5(d))
15365
+ const d = readJson(join16(pdir, e, "proof.json"));
15366
+ if (!isObj7(d))
14850
15367
  continue;
14851
15368
  total++;
14852
- const head = isObj5(d.honesty) ? d.honesty.headline : null;
15369
+ const head = isObj7(d.honesty) ? d.honesty.headline : null;
14853
15370
  if (head === "VERIFIED")
14854
15371
  ver++;
14855
15372
  else if (head === "VERIFIED WITH GAPS")
@@ -14858,7 +15375,7 @@ function buildMetricsText(repoDir, now = Date.now()) {
14858
15375
  notver++;
14859
15376
  else
14860
15377
  unk++;
14861
- if (isObj5(d.verification)) {
15378
+ if (isObj7(d.verification)) {
14862
15379
  if (d.verification.attestation)
14863
15380
  att++;
14864
15381
  if (d.verification.gpg_signature)
@@ -14880,7 +15397,7 @@ function buildMetricsText(repoDir, now = Date.now()) {
14880
15397
  `) + `
14881
15398
  `;
14882
15399
  }
14883
- function mount9(ctx) {
15400
+ function mount11(ctx) {
14884
15401
  ctx.app.get("/v1/metrics", (c) => {
14885
15402
  if (!probeAllowed(ctx, c))
14886
15403
  return c.json({ error: "loopback only without a token" }, 403);
@@ -14888,14 +15405,216 @@ function mount9(ctx) {
14888
15405
  });
14889
15406
  }
14890
15407
 
15408
+ // src/server/routes/session_control.ts
15409
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync8, readFileSync as readFileSync14, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
15410
+ import { dirname as dirname3, join as join17 } from "path";
15411
+ var isObj8 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
15412
+ var ACTIONS = ["pause", "resume", "stop", "council-review"];
15413
+ var isAction = (v) => ACTIONS.includes(v);
15414
+ function sameOrigin(c) {
15415
+ if (c.req.header("sec-fetch-site") === "cross-site")
15416
+ return false;
15417
+ const origin = c.req.header("origin");
15418
+ if (origin === undefined)
15419
+ return true;
15420
+ try {
15421
+ return new URL(origin).host.toLowerCase() === (c.req.header("host") ?? "").toLowerCase();
15422
+ } catch {
15423
+ return false;
15424
+ }
15425
+ }
15426
+ var readJson2 = (p) => {
15427
+ try {
15428
+ return JSON.parse(readFileSync14(p, "utf8"));
15429
+ } catch {
15430
+ return null;
15431
+ }
15432
+ };
15433
+ function runnerPid(loki) {
15434
+ try {
15435
+ const pid = parseInt(readFileSync14(join17(loki, "loki.pid"), "utf8").trim(), 10);
15436
+ return pidAlive(pid) ? pid : null;
15437
+ } catch {
15438
+ return null;
15439
+ }
15440
+ }
15441
+ function applyControl(repoDir, action) {
15442
+ const loki = lokiDirOf(repoDir);
15443
+ const running = runnerPid(loki) !== null;
15444
+ const put = (rel) => {
15445
+ const f = join17(loki, rel);
15446
+ mkdirSync4(dirname3(f), { recursive: true });
15447
+ writeFileSync5(f, new Date().toISOString());
15448
+ };
15449
+ if (action === "council-review") {
15450
+ put(join17("signals", "COUNCIL_REVIEW_REQUESTED"));
15451
+ return { status: 200, body: { success: true, message: "Council review requested" } };
15452
+ }
15453
+ if (action === "resume") {
15454
+ for (const n2 of ["PAUSE", "STOP"]) {
15455
+ try {
15456
+ unlinkSync2(join17(loki, n2));
15457
+ } catch {}
15458
+ }
15459
+ return running ? { status: 200, body: { success: true, message: "Session resumed", process_verified: true } } : { status: 503, body: { success: false, message: "Session process is not running; the signal files were cleared" } };
15460
+ }
15461
+ put(action === "pause" ? "PAUSE" : "STOP");
15462
+ if (!running)
15463
+ return { status: 503, body: { success: false, message: `Session process is not running; the ${action} signal may have no effect` } };
15464
+ return { status: 200, body: { success: true, message: action === "pause" ? "Session paused" : "Stop requested; the runner exits at its next check", process_verified: true } };
15465
+ }
15466
+ function runControl(db, repoDir, action, c, via) {
15467
+ if (!sameOrigin(c)) {
15468
+ audit3(db, { kind: `session.${action}`, target: via, result: "refused", detail: "origin not allowed" });
15469
+ return c.json({ error: "origin not allowed" }, 403);
15470
+ }
15471
+ const r = applyControl(repoDir, action);
15472
+ audit3(db, { kind: `session.${action}`, target: via, result: r.status === 200 ? "ok" : "unverified" });
15473
+ return c.json(r.body, r.status);
15474
+ }
15475
+ function councilState(loki) {
15476
+ const f = join17(loki, "council", "state.json");
15477
+ if (!existsSync9(f))
15478
+ return { enabled: false, total_votes: 0, verdicts: [] };
15479
+ const d = readJson2(f);
15480
+ return isObj8(d) ? d : { enabled: null, total_votes: null, verdicts: null, error: "unreadable_state" };
15481
+ }
15482
+ function councilVerdicts(loki, limit = 20) {
15483
+ const st = readJson2(join17(loki, "council", "state.json"));
15484
+ const verdicts = isObj8(st) && Array.isArray(st.verdicts) ? st.verdicts : [];
15485
+ const votes = join17(loki, "council", "votes");
15486
+ const details = [];
15487
+ let names = [];
15488
+ try {
15489
+ names = readdirSync8(votes, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
15490
+ } catch {}
15491
+ const text2 = (p) => {
15492
+ try {
15493
+ return readFileSync14(p, "utf8");
15494
+ } catch {
15495
+ return null;
15496
+ }
15497
+ };
15498
+ for (const n2 of names.slice(0, limit)) {
15499
+ const dir = join17(votes, n2), d = { iteration: n2 };
15500
+ const ev = text2(join17(dir, "evidence.md"));
15501
+ if (ev !== null)
15502
+ d.evidence_preview = ev.slice(0, 500);
15503
+ let files = [];
15504
+ try {
15505
+ files = readdirSync8(dir).filter((f) => /^member-.*\.txt$/.test(f)).sort();
15506
+ } catch {}
15507
+ d.members = files.flatMap((f) => {
15508
+ const t = text2(join17(dir, f));
15509
+ return t === null ? [] : [{ member: f.replace(/\.txt$/, ""), content: t.trim() }];
15510
+ });
15511
+ const con = text2(join17(dir, "contrarian.txt"));
15512
+ if (con !== null)
15513
+ d.contrarian = con.trim();
15514
+ details.push(d);
15515
+ }
15516
+ return { verdicts, details };
15517
+ }
15518
+ function councilConvergence(loki) {
15519
+ const dataPoints = [];
15520
+ let raw2 = "";
15521
+ try {
15522
+ raw2 = readFileSync14(join17(loki, "council", "convergence.log"), "utf8");
15523
+ } catch {}
15524
+ for (const line2 of raw2.trim().split(`
15525
+ `)) {
15526
+ const p = line2.split("|");
15527
+ if (p.length < 5)
15528
+ continue;
15529
+ const n2 = p.slice(1, 5).map((x) => /^-?\d+$/.test(x.trim()) ? Number(x) : NaN);
15530
+ if (n2.some(Number.isNaN))
15531
+ continue;
15532
+ dataPoints.push({ timestamp: p[0], iteration: n2[0], files_changed: n2[1], no_change_streak: n2[2], done_signals: n2[3] });
15533
+ }
15534
+ return { dataPoints };
15535
+ }
15536
+ function councilReport(loki) {
15537
+ try {
15538
+ return { report: readFileSync14(join17(loki, "council", "report.md"), "utf8") };
15539
+ } catch {
15540
+ return { report: null };
15541
+ }
15542
+ }
15543
+ function councilTranscripts(loki, q) {
15544
+ const limit = Math.min(Math.max(q.limit ?? 20, 1), 200);
15545
+ let since = null;
15546
+ if (q.since) {
15547
+ since = Date.parse(q.since);
15548
+ if (Number.isNaN(since))
15549
+ return { error: "Invalid 'since' timestamp format; expected ISO8601" };
15550
+ }
15551
+ const dir = join17(loki, "council", "transcripts");
15552
+ let names = [];
15553
+ try {
15554
+ names = readdirSync8(dir).filter((f) => /^iter-.*\.json$/.test(f)).sort().reverse();
15555
+ } catch {
15556
+ return { transcripts: [], total: 0, latest_id: null };
15557
+ }
15558
+ const out2 = [];
15559
+ for (const n2 of names) {
15560
+ const rec = readJson2(join17(dir, n2));
15561
+ if (!isObj8(rec) || typeof rec.iteration_id !== "string")
15562
+ continue;
15563
+ if (since !== null) {
15564
+ const t = Date.parse(String(rec.timestamp ?? ""));
15565
+ if (Number.isNaN(t) || t <= since)
15566
+ continue;
15567
+ }
15568
+ if (q.iter_min !== undefined && Number(rec.iteration ?? 0) < q.iter_min)
15569
+ continue;
15570
+ out2.push(rec);
15571
+ if (out2.length >= limit)
15572
+ break;
15573
+ }
15574
+ return { transcripts: out2, total: out2.length, latest_id: out2.length ? out2[0].iteration_id : null };
15575
+ }
15576
+ var intQ = (v, max) => v === undefined ? undefined : /^\d+$/.test(v) && Number(v) <= max ? Number(v) : null;
15577
+ function mount12(ctx) {
15578
+ const deny = { error: "loopback only without a token" };
15579
+ const loki = () => lokiDirOf(ctx.repoDir);
15580
+ ctx.act.post("/v1/control/:action", (c) => {
15581
+ if (!ctx.local(c))
15582
+ return c.json({ error: "loopback JSON requests only" }, 403);
15583
+ const action = c.req.param("action");
15584
+ if (!isAction(action))
15585
+ return c.json({ error: `action must be one of ${ACTIONS.join(", ")}` }, 400);
15586
+ return runControl(ctx.db, ctx.repoDir, action, c, "v1");
15587
+ });
15588
+ const read = (path, f) => ctx.app.get(path, (c) => {
15589
+ if (!probeAllowed(ctx, c))
15590
+ return c.json(deny, 403);
15591
+ const r = f(c);
15592
+ return r instanceof Response ? r : c.json(r);
15593
+ });
15594
+ read("/v1/council/state", () => councilState(loki()));
15595
+ read("/v1/council/verdicts", (c) => {
15596
+ const n2 = intQ(c.req.query("limit"), 1000);
15597
+ return n2 === null || n2 === 0 ? c.json({ error: "limit must be 1..1000" }, 422) : councilVerdicts(loki(), n2);
15598
+ });
15599
+ read("/v1/council/convergence", () => councilConvergence(loki()));
15600
+ read("/v1/council/report", () => councilReport(loki()));
15601
+ read("/v1/council/transcripts", (c) => {
15602
+ const limit = intQ(c.req.query("limit"), 200), iterMin = intQ(c.req.query("iter_min"), Number.MAX_SAFE_INTEGER);
15603
+ if (limit === null || limit === 0 || iterMin === null)
15604
+ return c.json({ error: "limit must be 1..200 and iter_min a non-negative integer" }, 422);
15605
+ const r = councilTranscripts(loki(), { limit, since: c.req.query("since"), iter_min: iterMin });
15606
+ return "error" in r ? c.json(r, 400) : r;
15607
+ });
15608
+ }
15609
+
14891
15610
  // src/server/routes/integrations.ts
14892
15611
  import { spawn as spawn2 } from "child_process";
14893
- import { existsSync as existsSync7, readFileSync as readFileSync12, statSync as statSync7 } from "fs";
14894
- import { delimiter, join as join15 } from "path";
15612
+ import { existsSync as existsSync10, readFileSync as readFileSync15, statSync as statSync9 } from "fs";
15613
+ import { delimiter, join as join18 } from "path";
14895
15614
  var GH_TIMEOUT_MS = 3000;
14896
15615
  var isSet = (n2) => !!n2 && (process.env[n2] ?? "") !== "";
14897
15616
  function ghAuthed(pathVar = process.env.PATH ?? "", timeoutMs = GH_TIMEOUT_MS) {
14898
- const exe = pathVar.split(delimiter).filter(Boolean).map((d) => join15(d, "gh")).find((p) => existsSync7(p));
15617
+ const exe = pathVar.split(delimiter).filter(Boolean).map((d) => join18(d, "gh")).find((p) => existsSync10(p));
14899
15618
  if (!exe)
14900
15619
  return Promise.resolve(null);
14901
15620
  return new Promise((resolve4) => {
@@ -14927,9 +15646,9 @@ function ghAuthed(pathVar = process.env.PATH ?? "", timeoutMs = GH_TIMEOUT_MS) {
14927
15646
  function configuredName(repoDir, path) {
14928
15647
  try {
14929
15648
  const r = resolveConfigFile(repoDir);
14930
- if (!r.ok || !r.exists || statSync7(r.file).size > 1e6)
15649
+ if (!r.ok || !r.exists || statSync9(r.file).size > 1e6)
14931
15650
  return null;
14932
- const doc = $parseDocument(readFileSync12(r.file, "utf8"));
15651
+ const doc = $parseDocument(readFileSync15(r.file, "utf8"));
14933
15652
  if (doc.errors.length)
14934
15653
  return null;
14935
15654
  const v = doc.getIn(path);
@@ -14940,10 +15659,10 @@ function configuredName(repoDir, path) {
14940
15659
  }
14941
15660
  function mcpServers(repoDir) {
14942
15661
  try {
14943
- const p = join15(repoDir, ".mcp.json");
14944
- if (!existsSync7(p) || statSync7(p).size > 1e6)
15662
+ const p = join18(repoDir, ".mcp.json");
15663
+ if (!existsSync10(p) || statSync9(p).size > 1e6)
14945
15664
  return null;
14946
- const j = JSON.parse(readFileSync12(p, "utf8"));
15665
+ const j = JSON.parse(readFileSync15(p, "utf8"));
14947
15666
  return j.mcpServers && typeof j.mcpServers === "object" ? Object.keys(j.mcpServers).length : null;
14948
15667
  } catch {
14949
15668
  return null;
@@ -15010,7 +15729,7 @@ async function listIntegrations(repoDir, gh = () => ghAuthed()) {
15010
15729
  });
15011
15730
  return rows;
15012
15731
  }
15013
- function mount10(ctx) {
15732
+ function mount13(ctx) {
15014
15733
  const cached = ttlCache();
15015
15734
  ctx.app.get("/v1/integrations", async (c) => {
15016
15735
  if (!probeAllowed(ctx, c))
@@ -15021,7 +15740,7 @@ function mount10(ctx) {
15021
15740
 
15022
15741
  // src/server/routes/merge_risk.ts
15023
15742
  import { spawn as spawn3 } from "child_process";
15024
- import { realpathSync as realpathSync6 } from "fs";
15743
+ import { realpathSync as realpathSync8 } from "fs";
15025
15744
  import { basename as basename5, resolve as resolve4 } from "path";
15026
15745
  var PR = /^[0-9]{1,7}$/;
15027
15746
  var REF = /^[A-Za-z0-9._/][A-Za-z0-9._/-]{0,99}$/;
@@ -15113,14 +15832,14 @@ function parseRisk(out2) {
15113
15832
  return null;
15114
15833
  }
15115
15834
  }
15116
- function mount11(ctx) {
15835
+ function mount14(ctx) {
15117
15836
  const { act, db } = ctx;
15118
15837
  const bin = () => ctx.startBin ?? process.env["LOKI_BIN"] ?? "loki";
15119
15838
  const busy2 = new Set;
15120
15839
  const active = { run: 0, queue: 0, risk: 0 };
15121
15840
  const real2 = (p) => {
15122
15841
  try {
15123
- return realpathSync6(p);
15842
+ return realpathSync8(p);
15124
15843
  } catch {
15125
15844
  return null;
15126
15845
  }
@@ -15317,7 +16036,7 @@ function deriveNotifications(db) {
15317
16036
  }
15318
16037
  return out2.sort((a, b) => a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : a.id < b.id ? -1 : 1);
15319
16038
  }
15320
- function mount12(ctx) {
16039
+ function mount15(ctx) {
15321
16040
  ctx.app.get("/v1/notifications", (c) => {
15322
16041
  const all = deriveNotifications(ctx.db);
15323
16042
  const limit = Math.min(Math.max(Number.parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
@@ -15331,8 +16050,8 @@ function mount12(ctx) {
15331
16050
 
15332
16051
  // src/server/routes/providers.ts
15333
16052
  import { spawn as spawn4 } from "child_process";
15334
- import { existsSync as existsSync8, readFileSync as readFileSync13 } from "fs";
15335
- import { delimiter as delimiter2, join as join16 } from "path";
16053
+ import { existsSync as existsSync11, readFileSync as readFileSync16 } from "fs";
16054
+ import { delimiter as delimiter2, join as join19 } from "path";
15336
16055
  var ORDER = ["claude", "codex", "cline", "aider", "opencode"];
15337
16056
  var DEPRECATED = ["gemini"];
15338
16057
  var AUTH_ENV = {
@@ -15348,8 +16067,8 @@ function findOnPath(bin, pathVar) {
15348
16067
  for (const d of pathVar.split(delimiter2)) {
15349
16068
  if (!d)
15350
16069
  continue;
15351
- const p = join16(d, bin);
15352
- if (existsSync8(p))
16070
+ const p = join19(d, bin);
16071
+ if (existsSync11(p))
15353
16072
  return p;
15354
16073
  }
15355
16074
  return null;
@@ -15393,14 +16112,14 @@ function probeCli(bin, timeoutMs, pathVar = process.env.PATH ?? "") {
15393
16112
  });
15394
16113
  }
15395
16114
  function providersDir() {
15396
- const cands = [process.env.LOKI_PROVIDERS_DIR, join16(import.meta.dir, "../../../../../providers"), join16(import.meta.dir, "../../../providers")];
15397
- return cands.find((d) => !!d && existsSync8(join16(d, "model_catalog.json"))) ?? null;
16115
+ const cands = [process.env.LOKI_PROVIDERS_DIR, join19(import.meta.dir, "../../../../../providers"), join19(import.meta.dir, "../../../providers")];
16116
+ return cands.find((d) => !!d && existsSync11(join19(d, "model_catalog.json"))) ?? null;
15398
16117
  }
15399
16118
  function loadCatalog(dir) {
15400
16119
  if (!dir)
15401
16120
  return { providers: {}, updated: null };
15402
16121
  try {
15403
- const j = JSON.parse(readFileSync13(join16(dir, "model_catalog.json"), "utf8"));
16122
+ const j = JSON.parse(readFileSync16(join19(dir, "model_catalog.json"), "utf8"));
15404
16123
  return { providers: j.providers ?? {}, updated: typeof j.updated === "string" ? j.updated : null };
15405
16124
  } catch {
15406
16125
  return { providers: {}, updated: null };
@@ -15431,7 +16150,7 @@ async function listProviders(timeoutMs = DEFAULT_TIMEOUT_MS) {
15431
16150
  }));
15432
16151
  return { providers: rows, catalog_updated: cat.updated, probe_timeout_ms: timeoutMs };
15433
16152
  }
15434
- function mount13(ctx) {
16153
+ function mount16(ctx) {
15435
16154
  const cached = ttlCache();
15436
16155
  ctx.app.get("/v1/providers", async (c) => {
15437
16156
  if (!probeAllowed(ctx, c))
@@ -15504,7 +16223,7 @@ function computeStats(db, since) {
15504
16223
  receipts: { total, signed }
15505
16224
  };
15506
16225
  }
15507
- function mount14(ctx) {
16226
+ function mount17(ctx) {
15508
16227
  ctx.app.get("/v1/stats", (c) => {
15509
16228
  const since = c.req.query("since") ?? null;
15510
16229
  if (since !== null && (!/^\d{4}-\d{2}-\d{2}/.test(since) || Number.isNaN(Date.parse(since))))
@@ -15518,7 +16237,7 @@ var num = (v, d) => {
15518
16237
  const n2 = Number(v);
15519
16238
  return Number.isFinite(n2) && n2 > 0 ? n2 : d;
15520
16239
  };
15521
- function mount15(ctx) {
16240
+ function mount18(ctx) {
15522
16241
  const pollMs = num(process.env.LOKI_CONTROL_STREAM_POLL_MS, 1000);
15523
16242
  const heartbeatMs = num(process.env.LOKI_CONTROL_STREAM_HEARTBEAT_MS, 15000);
15524
16243
  const maxStreams = num(process.env.LOKI_CONTROL_STREAM_MAX, 32);
@@ -15636,27 +16355,27 @@ data: ${JSON.stringify({ source: k.slice(0, i), run: k.slice(i + 1) })}
15636
16355
  }
15637
16356
 
15638
16357
  // src/server/routes/verify.ts
15639
- import { readFileSync as readFileSync22, realpathSync as realpathSync9 } from "fs";
16358
+ import { readFileSync as readFileSync25, realpathSync as realpathSync11 } from "fs";
15640
16359
  import { createPublicKey as createPublicKey5 } from "crypto";
15641
- import { join as join25, sep as sep7 } from "path";
16360
+ import { join as join28, sep as sep9 } from "path";
15642
16361
 
15643
16362
  // ../../loki-ts/src/engine10/verify_cmd.ts
15644
- import { existsSync as existsSync14, readdirSync as readdirSync9, readFileSync as readFileSync21, statSync as statSync10 } from "fs";
15645
- import { dirname as dirname11, join as join24 } from "path";
16363
+ import { existsSync as existsSync17, readdirSync as readdirSync12, readFileSync as readFileSync24, statSync as statSync12 } from "fs";
16364
+ import { dirname as dirname12, join as join27 } from "path";
15646
16365
  import { createHash as createHash10, createPublicKey as createPublicKey4, verify as verify3 } from "crypto";
15647
16366
 
15648
16367
  // ../../loki-ts/src/util/paths.ts
15649
- import { resolve as resolve5, dirname as dirname3 } from "path";
16368
+ import { resolve as resolve5, dirname as dirname4 } from "path";
15650
16369
  import { fileURLToPath } from "url";
15651
- import { existsSync as existsSync9 } from "fs";
15652
- var HERE = dirname3(fileURLToPath(import.meta.url));
16370
+ import { existsSync as existsSync12 } from "fs";
16371
+ var HERE = dirname4(fileURLToPath(import.meta.url));
15653
16372
  function findRepoRoot() {
15654
16373
  let dir = HERE;
15655
16374
  for (let i = 0;i < 6; i++) {
15656
- if (existsSync9(resolve5(dir, "VERSION")) && existsSync9(resolve5(dir, "autonomy/run.sh"))) {
16375
+ if (existsSync12(resolve5(dir, "VERSION")) && existsSync12(resolve5(dir, "autonomy/run.sh"))) {
15657
16376
  return dir;
15658
16377
  }
15659
- const parent = dirname3(dir);
16378
+ const parent = dirname4(dir);
15660
16379
  if (parent === dir)
15661
16380
  break;
15662
16381
  dir = parent;
@@ -15667,15 +16386,15 @@ var REPO_ROOT = findRepoRoot();
15667
16386
 
15668
16387
  // ../../loki-ts/src/engine10/stages/seal.ts
15669
16388
  import { createHash as createHash9, randomBytes as randomBytes3, createPrivateKey, createPublicKey as createPublicKey2, generateKeyPairSync, sign } from "crypto";
15670
- import { chmodSync as chmodSync4, existsSync as existsSync13, readdirSync as readdirSync8, linkSync, mkdirSync as mkdirSync6, readFileSync as readFileSync19, statSync as statSync9, unlinkSync as unlinkSync3, writeFileSync as writeFileSync7 } from "fs";
16389
+ import { chmodSync as chmodSync4, existsSync as existsSync16, readdirSync as readdirSync11, linkSync, mkdirSync as mkdirSync8, readFileSync as readFileSync22, statSync as statSync11, unlinkSync as unlinkSync4, writeFileSync as writeFileSync9 } from "fs";
15671
16390
  import { homedir as homedir5 } from "os";
15672
- import { basename as basename9, dirname as dirname10, join as join23 } from "path";
16391
+ import { basename as basename9, dirname as dirname11, join as join26 } from "path";
15673
16392
 
15674
16393
  // ../../loki-ts/src/e10ext/assert_delta.ts
15675
- import { execFileSync as execFileSync2 } from "child_process";
15676
- import { mkdtempSync, readFileSync as readFileSync14, rmSync as rmSync2 } from "fs";
16394
+ import { execFileSync as execFileSync3 } from "child_process";
16395
+ import { mkdtempSync, readFileSync as readFileSync17, rmSync as rmSync3 } from "fs";
15677
16396
  import { tmpdir } from "os";
15678
- import { join as join17 } from "path";
16397
+ import { join as join20 } from "path";
15679
16398
  import { basename as basename6 } from "path";
15680
16399
  var WEAKENED = { verdict: "weakened", changes: [] };
15681
16400
  var PY = String.raw`
@@ -15834,8 +16553,8 @@ function classifyAssertDelta(inp) {
15834
16553
  return WEAKENED;
15835
16554
  let cwd = "";
15836
16555
  try {
15837
- cwd = mkdtempSync(join17(tmpdir(), "loki-ad-"));
15838
- const raw2 = execFileSync2("python3", ["-I", "-c", PY], {
16556
+ cwd = mkdtempSync(join20(tmpdir(), "loki-ad-"));
16557
+ const raw2 = execFileSync3("python3", ["-I", "-c", PY], {
15839
16558
  input: JSON.stringify({ path: inp.path, base: inp.base, head: inp.head, task: inp.task }),
15840
16559
  cwd,
15841
16560
  env: process.env,
@@ -15857,15 +16576,15 @@ function classifyAssertDelta(inp) {
15857
16576
  return WEAKENED;
15858
16577
  } finally {
15859
16578
  if (cwd)
15860
- rmSync2(cwd, { recursive: true, force: true });
16579
+ rmSync3(cwd, { recursive: true, force: true });
15861
16580
  }
15862
16581
  }
15863
16582
  function assertDeltaNotes(repoDir, baseSha, headRef, path, task, baseCounts, headCounts) {
15864
16583
  if (!baseCounts || !headCounts)
15865
16584
  return null;
15866
16585
  try {
15867
- const show = (ref) => execFileSync2("git", ["show", `${ref}:${path}`], { cwd: repoDir, encoding: "utf8", env: process.env, stdio: ["ignore", "pipe", "ignore"] });
15868
- const head = headRef ? show(headRef) : readFileSync14(join17(repoDir, path), "utf8");
16586
+ const show = (ref) => execFileSync3("git", ["show", `${ref}:${path}`], { cwd: repoDir, encoding: "utf8", env: process.env, stdio: ["ignore", "pipe", "ignore"] });
16587
+ const head = headRef ? show(headRef) : readFileSync17(join20(repoDir, path), "utf8");
15869
16588
  const r = classifyTestEdit(path, show(baseSha), head, task, baseCounts, headCounts);
15870
16589
  return r[0]?.kind === "literal-only" ? r.map((e) => `assertion value changed (not shown to be required by the task): ${e.file}:${e.line} ${e.old} -> ${e.new}`) : null;
15871
16590
  } catch {
@@ -15881,21 +16600,21 @@ function classifyTestEdit(file, oldText, newText, taskText, baseCounts, headCoun
15881
16600
  }
15882
16601
 
15883
16602
  // ../../loki-ts/src/e10ext/discard.ts
15884
- import { execFileSync as execFileSync3 } from "child_process";
15885
- import { chmodSync as chmodSync3, closeSync as closeSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync3, openSync as openSync4, readFileSync as readFileSync15, realpathSync as realpathSync7, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
15886
- import { dirname as dirname4, join as join18, relative, sep as sep4 } from "path";
16603
+ import { execFileSync as execFileSync4 } from "child_process";
16604
+ import { chmodSync as chmodSync3, closeSync as closeSync4, lstatSync as lstatSync3, mkdirSync as mkdirSync5, openSync as openSync4, readFileSync as readFileSync18, realpathSync as realpathSync9, rmdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "fs";
16605
+ import { dirname as dirname5, join as join21, relative, sep as sep6 } from "path";
15887
16606
  function safeRestore(repoDir, f, want, mode) {
15888
- const root = realpathSync7(repoDir), fp = join18(root, f), rel = relative(root, dirname4(fp));
16607
+ const root = realpathSync9(repoDir), fp = join21(root, f), rel = relative(root, dirname5(fp));
15889
16608
  if (rel.startsWith(".."))
15890
16609
  throw new Error("outside repo");
15891
16610
  let cur = root;
15892
- for (const part of rel === "" ? [] : rel.split(sep4)) {
15893
- cur = join18(cur, part);
16611
+ for (const part of rel === "" ? [] : rel.split(sep6)) {
16612
+ cur = join21(cur, part);
15894
16613
  let st2;
15895
16614
  try {
15896
- st2 = lstatSync2(cur);
16615
+ st2 = lstatSync3(cur);
15897
16616
  } catch {
15898
- mkdirSync3(cur);
16617
+ mkdirSync5(cur);
15899
16618
  continue;
15900
16619
  }
15901
16620
  if (!st2.isDirectory())
@@ -15903,11 +16622,11 @@ function safeRestore(repoDir, f, want, mode) {
15903
16622
  }
15904
16623
  let st;
15905
16624
  try {
15906
- st = lstatSync2(fp);
16625
+ st = lstatSync3(fp);
15907
16626
  } catch {
15908
16627
  st = null;
15909
16628
  }
15910
- if (st?.isFile() && readFileSync15(fp).equals(want)) {
16629
+ if (st?.isFile() && readFileSync18(fp).equals(want)) {
15911
16630
  chmodSync3(fp, mode);
15912
16631
  return;
15913
16632
  }
@@ -15915,11 +16634,11 @@ function safeRestore(repoDir, f, want, mode) {
15915
16634
  if (st.isDirectory())
15916
16635
  rmdirSync(fp);
15917
16636
  else
15918
- unlinkSync2(fp);
16637
+ unlinkSync3(fp);
15919
16638
  }
15920
16639
  const fd = openSync4(fp, "wx", mode);
15921
16640
  try {
15922
- writeFileSync4(fd, want);
16641
+ writeFileSync6(fd, want);
15923
16642
  } finally {
15924
16643
  closeSync4(fd);
15925
16644
  }
@@ -15939,15 +16658,15 @@ async function discardIfSatisfied(git, base, o, staged, keep, repoDir) {
15939
16658
  return { status: "failed", data: {}, reason: "already-satisfied discard failed" };
15940
16659
  const notProven = [];
15941
16660
  for (const [f, v] of Object.entries(o.intake?.preexisting_untracked_blobs ?? {})) {
15942
- const fp = join18(repoDir, f);
16661
+ const fp = join21(repoDir, f);
15943
16662
  try {
15944
16663
  if (v.startsWith("!")) {
15945
- const st = lstatSync2(fp);
16664
+ const st = lstatSync3(fp);
15946
16665
  if (`!${st.size}:${st.mtimeMs}:${st.ctimeMs}` !== v)
15947
16666
  throw new Error("changed, not snapshotted");
15948
16667
  continue;
15949
16668
  }
15950
- const [sha2, mode] = v.split(" "), want = execFileSync3("git", ["cat-file", "blob", sha2], { cwd: repoDir, env: process.env, maxBuffer: 1 << 28 });
16669
+ const [sha2, mode] = v.split(" "), want = execFileSync4("git", ["cat-file", "blob", sha2], { cwd: repoDir, env: process.env, maxBuffer: 1 << 28 });
15951
16670
  safeRestore(repoDir, f, want, parseInt(mode ?? "644", 8));
15952
16671
  } catch {
15953
16672
  notProven.push(f);
@@ -15958,11 +16677,11 @@ async function discardIfSatisfied(git, base, o, staged, keep, repoDir) {
15958
16677
  try {
15959
16678
  let m = 420;
15960
16679
  try {
15961
- const l = lstatSync2(join18(repoDir, f));
16680
+ const l = lstatSync3(join21(repoDir, f));
15962
16681
  if (l.isFile())
15963
16682
  m = l.mode & 511;
15964
16683
  } catch {}
15965
- safeRestore(repoDir, f, execFileSync3("git", ["cat-file", "blob", pre[f]], { cwd: repoDir, env: process.env, maxBuffer: 1 << 28 }), m);
16684
+ safeRestore(repoDir, f, execFileSync4("git", ["cat-file", "blob", pre[f]], { cwd: repoDir, env: process.env, maxBuffer: 1 << 28 }), m);
15966
16685
  } catch {
15967
16686
  notProven.push(f);
15968
16687
  }
@@ -15971,11 +16690,11 @@ async function discardIfSatisfied(git, base, o, staged, keep, repoDir) {
15971
16690
  }
15972
16691
 
15973
16692
  // ../../loki-ts/src/e10ext/commit_filter.ts
15974
- import { dirname as dirname5 } from "path";
16693
+ import { dirname as dirname6 } from "path";
15975
16694
 
15976
16695
  // ../../loki-ts/src/e10ext/preexisting_dirty.ts
15977
- import { execFileSync as execFileSync4 } from "child_process";
15978
- var blob2 = (repoDir, path, env = process.env) => execFileSync4("git", ["hash-object", "-w", "--no-filters", "--", path], { cwd: repoDir, encoding: "utf8", env, stdio: ["ignore", "pipe", "ignore"] }).trim();
16696
+ import { execFileSync as execFileSync5 } from "child_process";
16697
+ var blob2 = (repoDir, path, env = process.env) => execFileSync5("git", ["hash-object", "-w", "--no-filters", "--", path], { cwd: repoDir, encoding: "utf8", env, stdio: ["ignore", "pipe", "ignore"] }).trim();
15979
16698
  function untouchedSinceIntake(repoDir, recorded, env = process.env) {
15980
16699
  if (!recorded || typeof recorded !== "object")
15981
16700
  return [];
@@ -15991,14 +16710,14 @@ function untouchedSinceIntake(repoDir, recorded, env = process.env) {
15991
16710
  // ../../loki-ts/src/e10ext/commit_filter.ts
15992
16711
  var parseStaged = (out2) => out2.split("\x00").reduce((a, t, i, all) => i % 2 === 0 && t ? [...a, { st: t, f: all[i + 1] }] : a, []);
15993
16712
  function dropSet(repoDir, staged, preexistingDirty, env = process.env) {
15994
- return staged.concat(untouchedSinceIntake(repoDir, preexistingDirty, env).map((f) => ({ st: "L", f }))).filter(({ st, f }) => f.startsWith(".loki/") || st === "L" || /(^|\/)loki_wall_[^/]*$/.test(f) || st === "A" && !staged.some(({ f: m }) => /(^|\/)(package\.json|pyproject\.toml|requirements\.txt|Cargo\.toml|go\.mod)$/.test(m) && dirname5(m) === dirname5(f)) && /(^|\/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb?|poetry\.lock|Cargo\.lock|go\.sum)$/.test(f));
16713
+ return staged.concat(untouchedSinceIntake(repoDir, preexistingDirty, env).map((f) => ({ st: "L", f }))).filter(({ st, f }) => f.startsWith(".loki/") || st === "L" || /(^|\/)loki_wall_[^/]*$/.test(f) || st === "A" && !staged.some(({ f: m }) => /(^|\/)(package\.json|pyproject\.toml|requirements\.txt|Cargo\.toml|go\.mod)$/.test(m) && dirname6(m) === dirname6(f)) && /(^|\/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb?|poetry\.lock|Cargo\.lock|go\.sum)$/.test(f));
15995
16714
  }
15996
16715
 
15997
16716
  // ../../loki-ts/src/e10ext/scope.ts
15998
- import { basename as basename8, dirname as dirname6 } from "path";
16717
+ import { basename as basename8, dirname as dirname7 } from "path";
15999
16718
 
16000
16719
  // ../../loki-ts/src/engine10/testmap.ts
16001
- import { basename as basename7, extname, join as join19, relative as relative2, sep as sep5 } from "path";
16720
+ import { basename as basename7, extname, join as join22, relative as relative2, sep as sep7 } from "path";
16002
16721
  var SKIP_DIRS = new Set([
16003
16722
  ".git",
16004
16723
  "node_modules",
@@ -16023,14 +16742,14 @@ function isTestFile(rel) {
16023
16742
  }
16024
16743
 
16025
16744
  // ../../loki-ts/src/features/speed/unit_mode.ts
16026
- import { closeSync as closeSync5, constants, fstatSync as fstatSync3, lstatSync as lstatSync3, openSync as openSync5, readSync as readSync3, realpathSync as realpathSync8 } from "fs";
16745
+ import { closeSync as closeSync5, constants, fstatSync as fstatSync3, lstatSync as lstatSync4, openSync as openSync5, readSync as readSync3, realpathSync as realpathSync10 } from "fs";
16027
16746
  var MAX_SPEC_BYTES = 1024 * 1024;
16028
16747
  var MAX_TOKEN_BUDGET = 1e9;
16029
16748
  var norm2 = (p) => p.replace(/^\.\//, "");
16030
16749
  var CTRL = /[\x00-\x1f\x7f]/;
16031
16750
  var strs = (v) => Array.isArray(v) && v.every((x) => typeof x === "string" && !CTRL.test(x)) ? v.map(norm2) : null;
16032
16751
  function readBounded(path) {
16033
- const l = lstatSync3(path);
16752
+ const l = lstatSync4(path);
16034
16753
  if (l.isSymbolicLink() || !l.isFile() || l.size > MAX_SPEC_BYTES)
16035
16754
  return null;
16036
16755
  const fd = openSync5(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
@@ -16110,8 +16829,8 @@ function outsideScope(o, staged) {
16110
16829
  const text2 = `${plan}
16111
16830
  ${typeof o.intake?.task === "string" ? o.intake.task : ""}
16112
16831
  ${contractText(o)}`, pre = o.intake?.preexisting_dirty ?? {};
16113
- const toks = surfaceTokens(text2), plannedDirs = new Set(rel.map((r) => dirname6(r)).filter((d) => d !== "."));
16114
- return staged.filter(({ st, f }) => st !== "A" && !isTestFile(f) && !Object.hasOwn(pre, f) && !rel.includes(f) && !mentions(text2, f) && !mentions(text2, basename8(f)) && !plannedDirs.has(dirname6(f)) && !dirSegs(f).some((d) => toks.has(d))).map(({ f }) => f);
16832
+ const toks = surfaceTokens(text2), plannedDirs = new Set(rel.map((r) => dirname7(r)).filter((d) => d !== "."));
16833
+ return staged.filter(({ st, f }) => st !== "A" && !isTestFile(f) && !Object.hasOwn(pre, f) && !rel.includes(f) && !mentions(text2, f) && !mentions(text2, basename8(f)) && !plannedDirs.has(dirname7(f)) && !dirSegs(f).some((d) => toks.has(d))).map(({ f }) => f);
16115
16834
  }
16116
16835
  function flagOutsideScope(o, staged) {
16117
16836
  const unit = unitFlags(staged, process.env, o.intake?.preexisting_dirty ?? {});
@@ -16193,9 +16912,9 @@ async function run(argv, opts = {}) {
16193
16912
  // ../../loki-ts/src/features/visual_evidence.ts
16194
16913
  import { spawn as spawn5 } from "child_process";
16195
16914
  import { createHash as createHash6, randomBytes as randomBytes2 } from "crypto";
16196
- import { existsSync as existsSync10, lstatSync as lstatSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync5 } from "fs";
16915
+ import { existsSync as existsSync13, lstatSync as lstatSync5, mkdirSync as mkdirSync6, readdirSync as readdirSync9, readFileSync as readFileSync19, writeFileSync as writeFileSync7 } from "fs";
16197
16916
  import { createServer } from "net";
16198
- import { dirname as dirname7, isAbsolute, join as join20, normalize, relative as relative3, sep as sep6 } from "path";
16917
+ import { dirname as dirname8, isAbsolute, join as join23, normalize, relative as relative3, sep as sep8 } from "path";
16199
16918
  var visualEvidenceEnabled = (env = process.env) => env["LOKI_VISUAL_EVIDENCE"] !== "0";
16200
16919
  var PAGE_RE = /^(?:.*\/)?(?:app|pages|src|public)\/.*\.(?:html|jsx|tsx|vue|svelte)$/;
16201
16920
  var isPageFile = (p) => PAGE_RE.test(p);
@@ -16205,12 +16924,12 @@ function routeFor(file) {
16205
16924
  return `/${r}`.replace(/\/+/g, "/");
16206
16925
  }
16207
16926
  var screenName = (route) => (route === "/" ? "index" : route.slice(1).replace(/[^A-Za-z0-9._-]+/g, "_")) || "index";
16208
- var sha256File = (p) => createHash6("sha256").update(readFileSync16(p)).digest("hex");
16927
+ var sha256File = (p) => createHash6("sha256").update(readFileSync19(p)).digest("hex");
16209
16928
  function hashScreens(runDir, rels) {
16210
16929
  const out2 = [];
16211
16930
  for (const rel of rels) {
16212
16931
  try {
16213
- out2.push({ path: rel, sha256: sha256File(join20(runDir, rel)) });
16932
+ out2.push({ path: rel, sha256: sha256File(join23(runDir, rel)) });
16214
16933
  } catch {}
16215
16934
  }
16216
16935
  return out2;
@@ -16223,14 +16942,14 @@ function checkScreens(runDir, screens) {
16223
16942
  return "evidence_screens entry is malformed";
16224
16943
  if (isAbsolute(s.path) || normalize(s.path).startsWith(".."))
16225
16944
  return `evidence screenshot path escapes the evidence dir: ${s.path}`;
16226
- const file = join20(runDir, s.path);
16945
+ const file = join23(runDir, s.path);
16227
16946
  let cur = runDir;
16228
- for (const seg of normalize(s.path).split(sep6)) {
16229
- cur = join20(cur, seg);
16947
+ for (const seg of normalize(s.path).split(sep8)) {
16948
+ cur = join23(cur, seg);
16230
16949
  if (isSymlink(cur))
16231
16950
  return `evidence screenshot path is a symlink: ${s.path}`;
16232
16951
  }
16233
- if (!existsSync10(file))
16952
+ if (!existsSync13(file))
16234
16953
  return `evidence screenshot is missing: ${s.path}`;
16235
16954
  if (!isRegularFile(file))
16236
16955
  return `evidence screenshot is not a regular file: ${s.path}`;
@@ -16272,16 +16991,16 @@ async function waitUp(url, ms, signal) {
16272
16991
  return false;
16273
16992
  }
16274
16993
  var playwrightBin = (repoDir) => {
16275
- const bin = join20(repoDir, "node_modules", ".bin", "playwright");
16276
- return existsSync10(bin) ? bin : null;
16994
+ const bin = join23(repoDir, "node_modules", ".bin", "playwright");
16995
+ return existsSync13(bin) ? bin : null;
16277
16996
  };
16278
16997
  function openapiPaths(repoDir) {
16279
16998
  for (const f of ["openapi.json", "openapi.yaml", "openapi.yml", "swagger.json"]) {
16280
- const p = join20(repoDir, f);
16281
- if (!existsSync10(p))
16999
+ const p = join23(repoDir, f);
17000
+ if (!existsSync13(p))
16282
17001
  continue;
16283
17002
  try {
16284
- const txt = readFileSync16(p, "utf8");
17003
+ const txt = readFileSync19(p, "utf8");
16285
17004
  const paths = f.endsWith(".json") ? Object.keys(JSON.parse(txt).paths ?? {}) : [...txt.matchAll(/^ {2}(\/[^\s:]*):\s*$/gm)].map((m) => m[1]);
16286
17005
  return paths.filter((x) => !x.includes("{")).slice(0, 10);
16287
17006
  } catch {
@@ -16291,8 +17010,8 @@ function openapiPaths(repoDir) {
16291
17010
  return [];
16292
17011
  }
16293
17012
  var playwrightTestPkg = (repoDir) => {
16294
- const d = join20(repoDir, "node_modules", "@playwright", "test");
16295
- return existsSync10(join20(d, "package.json")) ? d : null;
17013
+ const d = join23(repoDir, "node_modules", "@playwright", "test");
17014
+ return existsSync13(join23(d, "package.json")) ? d : null;
16296
17015
  };
16297
17016
  var e2eSpecSource = (pkgDir, base, routes) => `const { test } = require(${JSON.stringify(pkgDir)});
16298
17017
  test("loki e2e walkthrough", async ({ page }) => {
@@ -16304,24 +17023,24 @@ async function recordE2eMedia(repoDir, runDir, relDir, base, routes, ms, signal)
16304
17023
  if (!pw || !pkg)
16305
17024
  return { rels: [], skipped: "playwright e2e video and trace skipped: @playwright/test is not installed in the repo" };
16306
17025
  try {
16307
- const dir = join20(runDir, relDir, "e2e");
16308
- mkdirSync4(dir, { recursive: true });
16309
- writeFileSync5(join20(dir, "walk.spec.cjs"), e2eSpecSource(pkg, base, routes));
16310
- writeFileSync5(join20(dir, "playwright.config.cjs"), `module.exports = { testDir: ".", testMatch: "walk.spec.cjs", outputDir: "./out", reporter: "null", use: { video: "on", trace: "on" } };
17026
+ const dir = join23(runDir, relDir, "e2e");
17027
+ mkdirSync6(dir, { recursive: true });
17028
+ writeFileSync7(join23(dir, "walk.spec.cjs"), e2eSpecSource(pkg, base, routes));
17029
+ writeFileSync7(join23(dir, "playwright.config.cjs"), `module.exports = { testDir: ".", testMatch: "walk.spec.cjs", outputDir: "./out", reporter: "null", use: { video: "on", trace: "on" } };
16311
17030
  `);
16312
- if (!await shoot(pw, ["test", "--config", join20(dir, "playwright.config.cjs")], repoDir, ms, signal))
17031
+ if (!await shoot(pw, ["test", "--config", join23(dir, "playwright.config.cjs")], repoDir, ms, signal))
16313
17032
  return { rels: [], skipped: "playwright e2e video and trace skipped: the run failed or timed out" };
16314
17033
  const rels = [];
16315
17034
  const walk = (d) => {
16316
- for (const e of readdirSync6(d, { withFileTypes: true })) {
16317
- const full = join20(d, e.name);
17035
+ for (const e of readdirSync9(d, { withFileTypes: true })) {
17036
+ const full = join23(d, e.name);
16318
17037
  if (e.isDirectory())
16319
17038
  walk(full);
16320
17039
  else if (e.isFile() && /\.(webm|zip)$/.test(e.name))
16321
- rels.push(join20(relDir, "e2e", relative3(dir, full)));
17040
+ rels.push(join23(relDir, "e2e", relative3(dir, full)));
16322
17041
  }
16323
17042
  };
16324
- walk(join20(dir, "out"));
17043
+ walk(join23(dir, "out"));
16325
17044
  return rels.length > 0 ? { rels, skipped: null } : { rels, skipped: "playwright e2e video and trace skipped: no video or trace was produced" };
16326
17045
  } catch (e) {
16327
17046
  return { rels: [], skipped: `playwright e2e video and trace skipped: ${String(e?.message ?? e).slice(0, 120)}` };
@@ -16359,14 +17078,14 @@ function shoot(pw, args, cwd, ms, signal) {
16359
17078
  }
16360
17079
  var isSymlink = (p) => {
16361
17080
  try {
16362
- return lstatSync4(p).isSymbolicLink();
17081
+ return lstatSync5(p).isSymbolicLink();
16363
17082
  } catch {
16364
17083
  return false;
16365
17084
  }
16366
17085
  };
16367
17086
  var isRegularFile = (p) => {
16368
17087
  try {
16369
- return lstatSync4(p).isFile();
17088
+ return lstatSync5(p).isFile();
16370
17089
  } catch {
16371
17090
  return false;
16372
17091
  }
@@ -16414,10 +17133,10 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
16414
17133
  try {
16415
17134
  if (!visualEvidenceEnabled(opts.env))
16416
17135
  return skip("LOKI_VISUAL_EVIDENCE is 0");
16417
- const pkgPath = join20(repoDir, "package.json");
16418
- if (!existsSync10(pkgPath))
17136
+ const pkgPath = join23(repoDir, "package.json");
17137
+ if (!existsSync13(pkgPath))
16419
17138
  return skip("no package.json");
16420
- const script = pickScript(JSON.parse(readFileSync16(pkgPath, "utf8")));
17139
+ const script = pickScript(JSON.parse(readFileSync19(pkgPath, "utf8")));
16421
17140
  if (!script)
16422
17141
  return skip("no dev, preview or start script");
16423
17142
  const pages = changed.filter(isPageFile);
@@ -16442,20 +17161,20 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
16442
17161
  }
16443
17162
  if (!await waitUp(base, left(), signal))
16444
17163
  return skip(aborted() ? "aborted" : `${script} server did not answer within ${Math.round(budget / 1000)}s`);
16445
- const evRoot = join20(runDir, "evidence");
17164
+ const evRoot = join23(runDir, "evidence");
16446
17165
  if (isSymlink(evRoot))
16447
17166
  return skip("evidence dir is a symlink");
16448
- mkdirSync4(evRoot, { recursive: true });
17167
+ mkdirSync6(evRoot, { recursive: true });
16449
17168
  if (pages.length > 0) {
16450
- const rel0 = join20("evidence", randomBytes2(6).toString("hex"));
16451
- mkdirSync4(join20(runDir, rel0));
17169
+ const rel0 = join23("evidence", randomBytes2(6).toString("hex"));
17170
+ mkdirSync6(join23(runDir, rel0));
16452
17171
  const rels = [];
16453
17172
  for (const route of [...new Set(pages.map(routeFor))].slice(0, opts.maxRoutes ?? MAX_ROUTES)) {
16454
17173
  if (aborted() || left() <= 0)
16455
17174
  break;
16456
- const rel = join20(rel0, `${screenName(route)}.png`);
16457
- const ok = await shoot(pw, ["screenshot", `${base}${route}`, join20(runDir, rel)], repoDir, left(), signal);
16458
- if (ok && !aborted() && !isSymlink(join20(runDir, rel)) && existsSync10(join20(runDir, rel)))
17175
+ const rel = join23(rel0, `${screenName(route)}.png`);
17176
+ const ok = await shoot(pw, ["screenshot", `${base}${route}`, join23(runDir, rel)], repoDir, left(), signal);
17177
+ if (ok && !aborted() && !isSymlink(join23(runDir, rel)) && existsSync13(join23(runDir, rel)))
16459
17178
  rels.push(rel);
16460
17179
  }
16461
17180
  if (aborted())
@@ -16480,9 +17199,9 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
16480
17199
  }
16481
17200
  if (aborted())
16482
17201
  return skip("aborted");
16483
- const hdir = join20(evRoot, randomBytes2(6).toString("hex"));
16484
- mkdirSync4(hdir);
16485
- writeFileSync5(join20(hdir, "http.json"), JSON.stringify(rows, null, 2) + `
17202
+ const hdir = join23(evRoot, randomBytes2(6).toString("hex"));
17203
+ mkdirSync6(hdir);
17204
+ writeFileSync7(join23(hdir, "http.json"), JSON.stringify(rows, null, 2) + `
16486
17205
  `);
16487
17206
  return { screens: [], http: true, skipped: null };
16488
17207
  } catch (e) {
@@ -16505,7 +17224,7 @@ async function sealEvidence(repoDir, runDir, o, notProven, signal, emit) {
16505
17224
  return ev.screens.length > 0 ? { evidence_screens: ev.screens } : {};
16506
17225
  }
16507
17226
  function receiptScreensProblem(receiptPath, receipt) {
16508
- return receipt["evidence_screens"] === undefined ? null : checkScreens(dirname7(receiptPath), receipt["evidence_screens"]);
17227
+ return receipt["evidence_screens"] === undefined ? null : checkScreens(dirname8(receiptPath), receipt["evidence_screens"]);
16509
17228
  }
16510
17229
 
16511
17230
  // ../../loki-ts/src/engine10/types.ts
@@ -16523,8 +17242,8 @@ var STAGE_BUDGETS = {
16523
17242
  };
16524
17243
 
16525
17244
  // ../../loki-ts/src/features/contract.ts
16526
- import { existsSync as existsSync11, lstatSync as lstatSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync6 } from "fs";
16527
- import { dirname as dirname8, join as join21 } from "path";
17245
+ import { existsSync as existsSync14, lstatSync as lstatSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync20, writeFileSync as writeFileSync8 } from "fs";
17246
+ import { dirname as dirname9, join as join24 } from "path";
16528
17247
  import { createHash as createHash7 } from "crypto";
16529
17248
  var MAX_CRITERIA = 50;
16530
17249
  var MAX_TEXT = 500;
@@ -16559,14 +17278,14 @@ function untracedLines(trace) {
16559
17278
  return trace.criteria.filter((c) => c.status === "no_match").map((c) => `contract ${sanitizeCriterion(c.id)} untraced (no keyword match in changed files): ${sanitizeCriterion(c.text)}`);
16560
17279
  }
16561
17280
  function contractPath(repoDir) {
16562
- return join21(repoDir, ".loki", "contract.json");
17281
+ return join24(repoDir, ".loki", "contract.json");
16563
17282
  }
16564
17283
  var MAX_CONTRACT_BYTES = 1024 * 1024;
16565
17284
  function readContract(repoDir) {
16566
17285
  const p = contractPath(repoDir);
16567
17286
  let st;
16568
17287
  try {
16569
- st = lstatSync5(p);
17288
+ st = lstatSync6(p);
16570
17289
  } catch {
16571
17290
  return { contract: null, notes: [], sha256: null };
16572
17291
  }
@@ -16576,7 +17295,7 @@ function readContract(repoDir) {
16576
17295
  return { contract: null, notes: ["contract unreadable: too large"], sha256: null };
16577
17296
  let raw2;
16578
17297
  try {
16579
- raw2 = readFileSync17(p);
17298
+ raw2 = readFileSync20(p);
16580
17299
  } catch {
16581
17300
  return { contract: null, notes: ["contract unreadable: read failed"], sha256: null };
16582
17301
  }
@@ -16611,7 +17330,7 @@ function sealContract(repoDir, body, rawDiff, checks, env = process.env, snap) {
16611
17330
  const live = readContract(repoDir);
16612
17331
  let exists2 = true;
16613
17332
  try {
16614
- lstatSync5(contractPath(repoDir));
17333
+ lstatSync6(contractPath(repoDir));
16615
17334
  } catch {
16616
17335
  exists2 = false;
16617
17336
  }
@@ -16640,32 +17359,32 @@ function sealContract(repoDir, body, rawDiff, checks, env = process.env, snap) {
16640
17359
 
16641
17360
  // ../../loki-ts/src/features/speed/seal_group.ts
16642
17361
  import { createHash as createHash8 } from "crypto";
16643
- import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync18, statSync as statSync8 } from "fs";
16644
- import { dirname as dirname9, join as join22 } from "path";
17362
+ import { existsSync as existsSync15, readdirSync as readdirSync10, readFileSync as readFileSync21, statSync as statSync10 } from "fs";
17363
+ import { dirname as dirname10, join as join25 } from "path";
16645
17364
  var ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
16646
17365
  var PASSING = new Set(["VERIFIED", "ALREADY_SATISFIED"]);
16647
17366
  var sha2 = (b) => createHash8("sha256").update(b).digest("hex");
16648
- var isObj6 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
17367
+ var isObj9 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
16649
17368
  var text2 = (v, cap) => typeof v === "string" ? v.replace(/[\x00-\x1f\x7f]+/g, " ").trim().slice(0, cap) : "";
16650
17369
  var num2 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
16651
- var unitsDir = (runDir) => join22(runDir, "group", "units");
16652
- var readJson2 = (p) => {
17370
+ var unitsDir = (runDir) => join25(runDir, "group", "units");
17371
+ var readJson3 = (p) => {
16653
17372
  try {
16654
- return JSON.parse(readFileSync18(p, "utf8"));
17373
+ return JSON.parse(readFileSync21(p, "utf8"));
16655
17374
  } catch {
16656
17375
  return;
16657
17376
  }
16658
17377
  };
16659
17378
  var dirNames = (d) => {
16660
17379
  try {
16661
- return readdirSync7(d).filter((n2) => !n2.startsWith(".")).sort();
17380
+ return readdirSync10(d).filter((n2) => !n2.startsWith(".")).sort();
16662
17381
  } catch {
16663
17382
  return null;
16664
17383
  }
16665
17384
  };
16666
17385
  function sealGroup(runDir, rh) {
16667
- const manifestPath = join22(runDir, "group", "manifest.json");
16668
- if (!existsSync12(manifestPath))
17386
+ const manifestPath = join25(runDir, "group", "manifest.json");
17387
+ if (!existsSync15(manifestPath))
16669
17388
  return { section: undefined, notProven: [], problems: 0, allUnitsPass: true };
16670
17389
  const notProven = [];
16671
17390
  let problems = 0;
@@ -16673,9 +17392,9 @@ function sealGroup(runDir, rh) {
16673
17392
  problems++;
16674
17393
  notProven.push(`group: ${m2}`);
16675
17394
  };
16676
- const m = readJson2(manifestPath);
16677
- const list = isObj6(m) && Array.isArray(m["units"]) ? m["units"] : [];
16678
- if (!isObj6(m) || !ID_RE.test(String(m["group_id"] ?? "")) || list.length === 0) {
17395
+ const m = readJson3(manifestPath);
17396
+ const list = isObj9(m) && Array.isArray(m["units"]) ? m["units"] : [];
17397
+ if (!isObj9(m) || !ID_RE.test(String(m["group_id"] ?? "")) || list.length === 0) {
16679
17398
  fail("manifest missing, unreadable or empty");
16680
17399
  return { section: undefined, notProven, problems, allUnitsPass: false };
16681
17400
  }
@@ -16683,21 +17402,21 @@ function sealGroup(runDir, rh) {
16683
17402
  const seen = new Set, seenLower = new Set;
16684
17403
  let allUnitsPass = true;
16685
17404
  list.forEach((raw2, index2) => {
16686
- const u = isObj6(raw2) ? raw2 : {};
17405
+ const u = isObj9(raw2) ? raw2 : {};
16687
17406
  const id = String(u["unit_id"] ?? "");
16688
17407
  if (!ID_RE.test(id) || seen.has(id) || seenLower.has(id.toLowerCase()))
16689
17408
  return fail(`unit ${index2} has an invalid, duplicate or case-colliding id`);
16690
17409
  seen.add(id);
16691
17410
  seenLower.add(id.toLowerCase());
16692
- const dir = join22(unitsDir(runDir), id), rp = join22(dir, "receipt.json"), ep = join22(dir, "events.jsonl");
17411
+ const dir = join25(unitsDir(runDir), id), rp = join25(dir, "receipt.json"), ep = join25(dir, "events.jsonl");
16693
17412
  let bytes;
16694
17413
  try {
16695
- bytes = readFileSync18(rp);
17414
+ bytes = readFileSync21(rp);
16696
17415
  } catch {
16697
17416
  return fail(`unit ${id} sub-receipt missing`);
16698
17417
  }
16699
- const sub = readJson2(rp);
16700
- if (!isObj6(sub) || typeof sub["receipt_sha256"] !== "string" || rh(sub) !== sub["receipt_sha256"])
17418
+ const sub = readJson3(rp);
17419
+ if (!isObj9(sub) || typeof sub["receipt_sha256"] !== "string" || rh(sub) !== sub["receipt_sha256"])
16701
17420
  return fail(`unit ${id} sub-receipt unreadable or its hash does not recompute`);
16702
17421
  const verdict = String(sub["verdict"] ?? "");
16703
17422
  if (!PASSING.has(verdict)) {
@@ -16709,7 +17428,7 @@ function sealGroup(runDir, rh) {
16709
17428
  unit_id: id,
16710
17429
  sub_receipt_sha256: sha2(bytes),
16711
17430
  receipt_sha256: sub["receipt_sha256"],
16712
- events_file_sha256: existsSync12(ep) ? sha2(readFileSync18(ep)) : null,
17431
+ events_file_sha256: existsSync15(ep) ? sha2(readFileSync21(ep)) : null,
16713
17432
  verdict,
16714
17433
  goal: text2(u["goal"], 200),
16715
17434
  files: (Array.isArray(u["files"]) ? u["files"] : []).map((f) => text2(f, 200)).filter(Boolean).slice(0, 50),
@@ -16729,16 +17448,16 @@ function capGroupVerdict(v, g) {
16729
17448
  return !g.allUnitsPass && (v === "VERIFIED" || v === "ALREADY_SATISFIED") ? "PARTIAL" : v;
16730
17449
  }
16731
17450
  async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
16732
- const runDir = dirname9(receiptPath);
16733
- const signed = isObj6(receipt["verification"]) && typeof receipt["verification"]["jwt"] === "string";
17451
+ const runDir = dirname10(receiptPath);
17452
+ const signed = isObj9(receipt["verification"]) && typeof receipt["verification"]["jwt"] === "string";
16734
17453
  const bad = (reason) => ({ verdict: "TAMPERED", reason: `group: ${reason}` });
16735
17454
  const g = receipt["group"];
16736
17455
  if (g === undefined)
16737
- return existsSync12(join22(runDir, "group", "manifest.json")) ? bad("group directory present but the receipt has no group section") : null;
16738
- if (!isObj6(g) || !ID_RE.test(String(g["group_id"] ?? "")) || !Array.isArray(g["units"]) || g["units"].length === 0)
17456
+ return existsSync15(join25(runDir, "group", "manifest.json")) ? bad("group directory present but the receipt has no group section") : null;
17457
+ if (!isObj9(g) || !ID_RE.test(String(g["group_id"] ?? "")) || !Array.isArray(g["units"]) || g["units"].length === 0)
16739
17458
  return bad("group section malformed");
16740
17459
  const entries = g["units"];
16741
- const ids = entries.map((e) => isObj6(e) ? String(e["unit_id"] ?? "") : "");
17460
+ const ids = entries.map((e) => isObj9(e) ? String(e["unit_id"] ?? "") : "");
16742
17461
  if (ids.some((id) => !ID_RE.test(id)) || new Set(ids.map((i) => i.toLowerCase())).size !== ids.length)
16743
17462
  return bad("unit ids invalid, duplicated or case-colliding");
16744
17463
  const onDisk = dirNames(unitsDir(runDir));
@@ -16746,24 +17465,24 @@ async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
16746
17465
  return bad("unit directories do not match the receipt (missing or extra unit)");
16747
17466
  let unchecked = null;
16748
17467
  for (let i = 0;i < entries.length; i++) {
16749
- const e = entries[i], id = ids[i], dir = join22(unitsDir(runDir), id), rp = join22(dir, "receipt.json"), ep = join22(dir, "events.jsonl");
17468
+ const e = entries[i], id = ids[i], dir = join25(unitsDir(runDir), id), rp = join25(dir, "receipt.json"), ep = join25(dir, "events.jsonl");
16750
17469
  if (e["index"] !== i)
16751
17470
  return bad(`unit ${id} is out of order (index ${String(e["index"])} at position ${i})`);
16752
17471
  let bytes;
16753
17472
  try {
16754
- if (!statSync8(dir).isDirectory())
17473
+ if (!statSync10(dir).isDirectory())
16755
17474
  return bad(`unit ${id} is not a directory`);
16756
- bytes = readFileSync18(rp);
17475
+ bytes = readFileSync21(rp);
16757
17476
  } catch {
16758
17477
  return bad(`unit ${id} sub-receipt missing`);
16759
17478
  }
16760
17479
  if (sha2(bytes) !== e["sub_receipt_sha256"])
16761
17480
  return bad(`unit ${id} sub-receipt does not match its recorded hash`);
16762
- const evSha = existsSync12(ep) ? sha2(readFileSync18(ep)) : null;
17481
+ const evSha = existsSync15(ep) ? sha2(readFileSync21(ep)) : null;
16763
17482
  if (evSha !== (e["events_file_sha256"] ?? null))
16764
17483
  return bad(`unit ${id} events.jsonl does not match its recorded hash`);
16765
- const sub = readJson2(rp);
16766
- if (!isObj6(sub) || sub["receipt_sha256"] !== e["receipt_sha256"] || rh(sub) !== e["receipt_sha256"])
17484
+ const sub = readJson3(rp);
17485
+ if (!isObj9(sub) || sub["receipt_sha256"] !== e["receipt_sha256"] || rh(sub) !== e["receipt_sha256"])
16767
17486
  return bad(`unit ${id} sub-receipt hash does not recompute`);
16768
17487
  if (String(sub["verdict"] ?? "") !== e["verdict"])
16769
17488
  return bad(`unit ${id} verdict differs from the combined receipt`);
@@ -16783,7 +17502,7 @@ var DEEP_NOT_PROVEN = ["full suite", "app boot", "council", "security scan"];
16783
17502
  var SIGNING_UNAVAILABLE = "receipt signing unavailable (no usable signing key: invalid key or unwritable ~/.loki/keys)";
16784
17503
  var EXCLUDE_LOKI = ":(exclude).loki";
16785
17504
  var sha2562 = (s) => createHash9("sha256").update(s).digest("hex");
16786
- var str2 = (v) => typeof v === "string" && v !== "" ? v : null;
17505
+ var str3 = (v) => typeof v === "string" && v !== "" ? v : null;
16787
17506
  var strs2 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x !== "") : [];
16788
17507
  function sanitizeReason(s) {
16789
17508
  const collapsed = s.replace(/[\x00-\x1f\x7f]+/g, " ").trim();
@@ -16814,30 +17533,30 @@ function loadSigningKey(generate = true) {
16814
17533
  let pem = inline ?? "";
16815
17534
  if (!inline) {
16816
17535
  const given = process.env["LOKI_RECEIPT_SIGNING_KEY_FILE"]?.trim();
16817
- const file = given || join23(process.env["HOME"] || homedir5(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
17536
+ const file = given || join26(process.env["HOME"] || homedir5(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
16818
17537
  try {
16819
- pem = readFileSync19(file);
17538
+ pem = readFileSync22(file);
16820
17539
  if (!given) {
16821
- for (const [f, m] of [[file, 384], [dirname10(file), 448]])
16822
- if (statSync9(f).mode & 63)
17540
+ for (const [f, m] of [[file, 384], [dirname11(file), 448]])
17541
+ if (statSync11(f).mode & 63)
16823
17542
  chmodSync4(f, m);
16824
17543
  }
16825
17544
  } catch (e) {
16826
17545
  if (e.code !== "ENOENT" || !generate)
16827
17546
  return null;
16828
- mkdirSync6(dirname10(dirname10(file)), { recursive: true, mode: 448 });
16829
- mkdirSync6(dirname10(file), { recursive: true, mode: 448 });
17547
+ mkdirSync8(dirname11(dirname11(file)), { recursive: true, mode: 448 });
17548
+ mkdirSync8(dirname11(file), { recursive: true, mode: 448 });
16830
17549
  const tmp = `${file}.${randomBytes3(6).toString("hex")}.tmp`;
16831
- writeFileSync7(tmp, generateKeyPairSync("ed25519").privateKey.export({ type: "pkcs8", format: "pem" }), { flag: "wx", mode: 384 });
17550
+ writeFileSync9(tmp, generateKeyPairSync("ed25519").privateKey.export({ type: "pkcs8", format: "pem" }), { flag: "wx", mode: 384 });
16832
17551
  try {
16833
17552
  linkSync(tmp, file);
16834
17553
  } catch (l) {
16835
17554
  if (l.code !== "EEXIST")
16836
17555
  throw l;
16837
17556
  } finally {
16838
- unlinkSync3(tmp);
17557
+ unlinkSync4(tmp);
16839
17558
  }
16840
- pem = readFileSync19(file);
17559
+ pem = readFileSync22(file);
16841
17560
  }
16842
17561
  }
16843
17562
  const k = createPrivateKey(pem);
@@ -16879,7 +17598,7 @@ var commitStage = {
16879
17598
  const dropped = new Set(drop.map(({ f }) => f)), notes = flagOutsideScope(ctx.outputs(), staged.filter(({ f }) => !dropped.has(f)));
16880
17599
  if ((await git(ctx, ["diff", "--cached", "--quiet"])).code === 0)
16881
17600
  return { status: "completed", data: { committed: false, scope_notes: notes } };
16882
- const title = (str2(ctx.outputs().intake?.title) ?? `run ${ctx.runId}`).split(`
17601
+ const title = (str3(ctx.outputs().intake?.title) ?? `run ${ctx.runId}`).split(`
16883
17602
  `)[0].slice(0, 72);
16884
17603
  const c = await git(ctx, ["commit", "-q", "-m", `loki: ${title}`, "-m", `Loki-Run: ${ctx.runId}`]);
16885
17604
  if (c.code !== 0)
@@ -16964,7 +17683,7 @@ var sealStage = {
16964
17683
  if (wallNotRun > 0) {
16965
17684
  try {
16966
17685
  const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) => basename9(String(f.path))));
16967
- for (const n2 of readdirSync8(join23(ctx.runDir, "wall")).sort())
17686
+ for (const n2 of readdirSync11(join26(ctx.runDir, "wall")).sort())
16968
17687
  if (!kept.has(n2))
16969
17688
  notProven.add(`wall test discarded: ${n2} (not_run)`);
16970
17689
  } catch {}
@@ -16983,7 +17702,7 @@ var sealStage = {
16983
17702
  if (!vl.length)
16984
17703
  continue;
16985
17704
  const tc = tcs?.[t];
16986
- const mine = assertDeltaNotes(ctx.repoDir, ctx.baseSha, head, t, str2(o.intake?.task) ?? "", cnt(tc?.b), cnt(tc?.h));
17705
+ const mine = assertDeltaNotes(ctx.repoDir, ctx.baseSha, head, t, str3(o.intake?.task) ?? "", cnt(tc?.b), cnt(tc?.h));
16987
17706
  if (!mine || mine.length !== vl.length || mine.some((n2) => !vl.includes(n2)))
16988
17707
  for (const v of vl)
16989
17708
  dropped.add(v);
@@ -17009,7 +17728,7 @@ var sealStage = {
17009
17728
  const source = o.intake?.source;
17010
17729
  if (source !== "text" && source !== "issue")
17011
17730
  notProven.add("task source not recorded by intake");
17012
- const repo = str2(o.intake?.repo);
17731
+ const repo = str3(o.intake?.repo);
17013
17732
  if (repo === null)
17014
17733
  notProven.add("repo not recorded by intake");
17015
17734
  if (typeof o.intake?.resumed !== "boolean")
@@ -17024,7 +17743,7 @@ var sealStage = {
17024
17743
  const cost = ctx.cost.read(ctx.repoDir, iterIds);
17025
17744
  if (cost.unmetered)
17026
17745
  notProven.add("cost unmetered (CLI invoker; recorded as 0)");
17027
- const eventsPath = join23(ctx.runDir, "events.jsonl");
17746
+ const eventsPath = join26(ctx.runDir, "events.jsonl");
17028
17747
  const wallFiles = Array.isArray(o.wall?.files) ? o.wall.files : [];
17029
17748
  const wallPassed = typeof o.verify?.wall_passed === "boolean" ? o.verify.wall_passed : null;
17030
17749
  if (wallFiles.length > 0 && wallPassed === null)
@@ -17032,7 +17751,7 @@ var sealStage = {
17032
17751
  const body = {
17033
17752
  schema: "loki.v10.receipt/1",
17034
17753
  run_id: ctx.runId,
17035
- task: { source: source === "issue" ? "issue" : "text", sha256: str2(o.intake?.task_sha256) ?? "" },
17754
+ task: { source: source === "issue" ? "issue" : "text", sha256: str3(o.intake?.task_sha256) ?? "" },
17036
17755
  repo: repo ?? "",
17037
17756
  base_sha: ctx.baseSha,
17038
17757
  head_sha: head,
@@ -17043,7 +17762,7 @@ var sealStage = {
17043
17762
  not_proven: [],
17044
17763
  verdict,
17045
17764
  ...grp.section ? { group: grp.section } : {},
17046
- ...str2(o.implement?.spec_conflict_reason) !== null ? { spec_conflict_reason: sanitizeReason(str2(o.implement?.spec_conflict_reason)) } : {},
17765
+ ...str3(o.implement?.spec_conflict_reason) !== null ? { spec_conflict_reason: sanitizeReason(str3(o.implement?.spec_conflict_reason)) } : {},
17047
17766
  evidence: strs2(o.intake?.evidence),
17048
17767
  ...o.intake?.preexisting_dirty ? { pre_existing_dirty: Object.keys(o.intake.preexisting_dirty) } : {},
17049
17768
  cost: {
@@ -17053,13 +17772,14 @@ var sealStage = {
17053
17772
  measured_sessions: cost.measuredCount ?? 0,
17054
17773
  total_sessions: cost.totalCount ?? 0,
17055
17774
  partial_usd: cost.partialUsd ?? 0,
17056
- ...cost.unmetered ? { source: "cli-invoker-unmetered" } : {}
17775
+ ...cost.unmetered ? { source: "cli-invoker-unmetered" } : {},
17776
+ ...Array.isArray(o.fix?.fix_rounds) ? { fix_rounds: o.fix.fix_rounds } : {}
17057
17777
  },
17058
17778
  time: { wall_s: Object.values(stages).reduce((a, b) => a + (b ?? 0), 0), stages },
17059
17779
  provider: ctx.provider,
17060
17780
  model: ctx.model,
17061
17781
  resumed: o.intake?.resumed === true,
17062
- events_sha256: sha2562(existsSync13(eventsPath) ? readFileSync19(eventsPath) : ""),
17782
+ events_sha256: sha2562(existsSync16(eventsPath) ? readFileSync22(eventsPath) : ""),
17063
17783
  ...await sealEvidence(ctx.repoDir, ctx.runDir, o, notProven, signal, ctx.emit),
17064
17784
  log_seal: true
17065
17785
  };
@@ -17073,11 +17793,11 @@ var sealStage = {
17073
17793
  hash = receiptSha256(body);
17074
17794
  }
17075
17795
  const receipt = { ...body, receipt_sha256: hash, verification: { jwt: sig.jwt, kid: sig.kid } };
17076
- mkdirSync6(ctx.runDir, { recursive: true });
17077
- const path = join23(ctx.runDir, "receipt.json");
17078
- writeFileSync7(path, JSON.stringify(receipt, null, 2) + `
17796
+ mkdirSync8(ctx.runDir, { recursive: true });
17797
+ const path = join26(ctx.runDir, "receipt.json");
17798
+ writeFileSync9(path, JSON.stringify(receipt, null, 2) + `
17079
17799
  `);
17080
- writeFileSync7(join23(ctx.runDir, "receipt.md"), renderReceiptMd(receipt));
17800
+ writeFileSync9(join26(ctx.runDir, "receipt.md"), renderReceiptMd(receipt));
17081
17801
  const signed = sig.jwt !== null;
17082
17802
  const data = { receipt_path: path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven };
17083
17803
  ctx.emit("receipt.sealed", "seal", { path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven });
@@ -17087,7 +17807,7 @@ var sealStage = {
17087
17807
 
17088
17808
  // ../../loki-ts/src/features/receipt_dsse.ts
17089
17809
  import { createPublicKey as createPublicKey3, sign as sign2, verify as verify2 } from "crypto";
17090
- import { readFileSync as readFileSync20 } from "fs";
17810
+ import { readFileSync as readFileSync23 } from "fs";
17091
17811
  var DSSE_PAYLOAD_TYPE = "application/vnd.in-toto+json";
17092
17812
  var STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
17093
17813
  var PREDICATE_TYPE = "https://autonomi.dev/loki/receipt/v10";
@@ -17168,7 +17888,7 @@ function verifyDsseReceipt(env, d) {
17168
17888
  }
17169
17889
  function runIdGuard(receiptPath, runId) {
17170
17890
  try {
17171
- return envelopeRunIdProblem(JSON.parse(readFileSync20(receiptPath, "utf8")), runId);
17891
+ return envelopeRunIdProblem(JSON.parse(readFileSync23(receiptPath, "utf8")), runId);
17172
17892
  } catch {
17173
17893
  return null;
17174
17894
  }
@@ -17182,7 +17902,7 @@ function pubFor(kid, given) {
17182
17902
  const active = loadSigningKey(false);
17183
17903
  const pubs = (process.env["LOKI_RECEIPT_RETIRED_PUBKEYS"] ?? "").split(":").map((f) => f.trim()).filter(Boolean).flatMap((f) => {
17184
17904
  try {
17185
- return [createPublicKey4(readFileSync21(f))];
17905
+ return [createPublicKey4(readFileSync24(f))];
17186
17906
  } catch {
17187
17907
  return [];
17188
17908
  }
@@ -17214,10 +17934,10 @@ function checkEventLog(receiptPath, receipt) {
17214
17934
  const bound = receipt["events_sha256"];
17215
17935
  if (typeof bound !== "string" || bound === createHash10("sha256").digest("hex"))
17216
17936
  return null;
17217
- const path = join24(dirname11(receiptPath), "events.jsonl");
17218
- if (!existsSync14(path))
17937
+ const path = join27(dirname12(receiptPath), "events.jsonl");
17938
+ if (!existsSync17(path))
17219
17939
  return "events.jsonl is missing";
17220
- const raw2 = readFileSync21(path, "utf8"), events2 = readEvents(path), lines = raw2.split(`
17940
+ const raw2 = readFileSync24(path, "utf8"), events2 = readEvents(path), lines = raw2.split(`
17221
17941
  `).filter((l) => l.trim() !== "");
17222
17942
  if (events2.length !== lines.length || events2.some((e, i) => e.seq !== i))
17223
17943
  return "events.jsonl has a forged, edited or missing line";
@@ -17233,7 +17953,7 @@ function checkEventLog(receiptPath, receipt) {
17233
17953
  return h.digest("hex") === bound ? null : "events.jsonl does not match the hash recorded at seal";
17234
17954
  }
17235
17955
  function checkLogSeal(receiptPath, hash, kid, given) {
17236
- const path = join24(dirname11(receiptPath), "events.jsonl"), events2 = readEvents(path), done = events2.findIndex((e) => e.type === "run.completed");
17956
+ const path = join27(dirname12(receiptPath), "events.jsonl"), events2 = readEvents(path), done = events2.findIndex((e) => e.type === "run.completed");
17237
17957
  if (!events2.some((e) => e.type === "receipt.sealed" && e.data["receipt_sha256"] === hash))
17238
17958
  return { verdict: "TAMPERED", reason: "events.jsonl has no receipt.sealed event for this receipt" };
17239
17959
  if (done < 0)
@@ -17241,7 +17961,7 @@ function checkLogSeal(receiptPath, hash, kid, given) {
17241
17961
  const seal = events2[done + 1], d = seal?.type === "log.sealed" ? seal.data : null;
17242
17962
  if (!d)
17243
17963
  return { verdict: "TAMPERED", reason: "events.jsonl has no signed log.sealed line after run.completed" };
17244
- const lines = readFileSync21(path, "utf8").split(`
17964
+ const lines = readFileSync24(path, "utf8").split(`
17245
17965
  `).filter((l) => l.trim() !== ""), sha3 = createHash10("sha256").update(lines.slice(0, done + 1).join(`
17246
17966
  `) + `
17247
17967
  `).digest("hex");
@@ -17251,11 +17971,11 @@ function checkLogSeal(receiptPath, hash, kid, given) {
17251
17971
  return null;
17252
17972
  }
17253
17973
  async function verifyReceipt(receiptPath, deps = {}) {
17254
- if (!existsSync14(receiptPath))
17974
+ if (!existsSync17(receiptPath))
17255
17975
  return { verdict: "UNCHECKED", reasons: [`receipt not found: ${receiptPath}`] };
17256
17976
  let receipt;
17257
17977
  try {
17258
- receipt = JSON.parse((deps.read ?? ((p) => readFileSync21(p, "utf8")))(receiptPath));
17978
+ receipt = JSON.parse((deps.read ?? ((p) => readFileSync24(p, "utf8")))(receiptPath));
17259
17979
  } catch {
17260
17980
  return { verdict: "UNCHECKED", reasons: ["receipt.json is not valid JSON"] };
17261
17981
  }
@@ -17299,7 +18019,7 @@ async function verifyReceipt(receiptPath, deps = {}) {
17299
18019
  // src/server/routes/verify.ts
17300
18020
  var ID4 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
17301
18021
  var safeId2 = (s) => ID4.test(s) && !s.includes("..");
17302
- function mount16(ctx) {
18022
+ function mount19(ctx) {
17303
18023
  const { act, db } = ctx;
17304
18024
  act.post("/v1/runs/:source/:run/verify", async (c) => {
17305
18025
  const source = c.req.param("source"), run2 = c.req.param("run");
@@ -17320,15 +18040,15 @@ function mount16(ctx) {
17320
18040
  return done(404, "receipt not available", "not_found");
17321
18041
  let runDir, file;
17322
18042
  try {
17323
- const root = realpathSync9(join25(repo.p, ".loki", "runs"));
17324
- runDir = realpathSync9(join25(root, run2));
17325
- if (runDir !== join25(root, run2))
18043
+ const root = realpathSync11(join28(repo.p, ".loki", "runs"));
18044
+ runDir = realpathSync11(join28(root, run2));
18045
+ if (runDir !== join28(root, run2))
17326
18046
  return done(404, "receipt not found", "not_found");
17327
- file = realpathSync9(join25(runDir, "receipt.json"));
18047
+ file = realpathSync11(join28(runDir, "receipt.json"));
17328
18048
  } catch {
17329
18049
  return done(404, "receipt not found", "not_found");
17330
18050
  }
17331
- if (!file.startsWith(runDir + sep7))
18051
+ if (!file.startsWith(runDir + sep9))
17332
18052
  return done(404, "receipt not found", "not_found");
17333
18053
  let integrity, reasons, sha3 = null, outcome = "UNREADABLE";
17334
18054
  try {
@@ -17343,7 +18063,7 @@ function mount16(ctx) {
17343
18063
  sha3 = r.receiptSha256 ?? null;
17344
18064
  }
17345
18065
  try {
17346
- outcome = outcomeOf(JSON.parse(readFileSync22(file, "utf8")));
18066
+ outcome = outcomeOf(JSON.parse(readFileSync25(file, "utf8")));
17347
18067
  } catch {
17348
18068
  outcome = "UNREADABLE";
17349
18069
  }
@@ -17378,7 +18098,7 @@ function mount16(ctx) {
17378
18098
  }
17379
18099
 
17380
18100
  // src/server/routes/index.ts
17381
- var routeModules = [mount, mount15, mount3, mount5, mount14, mount6, mount7, mount8, mount9, mount4, mount13, mount16, mount10, mount12, mount2, mount11];
18101
+ var routeModules = [mount, mount18, mount5, mount7, mount17, mount8, mount3, mount9, mount11, mount4, mount10, mount12, mount6, mount16, mount19, mount13, mount15, mount2, mount14];
17382
18102
  function registerRoutes(ctx) {
17383
18103
  mountRepos(ctx.act, ctx.db, ctx.peerIsLoopback);
17384
18104
  for (const m of routeModules)
@@ -17405,8 +18125,8 @@ function ttlCache(ttlMs = PROBE_CACHE_MS, now = Date.now) {
17405
18125
  }
17406
18126
 
17407
18127
  // src/server/legacy/shim.ts
17408
- import { existsSync as existsSync15, readFileSync as readFileSync23 } from "fs";
17409
- import { join as join26 } from "path";
18128
+ import { existsSync as existsSync18, readFileSync as readFileSync26 } from "fs";
18129
+ import { join as join29 } from "path";
17410
18130
 
17411
18131
  // src/server/legacy/routes.ts
17412
18132
  var LEGACY_ROUTES = [
@@ -17414,11 +18134,11 @@ var LEGACY_ROUTES = [
17414
18134
  { method: "GET", path: "/docs", action: "410", open: true, migrate: "" },
17415
18135
  { method: "GET", path: "/docs/oauth2-redirect", action: "410", open: true, migrate: "" },
17416
18136
  { method: "GET", path: "/redoc", action: "410", open: true, migrate: "" },
17417
- { method: "POST", path: "/api/v2/tenants", action: "501", open: false, migrate: "" },
17418
- { method: "GET", path: "/api/v2/tenants", action: "501", open: false, migrate: "" },
17419
- { method: "GET", path: "/api/v2/tenants/{tenant_id}", action: "501", open: false, migrate: "" },
18137
+ { method: "POST", path: "/api/v2/tenants", action: "410", open: false, migrate: "no tenants: local single user" },
18138
+ { method: "GET", path: "/api/v2/tenants", action: "410", open: false, migrate: "no tenants: local single user" },
18139
+ { method: "GET", path: "/api/v2/tenants/{tenant_id}", action: "410", open: false, migrate: "no tenants: local single user" },
17420
18140
  { method: "PUT", path: "/api/v2/tenants/{tenant_id}", action: "410", open: false, migrate: "" },
17421
- { method: "DELETE", path: "/api/v2/tenants/{tenant_id}", action: "501", open: false, migrate: "" },
18141
+ { method: "DELETE", path: "/api/v2/tenants/{tenant_id}", action: "410", open: false, migrate: "no tenants: local single user" },
17422
18142
  { method: "GET", path: "/api/v2/tenants/{tenant_id}/projects", action: "410", open: false, migrate: "" },
17423
18143
  { method: "POST", path: "/api/v2/runs", action: "501", open: false, migrate: "" },
17424
18144
  { method: "GET", path: "/api/v2/runs", action: "map", open: false, migrate: "/v1/runs" },
@@ -17426,12 +18146,12 @@ var LEGACY_ROUTES = [
17426
18146
  { method: "POST", path: "/api/v2/runs/{run_id}/cancel", action: "501", open: false, migrate: "" },
17427
18147
  { method: "POST", path: "/api/v2/runs/{run_id}/replay", action: "501", open: false, migrate: "" },
17428
18148
  { method: "GET", path: "/api/v2/runs/{run_id}/timeline", action: "map", open: false, migrate: "/v1/runs/:id" },
17429
- { method: "POST", path: "/api/v2/api-keys", action: "501", open: false, migrate: "" },
17430
- { method: "GET", path: "/api/v2/api-keys", action: "501", open: false, migrate: "" },
18149
+ { method: "POST", path: "/api/v2/api-keys", action: "410", open: false, migrate: "CP bearer token set by env (LOKI_CONTROL_TOKEN)" },
18150
+ { method: "GET", path: "/api/v2/api-keys", action: "410", open: false, migrate: "CP bearer token set by env (LOKI_CONTROL_TOKEN)" },
17431
18151
  { method: "GET", path: "/api/v2/api-keys/{identifier}", action: "410", open: false, migrate: "" },
17432
18152
  { method: "PUT", path: "/api/v2/api-keys/{identifier}", action: "410", open: false, migrate: "" },
17433
- { method: "DELETE", path: "/api/v2/api-keys/{identifier}", action: "501", open: false, migrate: "" },
17434
- { method: "POST", path: "/api/v2/api-keys/{identifier}/rotate", action: "501", open: false, migrate: "" },
18153
+ { method: "DELETE", path: "/api/v2/api-keys/{identifier}", action: "410", open: false, migrate: "CP bearer token set by env (LOKI_CONTROL_TOKEN)" },
18154
+ { method: "POST", path: "/api/v2/api-keys/{identifier}/rotate", action: "410", open: false, migrate: "CP bearer token set by env (LOKI_CONTROL_TOKEN)" },
17435
18155
  { method: "GET", path: "/api/v2/policies", action: "410", open: false, migrate: "" },
17436
18156
  { method: "PUT", path: "/api/v2/policies", action: "410", open: false, migrate: "" },
17437
18157
  { method: "POST", path: "/api/v2/policies/evaluate", action: "410", open: false, migrate: "" },
@@ -17467,31 +18187,31 @@ var LEGACY_ROUTES = [
17467
18187
  { method: "GET", path: "/api/projects/{project_id}", action: "501", open: false, migrate: "" },
17468
18188
  { method: "PUT", path: "/api/projects/{project_id}", action: "501", open: false, migrate: "" },
17469
18189
  { method: "DELETE", path: "/api/projects/{project_id}", action: "501", open: false, migrate: "" },
17470
- { method: "GET", path: "/api/tasks", action: "501", open: false, migrate: "" },
17471
- { method: "POST", path: "/api/tasks", action: "501", open: false, migrate: "" },
17472
- { method: "GET", path: "/api/tasks/{task_id}", action: "501", open: false, migrate: "" },
17473
- { method: "PUT", path: "/api/tasks/{task_id}", action: "501", open: false, migrate: "" },
17474
- { method: "DELETE", path: "/api/tasks/{task_id}", action: "501", open: false, migrate: "" },
17475
- { method: "POST", path: "/api/tasks/{task_id}/move", action: "501", open: false, migrate: "" },
17476
- { method: "WS", path: "/ws", action: "501", open: true, migrate: "" },
18190
+ { method: "GET", path: "/api/tasks", action: "map", open: false, migrate: "/v1/tasks" },
18191
+ { method: "POST", path: "/api/tasks", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
18192
+ { method: "GET", path: "/api/tasks/{task_id}", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
18193
+ { method: "PUT", path: "/api/tasks/{task_id}", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
18194
+ { method: "DELETE", path: "/api/tasks/{task_id}", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
18195
+ { method: "POST", path: "/api/tasks/{task_id}/move", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
18196
+ { method: "WS", path: "/ws", action: "410", open: true, migrate: "SSE at GET /v1/stream" },
17477
18197
  { method: "GET", path: "/api/registry/projects", action: "410", open: false, migrate: "" },
17478
- { method: "POST", path: "/api/registry/projects", action: "501", open: false, migrate: "" },
18198
+ { method: "POST", path: "/api/registry/projects", action: "410", open: false, migrate: "repos are discovered locally; GET /v1/repos" },
17479
18199
  { method: "GET", path: "/api/registry/projects/{identifier}", action: "410", open: false, migrate: "" },
17480
18200
  { method: "DELETE", path: "/api/registry/projects/{identifier}", action: "410", open: false, migrate: "" },
17481
18201
  { method: "GET", path: "/api/registry/projects/{identifier}/health", action: "410", open: false, migrate: "" },
17482
18202
  { method: "POST", path: "/api/registry/projects/{identifier}/access", action: "410", open: false, migrate: "" },
17483
18203
  { method: "GET", path: "/api/registry/discover", action: "410", open: false, migrate: "" },
17484
- { method: "POST", path: "/api/registry/sync", action: "501", open: false, migrate: "" },
18204
+ { method: "POST", path: "/api/registry/sync", action: "410", open: false, migrate: "repos are discovered locally; GET /v1/repos" },
17485
18205
  { method: "GET", path: "/api/registry/tasks", action: "410", open: false, migrate: "" },
17486
18206
  { method: "GET", path: "/api/registry/learnings", action: "410", open: false, migrate: "" },
17487
18207
  { method: "GET", path: "/api/fleet/runs", action: "map", open: false, migrate: "/v1/fleet/runs" },
17488
18208
  { method: "GET", path: "/api/fleet/summary", action: "map", open: false, migrate: "/v1/fleet/summary" },
17489
18209
  { method: "GET", path: "/api/fleet/runs/{identifier}", action: "410", open: false, migrate: "" },
17490
- { method: "POST", path: "/api/focus", action: "501", open: false, migrate: "" },
18210
+ { method: "POST", path: "/api/focus", action: "map", open: false, migrate: "/v1/focus" },
17491
18211
  { method: "GET", path: "/api/focus", action: "410", open: false, migrate: "" },
17492
18212
  { method: "DELETE", path: "/api/focus", action: "410", open: false, migrate: "" },
17493
18213
  { method: "GET", path: "/api/session/model", action: "410", open: false, migrate: "" },
17494
- { method: "POST", path: "/api/session/model", action: "501", open: false, migrate: "" },
18214
+ { method: "POST", path: "/api/session/model", action: "410", open: false, migrate: "no mid-run model switch; start a new run with another model (POST /v1/runs)" },
17495
18215
  { method: "GET", path: "/api/running-projects", action: "410", open: false, migrate: "" },
17496
18216
  { method: "POST", path: "/api/control/start", action: "501", open: false, migrate: "" },
17497
18217
  { method: "GET", path: "/api/control/builds/{execution_id}", action: "410", open: false, migrate: "" },
@@ -17505,19 +18225,19 @@ var LEGACY_ROUTES = [
17505
18225
  { method: "POST", path: "/api/enterprise/tokens", action: "410", open: false, migrate: "" },
17506
18226
  { method: "GET", path: "/api/enterprise/tokens", action: "410", open: false, migrate: "" },
17507
18227
  { method: "DELETE", path: "/api/enterprise/tokens/{identifier}", action: "410", open: false, migrate: "" },
17508
- { method: "GET", path: "/api/enterprise/audit", action: "410", open: false, migrate: "" },
17509
- { method: "GET", path: "/api/enterprise/audit/summary", action: "410", open: false, migrate: "" },
18228
+ { method: "GET", path: "/api/enterprise/audit", action: "map", open: false, migrate: "/v1/audit" },
18229
+ { method: "GET", path: "/api/enterprise/audit/summary", action: "map", open: false, migrate: "/v1/audit/summary" },
17510
18230
  { method: "GET", path: "/api/compliance", action: "410", open: false, migrate: "" },
17511
18231
  { method: "GET", path: "/api/memory/summary", action: "410", open: false, migrate: "" },
17512
- { method: "GET", path: "/api/memory/episodes", action: "501", open: false, migrate: "" },
17513
- { method: "GET", path: "/api/memory/episodes/{episode_id}", action: "501", open: false, migrate: "" },
17514
- { method: "GET", path: "/api/memory/patterns", action: "501", open: false, migrate: "" },
17515
- { method: "GET", path: "/api/memory/patterns/{pattern_id}", action: "501", open: false, migrate: "" },
17516
- { method: "GET", path: "/api/memory/skills", action: "501", open: false, migrate: "" },
18232
+ { method: "GET", path: "/api/memory/episodes", action: "map", open: false, migrate: "/v1/memory/episodes" },
18233
+ { method: "GET", path: "/api/memory/episodes/{episode_id}", action: "map", open: false, migrate: "/v1/memory/episodes/{id}" },
18234
+ { method: "GET", path: "/api/memory/patterns", action: "map", open: false, migrate: "/v1/memory/patterns" },
18235
+ { method: "GET", path: "/api/memory/patterns/{pattern_id}", action: "map", open: false, migrate: "/v1/memory/patterns/{id}" },
18236
+ { method: "GET", path: "/api/memory/skills", action: "map", open: false, migrate: "/v1/memory/skills" },
17517
18237
  { method: "GET", path: "/api/memory/skills/{skill_id}", action: "410", open: false, migrate: "" },
17518
- { method: "GET", path: "/api/memory/economics", action: "501", open: false, migrate: "" },
17519
- { method: "POST", path: "/api/memory/consolidate", action: "501", open: false, migrate: "" },
17520
- { method: "POST", path: "/api/memory/retrieve", action: "501", open: false, migrate: "" },
18238
+ { method: "GET", path: "/api/memory/economics", action: "map", open: false, migrate: "/v1/memory/economics" },
18239
+ { method: "POST", path: "/api/memory/consolidate", action: "410", open: false, migrate: "read-only GET /v1/memory/*" },
18240
+ { method: "POST", path: "/api/memory/retrieve", action: "410", open: false, migrate: "read-only GET /v1/memory/*" },
17521
18241
  { method: "GET", path: "/api/memory/index", action: "410", open: false, migrate: "" },
17522
18242
  { method: "GET", path: "/api/memory/timeline", action: "410", open: false, migrate: "" },
17523
18243
  { method: "GET", path: "/api/memory/files", action: "410", open: false, migrate: "" },
@@ -17528,41 +18248,41 @@ var LEGACY_ROUTES = [
17528
18248
  { method: "GET", path: "/api/learning/trends", action: "410", open: false, migrate: "" },
17529
18249
  { method: "GET", path: "/api/learning/signals", action: "410", open: false, migrate: "" },
17530
18250
  { method: "GET", path: "/api/learning/aggregation", action: "410", open: false, migrate: "" },
17531
- { method: "POST", path: "/api/learning/aggregate", action: "501", open: false, migrate: "" },
18251
+ { method: "POST", path: "/api/learning/aggregate", action: "410", open: false, migrate: "read-only GET /v1/memory/patterns" },
17532
18252
  { method: "GET", path: "/api/learning/preferences", action: "410", open: false, migrate: "" },
17533
18253
  { method: "GET", path: "/api/learning/errors", action: "410", open: false, migrate: "" },
17534
18254
  { method: "GET", path: "/api/learning/success", action: "410", open: false, migrate: "" },
17535
18255
  { method: "GET", path: "/api/learning/tools", action: "410", open: false, migrate: "" },
17536
- { method: "POST", path: "/api/control/pause", action: "501", open: false, migrate: "" },
17537
- { method: "POST", path: "/api/control/resume", action: "501", open: false, migrate: "" },
17538
- { method: "POST", path: "/api/control/stop", action: "501", open: false, migrate: "" },
18256
+ { method: "POST", path: "/api/control/pause", action: "map", open: false, migrate: "/v1/control/pause" },
18257
+ { method: "POST", path: "/api/control/resume", action: "map", open: false, migrate: "/v1/control/resume" },
18258
+ { method: "POST", path: "/api/control/stop", action: "map", open: false, migrate: "/v1/control/stop" },
17539
18259
  { method: "GET", path: "/api/cost", action: "map", open: false, migrate: "/v1/cost/snapshot" },
17540
18260
  { method: "GET", path: "/api/budget", action: "501", open: false, migrate: "" },
17541
18261
  { method: "GET", path: "/api/cost/timeline", action: "map", open: false, migrate: "/v1/cost/timeline" },
17542
18262
  { method: "GET", path: "/api/trust/trajectory", action: "410", open: false, migrate: "" },
17543
18263
  { method: "GET", path: "/api/gate-policy", action: "410", open: false, migrate: "" },
17544
18264
  { method: "GET", path: "/api/pricing", action: "410", open: false, migrate: "" },
17545
- { method: "GET", path: "/api/council/state", action: "410", open: false, migrate: "" },
17546
- { method: "GET", path: "/api/council/verdicts", action: "410", open: false, migrate: "" },
17547
- { method: "GET", path: "/api/council/convergence", action: "410", open: false, migrate: "" },
17548
- { method: "GET", path: "/api/council/report", action: "410", open: false, migrate: "" },
17549
- { method: "POST", path: "/api/council/force-review", action: "501", open: false, migrate: "" },
17550
- { method: "GET", path: "/api/council/transcripts", action: "501", open: false, migrate: "" },
18265
+ { method: "GET", path: "/api/council/state", action: "map", open: false, migrate: "/v1/council/state" },
18266
+ { method: "GET", path: "/api/council/verdicts", action: "map", open: false, migrate: "/v1/council/verdicts" },
18267
+ { method: "GET", path: "/api/council/convergence", action: "map", open: false, migrate: "/v1/council/convergence" },
18268
+ { method: "GET", path: "/api/council/report", action: "map", open: false, migrate: "/v1/council/report" },
18269
+ { method: "POST", path: "/api/council/force-review", action: "map", open: false, migrate: "/v1/control/council-review" },
18270
+ { method: "GET", path: "/api/council/transcripts", action: "map", open: false, migrate: "/v1/council/transcripts" },
17551
18271
  { method: "GET", path: "/api/council/transcripts/{iteration_id}", action: "410", open: false, migrate: "" },
17552
- { method: "GET", path: "/api/context", action: "501", open: false, migrate: "" },
18272
+ { method: "GET", path: "/api/context", action: "map", open: false, migrate: "/v1/context" },
17553
18273
  { method: "GET", path: "/api/notifications", action: "410", open: false, migrate: "" },
17554
18274
  { method: "GET", path: "/api/notifications/triggers", action: "410", open: false, migrate: "" },
17555
- { method: "PUT", path: "/api/notifications/triggers", action: "501", open: false, migrate: "" },
18275
+ { method: "PUT", path: "/api/notifications/triggers", action: "410", open: false, migrate: "GET /v1/notifications (derived from events)" },
17556
18276
  { method: "POST", path: "/api/notifications/{notification_id}/acknowledge", action: "410", open: false, migrate: "" },
17557
18277
  { method: "POST", path: "/api/notifications/{notification_id}/unacknowledge", action: "410", open: false, migrate: "" },
17558
- { method: "GET", path: "/api/checkpoints", action: "501", open: false, migrate: "" },
17559
- { method: "GET", path: "/api/checkpoints/{checkpoint_id}", action: "410", open: false, migrate: "" },
17560
- { method: "POST", path: "/api/checkpoints", action: "501", open: false, migrate: "" },
17561
- { method: "POST", path: "/api/checkpoints/{checkpoint_id}/rollback", action: "501", open: false, migrate: "" },
18278
+ { method: "GET", path: "/api/checkpoints", action: "map", open: false, migrate: "/v1/checkpoints" },
18279
+ { method: "GET", path: "/api/checkpoints/{checkpoint_id}", action: "map", open: false, migrate: "/v1/checkpoints/{id}" },
18280
+ { method: "POST", path: "/api/checkpoints", action: "map", open: false, migrate: "/v1/checkpoints" },
18281
+ { method: "POST", path: "/api/checkpoints/{checkpoint_id}/rollback", action: "map", open: false, migrate: "/v1/checkpoints/{id}/rollback" },
17562
18282
  { method: "GET", path: "/api/agents", action: "410", open: false, migrate: "" },
17563
- { method: "POST", path: "/api/agents/{agent_id}/kill", action: "501", open: false, migrate: "" },
17564
- { method: "POST", path: "/api/agents/{agent_id}/pause", action: "501", open: false, migrate: "" },
17565
- { method: "POST", path: "/api/agents/{agent_id}/resume", action: "501", open: false, migrate: "" },
18283
+ { method: "POST", path: "/api/agents/{agent_id}/kill", action: "410", open: false, migrate: "POST /v1/control/stop" },
18284
+ { method: "POST", path: "/api/agents/{agent_id}/pause", action: "410", open: false, migrate: "POST /v1/control/stop (v10 has no pause)" },
18285
+ { method: "POST", path: "/api/agents/{agent_id}/resume", action: "410", open: false, migrate: "POST /v1/control/resume" },
17566
18286
  { method: "GET", path: "/api/logs", action: "410", open: false, migrate: "" },
17567
18287
  { method: "POST", path: "/api/collab/join", action: "410", open: false, migrate: "" },
17568
18288
  { method: "POST", path: "/api/collab/leave", action: "410", open: false, migrate: "" },
@@ -17591,19 +18311,19 @@ var LEGACY_ROUTES = [
17591
18311
  { method: "GET", path: "/api/checklist/summary", action: "410", open: false, migrate: "" },
17592
18312
  { method: "GET", path: "/api/prd-observations", action: "410", open: false, migrate: "" },
17593
18313
  { method: "GET", path: "/api/checklist/waivers", action: "410", open: false, migrate: "" },
17594
- { method: "POST", path: "/api/checklist/waivers", action: "501", open: false, migrate: "" },
17595
- { method: "DELETE", path: "/api/checklist/waivers/{item_id}", action: "501", open: false, migrate: "" },
18314
+ { method: "POST", path: "/api/checklist/waivers", action: "410", open: false, migrate: "plan.json and receipt claims in GET /v1/runs/:id" },
18315
+ { method: "DELETE", path: "/api/checklist/waivers/{item_id}", action: "410", open: false, migrate: "plan.json and receipt claims in GET /v1/runs/:id" },
17596
18316
  { method: "GET", path: "/api/council/gate", action: "501", open: false, migrate: "" },
17597
18317
  { method: "GET", path: "/api/app-runner/status", action: "410", open: false, migrate: "" },
17598
18318
  { method: "GET", path: "/api/app-runner/logs", action: "410", open: false, migrate: "" },
17599
18319
  { method: "GET", path: "/api/app-runner/errors", action: "410", open: false, migrate: "" },
17600
- { method: "POST", path: "/api/control/app-restart", action: "501", open: false, migrate: "" },
17601
- { method: "POST", path: "/api/control/app-stop", action: "501", open: false, migrate: "" },
18320
+ { method: "POST", path: "/api/control/app-restart", action: "410", open: false, migrate: "evidence in GET /v1/runs/:source/:run/artifact/*" },
18321
+ { method: "POST", path: "/api/control/app-stop", action: "410", open: false, migrate: "evidence in GET /v1/runs/:source/:run/artifact/*" },
17602
18322
  { method: "GET", path: "/api/playwright/results", action: "410", open: false, migrate: "" },
17603
18323
  { method: "GET", path: "/api/playwright/screenshot", action: "410", open: false, migrate: "" },
17604
18324
  { method: "GET", path: "/api/failures", action: "410", open: false, migrate: "" },
17605
18325
  { method: "GET", path: "/api/prompt-versions", action: "410", open: false, migrate: "" },
17606
- { method: "POST", path: "/api/prompt-optimize", action: "501", open: false, migrate: "" },
18326
+ { method: "POST", path: "/api/prompt-optimize", action: "410", open: false, migrate: "verify stage: POST /v1/runs/:source/:run/verify" },
17607
18327
  { method: "GET", path: "/api/activity", action: "501", open: false, migrate: "" },
17608
18328
  { method: "GET", path: "/api/session-diff", action: "410", open: false, migrate: "" },
17609
18329
  { method: "POST", path: "/api/activity", action: "410", open: false, migrate: "" },
@@ -17612,8 +18332,8 @@ var LEGACY_ROUTES = [
17612
18332
  { method: "GET", path: "/", action: "308", open: false, migrate: "/" },
17613
18333
  { method: "GET", path: "/api/quality-score", action: "410", open: false, migrate: "" },
17614
18334
  { method: "GET", path: "/api/quality-score/history", action: "410", open: false, migrate: "" },
17615
- { method: "POST", path: "/api/quality-scan", action: "501", open: false, migrate: "" },
17616
- { method: "GET", path: "/api/quality-report", action: "501", open: false, migrate: "" },
18335
+ { method: "POST", path: "/api/quality-scan", action: "410", open: false, migrate: "verify stage: POST /v1/runs/:source/:run/verify" },
18336
+ { method: "GET", path: "/api/quality-report", action: "410", open: false, migrate: "verify stage: POST /v1/runs/:source/:run/verify" },
17617
18337
  { method: "GET", path: "/api/migration/list", action: "501", open: false, migrate: "" },
17618
18338
  { method: "POST", path: "/api/migration/start", action: "410", open: false, migrate: "" },
17619
18339
  { method: "GET", path: "/api/migration/{migration_id}/status", action: "501", open: false, migrate: "" },
@@ -17622,8 +18342,8 @@ var LEGACY_ROUTES = [
17622
18342
  { method: "GET", path: "/api/migration/{migration_id}/seams", action: "410", open: false, migrate: "" },
17623
18343
  { method: "POST", path: "/api/migration/{migration_id}/advance", action: "410", open: false, migrate: "" },
17624
18344
  { method: "POST", path: "/api/migration/{migration_id}/start-phase", action: "410", open: false, migrate: "" },
17625
- { method: "GET", path: "/api/managed/events", action: "501", open: false, migrate: "" },
17626
- { method: "GET", path: "/api/managed/status", action: "501", open: false, migrate: "" },
18345
+ { method: "GET", path: "/api/managed/events", action: "410", open: false, migrate: "managed memory dropped; GET /v1/runs/:source/:run/events" },
18346
+ { method: "GET", path: "/api/managed/status", action: "410", open: false, migrate: "managed memory dropped; GET /v1/runs" },
17627
18347
  { method: "GET", path: "/api/managed/memory_versions/{memory_id}", action: "410", open: false, migrate: "" },
17628
18348
  { method: "GET", path: "/api/findings/{iteration}", action: "410", open: false, migrate: "" },
17629
18349
  { method: "GET", path: "/api/quality/architecture", action: "410", open: false, migrate: "" },
@@ -17639,9 +18359,101 @@ var LEGACY_ROUTES = [
17639
18359
  { method: "GET", path: "/api/spec/history", action: "410", open: false, migrate: "" },
17640
18360
  { method: "GET", path: "/api/wiki", action: "410", open: false, migrate: "" },
17641
18361
  { method: "GET", path: "/api/wiki/{section}", action: "410", open: false, migrate: "" },
17642
- { method: "POST", path: "/api/wiki/ask", action: "501", open: false, migrate: "" }
18362
+ { method: "POST", path: "/api/wiki/ask", action: "410", open: false, migrate: "wiki deleted; no replacement" }
17643
18363
  ];
17644
18364
 
18365
+ // src/server/legacy/routes-audit.ts
18366
+ var BAD_ID = "Invalid checkpoint_id: must be 1-128 chars of alphanumeric, hyphens, and underscores";
18367
+ function auditCheckpointMapped(db, repoDir, auditList) {
18368
+ const local = (c) => peerIsLoopback(c) && isLoopbackHost(c.req.header("host")) && (c.req.header("content-type") ?? "").toLowerCase().startsWith("application/json");
18369
+ const refuse = (c) => c.json({ detail: "loopback JSON requests only" }, 403);
18370
+ return {
18371
+ "GET /api/enterprise/audit": (c) => {
18372
+ const r = auditList(c);
18373
+ return r.err ?? c.json(r.rows);
18374
+ },
18375
+ "GET /api/enterprise/audit/summary": (c) => c.json(auditSummary(db, parseDays(c.req.query("days")))),
18376
+ "GET /api/checkpoints": (c) => c.json(listCheckpoints(repoDir, parseLimit(c.req.query("limit")))),
18377
+ "GET /api/checkpoints/{checkpoint_id}": (c) => {
18378
+ const id = c.req.param("checkpoint_id") ?? "";
18379
+ if (!validCheckpointId(id))
18380
+ return c.json({ detail: BAD_ID }, 400);
18381
+ const m = getCheckpoint(repoDir, id);
18382
+ return m ? c.json(m) : c.json({ detail: "Checkpoint not found" }, 404);
18383
+ },
18384
+ "POST /api/checkpoints": async (c) => {
18385
+ if (!local(c))
18386
+ return refuse(c);
18387
+ const body = await c.req.json().catch(() => ({}));
18388
+ const message = typeof body?.message === "string" ? body.message : "";
18389
+ if (message.length > 500)
18390
+ return c.json({ detail: "message must be at most 500 characters" }, 422);
18391
+ try {
18392
+ const meta = createCheckpoint(repoDir, message);
18393
+ audit3(db, { kind: "checkpoint.create", target: String(meta.id), result: "ok", detail: "legacy route" });
18394
+ return c.json(meta, 201);
18395
+ } catch (e) {
18396
+ audit3(db, { kind: "checkpoint.create", result: "error", detail: e.message });
18397
+ return c.json({ detail: "could not create the checkpoint" }, 500);
18398
+ }
18399
+ },
18400
+ "POST /api/checkpoints/{checkpoint_id}/rollback": (c) => {
18401
+ if (!local(c))
18402
+ return refuse(c);
18403
+ const id = c.req.param("checkpoint_id") ?? "";
18404
+ if (!validCheckpointId(id)) {
18405
+ audit3(db, { kind: "checkpoint.rollback", target: id.slice(0, 128), result: "refused", detail: "invalid id (legacy route)" });
18406
+ return c.json({ detail: BAD_ID }, 400);
18407
+ }
18408
+ const r = rollbackCheckpoint(repoDir, id);
18409
+ if (!r) {
18410
+ audit3(db, { kind: "checkpoint.rollback", target: id, result: "refused", detail: "not found (legacy route)" });
18411
+ return c.json({ detail: "Checkpoint not found" }, 404);
18412
+ }
18413
+ audit3(db, { kind: "checkpoint.rollback", target: id, result: r.errors.length ? "partial" : "ok", detail: `restored ${r.restored}; pre-rollback snapshot ${r.pre_rollback_snapshot} (legacy route)` });
18414
+ return c.json({ ...r, message: `Restored ${r.restored} item(s) from ${id}. Prior state saved as ${r.pre_rollback_snapshot} (undo this rollback by restoring it).` });
18415
+ }
18416
+ };
18417
+ }
18418
+
18419
+ // src/server/legacy/routes-memory.ts
18420
+ function memoryMapped(repoDir) {
18421
+ const loki = () => lokiFor(repoDir);
18422
+ return {
18423
+ "GET /api/tasks": (c) => {
18424
+ if (c.req.query("project_id") !== undefined)
18425
+ return c.json({ detail: "project_id filter is not supported by the Control Plane" }, 400);
18426
+ return c.json(tasksList(loki(), c.req.query("status")));
18427
+ },
18428
+ "GET /api/memory/episodes": (c) => {
18429
+ const lim = c.req.query("limit") === undefined ? 50 : Number(c.req.query("limit"));
18430
+ return Number.isInteger(lim) && lim >= 1 && lim <= 1000 ? c.json(memoryEpisodes(loki(), lim)) : c.json({ detail: "limit must be 1..1000" }, 422);
18431
+ },
18432
+ "GET /api/memory/episodes/{episode_id}": (c) => {
18433
+ const e = memoryEpisode(loki(), c.req.param("episode_id") ?? "");
18434
+ return e ? c.json(e) : c.json({ detail: "Episode not found" }, 404);
18435
+ },
18436
+ "GET /api/memory/patterns": (c) => c.json(memoryPatterns(loki())),
18437
+ "GET /api/memory/patterns/{pattern_id}": (c) => {
18438
+ const p = memoryPatterns(loki()).find((x) => x.id === c.req.param("pattern_id"));
18439
+ return p ? c.json(p) : c.json({ detail: "Pattern not found" }, 404);
18440
+ },
18441
+ "GET /api/memory/skills": (c) => c.json(memorySkills(loki())),
18442
+ "GET /api/memory/economics": (c) => c.json(memoryEconomics(loki())),
18443
+ "GET /api/context": (c) => {
18444
+ const r = contextTracking(loki());
18445
+ return c.json(r.body, r.status);
18446
+ },
18447
+ "POST /api/focus": async (c) => {
18448
+ if (!mutationAllowed(c))
18449
+ return c.json({ detail: "loopback JSON requests only" }, 403);
18450
+ const body = await c.req.json().catch(() => null);
18451
+ const r = setFocus(repoDir, typeof body === "object" && body !== null ? body.project_dir : undefined);
18452
+ return r.ok ? c.json(focusInfo(repoDir)) : c.json({ detail: r.error }, 400);
18453
+ }
18454
+ };
18455
+ }
18456
+
17645
18457
  // src/server/legacy/routes-metrics.ts
17646
18458
  function metricsMapped(repoDir) {
17647
18459
  const inactive = (c) => parseInactive(c.req.query("include_inactive"));
@@ -17654,6 +18466,31 @@ function metricsMapped(repoDir) {
17654
18466
  };
17655
18467
  }
17656
18468
 
18469
+ // src/server/legacy/routes-control.ts
18470
+ function controlMapped(db, repoDir) {
18471
+ const loki = () => lokiDirOf(repoDir);
18472
+ return {
18473
+ "POST /api/control/pause": (c) => runControl(db, repoDir, "pause", c, "legacy"),
18474
+ "POST /api/control/resume": (c) => runControl(db, repoDir, "resume", c, "legacy"),
18475
+ "POST /api/control/stop": (c) => runControl(db, repoDir, "stop", c, "legacy"),
18476
+ "POST /api/council/force-review": (c) => runControl(db, repoDir, "council-review", c, "legacy"),
18477
+ "GET /api/council/state": (c) => c.json(councilState(loki())),
18478
+ "GET /api/council/verdicts": (c) => {
18479
+ const n2 = intQ(c.req.query("limit"), 1000);
18480
+ return n2 === null || n2 === 0 ? c.json({ detail: "limit must be 1..1000" }, 422) : c.json(councilVerdicts(loki(), n2));
18481
+ },
18482
+ "GET /api/council/convergence": (c) => c.json(councilConvergence(loki())),
18483
+ "GET /api/council/report": (c) => c.json(councilReport(loki())),
18484
+ "GET /api/council/transcripts": (c) => {
18485
+ const limit = intQ(c.req.query("limit"), 200), iterMin = intQ(c.req.query("iter_min"), Number.MAX_SAFE_INTEGER);
18486
+ if (limit === null || limit === 0 || iterMin === null)
18487
+ return c.json({ detail: "limit must be 1..200 and iter_min a non-negative integer" }, 422);
18488
+ const r = councilTranscripts(loki(), { limit, since: c.req.query("since"), iter_min: iterMin });
18489
+ return "error" in r ? c.json({ detail: r.error }, 400) : c.json(r);
18490
+ }
18491
+ };
18492
+ }
18493
+
17657
18494
  // src/server/legacy/shim.ts
17658
18495
  var flagOn = (v) => ["true", "1", "yes"].includes((v ?? "").toLowerCase());
17659
18496
  var legacyAuthEnabled = (env) => flagOn(env.LOKI_ENTERPRISE_AUTH) || !!(env.LOKI_OIDC_ISSUER && env.LOKI_OIDC_CLIENT_ID);
@@ -17661,10 +18498,10 @@ var logged = new Set;
17661
18498
  var toHono = (p) => p.replace(/\{([A-Za-z_]+):path\}/g, ":$1{.+}").replace(/\{([A-Za-z_]+)\}/g, ":$1");
17662
18499
  var NOT_SUPPORTED = "not yet supported by the Control Plane";
17663
18500
  var repoVersion = () => {
17664
- for (const f of [join26(import.meta.dir, "../../../../../VERSION")]) {
18501
+ for (const f of [join29(import.meta.dir, "../../../../../VERSION")]) {
17665
18502
  try {
17666
- if (existsSync15(f))
17667
- return readFileSync23(f, "utf8").trim();
18503
+ if (existsSync18(f))
18504
+ return readFileSync26(f, "utf8").trim();
17668
18505
  } catch {}
17669
18506
  }
17670
18507
  return "unknown";
@@ -17792,7 +18629,10 @@ function legacyShim(opts) {
17792
18629
  const r = auditList(c);
17793
18630
  return r.err ?? c.json(r.rows);
17794
18631
  },
17795
- ...metricsMapped(opts.repoDir ?? process.cwd())
18632
+ ...metricsMapped(opts.repoDir ?? process.cwd()),
18633
+ ...auditCheckpointMapped(db, opts.repoDir ?? process.cwd(), auditList),
18634
+ ...memoryMapped(opts.repoDir ?? process.cwd()),
18635
+ ...controlMapped(db, opts.repoDir ?? process.cwd())
17796
18636
  };
17797
18637
  app.all("/lab/api/*", (c) => {
17798
18638
  const d = denied(c);
@@ -17817,7 +18657,7 @@ function legacyShim(opts) {
17817
18657
  if (r.action === "308")
17818
18658
  return c.redirect(`${uiBase}/`, 308);
17819
18659
  if (r.action === "410")
17820
- return c.json({ error: "gone", detail: "This legacy dashboard route was retired; nothing consumes it.", ...body }, 410);
18660
+ return c.json({ error: "gone", detail: "This legacy dashboard route was retired; nothing consumes it.", ...r.migrate ? { replacement: r.migrate } : {}, ...body }, 410);
17821
18661
  return c.json({ error: NOT_SUPPORTED, detail: "The legacy dashboard route exists, but the Control Plane has no data or mechanism for it yet.", ...body }, 501);
17822
18662
  };
17823
18663
  const m = r.method === "WS" || r.method === "MOUNT" ? "GET" : r.method;
@@ -17843,7 +18683,7 @@ function legacyShim(opts) {
17843
18683
 
17844
18684
  // src/server/app.ts
17845
18685
  var MAX_BODY2 = 1e6;
17846
- var defaultUiDir = () => [join27(import.meta.dir, "../../ui/dist"), join27(import.meta.dir, "../ui/dist")].find((d) => existsSync16(join27(d, "index.html")));
18686
+ var defaultUiDir = () => [join30(import.meta.dir, "../../ui/dist"), join30(import.meta.dir, "../ui/dist")].find((d) => existsSync19(join30(d, "index.html")));
17847
18687
  function createApp(opts) {
17848
18688
  const uiDir = opts.uiDir ?? defaultUiDir();
17849
18689
  const { db, sqlite } = openDb(opts.dbPath);
@@ -17968,11 +18808,11 @@ function createApp(opts) {
17968
18808
  return c.json({ error: "not found" }, 404);
17969
18809
  const root = resolve6(uiDir);
17970
18810
  const f = resolve6(root, `.${p}`);
17971
- if (f.startsWith(root + sep8) && existsSync16(f) && statSync11(f).isFile())
18811
+ if (f.startsWith(root + sep10) && existsSync19(f) && statSync13(f).isFile())
17972
18812
  return new Response(Bun.file(f));
17973
18813
  if (/\.[a-z0-9]+$/i.test(p))
17974
18814
  return c.json({ error: "not found" }, 404);
17975
- return new Response(Bun.file(join27(root, "index.html")));
18815
+ return new Response(Bun.file(join30(root, "index.html")));
17976
18816
  });
17977
18817
  return { app, db, close: () => sqlite.close() };
17978
18818
  }