loki-mode 8.45.0 → 8.47.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.45.0
6
+ # Loki Mode v8.47.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.45.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
472
+ **v8.47.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 8.45.0
1
+ 8.47.0
package/autonomy/run.sh CHANGED
@@ -10583,7 +10583,6 @@ _loki_gate_stuck() {
10583
10583
  local threshold="${LOKI_GATE_STUCK_THRESHOLD:-3}"
10584
10584
 
10585
10585
  [ "${LOKI_GATE_STUCK_ABORT:-1}" = "0" ] && return 1
10586
- [ "${count:-0}" -lt "$threshold" ] 2>/dev/null && return 1
10587
10586
  [ -n "$reason_file" ] && [ -f "$reason_file" ] || return 1
10588
10587
 
10589
10588
  local cur prev_file prev
@@ -10592,11 +10591,26 @@ _loki_gate_stuck() {
10592
10591
  cur="$(head -1 "$reason_file" 2>/dev/null)" || return 1
10593
10592
  [ -n "$cur" ] || return 1
10594
10593
 
10594
+ # RECORD ON EVERY FAILURE, compare only at threshold.
10595
+ #
10596
+ # The recording used to sit behind the threshold check, so the first
10597
+ # comparison could not happen until count == threshold+1. Replayed against
10598
+ # the REAL FireLater artifact that motivated this feature, the abort fired
10599
+ # at iteration 4 -- and that run ended at 3. The safety valve would have
10600
+ # missed the exact case it was built for, by one iteration.
10601
+ #
10602
+ # Found only by replaying the preserved .loki/quality/mutation-findings.txt
10603
+ # rather than trusting the unit test, which used synthetic counts and so
10604
+ # never exercised the real arrival order.
10595
10605
  prev_file="${TARGET_DIR:-.}/.loki/quality/gate-stuck-${gate_name}.last"
10596
10606
  prev="$(cat "$prev_file" 2>/dev/null || true)"
10597
10607
  ( mkdir -p "$(dirname "$prev_file")" 2>/dev/null \
10598
10608
  && printf '%s\n' "$cur" > "$prev_file" 2>/dev/null ) || true
10599
10609
 
10610
+ # Below threshold: the reason is now on record for the next comparison, but
10611
+ # this is not yet enough evidence to stop.
10612
+ [ "${count:-0}" -lt "$threshold" ] 2>/dev/null && return 1
10613
+
10600
10614
  [ -n "$prev" ] && [ "$prev" = "$cur" ] && return 0
10601
10615
  return 1
10602
10616
  }
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "8.45.0"
10
+ __version__ = "8.47.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -65,7 +65,11 @@ work would have touched it.
65
65
 
66
66
  Ranked by measured contribution to iteration count.
67
67
 
68
- ### F0 -- A gate that cannot pass must abort, not iterate (highest value)
68
+ **STATUS 2026-08-01: F0, F2 and F3 SHIPPED. F1 was already built (verified, no
69
+ work needed). F4's mechanism exists; only its default is open, and that needs
70
+ real-build measurement rather than a guess.**
71
+
72
+ ### F0 -- SHIPPED v8.45.0. A gate that cannot pass aborts instead of iterating.
69
73
 
70
74
  FireLater burned 3 iterations against a gate whose detector did not exist. The
71
75
  gate correctly fail-closed each time; nothing noticed it was failing for the
@@ -81,7 +85,25 @@ same unfixable reason.
81
85
  - Guard rails: only on a byte-identical repeated cause, never a first failure,
82
86
  and it must map to a terminal-failure exit -- never a fake green.
83
87
 
84
- ### F1 -- Front-load the 82%: make the spec gate the first-pass gate
88
+ ### F1 -- ALREADY BUILT (verified 2026-08-01). No work needed.
89
+
90
+ The plan claimed this needed extending. It does not. Verified end to end:
91
+
92
+ - `spec_interrogation_class_for()` already classifies findings as
93
+ **ambiguous / underspecified / missing / contradictory** -- the classes the
94
+ 20,574-session study names, not just contradictions as the plan asserted.
95
+ - `spec_ledger_prompt_block()` renders them and `run.sh:19399` assigns the
96
+ result to `assumption_context`.
97
+ - `assumption_context` is interpolated into the built prompt at four sites
98
+ (run.sh:19685, 19687, 19691, 19693), so high-severity spec gaps reach the
99
+ agent on iteration 1.
100
+ - `LOKI_SPEC_GRILL` defaults to 1, so it runs by default.
101
+
102
+ **Recorded as a finding rather than converted into a change that was not
103
+ needed.** The 82% planning bucket already has its lever; the honest next step
104
+ is MEASURING what it catches per run, not building a second one.
105
+
106
+ ### F1-original (superseded) -- front-load the 82% via the spec gate
85
107
 
86
108
  `LOKI_SPEC_GRILL` already interrogates the spec before the loop and defaults
87
109
  ON. That is the correct lever for the 82% planning bucket, and it is already
@@ -95,7 +117,7 @@ paid for.
95
117
  **-3% success, +20% cost**; human-written ones **+4%**. So this must produce
96
118
  *questions and resolutions*, never a generated context blob.
97
119
 
98
- ### F2 -- Findings injection must not be silently optional
120
+ ### F2 -- SHIPPED v8.44.0. Findings injection can no longer degrade silently.
99
121
 
100
122
  `LOKI_INJECT_FINDINGS` defaults on, but the injection is gated on
101
123
  `command -v bun`. **Without bun, the agent is told it failed and not what to
@@ -105,7 +127,7 @@ fix** -- the exact "no error recovery" shape that is 56% of failures.
105
127
  - A silent degradation of the feedback loop is worse than a missing feature,
106
128
  because the next iteration looks like a model failure.
107
129
 
108
- ### F3 -- Verify before the gate, not after
130
+ ### F3 -- SHIPPED v8.46.0. Iteration 1 now names the gates that will judge it.
109
131
 
110
132
  47.8% first-iteration pass rate is the industry number. The cheap deterministic
111
133
  gates (test_suite, static_analysis, lsp_diagnostics) cost ~6s combined and run
@@ -117,15 +139,22 @@ gates (test_suite, static_analysis, lsp_diagnostics) cost ~6s combined and run
117
139
  edits the offending files, re-runs" -- their headline architectural change.
118
140
  We already have the gates; we just run them too late to help pass 1.
119
141
 
120
- ### F4 -- Iteration budget as a measured decision
142
+ ### F4 -- Iteration budget: mechanism exists, default is the open question
143
+
144
+ Verified: hitting the cap already records the named terminal
145
+ `max_iterations_reached` with exit 20 (run.sh:21433) -- it does NOT fake
146
+ success. That half of the plan was already satisfied.
121
147
 
122
- Research: 1-2 iteration caps fail even when the approach was sound; 5-10 is the
123
- recommended range. We ship `LOKI_MAX_ITERATIONS=1000`.
148
+ What is genuinely open is the DEFAULT. `LOKI_MAX_ITERATIONS` defaults to
149
+ **1000**; the 5 applies only to `LOKI_AUTO_FIX` tasks. Research puts the sound
150
+ range at 5-10 and shows 1-2 fails even when the approach was correct.
124
151
 
125
- - The goal is not a small cap. It is **finishing in one** and stopping honestly
126
- when one is not enough.
127
- - Pairs with F0: a cap is a blunt instrument; a named terminal reason is a
128
- diagnosis.
152
+ **Deliberately not changed here.** F0 (v8.45.0) already stops a doomed run at
153
+ the cause rather than the count, which is the better instrument -- a cap is
154
+ blunt, a named stuck-gate reason is a diagnosis. Lowering the default without
155
+ measuring how often real runs legitimately exceed 5 would trade one arbitrary
156
+ number for another. That measurement needs real builds, so it stays a founder
157
+ decision rather than a guess.
129
158
 
130
159
  ## 4. What we do NOT do
131
160
 
@@ -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.45.0";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.47.0";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)
@@ -817,6 +817,11 @@ This precedence override is narrow. It does NOT relax any safety rule. Every saf
817
817
  2. SELF-VERIFY BY RUNNING, not by reading. Run the build and the tests yourself; for each acceptance criterion, DRIVE the actual path and observe the result (submit the form, then reload and confirm the record persisted; hit a protected route logged-out and confirm it is rejected). Fix what fails now, in this pass. Do not mark done on "looks right" or a self-claim -- observed behavior is the only proof.
818
818
  3. LOCK THE ARCHITECTURE on this pass so later edits are small and additive. Decide the data model, routes, and component structure up front and build to them; never rewrite whole files later to patch a small thing (that is the doom loop that breaks working features).
819
819
  4. DESIGN: commit to ONE named aesthetic direction up front (editorial, brutalist, luxury, retro-futuristic, soft/pastel, industrial, etc. -- chosen from the product domain) and hold it on every surface. Use real content (never lorem). AVOID the AI-slop tells that instantly read as machine-generated: NO indigo/blue-to-purple gradient (the #1 tell), NO Inter/Roboto/system-font headlines (pick a real display+body pairing), NO three-equal-rounded-cards-in-a-row skeleton, NO flat 1px gray card borders or colored left-border strips, NO untouched shadcn defaults, NO reflexive dark mode. Cap the palette at ~3 hues (60/30/10), tinted not pure #fff/#000, separate sections by whitespace then a slight background shift before any border. Aim for Linear/Stripe/Duolingo-tier taste: "this does not look AI-generated".
820
+ 5. SATISFY THE GATES THAT WILL JUDGE THIS PASS. Your work is checked by deterministic gates before it can be accepted, and in measured runs EVERY extra iteration was caused by one of these rejecting the work -- never by the model failing to finish. Clear them now, in this pass:
821
+ - STATIC ANALYSIS: no syntax errors, no unused/undefined symbols, no lint errors in files you touched.
822
+ - TEST SUITE: the existing tests must still pass. Run them; do not assume.
823
+ - MUTATION/MOCK INTEGRITY: tests must assert real behaviour. No tautological assertions (expect(true).toBe(true)), no test that passes whether or not the code works, no mocking the very unit under test, no inline mock data standing in for a real query.
824
+ - CODE REVIEW: no scope creep beyond the stated task, no dead or commented-out code, no leftover debug output, and follow the conventions already in the surrounding files.
820
825
  Deliver the finished, self-verified, genuinely-designed result in THIS pass. Additional iterations should be the exception, not the plan.
821
826
  `+Kd();hj0=process.env.LOKI_CAVEMAN_VERSION||"1.9.0"});var Gq={};l0(Gq,{writeOrchestratorState:()=>Ww,updateStatusTxt:()=>gd,updateCurrentPhase:()=>hd,saveStateForRunner:()=>xd,saveState:()=>Kw,readProviderName:()=>vd,readOrchestratorState:()=>fd,loadStateForRunner:()=>Sd,loadState:()=>$w,atomicWriteFileSync:()=>S7,__setFsForTesting:()=>Dd});import{closeSync as Md,copyFileSync as Zw,existsSync as $Z,fsyncSync as Td,mkdirSync as Wq,openSync as wd,readFileSync as oY,readdirSync as Cd,renameSync as Vq,statSync as Fd,unlinkSync as rY,writeFileSync as Xw}from"fs";import{join as F2}from"path";function Dd(Z){Qw=Z.renameSync??Vq,Yw=Z.copyFileSync??Zw,Jw=Z.writeFileSync??Xw,nY=Z.unlinkSync??rY}function J3(Z){return Z??j0()}function zw(Z){return F2(Z,"autonomy-state.json")}function qq(Z){return F2(Z,"state","orchestrator.json")}function Ed(Z){return F2(Z,"state","provider")}function Id(Z){return F2(Z,"STATUS.txt")}function tT(Z){let X=null;try{X=wd(Z,"r"),Td(X)}catch{}finally{if(X!==null)try{Md(X)}catch{}}}function S7(Z,X){if(Z.endsWith(".lock"))throw Error(`atomicWriteFileSync: target path "${Z}" ends in .lock which collides with the lockfile naming convention; rename the target`);let Q=`${Z}.tmp.${process.pid}`;G6(Z,()=>{Jw(Q,X),tT(Q);try{Qw(Q,Z),RQ(Z)}catch(Y){if(Y?.code==="EXDEV")try{Yw(Q,Z),tT(Z),RQ(Z);try{nY(Q)}catch{}return}catch(z){try{nY(Q)}catch{}throw z}try{nY(Q)}catch{}throw Y}},{timeoutMs:Rd,staleMs:Pd})}function kd(Z){let X=Z.toISOString(),Q=X.indexOf(".");return Q>=0?`${X.slice(0,Q)}Z`:X}function $q(Z){return JSON.stringify(Z)}function Kw(Z){let X=J3(Z.lokiDirOverride);Wq(X,{recursive:!0});let Q=Z.now??new Date,Y=Z.prdPath??"",J=Z.pid??process.pid,z=`{
822
827
  "retryCount": ${Z.retryCount},
@@ -1222,4 +1227,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
1222
1227
  `),2}case"start":{let{runStart:Y}=await Promise.resolve().then(() => (b_(),y_));return Y(Q)}default:return process.stderr.write(`Unknown command: ${X}
1223
1228
  `),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
1229
 
1225
- //# debugId=F6128F50CF1C0A0064756E2164756E21
1230
+ //# debugId=F46363BF25356A3464756E2164756E21
package/mcp/__init__.py CHANGED
@@ -75,4 +75,4 @@ try:
75
75
  except ImportError:
76
76
  __all__ = ['mcp']
77
77
 
78
- __version__ = '8.45.0'
78
+ __version__ = '8.47.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.45.0",
4
+ "version": "8.47.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.45.0",
5
+ "version": "8.47.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",
@@ -327,6 +327,11 @@ LOKI_AUTONOMY_EOF
327
327
  2. SELF-VERIFY BY RUNNING, not by reading. Run the build and the tests yourself; for each acceptance criterion, DRIVE the actual path and observe the result (submit the form, then reload and confirm the record persisted; hit a protected route logged-out and confirm it is rejected). Fix what fails now, in this pass. Do not mark done on "looks right" or a self-claim -- observed behavior is the only proof.
328
328
  3. LOCK THE ARCHITECTURE on this pass so later edits are small and additive. Decide the data model, routes, and component structure up front and build to them; never rewrite whole files later to patch a small thing (that is the doom loop that breaks working features).
329
329
  4. DESIGN: commit to ONE named aesthetic direction up front (editorial, brutalist, luxury, retro-futuristic, soft/pastel, industrial, etc. -- chosen from the product domain) and hold it on every surface. Use real content (never lorem). AVOID the AI-slop tells that instantly read as machine-generated: NO indigo/blue-to-purple gradient (the #1 tell), NO Inter/Roboto/system-font headlines (pick a real display+body pairing), NO three-equal-rounded-cards-in-a-row skeleton, NO flat 1px gray card borders or colored left-border strips, NO untouched shadcn defaults, NO reflexive dark mode. Cap the palette at ~3 hues (60/30/10), tinted not pure #fff/#000, separate sections by whitespace then a slight background shift before any border. Aim for Linear/Stripe/Duolingo-tier taste: "this does not look AI-generated".
330
+ 5. SATISFY THE GATES THAT WILL JUDGE THIS PASS. Your work is checked by deterministic gates before it can be accepted, and in measured runs EVERY extra iteration was caused by one of these rejecting the work -- never by the model failing to finish. Clear them now, in this pass:
331
+ - STATIC ANALYSIS: no syntax errors, no unused/undefined symbols, no lint errors in files you touched.
332
+ - TEST SUITE: the existing tests must still pass. Run them; do not assume.
333
+ - MUTATION/MOCK INTEGRITY: tests must assert real behaviour. No tautological assertions (expect(true).toBe(true)), no test that passes whether or not the code works, no mocking the very unit under test, no inline mock data standing in for a real query.
334
+ - CODE REVIEW: no scope creep beyond the stated task, no dead or commented-out code, no leftover debug output, and follow the conventions already in the surrounding files.
330
335
  Deliver the finished, self-verified, genuinely-designed result in THIS pass. Additional iterations should be the exception, not the plan.
331
336
  LOKI_FIRSTPASS_EOF
332
337