loki-mode 7.86.0 → 7.88.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/autonomy/loki CHANGED
@@ -16500,6 +16500,16 @@ main() {
16500
16500
  # Receipt surface): same subcommands (list/show/verify/open/share).
16501
16501
  cmd_proof "$@"
16502
16502
  ;;
16503
+ secure)
16504
+ # Secure-by-default gate surface: inspect findings + manage waivers.
16505
+ cmd_secure "$@"
16506
+ ;;
16507
+ own|handoff)
16508
+ # Finish-and-own: a plain-English ownership handoff for a non-technical
16509
+ # owner (what was built, is it working, how to run/deploy, what is left).
16510
+ # `loki handoff` is an alias for `loki own`.
16511
+ cmd_own "$@"
16512
+ ;;
16503
16513
  bench)
16504
16514
  cmd_bench "$@"
16505
16515
  ;;
@@ -30434,6 +30444,164 @@ cmd_bench() {
30434
30444
  bash "$bench_sh" "$@"
30435
30445
  }
30436
30446
 
30447
+ # loki own - finish-and-own (v7.88.0): a plain-English ownership handoff for a
30448
+ # NON-technical owner. A pure render (autonomy/lib/own-render.py) over the data
30449
+ # Loki already captured -- the Evidence Receipt, the completion summary, and
30450
+ # USAGE.md -- so it cannot fabricate: the "is it working?" verdict comes verbatim
30451
+ # from the receipt's honest headline and is never green unless the receipt is
30452
+ # VERIFIED. Default prints the doc; --md writes HANDOFF.md; --json for tooling.
30453
+ # `loki handoff` is an alias.
30454
+ cmd_own() {
30455
+ local renderer="${_LOKI_SCRIPT_DIR}/lib/own-render.py"
30456
+ if [ ! -f "$renderer" ]; then
30457
+ echo -e "${RED}Finish-and-own renderer not found (autonomy/lib/own-render.py).${NC}" >&2
30458
+ exit 2
30459
+ fi
30460
+ case "${1:-}" in
30461
+ --help|-h|help)
30462
+ echo -e "${BOLD}loki own${NC} - a plain-English ownership handoff (alias: loki handoff)"
30463
+ echo ""
30464
+ echo "Usage: loki own [--md | --json]"
30465
+ echo ""
30466
+ echo "Explains, in plain language for a non-technical owner: what was"
30467
+ echo "built, whether Loki verified it works, how to run it, how to put"
30468
+ echo "it online, what a developer needs to know, and what is left to do."
30469
+ echo "It reads the last build's Evidence Receipt + completion summary;"
30470
+ echo "the 'is it working' verdict is the receipt's honest headline (never"
30471
+ echo "green unless the build is VERIFIED)."
30472
+ echo ""
30473
+ echo "Options:"
30474
+ echo " --md Write HANDOFF.md to the project root"
30475
+ echo " --json Emit the structured handoff as JSON"
30476
+ exit 0
30477
+ ;;
30478
+ esac
30479
+ # --md writes HANDOFF.md at the project root (matches the help). The renderer
30480
+ # prints markdown on stdout; the CLI places it as a file, atomically (temp+mv)
30481
+ # so a partial write never leaves a truncated HANDOFF.md. Any other args
30482
+ # (--json, default) pass through and print.
30483
+ if [ "${1:-}" = "--md" ]; then
30484
+ local _handoff="${TARGET_DIR:-.}/HANDOFF.md"
30485
+ local _handoff_tmp="${TARGET_DIR:-.}/.HANDOFF.md.tmp"
30486
+ if python3 "$renderer" --loki-dir "${LOKI_DIR:-.loki}" --md > "$_handoff_tmp" 2>/dev/null; then
30487
+ mv -f "$_handoff_tmp" "$_handoff" && \
30488
+ echo -e "${GREEN}Wrote ${_handoff}${NC} - open it or hand it to whoever owns this build." || \
30489
+ { rm -f "$_handoff_tmp" 2>/dev/null; echo -e "${RED}Could not write HANDOFF.md${NC}" >&2; exit 1; }
30490
+ else
30491
+ rm -f "$_handoff_tmp" 2>/dev/null
30492
+ echo -e "${RED}Could not render the ownership handoff${NC}" >&2
30493
+ exit 1
30494
+ fi
30495
+ exit 0
30496
+ fi
30497
+ python3 "$renderer" --loki-dir "${LOKI_DIR:-.loki}" "$@"
30498
+ exit $?
30499
+ }
30500
+
30501
+ # loki secure - the secure-by-default gate surface (v7.87.0).
30502
+ # Subcommands: list (show findings) | waive <rule> <file> [reason] | unwaive.
30503
+ # Waivers are written to .loki/quality/security-waivers.json, which the gate
30504
+ # (run.sh run_secure_scan) and the Evidence Receipt both READ + honor. The gate
30505
+ # is advisory by default; LOKI_SECURE_GATE=block makes un-waived HIGH findings
30506
+ # block. Honest: a waiver is RECORDED in the receipt (accepted with intent), never
30507
+ # silently hides a finding.
30508
+ cmd_secure() {
30509
+ local loki_dir="${LOKI_DIR:-.loki}"
30510
+ local quality_dir="${loki_dir}/quality"
30511
+ local findings_file="${quality_dir}/security-findings.json"
30512
+ local waivers_file="${quality_dir}/security-waivers.json"
30513
+ local sub="${1:-}"
30514
+ [ $# -gt 0 ] && shift
30515
+ case "$sub" in
30516
+ ""|--help|-h|help)
30517
+ echo -e "${BOLD}loki secure${NC} - secure-by-default gate: findings + waivers"
30518
+ echo ""
30519
+ echo "Usage: loki secure <subcommand> [args]"
30520
+ echo ""
30521
+ echo "Subcommands:"
30522
+ echo " list Show security findings from the last scan"
30523
+ echo " waive <rule> <file> [reason] Waive a finding (accepted with intent)"
30524
+ echo " unwaive <rule> <file> Remove a waiver"
30525
+ echo ""
30526
+ echo "The gate is advisory by default; set LOKI_SECURE_GATE=block to make"
30527
+ echo "un-waived HIGH findings block completion. Waivers are recorded in the"
30528
+ echo "Evidence Receipt (loki proof show) -- they are never hidden."
30529
+ [ "$sub" = "" ] && exit 1
30530
+ exit 0
30531
+ ;;
30532
+ list)
30533
+ if [ ! -f "$findings_file" ]; then
30534
+ echo -e "${YELLOW}No security scan results yet.${NC} Run 'loki start' (the gate runs in the review phase)."
30535
+ exit 0
30536
+ fi
30537
+ if command -v jq &>/dev/null; then
30538
+ jq '.findings' "$findings_file" 2>/dev/null || cat "$findings_file"
30539
+ else
30540
+ LOKI_SEC_F="$findings_file" python3 -c "import json,os; d=json.load(open(os.environ['LOKI_SEC_F'])); [print('%s [%s] %s%s -- %s' % (f.get('severity','?'), f.get('rule','?'), f.get('file','?'), (':'+str(f['line'])) if f.get('line') else '', f.get('fix',''))) for f in d.get('findings',[])] or print('No findings.')"
30541
+ fi
30542
+ exit 0
30543
+ ;;
30544
+ waive)
30545
+ local rule="${1:-}" file="${2:-}" reason="${3:-waived via loki secure}"
30546
+ if [ -z "$rule" ] || [ -z "$file" ]; then
30547
+ echo -e "${RED}Usage: loki secure waive <rule> <file> [reason]${NC}" >&2
30548
+ exit 2
30549
+ fi
30550
+ mkdir -p "$quality_dir"
30551
+ LOKI_SEC_W="$waivers_file" LOKI_SEC_RULE="$rule" LOKI_SEC_FILE="$file" LOKI_SEC_REASON="$reason" python3 - <<'PYW'
30552
+ import json, os
30553
+ p = os.environ["LOKI_SEC_W"]
30554
+ try:
30555
+ with open(p) as f: data = json.load(f)
30556
+ if not isinstance(data, dict): data = {}
30557
+ except Exception:
30558
+ data = {}
30559
+ waivers = data.get("waivers")
30560
+ if not isinstance(waivers, list): waivers = []
30561
+ rule, fl, reason = os.environ["LOKI_SEC_RULE"], os.environ["LOKI_SEC_FILE"], os.environ["LOKI_SEC_REASON"]
30562
+ if not any(w.get("rule") == rule and w.get("file") == fl for w in waivers if isinstance(w, dict)):
30563
+ waivers.append({"rule": rule, "file": fl, "reason": reason})
30564
+ data["waivers"] = waivers
30565
+ tmp = p + ".tmp"
30566
+ with open(tmp, "w") as f: json.dump(data, f, indent=2)
30567
+ os.replace(tmp, p)
30568
+ print("Waived %s on %s (recorded in the Evidence Receipt)." % (rule, fl))
30569
+ PYW
30570
+ exit $?
30571
+ ;;
30572
+ unwaive)
30573
+ local rule="${1:-}" file="${2:-}"
30574
+ if [ -z "$rule" ] || [ -z "$file" ]; then
30575
+ echo -e "${RED}Usage: loki secure unwaive <rule> <file>${NC}" >&2
30576
+ exit 2
30577
+ fi
30578
+ [ -f "$waivers_file" ] || { echo "No waivers to remove."; exit 0; }
30579
+ LOKI_SEC_W="$waivers_file" LOKI_SEC_RULE="$rule" LOKI_SEC_FILE="$file" python3 - <<'PYU'
30580
+ import json, os
30581
+ p = os.environ["LOKI_SEC_W"]
30582
+ try:
30583
+ with open(p) as f: data = json.load(f)
30584
+ except Exception:
30585
+ data = {}
30586
+ waivers = [w for w in (data.get("waivers") or [])
30587
+ if not (isinstance(w, dict) and w.get("rule") == os.environ["LOKI_SEC_RULE"]
30588
+ and w.get("file") == os.environ["LOKI_SEC_FILE"])]
30589
+ data["waivers"] = waivers
30590
+ tmp = p + ".tmp"
30591
+ with open(tmp, "w") as f: json.dump(data, f, indent=2)
30592
+ os.replace(tmp, p)
30593
+ print("Removed waiver for %s on %s." % (os.environ["LOKI_SEC_RULE"], os.environ["LOKI_SEC_FILE"]))
30594
+ PYU
30595
+ exit $?
30596
+ ;;
30597
+ *)
30598
+ echo -e "${RED}Unknown subcommand: secure $sub${NC}" >&2
30599
+ echo "Try: loki secure --help"
30600
+ exit 2
30601
+ ;;
30602
+ esac
30603
+ }
30604
+
30437
30605
  # loki proof - inspect and share proof-of-run artifacts (.loki/proofs/<id>/).
30438
30606
  # Subcommands: list | show <id> | open <id> | share <id>.
30439
30607
  # The proof.json schema is frozen (R1 spec). Reads are tolerant of missing
package/autonomy/run.sh CHANGED
@@ -7309,6 +7309,157 @@ SAFEOF
7309
7309
  fi
7310
7310
  }
7311
7311
 
7312
+ # ============================================================================
7313
+ # Secure-by-default scan (v7.87.0 - Loop 4)
7314
+ # Runs the high-precision rule engine (autonomy/lib/secure-scan.py) over the
7315
+ # generated app and reports known-bad security patterns.
7316
+ #
7317
+ # ADVISORY BY DEFAULT (mirrors the ktlint/detekt advisory linters above):
7318
+ # findings are reported via log_warn + the receipt json, but do NOT block. This
7319
+ # guarantees no existing build starts blocking on this new gate.
7320
+ #
7321
+ # OPT-IN BLOCK: only when LOKI_SECURE_GATE=block do un-waived HIGH findings
7322
+ # cause a blocking gate failure (return 1, same mechanism the other gates use).
7323
+ #
7324
+ # Waivers: .loki/quality/security-waivers.json ({"waivers":[{rule,file},...]})
7325
+ # is READ here and honored (matched findings recorded as waived, never counted
7326
+ # active). The waiver-write surface is a separate slice.
7327
+ #
7328
+ # Honest degrade: if python3 or secure-scan.py is absent, pass through cleanly
7329
+ # (no crash, no block), exactly like the optional linters.
7330
+ # ============================================================================
7331
+ run_secure_scan() {
7332
+ local loki_dir="${TARGET_DIR:-.}/.loki"
7333
+ local quality_dir="$loki_dir/quality"
7334
+ mkdir -p "$quality_dir"
7335
+
7336
+ local out_file="$quality_dir/security-findings.json"
7337
+ local waivers_file="$quality_dir/security-waivers.json"
7338
+ local scanner="$SCRIPT_DIR/lib/secure-scan.py"
7339
+
7340
+ # Honest pass-through if the engine or python3 is unavailable. Still write a
7341
+ # valid (empty) receipt so downstream consumers never read malformed JSON.
7342
+ if ! command -v python3 >/dev/null 2>&1 || [ ! -f "$scanner" ]; then
7343
+ cat > "$out_file" << 'SECEMPTY'
7344
+ {"rules_version":null,"findings":[],"summary":{"total":0,"by_severity":{}},"skipped":"scanner-unavailable"}
7345
+ SECEMPTY
7346
+ log_info "Security scan: secure-scan.py or python3 not available, skipping (pass-through)"
7347
+ return 0
7348
+ fi
7349
+
7350
+ # Run the scanner. exit 0 = no findings, 1 = findings, 2 = bad input.
7351
+ local raw rc=0
7352
+ raw=$(python3 "$scanner" "${TARGET_DIR:-.}" --json 2>/dev/null) || rc=$?
7353
+ if [ "$rc" -eq 2 ] || [ -z "$raw" ]; then
7354
+ cat > "$out_file" << 'SECEMPTY'
7355
+ {"rules_version":null,"findings":[],"summary":{"total":0,"by_severity":{}},"skipped":"scanner-error"}
7356
+ SECEMPTY
7357
+ log_info "Security scan: scanner returned no parseable output, skipping (pass-through)"
7358
+ return 0
7359
+ fi
7360
+
7361
+ # Apply waivers, build the receipt json, and emit a machine-readable verdict.
7362
+ # All policy lives in this one python pass so the bash stays bash-3.2 safe.
7363
+ # It prints a final line: ACTIVE_HIGH=<n>\tACTIVE_TOTAL=<n>\tWAIVED=<n>
7364
+ # and writes the enriched receipt (findings carry a "waived" bool).
7365
+ local verdict
7366
+ verdict=$(_SEC_RAW="$raw" _SEC_WAIVERS="$waivers_file" _SEC_OUT="$out_file" python3 -c '
7367
+ import json, os, sys
7368
+ raw = os.environ.get("_SEC_RAW", "")
7369
+ waivers_file = os.environ.get("_SEC_WAIVERS", "")
7370
+ out_file = os.environ.get("_SEC_OUT", "")
7371
+
7372
+ try:
7373
+ data = json.loads(raw)
7374
+ except Exception:
7375
+ data = {"rules_version": None, "findings": [], "summary": {"total": 0, "by_severity": {}}}
7376
+
7377
+ # Load waivers: {"waivers":[{"rule":..,"file":..}, ...]}. Match on rule+file.
7378
+ waived_set = set()
7379
+ try:
7380
+ with open(waivers_file) as f:
7381
+ wdoc = json.load(f)
7382
+ for w in wdoc.get("waivers", []):
7383
+ r = w.get("rule"); fl = w.get("file")
7384
+ if r is not None and fl is not None:
7385
+ waived_set.add((r, fl))
7386
+ except (OSError, json.JSONDecodeError, AttributeError):
7387
+ pass
7388
+
7389
+ findings = data.get("findings", []) or []
7390
+ active_high = 0
7391
+ active_total = 0
7392
+ waived_count = 0
7393
+ for fnd in findings:
7394
+ key = (fnd.get("rule"), fnd.get("file"))
7395
+ is_waived = key in waived_set
7396
+ fnd["waived"] = is_waived
7397
+ if is_waived:
7398
+ waived_count += 1
7399
+ else:
7400
+ active_total += 1
7401
+ if str(fnd.get("severity", "")).upper() == "HIGH":
7402
+ active_high += 1
7403
+
7404
+ data["waived"] = waived_count
7405
+ data["active"] = active_total
7406
+ try:
7407
+ with open(out_file, "w") as f:
7408
+ json.dump(data, f, indent=2)
7409
+ except OSError:
7410
+ pass
7411
+
7412
+ sys.stdout.write("ACTIVE_HIGH=%d\tACTIVE_TOTAL=%d\tWAIVED=%d" % (active_high, active_total, waived_count))
7413
+ ' 2>/dev/null) || verdict=""
7414
+
7415
+ if [ -z "$verdict" ]; then
7416
+ # python policy pass failed unexpectedly; preserve the raw scan as the
7417
+ # receipt so nothing is lost, and pass through (never crash the gate).
7418
+ printf '%s\n' "$raw" > "$out_file" 2>/dev/null || true
7419
+ log_info "Security scan: result recorded (policy pass unavailable, advisory)"
7420
+ return 0
7421
+ fi
7422
+
7423
+ local active_high active_total waived
7424
+ active_high=$(printf '%s' "$verdict" | sed -n 's/.*ACTIVE_HIGH=\([0-9]*\).*/\1/p')
7425
+ active_total=$(printf '%s' "$verdict" | sed -n 's/.*ACTIVE_TOTAL=\([0-9]*\).*/\1/p')
7426
+ waived=$(printf '%s' "$verdict" | sed -n 's/.*WAIVED=\([0-9]*\).*/\1/p')
7427
+ active_high=${active_high:-0}
7428
+ active_total=${active_total:-0}
7429
+ waived=${waived:-0}
7430
+
7431
+ if [ "$active_total" -eq 0 ]; then
7432
+ log_info "Security scan: no active findings (waived: $waived)"
7433
+ return 0
7434
+ fi
7435
+
7436
+ # Actionable advisory summary: rule + file + fix, from the receipt json.
7437
+ log_warn "Security scan: $active_total active finding(s) (HIGH: $active_high, waived: $waived)"
7438
+ _SEC_OUT="$out_file" python3 -c '
7439
+ import json, os
7440
+ try:
7441
+ with open(os.environ["_SEC_OUT"]) as f:
7442
+ data = json.load(f)
7443
+ except Exception:
7444
+ data = {"findings": []}
7445
+ for fnd in data.get("findings", []):
7446
+ if fnd.get("waived"):
7447
+ continue
7448
+ print(" [%s] %s %s:%s -- %s | fix: %s" % (
7449
+ fnd.get("severity", "?"), fnd.get("rule", "?"),
7450
+ fnd.get("file", "?"), fnd.get("line", "?"),
7451
+ fnd.get("message", ""), fnd.get("fix", "")))
7452
+ ' 2>/dev/null | while IFS= read -r line; do log_warn "$line"; done
7453
+
7454
+ # OPT-IN BLOCK: only un-waived HIGH findings block, and only when explicitly
7455
+ # enabled. Advisory default returns 0 (never surprise-blocks).
7456
+ if [ "${LOKI_SECURE_GATE:-advisory}" = "block" ] && [ "$active_high" -gt 0 ]; then
7457
+ log_warn "Security gate: $active_high un-waived HIGH finding(s) - BLOCK (LOKI_SECURE_GATE=block)"
7458
+ return 1
7459
+ fi
7460
+ return 0
7461
+ }
7462
+
7312
7463
  #===============================================================================
7313
7464
  # Gate Failure Tracking (v6.10.0)
7314
7465
  #===============================================================================
@@ -15262,6 +15413,18 @@ if __name__ == "__main__":
15262
15413
  log_warn "Static analysis FAILED ($sa_count consecutive) - findings injected into next iteration"
15263
15414
  fi
15264
15415
  fi
15416
+ # Secure-by-default scan (v7.87.0). Advisory by default (never
15417
+ # blocks); records .loki/quality/security-findings.json each
15418
+ # iteration. Blocks only on un-waived HIGH when LOKI_SECURE_GATE=block.
15419
+ log_info "Quality gate: security scan (advisory)..."
15420
+ if run_secure_scan; then
15421
+ clear_gate_failure "security_scan"
15422
+ else
15423
+ local sec_count
15424
+ sec_count=$(track_gate_failure "security_scan")
15425
+ gate_failures="${gate_failures}security_scan,"
15426
+ log_warn "Security gate BLOCKED ($sec_count consecutive) - un-waived HIGH findings (LOKI_SECURE_GATE=block)"
15427
+ fi
15265
15428
  # BUG-ST-002: Check pause signal between quality gates
15266
15429
  if [ -f "${TARGET_DIR:-.}/.loki/PAUSE" ] || [ -f "${TARGET_DIR:-.}/.loki/STOP" ]; then
15267
15430
  log_warn "Pause/stop signal detected between quality gates - deferring remaining gates"
@@ -17204,6 +17367,30 @@ main() {
17204
17367
  generate_proof_of_run "$result" || true
17205
17368
  fi
17206
17369
 
17370
+ # Finish-and-own (v7.88.0): write a plain-English ownership handoff
17371
+ # (HANDOFF.md) for a non-technical owner. Runs AFTER the proof so the
17372
+ # "is it working?" verdict reads the receipt's honest headline. Default-on,
17373
+ # opt out with LOKI_HANDOFF=0. Fire-and-forget: best-effort, never blocks
17374
+ # completion (same contract as the proof + usage-regen). A pure render over
17375
+ # the proof + completion + USAGE.md, so it cannot fabricate.
17376
+ if [ "${LOKI_HANDOFF:-1}" != "0" ]; then
17377
+ local _own_render="$SCRIPT_DIR/lib/own-render.py"
17378
+ if [ -f "$_own_render" ] && command -v python3 >/dev/null 2>&1; then
17379
+ # The renderer prints the plain-English doc on stdout (--md); the hook
17380
+ # places it at the project root as HANDOFF.md. Write to a temp then
17381
+ # move, so a partial write never leaves a truncated HANDOFF.md.
17382
+ local _handoff_dir _handoff_md _handoff_tmp
17383
+ _handoff_dir="${TARGET_DIR:-.}"
17384
+ _handoff_md="$_handoff_dir/HANDOFF.md"
17385
+ _handoff_tmp="$_handoff_dir/.HANDOFF.md.tmp"
17386
+ if python3 "$_own_render" --loki-dir "$LOKI_DIR" --md > "$_handoff_tmp" 2>/dev/null; then
17387
+ mv -f "$_handoff_tmp" "$_handoff_md" 2>/dev/null || rm -f "$_handoff_tmp" 2>/dev/null || true
17388
+ else
17389
+ rm -f "$_handoff_tmp" 2>/dev/null || true
17390
+ fi
17391
+ fi
17392
+ fi
17393
+
17207
17394
  # R7 (zero-config first run): "what next / go deeper" framing. Only when the
17208
17395
  # CLI flagged this as a TTFV first run and stdout is a TTY, so it stays
17209
17396
  # silent in CI / pipes and never fires for normal PRD runs. The wording
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.86.0"
10
+ __version__ = "7.88.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -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:** v7.86.0
5
+ **Version:** v7.88.0
6
6
 
7
7
  ---
8
8
 
@@ -395,7 +395,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
395
395
  # Run Loki Mode in Docker (Claude provider, API-key auth)
396
396
  docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
397
397
  -v $(pwd):/workspace -w /workspace \
398
- asklokesh/loki-mode:7.86.0 start ./my-spec.md
398
+ asklokesh/loki-mode:7.88.0 start ./my-spec.md
399
399
  ```
400
400
 
401
401
  ##### docker compose + .env (no host install)
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var QQ=Object.defineProperty;var ZQ=($)=>$;function zQ($,Q){this[$]=ZQ.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)QQ($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:zQ.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var q$=import.meta.require;var h1={};b(h1,{lokiDir:()=>P,homeLokiDir:()=>i$,findRepoRootForVersion:()=>t$,REPO_ROOT:()=>g});import{resolve as a,dirname as r$}from"path";import{fileURLToPath as XQ}from"url";import{existsSync as R$}from"fs";import{homedir as KQ}from"os";function qQ(){let $=b1;for(let Q=0;Q<6;Q++){if(R$(a($,"VERSION"))&&R$(a($,"autonomy/run.sh")))return $;let Z=r$($);if(Z===$)break;$=Z}return a(b1,"..","..","..")}function t$($){let Q=$;for(let Z=0;Z<6;Z++){if(R$(a(Q,"VERSION"))&&R$(a(Q,"autonomy/run.sh")))return Q;let z=r$(Q);if(z===Q)break;Q=z}return a($,"..","..","..")}function P(){return process.env.LOKI_DIR??a(process.cwd(),".loki")}function i$(){return a(KQ(),".loki")}var b1,g;var C=L(()=>{b1=r$(XQ(import.meta.url));g=qQ()});import{readFileSync as VQ}from"fs";import{resolve as JQ,dirname as UQ}from"path";import{fileURLToPath as WQ}from"url";function E$(){if(Q$!==null)return Q$;let $="7.86.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=UQ(WQ(import.meta.url)),Z=t$(Q);Q$=VQ(JQ(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var e$=L(()=>{C()});var g1={};b(g1,{runOrThrow:()=>HQ,run:()=>F,readStreamCapped:()=>m1,commandVersion:()=>BQ,commandExists:()=>f,ShellError:()=>$1,MAX_STDOUT_BYTES:()=>v1});async function m1($,Q=v1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:U}=await Z.read();if(K)break;if(!U)continue;if(q+=U.byteLength,q>Q){let J=U.byteLength-(q-Q);X+=z.decode(U.subarray(0,J),{stream:!0});break}X+=z.decode(U,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function F($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,U]=await Promise.all([m1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function HQ($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new $1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=GQ($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function GQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function BQ($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var v1=16777216,$1;var d=L(()=>{$1=class $1 extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function s($){return YQ?"":$}var YQ,O,S,_,_Z,I,k,h,V;var c=L(()=>{YQ=(process.env.NO_COLOR??"").length>0;O=s("\x1B[0;31m"),S=s("\x1B[0;32m"),_=s("\x1B[1;33m"),_Z=s("\x1B[0;34m"),I=s("\x1B[0;36m"),k=s("\x1B[1m"),h=s("\x1B[2m"),V=s("\x1B[0m")});import{existsSync as jQ}from"fs";async function Z$(){if(Y$!==void 0)return Y$;let $="/opt/homebrew/bin/python3.12";if(jQ($))return Y$=$,$;let Q=await f("python3.12");if(Q)return Y$=Q,Q;let Z=await f("python3");return Y$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var Y$;var V$=L(()=>{d()});var X0={};b(X0,{runStatus:()=>oQ});import{existsSync as y,readFileSync as U$,readdirSync as r1,statSync as t1}from"fs";import{resolve as D,basename as vQ}from"path";import{homedir as mQ}from"os";function i1($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function e1($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*N$/Q);if(X>N$)X=N$;let q=N$-X,K=S;if(z>=80)K=O;else if(z>=50)K=_;let U="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),J=i1($),W=i1(Q);return` ${k}${Z}${V} ${K}[${U}]${V} ${z}% (${J} / ${W})`}async function fQ(){if(await f("jq"))return!0;return process.stdout.write(`${O}Error: jq is required but not installed.${V}
2
+ var QQ=Object.defineProperty;var ZQ=($)=>$;function zQ($,Q){this[$]=ZQ.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)QQ($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:zQ.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var q$=import.meta.require;var h1={};b(h1,{lokiDir:()=>P,homeLokiDir:()=>i$,findRepoRootForVersion:()=>t$,REPO_ROOT:()=>g});import{resolve as a,dirname as r$}from"path";import{fileURLToPath as XQ}from"url";import{existsSync as R$}from"fs";import{homedir as KQ}from"os";function qQ(){let $=b1;for(let Q=0;Q<6;Q++){if(R$(a($,"VERSION"))&&R$(a($,"autonomy/run.sh")))return $;let Z=r$($);if(Z===$)break;$=Z}return a(b1,"..","..","..")}function t$($){let Q=$;for(let Z=0;Z<6;Z++){if(R$(a(Q,"VERSION"))&&R$(a(Q,"autonomy/run.sh")))return Q;let z=r$(Q);if(z===Q)break;Q=z}return a($,"..","..","..")}function P(){return process.env.LOKI_DIR??a(process.cwd(),".loki")}function i$(){return a(KQ(),".loki")}var b1,g;var C=L(()=>{b1=r$(XQ(import.meta.url));g=qQ()});import{readFileSync as VQ}from"fs";import{resolve as JQ,dirname as UQ}from"path";import{fileURLToPath as WQ}from"url";function E$(){if(Q$!==null)return Q$;let $="7.88.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=UQ(WQ(import.meta.url)),Z=t$(Q);Q$=VQ(JQ(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var e$=L(()=>{C()});var g1={};b(g1,{runOrThrow:()=>HQ,run:()=>F,readStreamCapped:()=>m1,commandVersion:()=>BQ,commandExists:()=>f,ShellError:()=>$1,MAX_STDOUT_BYTES:()=>v1});async function m1($,Q=v1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:U}=await Z.read();if(K)break;if(!U)continue;if(q+=U.byteLength,q>Q){let J=U.byteLength-(q-Q);X+=z.decode(U.subarray(0,J),{stream:!0});break}X+=z.decode(U,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function F($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,U]=await Promise.all([m1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function HQ($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new $1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=GQ($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function GQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function BQ($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var v1=16777216,$1;var d=L(()=>{$1=class $1 extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function s($){return YQ?"":$}var YQ,O,S,_,_Z,I,k,h,V;var c=L(()=>{YQ=(process.env.NO_COLOR??"").length>0;O=s("\x1B[0;31m"),S=s("\x1B[0;32m"),_=s("\x1B[1;33m"),_Z=s("\x1B[0;34m"),I=s("\x1B[0;36m"),k=s("\x1B[1m"),h=s("\x1B[2m"),V=s("\x1B[0m")});import{existsSync as jQ}from"fs";async function Z$(){if(Y$!==void 0)return Y$;let $="/opt/homebrew/bin/python3.12";if(jQ($))return Y$=$,$;let Q=await f("python3.12");if(Q)return Y$=Q,Q;let Z=await f("python3");return Y$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var Y$;var V$=L(()=>{d()});var X0={};b(X0,{runStatus:()=>oQ});import{existsSync as y,readFileSync as U$,readdirSync as r1,statSync as t1}from"fs";import{resolve as D,basename as vQ}from"path";import{homedir as mQ}from"os";function i1($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function e1($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*N$/Q);if(X>N$)X=N$;let q=N$-X,K=S;if(z>=80)K=O;else if(z>=50)K=_;let U="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),J=i1($),W=i1(Q);return` ${k}${Z}${V} ${K}[${U}]${V} ${z}% (${J} / ${W})`}async function fQ(){if(await f("jq"))return!0;return process.stdout.write(`${O}Error: jq is required but not installed.${V}
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)
@@ -796,4 +796,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
796
796
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
797
797
  `),process.stderr.write($Q),2}}s1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var qZ=await KZ(Bun.argv.slice(2));process.exit(qZ);
798
798
 
799
- //# debugId=5E11AD9E3DCEB72F64756E2164756E21
799
+ //# debugId=65E93B4739145ECE64756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.86.0'
60
+ __version__ = '7.88.0'
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": "7.86.0",
4
+ "version": "7.88.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).",
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": "7.86.0",
5
+ "version": "7.88.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",