loki-mode 9.22.5 → 9.22.6
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 +11 -4
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/issue-parser.sh +112 -0
- package/autonomy/lib/proof-generator.py +88 -0
- package/autonomy/lib/proof-pr.sh +52 -0
- package/autonomy/loki +147 -3
- package/autonomy/quickstart.sh +250 -5
- package/autonomy/run.sh +26 -0
- package/dashboard/__init__.py +1 -1
- package/docs/EXECUTION-COCKPIT-PLAN.md +289 -0
- package/docs/HARNESS-INTELLIGENCE-PLAN.md +194 -0
- package/docs/ISSUE-TO-PR-GOLDEN-PATH-PLAN.md +257 -0
- package/loki-ts/dist/loki.js +299 -297
- 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-DNGNuAge.js → AdminPage-DLhWX6si.js} +1 -1
- package/web-app/dist/assets/{Avatar-CqAveJaT.js → Avatar-CqyDcUxl.js} +1 -1
- package/web-app/dist/assets/{Badge-BtMejA0Z.js → Badge-BKGSt1n2.js} +1 -1
- package/web-app/dist/assets/{Button-CikxmqSs.js → Button-DuuKHXre.js} +1 -1
- package/web-app/dist/assets/CockpitPage-DdClYERC.js +47 -0
- package/web-app/dist/assets/{ComparePage-BB8DJBWR.js → ComparePage-CUWlqBxv.js} +1 -1
- package/web-app/dist/assets/ErrorBoundary-RwfGKyRJ.js +16 -0
- package/web-app/dist/assets/{EvidenceReceiptPanel-6oquKEjd.js → EvidenceReceiptPanel-CzIgk9sg.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-5XLYWzWH.js → GitHubIssuesPanel-CsXVKuyJ.js} +3 -3
- package/web-app/dist/assets/{GitHubPRsPanel-DUfmFYeu.js → GitHubPRsPanel-CYRp1eKS.js} +3 -3
- package/web-app/dist/assets/{HomePage-DZ4MmOdH.js → HomePage-CVJcI5pQ.js} +3 -3
- package/web-app/dist/assets/LoginPage-BBJNV9Qb.js +1 -0
- package/web-app/dist/assets/{MagicPage-CFbX5gWg.js → MagicPage-CIi9P8jK.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-DbL6xmRQ.js → MetricsPage-Bm34ipiy.js} +2 -2
- package/web-app/dist/assets/{NotFoundPage-BUrjXPAR.js → NotFoundPage-DpTbcC2P.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-iP6dJihg.js → ProjectPage-CY2SMGx9.js} +65 -90
- package/web-app/dist/assets/{ProjectsPage-BibN9bHl.js → ProjectsPage-DSrXUUKJ.js} +2 -2
- package/web-app/dist/assets/{SettingsPage-C0WJp1hW.js → SettingsPage-jvDyOqYi.js} +3 -3
- package/web-app/dist/assets/{ShowcasePage-DUv0Njp8.js → ShowcasePage-C8tFWKau.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-BIaCB0Vn.js → SystemSettingsPage-DH4FTH3K.js} +2 -2
- package/web-app/dist/assets/{TeamsPage-BE0szv9r.js → TeamsPage-C1fQYauF.js} +2 -2
- package/web-app/dist/assets/{TemplatesPage-BzkA9kat.js → TemplatesPage-CSTFt-4P.js} +1 -1
- package/web-app/dist/assets/TerminalOutput-Cu4NsyN6.js +16 -0
- package/web-app/dist/assets/{activity-AbfFz38r.js → activity-CtjsDzmh.js} +1 -1
- package/web-app/dist/assets/{bell-DF6Z6y5U.js → bell-C5es1xAs.js} +1 -1
- package/web-app/dist/assets/{bot-DQ4np_3n.js → bot-DE04U5Lc.js} +1 -1
- package/web-app/dist/assets/{check-RWuguCwE.js → check-DSKKA9EU.js} +1 -1
- package/web-app/dist/assets/{chevron-left-VpbQPOkC.js → chevron-left-DJ13W5AR.js} +1 -1
- package/web-app/dist/assets/{circle-alert-DwT82lJx.js → circle-alert-CS0BfqAd.js} +1 -1
- package/web-app/dist/assets/{clock-BBlUa93G.js → clock-D1Y5nruk.js} +1 -1
- package/web-app/dist/assets/{cloud-Dgbz3VCA.js → cloud-nU6LBoZL.js} +1 -1
- package/web-app/dist/assets/{code-xml-r6qqION5.js → code-xml-BZfvIdFo.js} +1 -1
- package/web-app/dist/assets/{copy-DMHj52B8.js → copy-CK_2O1Xv.js} +1 -1
- package/web-app/dist/assets/{database-uJqHSHR1.js → database-DJiwznAm.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-BZagljek.js → dollar-sign-pO20A3QY.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-DMopmhu0.js → file-code-corner-Besy6vek.js} +1 -1
- package/web-app/dist/assets/{file-plus-DU_hT_3w.js → file-plus-DeaswxYY.js} +1 -1
- package/web-app/dist/assets/{folder-open-BP5TtQAW.js → folder-open-cr1UvQLI.js} +1 -1
- package/web-app/dist/assets/{git-commit-horizontal-zFk_IobS.js → git-commit-horizontal-BLxnfbSn.js} +1 -1
- package/web-app/dist/assets/{globe-mthCRtfp.js → globe-DrhG1NfX.js} +1 -1
- package/web-app/dist/assets/{hammer-BWaLBvSA.js → hammer-CeZtPBfE.js} +1 -1
- package/web-app/dist/assets/index-84JUN_aA.css +1 -0
- package/web-app/dist/assets/{index-BXA3UT6P.js → index-CLvjO22Z.js} +24 -24
- package/web-app/dist/assets/{layers-Q1LXV5fy.js → layers-fldADzyE.js} +1 -1
- package/web-app/dist/assets/{lightbulb-vRRNJ3dB.js → lightbulb-CznOfsPb.js} +1 -1
- package/web-app/dist/assets/{loader-circle-KgjeHEcV.js → loader-circle---lPB1Fs.js} +1 -1
- package/web-app/dist/assets/{lock-BerKqno7.js → lock-D5jVF-tO.js} +1 -1
- package/web-app/dist/assets/{mail-XAxTbprX.js → mail-D7HkLnlW.js} +1 -1
- package/web-app/dist/assets/minus-C-dT19Ct.js +6 -0
- package/web-app/dist/assets/{package-DLPKWAvW.js → package-Cm9B-cKE.js} +1 -1
- package/web-app/dist/assets/{plus-NMxxwHMn.js → plus-CQwOffLD.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-Dt42kYtM.js → refresh-cw-CvWJZLbN.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-BCR4Tp9u.js → rotate-ccw-C2Q1LYgM.js} +1 -1
- package/web-app/dist/assets/{save-sR4MJd42.js → save-BP0MvpoQ.js} +1 -1
- package/web-app/dist/assets/{server-SXkchokx.js → server-4i4VZXix.js} +1 -1
- package/web-app/dist/assets/{shield-alert-Bvgp6_Dz.js → shield-alert-D2_bgb7j.js} +1 -1
- package/web-app/dist/assets/thumbs-up-vACbzeXj.js +26 -0
- package/web-app/dist/assets/{trash-2-D0IGudH7.js → trash-2-EYjdESLs.js} +1 -1
- package/web-app/dist/assets/trending-down-BWBdcvE3.js +6 -0
- package/web-app/dist/assets/{trending-up-DDrJKL5i.js → trending-up-Bix6V_-Y.js} +1 -1
- package/web-app/dist/assets/{upload-C54rjN8K.js → upload-B7hh6ERF.js} +1 -1
- package/web-app/dist/assets/{usePolling-D7fw4YSM.js → usePolling-b_e-k-IN.js} +1 -1
- package/web-app/dist/assets/{user-Cg7fbzdh.js → user-pmjZKWEg.js} +1 -1
- package/web-app/dist/index.html +2 -2
- package/web-app/dist/assets/LoginPage-VMzI6ROD.js +0 -1
- package/web-app/dist/assets/TerminalOutput-DBrdT_Fp.js +0 -31
- package/web-app/dist/assets/index-DgpUuqwR.css +0 -1
- package/web-app/dist/assets/trending-down-BbFFBq-v.js +0 -11
package/README.md
CHANGED
|
@@ -100,13 +100,20 @@ For scripts and local dashboards, add `--json` to receive one versioned JSON
|
|
|
100
100
|
object instead of terminal text:
|
|
101
101
|
|
|
102
102
|
```bash
|
|
103
|
-
loki quickstart "a todo app with user accounts" --dry-run --json
|
|
103
|
+
loki quickstart "a todo app with user accounts" --dry-run --json > preview.json
|
|
104
|
+
loki quickstart --from-preview preview.json --yes
|
|
104
105
|
```
|
|
105
106
|
|
|
106
107
|
The object contains the input kind, deterministic selected template (or `null`
|
|
107
|
-
for an existing PRD),
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
for an existing PRD), the exact estimator response under `plan`, and a bounded
|
|
109
|
+
continuation containing the exact idea/template or the PRD path and SHA-256.
|
|
110
|
+
`--from-preview` requires explicit argv `--yes`, rejects malformed, conflicting,
|
|
111
|
+
symlinked, or changed inputs before provider and build boundaries, then uses the
|
|
112
|
+
existing no-clobber quickstart path. The saved plan is evidence rather than
|
|
113
|
+
execution authority: Loki recomputes and displays the current estimate before
|
|
114
|
+
starting. `--json` requires `--dry-run`; invalid input or estimator failure
|
|
115
|
+
writes no JSON, and preview still exits before provider discovery, file writes,
|
|
116
|
+
or build execution.
|
|
110
117
|
|
|
111
118
|
Or go straight at it:
|
|
112
119
|
|
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.22.
|
|
6
|
+
# Loki Mode v9.22.6
|
|
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.22.
|
|
473
|
+
**v9.22.6 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.22.
|
|
1
|
+
9.22.6
|
package/autonomy/issue-parser.sh
CHANGED
|
@@ -247,6 +247,110 @@ extract_type_from_labels() {
|
|
|
247
247
|
#===============================================================================
|
|
248
248
|
|
|
249
249
|
# Parse a GitHub issue and output structured data
|
|
250
|
+
#===============================================================================
|
|
251
|
+
# Golden path: issue context + early plan
|
|
252
|
+
#===============================================================================
|
|
253
|
+
|
|
254
|
+
# Split the extracted acceptance-criteria blob into one normalized item per
|
|
255
|
+
# line. Keeps ONLY genuine list items (checkbox / bullet / numbered) so a
|
|
256
|
+
# section heading or a stray prose line never becomes a fake "criterion".
|
|
257
|
+
# Prints nothing when there is nothing to print -- an empty criteria list is a
|
|
258
|
+
# truthful answer, and inventing one would poison every downstream coverage
|
|
259
|
+
# claim.
|
|
260
|
+
_gp_criteria_lines() {
|
|
261
|
+
printf '%s\n' "${1:-}" \
|
|
262
|
+
| sed -E 's/^[[:space:]]*[-*][[:space:]]*\[[ xX]\][[:space:]]*//; s/^[[:space:]]*[-*][[:space:]]+//; s/^[[:space:]]*[0-9]+[.)][[:space:]]+//' \
|
|
263
|
+
| grep -vE '^[[:space:]]*$' \
|
|
264
|
+
| grep -vE '^[[:space:]]*-{3,}[[:space:]]*$'
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
# write_journey_context <owner> <repo> <number> <title> <url> <acceptance>
|
|
268
|
+
# <files> <type> <priority>
|
|
269
|
+
#
|
|
270
|
+
# Writes .loki/state/issue-context.json (feature 2: the exact acceptance context)
|
|
271
|
+
# and .loki/state/journey-plan.json (feature 3: the early machine-readable
|
|
272
|
+
# status/plan). Both are written from `gh` output BEFORE the first provider
|
|
273
|
+
# call, so the early artifact is deterministic rather than latency-dependent.
|
|
274
|
+
#
|
|
275
|
+
# Never fails the caller: every write is best-effort and the function always
|
|
276
|
+
# returns 0. Honors LOKI_DIR so a test can point it at a scratch directory.
|
|
277
|
+
write_journey_context() {
|
|
278
|
+
local owner="$1" repo="$2" number="$3" title="$4" url="$5"
|
|
279
|
+
local acceptance="$6" files="$7" issue_type="$8" priority="$9"
|
|
280
|
+
|
|
281
|
+
local state_dir="${LOKI_DIR:-.loki}/state"
|
|
282
|
+
mkdir -p "$state_dir" 2>/dev/null || return 0
|
|
283
|
+
|
|
284
|
+
local criteria
|
|
285
|
+
criteria=$(_gp_criteria_lines "$acceptance")
|
|
286
|
+
|
|
287
|
+
# jq builds both documents so quoting/escaping is handled once, correctly.
|
|
288
|
+
command -v jq >/dev/null 2>&1 || return 0
|
|
289
|
+
|
|
290
|
+
local ctx_tmp="$state_dir/.issue-context.$$.json"
|
|
291
|
+
if jq -n \
|
|
292
|
+
--arg owner "$owner" --arg repo "$repo" --arg number "$number" \
|
|
293
|
+
--arg title "$title" --arg url "$url" --arg criteria "$criteria" \
|
|
294
|
+
--arg files "$files" --arg type "$issue_type" --arg priority "$priority" \
|
|
295
|
+
--arg captured_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
296
|
+
'{
|
|
297
|
+
schema_version: "1.0",
|
|
298
|
+
captured_at: $captured_at,
|
|
299
|
+
issue: {
|
|
300
|
+
owner: $owner, repo: $repo,
|
|
301
|
+
number: (try ($number | tonumber) catch null),
|
|
302
|
+
ref: ($owner + "/" + $repo + "#" + $number),
|
|
303
|
+
url: $url, title: $title,
|
|
304
|
+
type: $type, priority: $priority
|
|
305
|
+
},
|
|
306
|
+
acceptance_criteria: (
|
|
307
|
+
if ($criteria | length) == 0 then []
|
|
308
|
+
else ($criteria | split("\n") | map(select(length > 0)))
|
|
309
|
+
end
|
|
310
|
+
),
|
|
311
|
+
file_references: (
|
|
312
|
+
if ($files | length) == 0 then []
|
|
313
|
+
else ($files | split("\n") | map(select(length > 0)))
|
|
314
|
+
end
|
|
315
|
+
)
|
|
316
|
+
}' > "$ctx_tmp" 2>/dev/null; then
|
|
317
|
+
mv -f "$ctx_tmp" "$state_dir/issue-context.json" 2>/dev/null || rm -f "$ctx_tmp"
|
|
318
|
+
else
|
|
319
|
+
rm -f "$ctx_tmp" 2>/dev/null || true
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
# The early plan is a PLAN, not a result: it states what was understood and
|
|
323
|
+
# what will be attempted. It deliberately carries no outcome fields -- a
|
|
324
|
+
# status file that guessed at outcomes before any work happened would be the
|
|
325
|
+
# exact fake-green this repo's trust core forbids.
|
|
326
|
+
local plan_tmp="$state_dir/.journey-plan.$$.json"
|
|
327
|
+
if jq -n \
|
|
328
|
+
--arg ref "$owner/$repo#$number" --arg title "$title" --arg url "$url" \
|
|
329
|
+
--arg criteria "$criteria" \
|
|
330
|
+
--arg generated_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
331
|
+
'{
|
|
332
|
+
schema_version: "1.0",
|
|
333
|
+
command: "loki start",
|
|
334
|
+
mode: "issue",
|
|
335
|
+
stage: "planned",
|
|
336
|
+
generated_at: $generated_at,
|
|
337
|
+
issue: { ref: $ref, title: $title, url: $url },
|
|
338
|
+
acceptance_criteria: (
|
|
339
|
+
if ($criteria | length) == 0 then []
|
|
340
|
+
else ($criteria | split("\n") | map(select(length > 0)))
|
|
341
|
+
end
|
|
342
|
+
),
|
|
343
|
+
provider_invoked: false,
|
|
344
|
+
next: "implement, verify, then review-ready result with evidence receipt"
|
|
345
|
+
}' > "$plan_tmp" 2>/dev/null; then
|
|
346
|
+
mv -f "$plan_tmp" "$state_dir/journey-plan.json" 2>/dev/null || rm -f "$plan_tmp"
|
|
347
|
+
else
|
|
348
|
+
rm -f "$plan_tmp" 2>/dev/null || true
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
return 0
|
|
352
|
+
}
|
|
353
|
+
|
|
250
354
|
parse_github_issue() {
|
|
251
355
|
local issue_ref="$1"
|
|
252
356
|
local output_format="${2:-yaml}" # yaml or json
|
|
@@ -303,6 +407,14 @@ parse_github_issue() {
|
|
|
303
407
|
priority=$(extract_priority_from_labels "$labels_json")
|
|
304
408
|
issue_type=$(extract_type_from_labels "$labels_json")
|
|
305
409
|
|
|
410
|
+
# Golden path (feature 2): persist the EXACT criteria we just extracted, plus
|
|
411
|
+
# the early machine-readable plan (feature 3), before any provider call. Both
|
|
412
|
+
# are derived from `gh` output only, so the early artifact does not depend on
|
|
413
|
+
# provider latency at all. Best-effort: a failure here must never break issue
|
|
414
|
+
# parsing, which is why it is guarded and ignores its own exit status.
|
|
415
|
+
write_journey_context "$owner" "$repo" "$number" "$title" "$url" \
|
|
416
|
+
"$acceptance_criteria" "$file_references" "$issue_type" "$priority" || true
|
|
417
|
+
|
|
306
418
|
# Output based on format
|
|
307
419
|
if [ "$output_format" = "json" ]; then
|
|
308
420
|
output_json "$owner" "$repo" "$number" "$title" "$body" "$problem_statement" \
|
|
@@ -990,6 +990,78 @@ def _collect_iterations(loki_dir):
|
|
|
990
990
|
return {"count": count, "succeeded": n_completed, "failed": n_failed}
|
|
991
991
|
|
|
992
992
|
|
|
993
|
+
def _collect_journey(loki_dir):
|
|
994
|
+
"""Issue-to-PR journey facts (golden path features 4 and 10).
|
|
995
|
+
|
|
996
|
+
Descriptive record half ONLY. Like `functional` and `healthcheck`, this is
|
|
997
|
+
NOT read by _compute_headline / _compute_degraded: nothing here can turn a
|
|
998
|
+
receipt green or make a blocked run look finished.
|
|
999
|
+
|
|
1000
|
+
Every field is absent-not-zero. A measurement that did not happen renders
|
|
1001
|
+
nothing rather than a fabricated 0, matching run.sh:4258 ("a wrong timing
|
|
1002
|
+
table is worse than silence"). Returns {} when this run was not an
|
|
1003
|
+
issue-mode run, so a PRD/brief run's proof bytes are unchanged.
|
|
1004
|
+
"""
|
|
1005
|
+
state = os.path.join(loki_dir, "state")
|
|
1006
|
+
ctx = _read_json(os.path.join(state, "issue-context.json"), default=None)
|
|
1007
|
+
if not isinstance(ctx, dict):
|
|
1008
|
+
return {}
|
|
1009
|
+
|
|
1010
|
+
issue = ctx.get("issue") if isinstance(ctx.get("issue"), dict) else {}
|
|
1011
|
+
stated = ctx.get("acceptance_criteria")
|
|
1012
|
+
stated = [str(c) for c in stated] if isinstance(stated, list) else []
|
|
1013
|
+
|
|
1014
|
+
out = {
|
|
1015
|
+
"issue": {
|
|
1016
|
+
"ref": str(issue.get("ref") or ""),
|
|
1017
|
+
"url": str(issue.get("url") or ""),
|
|
1018
|
+
"title": str(issue.get("title") or ""),
|
|
1019
|
+
},
|
|
1020
|
+
# Coverage is a CORRESPONDENCE, not a verdict. There is no deterministic
|
|
1021
|
+
# checker for free-text criteria, so we report what the issue stated and
|
|
1022
|
+
# decline to claim any of it was satisfied. `addressed` is deliberately
|
|
1023
|
+
# null rather than 0: 0 would read as "none met", which we do not know.
|
|
1024
|
+
"acceptance": {
|
|
1025
|
+
"stated": stated,
|
|
1026
|
+
"stated_count": len(stated),
|
|
1027
|
+
"addressed_count": None,
|
|
1028
|
+
"basis": "criteria imported verbatim from the issue; no "
|
|
1029
|
+
"deterministic checker exists for free-text criteria, so "
|
|
1030
|
+
"this receipt reports what was ASKED, never what was met",
|
|
1031
|
+
},
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
# Time to first useful result. Reuses the number run.sh already writes; we
|
|
1035
|
+
# do not add a second writer and we never synthesize the value.
|
|
1036
|
+
fa = _read_json(os.path.join(state, "first-artifact.json"), default=None)
|
|
1037
|
+
if isinstance(fa, dict):
|
|
1038
|
+
v = fa.get("seconds_to_first_artifact")
|
|
1039
|
+
if isinstance(v, (int, float)) and v >= 0:
|
|
1040
|
+
out["time_to_first_result_sec"] = int(v)
|
|
1041
|
+
|
|
1042
|
+
# Human interventions. Fills the socket trust_trajectory.py:145 already
|
|
1043
|
+
# reads and documents as "no per-run counter persisted today". Absent file
|
|
1044
|
+
# means unmeasured, NOT zero -- claiming a confident 0 would overstate
|
|
1045
|
+
# autonomy, which is the one direction this number must never err in.
|
|
1046
|
+
iv = _read_json(os.path.join(state, "interventions.json"), default=None)
|
|
1047
|
+
if isinstance(iv, dict):
|
|
1048
|
+
n = iv.get("count")
|
|
1049
|
+
if isinstance(n, int) and n >= 0:
|
|
1050
|
+
out["interventions"] = n
|
|
1051
|
+
|
|
1052
|
+
# PR state/URL, written by the consent-gated PR step. "prepared" means a PR
|
|
1053
|
+
# body exists locally and GitHub was NOT mutated.
|
|
1054
|
+
pr = _read_json(os.path.join(state, "pr.json"), default=None)
|
|
1055
|
+
if isinstance(pr, dict) and pr.get("state"):
|
|
1056
|
+
out["pull_request"] = {
|
|
1057
|
+
"state": str(pr.get("state")),
|
|
1058
|
+
"url": str(pr.get("url") or ""),
|
|
1059
|
+
"branch": str(pr.get("branch") or ""),
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
return out
|
|
1063
|
+
|
|
1064
|
+
|
|
993
1065
|
def _collect_spec(loki_dir, target_dir):
|
|
994
1066
|
"""Return spec dict {source, brief}. brief truncated to 600 chars."""
|
|
995
1067
|
prd_path = os.environ.get("PRD_PATH", "").strip()
|
|
@@ -1158,6 +1230,7 @@ def _build_proof(args, loki_dir, target_dir, repo_root):
|
|
|
1158
1230
|
healthcheck = _collect_healthcheck(loki_dir) # Evidence Receipt record-half
|
|
1159
1231
|
evidence_gate = _collect_evidence_gate(loki_dir)
|
|
1160
1232
|
functionality = _collect_functionality(loki_dir) # func axes as HONEST facts
|
|
1233
|
+
journey = _collect_journey(loki_dir) # issue-to-PR record half; {} when N/A
|
|
1161
1234
|
|
|
1162
1235
|
deployed_url = os.environ.get("LOKI_DEPLOYED_URL") or None
|
|
1163
1236
|
|
|
@@ -1238,6 +1311,12 @@ def _build_proof(args, loki_dir, target_dir, repo_root):
|
|
|
1238
1311
|
},
|
|
1239
1312
|
}
|
|
1240
1313
|
|
|
1314
|
+
# Issue-to-PR journey facts, attached ONLY on an issue-mode run so a PRD or
|
|
1315
|
+
# brief run's proof bytes stay exactly as they were. Record half: descriptive
|
|
1316
|
+
# only, never read by _compute_headline / _compute_degraded.
|
|
1317
|
+
if journey:
|
|
1318
|
+
facts["journey"] = journey
|
|
1319
|
+
|
|
1241
1320
|
# ASSESSMENTS: LLM opinions. Explicitly labeled as judgment, NOT proof. A
|
|
1242
1321
|
# green council verdict is an opinion that can be wrong or gamed; it never
|
|
1243
1322
|
# contributes to the deterministic headline.
|
|
@@ -1351,6 +1430,15 @@ def _build_proof(args, loki_dir, target_dir, repo_root):
|
|
|
1351
1430
|
"assessments": assessments,
|
|
1352
1431
|
"honesty": honesty,
|
|
1353
1432
|
}
|
|
1433
|
+
|
|
1434
|
+
# Top-level mirror for the intervention axis. trust_trajectory.py:145 already
|
|
1435
|
+
# reads proof["interventions"] and documents that no writer exists yet; this
|
|
1436
|
+
# is that writer. Mirrored (not moved) for the same back-compat reason the
|
|
1437
|
+
# other flat keys are mirrored. Only ever set when actually measured, so the
|
|
1438
|
+
# axis stays honestly "unavailable" rather than showing a fabricated zero.
|
|
1439
|
+
if isinstance(journey, dict) and isinstance(journey.get("interventions"), int):
|
|
1440
|
+
proof["interventions"] = journey["interventions"]
|
|
1441
|
+
|
|
1354
1442
|
return proof, run_id
|
|
1355
1443
|
|
|
1356
1444
|
|
package/autonomy/lib/proof-pr.sh
CHANGED
|
@@ -206,6 +206,58 @@ def main():
|
|
|
206
206
|
_line("| Gates disabled | " + ", ".join(sorted(_off)) + " |")
|
|
207
207
|
_line("| Base sha | `" + (base_sha or "(none)") + "` |")
|
|
208
208
|
_line("| Head sha | `" + (head_sha or "(none)") + "` |")
|
|
209
|
+
|
|
210
|
+
# Issue-to-PR journey rows. Emitted ONLY on an issue-mode run, and each row
|
|
211
|
+
# ONLY when that fact was actually measured -- an unmeasured number renders
|
|
212
|
+
# no row rather than a zero, the same rule the rest of this receipt follows.
|
|
213
|
+
journey = facts.get("journey") if isinstance(facts.get("journey"), dict) else {}
|
|
214
|
+
if journey:
|
|
215
|
+
jissue = journey.get("issue") if isinstance(journey.get("issue"), dict) else {}
|
|
216
|
+
jref = str(jissue.get("ref") or "").strip()
|
|
217
|
+
jurl = str(jissue.get("url") or "").strip()
|
|
218
|
+
if jref:
|
|
219
|
+
_line("| Issue | " + (("[" + jref + "](" + jurl + ")") if jurl else jref) + " |")
|
|
220
|
+
|
|
221
|
+
ttfr = journey.get("time_to_first_result_sec")
|
|
222
|
+
if isinstance(ttfr, int):
|
|
223
|
+
_line("| Time to first result | " + str(ttfr) + "s |")
|
|
224
|
+
|
|
225
|
+
ivs = journey.get("interventions")
|
|
226
|
+
if isinstance(ivs, int):
|
|
227
|
+
_line("| Human interventions | " + str(ivs) + " |")
|
|
228
|
+
|
|
229
|
+
acc = journey.get("acceptance") if isinstance(journey.get("acceptance"), dict) else {}
|
|
230
|
+
n_stated = acc.get("stated_count")
|
|
231
|
+
if isinstance(n_stated, int):
|
|
232
|
+
# Reported as ASKED, never as met. addressed_count is null by design
|
|
233
|
+
# (no deterministic checker for free-text criteria), so this row must
|
|
234
|
+
# never be phrased as coverage or a pass -- that would be the exact
|
|
235
|
+
# fake-green the headline rules forbid.
|
|
236
|
+
_line("| Acceptance criteria | " + str(n_stated)
|
|
237
|
+
+ " stated in the issue (not machine-verified) |")
|
|
238
|
+
|
|
239
|
+
pr = journey.get("pull_request") if isinstance(journey.get("pull_request"), dict) else {}
|
|
240
|
+
pr_state = str(pr.get("state") or "").strip()
|
|
241
|
+
if pr_state:
|
|
242
|
+
pr_url = str(pr.get("url") or "").strip()
|
|
243
|
+
_line("| Pull request | " + pr_state + ((" -- " + pr_url) if pr_url else "") + " |")
|
|
244
|
+
|
|
245
|
+
# Rollback: derived from the base sha this run started at, so the reader
|
|
246
|
+
# gets a runnable command rather than a promise. Only emitted when the
|
|
247
|
+
# base is actually known.
|
|
248
|
+
if base_sha:
|
|
249
|
+
_line("| Rollback | `git reset --hard " + base_sha + "` |")
|
|
250
|
+
|
|
251
|
+
# Uncertainty: the honest count of checks that did NOT conclusively pass,
|
|
252
|
+
# read from the SAME honesty.degraded[] the headline is computed from, so
|
|
253
|
+
# it can never disagree with the verdict above it.
|
|
254
|
+
_degraded = honesty.get("degraded")
|
|
255
|
+
_degraded = _degraded if isinstance(_degraded, list) else []
|
|
256
|
+
if effective_headline == "VERIFIED" and not _degraded:
|
|
257
|
+
_line("| Uncertainty | none recorded; every check that ran concluded |")
|
|
258
|
+
elif _degraded:
|
|
259
|
+
_line("| Uncertainty | " + str(len(_degraded))
|
|
260
|
+
+ " check(s) not conclusive -- see the list below |")
|
|
209
261
|
_line()
|
|
210
262
|
|
|
211
263
|
# Gaps: when headline != VERIFIED, list honesty.degraded[] verbatim. By
|
package/autonomy/loki
CHANGED
|
@@ -2583,6 +2583,14 @@ cmd_start() {
|
|
|
2583
2583
|
issue_mode_args+=("--pr")
|
|
2584
2584
|
shift
|
|
2585
2585
|
;;
|
|
2586
|
+
--prepare-pr)
|
|
2587
|
+
# Feature 1: the SAME `loki start <issue>` entrypoint carries the
|
|
2588
|
+
# prepare-only flag through to cmd_run. Does not set
|
|
2589
|
+
# issue_create_pr -- preparing is not publishing.
|
|
2590
|
+
issue_use_worktree=true
|
|
2591
|
+
issue_mode_args+=("--prepare-pr")
|
|
2592
|
+
shift
|
|
2593
|
+
;;
|
|
2586
2594
|
--ship)
|
|
2587
2595
|
issue_use_worktree=true
|
|
2588
2596
|
issue_create_pr=true
|
|
@@ -9734,6 +9742,7 @@ cmd_run() {
|
|
|
9734
9742
|
local provider_override=""
|
|
9735
9743
|
local use_worktree=false
|
|
9736
9744
|
local create_pr=false
|
|
9745
|
+
local prepare_pr=false
|
|
9737
9746
|
local auto_merge=false
|
|
9738
9747
|
local run_detached=false
|
|
9739
9748
|
|
|
@@ -9772,6 +9781,8 @@ cmd_run() {
|
|
|
9772
9781
|
echo "Progressive Isolation:"
|
|
9773
9782
|
echo " --worktree, -w Git worktree isolation (separate branch)"
|
|
9774
9783
|
echo " --pr Worktree + auto-create PR (implies --worktree)"
|
|
9784
|
+
echo " --prepare-pr Worktree + write a review-ready PR title/body"
|
|
9785
|
+
echo " locally; never contacts GitHub (implies --worktree)"
|
|
9775
9786
|
echo " --ship Worktree + PR + auto-merge (implies --pr)"
|
|
9776
9787
|
echo " --detach, -d Run in background (implies --worktree)"
|
|
9777
9788
|
echo ""
|
|
@@ -9888,6 +9899,15 @@ cmd_run() {
|
|
|
9888
9899
|
create_pr=true
|
|
9889
9900
|
shift
|
|
9890
9901
|
;;
|
|
9902
|
+
--prepare-pr)
|
|
9903
|
+
# Golden path (feature 9): produce a review-ready PR WITHOUT any
|
|
9904
|
+
# GitHub mutation. Writes the title/body to .loki/state/ and
|
|
9905
|
+
# records state="prepared" for the receipt. Deliberately does not
|
|
9906
|
+
# set create_pr: consent to prepare is not consent to publish.
|
|
9907
|
+
use_worktree=true
|
|
9908
|
+
prepare_pr=true
|
|
9909
|
+
shift
|
|
9910
|
+
;;
|
|
9891
9911
|
--ship)
|
|
9892
9912
|
use_worktree=true
|
|
9893
9913
|
create_pr=true
|
|
@@ -9971,6 +9991,21 @@ cmd_run() {
|
|
|
9971
9991
|
fi
|
|
9972
9992
|
source "$issue_providers_script"
|
|
9973
9993
|
|
|
9994
|
+
# Golden path: also source issue-parser.sh for write_journey_context and
|
|
9995
|
+
# extract_acceptance_criteria. Optional -- a missing file must never break a
|
|
9996
|
+
# run, so this is guarded rather than fatal like the block above.
|
|
9997
|
+
#
|
|
9998
|
+
# issue-parser.sh sets `set -euo pipefail` at the top, which would otherwise
|
|
9999
|
+
# LEAK into cmd_run and abort the CLI on the first benign non-zero. We
|
|
10000
|
+
# snapshot the current options and restore them immediately after sourcing.
|
|
10001
|
+
local _gp_parser_script="$SKILL_DIR/autonomy/issue-parser.sh"
|
|
10002
|
+
if [[ -f "$_gp_parser_script" ]]; then
|
|
10003
|
+
local _gp_saved_opts
|
|
10004
|
+
_gp_saved_opts=$(set +o)
|
|
10005
|
+
source "$_gp_parser_script" 2>/dev/null || true
|
|
10006
|
+
eval "$_gp_saved_opts" 2>/dev/null || true
|
|
10007
|
+
fi
|
|
10008
|
+
|
|
9974
10009
|
# Detect provider
|
|
9975
10010
|
local issue_provider
|
|
9976
10011
|
issue_provider=$(detect_issue_provider "$issue_ref")
|
|
@@ -9996,6 +10031,38 @@ cmd_run() {
|
|
|
9996
10031
|
fi
|
|
9997
10032
|
echo ""
|
|
9998
10033
|
|
|
10034
|
+
# Golden path (features 2 + 3): capture the EXACT acceptance context and the
|
|
10035
|
+
# early machine-readable plan here, at the real `loki start <issue>`
|
|
10036
|
+
# chokepoint, BEFORE any provider call.
|
|
10037
|
+
#
|
|
10038
|
+
# This is deliberately hooked to fetch_issue rather than to
|
|
10039
|
+
# issue-parser.sh's parse_github_issue: `loki start` routes through
|
|
10040
|
+
# fetch_issue (issue-providers.sh:333), while parse_github_issue serves only
|
|
10041
|
+
# the DEPRECATED `loki issue parse|view` commands. Hooking the parser alone
|
|
10042
|
+
# would leave the required path with no context written at all. fetch_issue
|
|
10043
|
+
# is also provider-agnostic, so gitlab/jira/azure get the same artifacts.
|
|
10044
|
+
#
|
|
10045
|
+
# Best-effort: never allowed to break the run.
|
|
10046
|
+
if declare -f write_journey_context >/dev/null 2>&1; then
|
|
10047
|
+
# owner/repo come from the NORMALIZED json's `repo` field ("owner/name"),
|
|
10048
|
+
# not from ISSUE_OWNER/ISSUE_REPO: those globals are re-cleared by any
|
|
10049
|
+
# later parse_issue_reference call, which yielded a "/#42" ref here.
|
|
10050
|
+
# Every provider populates `repo`, so this stays provider-agnostic.
|
|
10051
|
+
local _gp_owner="" _gp_repo="" _gp_acc="" _gp_body="" _gp_repo_ref=""
|
|
10052
|
+
_gp_repo_ref=$(printf '%s' "$issue_json" | python3 -c \
|
|
10053
|
+
"import json,sys; print(json.loads(sys.stdin.read()).get('repo','') or '')" 2>/dev/null || true)
|
|
10054
|
+
_gp_owner="${_gp_repo_ref%%/*}"
|
|
10055
|
+
_gp_repo="${_gp_repo_ref#*/}"
|
|
10056
|
+
[ "$_gp_owner" = "$_gp_repo_ref" ] && _gp_repo=""
|
|
10057
|
+
_gp_body=$(printf '%s' "$issue_json" | python3 -c \
|
|
10058
|
+
"import json,sys; print(json.loads(sys.stdin.read()).get('body','') or '')" 2>/dev/null || true)
|
|
10059
|
+
if declare -f extract_acceptance_criteria >/dev/null 2>&1; then
|
|
10060
|
+
_gp_acc=$(extract_acceptance_criteria "$_gp_body" 2>/dev/null || true)
|
|
10061
|
+
fi
|
|
10062
|
+
write_journey_context "$_gp_owner" "$_gp_repo" "${number:-}" "$title" \
|
|
10063
|
+
"$url" "$_gp_acc" "" "${issue_provider:-github}" "" 2>/dev/null || true
|
|
10064
|
+
fi
|
|
10065
|
+
|
|
9999
10066
|
# Per-session locking (v6.4.0): export session ID so run.sh uses
|
|
10000
10067
|
# per-session PID/lock files instead of the global lock. This allows
|
|
10001
10068
|
# multiple concurrent `loki run` sessions (e.g., loki run 52 -d && loki run 54 -d).
|
|
@@ -10190,7 +10257,71 @@ INNER_SCRIPT_EOF
|
|
|
10190
10257
|
else
|
|
10191
10258
|
echo ""
|
|
10192
10259
|
echo -e "${GREEN}Starting Loki Mode with generated PRD...${NC}"
|
|
10193
|
-
cmd_start
|
|
10260
|
+
# cmd_start EXEC-replaces this process (loki:3490
|
|
10261
|
+
# _loki_new_session_exec), so ANY code after a bare call is unreachable
|
|
10262
|
+
# -- which is why the --pr block below has never run on this path and
|
|
10263
|
+
# why the detached route at ~:10088 reimplements PR creation instead of
|
|
10264
|
+
# sharing it. Verified empirically, not inferred.
|
|
10265
|
+
#
|
|
10266
|
+
# Only --prepare-pr subshells the runner, so the exec replaces the CHILD
|
|
10267
|
+
# and control returns here. The default path keeps the bare call
|
|
10268
|
+
# untouched: subshelling everything would change signal delivery and
|
|
10269
|
+
# Ctrl+C semantics for every existing user, which this slice must not do.
|
|
10270
|
+
if $prepare_pr && ! $create_pr; then
|
|
10271
|
+
( cmd_start "$output_file" ${start_args[@]+"${start_args[@]}"} )
|
|
10272
|
+
else
|
|
10273
|
+
cmd_start "$output_file" ${start_args[@]+"${start_args[@]}"}
|
|
10274
|
+
fi
|
|
10275
|
+
|
|
10276
|
+
# Golden path (feature 9): prepare-only. Builds the SAME review-ready
|
|
10277
|
+
# title/body the --pr path would publish, writes it to .loki/state/, and
|
|
10278
|
+
# records state="prepared" for the receipt -- without ever invoking gh.
|
|
10279
|
+
# Consent to prepare is not consent to publish, so this branch is
|
|
10280
|
+
# deliberately separate from create_pr and never falls through to it.
|
|
10281
|
+
if $prepare_pr && ! $create_pr; then
|
|
10282
|
+
echo ""
|
|
10283
|
+
echo -e "${GREEN}Preparing pull request (no GitHub changes)...${NC}"
|
|
10284
|
+
local _pp_title="${title:-Implementation for issue ${issue_ref}}"
|
|
10285
|
+
local _pp_branch
|
|
10286
|
+
_pp_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
10287
|
+
local _pp_body="Implemented by Loki Mode (autonomous agent)
|
|
10288
|
+
|
|
10289
|
+
Issue: ${issue_ref}
|
|
10290
|
+
Provider: ${issue_provider}
|
|
10291
|
+
|
|
10292
|
+
## Changes
|
|
10293
|
+
$(git log --oneline "main..HEAD" 2>/dev/null || echo "See diff")"
|
|
10294
|
+
|
|
10295
|
+
# Same receipt the published path appends, so a prepared PR body is
|
|
10296
|
+
# byte-for-byte what would be posted. Print-only and honesty-gated.
|
|
10297
|
+
if [ "${LOKI_PROVEN_PR:-1}" != "0" ] && declare -f render_evidence_receipt_md >/dev/null 2>&1; then
|
|
10298
|
+
local _pp_idfile=".loki/state/last-proof-id.txt"
|
|
10299
|
+
if [ -s "$_pp_idfile" ]; then
|
|
10300
|
+
local _pp_rid=""
|
|
10301
|
+
_pp_rid="$(cat "$_pp_idfile" 2>/dev/null || true)"
|
|
10302
|
+
if [ -n "$_pp_rid" ] && [ -f ".loki/proofs/$_pp_rid/proof.json" ]; then
|
|
10303
|
+
local _pp_receipt=""
|
|
10304
|
+
_pp_receipt="$(render_evidence_receipt_md ".loki/proofs/$_pp_rid/proof.json" "" "" 2>/dev/null || true)"
|
|
10305
|
+
[ -n "$_pp_receipt" ] && _pp_body="${_pp_body}
|
|
10306
|
+
|
|
10307
|
+
${_pp_receipt}"
|
|
10308
|
+
fi
|
|
10309
|
+
fi
|
|
10310
|
+
fi
|
|
10311
|
+
|
|
10312
|
+
mkdir -p .loki/state 2>/dev/null || true
|
|
10313
|
+
printf '%s\n' "$_pp_title" > .loki/state/pr-title.txt 2>/dev/null || true
|
|
10314
|
+
printf '%s\n' "$_pp_body" > .loki/state/pr-body.md 2>/dev/null || true
|
|
10315
|
+
if command -v jq >/dev/null 2>&1; then
|
|
10316
|
+
jq -n --arg branch "$_pp_branch" --arg title "$_pp_title" \
|
|
10317
|
+
'{state:"prepared", url:"", branch:$branch, title:$title,
|
|
10318
|
+
body_path:".loki/state/pr-body.md",
|
|
10319
|
+
note:"written locally; GitHub was not contacted"}' \
|
|
10320
|
+
> .loki/state/pr.json 2>/dev/null || true
|
|
10321
|
+
fi
|
|
10322
|
+
echo -e "${GREEN}PR prepared${NC} -- review .loki/state/pr-body.md"
|
|
10323
|
+
echo -e "${DIM}Publish it yourself: gh pr create --title \"\$(cat .loki/state/pr-title.txt)\" --body-file .loki/state/pr-body.md${NC}"
|
|
10324
|
+
fi
|
|
10194
10325
|
|
|
10195
10326
|
# Progressive isolation: create PR
|
|
10196
10327
|
if $create_pr; then
|
|
@@ -10234,9 +10365,22 @@ ${_gh_receipt}"
|
|
|
10234
10365
|
branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
10235
10366
|
if [[ -n "$branch_current" && "$branch_current" != "main" && "$branch_current" != "master" ]]; then
|
|
10236
10367
|
git push origin "$branch_current" 2>/dev/null || true
|
|
10237
|
-
|
|
10238
|
-
|
|
10368
|
+
local _pr_out=""
|
|
10369
|
+
if _pr_out=$(gh pr create --title "$pr_title" --body "$pr_body" --head "$branch_current" 2>/dev/null); then
|
|
10370
|
+
echo -e "${GREEN}PR created${NC}"
|
|
10371
|
+
# Record the real outcome for the receipt. Only
|
|
10372
|
+
# written on ACTUAL success, so a failed create
|
|
10373
|
+
# can never leave a "created" claim behind.
|
|
10374
|
+
mkdir -p .loki/state 2>/dev/null || true
|
|
10375
|
+
if command -v jq >/dev/null 2>&1; then
|
|
10376
|
+
jq -n --arg url "$(printf '%s' "$_pr_out" | grep -Eo 'https://[^[:space:]]+' | head -1)" \
|
|
10377
|
+
--arg branch "$branch_current" \
|
|
10378
|
+
'{state:"created", url:$url, branch:$branch}' \
|
|
10379
|
+
> .loki/state/pr.json 2>/dev/null || true
|
|
10380
|
+
fi
|
|
10381
|
+
else
|
|
10239
10382
|
echo -e "${YELLOW}PR creation failed${NC}"
|
|
10383
|
+
fi
|
|
10240
10384
|
fi
|
|
10241
10385
|
fi
|
|
10242
10386
|
;;
|