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 +1 -1
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +985 -40
- package/completions/_loki +10 -0
- package/completions/loki.bash +10 -1
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
[](https://www.autonomi.dev/)
|
|
11
11
|
[](https://hub.docker.com/r/asklokesh/loki-mode)
|
|
12
12
|
|
|
13
|
-
**Current Version: v6.
|
|
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.
|
|
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.
|
|
270
|
+
**v6.32.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
6.
|
|
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
|
|
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
|
|
13833
|
-
local
|
|
13834
|
-
local
|
|
13836
|
+
local show_json=false
|
|
13837
|
+
local last_n=0
|
|
13838
|
+
local save_file=false
|
|
13839
|
+
local share_flag=false
|
|
13835
13840
|
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
|
|
13851
|
-
|
|
13852
|
-
|
|
13853
|
-
|
|
13854
|
-
|
|
13855
|
-
|
|
13856
|
-
|
|
13857
|
-
|
|
13858
|
-
|
|
13859
|
-
|
|
13860
|
-
|
|
13861
|
-
echo
|
|
13862
|
-
echo "
|
|
13863
|
-
|
|
13864
|
-
|
|
13865
|
-
|
|
13866
|
-
;;
|
|
13867
|
-
|
|
13868
|
-
|
|
13869
|
-
|
|
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
|
-
|
|
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'
|
package/completions/loki.bash
CHANGED
|
@@ -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") )
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
package/mcp/__init__.py
CHANGED