loki-mode 7.41.0 → 7.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 11 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v7.41.0
6
+ # Loki Mode v7.41.1
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -398,4 +398,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
398
398
 
399
399
  ---
400
400
 
401
- **v7.41.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
401
+ **v7.41.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.41.0
1
+ 7.41.1
@@ -136,6 +136,121 @@ HEALTH_EOF
136
136
  mv "$tmp_file" "$_APP_RUNNER_DIR/health.json"
137
137
  }
138
138
 
139
+ # Re-derive a detection.json field (type/command) so we can rewrite it after a
140
+ # port reconcile without threading those values through globals. Echoes the raw
141
+ # string value (empty on miss). Mirrors the grep-based read style used by
142
+ # app_runner_status.
143
+ _read_detection_field() {
144
+ local field="$1"
145
+ [ -f "$_APP_RUNNER_DIR/detection.json" ] || return 0
146
+ grep -o "\"${field}\": *\"[^\"]*\"" "$_APP_RUNNER_DIR/detection.json" 2>/dev/null \
147
+ | head -1 | sed 's/.*"\([^"]*\)"$/\1/'
148
+ }
149
+
150
+ # Rewrite detection.json with the reconciled port, preserving type/command.
151
+ _rewrite_detection_port() {
152
+ local d_type d_command
153
+ d_type=$(_read_detection_field "type")
154
+ d_command=$(_read_detection_field "command")
155
+ [ -n "$d_type" ] || return 0
156
+ _write_detection "$d_type" "$d_command"
157
+ }
158
+
159
+ # Fix #2 (finding #597): reconcile the recorded port with the port the app
160
+ # ACTUALLY bound, using the listen line in app.log as the source of truth. This
161
+ # corrects the dashboard Live Preview even when the app ignores PORT and picks
162
+ # its own port. Bounded poll: returns as soon as a listen line is found, and
163
+ # never runs for docker (compose URLs come from published-port mapping) or when
164
+ # no port was recorded. Default window LOKI_APP_PORT_RECONCILE_SECS (default 12)
165
+ # at 0.5s intervals. On no match within the window the recorded port is kept (no
166
+ # regression). Stdout: nothing; mutates _APP_RUNNER_PORT / _APP_RUNNER_URL and
167
+ # rewrites state.json + detection.json only when the real port differs.
168
+ _app_runner_reconcile_port() {
169
+ [ "$_APP_RUNNER_IS_DOCKER" != true ] || return 0
170
+ [ -n "$_APP_RUNNER_PORT" ] && [ "$_APP_RUNNER_PORT" -gt 0 ] 2>/dev/null || return 0
171
+ local log_file="$_APP_RUNNER_DIR/app.log"
172
+
173
+ # Fast path: if the recorded port already serves HTTP, the app honored our
174
+ # chosen port (fix #1 worked) or otherwise bound it -- nothing to reconcile,
175
+ # and we avoid the poll latency entirely. Covers quiet-but-serving apps that
176
+ # never log a recognizable listen line.
177
+ if command -v curl >/dev/null 2>&1 && \
178
+ curl -sf -o /dev/null -m 2 "http://localhost:${_APP_RUNNER_PORT}/" 2>/dev/null; then
179
+ return 0
180
+ fi
181
+
182
+ local max_secs="${LOKI_APP_PORT_RECONCILE_SECS:-12}"
183
+ [[ "$max_secs" =~ ^[0-9]+$ ]] || max_secs=12
184
+ local max_iter=$(( max_secs * 2 ))
185
+ [ "$max_iter" -gt 0 ] || max_iter=1
186
+
187
+ local real_port="" iter=0
188
+ while [ "$iter" -lt "$max_iter" ]; do
189
+ if [ -f "$log_file" ]; then
190
+ real_port=$(_parse_listen_port "$log_file")
191
+ [ -n "$real_port" ] && break
192
+ fi
193
+ # Stop early if the process already died (failed start): nothing to wait for.
194
+ if [ -n "$_APP_RUNNER_PID" ] && ! kill -0 "$_APP_RUNNER_PID" 2>/dev/null; then
195
+ break
196
+ fi
197
+ sleep 0.5
198
+ iter=$(( iter + 1 ))
199
+ done
200
+
201
+ [ -n "$real_port" ] || return 0
202
+ if [ "$real_port" != "$_APP_RUNNER_PORT" ]; then
203
+ log_info "App Runner: reconciled port $_APP_RUNNER_PORT -> $real_port (from app.log listen line)"
204
+ _APP_RUNNER_PORT="$real_port"
205
+ _APP_RUNNER_URL="http://localhost:${real_port}"
206
+ _rewrite_detection_port
207
+ fi
208
+ return 0
209
+ }
210
+
211
+ # Parse the actual bound port from an app log file. Scans known listen-line
212
+ # shapes in priority order and returns the LAST (most recent) plausible port,
213
+ # tolerating ANSI color codes that dev servers emit. Validates 1-65535. Echoes
214
+ # the port or nothing.
215
+ _parse_listen_port() {
216
+ local file="$1"
217
+ [ -f "$file" ] || return 0
218
+ # Strip ANSI SGR sequences (\e[...m) so color-wrapped URLs still match.
219
+ local clean
220
+ clean=$(sed -E $'s/\x1b\\[[0-9;]*m//g' "$file" 2>/dev/null) || clean=$(cat "$file" 2>/dev/null)
221
+ [ -n "$clean" ] || return 0
222
+
223
+ local candidate=""
224
+ # 1) Explicit URL with a port: http://host:PORT (most reliable).
225
+ candidate=$(printf '%s\n' "$clean" \
226
+ | grep -oiE 'https?://[a-z0-9.\-]+:[0-9]{1,5}' \
227
+ | grep -oE ':[0-9]{1,5}' | tr -d ':' | tail -1)
228
+ # 2) A number anchored to the literal word "port": "port 8080", "port=3000",
229
+ # "port: 5000". This runs BEFORE the bare host:port scan so a clock-style
230
+ # timestamp on the same line (e.g. "12:30:45 ... port 8080") cannot win.
231
+ if [ -z "$candidate" ]; then
232
+ candidate=$(printf '%s\n' "$clean" \
233
+ | grep -ioE 'port[ =:]+[0-9]{1,5}' \
234
+ | grep -oE '[0-9]{1,5}' | tail -1)
235
+ fi
236
+ # 3) Keyword listen lines with a real host token before the colon:
237
+ # "localhost:5173", "0.0.0.0:8080", "127.0.0.1:3000". Requiring a letter
238
+ # or a dot immediately left of the colon excludes "HH:MM" timestamps,
239
+ # which have a digit there.
240
+ if [ -z "$candidate" ]; then
241
+ candidate=$(printf '%s\n' "$clean" \
242
+ | grep -iE 'listen|running on|ready|started|serving|server' \
243
+ | grep -oiE '[a-z.][a-z0-9.\-]*:[0-9]{1,5}' \
244
+ | grep -oE ':[0-9]{1,5}' | tr -d ':' | tail -1)
245
+ fi
246
+
247
+ [ -n "$candidate" ] || return 0
248
+ # Validate range 1-65535.
249
+ if [ "$candidate" -ge 1 ] 2>/dev/null && [ "$candidate" -le 65535 ] 2>/dev/null; then
250
+ printf '%s\n' "$candidate"
251
+ fi
252
+ }
253
+
139
254
  # Rotate app.log if it exceeds max lines
140
255
  _rotate_app_log() {
141
256
  local log_file="$_APP_RUNNER_DIR/app.log"
@@ -606,6 +721,21 @@ app_runner_start() {
606
721
  log_step "App Runner: starting application ($_APP_RUNNER_METHOD on port $_APP_RUNNER_PORT)..."
607
722
  _rotate_app_log
608
723
 
724
+ # Fix #1 (finding #597): pass Loki's chosen port to the app via the env so the
725
+ # app honors it instead of binding its own default (e.g. a Node app reading
726
+ # `process.env.PORT || 4000` would otherwise bind 4000 while Loki recorded the
727
+ # guessed 3000, leaving the dashboard Live Preview pointed at a dead port).
728
+ # We export PORT plus the common ecosystem aliases. An app that ignores these
729
+ # vars is unaffected; an ignored env var is harmless by definition. We do NOT
730
+ # set HOST/BIND -- changing the bind address can break apps. For docker (which
731
+ # gets its port via published-port mapping, not the child env) this is a no-op
732
+ # at the binary boundary, so we only export for the direct-exec path.
733
+ local _port_env_prefix=""
734
+ if [ "$_APP_RUNNER_IS_DOCKER" != true ] && \
735
+ [ -n "$_APP_RUNNER_PORT" ] && [ "$_APP_RUNNER_PORT" -gt 0 ] 2>/dev/null; then
736
+ _port_env_prefix="export PORT=$_APP_RUNNER_PORT HTTP_PORT=$_APP_RUNNER_PORT SERVER_PORT=$_APP_RUNNER_PORT APP_PORT=$_APP_RUNNER_PORT; "
737
+ fi
738
+
609
739
  # Start the process in a new process group
610
740
  if command -v setsid >/dev/null 2>&1; then
611
741
  _APP_RUNNER_HAS_SETSID=true
@@ -615,7 +745,7 @@ app_runner_start() {
615
745
  # Note: $_APP_RUNNER_METHOD has passed _validate_app_command (whitelist).
616
746
  # The `--` after `bash -lc` prevents flag injection if the assembled
617
747
  # script string ever begins with a `-`.
618
- (cd "$dir" && setsid bash -lc -- 'echo $$ > "'"$_pgid_file"'"; exec '"$_APP_RUNNER_METHOD" >> "$_APP_RUNNER_DIR/app.log" 2>&1) &
748
+ (cd "$dir" && setsid bash -lc -- "$_port_env_prefix"'echo $$ > "'"$_pgid_file"'"; exec '"$_APP_RUNNER_METHOD" >> "$_APP_RUNNER_DIR/app.log" 2>&1) &
619
749
  local _subshell_pid=$!
620
750
  # Wait briefly for the pgid file to appear, then read the real PGID
621
751
  local _pgid_wait=0
@@ -633,7 +763,7 @@ app_runner_start() {
633
763
  _APP_RUNNER_HAS_SETSID=false
634
764
  # Note: $_APP_RUNNER_METHOD has passed _validate_app_command (whitelist).
635
765
  # The `--` after `bash -lc` prevents flag injection.
636
- (cd "$dir" && bash -lc -- "$_APP_RUNNER_METHOD" >> "$_APP_RUNNER_DIR/app.log" 2>&1) &
766
+ (cd "$dir" && bash -lc -- "${_port_env_prefix}exec $_APP_RUNNER_METHOD" >> "$_APP_RUNNER_DIR/app.log" 2>&1) &
637
767
  _APP_RUNNER_PID=$!
638
768
  fi
639
769
  # Register with central PID registry if available
@@ -675,8 +805,13 @@ app_runner_start() {
675
805
  return 1
676
806
  fi
677
807
  elif kill -0 "$_APP_RUNNER_PID" 2>/dev/null; then
808
+ # Reconcile recorded port with the port the app actually bound (finding
809
+ # #597), so state.json / detection.json / the preview URL point at the
810
+ # live port even when the app ignored PORT. Mutates globals before the
811
+ # state write below. Bounded; no-op when the app honored the chosen port.
812
+ _app_runner_reconcile_port
678
813
  _write_app_state "running"
679
- log_info "App Runner: application started (PID: $_APP_RUNNER_PID)"
814
+ log_info "App Runner: application started (PID: $_APP_RUNNER_PID) on port $_APP_RUNNER_PORT"
680
815
  return 0
681
816
  else
682
817
  log_error "App Runner: application failed to start"
@@ -527,8 +527,22 @@ LOKI_CAVEMAN_VERSION="${LOKI_CAVEMAN_VERSION:-1.9.0}"
527
527
 
528
528
  # The compression level for free-form activation. Maps directly to caveman's
529
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.
530
+ # wenyan-full | wenyan-ultra. Never "off" here -- "off" is the suppression value,
531
+ # not an activation level.
532
+ #
533
+ # v7.x #593 -- INTELLIGENT AUTO-SELECTION (no new user knob): when the user does
534
+ # NOT set LOKI_CAVEMAN_LEVEL explicitly, the level is INFERRED per-invocation from
535
+ # the run's existing RARV-tier signal (see _loki_caveman_infer_level). When the
536
+ # user DOES set it, that value overrides the inference entirely (opt-out escape
537
+ # hatch). Capture set-ness BEFORE the ":-full" default clobbers it -- once the
538
+ # default fills the var, "user set full" and "defaulted to full" are
539
+ # indistinguishable, so the inference would silently never fire. ${var+set} is
540
+ # non-empty only when the var was genuinely set (even to empty). The "full"
541
+ # default is kept so the public var still reads "full" for external readers and
542
+ # so a child re-source re-derives USERSET correctly (unexported default).
543
+ if [ -z "${LOKI_CAVEMAN_LEVEL_USERSET+x}" ]; then
544
+ LOKI_CAVEMAN_LEVEL_USERSET="${LOKI_CAVEMAN_LEVEL+set}"
545
+ fi
532
546
  LOKI_CAVEMAN_LEVEL="${LOKI_CAVEMAN_LEVEL:-full}"
533
547
 
534
548
  # ---------- DEFAULT-SUPPRESS: off by construction, tree-wide ----------
@@ -563,6 +577,34 @@ fi
563
577
  export LOKI_CAVEMAN_USER_MODE
564
578
  export CAVEMAN_DEFAULT_MODE=off
565
579
 
580
+ # ---------- v7.x #593 intelligent compression-level inference ----------
581
+ # Infer the caveman compression level from the run's existing RARV-tier signal,
582
+ # so the level is DECIDED by inspecting the work rather than asked of the user.
583
+ # No new user input is introduced: the tier already drives effort/model selection
584
+ # (loki_effort_for_tier, get_rarv_tier). On the bash route the tier is read from
585
+ # LOKI_CURRENT_TIER (exported by run_autonomous each iteration); the TS mirror
586
+ # (cavemanActivateEnv) receives the same tier vocabulary (planning|development|
587
+ # fast) via call.tier, so both routes infer identically from the same signal.
588
+ #
589
+ # INFERENCE RULE (deterministic, conservative-for-accuracy):
590
+ # planning (Reason phase -- architecture / design / nuanced reasoning) -> lite
591
+ # development (Act / Reflect -- implementation, the prior default) -> full
592
+ # fast (Verify phase -- testing / validation, more routine) -> full
593
+ # unknown / empty tier -> full
594
+ # The auto ceiling is "full": inference NEVER selects ultra. ultra is reachable
595
+ # only via an explicit LOKI_CAVEMAN_LEVEL override (the opt-out escape hatch), so
596
+ # the autonomous path can never compress hard enough to lose technical nuance.
597
+ # "lite" on planning protects the highest-nuance output (architecture/design);
598
+ # everything else stays at the established "full" default. When the tier is
599
+ # unknown we pick the SAFER (established) "full", never something more aggressive.
600
+ _loki_caveman_infer_level() {
601
+ local tier="${1:-${LOKI_CURRENT_TIER:-}}"
602
+ case "$tier" in
603
+ planning) printf '%s' "lite" ;;
604
+ *) printf '%s' "full" ;;
605
+ esac
606
+ }
607
+
566
608
  # Rank a caveman mode by compression aggressiveness for the no-raise comparison.
567
609
  # Higher number = more aggressive (drops more nuance). "off" is the floor; unknown
568
610
  # or empty modes rank as -1 (treated as "no opinion", so they never win a min()).
@@ -656,7 +698,17 @@ loki_caveman_enabled() {
656
698
  loki_caveman_activate_env() {
657
699
  loki_caveman_supported || return 0
658
700
  loki_caveman_enabled || return 0
659
- local level="${LOKI_CAVEMAN_LEVEL:-full}"
701
+ # #593: the level is the EXPLICIT user value when LOKI_CAVEMAN_LEVEL was set
702
+ # (override / opt-out escape hatch), else the INFERRED level from the RARV
703
+ # tier. The no-raise guard below then runs unchanged on this base, so an
704
+ # explicit level is still lowered toward a user's lower global mode exactly
705
+ # as before -- "override" means override the inference, not the no-raise.
706
+ local level
707
+ if [ -n "${LOKI_CAVEMAN_LEVEL_USERSET:-}" ]; then
708
+ level="${LOKI_CAVEMAN_LEVEL:-full}"
709
+ else
710
+ level="$(_loki_caveman_infer_level)"
711
+ fi
660
712
  # Respect (never exceed) a user's explicitly-lower global level. A user who
661
713
  # globally set CAVEMAN_DEFAULT_MODE=off opted OUT of compression entirely;
662
714
  # honor that by activating nothing (empty -> bare claude invocation).
package/autonomy/loki CHANGED
@@ -77,6 +77,35 @@ if [ -f "$_LOKI_SCRIPT_DIR/provider-offer.sh" ]; then
77
77
  source "$_LOKI_SCRIPT_DIR/provider-offer.sh"
78
78
  fi
79
79
 
80
+ # caveman ACTIVATION level for FREE-FORM (narration) claude subcalls in this CLI
81
+ # (migration phase exec, migration docs-gen, heal execution). Prints the level to
82
+ # pass as CAVEMAN_DEFAULT_MODE when activation is warranted, else nothing. autonomy/
83
+ # loki does NOT source claude-flags.sh globally, so this lazily sources it on demand
84
+ # (same pattern as the ultrareview/workflows gates and autonomy/lib/voter-agents.sh).
85
+ # The gate (provider==claude, claude on PATH, opt-in, no legacy-completion-match,
86
+ # no-raise of a user's lower global level) lives entirely in loki_caveman_activate_env;
87
+ # this is just the lazy-source wrapper. Degrades to empty (bare invocation, byte-
88
+ # identical to pre-caveman) when the lib is missing or the helper is unavailable.
89
+ # NEVER use this for captured-output / parsed subcalls -- those HARD-SUPPRESS with
90
+ # CAVEMAN_DEFAULT_MODE=off (see _docs_invoke_provider and run.sh:3286).
91
+ _loki_caveman_narration_level() {
92
+ if ! declare -F loki_caveman_activate_env >/dev/null 2>&1; then
93
+ local _cm_lib=""
94
+ if [ -f "${_LOKI_SCRIPT_DIR}/lib/claude-flags.sh" ]; then
95
+ _cm_lib="${_LOKI_SCRIPT_DIR}/lib/claude-flags.sh"
96
+ elif [ -f "$(dirname "$0")/lib/claude-flags.sh" ]; then
97
+ _cm_lib="$(dirname "$0")/lib/claude-flags.sh"
98
+ fi
99
+ if [ -n "$_cm_lib" ]; then
100
+ # shellcheck source=autonomy/lib/claude-flags.sh
101
+ . "$_cm_lib" 2>/dev/null || true
102
+ fi
103
+ fi
104
+ if declare -F loki_caveman_activate_env >/dev/null 2>&1; then
105
+ loki_caveman_activate_env
106
+ fi
107
+ }
108
+
80
109
  # Quickstart guided interview (v7.29.0): provides cmd_quickstart and its
81
110
  # deterministic offline template matcher. Functions-only file (no top-level
82
111
  # command), sourced here so the dispatch case can call cmd_quickstart. Composes
@@ -11499,9 +11528,19 @@ Tasks:
11499
11528
  local phase_exit=0
11500
11529
  if [ -n "$phase_prompt" ]; then
11501
11530
  local provider_name="${LOKI_PROVIDER:-claude}"
11531
+ # caveman ACTIVATION (free-form narration): the phase exec writes its
11532
+ # deliverables via tools; stdout is piped to python and only DISPLAYED,
11533
+ # never captured/parsed for a verdict. So compress the narration tokens
11534
+ # at the configured level when warranted (mirrors run.sh:13167 main loop).
11535
+ # An EMPTY CAVEMAN_DEFAULT_MODE is NOT inert (caveman falls back to the
11536
+ # user global default), so when activation is not warranted we must NOT
11537
+ # set the var at all (bare branch), keeping the call byte-identical to
11538
+ # pre-caveman. No-op when caveman is absent.
11539
+ local _loki_mig_cm=""
11540
+ _loki_mig_cm="$(_loki_caveman_narration_level)"
11502
11541
  case "$provider_name" in
11503
11542
  claude)
11504
- { (cd "$codebase_path" && claude --dangerously-skip-permissions -p "$phase_prompt" --output-format stream-json --verbose 2>&1) | \
11543
+ { (cd "$codebase_path" && if [ -n "$_loki_mig_cm" ]; then CAVEMAN_DEFAULT_MODE="$_loki_mig_cm" claude --dangerously-skip-permissions -p "$phase_prompt" --output-format stream-json --verbose 2>&1; else claude --dangerously-skip-permissions -p "$phase_prompt" --output-format stream-json --verbose 2>&1; fi) | \
11505
11544
  while IFS= read -r line; do
11506
11545
  # Extract text from stream-json
11507
11546
  if echo "$line" | python3 -c "
@@ -11667,9 +11706,15 @@ IMPORTANT RULES:
11667
11706
 
11668
11707
  local doc_exit=0
11669
11708
  local provider_name="${LOKI_PROVIDER:-claude}"
11709
+ # caveman ACTIVATION (free-form narration): docs-gen writes the docs via
11710
+ # tools; stdout is piped to python and only DISPLAYED, never captured/parsed.
11711
+ # Compress narration at the configured level when warranted; empty -> bare
11712
+ # branch, byte-identical to pre-caveman (empty mode is NOT inert). No-op absent.
11713
+ local _loki_doc_cm=""
11714
+ _loki_doc_cm="$(_loki_caveman_narration_level)"
11670
11715
  case "$provider_name" in
11671
11716
  claude)
11672
- { (cd "$codebase_path" && claude --dangerously-skip-permissions -p "$doc_prompt" --output-format stream-json --verbose 2>&1) | \
11717
+ { (cd "$codebase_path" && if [ -n "$_loki_doc_cm" ]; then CAVEMAN_DEFAULT_MODE="$_loki_doc_cm" claude --dangerously-skip-permissions -p "$doc_prompt" --output-format stream-json --verbose 2>&1; else claude --dangerously-skip-permissions -p "$doc_prompt" --output-format stream-json --verbose 2>&1; fi) | \
11673
11718
  while IFS= read -r line; do
11674
11719
  if echo "$line" | python3 -c "
11675
11720
  import sys, json
@@ -12290,10 +12335,17 @@ Begin healing now. Follow the RARV cycle for each action."
12290
12335
 
12291
12336
  # Execute with the appropriate provider
12292
12337
  local heal_exit=0
12338
+ # caveman ACTIVATION (free-form narration): heal execution writes its artifacts
12339
+ # (.loki/healing/*, characterization tests) via tools; stdout is piped to python
12340
+ # and only DISPLAYED, never captured/parsed for a verdict. Compress narration at
12341
+ # the configured level when warranted; empty -> bare branch, byte-identical to
12342
+ # pre-caveman (empty mode is NOT inert). No-op when caveman is absent.
12343
+ local _loki_heal_cm=""
12344
+ _loki_heal_cm="$(_loki_caveman_narration_level)"
12293
12345
  case "$provider" in
12294
12346
  claude)
12295
12347
  local run_args=(--dangerously-skip-permissions -p "$heal_prompt" --output-format stream-json --verbose)
12296
- (cd "$codebase_path" && claude "${run_args[@]}" 2>&1) | \
12348
+ (cd "$codebase_path" && if [ -n "$_loki_heal_cm" ]; then CAVEMAN_DEFAULT_MODE="$_loki_heal_cm" claude "${run_args[@]}" 2>&1; else claude "${run_args[@]}" 2>&1; fi) | \
12297
12349
  while IFS= read -r line; do
12298
12350
  if echo "$line" | python3 -c "
12299
12351
  import sys, json
@@ -13719,8 +13771,11 @@ tokens_per_tier = {
13719
13771
  # row from a stale \$0.25/\$1.25 (Haiku 3.5) to the published Claude Haiku 4.5
13720
13772
  # price of \$1/\$5 (anthropic.com/news/claude-haiku-4-5), so all four rows now
13721
13773
  # match the three canonical model-keyed tables (run.sh pricing.json, run.sh
13722
- # check_budget_limit, dashboard _DEFAULT_PRICING) and a fable-forced quote
13723
- # honestly shows MORE than Opus, not less.
13774
+ # check_budget_limit, dashboard _DEFAULT_PRICING). The Fable row (\$10/\$50) is
13775
+ # kept as the reference price, but since v7.39.1 fable is unavailable at the API
13776
+ # and a fable pin RESOLVES to Opus everywhere -- so a fable-pinned run both
13777
+ # dispatches AND quotes Opus (\$5/\$25), matching the runner. The Fable price row
13778
+ # is only reachable as an explicit advisory reference, not via a session pin.
13724
13779
  pricing = {
13725
13780
  'Fable': {'input': 10.00, 'output': 50.00},
13726
13781
  'Opus': {'input': 5.00, 'output': 25.00},
@@ -23691,7 +23746,16 @@ _docs_invoke_provider() {
23691
23746
 
23692
23747
  case "$provider" in
23693
23748
  claude)
23694
- result=$($t_prefix claude -p "$prompt" 2>/dev/null) || exit_code=$?
23749
+ # caveman HARD-SUPPRESS (captured deliverable): the claude stdout IS
23750
+ # the generated doc -- it is captured here and written verbatim to a
23751
+ # markdown file by both callers (loki:23869, loki:24239). A globally
23752
+ # active caveman would compress this stdout, corrupting the generated
23753
+ # CLAUDE.md / DECISIONS.md / README into caveman-speak. So disable
23754
+ # caveman unconditionally on this site (mirrors run.sh:3286, the
23755
+ # structurally identical captured `claude -p` resolution subcall).
23756
+ # env-prefix form because of the optional $t_prefix timeout wrapper.
23757
+ # No-op when caveman is absent.
23758
+ result=$($t_prefix env CAVEMAN_DEFAULT_MODE=off claude -p "$prompt" 2>/dev/null) || exit_code=$?
23695
23759
  ;;
23696
23760
  codex)
23697
23761
  result=$($t_prefix codex exec --full-auto "$prompt" 2>/dev/null) || exit_code=$?