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 +2 -2
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +66 -44
- package/autonomy/lib/workspace_diff.py +18 -1
- package/autonomy/run.sh +79 -30
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/v10/BACKLOG.md +10 -0
- package/docs/v10/METRICS.md +2 -1
- package/docs/v10/PROGRESS.md +18 -4
- package/loki-ts/dist/loki.js +3 -3
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
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.
|
|
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.
|
|
473
|
+
**v9.53.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.
|
|
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
|
-
|
|
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
|
-
|
|
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 -
|
|
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 ;;
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
|
@@ -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.
|
|
5
|
+
**Version:** v9.53.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
package/docs/v10/BACKLOG.md
CHANGED
|
@@ -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.
|
package/docs/v10/METRICS.md
CHANGED
|
@@ -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 |
|
|
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` |
|
package/docs/v10/PROGRESS.md
CHANGED
|
@@ -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
|
-
|
|
33
|
+
### Cycle 2 (2026-09-26)
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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).
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -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.
|
|
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
|
|
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=
|
|
1347
|
+
//# debugId=09DC6EF45F50512C5ACB2713447872BB
|
package/mcp/__init__.py
CHANGED
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.
|
|
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.
|
|
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",
|