loki-mode 10.6.14 → 10.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +8 -5
  2. package/SKILL.md +2 -2
  3. package/VERSION +1 -1
  4. package/autonomy/acp-agent.py +128 -0
  5. package/autonomy/docker-run.sh +8 -0
  6. package/autonomy/issue-parser.sh +7 -7
  7. package/autonomy/issue-providers.sh +159 -1
  8. package/autonomy/lib/engine10-push.sh +4 -1
  9. package/autonomy/lib/review_risk.py +91 -0
  10. package/autonomy/loki +209 -19
  11. package/autonomy/mobile-verify.sh +66 -0
  12. package/autonomy/run.sh +44 -2
  13. package/completions/_loki +2 -0
  14. package/completions/loki.bash +1 -1
  15. package/dashboard/__init__.py +1 -1
  16. package/dashboard/api_runs_v1.py +80 -0
  17. package/dashboard/server.py +2 -0
  18. package/docs/INSTALLATION.md +5 -1
  19. package/docs/acp-editors.md +29 -0
  20. package/docs/api-runs-v1.md +12 -0
  21. package/docs/dev/architecture-reference.md +4 -0
  22. package/docs/merge-queue.md +13 -0
  23. package/docs/mobile-verify.md +11 -0
  24. package/docs/sentry-intake.md +13 -0
  25. package/docs/v10/BOARD.md +31 -6
  26. package/docs/v10/CP-ENTERPRISE-UI.md +276 -0
  27. package/docs/v10/D61-SPEED.md +4 -0
  28. package/docs/v10/DECISIONS.md +74 -0
  29. package/docs/v10/FOUNDER-QUEUE.md +1 -0
  30. package/docs/v10/GAP-RESEARCH-1003.md +32 -0
  31. package/docs/v10/MORNING-BRIEF-1003.md +39 -0
  32. package/docs/v10/PROGRESS.md +71 -0
  33. package/docs/v10/SWARM.md +32 -0
  34. package/docs/visual-evidence.md +5 -0
  35. package/loki-ts/dist/loki.js +618 -611
  36. package/mcp/__init__.py +1 -1
  37. package/package.json +1 -1
  38. package/packages/control-plane/dist/server.js +263 -9
  39. package/packages/control-plane/src/server/actions.ts +60 -0
  40. package/packages/control-plane/src/server/app.ts +39 -3
  41. package/packages/control-plane/src/shipper/watch.ts +64 -0
  42. package/packages/control-plane/ui/dist/assets/index-CUQ65vJY.css +1 -0
  43. package/packages/control-plane/ui/dist/assets/index-CwwGfp6E.js +129 -0
  44. package/packages/control-plane/ui/dist/index.html +3 -3
  45. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  46. package/web-app/dist/assets/{AdminPage-oAFMNfKH.js → AdminPage-BQPb5rc5.js} +1 -1
  47. package/web-app/dist/assets/{Avatar-BA2gnzyf.js → Avatar-Cttco6FC.js} +1 -1
  48. package/web-app/dist/assets/{Badge-Dsluv8n1.js → Badge-CPb6kpbH.js} +1 -1
  49. package/web-app/dist/assets/{Button-WmW8A-iG.js → Button-caTL28a4.js} +1 -1
  50. package/web-app/dist/assets/{CockpitPage-BR5o_RgL.js → CockpitPage-bEbLx6ph.js} +1 -1
  51. package/web-app/dist/assets/{ComparePage-3Eg-pKvO.js → ComparePage-DNtJSij-.js} +1 -1
  52. package/web-app/dist/assets/{ErrorBoundary-DAfrnnsQ.js → ErrorBoundary-BrgC3K44.js} +1 -1
  53. package/web-app/dist/assets/{EvidenceReceiptPanel-wLZI4OTr.js → EvidenceReceiptPanel-Q-mSrUUQ.js} +1 -1
  54. package/web-app/dist/assets/{GitHubIssuesPanel-BOuadisf.js → GitHubIssuesPanel-DbmYl_Er.js} +1 -1
  55. package/web-app/dist/assets/{GitHubPRsPanel-BTVoVpB3.js → GitHubPRsPanel-C6PCHM4B.js} +1 -1
  56. package/web-app/dist/assets/{HomePage-s3GWdNEe.js → HomePage-7zsL_k_e.js} +2 -2
  57. package/web-app/dist/assets/{LoginPage-BfOZ566t.js → LoginPage-Cl4ON8RQ.js} +1 -1
  58. package/web-app/dist/assets/{MagicPage-9EZ83xOt.js → MagicPage-BNEAhTqH.js} +1 -1
  59. package/web-app/dist/assets/{MetricsPage-OJMOYhHP.js → MetricsPage-DyBH-88q.js} +1 -1
  60. package/web-app/dist/assets/{NotFoundPage-_LoJ6KEv.js → NotFoundPage-CIVr21yL.js} +1 -1
  61. package/web-app/dist/assets/{ProjectPage-Duj5LSt6.js → ProjectPage-BMeVYocF.js} +3 -3
  62. package/web-app/dist/assets/{ProjectsPage-BBud3JrO.js → ProjectsPage-D-H2WRVU.js} +1 -1
  63. package/web-app/dist/assets/{SettingsPage-C4zdgKno.js → SettingsPage-o0kR9RAt.js} +1 -1
  64. package/web-app/dist/assets/{ShowcasePage-Nb3ACjMR.js → ShowcasePage-DtfMDMjp.js} +1 -1
  65. package/web-app/dist/assets/{SystemSettingsPage-BDi49TDh.js → SystemSettingsPage-CkXdpZ_T.js} +1 -1
  66. package/web-app/dist/assets/{TeamsPage-HkcqkjLa.js → TeamsPage-Cu5H9cQ0.js} +1 -1
  67. package/web-app/dist/assets/{TemplatesPage-CK796Y1F.js → TemplatesPage-3wdbVwil.js} +1 -1
  68. package/web-app/dist/assets/{TerminalOutput-BkvdNuT6.js → TerminalOutput-CWn301xh.js} +1 -1
  69. package/web-app/dist/assets/{activity-DFgMgETR.js → activity-YGaAt4zJ.js} +1 -1
  70. package/web-app/dist/assets/{bell-BInGGOMZ.js → bell-C28q0GO7.js} +1 -1
  71. package/web-app/dist/assets/{bot-D43Yjsj4.js → bot-DLcQGI92.js} +1 -1
  72. package/web-app/dist/assets/{check-DFAGTLM9.js → check-w0sBNlcm.js} +1 -1
  73. package/web-app/dist/assets/{chevron-left-Drwin7n1.js → chevron-left-W7O0vC_m.js} +1 -1
  74. package/web-app/dist/assets/{circle-alert-DLWdb7V9.js → circle-alert-Cxjq9fxB.js} +1 -1
  75. package/web-app/dist/assets/{clock-Dsbi59u5.js → clock-C8EqzmNl.js} +1 -1
  76. package/web-app/dist/assets/{cloud-CUU-dXTP.js → cloud-xNgp4g3Y.js} +1 -1
  77. package/web-app/dist/assets/{code-xml-CqIh7Wui.js → code-xml-B-lyNDLJ.js} +1 -1
  78. package/web-app/dist/assets/{database-DASN5Bfh.js → database-DoW5lmWc.js} +1 -1
  79. package/web-app/dist/assets/{dollar-sign-ZvXzAihc.js → dollar-sign-BcARA5sC.js} +1 -1
  80. package/web-app/dist/assets/{file-code-corner-ED_gN-N8.js → file-code-corner-CehcII0J.js} +1 -1
  81. package/web-app/dist/assets/{file-plus-BmBHndXt.js → file-plus-DI9Zo5hP.js} +1 -1
  82. package/web-app/dist/assets/{globe-Ck_iEzO4.js → globe-CiZWBk3H.js} +1 -1
  83. package/web-app/dist/assets/{hammer-B8YKMard.js → hammer-3qv_T2N-.js} +1 -1
  84. package/web-app/dist/assets/{index-D7SxmjVQ.js → index-gF9grAz-.js} +3 -3
  85. package/web-app/dist/assets/{layers-CGVsj3wB.js → layers-CN7d0By9.js} +1 -1
  86. package/web-app/dist/assets/{loader-circle-FIH7UJDF.js → loader-circle-DywcM-im.js} +1 -1
  87. package/web-app/dist/assets/{lock-vR8HoAnK.js → lock-_hdSmkU4.js} +1 -1
  88. package/web-app/dist/assets/{package-mZ6LFwXp.js → package-BV5zgqxJ.js} +1 -1
  89. package/web-app/dist/assets/{plus-CtnSVu0w.js → plus-CU_Hh4-x.js} +1 -1
  90. package/web-app/dist/assets/{refresh-cw-BIapF6Gj.js → refresh-cw-DMK-fNl-.js} +1 -1
  91. package/web-app/dist/assets/{rotate-ccw-LMkSsm21.js → rotate-ccw-CEB3_v5_.js} +1 -1
  92. package/web-app/dist/assets/{scroll-text-DQVlIhr9.js → scroll-text-BSdXxcRN.js} +1 -1
  93. package/web-app/dist/assets/{server-GqyhQxmz.js → server-CAMlP1yJ.js} +1 -1
  94. package/web-app/dist/assets/{shield-alert-BrFTNJ-I.js → shield-alert-B-r1Dt3h.js} +1 -1
  95. package/web-app/dist/assets/{thumbs-up-lgwAEJFW.js → thumbs-up-CYfxw9o4.js} +1 -1
  96. package/web-app/dist/assets/{trash-2-C1cNysK4.js → trash-2-DX3nGPS8.js} +1 -1
  97. package/web-app/dist/assets/{trending-down-DQFZ6j5s.js → trending-down-Bt-hSntd.js} +1 -1
  98. package/web-app/dist/assets/{trending-up-BJG_Zo8V.js → trending-up-DVSlvQp8.js} +1 -1
  99. package/web-app/dist/assets/{upload-BOY1aTpA.js → upload-D45RRHAP.js} +1 -1
  100. package/web-app/dist/assets/{usePolling-DoAmsTNy.js → usePolling-fq_Xj8iw.js} +1 -1
  101. package/web-app/dist/assets/{user-iOfCe0_v.js → user-BWcJkLlr.js} +1 -1
  102. package/web-app/dist/index.html +1 -1
  103. package/packages/control-plane/ui/dist/assets/index-CFfrqCx2.css +0 -1
  104. package/packages/control-plane/ui/dist/assets/index-Jt31nrK-.js +0 -114
package/autonomy/loki CHANGED
@@ -1693,6 +1693,7 @@ show_help() {
1693
1693
  echo "Observe:"
1694
1694
  echo " dashboard [cmd] Operations UI server (start|stop|status|url|open)"
1695
1695
  echo " mcp Launch the MCP server (Model Context Protocol, stdio)"
1696
+ echo " acp Run Loki as an ACP agent over stdio (VS Code, JetBrains, Zed)"
1696
1697
  echo ""
1697
1698
  echo "Report:"
1698
1699
  echo " report [cmd] Reporting: session|metrics|cost|export|share|dogfood"
@@ -1812,7 +1813,7 @@ show_help() {
1812
1813
  # missing from this list, so a new command cannot ship invisible.
1813
1814
  echo ""
1814
1815
  echo "All commands:"
1815
- echo " agent analyze api assets audit backlog bench checkpoint (cp) ci cleanup"
1816
+ echo " acp agent analyze api assets audit backlog bench checkpoint (cp) ci cleanup"
1816
1817
  echo " cluster cockpit code compliance completions compound config context (ctx)"
1817
1818
  echo " cost council crash dashboard demo deploy docker docs doctor dogfood"
1818
1819
  echo " enterprise estimate explain export failover github grill heal help"
@@ -1820,7 +1821,7 @@ show_help() {
1820
1821
  echo " issue keys kpis logs magic mcp memory metrics migrate modernize monitor"
1821
1822
  echo " next notify onboard open optimize otel outcomes own (handoff) pause plan preview"
1822
1823
  echo " projects proof (receipt) provider quick quickstart rc remote report reset"
1823
- echo " resume review rollback run sandbox secrets secure self-update sentrux"
1824
+ echo " merge resume review rollback run sandbox secrets secure self-update sentrux"
1824
1825
  echo " serve setup-skill share ship spec start state stats status steer stop"
1825
1826
  echo " syslog telemetry template test tour trigger trust trust-metrics"
1826
1827
  echo " ultracode update verify version voice watch watchdog web welcome why"
@@ -2007,6 +2008,9 @@ _loki_control_ui() {
2007
2008
 
2008
2009
  cmd_ui() {
2009
2010
  _loki_control_ui "$@" && return 0
2011
+ # CP-LEGACY: callers that must never start the classic server (run.sh
2012
+ # auto-open) set LOKI_UI_NO_CLASSIC=1.
2013
+ [ "${LOKI_UI_NO_CLASSIC:-0}" = "1" ] && return 1
2010
2014
  local port="" pid=""
2011
2015
  if [ -f "$DASHBOARD_PID_FILE" ]; then
2012
2016
  pid=$(cat "$DASHBOARD_PID_FILE" 2>/dev/null)
@@ -2084,6 +2088,18 @@ detect_arg_type() {
2084
2088
  return 0
2085
2089
  fi
2086
2090
 
2091
+ # Sentry intake: an https Sentry issue URL, or an exported Sentry JSON file
2092
+ # on disk, is an issue (checked before the .json -> prd rule below).
2093
+ if [[ "$arg" =~ ^https://[^/]*sentry[^/]*/.*issues/[0-9]+ ]]; then
2094
+ echo "issue"
2095
+ return 0
2096
+ fi
2097
+ if [[ "$arg" == *.json ]] && [ -f "$arg" ] \
2098
+ && head -c 2000000 "$arg" 2>/dev/null | grep -Eq '"(shortId|culprit)"[[:space:]]*:|"exception"[[:space:]]*:|"type"[[:space:]]*:[[:space:]]*"exception"'; then
2099
+ echo "issue"
2100
+ return 0
2101
+ fi
2102
+
2087
2103
  # Known PRD extensions - treat as PRD first (highest priority).
2088
2104
  # This handles edge case where a file is literally named "123.md" or
2089
2105
  # sits in a dir that matches another pattern.
@@ -7441,14 +7457,28 @@ _loki_dashboard_bind_is_safe() {
7441
7457
  return 1
7442
7458
  }
7443
7459
 
7460
+ _loki_dashboard_args_need_classic() {
7461
+ local a
7462
+ for a in "$@"; do
7463
+ case "$a" in
7464
+ --host|--host=*|--tls-cert|--tls-cert=*|--tls-key|--tls-key=*) return 0 ;;
7465
+ esac
7466
+ done
7467
+ return 1
7468
+ }
7469
+
7444
7470
  cmd_dashboard() {
7445
7471
  local subcommand="${1:-}"
7446
7472
 
7447
- # The Control Plane is the default UI; LOKI_CONTROL_DEFAULT=0 restores the classic dashboard.
7448
- # Only the bare forms are taken over; `start --port ...` and friends keep the classic server.
7473
+ # CP-LEGACY (D57): the Control Plane is the default UI for `dashboard`,
7474
+ # `dashboard start` and `dashboard open`, with or without flags. The legacy
7475
+ # Python dashboard stays reachable only by explicit opt-in:
7476
+ # LOKI_LEGACY_DASHBOARD=1 or LOKI_CONTROL_DEFAULT=0. Enterprise bind/TLS
7477
+ # flags (--host, --tls-cert, --tls-key) are classic-server concepts and
7478
+ # keep the classic server.
7449
7479
  case "$subcommand" in
7450
7480
  ""|start|open)
7451
- if [ $# -le 1 ] || { [ $# -eq 2 ] && [ "$2" = "--no-open" ]; }; then
7481
+ if [ "${LOKI_LEGACY_DASHBOARD:-0}" != "1" ] && ! _loki_dashboard_args_need_classic "${@:2}"; then
7452
7482
  _loki_control_ui "${@:2}" && return 0
7453
7483
  fi
7454
7484
  ;;
@@ -8307,6 +8337,11 @@ cmd_web_redirect_to_dashboard() {
8307
8337
  echo " it watches runs that are already going. To build from that spec:" >&2
8308
8338
  echo " loki start '${_web_dropped_prd}'" >&2
8309
8339
  fi
8340
+ # Flags such as --port must reach the classic validator, so only the
8341
+ # bare form (optionally --no-open) is routed to the Control Plane.
8342
+ if [ "${LOKI_LEGACY_DASHBOARD:-0}" != "1" ] && [ ${#dash_args[@]} -eq 0 ] && _loki_control_ui; then
8343
+ return 0
8344
+ fi
8310
8345
  cmd_dashboard_start ${dash_args[@]+"${dash_args[@]}"}
8311
8346
  }
8312
8347
 
@@ -21764,8 +21799,10 @@ ultracode council demo dogfood heal modernize import issue github init template
21764
21799
  status stop pause resume monitor watch watchdog dashboard web open preview share \
21765
21800
  memory context ctx checkpoint state report kpis cost metrics stats logs otel \
21766
21801
  syslog telemetry crash config provider doctor setup-skill onboard quickstart \
21767
- welcome update self-update rollback failover cluster enterprise remote deploy docker \
21802
+ welcome update self-update rollback merge failover cluster enterprise remote deploy docker \
21768
21803
  sandbox api mcp magic compound assets export wiki control docs explain why audit compliance \
21804
+ welcome update self-update rollback failover cluster enterprise remote deploy docker \
21805
+ sandbox api mcp acp magic compound assets export wiki control docs explain why audit compliance \
21769
21806
  secrets analyze optimize bench ci reset cleanup notify trigger voice sentrux \
21770
21807
  worktree wt projects cp rc trust-metrics serve agent code self_update test help version"
21771
21808
  local best
@@ -22097,6 +22134,9 @@ main() {
22097
22134
  rollback)
22098
22135
  cmd_rollback "$@"
22099
22136
  ;;
22137
+ merge)
22138
+ cmd_merge "$@"
22139
+ ;;
22100
22140
  council)
22101
22141
  cmd_council "$@"
22102
22142
  ;;
@@ -22177,6 +22217,10 @@ main() {
22177
22217
  mcp)
22178
22218
  cmd_mcp "$@"
22179
22219
  ;;
22220
+ acp)
22221
+ # ACP agent over stdio; stdout is protocol-only. See docs/acp-editors.md.
22222
+ LOKI_ACP_LOKI="${LOKI_ACP_LOKI:-$_LOKI_SCRIPT_DIR/loki}" exec python3 "$_LOKI_SCRIPT_DIR/acp-agent.py"
22223
+ ;;
22180
22224
  migrate)
22181
22225
  # CLI consolidation (Phase B): 'migrate' -> 'modernize migrate'.
22182
22226
  _deprecated_alias migrate "modernize migrate" "$@"
@@ -22709,6 +22753,7 @@ cmd_review() {
22709
22753
  local review_ultra=false
22710
22754
  local review_assume_yes=false
22711
22755
  local review_timeout=""
22756
+ local review_risk=false
22712
22757
 
22713
22758
  while [[ $# -gt 0 ]]; do
22714
22759
  case "$1" in
@@ -22736,6 +22781,10 @@ cmd_review() {
22736
22781
  echo " skip the prompt. Optional [file-or-dir] is"
22737
22782
  echo " passed as the ultrareview target (branch / PR"
22738
22783
  echo " number / base branch)."
22784
+ echo " --risk Deterministic 0-100 diff risk score with per-factor"
22785
+ echo " breakdown (files, sensitive paths, size, test delta,"
22786
+ echo " deleted tests). Works with --pr/--staged/--since;"
22787
+ echo " 'loki review <pr-number> --risk' also works. Use --json"
22739
22788
  echo " --yes, -y Skip the --ultra confirmation prompt"
22740
22789
  echo " --timeout <minutes> --ultra: max minutes to wait (default 30)"
22741
22790
  echo " --help, -h Show this help"
@@ -22759,6 +22808,8 @@ cmd_review() {
22759
22808
  return 0
22760
22809
  ;;
22761
22810
  --ultra) review_ultra=true; shift ;;
22811
+ --risk) review_risk=true; shift ;;
22812
+ --json) review_format="json"; shift ;;
22762
22813
  --yes|-y) review_assume_yes=true; shift ;;
22763
22814
  --timeout)
22764
22815
  shift
@@ -22814,6 +22865,29 @@ cmd_review() {
22814
22865
  return $?
22815
22866
  fi
22816
22867
 
22868
+ if [ "$review_risk" = true ]; then
22869
+ if [ -z "$review_pr" ] && [ -z "$review_since" ] && [[ "$review_target" =~ ^[0-9]+$ ]] && [ ! -e "$review_target" ]; then
22870
+ review_pr="$review_target"
22871
+ fi
22872
+ local risk_diff risk_src="uncommitted changes"
22873
+ if [ -n "$review_pr" ]; then
22874
+ command -v gh &>/dev/null || { echo "Error: gh CLI required for PR risk" >&2; return 1; }
22875
+ risk_diff=$(gh pr diff "$review_pr" 2>&1) || { echo "Error: Could not fetch PR #${review_pr}: $risk_diff" >&2; return 1; }
22876
+ risk_src="PR #${review_pr}"
22877
+ elif [ -n "$review_since" ]; then
22878
+ risk_diff=$(git diff "$review_since" 2>&1) || { echo "Error: Invalid commit reference: $review_since" >&2; return 1; }
22879
+ risk_src="changes since $review_since"
22880
+ elif [ "$review_staged" = true ]; then
22881
+ risk_diff=$(git diff --cached 2>/dev/null); risk_src="staged changes"
22882
+ else
22883
+ risk_diff=$(git diff 2>/dev/null)
22884
+ fi
22885
+ local risk_args=()
22886
+ [ "$review_format" = "json" ] && risk_args+=(--json)
22887
+ printf '%s\n' "$risk_diff" | python3 "$_LOKI_SCRIPT_DIR/lib/review_risk.py" ${risk_args[@]+"${risk_args[@]}"} "$risk_src"
22888
+ return $?
22889
+ fi
22890
+
22817
22891
  # Severity level ordering (higher number = more severe)
22818
22892
  _review_sev_level() {
22819
22893
  case "$1" in
@@ -26294,6 +26368,93 @@ SHOW_EOF
26294
26368
  esac
26295
26369
  }
26296
26370
 
26371
+ # loki merge: serial PR merge queue. Queue lives in .loki/merge-queue (one PR
26372
+ # number per line). A PR merges only when `gh pr checks` exits 0 (all green);
26373
+ # after each merge the next PR is rebased onto the new base (gh pr update-branch
26374
+ # --rebase) and must go green again before it merges. Never forces anything.
26375
+ cmd_merge() {
26376
+ local sub="${1:-help}"
26377
+ shift 2>/dev/null || true
26378
+ local qfile=".loki/merge-queue"
26379
+ case "$sub" in
26380
+ add)
26381
+ [ $# -gt 0 ] || { echo "Usage: loki merge add <pr-number>..." >&2; return 2; }
26382
+ mkdir -p .loki || return 1
26383
+ local n
26384
+ for n in "$@"; do
26385
+ case "$n" in ''|*[!0-9]*) echo "Invalid PR number: $n" >&2; return 2 ;; esac
26386
+ if [ -f "$qfile" ] && grep -qx "$n" "$qfile"; then
26387
+ echo "PR #$n already queued"
26388
+ else
26389
+ echo "$n" >> "$qfile"
26390
+ echo "Queued PR #$n"
26391
+ fi
26392
+ done
26393
+ ;;
26394
+ list|ls)
26395
+ if [ -s "$qfile" ]; then cat "$qfile" | sed 's/^/#/'; else echo "Merge queue is empty"; fi
26396
+ ;;
26397
+ clear)
26398
+ rm -f "$qfile"; echo "Merge queue cleared"
26399
+ ;;
26400
+ run)
26401
+ local dry=false
26402
+ [ "${1:-}" = "--dry-run" ] && dry=true
26403
+ if [ ! -s "$qfile" ]; then echo "Merge queue is empty"; return 0; fi
26404
+ command -v gh >/dev/null 2>&1 || { echo "gh CLI is required" >&2; return 1; }
26405
+ local poll="${LOKI_MERGE_POLL_S:-20}" tries="${LOKI_MERGE_MAX_POLLS:-90}"
26406
+ local first=true n rc i remaining="" merged=0 blocked=0
26407
+ while IFS= read -r n; do
26408
+ [ -n "$n" ] || continue
26409
+ if $dry; then
26410
+ rc=0; gh pr checks "$n" >/dev/null 2>&1 || rc=$?
26411
+ case "$rc" in
26412
+ 0) echo "#$n: checks green, would merge (squash)" ;;
26413
+ 8) echo "#$n: checks pending, would wait" ;;
26414
+ *) echo "#$n: checks not green, would skip" ;;
26415
+ esac
26416
+ $first || echo "#$n: would rebase onto new base first"
26417
+ first=false
26418
+ continue
26419
+ fi
26420
+ if ! $first; then
26421
+ gh pr update-branch "$n" --rebase >/dev/null 2>&1 \
26422
+ || { echo "#$n: rebase onto new base failed, left in queue"; remaining="$remaining$n"$'\n'; blocked=$((blocked+1)); continue; }
26423
+ echo "#$n: rebased onto new base"
26424
+ fi
26425
+ rc=8; i=0
26426
+ while [ "$rc" = "8" ] && [ "$i" -lt "$tries" ]; do
26427
+ rc=0; gh pr checks "$n" >/dev/null 2>&1 || rc=$?
26428
+ [ "$rc" = "8" ] && { sleep "$poll"; i=$((i+1)); }
26429
+ done
26430
+ if [ "$rc" != "0" ]; then
26431
+ echo "#$n: checks not green (rc=$rc), left in queue, not merged"
26432
+ remaining="$remaining$n"$'\n'; blocked=$((blocked+1)); continue
26433
+ fi
26434
+ if gh pr merge "$n" --squash >/dev/null 2>&1; then
26435
+ echo "#$n: merged"; merged=$((merged+1)); first=false
26436
+ else
26437
+ echo "#$n: merge failed, left in queue"
26438
+ remaining="$remaining$n"$'\n'; blocked=$((blocked+1))
26439
+ fi
26440
+ done < "$qfile"
26441
+ if $dry; then echo "Dry run: nothing changed"; return 0; fi
26442
+ printf '%s' "$remaining" > "$qfile"
26443
+ echo "Merged: $merged, left in queue: $blocked"
26444
+ [ "$blocked" -eq 0 ]
26445
+ ;;
26446
+ help|--help|-h|*)
26447
+ echo "Usage: loki merge <add|list|run|clear>"
26448
+ echo " add <pr>... Queue pull requests"
26449
+ echo " list Show the queue"
26450
+ echo " run [--dry-run] Merge one at a time, only when checks are green;"
26451
+ echo " each next PR is rebased onto the new base first."
26452
+ echo " --dry-run lists what would happen, changes nothing."
26453
+ [ "$sub" = "help" ] || [ "$sub" = "--help" ] || [ "$sub" = "-h" ] || return 2
26454
+ ;;
26455
+ esac
26456
+ }
26457
+
26297
26458
  # R6: one-command rollback. Top-level, obvious entry point that mirrors the Bun
26298
26459
  # `loki rollback` subcommands (list/show/to/latest) so both routes are at parity.
26299
26460
  # Restore is destructive on .loki/ state, so it ALWAYS captures a forced
@@ -38109,18 +38270,28 @@ cmd_docker() {
38109
38270
  # the port-in-use check, so the picked port is ignored on reuse). Run in
38110
38271
  # a subshell so its internal `exit` paths (already-running -> exit 0,
38111
38272
  # errors -> exit 1) never abort the blocking docker run.
38112
- local _dash_port="57374"
38113
- if declare -F loki_docker_pick_host_port >/dev/null 2>&1; then
38114
- _dash_port="$(loki_docker_pick_host_port 2>/dev/null || echo 57374)"
38115
- fi
38116
- ( cmd_dashboard_start --port "$_dash_port" ) || true
38273
+ # CP-LEGACY (D57): open the Control Plane, never the classic server,
38274
+ # unless LOKI_LEGACY_DASHBOARD=1 opts back in. Same open gate as before.
38275
+ if [ "${LOKI_LEGACY_DASHBOARD:-0}" != "1" ]; then
38276
+ local _cp_args=()
38277
+ if [ ! -t 1 ] || [ "$_docker_bg" = "1" ] || [ "${LOKI_NO_AUTO_OPEN:-0}" = "1" ]; then
38278
+ _cp_args=(--no-open)
38279
+ fi
38280
+ ( _loki_control_ui ${_cp_args[@]+"${_cp_args[@]}"} ) >&2 || true
38281
+ else
38282
+ local _dash_port="57374"
38283
+ if declare -F loki_docker_pick_host_port >/dev/null 2>&1; then
38284
+ _dash_port="$(loki_docker_pick_host_port 2>/dev/null || echo 57374)"
38285
+ fi
38286
+ ( cmd_dashboard_start --port "$_dash_port" ) || true
38117
38287
 
38118
- # Auto-open the dashboard in the browser, gated EXACTLY like
38119
- # run.sh:~10088: a TTY on stdout, not background, and not opted out via
38120
- # LOKI_NO_AUTO_OPEN=1. cmd_dashboard_open reads the persisted port file,
38121
- # so the URL is correct even when an existing dashboard was reused.
38122
- if [ -t 1 ] && [ "$_docker_bg" != "1" ] && [ "${LOKI_NO_AUTO_OPEN:-0}" != "1" ]; then
38123
- ( cmd_dashboard_open ) || true
38288
+ # Auto-open the dashboard in the browser, gated EXACTLY like
38289
+ # run.sh:~10088: a TTY on stdout, not background, and not opted out via
38290
+ # LOKI_NO_AUTO_OPEN=1. cmd_dashboard_open reads the persisted port file,
38291
+ # so the URL is correct even when an existing dashboard was reused.
38292
+ if [ -t 1 ] && [ "$_docker_bg" != "1" ] && [ "${LOKI_NO_AUTO_OPEN:-0}" != "1" ]; then
38293
+ ( cmd_dashboard_open ) || true
38294
+ fi
38124
38295
  fi
38125
38296
 
38126
38297
  # F4 FIX-DOCKER-STOP (write side): record this run's container/state so
@@ -38139,8 +38310,27 @@ cmd_docker() {
38139
38310
  # its live status from the bind-mounted .loki/session.json. Best-effort and
38140
38311
  # failure-swallowed: registry bookkeeping must never block a build.
38141
38312
  _loki_docker_register_host running
38142
- "${docker_argv[@]}"
38143
- local rc=$?
38313
+ local rc _errlog=""
38314
+ # Capture output for a failure summary only when no TTY is attached (a TTY
38315
+ # run already shows the live output and must not be piped).
38316
+ if [ ! -t 1 ] || [ ! -t 0 ]; then
38317
+ _errlog="$(mktemp "${TMPDIR:-/tmp}/loki-docker-out.XXXXXX" 2>/dev/null)" || _errlog=""
38318
+ fi
38319
+ if [ -n "$_errlog" ]; then
38320
+ "${docker_argv[@]}" 2>&1 | tee "$_errlog"
38321
+ rc=${PIPESTATUS[0]}
38322
+ else
38323
+ "${docker_argv[@]}"
38324
+ rc=$?
38325
+ fi
38326
+ if [ "$rc" -ne 0 ]; then
38327
+ local _last=""
38328
+ [ -n "$_errlog" ] && _last="$(grep -v '^[[:space:]]*$' "$_errlog" 2>/dev/null | tail -n 1)"
38329
+ echo -e "${RED}loki docker: container exited with code ${rc}.${NC}" >&2
38330
+ [ -n "$_last" ] && echo "Last error: ${_last}" >&2
38331
+ echo "Fix hint: for PRs set GH_TOKEN=\$(gh auth token), and ensure ANTHROPIC_API_KEY or a Claude login is available, then rerun." >&2
38332
+ fi
38333
+ [ -n "$_errlog" ] && rm -f "$_errlog" 2>/dev/null
38144
38334
  _loki_docker_register_host stopped
38145
38335
  # F4 FIX-DOCKER-STOP (clear side): runs unconditionally after the blocking
38146
38336
  # call (not gated on rc), same as register_host stopped.
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env bash
2
+ # Mobile emulator tests: run a mobile project's tests on an available emulator
3
+ # or simulator. Never reports PASS without a run.
4
+ # Usage: mobile-verify.sh [project_dir]
5
+ # Env: LOKI_MOBILE_TEST_CMD override the test command
6
+ # LOKI_MOBILE_TIMEOUT seconds (default 600)
7
+ # Exit: 0 = ran and passed, or not a mobile project (stated)
8
+ # 1 = ran and failed
9
+ # 3 = NOT VERIFIED (no emulator/simulator, or no runnable test command)
10
+ set -u
11
+ dir="${1:-.}"
12
+ cd "$dir" || { echo "mobile tests: NOT VERIFIED (cannot enter $dir)"; exit 3; }
13
+
14
+ kind=""
15
+ if [ -f pubspec.yaml ] && grep -q "flutter" pubspec.yaml 2>/dev/null; then
16
+ kind="flutter"
17
+ elif [ -f package.json ] && grep -qE '"(expo|react-native)"[[:space:]]*:' package.json 2>/dev/null; then
18
+ kind="react-native"
19
+ fi
20
+ if [ -z "$kind" ]; then
21
+ echo "mobile tests: not applicable (no React Native, Expo or Flutter project)"
22
+ exit 0
23
+ fi
24
+
25
+ device=""
26
+ if command -v adb >/dev/null 2>&1; then
27
+ d="$(adb devices 2>/dev/null | awk 'NR>1 && $2=="device" {print $1; exit}')"
28
+ [ -n "$d" ] && device="android:$d"
29
+ fi
30
+ if [ -z "$device" ] && command -v xcrun >/dev/null 2>&1; then
31
+ d="$(xcrun simctl list devices booted 2>/dev/null | awk '/\(Booted\)/ {print "ios-simulator"; exit}')"
32
+ [ -n "$d" ] && device="$d"
33
+ fi
34
+ if [ -z "$device" ]; then
35
+ echo "mobile tests: NOT VERIFIED ($kind project, no booted Android emulator or iOS simulator found; tests were not run)"
36
+ exit 3
37
+ fi
38
+
39
+ cmd="${LOKI_MOBILE_TEST_CMD:-}"
40
+ if [ -z "$cmd" ]; then
41
+ if [ "$kind" = "flutter" ]; then
42
+ if [ -d integration_test ]; then cmd="flutter test integration_test"; else cmd="flutter test"; fi
43
+ elif grep -q '"test:e2e"' package.json 2>/dev/null; then
44
+ cmd="npm run test:e2e"
45
+ elif grep -q '"e2e"' package.json 2>/dev/null; then
46
+ cmd="npm run e2e"
47
+ fi
48
+ fi
49
+ if [ -z "$cmd" ]; then
50
+ echo "mobile tests: NOT VERIFIED ($kind project on $device, but no e2e test script (test:e2e or e2e) to run)"
51
+ exit 3
52
+ fi
53
+
54
+ timeout_s="${LOKI_MOBILE_TIMEOUT:-600}"
55
+ if command -v timeout >/dev/null 2>&1; then
56
+ timeout -k 10 "$timeout_s" bash -c "$cmd"
57
+ else
58
+ bash -c "$cmd"
59
+ fi
60
+ rc=$?
61
+ if [ "$rc" -eq 0 ]; then
62
+ echo "mobile tests: PASS ($kind on $device, ran: $cmd)"
63
+ exit 0
64
+ fi
65
+ echo "mobile tests: FAIL ($kind on $device, ran: $cmd, exit $rc)"
66
+ exit 1
package/autonomy/run.sh CHANGED
@@ -11804,6 +11804,26 @@ result = {
11804
11804
  "successes": filter_relevant(successes, context)
11805
11805
  }
11806
11806
 
11807
+ # Project memory: learnings and decisions persisted by earlier runs in THIS
11808
+ # project, newest first, bounded by entry count and total characters.
11809
+ if os.environ.get("LOKI_PROJECT_MEMORY", "1") != "0":
11810
+ try:
11811
+ max_chars = int(os.environ.get("LOKI_PROJECT_MEMORY_MAX_CHARS", "2000"))
11812
+ except ValueError:
11813
+ max_chars = 2000
11814
+ mem, used = [], 0
11815
+ for kind in ("mistakes", "patterns", "successes"):
11816
+ for e in reversed(load_jsonl(f".loki/memory/learnings/project-{kind}.jsonl")):
11817
+ d = e.get("description", "").strip()[:300]
11818
+ if not d or used + len(d) > max_chars:
11819
+ continue
11820
+ used += len(d)
11821
+ mem.append({"kind": kind[:-1] if kind != "successes" else "success", "description": d})
11822
+ if len(mem) >= 15:
11823
+ break
11824
+ if mem:
11825
+ result["project_memory"] = mem
11826
+
11807
11827
  with open(".loki/state/relevant-learnings.json", 'w') as f:
11808
11828
  json.dump(result, f, indent=2)
11809
11829
  LEARNINGS_SCRIPT
@@ -11863,7 +11883,23 @@ def get_existing_hashes(filepath):
11863
11883
  return hashes
11864
11884
 
11865
11885
  def save_entries(filepath, entries, category):
11866
- """Save entries avoiding duplicates (case-insensitive)"""
11886
+ """Save entries avoiding duplicates (case-insensitive).
11887
+
11888
+ Also mirrors into the project-scoped memory (.loki/memory/learnings/
11889
+ project-<kind>.jsonl) so the next run in this project reads them back.
11890
+ """
11891
+ if os.environ.get("LOKI_PROJECT_MEMORY", "1") != "0":
11892
+ try:
11893
+ pdir = ".loki/memory/learnings"
11894
+ os.makedirs(pdir, exist_ok=True)
11895
+ pfile = os.path.join(pdir, "project-" + os.path.basename(filepath))
11896
+ if pfile != filepath:
11897
+ save_entries_to(pfile, entries, category)
11898
+ except Exception:
11899
+ pass
11900
+ return save_entries_to(filepath, entries, category)
11901
+
11902
+ def save_entries_to(filepath, entries, category):
11867
11903
  existing = get_existing_hashes(filepath)
11868
11904
  saved = 0
11869
11905
  with open(filepath, 'a') as f:
@@ -19522,8 +19558,14 @@ start_dashboard() {
19522
19558
  # Auto-open the dashboard in the browser, but ONLY for an interactive
19523
19559
  # foreground session. loki_open_url (lib/browser-open.sh) refuses on
19524
19560
  # no TTY, CI, test runners, LOKI_NO_BROWSER=1 or LOKI_NO_AUTO_OPEN=1.
19561
+ # CP-LEGACY (D57): open the Control Plane, not this legacy server,
19562
+ # unless LOKI_LEGACY_DASHBOARD=1 opts back in.
19525
19563
  if [ "${BACKGROUND_MODE:-false}" != "true" ]; then
19526
- loki_open_url "${url_scheme}://127.0.0.1:$DASHBOARD_PORT/" 2>/dev/null || true
19564
+ if [ "${LOKI_LEGACY_DASHBOARD:-0}" = "1" ]; then
19565
+ loki_open_url "${url_scheme}://127.0.0.1:$DASHBOARD_PORT/" 2>/dev/null || true
19566
+ elif [ -t 1 ] && [ "${LOKI_NO_AUTO_OPEN:-0}" != "1" ] && [ -x "${skill_dir}/autonomy/loki" ]; then
19567
+ LOKI_UI_NO_CLASSIC=1 "${skill_dir}/autonomy/loki" ui >/dev/null 2>&1 || true
19568
+ fi
19527
19569
  fi
19528
19570
  return 0
19529
19571
  else
package/completions/_loki CHANGED
@@ -188,6 +188,8 @@ function _loki_commands {
188
188
  'deploy:Advisory deploy command (print-only)'
189
189
  'docker:Run Loki in a Docker container'
190
190
  'mcp:MCP server commands'
191
+ 'merge:Green-only serial merge queue'
192
+ 'acp:Agent Client Protocol agent over stdio'
191
193
  'magic:Spec-driven component generation'
192
194
  'assets:Export/import shareable team assets'
193
195
  'spec:Living spec drift detection'
@@ -5,7 +5,7 @@ _loki_completion() {
5
5
  _init_completion || return
6
6
 
7
7
  # Main subcommands (must match autonomy/loki main case statement)
8
- local main_commands="start backlog quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue intent config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt outcomes explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash control workspace open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help keys answer"
8
+ local main_commands="start backlog quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue intent config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt outcomes explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash control workspace open otel preview quickstart rc rollback merge self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help keys answer acp"
9
9
 
10
10
  # 1. If we are on the first argument (subcommand)
11
11
  if [[ $cword -eq 1 ]]; then
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "10.6.14"
10
+ __version__ = "10.7.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -0,0 +1,80 @@
1
+ """REST API for runs: /api/v1/runs.
2
+
3
+ A thin, stable surface over code that already exists:
4
+ GET /api/v1/runs -> api_runs.list_runs (envelope kept intact)
5
+ GET /api/v1/runs/{id} -> api_runs.get_run (404 when the run is unknown)
6
+ POST /api/v1/runs -> the existing /api/control/start handler
7
+ POST /api/v1/runs/{id}/stop -> the existing /api/control/stop handler,
8
+ only when {id} is the current run
9
+
10
+ Auth is the dashboard's own scope check: "read" for GETs, "control" for POSTs.
11
+ The dashboard binds loopback by default; nothing here opens a listener.
12
+ """
13
+ import os
14
+ from typing import Optional
15
+
16
+ from fastapi import APIRouter, Depends, HTTPException, Request
17
+ from pydantic import BaseModel, Field
18
+
19
+ from . import auth
20
+
21
+ router = APIRouter(prefix="/api/v1/runs", tags=["runs-v1"])
22
+ _READ = [Depends(auth.require_scope("read"))]
23
+ _CONTROL = [Depends(auth.require_scope("control"))]
24
+
25
+
26
+ def _loki_dir() -> str:
27
+ try:
28
+ from . import server
29
+ return str(server._get_loki_dir())
30
+ except Exception:
31
+ return os.environ.get("LOKI_DIR") or os.path.join(os.getcwd(), ".loki")
32
+
33
+
34
+ class RunStartIn(BaseModel):
35
+ prd_text: Optional[str] = Field(default=None, max_length=1_048_576)
36
+ prd_path: Optional[str] = None
37
+ provider: str = "claude"
38
+ parallel: bool = False
39
+
40
+
41
+ async def _start_impl(request: Request, body: RunStartIn):
42
+ from . import server
43
+ return await server.start_build(request, server.StartBuildRequest(**body.model_dump()))
44
+
45
+
46
+ async def _stop_impl(request: Request):
47
+ from . import server
48
+ return await server.stop_session(request)
49
+
50
+
51
+ @router.get("", dependencies=_READ)
52
+ def list_runs_v1():
53
+ from . import api_runs
54
+ return api_runs.list_runs(_loki_dir())
55
+
56
+
57
+ @router.get("/{run_id}", dependencies=_READ)
58
+ def get_run_v1(run_id: str):
59
+ from . import api_runs
60
+ env = api_runs.get_run(_loki_dir(), run_id)
61
+ if env.get("run") is None:
62
+ raise HTTPException(status_code=404, detail=env.get("reason") or "run not found")
63
+ return env
64
+
65
+
66
+ @router.post("", dependencies=_CONTROL)
67
+ async def start_run_v1(request: Request, body: RunStartIn):
68
+ return await _start_impl(request, body)
69
+
70
+
71
+ @router.post("/{run_id}/stop", dependencies=_CONTROL)
72
+ async def stop_run_v1(run_id: str, request: Request):
73
+ from . import api_runs
74
+ env = api_runs.get_run(_loki_dir(), run_id)
75
+ row = env.get("run")
76
+ if row is None:
77
+ raise HTTPException(status_code=404, detail=env.get("reason") or "run not found")
78
+ if not row.get("current"):
79
+ raise HTTPException(status_code=409, detail="only the current run can be stopped")
80
+ return await _stop_impl(request)
@@ -1362,6 +1362,8 @@ else:
1362
1362
  # D51 Phase A: first-run onboarding + backlog API and its /start page.
1363
1363
  from .api_start import router as api_start_router, START_HTML as _START_HTML
1364
1364
  app.include_router(api_start_router)
1365
+ from .api_runs_v1 import router as api_runs_v1_router
1366
+ app.include_router(api_runs_v1_router)
1365
1367
 
1366
1368
 
1367
1369
  @app.get("/start", include_in_schema=False, dependencies=[Depends(auth.require_scope("read"))])
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v10.6.14
5
+ **Version:** v10.7.0
6
6
 
7
7
  **Engine note:** `loki "<task>"`, `loki owner/repo#N` and `loki quick` run the Loki 10 engine. `loki start` still routes to the legacy engine, which is being removed (planned work resumes 2026-10-07); prefer `loki owner/repo#N`. See [docs/v10/GUIDE.md](v10/GUIDE.md).
8
8
 
@@ -434,11 +434,15 @@ runs any loki command inside the published `asklokesh/loki-mode` image with zero
434
434
  config:
435
435
 
436
436
  ```bash
437
+ loki docker start owner/repo#17 # work a GitHub issue or PR in Docker (primary path)
437
438
  loki docker start prd.md # full local experience in Docker
438
439
  loki docker status # any loki command works
439
440
  loki docker --dry-run start prd.md # print the docker command, do not run
440
441
  ```
441
442
 
443
+ On macOS, pass your GitHub login for PRs with
444
+ `GH_TOKEN=$(gh auth token) loki docker start owner/repo#17`.
445
+
442
446
  It bind-mounts the current folder to `/workspace`, so `.loki/` state (memory,
443
447
  session, queue, checkpoints) persists on the host and resume and continuity
444
448
  behave exactly like the local `loki` CLI. Auth is auto-detected:
@@ -0,0 +1,29 @@
1
+ # Loki in VS Code and JetBrains via ACP
2
+
3
+ `loki acp` speaks the Agent Client Protocol (newline-delimited JSON-RPC over
4
+ stdio). It supports `initialize`, `session/new`, `session/prompt` and
5
+ `session/cancel`. Each prompt runs `loki quick "<prompt>"` in the session's
6
+ working directory and streams the output back. A non-zero exit is reported as
7
+ "NOT VERIFIED"; nothing is claimed that did not run. Loki never receives editor
8
+ credentials, and no network listener is opened.
9
+
10
+ ## JetBrains (AI Assistant with ACP support)
11
+
12
+ Edit `~/.jetbrains/acp.json`:
13
+
14
+ {"agent_servers": {"Loki": {"command": "loki", "args": ["acp"]}}}
15
+
16
+ ## Zed
17
+
18
+ In settings.json:
19
+
20
+ {"agent_servers": {"Loki": {"command": "loki", "args": ["acp"]}}}
21
+
22
+ ## VS Code
23
+
24
+ Use any ACP client extension and register a custom agent with command `loki`
25
+ and args `acp`.
26
+
27
+ ## Smoke test
28
+
29
+ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1}}' | loki acp