loki-mode 6.30.3 → 6.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  [![Autonomi](https://img.shields.io/badge/Autonomi-autonomi.dev-5B4EEA)](https://www.autonomi.dev/)
11
11
  [![Docker Pulls](https://img.shields.io/docker/pulls/asklokesh/loki-mode)](https://hub.docker.com/r/asklokesh/loki-mode)
12
12
 
13
- **Current Version: v6.30.0**
13
+ **Current Version: v6.32.0**
14
14
 
15
15
  ### Traction
16
16
 
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v6.30.0
6
+ # Loki Mode v6.32.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -267,4 +267,4 @@ The following features are documented in skill modules but not yet fully automat
267
267
  | Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
268
268
  | Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
269
269
 
270
- **v6.30.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
270
+ **v6.32.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 6.30.3
1
+ 6.32.0
package/autonomy/loki CHANGED
@@ -424,7 +424,7 @@ show_help() {
424
424
  echo " review [opts] Standalone code review with quality gates (diff, staged, PR, files)"
425
425
  echo " optimize Optimize prompts based on session history"
426
426
  echo " enterprise Enterprise feature management (tokens, OIDC)"
427
- echo " metrics Prometheus/OpenMetrics metrics from dashboard"
427
+ echo " metrics [opts] Session productivity report (--json, --last N, --save, --share)"
428
428
  echo " dogfood Show self-development statistics"
429
429
  echo " secrets [cmd] API key status and validation (status|validate)"
430
430
  echo " reset [target] Reset session state (all|retries|failed)"
@@ -438,6 +438,7 @@ show_help() {
438
438
  echo " trigger Event-driven autonomous execution (schedules, webhooks)"
439
439
  echo " failover [cmd] Cross-provider auto-failover (status|--enable|--test|--chain)"
440
440
  echo " onboard [path] Analyze a repo and generate CLAUDE.md (structure, conventions, commands)"
441
+ echo ' explain [path] Analyze any codebase and explain its architecture in plain English'
441
442
  echo " plan <PRD> Dry-run PRD analysis: complexity, cost, and execution plan"
442
443
  echo " ci [opts] CI/CD quality gate integration (--pr, --report, --github-comment)"
443
444
  echo " test [opts] AI-powered test generation (--file, --dir, --changed, --dry-run)"
@@ -9549,6 +9550,9 @@ main() {
9549
9550
  onboard)
9550
9551
  cmd_onboard "$@"
9551
9552
  ;;
9553
+ explain)
9554
+ cmd_explain "$@"
9555
+ ;;
9552
9556
  ci)
9553
9557
  cmd_ci "$@"
9554
9558
  ;;
@@ -13829,47 +13833,448 @@ cmd_syslog() {
13829
13833
 
13830
13834
  # Fetch and display Prometheus metrics from dashboard
13831
13835
  cmd_metrics() {
13832
- local subcommand="${1:-}"
13833
- local port="${LOKI_DASHBOARD_PORT:-57374}"
13834
- local host="127.0.0.1"
13836
+ local show_json=false
13837
+ local last_n=0
13838
+ local save_file=false
13839
+ local share_flag=false
13835
13840
 
13836
- case "$subcommand" in
13837
- help|--help|-h)
13838
- echo -e "${BOLD}loki metrics${NC} - Prometheus/OpenMetrics metrics"
13839
- echo ""
13840
- echo "Usage: loki metrics [options]"
13841
- echo ""
13842
- echo "Fetches metrics from the dashboard API in Prometheus/OpenMetrics format."
13843
- echo "The dashboard must be running (loki dashboard start or loki serve)."
13844
- echo ""
13845
- echo "Options:"
13846
- echo " help Show this help"
13847
- echo ""
13848
- echo "Environment:"
13849
- echo " LOKI_DASHBOARD_PORT Dashboard port (default: 57374)"
13850
- echo ""
13851
- echo "Examples:"
13852
- echo " loki metrics # Display all metrics"
13853
- echo " loki metrics | grep loki_cost_usd # Filter specific metric"
13854
- ;;
13855
- "")
13856
- # Fetch metrics from dashboard
13857
- local url="http://${host}:${port}/metrics"
13858
- local response
13859
- response=$(curl -sf "$url" 2>/dev/null) || true
13860
- if [ -z "$response" ]; then
13861
- echo -e "${RED}Error: Could not connect to dashboard at ${url}${NC}"
13862
- echo "Make sure the dashboard is running: loki serve"
13863
- exit 1
13864
- fi
13865
- echo "$response"
13866
- ;;
13867
- *)
13868
- echo -e "${RED}Unknown metrics command: $subcommand${NC}"
13869
- echo "Run 'loki metrics help' for usage."
13841
+ while [[ $# -gt 0 ]]; do
13842
+ case "$1" in
13843
+ --help|-h)
13844
+ echo -e "${BOLD}loki metrics${NC} - Session productivity reporter"
13845
+ echo ""
13846
+ echo "Usage: loki metrics [options]"
13847
+ echo ""
13848
+ echo "Analyzes past Loki Mode session data and generates a productivity"
13849
+ echo "stats report showing agents deployed, iterations completed, files"
13850
+ echo "changed, and estimated time saved."
13851
+ echo ""
13852
+ echo "Options:"
13853
+ echo " --json Machine-readable JSON output"
13854
+ echo " --last N Analyze only the last N sessions (default: all)"
13855
+ echo " --save Write report to METRICS.md in project root"
13856
+ echo " --share Upload report as GitHub Gist (via loki share)"
13857
+ echo " --help, -h Show this help"
13858
+ echo ""
13859
+ echo "Subcommands:"
13860
+ echo " prometheus Fetch Prometheus/OpenMetrics from dashboard"
13861
+ echo ""
13862
+ echo "Examples:"
13863
+ echo " loki metrics # Full productivity report"
13864
+ echo " loki metrics --json # Machine-readable output"
13865
+ echo " loki metrics --last 5 # Last 5 sessions only"
13866
+ echo " loki metrics --save # Write METRICS.md"
13867
+ echo " loki metrics --share # Share as GitHub Gist"
13868
+ echo " loki metrics prometheus # Prometheus metrics from dashboard"
13869
+ exit 0
13870
+ ;;
13871
+ --json) show_json=true; shift ;;
13872
+ --last) last_n="${2:-0}"; shift 2 ;;
13873
+ --last=*) last_n="${1#*=}"; shift ;;
13874
+ --save) save_file=true; shift ;;
13875
+ --share) share_flag=true; shift ;;
13876
+ prometheus)
13877
+ # Legacy Prometheus metrics subcommand
13878
+ shift
13879
+ local port="${LOKI_DASHBOARD_PORT:-57374}"
13880
+ local host="127.0.0.1"
13881
+ local url="http://${host}:${port}/metrics"
13882
+ local response
13883
+ response=$(curl -sf "$url" 2>/dev/null) || true
13884
+ if [ -z "$response" ]; then
13885
+ echo -e "${RED}Error: Could not connect to dashboard at ${url}${NC}"
13886
+ echo "Make sure the dashboard is running: loki serve"
13887
+ exit 1
13888
+ fi
13889
+ echo "$response"
13890
+ exit 0
13891
+ ;;
13892
+ *) echo -e "${RED}Unknown option: $1${NC}"; echo "Run 'loki metrics --help' for usage."; exit 1 ;;
13893
+ esac
13894
+ done
13895
+
13896
+ local loki_dir="${LOKI_DIR:-.loki}"
13897
+
13898
+ if ! command -v python3 &>/dev/null; then
13899
+ echo -e "${RED}python3 is required for metrics generation${NC}"
13900
+ exit 1
13901
+ fi
13902
+
13903
+ local metrics_output
13904
+ metrics_output=$(LOKI_DIR="$loki_dir" \
13905
+ METRICS_JSON="$show_json" \
13906
+ METRICS_LAST_N="$last_n" \
13907
+ METRICS_SAVE="$save_file" \
13908
+ python3 << 'METRICS_SCRIPT'
13909
+ import json
13910
+ import os
13911
+ import sys
13912
+ import glob
13913
+ import subprocess
13914
+ from datetime import datetime
13915
+
13916
+ loki_dir = os.environ.get("LOKI_DIR", ".loki")
13917
+ show_json = os.environ.get("METRICS_JSON", "false") == "true"
13918
+ last_n = int(os.environ.get("METRICS_LAST_N", "0"))
13919
+ save_flag = os.environ.get("METRICS_SAVE", "false") == "true"
13920
+
13921
+ def load_json(path):
13922
+ try:
13923
+ with open(path) as f:
13924
+ return json.load(f)
13925
+ except Exception:
13926
+ return None
13927
+
13928
+ def fmt_number(n):
13929
+ return f"{n:,}"
13930
+
13931
+ def load_queue(path):
13932
+ data = load_json(path)
13933
+ if isinstance(data, list):
13934
+ return data
13935
+ if isinstance(data, dict) and "tasks" in data:
13936
+ return data["tasks"]
13937
+ return []
13938
+
13939
+ # --- Gather project name ---
13940
+ project_name = os.path.basename(os.getcwd())
13941
+
13942
+ # --- Session data ---
13943
+ sessions_analyzed = 0
13944
+ total_iterations = 0
13945
+ total_tasks_completed = 0
13946
+ total_tasks_failed = 0
13947
+ agent_types_seen = set()
13948
+ total_duration_seconds = 0
13949
+
13950
+ # Count sessions from checkpoints index or autonomy-state
13951
+ checkpoint_index = os.path.join(loki_dir, "state", "checkpoints", "index.jsonl")
13952
+ session_entries = []
13953
+ if os.path.isfile(checkpoint_index):
13954
+ try:
13955
+ with open(checkpoint_index) as f:
13956
+ for line in f:
13957
+ line = line.strip()
13958
+ if line:
13959
+ try:
13960
+ session_entries.append(json.loads(line))
13961
+ except Exception:
13962
+ pass
13963
+ except Exception:
13964
+ pass
13965
+
13966
+ # Orchestrator state
13967
+ orch = load_json(os.path.join(loki_dir, "state", "orchestrator.json")) or {}
13968
+ current_phase = orch.get("currentPhase", "N/A")
13969
+ orch_metrics = orch.get("metrics", {})
13970
+ tasks_completed_orch = orch_metrics.get("tasksCompleted", 0)
13971
+ tasks_failed_orch = orch_metrics.get("tasksFailed", 0)
13972
+
13973
+ # Per-iteration efficiency files
13974
+ eff_dir = os.path.join(loki_dir, "metrics", "efficiency")
13975
+ iterations = []
13976
+ if os.path.isdir(eff_dir):
13977
+ for path in sorted(glob.glob(os.path.join(eff_dir, "iteration-*.json"))):
13978
+ data = load_json(path)
13979
+ if data:
13980
+ iterations.append(data)
13981
+
13982
+ # Apply --last N filter
13983
+ if last_n > 0 and len(iterations) > last_n:
13984
+ iterations = iterations[-last_n:]
13985
+
13986
+ total_iterations = len(iterations) if iterations else max(orch.get("currentIteration", 0), 0)
13987
+
13988
+ # Gather agent types from iterations and agents.json
13989
+ for it in iterations:
13990
+ model = it.get("model", "")
13991
+ if model:
13992
+ agent_types_seen.add(model)
13993
+ total_duration_seconds += it.get("duration_seconds", 0)
13994
+
13995
+ agents_file = os.path.join(loki_dir, "state", "agents.json")
13996
+ agents_data = load_json(agents_file)
13997
+ if isinstance(agents_data, list):
13998
+ for agent in agents_data:
13999
+ atype = agent.get("agent_type", "")
14000
+ if atype:
14001
+ agent_types_seen.add(atype)
14002
+
14003
+ # Queue data for task counts
14004
+ completed_tasks = load_queue(os.path.join(loki_dir, "queue", "completed.json"))
14005
+ failed_tasks = load_queue(os.path.join(loki_dir, "queue", "failed.json"))
14006
+ pending_tasks = load_queue(os.path.join(loki_dir, "queue", "pending.json"))
14007
+ in_progress_tasks = load_queue(os.path.join(loki_dir, "queue", "in-progress.json"))
14008
+
14009
+ total_tasks_completed = len(completed_tasks) if completed_tasks else tasks_completed_orch
14010
+ total_tasks_failed = len(failed_tasks) if failed_tasks else tasks_failed_orch
14011
+ total_tasks = total_tasks_completed + total_tasks_failed + len(pending_tasks) + len(in_progress_tasks)
14012
+ success_rate = (total_tasks_completed / total_tasks * 100) if total_tasks > 0 else 0.0
14013
+
14014
+ # Session count: count unique session directories or fall back to 1 if .loki exists
14015
+ sessions_dir = os.path.join(loki_dir, "state", "sessions")
14016
+ if os.path.isdir(sessions_dir):
14017
+ sessions_analyzed = len([d for d in os.listdir(sessions_dir) if os.path.isdir(os.path.join(sessions_dir, d))])
14018
+ if sessions_analyzed == 0:
14019
+ # Check autonomy-state.json retryCount as proxy for session attempts
14020
+ auto_state = load_json(os.path.join(loki_dir, "autonomy-state.json"))
14021
+ if auto_state:
14022
+ sessions_analyzed = max(1, auto_state.get("retryCount", 1))
14023
+ elif os.path.isdir(loki_dir):
14024
+ sessions_analyzed = 1
14025
+
14026
+ # Apply --last N to sessions
14027
+ if last_n > 0:
14028
+ sessions_analyzed = min(sessions_analyzed, last_n)
14029
+
14030
+ # --- Git stats ---
14031
+ lines_added = 0
14032
+ lines_removed = 0
14033
+ commits_made = 0
14034
+ files_changed_git = 0
14035
+ tests_written = 0
14036
+
14037
+ try:
14038
+ result = subprocess.run(
14039
+ ["git", "log", "--shortstat", "--format="],
14040
+ capture_output=True, text=True, timeout=10
14041
+ )
14042
+ if result.returncode == 0:
14043
+ for line in result.stdout.strip().split("\n"):
14044
+ line = line.strip()
14045
+ if not line:
14046
+ continue
14047
+ parts = line.split(",")
14048
+ for part in parts:
14049
+ part = part.strip()
14050
+ if "file" in part and "changed" in part:
14051
+ try:
14052
+ files_changed_git += int(part.split()[0])
14053
+ except (ValueError, IndexError):
14054
+ pass
14055
+ elif "insertion" in part:
14056
+ try:
14057
+ lines_added += int(part.split()[0])
14058
+ except (ValueError, IndexError):
14059
+ pass
14060
+ elif "deletion" in part:
14061
+ try:
14062
+ lines_removed += int(part.split()[0])
14063
+ except (ValueError, IndexError):
14064
+ pass
14065
+
14066
+ # Count commits
14067
+ result2 = subprocess.run(
14068
+ ["git", "rev-list", "--count", "HEAD"],
14069
+ capture_output=True, text=True, timeout=10
14070
+ )
14071
+ if result2.returncode == 0:
14072
+ commits_made = int(result2.stdout.strip())
14073
+ except Exception:
14074
+ pass
14075
+
14076
+ # Estimate tests written from git log (files matching test/spec patterns)
14077
+ try:
14078
+ result3 = subprocess.run(
14079
+ ["git", "log", "--diff-filter=A", "--name-only", "--format="],
14080
+ capture_output=True, text=True, timeout=10
14081
+ )
14082
+ if result3.returncode == 0:
14083
+ for fname in result3.stdout.strip().split("\n"):
14084
+ fname = fname.strip().lower()
14085
+ if any(kw in fname for kw in ["test", "spec", ".test.", ".spec.", "_test.", "_spec."]):
14086
+ tests_written += 1
14087
+ except Exception:
14088
+ pass
14089
+
14090
+ # --- Token/cost data ---
14091
+ total_cost = sum(it.get("cost_usd", 0) for it in iterations)
14092
+ total_input_tokens = sum(it.get("input_tokens", 0) for it in iterations)
14093
+ total_output_tokens = sum(it.get("output_tokens", 0) for it in iterations)
14094
+
14095
+ # Also check context tracking for cost data
14096
+ ctx_file = os.path.join(loki_dir, "context", "tracking.json")
14097
+ ctx = load_json(ctx_file)
14098
+ if ctx and total_cost == 0:
14099
+ totals = ctx.get("totals", {})
14100
+ total_cost = totals.get("total_cost_usd", 0)
14101
+ total_input_tokens = totals.get("total_input", 0)
14102
+ total_output_tokens = totals.get("total_output", 0)
14103
+
14104
+ # --- Time saved estimate ---
14105
+ # Each Loki iteration replaces ~15min of manual work
14106
+ time_saved_hours = round(total_iterations * 15 / 60, 2)
14107
+
14108
+ # --- Memory stats ---
14109
+ episodic_count = 0
14110
+ semantic_count = 0
14111
+ episodic_dir = os.path.join(loki_dir, "memory", "episodic")
14112
+ semantic_dir = os.path.join(loki_dir, "memory", "semantic")
14113
+ if os.path.isdir(episodic_dir):
14114
+ for root, dirs, files in os.walk(episodic_dir):
14115
+ episodic_count += len([f for f in files if f.endswith(".json")])
14116
+ if os.path.isdir(semantic_dir):
14117
+ for root, dirs, files in os.walk(semantic_dir):
14118
+ semantic_count += len([f for f in files if f.endswith(".json")])
14119
+
14120
+ # --- Build output ---
14121
+ version = "6.32.0"
14122
+ try:
14123
+ version_file = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "VERSION")
14124
+ # In heredoc context, __file__ is not reliable; try relative
14125
+ for vf in ["VERSION", "../VERSION", os.path.join(loki_dir, "..", "VERSION")]:
14126
+ if os.path.isfile(vf):
14127
+ with open(vf) as f:
14128
+ version = f.read().strip()
14129
+ break
14130
+ except Exception:
14131
+ pass
14132
+
14133
+ data = {
14134
+ "project": project_name,
14135
+ "sessions_analyzed": sessions_analyzed,
14136
+ "agent_activity": {
14137
+ "total_iterations": total_iterations,
14138
+ "agent_types": len(agent_types_seen),
14139
+ "tasks_completed": total_tasks_completed,
14140
+ "tasks_failed": total_tasks_failed,
14141
+ "success_rate": round(success_rate, 1)
14142
+ },
14143
+ "code_output": {
14144
+ "files_changed": files_changed_git,
14145
+ "lines_added": lines_added,
14146
+ "lines_removed": lines_removed,
14147
+ "commits": commits_made,
14148
+ "tests_written": tests_written
14149
+ },
14150
+ "tokens": {
14151
+ "input": total_input_tokens,
14152
+ "output": total_output_tokens,
14153
+ "total": total_input_tokens + total_output_tokens,
14154
+ "cost_usd": round(total_cost, 2)
14155
+ },
14156
+ "time_saved": {
14157
+ "hours": time_saved_hours,
14158
+ "iterations": total_iterations,
14159
+ "minutes_per_iteration": 15
14160
+ },
14161
+ "memory": {
14162
+ "episodic_entries": episodic_count,
14163
+ "semantic_patterns": semantic_count
14164
+ },
14165
+ "version": version
14166
+ }
14167
+
14168
+ if show_json:
14169
+ print(json.dumps(data, indent=2))
14170
+ else:
14171
+ # ASCII stats card
14172
+ lines = []
14173
+ lines.append("")
14174
+ lines.append("LOKI MODE PRODUCTIVITY REPORT")
14175
+ lines.append("=" * 40)
14176
+ lines.append(f"Project: {project_name}")
14177
+ lines.append(f"Sessions analyzed: {sessions_analyzed}")
14178
+ lines.append("")
14179
+ lines.append("AGENT ACTIVITY")
14180
+ lines.append(f" Total iterations: {fmt_number(total_iterations)}")
14181
+ lines.append(f" Agent types used: {len(agent_types_seen)}")
14182
+ lines.append(f" Tasks completed: {fmt_number(total_tasks_completed)}")
14183
+ if total_tasks_failed > 0:
14184
+ lines.append(f" Tasks failed: {fmt_number(total_tasks_failed)}")
14185
+ if total_tasks > 0:
14186
+ lines.append(f" Success rate: {success_rate:.1f}%")
14187
+ lines.append("")
14188
+ lines.append("CODE OUTPUT")
14189
+ lines.append(f" Files changed: {fmt_number(files_changed_git)}")
14190
+ lines.append(f" Lines added: {fmt_number(lines_added)}")
14191
+ lines.append(f" Lines removed: {fmt_number(lines_removed)}")
14192
+ lines.append(f" Commits: {fmt_number(commits_made)}")
14193
+ lines.append(f" Tests written: {fmt_number(tests_written)}")
14194
+ lines.append("")
14195
+ if total_cost > 0 or total_input_tokens > 0:
14196
+ lines.append("TOKEN USAGE")
14197
+ lines.append(f" Input tokens: {fmt_number(total_input_tokens)}")
14198
+ lines.append(f" Output tokens: {fmt_number(total_output_tokens)}")
14199
+ lines.append(f" Estimated cost: ${total_cost:.2f}")
14200
+ lines.append("")
14201
+ lines.append("TIME SAVED")
14202
+ lines.append(f" Estimated: {time_saved_hours} hours")
14203
+ lines.append(f" (based on {fmt_number(total_iterations)} iterations x 15min each)")
14204
+ lines.append("")
14205
+ if episodic_count > 0 or semantic_count > 0:
14206
+ lines.append("MEMORY")
14207
+ lines.append(f" Episodic entries: {fmt_number(episodic_count)}")
14208
+ lines.append(f" Semantic patterns: {fmt_number(semantic_count)}")
14209
+ lines.append("")
14210
+ lines.append(f"Generated by Loki Mode v{version} | autonomi.dev")
14211
+ lines.append("Share your stats: loki metrics --share")
14212
+ lines.append("")
14213
+
14214
+ output_text = "\n".join(lines)
14215
+ print(output_text)
14216
+
14217
+ # Save to METRICS.md if requested
14218
+ if save_flag:
14219
+ try:
14220
+ with open("METRICS.md", "w") as f:
14221
+ f.write("# Loki Mode Productivity Report\n\n")
14222
+ f.write(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
14223
+ f.write("```\n")
14224
+ f.write(output_text.strip())
14225
+ f.write("\n```\n")
14226
+ print("Saved to METRICS.md")
14227
+ except Exception as e:
14228
+ print(f"Error saving METRICS.md: {e}", file=sys.stderr)
14229
+
14230
+ METRICS_SCRIPT
14231
+ )
14232
+ local metrics_exit=$?
14233
+
14234
+ if [ $metrics_exit -ne 0 ]; then
14235
+ echo -e "${RED}Failed to generate metrics report${NC}"
14236
+ echo "$metrics_output"
14237
+ exit 1
14238
+ fi
14239
+
14240
+ echo "$metrics_output"
14241
+
14242
+ # Handle --share flag
14243
+ if [ "$share_flag" = true ]; then
14244
+ local tmpfile
14245
+ tmpfile=$(mktemp "/tmp/loki-metrics-XXXXXX.md")
14246
+ echo "$metrics_output" > "$tmpfile"
14247
+
14248
+ if ! command -v gh &>/dev/null; then
14249
+ echo -e "${RED}gh CLI not found -- cannot share${NC}"
14250
+ echo "Install: brew install gh"
14251
+ rm -f "$tmpfile"
13870
14252
  exit 1
13871
- ;;
13872
- esac
14253
+ fi
14254
+
14255
+ if ! gh auth status &>/dev/null 2>&1; then
14256
+ echo -e "${RED}GitHub CLI not authenticated${NC}"
14257
+ echo "Run 'gh auth login' to authenticate."
14258
+ rm -f "$tmpfile"
14259
+ exit 1
14260
+ fi
14261
+
14262
+ echo "Uploading metrics report..."
14263
+ local gist_desc="Loki Mode productivity report - ${project_name:-project} ($(date +%Y-%m-%d))"
14264
+ local project_name
14265
+ project_name=$(basename "$(pwd)")
14266
+ local gist_url
14267
+ gist_url=$(gh gist create "$tmpfile" --desc "$gist_desc" --public 2>&1)
14268
+ local gist_exit=$?
14269
+ rm -f "$tmpfile"
14270
+
14271
+ if [ $gist_exit -ne 0 ]; then
14272
+ echo -e "${RED}Failed to create gist${NC}"
14273
+ echo "$gist_url"
14274
+ exit 1
14275
+ fi
14276
+ echo -e "${GREEN}Shared: ${gist_url}${NC}"
14277
+ fi
13873
14278
  }
13874
14279
 
13875
14280
  # Output shell completion scripts
@@ -15049,6 +15454,546 @@ Generated by loki onboard (depth $depth) on $(date +%Y-%m-%d)"
15049
15454
  fi
15050
15455
  }
15051
15456
 
15457
+
15458
+ # Analyze any codebase and explain its architecture in plain English (v6.31.0)
15459
+ cmd_explain() {
15460
+ local target_path="."
15461
+ set +e
15462
+ local output_json=false
15463
+ local output_brief=false
15464
+ local output_save=false
15465
+
15466
+ while [[ $# -gt 0 ]]; do
15467
+ case "$1" in
15468
+ --json) output_json=true; shift ;;
15469
+ --brief) output_brief=true; shift ;;
15470
+ --save) output_save=true; shift ;;
15471
+ --help|-h)
15472
+ echo -e "${BOLD}loki explain${NC} - Analyze any codebase and explain its architecture"
15473
+ echo ""
15474
+ echo "Usage: loki explain [path] [options]"
15475
+ echo ""
15476
+ echo "Arguments:"
15477
+ echo " path Path to repository (default: current directory)"
15478
+ echo ""
15479
+ echo "Options:"
15480
+ echo " --json Machine-readable JSON output"
15481
+ echo " --brief Condensed one-pager version"
15482
+ echo " --save Write output to EXPLAIN.md in the analyzed directory"
15483
+ echo " --help Show this help"
15484
+ echo ""
15485
+ echo "Examples:"
15486
+ echo " loki explain # Analyze current directory"
15487
+ echo " loki explain ~/projects/myapp # Analyze specific repo"
15488
+ echo " loki explain --brief # Condensed overview"
15489
+ echo " loki explain --json # JSON output for tooling"
15490
+ echo " loki explain --save # Save as EXPLAIN.md"
15491
+ return 0
15492
+ ;;
15493
+ -*)
15494
+ log_error "Unknown option: $1"
15495
+ echo "Run 'loki explain --help' for usage."
15496
+ return 1
15497
+ ;;
15498
+ *) target_path="$1"; shift ;;
15499
+ esac
15500
+ done
15501
+
15502
+ if [ ! -d "$target_path" ]; then
15503
+ log_error "Directory not found: $target_path"
15504
+ return 1
15505
+ fi
15506
+
15507
+ target_path="$(cd "$target_path" && pwd)"
15508
+ local project_name
15509
+ project_name="$(basename "$target_path")"
15510
+
15511
+ local languages="" frameworks="" build_system="" test_framework=""
15512
+ local entry_points="" package_manager="" project_description=""
15513
+ local project_version="" detected_patterns="" scripts_info=""
15514
+
15515
+ # --- package.json (JavaScript/TypeScript) ---
15516
+ if [ -f "$target_path/package.json" ]; then
15517
+ languages="$languages JavaScript/TypeScript"
15518
+ package_manager="npm"
15519
+ [ -f "$target_path/yarn.lock" ] && package_manager="yarn"
15520
+ [ -f "$target_path/pnpm-lock.yaml" ] && package_manager="pnpm"
15521
+ [ -f "$target_path/bun.lockb" ] && package_manager="bun"
15522
+
15523
+ if command -v python3 &>/dev/null; then
15524
+ local pkg_meta
15525
+ pkg_meta=$(python3 -c "
15526
+ import json
15527
+ try:
15528
+ d = json.load(open('$target_path/package.json'))
15529
+ print(d.get('name', ''))
15530
+ print(d.get('description', ''))
15531
+ print(d.get('version', ''))
15532
+ main = d.get('main', d.get('module', ''))
15533
+ print(main)
15534
+ s = d.get('scripts', {})
15535
+ for k in sorted(s.keys()):
15536
+ print(f'script:{k}:{s[k]}')
15537
+ for dep in list(d.get('dependencies', {}).keys()):
15538
+ print(f'dep:{dep}')
15539
+ for dep in list(d.get('devDependencies', {}).keys()):
15540
+ print(f'devdep:{dep}')
15541
+ except: pass
15542
+ " 2>/dev/null || true)
15543
+ local pkg_name pkg_desc pkg_ver pkg_main
15544
+ pkg_name=$(echo "$pkg_meta" | sed -n '1p')
15545
+ pkg_desc=$(echo "$pkg_meta" | sed -n '2p')
15546
+ pkg_ver=$(echo "$pkg_meta" | sed -n '3p')
15547
+ pkg_main=$(echo "$pkg_meta" | sed -n '4p')
15548
+ [ -n "$pkg_name" ] && project_name="$pkg_name"
15549
+ [ -n "$pkg_desc" ] && project_description="$pkg_desc"
15550
+ [ -n "$pkg_ver" ] && project_version="$pkg_ver"
15551
+ [ -n "$pkg_main" ] && entry_points="$pkg_main"
15552
+ scripts_info=$(echo "$pkg_meta" | grep '^script:' | sed 's/^script://' || true)
15553
+ local deps_list devdeps_list all_deps
15554
+ deps_list=$(echo "$pkg_meta" | grep '^dep:' | sed 's/^dep://' || true)
15555
+ devdeps_list=$(echo "$pkg_meta" | grep '^devdep:' | sed 's/^devdep://' || true)
15556
+ all_deps="$deps_list
15557
+ $devdeps_list"
15558
+ echo "$deps_list" | grep -q '^react$' && frameworks="$frameworks React" || true
15559
+ echo "$deps_list" | grep -q '^next$' && frameworks="$frameworks Next.js" || true
15560
+ echo "$deps_list" | grep -q '^vue$' && frameworks="$frameworks Vue" || true
15561
+ echo "$deps_list" | grep -q '^express$' && frameworks="$frameworks Express" || true
15562
+ echo "$deps_list" | grep -q '^fastify$' && frameworks="$frameworks Fastify" || true
15563
+ echo "$deps_list" | grep -q '^svelte$' && frameworks="$frameworks Svelte" || true
15564
+ echo "$deps_list" | grep -q '^@angular/core$' && frameworks="$frameworks Angular" || true
15565
+ echo "$deps_list" | grep -q '^hono$' && frameworks="$frameworks Hono" || true
15566
+ echo "$deps_list" | grep -q '^astro$' && frameworks="$frameworks Astro" || true
15567
+ echo "$deps_list" | grep -q '^nuxt$' && frameworks="$frameworks Nuxt" || true
15568
+ echo "$deps_list" | grep -q '^remix$' && frameworks="$frameworks Remix" || true
15569
+ echo "$deps_list" | grep -q '^electron$' && frameworks="$frameworks Electron" || true
15570
+ echo "$all_deps" | grep -q '^tailwindcss$' && frameworks="$frameworks Tailwind" || true
15571
+ echo "$all_deps" | grep -q '^jest$' && test_framework="$test_framework jest" || true
15572
+ echo "$all_deps" | grep -q '^vitest$' && test_framework="$test_framework vitest" || true
15573
+ echo "$all_deps" | grep -q '^mocha$' && test_framework="$test_framework mocha" || true
15574
+ echo "$all_deps" | grep -q '^@playwright/test$' && test_framework="$test_framework playwright" || true
15575
+ echo "$all_deps" | grep -q '^cypress$' && test_framework="$test_framework cypress" || true
15576
+ echo "$all_deps" | grep -q '^webpack$' && build_system="webpack" || true
15577
+ echo "$all_deps" | grep -q '^vite$' && build_system="vite" || true
15578
+ echo "$all_deps" | grep -q '^esbuild$' && build_system="esbuild" || true
15579
+ echo "$all_deps" | grep -q '^rollup$' && build_system="rollup" || true
15580
+ echo "$all_deps" | grep -q '^turbo$' && build_system="turborepo" || true
15581
+ echo "$all_deps" | grep -q '^prisma$' && detected_patterns="$detected_patterns ORM(Prisma)" || true
15582
+ echo "$all_deps" | grep -q '^drizzle-orm$' && detected_patterns="$detected_patterns ORM(Drizzle)" || true
15583
+ echo "$all_deps" | grep -q '^@trpc/server$' && detected_patterns="$detected_patterns tRPC" || true
15584
+ echo "$all_deps" | grep -q '^graphql$' && detected_patterns="$detected_patterns GraphQL" || true
15585
+ echo "$all_deps" | grep -q '^socket.io$' && detected_patterns="$detected_patterns WebSocket" || true
15586
+ echo "$all_deps" | grep -q '^ws$' && detected_patterns="$detected_patterns WebSocket" || true
15587
+ echo "$all_deps" | grep -q '^@nestjs/core$' && frameworks="$frameworks NestJS" || true
15588
+ echo "$all_deps" | grep -q '^redis$\|^ioredis$' && detected_patterns="$detected_patterns Redis" || true
15589
+ echo "$all_deps" | grep -q '^mongoose$' && detected_patterns="$detected_patterns MongoDB" || true
15590
+ echo "$all_deps" | grep -q '^pg$' && detected_patterns="$detected_patterns PostgreSQL" || true
15591
+ echo "$all_deps" | grep -q '^stripe$' && detected_patterns="$detected_patterns Stripe" || true
15592
+ fi
15593
+ fi
15594
+
15595
+ # --- Python ---
15596
+ if [ -f "$target_path/pyproject.toml" ]; then
15597
+ languages="$languages Python"
15598
+ package_manager="pip"
15599
+ grep -q '\[tool.poetry' "$target_path/pyproject.toml" 2>/dev/null && package_manager="poetry" || true
15600
+ grep -q "django" "$target_path/pyproject.toml" 2>/dev/null && frameworks="$frameworks Django" || true
15601
+ grep -q "flask" "$target_path/pyproject.toml" 2>/dev/null && frameworks="$frameworks Flask" || true
15602
+ grep -q "fastapi" "$target_path/pyproject.toml" 2>/dev/null && frameworks="$frameworks FastAPI" || true
15603
+ grep -q "sqlalchemy" "$target_path/pyproject.toml" 2>/dev/null && detected_patterns="$detected_patterns ORM(SQLAlchemy)" || true
15604
+ grep -q "pytest" "$target_path/pyproject.toml" 2>/dev/null && test_framework="$test_framework pytest" || true
15605
+ [ -z "$project_description" ] && project_description=$(grep '^description' "$target_path/pyproject.toml" 2>/dev/null | head -1 | sed 's/^description *= *"//;s/"$//' || true)
15606
+ [ -z "$project_version" ] && project_version=$(grep '^version' "$target_path/pyproject.toml" 2>/dev/null | head -1 | sed 's/^version *= *"//;s/"$//' || true)
15607
+ elif [ -f "$target_path/requirements.txt" ] || [ -f "$target_path/setup.py" ]; then
15608
+ languages="$languages Python"
15609
+ [ -z "$package_manager" ] && package_manager="pip"
15610
+ fi
15611
+
15612
+ # --- Go ---
15613
+ if [ -f "$target_path/go.mod" ]; then
15614
+ languages="$languages Go"
15615
+ package_manager="go-modules"; build_system="go"; test_framework="$test_framework go-test"
15616
+ grep -q "gin-gonic" "$target_path/go.mod" 2>/dev/null && frameworks="$frameworks Gin" || true
15617
+ grep -q "grpc" "$target_path/go.mod" 2>/dev/null && detected_patterns="$detected_patterns gRPC" || true
15618
+ fi
15619
+
15620
+ # --- Rust ---
15621
+ if [ -f "$target_path/Cargo.toml" ]; then
15622
+ languages="$languages Rust"
15623
+ package_manager="cargo"; build_system="cargo"; test_framework="$test_framework cargo-test"
15624
+ grep -q "actix" "$target_path/Cargo.toml" 2>/dev/null && frameworks="$frameworks Actix" || true
15625
+ grep -q "axum" "$target_path/Cargo.toml" 2>/dev/null && frameworks="$frameworks Axum" || true
15626
+ grep -q "tokio" "$target_path/Cargo.toml" 2>/dev/null && detected_patterns="$detected_patterns Async(Tokio)" || true
15627
+ fi
15628
+
15629
+ # --- Ruby ---
15630
+ if [ -f "$target_path/Gemfile" ]; then
15631
+ languages="$languages Ruby"; package_manager="bundler"
15632
+ grep -q "rails" "$target_path/Gemfile" 2>/dev/null && frameworks="$frameworks Rails" || true
15633
+ grep -q "rspec" "$target_path/Gemfile" 2>/dev/null && test_framework="$test_framework rspec" || true
15634
+ fi
15635
+
15636
+ # --- Java/Kotlin ---
15637
+ if [ -f "$target_path/pom.xml" ]; then languages="$languages Java" && build_system="maven" && package_manager="maven"; fi
15638
+ if [ -f "$target_path/build.gradle" ] || [ -f "$target_path/build.gradle.kts" ]; then languages="$languages Java/Kotlin"; build_system="gradle"; fi
15639
+
15640
+ # --- C/C++ ---
15641
+ if [ -f "$target_path/CMakeLists.txt" ]; then languages="$languages C/C++" && build_system="cmake"; fi
15642
+ if [ -f "$target_path/Makefile" ]; then [ -z "$build_system" ] && build_system="make"; fi
15643
+
15644
+ # --- Shell ---
15645
+ local shell_count
15646
+ shell_count=$(find "$target_path" -maxdepth 2 -name "*.sh" -type f 2>/dev/null | wc -l | tr -d ' ')
15647
+ [ "$shell_count" -gt 3 ] && languages="$languages Bash"
15648
+
15649
+ # --- Docker / CI ---
15650
+ local has_docker=false
15651
+ if [ -f "$target_path/Dockerfile" ]; then has_docker=true && detected_patterns="$detected_patterns Docker"; fi
15652
+ [ -f "$target_path/docker-compose.yml" ] || [ -f "$target_path/docker-compose.yaml" ] && detected_patterns="$detected_patterns DockerCompose"
15653
+
15654
+ local ci_system=""
15655
+ [ -d "$target_path/.github/workflows" ] && ci_system="GitHub Actions"
15656
+ if [ -f "$target_path/.gitlab-ci.yml" ]; then ci_system="$ci_system GitLab CI"; fi
15657
+
15658
+ # --- Monorepo ---
15659
+ local is_monorepo=false
15660
+ if [ -f "$target_path/lerna.json" ]; then is_monorepo=true && detected_patterns="$detected_patterns Monorepo(Lerna)"; fi
15661
+ if [ -f "$target_path/turbo.json" ]; then is_monorepo=true && detected_patterns="$detected_patterns Monorepo(Turborepo)"; fi
15662
+ if [ -f "$target_path/nx.json" ]; then is_monorepo=true && detected_patterns="$detected_patterns Monorepo(Nx)"; fi
15663
+ if [ -f "$target_path/pnpm-workspace.yaml" ]; then is_monorepo=true && detected_patterns="$detected_patterns Monorepo(pnpm)"; fi
15664
+ if [ -f "$target_path/package.json" ] && grep -q '"workspaces"' "$target_path/package.json" 2>/dev/null; then
15665
+ is_monorepo=true
15666
+ echo "$detected_patterns" | grep -q "Monorepo" || detected_patterns="$detected_patterns Monorepo(npm)"
15667
+ fi
15668
+
15669
+ # Deduplicate
15670
+ languages=$(echo "$languages" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
15671
+ frameworks=$(echo "$frameworks" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
15672
+ test_framework=$(echo "$test_framework" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
15673
+ detected_patterns=$(echo "$detected_patterns" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
15674
+
15675
+ # --- README ---
15676
+ local readme_content="" readme_file=""
15677
+ for f in README.md readme.md README.rst README README.txt; do
15678
+ if [ -f "$target_path/$f" ]; then
15679
+ readme_file="$f"
15680
+ readme_content=$(head -80 "$target_path/$f" 2>/dev/null || true)
15681
+ break
15682
+ fi
15683
+ done
15684
+ if [ -z "$project_description" ] && [ -n "$readme_content" ]; then
15685
+ project_description=$(echo "$readme_content" | grep -v '^#' | grep -v '^$' | grep -v '^\[' | grep -v '^!' | grep -v '^---' | head -3 | sed 's/^ *//' | tr '\n' ' ' | sed 's/ */ /g;s/^ *//;s/ *$//')
15686
+ fi
15687
+
15688
+ # --- File counts ---
15689
+ local tree_output=""
15690
+ if command -v git &>/dev/null && [ -d "$target_path/.git" ]; then
15691
+ tree_output=$(cd "$target_path" && git ls-files 2>/dev/null | head -500 || true)
15692
+ else
15693
+ tree_output=$(find "$target_path" -maxdepth 4 -type f \
15694
+ -not -path '*/node_modules/*' -not -path '*/.git/*' \
15695
+ -not -path '*/vendor/*' -not -path '*/__pycache__/*' \
15696
+ -not -path '*/dist/*' -not -path '*/build/*' \
15697
+ -not -path '*/.next/*' -not -path '*/target/*' \
15698
+ 2>/dev/null | sed "s|$target_path/||" | sort | head -500)
15699
+ fi
15700
+ local total_files src_count test_count doc_count config_count
15701
+ total_files=$(echo "$tree_output" | { grep -c . || true; })
15702
+ src_count=$(echo "$tree_output" | { grep -cE '\.(js|ts|tsx|jsx|py|rs|go|rb|java|kt|c|cpp|h|hpp|sh|swift|cs|php)$' || true; })
15703
+ test_count=$(echo "$tree_output" | { grep -cE '(\.test\.|\.spec\.|_test\.|_spec\.|tests/|test/|__tests__/)' || true; })
15704
+ doc_count=$(echo "$tree_output" | { grep -cE '\.(md|rst|txt)$' || true; })
15705
+ config_count=$(echo "$tree_output" | { grep -cE '\.(json|yaml|yml|toml|cfg|ini|env)$|Dockerfile|Makefile' || true; })
15706
+
15707
+ local top_dirs
15708
+ top_dirs=$(echo "$tree_output" | sed 's|/.*||' | sort | uniq -c | sort -rn | head -20)
15709
+
15710
+ # Detect patterns from directory structure
15711
+ echo "$tree_output" | grep -qE '^(api|routes|controllers)/' && detected_patterns="$detected_patterns REST"
15712
+ echo "$tree_output" | grep -qE '^(pages|app)/' && detected_patterns="$detected_patterns FileRouting"
15713
+ echo "$tree_output" | grep -q '^components/' && detected_patterns="$detected_patterns ComponentBased" || true
15714
+ echo "$tree_output" | grep -qE '(middleware|middlewares)/' && detected_patterns="$detected_patterns Middleware"
15715
+ echo "$tree_output" | grep -qE '(events|listeners|handlers)/' && detected_patterns="$detected_patterns EventDriven"
15716
+ echo "$tree_output" | grep -qE '(models|entities|schemas)/' && detected_patterns="$detected_patterns MVC"
15717
+ echo "$tree_output" | grep -qE '(services|service)/' && detected_patterns="$detected_patterns ServiceLayer"
15718
+ detected_patterns=$(echo "$detected_patterns" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
15719
+
15720
+ # --- Detect commands ---
15721
+ local build_cmd="" run_cmd="" test_cmd="" lint_cmd=""
15722
+ if [ -n "$scripts_info" ]; then
15723
+ local has_build has_dev has_start has_test has_lint
15724
+ has_build=$(echo "$scripts_info" | grep '^build:' | head -1)
15725
+ has_dev=$(echo "$scripts_info" | grep '^dev:' | head -1)
15726
+ has_start=$(echo "$scripts_info" | grep '^start:' | head -1)
15727
+ has_test=$(echo "$scripts_info" | grep '^test:' | head -1)
15728
+ has_lint=$(echo "$scripts_info" | grep '^lint:' | head -1)
15729
+ [ -n "$has_build" ] && build_cmd="${package_manager:-npm} run build"
15730
+ [ -n "$has_dev" ] && run_cmd="${package_manager:-npm} run dev"
15731
+ [ -z "$run_cmd" ] && [ -n "$has_start" ] && run_cmd="${package_manager:-npm} start"
15732
+ [ -n "$has_test" ] && test_cmd="${package_manager:-npm} test"
15733
+ [ -n "$has_lint" ] && lint_cmd="${package_manager:-npm} run lint"
15734
+ fi
15735
+ if [ -f "$target_path/Cargo.toml" ]; then build_cmd="cargo build" && run_cmd="cargo run" && test_cmd="cargo test"; fi
15736
+ if [ -f "$target_path/go.mod" ]; then build_cmd="go build ./..." && run_cmd="go run ." && test_cmd="go test ./..."; fi
15737
+ if [ -f "$target_path/Makefile" ]; then
15738
+ [ -z "$build_cmd" ] && build_cmd="make"
15739
+ grep -q '^test:' "$target_path/Makefile" 2>/dev/null && [ -z "$test_cmd" ] && test_cmd="make test" || true
15740
+ fi
15741
+ if [ -f "$target_path/pyproject.toml" ]; then grep -q '\[tool.pytest' "$target_path/pyproject.toml" 2>/dev/null && [ -z "$test_cmd" ] && test_cmd="pytest" || true; fi
15742
+
15743
+ # --- Key entry point files ---
15744
+ local major_files=""
15745
+ for candidate in \
15746
+ "src/index.ts" "src/index.js" "src/main.ts" "src/main.js" "src/app.ts" "src/app.js" \
15747
+ "index.ts" "index.js" "main.ts" "main.js" "app.ts" "app.js" "server.ts" "server.js" \
15748
+ "src/App.tsx" "src/App.jsx" "pages/index.tsx" "app/page.tsx" \
15749
+ "main.py" "app.py" "manage.py" "src/main.py" "__main__.py" \
15750
+ "main.go" "cmd/main.go" "src/main.rs" "src/lib.rs"; do
15751
+ echo "$tree_output" | grep -q "^${candidate}$" && major_files="$major_files $candidate"
15752
+ done
15753
+ major_files=$(echo "$major_files" | sed 's/^ *//')
15754
+
15755
+ # --- JSON output ---
15756
+ if [ "$output_json" = true ]; then
15757
+ python3 -c "
15758
+ import json
15759
+ data = {
15760
+ 'project': {'name': '$project_name', 'description': '''$(echo "$project_description" | sed "s/'/\\\\'/g")''', 'version': '$project_version', 'path': '$target_path'},
15761
+ 'stack': {'languages': '${languages}'.split() if '${languages}'.strip() else [], 'frameworks': '${frameworks}'.split() if '${frameworks}'.strip() else [], 'build_system': '$build_system' or None, 'package_manager': '$package_manager' or None, 'test_framework': '${test_framework}'.split() if '${test_framework}'.strip() else [], 'ci': '${ci_system}'.strip() or None},
15762
+ 'patterns': '${detected_patterns}'.split() if '${detected_patterns}'.strip() else [],
15763
+ 'files': {'total': $total_files, 'source': $src_count, 'test': $test_count, 'docs': $doc_count, 'config': $config_count},
15764
+ 'commands': {'build': '${build_cmd}' or None, 'run': '${run_cmd}' or None, 'test': '${test_cmd}' or None, 'lint': '${lint_cmd}' or None},
15765
+ 'entry_points': '${major_files}'.split() if '${major_files}'.strip() else [],
15766
+ 'monorepo': $( [ "$is_monorepo" = true ] && echo "True" || echo "False" ),
15767
+ 'has_docker': $( [ "$has_docker" = true ] && echo "True" || echo "False" )
15768
+ }
15769
+ print(json.dumps(data, indent=2))
15770
+ " 2>/dev/null || echo '{"error": "JSON generation failed"}'
15771
+ return 0
15772
+ fi
15773
+
15774
+ # --- Text output ---
15775
+ local output=""
15776
+ local sep="================================================================================"
15777
+ local sep2="--------------------------------------------------------------------------------"
15778
+
15779
+ if [ "$output_brief" = true ]; then
15780
+ output="${BOLD}$project_name${NC}"
15781
+ [ -n "$project_version" ] && output="$output (v$project_version)"
15782
+ output="$output
15783
+ $sep2"
15784
+ [ -n "$project_description" ] && output="$output
15785
+ $project_description
15786
+ "
15787
+ output="$output
15788
+ ${BOLD}Stack:${NC} ${languages:-unknown}"
15789
+ [ -n "$frameworks" ] && output="$output + $frameworks"
15790
+ [ -n "$build_system" ] && output="$output | Build: $build_system"
15791
+ [ -n "$package_manager" ] && output="$output | Pkg: $package_manager"
15792
+ output="$output
15793
+ ${BOLD}Scope:${NC} $total_files files ($src_count source, $test_count test, $doc_count docs)"
15794
+ [ -n "$detected_patterns" ] && output="$output
15795
+ ${BOLD}Patterns:${NC} $(echo "$detected_patterns" | tr ' ' ', ')"
15796
+ [ -n "$ci_system" ] && output="$output
15797
+ ${BOLD}CI/CD:${NC} $(echo "$ci_system" | sed 's/^ *//')"
15798
+ [ -n "$test_framework" ] && output="$output
15799
+ ${BOLD}Testing:${NC} $test_framework"
15800
+ if [ -n "$run_cmd" ] || [ -n "$build_cmd" ] || [ -n "$test_cmd" ]; then
15801
+ output="$output
15802
+ "
15803
+ [ -n "$run_cmd" ] && output="$output
15804
+ ${BOLD}Run:${NC} $run_cmd"
15805
+ [ -n "$build_cmd" ] && output="$output
15806
+ ${BOLD}Build:${NC} $build_cmd"
15807
+ [ -n "$test_cmd" ] && output="$output
15808
+ ${BOLD}Test:${NC} $test_cmd"
15809
+ fi
15810
+ [ -n "$major_files" ] && output="$output
15811
+
15812
+ ${BOLD}Entry points:${NC} $(echo "$major_files" | tr ' ' ', ')"
15813
+ output="$output
15814
+ $sep2
15815
+ Generated by loki explain --brief on $(date +%Y-%m-%d)"
15816
+ else
15817
+ output="$sep
15818
+ PROJECT ANALYSIS: $project_name"
15819
+ [ -n "$project_version" ] && output="$output (v$project_version)"
15820
+ output="$output
15821
+ $sep
15822
+
15823
+ ${BOLD}EXECUTIVE SUMMARY${NC}
15824
+ $sep2
15825
+ "
15826
+ if [ -n "$project_description" ]; then
15827
+ output="$output$project_description"
15828
+ else
15829
+ output="${output}A ${languages:-software} project"
15830
+ [ -n "$frameworks" ] && output="$output built with $frameworks"
15831
+ [ -z "$readme_file" ] && output="$output. No README found -- description derived from project metadata."
15832
+ [ -n "$readme_file" ] && output="$output."
15833
+ fi
15834
+ output="$output
15835
+
15836
+ Scope: $total_files tracked files ($src_count source, $test_count test, $doc_count docs, $config_count config).
15837
+
15838
+ ${BOLD}ARCHITECTURE OVERVIEW${NC}
15839
+ $sep2
15840
+ "
15841
+ if [ -n "$major_files" ]; then
15842
+ output="${output}Entry points:"
15843
+ for ef in $major_files; do
15844
+ output="$output
15845
+ - $ef"
15846
+ done
15847
+ output="$output
15848
+ "
15849
+ fi
15850
+ output="${output}
15851
+ Directory structure (by file count):"
15852
+ while IFS= read -r line; do
15853
+ [ -z "$line" ] && continue
15854
+ local dcount dname
15855
+ dcount=$(echo "$line" | awk '{print $1}')
15856
+ dname=$(echo "$line" | awk '{print $2}')
15857
+ if [ -d "$target_path/$dname" ]; then
15858
+ output="$output
15859
+ $dname/ ($dcount files)"
15860
+ else
15861
+ output="$output
15862
+ $dname"
15863
+ fi
15864
+ done <<< "$top_dirs"
15865
+ [ "$is_monorepo" = true ] && output="$output
15866
+
15867
+ This is a monorepo project."
15868
+ output="$output
15869
+
15870
+ ${BOLD}TECHNOLOGY STACK${NC}
15871
+ $sep2
15872
+ Languages: ${languages:-N/A}
15873
+ Frameworks: ${frameworks:-N/A}
15874
+ Build system: ${build_system:-N/A}
15875
+ Package manager: ${package_manager:-N/A}
15876
+ Test framework: ${test_framework:-N/A}
15877
+ CI/CD: ${ci_system:-N/A}
15878
+ "
15879
+ if [ -n "$detected_patterns" ]; then
15880
+ output="$output
15881
+ ${BOLD}KEY PATTERNS${NC}
15882
+ $sep2"
15883
+ for pattern in $detected_patterns; do
15884
+ case "$pattern" in
15885
+ REST) output="$output
15886
+ - REST API (route/controller directories detected)" ;;
15887
+ GraphQL) output="$output
15888
+ - GraphQL API" ;;
15889
+ gRPC) output="$output
15890
+ - gRPC (protocol buffer based RPC)" ;;
15891
+ tRPC) output="$output
15892
+ - tRPC (end-to-end typesafe APIs)" ;;
15893
+ WebSocket) output="$output
15894
+ - WebSocket (real-time communication)" ;;
15895
+ EventDriven) output="$output
15896
+ - Event-driven architecture" ;;
15897
+ MVC) output="$output
15898
+ - MVC pattern (model/entity directories detected)" ;;
15899
+ ServiceLayer) output="$output
15900
+ - Service layer pattern" ;;
15901
+ ComponentBased) output="$output
15902
+ - Component-based UI architecture" ;;
15903
+ FileRouting) output="$output
15904
+ - File-based routing (pages/app directory)" ;;
15905
+ Middleware) output="$output
15906
+ - Middleware pipeline" ;;
15907
+ Docker) output="$output
15908
+ - Docker containerized" ;;
15909
+ DockerCompose) output="$output
15910
+ - Docker Compose multi-service" ;;
15911
+ Monorepo*) output="$output
15912
+ - $pattern" ;;
15913
+ *) output="$output
15914
+ - $pattern" ;;
15915
+ esac
15916
+ done
15917
+ output="$output
15918
+ "
15919
+ fi
15920
+ output="$output
15921
+ ${BOLD}GETTING STARTED${NC}
15922
+ $sep2
15923
+ "
15924
+ if [ -n "$package_manager" ]; then
15925
+ case "$package_manager" in
15926
+ npm) output="${output}Install dependencies: npm install
15927
+ " ;;
15928
+ yarn) output="${output}Install dependencies: yarn install
15929
+ " ;;
15930
+ pnpm) output="${output}Install dependencies: pnpm install
15931
+ " ;;
15932
+ pip|poetry) output="${output}Install dependencies: ${package_manager} install
15933
+ " ;;
15934
+ cargo) output="${output}Build: cargo build
15935
+ " ;;
15936
+ go-modules) output="${output}Download dependencies: go mod download
15937
+ " ;;
15938
+ bundler) output="${output}Install dependencies: bundle install
15939
+ " ;;
15940
+ *) output="${output}Package manager: $package_manager
15941
+ " ;;
15942
+ esac
15943
+ fi
15944
+ [ -n "$run_cmd" ] && output="${output}
15945
+ Run (development): $run_cmd
15946
+ "
15947
+ [ -n "$build_cmd" ] && output="${output}
15948
+ Build: $build_cmd
15949
+ "
15950
+ [ -n "$test_cmd" ] && output="${output}
15951
+ Test: $test_cmd
15952
+ "
15953
+ [ -n "$lint_cmd" ] && output="${output}
15954
+ Lint: $lint_cmd
15955
+ "
15956
+ output="$output
15957
+ ${BOLD}FOR NEW CONTRIBUTORS${NC}
15958
+ $sep2
15959
+ "
15960
+ if [ -n "$readme_file" ]; then
15961
+ output="${output}Start by reading $readme_file for project-specific context.
15962
+ "
15963
+ else
15964
+ output="${output}Note: This project has no README. Consider adding one.
15965
+ "
15966
+ fi
15967
+ if [ -n "$major_files" ]; then
15968
+ output="${output}
15969
+ Key files to understand first:"
15970
+ for ef in $major_files; do
15971
+ output="$output
15972
+ - $ef"
15973
+ done
15974
+ output="$output
15975
+ "
15976
+ fi
15977
+ [ "$test_count" -gt 0 ] && [ -n "$test_cmd" ] && output="${output}
15978
+ Tests: $test_count test files found. Run '$test_cmd' to verify your changes.
15979
+ "
15980
+ output="$output
15981
+ $sep
15982
+ Generated by loki explain on $(date +%Y-%m-%d)"
15983
+ fi
15984
+
15985
+ # --- Output ---
15986
+ if [ "$output_save" = true ]; then
15987
+ local clean_output
15988
+ clean_output=$(echo -e "$output" | sed 's/\x1b\[[0-9;]*m//g')
15989
+ echo "$clean_output" > "$target_path/EXPLAIN.md"
15990
+ echo -e "${GREEN}Saved to $target_path/EXPLAIN.md${NC}"
15991
+ else
15992
+ echo -e "$output"
15993
+ fi
15994
+ set -euo pipefail
15995
+ }
15996
+
15052
15997
  # CI/CD quality gate integration (v6.22.0)
15053
15998
  cmd_ci() {
15054
15999
  local ci_pr=false
package/completions/_loki CHANGED
@@ -70,6 +70,15 @@ function _loki {
70
70
  issue)
71
71
  _loki_issue
72
72
  ;;
73
+ metrics)
74
+ _arguments \
75
+ '--json[Machine-readable JSON output]' \
76
+ '--last[Analyze last N sessions]:number:' \
77
+ '--save[Write report to METRICS.md]' \
78
+ '--share[Upload report as GitHub Gist]' \
79
+ '--help[Show help]' \
80
+ '1:subcommand:(prometheus)'
81
+ ;;
73
82
  share)
74
83
  _arguments \
75
84
  '--private[Create a secret gist]' \
@@ -115,6 +124,7 @@ function _loki_commands {
115
124
  'voice:Voice input commands'
116
125
  'doctor:Check system prerequisites'
117
126
  'onboard:Analyze repo and generate CLAUDE.md'
127
+ 'metrics:Session productivity report'
118
128
  'share:Share session report as GitHub Gist'
119
129
  'version:Show version'
120
130
  'completions:Output shell completions'
@@ -5,7 +5,7 @@ _loki_completion() {
5
5
  _init_completion || return
6
6
 
7
7
  # Main subcommands (must match autonomy/loki main case statement)
8
- local main_commands="start quick demo init stop pause resume status dashboard logs serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise voice secrets doctor watchdog audit metrics syslog onboard share version completions help"
8
+ local main_commands="start quick demo init stop pause resume status dashboard logs serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise voice secrets doctor watchdog audit metrics syslog onboard share explain plan report test ci version completions help"
9
9
 
10
10
  # 1. If we are on the first argument (subcommand)
11
11
  if [[ $cword -eq 1 ]]; then
@@ -144,6 +144,15 @@ _loki_completion() {
144
144
  _filedir -d
145
145
  ;;
146
146
 
147
+ metrics)
148
+ if [[ "$cur" == -* ]]; then
149
+ COMPREPLY=( $(compgen -W "--json --last --save --share --help" -- "$cur") )
150
+ return 0
151
+ fi
152
+ local metrics_cmds="prometheus"
153
+ COMPREPLY=( $(compgen -W "${metrics_cmds}" -- "$cur") )
154
+ ;;
155
+
147
156
  share)
148
157
  if [[ "$cur" == -* ]]; then
149
158
  COMPREPLY=( $(compgen -W "--private --format --help" -- "$cur") )
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "6.30.3"
10
+ __version__ = "6.32.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v6.30.0
5
+ **Version:** v6.32.0
6
6
 
7
7
  ---
8
8
 
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '6.30.0'
60
+ __version__ = '6.32.0'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "6.30.3",
3
+ "version": "6.32.0",
4
4
  "description": "Loki Mode by Autonomi - Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
5
5
  "keywords": [
6
6
  "agent",