loki-mode 9.50.2 → 9.50.3

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, 8 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 v9.50.2
6
+ # Loki Mode v9.50.3
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -470,4 +470,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
470
470
 
471
471
  ---
472
472
 
473
- **v9.50.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
473
+ **v9.50.3 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 9.50.2
1
+ 9.50.3
@@ -1536,11 +1536,13 @@ def _build_proof(args, loki_dir, target_dir, repo_root):
1536
1536
  "honesty": honesty,
1537
1537
  }
1538
1538
 
1539
- # Top-level mirror for the intervention axis. trust_trajectory.py:145 already
1540
- # reads proof["interventions"] and documents that no writer exists yet; this
1541
- # is that writer. Mirrored (not moved) for the same back-compat reason the
1542
- # other flat keys are mirrored. Only ever set when actually measured, so the
1543
- # axis stays honestly "unavailable" rather than showing a fabricated zero.
1539
+ # Top-level mirror for the intervention axis. _interventions_value in
1540
+ # trust_trajectory.py reads proof["interventions"]; the counter itself is
1541
+ # written by handle_pause in autonomy/run.sh, which increments
1542
+ # .loki/state/interventions.json once per blocking pause. Mirrored (not
1543
+ # moved) for the same back-compat reason the other flat keys are mirrored.
1544
+ # Only ever set when actually measured, so the axis stays honestly
1545
+ # "unavailable" rather than showing a fabricated zero.
1544
1546
  if isinstance(journey, dict) and isinstance(journey.get("interventions"), int):
1545
1547
  proof["interventions"] = journey["interventions"]
1546
1548
 
package/autonomy/run.sh CHANGED
@@ -4332,6 +4332,63 @@ else:
4332
4332
  ' 2>/dev/null || true
4333
4333
  }
4334
4334
 
4335
+ # _loki_receipt_facts <loki_dir>
4336
+ #
4337
+ # Echo three tab-separated facts about THIS run's Evidence Receipt, or nothing
4338
+ # at all: "<run_id>\t<proof_dir>\t<headline>".
4339
+ #
4340
+ # WHY THIS EXISTS (#209). The receipt is generated automatically and opt-OUT
4341
+ # (LOKI_PROOF defaults to 1), but the only place its location ever reached a
4342
+ # user was the TTFV first-run block, which fires solely on a zero-config first
4343
+ # run. A user who does not already know `loki proof` exists could therefore
4344
+ # finish a successful run without ever learning a checkable receipt was written.
4345
+ #
4346
+ # HONESTY RULES, deliberately fail-silent:
4347
+ # - Resolved from the PERSISTED pointer (.loki/state/last-proof-id.txt),
4348
+ # never newest-by-mtime, so it names the run the generator actually wrote.
4349
+ # - Emits NOTHING unless the pointer, the proof dir, and proof.json all exist.
4350
+ # the summary builder (build_completion_summary) is itself invoked mid-pause
4351
+ # before the teardown receipt on the success path, where no receipt exists
4352
+ # yet -- printing a path there would be a promise, not a fact.
4353
+ # - The headline is honesty.headline, which the generator computes
4354
+ # DETERMINISTICALLY from recorded facts (VERIFIED / VERIFIED WITH GAPS /
4355
+ # NOT VERIFIED). It is never the council verdict, which the generator
4356
+ # itself labels "AI judgment, not deterministic proof". A missing or
4357
+ # unreadable headline yields an empty third field, and the callers then
4358
+ # print the path without a verdict rather than inventing one.
4359
+ # Best-effort: never fails a run.
4360
+ _loki_receipt_facts() {
4361
+ local loki_dir="${1:-}"
4362
+ [ -n "$loki_dir" ] || return 0
4363
+ local id_file="$loki_dir/state/last-proof-id.txt"
4364
+ [ -s "$id_file" ] || return 0
4365
+ local rid=""
4366
+ rid="$(cat "$id_file" 2>/dev/null || true)"
4367
+ # Confine the run id to the alphabet the generator actually mints. The
4368
+ # headline is sanitized below; the id was not, so an interior newline or a
4369
+ # tab in the pointer split the tab-separated record and handed the consumers
4370
+ # a field boundary that is not there. Reject rather than repair: a pointer
4371
+ # outside this alphabet is corrupt, and a repaired id would name a directory
4372
+ # that does not exist.
4373
+ case "$rid" in
4374
+ ''|*[!A-Za-z0-9._-]*) return 0 ;;
4375
+ esac
4376
+ [ -n "$rid" ] || return 0
4377
+ local pj="$loki_dir/proofs/$rid/proof.json"
4378
+ [ -f "$pj" ] || return 0
4379
+ local headline=""
4380
+ headline="$(python3 -c "
4381
+ import json, sys
4382
+ try:
4383
+ d = json.load(open(sys.argv[1]))
4384
+ h = (d.get('honesty') or {}).get('headline') or ''
4385
+ print(str(h).replace('\t', ' ').replace('\n', ' ').strip())
4386
+ except Exception:
4387
+ print('')" "$pj" 2>/dev/null || true)"
4388
+ printf '%s\t%s\t%s\n' "$rid" "$loki_dir/proofs/$rid" "$headline"
4389
+ return 0
4390
+ }
4391
+
4335
4392
  build_completion_summary() {
4336
4393
  local outcome="${1:-complete}"
4337
4394
  local loki_dir="${TARGET_DIR:-.}/.loki"
@@ -4867,6 +4924,49 @@ except Exception:
4867
4924
  echo "Resolve those in the spec, then re-run."
4868
4925
  ;;
4869
4926
  esac
4927
+
4928
+ # Evidence Receipt (#209). The receipt is written automatically on every
4929
+ # run, success or failure, but its location only ever reached the user
4930
+ # through the zero-config first-run block. State it here, where every
4931
+ # terminal outcome passes, so a user who has never heard of `loki proof`
4932
+ # still learns a checkable receipt exists and how to re-check it.
4933
+ # Prints NOTHING when no receipt has been written yet (mid-pause, or the
4934
+ # first summary on the success path): a path that does not exist is a
4935
+ # promise, not a fact. The verdict line is the generator's deterministic
4936
+ # headline, omitted entirely when unreadable rather than invented.
4937
+ _cs_receipt="$(_loki_receipt_facts "$loki_dir" 2>/dev/null || true)"
4938
+ if [ -n "$_cs_receipt" ]; then
4939
+ _cs_rid="${_cs_receipt%% *}"
4940
+ _cs_rest="${_cs_receipt#* }"
4941
+ _cs_dir="${_cs_rest%% *}"
4942
+ _cs_headline="${_cs_rest#* }"
4943
+ echo ""
4944
+ echo "Evidence Receipt (this run):"
4945
+ if [ -n "$_cs_headline" ]; then
4946
+ echo " Verdict: $_cs_headline"
4947
+ fi
4948
+ # Gated on the PAGE, not the receipt. The helper gates on
4949
+ # proof.json, but the generator writes proof.json and THEN renders
4950
+ # index.html unwrapped (proof-generator.py), so a render failure
4951
+ # leaves the data present and the page absent. Naming a page that
4952
+ # is not there is the same class proof.ts:118 refuses ("worse than
4953
+ # an absent one") and `loki proof open` refuses with "Proof page
4954
+ # not found". Gate only this line: proof verify reads proof.json,
4955
+ # so the id and the re-check below still work without the page.
4956
+ if [ -f "$_cs_dir/index.html" ]; then
4957
+ echo " Receipt: $_cs_dir/index.html"
4958
+ fi
4959
+ echo " Re-check it yourself, do not take our word for it:"
4960
+ # Cwd-independent: the receipt is resolved from the run's target
4961
+ # dir, which need not be where the user is standing. A bare
4962
+ # `loki proof verify <id>` then fails for exactly the user we just
4963
+ # told to check our work.
4964
+ if [ "$(cd "$loki_dir/.." 2>/dev/null && pwd -P)" = "$(pwd -P)" ]; then
4965
+ echo " loki proof verify $_cs_rid"
4966
+ else
4967
+ echo " (cd $(cd "$loki_dir/.." 2>/dev/null && pwd -P) && loki proof verify $_cs_rid)"
4968
+ fi
4969
+ fi
4870
4970
  } > "$loki_dir/COMPLETION.txt" 2>/dev/null || true
4871
4971
 
4872
4972
  # ---- Durable machine-readable file: .loki/state/completion.json -----------
@@ -5199,6 +5299,20 @@ except Exception:
5199
5299
  echo -e "${GREEN}|${NC}"
5200
5300
  echo -e "${GREEN}|${NC} ${DIM}Review the work:${NC}"
5201
5301
  echo -e "${GREEN}|${NC} ${_review}"
5302
+ # NO Evidence Receipt line here, deliberately. The card renders from inside
5303
+ # emit_completion_summary, which is reached only from within run_autonomous
5304
+ # -- thousands of lines BEFORE the teardown that generates this run's
5305
+ # receipt. Announcing here would therefore be silent on a normal success
5306
+ # run (no proof exists yet), and on a SECOND run in the same directory it
5307
+ # would have been actively wrong before #211: .loki/state/last-proof-id.txt
5308
+ # was never cleared at run start, so the card would read the PREVIOUS run's
5309
+ # pointer and print that receipt, with that run's verdict, as though it
5310
+ # described this run. The pointer is now cleared during run init (search
5311
+ # "Same reasoning for the proof pointer"), which closes the cross-run leak,
5312
+ # but this site stays silent anyway: it renders thousands of lines before
5313
+ # THIS run's receipt exists, so it would print nothing on a normal success
5314
+ # run. The announcement lives at the teardown instead, after the final
5315
+ # generate_proof_of_run, where the pointer is guaranteed current.
5202
5316
  echo -e "${GREEN}+================================================================+${NC}"
5203
5317
  echo ""
5204
5318
  return 0
@@ -6529,6 +6643,15 @@ init_loki_dir() {
6529
6643
  mkdir -p .loki/metrics/efficiency
6530
6644
  # Clear stale metrics from previous sessions so loki metrics shows current run data (#75)
6531
6645
  rm -f .loki/metrics/efficiency/iteration-*.json 2>/dev/null || true
6646
+ # Same reasoning for the proof pointer (#211). .loki/state/last-proof-id.txt
6647
+ # is written by generate_proof_of_run and was never cleared, so a run that
6648
+ # died before generating a proof left the PREVIOUS run's id behind. Every
6649
+ # reader then resolved a receipt describing different work, and
6650
+ # the summary builder (build_completion_summary) would print it under the
6651
+ # literal heading "Evidence
6652
+ # Receipt (this run):". A receipt naming the wrong run is worse than no
6653
+ # receipt: absence reads as "no data", a stale one reads as evidence.
6654
+ rm -f .loki/state/last-proof-id.txt 2>/dev/null || true
6532
6655
  mkdir -p .loki/rules
6533
6656
  mkdir -p .loki/signals
6534
6657
 
@@ -25728,18 +25851,45 @@ except Exception:
25728
25851
 
25729
25852
  log_header "Execution Paused"
25730
25853
  echo ""
25731
- log_info "To resume: Remove .loki/PAUSE or press Enter"
25854
+ # The keypress half of this line is only true on an interactive terminal.
25855
+ # Off a TTY (--bg, a container, a CI job) no key can be read, so advertising
25856
+ # it there tells the operator to do something that cannot work.
25857
+ if [ -t 0 ]; then
25858
+ log_info "To resume: Remove .loki/PAUSE or press Enter"
25859
+ else
25860
+ log_info "To resume: Remove .loki/PAUSE (no TTY: keypress resume unavailable)"
25861
+ fi
25732
25862
  log_info "To add instructions: echo 'your instructions' > .loki/HUMAN_INPUT.md"
25733
25863
  log_info "To stop completely: touch .loki/STOP"
25734
25864
  echo ""
25735
25865
 
25736
- # Create resume instructions file
25866
+ # Create resume instructions file.
25867
+ #
25868
+ # The resume line is built OUTSIDE the heredoc because the heredoc is quoted
25869
+ # (<< 'EOF') and must stay that way: its body contains `rm .loki/PAUSE` and
25870
+ # `touch .loki/STOP` in backticks, so unquoting to interpolate a variable
25871
+ # would execute them and delete the PAUSE file this function just wrote.
25872
+ #
25873
+ # This file is the surface a NON-INTERACTIVE operator actually reads (--bg,
25874
+ # a container, a CI job), and it was the last place still promising a
25875
+ # keypress that cannot arrive there -- the same defect the console banner
25876
+ # above already fixed. The no-TTY wording is byte-identical to that banner's
25877
+ # so one grep spans both surfaces and any future divergence is visible.
25878
+ local _resume_line
25879
+ if [ -t 0 ]; then
25880
+ _resume_line='1. **Resume**: Press Enter in terminal or `rm .loki/PAUSE`'
25881
+ else
25882
+ _resume_line='1. **Resume**: `rm .loki/PAUSE` (no TTY: keypress resume unavailable)'
25883
+ fi
25884
+
25737
25885
  cat > "$loki_dir/PAUSED.md" << 'EOF'
25738
25886
  # Loki Mode - Paused
25739
25887
 
25740
25888
  Execution is currently paused. Options:
25741
25889
 
25742
- 1. **Resume**: Press Enter in terminal or `rm .loki/PAUSE`
25890
+ EOF
25891
+ printf '%s\n' "$_resume_line" >> "$loki_dir/PAUSED.md"
25892
+ cat >> "$loki_dir/PAUSED.md" << 'EOF'
25743
25893
  2. **Add Instructions**: `echo "Focus on fixing the login bug" > .loki/HUMAN_INPUT.md`
25744
25894
  3. **Stop**: `touch .loki/STOP`
25745
25895
 
@@ -25805,8 +25955,31 @@ except Exception:
25805
25955
  break
25806
25956
  fi
25807
25957
 
25808
- # Check for any key press (non-blocking)
25809
- if read -t 1 -n 1 2>/dev/null; then
25958
+ # Check for any key press (non-blocking). GATED ON AN INTERACTIVE STDIN
25959
+ # ([ -t 0 ], the established idiom in this file) because off a TTY this
25960
+ # arm is not merely useless, it is wrong in BOTH directions:
25961
+ #
25962
+ # 1. stdin is /dev/null (--bg, a container, a CI job): the read can
25963
+ # never succeed, so the loop spins on `sleep 1` forever with nobody
25964
+ # able to press anything. That is the reported hang.
25965
+ # 2. stdin is a PIPE OR FILE THAT HAS BYTES (stdin inherited from a
25966
+ # parent, a heredoc, `< somefile`): the read SUCCEEDS on the first
25967
+ # stray byte and the next line deletes .loki/PAUSE. A gate
25968
+ # escalation that paused at GATE_PAUSE_LIMIT is then silently
25969
+ # resumed by data nobody typed -- a false resume, which is worse
25970
+ # than the hang because the run continues past a blocking gate.
25971
+ #
25972
+ # Both were reproduced directly: with bytes on stdin `read -t 1 -n 1`
25973
+ # returns 0, with /dev/null it returns non-zero.
25974
+ #
25975
+ # The human escape path is UNCHANGED. The STOP and PAUSE-removal checks
25976
+ # above are file-based, run every second, and are what the dashboard,
25977
+ # the CLI, and `rm .loki/PAUSE` already use -- so a non-interactive
25978
+ # operator keeps every way out they had. Only the keypress, which that
25979
+ # operator never had, is skipped. On a real TTY this is byte-identical.
25980
+ # No timeout is imposed: a bounded wait would invent a new terminal
25981
+ # outcome and could fail a legitimate long human pause.
25982
+ if [ -t 0 ] && read -t 1 -n 1 2>/dev/null; then
25810
25983
  rm -f "$loki_dir/PAUSE"
25811
25984
  PAUSED=false
25812
25985
  break
@@ -26980,6 +27153,56 @@ except Exception:
26980
27153
  generate_proof_of_run "$result" || true
26981
27154
  fi
26982
27155
 
27156
+ # Evidence Receipt (#209): tell the user, on screen, that a checkable
27157
+ # receipt exists and how to re-check it.
27158
+ #
27159
+ # POSITION IS LOAD-BEARING. This sits immediately after the FINAL
27160
+ # generate_proof_of_run, which is the only point where the receipt for THIS
27161
+ # run is guaranteed written and .loki/state/last-proof-id.txt is guaranteed
27162
+ # to point at it. Every earlier surface is either too early (the completion
27163
+ # card renders from inside run_autonomous, long before any proof exists) or
27164
+ # unreachable to a foreground user (COMPLETION.txt self-heals here, but only
27165
+ # a --bg launch is ever told to read it). Before #211 the pointer was never
27166
+ # cleared at run start, so announcing from an earlier site printed the
27167
+ # PREVIOUS run's receipt and verdict on a second run in the same directory.
27168
+ # Run init now clears it (search "Same reasoning for the proof pointer"), so
27169
+ # a stale pointer no longer survives into a new run; the position still
27170
+ # matters because an earlier site is simply too early for THIS run's proof.
27171
+ #
27172
+ # TTY-gated the same way print_ttfv_next_steps is above: machine output and
27173
+ # --bg stay byte-identical, and those readers already get the same facts
27174
+ # from COMPLETION.txt. Fail-silent and best-effort: prints nothing at all
27175
+ # when no receipt was written (LOKI_PROOF=0, or generation failed), and
27176
+ # never fails the run.
27177
+ if [ -t 1 ] && [ "${BACKGROUND_MODE:-false}" != "true" ]; then
27178
+ _rcpt="$(_loki_receipt_facts "${TARGET_DIR:-.}/.loki" 2>/dev/null || true)"
27179
+ if [ -n "${_rcpt:-}" ]; then
27180
+ _rcpt_id="${_rcpt%% *}"
27181
+ _rcpt_rest="${_rcpt#* }"
27182
+ _rcpt_dir="${_rcpt_rest%% *}"
27183
+ _rcpt_headline="${_rcpt_rest#* }"
27184
+ echo ""
27185
+ if [ -n "$_rcpt_headline" ]; then
27186
+ echo "Evidence Receipt for this run: $_rcpt_headline"
27187
+ else
27188
+ echo "Evidence Receipt for this run:"
27189
+ fi
27190
+ # Gated on the PAGE existing -- see the COMPLETION.txt site above.
27191
+ if [ -f "$_rcpt_dir/index.html" ]; then
27192
+ echo " $_rcpt_dir/index.html"
27193
+ fi
27194
+ echo " Re-check it yourself, do not take our word for it:"
27195
+ # Cwd-independent, same reasoning as the COMPLETION.txt site.
27196
+ _rcpt_root="$(cd "${TARGET_DIR:-.}" 2>/dev/null && pwd -P)"
27197
+ if [ "$_rcpt_root" = "$(pwd -P)" ]; then
27198
+ echo " loki proof verify $_rcpt_id"
27199
+ else
27200
+ echo " (cd $_rcpt_root && loki proof verify $_rcpt_id)"
27201
+ fi
27202
+ echo ""
27203
+ fi
27204
+ fi
27205
+
26983
27206
  # Close the teardown window here rather than after cleanup: everything below
26984
27207
  # is process reaping and file removal, while everything above is the work a
26985
27208
  # user waits on (commit, PR, summary, proof). Emitting before cleanup also
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "9.50.2"
10
+ __version__ = "9.50.3"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try: