loki-mode 7.32.2 → 7.32.3

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, 11 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 v7.32.2
6
+ # Loki Mode v7.32.3
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -398,4 +398,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
398
398
 
399
399
  ---
400
400
 
401
- **v7.32.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
401
+ **v7.32.3 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.32.2
1
+ 7.32.3
package/autonomy/run.sh CHANGED
@@ -4543,7 +4543,14 @@ _loki_hash_stdin() {
4543
4543
  # Compute a cheap, clone-stable signature of the codebase so we can tell whether
4544
4544
  # it changed since the generated PRD was last written. Git repos: HEAD sha +
4545
4545
  # dirty flag (.loki/.git churn filtered out). Non-git: a hash of sorted
4546
- # path+size pairs (size, not mtime, so it is clone-stable). Echoes the signature.
4546
+ # path+size pairs PLUS file content (v7.32.3, #569: path+size alone was blind to
4547
+ # a same-size content edit, so a stale PRD could be silently reused with a
4548
+ # false "codebase unchanged" disclosure). Content hashing is clone-stable
4549
+ # (mtime is not, which is why mtime was never used). Trees larger than
4550
+ # LOKI_PRD_SIG_CONTENT_BUDGET bytes (default 50MB) skip the content pass and
4551
+ # emit a "files-shallow:" signature so startup stays fast; the safe failure
4552
+ # mode there is unchanged-from-before (size-blind), never a false "changed".
4553
+ # Echoes the signature.
4547
4554
  compute_codebase_signature() {
4548
4555
  local dir="${1:-.}"
4549
4556
  ( cd "$dir" 2>/dev/null || exit 0
@@ -4558,7 +4565,7 @@ compute_codebase_signature() {
4558
4565
  fi
4559
4566
  echo "git:${head}:${dirty}"
4560
4567
  else
4561
- local listing count
4568
+ local listing count total_sz budget
4562
4569
  listing=$(find . \
4563
4570
  -type d \( -name .loki -o -name .git -o -name node_modules -o -name dist \
4564
4571
  -o -name build -o -name .next -o -name target -o -name vendor \
@@ -4569,8 +4576,26 @@ compute_codebase_signature() {
4569
4576
  sz=$(stat -f%z "$f" 2>/dev/null || stat -c%s "$f" 2>/dev/null || echo 0)
4570
4577
  printf '%s\t%s\n' "$f" "$sz"
4571
4578
  done | LC_ALL=C sort)
4572
- count=$(printf '%s\n' "$listing" | grep -c . || echo 0)
4573
- echo "files:$(printf '%s' "$listing" | _loki_hash_stdin):${count}"
4579
+ # grep -c prints 0 itself on no match (exit 1); '|| true' avoids the
4580
+ # old '|| echo 0' double-zero that embedded a newline on empty trees
4581
+ count=$(printf '%s\n' "$listing" | grep -c . || true)
4582
+ total_sz=$(printf '%s\n' "$listing" | awk -F'\t' '{s+=$2} END {printf "%d", s}')
4583
+ budget="${LOKI_PRD_SIG_CONTENT_BUDGET:-52428800}"
4584
+ if [ "${total_sz:-0}" -le "$budget" ] 2>/dev/null; then
4585
+ # Content pass: stream all file contents through one hash in the
4586
+ # same sorted order as the listing. Detects same-size edits.
4587
+ # xargs -0 batches the reads into a handful of cat invocations,
4588
+ # so cost scales with BYTES (which the budget above bounds), not
4589
+ # file count: a fork-per-file loop here measured ~38s of added
4590
+ # startup on a 30k-small-file tree. Renames and content swaps
4591
+ # are still caught by the listing hash (paths+sizes) below.
4592
+ local content_hash
4593
+ content_hash=$(printf '%s\n' "$listing" | cut -f1 | tr '\n' '\0' \
4594
+ | xargs -0 cat 2>/dev/null | _loki_hash_stdin)
4595
+ echo "files:$(printf '%s' "$listing" | _loki_hash_stdin):${count}:${content_hash}"
4596
+ else
4597
+ echo "files-shallow:$(printf '%s' "$listing" | _loki_hash_stdin):${count}"
4598
+ fi
4574
4599
  fi
4575
4600
  )
4576
4601
  }
@@ -4644,6 +4669,25 @@ except Exception:
4644
4669
  if [ "$stored" = "$current" ]; then
4645
4670
  echo "reuse"
4646
4671
  else
4672
+ # v7.32.3 format transition (#569): a stored pre-content-hash signature
4673
+ # ("files:<listing>:<count>", 3 fields) compared against the new 4-field
4674
+ # format would falsely claim "codebase changed" on the first post-upgrade
4675
+ # run. When the new signature extends the stored one (same listing
4676
+ # fields), the tree is unchanged at the old format's trust level: reuse,
4677
+ # honestly. The next persist upgrades the stored format. A same-size edit
4678
+ # made BEFORE the upgrade stays invisible for this one run, exactly as it
4679
+ # was on the old version (no regression, no false disclosure).
4680
+ case "$stored" in
4681
+ files:*:*)
4682
+ # Require the legitimate old 3-field format (files:HASH:COUNT,
4683
+ # exactly 2 colons), not a truncated/corrupted 2-field value
4684
+ # (council hardening: corruption must fall to update, as before).
4685
+ if [ "$(printf '%s' "$stored" | tr -dc ':' | wc -c | tr -d ' ')" = "2" ] \
4686
+ && [ "${current#"${stored}":}" != "$current" ]; then
4687
+ echo "reuse"; return 0
4688
+ fi
4689
+ ;;
4690
+ esac
4647
4691
  echo "update"
4648
4692
  fi
4649
4693
  }
@@ -4692,7 +4736,16 @@ try:
4692
4736
  except Exception:
4693
4737
  prev = {}
4694
4738
  prev_at = prev.get('generated_at') if isinstance(prev, dict) else None
4695
- if prev_at and prev.get('signature') == sig:
4739
+ prev_sig = prev.get('signature') if isinstance(prev, dict) else None
4740
+ # Unchanged, OR the v7.32.3 files-signature format upgrade (#569): the new
4741
+ # 4-field signature extends an old 3-field one whose listing fields match.
4742
+ # Preserve the date in both cases; the PRD content did not change.
4743
+ _legacy_upgrade = (
4744
+ isinstance(prev_sig, str) and prev_sig.startswith('files:')
4745
+ and prev_sig.count(':') == 2
4746
+ and sig.startswith(prev_sig + ':')
4747
+ )
4748
+ if prev_at and (prev_sig == sig or _legacy_upgrade):
4696
4749
  generated_at = prev_at
4697
4750
  else:
4698
4751
  generated_at = datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00','Z')
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.32.2"
10
+ __version__ = "7.32.3"
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.32.2
5
+ **Version:** v7.32.3
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.32.2";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=QQ(ZQ(import.meta.url)),Z=d$(Q);$$=e6($Q(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var n$=L(()=>{C()});var C1={};h(C1,{runOrThrow:()=>zQ,run:()=>j,commandVersion:()=>KQ,commandExists:()=>f,ShellError:()=>a$});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,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[W,K,U]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:W,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function zQ($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=XQ($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function XQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function KQ($,Q="--version"){if(!await f($))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 a$;var d=L(()=>{a$=class a$ 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 WQ?"":$}var WQ,T,S,I,TZ,w,R,y,q;var c=L(()=>{WQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),I=a("\x1B[1;33m"),TZ=a("\x1B[0;34m"),w=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),q=a("\x1B[0m")});import{existsSync as TQ}from"fs";async function Q$(){if(B$!==void 0)return B$;let $="/opt/homebrew/bin/python3.12";if(TQ($))return B$=$,$;let Q=await f("python3.12");if(Q)return B$=Q,Q;let Z=await f("python3");return B$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B$;var W$=L(()=>{d()});var t1={};h(t1,{runStatus:()=>gQ});import{existsSync as v,readFileSync as U$,readdirSync as l1,statSync as d1}from"fs";import{resolve as D,basename as xQ}from"path";import{homedir as NQ}from"os";async function DQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${q}
2
+ var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.32.3";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=QQ(ZQ(import.meta.url)),Z=d$(Q);$$=e6($Q(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var n$=L(()=>{C()});var C1={};h(C1,{runOrThrow:()=>zQ,run:()=>j,commandVersion:()=>KQ,commandExists:()=>f,ShellError:()=>a$});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,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[W,K,U]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:W,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function zQ($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=XQ($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function XQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function KQ($,Q="--version"){if(!await f($))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 a$;var d=L(()=>{a$=class a$ 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 WQ?"":$}var WQ,T,S,I,TZ,w,R,y,q;var c=L(()=>{WQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),I=a("\x1B[1;33m"),TZ=a("\x1B[0;34m"),w=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),q=a("\x1B[0m")});import{existsSync as TQ}from"fs";async function Q$(){if(B$!==void 0)return B$;let $="/opt/homebrew/bin/python3.12";if(TQ($))return B$=$,$;let Q=await f("python3.12");if(Q)return B$=Q,Q;let Z=await f("python3");return B$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B$;var W$=L(()=>{d()});var t1={};h(t1,{runStatus:()=>gQ});import{existsSync as v,readFileSync as U$,readdirSync as l1,statSync as d1}from"fs";import{resolve as D,basename as xQ}from"path";import{homedir as NQ}from"os";async function DQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${q}
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)
@@ -789,4 +789,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
789
789
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
790
790
  `),process.stderr.write(o6),2}}p1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var ZZ=await QZ(Bun.argv.slice(2));process.exit(ZZ);
791
791
 
792
- //# debugId=F7A4FD0C7A94555A64756E2164756E21
792
+ //# debugId=E69BF9B144A89A3864756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.32.2'
60
+ __version__ = '7.32.3'
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.32.2",
4
+ "version": "7.32.3",
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 11 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
6
6
  "keywords": [
7
7
  "agent",