loki-mode 9.22.4 → 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 +18 -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 +342 -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/autonomy/quickstart.sh
CHANGED
|
@@ -283,6 +283,72 @@ _qs_template_summary() {
|
|
|
283
283
|
esac
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
+
# _qs_shipped_template_names: print every shipped template basename in stable
|
|
287
|
+
# catalog order. The filesystem is the source of truth: adding or removing a
|
|
288
|
+
# templates/*.md payload changes discovery automatically, while README.md is
|
|
289
|
+
# deliberately excluded because it is gallery documentation, not a PRD.
|
|
290
|
+
_qs_shipped_template_names() {
|
|
291
|
+
local tdir; tdir="$(_qs_templates_dir)"
|
|
292
|
+
local f name
|
|
293
|
+
for f in "$tdir"/*.md; do
|
|
294
|
+
[ -f "$f" ] || continue
|
|
295
|
+
name=$(basename "$f" .md)
|
|
296
|
+
[ "$name" = "README" ] && continue
|
|
297
|
+
printf '%s\n' "$name"
|
|
298
|
+
done | LC_ALL=C sort
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
# _qs_list_templates [json]: provider-free discovery for terminals and local
|
|
302
|
+
# automation. Human and machine output are derived from the same shipped-name
|
|
303
|
+
# stream and the same stable purpose table used by the interactive picker.
|
|
304
|
+
_qs_list_templates() {
|
|
305
|
+
local json_output="${1:-false}"
|
|
306
|
+
local catalog="" count=0 name purpose
|
|
307
|
+
while IFS= read -r name; do
|
|
308
|
+
[ -n "$name" ] || continue
|
|
309
|
+
purpose="$(_qs_template_summary "$name")"
|
|
310
|
+
catalog="${catalog}${name}\t${purpose}\n"
|
|
311
|
+
count=$((count + 1))
|
|
312
|
+
done < <(_qs_shipped_template_names)
|
|
313
|
+
|
|
314
|
+
if [ "$count" -eq 0 ]; then
|
|
315
|
+
printf 'No shipped quickstart templates were found.\n' >&2
|
|
316
|
+
return 2
|
|
317
|
+
fi
|
|
318
|
+
|
|
319
|
+
if [ "$json_output" = true ]; then
|
|
320
|
+
printf '%b' "$catalog" | python3 -c '
|
|
321
|
+
import json
|
|
322
|
+
import sys
|
|
323
|
+
|
|
324
|
+
templates = []
|
|
325
|
+
for raw in sys.stdin:
|
|
326
|
+
name, purpose = raw.rstrip("\n").split("\t", 1)
|
|
327
|
+
templates.append({"name": name, "purpose": purpose})
|
|
328
|
+
json.dump(
|
|
329
|
+
{
|
|
330
|
+
"schema_version": 1,
|
|
331
|
+
"command": "loki quickstart",
|
|
332
|
+
"mode": "list-templates",
|
|
333
|
+
"templates": templates,
|
|
334
|
+
},
|
|
335
|
+
sys.stdout,
|
|
336
|
+
separators=(",", ":"),
|
|
337
|
+
sort_keys=True,
|
|
338
|
+
)
|
|
339
|
+
sys.stdout.write("\n")
|
|
340
|
+
' || return 2
|
|
341
|
+
return 0
|
|
342
|
+
fi
|
|
343
|
+
|
|
344
|
+
printf '%sShipped quickstart templates (%d)%s\n' "$_QS_BOLD" "$count" "$_QS_NC"
|
|
345
|
+
printf '%b' "$catalog" | while IFS=$'\t' read -r name purpose; do
|
|
346
|
+
[ -n "$name" ] || continue
|
|
347
|
+
printf ' %-20s %s\n' "$name" "$purpose"
|
|
348
|
+
done
|
|
349
|
+
return 0
|
|
350
|
+
}
|
|
351
|
+
|
|
286
352
|
# _qs_template_exists <name>: accept only an exact shipped template basename.
|
|
287
353
|
# Keeping validation here (rather than accepting an arbitrary path) prevents
|
|
288
354
|
# --template from becoming a second PRD/file-read surface. The intentionally
|
|
@@ -340,12 +406,21 @@ _qs_selected_provider() {
|
|
|
340
406
|
# non-zero if the estimator gave no result (caller falls back to a no-number
|
|
341
407
|
# confirm, never fabricating a figure).
|
|
342
408
|
_qs_emit_plan() {
|
|
343
|
-
local prd_path="$1" template_name="$2" json_output="${3:-false}" input_kind="${4:-idea}"
|
|
409
|
+
local prd_path="$1" template_name="$2" json_output="${3:-false}" input_kind="${4:-idea}" input_value="${5:-}"
|
|
410
|
+
local source_digest_before="" source_digest_after=""
|
|
411
|
+
if [ "$json_output" = true ] && [ "$input_kind" = "prd" ]; then
|
|
412
|
+
source_digest_before=$(shasum -a 256 "$prd_path" 2>/dev/null | awk '{print $1}') || return 1
|
|
413
|
+
[ -n "$source_digest_before" ] || return 1
|
|
414
|
+
fi
|
|
344
415
|
local plan_json=""
|
|
345
416
|
plan_json=$(show_prd_plan "$prd_path" "true" "false" 2>/dev/null) || plan_json=""
|
|
346
417
|
if [ -z "$plan_json" ]; then
|
|
347
418
|
return 1
|
|
348
419
|
fi
|
|
420
|
+
if [ "$json_output" = true ] && [ "$input_kind" = "prd" ]; then
|
|
421
|
+
source_digest_after=$(shasum -a 256 "$prd_path" 2>/dev/null | awk '{print $1}') || return 1
|
|
422
|
+
[ "$source_digest_before" = "$source_digest_after" ] || return 1
|
|
423
|
+
fi
|
|
349
424
|
local parsed
|
|
350
425
|
parsed=$(printf '%s' "$plan_json" | python3 -c "
|
|
351
426
|
import json, sys
|
|
@@ -381,7 +456,19 @@ import sys
|
|
|
381
456
|
plan = json.load(sys.stdin)
|
|
382
457
|
if not isinstance(plan, dict):
|
|
383
458
|
sys.exit(1)
|
|
384
|
-
template_name, input_kind = sys.argv[1:
|
|
459
|
+
template_name, input_kind, input_value, source_digest = sys.argv[1:5]
|
|
460
|
+
if input_kind == "idea":
|
|
461
|
+
continuation = {
|
|
462
|
+
"kind": "idea",
|
|
463
|
+
"template": template_name,
|
|
464
|
+
"value": input_value,
|
|
465
|
+
}
|
|
466
|
+
else:
|
|
467
|
+
continuation = {
|
|
468
|
+
"kind": "prd",
|
|
469
|
+
"path": input_value,
|
|
470
|
+
"sha256": source_digest,
|
|
471
|
+
}
|
|
385
472
|
payload = {
|
|
386
473
|
"schema_version": 1,
|
|
387
474
|
"command": "loki quickstart",
|
|
@@ -390,9 +477,10 @@ payload = {
|
|
|
390
477
|
"selected_template": template_name if input_kind == "idea" else None,
|
|
391
478
|
"source_name": template_name if input_kind == "prd" else None,
|
|
392
479
|
"plan": plan,
|
|
480
|
+
"continuation": continuation,
|
|
393
481
|
}
|
|
394
482
|
json.dump(payload, sys.stdout, separators=(",", ":"), sort_keys=True)
|
|
395
|
-
' "$template_name" "$input_kind" 2>/dev/null) || json_payload=""
|
|
483
|
+
' "$template_name" "$input_kind" "$input_value" "$source_digest_after" 2>/dev/null) || json_payload=""
|
|
396
484
|
[ -n "$json_payload" ] || return 1
|
|
397
485
|
printf '%s\n' "$json_payload" >&3
|
|
398
486
|
return 0
|
|
@@ -415,6 +503,111 @@ json.dump(payload, sys.stdout, separators=(",", ":"), sort_keys=True)
|
|
|
415
503
|
return 0
|
|
416
504
|
}
|
|
417
505
|
|
|
506
|
+
# _qs_load_preview <path>: validate one bounded schema-v1 preview before any
|
|
507
|
+
# provider, estimator, PRD, or build boundary. Only the continuation fields are
|
|
508
|
+
# handed to the shell. The saved plan is evidence, not execution authority;
|
|
509
|
+
# cmd_quickstart always recomputes and displays the current estimator result.
|
|
510
|
+
_qs_load_preview() {
|
|
511
|
+
local preview_path="$1"
|
|
512
|
+
if [ ! -f "$preview_path" ] || [ ! -r "$preview_path" ] || [ -L "$preview_path" ]; then
|
|
513
|
+
printf 'Preview path is not a readable regular non-symlink file: %s\n' "$preview_path" >&2
|
|
514
|
+
return 2
|
|
515
|
+
fi
|
|
516
|
+
local preview_size
|
|
517
|
+
preview_size=$(wc -c < "$preview_path" 2>/dev/null | tr -d '[:space:]') || return 2
|
|
518
|
+
case "$preview_size" in
|
|
519
|
+
""|*[!0-9]*) return 2;;
|
|
520
|
+
esac
|
|
521
|
+
if [ "$preview_size" -eq 0 ] || [ "$preview_size" -gt 1048576 ]; then
|
|
522
|
+
printf 'Preview JSON must be between 1 byte and 1 MiB.\n' >&2
|
|
523
|
+
return 2
|
|
524
|
+
fi
|
|
525
|
+
|
|
526
|
+
python3 - "$preview_path" <<'PY'
|
|
527
|
+
import base64
|
|
528
|
+
import json
|
|
529
|
+
import os
|
|
530
|
+
import re
|
|
531
|
+
import stat
|
|
532
|
+
import sys
|
|
533
|
+
|
|
534
|
+
path = sys.argv[1]
|
|
535
|
+
try:
|
|
536
|
+
flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)
|
|
537
|
+
descriptor = os.open(path, flags)
|
|
538
|
+
try:
|
|
539
|
+
metadata = os.fstat(descriptor)
|
|
540
|
+
if not stat.S_ISREG(metadata.st_mode) or metadata.st_size < 1 or metadata.st_size > 1048576:
|
|
541
|
+
raise ValueError("unsafe preview")
|
|
542
|
+
raw = os.read(descriptor, 1048577)
|
|
543
|
+
finally:
|
|
544
|
+
os.close(descriptor)
|
|
545
|
+
if len(raw) > 1048576:
|
|
546
|
+
raise ValueError("oversized")
|
|
547
|
+
def reject_duplicate_keys(pairs):
|
|
548
|
+
result = {}
|
|
549
|
+
for key, value in pairs:
|
|
550
|
+
if key in result:
|
|
551
|
+
raise ValueError("duplicate key")
|
|
552
|
+
result[key] = value
|
|
553
|
+
return result
|
|
554
|
+
|
|
555
|
+
payload = json.loads(raw, object_pairs_hook=reject_duplicate_keys)
|
|
556
|
+
except Exception:
|
|
557
|
+
sys.exit(2)
|
|
558
|
+
|
|
559
|
+
if not isinstance(payload, dict):
|
|
560
|
+
sys.exit(2)
|
|
561
|
+
if payload.get("schema_version") != 1:
|
|
562
|
+
sys.exit(2)
|
|
563
|
+
if payload.get("command") != "loki quickstart" or payload.get("mode") != "dry-run":
|
|
564
|
+
sys.exit(2)
|
|
565
|
+
if not isinstance(payload.get("plan"), dict) or not payload["plan"]:
|
|
566
|
+
sys.exit(2)
|
|
567
|
+
|
|
568
|
+
continuation = payload.get("continuation")
|
|
569
|
+
if not isinstance(continuation, dict):
|
|
570
|
+
sys.exit(2)
|
|
571
|
+
kind = continuation.get("kind")
|
|
572
|
+
|
|
573
|
+
def valid_text(value):
|
|
574
|
+
return (
|
|
575
|
+
isinstance(value, str)
|
|
576
|
+
and 0 < len(value) <= 4096
|
|
577
|
+
and not any(ord(char) < 32 or ord(char) == 127 for char in value)
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
if kind == "idea":
|
|
581
|
+
value = continuation.get("value")
|
|
582
|
+
template = continuation.get("template")
|
|
583
|
+
if not valid_text(value):
|
|
584
|
+
sys.exit(2)
|
|
585
|
+
if not isinstance(template, str) or re.fullmatch(r"[a-z0-9-]+", template) is None:
|
|
586
|
+
sys.exit(2)
|
|
587
|
+
if payload.get("input_kind") != "idea":
|
|
588
|
+
sys.exit(2)
|
|
589
|
+
if payload.get("selected_template") != template or payload.get("source_name") is not None:
|
|
590
|
+
sys.exit(2)
|
|
591
|
+
encoded = base64.b64encode(value.encode("utf-8")).decode("ascii")
|
|
592
|
+
print(f"idea|{template}|{encoded}|")
|
|
593
|
+
elif kind == "prd":
|
|
594
|
+
value = continuation.get("path")
|
|
595
|
+
digest = continuation.get("sha256")
|
|
596
|
+
if not valid_text(value):
|
|
597
|
+
sys.exit(2)
|
|
598
|
+
if not isinstance(digest, str) or re.fullmatch(r"[0-9a-f]{64}", digest) is None:
|
|
599
|
+
sys.exit(2)
|
|
600
|
+
if payload.get("input_kind") != "prd":
|
|
601
|
+
sys.exit(2)
|
|
602
|
+
if payload.get("selected_template") is not None or payload.get("source_name") != os.path.basename(value):
|
|
603
|
+
sys.exit(2)
|
|
604
|
+
encoded = base64.b64encode(value.encode("utf-8")).decode("ascii")
|
|
605
|
+
print(f"prd||{encoded}|{digest}")
|
|
606
|
+
else:
|
|
607
|
+
sys.exit(2)
|
|
608
|
+
PY
|
|
609
|
+
}
|
|
610
|
+
|
|
418
611
|
# _qs_help: concise usage for `loki quickstart --help`.
|
|
419
612
|
_qs_help() {
|
|
420
613
|
printf '%sloki quickstart%s - guided first build (setup, idea, template, plan, go)\n' "$_QS_BOLD" "$_QS_NC"
|
|
@@ -432,7 +625,9 @@ _qs_help() {
|
|
|
432
625
|
printf ' --yes, -y Auto-confirm the final build prompt (still shows the plan)\n'
|
|
433
626
|
printf ' --dry-run Preview the selected template and plan; write/start nothing\n'
|
|
434
627
|
printf ' --json With --dry-run, emit one machine-readable JSON object\n'
|
|
628
|
+
printf ' --from-preview F Continue a saved JSON preview; requires explicit --yes\n'
|
|
435
629
|
printf ' --template N Use the exact shipped template N for an IDEA\n'
|
|
630
|
+
printf ' --list-templates List every shipped template and its purpose\n'
|
|
436
631
|
printf ' --help, -h Show this help and exit\n'
|
|
437
632
|
printf '\n'
|
|
438
633
|
printf 'Non-interactive use:\n'
|
|
@@ -441,12 +636,14 @@ _qs_help() {
|
|
|
441
636
|
printf ' Missing either one exits 2 and writes nothing. The top-ranked\n'
|
|
442
637
|
printf ' template is chosen automatically and the plan is still shown.\n'
|
|
443
638
|
printf ' Add --template NAME to choose a shipped template instead.\n'
|
|
639
|
+
printf ' Run with --list-templates (and optional --json) to discover names.\n'
|
|
444
640
|
printf '\n'
|
|
445
641
|
printf 'Zero-spend preview:\n'
|
|
446
642
|
printf ' loki quickstart "a todo app" --dry-run\n'
|
|
447
643
|
printf ' An IDEA (or readable PRD path) is required. No provider is checked,\n'
|
|
448
644
|
printf ' no file is written, and no build is started. Do not combine with --yes.\n'
|
|
449
645
|
printf ' Add --json for versioned JSON only; --json requires --dry-run.\n'
|
|
646
|
+
printf ' Save that JSON, then continue it with --from-preview FILE --yes.\n'
|
|
450
647
|
printf '\n'
|
|
451
648
|
printf 'Steps:\n'
|
|
452
649
|
printf ' 1. Setup Check for an AI provider for execution (skipped in preview)\n'
|
|
@@ -480,6 +677,11 @@ cmd_quickstart() {
|
|
|
480
677
|
local json_output=false
|
|
481
678
|
local template_override=""
|
|
482
679
|
local template_flag_seen=false
|
|
680
|
+
local list_templates=false
|
|
681
|
+
local list_templates_flag_seen=false
|
|
682
|
+
local from_preview=""
|
|
683
|
+
local from_preview_flag_seen=false
|
|
684
|
+
local preview_prd_digest=""
|
|
483
685
|
if _qs_assume_yes; then assume_yes=true; fi
|
|
484
686
|
|
|
485
687
|
# yes_flag tracks EXPLICIT --yes/-y on THIS command's argv, and nothing else.
|
|
@@ -525,6 +727,28 @@ cmd_quickstart() {
|
|
|
525
727
|
template_override="$2"
|
|
526
728
|
shift 2
|
|
527
729
|
;;
|
|
730
|
+
--list-templates)
|
|
731
|
+
if [ "$list_templates_flag_seen" = true ]; then
|
|
732
|
+
printf '%s--list-templates may be specified only once.%s\n' "$_QS_RED" "$_QS_NC" >&2
|
|
733
|
+
exit 2
|
|
734
|
+
fi
|
|
735
|
+
list_templates=true
|
|
736
|
+
list_templates_flag_seen=true
|
|
737
|
+
shift
|
|
738
|
+
;;
|
|
739
|
+
--from-preview)
|
|
740
|
+
if [ "$from_preview_flag_seen" = true ]; then
|
|
741
|
+
printf '%s--from-preview may be specified only once.%s\n' "$_QS_RED" "$_QS_NC" >&2
|
|
742
|
+
exit 2
|
|
743
|
+
fi
|
|
744
|
+
from_preview_flag_seen=true
|
|
745
|
+
if [ $# -lt 2 ] || [ -z "${2:-}" ] || [[ "${2:-}" == --* ]]; then
|
|
746
|
+
printf '%s--from-preview requires a preview JSON path.%s\n' "$_QS_RED" "$_QS_NC" >&2
|
|
747
|
+
exit 2
|
|
748
|
+
fi
|
|
749
|
+
from_preview="$2"
|
|
750
|
+
shift 2
|
|
751
|
+
;;
|
|
528
752
|
--*)
|
|
529
753
|
printf '%sUnknown option: %s%s\n' "$_QS_RED" "$1" "$_QS_NC" >&2
|
|
530
754
|
printf "Run 'loki quickstart --help' for usage.\n" >&2
|
|
@@ -543,6 +767,65 @@ cmd_quickstart() {
|
|
|
543
767
|
esac
|
|
544
768
|
done
|
|
545
769
|
|
|
770
|
+
# Continuation is a standalone execution shape. Explicit argv consent is
|
|
771
|
+
# mandatory, and no caller-supplied input or selector may compete with the
|
|
772
|
+
# reviewed preview. Validation happens before every provider, estimator,
|
|
773
|
+
# PRD, and build boundary. Accepted values then rejoin the existing path.
|
|
774
|
+
if [ "$from_preview_flag_seen" = true ]; then
|
|
775
|
+
if [ -n "$positional" ] || [ "$yes_flag" != true ] || [ "$dry_run" = true ] || [ "$json_output" = true ] || [ "$template_flag_seen" = true ] || [ "$list_templates" = true ]; then
|
|
776
|
+
printf '%s--from-preview accepts only a preview JSON path and explicit --yes.%s\n' "$_QS_RED" "$_QS_NC" >&2
|
|
777
|
+
exit 2
|
|
778
|
+
fi
|
|
779
|
+
local preview_fields="" preview_kind="" preview_template="" preview_encoded="" preview_digest="" preview_value=""
|
|
780
|
+
preview_fields=$(_qs_load_preview "$from_preview") || {
|
|
781
|
+
printf 'Preview JSON is malformed or incompatible.\n' >&2
|
|
782
|
+
exit 2
|
|
783
|
+
}
|
|
784
|
+
IFS='|' read -r preview_kind preview_template preview_encoded preview_digest <<< "$preview_fields"
|
|
785
|
+
preview_value=$(python3 -c 'import base64,sys; sys.stdout.buffer.write(base64.b64decode(sys.argv[1], validate=True))' "$preview_encoded" 2>/dev/null) || {
|
|
786
|
+
printf 'Preview JSON continuation is invalid.\n' >&2
|
|
787
|
+
exit 2
|
|
788
|
+
}
|
|
789
|
+
[ -n "$preview_value" ] || {
|
|
790
|
+
printf 'Preview JSON continuation is empty.\n' >&2
|
|
791
|
+
exit 2
|
|
792
|
+
}
|
|
793
|
+
if [ "$preview_kind" = "idea" ]; then
|
|
794
|
+
positional="$preview_value"
|
|
795
|
+
template_override="$preview_template"
|
|
796
|
+
template_flag_seen=true
|
|
797
|
+
elif [ "$preview_kind" = "prd" ]; then
|
|
798
|
+
positional="$preview_value"
|
|
799
|
+
if [ ! -f "$positional" ] || [ ! -r "$positional" ] || [ -L "$positional" ]; then
|
|
800
|
+
printf 'Preview PRD is not a readable regular non-symlink file: %s\n' "$positional" >&2
|
|
801
|
+
exit 2
|
|
802
|
+
fi
|
|
803
|
+
local current_digest=""
|
|
804
|
+
current_digest=$(shasum -a 256 "$positional" 2>/dev/null | awk '{print $1}') || current_digest=""
|
|
805
|
+
if [ -z "$current_digest" ] || [ "$current_digest" != "$preview_digest" ]; then
|
|
806
|
+
printf 'Preview PRD has changed since the saved preview; run --dry-run --json again.\n' >&2
|
|
807
|
+
exit 2
|
|
808
|
+
fi
|
|
809
|
+
preview_prd_digest="$preview_digest"
|
|
810
|
+
else
|
|
811
|
+
printf 'Preview JSON continuation kind is unsupported.\n' >&2
|
|
812
|
+
exit 2
|
|
813
|
+
fi
|
|
814
|
+
fi
|
|
815
|
+
|
|
816
|
+
# Discovery is a standalone read-only command shape. Refuse input and every
|
|
817
|
+
# execution/preview selector rather than guessing intent; --json is its only
|
|
818
|
+
# compatible modifier. This return precedes terminal, provider, estimator,
|
|
819
|
+
# consent, PRD, and build boundaries.
|
|
820
|
+
if [ "$list_templates" = true ]; then
|
|
821
|
+
if [ -n "$positional" ] || [ "$yes_flag" = true ] || [ "$dry_run" = true ] || [ "$template_flag_seen" = true ]; then
|
|
822
|
+
printf '%s--list-templates accepts only the optional --json flag.%s\n' "$_QS_RED" "$_QS_NC" >&2
|
|
823
|
+
exit 2
|
|
824
|
+
fi
|
|
825
|
+
_qs_list_templates "$json_output"
|
|
826
|
+
return $?
|
|
827
|
+
fi
|
|
828
|
+
|
|
546
829
|
# A preview is an explicit no-execution request. Reject simultaneous build
|
|
547
830
|
# consent instead of guessing which instruction wins. This check precedes
|
|
548
831
|
# provider discovery, estimation, and every write.
|
|
@@ -791,8 +1074,20 @@ cmd_quickstart() {
|
|
|
791
1074
|
# The non-interactive path shows it before execution rather than before a
|
|
792
1075
|
# prompt: the operator still gets the honest quote in the transcript, and it
|
|
793
1076
|
# is still the figure cmd_start runs with, so the quote equals the charge.
|
|
1077
|
+
if [ -n "$preview_prd_digest" ]; then
|
|
1078
|
+
local pre_estimate_digest=""
|
|
1079
|
+
if [ ! -f "$prd_source" ] || [ ! -r "$prd_source" ] || [ -L "$prd_source" ]; then
|
|
1080
|
+
printf 'Preview PRD changed before estimation; run --dry-run --json again.\n' >&2
|
|
1081
|
+
return 2
|
|
1082
|
+
fi
|
|
1083
|
+
pre_estimate_digest=$(shasum -a 256 "$prd_source" 2>/dev/null | awk '{print $1}') || pre_estimate_digest=""
|
|
1084
|
+
if [ "$pre_estimate_digest" != "$preview_prd_digest" ]; then
|
|
1085
|
+
printf 'Preview PRD changed before estimation; run --dry-run --json again.\n' >&2
|
|
1086
|
+
return 2
|
|
1087
|
+
fi
|
|
1088
|
+
fi
|
|
794
1089
|
local estimate_ok=true
|
|
795
|
-
if ! _qs_emit_plan "$prd_source" "$template_name" "$json_output" "$input_kind"; then
|
|
1090
|
+
if ! _qs_emit_plan "$prd_source" "$template_name" "$json_output" "$input_kind" "$positional"; then
|
|
796
1091
|
estimate_ok=false
|
|
797
1092
|
# No honest number available. Interactively this flips the confirm to
|
|
798
1093
|
# default-NO below. Non-interactively there is no one to review the
|
|
@@ -812,6 +1107,19 @@ cmd_quickstart() {
|
|
|
812
1107
|
fi
|
|
813
1108
|
fi
|
|
814
1109
|
|
|
1110
|
+
if [ -n "$preview_prd_digest" ]; then
|
|
1111
|
+
local post_estimate_digest=""
|
|
1112
|
+
if [ ! -f "$prd_source" ] || [ ! -r "$prd_source" ] || [ -L "$prd_source" ]; then
|
|
1113
|
+
printf 'Preview PRD changed during estimation; started no build.\n' >&2
|
|
1114
|
+
return 2
|
|
1115
|
+
fi
|
|
1116
|
+
post_estimate_digest=$(shasum -a 256 "$prd_source" 2>/dev/null | awk '{print $1}') || post_estimate_digest=""
|
|
1117
|
+
if [ "$post_estimate_digest" != "$preview_prd_digest" ]; then
|
|
1118
|
+
printf 'Preview PRD changed during estimation; started no build.\n' >&2
|
|
1119
|
+
return 2
|
|
1120
|
+
fi
|
|
1121
|
+
fi
|
|
1122
|
+
|
|
815
1123
|
# The preview terminal boundary is intentionally immediately after the
|
|
816
1124
|
# shared estimator. Reaching it proves the same template and plan were
|
|
817
1125
|
# rendered, while returning here makes the confirm, PRD copy, and cmd_start
|
|
@@ -876,7 +1184,36 @@ cmd_quickstart() {
|
|
|
876
1184
|
done
|
|
877
1185
|
fi
|
|
878
1186
|
fi
|
|
879
|
-
if
|
|
1187
|
+
if [ "$from_preview_flag_seen" = true ]; then
|
|
1188
|
+
# A reviewed continuation must publish exactly the bytes that were
|
|
1189
|
+
# previewed, and must never win a race by overwriting another file.
|
|
1190
|
+
# Stage in the destination directory, verify the staged bytes, then
|
|
1191
|
+
# claim the final name with an atomic hard link (which fails if another
|
|
1192
|
+
# process created it after the suffix walk). Removing the private stage
|
|
1193
|
+
# leaves the linked final file intact.
|
|
1194
|
+
local preview_stage="" preview_stage_digest=""
|
|
1195
|
+
preview_stage=$(mktemp "./.loki-quickstart-prd.XXXXXX") || {
|
|
1196
|
+
printf '%sCould not stage the reviewed PRD in the current directory.%s\n' "$_QS_RED" "$_QS_NC" >&2
|
|
1197
|
+
return 2
|
|
1198
|
+
}
|
|
1199
|
+
if ! cp "$prd_source" "$preview_stage" 2>/dev/null; then
|
|
1200
|
+
rm -f "$preview_stage"
|
|
1201
|
+
printf '%sCould not stage the reviewed PRD in the current directory.%s\n' "$_QS_RED" "$_QS_NC" >&2
|
|
1202
|
+
return 2
|
|
1203
|
+
fi
|
|
1204
|
+
preview_stage_digest=$(shasum -a 256 "$preview_stage" 2>/dev/null | awk '{print $1}') || preview_stage_digest=""
|
|
1205
|
+
if [ -z "$preview_stage_digest" ] || { [ -n "$preview_prd_digest" ] && [ "$preview_stage_digest" != "$preview_prd_digest" ]; }; then
|
|
1206
|
+
rm -f "$preview_stage"
|
|
1207
|
+
printf '%sReviewed PRD changed before publish; started no build.%s\n' "$_QS_RED" "$_QS_NC" >&2
|
|
1208
|
+
return 2
|
|
1209
|
+
fi
|
|
1210
|
+
if ! ln "$preview_stage" "$target" 2>/dev/null; then
|
|
1211
|
+
rm -f "$preview_stage"
|
|
1212
|
+
printf '%sPRD destination changed before publish; started no build.%s\n' "$_QS_RED" "$_QS_NC" >&2
|
|
1213
|
+
return 2
|
|
1214
|
+
fi
|
|
1215
|
+
rm -f "$preview_stage"
|
|
1216
|
+
elif ! cp "$prd_source" "$target" 2>/dev/null; then
|
|
880
1217
|
printf '%sCould not write the PRD to the current directory. Try a writable directory.%s\n' "$_QS_RED" "$_QS_NC" >&2
|
|
881
1218
|
exit 1
|
|
882
1219
|
fi
|
package/autonomy/run.sh
CHANGED
|
@@ -25125,6 +25125,32 @@ handle_pause() {
|
|
|
25125
25125
|
PAUSED=true
|
|
25126
25126
|
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
25127
25127
|
|
|
25128
|
+
# Human-intervention counter for the Evidence Receipt.
|
|
25129
|
+
#
|
|
25130
|
+
# Counted HERE because this is the single point where a run actually blocks
|
|
25131
|
+
# waiting on a person -- every pause path funnels through handle_pause, and
|
|
25132
|
+
# the _PAUSE_IN_PROGRESS guard above means one blocking pause counts once.
|
|
25133
|
+
# Counting at the PAUSE-file write sites instead would over-count
|
|
25134
|
+
# auto-cleared perpetual-mode pauses, which no human ever saw.
|
|
25135
|
+
#
|
|
25136
|
+
# proof-generator reads this; when the file is absent the receipt renders NO
|
|
25137
|
+
# interventions row rather than claiming zero. Best-effort: never fail a run.
|
|
25138
|
+
if [ -n "${loki_dir}" ]; then
|
|
25139
|
+
mkdir -p "$loki_dir/state" 2>/dev/null || true
|
|
25140
|
+
local _iv_file="$loki_dir/state/interventions.json"
|
|
25141
|
+
local _iv_prev=0
|
|
25142
|
+
if [ -f "$_iv_file" ]; then
|
|
25143
|
+
_iv_prev=$(python3 -c "import json,sys
|
|
25144
|
+
try:
|
|
25145
|
+
v=json.load(open(sys.argv[1])).get('count')
|
|
25146
|
+
print(int(v) if isinstance(v,int) and v>=0 else 0)
|
|
25147
|
+
except Exception:
|
|
25148
|
+
print(0)" "$_iv_file" 2>/dev/null || echo 0)
|
|
25149
|
+
fi
|
|
25150
|
+
printf '{"count":%s,"basis":"blocking pauses that waited on a human"}\n' \
|
|
25151
|
+
"$(( _iv_prev + 1 ))" > "$_iv_file" 2>/dev/null || true
|
|
25152
|
+
fi
|
|
25153
|
+
|
|
25128
25154
|
# Save state before pausing so it persists across potential crashes
|
|
25129
25155
|
save_state ${RETRY_COUNT:-0} "paused" 0
|
|
25130
25156
|
|