loki-mode 9.51.1 → 9.53.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/README.md CHANGED
@@ -282,7 +282,8 @@ loki proof show <id> # the facts, the assessments, and the headline
282
282
  loki proof verify <id> # re-hash the receipt and re-derive the diff
283
283
  ```
284
284
 
285
- `loki proof verify` exits 0 clean, 1 on tamper or drift. Receipts are attached
285
+ `loki proof verify` exits 0 clean, 1 on tamper or drift, 2 when it could not check
286
+ (full table: [docs/exit-codes.md](docs/exit-codes.md)). Receipts are attached
286
287
  to pull requests automatically (`LOKI_PROVEN_PR=0` to opt out), so a reviewer
287
288
  sees the evidence next to the code.
288
289
 
@@ -368,7 +369,7 @@ Receipts are written to `.loki/proofs/<run_id>/` automatically at run completion
368
369
  ```bash
369
370
  loki proof list # every receipt: run id, time, council verdict, cost, files
370
371
  loki proof show <id> # the full proof.json (facts, assessments, honesty)
371
- loki proof verify <id> # re-check the receipt against the repo (exit 0 clean, 1 tamper/drift)
372
+ loki proof verify <id> # re-check the receipt against the repo (exit 0 clean, 1 tamper/drift, 2 could not check)
372
373
  ```
373
374
 
374
375
  `loki proof verify` does two independent checks and prints the result as JSON:
package/SKILL.md CHANGED
@@ -3,13 +3,13 @@ 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.51.1
6
+ # Loki Mode v9.53.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
10
10
  **Spec in, verified product out.** Spec-driven: a "spec" is whatever describes the work -- a Markdown PRD, a GitHub issue, an OpenAPI doc, a Jira ticket (a PRD is one form of spec). The differentiator is the trust layer: Loki does not call work done until it is verified. The RARV-C closure loop, 8 quality gates, the completion council, and the verified-completion evidence gate must all clear before completion is accepted. The evidence gate blocks on empty-diff, red tests, an unhealthy serveable app (runtime-boot axis, `LOKI_EVIDENCE_BOOT_GATE=0` to opt out), and a leaked credential in the changed files (secret-leak axis, `LOKI_EVIDENCE_SECRET_GATE=0` to opt out) -- v8.0.0.
11
11
 
12
- **Evidence Receipt (verify it yourself).** Every run writes a receipt to `.loki/proofs/<run_id>/` (opt out with `LOKI_PROOF=0`) that separates deterministic FACTS (git diff with base/head SHAs and a `diff_sha256`, the test command + exit code, the build command + exit code, each gate verdict) from AI ASSESSMENTS (the council verdict, labeled judgment not proof). The headline is computed only from the facts: VERIFIED (tests ran a real command and exited 0, diff non-empty, nothing skipped), VERIFIED WITH GAPS (each gap listed by name), or NOT VERIFIED (a check ran and failed). Inspect and re-check with `loki proof list|show <id>|verify <id>` (aliased `loki receipt`); `loki proof verify` re-hashes the receipt (tamper) and re-derives the diff from the recorded base SHA against the live repo (drift), exiting 0 clean / 1 tamper-or-drift. This is honesty-of-done, not a claim that the code is bug-free.
12
+ **Evidence Receipt (verify it yourself).** Every run writes a receipt to `.loki/proofs/<run_id>/` (opt out with `LOKI_PROOF=0`) that separates deterministic FACTS (git diff with base/head SHAs and a `diff_sha256`, the test command + exit code, the build command + exit code, each gate verdict) from AI ASSESSMENTS (the council verdict, labeled judgment not proof). The headline is computed only from the facts: VERIFIED (tests ran a real command and exited 0, diff non-empty, nothing skipped), VERIFIED WITH GAPS (each gap listed by name), or NOT VERIFIED (a check ran and failed). Inspect and re-check with `loki proof list|show <id>|verify <id>` (aliased `loki receipt`); `loki proof verify` re-hashes the receipt (tamper) and re-derives the diff from the recorded base SHA against the live repo (drift), exiting 0 clean / 1 tamper-or-drift / 2 could not check (64 usage, 66 unknown id; see `docs/exit-codes.md`). This is honesty-of-done, not a claim that the code is bug-free.
13
13
 
14
14
  **Provider-agnostic (stable since v5.0.0):** runs on Claude/Codex/Cline/Aider with abstract model tiers and degraded mode for non-Claude providers; no vendor lock-in. Gemini deprecated v7.5.18. See `skills/providers.md`. **Current track (v8.0.0):** the Anthropic Agent SDK route (see below), spec-mode expansion for OpenAPI/GraphQL/Postman contracts, the runtime-boot and secret-leak evidence axes, and `loki steer` / `loki why` for mid-run control. Earlier tracks: LSP grounding as a first-class agent tool (v7.7.x) and Phase 1 RARV-C closure (real provider judges, gate-failure flock, synthetic PRD e2e, status `--json`).
15
15
 
@@ -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.51.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
473
+ **v9.53.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 9.51.1
1
+ 9.53.0
@@ -19,6 +19,12 @@
19
19
  # - Multi-agent debate: Voting beats unanimous (+13.2%), KS adaptive stopping
20
20
  # - NVIDIA ToolOrchestra: Efficiency metrics for agent tool use
21
21
  #
22
+ # Inline Python (D7): the council runs with the cwd inside the agent's repo, so
23
+ # every "python3 -c" / "python3 -" here runs -E (a PYTHONPATH empty component
24
+ # would load a committed sitecustomize.py) and drops '' and '.' from sys.path
25
+ # before any other import (a committed json.py would print the verdict).
26
+ # Pinned by tests/moat/p2-honest-verdict.sh P2.council-readers-not-shadowed.
27
+ #
22
28
  # Environment Variables:
23
29
  # LOKI_COUNCIL_ENABLED - Enable completion council (default: true)
24
30
  # LOKI_COUNCIL_SIZE - Number of council members (default: 3)
@@ -371,7 +377,7 @@ council_track_iteration() {
371
377
  _COUNCIL_ITERATION="${ITERATION_COUNT:-0}" \
372
378
  _COUNCIL_FILES_CHANGED="$files_changed" \
373
379
  _COUNCIL_DIFF_HASH="$combined_hash" \
374
- python3 -c "
380
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
375
381
  import json, os
376
382
  state_file = os.environ['_COUNCIL_STATE_FILE']
377
383
  try:
@@ -453,7 +459,7 @@ _uncertainty_state_path() {
453
459
  # Read uncertainty.json (or emit a default object if missing/corrupt) to stdout.
454
460
  _uncertainty_read_state() {
455
461
  local file="$1"
456
- _UNC_FILE="$file" python3 -c "
462
+ _UNC_FILE="$file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
457
463
  import json, os
458
464
  f = os.environ['_UNC_FILE']
459
465
  default = {
@@ -487,7 +493,7 @@ _uncertainty_write_state() {
487
493
  dir="$(dirname "$file")"
488
494
  mkdir -p "$dir" 2>/dev/null || true
489
495
  tmp="${file}.tmp.$$"
490
- if _UNC_PAYLOAD="$payload" _UNC_TMP="$tmp" python3 -c "
496
+ if _UNC_PAYLOAD="$payload" _UNC_TMP="$tmp" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
491
497
  import json, os
492
498
  payload = os.environ['_UNC_PAYLOAD']
493
499
  tmp = os.environ['_UNC_TMP']
@@ -539,7 +545,7 @@ uncertainty_should_escalate() {
539
545
  _UNC_SPLIT_ROUNDS="$split_rounds" \
540
546
  _UNC_NOCHANGE_MIN="$nochange_min" \
541
547
  _UNC_RING_SIZE="$ring_size" \
542
- python3 -c "
548
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
543
549
  import json, os
544
550
 
545
551
  prior = json.loads(os.environ['_UNC_PRIOR'])
@@ -826,7 +832,7 @@ council_vote() {
826
832
  local budget_exceeded=false
827
833
  if [ "$total_issue_count" -gt 0 ] && [ "$COUNCIL_ERROR_BUDGET" != "0.0" ] && [ "$COUNCIL_ERROR_BUDGET" != "0" ]; then
828
834
  # Check if non-blocking issue ratio exceeds the error budget
829
- budget_exceeded=$(_NB="$non_blocking_count" _TOTAL="$total_issue_count" _BUDGET="$COUNCIL_ERROR_BUDGET" python3 -c "
835
+ budget_exceeded=$(_NB="$non_blocking_count" _TOTAL="$total_issue_count" _BUDGET="$COUNCIL_ERROR_BUDGET" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
830
836
  import os
831
837
  nb = int(os.environ['_NB'])
832
838
  total = int(os.environ['_TOTAL'])
@@ -918,7 +924,7 @@ print('true' if ratio > budget else 'false')
918
924
  _COUNCIL_REJECT="$reject_count" \
919
925
  _COUNCIL_ITERATION="${ITERATION_COUNT:-0}" \
920
926
  _COUNCIL_THRESHOLD="$effective_threshold" \
921
- python3 -c "
927
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
922
928
  import json, os
923
929
  from datetime import datetime, timezone
924
930
  state_file = os.environ['_COUNCIL_STATE_FILE']
@@ -1036,7 +1042,7 @@ council_write_transcript() {
1036
1042
  _MEMBERS_DIR="$COUNCIL_STATE_DIR/votes/iteration-${iteration}" \
1037
1043
  _THRESHOLD="$effective_threshold" \
1038
1044
  _OUT="$transcript_file" \
1039
- python3 -c "
1045
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1040
1046
  import json, os, pathlib, re
1041
1047
 
1042
1048
  iteration_id = os.environ['_IID']
@@ -1214,7 +1220,7 @@ EVIDENCE_SECTION
1214
1220
  local queue_file=".loki/queue/${queue}.json"
1215
1221
  if [ -f "$queue_file" ]; then
1216
1222
  local count
1217
- count=$(_QUEUE_FILE="$queue_file" python3 -c "import json, os; print(len(json.load(open(os.environ['_QUEUE_FILE']))))" 2>/dev/null || echo "?")
1223
+ count=$(_QUEUE_FILE="$queue_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os; print(len(json.load(open(os.environ['_QUEUE_FILE']))))" 2>/dev/null || echo "?")
1218
1224
  echo "- ${queue}: $count tasks" >> "$evidence_file"
1219
1225
  fi
1220
1226
  done
@@ -1287,7 +1293,7 @@ EVIDENCE_SECTION
1287
1293
  elif [ -f ".loki/verification/playwright-results.json" ]; then
1288
1294
  echo "" >> "$evidence_file"
1289
1295
  echo "## Playwright Smoke Test Results" >> "$evidence_file"
1290
- _PW_RESULTS=".loki/verification/playwright-results.json" python3 -c "
1296
+ _PW_RESULTS=".loki/verification/playwright-results.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1291
1297
  import json, os
1292
1298
  try:
1293
1299
  d = json.load(open(os.environ['_PW_RESULTS']))
@@ -1375,7 +1381,7 @@ council_checklist_gate() {
1375
1381
  # council_heldout_gate at the ship gate, and surfacing them in this gate's
1376
1382
  # block report would leak their identity back into the build loop.
1377
1383
  local gate_result
1378
- gate_result=$(_RESULTS_FILE="$results_file" _WAIVERS_FILE="$waivers_file" _HELDOUT_FILE="$heldout_file" python3 -c "
1384
+ gate_result=$(_RESULTS_FILE="$results_file" _WAIVERS_FILE="$waivers_file" _HELDOUT_FILE="$heldout_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1379
1385
  import json, sys, os
1380
1386
 
1381
1387
  results_file = os.environ['_RESULTS_FILE']
@@ -1438,13 +1444,13 @@ else:
1438
1444
  local timestamp
1439
1445
  timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1440
1446
  local failures_json
1441
- failures_json=$(_FAILURES="$failures" python3 -c "
1447
+ failures_json=$(_FAILURES="$failures" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1442
1448
  import json, os
1443
1449
  items = os.environ['_FAILURES'].split('|')
1444
1450
  print(json.dumps(items))
1445
1451
  " 2>/dev/null || echo '[]')
1446
1452
  local critical_count
1447
- critical_count=$(_FAILURES="$failures" python3 -c "
1453
+ critical_count=$(_FAILURES="$failures" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1448
1454
  import os
1449
1455
  print(len(os.environ['_FAILURES'].split('|')))
1450
1456
  " 2>/dev/null || echo '0')
@@ -1514,7 +1520,7 @@ council_heldout_gate() {
1514
1520
  # contain ':' or '|'); they are read separately from the held-out JSON block
1515
1521
  # below in the BLOCK branch.
1516
1522
  local gate_result
1517
- gate_result=$(_RESULTS_FILE="$results_file" _HELDOUT_FILE="$heldout_file" _WAIVERS_FILE="$waivers_file" python3 -c "
1523
+ gate_result=$(_RESULTS_FILE="$results_file" _HELDOUT_FILE="$heldout_file" _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1518
1524
  import json, sys, os
1519
1525
 
1520
1526
  results_file = os.environ['_RESULTS_FILE']
@@ -1634,7 +1640,7 @@ print('%s %d %d' % (verdict, passed, failed))
1634
1640
  if [ "$verdict" = "BLOCK" ]; then
1635
1641
  # Read failing held-out titles directly from the data (colon/pipe-safe).
1636
1642
  local titles_json titles_display
1637
- titles_json=$(_RESULTS_FILE="$results_file" _HELDOUT_FILE="$heldout_file" _WAIVERS_FILE="$waivers_file" python3 -c "
1643
+ titles_json=$(_RESULTS_FILE="$results_file" _HELDOUT_FILE="$heldout_file" _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1638
1644
  import json, os
1639
1645
  results = json.load(open(os.environ['_RESULTS_FILE']))
1640
1646
  heldout_ids = set(json.load(open(os.environ['_HELDOUT_FILE'])).get('held_out', []))
@@ -1653,7 +1659,7 @@ for cat in results.get('categories', []):
1653
1659
  titles.append(item.get('title', iid))
1654
1660
  print(json.dumps(titles[:5]))
1655
1661
  " 2>/dev/null || echo '[]')
1656
- titles_display=$(_T="$titles_json" python3 -c "
1662
+ titles_display=$(_T="$titles_json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1657
1663
  import json, os
1658
1664
  try:
1659
1665
  print(', '.join(json.loads(os.environ['_T'])))
@@ -1927,7 +1933,8 @@ council_evidence_gate() {
1927
1933
  local test_inconclusive_reason=""
1928
1934
  if [ -f "$tr_file" ]; then
1929
1935
  local test_status
1930
- test_status=$(_TR_FILE="$tr_file" python3 <<'PYEOF' 2>/dev/null || echo "INCONCLUSIVE:none:true"
1936
+ test_status=$(_TR_FILE="$tr_file" python3 -E -s - <<'PYEOF' 2>/dev/null || echo "INCONCLUSIVE:none:true"
1937
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
1931
1938
  import json, os, sys
1932
1939
  tr_file = os.environ['_TR_FILE']
1933
1940
  try:
@@ -1937,7 +1944,9 @@ except (json.JSONDecodeError, IOError, KeyError, ValueError):
1937
1944
  print('INCONCLUSIVE:none:true')
1938
1945
  sys.exit(0)
1939
1946
  runner = d.get('runner', 'none')
1940
- passed = d.get('pass', True)
1947
+ # No default of True: a results file with no "pass" key recorded no outcome, and
1948
+ # an unrecorded outcome is not a pass. None falls to INCONCLUSIVE below.
1949
+ passed = d.get('pass')
1941
1950
  status = d.get('status', '')
1942
1951
  #82 (zero-test-file hardening): a runner that ran but executed ZERO real tests
1943
1952
  # (node --test on a *.test.js with no test() calls; jest --passWithNoTests with
@@ -1952,7 +1961,10 @@ if runner == 'none':
1952
1961
  elif passed is False:
1953
1962
  print('FAIL:%s:false' % runner)
1954
1963
  elif status == 'no_tests_run' or passed is not True:
1955
- print('INCONCLUSIVE:%s:true' % runner)
1964
+ # Both are INCONCLUSIVE; NO_PASS names the second: no boolean pass was
1965
+ # recorded at all (missing, null, or a non-boolean value), which is not a
1966
+ # zero-test run.
1967
+ print('%s:%s:true' % ('INCONCLUSIVE' if status == 'no_tests_run' else 'NO_PASS', runner))
1956
1968
  else:
1957
1969
  print('PASS:%s:true' % runner)
1958
1970
  PYEOF
@@ -1983,6 +1995,13 @@ PYEOF
1983
1995
  test_inconclusive="true"
1984
1996
  test_inconclusive_reason="no_tests_executed"
1985
1997
  fi
1998
+ # A real runner label but no boolean pass recorded: the same
1999
+ # pass-through INCONCLUSIVE, named for what it is. Not gated by the
2000
+ # opt-out either: an unrecorded outcome is never affirmative.
2001
+ if [ "$_verdict" = "NO_PASS" ]; then
2002
+ test_inconclusive="true"
2003
+ test_inconclusive_reason="no_pass_recorded"
2004
+ fi
1986
2005
  else
1987
2006
  # Missing test-results.json: no suite was recorded at all. Like the
1988
2007
  # runner=="none" case this is not affirmative evidence, so classify it
@@ -2173,7 +2192,8 @@ DETAILS_EOF
2173
2192
  boot_inconclusive_reason="no_app_runner"
2174
2193
  elif command -v python3 >/dev/null 2>&1; then
2175
2194
  local _boot_status
2176
- _boot_status=$(_HEALTH="$_health_file" _STATE="$_state_file" python3 <<'PYEOF' 2>/dev/null || echo "INCONCLUSIVE:parse_error"
2195
+ _boot_status=$(_HEALTH="$_health_file" _STATE="$_state_file" python3 -E -s - <<'PYEOF' 2>/dev/null || echo "INCONCLUSIVE:parse_error"
2196
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
2177
2197
  import json, os
2178
2198
  health_file = os.environ.get('_HEALTH', '')
2179
2199
  state_file = os.environ.get('_STATE', '')
@@ -2336,7 +2356,8 @@ PYEOF
2336
2356
  persist_inconclusive="true"; persist_inconclusive_reason="not_serveable"
2337
2357
  elif command -v python3 >/dev/null 2>&1; then
2338
2358
  local _p_status
2339
- _p_status=$(_PF="$_proof_file" _ITER="${ITERATION_COUNT:-0}" python3 <<'PYEOF' 2>/dev/null || echo "MISSING"
2359
+ _p_status=$(_PF="$_proof_file" _ITER="${ITERATION_COUNT:-0}" python3 -E -s - <<'PYEOF' 2>/dev/null || echo "MISSING"
2360
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
2340
2361
  import json, os
2341
2362
  p = os.environ['_PF']
2342
2363
  if not os.path.isfile(p):
@@ -2399,7 +2420,8 @@ PYEOF
2399
2420
  auth_inconclusive="true"; auth_inconclusive_reason="not_serveable"
2400
2421
  elif command -v python3 >/dev/null 2>&1; then
2401
2422
  local _a_status
2402
- _a_status=$(_PF="$_proof_file" _STRICT="${LOKI_PROOF_AUTH_STRICT:-1}" _ITER="${ITERATION_COUNT:-0}" python3 <<'PYEOF' 2>/dev/null || echo "MISSING"
2423
+ _a_status=$(_PF="$_proof_file" _STRICT="${LOKI_PROOF_AUTH_STRICT:-1}" _ITER="${ITERATION_COUNT:-0}" python3 -E -s - <<'PYEOF' 2>/dev/null || echo "MISSING"
2424
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
2403
2425
  import json, os
2404
2426
  p = os.environ['_PF']
2405
2427
  strict = os.environ.get('_STRICT', '1') != '0'
@@ -2477,7 +2499,8 @@ PYEOF
2477
2499
  authz_inconclusive="true"; authz_inconclusive_reason="not_serveable"
2478
2500
  elif command -v python3 >/dev/null 2>&1; then
2479
2501
  local _authz_status
2480
- _authz_status=$(_PF="$_proof_file" _ITER="${ITERATION_COUNT:-0}" python3 <<'PYEOF' 2>/dev/null || echo "MISSING"
2502
+ _authz_status=$(_PF="$_proof_file" _ITER="${ITERATION_COUNT:-0}" python3 -E -s - <<'PYEOF' 2>/dev/null || echo "MISSING"
2503
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
2481
2504
  import json, os
2482
2505
  p = os.environ['_PF']
2483
2506
  if not os.path.isfile(p):
@@ -2734,7 +2757,7 @@ PYEOF
2734
2757
  local timestamp
2735
2758
  timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
2736
2759
  local failures_json diff_ok tests_ok base_for_json
2737
- failures_json=$(_FAILURES="$failures" python3 -c "
2760
+ failures_json=$(_FAILURES="$failures" python3 -E -s -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
2738
2761
  import json, os
2739
2762
  items = [s for s in os.environ['_FAILURES'].split('|') if s]
2740
2763
  print(json.dumps(items[:5]))
@@ -2751,11 +2774,11 @@ print(json.dumps(items[:5]))
2751
2774
  if [ "$authz_fails" = "true" ]; then authz_ok="false"; else authz_ok="true"; fi
2752
2775
  # Record WHY boot was inconclusive (no_app_runner / not_serveable / etc.) so a
2753
2776
  # consumer of the block report can tell a genuine boot pass from a pass-through.
2754
- boot_reason_json=$(_R="${boot_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2755
- nomock_reason_json=$(_R="${nomock_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2756
- persist_reason_json=$(_R="${persist_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2757
- auth_reason_json=$(_R="${auth_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2758
- authz_reason_json=$(_R="${authz_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2777
+ boot_reason_json=$(_R="${boot_inconclusive_reason:-}" python3 -E -s -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2778
+ nomock_reason_json=$(_R="${nomock_inconclusive_reason:-}" python3 -E -s -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2779
+ persist_reason_json=$(_R="${persist_inconclusive_reason:-}" python3 -E -s -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2780
+ auth_reason_json=$(_R="${auth_inconclusive_reason:-}" python3 -E -s -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2781
+ authz_reason_json=$(_R="${authz_inconclusive_reason:-}" python3 -E -s -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2759
2782
  base_for_json="${base_sha:-}"
2760
2783
  cat > "$ev_tmp" << EVIDENCE_EOF
2761
2784
  {
@@ -3393,7 +3416,7 @@ council_heuristic_review() {
3393
3416
  local _tr_file=".loki/quality/test-results.json"
3394
3417
  local _tests_ok=0
3395
3418
  if [ -f "$_tr_file" ] && command -v python3 >/dev/null 2>&1; then
3396
- _tests_ok=$(_TR="$_tr_file" python3 -c "
3419
+ _tests_ok=$(_TR="$_tr_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
3397
3420
  import json, os
3398
3421
  try:
3399
3422
  with open(os.environ['_TR']) as f:
@@ -3558,13 +3581,15 @@ council_evaluate_member() {
3558
3581
  # the member stays CONTINUE.
3559
3582
  #
3560
3583
  # Parse verdict mirrors council_evidence_gate: runner=="none" => PASS,
3561
- # pass is False => FAIL, else PASS. Unparseable/missing => not present.
3584
+ # pass is False => FAIL, only a boolean True (and not status no_tests_run)
3585
+ # => PASS; a missing, null or non-boolean pass key recorded no outcome =>
3586
+ # INCONCLUSIVE (not red, not positive). Unparseable/missing file => absent.
3562
3587
  local tr_file="$loki_dir/quality/test-results.json"
3563
- local test_evidence="absent" # absent | pass | fail
3588
+ local test_evidence="absent" # absent | pass | fail | inconclusive
3564
3589
  local test_runner_seen="none"
3565
3590
  if [ -f "$tr_file" ]; then
3566
3591
  local _tr_status
3567
- _tr_status=$(_TR_FILE="$tr_file" python3 -c "
3592
+ _tr_status=$(_TR_FILE="$tr_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
3568
3593
  import json, os, sys
3569
3594
  try:
3570
3595
  with open(os.environ['_TR_FILE']) as f:
@@ -3573,11 +3598,13 @@ except (json.JSONDecodeError, IOError, KeyError, ValueError):
3573
3598
  print('absent:none')
3574
3599
  sys.exit(0)
3575
3600
  runner = d.get('runner', 'none')
3576
- passed = d.get('pass', True)
3601
+ passed = d.get('pass')
3577
3602
  if runner == 'none':
3578
3603
  print('pass:none')
3579
3604
  elif passed is False:
3580
3605
  print('fail:%s' % runner)
3606
+ elif passed is not True or d.get('status') == 'no_tests_run':
3607
+ print('inconclusive:%s' % runner)
3581
3608
  else:
3582
3609
  print('pass:%s' % runner)
3583
3610
  " 2>/dev/null || echo "absent:none")
@@ -3619,7 +3646,7 @@ else:
3619
3646
  local _q _qcount
3620
3647
  for _q in pending in-progress blocked; do
3621
3648
  [ -f "$loki_dir/queue/${_q}.json" ] || continue
3622
- _qcount=$(_QUEUE_FILE="$loki_dir/queue/${_q}.json" python3 -c "import json, os
3649
+ _qcount=$(_QUEUE_FILE="$loki_dir/queue/${_q}.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os
3623
3650
  d = json.load(open(os.environ['_QUEUE_FILE']))
3624
3651
  print(len(d.get('tasks', d) if isinstance(d, dict) else d))" 2>/dev/null || echo "0")
3625
3652
  # Guard against a non-numeric read (malformed file, python absent).
@@ -3642,6 +3669,8 @@ print(len(d.get('tasks', d) if isinstance(d, dict) else d))" 2>/dev/null || echo
3642
3669
  # a no-tests / greenfield project leaves test_auditor at CONTINUE.
3643
3670
  if [ "$test_evidence" = "absent" ]; then
3644
3671
  reasons="${reasons}no structured test results found; "
3672
+ elif [ "$test_evidence" = "inconclusive" ]; then
3673
+ reasons="${reasons}structured test results inconclusive (runner '$test_runner_seen' recorded no boolean pass); "
3645
3674
  elif [ "$test_runner_seen" = "none" ]; then
3646
3675
  reasons="${reasons}no real test suite ran (runner none); "
3647
3676
  elif [ "$test_evidence" = "pass" ]; then
@@ -3754,7 +3783,7 @@ council_aggregate_votes() {
3754
3783
  # Serialize the votes array with json.dumps so backslashes/control chars are
3755
3784
  # escaped correctly (BUG fix: sed-only escaping produced invalid JSON).
3756
3785
  local votes_json
3757
- votes_json=$(_MEMBERS="$_members" _ROLES="$_roles" _VOTEVALS="$_vote_values" _REASONS="$_reasons" python3 -c "
3786
+ votes_json=$(_MEMBERS="$_members" _ROLES="$_roles" _VOTEVALS="$_vote_values" _REASONS="$_reasons" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
3758
3787
  import json, os
3759
3788
  def lines(name):
3760
3789
  v = os.environ.get(name, '')
@@ -3797,7 +3826,7 @@ print(json.dumps(out))
3797
3826
  _VERDICT="$verdict" \
3798
3827
  _VOTES="$votes_json" \
3799
3828
  _ROUND_FILE="$round_file" \
3800
- python3 -c "
3829
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
3801
3830
  import json, os
3802
3831
  from datetime import datetime, timezone
3803
3832
  round_data = {
@@ -3846,14 +3875,19 @@ council_devils_advocate_review() {
3846
3875
  # (.loki/quality/test-results.json, written by run.sh:ensure_completion_test
3847
3876
  # _evidence; parsed the same way as council_evaluate_member ~2414-2438 and the
3848
3877
  # evidence gate). Parse verdict: runner=="none" => PASS (no real suite to
3849
- # contradict completion), pass is False => FAIL, else PASS. The legacy log
3878
+ # contradict completion), pass is False => FAIL, a boolean True => PASS,
3879
+ # anything else (missing/null/non-boolean pass, status no_tests_run) =>
3880
+ # INCONCLUSIVE. Only FAIL is an issue here: inconclusive does not contradict
3881
+ # completion, exactly as council_evidence_gate lets it through, and vetoing
3882
+ # it would stall a zero-test run (#82) that is meant to reach this vote.
3883
+ # The legacy log
3850
3884
  # glob is kept ONLY as an ADDITIONAL red signal -- its absence is NOT an issue
3851
3885
  # (nothing writes .loki/logs/test-*.log, so an empty glob is the normal case
3852
3886
  # and must never veto a unanimous COMPLETE on its own).
3853
3887
  local tr_file="$loki_dir/quality/test-results.json"
3854
3888
  if [ -f "$tr_file" ]; then
3855
3889
  local _tr_status
3856
- _tr_status=$(_TR_FILE="$tr_file" python3 -c "
3890
+ _tr_status=$(_TR_FILE="$tr_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
3857
3891
  import json, os, sys
3858
3892
  try:
3859
3893
  with open(os.environ['_TR_FILE']) as f:
@@ -3862,11 +3896,13 @@ except (json.JSONDecodeError, IOError, KeyError, ValueError):
3862
3896
  print('absent')
3863
3897
  sys.exit(0)
3864
3898
  runner = d.get('runner', 'none')
3865
- passed = d.get('pass', True)
3899
+ passed = d.get('pass')
3866
3900
  if runner == 'none':
3867
3901
  print('pass')
3868
3902
  elif passed is False:
3869
3903
  print('fail')
3904
+ elif passed is not True or d.get('status') == 'no_tests_run':
3905
+ print('inconclusive')
3870
3906
  else:
3871
3907
  print('pass')
3872
3908
  " 2>/dev/null || echo "absent")
@@ -3889,7 +3925,7 @@ else:
3889
3925
  # Skeptical check 2: Are there still failing tasks in the queue?
3890
3926
  if [ -f "$loki_dir/queue/failed.json" ]; then
3891
3927
  local failed_count
3892
- failed_count=$(_QUEUE_FILE="$loki_dir/queue/failed.json" python3 -c "import json, os; print(len(json.load(open(os.environ['_QUEUE_FILE']))))" 2>/dev/null || echo "0")
3928
+ failed_count=$(_QUEUE_FILE="$loki_dir/queue/failed.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os; print(len(json.load(open(os.environ['_QUEUE_FILE']))))" 2>/dev/null || echo "0")
3893
3929
  if [ "$failed_count" -gt 0 ]; then
3894
3930
  ((issues_found++))
3895
3931
  issue_details="${issue_details}$failed_count tasks in failed queue; "
@@ -3935,7 +3971,7 @@ else:
3935
3971
  _ISSUES="$issues_found" \
3936
3972
  _DETAILS="${issue_details:-none}" \
3937
3973
  _DA_FILE="$da_file" \
3938
- python3 -c "
3974
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
3939
3975
  import json, os
3940
3976
  from datetime import datetime, timezone
3941
3977
  da_result = {
@@ -4031,7 +4067,7 @@ council_evaluate() {
4031
4067
  if loki_council_dispatch_agents "$ITERATION_COUNT" "${COUNCIL_PRD_PATH:-}"; then
4032
4068
  local _va_round_file="$COUNCIL_STATE_DIR/votes/round-${ITERATION_COUNT}.json"
4033
4069
  if [ -f "$_va_round_file" ]; then
4034
- aggregate_result=$(_RF="$_va_round_file" python3 -c "import json, os; print(json.load(open(os.environ['_RF'])).get('verdict', 'CONTINUE'))" 2>/dev/null || echo "")
4070
+ aggregate_result=$(_RF="$_va_round_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os; print(json.load(open(os.environ['_RF'])).get('verdict', 'CONTINUE'))" 2>/dev/null || echo "")
4035
4071
  fi
4036
4072
  fi
4037
4073
  fi
@@ -4053,7 +4089,7 @@ council_evaluate() {
4053
4089
  local complete_count=0
4054
4090
  local members_present=0
4055
4091
  if [ -f "$round_file" ]; then
4056
- complete_count=$(_RF="$round_file" python3 -c "import json, os; print(json.load(open(os.environ['_RF'])).get('complete_votes', 0))" 2>/dev/null || echo "0")
4092
+ complete_count=$(_RF="$round_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os; print(json.load(open(os.environ['_RF'])).get('complete_votes', 0))" 2>/dev/null || echo "0")
4057
4093
  # WAVE13 CRITICAL quorum gate: how many voters actually responded
4058
4094
  # (total_members records the ACTUAL returned count -- see
4059
4095
  # voter-agents.sh). A degraded/partial dispatch response must never
@@ -4061,7 +4097,7 @@ council_evaluate() {
4061
4097
  # somehow read COMPLETE. This is defense-in-depth: the parser
4062
4098
  # already forces CONTINUE on undercount, but the completion-detection
4063
4099
  # trust core must independently assert full quorum before stopping.
4064
- members_present=$(_RF="$round_file" python3 -c "import json, os; print(json.load(open(os.environ['_RF'])).get('total_members', 0))" 2>/dev/null || echo "0")
4100
+ members_present=$(_RF="$round_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os; print(json.load(open(os.environ['_RF'])).get('total_members', 0))" 2>/dev/null || echo "0")
4065
4101
  fi
4066
4102
  # Normalize to integers (guard against empty/non-numeric on read failure)
4067
4103
  case "$complete_count" in (''|*[!0-9]*) complete_count=0 ;; esac
@@ -4157,7 +4193,7 @@ council_managed_should_stop() {
4157
4193
  diff_summary=$(cd "$project_dir" 2>/dev/null && git diff --stat 2>/dev/null | tail -20 | tr '\n' ' ' || echo "")
4158
4194
  local test_summary=""
4159
4195
  if [ -f "$loki_dir/quality/test-results.json" ]; then
4160
- test_summary=$(_TRF="$loki_dir/quality/test-results.json" python3 -c "
4196
+ 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 ('', '.')]
4161
4197
  import json, os
4162
4198
  try:
4163
4199
  d = json.load(open(os.environ['_TRF']))
@@ -4168,7 +4204,7 @@ except Exception:
4168
4204
  fi
4169
4205
  local pending_tasks="[]"
4170
4206
  if [ -f "$loki_dir/queue/pending.json" ]; then
4171
- pending_tasks=$(_QF="$loki_dir/queue/pending.json" python3 -c "
4207
+ 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 ('', '.')]
4172
4208
  import json, os
4173
4209
  try:
4174
4210
  d = json.load(open(os.environ['_QF']))
@@ -4191,7 +4227,8 @@ except Exception:
4191
4227
  _CC_VERDICTS_DIR="$verdicts_dir" \
4192
4228
  _CC_LOKI_DIR="$loki_dir" \
4193
4229
  LOKI_TARGET_DIR="${TARGET_DIR:-$(pwd)}" \
4194
- python3 - <<'PYEOF' 2>/dev/null || exit_code=$?
4230
+ python3 -E - <<'PYEOF' 2>/dev/null || exit_code=$?
4231
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
4195
4232
  import json, os, sys, pathlib
4196
4233
 
4197
4234
  # Path setup: prefer project_dir so providers.managed resolves from main tree.
@@ -4368,7 +4405,7 @@ _council_convergence_evidence_green() {
4368
4405
  # file or runner=="none" (no suite) is NOT affirmative evidence -> not green.
4369
4406
  [ -f "$tr_file" ] || return 1
4370
4407
  local tr_state
4371
- tr_state=$(_TR_FILE="$tr_file" python3 -c "
4408
+ tr_state=$(_TR_FILE="$tr_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4372
4409
  import json, os, sys
4373
4410
  try:
4374
4411
  with open(os.environ['_TR_FILE']) as f:
@@ -4376,9 +4413,12 @@ try:
4376
4413
  except (json.JSONDecodeError, IOError, KeyError, ValueError):
4377
4414
  print('no'); sys.exit(0)
4378
4415
  runner = d.get('runner', 'none')
4379
- passed = d.get('pass', True)
4380
- # Affirmative green requires a REAL suite (runner != none) that did not fail.
4381
- print('yes' if (runner != 'none' and passed is not False) else 'no')
4416
+ passed = d.get('pass')
4417
+ # Affirmative green requires a REAL suite (runner != none) that recorded a
4418
+ # boolean True pass. A missing/null/non-boolean pass key or a zero-test run
4419
+ # (status no_tests_run) is inconclusive, which is not green.
4420
+ print('yes' if (runner != 'none' and passed is True
4421
+ and d.get('status') != 'no_tests_run') else 'no')
4382
4422
  " 2>/dev/null || echo "no")
4383
4423
  [ "$tr_state" = "yes" ] || return 1
4384
4424
 
@@ -4388,7 +4428,7 @@ print('yes' if (runner != 'none' and passed is not False) else 'no')
4388
4428
  local results_file="${TARGET_DIR:-.}/.loki/checklist/verification-results.json"
4389
4429
  if [ -f "$results_file" ]; then
4390
4430
  local cl_state
4391
- cl_state=$(_RESULTS_FILE="$results_file" python3 -c "
4431
+ cl_state=$(_RESULTS_FILE="$results_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4392
4432
  import json, os, sys
4393
4433
  try:
4394
4434
  with open(os.environ['_RESULTS_FILE']) as f:
@@ -4637,7 +4677,7 @@ council_write_report() {
4637
4677
  REPORT_HEADER
4638
4678
 
4639
4679
  # Append vote history from state
4640
- _COUNCIL_STATE_FILE="$COUNCIL_STATE_DIR/state.json" python3 -c "
4680
+ _COUNCIL_STATE_FILE="$COUNCIL_STATE_DIR/state.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4641
4681
  import json, os
4642
4682
  try:
4643
4683
  with open(os.environ['_COUNCIL_STATE_FILE']) as f:
@@ -29,6 +29,21 @@ def _excluded(path):
29
29
  return path == ".loki" or path.startswith(".loki/")
30
30
 
31
31
 
32
+ 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."""
38
+ try:
39
+ with open(os.path.join(repo_dir, ".loki", "state", "preexisting-untracked.z"), "rb") as fh:
40
+ raw = fh.read()
41
+ except OSError:
42
+ return "", frozenset()
43
+ 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
45
+
46
+
32
47
  def _parse_numstat(raw):
33
48
  files = []
34
49
  for record in (raw or "").split("\0"):
@@ -107,7 +122,9 @@ def collect_workspace_diff(repo_dir, base, include_diffs=False):
107
122
  diffs.append({"path": path, "patch": chunk})
108
123
 
109
124
  untracked = _git(repo_dir, ["ls-files", "--others", "--exclude-standard", "-z"])
110
- for path in sorted(p for p in (untracked or "").split("\0") if p and not _excluded(p)):
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):
111
128
  stat = _git(
112
129
  repo_dir,
113
130
  ["diff", "--no-index", "--no-renames", "--numstat", "-z", "--", "/dev/null", path],