loki-mode 9.52.0 → 9.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +84 -48
- package/autonomy/council-v2.sh +13 -7
- package/autonomy/lib/done-recognition.sh +24 -16
- package/autonomy/lib/proof-check.sh +11 -3
- package/autonomy/lib/voter-agents.sh +8 -3
- package/autonomy/lib/workspace_diff.py +112 -1
- package/autonomy/prd-checklist.sh +19 -11
- package/autonomy/run.sh +336 -39
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/v10/BACKLOG.md +51 -0
- package/docs/v10/METRICS.md +3 -1
- package/docs/v10/PROGRESS.md +35 -4
- package/loki-ts/dist/loki.js +323 -322
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
# the single injection seam, mirroring autonomy/lib/prd-enrich.sh.
|
|
37
37
|
#
|
|
38
38
|
# No emojis. No em dashes. bash 3.2 safe. Honors `set -uo pipefail`.
|
|
39
|
+
#
|
|
40
|
+
# Inline Python (D7): the gate runs with the cwd inside the agent's repo, so
|
|
41
|
+
# every python3 here runs -E and drops '' and '.' from sys.path before any other
|
|
42
|
+
# import; a committed json.py or sitecustomize.py cannot turn the verdict done.
|
|
43
|
+
# Pinned by tests/test-reuse-done-recognition.sh case (d7).
|
|
39
44
|
|
|
40
45
|
# Bound the single model call so a huge PRD or test log cannot run away.
|
|
41
46
|
: "${LOKI_DONE_RECOG_TIMEOUT:=180}" # seconds for the single model call
|
|
@@ -122,7 +127,7 @@ _loki_done_recog_invoke() {
|
|
|
122
127
|
--json-schema "$_dr_schema_content" --output-format json 2>/dev/null) || _dr_json=""
|
|
123
128
|
fi
|
|
124
129
|
if [ -n "$_dr_json" ]; then
|
|
125
|
-
_dr_obj=$(printf '%s' "$_dr_json" | python3 -c 'import sys,json
|
|
130
|
+
_dr_obj=$(printf '%s' "$_dr_json" | python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]; import json
|
|
126
131
|
try:
|
|
127
132
|
e=json.load(sys.stdin)
|
|
128
133
|
p=e.get("structured_output")
|
|
@@ -194,7 +199,7 @@ _loki_done_recog_prd_sha() {
|
|
|
194
199
|
sha256sum "$prd_file" 2>/dev/null | awk '{print $1}'
|
|
195
200
|
else
|
|
196
201
|
# Last-resort: a python hash, still deterministic over the same bytes.
|
|
197
|
-
LOKI_DR_PRD="$prd_file" python3 -c "
|
|
202
|
+
LOKI_DR_PRD="$prd_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
198
203
|
import hashlib, os
|
|
199
204
|
p = os.environ.get('LOKI_DR_PRD','')
|
|
200
205
|
try:
|
|
@@ -275,7 +280,8 @@ reuse_done_recognition_gate() {
|
|
|
275
280
|
LOKI_DR_CHECKLIST="$loki_dir/checklist/checklist.json" \
|
|
276
281
|
LOKI_DR_MAX_PRD="${LOKI_DONE_RECOG_MAX_PRD_CHARS}" \
|
|
277
282
|
LOKI_DR_MAX_TEST="${LOKI_DONE_RECOG_MAX_TEST_CHARS}" \
|
|
278
|
-
python3 << 'DR_PROMPT_EOF'
|
|
283
|
+
python3 -E << 'DR_PROMPT_EOF'
|
|
284
|
+
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
279
285
|
import json, os, sys
|
|
280
286
|
|
|
281
287
|
def read_capped(path, cap):
|
|
@@ -376,7 +382,8 @@ DR_PROMPT_EOF
|
|
|
376
382
|
LOKI_DR_TESTS="$_test_results" \
|
|
377
383
|
LOKI_DR_ACTION="$action" \
|
|
378
384
|
LOKI_DR_PRD="$prd_path" \
|
|
379
|
-
python3 << 'DR_PARSE_EOF'
|
|
385
|
+
python3 -E << 'DR_PARSE_EOF'
|
|
386
|
+
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
380
387
|
import json, os, re, sys
|
|
381
388
|
|
|
382
389
|
resp = os.environ.get("LOKI_DR_RESP", "")
|
|
@@ -674,7 +681,7 @@ DR_PARSE_EOF
|
|
|
674
681
|
fi
|
|
675
682
|
|
|
676
683
|
local verdict
|
|
677
|
-
verdict=$(printf '%s' "$parsed" | python3 -c "import
|
|
684
|
+
verdict=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('verdict',''))" 2>/dev/null)
|
|
678
685
|
|
|
679
686
|
case "$verdict" in
|
|
680
687
|
done)
|
|
@@ -687,7 +694,7 @@ DR_PARSE_EOF
|
|
|
687
694
|
;;
|
|
688
695
|
*)
|
|
689
696
|
local _reason
|
|
690
|
-
_reason=$(printf '%s' "$parsed" | python3 -c "import
|
|
697
|
+
_reason=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('reason','') or 'unverifiable')" 2>/dev/null)
|
|
691
698
|
log_info "Done-recognition: could not confirm the existing code already satisfies the reused spec (${_reason}). Proceeding to build to be safe."
|
|
692
699
|
return 1
|
|
693
700
|
;;
|
|
@@ -723,10 +730,10 @@ _loki_done_recog_finish() {
|
|
|
723
730
|
mkdir -p "$loki_dir/state" 2>/dev/null || true
|
|
724
731
|
|
|
725
732
|
local summary met total axis
|
|
726
|
-
summary=$(printf '%s' "$parsed" | python3 -c "import
|
|
727
|
-
met=$(printf '%s' "$parsed" | python3 -c "import
|
|
728
|
-
total=$(printf '%s' "$parsed" | python3 -c "import
|
|
729
|
-
axis=$(printf '%s' "$parsed" | python3 -c "import
|
|
733
|
+
summary=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('summary','') or 'Project already satisfies its spec.')" 2>/dev/null)
|
|
734
|
+
met=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('met_count',0))" 2>/dev/null)
|
|
735
|
+
total=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('total_count',0))" 2>/dev/null)
|
|
736
|
+
axis=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('tests_axis','unknown'))" 2>/dev/null)
|
|
730
737
|
[ -n "$met" ] || met=0
|
|
731
738
|
[ -n "$total" ] || total=0
|
|
732
739
|
[ -n "$axis" ] || axis="unknown"
|
|
@@ -766,7 +773,7 @@ _loki_done_recog_finish() {
|
|
|
766
773
|
echo ""
|
|
767
774
|
echo "## Per-requirement evidence"
|
|
768
775
|
echo ""
|
|
769
|
-
printf '%s' "$parsed" | python3 -c "
|
|
776
|
+
printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
770
777
|
import json, sys
|
|
771
778
|
try:
|
|
772
779
|
d = json.load(sys.stdin)
|
|
@@ -785,7 +792,7 @@ for t in d.get('satisfied', []):
|
|
|
785
792
|
LOKI_DR_MET="$met" \
|
|
786
793
|
LOKI_DR_TOTAL="$total" \
|
|
787
794
|
LOKI_DR_TS="$ts" \
|
|
788
|
-
python3 -c "
|
|
795
|
+
python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
789
796
|
import json, os, tempfile
|
|
790
797
|
out = os.environ['LOKI_DR_OUT']
|
|
791
798
|
def i(v):
|
|
@@ -856,7 +863,7 @@ _loki_done_recog_write_manifest() {
|
|
|
856
863
|
LOKI_DR_PARSED="$parsed" \
|
|
857
864
|
LOKI_DR_SHA="$prd_sha" \
|
|
858
865
|
LOKI_DR_TS="$ts" \
|
|
859
|
-
python3 -c "
|
|
866
|
+
python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
860
867
|
import json, os, sys, tempfile
|
|
861
868
|
out = os.environ['LOKI_DR_OUT']
|
|
862
869
|
try:
|
|
@@ -897,7 +904,8 @@ except Exception:
|
|
|
897
904
|
if [ -f "$loki_dir/queue/pending.json" ]; then
|
|
898
905
|
# Drop prior PRD-sourced tasks so the incremental pass is the source of
|
|
899
906
|
# truth; non-PRD tasks (if any) are preserved.
|
|
900
|
-
LOKI_DR_PENDING="$loki_dir/queue/pending.json" python3 - <<'RESET_EOF' 2>/dev/null || true
|
|
907
|
+
LOKI_DR_PENDING="$loki_dir/queue/pending.json" python3 -E - <<'RESET_EOF' 2>/dev/null || true
|
|
908
|
+
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
901
909
|
import json, os, tempfile
|
|
902
910
|
p = os.environ.get("LOKI_DR_PENDING", ".loki/queue/pending.json")
|
|
903
911
|
try:
|
|
@@ -921,8 +929,8 @@ RESET_EOF
|
|
|
921
929
|
fi
|
|
922
930
|
|
|
923
931
|
local met total
|
|
924
|
-
met=$(printf '%s' "$parsed" | python3 -c "import
|
|
925
|
-
total=$(printf '%s' "$parsed" | python3 -c "import
|
|
932
|
+
met=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('met_count',0))" 2>/dev/null)
|
|
933
|
+
total=$(printf '%s' "$parsed" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json;print(json.load(sys.stdin).get('total_count',0))" 2>/dev/null)
|
|
926
934
|
local unmet=$(( ${total:-0} - ${met:-0} ))
|
|
927
935
|
log_info "Done-recognition: ${met:-0} of ${total:-0} requirements already satisfied; building only the ${unmet} unmet. Pass --fresh-prd to rebuild from scratch."
|
|
928
936
|
}
|
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
#
|
|
27
27
|
# This function is only ever called when the operator opted in (the call site
|
|
28
28
|
# guards on LOKI_PROVEN_PR_CHECK=1). It is also safe if called directly.
|
|
29
|
+
#
|
|
30
|
+
# Inline Python (D7): the call site's cwd is the agent's repo, so the proof
|
|
31
|
+
# readers run python3 -E and drop '' and '.' from sys.path first; a committed
|
|
32
|
+
# json.py or sitecustomize.py cannot change the headline. Pinned by
|
|
33
|
+
# tests/test-proven-pr-check.sh (D7 section).
|
|
29
34
|
|
|
30
35
|
# Double-source guard.
|
|
31
36
|
[ -n "${_PROOF_CHECK_SH:-}" ] && return 0
|
|
@@ -53,7 +58,8 @@ _proof_check_headline() {
|
|
|
53
58
|
command -v python3 >/dev/null 2>&1 || { printf '%s' ""; return 0; }
|
|
54
59
|
|
|
55
60
|
local headline=""
|
|
56
|
-
headline="$(python3 - "$proof_path" <<'PY' 2>/dev/null || true
|
|
61
|
+
headline="$(python3 -E - "$proof_path" <<'PY' 2>/dev/null || true
|
|
62
|
+
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
57
63
|
import json, sys
|
|
58
64
|
try:
|
|
59
65
|
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
@@ -84,7 +90,8 @@ _proof_check_proof_head_sha() {
|
|
|
84
90
|
command -v python3 >/dev/null 2>&1 || { printf '%s' ""; return 0; }
|
|
85
91
|
|
|
86
92
|
local sha=""
|
|
87
|
-
sha="$(python3 - "$proof_path" <<'PY' 2>/dev/null || true
|
|
93
|
+
sha="$(python3 -E - "$proof_path" <<'PY' 2>/dev/null || true
|
|
94
|
+
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
88
95
|
import json, sys
|
|
89
96
|
try:
|
|
90
97
|
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
@@ -118,7 +125,8 @@ _proof_check_run_id() {
|
|
|
118
125
|
command -v python3 >/dev/null 2>&1 || { printf '%s' ""; return 0; }
|
|
119
126
|
|
|
120
127
|
local rid=""
|
|
121
|
-
rid="$(python3 - "$proof_path" <<'PY' 2>/dev/null || true
|
|
128
|
+
rid="$(python3 -E - "$proof_path" <<'PY' 2>/dev/null || true
|
|
129
|
+
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
122
130
|
import json, sys
|
|
123
131
|
try:
|
|
124
132
|
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
# on success, 1 on any fallback
|
|
27
27
|
# condition (caller falls through
|
|
28
28
|
# to existing heuristic dispatch).
|
|
29
|
+
#
|
|
30
|
+
# Inline Python (D7): the dispatch runs with the cwd inside the agent's repo, so
|
|
31
|
+
# every python3 here runs -E and drops '' and '.' from sys.path before any other
|
|
32
|
+
# import; a committed json.py or sitecustomize.py cannot read the votes.
|
|
33
|
+
# Pinned by tests/test-voter-agents-json.sh (Case D).
|
|
29
34
|
|
|
30
35
|
# Guard against double-source.
|
|
31
36
|
if [ "${__LOKI_VOTER_AGENTS_SH_LOADED:-0}" = "1" ]; then
|
|
@@ -59,7 +64,7 @@ loki_voter_agents_json() {
|
|
|
59
64
|
_VA_PRD="$prd" \
|
|
60
65
|
_VA_TIER="$tier" \
|
|
61
66
|
_VA_COMPLEXITY="${LOKI_COMPLEXITY:-standard}" \
|
|
62
|
-
python3 -c '
|
|
67
|
+
python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
63
68
|
import json, os
|
|
64
69
|
iter_n = os.environ.get("_VA_ITER", "0")
|
|
65
70
|
prd = os.environ.get("_VA_PRD", "")
|
|
@@ -128,7 +133,7 @@ print(json.dumps(agents, separators=(",", ":")))
|
|
|
128
133
|
# Arg $1: terse base findings summary used to brief the DA prompt.
|
|
129
134
|
loki_devils_advocate_json() {
|
|
130
135
|
local summary="${1:-}"
|
|
131
|
-
_VA_SUMMARY="$summary" python3 -c '
|
|
136
|
+
_VA_SUMMARY="$summary" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
132
137
|
import json, os
|
|
133
138
|
summary = os.environ.get("_VA_SUMMARY", "")[:1000]
|
|
134
139
|
agents = {
|
|
@@ -349,7 +354,7 @@ loki_council_dispatch_agents() {
|
|
|
349
354
|
_VA_VDIR="$verdicts_dir" \
|
|
350
355
|
_VA_RFILE="$votes_dir/round-${iteration}.json" \
|
|
351
356
|
_VA_EXPECTED="${COUNCIL_SIZE:-3}" \
|
|
352
|
-
python3 -c '
|
|
357
|
+
python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
353
358
|
import json, os, sys
|
|
354
359
|
from datetime import datetime, timezone
|
|
355
360
|
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
import hashlib
|
|
6
7
|
import os
|
|
8
|
+
import stat as _stat
|
|
7
9
|
import subprocess
|
|
10
|
+
import sys
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
_EMPTY = {"count": 0, "insertions": 0, "deletions": 0, "files": []}
|
|
@@ -29,6 +32,95 @@ def _excluded(path):
|
|
|
29
32
|
return path == ".loki" or path.startswith(".loki/")
|
|
30
33
|
|
|
31
34
|
|
|
35
|
+
# Written by run.sh setup_agent_branch (_loki_snapshot_preexisting): the paths
|
|
36
|
+
# git did not track when the session started, untracked and gitignored,
|
|
37
|
+
# NUL-delimited and relative to the repo top. An entry ending in "/" is an
|
|
38
|
+
# ignored directory and covers its subtree. The sibling holds "<sha256> <path>"
|
|
39
|
+
# records, one per file entry, written by write_snapshot_hashes below.
|
|
40
|
+
_SNAPSHOT = "preexisting-untracked.z"
|
|
41
|
+
_SNAPSHOT_HASHES = "preexisting-untracked.sha.z"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _content_hash(path):
|
|
45
|
+
"""sha256 of a regular file's bytes or a symlink's target, else None.
|
|
46
|
+
|
|
47
|
+
The snapshot writer and the receipt reader both call this, so the two sides
|
|
48
|
+
always agree (git hash-object varies with filters and object format).
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
st = os.lstat(path)
|
|
52
|
+
digest = hashlib.sha256()
|
|
53
|
+
if _stat.S_ISLNK(st.st_mode):
|
|
54
|
+
digest.update(b"link\0" + os.fsencode(os.readlink(path)))
|
|
55
|
+
elif _stat.S_ISREG(st.st_mode):
|
|
56
|
+
with open(path, "rb") as fh:
|
|
57
|
+
for chunk in iter(lambda: fh.read(1 << 20), b""):
|
|
58
|
+
digest.update(chunk)
|
|
59
|
+
else:
|
|
60
|
+
return None
|
|
61
|
+
return digest.hexdigest()
|
|
62
|
+
except OSError:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def write_snapshot_hashes(top, snapshot):
|
|
67
|
+
"""Record a content hash for every file entry of ``snapshot`` that exists
|
|
68
|
+
now (a directory entry or a missing path gets none), atomically, into the
|
|
69
|
+
sibling hashes file."""
|
|
70
|
+
with open(snapshot, "rb") as fh:
|
|
71
|
+
raw = fh.read()
|
|
72
|
+
top_b = os.fsencode(top)
|
|
73
|
+
records = []
|
|
74
|
+
for path in raw.split(b"\0"):
|
|
75
|
+
if not path or path.endswith(b"/"):
|
|
76
|
+
continue
|
|
77
|
+
digest = _content_hash(os.path.join(top_b, path))
|
|
78
|
+
if digest:
|
|
79
|
+
records.append(digest.encode("ascii") + b" " + path + b"\0")
|
|
80
|
+
dest = os.path.join(os.path.dirname(snapshot), _SNAPSHOT_HASHES)
|
|
81
|
+
with open(dest + ".tmp", "wb") as fh:
|
|
82
|
+
fh.write(b"".join(records))
|
|
83
|
+
os.replace(dest + ".tmp", dest)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _preexisting_untracked(repo_dir):
|
|
87
|
+
"""The session-start snapshot: (prefix of repo_dir inside the repo, set of
|
|
88
|
+
repo-top-relative paths, {path: content hash}). Those paths are not this
|
|
89
|
+
run's work, so the receipt lists one only when its content changed;
|
|
90
|
+
tree_sha256 still binds their bytes. Empty when absent."""
|
|
91
|
+
state = os.path.join(repo_dir, ".loki", "state")
|
|
92
|
+
try:
|
|
93
|
+
with open(os.path.join(state, _SNAPSHOT), "rb") as fh:
|
|
94
|
+
raw = fh.read()
|
|
95
|
+
except OSError:
|
|
96
|
+
return "", frozenset(), {}
|
|
97
|
+
paths = frozenset(p.decode("utf-8", "surrogateescape") for p in raw.split(b"\0") if p)
|
|
98
|
+
hashes = {}
|
|
99
|
+
try:
|
|
100
|
+
with open(os.path.join(state, _SNAPSHOT_HASHES), "rb") as fh:
|
|
101
|
+
raw = fh.read()
|
|
102
|
+
except OSError:
|
|
103
|
+
raw = b""
|
|
104
|
+
for record in raw.split(b"\0"):
|
|
105
|
+
digest, sep, path = record.partition(b" ")
|
|
106
|
+
if sep:
|
|
107
|
+
hashes[path.decode("utf-8", "surrogateescape")] = digest.decode("ascii", "replace")
|
|
108
|
+
return (_git(repo_dir, ["rev-parse", "--show-prefix"]) or "").strip(), paths, hashes
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _covered(key, paths):
|
|
112
|
+
"""True when ``key`` or one of its parent directories ("a/", "a/b/") is in
|
|
113
|
+
``paths``: an ignored directory entry covers everything below it."""
|
|
114
|
+
if key in paths:
|
|
115
|
+
return True
|
|
116
|
+
cut = key.find("/")
|
|
117
|
+
while cut != -1:
|
|
118
|
+
if key[:cut + 1] in paths:
|
|
119
|
+
return True
|
|
120
|
+
cut = key.find("/", cut + 1)
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
|
|
32
124
|
def _parse_numstat(raw):
|
|
33
125
|
files = []
|
|
34
126
|
for record in (raw or "").split("\0"):
|
|
@@ -107,7 +199,19 @@ def collect_workspace_diff(repo_dir, base, include_diffs=False):
|
|
|
107
199
|
diffs.append({"path": path, "patch": chunk})
|
|
108
200
|
|
|
109
201
|
untracked = _git(repo_dir, ["ls-files", "--others", "--exclude-standard", "-z"])
|
|
202
|
+
prefix, preexisting, hashes = _preexisting_untracked(repo_dir)
|
|
110
203
|
for path in sorted(p for p in (untracked or "").split("\0") if p and not _excluded(p)):
|
|
204
|
+
key = prefix + path
|
|
205
|
+
if _covered(key, preexisting):
|
|
206
|
+
# The user's file, never committed. Listed only when the run changed
|
|
207
|
+
# it, with no counts and no patch: the old bytes were never stored,
|
|
208
|
+
# and its content (a config.local.json the agent un-ignored) must
|
|
209
|
+
# not land in the receipt.
|
|
210
|
+
recorded = hashes.get(key)
|
|
211
|
+
if recorded and _content_hash(os.path.join(repo_dir, path)) != recorded:
|
|
212
|
+
files.append({"path": path, "insertions": 0, "deletions": 0,
|
|
213
|
+
"status": "preexisting_modified"})
|
|
214
|
+
continue
|
|
111
215
|
stat = _git(
|
|
112
216
|
repo_dir,
|
|
113
217
|
["diff", "--no-index", "--no-renames", "--numstat", "-z", "--", "/dev/null", path],
|
|
@@ -138,4 +242,11 @@ def collect_workspace_diff(repo_dir, base, include_diffs=False):
|
|
|
138
242
|
}, diffs
|
|
139
243
|
|
|
140
244
|
|
|
141
|
-
__all__ = ["collect_workspace_diff"]
|
|
245
|
+
__all__ = ["collect_workspace_diff", "write_snapshot_hashes"]
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
if __name__ == "__main__":
|
|
249
|
+
# run.sh: python3 -E workspace_diff.py hash-snapshot <repo top> <snapshot>
|
|
250
|
+
if len(sys.argv) != 4 or sys.argv[1] != "hash-snapshot":
|
|
251
|
+
sys.exit("usage: workspace_diff.py hash-snapshot <repo top> <snapshot>")
|
|
252
|
+
write_snapshot_hashes(sys.argv[2], sys.argv[3])
|
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
# LOKI_CHECKLIST_TIMEOUT - Timeout per check in seconds (default: 30)
|
|
19
19
|
# LOKI_CHECKLIST_ENABLED - Enable/disable checklist (default: true)
|
|
20
20
|
#
|
|
21
|
+
# Inline Python (D7): verification runs with the cwd inside the agent's repo,
|
|
22
|
+
# so every python3 here runs -E (an empty PYTHONPATH component would load a
|
|
23
|
+
# committed sitecustomize.py) and every -c / - body drops '' and '.' from
|
|
24
|
+
# sys.path before any other import (a committed json.py would print the
|
|
25
|
+
# verdict). Pinned by tests/moat/p2-honest-verdict.sh
|
|
26
|
+
# P2.checklist-verify-not-shadowed.
|
|
27
|
+
#
|
|
21
28
|
# Data:
|
|
22
29
|
# .loki/checklist/checklist.json - Full checklist with verification
|
|
23
30
|
# .loki/checklist/verification-results.json - Summary of last verification
|
|
@@ -198,7 +205,8 @@ checklist_oracle_triangulate() {
|
|
|
198
205
|
_ORACLE_OUT="$findings_file" \
|
|
199
206
|
_ORACLE_PROJECT="$project_dir" \
|
|
200
207
|
_ORACLE_INSTALL_DIR="$oracle_install_dir" \
|
|
201
|
-
python3 - > "$status_file" 2>/dev/null <<'ORACLE_PY'
|
|
208
|
+
python3 -E - > "$status_file" 2>/dev/null <<'ORACLE_PY'
|
|
209
|
+
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
202
210
|
import json, os, re, sys, tempfile, glob
|
|
203
211
|
|
|
204
212
|
spec_path = os.environ["_ORACLE_SPEC"]
|
|
@@ -693,7 +701,7 @@ checklist_oracle_evidence() {
|
|
|
693
701
|
if [ ! -f "$findings_file" ]; then
|
|
694
702
|
return 0
|
|
695
703
|
fi
|
|
696
|
-
_ORACLE_OUT="$findings_file" python3 -c '
|
|
704
|
+
_ORACLE_OUT="$findings_file" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
697
705
|
import json, os
|
|
698
706
|
try:
|
|
699
707
|
with open(os.environ["_ORACLE_OUT"]) as f:
|
|
@@ -797,7 +805,7 @@ checklist_select_heldout() {
|
|
|
797
805
|
# items the build loop already saw in earlier prompts (the hidden-from-loop
|
|
798
806
|
# guarantee is best-effort once the checklist ids change mid-run).
|
|
799
807
|
local status_token
|
|
800
|
-
status_token=$(_CHECKLIST_FILE="$CHECKLIST_FILE" _HELDOUT_FILE="$heldout_file" python3 -c "
|
|
808
|
+
status_token=$(_CHECKLIST_FILE="$CHECKLIST_FILE" _HELDOUT_FILE="$heldout_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
801
809
|
import json, os, sys, hashlib, tempfile
|
|
802
810
|
|
|
803
811
|
cl_path = os.environ['_CHECKLIST_FILE']
|
|
@@ -945,7 +953,7 @@ checklist_heldout_ids() {
|
|
|
945
953
|
if [ ! -f "$heldout_file" ]; then
|
|
946
954
|
return 0
|
|
947
955
|
fi
|
|
948
|
-
_HELDOUT_FILE="$heldout_file" python3 -c "
|
|
956
|
+
_HELDOUT_FILE="$heldout_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
949
957
|
import json, os
|
|
950
958
|
try:
|
|
951
959
|
with open(os.environ['_HELDOUT_FILE']) as f:
|
|
@@ -991,7 +999,7 @@ checklist_verify() {
|
|
|
991
999
|
|
|
992
1000
|
log_step "Running PRD checklist verification..."
|
|
993
1001
|
|
|
994
|
-
python3 "$verify_script" \
|
|
1002
|
+
python3 -E "$verify_script" \
|
|
995
1003
|
--checklist "$CHECKLIST_FILE" \
|
|
996
1004
|
--timeout "$CHECKLIST_TIMEOUT" 2>/dev/null || true
|
|
997
1005
|
|
|
@@ -1023,7 +1031,7 @@ checklist_summary() {
|
|
|
1023
1031
|
_CHECKLIST_RESULTS="$CHECKLIST_RESULTS_FILE" \
|
|
1024
1032
|
_CHECKLIST_WAIVERS="${CHECKLIST_DIR:-".loki/checklist"}/waivers.json" \
|
|
1025
1033
|
_CHECKLIST_HELDOUT="${CHECKLIST_DIR:-".loki/checklist"}/held-out.json" \
|
|
1026
|
-
python3 -c "
|
|
1034
|
+
python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1027
1035
|
import json, sys, os
|
|
1028
1036
|
try:
|
|
1029
1037
|
fpath = os.environ.get('_CHECKLIST_RESULTS', '')
|
|
@@ -1126,7 +1134,7 @@ checklist_as_evidence() {
|
|
|
1126
1134
|
echo "## PRD Checklist Verification"
|
|
1127
1135
|
echo ""
|
|
1128
1136
|
|
|
1129
|
-
_CHECKLIST_RESULTS="$CHECKLIST_RESULTS_FILE" python3 -c "
|
|
1137
|
+
_CHECKLIST_RESULTS="$CHECKLIST_RESULTS_FILE" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1130
1138
|
import json, os
|
|
1131
1139
|
try:
|
|
1132
1140
|
data = json.load(open(os.environ['_CHECKLIST_RESULTS']))
|
|
@@ -1162,7 +1170,7 @@ checklist_waiver_load() {
|
|
|
1162
1170
|
if [ ! -f "$waivers_file" ]; then
|
|
1163
1171
|
return 0
|
|
1164
1172
|
fi
|
|
1165
|
-
_WAIVERS_FILE="$waivers_file" python3 -c "
|
|
1173
|
+
_WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1166
1174
|
import json, sys, os
|
|
1167
1175
|
try:
|
|
1168
1176
|
waivers_file = os.environ['_WAIVERS_FILE']
|
|
@@ -1186,7 +1194,7 @@ checklist_waiver_add() {
|
|
|
1186
1194
|
local timestamp
|
|
1187
1195
|
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1188
1196
|
|
|
1189
|
-
_WAIVERS_FILE="$waivers_file" python3 -c "
|
|
1197
|
+
_WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1190
1198
|
import json, os, sys
|
|
1191
1199
|
|
|
1192
1200
|
waivers_file = os.environ['_WAIVERS_FILE']
|
|
@@ -1239,7 +1247,7 @@ checklist_waiver_remove() {
|
|
|
1239
1247
|
return 1
|
|
1240
1248
|
fi
|
|
1241
1249
|
|
|
1242
|
-
_WAIVERS_FILE="$waivers_file" python3 -c "
|
|
1250
|
+
_WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1243
1251
|
import json, os, sys
|
|
1244
1252
|
|
|
1245
1253
|
waivers_file = os.environ['_WAIVERS_FILE']
|
|
@@ -1275,7 +1283,7 @@ checklist_waiver_list() {
|
|
|
1275
1283
|
return 0
|
|
1276
1284
|
fi
|
|
1277
1285
|
|
|
1278
|
-
_WAIVERS_FILE="$waivers_file" python3 -c "
|
|
1286
|
+
_WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1279
1287
|
import json, os
|
|
1280
1288
|
waivers_file = os.environ['_WAIVERS_FILE']
|
|
1281
1289
|
with open(waivers_file) as f:
|