loki-mode 7.56.0 → 7.57.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.
@@ -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:** v7.56.0
5
+ **Version:** v7.57.0
6
6
 
7
7
  ---
8
8
 
@@ -395,7 +395,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
395
395
  # Run Loki Mode in Docker (Claude provider, API-key auth)
396
396
  docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
397
397
  -v $(pwd):/workspace -w /workspace \
398
- asklokesh/loki-mode:7.56.0 start ./my-spec.md
398
+ asklokesh/loki-mode:7.57.0 start ./my-spec.md
399
399
  ```
400
400
 
401
401
  ##### docker compose + .env (no host install)
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var r6=Object.defineProperty;var t6=($)=>$;function i6($,Q){this[$]=t6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)r6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:i6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var D1={};h(D1,{lokiDir:()=>P,homeLokiDir:()=>n$,findRepoRootForVersion:()=>o$,REPO_ROOT:()=>g});import{resolve as n,dirname as d$}from"path";import{fileURLToPath as e6}from"url";import{existsSync as P$}from"fs";import{homedir as $Q}from"os";function QQ(){let $=S1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=d$($);if(Z===$)break;$=Z}return n(S1,"..","..","..")}function o$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=d$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function n$(){return n($Q(),".loki")}var S1,g;var b=L(()=>{S1=d$(e6(import.meta.url));g=QQ()});import{readFileSync as ZQ}from"fs";import{resolve as zQ,dirname as XQ}from"path";import{fileURLToPath as KQ}from"url";function j$(){if($$!==null)return $$;let $="7.56.0";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=XQ(KQ(import.meta.url)),Z=o$(Q);$$=ZQ(zQ(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var a$=L(()=>{b()});var b1={};h(b1,{runOrThrow:()=>qQ,run:()=>k,commandVersion:()=>WQ,commandExists:()=>f,ShellError:()=>s$});async function k($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,W]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:W}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function qQ($,Q={}){let Z=await k($,Q);if(Z.exitCode!==0)throw new s$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=VQ($),Z=await k(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function VQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function WQ($,Q="--version"){if(!await f($))return null;let z=await k([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var s$;var d=L(()=>{s$=class s$ extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function a($){return JQ?"":$}var JQ,T,S,_,wZ,I,R,y,V;var c=L(()=>{JQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),_=a("\x1B[1;33m"),wZ=a("\x1B[0;34m"),I=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),V=a("\x1B[0m")});import{existsSync as wQ}from"fs";async function Q$(){if(G$!==void 0)return G$;let $="/opt/homebrew/bin/python3.12";if(wQ($))return G$=$,$;let Q=await f("python3.12");if(Q)return G$=Q,Q;let Z=await f("python3");return G$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return k([Z,"-c",$],Q)}var G$;var q$=L(()=>{d()});var e1={};h(e1,{runStatus:()=>uQ});import{existsSync as v,readFileSync as W$,readdirSync as d1,statSync as o1}from"fs";import{resolve as C,basename as DQ}from"path";import{homedir as CQ}from"os";function n1($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function a1($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*k$/Q);if(X>k$)X=k$;let q=k$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),J=n1($),U=n1(Q);return` ${R}${Z}${V} ${K}[${W}]${V} ${z}% (${J} / ${U})`}async function hQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${V}
2
+ var r6=Object.defineProperty;var t6=($)=>$;function i6($,Q){this[$]=t6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)r6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:i6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var D1={};h(D1,{lokiDir:()=>P,homeLokiDir:()=>n$,findRepoRootForVersion:()=>o$,REPO_ROOT:()=>g});import{resolve as n,dirname as d$}from"path";import{fileURLToPath as e6}from"url";import{existsSync as P$}from"fs";import{homedir as $Q}from"os";function QQ(){let $=S1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=d$($);if(Z===$)break;$=Z}return n(S1,"..","..","..")}function o$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=d$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function n$(){return n($Q(),".loki")}var S1,g;var b=L(()=>{S1=d$(e6(import.meta.url));g=QQ()});import{readFileSync as ZQ}from"fs";import{resolve as zQ,dirname as XQ}from"path";import{fileURLToPath as KQ}from"url";function j$(){if($$!==null)return $$;let $="7.57.0";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=XQ(KQ(import.meta.url)),Z=o$(Q);$$=ZQ(zQ(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var a$=L(()=>{b()});var b1={};h(b1,{runOrThrow:()=>qQ,run:()=>k,commandVersion:()=>WQ,commandExists:()=>f,ShellError:()=>s$});async function k($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,W]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:W}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function qQ($,Q={}){let Z=await k($,Q);if(Z.exitCode!==0)throw new s$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=VQ($),Z=await k(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function VQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function WQ($,Q="--version"){if(!await f($))return null;let z=await k([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var s$;var d=L(()=>{s$=class s$ extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function a($){return JQ?"":$}var JQ,T,S,_,wZ,I,R,y,V;var c=L(()=>{JQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),_=a("\x1B[1;33m"),wZ=a("\x1B[0;34m"),I=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),V=a("\x1B[0m")});import{existsSync as wQ}from"fs";async function Q$(){if(G$!==void 0)return G$;let $="/opt/homebrew/bin/python3.12";if(wQ($))return G$=$,$;let Q=await f("python3.12");if(Q)return G$=Q,Q;let Z=await f("python3");return G$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return k([Z,"-c",$],Q)}var G$;var q$=L(()=>{d()});var e1={};h(e1,{runStatus:()=>uQ});import{existsSync as v,readFileSync as W$,readdirSync as d1,statSync as o1}from"fs";import{resolve as C,basename as DQ}from"path";import{homedir as CQ}from"os";function n1($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function a1($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*k$/Q);if(X>k$)X=k$;let q=k$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),J=n1($),U=n1(Q);return` ${R}${Z}${V} ${K}[${W}]${V} ${z}% (${J} / ${U})`}async function hQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}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)
@@ -790,4 +790,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
790
790
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
791
791
  `),process.stderr.write(s6),2}}l1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var KZ=await XZ(Bun.argv.slice(2));process.exit(KZ);
792
792
 
793
- //# debugId=3D16FCC1B4694B0E64756E2164756E21
793
+ //# debugId=6D8496B2540606D064756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.56.0'
60
+ __version__ = '7.57.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": "7.56.0",
4
+ "version": "7.57.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": "7.56.0",
5
+ "version": "7.57.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",
@@ -2,18 +2,35 @@
2
2
 
3
3
  **Never ship code without passing all quality gates.**
4
4
 
5
- ## The Quality Gates (8 default-on + 1 opt-in)
6
-
7
- Every gate below is wired into the orchestration loop (`autonomy/run.sh`). The 8
8
- numbered gates are default-on and block completion when they fail; the opt-in
9
- gate (marked below) is default-OFF and runs only when its flag is set. The table
10
- lists exactly what each gate detects, what it does NOT detect (so you never
11
- over-trust a green gate), its opt-out flag, and its blocking behavior. Transcribe
12
- this list verbatim; do not recompute it.
5
+ ## The Quality Gates (8 blocking default-on + 3 advisory default-on + 1 opt-in)
6
+
7
+ Every gate below is wired into the orchestration loop (`autonomy/run.sh`). Read
8
+ the count honestly, split by what a gate actually does:
9
+
10
+ - **8 BLOCKING default-on gates** (the numbered table below): run by default and
11
+ FAIL the build / block completion when they trip.
12
+ - **3 ADVISORY default-on gates** (LSP diagnostics, semantic test-authenticity,
13
+ invariant/property; see the advisory table further down): run by default,
14
+ SURFACE actionable findings into the next iteration's prompt, and never block
15
+ by default. Two of them gain a blocking arm only via an explicit `*_BLOCK`
16
+ opt-in flag; LSP diagnostics has no blocking arm at all.
17
+ - **1 OPT-IN gate** (coverage, `LOKI_COVERAGE_GATE`, default OFF): does not run
18
+ unless explicitly enabled. It is the lone opt-in because it doubles test
19
+ runtime (instrumented re-run); see the coverage section for why.
20
+
21
+ So: 8 blocking gates run by default, plus 3 advisory surfacing gates and 1
22
+ opt-in coverage gate. Only the 8 numbered gates block out of the box; the
23
+ advisory gates surface findings without blocking, and the coverage gate is OFF
24
+ unless explicitly enabled. Never count the advisory or opt-in gates as blockers.
25
+
26
+ The numbered table below lists the 8 BLOCKING default-on gates: exactly what each
27
+ detects, what it does NOT detect (so you never over-trust a green gate), its
28
+ opt-out flag, and its blocking behavior. Transcribe this list verbatim; do not
29
+ recompute it.
13
30
 
14
31
  | # | Gate | Detects | Does NOT detect | Blocking | Opt-out flag |
15
32
  |---|------|---------|-----------------|----------|--------------|
16
- | 1 | Static Analysis | CodeQL, ESLint/Pylint, type-checker findings on the diff | Logic bugs that pass the linters | Yes (severity ladder) | `PHASE_STATIC_ANALYSIS=false` |
33
+ | 1 | Static Analysis | ESLint/Pylint, type-checker findings on the diff | Logic bugs that pass the linters | Yes (severity ladder) | `PHASE_STATIC_ANALYSIS=false` |
17
34
  | 2 | Test Suite (pass/fail) | Whether the project test runner passes or fails (red blocks) | Coverage % (not measured in this release) | Yes (red blocks) | `PHASE_UNIT_TESTS=false` |
18
35
  | 3 | Blind Code Review (3-reviewer council + severity blocking) | Correctness/security/design issues via 3 blind reviewers; Critical/High block, Medium/Low advisory | Issues none of the 3 reviewers surface | Yes (Crit/High block) | `PHASE_CODE_REVIEW=false` |
19
36
  | 4 | Anti-Sycophancy / Devil's Advocate (on unanimous PASS) | Sycophantic unanimous approvals: a Devil's Advocate re-review on a unanimous PASS; its Crit/High findings block | Problems the Devil's Advocate reviewer also misses | Yes (DA Crit/High block) | `LOKI_GATE_DEVILS_ADVOCATE=false` |
@@ -21,7 +38,12 @@ this list verbatim; do not recompute it.
21
38
  | 6 | Test Mutation Detector | Assertion-value churn alongside implementation changes (test-fitting), low assertion density (`tests/detect-test-mutations.sh`); HIGH blocks | Logically-correct-but-weak assertions | Yes (HIGH blocks) | `LOKI_GATE_MUTATION=false` |
22
39
  | 7 | Documentation Coverage | README presence, docs freshness within 10 commits, API docs for exported symbols in packages | Whether the docs are accurate or useful | Yes | `LOKI_GATE_DOC_COVERAGE=false` |
23
40
  | 8 | Magic Modules Debate | Spec-vs-implementation debate findings on generated Magic Modules; BLOCK-severity findings block | Issues outside the Magic Modules debate scope | Yes (BLOCK severity) | `LOKI_GATE_MAGIC_DEBATE=false` |
24
- | 9 (opt-in, default OFF) | Semantic Test-Authenticity | Fake tests that look real but verify nothing (literal-via-variable echo, mock-return echo, deleted assertions) that gates 5+6 miss (`tests/detect-semantic-test-problems.sh --block-high`); CRITICAL/HIGH block | Deep dataflow, legitimate computed-literal assertions, Python/shell tests (JS/TS only); MED/LOW are advisory | Only when enabled, and only on CRITICAL/HIGH; runs solely on a completion claim | Opt-IN: `LOKI_GATE_SEMANTIC_TESTS=true` to enable (default off = not invoked, never blocks) |
41
+
42
+ The three advisory default-on gates (LSP diagnostics, semantic
43
+ test-authenticity, invariant/property) are documented in their own table under
44
+ "Advisory default-on verification gates" below. Semantic test-authenticity used
45
+ to appear here as "gate 9 (opt-in, default OFF)"; it is now a default-on
46
+ advisory gate and has moved to that table. See the migration note there.
25
47
 
26
48
  **Severity-based blocking** ties the review gates together: any Critical or High
27
49
  finding blocks completion. Medium, Low, and cosmetic findings are advisory and
@@ -100,6 +122,108 @@ LOKI_GATE_MUTATION=false # Disable gate 6 (Test Mutation Detector)
100
122
 
101
123
  ---
102
124
 
125
+ ## Advisory default-on verification gates (LSP, semantic, invariant)
126
+
127
+ These three gates extend the mock/mutation precedent (gates 5+6: default-on,
128
+ opt-out) into an ADVISORY-FIRST posture. They run by default, surface actionable
129
+ findings into the next iteration's prompt, and only BLOCK completion when their
130
+ opt-in `*_BLOCK` flag is set. LSP diagnostics has no blocking arm at all: it is
131
+ advisory by construction. Coverage is NOT in this group; it remains opt-in (see
132
+ the next section).
133
+
134
+ This is the FROZEN knob scheme. All flags accept `true` or `1`.
135
+
136
+ **Route scope (honest disclosure):** Bun-route parity is ACHIEVED for the
137
+ default-ON behavior. The Bun runner
138
+ (`loki-ts/src/runner/quality_gates.ts` `readToggles`) now defaults all three
139
+ advisory gates ON (`LOKI_GATE_SEMANTIC_TESTS`, `LOKI_GATE_INVARIANTS`,
140
+ `LOKI_GATE_LSP_DIAGNOSTICS` each `flag(X, true)`), matching the bash
141
+ `loki start` route (`autonomy/run.sh`), with blocking behind the same opt-in
142
+ `*_BLOCK` flags. Semantic and invariant findings surface into the next
143
+ iteration's prompt via the `build_prompt` readers
144
+ (`buildSemanticFindingsBlock` + `buildInvariantFindingsBlock`). The one real
145
+ remaining gap is a SURFACING ASYMMETRY: LSP runs default-ON on the Bun route,
146
+ but its advisory findings are NOT yet injected into the Bun prompt (LSP
147
+ contributes only the grounding instruction, not its diagnostics block). That
148
+ prompt-injection parity is still pending for LSP on the Bun route. This mirrors
149
+ the "Reachability note" at the end of this file for the v7.5.0 Phase 1 flags.
150
+
151
+ | Gate | Surfacing (advisory, default-ON, opt-out) | Blocking (opt-in, default-OFF) |
152
+ |------|--------------------------------------------|--------------------------------|
153
+ | LSP diagnostics | `LOKI_GATE_LSP_DIAGNOSTICS=true` | advisory-only, no blocking arm |
154
+ | Semantic test-authenticity | `LOKI_GATE_SEMANTIC_TESTS=true` | `LOKI_GATE_SEMANTIC_TESTS_BLOCK=true` |
155
+ | Invariant / property | `LOKI_GATE_INVARIANTS=true` | `LOKI_GATE_INVARIANTS_BLOCK=true` |
156
+
157
+ How to read the Surfacing column: each gate is already ON by default. The flag
158
+ shown is the gate's own toggle, and `true`/`1` is its enabled value (the default).
159
+ Set the flag to its off value (`false`/`0`) to OPT OUT of surfacing. Setting the
160
+ `=true` value is a no-op confirmation, not an opt-in: these are not opt-in gates.
161
+
162
+ How to read the Blocking column: by default these gates never block, they only
163
+ surface. To make a gate also block completion, set its `*_BLOCK` flag (default
164
+ OFF). The blocking arm fires only on a completion claim, and only on the gate's
165
+ high-severity findings; lower-severity findings stay advisory either way.
166
+
167
+ **What each gate surfaces:**
168
+
169
+ - **LSP diagnostics** (`LOKI_GATE_LSP_DIAGNOSTICS`, default on): language-server
170
+ diagnostics (errors/warnings) on the changed files. Advisory only; there is no
171
+ `_BLOCK` flag and no way to make it block. Inert when no language server is
172
+ available for the project's languages.
173
+ - **Semantic test-authenticity** (`LOKI_GATE_SEMANTIC_TESTS`, default on):
174
+ fake tests that look real but verify nothing (literal-via-variable echo,
175
+ mock-return echo, deleted assertions) that gates 5+6 miss
176
+ (`tests/detect-semantic-test-problems.sh`). Surfaces by default; blocks on
177
+ CRITICAL/HIGH only when `LOKI_GATE_SEMANTIC_TESTS_BLOCK=true`. Does NOT detect
178
+ deep dataflow, legitimate computed-literal assertions, or non-JS/TS tests
179
+ (JS/TS only).
180
+ - **Invariant / property** (`LOKI_GATE_INVARIANTS`, default on): property and
181
+ metamorphic invariant findings. Surfaces by default; blocks only when
182
+ `LOKI_GATE_INVARIANTS_BLOCK=true`.
183
+
184
+ **Advisory-first posture and deny-filter:** these gates run by default and feed
185
+ their findings into the next iteration's prompt so the agent can act on them.
186
+ They only stop a completion claim when the matching `*_BLOCK` opt-in is set. The
187
+ gates are deny-filtered: a clean result, an absent toolchain (e.g. no language
188
+ server, no test files in scope), or a timeout never fires the gate. The gate
189
+ surfaces or blocks only on real, parseable findings, so a missing toolchain does
190
+ not produce false surfacing or a false block.
191
+
192
+ ### Migration: semantic and invariant flags were repurposed
193
+
194
+ `LOKI_GATE_SEMANTIC_TESTS` and `LOKI_GATE_INVARIANTS` changed meaning:
195
+
196
+ - **Before:** these flags were default-OFF opt-ins that, when set, made the gate
197
+ BLOCK completion. Semantic test-authenticity was documented as "gate 9
198
+ (opt-in, default OFF)" and blocked on CRITICAL/HIGH when
199
+ `LOKI_GATE_SEMANTIC_TESTS=true`.
200
+ - **Now:** these flags are default-ON surfacing toggles (set to `false`/`0` to
201
+ opt OUT of surfacing). They no longer block. Blocking moved to the new
202
+ `LOKI_GATE_SEMANTIC_TESTS_BLOCK` and `LOKI_GATE_INVARIANTS_BLOCK` opt-ins
203
+ (default OFF).
204
+
205
+ **Action required if you relied on the old behavior:** anyone who set
206
+ `LOKI_GATE_SEMANTIC_TESTS=true` (or `LOKI_GATE_INVARIANTS=true`) specifically to
207
+ BLOCK the build must now set `LOKI_GATE_SEMANTIC_TESTS_BLOCK=true` (or
208
+ `LOKI_GATE_INVARIANTS_BLOCK=true`). The old flag set to `true` now only confirms
209
+ the default-on surfacing and will NOT block. This is the one behavior change in
210
+ the migration: a flag that used to block now only surfaces.
211
+
212
+ ### Coverage stays opt-in (the exception)
213
+
214
+ The coverage gate is the one verification gate that remains OPT-IN
215
+ (`LOKI_COVERAGE_GATE`, default OFF). It is the exception because measuring
216
+ coverage requires an instrumented SECOND test run, which roughly doubles test
217
+ runtime for every iteration. The advisory gates above add no such cost, so they
218
+ default on; coverage's cost is why it does not.
219
+
220
+ ```bash
221
+ LOKI_COVERAGE_GATE=1 # opt in: measure + record coverage. Default OFF because
222
+ # it doubles test runtime (instrumented re-run). Even
223
+ # when enabled it measures and warns; it does not block
224
+ # unless LOKI_ENFORCE_COVERAGE=1 is also set.
225
+ ```
226
+
103
227
  ## v7.5.0 Phase 1 environment flags
104
228
 
105
229
  These four flags activate the override council and structured-findings
@@ -662,7 +786,7 @@ Initial excitement -> Velocity spike -> Quality degradation accumulates
662
786
  ```yaml
663
787
  velocity_quality_balance:
664
788
  before_commit:
665
- - static_analysis: "Run ESLint/Pylint/CodeQL - warnings must not increase"
789
+ - static_analysis: "Run ESLint/Pylint - warnings must not increase"
666
790
  - complexity_check: "Cyclomatic complexity must not increase >10%"
667
791
  - test_suite: "Tests must pass (coverage % not measured in this release)"
668
792