loki-mode 11.3.9 → 11.4.2

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 (100) hide show
  1. package/README.md +34 -18
  2. package/SKILL.md +3 -3
  3. package/VERSION +1 -1
  4. package/agents/custom_agents.py +280 -0
  5. package/autonomy/council-v2.sh +22 -1
  6. package/autonomy/edit.sh +242 -0
  7. package/autonomy/lib/chat.sh +312 -0
  8. package/autonomy/lib/help-core.sh +164 -0
  9. package/autonomy/lib/json-output.sh +38 -0
  10. package/autonomy/lib/run-summary.sh +96 -0
  11. package/autonomy/loki +105 -10
  12. package/autonomy/route.sh +270 -0
  13. package/autonomy/run.sh +6 -0
  14. package/autonomy/setup-wizard.sh +161 -0
  15. package/autonomy/verify.sh +22 -2
  16. package/bin/loki +73 -18
  17. package/completions/_loki +55 -0
  18. package/completions/loki.bash +49 -3
  19. package/dashboard/__init__.py +1 -1
  20. package/docs/CLI-CORE-VERBS.md +112 -0
  21. package/docs/CLI-REFERENCE.md +7 -1
  22. package/docs/COMPARE-OPENCODE.md +94 -0
  23. package/docs/COMPONENTS.md +1 -0
  24. package/docs/INSTALLATION.md +56 -12
  25. package/docs/KNOWN-ISSUES.md +2 -12
  26. package/docs/QUICKSTART.md +108 -0
  27. package/docs/environment-variables.md +9 -2
  28. package/docs/exit-codes.md +29 -12
  29. package/docs/v10/CLI-MODERN.md +10 -4
  30. package/docs/v10/GUIDE.md +270 -44
  31. package/docs/v10/OPENCODE-PARITY-PLAN.md +84 -0
  32. package/loki-ts/dist/loki.js +968 -933
  33. package/mcp/__init__.py +1 -1
  34. package/package.json +2 -2
  35. package/packages/control-plane/dist/server.js +180 -80
  36. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  37. package/providers/claude.sh +16 -2
  38. package/providers/model_route_exec.sh +22 -0
  39. package/providers/model_routes.sh +188 -0
  40. package/skills/providers.md +13 -0
  41. package/templates/agents/planner.md +10 -0
  42. package/templates/agents/reviewer.md +10 -0
  43. package/templates/agents/tester.md +10 -0
  44. package/web-app/dist/assets/{AdminPage-QK-i_x-O.js → AdminPage-CYJ2BWRC.js} +1 -1
  45. package/web-app/dist/assets/{Avatar-DblkLNjB.js → Avatar-DdK4wynI.js} +1 -1
  46. package/web-app/dist/assets/{Badge-DIV2ny5H.js → Badge-CrX3MI7J.js} +1 -1
  47. package/web-app/dist/assets/{Button--rePJOlQ.js → Button-709-wwGM.js} +1 -1
  48. package/web-app/dist/assets/{CockpitPage-BZlKxpGy.js → CockpitPage-BapdTInO.js} +1 -1
  49. package/web-app/dist/assets/{ComparePage-3_INSVux.js → ComparePage-DT99pGvA.js} +1 -1
  50. package/web-app/dist/assets/{ErrorBoundary-DHG63CNr.js → ErrorBoundary-B0uJSys8.js} +1 -1
  51. package/web-app/dist/assets/{EvidenceReceiptPanel-DFsBnLlO.js → EvidenceReceiptPanel-BtMFgElP.js} +1 -1
  52. package/web-app/dist/assets/{GitHubIssuesPanel-phPGah98.js → GitHubIssuesPanel-DjJuU941.js} +1 -1
  53. package/web-app/dist/assets/{GitHubPRsPanel-T7KQqJmx.js → GitHubPRsPanel-HXfEX1BC.js} +1 -1
  54. package/web-app/dist/assets/{HomePage-DFjGUF5I.js → HomePage-Cqyves-S.js} +2 -2
  55. package/web-app/dist/assets/{LoginPage-CorUid_5.js → LoginPage-jUbtC7_Y.js} +1 -1
  56. package/web-app/dist/assets/{MagicPage-DofbSxKD.js → MagicPage-DHRsqjgR.js} +1 -1
  57. package/web-app/dist/assets/{MetricsPage-qzrhhfLC.js → MetricsPage-B-pED05R.js} +1 -1
  58. package/web-app/dist/assets/{NotFoundPage-BAZR0mMb.js → NotFoundPage-CsbUs21R.js} +1 -1
  59. package/web-app/dist/assets/{ProjectPage-BOSUzG9J.js → ProjectPage-BtlLEQwB.js} +3 -3
  60. package/web-app/dist/assets/{ProjectsPage-Ba0YLtzD.js → ProjectsPage-DaRbRjSp.js} +1 -1
  61. package/web-app/dist/assets/{SettingsPage-Cr10Ue2K.js → SettingsPage-DALrtP3F.js} +1 -1
  62. package/web-app/dist/assets/{ShowcasePage-Czn655fe.js → ShowcasePage-CoPHSNP0.js} +1 -1
  63. package/web-app/dist/assets/{SystemSettingsPage-Dwjnw6Ob.js → SystemSettingsPage-kOuSN50O.js} +1 -1
  64. package/web-app/dist/assets/{TeamsPage-D8nc69Be.js → TeamsPage-DGWK0LMp.js} +1 -1
  65. package/web-app/dist/assets/{TemplatesPage-DHsaCDGv.js → TemplatesPage-C-_10CwA.js} +1 -1
  66. package/web-app/dist/assets/{TerminalOutput-DBejni2a.js → TerminalOutput-Cz_vPbXL.js} +1 -1
  67. package/web-app/dist/assets/{activity-B2VsBl1C.js → activity-icIsXJVa.js} +1 -1
  68. package/web-app/dist/assets/{bell-CT77Fq2H.js → bell-DbPR2_4r.js} +1 -1
  69. package/web-app/dist/assets/{bot-DfaWgF4H.js → bot-DvjP3Yh5.js} +1 -1
  70. package/web-app/dist/assets/{check-D1yp7Ndv.js → check-D1OBxHeU.js} +1 -1
  71. package/web-app/dist/assets/{chevron-left-B6Pa8IOX.js → chevron-left-D09jDZ29.js} +1 -1
  72. package/web-app/dist/assets/{circle-alert-D5jrL_Y7.js → circle-alert-DyZMLOcC.js} +1 -1
  73. package/web-app/dist/assets/{clock-DIsY_fa_.js → clock-CdpdJTId.js} +1 -1
  74. package/web-app/dist/assets/{cloud-eGwLh1As.js → cloud-CoLTlmoj.js} +1 -1
  75. package/web-app/dist/assets/{code-xml-CGDIcZ3m.js → code-xml-DrkE2ZZY.js} +1 -1
  76. package/web-app/dist/assets/{database-CWpdaykI.js → database-CMQQuM2R.js} +1 -1
  77. package/web-app/dist/assets/{dollar-sign-DZEuTCs6.js → dollar-sign-CuioDaOG.js} +1 -1
  78. package/web-app/dist/assets/{file-code-corner-BHBW6m9v.js → file-code-corner-Cx9j9K1S.js} +1 -1
  79. package/web-app/dist/assets/{file-plus-D4h1jZSR.js → file-plus-CYD2zaaI.js} +1 -1
  80. package/web-app/dist/assets/{globe-CXdqKgwd.js → globe-6w-hnQkL.js} +1 -1
  81. package/web-app/dist/assets/{hammer-B3YuuYI_.js → hammer-BpqtautL.js} +1 -1
  82. package/web-app/dist/assets/{index-BLEnboSv.js → index-Dzs-9EgJ.js} +3 -3
  83. package/web-app/dist/assets/{layers-Dvp5nFtw.js → layers-D5hd7jb7.js} +1 -1
  84. package/web-app/dist/assets/{loader-circle-DodDmIZ3.js → loader-circle-C6Y9x_7H.js} +1 -1
  85. package/web-app/dist/assets/{lock-EiuaH1Ep.js → lock-D51L7rYh.js} +1 -1
  86. package/web-app/dist/assets/{package-Cx4m2hFg.js → package-BJF8hkP8.js} +1 -1
  87. package/web-app/dist/assets/{plus-D-Sd5rpH.js → plus-D5kUQ79n.js} +1 -1
  88. package/web-app/dist/assets/{refresh-cw-C0w1cdmF.js → refresh-cw-BKdlCa7F.js} +1 -1
  89. package/web-app/dist/assets/{rotate-ccw-C_qk6MTT.js → rotate-ccw-_bd_YI1e.js} +1 -1
  90. package/web-app/dist/assets/{scroll-text-DSxiFtkc.js → scroll-text-DbkegIxF.js} +1 -1
  91. package/web-app/dist/assets/{server-NTJt7I8M.js → server-DAwqFIsq.js} +1 -1
  92. package/web-app/dist/assets/{shield-alert-HBxMQOvH.js → shield-alert-BuR1tPHu.js} +1 -1
  93. package/web-app/dist/assets/{thumbs-up-DEgsHzSD.js → thumbs-up-BF6p3mRd.js} +1 -1
  94. package/web-app/dist/assets/{trash-2-px46FnTD.js → trash-2-BVpnkqqz.js} +1 -1
  95. package/web-app/dist/assets/{trending-down-CBosVGKa.js → trending-down-DpsDvRwW.js} +1 -1
  96. package/web-app/dist/assets/{trending-up-Dbd7zrQj.js → trending-up-mFogTjt3.js} +1 -1
  97. package/web-app/dist/assets/{upload-nhdFKAs1.js → upload-4T8zpBsi.js} +1 -1
  98. package/web-app/dist/assets/{usePolling-BgaGdinh.js → usePolling-DPXQv8WO.js} +1 -1
  99. package/web-app/dist/assets/{user-CzIihUy9.js → user-BYGIFaTm.js} +1 -1
  100. package/web-app/dist/index.html +1 -1
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env bash
2
+ # One predictable execution path (OC-ROUTE).
3
+ # Sourced by bin/loki. Normalizes argv BEFORE the engine decision so argument
4
+ # order and input form never change the engine:
5
+ # engine10 is the default for a PRD file, an issue ref, a multi-word task and a
6
+ # one-word brief, whether the flags come first or last.
7
+ # The legacy engine runs only on --engine legacy / LOKI_ENGINE=legacy, or, for
8
+ # one specific flag engine10 cannot honor, for that call only (with a notice).
9
+ # Pure functions: no exec, no side effects except R_* globals and the printers.
10
+ # Bash 3.2 compatible (no associative arrays, no ${var,,}).
11
+
12
+ # Flags that take a separate value token.
13
+ _ROUTE_VALUE_FLAGS=" --provider --budget --budget-limit --max-cost --max-iterations --max-retries --session-model --completion-promise --base-wait --max-wait --prd --brief --attempts --spec --aider-model --aider-flags --cline-model --isolation --output --engine --mirofish-rounds --mirofish-timeout "
14
+ # Flags engine10 honors as-is.
15
+ _ROUTE_E10_FLAGS=" --no-pr --deep --json --verbose --provider --max-cost --spec --attempts "
16
+ # Accepted and dropped (no-ops on a non-interactive run).
17
+ _ROUTE_NOOP_FLAGS=" --yes -y --no-plan --no-mirofish --no-dashboard "
18
+
19
+ _route_has() { case "$1" in *" $2 "*) return 0 ;; esac; return 1; }
20
+
21
+ _route_json_str() {
22
+ printf '%s' "$1" | tr '\n\t\r' ' ' | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
23
+ }
24
+
25
+ _route_classify() {
26
+ # $1 = single positional. Echoes: issue | prd | task | brief | missing-prd
27
+ local p="$1"
28
+ case "$p" in
29
+ */*\#[0-9]*|http*://*/issues/*|http*://*/browse/*|jira:*|linear:*|https://linear.app/*/issue/*) echo issue; return 0 ;;
30
+ esac
31
+ if [ -f "$p" ]; then echo prd; return 0; fi
32
+ case "$p" in
33
+ *" "*) echo task; return 0 ;;
34
+ *.md|*.markdown|*.txt|*.json|*.yaml|*.yml) echo missing-prd; return 0 ;;
35
+ esac
36
+ echo brief
37
+ }
38
+
39
+ # loki_route_resolve VERB ARGS... VERB = start | quick | bare
40
+ # Sets: R_DECIDED R_E10 R_HARD R_ENGINE R_REASON R_INPUT_TYPE R_INPUT R_PROVIDER
41
+ # R_MODEL R_NOTICE R_ERR R_EXPLAIN R_JSON R_E10_ARGS[] R_CLEAN[] R_SESSION_MODEL R_VERB
42
+ # R_* are the result interface read by the sourcing caller.
43
+ # shellcheck disable=SC2034
44
+ loki_route_resolve() {
45
+ local verb="$1"; shift
46
+ R_DECIDED=0; R_E10=0; R_HARD=0; R_ENGINE=""; R_REASON=""; R_INPUT_TYPE=""; R_INPUT=""
47
+ R_PROVIDER=""; R_MODEL=""; R_NOTICE=""; R_ERR=""; R_EXPLAIN=0; R_JSON=0; R_SESSION_MODEL=""; R_VERB=""
48
+ R_E10_ARGS=(); R_CLEAN=()
49
+
50
+ local a engine_req="" help=0 legacy_flag="" session_model="" provider_flag=""
51
+ local -a positionals=() passthru=()
52
+ local i=0 n=$#
53
+ local -a argv=("$@")
54
+ while [ "$i" -lt "$n" ]; do
55
+ a="${argv[$i]}"
56
+ i=$((i + 1))
57
+ case "$a" in
58
+ --engine) engine_req="${argv[$i]:-}"; i=$((i + 1)); continue ;;
59
+ --engine=*) engine_req="${a#--engine=}"; continue ;;
60
+ --explain-route) R_EXPLAIN=1; continue ;;
61
+ --help|-h) help=1; R_CLEAN+=("$a"); continue ;;
62
+ --) R_CLEAN+=("$a"); while [ "$i" -lt "$n" ]; do positionals+=("${argv[$i]}"); R_CLEAN+=("${argv[$i]}"); i=$((i + 1)); done; break ;;
63
+ esac
64
+ R_CLEAN+=("$a")
65
+ case "$a" in
66
+ -*)
67
+ local name="${a%%=*}" val=""
68
+ if _route_has "$_ROUTE_VALUE_FLAGS" "$name"; then
69
+ if [ "$name" = "$a" ]; then
70
+ val="${argv[$i]:-}"; i=$((i + 1)); R_CLEAN+=("$val")
71
+ else
72
+ val="${a#*=}"
73
+ fi
74
+ fi
75
+ case "$name" in
76
+ --prd) positionals+=("$val"); continue ;;
77
+ --brief) positionals+=("$val"); continue ;;
78
+ --provider) provider_flag="$val"; passthru+=(--provider "$val"); continue ;;
79
+ --session-model) session_model="$val"; continue ;;
80
+ --budget|--budget-limit|--max-cost) passthru+=(--max-cost "$val"); continue ;;
81
+ --json) R_JSON=1; passthru+=(--json); continue ;;
82
+ -v) passthru+=(--verbose); continue ;;
83
+ --attempts) passthru+=(--attempts "$val"); continue ;;
84
+ --spec) passthru+=(--spec "$val"); continue ;;
85
+ esac
86
+ if _route_has "$_ROUTE_NOOP_FLAGS" "$name"; then continue; fi
87
+ if _route_has "$_ROUTE_E10_FLAGS" "$name"; then passthru+=("$name"); continue; fi
88
+ # Everything else is a flag engine10 does not honor.
89
+ if [ -z "$legacy_flag" ]; then legacy_flag="$name"; fi
90
+ ;;
91
+ *) positionals+=("$a") ;;
92
+ esac
93
+ done
94
+
95
+ if [ "$help" = "1" ]; then return 0; fi
96
+
97
+ # Engine request: flag wins over env.
98
+ if [ -z "$engine_req" ]; then engine_req="${LOKI_ENGINE:-}"; fi
99
+ engine_req=$(printf '%s' "$engine_req" | tr '[:upper:]' '[:lower:]')
100
+ case "$engine_req" in
101
+ ""|legacy) ;;
102
+ engine10|e10|v10|10) engine_req=engine10 ;;
103
+ *) R_ERR="loki: unknown engine '$engine_req' (use --engine legacy or --engine engine10)"; R_DECIDED=1; return 0 ;;
104
+ esac
105
+
106
+ # Input.
107
+ local npos=${#positionals[@]}
108
+ if [ "$npos" -eq 0 ]; then
109
+ R_INPUT_TYPE=none
110
+ local k=0 sp=0
111
+ while [ "$k" -lt "${#passthru[@]}" ]; do
112
+ if [ "${passthru[$k]}" = "--spec" ]; then sp=1; fi
113
+ k=$((k + 1))
114
+ done
115
+ # No positional and no --spec: not ours (bare `loki start` auto-detects a PRD).
116
+ if [ "$sp" = "0" ]; then R_INPUT_TYPE=""; return 0; fi
117
+ R_INPUT_TYPE=spec
118
+ R_INPUT="(from --spec)"
119
+ elif [ "$npos" -eq 1 ]; then
120
+ R_INPUT_TYPE=$(_route_classify "${positionals[0]}")
121
+ R_INPUT="${positionals[0]}"
122
+ else
123
+ R_INPUT_TYPE=task
124
+ R_INPUT="${positionals[*]}"
125
+ fi
126
+
127
+ # Bare flag-first form takes over only multi-word tasks and issue refs (a one-word
128
+ # first argument is the command namespace) and only when no unknown flag is present.
129
+ if [ "$verb" = "bare" ]; then
130
+ case "$R_INPUT_TYPE" in
131
+ task|issue) ;;
132
+ *) R_INPUT_TYPE=""; return 0 ;;
133
+ esac
134
+ if [ -n "$legacy_flag" ] && [ -z "$engine_req" ]; then R_INPUT_TYPE=""; return 0; fi
135
+ fi
136
+
137
+ R_DECIDED=1
138
+ if [ "$R_INPUT_TYPE" = "missing-prd" ]; then
139
+ R_ERR="loki: PRD file not found: ${positionals[0]}"
140
+ return 0
141
+ fi
142
+
143
+ # Provider and model (display only; engine10 resolves its own at run time).
144
+ R_PROVIDER="$provider_flag"
145
+ if [ -z "$R_PROVIDER" ]; then R_PROVIDER="${LOKI_PROVIDER:-}"; fi
146
+ if [ -z "$R_PROVIDER" ] && [ -f "${PWD}/.loki/state/provider" ]; then
147
+ R_PROVIDER="$(sed -n '1p' "${PWD}/.loki/state/provider" 2>/dev/null || true)"
148
+ fi
149
+ if [ -z "$R_PROVIDER" ]; then R_PROVIDER=claude; fi
150
+ if [ -n "$session_model" ]; then
151
+ R_MODEL="$session_model"
152
+ elif [ -n "${LOKI_SESSION_MODEL:-}" ]; then
153
+ R_MODEL="$LOKI_SESSION_MODEL"
154
+ elif [ -n "${LOKI_MODEL_ROUTE:-}" ]; then
155
+ R_MODEL="route: $LOKI_MODEL_ROUTE"
156
+ elif [ -n "${LOKI_MODEL_OVERRIDE:-}" ]; then
157
+ R_MODEL="$LOKI_MODEL_OVERRIDE"
158
+ elif [ "$R_PROVIDER" = "opencode" ] && [ -n "${LOKI_OPENCODE_MODEL:-}" ]; then
159
+ R_MODEL="$LOKI_OPENCODE_MODEL"
160
+ elif [ "$R_PROVIDER" = "claude" ]; then
161
+ R_MODEL="${LOKI_CLAUDE_MODEL_DEVELOPMENT:-${LOKI_MODEL_DEVELOPMENT:-claude (provider default)}}"
162
+ else
163
+ R_MODEL="$R_PROVIDER (model not recorded)"
164
+ fi
165
+ R_SESSION_MODEL="$session_model"
166
+
167
+ # Task text for engine10: a PRD file is read, anything else is the words.
168
+ local task
169
+ if [ "$R_INPUT_TYPE" = "prd" ]; then
170
+ task=$(cat "${positionals[0]}" 2>/dev/null || true)
171
+ elif [ "$R_INPUT_TYPE" = "spec" ]; then
172
+ task=""
173
+ else
174
+ task="${positionals[*]}"
175
+ fi
176
+ if [ "$verb" = "quick" ]; then R_E10_ARGS+=(--no-pr); fi
177
+ if [ -n "$task" ]; then R_E10_ARGS+=("$task"); fi
178
+ R_E10_ARGS+=("${passthru[@]+"${passthru[@]}"}")
179
+
180
+ local why=""
181
+ if [ "$engine_req" = "legacy" ]; then
182
+ R_ENGINE=legacy; R_REASON="explicit: --engine legacy or LOKI_ENGINE=legacy"
183
+ elif [ "$engine_req" = "engine10" ]; then
184
+ if [ -n "$legacy_flag" ]; then
185
+ R_ERR="loki: flag $legacy_flag is not supported by the engine10 engine; drop it or use --engine legacy"
186
+ return 0
187
+ fi
188
+ R_ENGINE=engine10; R_E10=1; R_HARD=1; R_REASON="explicit: --engine engine10"
189
+ elif [ -n "$legacy_flag" ]; then
190
+ R_ENGINE=legacy
191
+ R_REASON="flag $legacy_flag is not supported by engine10"
192
+ R_NOTICE="loki: routing this call to the legacy engine because of $legacy_flag (engine10 does not support it). Drop the flag to use engine10, or pass --engine legacy to silence this."
193
+ else
194
+ case "$R_PROVIDER" in
195
+ claude|codex|cline|aider|opencode) ;;
196
+ *) why="provider $R_PROVIDER has no engine10 invoker (use claude, codex, cline, aider or opencode)" ;;
197
+ esac
198
+ if [ -z "$why" ] && [ "$R_INPUT_TYPE" = "prd" ]; then
199
+ local sz
200
+ sz=$(wc -c < "${positionals[0]}" 2>/dev/null | tr -d ' ' || echo 0)
201
+ if [ "${sz:-0}" -gt 100000 ]; then why="PRD is larger than 100000 bytes (engine10 takes the task as one argument)"; fi
202
+ fi
203
+ if [ -n "$why" ]; then
204
+ R_ENGINE=legacy; R_REASON="$why"
205
+ R_NOTICE="loki: routing this call to the legacy engine: $why."
206
+ else
207
+ R_ENGINE=engine10; R_E10=1
208
+ R_REASON="default: $R_INPUT_TYPE input runs on engine10 regardless of flag order"
209
+ fi
210
+ fi
211
+ return 0
212
+ }
213
+
214
+ # Print the route (human or --json). Returns 0.
215
+ loki_route_print() {
216
+ local shown="$R_INPUT"
217
+ if [ "${#shown}" -gt 72 ]; then shown="$(printf '%s' "$shown" | cut -c1-69)..."; fi
218
+ if [ "$R_JSON" = "1" ]; then
219
+ printf '{"engine":"%s","provider":"%s","model":"%s","input_type":"%s","input":"%s","reason":"%s"}\n' \
220
+ "$(_route_json_str "$R_ENGINE")" "$(_route_json_str "$R_PROVIDER")" "$(_route_json_str "$R_MODEL")" \
221
+ "$(_route_json_str "$R_INPUT_TYPE")" "$(_route_json_str "$shown")" "$(_route_json_str "$R_REASON")"
222
+ else
223
+ printf 'engine: %s\nprovider: %s\nmodel: %s\ninput: %s (%s)\nreason: %s\n' \
224
+ "$R_ENGINE" "$R_PROVIDER" "$R_MODEL" "$R_INPUT_TYPE" "$shown" "$R_REASON"
225
+ fi
226
+ }
227
+
228
+ # Entry used by bin/loki. Args: the full original argv. Sets R_* and returns:
229
+ # 0 = handled (caller reads R_DECIDED); 7 = `loki route` / --explain-route printed (caller exits 0)
230
+ # 8 = usage or input error already printed (caller exits 2)
231
+ # shellcheck disable=SC2034
232
+ loki_route_dispatch() {
233
+ R_DECIDED=0
234
+ local verb="" first="${1:-}"
235
+ case "$first" in
236
+ route)
237
+ shift
238
+ case "${1:-}" in start|quick) verb="$1"; shift ;; *) verb=start ;; esac
239
+ loki_route_resolve "$verb" "$@"
240
+ if [ -n "$R_ERR" ]; then echo "$R_ERR" >&2; return 8; fi
241
+ if [ "$R_DECIDED" != "1" ]; then
242
+ echo "loki: usage: loki route [start|quick] <task | prd.md | owner/repo#N> [flags] [--json]" >&2; return 8
243
+ fi
244
+ loki_route_print; return 7
245
+ ;;
246
+ start|quick)
247
+ verb="$first"; shift
248
+ loki_route_resolve "$verb" "$@"
249
+ ;;
250
+ -*)
251
+ loki_route_resolve bare "$@"
252
+ verb=bare
253
+ ;;
254
+ *)
255
+ # Positional-first call: only LOKI_ENGINE needs the resolver here.
256
+ if [ -n "${LOKI_ENGINE:-}" ]; then
257
+ case "$first" in
258
+ *" "*|*/*\#[0-9]*|http*://*/issues/*)
259
+ loki_route_resolve bare "$@"; verb=bare ;;
260
+ esac
261
+ fi
262
+ ;;
263
+ esac
264
+ if [ "$R_DECIDED" != "1" ]; then return 0; fi
265
+ if [ -n "$R_ERR" ]; then echo "$R_ERR" >&2; return 8; fi
266
+ if [ "$R_EXPLAIN" = "1" ]; then loki_route_print; return 7; fi
267
+ if [ -n "$R_NOTICE" ]; then echo "$R_NOTICE" >&2; fi
268
+ R_VERB="$verb"
269
+ return 0
270
+ }
package/autonomy/run.sh CHANGED
@@ -29819,6 +29819,12 @@ except (json.JSONDecodeError, OSError): pass
29819
29819
  _loki_surface_why_hint || true
29820
29820
  fi
29821
29821
 
29822
+ # OC-OUTPUT: unified run summary (same format as engine10); best-effort, never changes the exit code.
29823
+ if [ -f "$SCRIPT_DIR/lib/run-summary.sh" ]; then
29824
+ # shellcheck source=lib/run-summary.sh
29825
+ source "$SCRIPT_DIR/lib/run-summary.sh" 2>/dev/null && loki_run_summary "$result" 2>/dev/null || true
29826
+ fi
29827
+
29822
29828
  exit $result
29823
29829
  }
29824
29830
 
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env bash
2
+ # setup-wizard.sh -- `loki setup`: the single first-run wizard.
3
+ # Sourced by autonomy/loki; relies on cmd_config_set and SKILL_DIR from that
4
+ # file. `loki welcome` (intro), `loki quickstart` (first build) and
5
+ # `loki doctor` (health) remain separate entry points.
6
+ #
7
+ # REQUIRED (doctor fails without them): node >= 18, python3 >= 3.8, jq, git, curl
8
+ # AT LEAST ONE of: a provider CLI (claude, codex, cline, aider, opencode) or the
9
+ # bundled Claude Agent SDK with credentials. Claude Code is the default.
10
+ # OPTIONAL: bun (bundled by npm), bash >= 4, codex/cline/aider/opencode, ollama.
11
+
12
+ _lsw_ask() {
13
+ # _lsw_ask PROMPT DEFAULT -> echoes the answer; DEFAULT when --yes or no TTY
14
+ local prompt="$1" def="$2" ans=""
15
+ if [ "$_LSW_YES" = "1" ] || [ ! -t 0 ]; then
16
+ printf '%s' "$def"
17
+ return 0
18
+ fi
19
+ printf '%s [%s]: ' "$prompt" "$def" >&2
20
+ read -r ans || ans=""
21
+ printf '%s' "${ans:-$def}"
22
+ }
23
+
24
+ _lsw_confirm() {
25
+ # _lsw_confirm PROMPT -> 0 for yes; --yes accepts, no TTY declines
26
+ local ans=""
27
+ [ "$_LSW_YES" = "1" ] && return 0
28
+ [ ! -t 0 ] && return 1
29
+ printf '%s [y/N] ' "$1" >&2
30
+ read -r ans || ans=""
31
+ case "$ans" in y|Y|yes|YES) return 0 ;; *) return 1 ;; esac
32
+ }
33
+
34
+ _lsw_tool() {
35
+ # _lsw_tool NAME LABEL INSTALL_HINT
36
+ if command -v "$1" >/dev/null 2>&1; then
37
+ printf ' [ OK ] %-10s %s\n' "$1" "$2"
38
+ return 0
39
+ fi
40
+ printf ' [MISS] %-10s %s. Install: %s\n' "$1" "$2" "$3"
41
+ [ "$2" = "REQUIRED" ] && _LSW_MISSING=$((_LSW_MISSING + 1))
42
+ return 1
43
+ }
44
+
45
+ _lsw_help() {
46
+ echo "loki setup - first-run wizard (tools, provider, model route, spend cap)"
47
+ echo ""
48
+ echo "Usage: loki setup [--yes] [--provider NAME] [--model ROUTE] [--budget USD] [--global] [--check]"
49
+ echo ""
50
+ echo " --yes, -y Non-interactive: accept defaults, never prompt"
51
+ echo " --provider NAME claude (default), codex, cline, aider, opencode"
52
+ echo " --model ROUTE claude (default), ollama/<model>, openrouter/<vendor>/<model>"
53
+ echo " --budget USD Spend cap in USD (default: none)"
54
+ echo " --global Write to ~/.config/loki-mode instead of .loki/config"
55
+ echo " --check Report only; change nothing"
56
+ echo ""
57
+ echo "Related: loki welcome (intro), loki quickstart (first build), loki doctor (health)."
58
+ }
59
+
60
+ cmd_setup() {
61
+ _LSW_YES=0
62
+ _LSW_MISSING=0
63
+ local provider="" model="" budget="" use_global="" check_only=0
64
+ while [ $# -gt 0 ]; do
65
+ case "$1" in
66
+ --yes|-y) _LSW_YES=1 ;;
67
+ --provider) provider="${2:-}"; shift ;;
68
+ --model) model="${2:-}"; shift ;;
69
+ --budget) budget="${2:-}"; shift ;;
70
+ --global) use_global="--global" ;;
71
+ --check) check_only=1 ;;
72
+ --help|-h) _lsw_help; return 0 ;;
73
+ *) echo "Unknown option: $1" >&2; _lsw_help >&2; return 2 ;;
74
+ esac
75
+ shift
76
+ done
77
+ [ -n "${CI:-}" ] && _LSW_YES=1
78
+
79
+ echo "Loki Mode setup"
80
+ echo ""
81
+ echo "1. Required tools"
82
+ _lsw_tool node REQUIRED "https://nodejs.org (Node 20+ recommended, 18 minimum)" || true
83
+ _lsw_tool python3 REQUIRED "brew install python3, or your package manager" || true
84
+ _lsw_tool jq REQUIRED "brew install jq, or your package manager" || true
85
+ _lsw_tool git REQUIRED "brew install git, or your package manager" || true
86
+ _lsw_tool curl REQUIRED "brew install curl, or your package manager" || true
87
+ _lsw_tool bun OPTIONAL "bundled by npm; else see https://bun.sh" || true
88
+ if [ "$_LSW_MISSING" -gt 0 ]; then
89
+ echo ""
90
+ echo "Install the missing REQUIRED tools above with your package manager (setup never installs system packages), then rerun: loki setup"
91
+ return 1
92
+ fi
93
+
94
+ echo ""
95
+ echo "2. Provider CLI (Claude Code is the default and the only Tier-1 provider)"
96
+ local p
97
+ for p in codex cline aider opencode; do
98
+ _lsw_tool "$p" OPTIONAL "see 'loki doctor' for the command" || true
99
+ done
100
+ if command -v claude >/dev/null 2>&1; then
101
+ printf ' [ OK ] %-10s DEFAULT\n' claude
102
+ if claude auth status 2>/dev/null | grep -q '"loggedIn"[[:space:]]*:[[:space:]]*true'; then
103
+ echo " claude is logged in."
104
+ else
105
+ echo " claude is installed but not logged in. Run: claude auth login"
106
+ if [ "$check_only" = 0 ] && _lsw_confirm "Run 'claude auth login' now?"; then
107
+ claude auth login || true
108
+ fi
109
+ fi
110
+ else
111
+ echo " [MISS] claude DEFAULT. Install: npm install -g @anthropic-ai/claude-code (then: claude auth login)"
112
+ if [ "$check_only" = 0 ] && [ -f "${SKILL_DIR}/autonomy/provider-offer.sh" ]; then
113
+ if _lsw_confirm "Run that install command now?"; then
114
+ # shellcheck disable=SC1091
115
+ . "${SKILL_DIR}/autonomy/provider-offer.sh"
116
+ LOKI_ASSUME_YES="$_LSW_YES" _po_do_install || true
117
+ fi
118
+ fi
119
+ if ! command -v claude >/dev/null 2>&1; then
120
+ echo " Claude Code is not mandatory if another provider above is installed,"
121
+ echo " or if the bundled Claude Agent SDK has credentials."
122
+ fi
123
+ fi
124
+
125
+ if [ "$check_only" = 1 ]; then
126
+ echo ""
127
+ echo "Check only: no config written."
128
+ return 0
129
+ fi
130
+
131
+ echo ""
132
+ echo "3. Provider and model route"
133
+ [ -z "$provider" ] && provider="$(_lsw_ask "Provider (claude|codex|cline|aider|opencode)" claude)"
134
+ [ -z "$model" ] && model="$(_lsw_ask "Model route (claude | ollama/<model> | openrouter/<vendor>/<model>)" claude)"
135
+ echo ""
136
+ echo "4. Spend cap"
137
+ [ -z "$budget" ] && budget="$(_lsw_ask "Spend cap in USD (blank for none)" "")"
138
+
139
+ echo ""
140
+ echo "5. Writing config"
141
+ # shellcheck disable=SC2086
142
+ cmd_config_set $use_global provider "$provider" || return 1
143
+ if [ -n "$budget" ]; then
144
+ # shellcheck disable=SC2086
145
+ cmd_config_set $use_global budget "$budget" || return 1
146
+ fi
147
+ case "$model" in
148
+ ""|claude) ;;
149
+ ollama/*)
150
+ echo " Model route: pass '--model $model' to loki start (Ollama must be running: ollama serve)."
151
+ ;;
152
+ openrouter/*)
153
+ echo " Model route: pass '--model $model' and export OPENROUTER_API_KEY (setup never stores keys)."
154
+ ;;
155
+ *) echo " Unrecognized route '$model'; using the provider default." ;;
156
+ esac
157
+
158
+ echo ""
159
+ echo "Done. Next: loki doctor (verify), loki quickstart (first build). Inspect: loki config list"
160
+ return 0
161
+ }
@@ -38,6 +38,8 @@
38
38
  # 1 CONCERNS
39
39
  # 2 BLOCKED
40
40
  # 3 verifier error (could not complete; never silently passes)
41
+ # 64 usage error (unknown option / unexpected argument)
42
+ # Not a git repo exits 2; an empty diff exits 3 (enforced from v11.4.0).
41
43
  #
42
44
  # EXIT-CODE ORDERING, RECONCILED. An early draft spec listed
43
45
  # 0=VERIFIED, 1=BLOCKED, 2=CONCERNS, and this header used to say a human must
@@ -66,6 +68,7 @@ VERIFY_EXIT_VERIFIED=0
66
68
  VERIFY_EXIT_CONCERNS=1
67
69
  VERIFY_EXIT_BLOCKED=2
68
70
  VERIFY_EXIT_ERROR=3
71
+ VERIFY_EXIT_USAGE=64
69
72
  VERIFY_SCHEMA_VERSION="1.0"
70
73
 
71
74
  # S-216: verify runs from the tree under review (tree="."), and a bare
@@ -355,8 +358,10 @@ verify_diff_base() {
355
358
  VERIFY_DIFF_INS=0
356
359
  VERIFY_DIFF_DEL=0
357
360
  VERIFY_DIFF_NAMES=""
361
+ VERIFY_NOT_GIT=0
358
362
 
359
363
  if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
364
+ VERIFY_NOT_GIT=1
360
365
  _verify_log "not a git repository; diff base unresolved (inconclusive)"
361
366
  return 0
362
367
  fi
@@ -2187,6 +2192,16 @@ verify_compute_verdict() {
2187
2192
  elif [ "$has_nonblock_finding" = "true" ] || [ "$inconclusive" = "true" ]; then
2188
2193
  VERIFY_VERDICT="CONCERNS"
2189
2194
  VERIFY_EXIT=$VERIFY_EXIT_CONCERNS
2195
+ # Exit contract (v11.4.0): the two inputs that give the verifier nothing
2196
+ # to check map to their own codes. The verdict word stays CONCERNS so
2197
+ # evidence.json is unchanged; only the process status is sharper.
2198
+ if [ "$has_nonblock_finding" != "true" ]; then
2199
+ if [ "${VERIFY_NOT_GIT:-0}" = "1" ]; then
2200
+ VERIFY_EXIT=$VERIFY_EXIT_BLOCKED
2201
+ elif [ "${VERIFY_DIFF_RESOLVED:-false}" = "true" ] && [ "${VERIFY_DIFF_FILES:-0}" -eq 0 ]; then
2202
+ VERIFY_EXIT=$VERIFY_EXIT_ERROR
2203
+ fi
2204
+ fi
2190
2205
  else
2191
2206
  VERIFY_VERDICT="VERIFIED"
2192
2207
  VERIFY_EXIT=$VERIFY_EXIT_VERIFIED
@@ -2570,6 +2585,11 @@ EXIT CODES:
2570
2585
  1 CONCERNS
2571
2586
  2 BLOCKED
2572
2587
  3 verifier error (could not complete; never silently passes)
2588
+ 64 usage error (unknown option or unexpected argument)
2589
+
2590
+ Two inputs that leave nothing to check have their own codes: a directory
2591
+ that is not a git repository exits 2, an empty diff vs base exits 3. The
2592
+ VERDICT line still reads CONCERNS for both.
2573
2593
 
2574
2594
  Severity rises with the code, so `[ $rc -ge 2 ]` means "at least blocked".
2575
2595
  An early draft spec listed 1=BLOCKED, 2=CONCERNS; that ordering was
@@ -3238,10 +3258,10 @@ verify_main() {
3238
3258
  --allow-unsigned) VERIFY_ALLOW_UNSIGNED=1; shift ;;
3239
3259
  --) shift; break ;;
3240
3260
  -*)
3241
- _verify_err "unknown option: $1"; verify_help; return $VERIFY_EXIT_ERROR ;;
3261
+ _verify_err "unknown option: $1"; verify_help; return $VERIFY_EXIT_USAGE ;;
3242
3262
  *)
3243
3263
  if [ -z "$base_ref" ]; then base_ref="$1"; else
3244
- _verify_err "unexpected argument: $1"; return $VERIFY_EXIT_ERROR
3264
+ _verify_err "unexpected argument: $1"; return $VERIFY_EXIT_USAGE
3245
3265
  fi
3246
3266
  shift ;;
3247
3267
  esac
package/bin/loki CHANGED
@@ -245,6 +245,48 @@ if [ "${LOKI_PROVIDER:-}" = "gemini" ]; then
245
245
  exit 1
246
246
  fi
247
247
 
248
+ # OC-MODELS: `loki start|quick ... --model <id>` (also flag-first). One place maps the flag to
249
+ # env before ANY engine/route decision, so engine10, the Bun start command and the legacy
250
+ # loop all see the same thing. Route specs (openrouter|ollama|lmstudio|llamacpp|vllm|custom/<id>)
251
+ # set LOKI_MODEL_ROUTE (claude child only); tier names and Claude aliases become --session-model;
252
+ # any other id sets LOKI_OPENCODE_MODEL for opencode or LOKI_MODEL_OVERRIDE otherwise.
253
+ case "${1:-}" in
254
+ start|quick|--model|--model=*)
255
+ _lm_has=0
256
+ for _lm_a in "$@"; do case "$_lm_a" in --model|--model=*) _lm_has=1 ;; esac; done
257
+ if [ "$_lm_has" = "1" ]; then
258
+ _lm_out=(); _lm_model=""; _lm_prov=""; _lm_skip=0; _lm_pnext=0
259
+ for _lm_a in "$@"; do
260
+ if [ "$_lm_skip" = "1" ]; then case "$_lm_a" in -*) _lm_model=""; break ;; esac; _lm_model="$_lm_a"; _lm_skip=0; continue; fi
261
+ if [ "$_lm_pnext" = "1" ]; then _lm_prov="$_lm_a"; _lm_pnext=0; fi
262
+ case "$_lm_a" in
263
+ --model) _lm_skip=1; continue ;;
264
+ --model=*) _lm_model="${_lm_a#--model=}"; continue ;;
265
+ --provider) _lm_pnext=1 ;;
266
+ --provider=*) _lm_prov="${_lm_a#--provider=}" ;;
267
+ esac
268
+ _lm_out+=("$_lm_a")
269
+ done
270
+ if [ -n "$_lm_model" ]; then
271
+ [ -n "$_lm_prov" ] || _lm_prov="${LOKI_PROVIDER:-claude}"
272
+ case "$_lm_model" in
273
+ openrouter/*|ollama/*|lmstudio/*|llamacpp/*|vllm/*|custom/*) export LOKI_MODEL_ROUTE="$_lm_model" ;;
274
+ small|medium|high|opus|sonnet|haiku) _lm_out+=(--session-model "$_lm_model") ;;
275
+ *)
276
+ if [ "$_lm_prov" = "opencode" ]; then export LOKI_OPENCODE_MODEL="$_lm_model"
277
+ else export LOKI_MODEL_OVERRIDE="$_lm_model"; fi
278
+ ;;
279
+ esac
280
+ else
281
+ echo "loki: --model requires a value (for example ollama/qwen3-coder or openrouter/<id>)." >&2
282
+ exit 2
283
+ fi
284
+ set -- ${_lm_out[@]+"${_lm_out[@]}"}
285
+ fi
286
+ unset _lm_has _lm_out _lm_model _lm_prov _lm_skip _lm_pnext _lm_a
287
+ ;;
288
+ esac
289
+
248
290
  # First-run funnel, stage C: the activation ATTEMPT (first `loki start`).
249
291
  #
250
292
  # WHY HERE. This shim runs for BOTH routes before any exec, so one emit at this
@@ -406,29 +448,16 @@ _loki_start_needs_bash() {
406
448
  esac
407
449
  done
408
450
  unset _loki_iso_next
409
-
410
- # The Bun RARV loop has not ported the opencode invoker yet. Keep the
411
- # released, autonomous shell adapter reachable when SDK mode is enabled
412
- # instead of accepting the selection and then failing after intake. CLI
413
- # selection wins over env, which wins over the project-level saved choice.
414
- local effective_provider="$explicit_provider"
415
- if [ -z "$effective_provider" ] && [ -n "${LOKI_PROVIDER:-}" ]; then
416
- effective_provider="$LOKI_PROVIDER"
417
- fi
418
- if [ -z "$effective_provider" ] && [ -f "${PWD}/.loki/state/provider" ]; then
419
- effective_provider="$(sed -n '1p' "${PWD}/.loki/state/provider" 2>/dev/null || true)"
420
- fi
421
- [ "$effective_provider" = "opencode" ] && return 0
422
451
  return 1
423
452
  }
424
453
  _loki_attempts_refuse() {
425
454
  if _loki_start_needs_bash "$@"; then
426
- echo "loki: 'loki start --attempts' runs only the Loki 10 engine; an option or provider in this command (for example --parallel, --github, --sandbox, --issue, --dry-run, --detach, or opencode) was only honored by the removed legacy loop. Remove it, or use a task string, an issue ref or a PRD path." >&2
455
+ echo "loki: 'loki start --attempts' runs only the Loki 10 engine; an option or provider in this command (for example --parallel, --github, --sandbox, --issue, --dry-run, or --detach) was only honored by the removed legacy loop. Remove it, or use a task string, an issue ref or a PRD path." >&2
427
456
  exit 2
428
457
  fi
429
458
  case "${LOKI_PROVIDER:-claude}" in
430
- claude|codex|cline|aider) ;;
431
- *) echo "loki: the Loki 10 engine does not support LOKI_PROVIDER=${LOKI_PROVIDER:-}; use claude, codex, cline or aider." >&2; exit 2 ;;
459
+ claude|codex|cline|aider|opencode) ;;
460
+ *) echo "loki: the Loki 10 engine does not support LOKI_PROVIDER=${LOKI_PROVIDER:-}; use claude, codex, cline, aider or opencode." >&2; exit 2 ;;
432
461
  esac
433
462
  }
434
463
 
@@ -482,6 +511,28 @@ if [ "${1:-}" = "start" ]; then
482
511
  unset _loki_sp_a
483
512
  fi
484
513
 
514
+ # OC-ROUTE: normalize flags before the engine decision so argument order and input form
515
+ # never change the engine (autonomy/route.sh; `loki route ...` and --explain-route print it).
516
+ _R_APPLIED=0
517
+ if [ -f "$REPO_ROOT/autonomy/route.sh" ] && { [ "$_LOKI_START_ATTEMPTS" != "1" ] || case " $* " in *" --explain-route "*) true ;; *) false ;; esac; }; then
518
+ # shellcheck source=/dev/null
519
+ . "$REPO_ROOT/autonomy/route.sh"
520
+ _r_rc=0
521
+ loki_route_dispatch "$@" || _r_rc=$?
522
+ case "$_r_rc" in 7) exit 0 ;; 8) exit 2 ;; esac
523
+ if [ "$R_DECIDED" = "1" ]; then
524
+ _R_APPLIED=1
525
+ if [ -n "$R_SESSION_MODEL" ]; then
526
+ case "$R_SESSION_MODEL" in small) R_SESSION_MODEL=fast ;; medium) R_SESSION_MODEL=development ;; high) R_SESSION_MODEL=planning ;; esac
527
+ export LOKI_SESSION_MODEL="$R_SESSION_MODEL"
528
+ fi
529
+ if [ "$R_E10" != "1" ]; then
530
+ set -- "$([ "$R_VERB" = "bare" ] && echo start || echo "$R_VERB")" ${R_CLEAN[@]+"${R_CLEAN[@]}"}
531
+ fi
532
+ fi
533
+ unset _r_rc
534
+ fi
535
+
485
536
  # Loki 10 engine (D29, the only engine from 11.0.0). Routes `loki "<task>"`,
486
537
  # `loki owner/repo#N`, `loki quick "<task>"`, `keys` and `verify --pubkey` to engine10.
487
538
  # shellcheck disable=SC2194
@@ -566,14 +617,18 @@ case "v10" in
566
617
  # issue refs reach v10.
567
618
  *) _e10=0 ;;
568
619
  esac
620
+ if [ "$_R_APPLIED" = "1" ]; then
621
+ _e10="$R_E10"; _e10_hard="$R_HARD"
622
+ if [ "$R_E10" = "1" ]; then _e10_args=("${R_E10_ARGS[@]}"); fi
623
+ fi
569
624
  # Default (unset) mode never breaks a host: no bun, or a provider engine10 has no
570
625
  # invoker for, falls through to the legacy engine. Explicit v10 keeps its hard error.
571
626
  if [ "$_e10" = "1" ] && [ "$_e10_hard" = "0" ]; then
572
627
  case "${LOKI_PROVIDER:-claude}" in
573
- claude|codex|cline|aider) ;;
628
+ claude|codex|cline|aider|opencode) ;;
574
629
  *)
575
630
  _e10=0
576
- echo "loki: using the legacy engine (the Loki 10 engine does not support LOKI_PROVIDER=${LOKI_PROVIDER:-}; use claude, codex, cline or aider)." >&2
631
+ echo "loki: using the legacy engine (the Loki 10 engine does not support LOKI_PROVIDER=${LOKI_PROVIDER:-}; use claude, codex, cline, aider or opencode)." >&2
577
632
  ;;
578
633
  esac
579
634
  if [ "$_e10" = "1" ]; then