loki-mode 7.32.1 → 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.1
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.1 | [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.1
1
+ 7.32.3
package/autonomy/loki CHANGED
@@ -711,6 +711,7 @@ show_help() {
711
711
  echo " --no-dashboard Disable web dashboard"
712
712
  echo " --sandbox Run in Docker sandbox for isolation"
713
713
  echo " --skip-memory Skip loading memory context at startup"
714
+ echo " --fresh-prd Regenerate the PRD from the codebase (no-PRD runs; ignores the reusable generated PRD)"
714
715
  echo " --compliance PRESET Enable compliance mode (default|healthcare|fintech|government)"
715
716
  echo " --budget USD Set cost budget limit (display in dashboard/status)"
716
717
  echo " --bmad-project PATH Use BMAD Method project artifacts as input"
@@ -1030,6 +1031,9 @@ cmd_start() {
1030
1031
  echo " --api Start dashboard API server alongside the build"
1031
1032
  echo " --sandbox Run in Docker sandbox"
1032
1033
  echo " --skip-memory Skip loading memory context at startup"
1034
+ echo " --fresh-prd Regenerate the PRD from the codebase on a no-PRD run"
1035
+ echo " (ignores the reusable generated PRD; aliases: --regen-prd,"
1036
+ echo " --regenerate-prd, --regen, or LOKI_PRD_REGEN=1)"
1033
1037
  echo " --compliance PRESET Enable compliance mode (default|healthcare|fintech|government)"
1034
1038
  echo " --budget USD Cost budget limit (auto-pause when exceeded)"
1035
1039
  echo " --bmad-project PATH Use BMAD Method project artifacts as input"
@@ -1142,10 +1146,12 @@ cmd_start() {
1142
1146
  args+=("--parallel")
1143
1147
  shift
1144
1148
  ;;
1145
- --regen-prd|--regenerate-prd|--regen)
1149
+ --regen-prd|--regenerate-prd|--regen|--fresh-prd)
1146
1150
  # v7.8.1: force a fresh generated PRD on a no-PRD run, overriding
1147
1151
  # the staleness-aware reuse (decide_generated_prd_action in
1148
1152
  # run.sh reads LOKI_PRD_REGEN). Exported so the runner sees it.
1153
+ # --fresh-prd is the documented alias surfaced in the reuse
1154
+ # disclosure line ("pass --fresh-prd to regenerate").
1149
1155
  export LOKI_PRD_REGEN=1
1150
1156
  shift
1151
1157
  ;;
@@ -1820,13 +1826,30 @@ cmd_start() {
1820
1826
  echo -e "${YELLOW}Warning: No PRD file specified. Auto-confirming (CI mode).${NC}"
1821
1827
  else
1822
1828
  echo -e "${YELLOW}No PRD file specified.${NC}"
1823
- echo "Loki Mode will analyze the existing codebase and generate"
1824
- echo "a PRD automatically. No requirements document needed."
1829
+ # v7.8.1+: the runner (run_autonomous -> decide_generated_prd_action)
1830
+ # makes the real reuse|update|generate decision. Here we only adjust
1831
+ # the TTY prompt wording so it does not falsely claim "generate" when
1832
+ # a previously generated PRD already exists. --regen-prd / --fresh-prd
1833
+ # (LOKI_PRD_REGEN=1) always forces a fresh generation.
1834
+ local _prompt_q="Generate PRD from codebase and start? [Y/n] "
1835
+ if [ "${LOKI_PRD_REGEN:-}" = "1" ]; then
1836
+ echo "Loki Mode will regenerate the PRD from the codebase (forced)."
1837
+ _prompt_q="Regenerate PRD from codebase (forced) and start? [Y/n] "
1838
+ elif [ -f ".loki/generated-prd.md" ] || [ -f ".loki/generated-prd.json" ]; then
1839
+ echo "A previously generated PRD exists. Loki Mode will reuse it"
1840
+ echo "if the codebase is unchanged, update it incrementally if it"
1841
+ echo "changed, or use it as-is if you hand-edited it. Pass"
1842
+ echo "--fresh-prd to regenerate from scratch."
1843
+ _prompt_q="Continue with the existing generated PRD and start? [Y/n] "
1844
+ else
1845
+ echo "Loki Mode will analyze the existing codebase and generate"
1846
+ echo "a PRD automatically. No requirements document needed."
1847
+ fi
1825
1848
  echo ""
1826
1849
  # v7.5.3 UX: rephrased + default flipped from N to Y. The pre-v7.5.3
1827
1850
  # "Continue? [y/N]" with default-N caused users to accidentally
1828
1851
  # cancel by hitting Enter after reading the explanation.
1829
- echo -e "Generate PRD from codebase and start? [Y/n] \c"
1852
+ echo -e "$_prompt_q\c"
1830
1853
  read -r confirm
1831
1854
  if [[ "$confirm" =~ ^[Nn] ]]; then
1832
1855
  echo "Aborted. Usage: loki start <path-to-prd.md>"
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,16 +4576,54 @@ 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
  }
4577
4602
 
4603
+ # Content hash of the generated PRD file itself (NOT the codebase). Used to
4604
+ # detect that a user hand-edited the generated PRD: when the file no longer
4605
+ # matches the prd_sha Loki recorded after it last wrote the file, the PRD is
4606
+ # user-owned and must be used as-is, never silently overwritten. Echoes "" when
4607
+ # no generated PRD file is present.
4608
+ _loki_prd_file_hash() {
4609
+ local loki_dir="${1:-.}/.loki"
4610
+ local f=""
4611
+ if [ -f "$loki_dir/generated-prd.md" ]; then
4612
+ f="$loki_dir/generated-prd.md"
4613
+ elif [ -f "$loki_dir/generated-prd.json" ]; then
4614
+ f="$loki_dir/generated-prd.json"
4615
+ fi
4616
+ [ -n "$f" ] || { echo ""; return 0; }
4617
+ _loki_hash_stdin < "$f"
4618
+ }
4619
+
4578
4620
  # Decide what to do with a previously generated PRD on a no-PRD run.
4579
- # Echoes one of: reuse | update | generate. Never fails the run.
4580
- # - LOKI_PRD_REGEN=1 (or --regen-prd, which sets it) -> generate (force fresh).
4621
+ # Echoes one of: reuse | update | generate | user_owned. Never fails the run.
4622
+ # Precedence: force-regen > user_owned (hand-edited) > reuse/update > generate.
4623
+ # - LOKI_PRD_REGEN=1 (or --regen-prd/--fresh-prd, which set it) -> generate.
4581
4624
  # - no generated PRD present -> generate (first run).
4625
+ # - generated PRD present but its content hash differs from the recorded
4626
+ # prd_sha -> user_owned (the user hand-edited it; use as-is, do not rewrite).
4582
4627
  # - generated PRD present, no recorded signature -> update (have a PRD but no
4583
4628
  # provenance: reconcile incrementally rather than trust-blindly or discard).
4584
4629
  # - signature matches current codebase -> reuse (unchanged).
@@ -4595,19 +4640,54 @@ decide_generated_prd_action() {
4595
4640
  if [ ! -f "$sig_file" ]; then
4596
4641
  echo "update"; return 0
4597
4642
  fi
4598
- local stored current
4643
+ local stored stored_prd_sha current cur_prd_sha
4599
4644
  stored=$(LOKI_SIG_FILE="$sig_file" python3 -c "
4600
4645
  import json, os
4601
4646
  try:
4602
4647
  print(json.load(open(os.environ['LOKI_SIG_FILE'])).get('signature',''))
4603
4648
  except Exception:
4604
4649
  print('')
4650
+ " 2>/dev/null)
4651
+ stored_prd_sha=$(LOKI_SIG_FILE="$sig_file" python3 -c "
4652
+ import json, os
4653
+ try:
4654
+ print(json.load(open(os.environ['LOKI_SIG_FILE'])).get('prd_sha',''))
4655
+ except Exception:
4656
+ print('')
4605
4657
  " 2>/dev/null)
4606
4658
  [ -z "$stored" ] && { echo "update"; return 0; }
4659
+ # Hand-edit detection (precedence above reuse/update): if we recorded a
4660
+ # prd_sha and the file no longer matches it, the user edited it themselves.
4661
+ # Treat as user-owned: use as-is, never regenerate over their changes.
4662
+ if [ -n "$stored_prd_sha" ]; then
4663
+ cur_prd_sha=$(_loki_prd_file_hash "${TARGET_DIR:-.}")
4664
+ if [ -n "$cur_prd_sha" ] && [ "$cur_prd_sha" != "$stored_prd_sha" ]; then
4665
+ echo "user_owned"; return 0
4666
+ fi
4667
+ fi
4607
4668
  current=$(compute_codebase_signature "${TARGET_DIR:-.}")
4608
4669
  if [ "$stored" = "$current" ]; then
4609
4670
  echo "reuse"
4610
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
4611
4691
  echo "update"
4612
4692
  fi
4613
4693
  }
@@ -4618,6 +4698,12 @@ except Exception:
4618
4698
  persist_prd_signature_if_present() {
4619
4699
  local exit_code="${1:-0}"
4620
4700
  [ "$exit_code" = "0" ] || return 0
4701
+ # Hand-edited (user-owned) PRD: do NOT rewrite the signature. Re-hashing the
4702
+ # user's edited file would re-baseline its content as the new prd_sha, so the
4703
+ # next run would fall through to plain reuse with the wrong (non-user-owned)
4704
+ # disclosure. Preserve the prior Loki-authored prd_sha/generated_at so every
4705
+ # subsequent run keeps detecting user_owned until --fresh-prd forces a regen.
4706
+ [ "${GENERATED_PRD_ACTION:-}" = "user_owned" ] && return 0
4621
4707
  # only for no-PRD runs whose generated PRD exists
4622
4708
  case "${prd_path:-}" in
4623
4709
  ""|*.loki/generated-prd.md|*.loki/generated-prd.json) ;;
@@ -4630,17 +4716,47 @@ persist_prd_signature_if_present() {
4630
4716
  [ -n "$sig" ] || return 0
4631
4717
  mkdir -p "$loki_dir/state" 2>/dev/null || return 0
4632
4718
  local mode="files"; case "$sig" in git:*) mode="git" ;; esac
4719
+ # Record the content hash of the PRD file Loki just wrote so a later
4720
+ # hand-edit by the user is detectable (decide_generated_prd_action). This
4721
+ # runs AFTER the agent's own PRD writes, so Loki's updates are not mistaken
4722
+ # for user edits.
4723
+ local prd_sha; prd_sha=$(_loki_prd_file_hash "${TARGET_DIR:-.}")
4633
4724
  local tmp="$loki_dir/state/.prd-signature.json.tmp.$$"
4725
+ # Preserve generated_at when the codebase signature is unchanged so the
4726
+ # reuse disclosure ("generated on <date>") stays honest across reuse runs;
4727
+ # only stamp a new date when the PRD content actually changed (sig differs).
4634
4728
  LOKI_SIG="$sig" LOKI_SIG_MODE="$mode" LOKI_SIG_VER="$(get_version 2>/dev/null || echo unknown)" \
4729
+ LOKI_PRD_SHA="$prd_sha" LOKI_SIG_FILE="$loki_dir/state/prd-signature.json" \
4635
4730
  python3 -c "
4636
4731
  import json, os, datetime
4732
+ sig = os.environ['LOKI_SIG']
4733
+ prev = {}
4734
+ try:
4735
+ prev = json.load(open(os.environ['LOKI_SIG_FILE']))
4736
+ except Exception:
4737
+ prev = {}
4738
+ prev_at = prev.get('generated_at') if isinstance(prev, dict) else None
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):
4749
+ generated_at = prev_at
4750
+ else:
4751
+ generated_at = datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00','Z')
4637
4752
  rec = {
4638
- 'signature': os.environ['LOKI_SIG'],
4639
- 'generated_at': datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00','Z'),
4753
+ 'signature': sig,
4754
+ 'generated_at': generated_at,
4640
4755
  'prd_path': '.loki/generated-prd.md',
4756
+ 'prd_sha': os.environ.get('LOKI_PRD_SHA',''),
4641
4757
  'mode': os.environ['LOKI_SIG_MODE'],
4642
4758
  'loki_version': os.environ['LOKI_SIG_VER'],
4643
- }
4759
+ }
4644
4760
  print(json.dumps(rec))
4645
4761
  " > "$tmp" 2>/dev/null && mv -f "$tmp" "$loki_dir/state/prd-signature.json" 2>/dev/null || rm -f "$tmp" 2>/dev/null
4646
4762
  }
@@ -12014,13 +12130,35 @@ run_autonomous() {
12014
12130
  export GENERATED_PRD_ACTION
12015
12131
  local _gen_prd=".loki/generated-prd.md"
12016
12132
  [ -f ".loki/generated-prd.md" ] || _gen_prd=".loki/generated-prd.json"
12133
+ # Date the generated PRD was last written (for an honest disclosure).
12134
+ local _prd_date=""
12135
+ if [ -f ".loki/state/prd-signature.json" ]; then
12136
+ _prd_date=$(LOKI_SIG_FILE=".loki/state/prd-signature.json" python3 -c "
12137
+ import json, os
12138
+ try:
12139
+ d = json.load(open(os.environ['LOKI_SIG_FILE'])).get('generated_at','')
12140
+ print((d or '')[:10])
12141
+ except Exception:
12142
+ print('')
12143
+ " 2>/dev/null)
12144
+ fi
12017
12145
  case "$GENERATED_PRD_ACTION" in
12018
12146
  reuse)
12019
- log_info "No user PRD found. Reusing generated PRD (codebase unchanged): $_gen_prd"
12147
+ if [ -n "$_prd_date" ]; then
12148
+ log_info "Reusing the PRD last generated or updated on $_prd_date; pass --fresh-prd to regenerate ($_gen_prd)"
12149
+ else
12150
+ log_info "Reusing the generated PRD (codebase unchanged); pass --fresh-prd to regenerate ($_gen_prd)"
12151
+ fi
12152
+ prd_path="$_gen_prd"
12153
+ ;;
12154
+ user_owned)
12155
+ # The user hand-edited the generated PRD. Use it as-is (never
12156
+ # overwrite their edits); distinct disclosure from a clean reuse.
12157
+ log_info "Using your hand-edited PRD as-is ($_gen_prd); pass --fresh-prd to regenerate from the codebase"
12020
12158
  prd_path="$_gen_prd"
12021
12159
  ;;
12022
12160
  update)
12023
- log_info "No user PRD found. Codebase changed since the generated PRD; will update it incrementally: $_gen_prd"
12161
+ log_info "No user PRD found. Codebase changed since the generated PRD; will update it incrementally ($_gen_prd); pass --fresh-prd to regenerate from scratch"
12024
12162
  prd_path="$_gen_prd"
12025
12163
  ;;
12026
12164
  *)
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.32.1"
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.1
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.1";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=CD523BB6FF38AC8F64756E2164756E21
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.1'
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.1",
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",