loki-mode 9.45.0 → 9.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 v9.45.0
6
+ # Loki Mode v9.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
 
@@ -470,4 +470,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
470
470
 
471
471
  ---
472
472
 
473
- **v9.45.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
473
+ **v9.47.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 9.45.0
1
+ 9.47.0
package/autonomy/run.sh CHANGED
@@ -6707,19 +6707,48 @@ copy_skill_files() {
6707
6707
  # Also copy SKILL.md to .loki/ and rewrite paths for workspace access
6708
6708
  if [ -f "$PROJECT_DIR/SKILL.md" ]; then
6709
6709
  # Rewrite skill paths from skills/ to .loki/skills/
6710
- sed -e 's|skills/00-index\.md|.loki/skills/00-index.md|g' \
6711
- -e 's|skills/model-selection\.md|.loki/skills/model-selection.md|g' \
6712
- -e 's|skills/quality-gates\.md|.loki/skills/quality-gates.md|g' \
6713
- -e 's|skills/testing\.md|.loki/skills/testing.md|g' \
6714
- -e 's|skills/troubleshooting\.md|.loki/skills/troubleshooting.md|g' \
6715
- -e 's|skills/production\.md|.loki/skills/production.md|g' \
6716
- -e 's|skills/parallel-workflows\.md|.loki/skills/parallel-workflows.md|g' \
6717
- -e 's|skills/providers\.md|.loki/skills/providers.md|g' \
6718
- -e 's|Read skills/|Read .loki/skills/|g' \
6710
+ # ONE regex, not a hardcoded filename list. The old form named 8 skills
6711
+ # explicitly plus a `Read skills/` catchall, so any NEW skill silently
6712
+ # kept an unrewritten path, and three bare paths in SKILL.md survived
6713
+ # because they say "See", not "Read". The negated class avoids rewriting
6714
+ # an already-correct `.loki/skills/`.
6715
+ # Sentinel-protect, rewrite, restore. This is PORTABLE and idempotent.
6716
+ #
6717
+ # The old form named 8 skills explicitly plus a `Read skills/` catchall,
6718
+ # so any NEW skill silently kept an unrewritten path, and bare paths
6719
+ # introduced by "See skills/..." survived because they are not "Read".
6720
+ # A `sed -E 's|(^|[^.])skills/|...'` one-liner looks tidier but BSD sed
6721
+ # rejects it ("RE error: parentheses not balanced"), which would fail
6722
+ # SILENTLY on macOS and leave every path unrewritten -- verified by
6723
+ # running it. Protecting the already-correct prefixes first is what lets
6724
+ # the bare rewrite be unconditional.
6725
+ sed -e 's|\.loki/skills/|@@LOKI_S@@|g' \
6726
+ -e 's|\.loki/references/|@@LOKI_R@@|g' \
6727
+ -e 's|skills/|.loki/skills/|g' \
6728
+ -e 's|references/|.loki/references/|g' \
6729
+ -e 's|@@LOKI_S@@|.loki/skills/|g' \
6730
+ -e 's|@@LOKI_R@@|.loki/references/|g' \
6719
6731
  "$PROJECT_DIR/SKILL.md" > ".loki/SKILL.md"
6720
6732
  fi
6721
6733
 
6722
- log_info "Copied $copied skill files to .loki/skills/"
6734
+ # ALSO copy references/. The copied skills cite references/*.md 21 times
6735
+ # across 8 files, every one of which existed in the repo and was NEVER
6736
+ # copied, so the agent followed 21 dead paths and silently lost the guidance
6737
+ # this function believes it shipped. Copying skills without their references
6738
+ # is shipping half a manual.
6739
+ local refs_src="$PROJECT_DIR/references"
6740
+ local refs_dst=".loki/references"
6741
+ local refs_copied=0
6742
+ if [ -d "$refs_src" ]; then
6743
+ mkdir -p "$refs_dst"
6744
+ for ref_file in "$refs_src"/*.md; do
6745
+ if [ -f "$ref_file" ]; then
6746
+ cp "$ref_file" "$refs_dst/" && refs_copied=$((refs_copied + 1))
6747
+ fi
6748
+ done
6749
+ fi
6750
+
6751
+ log_info "Copied $copied skill files to .loki/skills/ and $refs_copied references to .loki/references/"
6723
6752
  }
6724
6753
 
6725
6754
  #===============================================================================
@@ -19411,6 +19440,7 @@ load_queue_tasks() {
19411
19440
  # Handles both formats, includes description, acceptance criteria, and user stories
19412
19441
  local extract_script='
19413
19442
  import json
19443
+ import os
19414
19444
  import sys
19415
19445
 
19416
19446
  def extract_tasks(filepath, prefix):
@@ -19422,7 +19452,23 @@ def extract_tasks(filepath, prefix):
19422
19452
  return ""
19423
19453
 
19424
19454
  results = []
19425
- for i, task in enumerate(tasks[:3]): # Limit to first 3 tasks
19455
+ # BOUND BY CHARACTERS, NOT BY AN ARBITRARY TASK COUNT.
19456
+ #
19457
+ # This was `tasks[:3]`, applied SEPARATELY to in-progress.json and
19458
+ # pending.json. A release doc decomposed into 5 tasks silently lost 2
19459
+ # from each file: the agent received a plan it was never told was
19460
+ # truncated, and the founder-facing case ("hand it a release doc") was
19461
+ # quietly capped at 3.
19462
+ #
19463
+ # A count is the wrong bound anyway: one rich PRD task with a 300-char
19464
+ # description plus acceptance criteria can outweigh ten legacy one-liners.
19465
+ # The real constraint is prompt budget, so bound on that and say so when
19466
+ # the budget is hit, rather than truncating in silence.
19467
+ _budget = int(os.environ.get("LOKI_QUEUE_TASK_CHARS", "6000") or "6000")
19468
+ _max_tasks = int(os.environ.get("LOKI_QUEUE_MAX_TASKS", "25") or "25")
19469
+ _used = 0
19470
+ _shown = 0
19471
+ for i, task in enumerate(tasks[:_max_tasks]):
19426
19472
  if not isinstance(task, dict):
19427
19473
  continue
19428
19474
  task_id = task.get("id") or "unknown"
@@ -19446,7 +19492,11 @@ def extract_tasks(filepath, prefix):
19446
19492
  story = task.get("user_story", "")
19447
19493
  if story:
19448
19494
  lines.append(f" User Story: {story}")
19449
- results.append("\n".join(lines))
19495
+ _entry = "\n".join(lines)
19496
+ if _used + len(_entry) > _budget and _shown > 0:
19497
+ break
19498
+ results.append(_entry)
19499
+ _used += len(_entry); _shown += 1
19450
19500
  else:
19451
19501
  # Legacy format: extract action from payload
19452
19502
  task_type = task.get("type") or "unknown"
@@ -19462,8 +19512,21 @@ def extract_tasks(filepath, prefix):
19462
19512
  action = str(action).replace("\n", " ").replace("\r", "")[:500]
19463
19513
  if len(str(action)) > 500:
19464
19514
  action += "..."
19465
- results.append(f"{prefix}[{i+1}] id={task_id} type={task_type}: {action}")
19466
-
19515
+ _entry = f"{prefix}[{i+1}] id={task_id} type={task_type}: {action}"
19516
+ if _used + len(_entry) > _budget and _shown > 0:
19517
+ break
19518
+ results.append(_entry)
19519
+ _used += len(_entry); _shown += 1
19520
+
19521
+ # Disclose truncation instead of hiding it. An agent told it has the
19522
+ # whole plan when it does not will confidently build the wrong subset.
19523
+ _remaining = len(tasks) - _shown
19524
+ if _remaining > 0:
19525
+ results.append(
19526
+ "[... %d more task(s) not shown: prompt budget %d chars reached. "
19527
+ "Raise LOKI_QUEUE_TASK_CHARS or LOKI_QUEUE_MAX_TASKS to include them.]"
19528
+ % (_remaining, _budget)
19529
+ )
19467
19530
  return "\n".join(results)
19468
19531
  except:
19469
19532
  return ""
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "9.45.0"
10
+ __version__ = "9.47.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:** v9.45.0
5
+ **Version:** v9.47.0
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var _t=Object.create;var{getPrototypeOf:vt,defineProperty:jG,getOwnPropertyNames:ht}=Object;var gt=Object.prototype.hasOwnProperty;function mt($){return this[$]}var ut,dt,pt=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?ut??=new WeakMap:dt??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?_t(vt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of ht($))if(!gt.call(J,q))jG(J,q,{get:mt.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var ct=($)=>$;function lt($,X){this[$]=ct.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:lt.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var UR={};B1(UR,{lokiDir:()=>h0,homeLokiDir:()=>WQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as it}from"url";import{existsSync as Zq}from"fs";import{homedir as at}from"os";function ot(){let $=YR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(YR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function WQ(){return h2(at(),".loki")}var YR,L1;var k1=s(()=>{YR=LG(it(import.meta.url));L1=ot()});import{readFileSync as st}from"fs";import{resolve as nt,dirname as rt}from"path";import{fileURLToPath as tt}from"url";function j9(){if(n3!==null)return n3;let $="9.45.0";if(typeof $==="string"&&$.length>0)return n3=$,n3;try{let X=rt(tt(import.meta.url)),Q=AG(X);n3=st(nt(Q,"VERSION"),"utf-8").trim()}catch{n3="unknown"}return n3}var n3=null;var Kq=s(()=>{k1()});var OR={};B1(OR,{runOrThrow:()=>Be,run:()=>$1,readStreamCapped:()=>Jq,commandVersion:()=>Me,commandExists:()=>g5,ShellError:()=>TG,MAX_STDOUT_BYTES:()=>MR});async function Jq($,X=MR){let Q=$.getReader(),z=new TextDecoder,Z="",K=0;try{while(K<X){let{done:J,value:q}=await Q.read();if(J)break;if(!q)continue;if(K+=q.byteLength,K>X){let V=q.byteLength-(K-X);Z+=z.decode(q.subarray(0,V),{stream:!0});break}Z+=z.decode(q,{stream:!0})}Z+=z.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return Z}async function $1($,X={}){let Q=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),z,Z;if(X.timeoutMs&&X.timeoutMs>0)z=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[K,J,q]=await Promise.all([Jq(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:K,stderr:J,exitCode:q}}finally{if(z)clearTimeout(z);if(Z)clearTimeout(Z)}}async function Be($,X={}){let Q=await $1($,X);if(Q.exitCode!==0)throw new TG(`command failed (${Q.exitCode}): ${$.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function g5($){let X=Ne($),Q=await $1(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Ne($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function Me($,X="--version"){if(!await g5($))return null;let z=await $1([$,X],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var MR=16777216,TG;var y8=s(()=>{TG=class TG extends Error{message;exitCode;stdout;stderr;constructor($,X,Q,z){super($);this.message=$;this.exitCode=X;this.stdout=Q;this.stderr=z;this.name="ShellError"}}});function g2($){return Oe?"":$}var Oe,p0,$5,q1,D61,A1,f1,m5,r;var t7=s(()=>{Oe=(process.env.NO_COLOR??"").length>0;p0=g2("\x1B[0;31m"),$5=g2("\x1B[0;32m"),q1=g2("\x1B[1;33m"),D61=g2("\x1B[0;34m"),A1=g2("\x1B[0;36m"),f1=g2("\x1B[1m"),m5=g2("\x1B[2m"),r=g2("\x1B[0m")});import{existsSync as we}from"fs";async function Z2(){if(GQ!==void 0)return GQ;let $="/opt/homebrew/bin/python3.12";if(we($))return GQ=$,$;let X=await g5("python3.12");if(X)return GQ=X,X;let Q=await g5("python3");return GQ=Q,Q}async function I4($,X={}){let Q=await Z2();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return $1([Q,"-c",$],X)}var GQ;var m2=s(()=>{y8()});var vR={};B1(vR,{runStatus:()=>te});import{existsSync as u5,readFileSync as C9,readdirSync as ER,statSync as xR}from"fs";import{resolve as A5,basename as pe}from"path";import{homedir as ce}from"os";function kR($){let X=Math.trunc($);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 SR($,X,Q){if(X===0)return null;let z=Math.trunc($*100/X),Z=Math.trunc($*Vq/X);if(Z>Vq)Z=Vq;let K=Vq-Z,J=$5;if(z>=80)J=p0;else if(z>=50)J=q1;let q="=".repeat(Math.max(0,Z))+" ".repeat(Math.max(0,K)),V=kR($),Y=kR(X);return` ${f1}${Q}${r} ${J}[${q}]${r} ${z}% (${V} / ${Y})`}async function ie(){if(await g5("jq"))return!0;return process.stdout.write(`${p0}Error: jq is required but not installed.${r}
2
+ var _t=Object.create;var{getPrototypeOf:vt,defineProperty:jG,getOwnPropertyNames:ht}=Object;var gt=Object.prototype.hasOwnProperty;function mt($){return this[$]}var ut,dt,pt=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?ut??=new WeakMap:dt??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?_t(vt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of ht($))if(!gt.call(J,q))jG(J,q,{get:mt.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var ct=($)=>$;function lt($,X){this[$]=ct.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:lt.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var UR={};B1(UR,{lokiDir:()=>h0,homeLokiDir:()=>WQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as it}from"url";import{existsSync as Zq}from"fs";import{homedir as at}from"os";function ot(){let $=YR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(YR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function WQ(){return h2(at(),".loki")}var YR,L1;var k1=s(()=>{YR=LG(it(import.meta.url));L1=ot()});import{readFileSync as st}from"fs";import{resolve as nt,dirname as rt}from"path";import{fileURLToPath as tt}from"url";function j9(){if(n3!==null)return n3;let $="9.47.0";if(typeof $==="string"&&$.length>0)return n3=$,n3;try{let X=rt(tt(import.meta.url)),Q=AG(X);n3=st(nt(Q,"VERSION"),"utf-8").trim()}catch{n3="unknown"}return n3}var n3=null;var Kq=s(()=>{k1()});var OR={};B1(OR,{runOrThrow:()=>Be,run:()=>$1,readStreamCapped:()=>Jq,commandVersion:()=>Me,commandExists:()=>g5,ShellError:()=>TG,MAX_STDOUT_BYTES:()=>MR});async function Jq($,X=MR){let Q=$.getReader(),z=new TextDecoder,Z="",K=0;try{while(K<X){let{done:J,value:q}=await Q.read();if(J)break;if(!q)continue;if(K+=q.byteLength,K>X){let V=q.byteLength-(K-X);Z+=z.decode(q.subarray(0,V),{stream:!0});break}Z+=z.decode(q,{stream:!0})}Z+=z.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return Z}async function $1($,X={}){let Q=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),z,Z;if(X.timeoutMs&&X.timeoutMs>0)z=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[K,J,q]=await Promise.all([Jq(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:K,stderr:J,exitCode:q}}finally{if(z)clearTimeout(z);if(Z)clearTimeout(Z)}}async function Be($,X={}){let Q=await $1($,X);if(Q.exitCode!==0)throw new TG(`command failed (${Q.exitCode}): ${$.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function g5($){let X=Ne($),Q=await $1(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Ne($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function Me($,X="--version"){if(!await g5($))return null;let z=await $1([$,X],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var MR=16777216,TG;var y8=s(()=>{TG=class TG extends Error{message;exitCode;stdout;stderr;constructor($,X,Q,z){super($);this.message=$;this.exitCode=X;this.stdout=Q;this.stderr=z;this.name="ShellError"}}});function g2($){return Oe?"":$}var Oe,p0,$5,q1,D61,A1,f1,m5,r;var t7=s(()=>{Oe=(process.env.NO_COLOR??"").length>0;p0=g2("\x1B[0;31m"),$5=g2("\x1B[0;32m"),q1=g2("\x1B[1;33m"),D61=g2("\x1B[0;34m"),A1=g2("\x1B[0;36m"),f1=g2("\x1B[1m"),m5=g2("\x1B[2m"),r=g2("\x1B[0m")});import{existsSync as we}from"fs";async function Z2(){if(GQ!==void 0)return GQ;let $="/opt/homebrew/bin/python3.12";if(we($))return GQ=$,$;let X=await g5("python3.12");if(X)return GQ=X,X;let Q=await g5("python3");return GQ=Q,Q}async function I4($,X={}){let Q=await Z2();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return $1([Q,"-c",$],X)}var GQ;var m2=s(()=>{y8()});var vR={};B1(vR,{runStatus:()=>te});import{existsSync as u5,readFileSync as C9,readdirSync as ER,statSync as xR}from"fs";import{resolve as A5,basename as pe}from"path";import{homedir as ce}from"os";function kR($){let X=Math.trunc($);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 SR($,X,Q){if(X===0)return null;let z=Math.trunc($*100/X),Z=Math.trunc($*Vq/X);if(Z>Vq)Z=Vq;let K=Vq-Z,J=$5;if(z>=80)J=p0;else if(z>=50)J=q1;let q="=".repeat(Math.max(0,Z))+" ".repeat(Math.max(0,K)),V=kR($),Y=kR(X);return` ${f1}${Q}${r} ${J}[${q}]${r} ${z}% (${V} / ${Y})`}async function ie(){if(await g5("jq"))return!0;return process.stdout.write(`${p0}Error: jq is required but not installed.${r}
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)
@@ -1337,4 +1337,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
1337
1337
  `),2}case"start":{let{runStart:z}=await Promise.resolve().then(() => (yt(),St));return z(Q)}default:return process.stderr.write(`Unknown command: ${X}
1338
1338
  `),process.stderr.write(bt),2}}wR();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var K61=await Z61(Bun.argv.slice(2));process.exit(K61);
1339
1339
 
1340
- //# debugId=1DD437C40F72C7156A78843ECF29A101
1340
+ //# debugId=F841E90B748FCDE3BB7AD1804EE3AED0
package/mcp/__init__.py CHANGED
@@ -75,4 +75,4 @@ try:
75
75
  except ImportError:
76
76
  __all__ = ['mcp']
77
77
 
78
- __version__ = '9.45.0'
78
+ __version__ = '9.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": "9.45.0",
4
+ "version": "9.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, opencode).",
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": "9.45.0",
5
+ "version": "9.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",
@@ -204,6 +204,35 @@ _loki_build_claude_auto_flags() {
204
204
  _LOKI_CLAUDE_AUTO_FLAGS+=("--exclude-dynamic-system-prompt-sections")
205
205
  fi
206
206
 
207
+ # --add-dir: grant the agent READ access to sibling repositories.
208
+ #
209
+ # THE DEFECT THIS CLOSES: an agent asked to change a shared type in
210
+ # ../service-b could not read it, did not error, and GUESSED. The user got a
211
+ # change that does not compile with no signal why. Silent wrong output is
212
+ # the worst failure mode this product has.
213
+ #
214
+ # LOKI_ADD_DIRS is a colon-separated list, matching PATH convention so an
215
+ # operator does not have to learn a new separator. Each entry is passed as
216
+ # its own `--add-dir <path>` pair.
217
+ #
218
+ # Only EXISTING directories are passed. A typo would otherwise abort the CLI
219
+ # and take the whole run with it, turning a convenience into an outage.
220
+ # Skipped entries are announced, because silently dropping a directory the
221
+ # operator asked for is the same silent-wrong-output defect in a new place.
222
+ if [ -n "${LOKI_ADD_DIRS:-}" ] && loki_claude_flag_supported "--add-dir"; then
223
+ local _ad_old_ifs="$IFS"
224
+ IFS=':'
225
+ for _ad in ${LOKI_ADD_DIRS}; do
226
+ [ -n "$_ad" ] || continue
227
+ if [ -d "$_ad" ]; then
228
+ _LOKI_CLAUDE_AUTO_FLAGS+=("--add-dir" "$_ad")
229
+ else
230
+ printf 'loki: LOKI_ADD_DIRS entry is not a directory, skipping: %s\n' "$_ad" >&2
231
+ fi
232
+ done
233
+ IFS="$_ad_old_ifs"
234
+ fi
235
+
207
236
  # --mcp-config (Phase D, v7.5.22). Variadic flag (Commander `<configs...>`):
208
237
  # Claude expects SEPARATE argv elements per path, not one space-joined
209
238
  # value. Per Dev-C parity concern -- spread each path as its own argv