loki-mode 7.27.0 → 7.28.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.
@@ -971,6 +971,55 @@ OUTPUT:
971
971
  EOF
972
972
  }
973
973
 
974
+ # ---------------------------------------------------------------------------
975
+ # Living-spec drift gate (integration with autonomy/spec.sh).
976
+ #
977
+ # When .loki/spec/spec.lock exists, source the spec module and run its
978
+ # verify hook, which emits at most one SPEC_DRIFT finding (Medium -> CONCERNS)
979
+ # in the verify finding TSV shape. Records a gate row either way so the
980
+ # evidence document shows the spec was checked. Fully graceful: no lock, no
981
+ # module, or a hook error all degrade to a skipped gate and never abort verify.
982
+ # ---------------------------------------------------------------------------
983
+ verify_spec_drift_gate() {
984
+ local tree="${1:-.}"
985
+ local spec_dir="$tree/.loki/spec"
986
+ # Normalize a leading "./" so the lock-path probe is clean.
987
+ spec_dir="${spec_dir#./}"
988
+ local lock_file="$spec_dir/spec.lock"
989
+
990
+ if [ ! -f "$lock_file" ]; then
991
+ _verify_add_gate "spec_drift" "skipped" "loki-spec" "no spec lock (.loki/spec/spec.lock); run 'loki spec lock' to enable" "true"
992
+ return 0
993
+ fi
994
+
995
+ local spec_mod
996
+ spec_mod="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/spec.sh"
997
+ if [ ! -f "$spec_mod" ]; then
998
+ _verify_add_gate "spec_drift" "inconclusive" "loki-spec" "spec lock present but spec module not found" "true"
999
+ return 0
1000
+ fi
1001
+
1002
+ # Source the spec module in a guarded subshell-free way: it defines
1003
+ # spec_verify_hook which prints zero or one TSV finding line on stdout.
1004
+ # shellcheck source=/dev/null
1005
+ if ! source "$spec_mod" 2>/dev/null; then
1006
+ _verify_add_gate "spec_drift" "inconclusive" "loki-spec" "could not load spec module" "true"
1007
+ return 0
1008
+ fi
1009
+
1010
+ local finding
1011
+ finding="$(spec_verify_hook "$spec_dir" 2>/dev/null || true)"
1012
+
1013
+ if [ -n "$finding" ]; then
1014
+ # Append the SPEC_DRIFT finding(s) verbatim (already TSV-shaped).
1015
+ printf '%s\n' "$finding" >>"$_VERIFY_FINDINGS_FILE"
1016
+ _verify_add_gate "spec_drift" "fail" "loki-spec" "spec has drifted from its lock (see SPEC_DRIFT finding)" "true"
1017
+ else
1018
+ _verify_add_gate "spec_drift" "pass" "loki-spec" "spec is in sync with its lock" "true"
1019
+ fi
1020
+ return 0
1021
+ }
1022
+
974
1023
  # ---------------------------------------------------------------------------
975
1024
  # Entry point
976
1025
  # ---------------------------------------------------------------------------
@@ -1052,6 +1101,12 @@ verify_main() {
1052
1101
  verify_gate_dependency_audit "$tree"
1053
1102
  fi
1054
1103
 
1104
+ # Living-spec integration: when a spec lock exists, fold a SPEC_DRIFT
1105
+ # finding into the verdict. Graceful no-op when there is no lock or the
1106
+ # spec machinery is unavailable -- verify must never fail to complete
1107
+ # because the optional spec module is missing.
1108
+ verify_spec_drift_gate "$tree"
1109
+
1055
1110
  verify_compute_verdict "$block_on"
1056
1111
 
1057
1112
  completed_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.27.0"
10
+ __version__ = "7.28.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:** v7.27.0
5
+ **Version:** v7.28.0
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var f8=Object.defineProperty;var u8=($)=>$;function c8($,Q){this[$]=u8.bind(null,Q)}var g=($,Q)=>{for(var Z in Q)f8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:c8.bind(Q,Z)})};var k=($,Q)=>()=>($&&(Q=$($=0)),Q);var X1=import.meta.require;var F$={};g(F$,{lokiDir:()=>P,homeLokiDir:()=>o1,findRepoRootForVersion:()=>d1,REPO_ROOT:()=>f});import{resolve as n,dirname as l1}from"path";import{fileURLToPath as p8}from"url";import{existsSync as L1}from"fs";import{homedir as l8}from"os";function d8(){let $=j$;for(let Q=0;Q<6;Q++){if(L1(n($,"VERSION"))&&L1(n($,"autonomy/run.sh")))return $;let Z=l1($);if(Z===$)break;$=Z}return n(j$,"..","..","..")}function d1($){let Q=$;for(let Z=0;Z<6;Z++){if(L1(n(Q,"VERSION"))&&L1(n(Q,"autonomy/run.sh")))return Q;let z=l1(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o1(){return n(l8(),".loki")}var j$,f;var y=k(()=>{j$=l1(p8(import.meta.url));f=d8()});import{readFileSync as o8}from"fs";import{resolve as n8,dirname as a8}from"path";import{fileURLToPath as s8}from"url";function k1(){if($1!==null)return $1;let $="7.27.0";if(typeof $==="string"&&$.length>0)return $1=$,$1;try{let Q=a8(s8(import.meta.url)),Z=d1(Q);$1=o8(n8(Z,"VERSION"),"utf-8").trim()}catch{$1="unknown"}return $1}var $1=null;var n1=k(()=>{y()});var E$={};g(E$,{runOrThrow:()=>t8,run:()=>j,commandVersion:()=>i8,commandExists:()=>v,ShellError:()=>a1});async function j($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,K;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}K=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[H,X,q]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:H,stderr:X,exitCode:q}}finally{if(z)clearTimeout(z);if(K)clearTimeout(K)}}async function t8($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function v($){let Q=r8($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function r8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function i8($,Q="--version"){if(!await v($))return null;let z=await j([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var a1;var d=k(()=>{a1=class a1 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 e8?"":$}var e8,T,N,_,KZ,A,R,h,J;var c=k(()=>{e8=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),N=a("\x1B[0;32m"),_=a("\x1B[1;33m"),KZ=a("\x1B[0;34m"),A=a("\x1B[0;36m"),R=a("\x1B[1m"),h=a("\x1B[2m"),J=a("\x1B[0m")});import{existsSync as U7}from"fs";async function Q1(){if(B1!==void 0)return B1;let $="/opt/homebrew/bin/python3.12";if(U7($))return B1=$,$;let Q=await v("python3.12");if(Q)return B1=Q,Q;let Z=await v("python3");return B1=Z,Z}async function Z1($,Q={}){let Z=await Q1();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B1;var H1=k(()=>{d()});var d$={};g(d$,{runStatus:()=>N7});import{existsSync as b,readFileSync as q1,readdirSync as v$,statSync as f$}from"fs";import{resolve as D,basename as P7}from"path";import{homedir as L7}from"os";async function j7(){if(await v("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${J}
2
+ var f8=Object.defineProperty;var u8=($)=>$;function c8($,Q){this[$]=u8.bind(null,Q)}var g=($,Q)=>{for(var Z in Q)f8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:c8.bind(Q,Z)})};var k=($,Q)=>()=>($&&(Q=$($=0)),Q);var X1=import.meta.require;var F$={};g(F$,{lokiDir:()=>P,homeLokiDir:()=>o1,findRepoRootForVersion:()=>d1,REPO_ROOT:()=>f});import{resolve as n,dirname as l1}from"path";import{fileURLToPath as p8}from"url";import{existsSync as L1}from"fs";import{homedir as l8}from"os";function d8(){let $=j$;for(let Q=0;Q<6;Q++){if(L1(n($,"VERSION"))&&L1(n($,"autonomy/run.sh")))return $;let Z=l1($);if(Z===$)break;$=Z}return n(j$,"..","..","..")}function d1($){let Q=$;for(let Z=0;Z<6;Z++){if(L1(n(Q,"VERSION"))&&L1(n(Q,"autonomy/run.sh")))return Q;let z=l1(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o1(){return n(l8(),".loki")}var j$,f;var y=k(()=>{j$=l1(p8(import.meta.url));f=d8()});import{readFileSync as o8}from"fs";import{resolve as n8,dirname as a8}from"path";import{fileURLToPath as s8}from"url";function k1(){if($1!==null)return $1;let $="7.28.0";if(typeof $==="string"&&$.length>0)return $1=$,$1;try{let Q=a8(s8(import.meta.url)),Z=d1(Q);$1=o8(n8(Z,"VERSION"),"utf-8").trim()}catch{$1="unknown"}return $1}var $1=null;var n1=k(()=>{y()});var E$={};g(E$,{runOrThrow:()=>t8,run:()=>j,commandVersion:()=>i8,commandExists:()=>v,ShellError:()=>a1});async function j($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,K;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}K=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[H,X,q]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:H,stderr:X,exitCode:q}}finally{if(z)clearTimeout(z);if(K)clearTimeout(K)}}async function t8($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function v($){let Q=r8($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function r8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function i8($,Q="--version"){if(!await v($))return null;let z=await j([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var a1;var d=k(()=>{a1=class a1 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 e8?"":$}var e8,T,N,_,KZ,A,R,h,J;var c=k(()=>{e8=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),N=a("\x1B[0;32m"),_=a("\x1B[1;33m"),KZ=a("\x1B[0;34m"),A=a("\x1B[0;36m"),R=a("\x1B[1m"),h=a("\x1B[2m"),J=a("\x1B[0m")});import{existsSync as U7}from"fs";async function Q1(){if(B1!==void 0)return B1;let $="/opt/homebrew/bin/python3.12";if(U7($))return B1=$,$;let Q=await v("python3.12");if(Q)return B1=Q,Q;let Z=await v("python3");return B1=Z,Z}async function Z1($,Q={}){let Z=await Q1();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B1;var H1=k(()=>{d()});var d$={};g(d$,{runStatus:()=>N7});import{existsSync as b,readFileSync as q1,readdirSync as v$,statSync as f$}from"fs";import{resolve as D,basename as P7}from"path";import{homedir as L7}from"os";async function j7(){if(await v("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${J}
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)
@@ -787,4 +787,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
787
787
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
788
788
  `),process.stderr.write(v8),2}}g$();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var l3=await p3(Bun.argv.slice(2));process.exit(l3);
789
789
 
790
- //# debugId=07AC5AC0D01821A064756E2164756E21
790
+ //# debugId=E87B6D92D567BF2C64756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.27.0'
60
+ __version__ = '7.28.0'
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "7.27.0",
3
+ "mcpName": "io.github.asklokesh/loki-mode",
4
+ "version": "7.28.0",
4
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 11 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
5
6
  "keywords": [
6
7
  "agent",
@@ -158,6 +158,18 @@ pass, not PRD-semantic correctness (the council vote is the semantic check).
158
158
  The common false-block is a project that was ALREADY red before the run; the
159
159
  one-step opt-out is the escape hatch.
160
160
 
161
+ **Inconclusive-baseline disclosure (v7.28.0):** when the gate cannot establish a
162
+ diff baseline (reason `no_git_repo` or `no_run_start_sha`) it still passes
163
+ through (it never blocks a non-git project), but completion is no longer
164
+ independently verified. Instead of passing silently, the gate writes
165
+ `.loki/state/evidence-inconclusive.json` (recording the reason, iteration, and
166
+ timestamp) and emits an `evidence_inconclusive` trust event. The run summary in
167
+ `.loki/COMPLETION.txt` then carries one honest line:
168
+ `Evidence gate: inconclusive (<reason>) - completion not independently
169
+ verified`. The record is removed automatically on any later run that resolves a
170
+ conclusive baseline. This is a diff-baseline-only disclosure: red tests still
171
+ block completion independently, regardless of the inconclusive state.
172
+
161
173
  **Override-judge knobs (v7.5.4+):**
162
174
 
163
175
  ```bash
@@ -220,6 +232,40 @@ crash via the primitive's `finally` cleanup.
220
232
 
221
233
  ---
222
234
 
235
+ ## Held-out spec evals (v7.28.0, default-on when reserved)
236
+
237
+ Anti-reward-hacking for the checklist. Before the first verification,
238
+ `checklist_select_heldout` (`autonomy/prd-checklist.sh`) deterministically
239
+ reserves a slice of checklist items as held-out:
240
+ `count = clamp(round(0.25 * N), 1, 5)` for checklists with `N >= 4` items
241
+ (smaller checklists reserve nothing). Selection is reproducible, not random:
242
+ items are ranked by `sha256(id)` and the first `count` are taken, then written
243
+ once to `.loki/checklist/held-out.json` (idempotent: never reselected once
244
+ chosen).
245
+
246
+ Held-out item IDs are EXCLUDED from everything the build loop sees: the checklist
247
+ summary, the visible counts, and the per-iteration checklist gate all omit them,
248
+ so the build agent cannot tune to those specific acceptance checks. The
249
+ completion council evaluates them only at the ship gate via
250
+ `council_heldout_gate` (`autonomy/completion-council.sh`): a held-out item whose
251
+ status is `failing` (and not waived) blocks completion exactly like any other
252
+ critical failure. Each evaluation records a `heldout_eval` trust event with the
253
+ verdict and pass/fail counts (no event is emitted when nothing is reserved).
254
+
255
+ ```bash
256
+ LOKI_HELDOUT_GATE=0 # opt out: the held-out gate never blocks completion.
257
+ # Default is on (1), and the gate is inert anyway when
258
+ # no held-out items were reserved (N < 4).
259
+ ```
260
+
261
+ Honest limit: this protects against the PROMPT FEED, not against filesystem
262
+ access. The reservation lives on disk at `.loki/checklist/held-out.json`; an
263
+ adversarial agent with read access to the working tree can open that file and
264
+ learn which items were held out. The guarantee is that held-out items are kept
265
+ out of the build loop's own prompt context, not that they are sandboxed.
266
+
267
+ ---
268
+
223
269
  ## Uncertainty-gated escalation (v7.19.2, default-on)
224
270
 
225
271
  When Loki is likely stuck or thrashing, it escalates proactively to the human