loki-mode 7.40.0 → 7.41.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/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.40.0
6
+ # Loki Mode v7.41.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.40.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
401
+ **v7.41.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.40.0
1
+ 7.41.0
@@ -1775,7 +1775,15 @@ ISSUES: CRITICAL:description (optional, one per line per issue)"
1775
1775
  if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
1776
1776
  _cm_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
1777
1777
  fi
1778
- verdict=$(echo "$prompt" | claude "${_cm_argv[@]}" -p 2>/dev/null | tail -20)
1778
+ # caveman HARD-SUPPRESS (parsed output): this council vote is
1779
+ # parsed for "VOTE: APPROVE|REJECT|CANNOT_VALIDATE". A globally-
1780
+ # active caveman would compress/reword that line and silently flip
1781
+ # the vote to the default REJECT, corrupting completion detection.
1782
+ # Disable caveman UNCONDITIONALLY with CAVEMAN_DEFAULT_MODE=off.
1783
+ # Set inline (not via a helper) so the carve-out holds even when
1784
+ # this file is sourced standalone and the helpers are out of scope.
1785
+ # Inlined on `claude` only (does not cross the pipe). No-op absent.
1786
+ verdict=$(echo "$prompt" | env CAVEMAN_DEFAULT_MODE=off claude "${_cm_argv[@]}" -p 2>/dev/null | tail -20)
1779
1787
  fi
1780
1788
  ;;
1781
1789
  codex)
@@ -1870,7 +1878,11 @@ REASON: your reasoning"
1870
1878
  if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
1871
1879
  _co_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
1872
1880
  fi
1873
- verdict=$(echo "$prompt" | claude "${_co_argv[@]}" -p 2>/dev/null | tail -20)
1881
+ # caveman HARD-SUPPRESS (parsed output): the devil's-advocate
1882
+ # (contrarian) vote is parsed for "VOTE:". Disable caveman
1883
+ # unconditionally so compression cannot flip the contrarian vote.
1884
+ # Inlined on `claude` only (does not cross the pipe). No-op absent.
1885
+ verdict=$(echo "$prompt" | env CAVEMAN_DEFAULT_MODE=off claude "${_co_argv[@]}" -p 2>/dev/null | tail -20)
1874
1886
  fi
1875
1887
  ;;
1876
1888
  codex)
@@ -276,7 +276,16 @@ Respond ONLY with a valid JSON object. No markdown fencing."
276
276
  if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
277
277
  _c2_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
278
278
  fi
279
- result=$(echo "$full_prompt" | claude "${_c2_argv[@]}" -p 2>/dev/null || echo '{"verdict":"REJECT","reasoning":"review execution failed","issues":[]}')
279
+ # caveman HARD-SUPPRESS (parsed output, v7.41.0): this reviewer
280
+ # verdict is captured and parsed for the JSON "verdict" field. A
281
+ # globally-active caveman would compress/reword that JSON and
282
+ # silently flip the verdict to the REJECT fallback. The tree-wide
283
+ # default-off export in claude-flags.sh already covers this (the
284
+ # whole subprocess tree inherits CAVEMAN_DEFAULT_MODE=off); the
285
+ # inline prefix here is belt-and-suspenders so the carve-out is
286
+ # self-documenting and robust to sourcing order. No-op when caveman
287
+ # is absent.
288
+ result=$(echo "$full_prompt" | CAVEMAN_DEFAULT_MODE=off claude "${_c2_argv[@]}" -p 2>/dev/null || echo '{"verdict":"REJECT","reasoning":"review execution failed","issues":[]}')
280
289
  else
281
290
  result='{"verdict":"REJECT","reasoning":"reviewer CLI unavailable","issues":[]}'
282
291
  fi
package/autonomy/grill.sh CHANGED
@@ -204,8 +204,16 @@ grill_invoke_provider() {
204
204
  if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
205
205
  _gr_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
206
206
  fi
207
+ # caveman HARD-SUPPRESS (parsed output, v7.41.0): the grill output is
208
+ # parsed downstream by loki-grill (and written to report.md as the
209
+ # hardest spec questions). Treat it as parsed: a globally-active
210
+ # caveman would compress/reword the questions. The tree-wide default-off
211
+ # export in claude-flags.sh (sourced at grill.sh:45) already covers
212
+ # this; the inline `env` prefix is belt-and-suspenders. `env` is used
213
+ # (not a bare VAR=val prefix) because the call goes through
214
+ # _grill_with_timeout, where the first token is exec'd as the command.
207
215
  out="$(printf '%s' "$prompt" \
208
- | _grill_with_timeout "${LOKI_GRILL_TIMEOUT:-180}" claude "${_gr_argv[@]}" -p - 2>/dev/null)"
216
+ | _grill_with_timeout "${LOKI_GRILL_TIMEOUT:-180}" env CAVEMAN_DEFAULT_MODE=off claude "${_gr_argv[@]}" -p - 2>/dev/null)"
209
217
  if [ -z "$out" ]; then
210
218
  _grill_err "provider returned no output (timeout or invocation error)"
211
219
  return $GRILL_EXIT_ERROR
@@ -482,6 +482,275 @@ loki_workflows_enabled() {
482
482
  [ "${LOKI_USE_CLAUDE_WORKFLOWS:-0}" = "1" ]
483
483
  }
484
484
 
485
+ # ---------- v7.x caveman output-token compressor gates ----------
486
+ # caveman (https://github.com/JuliusBrussee/caveman, MIT, vendor-less pin) is a
487
+ # Claude Code SKILL + SessionStart hook that instructs the model to compress its
488
+ # OUTPUT TOKENS only (prose style: lite / full / ultra / wenyan), keeping all
489
+ # technical substance. It wraps NO API, needs NO key, has NO network of its own.
490
+ # Once installed it activates GLOBALLY in Claude Code via a SessionStart hook
491
+ # that reads getDefaultMode() (env CAVEMAN_DEFAULT_MODE > repo .caveman config >
492
+ # user config > "full") and, unless the mode is "off", injects its ruleset.
493
+ #
494
+ # THE MOAT RISK (central, why this is wired the way it is): Loki's trust gates
495
+ # parse EXACT model prose -- council "VOTE: APPROVE|REJECT|CANNOT_VALIDATE", code
496
+ # review "^VERDICT:", the legacy completion-promise grep, the evidence-gate
497
+ # sentinels. A globally-active caveman would compress those subcall outputs and
498
+ # silently flip a verdict to the default (REJECT / not-complete), corrupting the
499
+ # loop. This is the same failure class as the --bare OAuth footgun documented at
500
+ # claude-flags.sh:152-161.
501
+ #
502
+ # THE DESIGN (off by construction, not by convention):
503
+ # - ACTIVATE compression only on FREE-FORM generation (main RARV dev loop +
504
+ # read-only codebase-analysis): inline `CAVEMAN_DEFAULT_MODE=<level> claude`.
505
+ # - HARD-SUPPRESS on EVERY parsed-output subcall (council vote, ^VERDICT:
506
+ # review, adversarial probe, conflict-merge, USAGE regen): inline
507
+ # `CAVEMAN_DEFAULT_MODE=off claude`. The activate hook then deletes its flag
508
+ # and emits nothing (verified: `CAVEMAN_DEFAULT_MODE=off node
509
+ # caveman-activate.js` prints "OK" with no ruleset).
510
+ #
511
+ # Suppression is UNCONDITIONAL and UNGATED (see loki_caveman_suppress_env): it is
512
+ # a harmless no-op env value when caveman is absent and the essential carve-out
513
+ # when caveman is globally present (surface b, or a user's own install) even with
514
+ # LOKI_CAVEMAN=0. NEVER gate suppression on supported/enabled -- that would leave
515
+ # the trust gates unprotected exactly when a user has caveman on but Loki off.
516
+ #
517
+ # Disclosure (honest, no fabricated figures): caveman compresses OUTPUT tokens
518
+ # only, not input/thinking; savings are real but bounded. There is no price API,
519
+ # so we disclose the savings CLASS, never a dollar amount (same posture as the
520
+ # ultrareview/workflows gates).
521
+
522
+ # Version pin (vendor-less). Upgrade by bumping this. The upstream installer pins
523
+ # its hook downloads to PINNED_REF = CAVEMAN_REF || 'v1.9.0' (a git tag), and the
524
+ # curl|bash path delegates to `npx -y github:JuliusBrussee/caveman#<ref>`. We
525
+ # default to 1.9.0 and derive the `v`-prefixed tag in the bootstrap helper.
526
+ LOKI_CAVEMAN_VERSION="${LOKI_CAVEMAN_VERSION:-1.9.0}"
527
+
528
+ # The compression level for free-form activation. Maps directly to caveman's
529
+ # CAVEMAN_DEFAULT_MODE values: lite | full | ultra | wenyan | wenyan-lite |
530
+ # wenyan-full | wenyan-ultra. Default "full" (the founder-locked global default).
531
+ # Never "off" here -- "off" is the suppression value, not an activation level.
532
+ LOKI_CAVEMAN_LEVEL="${LOKI_CAVEMAN_LEVEL:-full}"
533
+
534
+ # ---------- DEFAULT-SUPPRESS: off by construction, tree-wide ----------
535
+ # THE MOAT GUARANTEE (v7.41.0 council fix): instead of hand-enumerating every
536
+ # parsed-output trust-gate subcall and remembering to prefix each with
537
+ # CAVEMAN_DEFAULT_MODE=off (a missed site silently corrupts a verdict -- caveman
538
+ # exits 0 with mangled prose and the `|| REJECT` fallback never fires), we flip
539
+ # the ENTIRE process tree to suppressed at the one module every tree sources.
540
+ #
541
+ # claude-flags.sh is sourced by EVERY tree that can spawn a parsed claude
542
+ # subcall: the run.sh orchestrator (via providers/claude.sh), grill.sh (standalone
543
+ # `loki grill`), lib/voter-agents.sh (Phase C agent-dispatch voters), and the
544
+ # loki standalone review/workflows commands (on-demand). council-v2.sh carries no
545
+ # source of its own but only ever runs inside completion-council.sh, which is in
546
+ # the run.sh tree, so it inherits this export too. Exporting off HERE makes the
547
+ # whole spawned subprocess tree inherit suppression -- caveman's SessionStart
548
+ # hook reads process.env CAVEMAN_DEFAULT_MODE -- closing council-v2.sh,
549
+ # voter-agents.sh, grill.sh, every existing parsed subcall, and any FUTURE one by
550
+ # construction. ACTIVATION on the handful of free-form generation sites overrides
551
+ # this per-invocation (CAVEMAN_DEFAULT_MODE=<level> claude ...).
552
+ #
553
+ # Capture the user's pre-existing global CAVEMAN_DEFAULT_MODE BEFORE we clobber
554
+ # it, so the activation path can respect (never RAISE) a user's lower level (see
555
+ # loki_caveman_activate_env). Guarded on UNSET (not empty): a child process that
556
+ # inherits our exported LOKI_CAVEMAN_USER_MODE="" (user had no global mode) and
557
+ # re-sources this file must NOT recapture the now-exported CAVEMAN_DEFAULT_MODE=off
558
+ # as the user mode. ${var+x} is empty only when var is genuinely unset, so the
559
+ # capture runs exactly once across the whole process tree, never recapturing "off".
560
+ if [ -z "${LOKI_CAVEMAN_USER_MODE+x}" ]; then
561
+ LOKI_CAVEMAN_USER_MODE="${CAVEMAN_DEFAULT_MODE:-}"
562
+ fi
563
+ export LOKI_CAVEMAN_USER_MODE
564
+ export CAVEMAN_DEFAULT_MODE=off
565
+
566
+ # Rank a caveman mode by compression aggressiveness for the no-raise comparison.
567
+ # Higher number = more aggressive (drops more nuance). "off" is the floor; unknown
568
+ # or empty modes rank as -1 (treated as "no opinion", so they never win a min()).
569
+ # The wenyan-* variants mirror their plain counterparts' aggressiveness.
570
+ _loki_caveman_level_rank() {
571
+ case "${1:-}" in
572
+ off) printf '0' ;;
573
+ lite|wenyan-lite) printf '1' ;;
574
+ full|wenyan|wenyan-full) printf '2' ;;
575
+ ultra|wenyan-ultra) printf '3' ;;
576
+ *) printf '%s' '-1' ;;
577
+ esac
578
+ }
579
+
580
+ # Caveman config dir resolution mirrors caveman-config.js getConfigDir(): honors
581
+ # CLAUDE_CONFIG_DIR for the flag file location used to detect an existing install.
582
+ _loki_caveman_claude_dir() {
583
+ printf '%s' "${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
584
+ }
585
+
586
+ # True (0) when caveman appears installed: its SessionStart hook file exists in
587
+ # the resolved Claude config dir. Best-effort, read-only. We check the hook the
588
+ # upstream installer writes to ~/.claude/hooks/caveman-activate.js (standalone)
589
+ # OR a plugin install marker. Either presence means activation will fire.
590
+ _loki_caveman_installed() {
591
+ local dir
592
+ dir="$(_loki_caveman_claude_dir)"
593
+ [ -f "$dir/hooks/caveman-activate.js" ] && return 0
594
+ # Plugin install: the activate hook lives under a plugin root; the flag file
595
+ # path is stable. Treat a prior-session flag file as a weaker install signal.
596
+ [ -f "$dir/.caveman-active" ] && return 0
597
+ return 1
598
+ }
599
+
600
+ # Capability gate: can caveman compression be USED on this run? Provider is
601
+ # Claude AND the claude CLI is present AND caveman is installed-or-bootstrappable
602
+ # AND it is not disabled by the LOKI_CAVEMAN knob. Returns 0 when usable, 1
603
+ # otherwise (callers emit an honest message + degrade to an uncompressed run).
604
+ # Mirrors loki_workflows_supported's shape (provider + CLI + not-disabled).
605
+ loki_caveman_supported() {
606
+ # Provider must be Claude (Tier 1). caveman is Claude-Code-only.
607
+ [ "${LOKI_PROVIDER:-claude}" = "claude" ] || return 1
608
+ command -v claude >/dev/null 2>&1 || return 1
609
+ # Opt-out knob also suppresses the capability (no activation when off).
610
+ [ "${LOKI_CAVEMAN:-1}" = "0" ] && return 1
611
+ # Installed now, OR bootstrappable (node + npx present so the pin can install
612
+ # on demand). Either way activation can take effect this run or the next.
613
+ if _loki_caveman_installed; then
614
+ return 0
615
+ fi
616
+ command -v node >/dev/null 2>&1 && command -v npx >/dev/null 2>&1 && return 0
617
+ return 1
618
+ }
619
+
620
+ # Activation knob: is caveman compression ENABLED for free-form subcalls?
621
+ # DEFAULT ON (LOKI_CAVEMAN unset or 1). Opt out with LOKI_CAVEMAN=0.
622
+ #
623
+ # CROSS-COUPLING GUARD (moat safety): when LOKI_LEGACY_COMPLETION_MATCH=true the
624
+ # runner detects completion by grepping the MAIN-loop prose for the completion
625
+ # promise (run.sh:9641). Compressing the main loop would mangle that prose and
626
+ # break the legacy detector, so caveman activation is DISABLED whenever the
627
+ # legacy prose-match path is in use. The default completion path (the
628
+ # loki_complete_task MCP tool / COMPLETION_REQUESTED signal file) is immune to
629
+ # compression, so the default config keeps caveman on.
630
+ loki_caveman_enabled() {
631
+ [ "${LOKI_CAVEMAN:-1}" = "0" ] && return 1
632
+ [ "${LOKI_LEGACY_COMPLETION_MATCH:-false}" = "true" ] && return 1
633
+ return 0
634
+ }
635
+
636
+ # The activation env VALUE for a free-form subcall: the configured level, or
637
+ # empty when activation is not warranted (caveman unsupported or disabled). The
638
+ # caller inlines it as a per-invocation env prefix (NEVER `export` -- a persisted
639
+ # export would bleed into later parsed subcalls and defeat the carve-out):
640
+ # _cm_lvl="$(loki_caveman_activate_env)"
641
+ # if [ -n "$_cm_lvl" ]; then
642
+ # CAVEMAN_DEFAULT_MODE="$_cm_lvl" claude ... # free-form only
643
+ # else
644
+ # claude ...
645
+ # fi
646
+ #
647
+ # NO-RAISE (v7.41.0 R2 finding 4): the level returned is the configured Loki
648
+ # level, EXCEPT we never silently RAISE a user who set a LOWER global caveman
649
+ # level. If the user globally chose "lite" (less aggressive, preserves more
650
+ # nuance) we honor "lite" rather than forcing "full" on their free-form output.
651
+ # We only ever lower toward the user's preference, never above it; the activation
652
+ # level itself is the conservative-for-accuracy ceiling. The user's global mode is
653
+ # captured at source time into LOKI_CAVEMAN_USER_MODE before the default-off
654
+ # export clobbers CAVEMAN_DEFAULT_MODE. Unknown / empty user modes (rank -1) are
655
+ # ignored so a malformed value can never accidentally suppress activation.
656
+ loki_caveman_activate_env() {
657
+ loki_caveman_supported || return 0
658
+ loki_caveman_enabled || return 0
659
+ local level="${LOKI_CAVEMAN_LEVEL:-full}"
660
+ # Respect (never exceed) a user's explicitly-lower global level. A user who
661
+ # globally set CAVEMAN_DEFAULT_MODE=off opted OUT of compression entirely;
662
+ # honor that by activating nothing (empty -> bare claude invocation).
663
+ local user_mode="${LOKI_CAVEMAN_USER_MODE:-}"
664
+ if [ "$user_mode" = "off" ]; then
665
+ return 0
666
+ fi
667
+ if [ -n "$user_mode" ]; then
668
+ local user_rank level_rank
669
+ user_rank="$(_loki_caveman_level_rank "$user_mode")"
670
+ level_rank="$(_loki_caveman_level_rank "$level")"
671
+ # Only defer to the user when their mode is a recognized, lower level.
672
+ if [ "$user_rank" -ge 0 ] && [ "$level_rank" -ge 0 ] && [ "$user_rank" -lt "$level_rank" ]; then
673
+ level="$user_mode"
674
+ fi
675
+ fi
676
+ printf '%s' "$level"
677
+ }
678
+
679
+ # The suppression env VALUE for a parsed-output subcall: ALWAYS "off",
680
+ # UNCONDITIONALLY. Not gated on supported/enabled (see the design note above):
681
+ # it must hard-disable caveman on a trust-gate subcall even when a user has
682
+ # caveman globally on but LOKI_CAVEMAN=0, and it is a harmless no-op env value
683
+ # when caveman is absent. Every parsed-output call site uses this ONE helper so
684
+ # the carve-out is uniform:
685
+ # CAVEMAN_DEFAULT_MODE="$(loki_caveman_suppress_env)" claude ...
686
+ loki_caveman_suppress_env() {
687
+ printf '%s' "off"
688
+ }
689
+
690
+ # Idempotent on-demand bootstrap of caveman at the pinned version. Best-effort:
691
+ # installs once per machine, caches a marker under .loki/ so repeat runs are a
692
+ # no-op, degrades cleanly (run proceeds UNCOMPRESSED) with an honest stderr line
693
+ # if anything is missing or the upstream installer is unreachable. NEVER blocks
694
+ # or fails the run. Returns 0 if caveman is (now) installed, 1 on clean degrade.
695
+ #
696
+ # Opt out with LOKI_CAVEMAN_AUTO_BOOTSTRAP=0. Only attempts when provider==claude
697
+ # and the LOKI_CAVEMAN knob is on.
698
+ #
699
+ # GLOBAL SIDE EFFECT (disclosed): caveman installs GLOBALLY -- the upstream
700
+ # installer adds a SessionStart hook to ~/.claude/settings.json (or
701
+ # $CLAUDE_CONFIG_DIR) that affects EVERY Claude Code session on this machine, not
702
+ # only Loki runs. This is caveman's only install mode. The bootstrap therefore
703
+ # runs the upstream installer exactly as a user's own `curl|bash` would; we do
704
+ # not author or vendor any caveman file. The one-time stderr line below names
705
+ # this so the operator is never surprised.
706
+ #
707
+ # HARDENING: the npx call is forced non-interactive (--non-interactive, plus
708
+ # </dev/null so no stdin read can ever block) and time-bounded (timeout when
709
+ # available) so a stalled network or an unexpected prompt can never hang a user's
710
+ # first `loki start`. caveman's installer is already auto-non-interactive without
711
+ # a TTY, but we belt-and-suspenders it.
712
+ loki_caveman_bootstrap() {
713
+ [ "${LOKI_CAVEMAN:-1}" = "0" ] && return 1
714
+ [ "${LOKI_CAVEMAN_AUTO_BOOTSTRAP:-1}" = "0" ] && return 1
715
+ [ "${LOKI_PROVIDER:-claude}" = "claude" ] || return 1
716
+ # Already installed -> nothing to do.
717
+ if _loki_caveman_installed; then
718
+ return 0
719
+ fi
720
+ local ver="${LOKI_CAVEMAN_VERSION:-1.9.0}"
721
+ local marker_dir=".loki/state"
722
+ local marker="$marker_dir/caveman-bootstrap-${ver}.done"
723
+ # Cached attempt marker: do not re-attempt a failed install over and over
724
+ # within the same project tree (idempotent one-shot per pinned version).
725
+ if [ -f "$marker" ]; then
726
+ _loki_caveman_installed && return 0 || return 1
727
+ fi
728
+ if ! command -v node >/dev/null 2>&1 || ! command -v npx >/dev/null 2>&1; then
729
+ printf '%s\n' "[caveman] node>=18 + npx required to bootstrap; skipping (run proceeds uncompressed). Install Node or set LOKI_CAVEMAN=0 to silence." >&2
730
+ mkdir -p "$marker_dir" 2>/dev/null && : > "$marker" 2>/dev/null || true
731
+ return 1
732
+ fi
733
+ printf '%s\n' "[caveman] bootstrapping output-token compressor v${ver} (one-time, pinned). NOTE: caveman installs GLOBALLY (a Claude Code SessionStart hook in ~/.claude affecting every Claude Code session). Loki applies it only to free-form generation, NEVER to trust-gate subcalls. Opt out: LOKI_CAVEMAN=0." >&2
734
+ # Pin via the git tag (v-prefixed) on the npx ref AND CAVEMAN_REF so the
735
+ # downloaded hooks match the pinned release. Default install (no --all) wires
736
+ # the Claude Code hook for the detected `claude` CLI. A timeout backstops a
737
+ # network stall; </dev/null guarantees no interactive stdin read blocks.
738
+ local _cm_runner="npx"
739
+ if command -v timeout >/dev/null 2>&1; then
740
+ _cm_runner="timeout 120 npx"
741
+ fi
742
+ if CAVEMAN_REF="v${ver}" $_cm_runner -y "github:JuliusBrussee/caveman#v${ver}" -- --non-interactive >/dev/null 2>&1 </dev/null; then
743
+ mkdir -p "$marker_dir" 2>/dev/null && : > "$marker" 2>/dev/null || true
744
+ if _loki_caveman_installed; then
745
+ printf '%s\n' "[caveman] installed v${ver}." >&2
746
+ return 0
747
+ fi
748
+ fi
749
+ printf '%s\n' "[caveman] bootstrap unavailable (upstream unreachable, timed out, or install failed); run proceeds uncompressed." >&2
750
+ mkdir -p "$marker_dir" 2>/dev/null && : > "$marker" 2>/dev/null || true
751
+ return 1
752
+ }
753
+
485
754
  # ---------------------------------------------------------------------------
486
755
  # Session-continuity Phase 2 (GitHub #165) -- LOKI_RESUME_SESSION recovery resume
487
756
  #
@@ -250,7 +250,13 @@ loki_council_dispatch_agents() {
250
250
  local rc=0
251
251
  local stderr_log="$COUNCIL_STATE_DIR/votes/dispatch-stderr-${iteration}.log"
252
252
  mkdir -p "$(dirname "$stderr_log")" 2>/dev/null || true
253
- response=$(claude --dangerously-skip-permissions \
253
+ # caveman HARD-SUPPRESS (parsed output, v7.41.0): the response is parsed for
254
+ # findings[].vote against the JSON Schema. A globally-active caveman would
255
+ # compress/reword it and break the schema match or flip a vote. The tree-wide
256
+ # default-off export in claude-flags.sh (sourced above) already covers this;
257
+ # the inline prefix is belt-and-suspenders, self-documenting, and a no-op when
258
+ # caveman is absent.
259
+ response=$(CAVEMAN_DEFAULT_MODE=off claude --dangerously-skip-permissions \
254
260
  -p "$prompt" \
255
261
  --agents "$agents_json" \
256
262
  --json-schema "$schema_path" 2>"$stderr_log") || rc=$?
package/autonomy/run.sh CHANGED
@@ -3050,9 +3050,24 @@ spawn_worktree_session() {
3050
3050
  # Provider-specific invocation for parallel sessions
3051
3051
  case "${PROVIDER_NAME:-claude}" in
3052
3052
  claude)
3053
- claude --dangerously-skip-permissions \
3054
- -p "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
3055
- >> "$log_file" 2>&1 || _wt_exit=$?
3053
+ # caveman ACTIVATION (free-form): a parallel worktree dev stream
3054
+ # is free-form generation; its output goes to a log, not a parsed
3055
+ # sentinel. Activate compression at the configured level when
3056
+ # warranted (empty -> bare invocation, byte-identical to before).
3057
+ # Type-guarded; inlined per-invocation (not exported).
3058
+ local _loki_wt_cm=""
3059
+ if type loki_caveman_activate_env >/dev/null 2>&1; then
3060
+ _loki_wt_cm="$(loki_caveman_activate_env)"
3061
+ fi
3062
+ if [ -n "$_loki_wt_cm" ]; then
3063
+ CAVEMAN_DEFAULT_MODE="$_loki_wt_cm" claude --dangerously-skip-permissions \
3064
+ -p "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
3065
+ >> "$log_file" 2>&1 || _wt_exit=$?
3066
+ else
3067
+ claude --dangerously-skip-permissions \
3068
+ -p "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
3069
+ >> "$log_file" 2>&1 || _wt_exit=$?
3070
+ fi
3056
3071
  ;;
3057
3072
  codex)
3058
3073
  codex exec --full-auto --skip-git-repo-check \
@@ -3264,7 +3279,11 @@ Output ONLY the resolved file content with no conflict markers. No explanations.
3264
3279
  if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
3265
3280
  _cr_argv+=("--bare")
3266
3281
  fi
3267
- resolution=$(claude "${_cr_argv[@]}" -p "$conflict_prompt" --output-format text 2>/dev/null)
3282
+ # caveman HARD-SUPPRESS (parsed output): the output is captured as
3283
+ # the EXACT resolved file content (the shell writes it verbatim).
3284
+ # Compressing prose into the merged source would corrupt the file,
3285
+ # so disable caveman unconditionally here. No-op when absent.
3286
+ resolution=$(CAVEMAN_DEFAULT_MODE=off claude "${_cr_argv[@]}" -p "$conflict_prompt" --output-format text 2>/dev/null)
3268
3287
  ;;
3269
3288
  codex)
3270
3289
  resolution=$(codex exec --full-auto --skip-git-repo-check "$conflict_prompt" 2>/dev/null)
@@ -7912,6 +7931,15 @@ BUILD_PROMPT
7912
7931
  if type loki_review_allowlist_enabled >/dev/null 2>&1 && loki_review_allowlist_enabled; then
7913
7932
  _rv_argv+=("--allowedTools" "$(loki_review_allowlist)")
7914
7933
  fi
7934
+ # caveman HARD-SUPPRESS (parsed output): this is a trust-gate
7935
+ # subcall whose output is parsed for "^VERDICT:" + findings. A
7936
+ # globally-active caveman would compress/reword that line and
7937
+ # silently flip the verdict, so we UNCONDITIONALLY disable
7938
+ # caveman here with CAVEMAN_DEFAULT_MODE=off (the activate hook
7939
+ # then deletes its flag and emits nothing). Set inline, not via
7940
+ # the helper, so the carve-out holds even when the helper is
7941
+ # out of scope. No-op when caveman is absent.
7942
+ CAVEMAN_DEFAULT_MODE=off \
7915
7943
  claude "${_rv_argv[@]}" -p "$prompt_text" \
7916
7944
  --output-format text > "$review_output" 2>/dev/null
7917
7945
  ;;
@@ -8151,6 +8179,11 @@ ADVERSARIAL_EOF
8151
8179
  if type loki_review_allowlist_enabled >/dev/null 2>&1 && loki_review_allowlist_enabled; then
8152
8180
  _adv_argv+=("--allowedTools" "$(loki_review_allowlist)")
8153
8181
  fi
8182
+ # caveman HARD-SUPPRESS (parsed output): the adversarial probe's
8183
+ # output is parsed for findings/severity. Disable caveman
8184
+ # unconditionally (CAVEMAN_DEFAULT_MODE=off) so compression cannot
8185
+ # drop or reword a finding. No-op when caveman is absent.
8186
+ CAVEMAN_DEFAULT_MODE=off \
8154
8187
  claude "${_adv_argv[@]}" -p "$adversarial_prompt" \
8155
8188
  --output-format text > "$result_file" 2>/dev/null || true
8156
8189
  fi
@@ -10188,9 +10221,14 @@ ${_commits}"
10188
10221
  _ic_argv+=("--bare")
10189
10222
  fi
10190
10223
  _ic_argv+=("--model" "haiku")
10224
+ # caveman HARD-SUPPRESS (parsed output): the regen output is validated to be
10225
+ # Markdown (grep '^#') and written verbatim to USAGE.md. Compressed prose
10226
+ # would fail that check or produce caveman-style USAGE text, so disable
10227
+ # caveman unconditionally. Inlined on `claude` only (does not cross the pipe
10228
+ # to head). No-op when caveman is absent.
10191
10229
  local _ic_out
10192
10230
  _ic_out=$(printf '%s' "$_ic_prompt" \
10193
- | timeout 60 claude "${_ic_argv[@]}" -p - 2>/dev/null \
10231
+ | timeout 60 env CAVEMAN_DEFAULT_MODE=off claude "${_ic_argv[@]}" -p - 2>/dev/null \
10194
10232
  | head -200)
10195
10233
  # Sanity check: response must look like Markdown (starts with # or ##).
10196
10234
  if [ -z "$_ic_out" ] || ! printf '%s' "$_ic_out" | head -1 | grep -qE '^#'; then
@@ -13049,9 +13087,40 @@ except Exception as exc:
13049
13087
  # result-cost file under the correct iteration index.
13050
13088
  export LOKI_CURRENT_MODEL="$tier_param"
13051
13089
  export LOKI_ITERATION="$ITERATION_COUNT"
13090
+ # caveman ACTIVATION (free-form): the main RARV dev loop is
13091
+ # free-form generation, so we ask caveman to compress its OUTPUT
13092
+ # tokens at the configured level. Inlined as a per-invocation env
13093
+ # prefix (NOT exported) so it applies ONLY to `claude` (and the
13094
+ # SessionStart hook it spawns inherits it) and never bleeds into
13095
+ # later parsed subcalls. Empty when caveman is unsupported /
13096
+ # disabled / the legacy completion-prose match is active, in which
13097
+ # case the invocation is byte-identical to before. Type-guarded so
13098
+ # an older runtime without the helper degrades cleanly.
13099
+ local _loki_cm_level=""
13100
+ if type loki_caveman_activate_env >/dev/null 2>&1; then
13101
+ _loki_cm_level="$(loki_caveman_activate_env)"
13102
+ fi
13103
+ # Best-effort one-time bootstrap when activation is warranted but
13104
+ # caveman is not yet installed (idempotent, non-blocking, clean
13105
+ # degrade). The level stays usable next run even if this run is
13106
+ # uncompressed.
13107
+ if [ -n "$_loki_cm_level" ] && type loki_caveman_bootstrap >/dev/null 2>&1; then
13108
+ loki_caveman_bootstrap || true
13109
+ fi
13110
+ # NOTE: an EMPTY CAVEMAN_DEFAULT_MODE is NOT inert -- caveman's
13111
+ # getDefaultMode() treats empty as unset and falls back to the
13112
+ # user's global default (often "full"). So when activation is not
13113
+ # warranted we must NOT set the var at all (the bare branch),
13114
+ # keeping the invocation byte-identical to pre-caveman behavior.
13052
13115
  { \
13116
+ if [ -n "$_loki_cm_level" ]; then
13117
+ CAVEMAN_DEFAULT_MODE="$_loki_cm_level" \
13118
+ claude "${_loki_claude_argv[@]}" -p "$prompt" \
13119
+ --output-format stream-json --verbose 2>&1
13120
+ else
13053
13121
  claude "${_loki_claude_argv[@]}" -p "$prompt" \
13054
- --output-format stream-json --verbose 2>&1 | \
13122
+ --output-format stream-json --verbose 2>&1
13123
+ fi | \
13055
13124
  tee -a "$log_file" "$agent_log" "$iter_output" | \
13056
13125
  python3 -u -c '
13057
13126
  import sys
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.40.0"
10
+ __version__ = "7.41.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v7.40.0
5
+ **Version:** v7.41.0
6
6
 
7
7
  ---
8
8
 
@@ -30,6 +30,20 @@ setting any flag to `0`.
30
30
  in `loki status --json`).
31
31
  - See `skills/quality-gates.md` for full schema and reachability notes.
32
32
 
33
+ ### Output-token compressor (caveman, default-on, Claude-only)
34
+ Loki integrates [caveman](https://github.com/JuliusBrussee/caveman), an optional
35
+ Claude Code skill that compresses the model's OUTPUT tokens only (keeping all
36
+ technical substance). It activates on free-form generation (the main RARV dev
37
+ loop) and is HARD-SUPPRESSED on every trust-gate subcall (council votes, code
38
+ review verdict, evidence-related parses) so determinism is never affected.
39
+ - Claude-provider-only; runs are byte-identical on Codex / Cline / Aider.
40
+ - Default on; opt out with `LOKI_CAVEMAN=0`.
41
+ - Level: `LOKI_CAVEMAN_LEVEL` (default `full`; also `lite`, `ultra`, `wenyan*`).
42
+ - Pinned + vendor-less: `LOKI_CAVEMAN_VERSION` (default `1.9.0`); Loki bootstraps
43
+ the pinned version on demand (opt out `LOKI_CAVEMAN_AUTO_BOOTSTRAP=0`).
44
+ - Savings are output-token-only and bounded; Loki never quotes a dollar figure.
45
+ - See `skills/quality-gates.md` (Output-token compressor) for details.
46
+
33
47
  ### Earlier highlights still in scope
34
48
  - Bash-to-Bun runtime migration in progress (see `UPGRADING.md`)
35
49
  - Provider-agnostic runtime: Claude (full), Codex, Cline, Aider (no vendor lock-in)
@@ -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.40.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.41.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=CE2296A3A5523BAE64756E2164756E21
792
+ //# debugId=02B592B55ABCECB264756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.40.0'
60
+ __version__ = '7.41.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.40.0",
4
+ "version": "7.41.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",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "loki-mode",
4
4
  "displayName": "Loki Mode",
5
- "version": "7.40.0",
5
+ "version": "7.41.0",
6
6
  "description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 11 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
7
7
  "author": {
8
8
  "name": "Autonomi",
@@ -128,6 +128,39 @@ LOKI_CODE_INDEX_AUTOREINDEX=1 # auto incremental re-index of the semantic
128
128
  # Hybrid Codebase Search)
129
129
  ```
130
130
 
131
+ ## Output-token compressor (caveman, default-on, Claude-only)
132
+
133
+ [caveman](https://github.com/JuliusBrussee/caveman) is a Claude Code skill that
134
+ instructs the model to compress its OUTPUT tokens only (prose style), keeping all
135
+ technical substance. Loki ACTIVATES it on free-form generation (the main RARV dev
136
+ loop) and HARD-SUPPRESSES it on every parsed-output trust-gate subcall (council
137
+ votes, the code-review `^VERDICT:`, the adversarial probe, the merge-conflict
138
+ resolver, the USAGE.md regen). The suppression is by construction (one shared
139
+ helper sets `CAVEMAN_DEFAULT_MODE=off` on every parsed call site), so compression
140
+ can NEVER flip a verdict or completion decision.
141
+
142
+ Claude-provider-only: on Codex / Cline / Aider the run is byte-identical to
143
+ before. Vendor-less: Loki ships no copy of caveman and bootstraps the pinned
144
+ version on demand (idempotent, cached under `.loki/`). Savings are real but
145
+ bounded (output tokens only); there is no price API, so Loki discloses the
146
+ savings CLASS, never a dollar figure.
147
+
148
+ ```bash
149
+ LOKI_CAVEMAN=0 # opt out (default on). Disables activation; the
150
+ # parsed-subcall suppression still runs (it is a
151
+ # harmless no-op when caveman is absent).
152
+ LOKI_CAVEMAN_LEVEL=full # compression level: lite | full (default) |
153
+ # ultra | wenyan | wenyan-lite|full|ultra
154
+ LOKI_CAVEMAN_VERSION=1.9.0 # pinned caveman version (upgrade by bumping)
155
+ LOKI_CAVEMAN_AUTO_BOOTSTRAP=0 # disable the on-demand pinned install
156
+ ```
157
+
158
+ Note: when `LOKI_LEGACY_COMPLETION_MATCH=true` (the legacy prose-grep completion
159
+ path), main-loop activation is automatically disabled so compression cannot
160
+ mangle the prose completion-promise. The default completion path (the
161
+ `loki_complete_task` MCP tool / completion signal file) is immune to compression
162
+ and keeps caveman on.
163
+
131
164
  ## Verified-completion evidence gate (v7.19.1, default-on)
132
165
 
133
166
  The completion council will not accept a "done" claim without evidence. Before