loki-mode 9.52.0 → 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/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.52.0
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
 
@@ -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.52.0 | [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.52.0
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:
@@ -1954,7 +1961,10 @@ if runner == 'none':
1954
1961
  elif passed is False:
1955
1962
  print('FAIL:%s:false' % runner)
1956
1963
  elif status == 'no_tests_run' or passed is not True:
1957
- 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))
1958
1968
  else:
1959
1969
  print('PASS:%s:true' % runner)
1960
1970
  PYEOF
@@ -1985,6 +1995,13 @@ PYEOF
1985
1995
  test_inconclusive="true"
1986
1996
  test_inconclusive_reason="no_tests_executed"
1987
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
1988
2005
  else
1989
2006
  # Missing test-results.json: no suite was recorded at all. Like the
1990
2007
  # runner=="none" case this is not affirmative evidence, so classify it
@@ -2175,7 +2192,8 @@ DETAILS_EOF
2175
2192
  boot_inconclusive_reason="no_app_runner"
2176
2193
  elif command -v python3 >/dev/null 2>&1; then
2177
2194
  local _boot_status
2178
- _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 ("", ".")]
2179
2197
  import json, os
2180
2198
  health_file = os.environ.get('_HEALTH', '')
2181
2199
  state_file = os.environ.get('_STATE', '')
@@ -2338,7 +2356,8 @@ PYEOF
2338
2356
  persist_inconclusive="true"; persist_inconclusive_reason="not_serveable"
2339
2357
  elif command -v python3 >/dev/null 2>&1; then
2340
2358
  local _p_status
2341
- _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 ("", ".")]
2342
2361
  import json, os
2343
2362
  p = os.environ['_PF']
2344
2363
  if not os.path.isfile(p):
@@ -2401,7 +2420,8 @@ PYEOF
2401
2420
  auth_inconclusive="true"; auth_inconclusive_reason="not_serveable"
2402
2421
  elif command -v python3 >/dev/null 2>&1; then
2403
2422
  local _a_status
2404
- _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 ("", ".")]
2405
2425
  import json, os
2406
2426
  p = os.environ['_PF']
2407
2427
  strict = os.environ.get('_STRICT', '1') != '0'
@@ -2479,7 +2499,8 @@ PYEOF
2479
2499
  authz_inconclusive="true"; authz_inconclusive_reason="not_serveable"
2480
2500
  elif command -v python3 >/dev/null 2>&1; then
2481
2501
  local _authz_status
2482
- _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 ("", ".")]
2483
2504
  import json, os
2484
2505
  p = os.environ['_PF']
2485
2506
  if not os.path.isfile(p):
@@ -2736,7 +2757,7 @@ PYEOF
2736
2757
  local timestamp
2737
2758
  timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
2738
2759
  local failures_json diff_ok tests_ok base_for_json
2739
- 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 ('', '.')]
2740
2761
  import json, os
2741
2762
  items = [s for s in os.environ['_FAILURES'].split('|') if s]
2742
2763
  print(json.dumps(items[:5]))
@@ -2753,11 +2774,11 @@ print(json.dumps(items[:5]))
2753
2774
  if [ "$authz_fails" = "true" ]; then authz_ok="false"; else authz_ok="true"; fi
2754
2775
  # Record WHY boot was inconclusive (no_app_runner / not_serveable / etc.) so a
2755
2776
  # consumer of the block report can tell a genuine boot pass from a pass-through.
2756
- boot_reason_json=$(_R="${boot_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2757
- nomock_reason_json=$(_R="${nomock_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2758
- persist_reason_json=$(_R="${persist_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2759
- auth_reason_json=$(_R="${auth_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
2760
- 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 '""')
2761
2782
  base_for_json="${base_sha:-}"
2762
2783
  cat > "$ev_tmp" << EVIDENCE_EOF
2763
2784
  {
@@ -3395,7 +3416,7 @@ council_heuristic_review() {
3395
3416
  local _tr_file=".loki/quality/test-results.json"
3396
3417
  local _tests_ok=0
3397
3418
  if [ -f "$_tr_file" ] && command -v python3 >/dev/null 2>&1; then
3398
- _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 ('', '.')]
3399
3420
  import json, os
3400
3421
  try:
3401
3422
  with open(os.environ['_TR']) as f:
@@ -3568,7 +3589,7 @@ council_evaluate_member() {
3568
3589
  local test_runner_seen="none"
3569
3590
  if [ -f "$tr_file" ]; then
3570
3591
  local _tr_status
3571
- _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 ('', '.')]
3572
3593
  import json, os, sys
3573
3594
  try:
3574
3595
  with open(os.environ['_TR_FILE']) as f:
@@ -3625,7 +3646,7 @@ else:
3625
3646
  local _q _qcount
3626
3647
  for _q in pending in-progress blocked; do
3627
3648
  [ -f "$loki_dir/queue/${_q}.json" ] || continue
3628
- _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
3629
3650
  d = json.load(open(os.environ['_QUEUE_FILE']))
3630
3651
  print(len(d.get('tasks', d) if isinstance(d, dict) else d))" 2>/dev/null || echo "0")
3631
3652
  # Guard against a non-numeric read (malformed file, python absent).
@@ -3762,7 +3783,7 @@ council_aggregate_votes() {
3762
3783
  # Serialize the votes array with json.dumps so backslashes/control chars are
3763
3784
  # escaped correctly (BUG fix: sed-only escaping produced invalid JSON).
3764
3785
  local votes_json
3765
- 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 ('', '.')]
3766
3787
  import json, os
3767
3788
  def lines(name):
3768
3789
  v = os.environ.get(name, '')
@@ -3805,7 +3826,7 @@ print(json.dumps(out))
3805
3826
  _VERDICT="$verdict" \
3806
3827
  _VOTES="$votes_json" \
3807
3828
  _ROUND_FILE="$round_file" \
3808
- python3 -c "
3829
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
3809
3830
  import json, os
3810
3831
  from datetime import datetime, timezone
3811
3832
  round_data = {
@@ -3866,7 +3887,7 @@ council_devils_advocate_review() {
3866
3887
  local tr_file="$loki_dir/quality/test-results.json"
3867
3888
  if [ -f "$tr_file" ]; then
3868
3889
  local _tr_status
3869
- _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 ('', '.')]
3870
3891
  import json, os, sys
3871
3892
  try:
3872
3893
  with open(os.environ['_TR_FILE']) as f:
@@ -3904,7 +3925,7 @@ else:
3904
3925
  # Skeptical check 2: Are there still failing tasks in the queue?
3905
3926
  if [ -f "$loki_dir/queue/failed.json" ]; then
3906
3927
  local failed_count
3907
- 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")
3908
3929
  if [ "$failed_count" -gt 0 ]; then
3909
3930
  ((issues_found++))
3910
3931
  issue_details="${issue_details}$failed_count tasks in failed queue; "
@@ -3950,7 +3971,7 @@ else:
3950
3971
  _ISSUES="$issues_found" \
3951
3972
  _DETAILS="${issue_details:-none}" \
3952
3973
  _DA_FILE="$da_file" \
3953
- python3 -c "
3974
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
3954
3975
  import json, os
3955
3976
  from datetime import datetime, timezone
3956
3977
  da_result = {
@@ -4046,7 +4067,7 @@ council_evaluate() {
4046
4067
  if loki_council_dispatch_agents "$ITERATION_COUNT" "${COUNCIL_PRD_PATH:-}"; then
4047
4068
  local _va_round_file="$COUNCIL_STATE_DIR/votes/round-${ITERATION_COUNT}.json"
4048
4069
  if [ -f "$_va_round_file" ]; then
4049
- 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 "")
4050
4071
  fi
4051
4072
  fi
4052
4073
  fi
@@ -4068,7 +4089,7 @@ council_evaluate() {
4068
4089
  local complete_count=0
4069
4090
  local members_present=0
4070
4091
  if [ -f "$round_file" ]; then
4071
- 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")
4072
4093
  # WAVE13 CRITICAL quorum gate: how many voters actually responded
4073
4094
  # (total_members records the ACTUAL returned count -- see
4074
4095
  # voter-agents.sh). A degraded/partial dispatch response must never
@@ -4076,7 +4097,7 @@ council_evaluate() {
4076
4097
  # somehow read COMPLETE. This is defense-in-depth: the parser
4077
4098
  # already forces CONTINUE on undercount, but the completion-detection
4078
4099
  # trust core must independently assert full quorum before stopping.
4079
- 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")
4080
4101
  fi
4081
4102
  # Normalize to integers (guard against empty/non-numeric on read failure)
4082
4103
  case "$complete_count" in (''|*[!0-9]*) complete_count=0 ;; esac
@@ -4172,7 +4193,7 @@ council_managed_should_stop() {
4172
4193
  diff_summary=$(cd "$project_dir" 2>/dev/null && git diff --stat 2>/dev/null | tail -20 | tr '\n' ' ' || echo "")
4173
4194
  local test_summary=""
4174
4195
  if [ -f "$loki_dir/quality/test-results.json" ]; then
4175
- 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 ('', '.')]
4176
4197
  import json, os
4177
4198
  try:
4178
4199
  d = json.load(open(os.environ['_TRF']))
@@ -4183,7 +4204,7 @@ except Exception:
4183
4204
  fi
4184
4205
  local pending_tasks="[]"
4185
4206
  if [ -f "$loki_dir/queue/pending.json" ]; then
4186
- 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 ('', '.')]
4187
4208
  import json, os
4188
4209
  try:
4189
4210
  d = json.load(open(os.environ['_QF']))
@@ -4206,7 +4227,8 @@ except Exception:
4206
4227
  _CC_VERDICTS_DIR="$verdicts_dir" \
4207
4228
  _CC_LOKI_DIR="$loki_dir" \
4208
4229
  LOKI_TARGET_DIR="${TARGET_DIR:-$(pwd)}" \
4209
- 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 ("", ".")]
4210
4232
  import json, os, sys, pathlib
4211
4233
 
4212
4234
  # Path setup: prefer project_dir so providers.managed resolves from main tree.
@@ -4383,7 +4405,7 @@ _council_convergence_evidence_green() {
4383
4405
  # file or runner=="none" (no suite) is NOT affirmative evidence -> not green.
4384
4406
  [ -f "$tr_file" ] || return 1
4385
4407
  local tr_state
4386
- 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 ('', '.')]
4387
4409
  import json, os, sys
4388
4410
  try:
4389
4411
  with open(os.environ['_TR_FILE']) as f:
@@ -4406,7 +4428,7 @@ print('yes' if (runner != 'none' and passed is True
4406
4428
  local results_file="${TARGET_DIR:-.}/.loki/checklist/verification-results.json"
4407
4429
  if [ -f "$results_file" ]; then
4408
4430
  local cl_state
4409
- 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 ('', '.')]
4410
4432
  import json, os, sys
4411
4433
  try:
4412
4434
  with open(os.environ['_RESULTS_FILE']) as f:
@@ -4655,7 +4677,7 @@ council_write_report() {
4655
4677
  REPORT_HEADER
4656
4678
 
4657
4679
  # Append vote history from state
4658
- _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 ('', '.')]
4659
4681
  import json, os
4660
4682
  try:
4661
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],
package/autonomy/run.sh CHANGED
@@ -2678,7 +2678,7 @@ _advance_current_phase() {
2678
2678
  [ -f "$orch" ] || return 0
2679
2679
  # Values are passed via argv (not interpolated into the source) so a phase or
2680
2680
  # path containing quotes can never break or inject into the python.
2681
- python3 -c "
2681
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
2682
2682
  import json, sys
2683
2683
  f, phase = sys.argv[1], sys.argv[2]
2684
2684
  try:
@@ -4377,7 +4377,7 @@ _loki_receipt_facts() {
4377
4377
  local pj="$loki_dir/proofs/$rid/proof.json"
4378
4378
  [ -f "$pj" ] || return 0
4379
4379
  local headline=""
4380
- headline="$(python3 -c "
4380
+ headline="$(python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4381
4381
  import json, sys
4382
4382
  try:
4383
4383
  d = json.load(open(sys.argv[1]))
@@ -4999,7 +4999,7 @@ except Exception:
4999
4999
  _LOKI_CS_ASSUMPTIONS_HIGH="$assumptions_high" \
5000
5000
  _LOKI_CS_OUT_FILE="$loki_dir/state/completion.json" \
5001
5001
  _LOKI_CS_LAST_ERROR="$loki_dir/state/LAST_ERROR.json" \
5002
- python3 -c "
5002
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
5003
5003
  import json, os, tempfile
5004
5004
  out = os.environ['_LOKI_CS_OUT_FILE']
5005
5005
  def i(v):
@@ -5219,7 +5219,7 @@ print_completion_card() {
5219
5219
  # split would collapse adjacent empties). Any failure leaves the card
5220
5220
  # unrendered. Trailing-newline guard: NUL-free, fields are single-line.
5221
5221
  local _fields
5222
- _fields="$(python3 -c "
5222
+ _fields="$(python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
5223
5223
  import json,sys
5224
5224
  try:
5225
5225
  d=json.load(open(sys.argv[1]))
@@ -5541,7 +5541,8 @@ validate_exec_manifest_result() {
5541
5541
  result_file=$(mktemp "${TARGET_DIR}/.loki/.exec-manifest-result.XXXXXX") || return 1
5542
5542
  LOKI_RESULT_FILE="$result_file" LOKI_RESULT_STREAM="$stream_name" \
5543
5543
  LOKI_RESULT_BASE="$base_sha" LOKI_RESULT_BRANCH="$branch" \
5544
- LOKI_RESULT_REPO="$TARGET_DIR" python3 <<'PY'
5544
+ LOKI_RESULT_REPO="$TARGET_DIR" python3 -E - <<'PY'
5545
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
5545
5546
  import json, os, subprocess
5546
5547
  paths = subprocess.check_output(
5547
5548
  ["git", "-C", os.environ["LOKI_RESULT_REPO"], "diff", "--name-only",
@@ -8098,7 +8099,7 @@ generate_proof_of_run() {
8098
8099
  ITERATION_COUNT="${ITERATION_COUNT:-0}" \
8099
8100
  PROVIDER_NAME="$provider" \
8100
8101
  PRD_PATH="${prd_path:-}" \
8101
- python3 "$gen" \
8102
+ python3 -E "$gen" \
8102
8103
  --loki-dir "$loki_dir" \
8103
8104
  --loki-version "$ver" \
8104
8105
  --provider "$provider" \
@@ -8125,7 +8126,7 @@ generate_proof_of_run() {
8125
8126
  ITERATION_COUNT="${ITERATION_COUNT:-0}" \
8126
8127
  PROVIDER_NAME="$provider" \
8127
8128
  PRD_PATH="${prd_path:-}" \
8128
- python3 "$gen" \
8129
+ python3 -E "$gen" \
8129
8130
  --loki-dir "$loki_dir" \
8130
8131
  --loki-version "$ver" \
8131
8132
  --provider "$provider" \
@@ -9132,6 +9133,21 @@ setup_agent_branch() {
9132
9133
 
9133
9134
  log_info "Branch protection enabled - creating agent branch: $branch_name (base: $cur)"
9134
9135
 
9136
+ # Record the user's own untracked (not ignored) files, repo-wide,
9137
+ # NUL-delimited and relative to the repo top, so commit_session_changes
9138
+ # and the receipt (autonomy/lib/workspace_diff.py) leave them alone. Taken
9139
+ # only when a branch is minted: on resume, untracked files can be the
9140
+ # agent's own uncommitted work. .loki/ is self-ignored above.
9141
+ local top="" snap=".loki/state/preexisting-untracked.z"
9142
+ if top="$(git rev-parse --show-toplevel 2>/dev/null)" \
9143
+ && git -C "$top" ls-files -z --others --exclude-standard > "$snap.tmp" 2>/dev/null \
9144
+ && mv -f "$snap.tmp" "$snap" 2>/dev/null; then
9145
+ :
9146
+ else
9147
+ rm -f "$snap.tmp" "$snap" 2>/dev/null
9148
+ log_warn "Could not record pre-existing untracked files; the session commit may include them"
9149
+ fi
9150
+
9135
9151
  # Create and checkout the feature branch
9136
9152
  if ! git checkout -b "$branch_name" 2>/dev/null; then
9137
9153
  log_error "Failed to create agent branch: $branch_name"
@@ -9210,6 +9226,24 @@ commit_session_changes() {
9210
9226
  ':!*.key' ':!*.pem' ':!*.p12' ':!*.keystore' \
9211
9227
  ':!id_rsa*' ':!*.token' ':!credentials*' 2>/dev/null || true
9212
9228
 
9229
+ # Unstage exactly the files that were untracked when the branch was minted
9230
+ # (setup_agent_branch): they are the user's, not this session's work, and
9231
+ # committing them here would delete them from disk on a later checkout of
9232
+ # the base. Agent-created files stay staged. Skip an empty snapshot: an
9233
+ # empty --pathspec-from-file resets the WHOLE index. No snapshot (older
9234
+ # session): behave as before. If the unstage fails (git < 2.25), commit
9235
+ # nothing rather than sweep the user's files in.
9236
+ local preexisting="$PWD/.loki/state/preexisting-untracked.z" top=""
9237
+ if [ -s "$preexisting" ]; then
9238
+ if ! { top="$(git rev-parse --show-toplevel 2>/dev/null)" \
9239
+ && git -C "$top" --literal-pathspecs reset -q \
9240
+ --pathspec-from-file="$preexisting" --pathspec-file-nul >/dev/null 2>&1; }; then
9241
+ git reset -q >/dev/null 2>&1 || true
9242
+ log_warn "Left uncommitted: could not exclude your pre-existing untracked files from the session commit (needs git 2.25+). Review and commit manually."
9243
+ return 0
9244
+ fi
9245
+ fi
9246
+
9213
9247
  # Nothing staged = clean no-op, never an error.
9214
9248
  if git diff --cached --quiet 2>/dev/null; then
9215
9249
  return 0
@@ -10227,7 +10261,8 @@ _loki_supervised_build_result_passes() {
10227
10261
  local result_file="$1"
10228
10262
  ! loki_is_supervised_simple_web && return 0
10229
10263
  [ -s "$result_file" ] || return 1
10230
- python3 - "$result_file" <<'PYEOF' 2>/dev/null
10264
+ python3 -E - "$result_file" <<'PYEOF' 2>/dev/null
10265
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
10231
10266
  import json
10232
10267
  import sys
10233
10268
 
@@ -10712,7 +10747,8 @@ SAEMPTYEOF
10712
10747
  # not product-specific copy or design opinions.
10713
10748
  local _ui_contract_out="" _ui_contract_rc=0
10714
10749
  _ui_contract_out=$(
10715
- _LOKI_CHANGED_FILES="$changed_files" python3 - "${TARGET_DIR:-.}" <<'PYEOF' 2>/dev/null
10750
+ _LOKI_CHANGED_FILES="$changed_files" python3 -E - "${TARGET_DIR:-.}" <<'PYEOF' 2>/dev/null
10751
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
10716
10752
  import os
10717
10753
  import re
10718
10754
  import sys
@@ -10778,8 +10814,14 @@ PYEOF
10778
10814
  [ -f "${TARGET_DIR:-.}/$f" ] || continue
10779
10815
  total_checked=$((total_checked + 1))
10780
10816
  local _py_compile_rc=0
10817
+ # Same check as `python3 -m py_compile` (importlib's source_to_code
10818
+ # is compile(bytes, path, "exec", dont_inherit=True)), but it
10819
+ # writes no __pycache__/*.pyc into the user's repo, where the
10820
+ # session commit would pick it up. -E and the sys.path filter
10821
+ # (D7): the repo under check must not supply modules to the gate.
10781
10822
  LOKI_DEADLINE_IDLE_TIMEOUT=0 _loki_with_deadline "$gate_timeout" \
10782
- python3 -m py_compile "${TARGET_DIR:-.}/$f" 2>&1 || _py_compile_rc=$?
10823
+ python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]; compile(open(sys.argv[1], "rb").read(), sys.argv[1], "exec", dont_inherit=True)' \
10824
+ "${TARGET_DIR:-.}/$f" 2>&1 || _py_compile_rc=$?
10783
10825
  if [ "$_py_compile_rc" -ne 0 ]; then
10784
10826
  findings=$((findings + 1))
10785
10827
  if [ "$_py_compile_rc" -eq 124 ]; then
@@ -11096,7 +11138,7 @@ SECEMPTY
11096
11138
 
11097
11139
  # Run the scanner. exit 0 = no findings, 1 = findings, 2 = bad input.
11098
11140
  local raw rc=0
11099
- raw=$(python3 "$scanner" "${TARGET_DIR:-.}" --json 2>/dev/null) || rc=$?
11141
+ raw=$(python3 -E "$scanner" "${TARGET_DIR:-.}" --json 2>/dev/null) || rc=$?
11100
11142
  if [ "$rc" -eq 2 ] || [ -z "$raw" ]; then
11101
11143
  cat > "$out_file" << 'SECEMPTY'
11102
11144
  {"rules_version":null,"findings":[],"summary":{"total":0,"by_severity":{}},"skipped":"scanner-error"}
@@ -11110,7 +11152,7 @@ SECEMPTY
11110
11152
  # It prints a final line: ACTIVE_HIGH=<n>\tACTIVE_TOTAL=<n>\tWAIVED=<n>
11111
11153
  # and writes the enriched receipt (findings carry a "waived" bool).
11112
11154
  local verdict
11113
- verdict=$(_SEC_RAW="$raw" _SEC_WAIVERS="$waivers_file" _SEC_OUT="$out_file" python3 -c '
11155
+ verdict=$(_SEC_RAW="$raw" _SEC_WAIVERS="$waivers_file" _SEC_OUT="$out_file" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
11114
11156
  import json, os, sys
11115
11157
  raw = os.environ.get("_SEC_RAW", "")
11116
11158
  waivers_file = os.environ.get("_SEC_WAIVERS", "")
@@ -11610,7 +11652,7 @@ measure_test_coverage() {
11610
11652
  --coverage.reportsDirectory=.loki/quality/vitest-cov >/dev/null 2>&1) || true
11611
11653
  local f="$target_dir/.loki/quality/vitest-cov/coverage-summary.json"
11612
11654
  if [ -f "$f" ]; then
11613
- COVERAGE_PCT=$(_LOKI_COV_F="$f" python3 -c "
11655
+ COVERAGE_PCT=$(_LOKI_COV_F="$f" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
11614
11656
  import json, os, sys
11615
11657
  try:
11616
11658
  d=json.load(open(os.environ['_LOKI_COV_F']))
@@ -11629,7 +11671,7 @@ except Exception:
11629
11671
  --coverageDirectory=.loki/quality/jest-cov --passWithNoTests >/dev/null 2>&1) || true
11630
11672
  local f="$target_dir/.loki/quality/jest-cov/coverage-summary.json"
11631
11673
  if [ -f "$f" ]; then
11632
- COVERAGE_PCT=$(_LOKI_COV_F="$f" python3 -c "
11674
+ COVERAGE_PCT=$(_LOKI_COV_F="$f" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
11633
11675
  import json, os, sys
11634
11676
  try:
11635
11677
  d=json.load(open(os.environ['_LOKI_COV_F']))
@@ -11649,7 +11691,7 @@ except Exception:
11649
11691
  _loki_run_pytest_with_timeout "$target_dir" \
11650
11692
  --cov --cov-report="json:$pyc_json" -q >/dev/null 2>&1 || true
11651
11693
  if [ -f "$pyc_json" ]; then
11652
- COVERAGE_PCT=$(_LOKI_COV_F="$pyc_json" python3 -c "
11694
+ COVERAGE_PCT=$(_LOKI_COV_F="$pyc_json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
11653
11695
  import json, os, sys
11654
11696
  try:
11655
11697
  d=json.load(open(os.environ['_LOKI_COV_F']))
@@ -11689,7 +11731,7 @@ except Exception:
11689
11731
  local out
11690
11732
  out=$(cd "$target_dir" && timeout "$gate_timeout" cargo llvm-cov --json 2>/dev/null) || true
11691
11733
  if [ -n "$out" ]; then
11692
- COVERAGE_PCT=$(_LOKI_COV_JSON="$out" python3 -c "
11734
+ COVERAGE_PCT=$(_LOKI_COV_JSON="$out" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
11693
11735
  import json, os, sys
11694
11736
  try:
11695
11737
  d=json.loads(os.environ['_LOKI_COV_JSON'])
@@ -12412,7 +12454,7 @@ TREOF
12412
12454
  local cov_below=false
12413
12455
  if [ "$COVERAGE_MEASURED" = "true" ] && [ -n "$COVERAGE_PCT" ]; then
12414
12456
  # Float-safe compare via python3 (pct may be e.g. 87.5).
12415
- if _LOKI_COV_PCT="$COVERAGE_PCT" _LOKI_COV_MIN="$min_coverage" python3 -c "
12457
+ if _LOKI_COV_PCT="$COVERAGE_PCT" _LOKI_COV_MIN="$min_coverage" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
12416
12458
  import os, sys
12417
12459
  try:
12418
12460
  pct=float(os.environ['_LOKI_COV_PCT']); mn=float(os.environ['_LOKI_COV_MIN'])
@@ -12437,7 +12479,7 @@ sys.exit(0 if pct < mn else 1)
12437
12479
  _LOKI_COV_BLOCKED="$coverage_block" \
12438
12480
  _LOKI_COV_RUNNER="$test_runner" \
12439
12481
  _LOKI_COV_OUT="$quality_dir/coverage.json" \
12440
- python3 -c "
12482
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
12441
12483
  import json, os, tempfile
12442
12484
  out=os.environ['_LOKI_COV_OUT']
12443
12485
  measured = os.environ.get('_LOKI_COV_MEASURED','false') == 'true'
@@ -12498,7 +12540,7 @@ os.replace(tmp, out)
12498
12540
  _LOKI_COV_BLOCKED="false" \
12499
12541
  _LOKI_COV_RUNNER="$test_runner" \
12500
12542
  _LOKI_COV_OUT="$quality_dir/coverage.json" \
12501
- python3 -c "
12543
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
12502
12544
  import json, os, tempfile
12503
12545
  out=os.environ['_LOKI_COV_OUT']
12504
12546
  measured = os.environ.get('_LOKI_COV_MEASURED','false') == 'true'
@@ -12767,7 +12809,7 @@ run_doc_quality_gate() {
12767
12809
  local manifest="$project_dir/.loki/docs/docs-manifest.json"
12768
12810
  if [ -f "$manifest" ]; then
12769
12811
  local doc_sha
12770
- doc_sha=$(python3 -c "import json; print(json.load(open('$manifest')).get('git_sha', ''))" 2>/dev/null)
12812
+ doc_sha=$(python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json; print(json.load(open('$manifest')).get('git_sha', ''))" 2>/dev/null)
12771
12813
  if [ -n "$doc_sha" ]; then
12772
12814
  local behind
12773
12815
  behind=$(git -C "$project_dir" rev-list --count "$doc_sha..HEAD" 2>/dev/null || echo "0")
@@ -13198,7 +13240,7 @@ enforce_lsp_diagnostics() {
13198
13240
  fi
13199
13241
 
13200
13242
  if [ -f "$lsp_file" ]; then
13201
- verdict=$(_LOKI_LSP_FILE="$lsp_file" python3 -c '
13243
+ verdict=$(_LOKI_LSP_FILE="$lsp_file" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
13202
13244
  import json, os, sys
13203
13245
  try:
13204
13246
  with open(os.environ["_LOKI_LSP_FILE"], encoding="utf-8") as handle:
@@ -13537,7 +13579,8 @@ council_verdicts_to_txt_files() {
13537
13579
  local out_dir_env="$review_dir"
13538
13580
  export LOKI_COUNCIL_OUT_DIR="$out_dir_env"
13539
13581
  export LOKI_COUNCIL_VERDICTS_JSON="$verdicts_json"
13540
- python3 << 'COUNCIL_WRITE'
13582
+ python3 -E - << 'COUNCIL_WRITE'
13583
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
13541
13584
  import json
13542
13585
  import os
13543
13586
  import re
@@ -13626,6 +13669,9 @@ _run_managed_review_council() {
13626
13669
 
13627
13670
  local result_json
13628
13671
  result_json=$(python3 << 'MANAGED_REVIEW' 2>&1
13672
+ # Drops the cwd (the agent's repo) from sys.path. No -E here: the
13673
+ # LOKI_MANAGED_REVIEW_FAKE_MODULE test hook below is found through PYTHONPATH.
13674
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
13629
13675
  import json
13630
13676
  import os
13631
13677
  import sys
@@ -13724,7 +13770,7 @@ MANAGED_REVIEW
13724
13770
  fi
13725
13771
 
13726
13772
  local status
13727
- status=$(printf '%s' "$result_json" | python3 -c "import json,sys; d=json.loads(sys.stdin.read() or '{}'); print(d.get('status',''))" 2>/dev/null || echo "")
13773
+ status=$(printf '%s' "$result_json" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json,sys; d=json.loads(sys.stdin.read() or '{}'); print(d.get('status',''))" 2>/dev/null || echo "")
13728
13774
 
13729
13775
  if [ "$status" != "ok" ]; then
13730
13776
  local reason
@@ -16384,7 +16430,8 @@ REVIEW_WAVE_TIMING
16384
16430
  export LOKI_REVIEW_AGG_QSCORE="$quality_score"
16385
16431
  export LOKI_REVIEW_AGG_QMED="$nonblocking_medium"
16386
16432
  export LOKI_REVIEW_AGG_QLOW="$nonblocking_low"
16387
- python3 << 'AGG_SCRIPT'
16433
+ python3 -E - << 'AGG_SCRIPT'
16434
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
16388
16435
  import json, os
16389
16436
  result = {
16390
16437
  "review_id": os.environ["LOKI_REVIEW_AGG_ID"],
@@ -16527,7 +16574,8 @@ AGG_SCRIPT
16527
16574
 
16528
16575
  _LOKI_DA_AGG_FILE="$review_dir/$review_id/aggregate.json" \
16529
16576
  _LOKI_DA_STATUS="$da_status" _LOKI_DA_RC="$da_dispatch_rc" \
16530
- _LOKI_DA_SPECULATIVE="$da_speculative" python3 <<'DA_AGG_PATCH' 2>/dev/null || true
16577
+ _LOKI_DA_SPECULATIVE="$da_speculative" python3 -E - <<'DA_AGG_PATCH' 2>/dev/null || true
16578
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
16531
16579
  import json
16532
16580
  import os
16533
16581
 
@@ -17895,7 +17943,7 @@ is_completed() {
17895
17943
  # Check orchestrator state
17896
17944
  if [ -f ".loki/state/orchestrator.json" ]; then
17897
17945
  if command -v python3 &> /dev/null; then
17898
- local phase=$(python3 -c "import json; print(json.load(open('.loki/state/orchestrator.json')).get('currentPhase', ''))" 2>/dev/null || echo "")
17946
+ local phase=$(python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json; print(json.load(open('.loki/state/orchestrator.json')).get('currentPhase', ''))" 2>/dev/null || echo "")
17899
17947
  # Accept various completion states
17900
17948
  if [ "$phase" = "COMPLETED" ] || [ "$phase" = "complete" ] || [ "$phase" = "finalized" ] || [ "$phase" = "growth-loop" ]; then
17901
17949
  return 0
@@ -21909,7 +21957,8 @@ _loki_supervised_completion_gates_pass() {
21909
21957
  local test_iteration="$quality_dir/.test-results.iter"
21910
21958
  if [ ! -s "$test_results" ] || [ ! -s "$test_iteration" ] \
21911
21959
  || [ "$(tr -d '[:space:]' < "$test_iteration" 2>/dev/null)" != "${ITERATION_COUNT:-0}" ] \
21912
- || ! python3 - "$test_results" <<'PYEOF' 2>/dev/null
21960
+ || ! python3 -E - "$test_results" <<'PYEOF' 2>/dev/null
21961
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
21913
21962
  import json
21914
21963
  import sys
21915
21964
 
@@ -27102,7 +27151,7 @@ main() {
27102
27151
  local _terminal_status=""
27103
27152
  local _terminal_state_file
27104
27153
  _terminal_state_file="$(_loki_state_file)"
27105
- _terminal_status=$(LOKI_STATE_FILE="$_terminal_state_file" python3 -c "import json, os; print(json.load(open(os.environ['LOKI_STATE_FILE'])).get('status',''))" 2>/dev/null || true)
27154
+ _terminal_status=$(LOKI_STATE_FILE="$_terminal_state_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['LOKI_STATE_FILE'])).get('status',''))" 2>/dev/null || true)
27106
27155
  case "$_terminal_status" in
27107
27156
  deterministic_gates_passed|council_approved|council_force_approved|completion_promise_fulfilled|reuse_already_satisfied)
27108
27157
  if [ "$result" = "0" ]; then
@@ -27163,7 +27212,7 @@ main() {
27163
27212
  # Refresh its durable files against the final HEAD without notifying twice.
27164
27213
  local _completion_file="${TARGET_DIR:-.}/.loki/state/completion.json"
27165
27214
  local _completion_outcome=""
27166
- _completion_outcome=$(LOKI_COMPLETION_FILE="$_completion_file" python3 -c '
27215
+ _completion_outcome=$(LOKI_COMPLETION_FILE="$_completion_file" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
27167
27216
  import json, os
27168
27217
  try:
27169
27218
  value = json.load(open(os.environ["LOKI_COMPLETION_FILE"], encoding="utf-8")).get("outcome", "")
@@ -27288,7 +27337,7 @@ except Exception:
27288
27337
  if [ "${LOKI_DURABLE_STATE:-0}" = "1" ]; then
27289
27338
  local _final_status _final_state_file
27290
27339
  _final_state_file="$(_loki_state_file)"
27291
- _final_status=$(LOKI_STATE_FILE="$_final_state_file" python3 -c "import json, os; print(json.load(open(os.environ['LOKI_STATE_FILE'])).get('status','unknown'))" 2>/dev/null || echo "unknown")
27340
+ _final_status=$(LOKI_STATE_FILE="$_final_state_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['LOKI_STATE_FILE'])).get('status','unknown'))" 2>/dev/null || echo "unknown")
27292
27341
  case "$_final_status" in
27293
27342
  council_approved|council_force_approved|deterministic_gates_passed|completion_promise_fulfilled|paused|interrupted|stopped)
27294
27343
  result=0 ;;
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "9.52.0"
10
+ __version__ = "9.53.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.52.0
5
+ **Version:** v9.53.0
6
6
 
7
7
  ---
8
8
 
@@ -80,6 +80,16 @@ Status values: todo, in progress, shipped (version), parked (reason).
80
80
  51. **With `-E`, cryptography supplied via `PYTHONPATH`/`PYTHONUSERBASE` reads NOT CHECKED**; say so in the NOT CHECKED message.
81
81
  52. **CI check name:** "Moat suite" shows green at 0 of 9; consider a name that says "no rule failed" so the check mark is not read as "moat proven" (the step summary already carries the count).
82
82
 
83
+ 53. **Checklist verification still imports from the agent repo:** `autonomy/prd-checklist.sh` (a script call near 994 and about 9 inline `python3 -c` sites) is outside the cycle-2 fix; apply D7 there next (cycle-2 slice S).
84
+ 54. **Council helpers still unguarded:** `autonomy/lib/voter-agents.sh` (3 sites parse voter output), `autonomy/council-v2.sh` (7), `autonomy/lib/done-recognition.sh` (16), `autonomy/lib/proof-check.sh` (3); about 217 non-verdict inline sites in `run.sh`; script-form calls (claim_grounding, requirements helper, deadline.py) inherit `PYTHONPATH`. Managed council inserts `PROJECT_DIR or os.getcwd()` on `sys.path` behind experimental flags.
85
+ 55. **Zero-test honesty on bash:** `enforce_test_coverage`'s #82 zero-test branch downgrades only the JSON record; `test_passed` stays true, so `run.sh` (~12533) still touches `quality/unit-tests.pass` and logs "passed". `evidence-gate-details.json` records `tests.pass: true` for every inconclusive outcome.
86
+ 56. **Bun npm-test fallback has no zero-test detection**, and the Bun reader uses `passed`/`failed` while the bash writer emits `passed_count`/`failed_count` (the detail shows an unmeasured failed=0).
87
+
88
+ 57. **Resume must re-snapshot untracked files (data risk).** The snapshot is taken only when a session branch is minted; on the resume and already-on-loki paths (`run.sh` ~9100-9119) a file the user created between sessions is swept into the commit and removed from disk on checkout of the base. Add the current untracked list to the snapshot on both reuse paths (cycle-2 council).
89
+ 58. **Gitignored user files are swept when the agent rewrites `.gitignore` (data risk).** Also snapshot `git ls-files -z --others --ignored --exclude-standard --directory` and prefix-match directory entries in `workspace_diff` (cycle-2 council).
90
+ 59. **Agent edits to pre-existing untracked files are omitted from the receipt (new in v9.53.0).** Store a blob hash per snapshot entry; list a path whose hash changed (for example `preexisting_modified`) while still never committing the user's file (cycle-2 council). Not a false pass: no verdict reads the list.
91
+ 60. **Bun `runTestCoverage` accepts a stale `test-results.json`** (no freshness check against `.loki/quality/.test-results.iter`), and an inconclusive coverage still lands in `GateOutcome.passed[]` with an "(gate did not run)" log line.
92
+
83
93
  ## Later milestones
84
94
 
85
95
  M1 one command, M2 Seal and Wall, M3 assign like a teammate, M4 system map, M5 the line, M6 ship and operate, M7 one screen, M8 legacy lane, M9 enterprise readiness, M10 simplify and ship v10.0.0. Items get broken out here when their milestone comes up.
@@ -42,5 +42,6 @@ produced it. "Not measured" is a real value here: nothing is estimated.
42
42
 
43
43
  | Property | Status | Command |
44
44
  |---|---|---|
45
- | all 9 | 0 of 9 proven; 45 cases, 21 pass, 24 pending with milestones (2026-09-25, v9.52.0, macOS and ubuntu CI) | `bash tests/moat/run.sh` |
45
+ | all 9 | 1 of 9 proven (P6); 48 cases, 25 pass, 23 pending with milestones (2026-09-26, v9.53.0) | `bash tests/moat/run.sh` |
46
+ | history | v9.52.0: 0 of 9, 45 cases, 21 pass, 24 pending | - |
46
47
  | runtime | about 15s locally (9 scripts in parallel), about 1m15s CI job | `time bash tests/moat/run.sh` |
@@ -23,13 +23,27 @@
23
23
  - Council rounds 4-8 each found one more real verifier hole, all fixed with red-then-green tests: `--help` skipped the check (rounds 4-5: verify now never exits 0 without a verdict); the checkout could supply the verifier's Python modules through `python3 -` (round 6), through `PYTHONPATH`/`sitecustomize.py` (round 7: `python3 -E`, D7), and through chain stage subprocesses (round 8).
24
24
  - Council round 9: **3 of 3 APPROVE** at `5119f897`. One unreproduced observation (P1.modified-field-fails failed once in 7 local runs; 60 further runs under load passed).
25
25
  - Release v9.52.0 cut: version bump, CHANGELOG, dashboard rebuild (no diff), dist rebuild, tarball smoke-tested from a fresh PATH.
26
+ - **v9.52.0 verified on all channels (2026-09-26):** Release run green (required-ci: Tests, Bun Parity, Security Audit at `7c280ead`); npm `latest` 9.52.0 with `gitHead` `7c280ead`; tag `v9.52.0^{}` = `7c280ead`; GitHub release published; Docker Hub `9.52.0` amd64 + arm64; Homebrew formula sha256 equals the downloaded release tarball. Published package smoke-tested on both routes from a fresh PATH (reports 9.52.0; `verify -h` 64). Validation PR #216 auto-merged by GitHub; its branch is gone.
26
27
 
27
28
  ## Shipped in v10 program
28
29
 
30
+ - **v9.53.0** (2026-09-26): P6 in-place brownfield proven (moat 1 of 9); user untracked files never swept into session commits; no bytecode from the syntax gate; Bun gate never passes an inconclusive result; council and runner verdict paths cannot import from the agent repo.
29
31
  - **v9.52.0** (2026-09-25): moat suite `tests/moat/` (45 cases, 0 of 9 properties proven, 24 pending with milestones, ratchet baseline set by this release); verifier fixes (Bun `--jwks`, stripped signature, malformed tokens, strict flags and help, `python3 -E` on every verify path, exit contract 64/66); council `pass` readers fail closed; honest `doctor --airgap`; main-red fix.
30
32
 
31
- ## Next
33
+ ### Cycle 2 (2026-09-26)
32
34
 
33
- 1. Verify v9.52.0 on npm, Docker, Homebrew and the GitHub release; close validation PR #216.
34
- 2. BACKLOG 15 (untracked files swept into the session commit: data risk), 37 (Bun passes an inconclusive test result) and 43 (inline Python on council verdict paths importable from the agent repo): honest-verdict and data-risk items first.
35
- 3. Then P7 (no fabricated data) and P9 (Rule of Two), the moat gaps with the smallest fix, and M0 measurement (catalog + seeded-defect corpus).
35
+ - Dev fleet (3 worktree slices): untracked-file sweep and bytecode (BACKLOG 15, 16), Bun inconclusive pass and council reason naming (37, 38), cwd shadowing on council and runner verdict paths (43). Integrated plus the D7 guard on the new syntax check.
36
+ - Moat: **1 of 9 proven** (P6); ratchet live against v9.52.0 (23 pending, 48 registered).
37
+ - Council: 3 of 3 APPROVE on the first round. Non-blocking data-safety findings recorded as BACKLOG 57-60.
38
+ - Released as v9.53.0.
39
+
40
+ ## Next (after cycle 2)
41
+
42
+ 1. Brownfield data safety: BACKLOG 57, 58, 59 (resume re-snapshot, gitignored files, receipt omission).
43
+ 2. Honest verdict: BACKLOG 53-56, 60 (checklist verification and council helpers under D7; zero-test record; stale results).
44
+ 3. Then P7 (no fabricated data), P9 (Rule of Two), M0 measurement.
45
+
46
+ ## Next (before cycle 2, kept for history)
47
+
48
+ 1. Cycle 2: BACKLOG 15 (untracked files swept into the session commit: data risk), 37 (Bun passes an inconclusive test result) and 43 (inline Python on council verdict paths importable from the agent repo): honest-verdict and data-risk items first.
49
+ 2. Then P7 (no fabricated data) and P9 (Rule of Two), the moat gaps with the smallest fix, and M0 measurement (catalog + seeded-defect corpus).
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var mt=Object.create;var{getPrototypeOf:ut,defineProperty:jG,getOwnPropertyNames:dt}=Object;var pt=Object.prototype.hasOwnProperty;function ct($){return this[$]}var lt,it,at=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?lt??=new WeakMap:it??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?mt(ut($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of dt($))if(!pt.call(J,q))jG(J,q,{get:ct.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var ot=($)=>$;function st($,X){this[$]=ot.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:st.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var WR={};B1(WR,{lokiDir:()=>h0,homeLokiDir:()=>GQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as nt}from"url";import{existsSync as Zq}from"fs";import{homedir as rt}from"os";function tt(){let $=HR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(HR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function GQ(){return h2(rt(),".loki")}var HR,L1;var k1=s(()=>{HR=LG(nt(import.meta.url));L1=tt()});import{readFileSync as et}from"fs";import{resolve as $e,dirname as Xe}from"path";import{fileURLToPath as Qe}from"url";function j9(){if(n3!==null)return n3;let $="9.52.0";if(typeof $==="string"&&$.length>0)return n3=$,n3;try{let X=Xe(Qe(import.meta.url)),Q=AG(X);n3=et($e(Q,"VERSION"),"utf-8").trim()}catch{n3="unknown"}return n3}var n3=null;var Kq=s(()=>{k1()});var LR={};B1(LR,{runOrThrow:()=>je,run:()=>$1,readStreamCapped:()=>Jq,commandVersion:()=>Ae,commandExists:()=>g5,ShellError:()=>TG,MAX_STDOUT_BYTES:()=>jR});async function Jq($,X=jR){let Q=$.getReader(),z=new TextDecoder,Z="",K=0;try{while(K<X){let{done:J,value:q}=await Q.read();if(J)break;if(!q)continue;if(K+=q.byteLength,K>X){let V=q.byteLength-(K-X);Z+=z.decode(q.subarray(0,V),{stream:!0});break}Z+=z.decode(q,{stream:!0})}Z+=z.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return Z}async function $1($,X={}){let Q=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),z,Z;if(X.timeoutMs&&X.timeoutMs>0)z=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[K,J,q]=await Promise.all([Jq(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:K,stderr:J,exitCode:q}}finally{if(z)clearTimeout(z);if(Z)clearTimeout(Z)}}async function je($,X={}){let Q=await $1($,X);if(Q.exitCode!==0)throw new TG(`command failed (${Q.exitCode}): ${$.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function g5($){let X=Le($),Q=await $1(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Le($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function Ae($,X="--version"){if(!await g5($))return null;let z=await $1([$,X],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var jR=16777216,TG;var y8=s(()=>{TG=class TG extends Error{message;exitCode;stdout;stderr;constructor($,X,Q,z){super($);this.message=$;this.exitCode=X;this.stdout=Q;this.stderr=z;this.name="ShellError"}}});function g2($){return Ce?"":$}var Ce,p0,$5,z1,R61,A1,f1,m5,n;var t7=s(()=>{Ce=(process.env.NO_COLOR??"").length>0;p0=g2("\x1B[0;31m"),$5=g2("\x1B[0;32m"),z1=g2("\x1B[1;33m"),R61=g2("\x1B[0;34m"),A1=g2("\x1B[0;36m"),f1=g2("\x1B[1m"),m5=g2("\x1B[2m"),n=g2("\x1B[0m")});import{existsSync as ke}from"fs";async function Z2(){if(BQ!==void 0)return BQ;let $="/opt/homebrew/bin/python3.12";if(ke($))return BQ=$,$;let X=await g5("python3.12");if(X)return BQ=X,X;let Q=await g5("python3");return BQ=Q,Q}async function I4($,X={}){let Q=await Z2();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return $1([Q,"-c",$],X)}var BQ;var m2=s(()=>{y8()});var gR={};B1(gR,{runStatus:()=>Q00});import{existsSync as u5,readFileSync as C9,readdirSync as kR,statSync as SR}from"fs";import{resolve as A5,basename as ae}from"path";import{homedir as oe}from"os";function yR($){let X=Math.trunc($);if(X>=1e6)return`${(Math.trunc(X/1e6*10)/10).toFixed(1)}M`;if(X>=1000)return`${(Math.trunc(X/1000*10)/10).toFixed(1)}K`;return String(X)}function bR($,X,Q){if(X===0)return null;let z=Math.trunc($*100/X),Z=Math.trunc($*Vq/X);if(Z>Vq)Z=Vq;let K=Vq-Z,J=$5;if(z>=80)J=p0;else if(z>=50)J=z1;let q="=".repeat(Math.max(0,Z))+" ".repeat(Math.max(0,K)),V=yR($),Y=yR(X);return` ${f1}${Q}${n} ${J}[${q}]${n} ${z}% (${V} / ${Y})`}async function ne(){if(await g5("jq"))return!0;return process.stdout.write(`${p0}Error: jq is required but not installed.${n}
2
+ var mt=Object.create;var{getPrototypeOf:ut,defineProperty:jG,getOwnPropertyNames:dt}=Object;var pt=Object.prototype.hasOwnProperty;function ct($){return this[$]}var lt,it,at=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?lt??=new WeakMap:it??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?mt(ut($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of dt($))if(!pt.call(J,q))jG(J,q,{get:ct.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var ot=($)=>$;function st($,X){this[$]=ot.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:st.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var WR={};B1(WR,{lokiDir:()=>h0,homeLokiDir:()=>GQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as nt}from"url";import{existsSync as Zq}from"fs";import{homedir as rt}from"os";function tt(){let $=HR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(HR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function GQ(){return h2(rt(),".loki")}var HR,L1;var k1=s(()=>{HR=LG(nt(import.meta.url));L1=tt()});import{readFileSync as et}from"fs";import{resolve as $e,dirname as Xe}from"path";import{fileURLToPath as Qe}from"url";function j9(){if(n3!==null)return n3;let $="9.53.0";if(typeof $==="string"&&$.length>0)return n3=$,n3;try{let X=Xe(Qe(import.meta.url)),Q=AG(X);n3=et($e(Q,"VERSION"),"utf-8").trim()}catch{n3="unknown"}return n3}var n3=null;var Kq=s(()=>{k1()});var LR={};B1(LR,{runOrThrow:()=>je,run:()=>$1,readStreamCapped:()=>Jq,commandVersion:()=>Ae,commandExists:()=>g5,ShellError:()=>TG,MAX_STDOUT_BYTES:()=>jR});async function Jq($,X=jR){let Q=$.getReader(),z=new TextDecoder,Z="",K=0;try{while(K<X){let{done:J,value:q}=await Q.read();if(J)break;if(!q)continue;if(K+=q.byteLength,K>X){let V=q.byteLength-(K-X);Z+=z.decode(q.subarray(0,V),{stream:!0});break}Z+=z.decode(q,{stream:!0})}Z+=z.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return Z}async function $1($,X={}){let Q=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),z,Z;if(X.timeoutMs&&X.timeoutMs>0)z=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[K,J,q]=await Promise.all([Jq(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:K,stderr:J,exitCode:q}}finally{if(z)clearTimeout(z);if(Z)clearTimeout(Z)}}async function je($,X={}){let Q=await $1($,X);if(Q.exitCode!==0)throw new TG(`command failed (${Q.exitCode}): ${$.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function g5($){let X=Le($),Q=await $1(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Le($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function Ae($,X="--version"){if(!await g5($))return null;let z=await $1([$,X],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var jR=16777216,TG;var y8=s(()=>{TG=class TG extends Error{message;exitCode;stdout;stderr;constructor($,X,Q,z){super($);this.message=$;this.exitCode=X;this.stdout=Q;this.stderr=z;this.name="ShellError"}}});function g2($){return Ce?"":$}var Ce,p0,$5,z1,R61,A1,f1,m5,n;var t7=s(()=>{Ce=(process.env.NO_COLOR??"").length>0;p0=g2("\x1B[0;31m"),$5=g2("\x1B[0;32m"),z1=g2("\x1B[1;33m"),R61=g2("\x1B[0;34m"),A1=g2("\x1B[0;36m"),f1=g2("\x1B[1m"),m5=g2("\x1B[2m"),n=g2("\x1B[0m")});import{existsSync as ke}from"fs";async function Z2(){if(BQ!==void 0)return BQ;let $="/opt/homebrew/bin/python3.12";if(ke($))return BQ=$,$;let X=await g5("python3.12");if(X)return BQ=X,X;let Q=await g5("python3");return BQ=Q,Q}async function I4($,X={}){let Q=await Z2();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return $1([Q,"-c",$],X)}var BQ;var m2=s(()=>{y8()});var gR={};B1(gR,{runStatus:()=>Q00});import{existsSync as u5,readFileSync as C9,readdirSync as kR,statSync as SR}from"fs";import{resolve as A5,basename as ae}from"path";import{homedir as oe}from"os";function yR($){let X=Math.trunc($);if(X>=1e6)return`${(Math.trunc(X/1e6*10)/10).toFixed(1)}M`;if(X>=1000)return`${(Math.trunc(X/1000*10)/10).toFixed(1)}K`;return String(X)}function bR($,X,Q){if(X===0)return null;let z=Math.trunc($*100/X),Z=Math.trunc($*Vq/X);if(Z>Vq)Z=Vq;let K=Vq-Z,J=$5;if(z>=80)J=p0;else if(z>=50)J=z1;let q="=".repeat(Math.max(0,Z))+" ".repeat(Math.max(0,K)),V=yR($),Y=yR(X);return` ${f1}${Q}${n} ${J}[${q}]${n} ${z}% (${V} / ${Y})`}async function ne(){if(await g5("jq"))return!0;return process.stdout.write(`${p0}Error: jq is required but not installed.${n}
3
3
  `),process.stdout.write(`Install with:
4
4
  `),process.stdout.write(` brew install jq (macOS)
5
5
  `),process.stdout.write(` apt install jq (Debian/Ubuntu)
@@ -1129,7 +1129,7 @@ ${X}`:X;await Bun.write($,z)}function hF(){let $=JG("LOKI_CLAUDE_CLI","claude");
1129
1129
  `)){let J=K.match(/^(#{1,3})\s+(.+?)\s*$/);if(J&&J[1]&&J[2]!==void 0){let V=J[1].length,U=J[2].replace(/^\d+(\.\d+)*\.?\s*/,"").trim();if(V<=2){Z=X.test(U);continue}if(V===3&&!Z&&U.length>5&&!z.has(U))Q.push(U),z.add(U);continue}if(Z)continue;if(K.length>0&&(K[0]===" "||K[0]==="\t"))continue;let q=K.match(/^(?:\d+[.)]\s*|-\s+|\*\s+)(.+)$/);if(q&&q[1]){let V=q[1].trim();if(V.length>10&&!z.has(V))Q.push(V),z.add(V)}}return Q}function uF($,X){if(X<=3)return"high";let Q=X/3;if($<Q)return"high";if($<2*Q)return"medium";return"low"}async function c21($){let X=$.prdPath;if(!X||!a5(X))return;let Q=o5($.lokiDir,"queue"),z=o5(Q,".prd-populated");if(a5(z))return;for(let q of[".openspec-populated",".bmad-populated",".mirofish-populated"])if(a5(o5(Q,q)))return;let Z;try{Z=eJ(X,"utf8")}catch{return}let K=p21(d21(Z));if(K.length===0)return;if(!a5(Q))qG(Q,{recursive:!0});let J=o5(Q,"pending.json");await CQ(J,async()=>{if(a5(z))return;for(let H of[".openspec-populated",".bmad-populated",".mirofish-populated"])if(a5(o5(Q,H)))return;let{tasks:q,wrapper:V}=VG(J),Y=new Set(q.map((H)=>H.id));for(let H=0;H<K.length;H++){let W=`prd-${String(H+1).padStart(3,"0")}`;if(Y.has(W))continue;let G=K[H];q.push({id:W,title:G,description:G,priority:uF(H,K.length),status:"pending",source:"prd",acceptance_criteria:[`Feature implemented: ${G}`,"Tests added or updated for the feature","Quality gates pass (lint, typecheck, unit tests)"],notes:[],logs:[]})}let U=V?{...V,tasks:q}:q;YG(J,U),l3(z,"")})}function gr($,X){if(!a5($))return[];let Q;try{Q=h21($)}catch{return[]}let z=[];for(let Z of Q){if(Z.startsWith("."))continue;if(!Z.toLowerCase().endsWith(".md"))continue;if(!X(Z))continue;let K=o5($,Z);try{if(!m21(K).isFile())continue}catch{continue}z.push(K)}return z.sort(),z}function mr($){return($.split("/").pop()??$).replace(/\.md$/i,"")}function ur($,X){for(let Q of $.split(`
1130
1130
  `)){let z=Q.match(/^#\s+(.+?)\s*$/);if(z&&z[1])return z[1].trim()}return X}function l21($,X){let Q=$.split(`
1131
1131
  `),z=[];for(let K=0;K<Q.length;K++){let q=(Q[K]??"").match(/^##\s+(.+?)\s*$/);if(q&&q[1])z.push({idx:K,title:q[1].trim()})}if(z.length===0){let K=ur($,X);return[{title:K,description:K}]}let Z=[];for(let K=0;K<z.length;K++){let J=z[K],q=K+1<z.length?z[K+1].idx:Q.length,V=Q.slice(J.idx+1,q),Y=J.title;for(let U of V){let H=U.trim();if(H.length===0)continue;if(H.startsWith("#"))continue;Y=H;break}Z.push({title:J.title,description:Y})}return Z}async function i21($){let X=o5($.lokiDir,"bmad"),Q=o5($.lokiDir,"queue"),z=o5(Q,".bmad-populated");if(a5(z))return;let Z=gr(X,()=>!0);if(Z.length===0)return;let K=[];for(let q of Z){let V;try{V=eJ(q,"utf8")}catch{continue}for(let Y of l21(V,mr(q))){if(Y.title.length===0)continue;K.push(Y)}}if(K.length===0)return;if(!a5(Q))qG(Q,{recursive:!0});let J=o5(Q,"pending.json");await CQ(J,async()=>{if(a5(z))return;let{tasks:q,wrapper:V}=VG(J),Y=new Set(q.map((W)=>W.id)),U=0;for(let W=0;W<K.length;W++){let G=`bmad-${String(W+1).padStart(3,"0")}`;if(Y.has(G))continue;let N=K[W];q.push({id:G,title:N.title,description:N.description,priority:uF(W,K.length),status:"pending",source:"bmad"}),Y.add(G),U++}if(U===0){l3(z,"");return}let H=V?{...V,tasks:q}:q;YG(J,H),l3(z,"")})}async function a21($){let X=o5($.lokiDir,"openspec"),Q=o5($.lokiDir,"queue"),z=o5(Q,".openspec-populated");if(a5(z))return;let Z=gr(X,(q)=>q.toLowerCase().startsWith("spec-"));if(Z.length===0)return;let K=[];for(let q of Z){let V;try{V=eJ(q,"utf8")}catch{continue}let Y=mr(q),U=ur(V,Y);if(U.length===0)continue;K.push({title:U,description:`[OpenSpec] ${U}`})}if(K.length===0)return;if(!a5(Q))qG(Q,{recursive:!0});let J=o5(Q,"pending.json");await CQ(J,async()=>{if(a5(z))return;let{tasks:q,wrapper:V}=VG(J),Y=new Set(q.map((W)=>W.id)),U=0;for(let W=0;W<K.length;W++){let G=`openspec-${String(W+1).padStart(3,"0")}`;if(Y.has(G))continue;let N=K[W];q.push({id:G,title:N.title,description:N.description,priority:uF(W,K.length),status:"pending",source:"openspec"}),Y.add(G),U++}if(U===0){l3(z,"");return}let H=V?{...V,tasks:q}:q;YG(J,H),l3(z,"")})}var pr=s(()=>{w4()});var ir={};B1(ir,{checkCompletionPromise:()=>n21});import{existsSync as dF,readFileSync as o21,statSync as s21,unlinkSync as pF}from"fs";import{resolve as cr}from"path";async function n21($,X){let Q=cr($.lokiDir,"signals","TASK_COMPLETION_CLAIMED"),z=cr($.lokiDir,"signals","COMPLETION_REQUESTED");if(dF(Q)){try{pF(Q)}catch{}try{pF(z)}catch{}return!0}if(dF(z)){try{pF(z)}catch{}return!0}if(process.env.LOKI_LEGACY_COMPLETION_MATCH!=="true")return!1;if(!$.completionPromise)return!1;if(!X||!dF(X))return!1;try{if(s21(X).size===0)return!1;let K=o21(X);return(K.byteLength<=lr?K.toString("utf8"):K.subarray(K.byteLength-lr).toString("utf8")).includes($.completionPromise)}catch{return!1}}var lr=65536;var ar=()=>{};var Ct={};B1(Ct,{trackGateFailure:()=>rr,stubReviewer:()=>U31,selectReviewers:()=>Vt,runTestCoverage:()=>Xt,runStaticAnalysis:()=>$t,runSemanticTests:()=>Zt,runQualityGates:()=>P31,runMutationIntegrity:()=>zt,runMockIntegrity:()=>Qt,runMagicDebateGate:()=>jt,runLSPDiagnostics:()=>Lt,runInvariants:()=>Kt,runDocQualityGate:()=>Ot,runCodeReview:()=>sF,resolveDefaultReviewer:()=>Ht,rematerializeCodeReview:()=>lF,readToggles:()=>At,parseVerdict:()=>aF,matchesSeverityAnyForm:()=>oF,isHealingActive:()=>qt,getGateFailureCount:()=>Q31,filesFromDiff:()=>Mt,countNonBlockingFindings:()=>Bt,computeMergeabilityScore:()=>Nt,clearGateFailure:()=>tr,claudeReviewer:()=>iF,buildReviewerPrompt:()=>Yt,buildDevilsAdvocatePrompt:()=>Wt,REVIEWER_UNAVAILABLE_MARKER:()=>Ut});import{existsSync as L5,mkdirSync as qQ,readdirSync as r21,readFileSync as T4,rmSync as rF,statSync as t21,writeFileSync as x2}from"fs";import{createRequire as e21}from"module";import{dirname as WG,join as K1}from"path";function $31(){if(UG!==null)return UG;try{return UG=e21(import.meta.url)("./escalation_handoff.ts"),UG}catch{return null}}function X31(){let $=(X,Q)=>{let z=process.env[X];if(z===void 0||z==="")return Q;let Z=Number.parseInt(z,10);return Number.isFinite(Z)&&Z>0?Z:Q};return{repeat:$("LOKI_GATE_CLEAR_LIMIT",3),escalate:$("LOKI_GATE_ESCALATE_LIMIT",5),pause:$("LOKI_GATE_PAUSE_LIMIT",10)}}function GG($){return K1($,"quality","gate-failure-count.json")}function tF($){return $??h0()}function eF($){let X=GG($);if(!L5(X))return{};try{let Q=JSON.parse(T4(X,"utf-8"));if(Q===null||typeof Q!=="object"||Array.isArray(Q))return{};let z={};for(let[Z,K]of Object.entries(Q))if(typeof K==="number"&&Number.isFinite(K))z[Z]=K;return z}catch{return{}}}function nr($,X){T7(GG($),`${JSON.stringify(X,null,2)}
1132
- `)}function rr($,X){let Q=tF(X);return D7(GG(Q),()=>{let z=eF(Q),Z=(z[$]??0)+1;return z[$]=Z,nr(Q,z),Z})}function tr($,X){let Q=tF(X),z=GG(Q);if(!L5(z))return;D7(z,()=>{let Z=eF(Q);Z[$]=0,nr(Q,Z)})}function Q31($,X){return eF(tF(X))[$]??0}function X2($){let X=`LOKI_STUB_GATE_${$.toUpperCase()}`;if(process.env[X]==="fail")return{passed:!1,detail:`stub forced fail via ${X}`};return{passed:!0,detail:"stub"}}function er($,X){if(!L5($))return[];let Q;try{Q=t21($)}catch{return[]}if(!Q.isDirectory())return[];let z=[],Z=[$];while(Z.length>0){let K=Z.pop(),J;try{J=r21(K,{withFileTypes:!0})}catch{continue}for(let q of J){let V=K1(K,q.name);if(q.isDirectory()){if(q.name==="node_modules"||q.name.startsWith("."))continue;Z.push(V)}else if(q.isFile()&&q.name.endsWith(X))z.push(V)}}return z}async function $t($){let Q=process.env.LOKI_STUB_GATE_STATIC_ANALYSIS;if(Q==="fail"||Q==="pass")return X2("static_analysis");let z=$?.cwd??process.cwd(),Z=async(j)=>{let B=await $1(["git","-C",z,...j],{timeoutMs:30000});if(B.exitCode!==0)return null;return B.stdout},K,J=await Z(["diff","--name-only","HEAD~1","HEAD"]);if(J!==null)K=J;else{let j=await Z(["diff","--name-only","--cached"]);if(j!==null&&j.trim().length>0)K=j;else{let B=await Z(["ls-files"]);K=B!==null&&B.trim().length>0?B:""}}let q=K.split(/\r?\n/).map((j)=>j.trim()).filter((j)=>j.length>0),V=[],Y=[];for(let j of q){if(/\.(ts|tsx)$/i.test(j))continue;let B=K1(z,j);if(!L5(B))continue;if(j.endsWith(".sh"))V.push(B);else if(/\.(js|mjs|cjs)$/i.test(j))Y.push(B)}let U=30000,H=process.env.LOKI_STATIC_ANALYSIS_CONCURRENCY,W=H&&Number.parseInt(H,10)>0?Math.min(Math.max(1,Number.parseInt(H,10)),64):8,G=Y.filter((j)=>!j.endsWith(".ts")&&!j.endsWith(".tsx")),N=[...V.map((j)=>({kind:"bash",file:j})),...G.map((j)=>({kind:"node",file:j}))],M=[];for(let j=0;j<N.length;j+=W){let B=N.slice(j,j+W),L=await Promise.all(B.map(async(C)=>{let F=C.kind==="bash"?["bash","-n",C.file]:["node","--check",C.file],I=await $1(F,{timeoutMs:U});if(I.exitCode!==0){let T=(I.stderr||I.stdout||`exit ${I.exitCode}`).trim().split(/\r?\n/).slice(0,3).join(" | ");return`${C.kind==="bash"?"bash -n":"node --check"} ${C.file}: ${T}`}return null}));for(let C of L)if(C!==null)M.push(C)}let A=V.length+G.length;if(M.length>0)return{passed:!1,detail:`static_analysis: ${M.length}/${A} failed -- ${M.slice(0,3).join("; ")}`};return{passed:!0,detail:`static_analysis: ${A} files clean`}}function z31($){let X=K1($,"quality","test-results.json");if(!L5(X))return null;try{let Q=JSON.parse(T4(X,"utf-8"));if(Q===null||typeof Q!=="object"||Array.isArray(Q))return null;return Q}catch{return null}}async function Xt($){let Q=process.env.LOKI_STUB_GATE_TEST_COVERAGE;if(Q==="fail"||Q==="pass")return X2("test_coverage");let z=$?.lokiDir??h0(),Z=z31(z);if(Z!==null){let V=typeof Z.failed==="number"?Z.failed:0,Y=typeof Z.passed==="number"?Z.passed:0,U=Z.pass===!0,W=Z.pass===!1?!1:U||V===0,G=`test_coverage(artifact:${Z.runner??"unknown"}): passed=${Y} failed=${V}`;return{passed:W,detail:G}}let K=$?.cwd??process.cwd();if(!L5(K1(K,"package.json")))return{passed:!0,detail:"test_coverage: no test-results.json and no package.json -- skipping"};let J=await $1(["npm","test","--silent"],{cwd:K,timeoutMs:300000});if(J.exitCode===0)return{passed:!0,detail:"test_coverage: npm test exit 0"};let q=(J.stderr||J.stdout||"").trim().split(/\r?\n/).slice(-3).join(" | ");return{passed:!1,detail:`test_coverage: npm test exit ${J.exitCode} -- ${q}`}}function Z31($,X){let Q=K1($,"quality",X);if(!L5(Q))return null;try{return T4(Q,"utf-8")}catch{return null}}function cF($,X){return new RegExp(`\\[${X}\\]`,"i").test($)}function KQ($){return K1($,"quality","semantic-findings.txt")}function JQ($){return K1($,"quality","invariant-findings.txt")}function $q($,X){return $.split(/\r?\n/).filter((Q)=>X.test(Q))}function k2($,X,Q){qQ(WG($),{recursive:!0});let z=`${X}
1132
+ `)}function rr($,X){let Q=tF(X);return D7(GG(Q),()=>{let z=eF(Q),Z=(z[$]??0)+1;return z[$]=Z,nr(Q,z),Z})}function tr($,X){let Q=tF(X),z=GG(Q);if(!L5(z))return;D7(z,()=>{let Z=eF(Q);Z[$]=0,nr(Q,Z)})}function Q31($,X){return eF(tF(X))[$]??0}function X2($){let X=`LOKI_STUB_GATE_${$.toUpperCase()}`;if(process.env[X]==="fail")return{passed:!1,detail:`stub forced fail via ${X}`};return{passed:!0,detail:"stub"}}function er($,X){if(!L5($))return[];let Q;try{Q=t21($)}catch{return[]}if(!Q.isDirectory())return[];let z=[],Z=[$];while(Z.length>0){let K=Z.pop(),J;try{J=r21(K,{withFileTypes:!0})}catch{continue}for(let q of J){let V=K1(K,q.name);if(q.isDirectory()){if(q.name==="node_modules"||q.name.startsWith("."))continue;Z.push(V)}else if(q.isFile()&&q.name.endsWith(X))z.push(V)}}return z}async function $t($){let Q=process.env.LOKI_STUB_GATE_STATIC_ANALYSIS;if(Q==="fail"||Q==="pass")return X2("static_analysis");let z=$?.cwd??process.cwd(),Z=async(j)=>{let B=await $1(["git","-C",z,...j],{timeoutMs:30000});if(B.exitCode!==0)return null;return B.stdout},K,J=await Z(["diff","--name-only","HEAD~1","HEAD"]);if(J!==null)K=J;else{let j=await Z(["diff","--name-only","--cached"]);if(j!==null&&j.trim().length>0)K=j;else{let B=await Z(["ls-files"]);K=B!==null&&B.trim().length>0?B:""}}let q=K.split(/\r?\n/).map((j)=>j.trim()).filter((j)=>j.length>0),V=[],Y=[];for(let j of q){if(/\.(ts|tsx)$/i.test(j))continue;let B=K1(z,j);if(!L5(B))continue;if(j.endsWith(".sh"))V.push(B);else if(/\.(js|mjs|cjs)$/i.test(j))Y.push(B)}let U=30000,H=process.env.LOKI_STATIC_ANALYSIS_CONCURRENCY,W=H&&Number.parseInt(H,10)>0?Math.min(Math.max(1,Number.parseInt(H,10)),64):8,G=Y.filter((j)=>!j.endsWith(".ts")&&!j.endsWith(".tsx")),N=[...V.map((j)=>({kind:"bash",file:j})),...G.map((j)=>({kind:"node",file:j}))],M=[];for(let j=0;j<N.length;j+=W){let B=N.slice(j,j+W),L=await Promise.all(B.map(async(C)=>{let F=C.kind==="bash"?["bash","-n",C.file]:["node","--check",C.file],I=await $1(F,{timeoutMs:U});if(I.exitCode!==0){let T=(I.stderr||I.stdout||`exit ${I.exitCode}`).trim().split(/\r?\n/).slice(0,3).join(" | ");return`${C.kind==="bash"?"bash -n":"node --check"} ${C.file}: ${T}`}return null}));for(let C of L)if(C!==null)M.push(C)}let A=V.length+G.length;if(M.length>0)return{passed:!1,detail:`static_analysis: ${M.length}/${A} failed -- ${M.slice(0,3).join("; ")}`};return{passed:!0,detail:`static_analysis: ${A} files clean`}}function z31($){let X=K1($,"quality","test-results.json");if(!L5(X))return null;try{let Q=JSON.parse(T4(X,"utf-8"));if(Q===null||typeof Q!=="object"||Array.isArray(Q))return null;return Q}catch{return null}}async function Xt($){let Q=process.env.LOKI_STUB_GATE_TEST_COVERAGE;if(Q==="fail"||Q==="pass")return X2("test_coverage");let z=$?.lokiDir??h0(),Z=z31(z);if(Z!==null){let V=typeof Z.failed==="number"?Z.failed:0,Y=typeof Z.passed==="number"?Z.passed:0,U=`test_coverage(artifact:${Z.runner??"unknown"}): passed=${Y} failed=${V}`;if(Z.pass===!1||V>0)return{passed:!1,detail:U};if(Z.pass===!0)return{passed:!0,detail:U};return{passed:!0,inconclusive:!0,detail:`${U} -- INCONCLUSIVE: no recorded pass (pass=${JSON.stringify(Z.pass)??"missing"}; only boolean true passes)`}}let K=$?.cwd??process.cwd();if(!L5(K1(K,"package.json")))return{passed:!0,inconclusive:!0,detail:"test_coverage: no readable test-results.json and no package.json -- skipping, nothing measured"};let J=await $1(["npm","test","--silent"],{cwd:K,timeoutMs:300000});if(J.exitCode===0)return{passed:!0,detail:"test_coverage: npm test exit 0"};let q=(J.stderr||J.stdout||"").trim().split(/\r?\n/).slice(-3).join(" | ");return{passed:!1,detail:`test_coverage: npm test exit ${J.exitCode} -- ${q}`}}function Z31($,X){let Q=K1($,"quality",X);if(!L5(Q))return null;try{return T4(Q,"utf-8")}catch{return null}}function cF($,X){return new RegExp(`\\[${X}\\]`,"i").test($)}function KQ($){return K1($,"quality","semantic-findings.txt")}function JQ($){return K1($,"quality","invariant-findings.txt")}function $q($,X){return $.split(/\r?\n/).filter((Q)=>X.test(Q))}function k2($,X,Q){qQ(WG($),{recursive:!0});let z=`${X}
1133
1133
  ${Q.join(`
1134
1134
  `)}
1135
1135
  `;x2($,z)}function S2($){if(L5($))try{rF($)}catch{}}async function Qt($){let Q=process.env.LOKI_STUB_GATE_MOCK_INTEGRITY;if(Q==="fail"||Q==="pass")return X2("mock_integrity");let z=$?.lokiDir??h0(),Z=Z31(z,"mock-findings.txt");if(Z===null)return{passed:!0,detail:"mock_integrity: no mock-findings.txt artifact -- gate did not run"};let K=cF(Z,"CRITICAL"),J=cF(Z,"HIGH");if(K||J)return{passed:!1,detail:`mock_integrity: blocking findings present (critical=${K} high=${J})`};return{passed:!0,detail:"mock_integrity: no critical/high findings"}}async function zt($){let Q=process.env.LOKI_STUB_GATE_MUTATION_INTEGRITY;if(Q==="fail"||Q==="pass")return X2("mutation_integrity");let z=$?.cwd??process.cwd(),Z=$?.lokiDir??h0(),K=K1(Z,"quality","mutation-findings.txt"),J=K1(L1,"tests","detect-test-mutations.sh");if(!L5(J))return k2(K,"# Test mutation detector failure",["[HIGH] mutation detector unavailable"]),{passed:!1,detail:"mutation_integrity: detector unavailable -- BLOCK (fail-closed)"};let q,V;try{let H=await $1(["bash",J,"--block-high"],{cwd:z,env:{LOKI_SCAN_DIR:z},timeoutMs:300000});q=H.exitCode,V=`${H.stdout}${H.stderr}`}catch{return k2(K,"# Test mutation detector failure",["[HIGH] mutation detector spawn failed"]),{passed:!1,detail:"mutation_integrity: detector spawn failed -- BLOCK (fail-closed)"}}let Y=$q(V,/\[(HIGH|MEDIUM|LOW)\]/);if(q===2||cF(V,"HIGH"))return k2(K,"# Test mutation findings (HIGH blocks this iteration)",Y),{passed:!1,detail:"mutation_integrity: [HIGH] harness or test mutation finding present -- BLOCK"};if(q!==0)return k2(K,"# Test mutation detector failure",[`[HIGH] detector exited ${q} without a valid verdict`]),{passed:!1,detail:`mutation_integrity: detector exited ${q} -- BLOCK (fail-closed)`};let U=Y.filter((H)=>/\[(MEDIUM|LOW)\]/.test(H));if(U.length>0)k2(K,"# Test mutation advisory findings (MED/LOW, non-blocking)",U);else S2(K);return{passed:!0,detail:"mutation_integrity: no high findings"}}async function Zt($){let Q=process.env.LOKI_STUB_GATE_SEMANTIC_TESTS;if(Q==="fail"||Q==="pass")return X2("semantic_tests");let z=$?.cwd??process.cwd(),Z=$?.lokiDir??h0(),K=process.env.LOKI_SEMANTIC_DETECTOR??K1(L1,"tests","detect-semantic-test-problems.sh");if(!L5(K))return S2(KQ(Z)),{passed:!0,inconclusive:!0,detail:"semantic_tests: detector not found -- gate did not run"};let J=300000,q,V;try{let W=await $1(["bash",K,"--block-high"],{cwd:z,env:{LOKI_SCAN_DIR:z},timeoutMs:J});q=W.exitCode,V=`${W.stdout}${W.stderr}`}catch{return S2(KQ(Z)),{passed:!0,inconclusive:!0,detail:"semantic_tests: detector spawn failed -- inconclusive, not blocking"}}let Y=process.env.LOKI_GATE_SEMANTIC_TESTS_BLOCK,U=Y==="true"||Y==="1";if(q===2){if(k2(KQ(Z),"# Semantic test-authenticity findings (CRITICAL/HIGH block this completion)",$q(V,/\[(CRITICAL|HIGH|MEDIUM|LOW)\]/)),U)return{passed:!1,detail:"semantic_tests: CRITICAL/HIGH fake-test problems detected -- BLOCK (LOKI_GATE_SEMANTIC_TESTS_BLOCK)"};return{passed:!0,detail:"semantic_tests: CRITICAL/HIGH fake-test problems detected -- advisory (surfaced; set LOKI_GATE_SEMANTIC_TESTS_BLOCK=true to block)"}}if(q===124)return S2(KQ(Z)),{passed:!0,inconclusive:!0,detail:"semantic_tests: detector timed out -- inconclusive, not blocking"};let H=$q(V,/\[(MEDIUM|LOW)\]/);if(H.length>0)k2(KQ(Z),"# Semantic test advisory findings (MED/LOW, non-blocking)",H);else S2(KQ(Z));return{passed:!0,detail:`semantic_tests: no blocking findings (rc ${q})`}}async function Kt($){let Q=process.env.LOKI_STUB_GATE_INVARIANTS;if(Q==="fail"||Q==="pass")return X2("invariants");let z=$?.cwd??process.cwd(),Z=$?.lokiDir??h0(),K=process.env.LOKI_INVARIANT_DETECTOR??K1(L1,"tests","detect-invariant-violations.sh");if(!L5(K))return S2(JQ(Z)),{passed:!0,inconclusive:!0,detail:"invariants: detector not found -- gate did not run"};let J=300000,q,V;try{let W=await $1(["bash",K,"--strict"],{cwd:z,env:{LOKI_SCAN_DIR:z},timeoutMs:J});q=W.exitCode,V=`${W.stdout}${W.stderr}`}catch{return S2(JQ(Z)),{passed:!0,inconclusive:!0,detail:"invariants: detector spawn failed -- inconclusive, not blocking"}}let Y=process.env.LOKI_GATE_INVARIANTS_BLOCK,U=Y==="true"||Y==="1";if(q===1){if(k2(JQ(Z),"# Invariant findings (CRITICAL/HIGH block this completion)",$q(V,/\[(CRITICAL|HIGH|MEDIUM|LOW)\]/)),U)return{passed:!1,detail:"invariants: CRITICAL/HIGH invariant violation detected -- BLOCK (LOKI_GATE_INVARIANTS_BLOCK)"};return{passed:!0,detail:"invariants: CRITICAL/HIGH invariant violation detected -- advisory (surfaced; set LOKI_GATE_INVARIANTS_BLOCK=true to block)"}}if(q===124)return S2(JQ(Z)),{passed:!0,inconclusive:!0,detail:"invariants: detector timed out -- inconclusive, not blocking"};let H=$q(V,/\[(MEDIUM|LOW)\]/);if(H.length>0)k2(JQ(Z),"# Invariant advisory findings (MED/LOW, non-blocking)",H);else S2(JQ(Z));return{passed:!0,detail:`invariants: no blocking violations (rc ${q})`}}function qt($,X){if(process.env.LOKI_HEAL_MODE==="true"||process.env.LOKI_HEAL_MODE==="1")return!0;let Q=K1($,".loki","healing"),z=K1(Q,"friction-map.json");if(L5(z)){if(!X)return!0;try{let Z=T4(z,"utf8"),K=JSON.parse(Z),J=Array.isArray(K.entries)?K.entries:[],q=X.toLowerCase();for(let V of J){let Y=(V.file??"").toLowerCase().trim();if(Y&&q.includes(Y))return!0}return!1}catch{return!0}}return!1}function q31($){let X={};try{let Q=T4(K1($,".loki","agents","installed.json"),"utf8"),z=JSON.parse(Q),Z=Array.isArray(z)?z:z.agents??[],K=100;for(let J of Z){let q=(J?.type??"").trim();if(!q||q in Jt||q in X)continue;let V=(J.focus??[]).map((Y)=>String(Y).trim().toLowerCase()).filter((Y)=>Y.length>0);if(V.length===0)continue;X[q]={keywords:V,focus:J.capabilities||J.name||q,checks:`Review from ${J.name??q} perspective: ${(J.focus??[]).join(", ")}`,priority:K++}}}catch{}return X}function Y31($){let X=V31[($??"").trim().toLowerCase()]??2;return X<2?2:X}function Vt($,X,Q={}){let z=Q.healingActive===!0,Z={};for(let[M,A]of Object.entries(Jt)){if(M==="legacy-healing-auditor"&&!z)continue;Z[M]=A}let K=`${$} ${X}`.toLowerCase(),J={};for(let[M,A]of Object.entries(Z)){let j=0;for(let B of A.keywords)if(K.includes(B))j+=1;J[M]=j}let q=Y31(Q.complexity),V=Object.keys(Z).slice().sort((M,A)=>{let j=J[M]??0,B=J[A]??0;if(B!==j)return B-j;return Z[M].priority-Z[A].priority}),Y=Object.values(J).every((M)=>M===0),U;if(Y){U=["security-sentinel","test-coverage-auditor"];for(let M of V){if(U.length>=q)break;if(!U.includes(M))U.push(M)}U=U.slice(0,Math.max(q,2))}else U=V.slice(0,q);let H=2,W=Q.cwd===void 0?{}:q31(Q.cwd),G=[];for(let[M,A]of Object.entries(W)){let j=0;for(let B of A.keywords)if(K.includes(B))j+=1;J[M]=j}for(let M of Object.keys(W).sort((A,j)=>{let B=J[A]??0,L=J[j]??0;if(L!==B)return L-B;return W[A].priority-W[j].priority})){if(G.length>=H)break;if((J[M]??0)>0)G.push(M)}return{reviewers:[K31,J31,...U.map((M)=>{let A=Z[M];return{name:M,focus:A.focus,checks:A.checks}}),...G.map((M)=>{let A=W[M];return{name:M,focus:A.focus,checks:A.checks}})],scores:J,pool_size:Object.keys(Z).length+G.length}}function Yt($,X,Q){return`You are ${$.name}. Your SOLE focus is: ${$.focus}.
@@ -1344,4 +1344,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
1344
1344
  `),2}case"start":{let{runStart:z}=await Promise.resolve().then(() => (vt(),_t));return z(Q)}default:return process.stderr.write(`Unknown command: ${X}
1345
1345
  `),process.stderr.write(ht),2}}ER();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var q61=await J61(Bun.argv.slice(2));process.exit(q61);
1346
1346
 
1347
- //# debugId=A13974976FEDA6E710941BC9D02255FD
1347
+ //# debugId=09DC6EF45F50512C5ACB2713447872BB
package/mcp/__init__.py CHANGED
@@ -75,4 +75,4 @@ try:
75
75
  except ImportError:
76
76
  __all__ = ['mcp']
77
77
 
78
- __version__ = '9.52.0'
78
+ __version__ = '9.53.0'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "loki-mode",
3
3
  "mcpName": "io.github.asklokesh/loki-mode",
4
- "version": "9.52.0",
4
+ "version": "9.53.0",
5
5
  "description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider, opencode).",
6
6
  "keywords": [
7
7
  "agent",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "loki-mode",
4
4
  "displayName": "Loki Mode",
5
- "version": "9.52.0",
5
+ "version": "9.53.0",
6
6
  "description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
7
7
  "author": {
8
8
  "name": "Autonomi",