loki-mode 8.72.0 → 8.74.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 v8.72.0
6
+ # Loki Mode v8.74.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -469,4 +469,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
469
469
 
470
470
  ---
471
471
 
472
- **v8.72.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
472
+ **v8.74.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 8.72.0
1
+ 8.74.0
@@ -550,15 +550,21 @@ def verify_integrity(proof):
550
550
  "headline_consistent": None,
551
551
  "degraded": _recorded_degraded(proof) if isinstance(proof, dict) else [],
552
552
  "reason": "",
553
+ "reasons": [],
553
554
  "ok": False,
554
555
  }
555
556
  if not isinstance(proof, dict):
556
557
  result["reason"] = "proof root is not a JSON object"
558
+ result["reasons"].append(result["reason"])
557
559
  return result
558
560
 
559
561
  verification = proof.get("verification")
560
562
  if not isinstance(verification, dict) or not verification.get("hash"):
561
563
  result["reason"] = "no verification.hash recorded; cannot prove integrity"
564
+ result["reasons"].append(
565
+ "integrity hash missing: the receipt records no verification.hash, "
566
+ "so there is nothing to re-compute against and tampering cannot be "
567
+ "ruled out")
562
568
  return result
563
569
 
564
570
  unsigned = dict(proof)
@@ -571,23 +577,33 @@ def verify_integrity(proof):
571
577
  result["reason"] = (
572
578
  "integrity hash mismatch (proof.json was edited after signing)"
573
579
  )
580
+ result["reasons"].append(
581
+ "hash mismatch: recorded %s, computed %s -- proof.json was edited "
582
+ "after it was written" % (recorded_hash, recomputed))
574
583
 
575
584
  result["gpg_ok"] = _verify_gpg(
576
585
  canonical_bytes, verification.get("gpg_signature")
577
586
  )
578
587
  result["generator_trusted"] = result["gpg_ok"] is not True
588
+ if result["gpg_ok"] is False:
589
+ result["reasons"].append(
590
+ "gpg signature verification failed: a signature is recorded but "
591
+ "gpg could not verify it against the canonical receipt bytes")
579
592
 
580
593
  recorded_headline = _recorded_headline(proof)
581
594
  facts = proof.get("facts")
582
595
  if recorded_headline is not None and isinstance(facts, dict):
583
596
  derived = _compute_headline(facts, _recorded_degraded_raw(proof))
584
597
  result["headline_consistent"] = derived == recorded_headline
585
- if not result["headline_consistent"] and not result["reason"]:
586
- result["reason"] = (
598
+ if not result["headline_consistent"]:
599
+ _headline_reason = (
587
600
  "honesty.headline (%r) disagrees with the headline re-derived "
588
601
  "from the recorded facts (%r); the headline was edited to "
589
602
  "misrepresent the facts" % (recorded_headline, derived)
590
603
  )
604
+ if not result["reason"]:
605
+ result["reason"] = _headline_reason
606
+ result["reasons"].append(_headline_reason)
591
607
 
592
608
  # COST COHERENCE. The receipt is the product's trust artifact, and the
593
609
  # verifier checked hashes, diffs, gates and the headline -- but never cost.
@@ -636,6 +652,12 @@ def verify_integrity(proof):
636
652
  result["cost_coherent"] = not _bad
637
653
  if _bad and not result["reason"]:
638
654
  result["reason"] = _bad
655
+ # Keyed on cost_coherent, not on _bad: a mutation that forces the
656
+ # verdict True must not keep emitting the explanation it contradicts.
657
+ if result["cost_coherent"] is False:
658
+ result["reasons"].append(
659
+ "cost claim is incoherent: %s (unmeasured must read UNKNOWN, "
660
+ "never $0.00)" % _bad)
639
661
 
640
662
  result["ok"] = bool(
641
663
  result["hash_ok"]
@@ -645,6 +667,7 @@ def verify_integrity(proof):
645
667
  )
646
668
  if result["ok"]:
647
669
  result["reason"] = ""
670
+ result["reasons"] = []
648
671
  elif not result["reason"]:
649
672
  result["reason"] = (
650
673
  "gpg signature verification failed"
@@ -668,9 +691,15 @@ def verify(proof_path, repo_dir="."):
668
691
  headline_consistent: bool | None see note below
669
692
  degraded: [str] honesty.degraded from the proof
670
693
  reason: str why ok is False (when it is)
694
+ reasons: [str] EVERY failed check, spelled out
671
695
  ok: bool overall verdict
672
696
  }
673
697
 
698
+ reason vs reasons: `reason` is the FIRST failure only (first-wins
699
+ precedence, unchanged -- callers and tests depend on it). `reasons` lists
700
+ every check that failed, so a receipt failing on both cost and drift says
701
+ so instead of naming one. It is empty exactly when ok is True.
702
+
674
703
  `ok` = hash_ok AND diff_drift is False AND gpg_ok in (True, "n/a")
675
704
  AND headline_consistent is not False.
676
705
  Note: diff_drift None (unverifiable) makes ok False, by design -- we never
@@ -697,6 +726,7 @@ def verify(proof_path, repo_dir="."):
697
726
  integrity = verify_integrity(proof)
698
727
  result = {
699
728
  **integrity,
729
+ "reasons": list(integrity.get("reasons") or []),
700
730
  "diff_drift": None,
701
731
  "diff_recheck": {"recorded": None, "current": None},
702
732
  "tree_drift": None,
@@ -720,17 +750,28 @@ def verify(proof_path, repo_dir="."):
720
750
  result["diff_drift"] = None
721
751
  if not result["reason"]:
722
752
  result["reason"] = "repo_dir is not a git work tree; drift unverifiable"
753
+ result["reasons"].append(
754
+ "drift unverifiable: %r is not a git work tree, so the recorded "
755
+ "diff cannot be re-derived (re-run from the repository the receipt "
756
+ "was generated in)" % repo_dir)
723
757
  elif not base_sha:
724
758
  # Schema v1.0 (or a v1.1 proof missing base_sha): no recorded base ref,
725
759
  # so the diff cannot be re-derived. Report honestly, do NOT pass.
726
760
  result["diff_drift"] = None
727
761
  if not result["reason"]:
728
762
  result["reason"] = "base ref unresolvable (no recorded base_sha; drift unverifiable)"
763
+ result["reasons"].append(
764
+ "drift unverifiable: the receipt records no base_sha, so there is "
765
+ "no starting point to re-derive the diff from (schema v1.0 receipt)")
729
766
  elif not _rev_resolvable(repo_dir, base_sha):
730
767
  result["diff_drift"] = None
731
768
  if not result["reason"]:
732
769
  result["reason"] = ("base ref unresolvable (%s not found in repo; "
733
770
  "drift unverifiable)" % base_sha)
771
+ result["reasons"].append(
772
+ "drift unverifiable: recorded base ref %s is not present in this "
773
+ "repository (fetch the branch, or verify against the repo the "
774
+ "receipt was generated in)" % base_sha)
734
775
  else:
735
776
  # Drift answers "does this receipt still describe the CURRENT branch
736
777
  # state". A receipt is for verifying the work as it stands now, so we
@@ -747,6 +788,9 @@ def verify(proof_path, repo_dir="."):
747
788
  result["diff_drift"] = None
748
789
  if not result["reason"]:
749
790
  result["reason"] = "git diff could not be computed; drift unverifiable"
791
+ result["reasons"].append(
792
+ "drift unverifiable: git diff %s..HEAD could not be computed"
793
+ % base_sha)
750
794
  else:
751
795
  drift = False
752
796
  if recorded_stat is not None:
@@ -762,6 +806,10 @@ def verify(proof_path, repo_dir="."):
762
806
  if not result["reason"]:
763
807
  result["reason"] = ("no recorded diff stat to compare; "
764
808
  "drift unverifiable")
809
+ result["reasons"].append(
810
+ "drift unverifiable: the repository diff was re-derived, "
811
+ "but the receipt recorded no diff stat to compare it "
812
+ "against")
765
813
 
766
814
  # diff_sha256: a stronger content check than the counts. Only when
767
815
  # the receipt recorded one (v1.1).
@@ -782,6 +830,17 @@ def verify(proof_path, repo_dir="."):
782
830
  result["diff_drift"] = drift
783
831
  if drift and not result["reason"]:
784
832
  result["reason"] = "recorded diff no longer matches the repo (drift detected)"
833
+ if drift:
834
+ result["reasons"].append(
835
+ "diff drift: the receipt recorded %s files / +%s / -%s, "
836
+ "the repository now has %s files / +%s / -%s -- the "
837
+ "branch changed after the receipt was generated" % (
838
+ recorded_stat.get("count"),
839
+ recorded_stat.get("insertions"),
840
+ recorded_stat.get("deletions"),
841
+ current_stat.get("count"),
842
+ current_stat.get("insertions"),
843
+ current_stat.get("deletions")))
785
844
 
786
845
  recorded_tree = _recorded_tree_sha256(proof)
787
846
  result["tree_recheck"]["recorded"] = recorded_tree
@@ -791,10 +850,18 @@ def verify(proof_path, repo_dir="."):
791
850
  if not current_tree:
792
851
  if not result["reason"]:
793
852
  result["reason"] = "final workspace tree could not be re-derived"
853
+ result["reasons"].append(
854
+ "workspace tree unverifiable: the receipt records a final tree "
855
+ "digest, but the current workspace tree could not be re-derived")
794
856
  else:
795
857
  result["tree_drift"] = current_tree != recorded_tree
796
858
  if result["tree_drift"] and not result["reason"]:
797
859
  result["reason"] = "recorded final workspace tree no longer matches the repo"
860
+ if result["tree_drift"]:
861
+ result["reasons"].append(
862
+ "workspace tree drift: recorded %s, computed %s -- the "
863
+ "working tree changed after the receipt was generated" % (
864
+ recorded_tree, current_tree))
798
865
 
799
866
  # ----- overall verdict -------------------------------------------------
800
867
  result["ok"] = bool(
@@ -804,11 +871,18 @@ def verify(proof_path, repo_dir="."):
804
871
  )
805
872
  if result["ok"]:
806
873
  result["reason"] = ""
807
- elif not result["reason"]:
808
- if result["gpg_ok"] is False:
809
- result["reason"] = "gpg signature verification failed"
810
- else:
811
- result["reason"] = "verification failed"
874
+ result["reasons"] = []
875
+ else:
876
+ if not result["reason"]:
877
+ if result["gpg_ok"] is False:
878
+ result["reason"] = "gpg signature verification failed"
879
+ else:
880
+ result["reason"] = "verification failed"
881
+ # A failed verdict with no explanation is the bug this list exists to
882
+ # fix, so never emit one. Reaching here means a check failed without a
883
+ # matching append -- say so, rather than printing nothing.
884
+ if not result["reasons"]:
885
+ result["reasons"].append(result["reason"])
812
886
  return result
813
887
 
814
888
 
@@ -816,11 +890,33 @@ def verify(proof_path, repo_dir="."):
816
890
  # CLI shim (mirrors dashboard/audit.py _unified_cli style)
817
891
  # ---------------------------------------------------------------------------
818
892
 
893
+ def render_reasons(result):
894
+ """Render a verdict as human-readable lines.
895
+
896
+ The JSON report is the machine surface; this is the one a person reads.
897
+ A passing receipt renders the verdict alone -- never a fabricated reason.
898
+ """
899
+ lines = ["VERIFIED" if result.get("ok") else "FAILED"]
900
+ for reason in (result.get("reasons") or []):
901
+ lines.append(" - %s" % reason)
902
+ return "\n".join(lines)
903
+
904
+
819
905
  def _cli(argv=None):
820
906
  argv = list(sys.argv[1:] if argv is None else argv)
821
907
  if not argv or argv[0] in ("-h", "--help"):
822
908
  print(json.dumps(
823
- {"error": "usage: proof-verify.py <proof.json> [repo_dir]"}))
909
+ {"error":
910
+ "usage: proof-verify.py [--human] <proof.json> [repo_dir]"}))
911
+ return 2
912
+ # Flags are stripped BEFORE positional parsing: proof.ts pipes this
913
+ # command's stdout through verbatim, so --human must not shift repo_dir.
914
+ human = "--human" in argv
915
+ argv = [a for a in argv if a != "--human"]
916
+ if not argv:
917
+ print(json.dumps(
918
+ {"error":
919
+ "usage: proof-verify.py [--human] <proof.json> [repo_dir]"}))
824
920
  return 2
825
921
  proof_path = argv[0]
826
922
  repo_dir = argv[1] if len(argv) > 1 else "."
@@ -832,7 +928,7 @@ def _cli(argv=None):
832
928
  except Exception as exc: # defensive: never a traceback-as-UX
833
929
  print(json.dumps({"ok": False, "error": "verify failed: %s" % exc}))
834
930
  return 2
835
- print(json.dumps(result, indent=2))
931
+ print(render_reasons(result) if human else json.dumps(result, indent=2))
836
932
  return 0 if result.get("ok") else 1
837
933
 
838
934
 
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "8.72.0"
10
+ __version__ = "8.74.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var m_=Object.create;var{getPrototypeOf:u_,defineProperty:eK,getOwnPropertyNames:p_}=Object;var d_=Object.prototype.hasOwnProperty;function c_(Z){return this[Z]}var l_,i_,a_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?l_??=new WeakMap:i_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?m_(u_(Z)):{};let K=X||!Z||!Z.__esModule?eK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of p_(Z))if(!d_.call(K,$))eK(K,$,{get:c_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var HQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var s_=(Z)=>Z;function n_(Z,X){this[Z]=s_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)eK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:n_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var e0=import.meta.require;var kO={};l0(kO,{lokiDir:()=>j0,homeLokiDir:()=>R4,findRepoRootForVersion:()=>X$,REPO_ROOT:()=>i0});import{resolve as n7,dirname as Z$}from"path";import{fileURLToPath as o_}from"url";import{existsSync as UQ}from"fs";import{homedir as r_}from"os";function t_(){let Z=RO;for(let X=0;X<6;X++){if(UQ(n7(Z,"VERSION"))&&UQ(n7(Z,"autonomy/run.sh")))return Z;let Q=Z$(Z);if(Q===Z)break;Z=Q}return n7(RO,"..","..","..")}function X$(Z){let X=Z;for(let Q=0;Q<6;Q++){if(UQ(n7(X,"VERSION"))&&UQ(n7(X,"autonomy/run.sh")))return X;let Y=Z$(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function R4(){return n7(r_(),".loki")}var RO,i0;var H8=p(()=>{RO=Z$(o_(import.meta.url));i0=t_()});import{readFileSync as e_}from"fs";import{resolve as Zf,dirname as Xf}from"path";import{fileURLToPath as Qf}from"url";function h3(){if(h5!==null)return h5;let Z="8.72.0";if(typeof Z==="string"&&Z.length>0)return h5=Z,h5;try{let X=Xf(Qf(import.meta.url)),Q=X$(X);h5=e_(Zf(Q,"VERSION"),"utf-8").trim()}catch{h5="unknown"}return h5}var h5=null;var BQ=p(()=>{H8()});var bO={};l0(bO,{runOrThrow:()=>jf,run:()=>E0,readStreamCapped:()=>NQ,commandVersion:()=>Tf,commandExists:()=>X9,ShellError:()=>Q$,MAX_STDOUT_BYTES:()=>yO});async function NQ(Z,X=yO){let Q=Z.getReader(),Y=new TextDecoder,J="",z=0;try{while(z<X){let{done:K,value:$}=await Q.read();if(K)break;if(!$)continue;if(z+=$.byteLength,z>X){let W=$.byteLength-(z-X);J+=Y.decode($.subarray(0,W),{stream:!0});break}J+=Y.decode($,{stream:!0})}J+=Y.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return J}async function E0(Z,X={}){let Q=Bun.spawn({cmd:[...Z],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),Y,J;if(X.timeoutMs&&X.timeoutMs>0)Y=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}J=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[z,K,$]=await Promise.all([NQ(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:z,stderr:K,exitCode:$}}finally{if(Y)clearTimeout(Y);if(J)clearTimeout(J)}}async function jf(Z,X={}){let Q=await E0(Z,X);if(Q.exitCode!==0)throw new Q$(`command failed (${Q.exitCode}): ${Z.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function X9(Z){let X=Mf(Z),Q=await E0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Mf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function Tf(Z,X="--version"){if(!await X9(Z))return null;let Y=await E0([Z,X],{timeoutMs:5000});if(Y.exitCode!==0)return null;return((Y.stdout||Y.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var yO=16777216,Q$;var x9=p(()=>{Q$=class Q$ extends Error{message;exitCode;stdout;stderr;constructor(Z,X,Q,Y){super(Z);this.message=Z;this.exitCode=X;this.stdout=Q;this.stderr=Y;this.name="ShellError"}}});function o7(Z){return wf?"":Z}var wf,L0,F8,p0,zV0,a0,W8,Q9,v;var S6=p(()=>{wf=(process.env.NO_COLOR??"").length>0;L0=o7("\x1B[0;31m"),F8=o7("\x1B[0;32m"),p0=o7("\x1B[1;33m"),zV0=o7("\x1B[0;34m"),a0=o7("\x1B[0;36m"),W8=o7("\x1B[1m"),Q9=o7("\x1B[2m"),v=o7("\x1B[0m")});import{existsSync as bf}from"fs";async function E7(){if(x4!==void 0)return x4;let Z="/opt/homebrew/bin/python3.12";if(bf(Z))return x4=Z,Z;let X=await X9("python3.12");if(X)return x4=X,X;let Q=await X9("python3");return x4=Q,Q}async function Y7(Z,X={}){let Q=await E7();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return E0([Q,"-c",Z],X)}var x4;var r7=p(()=>{x9()});var ZL={};l0(ZL,{runStatus:()=>Kh});import{existsSync as Y9,readFileSync as g3,readdirSync as iO,statSync as aO}from"fs";import{resolve as h8,basename as rf}from"path";import{homedir as tf}from"os";function sO(Z){let X=Math.trunc(Z);if(X>=1e6)return`${(Math.trunc(X/1e6*10)/10).toFixed(1)}M`;if(X>=1000)return`${(Math.trunc(X/1000*10)/10).toFixed(1)}K`;return String(X)}function nO(Z,X,Q){if(X===0)return null;let Y=Math.trunc(Z*100/X),J=Math.trunc(Z*LQ/X);if(J>LQ)J=LQ;let z=LQ-J,K=F8;if(Y>=80)K=L0;else if(Y>=50)K=p0;let $="=".repeat(Math.max(0,J))+" ".repeat(Math.max(0,z)),W=sO(Z),V=sO(X);return` ${W8}${Q}${v} ${K}[${$}]${v} ${Y}% (${W} / ${V})`}async function Zh(){if(await X9("jq"))return!0;return process.stdout.write(`${L0}Error: jq is required but not installed.${v}
2
+ var m_=Object.create;var{getPrototypeOf:u_,defineProperty:eK,getOwnPropertyNames:p_}=Object;var d_=Object.prototype.hasOwnProperty;function c_(Z){return this[Z]}var l_,i_,a_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?l_??=new WeakMap:i_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?m_(u_(Z)):{};let K=X||!Z||!Z.__esModule?eK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of p_(Z))if(!d_.call(K,$))eK(K,$,{get:c_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var HQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var s_=(Z)=>Z;function n_(Z,X){this[Z]=s_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)eK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:n_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var e0=import.meta.require;var kO={};l0(kO,{lokiDir:()=>j0,homeLokiDir:()=>R4,findRepoRootForVersion:()=>X$,REPO_ROOT:()=>i0});import{resolve as n7,dirname as Z$}from"path";import{fileURLToPath as o_}from"url";import{existsSync as UQ}from"fs";import{homedir as r_}from"os";function t_(){let Z=RO;for(let X=0;X<6;X++){if(UQ(n7(Z,"VERSION"))&&UQ(n7(Z,"autonomy/run.sh")))return Z;let Q=Z$(Z);if(Q===Z)break;Z=Q}return n7(RO,"..","..","..")}function X$(Z){let X=Z;for(let Q=0;Q<6;Q++){if(UQ(n7(X,"VERSION"))&&UQ(n7(X,"autonomy/run.sh")))return X;let Y=Z$(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function R4(){return n7(r_(),".loki")}var RO,i0;var H8=p(()=>{RO=Z$(o_(import.meta.url));i0=t_()});import{readFileSync as e_}from"fs";import{resolve as Zf,dirname as Xf}from"path";import{fileURLToPath as Qf}from"url";function h3(){if(h5!==null)return h5;let Z="8.74.0";if(typeof Z==="string"&&Z.length>0)return h5=Z,h5;try{let X=Xf(Qf(import.meta.url)),Q=X$(X);h5=e_(Zf(Q,"VERSION"),"utf-8").trim()}catch{h5="unknown"}return h5}var h5=null;var BQ=p(()=>{H8()});var bO={};l0(bO,{runOrThrow:()=>jf,run:()=>E0,readStreamCapped:()=>NQ,commandVersion:()=>Tf,commandExists:()=>X9,ShellError:()=>Q$,MAX_STDOUT_BYTES:()=>yO});async function NQ(Z,X=yO){let Q=Z.getReader(),Y=new TextDecoder,J="",z=0;try{while(z<X){let{done:K,value:$}=await Q.read();if(K)break;if(!$)continue;if(z+=$.byteLength,z>X){let W=$.byteLength-(z-X);J+=Y.decode($.subarray(0,W),{stream:!0});break}J+=Y.decode($,{stream:!0})}J+=Y.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return J}async function E0(Z,X={}){let Q=Bun.spawn({cmd:[...Z],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),Y,J;if(X.timeoutMs&&X.timeoutMs>0)Y=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}J=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[z,K,$]=await Promise.all([NQ(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:z,stderr:K,exitCode:$}}finally{if(Y)clearTimeout(Y);if(J)clearTimeout(J)}}async function jf(Z,X={}){let Q=await E0(Z,X);if(Q.exitCode!==0)throw new Q$(`command failed (${Q.exitCode}): ${Z.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function X9(Z){let X=Mf(Z),Q=await E0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Mf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function Tf(Z,X="--version"){if(!await X9(Z))return null;let Y=await E0([Z,X],{timeoutMs:5000});if(Y.exitCode!==0)return null;return((Y.stdout||Y.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var yO=16777216,Q$;var x9=p(()=>{Q$=class Q$ extends Error{message;exitCode;stdout;stderr;constructor(Z,X,Q,Y){super(Z);this.message=Z;this.exitCode=X;this.stdout=Q;this.stderr=Y;this.name="ShellError"}}});function o7(Z){return wf?"":Z}var wf,L0,F8,p0,zV0,a0,W8,Q9,v;var S6=p(()=>{wf=(process.env.NO_COLOR??"").length>0;L0=o7("\x1B[0;31m"),F8=o7("\x1B[0;32m"),p0=o7("\x1B[1;33m"),zV0=o7("\x1B[0;34m"),a0=o7("\x1B[0;36m"),W8=o7("\x1B[1m"),Q9=o7("\x1B[2m"),v=o7("\x1B[0m")});import{existsSync as bf}from"fs";async function E7(){if(x4!==void 0)return x4;let Z="/opt/homebrew/bin/python3.12";if(bf(Z))return x4=Z,Z;let X=await X9("python3.12");if(X)return x4=X,X;let Q=await X9("python3");return x4=Q,Q}async function Y7(Z,X={}){let Q=await E7();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return E0([Q,"-c",Z],X)}var x4;var r7=p(()=>{x9()});var ZL={};l0(ZL,{runStatus:()=>Kh});import{existsSync as Y9,readFileSync as g3,readdirSync as iO,statSync as aO}from"fs";import{resolve as h8,basename as rf}from"path";import{homedir as tf}from"os";function sO(Z){let X=Math.trunc(Z);if(X>=1e6)return`${(Math.trunc(X/1e6*10)/10).toFixed(1)}M`;if(X>=1000)return`${(Math.trunc(X/1000*10)/10).toFixed(1)}K`;return String(X)}function nO(Z,X,Q){if(X===0)return null;let Y=Math.trunc(Z*100/X),J=Math.trunc(Z*LQ/X);if(J>LQ)J=LQ;let z=LQ-J,K=F8;if(Y>=80)K=L0;else if(Y>=50)K=p0;let $="=".repeat(Math.max(0,J))+" ".repeat(Math.max(0,z)),W=sO(Z),V=sO(X);return` ${W8}${Q}${v} ${K}[${$}]${v} ${Y}% (${W} / ${V})`}async function Zh(){if(await X9("jq"))return!0;return process.stdout.write(`${L0}Error: jq is required but not installed.${v}
3
3
  `),process.stdout.write(`Install with:
4
4
  `),process.stdout.write(` brew install jq (macOS)
5
5
  `),process.stdout.write(` apt install jq (Debian/Ubuntu)
@@ -442,7 +442,7 @@ Start a session with: loki start <prd>`}}let J=Gh(Y);return{exitCode:0,stdout:X?
442
442
  `),0}return kh()}var Ah,wQ,Th,wh,Eh=90;var ML=p(()=>{H8();x9();r7();S6();BQ();Ah=/(\d+\.\d+(?:\.\d+)*)/;wQ={fn:U$};Th=[{name:"Claude Code",dir:".claude/skills/loki-mode"},{name:"Codex CLI",dir:".codex/skills/loki-mode"},{name:"Cline CLI",dir:".cline/skills/loki-mode"},{name:"Aider CLI",dir:".aider/skills/loki-mode"}];wh=[{displayName:"Node.js (>= 18)",jsonName:"Node.js",cmd:"node",required:"required",min:"18.0"},{displayName:"Python 3 (>= 3.8)",jsonName:"Python 3",cmd:"python3",required:"required",min:"3.8"},{displayName:"jq",jsonName:"jq",cmd:"jq",required:"required"},{displayName:"git",jsonName:"git",cmd:"git",required:"required"},{displayName:"curl",jsonName:"curl",cmd:"curl",required:"required"},{displayName:"bash (>= 4.0)",jsonName:"bash",cmd:"bash",required:"recommended",min:"4.0"},{displayName:"Bun (>= 1.3)",jsonName:"Bun",cmd:"bun",required:"recommended",min:"1.3"},{displayName:"Claude CLI",jsonName:"Claude CLI",cmd:"claude",required:"optional"},{displayName:"Codex CLI",jsonName:"Codex CLI",cmd:"codex",required:"optional"},{displayName:"Cline CLI",jsonName:"Cline CLI",cmd:"cline",required:"optional"},{displayName:"Aider CLI",jsonName:"Aider CLI",cmd:"aider",required:"optional"}]});var DL={};l0(DL,{writeBudgetState:()=>j$,readEfficiencyDir:()=>FQ,readBudgetState:()=>uh,parseRetryAfter:()=>ih,isRateLimited:()=>lh,checkBudgetLimitForRunner:()=>sh,checkBudgetLimit:()=>FL,calculateRateLimitBackoff:()=>ah,calculateCostFromRecords:()=>CQ,PRICING:()=>A$});import{existsSync as M$,mkdirSync as O$,readdirSync as Sh,readFileSync as T$,renameSync as yh,writeFileSync as L$}from"fs";import{dirname as w$,join as Z5,resolve as bh}from"path";import{fileURLToPath as _h}from"url";function fh(){try{let Z=w$(_h(import.meta.url)),X=bh(Z,"..","..","data","model-pricing.json");if(!M$(X))return _4;let Y=JSON.parse(T$(X,"utf8")).pricing;if(!Y||typeof Y!=="object")return _4;let J={};for(let[z,K]of Object.entries(Y))if(K!==null&&typeof K==="object"&&typeof K.input==="number"&&typeof K.output==="number"){let $=K;J[z]={input:$.input,output:$.output,...typeof $.cache_read==="number"?{cache_read:$.cache_read}:{},...typeof $.cache_write==="number"?{cache_write:$.cache_write}:{}}}for(let z of Object.keys(_4))if(!(z in J))return _4;return J}catch{return _4}}function vh(Z){return Math.round((Z+Number.EPSILON)*1e4)/1e4}function gh(Z){let X=(Z??TL).toLowerCase();return A$[X]??A$[TL]}function CQ(Z){let X=0;for(let Q of Z){if(typeof Q.cost_usd==="number"&&Number.isFinite(Q.cost_usd)){X+=Q.cost_usd;continue}let Y=gh(Q.model),J=typeof Q.input_tokens==="number"?Q.input_tokens:0,z=typeof Q.output_tokens==="number"?Q.output_tokens:0,K=typeof Q.cache_read_tokens==="number"?Q.cache_read_tokens:0,$=typeof Q.cache_creation_tokens==="number"?Q.cache_creation_tokens:0,W=Y.cache_read??Y.input,V=Y.cache_write??Y.input;X+=J/1e6*Y.input+z/1e6*Y.output+K/1e6*W+$/1e6*V}return vh(X)}function FQ(Z){if(!M$(Z))return[];let X=[],Q;try{Q=Sh(Z)}catch{return[]}for(let Y of Q){if(!Y.endsWith(".json"))continue;let J=Z5(Z,Y);try{let z=T$(J,"utf8"),K=JSON.parse(z);if(K&&typeof K==="object")X.push(K)}catch{}}return X}function mh(Z){if(Z===null||typeof Z!=="object"||Array.isArray(Z))return!1;let X=Z;if(typeof X.limit!=="number"||!Number.isFinite(X.limit))return!1;if(typeof X.budget_limit!=="number"||!Number.isFinite(X.budget_limit))return!1;if(typeof X.budget_used!=="number"||!Number.isFinite(X.budget_used))return!1;if(typeof X.exceeded!=="boolean")return!1;if(X.exceeded_at!==void 0&&typeof X.exceeded_at!=="string")return!1;if(X.created_at!==void 0&&typeof X.created_at!=="string")return!1;return!0}function uh(Z){let X=Z??Z5(j0(),"metrics","budget.json");if(!M$(X))return null;try{let Q=T$(X,"utf8"),Y=JSON.parse(Q);if(!mh(Y))return console.warn(`[budget] discarding malformed budget state at ${X}`),null;return Y}catch{return null}}function j$(Z,X){let Q=X??Z5(j0(),"metrics","budget.json");O$(w$(Q),{recursive:!0});let Y=[];if(Y.push("{"),Y.push(` "limit": ${N$(Z.limit)},`),Y.push(` "budget_limit": ${N$(Z.budget_limit)},`),Y.push(` "budget_used": ${N$(Z.budget_used)},`),Z.exceeded){Y.push(' "exceeded": true,');let K=Z.exceeded_at??CL();Y.push(` "exceeded_at": "${K}"`)}else Y.push(' "exceeded": false');Y.push("}");let J=Y.join(`
443
443
  `)+`
444
444
  `,z=`${Q}.tmp.${process.pid}`;L$(z,J),yh(z,Q)}function N$(Z){if(Number.isInteger(Z))return Z.toString();return Z.toString()}function CL(Z=new Date){return Z.toISOString().replace(/\.\d{3}Z$/,"Z")}function ph(Z){if(Z===null||Z===void 0||Z==="")return null;if(typeof Z==="number")return Number.isFinite(Z)?Z:null;let X=Z.replace(/[^0-9.]/g,"");if(!X)return null;let Q=Number(X);return Number.isFinite(Q)?Q:null}function FL(Z={}){let X=j0(),Q=ph(Z.budgetLimit??process.env.BUDGET_LIMIT??null);if(Q===null)return{exceeded:!1,warn:!1,current_cost:0,limit:null};let Y=Z.efficiencyDir??Z5(X,"metrics","efficiency"),J=Z.budgetFile??Z5(X,"metrics","budget.json"),z=Z.pauseFile??Z5(X,"PAUSE"),K=Z.signalsDir??Z5(X,"signals"),$=Z.now??(()=>new Date),W=FQ(Y),V=CQ(W);if(V>=Q){let H=CL($());O$(w$(z),{recursive:!0}),L$(z,""),O$(K,{recursive:!0});let B={type:"BUDGET_EXCEEDED",limit:Q,current:V,timestamp:H};return L$(Z5(K,"BUDGET_EXCEEDED"),JSON.stringify(B)),j$({limit:Q,budget_limit:Q,budget_used:V,exceeded:!0,exceeded_at:H},J),{exceeded:!0,warn:!1,current_cost:V,limit:Q}}if(V>0)j$({limit:Q,budget_limit:Q,budget_used:V,exceeded:!1},J);return{exceeded:!1,warn:V>=hh*Q,current_cost:V,limit:Q}}function lh(Z){let X=Array.isArray(Z)?Z.join(`
445
- `):Z;if(!X)return!1;if(dh.test(X))return!0;if(ch.test(X))return!0;return!1}function ih(Z){if(!Z)return 0;wL.lastIndex=0;let X=0,Q;while((Q=wL.exec(Z))!==null){let Y=Q[1];if(Y!==void 0){let J=Number.parseInt(Y,10);if(Number.isFinite(J))X=J}}return X}function ah(Z,X){if(typeof Z==="number"&&Z>0)return Z;let Q=typeof X==="number"&&X>0?X:50,Y=Math.floor(7200/Q);if(Y<60)Y=60;if(Y>300)Y=300;return Y}async function sh(Z){return FL({budgetLimit:Z.budgetLimit,iteration:Z.iterationCount,efficiencyDir:`${Z.lokiDir}/metrics/efficiency`,budgetFile:`${Z.lokiDir}/metrics/budget.json`,pauseFile:`${Z.lokiDir}/PAUSE`,signalsDir:`${Z.lokiDir}/signals`}).exceeded}var _4,A$,TL="sonnet",hh=0.8,dh,ch,wL;var C$=p(()=>{H8();_4={fable:{input:10,output:50,cache_read:1,cache_write:12.5},opus:{input:5,output:25,cache_read:0.5,cache_write:6.25},sonnet:{input:3,output:15,cache_read:0.3,cache_write:3.75},haiku:{input:1,output:5,cache_read:0.1,cache_write:1.25},"gpt-5.3-codex":{input:1.5,output:12,cache_read:0.15,cache_write:1.875}};A$=Object.freeze(fh());dh=/(429|rate.?limit|too many requests|quota exceeded|request limit|retry.?after)/i,ch=/resets [0-9]+[ap]m/,wL=/retry.?after:?\s*([0-9]+)/gi});import{existsSync as DQ,readdirSync as nh,readFileSync as oh,statSync as rh}from"fs";import{join as EQ}from"path";function th(Z){let X=[],Q=EQ(Z,"votes");if(!DQ(Q))return X;let Y;try{Y=nh(Q)}catch{return X}for(let J of Y){if(!J.startsWith("round-")||!J.endsWith(".json"))continue;try{let z=EQ(Q,J);if(!rh(z).isFile())continue;let K=JSON.parse(oh(z,"utf8"));X.push({iteration:typeof K.iteration==="number"?K.iteration:void 0,verdict:typeof K.verdict==="string"?K.verdict:void 0,complete_votes:typeof K.complete_votes==="number"?K.complete_votes:void 0,total_members:typeof K.total_members==="number"?K.total_members:void 0,threshold:typeof K.threshold==="number"?K.threshold:void 0})}catch{}}return X}function Zv(Z){if(Z.length===0)return!1;for(let X of eh){let Q=0;for(let Y of Z){let J=Y[X];if(typeof J==="number"&&Number.isFinite(J))Q+=J}if(Q!==0)return!0}return!1}function Xv(){return{iteration_count:0,total_cost_usd:null,avg_cost_per_iteration:null,total_input_tokens:0,total_output_tokens:0,total_duration_ms:0,avg_duration_ms_per_iteration:null,model_breakdown:{},phase_breakdown:{},status_breakdown:{}}}function Qv(){return{council_rounds:0,unanimous_rate:null,approval_rate:null,iteration_success_rate:null}}function Yv(Z){let X=Xv();if(Z.length===0)return X;X.iteration_count=Z.length,X.total_cost_usd=Zv(Z)?Math.round(CQ(Z)*1e4)/1e4:null;for(let Q of Z){if(typeof Q.input_tokens==="number")X.total_input_tokens+=Q.input_tokens;if(typeof Q.output_tokens==="number")X.total_output_tokens+=Q.output_tokens;let Y=Q;if(typeof Y.duration_ms==="number")X.total_duration_ms+=Y.duration_ms;if(typeof Q.model==="string")X.model_breakdown[Q.model]=(X.model_breakdown[Q.model]??0)+1;if(typeof Y.phase==="string")X.phase_breakdown[Y.phase]=(X.phase_breakdown[Y.phase]??0)+1;if(typeof Y.status==="string")X.status_breakdown[Y.status]=(X.status_breakdown[Y.status]??0)+1}return X.avg_cost_per_iteration=X.total_cost_usd===null?null:Math.round(X.total_cost_usd/X.iteration_count*1e4)/1e4,X.avg_duration_ms_per_iteration=Math.round(X.total_duration_ms/X.iteration_count),X}function Jv(Z,X,Q){let Y=Qv();if(Y.council_rounds=Z.length,Z.length>0){let J=0,z=0;for(let K of Z){if(typeof K.complete_votes==="number"&&typeof K.total_members==="number"&&K.total_members>0&&K.complete_votes===K.total_members)J+=1;if(K.verdict==="COMPLETE")z+=1}Y.unanimous_rate=Math.round(J/Z.length*1e4)/1e4,Y.approval_rate=Math.round(z/Z.length*1e4)/1e4}if(Q>0)Y.iteration_success_rate=Math.round(X/Q*1e4)/1e4;return Y}function EL(Z){let X=[],Q=EQ(Z,"metrics","efficiency"),Y=EQ(Z,"council"),J=DQ(Q)?FQ(Q):[];if(!DQ(Q))X.push("no .loki/metrics/efficiency/ dir (cost UNKNOWN, token/duration KPIs zeroed)");else if(J.length===0)X.push(".loki/metrics/efficiency/ exists but no iteration files found (cost UNKNOWN, not zero)");let z=th(Y);if(!DQ(Y))X.push("no .loki/council/ dir (accuracy KPIs zeroed)");else if(z.length===0)X.push(".loki/council/ exists but no round-N.json files found");let K=Yv(J);if(J.length>0&&K.total_cost_usd===null)X.push(`${J.length} efficiency record(s) present but no token/cost values recorded (cost UNKNOWN, not zero)`);let $=K.status_breakdown.success??0,W=Jv(z,$,K.iteration_count);return{schema_version:1,generated_at:new Date().toISOString(),loki_dir:Z,efficiency:K,accuracy:W,notes:X}}function IL(Z){return JSON.stringify(Z,null,2)}function PL(Z){let X=[];X.push(`Loki Mode KPIs (snapshot at ${Z.generated_at})`),X.push(`Source: ${Z.loki_dir}`),X.push(""),X.push("Efficiency"),X.push(` Iterations: ${Z.efficiency.iteration_count}`),X.push(` Total cost USD: ${Z.efficiency.total_cost_usd??"UNKNOWN (not measured)"}`),X.push(` Avg cost per iter: ${Z.efficiency.avg_cost_per_iteration??"UNKNOWN (not measured)"}`),X.push(` Total input tokens: ${Z.efficiency.total_input_tokens}`),X.push(` Total output tokens: ${Z.efficiency.total_output_tokens}`),X.push(` Total duration (ms): ${Z.efficiency.total_duration_ms}`),X.push(` Avg duration / iter: ${Z.efficiency.avg_duration_ms_per_iteration??"n/a"}`);let Q=Object.entries(Z.efficiency.model_breakdown).sort((z,K)=>z[0].localeCompare(K[0]));if(Q.length>0)X.push(` Model breakdown: ${Q.map(([z,K])=>`${z}=${K}`).join(", ")}`);let Y=Object.entries(Z.efficiency.phase_breakdown).sort((z,K)=>z[0].localeCompare(K[0]));if(Y.length>0)X.push(` Phase breakdown: ${Y.map(([z,K])=>`${z}=${K}`).join(", ")}`);let J=Object.entries(Z.efficiency.status_breakdown).sort((z,K)=>z[0].localeCompare(K[0]));if(J.length>0)X.push(` Status breakdown: ${J.map(([z,K])=>`${z}=${K}`).join(", ")}`);if(X.push(""),X.push("Accuracy"),X.push(` Council rounds: ${Z.accuracy.council_rounds}`),X.push(` Unanimous rate: ${Z.accuracy.unanimous_rate??"n/a"}`),X.push(` Approval rate: ${Z.accuracy.approval_rate??"n/a"}`),X.push(` Iter success rate: ${Z.accuracy.iteration_success_rate??"n/a"}`),Z.notes.length>0){X.push(""),X.push("Notes");for(let z of Z.notes)X.push(` - ${z}`)}return X.push(""),X.push("See also: loki trust (trust trajectory across runs)"),X.join(`
445
+ `):Z;if(!X)return!1;if(dh.test(X))return!0;if(ch.test(X))return!0;return!1}function ih(Z){if(!Z)return 0;wL.lastIndex=0;let X=0,Q;while((Q=wL.exec(Z))!==null){let Y=Q[1];if(Y!==void 0){let J=Number.parseInt(Y,10);if(Number.isFinite(J))X=J}}return X}function ah(Z,X){if(typeof Z==="number"&&Z>0)return Z;let Q=typeof X==="number"&&X>0?X:50,Y=Math.floor(7200/Q);if(Y<60)Y=60;if(Y>300)Y=300;return Y}async function sh(Z){return FL({budgetLimit:Z.budgetLimit,iteration:Z.iterationCount,efficiencyDir:`${Z.lokiDir}/metrics/efficiency`,budgetFile:`${Z.lokiDir}/metrics/budget.json`,pauseFile:`${Z.lokiDir}/PAUSE`,signalsDir:`${Z.lokiDir}/signals`}).exceeded}var _4,A$,TL="sonnet",hh=0.8,dh,ch,wL;var C$=p(()=>{H8();_4={fable:{input:10,output:50,cache_read:1,cache_write:12.5},opus:{input:5,output:25,cache_read:0.5,cache_write:6.25},sonnet:{input:3,output:15,cache_read:0.3,cache_write:3.75},haiku:{input:1,output:5,cache_read:0.1,cache_write:1.25},"gpt-5.3-codex":{input:1.5,output:12,cache_read:0.15,cache_write:1.875}};A$=Object.freeze(fh());dh=/(429|rate.?limit|too many requests|quota exceeded|request limit|retry.?after)/i,ch=/resets [0-9]+[ap]m/,wL=/retry.?after:?\s*([0-9]+)/gi});import{existsSync as DQ,readdirSync as nh,readFileSync as oh,statSync as rh}from"fs";import{join as EQ}from"path";function th(Z){let X=[],Q=EQ(Z,"votes");if(!DQ(Q))return X;let Y;try{Y=nh(Q)}catch{return X}for(let J of Y){if(!J.startsWith("round-")||!J.endsWith(".json"))continue;try{let z=EQ(Q,J);if(!rh(z).isFile())continue;let K=JSON.parse(oh(z,"utf8"));X.push({iteration:typeof K.iteration==="number"?K.iteration:void 0,verdict:typeof K.verdict==="string"?K.verdict:void 0,complete_votes:typeof K.complete_votes==="number"?K.complete_votes:void 0,total_members:typeof K.total_members==="number"?K.total_members:void 0,threshold:typeof K.threshold==="number"?K.threshold:void 0})}catch{}}return X}function Zv(Z){if(Z.length===0)return!1;for(let X of eh){let Q=0;for(let Y of Z){let J=Y[X];if(typeof J==="number"&&Number.isFinite(J))Q+=J}if(Q!==0)return!0}return!1}function Xv(){return{iteration_count:0,total_cost_usd:null,avg_cost_per_iteration:null,total_input_tokens:null,total_output_tokens:null,total_duration_ms:0,avg_duration_ms_per_iteration:null,model_breakdown:{},phase_breakdown:{},status_breakdown:{}}}function Qv(){return{council_rounds:0,unanimous_rate:null,approval_rate:null,iteration_success_rate:null}}function Yv(Z){let X=Xv();if(Z.length===0)return X;X.iteration_count=Z.length;let Q=Zv(Z);X.total_cost_usd=Q?Math.round(CQ(Z)*1e4)/1e4:null;let Y=0,J=0;for(let z of Z){if(typeof z.input_tokens==="number")Y+=z.input_tokens;if(typeof z.output_tokens==="number")J+=z.output_tokens;let K=z;if(typeof K.duration_ms==="number")X.total_duration_ms+=K.duration_ms;if(typeof z.model==="string")X.model_breakdown[z.model]=(X.model_breakdown[z.model]??0)+1;if(typeof K.phase==="string")X.phase_breakdown[K.phase]=(X.phase_breakdown[K.phase]??0)+1;if(typeof K.status==="string")X.status_breakdown[K.status]=(X.status_breakdown[K.status]??0)+1}return X.total_input_tokens=Q?Y:null,X.total_output_tokens=Q?J:null,X.avg_cost_per_iteration=X.total_cost_usd===null?null:Math.round(X.total_cost_usd/X.iteration_count*1e4)/1e4,X.avg_duration_ms_per_iteration=Math.round(X.total_duration_ms/X.iteration_count),X}function Jv(Z,X,Q){let Y=Qv();if(Y.council_rounds=Z.length,Z.length>0){let J=0,z=0;for(let K of Z){if(typeof K.complete_votes==="number"&&typeof K.total_members==="number"&&K.total_members>0&&K.complete_votes===K.total_members)J+=1;if(K.verdict==="COMPLETE")z+=1}Y.unanimous_rate=Math.round(J/Z.length*1e4)/1e4,Y.approval_rate=Math.round(z/Z.length*1e4)/1e4}if(Q>0)Y.iteration_success_rate=Math.round(X/Q*1e4)/1e4;return Y}function EL(Z){let X=[],Q=EQ(Z,"metrics","efficiency"),Y=EQ(Z,"council"),J=DQ(Q)?FQ(Q):[];if(!DQ(Q))X.push("no .loki/metrics/efficiency/ dir (cost UNKNOWN, tokens UNKNOWN, duration zeroed)");else if(J.length===0)X.push(".loki/metrics/efficiency/ exists but no iteration files found (cost UNKNOWN, not zero)");let z=th(Y);if(!DQ(Y))X.push("no .loki/council/ dir (accuracy KPIs zeroed)");else if(z.length===0)X.push(".loki/council/ exists but no round-N.json files found");let K=Yv(J);if(J.length>0&&K.total_cost_usd===null)X.push(`${J.length} efficiency record(s) present but no token/cost values recorded (cost UNKNOWN, not zero; tokens UNKNOWN)`);let $=K.status_breakdown.success??0,W=Jv(z,$,K.iteration_count);return{schema_version:1,generated_at:new Date().toISOString(),loki_dir:Z,efficiency:K,accuracy:W,notes:X}}function IL(Z){return JSON.stringify(Z,null,2)}function PL(Z){let X=[];X.push(`Loki Mode KPIs (snapshot at ${Z.generated_at})`),X.push(`Source: ${Z.loki_dir}`),X.push(""),X.push("Efficiency"),X.push(` Iterations: ${Z.efficiency.iteration_count}`),X.push(` Total cost USD: ${Z.efficiency.total_cost_usd??"UNKNOWN (not measured)"}`),X.push(` Avg cost per iter: ${Z.efficiency.avg_cost_per_iteration??"UNKNOWN (not measured)"}`),X.push(` Total input tokens: ${Z.efficiency.total_input_tokens??"UNKNOWN (not measured)"}`),X.push(` Total output tokens: ${Z.efficiency.total_output_tokens??"UNKNOWN (not measured)"}`),X.push(` Total duration (ms): ${Z.efficiency.total_duration_ms}`),X.push(` Avg duration / iter: ${Z.efficiency.avg_duration_ms_per_iteration??"n/a"}`);let Q=Object.entries(Z.efficiency.model_breakdown).sort((z,K)=>z[0].localeCompare(K[0]));if(Q.length>0)X.push(` Model breakdown: ${Q.map(([z,K])=>`${z}=${K}`).join(", ")}`);let Y=Object.entries(Z.efficiency.phase_breakdown).sort((z,K)=>z[0].localeCompare(K[0]));if(Y.length>0)X.push(` Phase breakdown: ${Y.map(([z,K])=>`${z}=${K}`).join(", ")}`);let J=Object.entries(Z.efficiency.status_breakdown).sort((z,K)=>z[0].localeCompare(K[0]));if(J.length>0)X.push(` Status breakdown: ${J.map(([z,K])=>`${z}=${K}`).join(", ")}`);if(X.push(""),X.push("Accuracy"),X.push(` Council rounds: ${Z.accuracy.council_rounds}`),X.push(` Unanimous rate: ${Z.accuracy.unanimous_rate??"n/a"}`),X.push(` Approval rate: ${Z.accuracy.approval_rate??"n/a"}`),X.push(` Iter success rate: ${Z.accuracy.iteration_success_rate??"n/a"}`),Z.notes.length>0){X.push(""),X.push("Notes");for(let z of Z.notes)X.push(` - ${z}`)}return X.push(""),X.push("See also: loki trust (trust trajectory across runs)"),X.join(`
446
446
  `)}var eh;var RL=p(()=>{C$();eh=["cost_usd","input_tokens","output_tokens","cache_read_tokens","cache_creation_tokens"]});var F$={};l0(F$,{runKpis:()=>Kv});function Kv(Z,X={}){if(X.aliasOf)z$(X.aliasOf,"report kpis",Z);let Q=!1;for(let J of Z){if(J==="--help"||J==="-h"||J==="help")return process.stdout.write(zv),0;if(J==="--json"){Q=!0;continue}if(J==="-q"||J==="--quiet")continue;return process.stderr.write(`loki kpis: unknown arg: ${J}
447
447
  Run 'loki kpis --help' for usage.
448
448
  `),1}let Y=EL(j0());return process.stdout.write(Q?IL(Y)+`
@@ -1232,4 +1232,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
1232
1232
  `),2}case"start":{let{runStart:Y}=await Promise.resolve().then(() => (h_(),f_));return Y(Q)}default:return process.stderr.write(`Unknown command: ${X}
1233
1233
  `),process.stderr.write(v_),2}}cO();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var uW0=await mW0(Bun.argv.slice(2));process.exit(uW0);
1234
1234
 
1235
- //# debugId=C412FE1BA43AEE6064756E2164756E21
1235
+ //# debugId=A050F36D23AA4E6464756E2164756E21
package/mcp/__init__.py CHANGED
@@ -75,4 +75,4 @@ try:
75
75
  except ImportError:
76
76
  __all__ = ['mcp']
77
77
 
78
- __version__ = '8.72.0'
78
+ __version__ = '8.74.0'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "loki-mode",
3
3
  "mcpName": "io.github.asklokesh/loki-mode",
4
- "version": "8.72.0",
4
+ "version": "8.74.0",
5
5
  "description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
6
6
  "keywords": [
7
7
  "agent",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "loki-mode",
4
4
  "displayName": "Loki Mode",
5
- "version": "8.72.0",
5
+ "version": "8.74.0",
6
6
  "description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
7
7
  "author": {
8
8
  "name": "Autonomi",