loki-mode 7.125.0 → 7.127.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 +1 -0
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/cockpit-render.sh +358 -0
- package/autonomy/loki +579 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/static/index.html +143 -94
- package/docs/COCKPIT-SPEC.md +70 -0
- package/docs/COCKPIT-UX-MANDATE.md +53 -0
- package/docs/INSTALLATION.md +2 -2
- package/loki-ts/dist/cockpit.js +19 -0
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -483,6 +483,7 @@ Status legend: "E2E-verified" means we run real spec-to-code builds on it oursel
|
|
|
483
483
|
| `loki modernize heal <path>` | Legacy system healing (archaeology, stabilize, isolate, modernize, validate -- v6.67.0; was: `loki heal`) |
|
|
484
484
|
| `loki pause` / `resume` | Pause/resume after current session |
|
|
485
485
|
| `loki status` | Show current status |
|
|
486
|
+
| `loki cockpit` | Live multi-repo status as an inline terminal image (Kitty/iTerm2/WezTerm/Ghostty); text + dashboard fallback elsewhere (v7.126.0) |
|
|
486
487
|
| `loki dashboard` | Open web dashboard |
|
|
487
488
|
| `loki preview` | Print running app URL and open in browser (Live App Preview, v7.24.0; was: `loki open`) |
|
|
488
489
|
| `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] |
|
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, 8 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.127.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
408
408
|
|
|
409
409
|
---
|
|
410
410
|
|
|
411
|
-
**v7.
|
|
411
|
+
**v7.127.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.127.0
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# autonomy/lib/cockpit-render.sh -- state gathering + render dispatch for
|
|
3
|
+
# `loki cockpit`. Thin wrapper: it builds a CockpitState JSON from the machine
|
|
4
|
+
# registry + each project's .loki state, then pipes it to the pure Bun render
|
|
5
|
+
# pipeline (loki-ts/src/cockpit/cli.ts). All the SVG/PNG/terminal-encode logic
|
|
6
|
+
# lives there so it stays bun-testable; this file only does I/O and fallback.
|
|
7
|
+
#
|
|
8
|
+
# Contract with the TS entry:
|
|
9
|
+
# stdin : CockpitState JSON
|
|
10
|
+
# stdout : raw terminal image escape (image path), empty on fallback
|
|
11
|
+
# exit 0 : image emitted ; exit 3 : fallback (reason on stderr as FALLBACK\t..)
|
|
12
|
+
# other : hard error (bun missing etc.) -> caller falls back too
|
|
13
|
+
#
|
|
14
|
+
# Nothing here changes cmd_watch or any existing command.
|
|
15
|
+
|
|
16
|
+
# cockpit_gather_state <skill_dir> <focus_repo_or_empty>
|
|
17
|
+
# Emits a CockpitState JSON on stdout. Never fails hard: missing files degrade
|
|
18
|
+
# to empty/default fields so the render always has something to show.
|
|
19
|
+
cockpit_gather_state() {
|
|
20
|
+
local skill_dir="$1"
|
|
21
|
+
local focus_repo="${2:-}"
|
|
22
|
+
|
|
23
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
24
|
+
# No python: emit a minimal single-repo state from the cwd only.
|
|
25
|
+
printf '{"run":"%s","iteration":0,"phase":"unknown","tier":"","provider":"","verdict":"unknown","budgetUsd":0,"gates":[],"council":[],"fleet":[]}\n' \
|
|
26
|
+
"$(basename "$(pwd)")"
|
|
27
|
+
return 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
LOKI_CK_SKILL="$skill_dir" LOKI_CK_FOCUS="$focus_repo" LOKI_CK_CWD="$(pwd)" \
|
|
31
|
+
python3 - <<'PYCOCKPIT' 2>/dev/null || printf '{"run":"cockpit","iteration":0,"phase":"unknown","tier":"","provider":"","verdict":"unknown","budgetUsd":0,"gates":[],"council":[],"fleet":[]}\n'
|
|
32
|
+
import json, os, sys
|
|
33
|
+
|
|
34
|
+
skill = os.environ.get("LOKI_CK_SKILL", ".")
|
|
35
|
+
focus = (os.environ.get("LOKI_CK_FOCUS") or "").strip()
|
|
36
|
+
cwd = os.environ.get("LOKI_CK_CWD") or os.getcwd()
|
|
37
|
+
sys.path.insert(0, skill)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def read_json(path):
|
|
41
|
+
try:
|
|
42
|
+
with open(path) as f:
|
|
43
|
+
return json.load(f)
|
|
44
|
+
except Exception:
|
|
45
|
+
return {}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def project_state(path):
|
|
49
|
+
"""Best-effort per-project state from its .loki/ files."""
|
|
50
|
+
loki = os.path.join(path, ".loki")
|
|
51
|
+
st = read_json(os.path.join(loki, "autonomy-state.json"))
|
|
52
|
+
ev = read_json(os.path.join(loki, "verify", "evidence.json"))
|
|
53
|
+
|
|
54
|
+
iteration = st.get("iterationCount", st.get("iteration", 0)) or 0
|
|
55
|
+
phase = st.get("phase") or st.get("status") or ""
|
|
56
|
+
|
|
57
|
+
verdict = "unknown"
|
|
58
|
+
v = str(ev.get("verdict", "")).upper()
|
|
59
|
+
if v in ("PASS", "VERIFIED"):
|
|
60
|
+
verdict = "verified"
|
|
61
|
+
elif v in ("BLOCKED", "FAIL", "FAILED"):
|
|
62
|
+
verdict = "failed"
|
|
63
|
+
elif v in ("WORKING", "PARTIAL", "INCONCLUSIVE"):
|
|
64
|
+
verdict = "working"
|
|
65
|
+
elif v:
|
|
66
|
+
verdict = "pending"
|
|
67
|
+
|
|
68
|
+
gates = []
|
|
69
|
+
for g in ev.get("deterministic_gates", []) or []:
|
|
70
|
+
gs = str(g.get("status", "")).lower()
|
|
71
|
+
status = {"pass": "pass", "fail": "fail", "failed": "fail",
|
|
72
|
+
"skipped": "skip", "skip": "skip"}.get(gs, "pending")
|
|
73
|
+
# runner/scanner names the tool; summary is the one-line evidence.
|
|
74
|
+
runner = g.get("runner") or g.get("scanner") or ""
|
|
75
|
+
gates.append({
|
|
76
|
+
"name": g.get("gate", "gate"),
|
|
77
|
+
"status": status,
|
|
78
|
+
"runner": str(runner)[:24],
|
|
79
|
+
"evidence": str(g.get("summary", ""))[:80],
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
# Default model tier per reviewer (matches the standing council roster).
|
|
83
|
+
REVIEWER_TIER = {
|
|
84
|
+
"architecture-strategist": "opus",
|
|
85
|
+
"maintainer-mergeability": "opus",
|
|
86
|
+
"security-sentinel": "opus",
|
|
87
|
+
"test-coverage-auditor": "sonnet",
|
|
88
|
+
"performance-oracle": "opus",
|
|
89
|
+
"dependency-analyst": "sonnet",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Council votes: .loki/council/*.json, each with a vote-ish field. Skips the
|
|
93
|
+
# aggregate state.json (no per-reviewer vote) so only real reviewer entries
|
|
94
|
+
# populate the strip.
|
|
95
|
+
council = []
|
|
96
|
+
cdir = os.path.join(loki, "council")
|
|
97
|
+
try:
|
|
98
|
+
for fn in sorted(os.listdir(cdir)):
|
|
99
|
+
if not fn.endswith(".json") or fn == "state.json":
|
|
100
|
+
continue
|
|
101
|
+
cj = read_json(os.path.join(cdir, fn))
|
|
102
|
+
raw = str(cj.get("vote") or cj.get("verdict") or "").lower()
|
|
103
|
+
vote = "pending"
|
|
104
|
+
if "approve" in raw:
|
|
105
|
+
vote = "approve"
|
|
106
|
+
elif "reject" in raw:
|
|
107
|
+
vote = "reject"
|
|
108
|
+
elif "concern" in raw:
|
|
109
|
+
vote = "concern"
|
|
110
|
+
reviewer = cj.get("reviewer") or cj.get("name") or os.path.splitext(fn)[0]
|
|
111
|
+
reviewer = str(reviewer)
|
|
112
|
+
tier = cj.get("tier") or cj.get("model_tier") or REVIEWER_TIER.get(reviewer, "")
|
|
113
|
+
council.append({"reviewer": reviewer[:24], "vote": vote, "tier": str(tier)[:8]})
|
|
114
|
+
except Exception:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
"iteration": int(iteration) if isinstance(iteration, (int, float)) else 0,
|
|
119
|
+
"phase": phase or "idle",
|
|
120
|
+
"verdict": verdict,
|
|
121
|
+
"gates": gates,
|
|
122
|
+
"council": council,
|
|
123
|
+
"budgetUsd": float(st.get("cost_usd", 0.0) or 0.0),
|
|
124
|
+
"tier": st.get("tier", "") or "",
|
|
125
|
+
"provider": st.get("provider", "") or "",
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# Fleet from the registry; graceful empty on any failure.
|
|
130
|
+
fleet = []
|
|
131
|
+
runs = []
|
|
132
|
+
try:
|
|
133
|
+
from dashboard import registry
|
|
134
|
+
runs = registry.get_fleet_runs(include_inactive=True)
|
|
135
|
+
except Exception:
|
|
136
|
+
runs = []
|
|
137
|
+
|
|
138
|
+
for r in runs:
|
|
139
|
+
fleet.append({
|
|
140
|
+
"name": r.get("name") or os.path.basename(r.get("path", "") or "project"),
|
|
141
|
+
"path": r.get("path", "") or "",
|
|
142
|
+
"phase": r.get("phase", "") or "",
|
|
143
|
+
"iteration": r.get("iteration", 0) or 0,
|
|
144
|
+
"status": r.get("status", "") or "",
|
|
145
|
+
"running": bool(r.get("running")),
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
# Focused run: --repo wins; else first running fleet run; else cwd.
|
|
149
|
+
focus_path = focus or cwd
|
|
150
|
+
focus_entry = None
|
|
151
|
+
for r in runs:
|
|
152
|
+
rp = os.path.abspath(r.get("path", "") or "")
|
|
153
|
+
if rp and rp == os.path.abspath(focus_path):
|
|
154
|
+
focus_entry = r
|
|
155
|
+
break
|
|
156
|
+
if focus_entry is None and not focus:
|
|
157
|
+
for r in runs:
|
|
158
|
+
if r.get("running"):
|
|
159
|
+
focus_entry = r
|
|
160
|
+
focus_path = r.get("path", "") or cwd
|
|
161
|
+
break
|
|
162
|
+
|
|
163
|
+
ps = project_state(focus_path)
|
|
164
|
+
run_name = (focus_entry or {}).get("name") if focus_entry else os.path.basename(os.path.abspath(focus_path))
|
|
165
|
+
|
|
166
|
+
# Registry values override file reads where present (registry is fresher).
|
|
167
|
+
if focus_entry:
|
|
168
|
+
if focus_entry.get("iteration"):
|
|
169
|
+
ps["iteration"] = focus_entry["iteration"]
|
|
170
|
+
if focus_entry.get("phase"):
|
|
171
|
+
ps["phase"] = focus_entry["phase"]
|
|
172
|
+
if focus_entry.get("cost_usd"):
|
|
173
|
+
ps["budgetUsd"] = float(focus_entry["cost_usd"])
|
|
174
|
+
|
|
175
|
+
state = {
|
|
176
|
+
"run": run_name or "cockpit",
|
|
177
|
+
"iteration": ps["iteration"],
|
|
178
|
+
"phase": ps["phase"],
|
|
179
|
+
"tier": ps["tier"] or (os.environ.get("LOKI_SESSION_MODEL", "")),
|
|
180
|
+
"provider": ps["provider"] or (os.environ.get("LOKI_PROVIDER", "claude")),
|
|
181
|
+
"verdict": ps["verdict"],
|
|
182
|
+
"budgetUsd": ps["budgetUsd"],
|
|
183
|
+
"budgetLimitUsd": float(os.environ.get("LOKI_MAX_BUDGET_USD", "0") or 0) or None,
|
|
184
|
+
"freshness": "now",
|
|
185
|
+
"gates": ps["gates"],
|
|
186
|
+
"council": ps["council"],
|
|
187
|
+
"fleet": fleet,
|
|
188
|
+
}
|
|
189
|
+
print(json.dumps(state))
|
|
190
|
+
PYCOCKPIT
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# cockpit_render <skill_dir> <ts_dir> <protocol> <no_image> <focus_repo>
|
|
194
|
+
# Returns: 0 (image emitted to stdout), 3 (fallback), or 2 (bun unavailable).
|
|
195
|
+
# On fallback / bun-missing the caller is responsible for the text summary.
|
|
196
|
+
cockpit_render() {
|
|
197
|
+
local skill_dir="$1" ts_dir="$2" protocol="${3:-auto}" no_image="${4:-0}" focus_repo="${5:-}"
|
|
198
|
+
|
|
199
|
+
# Prefer the shipped bundle (dist/cockpit.js -- present in npm/Docker/brew
|
|
200
|
+
# installs, which ship loki-ts/dist/ but not src/); fall back to the source
|
|
201
|
+
# entry for in-repo development.
|
|
202
|
+
local entry="$ts_dir/dist/cockpit.js"
|
|
203
|
+
[ -f "$entry" ] || entry="$ts_dir/src/cockpit/cli.ts"
|
|
204
|
+
if ! command -v bun >/dev/null 2>&1 || [ ! -f "$entry" ]; then
|
|
205
|
+
return 2
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
local args=(--protocol "$protocol")
|
|
209
|
+
[ "$no_image" = "1" ] && args+=(--no-image)
|
|
210
|
+
|
|
211
|
+
# Gather -> render. Preserve the TS exit code (0 image / 3 fallback).
|
|
212
|
+
cockpit_gather_state "$skill_dir" "$focus_repo" | bun "$entry" "${args[@]}"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#===============================================================================
|
|
216
|
+
# E3: keyboard interactivity for `loki cockpit --follow`.
|
|
217
|
+
# These live here (sourced by cmd_cockpit) so they can be extracted + unit
|
|
218
|
+
# tested without pulling in the whole autonomy/loki CLI.
|
|
219
|
+
#===============================================================================
|
|
220
|
+
|
|
221
|
+
# Pure key -> action mapping. Takes a normalized key token, echoes one action
|
|
222
|
+
# word. Side-effect-free so it is trivially unit-testable.
|
|
223
|
+
# TAB / RIGHT / DOWN -> next (cycle focus forward across the fleet)
|
|
224
|
+
# LEFT / UP -> prev (cycle focus backward)
|
|
225
|
+
# e -> explain (print verify evidence for the focused run)
|
|
226
|
+
# s -> steer (print how to steer the focused run)
|
|
227
|
+
# q / ESC / Ctrl-C -> quit
|
|
228
|
+
# anything else -> noop
|
|
229
|
+
cockpit_handle_key() {
|
|
230
|
+
case "$1" in
|
|
231
|
+
TAB|RIGHT|DOWN) echo "next" ;;
|
|
232
|
+
LEFT|UP) echo "prev" ;;
|
|
233
|
+
e|E) echo "explain" ;;
|
|
234
|
+
s|S) echo "steer" ;;
|
|
235
|
+
q|Q|ESC|$'\x03') echo "quit" ;;
|
|
236
|
+
*) echo "noop" ;;
|
|
237
|
+
esac
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Read one keypress (non-blocking, no redraw stall) and normalize it to a token
|
|
241
|
+
# cockpit_handle_key understands. Echoes the token, or nothing if no key was
|
|
242
|
+
# pressed within the timeout. Arrow keys arrive as ESC [ A/B/C/D; a lone ESC (no
|
|
243
|
+
# following bytes) normalizes to ESC (quit). Only usable on a TTY.
|
|
244
|
+
# $1 = timeout seconds (fractional ok; feeds `read -t`)
|
|
245
|
+
cockpit_read_key() {
|
|
246
|
+
local timeout="${1:-2}"
|
|
247
|
+
local k rest
|
|
248
|
+
IFS= read -rsn1 -t "$timeout" k 2>/dev/null || return 1
|
|
249
|
+
case "$k" in
|
|
250
|
+
$'\t') echo "TAB"; return 0 ;;
|
|
251
|
+
$'\x03') echo "ESC"; return 0 ;; # Ctrl-C byte if trapped as data
|
|
252
|
+
$'\x1b')
|
|
253
|
+
# Possible escape sequence: grab up to 2 more bytes without blocking.
|
|
254
|
+
IFS= read -rsn2 -t 0.01 rest 2>/dev/null || rest=""
|
|
255
|
+
case "$rest" in
|
|
256
|
+
'[A') echo "UP" ;;
|
|
257
|
+
'[B') echo "DOWN" ;;
|
|
258
|
+
'[C') echo "RIGHT" ;;
|
|
259
|
+
'[D') echo "LEFT" ;;
|
|
260
|
+
*) echo "ESC" ;;
|
|
261
|
+
esac
|
|
262
|
+
return 0 ;;
|
|
263
|
+
'') return 1 ;; # timed out, no key
|
|
264
|
+
*) echo "$k"; return 0 ;;
|
|
265
|
+
esac
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# Echo the fleet repo paths (one per line) so the follow loop can cycle focus.
|
|
269
|
+
# Empty output = no fleet (cycling is a no-op). Best-effort, never errors.
|
|
270
|
+
cockpit_fleet_paths() {
|
|
271
|
+
local skill_dir="$1"
|
|
272
|
+
local state_json
|
|
273
|
+
state_json="$(cockpit_gather_state "$skill_dir" "" 2>/dev/null || true)"
|
|
274
|
+
[ -z "$state_json" ] && return 0
|
|
275
|
+
command -v python3 >/dev/null 2>&1 || return 0
|
|
276
|
+
LOKI_CK_STATE="$state_json" python3 - <<'PYPATHS' 2>/dev/null || true
|
|
277
|
+
import json, os
|
|
278
|
+
s = json.loads(os.environ.get("LOKI_CK_STATE") or "{}")
|
|
279
|
+
for r in s.get("fleet") or []:
|
|
280
|
+
p = r.get("path") or ""
|
|
281
|
+
if p:
|
|
282
|
+
print(p)
|
|
283
|
+
PYPATHS
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
# Given the current focus path, a direction (next|prev), and a newline list of
|
|
287
|
+
# fleet paths, echo the path to focus after cycling. Wraps around. If focus is
|
|
288
|
+
# not in the list, next -> first entry, prev -> last entry. Empty list echoes the
|
|
289
|
+
# current focus unchanged. Pure, unit-testable.
|
|
290
|
+
# $1 = current focus path (may be empty) $2 = next|prev $3 = newline paths
|
|
291
|
+
cockpit_cycle_focus() {
|
|
292
|
+
local cur="$1" dir="$2" paths="$3"
|
|
293
|
+
[ -z "$paths" ] && { echo "$cur"; return 0; }
|
|
294
|
+
local -a arr=()
|
|
295
|
+
local line
|
|
296
|
+
while IFS= read -r line; do
|
|
297
|
+
[ -n "$line" ] && arr+=("$line")
|
|
298
|
+
done <<< "$paths"
|
|
299
|
+
local n="${#arr[@]}"
|
|
300
|
+
[ "$n" -eq 0 ] && { echo "$cur"; return 0; }
|
|
301
|
+
# Find current index.
|
|
302
|
+
local idx=-1 i
|
|
303
|
+
for i in "${!arr[@]}"; do
|
|
304
|
+
if [ "${arr[$i]}" = "$cur" ]; then idx="$i"; break; fi
|
|
305
|
+
done
|
|
306
|
+
local target
|
|
307
|
+
if [ "$idx" -lt 0 ]; then
|
|
308
|
+
# Not in list: next -> first, prev -> last.
|
|
309
|
+
if [ "$dir" = "prev" ]; then target=$((n - 1)); else target=0; fi
|
|
310
|
+
elif [ "$dir" = "prev" ]; then
|
|
311
|
+
target=$(( (idx - 1 + n) % n ))
|
|
312
|
+
else
|
|
313
|
+
target=$(( (idx + 1) % n ))
|
|
314
|
+
fi
|
|
315
|
+
echo "${arr[$target]}"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# Print the verify evidence ("explain") for the focused run. Reads the run's
|
|
319
|
+
# .loki/verify/evidence.json directly. Never fabricates: says so if absent.
|
|
320
|
+
cockpit_explain_focus() {
|
|
321
|
+
local focus_repo="$1"
|
|
322
|
+
local repo="${focus_repo:-$(pwd)}"
|
|
323
|
+
local ev="$repo/.loki/verify/evidence.json"
|
|
324
|
+
printf '\n'
|
|
325
|
+
printf 'verify --explain (%s)\n' "$repo"
|
|
326
|
+
if [ ! -f "$ev" ] || ! command -v python3 >/dev/null 2>&1; then
|
|
327
|
+
echo " (no evidence.json for this run yet)"
|
|
328
|
+
return 0
|
|
329
|
+
fi
|
|
330
|
+
LOKI_CK_EV="$ev" python3 - <<'PYEV' 2>/dev/null || echo " (could not read evidence)"
|
|
331
|
+
import json, os
|
|
332
|
+
try:
|
|
333
|
+
ev = json.load(open(os.environ["LOKI_CK_EV"]))
|
|
334
|
+
except Exception:
|
|
335
|
+
print(" (could not read evidence)"); raise SystemExit(0)
|
|
336
|
+
print(f" Verdict {str(ev.get('verdict','?')).upper()}")
|
|
337
|
+
checks = ev.get("deterministic_gates") or ev.get("checks") or ev.get("gates") or []
|
|
338
|
+
for c in checks[:12]:
|
|
339
|
+
name = c.get("gate") or c.get("name") or c.get("id") or "?"
|
|
340
|
+
status = c.get("status") or c.get("result") or "?"
|
|
341
|
+
print(f" {name:<28} {status}")
|
|
342
|
+
reason = ev.get("reason") or ev.get("summary")
|
|
343
|
+
if reason:
|
|
344
|
+
print(f" Reason {reason}")
|
|
345
|
+
PYEV
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
# Print a steering hint for the focused run. Steering is opt-in prompt injection;
|
|
349
|
+
# this only tells the operator how, it does not inject anything.
|
|
350
|
+
cockpit_steer_hint() {
|
|
351
|
+
local focus_repo="$1"
|
|
352
|
+
local repo="${focus_repo:-$(pwd)}"
|
|
353
|
+
printf '\n'
|
|
354
|
+
echo "Steer this run"
|
|
355
|
+
echo " Drop a note the next iteration will read:"
|
|
356
|
+
echo " echo 'your guidance' > $repo/.loki/steering.md"
|
|
357
|
+
echo " Or open the dashboard chat: loki dashboard"
|
|
358
|
+
}
|