loki-mode 7.6.2 → 7.6.4

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: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v7.6.2
6
+ # Loki Mode v7.6.4
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -381,4 +381,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
381
381
 
382
382
  ---
383
383
 
384
- **v7.6.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
384
+ **v7.6.4 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.6.2
1
+ 7.6.4
package/autonomy/loki CHANGED
@@ -8247,6 +8247,33 @@ cmd_demo() {
8247
8247
 
8248
8248
  # Quick mode - lightweight single-task execution
8249
8249
  cmd_quick() {
8250
+ # v7.6.3 B-11 fix: --help previously fell through to provider invocation
8251
+ # (task_desc="--help") which hung the CLI for 10+ seconds. Explicit guard.
8252
+ case "${1:-}" in
8253
+ --help|-h|help)
8254
+ echo -e "${BOLD}Loki Mode -- quick lightweight execution${NC}"
8255
+ echo ""
8256
+ echo "Usage: loki quick \"description of what to do\""
8257
+ echo ""
8258
+ echo "Runs Loki with a lightweight execution profile: 3 iterations max,"
8259
+ echo "no quality council, no completion gate. Best for tiny tasks."
8260
+ echo ""
8261
+ echo "Examples:"
8262
+ echo " loki quick \"add dark mode to this React app\""
8263
+ echo " loki quick \"fix the login bug in auth.js\""
8264
+ echo " loki quick \"add input validation to the signup form\""
8265
+ echo " loki quick \"write unit tests for the API endpoints\""
8266
+ echo ""
8267
+ echo "Options:"
8268
+ echo " --help, -h Show this help and exit"
8269
+ echo ""
8270
+ echo "Environment:"
8271
+ echo " LOKI_MAX_ITERATIONS Override the default 3-iteration cap"
8272
+ echo ""
8273
+ echo "See also: loki start (full RARV cycle), loki run (alias for start)"
8274
+ return 0
8275
+ ;;
8276
+ esac
8250
8277
  if [ $# -eq 0 ]; then
8251
8278
  echo -e "${RED}Error: No task description provided${NC}"
8252
8279
  echo ""
package/autonomy/run.sh CHANGED
@@ -8749,14 +8749,46 @@ auto_capture_episode() {
8749
8749
  return
8750
8750
  fi
8751
8751
 
8752
- # Collect git context: files modified in this iteration
8752
+ # v7.6.4 B-3a fix: previously `git diff --name-only HEAD` only captured
8753
+ # UNSTAGED changes -- always empty after loki's per-iteration auto-commit
8754
+ # rolled the new files into HEAD. Now diff against the iteration-start
8755
+ # SHA captured at the top of the retry loop. Falls back to HEAD~1 if the
8756
+ # start SHA env is unset (older direct callers).
8753
8757
  local files_modified=""
8754
- files_modified=$(cd "$target_dir" && git diff --name-only HEAD 2>/dev/null | head -20 | tr '\n' '|' || true)
8758
+ local _diff_base="${_LOKI_ITER_START_SHA:-}"
8759
+ if [ -z "$_diff_base" ]; then
8760
+ _diff_base=$(cd "$target_dir" && git rev-parse HEAD~1 2>/dev/null || echo "")
8761
+ fi
8762
+ if [ -n "$_diff_base" ]; then
8763
+ files_modified=$(cd "$target_dir" && git diff --name-only "$_diff_base" HEAD 2>/dev/null | head -50 | tr '\n' '|' || true)
8764
+ # Also include unstaged changes (in case auto-commit didn't run)
8765
+ local _unstaged
8766
+ _unstaged=$(cd "$target_dir" && git diff --name-only HEAD 2>/dev/null | head -20 | tr '\n' '|' || true)
8767
+ if [ -n "$_unstaged" ]; then
8768
+ files_modified="${files_modified}${_unstaged}"
8769
+ fi
8770
+ else
8771
+ # No git history yet (initial commit) -- list all tracked + untracked.
8772
+ files_modified=$(cd "$target_dir" && git ls-files --others --exclude-standard 2>/dev/null | head -50 | tr '\n' '|' || true)
8773
+ fi
8755
8774
 
8756
8775
  # Collect last git commit if any
8757
8776
  local git_commit=""
8758
8777
  git_commit=$(cd "$target_dir" && git rev-parse --short HEAD 2>/dev/null || true)
8759
8778
 
8779
+ # v7.6.4 B-3a fix: aggregate per-iteration efficiency metrics into the
8780
+ # episode so `tokens_used` / `cost_usd` / `input_tokens` / `output_tokens`
8781
+ # are no longer always 0. Source: `.loki/metrics/efficiency/iter-N.json`
8782
+ # (same source `loki kpis` reads from).
8783
+ local _iter_metrics_file="$target_dir/.loki/metrics/efficiency/iter-${iteration}.json"
8784
+ local _iter_tokens_in=0 _iter_tokens_out=0 _iter_cost=0
8785
+ if [ -f "$_iter_metrics_file" ]; then
8786
+ _iter_tokens_in=$(python3 -c "import json; d=json.load(open('$_iter_metrics_file')); print(int(d.get('input_tokens', 0) or 0))" 2>/dev/null || echo 0)
8787
+ _iter_tokens_out=$(python3 -c "import json; d=json.load(open('$_iter_metrics_file')); print(int(d.get('output_tokens', 0) or 0))" 2>/dev/null || echo 0)
8788
+ _iter_cost=$(python3 -c "import json; d=json.load(open('$_iter_metrics_file')); print(float(d.get('cost_usd', 0) or 0))" 2>/dev/null || echo 0)
8789
+ fi
8790
+ local _iter_tokens_total=$((_iter_tokens_in + _iter_tokens_out))
8791
+
8760
8792
  # Determine outcome
8761
8793
  local outcome="success"
8762
8794
  if [ "$exit_code" -ne 0 ]; then
@@ -8774,6 +8806,8 @@ auto_capture_episode() {
8774
8806
  _LOKI_DURATION="$duration" _LOKI_OUTCOME="$outcome" \
8775
8807
  _LOKI_FILES_MODIFIED="$files_modified" _LOKI_GIT_COMMIT="$git_commit" \
8776
8808
  _LOKI_EPISODE_PATH_FILE="$episode_path_file" \
8809
+ _LOKI_TOKENS_IN="$_iter_tokens_in" _LOKI_TOKENS_OUT="$_iter_tokens_out" \
8810
+ _LOKI_TOKENS_TOTAL="$_iter_tokens_total" _LOKI_COST_USD="$_iter_cost" \
8777
8811
  python3 << 'PYEOF' 2>/dev/null || true
8778
8812
  import sys
8779
8813
  import os
@@ -8811,6 +8845,32 @@ try:
8811
8845
  trace.git_commit = git_commit if git_commit else None
8812
8846
  trace.files_modified = [f for f in files_modified.split('|') if f] if files_modified else []
8813
8847
 
8848
+ # v7.6.4 B-3a + B-3b fix: hydrate tokens + cost from the iteration's
8849
+ # efficiency metrics file (same source `loki kpis` reads). Backward
8850
+ # compat: zero stays zero on missing metrics.
8851
+ try:
8852
+ trace.tokens_used = int(os.environ.get('_LOKI_TOKENS_TOTAL', '0') or 0)
8853
+ except (TypeError, ValueError):
8854
+ trace.tokens_used = 0
8855
+ # Try to set the input/output/cost fields if the schema accepts them.
8856
+ for attr, env_key, caster in (
8857
+ ('input_tokens', '_LOKI_TOKENS_IN', int),
8858
+ ('output_tokens', '_LOKI_TOKENS_OUT', int),
8859
+ ('cost_usd', '_LOKI_COST_USD', float),
8860
+ ):
8861
+ try:
8862
+ value = caster(os.environ.get(env_key, '0') or 0)
8863
+ setattr(trace, attr, value)
8864
+ except (AttributeError, TypeError, ValueError):
8865
+ pass
8866
+ # files_modified -> artifacts_produced shadow (so .artifacts_produced
8867
+ # reflects what was created if the schema has that field separately).
8868
+ try:
8869
+ if not getattr(trace, 'artifacts_produced', None):
8870
+ trace.artifacts_produced = list(trace.files_modified)
8871
+ except AttributeError:
8872
+ pass
8873
+
8814
8874
  engine.store_episode(trace)
8815
8875
 
8816
8876
  # v6.83.0: surface the on-disk episode path + importance so bash can
@@ -10711,6 +10771,12 @@ except Exception as exc:
10711
10771
 
10712
10772
  save_state $retry "running" 0
10713
10773
 
10774
+ # v7.6.4 B-3a fix: capture iteration-start git SHA so auto_capture_episode
10775
+ # can diff against this baseline (not just HEAD, which is empty after
10776
+ # loki's per-iteration auto-commit makes the new files HEAD).
10777
+ _LOKI_ITER_START_SHA=$(cd "${TARGET_DIR:-.}" && git rev-parse HEAD 2>/dev/null || echo "")
10778
+ export _LOKI_ITER_START_SHA
10779
+
10714
10780
  # Run AI provider with live output
10715
10781
  local start_time=$(date +%s)
10716
10782
  local log_file=".loki/logs/autonomy-$(date +%Y%m%d).log"
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.6.2"
10
+ __version__ = "7.6.4"
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/). Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v7.6.2
5
+ **Version:** v7.6.4
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var _7=Object.defineProperty;var I7=(K)=>K;function P7(K,$){this[K]=I7.bind(null,$)}var b=(K,$)=>{for(var z in $)_7(K,z,{get:$[z],enumerable:!0,configurable:!0,set:P7.bind($,z)})};var L=(K,$)=>()=>(K&&($=K(K=0)),$);var V1=import.meta.require;var e1={};b(e1,{lokiDir:()=>P,homeLokiDir:()=>k1,findRepoRootForVersion:()=>N1,REPO_ROOT:()=>u});import{resolve as f,dirname as S1}from"path";import{fileURLToPath as L7}from"url";import{existsSync as J1}from"fs";import{homedir as R7}from"os";function E7(){let K=i1;for(let $=0;$<6;$++){if(J1(f(K,"VERSION"))&&J1(f(K,"autonomy/run.sh")))return K;let z=S1(K);if(z===K)break;K=z}return f(i1,"..","..","..")}function N1(K){let $=K;for(let z=0;z<6;z++){if(J1(f($,"VERSION"))&&J1(f($,"autonomy/run.sh")))return $;let Q=S1($);if(Q===$)break;$=Q}return f(K,"..","..","..")}function P(){return process.env.LOKI_DIR??f(process.cwd(),".loki")}function k1(){return f(R7(),".loki")}var i1,u;var y=L(()=>{i1=S1(L7(import.meta.url));u=E7()});import{readFileSync as x7}from"fs";import{resolve as F7,dirname as w7}from"path";import{fileURLToPath as S7}from"url";function G1(){if(o!==null)return o;let K="7.6.2";if(typeof K==="string"&&K.length>0)return o=K,o;try{let $=w7(S7(import.meta.url)),z=N1($);o=x7(F7(z,"VERSION"),"utf-8").trim()}catch{o="unknown"}return o}var o=null;var D1=L(()=>{y()});var $0={};b($0,{runOrThrow:()=>N7,run:()=>w,commandVersion:()=>D7,commandExists:()=>h,ShellError:()=>C1});async function w(K,$={}){let z=Bun.spawn({cmd:[...K],stdout:"pipe",stderr:"pipe",env:$.env?{...process.env,...$.env}:process.env,cwd:$.cwd}),Q,X;if($.timeoutMs&&$.timeoutMs>0)Q=setTimeout(()=>{try{z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{z.kill("SIGKILL")}catch{}},2000)},$.timeoutMs);try{let[H,Z,q]=await Promise.all([new Response(z.stdout).text(),new Response(z.stderr).text(),z.exited]);return{stdout:H,stderr:Z,exitCode:q}}finally{if(Q)clearTimeout(Q);if(X)clearTimeout(X)}}async function N7(K,$={}){let z=await w(K,$);if(z.exitCode!==0)throw new C1(`command failed (${z.exitCode}): ${K.join(" ")}`,z.exitCode,z.stdout,z.stderr);return z}async function h(K){let $=k7(K),z=await w(["sh","-c",`command -v ${$}`],{timeoutMs:5000});if(z.exitCode===0)return z.stdout.trim()||null;return null}function k7(K){if(!/^[A-Za-z0-9._/-]+$/.test(K))throw Error(`refused to shell-escape suspect token: ${K}`);return K}async function D7(K,$="--version"){if(!await h(K))return null;let Q=await w([K,$],{timeoutMs:5000});if(Q.exitCode!==0)return null;return((Q.stdout||Q.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var C1;var p=L(()=>{C1=class C1 extends Error{message;exitCode;stdout;stderr;constructor(K,$,z,Q){super(K);this.message=K;this.exitCode=$;this.stdout=z;this.stderr=Q;this.name="ShellError"}}});function c(K){return C7?"":K}var C7,R,D,E,O6,O,k,x,W;var n=L(()=>{C7=(process.env.NO_COLOR??"").length>0;R=c("\x1B[0;31m"),D=c("\x1B[0;32m"),E=c("\x1B[1;33m"),O6=c("\x1B[0;34m"),O=c("\x1B[0;36m"),k=c("\x1B[1m"),x=c("\x1B[2m"),W=c("\x1B[0m")});import{existsSync as c7}from"fs";async function r(){if(z1!==void 0)return z1;let K="/opt/homebrew/bin/python3.12";if(c7(K))return z1=K,K;let $=await h("python3.12");if($)return z1=$,$;let z=await h("python3");return z1=z,z}async function a(K,$={}){let z=await r();if(!z)return{stdout:"",stderr:"python3 not found",exitCode:127};return w([z,"-c",K],$)}var z1;var Q1=L(()=>{p()});var G0={};b(G0,{runStatus:()=>z5});import{existsSync as S,readFileSync as Z1,readdirSync as H0,statSync as W0}from"fs";import{resolve as F,basename as a7}from"path";async function r7(){if(await h("jq"))return!0;return process.stdout.write(`${R}Error: jq is required but not installed.${W}
2
+ var _7=Object.defineProperty;var I7=(K)=>K;function P7(K,$){this[K]=I7.bind(null,$)}var b=(K,$)=>{for(var z in $)_7(K,z,{get:$[z],enumerable:!0,configurable:!0,set:P7.bind($,z)})};var L=(K,$)=>()=>(K&&($=K(K=0)),$);var V1=import.meta.require;var e1={};b(e1,{lokiDir:()=>P,homeLokiDir:()=>k1,findRepoRootForVersion:()=>N1,REPO_ROOT:()=>u});import{resolve as f,dirname as S1}from"path";import{fileURLToPath as L7}from"url";import{existsSync as J1}from"fs";import{homedir as R7}from"os";function E7(){let K=i1;for(let $=0;$<6;$++){if(J1(f(K,"VERSION"))&&J1(f(K,"autonomy/run.sh")))return K;let z=S1(K);if(z===K)break;K=z}return f(i1,"..","..","..")}function N1(K){let $=K;for(let z=0;z<6;z++){if(J1(f($,"VERSION"))&&J1(f($,"autonomy/run.sh")))return $;let Q=S1($);if(Q===$)break;$=Q}return f(K,"..","..","..")}function P(){return process.env.LOKI_DIR??f(process.cwd(),".loki")}function k1(){return f(R7(),".loki")}var i1,u;var y=L(()=>{i1=S1(L7(import.meta.url));u=E7()});import{readFileSync as x7}from"fs";import{resolve as F7,dirname as w7}from"path";import{fileURLToPath as S7}from"url";function G1(){if(o!==null)return o;let K="7.6.4";if(typeof K==="string"&&K.length>0)return o=K,o;try{let $=w7(S7(import.meta.url)),z=N1($);o=x7(F7(z,"VERSION"),"utf-8").trim()}catch{o="unknown"}return o}var o=null;var D1=L(()=>{y()});var $0={};b($0,{runOrThrow:()=>N7,run:()=>w,commandVersion:()=>D7,commandExists:()=>h,ShellError:()=>C1});async function w(K,$={}){let z=Bun.spawn({cmd:[...K],stdout:"pipe",stderr:"pipe",env:$.env?{...process.env,...$.env}:process.env,cwd:$.cwd}),Q,X;if($.timeoutMs&&$.timeoutMs>0)Q=setTimeout(()=>{try{z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{z.kill("SIGKILL")}catch{}},2000)},$.timeoutMs);try{let[H,Z,q]=await Promise.all([new Response(z.stdout).text(),new Response(z.stderr).text(),z.exited]);return{stdout:H,stderr:Z,exitCode:q}}finally{if(Q)clearTimeout(Q);if(X)clearTimeout(X)}}async function N7(K,$={}){let z=await w(K,$);if(z.exitCode!==0)throw new C1(`command failed (${z.exitCode}): ${K.join(" ")}`,z.exitCode,z.stdout,z.stderr);return z}async function h(K){let $=k7(K),z=await w(["sh","-c",`command -v ${$}`],{timeoutMs:5000});if(z.exitCode===0)return z.stdout.trim()||null;return null}function k7(K){if(!/^[A-Za-z0-9._/-]+$/.test(K))throw Error(`refused to shell-escape suspect token: ${K}`);return K}async function D7(K,$="--version"){if(!await h(K))return null;let Q=await w([K,$],{timeoutMs:5000});if(Q.exitCode!==0)return null;return((Q.stdout||Q.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var C1;var p=L(()=>{C1=class C1 extends Error{message;exitCode;stdout;stderr;constructor(K,$,z,Q){super(K);this.message=K;this.exitCode=$;this.stdout=z;this.stderr=Q;this.name="ShellError"}}});function c(K){return C7?"":K}var C7,R,D,E,O6,O,k,x,W;var n=L(()=>{C7=(process.env.NO_COLOR??"").length>0;R=c("\x1B[0;31m"),D=c("\x1B[0;32m"),E=c("\x1B[1;33m"),O6=c("\x1B[0;34m"),O=c("\x1B[0;36m"),k=c("\x1B[1m"),x=c("\x1B[2m"),W=c("\x1B[0m")});import{existsSync as c7}from"fs";async function r(){if(z1!==void 0)return z1;let K="/opt/homebrew/bin/python3.12";if(c7(K))return z1=K,K;let $=await h("python3.12");if($)return z1=$,$;let z=await h("python3");return z1=z,z}async function a(K,$={}){let z=await r();if(!z)return{stdout:"",stderr:"python3 not found",exitCode:127};return w([z,"-c",K],$)}var z1;var Q1=L(()=>{p()});var G0={};b(G0,{runStatus:()=>z5});import{existsSync as S,readFileSync as Z1,readdirSync as H0,statSync as W0}from"fs";import{resolve as F,basename as a7}from"path";async function r7(){if(await h("jq"))return!0;return process.stdout.write(`${R}Error: jq is required but not installed.${W}
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)
@@ -534,4 +534,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
534
534
  `),2}default:return process.stderr.write(`Unknown command: ${$}
535
535
  `),process.stderr.write(j7),2}}process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var z6=await $6(Bun.argv.slice(2));process.exit(z6);
536
536
 
537
- //# debugId=9A266FF314C7FA3664756E2164756E21
537
+ //# debugId=75643AA67185D81E64756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.6.2'
60
+ __version__ = '7.6.4'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "7.6.2",
3
+ "version": "7.6.4",
4
4
  "description": "Loki Mode by Autonomi. Multi-agent autonomous SDLC framework. Spec to deployed app: PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief. 4 AI providers (Claude Code, OpenAI Codex, Cline, Aider). 11 quality gates.",
5
5
  "keywords": [
6
6
  "agent",