loki-mode 7.32.3 → 7.34.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
@@ -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.32.3
6
+ # Loki Mode v7.34.0
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.32.3 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
401
+ **v7.34.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.32.3
1
+ 7.34.0
@@ -1759,7 +1759,23 @@ ISSUES: CRITICAL:description (optional, one per line per issue)"
1759
1759
  claude)
1760
1760
  if command -v claude &>/dev/null; then
1761
1761
  local council_model="${PROVIDER_MODEL_FAST:-haiku}"
1762
- verdict=$(echo "$prompt" | claude --model "$council_model" -p 2>/dev/null | tail -20)
1762
+ # EMBED 2 + 3 (v7.33.0). Council member completion vote. The
1763
+ # $prompt is fully self-contained (evidence + instructions +
1764
+ # strict VOTE/REASON/ISSUES output format, piped via stdin) and
1765
+ # the verdict is captured. So --bare (cheap, no hooks/LSP/CLAUDE.
1766
+ # md/MCP) and --disallowedTools (a voting reviewer must never
1767
+ # mutate the tree) both apply. Gated + opt-out
1768
+ # LOKI_BARE_SUBCALLS=0 / LOKI_REVIEW_TOOL_GUARD=0. Helpers may be
1769
+ # out of scope when this file is sourced standalone, so each is
1770
+ # type-guarded (degrades to the prior bare invocation).
1771
+ local _cm_argv=("--model" "$council_model")
1772
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
1773
+ _cm_argv+=("--bare")
1774
+ fi
1775
+ if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
1776
+ _cm_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
1777
+ fi
1778
+ verdict=$(echo "$prompt" | claude "${_cm_argv[@]}" -p 2>/dev/null | tail -20)
1763
1779
  fi
1764
1780
  ;;
1765
1781
  codex)
@@ -1842,7 +1858,19 @@ REASON: your reasoning"
1842
1858
  claude)
1843
1859
  if command -v claude &>/dev/null; then
1844
1860
  local council_model="${PROVIDER_MODEL_FAST:-haiku}"
1845
- verdict=$(echo "$prompt" | claude --model "$council_model" -p 2>/dev/null | tail -20)
1861
+ # EMBED 2 + 3 (v7.33.0). Contrarian (devil's-advocate) vote --
1862
+ # an adversarial reviewer. Self-contained $prompt via stdin,
1863
+ # verdict captured. --bare + --disallowedTools both apply (a
1864
+ # reviewer must never mutate the tree). Gated + opt-out
1865
+ # LOKI_BARE_SUBCALLS=0 / LOKI_REVIEW_TOOL_GUARD=0; type-guarded.
1866
+ local _co_argv=("--model" "$council_model")
1867
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
1868
+ _co_argv+=("--bare")
1869
+ fi
1870
+ if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
1871
+ _co_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
1872
+ fi
1873
+ verdict=$(echo "$prompt" | claude "${_co_argv[@]}" -p 2>/dev/null | tail -20)
1846
1874
  fi
1847
1875
  ;;
1848
1876
  codex)
@@ -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"]))
@@ -263,7 +263,20 @@ Respond ONLY with a valid JSON object. No markdown fencing."
263
263
  case "${PROVIDER_NAME:-claude}" in
264
264
  claude)
265
265
  if command -v claude &>/dev/null; then
266
- result=$(echo "$full_prompt" | claude --model haiku -p 2>/dev/null || echo '{"verdict":"REJECT","reasoning":"review execution failed","issues":[]}')
266
+ # EMBED 2 + 3 (v7.33.0). Council-v2 reviewer verdict. $full_prompt
267
+ # is self-contained (evidence + PRD + strict JSON output, via
268
+ # stdin) and the JSON result is captured. --bare + --disallowedTools
269
+ # both apply (a reviewer must never mutate the tree). Gated +
270
+ # opt-out LOKI_BARE_SUBCALLS=0 / LOKI_REVIEW_TOOL_GUARD=0;
271
+ # type-guarded for standalone sourcing.
272
+ local _c2_argv=("--model" "haiku")
273
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
274
+ _c2_argv+=("--bare")
275
+ fi
276
+ if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
277
+ _c2_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
278
+ fi
279
+ result=$(echo "$full_prompt" | claude "${_c2_argv[@]}" -p 2>/dev/null || echo '{"verdict":"REJECT","reasoning":"review execution failed","issues":[]}')
267
280
  else
268
281
  result='{"verdict":"REJECT","reasoning":"reviewer CLI unavailable","issues":[]}'
269
282
  fi
package/autonomy/grill.sh CHANGED
@@ -38,6 +38,16 @@ GRILL_EXIT_ERROR=3
38
38
  GRILL_DIR_DEFAULT=".loki/grill"
39
39
  GRILL_REPORT_NAME="report.md"
40
40
 
41
+ # Source the claude-flags helper (idempotent, guarded) so the v7.33.0 embeds
42
+ # (--bare / --disallowedTools) are available in grill's standalone invocation
43
+ # path. Best-effort: if the file is missing the type-guards at the call site
44
+ # degrade to the prior bare invocation.
45
+ _grill_flags_helper="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/claude-flags.sh"
46
+ if [ -f "$_grill_flags_helper" ]; then
47
+ # shellcheck disable=SC1090
48
+ . "$_grill_flags_helper"
49
+ fi
50
+
41
51
  _grill_log() { printf '[grill] %s\n' "$*" >&2; }
42
52
  _grill_err() { printf '[grill][error] %s\n' "$*" >&2; }
43
53
 
@@ -178,8 +188,24 @@ grill_invoke_provider() {
178
188
  local out
179
189
  # Single-shot, non-interactive. Same pattern as the in-loop
180
190
  # adversarial reviewer (run.sh:7807) and USAGE regen (run.sh:9832).
191
+ # EMBED 2 + 3 (v7.33.0). The devil's-advocate grill is a cheap
192
+ # self-contained adversarial subcall (the entire spec + interrogation
193
+ # instructions are in $prompt, piped via -p -; output captured). So:
194
+ # EMBED 2 (--bare): no hooks/LSP/CLAUDE.md/MCP needed; cheaper.
195
+ # Opt out LOKI_BARE_SUBCALLS=0.
196
+ # EMBED 3 (--disallowedTools): a grill agent interrogates a spec and
197
+ # must never mutate the tree (defense-in-depth). Deny Edit/Write/
198
+ # NotebookEdit + git mutations. Opt out LOKI_REVIEW_TOOL_GUARD=0.
199
+ # Type-guarded so an environment without the helper degrades cleanly.
200
+ local _gr_argv=("--dangerously-skip-permissions" "--model" "${LOKI_GRILL_MODEL:-sonnet}")
201
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
202
+ _gr_argv+=("--bare")
203
+ fi
204
+ if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
205
+ _gr_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
206
+ fi
181
207
  out="$(printf '%s' "$prompt" \
182
- | _grill_with_timeout "${LOKI_GRILL_TIMEOUT:-180}" claude --dangerously-skip-permissions --model "${LOKI_GRILL_MODEL:-sonnet}" -p - 2>/dev/null)"
208
+ | _grill_with_timeout "${LOKI_GRILL_TIMEOUT:-180}" claude "${_gr_argv[@]}" -p - 2>/dev/null)"
183
209
  if [ -z "$out" ]; then
184
210
  _grill_err "provider returned no output (timeout or invocation error)"
185
211
  return $GRILL_EXIT_ERROR
@@ -130,3 +130,160 @@ loki_claude_flag_supported() {
130
130
  *) return 1 ;;
131
131
  esac
132
132
  }
133
+
134
+ # ---------- v7.33.0 cheap-subcall + reviewer-guard flag emitters ----------
135
+ # These centralize three Claude Code 2.1.170 embeds so every cheap NON-MAIN
136
+ # subcall site emits the same gated flags. Each emitter prints its flags to
137
+ # stdout (space-free per element via one-per-line is not needed; callers read
138
+ # them into an array with command substitution + word-splitting on the single
139
+ # token they emit, OR append the function output as additional argv elements).
140
+ # All are default-ON, opt-out via env, and gated on loki_claude_flag_supported
141
+ # so an older CLI degrades gracefully (emits nothing).
142
+
143
+ # EMBED 2 -- --bare (cheap NON-MAIN subcalls only). Minimal mode. Per
144
+ # `claude --help` it SKIPS hooks, LSP, plugin sync, attribution, auto-memory,
145
+ # background prefetches, keychain reads, and CLAUDE.md AUTO-discovery. It does
146
+ # NOT nullify explicit --mcp-config/--settings/--agents (the help lists those as
147
+ # the way to "explicitly provide context" UNDER --bare); what it drops is the
148
+ # IMPLICIT/auto-discovered context. Therefore --bare is ONLY safe on subcalls
149
+ # whose prompt is fully self-contained (the entire instruction set + context is
150
+ # passed via -p), and NEVER on the main RARV loop or on any call that relies on
151
+ # auto-discovered CLAUDE.md / hooks / auto-memory.
152
+ #
153
+ # AUTH GATE (critical): --bare sets CLAUDE_CODE_SIMPLE=1 and per `claude --help`
154
+ # reads Anthropic auth STRICTLY from ANTHROPIC_API_KEY or an apiKeyHelper via
155
+ # --settings -- "OAuth and keychain are never read". A subscription/OAuth user
156
+ # (no ANTHROPIC_API_KEY) gets "Not logged in" on every --bare subcall, which
157
+ # exits 0 with the error on stdout, so a council vote parses as the default
158
+ # REJECT and the loop silently corrupts. So --bare is enabled ONLY when an
159
+ # API-key auth path exists (ANTHROPIC_API_KEY set, or an apiKeyHelper configured
160
+ # in settings); otherwise it emits nothing and the subcall runs full-auth, the
161
+ # same as before this embed. Subscription users are thus unaffected by default.
162
+ # Default-ON (when auth-safe); opt out entirely with LOKI_BARE_SUBCALLS=0.
163
+ # Predicate so call sites can append "--bare" to their argv array uniformly:
164
+ # loki_subcall_bare_enabled && argv+=("--bare")
165
+ loki_subcall_bare_enabled() {
166
+ [ "${LOKI_BARE_SUBCALLS:-1}" = "0" ] && return 1
167
+ # API-key auth required (see AUTH GATE above). ANTHROPIC_API_KEY is the
168
+ # common case; apiKeyHelper covers the settings-configured helper. Anything
169
+ # else (OAuth/keychain subscription) must NOT use --bare. Trim the key so a
170
+ # whitespace-only value does not count as set (it would fail --bare auth).
171
+ local _key
172
+ _key="$(printf '%s' "${ANTHROPIC_API_KEY:-}" | tr -d '[:space:]')"
173
+ if [ -z "$_key" ] && ! _loki_apikey_helper_configured; then
174
+ return 1
175
+ fi
176
+ loki_claude_flag_supported "--bare"
177
+ }
178
+
179
+ # True when an apiKeyHelper is configured in any Claude settings source, which
180
+ # (unlike OAuth/keychain) IS honored under --bare. Best-effort, read-only.
181
+ _loki_apikey_helper_configured() {
182
+ local f
183
+ for f in "$HOME/.claude/settings.json" "$HOME/.config/claude/settings.json" \
184
+ "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json" \
185
+ "$PWD/.claude/settings.json" "$PWD/.claude/settings.local.json"; do
186
+ [ -f "$f" ] || continue
187
+ # Require "apiKeyHelper" : "<non-empty value>" (a real key:value pair),
188
+ # not the bare token in a comment/prose. Tolerates whitespace around the
189
+ # colon. Not a full JSON parse, but rejects the obvious false-positives.
190
+ grep -Eq '"apiKeyHelper"[[:space:]]*:[[:space:]]*"[^"]+"' "$f" 2>/dev/null && return 0
191
+ done
192
+ return 1
193
+ }
194
+
195
+ # EMBED 3 -- --disallowedTools (reviewer / adversarial subcalls). Motivation: a
196
+ # parallel agent once ran `git reset --hard` and wiped uncommitted work, so a
197
+ # reviewer/voter subcall should not casually mutate the tree. Per `claude --help`
198
+ # the value is a comma-or-space-separated list of tool names (e.g. "Bash(git *)
199
+ # Edit"); Bash(...) matching is command-PREFIX based (the `cmd:*` form).
200
+ #
201
+ # SCOPE / LIMITS (honest -- this is a denylist, NOT a sandbox):
202
+ # - Denies the direct file-mutation tools (Edit, Write, NotebookEdit).
203
+ # - Denies the common git MUTATION forms: the bare subcommand (`git reset:*`)
204
+ # AND the global-flag-prefixed evasions (`git -C:*`, `git --git-dir:*`,
205
+ # `git -c:*`) that slip a flag before the subcommand so the bare prefix
206
+ # does not match. Read-only git (diff/log/show/status) stays allowed.
207
+ # - It does NOT and cannot block every mutation path: a determined agent can
208
+ # still write via `Bash(echo > f)`, `sed -i`, `cp/mv/tee`, `python -c`, etc.
209
+ # This is a guardrail that raises the cost of the casual/common destructive
210
+ # command, not a guarantee the tree is immutable. The real safety net is
211
+ # that the integrator commits before any agent wave (see CLAUDE.md).
212
+ # The flag is variadic (<tools...>), so we emit ONE comma-separated token to
213
+ # avoid swallowing the following -p prompt as additional tool names.
214
+ # Default-ON; opt out with LOKI_REVIEW_TOOL_GUARD=0.
215
+ # Predicate + denylist so call sites append uniformly:
216
+ # if loki_review_guard_enabled; then
217
+ # argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
218
+ # fi
219
+ loki_review_guard_enabled() {
220
+ [ "${LOKI_REVIEW_TOOL_GUARD:-1}" = "0" ] && return 1
221
+ loki_claude_flag_supported "--disallowedTools"
222
+ }
223
+ loki_review_guard_denylist() {
224
+ # Comma-separated single token. Bash(cmd:*) is command-prefix matching.
225
+ # The `git -C:*` / `git --git-dir:*` / `git -c:*` entries close the
226
+ # global-flag-before-subcommand evasion of the bare git-mutation rules.
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
+ }
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
package/autonomy/run.sh CHANGED
@@ -3248,7 +3248,18 @@ Output ONLY the resolved file content with no conflict markers. No explanations.
3248
3248
 
3249
3249
  case "${PROVIDER_NAME:-claude}" in
3250
3250
  claude)
3251
- resolution=$(claude --dangerously-skip-permissions -p "$conflict_prompt" --output-format text 2>/dev/null)
3251
+ # EMBED 2 (v7.33.0): --bare on this cheap NON-MAIN subcall.
3252
+ # Reasoning: $conflict_prompt is fully self-contained -- it
3253
+ # carries the complete instruction set AND the entire conflicted
3254
+ # file content inline, and the agent's output is captured to a
3255
+ # variable (the shell, not the agent, writes the resolved file).
3256
+ # It needs no hooks, LSP, CLAUDE.md auto-discovery, or MCP, so
3257
+ # --bare is safe and cheaper. Gated + opt-out LOKI_BARE_SUBCALLS=0.
3258
+ local _cr_argv=("--dangerously-skip-permissions")
3259
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
3260
+ _cr_argv+=("--bare")
3261
+ fi
3262
+ resolution=$(claude "${_cr_argv[@]}" -p "$conflict_prompt" --output-format text 2>/dev/null)
3252
3263
  ;;
3253
3264
  codex)
3254
3265
  resolution=$(codex exec --full-auto --skip-git-repo-check "$conflict_prompt" 2>/dev/null)
@@ -7803,7 +7814,31 @@ BUILD_PROMPT
7803
7814
  # Mythos 5 (Project Glasswing), not Fable. If a future change
7804
7815
  # adds --model here, the security-sentinel reviewer must be
7805
7816
  # pinned to opus, never fable.
7806
- claude --dangerously-skip-permissions -p "$prompt_text" \
7817
+ # EMBED 2 + 3 (v7.33.0). This is a 3-reviewer council
7818
+ # subcall. $prompt_text is fully self-contained (built above
7819
+ # into $review_prompt_file with the diff, changed files,
7820
+ # checks, and strict VERDICT/FINDINGS output format), output
7821
+ # is captured to $review_output, and it deliberately does NOT
7822
+ # pass --model or go through buildAutoFlags. So:
7823
+ # EMBED 2 (--bare): the prompt needs no hooks/LSP/CLAUDE.md/
7824
+ # MCP discovery, so --bare is safe and cheaper. Opt out
7825
+ # LOKI_BARE_SUBCALLS=0.
7826
+ # EMBED 3 (--disallowedTools): raise the cost of a reviewer
7827
+ # casually mutating the tree (a parallel agent once ran
7828
+ # `git reset --hard` and wiped uncommitted work). Deny
7829
+ # Edit/Write/NotebookEdit + git mutation forms (incl. the
7830
+ # git -C / --git-dir evasions); read-only git stays allowed.
7831
+ # Guardrail, not a sandbox -- echo>/sed -i/etc. remain; the
7832
+ # real net is commit-before-agent-wave. Opt out
7833
+ # LOKI_REVIEW_TOOL_GUARD=0. See loki_review_guard_denylist.
7834
+ local _rv_argv=("--dangerously-skip-permissions")
7835
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
7836
+ _rv_argv+=("--bare")
7837
+ fi
7838
+ if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
7839
+ _rv_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
7840
+ fi
7841
+ claude "${_rv_argv[@]}" -p "$prompt_text" \
7807
7842
  --output-format text > "$review_output" 2>/dev/null
7808
7843
  ;;
7809
7844
  codex)
@@ -8016,7 +8051,25 @@ ADVERSARIAL_EOF
8016
8051
  case "${PROVIDER_NAME:-claude}" in
8017
8052
  claude)
8018
8053
  if command -v claude &>/dev/null; then
8019
- claude --dangerously-skip-permissions -p "$adversarial_prompt" \
8054
+ # EMBED 2 + 3 (v7.33.0). Adversarial probe subcall.
8055
+ # $adversarial_prompt is fully self-contained (instructions +
8056
+ # changed files + diff inlined via the heredoc above) and output
8057
+ # is captured to $result_file. So:
8058
+ # EMBED 2 (--bare): no hooks/LSP/CLAUDE.md/MCP needed; cheaper.
8059
+ # Opt out LOKI_BARE_SUBCALLS=0.
8060
+ # EMBED 3 (--disallowedTools): keep an adversarial agent from
8061
+ # casually mutating the tree. Deny Edit/Write/NotebookEdit +
8062
+ # git mutation forms (incl. git -C / --git-dir evasions);
8063
+ # read-only git stays allowed. Guardrail, not a sandbox.
8064
+ # Opt out LOKI_REVIEW_TOOL_GUARD=0.
8065
+ local _adv_argv=("--dangerously-skip-permissions")
8066
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
8067
+ _adv_argv+=("--bare")
8068
+ fi
8069
+ if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
8070
+ _adv_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
8071
+ fi
8072
+ claude "${_adv_argv[@]}" -p "$adversarial_prompt" \
8020
8073
  --output-format text > "$result_file" 2>/dev/null || true
8021
8074
  fi
8022
8075
  ;;
@@ -10041,9 +10094,21 @@ ${_commits}"
10041
10094
 
10042
10095
  # Use haiku for cheap, fast generation. --dangerously-skip-permissions
10043
10096
  # because this is a one-shot non-interactive call.
10097
+ # EMBED 2 (v7.33.0): --bare on this cheap NON-MAIN haiku subcall. The
10098
+ # USAGE.md-regen prompt ($_ic_prompt, piped via -p -) is fully self-contained
10099
+ # (project tree + manifests + entrypoint contents + commits inlined) and the
10100
+ # output is captured, not written by the agent. No hooks/LSP/CLAUDE.md/MCP
10101
+ # needed, so --bare is safe and cheaper. Opt out LOKI_BARE_SUBCALLS=0.
10102
+ # Always at least --dangerously-skip-permissions, so the array is never
10103
+ # empty (empty "${arr[@]}" under set -u errors on bash 3.2, stock macOS).
10104
+ local _ic_argv=("--dangerously-skip-permissions")
10105
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
10106
+ _ic_argv+=("--bare")
10107
+ fi
10108
+ _ic_argv+=("--model" "haiku")
10044
10109
  local _ic_out
10045
10110
  _ic_out=$(printf '%s' "$_ic_prompt" \
10046
- | timeout 60 claude --dangerously-skip-permissions --model haiku -p - 2>/dev/null \
10111
+ | timeout 60 claude "${_ic_argv[@]}" -p - 2>/dev/null \
10047
10112
  | head -200)
10048
10113
  # Sanity check: response must look like Markdown (starts with # or ##).
10049
10114
  if [ -z "$_ic_out" ] || ! printf '%s' "$_ic_out" | head -1 | grep -qE '^#'; then
@@ -12259,6 +12324,26 @@ except Exception:
12259
12324
  LOKI_TRUST_RUN_ID="$(_loki_trust_run_id --new)"
12260
12325
  export LOKI_TRUST_RUN_ID
12261
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
12262
12347
  fi
12263
12348
 
12264
12349
  # Notify dashboard of active project directory (for AI Chat cross-directory usage)
@@ -12654,6 +12739,20 @@ except Exception as exc:
12654
12739
  && loki_claude_flag_supported "--include-partial-messages"; then
12655
12740
  _loki_claude_argv+=("--include-partial-messages")
12656
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
12657
12756
  # ---- Bash<->Bun invocation-flag convergence ledger (v7.25.0) ----------
12658
12757
  # The fixture corpus covers build_prompt/stats output, NOT this claude
12659
12758
  # argv, so drift here is invisible to parity tests. Keep this ledger
@@ -12664,8 +12763,13 @@ except Exception as exc:
12664
12763
  # today.
12665
12764
  # Bash argv (canonical, live): --dangerously-skip-permissions --model M
12666
12765
  # [--append-system-prompt] [--setting-sources] [--include-partial-messages]
12766
+ # [--session-id UUID (only when LOKI_SESSION_STAMP=1, v7.34.0)]
12667
12767
  # [--effort] [--max-budget-usd] [--fallback-model] -p PROMPT
12668
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).
12669
12773
  # Bun buildAutoFlags also emits: --exclude-dynamic-system-prompt-sections
12670
12774
  # (cost-only), --mcp-config (bash gets MCP via --setting-sources +
12671
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.32.3"
10
+ __version__ = "7.34.0"
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.32.3
5
+ **Version:** v7.34.0
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.32.3";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.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}
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=E69BF9B144A89A3864756E2164756E21
792
+ //# debugId=C89D42E5B326555264756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.32.3'
60
+ __version__ = '7.34.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.32.3",
4
+ "version": "7.34.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 11 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
6
6
  "keywords": [
7
7
  "agent",
@@ -190,6 +190,20 @@ _loki_build_claude_auto_flags() {
190
190
  for _mcp_path in $_mcp_argv; do
191
191
  _LOKI_CLAUDE_AUTO_FLAGS+=("$_mcp_path")
192
192
  done
193
+ # EMBED 1 (v7.33.0): --strict-mcp-config. ONLY emitted alongside an
194
+ # actual --mcp-config bundle (never bare). Per `claude --help` it
195
+ # makes the agent load servers ONLY from --mcp-config, ignoring ALL
196
+ # other MCP sources (auto-discovered project .mcp.json AND any
197
+ # settings-injected configs). Note the bundle already includes the
198
+ # user's ~/.claude/mcp.json overlay explicitly, so the common
199
+ # user-MCP case is preserved; what is dropped is any MCP config not
200
+ # in the explicit bundle, making the run reproducible.
201
+ # Default-ON; opt out with LOKI_STRICT_MCP=0. Gated on CLI support so
202
+ # an older claude degrades gracefully.
203
+ if [ "${LOKI_STRICT_MCP:-1}" != "0" ] \
204
+ && loki_claude_flag_supported "--strict-mcp-config"; then
205
+ _LOKI_CLAUDE_AUTO_FLAGS+=("--strict-mcp-config")
206
+ fi
193
207
  fi
194
208
  fi
195
209
 
@@ -233,6 +247,17 @@ _loki_build_claude_auto_flags() {
233
247
  && loki_claude_flag_supported "--include-partial-messages"; then
234
248
  _LOKI_CLAUDE_AUTO_FLAGS+=("--include-partial-messages")
235
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
236
261
  }
237
262
 
238
263
  # The system-prompt text that authorizes autonomous operation and resolves