loki-mode 7.7.14 → 7.7.16
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 +4 -4
- package/VERSION +1 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/api_v2.py +38 -15
- package/dashboard/audit.py +119 -7
- package/docs/INSTALLATION.md +1 -1
- package/docs/plans/UT2-6-LSP-DIAGNOSTIC-BROADCAST.md +12 -0
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -3,15 +3,15 @@ name: loki-mode
|
|
|
3
3
|
description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.7.
|
|
6
|
+
# Loki Mode v7.7.16
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
10
10
|
**Spec in, product out.** A "spec" is whatever describes the work: a Markdown PRD, a GitHub issue, an OpenAPI doc, a Jira ticket -- a PRD is one form of spec.
|
|
11
11
|
|
|
12
|
-
**Multi-provider (stable since v5.0.0):** Claude/Codex/Cline/Aider with abstract model tiers and degraded mode for non-Claude providers. See `skills/providers.md`. **Current track (v7.
|
|
12
|
+
**Multi-provider (stable since v5.0.0):** Claude/Codex/Cline/Aider with abstract model tiers and degraded mode for non-Claude providers. Gemini deprecated v7.5.18. See `skills/providers.md`. **Current track (v7.7.x):** LSP grounding as first-class agent tool (v7.7.0-v7.7.9; lsp_get_diagnostics actually-returns-diagnostics regression fix v7.7.14), provider_source cli (v7.7.11-v7.7.12 bash/bun parity), Docker/bash-3.2 robustness (v7.7.13), audit chain cross-file verification fix (v7.7.15), Phase 1 RARV-C closure (real provider judges, gate-failure flock, synthetic PRD e2e, status `--json`).
|
|
13
13
|
|
|
14
|
-
**Runtime migration
|
|
14
|
+
**Runtime migration:** Bash-to-Bun migration. Read-only commands (`version`, `status`, `stats`, `doctor`, `provider show/list`, `memory list/index`) flow through Bun runtime via `bin/loki` since v7.3.0. Every other command remains on the Bash runtime (`autonomy/loki`). Rollback: `LOKI_LEGACY_BASH=1`. See `UPGRADING.md` and `docs/architecture/ADR-001-runtime-migration.md`.
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -381,4 +381,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
381
381
|
|
|
382
382
|
---
|
|
383
383
|
|
|
384
|
-
**v7.7.
|
|
384
|
+
**v7.7.16 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.7.
|
|
1
|
+
7.7.16
|
package/dashboard/__init__.py
CHANGED
package/dashboard/api_v2.py
CHANGED
|
@@ -541,24 +541,47 @@ async def query_audit_logs(
|
|
|
541
541
|
|
|
542
542
|
@router.get("/audit/verify", dependencies=[Depends(auth.require_scope("audit"))])
|
|
543
543
|
async def verify_audit_integrity():
|
|
544
|
-
"""Verify audit log integrity across all log files.
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
544
|
+
"""Verify audit log integrity across all log files.
|
|
545
|
+
|
|
546
|
+
v7.7.15 (council fix): delegates to `audit.verify_all_logs()` which
|
|
547
|
+
threads the chain hash across rotated daily files. The previous
|
|
548
|
+
per-file loop always started each file from genesis "0"*64, so any
|
|
549
|
+
log file beyond the first ever rotated false-negatived. Per-file
|
|
550
|
+
breakdown still returned alongside the aggregate verdict for
|
|
551
|
+
operator visibility.
|
|
552
|
+
"""
|
|
553
|
+
aggregate = audit.verify_all_logs()
|
|
554
|
+
|
|
555
|
+
# Per-file breakdown for operator visibility (sorted by mtime
|
|
556
|
+
# to match the aggregate chain-walk order)
|
|
549
557
|
results = []
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
+
if audit.AUDIT_DIR.exists():
|
|
559
|
+
log_files = sorted(audit.AUDIT_DIR.glob("audit-*.jsonl"),
|
|
560
|
+
key=lambda p: p.stat().st_mtime)
|
|
561
|
+
prev_hash = "0" * 64
|
|
562
|
+
for log_file in log_files:
|
|
563
|
+
if not audit._file_has_integrity(str(log_file)):
|
|
564
|
+
results.append({
|
|
565
|
+
"file": log_file.name,
|
|
566
|
+
"valid": True,
|
|
567
|
+
"skipped_pre_integrity": True,
|
|
568
|
+
"entries_checked": 0,
|
|
569
|
+
})
|
|
570
|
+
continue
|
|
571
|
+
r = audit.verify_log_integrity(str(log_file), start_hash=prev_hash)
|
|
572
|
+
r["file"] = log_file.name
|
|
573
|
+
results.append(r)
|
|
574
|
+
if r.get("valid"):
|
|
575
|
+
prev_hash = r.get("last_hash", prev_hash)
|
|
558
576
|
|
|
559
577
|
return {
|
|
560
|
-
"valid":
|
|
561
|
-
"files_checked":
|
|
578
|
+
"valid": aggregate["valid"],
|
|
579
|
+
"files_checked": aggregate["files_checked"],
|
|
580
|
+
"files_skipped": aggregate.get("files_skipped", 0),
|
|
581
|
+
"entries_checked": aggregate.get("entries_checked", 0),
|
|
582
|
+
"genesis_file": aggregate.get("genesis_file"),
|
|
583
|
+
"first_tampered_file": aggregate.get("first_tampered_file"),
|
|
584
|
+
"first_tampered_line": aggregate.get("first_tampered_line"),
|
|
562
585
|
"results": results,
|
|
563
586
|
}
|
|
564
587
|
|
package/dashboard/audit.py
CHANGED
|
@@ -408,15 +408,27 @@ def is_audit_enabled() -> bool:
|
|
|
408
408
|
return ENTERPRISE_AUDIT_ENABLED
|
|
409
409
|
|
|
410
410
|
|
|
411
|
-
def verify_log_integrity(log_file: str) -> dict:
|
|
411
|
+
def verify_log_integrity(log_file: str, start_hash: Optional[str] = None) -> dict:
|
|
412
412
|
"""Verify the integrity chain of a JSONL audit log file.
|
|
413
413
|
|
|
414
|
-
Reads each line, recomputes the chain hash
|
|
415
|
-
|
|
416
|
-
|
|
414
|
+
Reads each line, recomputes the chain hash, and compares to the
|
|
415
|
+
stored _integrity_hash. If any entry has been tampered with, all
|
|
416
|
+
subsequent hashes will also fail to match.
|
|
417
|
+
|
|
418
|
+
v7.7.15 fix: now accepts an optional `start_hash`. Audit logs rotate
|
|
419
|
+
daily and `_recover_last_hash()` carries the chain across file
|
|
420
|
+
boundaries at WRITE time. Without `start_hash`, verifying any log
|
|
421
|
+
file beyond the first-ever produces a false-negative (the file's
|
|
422
|
+
first entry was hashed against the PREVIOUS file's last hash, not
|
|
423
|
+
against the genesis "0"*64). Pass the previous file's final hash to
|
|
424
|
+
verify correctly, or use the new `verify_all_logs()` wrapper to
|
|
425
|
+
verify the entire chain across all rotated files.
|
|
417
426
|
|
|
418
427
|
Args:
|
|
419
428
|
log_file: Path to the JSONL audit log file to verify.
|
|
429
|
+
start_hash: Optional 64-hex starting hash for the chain. If
|
|
430
|
+
omitted, uses the genesis hash "0"*64 (correct only for the
|
|
431
|
+
very first audit log ever created on this machine).
|
|
420
432
|
|
|
421
433
|
Returns:
|
|
422
434
|
A dict with:
|
|
@@ -424,8 +436,10 @@ def verify_log_integrity(log_file: str) -> dict:
|
|
|
424
436
|
- entries_checked (int): Number of entries verified.
|
|
425
437
|
- first_tampered_line (int | None): 1-based line number of the
|
|
426
438
|
first entry where the hash chain broke, or None if valid.
|
|
439
|
+
- last_hash (str): The final hash in this file (caller chains
|
|
440
|
+
this into the next file's verification).
|
|
427
441
|
"""
|
|
428
|
-
prev_hash = "0" * 64
|
|
442
|
+
prev_hash = start_hash if start_hash is not None else ("0" * 64)
|
|
429
443
|
entries_checked = 0
|
|
430
444
|
|
|
431
445
|
try:
|
|
@@ -442,6 +456,7 @@ def verify_log_integrity(log_file: str) -> dict:
|
|
|
442
456
|
"valid": False,
|
|
443
457
|
"entries_checked": entries_checked,
|
|
444
458
|
"first_tampered_line": line_num,
|
|
459
|
+
"last_hash": prev_hash,
|
|
445
460
|
}
|
|
446
461
|
|
|
447
462
|
stored_hash = entry.pop("_integrity_hash", None)
|
|
@@ -451,6 +466,7 @@ def verify_log_integrity(log_file: str) -> dict:
|
|
|
451
466
|
"valid": False,
|
|
452
467
|
"entries_checked": entries_checked,
|
|
453
468
|
"first_tampered_line": line_num,
|
|
469
|
+
"last_hash": prev_hash,
|
|
454
470
|
}
|
|
455
471
|
|
|
456
472
|
entry_json = json.dumps(entry, sort_keys=True, default=str)
|
|
@@ -461,12 +477,108 @@ def verify_log_integrity(log_file: str) -> dict:
|
|
|
461
477
|
"valid": False,
|
|
462
478
|
"entries_checked": entries_checked,
|
|
463
479
|
"first_tampered_line": line_num,
|
|
480
|
+
"last_hash": prev_hash,
|
|
464
481
|
}
|
|
465
482
|
|
|
466
483
|
prev_hash = stored_hash
|
|
467
484
|
entries_checked += 1
|
|
468
485
|
|
|
469
486
|
except FileNotFoundError:
|
|
470
|
-
return {"valid": True, "entries_checked": 0, "first_tampered_line": None
|
|
487
|
+
return {"valid": True, "entries_checked": 0, "first_tampered_line": None,
|
|
488
|
+
"last_hash": prev_hash}
|
|
489
|
+
|
|
490
|
+
# Normal exit (no rows or all rows passed): valid + carry last_hash forward
|
|
491
|
+
return {"valid": True, "entries_checked": entries_checked,
|
|
492
|
+
"first_tampered_line": None, "last_hash": prev_hash}
|
|
493
|
+
|
|
471
494
|
|
|
472
|
-
|
|
495
|
+
def _file_has_integrity(log_file: str) -> bool:
|
|
496
|
+
"""Return True iff the first non-empty entry in `log_file` has an
|
|
497
|
+
`_integrity_hash` field. Used by `verify_all_logs` to skip
|
|
498
|
+
pre-integrity-era files entirely (integrity hashing was introduced
|
|
499
|
+
after some audit logs already existed)."""
|
|
500
|
+
try:
|
|
501
|
+
with open(log_file, "r") as f:
|
|
502
|
+
for line in f:
|
|
503
|
+
line = line.strip()
|
|
504
|
+
if not line:
|
|
505
|
+
continue
|
|
506
|
+
try:
|
|
507
|
+
entry = json.loads(line)
|
|
508
|
+
except json.JSONDecodeError:
|
|
509
|
+
return False
|
|
510
|
+
return "_integrity_hash" in entry
|
|
511
|
+
except OSError:
|
|
512
|
+
return False
|
|
513
|
+
return False
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def verify_all_logs() -> dict:
|
|
517
|
+
"""v7.7.15: verify the entire audit chain across all rotated log files.
|
|
518
|
+
|
|
519
|
+
Walks `AUDIT_DIR/audit-*.jsonl` in chronological order, threading
|
|
520
|
+
the chain hash from one file to the next via `start_hash`. Skips
|
|
521
|
+
files from the pre-integrity era (files whose first entry has no
|
|
522
|
+
`_integrity_hash` field, because integrity hashing was introduced
|
|
523
|
+
after some audit logs already existed).
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
A dict with:
|
|
527
|
+
- valid (bool): True if the entire cross-file chain is intact.
|
|
528
|
+
- files_checked (int): Count of integrity-bearing files inspected.
|
|
529
|
+
- files_skipped (int): Count of pre-integrity files skipped.
|
|
530
|
+
- entries_checked (int): Total entries verified across all files.
|
|
531
|
+
- first_tampered_file (str | None): Path to the first file
|
|
532
|
+
whose chain broke, or None if valid.
|
|
533
|
+
- first_tampered_line (int | None): 1-based line number in
|
|
534
|
+
that file where the chain broke, or None if valid.
|
|
535
|
+
- genesis_file (str | None): Path to the first integrity-bearing
|
|
536
|
+
log file (the chain's genesis on this machine), or None if
|
|
537
|
+
no integrity-bearing files exist.
|
|
538
|
+
"""
|
|
539
|
+
if not AUDIT_DIR.exists():
|
|
540
|
+
return {"valid": True, "files_checked": 0, "files_skipped": 0,
|
|
541
|
+
"entries_checked": 0, "first_tampered_file": None,
|
|
542
|
+
"first_tampered_line": None, "genesis_file": None}
|
|
543
|
+
# v7.7.15 council fix (Opus 2): rotated files have name shape
|
|
544
|
+
# `audit-YYYY-MM-DD.HHMMSS.jsonl` (from `_rotate_logs_if_needed` at
|
|
545
|
+
# line 167). Lexicographic sort puts `audit-2026-05-04.123456.jsonl`
|
|
546
|
+
# BEFORE `audit-2026-05-04.jsonl` (because `.1` < `.j` ASCII), which
|
|
547
|
+
# would break chain ordering and false-negative on any user who hit
|
|
548
|
+
# size-based rotation. Sort by mtime instead -- mirrors what
|
|
549
|
+
# `_cleanup_old_logs` already does at line 178.
|
|
550
|
+
files = sorted(AUDIT_DIR.glob("audit-*.jsonl"), key=lambda p: p.stat().st_mtime)
|
|
551
|
+
prev_hash = "0" * 64
|
|
552
|
+
total_entries = 0
|
|
553
|
+
files_checked = 0
|
|
554
|
+
files_skipped = 0
|
|
555
|
+
genesis_file = None
|
|
556
|
+
for log_file in files:
|
|
557
|
+
if genesis_file is None and not _file_has_integrity(str(log_file)):
|
|
558
|
+
files_skipped += 1
|
|
559
|
+
continue
|
|
560
|
+
if genesis_file is None:
|
|
561
|
+
genesis_file = str(log_file)
|
|
562
|
+
result = verify_log_integrity(str(log_file), start_hash=prev_hash)
|
|
563
|
+
files_checked += 1
|
|
564
|
+
total_entries += result.get("entries_checked", 0)
|
|
565
|
+
if not result.get("valid", False):
|
|
566
|
+
return {
|
|
567
|
+
"valid": False,
|
|
568
|
+
"files_checked": files_checked,
|
|
569
|
+
"files_skipped": files_skipped,
|
|
570
|
+
"entries_checked": total_entries,
|
|
571
|
+
"first_tampered_file": str(log_file),
|
|
572
|
+
"first_tampered_line": result.get("first_tampered_line"),
|
|
573
|
+
"genesis_file": genesis_file,
|
|
574
|
+
}
|
|
575
|
+
prev_hash = result.get("last_hash", prev_hash)
|
|
576
|
+
return {
|
|
577
|
+
"valid": True,
|
|
578
|
+
"files_checked": files_checked,
|
|
579
|
+
"files_skipped": files_skipped,
|
|
580
|
+
"entries_checked": total_entries,
|
|
581
|
+
"first_tampered_file": None,
|
|
582
|
+
"first_tampered_line": None,
|
|
583
|
+
"genesis_file": genesis_file,
|
|
584
|
+
}
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -34,6 +34,18 @@ So v1 keeps the N-process model and adds a publish/subscribe channel. v2 (out of
|
|
|
34
34
|
|
|
35
35
|
## 3. Prerequisite: fix the orphan `pending_diagnostics` reference
|
|
36
36
|
|
|
37
|
+
> **UPDATE 2026-05-27 (v7.7.14):** RESOLVED. The fix described below
|
|
38
|
+
> shipped in v7.7.14. `LSPClient` now spawns a dedicated daemon reader
|
|
39
|
+
> thread at end of `start()` that owns `proc.stdout`, routes responses
|
|
40
|
+
> by id to per-request `Queue`s, and routes `publishDiagnostics` into
|
|
41
|
+
> `self.pending_diagnostics`. `request()` parks on its Queue instead
|
|
42
|
+
> of reading stdout. Re-spawn after crash drains old reader cleanly.
|
|
43
|
+
> Reader-death drain pushes error sentinel to all pending waiters.
|
|
44
|
+
> See `mcp/lsp_proxy.py` and `tests/test-lsp-diagnostics-regression.sh`
|
|
45
|
+
> (5/5 PASS). The broadcast layer described in sections 4-13 can now
|
|
46
|
+
> be built on top of a working reader. The section below is kept for
|
|
47
|
+
> historical context.
|
|
48
|
+
|
|
37
49
|
`lsp_proxy.py` line 867 (in `lsp_get_diagnostics`) reads:
|
|
38
50
|
|
|
39
51
|
```python
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var _7=Object.defineProperty;var I7=(K)=>K;function P7(K,$){this[K]=I7.bind(null,$)}var b=(K,$)=>{for(var z in $)_7(K,z,{get:$[z],enumerable:!0,configurable:!0,set:P7.bind($,z)})};var R=(K,$)=>()=>(K&&($=K(K=0)),$);var V1=import.meta.require;var e1={};b(e1,{lokiDir:()=>P,homeLokiDir:()=>k1,findRepoRootForVersion:()=>N1,REPO_ROOT:()=>p});import{resolve as u,dirname as S1}from"path";import{fileURLToPath as L7}from"url";import{existsSync as J1}from"fs";import{homedir as R7}from"os";function E7(){let K=i1;for(let $=0;$<6;$++){if(J1(u(K,"VERSION"))&&J1(u(K,"autonomy/run.sh")))return K;let z=S1(K);if(z===K)break;K=z}return u(i1,"..","..","..")}function N1(K){let $=K;for(let z=0;z<6;z++){if(J1(u($,"VERSION"))&&J1(u($,"autonomy/run.sh")))return $;let Q=S1($);if(Q===$)break;$=Q}return u(K,"..","..","..")}function P(){return process.env.LOKI_DIR??u(process.cwd(),".loki")}function k1(){return u(R7(),".loki")}var i1,p;var y=R(()=>{i1=S1(L7(import.meta.url));p=E7()});import{readFileSync as x7}from"fs";import{resolve as F7,dirname as w7}from"path";import{fileURLToPath as S7}from"url";function G1(){if(n!==null)return n;let K="7.7.
|
|
2
|
+
var _7=Object.defineProperty;var I7=(K)=>K;function P7(K,$){this[K]=I7.bind(null,$)}var b=(K,$)=>{for(var z in $)_7(K,z,{get:$[z],enumerable:!0,configurable:!0,set:P7.bind($,z)})};var R=(K,$)=>()=>(K&&($=K(K=0)),$);var V1=import.meta.require;var e1={};b(e1,{lokiDir:()=>P,homeLokiDir:()=>k1,findRepoRootForVersion:()=>N1,REPO_ROOT:()=>p});import{resolve as u,dirname as S1}from"path";import{fileURLToPath as L7}from"url";import{existsSync as J1}from"fs";import{homedir as R7}from"os";function E7(){let K=i1;for(let $=0;$<6;$++){if(J1(u(K,"VERSION"))&&J1(u(K,"autonomy/run.sh")))return K;let z=S1(K);if(z===K)break;K=z}return u(i1,"..","..","..")}function N1(K){let $=K;for(let z=0;z<6;z++){if(J1(u($,"VERSION"))&&J1(u($,"autonomy/run.sh")))return $;let Q=S1($);if(Q===$)break;$=Q}return u(K,"..","..","..")}function P(){return process.env.LOKI_DIR??u(process.cwd(),".loki")}function k1(){return u(R7(),".loki")}var i1,p;var y=R(()=>{i1=S1(L7(import.meta.url));p=E7()});import{readFileSync as x7}from"fs";import{resolve as F7,dirname as w7}from"path";import{fileURLToPath as S7}from"url";function G1(){if(n!==null)return n;let K="7.7.16";if(typeof K==="string"&&K.length>0)return n=K,n;try{let $=w7(S7(import.meta.url)),z=N1($);n=x7(F7(z,"VERSION"),"utf-8").trim()}catch{n="unknown"}return n}var n=null;var D1=R(()=>{y()});var $0={};b($0,{runOrThrow:()=>N7,run:()=>S,commandVersion:()=>D7,commandExists:()=>D,ShellError:()=>C1});async function S(K,$={}){let z=Bun.spawn({cmd:[...K],stdout:"pipe",stderr:"pipe",env:$.env?{...process.env,...$.env}:process.env,cwd:$.cwd}),Q,X;if($.timeoutMs&&$.timeoutMs>0)Q=setTimeout(()=>{try{z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{z.kill("SIGKILL")}catch{}},2000)},$.timeoutMs);try{let[H,Z,q]=await Promise.all([new Response(z.stdout).text(),new Response(z.stderr).text(),z.exited]);return{stdout:H,stderr:Z,exitCode:q}}finally{if(Q)clearTimeout(Q);if(X)clearTimeout(X)}}async function N7(K,$={}){let z=await S(K,$);if(z.exitCode!==0)throw new C1(`command failed (${z.exitCode}): ${K.join(" ")}`,z.exitCode,z.stdout,z.stderr);return z}async function D(K){let $=k7(K),z=await S(["sh","-c",`command -v ${$}`],{timeoutMs:5000});if(z.exitCode===0)return z.stdout.trim()||null;return null}function k7(K){if(!/^[A-Za-z0-9._/-]+$/.test(K))throw Error(`refused to shell-escape suspect token: ${K}`);return K}async function D7(K,$="--version"){if(!await D(K))return null;let Q=await S([K,$],{timeoutMs:5000});if(Q.exitCode!==0)return null;return((Q.stdout||Q.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var C1;var c=R(()=>{C1=class C1 extends Error{message;exitCode;stdout;stderr;constructor(K,$,z,Q){super(K);this.message=K;this.exitCode=$;this.stdout=z;this.stderr=Q;this.name="ShellError"}}});function l(K){return C7?"":K}var C7,E,C,x,O6,O,k,F,W;var a=R(()=>{C7=(process.env.NO_COLOR??"").length>0;E=l("\x1B[0;31m"),C=l("\x1B[0;32m"),x=l("\x1B[1;33m"),O6=l("\x1B[0;34m"),O=l("\x1B[0;36m"),k=l("\x1B[1m"),F=l("\x1B[2m"),W=l("\x1B[0m")});import{existsSync as c7}from"fs";async function t(){if(z1!==void 0)return z1;let K="/opt/homebrew/bin/python3.12";if(c7(K))return z1=K,K;let $=await D("python3.12");if($)return z1=$,$;let z=await D("python3");return z1=z,z}async function s(K,$={}){let z=await t();if(!z)return{stdout:"",stderr:"python3 not found",exitCode:127};return S([z,"-c",K],$)}var z1;var Q1=R(()=>{c()});var G0={};b(G0,{runStatus:()=>z5});import{existsSync as N,readFileSync as Z1,readdirSync as H0,statSync as W0}from"fs";import{resolve as w,basename as a7}from"path";async function r7(){if(await D("jq"))return!0;return process.stdout.write(`${E}Error: jq is required but not installed.${W}
|
|
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)
|
|
@@ -584,4 +584,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
584
584
|
`),2}default:return process.stderr.write(`Unknown command: ${$}
|
|
585
585
|
`),process.stderr.write(j7),2}}process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var z6=await $6(Bun.argv.slice(2));process.exit(z6);
|
|
586
586
|
|
|
587
|
-
//# debugId=
|
|
587
|
+
//# debugId=C68414E7616DB4C664756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
|
-
"version": "7.7.
|
|
3
|
+
"version": "7.7.16",
|
|
4
4
|
"description": "Loki Mode by Autonomi. Multi-agent autonomous SDLC framework. Spec to deployed app: PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief. 4 AI providers (Claude Code, OpenAI Codex, Cline, Aider). 11 quality gates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|