loki-mode 9.74.0 → 9.76.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 +24 -6
- package/autonomy/council-v2.sh +60 -13
- package/autonomy/lib/done-recognition.sh +73 -21
- package/autonomy/lib/proof-check.sh +43 -8
- package/autonomy/lib/voter-agents.sh +42 -5
- package/autonomy/loki +3 -0
- package/autonomy/prd-checklist.sh +77 -16
- package/autonomy/run.sh +38 -3
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/R3-COST-OBSERVABILITY-DESIGN.md +7 -1
- package/docs/exit-codes.md +21 -0
- package/docs/v10/ARCHITECT-CUTS.md +397 -234
- package/docs/v10/BOARD.md +84 -44
- package/docs/v10/GUARDS.md +45 -28
- package/loki-ts/dist/loki.js +341 -338
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-DaeYgQSE.js → AdminPage-Bw8oPnII.js} +1 -1
- package/web-app/dist/assets/{Avatar-CgwQi58_.js → Avatar-BwEtc3RH.js} +1 -1
- package/web-app/dist/assets/{Badge-Bwq-lpRh.js → Badge-C_-lfbUl.js} +1 -1
- package/web-app/dist/assets/{Button-BEf4Nher.js → Button-BdDGKKeF.js} +1 -1
- package/web-app/dist/assets/CockpitPage-Dx9d4fEm.js +47 -0
- package/web-app/dist/assets/{ComparePage-tFbdo90G.js → ComparePage-BKtAhhIJ.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-Brewvgm9.js → ErrorBoundary-BuG16GpG.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-h_82QBVa.js → EvidenceReceiptPanel-NATnqxKs.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-Dcg5xDMm.js → GitHubIssuesPanel-BgOwdOpj.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-DFKzVyA1.js → GitHubPRsPanel-CBJyHI1c.js} +1 -1
- package/web-app/dist/assets/{HomePage-D21cM1Je.js → HomePage-BHj17Epq.js} +2 -2
- package/web-app/dist/assets/{LoginPage-DJ9yqMn4.js → LoginPage-Dig4H-HO.js} +1 -1
- package/web-app/dist/assets/{MagicPage-mwkjJhBy.js → MagicPage-w6WJ35PH.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-D2q-PD-s.js → MetricsPage-CvjSUOWn.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-BtusJvgS.js → NotFoundPage-PAcYmGkU.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-CRPALtO8.js → ProjectPage-CBkT5JyW.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-DuzABpfc.js → ProjectsPage-Cs6yuWEU.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-DhJPX-Rf.js → SettingsPage-BU9F40Yn.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-CvgS47nu.js → ShowcasePage-jCNABTeT.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-l3Cv9YjY.js → SystemSettingsPage-Crmc-ryL.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-DridGcnR.js → TeamsPage-DN8W-9m5.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-CTNsfp03.js → TemplatesPage-WvaXwZIl.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-BuyksqLh.js → TerminalOutput-Bf_do2tO.js} +1 -1
- package/web-app/dist/assets/{activity-DM2TKaBi.js → activity-CMl9JvHl.js} +1 -1
- package/web-app/dist/assets/{bell-D2PMLbbX.js → bell-CnjqTAUX.js} +1 -1
- package/web-app/dist/assets/{bot-BnN32Jxp.js → bot-BBprDFVS.js} +1 -1
- package/web-app/dist/assets/{check-CU128nw_.js → check-DW9Hq4y4.js} +1 -1
- package/web-app/dist/assets/{chevron-left-ehNW1Do7.js → chevron-left-DC9pMI2E.js} +1 -1
- package/web-app/dist/assets/{circle-alert-Cpw2gtOC.js → circle-alert-BJoll9YV.js} +1 -1
- package/web-app/dist/assets/{clock-BOZHdPVS.js → clock-BdHhRTHO.js} +1 -1
- package/web-app/dist/assets/{cloud-BZ2L9Fzi.js → cloud-C54B7EO9.js} +1 -1
- package/web-app/dist/assets/{code-xml-sSo522gu.js → code-xml-DwQK2vpV.js} +1 -1
- package/web-app/dist/assets/{database-DrR1xHzS.js → database-DsWVFir7.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-CHE3T_rh.js → dollar-sign-DuRPNc2M.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-CgcssyRQ.js → file-code-corner-C4j2P44-.js} +1 -1
- package/web-app/dist/assets/{file-plus-DtwfrxYH.js → file-plus-rnI2RZjF.js} +1 -1
- package/web-app/dist/assets/{globe-Dzla77iL.js → globe-BbBhsaS5.js} +1 -1
- package/web-app/dist/assets/{hammer-BAMdG66t.js → hammer-BxcycH3E.js} +1 -1
- package/web-app/dist/assets/{index-mQOnSYoW.js → index-xGj3noKA.js} +2 -2
- package/web-app/dist/assets/{layers-DhuyLqmc.js → layers-CcAOhZeY.js} +1 -1
- package/web-app/dist/assets/{loader-circle-DclFyQ3B.js → loader-circle-DN_Xjd5e.js} +1 -1
- package/web-app/dist/assets/{lock-C8v_bXNl.js → lock-Dn8AMO-D.js} +1 -1
- package/web-app/dist/assets/{package-B4AnjM5D.js → package-uroTpCjn.js} +1 -1
- package/web-app/dist/assets/{plus-DM95CmZI.js → plus-CYJAEX02.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-CWaauzqX.js → refresh-cw-BUGsMChj.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-DxcvylpJ.js → rotate-ccw-C_4D-XGl.js} +1 -1
- package/web-app/dist/assets/{scroll-text-Dx-V_BpP.js → scroll-text-DrAJG0H3.js} +1 -1
- package/web-app/dist/assets/{server-Cj2EfoGN.js → server-Di6lHsM9.js} +1 -1
- package/web-app/dist/assets/{shield-alert-OlmWv7sU.js → shield-alert-DH1bNjkD.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-CIpJhyTd.js → thumbs-up-Enk3ppj_.js} +1 -1
- package/web-app/dist/assets/{trash-2-BIkoZkY6.js → trash-2-CezqQT3x.js} +1 -1
- package/web-app/dist/assets/{trending-down-CI-OcCGt.js → trending-down-B_ChI0U7.js} +1 -1
- package/web-app/dist/assets/{trending-up-B0dpgKi-.js → trending-up-BtsvHd_-.js} +1 -1
- package/web-app/dist/assets/{upload-NhOBAjKA.js → upload-XiWa_WBM.js} +1 -1
- package/web-app/dist/assets/{usePolling-_QRF6f7x.js → usePolling-B6Hxwbk1.js} +1 -1
- package/web-app/dist/assets/{user-kRNvOpun.js → user-oL_Oya-S.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/web-app/dist/assets/CockpitPage-Bj41Xtg9.js +0 -47
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.76.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.76.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.
|
|
1
|
+
9.76.0
|
|
@@ -4368,19 +4368,34 @@ council_managed_should_stop() {
|
|
|
4368
4368
|
return 1
|
|
4369
4369
|
fi
|
|
4370
4370
|
|
|
4371
|
+
# S-196: every reader and the session heredoc run on the resolved -I -S
|
|
4372
|
+
# interpreter (see _loki_snapshot_py_tool); -E still loads user-site .pth
|
|
4373
|
+
# files. None resolvable -> never start the managed session: Bash fallback.
|
|
4374
|
+
# Deliberate consequence: -S hides site-packages, so the anthropic SDK is
|
|
4375
|
+
# never importable and is_enabled() is always False. The managed council
|
|
4376
|
+
# therefore always falls back to Bash voting (warned below). Reviving it
|
|
4377
|
+
# needs a pinned interpreter plus an SDK path that loads no .pth file;
|
|
4378
|
+
# a PATH or venv python3 without -S would let the agent forge the verdict.
|
|
4379
|
+
local _ms_py
|
|
4380
|
+
_ms_py="$(_loki_snapshot_py_tool)" || {
|
|
4381
|
+
log_warn "[Council] Managed completion council unavailable: no isolated -I -S interpreter; falling back to Bash voting"
|
|
4382
|
+
return 1
|
|
4383
|
+
}
|
|
4384
|
+
|
|
4371
4385
|
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
4372
|
-
local project_dir="${PROJECT_DIR:-$(pwd)}"
|
|
4373
4386
|
local round="${ITERATION_COUNT:-0}"
|
|
4374
4387
|
local verdicts_dir="$COUNCIL_STATE_DIR/verdicts"
|
|
4375
4388
|
mkdir -p "$verdicts_dir" 2>/dev/null || true
|
|
4376
4389
|
|
|
4377
4390
|
# Build session context: diff_summary, test_summary, pending_tasks.
|
|
4378
4391
|
# Kept deliberately small so we never choke the session budget.
|
|
4392
|
+
# S-196: diff the target project (same root as loki_dir and
|
|
4393
|
+
# LOKI_TARGET_DIR), never PROJECT_DIR, which is Loki's install tree.
|
|
4379
4394
|
local diff_summary=""
|
|
4380
|
-
diff_summary=$(cd "$
|
|
4395
|
+
diff_summary=$(cd "${TARGET_DIR:-.}" 2>/dev/null && git diff --stat 2>/dev/null | tail -20 | tr '\n' ' ' || echo "")
|
|
4381
4396
|
local test_summary=""
|
|
4382
4397
|
if [ -f "$loki_dir/quality/test-results.json" ]; then
|
|
4383
|
-
test_summary=$(_TRF="$loki_dir/quality/test-results.json"
|
|
4398
|
+
test_summary=$(_TRF="$loki_dir/quality/test-results.json" "$_ms_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
4384
4399
|
import json, os
|
|
4385
4400
|
try:
|
|
4386
4401
|
d = json.load(open(os.environ['_TRF']))
|
|
@@ -4391,7 +4406,7 @@ except Exception:
|
|
|
4391
4406
|
fi
|
|
4392
4407
|
local pending_tasks="[]"
|
|
4393
4408
|
if [ -f "$loki_dir/queue/pending.json" ]; then
|
|
4394
|
-
pending_tasks=$(_QF="$loki_dir/queue/pending.json"
|
|
4409
|
+
pending_tasks=$(_QF="$loki_dir/queue/pending.json" "$_ms_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
4395
4410
|
import json, os
|
|
4396
4411
|
try:
|
|
4397
4412
|
d = json.load(open(os.environ['_QF']))
|
|
@@ -4415,7 +4430,7 @@ except Exception:
|
|
|
4415
4430
|
_CC_LOKI_DIR="$loki_dir" \
|
|
4416
4431
|
LOKI_TARGET_DIR="${TARGET_DIR:-$(pwd)}" \
|
|
4417
4432
|
PROJECT_DIR="${PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)}" \
|
|
4418
|
-
|
|
4433
|
+
"$_ms_py" -I -S - <<'PYEOF' 2>/dev/null || exit_code=$?
|
|
4419
4434
|
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
4420
4435
|
import json, os, sys, pathlib
|
|
4421
4436
|
|
|
@@ -4553,7 +4568,10 @@ PYEOF
|
|
|
4553
4568
|
return 0
|
|
4554
4569
|
fi
|
|
4555
4570
|
|
|
4556
|
-
|
|
4571
|
+
case "$exit_code" in
|
|
4572
|
+
2|3) log_warn "[Council] Managed completion council unavailable (exit=$exit_code): SDK not importable under the isolated -I -S interpreter; falling back to Bash voting" ;;
|
|
4573
|
+
*) log_warn "[Council] Managed completion council unavailable (exit=$exit_code); falling back to Bash voting" ;;
|
|
4574
|
+
esac
|
|
4557
4575
|
return 1
|
|
4558
4576
|
}
|
|
4559
4577
|
|
package/autonomy/council-v2.sh
CHANGED
|
@@ -20,15 +20,43 @@
|
|
|
20
20
|
# - swarm/calibration.py (reviewer calibration)
|
|
21
21
|
#
|
|
22
22
|
# Inline Python (D7): the council runs with the cwd inside the agent's repo, so
|
|
23
|
-
# every
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
23
|
+
# every python here drops '' and '.' from sys.path before any other import (the
|
|
24
|
+
# swarm dir is inserted after that); a committed json.py or sitecustomize.py
|
|
25
|
+
# cannot read the votes. Pinned by tests/test-council-no-fabricated-verdict.sh
|
|
26
|
+
# (D7 section). S-201: -E alone still loads user site-packages, whose .pth
|
|
27
|
+
# "import" lines run before that scrub and can forge json.load, so every site
|
|
28
|
+
# runs the _loki_snapshot_py_tool interpreter -I -S. Pinned by
|
|
29
|
+
# tests/test-council-v2-no-user-site-pth.sh.
|
|
27
30
|
#
|
|
28
31
|
#===============================================================================
|
|
29
32
|
|
|
30
33
|
COUNCIL_V2_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
31
34
|
|
|
35
|
+
# Guarded copy for callers that source this file on its own (tests). In
|
|
36
|
+
# production completion-council.sh (or run.sh) has already defined it. run.sh
|
|
37
|
+
# is the source of truth: keep this copy byte-identical to it.
|
|
38
|
+
declare -F _loki_snapshot_py_tool >/dev/null 2>&1 || \
|
|
39
|
+
_loki_snapshot_py_tool() {
|
|
40
|
+
local c
|
|
41
|
+
for c in /usr/bin/python3 /bin/python3; do
|
|
42
|
+
[ -x "$c" ] && [ ! -d "$c" ] && "$c" -I -S -c '' >/dev/null 2>&1 && { printf '%s\n' "$c"; return 0; }
|
|
43
|
+
done
|
|
44
|
+
local dir
|
|
45
|
+
local IFS=:
|
|
46
|
+
for dir in $PATH; do
|
|
47
|
+
case "$dir" in
|
|
48
|
+
/*) ;;
|
|
49
|
+
*) continue ;;
|
|
50
|
+
esac
|
|
51
|
+
if [ -x "$dir/python3" ] && [ ! -d "$dir/python3" ] \
|
|
52
|
+
&& "$dir/python3" -I -S -c '' >/dev/null 2>&1; then
|
|
53
|
+
printf '%s\n' "$dir/python3"
|
|
54
|
+
return 0
|
|
55
|
+
fi
|
|
56
|
+
done
|
|
57
|
+
return 1
|
|
58
|
+
}
|
|
59
|
+
|
|
32
60
|
#===============================================================================
|
|
33
61
|
# council_v2_vote() -- Main entry point for v2 voting
|
|
34
62
|
#
|
|
@@ -48,6 +76,9 @@ council_v2_vote() {
|
|
|
48
76
|
local iteration="${4:-0}"
|
|
49
77
|
|
|
50
78
|
local council_size="${COUNCIL_SIZE:-3}"
|
|
79
|
+
# S-201: none resolvable -> every read below takes its fail-safe fallback.
|
|
80
|
+
local _c2_py
|
|
81
|
+
_c2_py="$(_loki_snapshot_py_tool)" || _c2_py=""
|
|
51
82
|
|
|
52
83
|
log_header "COMPLETION COUNCIL v2 - Iteration $iteration"
|
|
53
84
|
log_info "Convening ${council_size}-member blind review council..."
|
|
@@ -103,7 +134,7 @@ council_v2_vote() {
|
|
|
103
134
|
votes_json="$votes_json$vote_content"
|
|
104
135
|
|
|
105
136
|
local verdict
|
|
106
|
-
verdict=$(echo "$vote_content" |
|
|
137
|
+
verdict=$(echo "$vote_content" | "$_c2_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json; print(json.load(sys.stdin).get('verdict','').upper())" 2>/dev/null || echo "UNKNOWN")
|
|
107
138
|
# TRUST: a reviewer that could not be REACHED did not vote REJECT.
|
|
108
139
|
#
|
|
109
140
|
# Every provider arm below used to substitute a literal
|
|
@@ -141,14 +172,21 @@ council_v2_vote() {
|
|
|
141
172
|
|
|
142
173
|
# Step 4: Sycophancy detection
|
|
143
174
|
local sycophancy_score
|
|
144
|
-
sycophancy_score=$(
|
|
175
|
+
sycophancy_score=$("$_c2_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
145
176
|
import sys
|
|
146
177
|
sys.path.insert(0, '${COUNCIL_V2_DIR}/../swarm')
|
|
147
178
|
from sycophancy import detect_sycophancy
|
|
148
179
|
import json
|
|
149
180
|
votes = json.loads(sys.argv[1])
|
|
150
181
|
print('{:.3f}'.format(detect_sycophancy(votes)))
|
|
151
|
-
" "$votes_json" 2>/dev/null ||
|
|
182
|
+
" "$votes_json" 2>/dev/null) || sycophancy_score=""
|
|
183
|
+
# S-201: a crashed or missing detector did not measure 0.000. Record the
|
|
184
|
+
# score as unmeasured (null keeps summary.json valid JSON) so Step 5
|
|
185
|
+
# challenges instead of reading "independent".
|
|
186
|
+
case "$sycophancy_score" in
|
|
187
|
+
[0-9]*.[0-9][0-9][0-9]) ;;
|
|
188
|
+
*) sycophancy_score="null" ;;
|
|
189
|
+
esac
|
|
152
190
|
|
|
153
191
|
log_info "Sycophancy score: $sycophancy_score"
|
|
154
192
|
|
|
@@ -156,10 +194,17 @@ print('{:.3f}'.format(detect_sycophancy(votes)))
|
|
|
156
194
|
if [ "$approve_count" -eq "$council_size" ]; then
|
|
157
195
|
local threshold="${LOKI_COUNCIL_SYCOPHANCY_THRESHOLD:-0.6}"
|
|
158
196
|
local should_challenge
|
|
159
|
-
|
|
197
|
+
# S-201: an unmeasured score, or a compare that cannot run, challenges.
|
|
198
|
+
# Only a measured score below the threshold skips the devil's advocate.
|
|
199
|
+
if [ "$sycophancy_score" = "null" ]; then
|
|
200
|
+
should_challenge="yes"
|
|
201
|
+
else
|
|
202
|
+
should_challenge=$("$_c2_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; print('no' if float(sys.argv[1]) < float(sys.argv[2]) else 'yes')" "$sycophancy_score" "$threshold" 2>/dev/null) || should_challenge="yes"
|
|
203
|
+
[ "$should_challenge" = "no" ] || should_challenge="yes"
|
|
204
|
+
fi
|
|
160
205
|
|
|
161
206
|
if [ "$should_challenge" = "yes" ]; then
|
|
162
|
-
log_warn "Sycophancy score $sycophancy_score
|
|
207
|
+
log_warn "Sycophancy score $sycophancy_score (threshold $threshold, null = unmeasured) -- adding devil's advocate"
|
|
163
208
|
# Run devil's advocate with fresh evidence (no visibility of other votes)
|
|
164
209
|
local da_dir
|
|
165
210
|
da_dir=$(mktemp -d)
|
|
@@ -173,7 +218,7 @@ print('{:.3f}'.format(detect_sycophancy(votes)))
|
|
|
173
218
|
# Same trust rule as the main tally: an unparseable devil's
|
|
174
219
|
# advocate did not vote REJECT. Defaulting to REJECT here would
|
|
175
220
|
# silently overturn a unanimous APPROVE on a transient failure.
|
|
176
|
-
da_verdict=$(cat "$da_vote" |
|
|
221
|
+
da_verdict=$(cat "$da_vote" | "$_c2_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json; print(json.load(sys.stdin).get('verdict','').upper())" 2>/dev/null || echo "INCONCLUSIVE")
|
|
177
222
|
if [ "$da_verdict" = "INCONCLUSIVE" ]; then
|
|
178
223
|
log_warn "Devil's advocate produced no verdict -- unanimous approval left UNCHANGED (not overturned)"
|
|
179
224
|
elif [ "$da_verdict" = "REJECT" ]; then
|
|
@@ -207,7 +252,7 @@ print('{:.3f}'.format(detect_sycophancy(votes)))
|
|
|
207
252
|
final_decision="reject"
|
|
208
253
|
fi
|
|
209
254
|
|
|
210
|
-
|
|
255
|
+
"$_c2_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
211
256
|
import sys
|
|
212
257
|
sys.path.insert(0, '${COUNCIL_V2_DIR}/../swarm')
|
|
213
258
|
from calibration import CalibrationTracker
|
|
@@ -271,6 +316,8 @@ council_v2_run_reviewer() {
|
|
|
271
316
|
local role="$1"
|
|
272
317
|
local review_dir="$2"
|
|
273
318
|
local output_file="$3"
|
|
319
|
+
local _c2_py
|
|
320
|
+
_c2_py="$(_loki_snapshot_py_tool)" || _c2_py=""
|
|
274
321
|
|
|
275
322
|
# Build review prompt based on role
|
|
276
323
|
local prompt
|
|
@@ -394,7 +441,7 @@ Respond ONLY with a valid JSON object. No markdown fencing."
|
|
|
394
441
|
--json-schema "$_c2_schema_content" --output-format json 2>/dev/null)" || _c2_json=""
|
|
395
442
|
if [ -n "$_c2_json" ]; then
|
|
396
443
|
# Pull the schema-validated object out of the envelope.
|
|
397
|
-
result="$(printf '%s' "$_c2_json" |
|
|
444
|
+
result="$(printf '%s' "$_c2_json" | "$_c2_py" -I -S -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]; import json
|
|
398
445
|
try:
|
|
399
446
|
e=json.load(sys.stdin)
|
|
400
447
|
p=e.get("structured_output")
|
|
@@ -496,7 +543,7 @@ except Exception:
|
|
|
496
543
|
# or neither, stays INCONCLUSIVE -- never guessed.
|
|
497
544
|
if [ -z "$extracted" ] && [ -n "${result:-}" ]; then
|
|
498
545
|
local _recovered
|
|
499
|
-
_recovered="$(printf '%s' "$result" | _LOKI_RAW="$result"
|
|
546
|
+
_recovered="$(printf '%s' "$result" | _LOKI_RAW="$result" "$_c2_py" -I -S -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
500
547
|
import os, re, sys, json
|
|
501
548
|
raw = os.environ.get("_LOKI_RAW", "")
|
|
502
549
|
# Standalone words only: "APPROVE" not "approved-by", and not inside a URL.
|
|
@@ -38,9 +38,42 @@
|
|
|
38
38
|
# No emojis. No em dashes. bash 3.2 safe. Honors `set -uo pipefail`.
|
|
39
39
|
#
|
|
40
40
|
# Inline Python (D7): the gate runs with the cwd inside the agent's repo, so
|
|
41
|
-
# every python3 here
|
|
42
|
-
#
|
|
43
|
-
#
|
|
41
|
+
# every python3 here drops '' and '.' from sys.path before any other import; a
|
|
42
|
+
# committed json.py or sitecustomize.py cannot turn the verdict done. Pinned by
|
|
43
|
+
# tests/test-reuse-done-recognition.sh case (d7).
|
|
44
|
+
#
|
|
45
|
+
# S-200 (BACKLOG 54): -E alone still loads the user site-packages, whose .pth
|
|
46
|
+
# "import" lines run before that scrub and can forge json.load (a forged verdict
|
|
47
|
+
# read fast-stopped an incomplete project as done). Every call site resolves
|
|
48
|
+
# its interpreter through _loki_snapshot_py_tool and runs it -I -S; when none
|
|
49
|
+
# resolves, nothing reads as done or met (the gate builds, the writers write
|
|
50
|
+
# nothing). See that function's comment in run.sh for the rationale and accepted
|
|
51
|
+
# gaps. run.sh sources this file AFTER it defines _loki_snapshot_py_tool, so in
|
|
52
|
+
# production run.sh's definition is kept; the copy below only serves callers
|
|
53
|
+
# that source this file on its own (tests). run.sh is the source of truth: keep
|
|
54
|
+
# this copy byte-identical to it. Pinned by
|
|
55
|
+
# tests/test-done-recognition-no-user-site-pth.sh.
|
|
56
|
+
declare -F _loki_snapshot_py_tool >/dev/null 2>&1 || \
|
|
57
|
+
_loki_snapshot_py_tool() {
|
|
58
|
+
local c
|
|
59
|
+
for c in /usr/bin/python3 /bin/python3; do
|
|
60
|
+
[ -x "$c" ] && [ ! -d "$c" ] && "$c" -I -S -c '' >/dev/null 2>&1 && { printf '%s\n' "$c"; return 0; }
|
|
61
|
+
done
|
|
62
|
+
local dir
|
|
63
|
+
local IFS=:
|
|
64
|
+
for dir in $PATH; do
|
|
65
|
+
case "$dir" in
|
|
66
|
+
/*) ;;
|
|
67
|
+
*) continue ;;
|
|
68
|
+
esac
|
|
69
|
+
if [ -x "$dir/python3" ] && [ ! -d "$dir/python3" ] \
|
|
70
|
+
&& "$dir/python3" -I -S -c '' >/dev/null 2>&1; then
|
|
71
|
+
printf '%s\n' "$dir/python3"
|
|
72
|
+
return 0
|
|
73
|
+
fi
|
|
74
|
+
done
|
|
75
|
+
return 1
|
|
76
|
+
}
|
|
44
77
|
|
|
45
78
|
# Bound the single model call so a huge PRD or test log cannot run away.
|
|
46
79
|
: "${LOKI_DONE_RECOG_TIMEOUT:=180}" # seconds for the single model call
|
|
@@ -133,8 +166,9 @@ _loki_done_recog_invoke() {
|
|
|
133
166
|
claude --dangerously-skip-permissions -p "$prompt" \
|
|
134
167
|
--json-schema "$_dr_schema_content" --output-format json 2>/dev/null) || _dr_json=""
|
|
135
168
|
fi
|
|
136
|
-
|
|
137
|
-
|
|
169
|
+
local _dr_py
|
|
170
|
+
if [ -n "$_dr_json" ] && _dr_py="$(_loki_snapshot_py_tool)"; then
|
|
171
|
+
_dr_obj=$(printf '%s' "$_dr_json" | "$_dr_py" -I -S -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]; import json
|
|
138
172
|
try:
|
|
139
173
|
e=json.load(sys.stdin)
|
|
140
174
|
p=e.get("structured_output")
|
|
@@ -206,7 +240,9 @@ _loki_done_recog_prd_sha() {
|
|
|
206
240
|
sha256sum "$prd_file" 2>/dev/null | awk '{print $1}'
|
|
207
241
|
else
|
|
208
242
|
# Last-resort: a python hash, still deterministic over the same bytes.
|
|
209
|
-
|
|
243
|
+
local _dr_py
|
|
244
|
+
_dr_py="$(_loki_snapshot_py_tool)" || { printf ''; return 0; }
|
|
245
|
+
LOKI_DR_PRD="$prd_file" "$_dr_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
210
246
|
import hashlib, os
|
|
211
247
|
p = os.environ.get('LOKI_DR_PRD','')
|
|
212
248
|
try:
|
|
@@ -278,6 +314,13 @@ reuse_done_recognition_gate() {
|
|
|
278
314
|
fi
|
|
279
315
|
local _test_results="$loki_dir/quality/test-results.json"
|
|
280
316
|
|
|
317
|
+
# --- Resolve the -I -S interpreter (S-200); none -> inconclusive -> build.
|
|
318
|
+
local _dr_py
|
|
319
|
+
if ! _dr_py="$(_loki_snapshot_py_tool)"; then
|
|
320
|
+
log_info "Done-recognition: no isolated python3 interpreter resolved; proceeding to build."
|
|
321
|
+
return 1
|
|
322
|
+
fi
|
|
323
|
+
|
|
281
324
|
# --- Build the model prompt payload (python: bounded, defensive) ---------
|
|
282
325
|
local prompt
|
|
283
326
|
prompt=$(LOKI_DR_PRD="$prd_path" \
|
|
@@ -287,7 +330,7 @@ reuse_done_recognition_gate() {
|
|
|
287
330
|
LOKI_DR_CHECKLIST="$loki_dir/checklist/checklist.json" \
|
|
288
331
|
LOKI_DR_MAX_PRD="${LOKI_DONE_RECOG_MAX_PRD_CHARS}" \
|
|
289
332
|
LOKI_DR_MAX_TEST="${LOKI_DONE_RECOG_MAX_TEST_CHARS}" \
|
|
290
|
-
|
|
333
|
+
"$_dr_py" -I -S << 'DR_PROMPT_EOF'
|
|
291
334
|
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
292
335
|
import json, os, sys
|
|
293
336
|
|
|
@@ -389,7 +432,7 @@ DR_PROMPT_EOF
|
|
|
389
432
|
LOKI_DR_TESTS="$_test_results" \
|
|
390
433
|
LOKI_DR_ACTION="$action" \
|
|
391
434
|
LOKI_DR_PRD="$prd_path" \
|
|
392
|
-
|
|
435
|
+
"$_dr_py" -I -S << 'DR_PARSE_EOF'
|
|
393
436
|
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
394
437
|
import json, os, re, sys
|
|
395
438
|
|
|
@@ -700,11 +743,11 @@ DR_PARSE_EOF
|
|
|
700
743
|
fi
|
|
701
744
|
|
|
702
745
|
local verdict
|
|
703
|
-
verdict=$(printf '%s' "$parsed" |
|
|
746
|
+
verdict=$(printf '%s' "$parsed" | "$_dr_py" -I -S -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)
|
|
704
747
|
|
|
705
748
|
case "$verdict" in
|
|
706
749
|
done)
|
|
707
|
-
_loki_done_recog_finish "$prd_path" "$parsed"
|
|
750
|
+
_loki_done_recog_finish "$prd_path" "$parsed" || return 1
|
|
708
751
|
return 0
|
|
709
752
|
;;
|
|
710
753
|
incomplete)
|
|
@@ -713,7 +756,7 @@ DR_PARSE_EOF
|
|
|
713
756
|
;;
|
|
714
757
|
*)
|
|
715
758
|
local _reason
|
|
716
|
-
_reason=$(printf '%s' "$parsed" |
|
|
759
|
+
_reason=$(printf '%s' "$parsed" | "$_dr_py" -I -S -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)
|
|
717
760
|
log_info "Done-recognition: could not confirm the existing code already satisfies the reused spec (${_reason}). Proceeding to build to be safe."
|
|
718
761
|
return 1
|
|
719
762
|
;;
|
|
@@ -729,6 +772,10 @@ _loki_done_recog_finish() {
|
|
|
729
772
|
local parsed="$2"
|
|
730
773
|
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
731
774
|
|
|
775
|
+
# S-200: no -I -S interpreter -> write no done record (the gate then builds).
|
|
776
|
+
local _dr_py
|
|
777
|
+
_dr_py="$(_loki_snapshot_py_tool)" || return 1
|
|
778
|
+
|
|
732
779
|
# The gate runs EARLY in run_autonomous, before the run normally mints these
|
|
733
780
|
# run-scoped ids/baselines (run.sh sets them just after this call site). The
|
|
734
781
|
# council-parity finalizers below (and what they transitively call) expect
|
|
@@ -749,10 +796,10 @@ _loki_done_recog_finish() {
|
|
|
749
796
|
mkdir -p "$loki_dir/state" 2>/dev/null || true
|
|
750
797
|
|
|
751
798
|
local summary met total axis
|
|
752
|
-
summary=$(printf '%s' "$parsed" |
|
|
753
|
-
met=$(printf '%s' "$parsed" |
|
|
754
|
-
total=$(printf '%s' "$parsed" |
|
|
755
|
-
axis=$(printf '%s' "$parsed" |
|
|
799
|
+
summary=$(printf '%s' "$parsed" | "$_dr_py" -I -S -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)
|
|
800
|
+
met=$(printf '%s' "$parsed" | "$_dr_py" -I -S -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)
|
|
801
|
+
total=$(printf '%s' "$parsed" | "$_dr_py" -I -S -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)
|
|
802
|
+
axis=$(printf '%s' "$parsed" | "$_dr_py" -I -S -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)
|
|
756
803
|
[ -n "$met" ] || met=0
|
|
757
804
|
[ -n "$total" ] || total=0
|
|
758
805
|
[ -n "$axis" ] || axis="unknown"
|
|
@@ -792,7 +839,7 @@ _loki_done_recog_finish() {
|
|
|
792
839
|
echo ""
|
|
793
840
|
echo "## Per-requirement evidence"
|
|
794
841
|
echo ""
|
|
795
|
-
printf '%s' "$parsed" |
|
|
842
|
+
printf '%s' "$parsed" | "$_dr_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
796
843
|
import json, sys
|
|
797
844
|
try:
|
|
798
845
|
d = json.load(sys.stdin)
|
|
@@ -811,7 +858,7 @@ for t in d.get('satisfied', []):
|
|
|
811
858
|
LOKI_DR_MET="$met" \
|
|
812
859
|
LOKI_DR_TOTAL="$total" \
|
|
813
860
|
LOKI_DR_TS="$ts" \
|
|
814
|
-
|
|
861
|
+
"$_dr_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
815
862
|
import json, os, tempfile
|
|
816
863
|
out = os.environ['LOKI_DR_OUT']
|
|
817
864
|
def i(v):
|
|
@@ -861,6 +908,7 @@ except Exception:
|
|
|
861
908
|
log_info "Verified ${met}/${total} requirements met by code inspection (no passing test run was available to confirm). ${summary}"
|
|
862
909
|
fi
|
|
863
910
|
log_info "To rebuild from scratch run 'loki start --fresh-prd'; to extend it, edit the spec or pass a new/changed PRD."
|
|
911
|
+
return 0
|
|
864
912
|
}
|
|
865
913
|
|
|
866
914
|
# incomplete path: write the satisfied-requirements manifest so
|
|
@@ -871,6 +919,10 @@ _loki_done_recog_write_manifest() {
|
|
|
871
919
|
local parsed="$2"
|
|
872
920
|
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
873
921
|
|
|
922
|
+
# S-200: no -I -S interpreter -> record nothing as met (full build).
|
|
923
|
+
local _dr_py
|
|
924
|
+
_dr_py="$(_loki_snapshot_py_tool)" || return 1
|
|
925
|
+
|
|
874
926
|
mkdir -p "$loki_dir/state" 2>/dev/null || true
|
|
875
927
|
|
|
876
928
|
local prd_sha
|
|
@@ -882,7 +934,7 @@ _loki_done_recog_write_manifest() {
|
|
|
882
934
|
LOKI_DR_PARSED="$parsed" \
|
|
883
935
|
LOKI_DR_SHA="$prd_sha" \
|
|
884
936
|
LOKI_DR_TS="$ts" \
|
|
885
|
-
|
|
937
|
+
"$_dr_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
886
938
|
import json, os, sys, tempfile
|
|
887
939
|
out = os.environ['LOKI_DR_OUT']
|
|
888
940
|
try:
|
|
@@ -923,7 +975,7 @@ except Exception:
|
|
|
923
975
|
if [ -f "$loki_dir/queue/pending.json" ]; then
|
|
924
976
|
# Drop prior PRD-sourced tasks so the incremental pass is the source of
|
|
925
977
|
# truth; non-PRD tasks (if any) are preserved.
|
|
926
|
-
LOKI_DR_PENDING="$loki_dir/queue/pending.json"
|
|
978
|
+
LOKI_DR_PENDING="$loki_dir/queue/pending.json" "$_dr_py" -I -S - <<'RESET_EOF' 2>/dev/null || true
|
|
927
979
|
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
928
980
|
import json, os, tempfile
|
|
929
981
|
p = os.environ.get("LOKI_DR_PENDING", ".loki/queue/pending.json")
|
|
@@ -948,8 +1000,8 @@ RESET_EOF
|
|
|
948
1000
|
fi
|
|
949
1001
|
|
|
950
1002
|
local met total
|
|
951
|
-
met=$(printf '%s' "$parsed" |
|
|
952
|
-
total=$(printf '%s' "$parsed" |
|
|
1003
|
+
met=$(printf '%s' "$parsed" | "$_dr_py" -I -S -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)
|
|
1004
|
+
total=$(printf '%s' "$parsed" | "$_dr_py" -I -S -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)
|
|
953
1005
|
local unmet=$(( ${total:-0} - ${met:-0} ))
|
|
954
1006
|
log_info "Done-recognition: ${met:-0} of ${total:-0} requirements already satisfied; building only the ${unmet} unmet. Pass --fresh-prd to rebuild from scratch."
|
|
955
1007
|
}
|
|
@@ -28,9 +28,41 @@
|
|
|
28
28
|
# guards on LOKI_PROVEN_PR_CHECK=1). It is also safe if called directly.
|
|
29
29
|
#
|
|
30
30
|
# Inline Python (D7): the call site's cwd is the agent's repo, so the proof
|
|
31
|
-
# readers
|
|
32
|
-
#
|
|
31
|
+
# readers drop '' and '.' from sys.path first; a committed json.py or
|
|
32
|
+
# sitecustomize.py cannot change the headline. Pinned by
|
|
33
33
|
# tests/test-proven-pr-check.sh (D7 section).
|
|
34
|
+
#
|
|
35
|
+
# BACKLOG 54 / S-203: python3 -E still loaded the user site-packages, where a
|
|
36
|
+
# .pth "import" line runs before any sys.path scrub and could forge json.load
|
|
37
|
+
# (a NOT VERIFIED proof read as VERIFIED). The readers resolve their
|
|
38
|
+
# interpreter through _loki_snapshot_py_tool and run it -I -S; when none
|
|
39
|
+
# resolves they print empty, as they do when python3 is absent. run.sh sources
|
|
40
|
+
# this file before it defines _loki_snapshot_py_tool, and its later definition
|
|
41
|
+
# replaces this copy, so the copy below only serves callers that source this
|
|
42
|
+
# file on its own (tests). run.sh is the source of truth: keep this copy
|
|
43
|
+
# byte-identical to it. Pinned by tests/test-proof-check-no-user-site-pth.sh
|
|
44
|
+
# and tests/test-council-py-tool-identity.sh.
|
|
45
|
+
declare -F _loki_snapshot_py_tool >/dev/null 2>&1 || \
|
|
46
|
+
_loki_snapshot_py_tool() {
|
|
47
|
+
local c
|
|
48
|
+
for c in /usr/bin/python3 /bin/python3; do
|
|
49
|
+
[ -x "$c" ] && [ ! -d "$c" ] && "$c" -I -S -c '' >/dev/null 2>&1 && { printf '%s\n' "$c"; return 0; }
|
|
50
|
+
done
|
|
51
|
+
local dir
|
|
52
|
+
local IFS=:
|
|
53
|
+
for dir in $PATH; do
|
|
54
|
+
case "$dir" in
|
|
55
|
+
/*) ;;
|
|
56
|
+
*) continue ;;
|
|
57
|
+
esac
|
|
58
|
+
if [ -x "$dir/python3" ] && [ ! -d "$dir/python3" ] \
|
|
59
|
+
&& "$dir/python3" -I -S -c '' >/dev/null 2>&1; then
|
|
60
|
+
printf '%s\n' "$dir/python3"
|
|
61
|
+
return 0
|
|
62
|
+
fi
|
|
63
|
+
done
|
|
64
|
+
return 1
|
|
65
|
+
}
|
|
34
66
|
|
|
35
67
|
# Double-source guard.
|
|
36
68
|
[ -n "${_PROOF_CHECK_SH:-}" ] && return 0
|
|
@@ -71,10 +103,11 @@ _proof_check_headline() {
|
|
|
71
103
|
local proof_path="${1:-}"
|
|
72
104
|
[ -n "$proof_path" ] || { printf '%s' ""; return 0; }
|
|
73
105
|
[ -f "$proof_path" ] || { printf '%s' ""; return 0; }
|
|
74
|
-
|
|
106
|
+
local _pc_py=""
|
|
107
|
+
_pc_py="$(_loki_snapshot_py_tool)" || { printf '%s' ""; return 0; }
|
|
75
108
|
|
|
76
109
|
local headline=""
|
|
77
|
-
headline="$(
|
|
110
|
+
headline="$("$_pc_py" -I -S - "$proof_path" <<'PY' 2>/dev/null || true
|
|
78
111
|
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
79
112
|
import json, sys
|
|
80
113
|
try:
|
|
@@ -103,10 +136,11 @@ _proof_check_proof_head_sha() {
|
|
|
103
136
|
local proof_path="${1:-}"
|
|
104
137
|
[ -n "$proof_path" ] || { printf '%s' ""; return 0; }
|
|
105
138
|
[ -f "$proof_path" ] || { printf '%s' ""; return 0; }
|
|
106
|
-
|
|
139
|
+
local _pc_py=""
|
|
140
|
+
_pc_py="$(_loki_snapshot_py_tool)" || { printf '%s' ""; return 0; }
|
|
107
141
|
|
|
108
142
|
local sha=""
|
|
109
|
-
sha="$(
|
|
143
|
+
sha="$("$_pc_py" -I -S - "$proof_path" <<'PY' 2>/dev/null || true
|
|
110
144
|
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
111
145
|
import json, sys
|
|
112
146
|
try:
|
|
@@ -138,10 +172,11 @@ _proof_check_run_id() {
|
|
|
138
172
|
local proof_path="${1:-}"
|
|
139
173
|
[ -n "$proof_path" ] || { printf '%s' ""; return 0; }
|
|
140
174
|
[ -f "$proof_path" ] || { printf '%s' ""; return 0; }
|
|
141
|
-
|
|
175
|
+
local _pc_py=""
|
|
176
|
+
_pc_py="$(_loki_snapshot_py_tool)" || { printf '%s' ""; return 0; }
|
|
142
177
|
|
|
143
178
|
local rid=""
|
|
144
|
-
rid="$(
|
|
179
|
+
rid="$("$_pc_py" -I -S - "$proof_path" <<'PY' 2>/dev/null || true
|
|
145
180
|
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
146
181
|
import json, sys
|
|
147
182
|
try:
|
|
@@ -28,9 +28,18 @@
|
|
|
28
28
|
# to existing heuristic dispatch).
|
|
29
29
|
#
|
|
30
30
|
# Inline Python (D7): the dispatch runs with the cwd inside the agent's repo, so
|
|
31
|
-
# every python3 here
|
|
32
|
-
#
|
|
31
|
+
# every python3 here drops '' and '.' from sys.path before any other import; a
|
|
32
|
+
# committed json.py or sitecustomize.py cannot read the votes.
|
|
33
33
|
# Pinned by tests/test-voter-agents-json.sh (Case D).
|
|
34
|
+
#
|
|
35
|
+
# S-202 (BACKLOG 54): -E alone still loads the user site-packages, whose .pth
|
|
36
|
+
# "import" lines run before that scrub and can forge json.loads (3 REJECT
|
|
37
|
+
# findings read as a COMPLETE round). Every call site resolves its interpreter
|
|
38
|
+
# through _loki_snapshot_py_tool and runs it -I -S; when none resolves, the
|
|
39
|
+
# dispatch returns 1 and completion-council's fail-closed path applies. run.sh
|
|
40
|
+
# is the source of truth for the helper; the guarded copy below only serves
|
|
41
|
+
# callers that source this file on its own. Keep it byte-identical to run.sh's.
|
|
42
|
+
# Pinned by tests/test-voter-agents-no-user-site-pth.sh.
|
|
34
43
|
|
|
35
44
|
# Guard against double-source.
|
|
36
45
|
if [ "${__LOKI_VOTER_AGENTS_SH_LOADED:-0}" = "1" ]; then
|
|
@@ -38,6 +47,28 @@ if [ "${__LOKI_VOTER_AGENTS_SH_LOADED:-0}" = "1" ]; then
|
|
|
38
47
|
fi
|
|
39
48
|
__LOKI_VOTER_AGENTS_SH_LOADED=1
|
|
40
49
|
|
|
50
|
+
declare -F _loki_snapshot_py_tool >/dev/null 2>&1 || \
|
|
51
|
+
_loki_snapshot_py_tool() {
|
|
52
|
+
local c
|
|
53
|
+
for c in /usr/bin/python3 /bin/python3; do
|
|
54
|
+
[ -x "$c" ] && [ ! -d "$c" ] && "$c" -I -S -c '' >/dev/null 2>&1 && { printf '%s\n' "$c"; return 0; }
|
|
55
|
+
done
|
|
56
|
+
local dir
|
|
57
|
+
local IFS=:
|
|
58
|
+
for dir in $PATH; do
|
|
59
|
+
case "$dir" in
|
|
60
|
+
/*) ;;
|
|
61
|
+
*) continue ;;
|
|
62
|
+
esac
|
|
63
|
+
if [ -x "$dir/python3" ] && [ ! -d "$dir/python3" ] \
|
|
64
|
+
&& "$dir/python3" -I -S -c '' >/dev/null 2>&1; then
|
|
65
|
+
printf '%s\n' "$dir/python3"
|
|
66
|
+
return 0
|
|
67
|
+
fi
|
|
68
|
+
done
|
|
69
|
+
return 1
|
|
70
|
+
}
|
|
71
|
+
|
|
41
72
|
# Resolve repo root once. This file lives at autonomy/lib/voter-agents.sh.
|
|
42
73
|
__LOKI_VA_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
|
43
74
|
__LOKI_VA_REPO_ROOT="$(cd "${__LOKI_VA_LIB_DIR}/../.." 2>/dev/null && pwd)"
|
|
@@ -59,12 +90,14 @@ loki_voter_agents_json() {
|
|
|
59
90
|
fi
|
|
60
91
|
local prd="${LOKI_PRD_PATH:-}"
|
|
61
92
|
local tier="${LOKI_RARV_TIER:-development}"
|
|
93
|
+
local _va_py
|
|
94
|
+
_va_py="$(_loki_snapshot_py_tool)" || { printf '%s' '{}'; return 0; }
|
|
62
95
|
|
|
63
96
|
_VA_ITER="$iter" \
|
|
64
97
|
_VA_PRD="$prd" \
|
|
65
98
|
_VA_TIER="$tier" \
|
|
66
99
|
_VA_COMPLEXITY="${LOKI_COMPLEXITY:-standard}" \
|
|
67
|
-
|
|
100
|
+
"$_va_py" -I -S -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
68
101
|
import json, os
|
|
69
102
|
iter_n = os.environ.get("_VA_ITER", "0")
|
|
70
103
|
prd = os.environ.get("_VA_PRD", "")
|
|
@@ -133,7 +166,9 @@ print(json.dumps(agents, separators=(",", ":")))
|
|
|
133
166
|
# Arg $1: terse base findings summary used to brief the DA prompt.
|
|
134
167
|
loki_devils_advocate_json() {
|
|
135
168
|
local summary="${1:-}"
|
|
136
|
-
|
|
169
|
+
local _va_py
|
|
170
|
+
_va_py="$(_loki_snapshot_py_tool)" || { printf '%s' '{}'; return 0; }
|
|
171
|
+
_VA_SUMMARY="$summary" "$_va_py" -I -S -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
137
172
|
import json, os
|
|
138
173
|
summary = os.environ.get("_VA_SUMMARY", "")[:1000]
|
|
139
174
|
agents = {
|
|
@@ -355,13 +390,15 @@ loki_council_dispatch_agents() {
|
|
|
355
390
|
local verdicts_dir="$COUNCIL_STATE_DIR/verdicts"
|
|
356
391
|
local votes_dir="$COUNCIL_STATE_DIR/votes"
|
|
357
392
|
mkdir -p "$verdicts_dir" "$votes_dir" 2>/dev/null || return 1
|
|
393
|
+
local _va_py
|
|
394
|
+
_va_py="$(_loki_snapshot_py_tool)" || return 1
|
|
358
395
|
|
|
359
396
|
_VA_RESP="$response" \
|
|
360
397
|
_VA_ITER="$iteration" \
|
|
361
398
|
_VA_VDIR="$verdicts_dir" \
|
|
362
399
|
_VA_RFILE="$votes_dir/round-${iteration}.json" \
|
|
363
400
|
_VA_EXPECTED="${COUNCIL_SIZE:-3}" \
|
|
364
|
-
|
|
401
|
+
"$_va_py" -I -S -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
365
402
|
import json, os, sys
|
|
366
403
|
from datetime import datetime, timezone
|
|
367
404
|
|