loki-mode 9.37.0 → 9.38.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 v9.37.0
6
+ # Loki Mode v9.38.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -470,4 +470,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
470
470
 
471
471
  ---
472
472
 
473
- **v9.37.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
473
+ **v9.38.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 9.37.0
1
+ 9.38.0
@@ -127,6 +127,7 @@ def collect_efficiency(loki_dir):
127
127
  }
128
128
  model = ""
129
129
  collected = False
130
+ _records = []
130
131
  eff_dir = os.path.join(loki_dir, "metrics", "efficiency")
131
132
  try:
132
133
  names = sorted(os.listdir(eff_dir))
@@ -139,6 +140,7 @@ def collect_efficiency(loki_dir):
139
140
  if not isinstance(rec, dict):
140
141
  continue
141
142
  collected = True
143
+ _records.append(rec)
142
144
  cost["usd"] += _to_float(rec.get("cost_usd"))
143
145
  cost["input_tokens"] += _to_int(rec.get("input_tokens"))
144
146
  cost["output_tokens"] += _to_int(rec.get("output_tokens"))
@@ -171,11 +173,41 @@ def collect_efficiency(loki_dir):
171
173
  "cache_read_tokens": cost["cache_read_tokens"],
172
174
  "cache_creation_tokens": cost["cache_creation_tokens"],
173
175
  })
176
+ # COST AND TOKENS ARE MEASURED SEPARATELY, so they must be reported
177
+ # separately. `_observed` is true when EITHER a cost or a token count was
178
+ # seen, which is right for "did we measure anything" -- but it let a run with
179
+ # real token counts and NO priced record report usd=0.0 available=True. The
180
+ # receipt then asserts the run cost nothing while its own token counts prove
181
+ # work happened, which is a fabricated fact of exactly the kind this module
182
+ # exists to prevent. Measured, before this fix:
183
+ # [{"input_tokens":100,"output_tokens":200}] -> usd=0.0 available=True
184
+ # A partially-priced run has the same shape: an iteration missing cost_usd
185
+ # contributes 0.0 to the sum and silently understates total spend.
186
+ #
187
+ # So usd is now null unless at least one record actually carried a cost, and
188
+ # cost_partial marks the case where some did and some did not. Tokens are
189
+ # unaffected: they keep reporting whatever was observed.
190
+ # PRESENCE of the key, not its value. An explicit {"cost_usd": 0} is a
191
+ # GENUINE measured zero and must stay 0.0 -- a free cache-hit iteration is a
192
+ # real observation, and nulling it would be its own dishonesty (existing
193
+ # contract: test_genuine_zero_cost_stays_zero_not_null). What is unknown is a
194
+ # record that never carried the field at all.
195
+ _any_cost = any("cost_usd" in r for r in _records)
196
+ _missing_cost = sum(1 for r in _records if "cost_usd" not in r)
174
197
  if collected and _observed:
175
198
  # Round usd to a sane precision but keep it precise (anti-pattern:
176
199
  # round suspiciously-clean numbers). 4 decimals preserves odd values.
177
200
  cost["usd"] = round(cost["usd"], 4)
178
201
  cost["available"] = True
202
+ if not _any_cost:
203
+ # Tokens were observed but nothing was priced: unknown, not zero.
204
+ cost["usd"] = None
205
+ cost["cost_available"] = False
206
+ else:
207
+ cost["cost_available"] = True
208
+ # Some records priced, others not: the total is a LOWER BOUND.
209
+ cost["cost_partial"] = _missing_cost > 0
210
+ cost["cost_unpriced_records"] = _missing_cost
179
211
  else:
180
212
  # No record means unavailable, never an observed zero.
181
213
  for key in (
@@ -173,9 +173,21 @@ def _collect_council(loki_dir):
173
173
  ) + (" [round %s]" % round_tag if round_tag else ""),
174
174
  })
175
175
  continue
176
+ # Skip non-reviewer records. The nested path above already refuses a row
177
+ # with a blank role AND blank vote ("noise, not a reviewer"); this flat
178
+ # path did not, so any council/*.json that is not a verdict file was
179
+ # rendered as a phantom reviewer. Observed on a real run: the glob picked
180
+ # up `evidence-gate-details.json` and `state.json`, inflating a genuine
181
+ # 3-voter council to a larger roster of mostly-empty rows. The receipt's
182
+ # council block is the central trust signal, so a padded roster
183
+ # overstates how much independent review actually happened.
184
+ _flat_role = str(rec.get("role") or rec.get("reviewer") or "")
185
+ _flat_vote = str(rec.get("vote") or rec.get("decision") or "")
186
+ if not _flat_role and not _flat_vote:
187
+ continue
176
188
  reviewers.append({
177
- "role": str(rec.get("role") or rec.get("reviewer") or ""),
178
- "vote": str(rec.get("vote") or rec.get("decision") or ""),
189
+ "role": _flat_role,
190
+ "vote": _flat_vote,
179
191
  # Full text here; truncation to <=300 happens AFTER redaction so a
180
192
  # secret straddling the cap cannot be sliced into a sub-pattern
181
193
  # fragment that escapes the redactor.
@@ -1025,7 +1037,25 @@ def _empty_tree_sha(repo_dir):
1025
1037
 
1026
1038
  Diffing against this yields "everything that currently exists", which is the
1027
1039
  truthful baseline for a run that started from a repo with no commits.
1040
+
1041
+ REQUIRES AN ACTUAL REPOSITORY. `git hash-object -t tree /dev/null` is a pure
1042
+ hash computation: it succeeds OUTSIDE a repo too, returning the same
1043
+ constant 4b825dc6... So without this guard a run in a non-git directory got
1044
+ a well-formed, plausible-looking base_sha implying a real baseline had been
1045
+ captured, when no repository existed at all -- and every other git fact in
1046
+ the receipt (head_sha, tree_sha256, diff) was simultaneously empty. A
1047
+ fabricated-looking identifier beside empty siblings is worse than an honest
1048
+ empty string, because a reader checks the SHA and finds it valid.
1028
1049
  """
1050
+ try:
1051
+ inside = subprocess.run(
1052
+ ["git", "rev-parse", "--is-inside-work-tree"],
1053
+ cwd=repo_dir, capture_output=True, text=True, timeout=10,
1054
+ )
1055
+ if inside.returncode != 0:
1056
+ return ""
1057
+ except Exception:
1058
+ return ""
1029
1059
  try:
1030
1060
  out = subprocess.run(
1031
1061
  ["git", "hash-object", "-t", "tree", os.devnull],
package/autonomy/run.sh CHANGED
@@ -831,6 +831,17 @@ print(catalog["providers"]["claude"]["cli_aliases"].get(os.environ["_LOKI_SELECT
831
831
 
832
832
  export LOKI_PHASE_UNIT_TESTS LOKI_PHASE_E2E_TESTS
833
833
  export LOKI_PHASE_CODE_REVIEW LOKI_PHASE_SECURITY LOKI_PHASE_ACCESSIBILITY
834
+ # EXPORT THE PHASES THIS PROFILE TURNS OFF, not only the ones it leaves on.
835
+ # The receipt derives quality_gates.disabled_phases by scanning the
836
+ # environment for LOKI_PHASE_* set to false (proof-generator.py:470-478), so
837
+ # a phase that is disabled but never exported is INVISIBLE to it: the receipt
838
+ # reported disabled_phases [] and all_phases_enabled true while six phases
839
+ # were switched off. That defeats the field's stated purpose -- "a receipt
840
+ # must be able to say what was NOT checked" -- and makes a narrowed run look
841
+ # identical to a full one. The sibling loki_apply_scoped_change_profile
842
+ # already exports the phases it changes; this one exported only its enables.
843
+ export LOKI_PHASE_API_TESTS LOKI_PHASE_INTEGRATION LOKI_PHASE_PERFORMANCE
844
+ export LOKI_PHASE_REGRESSION LOKI_PHASE_UAT LOKI_PHASE_WEB_RESEARCH
834
845
  export LOKI_COUNCIL_ENABLED LOKI_EVIDENCE_GATE LOKI_PROOF_GATE LOKI_PROOF
835
846
  export LOKI_DASHBOARD LOKI_PARALLEL_MODE LOKI_MAX_PARALLEL_AGENTS
836
847
  export LOKI_MAX_ITERATIONS LOKI_MAX_RETRIES LOKI_BASE_WAIT LOKI_MAX_WAIT
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "9.37.0"
10
+ __version__ = "9.38.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v9.37.0
5
+ **Version:** v9.38.0
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var _t=Object.create;var{getPrototypeOf:vt,defineProperty:jG,getOwnPropertyNames:ht}=Object;var gt=Object.prototype.hasOwnProperty;function mt($){return this[$]}var ut,dt,pt=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?ut??=new WeakMap:dt??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?_t(vt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of ht($))if(!gt.call(J,q))jG(J,q,{get:mt.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var ct=($)=>$;function lt($,X){this[$]=ct.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:lt.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var UR={};B1(UR,{lokiDir:()=>h0,homeLokiDir:()=>WQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as it}from"url";import{existsSync as Zq}from"fs";import{homedir as at}from"os";function ot(){let $=YR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(YR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function WQ(){return h2(at(),".loki")}var YR,L1;var k1=s(()=>{YR=LG(it(import.meta.url));L1=ot()});import{readFileSync as st}from"fs";import{resolve as nt,dirname as rt}from"path";import{fileURLToPath as tt}from"url";function j9(){if(n3!==null)return n3;let $="9.37.0";if(typeof $==="string"&&$.length>0)return n3=$,n3;try{let X=rt(tt(import.meta.url)),Q=AG(X);n3=st(nt(Q,"VERSION"),"utf-8").trim()}catch{n3="unknown"}return n3}var n3=null;var Kq=s(()=>{k1()});var OR={};B1(OR,{runOrThrow:()=>Be,run:()=>$1,readStreamCapped:()=>Jq,commandVersion:()=>Me,commandExists:()=>g5,ShellError:()=>TG,MAX_STDOUT_BYTES:()=>MR});async function Jq($,X=MR){let Q=$.getReader(),z=new TextDecoder,Z="",K=0;try{while(K<X){let{done:J,value:q}=await Q.read();if(J)break;if(!q)continue;if(K+=q.byteLength,K>X){let V=q.byteLength-(K-X);Z+=z.decode(q.subarray(0,V),{stream:!0});break}Z+=z.decode(q,{stream:!0})}Z+=z.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return Z}async function $1($,X={}){let Q=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),z,Z;if(X.timeoutMs&&X.timeoutMs>0)z=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[K,J,q]=await Promise.all([Jq(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:K,stderr:J,exitCode:q}}finally{if(z)clearTimeout(z);if(Z)clearTimeout(Z)}}async function Be($,X={}){let Q=await $1($,X);if(Q.exitCode!==0)throw new TG(`command failed (${Q.exitCode}): ${$.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function g5($){let X=Ne($),Q=await $1(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Ne($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function Me($,X="--version"){if(!await g5($))return null;let z=await $1([$,X],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var MR=16777216,TG;var y8=s(()=>{TG=class TG extends Error{message;exitCode;stdout;stderr;constructor($,X,Q,z){super($);this.message=$;this.exitCode=X;this.stdout=Q;this.stderr=z;this.name="ShellError"}}});function g2($){return Oe?"":$}var Oe,p0,$5,q1,D61,A1,f1,m5,r;var t7=s(()=>{Oe=(process.env.NO_COLOR??"").length>0;p0=g2("\x1B[0;31m"),$5=g2("\x1B[0;32m"),q1=g2("\x1B[1;33m"),D61=g2("\x1B[0;34m"),A1=g2("\x1B[0;36m"),f1=g2("\x1B[1m"),m5=g2("\x1B[2m"),r=g2("\x1B[0m")});import{existsSync as we}from"fs";async function Z2(){if(GQ!==void 0)return GQ;let $="/opt/homebrew/bin/python3.12";if(we($))return GQ=$,$;let X=await g5("python3.12");if(X)return GQ=X,X;let Q=await g5("python3");return GQ=Q,Q}async function I4($,X={}){let Q=await Z2();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return $1([Q,"-c",$],X)}var GQ;var m2=s(()=>{y8()});var vR={};B1(vR,{runStatus:()=>te});import{existsSync as u5,readFileSync as C9,readdirSync as ER,statSync as xR}from"fs";import{resolve as A5,basename as pe}from"path";import{homedir as ce}from"os";function kR($){let X=Math.trunc($);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 SR($,X,Q){if(X===0)return null;let z=Math.trunc($*100/X),Z=Math.trunc($*Vq/X);if(Z>Vq)Z=Vq;let K=Vq-Z,J=$5;if(z>=80)J=p0;else if(z>=50)J=q1;let q="=".repeat(Math.max(0,Z))+" ".repeat(Math.max(0,K)),V=kR($),Y=kR(X);return` ${f1}${Q}${r} ${J}[${q}]${r} ${z}% (${V} / ${Y})`}async function ie(){if(await g5("jq"))return!0;return process.stdout.write(`${p0}Error: jq is required but not installed.${r}
2
+ var _t=Object.create;var{getPrototypeOf:vt,defineProperty:jG,getOwnPropertyNames:ht}=Object;var gt=Object.prototype.hasOwnProperty;function mt($){return this[$]}var ut,dt,pt=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?ut??=new WeakMap:dt??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?_t(vt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of ht($))if(!gt.call(J,q))jG(J,q,{get:mt.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var ct=($)=>$;function lt($,X){this[$]=ct.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:lt.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var UR={};B1(UR,{lokiDir:()=>h0,homeLokiDir:()=>WQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as it}from"url";import{existsSync as Zq}from"fs";import{homedir as at}from"os";function ot(){let $=YR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(YR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function WQ(){return h2(at(),".loki")}var YR,L1;var k1=s(()=>{YR=LG(it(import.meta.url));L1=ot()});import{readFileSync as st}from"fs";import{resolve as nt,dirname as rt}from"path";import{fileURLToPath as tt}from"url";function j9(){if(n3!==null)return n3;let $="9.38.0";if(typeof $==="string"&&$.length>0)return n3=$,n3;try{let X=rt(tt(import.meta.url)),Q=AG(X);n3=st(nt(Q,"VERSION"),"utf-8").trim()}catch{n3="unknown"}return n3}var n3=null;var Kq=s(()=>{k1()});var OR={};B1(OR,{runOrThrow:()=>Be,run:()=>$1,readStreamCapped:()=>Jq,commandVersion:()=>Me,commandExists:()=>g5,ShellError:()=>TG,MAX_STDOUT_BYTES:()=>MR});async function Jq($,X=MR){let Q=$.getReader(),z=new TextDecoder,Z="",K=0;try{while(K<X){let{done:J,value:q}=await Q.read();if(J)break;if(!q)continue;if(K+=q.byteLength,K>X){let V=q.byteLength-(K-X);Z+=z.decode(q.subarray(0,V),{stream:!0});break}Z+=z.decode(q,{stream:!0})}Z+=z.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return Z}async function $1($,X={}){let Q=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),z,Z;if(X.timeoutMs&&X.timeoutMs>0)z=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[K,J,q]=await Promise.all([Jq(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:K,stderr:J,exitCode:q}}finally{if(z)clearTimeout(z);if(Z)clearTimeout(Z)}}async function Be($,X={}){let Q=await $1($,X);if(Q.exitCode!==0)throw new TG(`command failed (${Q.exitCode}): ${$.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function g5($){let X=Ne($),Q=await $1(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Ne($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function Me($,X="--version"){if(!await g5($))return null;let z=await $1([$,X],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var MR=16777216,TG;var y8=s(()=>{TG=class TG extends Error{message;exitCode;stdout;stderr;constructor($,X,Q,z){super($);this.message=$;this.exitCode=X;this.stdout=Q;this.stderr=z;this.name="ShellError"}}});function g2($){return Oe?"":$}var Oe,p0,$5,q1,D61,A1,f1,m5,r;var t7=s(()=>{Oe=(process.env.NO_COLOR??"").length>0;p0=g2("\x1B[0;31m"),$5=g2("\x1B[0;32m"),q1=g2("\x1B[1;33m"),D61=g2("\x1B[0;34m"),A1=g2("\x1B[0;36m"),f1=g2("\x1B[1m"),m5=g2("\x1B[2m"),r=g2("\x1B[0m")});import{existsSync as we}from"fs";async function Z2(){if(GQ!==void 0)return GQ;let $="/opt/homebrew/bin/python3.12";if(we($))return GQ=$,$;let X=await g5("python3.12");if(X)return GQ=X,X;let Q=await g5("python3");return GQ=Q,Q}async function I4($,X={}){let Q=await Z2();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return $1([Q,"-c",$],X)}var GQ;var m2=s(()=>{y8()});var vR={};B1(vR,{runStatus:()=>te});import{existsSync as u5,readFileSync as C9,readdirSync as ER,statSync as xR}from"fs";import{resolve as A5,basename as pe}from"path";import{homedir as ce}from"os";function kR($){let X=Math.trunc($);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 SR($,X,Q){if(X===0)return null;let z=Math.trunc($*100/X),Z=Math.trunc($*Vq/X);if(Z>Vq)Z=Vq;let K=Vq-Z,J=$5;if(z>=80)J=p0;else if(z>=50)J=q1;let q="=".repeat(Math.max(0,Z))+" ".repeat(Math.max(0,K)),V=kR($),Y=kR(X);return` ${f1}${Q}${r} ${J}[${q}]${r} ${z}% (${V} / ${Y})`}async function ie(){if(await g5("jq"))return!0;return process.stdout.write(`${p0}Error: jq is required but not installed.${r}
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)
@@ -1337,4 +1337,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
1337
1337
  `),2}case"start":{let{runStart:z}=await Promise.resolve().then(() => (yt(),St));return z(Q)}default:return process.stderr.write(`Unknown command: ${X}
1338
1338
  `),process.stderr.write(bt),2}}wR();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var K61=await Z61(Bun.argv.slice(2));process.exit(K61);
1339
1339
 
1340
- //# debugId=DBEFF219B28673BB40CF832D5C8A4D9F
1340
+ //# debugId=E2FFCEDA097D2FB9EF9268C108032EA0
package/mcp/__init__.py CHANGED
@@ -75,4 +75,4 @@ try:
75
75
  except ImportError:
76
76
  __all__ = ['mcp']
77
77
 
78
- __version__ = '9.37.0'
78
+ __version__ = '9.38.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": "9.37.0",
4
+ "version": "9.38.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, opencode).",
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": "9.37.0",
5
+ "version": "9.38.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",