loki-mode 7.104.3 → 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 +119 -53
- 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
|
@@ -1784,6 +1784,15 @@ async def list_tasks(
|
|
|
1784
1784
|
try:
|
|
1785
1785
|
state = json.loads(state_file.read_text())
|
|
1786
1786
|
task_groups = state.get("tasks", {})
|
|
1787
|
+
# v7.104.4: dashboard-state.json is user/agent-written and can be
|
|
1788
|
+
# malformed or partially written. If "tasks" is not a dict, or a
|
|
1789
|
+
# group is not a list, or an item is not a dict, the old code raised
|
|
1790
|
+
# AttributeError (task_groups.get / task.get) which is NOT caught by
|
|
1791
|
+
# the surrounding (JSONDecodeError, KeyError) handler -> the whole
|
|
1792
|
+
# /api/tasks request 500s and the board goes blank. Coerce defensively
|
|
1793
|
+
# so a bad shape degrades to "skip that part", never a crash.
|
|
1794
|
+
if not isinstance(task_groups, dict):
|
|
1795
|
+
task_groups = {}
|
|
1787
1796
|
|
|
1788
1797
|
status_map = {
|
|
1789
1798
|
"pending": "pending",
|
|
@@ -1794,9 +1803,16 @@ async def list_tasks(
|
|
|
1794
1803
|
}
|
|
1795
1804
|
|
|
1796
1805
|
for group_key, mapped_status in status_map.items():
|
|
1797
|
-
|
|
1806
|
+
_group = task_groups.get(group_key, [])
|
|
1807
|
+
if not isinstance(_group, list):
|
|
1808
|
+
continue # malformed group (e.g. a dict) -> skip, don't crash
|
|
1809
|
+
for i, task in enumerate(_group):
|
|
1810
|
+
if not isinstance(task, dict):
|
|
1811
|
+
continue # malformed entry (string/None) -> skip
|
|
1798
1812
|
task_id = task.get("id", f"{group_key}-{i}")
|
|
1799
1813
|
payload = task.get("payload", {})
|
|
1814
|
+
if not isinstance(payload, dict):
|
|
1815
|
+
payload = {} # non-dict payload -> ignore, don't crash on payload.get
|
|
1800
1816
|
# v7.7.32: read enrichment fields from the TOP LEVEL of the
|
|
1801
1817
|
# task object (where run.sh writes them), falling back to
|
|
1802
1818
|
# payload only for legacy entries. Previously description was
|
|
@@ -4991,41 +5007,79 @@ def _sanitize_agent_id(agent_id: str) -> str:
|
|
|
4991
5007
|
)
|
|
4992
5008
|
return agent_id
|
|
4993
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
|
+
|
|
4994
5036
|
@app.get("/api/memory/summary", dependencies=[Depends(auth.require_scope("read"))])
|
|
4995
5037
|
async def get_memory_summary():
|
|
4996
5038
|
"""Get memory system summary from .loki/memory/."""
|
|
4997
|
-
# 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.
|
|
4998
5046
|
storage = _get_memory_storage()
|
|
4999
5047
|
if storage is not None:
|
|
5000
5048
|
try:
|
|
5001
5049
|
stats = storage.get_stats()
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
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:
|
|
5025
5081
|
summary["tokenEconomics"] = {"discoveryTokens": 0, "readTokens": 0, "savingsPercent": 0}
|
|
5026
|
-
|
|
5027
|
-
summary["tokenEconomics"] = {"discoveryTokens": 0, "readTokens": 0, "savingsPercent": 0}
|
|
5028
|
-
return summary
|
|
5082
|
+
return summary
|
|
5029
5083
|
except Exception:
|
|
5030
5084
|
pass
|
|
5031
5085
|
|
|
@@ -5041,14 +5095,28 @@ async def get_memory_summary():
|
|
|
5041
5095
|
|
|
5042
5096
|
ep_dir = memory_dir / "episodic"
|
|
5043
5097
|
if ep_dir.exists():
|
|
5044
|
-
|
|
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)
|
|
5045
5103
|
summary["episodic"]["count"] = len(episodes)
|
|
5046
5104
|
if episodes:
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
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
|
|
5052
5120
|
|
|
5053
5121
|
sem_dir = memory_dir / "semantic"
|
|
5054
5122
|
patterns_file = sem_dir / "patterns.json"
|
|
@@ -5068,7 +5136,7 @@ async def get_memory_summary():
|
|
|
5068
5136
|
|
|
5069
5137
|
skills_dir = memory_dir / "skills"
|
|
5070
5138
|
if skills_dir.exists():
|
|
5071
|
-
summary["procedural"]["skills"] = len(
|
|
5139
|
+
summary["procedural"]["skills"] = len(_memory_skill_files(skills_dir))
|
|
5072
5140
|
|
|
5073
5141
|
econ_file = memory_dir / "token_economics.json"
|
|
5074
5142
|
if econ_file.exists():
|
|
@@ -5106,20 +5174,22 @@ async def list_episodes(limit: int = Query(default=50, ge=1, le=1000)):
|
|
|
5106
5174
|
except Exception:
|
|
5107
5175
|
pass
|
|
5108
5176
|
|
|
5109
|
-
# Fallback to JSON files
|
|
5110
|
-
|
|
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).
|
|
5111
5183
|
ep_dir = _get_loki_dir() / "memory" / "episodic"
|
|
5112
|
-
|
|
5184
|
+
loaded = []
|
|
5113
5185
|
if ep_dir.exists():
|
|
5114
|
-
|
|
5115
|
-
# nlargest by filename (timestamps sort lexicographically) avoids full sort
|
|
5116
|
-
files = heapq.nlargest(limit, all_files, key=lambda f: f.name)
|
|
5117
|
-
for f in files:
|
|
5186
|
+
for f in _memory_episode_files(ep_dir):
|
|
5118
5187
|
try:
|
|
5119
|
-
|
|
5188
|
+
loaded.append(json.loads(f.read_text()))
|
|
5120
5189
|
except Exception:
|
|
5121
5190
|
pass
|
|
5122
|
-
|
|
5191
|
+
loaded.sort(key=lambda e: e.get("timestamp", "") if isinstance(e, dict) else "", reverse=True)
|
|
5192
|
+
return loaded[:limit]
|
|
5123
5193
|
|
|
5124
5194
|
return await asyncio.to_thread(_load_episodes)
|
|
5125
5195
|
|
|
@@ -5143,7 +5213,7 @@ async def get_episode(episode_id: str):
|
|
|
5143
5213
|
if not ep_dir.exists():
|
|
5144
5214
|
raise HTTPException(status_code=404, detail="Episode not found")
|
|
5145
5215
|
real_loki = os.path.realpath(str(loki_dir))
|
|
5146
|
-
for f in ep_dir
|
|
5216
|
+
for f in _memory_episode_files(ep_dir):
|
|
5147
5217
|
resolved = os.path.realpath(f)
|
|
5148
5218
|
if not resolved.startswith(real_loki + os.sep) and resolved != real_loki:
|
|
5149
5219
|
raise HTTPException(status_code=403, detail="Access denied")
|
|
@@ -5219,7 +5289,7 @@ async def list_skills():
|
|
|
5219
5289
|
skills_dir = _get_loki_dir() / "memory" / "skills"
|
|
5220
5290
|
skills = []
|
|
5221
5291
|
if skills_dir.exists():
|
|
5222
|
-
for f in
|
|
5292
|
+
for f in _memory_skill_files(skills_dir):
|
|
5223
5293
|
try:
|
|
5224
5294
|
skills.append(json.loads(f.read_text()))
|
|
5225
5295
|
except Exception:
|
|
@@ -5237,7 +5307,7 @@ async def get_skill(skill_id: str):
|
|
|
5237
5307
|
if not skills_dir.exists():
|
|
5238
5308
|
raise HTTPException(status_code=404, detail="Skill not found")
|
|
5239
5309
|
real_loki = os.path.realpath(str(loki_dir))
|
|
5240
|
-
for f in skills_dir
|
|
5310
|
+
for f in _memory_skill_files(skills_dir):
|
|
5241
5311
|
resolved = os.path.realpath(f)
|
|
5242
5312
|
if not resolved.startswith(real_loki + os.sep) and resolved != real_loki:
|
|
5243
5313
|
raise HTTPException(status_code=403, detail="Access denied")
|
|
@@ -5683,11 +5753,7 @@ async def get_memory_stats():
|
|
|
5683
5753
|
ep_count = 0
|
|
5684
5754
|
ep_dir = memory_dir / "episodic"
|
|
5685
5755
|
if ep_dir.exists():
|
|
5686
|
-
|
|
5687
|
-
if d.is_dir():
|
|
5688
|
-
ep_count += len(list(d.glob("*.json")))
|
|
5689
|
-
elif d.suffix == ".json":
|
|
5690
|
-
ep_count += 1
|
|
5756
|
+
ep_count = len(_memory_episode_files(ep_dir))
|
|
5691
5757
|
|
|
5692
5758
|
pat_count = 0
|
|
5693
5759
|
patterns_file = memory_dir / "semantic" / "patterns.json"
|
|
@@ -5701,7 +5767,7 @@ async def get_memory_stats():
|
|
|
5701
5767
|
skill_count = 0
|
|
5702
5768
|
skills_dir = memory_dir / "skills"
|
|
5703
5769
|
if skills_dir.exists():
|
|
5704
|
-
skill_count = len(
|
|
5770
|
+
skill_count = len(_memory_skill_files(skills_dir))
|
|
5705
5771
|
|
|
5706
5772
|
return {
|
|
5707
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",
|