loki-mode 9.75.0 → 9.76.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.
Files changed (75) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/completion-council.sh +24 -6
  4. package/autonomy/lib/proof-check.sh +43 -8
  5. package/autonomy/lib/voter-agents.sh +42 -5
  6. package/autonomy/loki +3 -0
  7. package/autonomy/prd-checklist.sh +77 -16
  8. package/autonomy/run.sh +38 -3
  9. package/dashboard/__init__.py +1 -1
  10. package/docs/INSTALLATION.md +1 -1
  11. package/docs/v10/ARCHITECT-CUTS.md +359 -322
  12. package/docs/v10/BOARD.md +57 -37
  13. package/docs/v10/GUARDS.md +32 -21
  14. package/loki-ts/dist/loki.js +341 -338
  15. package/mcp/__init__.py +1 -1
  16. package/package.json +1 -1
  17. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  18. package/web-app/dist/assets/{AdminPage-CMGkmNnt.js → AdminPage-Bw8oPnII.js} +1 -1
  19. package/web-app/dist/assets/{Avatar-9mn8sJ-2.js → Avatar-BwEtc3RH.js} +1 -1
  20. package/web-app/dist/assets/{Badge-mSFLvvRX.js → Badge-C_-lfbUl.js} +1 -1
  21. package/web-app/dist/assets/{Button-B1v4CSkD.js → Button-BdDGKKeF.js} +1 -1
  22. package/web-app/dist/assets/CockpitPage-Dx9d4fEm.js +47 -0
  23. package/web-app/dist/assets/{ComparePage-C1BXuolM.js → ComparePage-BKtAhhIJ.js} +1 -1
  24. package/web-app/dist/assets/{ErrorBoundary-C2sJyBhb.js → ErrorBoundary-BuG16GpG.js} +1 -1
  25. package/web-app/dist/assets/{EvidenceReceiptPanel-a1irlBPD.js → EvidenceReceiptPanel-NATnqxKs.js} +1 -1
  26. package/web-app/dist/assets/{GitHubIssuesPanel-zM-WRxYB.js → GitHubIssuesPanel-BgOwdOpj.js} +1 -1
  27. package/web-app/dist/assets/{GitHubPRsPanel-DvRa5Rxq.js → GitHubPRsPanel-CBJyHI1c.js} +1 -1
  28. package/web-app/dist/assets/{HomePage-CNwRexp5.js → HomePage-BHj17Epq.js} +2 -2
  29. package/web-app/dist/assets/{LoginPage-HKogNiEX.js → LoginPage-Dig4H-HO.js} +1 -1
  30. package/web-app/dist/assets/{MagicPage-u1mvdkUd.js → MagicPage-w6WJ35PH.js} +1 -1
  31. package/web-app/dist/assets/{MetricsPage-CThdMJb5.js → MetricsPage-CvjSUOWn.js} +1 -1
  32. package/web-app/dist/assets/{NotFoundPage-B1v3lELS.js → NotFoundPage-PAcYmGkU.js} +1 -1
  33. package/web-app/dist/assets/{ProjectPage-hDvSsdLF.js → ProjectPage-CBkT5JyW.js} +3 -3
  34. package/web-app/dist/assets/{ProjectsPage-CRAegSM6.js → ProjectsPage-Cs6yuWEU.js} +1 -1
  35. package/web-app/dist/assets/{SettingsPage-Dt90bFxo.js → SettingsPage-BU9F40Yn.js} +1 -1
  36. package/web-app/dist/assets/{ShowcasePage-C5a9l8u_.js → ShowcasePage-jCNABTeT.js} +1 -1
  37. package/web-app/dist/assets/{SystemSettingsPage-DtqsDXbx.js → SystemSettingsPage-Crmc-ryL.js} +1 -1
  38. package/web-app/dist/assets/{TeamsPage-CooeoWxR.js → TeamsPage-DN8W-9m5.js} +1 -1
  39. package/web-app/dist/assets/{TemplatesPage-CJhTxWrw.js → TemplatesPage-WvaXwZIl.js} +1 -1
  40. package/web-app/dist/assets/{TerminalOutput-DvjfbDxb.js → TerminalOutput-Bf_do2tO.js} +1 -1
  41. package/web-app/dist/assets/{activity-DK6KBQKA.js → activity-CMl9JvHl.js} +1 -1
  42. package/web-app/dist/assets/{bell-Cd8tQpN6.js → bell-CnjqTAUX.js} +1 -1
  43. package/web-app/dist/assets/{bot-Y30w8rML.js → bot-BBprDFVS.js} +1 -1
  44. package/web-app/dist/assets/{check-B5eUl1j9.js → check-DW9Hq4y4.js} +1 -1
  45. package/web-app/dist/assets/{chevron-left-qyvpFqze.js → chevron-left-DC9pMI2E.js} +1 -1
  46. package/web-app/dist/assets/{circle-alert-DXtjVPDB.js → circle-alert-BJoll9YV.js} +1 -1
  47. package/web-app/dist/assets/{clock-dqQis2Q6.js → clock-BdHhRTHO.js} +1 -1
  48. package/web-app/dist/assets/{cloud-CzTURYO9.js → cloud-C54B7EO9.js} +1 -1
  49. package/web-app/dist/assets/{code-xml-DLbw1HTk.js → code-xml-DwQK2vpV.js} +1 -1
  50. package/web-app/dist/assets/{database-BIj38v85.js → database-DsWVFir7.js} +1 -1
  51. package/web-app/dist/assets/{dollar-sign-9bVcHIkD.js → dollar-sign-DuRPNc2M.js} +1 -1
  52. package/web-app/dist/assets/{file-code-corner-D57x6xD1.js → file-code-corner-C4j2P44-.js} +1 -1
  53. package/web-app/dist/assets/{file-plus-BXOrz3T5.js → file-plus-rnI2RZjF.js} +1 -1
  54. package/web-app/dist/assets/{globe-Cnq8h4Gt.js → globe-BbBhsaS5.js} +1 -1
  55. package/web-app/dist/assets/{hammer-iiLGlF28.js → hammer-BxcycH3E.js} +1 -1
  56. package/web-app/dist/assets/{index-uGGZ1Ij3.js → index-xGj3noKA.js} +2 -2
  57. package/web-app/dist/assets/{layers-gcGxxNEN.js → layers-CcAOhZeY.js} +1 -1
  58. package/web-app/dist/assets/{loader-circle-B4Ad11xq.js → loader-circle-DN_Xjd5e.js} +1 -1
  59. package/web-app/dist/assets/{lock-DvQNG5RI.js → lock-Dn8AMO-D.js} +1 -1
  60. package/web-app/dist/assets/{package-BtQBNbq2.js → package-uroTpCjn.js} +1 -1
  61. package/web-app/dist/assets/{plus-DKLiMXBZ.js → plus-CYJAEX02.js} +1 -1
  62. package/web-app/dist/assets/{refresh-cw-B4Gu8e2m.js → refresh-cw-BUGsMChj.js} +1 -1
  63. package/web-app/dist/assets/{rotate-ccw-DATSPLYS.js → rotate-ccw-C_4D-XGl.js} +1 -1
  64. package/web-app/dist/assets/{scroll-text-GA8EtlY4.js → scroll-text-DrAJG0H3.js} +1 -1
  65. package/web-app/dist/assets/{server-DT_3kM4V.js → server-Di6lHsM9.js} +1 -1
  66. package/web-app/dist/assets/{shield-alert-BEaqZEhO.js → shield-alert-DH1bNjkD.js} +1 -1
  67. package/web-app/dist/assets/{thumbs-up-DVvA-Fas.js → thumbs-up-Enk3ppj_.js} +1 -1
  68. package/web-app/dist/assets/{trash-2-BCyT5A9N.js → trash-2-CezqQT3x.js} +1 -1
  69. package/web-app/dist/assets/{trending-down-BLcNt39o.js → trending-down-B_ChI0U7.js} +1 -1
  70. package/web-app/dist/assets/{trending-up-BqfBp13A.js → trending-up-BtsvHd_-.js} +1 -1
  71. package/web-app/dist/assets/{upload-BxH0rjQU.js → upload-XiWa_WBM.js} +1 -1
  72. package/web-app/dist/assets/{usePolling-CokbBZIQ.js → usePolling-B6Hxwbk1.js} +1 -1
  73. package/web-app/dist/assets/{user-C_u9mNZM.js → user-oL_Oya-S.js} +1 -1
  74. package/web-app/dist/index.html +1 -1
  75. package/web-app/dist/assets/CockpitPage-BW0hYuzC.js +0 -47
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.75.0
6
+ # Loki Mode v9.76.0
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.75.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
473
+ **v9.76.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 9.75.0
1
+ 9.76.0
@@ -4368,19 +4368,34 @@ council_managed_should_stop() {
4368
4368
  return 1
4369
4369
  fi
4370
4370
 
4371
+ # S-196: every reader and the session heredoc run on the resolved -I -S
4372
+ # interpreter (see _loki_snapshot_py_tool); -E still loads user-site .pth
4373
+ # files. None resolvable -> never start the managed session: Bash fallback.
4374
+ # Deliberate consequence: -S hides site-packages, so the anthropic SDK is
4375
+ # never importable and is_enabled() is always False. The managed council
4376
+ # therefore always falls back to Bash voting (warned below). Reviving it
4377
+ # needs a pinned interpreter plus an SDK path that loads no .pth file;
4378
+ # a PATH or venv python3 without -S would let the agent forge the verdict.
4379
+ local _ms_py
4380
+ _ms_py="$(_loki_snapshot_py_tool)" || {
4381
+ log_warn "[Council] Managed completion council unavailable: no isolated -I -S interpreter; falling back to Bash voting"
4382
+ return 1
4383
+ }
4384
+
4371
4385
  local loki_dir="${TARGET_DIR:-.}/.loki"
4372
- local project_dir="${PROJECT_DIR:-$(pwd)}"
4373
4386
  local round="${ITERATION_COUNT:-0}"
4374
4387
  local verdicts_dir="$COUNCIL_STATE_DIR/verdicts"
4375
4388
  mkdir -p "$verdicts_dir" 2>/dev/null || true
4376
4389
 
4377
4390
  # Build session context: diff_summary, test_summary, pending_tasks.
4378
4391
  # Kept deliberately small so we never choke the session budget.
4392
+ # S-196: diff the target project (same root as loki_dir and
4393
+ # LOKI_TARGET_DIR), never PROJECT_DIR, which is Loki's install tree.
4379
4394
  local diff_summary=""
4380
- diff_summary=$(cd "$project_dir" 2>/dev/null && git diff --stat 2>/dev/null | tail -20 | tr '\n' ' ' || echo "")
4395
+ diff_summary=$(cd "${TARGET_DIR:-.}" 2>/dev/null && git diff --stat 2>/dev/null | tail -20 | tr '\n' ' ' || echo "")
4381
4396
  local test_summary=""
4382
4397
  if [ -f "$loki_dir/quality/test-results.json" ]; then
4383
- test_summary=$(_TRF="$loki_dir/quality/test-results.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4398
+ test_summary=$(_TRF="$loki_dir/quality/test-results.json" "$_ms_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4384
4399
  import json, os
4385
4400
  try:
4386
4401
  d = json.load(open(os.environ['_TRF']))
@@ -4391,7 +4406,7 @@ except Exception:
4391
4406
  fi
4392
4407
  local pending_tasks="[]"
4393
4408
  if [ -f "$loki_dir/queue/pending.json" ]; then
4394
- pending_tasks=$(_QF="$loki_dir/queue/pending.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4409
+ pending_tasks=$(_QF="$loki_dir/queue/pending.json" "$_ms_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4395
4410
  import json, os
4396
4411
  try:
4397
4412
  d = json.load(open(os.environ['_QF']))
@@ -4415,7 +4430,7 @@ except Exception:
4415
4430
  _CC_LOKI_DIR="$loki_dir" \
4416
4431
  LOKI_TARGET_DIR="${TARGET_DIR:-$(pwd)}" \
4417
4432
  PROJECT_DIR="${PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)}" \
4418
- python3 -E - <<'PYEOF' 2>/dev/null || exit_code=$?
4433
+ "$_ms_py" -I -S - <<'PYEOF' 2>/dev/null || exit_code=$?
4419
4434
  import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
4420
4435
  import json, os, sys, pathlib
4421
4436
 
@@ -4553,7 +4568,10 @@ PYEOF
4553
4568
  return 0
4554
4569
  fi
4555
4570
 
4556
- log_warn "[Council] Managed completion council unavailable (exit=$exit_code); falling back to Bash voting"
4571
+ case "$exit_code" in
4572
+ 2|3) log_warn "[Council] Managed completion council unavailable (exit=$exit_code): SDK not importable under the isolated -I -S interpreter; falling back to Bash voting" ;;
4573
+ *) log_warn "[Council] Managed completion council unavailable (exit=$exit_code); falling back to Bash voting" ;;
4574
+ esac
4557
4575
  return 1
4558
4576
  }
4559
4577
 
@@ -28,9 +28,41 @@
28
28
  # guards on LOKI_PROVEN_PR_CHECK=1). It is also safe if called directly.
29
29
  #
30
30
  # Inline Python (D7): the call site's cwd is the agent's repo, so the proof
31
- # readers run python3 -E and drop '' and '.' from sys.path first; a committed
32
- # json.py or sitecustomize.py cannot change the headline. Pinned by
31
+ # readers drop '' and '.' from sys.path first; a committed json.py or
32
+ # sitecustomize.py cannot change the headline. Pinned by
33
33
  # tests/test-proven-pr-check.sh (D7 section).
34
+ #
35
+ # BACKLOG 54 / S-203: python3 -E still loaded the user site-packages, where a
36
+ # .pth "import" line runs before any sys.path scrub and could forge json.load
37
+ # (a NOT VERIFIED proof read as VERIFIED). The readers resolve their
38
+ # interpreter through _loki_snapshot_py_tool and run it -I -S; when none
39
+ # resolves they print empty, as they do when python3 is absent. run.sh sources
40
+ # this file before it defines _loki_snapshot_py_tool, and its later definition
41
+ # replaces this copy, so the copy below only serves callers that source this
42
+ # file on its own (tests). run.sh is the source of truth: keep this copy
43
+ # byte-identical to it. Pinned by tests/test-proof-check-no-user-site-pth.sh
44
+ # and tests/test-council-py-tool-identity.sh.
45
+ declare -F _loki_snapshot_py_tool >/dev/null 2>&1 || \
46
+ _loki_snapshot_py_tool() {
47
+ local c
48
+ for c in /usr/bin/python3 /bin/python3; do
49
+ [ -x "$c" ] && [ ! -d "$c" ] && "$c" -I -S -c '' >/dev/null 2>&1 && { printf '%s\n' "$c"; return 0; }
50
+ done
51
+ local dir
52
+ local IFS=:
53
+ for dir in $PATH; do
54
+ case "$dir" in
55
+ /*) ;;
56
+ *) continue ;;
57
+ esac
58
+ if [ -x "$dir/python3" ] && [ ! -d "$dir/python3" ] \
59
+ && "$dir/python3" -I -S -c '' >/dev/null 2>&1; then
60
+ printf '%s\n' "$dir/python3"
61
+ return 0
62
+ fi
63
+ done
64
+ return 1
65
+ }
34
66
 
35
67
  # Double-source guard.
36
68
  [ -n "${_PROOF_CHECK_SH:-}" ] && return 0
@@ -71,10 +103,11 @@ _proof_check_headline() {
71
103
  local proof_path="${1:-}"
72
104
  [ -n "$proof_path" ] || { printf '%s' ""; return 0; }
73
105
  [ -f "$proof_path" ] || { printf '%s' ""; return 0; }
74
- command -v python3 >/dev/null 2>&1 || { printf '%s' ""; return 0; }
106
+ local _pc_py=""
107
+ _pc_py="$(_loki_snapshot_py_tool)" || { printf '%s' ""; return 0; }
75
108
 
76
109
  local headline=""
77
- headline="$(python3 -E - "$proof_path" <<'PY' 2>/dev/null || true
110
+ headline="$("$_pc_py" -I -S - "$proof_path" <<'PY' 2>/dev/null || true
78
111
  import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
79
112
  import json, sys
80
113
  try:
@@ -103,10 +136,11 @@ _proof_check_proof_head_sha() {
103
136
  local proof_path="${1:-}"
104
137
  [ -n "$proof_path" ] || { printf '%s' ""; return 0; }
105
138
  [ -f "$proof_path" ] || { printf '%s' ""; return 0; }
106
- command -v python3 >/dev/null 2>&1 || { printf '%s' ""; return 0; }
139
+ local _pc_py=""
140
+ _pc_py="$(_loki_snapshot_py_tool)" || { printf '%s' ""; return 0; }
107
141
 
108
142
  local sha=""
109
- sha="$(python3 -E - "$proof_path" <<'PY' 2>/dev/null || true
143
+ sha="$("$_pc_py" -I -S - "$proof_path" <<'PY' 2>/dev/null || true
110
144
  import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
111
145
  import json, sys
112
146
  try:
@@ -138,10 +172,11 @@ _proof_check_run_id() {
138
172
  local proof_path="${1:-}"
139
173
  [ -n "$proof_path" ] || { printf '%s' ""; return 0; }
140
174
  [ -f "$proof_path" ] || { printf '%s' ""; return 0; }
141
- command -v python3 >/dev/null 2>&1 || { printf '%s' ""; return 0; }
175
+ local _pc_py=""
176
+ _pc_py="$(_loki_snapshot_py_tool)" || { printf '%s' ""; return 0; }
142
177
 
143
178
  local rid=""
144
- rid="$(python3 -E - "$proof_path" <<'PY' 2>/dev/null || true
179
+ rid="$("$_pc_py" -I -S - "$proof_path" <<'PY' 2>/dev/null || true
145
180
  import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
146
181
  import json, sys
147
182
  try:
@@ -28,9 +28,18 @@
28
28
  # to existing heuristic dispatch).
29
29
  #
30
30
  # Inline Python (D7): the dispatch runs with the cwd inside the agent's repo, so
31
- # every python3 here runs -E and drops '' and '.' from sys.path before any other
32
- # import; a committed json.py or sitecustomize.py cannot read the votes.
31
+ # every python3 here drops '' and '.' from sys.path before any other import; a
32
+ # committed json.py or sitecustomize.py cannot read the votes.
33
33
  # Pinned by tests/test-voter-agents-json.sh (Case D).
34
+ #
35
+ # S-202 (BACKLOG 54): -E alone still loads the user site-packages, whose .pth
36
+ # "import" lines run before that scrub and can forge json.loads (3 REJECT
37
+ # findings read as a COMPLETE round). Every call site resolves its interpreter
38
+ # through _loki_snapshot_py_tool and runs it -I -S; when none resolves, the
39
+ # dispatch returns 1 and completion-council's fail-closed path applies. run.sh
40
+ # is the source of truth for the helper; the guarded copy below only serves
41
+ # callers that source this file on its own. Keep it byte-identical to run.sh's.
42
+ # Pinned by tests/test-voter-agents-no-user-site-pth.sh.
34
43
 
35
44
  # Guard against double-source.
36
45
  if [ "${__LOKI_VOTER_AGENTS_SH_LOADED:-0}" = "1" ]; then
@@ -38,6 +47,28 @@ if [ "${__LOKI_VOTER_AGENTS_SH_LOADED:-0}" = "1" ]; then
38
47
  fi
39
48
  __LOKI_VOTER_AGENTS_SH_LOADED=1
40
49
 
50
+ declare -F _loki_snapshot_py_tool >/dev/null 2>&1 || \
51
+ _loki_snapshot_py_tool() {
52
+ local c
53
+ for c in /usr/bin/python3 /bin/python3; do
54
+ [ -x "$c" ] && [ ! -d "$c" ] && "$c" -I -S -c '' >/dev/null 2>&1 && { printf '%s\n' "$c"; return 0; }
55
+ done
56
+ local dir
57
+ local IFS=:
58
+ for dir in $PATH; do
59
+ case "$dir" in
60
+ /*) ;;
61
+ *) continue ;;
62
+ esac
63
+ if [ -x "$dir/python3" ] && [ ! -d "$dir/python3" ] \
64
+ && "$dir/python3" -I -S -c '' >/dev/null 2>&1; then
65
+ printf '%s\n' "$dir/python3"
66
+ return 0
67
+ fi
68
+ done
69
+ return 1
70
+ }
71
+
41
72
  # Resolve repo root once. This file lives at autonomy/lib/voter-agents.sh.
42
73
  __LOKI_VA_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
43
74
  __LOKI_VA_REPO_ROOT="$(cd "${__LOKI_VA_LIB_DIR}/../.." 2>/dev/null && pwd)"
@@ -59,12 +90,14 @@ loki_voter_agents_json() {
59
90
  fi
60
91
  local prd="${LOKI_PRD_PATH:-}"
61
92
  local tier="${LOKI_RARV_TIER:-development}"
93
+ local _va_py
94
+ _va_py="$(_loki_snapshot_py_tool)" || { printf '%s' '{}'; return 0; }
62
95
 
63
96
  _VA_ITER="$iter" \
64
97
  _VA_PRD="$prd" \
65
98
  _VA_TIER="$tier" \
66
99
  _VA_COMPLEXITY="${LOKI_COMPLEXITY:-standard}" \
67
- python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
100
+ "$_va_py" -I -S -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
68
101
  import json, os
69
102
  iter_n = os.environ.get("_VA_ITER", "0")
70
103
  prd = os.environ.get("_VA_PRD", "")
@@ -133,7 +166,9 @@ print(json.dumps(agents, separators=(",", ":")))
133
166
  # Arg $1: terse base findings summary used to brief the DA prompt.
134
167
  loki_devils_advocate_json() {
135
168
  local summary="${1:-}"
136
- _VA_SUMMARY="$summary" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
169
+ local _va_py
170
+ _va_py="$(_loki_snapshot_py_tool)" || { printf '%s' '{}'; return 0; }
171
+ _VA_SUMMARY="$summary" "$_va_py" -I -S -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
137
172
  import json, os
138
173
  summary = os.environ.get("_VA_SUMMARY", "")[:1000]
139
174
  agents = {
@@ -355,13 +390,15 @@ loki_council_dispatch_agents() {
355
390
  local verdicts_dir="$COUNCIL_STATE_DIR/verdicts"
356
391
  local votes_dir="$COUNCIL_STATE_DIR/votes"
357
392
  mkdir -p "$verdicts_dir" "$votes_dir" 2>/dev/null || return 1
393
+ local _va_py
394
+ _va_py="$(_loki_snapshot_py_tool)" || return 1
358
395
 
359
396
  _VA_RESP="$response" \
360
397
  _VA_ITER="$iteration" \
361
398
  _VA_VDIR="$verdicts_dir" \
362
399
  _VA_RFILE="$votes_dir/round-${iteration}.json" \
363
400
  _VA_EXPECTED="${COUNCIL_SIZE:-3}" \
364
- python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
401
+ "$_va_py" -I -S -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
365
402
  import json, os, sys
366
403
  from datetime import datetime, timezone
367
404
 
package/autonomy/loki CHANGED
@@ -1271,6 +1271,8 @@ loki_remote_verify_receipt() {
1271
1271
  echo " the JWKS URL is not https -- only loopback is allowed over plain http)."
1272
1272
  echo " Its integrity hash matches. Provenance is unproven here, not disproven."
1273
1273
  echo " Install python3 cryptography, or re-run where the build server is reachable."
1274
+ echo " The verifier runs python3 -E, so cryptography supplied through PYTHONPATH is"
1275
+ echo " not seen; install it where the base interpreter finds it (site or user site)."
1274
1276
  return 0
1275
1277
  fi
1276
1278
 
@@ -37143,6 +37145,7 @@ print(json.dumps(json.load(open(os.environ['LOKI_PROOF_JSON'])), indent=2))
37143
37145
  # verifier is a finding and is never softened to 2.
37144
37146
  echo -e "${YELLOW}attestation: NOT CHECKED -- could not check against ${_pv_jwks} (key set unreadable,${NC}" >&2
37145
37147
  echo -e "${YELLOW} verifier dependency missing, or the JWKS URL is not https -- only loopback is allowed over plain http).${NC}" >&2
37148
+ echo " The verifier runs python3 -E, so cryptography supplied through PYTHONPATH is not seen (a site or user-site install is)." >&2
37146
37149
  echo " This is not a failure of the receipt. Nothing was proven either way (exit 2)." >&2
37147
37150
  if [ "$_pv_rc" -eq 0 ]; then _pv_rc=2; fi
37148
37151
  _pv_attestation_field="NOT CHECKED"
@@ -19,12 +19,23 @@
19
19
  # LOKI_CHECKLIST_ENABLED - Enable/disable checklist (default: true)
20
20
  #
21
21
  # Inline Python (D7): verification runs with the cwd inside the agent's repo,
22
- # so every python3 here runs -E (an empty PYTHONPATH component would load a
23
- # committed sitecustomize.py) and every -c / - body drops '' and '.' from
24
- # sys.path before any other import (a committed json.py would print the
25
- # verdict). Pinned by tests/moat/p2-honest-verdict.sh
22
+ # so every python3 here ignores PYTHON* env vars (an empty PYTHONPATH component
23
+ # would load a committed sitecustomize.py) and every -c / - body drops '' and
24
+ # '.' from sys.path before any other import (a committed json.py would print
25
+ # the verdict). Pinned by tests/moat/p2-honest-verdict.sh
26
26
  # P2.checklist-verify-not-shadowed.
27
27
  #
28
+ # S-204 (BACKLOG 53): -E alone still loads the user site-packages, whose .pth
29
+ # "import" lines run at startup and can patch json.dump, so checklist-verify.py
30
+ # wrote failing checks as verified into the results file the council hard gate
31
+ # trusts. Every call site resolves its interpreter through
32
+ # _loki_snapshot_py_tool and runs it -I -S; when none resolves, nothing is
33
+ # verified or summarized (the hard gate then blocks fail-closed). See that
34
+ # function's comment in run.sh for the rationale and accepted gaps. The copy
35
+ # below only serves callers that source this file before or without run.sh
36
+ # (tests); run.sh is the source of truth, keep it byte-identical. Pinned by
37
+ # tests/test-prd-checklist-no-user-site-pth.sh.
38
+ #
28
39
  # Data:
29
40
  # .loki/checklist/checklist.json - Full checklist with verification
30
41
  # .loki/checklist/verification-results.json - Summary of last verification
@@ -37,6 +48,28 @@
37
48
  #
38
49
  #===============================================================================
39
50
 
51
+ declare -F _loki_snapshot_py_tool >/dev/null 2>&1 || \
52
+ _loki_snapshot_py_tool() {
53
+ local c
54
+ for c in /usr/bin/python3 /bin/python3; do
55
+ [ -x "$c" ] && [ ! -d "$c" ] && "$c" -I -S -c '' >/dev/null 2>&1 && { printf '%s\n' "$c"; return 0; }
56
+ done
57
+ local dir
58
+ local IFS=:
59
+ for dir in $PATH; do
60
+ case "$dir" in
61
+ /*) ;;
62
+ *) continue ;;
63
+ esac
64
+ if [ -x "$dir/python3" ] && [ ! -d "$dir/python3" ] \
65
+ && "$dir/python3" -I -S -c '' >/dev/null 2>&1; then
66
+ printf '%s\n' "$dir/python3"
67
+ return 0
68
+ fi
69
+ done
70
+ return 1
71
+ }
72
+
40
73
  # Configuration
41
74
  CHECKLIST_ENABLED=${LOKI_CHECKLIST_ENABLED:-true}
42
75
  CHECKLIST_INTERVAL=${LOKI_CHECKLIST_INTERVAL:-5}
@@ -200,12 +233,14 @@ checklist_oracle_triangulate() {
200
233
  local status_token="NOOP"
201
234
  local status_file=""
202
235
  local status_tmp_root="${TMPDIR:-/tmp}"
236
+ local _cl_py
237
+ _cl_py="$(_loki_snapshot_py_tool)" || return 0
203
238
  if status_file="$(mktemp "${status_tmp_root%/}/loki-oracle-status.XXXXXX" 2>/dev/null)"; then
204
239
  if _ORACLE_SPEC="$CHECKLIST_PRD_PATH" \
205
240
  _ORACLE_OUT="$findings_file" \
206
241
  _ORACLE_PROJECT="$project_dir" \
207
242
  _ORACLE_INSTALL_DIR="$oracle_install_dir" \
208
- python3 -E - > "$status_file" 2>/dev/null <<'ORACLE_PY'
243
+ "$_cl_py" -I -S - > "$status_file" 2>/dev/null <<'ORACLE_PY'
209
244
  import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
210
245
  import json, os, re, sys, tempfile, glob
211
246
 
@@ -701,7 +736,9 @@ checklist_oracle_evidence() {
701
736
  if [ ! -f "$findings_file" ]; then
702
737
  return 0
703
738
  fi
704
- _ORACLE_OUT="$findings_file" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
739
+ local _cl_py
740
+ _cl_py="$(_loki_snapshot_py_tool)" || return 0
741
+ _ORACLE_OUT="$findings_file" "$_cl_py" -I -S -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
705
742
  import json, os
706
743
  try:
707
744
  with open(os.environ["_ORACLE_OUT"]) as f:
@@ -804,8 +841,9 @@ checklist_select_heldout() {
804
841
  # Honest caveat: re-selection or partial-survival after a regen can reserve
805
842
  # items the build loop already saw in earlier prompts (the hidden-from-loop
806
843
  # guarantee is best-effort once the checklist ids change mid-run).
807
- local status_token
808
- status_token=$(_CHECKLIST_FILE="$CHECKLIST_FILE" _HELDOUT_FILE="$heldout_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
844
+ local status_token _cl_py
845
+ _cl_py="$(_loki_snapshot_py_tool)" || return 0
846
+ status_token=$(_CHECKLIST_FILE="$CHECKLIST_FILE" _HELDOUT_FILE="$heldout_file" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
809
847
  import json, os, sys, hashlib, tempfile
810
848
 
811
849
  cl_path = os.environ['_CHECKLIST_FILE']
@@ -953,7 +991,9 @@ checklist_heldout_ids() {
953
991
  if [ ! -f "$heldout_file" ]; then
954
992
  return 0
955
993
  fi
956
- _HELDOUT_FILE="$heldout_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
994
+ local _cl_py
995
+ _cl_py="$(_loki_snapshot_py_tool)" || return 0
996
+ _HELDOUT_FILE="$heldout_file" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
957
997
  import json, os
958
998
  try:
959
999
  with open(os.environ['_HELDOUT_FILE']) as f:
@@ -997,9 +1037,17 @@ checklist_verify() {
997
1037
  return 0
998
1038
  fi
999
1039
 
1040
+ # Non-zero so council_reverify_checklist replaces any stale green results
1041
+ # with its fail-closed sentinel (run.sh's interval call ignores the status).
1042
+ local _cl_py
1043
+ if ! _cl_py="$(_loki_snapshot_py_tool)"; then
1044
+ log_warn "checklist verification skipped: no isolated python3 interpreter resolved"
1045
+ return 1
1046
+ fi
1047
+
1000
1048
  log_step "Running PRD checklist verification..."
1001
1049
 
1002
- python3 -E "$verify_script" \
1050
+ "$_cl_py" -I -S "$verify_script" \
1003
1051
  --checklist "$CHECKLIST_FILE" \
1004
1052
  --timeout "$CHECKLIST_TIMEOUT" 2>/dev/null || true
1005
1053
 
@@ -1027,11 +1075,13 @@ checklist_summary() {
1027
1075
  echo ""
1028
1076
  return 0
1029
1077
  fi
1078
+ local _cl_py
1079
+ _cl_py="$(_loki_snapshot_py_tool)" || { echo ""; return 0; }
1030
1080
 
1031
1081
  _CHECKLIST_RESULTS="$CHECKLIST_RESULTS_FILE" \
1032
1082
  _CHECKLIST_WAIVERS="${CHECKLIST_DIR:-".loki/checklist"}/waivers.json" \
1033
1083
  _CHECKLIST_HELDOUT="${CHECKLIST_DIR:-".loki/checklist"}/held-out.json" \
1034
- python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1084
+ "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1035
1085
  import json, sys, os
1036
1086
  try:
1037
1087
  fpath = os.environ.get('_CHECKLIST_RESULTS', '')
@@ -1128,13 +1178,16 @@ checklist_as_evidence() {
1128
1178
  if [ ! -f "$CHECKLIST_RESULTS_FILE" ]; then
1129
1179
  return 0
1130
1180
  fi
1181
+ local _cl_py
1182
+ # No interpreter: the failed exec falls to "Checklist data unavailable" below.
1183
+ _cl_py="$(_loki_snapshot_py_tool)" || _cl_py="/nonexistent/python3"
1131
1184
 
1132
1185
  {
1133
1186
  echo ""
1134
1187
  echo "## PRD Checklist Verification"
1135
1188
  echo ""
1136
1189
 
1137
- _CHECKLIST_RESULTS="$CHECKLIST_RESULTS_FILE" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1190
+ _CHECKLIST_RESULTS="$CHECKLIST_RESULTS_FILE" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1138
1191
  import json, os
1139
1192
  try:
1140
1193
  data = json.load(open(os.environ['_CHECKLIST_RESULTS']))
@@ -1170,7 +1223,9 @@ checklist_waiver_load() {
1170
1223
  if [ ! -f "$waivers_file" ]; then
1171
1224
  return 0
1172
1225
  fi
1173
- _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1226
+ local _cl_py
1227
+ _cl_py="$(_loki_snapshot_py_tool)" || return 0
1228
+ _WAIVERS_FILE="$waivers_file" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1174
1229
  import json, sys, os
1175
1230
  try:
1176
1231
  waivers_file = os.environ['_WAIVERS_FILE']
@@ -1193,8 +1248,10 @@ checklist_waiver_add() {
1193
1248
  local waivers_file="${CHECKLIST_DIR:-".loki/checklist"}/waivers.json"
1194
1249
  local timestamp
1195
1250
  timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1251
+ local _cl_py
1252
+ _cl_py="$(_loki_snapshot_py_tool)" || return 1
1196
1253
 
1197
- _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1254
+ _WAIVERS_FILE="$waivers_file" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1198
1255
  import json, os, sys
1199
1256
 
1200
1257
  waivers_file = os.environ['_WAIVERS_FILE']
@@ -1246,8 +1303,10 @@ checklist_waiver_remove() {
1246
1303
  echo "No waivers file found"
1247
1304
  return 1
1248
1305
  fi
1306
+ local _cl_py
1307
+ _cl_py="$(_loki_snapshot_py_tool)" || return 1
1249
1308
 
1250
- _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1309
+ _WAIVERS_FILE="$waivers_file" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1251
1310
  import json, os, sys
1252
1311
 
1253
1312
  waivers_file = os.environ['_WAIVERS_FILE']
@@ -1282,8 +1341,10 @@ checklist_waiver_list() {
1282
1341
  echo "No waivers configured"
1283
1342
  return 0
1284
1343
  fi
1344
+ local _cl_py
1345
+ _cl_py="$(_loki_snapshot_py_tool)" || return 1
1285
1346
 
1286
- _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1347
+ _WAIVERS_FILE="$waivers_file" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1287
1348
  import json, os
1288
1349
  waivers_file = os.environ['_WAIVERS_FILE']
1289
1350
  with open(waivers_file) as f:
package/autonomy/run.sh CHANGED
@@ -10949,9 +10949,26 @@ _loki_untrack_agent_committed_user_files() {
10949
10949
  fi
10950
10950
  rm -f "$rec.added"
10951
10951
  if [ ! -s "$rec.new" ]; then
10952
- # BACKLOG 102: zero hits this session -- clear a stale record left by
10953
- # an earlier session, or it persists forever with nothing to read it.
10954
- rm -f "$rec.new" "$rec"
10952
+ rm -f "$rec.new"
10953
+ # S-194: the tree diff above cannot see a file an earlier (resumed)
10954
+ # session added and then untracked, but the branch history still
10955
+ # holds it and create_session_pr gates the push on this record. Keep
10956
+ # the record while that history holds any recorded path; on a failed
10957
+ # check, keep it too (fail closed).
10958
+ if [ -s "$rec" ]; then
10959
+ local range="${fork}..HEAD" held_paths=() p="" hist=""
10960
+ [ "$(git cat-file -t "$fork" 2>/dev/null)" = tree ] && range="HEAD"
10961
+ while IFS= read -r -d '' p; do held_paths+=("$p"); done < "$rec"
10962
+ if [ "${#held_paths[@]}" -eq 0 ] \
10963
+ || ! hist="$(git -C "$top" --literal-pathspecs log -1 --format=%H --no-renames --diff-filter=A "$range" -- "${held_paths[@]}" 2>/dev/null)" \
10964
+ || [ -n "$hist" ]; then
10965
+ return 0
10966
+ fi
10967
+ fi
10968
+ # BACKLOG 102: zero hits this session and no recorded path left in the
10969
+ # branch history -- clear a stale record left by an earlier session,
10970
+ # or it persists forever with nothing to read it.
10971
+ rm -f "$rec"
10955
10972
  return 0
10956
10973
  fi
10957
10974
  mv -f "$rec.new" "$rec"
@@ -11198,6 +11215,24 @@ create_session_pr() {
11198
11215
  return 0
11199
11216
  fi
11200
11217
 
11218
+ # BACKLOG 108: _loki_untrack_agent_committed_user_files removed the user's
11219
+ # pre-existing files from the branch tip, but the commit that added them is
11220
+ # still in the branch history, so a push publishes them. Print the cleanup
11221
+ # before any push advice, and refuse the LOKI_AUTO_PR push. The fork is the
11222
+ # merge base with the recorded base, as that helper computes it; its
11223
+ # session-start-sha fallback cannot apply here, because without a merge
11224
+ # base commit_count is 0 and this function already returned.
11225
+ if [ -s .loki/state/agent-committed-user-files.z ]; then
11226
+ local _uc_fork="" _uc_names=""
11227
+ _uc_fork="$(git merge-base HEAD "$base" 2>/dev/null)" || _uc_fork=""
11228
+ _uc_names="$(_loki_nul_names .loki/state/agent-committed-user-files.z)"
11229
+ log_warn "The history of ${branch_name} still holds your pre-existing files the agent committed: ${_uc_names:-see .loki/state/agent-committed-user-files.z}. Before pushing, drop them from its history: git reset --soft ${_uc_fork:-<fork commit>} && git commit"
11230
+ if [ "${LOKI_AUTO_PR:-0}" = "1" ]; then
11231
+ log_warn "LOKI_AUTO_PR: not pushing ${branch_name}; its history holds your pre-existing files."
11232
+ return 1
11233
+ fi
11234
+ fi
11235
+
11201
11236
  # DEFAULT: advisory only. Print the exact commands; never push, never PR.
11202
11237
  if [ "${LOKI_AUTO_PR:-0}" != "1" ]; then
11203
11238
  if declare -f print_pr_advice >/dev/null 2>&1; then
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "9.75.0"
10
+ __version__ = "9.76.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:** v9.75.0
5
+ **Version:** v9.76.0
6
6
 
7
7
  ---
8
8