loki-mode 9.31.0 → 9.33.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/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/web-app/server.py +66 -7
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.33.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.33.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.
|
|
1
|
+
9.33.0
|
package/dashboard/__init__.py
CHANGED
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.33.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.33.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=1FFE037BDC42A60DE15D378B00B3ED8F
|
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.33.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.33.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/web-app/server.py
CHANGED
|
@@ -7776,15 +7776,74 @@ class CreateTeamRequest(BaseModel):
|
|
|
7776
7776
|
|
|
7777
7777
|
class AddMemberRequest(BaseModel):
|
|
7778
7778
|
email: str
|
|
7779
|
+
# NOT AN AUTHORIZATION CONTROL. This value is stored on the member record
|
|
7780
|
+
# and echoed back; nothing in this server reads it to permit or deny
|
|
7781
|
+
# anything (measured: the only other `role` occurrences in this file are
|
|
7782
|
+
# chat-message roles around line 4166). /api/teams, /api/teams/{id}/members
|
|
7783
|
+
# and /api/audit-log carry no auth dependency at all, and web-app/auth.py:1-4
|
|
7784
|
+
# states that without DATABASE_URL authentication is completely disabled and
|
|
7785
|
+
# every endpoint is open.
|
|
7786
|
+
#
|
|
7787
|
+
# It is kept because the UI displays it and removing it would break that
|
|
7788
|
+
# surface, but a field that LOOKS like a permission and enforces nothing is
|
|
7789
|
+
# worse than no field: a buyer may rely on it. Wiring real enforcement means
|
|
7790
|
+
# adding an auth dependency to these routes first; until then this label is
|
|
7791
|
+
# descriptive only.
|
|
7779
7792
|
role: str = "viewer"
|
|
7780
7793
|
|
|
7781
7794
|
|
|
7782
|
-
def
|
|
7783
|
-
"""
|
|
7795
|
+
def _audit_actor(request=None) -> tuple:
|
|
7796
|
+
"""Resolve WHO performed an action, honestly.
|
|
7797
|
+
|
|
7798
|
+
Returns (actor, actor_state). actor_state is one of:
|
|
7799
|
+
"identified" - an authenticated principal; actor is their id
|
|
7800
|
+
"unauthenticated"- auth is not configured, so the actor is genuinely
|
|
7801
|
+
unknowable. web-app/auth.py:1-4 states that with no
|
|
7802
|
+
DATABASE_URL, authentication is completely disabled and
|
|
7803
|
+
all endpoints are open. That is the DEFAULT.
|
|
7804
|
+
"unreadable" - an actor should have been resolvable but the lookup
|
|
7805
|
+
failed; recorded so it is never mistaken for the above
|
|
7806
|
+
|
|
7807
|
+
Why this exists: _audit() previously hardcoded user="system" for every
|
|
7808
|
+
entry. An audit trail that cannot say who did something is not an audit
|
|
7809
|
+
trail, and "system" is worse than blank because it asserts an actor that was
|
|
7810
|
+
never established. Never invent an actor; say which of the three states
|
|
7811
|
+
applies and let the reader judge.
|
|
7812
|
+
"""
|
|
7813
|
+
if request is None:
|
|
7814
|
+
return ("unknown", "unauthenticated")
|
|
7815
|
+
try:
|
|
7816
|
+
principal = getattr(request.state, "user", None)
|
|
7817
|
+
if isinstance(principal, dict):
|
|
7818
|
+
ident = principal.get("sub") or principal.get("email") or principal.get("id")
|
|
7819
|
+
if ident:
|
|
7820
|
+
return (str(ident), "identified")
|
|
7821
|
+
elif principal:
|
|
7822
|
+
return (str(principal), "identified")
|
|
7823
|
+
return ("unknown", "unauthenticated")
|
|
7824
|
+
except Exception: # noqa: BLE001 - never let attribution break the action
|
|
7825
|
+
return ("unknown", "unreadable")
|
|
7826
|
+
|
|
7827
|
+
|
|
7828
|
+
def _audit(action: str, user: str = "", target: str = "", details: str = "",
|
|
7829
|
+
request=None):
|
|
7830
|
+
"""Record an audit log entry.
|
|
7831
|
+
|
|
7832
|
+
NOTE ON WHAT THIS LOG IS: _append_audit_log rewrites a 500-entry JSON array
|
|
7833
|
+
in place on every call. It is a recent-activity view, NOT tamper-evident and
|
|
7834
|
+
NOT append-only, and it is a different thing from the hash-chained log in
|
|
7835
|
+
dashboard/audit.py. Do not cite it as audit evidence. See
|
|
7836
|
+
docs/AUDIT-CHAIN-THREAT-MODEL.md.
|
|
7837
|
+
"""
|
|
7838
|
+
if user:
|
|
7839
|
+
actor, actor_state = (user, "identified")
|
|
7840
|
+
else:
|
|
7841
|
+
actor, actor_state = _audit_actor(request)
|
|
7784
7842
|
entry = {
|
|
7785
7843
|
"id": str(uuid.uuid4()),
|
|
7786
7844
|
"action": action,
|
|
7787
|
-
"user":
|
|
7845
|
+
"user": actor,
|
|
7846
|
+
"actor_state": actor_state,
|
|
7788
7847
|
"target": target,
|
|
7789
7848
|
"timestamp": datetime.now().isoformat(),
|
|
7790
7849
|
"details": details,
|
|
@@ -7800,7 +7859,7 @@ async def list_teams() -> JSONResponse:
|
|
|
7800
7859
|
|
|
7801
7860
|
|
|
7802
7861
|
@app.post("/api/teams")
|
|
7803
|
-
async def create_team(req: CreateTeamRequest) -> JSONResponse:
|
|
7862
|
+
async def create_team(req: CreateTeamRequest, request: Request = None) -> JSONResponse:
|
|
7804
7863
|
"""Create a new team."""
|
|
7805
7864
|
team_id = f"team-{uuid.uuid4().hex[:8]}"
|
|
7806
7865
|
team = {
|
|
@@ -7812,7 +7871,7 @@ async def create_team(req: CreateTeamRequest) -> JSONResponse:
|
|
|
7812
7871
|
store = _load_teams()
|
|
7813
7872
|
store[team_id] = team
|
|
7814
7873
|
_save_teams(store)
|
|
7815
|
-
_audit("team.created", target=req.name)
|
|
7874
|
+
_audit("team.created", target=req.name, request=request)
|
|
7816
7875
|
return JSONResponse(content={"id": team_id, "name": req.name, "created": True})
|
|
7817
7876
|
|
|
7818
7877
|
|
|
@@ -7827,7 +7886,7 @@ async def list_team_members(team_id: str) -> JSONResponse:
|
|
|
7827
7886
|
|
|
7828
7887
|
|
|
7829
7888
|
@app.post("/api/teams/{team_id}/members")
|
|
7830
|
-
async def add_team_member(team_id: str, req: AddMemberRequest) -> JSONResponse:
|
|
7889
|
+
async def add_team_member(team_id: str, req: AddMemberRequest, request: Request = None) -> JSONResponse:
|
|
7831
7890
|
"""Add a member to a team."""
|
|
7832
7891
|
store = _load_teams()
|
|
7833
7892
|
team = store.get(team_id)
|
|
@@ -7843,7 +7902,7 @@ async def add_team_member(team_id: str, req: AddMemberRequest) -> JSONResponse:
|
|
|
7843
7902
|
}
|
|
7844
7903
|
team.setdefault("members", []).append(member)
|
|
7845
7904
|
_save_teams(store)
|
|
7846
|
-
_audit("member.added", target=req.email, details=f"Role: {req.role}")
|
|
7905
|
+
_audit("member.added", target=req.email, details=f"Role: {req.role}", request=request)
|
|
7847
7906
|
return JSONResponse(content={"added": True, "member_id": member_id})
|
|
7848
7907
|
|
|
7849
7908
|
|