loki-mode 7.117.0 → 7.117.1
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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +3 -2
- package/autonomy/run.sh +190 -9
- package/autonomy/spec-interrogation.sh +38 -0
- package/autonomy/verify.sh +16 -0
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- 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/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.117.
|
|
6
|
+
# Loki Mode v7.117.1
|
|
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.117.
|
|
411
|
+
**v7.117.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.117.
|
|
1
|
+
7.117.1
|
|
@@ -1753,7 +1753,7 @@ council_evidence_gate() {
|
|
|
1753
1753
|
local test_inconclusive_reason=""
|
|
1754
1754
|
if [ -f "$tr_file" ]; then
|
|
1755
1755
|
local test_status
|
|
1756
|
-
test_status=$(_TR_FILE="$tr_file" python3
|
|
1756
|
+
test_status=$(_TR_FILE="$tr_file" python3 <<'PYEOF' 2>/dev/null || echo "INCONCLUSIVE:none:true"
|
|
1757
1757
|
import json, os, sys
|
|
1758
1758
|
tr_file = os.environ['_TR_FILE']
|
|
1759
1759
|
try:
|
|
@@ -1781,7 +1781,8 @@ elif status == 'no_tests_run' or passed is not True:
|
|
|
1781
1781
|
print('INCONCLUSIVE:%s:true' % runner)
|
|
1782
1782
|
else:
|
|
1783
1783
|
print('PASS:%s:true' % runner)
|
|
1784
|
-
|
|
1784
|
+
PYEOF
|
|
1785
|
+
)
|
|
1785
1786
|
local _verdict="${test_status%%:*}"
|
|
1786
1787
|
local _rest="${test_status#*:}"
|
|
1787
1788
|
test_runner="${_rest%%:*}"
|
package/autonomy/run.sh
CHANGED
|
@@ -1382,8 +1382,14 @@ _parse_json_field() {
|
|
|
1382
1382
|
if command -v python3 >/dev/null 2>&1; then
|
|
1383
1383
|
python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get(sys.argv[2],''))" "$file" "$field" 2>/dev/null
|
|
1384
1384
|
else
|
|
1385
|
-
# Shell fallback: extract value for simple flat JSON
|
|
1386
|
-
|
|
1385
|
+
# Shell fallback (no python3): extract value for simple flat JSON. Handles
|
|
1386
|
+
# BOTH quoted string values ("kind":"wrapper") and bare numeric values
|
|
1387
|
+
# ("ppid":206). The prior impl stripped the leading quote's content to empty
|
|
1388
|
+
# for string fields -- broke "kind" parsing on python3-less hosts, which
|
|
1389
|
+
# would drop wrapper entries into the legacy child path (loki-mode #92).
|
|
1390
|
+
sed 's/.*"'"$field"'":[[:space:]]*//' "$file" 2>/dev/null \
|
|
1391
|
+
| sed 's/^"//' \
|
|
1392
|
+
| sed 's/[",}].*//' | head -1
|
|
1387
1393
|
fi
|
|
1388
1394
|
}
|
|
1389
1395
|
|
|
@@ -1435,6 +1441,93 @@ kill_registered_pid() {
|
|
|
1435
1441
|
unregister_pid "$pid"
|
|
1436
1442
|
}
|
|
1437
1443
|
|
|
1444
|
+
# Register the CURRENTLY-RUNNING loki-run wrapper itself in the registry.
|
|
1445
|
+
# The wrapper registers its children but historically never itself, so a wrapper
|
|
1446
|
+
# whose launching session dies is never reaped (loki-mode #92). We record the
|
|
1447
|
+
# LAUNCHER's mortal pid (NOT $$ -- $$ is the wrapper's own pid, which would make
|
|
1448
|
+
# the parent-death check always succeed and the reaper INERT; NOT $PPID either --
|
|
1449
|
+
# in the detached setsid/nohup path bash caches getppid()==1 at exec, and kill -0 1
|
|
1450
|
+
# is always true -> also inert). The launcher exports LOKI_LAUNCHER_PID=$$ at each
|
|
1451
|
+
# backgrounding site; that launcher shell exits immediately after backgrounding, so
|
|
1452
|
+
# the recorded ppid genuinely dies and the parent-death precondition CAN fire. For
|
|
1453
|
+
# the foreground path LOKI_LAUNCHER_PID is unset -> $PPID (the user's shell) is the
|
|
1454
|
+
# correct mortal fallback. Reap-vs-spare is then decided by the LIVENESS predicate,
|
|
1455
|
+
# not by parentage alone. Uses a "kind":"wrapper" tag so cleanup_orphan_pids routes
|
|
1456
|
+
# only wrapper entries through the predicate and keeps child entries byte-identical.
|
|
1457
|
+
register_self_wrapper() {
|
|
1458
|
+
[ -z "$PID_REGISTRY_DIR" ] && init_pid_registry
|
|
1459
|
+
local launcher_ppid="${LOKI_LAUNCHER_PID:-$PPID}"
|
|
1460
|
+
case "$launcher_ppid" in ''|*[!0-9]*) launcher_ppid="$PPID" ;; esac
|
|
1461
|
+
local entry_file="$PID_REGISTRY_DIR/$$.json"
|
|
1462
|
+
cat > "$entry_file" << EOF
|
|
1463
|
+
{"pid":$$,"label":"loki-wrapper","started":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","ppid":$launcher_ppid,"kind":"wrapper","extra":""}
|
|
1464
|
+
EOF
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
# Pure, side-effect-free reap predicate (loki-mode #92). ALL inputs are
|
|
1468
|
+
# pre-computed scalars (0/1 booleans or integer ms); NO stat/kill/pgrep inside so
|
|
1469
|
+
# it is hermetically unit-testable. Prints "1" (reap) or "0" (spare); returns 0.
|
|
1470
|
+
# Reap ONLY when: parent dead AND no live engine child AND idle past the budget.
|
|
1471
|
+
# Mirrors the SaaS BFF sweepStuckBuilds and the #91 worker "no-activity != dead"
|
|
1472
|
+
# symmetry: a genuinely-detached run that keeps writing .loki activity is spared.
|
|
1473
|
+
# parent_alive: 1 if recorded ppid is still alive, else 0
|
|
1474
|
+
# last_activity_ms: epoch-ms of most recent .loki activity (0 if none found)
|
|
1475
|
+
# now_ms: current epoch-ms
|
|
1476
|
+
# idle_budget_ms: generous idle budget (default 900000 = 15 min)
|
|
1477
|
+
# has_live_child: 1 if wrapper has a live ENGINE child (claude/node), else 0
|
|
1478
|
+
shouldReapOrphan() {
|
|
1479
|
+
local parent_alive="$1" last_activity_ms="$2" now_ms="$3" idle_budget_ms="$4" has_live_child="$5"
|
|
1480
|
+
if [ "$parent_alive" = "0" ] && [ "$has_live_child" = "0" ] \
|
|
1481
|
+
&& [ $(( now_ms - last_activity_ms )) -ge "$idle_budget_ms" ]; then
|
|
1482
|
+
echo 1
|
|
1483
|
+
else
|
|
1484
|
+
echo 0
|
|
1485
|
+
fi
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
# Most-recent .loki activity as epoch-ms across events.jsonl, signals/*,
|
|
1489
|
+
# autonomy-state.json. Cross-platform stat (macOS -f %m / Linux -c %Y). On a stat
|
|
1490
|
+
# PARSE FAILURE we default to now_ms (SPARE), never 0 -- a wrong flag must not make a
|
|
1491
|
+
# live run look maximally idle and get reaped. "No activity files exist" legitimately
|
|
1492
|
+
# yields 0 (reap-eligible); that is distinct from a stat error.
|
|
1493
|
+
_loki_last_activity_ms() {
|
|
1494
|
+
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
1495
|
+
local newest=0 f mt stat_ok=0
|
|
1496
|
+
local candidates=()
|
|
1497
|
+
[ -f "$loki_dir/events.jsonl" ] && candidates+=("$loki_dir/events.jsonl")
|
|
1498
|
+
[ -f "$loki_dir/autonomy-state.json" ] && candidates+=("$loki_dir/autonomy-state.json")
|
|
1499
|
+
if [ -d "$loki_dir/signals" ]; then
|
|
1500
|
+
for f in "$loki_dir/signals"/*; do [ -e "$f" ] && candidates+=("$f"); done
|
|
1501
|
+
fi
|
|
1502
|
+
for f in "${candidates[@]:-}"; do
|
|
1503
|
+
[ -e "$f" ] || continue
|
|
1504
|
+
mt=$(stat -f %m "$f" 2>/dev/null) || mt=""
|
|
1505
|
+
[ -z "$mt" ] && { mt=$(stat -c %Y "$f" 2>/dev/null) || mt=""; }
|
|
1506
|
+
case "$mt" in ''|*[!0-9]*) continue ;; esac
|
|
1507
|
+
stat_ok=1
|
|
1508
|
+
[ "$mt" -gt "$newest" ] && newest="$mt"
|
|
1509
|
+
done
|
|
1510
|
+
# candidates present but every stat failed -> parse failure -> spare (now_ms)
|
|
1511
|
+
if [ "${#candidates[@]}" -gt 0 ] && [ "$stat_ok" = "0" ]; then
|
|
1512
|
+
echo "$(( $(date +%s) * 1000 ))"
|
|
1513
|
+
return 0
|
|
1514
|
+
fi
|
|
1515
|
+
echo "$(( newest * 1000 ))"
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
# Count LIVE ENGINE children of a wrapper (claude/node), EXCLUDING sleep and the
|
|
1519
|
+
# status/resource monitors. The observed 3h orphan had only sleep children, so a
|
|
1520
|
+
# naive `pgrep -P` (any child) would falsely spare it; the comm filter is what makes
|
|
1521
|
+
# the reaper actually fire on the real orphan shape. Prints 1 (has engine child) or 0.
|
|
1522
|
+
_loki_has_live_engine_child() {
|
|
1523
|
+
local wrapper_pid="$1" c cc
|
|
1524
|
+
for c in $(pgrep -P "$wrapper_pid" 2>/dev/null); do
|
|
1525
|
+
cc=$(ps -o comm= -p "$c" 2>/dev/null)
|
|
1526
|
+
case "$cc" in *claude*|*node*) echo 1; return 0 ;; esac
|
|
1527
|
+
done
|
|
1528
|
+
echo 0
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1438
1531
|
# Scan registry for orphaned processes and kill them
|
|
1439
1532
|
# Called on startup and by `loki cleanup`
|
|
1440
1533
|
# Returns: number of orphans killed
|
|
@@ -1457,6 +1550,9 @@ cleanup_orphan_pids() {
|
|
|
1457
1550
|
''|*[!0-9]*) continue ;;
|
|
1458
1551
|
esac
|
|
1459
1552
|
|
|
1553
|
+
# Never reap the currently-running wrapper itself (loki-mode #92 self-skip).
|
|
1554
|
+
[ "$pid" = "$$" ] && continue
|
|
1555
|
+
|
|
1460
1556
|
if kill -0 "$pid" 2>/dev/null; then
|
|
1461
1557
|
# Process is alive -- check if its parent session is dead
|
|
1462
1558
|
local ppid_val=""
|
|
@@ -1464,7 +1560,30 @@ cleanup_orphan_pids() {
|
|
|
1464
1560
|
|
|
1465
1561
|
# Validate ppid_val is numeric before using with kill
|
|
1466
1562
|
case "$ppid_val" in ''|*[!0-9]*) ppid_val="" ;; esac
|
|
1467
|
-
|
|
1563
|
+
|
|
1564
|
+
# Route WRAPPER entries through the liveness predicate; CHILD entries
|
|
1565
|
+
# (no "kind" field) keep the exact parent-death-only behavior (#92).
|
|
1566
|
+
local kind=""
|
|
1567
|
+
kind=$(_parse_json_field "$entry_file" "kind") || true
|
|
1568
|
+
|
|
1569
|
+
if [ "$kind" = "wrapper" ]; then
|
|
1570
|
+
# Wrapper: reap only if orphaned AND idle-past-budget AND no live
|
|
1571
|
+
# engine child. Compute the impure inputs here (predicate stays pure).
|
|
1572
|
+
if [ -n "$ppid_val" ]; then
|
|
1573
|
+
local parent_alive=0
|
|
1574
|
+
kill -0 "$ppid_val" 2>/dev/null && parent_alive=1
|
|
1575
|
+
local last_activity_ms now_ms has_live_child idle_budget_ms
|
|
1576
|
+
last_activity_ms=$(_loki_last_activity_ms)
|
|
1577
|
+
now_ms=$(( $(date +%s) * 1000 ))
|
|
1578
|
+
has_live_child=$(_loki_has_live_engine_child "$pid")
|
|
1579
|
+
idle_budget_ms="${LOKI_WRAPPER_IDLE_BUDGET_MS:-900000}"
|
|
1580
|
+
if [ "$(shouldReapOrphan "$parent_alive" "$last_activity_ms" "$now_ms" "$idle_budget_ms" "$has_live_child")" = "1" ]; then
|
|
1581
|
+
log_warn "Reaping idle orphaned loki wrapper PID=$pid (parent $ppid_val dead, idle >=$((idle_budget_ms/60000))m, no engine child)" >&2
|
|
1582
|
+
kill_registered_pid "$pid"
|
|
1583
|
+
orphan_count=$((orphan_count + 1))
|
|
1584
|
+
fi
|
|
1585
|
+
fi
|
|
1586
|
+
elif [ -n "$ppid_val" ] && [ "$ppid_val" != "$$" ]; then
|
|
1468
1587
|
if ! kill -0 "$ppid_val" 2>/dev/null; then
|
|
1469
1588
|
# Parent is dead -- this is an orphan
|
|
1470
1589
|
local label=""
|
|
@@ -1498,6 +1617,9 @@ kill_all_registered() {
|
|
|
1498
1617
|
case "$pid" in
|
|
1499
1618
|
''|*[!0-9]*) continue ;;
|
|
1500
1619
|
esac
|
|
1620
|
+
# Never SIGKILL the running wrapper itself mid-shutdown (loki-mode #92):
|
|
1621
|
+
# it now self-registers, so it would otherwise appear in this sweep.
|
|
1622
|
+
[ "$pid" = "$$" ] && continue
|
|
1501
1623
|
kill_registered_pid "$pid"
|
|
1502
1624
|
done
|
|
1503
1625
|
}
|
|
@@ -9023,6 +9145,21 @@ _loki_zero_tests_executed() {
|
|
|
9023
9145
|
fi
|
|
9024
9146
|
return 1
|
|
9025
9147
|
;;
|
|
9148
|
+
go-test)
|
|
9149
|
+
# #89: `go test ./...` on a package with NO *_test.go EXITS 0 and prints
|
|
9150
|
+
# `? <pkg> [no test files]` -- so a Go project with source but no
|
|
9151
|
+
# tests would score `pass` (a mini fake-green; unlike vitest/pytest/
|
|
9152
|
+
# mocha which exit NON-zero on zero tests and are already not-pass, so
|
|
9153
|
+
# go-test is the only runner in this class that needs detection).
|
|
9154
|
+
# POSITIVE detection: a `[no test files]` line AND no ran-package result
|
|
9155
|
+
# (`ok `/`FAIL`). A mixed run (some pkgs tested) has `ok`, so it is NOT
|
|
9156
|
+
# downgraded. Verified: real passing prints `ok <pkg> 0.2s`.
|
|
9157
|
+
if printf '%s' "$_zt_out" | grep -qE '\[no test files\]' 2>/dev/null \
|
|
9158
|
+
&& ! printf '%s' "$_zt_out" | grep -qE '^(ok|FAIL|--- FAIL)' 2>/dev/null; then
|
|
9159
|
+
return 0
|
|
9160
|
+
fi
|
|
9161
|
+
return 1
|
|
9162
|
+
;;
|
|
9026
9163
|
*)
|
|
9027
9164
|
# Unknown/unparseable runner -> never claim zero-tests (safe default).
|
|
9028
9165
|
return 1
|
|
@@ -16171,6 +16308,47 @@ except Exception:
|
|
|
16171
16308
|
if type spec_interrogation_run &>/dev/null; then
|
|
16172
16309
|
spec_interrogation_run "$prd_path" || true
|
|
16173
16310
|
fi
|
|
16311
|
+
# #87: no-HITL fast-fail on an unresolved spec-INTERNAL contradiction.
|
|
16312
|
+
# A contradiction (class=contradictory) is NEVER auto-acked (P2-4) and only
|
|
16313
|
+
# a human sets confirmed=true, so in an AUTONOMOUS run it can never clear ->
|
|
16314
|
+
# the completion gate blocks EVERY iteration -> the loop grinds to
|
|
16315
|
+
# max-iterations (~20min, opaque). This run is already doomed; here we fail
|
|
16316
|
+
# FAST + HONEST + NAMED instead. Honesty: this is the no-HITL rule verbatim
|
|
16317
|
+
# ("when unsafe to decide, inconclusive-and-proceed, never fake-green") --
|
|
16318
|
+
# it NEVER declares done/green (inconclusive_spec_contradiction maps to a
|
|
16319
|
+
# terminal-failure exit, tested), it just replaces the opaque grind.
|
|
16320
|
+
# SCOPE (deliberately narrow, no regression): fires ONLY when
|
|
16321
|
+
# (a) non-interactive/no-HITL (a TTY human COULD resolve it -> let them), AND
|
|
16322
|
+
# (b) LOKI_ASSUMPTIONS_REQUIRE_CONFIRM != 1 (that knob means a human WILL
|
|
16323
|
+
# confirm -> do not pre-empt), AND
|
|
16324
|
+
# (c) there is >=1 UNRESOLVED class=contradictory entry (the contradiction-
|
|
16325
|
+
# specific count, NOT the broader high-unresolved which includes
|
|
16326
|
+
# auto-ackable non-contradictions).
|
|
16327
|
+
# Opt-out: LOKI_SPEC_CONTRADICTION_FASTFAIL=0.
|
|
16328
|
+
if [ "${LOKI_SPEC_CONTRADICTION_FASTFAIL:-1}" = "1" ] \
|
|
16329
|
+
&& [ ! -t 0 ] \
|
|
16330
|
+
&& [ "${LOKI_ASSUMPTIONS_REQUIRE_CONFIRM:-0}" != "1" ] \
|
|
16331
|
+
&& type spec_ledger_contradiction_unresolved_count &>/dev/null; then
|
|
16332
|
+
_sc_out="$(spec_ledger_contradiction_unresolved_count 2>/dev/null)"
|
|
16333
|
+
_sc_n="$(printf '%s' "$_sc_out" | head -1)"
|
|
16334
|
+
case "$_sc_n" in ''|*[!0-9]*) _sc_n=0 ;; esac
|
|
16335
|
+
if [ "$_sc_n" -ge 1 ]; then
|
|
16336
|
+
# Option C: name the contradicting clauses so `loki why` is actionable.
|
|
16337
|
+
local _sc_titles
|
|
16338
|
+
_sc_titles="$(printf '%s' "$_sc_out" | tail -n +2 | sed 's/^/ - /' | head -5)"
|
|
16339
|
+
log_error "Spec has ${_sc_n} unresolved internal contradiction(s); an autonomous run cannot resolve them (only a human can). Failing fast instead of grinding to max-iterations."
|
|
16340
|
+
[ -n "$_sc_titles" ] && printf '%s\n' "$_sc_titles" >&2
|
|
16341
|
+
if type _loki_write_last_error &>/dev/null; then
|
|
16342
|
+
_loki_write_last_error 0 "spec_contradiction" \
|
|
16343
|
+
"Spec is internally inconsistent (${_sc_n} unresolved contradiction(s)); resolve the conflicting requirements, then re-run."
|
|
16344
|
+
fi
|
|
16345
|
+
save_state "$retry" "inconclusive_spec_contradiction" 0
|
|
16346
|
+
if type emit_completion_summary &>/dev/null; then
|
|
16347
|
+
emit_completion_summary inconclusive_spec_contradiction 2>/dev/null || true
|
|
16348
|
+
fi
|
|
16349
|
+
return 20
|
|
16350
|
+
fi
|
|
16351
|
+
fi
|
|
16174
16352
|
fi
|
|
16175
16353
|
|
|
16176
16354
|
# Auto-derive completion promise from PRD (v6.10.0)
|
|
@@ -19039,13 +19217,13 @@ main() {
|
|
|
19039
19217
|
# pgid.
|
|
19040
19218
|
case "$_sess_launcher" in
|
|
19041
19219
|
setsid)
|
|
19042
|
-
LOKI_RUNNING_FROM_TEMP='' LOKI_OWN_SESSION=1 nohup setsid "$original_script" "${cmd_args[@]}" > "$log_file" 2>&1 & ;;
|
|
19220
|
+
LOKI_RUNNING_FROM_TEMP='' LOKI_OWN_SESSION=1 LOKI_LAUNCHER_PID=$$ nohup setsid "$original_script" "${cmd_args[@]}" > "$log_file" 2>&1 & ;;
|
|
19043
19221
|
perl-setsid)
|
|
19044
|
-
LOKI_RUNNING_FROM_TEMP='' LOKI_OWN_SESSION=1 nohup perl -e 'use POSIX qw(setsid); setsid(); exec @ARGV or exit 127;' "$original_script" "${cmd_args[@]}" > "$log_file" 2>&1 & ;;
|
|
19222
|
+
LOKI_RUNNING_FROM_TEMP='' LOKI_OWN_SESSION=1 LOKI_LAUNCHER_PID=$$ nohup perl -e 'use POSIX qw(setsid); setsid(); exec @ARGV or exit 127;' "$original_script" "${cmd_args[@]}" > "$log_file" 2>&1 & ;;
|
|
19045
19223
|
python-setsid)
|
|
19046
|
-
LOKI_RUNNING_FROM_TEMP='' LOKI_OWN_SESSION=1 nohup python3 -c 'import os,sys; os.setsid(); os.execvp(sys.argv[1], sys.argv[1:])' "$original_script" "${cmd_args[@]}" > "$log_file" 2>&1 & ;;
|
|
19224
|
+
LOKI_RUNNING_FROM_TEMP='' LOKI_OWN_SESSION=1 LOKI_LAUNCHER_PID=$$ nohup python3 -c 'import os,sys; os.setsid(); os.execvp(sys.argv[1], sys.argv[1:])' "$original_script" "${cmd_args[@]}" > "$log_file" 2>&1 & ;;
|
|
19047
19225
|
*)
|
|
19048
|
-
LOKI_RUNNING_FROM_TEMP='' nohup "$original_script" "${cmd_args[@]}" > "$log_file" 2>&1 & ;;
|
|
19226
|
+
LOKI_RUNNING_FROM_TEMP='' LOKI_LAUNCHER_PID=$$ nohup "$original_script" "${cmd_args[@]}" > "$log_file" 2>&1 & ;;
|
|
19049
19227
|
esac
|
|
19050
19228
|
local bg_pid=$!
|
|
19051
19229
|
echo "$bg_pid" > "$pid_file"
|
|
@@ -19224,8 +19402,11 @@ main() {
|
|
|
19224
19402
|
echo "${LOKI_SESSION_ID}" > ".loki/sessions/${LOKI_SESSION_ID}/session_id"
|
|
19225
19403
|
fi
|
|
19226
19404
|
|
|
19227
|
-
# Initialize PID registry
|
|
19405
|
+
# Initialize PID registry, self-register this wrapper (loki-mode #92 -- so a
|
|
19406
|
+
# future session can reap it if this run is orphaned+idle), then clean up
|
|
19407
|
+
# orphans from previous sessions.
|
|
19228
19408
|
init_pid_registry
|
|
19409
|
+
register_self_wrapper
|
|
19229
19410
|
local orphan_count
|
|
19230
19411
|
orphan_count=$(cleanup_orphan_pids)
|
|
19231
19412
|
if [ "$orphan_count" -gt 0 ]; then
|
|
@@ -19552,7 +19733,7 @@ main() {
|
|
|
19552
19733
|
case "$_final_status" in
|
|
19553
19734
|
council_approved|council_force_approved|completion_promise_fulfilled|force_stopped|paused|interrupted|budget_exceeded|stopped)
|
|
19554
19735
|
result=0 ;;
|
|
19555
|
-
failed|max_iterations_reached|max_retries_exceeded|policy_blocked)
|
|
19736
|
+
failed|max_iterations_reached|max_retries_exceeded|policy_blocked|inconclusive_spec_contradiction)
|
|
19556
19737
|
result=20 ;;
|
|
19557
19738
|
*)
|
|
19558
19739
|
# Unknown/running/exited terminal: leave $result as-is (nonzero on a
|
|
@@ -784,6 +784,44 @@ print(n)
|
|
|
784
784
|
' 2>/dev/null || printf '0'
|
|
785
785
|
}
|
|
786
786
|
|
|
787
|
+
# #87: the count of UNRESOLVED spec-INTERNAL CONTRADICTIONS (class=contradictory,
|
|
788
|
+
# not confirmed, not acknowledged). This is the SUBSET of high-unresolved that can
|
|
789
|
+
# NEVER clear in an autonomous run: contradictions are the only high entries that
|
|
790
|
+
# spec_ledger_acknowledge_all deliberately refuses to auto-ack (P2-4), and only a
|
|
791
|
+
# human sets confirmed=true. So >0 here means the completion gate will block every
|
|
792
|
+
# iteration -> the run is ALREADY doomed to grind to max-iterations. run.sh uses
|
|
793
|
+
# this at DISCOVERY to fail fast+honest (inconclusive_spec_contradiction) instead
|
|
794
|
+
# of the opaque grind. Distinct from spec_ledger_high_unresolved_count (which
|
|
795
|
+
# includes non-contradiction highs that CAN auto-ack and must NOT trigger fast-fail).
|
|
796
|
+
# Also prints the contradicting assumption titles (one per line after the count)
|
|
797
|
+
# so the terminal can NAME the cause (Option C).
|
|
798
|
+
spec_ledger_contradiction_unresolved_count() {
|
|
799
|
+
local dir
|
|
800
|
+
dir="$(_spec_ledger_dir)"
|
|
801
|
+
if [ ! -d "$dir" ]; then printf '0'; return 0; fi
|
|
802
|
+
_SL_DIR="$dir" python3 -c '
|
|
803
|
+
import glob, json, os
|
|
804
|
+
d = os.environ["_SL_DIR"]
|
|
805
|
+
n = 0
|
|
806
|
+
titles = []
|
|
807
|
+
for p in sorted(glob.glob(os.path.join(d, "a-*.json"))):
|
|
808
|
+
try:
|
|
809
|
+
with open(p) as f:
|
|
810
|
+
r = json.load(f)
|
|
811
|
+
except Exception:
|
|
812
|
+
continue
|
|
813
|
+
if (r.get("class") == "contradictory"
|
|
814
|
+
and not r.get("confirmed")
|
|
815
|
+
and not r.get("acknowledged")):
|
|
816
|
+
n += 1
|
|
817
|
+
t = r.get("title") or r.get("assumption") or r.get("finding") or "(unnamed contradiction)"
|
|
818
|
+
titles.append(str(t).replace("\n", " ")[:200])
|
|
819
|
+
print(n)
|
|
820
|
+
for t in titles:
|
|
821
|
+
print(t)
|
|
822
|
+
' 2>/dev/null || printf '0'
|
|
823
|
+
}
|
|
824
|
+
|
|
787
825
|
# Total ledger entries + high count, "total high" on one line. For summaries.
|
|
788
826
|
spec_ledger_counts() {
|
|
789
827
|
local dir
|
package/autonomy/verify.sh
CHANGED
|
@@ -334,6 +334,22 @@ _verify_zero_tests_executed() {
|
|
|
334
334
|
fi
|
|
335
335
|
return 1
|
|
336
336
|
;;
|
|
337
|
+
go-test)
|
|
338
|
+
# #89: `go test ./...` on a package with NO *_test.go files prints
|
|
339
|
+
# `? <pkg> [no test files]` and EXITS 0 (unlike vitest/pytest/mocha,
|
|
340
|
+
# which exit non-zero on zero tests and are already caught as not-pass).
|
|
341
|
+
# So a Go project with source but no tests would score `pass` -- a mini
|
|
342
|
+
# fake-green. Detect it POSITIVELY: at least one `[no test files]` line
|
|
343
|
+
# AND ZERO package results that actually ran (`ok `/`FAIL`). A mixed
|
|
344
|
+
# run (some pkgs tested -> `ok`, others not -> `[no test files]`) has
|
|
345
|
+
# `ok` present, so it is NOT downgraded (verified: real passing prints
|
|
346
|
+
# `ok <pkg> 0.2s`, a bare pkg prints `[no test files]`).
|
|
347
|
+
if printf '%s' "$_zt_out" | grep -qE '\[no test files\]' 2>/dev/null \
|
|
348
|
+
&& ! printf '%s' "$_zt_out" | grep -qE '^(ok|FAIL|--- FAIL)' 2>/dev/null; then
|
|
349
|
+
return 0
|
|
350
|
+
fi
|
|
351
|
+
return 1
|
|
352
|
+
;;
|
|
337
353
|
*)
|
|
338
354
|
return 1
|
|
339
355
|
;;
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v7.117.
|
|
5
|
+
**Version:** v7.117.1
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var q8=Object.defineProperty;var J8=($)=>$;function V8($,Q){this[$]=J8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)q8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:V8.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var m1={};b(m1,{lokiDir:()=>j,homeLokiDir:()=>T$,findRepoRootForVersion:()=>$1,REPO_ROOT:()=>h});import{resolve as t,dirname as e$}from"path";import{fileURLToPath as W8}from"url";import{existsSync as N$}from"fs";import{homedir as H8}from"os";function G8(){let $=v1;for(let Q=0;Q<6;Q++){if(N$(t($,"VERSION"))&&N$(t($,"autonomy/run.sh")))return $;let Z=e$($);if(Z===$)break;$=Z}return t(v1,"..","..","..")}function $1($){let Q=$;for(let Z=0;Z<6;Z++){if(N$(t(Q,"VERSION"))&&N$(t(Q,"autonomy/run.sh")))return Q;let z=e$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function j(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(H8(),".loki")}var v1,h;var C=P(()=>{v1=e$(W8(import.meta.url));h=G8()});import{readFileSync as U8}from"fs";import{resolve as Y8,dirname as B8}from"path";import{fileURLToPath as M8}from"url";function S$(){if(Q$!==null)return Q$;let $="7.117.
|
|
2
|
+
var q8=Object.defineProperty;var J8=($)=>$;function V8($,Q){this[$]=J8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)q8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:V8.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var m1={};b(m1,{lokiDir:()=>j,homeLokiDir:()=>T$,findRepoRootForVersion:()=>$1,REPO_ROOT:()=>h});import{resolve as t,dirname as e$}from"path";import{fileURLToPath as W8}from"url";import{existsSync as N$}from"fs";import{homedir as H8}from"os";function G8(){let $=v1;for(let Q=0;Q<6;Q++){if(N$(t($,"VERSION"))&&N$(t($,"autonomy/run.sh")))return $;let Z=e$($);if(Z===$)break;$=Z}return t(v1,"..","..","..")}function $1($){let Q=$;for(let Z=0;Z<6;Z++){if(N$(t(Q,"VERSION"))&&N$(t(Q,"autonomy/run.sh")))return Q;let z=e$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function j(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(H8(),".loki")}var v1,h;var C=P(()=>{v1=e$(W8(import.meta.url));h=G8()});import{readFileSync as U8}from"fs";import{resolve as Y8,dirname as B8}from"path";import{fileURLToPath as M8}from"url";function S$(){if(Q$!==null)return Q$;let $="7.117.1";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=B8(M8(import.meta.url)),Z=$1(Q);Q$=U8(Y8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var Q1=P(()=>{C()});var p1={};b(p1,{runOrThrow:()=>S8,run:()=>R,readStreamCapped:()=>c1,commandVersion:()=>C8,commandExists:()=>u,ShellError:()=>Z1,MAX_STDOUT_BYTES:()=>u1});async function c1($,Q=u1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:W}=await Z.read();if(K)break;if(!W)continue;if(q+=W.byteLength,q>Q){let V=W.byteLength-(q-Q);X+=z.decode(W.subarray(0,V),{stream:!0});break}X+=z.decode(W,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function R($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,W]=await Promise.all([c1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:W}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function S8($,Q={}){let Z=await R($,Q);if(Z.exitCode!==0)throw new Z1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function u($){let Q=D8($),Z=await R(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function D8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function C8($,Q="--version"){if(!await u($))return null;let z=await R([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var u1=16777216,Z1;var o=P(()=>{Z1=class Z1 extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function r($){return b8?"":$}var b8,M,S,_,tZ,L,k,y,J;var p=P(()=>{b8=(process.env.NO_COLOR??"").length>0;M=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),tZ=r("\x1B[0;34m"),L=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as l8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(l8($))return A$=$,$;let Q=await u("python3.12");if(Q)return A$=Q,Q;let Z=await u("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return R([Z,"-c",$],Q)}var A$;var V$=P(()=>{o()});var V0={};b(V0,{runStatus:()=>U3});import{existsSync as v,readFileSync as H$,readdirSync as $0,statSync as Q0}from"fs";import{resolve as D,basename as z3}from"path";import{homedir as X3}from"os";function Z0($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function z0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*C$/Q);if(X>C$)X=C$;let q=C$-X,K=S;if(z>=80)K=M;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=Z0($),H=Z0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${H})`}async function q3(){if(await u("jq"))return!0;return process.stdout.write(`${M}Error: jq is required but not installed.${J}
|
|
3
3
|
`),process.stdout.write(`Install with:
|
|
4
4
|
`),process.stdout.write(` brew install jq (macOS)
|
|
5
5
|
`),process.stdout.write(` apt install jq (Debian/Ubuntu)
|
|
@@ -814,4 +814,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
814
814
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
815
815
|
`),process.stderr.write(K8),2}}e1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var SZ=await NZ(Bun.argv.slice(2));process.exit(SZ);
|
|
816
816
|
|
|
817
|
-
//# debugId=
|
|
817
|
+
//# debugId=C0F83BD59DEB4E5764756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "7.117.
|
|
4
|
+
"version": "7.117.1",
|
|
5
5
|
"description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "7.117.
|
|
5
|
+
"version": "7.117.1",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|