loki-mode 9.22.2 → 9.22.3
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/OUTCOME-CANARY-EVALUATION.md +32 -2
- 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/outcome-canary-evaluate.py +17 -2
- package/tools/outcome-canary-receipt-verify.py +17 -2
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.22.
|
|
6
|
+
# Loki Mode v9.22.3
|
|
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.22.
|
|
473
|
+
**v9.22.3 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.22.
|
|
1
|
+
9.22.3
|
package/dashboard/__init__.py
CHANGED
|
@@ -25,6 +25,21 @@ loki outcomes canary evaluate report.json observations.json \
|
|
|
25
25
|
|
|
26
26
|
Evaluation is opt-in. Without `--enable-evaluation`, the command refuses.
|
|
27
27
|
|
|
28
|
+
Automation that may act only on one exact decision can add an explicit verdict
|
|
29
|
+
gate:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
loki outcomes canary evaluate report.json observations.json \
|
|
33
|
+
--enable-evaluation --control-route safe --canary-percent 10 \
|
|
34
|
+
--require-verdict PROMOTE --json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`--require-verdict` accepts only `PROMOTE`, `HOLD`, or `ROLLBACK`. A complete
|
|
38
|
+
evaluation still prints its actual verdict, but exits 3 when that verdict does
|
|
39
|
+
not exactly match the required value. JSON output also includes
|
|
40
|
+
`required_verdict` and `requirement_met` when the gate is requested. Omitting the
|
|
41
|
+
gate preserves the existing behavior: every complete verdict exits 0.
|
|
42
|
+
|
|
28
43
|
## Retain a decision receipt
|
|
29
44
|
|
|
30
45
|
After a complete evaluation, create one immutable portable proof by independently
|
|
@@ -56,6 +71,20 @@ loki outcomes canary verify report.json observations.json receipt.json \
|
|
|
56
71
|
--enable-verification --json
|
|
57
72
|
```
|
|
58
73
|
|
|
74
|
+
Automation that may act only on one exact independently verified decision can
|
|
75
|
+
gate the verifier itself:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
loki outcomes canary verify report.json observations.json receipt.json \
|
|
79
|
+
--enable-verification --require-verdict PROMOTE --json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`--require-verdict` accepts only `PROMOTE`, `HOLD`, or `ROLLBACK`. A receipt that
|
|
83
|
+
fully verifies still prints its actual verdict, but exits 3 when that verdict
|
|
84
|
+
does not exactly match the required value. JSON output also includes
|
|
85
|
+
`required_verdict` and `requirement_met` when the gate is requested. Omitting the
|
|
86
|
+
gate preserves the existing behavior: every fully verified receipt exits 0.
|
|
87
|
+
|
|
59
88
|
The read-only verifier takes the policy from the canonical receipt, independently
|
|
60
89
|
reruns the deterministic evaluation, rechecks the report, source, and observation
|
|
61
90
|
bytes, and requires the complete rederived receipt to match exactly. It returns
|
|
@@ -139,5 +168,6 @@ unbound evidence returns `REFUSED` rather than a partial verdict.
|
|
|
139
168
|
`--json` emits the aggregate arms, policy, exact evidence digests, verdict, and
|
|
140
169
|
refusal reasons. It is portable across machines because it omits local input paths.
|
|
141
170
|
The default output is a short human-readable summary. Exit 0 means
|
|
142
|
-
a verdict was produced (including `ROLLBACK`)
|
|
143
|
-
|
|
171
|
+
a verdict was produced (including `ROLLBACK`) and any requested exact-verdict
|
|
172
|
+
gate matched. Exit 3 means evaluation was refused or an exact-verdict gate did
|
|
173
|
+
not match, 64 is an invocation error, and 66 is a missing input file.
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var p_=Object.create;var{getPrototypeOf:d_,defineProperty:eK,getOwnPropertyNames:c_}=Object;var l_=Object.prototype.hasOwnProperty;function i_(Z){return this[Z]}var a_,s_,n_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?a_??=new WeakMap:s_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?p_(d_(Z)):{};let K=X||!Z||!Z.__esModule?eK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of c_(Z))if(!l_.call(K,$))eK(K,$,{get:i_.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 o_=(Z)=>Z;function r_(Z,X){this[Z]=o_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)eK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:r_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var t0=import.meta.require;var xO={};l0(xO,{lokiDir:()=>j0,homeLokiDir:()=>R4,findRepoRootForVersion:()=>X$,REPO_ROOT:()=>i0});import{resolve as n7,dirname as Z$}from"path";import{fileURLToPath as t_}from"url";import{existsSync as UQ}from"fs";import{homedir as e_}from"os";function Zf(){let Z=kO;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(kO,"..","..","..")}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(e_(),".loki")}var kO,i0;var H8=p(()=>{kO=Z$(t_(import.meta.url));i0=Zf()});import{readFileSync as Xf}from"fs";import{resolve as Qf,dirname as Yf}from"path";import{fileURLToPath as Jf}from"url";function h3(){if(h5!==null)return h5;let Z="9.22.
|
|
2
|
+
var p_=Object.create;var{getPrototypeOf:d_,defineProperty:eK,getOwnPropertyNames:c_}=Object;var l_=Object.prototype.hasOwnProperty;function i_(Z){return this[Z]}var a_,s_,n_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?a_??=new WeakMap:s_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?p_(d_(Z)):{};let K=X||!Z||!Z.__esModule?eK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of c_(Z))if(!l_.call(K,$))eK(K,$,{get:i_.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 o_=(Z)=>Z;function r_(Z,X){this[Z]=o_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)eK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:r_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var t0=import.meta.require;var xO={};l0(xO,{lokiDir:()=>j0,homeLokiDir:()=>R4,findRepoRootForVersion:()=>X$,REPO_ROOT:()=>i0});import{resolve as n7,dirname as Z$}from"path";import{fileURLToPath as t_}from"url";import{existsSync as UQ}from"fs";import{homedir as e_}from"os";function Zf(){let Z=kO;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(kO,"..","..","..")}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(e_(),".loki")}var kO,i0;var H8=p(()=>{kO=Z$(t_(import.meta.url));i0=Zf()});import{readFileSync as Xf}from"fs";import{resolve as Qf,dirname as Yf}from"path";import{fileURLToPath as Jf}from"url";function h3(){if(h5!==null)return h5;let Z="9.22.3";if(typeof Z==="string"&&Z.length>0)return h5=Z,h5;try{let X=Yf(Jf(import.meta.url)),Q=X$(X);h5=Xf(Qf(Q,"VERSION"),"utf-8").trim()}catch{h5="unknown"}return h5}var h5=null;var BQ=p(()=>{H8()});var _O={};l0(_O,{runOrThrow:()=>Tf,run:()=>E0,readStreamCapped:()=>NQ,commandVersion:()=>Cf,commandExists:()=>X9,ShellError:()=>Q$,MAX_STDOUT_BYTES:()=>bO});async function NQ(Z,X=bO){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 V=$.byteLength-(z-X);J+=Y.decode($.subarray(0,V),{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 Tf(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=wf(Z),Q=await E0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function wf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function Cf(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 bO=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 Ff?"":Z}var Ff,A0,F8,_0,$W0,a0,V8,Q9,h;var S6=p(()=>{Ff=(process.env.NO_COLOR??"").length>0;A0=o7("\x1B[0;31m"),F8=o7("\x1B[0;32m"),_0=o7("\x1B[1;33m"),$W0=o7("\x1B[0;34m"),a0=o7("\x1B[0;36m"),V8=o7("\x1B[1m"),Q9=o7("\x1B[2m"),h=o7("\x1B[0m")});import{existsSync as ff}from"fs";async function E7(){if(x4!==void 0)return x4;let Z="/opt/homebrew/bin/python3.12";if(ff(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 XA={};l0(XA,{runStatus:()=>Vh});import{existsSync as Y9,readFileSync as g3,readdirSync as aO,statSync as sO}from"fs";import{resolve as h8,basename as ef}from"path";import{homedir as Zh}from"os";function nO(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 oO(Z,X,Q){if(X===0)return null;let Y=Math.trunc(Z*100/X),J=Math.trunc(Z*AQ/X);if(J>AQ)J=AQ;let z=AQ-J,K=F8;if(Y>=80)K=A0;else if(Y>=50)K=_0;let $="=".repeat(Math.max(0,J))+" ".repeat(Math.max(0,z)),V=nO(Z),W=nO(X);return` ${V8}${Q}${h} ${K}[${$}]${h} ${Y}% (${V} / ${W})`}async function Qh(){if(await X9("jq"))return!0;return process.stdout.write(`${A0}Error: jq is required but not installed.${h}
|
|
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)
|
|
@@ -1236,4 +1236,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
1236
1236
|
`),2}case"start":{let{runStart:Y}=await Promise.resolve().then(() => (g_(),v_));return Y(Q)}default:return process.stderr.write(`Unknown command: ${X}
|
|
1237
1237
|
`),process.stderr.write(m_),2}}lO();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var dV0=await pV0(Bun.argv.slice(2));process.exit(dV0);
|
|
1238
1238
|
|
|
1239
|
-
//# debugId=
|
|
1239
|
+
//# debugId=45400DDB79710D0464756E2164756E21
|
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.22.
|
|
4
|
+
"version": "9.22.3",
|
|
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": "9.22.
|
|
5
|
+
"version": "9.22.3",
|
|
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",
|
|
@@ -235,6 +235,7 @@ def main(argv=None):
|
|
|
235
235
|
parser.add_argument("--max-risk", type=float, default=.25)
|
|
236
236
|
parser.add_argument("--min-samples", type=int, default=5)
|
|
237
237
|
parser.add_argument("--min-lift-bps", type=int, default=1)
|
|
238
|
+
parser.add_argument("--require-verdict", choices=("PROMOTE", "HOLD", "ROLLBACK"))
|
|
238
239
|
parser.add_argument("--json", action="store_true")
|
|
239
240
|
args = parser.parse_args(argv)
|
|
240
241
|
for path in (args.report, args.observations):
|
|
@@ -245,8 +246,19 @@ def main(argv=None):
|
|
|
245
246
|
args.report, args.observations, args.control_route, args.canary_percent,
|
|
246
247
|
args.max_risk, args.min_samples, args.min_lift_bps, args.enable_evaluation,
|
|
247
248
|
)
|
|
249
|
+
requirement_met = (
|
|
250
|
+
not result["refusal_reasons"]
|
|
251
|
+
and (args.require_verdict is None or result["verdict"] == args.require_verdict)
|
|
252
|
+
)
|
|
248
253
|
if args.json:
|
|
249
|
-
|
|
254
|
+
output = result
|
|
255
|
+
if args.require_verdict is not None:
|
|
256
|
+
output = {
|
|
257
|
+
**result,
|
|
258
|
+
"required_verdict": args.require_verdict,
|
|
259
|
+
"requirement_met": requirement_met,
|
|
260
|
+
}
|
|
261
|
+
print(json.dumps(output, sort_keys=True))
|
|
250
262
|
elif result["refusal_reasons"]:
|
|
251
263
|
print("Canary evaluation: REFUSED")
|
|
252
264
|
for reason in result["refusal_reasons"]:
|
|
@@ -264,7 +276,10 @@ def main(argv=None):
|
|
|
264
276
|
print(f" accepted delta: {result['accepted_delta_bps']} bps")
|
|
265
277
|
print(f" report sha256: {result['report_sha256']}")
|
|
266
278
|
print(f" observations sha256: {result['observations_sha256']}")
|
|
267
|
-
|
|
279
|
+
if args.require_verdict is not None:
|
|
280
|
+
status = "MET" if requirement_met else "NOT MET"
|
|
281
|
+
print(f" required verdict: {args.require_verdict} ({status})")
|
|
282
|
+
return OK if requirement_met else REFUSED
|
|
268
283
|
|
|
269
284
|
|
|
270
285
|
if __name__ == "__main__":
|
|
@@ -160,6 +160,7 @@ def main(argv=None):
|
|
|
160
160
|
parser.add_argument("observations")
|
|
161
161
|
parser.add_argument("receipt")
|
|
162
162
|
parser.add_argument("--enable-verification", action="store_true")
|
|
163
|
+
parser.add_argument("--require-verdict", choices=("PROMOTE", "HOLD", "ROLLBACK"))
|
|
163
164
|
parser.add_argument("--json", action="store_true")
|
|
164
165
|
args = parser.parse_args(argv)
|
|
165
166
|
for path in (args.report, args.observations, args.receipt):
|
|
@@ -169,14 +170,28 @@ def main(argv=None):
|
|
|
169
170
|
result = verify_receipt(
|
|
170
171
|
args.report, args.observations, args.receipt, args.enable_verification
|
|
171
172
|
)
|
|
173
|
+
requirement_met = (
|
|
174
|
+
result["status"] == "VERIFIED"
|
|
175
|
+
and (args.require_verdict is None or result["verdict"] == args.require_verdict)
|
|
176
|
+
)
|
|
172
177
|
if args.json:
|
|
173
|
-
|
|
178
|
+
output = result
|
|
179
|
+
if args.require_verdict is not None:
|
|
180
|
+
output = {
|
|
181
|
+
**result,
|
|
182
|
+
"required_verdict": args.require_verdict,
|
|
183
|
+
"requirement_met": requirement_met,
|
|
184
|
+
}
|
|
185
|
+
print(json.dumps(output, sort_keys=True))
|
|
174
186
|
elif result["status"] == "VERIFIED":
|
|
175
187
|
print(f"Canary decision receipt: VERIFIED ({result['verdict']})")
|
|
176
188
|
print(f" sha256={result['receipt_sha256']}")
|
|
189
|
+
if args.require_verdict is not None:
|
|
190
|
+
status = "MET" if requirement_met else "NOT MET"
|
|
191
|
+
print(f" required verdict: {args.require_verdict} ({status})")
|
|
177
192
|
else:
|
|
178
193
|
print(f"Canary decision receipt: REFUSED ({result['refusal_reason']})")
|
|
179
|
-
return OK if
|
|
194
|
+
return OK if requirement_met else REFUSED
|
|
180
195
|
|
|
181
196
|
|
|
182
197
|
if __name__ == "__main__":
|