loki-mode 7.7.19 → 7.7.20

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.7.19
6
+ # Loki Mode v7.7.20
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.7.19 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
384
+ **v7.7.20 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.7.19
1
+ 7.7.20
package/autonomy/loki CHANGED
@@ -377,6 +377,30 @@ try:
377
377
  }
378
378
  output['memories'].append(memory_item)
379
379
 
380
+ # v7.7.20: wake the previously-dead cross-project + knowledge-graph
381
+ # code. When local episodic retrieval is sparse (< 5 hits), augment
382
+ # with patterns mined from OTHER projects' .loki/memory/ stores. This
383
+ # is the cross-project learning the diagnosis flagged as dead code
384
+ # (zero call sites). Best-effort: any failure is swallowed and the
385
+ # local results stand alone. Opt out with LOKI_SKIP_CROSS_PROJECT=true.
386
+ output['cross_project'] = []
387
+ if os.environ.get('LOKI_SKIP_CROSS_PROJECT', '').lower() not in ('true', '1', 'yes'):
388
+ try:
389
+ from memory.knowledge_graph import OrganizationKnowledgeGraph
390
+ kg = OrganizationKnowledgeGraph()
391
+ query_text = context.get('goal', '') or 'general'
392
+ patterns = kg.query_patterns(query_text, max_results=3)
393
+ for p in patterns:
394
+ output['cross_project'].append({
395
+ 'source': 'knowledge-graph',
396
+ 'pattern': str(p.get('description', p.get('pattern', p)))[:200]
397
+ if isinstance(p, dict) else str(p)[:200],
398
+ 'score': round(float(p.get('score', 0.0)), 3) if isinstance(p, dict) else 0.0,
399
+ })
400
+ except Exception:
401
+ pass # cross-project augmentation is best-effort
402
+ output['cross_project_count'] = len(output['cross_project'])
403
+
380
404
  print(json.dumps(output, indent=2))
381
405
  except ImportError:
382
406
  print('{"error": "Memory module not available", "memory_count": 0, "memories": []}')
@@ -15028,6 +15052,14 @@ except Exception as e:
15028
15052
  echo " loki memory vectors"
15029
15053
  echo " loki memory vectors rebuild"
15030
15054
  echo ""
15055
+ echo " loki memory ingest --from-claude-transcript <path> # v7.7.18"
15056
+ echo " loki memory ingest --from-stdin # v7.7.18"
15057
+ echo " loki memory crossproject --for 'build api' # v7.7.20"
15058
+ echo " loki memory graph --export graph.json # v7.7.20"
15059
+ echo " loki memory graph rebuild # v7.7.20"
15060
+ echo " loki memory enable-hook # v7.7.20"
15061
+ echo " loki memory disable-hook # v7.7.20"
15062
+ echo ""
15031
15063
  echo " loki memory namespace detect"
15032
15064
  echo " loki memory namespace list"
15033
15065
  echo " loki memory namespace create my-project"
@@ -15106,15 +15138,210 @@ print(json.dumps({'episode_path': path}))
15106
15138
  fi
15107
15139
  ;;
15108
15140
 
15109
- # NOTE (v7.7.18 council fix Opus 2): hook installer (enable-hook /
15110
- # disable-hook) DEFERRED to v7.7.19. Claude Code SessionEnd schema
15111
- # requires {matcher, hooks:[{type,command}]} nested format -- not
15112
- # the {id, command} format originally drafted. Also only fires on
15113
- # /clear (not normal exits), and payload is JSON on stdin (not the
15114
- # $CLAUDE_TRANSCRIPT_PATH env var). The sample hook script at
15115
- # claude/hooks/loki-session-end.sh ships for users who want to
15116
- # install manually; the automated installer comes after we verify
15117
- # the exact schema empirically against a real Claude Code install.
15141
+ crossproject)
15142
+ # v7.7.20: surface cross-project knowledge-graph patterns
15143
+ # (wakes the previously-dead memory/knowledge_graph.py).
15144
+ # Usage: loki memory crossproject [--for <goal text>]
15145
+ shift # drop "crossproject"
15146
+ local _cp_query="general"
15147
+ while [ $# -gt 0 ]; do
15148
+ case "$1" in
15149
+ --for) _cp_query="${2:-general}"; if [ $# -ge 2 ]; then shift 2; else shift; fi ;;
15150
+ -h|--help) echo "Usage: loki memory crossproject [--for <goal>]"; return 0 ;;
15151
+ *) shift ;;
15152
+ esac
15153
+ done
15154
+ PYTHONPATH="${SKILL_DIR:-$(pwd)}" python3 -c "
15155
+ import sys, json
15156
+ try:
15157
+ from memory.knowledge_graph import OrganizationKnowledgeGraph
15158
+ kg = OrganizationKnowledgeGraph()
15159
+ patterns = kg.query_patterns(sys.argv[1], max_results=10)
15160
+ if not patterns:
15161
+ print('No cross-project patterns found. Run sessions in multiple projects to build the graph.')
15162
+ else:
15163
+ for i, p in enumerate(patterns, 1):
15164
+ desc = p.get('description', p.get('pattern', p)) if isinstance(p, dict) else p
15165
+ print(f'{i}. {str(desc)[:160]}')
15166
+ except ImportError:
15167
+ print('knowledge_graph module not available')
15168
+ except Exception as e:
15169
+ print(f'Error: {e}')
15170
+ " "$_cp_query"
15171
+ ;;
15172
+
15173
+ graph)
15174
+ # v7.7.20: export OR rebuild the cross-project knowledge graph.
15175
+ # Usage: loki memory graph [--export <path>]
15176
+ # loki memory graph rebuild # populate from semantic patterns
15177
+ shift # drop "graph"
15178
+ # v7.7.20 council fix (Opus 1): the read side (query_patterns)
15179
+ # was woken but the WRITE side had no caller, so the graph
15180
+ # stayed empty forever. `graph rebuild` mines .loki/memory/
15181
+ # semantic/*.json across discovered projects and persists them
15182
+ # to the org knowledge graph -- the population path that makes
15183
+ # crossproject + load_memory_context augmentation non-inert.
15184
+ if [ "${1:-}" = "rebuild" ]; then
15185
+ shift
15186
+ PYTHONPATH="${SKILL_DIR:-$(pwd)}" python3 -c "
15187
+ import sys, os
15188
+ try:
15189
+ from memory.knowledge_graph import OrganizationKnowledgeGraph
15190
+ from memory.cross_project import CrossProjectIndex
15191
+ cpi = CrossProjectIndex()
15192
+ cpi.discover_projects()
15193
+ project_dirs = cpi.get_project_dirs()
15194
+ kg = OrganizationKnowledgeGraph()
15195
+ # v7.7.20 council fix (Opus 1): save_patterns appends, so a naive
15196
+ # rebuild would accumulate duplicates across runs. Dedup the UNION of
15197
+ # existing + freshly-extracted, then truncate-rewrite so rebuild is
15198
+ # idempotent.
15199
+ existing = kg.load_patterns(limit=100000)
15200
+ fresh = kg.extract_patterns(project_dirs)
15201
+ merged = kg.deduplicate_patterns(list(existing) + list(fresh))
15202
+ # Truncate then write the deduped union (save_patterns appends).
15203
+ try:
15204
+ if os.path.exists(kg.patterns_file):
15205
+ open(kg.patterns_file, 'w').close()
15206
+ except OSError:
15207
+ pass
15208
+ if merged:
15209
+ kg.save_patterns(merged)
15210
+ print(f'Rebuilt knowledge graph: {len(merged)} unique patterns from {len(project_dirs)} project(s)')
15211
+ else:
15212
+ print(f'No semantic patterns found across {len(project_dirs)} project(s). Run sessions + consolidation first.')
15213
+ except ImportError as e:
15214
+ print(f'knowledge_graph/cross_project module not available: {e}')
15215
+ except Exception as e:
15216
+ print(f'Error: {e}')
15217
+ "
15218
+ return 0
15219
+ fi
15220
+ local _graph_export=""
15221
+ while [ $# -gt 0 ]; do
15222
+ case "$1" in
15223
+ --export) _graph_export="${2:-}"; if [ $# -ge 2 ]; then shift 2; else shift; fi ;;
15224
+ -h|--help) echo "Usage: loki memory graph [--export <path>] | loki memory graph rebuild"; return 0 ;;
15225
+ *) shift ;;
15226
+ esac
15227
+ done
15228
+ PYTHONPATH="${SKILL_DIR:-$(pwd)}" _LOKI_GRAPH_EXPORT="$_graph_export" python3 -c "
15229
+ import sys, os, json
15230
+ export_path = os.environ.get('_LOKI_GRAPH_EXPORT', '')
15231
+ try:
15232
+ from memory.knowledge_graph import OrganizationKnowledgeGraph
15233
+ kg = OrganizationKnowledgeGraph()
15234
+ patterns = kg.load_patterns(limit=100)
15235
+ summary = {'pattern_count': len(patterns), 'patterns': patterns[:100]}
15236
+ if export_path:
15237
+ with open(export_path, 'w') as f:
15238
+ json.dump(summary, f, indent=2, default=str)
15239
+ print(f'Exported {len(patterns)} patterns to {export_path}')
15240
+ else:
15241
+ print(json.dumps(summary, indent=2, default=str)[:4000])
15242
+ except ImportError:
15243
+ print('knowledge_graph module not available')
15244
+ except Exception as e:
15245
+ print(f'Error: {e}')
15246
+ "
15247
+ ;;
15248
+
15249
+ enable-hook)
15250
+ # v7.7.20: idempotently install a Claude Code SessionEnd hook
15251
+ # using the VERIFIED schema {matcher, hooks:[{type,command}]}.
15252
+ # Per WebSearch (v7.7.18 council): SessionEnd fires on /clear,
15253
+ # payload is JSON on stdin with transcript_path. The shipped
15254
+ # script claude/hooks/loki-session-end.sh handles both stdin
15255
+ # JSON and env-var fallback, so we point the hook at it.
15256
+ # No-op under LOKI_MEMORY_HOOK_DISABLED=true.
15257
+ shift
15258
+ if [ "${LOKI_MEMORY_HOOK_DISABLED:-}" = "true" ]; then
15259
+ echo -e "${YELLOW}Hook install skipped (LOKI_MEMORY_HOOK_DISABLED=true)${NC}"
15260
+ return 0
15261
+ fi
15262
+ local _settings="$HOME/.claude/settings.json"
15263
+ local _hook_script="${SKILL_DIR:-$(pwd)}/claude/hooks/loki-session-end.sh"
15264
+ mkdir -p "$(dirname "$_settings")" 2>/dev/null || true
15265
+ [ ! -f "$_settings" ] && echo '{}' > "$_settings"
15266
+ _LOKI_HOOK_SCRIPT="$_hook_script" _LOKI_SETTINGS="$_settings" python3 -c "
15267
+ import json, os, sys, tempfile
15268
+ settings_path = os.environ['_LOKI_SETTINGS']
15269
+ hook_script = os.environ['_LOKI_HOOK_SCRIPT']
15270
+ hook_cmd = f'bash {hook_script}'
15271
+ try:
15272
+ with open(settings_path) as f:
15273
+ data = json.load(f)
15274
+ except Exception:
15275
+ data = {}
15276
+ hooks = data.setdefault('hooks', {})
15277
+ session_end = hooks.setdefault('SessionEnd', [])
15278
+ # Verified Claude Code schema: list of {matcher, hooks:[{type,command}]}.
15279
+ # Idempotency: detect an existing entry whose nested command references
15280
+ # our hook script.
15281
+ for entry in session_end:
15282
+ if isinstance(entry, dict):
15283
+ for h in entry.get('hooks', []):
15284
+ if isinstance(h, dict) and 'loki-session-end.sh' in str(h.get('command', '')):
15285
+ print('already-installed')
15286
+ sys.exit(0)
15287
+ session_end.append({
15288
+ 'matcher': 'clear',
15289
+ 'hooks': [{'type': 'command', 'command': hook_cmd}],
15290
+ })
15291
+ tmp_fd, tmp_path = tempfile.mkstemp(dir=os.path.dirname(settings_path), prefix='.loki-settings-')
15292
+ with os.fdopen(tmp_fd, 'w') as f:
15293
+ json.dump(data, f, indent=2)
15294
+ os.replace(tmp_path, settings_path)
15295
+ print('installed')
15296
+ "
15297
+ local _r=$?
15298
+ if [ $_r -eq 0 ]; then
15299
+ echo -e "${GREEN}SessionEnd hook ready.${NC} Fires on /clear; pipes transcript to loki memory ingest."
15300
+ echo -e "${CYAN}Note: SessionEnd only fires on /clear, not normal exits (Claude Code limitation).${NC}"
15301
+ echo -e "${CYAN}Remove with: loki memory disable-hook${NC}"
15302
+ fi
15303
+ ;;
15304
+
15305
+ disable-hook)
15306
+ # v7.7.20: reverse counterpart. Removes any SessionEnd entry
15307
+ # whose nested command references loki-session-end.sh.
15308
+ shift
15309
+ local _settings="$HOME/.claude/settings.json"
15310
+ if [ ! -f "$_settings" ]; then
15311
+ echo -e "${YELLOW}No ~/.claude/settings.json to clean${NC}"
15312
+ return 0
15313
+ fi
15314
+ _LOKI_SETTINGS="$_settings" python3 -c "
15315
+ import json, os, sys, tempfile
15316
+ settings_path = os.environ['_LOKI_SETTINGS']
15317
+ try:
15318
+ with open(settings_path) as f:
15319
+ data = json.load(f)
15320
+ except Exception:
15321
+ print('settings-not-readable')
15322
+ sys.exit(0)
15323
+ hooks = data.get('hooks', {})
15324
+ session_end = hooks.get('SessionEnd', [])
15325
+ def refs_loki(entry):
15326
+ if not isinstance(entry, dict):
15327
+ return False
15328
+ for h in entry.get('hooks', []):
15329
+ if isinstance(h, dict) and 'loki-session-end.sh' in str(h.get('command', '')):
15330
+ return True
15331
+ return False
15332
+ filtered = [e for e in session_end if not refs_loki(e)]
15333
+ if len(filtered) == len(session_end):
15334
+ print('not-installed')
15335
+ sys.exit(0)
15336
+ hooks['SessionEnd'] = filtered
15337
+ data['hooks'] = hooks
15338
+ tmp_fd, tmp_path = tempfile.mkstemp(dir=os.path.dirname(settings_path), prefix='.loki-settings-')
15339
+ with os.fdopen(tmp_fd, 'w') as f:
15340
+ json.dump(data, f, indent=2)
15341
+ os.replace(tmp_path, settings_path)
15342
+ print('removed')
15343
+ "
15344
+ ;;
15118
15345
 
15119
15346
  *)
15120
15347
  echo -e "${RED}Unknown memory command: $subcommand${NC}"
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.7.19"
10
+ __version__ = "7.7.20"
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.7.19
5
+ **Version:** v7.7.20
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 v=(K,$)=>{for(var Q in $)_7(K,Q,{get:$[Q],enumerable:!0,configurable:!0,set:P7.bind($,Q)})};var R=(K,$)=>()=>(K&&($=K(K=0)),$);var t=import.meta.require;var e1={};v(e1,{lokiDir:()=>P,homeLokiDir:()=>k1,findRepoRootForVersion:()=>N1,REPO_ROOT:()=>p});import{resolve as u,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(u(K,"VERSION"))&&J1(u(K,"autonomy/run.sh")))return K;let Q=S1(K);if(Q===K)break;K=Q}return u(i1,"..","..","..")}function N1(K){let $=K;for(let Q=0;Q<6;Q++){if(J1(u($,"VERSION"))&&J1(u($,"autonomy/run.sh")))return $;let X=S1($);if(X===$)break;$=X}return u(K,"..","..","..")}function P(){return process.env.LOKI_DIR??u(process.cwd(),".loki")}function k1(){return u(R7(),".loki")}var i1,p;var g=R(()=>{i1=S1(L7(import.meta.url));p=E7()});import{readFileSync as F7}from"fs";import{resolve as w7,dirname as x7}from"path";import{fileURLToPath as S7}from"url";function G1(){if(o!==null)return o;let K="7.7.19";if(typeof K==="string"&&K.length>0)return o=K,o;try{let $=x7(S7(import.meta.url)),Q=N1($);o=F7(w7(Q,"VERSION"),"utf-8").trim()}catch{o="unknown"}return o}var o=null;var D1=R(()=>{g()});var $0={};v($0,{runOrThrow:()=>N7,run:()=>k,commandVersion:()=>D7,commandExists:()=>h,ShellError:()=>C1});async function k(K,$={}){let Q=Bun.spawn({cmd:[...K],stdout:"pipe",stderr:"pipe",env:$.env?{...process.env,...$.env}:process.env,cwd:$.cwd}),X,Z;if($.timeoutMs&&$.timeoutMs>0)X=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},$.timeoutMs);try{let[W,z,q]=await Promise.all([new Response(Q.stdout).text(),new Response(Q.stderr).text(),Q.exited]);return{stdout:W,stderr:z,exitCode:q}}finally{if(X)clearTimeout(X);if(Z)clearTimeout(Z)}}async function N7(K,$={}){let Q=await k(K,$);if(Q.exitCode!==0)throw new C1(`command failed (${Q.exitCode}): ${K.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function h(K){let $=k7(K),Q=await k(["sh","-c",`command -v ${$}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.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 X=await k([K,$],{timeoutMs:5000});if(X.exitCode!==0)return null;return((X.stdout||X.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var C1;var n=R(()=>{C1=class C1 extends Error{message;exitCode;stdout;stderr;constructor(K,$,Q,X){super(K);this.message=K;this.exitCode=$;this.stdout=Q;this.stderr=X;this.name="ShellError"}}});function c(K){return C7?"":K}var C7,E,b,F,T6,O,D,w,H;var a=R(()=>{C7=(process.env.NO_COLOR??"").length>0;E=c("\x1B[0;31m"),b=c("\x1B[0;32m"),F=c("\x1B[1;33m"),T6=c("\x1B[0;34m"),O=c("\x1B[0;36m"),D=c("\x1B[1m"),w=c("\x1B[2m"),H=c("\x1B[0m")});import{existsSync as c7}from"fs";async function i(){if(X1!==void 0)return X1;let K="/opt/homebrew/bin/python3.12";if(c7(K))return X1=K,K;let $=await h("python3.12");if($)return X1=$,$;let Q=await h("python3");return X1=Q,Q}async function s(K,$={}){let Q=await i();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return k([Q,"-c",K],$)}var X1;var Z1=R(()=>{n()});var G0={};v(G0,{runStatus:()=>Q5});import{existsSync as N,readFileSync as W1,readdirSync as W0,statSync as H0}from"fs";import{resolve as x,basename as a7}from"path";async function r7(){if(await h("jq"))return!0;return process.stdout.write(`${E}Error: jq is required but not installed.${H}
2
+ var _7=Object.defineProperty;var I7=(K)=>K;function P7(K,$){this[K]=I7.bind(null,$)}var v=(K,$)=>{for(var Q in $)_7(K,Q,{get:$[Q],enumerable:!0,configurable:!0,set:P7.bind($,Q)})};var R=(K,$)=>()=>(K&&($=K(K=0)),$);var t=import.meta.require;var e1={};v(e1,{lokiDir:()=>P,homeLokiDir:()=>k1,findRepoRootForVersion:()=>N1,REPO_ROOT:()=>p});import{resolve as u,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(u(K,"VERSION"))&&J1(u(K,"autonomy/run.sh")))return K;let Q=S1(K);if(Q===K)break;K=Q}return u(i1,"..","..","..")}function N1(K){let $=K;for(let Q=0;Q<6;Q++){if(J1(u($,"VERSION"))&&J1(u($,"autonomy/run.sh")))return $;let X=S1($);if(X===$)break;$=X}return u(K,"..","..","..")}function P(){return process.env.LOKI_DIR??u(process.cwd(),".loki")}function k1(){return u(R7(),".loki")}var i1,p;var g=R(()=>{i1=S1(L7(import.meta.url));p=E7()});import{readFileSync as F7}from"fs";import{resolve as w7,dirname as x7}from"path";import{fileURLToPath as S7}from"url";function G1(){if(o!==null)return o;let K="7.7.20";if(typeof K==="string"&&K.length>0)return o=K,o;try{let $=x7(S7(import.meta.url)),Q=N1($);o=F7(w7(Q,"VERSION"),"utf-8").trim()}catch{o="unknown"}return o}var o=null;var D1=R(()=>{g()});var $0={};v($0,{runOrThrow:()=>N7,run:()=>k,commandVersion:()=>D7,commandExists:()=>h,ShellError:()=>C1});async function k(K,$={}){let Q=Bun.spawn({cmd:[...K],stdout:"pipe",stderr:"pipe",env:$.env?{...process.env,...$.env}:process.env,cwd:$.cwd}),X,Z;if($.timeoutMs&&$.timeoutMs>0)X=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},$.timeoutMs);try{let[W,z,q]=await Promise.all([new Response(Q.stdout).text(),new Response(Q.stderr).text(),Q.exited]);return{stdout:W,stderr:z,exitCode:q}}finally{if(X)clearTimeout(X);if(Z)clearTimeout(Z)}}async function N7(K,$={}){let Q=await k(K,$);if(Q.exitCode!==0)throw new C1(`command failed (${Q.exitCode}): ${K.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function h(K){let $=k7(K),Q=await k(["sh","-c",`command -v ${$}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.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 X=await k([K,$],{timeoutMs:5000});if(X.exitCode!==0)return null;return((X.stdout||X.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var C1;var n=R(()=>{C1=class C1 extends Error{message;exitCode;stdout;stderr;constructor(K,$,Q,X){super(K);this.message=K;this.exitCode=$;this.stdout=Q;this.stderr=X;this.name="ShellError"}}});function c(K){return C7?"":K}var C7,E,b,F,T6,O,D,w,H;var a=R(()=>{C7=(process.env.NO_COLOR??"").length>0;E=c("\x1B[0;31m"),b=c("\x1B[0;32m"),F=c("\x1B[1;33m"),T6=c("\x1B[0;34m"),O=c("\x1B[0;36m"),D=c("\x1B[1m"),w=c("\x1B[2m"),H=c("\x1B[0m")});import{existsSync as c7}from"fs";async function i(){if(X1!==void 0)return X1;let K="/opt/homebrew/bin/python3.12";if(c7(K))return X1=K,K;let $=await h("python3.12");if($)return X1=$,$;let Q=await h("python3");return X1=Q,Q}async function s(K,$={}){let Q=await i();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return k([Q,"-c",K],$)}var X1;var Z1=R(()=>{n()});var G0={};v(G0,{runStatus:()=>Q5});import{existsSync as N,readFileSync as W1,readdirSync as W0,statSync as H0}from"fs";import{resolve as x,basename as a7}from"path";async function r7(){if(await h("jq"))return!0;return process.stdout.write(`${E}Error: jq is required but not installed.${H}
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)
@@ -585,4 +585,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
585
585
  `),2}default:return process.stderr.write(`Unknown command: ${$}
586
586
  `),process.stderr.write(j7),2}}process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var X6=await Q6(Bun.argv.slice(2));process.exit(X6);
587
587
 
588
- //# debugId=2A67FE71C904504464756E2164756E21
588
+ //# debugId=E25419B4237E69C764756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.7.19'
60
+ __version__ = '7.7.20'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "7.7.19",
3
+ "version": "7.7.20",
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",