loki-mode 7.42.0 → 7.44.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.
- package/README.md +4 -31
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/app-runner.sh +174 -8
- package/autonomy/completion-council.sh +16 -3
- package/autonomy/loki +54 -9
- package/autonomy/run.sh +73 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +226 -1
- package/dashboard/static/index.html +87 -30
- package/docs/INSTALLATION.md +1 -1
- package/events/bus.py +9 -6
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/mcp/server.py +26 -2
- package/memory/vector_index.py +6 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/codex.sh +21 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ _The free, source-available autonomous coding agent by [Autonomi](https://www.au
|
|
|
13
13
|
[](https://hub.docker.com/r/asklokesh/loki-mode)
|
|
14
14
|
[](LICENSE)
|
|
15
15
|
|
|
16
|
-
[Website](https://www.autonomi.dev/) | [Documentation](wiki/Home.md) | [Installation](docs/INSTALLATION.md) | [Changelog](CHANGELOG.md) | [Purple Lab
|
|
16
|
+
[Website](https://www.autonomi.dev/) | [Documentation](wiki/Home.md) | [Installation](docs/INSTALLATION.md) | [Changelog](CHANGELOG.md) | [Purple Lab -- deprecated v7.44.0](#purple-lab)
|
|
17
17
|
|
|
18
18
|
</div>
|
|
19
19
|
|
|
@@ -290,36 +290,9 @@ TLS, OIDC/SSO, RBAC, OTEL tracing, policy engine, audit trails. Activated via en
|
|
|
290
290
|
|
|
291
291
|
## Purple Lab
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
**[DEPRECATED in v7.44.0]** Purple Lab (`loki web`, port 57375) is deprecated. The local build monitor and project dashboard are now the dashboard (auto-launched by `loki start`, http://localhost:57374). For the hosted/commercial platform, see [Autonomi Cloud](https://www.autonomi.dev/).
|
|
294
294
|
|
|
295
|
-
|
|
296
|
-
loki web # launches at http://localhost:57375
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
<table>
|
|
300
|
-
<tr>
|
|
301
|
-
<td width="50%" valign="top">
|
|
302
|
-
|
|
303
|
-
**Platform Pages**
|
|
304
|
-
- Home -- One-line prompt to start building instantly
|
|
305
|
-
- Projects -- Browse, search, filter past builds
|
|
306
|
-
- Templates -- 20+ starter PRDs by category
|
|
307
|
-
- Showcase -- Gallery of example projects to build
|
|
308
|
-
- Compare -- Feature comparison vs competitors
|
|
309
|
-
|
|
310
|
-
</td>
|
|
311
|
-
<td width="50%" valign="top">
|
|
312
|
-
|
|
313
|
-
**IDE Workspace**
|
|
314
|
-
- Monaco editor with tabs, Cmd+P quick open
|
|
315
|
-
- AI chat panel for iterative development
|
|
316
|
-
- Activity panel: build log, agents, quality gates
|
|
317
|
-
- Live preview with URL bar navigation
|
|
318
|
-
- Right-click context menu: Review, Test, Explain
|
|
319
|
-
|
|
320
|
-
</td>
|
|
321
|
-
</tr>
|
|
322
|
-
</table>
|
|
295
|
+
The historical feature set (platform pages, Monaco IDE workspace, AI chat panel) lives on in the dashboard and in Autonomi Cloud. `loki web` still invokes the old binary for backward compatibility but will be removed in a future major version.
|
|
323
296
|
|
|
324
297
|
---
|
|
325
298
|
|
|
@@ -372,7 +345,7 @@ Status legend: "E2E-verified" means we run real spec-to-code builds on it oursel
|
|
|
372
345
|
| `loki status` | Show current status |
|
|
373
346
|
| `loki dashboard` | Open web dashboard |
|
|
374
347
|
| `loki preview` | Print running app URL and open in browser (Live App Preview, v7.24.0; was: `loki open`) |
|
|
375
|
-
| `loki web` | Launch Purple Lab web UI |
|
|
348
|
+
| `loki web` | Launch Purple Lab web UI [DEPRECATED in v7.44.0 -- use `loki start` which auto-opens the dashboard at http://localhost:57374; for the hosted platform see Autonomi Cloud] |
|
|
376
349
|
| `loki doctor` | Check environment and dependencies |
|
|
377
350
|
| `loki plan [PRD]` | Pre-execution analysis: complexity, cost, iterations |
|
|
378
351
|
| `loki review [--staged\|--diff]` | AI-powered code review with severity filtering |
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 11 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.
|
|
6
|
+
# Loki Mode v7.44.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -398,4 +398,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
398
398
|
|
|
399
399
|
---
|
|
400
400
|
|
|
401
|
-
**v7.
|
|
401
|
+
**v7.44.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.44.0
|
package/autonomy/app-runner.sh
CHANGED
|
@@ -156,6 +156,112 @@ _rewrite_detection_port() {
|
|
|
156
156
|
_write_detection "$d_type" "$d_command"
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
# Collect the transitive descendant tree of a PID (children, grandchildren, ...).
|
|
160
|
+
#
|
|
161
|
+
# Echoes one PID per line, deepest-LAST is NOT guaranteed; order is breadth-first
|
|
162
|
+
# from the root. The root PID itself is NOT included. Used by the non-setsid stop
|
|
163
|
+
# fallback (BUG 1): the app is started as `( ... ) &` WITHOUT setsid, so on stock
|
|
164
|
+
# macOS the whole tree (subshell -> bash -lc -> npm -> sh -> node -> workers)
|
|
165
|
+
# inherits the ORCHESTRATOR's process group. A `kill -- -PGID` would therefore
|
|
166
|
+
# signal run.sh and the Claude agent driving it (self-termination), so we MUST
|
|
167
|
+
# walk parent->child links from OUR pid only. This guarantees we never signal a
|
|
168
|
+
# process outside our own subtree: every returned pid has our root as an ancestor.
|
|
169
|
+
#
|
|
170
|
+
# Snapshot semantics: the caller MUST collect the full tree BEFORE sending any
|
|
171
|
+
# signal. If we TERM top-down while walking, grandchildren reparent to init and
|
|
172
|
+
# `pgrep -P <dead-parent>` returns nothing, re-creating the orphaned-worker bug
|
|
173
|
+
# this fix exists to close.
|
|
174
|
+
_app_runner_collect_descendants() {
|
|
175
|
+
local root="$1"
|
|
176
|
+
# Guard against empty / init / kernel pids: walking from 0/1 would sweep
|
|
177
|
+
# unrelated processes. A valid app pid is always > 1.
|
|
178
|
+
case "$root" in
|
|
179
|
+
''|0|1) return 0 ;;
|
|
180
|
+
esac
|
|
181
|
+
if ! [[ "$root" =~ ^[0-9]+$ ]]; then
|
|
182
|
+
return 0
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
local -a frontier=("$root")
|
|
186
|
+
local -a found=()
|
|
187
|
+
local pid child
|
|
188
|
+
local -a kids
|
|
189
|
+
# Bound iterations defensively against a pathological/looping tree.
|
|
190
|
+
local guard=0
|
|
191
|
+
while [ "${#frontier[@]}" -gt 0 ] && [ "$guard" -lt 10000 ]; do
|
|
192
|
+
guard=$(( guard + 1 ))
|
|
193
|
+
pid="${frontier[0]}"
|
|
194
|
+
frontier=("${frontier[@]:1}")
|
|
195
|
+
# Direct children of pid.
|
|
196
|
+
kids=()
|
|
197
|
+
while IFS= read -r child; do
|
|
198
|
+
[ -n "$child" ] && kids+=("$child")
|
|
199
|
+
done < <(pgrep -P "$pid" 2>/dev/null)
|
|
200
|
+
local k
|
|
201
|
+
for k in "${kids[@]:-}"; do
|
|
202
|
+
[ -n "$k" ] || continue
|
|
203
|
+
found+=("$k")
|
|
204
|
+
frontier+=("$k")
|
|
205
|
+
done
|
|
206
|
+
done
|
|
207
|
+
|
|
208
|
+
local f
|
|
209
|
+
for f in "${found[@]:-}"; do
|
|
210
|
+
[ -n "$f" ] && printf '%s\n' "$f"
|
|
211
|
+
done
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# Signal an EXPLICIT, pre-captured set of PIDs with a given signal.
|
|
215
|
+
#
|
|
216
|
+
# Usage: _app_runner_signal_pids <SIGNAL> <pid> [pid ...]
|
|
217
|
+
#
|
|
218
|
+
# Why an explicit list and not "(re-)walk from root": a worker that traps
|
|
219
|
+
# SIGTERM (a Node server doing graceful shutdown is the textbook case) survives
|
|
220
|
+
# the TERM phase while its intermediate ancestors (npm, sh) die. Once the
|
|
221
|
+
# ancestors die, the surviving worker reparents to init, so re-deriving the tree
|
|
222
|
+
# from the now-dead root via `pgrep -P` would return NOTHING -- the KILL phase
|
|
223
|
+
# would be skipped and the orphaned, port-holding worker would live on. That is
|
|
224
|
+
# exactly the orphaned-worker bug (BUG 1) resurfacing at the force-kill phase.
|
|
225
|
+
# The fix: the caller snapshots root + all descendants ONCE before any signal,
|
|
226
|
+
# and every phase (TERM, aliveness, KILL) operates over that frozen list.
|
|
227
|
+
#
|
|
228
|
+
# Safety: the caller builds the list from _app_runner_collect_descendants, which
|
|
229
|
+
# only ever follows parent->child links from OUR pid, so the list can never
|
|
230
|
+
# contain a process outside our own subtree. We signal pids individually (never
|
|
231
|
+
# a process group) because in the non-setsid path the app inherits the
|
|
232
|
+
# orchestrator's process group; a group signal would kill run.sh and the agent.
|
|
233
|
+
# Pids are signaled in REVERSE capture order so descendants (captured after the
|
|
234
|
+
# root) are signaled before the root.
|
|
235
|
+
_app_runner_signal_pids() {
|
|
236
|
+
local sig="$1"; shift
|
|
237
|
+
local -a pids=("$@")
|
|
238
|
+
local i p
|
|
239
|
+
for (( i=${#pids[@]}-1; i>=0; i-- )); do
|
|
240
|
+
p="${pids[$i]}"
|
|
241
|
+
case "$p" in
|
|
242
|
+
''|0|1) continue ;;
|
|
243
|
+
esac
|
|
244
|
+
kill "-${sig}" "$p" 2>/dev/null || true
|
|
245
|
+
done
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
# True (0) if ANY pid in the EXPLICIT pre-captured list is still alive.
|
|
249
|
+
# Used by the non-setsid stop grace-wait so a deep worker that outlived the main
|
|
250
|
+
# subshell does not let us fall through to "stopped" prematurely. Operates over
|
|
251
|
+
# the frozen snapshot for the same reason _app_runner_signal_pids does.
|
|
252
|
+
_app_runner_any_alive() {
|
|
253
|
+
local p
|
|
254
|
+
for p in "$@"; do
|
|
255
|
+
case "$p" in
|
|
256
|
+
''|0|1) continue ;;
|
|
257
|
+
esac
|
|
258
|
+
if kill -0 "$p" 2>/dev/null; then
|
|
259
|
+
return 0
|
|
260
|
+
fi
|
|
261
|
+
done
|
|
262
|
+
return 1
|
|
263
|
+
}
|
|
264
|
+
|
|
159
265
|
# Fix #2 (finding #597): reconcile the recorded port with the port the app
|
|
160
266
|
# ACTUALLY bound, using the listen line in app.log as the source of truth. This
|
|
161
267
|
# corrects the dashboard Live Preview even when the app ignores PORT and picks
|
|
@@ -887,32 +993,82 @@ app_runner_stop() {
|
|
|
887
993
|
fi
|
|
888
994
|
fi
|
|
889
995
|
|
|
996
|
+
# BUG 1 fix: on the non-setsid fallback (the DEFAULT path on stock macOS,
|
|
997
|
+
# which has no setsid) capture the FULL process subtree -- root + every
|
|
998
|
+
# transitive descendant -- ONCE, BEFORE sending any signal. The old
|
|
999
|
+
# `pkill -TERM -P <pid>` reached only ONE level of children, so deep workers
|
|
1000
|
+
# (npm -> sh -> node -> workers) holding the listening socket survived as
|
|
1001
|
+
# orphans and kept the port bound, blocking the next start.
|
|
1002
|
+
#
|
|
1003
|
+
# Capturing once is load-bearing: a worker that traps SIGTERM survives the
|
|
1004
|
+
# TERM phase while its intermediate ancestors die, then reparents to init.
|
|
1005
|
+
# Re-deriving the tree from the now-dead root would return nothing and skip
|
|
1006
|
+
# the KILL phase, leaving the port-holder alive. Every phase below (TERM,
|
|
1007
|
+
# grace-wait, KILL) operates over this one frozen snapshot instead.
|
|
1008
|
+
local -a _stop_snapshot=()
|
|
1009
|
+
if [ "$_APP_RUNNER_HAS_SETSID" != true ]; then
|
|
1010
|
+
_stop_snapshot=("$_APP_RUNNER_PID")
|
|
1011
|
+
local _snap_d
|
|
1012
|
+
while IFS= read -r _snap_d; do
|
|
1013
|
+
[ -n "$_snap_d" ] && _stop_snapshot+=("$_snap_d")
|
|
1014
|
+
done < <(_app_runner_collect_descendants "$_APP_RUNNER_PID")
|
|
1015
|
+
fi
|
|
1016
|
+
|
|
890
1017
|
# Send SIGTERM to process and children
|
|
891
1018
|
if [ "$_APP_RUNNER_HAS_SETSID" = true ]; then
|
|
1019
|
+
# setsid path: the app is its own process group leader, so a group
|
|
1020
|
+
# signal reaches the whole tree safely. Unchanged.
|
|
892
1021
|
kill -TERM "-$_APP_RUNNER_PID" 2>/dev/null || kill -TERM "$_APP_RUNNER_PID" 2>/dev/null || true
|
|
893
1022
|
else
|
|
894
|
-
|
|
895
|
-
|
|
1023
|
+
# Group-kill is NOT used here: in this path the app inherits the
|
|
1024
|
+
# orchestrator's process group, so a group signal would kill run.sh and
|
|
1025
|
+
# the agent driving it. Signal the frozen snapshot, descendants first.
|
|
1026
|
+
_app_runner_signal_pids TERM "${_stop_snapshot[@]}"
|
|
896
1027
|
fi
|
|
897
1028
|
|
|
898
|
-
# Wait up to 5 seconds for graceful shutdown
|
|
1029
|
+
# Wait up to 5 seconds for graceful shutdown. Key the wait on the WHOLE
|
|
1030
|
+
# snapshot being alive (not just the main pid): a deep worker can outlive the
|
|
1031
|
+
# main subshell, and treating the main pid's exit as "done" is exactly what
|
|
1032
|
+
# let workers leak before. setsid path keeps the simpler main-pid check.
|
|
899
1033
|
local waited=0
|
|
900
1034
|
while [ "$waited" -lt 5 ]; do
|
|
901
|
-
if
|
|
902
|
-
break
|
|
1035
|
+
if [ "$_APP_RUNNER_HAS_SETSID" = true ]; then
|
|
1036
|
+
kill -0 "$_APP_RUNNER_PID" 2>/dev/null || break
|
|
1037
|
+
else
|
|
1038
|
+
_app_runner_any_alive "${_stop_snapshot[@]}" || break
|
|
903
1039
|
fi
|
|
904
1040
|
sleep 1
|
|
905
1041
|
waited=$(( waited + 1 ))
|
|
906
1042
|
done
|
|
907
1043
|
|
|
908
1044
|
# Force kill if still running
|
|
909
|
-
|
|
1045
|
+
local _still_alive=false
|
|
1046
|
+
if [ "$_APP_RUNNER_HAS_SETSID" = true ]; then
|
|
1047
|
+
kill -0 "$_APP_RUNNER_PID" 2>/dev/null && _still_alive=true
|
|
1048
|
+
else
|
|
1049
|
+
_app_runner_any_alive "${_stop_snapshot[@]}" && _still_alive=true
|
|
1050
|
+
fi
|
|
1051
|
+
if [ "$_still_alive" = true ]; then
|
|
910
1052
|
log_warn "App Runner: process did not stop gracefully, sending SIGKILL"
|
|
911
1053
|
if [ "$_APP_RUNNER_HAS_SETSID" = true ]; then
|
|
912
1054
|
kill -KILL "-$_APP_RUNNER_PID" 2>/dev/null || kill -KILL "$_APP_RUNNER_PID" 2>/dev/null || true
|
|
913
1055
|
else
|
|
914
|
-
|
|
915
|
-
|
|
1056
|
+
# BUG 1 fix (KILL phase): SIGKILL the SAME frozen snapshot (root +
|
|
1057
|
+
# all descendants captured pre-signal), so a TERM-trapping worker
|
|
1058
|
+
# that reparented to init is still force-killed. SIGKILL cannot be
|
|
1059
|
+
# trapped, so this is the terminal guarantee that no port-holder
|
|
1060
|
+
# survives. The snapshot does the real work. The fresh walk below only
|
|
1061
|
+
# adds anything while the root is still alive (a worker spawned during
|
|
1062
|
+
# shutdown); once the root is dead it is empty and the snapshot covers.
|
|
1063
|
+
_app_runner_signal_pids KILL "${_stop_snapshot[@]}"
|
|
1064
|
+
local -a _kill_fresh=()
|
|
1065
|
+
local _kf
|
|
1066
|
+
while IFS= read -r _kf; do
|
|
1067
|
+
[ -n "$_kf" ] && _kill_fresh+=("$_kf")
|
|
1068
|
+
done < <(_app_runner_collect_descendants "$_APP_RUNNER_PID")
|
|
1069
|
+
if [ "${#_kill_fresh[@]}" -gt 0 ]; then
|
|
1070
|
+
_app_runner_signal_pids KILL "${_kill_fresh[@]}"
|
|
1071
|
+
fi
|
|
916
1072
|
fi
|
|
917
1073
|
fi
|
|
918
1074
|
|
|
@@ -1094,6 +1250,11 @@ app_runner_watchdog() {
|
|
|
1094
1250
|
# it restarts the stack under the same crash-count circuit breaker.
|
|
1095
1251
|
if [ "$_APP_RUNNER_IS_DOCKER" = true ] && echo "$_APP_RUNNER_METHOD" | grep -q "docker compose"; then
|
|
1096
1252
|
if app_runner_health_check; then
|
|
1253
|
+
# BUG 3 fix: the breaker is meant to fire on 5 CONSECUTIVE failures.
|
|
1254
|
+
# A confirmed-healthy observation clears any accumulated count so a
|
|
1255
|
+
# long-lived stack that recovered from a few transient blips is not
|
|
1256
|
+
# tripped permanently on cumulative (non-consecutive) crashes.
|
|
1257
|
+
_APP_RUNNER_CRASH_COUNT=0
|
|
1097
1258
|
return 0
|
|
1098
1259
|
fi
|
|
1099
1260
|
_APP_RUNNER_CRASH_COUNT=$(( _APP_RUNNER_CRASH_COUNT + 1 ))
|
|
@@ -1125,6 +1286,11 @@ app_runner_watchdog() {
|
|
|
1125
1286
|
|
|
1126
1287
|
# Process alive, nothing to do
|
|
1127
1288
|
if kill -0 "$_APP_RUNNER_PID" 2>/dev/null; then
|
|
1289
|
+
# BUG 3 fix: a confirmed-alive observation clears the accumulated crash
|
|
1290
|
+
# count so the breaker fires only on 5 CONSECUTIVE deaths, not on 5
|
|
1291
|
+
# cumulative crashes that were each successfully recovered over a long
|
|
1292
|
+
# session (which would trip the breaker on a HEALTHY app).
|
|
1293
|
+
_APP_RUNNER_CRASH_COUNT=0
|
|
1128
1294
|
return 0
|
|
1129
1295
|
fi
|
|
1130
1296
|
|
|
@@ -1519,8 +1519,17 @@ council_evidence_gate() {
|
|
|
1519
1519
|
if committed_files=$(git diff --name-only "$base_sha" HEAD 2>/dev/null); then
|
|
1520
1520
|
:
|
|
1521
1521
|
else
|
|
1522
|
-
# Base present but
|
|
1523
|
-
#
|
|
1522
|
+
# Base present but UNREACHABLE (e.g. shallow clone, history rewrite,
|
|
1523
|
+
# or `git reset --hard` -- a documented live hazard). The diff vs the
|
|
1524
|
+
# run-start SHA cannot be computed, so we can no longer prove that the
|
|
1525
|
+
# committed-union diff is empty. Treat this as INCONCLUSIVE, not as
|
|
1526
|
+
# positive empty-diff fabrication evidence: an agent that committed
|
|
1527
|
+
# all its work leaves a clean working tree, and `git diff HEAD` would
|
|
1528
|
+
# read empty -> a false BLOCK. We still fall back to the working-tree
|
|
1529
|
+
# diff vs HEAD to capture any uncommitted work, but the empty-diff
|
|
1530
|
+
# block is suppressed below via the diff_inconclusive guard.
|
|
1531
|
+
diff_inconclusive="true"
|
|
1532
|
+
diff_inconclusive_reason="base_unreachable"
|
|
1524
1533
|
committed_files=$(git diff --name-only HEAD 2>/dev/null || echo "")
|
|
1525
1534
|
fi
|
|
1526
1535
|
unstaged_files=$(git diff --name-only HEAD 2>/dev/null || echo "")
|
|
@@ -1543,7 +1552,11 @@ council_evidence_gate() {
|
|
|
1543
1552
|
else
|
|
1544
1553
|
diff_files=0
|
|
1545
1554
|
fi
|
|
1546
|
-
|
|
1555
|
+
# Only treat an empty union as positive fabrication evidence when the
|
|
1556
|
+
# baseline was CONCLUSIVE. If the base SHA was unreachable (history
|
|
1557
|
+
# rewrite / reset --hard), a clean committed tree yields an empty
|
|
1558
|
+
# working-tree diff that must NOT read as empty-diff fabrication.
|
|
1559
|
+
if [ "$diff_files" -eq 0 ] && [ "$diff_inconclusive" != "true" ]; then
|
|
1547
1560
|
diff_fails="true"
|
|
1548
1561
|
fi
|
|
1549
1562
|
fi
|
package/autonomy/loki
CHANGED
|
@@ -891,7 +891,6 @@ show_landing() {
|
|
|
891
891
|
echo "Get started:"
|
|
892
892
|
echo -e " ${CYAN}loki start ./prd.md${NC} Build from a spec (PRD file, GitHub issue, or no arg)"
|
|
893
893
|
echo -e " ${CYAN}loki demo${NC} Build a sample todo app end to end (real run)"
|
|
894
|
-
echo -e " ${CYAN}loki web${NC} Open the visual builder to input a spec and watch agents build"
|
|
895
894
|
echo -e " ${CYAN}loki dashboard start${NC} Start the live run monitor (then: loki dashboard open)"
|
|
896
895
|
echo ""
|
|
897
896
|
echo -e "Need help? ${CYAN}loki help${NC} lists every command."
|
|
@@ -1057,6 +1056,7 @@ cmd_start() {
|
|
|
1057
1056
|
echo "Options:"
|
|
1058
1057
|
echo " --provider NAME AI provider: claude (default), codex, cline, aider"
|
|
1059
1058
|
echo " --parallel Enable parallel mode with git worktrees"
|
|
1059
|
+
echo " --allow-haiku Enable Haiku model for the fast tier (default: disabled)"
|
|
1060
1060
|
echo " --bg, --background Run in background mode"
|
|
1061
1061
|
echo " --simple Force simple complexity tier (3 phases)"
|
|
1062
1062
|
echo " --complex Force complex complexity tier (8 phases)"
|
|
@@ -1180,6 +1180,17 @@ cmd_start() {
|
|
|
1180
1180
|
args+=("--parallel")
|
|
1181
1181
|
shift
|
|
1182
1182
|
;;
|
|
1183
|
+
--allow-haiku)
|
|
1184
|
+
# Enable Haiku for the fast tier. Mirrors the LOKI_ALLOW_HAIKU=true
|
|
1185
|
+
# env var (consumed by providers/claude.sh and run.sh). Documented in
|
|
1186
|
+
# loki --help and run.sh; previously only the env var worked here, so
|
|
1187
|
+
# `loki start ./prd.md --allow-haiku` aborted with "Unknown option".
|
|
1188
|
+
# Export reaches the runner; also forward as an arg so the run.sh
|
|
1189
|
+
# parser (run.sh:15015) sees it on every route.
|
|
1190
|
+
export LOKI_ALLOW_HAIKU=true
|
|
1191
|
+
args+=("--allow-haiku")
|
|
1192
|
+
shift
|
|
1193
|
+
;;
|
|
1183
1194
|
--regen-prd|--regenerate-prd|--regen|--fresh-prd)
|
|
1184
1195
|
# v7.8.1: force a fresh generated PRD on a no-PRD run, overriding
|
|
1185
1196
|
# the staleness-aware reuse (decide_generated_prd_action in
|
|
@@ -3958,9 +3969,9 @@ cmd_dashboard_help() {
|
|
|
3958
3969
|
echo "Usage: loki dashboard <command> [options]"
|
|
3959
3970
|
echo ""
|
|
3960
3971
|
echo "Note: 'loki dashboard' is the operations/observability UI (port ${DASHBOARD_DEFAULT_PORT})."
|
|
3961
|
-
echo " It is NOT the same as 'loki web' (Purple Lab, port ${PURPLE_LAB_DEFAULT_PORT},
|
|
3962
|
-
echo " Use 'loki dashboard' to monitor agents, tasks, costs, council, escalations
|
|
3963
|
-
echo "
|
|
3972
|
+
echo " It is NOT the same as 'loki web' (Purple Lab, port ${PURPLE_LAB_DEFAULT_PORT}, deprecated v7.44.0)."
|
|
3973
|
+
echo " Use 'loki dashboard' to monitor agents, tasks, costs, council, escalations,"
|
|
3974
|
+
echo " and to submit a PRD via the embedded 'Lab' tab (replaces the deprecated 'loki web')."
|
|
3964
3975
|
echo ""
|
|
3965
3976
|
echo "Commands:"
|
|
3966
3977
|
echo " start Start the dashboard server"
|
|
@@ -4193,9 +4204,11 @@ cmd_dashboard_start() {
|
|
|
4193
4204
|
echo " URL: $url"
|
|
4194
4205
|
echo " Logs: $log_file"
|
|
4195
4206
|
echo ""
|
|
4196
|
-
# v7.28.x UX #5:
|
|
4197
|
-
#
|
|
4198
|
-
|
|
4207
|
+
# v7.28.x UX #5: point a newcomer who wants to submit a spec at the right
|
|
4208
|
+
# surface. v7.44.0: the standalone Purple Lab (loki web) is deprecated and
|
|
4209
|
+
# consolidated here, so steer to the embedded Lab tab instead of spawning
|
|
4210
|
+
# a second port.
|
|
4211
|
+
echo -e "${DIM}Want to submit a spec in the browser? Open the 'Lab' tab in this dashboard.${NC}"
|
|
4199
4212
|
echo ""
|
|
4200
4213
|
echo -e "Open in browser: ${CYAN}loki dashboard open${NC}"
|
|
4201
4214
|
echo -e "Check status: ${CYAN}loki dashboard status${NC}"
|
|
@@ -4566,6 +4579,32 @@ PURPLE_LAB_PID_FILE="${PURPLE_LAB_STATE_DIR}/purple-lab.pid"
|
|
|
4566
4579
|
cmd_web() {
|
|
4567
4580
|
local subcommand="${1:-start}"
|
|
4568
4581
|
|
|
4582
|
+
# Deprecation (v7.44.0): Purple Lab (loki web, port 57375) is deprecated and
|
|
4583
|
+
# consolidated into the dashboard. The hosted/commercial role moved to the
|
|
4584
|
+
# separate Autonomi Cloud repo. This is a VALUE-PRESERVING deprecation: the
|
|
4585
|
+
# command STILL WORKS (it launches after the banner) so no one is stranded.
|
|
4586
|
+
# The banner + telemetry mirror the run->start deprecation contract: print
|
|
4587
|
+
# to stderr, suppress under machine-output flags (--json/-q/--quiet/json...),
|
|
4588
|
+
# and only emit telemetry when .loki already exists (events/emit.sh creates
|
|
4589
|
+
# "$LOKI_DIR/events/pending" as a side effect that would flip downstream
|
|
4590
|
+
# guards in a clean directory). The --help/-h/help path shows the deprecation
|
|
4591
|
+
# in its own help text instead, so we skip the banner there.
|
|
4592
|
+
case "$subcommand" in
|
|
4593
|
+
--help|-h|help) ;;
|
|
4594
|
+
*)
|
|
4595
|
+
if ! _deprecated_alias_should_suppress "$@"; then
|
|
4596
|
+
echo "loki web (Purple Lab) is deprecated as of v7.44.0. For local build + monitoring, use the dashboard (auto-launches at 'loki start', http://localhost:${DASHBOARD_DEFAULT_PORT}; or 'loki dashboard'). For the hosted platform, see Autonomi Cloud." >&2
|
|
4597
|
+
if [ -d "${LOKI_DIR:-.loki}" ]; then
|
|
4598
|
+
emit_event cli_command_deprecated cli web \
|
|
4599
|
+
"from=web" \
|
|
4600
|
+
"to=dashboard" \
|
|
4601
|
+
"version=7.44.0" \
|
|
4602
|
+
"argv=${*:-}"
|
|
4603
|
+
fi
|
|
4604
|
+
fi
|
|
4605
|
+
;;
|
|
4606
|
+
esac
|
|
4607
|
+
|
|
4569
4608
|
case "$subcommand" in
|
|
4570
4609
|
start)
|
|
4571
4610
|
shift || true
|
|
@@ -4605,13 +4644,19 @@ cmd_web() {
|
|
|
4605
4644
|
}
|
|
4606
4645
|
|
|
4607
4646
|
cmd_web_help() {
|
|
4608
|
-
echo -e "${BOLD}Purple Lab -- Loki Mode Web UI${NC}"
|
|
4647
|
+
echo -e "${BOLD}Purple Lab -- Loki Mode Web UI (deprecated -- use the dashboard)${NC}"
|
|
4609
4648
|
echo ""
|
|
4610
4649
|
echo "Usage: loki web [command] [options]"
|
|
4611
4650
|
echo ""
|
|
4651
|
+
echo "DEPRECATED as of v7.44.0: Purple Lab is consolidated into the dashboard."
|
|
4652
|
+
echo " For local build + monitoring, use the dashboard (auto-launches at"
|
|
4653
|
+
echo " 'loki start', http://localhost:${DASHBOARD_DEFAULT_PORT}; or 'loki dashboard')."
|
|
4654
|
+
echo " For the hosted platform, see Autonomi Cloud."
|
|
4655
|
+
echo " 'loki web' still works for now (it launches after a deprecation notice),"
|
|
4656
|
+
echo " but it will be removed in a future release. Migrate to the dashboard."
|
|
4657
|
+
echo ""
|
|
4612
4658
|
echo "Note: 'loki web' is Purple Lab (the PRD-input/build-watch UI, port ${PURPLE_LAB_DEFAULT_PORT})."
|
|
4613
4659
|
echo " It is NOT the same as 'loki dashboard' (operations UI, port ${DASHBOARD_DEFAULT_PORT})."
|
|
4614
|
-
echo " Use 'loki web' to submit a PRD and watch agents build it."
|
|
4615
4660
|
echo " Use 'loki dashboard' to monitor running agents, tasks, costs, council, escalations."
|
|
4616
4661
|
echo ""
|
|
4617
4662
|
echo "Commands:"
|
package/autonomy/run.sh
CHANGED
|
@@ -7041,6 +7041,48 @@ enforce_test_coverage() {
|
|
|
7041
7041
|
local output
|
|
7042
7042
|
output=$(cd "${TARGET_DIR:-.}" && timeout "$gate_timeout" npx mocha 2>&1) || test_passed=false
|
|
7043
7043
|
details="mocha: $(echo "$output" | tail -3 | tr '\n' ' ')"
|
|
7044
|
+
else
|
|
7045
|
+
# v7.41.x (test-coverage fail-open fix): a real "scripts.test" was
|
|
7046
|
+
# previously missed entirely. A greenfield project whose package.json
|
|
7047
|
+
# has {"scripts":{"test":"node --test"}} (or any non-placeholder test
|
|
7048
|
+
# script) actually runs a working suite via `npm test`, yet the gate
|
|
7049
|
+
# reported runner:none + pass:true -- so a project whose tests FAIL
|
|
7050
|
+
# green-lit identically. Detect a real test script (excluding the npm
|
|
7051
|
+
# placeholder "no test specified") with a JSON parser, not grep (grep
|
|
7052
|
+
# would false-positive on devDeps / unrelated keys), then run the
|
|
7053
|
+
# configured command. This MUST sit before the monorepo/python/go/rust
|
|
7054
|
+
# checks, all of which gate on test_runner=="none".
|
|
7055
|
+
local _pkg_test_script
|
|
7056
|
+
_pkg_test_script=$(_LOKI_PKG="${TARGET_DIR:-.}/package.json" python3 -c "
|
|
7057
|
+
import json, os, sys
|
|
7058
|
+
try:
|
|
7059
|
+
with open(os.environ['_LOKI_PKG']) as f:
|
|
7060
|
+
d = json.load(f)
|
|
7061
|
+
except Exception:
|
|
7062
|
+
sys.exit(0)
|
|
7063
|
+
t = (d.get('scripts') or {}).get('test') or ''
|
|
7064
|
+
# npm's default placeholder; treat as 'no test'.
|
|
7065
|
+
if 'no test specified' in t.lower():
|
|
7066
|
+
sys.exit(0)
|
|
7067
|
+
sys.stdout.write(t.strip())
|
|
7068
|
+
" 2>/dev/null || echo "")
|
|
7069
|
+
if [ -n "$_pkg_test_script" ]; then
|
|
7070
|
+
# LOKI_TEST_COMMAND lets an operator override the invocation; the
|
|
7071
|
+
# default is the project's own `npm test`.
|
|
7072
|
+
local _test_cmd="${LOKI_TEST_COMMAND:-npm test}"
|
|
7073
|
+
# Label the runner by what the script invokes so evidence is
|
|
7074
|
+
# honest (node --test, vitest, jest, etc. all surface here).
|
|
7075
|
+
case "$_pkg_test_script" in
|
|
7076
|
+
*"node --test"*|*"node:test"*) test_runner="node-test" ;;
|
|
7077
|
+
*vitest*) test_runner="vitest" ;;
|
|
7078
|
+
*jest*) test_runner="jest" ;;
|
|
7079
|
+
*mocha*) test_runner="mocha" ;;
|
|
7080
|
+
*) test_runner="npm-test" ;;
|
|
7081
|
+
esac
|
|
7082
|
+
local output
|
|
7083
|
+
output=$(cd "${TARGET_DIR:-.}" && timeout "$gate_timeout" sh -c "$_test_cmd" 2>&1) || test_passed=false
|
|
7084
|
+
details="$test_runner ($_test_cmd): $(echo "$output" | tail -5 | tr '\n' ' ')"
|
|
7085
|
+
fi
|
|
7044
7086
|
fi
|
|
7045
7087
|
fi
|
|
7046
7088
|
|
|
@@ -7165,10 +7207,23 @@ enforce_test_coverage() {
|
|
|
7165
7207
|
fi
|
|
7166
7208
|
|
|
7167
7209
|
if [ "$test_runner" = "none" ]; then
|
|
7168
|
-
log_info "Test coverage: no test runner detected,
|
|
7210
|
+
log_info "Test coverage: no test runner detected, recording inconclusive (not pass)"
|
|
7211
|
+
# v7.41.x fail-open fix: previously this wrote pass:true, so a project
|
|
7212
|
+
# whose tests truly do not run was indistinguishable from one whose tests
|
|
7213
|
+
# passed. Record pass:"inconclusive" instead. The completion-council
|
|
7214
|
+
# evidence gate already treats runner=="none" as pass-through regardless
|
|
7215
|
+
# of the pass value (completion-council.sh: runner=='none' short-circuits
|
|
7216
|
+
# BEFORE the `passed is False` block), so genuinely-no-tests stays
|
|
7217
|
+
# non-blocking (no infinite hang), while the JSON record is now honest:
|
|
7218
|
+
# "no tests" never reads as "tests passed". A DETECTED runner that fails
|
|
7219
|
+
# still writes pass:false below and BLOCKS.
|
|
7220
|
+
#
|
|
7221
|
+
# unit-tests.pass is only read for the status-line display (run.sh ~2183,
|
|
7222
|
+
# PASS vs PENDING); keeping the touch preserves the historical
|
|
7223
|
+
# non-blocking behavior for legitimate no-test projects.
|
|
7169
7224
|
touch "$quality_dir/unit-tests.pass"
|
|
7170
7225
|
cat > "$quality_dir/test-results.json" << TREOF
|
|
7171
|
-
{"timestamp":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","runner":"none","pass":
|
|
7226
|
+
{"timestamp":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","runner":"none","pass":"inconclusive","summary":"No test runner detected"}
|
|
7172
7227
|
TREOF
|
|
7173
7228
|
# Finding #598: stamp the per-iteration freshness marker so a later
|
|
7174
7229
|
# completion-route capture (ensure_completion_test_evidence) reuses this
|
|
@@ -14161,6 +14216,22 @@ if __name__ == "__main__":
|
|
|
14161
14216
|
log_warn " Review details under .loki/quality/reviews/ ; gate_failures=${gate_failures}"
|
|
14162
14217
|
_gate_block_for_completion=""
|
|
14163
14218
|
# Fall through; the gate-failed loop continues normally
|
|
14219
|
+
# HIGH (trust-gate): the checklist hard gate must also guard the
|
|
14220
|
+
# DEFAULT completion-promise / loki_complete_task route, not only the
|
|
14221
|
+
# interval-gated council path (council_evaluate) and the dashboard
|
|
14222
|
+
# force-review path -- both of which already call this gate. Without
|
|
14223
|
+
# it, an agent that leaves a `priority: critical` checklist item
|
|
14224
|
+
# `failing` and claims done on a non-council-interval iteration would
|
|
14225
|
+
# ship, bypassing the checklist gate entirely. council_reverify_checklist
|
|
14226
|
+
# ran above (when a claim is present) so statuses are fresh here.
|
|
14227
|
+
# Mirrors the evidence/held-out gate arms below. No-op safe:
|
|
14228
|
+
# council_checklist_gate returns 0 (pass) when there is no checklist
|
|
14229
|
+
# results file or when no critical items are failing, so this branch
|
|
14230
|
+
# never fires on those projects. Gate output is written by the gate.
|
|
14231
|
+
elif [ "$_completion_claimed" = 1 ] && type council_checklist_gate &>/dev/null && ! council_checklist_gate; then
|
|
14232
|
+
log_warn "Completion claim rejected: critical checklist item(s) failing (hard gate)."
|
|
14233
|
+
log_warn " Details under .loki/council/gate-block.json"
|
|
14234
|
+
# Fall through; keep iterating until critical checklist items pass.
|
|
14164
14235
|
# v7.19.1: the verified-completion evidence gate must also guard the
|
|
14165
14236
|
# DEFAULT completion route (a completion claim via loki_complete_task
|
|
14166
14237
|
# / the completion-promise text), not only the interval-gated council
|