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.
Files changed (79) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/completion-council.sh +24 -6
  4. package/autonomy/council-v2.sh +60 -13
  5. package/autonomy/lib/done-recognition.sh +73 -21
  6. package/autonomy/lib/proof-check.sh +43 -8
  7. package/autonomy/lib/voter-agents.sh +42 -5
  8. package/autonomy/loki +3 -0
  9. package/autonomy/prd-checklist.sh +77 -16
  10. package/autonomy/run.sh +38 -3
  11. package/dashboard/__init__.py +1 -1
  12. package/docs/INSTALLATION.md +1 -1
  13. package/docs/R3-COST-OBSERVABILITY-DESIGN.md +7 -1
  14. package/docs/exit-codes.md +21 -0
  15. package/docs/v10/ARCHITECT-CUTS.md +397 -234
  16. package/docs/v10/BOARD.md +84 -44
  17. package/docs/v10/GUARDS.md +45 -28
  18. package/loki-ts/dist/loki.js +341 -338
  19. package/mcp/__init__.py +1 -1
  20. package/package.json +1 -1
  21. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  22. package/web-app/dist/assets/{AdminPage-DaeYgQSE.js → AdminPage-Bw8oPnII.js} +1 -1
  23. package/web-app/dist/assets/{Avatar-CgwQi58_.js → Avatar-BwEtc3RH.js} +1 -1
  24. package/web-app/dist/assets/{Badge-Bwq-lpRh.js → Badge-C_-lfbUl.js} +1 -1
  25. package/web-app/dist/assets/{Button-BEf4Nher.js → Button-BdDGKKeF.js} +1 -1
  26. package/web-app/dist/assets/CockpitPage-Dx9d4fEm.js +47 -0
  27. package/web-app/dist/assets/{ComparePage-tFbdo90G.js → ComparePage-BKtAhhIJ.js} +1 -1
  28. package/web-app/dist/assets/{ErrorBoundary-Brewvgm9.js → ErrorBoundary-BuG16GpG.js} +1 -1
  29. package/web-app/dist/assets/{EvidenceReceiptPanel-h_82QBVa.js → EvidenceReceiptPanel-NATnqxKs.js} +1 -1
  30. package/web-app/dist/assets/{GitHubIssuesPanel-Dcg5xDMm.js → GitHubIssuesPanel-BgOwdOpj.js} +1 -1
  31. package/web-app/dist/assets/{GitHubPRsPanel-DFKzVyA1.js → GitHubPRsPanel-CBJyHI1c.js} +1 -1
  32. package/web-app/dist/assets/{HomePage-D21cM1Je.js → HomePage-BHj17Epq.js} +2 -2
  33. package/web-app/dist/assets/{LoginPage-DJ9yqMn4.js → LoginPage-Dig4H-HO.js} +1 -1
  34. package/web-app/dist/assets/{MagicPage-mwkjJhBy.js → MagicPage-w6WJ35PH.js} +1 -1
  35. package/web-app/dist/assets/{MetricsPage-D2q-PD-s.js → MetricsPage-CvjSUOWn.js} +1 -1
  36. package/web-app/dist/assets/{NotFoundPage-BtusJvgS.js → NotFoundPage-PAcYmGkU.js} +1 -1
  37. package/web-app/dist/assets/{ProjectPage-CRPALtO8.js → ProjectPage-CBkT5JyW.js} +3 -3
  38. package/web-app/dist/assets/{ProjectsPage-DuzABpfc.js → ProjectsPage-Cs6yuWEU.js} +1 -1
  39. package/web-app/dist/assets/{SettingsPage-DhJPX-Rf.js → SettingsPage-BU9F40Yn.js} +1 -1
  40. package/web-app/dist/assets/{ShowcasePage-CvgS47nu.js → ShowcasePage-jCNABTeT.js} +1 -1
  41. package/web-app/dist/assets/{SystemSettingsPage-l3Cv9YjY.js → SystemSettingsPage-Crmc-ryL.js} +1 -1
  42. package/web-app/dist/assets/{TeamsPage-DridGcnR.js → TeamsPage-DN8W-9m5.js} +1 -1
  43. package/web-app/dist/assets/{TemplatesPage-CTNsfp03.js → TemplatesPage-WvaXwZIl.js} +1 -1
  44. package/web-app/dist/assets/{TerminalOutput-BuyksqLh.js → TerminalOutput-Bf_do2tO.js} +1 -1
  45. package/web-app/dist/assets/{activity-DM2TKaBi.js → activity-CMl9JvHl.js} +1 -1
  46. package/web-app/dist/assets/{bell-D2PMLbbX.js → bell-CnjqTAUX.js} +1 -1
  47. package/web-app/dist/assets/{bot-BnN32Jxp.js → bot-BBprDFVS.js} +1 -1
  48. package/web-app/dist/assets/{check-CU128nw_.js → check-DW9Hq4y4.js} +1 -1
  49. package/web-app/dist/assets/{chevron-left-ehNW1Do7.js → chevron-left-DC9pMI2E.js} +1 -1
  50. package/web-app/dist/assets/{circle-alert-Cpw2gtOC.js → circle-alert-BJoll9YV.js} +1 -1
  51. package/web-app/dist/assets/{clock-BOZHdPVS.js → clock-BdHhRTHO.js} +1 -1
  52. package/web-app/dist/assets/{cloud-BZ2L9Fzi.js → cloud-C54B7EO9.js} +1 -1
  53. package/web-app/dist/assets/{code-xml-sSo522gu.js → code-xml-DwQK2vpV.js} +1 -1
  54. package/web-app/dist/assets/{database-DrR1xHzS.js → database-DsWVFir7.js} +1 -1
  55. package/web-app/dist/assets/{dollar-sign-CHE3T_rh.js → dollar-sign-DuRPNc2M.js} +1 -1
  56. package/web-app/dist/assets/{file-code-corner-CgcssyRQ.js → file-code-corner-C4j2P44-.js} +1 -1
  57. package/web-app/dist/assets/{file-plus-DtwfrxYH.js → file-plus-rnI2RZjF.js} +1 -1
  58. package/web-app/dist/assets/{globe-Dzla77iL.js → globe-BbBhsaS5.js} +1 -1
  59. package/web-app/dist/assets/{hammer-BAMdG66t.js → hammer-BxcycH3E.js} +1 -1
  60. package/web-app/dist/assets/{index-mQOnSYoW.js → index-xGj3noKA.js} +2 -2
  61. package/web-app/dist/assets/{layers-DhuyLqmc.js → layers-CcAOhZeY.js} +1 -1
  62. package/web-app/dist/assets/{loader-circle-DclFyQ3B.js → loader-circle-DN_Xjd5e.js} +1 -1
  63. package/web-app/dist/assets/{lock-C8v_bXNl.js → lock-Dn8AMO-D.js} +1 -1
  64. package/web-app/dist/assets/{package-B4AnjM5D.js → package-uroTpCjn.js} +1 -1
  65. package/web-app/dist/assets/{plus-DM95CmZI.js → plus-CYJAEX02.js} +1 -1
  66. package/web-app/dist/assets/{refresh-cw-CWaauzqX.js → refresh-cw-BUGsMChj.js} +1 -1
  67. package/web-app/dist/assets/{rotate-ccw-DxcvylpJ.js → rotate-ccw-C_4D-XGl.js} +1 -1
  68. package/web-app/dist/assets/{scroll-text-Dx-V_BpP.js → scroll-text-DrAJG0H3.js} +1 -1
  69. package/web-app/dist/assets/{server-Cj2EfoGN.js → server-Di6lHsM9.js} +1 -1
  70. package/web-app/dist/assets/{shield-alert-OlmWv7sU.js → shield-alert-DH1bNjkD.js} +1 -1
  71. package/web-app/dist/assets/{thumbs-up-CIpJhyTd.js → thumbs-up-Enk3ppj_.js} +1 -1
  72. package/web-app/dist/assets/{trash-2-BIkoZkY6.js → trash-2-CezqQT3x.js} +1 -1
  73. package/web-app/dist/assets/{trending-down-CI-OcCGt.js → trending-down-B_ChI0U7.js} +1 -1
  74. package/web-app/dist/assets/{trending-up-B0dpgKi-.js → trending-up-BtsvHd_-.js} +1 -1
  75. package/web-app/dist/assets/{upload-NhOBAjKA.js → upload-XiWa_WBM.js} +1 -1
  76. package/web-app/dist/assets/{usePolling-_QRF6f7x.js → usePolling-B6Hxwbk1.js} +1 -1
  77. package/web-app/dist/assets/{user-kRNvOpun.js → user-oL_Oya-S.js} +1 -1
  78. package/web-app/dist/index.html +1 -1
  79. package/web-app/dist/assets/CockpitPage-Bj41Xtg9.js +0 -47
package/autonomy/loki CHANGED
@@ -1271,6 +1271,8 @@ loki_remote_verify_receipt() {
1271
1271
  echo " the JWKS URL is not https -- only loopback is allowed over plain http)."
1272
1272
  echo " Its integrity hash matches. Provenance is unproven here, not disproven."
1273
1273
  echo " Install python3 cryptography, or re-run where the build server is reachable."
1274
+ echo " The verifier runs python3 -E, so cryptography supplied through PYTHONPATH is"
1275
+ echo " not seen; install it where the base interpreter finds it (site or user site)."
1274
1276
  return 0
1275
1277
  fi
1276
1278
 
@@ -37143,6 +37145,7 @@ print(json.dumps(json.load(open(os.environ['LOKI_PROOF_JSON'])), indent=2))
37143
37145
  # verifier is a finding and is never softened to 2.
37144
37146
  echo -e "${YELLOW}attestation: NOT CHECKED -- could not check against ${_pv_jwks} (key set unreadable,${NC}" >&2
37145
37147
  echo -e "${YELLOW} verifier dependency missing, or the JWKS URL is not https -- only loopback is allowed over plain http).${NC}" >&2
37148
+ echo " The verifier runs python3 -E, so cryptography supplied through PYTHONPATH is not seen (a site or user-site install is)." >&2
37146
37149
  echo " This is not a failure of the receipt. Nothing was proven either way (exit 2)." >&2
37147
37150
  if [ "$_pv_rc" -eq 0 ]; then _pv_rc=2; fi
37148
37151
  _pv_attestation_field="NOT CHECKED"
@@ -19,12 +19,23 @@
19
19
  # LOKI_CHECKLIST_ENABLED - Enable/disable checklist (default: true)
20
20
  #
21
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
22
+ # so every python3 here ignores PYTHON* env vars (an empty PYTHONPATH component
23
+ # would load a committed sitecustomize.py) and every -c / - body drops '' and
24
+ # '.' from sys.path before any other import (a committed json.py would print
25
+ # the verdict). Pinned by tests/moat/p2-honest-verdict.sh
26
26
  # P2.checklist-verify-not-shadowed.
27
27
  #
28
+ # S-204 (BACKLOG 53): -E alone still loads the user site-packages, whose .pth
29
+ # "import" lines run at startup and can patch json.dump, so checklist-verify.py
30
+ # wrote failing checks as verified into the results file the council hard gate
31
+ # trusts. Every call site resolves its interpreter through
32
+ # _loki_snapshot_py_tool and runs it -I -S; when none resolves, nothing is
33
+ # verified or summarized (the hard gate then blocks fail-closed). See that
34
+ # function's comment in run.sh for the rationale and accepted gaps. The copy
35
+ # below only serves callers that source this file before or without run.sh
36
+ # (tests); run.sh is the source of truth, keep it byte-identical. Pinned by
37
+ # tests/test-prd-checklist-no-user-site-pth.sh.
38
+ #
28
39
  # Data:
29
40
  # .loki/checklist/checklist.json - Full checklist with verification
30
41
  # .loki/checklist/verification-results.json - Summary of last verification
@@ -37,6 +48,28 @@
37
48
  #
38
49
  #===============================================================================
39
50
 
51
+ declare -F _loki_snapshot_py_tool >/dev/null 2>&1 || \
52
+ _loki_snapshot_py_tool() {
53
+ local c
54
+ for c in /usr/bin/python3 /bin/python3; do
55
+ [ -x "$c" ] && [ ! -d "$c" ] && "$c" -I -S -c '' >/dev/null 2>&1 && { printf '%s\n' "$c"; return 0; }
56
+ done
57
+ local dir
58
+ local IFS=:
59
+ for dir in $PATH; do
60
+ case "$dir" in
61
+ /*) ;;
62
+ *) continue ;;
63
+ esac
64
+ if [ -x "$dir/python3" ] && [ ! -d "$dir/python3" ] \
65
+ && "$dir/python3" -I -S -c '' >/dev/null 2>&1; then
66
+ printf '%s\n' "$dir/python3"
67
+ return 0
68
+ fi
69
+ done
70
+ return 1
71
+ }
72
+
40
73
  # Configuration
41
74
  CHECKLIST_ENABLED=${LOKI_CHECKLIST_ENABLED:-true}
42
75
  CHECKLIST_INTERVAL=${LOKI_CHECKLIST_INTERVAL:-5}
@@ -200,12 +233,14 @@ checklist_oracle_triangulate() {
200
233
  local status_token="NOOP"
201
234
  local status_file=""
202
235
  local status_tmp_root="${TMPDIR:-/tmp}"
236
+ local _cl_py
237
+ _cl_py="$(_loki_snapshot_py_tool)" || return 0
203
238
  if status_file="$(mktemp "${status_tmp_root%/}/loki-oracle-status.XXXXXX" 2>/dev/null)"; then
204
239
  if _ORACLE_SPEC="$CHECKLIST_PRD_PATH" \
205
240
  _ORACLE_OUT="$findings_file" \
206
241
  _ORACLE_PROJECT="$project_dir" \
207
242
  _ORACLE_INSTALL_DIR="$oracle_install_dir" \
208
- python3 -E - > "$status_file" 2>/dev/null <<'ORACLE_PY'
243
+ "$_cl_py" -I -S - > "$status_file" 2>/dev/null <<'ORACLE_PY'
209
244
  import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
210
245
  import json, os, re, sys, tempfile, glob
211
246
 
@@ -701,7 +736,9 @@ checklist_oracle_evidence() {
701
736
  if [ ! -f "$findings_file" ]; then
702
737
  return 0
703
738
  fi
704
- _ORACLE_OUT="$findings_file" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
739
+ local _cl_py
740
+ _cl_py="$(_loki_snapshot_py_tool)" || return 0
741
+ _ORACLE_OUT="$findings_file" "$_cl_py" -I -S -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
705
742
  import json, os
706
743
  try:
707
744
  with open(os.environ["_ORACLE_OUT"]) as f:
@@ -804,8 +841,9 @@ checklist_select_heldout() {
804
841
  # Honest caveat: re-selection or partial-survival after a regen can reserve
805
842
  # items the build loop already saw in earlier prompts (the hidden-from-loop
806
843
  # guarantee is best-effort once the checklist ids change mid-run).
807
- local status_token
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 ('', '.')]
844
+ local status_token _cl_py
845
+ _cl_py="$(_loki_snapshot_py_tool)" || return 0
846
+ status_token=$(_CHECKLIST_FILE="$CHECKLIST_FILE" _HELDOUT_FILE="$heldout_file" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
809
847
  import json, os, sys, hashlib, tempfile
810
848
 
811
849
  cl_path = os.environ['_CHECKLIST_FILE']
@@ -953,7 +991,9 @@ checklist_heldout_ids() {
953
991
  if [ ! -f "$heldout_file" ]; then
954
992
  return 0
955
993
  fi
956
- _HELDOUT_FILE="$heldout_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
994
+ local _cl_py
995
+ _cl_py="$(_loki_snapshot_py_tool)" || return 0
996
+ _HELDOUT_FILE="$heldout_file" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
957
997
  import json, os
958
998
  try:
959
999
  with open(os.environ['_HELDOUT_FILE']) as f:
@@ -997,9 +1037,17 @@ checklist_verify() {
997
1037
  return 0
998
1038
  fi
999
1039
 
1040
+ # Non-zero so council_reverify_checklist replaces any stale green results
1041
+ # with its fail-closed sentinel (run.sh's interval call ignores the status).
1042
+ local _cl_py
1043
+ if ! _cl_py="$(_loki_snapshot_py_tool)"; then
1044
+ log_warn "checklist verification skipped: no isolated python3 interpreter resolved"
1045
+ return 1
1046
+ fi
1047
+
1000
1048
  log_step "Running PRD checklist verification..."
1001
1049
 
1002
- python3 -E "$verify_script" \
1050
+ "$_cl_py" -I -S "$verify_script" \
1003
1051
  --checklist "$CHECKLIST_FILE" \
1004
1052
  --timeout "$CHECKLIST_TIMEOUT" 2>/dev/null || true
1005
1053
 
@@ -1027,11 +1075,13 @@ checklist_summary() {
1027
1075
  echo ""
1028
1076
  return 0
1029
1077
  fi
1078
+ local _cl_py
1079
+ _cl_py="$(_loki_snapshot_py_tool)" || { echo ""; return 0; }
1030
1080
 
1031
1081
  _CHECKLIST_RESULTS="$CHECKLIST_RESULTS_FILE" \
1032
1082
  _CHECKLIST_WAIVERS="${CHECKLIST_DIR:-".loki/checklist"}/waivers.json" \
1033
1083
  _CHECKLIST_HELDOUT="${CHECKLIST_DIR:-".loki/checklist"}/held-out.json" \
1034
- python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1084
+ "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1035
1085
  import json, sys, os
1036
1086
  try:
1037
1087
  fpath = os.environ.get('_CHECKLIST_RESULTS', '')
@@ -1128,13 +1178,16 @@ checklist_as_evidence() {
1128
1178
  if [ ! -f "$CHECKLIST_RESULTS_FILE" ]; then
1129
1179
  return 0
1130
1180
  fi
1181
+ local _cl_py
1182
+ # No interpreter: the failed exec falls to "Checklist data unavailable" below.
1183
+ _cl_py="$(_loki_snapshot_py_tool)" || _cl_py="/nonexistent/python3"
1131
1184
 
1132
1185
  {
1133
1186
  echo ""
1134
1187
  echo "## PRD Checklist Verification"
1135
1188
  echo ""
1136
1189
 
1137
- _CHECKLIST_RESULTS="$CHECKLIST_RESULTS_FILE" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1190
+ _CHECKLIST_RESULTS="$CHECKLIST_RESULTS_FILE" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1138
1191
  import json, os
1139
1192
  try:
1140
1193
  data = json.load(open(os.environ['_CHECKLIST_RESULTS']))
@@ -1170,7 +1223,9 @@ checklist_waiver_load() {
1170
1223
  if [ ! -f "$waivers_file" ]; then
1171
1224
  return 0
1172
1225
  fi
1173
- _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1226
+ local _cl_py
1227
+ _cl_py="$(_loki_snapshot_py_tool)" || return 0
1228
+ _WAIVERS_FILE="$waivers_file" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1174
1229
  import json, sys, os
1175
1230
  try:
1176
1231
  waivers_file = os.environ['_WAIVERS_FILE']
@@ -1193,8 +1248,10 @@ checklist_waiver_add() {
1193
1248
  local waivers_file="${CHECKLIST_DIR:-".loki/checklist"}/waivers.json"
1194
1249
  local timestamp
1195
1250
  timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1251
+ local _cl_py
1252
+ _cl_py="$(_loki_snapshot_py_tool)" || return 1
1196
1253
 
1197
- _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1254
+ _WAIVERS_FILE="$waivers_file" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1198
1255
  import json, os, sys
1199
1256
 
1200
1257
  waivers_file = os.environ['_WAIVERS_FILE']
@@ -1246,8 +1303,10 @@ checklist_waiver_remove() {
1246
1303
  echo "No waivers file found"
1247
1304
  return 1
1248
1305
  fi
1306
+ local _cl_py
1307
+ _cl_py="$(_loki_snapshot_py_tool)" || return 1
1249
1308
 
1250
- _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1309
+ _WAIVERS_FILE="$waivers_file" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1251
1310
  import json, os, sys
1252
1311
 
1253
1312
  waivers_file = os.environ['_WAIVERS_FILE']
@@ -1282,8 +1341,10 @@ checklist_waiver_list() {
1282
1341
  echo "No waivers configured"
1283
1342
  return 0
1284
1343
  fi
1344
+ local _cl_py
1345
+ _cl_py="$(_loki_snapshot_py_tool)" || return 1
1285
1346
 
1286
- _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1347
+ _WAIVERS_FILE="$waivers_file" "$_cl_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
1287
1348
  import json, os
1288
1349
  waivers_file = os.environ['_WAIVERS_FILE']
1289
1350
  with open(waivers_file) as f:
package/autonomy/run.sh CHANGED
@@ -10949,9 +10949,26 @@ _loki_untrack_agent_committed_user_files() {
10949
10949
  fi
10950
10950
  rm -f "$rec.added"
10951
10951
  if [ ! -s "$rec.new" ]; then
10952
- # BACKLOG 102: zero hits this session -- clear a stale record left by
10953
- # an earlier session, or it persists forever with nothing to read it.
10954
- rm -f "$rec.new" "$rec"
10952
+ rm -f "$rec.new"
10953
+ # S-194: the tree diff above cannot see a file an earlier (resumed)
10954
+ # session added and then untracked, but the branch history still
10955
+ # holds it and create_session_pr gates the push on this record. Keep
10956
+ # the record while that history holds any recorded path; on a failed
10957
+ # check, keep it too (fail closed).
10958
+ if [ -s "$rec" ]; then
10959
+ local range="${fork}..HEAD" held_paths=() p="" hist=""
10960
+ [ "$(git cat-file -t "$fork" 2>/dev/null)" = tree ] && range="HEAD"
10961
+ while IFS= read -r -d '' p; do held_paths+=("$p"); done < "$rec"
10962
+ if [ "${#held_paths[@]}" -eq 0 ] \
10963
+ || ! hist="$(git -C "$top" --literal-pathspecs log -1 --format=%H --no-renames --diff-filter=A "$range" -- "${held_paths[@]}" 2>/dev/null)" \
10964
+ || [ -n "$hist" ]; then
10965
+ return 0
10966
+ fi
10967
+ fi
10968
+ # BACKLOG 102: zero hits this session and no recorded path left in the
10969
+ # branch history -- clear a stale record left by an earlier session,
10970
+ # or it persists forever with nothing to read it.
10971
+ rm -f "$rec"
10955
10972
  return 0
10956
10973
  fi
10957
10974
  mv -f "$rec.new" "$rec"
@@ -11198,6 +11215,24 @@ create_session_pr() {
11198
11215
  return 0
11199
11216
  fi
11200
11217
 
11218
+ # BACKLOG 108: _loki_untrack_agent_committed_user_files removed the user's
11219
+ # pre-existing files from the branch tip, but the commit that added them is
11220
+ # still in the branch history, so a push publishes them. Print the cleanup
11221
+ # before any push advice, and refuse the LOKI_AUTO_PR push. The fork is the
11222
+ # merge base with the recorded base, as that helper computes it; its
11223
+ # session-start-sha fallback cannot apply here, because without a merge
11224
+ # base commit_count is 0 and this function already returned.
11225
+ if [ -s .loki/state/agent-committed-user-files.z ]; then
11226
+ local _uc_fork="" _uc_names=""
11227
+ _uc_fork="$(git merge-base HEAD "$base" 2>/dev/null)" || _uc_fork=""
11228
+ _uc_names="$(_loki_nul_names .loki/state/agent-committed-user-files.z)"
11229
+ log_warn "The history of ${branch_name} still holds your pre-existing files the agent committed: ${_uc_names:-see .loki/state/agent-committed-user-files.z}. Before pushing, drop them from its history: git reset --soft ${_uc_fork:-<fork commit>} && git commit"
11230
+ if [ "${LOKI_AUTO_PR:-0}" = "1" ]; then
11231
+ log_warn "LOKI_AUTO_PR: not pushing ${branch_name}; its history holds your pre-existing files."
11232
+ return 1
11233
+ fi
11234
+ fi
11235
+
11201
11236
  # DEFAULT: advisory only. Print the exact commands; never push, never PR.
11202
11237
  if [ "${LOKI_AUTO_PR:-0}" != "1" ]; then
11203
11238
  if declare -f print_pr_advice >/dev/null 2>&1; then
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "9.74.0"
10
+ __version__ = "9.76.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v9.74.0
5
+ **Version:** v9.76.0
6
6
 
7
7
  ---
8
8
 
@@ -60,6 +60,7 @@ Read-only. Returns two honest series plus a budget block:
60
60
  "cost_usd": 1.84, "files_changed": 3, "final_verdict": "APPROVE"}
61
61
  ],
62
62
  "project_total_usd": 1.89,
63
+ "project_total_partial": false,
63
64
  "runs_count": 1,
64
65
  "budget": {
65
66
  "limit": 50.0, "used": 1.89, "remaining": 48.11,
@@ -74,7 +75,12 @@ Read-only. Returns two honest series plus a budget block:
74
75
  prefer `cost_usd`; if null, price from tokens via the EXISTING
75
76
  `_calculate_model_cost` helper (do not add a new pricer).
76
77
  - `runs` from `.loki/proofs/*/proof.json` (reuse `_proofs_dir` + `_safe_json_read`).
77
- - `project_total_usd` = sum of per-run proof costs (the persistent history).
78
+ - `project_total_usd` totals only the runs whose proof recorded a cost
79
+ (the persistent history). It is null when no run recorded a cost, since
80
+ runs that each read "not recorded" do not add up to a $0.00 project; a
81
+ measured $0.00 run still makes it 0.0.
82
+ - `project_total_partial` is true when only some runs recorded a cost. The
83
+ total is then a lower bound, not the project's full spend.
78
84
  - `budget.status`: "ok" (<80%), "warn" (>=80% and <100%), "exceeded" (>=100%).
79
85
  Computed at read time. No budget.json schema change (avoids the
80
86
  byte-identical-JSON parity trap with run.sh heredoc / budget.ts).
@@ -67,6 +67,27 @@ An early draft spec listed `1=BLOCKED, 2=CONCERNS`. That ordering was rejected:
67
67
  it is not used anywhere, it has no consumers, and it inverts the
68
68
  severity-rises-with-the-code rule that every other command follows.
69
69
 
70
+ ### Known gaps until v10.0.0
71
+
72
+ The table above is the target. On the current release, four inputs do not
73
+ return what it implies. Measured on this checkout:
74
+
75
+ | Input | Command | Exit today | Target |
76
+ |---|---|---|---|
77
+ | Empty diff (a git repo with no changes vs base) | `loki verify` | 1 (CONCERNS) | 3 |
78
+ | Not a git directory | `loki verify` | 1 (CONCERNS) | 2 |
79
+ | Unknown flag | `loki verify --no-such-flag` | 3 | 64 |
80
+ | `--fast` with nothing scanned (0 files) | `loki verify --fast <empty-dir> --no-cache` | 0 | nonzero |
81
+
82
+ `loki verify --fast` (`autonomy/lib/fast_verify.py`) also exits 0 for a
83
+ nonexistent root and silently ignores unknown flags. Until v10.0.0, do not
84
+ trust exit 0 from `--fast` alone: check stdout for `INCONCLUSIVE`, which it
85
+ prints when nothing was scanned.
86
+
87
+ Both gaps are tracked as pending moat cases, milestone v10.0.0, in
88
+ `tests/moat/pending.txt`: P2.verify-exit-contract (the first three rows)
89
+ and P2.fast-verify-inconclusive-not-zero (the `--fast` row).
90
+
70
91
  ## `loki proof verify <id>`
71
92
 
72
93
  Re-checks one Evidence Receipt against the repo (tamper and drift). The same