loki-mode 7.41.4 → 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 +2 -2
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +121 -24
- package/autonomy/run.sh +1 -1
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/memory/engine.py +15 -3
- package/memory/storage.py +6 -0
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
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.
|
|
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.
|
|
401
|
+
**v7.41.5 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.41.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2081
|
-
# which penalized active progress.
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
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
|
-
|
|
2123
|
+
blocked="true"
|
|
2104
2124
|
reasons="${reasons}uncaught errors in logs ($error_count); "
|
|
2105
2125
|
fi
|
|
2106
2126
|
|
|
2107
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
#
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
if [ "$
|
|
2127
|
-
|
|
2128
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
|
@@ -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.
|
|
5
|
+
**Version:** v7.41.5
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -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.
|
|
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=
|
|
792
|
+
//# debugId=BBB074C842B2B95764756E2164756E21
|
package/mcp/__init__.py
CHANGED
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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.
|
|
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.
|
|
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",
|