loki-mode 8.19.2 → 8.19.4

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.19.2
6
+ # Loki Mode v8.19.4
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.19.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
472
+ **v8.19.4 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 8.19.2
1
+ 8.19.4
@@ -1278,7 +1278,14 @@ def _build_proof(args, loki_dir, target_dir, repo_root):
1278
1278
  "item": _name,
1279
1279
  "status": "disabled",
1280
1280
  "reason": "switched off for this run, so the check never ran",
1281
+ # Marks an entry appended AFTER the headline was computed. The
1282
+ # verifier filters on this flag rather than on a status string:
1283
+ # statuses will keep being added, and a filter keyed on one of them
1284
+ # silently breaks the next time -- which is exactly what happened
1285
+ # when the unrun-security entry landed with status "not_run".
1286
+ "post_headline": True,
1281
1287
  })
1288
+
1282
1289
  honesty = {
1283
1290
  "headline": headline,
1284
1291
  "degraded": degraded,
@@ -397,8 +397,18 @@ def _recorded_degraded(proof):
397
397
  # Accusing an honest receipt of forgery is the worst failure this
398
398
  # verifier can have, so the re-derivation must use exactly the list
399
399
  # the generator used: everything except the post-hoc gate entries.
400
+ # Entries the generator appended AFTER computing the headline were
401
+ # never an input to the recorded value, so re-deriving with them
402
+ # makes an honest proof look edited. Keyed on the explicit
403
+ # post_headline flag, not on a status string: the first version of
404
+ # this filter matched status=="disabled" and broke the moment a
405
+ # second post-headline entry arrived with a different status.
406
+ # status=="disabled" is still honoured for proofs written by
407
+ # v8.19.0-v8.19.2, which predate the flag.
400
408
  deg = [d for d in deg
401
- if not (isinstance(d, dict) and d.get("status") == "disabled")]
409
+ if not (isinstance(d, dict)
410
+ and (d.get("post_headline") is True
411
+ or d.get("status") == "disabled"))]
402
412
  return [str(x) for x in deg]
403
413
  return []
404
414
 
@@ -425,8 +435,18 @@ def _recorded_degraded_raw(proof):
425
435
  # Accusing an honest receipt of forgery is the worst failure this
426
436
  # verifier can have, so the re-derivation must use exactly the list
427
437
  # the generator used: everything except the post-hoc gate entries.
438
+ # Entries the generator appended AFTER computing the headline were
439
+ # never an input to the recorded value, so re-deriving with them
440
+ # makes an honest proof look edited. Keyed on the explicit
441
+ # post_headline flag, not on a status string: the first version of
442
+ # this filter matched status=="disabled" and broke the moment a
443
+ # second post-headline entry arrived with a different status.
444
+ # status=="disabled" is still honoured for proofs written by
445
+ # v8.19.0-v8.19.2, which predate the flag.
428
446
  deg = [d for d in deg
429
- if not (isinstance(d, dict) and d.get("status") == "disabled")]
447
+ if not (isinstance(d, dict)
448
+ and (d.get("post_headline") is True
449
+ or d.get("status") == "disabled"))]
430
450
  return deg
431
451
  return []
432
452
 
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "8.19.2"
10
+ __version__ = "8.19.4"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -0,0 +1,104 @@
1
+ # Founder Decisions: trust semantics (2026-08-01)
2
+
3
+ Two questions this session surfaced that I deliberately did not answer, because
4
+ answering them changes what the word "Verified" means on a receipt. Both are
5
+ recorded with the measurement that raised them, so the decision can be made on
6
+ evidence rather than on my summary of it.
7
+
8
+ Everything else from this session shipped. These are the only open items.
9
+
10
+ ## Why these are yours and not mine
11
+
12
+ `autonomy/lib/proof-generator.py` already records the rule, in the note on the
13
+ functional-verification fact:
14
+
15
+ > Making functional-satisfaction gate the green headline is a trust-semantics
16
+ > product decision (council + founder), the second half of FV-2. Recording it
17
+ > first lets the signal be seen and validated safely.
18
+
19
+ Record first, gate later. Both items below follow that pattern: the fact is now
20
+ recorded and visible on every surface, and the verdict is unchanged pending your
21
+ call.
22
+
23
+ ---
24
+
25
+ ## Decision 1: should a disabled trust gate demote "Verified"?
26
+
27
+ **Status today:** the gate is recorded and shown everywhere. The headline is
28
+ unchanged.
29
+
30
+ A run can disable any phase by environment variable. When code review or
31
+ security is switched off, the proof now records it (`quality_gates.disabled_phases`),
32
+ the honesty ledger carries a per-gate entry, both receipts name it, and the
33
+ plain-English owner page refuses to show a green "ready" badge.
34
+
35
+ What it does **not** do is change `honesty.headline`. A receipt with code review
36
+ disabled can still read `VERIFIED`.
37
+
38
+ **The case for demoting it:** "Verified" on a receipt with two correctness gates
39
+ switched off is defensible only if it means "verified as far as it was checked".
40
+ A reader who has not read the gaps list will not make that distinction.
41
+
42
+ **The case against:** the headline is re-derived by `proof-verify` to detect
43
+ tampering. Changing what feeds it changes what an honest proof looks like, and
44
+ older signed receipts would re-derive differently. There is also a real risk of
45
+ teaching users to ignore the headline if it moves for reasons they consider
46
+ routine.
47
+
48
+ **If you decide to demote:** the change is in `_compute_headline`, and the
49
+ disabled-gate entries must then be included in the verifier's re-derivation
50
+ rather than filtered out (`_recorded_degraded_raw`). Both halves must move
51
+ together or every honest receipt is reported as forged, which is exactly the bug
52
+ v8.19.2 fixed.
53
+
54
+ ---
55
+
56
+ ## Decision 2: is an unrun security scan a gap?
57
+
58
+ **Status today:** deliberately not a gap. There is a test asserting it.
59
+
60
+ Measured on a real generated proof, with a clean build and passing tests:
61
+
62
+ ```
63
+ headline : VERIFIED
64
+ gaps listed : []
65
+ security fact : {'ran': False, 'status': 'not_run', 'high_active': 0}
66
+ ```
67
+
68
+ So a receipt reads `VERIFIED` with an empty gaps list while the security scan
69
+ never ran at all.
70
+
71
+ Tests and build are both flagged as gaps when they do not run. Security is
72
+ flagged only when it **ran and found** an active HIGH finding. The asymmetry is
73
+ intentional: `tests/test_proof_generator.py::test_no_security_file_is_not_a_gap`
74
+ states it outright, "Absence of a scan is not a security gap (the gate did not
75
+ run)".
76
+
77
+ I prototyped changing it and reverted, because overriding a documented decision
78
+ with a test guarding it is not a call I should make silently.
79
+
80
+ **The case for treating it as a gap:** the honesty ledger exists so "a reader
81
+ sees exactly what was NOT verified rather than inferring it from silence"
82
+ (`_compute_degraded`'s own docstring). An unrun security scan is precisely that,
83
+ and it is the one omission a reader is least likely to notice on their own.
84
+
85
+ **The case against:** most projects have no security scanner configured, so
86
+ every receipt would carry a security gap that means nothing about that
87
+ project's quality. A gap list that is never empty is a gap list nobody reads,
88
+ and that costs more than the omission.
89
+
90
+ **A middle option:** treat it as a gap only when the project has a scanner
91
+ configured and it did not run. That distinguishes "not applicable" from
92
+ "skipped", which is the distinction the current binary answer cannot express.
93
+
94
+ ---
95
+
96
+ ## What I would recommend, briefly
97
+
98
+ Decision 2 first, using the middle option: it removes a real blind spot without
99
+ padding every receipt. Decision 1 is the larger change and depends on how you
100
+ want "Verified" to read to someone who did not run the build; it is worth
101
+ deciding deliberately rather than inheriting.
102
+
103
+ Neither is blocking. Both facts are already visible to anyone who reads the
104
+ receipt.
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var h_=Object.create;var{getPrototypeOf:v_,defineProperty:rK,getOwnPropertyNames:g_}=Object;var m_=Object.prototype.hasOwnProperty;function u_(Z){return this[Z]}var p_,d_,c_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?p_??=new WeakMap:d_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?h_(v_(Z)):{};let K=X||!Z||!Z.__esModule?rK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of g_(Z))if(!m_.call(K,$))rK(K,$,{get:u_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var qQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var l_=(Z)=>Z;function i_(Z,X){this[Z]=l_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)rK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:i_.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:()=>P4,findRepoRootForVersion:()=>eK,REPO_ROOT:()=>r0});import{resolve as n7,dirname as tK}from"path";import{fileURLToPath as a_}from"url";import{existsSync as GQ}from"fs";import{homedir as s_}from"os";function n_(){let Z=RO;for(let X=0;X<6;X++){if(GQ(n7(Z,"VERSION"))&&GQ(n7(Z,"autonomy/run.sh")))return Z;let Q=tK(Z);if(Q===Z)break;Z=Q}return n7(RO,"..","..","..")}function eK(Z){let X=Z;for(let Q=0;Q<6;Q++){if(GQ(n7(X,"VERSION"))&&GQ(n7(X,"autonomy/run.sh")))return X;let Y=tK(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function P4(){return n7(s_(),".loki")}var RO,r0;var G8=p(()=>{RO=tK(a_(import.meta.url));r0=n_()});import{readFileSync as o_}from"fs";import{resolve as r_,dirname as t_}from"path";import{fileURLToPath as e_}from"url";function f3(){if(h5!==null)return h5;let Z="8.19.2";if(typeof Z==="string"&&Z.length>0)return h5=Z,h5;try{let X=t_(e_(import.meta.url)),Q=eK(X);h5=o_(r_(Q,"VERSION"),"utf-8").trim()}catch{h5="unknown"}return h5}var h5=null;var HQ=p(()=>{G8()});var bO={};l0(bO,{runOrThrow:()=>Of,run:()=>E0,readStreamCapped:()=>UQ,commandVersion:()=>Af,commandExists:()=>X9,ShellError:()=>Z$,MAX_STDOUT_BYTES:()=>yO});async function UQ(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([UQ(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 Of(Z,X={}){let Q=await E0(Z,X);if(Q.exitCode!==0)throw new Z$(`command failed (${Q.exitCode}): ${Z.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function X9(Z){let X=Lf(Z),Q=await E0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Lf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function Af(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,Z$;var x9=p(()=>{Z$=class Z$ 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 jf?"":Z}var jf,L0,k8,p0,ZV0,i0,H8,Q9,v;var S6=p(()=>{jf=(process.env.NO_COLOR??"").length>0;L0=o7("\x1B[0;31m"),k8=o7("\x1B[0;32m"),p0=o7("\x1B[1;33m"),ZV0=o7("\x1B[0;34m"),i0=o7("\x1B[0;36m"),H8=o7("\x1B[1m"),Q9=o7("\x1B[2m"),v=o7("\x1B[0m")});import{existsSync as xf}from"fs";async function E7(){if(k4!==void 0)return k4;let Z="/opt/homebrew/bin/python3.12";if(xf(Z))return k4=Z,Z;let X=await X9("python3.12");if(X)return k4=X,X;let Q=await X9("python3");return k4=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 k4;var r7=p(()=>{x9()});var ZL={};l0(ZL,{runStatus:()=>Yh});import{existsSync as Y9,readFileSync as v3,readdirSync as iO,statSync as aO}from"fs";import{resolve as h8,basename as sf}from"path";import{homedir as nf}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*NQ/X);if(J>NQ)J=NQ;let z=NQ-J,K=k8;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` ${H8}${Q}${v} ${K}[${$}]${v} ${Y}% (${W} / ${V})`}async function rf(){if(await X9("jq"))return!0;return process.stdout.write(`${L0}Error: jq is required but not installed.${v}
2
+ var h_=Object.create;var{getPrototypeOf:v_,defineProperty:rK,getOwnPropertyNames:g_}=Object;var m_=Object.prototype.hasOwnProperty;function u_(Z){return this[Z]}var p_,d_,c_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?p_??=new WeakMap:d_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?h_(v_(Z)):{};let K=X||!Z||!Z.__esModule?rK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of g_(Z))if(!m_.call(K,$))rK(K,$,{get:u_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var qQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var l_=(Z)=>Z;function i_(Z,X){this[Z]=l_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)rK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:i_.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:()=>P4,findRepoRootForVersion:()=>eK,REPO_ROOT:()=>r0});import{resolve as n7,dirname as tK}from"path";import{fileURLToPath as a_}from"url";import{existsSync as GQ}from"fs";import{homedir as s_}from"os";function n_(){let Z=RO;for(let X=0;X<6;X++){if(GQ(n7(Z,"VERSION"))&&GQ(n7(Z,"autonomy/run.sh")))return Z;let Q=tK(Z);if(Q===Z)break;Z=Q}return n7(RO,"..","..","..")}function eK(Z){let X=Z;for(let Q=0;Q<6;Q++){if(GQ(n7(X,"VERSION"))&&GQ(n7(X,"autonomy/run.sh")))return X;let Y=tK(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function P4(){return n7(s_(),".loki")}var RO,r0;var G8=p(()=>{RO=tK(a_(import.meta.url));r0=n_()});import{readFileSync as o_}from"fs";import{resolve as r_,dirname as t_}from"path";import{fileURLToPath as e_}from"url";function f3(){if(h5!==null)return h5;let Z="8.19.4";if(typeof Z==="string"&&Z.length>0)return h5=Z,h5;try{let X=t_(e_(import.meta.url)),Q=eK(X);h5=o_(r_(Q,"VERSION"),"utf-8").trim()}catch{h5="unknown"}return h5}var h5=null;var HQ=p(()=>{G8()});var bO={};l0(bO,{runOrThrow:()=>Of,run:()=>E0,readStreamCapped:()=>UQ,commandVersion:()=>Af,commandExists:()=>X9,ShellError:()=>Z$,MAX_STDOUT_BYTES:()=>yO});async function UQ(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([UQ(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 Of(Z,X={}){let Q=await E0(Z,X);if(Q.exitCode!==0)throw new Z$(`command failed (${Q.exitCode}): ${Z.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function X9(Z){let X=Lf(Z),Q=await E0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Lf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function Af(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,Z$;var x9=p(()=>{Z$=class Z$ 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 jf?"":Z}var jf,L0,k8,p0,ZV0,i0,H8,Q9,v;var S6=p(()=>{jf=(process.env.NO_COLOR??"").length>0;L0=o7("\x1B[0;31m"),k8=o7("\x1B[0;32m"),p0=o7("\x1B[1;33m"),ZV0=o7("\x1B[0;34m"),i0=o7("\x1B[0;36m"),H8=o7("\x1B[1m"),Q9=o7("\x1B[2m"),v=o7("\x1B[0m")});import{existsSync as xf}from"fs";async function E7(){if(k4!==void 0)return k4;let Z="/opt/homebrew/bin/python3.12";if(xf(Z))return k4=Z,Z;let X=await X9("python3.12");if(X)return k4=X,X;let Q=await X9("python3");return k4=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 k4;var r7=p(()=>{x9()});var ZL={};l0(ZL,{runStatus:()=>Yh});import{existsSync as Y9,readFileSync as v3,readdirSync as iO,statSync as aO}from"fs";import{resolve as h8,basename as sf}from"path";import{homedir as nf}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*NQ/X);if(J>NQ)J=NQ;let z=NQ-J,K=k8;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` ${H8}${Q}${v} ${K}[${$}]${v} ${Y}% (${W} / ${V})`}async function rf(){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)
@@ -1222,4 +1222,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
1222
1222
  `),2}case"start":{let{runStart:Y}=await Promise.resolve().then(() => (b_(),y_));return Y(Q)}default:return process.stderr.write(`Unknown command: ${X}
1223
1223
  `),process.stderr.write(__),2}}cO();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var fW0=await _W0(Bun.argv.slice(2));process.exit(fW0);
1224
1224
 
1225
- //# debugId=445E054BED8A9A5864756E2164756E21
1225
+ //# debugId=7703A7468D3C017664756E2164756E21
package/mcp/__init__.py CHANGED
@@ -75,4 +75,4 @@ try:
75
75
  except ImportError:
76
76
  __all__ = ['mcp']
77
77
 
78
- __version__ = '8.19.2'
78
+ __version__ = '8.19.4'
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.19.2",
4
+ "version": "8.19.4",
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.19.2",
5
+ "version": "8.19.4",
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",