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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/own-render.py +547 -0
- package/autonomy/lib/proof-generator.py +64 -0
- package/autonomy/lib/secure-scan.py +652 -0
- package/autonomy/loki +168 -0
- package/autonomy/run.sh +187 -0
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +2 -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/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
|
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:** v7.
|
|
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.
|
|
398
|
+
asklokesh/loki-mode:7.88.0 start ./my-spec.md
|
|
399
399
|
```
|
|
400
400
|
|
|
401
401
|
##### docker compose + .env (no host install)
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -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.
|
|
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=
|
|
799
|
+
//# debugId=65E93B4739145ECE64756E2164756E21
|
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": "7.
|
|
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.
|
|
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",
|