loki-mode 9.30.1 → 9.31.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/autonomy/run.sh +33 -4
- 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/src/audit/subscriber.js +5 -0
- package/src/policies/engine.js +20 -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.31.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.31.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.
|
|
1
|
+
9.31.0
|
package/autonomy/run.sh
CHANGED
|
@@ -2812,14 +2812,43 @@ check_policy() {
|
|
|
2812
2812
|
local context_json="${2:-}"
|
|
2813
2813
|
[ -z "$context_json" ] && context_json='{}'
|
|
2814
2814
|
|
|
2815
|
-
# Only check if policy
|
|
2816
|
-
|
|
2815
|
+
# Only check if a policy file exists. This gate MUST agree with what
|
|
2816
|
+
# src/policies/engine.js:_init actually loads, or the two disagree silently:
|
|
2817
|
+
# engine.js falls back to the global ~/.loki policy (which is the only place
|
|
2818
|
+
# the dashboard writes, api_v2.py:75,105), and a project-local-only test
|
|
2819
|
+
# here would short-circuit before that fallback was ever consulted.
|
|
2820
|
+
local _pol_global_dir="${LOKI_DATA_DIR:-$HOME/.loki}"
|
|
2821
|
+
if [ ! -f ".loki/policies.json" ] && [ ! -f ".loki/policies.yaml" ] \
|
|
2822
|
+
&& [ ! -f "$_pol_global_dir/policies.json" ] \
|
|
2823
|
+
&& [ ! -f "$_pol_global_dir/policies.yaml" ]; then
|
|
2817
2824
|
return 0
|
|
2818
2825
|
fi
|
|
2819
2826
|
|
|
2820
|
-
# Requires Node.js
|
|
2827
|
+
# Requires Node.js. Reaching here means a policy file EXISTS, so the
|
|
2828
|
+
# operator has expressed intent to enforce. Returning 0 for a missing
|
|
2829
|
+
# runtime silently disables that enforcement and reports nothing: the run
|
|
2830
|
+
# proceeds exactly as if every action were permitted. That is the worst
|
|
2831
|
+
# shape a security control can take, because the operator believes it is on.
|
|
2832
|
+
#
|
|
2833
|
+
# Fail CLOSED instead, matching what check.js already does for a corrupt
|
|
2834
|
+
# policy file (tests/test-policy-failclosed.sh). LOKI_POLICY_REQUIRE_NODE=0
|
|
2835
|
+
# restores the old fail-open for a host that genuinely cannot install node
|
|
2836
|
+
# and accepts running unenforced.
|
|
2821
2837
|
if ! command -v node >/dev/null 2>&1; then
|
|
2822
|
-
|
|
2838
|
+
if [ "${LOKI_POLICY_REQUIRE_NODE:-1}" = "0" ]; then
|
|
2839
|
+
log_warn "Policy file present but node is unavailable; enforcement SKIPPED (LOKI_POLICY_REQUIRE_NODE=0)"
|
|
2840
|
+
return 0
|
|
2841
|
+
fi
|
|
2842
|
+
log_error "Policy file present but node is unavailable: cannot evaluate policy, refusing the action (fail-closed). Set LOKI_POLICY_REQUIRE_NODE=0 to run unenforced."
|
|
2843
|
+
# Recorded like any other block. A refusal that leaves no trace is
|
|
2844
|
+
# indistinguishable from a run that was never gated, and the receipt
|
|
2845
|
+
# must be able to say WHY the action did not happen. The reason is
|
|
2846
|
+
# distinct from a DENY: the policy was never evaluated.
|
|
2847
|
+
audit_agent_action "policy_unevaluable" "Policy present but node unavailable" "enforcement=$enforcement_point"
|
|
2848
|
+
emit_event_json "policy_unevaluable" \
|
|
2849
|
+
"enforcement=$enforcement_point" \
|
|
2850
|
+
"reason=node_unavailable"
|
|
2851
|
+
return 1
|
|
2823
2852
|
fi
|
|
2824
2853
|
|
|
2825
2854
|
local result
|
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.31.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.31.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=B3C3956E251FE7CEBC7F7E5A4F250900
|
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.31.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.31.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/src/audit/subscriber.js
CHANGED
|
@@ -20,6 +20,11 @@ var EVENT_TO_AUDIT = {
|
|
|
20
20
|
'session_end': { what: 'session_end', why: 'Loki session terminated' },
|
|
21
21
|
'phase_change': { what: 'phase_change', why: 'RARV phase transition' },
|
|
22
22
|
'policy_denied': { what: 'policy_violation', why: 'Policy engine blocked action' },
|
|
23
|
+
// Distinct from policy_denied: the policy was never EVALUATED. A policy
|
|
24
|
+
// file was present (so enforcement was intended) but node was unavailable,
|
|
25
|
+
// so run.sh refused fail-closed rather than proceeding unenforced. The
|
|
26
|
+
// receipt must be able to tell "denied by a rule" from "could not check".
|
|
27
|
+
'policy_unevaluable': { what: 'policy_unevaluable', why: 'Policy present but could not be evaluated; action refused fail-closed' },
|
|
23
28
|
'policy_approval_required': { what: 'policy_approval', why: 'Policy requires approval' },
|
|
24
29
|
'otel_span_start': null, // Skip OTEL internal events
|
|
25
30
|
'otel_span_end': null, // Skip OTEL internal events
|
package/src/policies/engine.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
const fs = require('fs');
|
|
20
20
|
const path = require('path');
|
|
21
|
+
const os = require('os');
|
|
21
22
|
const { Decision, evaluateRule, scanContent, validatePreExecution,
|
|
22
23
|
validatePreDeployment, validateResource, validateData,
|
|
23
24
|
validateApprovalGate, RULE_EVALUATORS } = require('./types');
|
|
@@ -250,11 +251,29 @@ class PolicyEngine {
|
|
|
250
251
|
const jsonPath = path.join(lokiDir, 'policies.json');
|
|
251
252
|
const yamlPath = path.join(lokiDir, 'policies.yaml');
|
|
252
253
|
|
|
253
|
-
//
|
|
254
|
+
// The dashboard writes policies to ~/.loki/policies.json
|
|
255
|
+
// (dashboard/api_v2.py:75,105 via LOKI_DATA_DIR, default ~/.loki) while this
|
|
256
|
+
// reader only ever looked project-local. The ONE existing writer therefore
|
|
257
|
+
// fed a file this reader never opened, and an operator who created a policy
|
|
258
|
+
// through the UI got no enforcement at all and no error saying why.
|
|
259
|
+
//
|
|
260
|
+
// Project-local still WINS: a repo that ships its own policy must not be
|
|
261
|
+
// silently overridden by a machine-global one. The global file is a
|
|
262
|
+
// fallback, consulted only when the project defines nothing.
|
|
263
|
+
const globalDir = path.join(
|
|
264
|
+
process.env.LOKI_DATA_DIR || path.join(os.homedir(), '.loki'));
|
|
265
|
+
const globalJson = path.join(globalDir, 'policies.json');
|
|
266
|
+
const globalYaml = path.join(globalDir, 'policies.yaml');
|
|
267
|
+
|
|
268
|
+
// Try JSON first, then YAML, project-local before global.
|
|
254
269
|
if (fs.existsSync(jsonPath)) {
|
|
255
270
|
this._policyPath = jsonPath;
|
|
256
271
|
} else if (fs.existsSync(yamlPath)) {
|
|
257
272
|
this._policyPath = yamlPath;
|
|
273
|
+
} else if (fs.existsSync(globalJson)) {
|
|
274
|
+
this._policyPath = globalJson;
|
|
275
|
+
} else if (fs.existsSync(globalYaml)) {
|
|
276
|
+
this._policyPath = globalYaml;
|
|
258
277
|
} else {
|
|
259
278
|
// No policy file -- zero overhead mode
|
|
260
279
|
this._policies = null;
|