loki-mode 8.77.0 → 8.79.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/memory/rag_injector.py +17 -2
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/loader.sh +12 -4
- package/providers/opencode.sh +12 -4
- package/skills/providers.md +25 -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 v8.
|
|
6
|
+
# Loki Mode v8.79.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.79.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
8.
|
|
1
|
+
8.79.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.79.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=54F89B4B63E616A664756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/memory/rag_injector.py
CHANGED
|
@@ -98,9 +98,24 @@ def build_rag_context(query, max_tokens=2000, knowledge_dir=None):
|
|
|
98
98
|
# Support both 'name'/'pattern' and 'description' fields.
|
|
99
99
|
# Every field is sanitized: memory entries are untrusted input and must
|
|
100
100
|
# not be able to inject instructions or break the prompt structure.
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
# `or` chaining, not .get(a, .get(b, default)): a present-but-empty
|
|
102
|
+
# 'description' would otherwise shadow a real 'correct_approach', and a
|
|
103
|
+
# row missing BOTH name keys would resolve to the literal string
|
|
104
|
+
# 'Unknown Pattern' -- truthy, so the substance check below could never
|
|
105
|
+
# fire on it.
|
|
106
|
+
name = _sanitize_field(p.get('name') or p.get('pattern') or '')
|
|
107
|
+
desc = _sanitize_field(p.get('description') or p.get('correct_approach') or '')
|
|
103
108
|
category = _sanitize_field(p.get('category', ''))
|
|
109
|
+
|
|
110
|
+
# Substance check: a pattern that matched only on its category (or
|
|
111
|
+
# whose fields sanitized away to nothing) has no memory to inject.
|
|
112
|
+
# Rendering it as "### Unknown Pattern" under the "patterns were found
|
|
113
|
+
# in the organization knowledge base" header presents a fabricated
|
|
114
|
+
# memory as a real one and burns prompt budget. Name OR description is
|
|
115
|
+
# enough -- category alone is a bucket label, not a memory, and a named
|
|
116
|
+
# pattern with no description is still genuine signal.
|
|
117
|
+
if not name and not desc:
|
|
118
|
+
continue
|
|
104
119
|
source_raw = p.get('_source_project', '')
|
|
105
120
|
# Path().name strips any directory traversal; sanitize the basename too.
|
|
106
121
|
source = _sanitize_field(Path(str(source_raw)).name) if source_raw else ''
|
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.79.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.79.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",
|
package/providers/loader.sh
CHANGED
|
@@ -76,10 +76,18 @@ validate_provider_config() {
|
|
|
76
76
|
# Variables that must be defined but can be empty string.
|
|
77
77
|
#
|
|
78
78
|
# PROVIDER_AUTONOMOUS_FLAG moved here in v8.2.0: not every CLI needs a flag
|
|
79
|
-
# to run non-interactively
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
79
|
+
# to run non-interactively, so requiring a non-empty value could reject a
|
|
80
|
+
# valid provider as "incomplete". The variable must still be DEFINED -- an
|
|
81
|
+
# author who forgets it entirely is still caught -- but an intentional empty
|
|
82
|
+
# value is legal.
|
|
83
|
+
#
|
|
84
|
+
# The original justification cited `opencode run <prompt>` as already
|
|
85
|
+
# autonomous. That was WRONG and cost opencode users every run: `opencode run
|
|
86
|
+
# --help` (1.18.9) shows `--auto ... [default: false]`, so without it the run
|
|
87
|
+
# blocks on a permission prompt. opencode now declares "--auto". Keep this
|
|
88
|
+
# list for a provider that genuinely needs no flag, but VERIFY against the
|
|
89
|
+
# real CLI's --help before adding one -- an empty value here is a silent hang,
|
|
90
|
+
# not a validation error.
|
|
83
91
|
local allow_empty_vars=(
|
|
84
92
|
PROVIDER_PROMPT_FLAG
|
|
85
93
|
PROVIDER_AUTONOMOUS_FLAG
|
package/providers/opencode.sh
CHANGED
|
@@ -46,7 +46,15 @@ PROVIDER_CLI="opencode"
|
|
|
46
46
|
# CLI Invocation
|
|
47
47
|
# `run` takes the prompt POSITIONALLY; there is no -p/--prompt flag.
|
|
48
48
|
PROVIDER_SUBCOMMAND="run"
|
|
49
|
-
|
|
49
|
+
# --auto is REQUIRED for autonomous operation. Verified against the real CLI
|
|
50
|
+
# (opencode 1.18.9): `opencode run --help` documents
|
|
51
|
+
# --auto auto-approve permissions that are not explicitly denied [default: false]
|
|
52
|
+
# The earlier empty value assumed `opencode run <prompt>` was already
|
|
53
|
+
# non-interactive. It is not: without --auto the run stalls on the first
|
|
54
|
+
# permission prompt with no TTY to answer it, which is indistinguishable from a
|
|
55
|
+
# hang. This is opencode's equivalent of claude's --dangerously-skip-permissions
|
|
56
|
+
# and aider's --yes-always, both of which their providers already pass.
|
|
57
|
+
PROVIDER_AUTONOMOUS_FLAG="--auto"
|
|
50
58
|
PROVIDER_PROMPT_FLAG=""
|
|
51
59
|
PROVIDER_PROMPT_POSITIONAL=true
|
|
52
60
|
|
|
@@ -123,7 +131,7 @@ provider_invoke() {
|
|
|
123
131
|
shift
|
|
124
132
|
[ -n "$prompt" ] || return 1
|
|
125
133
|
command -v opencode >/dev/null 2>&1 || return 127
|
|
126
|
-
opencode run --model "$PROVIDER_MODEL_DEVELOPMENT" "$prompt" "$@"
|
|
134
|
+
opencode run --auto --model "$PROVIDER_MODEL_DEVELOPMENT" "$prompt" "$@"
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
# provider_invoke_with_tier <tier> <prompt>
|
|
@@ -138,7 +146,7 @@ provider_invoke_with_tier() {
|
|
|
138
146
|
command -v opencode >/dev/null 2>&1 || return 127
|
|
139
147
|
local model
|
|
140
148
|
model="$(provider_get_tier_param "$tier")"
|
|
141
|
-
opencode run --model "$model" "$prompt" "$@"
|
|
149
|
+
opencode run --auto --model "$model" "$prompt" "$@"
|
|
142
150
|
}
|
|
143
151
|
|
|
144
152
|
# provider_invoke_argv <tier> <prompt> -- see providers/claude.sh for rationale.
|
|
@@ -147,5 +155,5 @@ provider_invoke_argv() {
|
|
|
147
155
|
local prompt="${2:-}"
|
|
148
156
|
local model
|
|
149
157
|
model="$(provider_get_tier_param "$tier")"
|
|
150
|
-
_LOKI_INVOKE_ARGV=(opencode run --model "$model" "$prompt")
|
|
158
|
+
_LOKI_INVOKE_ARGV=(opencode run --auto --model "$model" "$prompt")
|
|
151
159
|
}
|
package/skills/providers.md
CHANGED
|
@@ -22,15 +22,39 @@ Loki Mode supports four AI providers for autonomous execution.
|
|
|
22
22
|
|
|
23
23
|
## Provider Selection
|
|
24
24
|
|
|
25
|
+
**You do not have to choose one.** Since v8.64.0, leaving `LOKI_PROVIDER`
|
|
26
|
+
unset auto-detects the first installed provider in this priority order:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
claude > cline > codex > aider > opencode
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
[loki] provider: codex (auto-detected)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The order lives in `auto_detect_provider()` (`providers/loader.sh`), which is
|
|
37
|
+
the single authority. Any other list that names providers must agree with it --
|
|
38
|
+
a second list that had drifted was blocking opencode-only machines from
|
|
39
|
+
starting a build at all until v8.76.0.
|
|
40
|
+
|
|
41
|
+
Set it explicitly only when you want a provider other than the highest-priority
|
|
42
|
+
installed one. An explicit choice always wins, and is never silently
|
|
43
|
+
substituted: naming a provider that is not installed fails immediately with its
|
|
44
|
+
install command rather than quietly running a different model (v8.66.0).
|
|
45
|
+
|
|
25
46
|
```bash
|
|
26
47
|
# Via environment variable
|
|
27
|
-
export LOKI_PROVIDER=claude # or codex,
|
|
48
|
+
export LOKI_PROVIDER=claude # or cline, codex, aider, opencode
|
|
28
49
|
|
|
29
50
|
# Via CLI flag
|
|
30
51
|
./autonomy/run.sh --provider codex ./prd.md
|
|
31
52
|
loki start --provider cline ./prd.md
|
|
32
53
|
```
|
|
33
54
|
|
|
55
|
+
`loki doctor` prints which providers are installed and which one would be
|
|
56
|
+
auto-selected.
|
|
57
|
+
|
|
34
58
|
## Any Model, Any Provider (ANTHROPIC_BASE_URL)
|
|
35
59
|
|
|
36
60
|
Independent of the four CLI providers below. Loki speaks the Anthropic Messages
|