loki-mode 7.5.31 → 7.6.1
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 +10 -7
- package/autonomy/run.sh +13 -4
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +190 -4
- package/dashboard/static/index.html +83 -0
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +85 -85
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/web-app/server.py +46 -28
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ 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.
|
|
6
|
+
# Loki Mode v7.6.1
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -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.
|
|
384
|
+
**v7.6.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.6.1
|
package/autonomy/loki
CHANGED
|
@@ -6915,7 +6915,9 @@ cmd_doctor() {
|
|
|
6915
6915
|
|
|
6916
6916
|
# JSON output for loki doctor --json
|
|
6917
6917
|
cmd_doctor_json() {
|
|
6918
|
-
|
|
6918
|
+
local _loki_version
|
|
6919
|
+
_loki_version=$(get_version)
|
|
6920
|
+
LOKI_VERSION="$_loki_version" python3 -c "
|
|
6919
6921
|
import json, os, subprocess, sys, shutil
|
|
6920
6922
|
|
|
6921
6923
|
def get_version(cmd, args=None):
|
|
@@ -7008,6 +7010,7 @@ elif disk_status == 'fail': fail_count += 1
|
|
|
7008
7010
|
elif disk_status == 'warn': warn_count += 1
|
|
7009
7011
|
|
|
7010
7012
|
result = {
|
|
7013
|
+
'loki_mode_version': os.environ.get('LOKI_VERSION', 'unknown'),
|
|
7011
7014
|
'checks': checks,
|
|
7012
7015
|
'disk': {
|
|
7013
7016
|
'available_gb': disk_gb,
|
|
@@ -13999,7 +14002,7 @@ if total_removed > 0:
|
|
|
13999
14002
|
index)
|
|
14000
14003
|
# Show or rebuild the memory index layer
|
|
14001
14004
|
if [ "${2:-}" = "rebuild" ]; then
|
|
14002
|
-
python3 -c "
|
|
14005
|
+
PYTHONPATH="${SKILL_DIR}${PYTHONPATH:+:$PYTHONPATH}" python3 -c "
|
|
14003
14006
|
try:
|
|
14004
14007
|
from memory.layers import IndexLayer
|
|
14005
14008
|
layer = IndexLayer('.loki/memory')
|
|
@@ -14023,7 +14026,7 @@ except Exception as e:
|
|
|
14023
14026
|
consolidate)
|
|
14024
14027
|
# Run consolidation pipeline
|
|
14025
14028
|
local hours="${2:-24}"
|
|
14026
|
-
LOKI_HOURS="$hours" python3 -c "
|
|
14029
|
+
LOKI_HOURS="$hours" PYTHONPATH="${SKILL_DIR}${PYTHONPATH:+:$PYTHONPATH}" python3 -c "
|
|
14027
14030
|
import os
|
|
14028
14031
|
try:
|
|
14029
14032
|
from memory.consolidation import ConsolidationPipeline
|
|
@@ -14060,7 +14063,7 @@ except Exception as e:
|
|
|
14060
14063
|
echo "Usage: loki memory retrieve <query>"
|
|
14061
14064
|
exit 1
|
|
14062
14065
|
fi
|
|
14063
|
-
LOKI_QUERY="$query" python3 -c "
|
|
14066
|
+
LOKI_QUERY="$query" PYTHONPATH="${SKILL_DIR}${PYTHONPATH:+:$PYTHONPATH}" python3 -c "
|
|
14064
14067
|
import os
|
|
14065
14068
|
try:
|
|
14066
14069
|
from memory.retrieval import MemoryRetrieval
|
|
@@ -14091,7 +14094,7 @@ except Exception as e:
|
|
|
14091
14094
|
echo "Usage: loki memory episode <id>"
|
|
14092
14095
|
exit 1
|
|
14093
14096
|
fi
|
|
14094
|
-
LOKI_ID="$id" python3 -c "
|
|
14097
|
+
LOKI_ID="$id" PYTHONPATH="${SKILL_DIR}${PYTHONPATH:+:$PYTHONPATH}" python3 -c "
|
|
14095
14098
|
import os
|
|
14096
14099
|
try:
|
|
14097
14100
|
from memory.engine import MemoryEngine
|
|
@@ -14115,7 +14118,7 @@ except Exception as e:
|
|
|
14115
14118
|
local id="${2:-}"
|
|
14116
14119
|
if [ -z "$id" ]; then
|
|
14117
14120
|
# List all patterns
|
|
14118
|
-
python3 -c "
|
|
14121
|
+
PYTHONPATH="${SKILL_DIR}${PYTHONPATH:+:$PYTHONPATH}" python3 -c "
|
|
14119
14122
|
try:
|
|
14120
14123
|
from memory.engine import MemoryEngine
|
|
14121
14124
|
engine = MemoryEngine(base_path='.loki/memory')
|
|
@@ -14133,7 +14136,7 @@ except Exception as e:
|
|
|
14133
14136
|
print(f'Error: {e}')
|
|
14134
14137
|
" 2>/dev/null
|
|
14135
14138
|
else
|
|
14136
|
-
LOKI_ID="$id" python3 -c "
|
|
14139
|
+
LOKI_ID="$id" PYTHONPATH="${SKILL_DIR}${PYTHONPATH:+:$PYTHONPATH}" python3 -c "
|
|
14137
14140
|
import os
|
|
14138
14141
|
try:
|
|
14139
14142
|
from memory.engine import MemoryEngine
|
package/autonomy/run.sh
CHANGED
|
@@ -9189,6 +9189,13 @@ build_prompt() {
|
|
|
9189
9189
|
# Context Memory Instructions (integrated with new memory system)
|
|
9190
9190
|
local memory_instruction="MEMORY SYSTEM: Relevant context from past sessions is provided below (if any). Your actions will be automatically recorded for future reference. For complex handoffs: create .loki/memory/handoffs/{timestamp}.md. For important decisions: they will be captured in the timeline. Check .loki/CONTINUITY.md for session-level working memory."
|
|
9191
9191
|
|
|
9192
|
+
# USAGE.md instruction (v7.6.0) -- always-on end-user handoff doc.
|
|
9193
|
+
# REGARDLESS of whether the PRD mentions it, the agent MUST write USAGE.md
|
|
9194
|
+
# at the project root before signaling completion. This becomes the
|
|
9195
|
+
# canonical "how do I run and verify this" artifact surfaced to the user
|
|
9196
|
+
# and to the dashboard/Purple Lab UI.
|
|
9197
|
+
local usage_doc_instruction="USAGE_DOC_REQUIRED: Before invoking loki_complete_task (or touching .loki/signals/COMPLETION_REQUESTED), write USAGE.md at the project root. Detect the stack from package.json/requirements.txt/Cargo.toml/go.mod/etc. and include these sections: (1) Prerequisites (runtimes, ports, env vars), (2) Install (exact command, e.g. 'npm install' or 'pip install -r requirements.txt'), (3) Start (exact command, e.g. 'npm start' or 'python server.py'), (4) Verify -- 2 to 3 copy-paste commands the user can run to confirm it works (curl examples for APIs with expected output, browser URL for web UIs, command invocation for CLIs), (5) Stop (Ctrl+C or 'lsof -ti:PORT | xargs kill -9' for backgrounded servers). Keep it under 100 lines, plain Markdown, no emojis. If USAGE.md already exists and is accurate, leave it; otherwise create or update it."
|
|
9198
|
+
|
|
9192
9199
|
# Load existing context if resuming
|
|
9193
9200
|
local context_injection=""
|
|
9194
9201
|
if [ $retry -gt 0 ]; then
|
|
@@ -9518,15 +9525,15 @@ except Exception:
|
|
|
9518
9525
|
else
|
|
9519
9526
|
if [ $retry -eq 0 ]; then
|
|
9520
9527
|
if [ -n "$prd" ]; then
|
|
9521
|
-
echo "Loki Mode with PRD at $prd. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
9528
|
+
echo "Loki Mode with PRD at $prd. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $usage_doc_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
9522
9529
|
else
|
|
9523
|
-
echo "Loki Mode. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
9530
|
+
echo "Loki Mode. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $usage_doc_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
9524
9531
|
fi
|
|
9525
9532
|
else
|
|
9526
9533
|
if [ -n "$prd" ]; then
|
|
9527
|
-
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
9534
|
+
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $usage_doc_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
9528
9535
|
else
|
|
9529
|
-
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
9536
|
+
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $mirofish_context $magic_context $checklist_status $app_runner_info $playwright_info $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $usage_doc_instruction $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
9530
9537
|
fi
|
|
9531
9538
|
fi
|
|
9532
9539
|
fi
|
|
@@ -9567,6 +9574,7 @@ except Exception:
|
|
|
9567
9574
|
else
|
|
9568
9575
|
printf 'You are a coding assistant. Analyze this codebase and suggest improvements. Write working code and commit changes.\n'
|
|
9569
9576
|
fi
|
|
9577
|
+
printf '%s\n' "$usage_doc_instruction"
|
|
9570
9578
|
printf '</loki_system>\n'
|
|
9571
9579
|
printf '[CACHE_BREAKPOINT]\n'
|
|
9572
9580
|
|
|
@@ -9597,6 +9605,7 @@ except Exception:
|
|
|
9597
9605
|
printf '%s\n' "$sdlc_instruction"
|
|
9598
9606
|
printf '%s\n' "$autonomous_suffix"
|
|
9599
9607
|
printf '%s\n' "$memory_instruction"
|
|
9608
|
+
printf '%s\n' "$usage_doc_instruction"
|
|
9600
9609
|
# For codebase-analysis mode (no PRD), analysis_instruction is part of the
|
|
9601
9610
|
# static prefix so it remains cache-stable.
|
|
9602
9611
|
if [ -z "$prd" ]; then
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -2688,6 +2688,167 @@ async def get_memory_timeline():
|
|
|
2688
2688
|
return {"entries": episodes, "lastUpdated": None}
|
|
2689
2689
|
|
|
2690
2690
|
|
|
2691
|
+
# ---------------------------------------------------------------------------
|
|
2692
|
+
# Memory File Browser (v7.6.0) - generic drill-down into .loki/memory/
|
|
2693
|
+
# ---------------------------------------------------------------------------
|
|
2694
|
+
# Exposes raw episodic/learnings/ledgers/handoffs files plus root-level
|
|
2695
|
+
# notes (decisions.md, mistakes.md, patterns.md, investigation-*.md, etc.)
|
|
2696
|
+
# so the dashboard can let users click through what the agent has stored.
|
|
2697
|
+
#
|
|
2698
|
+
# Safety: type is a whitelisted enum; path is validated by resolving against
|
|
2699
|
+
# the memory directory and rejecting anything outside it (also rejects
|
|
2700
|
+
# absolute paths and ".." segments before resolution).
|
|
2701
|
+
|
|
2702
|
+
_MEMORY_FILE_TYPES = {
|
|
2703
|
+
"episodic": "episodic",
|
|
2704
|
+
"learnings": "learnings",
|
|
2705
|
+
"ledgers": "ledgers",
|
|
2706
|
+
"handoffs": "handoffs",
|
|
2707
|
+
"semantic": "semantic",
|
|
2708
|
+
"skills": "skills",
|
|
2709
|
+
"root": "", # files directly under .loki/memory/
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
_MEMORY_FILE_MAX_BYTES = 2 * 1024 * 1024 # 2 MiB cap per file read
|
|
2713
|
+
|
|
2714
|
+
|
|
2715
|
+
def _safe_memory_path(rel_path: str) -> _Path:
|
|
2716
|
+
"""Resolve rel_path under .loki/memory/ and reject traversal attempts.
|
|
2717
|
+
|
|
2718
|
+
Raises HTTPException(400) on bad input, HTTPException(403) on traversal.
|
|
2719
|
+
"""
|
|
2720
|
+
if not rel_path or not isinstance(rel_path, str):
|
|
2721
|
+
raise HTTPException(status_code=400, detail="path required")
|
|
2722
|
+
# Reject NULs and absolute paths up front
|
|
2723
|
+
if "\x00" in rel_path or rel_path.startswith("/") or rel_path.startswith("\\"):
|
|
2724
|
+
raise HTTPException(status_code=400, detail="invalid path")
|
|
2725
|
+
# Reject explicit traversal segments before touching the filesystem
|
|
2726
|
+
parts = rel_path.replace("\\", "/").split("/")
|
|
2727
|
+
if any(p == ".." for p in parts):
|
|
2728
|
+
raise HTTPException(status_code=403, detail="path traversal blocked")
|
|
2729
|
+
memory_dir = _get_loki_dir() / "memory"
|
|
2730
|
+
try:
|
|
2731
|
+
real_memory = os.path.realpath(str(memory_dir))
|
|
2732
|
+
except Exception:
|
|
2733
|
+
raise HTTPException(status_code=500, detail="memory dir unavailable")
|
|
2734
|
+
candidate = memory_dir / rel_path
|
|
2735
|
+
try:
|
|
2736
|
+
resolved = os.path.realpath(str(candidate))
|
|
2737
|
+
except Exception:
|
|
2738
|
+
raise HTTPException(status_code=400, detail="invalid path")
|
|
2739
|
+
# Must live strictly under real_memory (not be it, not escape via symlink)
|
|
2740
|
+
if not (resolved == real_memory or resolved.startswith(real_memory + os.sep)):
|
|
2741
|
+
raise HTTPException(status_code=403, detail="path outside memory dir")
|
|
2742
|
+
return _Path(resolved)
|
|
2743
|
+
|
|
2744
|
+
|
|
2745
|
+
@app.get("/api/memory/files", dependencies=[Depends(auth.require_scope("read"))])
|
|
2746
|
+
async def list_memory_files(
|
|
2747
|
+
type: str = Query(default="root", description="One of: episodic, learnings, ledgers, handoffs, semantic, skills, root"),
|
|
2748
|
+
limit: int = Query(default=500, ge=1, le=2000),
|
|
2749
|
+
):
|
|
2750
|
+
"""List files under a memory subdirectory.
|
|
2751
|
+
|
|
2752
|
+
Returns: {type, dir, files: [{path, name, size, modified, kind}]}
|
|
2753
|
+
`path` is relative to .loki/memory/ and safe to pass back to /api/memory/file.
|
|
2754
|
+
"""
|
|
2755
|
+
if type not in _MEMORY_FILE_TYPES:
|
|
2756
|
+
raise HTTPException(status_code=400, detail=f"unknown type; expected one of {sorted(_MEMORY_FILE_TYPES)}")
|
|
2757
|
+
sub = _MEMORY_FILE_TYPES[type]
|
|
2758
|
+
memory_dir = _get_loki_dir() / "memory"
|
|
2759
|
+
target_dir = memory_dir / sub if sub else memory_dir
|
|
2760
|
+
if not target_dir.exists():
|
|
2761
|
+
return {"type": type, "dir": str(target_dir), "files": []}
|
|
2762
|
+
|
|
2763
|
+
real_memory = os.path.realpath(str(memory_dir))
|
|
2764
|
+
entries: list[dict[str, Any]] = []
|
|
2765
|
+
|
|
2766
|
+
if type == "root":
|
|
2767
|
+
# Only files directly under .loki/memory/ (don't descend into subdirs)
|
|
2768
|
+
iterator = (p for p in target_dir.iterdir() if p.is_file())
|
|
2769
|
+
elif type == "episodic":
|
|
2770
|
+
# Episodic is organized by date subdirectory; walk one level.
|
|
2771
|
+
def _ep_iter():
|
|
2772
|
+
for child in target_dir.iterdir():
|
|
2773
|
+
if child.is_file():
|
|
2774
|
+
yield child
|
|
2775
|
+
elif child.is_dir():
|
|
2776
|
+
for f in child.iterdir():
|
|
2777
|
+
if f.is_file():
|
|
2778
|
+
yield f
|
|
2779
|
+
iterator = _ep_iter()
|
|
2780
|
+
else:
|
|
2781
|
+
# Flat directory listing (learnings, ledgers, handoffs, semantic, skills)
|
|
2782
|
+
iterator = (p for p in target_dir.rglob("*") if p.is_file())
|
|
2783
|
+
|
|
2784
|
+
for f in iterator:
|
|
2785
|
+
try:
|
|
2786
|
+
resolved = os.path.realpath(str(f))
|
|
2787
|
+
if not resolved.startswith(real_memory + os.sep):
|
|
2788
|
+
continue # skip symlinks escaping the memory dir
|
|
2789
|
+
rel = os.path.relpath(resolved, real_memory)
|
|
2790
|
+
st = f.stat()
|
|
2791
|
+
entries.append({
|
|
2792
|
+
"path": rel,
|
|
2793
|
+
"name": f.name,
|
|
2794
|
+
"size": st.st_size,
|
|
2795
|
+
"modified": st.st_mtime,
|
|
2796
|
+
"kind": f.suffix.lstrip(".").lower() or "txt",
|
|
2797
|
+
})
|
|
2798
|
+
except Exception:
|
|
2799
|
+
continue
|
|
2800
|
+
if len(entries) >= limit:
|
|
2801
|
+
break
|
|
2802
|
+
|
|
2803
|
+
# Newest first
|
|
2804
|
+
entries.sort(key=lambda e: e["modified"], reverse=True)
|
|
2805
|
+
return {"type": type, "dir": str(target_dir), "files": entries}
|
|
2806
|
+
|
|
2807
|
+
|
|
2808
|
+
@app.get("/api/memory/file", dependencies=[Depends(auth.require_scope("read"))])
|
|
2809
|
+
async def get_memory_file(
|
|
2810
|
+
path: str = Query(..., min_length=1, max_length=512, description="Path relative to .loki/memory/"),
|
|
2811
|
+
):
|
|
2812
|
+
"""Read a single file under .loki/memory/ with strict path-traversal guards.
|
|
2813
|
+
|
|
2814
|
+
Returns: {path, name, size, modified, kind, content, truncated}
|
|
2815
|
+
JSON files are returned with content as a string; the caller can JSON.parse.
|
|
2816
|
+
"""
|
|
2817
|
+
target = _safe_memory_path(path)
|
|
2818
|
+
if not target.exists():
|
|
2819
|
+
raise HTTPException(status_code=404, detail="file not found")
|
|
2820
|
+
if not target.is_file():
|
|
2821
|
+
raise HTTPException(status_code=400, detail="not a file")
|
|
2822
|
+
try:
|
|
2823
|
+
st = target.stat()
|
|
2824
|
+
except Exception:
|
|
2825
|
+
raise HTTPException(status_code=500, detail="stat failed")
|
|
2826
|
+
truncated = False
|
|
2827
|
+
try:
|
|
2828
|
+
if st.st_size > _MEMORY_FILE_MAX_BYTES:
|
|
2829
|
+
with open(target, "rb") as fh:
|
|
2830
|
+
raw = fh.read(_MEMORY_FILE_MAX_BYTES)
|
|
2831
|
+
truncated = True
|
|
2832
|
+
else:
|
|
2833
|
+
with open(target, "rb") as fh:
|
|
2834
|
+
raw = fh.read()
|
|
2835
|
+
# Decode as UTF-8 with replacement so we never 500 on a stray byte.
|
|
2836
|
+
content = raw.decode("utf-8", errors="replace")
|
|
2837
|
+
except HTTPException:
|
|
2838
|
+
raise
|
|
2839
|
+
except Exception as e:
|
|
2840
|
+
raise HTTPException(status_code=500, detail=f"read failed: {e}")
|
|
2841
|
+
return {
|
|
2842
|
+
"path": path,
|
|
2843
|
+
"name": target.name,
|
|
2844
|
+
"size": st.st_size,
|
|
2845
|
+
"modified": st.st_mtime,
|
|
2846
|
+
"kind": target.suffix.lstrip(".").lower() or "txt",
|
|
2847
|
+
"content": content,
|
|
2848
|
+
"truncated": truncated,
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
|
|
2691
2852
|
# ---------------------------------------------------------------------------
|
|
2692
2853
|
# Memory Search & Stats (v6.15.0) - SQLite FTS5 powered
|
|
2693
2854
|
# ---------------------------------------------------------------------------
|
|
@@ -2862,9 +3023,20 @@ async def get_learning_metrics(
|
|
|
2862
3023
|
|
|
2863
3024
|
total_count = len(events) + len(all_signals)
|
|
2864
3025
|
|
|
2865
|
-
# Calculate average confidence across both sources
|
|
2866
|
-
|
|
2867
|
-
|
|
3026
|
+
# Calculate average confidence across both sources. Coerce values to float
|
|
3027
|
+
# because some legacy events/signals stored confidence as a string, which
|
|
3028
|
+
# made sum() raise TypeError: unsupported operand type(s) for +: 'int' and 'str'.
|
|
3029
|
+
# B-7 fix (v7.6.1): silently skip non-numeric confidence values.
|
|
3030
|
+
def _as_num(v: object) -> float:
|
|
3031
|
+
if isinstance(v, (int, float)):
|
|
3032
|
+
return float(v)
|
|
3033
|
+
try:
|
|
3034
|
+
return float(v) if v is not None else 0.0
|
|
3035
|
+
except (TypeError, ValueError):
|
|
3036
|
+
return 0.0
|
|
3037
|
+
|
|
3038
|
+
total_conf = sum(_as_num(e.get("data", {}).get("confidence", 0)) for e in events)
|
|
3039
|
+
total_conf += sum(_as_num(s.get("confidence", 0)) for s in all_signals)
|
|
2868
3040
|
|
|
2869
3041
|
# Load aggregation data from file if available
|
|
2870
3042
|
aggregation = {
|
|
@@ -6227,7 +6399,21 @@ async def get_escalation(filename: str):
|
|
|
6227
6399
|
# ---------------------------------------------------------------------------
|
|
6228
6400
|
@app.get("/{full_path:path}", include_in_schema=False)
|
|
6229
6401
|
async def serve_spa_catchall(full_path: str):
|
|
6230
|
-
"""Serve static files or fall back to index.html for SPA routing.
|
|
6402
|
+
"""Serve static files or fall back to index.html for SPA routing.
|
|
6403
|
+
|
|
6404
|
+
v7.6.1 B-10 fix: requests under /api/, /lab/api/, or /ws/ that fall through
|
|
6405
|
+
here are missing routes, not SPA navigation. Returning index.html (text/html)
|
|
6406
|
+
silently masks 404s and breaks JSON clients (the dashboard UI's loki-memory-browser
|
|
6407
|
+
pinged /api/learning/metrics expecting JSON and got an HTML SPA on prior
|
|
6408
|
+
failures). Return a JSON 404 instead so clients fail loud.
|
|
6409
|
+
"""
|
|
6410
|
+
# API paths that fell through are real 404s, not SPA routes.
|
|
6411
|
+
api_like = full_path.startswith("api/") or full_path.startswith("lab/api/") or full_path.startswith("ws/")
|
|
6412
|
+
if api_like:
|
|
6413
|
+
return JSONResponse(
|
|
6414
|
+
status_code=404,
|
|
6415
|
+
content={"error": "Not Found", "path": f"/{full_path}"},
|
|
6416
|
+
)
|
|
6231
6417
|
if STATIC_DIR:
|
|
6232
6418
|
static_root = os.path.realpath(STATIC_DIR)
|
|
6233
6419
|
# Try to serve the exact file first (e.g. /vite.svg, /manifest.json)
|
|
@@ -615,6 +615,89 @@
|
|
|
615
615
|
<h3 style="font-family: 'DM Serif Display', Georgia, serif; font-size: 1.15rem; font-weight: 400; color: var(--loki-text-primary); margin-bottom: 12px;">Memory</h3>
|
|
616
616
|
<loki-memory-browser id="memory-browser" tab="summary"></loki-memory-browser>
|
|
617
617
|
</div>
|
|
618
|
+
<div>
|
|
619
|
+
<h3 style="font-family: 'DM Serif Display', Georgia, serif; font-size: 1.15rem; font-weight: 400; color: var(--loki-text-primary); margin-bottom: 12px;">Memory Files</h3>
|
|
620
|
+
<div id="memory-files-panel" style="background: var(--loki-bg-card, #1a1a1a); border: 1px solid var(--loki-border, #333); border-radius: 5px; padding: 12px;">
|
|
621
|
+
<div style="display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px;" id="memory-files-tabs"></div>
|
|
622
|
+
<div style="display: grid; grid-template-columns: minmax(220px, 320px) 1fr; gap: 12px; min-height: 280px;">
|
|
623
|
+
<div id="memory-files-list" style="border: 1px solid var(--loki-border, #333); border-radius: 4px; padding: 8px; max-height: 480px; overflow-y: auto; font-size: 12px;">Loading...</div>
|
|
624
|
+
<div id="memory-files-viewer" style="border: 1px solid var(--loki-border, #333); border-radius: 4px; padding: 12px; max-height: 480px; overflow: auto; font-family: 'JetBrains Mono', monospace; font-size: 12px; white-space: pre-wrap; word-break: break-word; color: var(--loki-text-primary, #eee);">Select a file to view its contents.</div>
|
|
625
|
+
</div>
|
|
626
|
+
</div>
|
|
627
|
+
<script>
|
|
628
|
+
(function(){
|
|
629
|
+
var TYPES = [
|
|
630
|
+
{id: 'root', label: 'Notes'},
|
|
631
|
+
{id: 'episodic', label: 'Episodes'},
|
|
632
|
+
{id: 'learnings', label: 'Learnings'},
|
|
633
|
+
{id: 'ledgers', label: 'Ledgers'},
|
|
634
|
+
{id: 'handoffs', label: 'Handoffs'},
|
|
635
|
+
{id: 'semantic', label: 'Semantic'},
|
|
636
|
+
{id: 'skills', label: 'Skills'}
|
|
637
|
+
];
|
|
638
|
+
var state = {type: 'root', files: [], selected: null};
|
|
639
|
+
var tabsEl = document.getElementById('memory-files-tabs');
|
|
640
|
+
var listEl = document.getElementById('memory-files-list');
|
|
641
|
+
var viewEl = document.getElementById('memory-files-viewer');
|
|
642
|
+
function esc(s){ var d = document.createElement('div'); d.textContent = String(s == null ? '' : s); return d.innerHTML; }
|
|
643
|
+
function fmtSize(n){ if (n < 1024) return n + ' B'; if (n < 1024*1024) return (n/1024).toFixed(1) + ' KB'; return (n/1024/1024).toFixed(2) + ' MB'; }
|
|
644
|
+
function fmtTime(ts){ try { return new Date(ts*1000).toLocaleString(); } catch(e){ return ''; } }
|
|
645
|
+
function renderTabs(){
|
|
646
|
+
tabsEl.innerHTML = TYPES.map(function(t){
|
|
647
|
+
var active = t.id === state.type;
|
|
648
|
+
return '<button data-type="' + t.id + '" style="padding: 5px 10px; font-size: 11px; border-radius: 4px; border: 1px solid ' + (active ? 'var(--loki-accent, #553de9)' : 'var(--loki-border, #333)') + '; background: ' + (active ? 'var(--loki-accent, #553de9)' : 'transparent') + '; color: ' + (active ? '#fff' : 'var(--loki-text-primary, #ccc)') + '; cursor: pointer;">' + esc(t.label) + '</button>';
|
|
649
|
+
}).join('');
|
|
650
|
+
Array.prototype.forEach.call(tabsEl.querySelectorAll('button'), function(b){
|
|
651
|
+
b.addEventListener('click', function(){ loadType(b.getAttribute('data-type')); });
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
function renderList(){
|
|
655
|
+
if (!state.files.length){ listEl.innerHTML = '<div style="color: var(--loki-text-muted, #888); padding: 8px;">No files in this section.</div>'; return; }
|
|
656
|
+
listEl.innerHTML = state.files.map(function(f){
|
|
657
|
+
var active = state.selected && state.selected.path === f.path;
|
|
658
|
+
return '<div data-path="' + esc(f.path) + '" style="padding: 6px 8px; border-radius: 3px; cursor: pointer; margin-bottom: 2px; background: ' + (active ? 'var(--loki-accent, #553de9)' : 'transparent') + '; color: ' + (active ? '#fff' : 'inherit') + ';" title="' + esc(f.path) + '"><div style="font-weight: 500;">' + esc(f.name) + '</div><div style="font-size: 10px; color: ' + (active ? 'rgba(255,255,255,0.8)' : 'var(--loki-text-muted, #888)') + ';">' + fmtSize(f.size) + ' -- ' + esc(fmtTime(f.modified)) + '</div></div>';
|
|
659
|
+
}).join('');
|
|
660
|
+
Array.prototype.forEach.call(listEl.querySelectorAll('div[data-path]'), function(d){
|
|
661
|
+
d.addEventListener('click', function(){ loadFile(d.getAttribute('data-path')); });
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
function renderViewer(file){
|
|
665
|
+
if (!file){ viewEl.textContent = 'Select a file to view its contents.'; return; }
|
|
666
|
+
var header = file.name + ' (' + fmtSize(file.size) + (file.truncated ? ', truncated' : '') + ')
|
|
667
|
+
' + file.path + '
|
|
668
|
+
|
|
669
|
+
';
|
|
670
|
+
var body = file.content || '';
|
|
671
|
+
if (file.kind === 'json'){
|
|
672
|
+
try { body = JSON.stringify(JSON.parse(body), null, 2); } catch(e){ /* leave raw */ }
|
|
673
|
+
}
|
|
674
|
+
viewEl.textContent = header + body;
|
|
675
|
+
}
|
|
676
|
+
function loadType(t){
|
|
677
|
+
state.type = t; state.selected = null;
|
|
678
|
+
renderTabs(); renderViewer(null);
|
|
679
|
+
listEl.innerHTML = 'Loading...';
|
|
680
|
+
fetch('/api/memory/files?type=' + encodeURIComponent(t) + '&limit=500')
|
|
681
|
+
.then(function(r){ if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
|
|
682
|
+
.then(function(j){ state.files = (j && j.files) || []; renderList(); })
|
|
683
|
+
.catch(function(e){ listEl.innerHTML = '<div style="color: var(--loki-red, #e74c3c); padding: 8px;">Failed: ' + esc(e.message) + '</div>'; });
|
|
684
|
+
}
|
|
685
|
+
function loadFile(p){
|
|
686
|
+
state.selected = state.files.filter(function(f){ return f.path === p; })[0] || {path: p};
|
|
687
|
+
renderList();
|
|
688
|
+
viewEl.textContent = 'Loading ' + p + '...';
|
|
689
|
+
fetch('/api/memory/file?path=' + encodeURIComponent(p))
|
|
690
|
+
.then(function(r){ if (!r.ok) return r.json().then(function(j){ throw new Error(j.detail || ('HTTP ' + r.status)); }); return r.json(); })
|
|
691
|
+
.then(function(j){ renderViewer(j); })
|
|
692
|
+
.catch(function(e){ viewEl.textContent = 'Failed: ' + e.message; });
|
|
693
|
+
}
|
|
694
|
+
// Initialize when Insights becomes visible (or immediately if already visible)
|
|
695
|
+
function init(){ renderTabs(); loadType('root'); }
|
|
696
|
+
if (document.getElementById('page-insights')){ init(); }
|
|
697
|
+
else { document.addEventListener('DOMContentLoaded', init); }
|
|
698
|
+
})();
|
|
699
|
+
</script>
|
|
700
|
+
</div>
|
|
618
701
|
<div>
|
|
619
702
|
<h3 style="font-family: 'DM Serif Display', Georgia, serif; font-size: 1.15rem; font-weight: 400; color: var(--loki-text-primary); margin-bottom: 12px;">Learning Metrics</h3>
|
|
620
703
|
<loki-learning-dashboard id="learning-dashboard" time-range="7d"></loki-learning-dashboard>
|
package/docs/INSTALLATION.md
CHANGED
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var
|
|
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 L=(K,$)=>()=>(K&&($=K(K=0)),$);var V1=import.meta.require;var e1={};b(e1,{lokiDir:()=>P,homeLokiDir:()=>k1,findRepoRootForVersion:()=>N1,REPO_ROOT:()=>u});import{resolve as f,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(f(K,"VERSION"))&&J1(f(K,"autonomy/run.sh")))return K;let z=S1(K);if(z===K)break;K=z}return f(i1,"..","..","..")}function N1(K){let $=K;for(let z=0;z<6;z++){if(J1(f($,"VERSION"))&&J1(f($,"autonomy/run.sh")))return $;let Q=S1($);if(Q===$)break;$=Q}return f(K,"..","..","..")}function P(){return process.env.LOKI_DIR??f(process.cwd(),".loki")}function k1(){return f(R7(),".loki")}var i1,u;var y=L(()=>{i1=S1(L7(import.meta.url));u=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(o!==null)return o;let K="7.6.1";if(typeof K==="string"&&K.length>0)return o=K,o;try{let $=w7(S7(import.meta.url)),z=N1($);o=x7(F7(z,"VERSION"),"utf-8").trim()}catch{o="unknown"}return o}var o=null;var D1=L(()=>{y()});var $0={};b($0,{runOrThrow:()=>N7,run:()=>w,commandVersion:()=>D7,commandExists:()=>h,ShellError:()=>C1});async function w(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 w(K,$);if(z.exitCode!==0)throw new C1(`command failed (${z.exitCode}): ${K.join(" ")}`,z.exitCode,z.stdout,z.stderr);return z}async function h(K){let $=k7(K),z=await w(["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 h(K))return null;let Q=await w([K,$],{timeoutMs:5000});if(Q.exitCode!==0)return null;return((Q.stdout||Q.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var C1;var p=L(()=>{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 c(K){return C7?"":K}var C7,R,D,E,O6,O,k,x,W;var n=L(()=>{C7=(process.env.NO_COLOR??"").length>0;R=c("\x1B[0;31m"),D=c("\x1B[0;32m"),E=c("\x1B[1;33m"),O6=c("\x1B[0;34m"),O=c("\x1B[0;36m"),k=c("\x1B[1m"),x=c("\x1B[2m"),W=c("\x1B[0m")});import{existsSync as c7}from"fs";async function r(){if(z1!==void 0)return z1;let K="/opt/homebrew/bin/python3.12";if(c7(K))return z1=K,K;let $=await h("python3.12");if($)return z1=$,$;let z=await h("python3");return z1=z,z}async function a(K,$={}){let z=await r();if(!z)return{stdout:"",stderr:"python3 not found",exitCode:127};return w([z,"-c",K],$)}var z1;var Q1=L(()=>{p()});var G0={};b(G0,{runStatus:()=>z5});import{existsSync as S,readFileSync as Z1,readdirSync as H0,statSync as W0}from"fs";import{resolve as F,basename as a7}from"path";async function r7(){if(await h("jq"))return!0;return process.stdout.write(`${R}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)
|
|
6
6
|
`),process.stdout.write(` yum install jq (RHEL/CentOS)
|
|
7
|
-
`),!1}function
|
|
7
|
+
`),!1}function B1(K){if(!Number.isFinite(K)||K<=0)return!1;try{return process.kill(K,0),!0}catch{return!1}}function M1(K){if(!S(K))return null;try{let $=Z1(K,"utf-8").trim();if(!$)return null;let z=Number.parseInt($,10);return Number.isFinite(z)?z:null}catch{return null}}function t7(K){let $=[],z=M1(F(K,"loki.pid"));if(z!==null&&B1(z))$.push(`global:${z}`);let Q=F(K,"sessions");if(S(Q)){let X=[];try{X=H0(Q)}catch{X=[]}for(let H of X){let Z=F(Q,H);try{if(!W0(Z).isDirectory())continue}catch{continue}let q=F(Z,"loki.pid"),U=M1(q);if(U!==null&&B1(U))$.push(`${H}:${U}`)}}if(S(K)){let X=[];try{X=H0(K)}catch{X=[]}for(let H of X){if(!H.startsWith("run-")||!H.endsWith(".pid"))continue;let Z=F(K,H);try{if(!W0(Z).isFile())continue}catch{continue}let q=a7(H,".pid").slice(4),U=M1(Z);if(U!==null&&B1(U)){if(!$.some((G)=>G.startsWith(`${q}:`)))$.push(`${q}:${U}`)}}}return $}async function q0(K,$){let z=await w(["jq","-r",K,$]);if(z.exitCode!==0)return null;return z.stdout.trim()}function U0(K,$){try{let z=Z1(K,"utf-8"),X=JSON.parse(z)[$];if(typeof X==="number"){if($==="budget_used"){let H=Math.round(X*100)/100;if(Number.isInteger(H))return String(H);return String(H)}return String(X)}if(X===void 0||X===null)return"0";return String(X)}catch{return"0"}}function V0(K,$,z){try{let Q=Z1(K,"utf-8"),H=JSON.parse(Q)[$];if(typeof H==="number"&&Number.isFinite(H))return H;return z}catch{return z}}async function i7(){let K=P();if(!await r7())return 1;if(!S(K))return process.stdout.write(`${k}Loki Mode Status${W}
|
|
8
8
|
`),process.stdout.write(`
|
|
9
9
|
`),process.stdout.write(`${E}No active session found.${W}
|
|
10
10
|
`),process.stdout.write(`Loki Mode has not been initialized in this directory.
|
|
@@ -19,7 +19,7 @@ var j7=Object.defineProperty;var _7=(K)=>K;function I7(K,$){this[K]=_7.bind(null
|
|
|
19
19
|
`);let $="",z=F(K,"state","provider");if(S(z))try{$=Z1(z,"utf-8").trim()}catch{$=""}let Q=$||process.env.LOKI_PROVIDER||"claude",X="full features";switch(Q){case"codex":case"aider":X="degraded mode";break;case"cline":X="near-full mode";break;default:X="full features";break}process.stdout.write(`${O}Provider:${W} ${Q} (${X})
|
|
20
20
|
`),process.stdout.write(`${x} Switch with: loki provider set <claude|codex|cline|aider>${W}
|
|
21
21
|
`),process.stdout.write(`
|
|
22
|
-
`);let H=
|
|
22
|
+
`);let H=t7(K);if(H.length>0){process.stdout.write(`${D}Active Sessions: ${H.length}${W}
|
|
23
23
|
`);for(let J of H){let Y=J.indexOf(":"),j=Y>=0?J.slice(0,Y):J,_=Y>=0?J.slice(Y+1):"";if(j==="global")process.stdout.write(` ${O}[global]${W} PID ${_}
|
|
24
24
|
`);else process.stdout.write(` ${O}[#${j}]${W} PID ${_}
|
|
25
25
|
`)}process.stdout.write(`
|
|
@@ -34,27 +34,27 @@ var j7=Object.defineProperty;var _7=(K)=>K;function I7(K,$){this[K]=_7.bind(null
|
|
|
34
34
|
`),process.stdout.write(`
|
|
35
35
|
`);let Z=F(K,"STATUS.txt");if(S(Z)){process.stdout.write(`${O}Session Info:${W}
|
|
36
36
|
`);try{process.stdout.write(Z1(Z,"utf-8"))}catch{}process.stdout.write(`
|
|
37
|
-
`)}let
|
|
38
|
-
`);let J=await
|
|
39
|
-
`)}let
|
|
40
|
-
`)}let
|
|
37
|
+
`)}let q=F(K,"state","orchestrator.json");if(S(q)){process.stdout.write(`${O}Orchestrator State:${W}
|
|
38
|
+
`);let J=await q0('.currentPhase // "unknown"',q);process.stdout.write(`${J??"unknown"}
|
|
39
|
+
`)}let U=F(K,"queue","pending.json");if(S(U)){let J=await q0('if type == "array" then length elif .tasks then .tasks | length else 0 end',U);process.stdout.write(`${O}Pending Tasks:${W} ${J??"0"}
|
|
40
|
+
`)}let V=F(K,"metrics","budget.json");if(S(V)){let J=U0(V,"budget_limit"),Y=U0(V,"budget_used");if(J!=="0")process.stdout.write(`${O}Budget:${W} $${Y} / $${J}
|
|
41
41
|
`);else process.stdout.write(`${O}Cost:${W} $${Y} (no limit)
|
|
42
|
-
`)}let G=F(K,"state","context-usage.json");if(S(G)){let J=
|
|
43
|
-
`)}let B=F(K,"dashboard","dashboard.pid");if(S(B)){let J=
|
|
44
|
-
`)}}return await
|
|
42
|
+
`)}let G=F(K,"state","context-usage.json");if(S(G)){let J=V0(G,"window_size",200000),Y=V0(G,"used_tokens",0),j=0;if(J>0)j=Math.floor(Y*100/J);process.stdout.write(`${O}Context:${W} ${j}% (${Y} / ${J} tokens)
|
|
43
|
+
`)}let B=F(K,"dashboard","dashboard.pid");if(S(B)){let J=M1(B);if(J!==null&&B1(J)){let Y=process.env.LOKI_DASHBOARD_PORT||"57374";process.stdout.write(`${O}Dashboard:${W} http://127.0.0.1:${Y}/
|
|
44
|
+
`)}}return await e7(K),process.stdout.write(`
|
|
45
45
|
`),process.stdout.write(`${x} Tip: loki context show - detailed token breakdown${W}
|
|
46
46
|
`),process.stdout.write(`${x} Tip: loki code overview - codebase intelligence${W}
|
|
47
|
-
`),0}async function
|
|
47
|
+
`),0}async function e7(K){let $=F(K,"state"),z=K5($),Q=F($,"relevant-learnings.json"),X=F(K,"escalations"),H=z.length>0,Z=S(Q),q=S(X);if(!H&&!Z&&!q)return;if(process.stdout.write(`
|
|
48
48
|
${O}Phase 1 artifacts:${W}
|
|
49
|
-
`),H){let
|
|
50
|
-
`)}}if(Z){let
|
|
51
|
-
`)}}if(
|
|
52
|
-
`)}}function
|
|
53
|
-
`),1;let $=u,z=P(),Q=process.env.LOKI_DASHBOARD_PORT||"57374",X=process.env.LOKI_PROVIDER||"claude",H=await w([K,"-c",
|
|
54
|
-
`),1;return process.stdout.write(H.stdout),0}async function
|
|
49
|
+
`),H){let U=z[z.length-1],V=J0(U);if(V&&Array.isArray(V.findings)){let G={Critical:0,High:0,Medium:0,Low:0};for(let J of V.findings){let Y=String(J.severity??"");if(Y in G)G[Y]=(G[Y]??0)+1}let B=Object.entries(G).filter(([,J])=>J>0).map(([J,Y])=>`${Y} ${J.toLowerCase()}`).join(", ");process.stdout.write(` Findings (iter ${V.iteration??"?"}): ${B||"none"} -- ${V.findings.length} total
|
|
50
|
+
`)}}if(Z){let U=J0(Q);if(U&&Array.isArray(U.learnings)&&U.learnings.length>0){let V=new Map;for(let B of U.learnings){let J=String(B.trigger??"unknown");V.set(J,(V.get(J)??0)+1)}let G=[...V.entries()].sort((B,J)=>J[1]-B[1]).slice(0,3).map(([B,J])=>`${J} ${B}`).join(", ");process.stdout.write(` Learnings: ${U.learnings.length} total (${G})
|
|
51
|
+
`)}}if(q){let U=0,V="";try{let B=(await import("fs")).readdirSync(X).filter((J)=>J.endsWith(".md"));if(U=B.length,B.length>0)B.sort(),V=B[B.length-1]??""}catch{}if(U>0)process.stdout.write(` Escalations: ${U} handoff doc${U===1?"":"s"} (latest: ${V})
|
|
52
|
+
`)}}function K5(K){if(!S(K))return[];try{return V1("fs").readdirSync(K).filter((Q)=>/^findings-\d+\.json$/.test(Q)).sort((Q,X)=>{let H=Number.parseInt(Q.replace(/[^0-9]/g,""),10)||0,Z=Number.parseInt(X.replace(/[^0-9]/g,""),10)||0;return H-Z}).map((Q)=>F(K,Q))}catch{return[]}}function J0(K){try{let $=V1("fs");return JSON.parse($.readFileSync(K,"utf-8"))}catch{return null}}async function $5(){let K=await r();if(!K)return process.stderr.write(`{"error": "Failed to generate JSON status. Ensure python3 is available."}
|
|
53
|
+
`),1;let $=u,z=P(),Q=process.env.LOKI_DASHBOARD_PORT||"57374",X=process.env.LOKI_PROVIDER||"claude",H=await w([K,"-c",s7,$,z,Q,X],{timeoutMs:30000});if(H.exitCode!==0)return process.stderr.write(`{"error": "Failed to generate JSON status. Ensure python3 is available."}
|
|
54
|
+
`),1;return process.stdout.write(H.stdout),0}async function z5(K){let $=[...K];while($.length>0){let z=$[0];if(z==="--json")return $5();if(z==="--help"||z==="-h")return process.stdout.write(`Usage: loki status [--json]
|
|
55
55
|
`),0;return process.stdout.write(`${R}Unknown flag: ${z}${W}
|
|
56
56
|
`),process.stdout.write(`Usage: loki status [--json]
|
|
57
|
-
`),1}return
|
|
57
|
+
`),1}return i7()}var s7=`
|
|
58
58
|
import json, os, sys, time
|
|
59
59
|
|
|
60
60
|
skill_dir = sys.argv[1]
|
|
@@ -261,9 +261,9 @@ if os.path.isfile(gate_count_file):
|
|
|
261
261
|
result['phase1'] = phase1
|
|
262
262
|
|
|
263
263
|
print(json.dumps(result, indent=2))
|
|
264
|
-
`;var
|
|
265
|
-
`)}function
|
|
266
|
-
Start a session with: loki start <prd>`}}let X=
|
|
264
|
+
`;var B0=L(()=>{p();Q1();n();y()});var T0={};b(T0,{runStats:()=>q5,computeStats:()=>O0});import{readdirSync as M0,readFileSync as Q5,statSync as Y0}from"fs";import{join as l}from"path";function i(K){try{if(!Y0(K).isFile())return null;return JSON.parse(Q5(K,"utf-8"))}catch{return null}}function v1(K){try{return Y0(K).isDirectory()}catch{return!1}}function X5(K){if(!v1(K))return[];try{let $=M0(K).filter((z)=>z.startsWith("iteration-")&&z.endsWith(".json"));return $.sort(),$.map((z)=>l(K,z))}catch{return[]}}function e(K){return Math.trunc(K).toLocaleString("en-US")}function b1(K){let $=Math.trunc(K);if($<60)return`${$}s`;let z=Math.trunc($/3600),Q=Math.trunc($%3600/60),X=$%60;if(z>0)return`${z}h ${String(Q).padStart(2,"0")}m`;return`${Q}m ${String(X).padStart(2,"0")}s`}function d(K,$=0){let z=Math.pow(10,$);return Math.round(K*z)/z}function K1(K,$){return K.toFixed($)}function y1(K,$){return K.length>=$?K:K+" ".repeat($-K.length)}function Z5(K){let $="N/A",z=0,Q=i(l(K,"state","orchestrator.json"));if(Q&&typeof Q==="object"){if(typeof Q.currentPhase==="string")$=Q.currentPhase;if(typeof Q.currentIteration==="number")z=Q.currentIteration}let X=l(K,"metrics","efficiency"),H=X5(X),Z=[];for(let T of H){let I=i(T);if(I&&typeof I==="object")Z.push(I)}if(Z.length>0)z=Math.max(z,Z.length);let q=Z.reduce((T,I)=>T+(I.input_tokens??0),0),U=Z.reduce((T,I)=>T+(I.output_tokens??0),0),V=q+U,G=Z.reduce((T,I)=>T+(I.cost_usd??0),0),B=Z.reduce((T,I)=>T+(I.duration_seconds??0),0),J=0,Y=0,j=i(l(K,"metrics","budget.json"));if(j&&typeof j==="object"){if(typeof j.budget_limit==="number")J=j.budget_limit;if(typeof j.budget_used==="number")Y=j.budget_used}let _=0,C=0,g=i(l(K,"state","quality-gates.json"));if(g&&typeof g==="object"){if(Array.isArray(g)){for(let T of g)if(C+=1,T===!0)_+=1;else if(T&&typeof T==="object"){let I=T;if(I.passed===!0||I.status==="passed")_+=1}}else for(let T of Object.values(g))if(typeof T==="boolean"){if(C+=1,T)_+=1}else if(T&&typeof T==="object"){C+=1;let I=T;if(I.passed===!0||I.status==="passed")_+=1}}let m={},A=i(l(K,"quality","gate-failure-count.json"));if(A&&typeof A==="object"&&!Array.isArray(A)){let T={};for(let[I,v]of Object.entries(A))if(typeof v==="number")T[I]=v;m=T}let N=0,$1=0,r1=0,w1=l(K,"quality");if(v1(w1)){let T=[];try{T=M0(w1)}catch{T=[]}for(let I of T){if(!I.endsWith(".json")||I==="gate-failure-count.json")continue;let v=i(l(w1,I));if(!v||typeof v!=="object")continue;if(!(("verdict"in v)||("approved"in v)||("reviewers"in v)))continue;N+=1;let t1=(v.verdict??"").toString().toLowerCase();if(v.approved===!0||["approved","approve","pass"].includes(t1))$1+=1;else if(["revision","revise","changes_requested","reject"].includes(t1))r1+=1}}return{phase:$,iterationCount:z,iterations:Z,totalInput:q,totalOutput:U,totalTokens:V,totalCost:G,totalDuration:B,budgetLimit:J,budgetUsed:Y,gatesPassed:_,gatesTotal:C,gateFailures:m,reviewsTotal:N,reviewsApproved:$1,reviewsRevision:r1}}function H5(K,$){let z=K.iterationCount,Q={session:{iterations:z,duration_seconds:K.totalDuration,phase:K.phase},tokens:{input:K.totalInput,output:K.totalOutput,total:K.totalTokens,cost_usd:d(K.totalCost,2)},quality:{gates_passed:K.gatesPassed,gates_total:K.gatesTotal,reviews_total:K.reviewsTotal,reviews_approved:K.reviewsApproved,reviews_revision:K.reviewsRevision,gate_failures:K.gateFailures},efficiency:{avg_tokens_per_iteration:z>0?d(K.totalTokens/z,0):0,avg_cost_per_iteration:z>0?d(K.totalCost/z,2):0,avg_duration_per_iteration:z>0?d(K.totalDuration/z,1):0},budget:{used:d(K.budgetUsed,2),limit:K.budgetLimit,percent:K.budgetLimit>0?d(K.budgetUsed/K.budgetLimit*100,1):0}};if($)Q.iterations=K.iterations.map((Z,q)=>({number:q+1,input_tokens:Z.input_tokens??0,output_tokens:Z.output_tokens??0,cost_usd:d(Z.cost_usd??0,2),duration_seconds:Z.duration_seconds??0}));let X=JSON.stringify(Q,null,2);function H(Z,q){if(!q)return;let U=new RegExp(`("${Z}": )(-?\\d+)(,?)$`,"m");X=X.replace(U,(V,G,B,J)=>`${G}${B}.0${J}`)}if(H("avg_duration_per_iteration",z>0&&Number.isInteger(Q.efficiency.avg_duration_per_iteration)),H("percent",K.budgetLimit>0&&Number.isInteger(Q.budget.percent)),H("cost_usd",z>0&&Number.isInteger(Q.tokens.cost_usd)),$)X=X.replace(/("cost_usd": )(-?\d+)(,?)$/gm,(Z,q,U,V)=>`${q}${U}.0${V}`);return X}function W5(K,$){let z=[];if(z.push("Loki Mode Session Statistics"),z.push("============================"),z.push(""),z.push("Session"),z.push(` Iterations completed: ${K.iterationCount}`),z.push(` Duration: ${b1(K.totalDuration)}`),z.push(` Current phase: ${K.phase}`),z.push(""),z.push("Token Usage"),K.iterations.length>0)z.push(` Input tokens: ${e(K.totalInput)}`),z.push(` Output tokens: ${e(K.totalOutput)}`),z.push(` Total tokens: ${e(K.totalTokens)}`),z.push(` Estimated cost: $${K1(K.totalCost,2)}`);else z.push(" N/A (no iteration metrics found)");if(z.push(""),z.push("Quality Gates"),K.gatesTotal>0){let Q=Math.round(K.gatesPassed/K.gatesTotal*100);z.push(` Gates passed: ${K.gatesPassed}/${K.gatesTotal} (${Q}%)`)}else z.push(" Gates passed: N/A");if(K.reviewsTotal>0){let Q=[];if(K.reviewsApproved>0)Q.push(`${K.reviewsApproved} approved`);if(K.reviewsRevision>0)Q.push(`${K.reviewsRevision} revision requested`);let X=Q.length>0?Q.join(", "):"N/A";z.push(` Code reviews: ${K.reviewsTotal} (${X})`)}if(Object.keys(K.gateFailures).length>0){let Q=Object.entries(K.gateFailures).filter(([,X])=>X>0).map(([X,H])=>`${X} (${H})`);if(Q.length>0)z.push(` Gate failures: ${Q.join(", ")}`)}if(z.push(""),z.push("Efficiency"),K.iterationCount>0&&K.iterations.length>0){let Q=Math.round(K.totalTokens/K.iterationCount),X=K.totalCost/K.iterationCount,H=K.totalDuration/K.iterationCount;z.push(` Avg tokens/iteration: ${e(Q)}`),z.push(` Avg cost/iteration: $${K1(X,2)}`),z.push(` Avg duration/iteration: ${b1(H)}`)}else z.push(" N/A (no iteration metrics found)");if(z.push(""),z.push("Budget"),K.budgetLimit>0){let Q=d(K.budgetUsed/K.budgetLimit*100,1),X=Number.isInteger(Q)?`${Q}.0`:`${Q}`;z.push(` Used: $${K1(K.budgetUsed,2)} / $${K1(K.budgetLimit,2)} (${X}%)`)}else if(K.budgetUsed>0)z.push(` Used: $${K1(K.budgetUsed,2)} (no limit set)`);else z.push(" N/A");if($&&K.iterations.length>0)z.push(""),z.push("Per-Iteration Breakdown"),K.iterations.forEach((Q,X)=>{let H=X+1,Z=y1(e(Q.input_tokens??0),10),q=y1(e(Q.output_tokens??0),10),U=Q.cost_usd??0,V=b1(Q.duration_seconds??0),G=y1(`${H}`,3);z.push(` #${G} input: ${Z} output: ${q} cost: $${K1(U,2)} time: ${V}`)});return z.join(`
|
|
265
|
+
`)}function O0(K){let $=!1,z=!1;for(let Z of K)if(Z==="--json")$=!0;else if(Z==="--efficiency")z=!0;let Q=P();if(!v1(Q)){if($)return{exitCode:0,stdout:'{"error": "No active session"}'};return{exitCode:0,stdout:`${E}No active session found.${W}
|
|
266
|
+
Start a session with: loki start <prd>`}}let X=Z5(Q);return{exitCode:0,stdout:$?H5(X,z):W5(X,z)}}async function q5(K){let $=O0(K);return console.log($.stdout),$.exitCode}var A0=L(()=>{y();n()});var F0={};b(F0,{runDoctor:()=>I5,pythonImportOk:()=>f1,httpReachable:()=>g1,checkTool:()=>L0,checkSkills:()=>R0,checkDisk:()=>m1,buildDoctorJson:()=>x0,_setPythonImportOkForTest:()=>Y5});import{existsSync as U5,lstatSync as V5,readlinkSync as J5,statfsSync as G5}from"fs";import{homedir as _0}from"os";import{resolve as j0}from"path";function M5(K){let $=K.match(B5);return $?$[1]:null}async function I0(K){try{let $=await w([K,"--version"],{timeoutMs:5000}),z=($.stdout||$.stderr||"").trim();return M5(z)}catch{return null}}function P0(K,$){let z=K.split(".").map((X)=>parseInt(X,10)),Q=$.split(".").map((X)=>parseInt(X,10));while(z.length<2)z.push(0);while(Q.length<2)Q.push(0);for(let X=0;X<2;X++){let H=z[X]??0,Z=Q[X]??0;if(Number.isNaN(H)||Number.isNaN(Z))return 0;if(H!==Z)return H-Z}return 0}async function L0(K,$,z,Q=null){let X=await h($),H=X!==null,Z=H?await I0($):null,q="pass";if(!H)q=z==="required"?"fail":"warn";else if(Q&&Z){if(P0(Z,Q)<0)q=z==="required"?"fail":"warn"}return{name:K,command:$,found:H,version:Z,required:z,min_version:Q,status:q,path:X}}function m1(){let K=null;try{let z=G5(_0()),Q=Number(z.bavail)*Number(z.bsize);K=Math.round(Q/1073741824*10)/10}catch{K=null}let $="pass";if(K!==null){if(K<1)$="fail";else if(K<5)$="warn"}return{available_gb:K,status:$}}async function g1(K,$=2000){try{return(await fetch(K,{signal:AbortSignal.timeout($)})).ok}catch{return!1}}async function f1(K,$=!1){let z=`import ${K}`,Q=$?30000:5000;if(!$)return(await a(z,{timeoutMs:Q})).exitCode===0;let X=await r();if(!X)return!1;return(await w([X,"-c",z],{timeoutMs:Q})).exitCode===0}function Y5(K){T1.fn=K??f1}function R0(){let K=_0();return O5.map(({name:$,dir:z})=>{let Q=j0(K,z),X=Q,H=j0(Q,"SKILL.md");if(U5(H))return{name:$,path:X,status:"pass",detail:""};try{if(V5(Q).isSymbolicLink()){let q="unknown";try{q=J5(Q)}catch{}return{name:$,path:X,status:"fail",detail:`(broken symlink -> ${q})`}}}catch{}return{name:$,path:X,status:"warn",detail:"(not found - run 'loki setup-skill')"}})}async function E0(){return Promise.all(T5.map(async(K)=>{return{...await L0(K.jsonName,K.cmd,K.required,K.min??null),displayName:K.displayName}}))}async function A5(){let $=await h("sentrux")!==null,z=$?await I0("sentrux"):null;return{found:$,version:z,status:$?"pass":"warn",required:"optional"}}async function x0(){let $=(await E0()).map(({displayName:q,...U})=>U),z=m1(),Q=await A5(),X=0,H=0,Z=0;for(let q of $)if(q.status==="pass")X++;else if(q.status==="fail")H++;else Z++;if(z.status==="pass")X++;else if(z.status==="fail")H++;else Z++;return{loki_mode_version:G1(),checks:$,disk:z,sentrux:Q,summary:{passed:X,failed:H,warnings:Z,ok:H===0}}}function M(K){switch(K){case"pass":return`${D}PASS${W}`;case"fail":return`${R}FAIL${W}`;case"warn":return`${E}WARN${W}`}}function Y1(K){let $=K.version?` (v${K.version})`:"",z=K.displayName;if(!K.found){let Q=K.required==="required"?"not found":K.required==="recommended"?"not found (recommended)":"not found (optional)";return` ${M(K.status)} ${z} - ${Q}`}if(K.min_version&&K.version&&P0(K.version,K.min_version)<0){let Q=K.required==="required"?"requires":"recommended";return` ${M(K.status)} ${z}${$} - ${Q} >= ${K.min_version}`}return` ${M(K.status)} ${z}${$}`}function O1(K,$){if($==="pass")K.pass++;else if($==="fail")K.fail++;else K.warn++}function j5(){process.stdout.write(`${k}loki doctor${W} - Check system prerequisites
|
|
267
267
|
|
|
268
268
|
`),process.stdout.write(`Usage: loki doctor [--json]
|
|
269
269
|
|
|
@@ -272,43 +272,43 @@ Start a session with: loki start <prd>`}}let X=X5(Q);return{exitCode:0,stdout:$?
|
|
|
272
272
|
|
|
273
273
|
`),process.stdout.write(`Checks: node, python3, jq, git, curl, bash version,
|
|
274
274
|
`),process.stdout.write(` claude/codex CLIs, and disk space.
|
|
275
|
-
`)}async function
|
|
275
|
+
`)}async function _5(){process.stdout.write(`${k}Loki Mode Doctor${W}
|
|
276
276
|
|
|
277
277
|
`),process.stdout.write(`Checking system prerequisites...
|
|
278
278
|
|
|
279
|
-
`);let K={pass:0,fail:0,warn:0},$=await
|
|
280
|
-
`);for(let A of["node","python3","jq","git","curl"]){let N=z.get(A);process.stdout.write(
|
|
281
|
-
`),
|
|
279
|
+
`);let K={pass:0,fail:0,warn:0},$=await E0(),z=new Map($.map((A)=>[A.command,A]));process.stdout.write(`${O}Required:${W}
|
|
280
|
+
`);for(let A of["node","python3","jq","git","curl"]){let N=z.get(A);process.stdout.write(Y1(N)+`
|
|
281
|
+
`),O1(K,N.status)}process.stdout.write(`
|
|
282
282
|
`),process.stdout.write(`${O}AI Providers:${W}
|
|
283
|
-
`);let Q=["claude","codex","cline","aider"],X=!1;for(let A of Q){let N=z.get(A);if(process.stdout.write(
|
|
284
|
-
`),
|
|
283
|
+
`);let Q=["claude","codex","cline","aider"],X=!1;for(let A of Q){let N=z.get(A);if(process.stdout.write(Y1(N)+`
|
|
284
|
+
`),O1(K,N.status),N.found)X=!0}if(!X)process.stdout.write(` ${M("fail")} No AI provider CLI installed -- at least one is required
|
|
285
285
|
`),process.stdout.write(` ${E}Install: npm install -g @anthropic-ai/claude-code${W}
|
|
286
286
|
`),K.fail++;process.stdout.write(`
|
|
287
287
|
`),process.stdout.write(`${O}API Keys:${W}
|
|
288
|
-
`);let H=z.get("claude")?.found??!1,Z=z.get("codex")?.found??!1,
|
|
288
|
+
`);let H=z.get("claude")?.found??!1,Z=z.get("codex")?.found??!1,q=process.env;if(q.ANTHROPIC_API_KEY)process.stdout.write(` ${M("pass")} ANTHROPIC_API_KEY is set
|
|
289
289
|
`),K.pass++;else if(H)process.stdout.write(` ${x} -- ${W} ANTHROPIC_API_KEY not set (Claude CLI uses its own login)
|
|
290
|
-
`);if(
|
|
290
|
+
`);if(q.OPENAI_API_KEY)process.stdout.write(` ${M("pass")} OPENAI_API_KEY is set
|
|
291
291
|
`),K.pass++;else if(Z)process.stdout.write(` ${x} -- ${W} OPENAI_API_KEY not set (Codex CLI uses its own login)
|
|
292
|
-
`);if(
|
|
293
|
-
`),K.pass++,!
|
|
294
|
-
`),K.warn++;else process.stdout.write(` ${M("pass")} LOKI_MODEL_OVERRIDE: ${
|
|
292
|
+
`);if(q.ANTHROPIC_BASE_URL){let A=q.ANTHROPIC_BASE_URL;if(process.stdout.write(` ${M("pass")} ANTHROPIC_BASE_URL: ${A}
|
|
293
|
+
`),K.pass++,!q.LOKI_MODEL_OVERRIDE)process.stdout.write(` ${M("warn")} LOKI_MODEL_OVERRIDE not set -- opus/sonnet/haiku aliases may not resolve on alt-provider
|
|
294
|
+
`),K.warn++;else process.stdout.write(` ${M("pass")} LOKI_MODEL_OVERRIDE: ${q.LOKI_MODEL_OVERRIDE}
|
|
295
295
|
`),K.pass++}process.stdout.write(`
|
|
296
296
|
`),process.stdout.write(`${O}Skills:${W}
|
|
297
|
-
`);for(let A of
|
|
297
|
+
`);for(let A of R0())if(A.status==="pass")process.stdout.write(` ${M("pass")} ${A.name} ${x}${A.path}${W}
|
|
298
298
|
`),K.pass++;else if(A.status==="fail")process.stdout.write(` ${M("fail")} ${A.name} ${x}${A.detail}${W}
|
|
299
299
|
`),process.stdout.write(` ${E}Fix: loki setup-skill${W}
|
|
300
300
|
`),K.fail++;else process.stdout.write(` ${M("warn")} ${A.name} ${x}${A.detail}${W}
|
|
301
301
|
`),K.warn++;process.stdout.write(`
|
|
302
302
|
`),process.stdout.write(`${O}Integrations:${W}
|
|
303
|
-
`);let[
|
|
303
|
+
`);let[U,V,G]=await Promise.all([T1.fn("mcp"),T1.fn("numpy",!0),T1.fn("sentence_transformers",!0)]);if(U)process.stdout.write(` ${M("pass")} MCP SDK (Python)
|
|
304
304
|
`),K.pass++;else process.stdout.write(` ${M("warn")} MCP SDK - not installed (pip3 install mcp)
|
|
305
|
-
`),K.warn++;if(
|
|
305
|
+
`),K.warn++;if(V)process.stdout.write(` ${M("pass")} numpy (vector search)
|
|
306
306
|
`),K.pass++;else process.stdout.write(` ${M("warn")} numpy - not installed (pip3 install numpy)
|
|
307
307
|
`),K.warn++;if(G)process.stdout.write(` ${M("pass")} sentence-transformers (embeddings)
|
|
308
308
|
`),K.pass++;else process.stdout.write(` ${M("warn")} sentence-transformers - not installed (loki memory vectors setup)
|
|
309
|
-
`),K.warn++;if(await
|
|
309
|
+
`),K.warn++;if(await g1("http://localhost:8100/api/v2/heartbeat"))process.stdout.write(` ${M("pass")} ChromaDB server (port 8100)
|
|
310
310
|
`),K.pass++;else process.stdout.write(` ${M("warn")} ChromaDB - not running (docker start loki-chroma)
|
|
311
|
-
`),K.warn++;let B=process.env.LOKI_MIROFISH_URL;if(B)if(await
|
|
311
|
+
`),K.warn++;let B=process.env.LOKI_MIROFISH_URL;if(B)if(await g1(`${B}/health`))process.stdout.write(` ${M("pass")} MiroFish server (${B})
|
|
312
312
|
`),K.pass++;else process.stdout.write(` ${M("warn")} MiroFish - not running (loki start --mirofish-docker <image>)
|
|
313
313
|
`),K.warn++;if(process.env.LOKI_OTEL_ENDPOINT)process.stdout.write(` ${M("pass")} OTEL endpoint: ${process.env.LOKI_OTEL_ENDPOINT}
|
|
314
314
|
`),K.pass++;else process.stdout.write(` ${M("warn")} OTEL - not configured (set LOKI_OTEL_ENDPOINT)
|
|
@@ -316,9 +316,9 @@ Start a session with: loki start <prd>`}}let X=X5(Q);return{exitCode:0,stdout:$?
|
|
|
316
316
|
`),K.pass++}else process.stdout.write(` ${M("warn")} sentrux - not installed (optional, brew install sentrux/tap/sentrux)
|
|
317
317
|
`),K.warn++;process.stdout.write(`
|
|
318
318
|
`),process.stdout.write(`${O}System:${W}
|
|
319
|
-
`);let J=z.get("bash");process.stdout.write(
|
|
320
|
-
`),
|
|
321
|
-
`),
|
|
319
|
+
`);let J=z.get("bash");process.stdout.write(Y1(J)+`
|
|
320
|
+
`),O1(K,J.status);let Y=z.get("bun");if(Y)process.stdout.write(Y1(Y)+`
|
|
321
|
+
`),O1(K,Y.status);let j=m1(),_=j.available_gb===null?null:Math.floor(j.available_gb);if(_===null)process.stdout.write(` ${M("warn")} Disk space: unable to determine
|
|
322
322
|
`),K.warn++;else if(j.status==="fail")process.stdout.write(` ${M("fail")} Disk space: ${_}GB available (need >= 1GB)
|
|
323
323
|
`),K.fail++;else if(j.status==="warn")process.stdout.write(` ${M("warn")} Disk space: ${_}GB available (low)
|
|
324
324
|
`),K.warn++;else process.stdout.write(` ${M("pass")} Disk space: ${_}GB available
|
|
@@ -339,15 +339,15 @@ Start a session with: loki start <prd>`}}let X=X5(Q);return{exitCode:0,stdout:$?
|
|
|
339
339
|
`),process.stdout.write(`Install missing dependencies and run 'loki doctor' again.
|
|
340
340
|
`),1;if(K.warn>0)return process.stdout.write(`${E}All required checks passed with some warnings.${W}
|
|
341
341
|
`),0;return process.stdout.write(`${D}All checks passed. System is ready for Loki Mode.${W}
|
|
342
|
-
`),0}async function
|
|
342
|
+
`),0}async function I5(K){let $=!1;for(let z of K)if(z==="--json")$=!0;else if(z==="--help"||z==="-h")return j5(),0;else return process.stderr.write(`${R}Unknown option: ${z}${W}
|
|
343
343
|
`),process.stderr.write(`Usage: loki doctor [--json]
|
|
344
|
-
`),1;if($){let z=await
|
|
345
|
-
`),0}return
|
|
346
|
-
`)}var
|
|
344
|
+
`),1;if($){let z=await x0();return process.stdout.write(JSON.stringify(z,null,2)+`
|
|
345
|
+
`),0}return _5()}var B5,T1,O5,T5;var w0=L(()=>{p();Q1();n();D1();B5=/(\d+\.\d+(?:\.\d+)*)/;T1={fn:f1};O5=[{name:"Claude Code",dir:".claude/skills/loki-mode"},{name:"Codex CLI",dir:".codex/skills/loki-mode"},{name:"Cline CLI",dir:".cline/skills/loki-mode"},{name:"Aider CLI",dir:".aider/skills/loki-mode"}];T5=[{displayName:"Node.js (>= 18)",jsonName:"Node.js",cmd:"node",required:"required",min:"18.0"},{displayName:"Python 3 (>= 3.8)",jsonName:"Python 3",cmd:"python3",required:"required",min:"3.8"},{displayName:"jq",jsonName:"jq",cmd:"jq",required:"required"},{displayName:"git",jsonName:"git",cmd:"git",required:"required"},{displayName:"curl",jsonName:"curl",cmd:"curl",required:"required"},{displayName:"bash (>= 4.0)",jsonName:"bash",cmd:"bash",required:"recommended",min:"4.0"},{displayName:"Bun (>= 1.3)",jsonName:"Bun",cmd:"bun",required:"recommended",min:"1.3"},{displayName:"Claude CLI",jsonName:"Claude CLI",cmd:"claude",required:"optional"},{displayName:"Codex CLI",jsonName:"Codex CLI",cmd:"codex",required:"optional"},{displayName:"Cline CLI",jsonName:"Cline CLI",cmd:"cline",required:"optional"},{displayName:"Aider CLI",jsonName:"Aider CLI",cmd:"aider",required:"optional"}]});import{existsSync as k0,mkdirSync as e6,readdirSync as P5,readFileSync as D0,renameSync as K8,writeFileSync as $8}from"fs";import{dirname as L5,join as R5,resolve as E5}from"path";import{fileURLToPath as x5}from"url";function F5(){try{let K=L5(x5(import.meta.url)),$=E5(K,"..","..","data","model-pricing.json");if(!k0($))return H1;let Q=JSON.parse(D0($,"utf8")).pricing;if(!Q||typeof Q!=="object")return H1;let X={};for(let[H,Z]of Object.entries(Q))if(Z!==null&&typeof Z==="object"&&typeof Z.input==="number"&&typeof Z.output==="number")X[H]={input:Z.input,output:Z.output};for(let H of Object.keys(H1))if(!(H in X))return H1;return X}catch{return H1}}function w5(K){return Math.round((K+Number.EPSILON)*1e4)/1e4}function S5(K){let $=(K??N0).toLowerCase();return S0[$]??S0[N0]}function C0(K){let $=0;for(let z of K){if(typeof z.cost_usd==="number"&&Number.isFinite(z.cost_usd)){$+=z.cost_usd;continue}let Q=S5(z.model),X=typeof z.input_tokens==="number"?z.input_tokens:0,H=typeof z.output_tokens==="number"?z.output_tokens:0;$+=X/1e6*Q.input+H/1e6*Q.output}return w5($)}function h0(K){if(!k0(K))return[];let $=[],z;try{z=P5(K)}catch{return[]}for(let Q of z){if(!Q.endsWith(".json"))continue;let X=R5(K,Q);try{let H=D0(X,"utf8"),Z=JSON.parse(H);if(Z&&typeof Z==="object")$.push(Z)}catch{}}return $}var H1,S0,N0="sonnet";var b0=L(()=>{y();H1={opus:{input:5,output:25},sonnet:{input:3,output:15},haiku:{input:1,output:5},"gpt-5.3-codex":{input:1.5,output:12}};S0=Object.freeze(F5())});import{existsSync as A1,readdirSync as N5,readFileSync as k5,statSync as D5}from"fs";import{join as j1}from"path";function C5(K){let $=[],z=j1(K,"votes");if(!A1(z))return $;let Q;try{Q=N5(z)}catch{return $}for(let X of Q){if(!X.startsWith("round-")||!X.endsWith(".json"))continue;try{let H=j1(z,X);if(!D5(H).isFile())continue;let Z=JSON.parse(k5(H,"utf8"));$.push({iteration:typeof Z.iteration==="number"?Z.iteration:void 0,verdict:typeof Z.verdict==="string"?Z.verdict:void 0,complete_votes:typeof Z.complete_votes==="number"?Z.complete_votes:void 0,total_members:typeof Z.total_members==="number"?Z.total_members:void 0,threshold:typeof Z.threshold==="number"?Z.threshold:void 0})}catch{}}return $}function h5(){return{iteration_count:0,total_cost_usd:0,avg_cost_per_iteration:null,total_input_tokens:0,total_output_tokens:0,total_duration_ms:0,avg_duration_ms_per_iteration:null,model_breakdown:{},phase_breakdown:{},status_breakdown:{}}}function b5(){return{council_rounds:0,unanimous_rate:null,approval_rate:null,iteration_success_rate:null}}function y5(K){let $=h5();if(K.length===0)return $;$.iteration_count=K.length,$.total_cost_usd=Math.round(C0(K)*1e4)/1e4;for(let z of K){if(typeof z.input_tokens==="number")$.total_input_tokens+=z.input_tokens;if(typeof z.output_tokens==="number")$.total_output_tokens+=z.output_tokens;let Q=z;if(typeof Q.duration_ms==="number")$.total_duration_ms+=Q.duration_ms;if(typeof z.model==="string")$.model_breakdown[z.model]=($.model_breakdown[z.model]??0)+1;if(typeof Q.phase==="string")$.phase_breakdown[Q.phase]=($.phase_breakdown[Q.phase]??0)+1;if(typeof Q.status==="string")$.status_breakdown[Q.status]=($.status_breakdown[Q.status]??0)+1}return $.avg_cost_per_iteration=Math.round($.total_cost_usd/$.iteration_count*1e4)/1e4,$.avg_duration_ms_per_iteration=Math.round($.total_duration_ms/$.iteration_count),$}function v5(K,$,z){let Q=b5();if(Q.council_rounds=K.length,K.length>0){let X=0,H=0;for(let Z of K){if(typeof Z.complete_votes==="number"&&typeof Z.total_members==="number"&&Z.total_members>0&&Z.complete_votes===Z.total_members)X+=1;if(Z.verdict==="COMPLETE")H+=1}Q.unanimous_rate=Math.round(X/K.length*1e4)/1e4,Q.approval_rate=Math.round(H/K.length*1e4)/1e4}if(z>0)Q.iteration_success_rate=Math.round($/z*1e4)/1e4;return Q}function y0(K){let $=[],z=j1(K,"metrics","efficiency"),Q=j1(K,"council"),X=A1(z)?h0(z):[];if(!A1(z))$.push("no .loki/metrics/efficiency/ dir (efficiency KPIs zeroed)");else if(X.length===0)$.push(".loki/metrics/efficiency/ exists but no iteration files found");let H=C5(Q);if(!A1(Q))$.push("no .loki/council/ dir (accuracy KPIs zeroed)");else if(H.length===0)$.push(".loki/council/ exists but no round-N.json files found");let Z=y5(X),q=Z.status_breakdown.success??0,U=v5(H,q,Z.iteration_count);return{schema_version:1,generated_at:new Date().toISOString(),loki_dir:K,efficiency:Z,accuracy:U,notes:$}}function v0(K){return JSON.stringify(K,null,2)}function g0(K){let $=[];$.push(`Loki Mode KPIs (snapshot at ${K.generated_at})`),$.push(`Source: ${K.loki_dir}`),$.push(""),$.push("Efficiency"),$.push(` Iterations: ${K.efficiency.iteration_count}`),$.push(` Total cost USD: ${K.efficiency.total_cost_usd}`),$.push(` Avg cost per iter: ${K.efficiency.avg_cost_per_iteration??"n/a"}`),$.push(` Total input tokens: ${K.efficiency.total_input_tokens}`),$.push(` Total output tokens: ${K.efficiency.total_output_tokens}`),$.push(` Total duration (ms): ${K.efficiency.total_duration_ms}`),$.push(` Avg duration / iter: ${K.efficiency.avg_duration_ms_per_iteration??"n/a"}`);let z=Object.entries(K.efficiency.model_breakdown).sort((H,Z)=>H[0].localeCompare(Z[0]));if(z.length>0)$.push(` Model breakdown: ${z.map(([H,Z])=>`${H}=${Z}`).join(", ")}`);let Q=Object.entries(K.efficiency.phase_breakdown).sort((H,Z)=>H[0].localeCompare(Z[0]));if(Q.length>0)$.push(` Phase breakdown: ${Q.map(([H,Z])=>`${H}=${Z}`).join(", ")}`);let X=Object.entries(K.efficiency.status_breakdown).sort((H,Z)=>H[0].localeCompare(Z[0]));if(X.length>0)$.push(` Status breakdown: ${X.map(([H,Z])=>`${H}=${Z}`).join(", ")}`);if($.push(""),$.push("Accuracy"),$.push(` Council rounds: ${K.accuracy.council_rounds}`),$.push(` Unanimous rate: ${K.accuracy.unanimous_rate??"n/a"}`),$.push(` Approval rate: ${K.accuracy.approval_rate??"n/a"}`),$.push(` Iter success rate: ${K.accuracy.iteration_success_rate??"n/a"}`),K.notes.length>0){$.push(""),$.push("Notes");for(let H of K.notes)$.push(` - ${H}`)}return $.join(`
|
|
346
|
+
`)}var m0=L(()=>{b0()});var f0={};b(f0,{runKpis:()=>m5});function m5(K){let $=!1;for(let Q of K){if(Q==="--help"||Q==="-h"||Q==="help")return process.stdout.write(g5),0;if(Q==="--json"){$=!0;continue}return process.stderr.write(`loki kpis: unknown arg: ${Q}
|
|
347
347
|
Run 'loki kpis --help' for usage.
|
|
348
|
-
`),1}let z=
|
|
349
|
-
`:
|
|
350
|
-
`),0}var
|
|
348
|
+
`),1}let z=y0(P());return process.stdout.write($?v0(z)+`
|
|
349
|
+
`:g0(z)+`
|
|
350
|
+
`),0}var g5=`loki kpis -- accuracy + efficiency KPI snapshot (v7.5.28 MVP)
|
|
351
351
|
|
|
352
352
|
Usage:
|
|
353
353
|
loki kpis Pretty-print KPI snapshot
|
|
@@ -367,23 +367,23 @@ iteration success rate.
|
|
|
367
367
|
This is the Phase K MVP -- read-only derivation. Per-iteration
|
|
368
368
|
emission, dashboard panel, and the loki-bench harness are deferred
|
|
369
369
|
follow-ups (see project_v7_5_18_arc_status.md).
|
|
370
|
-
`;var
|
|
371
|
-
`),
|
|
370
|
+
`;var u0=L(()=>{m0();y()});import{closeSync as B8,fstatSync as M8,lstatSync as Y8,mkdirSync as f5,openSync as O8,readSync as T8,renameSync as u5,rmSync as A8,statSync as j8,unlinkSync as _8,writeFileSync as p5,writeSync as I8}from"fs";import{dirname as c5}from"path";function W1(K,$){f5(c5(K),{recursive:!0});let z=`${K}.tmp.${process.pid}.${++l5}`;p5(z,`${JSON.stringify($,null,2)}
|
|
371
|
+
`),u5(z,K)}async function p0(K,$){let z=_1.get(K)??Promise.resolve(),Q=()=>{},X=new Promise((Z)=>{Q=Z}),H=z.catch(()=>{}).then(()=>X);_1.set(K,H);try{return await z.catch(()=>{}),await $()}finally{if(Q(),_1.get(K)===H)_1.delete(K)}}var l5=0,_1;var I1=L(()=>{_1=new Map});import{existsSync as P1,mkdirSync as d5,copyFileSync as o5,readFileSync as n5,readdirSync as a5,statSync as s5,writeFileSync as x8,renameSync as r5,appendFileSync as F8,rmSync as w8}from"fs";import{join as s,dirname as t5}from"path";function L1(K){return s(K,"state","checkpoints")}function e5(K){let $=L1(K);if(!P1($))return[];return a5($).filter((z)=>z.startsWith("cp-")).filter((z)=>{try{return s5(s($,z)).isDirectory()}catch{return!1}})}function KK(K){return[...K].sort(($,z)=>{let Q=c0($),X=c0(z);return Q-X})}function c0(K){let $=K.split("-");if($.length<3)return 0;let z=$[$.length-1],Q=Number.parseInt(z??"0",10);return Number.isFinite(Q)?Q:0}function p1(K){let $=K??P(),z=KK(e5($)),Q=[];for(let X of z){let H=l0($,X);if(H)Q.push(H)}return Q}function l0(K,$){let z=s(L1(K),$,"metadata.json");if(!P1(z))return null;try{let Q=JSON.parse(n5(z,"utf-8"));return $K(Q,z)}catch{return null}}function $K(K,$){let z=XK(K,$);return z.ok?z.value:null}function XK(K,$){if(K===null||typeof K!=="object")return console.warn(`[checkpoint] invalid metadata at ${$}: not an object`),{ok:!1,reason:"invalid_type",field:"<root>"};let z=K,Q=["id","timestamp","task_id","task_description","git_sha","git_branch","provider","phase"];for(let X of Q){if(!(X in z))return console.warn(`[checkpoint] invalid metadata at ${$}: field "${X}" missing`),{ok:!1,reason:"missing_field",field:X};if(typeof z[X]!=="string")return console.warn(`[checkpoint] invalid metadata at ${$}: field "${X}" not a string`),{ok:!1,reason:"invalid_type",field:X}}if(!Object.prototype.hasOwnProperty.call(z,"iteration"))return console.warn(`[checkpoint] invalid metadata at ${$}: field "iteration" missing`),{ok:!1,reason:"missing_field",field:"iteration"};if(typeof z.iteration!=="number"||!Number.isFinite(z.iteration))return console.warn(`[checkpoint] invalid metadata at ${$}: field "iteration" not a finite number`),{ok:!1,reason:"invalid_type",field:"iteration"};for(let X of QK){let H=z[X];if(zK.test(H))return console.warn(`[checkpoint] invalid metadata at ${$}: field "${X}" contains control characters`),{ok:!1,reason:"control_chars",field:X}}return{ok:!0,value:{id:z.id,timestamp:z.timestamp,iteration:z.iteration,task_id:z.task_id,task_description:z.task_description,git_sha:z.git_sha,git_branch:z.git_branch,provider:z.provider,phase:z.phase}}}function c1(K,$){if(!ZK.test(K))throw new d0(K);let z=$??P(),Q=s(L1(z),K);if(!P1(Q))throw new u1(K);let X=l0(z,K);if(!X)throw new u1(K);return X}function o0(K,$){let z=c1(K,$),Q=$??P(),X=s(L1(Q),K),H=[];for(let Z of HK){let q=s(X,Z);if(!P1(q))continue;H.push({from:q,to:s(Q,Z)})}return{id:K,metadata:z,restore:H}}function n0(K){let $=[],z=0;for(let Q of K.restore)try{d5(t5(Q.to),{recursive:!0});let X=`${Q.to}.tmp.${process.pid}.${++i5}`;o5(Q.from,X),r5(X,Q.to),z+=1}catch(X){$.push(`${Q.from} -> ${Q.to}: ${X.message}`)}return{restored:z,errors:$}}var C8,i5=0,zK,QK,ZK,u1,d0,HK;var a0=L(()=>{y();p();I1();C8=Promise.resolve();zK=/[\x00-\x08\x0a-\x1f\x7f-\x9f]/,QK=["id","task_id","git_sha","git_branch","provider","phase"];ZK=/^[a-zA-Z0-9_-]+$/;u1=class u1 extends Error{id;constructor(K){super(`Checkpoint not found: ${K}`);this.id=K;this.name="CheckpointNotFoundError"}};d0=class d0 extends Error{id;constructor(K){super(`Invalid checkpoint ID: must be alphanumeric, hyphens, underscores only (got: ${K})`);this.id=K;this.name="InvalidCheckpointIdError"}};HK=["state/orchestrator.json","queue/pending.json","queue/completed.json","queue/in-progress.json","queue/current-task.json"]});var t0={};b(t0,{runRollback:()=>WK});async function WK(K){let $=K[0],z=K.slice(1);if($===void 0||$==="help"||$==="--help"||$==="-h")return process.stdout.write(s0),$===void 0?1:0;switch($){case"list":{let Q=[...p1()].reverse();if(Q.length===0)return process.stdout.write(`${E}No checkpoints found.${W}
|
|
372
372
|
`),0;process.stdout.write(`${k}Checkpoints${W} (${Q.length}, newest first):
|
|
373
373
|
`);for(let X of Q)process.stdout.write(` ${O}${X.id}${W} iter=${X.iteration} ${X.git_branch||"(no branch)"}@${(X.git_sha||"").slice(0,7)} ${X.timestamp}
|
|
374
374
|
`);return 0}case"show":{let Q=z[0];if(!Q)return process.stderr.write(`${R}Missing checkpoint id.${W} Use \`loki rollback list\`.
|
|
375
|
-
`),2;try{let X=
|
|
375
|
+
`),2;try{let X=c1(Q);return process.stdout.write(`${JSON.stringify(X,null,2)}
|
|
376
376
|
`),0}catch(X){return process.stderr.write(`${R}Failed to read checkpoint:${W} ${X.message}
|
|
377
377
|
`),1}}case"to":{let Q=z[0];if(!Q)return process.stderr.write(`${R}Missing checkpoint id.${W} Use \`loki rollback list\`.
|
|
378
|
-
`),2;return
|
|
378
|
+
`),2;return r0(Q)}case"latest":{let Q=p1(),X=Q[Q.length-1];if(!X)return process.stderr.write(`${R}No checkpoints found to roll back to.${W}
|
|
379
379
|
`),1;return process.stdout.write(`Rolling back to latest checkpoint: ${O}${X.id}${W}
|
|
380
|
-
`),
|
|
381
|
-
`),process.stderr.write(
|
|
380
|
+
`),r0(X.id)}default:return process.stderr.write(`Unknown subcommand: ${$}
|
|
381
|
+
`),process.stderr.write(s0),2}}function r0(K){let $;try{$=o0(K)}catch(Q){return process.stderr.write(`${R}Cannot plan rollback:${W} ${Q.message}
|
|
382
382
|
`),1}if($.restore.length===0)return process.stdout.write(`${E}Checkpoint ${K} has no restorable state files; nothing to do.${W}
|
|
383
|
-
`),0;let z=
|
|
383
|
+
`),0;let z=n0($);if(z.errors.length>0){for(let Q of z.errors)process.stderr.write(`${R}restore error:${W} ${Q}
|
|
384
384
|
`);return process.stderr.write(`${R}Partial rollback: ${z.restored}/${$.restore.length} files restored.${W}
|
|
385
385
|
`),1}return process.stdout.write(`${D}Rolled back ${z.restored}/${$.restore.length} state files from ${K}.${W}
|
|
386
|
-
`),process.stdout.write("Run `loki start` to resume from the restored state.\n"),0}var
|
|
386
|
+
`),process.stdout.write("Run `loki start` to resume from the restored state.\n"),0}var s0=`Usage: loki rollback <subcommand>
|
|
387
387
|
|
|
388
388
|
Subcommands:
|
|
389
389
|
list List checkpoints (newest first)
|
|
@@ -398,8 +398,8 @@ Restored files (matches autonomy/run.sh:7028 byte-for-byte):
|
|
|
398
398
|
Note: only state files are restored. Source code, git history, and the
|
|
399
399
|
session's autonomy-state.json are unchanged. Re-run \`loki start\` to
|
|
400
400
|
resume from the restored state.
|
|
401
|
-
`;var
|
|
402
|
-
`)}function
|
|
401
|
+
`;var i0=L(()=>{a0();n()});var d1={};b(d1,{renderFindingsForPrompt:()=>GK,loadPreviousFindings:()=>l1,findLatestReviewDir:()=>Q7,_parseReviewerOutputForTests:()=>BK});import{existsSync as K7,readFileSync as e0,readdirSync as $7,statSync as qK}from"fs";import{join as R1}from"path";function JK(K){let $=K.toLowerCase();if($==="critical")return"Critical";if($==="high")return"High";if($==="medium")return"Medium";return"Low"}function z7(K,$,z,Q){let X=[],H=K.split(/\r?\n/);for(let Z of H){let q=Z.trim();if(q.length===0)continue;let U=q.replace(/^[-*]\s*/,""),V=UK.exec(U);if(!V||!V[1]||!V[2])continue;let G=JK(V[1]),B=V[2].trim(),J=VK.exec(B),Y=J&&J[1]?J[1]:null,j=J&&J[2]?Number.parseInt(J[2],10):null;X.push({reviewId:z,iteration:Q,reviewer:$,severity:G,description:B,file:Y,line:Number.isFinite(j)?j:null,raw:q})}return X}function Q7(K,$){let z=R1(K,"quality","reviews");if(!K7(z))return null;let Q;try{Q=$7(z)}catch{return null}let X=$===void 0?Q.filter((q)=>q.startsWith("review-")):Q.filter((q)=>q.endsWith(`-${$}`)&&q.startsWith("review-"));if(X.length===0)return null;X.sort();let H=X[X.length-1];if(!H)return null;let Z=R1(z,H);try{if(!qK(Z).isDirectory())return null}catch{return null}return Z}function l1(K,$){let z=Q7(K,$);if(z===null)return{reviewDir:null,reviewId:null,iteration:null,findings:[]};let Q=null,X=null,H=R1(z,"aggregate.json");if(K7(H))try{let V=e0(H,"utf-8"),G=JSON.parse(V);if(typeof G.review_id==="string")Q=G.review_id;if(typeof G.iteration==="number")X=G.iteration}catch{}let Z;try{Z=$7(z)}catch{return{reviewDir:z,reviewId:Q,iteration:X,findings:[]}}let q=new Set(["diff.txt","files.txt","anti-sycophancy.txt"]),U=[];for(let V of Z){if(!V.endsWith(".txt"))continue;if(q.has(V))continue;if(V.endsWith("-prompt.txt"))continue;let G=V.replace(/\.txt$/,""),B;try{B=e0(R1(z,V),"utf-8")}catch{continue}U.push(...z7(B,G,Q??"",X??-1))}return{reviewDir:z,reviewId:Q,iteration:X,findings:U}}function GK(K){if(K.length===0)return"";let $=["Critical","High","Medium","Low"],z=new Map;for(let X of $)z.set(X,[]);for(let X of K){let H=z.get(X.severity);if(H)H.push(X)}let Q=[];Q.push("PREVIOUS REVIEWER FINDINGS (must address each, or supply counter-evidence in .loki/state/counter-evidence-<iter>.json):");for(let X of $){let H=z.get(X)??[];if(H.length===0)continue;Q.push(` [${X}] (${H.length}):`);for(let Z of H){let q=Z.file?` (${Z.file}${Z.line!==null?":"+Z.line:""})`:"";Q.push(` - ${Z.description}${q} -- via ${Z.reviewer}`)}}return Q.join(`
|
|
402
|
+
`)}function BK(K,$,z="review-test",Q=0){return z7(K,$,z,Q)}var UK,VK;var E1=L(()=>{UK=/\[(Critical|High|Medium|Low)\]\s*(.+)/i,VK=/([\w.\-/]+\.[a-zA-Z]+):(\d+)/});import{existsSync as MK}from"fs";import{join as YK}from"path";async function X7(K,$){let z=YK(K,"memory");if(!MK(z))return{stored:!1,reason:"memory dir not initialized"};let Q=Math.max(0,Math.floor($.durationSeconds??0)),X={_LOKI_PROJECT_DIR:u,_LOKI_TARGET_DIR:process.cwd(),_LOKI_TASK_ID:$.taskId,_LOKI_OUTCOME:$.outcome,_LOKI_PHASE:$.phase,_LOKI_GOAL:$.goal,_LOKI_DURATION:String(Q),_LOKI_LOKI_DIR:K},Z=await a(`
|
|
403
403
|
import os, sys
|
|
404
404
|
project = os.environ.get('_LOKI_PROJECT_DIR', '')
|
|
405
405
|
loki = os.environ.get('_LOKI_LOKI_DIR', '.loki')
|
|
@@ -426,23 +426,23 @@ try:
|
|
|
426
426
|
print('OK')
|
|
427
427
|
except Exception as e:
|
|
428
428
|
print('ERR:' + str(e))
|
|
429
|
-
`,{env:X,timeoutMs:15000});if(Z.exitCode===127)return{stored:!1,reason:"python3 not found"};let
|
|
430
|
-
`)}async function
|
|
431
|
-
`)}function
|
|
432
|
-
`),process.stderr.write(
|
|
433
|
-
`),2;let z=P();try{let X=(await Promise.resolve().then(() => (
|
|
434
|
-
`),0;let H=
|
|
429
|
+
`,{env:X,timeoutMs:15000});if(Z.exitCode===127)return{stored:!1,reason:"python3 not found"};let q=Z.stdout.trim();if(q==="OK")return{stored:!0,reason:"stored"};if(q.startsWith("ERR:"))return{stored:!1,reason:q.replace(/^ERR:/,"")};return{stored:!1,reason:Z.stderr.trim()||"unknown"}}var Z7=L(()=>{Q1();y()});var U7={};b(U7,{loadLearnings:()=>o1,appendLearning:()=>q1,appendFromGateFailure:()=>LK});import{existsSync as OK,readFileSync as TK}from"fs";import{join as H7}from"path";import{createHash as AK}from"crypto";function W7(K){return H7(K,jK)}function _K(K){if(K===null||typeof K!=="object")return!1;let $=K;return typeof $.id==="string"&&typeof $.timestamp==="string"&&typeof $.iteration==="number"&&typeof $.trigger==="string"&&typeof $.rootCause==="string"&&typeof $.fix==="string"&&typeof $.preventInFuture==="string"&&typeof $.evidence==="object"&&$.evidence!==null}function q7(K){if(!OK(K))return{version:1,learnings:[]};try{let $=TK(K,"utf-8"),z=JSON.parse($);if(z.version===1&&Array.isArray(z.learnings))return{version:1,learnings:z.learnings.filter(_K)}}catch{}return{version:1,learnings:[]}}function IK(K,$){return AK("sha256").update(`${K}\x00${$}`).digest("hex").slice(0,16)}async function q1(K,$,z={}){let Q=IK($.trigger,$.rootCause),X=new Date().toISOString(),H={id:Q,timestamp:X,...$},Z=W7(K);if(await p0(Z,()=>{let U=q7(Z),V=U.learnings.findIndex((G)=>G.id===Q);if(V>=0){let G=U.learnings[V];U.learnings[V]={...G,timestamp:X,iteration:H.iteration}}else U.learnings.push(H);W1(Z,U)}),z.episodeBridge!==null&&(z.episodeBridge!==void 0||process.env.LOKI_AUTO_LEARNINGS_EPISODE==="1")){let U=z.episodeBridge??X7,V=z.bridgeFailureLog??PK;try{let G=await U(K,{taskId:`learning-${Q}`,outcome:"failure",phase:"VERIFY",goal:`${$.trigger}: ${$.rootCause}`});if(G&&!G.stored){if(!new Set(["memory dir not initialized","stub"]).has(G.reason))V(`episode_bridge skipped: ${G.reason}`)}}catch(G){V(`episode_bridge threw: ${G.message}`)}}return H}function PK(K){process.stderr.write(`[learnings_writer] ${K}
|
|
430
|
+
`)}async function LK(K,$,z,Q={}){let X=`[${z.severity}] ${z.description}`;return q1(K,{iteration:$,trigger:"gate_failure",rootCause:X,fix:"pending: dev agent must address in next iteration or supply counter-evidence",preventInFuture:"if this finding recurs, lower its severity threshold or add a regression test",evidence:{reviewId:z.reviewId,file:z.file??void 0,line:z.line??void 0,severity:z.severity,reviewer:z.reviewer}},Q)}function o1(K){return q7(W7(K))}var jK;var x1=L(()=>{I1();Z7();jK=H7("state","relevant-learnings.json")});var J7={};b(J7,{runOverrideCouncil:()=>SK,recordOverrideOutcome:()=>NK,loadCounterEvidence:()=>wK,canonicalFindingId:()=>n1,DEFAULT_OVERRIDE_JUDGES:()=>V7});import{existsSync as RK,readFileSync as EK}from"fs";import{join as xK}from"path";function wK(K,$){let z=xK(K,"state",`counter-evidence-${$}.json`);if(!RK(z))return null;try{let Q=EK(z,"utf-8"),X=JSON.parse(Q);if(typeof X.iteration!=="number")return null;let H=Array.isArray(X.evidence)?X.evidence:[],Z=[];for(let q of H){if(typeof q!=="object"||q===null)continue;let U=q;if(typeof U.findingId!=="string")continue;if(typeof U.claim!=="string")continue;let V=U.proofType;if(typeof V!=="string"||!FK.has(V))continue;let G=V,B=Array.isArray(U.artifacts)?U.artifacts:[];Z.push({findingId:U.findingId,claim:U.claim,proofType:G,artifacts:B.filter((J)=>typeof J==="string")})}return{iteration:X.iteration,evidence:Z}}catch{return null}}async function SK(K,$,z,Q={}){let X=Q.judges??V7,H=new Set,Z=new Set,q={},U=new Map;for(let V of $.evidence)U.set(V.findingId,V);for(let V of K){let G=n1(V),B=U.get(G);if(!B){Z.add(G);continue}let J=await Promise.all(X.map((j)=>z({finding:V,evidence:B,judge:j})));if(q[G]=J,J.filter((j)=>j.verdict==="APPROVE_OVERRIDE").length>=2)H.add(G);else Z.add(G)}return{approvedFindingIds:H,rejectedFindingIds:Z,votes:q}}function n1(K){let $=K.raw.slice(0,80).replace(/\s+/g," ").trim();return`${K.reviewer}::${$}`}async function NK(K,$,z,Q,X={}){let H={episodeBridge:X.episodeBridge===void 0?null:X.episodeBridge};for(let Z of Q){let q=n1(Z);if(z.approvedFindingIds.has(q))await q1(K,{iteration:$,trigger:"override_approved",rootCause:`[${Z.severity}] ${Z.description}`,fix:"override council approved counter-evidence; finding lifted",preventInFuture:"if this reviewer/file pair recurs, narrow the reviewer's selector OR add a baseline doc",evidence:{findingId:q,reviewId:Z.reviewId,file:Z.file??void 0,line:Z.line??void 0,severity:Z.severity,reviewer:Z.reviewer}},H);else if(z.rejectedFindingIds.has(q))await q1(K,{iteration:$,trigger:"override_rejected",rootCause:`[${Z.severity}] ${Z.description}`,fix:"override council rejected -- dev agent must fix the finding",preventInFuture:"address this finding in the next iteration",evidence:{findingId:q,reviewId:Z.reviewId,file:Z.file??void 0,line:Z.line??void 0,severity:Z.severity,reviewer:Z.reviewer}},H)}}var FK,V7;var G7=L(()=>{x1();FK=new Set(["file-exists","test-passes","grep-miss","reviewer-misread","duplicate-code-path","out-of-scope"]);V7=["judge-primary","judge-secondary","judge-tertiary"]});var Y7={};b(Y7,{writeEscalationHandoff:()=>pK,renderHandoff:()=>B7,readLatestHandoff:()=>cK});import{existsSync as kK,mkdirSync as DK,readdirSync as CK,readFileSync as hK,renameSync as bK,writeFileSync as yK}from"fs";import{dirname as vK,join as F1}from"path";function gK(){return new Date().toISOString()}function mK(K){let $=K.file?` (${K.file}${K.line!==null?":"+K.line:""})`:"";return` - [${K.severity}] ${K.description}${$} -- ${K.reviewer}`}function fK(K){let $=K.evidence,z=$.file?` ${$.file}${$.line!==void 0?":"+$.line:""}`:"";return` - **${K.trigger}** (iter ${K.iteration})${z}: ${K.rootCause}`}function B7(K,$,z){let Q=[];if(Q.push(`# Loki escalation handoff -- ${gK()}`),Q.push(""),Q.push(`Gate **${K.gateName}** has failed ${K.consecutiveFailures} consecutive times at iteration ${K.iteration}.`),Q.push(""),Q.push(`Reason: ${K.detail}`),Q.push(""),$.length>0){Q.push(`## Outstanding findings (${$.length})`),Q.push("");for(let X of $)Q.push(mK(X));Q.push("")}else Q.push("## Outstanding findings"),Q.push(""),Q.push("(no per-finding records captured -- gate failed without populating reviewer outputs)"),Q.push("");if(z.length>0){Q.push(`## Recent learnings (${Math.min(z.length,10)})`),Q.push("");for(let X of z.slice(-10))Q.push(fK(X));Q.push("")}return Q.push("## What the human must decide"),Q.push(""),Q.push("- Approve override? Write `.loki/state/counter-evidence-<iter>.json` with one entry per finding to dispute, then `rm .loki/PAUSE` to resume."),Q.push("- Disable a gate? Set `LOKI_GATE_<NAME>=false` in env (see skills/quality-gates.md)."),Q.push("- Tweak escalation? Set `LOKI_GATE_PAUSE_LIMIT` or `LOKI_GATE_ESCALATE_LIMIT`."),Q.push("- Roll back? Switch to `LOKI_LEGACY_BASH=1` and re-run; the bash route does not consult this handoff doc."),Q.push(""),Q.push("To resume: address the findings (or supply counter-evidence) and `rm .loki/PAUSE`."),Q.join(`
|
|
431
|
+
`)}function uK(K,$){DK(vK(K),{recursive:!0});let z=`${K}.tmp.${process.pid}.${++M7}`;yK(z,$),bK(z,K)}function pK(K,$,z={}){let Q=z.findings??l1(K,$.iteration).findings,X=z.learnings??o1(K).learnings,H=B7($,Q,X),Z=(z.now?.()??new Date).toISOString().replace(/[-:.]/g,""),q=F1(K,"escalations"),U=++M7,V=F1(q,`handoff-${Z}-${process.pid}-${U}-${$.gateName}.md`);return uK(V,H),{path:V,bytes:H.length}}function cK(K){let $=F1(K,"escalations");if(!kK($))return null;let z;try{z=CK($).filter((H)=>H.endsWith(".md"))}catch{return null}if(z.length===0)return null;z.sort();let Q=z[z.length-1];if(!Q)return null;let X=F1($,Q);try{return{path:X,body:hK(X,"utf-8")}}catch{return null}}var M7=0;var O7=L(()=>{E1();x1()});var T7={};b(T7,{runInternalPhase1Hooks:()=>sK,_resolveForTests:()=>aK,_internalPhase1HooksHelp:()=>eK});import{existsSync as lK,mkdirSync as dK,readdirSync as oK,statSync as nK}from"fs";import{join as U1,resolve as aK}from"path";async function sK(K){let[$,...z]=K;switch($){case void 0:case"help":case"--help":case"-h":return process.stdout.write(a1),$===void 0?1:0;case"reflect":return rK(z);case"override":return tK(z);case"handoff":return iK(z);default:return process.stderr.write(`Unknown subcommand: ${$}
|
|
432
|
+
`),process.stderr.write(a1),2}}async function rK(K){let $=s1(K[0]);if($===null)return process.stderr.write(`reflect: missing or invalid <iter>
|
|
433
|
+
`),2;let z=P();try{let X=(await Promise.resolve().then(() => (E1(),d1))).loadPreviousFindings(z,$);if(X.findings.length===0)return process.stdout.write(`reflect: no findings for iter ${$} (nothing to do)
|
|
434
|
+
`),0;let H=U1(z,"state");dK(H,{recursive:!0}),W1(U1(H,`findings-${$}.json`),{review_id:X.reviewId,iteration:$,findings:X.findings});let Z=await Promise.resolve().then(() => (x1(),U7)),q=0;if(process.env.LOKI_AUTO_LEARNINGS!=="0"){for(let U of X.findings)if(U.severity==="Critical"||U.severity==="High")await Z.appendFromGateFailure(z,$,U,{episodeBridge:null}),q+=1}return process.stdout.write(`reflect: persisted ${X.findings.length} findings + ${q} learnings (iter ${$})
|
|
435
435
|
`),0}catch(Q){return process.stderr.write(`reflect: ${Q.message}
|
|
436
|
-
`),1}}async function
|
|
437
|
-
`),2;let z=P();try{let Q=await Promise.resolve().then(() => (
|
|
438
|
-
`),0;let Z=(await Promise.resolve().then(() => (
|
|
439
|
-
`),0;let
|
|
436
|
+
`),1}}async function tK(K){let $=s1(K[0]);if($===null)return process.stderr.write(`override: missing or invalid <iter>
|
|
437
|
+
`),2;let z=P();try{let Q=await Promise.resolve().then(() => (G7(),J7)),X=Q.loadCounterEvidence(z,$);if(X===null||X.evidence.length===0)return process.stdout.write(`override: no counter-evidence for iter ${$} (skip)
|
|
438
|
+
`),0;let Z=(await Promise.resolve().then(() => (E1(),d1))).loadPreviousFindings(z,$),q=Z.findings.filter((_)=>_.severity==="Critical"||_.severity==="High");if(q.length===0)return process.stdout.write(`override: no blocking findings for iter ${$} (skip)
|
|
439
|
+
`),0;let U=new Set(["duplicate-code-path","file-exists","test-passes","grep-miss","out-of-scope"]),V=async(_)=>{let C=U.has(_.evidence.proofType);return{judge:_.judge,verdict:C?"APPROVE_OVERRIDE":"REJECT_OVERRIDE",reasoning:C?`[stub] proofType=${_.evidence.proofType} trusted`:`[stub] proofType=${_.evidence.proofType} requires manual review`}},G=await Q.runOverrideCouncil(q,X,V);await Q.recordOverrideOutcome(z,$,G,q);let B=U1(z,"quality","reviews");if(lK(B))try{let _=oK(B).filter((g)=>g.startsWith("review-")).sort(),C=_[_.length-1];if(C&&nK(U1(B,C)).isDirectory())W1(U1(B,C,`override-${$}.json`),{review_id:Z.reviewId,iteration:$,approved_finding_ids:Array.from(G.approvedFindingIds),rejected_finding_ids:Array.from(G.rejectedFindingIds),votes:G.votes})}catch{}let J=G.approvedFindingIds.size,Y=G.rejectedFindingIds.size;if(Y===0&&J>0)process.stdout.write(`override: LIFTED -- ${J} approved, ${Y} rejected
|
|
440
440
|
`);else process.stdout.write(`override: BLOCKED -- ${J} approved, ${Y} rejected
|
|
441
441
|
`);return 0}catch(Q){return process.stderr.write(`override: ${Q.message}
|
|
442
|
-
`),1}}async function
|
|
443
|
-
`),2;let X=P();try{let Z=(await Promise.resolve().then(() => (
|
|
442
|
+
`),1}}async function iK(K){let $=K[0],z=Number.parseInt(K[1]??"0",10),Q=s1(K[2]);if(!$||!Number.isFinite(z)||Q===null)return process.stderr.write(`handoff: usage: handoff <gate> <consecutive-failures> <iter>
|
|
443
|
+
`),2;let X=P();try{let Z=(await Promise.resolve().then(() => (O7(),Y7))).writeEscalationHandoff(X,{gateName:$,iteration:Q,consecutiveFailures:z,detail:`${$} hit PAUSE_LIMIT (${z} consecutive failures)`});return process.stdout.write(`handoff: wrote ${Z.path} (${Z.bytes}B)
|
|
444
444
|
`),0}catch(H){return process.stderr.write(`handoff: ${H.message}
|
|
445
|
-
`),1}}function
|
|
445
|
+
`),1}}function s1(K){if(K===void 0)return null;let $=Number.parseInt(K,10);return Number.isFinite($)&&$>=0?$:null}var a1=`loki internal phase1-hooks <subcommand>
|
|
446
446
|
|
|
447
447
|
Subcommands:
|
|
448
448
|
reflect <iter> Persist structured findings + auto-learnings.
|
|
@@ -451,8 +451,8 @@ Subcommands:
|
|
|
451
451
|
|
|
452
452
|
This command is invoked by autonomy/run.sh between iterations. Users
|
|
453
453
|
should not run it directly -- run \`loki start\` instead.
|
|
454
|
-
`,
|
|
455
|
-
`),0}p();n();y();import{readFileSync as
|
|
454
|
+
`,eK;var A7=L(()=>{y();I1();eK=a1});D1();function K0(){return process.stdout.write(`Loki Mode v${G1()}
|
|
455
|
+
`),0}p();n();y();import{readFileSync as h7,existsSync as b7}from"fs";import{resolve as y7}from"path";var v7=["claude","codex","cline","aider"];function z0(){let K=y7(P(),"state","provider");if(!b7(K))return"";try{return h7(K,"utf-8").trim()}catch{return""}}function g7(K,$){return K||$||process.env.LOKI_PROVIDER||"claude"}function m7(K){let $=z0(),z=g7(K,$);switch(process.stdout.write(`${k}Current Provider${W}
|
|
456
456
|
`),process.stdout.write(`
|
|
457
457
|
`),process.stdout.write(`${O}Provider:${W} ${z}
|
|
458
458
|
`),z){case"claude":process.stdout.write(`${D}Status:${W} Full features (subagents, parallel, MCP)
|
|
@@ -463,12 +463,12 @@ should not run it directly -- run \`loki start\` instead.
|
|
|
463
463
|
`);return process.stdout.write(`
|
|
464
464
|
`),process.stdout.write(`Switch provider: ${O}loki provider set <name>${W}
|
|
465
465
|
`),process.stdout.write(`Available: ${O}loki provider list${W}
|
|
466
|
-
`),0}async function
|
|
466
|
+
`),0}async function f7(){let $=z0()||process.env.LOKI_PROVIDER||"claude";process.stdout.write(`${k}Available Providers${W}
|
|
467
467
|
`),process.stdout.write(`
|
|
468
|
-
`);let z=await Promise.all(
|
|
468
|
+
`);let z=await Promise.all(v7.map(async(H)=>[H,await h(H)!==null])),Q=new Map;for(let[H,Z]of z)Q.set(H,Z?`${D}installed${W}`:`${R}not installed${W}`);let X=[["claude","claude - Claude Code (Anthropic) "],["codex","codex - Codex CLI (OpenAI) "],["cline","cline - Cline (multi-provider) "],["aider","aider - Aider (terminal pair prog) "]];for(let[H,Z]of X){let q=$===H?` ${O}(current)${W}`:"";process.stdout.write(` ${Z} ${Q.get(H)}${q}
|
|
469
469
|
`)}return process.stdout.write(`
|
|
470
470
|
`),process.stdout.write(`Set provider: ${O}loki provider set <name>${W}
|
|
471
|
-
`),0}function
|
|
471
|
+
`),0}function u7(){return process.stdout.write(`${k}Loki Mode Provider Management${W}
|
|
472
472
|
`),process.stdout.write(`
|
|
473
473
|
`),process.stdout.write(`Usage: loki provider <command>
|
|
474
474
|
`),process.stdout.write(`
|
|
@@ -486,8 +486,8 @@ should not run it directly -- run \`loki start\` instead.
|
|
|
486
486
|
`),process.stdout.write(` loki provider list
|
|
487
487
|
`),process.stdout.write(` loki provider info codex
|
|
488
488
|
`),process.stdout.write(` loki provider models
|
|
489
|
-
`),0}async function
|
|
490
|
-
`))if(Q.includes('"description"'))z++;return z}catch{return 0}}async function
|
|
489
|
+
`),0}async function Q0(K){let $=K[0]??"show",z=K.slice(1);switch($){case"show":case"current":return m7(z[0]);case"list":return f7();case"set":case"info":case"models":return p7(["provider",$,...z]);default:return u7()}}async function p7(K){let{run:$}=await Promise.resolve().then(() => (p(),$0)),{resolve:z}=await import("path"),{REPO_ROOT:Q}=await Promise.resolve().then(() => (y(),e1)),X=z(Q,"autonomy","loki"),H=await $([X,...K],{env:{LOKI_LEGACY_BASH:"1"},timeoutMs:3600000});return process.stdout.write(H.stdout),process.stderr.write(H.stderr),H.exitCode}n();y();Q1();p();import{existsSync as X0,readFileSync as l7}from"fs";import{resolve as t}from"path";import{mkdir as d7}from"fs/promises";var X1=t(k1(),"learnings");function h1(K){if(!X0(K))return 0;try{let $=l7(K,"utf-8"),z=0;for(let Q of $.split(`
|
|
490
|
+
`))if(Q.includes('"description"'))z++;return z}catch{return 0}}async function o7(){await d7(X1,{recursive:!0});let K=h1(t(X1,"patterns.jsonl")),$=h1(t(X1,"mistakes.jsonl")),z=h1(t(X1,"successes.jsonl"));return process.stdout.write(`${k}Cross-Project Learnings${W}
|
|
491
491
|
`),process.stdout.write(`
|
|
492
492
|
`),process.stdout.write(` Patterns: ${D}${K}${W}
|
|
493
493
|
`),process.stdout.write(` Mistakes: ${E}${$}${W}
|
|
@@ -496,7 +496,7 @@ should not run it directly -- run \`loki start\` instead.
|
|
|
496
496
|
`),process.stdout.write(`Location: ${X1}
|
|
497
497
|
`),process.stdout.write(`
|
|
498
498
|
`),process.stdout.write(`Use 'loki memory show <type>' to view entries
|
|
499
|
-
`),0}async function
|
|
499
|
+
`),0}async function n7(K){if(K){let Q=`
|
|
500
500
|
try:
|
|
501
501
|
from memory.layers import IndexLayer
|
|
502
502
|
layer = IndexLayer('.loki/memory')
|
|
@@ -506,9 +506,9 @@ except ImportError:
|
|
|
506
506
|
print('Error: memory.layers module not found')
|
|
507
507
|
except Exception as e:
|
|
508
508
|
print(f'Error: {e}')
|
|
509
|
-
`.trim(),X=await a(Q,{cwd:u});return process.stdout.write(X.stdout),0}let $=t(P(),"memory","index.json");if(!
|
|
509
|
+
`.trim(),X=await a(Q,{cwd:u});return process.stdout.write(X.stdout),0}let $=t(P(),"memory","index.json");if(!X0($))return process.stdout.write(`No index found
|
|
510
510
|
`),0;let z=await a(`import json, sys; sys.stdout.write(json.dumps(json.load(open(${JSON.stringify($)})), indent=4) + "\\n")`);if(z.exitCode!==0)return process.stdout.write(`No index found
|
|
511
|
-
`),0;return process.stdout.write(z.stdout),0}async function
|
|
511
|
+
`),0;return process.stdout.write(z.stdout),0}async function Z0(K){switch(K[0]??"list"){case"list":case"ls":return o7();case"index":return n7(K[1]==="rebuild");default:{let z=t(u,"autonomy","loki"),Q=await w([z,"memory",...K],{env:{LOKI_LEGACY_BASH:"1"},timeoutMs:3600000});return process.stdout.write(Q.stdout),process.stderr.write(Q.stderr),Q.exitCode}}}var j7=`Loki Mode (TypeScript port, Phase 2 of bash->Bun migration)
|
|
512
512
|
|
|
513
513
|
Usage: loki <command> [args...]
|
|
514
514
|
|
|
@@ -526,12 +526,12 @@ Phase 2 ported (Bun-native, fast):
|
|
|
526
526
|
|
|
527
527
|
All other commands fall through to the bash CLI (autonomy/loki).
|
|
528
528
|
Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
529
|
-
`;function
|
|
530
|
-
`)}async function
|
|
529
|
+
`;function K6(){let K=process.env.LOKI_LEGACY_BASH;if(K===void 0)return;let $=K.trim().toLowerCase();if($!=="1"&&$!=="true"&&$!=="yes"&&$!=="on")return;if(process.env.LOKI_SUPPRESS_BUN_DIRECT_WARN==="1")return;process.stderr.write(`warning: LOKI_LEGACY_BASH is set, but you are running the Bun runtime directly (src/cli.ts). The env var only takes effect via the bin/loki shim, which dispatches between Bun and bash. Behavior is unchanged; this message is informational.
|
|
530
|
+
`)}async function $6(K){K6();let $=K[0],z=K.slice(1);switch($){case void 0:case"help":case"--help":case"-h":return process.stdout.write(j7),0;case"version":case"--version":case"-v":return K0();case"provider":return Q0(z);case"memory":return Z0(z);case"status":{let{runStatus:Q}=await Promise.resolve().then(() => (B0(),G0));return Q(z)}case"stats":{let{runStats:Q}=await Promise.resolve().then(() => (A0(),T0));return Q(z)}case"doctor":{let{runDoctor:Q}=await Promise.resolve().then(() => (w0(),F0));return Q(z)}case"kpis":{let{runKpis:Q}=await Promise.resolve().then(() => (u0(),f0));return Q(z)}case"rollback":{let{runRollback:Q}=await Promise.resolve().then(() => (i0(),t0));return Q(z)}case"internal":{let Q=z[0];if(!Q||Q==="--help"||Q==="-h"||Q==="help"){let H=["loki internal -- runtime hooks driven by autonomy/run.sh","","Subcommands:"," phase1-hooks Persist structured findings, run override council,"," append learnings, and write the escalation handoff"," doc once per iteration. Driven by run.sh; not"," intended for direct invocation.","","Phase 1 (RARV-C closure) env vars:"," LOKI_INJECT_FINDINGS=1 Persist structured reviewer findings to"," .loki/state/findings-<iter>.json so the"," next iteration can address them."," LOKI_OVERRIDE_COUNCIL=1 Allow a 3-LLM override panel to lift a"," BLOCK when counter-evidence is presented."," See LOKI_OVERRIDE_JUDGES (csv),"," LOKI_OVERRIDE_PANEL_SIZE,"," LOKI_OVERRIDE_REAL_JUDGE."," LOKI_AUTO_LEARNINGS=1 Append failure rootcauses to"," .loki/state/relevant-learnings.json via"," the episodic memory bridge."," LOKI_HANDOFF_MD=1 Write a structured human handoff doc to"," .loki/escalations/<ts>.md before PAUSE.","","All four are default-on as of v7.5.3. Set to 0 to disable.","Reference: CHANGELOG.md (search 'Phase 1') and skills/healing.md.","","These commands are wired into the autonomous loop and may change","without notice. Do not script against them.",""].join(`
|
|
531
531
|
`);return process.stdout.write(`${H}
|
|
532
|
-
`),0}if(Q==="phase1-hooks"){let{runInternalPhase1Hooks:H}=await Promise.resolve().then(() => (
|
|
532
|
+
`),0}if(Q==="phase1-hooks"){let{runInternalPhase1Hooks:H}=await Promise.resolve().then(() => (A7(),T7));return H(z.slice(1))}return process.stderr.write(`Unknown internal subcommand: ${Q}
|
|
533
533
|
`),process.stderr.write(`Run 'loki internal --help' for the supported list.
|
|
534
534
|
`),2}default:return process.stderr.write(`Unknown command: ${$}
|
|
535
|
-
`),process.stderr.write(
|
|
535
|
+
`),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);
|
|
536
536
|
|
|
537
|
-
//# debugId=
|
|
537
|
+
//# debugId=F0683D8581DBAEE664756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.6.1",
|
|
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",
|
package/web-app/server.py
CHANGED
|
@@ -7720,36 +7720,15 @@ async def get_audit_log() -> JSONResponse:
|
|
|
7720
7720
|
|
|
7721
7721
|
# ---------------------------------------------------------------------------
|
|
7722
7722
|
# Static file serving (built React app)
|
|
7723
|
+
# IMPORTANT: serve_spa was previously defined here at line 7725, BEFORE the
|
|
7724
|
+
# /api/magic, /api/deploy, /api/sessions/.../github/actions, and
|
|
7725
|
+
# /api/sessions/.../docs route registrations. FastAPI registers routes in
|
|
7726
|
+
# definition order, so the catch-all '/{full_path:path}' silently swallowed
|
|
7727
|
+
# those 22 endpoints (they returned index.html instead of JSON). Moved to
|
|
7728
|
+
# the very end of the file just before standalone_app in v7.6.0 so all
|
|
7729
|
+
# specific API routes register first.
|
|
7723
7730
|
# ---------------------------------------------------------------------------
|
|
7724
7731
|
|
|
7725
|
-
@app.get("/{full_path:path}")
|
|
7726
|
-
async def serve_spa(full_path: str) -> FileResponse:
|
|
7727
|
-
"""Serve the React SPA and static assets from dist/.
|
|
7728
|
-
|
|
7729
|
-
The Vite build is configured with base: '/lab/' (Phase Merge-3), so the
|
|
7730
|
-
bundled HTML references assets at '/lab/assets/...'. Under the canonical
|
|
7731
|
-
standalone_app (and dashboard's Merge-4 mount), Starlette's Mount strips
|
|
7732
|
-
'/lab' from the scope path before dispatch, so this handler usually sees
|
|
7733
|
-
'assets/...' directly and the strip below is a no-op. The strip is
|
|
7734
|
-
retained as defense-in-depth for direct-`app` invocations (e.g. tests or
|
|
7735
|
-
operators running `uvicorn server:app` instead of `server:standalone_app`).
|
|
7736
|
-
"""
|
|
7737
|
-
index = DIST_DIR / "index.html"
|
|
7738
|
-
if not index.exists():
|
|
7739
|
-
return JSONResponse(
|
|
7740
|
-
status_code=503,
|
|
7741
|
-
content={"error": "Web app not built. Run: cd web-app && npm run build"},
|
|
7742
|
-
)
|
|
7743
|
-
# Resolve to the dist directory, tolerating the /lab/ base in standalone mode.
|
|
7744
|
-
relative = full_path[4:] if full_path.startswith("lab/") else full_path
|
|
7745
|
-
requested = DIST_DIR / relative
|
|
7746
|
-
if relative and requested.is_file() and str(requested.resolve()).startswith(str(DIST_DIR.resolve())):
|
|
7747
|
-
import mimetypes
|
|
7748
|
-
content_type = mimetypes.guess_type(str(requested))[0] or "application/octet-stream"
|
|
7749
|
-
return FileResponse(str(requested), media_type=content_type)
|
|
7750
|
-
# SPA fallback: return index.html for all non-file routes
|
|
7751
|
-
return FileResponse(str(index))
|
|
7752
|
-
|
|
7753
7732
|
|
|
7754
7733
|
# ---------------------------------------------------------------------------
|
|
7755
7734
|
# Magic Modules API (/api/magic/*)
|
|
@@ -8649,6 +8628,45 @@ async def docs_get_file(session_id: str, filename: str) -> Response:
|
|
|
8649
8628
|
return Response(content=content, media_type="text/markdown")
|
|
8650
8629
|
|
|
8651
8630
|
|
|
8631
|
+
# ---------------------------------------------------------------------------
|
|
8632
|
+
# SPA catch-all (MUST be last @app route -- see comment at the old serve_spa
|
|
8633
|
+
# location higher in this file). Any path that isn't matched by a specific
|
|
8634
|
+
# @app.get/post/... above falls through here and serves the React bundle.
|
|
8635
|
+
# v7.6.0 bug fix: previously this was at line 7725 and silently swallowed
|
|
8636
|
+
# 22 downstream API routes (/api/magic/*, /api/deploy/*, /api/sessions/.../
|
|
8637
|
+
# github/actions/*, /api/sessions/.../docs/*) which returned text/html
|
|
8638
|
+
# instead of JSON. Real-user test (Playwright on /lab/magic) surfaced it.
|
|
8639
|
+
# ---------------------------------------------------------------------------
|
|
8640
|
+
|
|
8641
|
+
@app.get("/{full_path:path}")
|
|
8642
|
+
async def serve_spa(full_path: str) -> FileResponse:
|
|
8643
|
+
"""Serve the React SPA and static assets from dist/.
|
|
8644
|
+
|
|
8645
|
+
The Vite build is configured with base: '/lab/' (Phase Merge-3), so the
|
|
8646
|
+
bundled HTML references assets at '/lab/assets/...'. Under the canonical
|
|
8647
|
+
standalone_app (and dashboard's Merge-4 mount), Starlette's Mount strips
|
|
8648
|
+
'/lab' from the scope path before dispatch, so this handler usually sees
|
|
8649
|
+
'assets/...' directly and the strip below is a no-op. The strip is
|
|
8650
|
+
retained as defense-in-depth for direct-`app` invocations (e.g. tests or
|
|
8651
|
+
operators running `uvicorn server:app` instead of `server:standalone_app`).
|
|
8652
|
+
"""
|
|
8653
|
+
index = DIST_DIR / "index.html"
|
|
8654
|
+
if not index.exists():
|
|
8655
|
+
return JSONResponse(
|
|
8656
|
+
status_code=503,
|
|
8657
|
+
content={"error": "Web app not built. Run: cd web-app && npm run build"},
|
|
8658
|
+
)
|
|
8659
|
+
# Resolve to the dist directory, tolerating the /lab/ base in standalone mode.
|
|
8660
|
+
relative = full_path[4:] if full_path.startswith("lab/") else full_path
|
|
8661
|
+
requested = DIST_DIR / relative
|
|
8662
|
+
if relative and requested.is_file() and str(requested.resolve()).startswith(str(DIST_DIR.resolve())):
|
|
8663
|
+
import mimetypes
|
|
8664
|
+
content_type = mimetypes.guess_type(str(requested))[0] or "application/octet-stream"
|
|
8665
|
+
return FileResponse(str(requested), media_type=content_type)
|
|
8666
|
+
# SPA fallback: return index.html for all non-file routes
|
|
8667
|
+
return FileResponse(str(index))
|
|
8668
|
+
|
|
8669
|
+
|
|
8652
8670
|
# ---------------------------------------------------------------------------
|
|
8653
8671
|
# Standalone wrapper (Phase Merge-3): mounts the FastAPI `app` under '/lab/'
|
|
8654
8672
|
# so the rebased Vite bundle (with base: '/lab/') routes correctly through
|