loki-mode 8.0.1 → 8.0.2

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.0.1
6
+ # Loki Mode v8.0.2
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.0.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
472
+ **v8.0.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 8.0.1
1
+ 8.0.2
package/autonomy/loki CHANGED
@@ -3896,6 +3896,8 @@ WHYJSON
3896
3896
  _LOKI_WHY_LAST_ERROR="$loki_dir/state/LAST_ERROR.json" \
3897
3897
  _LOKI_WHY_UNCERTAINTY="$loki_dir/signals/UNCERTAINTY_ESCALATION" \
3898
3898
  _LOKI_WHY_CONVERGENCE="$loki_dir/council/convergence.log" \
3899
+ _LOKI_WHY_GATE="$loki_dir/signals/GATE_ESCALATION.json" \
3900
+ _LOKI_WHY_TARGET="${TARGET_DIR:-$(dirname "$loki_dir")}" \
3899
3901
  _LOKI_WHY_HANDOFFS="$loki_dir/memory/handoffs" python3 - <<'WHYTXT'
3900
3902
  import json, os, glob
3901
3903
  def load(p):
@@ -3953,6 +3955,11 @@ GUIDE = {
3953
3955
  "If no build is active it likely crashed; in durable mode (LOKI_DURABLE_STATE=1) a restart resumes, else loki start re-runs."),
3954
3956
  "exited": ("The build process exited mid-iteration (likely a crash, kill, or empty provider output).",
3955
3957
  "Read .loki/logs/ for the last error; if nothing is running it crashed -- re-run with loki start, or loki resume in durable mode."),
3958
+ # A real terminal state that had no entry, so `loki why` answered the one
3959
+ # question it exists to answer with "No diagnosis mapping for this status".
3960
+ "inconclusive_spec_contradiction": (
3961
+ "Loki could not reconcile parts of the spec, so it stopped rather than guess.",
3962
+ "The specific contradictions are in .loki/assumptions/ledger.md -- resolve them in the spec, then re-run."),
3956
3963
  }
3957
3964
  meaning, action = GUIDE.get(status, ("No diagnosis mapping for this status; see the raw fields below.",
3958
3965
  "Check loki status and .loki/logs/ for detail."))
@@ -3979,13 +3986,66 @@ if iters is not None:
3979
3986
  print(f" Iterations : {iters}")
3980
3987
  if comp.get("branch"):
3981
3988
  print(f" Branch : {comp['branch']}{comp_label}")
3982
- if comp.get("files_changed") is not None:
3983
- print(f" Changes : {comp.get('files_changed',0)} files (+{comp.get('insertions',0)}/-{comp.get('deletions',0)}){comp_label}")
3989
+ _fc = comp.get("files_changed")
3990
+ if _fc is not None:
3991
+ if _fc == 0:
3992
+ # A recorded 0 is NOT trustworthy on its own: a greenfield run whose
3993
+ # baseline never resolved records 0 while having committed real work.
3994
+ # Saying "0 files" flatly told users nothing was built on a run that
3995
+ # produced 9 files and 1300 insertions. Re-derive from git, and only
3996
+ # claim zero when git agrees.
3997
+ _real = None
3998
+ try:
3999
+ import subprocess as _sp
4000
+ _tgt = os.environ.get("_LOKI_WHY_TARGET") or "."
4001
+ _base = (comp.get("start_sha") or "").strip()
4002
+ if not _base or _base == "HEAD":
4003
+ _base = _sp.run(["git", "-C", _tgt, "hash-object", "-t", "tree", os.devnull],
4004
+ capture_output=True, text=True, timeout=10).stdout.strip()
4005
+ if _base:
4006
+ _o = _sp.run(["git", "-C", _tgt, "diff", "--shortstat", f"{_base}..HEAD",
4007
+ "--", ".", ":(exclude).loki/"],
4008
+ capture_output=True, text=True, timeout=15).stdout.strip()
4009
+ if _o:
4010
+ _real = _o
4011
+ except Exception:
4012
+ _real = None
4013
+ if _real:
4014
+ print(f" Changes : {_real} (re-derived; the run record said 0)")
4015
+ else:
4016
+ print(f" Changes : none recorded (the run did not resolve a baseline)")
4017
+ else:
4018
+ print(f" Changes : {_fc} files (+{comp.get('insertions',0)}/-{comp.get('deletions',0)}){comp_label}")
3984
4019
  if comp.get("pr_url"):
3985
4020
  print(f" PR : {comp['pr_url']}{comp_label}")
4021
+
4022
+ # Name the gate that actually stopped the run. Without this the user is told to
4023
+ # "resume", which the run's own pause notice warns will stop at the same gate.
4024
+ _gate = load(os.environ.get("_LOKI_WHY_GATE", "")) or {}
4025
+ _gname = str(_gate.get("gate", "") or "").strip()
4026
+ if _gname:
4027
+ _gc, _gt = _gate.get("count"), _gate.get("threshold")
4028
+ _detail = f"{_gname} (failed {_gc} times, threshold {_gt})" if isinstance(_gc, int) and isinstance(_gt, int) else _gname
4029
+ print(f" Blocked by : {_detail}")
4030
+
3986
4031
  print()
3987
4032
  print(f" What happened: {meaning}")
3988
- print(f" What to do : {action}")
4033
+ if _gname:
4034
+ print(f" What to do : Read the {_gname} findings and fix what they name, then resume.")
4035
+ print(f" Resuming unchanged will stop at the same gate again.")
4036
+ _art = str(_gate.get("latest_artifact", "") or "")
4037
+ if _art:
4038
+ # Make the pointer relative to the workspace. The recorded value is an
4039
+ # absolute path from the machine that ran the build -- on the preserved
4040
+ # run it names a /var/folders temp dir that no longer exists, so the one
4041
+ # actionable pointer in the whole notice led nowhere. Anchor on
4042
+ # ".loki/" so it works even when the run happened elsewhere.
4043
+ _i = _art.find(".loki/")
4044
+ if _i != -1:
4045
+ _art = _art[_i:]
4046
+ print(f" Findings: {_art}")
4047
+ else:
4048
+ print(f" What to do : {action}")
3989
4049
 
3990
4050
  # RUN-25 iter 22b/iter 23 (Wave D #6): when a run is PAUSED / live and actually
3991
4051
  # STUCK, name the REAL stall reason instead of the generic "resume". The runner
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "8.0.1"
10
+ __version__ = "8.0.2"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var k_=Object.create;var{getPrototypeOf:x_,defineProperty:oK,getOwnPropertyNames:S_}=Object;var y_=Object.prototype.hasOwnProperty;function b_(Z){return this[Z]}var __,f_,h_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?__??=new WeakMap:f_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?k_(x_(Z)):{};let K=X||!Z||!Z.__esModule?oK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of S_(Z))if(!y_.call(K,$))oK(K,$,{get:b_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var VQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var v_=(Z)=>Z;function g_(Z,X){this[Z]=v_.bind(null,X)}var c0=(Z,X)=>{for(var Q in X)oK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:g_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var t0=import.meta.require;var EO={};c0(EO,{lokiDir:()=>j0,homeLokiDir:()=>I4,findRepoRootForVersion:()=>tK,REPO_ROOT:()=>Q8});import{resolve as s7,dirname as rK}from"path";import{fileURLToPath as m_}from"url";import{existsSync as qQ}from"fs";import{homedir as u_}from"os";function p_(){let Z=DO;for(let X=0;X<6;X++){if(qQ(s7(Z,"VERSION"))&&qQ(s7(Z,"autonomy/run.sh")))return Z;let Q=rK(Z);if(Q===Z)break;Z=Q}return s7(DO,"..","..","..")}function tK(Z){let X=Z;for(let Q=0;Q<6;Q++){if(qQ(s7(X,"VERSION"))&&qQ(s7(X,"autonomy/run.sh")))return X;let Y=rK(X);if(Y===X)break;X=Y}return s7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??s7(process.cwd(),".loki")}function I4(){return s7(u_(),".loki")}var DO,Q8;var G8=p(()=>{DO=rK(m_(import.meta.url));Q8=p_()});import{readFileSync as d_}from"fs";import{resolve as c_,dirname as l_}from"path";import{fileURLToPath as i_}from"url";function _3(){if(f5!==null)return f5;let Z="8.0.1";if(typeof Z==="string"&&Z.length>0)return f5=Z,f5;try{let X=l_(i_(import.meta.url)),Q=tK(X);f5=d_(c_(Q,"VERSION"),"utf-8").trim()}catch{f5="unknown"}return f5}var f5=null;var GQ=p(()=>{G8()});var kO={};c0(kO,{runOrThrow:()=>Wf,run:()=>D0,readStreamCapped:()=>HQ,commandVersion:()=>qf,commandExists:()=>H9,ShellError:()=>eK,MAX_STDOUT_BYTES:()=>RO});async function HQ(Z,X=RO){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 D0(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([HQ(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 Wf(Z,X={}){let Q=await D0(Z,X);if(Q.exitCode!==0)throw new eK(`command failed (${Q.exitCode}): ${Z.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function H9(Z){let X=Vf(Z),Q=await D0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Vf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function qf(Z,X="--version"){if(!await H9(Z))return null;let Y=await D0([Z,X],{timeoutMs:5000});if(Y.exitCode!==0)return null;return((Y.stdout||Y.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var RO=16777216,eK;var k9=p(()=>{eK=class eK 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 n7(Z){return Gf?"":Z}var Gf,M0,k8,l0,gW0,a0,H8,X9,g;var S6=p(()=>{Gf=(process.env.NO_COLOR??"").length>0;M0=n7("\x1B[0;31m"),k8=n7("\x1B[0;32m"),l0=n7("\x1B[1;33m"),gW0=n7("\x1B[0;34m"),a0=n7("\x1B[0;36m"),H8=n7("\x1B[1m"),X9=n7("\x1B[2m"),g=n7("\x1B[0m")});import{existsSync as wf}from"fs";async function F7(){if(R4!==void 0)return R4;let Z="/opt/homebrew/bin/python3.12";if(wf(Z))return R4=Z,Z;let X=await H9("python3.12");if(X)return R4=X,X;let Q=await H9("python3");return R4=Q,Q}async function D7(Z,X={}){let Q=await F7();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return D0([Q,"-c",Z],X)}var R4;var o7=p(()=>{k9()});var oO={};c0(oO,{runStatus:()=>of});import{existsSync as Q9,readFileSync as h3,readdirSync as pO,statSync as dO}from"fs";import{resolve as h8,basename as mf}from"path";import{homedir as uf}from"os";function cO(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 lO(Z,X,Q){if(X===0)return null;let Y=Math.trunc(Z*100/X),J=Math.trunc(Z*BQ/X);if(J>BQ)J=BQ;let z=BQ-J,K=k8;if(Y>=80)K=M0;else if(Y>=50)K=l0;let $="=".repeat(Math.max(0,J))+" ".repeat(Math.max(0,z)),W=cO(Z),V=cO(X);return` ${H8}${Q}${g} ${K}[${$}]${g} ${Y}% (${W} / ${V})`}async function df(){if(await H9("jq"))return!0;return process.stdout.write(`${M0}Error: jq is required but not installed.${g}
2
+ var k_=Object.create;var{getPrototypeOf:x_,defineProperty:oK,getOwnPropertyNames:S_}=Object;var y_=Object.prototype.hasOwnProperty;function b_(Z){return this[Z]}var __,f_,h_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?__??=new WeakMap:f_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?k_(x_(Z)):{};let K=X||!Z||!Z.__esModule?oK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of S_(Z))if(!y_.call(K,$))oK(K,$,{get:b_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var VQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var v_=(Z)=>Z;function g_(Z,X){this[Z]=v_.bind(null,X)}var c0=(Z,X)=>{for(var Q in X)oK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:g_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var t0=import.meta.require;var EO={};c0(EO,{lokiDir:()=>j0,homeLokiDir:()=>I4,findRepoRootForVersion:()=>tK,REPO_ROOT:()=>Q8});import{resolve as s7,dirname as rK}from"path";import{fileURLToPath as m_}from"url";import{existsSync as qQ}from"fs";import{homedir as u_}from"os";function p_(){let Z=DO;for(let X=0;X<6;X++){if(qQ(s7(Z,"VERSION"))&&qQ(s7(Z,"autonomy/run.sh")))return Z;let Q=rK(Z);if(Q===Z)break;Z=Q}return s7(DO,"..","..","..")}function tK(Z){let X=Z;for(let Q=0;Q<6;Q++){if(qQ(s7(X,"VERSION"))&&qQ(s7(X,"autonomy/run.sh")))return X;let Y=rK(X);if(Y===X)break;X=Y}return s7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??s7(process.cwd(),".loki")}function I4(){return s7(u_(),".loki")}var DO,Q8;var G8=p(()=>{DO=rK(m_(import.meta.url));Q8=p_()});import{readFileSync as d_}from"fs";import{resolve as c_,dirname as l_}from"path";import{fileURLToPath as i_}from"url";function _3(){if(f5!==null)return f5;let Z="8.0.2";if(typeof Z==="string"&&Z.length>0)return f5=Z,f5;try{let X=l_(i_(import.meta.url)),Q=tK(X);f5=d_(c_(Q,"VERSION"),"utf-8").trim()}catch{f5="unknown"}return f5}var f5=null;var GQ=p(()=>{G8()});var kO={};c0(kO,{runOrThrow:()=>Wf,run:()=>D0,readStreamCapped:()=>HQ,commandVersion:()=>qf,commandExists:()=>H9,ShellError:()=>eK,MAX_STDOUT_BYTES:()=>RO});async function HQ(Z,X=RO){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 D0(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([HQ(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 Wf(Z,X={}){let Q=await D0(Z,X);if(Q.exitCode!==0)throw new eK(`command failed (${Q.exitCode}): ${Z.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function H9(Z){let X=Vf(Z),Q=await D0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Vf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function qf(Z,X="--version"){if(!await H9(Z))return null;let Y=await D0([Z,X],{timeoutMs:5000});if(Y.exitCode!==0)return null;return((Y.stdout||Y.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var RO=16777216,eK;var k9=p(()=>{eK=class eK 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 n7(Z){return Gf?"":Z}var Gf,M0,k8,l0,gW0,a0,H8,X9,g;var S6=p(()=>{Gf=(process.env.NO_COLOR??"").length>0;M0=n7("\x1B[0;31m"),k8=n7("\x1B[0;32m"),l0=n7("\x1B[1;33m"),gW0=n7("\x1B[0;34m"),a0=n7("\x1B[0;36m"),H8=n7("\x1B[1m"),X9=n7("\x1B[2m"),g=n7("\x1B[0m")});import{existsSync as wf}from"fs";async function F7(){if(R4!==void 0)return R4;let Z="/opt/homebrew/bin/python3.12";if(wf(Z))return R4=Z,Z;let X=await H9("python3.12");if(X)return R4=X,X;let Q=await H9("python3");return R4=Q,Q}async function D7(Z,X={}){let Q=await F7();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return D0([Q,"-c",Z],X)}var R4;var o7=p(()=>{k9()});var oO={};c0(oO,{runStatus:()=>of});import{existsSync as Q9,readFileSync as h3,readdirSync as pO,statSync as dO}from"fs";import{resolve as h8,basename as mf}from"path";import{homedir as uf}from"os";function cO(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 lO(Z,X,Q){if(X===0)return null;let Y=Math.trunc(Z*100/X),J=Math.trunc(Z*BQ/X);if(J>BQ)J=BQ;let z=BQ-J,K=k8;if(Y>=80)K=M0;else if(Y>=50)K=l0;let $="=".repeat(Math.max(0,J))+" ".repeat(Math.max(0,z)),W=cO(Z),V=cO(X);return` ${H8}${Q}${g} ${K}[${$}]${g} ${Y}% (${W} / ${V})`}async function df(){if(await H9("jq"))return!0;return process.stdout.write(`${M0}Error: jq is required but not installed.${g}
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)
@@ -1203,4 +1203,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
1203
1203
  `),2}case"start":{let{runStart:Y}=await Promise.resolve().then(() => (I_(),E_));return Y(Q)}default:return process.stderr.write(`Unknown command: ${X}
1204
1204
  `),process.stderr.write(P_),2}}mO();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var TW0=await MW0(Bun.argv.slice(2));process.exit(TW0);
1205
1205
 
1206
- //# debugId=1CA655940A54189964756E2164756E21
1206
+ //# debugId=0B4A1F7E210B77BE64756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '8.0.1'
60
+ __version__ = '8.0.2'
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.0.1",
4
+ "version": "8.0.2",
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.0.1",
5
+ "version": "8.0.2",
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",