loki-mode 6.31.0 → 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 +441 -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)"
|
|
@@ -13833,47 +13833,448 @@ cmd_syslog() {
|
|
|
13833
13833
|
|
|
13834
13834
|
# Fetch and display Prometheus metrics from dashboard
|
|
13835
13835
|
cmd_metrics() {
|
|
13836
|
-
local
|
|
13837
|
-
local
|
|
13838
|
-
local
|
|
13836
|
+
local show_json=false
|
|
13837
|
+
local last_n=0
|
|
13838
|
+
local save_file=false
|
|
13839
|
+
local share_flag=false
|
|
13839
13840
|
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
|
|
13851
|
-
|
|
13852
|
-
|
|
13853
|
-
|
|
13854
|
-
|
|
13855
|
-
|
|
13856
|
-
|
|
13857
|
-
|
|
13858
|
-
|
|
13859
|
-
|
|
13860
|
-
|
|
13861
|
-
|
|
13862
|
-
|
|
13863
|
-
|
|
13864
|
-
|
|
13865
|
-
echo
|
|
13866
|
-
echo "
|
|
13867
|
-
|
|
13868
|
-
|
|
13869
|
-
|
|
13870
|
-
;;
|
|
13871
|
-
|
|
13872
|
-
|
|
13873
|
-
|
|
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"
|
|
13874
14252
|
exit 1
|
|
13875
|
-
|
|
13876
|
-
|
|
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
|
|
13877
14278
|
}
|
|
13878
14279
|
|
|
13879
14280
|
# Output shell completion scripts
|
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