loki-mode 6.17.0 → 6.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +291 -0
- package/autonomy/run.sh +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/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.17.
|
|
6
|
+
# Loki Mode v6.17.2
|
|
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.17.
|
|
270
|
+
**v6.17.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
6.17.
|
|
1
|
+
6.17.2
|
package/autonomy/loki
CHANGED
|
@@ -401,6 +401,7 @@ show_help() {
|
|
|
401
401
|
echo " pause Pause after current session"
|
|
402
402
|
echo " resume Resume paused execution"
|
|
403
403
|
echo " status [--json] Show current status (--json for machine-readable)"
|
|
404
|
+
echo " stats [flags] Session statistics (--json, --efficiency)"
|
|
404
405
|
echo " logs Show recent log output"
|
|
405
406
|
echo " dashboard [cmd] Dashboard server (start|stop|status|url|open)"
|
|
406
407
|
echo " provider [cmd] Manage AI provider (show|set|list|info)"
|
|
@@ -1713,6 +1714,293 @@ print(json.dumps(result, indent=2))
|
|
|
1713
1714
|
fi
|
|
1714
1715
|
}
|
|
1715
1716
|
|
|
1717
|
+
# Session statistics
|
|
1718
|
+
cmd_stats() {
|
|
1719
|
+
local show_json=false
|
|
1720
|
+
local show_efficiency=false
|
|
1721
|
+
|
|
1722
|
+
while [[ $# -gt 0 ]]; do
|
|
1723
|
+
case "$1" in
|
|
1724
|
+
--json) show_json=true; shift ;;
|
|
1725
|
+
--efficiency) show_efficiency=true; shift ;;
|
|
1726
|
+
*) shift ;;
|
|
1727
|
+
esac
|
|
1728
|
+
done
|
|
1729
|
+
|
|
1730
|
+
if [ ! -d "$LOKI_DIR" ]; then
|
|
1731
|
+
if [ "$show_json" = true ]; then
|
|
1732
|
+
echo '{"error": "No active session"}'
|
|
1733
|
+
else
|
|
1734
|
+
echo -e "${YELLOW}No active session found.${NC}"
|
|
1735
|
+
echo "Start a session with: loki start <prd>"
|
|
1736
|
+
fi
|
|
1737
|
+
return 0
|
|
1738
|
+
fi
|
|
1739
|
+
|
|
1740
|
+
local loki_dir="$LOKI_DIR"
|
|
1741
|
+
local eff_flag="$show_efficiency"
|
|
1742
|
+
local json_flag="$show_json"
|
|
1743
|
+
|
|
1744
|
+
python3 -c "
|
|
1745
|
+
import json, os, sys, glob
|
|
1746
|
+
|
|
1747
|
+
loki_dir = sys.argv[1]
|
|
1748
|
+
show_efficiency = sys.argv[2] == 'true'
|
|
1749
|
+
show_json = sys.argv[3] == 'true'
|
|
1750
|
+
|
|
1751
|
+
# --- Gather data ---
|
|
1752
|
+
|
|
1753
|
+
# Session state (orchestrator)
|
|
1754
|
+
phase = 'N/A'
|
|
1755
|
+
iteration_count = 0
|
|
1756
|
+
orch_file = os.path.join(loki_dir, 'state', 'orchestrator.json')
|
|
1757
|
+
if os.path.isfile(orch_file):
|
|
1758
|
+
try:
|
|
1759
|
+
with open(orch_file) as f:
|
|
1760
|
+
orch = json.load(f)
|
|
1761
|
+
phase = orch.get('currentPhase', 'N/A')
|
|
1762
|
+
iteration_count = orch.get('currentIteration', 0)
|
|
1763
|
+
except Exception:
|
|
1764
|
+
pass
|
|
1765
|
+
|
|
1766
|
+
# Per-iteration metrics
|
|
1767
|
+
eff_dir = os.path.join(loki_dir, 'metrics', 'efficiency')
|
|
1768
|
+
iterations = []
|
|
1769
|
+
if os.path.isdir(eff_dir):
|
|
1770
|
+
for path in sorted(glob.glob(os.path.join(eff_dir, 'iteration-*.json'))):
|
|
1771
|
+
try:
|
|
1772
|
+
with open(path) as f:
|
|
1773
|
+
iterations.append(json.load(f))
|
|
1774
|
+
except Exception:
|
|
1775
|
+
pass
|
|
1776
|
+
|
|
1777
|
+
# If we have iteration files, use their count (more accurate)
|
|
1778
|
+
if iterations:
|
|
1779
|
+
iteration_count = max(iteration_count, len(iterations))
|
|
1780
|
+
|
|
1781
|
+
total_input = sum(it.get('input_tokens', 0) for it in iterations)
|
|
1782
|
+
total_output = sum(it.get('output_tokens', 0) for it in iterations)
|
|
1783
|
+
total_tokens = total_input + total_output
|
|
1784
|
+
total_cost = sum(it.get('cost_usd', 0) for it in iterations)
|
|
1785
|
+
total_duration = sum(it.get('duration_seconds', 0) for it in iterations)
|
|
1786
|
+
|
|
1787
|
+
# Budget
|
|
1788
|
+
budget_limit = 0
|
|
1789
|
+
budget_used = 0
|
|
1790
|
+
budget_file = os.path.join(loki_dir, 'metrics', 'budget.json')
|
|
1791
|
+
if os.path.isfile(budget_file):
|
|
1792
|
+
try:
|
|
1793
|
+
with open(budget_file) as f:
|
|
1794
|
+
bd = json.load(f)
|
|
1795
|
+
budget_limit = bd.get('budget_limit', 0)
|
|
1796
|
+
budget_used = bd.get('budget_used', 0)
|
|
1797
|
+
except Exception:
|
|
1798
|
+
pass
|
|
1799
|
+
|
|
1800
|
+
# Quality gates
|
|
1801
|
+
gates_passed = 0
|
|
1802
|
+
gates_total = 0
|
|
1803
|
+
gates_file = os.path.join(loki_dir, 'state', 'quality-gates.json')
|
|
1804
|
+
if os.path.isfile(gates_file):
|
|
1805
|
+
try:
|
|
1806
|
+
with open(gates_file) as f:
|
|
1807
|
+
gates = json.load(f)
|
|
1808
|
+
if isinstance(gates, dict):
|
|
1809
|
+
for k, v in gates.items():
|
|
1810
|
+
if isinstance(v, dict):
|
|
1811
|
+
gates_total += 1
|
|
1812
|
+
if v.get('passed') or v.get('status') == 'passed':
|
|
1813
|
+
gates_passed += 1
|
|
1814
|
+
elif isinstance(v, bool):
|
|
1815
|
+
gates_total += 1
|
|
1816
|
+
if v:
|
|
1817
|
+
gates_passed += 1
|
|
1818
|
+
elif isinstance(gates, list):
|
|
1819
|
+
for g in gates:
|
|
1820
|
+
gates_total += 1
|
|
1821
|
+
if isinstance(g, dict) and (g.get('passed') or g.get('status') == 'passed'):
|
|
1822
|
+
gates_passed += 1
|
|
1823
|
+
elif g is True:
|
|
1824
|
+
gates_passed += 1
|
|
1825
|
+
except Exception:
|
|
1826
|
+
pass
|
|
1827
|
+
|
|
1828
|
+
# Gate failures
|
|
1829
|
+
gate_failures = {}
|
|
1830
|
+
gf_file = os.path.join(loki_dir, 'quality', 'gate-failure-count.json')
|
|
1831
|
+
if os.path.isfile(gf_file):
|
|
1832
|
+
try:
|
|
1833
|
+
with open(gf_file) as f:
|
|
1834
|
+
gate_failures = json.load(f)
|
|
1835
|
+
if not isinstance(gate_failures, dict):
|
|
1836
|
+
gate_failures = {}
|
|
1837
|
+
except Exception:
|
|
1838
|
+
pass
|
|
1839
|
+
|
|
1840
|
+
# Code reviews
|
|
1841
|
+
reviews_total = 0
|
|
1842
|
+
reviews_approved = 0
|
|
1843
|
+
reviews_revision = 0
|
|
1844
|
+
quality_dir = os.path.join(loki_dir, 'quality')
|
|
1845
|
+
if os.path.isdir(quality_dir):
|
|
1846
|
+
for fname in os.listdir(quality_dir):
|
|
1847
|
+
if not fname.endswith('.json') or fname == 'gate-failure-count.json':
|
|
1848
|
+
continue
|
|
1849
|
+
fpath = os.path.join(quality_dir, fname)
|
|
1850
|
+
try:
|
|
1851
|
+
with open(fpath) as f:
|
|
1852
|
+
rev = json.load(f)
|
|
1853
|
+
if isinstance(rev, dict) and ('verdict' in rev or 'approved' in rev or 'reviewers' in rev):
|
|
1854
|
+
reviews_total += 1
|
|
1855
|
+
verdict = rev.get('verdict', '').lower()
|
|
1856
|
+
if rev.get('approved') or verdict in ('approved', 'approve', 'pass'):
|
|
1857
|
+
reviews_approved += 1
|
|
1858
|
+
elif verdict in ('revision', 'revise', 'changes_requested', 'reject'):
|
|
1859
|
+
reviews_revision += 1
|
|
1860
|
+
except Exception:
|
|
1861
|
+
pass
|
|
1862
|
+
|
|
1863
|
+
# --- Format duration helper ---
|
|
1864
|
+
def fmt_duration(secs):
|
|
1865
|
+
secs = int(secs)
|
|
1866
|
+
if secs < 60:
|
|
1867
|
+
return f'{secs}s'
|
|
1868
|
+
hours = secs // 3600
|
|
1869
|
+
mins = (secs % 3600) // 60
|
|
1870
|
+
secs_rem = secs % 60
|
|
1871
|
+
if hours > 0:
|
|
1872
|
+
return f'{hours}h {mins:02d}m'
|
|
1873
|
+
return f'{mins}m {secs_rem:02d}s'
|
|
1874
|
+
|
|
1875
|
+
def fmt_number(n):
|
|
1876
|
+
return f'{n:,}'
|
|
1877
|
+
|
|
1878
|
+
# --- JSON output ---
|
|
1879
|
+
if show_json:
|
|
1880
|
+
output = {
|
|
1881
|
+
'session': {
|
|
1882
|
+
'iterations': iteration_count,
|
|
1883
|
+
'duration_seconds': total_duration,
|
|
1884
|
+
'phase': phase
|
|
1885
|
+
},
|
|
1886
|
+
'tokens': {
|
|
1887
|
+
'input': total_input,
|
|
1888
|
+
'output': total_output,
|
|
1889
|
+
'total': total_tokens,
|
|
1890
|
+
'cost_usd': round(total_cost, 2)
|
|
1891
|
+
},
|
|
1892
|
+
'quality': {
|
|
1893
|
+
'gates_passed': gates_passed,
|
|
1894
|
+
'gates_total': gates_total,
|
|
1895
|
+
'reviews_total': reviews_total,
|
|
1896
|
+
'reviews_approved': reviews_approved,
|
|
1897
|
+
'reviews_revision': reviews_revision,
|
|
1898
|
+
'gate_failures': gate_failures
|
|
1899
|
+
},
|
|
1900
|
+
'efficiency': {
|
|
1901
|
+
'avg_tokens_per_iteration': round(total_tokens / iteration_count) if iteration_count > 0 else 0,
|
|
1902
|
+
'avg_cost_per_iteration': round(total_cost / iteration_count, 2) if iteration_count > 0 else 0,
|
|
1903
|
+
'avg_duration_per_iteration': round(total_duration / iteration_count, 1) if iteration_count > 0 else 0
|
|
1904
|
+
},
|
|
1905
|
+
'budget': {
|
|
1906
|
+
'used': round(budget_used, 2),
|
|
1907
|
+
'limit': budget_limit,
|
|
1908
|
+
'percent': round((budget_used / budget_limit) * 100, 1) if budget_limit > 0 else 0
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
if show_efficiency:
|
|
1912
|
+
output['iterations'] = []
|
|
1913
|
+
for i, it in enumerate(iterations, 1):
|
|
1914
|
+
output['iterations'].append({
|
|
1915
|
+
'number': i,
|
|
1916
|
+
'input_tokens': it.get('input_tokens', 0),
|
|
1917
|
+
'output_tokens': it.get('output_tokens', 0),
|
|
1918
|
+
'cost_usd': round(it.get('cost_usd', 0), 2),
|
|
1919
|
+
'duration_seconds': it.get('duration_seconds', 0)
|
|
1920
|
+
})
|
|
1921
|
+
print(json.dumps(output, indent=2))
|
|
1922
|
+
sys.exit(0)
|
|
1923
|
+
|
|
1924
|
+
# --- Text output ---
|
|
1925
|
+
print('Loki Mode Session Statistics')
|
|
1926
|
+
print('============================')
|
|
1927
|
+
print()
|
|
1928
|
+
|
|
1929
|
+
# Session
|
|
1930
|
+
print('Session')
|
|
1931
|
+
print(f' Iterations completed: {iteration_count}')
|
|
1932
|
+
print(f' Duration: {fmt_duration(total_duration)}')
|
|
1933
|
+
print(f' Current phase: {phase}')
|
|
1934
|
+
print()
|
|
1935
|
+
|
|
1936
|
+
# Token Usage
|
|
1937
|
+
print('Token Usage')
|
|
1938
|
+
if iterations:
|
|
1939
|
+
print(f' Input tokens: {fmt_number(total_input)}')
|
|
1940
|
+
print(f' Output tokens: {fmt_number(total_output)}')
|
|
1941
|
+
print(f' Total tokens: {fmt_number(total_tokens)}')
|
|
1942
|
+
print(f' Estimated cost: \${total_cost:.2f}')
|
|
1943
|
+
else:
|
|
1944
|
+
print(' N/A (no iteration metrics found)')
|
|
1945
|
+
print()
|
|
1946
|
+
|
|
1947
|
+
# Quality Gates
|
|
1948
|
+
print('Quality Gates')
|
|
1949
|
+
if gates_total > 0:
|
|
1950
|
+
pct = round((gates_passed / gates_total) * 100) if gates_total > 0 else 0
|
|
1951
|
+
print(f' Gates passed: {gates_passed}/{gates_total} ({pct}%)')
|
|
1952
|
+
else:
|
|
1953
|
+
print(' Gates passed: N/A')
|
|
1954
|
+
if reviews_total > 0:
|
|
1955
|
+
parts = []
|
|
1956
|
+
if reviews_approved > 0:
|
|
1957
|
+
parts.append(f'{reviews_approved} approved')
|
|
1958
|
+
if reviews_revision > 0:
|
|
1959
|
+
parts.append(f'{reviews_revision} revision requested')
|
|
1960
|
+
detail = ', '.join(parts) if parts else 'N/A'
|
|
1961
|
+
print(f' Code reviews: {reviews_total} ({detail})')
|
|
1962
|
+
if gate_failures:
|
|
1963
|
+
failure_parts = [f'{k} ({v})' for k, v in gate_failures.items() if v > 0]
|
|
1964
|
+
if failure_parts:
|
|
1965
|
+
print(f' Gate failures: {', '.join(failure_parts)}')
|
|
1966
|
+
print()
|
|
1967
|
+
|
|
1968
|
+
# Efficiency
|
|
1969
|
+
print('Efficiency')
|
|
1970
|
+
if iteration_count > 0 and iterations:
|
|
1971
|
+
avg_tokens = round(total_tokens / iteration_count)
|
|
1972
|
+
avg_cost = total_cost / iteration_count
|
|
1973
|
+
avg_dur = total_duration / iteration_count
|
|
1974
|
+
print(f' Avg tokens/iteration: {fmt_number(avg_tokens)}')
|
|
1975
|
+
print(f' Avg cost/iteration: \${avg_cost:.2f}')
|
|
1976
|
+
print(f' Avg duration/iteration: {fmt_duration(avg_dur)}')
|
|
1977
|
+
else:
|
|
1978
|
+
print(' N/A (no iteration metrics found)')
|
|
1979
|
+
print()
|
|
1980
|
+
|
|
1981
|
+
# Budget
|
|
1982
|
+
print('Budget')
|
|
1983
|
+
if budget_limit > 0:
|
|
1984
|
+
pct = round((budget_used / budget_limit) * 100, 1)
|
|
1985
|
+
print(f' Used: \${budget_used:.2f} / \${budget_limit:.2f} ({pct}%)')
|
|
1986
|
+
elif budget_used > 0:
|
|
1987
|
+
print(f' Used: \${budget_used:.2f} (no limit set)')
|
|
1988
|
+
else:
|
|
1989
|
+
print(' N/A')
|
|
1990
|
+
|
|
1991
|
+
# Per-iteration breakdown
|
|
1992
|
+
if show_efficiency and iterations:
|
|
1993
|
+
print()
|
|
1994
|
+
print('Per-Iteration Breakdown')
|
|
1995
|
+
for i, it in enumerate(iterations, 1):
|
|
1996
|
+
inp = fmt_number(it.get('input_tokens', 0))
|
|
1997
|
+
out = fmt_number(it.get('output_tokens', 0))
|
|
1998
|
+
cost = it.get('cost_usd', 0)
|
|
1999
|
+
dur = fmt_duration(it.get('duration_seconds', 0))
|
|
2000
|
+
print(f' #{i:<3} input: {inp:<10} output: {out:<10} cost: \${cost:.2f} time: {dur}')
|
|
2001
|
+
" "$loki_dir" "$eff_flag" "$json_flag"
|
|
2002
|
+
}
|
|
2003
|
+
|
|
1716
2004
|
# Provider management
|
|
1717
2005
|
cmd_provider() {
|
|
1718
2006
|
local subcommand="${1:-show}"
|
|
@@ -8053,6 +8341,9 @@ main() {
|
|
|
8053
8341
|
status)
|
|
8054
8342
|
cmd_status "$@"
|
|
8055
8343
|
;;
|
|
8344
|
+
stats)
|
|
8345
|
+
cmd_stats "$@"
|
|
8346
|
+
;;
|
|
8056
8347
|
dashboard)
|
|
8057
8348
|
cmd_dashboard "$@"
|
|
8058
8349
|
;;
|
package/autonomy/run.sh
CHANGED
|
@@ -8617,6 +8617,8 @@ if __name__ == "__main__":
|
|
|
8617
8617
|
fi
|
|
8618
8618
|
fi
|
|
8619
8619
|
|
|
8620
|
+
log_step "Post-iteration: running inter-iteration checks..."
|
|
8621
|
+
|
|
8620
8622
|
# App Runner: restart on code changes (v5.45.0)
|
|
8621
8623
|
if [ "${APP_RUNNER_INITIALIZED:-}" = "true" ] && type app_runner_should_restart &>/dev/null; then
|
|
8622
8624
|
if app_runner_should_restart; then
|
|
@@ -8661,10 +8663,12 @@ if __name__ == "__main__":
|
|
|
8661
8663
|
create_checkpoint "iteration-${ITERATION_COUNT} complete" "iteration-${ITERATION_COUNT}"
|
|
8662
8664
|
|
|
8663
8665
|
# Quality gates (v6.10.0 - escalation ladder)
|
|
8666
|
+
log_step "Post-iteration: running quality gates..."
|
|
8664
8667
|
local gate_failures=""
|
|
8665
8668
|
if [ "${LOKI_HARD_GATES:-true}" = "true" ]; then
|
|
8666
8669
|
# Static analysis gate
|
|
8667
8670
|
if [ "${PHASE_STATIC_ANALYSIS:-true}" = "true" ]; then
|
|
8671
|
+
log_info "Quality gate: static analysis..."
|
|
8668
8672
|
if enforce_static_analysis; then
|
|
8669
8673
|
clear_gate_failure "static_analysis"
|
|
8670
8674
|
else
|
|
@@ -8676,6 +8680,7 @@ if __name__ == "__main__":
|
|
|
8676
8680
|
fi
|
|
8677
8681
|
# Test coverage gate
|
|
8678
8682
|
if [ "${PHASE_UNIT_TESTS:-true}" = "true" ]; then
|
|
8683
|
+
log_info "Quality gate: test coverage..."
|
|
8679
8684
|
if enforce_test_coverage; then
|
|
8680
8685
|
clear_gate_failure "test_coverage"
|
|
8681
8686
|
else
|
|
@@ -8687,6 +8692,7 @@ if __name__ == "__main__":
|
|
|
8687
8692
|
fi
|
|
8688
8693
|
# Code review gate (upgraded from advisory, with escalation)
|
|
8689
8694
|
if [ "$PHASE_CODE_REVIEW" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
8695
|
+
log_info "Quality gate: code review..."
|
|
8690
8696
|
if run_code_review; then
|
|
8691
8697
|
clear_gate_failure "code_review"
|
|
8692
8698
|
else
|
|
@@ -8717,9 +8723,11 @@ if __name__ == "__main__":
|
|
|
8717
8723
|
fi
|
|
8718
8724
|
else
|
|
8719
8725
|
if [ "$PHASE_CODE_REVIEW" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
8726
|
+
log_info "Quality gate: code review (advisory)..."
|
|
8720
8727
|
run_code_review || log_warn "Code review found issues - check .loki/quality/reviews/"
|
|
8721
8728
|
fi
|
|
8722
8729
|
fi
|
|
8730
|
+
log_info "Quality gates complete."
|
|
8723
8731
|
|
|
8724
8732
|
# Automatic episode capture after every RARV iteration (v6.15.0)
|
|
8725
8733
|
# Captures RARV phase, git changes, and iteration context automatically
|
|
@@ -8745,6 +8753,7 @@ if __name__ == "__main__":
|
|
|
8745
8753
|
|
|
8746
8754
|
# Completion Council check (v5.25.0) - multi-agent voting on completion
|
|
8747
8755
|
# Runs before completion promise check since council is more comprehensive
|
|
8756
|
+
log_step "Post-iteration: checking completion council..."
|
|
8748
8757
|
if type council_should_stop &>/dev/null && council_should_stop; then
|
|
8749
8758
|
echo ""
|
|
8750
8759
|
log_header "COMPLETION COUNCIL: PROJECT COMPLETE"
|
|
@@ -8776,7 +8785,7 @@ if __name__ == "__main__":
|
|
|
8776
8785
|
fi
|
|
8777
8786
|
|
|
8778
8787
|
# SUCCESS exit - continue IMMEDIATELY to next iteration (no wait!)
|
|
8779
|
-
|
|
8788
|
+
log_step "Starting next iteration..."
|
|
8780
8789
|
((retry++))
|
|
8781
8790
|
continue # Immediately start next iteration, no exponential backoff
|
|
8782
8791
|
fi
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
package/mcp/__init__.py
CHANGED