loki-mode 7.7.27 → 7.7.28

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-to-product system. Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product via the RARV-C closure loop, with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v7.7.27
6
+ # Loki Mode v7.7.28
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.27 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
384
+ **v7.7.28 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.7.27
1
+ 7.7.28
@@ -1199,7 +1199,16 @@ council_evaluate_member() {
1199
1199
  for test_log in "$loki_dir"/logs/test-*.log "$loki_dir"/logs/*test*.log; do
1200
1200
  if [ -f "$test_log" ]; then
1201
1201
  local fail_count
1202
- fail_count=$(grep -ciE "(FAIL|ERROR|failed|error:)" "$test_log" 2>/dev/null || echo "0")
1202
+ # Detect REAL test failures, not any line containing "error".
1203
+ # The old blanket grep "(FAIL|ERROR|failed|error:)" counted
1204
+ # benign lines ("0 errors", "0 failed", a test named
1205
+ # test_error_handling, "no errors found"), which forced CONTINUE
1206
+ # forever on a fully-passing suite that merely mentions "error".
1207
+ # Match actual failure signals and exclude the zero-count forms.
1208
+ fail_count=$(grep -ciE \
1209
+ '([1-9][0-9]*[[:space:]]+(failed|errors?)|^FAILED|[[:space:]]FAILED[[:space:]]|tests? failed|assertionerror|traceback \(most recent)' \
1210
+ "$test_log" 2>/dev/null | tr -dc '0-9')
1211
+ fail_count=${fail_count:-0}
1203
1212
  test_failures=$((test_failures + fail_count))
1204
1213
  fi
1205
1214
  done
@@ -362,7 +362,10 @@ import json, sys
362
362
  from datetime import datetime
363
363
  with open(sys.argv[1]) as f:
364
364
  data = json.load(f)
365
- data.get('modes', []).append({
365
+ # setdefault (not get): get('modes', []) appended to a THROWAWAY list when
366
+ # the key was missing, so the failure mode was silently lost on a fresh
367
+ # failure-modes.json. setdefault binds the list into data before appending.
368
+ data.setdefault('modes', []).append({
366
369
  'mode_id': 'heal-fail-' + datetime.now().strftime('%Y%m%dT%H%M%S'),
367
370
  'trigger': 'healing_modification',
368
371
  'file': sys.argv[2],
package/autonomy/run.sh CHANGED
@@ -8710,7 +8710,11 @@ try:
8710
8710
  from memory.engine import MemoryEngine
8711
8711
  from memory.schemas import EpisodeTrace
8712
8712
  from datetime import datetime, timezone
8713
- engine = MemoryEngine(f'{target_dir}/.loki/memory')
8713
+ # base_path= is required: MemoryEngine.__init__(self, storage=None, base_path=...)
8714
+ # takes `storage` first, so a bare positional path was assigned to
8715
+ # self.storage and engine.initialize() crashed on str.ensure_directory,
8716
+ # silently dropping every store_episode_trace into the except handler.
8717
+ engine = MemoryEngine(base_path=f'{target_dir}/.loki/memory')
8714
8718
  engine.initialize()
8715
8719
  trace = EpisodeTrace.create(
8716
8720
  task_id=task_id,
@@ -9071,7 +9075,15 @@ try:
9071
9075
  importance = float(getattr(trace, 'importance', 0.0) or 0.0)
9072
9076
  except (TypeError, ValueError):
9073
9077
  importance = 0.0
9074
- episode_file = Path(f'{target_dir}/.loki/memory/episodic') / f'{trace.id}.json'
9078
+ # Reconstruct the ACTUAL on-disk path. storage.save_episode writes to
9079
+ # episodic/<YYYY-MM-DD>/task-<id>.json (date from the trace timestamp),
9080
+ # NOT episodic/<id>.json. The old flat path never existed, so the
9081
+ # importance shadow-write guard in bash never fired.
9082
+ _ts = getattr(trace, 'timestamp', '') or ''
9083
+ _date_str = str(_ts)[:10] if _ts else __import__('datetime').datetime.now(
9084
+ __import__('datetime').timezone.utc).strftime('%Y-%m-%d')
9085
+ episode_file = (Path(f'{target_dir}/.loki/memory/episodic')
9086
+ / _date_str / f'task-{trace.id}.json')
9075
9087
  if path_out_file:
9076
9088
  try:
9077
9089
  with open(path_out_file, 'w', encoding='utf-8') as f:
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.7.27"
10
+ __version__ = "7.7.28"
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.27
5
+ **Version:** v7.7.28
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.27";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.28";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=007306826576BB8664756E2164756E21
588
+ //# debugId=FB1EB87ADC30E73E64756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.7.27'
60
+ __version__ = '7.7.28'
@@ -1558,6 +1558,35 @@ class MemoryRetrieval:
1558
1558
  if self._belongs_to_namespace(anti_copy):
1559
1559
  results.append(anti_copy)
1560
1560
 
1561
+ # Consolidation writes anti-patterns as SemanticPattern objects with
1562
+ # category="anti-pattern" into semantic/patterns.json (not the legacy
1563
+ # anti-patterns.json above), with fields incorrect_approach /
1564
+ # description / correct_approach. Without this bridge, consolidated
1565
+ # anti-patterns were never retrievable. Map them onto the same
1566
+ # what_fails / why / prevention scoring shape.
1567
+ patterns_data = self.storage.read_json("semantic/patterns.json") or {}
1568
+ for pat in patterns_data.get("patterns", []):
1569
+ if pat.get("category") != "anti-pattern":
1570
+ continue
1571
+ what_fails = (pat.get("incorrect_approach", "")
1572
+ or pat.get("pattern", "")).lower()
1573
+ why = pat.get("description", "").lower()
1574
+ prevention = pat.get("correct_approach", "").lower()
1575
+
1576
+ score = sum(2 for kw in keywords if kw in what_fails)
1577
+ score += sum(1 for kw in keywords if kw in why)
1578
+ score += sum(1 for kw in keywords if kw in prevention)
1579
+
1580
+ if score > 0:
1581
+ anti_copy = dict(pat)
1582
+ anti_copy["_score"] = score
1583
+ anti_copy["_source"] = "anti_patterns"
1584
+ anti_copy.setdefault("what_fails", pat.get("incorrect_approach", "") or pat.get("pattern", ""))
1585
+ anti_copy.setdefault("why", pat.get("description", ""))
1586
+ anti_copy.setdefault("prevention", pat.get("correct_approach", ""))
1587
+ if self._belongs_to_namespace(anti_copy):
1588
+ results.append(anti_copy)
1589
+
1561
1590
  return results
1562
1591
 
1563
1592
  def _build_episodic_index(self) -> None:
@@ -1654,3 +1683,23 @@ class MemoryRetrieval:
1654
1683
  # Add to index with ID
1655
1684
  item_id = anti.get("id", anti.get("source", f"anti-{hash(text) % 10000}"))
1656
1685
  index.add(item_id, embedding, anti)
1686
+
1687
+ # Parity with the keyword path: consolidation writes anti-patterns as
1688
+ # category="anti-pattern" entries in semantic/patterns.json, not the
1689
+ # legacy anti-patterns.json above. Bridge those into the vector index
1690
+ # too so embedding-based retrieval sees consolidated anti-patterns.
1691
+ patterns_data = self.storage.read_json("semantic/patterns.json") or {}
1692
+ for pat in patterns_data.get("patterns", []):
1693
+ if pat.get("category") != "anti-pattern":
1694
+ continue
1695
+ what_fails = pat.get("incorrect_approach", "") or pat.get("pattern", "")
1696
+ why = pat.get("description", "")
1697
+ prevention = pat.get("correct_approach", "")
1698
+ text = f"{what_fails} {why} {prevention}"
1699
+ embedding = self.embedding_engine.embed(text)
1700
+ item_id = pat.get("id", f"anti-{hash(text) % 10000}")
1701
+ bridged = dict(pat)
1702
+ bridged.setdefault("what_fails", what_fails)
1703
+ bridged.setdefault("why", why)
1704
+ bridged.setdefault("prevention", prevention)
1705
+ index.add(item_id, embedding, bridged)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "7.7.27",
3
+ "version": "7.7.28",
4
4
  "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).",
5
5
  "keywords": [
6
6
  "agent",