loki-mode 8.93.0 → 8.95.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.93.0
6
+ # Loki Mode v8.95.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.93.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
472
+ **v8.95.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 8.93.0
1
+ 8.95.0
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "8.93.0"
10
+ __version__ = "8.95.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.93.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.95.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)
@@ -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=232EF166A0E3908D64756E2164756E21
1235
+ //# debugId=FD5A9EB9795BBFB164756E2164756E21
package/mcp/__init__.py CHANGED
@@ -75,4 +75,4 @@ try:
75
75
  except ImportError:
76
76
  __all__ = ['mcp']
77
77
 
78
- __version__ = '8.93.0'
78
+ __version__ = '8.95.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.93.0",
4
+ "version": "8.95.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.93.0",
5
+ "version": "8.95.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",
@@ -334,11 +334,50 @@ def render(record):
334
334
  return "\n".join(lines)
335
335
 
336
336
 
337
+ _USAGE = """usage: receipt-attest.py <proof.json> [--json]
338
+
339
+ Turn a receipt into a portable ATTESTATION a third party can check without the
340
+ original workspace.
341
+
342
+ --json emit the attestation record as JSON
343
+ --help show this message
344
+
345
+ States, which are never collapsed into one another:
346
+ VERIFIED the axis was checked and passed
347
+ FAILED the axis was checked and failed
348
+ UNVERIFIABLE the axis could NOT be checked here, with the reason
349
+
350
+ Exit: 0 all scored axes verified, 1 something FAILED, 2 something was
351
+ UNVERIFIABLE, 64 usage error.
352
+ """
353
+
354
+
337
355
  def main(argv):
338
- args = [a for a in argv[1:] if a != "--json"]
339
- as_json = "--json" in argv[1:]
356
+ rest = argv[1:]
357
+
358
+ # --help must print usage, NOT be read as a filename. It previously fell
359
+ # through to the positional slot and produced
360
+ # "UNVERIFIABLE -- proof file not found: --help", which is a verification
361
+ # verdict about a file the user never named: a fabricated answer to a
362
+ # question they did not ask.
363
+ if "--help" in rest or "-h" in rest:
364
+ sys.stdout.write(_USAGE)
365
+ return 0
366
+
367
+ as_json = "--json" in rest
368
+ args = [a for a in rest if a != "--json"]
369
+
370
+ # An unrecognized flag is an ERROR, never a path. Treating "--jsonn" as a
371
+ # proof file reports UNVERIFIABLE for a typo, which reads as a finding
372
+ # about the receipt rather than about the command line.
373
+ unknown = [a for a in args if a.startswith("-")]
374
+ if unknown:
375
+ sys.stderr.write("unknown option(s): %s\n" % " ".join(unknown))
376
+ sys.stderr.write(_USAGE)
377
+ return 64
378
+
340
379
  if len(args) != 1:
341
- sys.stderr.write("usage: receipt-attest.py <proof.json> [--json]\n")
380
+ sys.stderr.write(_USAGE)
342
381
  return 64
343
382
  record = attest(args[0])
344
383
  print(json.dumps(record, indent=2, sort_keys=True) if as_json
@@ -0,0 +1,374 @@
1
+ #!/usr/bin/env python3
2
+ """Reconstruct what a completed run actually did, iteration by iteration.
3
+
4
+ WHY. Unit cases prove the RULE. Only the recorded artifact proves the CASE.
5
+ A run leaves a flight recorder behind -- .loki/events.jsonl plus the
6
+ per-iteration efficiency records -- and until now reading it back meant either
7
+ hand-parsing JSONL or running scripts/measure-run.sh, which renders ONE
8
+ aggregate stage table for the whole run. An aggregate cannot answer the
9
+ questions that matter after a bad run: did cost climb between iterations, did
10
+ the same gate fail twice, which iteration was the slow one.
11
+
12
+ This reads ONLY what a run already wrote. It starts nothing, spends nothing,
13
+ and never contacts a provider. Replay is free.
14
+
15
+ Usage:
16
+ tools/run-replay.py [workspace] # default: .
17
+ tools/run-replay.py --json [ws] # machine-readable
18
+
19
+ THE HONESTY RULES, which are the point of this tool:
20
+
21
+ 1. A truncated or unparseable line is COUNTED and REPORTED, never silently
22
+ dropped. measure-run.sh skips bad lines by design, so a corrupt tail
23
+ disappears without a trace and the replay reports a cleaner run than
24
+ happened. A replay that quietly loses data is worse than no replay.
25
+ 2. An unmeasured cost reads UNKNOWN, never $0.00 -- via the single shared
26
+ record_is_measured() from autonomy/lib/efficiency_cost.py. Free and
27
+ unmeasured are different claims and only one of them is honest.
28
+ 3. A stage that never emitted stage_complete reads "not recorded", NOT 0s.
29
+ 0s reads as "instant", which is a different claim. Note a genuine
30
+ duration_s of 0 is real data (fast gates emit it) and is preserved.
31
+ 4. An empty or missing events.jsonl says so and exits non-zero. An
32
+ empty-but-successful-looking replay certifies a run that never happened.
33
+ """
34
+
35
+ import argparse
36
+ import json
37
+ import os
38
+ import sys
39
+
40
+ sys.dont_write_bytecode = True
41
+
42
+ _HERE = os.path.dirname(os.path.abspath(__file__))
43
+ _REPO_ROOT = os.path.dirname(_HERE)
44
+ # Resolved from __file__, never from the workspace argument: the workspace
45
+ # being replayed is a different tree and has no autonomy/lib.
46
+ sys.path.insert(0, os.path.join(_REPO_ROOT, "autonomy", "lib"))
47
+
48
+ from efficiency_cost import record_is_measured # noqa: E402
49
+
50
+ EXIT_NO_DATA = 66 # matches the missing-workspace convention in measure-run.sh
51
+
52
+
53
+ def _load_events(path):
54
+ """Return (events, skipped_line_count).
55
+
56
+ Every line that does not parse into a dict is counted. The count is the
57
+ deliverable, not a debug aid: it is the only evidence that the replay is
58
+ reading a whole recording.
59
+ """
60
+ events = []
61
+ skipped = 0
62
+ with open(path, errors="replace") as fh:
63
+ for line in fh:
64
+ if not line.strip():
65
+ continue
66
+ try:
67
+ e = json.loads(line)
68
+ except Exception:
69
+ # MUTATION PROBE TARGET. The increment below is the probe's
70
+ # find-string and must stay the ONLY compound-assignment
71
+ # spelling of it in this file -- which is why the sibling
72
+ # branch writes the increment out longhand, and why this
73
+ # comment does not quote it. A probe whose find-string is
74
+ # ambiguous hits whichever copy comes first (here, a comment)
75
+ # and reports MUTATION SURVIVED, which is indistinguishable
76
+ # from a test that checks nothing.
77
+ skipped += 1
78
+ continue
79
+ if not isinstance(e, dict):
80
+ # A bare JSON scalar parses fine but is not a record.
81
+ skipped = skipped + 1
82
+ continue
83
+ events.append(e)
84
+ return events, skipped
85
+
86
+
87
+ def _iter_key(value):
88
+ """Iteration number as an int, or None when unusable."""
89
+ if isinstance(value, bool):
90
+ return None
91
+ if isinstance(value, int):
92
+ return value
93
+ if isinstance(value, float):
94
+ return int(value)
95
+ try:
96
+ return int(str(value).strip())
97
+ except Exception:
98
+ return None
99
+
100
+
101
+ def _read_cost(workspace, iteration):
102
+ """Per-iteration cost dict, or None when nothing was measured.
103
+
104
+ Read per FILE, not via collect_efficiency(): that sums the whole directory
105
+ into one aggregate, which cannot answer "did cost climb between
106
+ iterations". record_is_measured() is the shared rule and is applied here
107
+ unchanged -- a second copy of that predicate is how the honesty rule
108
+ drifts.
109
+ """
110
+ path = os.path.join(
111
+ workspace, ".loki", "metrics", "efficiency",
112
+ "iteration-%d.json" % iteration,
113
+ )
114
+ try:
115
+ with open(path) as fh:
116
+ rec = json.load(fh)
117
+ except Exception:
118
+ return None
119
+ if not isinstance(rec, dict) or not record_is_measured(rec):
120
+ return None
121
+ return {
122
+ "usd": float(rec.get("cost_usd") or 0.0),
123
+ "input_tokens": int(rec.get("input_tokens") or 0),
124
+ "output_tokens": int(rec.get("output_tokens") or 0),
125
+ "cache_read_tokens": int(rec.get("cache_read_tokens") or 0),
126
+ "cache_creation_tokens": int(rec.get("cache_creation_tokens") or 0),
127
+ "model": str(rec.get("model") or "") or None,
128
+ }
129
+
130
+
131
+ def build_replay(workspace):
132
+ """Reconstruct the run. Returns a dict, or raises SystemExit on no data."""
133
+ events_path = os.path.join(workspace, ".loki", "events.jsonl")
134
+ if not os.path.isfile(events_path):
135
+ print("no events at %s -- pass the workspace directory of a "
136
+ "completed run" % events_path, file=sys.stderr)
137
+ raise SystemExit(EXIT_NO_DATA)
138
+
139
+ events, skipped = _load_events(events_path)
140
+
141
+ # An events file that exists but yields nothing usable is a no-data run.
142
+ # The skipped count still gets reported: a FULLY corrupt file is the worst
143
+ # case of the bug rule 1 guards, so it must never exit silently.
144
+ if not events:
145
+ print("events.jsonl at %s has no parseable records "
146
+ "(%d unparseable line(s) skipped) -- nothing to replay"
147
+ % (events_path, skipped), file=sys.stderr)
148
+ raise SystemExit(EXIT_NO_DATA)
149
+
150
+ # stages[iteration][stage] = {"duration_s": .., "status": ..}
151
+ stages = {}
152
+ iteration_status = {}
153
+ for e in events:
154
+ etype = e.get("type") or e.get("event")
155
+ data = e.get("data") if isinstance(e.get("data"), dict) else {}
156
+ if etype == "stage_complete":
157
+ it = _iter_key(data.get("iteration"))
158
+ name = data.get("stage")
159
+ secs = data.get("duration_s")
160
+ if it is None or not name:
161
+ continue
162
+ # isinstance guard, not truthiness: duration_s of 0 is REAL data
163
+ # from a fast gate. Filtering on falsy would convert a genuine 0
164
+ # into "not recorded" and break the absent-stage rule the other
165
+ # way round.
166
+ if isinstance(secs, bool) or not isinstance(secs, (int, float)):
167
+ continue
168
+ stages.setdefault(it, {})[str(name)] = {
169
+ "duration_s": float(secs),
170
+ "status": str(data.get("status") or "unknown"),
171
+ }
172
+ elif etype == "iteration_complete":
173
+ it = _iter_key(data.get("iteration"))
174
+ if it is not None:
175
+ iteration_status[it] = str(data.get("status") or "unknown")
176
+
177
+ # An events.jsonl truncated at a LINE BOUNDARY -- what a killed run
178
+ # leaves -- parses cleanly, so skipped_lines is 0 and the recording looks
179
+ # whole while a whole iteration is missing. The efficiency records are a
180
+ # second, independent witness to which iterations existed; without them
181
+ # the missing iteration's cost silently vanishes from the total and from
182
+ # "largest cost contributor", with no signal at all. A killed run is
183
+ # exactly the case this tool exists for.
184
+ numbers = set(stages) | set(iteration_status)
185
+ try:
186
+ for name in os.listdir(os.path.join(
187
+ workspace, ".loki", "metrics", "efficiency")):
188
+ if name.startswith("iteration-") and name.endswith(".json"):
189
+ n = _iter_key(name[len("iteration-"):-len(".json")])
190
+ if n is not None:
191
+ numbers.add(n)
192
+ except Exception:
193
+ pass
194
+ numbers = sorted(numbers)
195
+ if not numbers:
196
+ print("no iteration or stage records in %s "
197
+ "(%d unparseable line(s) skipped) -- nothing to replay"
198
+ % (events_path, skipped), file=sys.stderr)
199
+ raise SystemExit(EXIT_NO_DATA)
200
+
201
+ # The stage vocabulary is the union actually observed, never a hardcoded
202
+ # list: a fixed list rots, and would claim stages were missing on runs
203
+ # that never had them.
204
+ all_stages = sorted({s for per in stages.values() for s in per})
205
+
206
+ iterations = []
207
+ prev_cost = None
208
+ prev_failed = set()
209
+ for n in numbers:
210
+ seen = stages.get(n, {})
211
+ cost = _read_cost(workspace, n)
212
+ failed = {s for s, v in seen.items() if v["status"] == "fail"}
213
+
214
+ # Only comparable when BOTH ends were measured. A delta against an
215
+ # unmeasured neighbour is a fabricated comparison.
216
+ delta = None
217
+ if cost is not None and prev_cost is not None:
218
+ delta = round(cost["usd"] - prev_cost["usd"], 6)
219
+
220
+ iterations.append({
221
+ "iteration": n,
222
+ "status": iteration_status.get(n),
223
+ "stages": {
224
+ s: (seen[s] if s in seen else None) for s in all_stages
225
+ },
226
+ "stages_not_recorded": [s for s in all_stages if s not in seen],
227
+ "cost": cost,
228
+ "cost_delta_usd": delta,
229
+ "cost_comparable": cost is not None and prev_cost is not None,
230
+ "gates_failed": sorted(failed),
231
+ "gates_failed_again": sorted(failed & prev_failed),
232
+ })
233
+ # NOT `cost if cost is not None else prev_cost`: carrying the last
234
+ # measured value across an unmeasured gap makes measured/unmeasured/
235
+ # measured compare iteration 3 against iteration 1 while labelling it
236
+ # "vs previous iteration". That is a fabricated comparison wearing a
237
+ # true-sounding label.
238
+ prev_cost = cost
239
+ prev_failed = failed
240
+
241
+ # Slowest stage overall, summed across iterations that recorded it.
242
+ totals = {}
243
+ for it in iterations:
244
+ for s, v in it["stages"].items():
245
+ if v is not None:
246
+ totals[s] = totals.get(s, 0.0) + v["duration_s"]
247
+ slowest = None
248
+ if totals:
249
+ name = max(totals, key=lambda k: totals[k])
250
+ slowest = {"stage": name, "total_s": round(totals[name], 1)}
251
+
252
+ measured = [it for it in iterations if it["cost"] is not None]
253
+ largest = None
254
+ if measured:
255
+ top = max(measured, key=lambda it: it["cost"]["usd"])
256
+ largest = {
257
+ "iteration": top["iteration"],
258
+ "usd": round(top["cost"]["usd"], 6),
259
+ "measured_iterations": len(measured),
260
+ "total_iterations": len(iterations),
261
+ }
262
+
263
+ return {
264
+ "workspace": os.path.abspath(workspace),
265
+ "read_only": True,
266
+ "skipped_lines": skipped,
267
+ "iterations": iterations,
268
+ "slowest_stage": slowest,
269
+ "largest_cost_contributor": largest,
270
+ "total_cost_usd": (
271
+ round(sum(it["cost"]["usd"] for it in measured), 6)
272
+ if measured else None
273
+ ),
274
+ }
275
+
276
+
277
+ def _fmt_cost(cost):
278
+ if cost is None:
279
+ return "cost not recorded"
280
+ return "$%.4f in %d / out %d / cache-read %d tok%s" % (
281
+ cost["usd"], cost["input_tokens"], cost["output_tokens"],
282
+ cost["cache_read_tokens"],
283
+ (" [%s]" % cost["model"]) if cost["model"] else "",
284
+ )
285
+
286
+
287
+ def render(rep):
288
+ out = []
289
+ out.append("RUN REPLAY -- reconstructed from artifacts only.")
290
+ out.append("Nothing was started and nothing was spent; this reads "
291
+ ".loki/ and exits.")
292
+ out.append(" workspace: %s" % rep["workspace"])
293
+ if rep["skipped_lines"]:
294
+ out.append(" UNPARSEABLE LINES SKIPPED: %d "
295
+ "(counted, not dropped -- this replay is reading an "
296
+ "incomplete recording)" % rep["skipped_lines"])
297
+ else:
298
+ out.append(" unparseable lines skipped: 0 (whole recording read)")
299
+ out.append("")
300
+
301
+ for i, it in enumerate(rep["iterations"]):
302
+ head = "ITERATION %d" % it["iteration"]
303
+ if it["status"]:
304
+ head += " [%s]" % it["status"]
305
+ out.append(head)
306
+ out.append("-" * 60)
307
+ for name, v in it["stages"].items():
308
+ if v is None:
309
+ out.append(" %-24s not recorded" % name)
310
+ else:
311
+ out.append(" %-24s %6.0fs %s"
312
+ % (name, v["duration_s"], v["status"]))
313
+ out.append(" %-24s %s" % ("cost", _fmt_cost(it["cost"])))
314
+ if it["cost_delta_usd"] is not None:
315
+ direction = "climbed" if it["cost_delta_usd"] > 0 else "fell"
316
+ out.append(" %-24s %s $%+.4f vs previous iteration"
317
+ % ("change", direction, it["cost_delta_usd"]))
318
+ elif it["cost"] is None:
319
+ out.append(" %-24s cannot compare, cost not recorded"
320
+ % "change")
321
+ elif i > 0:
322
+ out.append(" %-24s cannot compare, previous iteration's cost "
323
+ "not recorded" % "change")
324
+ if it["gates_failed"]:
325
+ out.append(" %-24s %s" % ("gates failed",
326
+ ", ".join(it["gates_failed"])))
327
+ if it["gates_failed_again"]:
328
+ out.append(" %-24s %s <-- failed in the previous iteration too"
329
+ % ("REPEAT FAILURE",
330
+ ", ".join(it["gates_failed_again"])))
331
+ out.append("")
332
+
333
+ out.append("=" * 60)
334
+ if rep["slowest_stage"]:
335
+ out.append("slowest stage overall : %s (%.0fs across the run)"
336
+ % (rep["slowest_stage"]["stage"],
337
+ rep["slowest_stage"]["total_s"]))
338
+ else:
339
+ out.append("slowest stage overall : no stage durations recorded")
340
+
341
+ lg = rep["largest_cost_contributor"]
342
+ if lg is None:
343
+ out.append("largest cost : cost not recorded for any "
344
+ "iteration")
345
+ else:
346
+ out.append("largest cost : iteration %d at $%.4f"
347
+ % (lg["iteration"], lg["usd"]))
348
+ if lg["measured_iterations"] < lg["total_iterations"]:
349
+ # A claim over a subset must say it is a claim over a subset.
350
+ out.append(" (largest among the %d of %d "
351
+ "iterations that recorded cost)"
352
+ % (lg["measured_iterations"], lg["total_iterations"]))
353
+ out.append("total measured cost : $%.4f" % rep["total_cost_usd"])
354
+ return "\n".join(out)
355
+
356
+
357
+ def main(argv=None):
358
+ ap = argparse.ArgumentParser(
359
+ description="Replay a completed run from its artifacts. Reads only; "
360
+ "starts nothing, spends nothing.")
361
+ ap.add_argument("workspace", nargs="?", default=".")
362
+ ap.add_argument("--json", action="store_true", dest="as_json")
363
+ args = ap.parse_args(argv)
364
+
365
+ rep = build_replay(args.workspace)
366
+ if args.as_json:
367
+ print(json.dumps(rep, indent=2))
368
+ else:
369
+ print(render(rep))
370
+ return 0
371
+
372
+
373
+ if __name__ == "__main__":
374
+ sys.exit(main())