loki-mode 7.129.0 → 7.129.1

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 v7.129.0
6
+ # Loki Mode v7.129.1
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
408
408
 
409
409
  ---
410
410
 
411
- **v7.129.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
411
+ **v7.129.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.129.0
1
+ 7.129.1
package/autonomy/loki CHANGED
@@ -2348,6 +2348,44 @@ cmd_start() {
2348
2348
  fi
2349
2349
  fi
2350
2350
 
2351
+ # Shared-checkout concurrency guard (client bug: N `loki start ... --detach`
2352
+ # in ONE working dir with a SHARED .loki/ collide on non-namespaced worktree
2353
+ # paths + branches, producing missing worktrees and log spam). Issue-mode
2354
+ # namespaces pids PER SESSION, so the global loki.pid check below never trips
2355
+ # for DIFFERENT issues -- there was no guard on concurrent runs per checkout.
2356
+ # Detect a LIVE sibling loki running in THIS checkout via per-run pid markers
2357
+ # (crash-safe: dead markers are pruned via kill -0, so no deadlock). A run
2358
+ # that isolates its state (a distinct LOKI_DIR, i.e. NOT the default .loki) is
2359
+ # exempt -- that IS the safe per-issue pattern. Escape hatch:
2360
+ # LOKI_ALLOW_SHARED_CHECKOUT=1.
2361
+ local _co_loki_dir="${LOKI_DIR:-.loki}"
2362
+ if [ "${LOKI_ALLOW_SHARED_CHECKOUT:-0}" != "1" ] \
2363
+ && { [ -z "${LOKI_DIR:-}" ] || [ "$(cd "${LOKI_DIR}" 2>/dev/null && pwd)" = "$(cd .loki 2>/dev/null && pwd)" ]; }; then
2364
+ local _co_runs_dir="$_co_loki_dir/checkout-runs"
2365
+ mkdir -p "$_co_runs_dir" 2>/dev/null || true
2366
+ local _co_marker _co_pid _co_live=""
2367
+ for _co_marker in "$_co_runs_dir"/*; do
2368
+ [ -f "$_co_marker" ] || continue
2369
+ _co_pid=$(cat "$_co_marker" 2>/dev/null | tr -dc '0-9')
2370
+ if [ -n "$_co_pid" ] && [ "$_co_pid" != "$$" ] && kill -0 "$_co_pid" 2>/dev/null; then
2371
+ _co_live="$_co_pid"; break
2372
+ fi
2373
+ # Prune a dead marker.
2374
+ [ -n "$_co_pid" ] && rm -f "$_co_marker" 2>/dev/null || true
2375
+ done
2376
+ if [ -n "$_co_live" ]; then
2377
+ echo -e "${RED}Error: another loki run (pid $_co_live) is active in this checkout ($(pwd)).${NC}" >&2
2378
+ echo -e "${YELLOW}Running several issues at once from ONE checkout + shared .loki/ collides on worktrees and branches.${NC}" >&2
2379
+ echo -e "${YELLOW}Give each issue its own worktree AND state dir, e.g.:${NC}" >&2
2380
+ echo -e "${YELLOW} git worktree add ../repo-issue-N -b loki/issue-N && (cd ../repo-issue-N && LOKI_DIR=\"\$PWD/.loki\" loki start owner/repo#N --pr --detach)${NC}" >&2
2381
+ echo -e "${YELLOW}See 'skills/github-integration.md' (Multiple issues at once). Override with LOKI_ALLOW_SHARED_CHECKOUT=1.${NC}" >&2
2382
+ exit 1
2383
+ fi
2384
+ # Register our own marker for the duration of this run (best-effort;
2385
+ # pruned by the next start if we die, and by 'loki stop').
2386
+ echo "$$" > "$_co_runs_dir/$$" 2>/dev/null || true
2387
+ fi
2388
+
2351
2389
  # v7.5.12 Gap B: Stale-PID detection. Hard-kill (Ctrl+C followed by SIGKILL or
2352
2390
  # `loki stop`) can leave .loki/loki.pid + .loki/session.lock orphaned. The
2353
2391
  # next `loki start` then refuses to launch -- or worse, run.sh's downstream
package/autonomy/run.sh CHANGED
@@ -817,8 +817,27 @@ fi
817
817
  PARALLEL_MODE=${LOKI_PARALLEL_MODE:-false}
818
818
  MAX_WORKTREES=${LOKI_MAX_WORKTREES:-5}
819
819
  MAX_PARALLEL_SESSIONS=${LOKI_MAX_PARALLEL_SESSIONS:-3}
820
- PARALLEL_TESTING=${LOKI_PARALLEL_TESTING:-true}
821
- PARALLEL_DOCS=${LOKI_PARALLEL_DOCS:-true}
820
+ # Nested-agent guard: the fixed testing/docs sub-streams each spawn a FURTHER
821
+ # nested `claude` session in a NON-namespaced worktree (`<project>-testing`,
822
+ # `<project>-docs`). When Loki itself runs inside another agent session (Claude
823
+ # Code sets CLAUDECODE; the Loki Mode skill, or a user driving the CLI as a
824
+ # background process from an agent), those nested spawns fight over the shared
825
+ # checkout/worktree paths and flood the log -- the reported "N issues in one dir"
826
+ # failure. So default the sub-streams OFF when nested; the core issue work still
827
+ # runs. Explicit LOKI_PARALLEL_TESTING/DOCS=true overrides (opt back in). This
828
+ # does NOT disable the user's own --parallel/--pr issue work, only the auxiliary
829
+ # testing/docs fan-out that is unsafe to nest.
830
+ _loki_nested_agent=false
831
+ if [ -n "${CLAUDECODE:-}" ] || [ -n "${LOKI_NESTED_AGENT:-}" ]; then
832
+ _loki_nested_agent=true
833
+ fi
834
+ if [ "$_loki_nested_agent" = "true" ]; then
835
+ PARALLEL_TESTING=${LOKI_PARALLEL_TESTING:-false}
836
+ PARALLEL_DOCS=${LOKI_PARALLEL_DOCS:-false}
837
+ else
838
+ PARALLEL_TESTING=${LOKI_PARALLEL_TESTING:-true}
839
+ PARALLEL_DOCS=${LOKI_PARALLEL_DOCS:-true}
840
+ fi
822
841
 
823
842
  # Dynamic resource-aware session concurrency (Release 3, slice 3).
824
843
  # DEFAULT OFF: when LOKI_DYNAMIC_CONCURRENCY is unset, effective_session_cap()
@@ -4423,6 +4442,7 @@ process_pending_merges() {
4423
4442
  local failed=0
4424
4443
 
4425
4444
  for signal_file in "$signals_dir"/MERGE_REQUESTED_*; do
4445
+ case "$signal_file" in *.ack) continue ;; esac
4426
4446
  [ -f "$signal_file" ] || continue
4427
4447
  local stream_name
4428
4448
  stream_name=$(basename "$signal_file" | sed 's/MERGE_REQUESTED_//')
@@ -4465,12 +4485,28 @@ check_merge_queue() {
4465
4485
  fi
4466
4486
 
4467
4487
  for signal in "$signals_dir"/MERGE_REQUESTED_*; do
4488
+ # Skip our own acknowledgement markers (*.ack) so they are not treated
4489
+ # as signals themselves.
4490
+ case "$signal" in *.ack) continue ;; esac
4468
4491
  if [ -f "$signal" ]; then
4469
4492
  local feature=$(basename "$signal" | sed 's/MERGE_REQUESTED_//')
4470
- log_info "Merge requested: $feature"
4471
4493
 
4472
4494
  if [ "$AUTO_MERGE" = "true" ]; then
4495
+ # Auto-merge consumes the signal (merge_feature rm's it), so the
4496
+ # log line fires exactly once per completed feature.
4497
+ log_info "Merge requested: $feature"
4473
4498
  merge_feature "$feature"
4499
+ else
4500
+ # Auto-merge is OFF (e.g. `--pr` without `--ship`): the PR is left
4501
+ # for a human to merge, so the signal is INTENTIONALLY not consumed.
4502
+ # Log ONCE (guarded by a sibling .ack marker) instead of re-logging
4503
+ # the same "Merge requested" line every orchestrator pass -- that
4504
+ # re-log was a 90-minute "looks stuck" spam while real work was
4505
+ # already done. The signal itself is preserved untouched.
4506
+ if [ ! -f "${signal}.ack" ]; then
4507
+ log_info "Merge requested: $feature (PR ready; auto-merge off -- merge the PR, or use --ship to auto-merge)"
4508
+ : > "${signal}.ack" 2>/dev/null || true
4509
+ fi
4474
4510
  fi
4475
4511
  fi
4476
4512
  done
@@ -4585,8 +4621,9 @@ merge_feature() {
4585
4621
  fi
4586
4622
  fi
4587
4623
 
4588
- # Remove signal
4589
- rm -f "$TARGET_DIR/.loki/signals/MERGE_REQUESTED_$feature"
4624
+ # Remove signal (and its log-once .ack marker, if any)
4625
+ rm -f "$TARGET_DIR/.loki/signals/MERGE_REQUESTED_$feature" \
4626
+ "$TARGET_DIR/.loki/signals/MERGE_REQUESTED_$feature.ack"
4590
4627
 
4591
4628
  # Remove worktree
4592
4629
  remove_worktree "feature-$clean_feature"
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.129.0"
10
+ __version__ = "7.129.1"
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.129.0
5
+ **Version:** v7.129.1
6
6
 
7
7
  ---
8
8
 
@@ -396,7 +396,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
396
396
  # Run Loki Mode in Docker (Claude provider, API-key auth)
397
397
  docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
398
398
  -v $(pwd):/workspace -w /workspace \
399
- asklokesh/loki-mode:7.129.0 start ./my-spec.md
399
+ asklokesh/loki-mode:7.129.1 start ./my-spec.md
400
400
  ```
401
401
 
402
402
  ##### docker compose + .env (no host install)
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var q8=Object.defineProperty;var J8=($)=>$;function V8($,Q){this[$]=J8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)q8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:V8.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var m1={};b(m1,{lokiDir:()=>j,homeLokiDir:()=>T$,findRepoRootForVersion:()=>$1,REPO_ROOT:()=>h});import{resolve as t,dirname as e$}from"path";import{fileURLToPath as W8}from"url";import{existsSync as N$}from"fs";import{homedir as H8}from"os";function G8(){let $=v1;for(let Q=0;Q<6;Q++){if(N$(t($,"VERSION"))&&N$(t($,"autonomy/run.sh")))return $;let Z=e$($);if(Z===$)break;$=Z}return t(v1,"..","..","..")}function $1($){let Q=$;for(let Z=0;Z<6;Z++){if(N$(t(Q,"VERSION"))&&N$(t(Q,"autonomy/run.sh")))return Q;let z=e$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function j(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(H8(),".loki")}var v1,h;var C=P(()=>{v1=e$(W8(import.meta.url));h=G8()});import{readFileSync as U8}from"fs";import{resolve as Y8,dirname as B8}from"path";import{fileURLToPath as M8}from"url";function S$(){if(Q$!==null)return Q$;let $="7.129.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=B8(M8(import.meta.url)),Z=$1(Q);Q$=U8(Y8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var Q1=P(()=>{C()});var p1={};b(p1,{runOrThrow:()=>S8,run:()=>R,readStreamCapped:()=>c1,commandVersion:()=>C8,commandExists:()=>u,ShellError:()=>Z1,MAX_STDOUT_BYTES:()=>u1});async function c1($,Q=u1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:W}=await Z.read();if(K)break;if(!W)continue;if(q+=W.byteLength,q>Q){let V=W.byteLength-(q-Q);X+=z.decode(W.subarray(0,V),{stream:!0});break}X+=z.decode(W,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function R($,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([c1(Z.stdout),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 S8($,Q={}){let Z=await R($,Q);if(Z.exitCode!==0)throw new Z1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function u($){let Q=D8($),Z=await R(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function D8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function C8($,Q="--version"){if(!await u($))return null;let z=await R([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var u1=16777216,Z1;var o=P(()=>{Z1=class Z1 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 r($){return b8?"":$}var b8,M,S,_,tZ,L,k,y,J;var p=P(()=>{b8=(process.env.NO_COLOR??"").length>0;M=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),tZ=r("\x1B[0;34m"),L=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as l8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(l8($))return A$=$,$;let Q=await u("python3.12");if(Q)return A$=Q,Q;let Z=await u("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return R([Z,"-c",$],Q)}var A$;var V$=P(()=>{o()});var V0={};b(V0,{runStatus:()=>U3});import{existsSync as v,readFileSync as H$,readdirSync as $0,statSync as Q0}from"fs";import{resolve as D,basename as z3}from"path";import{homedir as X3}from"os";function Z0($){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 z0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*C$/Q);if(X>C$)X=C$;let q=C$-X,K=S;if(z>=80)K=M;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=Z0($),H=Z0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${H})`}async function q3(){if(await u("jq"))return!0;return process.stdout.write(`${M}Error: jq is required but not installed.${J}
2
+ var q8=Object.defineProperty;var J8=($)=>$;function V8($,Q){this[$]=J8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)q8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:V8.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var m1={};b(m1,{lokiDir:()=>j,homeLokiDir:()=>T$,findRepoRootForVersion:()=>$1,REPO_ROOT:()=>h});import{resolve as t,dirname as e$}from"path";import{fileURLToPath as W8}from"url";import{existsSync as N$}from"fs";import{homedir as H8}from"os";function G8(){let $=v1;for(let Q=0;Q<6;Q++){if(N$(t($,"VERSION"))&&N$(t($,"autonomy/run.sh")))return $;let Z=e$($);if(Z===$)break;$=Z}return t(v1,"..","..","..")}function $1($){let Q=$;for(let Z=0;Z<6;Z++){if(N$(t(Q,"VERSION"))&&N$(t(Q,"autonomy/run.sh")))return Q;let z=e$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function j(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(H8(),".loki")}var v1,h;var C=P(()=>{v1=e$(W8(import.meta.url));h=G8()});import{readFileSync as U8}from"fs";import{resolve as Y8,dirname as B8}from"path";import{fileURLToPath as M8}from"url";function S$(){if(Q$!==null)return Q$;let $="7.129.1";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=B8(M8(import.meta.url)),Z=$1(Q);Q$=U8(Y8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var Q1=P(()=>{C()});var p1={};b(p1,{runOrThrow:()=>S8,run:()=>R,readStreamCapped:()=>c1,commandVersion:()=>C8,commandExists:()=>u,ShellError:()=>Z1,MAX_STDOUT_BYTES:()=>u1});async function c1($,Q=u1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:W}=await Z.read();if(K)break;if(!W)continue;if(q+=W.byteLength,q>Q){let V=W.byteLength-(q-Q);X+=z.decode(W.subarray(0,V),{stream:!0});break}X+=z.decode(W,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function R($,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([c1(Z.stdout),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 S8($,Q={}){let Z=await R($,Q);if(Z.exitCode!==0)throw new Z1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function u($){let Q=D8($),Z=await R(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function D8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function C8($,Q="--version"){if(!await u($))return null;let z=await R([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var u1=16777216,Z1;var o=P(()=>{Z1=class Z1 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 r($){return b8?"":$}var b8,M,S,_,tZ,L,k,y,J;var p=P(()=>{b8=(process.env.NO_COLOR??"").length>0;M=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),tZ=r("\x1B[0;34m"),L=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as l8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(l8($))return A$=$,$;let Q=await u("python3.12");if(Q)return A$=Q,Q;let Z=await u("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return R([Z,"-c",$],Q)}var A$;var V$=P(()=>{o()});var V0={};b(V0,{runStatus:()=>U3});import{existsSync as v,readFileSync as H$,readdirSync as $0,statSync as Q0}from"fs";import{resolve as D,basename as z3}from"path";import{homedir as X3}from"os";function Z0($){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 z0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*C$/Q);if(X>C$)X=C$;let q=C$-X,K=S;if(z>=80)K=M;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=Z0($),H=Z0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${H})`}async function q3(){if(await u("jq"))return!0;return process.stdout.write(`${M}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)
@@ -814,4 +814,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
814
814
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
815
815
  `),process.stderr.write(K8),2}}e1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var SZ=await NZ(Bun.argv.slice(2));process.exit(SZ);
816
816
 
817
- //# debugId=4C57B2F2D01C30E264756E2164756E21
817
+ //# debugId=E67A22525236ED6C64756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.129.0'
60
+ __version__ = '7.129.1'
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.129.0",
4
+ "version": "7.129.1",
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.129.0",
5
+ "version": "7.129.1",
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",
@@ -171,6 +171,63 @@ LOKI_GITHUB_IMPORT=true \
171
171
 
172
172
  ---
173
173
 
174
+ ## Multiple issues at once (parallel, reliably)
175
+
176
+ There are two safe ways to work several issues; pick by whether you want them
177
+ concurrent.
178
+
179
+ ### Sequential (simplest, one process, one checkout)
180
+
181
+ Import all open issues into one queue and let a single run drain them one at a
182
+ time. No worktree/state collisions because there is only one run.
183
+
184
+ ```bash
185
+ LOKI_GITHUB_IMPORT=true loki start # drains .loki/queue sequentially
186
+ ```
187
+
188
+ ### Concurrent (one isolated run PER issue)
189
+
190
+ To run N issues at the SAME time, each run needs its OWN git worktree AND its
191
+ OWN state dir. `--pr`/`--worktree` isolates the git branch, but two runs in the
192
+ same checkout still share one `.loki/` unless you also set a distinct `LOKI_DIR`.
193
+ Give each issue its own checkout + `LOKI_DIR`:
194
+
195
+ ```bash
196
+ for n in 101 102 103; do
197
+ git worktree add "../repo-issue-$n" -b "loki/issue-$n" >/dev/null
198
+ ( cd "../repo-issue-$n" \
199
+ && LOKI_DIR="$PWD/.loki" \
200
+ loki start "owner/repo#$n" --pr --detach )
201
+ done
202
+ loki status --all # watch all runs
203
+ ```
204
+
205
+ Each run is fully isolated: separate working tree, branch, and `.loki/` state, so
206
+ they cannot corrupt each other's branches or worktrees.
207
+
208
+ DO NOT run several `loki start ... --detach` from the SAME directory with a shared
209
+ `.loki/`. The runs collide on shared (non-namespaced) worktree paths and state,
210
+ which produces missing branches, "claimed worktree missing", and a repeating
211
+ `Merge requested: testing/docs` log (the auxiliary sub-streams, not your issues).
212
+
213
+ ### Inside a Claude Code session (the Loki Mode skill)
214
+
215
+ When you invoke Loki Mode from inside Claude Code, Loki detects the nested agent
216
+ (`CLAUDECODE`) and automatically disables its internal `testing`/`docs`
217
+ worktree sub-streams (they would spawn further nested `claude` sessions that
218
+ collide) -- your issue work still runs. To fan out across several issues from a
219
+ Claude Code session, prefer the per-issue-isolated loop above (each with its own
220
+ worktree + `LOKI_DIR`), or let Claude Code orchestrate one Loki run per issue.
221
+ Re-enable the sub-streams explicitly with `LOKI_PARALLEL_TESTING=true` /
222
+ `LOKI_PARALLEL_DOCS=true` only if you know the checkout is not shared.
223
+
224
+ Notes:
225
+ - `--pr` leaves the PR for you to merge (auto-merge OFF). Use `--ship` to
226
+ auto-merge. Either way the "Merge requested" line now logs once, not on a loop.
227
+ - `loki status --all` and `loki stop --all` operate across all runs/projects.
228
+
229
+ ---
230
+
174
231
  ## CLI Commands
175
232
 
176
233
  ```bash