loki-mode 8.90.0 → 8.91.0
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/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/tools/tool-index.py +188 -0
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 v8.
|
|
6
|
+
# Loki Mode v8.91.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -469,4 +469,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
469
469
|
|
|
470
470
|
---
|
|
471
471
|
|
|
472
|
-
**v8.
|
|
472
|
+
**v8.91.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
8.
|
|
1
|
+
8.91.0
|
package/dashboard/__init__.py
CHANGED
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var m_=Object.create;var{getPrototypeOf:u_,defineProperty:eK,getOwnPropertyNames:p_}=Object;var d_=Object.prototype.hasOwnProperty;function c_(Z){return this[Z]}var l_,i_,a_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?l_??=new WeakMap:i_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?m_(u_(Z)):{};let K=X||!Z||!Z.__esModule?eK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of p_(Z))if(!d_.call(K,$))eK(K,$,{get:c_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var HQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var s_=(Z)=>Z;function n_(Z,X){this[Z]=s_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)eK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:n_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var e0=import.meta.require;var kO={};l0(kO,{lokiDir:()=>j0,homeLokiDir:()=>R4,findRepoRootForVersion:()=>X$,REPO_ROOT:()=>i0});import{resolve as n7,dirname as Z$}from"path";import{fileURLToPath as o_}from"url";import{existsSync as UQ}from"fs";import{homedir as r_}from"os";function t_(){let Z=RO;for(let X=0;X<6;X++){if(UQ(n7(Z,"VERSION"))&&UQ(n7(Z,"autonomy/run.sh")))return Z;let Q=Z$(Z);if(Q===Z)break;Z=Q}return n7(RO,"..","..","..")}function X$(Z){let X=Z;for(let Q=0;Q<6;Q++){if(UQ(n7(X,"VERSION"))&&UQ(n7(X,"autonomy/run.sh")))return X;let Y=Z$(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function R4(){return n7(r_(),".loki")}var RO,i0;var H8=p(()=>{RO=Z$(o_(import.meta.url));i0=t_()});import{readFileSync as e_}from"fs";import{resolve as Zf,dirname as Xf}from"path";import{fileURLToPath as Qf}from"url";function h3(){if(h5!==null)return h5;let Z="8.
|
|
2
|
+
var m_=Object.create;var{getPrototypeOf:u_,defineProperty:eK,getOwnPropertyNames:p_}=Object;var d_=Object.prototype.hasOwnProperty;function c_(Z){return this[Z]}var l_,i_,a_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?l_??=new WeakMap:i_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?m_(u_(Z)):{};let K=X||!Z||!Z.__esModule?eK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of p_(Z))if(!d_.call(K,$))eK(K,$,{get:c_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var HQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var s_=(Z)=>Z;function n_(Z,X){this[Z]=s_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)eK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:n_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var e0=import.meta.require;var kO={};l0(kO,{lokiDir:()=>j0,homeLokiDir:()=>R4,findRepoRootForVersion:()=>X$,REPO_ROOT:()=>i0});import{resolve as n7,dirname as Z$}from"path";import{fileURLToPath as o_}from"url";import{existsSync as UQ}from"fs";import{homedir as r_}from"os";function t_(){let Z=RO;for(let X=0;X<6;X++){if(UQ(n7(Z,"VERSION"))&&UQ(n7(Z,"autonomy/run.sh")))return Z;let Q=Z$(Z);if(Q===Z)break;Z=Q}return n7(RO,"..","..","..")}function X$(Z){let X=Z;for(let Q=0;Q<6;Q++){if(UQ(n7(X,"VERSION"))&&UQ(n7(X,"autonomy/run.sh")))return X;let Y=Z$(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function R4(){return n7(r_(),".loki")}var RO,i0;var H8=p(()=>{RO=Z$(o_(import.meta.url));i0=t_()});import{readFileSync as e_}from"fs";import{resolve as Zf,dirname as Xf}from"path";import{fileURLToPath as Qf}from"url";function h3(){if(h5!==null)return h5;let Z="8.91.0";if(typeof Z==="string"&&Z.length>0)return h5=Z,h5;try{let X=Xf(Qf(import.meta.url)),Q=X$(X);h5=e_(Zf(Q,"VERSION"),"utf-8").trim()}catch{h5="unknown"}return h5}var h5=null;var BQ=p(()=>{H8()});var bO={};l0(bO,{runOrThrow:()=>jf,run:()=>E0,readStreamCapped:()=>NQ,commandVersion:()=>Tf,commandExists:()=>X9,ShellError:()=>Q$,MAX_STDOUT_BYTES:()=>yO});async function NQ(Z,X=yO){let Q=Z.getReader(),Y=new TextDecoder,J="",z=0;try{while(z<X){let{done:K,value:$}=await Q.read();if(K)break;if(!$)continue;if(z+=$.byteLength,z>X){let W=$.byteLength-(z-X);J+=Y.decode($.subarray(0,W),{stream:!0});break}J+=Y.decode($,{stream:!0})}J+=Y.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return J}async function E0(Z,X={}){let Q=Bun.spawn({cmd:[...Z],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),Y,J;if(X.timeoutMs&&X.timeoutMs>0)Y=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}J=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[z,K,$]=await Promise.all([NQ(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:z,stderr:K,exitCode:$}}finally{if(Y)clearTimeout(Y);if(J)clearTimeout(J)}}async function jf(Z,X={}){let Q=await E0(Z,X);if(Q.exitCode!==0)throw new Q$(`command failed (${Q.exitCode}): ${Z.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function X9(Z){let X=Mf(Z),Q=await E0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Mf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function Tf(Z,X="--version"){if(!await X9(Z))return null;let Y=await E0([Z,X],{timeoutMs:5000});if(Y.exitCode!==0)return null;return((Y.stdout||Y.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var yO=16777216,Q$;var x9=p(()=>{Q$=class Q$ extends Error{message;exitCode;stdout;stderr;constructor(Z,X,Q,Y){super(Z);this.message=Z;this.exitCode=X;this.stdout=Q;this.stderr=Y;this.name="ShellError"}}});function o7(Z){return wf?"":Z}var wf,L0,F8,p0,zV0,a0,W8,Q9,v;var S6=p(()=>{wf=(process.env.NO_COLOR??"").length>0;L0=o7("\x1B[0;31m"),F8=o7("\x1B[0;32m"),p0=o7("\x1B[1;33m"),zV0=o7("\x1B[0;34m"),a0=o7("\x1B[0;36m"),W8=o7("\x1B[1m"),Q9=o7("\x1B[2m"),v=o7("\x1B[0m")});import{existsSync as bf}from"fs";async function E7(){if(x4!==void 0)return x4;let Z="/opt/homebrew/bin/python3.12";if(bf(Z))return x4=Z,Z;let X=await X9("python3.12");if(X)return x4=X,X;let Q=await X9("python3");return x4=Q,Q}async function Y7(Z,X={}){let Q=await E7();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return E0([Q,"-c",Z],X)}var x4;var r7=p(()=>{x9()});var ZL={};l0(ZL,{runStatus:()=>Kh});import{existsSync as Y9,readFileSync as g3,readdirSync as iO,statSync as aO}from"fs";import{resolve as h8,basename as rf}from"path";import{homedir as tf}from"os";function sO(Z){let X=Math.trunc(Z);if(X>=1e6)return`${(Math.trunc(X/1e6*10)/10).toFixed(1)}M`;if(X>=1000)return`${(Math.trunc(X/1000*10)/10).toFixed(1)}K`;return String(X)}function nO(Z,X,Q){if(X===0)return null;let Y=Math.trunc(Z*100/X),J=Math.trunc(Z*LQ/X);if(J>LQ)J=LQ;let z=LQ-J,K=F8;if(Y>=80)K=L0;else if(Y>=50)K=p0;let $="=".repeat(Math.max(0,J))+" ".repeat(Math.max(0,z)),W=sO(Z),V=sO(X);return` ${W8}${Q}${v} ${K}[${$}]${v} ${Y}% (${W} / ${V})`}async function Zh(){if(await X9("jq"))return!0;return process.stdout.write(`${L0}Error: jq is required but not installed.${v}
|
|
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)
|
|
@@ -1232,4 +1232,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
1232
1232
|
`),2}case"start":{let{runStart:Y}=await Promise.resolve().then(() => (h_(),f_));return Y(Q)}default:return process.stderr.write(`Unknown command: ${X}
|
|
1233
1233
|
`),process.stderr.write(v_),2}}cO();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var uW0=await mW0(Bun.argv.slice(2));process.exit(uW0);
|
|
1234
1234
|
|
|
1235
|
-
//# debugId=
|
|
1235
|
+
//# debugId=8791DC1CFDEF673D64756E2164756E21
|
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": "8.
|
|
4
|
+
"version": "8.91.0",
|
|
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": "8.
|
|
5
|
+
"version": "8.91.0",
|
|
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",
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""List the tools that ship with loki, and say which ones a user can reach.
|
|
3
|
+
|
|
4
|
+
WHY THIS EXISTS. Eleven tools live under tools/ and NONE of them appears in
|
|
5
|
+
`loki help`. Every one was built, tested, packaged -- and left undiscoverable.
|
|
6
|
+
That is the same shape this repo has now paid for seven times: auto-detection
|
|
7
|
+
sat unwired from v5.0.0 to v8.64.0; the receipt's own `--human` explanation
|
|
8
|
+
shipped in v8.73.0 and reached no CLI path until v8.84.0; `preflight.sh` was
|
|
9
|
+
released in v8.87.0 into a directory that is not in package.json `files[]`, so
|
|
10
|
+
no npm user could run it at all.
|
|
11
|
+
|
|
12
|
+
A capability nobody can find is worth exactly as much as one that does not
|
|
13
|
+
exist. This is the cheapest possible fix for that class: a single honest index.
|
|
14
|
+
|
|
15
|
+
THE HONESTY RULES, which this repo has spent 13+ surfaces establishing and
|
|
16
|
+
which apply to an index just as much as to a cost figure:
|
|
17
|
+
|
|
18
|
+
- A tool with no description reads "(no description)", never an invented one.
|
|
19
|
+
Guessing a summary from a filename is fabrication; the reader cannot tell a
|
|
20
|
+
guess from a fact, so the guess is worse than the gap.
|
|
21
|
+
|
|
22
|
+
- SHIPPED status is derived from package.json `files[]`, the same source npm
|
|
23
|
+
uses. A tool present in the repo but absent from the tarball is reported as
|
|
24
|
+
NOT SHIPPED rather than silently listed alongside the reachable ones. That
|
|
25
|
+
is exactly the v8.87.0 defect, and an index that hid it would have helped
|
|
26
|
+
the release lie.
|
|
27
|
+
|
|
28
|
+
- An empty tools/ directory says so and exits non-zero. Zero tools is not a
|
|
29
|
+
successful listing.
|
|
30
|
+
|
|
31
|
+
Reads the filesystem only. Starts nothing, spends nothing, contacts nothing.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
import argparse
|
|
35
|
+
import ast
|
|
36
|
+
import json
|
|
37
|
+
import os
|
|
38
|
+
import sys
|
|
39
|
+
|
|
40
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
41
|
+
_ROOT = os.path.dirname(_HERE)
|
|
42
|
+
|
|
43
|
+
NO_DESC = "(no description)"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _first_sentence(text):
|
|
47
|
+
"""First line of a docstring or header block, trimmed.
|
|
48
|
+
|
|
49
|
+
Deliberately does NOT reflow or summarise: the author's own first line is
|
|
50
|
+
reported verbatim, so nothing here can invent a claim the tool does not make.
|
|
51
|
+
"""
|
|
52
|
+
for raw in (text or "").splitlines():
|
|
53
|
+
line = raw.strip()
|
|
54
|
+
if line:
|
|
55
|
+
return line if len(line) <= 100 else line[:97] + "..."
|
|
56
|
+
return ""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def describe(path):
|
|
60
|
+
"""Pull a one-line description from a tool, or report its absence.
|
|
61
|
+
|
|
62
|
+
Python: the module docstring, parsed rather than regexed, so a `#` inside a
|
|
63
|
+
string literal cannot be mistaken for a comment.
|
|
64
|
+
Shell: the first comment block under the shebang.
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
with open(path, "r", encoding="utf-8", errors="replace") as fh:
|
|
68
|
+
src = fh.read()
|
|
69
|
+
except OSError:
|
|
70
|
+
return NO_DESC
|
|
71
|
+
|
|
72
|
+
if path.endswith(".py"):
|
|
73
|
+
try:
|
|
74
|
+
doc = ast.get_docstring(ast.parse(src))
|
|
75
|
+
except SyntaxError:
|
|
76
|
+
doc = None
|
|
77
|
+
return _first_sentence(doc) or NO_DESC
|
|
78
|
+
|
|
79
|
+
lines = []
|
|
80
|
+
for raw in src.splitlines():
|
|
81
|
+
if raw.startswith("#!"):
|
|
82
|
+
continue
|
|
83
|
+
if raw.startswith("#"):
|
|
84
|
+
lines.append(raw.lstrip("#").strip())
|
|
85
|
+
continue
|
|
86
|
+
if lines:
|
|
87
|
+
break
|
|
88
|
+
if raw.strip():
|
|
89
|
+
break
|
|
90
|
+
return _first_sentence("\n".join(lines)) or NO_DESC
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _files_globs():
|
|
94
|
+
"""The `files[]` entries from package.json, or None when unreadable.
|
|
95
|
+
|
|
96
|
+
None means UNKNOWN, never "nothing ships": an unreadable manifest is an
|
|
97
|
+
absent measurement, and reporting every tool as unshipped on that basis
|
|
98
|
+
would be its own fabrication.
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
with open(os.path.join(_ROOT, "package.json"), encoding="utf-8") as fh:
|
|
102
|
+
return json.load(fh).get("files") or []
|
|
103
|
+
except (OSError, ValueError):
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def ships(rel, globs):
|
|
108
|
+
"""True/False/None -- shipped, not shipped, or manifest unknown."""
|
|
109
|
+
if globs is None:
|
|
110
|
+
return None
|
|
111
|
+
for entry in globs:
|
|
112
|
+
if rel == entry:
|
|
113
|
+
return True
|
|
114
|
+
if entry.endswith("/") and rel.startswith(entry):
|
|
115
|
+
return True
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def collect(tools_dir=None):
|
|
120
|
+
tools_dir = tools_dir or os.path.join(_ROOT, "tools")
|
|
121
|
+
globs = _files_globs()
|
|
122
|
+
out = []
|
|
123
|
+
try:
|
|
124
|
+
names = sorted(os.listdir(tools_dir))
|
|
125
|
+
except OSError:
|
|
126
|
+
return out
|
|
127
|
+
for name in names:
|
|
128
|
+
if not (name.endswith(".py") or name.endswith(".sh")):
|
|
129
|
+
continue
|
|
130
|
+
full = os.path.join(tools_dir, name)
|
|
131
|
+
if not os.path.isfile(full):
|
|
132
|
+
continue
|
|
133
|
+
out.append({
|
|
134
|
+
"name": name,
|
|
135
|
+
"path": "tools/" + name,
|
|
136
|
+
"description": describe(full),
|
|
137
|
+
"shipped": ships("tools/" + name, globs),
|
|
138
|
+
})
|
|
139
|
+
return out
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def render(rows):
|
|
143
|
+
lines = ["loki tools", ""]
|
|
144
|
+
width = max([len(r["name"]) for r in rows] + [4])
|
|
145
|
+
for r in rows:
|
|
146
|
+
if r["shipped"] is True:
|
|
147
|
+
mark = " "
|
|
148
|
+
elif r["shipped"] is False:
|
|
149
|
+
mark = "[dev]"
|
|
150
|
+
else:
|
|
151
|
+
mark = "[ ? ]"
|
|
152
|
+
lines.append(" %s %-*s %s" % (mark, width, r["name"], r["description"]))
|
|
153
|
+
lines.append("")
|
|
154
|
+
unshipped = [r["name"] for r in rows if r["shipped"] is False]
|
|
155
|
+
if unshipped:
|
|
156
|
+
lines.append("[dev] = present in this checkout but NOT in the npm package")
|
|
157
|
+
lines.append(" (not in package.json files[]): " + ", ".join(unshipped))
|
|
158
|
+
if any(r["shipped"] is None for r in rows):
|
|
159
|
+
lines.append("[ ? ] = package.json could not be read, so shipping is UNKNOWN")
|
|
160
|
+
lines.append("Run one with: python3 tools/<name> (this index reads files only)")
|
|
161
|
+
return "\n".join(lines)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def main(argv=None):
|
|
165
|
+
ap = argparse.ArgumentParser(description="List loki's bundled tools.")
|
|
166
|
+
ap.add_argument("--json", action="store_true")
|
|
167
|
+
ap.add_argument("--tools-dir", default=None)
|
|
168
|
+
args = ap.parse_args(argv)
|
|
169
|
+
|
|
170
|
+
rows = collect(args.tools_dir)
|
|
171
|
+
if not rows:
|
|
172
|
+
# Zero tools is not a successful listing.
|
|
173
|
+
msg = "no tools found under tools/ -- nothing to list"
|
|
174
|
+
if args.json:
|
|
175
|
+
print(json.dumps({"tools": [], "error": msg}, indent=2))
|
|
176
|
+
else:
|
|
177
|
+
print(msg, file=sys.stderr)
|
|
178
|
+
return 2
|
|
179
|
+
|
|
180
|
+
if args.json:
|
|
181
|
+
print(json.dumps({"tools": rows, "count": len(rows)}, indent=2))
|
|
182
|
+
else:
|
|
183
|
+
print(render(rows))
|
|
184
|
+
return 0
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
if __name__ == "__main__":
|
|
188
|
+
sys.exit(main())
|