loki-mode 9.28.1 → 9.29.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/dashboard/audit.py +10 -2
- package/docs/AUDIT-CHAIN-THREAT-MODEL.md +91 -0
- 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 v9.
|
|
6
|
+
# Loki Mode v9.29.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -470,4 +470,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
470
470
|
|
|
471
471
|
---
|
|
472
472
|
|
|
473
|
-
**v9.
|
|
473
|
+
**v9.29.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.
|
|
1
|
+
9.29.0
|
package/dashboard/__init__.py
CHANGED
package/dashboard/audit.py
CHANGED
|
@@ -194,8 +194,16 @@ def _ensure_audit_dir() -> None:
|
|
|
194
194
|
def _compute_chain_hash(entry_json: str, prev_hash: str) -> str:
|
|
195
195
|
"""Compute a SHA-256 chain hash linking this entry to the previous one.
|
|
196
196
|
|
|
197
|
-
Each hash depends on the previous entry's hash,
|
|
198
|
-
|
|
197
|
+
Each hash depends on the previous entry's hash, so modifying an entry
|
|
198
|
+
invalidates every hash after it. That detects corruption and truncation.
|
|
199
|
+
|
|
200
|
+
It is NOT tamper-proof. This hash is unkeyed and the genesis is a constant
|
|
201
|
+
("0" * 64), so every input is available to anyone who can write the file:
|
|
202
|
+
an attacker can recompute a fully consistent chain over invented history.
|
|
203
|
+
Reproduced in docs/AUDIT-CHAIN-THREAT-MODEL.md. Closing it requires
|
|
204
|
+
something the writer cannot reproduce (an external witness, or a signature
|
|
205
|
+
over the tip with an off-machine key). Do not describe the output of this
|
|
206
|
+
function as tamper-proof.
|
|
199
207
|
"""
|
|
200
208
|
return hashlib.sha256((prev_hash + entry_json).encode("utf-8")).hexdigest()
|
|
201
209
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# What the audit hash chain does and does not prove
|
|
2
|
+
|
|
3
|
+
Loki's wedge is a receipt the buyer verifies without trusting us. That claim is
|
|
4
|
+
only worth making if we are precise about what the current chain actually
|
|
5
|
+
proves. This document states the limit, with a reproduction, because a
|
|
6
|
+
tamper-evidence claim that does not hold is worse than no claim: a buyer may
|
|
7
|
+
rely on it.
|
|
8
|
+
|
|
9
|
+
## Measured: the chain is re-forgeable
|
|
10
|
+
|
|
11
|
+
`src/audit/log.js` computes each entry hash as an unkeyed SHA-256 over public
|
|
12
|
+
fields, with a constant genesis:
|
|
13
|
+
|
|
14
|
+
- genesis is the literal string `GENESIS` (`src/audit/log.js:16`)
|
|
15
|
+
- `_computeHash` (`:127-134`) hashes `{seq,timestamp,who,what,where,why,metadata,previousHash}`
|
|
16
|
+
- every input to that hash is present in the file the attacker is editing
|
|
17
|
+
|
|
18
|
+
Nothing in the recipe is secret, so anyone who can write the log can recompute a
|
|
19
|
+
complete, internally consistent chain over invented history. Reproduced on
|
|
20
|
+
v9.28.1:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
honest verify : {"valid":true,"entries":2,"brokenAt":null,"error":null}
|
|
24
|
+
forged verify : {"valid":true,"entries":2,"brokenAt":null,"error":null}
|
|
25
|
+
forged contents : NEVER HAPPENED | ALSO FORGED
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The history was replaced wholesale and `verifyChain()` reported `valid: true`.
|
|
29
|
+
|
|
30
|
+
### The same holds for the dashboard chain
|
|
31
|
+
|
|
32
|
+
`dashboard/audit.py` is a separate implementation with the same property:
|
|
33
|
+
genesis is the constant `"0" * 64` (`:58`, `:115`), and `_compute_chain_hash`
|
|
34
|
+
(`:194-200`) is an unkeyed `sha256(prev_hash + entry_json)`. Its own docstring
|
|
35
|
+
at `:197` calls the result tamper-evident. Forged with the writer's exact recipe
|
|
36
|
+
(`json.dumps(entry, sort_keys=True, default=str)`, field `_integrity_hash`,
|
|
37
|
+
`:369-371`), `verify_log_integrity` reports:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
forged verdict: {'valid': True, 'entries_checked': 2, 'first_tampered_line': None, ...}
|
|
41
|
+
contents : NEVER HAPPENED | ALSO FORGED
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
One methodological note, because it nearly produced a false all-clear here: a
|
|
45
|
+
first forgery attempt using a guessed field name returned `valid: False` with
|
|
46
|
+
`entries_checked: 0`. That was the verifier rejecting the probe's SCHEMA, not
|
|
47
|
+
detecting tampering. A failed exploit can mean the probe was wrong rather than
|
|
48
|
+
the target is sound, and `entries_checked: 0` is the tell. Any future test of
|
|
49
|
+
this property must assert that entries were actually checked.
|
|
50
|
+
|
|
51
|
+
## What this means
|
|
52
|
+
|
|
53
|
+
The chain detects **accidental corruption and truncation**: a partial write, a
|
|
54
|
+
dropped line, a byte flip. Those are real failure modes and catching them has
|
|
55
|
+
value.
|
|
56
|
+
|
|
57
|
+
It does **not** detect a deliberate rewrite by anyone with write access to the
|
|
58
|
+
log, which is the threat an audit trail exists to address. Under the standard
|
|
59
|
+
audit-log threat model the writer is the adversary, and an unkeyed chain the
|
|
60
|
+
writer can recompute offers that adversary no obstacle.
|
|
61
|
+
|
|
62
|
+
Note the failure direction. A broken chain is strong evidence of a problem. An
|
|
63
|
+
intact chain is NOT evidence of integrity, because it is exactly what both an
|
|
64
|
+
honest run and a competent forgery produce. Absence of evidence is not evidence
|
|
65
|
+
of absence.
|
|
66
|
+
|
|
67
|
+
## What would actually close it
|
|
68
|
+
|
|
69
|
+
Tamper-evidence requires something the log's writer cannot reproduce. In
|
|
70
|
+
increasing order of strength:
|
|
71
|
+
|
|
72
|
+
1. **A keyed MAC** with a key the agent cannot read. Moves the problem to key
|
|
73
|
+
custody; on a developer laptop where the agent runs as the user, there is
|
|
74
|
+
often nowhere to put such a key that the agent cannot reach.
|
|
75
|
+
2. **An external witness**: periodically pin the chain tip somewhere append-only
|
|
76
|
+
that the agent cannot rewrite. `writeWitness` already exists at
|
|
77
|
+
`src/audit/crosslink.js:234` and has zero production callers.
|
|
78
|
+
3. **A signature over the tip** with a key held off the machine.
|
|
79
|
+
|
|
80
|
+
Only 2 and 3 survive an adversary who controls the machine, which is the case
|
|
81
|
+
that matters for a receipt a third party is meant to trust.
|
|
82
|
+
|
|
83
|
+
## Current honest claim
|
|
84
|
+
|
|
85
|
+
Until a witness or off-machine signature is wired, the supportable claim is:
|
|
86
|
+
|
|
87
|
+
> The audit log is hash-chained, which detects corruption and truncation. It is
|
|
88
|
+
> not tamper-proof against an adversary with write access to the log.
|
|
89
|
+
|
|
90
|
+
Do not describe the current chain as tamper-proof, tamper-resistant, or as
|
|
91
|
+
evidence a third party can rely on for integrity against a motivated writer.
|
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:** v9.
|
|
5
|
+
**Version:** v9.29.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var St=Object.create;var{getPrototypeOf:yt,defineProperty:jG,getOwnPropertyNames:bt}=Object;var ft=Object.prototype.hasOwnProperty;function _t($){return this[$]}var vt,ht,gt=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?vt??=new WeakMap:ht??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?St(yt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of bt($))if(!ft.call(J,q))jG(J,q,{get:_t.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var mt=($)=>$;function ut($,X){this[$]=mt.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:ut.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var YR={};B1(YR,{lokiDir:()=>h0,homeLokiDir:()=>HQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as dt}from"url";import{existsSync as Zq}from"fs";import{homedir as pt}from"os";function ct(){let $=VR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(VR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function HQ(){return h2(pt(),".loki")}var VR,L1;var k1=s(()=>{VR=LG(dt(import.meta.url));L1=ct()});import{readFileSync as lt}from"fs";import{resolve as it,dirname as at}from"path";import{fileURLToPath as ot}from"url";function j9(){if(n3!==null)return n3;let $="9.
|
|
2
|
+
var St=Object.create;var{getPrototypeOf:yt,defineProperty:jG,getOwnPropertyNames:bt}=Object;var ft=Object.prototype.hasOwnProperty;function _t($){return this[$]}var vt,ht,gt=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?vt??=new WeakMap:ht??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?St(yt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of bt($))if(!ft.call(J,q))jG(J,q,{get:_t.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var mt=($)=>$;function ut($,X){this[$]=mt.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:ut.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var YR={};B1(YR,{lokiDir:()=>h0,homeLokiDir:()=>HQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as dt}from"url";import{existsSync as Zq}from"fs";import{homedir as pt}from"os";function ct(){let $=VR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(VR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function HQ(){return h2(pt(),".loki")}var VR,L1;var k1=s(()=>{VR=LG(dt(import.meta.url));L1=ct()});import{readFileSync as lt}from"fs";import{resolve as it,dirname as at}from"path";import{fileURLToPath as ot}from"url";function j9(){if(n3!==null)return n3;let $="9.29.0";if(typeof $==="string"&&$.length>0)return n3=$,n3;try{let X=at(ot(import.meta.url)),Q=AG(X);n3=lt(it(Q,"VERSION"),"utf-8").trim()}catch{n3="unknown"}return n3}var n3=null;var Kq=s(()=>{k1()});var GR={};B1(GR,{runOrThrow:()=>Ue,run:()=>$1,readStreamCapped:()=>Jq,commandVersion:()=>We,commandExists:()=>g5,ShellError:()=>CG,MAX_STDOUT_BYTES:()=>WR});async function Jq($,X=WR){let Q=$.getReader(),z=new TextDecoder,Z="",K=0;try{while(K<X){let{done:J,value:q}=await Q.read();if(J)break;if(!q)continue;if(K+=q.byteLength,K>X){let V=q.byteLength-(K-X);Z+=z.decode(q.subarray(0,V),{stream:!0});break}Z+=z.decode(q,{stream:!0})}Z+=z.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return Z}async function $1($,X={}){let Q=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),z,Z;if(X.timeoutMs&&X.timeoutMs>0)z=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[K,J,q]=await Promise.all([Jq(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:K,stderr:J,exitCode:q}}finally{if(z)clearTimeout(z);if(Z)clearTimeout(Z)}}async function Ue($,X={}){let Q=await $1($,X);if(Q.exitCode!==0)throw new CG(`command failed (${Q.exitCode}): ${$.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function g5($){let X=He($),Q=await $1(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function He($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function We($,X="--version"){if(!await g5($))return null;let z=await $1([$,X],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var WR=16777216,CG;var y8=s(()=>{CG=class CG extends Error{message;exitCode;stdout;stderr;constructor($,X,Q,z){super($);this.message=$;this.exitCode=X;this.stdout=Q;this.stderr=z;this.name="ShellError"}}});function g2($){return Ge?"":$}var Ge,p0,$5,q1,L61,A1,f1,m5,r;var t7=s(()=>{Ge=(process.env.NO_COLOR??"").length>0;p0=g2("\x1B[0;31m"),$5=g2("\x1B[0;32m"),q1=g2("\x1B[1;33m"),L61=g2("\x1B[0;34m"),A1=g2("\x1B[0;36m"),f1=g2("\x1B[1m"),m5=g2("\x1B[2m"),r=g2("\x1B[0m")});import{existsSync as De}from"fs";async function Z2(){if(GQ!==void 0)return GQ;let $="/opt/homebrew/bin/python3.12";if(De($))return GQ=$,$;let X=await g5("python3.12");if(X)return GQ=X,X;let Q=await g5("python3");return GQ=Q,Q}async function I4($,X={}){let Q=await Z2();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return $1([Q,"-c",$],X)}var GQ;var m2=s(()=>{y8()});var yR={};B1(yR,{runStatus:()=>oe});import{existsSync as u5,readFileSync as A9,readdirSync as RR,statSync as IR}from"fs";import{resolve as A5,basename as ge}from"path";import{homedir as me}from"os";function wR($){let X=Math.trunc($);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 PR($,X,Q){if(X===0)return null;let z=Math.trunc($*100/X),Z=Math.trunc($*Vq/X);if(Z>Vq)Z=Vq;let K=Vq-Z,J=$5;if(z>=80)J=p0;else if(z>=50)J=q1;let q="=".repeat(Math.max(0,Z))+" ".repeat(Math.max(0,K)),V=wR($),Y=wR(X);return` ${f1}${Q}${r} ${J}[${q}]${r} ${z}% (${V} / ${Y})`}async function de(){if(await g5("jq"))return!0;return process.stdout.write(`${p0}Error: jq is required but not installed.${r}
|
|
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)
|
|
@@ -1334,4 +1334,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
1334
1334
|
`),2}case"start":{let{runStart:z}=await Promise.resolve().then(() => (Et(),Pt));return z(Q)}default:return process.stderr.write(`Unknown command: ${X}
|
|
1335
1335
|
`),process.stderr.write(xt),2}}DR();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var X61=await $61(Bun.argv.slice(2));process.exit(X61);
|
|
1336
1336
|
|
|
1337
|
-
//# debugId=
|
|
1337
|
+
//# debugId=AC850A55189112371E3E6B205BFE8EF6
|
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": "9.
|
|
4
|
+
"version": "9.29.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, opencode).",
|
|
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": "9.
|
|
5
|
+
"version": "9.29.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",
|