loki-mode 9.53.0 → 9.54.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, 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.53.0
6
+ # Loki Mode v9.54.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.53.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
473
+ **v9.54.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 9.53.0
1
+ 9.54.0
@@ -2116,6 +2116,16 @@ INCONCLUSIVE_EOF
2116
2116
  local _diff_ok _tests_ok
2117
2117
  if [ "$diff_fails" = "true" ]; then _diff_ok="false"; else _diff_ok="true"; fi
2118
2118
  if [ "$test_fails" = "true" ]; then _tests_ok="false"; else _tests_ok="true"; fi
2119
+ # BACKLOG 55: tests.pass is true only for affirmative evidence (a real
2120
+ # runner recorded a boolean pass and nothing downgraded it). $test_pass
2121
+ # itself is "true" for every inconclusive outcome (no runner, no results,
2122
+ # zero tests, no pass recorded, tautological), so it is not written raw.
2123
+ local _tests_pass_json='"inconclusive"'
2124
+ if [ "$test_fails" = "true" ]; then
2125
+ _tests_pass_json="false"
2126
+ elif [ "$test_inconclusive" != "true" ] && [ "$test_runner" != "none" ] && [ "$test_pass" = "true" ]; then
2127
+ _tests_pass_json="true"
2128
+ fi
2119
2129
  # Proof-of-Function axes (nomock/persistence/auth) are declared later in
2120
2130
  # the gate; default to "true"/"" when this helper runs before they are
2121
2131
  # set (it never does in practice, but keep the write robust).
@@ -2139,7 +2149,7 @@ INCONCLUSIVE_EOF
2139
2149
  "tests": {
2140
2150
  "ok": $_tests_ok,
2141
2151
  "runner": "$test_runner",
2142
- "pass": $test_pass,
2152
+ "pass": $_tests_pass_json,
2143
2153
  "inconclusive": $test_inconclusive,
2144
2154
  "inconclusive_reason": "$test_inconclusive_reason"
2145
2155
  },
@@ -4227,13 +4237,17 @@ except Exception:
4227
4237
  _CC_VERDICTS_DIR="$verdicts_dir" \
4228
4238
  _CC_LOKI_DIR="$loki_dir" \
4229
4239
  LOKI_TARGET_DIR="${TARGET_DIR:-$(pwd)}" \
4240
+ PROJECT_DIR="${PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)}" \
4230
4241
  python3 -E - <<'PYEOF' 2>/dev/null || exit_code=$?
4231
4242
  import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
4232
4243
  import json, os, sys, pathlib
4233
4244
 
4234
- # Path setup: prefer project_dir so providers.managed resolves from main tree.
4235
- project_dir = os.environ.get("PROJECT_DIR") or os.getcwd()
4236
- sys.path.insert(0, project_dir)
4245
+ # Path setup: import providers.managed only from an explicit PROJECT_DIR. Never
4246
+ # fall back to the cwd (D7): it is the agent's repo, which could ship its own
4247
+ # providers/managed.py. Unset -> ImportError -> the Bash voting path.
4248
+ project_dir = os.environ.get("PROJECT_DIR", "")
4249
+ if project_dir:
4250
+ sys.path.insert(0, project_dir)
4237
4251
 
4238
4252
  try:
4239
4253
  from providers.managed import (
@@ -19,6 +19,12 @@
19
19
  # - swarm/sycophancy.py (sycophancy detection)
20
20
  # - swarm/calibration.py (reviewer calibration)
21
21
  #
22
+ # Inline Python (D7): the council runs with the cwd inside the agent's repo, so
23
+ # every python3 here runs -E and drops '' and '.' from sys.path before any other
24
+ # import (the swarm dir is inserted after that); a committed json.py or
25
+ # sitecustomize.py cannot read the votes. Pinned by
26
+ # tests/test-council-no-fabricated-verdict.sh (D7 section).
27
+ #
22
28
  #===============================================================================
23
29
 
24
30
  COUNCIL_V2_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -97,7 +103,7 @@ council_v2_vote() {
97
103
  votes_json="$votes_json$vote_content"
98
104
 
99
105
  local verdict
100
- verdict=$(echo "$vote_content" | python3 -c "import sys,json; print(json.load(sys.stdin).get('verdict','').upper())" 2>/dev/null || echo "UNKNOWN")
106
+ verdict=$(echo "$vote_content" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json; print(json.load(sys.stdin).get('verdict','').upper())" 2>/dev/null || echo "UNKNOWN")
101
107
  # TRUST: a reviewer that could not be REACHED did not vote REJECT.
102
108
  #
103
109
  # Every provider arm below used to substitute a literal
@@ -135,7 +141,7 @@ council_v2_vote() {
135
141
 
136
142
  # Step 4: Sycophancy detection
137
143
  local sycophancy_score
138
- sycophancy_score=$(python3 -c "
144
+ sycophancy_score=$(python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
139
145
  import sys
140
146
  sys.path.insert(0, '${COUNCIL_V2_DIR}/../swarm')
141
147
  from sycophancy import detect_sycophancy
@@ -150,7 +156,7 @@ print('{:.3f}'.format(detect_sycophancy(votes)))
150
156
  if [ "$approve_count" -eq "$council_size" ]; then
151
157
  local threshold="${LOKI_COUNCIL_SYCOPHANCY_THRESHOLD:-0.6}"
152
158
  local should_challenge
153
- should_challenge=$(python3 -c "print('yes' if float('$sycophancy_score') >= float('$threshold') else 'no')" 2>/dev/null || echo "no")
159
+ should_challenge=$(python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; print('yes' if float('$sycophancy_score') >= float('$threshold') else 'no')" 2>/dev/null || echo "no")
154
160
 
155
161
  if [ "$should_challenge" = "yes" ]; then
156
162
  log_warn "Sycophancy score $sycophancy_score >= $threshold -- adding devil's advocate"
@@ -167,7 +173,7 @@ print('{:.3f}'.format(detect_sycophancy(votes)))
167
173
  # Same trust rule as the main tally: an unparseable devil's
168
174
  # advocate did not vote REJECT. Defaulting to REJECT here would
169
175
  # silently overturn a unanimous APPROVE on a transient failure.
170
- da_verdict=$(cat "$da_vote" | python3 -c "import sys,json; print(json.load(sys.stdin).get('verdict','').upper())" 2>/dev/null || echo "INCONCLUSIVE")
176
+ da_verdict=$(cat "$da_vote" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json; print(json.load(sys.stdin).get('verdict','').upper())" 2>/dev/null || echo "INCONCLUSIVE")
171
177
  if [ "$da_verdict" = "INCONCLUSIVE" ]; then
172
178
  log_warn "Devil's advocate produced no verdict -- unanimous approval left UNCHANGED (not overturned)"
173
179
  elif [ "$da_verdict" = "REJECT" ]; then
@@ -201,7 +207,7 @@ print('{:.3f}'.format(detect_sycophancy(votes)))
201
207
  final_decision="reject"
202
208
  fi
203
209
 
204
- python3 -c "
210
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
205
211
  import sys
206
212
  sys.path.insert(0, '${COUNCIL_V2_DIR}/../swarm')
207
213
  from calibration import CalibrationTracker
@@ -388,7 +394,7 @@ Respond ONLY with a valid JSON object. No markdown fencing."
388
394
  --json-schema "$_c2_schema_content" --output-format json 2>/dev/null)" || _c2_json=""
389
395
  if [ -n "$_c2_json" ]; then
390
396
  # Pull the schema-validated object out of the envelope.
391
- result="$(printf '%s' "$_c2_json" | python3 -c 'import sys,json
397
+ result="$(printf '%s' "$_c2_json" | python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]; import json
392
398
  try:
393
399
  e=json.load(sys.stdin)
394
400
  p=e.get("structured_output")
@@ -490,7 +496,7 @@ except Exception:
490
496
  # or neither, stays INCONCLUSIVE -- never guessed.
491
497
  if [ -z "$extracted" ] && [ -n "${result:-}" ]; then
492
498
  local _recovered
493
- _recovered="$(printf '%s' "$result" | _LOKI_RAW="$result" python3 -c '
499
+ _recovered="$(printf '%s' "$result" | _LOKI_RAW="$result" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
494
500
  import os, re, sys, json
495
501
  raw = os.environ.get("_LOKI_RAW", "")
496
502
  # Standalone words only: "APPROVE" not "approved-by", and not inside a URL.
@@ -36,6 +36,11 @@
36
36
  # the single injection seam, mirroring autonomy/lib/prd-enrich.sh.
37
37
  #
38
38
  # No emojis. No em dashes. bash 3.2 safe. Honors `set -uo pipefail`.
39
+ #
40
+ # Inline Python (D7): the gate runs with the cwd inside the agent's repo, so
41
+ # every python3 here runs -E and drops '' and '.' from sys.path before any other
42
+ # import; a committed json.py or sitecustomize.py cannot turn the verdict done.
43
+ # Pinned by tests/test-reuse-done-recognition.sh case (d7).
39
44
 
40
45
  # Bound the single model call so a huge PRD or test log cannot run away.
41
46
  : "${LOKI_DONE_RECOG_TIMEOUT:=180}" # seconds for the single model call
@@ -122,7 +127,7 @@ _loki_done_recog_invoke() {
122
127
  --json-schema "$_dr_schema_content" --output-format json 2>/dev/null) || _dr_json=""
123
128
  fi
124
129
  if [ -n "$_dr_json" ]; then
125
- _dr_obj=$(printf '%s' "$_dr_json" | python3 -c 'import sys,json
130
+ _dr_obj=$(printf '%s' "$_dr_json" | python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]; import json
126
131
  try:
127
132
  e=json.load(sys.stdin)
128
133
  p=e.get("structured_output")
@@ -194,7 +199,7 @@ _loki_done_recog_prd_sha() {
194
199
  sha256sum "$prd_file" 2>/dev/null | awk '{print $1}'
195
200
  else
196
201
  # Last-resort: a python hash, still deterministic over the same bytes.
197
- LOKI_DR_PRD="$prd_file" python3 -c "
202
+ LOKI_DR_PRD="$prd_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
198
203
  import hashlib, os
199
204
  p = os.environ.get('LOKI_DR_PRD','')
200
205
  try:
@@ -275,7 +280,8 @@ reuse_done_recognition_gate() {
275
280
  LOKI_DR_CHECKLIST="$loki_dir/checklist/checklist.json" \
276
281
  LOKI_DR_MAX_PRD="${LOKI_DONE_RECOG_MAX_PRD_CHARS}" \
277
282
  LOKI_DR_MAX_TEST="${LOKI_DONE_RECOG_MAX_TEST_CHARS}" \
278
- python3 << 'DR_PROMPT_EOF'
283
+ python3 -E << 'DR_PROMPT_EOF'
284
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
279
285
  import json, os, sys
280
286
 
281
287
  def read_capped(path, cap):
@@ -376,7 +382,8 @@ DR_PROMPT_EOF
376
382
  LOKI_DR_TESTS="$_test_results" \
377
383
  LOKI_DR_ACTION="$action" \
378
384
  LOKI_DR_PRD="$prd_path" \
379
- python3 << 'DR_PARSE_EOF'
385
+ python3 -E << 'DR_PARSE_EOF'
386
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
380
387
  import json, os, re, sys
381
388
 
382
389
  resp = os.environ.get("LOKI_DR_RESP", "")
@@ -674,7 +681,7 @@ DR_PARSE_EOF
674
681
  fi
675
682
 
676
683
  local verdict
677
- verdict=$(printf '%s' "$parsed" | python3 -c "import json,sys;print(json.load(sys.stdin).get('verdict',''))" 2>/dev/null)
684
+ verdict=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('verdict',''))" 2>/dev/null)
678
685
 
679
686
  case "$verdict" in
680
687
  done)
@@ -687,7 +694,7 @@ DR_PARSE_EOF
687
694
  ;;
688
695
  *)
689
696
  local _reason
690
- _reason=$(printf '%s' "$parsed" | python3 -c "import json,sys;print(json.load(sys.stdin).get('reason','') or 'unverifiable')" 2>/dev/null)
697
+ _reason=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('reason','') or 'unverifiable')" 2>/dev/null)
691
698
  log_info "Done-recognition: could not confirm the existing code already satisfies the reused spec (${_reason}). Proceeding to build to be safe."
692
699
  return 1
693
700
  ;;
@@ -723,10 +730,10 @@ _loki_done_recog_finish() {
723
730
  mkdir -p "$loki_dir/state" 2>/dev/null || true
724
731
 
725
732
  local summary met total axis
726
- summary=$(printf '%s' "$parsed" | python3 -c "import json,sys;print(json.load(sys.stdin).get('summary','') or 'Project already satisfies its spec.')" 2>/dev/null)
727
- met=$(printf '%s' "$parsed" | python3 -c "import json,sys;print(json.load(sys.stdin).get('met_count',0))" 2>/dev/null)
728
- total=$(printf '%s' "$parsed" | python3 -c "import json,sys;print(json.load(sys.stdin).get('total_count',0))" 2>/dev/null)
729
- axis=$(printf '%s' "$parsed" | python3 -c "import json,sys;print(json.load(sys.stdin).get('tests_axis','unknown'))" 2>/dev/null)
733
+ summary=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('summary','') or 'Project already satisfies its spec.')" 2>/dev/null)
734
+ met=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('met_count',0))" 2>/dev/null)
735
+ total=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('total_count',0))" 2>/dev/null)
736
+ axis=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('tests_axis','unknown'))" 2>/dev/null)
730
737
  [ -n "$met" ] || met=0
731
738
  [ -n "$total" ] || total=0
732
739
  [ -n "$axis" ] || axis="unknown"
@@ -766,7 +773,7 @@ _loki_done_recog_finish() {
766
773
  echo ""
767
774
  echo "## Per-requirement evidence"
768
775
  echo ""
769
- printf '%s' "$parsed" | python3 -c "
776
+ printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
770
777
  import json, sys
771
778
  try:
772
779
  d = json.load(sys.stdin)
@@ -785,7 +792,7 @@ for t in d.get('satisfied', []):
785
792
  LOKI_DR_MET="$met" \
786
793
  LOKI_DR_TOTAL="$total" \
787
794
  LOKI_DR_TS="$ts" \
788
- python3 -c "
795
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
789
796
  import json, os, tempfile
790
797
  out = os.environ['LOKI_DR_OUT']
791
798
  def i(v):
@@ -856,7 +863,7 @@ _loki_done_recog_write_manifest() {
856
863
  LOKI_DR_PARSED="$parsed" \
857
864
  LOKI_DR_SHA="$prd_sha" \
858
865
  LOKI_DR_TS="$ts" \
859
- python3 -c "
866
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
860
867
  import json, os, sys, tempfile
861
868
  out = os.environ['LOKI_DR_OUT']
862
869
  try:
@@ -897,7 +904,8 @@ except Exception:
897
904
  if [ -f "$loki_dir/queue/pending.json" ]; then
898
905
  # Drop prior PRD-sourced tasks so the incremental pass is the source of
899
906
  # truth; non-PRD tasks (if any) are preserved.
900
- LOKI_DR_PENDING="$loki_dir/queue/pending.json" python3 - <<'RESET_EOF' 2>/dev/null || true
907
+ LOKI_DR_PENDING="$loki_dir/queue/pending.json" python3 -E - <<'RESET_EOF' 2>/dev/null || true
908
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
901
909
  import json, os, tempfile
902
910
  p = os.environ.get("LOKI_DR_PENDING", ".loki/queue/pending.json")
903
911
  try:
@@ -921,8 +929,8 @@ RESET_EOF
921
929
  fi
922
930
 
923
931
  local met total
924
- met=$(printf '%s' "$parsed" | python3 -c "import json,sys;print(json.load(sys.stdin).get('met_count',0))" 2>/dev/null)
925
- total=$(printf '%s' "$parsed" | python3 -c "import json,sys;print(json.load(sys.stdin).get('total_count',0))" 2>/dev/null)
932
+ met=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('met_count',0))" 2>/dev/null)
933
+ total=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('total_count',0))" 2>/dev/null)
926
934
  local unmet=$(( ${total:-0} - ${met:-0} ))
927
935
  log_info "Done-recognition: ${met:-0} of ${total:-0} requirements already satisfied; building only the ${unmet} unmet. Pass --fresh-prd to rebuild from scratch."
928
936
  }
@@ -26,6 +26,11 @@
26
26
  #
27
27
  # This function is only ever called when the operator opted in (the call site
28
28
  # guards on LOKI_PROVEN_PR_CHECK=1). It is also safe if called directly.
29
+ #
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
33
+ # tests/test-proven-pr-check.sh (D7 section).
29
34
 
30
35
  # Double-source guard.
31
36
  [ -n "${_PROOF_CHECK_SH:-}" ] && return 0
@@ -53,7 +58,8 @@ _proof_check_headline() {
53
58
  command -v python3 >/dev/null 2>&1 || { printf '%s' ""; return 0; }
54
59
 
55
60
  local headline=""
56
- headline="$(python3 - "$proof_path" <<'PY' 2>/dev/null || true
61
+ headline="$(python3 -E - "$proof_path" <<'PY' 2>/dev/null || true
62
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
57
63
  import json, sys
58
64
  try:
59
65
  with open(sys.argv[1], "r", encoding="utf-8") as fh:
@@ -84,7 +90,8 @@ _proof_check_proof_head_sha() {
84
90
  command -v python3 >/dev/null 2>&1 || { printf '%s' ""; return 0; }
85
91
 
86
92
  local sha=""
87
- sha="$(python3 - "$proof_path" <<'PY' 2>/dev/null || true
93
+ sha="$(python3 -E - "$proof_path" <<'PY' 2>/dev/null || true
94
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
88
95
  import json, sys
89
96
  try:
90
97
  with open(sys.argv[1], "r", encoding="utf-8") as fh:
@@ -118,7 +125,8 @@ _proof_check_run_id() {
118
125
  command -v python3 >/dev/null 2>&1 || { printf '%s' ""; return 0; }
119
126
 
120
127
  local rid=""
121
- rid="$(python3 - "$proof_path" <<'PY' 2>/dev/null || true
128
+ rid="$(python3 -E - "$proof_path" <<'PY' 2>/dev/null || true
129
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
122
130
  import json, sys
123
131
  try:
124
132
  with open(sys.argv[1], "r", encoding="utf-8") as fh:
@@ -26,6 +26,11 @@
26
26
  # on success, 1 on any fallback
27
27
  # condition (caller falls through
28
28
  # to existing heuristic dispatch).
29
+ #
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.
33
+ # Pinned by tests/test-voter-agents-json.sh (Case D).
29
34
 
30
35
  # Guard against double-source.
31
36
  if [ "${__LOKI_VOTER_AGENTS_SH_LOADED:-0}" = "1" ]; then
@@ -59,7 +64,7 @@ loki_voter_agents_json() {
59
64
  _VA_PRD="$prd" \
60
65
  _VA_TIER="$tier" \
61
66
  _VA_COMPLEXITY="${LOKI_COMPLEXITY:-standard}" \
62
- python3 -c '
67
+ python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
63
68
  import json, os
64
69
  iter_n = os.environ.get("_VA_ITER", "0")
65
70
  prd = os.environ.get("_VA_PRD", "")
@@ -128,7 +133,7 @@ print(json.dumps(agents, separators=(",", ":")))
128
133
  # Arg $1: terse base findings summary used to brief the DA prompt.
129
134
  loki_devils_advocate_json() {
130
135
  local summary="${1:-}"
131
- _VA_SUMMARY="$summary" python3 -c '
136
+ _VA_SUMMARY="$summary" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
132
137
  import json, os
133
138
  summary = os.environ.get("_VA_SUMMARY", "")[:1000]
134
139
  agents = {
@@ -349,7 +354,7 @@ loki_council_dispatch_agents() {
349
354
  _VA_VDIR="$verdicts_dir" \
350
355
  _VA_RFILE="$votes_dir/round-${iteration}.json" \
351
356
  _VA_EXPECTED="${COUNCIL_SIZE:-3}" \
352
- python3 -c '
357
+ python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
353
358
  import json, os, sys
354
359
  from datetime import datetime, timezone
355
360
 
@@ -3,8 +3,11 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
+ import hashlib
6
7
  import os
8
+ import stat as _stat
7
9
  import subprocess
10
+ import sys
8
11
 
9
12
 
10
13
  _EMPTY = {"count": 0, "insertions": 0, "deletions": 0, "files": []}
@@ -29,19 +32,93 @@ def _excluded(path):
29
32
  return path == ".loki" or path.startswith(".loki/")
30
33
 
31
34
 
35
+ # Written by run.sh setup_agent_branch (_loki_snapshot_preexisting): the paths
36
+ # git did not track when the session started, untracked and gitignored,
37
+ # NUL-delimited and relative to the repo top. An entry ending in "/" is an
38
+ # ignored directory and covers its subtree. The sibling holds "<sha256> <path>"
39
+ # records, one per file entry, written by write_snapshot_hashes below.
40
+ _SNAPSHOT = "preexisting-untracked.z"
41
+ _SNAPSHOT_HASHES = "preexisting-untracked.sha.z"
42
+
43
+
44
+ def _content_hash(path):
45
+ """sha256 of a regular file's bytes or a symlink's target, else None.
46
+
47
+ The snapshot writer and the receipt reader both call this, so the two sides
48
+ always agree (git hash-object varies with filters and object format).
49
+ """
50
+ try:
51
+ st = os.lstat(path)
52
+ digest = hashlib.sha256()
53
+ if _stat.S_ISLNK(st.st_mode):
54
+ digest.update(b"link\0" + os.fsencode(os.readlink(path)))
55
+ elif _stat.S_ISREG(st.st_mode):
56
+ with open(path, "rb") as fh:
57
+ for chunk in iter(lambda: fh.read(1 << 20), b""):
58
+ digest.update(chunk)
59
+ else:
60
+ return None
61
+ return digest.hexdigest()
62
+ except OSError:
63
+ return None
64
+
65
+
66
+ def write_snapshot_hashes(top, snapshot):
67
+ """Record a content hash for every file entry of ``snapshot`` that exists
68
+ now (a directory entry or a missing path gets none), atomically, into the
69
+ sibling hashes file."""
70
+ with open(snapshot, "rb") as fh:
71
+ raw = fh.read()
72
+ top_b = os.fsencode(top)
73
+ records = []
74
+ for path in raw.split(b"\0"):
75
+ if not path or path.endswith(b"/"):
76
+ continue
77
+ digest = _content_hash(os.path.join(top_b, path))
78
+ if digest:
79
+ records.append(digest.encode("ascii") + b" " + path + b"\0")
80
+ dest = os.path.join(os.path.dirname(snapshot), _SNAPSHOT_HASHES)
81
+ with open(dest + ".tmp", "wb") as fh:
82
+ fh.write(b"".join(records))
83
+ os.replace(dest + ".tmp", dest)
84
+
85
+
32
86
  def _preexisting_untracked(repo_dir):
33
- """Paths the user already had untracked when the Loki session branch was
34
- minted (run.sh setup_agent_branch writes .loki/state/preexisting-untracked.z,
35
- NUL-delimited, relative to the repo top). They are not this run's work, so
36
- the receipt does not list them; tree_sha256 still binds their bytes. Returns
37
- (prefix of repo_dir inside the repo, set of paths); empty when absent."""
87
+ """The session-start snapshot: (prefix of repo_dir inside the repo, set of
88
+ repo-top-relative paths, {path: content hash}). Those paths are not this
89
+ run's work, so the receipt lists one only when its content changed;
90
+ tree_sha256 still binds their bytes. Empty when absent."""
91
+ state = os.path.join(repo_dir, ".loki", "state")
38
92
  try:
39
- with open(os.path.join(repo_dir, ".loki", "state", "preexisting-untracked.z"), "rb") as fh:
93
+ with open(os.path.join(state, _SNAPSHOT), "rb") as fh:
40
94
  raw = fh.read()
41
95
  except OSError:
42
- return "", frozenset()
96
+ return "", frozenset(), {}
43
97
  paths = frozenset(p.decode("utf-8", "surrogateescape") for p in raw.split(b"\0") if p)
44
- return (_git(repo_dir, ["rev-parse", "--show-prefix"]) or "").strip(), paths
98
+ hashes = {}
99
+ try:
100
+ with open(os.path.join(state, _SNAPSHOT_HASHES), "rb") as fh:
101
+ raw = fh.read()
102
+ except OSError:
103
+ raw = b""
104
+ for record in raw.split(b"\0"):
105
+ digest, sep, path = record.partition(b" ")
106
+ if sep:
107
+ hashes[path.decode("utf-8", "surrogateescape")] = digest.decode("ascii", "replace")
108
+ return (_git(repo_dir, ["rev-parse", "--show-prefix"]) or "").strip(), paths, hashes
109
+
110
+
111
+ def _covered(key, paths):
112
+ """True when ``key`` or one of its parent directories ("a/", "a/b/") is in
113
+ ``paths``: an ignored directory entry covers everything below it."""
114
+ if key in paths:
115
+ return True
116
+ cut = key.find("/")
117
+ while cut != -1:
118
+ if key[:cut + 1] in paths:
119
+ return True
120
+ cut = key.find("/", cut + 1)
121
+ return False
45
122
 
46
123
 
47
124
  def _parse_numstat(raw):
@@ -122,9 +199,19 @@ def collect_workspace_diff(repo_dir, base, include_diffs=False):
122
199
  diffs.append({"path": path, "patch": chunk})
123
200
 
124
201
  untracked = _git(repo_dir, ["ls-files", "--others", "--exclude-standard", "-z"])
125
- prefix, preexisting = _preexisting_untracked(repo_dir)
126
- for path in sorted(p for p in (untracked or "").split("\0")
127
- if p and not _excluded(p) and prefix + p not in preexisting):
202
+ prefix, preexisting, hashes = _preexisting_untracked(repo_dir)
203
+ for path in sorted(p for p in (untracked or "").split("\0") if p and not _excluded(p)):
204
+ key = prefix + path
205
+ if _covered(key, preexisting):
206
+ # The user's file, never committed. Listed only when the run changed
207
+ # it, with no counts and no patch: the old bytes were never stored,
208
+ # and its content (a config.local.json the agent un-ignored) must
209
+ # not land in the receipt.
210
+ recorded = hashes.get(key)
211
+ if recorded and _content_hash(os.path.join(repo_dir, path)) != recorded:
212
+ files.append({"path": path, "insertions": 0, "deletions": 0,
213
+ "status": "preexisting_modified"})
214
+ continue
128
215
  stat = _git(
129
216
  repo_dir,
130
217
  ["diff", "--no-index", "--no-renames", "--numstat", "-z", "--", "/dev/null", path],
@@ -155,4 +242,11 @@ def collect_workspace_diff(repo_dir, base, include_diffs=False):
155
242
  }, diffs
156
243
 
157
244
 
158
- __all__ = ["collect_workspace_diff"]
245
+ __all__ = ["collect_workspace_diff", "write_snapshot_hashes"]
246
+
247
+
248
+ if __name__ == "__main__":
249
+ # run.sh: python3 -E workspace_diff.py hash-snapshot <repo top> <snapshot>
250
+ if len(sys.argv) != 4 or sys.argv[1] != "hash-snapshot":
251
+ sys.exit("usage: workspace_diff.py hash-snapshot <repo top> <snapshot>")
252
+ write_snapshot_hashes(sys.argv[2], sys.argv[3])
@@ -18,6 +18,13 @@
18
18
  # LOKI_CHECKLIST_TIMEOUT - Timeout per check in seconds (default: 30)
19
19
  # LOKI_CHECKLIST_ENABLED - Enable/disable checklist (default: true)
20
20
  #
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
26
+ # P2.checklist-verify-not-shadowed.
27
+ #
21
28
  # Data:
22
29
  # .loki/checklist/checklist.json - Full checklist with verification
23
30
  # .loki/checklist/verification-results.json - Summary of last verification
@@ -198,7 +205,8 @@ checklist_oracle_triangulate() {
198
205
  _ORACLE_OUT="$findings_file" \
199
206
  _ORACLE_PROJECT="$project_dir" \
200
207
  _ORACLE_INSTALL_DIR="$oracle_install_dir" \
201
- python3 - > "$status_file" 2>/dev/null <<'ORACLE_PY'
208
+ python3 -E - > "$status_file" 2>/dev/null <<'ORACLE_PY'
209
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
202
210
  import json, os, re, sys, tempfile, glob
203
211
 
204
212
  spec_path = os.environ["_ORACLE_SPEC"]
@@ -693,7 +701,7 @@ checklist_oracle_evidence() {
693
701
  if [ ! -f "$findings_file" ]; then
694
702
  return 0
695
703
  fi
696
- _ORACLE_OUT="$findings_file" python3 -c '
704
+ _ORACLE_OUT="$findings_file" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
697
705
  import json, os
698
706
  try:
699
707
  with open(os.environ["_ORACLE_OUT"]) as f:
@@ -797,7 +805,7 @@ checklist_select_heldout() {
797
805
  # items the build loop already saw in earlier prompts (the hidden-from-loop
798
806
  # guarantee is best-effort once the checklist ids change mid-run).
799
807
  local status_token
800
- status_token=$(_CHECKLIST_FILE="$CHECKLIST_FILE" _HELDOUT_FILE="$heldout_file" python3 -c "
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 ('', '.')]
801
809
  import json, os, sys, hashlib, tempfile
802
810
 
803
811
  cl_path = os.environ['_CHECKLIST_FILE']
@@ -945,7 +953,7 @@ checklist_heldout_ids() {
945
953
  if [ ! -f "$heldout_file" ]; then
946
954
  return 0
947
955
  fi
948
- _HELDOUT_FILE="$heldout_file" python3 -c "
956
+ _HELDOUT_FILE="$heldout_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
949
957
  import json, os
950
958
  try:
951
959
  with open(os.environ['_HELDOUT_FILE']) as f:
@@ -991,7 +999,7 @@ checklist_verify() {
991
999
 
992
1000
  log_step "Running PRD checklist verification..."
993
1001
 
994
- python3 "$verify_script" \
1002
+ python3 -E "$verify_script" \
995
1003
  --checklist "$CHECKLIST_FILE" \
996
1004
  --timeout "$CHECKLIST_TIMEOUT" 2>/dev/null || true
997
1005
 
@@ -1023,7 +1031,7 @@ checklist_summary() {
1023
1031
  _CHECKLIST_RESULTS="$CHECKLIST_RESULTS_FILE" \
1024
1032
  _CHECKLIST_WAIVERS="${CHECKLIST_DIR:-".loki/checklist"}/waivers.json" \
1025
1033
  _CHECKLIST_HELDOUT="${CHECKLIST_DIR:-".loki/checklist"}/held-out.json" \
1026
- python3 -c "
1034
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1027
1035
  import json, sys, os
1028
1036
  try:
1029
1037
  fpath = os.environ.get('_CHECKLIST_RESULTS', '')
@@ -1126,7 +1134,7 @@ checklist_as_evidence() {
1126
1134
  echo "## PRD Checklist Verification"
1127
1135
  echo ""
1128
1136
 
1129
- _CHECKLIST_RESULTS="$CHECKLIST_RESULTS_FILE" python3 -c "
1137
+ _CHECKLIST_RESULTS="$CHECKLIST_RESULTS_FILE" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1130
1138
  import json, os
1131
1139
  try:
1132
1140
  data = json.load(open(os.environ['_CHECKLIST_RESULTS']))
@@ -1162,7 +1170,7 @@ checklist_waiver_load() {
1162
1170
  if [ ! -f "$waivers_file" ]; then
1163
1171
  return 0
1164
1172
  fi
1165
- _WAIVERS_FILE="$waivers_file" python3 -c "
1173
+ _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1166
1174
  import json, sys, os
1167
1175
  try:
1168
1176
  waivers_file = os.environ['_WAIVERS_FILE']
@@ -1186,7 +1194,7 @@ checklist_waiver_add() {
1186
1194
  local timestamp
1187
1195
  timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1188
1196
 
1189
- _WAIVERS_FILE="$waivers_file" python3 -c "
1197
+ _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1190
1198
  import json, os, sys
1191
1199
 
1192
1200
  waivers_file = os.environ['_WAIVERS_FILE']
@@ -1239,7 +1247,7 @@ checklist_waiver_remove() {
1239
1247
  return 1
1240
1248
  fi
1241
1249
 
1242
- _WAIVERS_FILE="$waivers_file" python3 -c "
1250
+ _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1243
1251
  import json, os, sys
1244
1252
 
1245
1253
  waivers_file = os.environ['_WAIVERS_FILE']
@@ -1275,7 +1283,7 @@ checklist_waiver_list() {
1275
1283
  return 0
1276
1284
  fi
1277
1285
 
1278
- _WAIVERS_FILE="$waivers_file" python3 -c "
1286
+ _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1279
1287
  import json, os
1280
1288
  waivers_file = os.environ['_WAIVERS_FILE']
1281
1289
  with open(waivers_file) as f: