loki-mode 7.97.0 → 7.98.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
@@ -197,7 +197,7 @@ loki quick "build a landing page with a signup form"
197
197
  | **Bun (recommended)** | `bun install -g loki-mode` | Fastest startup for CLI commands. |
198
198
  | **Homebrew** | `brew tap asklokesh/tap && brew install loki-mode` | Auto-installs Bun as a dep |
199
199
  | **Docker (easiest)** | `loki docker start prd.md` | Host wrapper: runs loki in the published image with zero config. Bind-mounts the current folder so `.loki` state, resume, and continuity work exactly like local. Auto-detects auth (`ANTHROPIC_API_KEY`, else your host Claude Code login). Needs loki + Docker on the host. See DOCKER_README.md |
200
- | **Docker (raw)** | `docker pull asklokesh/loki-mode:7.58.1 && docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" asklokesh/loki-mode:7.58.1 start prd.md` | Bun + Claude CLI pre-installed; needs an API key, or use docker compose with a .env file, see DOCKER_README.md |
200
+ | **Docker (raw)** | `docker pull asklokesh/loki-mode:latest && docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" asklokesh/loki-mode:latest start prd.md` | Bun + Claude CLI pre-installed; needs an API key, or use docker compose with a .env file, see DOCKER_README.md |
201
201
  | **npm (compat)** | `npm install -g loki-mode` | Works without Bun (bash fallback). Migrate any time with `loki self-update --to bun`. |
202
202
 
203
203
  **Upgrading:**
@@ -257,7 +257,7 @@ The next major release sunsets the Bash runtime entirely. There is no firm calen
257
257
  | Method | Command |
258
258
  |--------|---------|
259
259
  | **Homebrew** | `brew tap asklokesh/tap && brew install loki-mode` |
260
- | **Docker** | `docker pull asklokesh/loki-mode:7.58.1` |
260
+ | **Docker** | `docker pull asklokesh/loki-mode:latest` |
261
261
  | **Inside Claude Code** | `claude --dangerously-skip-permissions` then type "Loki Mode" |
262
262
  | **Git clone** | `git clone https://github.com/asklokesh/loki-mode.git` |
263
263
 
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.97.0
6
+ # Loki Mode v7.98.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
408
408
 
409
409
  ---
410
410
 
411
- **v7.97.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
411
+ **v7.98.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.97.0
1
+ 7.98.0
@@ -44,7 +44,37 @@
44
44
  # Council configuration
45
45
  COUNCIL_ENABLED=${LOKI_COUNCIL_ENABLED:-true}
46
46
  COUNCIL_SIZE=${LOKI_COUNCIL_SIZE:-3}
47
+ # Guard COUNCIL_SIZE: a non-positive/non-numeric size would make the 2/3 floor 0
48
+ # and let an empty council "approve" by default (fake-green). Force a sane floor.
49
+ case "$COUNCIL_SIZE" in
50
+ ''|*[!0-9]*) COUNCIL_SIZE=3 ;;
51
+ esac
52
+ [ "$COUNCIL_SIZE" -lt 1 ] 2>/dev/null && COUNCIL_SIZE=3
47
53
  COUNCIL_THRESHOLD=${LOKI_COUNCIL_THRESHOLD:-2}
54
+
55
+ # Effective completion threshold: the operator's COUNCIL_THRESHOLD may only ever
56
+ # TIGHTEN the gate, never weaken it below the 2/3-majority safety floor. Before
57
+ # this, the operator's COUNCIL_THRESHOLD was silently ignored by the vote (a
58
+ # hardcoded 2/3 formula was used), so an operator who set a stricter threshold
59
+ # got a no-op while the logs claimed it was active. We honor it as a floor-raise
60
+ # only: effective = max(ceil(2/3*size), operator_threshold), clamped to size.
61
+ _council_effective_threshold() {
62
+ # Optional $1 = the size to compute against (e.g. the actual number of voters
63
+ # present in this round); defaults to COUNCIL_SIZE. The 2/3 floor + the
64
+ # operator clamp are both relative to this size, so the helper works for both
65
+ # the configured council size and a per-round member count.
66
+ local _size="${1:-$COUNCIL_SIZE}"
67
+ case "$_size" in ''|*[!0-9]*) _size="$COUNCIL_SIZE" ;; esac
68
+ [ "$_size" -lt 1 ] 2>/dev/null && _size=1
69
+ local _floor=$(( (_size * 2 + 2) / 3 ))
70
+ local _op="${COUNCIL_THRESHOLD:-0}"
71
+ case "$_op" in ''|*[!0-9]*) _op=0 ;; esac
72
+ local _eff="$_floor"
73
+ [ "$_op" -gt "$_eff" ] 2>/dev/null && _eff="$_op"
74
+ [ "$_eff" -gt "$_size" ] 2>/dev/null && _eff="$_size"
75
+ [ "$_eff" -lt 1 ] 2>/dev/null && _eff=1
76
+ printf '%s' "$_eff"
77
+ }
48
78
  COUNCIL_CHECK_INTERVAL=${LOKI_COUNCIL_CHECK_INTERVAL:-5}
49
79
  # Guard against invalid interval (must be positive integer)
50
80
  if ! [[ "$COUNCIL_CHECK_INTERVAL" =~ ^[1-9][0-9]*$ ]]; then
@@ -608,8 +638,11 @@ council_vote() {
608
638
  log_header "COMPLETION COUNCIL - Iteration $ITERATION_COUNT"
609
639
  log_info "Convening ${COUNCIL_SIZE}-member council..."
610
640
 
611
- # Compute threshold using ceiling(2/3) formula, consistent with council_aggregate_votes
612
- local effective_threshold=$(( (COUNCIL_SIZE * 2 + 2) / 3 ))
641
+ # Effective threshold honors the operator's COUNCIL_THRESHOLD as a floor-raise
642
+ # over the 2/3-majority safety floor (tighten-only); see
643
+ # _council_effective_threshold.
644
+ local effective_threshold
645
+ effective_threshold=$(_council_effective_threshold)
613
646
 
614
647
  # Gather evidence for council members
615
648
  local evidence_file="$vote_dir/evidence.md"
@@ -2604,7 +2637,13 @@ print(json.dumps(out))
2604
2637
  " 2>/dev/null || echo "[]")
2605
2638
 
2606
2639
  # Calculate threshold: 2/3 majority
2607
- local threshold=$(( (total_members * 2 + 2) / 3 )) # ceiling of 2/3
2640
+ # Effective threshold honors the operator's COUNCIL_THRESHOLD as a tighten-only
2641
+ # floor-raise over the 2/3 ceiling, computed against the actual member count.
2642
+ # This is the PRIMARY completion-decision path (council_should_stop ->
2643
+ # council_evaluate -> council_aggregate_votes), so it must use the same helper
2644
+ # as council_vote, not a separate hardcoded formula.
2645
+ local threshold
2646
+ threshold=$(_council_effective_threshold "$total_members")
2608
2647
  local verdict="CONTINUE"
2609
2648
  if [ "$complete_count" -ge "$threshold" ]; then
2610
2649
  verdict="COMPLETE"
@@ -2833,7 +2872,8 @@ council_evaluate() {
2833
2872
  fi
2834
2873
 
2835
2874
  # Compute threshold using the same ceiling(2/3) formula as council_vote and council_aggregate_votes
2836
- local _eval_threshold=$(( (COUNCIL_SIZE * 2 + 2) / 3 ))
2875
+ local _eval_threshold
2876
+ _eval_threshold=$(_council_effective_threshold)
2837
2877
 
2838
2878
  # Step 1: Aggregate votes from all members.
2839
2879
  # Phase C (v7.5.20): try the Claude `--agents <json>` + `--json-schema`
package/autonomy/run.sh CHANGED
@@ -2058,6 +2058,25 @@ validate_api_keys() {
2058
2058
  return 1
2059
2059
  fi
2060
2060
 
2061
+ # Zero-friction auth preflight for LOCAL runs (must run BEFORE the early
2062
+ # return below, which exits for non-Docker/K8s envs). When claude is the
2063
+ # provider, the claude CLI is on PATH, there is no ANTHROPIC_API_KEY, and no
2064
+ # OAuth credentials file exists (the user installed claude but never ran
2065
+ # `claude login`), the build would otherwise enter, make a failing call, and
2066
+ # 401 -- the worst first impression -- forcing the user to run `loki why`.
2067
+ # Fail fast with the one-step fix instead. Opt out with LOKI_SKIP_AUTH_PREFLIGHT=1.
2068
+ if [[ "$provider" == "claude" && "${LOKI_SKIP_AUTH_PREFLIGHT:-}" != "1" && -z "${ANTHROPIC_API_KEY:-}" ]] \
2069
+ && command -v claude >/dev/null 2>&1; then
2070
+ local _local_creds="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.credentials.json"
2071
+ if [[ ! -s "$_local_creds" ]]; then
2072
+ log_error "Claude Code is installed but not logged in -- the build would stall instead of running."
2073
+ log_error "Log in once, then retry:"
2074
+ log_error " claude login"
2075
+ log_error "(or set ANTHROPIC_API_KEY, or LOKI_SKIP_AUTH_PREFLIGHT=1 to bypass this check)"
2076
+ return 1
2077
+ fi
2078
+ fi
2079
+
2061
2080
  # CLI tools (claude, codex, cline, aider) use their own login sessions.
2062
2081
  # Only require API keys inside Docker/K8s where CLI login isn't available.
2063
2082
  if [[ ! -f "/.dockerenv" ]] && [[ -z "${KUBERNETES_SERVICE_HOST:-}" ]]; then
@@ -2162,6 +2181,9 @@ except Exception:
2162
2181
  return 1
2163
2182
  fi
2164
2183
  fi
2184
+ # NOTE: the never-logged-in (no credentials file) case is handled earlier,
2185
+ # BEFORE the local early-return, so it covers local runs too (this Docker/
2186
+ # K8s-only block would otherwise be unreachable for that case).
2165
2187
  fi
2166
2188
 
2167
2189
  return 0
@@ -12224,16 +12246,31 @@ check_task_completion_signal() {
12224
12246
  local fb_statement="${fb_content:-All PRD requirements implemented and tests passing}"
12225
12247
  # Build minimal JSON payload
12226
12248
  signal_file="$fallback_file"
12227
- # Write the synthesized payload back into the signal file so the
12228
- # rest of this function can read it uniformly.
12229
- python3 -c "
12249
+ # Synthesize the payload into a TEMP file then atomically move it into
12250
+ # place, so the signal file is never observed truncated/empty/malformed:
12251
+ # a bare `python3 ... > "$fallback_file"` truncates the file BEFORE python
12252
+ # runs, so a python crash (or missing interpreter) mid-write would leave
12253
+ # an empty/partial file that downstream json.loads then silently drops.
12254
+ local _fb_tmp="${fallback_file}.tmp.$$"
12255
+ if python3 -c "
12230
12256
  import json, sys
12231
- print(json.dumps({
12257
+ sys.stdout.write(json.dumps({
12232
12258
  'statement': sys.argv[1][:1000],
12233
12259
  'evidence': 'file-based completion via COMPLETION_REQUESTED fallback',
12234
12260
  'confidence': 'medium',
12235
12261
  'source': 'completion_requested_file_fallback'
12236
- }))" "$fb_statement" > "$fallback_file" 2>/dev/null || echo '{}' > "$fallback_file"
12262
+ }))" "$fb_statement" > "$_fb_tmp" 2>/dev/null && [ -s "$_fb_tmp" ]; then
12263
+ mv -f "$_fb_tmp" "$fallback_file" 2>/dev/null || rm -f "$_fb_tmp" 2>/dev/null
12264
+ else
12265
+ # python unavailable or produced nothing: write a hand-built minimal
12266
+ # valid JSON (statement embedded with a conservative escape) atomically.
12267
+ rm -f "$_fb_tmp" 2>/dev/null
12268
+ local _fb_esc
12269
+ _fb_esc=$(printf '%s' "$fb_statement" | tr -d '"\\\n\r' | cut -c1-1000)
12270
+ printf '{"statement":"%s","evidence":"file-based completion via COMPLETION_REQUESTED fallback","confidence":"medium","source":"completion_requested_file_fallback"}' "$_fb_esc" > "${fallback_file}.tmp2.$$" 2>/dev/null \
12271
+ && mv -f "${fallback_file}.tmp2.$$" "$fallback_file" 2>/dev/null \
12272
+ || { rm -f "${fallback_file}.tmp2.$$" 2>/dev/null; printf '{"statement":"completion requested","evidence":"fallback","confidence":"low","source":"completion_requested_file_fallback"}' > "$fallback_file" 2>/dev/null; }
12273
+ fi
12237
12274
  fi
12238
12275
 
12239
12276
  if [ ! -f "$signal_file" ]; then
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.97.0"
10
+ __version__ = "7.98.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.97.0
5
+ **Version:** v7.98.0
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var z8=Object.defineProperty;var X8=($)=>$;function K8($,Q){this[$]=X8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)z8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:K8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var y1={};b(y1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as t,dirname as i$}from"path";import{fileURLToPath as q8}from"url";import{existsSync as E$}from"fs";import{homedir as J8}from"os";function V8(){let $=h1;for(let Q=0;Q<6;Q++){if(E$(t($,"VERSION"))&&E$(t($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return t(h1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(E$(t(Q,"VERSION"))&&E$(t(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function P(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(J8(),".loki")}var h1,h;var C=L(()=>{h1=i$(q8(import.meta.url));h=V8()});import{readFileSync as W8}from"fs";import{resolve as U8,dirname as H8}from"path";import{fileURLToPath as G8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.97.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=H8(G8(import.meta.url)),Z=e$(Q);Q$=W8(U8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var $1=L(()=>{C()});var u1={};b(u1,{runOrThrow:()=>x8,run:()=>F,readStreamCapped:()=>f1,commandVersion:()=>N8,commandExists:()=>f,ShellError:()=>Q1,MAX_STDOUT_BYTES:()=>m1});async function f1($,Q=m1){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 F($,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([f1(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 x8($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new Q1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=E8($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function E8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function N8($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var m1=16777216,Q1;var d=L(()=>{Q1=class Q1 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 S8?"":$}var S8,T,S,_,lZ,I,k,y,J;var c=L(()=>{S8=(process.env.NO_COLOR??"").length>0;T=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),lZ=r("\x1B[0;34m"),I=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as u8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(u8($))return A$=$,$;let Q=await f("python3.12");if(Q)return A$=Q,Q;let Z=await f("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var A$;var V$=L(()=>{d()});var q0={};b(q0,{runStatus:()=>W3});import{existsSync as v,readFileSync as U$,readdirSync as i1,statSync as e1}from"fs";import{resolve as D,basename as $3}from"path";import{homedir as Q3}from"os";function $0($){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 Q0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*D$/Q);if(X>D$)X=D$;let q=D$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=$0($),U=$0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${U})`}async function z3(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${J}
2
+ var z8=Object.defineProperty;var X8=($)=>$;function K8($,Q){this[$]=X8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)z8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:K8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var y1={};b(y1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as t,dirname as i$}from"path";import{fileURLToPath as q8}from"url";import{existsSync as E$}from"fs";import{homedir as J8}from"os";function V8(){let $=h1;for(let Q=0;Q<6;Q++){if(E$(t($,"VERSION"))&&E$(t($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return t(h1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(E$(t(Q,"VERSION"))&&E$(t(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function P(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(J8(),".loki")}var h1,h;var C=L(()=>{h1=i$(q8(import.meta.url));h=V8()});import{readFileSync as W8}from"fs";import{resolve as U8,dirname as H8}from"path";import{fileURLToPath as G8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.98.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=H8(G8(import.meta.url)),Z=e$(Q);Q$=W8(U8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var $1=L(()=>{C()});var u1={};b(u1,{runOrThrow:()=>x8,run:()=>F,readStreamCapped:()=>f1,commandVersion:()=>N8,commandExists:()=>f,ShellError:()=>Q1,MAX_STDOUT_BYTES:()=>m1});async function f1($,Q=m1){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 F($,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([f1(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 x8($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new Q1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=E8($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function E8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function N8($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var m1=16777216,Q1;var d=L(()=>{Q1=class Q1 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 S8?"":$}var S8,T,S,_,lZ,I,k,y,J;var c=L(()=>{S8=(process.env.NO_COLOR??"").length>0;T=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),lZ=r("\x1B[0;34m"),I=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as u8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(u8($))return A$=$,$;let Q=await f("python3.12");if(Q)return A$=Q,Q;let Z=await f("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var A$;var V$=L(()=>{d()});var q0={};b(q0,{runStatus:()=>W3});import{existsSync as v,readFileSync as U$,readdirSync as i1,statSync as e1}from"fs";import{resolve as D,basename as $3}from"path";import{homedir as Q3}from"os";function $0($){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 Q0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*D$/Q);if(X>D$)X=D$;let q=D$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=$0($),U=$0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${U})`}async function z3(){if(await f("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)
@@ -802,4 +802,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
802
802
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
803
803
  `),process.stderr.write(Z8),2}}r1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var FZ=await jZ(Bun.argv.slice(2));process.exit(FZ);
804
804
 
805
- //# debugId=ABBB64E42A394DE264756E2164756E21
805
+ //# debugId=5C332E0B9079B5FC64756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.97.0'
60
+ __version__ = '7.98.0'
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.97.0",
4
+ "version": "7.98.0",
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.97.0",
5
+ "version": "7.98.0",
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",