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 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 commands |
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.72.0
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.72.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
409
+ **v7.74.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.72.0
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 = (total * 2 + 2) // 3
342
- verdict = "COMPLETE" if complete >= threshold else "CONTINUE"
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,