loki-mode 7.35.0 → 7.37.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/LICENSE CHANGED
@@ -1,5 +1,3 @@
1
- SPDX-License-Identifier: BUSL-1.1
2
-
3
1
  Business Source License 1.1
4
2
 
5
3
  Parameters
@@ -12,40 +10,13 @@ Licensed Work: Loki Mode and all associated components, including but not
12
10
  rebranded or successor versions of this software.
13
11
  The Licensed Work is (c) 2024-2026 Autonomi, Inc.
14
12
  All rights reserved.
15
- Additional Use Grant: You may make production use of the Licensed Work, provided
16
- that such use does not include offering the Licensed Work,
17
- or any derivative work or adaptation of it, to third parties
18
- as a commercial product, hosted service, managed service, or
19
- embedded component of a commercial offering that competes
20
- with any product or service provided by the Licensor.
21
-
22
- For purposes of this license, "compete" means offering a
23
- product or service that provides substantially the same or
24
- similar functionality as the Licensed Work to third parties,
25
- whether for a fee or free of charge, including but not
26
- limited to:
27
- (a) multi-agent AI development orchestration platforms or
28
- frameworks;
29
- (b) autonomous software development products or services;
30
- (c) AI-powered code generation, review, or deployment
31
- services that incorporate or replicate the Licensed
32
- Work's orchestration, review, verification, or agent
33
- coordination systems; or
34
- (d) products or services that substantially replicate the
35
- Licensed Work's core architectural patterns, including
36
- the RARV (Reason-Act-Reflect-Verify) cycle, Completion
37
- Council peer review system, or multi-agent swarm
38
- coordination model.
39
-
40
- The following uses are permitted without a commercial license:
41
- (i) individual and non-commercial use;
42
- (ii) internal business use where the Licensed Work is used
43
- as a tool and is not itself the product or service
44
- being offered to third parties;
45
- (iii) academic, educational, and research use;
46
- (iv) evaluation and testing in non-production environments;
47
- (v) contributions back to the Licensed Work under a signed
48
- Contributor License Agreement.
13
+ Additional Use Grant: You may make production use of the Licensed Work for
14
+ individual, non-commercial, internal-business (as a tool,
15
+ not as a product offered to third parties), academic, and
16
+ evaluation purposes, subject to the full scope, the
17
+ definition of "compete," and the Intellectual Property and
18
+ Contributions terms set out in COMMERCIAL-TERMS.md, which is
19
+ incorporated into this Additional Use Grant by reference.
49
20
 
50
21
  Change Date: March 19, 2030
51
22
  Change License: Apache License, Version 2.0
@@ -53,25 +24,8 @@ Change License: Apache License, Version 2.0
53
24
  For information about alternative licensing arrangements for the Licensed Work,
54
25
  please contact: founder@autonomi.dev
55
26
 
56
- Intellectual Property Notice
57
-
58
- The Licensed Work embodies proprietary methodologies, architectural patterns,
59
- and research developed by the Licensor. These include, without limitation, the
60
- RARV execution cycle, the Completion Council consensus-based review system,
61
- context persistence mechanisms for long-running autonomous sessions, multi-
62
- provider orchestration protocols, and swarm-based agent coordination models.
63
- Use of these methodologies, whether through the Licensed Work's code or through
64
- independent reimplementation based on the Licensed Work's documentation or
65
- published research, is subject to the terms of this License.
66
-
67
- Contributions
68
-
69
- External contributions to the Licensed Work are accepted only under a signed
70
- Contributor License Agreement (CLA) that assigns sufficient rights to the
71
- Licensor to sublicense, relicense, and commercially distribute the contribution
72
- as part of the Licensed Work. Contributing code to the Licensed Work without a
73
- signed CLA does not grant the Licensor any rights to the contribution, and such
74
- contributions may be removed.
27
+ The Intellectual Property Notice and Contributions (CLA) terms that supplement
28
+ this License are set out in COMMERCIAL-TERMS.md.
75
29
 
76
30
  Notice
77
31
 
package/README.md CHANGED
@@ -4,11 +4,12 @@
4
4
 
5
5
  ### The spec-driven autonomous builder with verified completion.
6
6
 
7
+ _The free, source-available autonomous coding agent by [Autonomi](https://www.autonomi.dev/). Same Loki CLI, SDK, and MCP for everyone; the commercial editions for teams and enterprises are sold under the **Autonomi** brand (Autonomi Cloud, Autonomi Enterprise)._
8
+
7
9
  **Hand it a spec. It does not accept "done" on an empty diff or failing tests.**
8
10
 
9
11
  [![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
12
  [![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://badgen.net/github/stars/asklokesh/loki-mode?label=Stars&color=553DE9&icon=github)](https://github.com/asklokesh/loki-mode/stargazers)
12
13
  [![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
14
  [![License](https://img.shields.io/badge/License-BUSL--1.1-36342E?style=for-the-badge)](LICENSE)
14
15
 
@@ -289,7 +290,7 @@ TLS, OIDC/SSO, RBAC, OTEL tracing, policy engine, audit trails. Activated via en
289
290
 
290
291
  ## Purple Lab
291
292
 
292
- The hosted development platform. A Replit-like web UI for visual PRD-to-code workflow with AI chat for iterative development.
293
+ The hosted development platform. A Replit-like web UI for visual PRD-to-code workflow, with the Loki agent for iterative development. The same software is free and source-available as the local Loki Mode dashboard; offered managed to teams and enterprises under the **Autonomi** brand (Autonomi Cloud).
293
294
 
294
295
  ```bash
295
296
  loki web # launches at http://localhost:57375
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.35.0
6
+ # Loki Mode v7.37.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.35.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
401
+ **v7.37.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.35.0
1
+ 7.37.0
@@ -227,6 +227,61 @@ loki_review_guard_denylist() {
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
229
 
230
+ # EMBED 3b (v7.35.0, GitHub #167) -- --allowedTools POSITIVE ALLOWLIST for the
231
+ # reviewer / adversarial / council subcalls. Complements the v7.33 denylist with
232
+ # a least-privilege grant: the value lists ONLY the read/inspect tools a voter
233
+ # needs (Read, Grep, Glob, read-only git, and a small set of read-only shell
234
+ # commands). Per `claude --help` and the official permission docs
235
+ # (https://code.claude.com/docs/en/cli-reference,
236
+ # https://code.claude.com/docs/en/permissions) --allowedTools entries are
237
+ # permission ALLOW rules ("tools that execute without prompting"), using the same
238
+ # Tool / Tool(specifier) / Bash(cmd:*) prefix syntax as --disallowedTools.
239
+ #
240
+ # PRECEDENCE (proven empirically + documented): the docs state rules are
241
+ # "evaluated in order: deny, then ask, then allow ... the first match in that
242
+ # order determines the outcome", and "if a tool is denied at any level, no other
243
+ # level can allow it." Verified live against claude 2.1.177 on 2026-06-13 with a
244
+ # clean minimal env (temp git dir, --strict-mcp-config --mcp-config
245
+ # '{"mcpServers":{}}', --advisor opus to dodge the Fable-pinned default advisor
246
+ # tool, --tools "Bash,Read"):
247
+ # - allow-only Bash(git status:*) -> PERMITTED (control)
248
+ # - deny-only Bash(git status:*) -> BLOCKED (control)
249
+ # - allow + deny BOTH on Bash(git status:*) -> BLOCKED (deny wins)
250
+ # - allow + deny BOTH, under --dangerously-skip-permissions -> BLOCKED
251
+ # So DENY PRECEDENCE holds even in bypassPermissions mode (the mode every council
252
+ # subcall uses). This is the SAFE outcome: the allowlist and the denylist can be
253
+ # emitted TOGETHER. The denylist still blocks every mutation form even though the
254
+ # allowlist names read-only git; the allowlist additionally narrows the surface
255
+ # to least-privilege. They are NOT mutually exclusive; we ship both.
256
+ #
257
+ # This is a GUARDRAIL, not a sandbox (same caveat as the denylist): --allowedTools
258
+ # governs whether a tool runs WITHOUT A PROMPT, and these subcalls already run
259
+ # under --dangerously-skip-permissions, so the practical effect is to restrict the
260
+ # in-context tool surface to the allowlisted set while the denylist hard-blocks
261
+ # the dangerous forms. echo>/sed -i/python -c style writes are not enumerable and
262
+ # remain possible; the real net is commit-before-agent-wave (see CLAUDE.md).
263
+ #
264
+ # DEFAULT OFF (opt-in LOKI_REVIEW_ALLOWLIST=1) so the default argv on BOTH routes
265
+ # stays byte-identical to v7.34. Gated on CLI support so an older claude degrades
266
+ # gracefully (emits nothing). Predicate + token so call sites append uniformly:
267
+ # if loki_review_allowlist_enabled; then
268
+ # argv+=("--allowedTools" "$(loki_review_allowlist)")
269
+ # fi
270
+ loki_review_allowlist_enabled() {
271
+ [ "${LOKI_REVIEW_ALLOWLIST:-0}" = "1" ] || return 1
272
+ loki_claude_flag_supported "--allowedTools"
273
+ }
274
+ loki_review_allowlist() {
275
+ # Comma-separated single token (the flag is variadic; one token keeps the
276
+ # following -p prompt from being swallowed as additional tool names). Grants
277
+ # ONLY read/inspect tools: the file-read tools (Read/Grep/Glob), read-only
278
+ # git (diff/log/show/status/ls-files/rev-parse/blame), and a small set of
279
+ # read-only shell commands a reviewer uses to inspect the tree. No Edit,
280
+ # Write, NotebookEdit, or any mutation form. Kept BYTE-IDENTICAL to
281
+ # REVIEW_ALLOWLIST_TOKEN in loki-ts/src/providers/claude_flags.ts.
282
+ printf '%s' "Read,Grep,Glob,Bash(git diff:*),Bash(git log:*),Bash(git show:*),Bash(git status:*),Bash(git ls-files:*),Bash(git rev-parse:*),Bash(git blame:*),Bash(cat:*),Bash(ls:*),Bash(grep:*),Bash(rg:*),Bash(find:*),Bash(head:*),Bash(tail:*),Bash(wc:*)"
283
+ }
284
+
230
285
  # ---------- v7.34.0 Claude session-id stamping (Phase 1, correlation-only) -----
231
286
  # Derive a deterministic per-run UUID from the existing trust-run-id so the same
232
287
  # run always maps to the same claude session UUID, and the bash + Bun routes
@@ -287,3 +342,114 @@ loki_session_stamp_enabled() {
287
342
  [ "${LOKI_SESSION_STAMP:-0}" = "1" ] || return 1
288
343
  loki_claude_flag_supported "--session-id"
289
344
  }
345
+
346
+ # ---------- v7.36.0 ultrareview (cloud multi-agent review) gates ----------
347
+ # `claude ultrareview [options] [target]` (Claude Code 2.1.x) runs a
348
+ # cloud-hosted multi-agent code review of the current branch / a PR number / a
349
+ # base branch and prints the findings. It is a PAID CLOUD operation billed by
350
+ # Anthropic, SEPARATE from local model spend, and can take up to 30 minutes.
351
+ #
352
+ # These two predicates are the ONLY policy surface for the optional `loki review
353
+ # --ultra` tier (issue #168). Both are read-only and side-effect-free.
354
+ #
355
+ # DESIGN (issue #168, architected):
356
+ # - This is an EXPLICIT, on-demand user action (`loki review --ultra`), NEVER
357
+ # an automatic completion-council voice. The council runs many times per
358
+ # build; auto-running a paid cloud call there would be a silent-billing
359
+ # footgun. The user typing "ultra" IS the consent signal; a confirmation
360
+ # prompt (or --yes / LOKI_ULTRAREVIEW=1) is still required before any spend.
361
+ # - There is NO price API, so we CANNOT show a dollar figure. We disclose the
362
+ # cost-CLASS (paid cloud op) and require confirmation. Never claim to show
363
+ # an estimated dollar amount here (that would be a lie).
364
+
365
+ # Capability gate: does the installed `claude` CLI ship the `ultrareview`
366
+ # subcommand? loki_claude_flag_supported greps `claude --help`, whose top-level
367
+ # command list includes "ultrareview", so it matches subcommands too. Returns 0
368
+ # when supported, 1 otherwise (so callers can emit an honest upgrade message).
369
+ loki_ultrareview_supported() {
370
+ loki_claude_flag_supported "ultrareview"
371
+ }
372
+
373
+ # Non-interactive opt-in: is LOKI_ULTRAREVIEW=1 set? This is the env equivalent
374
+ # of passing --yes to THIS command only. It is deliberately NOT wired into the
375
+ # completion council or any auto path -- it only suppresses the interactive
376
+ # confirmation prompt for an explicit `loki review --ultra` invocation. Any
377
+ # other value (unset, 0, "true", etc.) returns 1 so only the exact "1" opts in.
378
+ loki_ultrareview_enabled() {
379
+ [ "${LOKI_ULTRAREVIEW:-0}" = "1" ]
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/loki CHANGED
@@ -13898,6 +13898,7 @@ if show_json:
13898
13898
  'recommended': recommended_provider,
13899
13899
  'reason': provider_reason,
13900
13900
  },
13901
+ 'moat': {'verified_completion': True, 'cost_honest': True},
13901
13902
  }
13902
13903
  if show_verbose:
13903
13904
  result['iteration_details'] = iteration_plan
@@ -14034,6 +14035,12 @@ if show_verbose:
14034
14035
 
14035
14036
  print(f'\n{DIM}This is an estimate. Actual usage depends on PRD complexity,')
14036
14037
  print(f'code review cycles, and test failures.{NC}')
14038
+
14039
+ print(f'\n{CYAN}Why this estimate is trustworthy{NC}')
14040
+ print(f'{DIM}Verified completion. The completion council refuses to call work done')
14041
+ print(f'without evidence - a real diff and green tests, or it blocks.{NC}')
14042
+ print(f'{DIM}Cost honesty. The model quoted here is the model the run dispatches')
14043
+ print(f'and the dashboard reports. Quote, dashboard, and dispatch agree.{NC}')
14037
14044
  print()
14038
14045
  " "$prd_path" "$show_json" "$show_verbose"
14039
14046
  }
@@ -14520,6 +14527,117 @@ main() {
14520
14527
  ;;
14521
14528
  esac
14522
14529
  }
14530
+ # v7.36.0 (issue #168): optional cloud multi-agent review tier (`loki review
14531
+ # --ultra`). Wraps the upstream `claude ultrareview` subcommand. ALWAYS opt-in,
14532
+ # cost-disclosed, default OFF, and advisory only (it does NOT block any gate).
14533
+ #
14534
+ # Consent model (modeled on cmd_demo's cost-confirm, autonomy/loki ~9488-9540):
14535
+ # - The disclosure ALWAYS prints (paid cloud op + may take up to 30 min).
14536
+ # - There is NO price API: we disclose the cost-CLASS, never a dollar figure.
14537
+ # - Interactive TTY without --yes: prompt, default NO.
14538
+ # - Non-TTY/CI without --yes (and no LOKI_ULTRAREVIEW=1): REFUSE with exit 2
14539
+ # and ZERO ultrareview calls (the no-silent-bill guard). Never hang on read.
14540
+ # - --yes or LOKI_ULTRAREVIEW=1 proceeds without prompting (this command only).
14541
+ # Args: <format> <assume_yes> <timeout_minutes> <target>
14542
+ _review_ultra() {
14543
+ local fmt="${1:-text}"
14544
+ local assume_yes="${2:-false}"
14545
+ local timeout_min="${3:-}"
14546
+ local target="${4:-}"
14547
+
14548
+ # Make the ultrareview gate predicates available even though autonomy/loki
14549
+ # does not source claude-flags.sh globally (same on-demand pattern as
14550
+ # autonomy/lib/voter-agents.sh). Degrade honestly if the lib is missing.
14551
+ if ! declare -F loki_ultrareview_supported >/dev/null 2>&1; then
14552
+ local _ur_lib=""
14553
+ if [ -f "${_LOKI_SCRIPT_DIR}/lib/claude-flags.sh" ]; then
14554
+ _ur_lib="${_LOKI_SCRIPT_DIR}/lib/claude-flags.sh"
14555
+ elif [ -f "$(dirname "$0")/lib/claude-flags.sh" ]; then
14556
+ _ur_lib="$(dirname "$0")/lib/claude-flags.sh"
14557
+ fi
14558
+ if [ -n "$_ur_lib" ]; then
14559
+ # shellcheck source=autonomy/lib/claude-flags.sh
14560
+ . "$_ur_lib" 2>/dev/null || true
14561
+ fi
14562
+ fi
14563
+
14564
+ # 1. claude CLI present at all?
14565
+ if ! command -v claude >/dev/null 2>&1; then
14566
+ echo -e "${RED}Error: 'claude' CLI not found on PATH.${NC}" >&2
14567
+ echo "loki review --ultra requires the Claude Code CLI (2.1.x) for cloud review." >&2
14568
+ return 1
14569
+ fi
14570
+
14571
+ # 2. Capability gate: does this claude support the ultrareview subcommand?
14572
+ # If absent, honest message + clean exit, never a half-feature.
14573
+ if ! declare -F loki_ultrareview_supported >/dev/null 2>&1 \
14574
+ || ! loki_ultrareview_supported; then
14575
+ echo -e "${RED}Error: your 'claude' CLI does not support 'ultrareview'.${NC}" >&2
14576
+ echo "Cloud multi-agent review needs Claude Code 2.1.x or newer; please upgrade." >&2
14577
+ return 1
14578
+ fi
14579
+
14580
+ # 3. Cost disclosure -- ALWAYS printed, even with --yes, so spend is never
14581
+ # hidden. NO dollar figure (there is no price API); cost-CLASS only.
14582
+ echo -e "${BOLD}loki review --ultra${NC} - cloud multi-agent code review (optional, paid)" >&2
14583
+ echo "" >&2
14584
+ echo -e "${YELLOW}ultrareview is a PAID cloud operation billed by Anthropic, separate${NC}" >&2
14585
+ echo -e "${YELLOW}from local model spend. It may take up to 30 minutes.${NC}" >&2
14586
+ echo "Findings are advisory only and do not block Loki's completion gate." >&2
14587
+ echo "" >&2
14588
+
14589
+ # 4. Consent. LOKI_ULTRAREVIEW=1 is the non-interactive opt-in equivalent of
14590
+ # --yes for THIS command only (never a council auto-trigger).
14591
+ local proceed=false
14592
+ if [ "$assume_yes" = true ]; then
14593
+ proceed=true
14594
+ elif declare -F loki_ultrareview_enabled >/dev/null 2>&1 && loki_ultrareview_enabled; then
14595
+ proceed=true
14596
+ else
14597
+ # Interactive only when stdin is a real TTY and CI is not forcing
14598
+ # non-interactive (matches the project's first-run gate semantics).
14599
+ local ultra_interactive=true
14600
+ if [ ! -t 0 ] || [ -n "${CI:-}" ]; then
14601
+ ultra_interactive=false
14602
+ fi
14603
+ if [ "$ultra_interactive" = true ]; then
14604
+ local ans=""
14605
+ echo -n "Run cloud ultrareview now? [y/N] " >&2
14606
+ read -r ans </dev/tty 2>/dev/null || ans=""
14607
+ if [[ "$ans" =~ ^[Yy] ]]; then
14608
+ proceed=true
14609
+ else
14610
+ echo "Cancelled. Nothing was spent." >&2
14611
+ return 0
14612
+ fi
14613
+ else
14614
+ # Non-TTY/CI without --yes: never hang; refuse with exit 2 and make
14615
+ # ZERO ultrareview calls (the no-silent-bill guard).
14616
+ echo "ultrareview needs confirmation; re-run with --yes (or set LOKI_ULTRAREVIEW=1) to proceed non-interactively" >&2
14617
+ return 2
14618
+ fi
14619
+ fi
14620
+
14621
+ [ "$proceed" = true ] || { echo "Cancelled. Nothing was spent." >&2; return 0; }
14622
+
14623
+ # 5. Invoke `claude ultrareview [--json] [--timeout N] [target]` exactly once.
14624
+ local ur_argv=("ultrareview")
14625
+ if [ "$fmt" = "json" ]; then
14626
+ ur_argv+=("--json")
14627
+ fi
14628
+ if [ -n "$timeout_min" ]; then
14629
+ ur_argv+=("--timeout" "$timeout_min")
14630
+ fi
14631
+ if [ -n "$target" ]; then
14632
+ ur_argv+=("$target")
14633
+ fi
14634
+
14635
+ echo -e "${DIM}Running: claude ${ur_argv[*]}${NC}" >&2
14636
+ echo "" >&2
14637
+ claude "${ur_argv[@]}"
14638
+ return $?
14639
+ }
14640
+
14523
14641
  # Standalone code review - diff-based quality gates (v6.20.0)
14524
14642
  cmd_review() {
14525
14643
  local review_staged=false
@@ -14528,6 +14646,9 @@ cmd_review() {
14528
14646
  local review_target=""
14529
14647
  local review_format="text"
14530
14648
  local review_severity="all"
14649
+ local review_ultra=false
14650
+ local review_assume_yes=false
14651
+ local review_timeout=""
14531
14652
 
14532
14653
  while [[ $# -gt 0 ]]; do
14533
14654
  case "$1" in
@@ -14548,6 +14669,15 @@ cmd_review() {
14548
14669
  echo "Options:"
14549
14670
  echo " --format json Output as JSON (for CI integration)"
14550
14671
  echo " --severity <level> Filter: critical, high, medium, low, info (shows level+above)"
14672
+ echo " --ultra Run an OPTIONAL cloud multi-agent review via"
14673
+ echo " 'claude ultrareview' (PAID cloud op, opt-in,"
14674
+ echo " default OFF, advisory only). Requires"
14675
+ echo " confirmation; --yes or LOKI_ULTRAREVIEW=1 to"
14676
+ echo " skip the prompt. Optional [file-or-dir] is"
14677
+ echo " passed as the ultrareview target (branch / PR"
14678
+ echo " number / base branch)."
14679
+ echo " --yes, -y Skip the --ultra confirmation prompt"
14680
+ echo " --timeout <minutes> --ultra: max minutes to wait (default 30)"
14551
14681
  echo " --help, -h Show this help"
14552
14682
  echo ""
14553
14683
  echo "Exit codes:"
@@ -14563,8 +14693,22 @@ cmd_review() {
14563
14693
  echo " loki review --since HEAD~5 # Changes in last 5 commits"
14564
14694
  echo " loki review --format json # JSON output for CI"
14565
14695
  echo " loki review --severity high # Only HIGH+ findings"
14696
+ echo " loki review --ultra # Cloud multi-agent review (paid, opt-in)"
14697
+ echo " loki review --ultra 42 # Cloud review of GitHub PR #42"
14698
+ echo " loki review --ultra --yes # Skip confirmation (e.g. in a script)"
14566
14699
  return 0
14567
14700
  ;;
14701
+ --ultra) review_ultra=true; shift ;;
14702
+ --yes|-y) review_assume_yes=true; shift ;;
14703
+ --timeout)
14704
+ shift
14705
+ review_timeout="${1:-}"
14706
+ if [ -z "$review_timeout" ]; then
14707
+ echo -e "${RED}Error: --timeout requires a value in minutes${NC}"
14708
+ return 1
14709
+ fi
14710
+ shift
14711
+ ;;
14568
14712
  --staged) review_staged=true; shift ;;
14569
14713
  --pr)
14570
14714
  shift
@@ -14600,6 +14744,16 @@ cmd_review() {
14600
14744
  esac
14601
14745
  done
14602
14746
 
14747
+ # v7.36.0 (issue #168): optional cloud multi-agent review tier. This is an
14748
+ # explicit, on-demand, opt-in, cost-disclosed path that wraps the existing
14749
+ # `claude ultrareview` subcommand. It short-circuits the local-only review
14750
+ # below: --ultra is a different operation (paid cloud), not a flag that
14751
+ # augments the local static analysis.
14752
+ if [ "$review_ultra" = true ]; then
14753
+ _review_ultra "$review_format" "$review_assume_yes" "$review_timeout" "$review_target"
14754
+ return $?
14755
+ fi
14756
+
14603
14757
  # Severity level ordering (higher number = more severe)
14604
14758
  _review_sev_level() {
14605
14759
  case "$1" in
package/autonomy/run.sh CHANGED
@@ -4557,10 +4557,21 @@ _loki_hash_stdin() {
4557
4557
  # path+size pairs PLUS file content (v7.32.3, #569: path+size alone was blind to
4558
4558
  # a same-size content edit, so a stale PRD could be silently reused with a
4559
4559
  # false "codebase unchanged" disclosure). Content hashing is clone-stable
4560
- # (mtime is not, which is why mtime was never used). Trees larger than
4561
- # LOKI_PRD_SIG_CONTENT_BUDGET bytes (default 50MB) skip the content pass and
4562
- # emit a "files-shallow:" signature so startup stays fast; the safe failure
4563
- # mode there is unchanged-from-before (size-blind), never a false "changed".
4560
+ # (mtime is not, which is why mtime was never used). Three content tiers:
4561
+ # 1. full content hash ("files:") when the tree is both at-or-under
4562
+ # LOKI_PRD_SIG_CONTENT_BUDGET bytes (default 50MB) AND at-or-under
4563
+ # LOKI_PRD_SIG_CONTENT_MAXFILES files (default 20000). Detects any edit.
4564
+ # 2. sampled content hash ("files-sampled:", #171) when the tree exceeds
4565
+ # either bound: hashes the head + tail (first 4096 + last 4096 bytes) of
4566
+ # every file. Catches same-size edits at the start or end of a file
4567
+ # without a full read of a huge tree. Residual honest gap: a same-size
4568
+ # edit in the MIDDLE of a large file (>8192 bytes) that touches neither
4569
+ # 4KB window is still invisible. Far narrower than the old size-blind
4570
+ # fallback, which missed ALL same-size edits.
4571
+ # 3. (historical) "files-shallow:" was the old content-blind fallback. It is
4572
+ # no longer emitted, but is still ACCEPTED when read from a stored pre-#171
4573
+ # signature so the first post-upgrade run reuses instead of falsely
4574
+ # claiming "codebase changed" (one-run format-transition, see consumer).
4564
4575
  # Echoes the signature.
4565
4576
  compute_codebase_signature() {
4566
4577
  local dir="${1:-.}"
@@ -4576,7 +4587,7 @@ compute_codebase_signature() {
4576
4587
  fi
4577
4588
  echo "git:${head}:${dirty}"
4578
4589
  else
4579
- local listing count total_sz budget
4590
+ local listing count total_sz budget maxfiles
4580
4591
  listing=$(find . \
4581
4592
  -type d \( -name .loki -o -name .git -o -name node_modules -o -name dist \
4582
4593
  -o -name build -o -name .next -o -name target -o -name vendor \
@@ -4592,20 +4603,35 @@ compute_codebase_signature() {
4592
4603
  count=$(printf '%s\n' "$listing" | grep -c . || true)
4593
4604
  total_sz=$(printf '%s\n' "$listing" | awk -F'\t' '{s+=$2} END {printf "%d", s}')
4594
4605
  budget="${LOKI_PRD_SIG_CONTENT_BUDGET:-52428800}"
4595
- if [ "${total_sz:-0}" -le "$budget" ] 2>/dev/null; then
4596
- # Content pass: stream all file contents through one hash in the
4597
- # same sorted order as the listing. Detects same-size edits.
4598
- # xargs -0 batches the reads into a handful of cat invocations,
4599
- # so cost scales with BYTES (which the budget above bounds), not
4600
- # file count: a fork-per-file loop here measured ~38s of added
4601
- # startup on a 30k-small-file tree. Renames and content swaps
4602
- # are still caught by the listing hash (paths+sizes) below.
4606
+ maxfiles="${LOKI_PRD_SIG_CONTENT_MAXFILES:-20000}"
4607
+ if [ "${total_sz:-0}" -le "$budget" ] 2>/dev/null \
4608
+ && [ "${count:-0}" -le "$maxfiles" ] 2>/dev/null; then
4609
+ # Tier 1 -- full content pass: stream all file contents through one
4610
+ # hash in the same sorted order as the listing. Detects any edit,
4611
+ # including same-size ones. xargs -0 batches the reads into a
4612
+ # handful of cat invocations, so cost scales with BYTES (which the
4613
+ # budget above bounds), not file count: a fork-per-file loop here
4614
+ # measured ~38s of added startup on a 30k-small-file tree. Renames
4615
+ # and content swaps are still caught by the listing hash below.
4603
4616
  local content_hash
4604
4617
  content_hash=$(printf '%s\n' "$listing" | cut -f1 | tr '\n' '\0' \
4605
4618
  | xargs -0 cat 2>/dev/null | _loki_hash_stdin)
4606
4619
  echo "files:$(printf '%s' "$listing" | _loki_hash_stdin):${count}:${content_hash}"
4607
4620
  else
4608
- echo "files-shallow:$(printf '%s' "$listing" | _loki_hash_stdin):${count}"
4621
+ # Tier 2 -- sampled content pass (#171): the tree is over the byte
4622
+ # budget OR over the file-count cap, so a full read would be slow.
4623
+ # Hash the head + tail (first 4096 + last 4096 bytes) of every file
4624
+ # instead. This catches same-size edits at the start or end of a
4625
+ # file (which the old size-blind "files-shallow:" missed entirely),
4626
+ # at a fixed <=8KB-per-file cost. -n 64 batches files per sh fork
4627
+ # to avoid a fork-per-file storm on large trees. Same sorted order
4628
+ # as the listing keeps the hash deterministic. Residual honest gap:
4629
+ # a same-size edit in the middle of a >8KB file is still invisible.
4630
+ local sample_hash
4631
+ sample_hash=$(printf '%s\n' "$listing" | cut -f1 | tr '\n' '\0' \
4632
+ | xargs -0 -n 64 sh -c 'for f in "$@"; do head -c 4096 -- "$f" 2>/dev/null; tail -c 4096 -- "$f" 2>/dev/null; done' _ 2>/dev/null \
4633
+ | _loki_hash_stdin)
4634
+ echo "files-sampled:$(printf '%s' "$listing" | _loki_hash_stdin):${count}:${sample_hash}"
4609
4635
  fi
4610
4636
  fi
4611
4637
  )
@@ -4698,6 +4724,30 @@ except Exception:
4698
4724
  echo "reuse"; return 0
4699
4725
  fi
4700
4726
  ;;
4727
+ files-shallow:*:*)
4728
+ # #171 format transition: a stored pre-#171 size-blind signature
4729
+ # ("files-shallow:<listing>:<count>", 3 fields) compared against
4730
+ # the new sampled signature ("files-sampled:<listing>:<count>:
4731
+ # <samplehash>") would falsely claim "codebase changed" on the
4732
+ # first post-upgrade run. When the listing-hash and count match
4733
+ # (i.e. the stored value with its prefix swapped to files-sampled:
4734
+ # is a prefix of the current sampled signature), the tree is
4735
+ # unchanged at the OLD format's trust level (paths+sizes): reuse,
4736
+ # honestly. The next persist upgrades the stored format to the
4737
+ # sampled tier. A same-size edit made BEFORE the upgrade stays
4738
+ # invisible for this one run, exactly as on the old version (no
4739
+ # regression, no false disclosure).
4740
+ if [ "$(printf '%s' "$stored" | tr -dc ':' | wc -c | tr -d ' ')" = "2" ]; then
4741
+ local stored_sampled="files-sampled:${stored#files-shallow:}"
4742
+ case "$current" in
4743
+ files-sampled:*)
4744
+ if [ "${current#"${stored_sampled}":}" != "$current" ]; then
4745
+ echo "reuse"; return 0
4746
+ fi
4747
+ ;;
4748
+ esac
4749
+ fi
4750
+ ;;
4701
4751
  esac
4702
4752
  echo "update"
4703
4753
  fi
@@ -4756,7 +4806,16 @@ _legacy_upgrade = (
4756
4806
  and prev_sig.count(':') == 2
4757
4807
  and sig.startswith(prev_sig + ':')
4758
4808
  )
4759
- if prev_at and (prev_sig == sig or _legacy_upgrade):
4809
+ # #171 format upgrade: a stored pre-#171 size-blind 'files-shallow:<listing>:
4810
+ # <count>' (3 fields) reused into the new sampled 'files-sampled:<listing>:
4811
+ # <count>:<samplehash>' whose listing fields match. Same trust level (the
4812
+ # decide returned reuse), so the PRD content did not change: preserve the date.
4813
+ _sampled_upgrade = (
4814
+ isinstance(prev_sig, str) and prev_sig.startswith('files-shallow:')
4815
+ and prev_sig.count(':') == 2
4816
+ and sig.startswith('files-sampled:' + prev_sig[len('files-shallow:'):] + ':')
4817
+ )
4818
+ if prev_at and (prev_sig == sig or _legacy_upgrade or _sampled_upgrade):
4760
4819
  generated_at = prev_at
4761
4820
  else:
4762
4821
  generated_at = datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00','Z')
@@ -7838,6 +7897,16 @@ BUILD_PROMPT
7838
7897
  if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
7839
7898
  _rv_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
7840
7899
  fi
7900
+ # EMBED 3b (--allowedTools, #167): positive least-privilege
7901
+ # allowlist. DEFAULT OFF (opt-in LOKI_REVIEW_ALLOWLIST=1).
7902
+ # Emitted ALONGSIDE the denylist: verified live (claude
7903
+ # 2.1.177) that deny precedence holds even under
7904
+ # --dangerously-skip-permissions, so the denylist still
7905
+ # hard-blocks mutations while this narrows the surface to
7906
+ # read/inspect tools. See loki_review_allowlist.
7907
+ if type loki_review_allowlist_enabled >/dev/null 2>&1 && loki_review_allowlist_enabled; then
7908
+ _rv_argv+=("--allowedTools" "$(loki_review_allowlist)")
7909
+ fi
7841
7910
  claude "${_rv_argv[@]}" -p "$prompt_text" \
7842
7911
  --output-format text > "$review_output" 2>/dev/null
7843
7912
  ;;
@@ -8069,6 +8138,14 @@ ADVERSARIAL_EOF
8069
8138
  if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
8070
8139
  _adv_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
8071
8140
  fi
8141
+ # EMBED 3b (--allowedTools, #167): positive least-privilege
8142
+ # allowlist. DEFAULT OFF (opt-in LOKI_REVIEW_ALLOWLIST=1).
8143
+ # Emitted ALONGSIDE the denylist (deny precedence verified
8144
+ # live, holds under --dangerously-skip-permissions). See
8145
+ # loki_review_allowlist.
8146
+ if type loki_review_allowlist_enabled >/dev/null 2>&1 && loki_review_allowlist_enabled; then
8147
+ _adv_argv+=("--allowedTools" "$(loki_review_allowlist)")
8148
+ fi
8072
8149
  claude "${_adv_argv[@]}" -p "$adversarial_prompt" \
8073
8150
  --output-format text > "$result_file" 2>/dev/null || true
8074
8151
  fi
@@ -12313,6 +12390,25 @@ except Exception:
12313
12390
  fi
12314
12391
  fi
12315
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
+
12316
12412
  # Trust-metrics instrumentation marker: record one run_start event per
12317
12413
  # fresh run so the trust-metrics denominator counts ONLY instrumented runs.
12318
12414
  # This is what lets the aggregator distinguish "0 blocks measured" from
@@ -12338,9 +12434,22 @@ except Exception:
12338
12434
  if [ -n "$_loki_session_uuid" ]; then
12339
12435
  local _loki_session_created
12340
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
12341
12450
  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" \
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" \
12344
12453
  > ".loki/state/claude-session.json" 2>/dev/null || true
12345
12454
  fi
12346
12455
  fi
@@ -12739,6 +12848,34 @@ except Exception as exc:
12739
12848
  && loki_claude_flag_supported "--include-partial-messages"; then
12740
12849
  _loki_claude_argv+=("--include-partial-messages")
12741
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
12742
12879
  # v7.34.0 Phase 1 (correlation-only): per-iteration --session-id. OPT-IN
12743
12880
  # via LOKI_SESSION_STAMP=1 (CONSERVATIVE DEFAULT is OFF so the default
12744
12881
  # argv stays byte-identical to v7.33 -- the UX-monotonicity requirement).
@@ -12747,7 +12884,10 @@ except Exception as exc:
12747
12884
  # and accumulate transcript (Phase 2 continuity, out of scope). This keeps
12748
12885
  # each iteration a fresh stateless session while making its ~/.claude
12749
12886
  # JSONL name predictable for dashboard correlation. Gated on CLI support.
12750
- 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 \
12751
12891
  && loki_session_stamp_enabled; then
12752
12892
  local _loki_iter_session_uuid
12753
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.35.0"
10
+ __version__ = "7.37.0"
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.35.0
5
+ **Version:** v7.37.0
6
6
 
7
7
  ---
8
8
 
@@ -0,0 +1,65 @@
1
+ <!-- SPDX-License-Identifier: BUSL-1.1 -->
2
+
3
+ # License Change Notice
4
+
5
+ ## Summary
6
+
7
+ Effective March 19, 2026, Loki Mode transitioned from the MIT License to the
8
+ Business Source License 1.1 (BSL 1.1). The Licensed Work is owned by
9
+ Autonomi, Inc. (autonomi.dev).
10
+
11
+ ## What this means
12
+
13
+ **If you are using Loki Mode for personal projects, learning, research, or
14
+ internal tooling:** Nothing changes for you. You can continue using, modifying,
15
+ and redistributing Loki Mode freely.
16
+
17
+ **If you are building a competing commercial product or service:** You need a
18
+ commercial license. Contact founder@autonomi.dev.
19
+
20
+ ## What counts as "competing"
21
+
22
+ Offering a product or service to third parties that provides substantially
23
+ the same or similar functionality as Loki Mode, including multi-agent AI
24
+ development orchestration, autonomous software development services, AI code
25
+ generation or deployment services that incorporate or replicate Loki Mode's
26
+ orchestration, review, verification, or agent coordination systems, or
27
+ products that substantially replicate core architectural patterns such as the
28
+ RARV cycle, Completion Council, or swarm coordination model.
29
+
30
+ ## What does NOT require a commercial license
31
+
32
+ - Using Loki Mode to build your own software products
33
+ - Internal company use where Loki Mode is a tool, not the product
34
+ - Academic, educational, and research use
35
+ - Personal and non-commercial use
36
+ - Evaluation and testing in non-production environments
37
+ - Contributing improvements back under a signed CLA
38
+
39
+ ## Contributing
40
+
41
+ All external contributions require a signed Contributor License Agreement
42
+ (CLA). This ensures Autonomi maintains the rights needed to license and
43
+ distribute the Licensed Work, including in commercial contexts. Contributions
44
+ submitted without a signed CLA may be removed from the codebase.
45
+
46
+ ## Intellectual Property
47
+
48
+ Loki Mode embodies proprietary methodologies and architectural patterns
49
+ developed by Autonomi, including the RARV execution cycle, the Completion
50
+ Council consensus review system, context persistence for long-running
51
+ autonomous sessions, multi-provider orchestration protocols, and swarm-based
52
+ agent coordination. These are protected under this License regardless of
53
+ whether they are used via the code directly or reimplemented independently
54
+ from the documentation.
55
+
56
+ ## Open Source Conversion
57
+
58
+ On March 19, 2030 (or four years from the release date of each version,
59
+ whichever comes first), each version automatically converts to the
60
+ Apache License 2.0.
61
+
62
+ ## Contact
63
+
64
+ founder@autonomi.dev for commercial licensing, partnership inquiries, or
65
+ questions about permitted use.
@@ -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.35.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.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)
@@ -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=4A51F9C635720B1064756E2164756E21
792
+ //# debugId=92753F401EF6DED164756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.35.0'
60
+ __version__ = '7.37.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.35.0",
4
+ "version": "7.37.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",