loki-mode 7.129.5 → 8.0.1
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 +121 -9
- package/SKILL.md +66 -5
- package/VERSION +1 -1
- package/autonomy/app-runner.sh +37 -0
- package/autonomy/completion-council.sh +862 -28
- package/autonomy/council-v2.sh +39 -0
- package/autonomy/crash.sh +3 -2
- package/autonomy/grill.sh +42 -0
- package/autonomy/hooks/validate-bash.sh +301 -4
- package/autonomy/lib/cockpit-render.sh +8 -2
- package/autonomy/lib/cr-rematerialize.py +101 -4
- package/autonomy/lib/deadline.py +957 -0
- package/autonomy/lib/dependency-setup.sh +205 -0
- package/autonomy/lib/done-recognition.sh +45 -0
- package/autonomy/lib/efficiency_cost.py +13 -6
- package/autonomy/lib/no_mock_scan.py +793 -0
- package/autonomy/lib/prd-enrich.sh +42 -0
- package/autonomy/lib/proof-analytics-props.py +69 -0
- package/autonomy/lib/proof-generator.py +326 -92
- package/autonomy/lib/proof-template.html +15 -12
- package/autonomy/lib/proof-verify.py +169 -105
- package/autonomy/lib/requirements_contract.py +848 -0
- package/autonomy/lib/sdk-mode.sh +103 -0
- package/autonomy/lib/secret-scan.sh +139 -0
- package/autonomy/lib/spec-expand.sh +208 -0
- package/autonomy/lib/tree_digest.py +266 -0
- package/autonomy/lib/voter-agents.sh +58 -8
- package/autonomy/lib/workspace_diff.py +141 -0
- package/autonomy/loki +196 -17
- package/autonomy/playwright-verify.sh +501 -0
- package/autonomy/prd-checklist.sh +17 -8
- package/autonomy/provider-offer.sh +111 -0
- package/autonomy/run.sh +4587 -671
- package/autonomy/sandbox.sh +42 -0
- package/autonomy/spec-interrogation.sh +148 -5
- package/autonomy/spec.sh +118 -1
- package/autonomy/telemetry.sh +133 -7
- package/autonomy/verify.sh +107 -0
- package/bin/loki +110 -3
- package/completions/_loki +10 -0
- package/completions/loki.bash +1 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/audit.py +96 -7
- package/dashboard/build_supervisor.py +1619 -0
- package/dashboard/control.py +8 -0
- package/dashboard/prompt_optimizer.py +7 -0
- package/dashboard/server.py +577 -22
- package/dashboard/static/trust.html +1 -1
- package/docs/ARCHITECTURE-OVERVIEW.md +207 -0
- package/docs/AUTONOMI-ECOSYSTEM.md +107 -0
- package/docs/INSTALLATION.md +19 -2
- package/docs/MODEL-EQUIVALENCE-HARNESS-PLAN.md +70 -0
- package/docs/PLANS-INDEX.md +33 -0
- package/docs/PRIVACY.md +29 -1
- package/docs/SIGNED-RECEIPTS.md +102 -0
- package/docs/SONNET5-DEFAULT-PLAN.md +1 -1
- package/docs/V8-ACCEPTANCE-TRIAGE-2026-07-24.md +114 -0
- package/docs/V8-AGENT-SDK-PLAN.md +692 -0
- package/docs/V8-COMPLEXITY-AUDIT-2026-07-24.md +45 -0
- package/docs/V8-MAJOR-RELEASE-PLAN.md +137 -0
- package/docs/V8-OVERNIGHT-PLAN-2026-07-25.md +82 -0
- package/docs/V8-RUNTIME-TRUTH-2026-07-25.md +232 -0
- package/docs/V8-SDK-RESEARCH-RAW.md +129 -0
- package/docs/alternative-installations.md +4 -4
- package/loki-ts/dist/loki.js +674 -285
- package/loki-ts/package.json +2 -0
- package/mcp/__init__.py +1 -1
- package/package.json +7 -6
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/claude.sh +75 -0
- package/providers/codex.sh +85 -13
- package/references/sdk-mode.md +106 -0
- package/skills/model-selection.md +1 -1
- package/skills/providers.md +42 -0
- package/skills/quality-gates.md +22 -0
|
@@ -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-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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)
|
|
@@ -105,50 +113,36 @@ def _is_git_repo(repo_dir):
|
|
|
105
113
|
|
|
106
114
|
|
|
107
115
|
def _rev_resolvable(repo_dir, ref):
|
|
108
|
-
"""True iff `ref`
|
|
116
|
+
"""True iff `ref` names something in repo_dir that a diff can be taken from.
|
|
117
|
+
|
|
118
|
+
Accepts a COMMIT or a TREE. The tree case is load-bearing: a greenfield run
|
|
119
|
+
starts in a repo with no commits, so the generator records the EMPTY TREE
|
|
120
|
+
(4b825dc6...) as its baseline -- "everything that now exists". That object
|
|
121
|
+
is a tree, not a commit, so a commit-only check rejects it and the verifier
|
|
122
|
+
reports "base ref unresolvable" for a perfectly genuine proof.
|
|
123
|
+
|
|
124
|
+
`git diff <empty-tree>` works fine, so the diff really is re-derivable; only
|
|
125
|
+
the resolvability probe was too narrow. This does NOT loosen the forgery
|
|
126
|
+
defense -- an unknown or fabricated SHA still resolves as neither and is
|
|
127
|
+
still rejected.
|
|
128
|
+
"""
|
|
109
129
|
if not ref:
|
|
110
130
|
return False
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
for kind in ("commit", "tree"):
|
|
132
|
+
out = _git(repo_dir, ["rev-parse", "--verify", "--quiet", "%s^{%s}" % (ref, kind)])
|
|
133
|
+
if out and out.strip():
|
|
134
|
+
return True
|
|
135
|
+
return False
|
|
113
136
|
|
|
114
137
|
|
|
115
138
|
def _numstat(repo_dir, base, head):
|
|
116
|
-
"""Return
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
}
|
|
139
|
+
"""Return final-worktree stats relative to ``base``.
|
|
140
|
+
|
|
141
|
+
``head`` remains in the signature for installed-layout compatibility. The
|
|
142
|
+
live working tree is the proof target, not only its current commit.
|
|
143
|
+
"""
|
|
144
|
+
stat, _ = collect_workspace_diff(repo_dir, str(base), False)
|
|
145
|
+
return stat
|
|
152
146
|
|
|
153
147
|
|
|
154
148
|
def _diff_sha256_from_stat(files_changed):
|
|
@@ -205,12 +199,42 @@ def _compute_headline(facts, degraded):
|
|
|
205
199
|
|
|
206
200
|
sec = facts.get("security") or {}
|
|
207
201
|
sec_high = bool(sec.get("ran") and (sec.get("high_active") or 0) > 0)
|
|
202
|
+
fn = facts.get("functional") or {}
|
|
203
|
+
fn_failed = bool(
|
|
204
|
+
os.environ.get("LOKI_FV_GATE") == "1"
|
|
205
|
+
and fn.get("ran")
|
|
206
|
+
and fn.get("functional_status") == "failed"
|
|
207
|
+
)
|
|
208
|
+
execution = facts.get("execution") or {}
|
|
209
|
+
execution_outcome = str(execution.get("outcome") or "").lower()
|
|
210
|
+
failed_run_statuses = {
|
|
211
|
+
"failed",
|
|
212
|
+
"force_stopped",
|
|
213
|
+
"inconclusive_spec_contradiction",
|
|
214
|
+
"interrupted",
|
|
215
|
+
"max_iterations_reached",
|
|
216
|
+
"max_retries_exceeded",
|
|
217
|
+
"paused",
|
|
218
|
+
"policy_blocked",
|
|
219
|
+
"provider_deadline_partial_mutation",
|
|
220
|
+
}
|
|
221
|
+
execution_failed = bool(
|
|
222
|
+
execution.get("terminated")
|
|
223
|
+
or execution.get("exit_code") not in (None, 0)
|
|
224
|
+
or str(execution.get("run_status") or "").lower() in failed_run_statuses
|
|
225
|
+
or (
|
|
226
|
+
execution_outcome
|
|
227
|
+
and execution_outcome not in ("complete", "completed", "success")
|
|
228
|
+
)
|
|
229
|
+
)
|
|
208
230
|
any_failed = (
|
|
209
|
-
|
|
231
|
+
execution_failed
|
|
232
|
+
or tests.get("status") == "failed"
|
|
210
233
|
or build.get("status") == "failed"
|
|
211
234
|
or any(g.get("status") == "failed"
|
|
212
235
|
for g in (facts.get("quality_gates") or []))
|
|
213
236
|
or sec_high
|
|
237
|
+
or fn_failed
|
|
214
238
|
)
|
|
215
239
|
if any_failed:
|
|
216
240
|
return "NOT VERIFIED"
|
|
@@ -294,6 +318,16 @@ def _recorded_diff_sha256(proof):
|
|
|
294
318
|
return None
|
|
295
319
|
|
|
296
320
|
|
|
321
|
+
def _recorded_tree_sha256(proof):
|
|
322
|
+
facts = proof.get("facts")
|
|
323
|
+
if isinstance(facts, dict):
|
|
324
|
+
git = facts.get("git")
|
|
325
|
+
if isinstance(git, dict) and git.get("tree_sha256"):
|
|
326
|
+
return str(git["tree_sha256"])
|
|
327
|
+
value = proof.get("tree_sha256")
|
|
328
|
+
return str(value) if value else None
|
|
329
|
+
|
|
330
|
+
|
|
297
331
|
def _recorded_degraded(proof):
|
|
298
332
|
"""Return the honesty.degraded list the generator disclosed, or []."""
|
|
299
333
|
honesty = proof.get("honesty")
|
|
@@ -404,6 +438,75 @@ def _load_proof(proof_path):
|
|
|
404
438
|
return data
|
|
405
439
|
|
|
406
440
|
|
|
441
|
+
def verify_integrity(proof):
|
|
442
|
+
"""Verify the receipt hash, signature, and recorded headline.
|
|
443
|
+
|
|
444
|
+
This is the canonical in-memory integrity check shared by the CLI verifier
|
|
445
|
+
and supervised execution binding. Repository drift is intentionally left to
|
|
446
|
+
verify(), whose caller supplies the repository path.
|
|
447
|
+
"""
|
|
448
|
+
result = {
|
|
449
|
+
"hash_ok": False,
|
|
450
|
+
"gpg_ok": "n/a",
|
|
451
|
+
"generator_trusted": True,
|
|
452
|
+
"headline_consistent": None,
|
|
453
|
+
"degraded": _recorded_degraded(proof) if isinstance(proof, dict) else [],
|
|
454
|
+
"reason": "",
|
|
455
|
+
"ok": False,
|
|
456
|
+
}
|
|
457
|
+
if not isinstance(proof, dict):
|
|
458
|
+
result["reason"] = "proof root is not a JSON object"
|
|
459
|
+
return result
|
|
460
|
+
|
|
461
|
+
verification = proof.get("verification")
|
|
462
|
+
if not isinstance(verification, dict) or not verification.get("hash"):
|
|
463
|
+
result["reason"] = "no verification.hash recorded; cannot prove integrity"
|
|
464
|
+
return result
|
|
465
|
+
|
|
466
|
+
unsigned = dict(proof)
|
|
467
|
+
unsigned.pop("verification", None)
|
|
468
|
+
canonical_bytes = _canonical(unsigned).encode("utf-8")
|
|
469
|
+
recorded_hash = str(verification.get("hash"))
|
|
470
|
+
recomputed = hashlib.sha256(canonical_bytes).hexdigest()
|
|
471
|
+
result["hash_ok"] = recomputed == recorded_hash
|
|
472
|
+
if not result["hash_ok"]:
|
|
473
|
+
result["reason"] = (
|
|
474
|
+
"integrity hash mismatch (proof.json was edited after signing)"
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
result["gpg_ok"] = _verify_gpg(
|
|
478
|
+
canonical_bytes, verification.get("gpg_signature")
|
|
479
|
+
)
|
|
480
|
+
result["generator_trusted"] = result["gpg_ok"] is not True
|
|
481
|
+
|
|
482
|
+
recorded_headline = _recorded_headline(proof)
|
|
483
|
+
facts = proof.get("facts")
|
|
484
|
+
if recorded_headline is not None and isinstance(facts, dict):
|
|
485
|
+
derived = _compute_headline(facts, _recorded_degraded_raw(proof))
|
|
486
|
+
result["headline_consistent"] = derived == recorded_headline
|
|
487
|
+
if not result["headline_consistent"] and not result["reason"]:
|
|
488
|
+
result["reason"] = (
|
|
489
|
+
"honesty.headline (%r) disagrees with the headline re-derived "
|
|
490
|
+
"from the recorded facts (%r); the headline was edited to "
|
|
491
|
+
"misrepresent the facts" % (recorded_headline, derived)
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
result["ok"] = bool(
|
|
495
|
+
result["hash_ok"]
|
|
496
|
+
and result["gpg_ok"] in (True, "n/a")
|
|
497
|
+
and result["headline_consistent"] is not False
|
|
498
|
+
)
|
|
499
|
+
if result["ok"]:
|
|
500
|
+
result["reason"] = ""
|
|
501
|
+
elif not result["reason"]:
|
|
502
|
+
result["reason"] = (
|
|
503
|
+
"gpg signature verification failed"
|
|
504
|
+
if result["gpg_ok"] is False
|
|
505
|
+
else "verification failed"
|
|
506
|
+
)
|
|
507
|
+
return result
|
|
508
|
+
|
|
509
|
+
|
|
407
510
|
def verify(proof_path, repo_dir="."):
|
|
408
511
|
"""Re-verify a proof.json against the repo.
|
|
409
512
|
|
|
@@ -444,47 +547,21 @@ def verify(proof_path, repo_dir="."):
|
|
|
444
547
|
"""
|
|
445
548
|
proof = _load_proof(proof_path)
|
|
446
549
|
|
|
550
|
+
integrity = verify_integrity(proof)
|
|
447
551
|
result = {
|
|
448
|
-
|
|
552
|
+
**integrity,
|
|
449
553
|
"diff_drift": None,
|
|
450
554
|
"diff_recheck": {"recorded": None, "current": None},
|
|
451
|
-
"
|
|
452
|
-
"
|
|
453
|
-
"headline_consistent": None,
|
|
454
|
-
"degraded": _recorded_degraded(proof),
|
|
455
|
-
"reason": "",
|
|
555
|
+
"tree_drift": None,
|
|
556
|
+
"tree_recheck": {"recorded": None, "current": None},
|
|
456
557
|
"ok": False,
|
|
457
558
|
}
|
|
458
559
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
result["reason"] = "no verification.hash recorded; cannot prove integrity"
|
|
560
|
+
if not integrity["hash_ok"] and not (
|
|
561
|
+
isinstance(proof.get("verification"), dict)
|
|
562
|
+
and proof["verification"].get("hash")
|
|
563
|
+
):
|
|
464
564
|
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
565
|
|
|
489
566
|
# ----- 2. DRIFT CHECK --------------------------------------------------
|
|
490
567
|
recorded_stat = _recorded_diff_stat(proof)
|
|
@@ -559,37 +636,24 @@ def verify(proof_path, repo_dir="."):
|
|
|
559
636
|
if drift and not result["reason"]:
|
|
560
637
|
result["reason"] = "recorded diff no longer matches the repo (drift detected)"
|
|
561
638
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
|
639
|
+
recorded_tree = _recorded_tree_sha256(proof)
|
|
640
|
+
result["tree_recheck"]["recorded"] = recorded_tree
|
|
641
|
+
if recorded_tree:
|
|
642
|
+
current_tree = compute_tree_digest(repo_dir)
|
|
643
|
+
result["tree_recheck"]["current"] = current_tree or None
|
|
644
|
+
if not current_tree:
|
|
645
|
+
if not result["reason"]:
|
|
646
|
+
result["reason"] = "final workspace tree could not be re-derived"
|
|
647
|
+
else:
|
|
648
|
+
result["tree_drift"] = current_tree != recorded_tree
|
|
649
|
+
if result["tree_drift"] and not result["reason"]:
|
|
650
|
+
result["reason"] = "recorded final workspace tree no longer matches the repo"
|
|
586
651
|
|
|
587
652
|
# ----- overall verdict -------------------------------------------------
|
|
588
653
|
result["ok"] = bool(
|
|
589
|
-
|
|
654
|
+
integrity["ok"]
|
|
590
655
|
and result["diff_drift"] is False
|
|
591
|
-
and result["
|
|
592
|
-
and result["headline_consistent"] is not False
|
|
656
|
+
and (not recorded_tree or result["tree_drift"] is False)
|
|
593
657
|
)
|
|
594
658
|
if result["ok"]:
|
|
595
659
|
result["reason"] = ""
|