loki-mode 7.89.1 → 7.90.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/README.md +15 -0
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/proof-check.sh +234 -0
- package/autonomy/lib/proof-pr.sh +251 -0
- package/autonomy/loki +62 -1
- package/autonomy/run.sh +209 -5
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +30 -0
- package/dashboard/static/proofs.html +32 -3
- package/docs/INSTALLATION.md +2 -2
- 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/README.md
CHANGED
|
@@ -119,6 +119,21 @@ redacted before it leaves your machine). An optional, off-by-default GPG detache
|
|
|
119
119
|
signature (`LOKI_PROOF_GPG_KEY`) lets a third party confirm the receipt came from
|
|
120
120
|
you.
|
|
121
121
|
|
|
122
|
+
### Proven PR
|
|
123
|
+
|
|
124
|
+
When Loki opens a pull request, the PR body includes the Evidence Receipt
|
|
125
|
+
summary, so a reviewer does not have to take the agent on faith. It shows the
|
|
126
|
+
honest verdict (VERIFIED / VERIFIED WITH GAPS / NOT VERIFIED), the key facts
|
|
127
|
+
(diff hash, tests, secure-gate, cost), and a "verify this yourself" line:
|
|
128
|
+
`loki proof verify <id>` against the recorded base SHA. A green claim appears
|
|
129
|
+
only when the receipt's own headline is VERIFIED. This is on by default whenever
|
|
130
|
+
Loki opens or advises a PR; opt out with `LOKI_PROVEN_PR=0`.
|
|
131
|
+
|
|
132
|
+
An optional advisory status check (`loki: verified-completion`) maps the verdict
|
|
133
|
+
to a GitHub check-run. It is opt-in (`LOKI_PROVEN_PR_CHECK=1`) and can never block
|
|
134
|
+
a merge on its own. To make verified-completion blocking, add it as a required
|
|
135
|
+
status check in your repository's branch-protection settings.
|
|
136
|
+
|
|
122
137
|
---
|
|
123
138
|
|
|
124
139
|
## Get Started in 30 Seconds
|
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 v7.
|
|
6
|
+
# Loki Mode v7.90.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
408
408
|
|
|
409
409
|
---
|
|
410
410
|
|
|
411
|
-
**v7.
|
|
411
|
+
**v7.90.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.90.0
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# proof-check.sh -- best-effort, advisory GitHub check-run for a Loki run.
|
|
3
|
+
#
|
|
4
|
+
# Posts an ADVISORY check-run named "loki: verified-completion" to a PR's head
|
|
5
|
+
# commit, mapping the deterministic honesty headline from the redacted proof.json
|
|
6
|
+
# 1:1 to a check-run conclusion. This surface is purely advisory.
|
|
7
|
+
#
|
|
8
|
+
# HONEST FRAMING (load-bearing invariant): Loki CANNOT block a merge. This posts
|
|
9
|
+
# an advisory check-run only. It NEVER sets any merge gate, NEVER marks the check
|
|
10
|
+
# as required, NEVER calls any merge-gate API. Requiring this check is the repo
|
|
11
|
+
# owner's setting (add "loki: verified-completion" as a required status check in
|
|
12
|
+
# repository settings). We do not overclaim.
|
|
13
|
+
#
|
|
14
|
+
# HONESTY (single source of truth): the ONLY input that can produce a green
|
|
15
|
+
# (success) conclusion is honesty.headline == "VERIFIED" read from the redacted
|
|
16
|
+
# proof.json. We never recompute a verdict, never read raw .loki state, never
|
|
17
|
+
# infer a conclusion from anything but the headline. A missing/unknown headline
|
|
18
|
+
# posts NOTHING (no fabricated green or red).
|
|
19
|
+
#
|
|
20
|
+
# set -e SAFE: this lib may be sourced under `set -uo pipefail` or
|
|
21
|
+
# `set -euo pipefail`. Every fallible command ends with `|| true` or sits in a
|
|
22
|
+
# guarded `if`; every optional tool is `command -v`-guarded; every var is
|
|
23
|
+
# defaulted with `${VAR:-}`; all paths `return 0` so a sourced call cannot abort
|
|
24
|
+
# the caller. This is pure best-effort: it NEVER fails the caller and NEVER
|
|
25
|
+
# blocks PR creation.
|
|
26
|
+
#
|
|
27
|
+
# This function is only ever called when the operator opted in (the call site
|
|
28
|
+
# guards on LOKI_PROVEN_PR_CHECK=1). It is also safe if called directly.
|
|
29
|
+
|
|
30
|
+
# Double-source guard.
|
|
31
|
+
[ -n "${_PROOF_CHECK_SH:-}" ] && return 0
|
|
32
|
+
_PROOF_CHECK_SH=1
|
|
33
|
+
|
|
34
|
+
# _proof_check_net <cmd...>
|
|
35
|
+
# Best-effort timeout wrapper so a hung network call cannot stall the caller.
|
|
36
|
+
# Mirrors the run.sh _loki_net idiom. Never fatal.
|
|
37
|
+
_proof_check_net() {
|
|
38
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
39
|
+
timeout 30 "$@"
|
|
40
|
+
else
|
|
41
|
+
"$@"
|
|
42
|
+
fi
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# _proof_check_headline <proof_json_path>
|
|
46
|
+
# Echoes the exact honesty.headline string from the redacted proof.json, or an
|
|
47
|
+
# empty string if the file is missing/unreadable/not-a-dict or the headline is
|
|
48
|
+
# absent. Reads ONLY the passed proof.json, NEVER raw .loki state. Best-effort.
|
|
49
|
+
_proof_check_headline() {
|
|
50
|
+
local proof_path="${1:-}"
|
|
51
|
+
[ -n "$proof_path" ] || { printf '%s' ""; return 0; }
|
|
52
|
+
[ -f "$proof_path" ] || { printf '%s' ""; return 0; }
|
|
53
|
+
command -v python3 >/dev/null 2>&1 || { printf '%s' ""; return 0; }
|
|
54
|
+
|
|
55
|
+
local headline=""
|
|
56
|
+
headline="$(python3 - "$proof_path" <<'PY' 2>/dev/null || true
|
|
57
|
+
import json, sys
|
|
58
|
+
try:
|
|
59
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
60
|
+
data = json.load(fh)
|
|
61
|
+
if not isinstance(data, dict):
|
|
62
|
+
sys.exit(0)
|
|
63
|
+
honesty = data.get("honesty")
|
|
64
|
+
if not isinstance(honesty, dict):
|
|
65
|
+
sys.exit(0)
|
|
66
|
+
h = honesty.get("headline")
|
|
67
|
+
if isinstance(h, str):
|
|
68
|
+
sys.stdout.write(h)
|
|
69
|
+
except Exception:
|
|
70
|
+
sys.exit(0)
|
|
71
|
+
PY
|
|
72
|
+
)"
|
|
73
|
+
printf '%s' "${headline:-}"
|
|
74
|
+
return 0
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# _proof_check_proof_head_sha <proof_json_path>
|
|
78
|
+
# Echoes facts.git.head_sha from the redacted proof.json (the fallback head sha),
|
|
79
|
+
# or empty string. Reads ONLY the passed proof.json. Best-effort.
|
|
80
|
+
_proof_check_proof_head_sha() {
|
|
81
|
+
local proof_path="${1:-}"
|
|
82
|
+
[ -n "$proof_path" ] || { printf '%s' ""; return 0; }
|
|
83
|
+
[ -f "$proof_path" ] || { printf '%s' ""; return 0; }
|
|
84
|
+
command -v python3 >/dev/null 2>&1 || { printf '%s' ""; return 0; }
|
|
85
|
+
|
|
86
|
+
local sha=""
|
|
87
|
+
sha="$(python3 - "$proof_path" <<'PY' 2>/dev/null || true
|
|
88
|
+
import json, sys
|
|
89
|
+
try:
|
|
90
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
91
|
+
data = json.load(fh)
|
|
92
|
+
if not isinstance(data, dict):
|
|
93
|
+
sys.exit(0)
|
|
94
|
+
facts = data.get("facts")
|
|
95
|
+
if not isinstance(facts, dict):
|
|
96
|
+
sys.exit(0)
|
|
97
|
+
git = facts.get("git")
|
|
98
|
+
if not isinstance(git, dict):
|
|
99
|
+
sys.exit(0)
|
|
100
|
+
s = git.get("head_sha")
|
|
101
|
+
if isinstance(s, str):
|
|
102
|
+
sys.stdout.write(s.strip())
|
|
103
|
+
except Exception:
|
|
104
|
+
sys.exit(0)
|
|
105
|
+
PY
|
|
106
|
+
)"
|
|
107
|
+
printf '%s' "${sha:-}"
|
|
108
|
+
return 0
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# _proof_check_run_id <proof_json_path>
|
|
112
|
+
# Echoes run_id from the redacted proof.json (for the verify-yourself hint in the
|
|
113
|
+
# advisory summary), or empty string. Best-effort.
|
|
114
|
+
_proof_check_run_id() {
|
|
115
|
+
local proof_path="${1:-}"
|
|
116
|
+
[ -n "$proof_path" ] || { printf '%s' ""; return 0; }
|
|
117
|
+
[ -f "$proof_path" ] || { printf '%s' ""; return 0; }
|
|
118
|
+
command -v python3 >/dev/null 2>&1 || { printf '%s' ""; return 0; }
|
|
119
|
+
|
|
120
|
+
local rid=""
|
|
121
|
+
rid="$(python3 - "$proof_path" <<'PY' 2>/dev/null || true
|
|
122
|
+
import json, sys
|
|
123
|
+
try:
|
|
124
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
125
|
+
data = json.load(fh)
|
|
126
|
+
if not isinstance(data, dict):
|
|
127
|
+
sys.exit(0)
|
|
128
|
+
r = data.get("run_id")
|
|
129
|
+
if isinstance(r, str):
|
|
130
|
+
sys.stdout.write(r)
|
|
131
|
+
except Exception:
|
|
132
|
+
sys.exit(0)
|
|
133
|
+
PY
|
|
134
|
+
)"
|
|
135
|
+
printf '%s' "${rid:-}"
|
|
136
|
+
return 0
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# post_verified_completion_check <proof_json_path> <pr_url_or_empty>
|
|
140
|
+
#
|
|
141
|
+
# Best-effort: post an advisory GitHub check-run "loki: verified-completion" to
|
|
142
|
+
# the PR's head commit. Maps honesty.headline 1:1 to the check-run conclusion:
|
|
143
|
+
# VERIFIED -> success
|
|
144
|
+
# VERIFIED WITH GAPS -> neutral
|
|
145
|
+
# NOT VERIFIED -> failure
|
|
146
|
+
# A missing/unknown headline posts NOTHING. ALWAYS returns 0; NEVER fails the
|
|
147
|
+
# caller; NEVER blocks PR creation. This is advisory only: it NEVER makes the
|
|
148
|
+
# check required and NEVER calls any merge-gate API. Requiring the check is the
|
|
149
|
+
# repo owner's setting.
|
|
150
|
+
post_verified_completion_check() {
|
|
151
|
+
local proof_path="${1:-}"
|
|
152
|
+
local pr_url="${2:-}"
|
|
153
|
+
|
|
154
|
+
# --- Honesty gate FIRST: read + map the headline before touching gh. -------
|
|
155
|
+
# A missing/unknown headline must produce NO gh call at all (no fabricated
|
|
156
|
+
# green or red).
|
|
157
|
+
local headline=""
|
|
158
|
+
headline="$(_proof_check_headline "$proof_path")"
|
|
159
|
+
|
|
160
|
+
local conclusion=""
|
|
161
|
+
case "$headline" in
|
|
162
|
+
"VERIFIED") conclusion="success" ;;
|
|
163
|
+
"VERIFIED WITH GAPS") conclusion="neutral" ;;
|
|
164
|
+
"NOT VERIFIED") conclusion="failure" ;;
|
|
165
|
+
*)
|
|
166
|
+
# Missing or unknown headline -> do not post anything.
|
|
167
|
+
return 0
|
|
168
|
+
;;
|
|
169
|
+
esac
|
|
170
|
+
|
|
171
|
+
# --- gh availability + auth (best-effort, never fatal). --------------------
|
|
172
|
+
if ! command -v gh >/dev/null 2>&1; then
|
|
173
|
+
printf '%s\n' "loki: advisory check-run not posted (gh CLI not found)." || true
|
|
174
|
+
return 0
|
|
175
|
+
fi
|
|
176
|
+
if ! _proof_check_net gh auth status >/dev/null 2>&1; then
|
|
177
|
+
printf '%s\n' "loki: advisory check-run not posted (gh not authenticated)." || true
|
|
178
|
+
return 0
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
# --- Resolve owner/repo (nameWithOwner). ----------------------------------
|
|
182
|
+
# Prefer the current repo context (Loki's model is same-repo branch PRs).
|
|
183
|
+
local repo=""
|
|
184
|
+
repo="$(_proof_check_net gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || true)"
|
|
185
|
+
|
|
186
|
+
# --- Resolve head sha: PR head if a pr_url is given, else proof fallback. --
|
|
187
|
+
local head_sha=""
|
|
188
|
+
if [ -n "$pr_url" ]; then
|
|
189
|
+
head_sha="$(_proof_check_net gh pr view "$pr_url" --json headRefOid -q .headRefOid 2>/dev/null || true)"
|
|
190
|
+
fi
|
|
191
|
+
if [ -z "$head_sha" ]; then
|
|
192
|
+
head_sha="$(_proof_check_proof_head_sha "$proof_path")"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# If we cannot identify both the repo and the head commit, do not guess.
|
|
196
|
+
if [ -z "$repo" ] || [ -z "$head_sha" ]; then
|
|
197
|
+
printf '%s\n' "loki: advisory check-run not posted (could not resolve repo or head commit)." || true
|
|
198
|
+
return 0
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
# --- Build the advisory summary text. -------------------------------------
|
|
202
|
+
# Plainly states this is advisory and how the OWNER can make it blocking.
|
|
203
|
+
local run_id=""
|
|
204
|
+
run_id="$(_proof_check_run_id "$proof_path")"
|
|
205
|
+
|
|
206
|
+
local verify_line="Verify it yourself: loki proof verify"
|
|
207
|
+
if [ -n "$run_id" ]; then
|
|
208
|
+
verify_line="Verify it yourself: loki proof verify ${run_id}"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
local summary=""
|
|
212
|
+
summary="This is an advisory check posted by Loki. It reports the deterministic verified-completion headline (${headline}) for this run. Loki does not enforce a merge gate and does not make this check required. To make this gate blocking, the repository owner must add \"loki: verified-completion\" as a required status check in repository settings. ${verify_line}"
|
|
213
|
+
|
|
214
|
+
# --- Post the advisory check-run (best-effort). ---------------------------
|
|
215
|
+
# gh api supports nested fields via key[subkey]=value, so conclusion appears
|
|
216
|
+
# as a direct flat parameter. We POST to the check-runs endpoint only; we
|
|
217
|
+
# NEVER touch any merge-gate / required-status endpoint.
|
|
218
|
+
if _proof_check_net gh api \
|
|
219
|
+
-X POST \
|
|
220
|
+
"repos/${repo}/check-runs" \
|
|
221
|
+
-f "name=loki: verified-completion" \
|
|
222
|
+
-f "head_sha=${head_sha}" \
|
|
223
|
+
-f "status=completed" \
|
|
224
|
+
-f "conclusion=${conclusion}" \
|
|
225
|
+
-f "output[title]=loki: verified-completion" \
|
|
226
|
+
-f "output[summary]=${summary}" \
|
|
227
|
+
>/dev/null 2>&1; then
|
|
228
|
+
printf '%s\n' "loki: posted advisory check-run \"loki: verified-completion\" (${conclusion})." || true
|
|
229
|
+
else
|
|
230
|
+
printf '%s\n' "loki: advisory check-run not posted (gh API error or insufficient check permission)." || true
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
return 0
|
|
234
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# proof-pr.sh -- shared, PRINT-ONLY Evidence Receipt renderer for PR bodies.
|
|
3
|
+
#
|
|
4
|
+
# LOAD-BEARING INVARIANT: this lib is pure and print-only. It NEVER runs
|
|
5
|
+
# `git push`, NEVER runs `gh pr create`, NEVER mutates the repo, NEVER posts a
|
|
6
|
+
# check-run. It reads ONLY the already-redacted proof.json passed to it (past the
|
|
7
|
+
# redaction chokepoint at proof-generator.py:1086) and prints a markdown block
|
|
8
|
+
# that gets appended into a PR body. It is the single source of truth sourced by
|
|
9
|
+
# autonomy/run.sh (the three create_session_pr / body-file / delegate PR sites)
|
|
10
|
+
# and autonomy/loki (cmd_github) so every PR surface renders a byte-identical,
|
|
11
|
+
# correct receipt and cannot drift. Mirrors the contract of git-pr-advisory.sh.
|
|
12
|
+
#
|
|
13
|
+
# HONESTY GATE (R-HON-1): the ONLY input that may produce a green/VERIFIED claim
|
|
14
|
+
# is honesty.headline == "VERIFIED" read from the redacted proof.json. The
|
|
15
|
+
# renderer NEVER recomputes a verdict, NEVER reads council/LLM opinion to turn
|
|
16
|
+
# green, NEVER infers VERIFIED from a bare pass.
|
|
17
|
+
#
|
|
18
|
+
# DETERMINISM (R-DET-2): when an expected_head_sha is supplied AND the proof is
|
|
19
|
+
# VERIFIED, the renderer cross-checks it against facts.git.head_sha; on mismatch
|
|
20
|
+
# it does NOT render green, it prints an honest "does not match this branch head"
|
|
21
|
+
# line. Production callers pass an empty expected_head_sha (the session commit at
|
|
22
|
+
# run.sh sits BETWEEN proof generation and PR creation, so the post-commit branch
|
|
23
|
+
# head is structurally offset from the proof's pre-commit head; feeding it would
|
|
24
|
+
# false-degrade every legitimate receipt). The anti-stale guarantee on the
|
|
25
|
+
# production path is R-DET-1: the persisted run_id pointer (.loki/state/
|
|
26
|
+
# last-proof-id.txt), not a head comparison. The R-DET-2 capability stays intact
|
|
27
|
+
# and is exercised by the SDET fixtures.
|
|
28
|
+
#
|
|
29
|
+
# set -e SAFE: this lib may be sourced under `set -uo pipefail` (run.sh) AND
|
|
30
|
+
# `set -euo pipefail` (loki). Every fallible command ends with `|| true` or sits
|
|
31
|
+
# in a guarded `if`; no bare `((..))`; every var defaulted with `${VAR:-}`;
|
|
32
|
+
# every optional tool is `command -v`-guarded. All print paths `return 0` so a
|
|
33
|
+
# sourced call cannot abort the caller under set -e.
|
|
34
|
+
|
|
35
|
+
# Double-source guard.
|
|
36
|
+
[ -n "${_PROOF_PR_SH:-}" ] && return 0
|
|
37
|
+
_PROOF_PR_SH=1
|
|
38
|
+
|
|
39
|
+
# render_evidence_receipt_md <proof_json_path> [expected_head_sha] [expected_base_sha]
|
|
40
|
+
# Prints the Evidence Receipt markdown block for a PR body. PRINT-ONLY: never
|
|
41
|
+
# pushes, never creates a PR, never mutates the repo. Always returns 0. A missing
|
|
42
|
+
# or unreadable proof prints ONE honest "unavailable" line and returns 0 so it
|
|
43
|
+
# can NEVER crash the caller or block PR creation.
|
|
44
|
+
#
|
|
45
|
+
# expected_base_sha is accepted for call-site symmetry but is intentionally
|
|
46
|
+
# unused for any green/red gate: the proof's base is a sha (_LOKI_ITER_START_SHA)
|
|
47
|
+
# while a PR base is a branch NAME, so a base comparison would misfire. The base
|
|
48
|
+
# is informational only and is printed from the proof itself.
|
|
49
|
+
render_evidence_receipt_md() {
|
|
50
|
+
local proof_json_path="${1:-}"
|
|
51
|
+
local expected_head_sha="${2:-}"
|
|
52
|
+
local _expected_base_sha="${3:-}"
|
|
53
|
+
: "$_expected_base_sha"
|
|
54
|
+
|
|
55
|
+
# No python3 -> degrade honestly, never crash.
|
|
56
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
57
|
+
printf '%s\n' "Evidence Receipt: unavailable for this run."
|
|
58
|
+
return 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Pass every input as argv (sys.argv), NEVER interpolated into the heredoc:
|
|
62
|
+
# quote-safe and injection-safe against a hostile proof path. The heredoc
|
|
63
|
+
# delimiter is quoted so bash performs no expansion inside the program.
|
|
64
|
+
# Capture into a var so a non-zero python exit degrades honestly without a
|
|
65
|
+
# brace group on the heredoc command (which bash mis-parses). The program
|
|
66
|
+
# always handles its own errors and prints, so a non-zero exit is a last
|
|
67
|
+
# resort (interpreter crash) -> print the single honest line.
|
|
68
|
+
local _receipt_out=""
|
|
69
|
+
local _receipt_rc=0
|
|
70
|
+
_receipt_out="$(python3 - "$proof_json_path" "$expected_head_sha" <<'PROOF_PR_PY' 2>/dev/null
|
|
71
|
+
import json
|
|
72
|
+
import sys
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _line(s=""):
|
|
76
|
+
sys.stdout.write(s + "\n")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def main():
|
|
80
|
+
argv = sys.argv[1:]
|
|
81
|
+
proof_path = argv[0] if len(argv) > 0 else ""
|
|
82
|
+
expected_head = (argv[1] if len(argv) > 1 else "").strip()
|
|
83
|
+
|
|
84
|
+
if not proof_path:
|
|
85
|
+
_line("Evidence Receipt: unavailable for this run.")
|
|
86
|
+
return 0
|
|
87
|
+
try:
|
|
88
|
+
with open(proof_path, "r") as f:
|
|
89
|
+
proof = json.load(f)
|
|
90
|
+
except Exception:
|
|
91
|
+
_line("Evidence Receipt: unavailable for this run.")
|
|
92
|
+
return 0
|
|
93
|
+
if not isinstance(proof, dict):
|
|
94
|
+
_line("Evidence Receipt: unavailable for this run.")
|
|
95
|
+
return 0
|
|
96
|
+
|
|
97
|
+
honesty = proof.get("honesty")
|
|
98
|
+
honesty = honesty if isinstance(honesty, dict) else {}
|
|
99
|
+
headline = str(honesty.get("headline") or "").strip()
|
|
100
|
+
if not headline:
|
|
101
|
+
_line("Evidence Receipt: unavailable for this run.")
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
facts = proof.get("facts")
|
|
105
|
+
facts = facts if isinstance(facts, dict) else {}
|
|
106
|
+
git = facts.get("git") if isinstance(facts.get("git"), dict) else {}
|
|
107
|
+
tests = facts.get("tests") if isinstance(facts.get("tests"), dict) else {}
|
|
108
|
+
build = facts.get("build") if isinstance(facts.get("build"), dict) else {}
|
|
109
|
+
security = facts.get("security") if isinstance(facts.get("security"), dict) else {}
|
|
110
|
+
cost = facts.get("cost") if isinstance(facts.get("cost"), dict) else {}
|
|
111
|
+
meta = facts.get("meta") if isinstance(facts.get("meta"), dict) else {}
|
|
112
|
+
|
|
113
|
+
diff = git.get("diff") if isinstance(git.get("diff"), dict) else {}
|
|
114
|
+
diff_count = diff.get("count")
|
|
115
|
+
diff_sha = str(git.get("diff_sha256") or "")
|
|
116
|
+
base_sha = str(git.get("base_sha") or "")
|
|
117
|
+
head_sha = str(git.get("head_sha") or "")
|
|
118
|
+
|
|
119
|
+
# run_id: prefer facts.meta.run_id, fall back to the top-level mirror.
|
|
120
|
+
run_id = str(meta.get("run_id") or proof.get("run_id") or "").strip()
|
|
121
|
+
|
|
122
|
+
# R-DET-2 cross-check. Only a supplied expected_head AND a VERIFIED headline
|
|
123
|
+
# can trigger it. On mismatch we do NOT print a VERIFIED banner; we print an
|
|
124
|
+
# honest line so a stale / wrong-run proof fails safe, never fake-green.
|
|
125
|
+
head_mismatch = (
|
|
126
|
+
bool(expected_head)
|
|
127
|
+
and headline == "VERIFIED"
|
|
128
|
+
and head_sha != ""
|
|
129
|
+
and head_sha != expected_head
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# ---- Render -----------------------------------------------------------
|
|
133
|
+
_line("### Evidence Receipt")
|
|
134
|
+
_line()
|
|
135
|
+
|
|
136
|
+
if head_mismatch:
|
|
137
|
+
_line(
|
|
138
|
+
"Evidence Receipt: available but does not match this branch head "
|
|
139
|
+
"(proof head " + (head_sha or "(none)")
|
|
140
|
+
+ ", branch head " + (expected_head or "(none)") + "). Run "
|
|
141
|
+
"`loki proof verify " + (run_id or "<run_id>") + "` to inspect."
|
|
142
|
+
)
|
|
143
|
+
_line()
|
|
144
|
+
# Fall through: still render facts + verify-yourself so the reviewer can
|
|
145
|
+
# check, but emit NO green headline label.
|
|
146
|
+
effective_headline = ""
|
|
147
|
+
else:
|
|
148
|
+
# Headline mapped 1:1 from honesty.headline. Plain text label, NO color
|
|
149
|
+
# codes -- this goes into a PR body. R-HON-1: only VERIFIED is green.
|
|
150
|
+
effective_headline = headline
|
|
151
|
+
_line("Headline: " + headline)
|
|
152
|
+
_line()
|
|
153
|
+
|
|
154
|
+
# Facts table. Deterministic, non-LLM facts a skeptic can recompute.
|
|
155
|
+
def _stat(d):
|
|
156
|
+
s = str(d.get("status") or "").strip()
|
|
157
|
+
return s if s else "not_run"
|
|
158
|
+
|
|
159
|
+
tests_cell = _stat(tests)
|
|
160
|
+
tests_cmd = str(tests.get("command") or "").strip()
|
|
161
|
+
if tests_cmd:
|
|
162
|
+
tests_cell = tests_cell + " (`" + tests_cmd + "`)"
|
|
163
|
+
build_cell = _stat(build)
|
|
164
|
+
build_cmd = str(build.get("command") or "").strip()
|
|
165
|
+
if build_cmd:
|
|
166
|
+
build_cell = build_cell + " (`" + build_cmd + "`)"
|
|
167
|
+
|
|
168
|
+
sec_cell = _stat(security)
|
|
169
|
+
if security.get("ran"):
|
|
170
|
+
ha = security.get("high_active") or 0
|
|
171
|
+
try:
|
|
172
|
+
ha = int(ha)
|
|
173
|
+
except Exception:
|
|
174
|
+
ha = 0
|
|
175
|
+
if ha > 0:
|
|
176
|
+
sec_cell = sec_cell + " (" + str(ha) + " un-waived HIGH)"
|
|
177
|
+
|
|
178
|
+
cost_usd = cost.get("usd")
|
|
179
|
+
cost_cell = "not recorded" if cost_usd is None else ("$" + str(cost_usd))
|
|
180
|
+
|
|
181
|
+
files_cell = "0" if diff_count is None else str(diff_count)
|
|
182
|
+
|
|
183
|
+
_line("| Fact | Value |")
|
|
184
|
+
_line("| --- | --- |")
|
|
185
|
+
_line("| Files changed | " + files_cell + " |")
|
|
186
|
+
_line("| Diff sha256 | `" + (diff_sha or "(none)") + "` |")
|
|
187
|
+
_line("| Tests | " + tests_cell + " |")
|
|
188
|
+
_line("| Build | " + build_cell + " |")
|
|
189
|
+
_line("| Security | " + sec_cell + " |")
|
|
190
|
+
_line("| Cost | " + cost_cell + " |")
|
|
191
|
+
_line("| Base sha | `" + (base_sha or "(none)") + "` |")
|
|
192
|
+
_line("| Head sha | `" + (head_sha or "(none)") + "` |")
|
|
193
|
+
_line()
|
|
194
|
+
|
|
195
|
+
# Gaps: when headline != VERIFIED, list honesty.degraded[] verbatim. By
|
|
196
|
+
# _compute_degraded design an empty list with a non-VERIFIED headline is
|
|
197
|
+
# impossible, but guard anyway (R-HON-2).
|
|
198
|
+
if effective_headline != "VERIFIED":
|
|
199
|
+
degraded = honesty.get("degraded")
|
|
200
|
+
degraded = degraded if isinstance(degraded, list) else []
|
|
201
|
+
if degraded:
|
|
202
|
+
_line("Not yet verified:")
|
|
203
|
+
for d in degraded:
|
|
204
|
+
if not isinstance(d, dict):
|
|
205
|
+
continue
|
|
206
|
+
item = str(d.get("item") or "").strip()
|
|
207
|
+
status = str(d.get("status") or "").strip()
|
|
208
|
+
reason = str(d.get("reason") or "").strip()
|
|
209
|
+
_line(
|
|
210
|
+
"- " + (item or "(item)")
|
|
211
|
+
+ ": " + (status or "(status)")
|
|
212
|
+
+ (" -- " + reason if reason else "")
|
|
213
|
+
)
|
|
214
|
+
_line()
|
|
215
|
+
|
|
216
|
+
# Verify-yourself block. ALWAYS rendered, even on NOT VERIFIED -- the whole
|
|
217
|
+
# point is that the reviewer can recompute the verdict and does not have to
|
|
218
|
+
# trust Loki.
|
|
219
|
+
_line("You do not have to trust this. Verify it yourself:")
|
|
220
|
+
_line()
|
|
221
|
+
_line("```")
|
|
222
|
+
_line(
|
|
223
|
+
"loki proof verify " + (run_id or "<run_id>")
|
|
224
|
+
+ " (base " + (base_sha or "(none)") + ")"
|
|
225
|
+
)
|
|
226
|
+
_line("```")
|
|
227
|
+
_line()
|
|
228
|
+
|
|
229
|
+
# What the headline means: a first-time reviewer must understand that
|
|
230
|
+
# VERIFIED WITH GAPS is honest, not a failure.
|
|
231
|
+
_line(
|
|
232
|
+
"What the headline means: VERIFIED means every recorded check passed; "
|
|
233
|
+
"VERIFIED WITH GAPS means the checks that ran passed but some checks "
|
|
234
|
+
"were not run (listed above); NOT VERIFIED means a check failed or "
|
|
235
|
+
"nothing could be verified. The headline is computed only from "
|
|
236
|
+
"deterministic, re-derivable facts, never from an AI opinion."
|
|
237
|
+
)
|
|
238
|
+
return 0
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
sys.exit(main())
|
|
242
|
+
PROOF_PR_PY
|
|
243
|
+
)" || _receipt_rc=$?
|
|
244
|
+
|
|
245
|
+
if [ "$_receipt_rc" != "0" ] || [ -z "$_receipt_out" ]; then
|
|
246
|
+
printf '%s\n' "Evidence Receipt: unavailable for this run."
|
|
247
|
+
return 0
|
|
248
|
+
fi
|
|
249
|
+
printf '%s\n' "$_receipt_out"
|
|
250
|
+
return 0
|
|
251
|
+
}
|
package/autonomy/loki
CHANGED
|
@@ -125,6 +125,14 @@ if [ -f "$_LOKI_SCRIPT_DIR/lib/git-pr-advisory.sh" ]; then
|
|
|
125
125
|
source "$_LOKI_SCRIPT_DIR/lib/git-pr-advisory.sh"
|
|
126
126
|
fi
|
|
127
127
|
|
|
128
|
+
# Proven PR (Loop 6): shared print-only Evidence Receipt renderer for PR bodies
|
|
129
|
+
# (render_evidence_receipt_md). Sourced so cmd_github can append the receipt to
|
|
130
|
+
# the PR body. Self-guarded against double-source; existence-guarded.
|
|
131
|
+
if [ -f "$_LOKI_SCRIPT_DIR/lib/proof-pr.sh" ]; then
|
|
132
|
+
# shellcheck source=autonomy/lib/proof-pr.sh
|
|
133
|
+
source "$_LOKI_SCRIPT_DIR/lib/proof-pr.sh"
|
|
134
|
+
fi
|
|
135
|
+
|
|
128
136
|
# Unified config-file support (#691). Canonical LOKI_CONFIG_MAP + the config-file
|
|
129
137
|
# loader (loki_maybe_apply_config_file / loki_apply_config_file) and the
|
|
130
138
|
# example/schema/validate generators. Side-effect-free on source: defines the
|
|
@@ -7574,7 +7582,37 @@ if [[ "$LOKI_CREATE_PR" == "true" ]]; then
|
|
|
7574
7582
|
branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
7575
7583
|
if [[ -n "$branch_current" && "$branch_current" != "main" && "$branch_current" != "master" ]]; then
|
|
7576
7584
|
git push origin "$branch_current" 2>/dev/null || true
|
|
7577
|
-
|
|
7585
|
+
# Proven PR (v7.90.0): render this run's Evidence Receipt into the PR body
|
|
7586
|
+
# so even the detached --pr/--ship path proves itself. The proof + the
|
|
7587
|
+
# run-id pointer (.loki/state/last-proof-id.txt) exist by now (the
|
|
7588
|
+
# `loki start` above generated them). On by default (LOKI_PROVEN_PR=0
|
|
7589
|
+
# opts out); degrades safely to the plain body if anything is missing.
|
|
7590
|
+
# The renderer is print-only + honesty-gated (green only when the proof
|
|
7591
|
+
# headline is VERIFIED); it can never make a receipt-less PR look proven.
|
|
7592
|
+
_pr_body="Implemented by Loki Mode"
|
|
7593
|
+
if [[ "${LOKI_PROVEN_PR:-1}" != "0" ]]; then
|
|
7594
|
+
# Resolve the renderer from the ALREADY-RESOLVED autonomy dir passed
|
|
7595
|
+
# in by the parent (LOKI_SCRIPT_DIR_RESOLVED = _LOKI_SCRIPT_DIR, which
|
|
7596
|
+
# is symlink-safe). Do NOT recompute from `command -v loki` -- on
|
|
7597
|
+
# npm/bun/brew global installs that is an UNRESOLVED bin symlink, so
|
|
7598
|
+
# dirname-based guessing misses the lib and the receipt silently no-ops
|
|
7599
|
+
# on the dominant install channels (council cIb_r2). Fall back to the
|
|
7600
|
+
# old dirname guesses only if the resolved dir was not passed.
|
|
7601
|
+
_pp_lib="${LOKI_SCRIPT_DIR_RESOLVED:-}/lib/proof-pr.sh"
|
|
7602
|
+
[[ -f "$_pp_lib" ]] || _pp_lib="$(dirname "$LOKI_CMD")/../autonomy/lib/proof-pr.sh"
|
|
7603
|
+
[[ -f "$_pp_lib" ]] || _pp_lib="$(dirname "$LOKI_CMD")/autonomy/lib/proof-pr.sh"
|
|
7604
|
+
_pp_id=""
|
|
7605
|
+
[[ -f .loki/state/last-proof-id.txt ]] && _pp_id="$(cat .loki/state/last-proof-id.txt 2>/dev/null)"
|
|
7606
|
+
_pp_proof=".loki/proofs/${_pp_id}/proof.json"
|
|
7607
|
+
if [[ -n "$_pp_id" && -f "$_pp_proof" && -f "$_pp_lib" ]]; then
|
|
7608
|
+
# shellcheck source=/dev/null
|
|
7609
|
+
if source "$_pp_lib" 2>/dev/null && declare -f render_evidence_receipt_md >/dev/null 2>&1; then
|
|
7610
|
+
_pp_block="$(render_evidence_receipt_md "$_pp_proof" "" "" 2>/dev/null || true)"
|
|
7611
|
+
[[ -n "$_pp_block" ]] && _pr_body="Implemented by Loki Mode"$'\n\n'"$_pp_block"
|
|
7612
|
+
fi
|
|
7613
|
+
fi
|
|
7614
|
+
fi
|
|
7615
|
+
gh pr create --title "$LOKI_PR_TITLE" --body "$_pr_body" --head "$branch_current" 2>/dev/null || true
|
|
7578
7616
|
fi
|
|
7579
7617
|
fi
|
|
7580
7618
|
# Post-completion: auto-merge if requested
|
|
@@ -7592,6 +7630,7 @@ INNER_SCRIPT_EOF
|
|
|
7592
7630
|
# Pass all variables safely via environment
|
|
7593
7631
|
LOKI_RUN_DIR="$(pwd)" \
|
|
7594
7632
|
LOKI_CMD="$loki_cmd" \
|
|
7633
|
+
LOKI_SCRIPT_DIR_RESOLVED="$_LOKI_SCRIPT_DIR" \
|
|
7595
7634
|
LOKI_SESSION_ID="$session_id" \
|
|
7596
7635
|
LOKI_WORKTREE_BRANCH="$branch_name" \
|
|
7597
7636
|
LOKI_PRD_PATH="$detach_prd" \
|
|
@@ -7682,6 +7721,28 @@ Provider: ${issue_provider}
|
|
|
7682
7721
|
## Changes
|
|
7683
7722
|
$(git log --oneline "main..HEAD" 2>/dev/null || echo "See diff")"
|
|
7684
7723
|
|
|
7724
|
+
# Proven PR (Loop 6): append the Evidence Receipt to the body. One
|
|
7725
|
+
# append covers BOTH the gh and glab branches below. Default-on;
|
|
7726
|
+
# LOKI_PROVEN_PR=0 -> body byte-identical to before. The proof lives
|
|
7727
|
+
# under the project .loki resolved from the run_id pointer; empty
|
|
7728
|
+
# expected_head_sha by design (R-DET-1 run_id pointer is the guard).
|
|
7729
|
+
if [ "${LOKI_PROVEN_PR:-1}" != "0" ] && declare -f render_evidence_receipt_md >/dev/null 2>&1; then
|
|
7730
|
+
local _gh_idfile=".loki/state/last-proof-id.txt"
|
|
7731
|
+
if [ -s "$_gh_idfile" ]; then
|
|
7732
|
+
local _gh_rid=""
|
|
7733
|
+
_gh_rid="$(cat "$_gh_idfile" 2>/dev/null || true)"
|
|
7734
|
+
if [ -n "$_gh_rid" ] && [ -f ".loki/proofs/$_gh_rid/proof.json" ]; then
|
|
7735
|
+
local _gh_receipt=""
|
|
7736
|
+
_gh_receipt="$(render_evidence_receipt_md ".loki/proofs/$_gh_rid/proof.json" "" "" 2>/dev/null || true)"
|
|
7737
|
+
if [ -n "$_gh_receipt" ]; then
|
|
7738
|
+
pr_body="${pr_body}
|
|
7739
|
+
|
|
7740
|
+
${_gh_receipt}"
|
|
7741
|
+
fi
|
|
7742
|
+
fi
|
|
7743
|
+
fi
|
|
7744
|
+
fi
|
|
7745
|
+
|
|
7685
7746
|
case "${issue_provider:-github}" in
|
|
7686
7747
|
github)
|
|
7687
7748
|
if command -v gh &>/dev/null; then
|
package/autonomy/run.sh
CHANGED
|
@@ -572,6 +572,25 @@ if [ -f "$GIT_PR_ADVISORY_LIB" ]; then
|
|
|
572
572
|
source "$GIT_PR_ADVISORY_LIB"
|
|
573
573
|
fi
|
|
574
574
|
|
|
575
|
+
# Proven PR (Loop 6): shared print-only Evidence Receipt renderer for PR bodies.
|
|
576
|
+
# render_evidence_receipt_md prints the run's honest headline + facts +
|
|
577
|
+
# verify-yourself block into the PR body. Pure, never pushes/PRs/mutates.
|
|
578
|
+
PROOF_PR_LIB="$SCRIPT_DIR/lib/proof-pr.sh"
|
|
579
|
+
if [ -f "$PROOF_PR_LIB" ]; then
|
|
580
|
+
# shellcheck source=lib/proof-pr.sh
|
|
581
|
+
source "$PROOF_PR_LIB"
|
|
582
|
+
fi
|
|
583
|
+
|
|
584
|
+
# Proven PR (Loop 6 / Slice B): optional advisory verified-completion check-run.
|
|
585
|
+
# Owned by Slice B (autonomy/lib/proof-check.sh); sourced guarded so this slice
|
|
586
|
+
# is correct whether or not the file is present in the tree, and the single call
|
|
587
|
+
# site is itself guarded on declare -f + LOKI_PROVEN_PR_CHECK.
|
|
588
|
+
PROOF_CHECK_LIB="$SCRIPT_DIR/lib/proof-check.sh"
|
|
589
|
+
if [ -f "$PROOF_CHECK_LIB" ]; then
|
|
590
|
+
# shellcheck source=lib/proof-check.sh
|
|
591
|
+
source "$PROOF_CHECK_LIB"
|
|
592
|
+
fi
|
|
593
|
+
|
|
575
594
|
# Completion Council (v5.25.0) - Multi-agent completion verification
|
|
576
595
|
# Source completion council module
|
|
577
596
|
COUNCIL_SCRIPT="$SCRIPT_DIR/completion-council.sh"
|
|
@@ -2330,6 +2349,19 @@ EOF
|
|
|
2330
2349
|
jq -r '.completed_tasks[]? | select(.github_issue) | "Closes #\(.github_issue)"' .loki/ledger.json >> "$pr_body" 2>/dev/null || true
|
|
2331
2350
|
fi
|
|
2332
2351
|
|
|
2352
|
+
# Proven PR (Loop 6): append the Evidence Receipt to the body file before
|
|
2353
|
+
# gh pr create --body-file. Default-on; LOKI_PROVEN_PR=0 -> body file bytes
|
|
2354
|
+
# byte-identical to before. Empty expected_head_sha by design (R-DET-1
|
|
2355
|
+
# run_id pointer is the anti-stale guard; the branch head is offset by the
|
|
2356
|
+
# session commit). Best-effort: a missing proof appends nothing extra here.
|
|
2357
|
+
if [ "${LOKI_PROVEN_PR:-1}" != "0" ] && declare -f render_evidence_receipt_md >/dev/null 2>&1; then
|
|
2358
|
+
local _bf_proof=""
|
|
2359
|
+
_bf_proof="$(_loki_proof_json_for_pr 2>/dev/null || true)"
|
|
2360
|
+
if [ -n "$_bf_proof" ]; then
|
|
2361
|
+
{ printf '\n'; render_evidence_receipt_md "$_bf_proof" "" "" 2>/dev/null; } >> "$pr_body" 2>/dev/null || true
|
|
2362
|
+
fi
|
|
2363
|
+
fi
|
|
2364
|
+
|
|
2333
2365
|
# Build PR create command
|
|
2334
2366
|
local pr_args=("pr" "create" "--repo" "$repo" "--title" "[Loki Mode] $feature_name" "--body-file" "$pr_body")
|
|
2335
2367
|
|
|
@@ -3184,7 +3216,30 @@ on_run_complete() {
|
|
|
3184
3216
|
log_info "LOKI_DELEGATE_PR=1: PR already exists for branch '$branch': $existing_pr (skipping create)."
|
|
3185
3217
|
return 0
|
|
3186
3218
|
fi
|
|
3187
|
-
|
|
3219
|
+
# Proven PR (Loop 6): append the Evidence Receipt to the inline body.
|
|
3220
|
+
# Default-on; LOKI_PROVEN_PR=0 -> body byte-identical to before. This path
|
|
3221
|
+
# runs gh from a `cd "${TARGET_DIR:-.}"` subshell, so resolve the proof
|
|
3222
|
+
# relative to TARGET_DIR (not the bare-relative helper). Empty
|
|
3223
|
+
# expected_head_sha by design (R-DET-1 run_id pointer is the anti-stale guard).
|
|
3224
|
+
local _del_body="Opened by Loki Mode (delegate mode). Review locally before merge."
|
|
3225
|
+
if [ "${LOKI_PROVEN_PR:-1}" != "0" ] && declare -f render_evidence_receipt_md >/dev/null 2>&1; then
|
|
3226
|
+
local _del_loki="${TARGET_DIR:-.}/.loki"
|
|
3227
|
+
local _del_idfile="$_del_loki/state/last-proof-id.txt"
|
|
3228
|
+
if [ -s "$_del_idfile" ]; then
|
|
3229
|
+
local _del_rid=""
|
|
3230
|
+
_del_rid="$(cat "$_del_idfile" 2>/dev/null || true)"
|
|
3231
|
+
if [ -n "$_del_rid" ] && [ -f "$_del_loki/proofs/$_del_rid/proof.json" ]; then
|
|
3232
|
+
local _del_receipt=""
|
|
3233
|
+
_del_receipt="$(render_evidence_receipt_md "$_del_loki/proofs/$_del_rid/proof.json" "" "" 2>/dev/null || true)"
|
|
3234
|
+
if [ -n "$_del_receipt" ]; then
|
|
3235
|
+
_del_body="${_del_body}
|
|
3236
|
+
|
|
3237
|
+
${_del_receipt}"
|
|
3238
|
+
fi
|
|
3239
|
+
fi
|
|
3240
|
+
fi
|
|
3241
|
+
fi
|
|
3242
|
+
pr_url="$( (cd "${TARGET_DIR:-.}" && _loki_net gh pr create --title "$pr_title" --body "$_del_body" --head "$branch") 2>/dev/null || true )"
|
|
3188
3243
|
if [ -n "$pr_url" ]; then
|
|
3189
3244
|
# Export so build_completion_summary folds the url into the summary.
|
|
3190
3245
|
_LOKI_DELEGATE_PR_URL="$pr_url"
|
|
@@ -5501,6 +5556,46 @@ generate_proof_of_run() {
|
|
|
5501
5556
|
local ver provider
|
|
5502
5557
|
ver="$(get_version 2>/dev/null || echo unknown)"
|
|
5503
5558
|
provider="${PROVIDER_NAME:-claude}"
|
|
5559
|
+
|
|
5560
|
+
# Proven PR (Loop 6 / Slice A2): resolve a deterministic run_id so the
|
|
5561
|
+
# proof + the PR Evidence Receipt agree, and persist a stable pointer the PR
|
|
5562
|
+
# sites read (never newest-by-mtime). The generator otherwise mints a fresh
|
|
5563
|
+
# _gen_run_id() when LOKI_SESSION_ID is unset (the `loki start ./prd.md`
|
|
5564
|
+
# case), which the later PR step could not know. Gated on the SAME flag as the
|
|
5565
|
+
# receipt so LOKI_PROVEN_PR=0 is a byte-identical no-op (no --run-id passed,
|
|
5566
|
+
# no pointer written): the pointer is only ever READ by the renderer, which
|
|
5567
|
+
# is itself off under that flag.
|
|
5568
|
+
if [ "${LOKI_PROVEN_PR:-1}" != "0" ]; then
|
|
5569
|
+
local _rid=""
|
|
5570
|
+
if declare -f _loki_trust_run_id >/dev/null 2>&1; then
|
|
5571
|
+
_rid="$(_loki_trust_run_id 2>/dev/null || true)"
|
|
5572
|
+
fi
|
|
5573
|
+
# Fall back to a locally minted id when no persisted per-run id exists.
|
|
5574
|
+
# Do NOT call _loki_trust_run_id --new here: that would clobber the
|
|
5575
|
+
# trust-events run-id file; this is a read-or-mint-local resolution.
|
|
5576
|
+
if [ -z "$_rid" ]; then
|
|
5577
|
+
_rid="proof-$(date -u +%Y%m%d%H%M%S 2>/dev/null || echo 0)-$$-${RANDOM:-0}"
|
|
5578
|
+
fi
|
|
5579
|
+
ITERATION_COUNT="${ITERATION_COUNT:-0}" \
|
|
5580
|
+
PROVIDER_NAME="$provider" \
|
|
5581
|
+
PRD_PATH="${prd_path:-}" \
|
|
5582
|
+
python3 "$gen" \
|
|
5583
|
+
--loki-dir "$loki_dir" \
|
|
5584
|
+
--loki-version "$ver" \
|
|
5585
|
+
--provider "$provider" \
|
|
5586
|
+
--run-id "$_rid" \
|
|
5587
|
+
--quiet >/dev/null 2>&1 || true
|
|
5588
|
+
# Persist the resolved run_id atomically (.tmp + mv) so the PR sites read
|
|
5589
|
+
# the exact run the generator wrote, never an mtime guess.
|
|
5590
|
+
local _id_dir="$loki_dir/state"
|
|
5591
|
+
local _id_file="$_id_dir/last-proof-id.txt"
|
|
5592
|
+
mkdir -p "$_id_dir" 2>/dev/null || true
|
|
5593
|
+
if printf '%s' "$_rid" > "${_id_file}.tmp" 2>/dev/null; then
|
|
5594
|
+
mv -f "${_id_file}.tmp" "$_id_file" 2>/dev/null || rm -f "${_id_file}.tmp" 2>/dev/null || true
|
|
5595
|
+
fi
|
|
5596
|
+
return 0
|
|
5597
|
+
fi
|
|
5598
|
+
|
|
5504
5599
|
ITERATION_COUNT="${ITERATION_COUNT:-0}" \
|
|
5505
5600
|
PROVIDER_NAME="$provider" \
|
|
5506
5601
|
PRD_PATH="${prd_path:-}" \
|
|
@@ -6327,6 +6422,25 @@ commit_session_changes() {
|
|
|
6327
6422
|
return 0
|
|
6328
6423
|
}
|
|
6329
6424
|
|
|
6425
|
+
# _loki_proof_json_for_pr
|
|
6426
|
+
# Resolve THIS run's proof.json path from the persisted run_id pointer
|
|
6427
|
+
# (.loki/state/last-proof-id.txt, written by generate_proof_of_run / Slice A2).
|
|
6428
|
+
# Echoes the path when both the pointer and the file exist, else empty. NEVER
|
|
6429
|
+
# uses newest-by-mtime (R-DET-1). Best-effort, always returns 0. Uses the bare
|
|
6430
|
+
# relative .loki to match the other create_session_pr state reads (cwd==TARGET_DIR
|
|
6431
|
+
# at PR time). Returns empty under LOKI_PROVEN_PR=0 (pointer is never written).
|
|
6432
|
+
_loki_proof_json_for_pr() {
|
|
6433
|
+
local id_file=".loki/state/last-proof-id.txt"
|
|
6434
|
+
[ -s "$id_file" ] || { printf '%s' ""; return 0; }
|
|
6435
|
+
local rid=""
|
|
6436
|
+
rid="$(cat "$id_file" 2>/dev/null || true)"
|
|
6437
|
+
[ -n "$rid" ] || { printf '%s' ""; return 0; }
|
|
6438
|
+
local p=".loki/proofs/$rid/proof.json"
|
|
6439
|
+
[ -f "$p" ] || { printf '%s' ""; return 0; }
|
|
6440
|
+
printf '%s' "$p"
|
|
6441
|
+
return 0
|
|
6442
|
+
}
|
|
6443
|
+
|
|
6330
6444
|
create_session_pr() {
|
|
6331
6445
|
# Advise the user how to open a PR for the agent branch. PRINT-ONLY by
|
|
6332
6446
|
# default (no push, no PR). LOKI_AUTO_PR=1 restores the legacy auto behavior.
|
|
@@ -6371,6 +6485,22 @@ create_session_pr() {
|
|
|
6371
6485
|
else
|
|
6372
6486
|
log_info "To open a pull request: git push -u origin ${branch_name}, then open a PR (base: ${base})"
|
|
6373
6487
|
fi
|
|
6488
|
+
# Proven PR (Loop 6): print the Evidence Receipt block AFTER the push/PR
|
|
6489
|
+
# advice so a user opening a manual PR can paste it into the body. This is
|
|
6490
|
+
# the print-PR-body fallback. Default-on; LOKI_PROVEN_PR=0 -> not invoked
|
|
6491
|
+
# (advisory output byte-identical to before). Production callers pass an
|
|
6492
|
+
# empty expected_head_sha: the session commit lands between proof-gen and
|
|
6493
|
+
# this point, so the branch head is structurally offset from the proof's
|
|
6494
|
+
# head and feeding it would false-degrade every legitimate receipt; the
|
|
6495
|
+
# anti-stale guarantee is the run_id pointer (R-DET-1), not a head match.
|
|
6496
|
+
if [ "${LOKI_PROVEN_PR:-1}" != "0" ] && declare -f render_evidence_receipt_md >/dev/null 2>&1; then
|
|
6497
|
+
local _pr_proof=""
|
|
6498
|
+
_pr_proof="$(_loki_proof_json_for_pr 2>/dev/null || true)"
|
|
6499
|
+
if [ -n "$_pr_proof" ]; then
|
|
6500
|
+
printf '\n'
|
|
6501
|
+
render_evidence_receipt_md "$_pr_proof" "" "" || true
|
|
6502
|
+
fi
|
|
6503
|
+
fi
|
|
6374
6504
|
return 0
|
|
6375
6505
|
fi
|
|
6376
6506
|
|
|
@@ -6395,21 +6525,95 @@ create_session_pr() {
|
|
|
6395
6525
|
if [ -n "$existing_pr" ]; then
|
|
6396
6526
|
log_info "PR already exists for branch $branch_name: $existing_pr (skipping create)"
|
|
6397
6527
|
audit_log "PR_EXISTS" "branch=$branch_name,url=$existing_pr"
|
|
6528
|
+
# Proven PR (Loop 6, PO-locked Q1): on idempotent reuse, leave the
|
|
6529
|
+
# existing PR untouched (do not rewrite the body, edit the PR, or
|
|
6530
|
+
# post a comment). Just hint that an Evidence Receipt is available.
|
|
6531
|
+
if [ "${LOKI_PROVEN_PR:-1}" != "0" ]; then
|
|
6532
|
+
local _exist_proof=""
|
|
6533
|
+
_exist_proof="$(_loki_proof_json_for_pr 2>/dev/null || true)"
|
|
6534
|
+
if [ -n "$_exist_proof" ]; then
|
|
6535
|
+
log_info "Evidence Receipt available for this run: loki proof open (run id in .loki/state/last-proof-id.txt)"
|
|
6536
|
+
fi
|
|
6537
|
+
fi
|
|
6398
6538
|
return 0
|
|
6399
6539
|
fi
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6540
|
+
# Build the body into a variable so the Proven PR Evidence Receipt can be
|
|
6541
|
+
# appended (Loop 6). Default-on; LOKI_PROVEN_PR=0 -> body bytes are
|
|
6542
|
+
# byte-identical to the legacy inline body. Empty expected_head_sha by
|
|
6543
|
+
# design (see the advisory-branch note: the session commit offsets the
|
|
6544
|
+
# branch head from the proof head; R-DET-1 run_id pointer is the guard).
|
|
6545
|
+
local _auto_body
|
|
6546
|
+
_auto_body="Automated changes from Loki Mode agent session.
|
|
6403
6547
|
|
|
6404
6548
|
Branch: \`$branch_name\`
|
|
6405
6549
|
Session PID: $$
|
|
6406
|
-
Created: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
6550
|
+
Created: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
6551
|
+
local _auto_proof=""
|
|
6552
|
+
if [ "${LOKI_PROVEN_PR:-1}" != "0" ] && declare -f render_evidence_receipt_md >/dev/null 2>&1; then
|
|
6553
|
+
_auto_proof="$(_loki_proof_json_for_pr 2>/dev/null || true)"
|
|
6554
|
+
if [ -n "$_auto_proof" ]; then
|
|
6555
|
+
local _auto_receipt=""
|
|
6556
|
+
_auto_receipt="$(render_evidence_receipt_md "$_auto_proof" "" "" 2>/dev/null || true)"
|
|
6557
|
+
if [ -n "$_auto_receipt" ]; then
|
|
6558
|
+
_auto_body="${_auto_body}
|
|
6559
|
+
|
|
6560
|
+
${_auto_receipt}"
|
|
6561
|
+
fi
|
|
6562
|
+
fi
|
|
6563
|
+
fi
|
|
6564
|
+
pr_url=$(gh pr create \
|
|
6565
|
+
--title "Loki Mode: Agent session changes ($branch_name)" \
|
|
6566
|
+
--body "$_auto_body" \
|
|
6407
6567
|
--base "$base" \
|
|
6408
6568
|
--head "$branch_name" 2>/dev/null) || true
|
|
6409
6569
|
|
|
6410
6570
|
if [ -n "$pr_url" ]; then
|
|
6411
6571
|
log_info "PR created: $pr_url"
|
|
6412
6572
|
audit_log "PR_CREATED" "branch=$branch_name,url=$pr_url"
|
|
6573
|
+
# Proven PR (Loop 6 / Slice D linkage): persist {run_id, pr_url} next
|
|
6574
|
+
# to the proof so the dashboard proofs panel can show "PR #N:
|
|
6575
|
+
# <headline>". Slice D owns the READ; Slice A owns this WRITE. Atomic
|
|
6576
|
+
# .tmp + mv, python3-guarded for correct JSON escaping, only when a
|
|
6577
|
+
# proof for THIS run exists and a pr_url was returned. Best-effort.
|
|
6578
|
+
if [ -n "$_auto_proof" ] && command -v python3 >/dev/null 2>&1; then
|
|
6579
|
+
local _pr_json_dir
|
|
6580
|
+
_pr_json_dir="$(dirname "$_auto_proof" 2>/dev/null || true)"
|
|
6581
|
+
if [ -n "$_pr_json_dir" ] && [ -d "$_pr_json_dir" ]; then
|
|
6582
|
+
local _pr_run_id
|
|
6583
|
+
_pr_run_id="$(basename "$_pr_json_dir" 2>/dev/null || true)"
|
|
6584
|
+
LOKI_PR_JSON_DIR="$_pr_json_dir" \
|
|
6585
|
+
LOKI_PR_RUN_ID="$_pr_run_id" \
|
|
6586
|
+
LOKI_PR_URL="$pr_url" \
|
|
6587
|
+
python3 - <<'PR_JSON_PY' 2>/dev/null || true
|
|
6588
|
+
import json
|
|
6589
|
+
import os
|
|
6590
|
+
|
|
6591
|
+
d = os.environ.get("LOKI_PR_JSON_DIR", "")
|
|
6592
|
+
run_id = os.environ.get("LOKI_PR_RUN_ID", "")
|
|
6593
|
+
pr_url = os.environ.get("LOKI_PR_URL", "")
|
|
6594
|
+
if d and os.path.isdir(d):
|
|
6595
|
+
path = os.path.join(d, "pr.json")
|
|
6596
|
+
tmp = path + ".tmp"
|
|
6597
|
+
try:
|
|
6598
|
+
with open(tmp, "w") as f:
|
|
6599
|
+
json.dump({"run_id": run_id, "pr_url": pr_url}, f)
|
|
6600
|
+
os.replace(tmp, path)
|
|
6601
|
+
except Exception:
|
|
6602
|
+
try:
|
|
6603
|
+
os.remove(tmp)
|
|
6604
|
+
except Exception:
|
|
6605
|
+
pass
|
|
6606
|
+
PR_JSON_PY
|
|
6607
|
+
fi
|
|
6608
|
+
fi
|
|
6609
|
+
# Proven PR (Loop 6 / Slice B integration): one guarded call to the
|
|
6610
|
+
# optional advisory verified-completion check-run. Posts NOTHING by
|
|
6611
|
+
# default (LOKI_PROVEN_PR_CHECK unset). Slice B owns proof-check.sh;
|
|
6612
|
+
# guarded on the file being sourced (declare -f) so this slice is
|
|
6613
|
+
# correct whether or not B is integrated. Best-effort, never fails PR.
|
|
6614
|
+
if [ "${LOKI_PROVEN_PR_CHECK:-0}" = "1" ] && declare -f post_verified_completion_check >/dev/null 2>&1; then
|
|
6615
|
+
post_verified_completion_check "${_auto_proof:-}" "$pr_url" || true
|
|
6616
|
+
fi
|
|
6413
6617
|
else
|
|
6414
6618
|
log_warn "Failed to create PR - branch pushed to: $branch_name"
|
|
6415
6619
|
fi
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -10078,6 +10078,25 @@ def _safe_proof_run_dir(run_id: str) -> _Path:
|
|
|
10078
10078
|
return _Path(target)
|
|
10079
10079
|
|
|
10080
10080
|
|
|
10081
|
+
def _proof_pr_url(run_dir: _Path) -> Optional[str]:
|
|
10082
|
+
"""Return the PR URL linked to this run, or None.
|
|
10083
|
+
|
|
10084
|
+
Reads the optional <run_dir>/pr.json that the run.sh auto-PR path writes at
|
|
10085
|
+
PR-creation time ({"run_id": "...", "pr_url": "..."}). The file is read only
|
|
10086
|
+
inside the already-validated run dir (the caller passes either an iterdir()
|
|
10087
|
+
entry or a _safe_proof_run_dir result -- never a raw run_id), so there is no
|
|
10088
|
+
additional traversal surface. Missing/unreadable/non-dict pr.json or an
|
|
10089
|
+
empty/non-string pr_url -> None, never an error. Most proofs have no PR.
|
|
10090
|
+
"""
|
|
10091
|
+
pr_data = _safe_json_read(run_dir / "pr.json", default=None)
|
|
10092
|
+
if not isinstance(pr_data, dict):
|
|
10093
|
+
return None
|
|
10094
|
+
url = pr_data.get("pr_url")
|
|
10095
|
+
if isinstance(url, str) and url:
|
|
10096
|
+
return url
|
|
10097
|
+
return None
|
|
10098
|
+
|
|
10099
|
+
|
|
10081
10100
|
@app.get("/api/proofs", dependencies=[Depends(auth.require_scope("read"))])
|
|
10082
10101
|
async def list_proofs():
|
|
10083
10102
|
"""List proof-of-run artifacts for the active project's .loki/proofs/."""
|
|
@@ -10096,6 +10115,11 @@ async def list_proofs():
|
|
|
10096
10115
|
data = _safe_json_read(proof_json, default=None)
|
|
10097
10116
|
if not isinstance(data, dict):
|
|
10098
10117
|
continue
|
|
10118
|
+
# Deterministic honesty headline (single source of truth, same access as
|
|
10119
|
+
# proofs_summary's bucketing). Read, never recomputed. The list endpoint
|
|
10120
|
+
# surfaces it so the panel can show the verdict without a second fetch.
|
|
10121
|
+
honesty = data.get("honesty")
|
|
10122
|
+
headline = honesty.get("headline") if isinstance(honesty, dict) else None
|
|
10099
10123
|
items.append({
|
|
10100
10124
|
"run_id": data.get("run_id", entry.name),
|
|
10101
10125
|
"generated_at": data.get("generated_at"),
|
|
@@ -10103,6 +10127,8 @@ async def list_proofs():
|
|
|
10103
10127
|
"cost_usd": (data.get("cost") or {}).get("usd"),
|
|
10104
10128
|
"files_changed": (data.get("files_changed") or {}).get("count"),
|
|
10105
10129
|
"final_verdict": (data.get("council") or {}).get("final_verdict"),
|
|
10130
|
+
"headline": headline,
|
|
10131
|
+
"pr_url": _proof_pr_url(entry),
|
|
10106
10132
|
"has_html": (entry / "index.html").is_file(),
|
|
10107
10133
|
})
|
|
10108
10134
|
# Newest first when generated_at is present.
|
|
@@ -10176,6 +10202,10 @@ async def get_proof(run_id: str):
|
|
|
10176
10202
|
data = _safe_json_read(proof_json, default=None)
|
|
10177
10203
|
if not isinstance(data, dict):
|
|
10178
10204
|
raise HTTPException(status_code=500, detail="proof.json unreadable")
|
|
10205
|
+
# Surface the optional PR linkage alongside the proof. The proof.json itself
|
|
10206
|
+
# already carries honesty.headline; we only add pr_url so the panel can show
|
|
10207
|
+
# "PR #N -> <headline>". Absent pr.json -> pr_url null, never an error.
|
|
10208
|
+
data["pr_url"] = _proof_pr_url(run_dir)
|
|
10179
10209
|
return JSONResponse(content=data)
|
|
10180
10210
|
|
|
10181
10211
|
|
|
@@ -69,12 +69,18 @@
|
|
|
69
69
|
display: block; background: var(--panel); border: 1px solid var(--border);
|
|
70
70
|
border-radius: 12px; padding: 16px 18px; color: var(--text);
|
|
71
71
|
}
|
|
72
|
-
.row:hover { border-color: var(--accent);
|
|
72
|
+
.row:hover { border-color: var(--accent); }
|
|
73
|
+
.row-main { display: block; color: var(--text); }
|
|
74
|
+
.row-main:hover { text-decoration: none; }
|
|
73
75
|
.row .top { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
|
|
74
76
|
.row .rid { font-family: var(--mono); font-size: 14px; font-weight: 600; }
|
|
75
77
|
.row .usd { font-family: var(--mono); font-size: 16px; font-weight: 650; }
|
|
76
78
|
.row .ts { color: var(--faint); font-size: 12px; margin-left: auto; }
|
|
77
79
|
.row .meta { color: var(--muted); font-size: 13px; margin-top: 6px; display: flex; gap: 14px; flex-wrap: wrap; }
|
|
80
|
+
.row .pr { font-size: 13px; margin-top: 8px; }
|
|
81
|
+
.row .pr a { font-family: var(--mono); font-weight: 600; }
|
|
82
|
+
.row .pr .arrow { color: var(--faint); margin: 0 6px; }
|
|
83
|
+
.row .pr .verdict { color: var(--muted); }
|
|
78
84
|
.badge { font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 6px; border: 1px solid var(--border); }
|
|
79
85
|
.b-approve { color: var(--green); border-color: rgba(52,211,153,0.4); }
|
|
80
86
|
.b-reject { color: var(--red); border-color: rgba(248,113,113,0.4); }
|
|
@@ -115,6 +121,16 @@
|
|
|
115
121
|
if (v.indexOf("CONCERN") === 0) return "b-concern";
|
|
116
122
|
return "";
|
|
117
123
|
}
|
|
124
|
+
// Extract the PR/MR number from a GitHub/GitLab PR URL for the display label.
|
|
125
|
+
// GitHub: .../pull/123 GitLab: .../merge_requests/123. Falls back to the
|
|
126
|
+
// trailing numeric path segment. Returns "" when no number can be found.
|
|
127
|
+
function prNumber(url) {
|
|
128
|
+
var s = String(url || "");
|
|
129
|
+
var m = s.match(/\/(?:pull|merge_requests|pulls)\/(\d+)/);
|
|
130
|
+
if (m) return m[1];
|
|
131
|
+
m = s.match(/\/(\d+)(?:[/?#]|$)/);
|
|
132
|
+
return m ? m[1] : "";
|
|
133
|
+
}
|
|
118
134
|
function render(proofs) {
|
|
119
135
|
var c = document.getElementById("content");
|
|
120
136
|
if (!proofs || proofs.length === 0) {
|
|
@@ -133,11 +149,24 @@
|
|
|
133
149
|
var ts = p.generated_at ? esc(p.generated_at) : "";
|
|
134
150
|
var href = p.has_html ? ("/api/proofs/" + encodeURIComponent(p.run_id) + "/html") : ("/api/proofs/" + encodeURIComponent(p.run_id));
|
|
135
151
|
var meta = [usd ? '<span class="usd">' + usd + "</span>" : "", fc, ver].filter(Boolean).join(" ");
|
|
136
|
-
|
|
152
|
+
// PR linkage: render "PR #N -> <headline>" only when a PR is attached. The
|
|
153
|
+
// headline is the proof's own deterministic honesty.headline (display only,
|
|
154
|
+
// no client-side verdict logic). No pr_url -> nothing extra is rendered.
|
|
155
|
+
var prBlock = "";
|
|
156
|
+
if (p.pr_url) {
|
|
157
|
+
var num = prNumber(p.pr_url);
|
|
158
|
+
var label = num ? ("PR #" + esc(num)) : "PR";
|
|
159
|
+
var hl = p.headline ? ('<span class="arrow">-></span><span class="verdict">' + esc(p.headline) + "</span>") : "";
|
|
160
|
+
prBlock = '<div class="pr"><a href="' + esc(p.pr_url) +
|
|
161
|
+
'" target="_blank" rel="noopener noreferrer">' + label + "</a>" + hl + "</div>";
|
|
162
|
+
}
|
|
163
|
+
rows += '<div class="row">' +
|
|
164
|
+
'<a class="row-main" href="' + href + '">' +
|
|
137
165
|
'<div class="top"><span class="rid">' + rid + "</span>" + verdict +
|
|
138
166
|
(ts ? '<span class="ts">' + ts + "</span>" : "") + "</div>" +
|
|
139
167
|
(meta ? '<div class="meta">' + meta + "</div>" : "") +
|
|
140
|
-
"</a>"
|
|
168
|
+
"</a>" + prBlock +
|
|
169
|
+
"</div>";
|
|
141
170
|
}
|
|
142
171
|
c.innerHTML = '<div class="list">' + rows + "</div>";
|
|
143
172
|
}
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v7.
|
|
5
|
+
**Version:** v7.90.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -395,7 +395,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
|
|
|
395
395
|
# Run Loki Mode in Docker (Claude provider, API-key auth)
|
|
396
396
|
docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
|
|
397
397
|
-v $(pwd):/workspace -w /workspace \
|
|
398
|
-
asklokesh/loki-mode:7.
|
|
398
|
+
asklokesh/loki-mode:7.90.0 start ./my-spec.md
|
|
399
399
|
```
|
|
400
400
|
|
|
401
401
|
##### docker compose + .env (no host install)
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var z8=Object.defineProperty;var X8=($)=>$;function K8($,Q){this[$]=X8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)z8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:K8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var y1={};b(y1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as t,dirname as i$}from"path";import{fileURLToPath as q8}from"url";import{existsSync as E$}from"fs";import{homedir as J8}from"os";function V8(){let $=h1;for(let Q=0;Q<6;Q++){if(E$(t($,"VERSION"))&&E$(t($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return t(h1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(E$(t(Q,"VERSION"))&&E$(t(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function P(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(J8(),".loki")}var h1,h;var C=L(()=>{h1=i$(q8(import.meta.url));h=V8()});import{readFileSync as W8}from"fs";import{resolve as U8,dirname as H8}from"path";import{fileURLToPath as G8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.
|
|
2
|
+
var z8=Object.defineProperty;var X8=($)=>$;function K8($,Q){this[$]=X8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)z8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:K8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var y1={};b(y1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as t,dirname as i$}from"path";import{fileURLToPath as q8}from"url";import{existsSync as E$}from"fs";import{homedir as J8}from"os";function V8(){let $=h1;for(let Q=0;Q<6;Q++){if(E$(t($,"VERSION"))&&E$(t($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return t(h1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(E$(t(Q,"VERSION"))&&E$(t(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function P(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(J8(),".loki")}var h1,h;var C=L(()=>{h1=i$(q8(import.meta.url));h=V8()});import{readFileSync as W8}from"fs";import{resolve as U8,dirname as H8}from"path";import{fileURLToPath as G8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.90.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=H8(G8(import.meta.url)),Z=e$(Q);Q$=W8(U8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var $1=L(()=>{C()});var u1={};b(u1,{runOrThrow:()=>x8,run:()=>F,readStreamCapped:()=>f1,commandVersion:()=>N8,commandExists:()=>f,ShellError:()=>Q1,MAX_STDOUT_BYTES:()=>m1});async function f1($,Q=m1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:W}=await Z.read();if(K)break;if(!W)continue;if(q+=W.byteLength,q>Q){let V=W.byteLength-(q-Q);X+=z.decode(W.subarray(0,V),{stream:!0});break}X+=z.decode(W,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function F($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,W]=await Promise.all([f1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:W}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function x8($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new Q1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=E8($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function E8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function N8($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var m1=16777216,Q1;var d=L(()=>{Q1=class Q1 extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function r($){return S8?"":$}var S8,T,S,_,lZ,I,k,y,J;var c=L(()=>{S8=(process.env.NO_COLOR??"").length>0;T=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),lZ=r("\x1B[0;34m"),I=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as u8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(u8($))return A$=$,$;let Q=await f("python3.12");if(Q)return A$=Q,Q;let Z=await f("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var A$;var V$=L(()=>{d()});var q0={};b(q0,{runStatus:()=>W3});import{existsSync as v,readFileSync as U$,readdirSync as i1,statSync as e1}from"fs";import{resolve as D,basename as $3}from"path";import{homedir as Q3}from"os";function $0($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function Q0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*D$/Q);if(X>D$)X=D$;let q=D$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=$0($),U=$0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${U})`}async function z3(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${J}
|
|
3
3
|
`),process.stdout.write(`Install with:
|
|
4
4
|
`),process.stdout.write(` brew install jq (macOS)
|
|
5
5
|
`),process.stdout.write(` apt install jq (Debian/Ubuntu)
|
|
@@ -802,4 +802,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
802
802
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
803
803
|
`),process.stderr.write(Z8),2}}r1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var FZ=await jZ(Bun.argv.slice(2));process.exit(FZ);
|
|
804
804
|
|
|
805
|
-
//# debugId=
|
|
805
|
+
//# debugId=BB4B923236A077B864756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "7.
|
|
4
|
+
"version": "7.90.0",
|
|
5
5
|
"description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "7.
|
|
5
|
+
"version": "7.90.0",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|