loki-mode 9.17.2 → 9.18.2
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/loki +439 -2
- package/autonomy/run.sh +215 -11
- package/autonomy/trigger-server.py +518 -17
- package/bin/loki +7 -1
- package/dashboard/__init__.py +1 -1
- package/docs/COMPETITIVE-SCORECARD.md +38 -0
- package/docs/COMPETITOR-DEPLOYMENT-MODELS.md +475 -0
- package/docs/DEPLOYMENT.md +542 -0
- package/docs/STALE-STATE-AUDIT.md +174 -0
- package/docs/VERIFICATION-COST.md +31 -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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Stale-State Audit
|
|
2
|
+
|
|
3
|
+
Audit for siblings of the `loki.pgid` session-killer (fixed in 4792b521).
|
|
4
|
+
|
|
5
|
+
**The shape being hunted:** a file recording a PID, PGID, port, lock, or session
|
|
6
|
+
id, written by one run, NOT removed on abnormal exit, later TRUSTED by another
|
|
7
|
+
run after the OS recycled the identifier.
|
|
8
|
+
|
|
9
|
+
**Method:** grep for the write, find its `rm -f`, check whether a trap covers
|
|
10
|
+
INT/TERM/crash, then check whether the reader proves the record is still
|
|
11
|
+
current. Every candidate was measured on this host rather than judged by
|
|
12
|
+
reading, because the pgid finding was credible on evidence (155h/202h orphans),
|
|
13
|
+
not on the code looking wrong.
|
|
14
|
+
|
|
15
|
+
Scope: read-only across the repo; fix applied to one file (`autonomy/run.sh`).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Measured state of this host
|
|
20
|
+
|
|
21
|
+
Run from the repo root on 2026-08-08:
|
|
22
|
+
|
|
23
|
+
| File | Contents | Live? | Age |
|
|
24
|
+
|---|---|---|---|
|
|
25
|
+
| `~/.loki/dashboard/dashboard.pid` | absent | n/a | n/a |
|
|
26
|
+
| `.loki/dashboard/dashboard.pid` | `87992` | **DEAD** | **mtime Jul 31 (8 days)** |
|
|
27
|
+
| `$TMPDIR/loki-local-ci.lock` | `61876` | LIVE (real local-ci) | current |
|
|
28
|
+
| `.loki/app-runner/app.pid` | absent | n/a | n/a |
|
|
29
|
+
|
|
30
|
+
The dashboard pid file is the same measured shape as the pgid orphans: a dead
|
|
31
|
+
identifier, days old, still on disk, still trusted by a code path that kills.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Findings, ranked by blast radius
|
|
36
|
+
|
|
37
|
+
### FINDING 1 (HIGH -- fixed here): shared dashboard pid is killed unverified
|
|
38
|
+
|
|
39
|
+
**Evidence.** `autonomy/run.sh:1597-1600` reads a pid from
|
|
40
|
+
`~/.loki/dashboard/dashboard.pid` and sends it `kill` then `kill -9` with no
|
|
41
|
+
liveness and no identity check.
|
|
42
|
+
|
|
43
|
+
Removal of that file happens ONLY on explicit stop paths -- `run.sh:16608`,
|
|
44
|
+
`:16658`, `:17399`, and `autonomy/loki:7078-7079`. **No trap covers a crash or
|
|
45
|
+
Ctrl+C.** That is precisely why the measured copy in this checkout is 8 days
|
|
46
|
+
stale with a dead pid.
|
|
47
|
+
|
|
48
|
+
**Reachable:** yes. Called from `cleanup` on both stop paths (`run.sh:25311`,
|
|
49
|
+
`:25384`).
|
|
50
|
+
|
|
51
|
+
**Blast radius: the worst class.** The file lives under `~/.loki`, so it is
|
|
52
|
+
machine-global -- the victim need not belong to this project. A recycled pid
|
|
53
|
+
names an unrelated live process and receives `kill -9`.
|
|
54
|
+
|
|
55
|
+
**Why `kill -0` would not have fixed it:** a recycled pid IS alive, so a
|
|
56
|
+
liveness check passes. This is the identical insufficiency the `loki.pgid`
|
|
57
|
+
self-check had. The guard has to check IDENTITY.
|
|
58
|
+
|
|
59
|
+
**Fix applied.** `_loki_pid_looks_like_dashboard()` at `autonomy/run.sh:1526`,
|
|
60
|
+
gating the kill at `:1597`. It mirrors `_app_runner_pid_is_ours`
|
|
61
|
+
(`app-runner.sh:242`) and **fails OPEN**: when `ps` reports nothing we signal
|
|
62
|
+
exactly as before, so a legitimate dashboard is never left running by this
|
|
63
|
+
check. The only behavior change is refusing to kill a process positively
|
|
64
|
+
identified as not a dashboard.
|
|
65
|
+
|
|
66
|
+
Deliberately NOT done: adding a trap to remove the pid file on crash. That is a
|
|
67
|
+
larger change across the dashboard lifecycle, and the identity guard already
|
|
68
|
+
makes a stale file harmless at the point where it does damage. The stale file
|
|
69
|
+
still being present is untidy, not dangerous, once the killer verifies.
|
|
70
|
+
|
|
71
|
+
**Test:** `tests/test-stale-dashboard-pid.sh`, 6/6, mutation-verified below.
|
|
72
|
+
|
|
73
|
+
### FINDING 2 (MEDIUM -- reported, not fixed): `app_runner_stop` group-kills unverified
|
|
74
|
+
|
|
75
|
+
**Evidence.** `app-runner.sh:1407` falls back to reading `app.pid` from disk,
|
|
76
|
+
then `:1456` sends `kill -TERM "-$_APP_RUNNER_PID"` -- a **process-group**
|
|
77
|
+
signal -- without an identity check.
|
|
78
|
+
|
|
79
|
+
The repo already has the right guard: `_app_runner_pid_is_ours`
|
|
80
|
+
(`app-runner.sh:242`). It is called at `:1657` and `:1829` but **not** on the
|
|
81
|
+
stop path. `app-runner.sh` has **no trap at all** (`grep -n "trap " ` returns
|
|
82
|
+
nothing), so `app.pid` survives a crash exactly like the pgid file did.
|
|
83
|
+
|
|
84
|
+
**Blast radius:** higher per-hit than Finding 1 (a group signal reaches a whole
|
|
85
|
+
tree) but narrower reach: the file is project-local (`.loki/app-runner/`), not
|
|
86
|
+
machine-global, and was absent on this host. Not fixed because it is a third
|
|
87
|
+
file and the lead scoped this task to one; it needs its own change adding
|
|
88
|
+
`_app_runner_pid_is_ours` to the stop path.
|
|
89
|
+
|
|
90
|
+
### FINDING 3 (LOW -- reported, not fixed): local-ci lock guard is a no-op under `/bin/bash`
|
|
91
|
+
|
|
92
|
+
**Evidence.** `scripts/local-ci.sh:104`:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
trap '[ "$$" = "$_lci_owner" ] && rm -f "$_lci_lock" 2>/dev/null || true' EXIT
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The scar comment above it (`:99-103`) correctly diagnoses that a bare EXIT trap
|
|
99
|
+
fires in every subshell and that a finishing child deleted the parent's lock.
|
|
100
|
+
**But `$$` does not change in a bash subshell, so this guard does not
|
|
101
|
+
discriminate.** Verified:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
$ bash -c 'p=$$; ( [ "$$" = "$p" ] && echo same )'
|
|
105
|
+
same
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The correct discriminators are `BASHPID` (bash 4+) or `BASH_SUBSHELL` (works in
|
|
109
|
+
3.2). Measured on this host: the script is `#!/usr/bin/env bash` which resolves
|
|
110
|
+
to **bash 5.3**, where `BASHPID` is available. But `/bin/bash` here is **3.2**,
|
|
111
|
+
where `BASHPID` is unset and `BASH_SUBSHELL` is the version-safe choice.
|
|
112
|
+
|
|
113
|
+
**Blast radius: benign** by the lead's own criterion. Worst case is two
|
|
114
|
+
concurrent local-ci runs starving each other into phantom failures -- costly in
|
|
115
|
+
time and trust, but it kills nothing. Left unfixed as out of scope; the one-line
|
|
116
|
+
change is `[ "${BASH_SUBSHELL:-0}" = 0 ]`.
|
|
117
|
+
|
|
118
|
+
Note on the sibling fix already shipped: `_loki_remove_pgid_file`
|
|
119
|
+
(`run.sh:25189`) uses `${BASHPID:-$$}`. Under bash 3.2 that collapses to `$$`
|
|
120
|
+
and the guard degrades to the no-op. **The degradation direction is safe** -- a
|
|
121
|
+
subshell deletes the pgid file early, the reap then finds no file and skips, so
|
|
122
|
+
an orphan survives and nobody gets killed. `run.sh` is also `#!/usr/bin/env
|
|
123
|
+
bash` (5.3 here), so this is a portability note, not a live defect. Worth
|
|
124
|
+
knowing: the mutation test for that guard asserts *source text* contains
|
|
125
|
+
`BASHPID`, so it proves the code is present, not that it behaves correctly on a
|
|
126
|
+
3.2 host.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Already guarded -- do not re-audit
|
|
131
|
+
|
|
132
|
+
Checked and found correct. Listed explicitly so this ground is not covered
|
|
133
|
+
twice.
|
|
134
|
+
|
|
135
|
+
- **`autonomy/lib/lock.sh:56-67`** -- `_loki_lock_is_stale` requires the
|
|
136
|
+
sentinel PID to be dead **AND** mtime > 30s. Both conditions, not either.
|
|
137
|
+
Correct.
|
|
138
|
+
- **`cleanup_orphan_pids` (`run.sh:2300+`)** -- reaps only on liveness AND
|
|
139
|
+
parent death AND (for wrappers) idle-past-budget AND no live engine child.
|
|
140
|
+
Also self-skips `$$`. Correct, and notably stricter than the pgid reap was.
|
|
141
|
+
- **`_app_runner_pid_is_ours` (`app-runner.sh:242`)** -- a real identity token
|
|
142
|
+
captured post-exec, failing open on a missing token so a live app is never
|
|
143
|
+
falsely killed. Correct where it is called; see Finding 2 for where it is not.
|
|
144
|
+
- **`_app_runner_collect_descendants` (`app-runner.sh:289`)** -- refuses pid
|
|
145
|
+
0/1 and walks parent-child links from our own pid only, so it structurally
|
|
146
|
+
cannot signal outside our subtree. Correct.
|
|
147
|
+
- **`status.ts:266-270` and the bash status reader (`loki:5092`)** -- both do
|
|
148
|
+
`os.kill(pid, 0)` before reporting. A wrong answer only mislabels a URL in
|
|
149
|
+
status output. Benign even when stale.
|
|
150
|
+
- **The `CLEAR`/`KEEP` registry check (`run.sh:1545-1560`)** -- correctly
|
|
151
|
+
refuses to tear the shared dashboard down while any other project holds a
|
|
152
|
+
live pid. This gates Finding 1's call site; the defect was the missing check
|
|
153
|
+
on the pid itself, not this decision.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Mutation verification (Finding 1's fix)
|
|
158
|
+
|
|
159
|
+
Each guard was reverted individually; the test must go red, and only on its own
|
|
160
|
+
assertion.
|
|
161
|
+
|
|
162
|
+
| Mutation | Before | After | Assertion killed |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| Identity check downgraded to bare `kill -0` | 6 pass / 0 fail | 5 / 1 | "a live non-dashboard process is refused" |
|
|
165
|
+
| Call site ungated (guard present but unused) | 6 / 0 | 5 / 1 | "the kill is gated on the identity guard" |
|
|
166
|
+
| `pid > 1` check removed | 6 / 0 | 5 / 1 | "pid 0/1, empty and malformed inputs refused" |
|
|
167
|
+
|
|
168
|
+
Restored after each. Final: `bash -n autonomy/run.sh` clean,
|
|
169
|
+
`tests/test-stale-dashboard-pid.sh` 6/6, `tests/test-pgid-stale-reap.sh` 8/8
|
|
170
|
+
(unaffected).
|
|
171
|
+
|
|
172
|
+
The first mutation is the important one: it replaces the identity check with
|
|
173
|
+
exactly the insufficient guard (`kill -0`) that a reviewer would most likely
|
|
174
|
+
propose, and the test catches it.
|
|
@@ -115,9 +115,39 @@ above less trustworthy:
|
|
|
115
115
|
LOCAL_CI_TIER=full bash scripts/local-ci.sh # the full gate, timed
|
|
116
116
|
loki outcomes --json # post-merge outcomes, or UNKNOWN with reasons
|
|
117
117
|
loki intent status --json # spec-vs-intent drift
|
|
118
|
-
loki proof verify <id> # re-hash a receipt
|
|
118
|
+
loki proof verify <id> # re-hash a receipt (see below)
|
|
119
119
|
bash tests/test-competitor-verify-surface.sh # the competitor CLI measurement
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
### Prove the tamper detection yourself, in three commands
|
|
123
|
+
|
|
124
|
+
The claim is narrow and worth stating exactly: editing a receipt's recorded
|
|
125
|
+
facts is DETECTED. Run this against any receipt in `.loki/proofs/`:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
ID=$(ls .loki/proofs | head -1)
|
|
129
|
+
V() { loki proof verify "$ID" --json | python3 -c 'import json,sys;print(json.load(sys.stdin)["hash_ok"])'; }
|
|
130
|
+
V # True
|
|
131
|
+
python3 -c "import json;p='.loki/proofs/$ID/proof.json';d=json.load(open(p));d['files_changed']={'count':999999};json.dump(d,open(p,'w'))"
|
|
132
|
+
V # False
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Measured on this repository: `True` -> `False` -> `True` after restoring.
|
|
136
|
+
|
|
137
|
+
**Read `hash_ok`, not `ok`.** They answer different questions and conflating
|
|
138
|
+
them produces a false alarm. `hash_ok` is integrity: do the recorded facts still
|
|
139
|
+
hash to the recorded digest. `ok` also folds in `tree_drift`, which is true
|
|
140
|
+
whenever the working tree has moved since the receipt was written -- so an
|
|
141
|
+
untampered receipt from last month correctly reports `ok: false` with
|
|
142
|
+
`hash_ok: true`. An earlier version of this document said "exit 1 on tamper",
|
|
143
|
+
which is wrong in exactly that way: a drifted-but-intact receipt also exits 1.
|
|
144
|
+
|
|
145
|
+
**What this does NOT establish.** Integrity is not provenance. On the unsigned
|
|
146
|
+
path a party who rewrites the facts AND recomputes the digest passes this check
|
|
147
|
+
-- see the forgeability limit above. Provenance requires the signed path
|
|
148
|
+
(`LOKI_PROOF_GPG_KEY`, [SIGNED-RECEIPTS.md](SIGNED-RECEIPTS.md)), and the remote
|
|
149
|
+
client reports the two separately for that reason: VERIFIED, UNSIGNED,
|
|
150
|
+
UNCHECKED and TAMPERED are four distinct verdicts, never collapsed.
|
|
151
|
+
|
|
122
152
|
If a number here does not reproduce on your machine, that is a defect and we
|
|
123
153
|
want the report.
|
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.
|
|
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.18.2";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=B34A84808461BB0C64756E2164756E21
|
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.18.2",
|
|
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.
|
|
5
|
+
"version": "9.18.2",
|
|
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",
|