loki-mode 6.17.1 → 6.18.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/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.1
6
+ # Loki Mode v6.18.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.17.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
270
+ **v6.18.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 6.17.1
1
+ 6.18.0
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)"
@@ -434,6 +435,7 @@ show_help() {
434
435
  echo " agent [cmd] Agent type dispatch (list|info|run|start|review)"
435
436
  echo " remote [PRD] Start remote session (connect from phone/browser, Claude Pro/Max)"
436
437
  echo " trigger Event-driven autonomous execution (schedules, webhooks)"
438
+ echo " plan <PRD> Dry-run PRD analysis: complexity, cost, and execution plan"
437
439
  echo " version Show version"
438
440
  echo " help Show this help"
439
441
  echo ""
@@ -1713,6 +1715,293 @@ print(json.dumps(result, indent=2))
1713
1715
  fi
1714
1716
  }
1715
1717
 
1718
+ # Session statistics
1719
+ cmd_stats() {
1720
+ local show_json=false
1721
+ local show_efficiency=false
1722
+
1723
+ while [[ $# -gt 0 ]]; do
1724
+ case "$1" in
1725
+ --json) show_json=true; shift ;;
1726
+ --efficiency) show_efficiency=true; shift ;;
1727
+ *) shift ;;
1728
+ esac
1729
+ done
1730
+
1731
+ if [ ! -d "$LOKI_DIR" ]; then
1732
+ if [ "$show_json" = true ]; then
1733
+ echo '{"error": "No active session"}'
1734
+ else
1735
+ echo -e "${YELLOW}No active session found.${NC}"
1736
+ echo "Start a session with: loki start <prd>"
1737
+ fi
1738
+ return 0
1739
+ fi
1740
+
1741
+ local loki_dir="$LOKI_DIR"
1742
+ local eff_flag="$show_efficiency"
1743
+ local json_flag="$show_json"
1744
+
1745
+ python3 -c "
1746
+ import json, os, sys, glob
1747
+
1748
+ loki_dir = sys.argv[1]
1749
+ show_efficiency = sys.argv[2] == 'true'
1750
+ show_json = sys.argv[3] == 'true'
1751
+
1752
+ # --- Gather data ---
1753
+
1754
+ # Session state (orchestrator)
1755
+ phase = 'N/A'
1756
+ iteration_count = 0
1757
+ orch_file = os.path.join(loki_dir, 'state', 'orchestrator.json')
1758
+ if os.path.isfile(orch_file):
1759
+ try:
1760
+ with open(orch_file) as f:
1761
+ orch = json.load(f)
1762
+ phase = orch.get('currentPhase', 'N/A')
1763
+ iteration_count = orch.get('currentIteration', 0)
1764
+ except Exception:
1765
+ pass
1766
+
1767
+ # Per-iteration metrics
1768
+ eff_dir = os.path.join(loki_dir, 'metrics', 'efficiency')
1769
+ iterations = []
1770
+ if os.path.isdir(eff_dir):
1771
+ for path in sorted(glob.glob(os.path.join(eff_dir, 'iteration-*.json'))):
1772
+ try:
1773
+ with open(path) as f:
1774
+ iterations.append(json.load(f))
1775
+ except Exception:
1776
+ pass
1777
+
1778
+ # If we have iteration files, use their count (more accurate)
1779
+ if iterations:
1780
+ iteration_count = max(iteration_count, len(iterations))
1781
+
1782
+ total_input = sum(it.get('input_tokens', 0) for it in iterations)
1783
+ total_output = sum(it.get('output_tokens', 0) for it in iterations)
1784
+ total_tokens = total_input + total_output
1785
+ total_cost = sum(it.get('cost_usd', 0) for it in iterations)
1786
+ total_duration = sum(it.get('duration_seconds', 0) for it in iterations)
1787
+
1788
+ # Budget
1789
+ budget_limit = 0
1790
+ budget_used = 0
1791
+ budget_file = os.path.join(loki_dir, 'metrics', 'budget.json')
1792
+ if os.path.isfile(budget_file):
1793
+ try:
1794
+ with open(budget_file) as f:
1795
+ bd = json.load(f)
1796
+ budget_limit = bd.get('budget_limit', 0)
1797
+ budget_used = bd.get('budget_used', 0)
1798
+ except Exception:
1799
+ pass
1800
+
1801
+ # Quality gates
1802
+ gates_passed = 0
1803
+ gates_total = 0
1804
+ gates_file = os.path.join(loki_dir, 'state', 'quality-gates.json')
1805
+ if os.path.isfile(gates_file):
1806
+ try:
1807
+ with open(gates_file) as f:
1808
+ gates = json.load(f)
1809
+ if isinstance(gates, dict):
1810
+ for k, v in gates.items():
1811
+ if isinstance(v, dict):
1812
+ gates_total += 1
1813
+ if v.get('passed') or v.get('status') == 'passed':
1814
+ gates_passed += 1
1815
+ elif isinstance(v, bool):
1816
+ gates_total += 1
1817
+ if v:
1818
+ gates_passed += 1
1819
+ elif isinstance(gates, list):
1820
+ for g in gates:
1821
+ gates_total += 1
1822
+ if isinstance(g, dict) and (g.get('passed') or g.get('status') == 'passed'):
1823
+ gates_passed += 1
1824
+ elif g is True:
1825
+ gates_passed += 1
1826
+ except Exception:
1827
+ pass
1828
+
1829
+ # Gate failures
1830
+ gate_failures = {}
1831
+ gf_file = os.path.join(loki_dir, 'quality', 'gate-failure-count.json')
1832
+ if os.path.isfile(gf_file):
1833
+ try:
1834
+ with open(gf_file) as f:
1835
+ gate_failures = json.load(f)
1836
+ if not isinstance(gate_failures, dict):
1837
+ gate_failures = {}
1838
+ except Exception:
1839
+ pass
1840
+
1841
+ # Code reviews
1842
+ reviews_total = 0
1843
+ reviews_approved = 0
1844
+ reviews_revision = 0
1845
+ quality_dir = os.path.join(loki_dir, 'quality')
1846
+ if os.path.isdir(quality_dir):
1847
+ for fname in os.listdir(quality_dir):
1848
+ if not fname.endswith('.json') or fname == 'gate-failure-count.json':
1849
+ continue
1850
+ fpath = os.path.join(quality_dir, fname)
1851
+ try:
1852
+ with open(fpath) as f:
1853
+ rev = json.load(f)
1854
+ if isinstance(rev, dict) and ('verdict' in rev or 'approved' in rev or 'reviewers' in rev):
1855
+ reviews_total += 1
1856
+ verdict = rev.get('verdict', '').lower()
1857
+ if rev.get('approved') or verdict in ('approved', 'approve', 'pass'):
1858
+ reviews_approved += 1
1859
+ elif verdict in ('revision', 'revise', 'changes_requested', 'reject'):
1860
+ reviews_revision += 1
1861
+ except Exception:
1862
+ pass
1863
+
1864
+ # --- Format duration helper ---
1865
+ def fmt_duration(secs):
1866
+ secs = int(secs)
1867
+ if secs < 60:
1868
+ return f'{secs}s'
1869
+ hours = secs // 3600
1870
+ mins = (secs % 3600) // 60
1871
+ secs_rem = secs % 60
1872
+ if hours > 0:
1873
+ return f'{hours}h {mins:02d}m'
1874
+ return f'{mins}m {secs_rem:02d}s'
1875
+
1876
+ def fmt_number(n):
1877
+ return f'{n:,}'
1878
+
1879
+ # --- JSON output ---
1880
+ if show_json:
1881
+ output = {
1882
+ 'session': {
1883
+ 'iterations': iteration_count,
1884
+ 'duration_seconds': total_duration,
1885
+ 'phase': phase
1886
+ },
1887
+ 'tokens': {
1888
+ 'input': total_input,
1889
+ 'output': total_output,
1890
+ 'total': total_tokens,
1891
+ 'cost_usd': round(total_cost, 2)
1892
+ },
1893
+ 'quality': {
1894
+ 'gates_passed': gates_passed,
1895
+ 'gates_total': gates_total,
1896
+ 'reviews_total': reviews_total,
1897
+ 'reviews_approved': reviews_approved,
1898
+ 'reviews_revision': reviews_revision,
1899
+ 'gate_failures': gate_failures
1900
+ },
1901
+ 'efficiency': {
1902
+ 'avg_tokens_per_iteration': round(total_tokens / iteration_count) if iteration_count > 0 else 0,
1903
+ 'avg_cost_per_iteration': round(total_cost / iteration_count, 2) if iteration_count > 0 else 0,
1904
+ 'avg_duration_per_iteration': round(total_duration / iteration_count, 1) if iteration_count > 0 else 0
1905
+ },
1906
+ 'budget': {
1907
+ 'used': round(budget_used, 2),
1908
+ 'limit': budget_limit,
1909
+ 'percent': round((budget_used / budget_limit) * 100, 1) if budget_limit > 0 else 0
1910
+ }
1911
+ }
1912
+ if show_efficiency:
1913
+ output['iterations'] = []
1914
+ for i, it in enumerate(iterations, 1):
1915
+ output['iterations'].append({
1916
+ 'number': i,
1917
+ 'input_tokens': it.get('input_tokens', 0),
1918
+ 'output_tokens': it.get('output_tokens', 0),
1919
+ 'cost_usd': round(it.get('cost_usd', 0), 2),
1920
+ 'duration_seconds': it.get('duration_seconds', 0)
1921
+ })
1922
+ print(json.dumps(output, indent=2))
1923
+ sys.exit(0)
1924
+
1925
+ # --- Text output ---
1926
+ print('Loki Mode Session Statistics')
1927
+ print('============================')
1928
+ print()
1929
+
1930
+ # Session
1931
+ print('Session')
1932
+ print(f' Iterations completed: {iteration_count}')
1933
+ print(f' Duration: {fmt_duration(total_duration)}')
1934
+ print(f' Current phase: {phase}')
1935
+ print()
1936
+
1937
+ # Token Usage
1938
+ print('Token Usage')
1939
+ if iterations:
1940
+ print(f' Input tokens: {fmt_number(total_input)}')
1941
+ print(f' Output tokens: {fmt_number(total_output)}')
1942
+ print(f' Total tokens: {fmt_number(total_tokens)}')
1943
+ print(f' Estimated cost: \${total_cost:.2f}')
1944
+ else:
1945
+ print(' N/A (no iteration metrics found)')
1946
+ print()
1947
+
1948
+ # Quality Gates
1949
+ print('Quality Gates')
1950
+ if gates_total > 0:
1951
+ pct = round((gates_passed / gates_total) * 100) if gates_total > 0 else 0
1952
+ print(f' Gates passed: {gates_passed}/{gates_total} ({pct}%)')
1953
+ else:
1954
+ print(' Gates passed: N/A')
1955
+ if reviews_total > 0:
1956
+ parts = []
1957
+ if reviews_approved > 0:
1958
+ parts.append(f'{reviews_approved} approved')
1959
+ if reviews_revision > 0:
1960
+ parts.append(f'{reviews_revision} revision requested')
1961
+ detail = ', '.join(parts) if parts else 'N/A'
1962
+ print(f' Code reviews: {reviews_total} ({detail})')
1963
+ if gate_failures:
1964
+ failure_parts = [f'{k} ({v})' for k, v in gate_failures.items() if v > 0]
1965
+ if failure_parts:
1966
+ print(f' Gate failures: {', '.join(failure_parts)}')
1967
+ print()
1968
+
1969
+ # Efficiency
1970
+ print('Efficiency')
1971
+ if iteration_count > 0 and iterations:
1972
+ avg_tokens = round(total_tokens / iteration_count)
1973
+ avg_cost = total_cost / iteration_count
1974
+ avg_dur = total_duration / iteration_count
1975
+ print(f' Avg tokens/iteration: {fmt_number(avg_tokens)}')
1976
+ print(f' Avg cost/iteration: \${avg_cost:.2f}')
1977
+ print(f' Avg duration/iteration: {fmt_duration(avg_dur)}')
1978
+ else:
1979
+ print(' N/A (no iteration metrics found)')
1980
+ print()
1981
+
1982
+ # Budget
1983
+ print('Budget')
1984
+ if budget_limit > 0:
1985
+ pct = round((budget_used / budget_limit) * 100, 1)
1986
+ print(f' Used: \${budget_used:.2f} / \${budget_limit:.2f} ({pct}%)')
1987
+ elif budget_used > 0:
1988
+ print(f' Used: \${budget_used:.2f} (no limit set)')
1989
+ else:
1990
+ print(' N/A')
1991
+
1992
+ # Per-iteration breakdown
1993
+ if show_efficiency and iterations:
1994
+ print()
1995
+ print('Per-Iteration Breakdown')
1996
+ for i, it in enumerate(iterations, 1):
1997
+ inp = fmt_number(it.get('input_tokens', 0))
1998
+ out = fmt_number(it.get('output_tokens', 0))
1999
+ cost = it.get('cost_usd', 0)
2000
+ dur = fmt_duration(it.get('duration_seconds', 0))
2001
+ print(f' #{i:<3} input: {inp:<10} output: {out:<10} cost: \${cost:.2f} time: {dur}')
2002
+ " "$loki_dir" "$eff_flag" "$json_flag"
2003
+ }
2004
+
1716
2005
  # Provider management
1717
2006
  cmd_provider() {
1718
2007
  local subcommand="${1:-show}"
@@ -8010,6 +8299,507 @@ for line in sys.stdin:
8010
8299
  esac
8011
8300
  }
8012
8301
 
8302
+ # Dry-run PRD analysis and cost estimation (v6.18.0)
8303
+ cmd_plan() {
8304
+ local prd_file=""
8305
+ local show_json=false
8306
+ local show_verbose=false
8307
+
8308
+ while [[ $# -gt 0 ]]; do
8309
+ case "$1" in
8310
+ --help|-h)
8311
+ echo -e "${BOLD}loki plan${NC} - Dry-run PRD analysis and cost estimation"
8312
+ echo ""
8313
+ echo "Usage: loki plan <PRD> [options]"
8314
+ echo ""
8315
+ echo "Analyzes a PRD file without executing anything. Outputs complexity,"
8316
+ echo "estimated iterations, token usage, cost, and execution plan."
8317
+ echo ""
8318
+ echo "Options:"
8319
+ echo " --json Machine-readable JSON output"
8320
+ echo " --verbose Show detailed per-iteration breakdown"
8321
+ echo " --help, -h Show this help"
8322
+ echo ""
8323
+ echo "Examples:"
8324
+ echo " loki plan ./prd.md"
8325
+ echo " loki plan ./prd.md --json"
8326
+ echo " loki plan ./prd.md --verbose"
8327
+ return 0
8328
+ ;;
8329
+ --json) show_json=true; shift ;;
8330
+ --verbose) show_verbose=true; shift ;;
8331
+ *)
8332
+ if [ -z "$prd_file" ]; then
8333
+ prd_file="$1"
8334
+ fi
8335
+ shift
8336
+ ;;
8337
+ esac
8338
+ done
8339
+
8340
+ if [ -z "$prd_file" ]; then
8341
+ echo -e "${RED}Usage: loki plan <PRD file>${NC}"
8342
+ echo "Run 'loki plan --help' for usage."
8343
+ return 1
8344
+ fi
8345
+
8346
+ if [ ! -f "$prd_file" ]; then
8347
+ echo -e "${RED}PRD file not found: $prd_file${NC}"
8348
+ return 1
8349
+ fi
8350
+
8351
+ local prd_path
8352
+ prd_path="$(cd "$(dirname "$prd_file")" && pwd)/$(basename "$prd_file")"
8353
+
8354
+ python3 -c "
8355
+ import json, sys, os, re, math
8356
+
8357
+ prd_path = sys.argv[1]
8358
+ show_json = sys.argv[2] == 'true'
8359
+ show_verbose = sys.argv[3] == 'true'
8360
+
8361
+ # Colors (disabled for JSON mode)
8362
+ if show_json:
8363
+ RED = GREEN = YELLOW = BLUE = CYAN = BOLD = DIM = NC = ''
8364
+ else:
8365
+ RED = '\033[0;31m'
8366
+ GREEN = '\033[0;32m'
8367
+ YELLOW = '\033[1;33m'
8368
+ BLUE = '\033[0;34m'
8369
+ CYAN = '\033[0;36m'
8370
+ BOLD = '\033[1m'
8371
+ DIM = '\033[2m'
8372
+ NC = '\033[0m'
8373
+
8374
+ # --- Read and analyze PRD ---
8375
+ with open(prd_path, 'r') as f:
8376
+ content = f.read()
8377
+
8378
+ prd_words = len(content.split())
8379
+ prd_lines = content.count('\n') + 1
8380
+
8381
+ # Detect PRD format
8382
+ is_json_prd = prd_path.endswith('.json')
8383
+ sections = []
8384
+ features = []
8385
+ endpoints = []
8386
+ integrations = []
8387
+ databases = []
8388
+ ui_components = []
8389
+
8390
+ if is_json_prd:
8391
+ try:
8392
+ prd_data = json.loads(content)
8393
+ features = prd_data.get('features', prd_data.get('requirements', []))
8394
+ if isinstance(features, list):
8395
+ features = [f.get('title', f.get('name', str(f))) if isinstance(f, dict) else str(f) for f in features]
8396
+ else:
8397
+ features = []
8398
+ except Exception:
8399
+ features = []
8400
+ else:
8401
+ # Markdown analysis
8402
+ for line in content.split('\n'):
8403
+ stripped = line.strip()
8404
+ if stripped.startswith('## '):
8405
+ sections.append(stripped[3:].strip())
8406
+ elif stripped.startswith('### '):
8407
+ sections.append(stripped[4:].strip())
8408
+
8409
+ # Count features: headers + checkboxes
8410
+ feature_patterns = re.findall(r'^(?:##?\s+|[-*]\s+\[.\]\s+)(.+)', content, re.MULTILINE)
8411
+ features = feature_patterns
8412
+
8413
+ # Count API endpoints
8414
+ endpoint_patterns = re.findall(
8415
+ r'(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+[/\w{}:.-]+',
8416
+ content, re.IGNORECASE
8417
+ )
8418
+ endpoints = endpoint_patterns
8419
+
8420
+ # Count external integrations
8421
+ integration_keywords = [
8422
+ 'oauth', 'saml', 'oidc', 'stripe', 'twilio', 'sendgrid',
8423
+ 'aws', 'gcp', 'azure', 'firebase', 's3', 'redis', 'kafka',
8424
+ 'elasticsearch', 'rabbitmq', 'webhook', 'socket.io', 'websocket',
8425
+ 'graphql', 'grpc', 'docker', 'kubernetes', 'k8s'
8426
+ ]
8427
+ content_lower = content.lower()
8428
+ for kw in integration_keywords:
8429
+ if kw in content_lower:
8430
+ integrations.append(kw)
8431
+
8432
+ # Count database mentions
8433
+ db_keywords = ['postgresql', 'postgres', 'mysql', 'mongodb', 'sqlite',
8434
+ 'dynamodb', 'cassandra', 'redis', 'prisma', 'typeorm',
8435
+ 'sequelize', 'drizzle', 'knex', 'database', 'migration']
8436
+ for kw in db_keywords:
8437
+ if kw in content_lower:
8438
+ databases.append(kw)
8439
+
8440
+ # Count UI components
8441
+ ui_keywords = ['dashboard', 'form', 'table', 'modal', 'navbar',
8442
+ 'sidebar', 'chart', 'graph', 'calendar', 'kanban',
8443
+ 'drag.and.drop', 'responsive', 'mobile', 'animation']
8444
+ for kw in ui_keywords:
8445
+ if re.search(kw, content_lower):
8446
+ ui_components.append(kw)
8447
+
8448
+ feature_count = len(features)
8449
+ endpoint_count = len(endpoints)
8450
+ integration_count = len(integrations)
8451
+ db_count = len(databases)
8452
+ ui_count = len(ui_components)
8453
+ section_count = len(sections)
8454
+
8455
+ # --- Determine complexity ---
8456
+ complexity_score = 0
8457
+ complexity_reasons = []
8458
+
8459
+ if prd_words < 200 and feature_count < 5:
8460
+ complexity = 'simple'
8461
+ complexity_reasons.append('Short PRD (<200 words, <5 features)')
8462
+ elif prd_words > 3000 or feature_count > 30:
8463
+ complexity_score += 3
8464
+ complexity_reasons.append(f'Large PRD ({prd_words} words, {feature_count} features)')
8465
+ elif prd_words > 1000 or feature_count > 15:
8466
+ complexity_score += 2
8467
+ complexity_reasons.append(f'Medium PRD ({prd_words} words, {feature_count} features)')
8468
+ else:
8469
+ complexity_score += 1
8470
+ complexity_reasons.append(f'Standard PRD ({prd_words} words, {feature_count} features)')
8471
+
8472
+ if endpoint_count > 20:
8473
+ complexity_score += 2
8474
+ complexity_reasons.append(f'{endpoint_count} API endpoints detected')
8475
+ elif endpoint_count > 5:
8476
+ complexity_score += 1
8477
+ complexity_reasons.append(f'{endpoint_count} API endpoints detected')
8478
+
8479
+ if integration_count > 3:
8480
+ complexity_score += 2
8481
+ complexity_reasons.append(str(integration_count) + ' external integrations: ' + ', '.join(integrations[:5]))
8482
+ elif integration_count > 0:
8483
+ complexity_score += 1
8484
+ complexity_reasons.append(str(integration_count) + ' external integrations: ' + ', '.join(integrations))
8485
+
8486
+ if db_count > 1:
8487
+ complexity_score += 1
8488
+ complexity_reasons.append('Multiple data stores: ' + ', '.join(databases[:4]))
8489
+
8490
+ if ui_count > 5:
8491
+ complexity_score += 1
8492
+ complexity_reasons.append(f'{ui_count} UI components detected')
8493
+
8494
+ # Map score to tier
8495
+ if complexity_score <= 1:
8496
+ complexity = 'simple'
8497
+ elif complexity_score <= 3:
8498
+ complexity = 'moderate'
8499
+ elif complexity_score <= 5:
8500
+ complexity = 'complex'
8501
+ else:
8502
+ complexity = 'enterprise'
8503
+
8504
+ # --- Estimate iterations ---
8505
+ iteration_map = {
8506
+ 'simple': (3, 5),
8507
+ 'moderate': (6, 12),
8508
+ 'complex': (12, 24),
8509
+ 'enterprise': (24, 48),
8510
+ }
8511
+ min_iter, max_iter = iteration_map[complexity]
8512
+
8513
+ # Adjust based on specific counts
8514
+ if endpoint_count > 10:
8515
+ max_iter = max(max_iter, max_iter + endpoint_count // 5)
8516
+ if integration_count > 2:
8517
+ max_iter += integration_count
8518
+
8519
+ estimated_iterations = (min_iter + max_iter) // 2
8520
+
8521
+ # --- Token estimation per iteration by RARV tier ---
8522
+ # RARV cycle: 4 iterations per cycle (plan, develop, develop, test)
8523
+ # Tokens estimated per iteration step
8524
+ tokens_per_tier = {
8525
+ 'planning': {'input': 50000, 'output': 8000, 'model': 'Opus'},
8526
+ 'development': {'input': 80000, 'output': 15000, 'model': 'Sonnet'},
8527
+ 'fast': {'input': 30000, 'output': 5000, 'model': 'Haiku'},
8528
+ }
8529
+
8530
+ # Pricing per 1M tokens
8531
+ pricing = {
8532
+ 'Opus': {'input': 15.00, 'output': 75.00},
8533
+ 'Sonnet': {'input': 3.00, 'output': 15.00},
8534
+ 'Haiku': {'input': 0.25, 'output': 1.25},
8535
+ }
8536
+
8537
+ # Build per-iteration plan
8538
+ iteration_plan = []
8539
+ total_input_tokens = 0
8540
+ total_output_tokens = 0
8541
+ total_cost = 0.0
8542
+ tier_totals = {'Opus': 0.0, 'Sonnet': 0.0, 'Haiku': 0.0}
8543
+ tier_iterations = {'Opus': 0, 'Sonnet': 0, 'Haiku': 0}
8544
+
8545
+ for i in range(estimated_iterations):
8546
+ rarv_step = i % 4
8547
+ if rarv_step == 0:
8548
+ tier = 'planning'
8549
+ phase_label = 'Reason (Planning)'
8550
+ elif rarv_step == 1:
8551
+ tier = 'development'
8552
+ phase_label = 'Act (Implementation)'
8553
+ elif rarv_step == 2:
8554
+ tier = 'development'
8555
+ phase_label = 'Reflect (Review)'
8556
+ else:
8557
+ tier = 'fast'
8558
+ phase_label = 'Verify (Testing)'
8559
+
8560
+ info = tokens_per_tier[tier]
8561
+ model = info['model']
8562
+ inp = info['input']
8563
+ out = info['output']
8564
+
8565
+ cost = (inp / 1_000_000) * pricing[model]['input'] + \
8566
+ (out / 1_000_000) * pricing[model]['output']
8567
+
8568
+ total_input_tokens += inp
8569
+ total_output_tokens += out
8570
+ total_cost += cost
8571
+ tier_totals[model] += cost
8572
+ tier_iterations[model] += 1
8573
+
8574
+ iteration_plan.append({
8575
+ 'iteration': i + 1,
8576
+ 'phase': phase_label,
8577
+ 'model': model,
8578
+ 'input_tokens': inp,
8579
+ 'output_tokens': out,
8580
+ 'cost_usd': round(cost, 4),
8581
+ })
8582
+
8583
+ # --- Execution plan (what each cycle tackles) ---
8584
+ execution_phases = []
8585
+ cycle_count = math.ceil(estimated_iterations / 4)
8586
+ phase_names = []
8587
+
8588
+ # Generate phase names based on PRD content
8589
+ if sections:
8590
+ phase_names = sections[:cycle_count]
8591
+ else:
8592
+ # Generic phases based on complexity
8593
+ generic = [
8594
+ 'Project setup and scaffolding',
8595
+ 'Core data models and database schema',
8596
+ 'API endpoints and business logic',
8597
+ 'Frontend components and pages',
8598
+ 'Authentication and authorization',
8599
+ 'External integrations',
8600
+ 'Testing and quality assurance',
8601
+ 'Performance optimization',
8602
+ 'Documentation and deployment',
8603
+ 'Edge cases and error handling',
8604
+ 'Security hardening',
8605
+ 'Final polish and review',
8606
+ ]
8607
+ phase_names = generic[:cycle_count]
8608
+
8609
+ for idx, name in enumerate(phase_names):
8610
+ execution_phases.append({
8611
+ 'cycle': idx + 1,
8612
+ 'iterations': f'{idx * 4 + 1}-{min((idx + 1) * 4, estimated_iterations)}',
8613
+ 'focus': name,
8614
+ })
8615
+
8616
+ # --- Time estimation ---
8617
+ # Average ~3.5 minutes per iteration (API calls + processing)
8618
+ minutes_per_iteration = 3.5
8619
+ total_minutes = estimated_iterations * minutes_per_iteration
8620
+ if total_minutes < 60:
8621
+ time_estimate = f'{int(total_minutes)} minutes'
8622
+ elif total_minutes < 120:
8623
+ time_estimate = f'{int(total_minutes // 60)} hour {int(total_minutes % 60)} minutes'
8624
+ else:
8625
+ time_estimate = f'{total_minutes / 60:.1f} hours'
8626
+
8627
+ min_time = min_iter * minutes_per_iteration
8628
+ max_time = max_iter * minutes_per_iteration
8629
+
8630
+ # --- Quality gates ---
8631
+ quality_gates = [
8632
+ {'gate': 'Static Analysis', 'trigger': 'Every iteration', 'applies': True},
8633
+ {'gate': 'Unit Tests', 'trigger': 'Verify phase (every 4th iteration)', 'applies': True},
8634
+ {'gate': 'Code Review (3-reviewer)', 'trigger': 'After implementation cycles', 'applies': complexity in ('complex', 'enterprise')},
8635
+ {'gate': 'Anti-sycophancy Check', 'trigger': 'On unanimous review approval', 'applies': complexity in ('complex', 'enterprise')},
8636
+ {'gate': 'Integration Tests', 'trigger': 'Mid-project and final', 'applies': integration_count > 0 or endpoint_count > 3},
8637
+ {'gate': 'Coverage Gate (>80%)', 'trigger': 'Final verification', 'applies': True},
8638
+ {'gate': 'Security Scan', 'trigger': 'Before completion', 'applies': integration_count > 0},
8639
+ {'gate': 'Performance Benchmark', 'trigger': 'Before completion', 'applies': complexity in ('complex', 'enterprise')},
8640
+ {'gate': 'Completion Council', 'trigger': 'After all phases', 'applies': True},
8641
+ ]
8642
+ active_gates = [g for g in quality_gates if g['applies']]
8643
+
8644
+ # --- Provider recommendation ---
8645
+ if integration_count > 3 or complexity in ('complex', 'enterprise'):
8646
+ recommended_provider = 'Claude'
8647
+ provider_reason = 'Full feature support needed (subagents, parallel, MCP)'
8648
+ elif complexity == 'simple' and endpoint_count == 0:
8649
+ recommended_provider = 'Any (Claude/Codex/Gemini)'
8650
+ provider_reason = 'Simple project works with all providers'
8651
+ else:
8652
+ recommended_provider = 'Claude'
8653
+ provider_reason = 'Best iteration quality and tool support'
8654
+
8655
+ # --- Output ---
8656
+ if show_json:
8657
+ result = {
8658
+ 'prd_file': prd_path,
8659
+ 'prd_stats': {
8660
+ 'words': prd_words,
8661
+ 'lines': prd_lines,
8662
+ 'sections': section_count,
8663
+ 'features': feature_count,
8664
+ 'endpoints': endpoint_count,
8665
+ 'integrations': integrations,
8666
+ 'databases': databases,
8667
+ 'ui_components': ui_count,
8668
+ },
8669
+ 'complexity': {
8670
+ 'tier': complexity,
8671
+ 'score': complexity_score,
8672
+ 'reasons': complexity_reasons,
8673
+ },
8674
+ 'iterations': {
8675
+ 'estimated': estimated_iterations,
8676
+ 'range': [min_iter, max_iter],
8677
+ 'rarv_cycles': cycle_count,
8678
+ },
8679
+ 'tokens': {
8680
+ 'total_input': total_input_tokens,
8681
+ 'total_output': total_output_tokens,
8682
+ 'total': total_input_tokens + total_output_tokens,
8683
+ },
8684
+ 'cost': {
8685
+ 'total_usd': round(total_cost, 2),
8686
+ 'by_model': {k: round(v, 2) for k, v in tier_totals.items()},
8687
+ 'iterations_by_model': tier_iterations,
8688
+ },
8689
+ 'time': {
8690
+ 'estimated': time_estimate,
8691
+ 'minutes': round(total_minutes, 1),
8692
+ 'range_minutes': [round(min_time, 1), round(max_time, 1)],
8693
+ },
8694
+ 'execution_plan': execution_phases,
8695
+ 'quality_gates': [g['gate'] for g in active_gates],
8696
+ 'provider': {
8697
+ 'recommended': recommended_provider,
8698
+ 'reason': provider_reason,
8699
+ },
8700
+ }
8701
+ if show_verbose:
8702
+ result['iteration_details'] = iteration_plan
8703
+ print(json.dumps(result, indent=2))
8704
+ sys.exit(0)
8705
+
8706
+ # --- Formatted output ---
8707
+ print()
8708
+ print(f'{BOLD}PRD Analysis: {os.path.basename(prd_path)}{NC}')
8709
+ print(f'{DIM}' + '=' * 60 + f'{NC}')
8710
+
8711
+ # PRD Stats
8712
+ print(f'\n{CYAN}PRD Statistics{NC}')
8713
+ print(f' Words: {prd_words} | Lines: {prd_lines} | Sections: {section_count}')
8714
+ print(f' Features: {feature_count} | Endpoints: {endpoint_count} | Integrations: {integration_count}')
8715
+ if databases:
8716
+ print(' Data stores: ' + ', '.join(databases[:4]))
8717
+ if ui_count > 0:
8718
+ print(f' UI components: {ui_count}')
8719
+
8720
+ # Complexity
8721
+ color = {'simple': GREEN, 'moderate': YELLOW, 'complex': RED, 'enterprise': RED}
8722
+ cx_color = color.get(complexity, NC)
8723
+ print(f'\n{CYAN}Complexity{NC}')
8724
+ print(f' Tier: {cx_color}{BOLD}{complexity.upper()}{NC} (score: {complexity_score})')
8725
+ for reason in complexity_reasons:
8726
+ print(f' {DIM}- {reason}{NC}')
8727
+
8728
+ # Iterations
8729
+ print(f'\n{CYAN}Estimated Iterations{NC}')
8730
+ print(f' Count: {BOLD}{estimated_iterations}{NC} (range: {min_iter}-{max_iter})')
8731
+ print(f' RARV cycles: {cycle_count} (4 iterations per cycle)')
8732
+ opus_n = tier_iterations.get('Opus', 0)
8733
+ sonnet_n = tier_iterations.get('Sonnet', 0)
8734
+ haiku_n = tier_iterations.get('Haiku', 0)
8735
+ print(f' Model distribution: Opus x{opus_n} | Sonnet x{sonnet_n} | Haiku x{haiku_n}')
8736
+
8737
+ # Tokens
8738
+ total_tok = total_input_tokens + total_output_tokens
8739
+ print(f'\n{CYAN}Token Usage Estimate{NC}')
8740
+ print(f' Input: {total_input_tokens:>12,} tokens')
8741
+ print(f' Output: {total_output_tokens:>12,} tokens')
8742
+ print(f' Total: {total_tok:>12,} tokens')
8743
+
8744
+ # Cost
8745
+ ds = chr(36) # dollar sign
8746
+ print(f'\n{CYAN}Cost Estimate{NC}')
8747
+ print(f' {BOLD}Total: {ds}{total_cost:.2f}{NC}')
8748
+ for model in ['Opus', 'Sonnet', 'Haiku']:
8749
+ pct = (tier_totals[model] / total_cost * 100) if total_cost > 0 else 0
8750
+ bar_len = int(pct / 5)
8751
+ bar = '#' * bar_len + '.' * (20 - bar_len)
8752
+ mc = tier_totals[model]
8753
+ print(' {:>6}: {}{:>7.2f} ({:4.1f}%) [{}]'.format(model, ds, mc, pct, bar))
8754
+
8755
+ # Time
8756
+ print(f'\n{CYAN}Time Estimate{NC}')
8757
+ print(f' Estimated: {BOLD}{time_estimate}{NC}')
8758
+ min_t_str = f'{int(min_time)} min' if min_time < 60 else f'{min_time/60:.1f} hr'
8759
+ max_t_str = f'{int(max_time)} min' if max_time < 60 else f'{max_time/60:.1f} hr'
8760
+ print(f' Range: {min_t_str} - {max_t_str}')
8761
+
8762
+ # Execution plan
8763
+ print(f'\n{CYAN}Execution Plan{NC}')
8764
+ for phase in execution_phases:
8765
+ c_num = phase.get('cycle', '')
8766
+ c_iters = phase.get('iterations', '')
8767
+ c_focus = phase.get('focus', '')
8768
+ print(f' {BOLD}Cycle {c_num}{NC} (iterations {c_iters}): {c_focus}')
8769
+
8770
+ # Quality gates
8771
+ print(f'\n{CYAN}Quality Gates ({len(active_gates)} active){NC}')
8772
+ for g in active_gates:
8773
+ g_name = g.get('gate', '')
8774
+ g_trig = g.get('trigger', '')
8775
+ print(f' {GREEN}[*]{NC} {g_name} -- {DIM}{g_trig}{NC}')
8776
+
8777
+ # Provider recommendation
8778
+ print(f'\n{CYAN}Recommended Provider{NC}')
8779
+ print(f' {BOLD}{recommended_provider}{NC}')
8780
+ print(f' {DIM}{provider_reason}{NC}')
8781
+
8782
+ # Verbose: per-iteration breakdown
8783
+ if show_verbose:
8784
+ print(f'\n{CYAN}Per-Iteration Breakdown{NC}')
8785
+ hdr = ' ' + DIM + '{:>4} {:<24} {:<7} {:>8} {:>8} {:>8}'.format('Iter', 'Phase', 'Model', 'Input', 'Output', 'Cost') + NC
8786
+ print(hdr)
8787
+ print(' ' + DIM + '-' * 65 + NC)
8788
+ for it in iteration_plan:
8789
+ i_num = it.get('iteration', 0)
8790
+ i_phase = it.get('phase', '')
8791
+ i_model = it.get('model', '')
8792
+ i_inp = it.get('input_tokens', 0)
8793
+ i_out = it.get('output_tokens', 0)
8794
+ i_cost = it.get('cost_usd', 0)
8795
+ print(' {:>4} {:<24} {:<7} {:>7,} {:>7,} {}{:>6.2f}'.format(i_num, i_phase, i_model, i_inp, i_out, ds, i_cost))
8796
+
8797
+ print(f'\n{DIM}This is an estimate. Actual usage depends on PRD complexity,')
8798
+ print(f'code review cycles, and test failures.{NC}')
8799
+ print()
8800
+ " "$prd_path" "$show_json" "$show_verbose"
8801
+ }
8802
+
8013
8803
  # Main command dispatcher
8014
8804
  main() {
8015
8805
  if [ $# -eq 0 ]; then
@@ -8053,6 +8843,9 @@ main() {
8053
8843
  status)
8054
8844
  cmd_status "$@"
8055
8845
  ;;
8846
+ stats)
8847
+ cmd_stats "$@"
8848
+ ;;
8056
8849
  dashboard)
8057
8850
  cmd_dashboard "$@"
8058
8851
  ;;
@@ -8170,6 +8963,9 @@ main() {
8170
8963
  trigger)
8171
8964
  cmd_trigger "$@"
8172
8965
  ;;
8966
+ plan)
8967
+ cmd_plan "$@"
8968
+ ;;
8173
8969
  version|--version|-v)
8174
8970
  cmd_version
8175
8971
  ;;
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "6.17.1"
10
+ __version__ = "6.18.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v6.17.1
5
+ **Version:** v6.18.0
6
6
 
7
7
  ---
8
8
 
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '6.17.1'
60
+ __version__ = '6.18.0'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "6.17.1",
3
+ "version": "6.18.0",
4
4
  "description": "Loki Mode by Autonomi - Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
5
5
  "keywords": [
6
6
  "agent",