loki-mode 11.7.1 → 11.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 (92) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/lib/backlog.py +40 -4
  4. package/autonomy/lib/chat.sh +3 -2
  5. package/autonomy/lib/workspace.py +95 -4
  6. package/autonomy/loki +185 -106
  7. package/autonomy/trigger-server.py +550 -5
  8. package/autonomy/verify.sh +5 -0
  9. package/bin/loki +1 -1
  10. package/completions/_loki +34 -1
  11. package/completions/loki.bash +10 -3
  12. package/dashboard/__init__.py +1 -1
  13. package/docs/ARCHITECTURE.md +0 -1
  14. package/docs/CLI-REFERENCE.md +1 -1
  15. package/docs/COMPONENTS.md +1 -1
  16. package/docs/DEPLOY-PLAN.md +13 -3
  17. package/docs/INSTALLATION.md +1 -1
  18. package/docs/WORKSPACES.md +1 -1
  19. package/docs/dev/architecture-reference.md +1 -1
  20. package/docs/sentry-intake.md +36 -0
  21. package/docs/v10/D61-SPEED.md +14 -0
  22. package/docs/v10/PROGRESS.md +22 -0
  23. package/loki-ts/dist/loki.js +962 -879
  24. package/mcp/__init__.py +1 -1
  25. package/package.json +2 -2
  26. package/packages/control-plane/dist/ask-tools-server.js +13 -4
  27. package/packages/control-plane/dist/server.js +614 -456
  28. package/packages/control-plane/src/server/diffstat.ts +10 -2
  29. package/packages/control-plane/src/server/legacy/routes-control.ts +8 -4
  30. package/packages/control-plane/src/server/routes/checkpoints.ts +11 -2
  31. package/packages/control-plane/ui/dist/assets/index-u8cd6TRQ.js +190 -0
  32. package/packages/control-plane/ui/dist/index.html +1 -1
  33. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  34. package/web-app/dist/assets/{AdminPage-S3BYeapm.js → AdminPage-BdH9iL2I.js} +1 -1
  35. package/web-app/dist/assets/{Avatar-C1rHwLgn.js → Avatar-C-WixxR6.js} +1 -1
  36. package/web-app/dist/assets/{Badge-CzlnN5aQ.js → Badge-BFWpRd_L.js} +1 -1
  37. package/web-app/dist/assets/{Button-B1w5AeGC.js → Button-CE5vG66Y.js} +1 -1
  38. package/web-app/dist/assets/{CockpitPage-Dyeqjfkc.js → CockpitPage-BAVn10bY.js} +1 -1
  39. package/web-app/dist/assets/{ComparePage-C6y2GkGR.js → ComparePage-DOecIGfM.js} +1 -1
  40. package/web-app/dist/assets/{ErrorBoundary-Dg2fcGkR.js → ErrorBoundary-1rNA5v7f.js} +1 -1
  41. package/web-app/dist/assets/{EvidenceReceiptPanel-CxbyjvTj.js → EvidenceReceiptPanel-DjOaWfdH.js} +1 -1
  42. package/web-app/dist/assets/{GitHubIssuesPanel-Dpys1xDb.js → GitHubIssuesPanel-NNPBuRPD.js} +1 -1
  43. package/web-app/dist/assets/{GitHubPRsPanel-CRVjWoFl.js → GitHubPRsPanel-CL1b--tV.js} +1 -1
  44. package/web-app/dist/assets/{HomePage-BKnv-oYw.js → HomePage-BsLDN6MW.js} +2 -2
  45. package/web-app/dist/assets/{LoginPage-BGksmFRd.js → LoginPage-CVIv5bCS.js} +1 -1
  46. package/web-app/dist/assets/{MagicPage-OCvhp16M.js → MagicPage-BgMBB9lK.js} +1 -1
  47. package/web-app/dist/assets/{MetricsPage-CJOJpXWC.js → MetricsPage-BHeJlqPC.js} +1 -1
  48. package/web-app/dist/assets/{NotFoundPage-C04p3QvN.js → NotFoundPage-DDzwfyDK.js} +1 -1
  49. package/web-app/dist/assets/{ProjectPage-BRB8txsY.js → ProjectPage-CuQWEYBF.js} +3 -3
  50. package/web-app/dist/assets/{ProjectsPage-CoUYeQEh.js → ProjectsPage-OoD0RFDZ.js} +1 -1
  51. package/web-app/dist/assets/{SettingsPage-BkjmJGfM.js → SettingsPage-tMFMKkK5.js} +1 -1
  52. package/web-app/dist/assets/{ShowcasePage-B4vU9TjQ.js → ShowcasePage-DVfLoWdz.js} +1 -1
  53. package/web-app/dist/assets/{SystemSettingsPage-Z2jiVsni.js → SystemSettingsPage-CThV9N4l.js} +1 -1
  54. package/web-app/dist/assets/{TeamsPage-Ulm8SISq.js → TeamsPage-CV0nvXI8.js} +1 -1
  55. package/web-app/dist/assets/{TemplatesPage-C_7MM6Ey.js → TemplatesPage-D81EI1L8.js} +1 -1
  56. package/web-app/dist/assets/{TerminalOutput-Cumd7Tgs.js → TerminalOutput-Bj7uKRIe.js} +1 -1
  57. package/web-app/dist/assets/{activity-CZAUkbiY.js → activity-DoNfNkOL.js} +1 -1
  58. package/web-app/dist/assets/{bell-BOvSXpEr.js → bell-BWO3OU50.js} +1 -1
  59. package/web-app/dist/assets/{bot-DOTZxBYj.js → bot-DcvWv_zD.js} +1 -1
  60. package/web-app/dist/assets/{check-BxpE0B5u.js → check-CfG09Z18.js} +1 -1
  61. package/web-app/dist/assets/{chevron-left-D6KPvBOA.js → chevron-left-z0fh4x2h.js} +1 -1
  62. package/web-app/dist/assets/{circle-alert-Bajq86oe.js → circle-alert-C_7GlYul.js} +1 -1
  63. package/web-app/dist/assets/{clock-CnlHAVJc.js → clock-BsYUp070.js} +1 -1
  64. package/web-app/dist/assets/{cloud-DjaymjwM.js → cloud-BVWAJ7Rv.js} +1 -1
  65. package/web-app/dist/assets/{code-xml-BADEw2Ld.js → code-xml-CdZT-6zu.js} +1 -1
  66. package/web-app/dist/assets/{database-B4aKb5Cq.js → database-Dbkm9t2D.js} +1 -1
  67. package/web-app/dist/assets/{dollar-sign-DrldXpja.js → dollar-sign-CfIUM5Pd.js} +1 -1
  68. package/web-app/dist/assets/{file-code-corner-DVebDVR-.js → file-code-corner-CrUKhnj-.js} +1 -1
  69. package/web-app/dist/assets/{file-plus-BD-N_-Tl.js → file-plus-Cf6u0xHY.js} +1 -1
  70. package/web-app/dist/assets/{globe-C7ieInC-.js → globe-DsIqaI7w.js} +1 -1
  71. package/web-app/dist/assets/{hammer-BSugKIji.js → hammer-CA14BeMp.js} +1 -1
  72. package/web-app/dist/assets/{index-nLQgfvOn.js → index-DpJ_GJuZ.js} +3 -3
  73. package/web-app/dist/assets/{layers-CHXxU2on.js → layers-Dh5Oqhrj.js} +1 -1
  74. package/web-app/dist/assets/{loader-circle-BEEz0LbD.js → loader-circle-B0WHIGqt.js} +1 -1
  75. package/web-app/dist/assets/{lock-CzQGfcnE.js → lock-DkgfEGYF.js} +1 -1
  76. package/web-app/dist/assets/{package-CcHYQLGd.js → package-LzjmwD1S.js} +1 -1
  77. package/web-app/dist/assets/{plus-DdzgFEXn.js → plus-BNjA0KSV.js} +1 -1
  78. package/web-app/dist/assets/{refresh-cw-7x0HbH9Y.js → refresh-cw-k3IacuDX.js} +1 -1
  79. package/web-app/dist/assets/{rotate-ccw-DBahX1Sp.js → rotate-ccw-BFON_xv-.js} +1 -1
  80. package/web-app/dist/assets/{scroll-text-BB8y8a9Z.js → scroll-text-CaIlSPZB.js} +1 -1
  81. package/web-app/dist/assets/{server-CfkbW2MH.js → server-CKFUccCG.js} +1 -1
  82. package/web-app/dist/assets/{shield-alert-DUgD8rBF.js → shield-alert-Q4-rPo5r.js} +1 -1
  83. package/web-app/dist/assets/{thumbs-up-DGoyY6QC.js → thumbs-up-BYMeh8oV.js} +1 -1
  84. package/web-app/dist/assets/{trash-2-B6xrSUMr.js → trash-2-BHSUEIHw.js} +1 -1
  85. package/web-app/dist/assets/{trending-down-B6RGKTOx.js → trending-down-BC_FR2VT.js} +1 -1
  86. package/web-app/dist/assets/{trending-up-B44DCrt1.js → trending-up-CmA-WmLW.js} +1 -1
  87. package/web-app/dist/assets/{upload-4kWm33ZY.js → upload-BxLgBa1B.js} +1 -1
  88. package/web-app/dist/assets/{usePolling-zkUtRQG6.js → usePolling-DKNgwt5n.js} +1 -1
  89. package/web-app/dist/assets/{user-04cEVIe2.js → user-CA2DlmLc.js} +1 -1
  90. package/web-app/dist/index.html +1 -1
  91. package/memory/rag_injector.py +0 -161
  92. package/packages/control-plane/ui/dist/assets/index-DbLzr87u.js +0 -190
package/autonomy/loki CHANGED
@@ -2703,13 +2703,14 @@ cmd_start() {
2703
2703
  echo " --log-level LEVEL debug | info | warn | error (default: info)"
2704
2704
  echo " --max-duration DUR Wall-clock cap: seconds, or 90m / 2h (default: none)"
2705
2705
  echo ""
2706
- echo "Issue-mode options (only used when input is an ISSUE-REF):"
2706
+ echo "Issue-mode options (used when input is an ISSUE-REF or a --brief):"
2707
2707
  echo " --dry-run Preview generated PRD without starting"
2708
2708
  echo " --no-start Generate PRD but don't start execution"
2709
2709
  echo " --output FILE Save generated PRD to custom path"
2710
2710
  echo " --worktree, -w Git worktree isolation (separate branch)"
2711
2711
  echo " --pr Worktree + auto-create PR"
2712
2712
  echo " --ship Worktree + PR + auto-merge"
2713
+ echo " --detach, -d Run in background (implies --worktree)"
2713
2714
  echo ""
2714
2715
  echo "Preview without spending (any input mode):"
2715
2716
  echo " loki plan <PRD> Analyze a spec WITHOUT executing: complexity,"
@@ -3443,6 +3444,24 @@ cmd_start() {
3443
3444
  return $?
3444
3445
  fi
3445
3446
 
3447
+ # BRIEF + isolation flags (--pr/--worktree/--detach/--prepare-pr/--ship):
3448
+ # the flags were collected above but only the issue path consumed them, so a
3449
+ # delegated brief ran in the caller's checkout with no PR. Route it through
3450
+ # cmd_run (worktree, detach, receipt-bearing PR) as a tracker-less issue.
3451
+ if [ "$detected_type" = "brief" ] && [ "$issue_use_worktree" = "true" ]; then
3452
+ local brief_run_args=("--brief-text" "$brief_text")
3453
+ for a in "${issue_mode_args[@]+"${issue_mode_args[@]}"}"; do
3454
+ brief_run_args+=("$a")
3455
+ done
3456
+ for a in "${args[@]+"${args[@]}"}"; do
3457
+ brief_run_args+=("$a")
3458
+ done
3459
+ brief_run_args+=("--no-plan")
3460
+ export LOKI_UNIFIED_START=1
3461
+ cmd_run "${brief_run_args[@]}"
3462
+ return $?
3463
+ fi
3464
+
3446
3465
  # R7 (zero-config first run): BRIEF mode. The user gave a one-line brief
3447
3466
  # (`loki start "build a todo app"` or `loki start --brief "..."`). Synthesize
3448
3467
  # a forward-looking PRD, switch to the lightweight TTFV profile (honest fast:
@@ -9751,7 +9770,11 @@ _deploy_latest_receipt() {
9751
9770
  return 0
9752
9771
  }
9753
9772
 
9754
- # _deploy_record <dir> <command> <exit_code> <run_id> <anchor_sha>
9773
+ # _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]
9774
+ # Writes the executed deploy's own record, then signs it (loki.deploy/1, Ed25519 via the
9775
+ # receipt signer in loki-ts/src/util/receipt_signer.ts) so it is bound to the receipt that
9776
+ # authorized it; `loki verify <record>` re-checks it offline. An unsigned record is reported
9777
+ # loudly and verifies as UNCHECKED, never VERIFIED.
9755
9778
  # Writes the executed deploy's own record. A deploy with no record of itself
9756
9779
  # defeats the entire premise of gating on evidence.
9757
9780
  #
@@ -9769,17 +9792,27 @@ _deploy_record() {
9769
9792
  # Post-deploy health and rollback facts. "unchecked" never implies healthy.
9770
9793
  local health_status="${7:-unchecked}" health_url="${8:-}"
9771
9794
  local rb_attempted="${9:-false}" rb_exit="${10:-}"
9795
+ local receipt_sha="${11:-}" started_at="${12:-}" finished_at="${13:-}" target="${14:-}" canary="${15:-false}"
9772
9796
  local ddir="$dir/.loki/deploys"
9773
9797
  [ -d "${LOKI_DIR:-}" ] && ddir="${LOKI_DIR}/deploys"
9774
9798
  mkdir -p "$ddir" 2>/dev/null || return 0
9775
9799
  local f="$ddir/$(date -u +%Y%m%dT%H%M%SZ)-$$.json"
9776
9800
  DR_CMD="$command" DR_RC="$rc" DR_RUN="$run_id" DR_ANCHOR="$anchor" DR_OUT="$f" \
9777
9801
  DR_STATE="$anchor_state" DR_HS="$health_status" DR_HU="$health_url" \
9778
- DR_RBA="$rb_attempted" DR_RBX="$rb_exit" \
9802
+ DR_RBA="$rb_attempted" DR_RBX="$rb_exit" DR_RSHA="$receipt_sha" DR_T0="$started_at" \
9803
+ DR_T1="$finished_at" DR_TGT="$target" DR_CAN="$canary" \
9779
9804
  python3 -c '
9780
9805
  import json, os, datetime
9781
9806
  state = os.environ.get("DR_STATE") or "anchored"
9807
+ now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
9782
9808
  json.dump({
9809
+ "schema": "loki.deploy/1",
9810
+ "receipt_sha256": os.environ.get("DR_RSHA", ""),
9811
+ "head_sha": os.environ["DR_ANCHOR"],
9812
+ "target": os.environ.get("DR_TGT", ""),
9813
+ "started_at": os.environ.get("DR_T0") or now,
9814
+ "finished_at": os.environ.get("DR_T1") or now,
9815
+ "canary": os.environ.get("DR_CAN") == "true",
9783
9816
  "recorded_at": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
9784
9817
  "command": os.environ["DR_CMD"],
9785
9818
  "exit_code": int(os.environ["DR_RC"]),
@@ -9795,6 +9828,10 @@ json.dump({
9795
9828
  "rollback_exit": int(os.environ["DR_RBX"]) if os.environ.get("DR_RBX", "") != "" else None,
9796
9829
  }, open(os.environ["DR_OUT"], "w"), indent=2)
9797
9830
  ' 2>/dev/null || return 0
9831
+ # Sign through the one Bun implementation. A subshell, because the delegate execs.
9832
+ if ! ( _loki_bun_delegate engine10 verify --sign-deploy "$f" ) >/dev/null 2>&1; then
9833
+ echo -e "${YELLOW}WARNING: the deploy record is UNSIGNED (no signing key or Bun route); it verifies as UNCHECKED.${NC}" >&2
9834
+ fi
9798
9835
  printf '%s' "$f"
9799
9836
  return 0
9800
9837
  }
@@ -9946,6 +9983,20 @@ _deploy_try_execute() {
9946
9983
  return 1
9947
9984
  fi
9948
9985
 
9986
+ # --canary uses only documented staged verbs; anything else refuses, naming why.
9987
+ local canary="${_LOKI_DEPLOY_CANARY:-false}" exec_cmd="$command" canary_kind=""
9988
+ if [ "$canary" = true ]; then
9989
+ case "$command" in
9990
+ "vercel --prod") canary_kind="vercel"; exec_cmd="vercel" ;;
9991
+ "fly deploy") canary_kind="fly"; exec_cmd="fly deploy --strategy canary" ;;
9992
+ *)
9993
+ echo -e "${RED}REFUSED: --canary is not supported for: ${command}${NC}" >&2
9994
+ echo " This provider documents no staged rollout verb (Vercel: deploy then 'vercel promote'; Fly: 'fly deploy --strategy canary'). Nothing was run." >&2
9995
+ return 1
9996
+ ;;
9997
+ esac
9998
+ fi
9999
+
9949
10000
  local reasons
9950
10001
  reasons="$(_deploy_gate "$dir" "$command")"
9951
10002
 
@@ -10002,7 +10053,7 @@ _deploy_try_execute() {
10002
10053
  else
10003
10054
  echo -e "${GREEN}Evidence gate PASSED.${NC} Receipt ${run_id:-unknown} VERIFIED this tree (${anchor:0:12})."
10004
10055
  fi
10005
- echo "Running: ${command}"
10056
+ if [ "$canary" = true ]; then echo "Running (canary): ${exec_cmd}"; else echo "Running: ${command}"; fi
10006
10057
  echo ""
10007
10058
 
10008
10059
  # Run in the project dir. No credential env var is read, set, or forwarded:
@@ -10032,13 +10083,40 @@ _deploy_try_execute() {
10032
10083
  esac
10033
10084
  fi
10034
10085
 
10035
- local rc=0
10036
- ( cd "$dir" && eval "$command" ) || rc=$?
10086
+ local rc=0 started_at finished_at purl="" canary_out=""
10087
+ started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
10088
+ local health_status="unchecked" rb_attempted=false rb_exit="" hcode=""
10089
+ if [ "$canary_kind" = vercel ]; then
10090
+ # Staged: a preview deploy (no --prod), probe the preview URL, promote only if healthy.
10091
+ # Nothing reaches production before the probe, so there is nothing to roll back.
10092
+ rb_cmd=""; auto_rollback=false
10093
+ exec_cmd="vercel"
10094
+ canary_out="$( cd "$dir" && eval "$exec_cmd" )" || rc=$?
10095
+ printf '%s\n' "$canary_out"
10096
+ purl="$(grep -Eo 'https?://[^[:space:]]+' <<<"$canary_out" | tail -n 1)"
10097
+ if [ "$rc" -eq 0 ] && [ -n "$purl" ]; then
10098
+ health_url="$purl"
10099
+ echo "Probing canary health: ${health_url}"
10100
+ if hcode="$(_deploy_probe_health "$health_url")"; then
10101
+ health_status="healthy"
10102
+ echo "Promoting: vercel promote ${purl}"
10103
+ exec_cmd="vercel; vercel promote ${purl}"
10104
+ ( cd "$dir" && vercel promote "$purl" ) || rc=$?
10105
+ else
10106
+ health_status="FAILED_HEALTH"
10107
+ echo " The canary was NOT promoted. Production is unchanged." >&2
10108
+ fi
10109
+ elif [ "$rc" -eq 0 ]; then
10110
+ echo " The preview deploy printed no URL, so it cannot be health-checked and is NOT promoted." >&2
10111
+ rc=4
10112
+ fi
10113
+ else
10114
+ ( cd "$dir" && eval "$exec_cmd" ) || rc=$?
10115
+ fi
10037
10116
 
10038
10117
  # Health probe only after a deploy that exited 0. Without a URL, or after a
10039
10118
  # failed deploy, status is "unchecked": never a claim the service is healthy.
10040
- local health_status="unchecked" rb_attempted=false rb_exit="" hcode=""
10041
- if [ "$rc" -eq 0 ] && [ -n "$health_url" ]; then
10119
+ if [ "$canary_kind" != vercel ] && [ "$rc" -eq 0 ] && [ -n "$health_url" ]; then
10042
10120
  echo "Probing health: ${health_url}"
10043
10121
  if hcode="$(_deploy_probe_health "$health_url")"; then
10044
10122
  health_status="healthy"
@@ -10066,9 +10144,12 @@ _deploy_try_execute() {
10066
10144
  fi
10067
10145
  fi
10068
10146
 
10069
- local rec
10070
- rec="$(_deploy_record "$dir" "$command" "$rc" "${run_id:-unknown}" "${anchor:-unknown}" \
10071
- "$anchor_state" "$health_status" "$health_url" "$rb_attempted" "$rb_exit")"
10147
+ local rec rsha
10148
+ finished_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
10149
+ rsha="$(python3 -c 'import json,sys;print((json.load(open(sys.argv[1])).get("verification") or {}).get("hash") or "")' "$pj" 2>/dev/null || printf '')"
10150
+ rec="$(_deploy_record "$dir" "$exec_cmd" "$rc" "${run_id:-unknown}" "${anchor:-unknown}" \
10151
+ "$anchor_state" "$health_status" "$health_url" "$rb_attempted" "$rb_exit" \
10152
+ "$rsha" "$started_at" "$finished_at" "${command%% *}" "$canary")"
10072
10153
  echo ""
10073
10154
  if [ "$rc" -eq 0 ]; then
10074
10155
  echo -e "${GREEN}Deploy command exited 0.${NC}"
@@ -10208,7 +10289,7 @@ cmd_deploy() {
10208
10289
  # file alone weakens this gate.
10209
10290
  local LOKI_DEPLOY_ALLOW_UNANCHORED=false
10210
10291
  # Per-invocation like --execute; local so an ambient env var cannot set them.
10211
- local _LOKI_DEPLOY_HEALTH_URL="" _LOKI_DEPLOY_AUTO_ROLLBACK=false
10292
+ local _LOKI_DEPLOY_HEALTH_URL="" _LOKI_DEPLOY_AUTO_ROLLBACK=false _LOKI_DEPLOY_CANARY=false
10212
10293
 
10213
10294
  # Arg parse (mirror cmd_preview: lenient on unknown args -> no behavior drift).
10214
10295
  while [ $# -gt 0 ]; do
@@ -10216,7 +10297,7 @@ cmd_deploy() {
10216
10297
  --help|-h|help)
10217
10298
  echo -e "${BOLD}Loki Mode -- deploy command advisory (print-only by default)${NC}"
10218
10299
  echo ""
10219
- echo "Usage: loki deploy [--dir <path>] [--no-clip] [--execute] [--health-url URL [--auto-rollback]]"
10300
+ echo "Usage: loki deploy [--dir <path>] [--no-clip] [--execute [--canary]] [--health-url URL [--auto-rollback]]"
10220
10301
  echo ""
10221
10302
  echo "Detects your project type and your installed cloud CLI (and any"
10222
10303
  echo "CI/CD pipeline), then PRINTS the exact deploy command for YOU to run."
@@ -10283,6 +10364,18 @@ cmd_deploy() {
10283
10364
  echo " print a manual note. Needs --health-url. The rolled-back"
10284
10365
  echo " service is not re-probed."
10285
10366
  echo ""
10367
+ echo "Staged rollout (only with --execute):"
10368
+ echo " --canary Vercel: deploy without --prod, health-check the preview"
10369
+ echo " URL, then run 'vercel promote <url>'; a failed probe never"
10370
+ echo " promotes. Fly: 'fly deploy --strategy canary'. Other"
10371
+ echo " providers document no staged path and refuse --canary."
10372
+ echo ""
10373
+ echo "The executed deploy's record in .loki/deploys/ is a signed loki.deploy/1"
10374
+ echo "attestation bound to the receipt (receipt_sha256). Re-check it offline with"
10375
+ echo "'loki verify <record.json>' (add --pubkey FILE to use a given key). It fails"
10376
+ echo "closed: a tampered, unsigned or unkeyed record, or a cited receipt that is not"
10377
+ echo "VERIFIED, is never reported as VERIFIED."
10378
+ echo ""
10286
10379
  echo "Options:"
10287
10380
  echo " --dir <path> Project directory to scan (default: current dir)"
10288
10381
  echo " --no-clip Do not copy the idiomatic command to the clipboard"
@@ -10311,6 +10404,9 @@ cmd_deploy() {
10311
10404
  --auto-rollback)
10312
10405
  _LOKI_DEPLOY_AUTO_ROLLBACK=true
10313
10406
  ;;
10407
+ --canary)
10408
+ _LOKI_DEPLOY_CANARY=true
10409
+ ;;
10314
10410
  --dir)
10315
10411
  # Guard against a missing OR flag-shaped value: an unguarded
10316
10412
  # shift when --dir is the last arg underflows under set -e, and a
@@ -10332,6 +10428,10 @@ cmd_deploy() {
10332
10428
  echo "loki deploy --auto-rollback requires --health-url (no health signal, no rollback)" >&2
10333
10429
  return 1
10334
10430
  fi
10431
+ if [ "$_LOKI_DEPLOY_CANARY" = true ] && [ "$do_execute" != true ]; then
10432
+ echo "loki deploy --canary requires --execute" >&2
10433
+ return 1
10434
+ fi
10335
10435
  if [ "$do_execute" != true ] && [ -n "$_LOKI_DEPLOY_HEALTH_URL" ]; then
10336
10436
  echo "Note: --health-url and --auto-rollback only apply with --execute; ignored on this advisory run." >&2
10337
10437
  fi
@@ -10939,6 +11039,10 @@ cmd_run() {
10939
11039
  local prepare_pr=false
10940
11040
  local auto_merge=false
10941
11041
  local run_detached=false
11042
+ # Internal: set by cmd_start when a one-line brief carries --pr/--worktree/
11043
+ # --detach. The brief is treated as a tracker-less issue so it reuses the
11044
+ # worktree, detach and PR machinery below instead of a parallel copy.
11045
+ local brief_in=""
10942
11046
 
10943
11047
  # Parse arguments
10944
11048
  while [[ $# -gt 0 ]]; do
@@ -11128,6 +11232,15 @@ cmd_run() {
11128
11232
  --config=*|--vars=*|--env-file=*)
11129
11233
  shift
11130
11234
  ;;
11235
+ --brief-text)
11236
+ if [ "$#" -ge 2 ]; then
11237
+ brief_in="$2"
11238
+ shift 2
11239
+ else
11240
+ echo -e "${RED}--brief-text requires text${NC}"
11241
+ exit 1
11242
+ fi
11243
+ ;;
11131
11244
  -*)
11132
11245
  echo -e "${RED}Unknown option: $1${NC}"
11133
11246
  echo "Run 'loki run --help' for usage."
@@ -11163,6 +11276,10 @@ cmd_run() {
11163
11276
  start_args+=("--parallel")
11164
11277
  fi
11165
11278
 
11279
+ if [[ -n "$brief_in" ]]; then
11280
+ issue_ref="brief"
11281
+ fi
11282
+
11166
11283
  if [[ -z "$issue_ref" ]]; then
11167
11284
  echo -e "${RED}Error: Issue reference required${NC}"
11168
11285
  echo ""
@@ -11202,16 +11319,33 @@ cmd_run() {
11202
11319
 
11203
11320
  # Detect provider
11204
11321
  local issue_provider
11205
- issue_provider=$(detect_issue_provider "$issue_ref")
11206
- echo -e "${CYAN}Issue provider:${NC} ${issue_provider}"
11207
-
11208
- # Fetch the issue
11209
- echo -e "${CYAN}Fetching issue...${NC}"
11210
11322
  local issue_json
11211
- issue_json=$(fetch_issue "$issue_ref") || {
11212
- echo -e "${RED}Failed to fetch issue${NC}"
11213
- exit 1
11214
- }
11323
+ if [[ -n "$brief_in" ]]; then
11324
+ # Brief: no tracker to fetch from. The text travels in env so quoting in
11325
+ # the brief can never reach the shell or python source.
11326
+ issue_provider="brief"
11327
+ echo -e "${CYAN}Source:${NC} one-line brief"
11328
+ issue_json=$(LOKI_BRIEF_IN="$brief_in" LOKI_BRIEF_TS="$(date +%s)" python3 -c "
11329
+ import json, os
11330
+ t = os.environ['LOKI_BRIEF_IN']
11331
+ first = t.strip().splitlines()[0] if t.strip() else 'brief'
11332
+ print(json.dumps({'provider': 'brief', 'number': 'b' + os.environ['LOKI_BRIEF_TS'],
11333
+ 'title': first[:80], 'body': t, 'labels': [], 'url': '', 'repo': ''}))
11334
+ ") || {
11335
+ echo -e "${RED}Failed to build brief${NC}"
11336
+ exit 1
11337
+ }
11338
+ else
11339
+ issue_provider=$(detect_issue_provider "$issue_ref")
11340
+ echo -e "${CYAN}Issue provider:${NC} ${issue_provider}"
11341
+
11342
+ # Fetch the issue
11343
+ echo -e "${CYAN}Fetching issue...${NC}"
11344
+ issue_json=$(fetch_issue "$issue_ref") || {
11345
+ echo -e "${RED}Failed to fetch issue${NC}"
11346
+ exit 1
11347
+ }
11348
+ fi
11215
11349
 
11216
11350
  # Extract fields for display
11217
11351
  local title number url
@@ -11557,7 +11691,7 @@ ${_gh_receipt}"
11557
11691
  fi
11558
11692
 
11559
11693
  case "${issue_provider:-github}" in
11560
- github)
11694
+ github|brief)
11561
11695
  if command -v gh &>/dev/null; then
11562
11696
  local branch_current
11563
11697
  branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
@@ -11609,7 +11743,7 @@ ${_gh_receipt}"
11609
11743
  branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
11610
11744
  if gh pr merge "$branch_current" --squash --delete-branch 2>/dev/null; then
11611
11745
  echo -e "${GREEN}PR merged and branch deleted${NC}"
11612
- if [[ -n "${number:-}" ]]; then
11746
+ if [[ "${number:-}" =~ ^[0-9]+$ ]]; then
11613
11747
  gh issue close "$number" --comment "Resolved by Loki Mode" 2>/dev/null || true
11614
11748
  fi
11615
11749
  else
@@ -18134,9 +18268,15 @@ cmd_verify() {
18134
18268
  # verified by the engine10 verifier directly; bin/loki sends a bare verify back here.
18135
18269
  for _pk_arg in "$@"; do
18136
18270
  case "$_pk_arg" in
18137
- composite.json|*/composite.json|--seal-composite|--seal-composite=*)
18271
+ --sign-deploy|--receipt|composite.json|*/composite.json|--seal-composite|--seal-composite=*|--max-age|--max-age=*|.loki/groups/*/receipt.json|*/.loki/groups/*/receipt.json)
18138
18272
  _loki_bun_delegate engine10 verify "$@"
18139
18273
  return $? ;;
18274
+ # A signed deploy record (.loki/deploys/*.json, schema loki.deploy/1) is re-checked by the same verifier.
18275
+ *.json)
18276
+ if [ -f "$_pk_arg" ] && grep -q 'loki\.deploy/1' "$_pk_arg" 2>/dev/null; then
18277
+ _loki_bun_delegate engine10 verify "$@"
18278
+ return $?
18279
+ fi ;;
18140
18280
  esac
18141
18281
  done
18142
18282
  # --fast: deterministic checks only, in milliseconds. Measured on this repo
@@ -28012,93 +28152,32 @@ cmd_trust_metrics() {
28012
28152
  python3 "$tm_mod" --loki-dir "$loki_dir" ${pass_args[@]+"${pass_args[@]}"}
28013
28153
  }
28014
28154
 
28015
- # Transparent cost view (R3): per-run + per-project spend, model routing, and
28016
- # budget status with the 80% warn line. Reuses efficiency_cost.collect_efficiency
28017
- # for the current-run aggregate (single source of truth) and reads .loki/proofs/
28018
- # for persistent per-run history. Honest: prints "not recorded" when cost was
28019
- # never collected, never a fabricated $0.00.
28020
- # Forward cost projection. Wraps tools/estimate-run.py.
28021
- #
28022
- # --iterations is REQUIRED by that tool by design: no multi-run history exists
28023
- # to derive a horizon from, so projecting a total without one would be guessing
28024
- # at the single number the operator asked for. We surface that requirement here
28025
- # rather than inventing a default, for the same reason cost reports UNKNOWN
28026
- # instead of $0.00 -- an unearned number is worse than an absent one.
28155
+ # loki estimate: one estimator. The estimate is loki-ts/src/runner/router/cost_preview.ts,
28156
+ # the same one 'loki start' prints; this arm only hands off to the Bun CLI (bin/loki is the
28157
+ # normal route; this covers 'loki help estimate' and a direct bash invocation). tools/estimate-run.py
28158
+ # stays for its other callers (preflight, model-advisor) but is no longer reachable from here.
28027
28159
  cmd_estimate() {
28028
- local iterations=""
28029
- local show_json=false
28030
-
28031
- while [[ $# -gt 0 ]]; do
28032
- case "$1" in
28033
- --help|-h)
28034
- echo -e "${BOLD}loki estimate${NC} - Project what a run is likely to cost"
28035
- echo ""
28036
- echo "Usage: loki estimate --iterations N [--json]"
28037
- echo ""
28038
- echo "Projects a run cost from this workspace's MEASURED history in"
28039
- echo ".loki/metrics/efficiency/. With no measured history there is no"
28040
- echo "basis, and it says so rather than returning a fabricated number."
28041
- echo ""
28042
- echo "Options:"
28043
- echo " --iterations N REQUIRED. How many iterations to project."
28044
- echo " No multi-run history exists to derive a horizon"
28045
- echo " from, so there is no honest default."
28046
- echo " --json Machine-readable JSON output"
28047
- echo " --help, -h Show this help"
28048
- echo ""
28049
- echo "Examples:"
28050
- echo " loki estimate --iterations 10"
28051
- echo " loki estimate --iterations 25 --json"
28052
- echo ""
28053
- echo "See also: loki cost --detail (what a run ALREADY spent)"
28054
- return 0
28055
- ;;
28056
- --json) show_json=true; shift ;;
28057
- --iterations) iterations="${2:-}"; shift 2 || shift ;;
28058
- --iterations=*) iterations="${1#*=}"; shift ;;
28059
- *) echo -e "${RED}Unknown option: $1${NC}" >&2; echo "Run 'loki estimate --help' for usage." >&2; return 1 ;;
28060
- esac
28061
- done
28062
-
28063
- if [ -z "$iterations" ]; then
28064
- echo -e "${RED}--iterations is required${NC}" >&2
28065
- echo "" >&2
28066
- echo "No multi-run history exists to derive a horizon from, so there is no" >&2
28067
- echo "honest default for how many iterations to project. Say how many you" >&2
28068
- echo "expect and the projection is scaled from measured per-iteration cost." >&2
28069
- echo "" >&2
28070
- echo " loki estimate --iterations 10" >&2
28071
- echo "" >&2
28072
- echo "Run 'loki estimate --help' for usage." >&2
28073
- return 1
28074
- fi
28075
-
28076
- case "$iterations" in
28077
- ''|*[!0-9]*)
28078
- echo -e "${RED}--iterations must be a positive whole number (got: $iterations)${NC}" >&2
28079
- return 1
28080
- ;;
28081
- esac
28082
-
28083
- local estimate_py="$SKILL_DIR/tools/estimate-run.py"
28084
- if ! command -v python3 >/dev/null 2>&1; then
28085
- echo -e "${RED}python3 is required for 'loki estimate'${NC}" >&2
28086
- echo "Install python3 and re-run." >&2
28087
- return 1
28160
+ local entry="${LOKI_TS_ENTRY:-}"
28161
+ if [ -z "$entry" ]; then
28162
+ if [ -f "$SKILL_DIR/loki-ts/dist/loki.js" ]; then
28163
+ entry="$SKILL_DIR/loki-ts/dist/loki.js"
28164
+ elif [ -f "$SKILL_DIR/loki-ts/src/cli.ts" ]; then
28165
+ entry="$SKILL_DIR/loki-ts/src/cli.ts"
28166
+ fi
28088
28167
  fi
28089
- if [ ! -f "$estimate_py" ]; then
28090
- echo -e "${RED}Estimate tool not found: $estimate_py${NC}" >&2
28091
- echo "Your install looks incomplete. Reinstall: loki self-update" >&2
28168
+ if [ -z "$entry" ] || [ ! -f "$entry" ] || ! command -v bun >/dev/null 2>&1; then
28169
+ echo -e "${RED}loki estimate requires the Bun runtime and is not available on the bash route.${NC}" >&2
28170
+ echo "Install Bun (https://bun.sh) or remove LOKI_LEGACY_BASH=1." >&2
28092
28171
  return 1
28093
28172
  fi
28094
-
28095
- # Same workspace derivation as 'loki cost --detail': the tool takes the
28096
- # workspace and appends .loki itself, while LOKI_DIR is the .loki path.
28097
- local est_args=("$(dirname "${LOKI_DIR:-.loki}")" "--iterations" "$iterations")
28098
- [ "$show_json" = "true" ] && est_args+=("--json")
28099
- python3 "$estimate_py" "${est_args[@]}"
28173
+ bun "$entry" estimate "$@"
28100
28174
  }
28101
28175
 
28176
+ # Transparent cost view (R3): per-run + per-project spend, model routing, and
28177
+ # budget status with the 80% warn line. Reuses efficiency_cost.collect_efficiency
28178
+ # for the current-run aggregate (single source of truth) and reads .loki/proofs/
28179
+ # for persistent per-run history. Honest: prints "not recorded" when cost was
28180
+ # never collected, never a fabricated $0.00.
28102
28181
  cmd_cost() {
28103
28182
  local show_json=false
28104
28183
  local last_n=0