loki-mode 9.66.0 → 9.68.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/lib/done-recognition.sh +12 -0
- package/autonomy/loki +101 -9
- package/autonomy/run.sh +38 -7
- package/dashboard/__init__.py +1 -1
- package/dashboard/migration_engine.py +38 -13
- package/dashboard/prompt_optimizer.py +19 -6
- package/dashboard/server.py +1 -1
- package/dashboard/static/index.html +66 -66
- package/docs/INSTALLATION.md +1 -1
- package/docs/v10/ARCHITECT-CUTS.md +89 -0
- package/docs/v10/BOARD.md +53 -31
- package/docs/v10/METRICS.md +57 -0
- package/docs/v10/PROGRESS.md +14 -0
- package/loki-ts/dist/loki.js +2 -2
- 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-ChUvLzkF.js → AdminPage-DVx_wD7J.js} +1 -1
- package/web-app/dist/assets/{Avatar-Dpy96tAc.js → Avatar-B96e5b-p.js} +1 -1
- package/web-app/dist/assets/{Badge-BOHHYZTu.js → Badge-CYIMEysJ.js} +1 -1
- package/web-app/dist/assets/{Button-B_XqEF7a.js → Button-CFkLj4fj.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-C0z5QwNJ.js → CockpitPage-CD3N0t4G.js} +1 -1
- package/web-app/dist/assets/{ComparePage-KWL6acQk.js → ComparePage-Dfb-kBKl.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-BbyBJ4ZP.js → ErrorBoundary-DxBJ42I1.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-Btblkwfr.js → EvidenceReceiptPanel-30CPS1Mt.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-7Bi1t04Q.js → GitHubIssuesPanel-CW-er50w.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-BxZtNprq.js → GitHubPRsPanel-DYHxxH3B.js} +1 -1
- package/web-app/dist/assets/{HomePage-BxDh3-CD.js → HomePage-KSJPrzJw.js} +2 -2
- package/web-app/dist/assets/{LoginPage-DpXq4rQe.js → LoginPage-Bz9xYiFY.js} +1 -1
- package/web-app/dist/assets/{MagicPage-DP2k0ioE.js → MagicPage-kn-ESajN.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-Dus04BUR.js → MetricsPage-ne6rjAjb.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage--54yr9Cy.js → NotFoundPage-BY9GRHUS.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-Cs6LOI7R.js → ProjectPage-C6wd7nwM.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-5cpa0X1d.js → ProjectsPage-DCZFtBYI.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-Duc5ApRj.js → SettingsPage-BxBtwIEH.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-C1wrPZgl.js → ShowcasePage-rj7G7hEn.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-CoD3LARj.js → SystemSettingsPage-C6ulpcUC.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-GuQ4YDV5.js → TeamsPage-CtnFtvT9.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-CvWecmzl.js → TemplatesPage-Btrc0vGx.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-DFZw34kD.js → TerminalOutput-DNOHB6r6.js} +1 -1
- package/web-app/dist/assets/{activity-BvHkXBYj.js → activity-nzowoOR9.js} +1 -1
- package/web-app/dist/assets/{bell-Bw2j-HI7.js → bell-D_z8w7hF.js} +1 -1
- package/web-app/dist/assets/{bot-BRuEmshQ.js → bot-BIUaGOa_.js} +1 -1
- package/web-app/dist/assets/{check-B5uToWdF.js → check-BDJjUhRA.js} +1 -1
- package/web-app/dist/assets/{chevron-left-D-tXeEPI.js → chevron-left-D012PxLS.js} +1 -1
- package/web-app/dist/assets/{circle-alert-p3-Bg3sx.js → circle-alert-CSwiL_Ci.js} +1 -1
- package/web-app/dist/assets/{clock-CJc_91vp.js → clock-D998ijVs.js} +1 -1
- package/web-app/dist/assets/{cloud-lhmojUN8.js → cloud-DRi3zhWw.js} +1 -1
- package/web-app/dist/assets/{code-xml-DMvQ2jw1.js → code-xml-5-tMr26k.js} +1 -1
- package/web-app/dist/assets/{database-EdwkplCN.js → database-BJyZFH_L.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-BQiPo9Co.js → dollar-sign-DV3VUIZD.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-CXUzs2z1.js → file-code-corner-Dt1RB6AK.js} +1 -1
- package/web-app/dist/assets/{file-plus-hS0xkTL5.js → file-plus-BK6e_Aa4.js} +1 -1
- package/web-app/dist/assets/{globe-B6Xk69Fj.js → globe-BLD9gQs7.js} +1 -1
- package/web-app/dist/assets/{hammer-B_evoVEr.js → hammer-BpgnUNfT.js} +1 -1
- package/web-app/dist/assets/{index-XZrgDphJ.js → index-B9_MlKR6.js} +2 -2
- package/web-app/dist/assets/{layers-Bb295b4u.js → layers-KtTIakvg.js} +1 -1
- package/web-app/dist/assets/{loader-circle-C8cKZhjP.js → loader-circle-B1xKf0Rv.js} +1 -1
- package/web-app/dist/assets/{lock-DcC9B8sE.js → lock-CjDPMVtQ.js} +1 -1
- package/web-app/dist/assets/{package-BFz4FYcR.js → package-BJeOvQMj.js} +1 -1
- package/web-app/dist/assets/{plus-aS4YpFPG.js → plus-Hz9gp1EB.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-B9_ksndc.js → refresh-cw-DQ_mu77L.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-CBogZEoT.js → rotate-ccw-BfLD_AC3.js} +1 -1
- package/web-app/dist/assets/{scroll-text-DMGgO6uq.js → scroll-text-fPCd2SRe.js} +1 -1
- package/web-app/dist/assets/{server-CsEqUM8R.js → server-B5Vp_LNG.js} +1 -1
- package/web-app/dist/assets/{shield-alert-Btnj6tmQ.js → shield-alert-CyIXZM2p.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-C_NLSPqa.js → thumbs-up-CfvcaIBe.js} +1 -1
- package/web-app/dist/assets/{trash-2-zqZc9cSK.js → trash-2-DpV8R-rX.js} +1 -1
- package/web-app/dist/assets/{trending-down-BGQh2rVE.js → trending-down-BUPL60c2.js} +1 -1
- package/web-app/dist/assets/{trending-up-C31TrzUt.js → trending-up-C10V0yZr.js} +1 -1
- package/web-app/dist/assets/{upload-CLwWKjCG.js → upload-D0psWSbr.js} +1 -1
- package/web-app/dist/assets/{usePolling-8wwOP53Y.js → usePolling-zDjbIVGY.js} +1 -1
- package/web-app/dist/assets/{user-DUkbxibF.js → user-R0DRj17j.js} +1 -1
- package/web-app/dist/index.html +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.68.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.68.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.
|
|
1
|
+
9.68.0
|
|
@@ -461,8 +461,20 @@ def tests_axis(path):
|
|
|
461
461
|
if ec != 0:
|
|
462
462
|
return "red"
|
|
463
463
|
# exit 0 alone is green only if nothing else contradicts it (checked below).
|
|
464
|
+
def _nonzero_int(v):
|
|
465
|
+
return isinstance(v, int) and not isinstance(v, bool) and v != 0
|
|
466
|
+
# Zero-test record: nothing actually ran, regardless of self-reported
|
|
467
|
+
# pass/fail. Checked before the pass:true/false branches below so a
|
|
468
|
+
# plausible {"pass": true, "total": 0} shape cannot slip through the
|
|
469
|
+
# pass:true branch as a false green.
|
|
470
|
+
if isinstance(d.get("total"), int) and d.get("total") == 0:
|
|
471
|
+
return "unknown"
|
|
464
472
|
p = d.get("pass")
|
|
465
473
|
if p is True:
|
|
474
|
+
# A self-reported pass contradicted by a nonzero failure count (under
|
|
475
|
+
# either key production/legacy shapes use) is not authoritative green.
|
|
476
|
+
if _nonzero_int(d.get("failed")) or _nonzero_int(d.get("failed_count")):
|
|
477
|
+
return "red"
|
|
466
478
|
# A clean pass:true with no contradicting red signal is authoritative green.
|
|
467
479
|
if d.get("failed") in (None, 0) and (ec in (None, 0)):
|
|
468
480
|
return "green"
|
package/autonomy/loki
CHANGED
|
@@ -856,8 +856,48 @@ PY
|
|
|
856
856
|
# absent measurement is not evidence, and inventing a PASS here would be worse
|
|
857
857
|
# than inventing a FAIL.
|
|
858
858
|
loki_remote_attestation_status() {
|
|
859
|
-
local pj="$1" url="$2"
|
|
859
|
+
local pj="$1" url="$2" _jh
|
|
860
860
|
[ -n "$url" ] || { printf ''; return 0; }
|
|
861
|
+
# A JWKS key set travels over the same channel as the receipt it certifies.
|
|
862
|
+
# Plain http lets anyone on the network path swap in their own key set, and
|
|
863
|
+
# the attestation would then "verify" against a forged key -- a MITM the
|
|
864
|
+
# TLS the API already uses is meant to prevent. Only https is trusted;
|
|
865
|
+
# loopback http and file:// are the exceptions, because neither one leaves
|
|
866
|
+
# this machine for anyone to sit on. Checked here, before python is even
|
|
867
|
+
# invoked, so a rejected URL is never fetched -- not "fetched and then
|
|
868
|
+
# distrusted". Same check in loki_proof_attestation_check below -- change
|
|
869
|
+
# both.
|
|
870
|
+
#
|
|
871
|
+
# The host is bounded exactly (up to the first "/", then stripped of any
|
|
872
|
+
# port) rather than matched with a trailing glob, and a userinfo "@" is
|
|
873
|
+
# rejected outright -- "http://127.0.0.1:80@evil.com" is host evil.com to
|
|
874
|
+
# any real URL parser (userinfo before the @, host after), and a loose
|
|
875
|
+
# `http://127.0.0.1:*` glob would wrongly wave that straight through.
|
|
876
|
+
case "$url" in
|
|
877
|
+
https://*|file://*) ;;
|
|
878
|
+
http://*)
|
|
879
|
+
_jh="${url#http://}"; _jh="${_jh%%/*}"
|
|
880
|
+
case "$_jh" in
|
|
881
|
+
*@*) printf ''; return 0 ;;
|
|
882
|
+
esac
|
|
883
|
+
# A bracketed IPv6 literal must be the WHOLE host: "[::1]" or
|
|
884
|
+
# "[::1]:port", never a prefix, or "[::1].evil.com" (not itself
|
|
885
|
+
# resolvable as loopback, but not rejected either) would parse as
|
|
886
|
+
# loopback "::1" with everything after the first "]" silently
|
|
887
|
+
# dropped instead of validated.
|
|
888
|
+
case "$_jh" in
|
|
889
|
+
\[*\]) _jh="${_jh#\[}"; _jh="${_jh%\]}" ;;
|
|
890
|
+
\[*\]:*) _jh="${_jh#\[}"; _jh="${_jh%%\]:*}" ;;
|
|
891
|
+
\[*) printf ''; return 0 ;;
|
|
892
|
+
*) _jh="${_jh%%:*}" ;;
|
|
893
|
+
esac
|
|
894
|
+
case "$_jh" in
|
|
895
|
+
127.0.0.1|localhost|::1) ;;
|
|
896
|
+
*) printf ''; return 0 ;;
|
|
897
|
+
esac
|
|
898
|
+
;;
|
|
899
|
+
*) printf ''; return 0 ;;
|
|
900
|
+
esac
|
|
861
901
|
command -v python3 >/dev/null 2>&1 || { printf ''; return 0; }
|
|
862
902
|
local tok
|
|
863
903
|
# jq's `//` treats JSON false the same as null/missing, which is exactly
|
|
@@ -954,8 +994,53 @@ PY
|
|
|
954
994
|
# testing found exactly this: a mutation applied to the first copy left every
|
|
955
995
|
# assertion of the second suite green.
|
|
956
996
|
loki_proof_attestation_check() {
|
|
957
|
-
local pj="$1" src="$2"
|
|
997
|
+
local pj="$1" src="$2" _jh
|
|
958
998
|
[ -n "$src" ] || { printf ''; return 0; }
|
|
999
|
+
# Same MITM guard as loki_remote_attestation_status above -- change both.
|
|
1000
|
+
# Only applies to a URL source; a local file (the third-party offline case)
|
|
1001
|
+
# never travels a network and needs no scheme at all. The host is bounded
|
|
1002
|
+
# exactly and a userinfo "@" is rejected outright -- see the sibling
|
|
1003
|
+
# function's comment for why a trailing glob is not safe here.
|
|
1004
|
+
#
|
|
1005
|
+
# A recognized scheme prefix is checked FIRST, before any "-f" test: the
|
|
1006
|
+
# process cwd is the checkout being verified (attacker-controlled), and a
|
|
1007
|
+
# repo can ship a file whose literal name IS a URL string (for example
|
|
1008
|
+
# "https:/evil.example.com/.well-known/jwks.json" as a real path on disk).
|
|
1009
|
+
# Testing "-f" first would treat that shadow file as the local-file form
|
|
1010
|
+
# and hand its content straight to the caller with no network involved at
|
|
1011
|
+
# all -- silently defeating the "only https is trusted" guarantee this
|
|
1012
|
+
# function exists to provide. Scheme detection must win over the
|
|
1013
|
+
# filesystem every time, not just when no file happens to collide.
|
|
1014
|
+
case "$src" in
|
|
1015
|
+
https://*|http://*|file://*)
|
|
1016
|
+
case "$src" in
|
|
1017
|
+
https://*|file://*) ;;
|
|
1018
|
+
http://*)
|
|
1019
|
+
_jh="${src#http://}"; _jh="${_jh%%/*}"
|
|
1020
|
+
case "$_jh" in
|
|
1021
|
+
*@*) printf ''; return 0 ;;
|
|
1022
|
+
esac
|
|
1023
|
+
# See loki_remote_attestation_status above for why the
|
|
1024
|
+
# bracketed-IPv6 match must bound the closing bracket.
|
|
1025
|
+
case "$_jh" in
|
|
1026
|
+
\[*\]) _jh="${_jh#\[}"; _jh="${_jh%\]}" ;;
|
|
1027
|
+
\[*\]:*) _jh="${_jh#\[}"; _jh="${_jh%%\]:*}" ;;
|
|
1028
|
+
\[*) printf ''; return 0 ;;
|
|
1029
|
+
*) _jh="${_jh%%:*}" ;;
|
|
1030
|
+
esac
|
|
1031
|
+
case "$_jh" in
|
|
1032
|
+
127.0.0.1|localhost|::1) ;;
|
|
1033
|
+
*) printf ''; return 0 ;;
|
|
1034
|
+
esac
|
|
1035
|
+
;;
|
|
1036
|
+
esac
|
|
1037
|
+
;;
|
|
1038
|
+
*)
|
|
1039
|
+
# A plain path is decided in python below, which reports an
|
|
1040
|
+
# unsigned receipt as "none" BEFORE it looks at the key file, so a
|
|
1041
|
+
# missing key file on an unsigned receipt still fails (exit 1).
|
|
1042
|
+
;;
|
|
1043
|
+
esac
|
|
959
1044
|
command -v python3 >/dev/null 2>&1 || { printf ''; return 0; }
|
|
960
1045
|
python3 -E - "$pj" "$src" "${_LOKI_SCRIPT_DIR}" <<'PY' 2>/dev/null || printf ''
|
|
961
1046
|
# The cwd is the checkout being verified: drop it from sys.path before any
|
|
@@ -984,15 +1069,20 @@ if tok is None or tok == "" or tok is False:
|
|
|
984
1069
|
print("none"); sys.exit(0) # a FACT about the receipt, not an error
|
|
985
1070
|
src = sys.argv[2]
|
|
986
1071
|
try:
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1072
|
+
# Scheme check first, same as the bash gate above -- a "-f" test run
|
|
1073
|
+
# first would let a local file whose literal name IS an http(s) URL
|
|
1074
|
+
# string (the process cwd is the checkout being verified) silently stand
|
|
1075
|
+
# in for a real fetch. A recognized scheme always means "URL", even if a
|
|
1076
|
+
# same-named file happens to exist on disk.
|
|
1077
|
+
if src.startswith(("http://", "https://")):
|
|
992
1078
|
url = src if src.rstrip("/").endswith("jwks.json") else \
|
|
993
1079
|
src.rstrip("/") + "/.well-known/jwks.json"
|
|
994
1080
|
with urllib.request.urlopen(url, timeout=10) as r:
|
|
995
1081
|
jwks = json.loads(r.read())
|
|
1082
|
+
elif os.path.isfile(src):
|
|
1083
|
+
jwks = json.load(open(src))
|
|
1084
|
+
else:
|
|
1085
|
+
sys.exit(1)
|
|
996
1086
|
except Exception:
|
|
997
1087
|
sys.exit(1) # "" : unreachable/unreadable, no verdict
|
|
998
1088
|
# A key set that is not {"keys": [{...}, ...]} -- INCLUDING an empty list -- is
|
|
@@ -1177,7 +1267,8 @@ loki_remote_verify_receipt() {
|
|
|
1177
1267
|
# gpg-unavailable branch above: NOT CHECKED, return 0, provenance unproven.
|
|
1178
1268
|
if [ -z "$_attest" ] && jq -e '.verification.attestation // empty' "$pj" >/dev/null 2>&1; then
|
|
1179
1269
|
echo -e " ${YELLOW}NOT CHECKED${NC}: the receipt carries an attestation, but it could not be checked"
|
|
1180
|
-
echo " against ${_attest_url:-<no url>} (JWKS unreachable,
|
|
1270
|
+
echo " against ${_attest_url:-<no url>} (JWKS unreachable, python cryptography missing, or"
|
|
1271
|
+
echo " the JWKS URL is not https -- only loopback is allowed over plain http)."
|
|
1181
1272
|
echo " Its integrity hash matches. Provenance is unproven here, not disproven."
|
|
1182
1273
|
echo " Install python3 cryptography, or re-run where the build server is reachable."
|
|
1183
1274
|
return 0
|
|
@@ -37014,7 +37105,8 @@ print(json.dumps(json.load(open(os.environ['LOKI_PROOF_JSON'])), indent=2))
|
|
|
37014
37105
|
# read as either a pass or an accusation, so it exits 2
|
|
37015
37106
|
# (could not check). A tamper/drift 1 from the base
|
|
37016
37107
|
# verifier is a finding and is never softened to 2.
|
|
37017
|
-
echo -e "${YELLOW}attestation: NOT CHECKED -- could not check against ${_pv_jwks} (key set unreadable
|
|
37108
|
+
echo -e "${YELLOW}attestation: NOT CHECKED -- could not check against ${_pv_jwks} (key set unreadable,${NC}" >&2
|
|
37109
|
+
echo -e "${YELLOW} verifier dependency missing, or the JWKS URL is not https -- only loopback is allowed over plain http).${NC}" >&2
|
|
37018
37110
|
echo " This is not a failure of the receipt. Nothing was proven either way (exit 2)." >&2
|
|
37019
37111
|
if [ "$_pv_rc" -eq 0 ]; then _pv_rc=2; fi
|
|
37020
37112
|
_pv_attestation_field="NOT CHECKED"
|
package/autonomy/run.sh
CHANGED
|
@@ -5911,6 +5911,27 @@ _loki_trusted_push() {
|
|
|
5911
5911
|
log_warn "Not pushing branch '$_branch': origin changed during the run (it was $(_loki_github_repo_from_url "$_LOKI_PINNED_ORIGIN" 2>/dev/null || echo "not a GitHub repository") when the run started). Loki does not push to a destination the agent could have chosen."
|
|
5912
5912
|
return 2
|
|
5913
5913
|
fi
|
|
5914
|
+
# S-100: never push a default branch, for every caller (on_run_complete,
|
|
5915
|
+
# create_session_pr): the branch name can come from agent-writable state.
|
|
5916
|
+
# The default branch is resolved for the origin just validated (the pinned
|
|
5917
|
+
# one when pinned), gh run from / with an explicit OWNER/REPO. An
|
|
5918
|
+
# unresolvable default branch refuses: fail closed.
|
|
5919
|
+
local _repo _def=""
|
|
5920
|
+
case "$_branch" in
|
|
5921
|
+
main|master|HEAD)
|
|
5922
|
+
log_warn "Not pushing branch '$_branch': Loki never pushes directly to a default branch."
|
|
5923
|
+
return 2 ;;
|
|
5924
|
+
esac
|
|
5925
|
+
_repo="$(_loki_github_repo_from_url "$_url")"
|
|
5926
|
+
_def="$(_loki_run_neutral "$_repo" "$_runner" gh repo view "$_repo" --json defaultBranchRef --jq '.defaultBranchRef.name' 2>/dev/null)" || _def=""
|
|
5927
|
+
if [ -z "$_def" ]; then
|
|
5928
|
+
log_warn "Not pushing branch '$_branch': could not resolve the default branch of $_repo, and Loki never risks a direct push to it."
|
|
5929
|
+
return 2
|
|
5930
|
+
fi
|
|
5931
|
+
if [ "$_branch" = "$_def" ]; then
|
|
5932
|
+
log_warn "Not pushing branch '$_branch': it is the default branch of $_repo, and Loki never pushes directly to the default branch."
|
|
5933
|
+
return 2
|
|
5934
|
+
fi
|
|
5914
5935
|
_tmp="$(mktemp -d "${TMPDIR:-/tmp}/loki-push.XXXXXX")" || return 1
|
|
5915
5936
|
# --update-shallow: a shallow agent repo's history ends in shallow roots,
|
|
5916
5937
|
# which a plain fetch rejects while still exiting 0. The rev-parse makes a
|
|
@@ -6070,6 +6091,8 @@ on_run_complete() {
|
|
|
6070
6091
|
case "$branch" in
|
|
6071
6092
|
""|main|master|HEAD) return 0 ;;
|
|
6072
6093
|
esac
|
|
6094
|
+
# S-100: the repository's ACTUAL default branch (develop, trunk, ...) is
|
|
6095
|
+
# refused inside _loki_trusted_push, for every caller.
|
|
6073
6096
|
log_info "LOKI_DELEGATE_PR=1: opening a local pull request for branch '$branch'..."
|
|
6074
6097
|
# Push, then create. Non-interactive (no tty in --bg). Best-effort, each
|
|
6075
6098
|
# network call bounded by the timeout guard above.
|
|
@@ -21516,9 +21539,14 @@ except (json.JSONDecodeError, KeyError, TypeError, OSError):
|
|
|
21516
21539
|
mv "$state_file" "${state_file}.corrupt.$(date +%s)" 2>/dev/null || true
|
|
21517
21540
|
# Starts at iteration 0: drop the previous session's test
|
|
21518
21541
|
# evidence, as the iteration-0 block at the end does.
|
|
21542
|
+
# S-124 follow-up: static-analysis.pass has no freshness
|
|
21543
|
+
# marker of its own (same reasoning as the block below), so
|
|
21544
|
+
# this corrupted-state restart must drop it too, or a stale
|
|
21545
|
+
# copy survives this sibling iteration-0 trigger.
|
|
21519
21546
|
rm -f "${TARGET_DIR:-.}/.loki/quality/.test-results.iter" \
|
|
21520
21547
|
"${TARGET_DIR:-.}/.loki/quality/unit-tests.pass" \
|
|
21521
|
-
"${TARGET_DIR:-.}/.loki/quality/test-results.json"
|
|
21548
|
+
"${TARGET_DIR:-.}/.loki/quality/test-results.json" \
|
|
21549
|
+
"${TARGET_DIR:-.}/.loki/quality/static-analysis.pass" 2>/dev/null || true
|
|
21522
21550
|
return
|
|
21523
21551
|
fi
|
|
21524
21552
|
|
|
@@ -21589,16 +21617,19 @@ except (json.JSONDecodeError, KeyError, TypeError, OSError):
|
|
|
21589
21617
|
RETRY_COUNT=0
|
|
21590
21618
|
fi
|
|
21591
21619
|
# A session that starts at iteration 0 has run nothing yet, so a leftover
|
|
21592
|
-
# freshness marker
|
|
21593
|
-
# Iterations restart at 0, so keeping them would let
|
|
21594
|
-
# as this iteration's result.
|
|
21620
|
+
# freshness marker, unit-tests.pass, or static-analysis.pass is a previous
|
|
21621
|
+
# session's evidence. Iterations restart at 0, so keeping them would let
|
|
21622
|
+
# an old pass:true read as this iteration's result.
|
|
21595
21623
|
if [ "${ITERATION_COUNT:-0}" = "0" ]; then
|
|
21596
|
-
# Same path the writer (enforce_test_coverage)
|
|
21597
|
-
# readers use.
|
|
21624
|
+
# Same path the writer (enforce_test_coverage, enforce_static_analysis)
|
|
21625
|
+
# and the freshness readers use.
|
|
21598
21626
|
local _q="${TARGET_DIR:-.}/.loki/quality"
|
|
21599
21627
|
# test-results.json too: with the marker gone the receipt's quality
|
|
21600
21628
|
# gates fall back to its status, reporting a previous session's run.
|
|
21601
|
-
|
|
21629
|
+
# static-analysis.pass has no freshness marker of its own, so a stale
|
|
21630
|
+
# copy from a previous session reads as a pass of this session's
|
|
21631
|
+
# exogenous static_analysis gate unless it is dropped here too.
|
|
21632
|
+
rm -f "$_q/.test-results.iter" "$_q/unit-tests.pass" "$_q/test-results.json" "$_q/static-analysis.pass" 2>/dev/null || true
|
|
21602
21633
|
fi
|
|
21603
21634
|
}
|
|
21604
21635
|
|
package/dashboard/__init__.py
CHANGED
|
@@ -766,19 +766,29 @@ class MigrationPipeline:
|
|
|
766
766
|
current_phase = phase
|
|
767
767
|
overall_status = "failed"
|
|
768
768
|
|
|
769
|
-
# Feature stats
|
|
770
|
-
|
|
771
|
-
|
|
769
|
+
# Feature stats. Missing/corrupt is NOT the same as a real empty
|
|
770
|
+
# plan (0 features): counts go to None with a reason so a consumer
|
|
771
|
+
# can tell "no features yet" apart from "could not read the file".
|
|
772
|
+
features_total: Optional[int] = 0
|
|
773
|
+
features_passing: Optional[int] = 0
|
|
774
|
+
features_error: Optional[str] = None
|
|
772
775
|
try:
|
|
773
776
|
features = self.load_features()
|
|
774
777
|
features_total = len(features)
|
|
775
778
|
features_passing = sum(1 for f in features if f.passes)
|
|
776
|
-
except
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
779
|
+
except FileNotFoundError:
|
|
780
|
+
features_total = None
|
|
781
|
+
features_passing = None
|
|
782
|
+
features_error = "features.json not found"
|
|
783
|
+
except (json.JSONDecodeError, TypeError) as exc:
|
|
784
|
+
features_total = None
|
|
785
|
+
features_passing = None
|
|
786
|
+
features_error = f"features.json is corrupt: {exc}"
|
|
787
|
+
|
|
788
|
+
# Step stats. Same missing/corrupt-vs-empty distinction as features.
|
|
789
|
+
steps_total: Optional[int] = 0
|
|
790
|
+
steps_completed: Optional[int] = 0
|
|
791
|
+
steps_error: Optional[str] = None
|
|
782
792
|
current_step = None
|
|
783
793
|
current_step_index = 0
|
|
784
794
|
try:
|
|
@@ -790,8 +800,14 @@ class MigrationPipeline:
|
|
|
790
800
|
current_step = in_progress[0].id
|
|
791
801
|
# Current step index: completed + 1 (1-based) or completed if all done
|
|
792
802
|
current_step_index = min(steps_completed + 1, steps_total) if steps_total else 0
|
|
793
|
-
except
|
|
794
|
-
|
|
803
|
+
except FileNotFoundError:
|
|
804
|
+
steps_total = None
|
|
805
|
+
steps_completed = None
|
|
806
|
+
steps_error = "migration-plan.json not found"
|
|
807
|
+
except (json.JSONDecodeError, TypeError) as exc:
|
|
808
|
+
steps_total = None
|
|
809
|
+
steps_completed = None
|
|
810
|
+
steps_error = f"migration-plan.json is corrupt: {exc}"
|
|
795
811
|
|
|
796
812
|
# Last checkpoint metadata
|
|
797
813
|
last_checkpoint_data: Optional[dict[str, Any]] = None
|
|
@@ -849,8 +865,17 @@ class MigrationPipeline:
|
|
|
849
865
|
"source": source_path,
|
|
850
866
|
"target": target_name,
|
|
851
867
|
"current_step": current_step,
|
|
852
|
-
"features": {
|
|
853
|
-
|
|
868
|
+
"features": {
|
|
869
|
+
"passing": features_passing,
|
|
870
|
+
"total": features_total,
|
|
871
|
+
**({"reason": features_error} if features_error else {}),
|
|
872
|
+
},
|
|
873
|
+
"steps": {
|
|
874
|
+
"current": current_step_index,
|
|
875
|
+
"completed": steps_completed,
|
|
876
|
+
"total": steps_total,
|
|
877
|
+
**({"reason": steps_error} if steps_error else {}),
|
|
878
|
+
},
|
|
854
879
|
"last_checkpoint": last_checkpoint_data,
|
|
855
880
|
"checkpoints_count": len(manifest.checkpoints),
|
|
856
881
|
"seams": seams_data,
|
|
@@ -66,7 +66,11 @@ class PromptOptimizer:
|
|
|
66
66
|
"""Get the current (latest) prompt optimization version.
|
|
67
67
|
|
|
68
68
|
Returns:
|
|
69
|
-
The latest version data
|
|
69
|
+
The latest version data on a clean read. If no version file
|
|
70
|
+
exists yet, a "never_ran" marker with null fields (distinct
|
|
71
|
+
from a real version 0). If the file exists but cannot be
|
|
72
|
+
parsed, an "error" marker (also null fields) so a corrupt file
|
|
73
|
+
is never mistaken for "never ran".
|
|
70
74
|
"""
|
|
71
75
|
latest = self._latest_file()
|
|
72
76
|
if latest.is_file():
|
|
@@ -74,13 +78,20 @@ class PromptOptimizer:
|
|
|
74
78
|
return json.loads(latest.read_text(encoding="utf-8"))
|
|
75
79
|
except (json.JSONDecodeError, OSError) as exc:
|
|
76
80
|
logger.warning("Failed to read latest prompt version: %s", exc)
|
|
81
|
+
return self._sentinel("error")
|
|
77
82
|
|
|
83
|
+
return self._sentinel("never_ran")
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _sentinel(status: str) -> dict[str, Any]:
|
|
87
|
+
"""Build a never-ran/error marker. Never a fake version 0 or a fake count."""
|
|
78
88
|
return {
|
|
79
|
-
"version":
|
|
89
|
+
"version": None,
|
|
80
90
|
"generated_at": None,
|
|
81
|
-
"based_on_sessions":
|
|
82
|
-
"failures_analyzed":
|
|
91
|
+
"based_on_sessions": None,
|
|
92
|
+
"failures_analyzed": None,
|
|
83
93
|
"changes": [],
|
|
94
|
+
"status": status,
|
|
84
95
|
}
|
|
85
96
|
|
|
86
97
|
def get_prompt_for_agent(self, agent_type: str) -> dict[str, Any] | None:
|
|
@@ -95,7 +106,7 @@ class PromptOptimizer:
|
|
|
95
106
|
Dict with agent-specific prompt changes, or None if no optimizations exist.
|
|
96
107
|
"""
|
|
97
108
|
current = self.get_current_version()
|
|
98
|
-
if current["version"]
|
|
109
|
+
if not current["version"]:
|
|
99
110
|
return None
|
|
100
111
|
|
|
101
112
|
agent_changes = [c for c in current.get("changes", []) if c.get("agent_type") == agent_type]
|
|
@@ -225,7 +236,9 @@ class PromptOptimizer:
|
|
|
225
236
|
changes = self._generate_changes(failure_data)
|
|
226
237
|
|
|
227
238
|
with self._file_lock:
|
|
228
|
-
|
|
239
|
+
# Never-ran and error sentinels both carry version None; either
|
|
240
|
+
# way there is no real prior version, so the next one is 1.
|
|
241
|
+
current_version = self.get_current_version()["version"] or 0
|
|
229
242
|
new_version = current_version + 1
|
|
230
243
|
|
|
231
244
|
result = {
|
package/dashboard/server.py
CHANGED
|
@@ -10350,7 +10350,7 @@ async def get_checklist_waivers():
|
|
|
10350
10350
|
try:
|
|
10351
10351
|
return json.loads(waivers_file.read_text())
|
|
10352
10352
|
except (json.JSONDecodeError, IOError):
|
|
10353
|
-
return
|
|
10353
|
+
return JSONResponse(status_code=500, content={"error": "Failed to read waivers file"})
|
|
10354
10354
|
|
|
10355
10355
|
|
|
10356
10356
|
@app.post("/api/checklist/waivers", dependencies=[Depends(auth.require_scope("control"))])
|