loki-mode 7.41.3 → 7.41.5

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.41.3
6
+ # Loki Mode v7.41.5
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.41.3 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
401
+ **v7.41.5 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.41.3
1
+ 7.41.5
@@ -2045,7 +2045,23 @@ council_evaluate_member() {
2045
2045
  local role="$1"
2046
2046
  local criteria="${2:-general completion check}"
2047
2047
  local loki_dir="${TARGET_DIR:-.}/.loki"
2048
- local vote="COMPLETE"
2048
+ # Trust-gate inversion (v7.41.5): the default is CONTINUE, not COMPLETE.
2049
+ # The old default ("absence of a detected failure == COMPLETE") let a
2050
+ # greenfield run with an empty .loki/ (no test logs, no queue, few TODOs)
2051
+ # cause requirements_verifier + devils_advocate to vote COMPLETE while only
2052
+ # test_auditor went CONTINUE -> 2-of-3 cleared the size-3 threshold and the
2053
+ # heuristic council approved a project with ZERO positive evidence. We now
2054
+ # require AFFIRMATIVE positive evidence before any member votes COMPLETE,
2055
+ # mirroring the LLM prompt's "do not approve incomplete work" stance.
2056
+ #
2057
+ # Mechanics:
2058
+ # - vote starts at CONTINUE.
2059
+ # - The existing failure detectors set blocked=true (a hard "not done"
2060
+ # signal) and accumulate reasons.
2061
+ # - At the end, vote flips to COMPLETE only if blocked==false AND the
2062
+ # member's positive signal holds. No positive signal => CONTINUE.
2063
+ local vote="CONTINUE"
2064
+ local blocked="false"
2049
2065
  local reasons=""
2050
2066
 
2051
2067
  # Check 1: Do tests pass? Look for test results in .loki/
@@ -2067,7 +2083,7 @@ council_evaluate_member() {
2067
2083
  fi
2068
2084
  done
2069
2085
  if [ "$test_failures" -gt 0 ]; then
2070
- vote="CONTINUE"
2086
+ blocked="true"
2071
2087
  reasons="${reasons}test failures found ($test_failures); "
2072
2088
  fi
2073
2089
 
@@ -2076,12 +2092,16 @@ council_evaluate_member() {
2076
2092
  local current_diff_hash
2077
2093
  current_diff_hash=$(git diff --stat HEAD 2>/dev/null | (md5sum 2>/dev/null || md5 -r 2>/dev/null) | cut -d' ' -f1 || echo "unknown")
2078
2094
  if [ "$COUNCIL_CONSECUTIVE_NO_CHANGE" -gt 0 ] && [ "$ITERATION_COUNT" -gt "$COUNCIL_MIN_ITERATIONS" ]; then
2079
- # Code has stopped changing -- stagnation, not necessarily done
2080
- # (BUG-QG-011: previously inverted -- forced CONTINUE when code was changing,
2081
- # which penalized active progress. Now: stagnation with no passing checks = CONTINUE)
2082
- if [ "$vote" = "COMPLETE" ]; then
2083
- : # Other checks passed despite stagnation -- allow COMPLETE
2084
- else
2095
+ # Code has stopped changing -- stagnation, not necessarily done.
2096
+ # (BUG-QG-011 history: previously inverted -- forced CONTINUE when code
2097
+ # was changing, which penalized active progress.)
2098
+ # Under the v7.41.5 affirmative-evidence default this is INFORMATIONAL
2099
+ # only: stagnation by itself is neither a failure (do not set blocked)
2100
+ # nor positive evidence (does not flip vote to COMPLETE). Whether the
2101
+ # member completes is decided entirely by blocked + the positive-signal
2102
+ # check below. Surface the stagnation note only when a real failure was
2103
+ # also detected, so the reason string stays honest.
2104
+ if [ "$blocked" = "true" ]; then
2085
2105
  reasons="${reasons}code stagnated with failing checks; "
2086
2106
  fi
2087
2107
  fi
@@ -2100,49 +2120,126 @@ council_evaluate_member() {
2100
2120
  done
2101
2121
  fi
2102
2122
  if [ "$error_count" -gt 0 ]; then
2103
- vote="CONTINUE"
2123
+ blocked="true"
2104
2124
  reasons="${reasons}uncaught errors in logs ($error_count); "
2105
2125
  fi
2106
2126
 
2107
- # Role-specific checks
2127
+ # --- Affirmative positive evidence (v7.41.5) ----------------------------
2128
+ # Base positive signal, shared by all members: a structured test-results.json
2129
+ # exists and is NOT red. This is the SAME file + parse the evidence hard gate
2130
+ # uses (council_evidence_gate, see this file ~line 1543-1568), so the member
2131
+ # vote and the gate agree on what "tests are not red" means instead of a
2132
+ # fragile log grep. The completion route guarantees this file is written
2133
+ # before the council votes via ensure_completion_test_evidence()
2134
+ # (autonomy/run.sh): a project with a real runner records its true PASS/FAIL,
2135
+ # and a project with no runner records {"runner":"none","pass":true}. A
2136
+ # greenfield run with an empty .loki/ has NO such file -> no positive base ->
2137
+ # the member stays CONTINUE.
2138
+ #
2139
+ # Parse verdict mirrors council_evidence_gate: runner=="none" => PASS,
2140
+ # pass is False => FAIL, else PASS. Unparseable/missing => not present.
2141
+ local tr_file="$loki_dir/quality/test-results.json"
2142
+ local test_evidence="absent" # absent | pass | fail
2143
+ local test_runner_seen="none"
2144
+ if [ -f "$tr_file" ]; then
2145
+ local _tr_status
2146
+ _tr_status=$(_TR_FILE="$tr_file" python3 -c "
2147
+ import json, os, sys
2148
+ try:
2149
+ with open(os.environ['_TR_FILE']) as f:
2150
+ d = json.load(f)
2151
+ except (json.JSONDecodeError, IOError, KeyError, ValueError):
2152
+ print('absent:none')
2153
+ sys.exit(0)
2154
+ runner = d.get('runner', 'none')
2155
+ passed = d.get('pass', True)
2156
+ if runner == 'none':
2157
+ print('pass:none')
2158
+ elif passed is False:
2159
+ print('fail:%s' % runner)
2160
+ else:
2161
+ print('pass:%s' % runner)
2162
+ " 2>/dev/null || echo "absent:none")
2163
+ test_evidence="${_tr_status%%:*}"
2164
+ test_runner_seen="${_tr_status#*:}"
2165
+ fi
2166
+ if [ "$test_evidence" = "fail" ]; then
2167
+ # A red structured result is a hard failure for every member, on top of
2168
+ # any log-derived failures already counted above.
2169
+ blocked="true"
2170
+ reasons="${reasons}structured test results red (runner '$test_runner_seen'); "
2171
+ fi
2172
+
2173
+ # Per-member positive signal, evaluated on top of the shared base.
2174
+ local positive="false"
2108
2175
  case "$role" in
2109
2176
  requirements_verifier)
2110
- # Check if pending tasks remain
2177
+ # Positive: tests not red AND no pending tasks. A present queue file
2178
+ # with pending>0 is a hard "not done"; an ABSENT queue file is not
2179
+ # itself disqualifying (a legit run need not have one), it just means
2180
+ # this member relies on the base test evidence.
2181
+ local pending=0
2111
2182
  if [ -f "$loki_dir/queue/pending.json" ]; then
2112
- local pending
2113
2183
  pending=$(_QUEUE_FILE="$loki_dir/queue/pending.json" python3 -c "import json, os; print(len(json.load(open(os.environ['_QUEUE_FILE']))))" 2>/dev/null || echo "0")
2114
2184
  if [ "$pending" -gt 0 ]; then
2115
- vote="CONTINUE"
2185
+ blocked="true"
2116
2186
  reasons="${reasons}$pending tasks still pending; "
2117
2187
  fi
2118
2188
  fi
2189
+ if [ "$test_evidence" = "pass" ] && [ "$pending" -eq 0 ]; then
2190
+ positive="true"
2191
+ fi
2119
2192
  ;;
2120
2193
  test_auditor)
2121
- # Check if any test log exists at all
2122
- local has_tests=false
2123
- for f in "$loki_dir"/logs/test-*.log "$loki_dir"/logs/*test*.log; do
2124
- [ -f "$f" ] && has_tests=true && break
2125
- done
2126
- if [ "$has_tests" = "false" ]; then
2127
- vote="CONTINUE"
2128
- reasons="${reasons}no test results found; "
2194
+ # Positive requires a REAL passing test signal, not merely the
2195
+ # absence of a failing one: a structured result with runner != none
2196
+ # AND pass == true. {"runner":"none"} (no suite ran) is NOT positive
2197
+ # test evidence for this member, and a missing file is not either, so
2198
+ # a no-tests / greenfield project leaves test_auditor at CONTINUE.
2199
+ if [ "$test_evidence" = "absent" ]; then
2200
+ reasons="${reasons}no structured test results found; "
2201
+ elif [ "$test_runner_seen" = "none" ]; then
2202
+ reasons="${reasons}no real test suite ran (runner none); "
2203
+ elif [ "$test_evidence" = "pass" ]; then
2204
+ positive="true"
2129
2205
  fi
2130
2206
  ;;
2131
2207
  devils_advocate)
2132
- # Check for TODO/FIXME markers
2208
+ # Positive: tests not red AND a low TODO/FIXME density. A high marker
2209
+ # count is a hard "not done"; a missing/absent test base means no
2210
+ # positive evidence even when TODOs are low.
2133
2211
  local todo_count
2134
2212
  todo_count=$(grep -rl "TODO\|FIXME\|HACK\|XXX" . --include="*.ts" --include="*.js" --include="*.py" --include="*.sh" 2>/dev/null | wc -l | tr -d ' ')
2135
2213
  if [ "$todo_count" -gt 5 ]; then
2136
- vote="CONTINUE"
2214
+ blocked="true"
2137
2215
  reasons="${reasons}$todo_count files with TODO/FIXME markers; "
2138
2216
  fi
2217
+ if [ "$test_evidence" = "pass" ] && [ "$todo_count" -le 5 ]; then
2218
+ positive="true"
2219
+ fi
2220
+ ;;
2221
+ *)
2222
+ # Unknown role: fall back to the shared base signal only.
2223
+ if [ "$test_evidence" = "pass" ]; then
2224
+ positive="true"
2225
+ fi
2139
2226
  ;;
2140
2227
  esac
2141
2228
 
2229
+ # Final decision: COMPLETE only when nothing blocks AND positive evidence
2230
+ # is present. Otherwise CONTINUE (the affirmative-evidence default).
2231
+ if [ "$blocked" = "false" ] && [ "$positive" = "true" ]; then
2232
+ vote="COMPLETE"
2233
+ fi
2234
+
2142
2235
  # Clean up trailing separator
2143
2236
  reasons="${reasons%; }"
2144
2237
  if [ -z "$reasons" ]; then
2145
- reasons="all checks passed for $role ($criteria)"
2238
+ if [ "$vote" = "COMPLETE" ]; then
2239
+ reasons="positive evidence present, no failures for $role ($criteria)"
2240
+ else
2241
+ reasons="no positive completion evidence for $role ($criteria)"
2242
+ fi
2146
2243
  fi
2147
2244
 
2148
2245
  echo "$vote $reasons"
package/autonomy/run.sh CHANGED
@@ -1003,7 +1003,7 @@ log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
1003
1003
  log_warning() { log_warn "$@"; } # Alias for backwards compatibility
1004
1004
  log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
1005
1005
  log_step() { echo -e "${CYAN}[STEP]${NC} $*"; }
1006
- log_debug() { [[ "${LOKI_DEBUG:-}" == "true" ]] && echo -e "${CYAN}[DEBUG]${NC} $*" || true; }
1006
+ log_debug() { [[ "${LOKI_DEBUG:-}" == "true" ]] && echo -e "${CYAN}[DEBUG]${NC} $*" >&2 || true; }
1007
1007
 
1008
1008
  #===============================================================================
1009
1009
  # Process Registry (PID Supervisor)
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.41.3"
10
+ __version__ = "7.41.5"
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.41.3
5
+ **Version:** v7.41.5
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.41.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}
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.41.5";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=B525779BEC918A6664756E2164756E21
792
+ //# debugId=BBB074C842B2B95764756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.41.3'
60
+ __version__ = '7.41.5'
package/memory/engine.py CHANGED
@@ -242,6 +242,8 @@ class MemoryEngine:
242
242
  patterns_data = self.storage.read_json("semantic/patterns.json") or {}
243
243
  referenced_ids: set = set()
244
244
  for pattern in patterns_data.get("patterns", []):
245
+ if not isinstance(pattern, dict):
246
+ continue
245
247
  referenced_ids.update(pattern.get("source_episodes", []))
246
248
 
247
249
  # Scan episodic directories
@@ -366,11 +368,15 @@ class MemoryEngine:
366
368
  })
367
369
  index["total_memories"] = index.get("total_memories", 0) + 1
368
370
  else:
371
+ # Only count a given episode once. On resume/checkpoint the same
372
+ # trace id can be re-saved; without this guard episode_count,
373
+ # total_cost_usd, and total_tokens would inflate on every re-save
374
+ # even though episode_ids is already de-duplicated.
369
375
  if episode_id and episode_id not in found.get("episode_ids", []):
370
376
  found.setdefault("episode_ids", []).append(episode_id)
371
- found["episode_count"] = found.get("episode_count", 0) + 1
372
- found["total_cost_usd"] = float(found.get("total_cost_usd", 0) or 0) + cost
373
- found["total_tokens"] = int(found.get("total_tokens", 0) or 0) + tokens
377
+ found["episode_count"] = found.get("episode_count", 0) + 1
378
+ found["total_cost_usd"] = float(found.get("total_cost_usd", 0) or 0) + cost
379
+ found["total_tokens"] = int(found.get("total_tokens", 0) or 0) + tokens
374
380
  merged = set(found.get("files_touched", []) or []) | set(files[:20])
375
381
  found["files_touched"] = sorted(merged)[:50]
376
382
  found["last_accessed"] = now
@@ -489,6 +495,8 @@ class MemoryEngine:
489
495
  """
490
496
  patterns_data = self.storage.read_json("semantic/patterns.json") or {}
491
497
  for pattern in patterns_data.get("patterns", []):
498
+ if not isinstance(pattern, dict):
499
+ continue
492
500
  if pattern.get("id") == pattern_id:
493
501
  return self._dict_to_pattern(pattern)
494
502
  return None
@@ -512,6 +520,8 @@ class MemoryEngine:
512
520
  results: List[SemanticPattern] = []
513
521
 
514
522
  for pattern in patterns_data.get("patterns", []):
523
+ if not isinstance(pattern, dict):
524
+ continue
515
525
  # Filter by confidence
516
526
  if pattern.get("confidence", 0) < min_confidence:
517
527
  continue
@@ -837,6 +847,8 @@ class MemoryEngine:
837
847
  # Index semantic patterns
838
848
  patterns_data = self.storage.read_json("semantic/patterns.json") or {}
839
849
  for pattern in patterns_data.get("patterns", []):
850
+ if not isinstance(pattern, dict):
851
+ continue
840
852
  total_memories += 1
841
853
  category = pattern.get("category", "general")
842
854
 
package/memory/storage.py CHANGED
@@ -617,6 +617,12 @@ class MemoryStorage:
617
617
  "patterns": []
618
618
  }
619
619
 
620
+ # Defensive: a pre-existing patterns.json that is valid JSON but
621
+ # lacks the "patterns" key (partial/external write, alternate
622
+ # schema, or a {"version": ...}-only file) would otherwise raise
623
+ # KeyError below and silently lose the save. Ensure the list exists.
624
+ patterns_file.setdefault("patterns", [])
625
+
620
626
  # Upsert: update existing pattern or append new
621
627
  existing_idx = None
622
628
  for i, p in enumerate(patterns_file["patterns"]):
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.41.3",
4
+ "version": "7.41.5",
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",
@@ -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": "7.41.3",
5
+ "version": "7.41.5",
6
6
  "description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 11 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",