loki-mode 11.88.0 → 11.90.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 (76) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/lib/backlog.py +9 -0
  4. package/autonomy/lib/claude-flags.sh +13 -0
  5. package/autonomy/loki +71 -1
  6. package/autonomy/route.sh +56 -2
  7. package/autonomy/run.sh +20 -7
  8. package/bin/loki +3 -1
  9. package/dashboard/__init__.py +1 -1
  10. package/docs/CLI-REFERENCE.md +1 -1
  11. package/docs/INSTALLATION.md +1 -1
  12. package/docs/dev/release-checklist.md +13 -0
  13. package/docs/v11/VERIFY-PR-SANDBOX.md +2 -0
  14. package/loki-ts/dist/loki.js +1480 -1447
  15. package/mcp/__init__.py +1 -1
  16. package/package.json +2 -2
  17. package/packages/control-plane/dist/server.js +1693 -1238
  18. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  19. package/providers/claude.sh +3 -2
  20. package/web-app/dist/assets/{AdminPage-DGFYz7jP.js → AdminPage-CZL0cONl.js} +1 -1
  21. package/web-app/dist/assets/{Avatar-DimAK0qS.js → Avatar-tJZxSP21.js} +1 -1
  22. package/web-app/dist/assets/{Badge-CtVHUWPT.js → Badge-B-uFJM_y.js} +1 -1
  23. package/web-app/dist/assets/{Button-B6GOqLuT.js → Button-DfKU7ctr.js} +1 -1
  24. package/web-app/dist/assets/{CockpitPage--Ua8KoOS.js → CockpitPage-CRNp09jN.js} +1 -1
  25. package/web-app/dist/assets/{ComparePage-BtMraAUs.js → ComparePage-Cdvjcs57.js} +1 -1
  26. package/web-app/dist/assets/{ErrorBoundary-CA8nmpsN.js → ErrorBoundary-C_benNK3.js} +1 -1
  27. package/web-app/dist/assets/{EvidenceReceiptPanel-Dj0HmQVK.js → EvidenceReceiptPanel-Ch_6YkUO.js} +1 -1
  28. package/web-app/dist/assets/{GitHubIssuesPanel-Crb9rp_b.js → GitHubIssuesPanel-C_a7mAlw.js} +1 -1
  29. package/web-app/dist/assets/{GitHubPRsPanel-DhFPYxS7.js → GitHubPRsPanel-xMr7IKsr.js} +1 -1
  30. package/web-app/dist/assets/{HomePage-C51QMxKa.js → HomePage-BVe6s1kz.js} +2 -2
  31. package/web-app/dist/assets/{LoginPage-CcjKShUz.js → LoginPage-DKlLaNw0.js} +1 -1
  32. package/web-app/dist/assets/{MagicPage-DYvCJMyL.js → MagicPage-StmxQunX.js} +1 -1
  33. package/web-app/dist/assets/{MetricsPage-C7NfdivW.js → MetricsPage-C7e2CioT.js} +1 -1
  34. package/web-app/dist/assets/{NotFoundPage-D_i8KOHP.js → NotFoundPage-B2IopV0V.js} +1 -1
  35. package/web-app/dist/assets/{ProjectPage-BnX-bdrE.js → ProjectPage-CXs8d6Cx.js} +3 -3
  36. package/web-app/dist/assets/{ProjectsPage-Dw9vd8nh.js → ProjectsPage-BfgMJYYj.js} +1 -1
  37. package/web-app/dist/assets/{SettingsPage-BiMlvct4.js → SettingsPage-CZ8lwCF2.js} +1 -1
  38. package/web-app/dist/assets/{ShowcasePage-CTicKPSd.js → ShowcasePage-iNCfKZj0.js} +1 -1
  39. package/web-app/dist/assets/{SystemSettingsPage-CCaBdNUV.js → SystemSettingsPage-FMV2pNNz.js} +1 -1
  40. package/web-app/dist/assets/{TeamsPage-ZgHxhez9.js → TeamsPage-D_SzTjuD.js} +1 -1
  41. package/web-app/dist/assets/{TemplatesPage-CdabY5xG.js → TemplatesPage-BMF_6X7R.js} +1 -1
  42. package/web-app/dist/assets/{TerminalOutput-BnEw4Cel.js → TerminalOutput-ClZtwhW8.js} +1 -1
  43. package/web-app/dist/assets/{activity-CMam860z.js → activity-DSOGDSlj.js} +1 -1
  44. package/web-app/dist/assets/{bell-znbIfqyN.js → bell-BQaMS_Ge.js} +1 -1
  45. package/web-app/dist/assets/{bot-BEvnLIzv.js → bot-Iw0qLbSN.js} +1 -1
  46. package/web-app/dist/assets/{check-7vhpsKmE.js → check-BNNpt5tE.js} +1 -1
  47. package/web-app/dist/assets/{chevron-left-DclMmWdO.js → chevron-left-DDFdacve.js} +1 -1
  48. package/web-app/dist/assets/{circle-alert-DTrBplJz.js → circle-alert-CiRTbi7c.js} +1 -1
  49. package/web-app/dist/assets/{clock-B_C913Rx.js → clock-hudCD7-Z.js} +1 -1
  50. package/web-app/dist/assets/{cloud-CPXqqekD.js → cloud-Brn55LA3.js} +1 -1
  51. package/web-app/dist/assets/{code-xml-BcFkh38Q.js → code-xml-kiS4rISP.js} +1 -1
  52. package/web-app/dist/assets/{database-wb6mQZnf.js → database-BD1l1Tuw.js} +1 -1
  53. package/web-app/dist/assets/{dollar-sign-dD_UVJpH.js → dollar-sign-DT5e3PIo.js} +1 -1
  54. package/web-app/dist/assets/{file-code-corner-BNeg4iZA.js → file-code-corner-DmixJzQ5.js} +1 -1
  55. package/web-app/dist/assets/{file-plus-CUcAf2PD.js → file-plus-CeQieVbR.js} +1 -1
  56. package/web-app/dist/assets/{globe-3rj7nR-A.js → globe-BayFHork.js} +1 -1
  57. package/web-app/dist/assets/{hammer-DP5N9S57.js → hammer-BzJ_7lDH.js} +1 -1
  58. package/web-app/dist/assets/{index-BplYtTNn.js → index-DgDlNUmy.js} +3 -3
  59. package/web-app/dist/assets/{layers-DDhFYiTv.js → layers-CfWES4J2.js} +1 -1
  60. package/web-app/dist/assets/{loader-circle-BbsRrghq.js → loader-circle-CH0UbcAO.js} +1 -1
  61. package/web-app/dist/assets/{lock-COw30Y3Z.js → lock-BuKglb_x.js} +1 -1
  62. package/web-app/dist/assets/{package-BYq-PY2J.js → package-BkpU_Qb_.js} +1 -1
  63. package/web-app/dist/assets/{plus-7NF-59Ud.js → plus-BFMNyc0G.js} +1 -1
  64. package/web-app/dist/assets/{refresh-cw-CPAgqFOB.js → refresh-cw-DQnQVKNC.js} +1 -1
  65. package/web-app/dist/assets/{rotate-ccw-Cvy3Bma-.js → rotate-ccw-B_M9AdjB.js} +1 -1
  66. package/web-app/dist/assets/{scroll-text-CKOo3ri8.js → scroll-text-1P39rc_L.js} +1 -1
  67. package/web-app/dist/assets/{server-DexVW09l.js → server-BjGwVEsb.js} +1 -1
  68. package/web-app/dist/assets/{shield-alert-Cof-u0b5.js → shield-alert-CKcYjHu8.js} +1 -1
  69. package/web-app/dist/assets/{thumbs-up-CErphzWx.js → thumbs-up-BC_5mpCw.js} +1 -1
  70. package/web-app/dist/assets/{trash-2-CO-IQ0pS.js → trash-2-CjtQl9fg.js} +1 -1
  71. package/web-app/dist/assets/{trending-down-CeSfSLgm.js → trending-down-D9MnFNOD.js} +1 -1
  72. package/web-app/dist/assets/{trending-up-DY6hK6l9.js → trending-up-DrHz29bk.js} +1 -1
  73. package/web-app/dist/assets/{upload-B11VKMdc.js → upload-D7jmahPb.js} +1 -1
  74. package/web-app/dist/assets/{usePolling-Dn_yhEG9.js → usePolling-C-zxuzqz.js} +1 -1
  75. package/web-app/dist/assets/{user-DHXJr_nV.js → user-DE2Mkf9B.js} +1 -1
  76. package/web-app/dist/index.html +1 -1
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Runs an autonomous software factory that knows what it is supposed to deliver and proves it did, with a signed receipt. Use when the user says Loki Mode or asks to build, fix or verify software autonomously.
4
4
  ---
5
5
 
6
- # Loki Mode v11.88.0
6
+ # Loki Mode v11.90.0
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
 
@@ -475,4 +475,4 @@ Implemented, planned and deprecated items, with the version each landed in: `ski
475
475
 
476
476
  ---
477
477
 
478
- **v11.88.0 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
478
+ **v11.90.0 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 11.88.0
1
+ 11.90.0
@@ -378,6 +378,15 @@ def main(argv):
378
378
  tmp = dst + ".tmp%d" % os.getpid()
379
379
  shutil.copyfile(src, tmp)
380
380
  os.replace(tmp, dst)
381
+ # The sealed Wall copies travel with the receipt: integrate reruns them on the merged tree after the worktree is gone.
382
+ wsrc, wdst = os.path.join(src_dir, "wall"), os.path.join(dst_dir, "wall")
383
+ if os.path.isdir(wsrc) and not os.path.islink(wsrc) and not os.path.islink(wdst):
384
+ shutil.rmtree(wdst, ignore_errors=True)
385
+ os.makedirs(wdst, mode=0o700, exist_ok=True)
386
+ for name in os.listdir(wsrc):
387
+ f = os.path.join(wsrc, name)
388
+ if os.path.isfile(f) and not os.path.islink(f):
389
+ shutil.copyfile(f, os.path.join(wdst, name))
381
390
  except OSError:
382
391
  pass
383
392
 
@@ -1174,3 +1174,16 @@ _loki_claude_permission_argv() {
1174
1174
  _LOKI_PERM_ARGV=()
1175
1175
  while IFS= read -r _a; do _LOKI_PERM_ARGV+=("$_a"); done <<< "$_out"
1176
1176
  }
1177
+
1178
+ # SHELL value for a claude child. Claude Code's Bash tool replays the aliases of
1179
+ # the shell named in SHELL, so a user's `alias ls=eza` hangs agent Bash commands.
1180
+ # Pin /bin/bash when executable (same rule as the engine10 route,
1181
+ # loki-ts/src/runner/worker_proc.ts pinBashShellEnv); LOKI_E10_KEEP_USER_SHELL=1
1182
+ # keeps the user's shell. Only SHELL changes; claude spawn sites only.
1183
+ _loki_claude_shell_value() {
1184
+ if [ "${LOKI_E10_KEEP_USER_SHELL:-}" != "1" ] && [ -x /bin/bash ]; then
1185
+ printf '%s' "/bin/bash"
1186
+ else
1187
+ printf '%s' "${SHELL-}"
1188
+ fi
1189
+ }
package/autonomy/loki CHANGED
@@ -28170,13 +28170,75 @@ _pr_record_merge() {
28170
28170
  return 0
28171
28171
  }
28172
28172
 
28173
+ # Owner opt-in earned merge (LOKI_EARNED_MERGE=1, off by default). Every path
28174
+ # the PR changes (the new name and, for a rename, the previous name, listed by
28175
+ # paginated REST and required to match the PR's changedFiles count) must have a
28176
+ # Seal record from `loki trust --json` (path_seals) with at least
28177
+ # LOKI_EARNED_MERGE_MIN_VERIFIED (default 20, floor 1) VERIFIED receipts and
28178
+ # exactly 0 reverts in git history. A revert count of null (history
28179
+ # unreadable), a missing record, an unreadable trust output or an unreadable or
28180
+ # truncated file list or an unreadable diff all refuse, as does any PR whose
28181
+ # diff touches a symlink (120000) or submodule (160000) mode. Protected paths (.github/, .loki/, .env*,
28182
+ # .envrc, CODEOWNERS, plus the space-separated globs in
28183
+ # LOKI_EARNED_MERGE_PROTECTED), matched case-insensitively, always stay
28184
+ # PR-only. The reason for a refusal goes to stdout; the return code is 1.
28185
+ _pr_earned_merge_check() {
28186
+ local pr="$1" min="${LOKI_EARNED_MERGE_MIN_VERIFIED:-20}" meta="" num="" want="" lines="" seals=""
28187
+ local kind p lp g row v r got=0 globs=() diff="" bad_mode=""
28188
+ case "$min" in ''|*[!0-9]*) min=20 ;; esac
28189
+ [ "$min" -ge 1 ] || min=1
28190
+ command -v jq >/dev/null 2>&1 || { echo "jq is required to read the Seal record"; return 1; }
28191
+ meta="$(gh pr view "$pr" --json number,changedFiles -q '"\(.number) \(.changedFiles)"' 2>/dev/null)" || meta=""
28192
+ num="${meta%% *}"; want="${meta#* }"
28193
+ case "$num$want" in ''|*[!0-9]*) echo "the PR's changed files are unreadable"; return 1 ;; esac
28194
+ lines="$(gh api --paginate "repos/{owner}/{repo}/pulls/${num}/files" -q '.[] | ("F\t" + .filename), (select(.previous_filename != null) | "P\t" + .previous_filename)' 2>/dev/null)" || lines=""
28195
+ [ -n "$lines" ] || { echo "the PR's changed files are unreadable"; return 1; }
28196
+ seals="$("$_LOKI_SCRIPT_DIR/../bin/loki" trust --json 2>/dev/null)" || seals=""
28197
+ printf '%s' "$seals" | jq -e '.path_seals | type == "array"' >/dev/null 2>&1 || { echo "the Seal record is unreadable"; return 1; }
28198
+ # read -a splits on whitespace without expanding globs against the cwd.
28199
+ read -r -a globs <<EOF_GLOBS
28200
+ $(printf '%s' "${LOKI_EARNED_MERGE_PROTECTED:-}" | tr '[:upper:]' '[:lower:]')
28201
+ EOF_GLOBS
28202
+ while IFS="$(printf '\t')" read -r kind p; do
28203
+ [ -n "$p" ] || continue
28204
+ [ "$kind" = "F" ] && got=$((got + 1))
28205
+ lp="$(printf '%s' "$p" | tr '[:upper:]' '[:lower:]')"
28206
+ case "$lp" in .github/*|.loki/*|.env|.env.*|*/.env|*/.env.*|.envrc|*/.envrc|codeowners|docs/codeowners)
28207
+ echo "$p is a protected path"; return 1 ;;
28208
+ esac
28209
+ for g in ${globs[@]+"${globs[@]}"}; do
28210
+ # shellcheck disable=SC2254
28211
+ case "$lp" in $g) echo "$p is a protected path"; return 1 ;; esac
28212
+ done
28213
+ row="$(printf '%s' "$seals" | jq -r --arg p "$p" '[.path_seals[] | select(.path == $p)] | if length == 0 then "none" else "\(.[0].verified) \(.[0].reverts)" end')" || row="none"
28214
+ [ "$row" != "none" ] || { echo "$p has no Seal record"; return 1; }
28215
+ v="${row%% *}"; r="${row#* }"
28216
+ case "$r" in
28217
+ 0) ;;
28218
+ null) echo "$p revert status is unknown"; return 1 ;;
28219
+ *) echo "$p has $r recorded revert(s)"; return 1 ;;
28220
+ esac
28221
+ [ "$v" -ge "$min" ] 2>/dev/null || { echo "$p has $v VERIFIED receipts, need $min"; return 1; }
28222
+ done <<EOF_FILES
28223
+ $lines
28224
+ EOF_FILES
28225
+ [ "$got" = "$want" ] || { echo "the PR lists $want changed files but $got were readable"; return 1; }
28226
+ # A symlink (120000) or submodule (160000) can point an earned path anywhere:
28227
+ # refuse any PR whose diff adds, removes, changes or re-modes one.
28228
+ diff="$(gh pr diff "$pr" 2>/dev/null)" || diff=""
28229
+ [ -n "$diff" ] || { echo "the PR's file modes are unreadable"; return 1; }
28230
+ bad_mode="$(printf '%s\n' "$diff" | awk '/^diff --git /{h=$0} /^(new file mode|deleted file mode|old mode|new mode) (120000|160000)$/{print h; exit} /^index [0-9a-f.]+ (120000|160000)$/{print h; exit}')"
28231
+ [ -z "$bad_mode" ] || { echo "${bad_mode#diff --git } involves a symlink or submodule"; return 1; }
28232
+ return 0
28233
+ }
28234
+
28173
28235
  # Shared merge gate. One PR (number or branch) merges only when it is not a
28174
28236
  # draft, `gh pr checks` exits 0 after polling pending (rc=8), and, with
28175
28237
  # --require-verified, every clean proof recorded for the PR's head commit
28176
28238
  # (facts.git.head_sha == headRefOid, dirty-tree proofs ignored) says VERIFIED; a stale or missing proof
28177
28239
  # refuses ("gate: head"). The merge passes --match-head-commit so a push after
28178
28240
  # the gate cannot land unjudged. On refusal it prints the exact blocking gate
28179
- # ("gate: draft|head|verdict|checks") and leaves the PR open.
28241
+ # ("gate: draft|head|verdict|trust|checks") and leaves the PR open.
28180
28242
  # Usage: _merge_pr_gated <pr> [--require-verified] [--delete-branch]
28181
28243
  _merge_pr_gated() {
28182
28244
  local pr="$1" need_verified=false margs=(--squash) rc d
@@ -28252,6 +28314,14 @@ _merge_pr_gated() {
28252
28314
  return 1
28253
28315
  fi
28254
28316
  fi
28317
+ if [ "${LOKI_EARNED_MERGE:-0}" = "1" ]; then
28318
+ local trust_why=""
28319
+ trust_why="$(_pr_earned_merge_check "$pr")" || {
28320
+ echo "$pr: not merged, gate: trust ($trust_why); PR left open" >&2
28321
+ _pr_record_merge "$pr" "blocked:trust" "trust"
28322
+ return 1
28323
+ }
28324
+ fi
28255
28325
  rc=0; _pr_checks_wait "$pr" || rc=$?
28256
28326
  if [ "$rc" != "0" ]; then
28257
28327
  echo "$pr: not merged, gate: checks (gh pr checks rc=$rc); PR left open" >&2
package/autonomy/route.sh CHANGED
@@ -60,9 +60,60 @@ _route_json_str() {
60
60
  printf '%s' "$1" | tr '\n\t\r' ' ' | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
61
61
  }
62
62
 
63
+ # Echoes the canonical https://github.com/owner/repo/pull/N for a github.com pull request URL
64
+ # (host case-insensitive, optional www., http or https, optional /files style suffix); echoes nothing otherwise.
65
+ # The owner/repo shape mirrors parsePrRef in loki-ts/src/commands/verify_pr.ts.
66
+ _route_pr_url() {
67
+ local re='^[Hh][Tt][Tt][Pp][Ss]?://([Ww][Ww][Ww]\.)?[Gg][Ii][Tt][Hh][Uu][Bb]\.[Cc][Oo][Mm]/([A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*)/pull/([0-9]+)([/?#].*)?$'
68
+ if [[ "$1" =~ $re ]]; then printf 'https://github.com/%s/pull/%s' "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}"; fi
69
+ }
70
+
71
+ # `loki start <PR URL> [--json] [--out DIR] [--no-pr]` is the verify-pr command, whatever the flag order.
72
+ # Returns 1 when no argument is a PR URL (the caller carries on). Otherwise sets R_* (R_PR_ARGS = the verify-pr argv)
73
+ # and returns 0; an unsupported flag or a second input sets R_ERR.
74
+ # shellcheck disable=SC2034
75
+ _route_pr_mode() {
76
+ local a pr="" bad="" badref="" extra="" i=0 n=$# u
77
+ local -a argv=("$@") eargs=()
78
+ for u in "$@"; do
79
+ case "$u" in --help|-h) return 1 ;; esac
80
+ done
81
+ while [ "$i" -lt "$n" ]; do
82
+ a="${argv[$i]}"; i=$((i + 1))
83
+ case "$a" in
84
+ --explain-route) R_EXPLAIN=1 ;;
85
+ --json) eargs+=(--json); R_JSON=1 ;;
86
+ --no-pr) ;;
87
+ --out) eargs+=(--out "${argv[$i]:-}"); i=$((i + 1)) ;;
88
+ --out=*) eargs+=(--out "${a#--out=}") ;;
89
+ -*) [ -n "$bad" ] || bad="${a%%=*}" ;;
90
+ *)
91
+ u="$(_route_pr_url "$a")"
92
+ # PR-shaped but invalid (a ".." in owner/repo): refused, like parsePrRef in verify_pr.ts.
93
+ if [ -n "$u" ]; then case "${u#https://github.com/}" in *..*) [ -n "$badref" ] || badref="$a" ;; esac; fi
94
+ if [ -n "$u" ] && [ -z "$pr" ]; then pr="$u"; else extra="${extra:+$extra }$a"; fi ;;
95
+ esac
96
+ done
97
+ [ -n "$pr" ] || return 1
98
+ R_DECIDED=1; R_INPUT_TYPE="pr"; R_INPUT="$pr"; R_ENGINE="verify-pr"; R_ADAPTER="verify-pr"
99
+ R_PROVIDER="n/a"; R_MODEL="n/a (no model call)"
100
+ R_REASON="pull request URL: verify-pr seals it; no plan or implement stage, nothing is pushed"
101
+ if [ -n "$badref" ]; then
102
+ R_ERR="loki: start <PR url>: not a valid pull request URL: $badref"
103
+ elif [ -n "$bad" ]; then
104
+ R_ERR="loki: start <PR url>: flag $bad is not supported (verify-pr takes --out DIR, --json and --no-pr)"
105
+ elif [ -n "$extra" ]; then
106
+ R_ERR="loki: start <PR url> takes one input; also got: $extra"
107
+ else
108
+ R_PR_ARGS=("$pr" ${eargs[@]+"${eargs[@]}"})
109
+ fi
110
+ return 0
111
+ }
112
+
63
113
  _route_classify() {
64
- # $1 = single positional. Echoes: issue | prd | task | brief | missing-prd
114
+ # $1 = single positional. Echoes: pr | issue | prd | task | brief | missing-prd
65
115
  local p="$1"
116
+ if [ -n "$(_route_pr_url "$p")" ]; then echo pr; return 0; fi
66
117
  case "$p" in
67
118
  */*\#[0-9]*|http*://*/issues/*|http*://*/browse/*|jira:*|linear:*|https://linear.app/*/issue/*) echo issue; return 0 ;;
68
119
  esac
@@ -83,7 +134,8 @@ loki_route_resolve() {
83
134
  local verb="$1"; shift
84
135
  R_DECIDED=0; R_E10=0; R_HARD=0; R_ENGINE=""; R_REASON=""; R_INPUT_TYPE=""; R_INPUT=""
85
136
  R_PROVIDER=""; R_MODEL=""; R_NOTICE=""; R_ERR=""; R_EXPLAIN=0; R_JSON=0; R_SESSION_MODEL=""; R_VERB=""
86
- R_E10_ARGS=(); R_CLEAN=(); R_ADAPTER=""; R_CAPDIFF=(); R_E10_CAP_S=""; R_DRY=0; R_MAXCOST=""
137
+ R_E10_ARGS=(); R_CLEAN=(); R_ADAPTER=""; R_CAPDIFF=(); R_E10_CAP_S=""; R_DRY=0; R_MAXCOST=""; R_PR_ARGS=()
138
+ if [ "$verb" = "start" ] && _route_pr_mode "$@"; then return 0; fi
87
139
 
88
140
  local a engine_req="" dry=0 help=0 legacy_flag="" session_model="" provider_flag="" max_duration="" cap_err="" input_flag=""
89
141
  local -a positionals=() passthru=()
@@ -171,6 +223,8 @@ loki_route_resolve() {
171
223
  R_INPUT="(from --spec)"
172
224
  elif [ "$npos" -eq 1 ]; then
173
225
  R_INPUT_TYPE=$(_route_classify "${positionals[0]}")
226
+ # Only `loki start` sends a PR URL to verify-pr (_route_pr_mode); on any other verb engine10 runs it as a brief.
227
+ if [ "$R_INPUT_TYPE" = "pr" ]; then R_INPUT_TYPE=brief; fi
174
228
  R_INPUT="${positionals[0]}"
175
229
  else
176
230
  R_INPUT_TYPE=task
package/autonomy/run.sh CHANGED
@@ -6874,12 +6874,12 @@ spawn_worktree_session() {
6874
6874
  _LOKI_PERM_ARGV=(--dangerously-skip-permissions)
6875
6875
  ! type _loki_claude_permission_argv >/dev/null 2>&1 || _loki_claude_permission_argv
6876
6876
  if [ -n "$_loki_wt_cm" ]; then
6877
- CAVEMAN_DEFAULT_MODE="$_loki_wt_cm" claude "${_LOKI_PERM_ARGV[@]}" \
6877
+ SHELL="$(_loki_claude_shell_value)" CAVEMAN_DEFAULT_MODE="$_loki_wt_cm" claude "${_LOKI_PERM_ARGV[@]}" \
6878
6878
  "${_LOKI_WT_AUTO_FLAGS[@]+"${_LOKI_WT_AUTO_FLAGS[@]}"}" \
6879
6879
  -p "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
6880
6880
  >> "$log_file" 2>&1 || _wt_exit=$?
6881
6881
  else
6882
- claude "${_LOKI_PERM_ARGV[@]}" \
6882
+ SHELL="$(_loki_claude_shell_value)" claude "${_LOKI_PERM_ARGV[@]}" \
6883
6883
  "${_LOKI_WT_AUTO_FLAGS[@]+"${_LOKI_WT_AUTO_FLAGS[@]}"}" \
6884
6884
  -p "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
6885
6885
  >> "$log_file" 2>&1 || _wt_exit=$?
@@ -7124,7 +7124,7 @@ Output ONLY the resolved file content with no conflict markers. No explanations.
7124
7124
  # the EXACT resolved file content (the shell writes it verbatim).
7125
7125
  # Compressing prose into the merged source would corrupt the file,
7126
7126
  # so disable caveman unconditionally here. No-op when absent.
7127
- resolution=$(CAVEMAN_DEFAULT_MODE=off claude "${_cr_argv[@]}" -p "$conflict_prompt" --output-format text < /dev/null 2>/dev/null)
7127
+ resolution=$(SHELL="$(_loki_claude_shell_value)" CAVEMAN_DEFAULT_MODE=off claude "${_cr_argv[@]}" -p "$conflict_prompt" --output-format text < /dev/null 2>/dev/null)
7128
7128
  ;;
7129
7129
  codex)
7130
7130
  resolution=$(codex exec --sandbox workspace-write --skip-git-repo-check "$conflict_prompt" 2>/dev/null)
@@ -16821,7 +16821,7 @@ _dispatch_reviewer() {
16821
16821
  }
16822
16822
  fi
16823
16823
  _loki_with_deadline_stdin_text "$_cr_req_cap" "$prompt_text" \
16824
- "${_cr_provider_env[@]}" CAVEMAN_DEFAULT_MODE=off \
16824
+ "${_cr_provider_env[@]}" "SHELL=$(_loki_claude_shell_value)" CAVEMAN_DEFAULT_MODE=off \
16825
16825
  claude "${_rv_argv[@]}" -p \
16826
16826
  --output-format text > "$_cr_req_raw" \
16827
16827
  || _cr_req_rc=$?
@@ -16872,7 +16872,7 @@ _dispatch_reviewer() {
16872
16872
  _cr_cap=$(_loki_review_deadline_remaining "$_review_deadline_ms") || return 124
16873
16873
  fi
16874
16874
  _cr_json="$(_loki_with_deadline_stdin_text "$_cr_cap" "$prompt_text" \
16875
- "${_cr_provider_env[@]}" CAVEMAN_DEFAULT_MODE=off \
16875
+ "${_cr_provider_env[@]}" "SHELL=$(_loki_claude_shell_value)" CAVEMAN_DEFAULT_MODE=off \
16876
16876
  claude "${_rv_argv[@]}" -p \
16877
16877
  --json-schema "$_cr_schema_content" \
16878
16878
  --output-format json)" || _cr_rc=$?
@@ -16902,7 +16902,7 @@ _dispatch_reviewer() {
16902
16902
  _cr_text_cap=$(_loki_review_deadline_remaining "$_review_deadline_ms") || return 124
16903
16903
  fi
16904
16904
  _loki_with_deadline_stdin_text "$_cr_text_cap" "$prompt_text" \
16905
- "${_cr_provider_env[@]}" CAVEMAN_DEFAULT_MODE=off claude "${_rv_argv[@]}" -p \
16905
+ "${_cr_provider_env[@]}" "SHELL=$(_loki_claude_shell_value)" CAVEMAN_DEFAULT_MODE=off claude "${_rv_argv[@]}" -p \
16906
16906
  --output-format text > "$review_output"
16907
16907
  ;;
16908
16908
  codex)
@@ -21774,9 +21774,20 @@ ${_commits}"
21774
21774
  # would fail that check or produce caveman-style USAGE text, so disable
21775
21775
  # caveman unconditionally. Inlined on `claude` only (does not cross the pipe
21776
21776
  # to head). No-op when caveman is absent.
21777
+ # A non-claude provider with LOKI_ALLOW_CLAUDE_SIDECALLS=1 never loads
21778
+ # claude.sh, so compute the pinned shell in a subshell: the helper's
21779
+ # top-level exports (caveman vars) must not leak into this process.
21780
+ local _ic_shell
21781
+ _ic_shell="$(
21782
+ # shellcheck source=/dev/null
21783
+ type _loki_claude_shell_value >/dev/null 2>&1 \
21784
+ || . "$PROJECT_DIR/autonomy/lib/claude-flags.sh" >/dev/null 2>&1 || exit 1
21785
+ _loki_claude_shell_value
21786
+ )" || _ic_shell=""
21787
+ [ -n "$_ic_shell" ] || _ic_shell="${SHELL-}"
21777
21788
  local _ic_out
21778
21789
  _ic_out=$(printf '%s' "$_ic_prompt" \
21779
- | timeout 60 env CAVEMAN_DEFAULT_MODE=off claude "${_ic_argv[@]}" -p - 2>/dev/null \
21790
+ | timeout 60 env "SHELL=$_ic_shell" CAVEMAN_DEFAULT_MODE=off claude "${_ic_argv[@]}" -p - 2>/dev/null \
21780
21791
  | head -200)
21781
21792
  # Sanity check: response must look like Markdown (starts with # or ##).
21782
21793
  if [ -z "$_ic_out" ] || ! printf '%s' "$_ic_out" | head -1 | grep -qE '^#'; then
@@ -26174,11 +26185,13 @@ except Exception as exc:
26174
26185
  CAVEMAN_DEFAULT_MODE="$_loki_cm_level" \
26175
26186
  LOKI_DEADLINE_IDLE_TIMEOUT="${LOKI_PROVIDER_IDLE_TIMEOUT:-0}" \
26176
26187
  _loki_with_deadline "${LOKI_PROVIDER_CALL_TIMEOUT:-0}" \
26188
+ env "SHELL=$(_loki_claude_shell_value)" \
26177
26189
  claude "${_loki_claude_argv[@]}" -p "$prompt" \
26178
26190
  --output-format stream-json --verbose < /dev/null 2>&1
26179
26191
  else
26180
26192
  LOKI_DEADLINE_IDLE_TIMEOUT="${LOKI_PROVIDER_IDLE_TIMEOUT:-0}" \
26181
26193
  _loki_with_deadline "${LOKI_PROVIDER_CALL_TIMEOUT:-0}" \
26194
+ env "SHELL=$(_loki_claude_shell_value)" \
26182
26195
  claude "${_loki_claude_argv[@]}" -p "$prompt" \
26183
26196
  --output-format stream-json --verbose < /dev/null 2>&1
26184
26197
  fi | \
package/bin/loki CHANGED
@@ -643,7 +643,9 @@ if [ -f "$REPO_ROOT/autonomy/route.sh" ] && { [ "$_LOKI_START_ATTEMPTS" != "1" ]
643
643
  export LOKI_SESSION_MODEL="$R_SESSION_MODEL"
644
644
  fi
645
645
  if [ "$R_E10" = "1" ] && [ -n "${R_E10_CAP_S:-}" ]; then export LOKI_E10_CAP_S="$R_E10_CAP_S"; fi
646
- if [ "$R_E10" != "1" ]; then
646
+ if [ "$R_ENGINE" = "verify-pr" ]; then
647
+ set -- verify-pr "${R_PR_ARGS[@]}"
648
+ elif [ "$R_E10" != "1" ]; then
647
649
  set -- "$([ "$R_VERB" = "bare" ] && echo start || echo "$R_VERB")" ${R_CLEAN[@]+"${R_CLEAN[@]}"}
648
650
  fi
649
651
  fi
@@ -7,6 +7,6 @@ Modules:
7
7
  control: Session control helpers (status, atomic writes, origin check)
8
8
  """
9
9
 
10
- __version__ = "11.88.0"
10
+ __version__ = "11.90.0"
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.88.0. Do not edit by hand.
3
+ Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.90.0. Do not edit by hand.
4
4
  Hidden and legacy-only commands are omitted; see `docs/CLI-INVENTORY.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.88.0
5
+ **Version:** v11.90.0
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
 
@@ -12,6 +12,19 @@ refs/heads/main` matches `git rev-parse HEAD`. `scripts/local-ci.sh` remains
12
12
  available for diagnosis on a quiet cycle -- it is no longer a push
13
13
  precondition, fast tier or full.
14
14
 
15
+ Slice gate (run before reporting a slice built; a reviewer reruns the same command):
16
+ `bash scripts/select-tests.sh --gate [--suite tests/test-foo.sh]... [--no-loki-ts]`.
17
+ It checks that HEAD contains the current origin/main (and names how far behind
18
+ it is, never rebasing for you), runs exactly the `--guards-only` row set (the
19
+ `scripts/global-guards.tsv` rows plus the marker-only walkers, from one shared emitter) one
20
+ at a time and names any red row, runs each `--suite`, then runs full engine10
21
+ (`bun run test:engine10` in loki-ts, through the slot queue when the tree has that script) unless `--no-loki-ts` says the slice
22
+ touched no loki-ts source. Every row, suite and the engine10 run is bounded by
23
+ `timeout -k 15` (`LOKI_GATE_ROW_TIMEOUT`, `LOKI_GATE_SUITE_TIMEOUT`, `LOKI_GATE_ENGINE10_TIMEOUT`);
24
+ a timeout, an empty guard list, a failed fetch (unless `--no-fetch`) or a missing
25
+ `bun` fails the gate. It prints a pass/fail summary per step and exits
26
+ non-zero on any failure.
27
+
15
28
  After a release ships, run post-release distribution validation:
16
29
  - npm: `npm pack loki-mode@<VERSION>`, untar, run `bash package/bin/loki version`
17
30
  - Docker: `docker pull asklokesh/loki-mode:<VERSION>`, `docker run --rm <img> version`,
@@ -25,6 +25,7 @@ verify-pr-sandbox.sh --repo DIR --out DIR --cmd 'SHELL CMD' --image IMG [--timeo
25
25
  | Env scrub | `docker run` inherits no host env; only PATH, HOME=/work, CI=1 are passed. GH_TOKEN, GITHUB_TOKEN, SSH_AUTH_SOCK never reach argv |
26
26
  | Mounts | exactly one: the repo, read-only. No HOME, no docker socket, no output mount |
27
27
  | Output | stdout is captured by the host, capped in-container (`--max-output`); the host writes result.json and output.txt |
28
+ | Report | `--report /work/NAME` returns the file the check's runner wrote there (up to 1 MiB, outside the output cap) between nonce-bearing markers; the host writes it as report.txt. verify-pr runs bun test, pytest, jest and vitest with a report flag and a random path, and reads pass/fail only from that file |
28
29
 
29
30
  Deviation from the card: there is no mounted output dir. A writable host mount would let the checked code fill the host disk, so the result travels on stdout and only the host writes `OUT`. Numeric flags are forced to base 10 (a leading zero is not octal) and every value flag requires a value. `--image` is restricted to `[A-Za-z0-9._/:@-]`.
30
31
 
@@ -35,6 +36,7 @@ Deviation from the card: there is no mounted output dir. A writable host mount w
35
36
  - Oversized output: capped by `--max-output`; `output_truncated` is set.
36
37
  - Fork bomb: `--pids-limit 256`. Memory bomb: `--memory`. Disk fill: tmpfs size caps.
37
38
  - Forged result: the checked code can print a fake `LOKI_VPR_END` line. A per-run 128-bit nonce is sent to the wrapper on stdin and the checked command runs with stdin from /dev/null; the host accepts only the last line carrying that nonce. Docker's default seccomp profile does NOT block ptrace or process_vm_readv on kernels >= 4.8, and the checked command runs as the same uid as the wrapper inside the container, so a hostile process can read the wrapper's memory and recover the nonce. The nonce therefore only stops accidental or naive forgery; it is not a security boundary. One cross-check is added: the wrapper exits with the check's code and the host rejects (status ERROR) a sentinel that disagrees with docker's own exit code. The PR code can still make its own check report success, which is why `result.json` is a signal about the check and not the final verdict (VPR-2 owns that, and it must not treat a bare COMPLETED exit 0 as proof). A custom seccomp profile denying ptrace is a possible hardening and is not shipped here.
39
+ - Forged report: the report path is a per-run random name given only on the runner's command line, so printed totals cannot forge it. A hostile test that reads its parent's command line (or the nonce, see above) and rewrites the file after the runner exits can still forge a pass; the same ptrace-class residual applies.
38
40
  - Docker pool exhaustion: reported as BLOCKED, never as a failing check.
39
41
 
40
42
  ## Not covered (residual, tracked for VPR-2 review)