loki-mode 7.36.0 → 7.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md 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.36.0
6
+ # Loki Mode v7.37.1
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -398,4 +398,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
398
398
 
399
399
  ---
400
400
 
401
- **v7.36.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
401
+ **v7.37.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.36.0
1
+ 7.37.1
@@ -378,3 +378,78 @@ loki_ultrareview_supported() {
378
378
  loki_ultrareview_enabled() {
379
379
  [ "${LOKI_ULTRAREVIEW:-0}" = "1" ]
380
380
  }
381
+
382
+ # ---------------------------------------------------------------------------
383
+ # Session-continuity Phase 2 (GitHub #165) -- LOKI_RESUME_SESSION recovery resume
384
+ #
385
+ # NAMING COLLISION WARNING: Loki already has a user-facing `loki heal --resume`
386
+ # / `loki migrate --resume` CHECKPOINT flag (autonomy/loki). LOKI_RESUME_SESSION
387
+ # governs the CLAUDE-CLI session-resume layer (claude --resume <uuid>), NOT the
388
+ # Loki checkpoint resume. They are unrelated.
389
+ #
390
+ # SCOPE (minimum useful, design s2): on a RESTARTED run (the prior run was
391
+ # interrupted -- crash / rate limit / --max-budget cutoff -- so its non-terminal
392
+ # autonomy-state.json persisted iterationCount>0), the FIRST main-loop claude
393
+ # call of the restarted run emits `claude --resume <stored-uuid>` (the stable
394
+ # per-run uuid from .loki/state/claude-session.json) instead of a fresh
395
+ # stateless call, reattaching the prior Claude context. After that single
396
+ # resumed call the run reverts to normal per-iteration stateless behavior
397
+ # (injected memory carries forward as today). This is a RECOVERY feature, NOT a
398
+ # per-iteration resume chain -- so transcript growth cannot accumulate.
399
+ #
400
+ # CONSERVATIVE DEFAULT OFF: with the knob unset the default claude argv is
401
+ # byte-identical to v7.34 (no --resume ever emitted). Opt IN with
402
+ # LOKI_RESUME_SESSION=1. Gated on CLI support so an older claude degrades.
403
+ #
404
+ # MUTUAL EXCLUSION: --session-id and --resume are mutually exclusive on one
405
+ # claude invocation (claude rejects a session-id already in use, and resume
406
+ # replaces the fresh-session intent). The run.sh main-loop block emits the
407
+ # resume slice INSTEAD of the stamp slice on the one resumed call, never both.
408
+ # ---------------------------------------------------------------------------
409
+
410
+ # Predicate: is recovery resume enabled AND supported? CONSERVATIVE DEFAULT OFF.
411
+ # Opt IN with LOKI_RESUME_SESSION=1; gated on `claude --resume` support so an
412
+ # older CLI degrades to normal stateless behavior (no flag emitted).
413
+ loki_resume_session_enabled() {
414
+ [ "${LOKI_RESUME_SESSION:-0}" = "1" ] || return 1
415
+ loki_claude_flag_supported "--resume"
416
+ }
417
+
418
+ # Predicate: when resuming, also fork into a NEW session id (leaving the parent
419
+ # transcript untouched)? Only honored together with LOKI_RESUME_SESSION=1.
420
+ # DEFAULT OFF. Gated on `claude --fork-session` support.
421
+ loki_session_fork_enabled() {
422
+ [ "${LOKI_SESSION_FORK:-0}" = "1" ] || return 1
423
+ loki_resume_session_enabled || return 1
424
+ loki_claude_flag_supported "--fork-session"
425
+ }
426
+
427
+ # The stable per-run claude session uuid to resume, read from the run-start
428
+ # metadata file .loki/state/claude-session.json (written on the FRESH run, so it
429
+ # survives into a restart). Emits the stored uuid on stdout, or nothing when the
430
+ # file is absent / unreadable / has no uuid (caller then skips resume and runs a
431
+ # normal fresh call -- safe degrade). Pure read, no side effects. Honors LOKI_DIR
432
+ # / TARGET_DIR exactly like the rest of run.sh.
433
+ _loki_resume_target_uuid() {
434
+ local loki_dir="${LOKI_DIR:-${TARGET_DIR:-.}/.loki}"
435
+ local cs_file="$loki_dir/state/claude-session.json"
436
+ [ -s "$cs_file" ] || return 0
437
+ command -v python3 >/dev/null 2>&1 || return 0
438
+ _LOKI_CS_FILE="$cs_file" python3 - <<'RESUME_UUID_PY' 2>/dev/null || true
439
+ import json, os, re
440
+ try:
441
+ with open(os.environ["_LOKI_CS_FILE"]) as f:
442
+ d = json.load(f)
443
+ if not isinstance(d, dict):
444
+ raise ValueError
445
+ u = d.get("claude_session_uuid", "")
446
+ # RFC-4122 shape check: only print a well-formed uuid so a corrupt file
447
+ # never injects a bogus --resume argument.
448
+ if isinstance(u, str) and re.match(
449
+ r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", u
450
+ ):
451
+ print(u, end="")
452
+ except Exception:
453
+ pass
454
+ RESUME_UUID_PY
455
+ }
package/autonomy/run.sh CHANGED
@@ -12390,6 +12390,25 @@ except Exception:
12390
12390
  fi
12391
12391
  fi
12392
12392
 
12393
+ # Session-continuity Phase 2 (GitHub #165): snapshot whether THIS run is a
12394
+ # RESTARTED run BEFORE the main loop mutates ITERATION_COUNT. load_state
12395
+ # (called above) restored ITERATION_COUNT from .loki/autonomy-state.json's
12396
+ # iterationCount, resetting to 0 after a terminal prior run. So at this point
12397
+ # ITERATION_COUNT>0 means "the prior run was interrupted (non-terminal) and
12398
+ # is being restarted"; ==0 means fresh. The main loop increments
12399
+ # ITERATION_COUNT at the top of each pass, so the resume decision MUST key on
12400
+ # this run-start snapshot, never the live counter. _LOKI_RESUME_CONSUMED is
12401
+ # the once-only latch so the recovery resume fires on exactly the FIRST
12402
+ # main-loop call of a restarted run, then the run reverts to normal stateless
12403
+ # iterations (no resume chain -- transcript growth cannot accumulate).
12404
+ if [ "${ITERATION_COUNT:-0}" -gt 0 ]; then
12405
+ _LOKI_RESTARTED_RUN=1
12406
+ else
12407
+ _LOKI_RESTARTED_RUN=0
12408
+ fi
12409
+ _LOKI_RESUME_CONSUMED=0
12410
+ export _LOKI_RESTARTED_RUN _LOKI_RESUME_CONSUMED
12411
+
12393
12412
  # Trust-metrics instrumentation marker: record one run_start event per
12394
12413
  # fresh run so the trust-metrics denominator counts ONLY instrumented runs.
12395
12414
  # This is what lets the aggregator distinguish "0 blocks measured" from
@@ -12415,9 +12434,22 @@ except Exception:
12415
12434
  if [ -n "$_loki_session_uuid" ]; then
12416
12435
  local _loki_session_created
12417
12436
  _loki_session_created="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
12437
+ # mode reflects the active session-continuity layer for this run,
12438
+ # surfaced on the dashboard: "resume" when Phase 2 recovery resume
12439
+ # is enabled (GitHub #165), else "stamp" (Phase 1 correlation-only,
12440
+ # v7.34). DEFAULT (no knobs) stays "stamp" so existing behavior +
12441
+ # dashboard output are unchanged. The uuid is the SAME stable
12442
+ # per-run uuid either way -- it is the resume anchor a later
12443
+ # restart reads back. The "stamp" vs "resume" label only records
12444
+ # intent; the actual argv decision is gated again at call time.
12445
+ local _loki_session_mode="stamp"
12446
+ if type loki_resume_session_enabled >/dev/null 2>&1 \
12447
+ && loki_resume_session_enabled; then
12448
+ _loki_session_mode="resume"
12449
+ fi
12418
12450
  mkdir -p ".loki/state" 2>/dev/null || true
12419
- printf '{"run_id":"%s","claude_session_uuid":"%s","mode":"stamp","created_at":"%s"}\n' \
12420
- "$LOKI_TRUST_RUN_ID" "$_loki_session_uuid" "$_loki_session_created" \
12451
+ printf '{"run_id":"%s","claude_session_uuid":"%s","mode":"%s","created_at":"%s"}\n' \
12452
+ "$LOKI_TRUST_RUN_ID" "$_loki_session_uuid" "$_loki_session_mode" "$_loki_session_created" \
12421
12453
  > ".loki/state/claude-session.json" 2>/dev/null || true
12422
12454
  fi
12423
12455
  fi
@@ -12816,6 +12848,34 @@ except Exception as exc:
12816
12848
  && loki_claude_flag_supported "--include-partial-messages"; then
12817
12849
  _loki_claude_argv+=("--include-partial-messages")
12818
12850
  fi
12851
+ # Session-continuity Phase 2 (GitHub #165): on the FIRST main-loop call of
12852
+ # a RESTARTED run (snapshot _LOKI_RESTARTED_RUN==1, latch
12853
+ # _LOKI_RESUME_CONSUMED==0) with LOKI_RESUME_SESSION=1, emit
12854
+ # `--resume <stored-uuid>` INSTEAD of the per-iteration --session-id stamp
12855
+ # (the two are mutually exclusive on one invocation). This reattaches the
12856
+ # prior Claude context once, then the latch flips so every later iteration
12857
+ # reverts to normal stateless behavior. Optional --fork-session
12858
+ # (LOKI_SESSION_FORK=1) writes the resumed turn to a new id, leaving the
12859
+ # parent transcript untouched. DEFAULT OFF: with no knobs neither --resume
12860
+ # nor --session-id is emitted (argv byte-identical to v7.34).
12861
+ local _loki_did_resume=0
12862
+ if [ "${_LOKI_RESTARTED_RUN:-0}" = "1" ] && [ "${_LOKI_RESUME_CONSUMED:-0}" = "0" ] \
12863
+ && type loki_resume_session_enabled >/dev/null 2>&1 \
12864
+ && loki_resume_session_enabled; then
12865
+ local _loki_resume_uuid
12866
+ _loki_resume_uuid="$(_loki_resume_target_uuid)"
12867
+ if [ -n "$_loki_resume_uuid" ]; then
12868
+ _loki_claude_argv+=("--resume" "$_loki_resume_uuid")
12869
+ if type loki_session_fork_enabled >/dev/null 2>&1 \
12870
+ && loki_session_fork_enabled; then
12871
+ _loki_claude_argv+=("--fork-session")
12872
+ fi
12873
+ _loki_did_resume=1
12874
+ _LOKI_RESUME_CONSUMED=1
12875
+ export _LOKI_RESUME_CONSUMED
12876
+ log_info "LOKI_RESUME_SESSION=1: resuming Claude session $_loki_resume_uuid (recovery resume, first call of restarted run)"
12877
+ fi
12878
+ fi
12819
12879
  # v7.34.0 Phase 1 (correlation-only): per-iteration --session-id. OPT-IN
12820
12880
  # via LOKI_SESSION_STAMP=1 (CONSERVATIVE DEFAULT is OFF so the default
12821
12881
  # argv stays byte-identical to v7.33 -- the UX-monotonicity requirement).
@@ -12824,7 +12884,10 @@ except Exception as exc:
12824
12884
  # and accumulate transcript (Phase 2 continuity, out of scope). This keeps
12825
12885
  # each iteration a fresh stateless session while making its ~/.claude
12826
12886
  # JSONL name predictable for dashboard correlation. Gated on CLI support.
12827
- if type loki_session_stamp_enabled >/dev/null 2>&1 \
12887
+ # MUTUAL EXCLUSION: skip the stamp on the call that emitted --resume above
12888
+ # (claude rejects --session-id + --resume together).
12889
+ if [ "$_loki_did_resume" = "0" ] \
12890
+ && type loki_session_stamp_enabled >/dev/null 2>&1 \
12828
12891
  && loki_session_stamp_enabled; then
12829
12892
  local _loki_iter_session_uuid
12830
12893
  _loki_iter_session_uuid="$(_loki_claude_iteration_session_uuid "${LOKI_TRUST_RUN_ID:-}" "$ITERATION_COUNT")"
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.36.0"
10
+ __version__ = "7.37.1"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -392,6 +392,11 @@ class StatusResponse(BaseModel):
392
392
  # user can correlate the run with its Claude session JSONL (in ~/.claude/projects). Empty when
393
393
  # the run predates this field or no claude session was stamped.
394
394
  claude_session_id: str = ""
395
+ # Session-continuity Phase 2 (#165): the active session-continuity layer for
396
+ # the current run, read from claude-session.json. "stamp" = Phase 1
397
+ # correlation-only (v7.34); "resume" = LOKI_RESUME_SESSION recovery resume.
398
+ # Empty when the run predates this field or no claude session was stamped.
399
+ claude_session_mode: str = ""
395
400
  # Concurrent sessions (v6.4.0)
396
401
  sessions: list[SessionInfo] = []
397
402
 
@@ -963,6 +968,7 @@ async def get_status() -> StatusResponse:
963
968
  # run-start by run.sh (correlation-only). Best-effort read; empty when the
964
969
  # file is absent (run predates the field, or a non-claude provider).
965
970
  claude_session_id = ""
971
+ claude_session_mode = ""
966
972
  claude_session_file = loki_dir / "state" / "claude-session.json"
967
973
  if claude_session_file.exists():
968
974
  try:
@@ -976,6 +982,11 @@ async def get_status() -> StatusResponse:
976
982
  if isinstance(_cs, dict):
977
983
  _v = _cs.get("claude_session_uuid", "")
978
984
  claude_session_id = _v if isinstance(_v, str) else ""
985
+ # Phase 2 (#165): "stamp" (Phase 1 correlation-only) or "resume"
986
+ # (LOKI_RESUME_SESSION recovery resume). Empty when the run
987
+ # predates the field. Same non-string guard as the uuid.
988
+ _m = _cs.get("mode", "")
989
+ claude_session_mode = _m if isinstance(_m, str) else ""
979
990
  except (json.JSONDecodeError, OSError, KeyError, AttributeError):
980
991
  pass
981
992
 
@@ -1232,6 +1243,7 @@ async def get_status() -> StatusResponse:
1232
1243
  provider=provider,
1233
1244
  current_task=current_task,
1234
1245
  claude_session_id=claude_session_id,
1246
+ claude_session_mode=claude_session_mode,
1235
1247
  sessions=active_session_list,
1236
1248
  )
1237
1249
 
@@ -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.36.0
5
+ **Version:** v7.37.1
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.36.0";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=QQ(ZQ(import.meta.url)),Z=d$(Q);$$=e6($Q(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var n$=L(()=>{C()});var C1={};h(C1,{runOrThrow:()=>zQ,run:()=>j,commandVersion:()=>KQ,commandExists:()=>f,ShellError:()=>a$});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,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[W,K,U]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:W,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function zQ($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=XQ($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function XQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function KQ($,Q="--version"){if(!await f($))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 a$;var d=L(()=>{a$=class a$ 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 WQ?"":$}var WQ,T,S,I,TZ,w,R,y,q;var c=L(()=>{WQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),I=a("\x1B[1;33m"),TZ=a("\x1B[0;34m"),w=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),q=a("\x1B[0m")});import{existsSync as TQ}from"fs";async function Q$(){if(B$!==void 0)return B$;let $="/opt/homebrew/bin/python3.12";if(TQ($))return B$=$,$;let Q=await f("python3.12");if(Q)return B$=Q,Q;let Z=await f("python3");return B$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B$;var W$=L(()=>{d()});var t1={};h(t1,{runStatus:()=>gQ});import{existsSync as v,readFileSync as U$,readdirSync as l1,statSync as d1}from"fs";import{resolve as D,basename as xQ}from"path";import{homedir as NQ}from"os";async function DQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${q}
2
+ var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.37.1";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=QQ(ZQ(import.meta.url)),Z=d$(Q);$$=e6($Q(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var n$=L(()=>{C()});var C1={};h(C1,{runOrThrow:()=>zQ,run:()=>j,commandVersion:()=>KQ,commandExists:()=>f,ShellError:()=>a$});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,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[W,K,U]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:W,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function zQ($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=XQ($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function XQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function KQ($,Q="--version"){if(!await f($))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 a$;var d=L(()=>{a$=class a$ 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 WQ?"":$}var WQ,T,S,I,TZ,w,R,y,q;var c=L(()=>{WQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),I=a("\x1B[1;33m"),TZ=a("\x1B[0;34m"),w=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),q=a("\x1B[0m")});import{existsSync as TQ}from"fs";async function Q$(){if(B$!==void 0)return B$;let $="/opt/homebrew/bin/python3.12";if(TQ($))return B$=$,$;let Q=await f("python3.12");if(Q)return B$=Q,Q;let Z=await f("python3");return B$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B$;var W$=L(()=>{d()});var t1={};h(t1,{runStatus:()=>gQ});import{existsSync as v,readFileSync as U$,readdirSync as l1,statSync as d1}from"fs";import{resolve as D,basename as xQ}from"path";import{homedir as NQ}from"os";async function DQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${q}
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)
@@ -789,4 +789,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
789
789
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
790
790
  `),process.stderr.write(o6),2}}p1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var ZZ=await QZ(Bun.argv.slice(2));process.exit(ZZ);
791
791
 
792
- //# debugId=948D8F6B9A2EE4AF64756E2164756E21
792
+ //# debugId=4D62C09E1CA3E64A64756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.36.0'
60
+ __version__ = '7.37.1'
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.36.0",
4
+ "version": "7.37.1",
5
5
  "description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 11 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
6
6
  "keywords": [
7
7
  "agent",