loki-mode 7.129.5 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +81 -5
  2. package/SKILL.md +66 -5
  3. package/VERSION +1 -1
  4. package/autonomy/app-runner.sh +37 -0
  5. package/autonomy/completion-council.sh +862 -28
  6. package/autonomy/council-v2.sh +39 -0
  7. package/autonomy/crash.sh +3 -2
  8. package/autonomy/grill.sh +42 -0
  9. package/autonomy/hooks/validate-bash.sh +301 -4
  10. package/autonomy/lib/cockpit-render.sh +8 -2
  11. package/autonomy/lib/cr-rematerialize.py +101 -4
  12. package/autonomy/lib/deadline.py +957 -0
  13. package/autonomy/lib/dependency-setup.sh +205 -0
  14. package/autonomy/lib/done-recognition.sh +45 -0
  15. package/autonomy/lib/efficiency_cost.py +13 -6
  16. package/autonomy/lib/no_mock_scan.py +793 -0
  17. package/autonomy/lib/prd-enrich.sh +42 -0
  18. package/autonomy/lib/proof-analytics-props.py +69 -0
  19. package/autonomy/lib/proof-generator.py +282 -95
  20. package/autonomy/lib/proof-template.html +15 -12
  21. package/autonomy/lib/proof-verify.py +151 -102
  22. package/autonomy/lib/requirements_contract.py +848 -0
  23. package/autonomy/lib/sdk-mode.sh +103 -0
  24. package/autonomy/lib/secret-scan.sh +139 -0
  25. package/autonomy/lib/spec-expand.sh +208 -0
  26. package/autonomy/lib/tree_digest.py +266 -0
  27. package/autonomy/lib/voter-agents.sh +58 -8
  28. package/autonomy/lib/workspace_diff.py +124 -0
  29. package/autonomy/loki +189 -17
  30. package/autonomy/playwright-verify.sh +501 -0
  31. package/autonomy/prd-checklist.sh +17 -8
  32. package/autonomy/provider-offer.sh +111 -0
  33. package/autonomy/run.sh +4417 -676
  34. package/autonomy/sandbox.sh +42 -0
  35. package/autonomy/spec-interrogation.sh +148 -5
  36. package/autonomy/spec.sh +118 -1
  37. package/autonomy/telemetry.sh +133 -7
  38. package/autonomy/verify.sh +107 -0
  39. package/bin/loki +110 -3
  40. package/completions/_loki +10 -0
  41. package/completions/loki.bash +1 -1
  42. package/dashboard/__init__.py +1 -1
  43. package/dashboard/audit.py +96 -7
  44. package/dashboard/build_supervisor.py +1619 -0
  45. package/dashboard/control.py +8 -0
  46. package/dashboard/prompt_optimizer.py +7 -0
  47. package/dashboard/server.py +577 -22
  48. package/dashboard/static/trust.html +1 -1
  49. package/docs/ARCHITECTURE-OVERVIEW.md +207 -0
  50. package/docs/AUTONOMI-ECOSYSTEM.md +107 -0
  51. package/docs/INSTALLATION.md +19 -2
  52. package/docs/MODEL-EQUIVALENCE-HARNESS-PLAN.md +70 -0
  53. package/docs/PLANS-INDEX.md +33 -0
  54. package/docs/PRIVACY.md +29 -1
  55. package/docs/SIGNED-RECEIPTS.md +102 -0
  56. package/docs/SONNET5-DEFAULT-PLAN.md +1 -1
  57. package/docs/V8-ACCEPTANCE-TRIAGE-2026-07-24.md +114 -0
  58. package/docs/V8-AGENT-SDK-PLAN.md +692 -0
  59. package/docs/V8-COMPLEXITY-AUDIT-2026-07-24.md +45 -0
  60. package/docs/V8-MAJOR-RELEASE-PLAN.md +137 -0
  61. package/docs/V8-OVERNIGHT-PLAN-2026-07-25.md +82 -0
  62. package/docs/V8-RUNTIME-TRUTH-2026-07-25.md +232 -0
  63. package/docs/V8-SDK-RESEARCH-RAW.md +129 -0
  64. package/docs/alternative-installations.md +4 -4
  65. package/loki-ts/dist/loki.js +674 -285
  66. package/loki-ts/package.json +2 -0
  67. package/mcp/__init__.py +1 -1
  68. package/package.json +7 -6
  69. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  70. package/providers/claude.sh +75 -0
  71. package/providers/codex.sh +85 -13
  72. package/references/sdk-mode.md +106 -0
  73. package/skills/model-selection.md +1 -1
  74. package/skills/quality-gates.md +22 -0
@@ -20,9 +20,8 @@
20
20
  the literal token anywhere else (this comment deliberately spells it out
21
21
  instead of repeating it, and the renderer detects an un-substituted
22
22
  blob structurally rather than by string-matching the token).
23
- 4. No other token substitution is required. All rendering happens client
24
- side from the parsed blob. This keeps Bun and bash routes at exact
25
- parity (one string replace, one template).
23
+ 4. The generator also substitutes the receipt title, social description,
24
+ and share-button setting before embedding the parsed blob.
26
25
 
27
26
  The renderer below is defensive: every nullable / optional schema field is
28
27
  guarded (deployed_url, public_url, diffs, council.enabled false, empty
@@ -42,10 +41,10 @@
42
41
  <head>
43
42
  <meta charset="utf-8">
44
43
  <meta name="viewport" content="width=device-width, initial-scale=1">
45
- <title>Loki Mode - Proof of Run</title>
44
+ <title>__PROOF_RECEIPT_TITLE__ - Loki Mode Evidence Receipt</title>
46
45
  <!-- Static text-only social meta. No og:image (would break self-containment). -->
47
46
  <meta property="og:type" content="website">
48
- <meta property="og:title" content="Built and verified autonomously by Loki Mode">
47
+ <meta property="og:title" content="__PROOF_RECEIPT_TITLE__ - Loki Mode Evidence Receipt">
49
48
  <!-- og:description and twitter:description are filled at generation time with
50
49
  the real measured cost + files-changed + council ratio (cost-free variant
51
50
  when cost was not collected). The generator replaces the og token below in
@@ -589,7 +588,7 @@
589
588
  function honestyVariant(headline) {
590
589
  var h = String(headline || "").toUpperCase();
591
590
  if (h === "VERIFIED") return { cls: "is-verified", mark: "OK",
592
- sub: "Tests verified, a real diff exists, and nothing was left unverified. Every claim below is deterministic and re-checkable." };
591
+ sub: "The recorded tests passed and this receipt contains a non-empty source change. Review the evidence below for the exact scope." };
593
592
  if (h.indexOf("GAPS") !== -1) return { cls: "is-gaps", mark: "!",
594
593
  sub: "Some evidence is verified, but the items listed below were not. A gap is never hidden: read them before trusting this run." };
595
594
  return { cls: "is-failed", mark: "X",
@@ -776,7 +775,7 @@
776
775
  // fmtUsd: a skeptic seeing a zero-dollar total assumes the artifact is fake.
777
776
  var usdRaw = g(p, "cost.usd", null);
778
777
  var fc = num(g(p, "files_changed.count", 0));
779
- var parts = ["Built and verified autonomously"];
778
+ var parts = [__PROOF_RECEIPT_TITLE_JSON__];
780
779
  if (usdRaw !== null) parts.push(fmtUsd(usdRaw));
781
780
  else parts.push("cost not recorded");
782
781
  parts.push(fc + " file" + (fc === 1 ? "" : "s") + " changed");
@@ -929,11 +928,15 @@
929
928
  function renderCost(p) {
930
929
  var card = document.getElementById("costCard");
931
930
  var usdRaw = g(p, "cost.usd", null);
931
+ function tokenValue(path) {
932
+ var raw = g(p, path, null);
933
+ return raw === null ? "not recorded" : fmtInt(raw);
934
+ }
932
935
  var cells = [
933
- ["Input tokens", fmtInt(g(p, "cost.input_tokens", 0))],
934
- ["Output tokens", fmtInt(g(p, "cost.output_tokens", 0))],
935
- ["Cache read", fmtInt(g(p, "cost.cache_read_tokens", 0))],
936
- ["Cache write", fmtInt(g(p, "cost.cache_creation_tokens", 0))]
936
+ ["Input tokens", tokenValue("cost.input_tokens")],
937
+ ["Output tokens", tokenValue("cost.output_tokens")],
938
+ ["Cache read", tokenValue("cost.cache_read_tokens")],
939
+ ["Cache write", tokenValue("cost.cache_creation_tokens")]
937
940
  ];
938
941
  var grid = "";
939
942
  for (var i = 0; i < cells.length; i++) {
@@ -1249,7 +1252,7 @@
1249
1252
  function shareHook() {
1250
1253
  var m = document.querySelector('meta[property="og:description"]');
1251
1254
  var c = m ? (m.getAttribute("content") || "") : "";
1252
- return c || "Built and verified autonomously by Loki Mode";
1255
+ return c || (__PROOF_RECEIPT_TITLE_JSON__ + " - Loki Mode Evidence Receipt");
1253
1256
  }
1254
1257
  document.addEventListener("click", function (e) {
1255
1258
  var t = e.target && e.target.closest ? e.target.closest("[data-share]") : null;
@@ -30,10 +30,11 @@ Three checks (mirrors dashboard/audit.py verify-CLI style):
30
30
  ensure_ascii setting), sha256, compare to the recorded verification.hash.
31
31
  Any mismatch means the JSON was edited after signing.
32
32
 
33
- 2. DRIFT CHECK (diff_drift): from the recorded git base sha, re-run
34
- `git diff --shortstat <base> <head>` in the repo and compare the file /
35
- insertion / deletion counts (and diff_sha256 when present) to what the
36
- receipt recorded. A mismatch means the repo no longer matches the receipt.
33
+ 2. DRIFT CHECK (diff_drift): from the recorded git base sha, re-derive the
34
+ final committed, staged, unstaged, deleted, and untracked workspace diff.
35
+ Compare its stat and diff_sha256 to the receipt. When tree_sha256 is
36
+ present, also bind exact final file content so equal line counts cannot
37
+ hide content drift.
37
38
 
38
39
  3. GPG (gpg_ok): if a detached signature is present and gpg is available,
39
40
  verify it over the canonical bytes. Otherwise "n/a".
@@ -67,6 +68,13 @@ import os
67
68
  import subprocess
68
69
  import sys
69
70
 
71
+ _HERE = os.path.dirname(os.path.abspath(__file__))
72
+ if _HERE not in sys.path:
73
+ sys.path.insert(0, _HERE)
74
+
75
+ from tree_digest import compute_tree_digest
76
+ from workspace_diff import collect_workspace_diff
77
+
70
78
 
71
79
  # ---------------------------------------------------------------------------
72
80
  # canonicalization (MUST match proof-generator._canonical exactly)
@@ -113,42 +121,13 @@ def _rev_resolvable(repo_dir, ref):
113
121
 
114
122
 
115
123
  def _numstat(repo_dir, base, head):
116
- """Return {count, insertions, deletions, files} for
117
- `git diff --numstat base head`, or None if the diff could not be computed.
118
-
119
- The `files` list mirrors proof-generator._git_diffstat EXACTLY (path /
120
- insertions / deletions / status) so that hashing the canonical stat here
121
- reproduces the generator's diff_sha256. (Earlier this hashed the full patch
122
- text while the generator hashed the stat object -- so every untampered v1.1
123
- proof falsely reported drift. BUG-DIFFSHA.)"""
124
- raw = _git(repo_dir, ["diff", "--numstat", str(base), str(head)])
125
- if raw is None:
126
- return None
127
- files = []
128
- ins_total = 0
129
- del_total = 0
130
- for line in raw.splitlines():
131
- parts = line.split("\t")
132
- if len(parts) < 3:
133
- continue
134
- ins_s, del_s, path = parts[0], parts[1], parts[2]
135
- # binary files show "-" for both columns; count the file, add 0.
136
- ins = 0 if ins_s == "-" else _to_int(ins_s)
137
- dele = 0 if del_s == "-" else _to_int(del_s)
138
- ins_total += ins
139
- del_total += dele
140
- files.append({
141
- "path": path,
142
- "insertions": ins,
143
- "deletions": dele,
144
- "status": "binary" if ins_s == "-" else "modified",
145
- })
146
- return {
147
- "count": len(files),
148
- "insertions": ins_total,
149
- "deletions": del_total,
150
- "files": files,
151
- }
124
+ """Return final-worktree stats relative to ``base``.
125
+
126
+ ``head`` remains in the signature for installed-layout compatibility. The
127
+ live working tree is the proof target, not only its current commit.
128
+ """
129
+ stat, _ = collect_workspace_diff(repo_dir, str(base), False)
130
+ return stat
152
131
 
153
132
 
154
133
  def _diff_sha256_from_stat(files_changed):
@@ -205,12 +184,42 @@ def _compute_headline(facts, degraded):
205
184
 
206
185
  sec = facts.get("security") or {}
207
186
  sec_high = bool(sec.get("ran") and (sec.get("high_active") or 0) > 0)
187
+ fn = facts.get("functional") or {}
188
+ fn_failed = bool(
189
+ os.environ.get("LOKI_FV_GATE") == "1"
190
+ and fn.get("ran")
191
+ and fn.get("functional_status") == "failed"
192
+ )
193
+ execution = facts.get("execution") or {}
194
+ execution_outcome = str(execution.get("outcome") or "").lower()
195
+ failed_run_statuses = {
196
+ "failed",
197
+ "force_stopped",
198
+ "inconclusive_spec_contradiction",
199
+ "interrupted",
200
+ "max_iterations_reached",
201
+ "max_retries_exceeded",
202
+ "paused",
203
+ "policy_blocked",
204
+ "provider_deadline_partial_mutation",
205
+ }
206
+ execution_failed = bool(
207
+ execution.get("terminated")
208
+ or execution.get("exit_code") not in (None, 0)
209
+ or str(execution.get("run_status") or "").lower() in failed_run_statuses
210
+ or (
211
+ execution_outcome
212
+ and execution_outcome not in ("complete", "completed", "success")
213
+ )
214
+ )
208
215
  any_failed = (
209
- tests.get("status") == "failed"
216
+ execution_failed
217
+ or tests.get("status") == "failed"
210
218
  or build.get("status") == "failed"
211
219
  or any(g.get("status") == "failed"
212
220
  for g in (facts.get("quality_gates") or []))
213
221
  or sec_high
222
+ or fn_failed
214
223
  )
215
224
  if any_failed:
216
225
  return "NOT VERIFIED"
@@ -294,6 +303,16 @@ def _recorded_diff_sha256(proof):
294
303
  return None
295
304
 
296
305
 
306
+ def _recorded_tree_sha256(proof):
307
+ facts = proof.get("facts")
308
+ if isinstance(facts, dict):
309
+ git = facts.get("git")
310
+ if isinstance(git, dict) and git.get("tree_sha256"):
311
+ return str(git["tree_sha256"])
312
+ value = proof.get("tree_sha256")
313
+ return str(value) if value else None
314
+
315
+
297
316
  def _recorded_degraded(proof):
298
317
  """Return the honesty.degraded list the generator disclosed, or []."""
299
318
  honesty = proof.get("honesty")
@@ -404,6 +423,75 @@ def _load_proof(proof_path):
404
423
  return data
405
424
 
406
425
 
426
+ def verify_integrity(proof):
427
+ """Verify the receipt hash, signature, and recorded headline.
428
+
429
+ This is the canonical in-memory integrity check shared by the CLI verifier
430
+ and supervised execution binding. Repository drift is intentionally left to
431
+ verify(), whose caller supplies the repository path.
432
+ """
433
+ result = {
434
+ "hash_ok": False,
435
+ "gpg_ok": "n/a",
436
+ "generator_trusted": True,
437
+ "headline_consistent": None,
438
+ "degraded": _recorded_degraded(proof) if isinstance(proof, dict) else [],
439
+ "reason": "",
440
+ "ok": False,
441
+ }
442
+ if not isinstance(proof, dict):
443
+ result["reason"] = "proof root is not a JSON object"
444
+ return result
445
+
446
+ verification = proof.get("verification")
447
+ if not isinstance(verification, dict) or not verification.get("hash"):
448
+ result["reason"] = "no verification.hash recorded; cannot prove integrity"
449
+ return result
450
+
451
+ unsigned = dict(proof)
452
+ unsigned.pop("verification", None)
453
+ canonical_bytes = _canonical(unsigned).encode("utf-8")
454
+ recorded_hash = str(verification.get("hash"))
455
+ recomputed = hashlib.sha256(canonical_bytes).hexdigest()
456
+ result["hash_ok"] = recomputed == recorded_hash
457
+ if not result["hash_ok"]:
458
+ result["reason"] = (
459
+ "integrity hash mismatch (proof.json was edited after signing)"
460
+ )
461
+
462
+ result["gpg_ok"] = _verify_gpg(
463
+ canonical_bytes, verification.get("gpg_signature")
464
+ )
465
+ result["generator_trusted"] = result["gpg_ok"] is not True
466
+
467
+ recorded_headline = _recorded_headline(proof)
468
+ facts = proof.get("facts")
469
+ if recorded_headline is not None and isinstance(facts, dict):
470
+ derived = _compute_headline(facts, _recorded_degraded_raw(proof))
471
+ result["headline_consistent"] = derived == recorded_headline
472
+ if not result["headline_consistent"] and not result["reason"]:
473
+ result["reason"] = (
474
+ "honesty.headline (%r) disagrees with the headline re-derived "
475
+ "from the recorded facts (%r); the headline was edited to "
476
+ "misrepresent the facts" % (recorded_headline, derived)
477
+ )
478
+
479
+ result["ok"] = bool(
480
+ result["hash_ok"]
481
+ and result["gpg_ok"] in (True, "n/a")
482
+ and result["headline_consistent"] is not False
483
+ )
484
+ if result["ok"]:
485
+ result["reason"] = ""
486
+ elif not result["reason"]:
487
+ result["reason"] = (
488
+ "gpg signature verification failed"
489
+ if result["gpg_ok"] is False
490
+ else "verification failed"
491
+ )
492
+ return result
493
+
494
+
407
495
  def verify(proof_path, repo_dir="."):
408
496
  """Re-verify a proof.json against the repo.
409
497
 
@@ -444,47 +532,21 @@ def verify(proof_path, repo_dir="."):
444
532
  """
445
533
  proof = _load_proof(proof_path)
446
534
 
535
+ integrity = verify_integrity(proof)
447
536
  result = {
448
- "hash_ok": False,
537
+ **integrity,
449
538
  "diff_drift": None,
450
539
  "diff_recheck": {"recorded": None, "current": None},
451
- "gpg_ok": "n/a",
452
- "generator_trusted": True,
453
- "headline_consistent": None,
454
- "degraded": _recorded_degraded(proof),
455
- "reason": "",
540
+ "tree_drift": None,
541
+ "tree_recheck": {"recorded": None, "current": None},
456
542
  "ok": False,
457
543
  }
458
544
 
459
- # ----- 1. TAMPER CHECK -------------------------------------------------
460
- verification = proof.get("verification")
461
- if not isinstance(verification, dict) or not verification.get("hash"):
462
- result["hash_ok"] = False
463
- result["reason"] = "no verification.hash recorded; cannot prove integrity"
545
+ if not integrity["hash_ok"] and not (
546
+ isinstance(proof.get("verification"), dict)
547
+ and proof["verification"].get("hash")
548
+ ):
464
549
  return result
465
- recorded_hash = str(verification.get("hash"))
466
-
467
- # Recompute over the canonical form with verification REMOVED, exactly as
468
- # the generator hashed it (hash computed before verification was attached).
469
- unsigned = dict(proof)
470
- unsigned.pop("verification", None)
471
- canonical_str = _canonical(unsigned)
472
- canonical_bytes = canonical_str.encode("utf-8")
473
- recomputed = hashlib.sha256(canonical_bytes).hexdigest()
474
- result["hash_ok"] = (recomputed == recorded_hash)
475
- if not result["hash_ok"]:
476
- result["reason"] = "integrity hash mismatch (proof.json was edited after signing)"
477
- # Continue to gather drift/gpg signals for the report, but ok stays False.
478
-
479
- # ----- 3. GPG (compute before returning so the report is complete) -----
480
- gpg_sig = verification.get("gpg_signature")
481
- result["gpg_ok"] = _verify_gpg(canonical_bytes, gpg_sig)
482
-
483
- # generator_trusted: only a VALID signature (gpg_ok is True) means the
484
- # generator is NOT trusted (neutral non-forgeability). "n/a" or a bad sig
485
- # leaves the generator trusted -- the facts are taken at face value and a
486
- # consistent forger is NOT caught. Stated so ok=True still discloses it.
487
- result["generator_trusted"] = (result["gpg_ok"] is not True)
488
550
 
489
551
  # ----- 2. DRIFT CHECK --------------------------------------------------
490
552
  recorded_stat = _recorded_diff_stat(proof)
@@ -559,37 +621,24 @@ def verify(proof_path, repo_dir="."):
559
621
  if drift and not result["reason"]:
560
622
  result["reason"] = "recorded diff no longer matches the repo (drift detected)"
561
623
 
562
- # ----- 4. HEADLINE CONSISTENCY (defense-in-depth) ----------------------
563
- # Re-derive the headline from the recorded facts and compare to the stored
564
- # honesty.headline. A mismatch means the headline was edited to disagree
565
- # with the facts it claims to summarize (an INCONSISTENT forgery, e.g.
566
- # headline flipped to VERIFIED while facts.tests.status is still not_run).
567
- # This catches careless/partial tampering only. It does NOT catch a
568
- # CONSISTENT forger who rewrites both the facts and the headline to a
569
- # matching lie and recomputes the integrity hash -- on the unsigned path
570
- # that still passes (see generator_trusted). Neutral non-forgeability needs
571
- # the signed record, not this check.
572
- recorded_headline = _recorded_headline(proof)
573
- facts = proof.get("facts")
574
- if recorded_headline is not None and isinstance(facts, dict):
575
- derived = _compute_headline(facts, _recorded_degraded_raw(proof))
576
- result["headline_consistent"] = (derived == recorded_headline)
577
- if not result["headline_consistent"] and not result["reason"]:
578
- result["reason"] = (
579
- "honesty.headline (%r) disagrees with the headline re-derived "
580
- "from the recorded facts (%r); the headline was edited to "
581
- "misrepresent the facts" % (recorded_headline, derived)
582
- )
583
- else:
584
- # No recorded headline, or no facts to re-derive from: cannot check.
585
- result["headline_consistent"] = None
624
+ recorded_tree = _recorded_tree_sha256(proof)
625
+ result["tree_recheck"]["recorded"] = recorded_tree
626
+ if recorded_tree:
627
+ current_tree = compute_tree_digest(repo_dir)
628
+ result["tree_recheck"]["current"] = current_tree or None
629
+ if not current_tree:
630
+ if not result["reason"]:
631
+ result["reason"] = "final workspace tree could not be re-derived"
632
+ else:
633
+ result["tree_drift"] = current_tree != recorded_tree
634
+ if result["tree_drift"] and not result["reason"]:
635
+ result["reason"] = "recorded final workspace tree no longer matches the repo"
586
636
 
587
637
  # ----- overall verdict -------------------------------------------------
588
638
  result["ok"] = bool(
589
- result["hash_ok"]
639
+ integrity["ok"]
590
640
  and result["diff_drift"] is False
591
- and result["gpg_ok"] in (True, "n/a")
592
- and result["headline_consistent"] is not False
641
+ and (not recorded_tree or result["tree_drift"] is False)
593
642
  )
594
643
  if result["ok"]:
595
644
  result["reason"] = ""