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 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.5.31
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.5.31 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
384
+ **v7.6.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.5.31
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
- python3 -c "
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
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.5.31"
10
+ __version__ = "7.6.1"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -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
- total_conf = sum(e.get("data", {}).get("confidence", 0) for e in events)
2867
- total_conf += sum(s.get("confidence", 0) for s in all_signals)
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>
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v7.5.31
5
+ **Version:** v7.6.1
6
6
 
7
7
  ---
8
8
 
@@ -1,10 +1,10 @@
1
1
  // @bun
2
- var j7=Object.defineProperty;var _7=(K)=>K;function I7(K,$){this[K]=_7.bind(null,$)}var b=(K,$)=>{for(var z in $)j7(K,z,{get:$[z],enumerable:!0,configurable:!0,set:I7.bind($,z)})};var L=(K,$)=>()=>(K&&($=K(K=0)),$);var U1=import.meta.require;var t1={};b(t1,{lokiDir:()=>P,homeLokiDir:()=>N1,findRepoRootForVersion:()=>S1,REPO_ROOT:()=>u});import{resolve as f,dirname as w1}from"path";import{fileURLToPath as P7}from"url";import{existsSync as J1}from"fs";import{homedir as L7}from"os";function R7(){let K=r1;for(let $=0;$<6;$++){if(J1(f(K,"VERSION"))&&J1(f(K,"autonomy/run.sh")))return K;let z=w1(K);if(z===K)break;K=z}return f(r1,"..","..","..")}function S1(K){let $=K;for(let z=0;z<6;z++){if(J1(f($,"VERSION"))&&J1(f($,"autonomy/run.sh")))return $;let Q=w1($);if(Q===$)break;$=Q}return f(K,"..","..","..")}function P(){return process.env.LOKI_DIR??f(process.cwd(),".loki")}function N1(){return f(L7(),".loki")}var r1,u;var y=L(()=>{r1=w1(P7(import.meta.url));u=R7()});var K0={};b(K0,{runOrThrow:()=>S7,run:()=>w,commandVersion:()=>k7,commandExists:()=>h,ShellError:()=>k1});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,V]=await Promise.all([new Response(z.stdout).text(),new Response(z.stderr).text(),z.exited]);return{stdout:H,stderr:Z,exitCode:V}}finally{if(Q)clearTimeout(Q);if(X)clearTimeout(X)}}async function S7(K,$={}){let z=await w(K,$);if(z.exitCode!==0)throw new k1(`command failed (${z.exitCode}): ${K.join(" ")}`,z.exitCode,z.stdout,z.stderr);return z}async function h(K){let $=N7(K),z=await w(["sh","-c",`command -v ${$}`],{timeoutMs:5000});if(z.exitCode===0)return z.stdout.trim()||null;return null}function N7(K){if(!/^[A-Za-z0-9._/-]+$/.test(K))throw Error(`refused to shell-escape suspect token: ${K}`);return K}async function k7(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 k1;var p=L(()=>{k1=class k1 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 D7?"":K}var D7,R,D,E,Y6,O,k,x,W;var n=L(()=>{D7=(process.env.NO_COLOR??"").length>0;R=c("\x1B[0;31m"),D=c("\x1B[0;32m"),E=c("\x1B[1;33m"),Y6=c("\x1B[0;34m"),O=c("\x1B[0;36m"),k=c("\x1B[1m"),x=c("\x1B[2m"),W=c("\x1B[0m")});import{existsSync as p7}from"fs";async function r(){if(z1!==void 0)return z1;let K="/opt/homebrew/bin/python3.12";if(p7(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 J0={};b(J0,{runStatus:()=>$5});import{existsSync as S,readFileSync as Z1,readdirSync as Z0,statSync as H0}from"fs";import{resolve as F,basename as n7}from"path";async function s7(){if(await h("jq"))return!0;return process.stdout.write(`${R}Error: jq is required but not installed.${W}
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 G1(K){if(!Number.isFinite(K)||K<=0)return!1;try{return process.kill(K,0),!0}catch{return!1}}function B1(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 r7(K){let $=[],z=B1(F(K,"loki.pid"));if(z!==null&&G1(z))$.push(`global:${z}`);let Q=F(K,"sessions");if(S(Q)){let X=[];try{X=Z0(Q)}catch{X=[]}for(let H of X){let Z=F(Q,H);try{if(!H0(Z).isDirectory())continue}catch{continue}let V=F(Z,"loki.pid"),q=B1(V);if(q!==null&&G1(q))$.push(`${H}:${q}`)}}if(S(K)){let X=[];try{X=Z0(K)}catch{X=[]}for(let H of X){if(!H.startsWith("run-")||!H.endsWith(".pid"))continue;let Z=F(K,H);try{if(!H0(Z).isFile())continue}catch{continue}let V=n7(H,".pid").slice(4),q=B1(Z);if(q!==null&&G1(q)){if(!$.some((G)=>G.startsWith(`${V}:`)))$.push(`${V}:${q}`)}}}return $}async function W0(K,$){let z=await w(["jq","-r",K,$]);if(z.exitCode!==0)return null;return z.stdout.trim()}function V0(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 q0(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 t7(){let K=P();if(!await s7())return 1;if(!S(K))return process.stdout.write(`${k}Loki Mode Status${W}
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=r7(K);if(H.length>0){process.stdout.write(`${D}Active Sessions: ${H.length}${W}
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 V=F(K,"state","orchestrator.json");if(S(V)){process.stdout.write(`${O}Orchestrator State:${W}
38
- `);let J=await W0('.currentPhase // "unknown"',V);process.stdout.write(`${J??"unknown"}
39
- `)}let q=F(K,"queue","pending.json");if(S(q)){let J=await W0('if type == "array" then length elif .tasks then .tasks | length else 0 end',q);process.stdout.write(`${O}Pending Tasks:${W} ${J??"0"}
40
- `)}let U=F(K,"metrics","budget.json");if(S(U)){let J=V0(U,"budget_limit"),Y=V0(U,"budget_used");if(J!=="0")process.stdout.write(`${O}Budget:${W} $${Y} / $${J}
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=q0(G,"window_size",200000),Y=q0(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=B1(B);if(J!==null&&G1(J)){let Y=process.env.LOKI_DASHBOARD_PORT||"57374";process.stdout.write(`${O}Dashboard:${W} http://127.0.0.1:${Y}/
44
- `)}}return await i7(K),process.stdout.write(`
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 i7(K){let $=F(K,"state"),z=e7($),Q=F($,"relevant-learnings.json"),X=F(K,"escalations"),H=z.length>0,Z=S(Q),V=S(X);if(!H&&!Z&&!V)return;if(process.stdout.write(`
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 q=z[z.length-1],U=U0(q);if(U&&Array.isArray(U.findings)){let G={Critical:0,High:0,Medium:0,Low:0};for(let J of U.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 ${U.iteration??"?"}): ${B||"none"} -- ${U.findings.length} total
50
- `)}}if(Z){let q=U0(Q);if(q&&Array.isArray(q.learnings)&&q.learnings.length>0){let U=new Map;for(let B of q.learnings){let J=String(B.trigger??"unknown");U.set(J,(U.get(J)??0)+1)}let G=[...U.entries()].sort((B,J)=>J[1]-B[1]).slice(0,3).map(([B,J])=>`${J} ${B}`).join(", ");process.stdout.write(` Learnings: ${q.learnings.length} total (${G})
51
- `)}}if(V){let q=0,U="";try{let B=(await import("fs")).readdirSync(X).filter((J)=>J.endsWith(".md"));if(q=B.length,B.length>0)B.sort(),U=B[B.length-1]??""}catch{}if(q>0)process.stdout.write(` Escalations: ${q} handoff doc${q===1?"":"s"} (latest: ${U})
52
- `)}}function e7(K){if(!S(K))return[];try{return U1("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 U0(K){try{let $=U1("fs");return JSON.parse($.readFileSync(K,"utf-8"))}catch{return null}}async function K5(){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",a7,$,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 $5(K){let $=[...K];while($.length>0){let z=$[0];if(z==="--json")return K5();if(z==="--help"||z==="-h")return process.stdout.write(`Usage: loki status [--json]
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 t7()}var a7=`
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 G0=L(()=>{p();Q1();n();y()});var O0={};b(O0,{runStats:()=>W5,computeStats:()=>Y0});import{readdirSync as B0,readFileSync as z5,statSync as M0}from"fs";import{join as l}from"path";function i(K){try{if(!M0(K).isFile())return null;return JSON.parse(z5(K,"utf-8"))}catch{return null}}function b1(K){try{return M0(K).isDirectory()}catch{return!1}}function Q5(K){if(!b1(K))return[];try{let $=B0(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 C1(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 h1(K,$){return K.length>=$?K:K+" ".repeat($-K.length)}function X5(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=Q5(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 V=Z.reduce((T,I)=>T+(I.input_tokens??0),0),q=Z.reduce((T,I)=>T+(I.output_tokens??0),0),U=V+q,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,a1=0,F1=l(K,"quality");if(b1(F1)){let T=[];try{T=B0(F1)}catch{T=[]}for(let I of T){if(!I.endsWith(".json")||I==="gate-failure-count.json")continue;let v=i(l(F1,I));if(!v||typeof v!=="object")continue;if(!(("verdict"in v)||("approved"in v)||("reviewers"in v)))continue;N+=1;let s1=(v.verdict??"").toString().toLowerCase();if(v.approved===!0||["approved","approve","pass"].includes(s1))$1+=1;else if(["revision","revise","changes_requested","reject"].includes(s1))a1+=1}}return{phase:$,iterationCount:z,iterations:Z,totalInput:V,totalOutput:q,totalTokens:U,totalCost:G,totalDuration:B,budgetLimit:J,budgetUsed:Y,gatesPassed:_,gatesTotal:C,gateFailures:m,reviewsTotal:N,reviewsApproved:$1,reviewsRevision:a1}}function Z5(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,V)=>({number:V+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,V){if(!V)return;let q=new RegExp(`("${Z}": )(-?\\d+)(,?)$`,"m");X=X.replace(q,(U,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,V,q,U)=>`${V}${q}.0${U}`);return X}function H5(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: ${C1(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: ${C1(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=h1(e(Q.input_tokens??0),10),V=h1(e(Q.output_tokens??0),10),q=Q.cost_usd??0,U=C1(Q.duration_seconds??0),G=h1(`${H}`,3);z.push(` #${G} input: ${Z} output: ${V} cost: $${K1(q,2)} time: ${U}`)});return z.join(`
265
- `)}function Y0(K){let $=!1,z=!1;for(let Z of K)if(Z==="--json")$=!0;else if(Z==="--efficiency")z=!0;let Q=P();if(!b1(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=X5(Q);return{exitCode:0,stdout:$?Z5(X,z):H5(X,z)}}async function W5(K){let $=Y0(K);return console.log($.stdout),$.exitCode}var T0=L(()=>{y();n()});var x0={};b(x0,{runDoctor:()=>_5,pythonImportOk:()=>g1,httpReachable:()=>y1,checkTool:()=>P0,checkSkills:()=>L0,checkDisk:()=>v1,buildDoctorJson:()=>E0,_setPythonImportOkForTest:()=>M5});import{existsSync as V5,lstatSync as q5,readlinkSync as U5,statfsSync as J5}from"fs";import{homedir as j0}from"os";import{resolve as A0}from"path";function B5(K){let $=K.match(G5);return $?$[1]:null}async function _0(K){try{let $=await w([K,"--version"],{timeoutMs:5000}),z=($.stdout||$.stderr||"").trim();return B5(z)}catch{return null}}function I0(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 P0(K,$,z,Q=null){let X=await h($),H=X!==null,Z=H?await _0($):null,V="pass";if(!H)V=z==="required"?"fail":"warn";else if(Q&&Z){if(I0(Z,Q)<0)V=z==="required"?"fail":"warn"}return{name:K,command:$,found:H,version:Z,required:z,min_version:Q,status:V,path:X}}function v1(){let K=null;try{let z=J5(j0()),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 y1(K,$=2000){try{return(await fetch(K,{signal:AbortSignal.timeout($)})).ok}catch{return!1}}async function g1(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 M5(K){O1.fn=K??g1}function L0(){let K=j0();return Y5.map(({name:$,dir:z})=>{let Q=A0(K,z),X=Q,H=A0(Q,"SKILL.md");if(V5(H))return{name:$,path:X,status:"pass",detail:""};try{if(q5(Q).isSymbolicLink()){let V="unknown";try{V=U5(Q)}catch{}return{name:$,path:X,status:"fail",detail:`(broken symlink -> ${V})`}}}catch{}return{name:$,path:X,status:"warn",detail:"(not found - run 'loki setup-skill')"}})}async function R0(){return Promise.all(O5.map(async(K)=>{return{...await P0(K.jsonName,K.cmd,K.required,K.min??null),displayName:K.displayName}}))}async function T5(){let $=await h("sentrux")!==null,z=$?await _0("sentrux"):null;return{found:$,version:z,status:$?"pass":"warn",required:"optional"}}async function E0(){let $=(await R0()).map(({displayName:V,...q})=>q),z=v1(),Q=await T5(),X=0,H=0,Z=0;for(let V of $)if(V.status==="pass")X++;else if(V.status==="fail")H++;else Z++;if(z.status==="pass")X++;else if(z.status==="fail")H++;else Z++;return{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 M1(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&&I0(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 Y1(K,$){if($==="pass")K.pass++;else if($==="fail")K.fail++;else K.warn++}function A5(){process.stdout.write(`${k}loki doctor${W} - Check system prerequisites
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 j5(){process.stdout.write(`${k}Loki Mode Doctor${W}
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 R0(),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(M1(N)+`
281
- `),Y1(K,N.status)}process.stdout.write(`
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(M1(N)+`
284
- `),Y1(K,N.status),N.found)X=!0}if(!X)process.stdout.write(` ${M("fail")} No AI provider CLI installed -- at least one is required
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,V=process.env;if(V.ANTHROPIC_API_KEY)process.stdout.write(` ${M("pass")} ANTHROPIC_API_KEY is set
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(V.OPENAI_API_KEY)process.stdout.write(` ${M("pass")} OPENAI_API_KEY is set
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(V.ANTHROPIC_BASE_URL){let A=V.ANTHROPIC_BASE_URL;if(process.stdout.write(` ${M("pass")} ANTHROPIC_BASE_URL: ${A}
293
- `),K.pass++,!V.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: ${V.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 L0())if(A.status==="pass")process.stdout.write(` ${M("pass")} ${A.name} ${x}${A.path}${W}
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[q,U,G]=await Promise.all([O1.fn("mcp"),O1.fn("numpy",!0),O1.fn("sentence_transformers",!0)]);if(q)process.stdout.write(` ${M("pass")} MCP SDK (Python)
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(U)process.stdout.write(` ${M("pass")} numpy (vector search)
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 y1("http://localhost:8100/api/v2/heartbeat"))process.stdout.write(` ${M("pass")} ChromaDB server (port 8100)
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 y1(`${B}/health`))process.stdout.write(` ${M("pass")} MiroFish server (${B})
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(M1(J)+`
320
- `),Y1(K,J.status);let Y=z.get("bun");if(Y)process.stdout.write(M1(Y)+`
321
- `),Y1(K,Y.status);let j=v1(),_=j.available_gb===null?null:Math.floor(j.available_gb);if(_===null)process.stdout.write(` ${M("warn")} Disk space: unable to determine
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 _5(K){let $=!1;for(let z of K)if(z==="--json")$=!0;else if(z==="--help"||z==="-h")return A5(),0;else return process.stderr.write(`${R}Unknown option: ${z}${W}
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 E0();return process.stdout.write(JSON.stringify(z,null,2)+`
345
- `),0}return j5()}var G5,O1,Y5,O5;var F0=L(()=>{p();Q1();n();G5=/(\d+\.\d+(?:\.\d+)*)/;O1={fn:g1};Y5=[{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"}];O5=[{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 N0,mkdirSync as t6,readdirSync as I5,readFileSync as k0,renameSync as i6,writeFileSync as e6}from"fs";import{dirname as P5,join as L5,resolve as R5}from"path";import{fileURLToPath as E5}from"url";function x5(){try{let K=P5(E5(import.meta.url)),$=R5(K,"..","..","data","model-pricing.json");if(!N0($))return H1;let Q=JSON.parse(k0($,"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 F5(K){return Math.round((K+Number.EPSILON)*1e4)/1e4}function w5(K){let $=(K??S0).toLowerCase();return w0[$]??w0[S0]}function D0(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=w5(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 F5($)}function C0(K){if(!N0(K))return[];let $=[],z;try{z=I5(K)}catch{return[]}for(let Q of z){if(!Q.endsWith(".json"))continue;let X=L5(K,Q);try{let H=k0(X,"utf8"),Z=JSON.parse(H);if(Z&&typeof Z==="object")$.push(Z)}catch{}}return $}var H1,w0,S0="sonnet";var h0=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}};w0=Object.freeze(x5())});import{existsSync as T1,readdirSync as S5,readFileSync as N5,statSync as k5}from"fs";import{join as A1}from"path";function D5(K){let $=[],z=A1(K,"votes");if(!T1(z))return $;let Q;try{Q=S5(z)}catch{return $}for(let X of Q){if(!X.startsWith("round-")||!X.endsWith(".json"))continue;try{let H=A1(z,X);if(!k5(H).isFile())continue;let Z=JSON.parse(N5(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 C5(){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 h5(){return{council_rounds:0,unanimous_rate:null,approval_rate:null,iteration_success_rate:null}}function b5(K){let $=C5();if(K.length===0)return $;$.iteration_count=K.length,$.total_cost_usd=Math.round(D0(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 y5(K,$,z){let Q=h5();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 b0(K){let $=[],z=A1(K,"metrics","efficiency"),Q=A1(K,"council"),X=T1(z)?C0(z):[];if(!T1(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=D5(Q);if(!T1(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=b5(X),V=Z.status_breakdown.success??0,q=y5(H,V,Z.iteration_count);return{schema_version:1,generated_at:new Date().toISOString(),loki_dir:K,efficiency:Z,accuracy:q,notes:$}}function y0(K){return JSON.stringify(K,null,2)}function v0(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 g0=L(()=>{h0()});var m0={};b(m0,{runKpis:()=>g5});function g5(K){let $=!1;for(let Q of K){if(Q==="--help"||Q==="-h"||Q==="help")return process.stdout.write(v5),0;if(Q==="--json"){$=!0;continue}return process.stderr.write(`loki kpis: unknown arg: ${Q}
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=b0(P());return process.stdout.write($?y0(z)+`
349
- `:v0(z)+`
350
- `),0}var v5=`loki kpis -- accuracy + efficiency KPI snapshot (v7.5.28 MVP)
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 f0=L(()=>{g0();y()});import{closeSync as J8,fstatSync as G8,lstatSync as B8,mkdirSync as m5,openSync as M8,readSync as Y8,renameSync as f5,rmSync as O8,statSync as T8,unlinkSync as A8,writeFileSync as u5,writeSync as j8}from"fs";import{dirname as p5}from"path";function W1(K,$){m5(p5(K),{recursive:!0});let z=`${K}.tmp.${process.pid}.${++c5}`;u5(z,`${JSON.stringify($,null,2)}
371
- `),f5(z,K)}async function u0(K,$){let z=j1.get(K)??Promise.resolve(),Q=()=>{},X=new Promise((Z)=>{Q=Z}),H=z.catch(()=>{}).then(()=>X);j1.set(K,H);try{return await z.catch(()=>{}),await $()}finally{if(Q(),j1.get(K)===H)j1.delete(K)}}var c5=0,j1;var _1=L(()=>{j1=new Map});import{existsSync as I1,mkdirSync as l5,copyFileSync as d5,readFileSync as o5,readdirSync as n5,statSync as a5,writeFileSync as R8,renameSync as s5,appendFileSync as E8,rmSync as x8}from"fs";import{join as s,dirname as r5}from"path";function P1(K){return s(K,"state","checkpoints")}function i5(K){let $=P1(K);if(!I1($))return[];return n5($).filter((z)=>z.startsWith("cp-")).filter((z)=>{try{return a5(s($,z)).isDirectory()}catch{return!1}})}function e5(K){return[...K].sort(($,z)=>{let Q=p0($),X=p0(z);return Q-X})}function p0(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 f1(K){let $=K??P(),z=e5(i5($)),Q=[];for(let X of z){let H=c0($,X);if(H)Q.push(H)}return Q}function c0(K,$){let z=s(P1(K),$,"metadata.json");if(!I1(z))return null;try{let Q=JSON.parse(o5(z,"utf-8"));return KK(Q,z)}catch{return null}}function KK(K,$){let z=QK(K,$);return z.ok?z.value:null}function QK(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 zK){let H=z[X];if($K.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 u1(K,$){if(!XK.test(K))throw new l0(K);let z=$??P(),Q=s(P1(z),K);if(!I1(Q))throw new m1(K);let X=c0(z,K);if(!X)throw new m1(K);return X}function d0(K,$){let z=u1(K,$),Q=$??P(),X=s(P1(Q),K),H=[];for(let Z of ZK){let V=s(X,Z);if(!I1(V))continue;H.push({from:V,to:s(Q,Z)})}return{id:K,metadata:z,restore:H}}function o0(K){let $=[],z=0;for(let Q of K.restore)try{l5(r5(Q.to),{recursive:!0});let X=`${Q.to}.tmp.${process.pid}.${++t5}`;d5(Q.from,X),s5(X,Q.to),z+=1}catch(X){$.push(`${Q.from} -> ${Q.to}: ${X.message}`)}return{restored:z,errors:$}}var k8,t5=0,$K,zK,XK,m1,l0,ZK;var n0=L(()=>{y();p();_1();k8=Promise.resolve();$K=/[\x00-\x08\x0a-\x1f\x7f-\x9f]/,zK=["id","task_id","git_sha","git_branch","provider","phase"];XK=/^[a-zA-Z0-9_-]+$/;m1=class m1 extends Error{id;constructor(K){super(`Checkpoint not found: ${K}`);this.id=K;this.name="CheckpointNotFoundError"}};l0=class l0 extends Error{id;constructor(K){super(`Invalid checkpoint ID: must be alphanumeric, hyphens, underscores only (got: ${K})`);this.id=K;this.name="InvalidCheckpointIdError"}};ZK=["state/orchestrator.json","queue/pending.json","queue/completed.json","queue/in-progress.json","queue/current-task.json"]});var r0={};b(r0,{runRollback:()=>HK});async function HK(K){let $=K[0],z=K.slice(1);if($===void 0||$==="help"||$==="--help"||$==="-h")return process.stdout.write(a0),$===void 0?1:0;switch($){case"list":{let Q=[...f1()].reverse();if(Q.length===0)return process.stdout.write(`${E}No checkpoints found.${W}
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=u1(Q);return process.stdout.write(`${JSON.stringify(X,null,2)}
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 s0(Q)}case"latest":{let Q=f1(),X=Q[Q.length-1];if(!X)return process.stderr.write(`${R}No checkpoints found to roll back to.${W}
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
- `),s0(X.id)}default:return process.stderr.write(`Unknown subcommand: ${$}
381
- `),process.stderr.write(a0),2}}function s0(K){let $;try{$=d0(K)}catch(Q){return process.stderr.write(`${R}Cannot plan rollback:${W} ${Q.message}
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=o0($);if(z.errors.length>0){for(let Q of z.errors)process.stderr.write(`${R}restore error:${W} ${Q}
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 a0=`Usage: loki rollback <subcommand>
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 t0=L(()=>{n0();n()});var c1={};b(c1,{renderFindingsForPrompt:()=>JK,loadPreviousFindings:()=>p1,findLatestReviewDir:()=>z7,_parseReviewerOutputForTests:()=>GK});import{existsSync as e0,readFileSync as i0,readdirSync as K7,statSync as WK}from"fs";import{join as L1}from"path";function UK(K){let $=K.toLowerCase();if($==="critical")return"Critical";if($==="high")return"High";if($==="medium")return"Medium";return"Low"}function $7(K,$,z,Q){let X=[],H=K.split(/\r?\n/);for(let Z of H){let V=Z.trim();if(V.length===0)continue;let q=V.replace(/^[-*]\s*/,""),U=VK.exec(q);if(!U||!U[1]||!U[2])continue;let G=UK(U[1]),B=U[2].trim(),J=qK.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:V})}return X}function z7(K,$){let z=L1(K,"quality","reviews");if(!e0(z))return null;let Q;try{Q=K7(z)}catch{return null}let X=$===void 0?Q.filter((V)=>V.startsWith("review-")):Q.filter((V)=>V.endsWith(`-${$}`)&&V.startsWith("review-"));if(X.length===0)return null;X.sort();let H=X[X.length-1];if(!H)return null;let Z=L1(z,H);try{if(!WK(Z).isDirectory())return null}catch{return null}return Z}function p1(K,$){let z=z7(K,$);if(z===null)return{reviewDir:null,reviewId:null,iteration:null,findings:[]};let Q=null,X=null,H=L1(z,"aggregate.json");if(e0(H))try{let U=i0(H,"utf-8"),G=JSON.parse(U);if(typeof G.review_id==="string")Q=G.review_id;if(typeof G.iteration==="number")X=G.iteration}catch{}let Z;try{Z=K7(z)}catch{return{reviewDir:z,reviewId:Q,iteration:X,findings:[]}}let V=new Set(["diff.txt","files.txt","anti-sycophancy.txt"]),q=[];for(let U of Z){if(!U.endsWith(".txt"))continue;if(V.has(U))continue;if(U.endsWith("-prompt.txt"))continue;let G=U.replace(/\.txt$/,""),B;try{B=i0(L1(z,U),"utf-8")}catch{continue}q.push(...$7(B,G,Q??"",X??-1))}return{reviewDir:z,reviewId:Q,iteration:X,findings:q}}function JK(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 V=Z.file?` (${Z.file}${Z.line!==null?":"+Z.line:""})`:"";Q.push(` - ${Z.description}${V} -- via ${Z.reviewer}`)}}return Q.join(`
402
- `)}function GK(K,$,z="review-test",Q=0){return $7(K,$,z,Q)}var VK,qK;var R1=L(()=>{VK=/\[(Critical|High|Medium|Low)\]\s*(.+)/i,qK=/([\w.\-/]+\.[a-zA-Z]+):(\d+)/});import{existsSync as BK}from"fs";import{join as MK}from"path";async function Q7(K,$){let z=MK(K,"memory");if(!BK(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(`
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 V=Z.stdout.trim();if(V==="OK")return{stored:!0,reason:"stored"};if(V.startsWith("ERR:"))return{stored:!1,reason:V.replace(/^ERR:/,"")};return{stored:!1,reason:Z.stderr.trim()||"unknown"}}var X7=L(()=>{Q1();y()});var V7={};b(V7,{loadLearnings:()=>l1,appendLearning:()=>V1,appendFromGateFailure:()=>PK});import{existsSync as YK,readFileSync as OK}from"fs";import{join as Z7}from"path";import{createHash as TK}from"crypto";function H7(K){return Z7(K,AK)}function jK(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 W7(K){if(!YK(K))return{version:1,learnings:[]};try{let $=OK(K,"utf-8"),z=JSON.parse($);if(z.version===1&&Array.isArray(z.learnings))return{version:1,learnings:z.learnings.filter(jK)}}catch{}return{version:1,learnings:[]}}function _K(K,$){return TK("sha256").update(`${K}\x00${$}`).digest("hex").slice(0,16)}async function V1(K,$,z={}){let Q=_K($.trigger,$.rootCause),X=new Date().toISOString(),H={id:Q,timestamp:X,...$},Z=H7(K);if(await u0(Z,()=>{let q=W7(Z),U=q.learnings.findIndex((G)=>G.id===Q);if(U>=0){let G=q.learnings[U];q.learnings[U]={...G,timestamp:X,iteration:H.iteration}}else q.learnings.push(H);W1(Z,q)}),z.episodeBridge!==null&&(z.episodeBridge!==void 0||process.env.LOKI_AUTO_LEARNINGS_EPISODE==="1")){let q=z.episodeBridge??Q7,U=z.bridgeFailureLog??IK;try{let G=await q(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))U(`episode_bridge skipped: ${G.reason}`)}}catch(G){U(`episode_bridge threw: ${G.message}`)}}return H}function IK(K){process.stderr.write(`[learnings_writer] ${K}
430
- `)}async function PK(K,$,z,Q={}){let X=`[${z.severity}] ${z.description}`;return V1(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 l1(K){return W7(H7(K))}var AK;var E1=L(()=>{_1();X7();AK=Z7("state","relevant-learnings.json")});var U7={};b(U7,{runOverrideCouncil:()=>wK,recordOverrideOutcome:()=>SK,loadCounterEvidence:()=>FK,canonicalFindingId:()=>d1,DEFAULT_OVERRIDE_JUDGES:()=>q7});import{existsSync as LK,readFileSync as RK}from"fs";import{join as EK}from"path";function FK(K,$){let z=EK(K,"state",`counter-evidence-${$}.json`);if(!LK(z))return null;try{let Q=RK(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 V of H){if(typeof V!=="object"||V===null)continue;let q=V;if(typeof q.findingId!=="string")continue;if(typeof q.claim!=="string")continue;let U=q.proofType;if(typeof U!=="string"||!xK.has(U))continue;let G=U,B=Array.isArray(q.artifacts)?q.artifacts:[];Z.push({findingId:q.findingId,claim:q.claim,proofType:G,artifacts:B.filter((J)=>typeof J==="string")})}return{iteration:X.iteration,evidence:Z}}catch{return null}}async function wK(K,$,z,Q={}){let X=Q.judges??q7,H=new Set,Z=new Set,V={},q=new Map;for(let U of $.evidence)q.set(U.findingId,U);for(let U of K){let G=d1(U),B=q.get(G);if(!B){Z.add(G);continue}let J=await Promise.all(X.map((j)=>z({finding:U,evidence:B,judge:j})));if(V[G]=J,J.filter((j)=>j.verdict==="APPROVE_OVERRIDE").length>=2)H.add(G);else Z.add(G)}return{approvedFindingIds:H,rejectedFindingIds:Z,votes:V}}function d1(K){let $=K.raw.slice(0,80).replace(/\s+/g," ").trim();return`${K.reviewer}::${$}`}async function SK(K,$,z,Q,X={}){let H={episodeBridge:X.episodeBridge===void 0?null:X.episodeBridge};for(let Z of Q){let V=d1(Z);if(z.approvedFindingIds.has(V))await V1(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:V,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(V))await V1(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:V,reviewId:Z.reviewId,file:Z.file??void 0,line:Z.line??void 0,severity:Z.severity,reviewer:Z.reviewer}},H)}}var xK,q7;var J7=L(()=>{E1();xK=new Set(["file-exists","test-passes","grep-miss","reviewer-misread","duplicate-code-path","out-of-scope"]);q7=["judge-primary","judge-secondary","judge-tertiary"]});var M7={};b(M7,{writeEscalationHandoff:()=>uK,renderHandoff:()=>G7,readLatestHandoff:()=>pK});import{existsSync as NK,mkdirSync as kK,readdirSync as DK,readFileSync as CK,renameSync as hK,writeFileSync as bK}from"fs";import{dirname as yK,join as x1}from"path";function vK(){return new Date().toISOString()}function gK(K){let $=K.file?` (${K.file}${K.line!==null?":"+K.line:""})`:"";return` - [${K.severity}] ${K.description}${$} -- ${K.reviewer}`}function mK(K){let $=K.evidence,z=$.file?` ${$.file}${$.line!==void 0?":"+$.line:""}`:"";return` - **${K.trigger}** (iter ${K.iteration})${z}: ${K.rootCause}`}function G7(K,$,z){let Q=[];if(Q.push(`# Loki escalation handoff -- ${vK()}`),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(gK(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(mK(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 fK(K,$){kK(yK(K),{recursive:!0});let z=`${K}.tmp.${process.pid}.${++B7}`;bK(z,$),hK(z,K)}function uK(K,$,z={}){let Q=z.findings??p1(K,$.iteration).findings,X=z.learnings??l1(K).learnings,H=G7($,Q,X),Z=(z.now?.()??new Date).toISOString().replace(/[-:.]/g,""),V=x1(K,"escalations"),q=++B7,U=x1(V,`handoff-${Z}-${process.pid}-${q}-${$.gateName}.md`);return fK(U,H),{path:U,bytes:H.length}}function pK(K){let $=x1(K,"escalations");if(!NK($))return null;let z;try{z=DK($).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=x1($,Q);try{return{path:X,body:CK(X,"utf-8")}}catch{return null}}var B7=0;var Y7=L(()=>{R1();E1()});var O7={};b(O7,{runInternalPhase1Hooks:()=>aK,_resolveForTests:()=>nK,_internalPhase1HooksHelp:()=>iK});import{existsSync as cK,mkdirSync as lK,readdirSync as dK,statSync as oK}from"fs";import{join as q1,resolve as nK}from"path";async function aK(K){let[$,...z]=K;switch($){case void 0:case"help":case"--help":case"-h":return process.stdout.write(o1),$===void 0?1:0;case"reflect":return sK(z);case"override":return rK(z);case"handoff":return tK(z);default:return process.stderr.write(`Unknown subcommand: ${$}
432
- `),process.stderr.write(o1),2}}async function sK(K){let $=n1(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(() => (R1(),c1))).loadPreviousFindings(z,$);if(X.findings.length===0)return process.stdout.write(`reflect: no findings for iter ${$} (nothing to do)
434
- `),0;let H=q1(z,"state");lK(H,{recursive:!0}),W1(q1(H,`findings-${$}.json`),{review_id:X.reviewId,iteration:$,findings:X.findings});let Z=await Promise.resolve().then(() => (E1(),V7)),V=0;if(process.env.LOKI_AUTO_LEARNINGS!=="0"){for(let q of X.findings)if(q.severity==="Critical"||q.severity==="High")await Z.appendFromGateFailure(z,$,q,{episodeBridge:null}),V+=1}return process.stdout.write(`reflect: persisted ${X.findings.length} findings + ${V} learnings (iter ${$})
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 rK(K){let $=n1(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(() => (J7(),U7)),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(() => (R1(),c1))).loadPreviousFindings(z,$),V=Z.findings.filter((_)=>_.severity==="Critical"||_.severity==="High");if(V.length===0)return process.stdout.write(`override: no blocking findings for iter ${$} (skip)
439
- `),0;let q=new Set(["duplicate-code-path","file-exists","test-passes","grep-miss","out-of-scope"]),U=async(_)=>{let C=q.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(V,X,U);await Q.recordOverrideOutcome(z,$,G,V);let B=q1(z,"quality","reviews");if(cK(B))try{let _=dK(B).filter((g)=>g.startsWith("review-")).sort(),C=_[_.length-1];if(C&&oK(q1(B,C)).isDirectory())W1(q1(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
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 tK(K){let $=K[0],z=Number.parseInt(K[1]??"0",10),Q=n1(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(() => (Y7(),M7))).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)
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 n1(K){if(K===void 0)return null;let $=Number.parseInt(K,10);return Number.isFinite($)&&$>=0?$:null}var o1=`loki internal phase1-hooks <subcommand>
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
- `,iK;var T7=L(()=>{y();_1();iK=o1});y();import{readFileSync as E7}from"fs";import{resolve as x7,dirname as F7}from"path";import{fileURLToPath as w7}from"url";var o=null;function i1(){if(o!==null)return o;let K="7.5.31";if(typeof K==="string"&&K.length>0)return o=K,o;try{let $=F7(w7(import.meta.url)),z=S1($);o=E7(x7(z,"VERSION"),"utf-8").trim()}catch{o="unknown"}return o}function e1(){return process.stdout.write(`Loki Mode v${i1()}
455
- `),0}p();n();y();import{readFileSync as C7,existsSync as h7}from"fs";import{resolve as b7}from"path";var y7=["claude","codex","cline","aider"];function $0(){let K=b7(P(),"state","provider");if(!h7(K))return"";try{return C7(K,"utf-8").trim()}catch{return""}}function v7(K,$){return K||$||process.env.LOKI_PROVIDER||"claude"}function g7(K){let $=$0(),z=v7(K,$);switch(process.stdout.write(`${k}Current Provider${W}
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 m7(){let $=$0()||process.env.LOKI_PROVIDER||"claude";process.stdout.write(`${k}Available Providers${W}
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(y7.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 V=$===H?` ${O}(current)${W}`:"";process.stdout.write(` ${Z} ${Q.get(H)}${V}
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 f7(){return process.stdout.write(`${k}Loki Mode Provider Management${W}
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 z0(K){let $=K[0]??"show",z=K.slice(1);switch($){case"show":case"current":return g7(z[0]);case"list":return m7();case"set":case"info":case"models":return u7(["provider",$,...z]);default:return f7()}}async function u7(K){let{run:$}=await Promise.resolve().then(() => (p(),K0)),{resolve:z}=await import("path"),{REPO_ROOT:Q}=await Promise.resolve().then(() => (y(),t1)),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 Q0,readFileSync as c7}from"fs";import{resolve as t}from"path";import{mkdir as l7}from"fs/promises";var X1=t(N1(),"learnings");function D1(K){if(!Q0(K))return 0;try{let $=c7(K,"utf-8"),z=0;for(let Q of $.split(`
490
- `))if(Q.includes('"description"'))z++;return z}catch{return 0}}async function d7(){await l7(X1,{recursive:!0});let K=D1(t(X1,"patterns.jsonl")),$=D1(t(X1,"mistakes.jsonl")),z=D1(t(X1,"successes.jsonl"));return process.stdout.write(`${k}Cross-Project Learnings${W}
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 o7(K){if(K){let Q=`
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(!Q0($))return process.stdout.write(`No index found
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 X0(K){switch(K[0]??"list"){case"list":case"ls":return d7();case"index":return o7(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 A7=`Loki Mode (TypeScript port, Phase 2 of bash->Bun migration)
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 eK(){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 K6(K){eK();let $=K[0],z=K.slice(1);switch($){case void 0:case"help":case"--help":case"-h":return process.stdout.write(A7),0;case"version":case"--version":case"-v":return e1();case"provider":return z0(z);case"memory":return X0(z);case"status":{let{runStatus:Q}=await Promise.resolve().then(() => (G0(),J0));return Q(z)}case"stats":{let{runStats:Q}=await Promise.resolve().then(() => (T0(),O0));return Q(z)}case"doctor":{let{runDoctor:Q}=await Promise.resolve().then(() => (F0(),x0));return Q(z)}case"kpis":{let{runKpis:Q}=await Promise.resolve().then(() => (f0(),m0));return Q(z)}case"rollback":{let{runRollback:Q}=await Promise.resolve().then(() => (t0(),r0));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(`
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(() => (T7(),O7));return H(z.slice(1))}return process.stderr.write(`Unknown internal subcommand: ${Q}
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(A7),2}}process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var $6=await K6(Bun.argv.slice(2));process.exit($6);
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=F6366B267231CAD664756E2164756E21
537
+ //# debugId=F0683D8581DBAEE664756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.5.31'
60
+ __version__ = '7.6.1'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "7.5.31",
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