loki-mode 7.45.0 → 7.45.1
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/registry.py +156 -62
- package/docs/INSTALLATION.md +2 -2
- 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, 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.45.
|
|
6
|
+
# Loki Mode v7.45.1
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -407,4 +407,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
407
407
|
|
|
408
408
|
---
|
|
409
409
|
|
|
410
|
-
**v7.45.
|
|
410
|
+
**v7.45.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.45.
|
|
1
|
+
7.45.1
|
package/dashboard/__init__.py
CHANGED
package/dashboard/registry.py
CHANGED
|
@@ -9,9 +9,11 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
11
|
import os
|
|
12
|
+
import tempfile
|
|
13
|
+
from contextlib import contextmanager
|
|
12
14
|
from datetime import datetime, timezone
|
|
13
15
|
from pathlib import Path
|
|
14
|
-
from typing import Optional
|
|
16
|
+
from typing import Iterator, Optional
|
|
15
17
|
import hashlib
|
|
16
18
|
|
|
17
19
|
|
|
@@ -25,6 +27,62 @@ def _ensure_registry_dir() -> None:
|
|
|
25
27
|
REGISTRY_DIR.mkdir(parents=True, exist_ok=True)
|
|
26
28
|
|
|
27
29
|
|
|
30
|
+
@contextmanager
|
|
31
|
+
def _registry_lock() -> Iterator[None]:
|
|
32
|
+
"""
|
|
33
|
+
Best-effort advisory lock around a read-modify-write of the registry.
|
|
34
|
+
|
|
35
|
+
Two concurrent writers (e.g. two `loki docker start` in different repos, or
|
|
36
|
+
a docker run racing a host `loki start`) would otherwise both load the old
|
|
37
|
+
registry, mutate, and save, dropping one writer's entry (lost update). This
|
|
38
|
+
serializes the leaf mutators so they take turns.
|
|
39
|
+
|
|
40
|
+
Degrades gracefully: if fcntl is unavailable (Windows) or the lock cannot
|
|
41
|
+
be acquired for any reason, execution proceeds without a lock rather than
|
|
42
|
+
blocking a build. The atomic write in _save_registry still guarantees no
|
|
43
|
+
reader ever sees a torn file; only the lost-update protection is
|
|
44
|
+
best-effort.
|
|
45
|
+
|
|
46
|
+
The lock path is derived from the current REGISTRY_DIR at call time (not a
|
|
47
|
+
module-level constant) so tests that monkeypatch REGISTRY_DIR stay
|
|
48
|
+
hermetic. Not reentrant: do not nest this around another leaf mutator (the
|
|
49
|
+
leaf mutators do not call one another).
|
|
50
|
+
"""
|
|
51
|
+
_ensure_registry_dir()
|
|
52
|
+
lock_fd = None
|
|
53
|
+
locked = False
|
|
54
|
+
try:
|
|
55
|
+
import fcntl # POSIX only; absent on Windows
|
|
56
|
+
|
|
57
|
+
lock_path = REGISTRY_DIR / ".registry.lock"
|
|
58
|
+
try:
|
|
59
|
+
lock_fd = os.open(str(lock_path), os.O_CREAT | os.O_RDWR, 0o644)
|
|
60
|
+
fcntl.flock(lock_fd, fcntl.LOCK_EX)
|
|
61
|
+
locked = True
|
|
62
|
+
except OSError:
|
|
63
|
+
# Could not open or lock the file; proceed without the lock.
|
|
64
|
+
locked = False
|
|
65
|
+
except ImportError:
|
|
66
|
+
# fcntl not available (e.g. Windows); proceed without the lock.
|
|
67
|
+
lock_fd = None
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
yield
|
|
71
|
+
finally:
|
|
72
|
+
if lock_fd is not None:
|
|
73
|
+
try:
|
|
74
|
+
if locked:
|
|
75
|
+
import fcntl
|
|
76
|
+
|
|
77
|
+
fcntl.flock(lock_fd, fcntl.LOCK_UN)
|
|
78
|
+
except (OSError, ImportError):
|
|
79
|
+
pass
|
|
80
|
+
try:
|
|
81
|
+
os.close(lock_fd)
|
|
82
|
+
except OSError:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
|
|
28
86
|
def _load_registry() -> dict:
|
|
29
87
|
"""Load the project registry from disk."""
|
|
30
88
|
_ensure_registry_dir()
|
|
@@ -38,10 +96,39 @@ def _load_registry() -> dict:
|
|
|
38
96
|
|
|
39
97
|
|
|
40
98
|
def _save_registry(registry: dict) -> None:
|
|
41
|
-
"""
|
|
99
|
+
"""
|
|
100
|
+
Save the project registry to disk atomically.
|
|
101
|
+
|
|
102
|
+
Writes to a temp file in the SAME directory as REGISTRY_FILE (so os.replace
|
|
103
|
+
is an atomic rename on the same filesystem), flushes and fsyncs it, then
|
|
104
|
+
os.replace()s it over the destination. Every reader therefore sees either
|
|
105
|
+
the complete old file or the complete new file, never a half-written (torn)
|
|
106
|
+
one. The temp file is removed on any error path so partial files never
|
|
107
|
+
leak.
|
|
108
|
+
|
|
109
|
+
Note: atomic write alone eliminates torn reads but does not by itself
|
|
110
|
+
prevent lost updates under true simultaneity. The leaf mutators wrap their
|
|
111
|
+
load->mutate->save in _registry_lock() to serialize concurrent writers and
|
|
112
|
+
reduce that window; when locking is unavailable the degradation is honest
|
|
113
|
+
(torn reads still impossible, lost-update still possible).
|
|
114
|
+
"""
|
|
42
115
|
_ensure_registry_dir()
|
|
43
|
-
|
|
44
|
-
|
|
116
|
+
tmp_fd, tmp_path = tempfile.mkstemp(
|
|
117
|
+
dir=str(REGISTRY_DIR), prefix=".projects.", suffix=".tmp"
|
|
118
|
+
)
|
|
119
|
+
try:
|
|
120
|
+
with os.fdopen(tmp_fd, "w") as f:
|
|
121
|
+
json.dump(registry, f, indent=2, default=str)
|
|
122
|
+
f.flush()
|
|
123
|
+
os.fsync(f.fileno())
|
|
124
|
+
os.replace(tmp_path, str(REGISTRY_FILE))
|
|
125
|
+
except BaseException:
|
|
126
|
+
# Clean up the temp file on any failure so we never leak partial files.
|
|
127
|
+
try:
|
|
128
|
+
os.unlink(tmp_path)
|
|
129
|
+
except OSError:
|
|
130
|
+
pass
|
|
131
|
+
raise
|
|
45
132
|
|
|
46
133
|
|
|
47
134
|
def _generate_project_id(path: str) -> str:
|
|
@@ -70,34 +157,38 @@ def register_project(
|
|
|
70
157
|
if not os.path.isdir(path):
|
|
71
158
|
raise ValueError(f"Path does not exist: {path}")
|
|
72
159
|
|
|
73
|
-
registry = _load_registry()
|
|
74
160
|
project_id = _generate_project_id(path)
|
|
75
161
|
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if name:
|
|
81
|
-
project["name"] = name
|
|
82
|
-
if alias:
|
|
83
|
-
project["alias"] = alias
|
|
84
|
-
project["updated_at"] = datetime.now(timezone.utc).isoformat()
|
|
85
|
-
else:
|
|
86
|
-
# Create new entry
|
|
87
|
-
project = {
|
|
88
|
-
"id": project_id,
|
|
89
|
-
"path": path,
|
|
90
|
-
"name": name or os.path.basename(path),
|
|
91
|
-
"alias": alias,
|
|
92
|
-
"registered_at": datetime.now(timezone.utc).isoformat(),
|
|
93
|
-
"updated_at": datetime.now(timezone.utc).isoformat(),
|
|
94
|
-
"last_accessed": None,
|
|
95
|
-
"has_loki_dir": os.path.isdir(os.path.join(path, ".loki")),
|
|
96
|
-
"status": "active",
|
|
97
|
-
}
|
|
98
|
-
registry["projects"][project_id] = project
|
|
162
|
+
# Lock the load->mutate->save so concurrent registrations serialize and do
|
|
163
|
+
# not lost-update each other (the multi-repo `loki docker` happy path).
|
|
164
|
+
with _registry_lock():
|
|
165
|
+
registry = _load_registry()
|
|
99
166
|
|
|
100
|
-
|
|
167
|
+
# Check if already registered
|
|
168
|
+
if project_id in registry["projects"]:
|
|
169
|
+
# Update existing entry
|
|
170
|
+
project = registry["projects"][project_id]
|
|
171
|
+
if name:
|
|
172
|
+
project["name"] = name
|
|
173
|
+
if alias:
|
|
174
|
+
project["alias"] = alias
|
|
175
|
+
project["updated_at"] = datetime.now(timezone.utc).isoformat()
|
|
176
|
+
else:
|
|
177
|
+
# Create new entry
|
|
178
|
+
project = {
|
|
179
|
+
"id": project_id,
|
|
180
|
+
"path": path,
|
|
181
|
+
"name": name or os.path.basename(path),
|
|
182
|
+
"alias": alias,
|
|
183
|
+
"registered_at": datetime.now(timezone.utc).isoformat(),
|
|
184
|
+
"updated_at": datetime.now(timezone.utc).isoformat(),
|
|
185
|
+
"last_accessed": None,
|
|
186
|
+
"has_loki_dir": os.path.isdir(os.path.join(path, ".loki")),
|
|
187
|
+
"status": "active",
|
|
188
|
+
}
|
|
189
|
+
registry["projects"][project_id] = project
|
|
190
|
+
|
|
191
|
+
_save_registry(registry)
|
|
101
192
|
return project
|
|
102
193
|
|
|
103
194
|
|
|
@@ -111,19 +202,20 @@ def unregister_project(identifier: str) -> bool:
|
|
|
111
202
|
Returns:
|
|
112
203
|
True if removed, False if not found
|
|
113
204
|
"""
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
205
|
+
with _registry_lock():
|
|
206
|
+
registry = _load_registry()
|
|
207
|
+
|
|
208
|
+
# Find by ID, path, or alias
|
|
209
|
+
project_id = None
|
|
210
|
+
for pid, project in registry["projects"].items():
|
|
211
|
+
if pid == identifier or project["path"] == identifier or project.get("alias") == identifier:
|
|
212
|
+
project_id = pid
|
|
213
|
+
break
|
|
214
|
+
|
|
215
|
+
if project_id:
|
|
216
|
+
del registry["projects"][project_id]
|
|
217
|
+
_save_registry(registry)
|
|
218
|
+
return True
|
|
127
219
|
return False
|
|
128
220
|
|
|
129
221
|
|
|
@@ -179,13 +271,14 @@ def update_last_accessed(identifier: str) -> Optional[dict]:
|
|
|
179
271
|
Returns:
|
|
180
272
|
Updated project entry or None
|
|
181
273
|
"""
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
project["
|
|
187
|
-
|
|
188
|
-
|
|
274
|
+
with _registry_lock():
|
|
275
|
+
registry = _load_registry()
|
|
276
|
+
|
|
277
|
+
for pid, project in registry["projects"].items():
|
|
278
|
+
if pid == identifier or project["path"] == identifier or project.get("alias") == identifier:
|
|
279
|
+
project["last_accessed"] = datetime.now(timezone.utc).isoformat()
|
|
280
|
+
_save_registry(registry)
|
|
281
|
+
return project
|
|
189
282
|
return None
|
|
190
283
|
|
|
191
284
|
|
|
@@ -207,19 +300,20 @@ def mark_project_stopped(identifier: str) -> Optional[dict]:
|
|
|
207
300
|
Idempotent: marking an already-stopped project is a no-op that still
|
|
208
301
|
returns the entry.
|
|
209
302
|
"""
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
303
|
+
with _registry_lock():
|
|
304
|
+
registry = _load_registry()
|
|
305
|
+
|
|
306
|
+
for pid_key, project in registry["projects"].items():
|
|
307
|
+
if (
|
|
308
|
+
pid_key == identifier
|
|
309
|
+
or project["path"] == identifier
|
|
310
|
+
or project.get("alias") == identifier
|
|
311
|
+
):
|
|
312
|
+
project["status"] = "stopped"
|
|
313
|
+
project["pid"] = None
|
|
314
|
+
project["updated_at"] = datetime.now(timezone.utc).isoformat()
|
|
315
|
+
_save_registry(registry)
|
|
316
|
+
return project
|
|
223
317
|
return None
|
|
224
318
|
|
|
225
319
|
|
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.45.
|
|
5
|
+
**Version:** v7.45.1
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -389,7 +389,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
|
|
|
389
389
|
# Run Loki Mode in Docker (Claude provider, API-key auth)
|
|
390
390
|
docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
|
|
391
391
|
-v $(pwd):/workspace -w /workspace \
|
|
392
|
-
asklokesh/loki-mode:7.45.
|
|
392
|
+
asklokesh/loki-mode:7.45.1 start ./my-spec.md
|
|
393
393
|
```
|
|
394
394
|
|
|
395
395
|
##### docker compose + .env (no host install)
|
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.45.
|
|
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.45.1";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=91DA611FDFEA183B64756E2164756E21
|
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.45.
|
|
4
|
+
"version": "7.45.1",
|
|
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.45.
|
|
5
|
+
"version": "7.45.1",
|
|
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",
|