loki-mode 7.72.0 → 7.74.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 -1
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +26 -0
- package/autonomy/lib/git-pr-advisory.sh +112 -0
- package/autonomy/lib/voter-agents.sh +43 -2
- package/autonomy/loki +641 -132
- package/autonomy/run.sh +416 -28
- package/autonomy/verify.sh +7 -1
- package/dashboard/__init__.py +1 -1
- package/docs/BRANCH-LIFECYCLE-PLAN.md +354 -0
- package/docs/DEPLOY-PLAN.md +302 -0
- package/docs/INSTALLATION.md +2 -2
- package/loki-ts/data/finding-schema.json +1 -0
- package/loki-ts/dist/loki.js +189 -189
- 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
|
@@ -444,7 +444,7 @@ See [benchmarks/](benchmarks/) for methodology.
|
|
|
444
444
|
| Area | What Works | What Doesn't (Yet) |
|
|
445
445
|
|------|-----------|---------------------|
|
|
446
446
|
| **Code Gen** | Full-stack apps from PRDs | Complex domain logic may need human review |
|
|
447
|
-
| **Deploy** | Generates configs, Dockerfiles, CI/CD | Does not deploy -- human runs deploy
|
|
447
|
+
| **Deploy** | Generates configs, Dockerfiles, CI/CD; `loki deploy` prints the exact deploy command | Does not deploy -- human runs the printed deploy command (Loki never runs a cloud CLI or git push) |
|
|
448
448
|
| **Testing** | 8 automated quality gates | Test quality depends on AI assertions |
|
|
449
449
|
| **Providers** | 5 providers with auto-failover | Non-Claude providers lack parallel agents |
|
|
450
450
|
| **Dashboard** | Real-time single-machine monitoring | No multi-node clustering |
|
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.74.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -406,4 +406,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
406
406
|
|
|
407
407
|
---
|
|
408
408
|
|
|
409
|
-
**v7.
|
|
409
|
+
**v7.74.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.74.0
|
|
@@ -2812,8 +2812,34 @@ council_evaluate() {
|
|
|
2812
2812
|
# Re-derive complete count from the round file
|
|
2813
2813
|
local round_file="$COUNCIL_STATE_DIR/votes/round-${ITERATION_COUNT}.json"
|
|
2814
2814
|
local complete_count=0
|
|
2815
|
+
local members_present=0
|
|
2815
2816
|
if [ -f "$round_file" ]; then
|
|
2816
2817
|
complete_count=$(_RF="$round_file" python3 -c "import json, os; print(json.load(open(os.environ['_RF'])).get('complete_votes', 0))" 2>/dev/null || echo "0")
|
|
2818
|
+
# WAVE13 CRITICAL quorum gate: how many voters actually responded
|
|
2819
|
+
# (total_members records the ACTUAL returned count -- see
|
|
2820
|
+
# voter-agents.sh). A degraded/partial dispatch response must never
|
|
2821
|
+
# be honored as COMPLETE even if its (now quorum-aware) verdict
|
|
2822
|
+
# somehow read COMPLETE. This is defense-in-depth: the parser
|
|
2823
|
+
# already forces CONTINUE on undercount, but the completion-detection
|
|
2824
|
+
# trust core must independently assert full quorum before stopping.
|
|
2825
|
+
members_present=$(_RF="$round_file" python3 -c "import json, os; print(json.load(open(os.environ['_RF'])).get('total_members', 0))" 2>/dev/null || echo "0")
|
|
2826
|
+
fi
|
|
2827
|
+
# Normalize to integers (guard against empty/non-numeric on read failure)
|
|
2828
|
+
case "$complete_count" in (''|*[!0-9]*) complete_count=0 ;; esac
|
|
2829
|
+
case "$members_present" in (''|*[!0-9]*) members_present=0 ;; esac
|
|
2830
|
+
|
|
2831
|
+
# Quorum-presence gate (distinct from the DA-unanimity trigger below):
|
|
2832
|
+
# a COMPLETE verdict only stands when EXACTLY the expected council
|
|
2833
|
+
# responded. Any mismatch is a degraded response and fails closed:
|
|
2834
|
+
# - undercount (< COUNCIL_SIZE): missing voters are non-approval.
|
|
2835
|
+
# - overcount (> COUNCIL_SIZE): extra/unprompted findings (e.g. a
|
|
2836
|
+
# model adding a 4th 'devils-advocate' finding) would otherwise let
|
|
2837
|
+
# a low-approval-ratio response clear the fixed threshold=2. Both
|
|
2838
|
+
# directions must CONTINUE, so we assert exact quorum (== not <).
|
|
2839
|
+
if [ "$members_present" -ne "$COUNCIL_SIZE" ]; then
|
|
2840
|
+
log_warn "Council evaluate: COMPLETE rejected -- quorum mismatch ($members_present voters present, expected $COUNCIL_SIZE); failing closed to CONTINUE"
|
|
2841
|
+
council_write_transcript "${ITERATION_COUNT:-0}" "REJECTED" "false" "false" "$_eval_threshold"
|
|
2842
|
+
return 1 # CONTINUE
|
|
2817
2843
|
fi
|
|
2818
2844
|
|
|
2819
2845
|
if [ "$complete_count" -eq "$COUNCIL_SIZE" ] && [ "$COUNCIL_SIZE" -ge 2 ]; then
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# git-pr-advisory.sh -- shared, PRINT-ONLY pull-request advisory helper.
|
|
3
|
+
#
|
|
4
|
+
# LOAD-BEARING INVARIANT: every function here is pure and print-only. It NEVER
|
|
5
|
+
# runs `git push`, NEVER runs `gh pr create`, NEVER mutates the repo. It only
|
|
6
|
+
# prints the commands a user would run, plus a best-effort clipboard copy of the
|
|
7
|
+
# push line. This is the single source of truth sourced by BOTH autonomy/run.sh
|
|
8
|
+
# (LOCK A3 create_session_pr) and autonomy/loki (cmd_deploy) so the two surfaces
|
|
9
|
+
# print byte-identical, correct commands and cannot drift.
|
|
10
|
+
#
|
|
11
|
+
# set -e SAFE: this lib may be sourced under `set -uo pipefail` (run.sh) AND
|
|
12
|
+
# `set -euo pipefail` (loki). Every fallible command ends with `|| true` or sits
|
|
13
|
+
# in a guarded `if`; no bare `((..))`; every var defaulted with `${VAR:-}`;
|
|
14
|
+
# every optional tool is `command -v`-guarded. All print paths `return 0` so a
|
|
15
|
+
# sourced call cannot abort the caller under set -e.
|
|
16
|
+
|
|
17
|
+
# Double-source guard.
|
|
18
|
+
[ -n "${_GIT_PR_ADVISORY_SH:-}" ] && return 0
|
|
19
|
+
_GIT_PR_ADVISORY_SH=1
|
|
20
|
+
|
|
21
|
+
# _git_pr_advisory_origin_url [dir]
|
|
22
|
+
# Echoes the origin remote URL, or empty string if none. Best-effort, never errors.
|
|
23
|
+
_git_pr_advisory_origin_url() {
|
|
24
|
+
local dir="${1:-.}"
|
|
25
|
+
local url=""
|
|
26
|
+
command -v git >/dev/null 2>&1 || { printf '%s' ""; return 0; }
|
|
27
|
+
url="$(git -C "$dir" remote get-url origin 2>/dev/null || true)"
|
|
28
|
+
if [ -z "$url" ]; then
|
|
29
|
+
url="$(git -C "$dir" config --get remote.origin.url 2>/dev/null || true)"
|
|
30
|
+
fi
|
|
31
|
+
printf '%s' "${url:-}"
|
|
32
|
+
return 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# _git_pr_advisory_compare_url <origin_url> <base> <head>
|
|
36
|
+
# Echoes a GitHub compare URL, or empty if the origin URL is not a parseable
|
|
37
|
+
# github.com remote. Handles both ssh (git@github.com:owner/repo.git) and https
|
|
38
|
+
# (https://github.com/owner/repo[.git]) forms. Non-github hosts -> empty.
|
|
39
|
+
_git_pr_advisory_compare_url() {
|
|
40
|
+
local origin_url="${1:-}"
|
|
41
|
+
local base="${2:-}"
|
|
42
|
+
local head="${3:-}"
|
|
43
|
+
[ -n "$origin_url" ] || { printf '%s' ""; return 0; }
|
|
44
|
+
[ -n "$base" ] || { printf '%s' ""; return 0; }
|
|
45
|
+
[ -n "$head" ] || { printf '%s' ""; return 0; }
|
|
46
|
+
|
|
47
|
+
# Only github.com remotes yield a compare URL. Do not fabricate for other hosts.
|
|
48
|
+
case "$origin_url" in
|
|
49
|
+
*github.com[:/]*) : ;;
|
|
50
|
+
*) printf '%s' ""; return 0 ;;
|
|
51
|
+
esac
|
|
52
|
+
|
|
53
|
+
# Reuse the run.sh:2123-2133 idiom: extract owner/repo from ssh or https forms.
|
|
54
|
+
local repo=""
|
|
55
|
+
repo="$(printf '%s' "$origin_url" | sed -E 's/.*github\.com[:/]([^/]+\/[^/]+)(\.git)?$/\1/' 2>/dev/null || true)"
|
|
56
|
+
repo="${repo%.git}"
|
|
57
|
+
|
|
58
|
+
if [ -n "$repo" ] && [ "$repo" != "$origin_url" ] && [ "${repo#*/}" != "$repo" ]; then
|
|
59
|
+
printf '%s' "https://github.com/${repo}/compare/${base}...${head}?expand=1"
|
|
60
|
+
return 0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
printf '%s' ""
|
|
64
|
+
return 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# print_pr_advice <base_branch> <head_branch> [dir]
|
|
68
|
+
# Prints PR advice. PRINT-ONLY: never pushes, never creates a PR. Always return 0.
|
|
69
|
+
print_pr_advice() {
|
|
70
|
+
local base="${1:-main}"
|
|
71
|
+
local head="${2:-HEAD}"
|
|
72
|
+
local dir="${3:-.}"
|
|
73
|
+
|
|
74
|
+
printf '%s\n' "To open a pull request:"
|
|
75
|
+
printf '%s\n' " git push -u origin ${head}"
|
|
76
|
+
|
|
77
|
+
if command -v gh >/dev/null 2>&1; then
|
|
78
|
+
printf '%s\n' " gh pr create --base ${base} --head ${head} --title \"Loki Mode session changes\" --fill"
|
|
79
|
+
else
|
|
80
|
+
local origin_url="" compare_url=""
|
|
81
|
+
origin_url="$(_git_pr_advisory_origin_url "$dir")"
|
|
82
|
+
compare_url="$(_git_pr_advisory_compare_url "$origin_url" "$base" "$head")"
|
|
83
|
+
if [ -n "$compare_url" ]; then
|
|
84
|
+
printf '%s\n' " Open: ${compare_url}"
|
|
85
|
+
else
|
|
86
|
+
printf '%s\n' " Open a pull request for branch ${head} on your git host."
|
|
87
|
+
fi
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Best-effort clipboard copy of the push line. TTY-gated, command-v guarded,
|
|
91
|
+
# never fatal. Print a note only if a copy tool actually ran.
|
|
92
|
+
if [ -t 1 ]; then
|
|
93
|
+
local push_line="git push -u origin ${head}"
|
|
94
|
+
local copied=""
|
|
95
|
+
if command -v pbcopy >/dev/null 2>&1; then
|
|
96
|
+
printf '%s' "$push_line" | pbcopy >/dev/null 2>&1 && copied="1" || true
|
|
97
|
+
elif command -v wl-copy >/dev/null 2>&1; then
|
|
98
|
+
printf '%s' "$push_line" | wl-copy >/dev/null 2>&1 && copied="1" || true
|
|
99
|
+
elif command -v xclip >/dev/null 2>&1; then
|
|
100
|
+
printf '%s' "$push_line" | xclip -selection clipboard >/dev/null 2>&1 && copied="1" || true
|
|
101
|
+
elif command -v xsel >/dev/null 2>&1; then
|
|
102
|
+
printf '%s' "$push_line" | xsel --clipboard --input >/dev/null 2>&1 && copied="1" || true
|
|
103
|
+
elif command -v clip >/dev/null 2>&1; then
|
|
104
|
+
printf '%s' "$push_line" | clip >/dev/null 2>&1 && copied="1" || true
|
|
105
|
+
fi
|
|
106
|
+
if [ -n "$copied" ]; then
|
|
107
|
+
printf '%s\n' " (push command copied to clipboard)"
|
|
108
|
+
fi
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
return 0
|
|
112
|
+
}
|
|
@@ -273,6 +273,7 @@ loki_council_dispatch_agents() {
|
|
|
273
273
|
_VA_ITER="$iteration" \
|
|
274
274
|
_VA_VDIR="$verdicts_dir" \
|
|
275
275
|
_VA_RFILE="$votes_dir/round-${iteration}.json" \
|
|
276
|
+
_VA_EXPECTED="${COUNCIL_SIZE:-3}" \
|
|
276
277
|
python3 -c '
|
|
277
278
|
import json, os, sys
|
|
278
279
|
from datetime import datetime, timezone
|
|
@@ -290,6 +291,26 @@ it = int(os.environ.get("_VA_ITER", "0") or 0)
|
|
|
290
291
|
vdir = os.environ["_VA_VDIR"]
|
|
291
292
|
rfile = os.environ["_VA_RFILE"]
|
|
292
293
|
|
|
294
|
+
# WAVE13 CRITICAL quorum fix: the quorum denominator MUST be the EXPECTED
|
|
295
|
+
# council size (COUNCIL_SIZE), never the number of findings the model happened
|
|
296
|
+
# to return. Pre-fix this parser computed threshold = (returned*2+2)//3, so a
|
|
297
|
+
# degraded response with a single APPROVE finding (returned=1) yielded
|
|
298
|
+
# threshold=1 and a COMPLETE verdict from a SINGLE voter, with the missing
|
|
299
|
+
# voters silently dropped. That fails OPEN on the completion-detection trust
|
|
300
|
+
# core. We now fail CLOSED: any undercount (returned < expected) forces a
|
|
301
|
+
# CONTINUE verdict so a partial/degraded model response can never reach
|
|
302
|
+
# COMPLETE on the returned subset. Design choice (Option 2): compute the
|
|
303
|
+
# verdict in-path (rather than sys.exit -> heuristic fallback) so the round
|
|
304
|
+
# file always records the actual returned count in total_members, making the
|
|
305
|
+
# downstream quorum assertion in completion-council.sh meaningful and locally
|
|
306
|
+
# testable without depending on the heuristic-path disk-state behavior.
|
|
307
|
+
try:
|
|
308
|
+
expected_count = int(os.environ.get("_VA_EXPECTED", "3") or 3)
|
|
309
|
+
except (TypeError, ValueError):
|
|
310
|
+
expected_count = 3
|
|
311
|
+
if expected_count < 1:
|
|
312
|
+
expected_count = 1
|
|
313
|
+
|
|
293
314
|
def to_legacy(vote: str) -> str:
|
|
294
315
|
v = (vote or "").upper()
|
|
295
316
|
if v == "APPROVE":
|
|
@@ -338,14 +359,34 @@ for idx, f in enumerate(findings, start=1):
|
|
|
338
359
|
if total == 0:
|
|
339
360
|
sys.exit(5)
|
|
340
361
|
|
|
341
|
-
threshold
|
|
342
|
-
|
|
362
|
+
# Quorum-aware threshold (WAVE13). threshold is computed against the EXPECTED
|
|
363
|
+
# council size so it can never shrink to 1 on a degraded response. Absent
|
|
364
|
+
# voters (total < expected) are treated as non-approval: the round is forced
|
|
365
|
+
# to CONTINUE and can never reach COMPLETE on the returned subset. With
|
|
366
|
+
# expected=3, threshold = ceil(2/3 * 3) = 2, so 1-of-3 (or any single voter)
|
|
367
|
+
# is structurally incapable of producing COMPLETE.
|
|
368
|
+
threshold = (expected_count * 2 + 2) // 3
|
|
369
|
+
if total != expected_count:
|
|
370
|
+
# Fail closed on ANY quorum mismatch:
|
|
371
|
+
# - undercount (total < expected): missing voters count as non-approval.
|
|
372
|
+
# - overcount (total > expected): extra/unprompted findings (e.g. a model
|
|
373
|
+
# adding a 4th finding) would otherwise let a low-approval-ratio response
|
|
374
|
+
# clear the fixed threshold. A degraded response in either direction must
|
|
375
|
+
# never reach COMPLETE on the returned subset.
|
|
376
|
+
verdict = "CONTINUE"
|
|
377
|
+
else:
|
|
378
|
+
verdict = "COMPLETE" if complete >= threshold else "CONTINUE"
|
|
343
379
|
round_data = {
|
|
344
380
|
"round": it,
|
|
345
381
|
"timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
346
382
|
"complete_votes": complete,
|
|
347
383
|
"continue_votes": total - complete,
|
|
384
|
+
# total_members records the ACTUAL number of voters that responded (not the
|
|
385
|
+
# expected size) so the completion-council quorum assertion can detect an
|
|
386
|
+
# undercount. expected_members records the size the verdict was judged
|
|
387
|
+
# against.
|
|
348
388
|
"total_members": total,
|
|
389
|
+
"expected_members": expected_count,
|
|
349
390
|
"threshold": threshold,
|
|
350
391
|
"verdict": verdict,
|
|
351
392
|
"votes": votes,
|