loki-mode 7.33.0 → 7.34.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/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/loki-mode?style=for-the-badge&logo=npm&logoColor=white&color=553DE9)](https://www.npmjs.com/package/loki-mode)
10
10
  [![npm downloads](https://img.shields.io/npm/dt/loki-mode?style=for-the-badge&logo=npm&logoColor=white&color=1FC5A8&label=downloads)](https://www.npmjs.com/package/loki-mode)
11
- [![GitHub stars](https://img.shields.io/github/stars/asklokesh/loki-mode?style=for-the-badge&logo=github&color=553DE9)](https://github.com/asklokesh/loki-mode)
11
+ [![GitHub stars](https://img.shields.io/github/stars/asklokesh/loki-mode?style=for-the-badge&logo=github&color=553DE9&cacheSeconds=86400)](https://github.com/asklokesh/loki-mode/stargazers)
12
12
  [![Docker Pulls](https://img.shields.io/docker/pulls/asklokesh/loki-mode?style=for-the-badge&logo=docker&logoColor=white&color=2F71E3)](https://hub.docker.com/r/asklokesh/loki-mode)
13
13
  [![License](https://img.shields.io/badge/License-BUSL--1.1-36342E?style=for-the-badge)](LICENSE)
14
14
 
@@ -36,7 +36,7 @@
36
36
  - **Intelligent `loki start`** -- For interactive foreground runs the dashboard auto-opens in the browser (cross-platform; skipped in CI, SSH-without-TTY, and piped runs; opt out with `LOKI_NO_AUTO_OPEN=1`). The completion summary shows "Your app is live at <url>" so you know exactly where to try what Loki just built. The autonomous loop passes Claude Code's `--effort`, `--max-budget-usd`, and `--fallback-model` on every iteration (each gated on CLI support and individual opt-out env vars) for better long-run unattended execution (v7.25.0).
37
37
  - **Cross-project memory** -- Episodic/semantic/procedural memory with vector search; knowledge learned on one project surfaces on the next (v5.15.0+, see `memory/engine.py`)
38
38
  - **Self-hosted and private** -- Your keys, your infrastructure, no data leaves your network
39
- - **Legacy system healing** -- `loki heal` archaeology/stabilize/isolate/modernize/validate phases (v6.67.0, see `skills/healing.md`)
39
+ - **Legacy system healing** -- `loki modernize heal` archaeology/stabilize/isolate/modernize/validate phases (v6.67.0, see `skills/healing.md`)
40
40
  - **MCP server** -- 34 tools (including ChromaDB code search) plus 3 resources and 2 prompts (`mcp/server.py`, with magic tools registered from `mcp/magic_tools.py` and the managed-memory tool from `mcp/managed_tools.py`). Of the 34, 33 are always available; `loki_memory_redact` is registered but only succeeds when `LOKI_MANAGED_AGENTS=true` and `LOKI_MANAGED_MEMORY=true`. Launch with `loki mcp` (bootstraps the Python MCP SDK on first run).
41
41
  - **Full-stack output** -- Source code, tests, Docker Compose stacks (multi-service with healthchecks), CI/CD pipelines, audit logs
42
42
  - **Provider-agnostic** -- runs on Claude, Codex, Cline, or Aider with automatic failover (`loki-ts/src/runner/providers.ts`); no vendor lock-in. Gemini CLI deprecated v7.5.18; Antigravity CLI coming soon.
@@ -366,17 +366,17 @@ Status legend: "E2E-verified" means we run real spec-to-code builds on it oursel
366
366
  |---------|-------------|
367
367
  | `loki start [PRD]` | Start with optional PRD file (also accepts an issue ref; replaces deprecated `loki run`). Auto-opens the dashboard in the browser for interactive runs and passes native `--effort`/`--max-budget-usd`/`--fallback-model` for resilience (v7.25.0) |
368
368
  | `loki stop` | Stop execution |
369
- | `loki heal <path>` | Legacy system healing (archaeology, stabilize, isolate, modernize, validate -- v6.67.0) |
369
+ | `loki modernize heal <path>` | Legacy system healing (archaeology, stabilize, isolate, modernize, validate -- v6.67.0; was: `loki heal`) |
370
370
  | `loki pause` / `resume` | Pause/resume after current session |
371
371
  | `loki status` | Show current status |
372
372
  | `loki dashboard` | Open web dashboard |
373
- | `loki preview` / `loki open` | Print running app URL and open in browser (Live App Preview, v7.24.0) |
373
+ | `loki preview` | Print running app URL and open in browser (Live App Preview, v7.24.0; was: `loki open`) |
374
374
  | `loki web` | Launch Purple Lab web UI |
375
375
  | `loki doctor` | Check environment and dependencies |
376
376
  | `loki plan [PRD]` | Pre-execution analysis: complexity, cost, iterations |
377
377
  | `loki review [--staged\|--diff]` | AI-powered code review with severity filtering |
378
378
  | `loki test [--file\|--dir\|--changed]` | AI test generation (8 languages, 9 frameworks) |
379
- | `loki onboard [path]` | Project analysis and CLAUDE.md generation |
379
+ | `loki analyze onboard [path]` | Project analysis and CLAUDE.md generation (was: `loki onboard`) |
380
380
  | `loki import` | Import GitHub issues as tasks |
381
381
  | `loki ci` | CI/CD quality gate integration |
382
382
  | `loki failover` | Cross-provider auto-failover management |
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.33.0
6
+ # Loki Mode v7.34.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.33.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
401
+ **v7.34.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.33.0
1
+ 7.34.1
@@ -304,7 +304,13 @@ def update_tracking(loki_dir, iteration, window_size, provider="claude",
304
304
  if result["new_offset"] == last_offset:
305
305
  return # Nothing new
306
306
 
307
- # Update session ID and offset
307
+ # Update session ID and offset.
308
+ # v7.34.0 Phase 1: when LOKI_SESSION_STAMP=1, run.sh passes a
309
+ # per-iteration --session-id, so claude names the JSONL after that uuid
310
+ # and jsonl_path.stem is that uuid rather than a claude-minted one. This
311
+ # is just a string label for the tracking record; any value (uuid or
312
+ # otherwise) is fine here, so no reconcile/parse is needed and there is
313
+ # no crash path from the id differing.
308
314
  tracking["session_id"] = jsonl_path.stem
309
315
  tracking["updated_at"] = datetime.now(timezone.utc).isoformat()
310
316
  offset_file.write_text(str(result["new_offset"]))
@@ -226,3 +226,64 @@ loki_review_guard_denylist() {
226
226
  # global-flag-before-subcommand evasion of the bare git-mutation rules.
227
227
  printf '%s' "Edit,Write,NotebookEdit,Bash(git commit:*),Bash(git reset:*),Bash(git push:*),Bash(git checkout:*),Bash(git clean:*),Bash(git rm:*),Bash(git stash:*),Bash(git -C:*),Bash(git --git-dir:*),Bash(git -c:*)"
228
228
  }
229
+
230
+ # ---------- v7.34.0 Claude session-id stamping (Phase 1, correlation-only) -----
231
+ # Derive a deterministic per-run UUID from the existing trust-run-id so the same
232
+ # run always maps to the same claude session UUID, and the bash + Bun routes
233
+ # produce BYTE-IDENTICAL uuids for the same run id. We use RFC-4122 UUIDv5
234
+ # (SHA-1 over a stable namespace + the name). The namespace below is a fixed,
235
+ # Loki-specific constant; never change it (changing it re-keys every run's uuid).
236
+ # The TS mirror is loki-ts/src/providers/claude_flags.ts claudeSessionUuid().
237
+ #
238
+ # Phase 1 is correlation-only: the uuid is written to a metadata file and (only
239
+ # when LOKI_SESSION_STAMP=1) emitted as a PER-ITERATION --session-id on the main
240
+ # loop. It NEVER pins one id across the run (that is Phase 2 continuity, which
241
+ # would accumulate transcript and compete with Loki's own injected memory).
242
+ LOKI_CLAUDE_SESSION_NS="b6f3c7a2-9d41-5e8b-9c2a-3f7d6e1a4b50"
243
+
244
+ # UUIDv5 over the Loki session namespace + an arbitrary name string. Pure
245
+ # (stdout only), deterministic, no side effects. Uses python3 (always present on
246
+ # the bash route; every other helper here already depends on it). Emits empty on
247
+ # any failure so the caller degrades to metadata-only without breaking the run.
248
+ _loki_uuid5() {
249
+ local name="${1:-}"
250
+ [ -z "$name" ] && return 0
251
+ command -v python3 >/dev/null 2>&1 || return 0
252
+ LOKI_CLAUDE_SESSION_NS="$LOKI_CLAUDE_SESSION_NS" _LOKI_UUID5_NAME="$name" \
253
+ python3 - <<'UUID5_PY' 2>/dev/null || true
254
+ import os, uuid
255
+ ns = uuid.UUID(os.environ["LOKI_CLAUDE_SESSION_NS"])
256
+ print(uuid.uuid5(ns, os.environ["_LOKI_UUID5_NAME"]))
257
+ UUID5_PY
258
+ }
259
+
260
+ # The stable per-run claude session UUID: UUIDv5 of the trust-run-id. Same run
261
+ # id -> same uuid, on every route. Emits empty when no run id is resolvable.
262
+ _loki_claude_session_uuid() {
263
+ local run_id="${1:-${LOKI_TRUST_RUN_ID:-}}"
264
+ [ -z "$run_id" ] && return 0
265
+ _loki_uuid5 "$run_id"
266
+ }
267
+
268
+ # The PER-ITERATION session UUID emitted on the main loop when LOKI_SESSION_STAMP=1.
269
+ # UUIDv5 of "<run-id>:<iteration>", so every iteration gets a DISTINCT, deterministic
270
+ # id. This is deliberately NOT the stable per-run uuid: a single pinned id reused
271
+ # across iterations would make claude RESUME (accumulate transcript), which is
272
+ # Phase 2 continuity, explicitly out of scope here. Distinct-per-iteration keeps
273
+ # each iteration a fresh stateless session (byte-identical default behavior to
274
+ # v7.33 except for the added correlation flag).
275
+ _loki_claude_iteration_session_uuid() {
276
+ local run_id="${1:-${LOKI_TRUST_RUN_ID:-}}"
277
+ local iteration="${2:-${ITERATION_COUNT:-0}}"
278
+ [ -z "$run_id" ] && return 0
279
+ _loki_uuid5 "${run_id}:${iteration}"
280
+ }
281
+
282
+ # Predicate: emit the per-iteration --session-id ARGV flag? CONSERVATIVE DEFAULT
283
+ # is OFF (metadata-file-only) so the default claude argv stays byte-identical to
284
+ # v7.33 (the UX-monotonicity requirement). Opt IN with LOKI_SESSION_STAMP=1.
285
+ # Gated on CLI support so an older claude degrades gracefully (no flag emitted).
286
+ loki_session_stamp_enabled() {
287
+ [ "${LOKI_SESSION_STAMP:-0}" = "1" ] || return 1
288
+ loki_claude_flag_supported "--session-id"
289
+ }
package/autonomy/loki CHANGED
@@ -685,7 +685,7 @@ show_help() {
685
685
  echo " modernize [cmd] Legacy modernization: heal|migrate"
686
686
  echo ""
687
687
  echo "Config:"
688
- echo " config [cmd] Manage configuration + provider (show|set|get|provider)"
688
+ echo " config [cmd] Manage configuration (show|init|edit|path|set|get)"
689
689
  echo " doctor [--json] Check system prerequisites and skill symlinks"
690
690
  echo ""
691
691
  echo " version Show version"
@@ -2888,8 +2888,8 @@ cmd_status() {
2888
2888
  fi
2889
2889
 
2890
2890
  echo ""
2891
- echo -e "${DIM} Tip: loki context show - detailed token breakdown${NC}"
2892
- echo -e "${DIM} Tip: loki code overview - codebase intelligence${NC}"
2891
+ echo -e "${DIM} Tip: loki analyze context show - detailed token breakdown${NC}"
2892
+ echo -e "${DIM} Tip: loki analyze code overview - codebase intelligence${NC}"
2893
2893
  }
2894
2894
 
2895
2895
  # JSON output for loki status --json
@@ -8697,8 +8697,12 @@ cmd_api() {
8697
8697
 
8698
8698
  # Parse --host/--port (the old code ignored them, so `loki serve --port X`
8699
8699
  # silently bound 57374 and `--host 0.0.0.0` was dropped).
8700
+ # A trailing --help/-h on any subcommand (e.g. `loki api start --help`)
8701
+ # short-circuits to the help text instead of being swallowed and starting
8702
+ # the server (#574).
8700
8703
  while [ $# -gt 0 ]; do
8701
8704
  case "$1" in
8705
+ --help|-h) subcommand="help"; break ;;
8702
8706
  --port) port="${2:-$port}"; shift 2 ;;
8703
8707
  --port=*) port="${1#*=}"; shift ;;
8704
8708
  --host) host="${2:-$host}"; shift 2 ;;
package/autonomy/run.sh CHANGED
@@ -12324,6 +12324,26 @@ except Exception:
12324
12324
  LOKI_TRUST_RUN_ID="$(_loki_trust_run_id --new)"
12325
12325
  export LOKI_TRUST_RUN_ID
12326
12326
  record_trust_event_bash "run_start" "start_sha=${_LOKI_RUN_START_SHA:-}" 2>/dev/null || true
12327
+
12328
+ # v7.34.0 Phase 1 (correlation-only): write a deterministic claude
12329
+ # session UUID derived from the trust-run-id to .loki/state/claude-session.json.
12330
+ # mode is "stamp" (Phase 1); Phase 2 continuity is a separate, opt-in arc.
12331
+ # Best-effort: the helper is in scope via providers/claude.sh sourcing
12332
+ # claude-flags.sh; if absent (e.g. non-claude provider, no python3) we
12333
+ # skip silently and never fail the run. The dashboard reads this file to
12334
+ # surface it for correlating the run with its Claude session JSONL.
12335
+ if type _loki_claude_session_uuid >/dev/null 2>&1; then
12336
+ local _loki_session_uuid
12337
+ _loki_session_uuid="$(_loki_claude_session_uuid "$LOKI_TRUST_RUN_ID")"
12338
+ if [ -n "$_loki_session_uuid" ]; then
12339
+ local _loki_session_created
12340
+ _loki_session_created="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
12341
+ mkdir -p ".loki/state" 2>/dev/null || true
12342
+ printf '{"run_id":"%s","claude_session_uuid":"%s","mode":"stamp","created_at":"%s"}\n' \
12343
+ "$LOKI_TRUST_RUN_ID" "$_loki_session_uuid" "$_loki_session_created" \
12344
+ > ".loki/state/claude-session.json" 2>/dev/null || true
12345
+ fi
12346
+ fi
12327
12347
  fi
12328
12348
 
12329
12349
  # Notify dashboard of active project directory (for AI Chat cross-directory usage)
@@ -12719,6 +12739,20 @@ except Exception as exc:
12719
12739
  && loki_claude_flag_supported "--include-partial-messages"; then
12720
12740
  _loki_claude_argv+=("--include-partial-messages")
12721
12741
  fi
12742
+ # v7.34.0 Phase 1 (correlation-only): per-iteration --session-id. OPT-IN
12743
+ # via LOKI_SESSION_STAMP=1 (CONSERVATIVE DEFAULT is OFF so the default
12744
+ # argv stays byte-identical to v7.33 -- the UX-monotonicity requirement).
12745
+ # The id is a DISTINCT, deterministic UUIDv5 of "<run-id>:<iteration>",
12746
+ # never one pinned id across the run: a reused id would make claude RESUME
12747
+ # and accumulate transcript (Phase 2 continuity, out of scope). This keeps
12748
+ # each iteration a fresh stateless session while making its ~/.claude
12749
+ # JSONL name predictable for dashboard correlation. Gated on CLI support.
12750
+ if type loki_session_stamp_enabled >/dev/null 2>&1 \
12751
+ && loki_session_stamp_enabled; then
12752
+ local _loki_iter_session_uuid
12753
+ _loki_iter_session_uuid="$(_loki_claude_iteration_session_uuid "${LOKI_TRUST_RUN_ID:-}" "$ITERATION_COUNT")"
12754
+ [ -n "$_loki_iter_session_uuid" ] && _loki_claude_argv+=("--session-id" "$_loki_iter_session_uuid")
12755
+ fi
12722
12756
  # ---- Bash<->Bun invocation-flag convergence ledger (v7.25.0) ----------
12723
12757
  # The fixture corpus covers build_prompt/stats output, NOT this claude
12724
12758
  # argv, so drift here is invisible to parity tests. Keep this ledger
@@ -12729,8 +12763,13 @@ except Exception as exc:
12729
12763
  # today.
12730
12764
  # Bash argv (canonical, live): --dangerously-skip-permissions --model M
12731
12765
  # [--append-system-prompt] [--setting-sources] [--include-partial-messages]
12766
+ # [--session-id UUID (only when LOKI_SESSION_STAMP=1, v7.34.0)]
12732
12767
  # [--effort] [--max-budget-usd] [--fallback-model] -p PROMPT
12733
12768
  # --output-format stream-json --verbose
12769
+ # v7.34.0: --session-id is emitted ONLY on this MAIN loop, only under
12770
+ # LOKI_SESSION_STAMP=1, as a per-iteration distinct UUIDv5; the DEFAULT
12771
+ # argv (knob unset) is byte-identical to v7.33. Bun mirror lives in
12772
+ # loki-ts/src/runner/providers.ts (sessionStampArgv).
12734
12773
  # Bun buildAutoFlags also emits: --exclude-dynamic-system-prompt-sections
12735
12774
  # (cost-only), --mcp-config (bash gets MCP via --setting-sources +
12736
12775
  # .mcp.json discovery; a how-difference, likely behavior-equivalent),
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.33.0"
10
+ __version__ = "7.34.1"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -387,6 +387,11 @@ class StatusResponse(BaseModel):
387
387
  mode: str = ""
388
388
  provider: str = "claude"
389
389
  current_task: str = ""
390
+ # v7.34.0 Phase 1: the deterministic per-run Claude session UUID derived from
391
+ # the trust-run-id (read from .loki/state/claude-session.json). Surfaced so a
392
+ # user can correlate the run with its Claude session JSONL (in ~/.claude/projects). Empty when
393
+ # the run predates this field or no claude session was stamped.
394
+ claude_session_id: str = ""
390
395
  # Concurrent sessions (v6.4.0)
391
396
  sessions: list[SessionInfo] = []
392
397
 
@@ -954,6 +959,26 @@ async def get_status() -> StatusResponse:
954
959
  pending_tasks = 0
955
960
  running_agents = 0
956
961
 
962
+ # v7.34.0 Phase 1: the deterministic per-run Claude session UUID, written at
963
+ # run-start by run.sh (correlation-only). Best-effort read; empty when the
964
+ # file is absent (run predates the field, or a non-claude provider).
965
+ claude_session_id = ""
966
+ claude_session_file = loki_dir / "state" / "claude-session.json"
967
+ if claude_session_file.exists():
968
+ try:
969
+ _cs = _safe_json_read(claude_session_file, {})
970
+ # Guard against a syntactically-valid non-object JSON (array, string,
971
+ # number) that would make .get() raise AttributeError, AND a
972
+ # non-string VALUE that would fail StatusResponse's str validation
973
+ # (both -> 500). The normal writer (run.sh) always emits an object
974
+ # with a string uuid, so this only triggers on external file
975
+ # corruption, but /api/status must never 500 on it.
976
+ if isinstance(_cs, dict):
977
+ _v = _cs.get("claude_session_uuid", "")
978
+ claude_session_id = _v if isinstance(_v, str) else ""
979
+ except (json.JSONDecodeError, OSError, KeyError, AttributeError):
980
+ pass
981
+
957
982
  # Read dashboard state (with retry for concurrent writes)
958
983
  _has_dashboard_state = False
959
984
  if state_file.exists():
@@ -1206,6 +1231,7 @@ async def get_status() -> StatusResponse:
1206
1231
  mode=mode,
1207
1232
  provider=provider,
1208
1233
  current_task=current_task,
1234
+ claude_session_id=claude_session_id,
1209
1235
  sessions=active_session_list,
1210
1236
  )
1211
1237
 
@@ -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.33.0
5
+ **Version:** v7.34.1
6
6
 
7
7
  ---
8
8
 
@@ -534,7 +534,7 @@ Loki Mode uses two network ports for different services:
534
534
  LOKI_DASHBOARD_PORT=57374 loki dashboard start
535
535
 
536
536
  # API port (default: 57374)
537
- loki serve --port 57374
537
+ loki api start --port 57374 # was: loki serve
538
538
  ```
539
539
 
540
540
  ### CORS Configuration
@@ -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.33.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.34.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)
@@ -42,8 +42,8 @@ var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null
42
42
  `)}let J=D($,"state","context-usage.json");if(v(J)){let B=a1(J,"window_size",200000),O=a1(J,"used_tokens",0),M=0;if(B>0)M=Math.floor(O*100/B);process.stdout.write(`${w}Context:${q} ${M}% (${O} / ${B} tokens)
43
43
  `)}let G=[D($,"dashboard","dashboard.pid"),D(NQ(),".loki","dashboard","dashboard.pid")].find((B)=>v(B))??"";if(G&&v(G)){let B=R$(G);if(B!==null&&k$(B)){let O=D(G,".."),M=(_,k)=>{let u=D(O,_);try{return v(u)?U$(u,"utf-8").trim()||k:k}catch{return k}},x=M("scheme","http"),N=M("host","127.0.0.1"),b=M("port",process.env.LOKI_DASHBOARD_PORT||"57374");if(N==="0.0.0.0")N="127.0.0.1";process.stdout.write(`${w}Dashboard:${q} ${x}://${N}:${b}/
44
44
  `)}}return await hQ($),process.stdout.write(`
45
- `),process.stdout.write(`${y} Tip: loki context show - detailed token breakdown${q}
46
- `),process.stdout.write(`${y} Tip: loki code overview - codebase intelligence${q}
45
+ `),process.stdout.write(`${y} Tip: loki analyze context show - detailed token breakdown${q}
46
+ `),process.stdout.write(`${y} Tip: loki analyze code overview - codebase intelligence${q}
47
47
  `),0}async function hQ($){let Q=D($,"state"),Z=yQ(Q),z=D(Q,"relevant-learnings.json"),X=D($,"escalations"),W=Z.length>0,K=v(z),U=v(X);if(!W&&!K&&!U)return;if(process.stdout.write(`
48
48
  ${w}Phase 1 artifacts:${q}
49
49
  `),W){let V=Z[Z.length-1],H=s1(V);if(H&&Array.isArray(H.findings)){let J={Critical:0,High:0,Medium:0,Low:0};for(let G of H.findings){let B=String(G.severity??"");if(B in J)J[B]=(J[B]??0)+1}let Y=Object.entries(J).filter(([,G])=>G>0).map(([G,B])=>`${B} ${G.toLowerCase()}`).join(", ");process.stdout.write(` Findings (iter ${H.iteration??"?"}): ${Y||"none"} -- ${H.findings.length} total
@@ -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=27D659E047A7DB5564756E2164756E21
792
+ //# debugId=F6F45F7E10405C2564756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.33.0'
60
+ __version__ = '7.34.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.33.0",
4
+ "version": "7.34.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",
@@ -247,6 +247,17 @@ _loki_build_claude_auto_flags() {
247
247
  && loki_claude_flag_supported "--include-partial-messages"; then
248
248
  _LOKI_CLAUDE_AUTO_FLAGS+=("--include-partial-messages")
249
249
  fi
250
+
251
+ # --no-session-persistence (v7.34.0): boolean flag that disables Claude's
252
+ # own session persistence (it would otherwise write transcript JSONL under
253
+ # ~/.claude/projects). OPT-IN only via LOKI_NO_SESSION_PERSIST=1; DEFAULT OFF
254
+ # so this is zero behavior change (the flag is never emitted unless the user
255
+ # asks for it). Useful for ephemeral/CI runs that do not want on-disk
256
+ # transcripts. Gated on CLI support so an older claude degrades gracefully.
257
+ if [ "${LOKI_NO_SESSION_PERSIST:-0}" = "1" ] \
258
+ && loki_claude_flag_supported "--no-session-persistence"; then
259
+ _LOKI_CLAUDE_AUTO_FLAGS+=("--no-session-persistence")
260
+ fi
250
261
  }
251
262
 
252
263
  # The system-prompt text that authorizes autonomous operation and resolves