loki-mode 7.20.0 → 7.22.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/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.20.0
6
+ # Loki Mode v7.22.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -383,4 +383,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
383
383
 
384
384
  ---
385
385
 
386
- **v7.20.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
386
+ **v7.22.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.20.0
1
+ 7.22.0
@@ -557,6 +557,32 @@ _lpg_find_git_root() {
557
557
  # Layer order: parent -> members -> subdir(root-to-leaf, deepest last)
558
558
  # -> scope. All paths are deduped via a tracked set of absolute paths.
559
559
  # Honors __LPG_TOTAL_CAP across all layers (stops at layer boundary).
560
+ # _lpg_memory_file <dir>
561
+ #
562
+ # Resolve the per-directory memory/conventions file for the layered doc walker.
563
+ # Prefers AGENTS.md (the agents.md standard: plain Markdown, nearest-file-wins,
564
+ # read natively by Claude Code/Codex/etc.) and falls back to CLAUDE.md only when
565
+ # AGENTS.md is absent in that directory. The two are never merged. The reader
566
+ # (_lpg_read_layer / _append_layer) is filename-agnostic, so this is the single
567
+ # point that decides which file each layer site reads.
568
+ _lpg_memory_file() {
569
+ local dir="$1"
570
+ if [ -f "$dir/AGENTS.md" ]; then
571
+ printf '%s/AGENTS.md' "$dir"
572
+ else
573
+ printf '%s/CLAUDE.md' "$dir"
574
+ fi
575
+ }
576
+
577
+ # load_app_graph_context -- emit the layered conventions/doc context (parent,
578
+ # member, subdir, and scope layers) as <!-- LOKI_LAYER --> blocks.
579
+ #
580
+ # NOTE (route asymmetry, conscious + pre-existing): the TS route has NO port of
581
+ # this function. loki-ts/src/project_graph.ts is a membership graph, not a
582
+ # doc-layer text emitter, so this layered AGENTS.md/CLAUDE.md walker is BASH-ONLY
583
+ # by design (the layered-CLAUDE.md doc walker was never ported to TS). The
584
+ # AGENTS.md precedence added here therefore lives only in bash; do not add a TS
585
+ # walker to "fix" the asymmetry.
560
586
  load_app_graph_context() {
561
587
  local root="${LOKI_PROJECT_GRAPH_ROOT:-}"
562
588
 
@@ -634,7 +660,7 @@ load_app_graph_context() {
634
660
 
635
661
  # Parent layer first.
636
662
  if [ -n "$root" ]; then
637
- _append_layer parent "$root/CLAUDE.md" || { printf '%s' "$out"; return 0; }
663
+ _append_layer parent "$(_lpg_memory_file "$root")" || { printf '%s' "$out"; return 0; }
638
664
  fi
639
665
 
640
666
  # Member layers (skip the scope member -- we add it as scope below).
@@ -644,7 +670,7 @@ load_app_graph_context() {
644
670
  if [ "$m" = "$target_dir" ]; then
645
671
  continue
646
672
  fi
647
- _append_layer member "$m/CLAUDE.md" || { printf '%s' "$out"; return 0; }
673
+ _append_layer member "$(_lpg_memory_file "$m")" || { printf '%s' "$out"; return 0; }
648
674
  done
649
675
 
650
676
  # Subdir layers: ancestors of target_dir up to (and including) git_root,
@@ -674,12 +700,12 @@ load_app_graph_context() {
674
700
  # subdir_chain is leaf-to-root order; reverse so we emit root-to-leaf.
675
701
  local i count=${#subdir_chain[@]}
676
702
  for (( i = count - 1; i >= 0; i-- )); do
677
- _append_layer subdir "${subdir_chain[$i]}/CLAUDE.md" || { printf '%s' "$out"; return 0; }
703
+ _append_layer subdir "$(_lpg_memory_file "${subdir_chain[$i]}")" || { printf '%s' "$out"; return 0; }
678
704
  done
679
705
  fi
680
706
 
681
707
  # Scope layer (target dir).
682
- _append_layer scope "$target_dir/CLAUDE.md" || { printf '%s' "$out"; return 0; }
708
+ _append_layer scope "$(_lpg_memory_file "$target_dir")" || { printf '%s' "$out"; return 0; }
683
709
 
684
710
  printf '%s' "$out"
685
711
  }
package/autonomy/run.sh CHANGED
@@ -2355,6 +2355,15 @@ notify_all_complete() {
2355
2355
 
2356
2356
  notify_intervention_needed() {
2357
2357
  local reason="$1"
2358
+ # Delegate-then-notify: this helper ONLY fires the (gated) desktop ping. It
2359
+ # deliberately does NOT write the durable COMPLETION.txt / completion.json
2360
+ # record. Reason: notify_intervention_needed is also called from NON-terminal
2361
+ # sites (the perpetual-mode PAUSE auto-clear branch, uncertainty escalation)
2362
+ # where the run keeps going. Writing a "Needs input" durable file there would
2363
+ # falsely tell a detached user the run is done / blocked when it is not. The
2364
+ # durable intervention write now lives only at the genuinely blocking pause
2365
+ # sites (immediately before handle_pause), so the durable state matches the
2366
+ # actual run state.
2358
2367
  send_notification "Intervention Needed" "$reason" "critical"
2359
2368
  }
2360
2369
 
@@ -2363,6 +2372,265 @@ notify_rate_limit() {
2363
2372
  send_notification "Rate Limited" "Waiting ${wait_time}s before retry" "normal"
2364
2373
  }
2365
2374
 
2375
+ #===============================================================================
2376
+ # Delegate-then-notify: completion summary (Release 2, "delegate then notify")
2377
+ #
2378
+ # build_completion_summary <outcome> writes two durable files that survive a
2379
+ # detached (--bg) run where the terminal is gone and a bell would be useless:
2380
+ # .loki/COMPLETION.txt human plain text (no emojis, no dashes)
2381
+ # .loki/state/completion.json machine-readable record of the same facts
2382
+ # It also exports two strings for send_notification to consume:
2383
+ # _LOKI_SUMMARY_TITLE short notification subtitle
2384
+ # _LOKI_SUMMARY_BODY short notification body (outcome + branch + file count)
2385
+ #
2386
+ # All git reads are best-effort and non-fatal. The diff window is the run-start
2387
+ # SHA captured once at runner init (_LOKI_RUN_START_SHA); we REUSE it and never
2388
+ # recapture, so the reported diff matches the evidence gate's window exactly.
2389
+ #
2390
+ # This function NEVER sends a notification and NEVER gates on
2391
+ # NOTIFICATIONS_ENABLED: the files are state, not a notification, and must be
2392
+ # written even when desktop notifications are disabled. emit_completion_summary
2393
+ # below is the wrapper that writes the files AND (gated) fires the desktop ping.
2394
+ #===============================================================================
2395
+ build_completion_summary() {
2396
+ local outcome="${1:-complete}"
2397
+ local loki_dir="${TARGET_DIR:-.}/.loki"
2398
+ mkdir -p "$loki_dir/state" 2>/dev/null || true
2399
+
2400
+ # Human-readable outcome label and notification title.
2401
+ local outcome_label notify_title
2402
+ case "$outcome" in
2403
+ complete) outcome_label="Completed"; notify_title="Run complete" ;;
2404
+ max_iterations) outcome_label="Max iterations"; notify_title="Run stopped (max iterations)" ;;
2405
+ stopped) outcome_label="Stopped"; notify_title="Run stopped" ;;
2406
+ failed) outcome_label="Failed"; notify_title="Run failed" ;;
2407
+ intervention) outcome_label="Needs input"; notify_title="Input needed" ;;
2408
+ *) outcome_label="$outcome"; notify_title="Run finished" ;;
2409
+ esac
2410
+
2411
+ # Branch + diff stats vs the run-start SHA (best-effort; non-git or empty
2412
+ # baseline yields empty values, which we render as "unknown"/"0").
2413
+ local start_sha="${_LOKI_RUN_START_SHA:-}"
2414
+ local branch="" head_sha="" diff_stat="" files_changed=0 insertions=0 deletions=0 review_cmd=""
2415
+ branch="$( (cd "${TARGET_DIR:-.}" && git rev-parse --abbrev-ref HEAD) 2>/dev/null || true )"
2416
+ [ -z "$branch" ] && branch="unknown"
2417
+ head_sha="$( (cd "${TARGET_DIR:-.}" && git rev-parse HEAD) 2>/dev/null || true )"
2418
+
2419
+ if [ -n "$start_sha" ]; then
2420
+ diff_stat="$( (cd "${TARGET_DIR:-.}" && git diff --stat "${start_sha}..HEAD") 2>/dev/null || true )"
2421
+ # Parse the git diff --shortstat tail for counts (locale-stable enough
2422
+ # for our display; failures leave the zeros in place).
2423
+ local shortstat
2424
+ shortstat="$( (cd "${TARGET_DIR:-.}" && git diff --shortstat "${start_sha}..HEAD") 2>/dev/null || true )"
2425
+ if [ -n "$shortstat" ]; then
2426
+ files_changed="$(printf '%s\n' "$shortstat" | grep -oE '[0-9]+ file' | grep -oE '[0-9]+' | head -1)"
2427
+ insertions="$(printf '%s\n' "$shortstat" | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' | head -1)"
2428
+ deletions="$(printf '%s\n' "$shortstat" | grep -oE '[0-9]+ deletion' | grep -oE '[0-9]+' | head -1)"
2429
+ fi
2430
+ review_cmd="git diff ${start_sha}..HEAD"
2431
+ else
2432
+ review_cmd="git diff HEAD"
2433
+ fi
2434
+ [ -z "$files_changed" ] && files_changed=0
2435
+ [ -z "$insertions" ] && insertions=0
2436
+ [ -z "$deletions" ] && deletions=0
2437
+
2438
+ # Task counts: reuse the SAME queue reads as update_status_file.
2439
+ local pending=0 in_progress=0 completed=0 failed=0
2440
+ [ -f "$loki_dir/queue/pending.json" ] && pending=$(python3 -c "import json; print(len(json.load(open('$loki_dir/queue/pending.json'))))" 2>/dev/null || echo "0")
2441
+ [ -f "$loki_dir/queue/in-progress.json" ] && in_progress=$(python3 -c "import json; print(len(json.load(open('$loki_dir/queue/in-progress.json'))))" 2>/dev/null || echo "0")
2442
+ [ -f "$loki_dir/queue/completed.json" ] && completed=$(python3 -c "import json; print(len(json.load(open('$loki_dir/queue/completed.json'))))" 2>/dev/null || echo "0")
2443
+ [ -f "$loki_dir/queue/failed.json" ] && failed=$(python3 -c "import json; print(len(json.load(open('$loki_dir/queue/failed.json'))))" 2>/dev/null || echo "0")
2444
+
2445
+ # Optional delegate-mode extras populated by Slice 3 (branch isolation / PR).
2446
+ local delegate_branch="${_LOKI_DELEGATE_BRANCH_NAME:-}"
2447
+ local pr_url="${_LOKI_DELEGATE_PR_URL:-}"
2448
+
2449
+ local ts
2450
+ ts="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date)"
2451
+
2452
+ # ---- Durable human-readable file: .loki/COMPLETION.txt --------------------
2453
+ {
2454
+ echo "Loki Mode run summary"
2455
+ echo "====================="
2456
+ echo ""
2457
+ echo "Outcome: $outcome_label"
2458
+ echo "Branch: $branch"
2459
+ echo "Files changed: $files_changed (+$insertions / -$deletions)"
2460
+ echo "Finished: $ts"
2461
+ echo ""
2462
+ if [ -n "$delegate_branch" ]; then
2463
+ echo "Delegate branch: $delegate_branch"
2464
+ fi
2465
+ if [ -n "$pr_url" ]; then
2466
+ echo "Pull request: $pr_url"
2467
+ elif [ "$outcome" = "complete" ]; then
2468
+ echo "Pull request: not opened (set LOKI_DELEGATE_PR=1 to open one)"
2469
+ fi
2470
+ echo ""
2471
+ echo "Tasks: pending=$pending in_progress=$in_progress completed=$completed failed=$failed"
2472
+ echo ""
2473
+ echo "Review the work:"
2474
+ echo " $review_cmd"
2475
+ echo ""
2476
+ if [ -n "$diff_stat" ]; then
2477
+ echo "Diff stat:"
2478
+ echo "$diff_stat"
2479
+ else
2480
+ echo "Diff stat: (no changes detected vs run start, or git unavailable)"
2481
+ fi
2482
+ } > "$loki_dir/COMPLETION.txt" 2>/dev/null || true
2483
+
2484
+ # ---- Durable machine-readable file: .loki/state/completion.json -----------
2485
+ _LOKI_CS_OUTCOME="$outcome" \
2486
+ _LOKI_CS_BRANCH="$branch" \
2487
+ _LOKI_CS_START_SHA="$start_sha" \
2488
+ _LOKI_CS_HEAD_SHA="$head_sha" \
2489
+ _LOKI_CS_FILES="$files_changed" \
2490
+ _LOKI_CS_INS="$insertions" \
2491
+ _LOKI_CS_DEL="$deletions" \
2492
+ _LOKI_CS_REVIEW="$review_cmd" \
2493
+ _LOKI_CS_DELEGATE_BRANCH="$delegate_branch" \
2494
+ _LOKI_CS_PR_URL="$pr_url" \
2495
+ _LOKI_CS_TS="$ts" \
2496
+ _LOKI_CS_OUT_FILE="$loki_dir/state/completion.json" \
2497
+ python3 -c "
2498
+ import json, os, tempfile
2499
+ out = os.environ['_LOKI_CS_OUT_FILE']
2500
+ def i(v):
2501
+ try: return int(v)
2502
+ except (TypeError, ValueError): return 0
2503
+ rec = {
2504
+ 'outcome': os.environ.get('_LOKI_CS_OUTCOME', ''),
2505
+ 'branch': os.environ.get('_LOKI_CS_BRANCH', ''),
2506
+ 'start_sha': os.environ.get('_LOKI_CS_START_SHA', ''),
2507
+ 'head_sha': os.environ.get('_LOKI_CS_HEAD_SHA', ''),
2508
+ 'files_changed': i(os.environ.get('_LOKI_CS_FILES')),
2509
+ 'insertions': i(os.environ.get('_LOKI_CS_INS')),
2510
+ 'deletions': i(os.environ.get('_LOKI_CS_DEL')),
2511
+ 'review_cmd': os.environ.get('_LOKI_CS_REVIEW', ''),
2512
+ 'delegate_branch': os.environ.get('_LOKI_CS_DELEGATE_BRANCH', ''),
2513
+ 'pr_url': os.environ.get('_LOKI_CS_PR_URL', ''),
2514
+ 'timestamp': os.environ.get('_LOKI_CS_TS', ''),
2515
+ }
2516
+ d = os.path.dirname(out)
2517
+ fd, tmp = tempfile.mkstemp(dir=d, suffix='.json')
2518
+ with os.fdopen(fd, 'w') as f:
2519
+ json.dump(rec, f, indent=2)
2520
+ os.replace(tmp, out)
2521
+ " 2>/dev/null || true
2522
+
2523
+ # ---- Short strings for the desktop notification --------------------------
2524
+ # Desktop body stays terse; full detail lives in COMPLETION.txt.
2525
+ _LOKI_SUMMARY_TITLE="$notify_title"
2526
+ _LOKI_SUMMARY_BODY="${outcome_label} on ${branch}: ${files_changed} files changed"
2527
+ if [ -n "$pr_url" ]; then
2528
+ _LOKI_SUMMARY_BODY="${_LOKI_SUMMARY_BODY}. PR: ${pr_url}"
2529
+ fi
2530
+ export _LOKI_SUMMARY_TITLE _LOKI_SUMMARY_BODY
2531
+ return 0
2532
+ }
2533
+
2534
+ #===============================================================================
2535
+ # emit_completion_summary <outcome> [urgency]
2536
+ #
2537
+ # The single entry point every terminal state calls. It ALWAYS writes the
2538
+ # durable summary files (state, not a notification) and then fires ONE desktop
2539
+ # notification gated by the existing LOKI_NOTIFICATIONS flag (send_notification
2540
+ # already short-circuits when disabled, so the gate is implicit but explicit
2541
+ # here for clarity). Centralizing this keeps the success-only PR side effect
2542
+ # (Slice 3) in one place and prevents duplicate notifications.
2543
+ #===============================================================================
2544
+ emit_completion_summary() {
2545
+ local outcome="${1:-complete}"
2546
+ local urgency="${2:-normal}"
2547
+ build_completion_summary "$outcome"
2548
+ send_notification "${_LOKI_SUMMARY_TITLE:-Run finished}" "${_LOKI_SUMMARY_BODY:-}" "$urgency"
2549
+ return 0
2550
+ }
2551
+
2552
+ #===============================================================================
2553
+ # on_run_complete (Slice 3: opt-in local git output on success)
2554
+ #
2555
+ # Called from every SUCCESS exit BEFORE emit_completion_summary so the PR url it
2556
+ # discovers is folded into the summary. Default behavior is a no-op: it only
2557
+ # acts when LOKI_DELEGATE_PR=1.
2558
+ #
2559
+ # LOKI_DELEGATE_PR=1 opens a LOCAL pull request from the user's machine, only if:
2560
+ # - this is a GitHub repo (gh + a github.com remote), AND
2561
+ # - `gh auth status` succeeds, AND
2562
+ # - the current branch is not main/master (never PR a default branch to itself)
2563
+ # It mirrors the proven pattern at autonomy/loki:5524-5527: push the branch,
2564
+ # then `gh pr create --head <branch>`. NO auto-merge. Every call is best-effort
2565
+ # (`|| true`); failures never block completion. This is a single sanctioned
2566
+ # local network call, never CI.
2567
+ #
2568
+ # Reconciliation with the existing GITHUB_PR path (run.sh create_github_pr,
2569
+ # invoked after run_autonomous returns when LOKI_GITHUB_PR=true): if GITHUB_PR
2570
+ # is already true we DEFER to that path and do nothing here, so a user who set
2571
+ # both knobs never gets a double PR.
2572
+ #===============================================================================
2573
+ on_run_complete() {
2574
+ # Default OFF.
2575
+ if [ "${LOKI_DELEGATE_PR:-0}" != "1" ]; then
2576
+ return 0
2577
+ fi
2578
+ # Defer to the existing dedicated PR path to avoid a double PR.
2579
+ if [ "${GITHUB_PR:-false}" = "true" ]; then
2580
+ return 0
2581
+ fi
2582
+ # Network-call timeout guard: a stalled network / auth prompt would
2583
+ # otherwise hang the completion path indefinitely in --bg. Run each network
2584
+ # call through `timeout 30` when available; fall back to the bare call if
2585
+ # timeout is not installed (a local wrapper keeps this set -u safe on bash
2586
+ # 3.2, where an empty array expansion would error). Keeps every existing
2587
+ # `|| true` non-fatal behavior.
2588
+ _loki_net() {
2589
+ if command -v timeout >/dev/null 2>&1; then
2590
+ timeout 30 "$@"
2591
+ else
2592
+ "$@"
2593
+ fi
2594
+ }
2595
+ # Require gh + auth.
2596
+ if ! command -v gh >/dev/null 2>&1; then
2597
+ return 0
2598
+ fi
2599
+ if ! (cd "${TARGET_DIR:-.}" && _loki_net gh auth status) >/dev/null 2>&1; then
2600
+ return 0
2601
+ fi
2602
+ # Require a GitHub remote (skip silently on non-GitHub repos).
2603
+ local remote_url
2604
+ remote_url="$( (cd "${TARGET_DIR:-.}" && git config --get remote.origin.url) 2>/dev/null || true )"
2605
+ case "$remote_url" in
2606
+ *github.com*) : ;;
2607
+ *) return 0 ;;
2608
+ esac
2609
+ # Resolve current branch; never PR a default branch to itself.
2610
+ local branch
2611
+ branch="$( (cd "${TARGET_DIR:-.}" && git rev-parse --abbrev-ref HEAD) 2>/dev/null || true )"
2612
+ case "$branch" in
2613
+ ""|main|master|HEAD) return 0 ;;
2614
+ esac
2615
+ log_info "LOKI_DELEGATE_PR=1: opening a local pull request for branch '$branch'..."
2616
+ # Push, then create. Non-interactive (no tty in --bg). Best-effort, each
2617
+ # network call bounded by the timeout guard above.
2618
+ (cd "${TARGET_DIR:-.}" && _loki_net git push -u origin "$branch") >/dev/null 2>&1 || true
2619
+ local pr_title
2620
+ pr_title="Loki Mode: ${branch}"
2621
+ local pr_url=""
2622
+ pr_url="$( (cd "${TARGET_DIR:-.}" && _loki_net gh pr create --title "$pr_title" --body "Opened by Loki Mode (delegate mode). Review locally before merge." --head "$branch") 2>/dev/null || true )"
2623
+ if [ -n "$pr_url" ]; then
2624
+ # Export so build_completion_summary folds the url into the summary.
2625
+ _LOKI_DELEGATE_PR_URL="$pr_url"
2626
+ export _LOKI_DELEGATE_PR_URL
2627
+ log_info "Pull request opened: $pr_url"
2628
+ else
2629
+ log_warn "LOKI_DELEGATE_PR=1: gh pr create did not return a URL (a PR may already exist for this branch)."
2630
+ fi
2631
+ return 0
2632
+ }
2633
+
2366
2634
  #===============================================================================
2367
2635
  # Parallel Workflow Functions (Git Worktrees)
2368
2636
  #===============================================================================
@@ -10068,6 +10336,14 @@ build_prompt() {
10068
10336
  # is the single most leveraged grounding primitive per OpenCode research.
10069
10337
  local lsp_grounding_instruction="LSP_GROUNDING: When the loki-mode-lsp-proxy MCP server is available, prefer LSP tools for symbol verification BEFORE writing code that references those symbols. Workflow: (1) Need to call \`foo.bar()\` you have not already read? -> mcp__loki-mode-lsp-proxy__lsp_check_exists with symbol='bar' (sub-200ms when cached). If exists:false, do NOT write the call -- use mcp__loki-mode-lsp-proxy__lsp_workspace_symbols with the concept name to find the real symbol, or use Read to see the actual API. (2) Just edited a file? -> mcp__loki-mode-lsp-proxy__lsp_get_diagnostics on that file to see new errors before the next iteration. (3) Need to jump to a definition by name (no file:line known)? -> mcp__loki-mode-lsp-proxy__lsp_find_definition_by_name. Skip these tools silently when the server is not available -- check the tool list, do not retry on errors. Goal: eliminate hallucinated API calls before they ship."
10070
10338
 
10339
+ # AGENTS.md instruction (agents.md standard: plain Markdown at repo root,
10340
+ # nearest-file-wins, read natively by Claude Code/Codex/etc.). Loki prefers
10341
+ # AGENTS.md and falls back to CLAUDE.md only when AGENTS.md is absent; the
10342
+ # two are never merged. This string MUST stay byte-identical to
10343
+ # AGENTS_MD_INSTRUCTION in loki-ts/src/runner/build_prompt.ts (parity-locked,
10344
+ # same precedent as AUTONOMY_OVERRIDE_TEXT in providers/claude_flags.ts).
10345
+ local agents_md_instruction="Project conventions: read AGENTS.md in the repository root for build, test, and style conventions. If AGENTS.md is absent, read CLAUDE.md instead. The nearest such file to the code you are editing takes precedence."
10346
+
10071
10347
  # Load existing context if resuming
10072
10348
  local context_injection=""
10073
10349
  if [ $retry -gt 0 ]; then
@@ -10397,15 +10673,15 @@ except Exception:
10397
10673
  else
10398
10674
  if [ $retry -eq 0 ]; then
10399
10675
  if [ -n "$prd" ]; then
10400
- echo "Loki Mode with PRD at $prd. $update_instruction $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $usage_doc_instruction $lsp_grounding_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
10676
+ echo "Loki Mode with PRD at $prd. $update_instruction $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $usage_doc_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
10401
10677
  else
10402
- echo "Loki Mode. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $usage_doc_instruction $lsp_grounding_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
10678
+ echo "Loki Mode. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $usage_doc_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
10403
10679
  fi
10404
10680
  else
10405
10681
  if [ -n "$prd" ]; then
10406
- echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $usage_doc_instruction $lsp_grounding_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
10682
+ echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $usage_doc_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
10407
10683
  else
10408
- echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $usage_doc_instruction $lsp_grounding_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
10684
+ echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $usage_doc_instruction $lsp_grounding_instruction $agents_md_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
10409
10685
  fi
10410
10686
  fi
10411
10687
  fi
@@ -10448,6 +10724,7 @@ except Exception:
10448
10724
  fi
10449
10725
  printf '%s\n' "$usage_doc_instruction"
10450
10726
  printf '%s\n' "$lsp_grounding_instruction"
10727
+ printf '%s\n' "$agents_md_instruction"
10451
10728
  printf '</loki_system>\n'
10452
10729
  printf '[CACHE_BREAKPOINT]\n'
10453
10730
 
@@ -10480,6 +10757,7 @@ except Exception:
10480
10757
  printf '%s\n' "$memory_instruction"
10481
10758
  printf '%s\n' "$usage_doc_instruction"
10482
10759
  printf '%s\n' "$lsp_grounding_instruction"
10760
+ printf '%s\n' "$agents_md_instruction"
10483
10761
  # For codebase-analysis mode (no PRD), analysis_instruction is part of the
10484
10762
  # static prefix so it remains cache-stable.
10485
10763
  if [ -z "$prd" ]; then
@@ -11463,6 +11741,28 @@ run_autonomous() {
11463
11741
  # file, which the gate treats as inconclusive (pass-through).
11464
11742
  local _start_sha_file=".loki/state/start-sha"
11465
11743
  mkdir -p ".loki/state"
11744
+
11745
+ # Delegate-then-notify (Slice 3): LOKI_DELEGATE_BRANCH=1 (default OFF)
11746
+ # isolates this run's work on a fresh branch loki/delegate-<timestamp> so the
11747
+ # user's working branch stays clean. Created IN-PROCESS (plain git, no
11748
+ # detached child) only on a genuine fresh run (ITERATION_COUNT==0) so a
11749
+ # resume does not spawn a new branch each time. Best-effort: a non-git repo,
11750
+ # dirty tree that blocks checkout, or any git failure leaves the run on the
11751
+ # current branch (default behavior preserved). Done BEFORE the start-sha
11752
+ # capture so the diff window baselines to the new branch HEAD.
11753
+ if [ "${LOKI_DELEGATE_BRANCH:-0}" = "1" ] && [ "${ITERATION_COUNT:-0}" -eq 0 ]; then
11754
+ if (cd "${TARGET_DIR:-.}" && git rev-parse --git-dir) >/dev/null 2>&1; then
11755
+ local _delegate_branch="loki/delegate-$(date +%Y%m%d-%H%M%S)"
11756
+ if (cd "${TARGET_DIR:-.}" && git checkout -b "$_delegate_branch") >/dev/null 2>&1; then
11757
+ _LOKI_DELEGATE_BRANCH_NAME="$_delegate_branch"
11758
+ export _LOKI_DELEGATE_BRANCH_NAME
11759
+ log_info "LOKI_DELEGATE_BRANCH=1: isolated work on new branch '$_delegate_branch'"
11760
+ else
11761
+ log_warn "LOKI_DELEGATE_BRANCH=1: could not create branch (dirty tree or git error); continuing on current branch."
11762
+ fi
11763
+ fi
11764
+ fi
11765
+
11466
11766
  if [ "${ITERATION_COUNT:-0}" -eq 0 ] || [ ! -s "$_start_sha_file" ]; then
11467
11767
  (cd "${TARGET_DIR:-.}" && git rev-parse HEAD 2>/dev/null) > "$_start_sha_file" 2>/dev/null || true
11468
11768
  fi
@@ -11546,6 +11846,13 @@ except Exception as exc:
11546
11846
  # Check max iterations before starting
11547
11847
  if check_max_iterations; then
11548
11848
  log_error "Max iterations already reached. Reset with: rm .loki/autonomy-state.json"
11849
+ # Delegate-then-notify: terminal state. Mirror the in-loop max-iterations
11850
+ # site so a detached (--bg) run still writes COMPLETION.txt + fires the
11851
+ # ping on this pre-loop exit. _LOKI_RUN_START_SHA is already exported
11852
+ # above (runner init), so the diff window is correct. This return is
11853
+ # mutually exclusive with the in-loop site (it returns before the loop),
11854
+ # so there is no double-emit.
11855
+ emit_completion_summary max_iterations
11549
11856
  return 1
11550
11857
  fi
11551
11858
 
@@ -11573,6 +11880,9 @@ except Exception as exc:
11573
11880
  # Check max iterations
11574
11881
  if check_max_iterations; then
11575
11882
  save_state $retry "max_iterations_reached" 0
11883
+ # Delegate-then-notify: terminal state, write summary + ping so a
11884
+ # detached run tells the user it stopped at the iteration cap.
11885
+ emit_completion_summary max_iterations
11576
11886
  return 0
11577
11887
  fi
11578
11888
 
@@ -12469,7 +12779,11 @@ if __name__ == "__main__":
12469
12779
  log_info "Council voted to stop (convergence detected + requirements verified)"
12470
12780
  log_info "Running memory consolidation..."
12471
12781
  run_memory_consolidation
12472
- notify_all_complete
12782
+ # Delegate-then-notify: optional local PR on success, then the
12783
+ # durable summary + desktop ping. on_run_complete is idempotent
12784
+ # and only opens a PR when LOKI_DELEGATE_PR=1 (default OFF).
12785
+ on_run_complete
12786
+ emit_completion_summary complete
12473
12787
  save_state $retry "council_approved" 0
12474
12788
  rm -f "$iter_output" 2>/dev/null
12475
12789
  return 0
@@ -12532,7 +12846,10 @@ if __name__ == "__main__":
12532
12846
  # Run memory consolidation on successful completion
12533
12847
  log_info "Running memory consolidation..."
12534
12848
  run_memory_consolidation
12535
- notify_all_complete
12849
+ # Delegate-then-notify: optional local PR on success, then the
12850
+ # durable summary + desktop ping (see on_run_complete).
12851
+ on_run_complete
12852
+ emit_completion_summary complete
12536
12853
  save_state $retry "completion_promise_fulfilled" 0
12537
12854
  rm -f "$iter_output" 2>/dev/null
12538
12855
  return 0
@@ -12626,6 +12943,9 @@ if __name__ == "__main__":
12626
12943
 
12627
12944
  log_error "Max retries ($MAX_RETRIES) exceeded"
12628
12945
  save_state $retry "failed" 1
12946
+ # Delegate-then-notify: terminal failure. critical urgency so the desktop
12947
+ # ping is louder; the summary file records where the partial work landed.
12948
+ emit_completion_summary failed critical
12629
12949
  return 1
12630
12950
  }
12631
12951
 
@@ -12768,11 +13088,19 @@ check_human_intervention() {
12768
13088
  log_warn "PAUSE file created by budget limit - NOT auto-clearing in perpetual mode"
12769
13089
  log_warn "Budget limit reached. Remove .loki/signals/BUDGET_EXCEEDED and .loki/PAUSE to continue."
12770
13090
  notify_intervention_needed "Budget limit reached - execution paused" 2>/dev/null || true
13091
+ # Genuinely blocking pause: write the durable intervention record
13092
+ # now (state-only; the ping above already fired). This is the
13093
+ # correct site for the durable file because the run actually halts
13094
+ # here until the operator clears the budget signal.
13095
+ build_completion_summary intervention 2>/dev/null || true
12771
13096
  local pause_result
12772
13097
  handle_pause
12773
13098
  pause_result=$?
12774
13099
  rm -f "$loki_dir/PAUSE"
12775
13100
  if [ "$pause_result" -eq 1 ]; then
13101
+ # STOP requested DURING the pause: relabel the durable record
13102
+ # as stopped (state-only; the user typed STOP and is aware).
13103
+ build_completion_summary stopped 2>/dev/null || true
12776
13104
  return 2
12777
13105
  fi
12778
13106
  return 1
@@ -12786,12 +13114,17 @@ check_human_intervention() {
12786
13114
  fi
12787
13115
  log_warn "PAUSE file detected - pausing execution"
12788
13116
  notify_intervention_needed "Execution paused via PAUSE file"
13117
+ # Genuinely blocking pause: write the durable intervention record now
13118
+ # (state-only; the ping above already fired).
13119
+ build_completion_summary intervention 2>/dev/null || true
12789
13120
  local pause_result
12790
13121
  handle_pause
12791
13122
  pause_result=$?
12792
13123
  rm -f "$loki_dir/PAUSE"
12793
13124
  if [ "$pause_result" -eq 1 ]; then
12794
- # STOP was requested during pause
13125
+ # STOP was requested during pause: relabel the durable record as
13126
+ # stopped (state-only; the user typed STOP and is aware).
13127
+ build_completion_summary stopped 2>/dev/null || true
12795
13128
  return 2
12796
13129
  fi
12797
13130
  return 1
@@ -12804,11 +13137,16 @@ check_human_intervention() {
12804
13137
  rm -f "$loki_dir/PAUSE_AT_CHECKPOINT"
12805
13138
  notify_intervention_needed "Execution paused at checkpoint"
12806
13139
  touch "$loki_dir/PAUSE"
13140
+ # Genuinely blocking pause: write the durable intervention record now
13141
+ # (state-only; the ping above already fired).
13142
+ build_completion_summary intervention 2>/dev/null || true
12807
13143
  local pause_result
12808
13144
  handle_pause
12809
13145
  pause_result=$?
12810
13146
  rm -f "$loki_dir/PAUSE"
12811
13147
  if [ "$pause_result" -eq 1 ]; then
13148
+ # STOP requested during pause: relabel as stopped (state-only).
13149
+ build_completion_summary stopped 2>/dev/null || true
12812
13150
  return 2
12813
13151
  fi
12814
13152
  return 1
@@ -12876,7 +13214,11 @@ check_human_intervention() {
12876
13214
  fi
12877
13215
  log_info "Running memory consolidation..."
12878
13216
  run_memory_consolidation
12879
- notify_all_complete
13217
+ # Delegate-then-notify: force-review approval is a real completion
13218
+ # (returns 2, which the run loop maps to a clean return 0). Treat it
13219
+ # like the other success exits: optional local PR + summary + ping.
13220
+ on_run_complete
13221
+ emit_completion_summary complete
12880
13222
  save_state ${RETRY_COUNT:-0} "council_force_approved" 0
12881
13223
  return 2 # Stop
12882
13224
  fi
@@ -12887,6 +13229,12 @@ check_human_intervention() {
12887
13229
  if [ -f "$loki_dir/STOP" ]; then
12888
13230
  log_warn "STOP file detected - stopping execution"
12889
13231
  rm -f "$loki_dir/STOP"
13232
+ # Delegate-then-notify: an explicit STOP file is a deliberate stop, but
13233
+ # a detached (--bg) user still benefits from a summary of partial work.
13234
+ # NOTE: the SIGTERM/`loki stop` group-kill path (cleanup handler near the
13235
+ # end of this file) is intentionally NOT notified: that user is at a
13236
+ # terminal issuing the stop and is already aware.
13237
+ emit_completion_summary stopped
12890
13238
  return 2
12891
13239
  fi
12892
13240
 
@@ -13354,6 +13702,9 @@ main() {
13354
13702
  echo -e " ${DIM}Logs:${NC} tail -f $log_file"
13355
13703
  echo -e " ${DIM}Status:${NC} cat .loki/STATUS.txt"
13356
13704
  echo ""
13705
+ echo -e "${GREEN}You will be notified when done (or if input is needed).${NC}"
13706
+ echo -e " ${DIM}Summary on completion:${NC} cat .loki/COMPLETION.txt"
13707
+ echo ""
13357
13708
 
13358
13709
  exit 0
13359
13710
  fi
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.20.0"
10
+ __version__ = "7.22.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -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.20.0
5
+ **Version:** v7.22.0
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var f8=Object.defineProperty;var u8=($)=>$;function c8($,Q){this[$]=u8.bind(null,Q)}var g=($,Q)=>{for(var Z in Q)f8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:c8.bind(Q,Z)})};var k=($,Q)=>()=>($&&(Q=$($=0)),Q);var X1=import.meta.require;var F$={};g(F$,{lokiDir:()=>P,homeLokiDir:()=>o1,findRepoRootForVersion:()=>d1,REPO_ROOT:()=>f});import{resolve as n,dirname as l1}from"path";import{fileURLToPath as p8}from"url";import{existsSync as L1}from"fs";import{homedir as l8}from"os";function d8(){let $=j$;for(let Q=0;Q<6;Q++){if(L1(n($,"VERSION"))&&L1(n($,"autonomy/run.sh")))return $;let Z=l1($);if(Z===$)break;$=Z}return n(j$,"..","..","..")}function d1($){let Q=$;for(let Z=0;Z<6;Z++){if(L1(n(Q,"VERSION"))&&L1(n(Q,"autonomy/run.sh")))return Q;let z=l1(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o1(){return n(l8(),".loki")}var j$,f;var y=k(()=>{j$=l1(p8(import.meta.url));f=d8()});import{readFileSync as o8}from"fs";import{resolve as n8,dirname as a8}from"path";import{fileURLToPath as s8}from"url";function k1(){if($1!==null)return $1;let $="7.20.0";if(typeof $==="string"&&$.length>0)return $1=$,$1;try{let Q=a8(s8(import.meta.url)),Z=d1(Q);$1=o8(n8(Z,"VERSION"),"utf-8").trim()}catch{$1="unknown"}return $1}var $1=null;var n1=k(()=>{y()});var E$={};g(E$,{runOrThrow:()=>t8,run:()=>j,commandVersion:()=>i8,commandExists:()=>v,ShellError:()=>a1});async function j($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,K;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}K=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[H,X,q]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:H,stderr:X,exitCode:q}}finally{if(z)clearTimeout(z);if(K)clearTimeout(K)}}async function t8($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function v($){let Q=r8($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function r8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function i8($,Q="--version"){if(!await v($))return null;let z=await j([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var a1;var d=k(()=>{a1=class a1 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 a($){return e8?"":$}var e8,T,N,_,KZ,A,R,h,J;var c=k(()=>{e8=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),N=a("\x1B[0;32m"),_=a("\x1B[1;33m"),KZ=a("\x1B[0;34m"),A=a("\x1B[0;36m"),R=a("\x1B[1m"),h=a("\x1B[2m"),J=a("\x1B[0m")});import{existsSync as U7}from"fs";async function Q1(){if(B1!==void 0)return B1;let $="/opt/homebrew/bin/python3.12";if(U7($))return B1=$,$;let Q=await v("python3.12");if(Q)return B1=Q,Q;let Z=await v("python3");return B1=Z,Z}async function Z1($,Q={}){let Z=await Q1();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B1;var H1=k(()=>{d()});var d$={};g(d$,{runStatus:()=>N7});import{existsSync as b,readFileSync as q1,readdirSync as v$,statSync as f$}from"fs";import{resolve as D,basename as P7}from"path";import{homedir as L7}from"os";async function j7(){if(await v("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${J}
2
+ var f8=Object.defineProperty;var u8=($)=>$;function c8($,Q){this[$]=u8.bind(null,Q)}var g=($,Q)=>{for(var Z in Q)f8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:c8.bind(Q,Z)})};var k=($,Q)=>()=>($&&(Q=$($=0)),Q);var X1=import.meta.require;var F$={};g(F$,{lokiDir:()=>P,homeLokiDir:()=>o1,findRepoRootForVersion:()=>d1,REPO_ROOT:()=>f});import{resolve as n,dirname as l1}from"path";import{fileURLToPath as p8}from"url";import{existsSync as L1}from"fs";import{homedir as l8}from"os";function d8(){let $=j$;for(let Q=0;Q<6;Q++){if(L1(n($,"VERSION"))&&L1(n($,"autonomy/run.sh")))return $;let Z=l1($);if(Z===$)break;$=Z}return n(j$,"..","..","..")}function d1($){let Q=$;for(let Z=0;Z<6;Z++){if(L1(n(Q,"VERSION"))&&L1(n(Q,"autonomy/run.sh")))return Q;let z=l1(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o1(){return n(l8(),".loki")}var j$,f;var y=k(()=>{j$=l1(p8(import.meta.url));f=d8()});import{readFileSync as o8}from"fs";import{resolve as n8,dirname as a8}from"path";import{fileURLToPath as s8}from"url";function k1(){if($1!==null)return $1;let $="7.22.0";if(typeof $==="string"&&$.length>0)return $1=$,$1;try{let Q=a8(s8(import.meta.url)),Z=d1(Q);$1=o8(n8(Z,"VERSION"),"utf-8").trim()}catch{$1="unknown"}return $1}var $1=null;var n1=k(()=>{y()});var E$={};g(E$,{runOrThrow:()=>t8,run:()=>j,commandVersion:()=>i8,commandExists:()=>v,ShellError:()=>a1});async function j($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,K;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}K=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[H,X,q]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:H,stderr:X,exitCode:q}}finally{if(z)clearTimeout(z);if(K)clearTimeout(K)}}async function t8($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function v($){let Q=r8($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function r8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function i8($,Q="--version"){if(!await v($))return null;let z=await j([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var a1;var d=k(()=>{a1=class a1 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 a($){return e8?"":$}var e8,T,N,_,KZ,A,R,h,J;var c=k(()=>{e8=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),N=a("\x1B[0;32m"),_=a("\x1B[1;33m"),KZ=a("\x1B[0;34m"),A=a("\x1B[0;36m"),R=a("\x1B[1m"),h=a("\x1B[2m"),J=a("\x1B[0m")});import{existsSync as U7}from"fs";async function Q1(){if(B1!==void 0)return B1;let $="/opt/homebrew/bin/python3.12";if(U7($))return B1=$,$;let Q=await v("python3.12");if(Q)return B1=Q,Q;let Z=await v("python3");return B1=Z,Z}async function Z1($,Q={}){let Z=await Q1();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B1;var H1=k(()=>{d()});var d$={};g(d$,{runStatus:()=>N7});import{existsSync as b,readFileSync as q1,readdirSync as v$,statSync as f$}from"fs";import{resolve as D,basename as P7}from"path";import{homedir as L7}from"os";async function j7(){if(await v("jq"))return!0;return process.stdout.write(`${T}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)
@@ -787,4 +787,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
787
787
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
788
788
  `),process.stderr.write(v8),2}}g$();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var l3=await p3(Bun.argv.slice(2));process.exit(l3);
789
789
 
790
- //# debugId=A7D2C59E16AFDEA864756E2164756E21
790
+ //# debugId=BA6A18624E28A6CC64756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.20.0'
60
+ __version__ = '7.22.0'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "7.20.0",
3
+ "version": "7.22.0",
4
4
  "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 11 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
5
5
  "keywords": [
6
6
  "agent",