loki-mode 11.17.6 → 11.17.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/lib/cost-summary.py +5 -2
  4. package/autonomy/lib/efficiency_cost.py +21 -1
  5. package/autonomy/lib/proof-generator.py +5 -0
  6. package/autonomy/lib/proof-template.html +21 -6
  7. package/autonomy/lib/trust_trajectory.py +11 -1
  8. package/autonomy/loki +42 -28
  9. package/completions/_loki +1 -0
  10. package/completions/loki.bash +1 -1
  11. package/dashboard/__init__.py +1 -1
  12. package/docs/CLI-REFERENCE.md +1 -1
  13. package/docs/INSTALLATION.md +1 -1
  14. package/docs/v10/BOARD.md +108 -4
  15. package/docs/v10/FAILURE-CLASSES.md +106 -2
  16. package/docs/v10/MODERNIZE.md +7 -1
  17. package/docs/v10/PROGRESS.md +10 -0
  18. package/loki-ts/dist/loki.js +1014 -999
  19. package/mcp/__init__.py +1 -1
  20. package/package.json +2 -2
  21. package/packages/control-plane/dist/ask-tools-server.js +218 -12
  22. package/packages/control-plane/dist/server.js +719 -593
  23. package/packages/control-plane/src/server/diffstat.ts +2 -2
  24. package/packages/control-plane/src/server/routes/checkpoints.ts +2 -2
  25. package/packages/control-plane/src/server/routes/integrations.ts +1 -1
  26. package/packages/control-plane/src/shipper/discover.ts +2 -2
  27. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  28. package/web-app/dist/assets/{AdminPage-CCedYmn3.js → AdminPage-CwtSZac4.js} +1 -1
  29. package/web-app/dist/assets/{Avatar-DMrDsuGe.js → Avatar-BfguNHDC.js} +1 -1
  30. package/web-app/dist/assets/{Badge-K4Ui2tJz.js → Badge-DQGEOIqb.js} +1 -1
  31. package/web-app/dist/assets/{Button-Bhjts5QZ.js → Button-CRb-wJga.js} +1 -1
  32. package/web-app/dist/assets/{CockpitPage-Be7sEVqh.js → CockpitPage-DnA9wewN.js} +1 -1
  33. package/web-app/dist/assets/{ComparePage-D4ty3jiF.js → ComparePage-DX6F_G0l.js} +1 -1
  34. package/web-app/dist/assets/{ErrorBoundary-Cmt2x_mL.js → ErrorBoundary-VVIdgfkb.js} +1 -1
  35. package/web-app/dist/assets/{EvidenceReceiptPanel-D2IXLnp9.js → EvidenceReceiptPanel-FxknNXxU.js} +1 -1
  36. package/web-app/dist/assets/{GitHubIssuesPanel-CyXitr6h.js → GitHubIssuesPanel-DqiraoJP.js} +1 -1
  37. package/web-app/dist/assets/{GitHubPRsPanel-RRM3H-xd.js → GitHubPRsPanel-BYkTsRfQ.js} +1 -1
  38. package/web-app/dist/assets/{HomePage-CMtrrgfs.js → HomePage-BjpWZdl4.js} +2 -2
  39. package/web-app/dist/assets/{LoginPage-BrzNhnMJ.js → LoginPage-DdBnFY4Y.js} +1 -1
  40. package/web-app/dist/assets/{MagicPage-D7VynOKM.js → MagicPage-ihQmO1wm.js} +1 -1
  41. package/web-app/dist/assets/{MetricsPage-DJ-gDbfP.js → MetricsPage-Dq5YARm1.js} +1 -1
  42. package/web-app/dist/assets/{NotFoundPage-BJh0XVTj.js → NotFoundPage-CLzwsmxk.js} +1 -1
  43. package/web-app/dist/assets/{ProjectPage-CAodVUpE.js → ProjectPage-Dm8X4ntm.js} +3 -3
  44. package/web-app/dist/assets/{ProjectsPage-m3siS0Rf.js → ProjectsPage-DI3PhBwa.js} +1 -1
  45. package/web-app/dist/assets/{SettingsPage-CFGgjOvy.js → SettingsPage-D3bL3Ll-.js} +1 -1
  46. package/web-app/dist/assets/{ShowcasePage-Dd9tpSKJ.js → ShowcasePage-ypNyQAKe.js} +1 -1
  47. package/web-app/dist/assets/{SystemSettingsPage-DXd0PtTI.js → SystemSettingsPage-a9KPi49-.js} +1 -1
  48. package/web-app/dist/assets/{TeamsPage-C5KaLIGW.js → TeamsPage-B2KmQjeS.js} +1 -1
  49. package/web-app/dist/assets/{TemplatesPage-CDbzE0ZZ.js → TemplatesPage-BnK5NB62.js} +1 -1
  50. package/web-app/dist/assets/{TerminalOutput-CKG79r_-.js → TerminalOutput-CYbPu51X.js} +1 -1
  51. package/web-app/dist/assets/{activity-BaQQRhDp.js → activity-DBIfCcER.js} +1 -1
  52. package/web-app/dist/assets/{bell-OeFqvU4e.js → bell-B9HN4QkY.js} +1 -1
  53. package/web-app/dist/assets/{bot-CSxaA0KU.js → bot-DamtEiBN.js} +1 -1
  54. package/web-app/dist/assets/{check-DJml2Xdn.js → check-DXifl-uv.js} +1 -1
  55. package/web-app/dist/assets/{chevron-left-DGnt-DtK.js → chevron-left-BrlJBb8m.js} +1 -1
  56. package/web-app/dist/assets/{circle-alert-BV5rVL7K.js → circle-alert-CVKCTAb9.js} +1 -1
  57. package/web-app/dist/assets/{clock-CZ7SvULz.js → clock-HQ6E5gSp.js} +1 -1
  58. package/web-app/dist/assets/{cloud-lkIzrZps.js → cloud-B15Ysqug.js} +1 -1
  59. package/web-app/dist/assets/{code-xml-DzA2wL8F.js → code-xml-hqFIp0_h.js} +1 -1
  60. package/web-app/dist/assets/{database-CLFG5upC.js → database-CcSm0M_K.js} +1 -1
  61. package/web-app/dist/assets/{dollar-sign-DgWePGsv.js → dollar-sign-acxpddMc.js} +1 -1
  62. package/web-app/dist/assets/{file-code-corner-DGQX2bmv.js → file-code-corner-B5IIv12W.js} +1 -1
  63. package/web-app/dist/assets/{file-plus-LkaXeYd6.js → file-plus-CkbMFWJb.js} +1 -1
  64. package/web-app/dist/assets/{globe-rSsEzPYv.js → globe-CWmZ3xTF.js} +1 -1
  65. package/web-app/dist/assets/{hammer-CPogONt9.js → hammer-BSaCsCAo.js} +1 -1
  66. package/web-app/dist/assets/{index-DpBLuMuz.js → index-BCf0fCNu.js} +3 -3
  67. package/web-app/dist/assets/{layers-CwFaqidn.js → layers-CL82nJ3b.js} +1 -1
  68. package/web-app/dist/assets/{loader-circle-C43izEm5.js → loader-circle-DBhm8R7M.js} +1 -1
  69. package/web-app/dist/assets/{lock-Bc_Uqxpp.js → lock-wWcqImfq.js} +1 -1
  70. package/web-app/dist/assets/{package-BM25lcR-.js → package-BaZa22IO.js} +1 -1
  71. package/web-app/dist/assets/{plus-D0xoiY6N.js → plus-TFOQPEeK.js} +1 -1
  72. package/web-app/dist/assets/{refresh-cw-COI1R5Oo.js → refresh-cw-BOKfYIta.js} +1 -1
  73. package/web-app/dist/assets/{rotate-ccw-BoNZx4xe.js → rotate-ccw-DrH5oGwW.js} +1 -1
  74. package/web-app/dist/assets/{scroll-text-Cakp8XHd.js → scroll-text-aY8_wIJd.js} +1 -1
  75. package/web-app/dist/assets/{server-CjfIlPmE.js → server-C6FQ01rB.js} +1 -1
  76. package/web-app/dist/assets/{shield-alert-BilhziJF.js → shield-alert-DurYyWUo.js} +1 -1
  77. package/web-app/dist/assets/{thumbs-up-CglMrtrt.js → thumbs-up-wUuSs67Q.js} +1 -1
  78. package/web-app/dist/assets/{trash-2-B6zVvnoG.js → trash-2-DAob0LKZ.js} +1 -1
  79. package/web-app/dist/assets/{trending-down-BVhwD9bt.js → trending-down-yKL9mp2O.js} +1 -1
  80. package/web-app/dist/assets/{trending-up-CVJ1ox6c.js → trending-up-BD55eCE3.js} +1 -1
  81. package/web-app/dist/assets/{upload-DmuR09WL.js → upload-0IJxnT31.js} +1 -1
  82. package/web-app/dist/assets/{usePolling-BTALfO91.js → usePolling-DuBiboP5.js} +1 -1
  83. package/web-app/dist/assets/{user-CRdHMZNH.js → user-BDw_rGaY.js} +1 -1
  84. package/web-app/dist/index.html +1 -1
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: An autonomous software factory that knows what it is supposed to deliver, and proves it did. Use when the user says Loki Mode or asks to build, fix or verify software autonomously.
4
4
  ---
5
5
 
6
- # Loki Mode v11.17.6
6
+ # Loki Mode v11.17.13
7
7
 
8
8
  **Current entry points (Loki 10 engine):** `loki "<task>"`, `loki owner/repo#N` and `loki quick "<task>"` run the Loki 10 engine and end with a signed receipt (`loki verify`). `loki backlog owner/repo --all` runs many issues in parallel. Guide: `docs/v10/GUIDE.md`. The skill instructions below, and `loki start`, describe the previous bash loop. PRD-file starts (`loki start ./prd.md`) and the opencode provider still run on it until FC38-SWEEP ports them; the legacy-engine command and the LOKI_ENGINE switch remain removed. Prefer `loki owner/repo#N` for issues.
9
9
 
@@ -483,4 +483,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
483
483
 
484
484
  ---
485
485
 
486
- **v11.17.6 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
486
+ **v11.17.13 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 11.17.6
1
+ 11.17.13
@@ -50,7 +50,7 @@ _HERE = os.path.dirname(os.path.abspath(__file__))
50
50
  if _HERE not in sys.path:
51
51
  sys.path.insert(0, _HERE)
52
52
 
53
- from efficiency_cost import record_is_measured # noqa: E402
53
+ from efficiency_cost import cost_block_is_partial, record_is_measured # noqa: E402
54
54
 
55
55
  # iteration_attribution.py already reads and sorts the efficiency dir, skipping
56
56
  # malformed records. Importing it keeps ONE reader of that directory; a third
@@ -167,6 +167,8 @@ def summarize(workspace="."):
167
167
  "fully_measured": exists > 0 and measured == exists,
168
168
  "total_cost_usd": round(total_usd, 4) if have_cost else None,
169
169
  "cost_iterations_counted": len(cost_points),
170
+ "cost_partial": cost_block_is_partial({
171
+ "cost_partial": have_cost and (len(cost_points) < measured or measured < exists)}),
170
172
  "avg_cost_per_iteration": (
171
173
  round(total_usd / len(cost_points), 4) if have_cost else None),
172
174
  "cache_hit_ratio": _ratio(
@@ -262,7 +264,8 @@ def render(s):
262
264
  s["iterations_found"], s["iterations_measured"],
263
265
  "" if s["fully_measured"] else " <- PARTIAL" if s["iterations_found"]
264
266
  else ""))
265
- L.append("Total cost: " + _usd(s["total_cost_usd"]))
267
+ L.append("Total cost: " + _usd(s["total_cost_usd"])
268
+ + (" <- LOWER BOUND" if s.get("cost_partial") else ""))
266
269
  L.append("Avg / iter: " + _usd(s["avg_cost_per_iteration"]))
267
270
  L.append("Cache ratio: " + _pct(s["cache_hit_ratio"])
268
271
  + " (cache_read / (input + cache_read))")
@@ -238,6 +238,19 @@ _LEDGER_TOKEN_KEYS = (
238
238
  )
239
239
 
240
240
 
241
+ def cost_block_is_partial(cost):
242
+ """The one partial-cost check (FC-COST-PARTIAL-b): True when a cost block's
243
+ dollar total is a lower bound or unknown, so consumers label it and never
244
+ print it as complete."""
245
+ if not isinstance(cost, dict):
246
+ return False
247
+ if cost.get("cost_partial") is True or cost.get("cost_available") is False:
248
+ return True
249
+ return any(
250
+ isinstance(cost.get(k), int) and not isinstance(cost.get(k), bool) and cost.get(k) > 0
251
+ for k in ("ledger_invalid_lines", "cost_unpriced_records"))
252
+
253
+
241
254
  def read_child_ledger(loki_dir):
242
255
  """Read .loki/metrics/child-sessions.jsonl, one JSON object per child session.
243
256
 
@@ -295,12 +308,19 @@ def _merge_child_ledger(loki_dir, cost):
295
308
  cost["ledger"] = "present"
296
309
  cost["ledger_records"] = len(records)
297
310
  cost["ledger_invalid_lines"] = invalid
311
+ if invalid > 0:
312
+ # A line that could not be parsed is spend we cannot see: lower bound.
313
+ cost["cost_partial"] = True
298
314
  if not records:
299
315
  if by_phase:
300
316
  cost["cost_by_phase"] = by_phase
301
317
  return
302
318
  ledger_usd, priced, unpriced = 0.0, 0, 0
303
319
  main_known = bool(cost.get("available"))
320
+ # Main records exist but none carried a dollar figure (cost_available is
321
+ # False, set only by the token-only branch): the ledger-only total is a
322
+ # lower bound, never complete.
323
+ main_unpriced = main_known and cost.get("cost_available") is False
304
324
  tokens = {k: cost.get(k) for k in _LEDGER_TOKEN_KEYS}
305
325
  if not main_known:
306
326
  tokens = {k: 0 for k in _LEDGER_TOKEN_KEYS}
@@ -321,7 +341,7 @@ def _merge_child_ledger(loki_dir, cost):
321
341
  if priced:
322
342
  cost["usd"] = round((main_usd or 0.0) + ledger_usd, 4)
323
343
  cost["cost_available"] = True
324
- partial = bool(cost.get("cost_partial")) or unpriced > 0 or not main_known
344
+ partial = bool(cost.get("cost_partial")) or unpriced > 0 or not main_known or main_unpriced
325
345
  cost["cost_partial"] = partial
326
346
  cost["cost_unpriced_records"] = int(cost.get("cost_unpriced_records") or 0) + unpriced
327
347
  if by_phase:
@@ -37,6 +37,7 @@ if _HERE not in sys.path:
37
37
 
38
38
  import proof_redact # noqa: E402
39
39
  from efficiency_cost import collect_efficiency as _collect_efficiency # noqa: E402
40
+ from efficiency_cost import cost_block_is_partial as _cost_is_partial # noqa: E402
40
41
  import effort_estimator # noqa: E402
41
42
  from tree_digest import MANIFEST_VERSION, compute_tree_digest # noqa: E402
42
43
  from workspace_diff import collect_workspace_diff # noqa: E402
@@ -2133,6 +2134,8 @@ def _build_social_hook(proof):
2133
2134
  council ratio. When cost was not collected, omit the cost (never fabricate
2134
2135
  a number, never print "$0.00")."""
2135
2136
  usd = _fmt_usd_hook((proof.get("cost") or {}).get("usd"))
2137
+ if usd is not None and _cost_is_partial(proof.get("cost")):
2138
+ usd = "at least " + usd
2136
2139
  lead = ("Built autonomously for " + usd) if usd is not None \
2137
2140
  else "Built autonomously by Loki Mode"
2138
2141
  parts = [lead]
@@ -2184,6 +2187,8 @@ def _render_fallback_html(proof):
2184
2187
 
2185
2188
  usd_val = cost.get("usd", None)
2186
2189
  usd_disp = _fmt_usd_hook(usd_val)
2190
+ if usd_disp is not None and _cost_is_partial(cost):
2191
+ usd_disp = "at least " + usd_disp
2187
2192
 
2188
2193
  rows = []
2189
2194
  rows.append("<h1>Loki Mode -- Proof of Run</h1>")
@@ -803,14 +803,28 @@
803
803
  sec.classList.remove("hide");
804
804
  }
805
805
 
806
+ // Mirror of cost_block_is_partial (autonomy/lib/efficiency_cost.py).
807
+ function costIsPartial(p) {
808
+ var c = g(p, "cost", null);
809
+ if (!c || typeof c !== "object") return false;
810
+ if (c.cost_partial === true || c.cost_available === false) return true;
811
+ var ks = ["ledger_invalid_lines", "cost_unpriced_records"];
812
+ for (var i = 0; i < ks.length; i++) {
813
+ var v = c[ks[i]];
814
+ if (typeof v === "number" && isFinite(v) && Math.floor(v) === v && v > 0) return true;
815
+ }
816
+ return false;
817
+ }
818
+
806
819
  function renderHero(p) {
807
820
  // cost.usd is null when cost was not collected for this run. g() coerces
808
821
  // null to its default, so read with a null default and branch BEFORE
809
822
  // fmtUsd: a skeptic seeing a zero-dollar total assumes the artifact is fake.
810
823
  var usdRaw = g(p, "cost.usd", null);
824
+ var partial = costIsPartial(p);
811
825
  var fc = num(g(p, "files_changed.count", 0));
812
826
  var parts = [__PROOF_RECEIPT_TITLE_JSON__];
813
- if (usdRaw !== null) parts.push(fmtUsd(usdRaw));
827
+ if (usdRaw !== null) parts.push((partial ? "at least " : "") + fmtUsd(usdRaw));
814
828
  else parts.push("cost not recorded");
815
829
  parts.push(fc + " file" + (fc === 1 ? "" : "s") + " changed");
816
830
  var cs = councilSummary(p);
@@ -825,7 +839,7 @@
825
839
  if (prov || model) sub.push("via " + esc(prov) + (model ? " / " + esc(model) : ""));
826
840
  document.getElementById("heroSub").innerHTML = sub.join(" · ");
827
841
 
828
- renderHeroStats(p, usdRaw, fc, cs);
842
+ renderHeroStats(p, usdRaw, fc, cs, partial);
829
843
 
830
844
  var actions = document.getElementById("heroActions");
831
845
  var html = "";
@@ -872,7 +886,7 @@
872
886
  actions.innerHTML = html + share;
873
887
  }
874
888
 
875
- function renderHeroStats(p, usdRaw, fc, cs) {
889
+ function renderHeroStats(p, usdRaw, fc, cs, partial) {
876
890
  var el = document.getElementById("heroStats");
877
891
  if (!el) return;
878
892
  var cells = [];
@@ -895,7 +909,7 @@
895
909
  }
896
910
  cells.push(["Verified", vMain, esc(vSub), cs ? "ok" : ""]);
897
911
  // Cost: never render a zero-dollar total when cost was not collected.
898
- if (usdRaw !== null) cells.push(["Cost", fmtUsd(usdRaw), "total for this run", ""]);
912
+ if (usdRaw !== null) cells.push(["Cost", (partial ? "at least " : "") + fmtUsd(usdRaw), partial ? "lower bound, some cost not priced" : "total for this run", ""]);
899
913
  else cells.push(["Cost", "not recorded", "no cost data", ""]);
900
914
  // Duration.
901
915
  var dur = g(p, "wall_clock_sec", null);
@@ -962,6 +976,7 @@
962
976
  function renderCost(p) {
963
977
  var card = document.getElementById("costCard");
964
978
  var usdRaw = g(p, "cost.usd", null);
979
+ var partial = costIsPartial(p);
965
980
  function tokenValue(path) {
966
981
  var raw = g(p, path, null);
967
982
  return raw === null ? "not recorded" : fmtInt(raw);
@@ -981,8 +996,8 @@
981
996
  // a zero-dollar total (which reads as fake/broken to a skeptic).
982
997
  var top;
983
998
  if (usdRaw !== null) {
984
- top = '<div class="cost-top"><span class="cost-usd">' + fmtUsd(usdRaw) +
985
- '</span><span class="cost-label">total cost for this run, itemized below</span></div>';
999
+ top = '<div class="cost-top"><span class="cost-usd">' + (partial ? "at least " : "") + fmtUsd(usdRaw) +
1000
+ '</span><span class="cost-label">' + (partial ? "lower bound: some cost was not priced or recorded" : "total cost for this run, itemized below") + '</span></div>';
986
1001
  } else {
987
1002
  top = '<div class="cost-top"><span class="cost-label">Cost was not recorded for this run. ' +
988
1003
  'Token usage is itemized below.</span></div>';
@@ -171,6 +171,12 @@ def _interventions_value(proof):
171
171
  _IV_TYPES = ("steer", "input", "authorization", "repair")
172
172
 
173
173
 
174
+ _LIB_DIR = os.path.dirname(os.path.abspath(__file__))
175
+ if _LIB_DIR not in sys.path:
176
+ sys.path.insert(0, _LIB_DIR)
177
+ from efficiency_cost import cost_block_is_partial # noqa: E402
178
+
179
+
174
180
  def _cost_value(proof):
175
181
  usd = _obj(proof.get("cost")).get("usd")
176
182
  if usd is None or isinstance(usd, bool):
@@ -236,6 +242,7 @@ def _load_runs(loki_dir):
236
242
  "verdict": verdict,
237
243
  "false_verified": false_verified,
238
244
  "cost_usd": _cost_value(proof),
245
+ "cost_partial": cost_block_is_partial(_obj(proof.get("cost"))),
239
246
  "interventions_by_type": by_type,
240
247
  "waited_s": waited_s,
241
248
  "active_s": active_s,
@@ -336,6 +343,9 @@ def outcome_effort(series):
336
343
  cost_note = "no runs"
337
344
  elif accepted == 0:
338
345
  cost_note = "no accepted run, cost per accepted is undefined"
346
+ elif any(r.get("cost_partial") for r in series):
347
+ cost_note = "cost partial for %d of %d runs, a lower bound is not a total" % (
348
+ sum(1 for r in series if r.get("cost_partial")), len(series))
339
349
  elif unmeasured:
340
350
  cost_note = "cost unmeasured for %d of %d runs, a partial sum is not a total" % (unmeasured, len(series))
341
351
  else:
@@ -449,7 +459,7 @@ def compute_trajectory(loki_dir):
449
459
  "regressing_axes": regressing_axes,
450
460
  "series": series,
451
461
  "notes": notes,
452
- "outcome_effort": outcome_effort(series),
462
+ "outcome_effort": outcome_effort(runs),
453
463
  }
454
464
 
455
465
 
package/autonomy/loki CHANGED
@@ -397,6 +397,24 @@ _loki_update_cache_path() {
397
397
  echo "$HOME/.loki/cache/update-check-bash.json"
398
398
  }
399
399
 
400
+ # _loki_verdict_badge <headline> <url> -> prints a "Verified by Loki" markdown
401
+ # badge for an honesty.headline, or nothing. Exact-match only: VERIFIED
402
+ # (brightgreen), VERIFIED WITH GAPS (orange), NOT VERIFIED (red). Any other
403
+ # headline, or an empty url, omits the badge (never green, never forced red).
404
+ _loki_verdict_badge() {
405
+ local headline="${1:-}" url="${2:-}" message color
406
+ url="${url#"${url%%[![:space:]]*}"}"; url="${url%"${url##*[![:space:]]}"}"
407
+ [ -n "$url" ] || return 0
408
+ case "$headline" in
409
+ "VERIFIED") message="verified"; color="brightgreen" ;;
410
+ "VERIFIED WITH GAPS") message="verified with gaps"; color="orange" ;;
411
+ "NOT VERIFIED") message="not verified"; color="red" ;;
412
+ *) return 0 ;;
413
+ esac
414
+ printf '[![Loki: %s](https://img.shields.io/static/v1?label=Loki&message=%s&color=%s)](%s)\n' \
415
+ "$message" "${message// /%20}" "$color" "$url"
416
+ }
417
+
400
418
  # _loki_semver_newer <latest> <current> -> 0 (true) iff latest is strictly newer.
401
419
  # Rejects anything that is not a plain MAJOR.MINOR.PATCH (e.g. "unknown",
402
420
  # prereleases): on any unparseable input it returns 1 (never nudge).
@@ -9973,7 +9991,7 @@ _deploy_latest_receipt() {
9973
9991
  return 0
9974
9992
  }
9975
9993
 
9976
- # _deploy_record <dir> <command> <exit_code> <run_id> <anchor_sha> [state] [health_status] [health_url] [rb_attempted] [rb_exit] [receipt_sha256] [started_at] [finished_at] [target] [canary] [post_rollback_health] [health_http_code] [health_match] [tree_sha] [destination]
9994
+ # _deploy_record <dir> <command> <exit_code> <run_id> <anchor_sha> [state] [health_status] [health_url] [rb_attempted] [rb_exit] [receipt_sha256] [started_at] [finished_at] [target] [canary] [post_rollback_health] [health_http_code] [health_match] [tree_sha] [destination] [deployed_url] [health_url_source]
9977
9995
  # Writes the executed deploy's own record, then signs it (loki.deploy/1, Ed25519 via the
9978
9996
  # receipt signer in loki-ts/src/util/receipt_signer.ts) so it is bound to the receipt that
9979
9997
  # authorized it; `loki verify <record>` re-checks it offline. An unsigned record is reported
@@ -9997,6 +10015,7 @@ _deploy_record() {
9997
10015
  local rb_attempted="${9:-false}" rb_exit="${10:-}"
9998
10016
  local receipt_sha="${11:-}" started_at="${12:-}" finished_at="${13:-}" target="${14:-}" canary="${15:-false}"
9999
10017
  local post_rb="${16:-unchecked}" hcode_rec="${17:-}" hmatch="${18:-}" tree_sha="${19:-}" dest_rec="${20:-}"
10018
+ local deployed_url="${21:-}" hsrc="${22:-}"
10000
10019
  local ddir="$dir/.loki/deploys"
10001
10020
  [ -d "${LOKI_DIR:-}" ] && ddir="${LOKI_DIR}/deploys"
10002
10021
  mkdir -p "$ddir" 2>/dev/null || return 0
@@ -10004,7 +10023,7 @@ _deploy_record() {
10004
10023
  DR_CMD="$command" DR_RC="$rc" DR_RUN="$run_id" DR_ANCHOR="$anchor" DR_OUT="$f" \
10005
10024
  DR_STATE="$anchor_state" DR_HS="$health_status" DR_HU="$health_url" \
10006
10025
  DR_RBA="$rb_attempted" DR_RBX="$rb_exit" DR_RSHA="$receipt_sha" DR_T0="$started_at" \
10007
- DR_T1="$finished_at" DR_TGT="$target" DR_CAN="$canary" DR_PRB="$post_rb" DR_HC="$hcode_rec" DR_HM="$hmatch" DR_TREE="$tree_sha" DR_DEST="$dest_rec" \
10026
+ DR_T1="$finished_at" DR_TGT="$target" DR_CAN="$canary" DR_PRB="$post_rb" DR_HC="$hcode_rec" DR_HM="$hmatch" DR_TREE="$tree_sha" DR_DEST="$dest_rec" DR_DURL="$deployed_url" DR_HSRC="$hsrc" \
10008
10027
  python3 -c '
10009
10028
  import json, os, datetime
10010
10029
  state = os.environ.get("DR_STATE") or "anchored"
@@ -10034,6 +10053,8 @@ json.dump({
10034
10053
  "health_match": os.environ.get("DR_HM", ""),
10035
10054
  "tree_sha": os.environ.get("DR_TREE", ""),
10036
10055
  "destination": os.environ.get("DR_DEST", ""),
10056
+ "deployed_url": os.environ.get("DR_DURL", ""),
10057
+ "health_url_source": os.environ.get("DR_HSRC", ""),
10037
10058
  "health_http_code": int(os.environ["DR_HC"]) if os.environ.get("DR_HC", "").isdigit() else None,
10038
10059
  }, open(os.environ["DR_OUT"], "w"), indent=2)
10039
10060
  ' 2>/dev/null || return 0
@@ -10339,6 +10360,8 @@ _deploy_try_execute() {
10339
10360
 
10340
10361
  local rc=0 started_at finished_at purl="" canary_out=""
10341
10362
  started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
10363
+ local deployed_url="" health_src=""
10364
+ [ -n "$health_url" ] && health_src="flag"
10342
10365
  local health_status="unchecked" rb_attempted=false rb_exit="" hcode="" rb_hcode="" post_rb="unchecked"
10343
10366
  if [ "$canary_kind" = vercel ]; then
10344
10367
  # Staged: a preview deploy (no --prod), probe the preview URL, promote only if healthy.
@@ -10349,7 +10372,7 @@ _deploy_try_execute() {
10349
10372
  printf '%s\n' "$canary_out"
10350
10373
  purl="$(grep -Eo 'https?://[^[:space:]]+' <<<"$canary_out" | tail -n 1)"
10351
10374
  if [ "$rc" -eq 0 ] && [ -n "$purl" ]; then
10352
- health_url="$purl"
10375
+ health_url="$purl"; deployed_url="$purl"; health_src="captured"
10353
10376
  echo "Probing canary health: ${health_url}"
10354
10377
  if hcode="$(_deploy_probe_health "$health_url" "$health_match")"; then
10355
10378
  health_status="healthy"
@@ -10365,7 +10388,16 @@ _deploy_try_execute() {
10365
10388
  rc=4
10366
10389
  fi
10367
10390
  else
10368
- ( cd "$dir" && eval "$exec_cmd" ) || rc=$?
10391
+ canary_out="$( cd "$dir" && eval "$exec_cmd" )" || rc=$?
10392
+ printf '%s\n' "$canary_out"
10393
+ if [ "$rc" -eq 0 ]; then
10394
+ # Same last-https-URL parse as the canary path; never invented.
10395
+ deployed_url="$(grep -Eo 'https?://[^[:space:]]+' <<<"$canary_out" | tail -n 1)" || deployed_url=""
10396
+ if [ -n "$deployed_url" ]; then
10397
+ echo "Live at: ${deployed_url}"
10398
+ if [ -z "$health_url" ]; then health_url="$deployed_url"; health_src="captured"; fi
10399
+ fi
10400
+ fi
10369
10401
  fi
10370
10402
 
10371
10403
  # Health probe only after a deploy that exited 0. Without a URL, or after a
@@ -10410,7 +10442,7 @@ _deploy_try_execute() {
10410
10442
  rsha="$(python3 -c 'import json,sys;print((json.load(open(sys.argv[1])).get("verification") or {}).get("hash") or "")' "$pj" 2>/dev/null || printf '')"
10411
10443
  rec="$(_deploy_record "$dir" "$exec_cmd" "$rc" "${run_id:-unknown}" "${anchor:-unknown}" \
10412
10444
  "$anchor_state" "$health_status" "$health_url" "$rb_attempted" "$rb_exit" \
10413
- "$rsha" "$started_at" "$finished_at" "${command%% *}" "$canary" "$post_rb" "$hcode" "$health_match" "$(git -C "$dir" rev-parse 'HEAD^{tree}' 2>/dev/null || true)" "$dest")"
10445
+ "$rsha" "$started_at" "$finished_at" "${command%% *}" "$canary" "$post_rb" "$hcode" "$health_match" "$(git -C "$dir" rev-parse 'HEAD^{tree}' 2>/dev/null || true)" "$dest" "$deployed_url" "$health_src")"
10414
10446
  echo ""
10415
10447
  if [ "$rc" -eq 0 ]; then
10416
10448
  echo -e "${GREEN}Deploy command exited 0.${NC}"
@@ -38275,39 +38307,21 @@ PYEOF
38275
38307
  # The badge links to the gist we just published (no new egress).
38276
38308
  # shields.io static/v1 query endpoint avoids structural hyphens.
38277
38309
  local badge
38278
- badge=$(LOKI_PROOF_JSON="$pj" LOKI_GIST_URL="$LOKI_LAST_GIST_URL" "$_share_py3" -E - <<'PYEOF' 2>/dev/null || true
38310
+ badge=$(LOKI_PROOF_JSON="$pj" "$_share_py3" -E - <<'PYEOF' 2>/dev/null || true
38279
38311
  # The cwd is the checkout being shared: drop it from sys.path before any
38280
38312
  # other import, or a committed json.py/os.py could answer this badge text.
38281
38313
  import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
38282
38314
  import json, os
38283
- from urllib.parse import quote
38284
38315
  try:
38285
38316
  d = json.load(open(os.environ["LOKI_PROOF_JSON"]))
38286
38317
  except Exception:
38287
38318
  d = {}
38288
- headline = (d.get("honesty") or {}).get("headline")
38289
- # Exact-match ONLY. council.final_verdict is intentionally NOT consulted: it is
38290
- # an LLM opinion, the precise fake-green source the deterministic headline
38291
- # overrides. Unmapped headline -> no entry -> badge omitted.
38292
- mapping = {
38293
- "VERIFIED": ("verified", "brightgreen"),
38294
- "VERIFIED WITH GAPS": ("verified with gaps", "orange"),
38295
- "NOT VERIFIED": ("not verified", "red"),
38296
- }
38297
- entry = mapping.get(headline)
38298
- url = os.environ.get("LOKI_GIST_URL", "").strip()
38299
- # Omit the badge for any unmapped headline (never green, never forced red),
38300
- # and when we have no link target to point at.
38301
- if entry and url:
38302
- message, color = entry
38303
- badge_img = (
38304
- "https://img.shields.io/static/v1?label=Loki"
38305
- "&message=" + quote(message)
38306
- + "&color=" + color
38307
- )
38308
- print("[![Loki: {msg}]({img})]({url})".format(msg=message, img=badge_img, url=url))
38319
+ h = (d.get("honesty") or {}).get("headline")
38320
+ if isinstance(h, str):
38321
+ print(h)
38309
38322
  PYEOF
38310
38323
  )
38324
+ badge=$(_loki_verdict_badge "$badge" "$LOKI_LAST_GIST_URL")
38311
38325
  if [ -n "$badge" ]; then
38312
38326
  echo ""
38313
38327
  echo "Markdown badge (paste into a README or PR):"
package/completions/_loki CHANGED
@@ -242,6 +242,7 @@ function _loki_commands {
242
242
  'secure:Secure-by-default gate (findings and waivers)'
243
243
  'own:Finish-and-own ownership handoff'
244
244
  'backup:Back up dashboard state (verified archive)'
245
+ 'restore:Restore dashboard state from a verified backup archive'
245
246
  'telemetry:OpenTelemetry management'
246
247
  'agent:Agent type dispatch'
247
248
  'doctor:Check system prerequisites'
@@ -150,7 +150,7 @@ _loki_completion() {
150
150
 
151
151
  deploy)
152
152
  if [[ "$cur" == -* ]]; then
153
- COMPREPLY=( $(compgen -W "--execute --canary --health-url --health-match --auto-rollback --dir --no-clip --allow-unanchored --help" -- "$cur") )
153
+ COMPREPLY=( $(compgen -W "--execute --destination --canary --health-url --health-match --auto-rollback --dir --no-clip --allow-unanchored --help" -- "$cur") )
154
154
  return 0
155
155
  fi
156
156
  ;;
@@ -7,6 +7,6 @@ Modules:
7
7
  control: Session control helpers (status, atomic writes, origin check)
8
8
  """
9
9
 
10
- __version__ = "11.17.6"
10
+ __version__ = "11.17.13"
11
11
 
12
12
  __all__ = ["__version__"]
@@ -1,6 +1,6 @@
1
1
  # CLI reference
2
2
 
3
- Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.17.6. Do not edit by hand.
3
+ Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.17.13. Do not edit by hand.
4
4
  Hidden and legacy-only commands are omitted; see `docs/v10/CLI-MODERN.md` for the full inventory.
5
5
 
6
6
  | Command | Description |
@@ -2,7 +2,7 @@
2
2
 
3
3
  Loki Mode, by [Autonomi](https://www.autonomi.dev/), turns a spec or an issue into a verified pull request with a receipt you can check. This page covers installing it, a first run, and the options around it.
4
4
 
5
- **Version:** v11.17.6
5
+ **Version:** v11.17.13
6
6
 
7
7
  **Engine note:** `loki start` is the primary command. `loki start`, `loki "<task>"`, `loki owner/repo#N` and `loki quick` all run the Loki 10 engine, whatever the flag order or input form (PRD file, issue ref, task or one-word brief). The previous loop runs only on `--engine legacy`, `LOKI_ENGINE=legacy`, or for one call when a flag Loki 10 cannot honor is present. See the [user guide](v10/GUIDE.md) and the [5-minute quickstart](QUICKSTART.md).
8
8