loki-mode 7.104.4 → 7.104.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/dashboard/__init__.py +1 -1
- package/dashboard/server.py +102 -52
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- 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, 8 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.104.
|
|
6
|
+
# Loki Mode v7.104.5
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
408
408
|
|
|
409
409
|
---
|
|
410
410
|
|
|
411
|
-
**v7.104.
|
|
411
|
+
**v7.104.5 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.104.
|
|
1
|
+
7.104.5
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -5007,41 +5007,79 @@ def _sanitize_agent_id(agent_id: str) -> str:
|
|
|
5007
5007
|
)
|
|
5008
5008
|
return agent_id
|
|
5009
5009
|
|
|
5010
|
+
|
|
5011
|
+
# --- Memory file enumeration (single source of truth) ------------------------
|
|
5012
|
+
# v7.104.5 (PR #178 follow-up): the memory store writes episodes and skills into
|
|
5013
|
+
# DATED subdirectories (episodic/YYYY-MM-DD/*.json, skills/*/*.json), so a flat
|
|
5014
|
+
# .glob("*.json") finds nothing and every endpoint that used it under-reported.
|
|
5015
|
+
# PR #178 fixed only /api/memory/summary; the sibling /api/memory/episodes and
|
|
5016
|
+
# the skills listings still used a flat glob, so the count and the drill-in list
|
|
5017
|
+
# disagreed (summary said 28, the list returned 0) -- a trust-eroding
|
|
5018
|
+
# inconsistency. These two helpers are the ONE way to enumerate memory files, so
|
|
5019
|
+
# counts and lists can never diverge again. Both recurse and skip .lock files.
|
|
5020
|
+
def _memory_episode_files(ep_dir) -> list:
|
|
5021
|
+
"""All episode JSON files under episodic/ (recursing dated subdirs), sorted."""
|
|
5022
|
+
try:
|
|
5023
|
+
return sorted(p for p in ep_dir.rglob("*.json") if not p.name.endswith(".lock"))
|
|
5024
|
+
except Exception:
|
|
5025
|
+
return []
|
|
5026
|
+
|
|
5027
|
+
|
|
5028
|
+
def _memory_skill_files(skills_dir) -> list:
|
|
5029
|
+
"""All skill JSON files under skills/ (recursing subdirs), sorted."""
|
|
5030
|
+
try:
|
|
5031
|
+
return sorted(p for p in skills_dir.rglob("*.json") if not p.name.endswith(".lock"))
|
|
5032
|
+
except Exception:
|
|
5033
|
+
return []
|
|
5034
|
+
|
|
5035
|
+
|
|
5010
5036
|
@app.get("/api/memory/summary", dependencies=[Depends(auth.require_scope("read"))])
|
|
5011
5037
|
async def get_memory_summary():
|
|
5012
5038
|
"""Get memory system summary from .loki/memory/."""
|
|
5013
|
-
# Try SQLite backend first for accurate counts
|
|
5039
|
+
# Try SQLite backend first for accurate counts.
|
|
5040
|
+
# NOTE (PR #178, @iizotov): _get_memory_storage() returns a SQLiteMemoryStorage
|
|
5041
|
+
# object whenever the module imports, even if no .db file exists yet. On a
|
|
5042
|
+
# JSON-backed project that yields all-zero stats, so we must NOT return here on
|
|
5043
|
+
# empty counts -- otherwise the JSON file fallback below never runs and the
|
|
5044
|
+
# dashboard shows 0 episodes/patterns/skills even though they exist on disk.
|
|
5045
|
+
# Only trust SQLite when it actually reports data; else fall through to JSON.
|
|
5014
5046
|
storage = _get_memory_storage()
|
|
5015
5047
|
if storage is not None:
|
|
5016
5048
|
try:
|
|
5017
5049
|
stats = storage.get_stats()
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5050
|
+
total = (
|
|
5051
|
+
stats.get("episode_count", 0)
|
|
5052
|
+
+ stats.get("pattern_count", 0)
|
|
5053
|
+
+ stats.get("skill_count", 0)
|
|
5054
|
+
)
|
|
5055
|
+
if total > 0:
|
|
5056
|
+
summary = {
|
|
5057
|
+
"episodic": {"count": stats.get("episode_count", 0), "latestDate": None},
|
|
5058
|
+
"semantic": {"patterns": stats.get("pattern_count", 0), "antiPatterns": 0},
|
|
5059
|
+
"procedural": {"skills": stats.get("skill_count", 0)},
|
|
5060
|
+
"backend": "sqlite",
|
|
5061
|
+
}
|
|
5062
|
+
# Get latest episode date
|
|
5063
|
+
episode_ids = storage.list_episodes(limit=1)
|
|
5064
|
+
if episode_ids:
|
|
5065
|
+
ep = storage.load_episode(episode_ids[0])
|
|
5066
|
+
if ep:
|
|
5067
|
+
summary["episodic"]["latestDate"] = ep.get("timestamp", "")
|
|
5068
|
+
# Token economics from JSON (not in SQLite)
|
|
5069
|
+
econ_file = _get_loki_dir() / "memory" / "token_economics.json"
|
|
5070
|
+
if econ_file.exists():
|
|
5071
|
+
try:
|
|
5072
|
+
econ = json.loads(econ_file.read_text())
|
|
5073
|
+
summary["tokenEconomics"] = {
|
|
5074
|
+
"discoveryTokens": econ.get("discoveryTokens", 0),
|
|
5075
|
+
"readTokens": econ.get("readTokens", 0),
|
|
5076
|
+
"savingsPercent": econ.get("savingsPercent", 0),
|
|
5077
|
+
}
|
|
5078
|
+
except Exception:
|
|
5079
|
+
summary["tokenEconomics"] = {"discoveryTokens": 0, "readTokens": 0, "savingsPercent": 0}
|
|
5080
|
+
else:
|
|
5041
5081
|
summary["tokenEconomics"] = {"discoveryTokens": 0, "readTokens": 0, "savingsPercent": 0}
|
|
5042
|
-
|
|
5043
|
-
summary["tokenEconomics"] = {"discoveryTokens": 0, "readTokens": 0, "savingsPercent": 0}
|
|
5044
|
-
return summary
|
|
5082
|
+
return summary
|
|
5045
5083
|
except Exception:
|
|
5046
5084
|
pass
|
|
5047
5085
|
|
|
@@ -5057,14 +5095,28 @@ async def get_memory_summary():
|
|
|
5057
5095
|
|
|
5058
5096
|
ep_dir = memory_dir / "episodic"
|
|
5059
5097
|
if ep_dir.exists():
|
|
5060
|
-
|
|
5098
|
+
# Episodes are stored under dated subdirectories (episodic/YYYY-MM-DD/*.json),
|
|
5099
|
+
# so a flat glob("*.json") finds nothing (PR #178, @iizotov). Recurse and
|
|
5100
|
+
# ignore .lock files. Shared with /api/memory/episodes via _memory_episode_
|
|
5101
|
+
# files() so the summary count and the drill-in list can never disagree.
|
|
5102
|
+
episodes = _memory_episode_files(ep_dir)
|
|
5061
5103
|
summary["episodic"]["count"] = len(episodes)
|
|
5062
5104
|
if episodes:
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5105
|
+
# latestDate must be the newest by RECORDED TIMESTAMP, not by filename
|
|
5106
|
+
# sort: intra-day filenames carry a hash tiebreak, not a time, so a
|
|
5107
|
+
# filename sort can report the wrong episode (observed ~15h off). Pick
|
|
5108
|
+
# the max parsed timestamp; fall back to the last file only if no
|
|
5109
|
+
# timestamp is readable, so an honest value is preferred over a
|
|
5110
|
+
# confidently-wrong one.
|
|
5111
|
+
latest_ts = ""
|
|
5112
|
+
for _epf in episodes:
|
|
5113
|
+
try:
|
|
5114
|
+
_ts = json.loads(_epf.read_text()).get("timestamp", "")
|
|
5115
|
+
if isinstance(_ts, str) and _ts > latest_ts:
|
|
5116
|
+
latest_ts = _ts
|
|
5117
|
+
except Exception:
|
|
5118
|
+
continue
|
|
5119
|
+
summary["episodic"]["latestDate"] = latest_ts or None
|
|
5068
5120
|
|
|
5069
5121
|
sem_dir = memory_dir / "semantic"
|
|
5070
5122
|
patterns_file = sem_dir / "patterns.json"
|
|
@@ -5084,7 +5136,7 @@ async def get_memory_summary():
|
|
|
5084
5136
|
|
|
5085
5137
|
skills_dir = memory_dir / "skills"
|
|
5086
5138
|
if skills_dir.exists():
|
|
5087
|
-
summary["procedural"]["skills"] = len(
|
|
5139
|
+
summary["procedural"]["skills"] = len(_memory_skill_files(skills_dir))
|
|
5088
5140
|
|
|
5089
5141
|
econ_file = memory_dir / "token_economics.json"
|
|
5090
5142
|
if econ_file.exists():
|
|
@@ -5122,20 +5174,22 @@ async def list_episodes(limit: int = Query(default=50, ge=1, le=1000)):
|
|
|
5122
5174
|
except Exception:
|
|
5123
5175
|
pass
|
|
5124
5176
|
|
|
5125
|
-
# Fallback to JSON files
|
|
5126
|
-
|
|
5177
|
+
# Fallback to JSON files. Enumerate via the shared helper (recurses the
|
|
5178
|
+
# dated subdirs) so this list agrees with /api/memory/summary's count --
|
|
5179
|
+
# the flat glob here was the exact parity gap PR #178's review flagged
|
|
5180
|
+
# (summary said 28, this returned 0). Read each file, then return the
|
|
5181
|
+
# newest `limit` by RECORDED timestamp (not filename, whose intra-day
|
|
5182
|
+
# tiebreak is a hash, not a time).
|
|
5127
5183
|
ep_dir = _get_loki_dir() / "memory" / "episodic"
|
|
5128
|
-
|
|
5184
|
+
loaded = []
|
|
5129
5185
|
if ep_dir.exists():
|
|
5130
|
-
|
|
5131
|
-
# nlargest by filename (timestamps sort lexicographically) avoids full sort
|
|
5132
|
-
files = heapq.nlargest(limit, all_files, key=lambda f: f.name)
|
|
5133
|
-
for f in files:
|
|
5186
|
+
for f in _memory_episode_files(ep_dir):
|
|
5134
5187
|
try:
|
|
5135
|
-
|
|
5188
|
+
loaded.append(json.loads(f.read_text()))
|
|
5136
5189
|
except Exception:
|
|
5137
5190
|
pass
|
|
5138
|
-
|
|
5191
|
+
loaded.sort(key=lambda e: e.get("timestamp", "") if isinstance(e, dict) else "", reverse=True)
|
|
5192
|
+
return loaded[:limit]
|
|
5139
5193
|
|
|
5140
5194
|
return await asyncio.to_thread(_load_episodes)
|
|
5141
5195
|
|
|
@@ -5159,7 +5213,7 @@ async def get_episode(episode_id: str):
|
|
|
5159
5213
|
if not ep_dir.exists():
|
|
5160
5214
|
raise HTTPException(status_code=404, detail="Episode not found")
|
|
5161
5215
|
real_loki = os.path.realpath(str(loki_dir))
|
|
5162
|
-
for f in ep_dir
|
|
5216
|
+
for f in _memory_episode_files(ep_dir):
|
|
5163
5217
|
resolved = os.path.realpath(f)
|
|
5164
5218
|
if not resolved.startswith(real_loki + os.sep) and resolved != real_loki:
|
|
5165
5219
|
raise HTTPException(status_code=403, detail="Access denied")
|
|
@@ -5235,7 +5289,7 @@ async def list_skills():
|
|
|
5235
5289
|
skills_dir = _get_loki_dir() / "memory" / "skills"
|
|
5236
5290
|
skills = []
|
|
5237
5291
|
if skills_dir.exists():
|
|
5238
|
-
for f in
|
|
5292
|
+
for f in _memory_skill_files(skills_dir):
|
|
5239
5293
|
try:
|
|
5240
5294
|
skills.append(json.loads(f.read_text()))
|
|
5241
5295
|
except Exception:
|
|
@@ -5253,7 +5307,7 @@ async def get_skill(skill_id: str):
|
|
|
5253
5307
|
if not skills_dir.exists():
|
|
5254
5308
|
raise HTTPException(status_code=404, detail="Skill not found")
|
|
5255
5309
|
real_loki = os.path.realpath(str(loki_dir))
|
|
5256
|
-
for f in skills_dir
|
|
5310
|
+
for f in _memory_skill_files(skills_dir):
|
|
5257
5311
|
resolved = os.path.realpath(f)
|
|
5258
5312
|
if not resolved.startswith(real_loki + os.sep) and resolved != real_loki:
|
|
5259
5313
|
raise HTTPException(status_code=403, detail="Access denied")
|
|
@@ -5699,11 +5753,7 @@ async def get_memory_stats():
|
|
|
5699
5753
|
ep_count = 0
|
|
5700
5754
|
ep_dir = memory_dir / "episodic"
|
|
5701
5755
|
if ep_dir.exists():
|
|
5702
|
-
|
|
5703
|
-
if d.is_dir():
|
|
5704
|
-
ep_count += len(list(d.glob("*.json")))
|
|
5705
|
-
elif d.suffix == ".json":
|
|
5706
|
-
ep_count += 1
|
|
5756
|
+
ep_count = len(_memory_episode_files(ep_dir))
|
|
5707
5757
|
|
|
5708
5758
|
pat_count = 0
|
|
5709
5759
|
patterns_file = memory_dir / "semantic" / "patterns.json"
|
|
@@ -5717,7 +5767,7 @@ async def get_memory_stats():
|
|
|
5717
5767
|
skill_count = 0
|
|
5718
5768
|
skills_dir = memory_dir / "skills"
|
|
5719
5769
|
if skills_dir.exists():
|
|
5720
|
-
skill_count = len(
|
|
5770
|
+
skill_count = len(_memory_skill_files(skills_dir))
|
|
5721
5771
|
|
|
5722
5772
|
return {
|
|
5723
5773
|
"backend": "json",
|
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.104.
|
|
5
|
+
**Version:** v7.104.5
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var X8=Object.defineProperty;var K8=($)=>$;function q8($,Q){this[$]=K8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)X8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:q8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var v1={};b(v1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as r,dirname as i$}from"path";import{fileURLToPath as J8}from"url";import{existsSync as x$}from"fs";import{homedir as V8}from"os";function W8(){let $=y1;for(let Q=0;Q<6;Q++){if(x$(r($,"VERSION"))&&x$(r($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return r(y1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(x$(r(Q,"VERSION"))&&x$(r(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return r($,"..","..","..")}function P(){return process.env.LOKI_DIR??r(process.cwd(),".loki")}function T$(){return r(V8(),".loki")}var y1,h;var C=L(()=>{y1=i$(J8(import.meta.url));h=W8()});import{readFileSync as U8}from"fs";import{resolve as H8,dirname as G8}from"path";import{fileURLToPath as B8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.104.
|
|
2
|
+
var X8=Object.defineProperty;var K8=($)=>$;function q8($,Q){this[$]=K8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)X8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:q8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var v1={};b(v1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as r,dirname as i$}from"path";import{fileURLToPath as J8}from"url";import{existsSync as x$}from"fs";import{homedir as V8}from"os";function W8(){let $=y1;for(let Q=0;Q<6;Q++){if(x$(r($,"VERSION"))&&x$(r($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return r(y1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(x$(r(Q,"VERSION"))&&x$(r(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return r($,"..","..","..")}function P(){return process.env.LOKI_DIR??r(process.cwd(),".loki")}function T$(){return r(V8(),".loki")}var y1,h;var C=L(()=>{y1=i$(J8(import.meta.url));h=W8()});import{readFileSync as U8}from"fs";import{resolve as H8,dirname as G8}from"path";import{fileURLToPath as B8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.104.5";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=G8(B8(import.meta.url)),Z=e$(Q);Q$=U8(H8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var $1=L(()=>{C()});var c1={};b(c1,{runOrThrow:()=>x8,run:()=>F,readStreamCapped:()=>u1,commandVersion:()=>S8,commandExists:()=>f,ShellError:()=>Q1,MAX_STDOUT_BYTES:()=>f1});async function u1($,Q=f1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:W}=await Z.read();if(K)break;if(!W)continue;if(q+=W.byteLength,q>Q){let V=W.byteLength-(q-Q);X+=z.decode(W.subarray(0,V),{stream:!0});break}X+=z.decode(W,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function F($,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[q,K,W]=await Promise.all([u1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:W}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function x8($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new Q1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=N8($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function N8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function S8($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var f1=16777216,Q1;var d=L(()=>{Q1=class Q1 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 t($){return D8?"":$}var D8,T,S,_,nZ,I,k,y,J;var c=L(()=>{D8=(process.env.NO_COLOR??"").length>0;T=t("\x1B[0;31m"),S=t("\x1B[0;32m"),_=t("\x1B[1;33m"),nZ=t("\x1B[0;34m"),I=t("\x1B[0;36m"),k=t("\x1B[1m"),y=t("\x1B[2m"),J=t("\x1B[0m")});import{existsSync as c8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(c8($))return A$=$,$;let Q=await f("python3.12");if(Q)return A$=Q,Q;let Z=await f("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var A$;var V$=L(()=>{d()});var J0={};b(J0,{runStatus:()=>U3});import{existsSync as v,readFileSync as U$,readdirSync as e1,statSync as $0}from"fs";import{resolve as D,basename as Q3}from"path";import{homedir as Z3}from"os";function Q0($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function Z0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*D$/Q);if(X>D$)X=D$;let q=D$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=Q0($),U=Q0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${U})`}async function X3(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${J}
|
|
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)
|
|
@@ -805,4 +805,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
805
805
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
806
806
|
`),process.stderr.write(z8),2}}i1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var EZ=await RZ(Bun.argv.slice(2));process.exit(EZ);
|
|
807
807
|
|
|
808
|
-
//# debugId=
|
|
808
|
+
//# debugId=CD7A0B6FD83A195A64756E2164756E21
|
package/mcp/__init__.py
CHANGED
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.104.
|
|
4
|
+
"version": "7.104.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 8 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.104.
|
|
5
|
+
"version": "7.104.5",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 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",
|