loki-mode 9.47.0 → 9.48.2

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.
@@ -0,0 +1,105 @@
1
+ name: 'Loki Issue to PR'
2
+ description: 'Hand Loki Mode a GitHub issue; it opens a pull request with a verifiable evidence receipt.'
3
+ author: 'asklokesh'
4
+
5
+ branding:
6
+ icon: 'git-pull-request'
7
+ color: 'purple'
8
+
9
+ inputs:
10
+ issue:
11
+ description: 'Issue reference. Defaults to the issue that triggered this workflow.'
12
+ required: false
13
+ default: ''
14
+ provider:
15
+ description: 'AI provider: claude, codex, cline, aider, or opencode'
16
+ required: false
17
+ default: 'claude'
18
+ budget_limit:
19
+ description: 'Max spend in USD before the run pauses. Breaching PAUSES; it never discards work.'
20
+ required: false
21
+ default: '10.00'
22
+ max_iterations:
23
+ description: 'Hard ceiling on RARV iterations.'
24
+ required: false
25
+ default: '20'
26
+ version:
27
+ description: 'loki-mode version to install. Pin this for reproducible runs.'
28
+ required: false
29
+ default: 'latest'
30
+
31
+ outputs:
32
+ pr_url:
33
+ description: 'URL of the pull request Loki opened, empty if none was opened.'
34
+ value: ${{ steps.run.outputs.pr_url }}
35
+
36
+ runs:
37
+ using: 'composite'
38
+ steps:
39
+ - name: Install Loki Mode
40
+ shell: bash
41
+ run: npm install -g loki-mode@${{ inputs.version }}
42
+
43
+ - name: Resolve the issue reference
44
+ id: ref
45
+ shell: bash
46
+ run: |
47
+ # Prefer the explicit input; otherwise use the triggering issue. Failing
48
+ # CLOSED here is deliberate: a run with no issue would otherwise start
49
+ # from the repository's own state and produce an unrelated PR.
50
+ REF="${{ inputs.issue }}"
51
+ if [ -z "$REF" ]; then
52
+ NUM="${{ github.event.issue.number }}"
53
+ if [ -z "$NUM" ]; then
54
+ echo "::error::No issue to work on. Pass the 'issue' input, or trigger this action from an issue event."
55
+ exit 1
56
+ fi
57
+ REF="${{ github.repository }}#${NUM}"
58
+ fi
59
+ echo "ref=$REF" >> "$GITHUB_OUTPUT"
60
+ echo "Resolved issue reference: $REF"
61
+
62
+ - name: Resolve the issue to a pull request
63
+ id: run
64
+ shell: bash
65
+ env:
66
+ # gh is what opens the PR. Without a token the run still builds and the
67
+ # PR step no-ops, so this is not fatal, but it IS the common misconfig.
68
+ GH_TOKEN: ${{ github.token }}
69
+ LOKI_PROVIDER: ${{ inputs.provider }}
70
+ LOKI_BUDGET_LIMIT: ${{ inputs.budget_limit }}
71
+ LOKI_MAX_ITERATIONS: ${{ inputs.max_iterations }}
72
+ # The PR is the deliverable, so the PR path is on. Default-on since
73
+ # v9.43.0; set explicitly here so the action does not depend on the
74
+ # installed version's default.
75
+ LOKI_DELEGATE_PR: '1'
76
+ run: |
77
+ set -uo pipefail
78
+
79
+ # git identity: gh pr create needs commits to have an author.
80
+ git config user.name "${GITHUB_ACTOR:-loki-mode}"
81
+ git config user.email "${GITHUB_ACTOR:-loki-mode}@users.noreply.github.com"
82
+
83
+ loki start "${{ steps.ref.outputs.ref }}" || RC=$?
84
+ RC="${RC:-0}"
85
+
86
+ # Report the PR if one was opened. Read it from the state Loki writes
87
+ # rather than re-deriving it, so this reports what ACTUALLY happened.
88
+ PR_URL=""
89
+ if [ -f .loki/state/pr-url.txt ]; then
90
+ PR_URL="$(cat .loki/state/pr-url.txt 2>/dev/null || true)"
91
+ fi
92
+ echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
93
+
94
+ if [ -n "$PR_URL" ]; then
95
+ echo "Pull request: $PR_URL"
96
+ else
97
+ echo "::warning::No pull request was opened. Check that gh is authenticated, the branch is not a default branch, and the run produced changes."
98
+ fi
99
+
100
+ # Surface the receipt path so a reviewer can check the evidence.
101
+ if [ -d .loki/proofs ]; then
102
+ echo "Evidence receipts under .loki/proofs/"
103
+ fi
104
+
105
+ exit "$RC"
@@ -0,0 +1,87 @@
1
+ # Hand Loki an issue, get a pull request.
2
+ #
3
+ # Label an issue `loki` (or comment `/loki`) and this opens a PR with an
4
+ # evidence receipt. Copy this file into .github/workflows/ of any repository.
5
+ #
6
+ # REQUIRED: set ANTHROPIC_API_KEY in repository secrets. Without it the run
7
+ # cannot call a model and fails fast rather than burning minutes.
8
+ name: Loki - Issue to PR
9
+
10
+ on:
11
+ issues:
12
+ types: [labeled]
13
+ issue_comment:
14
+ types: [created]
15
+ workflow_dispatch:
16
+ inputs:
17
+ issue:
18
+ description: 'Issue number to resolve'
19
+ required: true
20
+
21
+ # Serialize per issue. Two agents racing on one issue would open two PRs from
22
+ # two branches and neither would see the other's work.
23
+ concurrency:
24
+ group: loki-issue-${{ github.event.issue.number || inputs.issue }}
25
+ cancel-in-progress: false
26
+
27
+ permissions:
28
+ contents: write # push the agent branch
29
+ pull-requests: write # open the PR
30
+ issues: read
31
+
32
+ jobs:
33
+ resolve:
34
+ # Gate on an explicit signal so the agent never fires on unrelated activity.
35
+ if: >-
36
+ (github.event_name == 'issues' && github.event.label.name == 'loki') ||
37
+ (github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '/loki')) ||
38
+ github.event_name == 'workflow_dispatch'
39
+ runs-on: ubuntu-latest
40
+ timeout-minutes: 45
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+ with:
44
+ fetch-depth: 0 # the agent branches and diffs; a shallow clone breaks both
45
+
46
+ - uses: actions/setup-node@v4
47
+ with:
48
+ node-version: '20'
49
+
50
+ - name: Fail fast without a model key
51
+ env:
52
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
53
+ run: |
54
+ if [ -z "${ANTHROPIC_API_KEY:-}" ]; then
55
+ echo "::error::ANTHROPIC_API_KEY is not set in repository secrets. Add it under Settings > Secrets and variables > Actions."
56
+ exit 1
57
+ fi
58
+
59
+ - name: Resolve the issue
60
+ id: loki
61
+ # Pinned to a TAG, not @main. A moving ref would hand every user
62
+ # whatever main holds at the moment their issue is labeled, including a
63
+ # half-landed release. Bump this line deliberately.
64
+ uses: asklokesh/loki-mode/.github/actions/issue-to-pr@v9.48.2
65
+ env:
66
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
67
+ with:
68
+ issue: ${{ github.event.issue.number || inputs.issue }}
69
+ budget_limit: '10.00'
70
+
71
+ - name: Report back on the issue
72
+ if: always() && steps.loki.outputs.pr_url != ''
73
+ env:
74
+ GH_TOKEN: ${{ github.token }}
75
+ PR_URL: ${{ steps.loki.outputs.pr_url }}
76
+ ISSUE: ${{ github.event.issue.number || inputs.issue }}
77
+ run: |
78
+ # Build the body in a file: a blank line inside an inline YAML string
79
+ # terminates the block scalar and breaks the workflow parse.
80
+ {
81
+ echo "Loki opened a pull request: $PR_URL"
82
+ echo ""
83
+ echo "The PR body carries an evidence receipt stating what was verified,"
84
+ echo "what was skipped, and what is degraded. Check it rather than taking"
85
+ echo "the result on trust."
86
+ } > /tmp/loki-issue-comment.md
87
+ gh issue comment "$ISSUE" --body-file /tmp/loki-issue-comment.md || true
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.47.0
6
+ # Loki Mode v9.48.2
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.47.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
473
+ **v9.48.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 9.47.0
1
+ 9.48.2
package/autonomy/loki CHANGED
@@ -4502,10 +4502,27 @@ cmd_steer() {
4502
4502
  # duplicating them here would contradict it.
4503
4503
  #
4504
4504
  # Read-only: reads state, writes nothing, spends nothing.
4505
+ # Print the "you can pick this up" hint for a run that STOPPED WITHOUT A VERDICT.
4506
+ #
4507
+ # THE DEFECT this widening fixes, reproduced: cmd_next maps
4508
+ # max_iterations_reached and budget_exceeded to `loki resume`, but this helper
4509
+ # accepted ONLY "interrupted", so cmd_resume fell through to "No session to
4510
+ # resume. Start a session with: loki start" -- rc=0. The two commands whose
4511
+ # whole job is "do the right next thing" contradicted each other on every run
4512
+ # that hit a limit.
4513
+ #
4514
+ # WHAT MUST NOT BE ADDED HERE: council_approved, council_force_approved and
4515
+ # completion_promise_fulfilled. Those carry a VERDICT and route to `loki ship`.
4516
+ # Accepting them would send an approved build back into the loop.
4517
+ # failed / policy_blocked / force_stopped route to `loki why` and stay out too.
4518
+ # The set below is exactly "stopped on a limit, no verdict reached".
4505
4519
  _loki_print_interrupted_resume_hint() {
4506
4520
  local status
4507
4521
  status="$(_loki_resolve_run_status 2>/dev/null)" || return 1
4508
- [ "$status" = "interrupted" ] || return 1
4522
+ case "$status" in
4523
+ interrupted|max_iterations_reached|budget_exceeded) ;;
4524
+ *) return 1 ;;
4525
+ esac
4509
4526
 
4510
4527
  local state_file="$LOKI_DIR/autonomy-state.json"
4511
4528
  if [ -n "${LOKI_SESSION_ID:-}" ] && [ -f "$LOKI_DIR/sessions/${LOKI_SESSION_ID}/autonomy-state.json" ]; then
@@ -4542,12 +4559,52 @@ print('\t'.join([
4542
4559
  resume_cmd="loki start \"$prd_path\""
4543
4560
  fi
4544
4561
 
4545
- echo -e "${YELLOW}Interrupted run found.${NC} Stopped at iteration ${BOLD}${iteration}${NC} (last activity: ${last_run})."
4546
- echo "Saved progress is intact. Resume it with:"
4547
- echo ""
4548
- echo -e " ${BOLD}${resume_cmd}${NC}"
4562
+ # A capped run needs DIFFERENT advice than an interrupted one: re-running it
4563
+ # unchanged hits the very same limit on the next iteration. cmd_next's
4564
+ # action_text already says to raise the cap first; saying less here would
4565
+ # move the contradiction rather than close it.
4566
+ case "$status" in
4567
+ max_iterations_reached)
4568
+ # Deliberately does NOT print the iteration it reached.
4569
+ # `load_state` resets ITERATION_COUNT=0 for this terminal status
4570
+ # (autonomy/run.sh, the failure-terminals case arm), so a fresh
4571
+ # `loki start` is a NEW session from iteration 0. Advertising the
4572
+ # old number would promise a continuation the runtime will not
4573
+ # honour -- the exact false claim tests/test-resume-discoverability.sh
4574
+ # guards, and it caught this in CI.
4575
+ echo -e "${YELLOW}Iteration limit reached.${NC} (last activity: ${last_run})."
4576
+ echo "The committed work is intact, but this starts a NEW session from"
4577
+ echo "iteration 0 -- it does not continue the old count."
4578
+ echo "Raise the cap (or narrow the spec), then run:"
4579
+ echo ""
4580
+ echo -e " ${BOLD}LOKI_MAX_ITERATIONS=<higher> ${resume_cmd}${NC}"
4581
+ ;;
4582
+ budget_exceeded)
4583
+ # Same reasoning as max_iterations_reached: this terminal status is
4584
+ # in load_state's reset list, so the prior iteration count is gone.
4585
+ echo -e "${YELLOW}Budget limit reached.${NC} (last activity: ${last_run})."
4586
+ echo "The committed work is intact, but this starts a NEW session from"
4587
+ echo "iteration 0 -- it does not continue the old count."
4588
+ echo "Raise the budget (or narrow the spec), then run:"
4589
+ echo ""
4590
+ echo -e " ${BOLD}LOKI_BUDGET_LIMIT=<higher> ${resume_cmd}${NC}"
4591
+ ;;
4592
+ *)
4593
+ echo -e "${YELLOW}Interrupted run found.${NC} Stopped at iteration ${BOLD}${iteration}${NC} (last activity: ${last_run})."
4594
+ echo "Saved progress is intact. Resume it with:"
4595
+ echo ""
4596
+ echo -e " ${BOLD}${resume_cmd}${NC}"
4597
+ ;;
4598
+ esac
4549
4599
  echo ""
4550
- echo -e "${DIM}It picks up from iteration ${iteration}; verification re-runs, so nothing inherits a stale PASS.${NC}"
4600
+ # Only an INTERRUPTED run actually resumes its iteration count. The capped
4601
+ # terminals above are reset to 0 by load_state, so the pick-up sentence must
4602
+ # not be printed for them.
4603
+ if [ "$status" = "interrupted" ]; then
4604
+ echo -e "${DIM}It picks up from iteration ${iteration}; verification re-runs, so nothing inherits a stale PASS.${NC}"
4605
+ else
4606
+ echo -e "${DIM}Verification re-runs from scratch, so nothing inherits a stale PASS.${NC}"
4607
+ fi
4551
4608
  return 0
4552
4609
  }
4553
4610
 
@@ -17301,12 +17358,40 @@ if manifests:
17301
17358
  echo -e "${RED}Error: No repositories found matching: ${multi_repo}${NC}"
17302
17359
  return 1
17303
17360
  fi
17304
- echo -e "${BOLD}Multi-repo migration${NC} ($repo_count repositories)"
17361
+ # HONESTY: this flag DISCOVERS repositories; it does not migrate them.
17362
+ # `repos[]` is populated here and never referenced again -- everything
17363
+ # below operates on the single "$codebase_path". The old message said
17364
+ # "Multi-repo migration (3 repositories)" and listed all three, then
17365
+ # created one migration scoped to the first. A user reading that
17366
+ # believed three repos were migrated when one was.
17367
+ #
17368
+ # Orchestrating a real migration across repositories is a separate piece
17369
+ # of work (dependency order, per-repo gates, per-repo receipts). Until
17370
+ # that exists, this says exactly what it does and hands the user the
17371
+ # commands that cover the rest, rather than implying coverage it has not
17372
+ # delivered.
17373
+ local _mr_others=$((repo_count - 1))
17374
+ echo -e "${BOLD}Repositories found${NC} ($repo_count)"
17305
17375
  echo "---"
17306
17376
  for repo in "${repos[@]}"; do
17307
- echo " - $repo"
17377
+ if [ "$repo" = "$codebase_path" ]; then
17378
+ echo " - $repo (migrating now)"
17379
+ else
17380
+ echo " - $repo"
17381
+ fi
17308
17382
  done
17309
17383
  echo ""
17384
+ if [ "$_mr_others" -gt 0 ]; then
17385
+ echo -e "${YELLOW}--multi-repo discovers repositories; it does not migrate them together.${NC}"
17386
+ echo "This run migrates only: $codebase_path"
17387
+ echo ""
17388
+ echo "To cover the others, run each one:"
17389
+ for repo in "${repos[@]}"; do
17390
+ [ "$repo" = "$codebase_path" ] && continue
17391
+ echo " loki modernize migrate \"$repo\" --target \"$target\""
17392
+ done
17393
+ echo ""
17394
+ fi
17310
17395
  fi
17311
17396
 
17312
17397
  local migration_dir
package/autonomy/run.sh CHANGED
@@ -5223,6 +5223,24 @@ except Exception:
5223
5223
  # is already true we DEFER to that path and do nothing here, so a user who set
5224
5224
  # both knobs never gets a double PR.
5225
5225
  #===============================================================================
5226
+ # Write the PR url where an OUT-OF-PROCESS caller can read it.
5227
+ #
5228
+ # _LOKI_DELEGATE_PR_URL is exported, which reaches children but NOT a sibling
5229
+ # step. A GitHub composite action runs `loki start` in one step and reports the
5230
+ # result in the next, so an exported variable is invisible to it and the action
5231
+ # would have to re-derive the url and could get it wrong. Persisting it means a
5232
+ # caller reports what ACTUALLY happened.
5233
+ #
5234
+ # Best-effort by construction: a failure here must never affect a run whose PR
5235
+ # was already opened successfully.
5236
+ _loki_persist_pr_url() {
5237
+ local _u="${1:-}"
5238
+ [ -n "$_u" ] || return 0
5239
+ mkdir -p ".loki/state" 2>/dev/null || return 0
5240
+ printf '%s\n' "$_u" > ".loki/state/pr-url.txt" 2>/dev/null || true
5241
+ return 0
5242
+ }
5243
+
5226
5244
  on_run_complete() {
5227
5245
  # DEFAULT ON as of v9.43.0.
5228
5246
  #
@@ -5290,6 +5308,7 @@ on_run_complete() {
5290
5308
  if [ -n "$existing_pr" ]; then
5291
5309
  _LOKI_DELEGATE_PR_URL="$existing_pr"
5292
5310
  export _LOKI_DELEGATE_PR_URL
5311
+ _loki_persist_pr_url "$existing_pr"
5293
5312
  log_info "LOKI_DELEGATE_PR=1: PR already exists for branch '$branch': $existing_pr (skipping create)."
5294
5313
  return 0
5295
5314
  fi
@@ -5321,6 +5340,7 @@ ${_del_receipt}"
5321
5340
  # Export so build_completion_summary folds the url into the summary.
5322
5341
  _LOKI_DELEGATE_PR_URL="$pr_url"
5323
5342
  export _LOKI_DELEGATE_PR_URL
5343
+ _loki_persist_pr_url "$pr_url"
5324
5344
  log_info "Pull request opened: $pr_url"
5325
5345
  else
5326
5346
  log_warn "LOKI_DELEGATE_PR=1: gh pr create did not return a URL (a PR may already exist for this branch)."
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "9.47.0"
10
+ __version__ = "9.48.2"
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.47.0
5
+ **Version:** v9.48.2
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.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}
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.48.2";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=F841E90B748FCDE3BB7AD1804EE3AED0
1340
+ //# debugId=5ED131B9EB93A5047CFCB3C8625CAE63
package/mcp/__init__.py CHANGED
@@ -75,4 +75,4 @@ try:
75
75
  except ImportError:
76
76
  __all__ = ['mcp']
77
77
 
78
- __version__ = '9.47.0'
78
+ __version__ = '9.48.2'
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.47.0",
4
+ "version": "9.48.2",
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",
@@ -69,6 +69,8 @@
69
69
  "tools/",
70
70
  "plugins/",
71
71
  ".claude-plugin/marketplace.json",
72
+ ".github/workflows/loki-issue-to-pr.yml",
73
+ ".github/actions/issue-to-pr/",
72
74
  "claude/hooks/",
73
75
  "autonomy/",
74
76
  "providers/",
@@ -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.47.0",
5
+ "version": "9.48.2",
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",