loki-mode 7.77.0 → 7.78.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 +2 -2
- package/VERSION +1 -1
- package/autonomy/NOTIFY_INTEGRATION.md +9 -0
- package/autonomy/run.sh +7 -973
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +2 -2
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 8 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.
|
|
6
|
+
# Loki Mode v7.78.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -406,4 +406,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
406
406
|
|
|
407
407
|
---
|
|
408
408
|
|
|
409
|
-
**v7.
|
|
409
|
+
**v7.78.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.78.0
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
This document describes how to integrate multi-channel notifications (Slack, Discord, Webhook) into run.sh.
|
|
4
4
|
|
|
5
|
+
> Note (v7.78.0): the convenience wrappers `notify_task_started`,
|
|
6
|
+
> `notify_task_completed`, `notify_task_failed`, `notify_phase_complete`, and
|
|
7
|
+
> `notify_all_complete` were never wired into the runtime and were removed as dead
|
|
8
|
+
> code. The live notification helpers are `notify_intervention_needed` and
|
|
9
|
+
> `notify_rate_limit` (plus the `notify_channels` snippets shown below). Snippets
|
|
10
|
+
> in this guide that reference the removed wrappers are illustrative of where a
|
|
11
|
+
> notification COULD be emitted; use `notify_channels` (or the live helpers) when
|
|
12
|
+
> actually wiring one.
|
|
13
|
+
|
|
5
14
|
## Quick Start
|
|
6
15
|
|
|
7
16
|
```bash
|
package/autonomy/run.sh
CHANGED
|
@@ -40,7 +40,12 @@
|
|
|
40
40
|
# LOKI_AUDIT_DISABLED - Disable audit logging (default: false)
|
|
41
41
|
# LOKI_MAX_PARALLEL_AGENTS - Limit concurrent agent spawning (default: 10)
|
|
42
42
|
# LOKI_SANDBOX_MODE - Run in sandboxed container (default: false, requires Docker)
|
|
43
|
-
# LOKI_ALLOWED_PATHS -
|
|
43
|
+
# LOKI_ALLOWED_PATHS - Reserved: comma-separated path allowlist. NOT YET
|
|
44
|
+
# ENFORCED by the runtime (the value is read from
|
|
45
|
+
# config but no path-restriction check consumes it).
|
|
46
|
+
# Do not rely on it as a security control. Agent file
|
|
47
|
+
# writes are constrained by the OS user + container
|
|
48
|
+
# sandbox (LOKI_SANDBOX_MODE), not by this var.
|
|
44
49
|
# LOKI_BLOCKED_COMMANDS - Comma-separated blocked shell commands (default: rm -rf /)
|
|
45
50
|
#
|
|
46
51
|
# OIDC / SSO Authentication (optional, works alongside token auth):
|
|
@@ -588,7 +593,7 @@ STAGED_AUTONOMY=${LOKI_STAGED_AUTONOMY:-false} # Require plan approval
|
|
|
588
593
|
AUDIT_LOG_ENABLED=${LOKI_AUDIT_LOG:-true} # Enable audit logging (on by default)
|
|
589
594
|
MAX_PARALLEL_AGENTS=${LOKI_MAX_PARALLEL_AGENTS:-10} # Limit concurrent agents
|
|
590
595
|
SANDBOX_MODE=${LOKI_SANDBOX_MODE:-false} # Docker sandbox mode (informational; the real dispatch reads LOKI_SANDBOX_MODE at autonomy/loki:1965 and execs sandbox.sh -- this var is not consumed in run.sh)
|
|
591
|
-
ALLOWED_PATHS=${LOKI_ALLOWED_PATHS:-""} #
|
|
596
|
+
ALLOWED_PATHS=${LOKI_ALLOWED_PATHS:-""} # Reserved, NOT enforced (see header note). No runtime path check reads this; kept only so config plumbing + a future enforcement wiring have a single var to bind. Not a security control today.
|
|
592
597
|
BLOCKED_COMMANDS=${LOKI_BLOCKED_COMMANDS:-"rm -rf /,dd if=,mkfs,:(){ :|:& };:"}
|
|
593
598
|
|
|
594
599
|
# Process Supervision (opt-in)
|
|
@@ -1902,41 +1907,7 @@ detect_complexity() {
|
|
|
1902
1907
|
}
|
|
1903
1908
|
|
|
1904
1909
|
# Get phases based on complexity tier
|
|
1905
|
-
get_complexity_phases() {
|
|
1906
|
-
case "$DETECTED_COMPLEXITY" in
|
|
1907
|
-
simple)
|
|
1908
|
-
echo "3"
|
|
1909
|
-
;;
|
|
1910
|
-
standard)
|
|
1911
|
-
echo "6"
|
|
1912
|
-
;;
|
|
1913
|
-
complex)
|
|
1914
|
-
echo "8"
|
|
1915
|
-
;;
|
|
1916
|
-
*)
|
|
1917
|
-
echo "6" # Default to standard
|
|
1918
|
-
;;
|
|
1919
|
-
esac
|
|
1920
|
-
}
|
|
1921
|
-
|
|
1922
1910
|
# Get phase names based on complexity tier
|
|
1923
|
-
get_phase_names() {
|
|
1924
|
-
case "$DETECTED_COMPLEXITY" in
|
|
1925
|
-
simple)
|
|
1926
|
-
echo "IMPLEMENT TEST DEPLOY"
|
|
1927
|
-
;;
|
|
1928
|
-
standard)
|
|
1929
|
-
echo "RESEARCH DESIGN IMPLEMENT TEST REVIEW DEPLOY"
|
|
1930
|
-
;;
|
|
1931
|
-
complex)
|
|
1932
|
-
echo "RESEARCH ARCHITECTURE DESIGN IMPLEMENT TEST REVIEW SECURITY DEPLOY"
|
|
1933
|
-
;;
|
|
1934
|
-
*)
|
|
1935
|
-
echo "RESEARCH DESIGN IMPLEMENT TEST REVIEW DEPLOY"
|
|
1936
|
-
;;
|
|
1937
|
-
esac
|
|
1938
|
-
}
|
|
1939
|
-
|
|
1940
1911
|
#===============================================================================
|
|
1941
1912
|
# Dynamic Tier Selection (RARV-aware model routing)
|
|
1942
1913
|
#===============================================================================
|
|
@@ -2629,32 +2600,6 @@ send_notification() {
|
|
|
2629
2600
|
return 0
|
|
2630
2601
|
}
|
|
2631
2602
|
|
|
2632
|
-
# Convenience notification functions
|
|
2633
|
-
notify_task_started() {
|
|
2634
|
-
local task_name="$1"
|
|
2635
|
-
send_notification "Task Started" "$task_name" "low"
|
|
2636
|
-
}
|
|
2637
|
-
|
|
2638
|
-
notify_task_completed() {
|
|
2639
|
-
local task_name="$1"
|
|
2640
|
-
send_notification "Task Completed" "$task_name" "normal"
|
|
2641
|
-
}
|
|
2642
|
-
|
|
2643
|
-
notify_task_failed() {
|
|
2644
|
-
local task_name="$1"
|
|
2645
|
-
local error="${2:-Unknown error}"
|
|
2646
|
-
send_notification "Task Failed" "$task_name: $error" "critical"
|
|
2647
|
-
}
|
|
2648
|
-
|
|
2649
|
-
notify_phase_complete() {
|
|
2650
|
-
local phase_name="$1"
|
|
2651
|
-
send_notification "Phase Complete" "$phase_name" "normal"
|
|
2652
|
-
}
|
|
2653
|
-
|
|
2654
|
-
notify_all_complete() {
|
|
2655
|
-
send_notification "All Tasks Complete" "Loki Mode has finished all tasks" "normal"
|
|
2656
|
-
}
|
|
2657
|
-
|
|
2658
2603
|
notify_intervention_needed() {
|
|
2659
2604
|
local reason="$1"
|
|
2660
2605
|
# Delegate-then-notify: this helper ONLY fires the (gated) desktop ping. It
|
|
@@ -3722,23 +3667,6 @@ init_parallel_streams() {
|
|
|
3722
3667
|
}
|
|
3723
3668
|
|
|
3724
3669
|
# Spawn feature worktree from task
|
|
3725
|
-
spawn_feature_stream() {
|
|
3726
|
-
local feature_name="$1"
|
|
3727
|
-
local task_description="$2"
|
|
3728
|
-
|
|
3729
|
-
# Check worktree limit
|
|
3730
|
-
# BUG-PAR-012: Worktree count subtracts 1 for main (git worktree list includes main)
|
|
3731
|
-
local worktree_count_raw=$(git -C "$TARGET_DIR" worktree list 2>/dev/null | wc -l)
|
|
3732
|
-
local worktree_count=$((worktree_count_raw > 0 ? worktree_count_raw - 1 : 0))
|
|
3733
|
-
if [ "$worktree_count" -ge "$MAX_WORKTREES" ]; then
|
|
3734
|
-
log_warn "Max worktrees reached ($MAX_WORKTREES). Queuing feature: $feature_name"
|
|
3735
|
-
return 1
|
|
3736
|
-
fi
|
|
3737
|
-
|
|
3738
|
-
create_worktree "feature-$feature_name" "feature/$feature_name"
|
|
3739
|
-
spawn_worktree_session "feature-$feature_name" "$task_description"
|
|
3740
|
-
}
|
|
3741
|
-
|
|
3742
3670
|
# Cleanup all worktrees on exit
|
|
3743
3671
|
cleanup_parallel_streams() {
|
|
3744
3672
|
log_header "Cleaning Up Parallel Streams"
|
|
@@ -4352,55 +4280,6 @@ EOF
|
|
|
4352
4280
|
LAST_KNOWN_PHASE=""
|
|
4353
4281
|
|
|
4354
4282
|
# Set the current phase and emit event if changed
|
|
4355
|
-
set_phase() {
|
|
4356
|
-
local new_phase="$1"
|
|
4357
|
-
local orch_file=".loki/state/orchestrator.json"
|
|
4358
|
-
|
|
4359
|
-
mkdir -p .loki/state
|
|
4360
|
-
|
|
4361
|
-
# Get current phase
|
|
4362
|
-
local current_phase=""
|
|
4363
|
-
if [ -f "$orch_file" ]; then
|
|
4364
|
-
current_phase=$(python3 -c "import json; print(json.load(open('$orch_file')).get('currentPhase', ''))" 2>/dev/null || echo "")
|
|
4365
|
-
fi
|
|
4366
|
-
|
|
4367
|
-
# Only emit event if phase changed
|
|
4368
|
-
if [ "$new_phase" != "$current_phase" ]; then
|
|
4369
|
-
emit_event_json "phase_change" \
|
|
4370
|
-
"from=$current_phase" \
|
|
4371
|
-
"to=$new_phase" \
|
|
4372
|
-
"iteration=$ITERATION_COUNT"
|
|
4373
|
-
|
|
4374
|
-
log_info "Phase changed: $current_phase -> $new_phase"
|
|
4375
|
-
|
|
4376
|
-
# Update orchestrator state (atomic via temp file + mv)
|
|
4377
|
-
# BUG ARCH-001 fix: prevent state corruption if process is killed mid-write
|
|
4378
|
-
if [ -f "$orch_file" ]; then
|
|
4379
|
-
python3 -c "
|
|
4380
|
-
import json, sys, os, tempfile
|
|
4381
|
-
orch_file = sys.argv[1]
|
|
4382
|
-
new_phase = sys.argv[2]
|
|
4383
|
-
with open(orch_file, 'r') as f:
|
|
4384
|
-
data = json.load(f)
|
|
4385
|
-
data['currentPhase'] = new_phase
|
|
4386
|
-
orch_dir = os.path.dirname(orch_file)
|
|
4387
|
-
fd, tmp = tempfile.mkstemp(dir=orch_dir, suffix='.json')
|
|
4388
|
-
with os.fdopen(fd, 'w') as f:
|
|
4389
|
-
json.dump(data, f, indent=2)
|
|
4390
|
-
os.replace(tmp, orch_file)
|
|
4391
|
-
" "$orch_file" "$new_phase" 2>/dev/null || true
|
|
4392
|
-
fi
|
|
4393
|
-
fi
|
|
4394
|
-
|
|
4395
|
-
LAST_KNOWN_PHASE="$new_phase"
|
|
4396
|
-
|
|
4397
|
-
# v7.5.12: Append a structured log entry to the active iteration task so
|
|
4398
|
-
# the dashboard shows per-phase progress (REASON / ACT / REFLECT / VERIFY).
|
|
4399
|
-
# No-op if no iteration is active or queue file is missing/corrupt.
|
|
4400
|
-
append_iteration_task_log "${ITERATION_COUNT:-0}" "$new_phase" "info" \
|
|
4401
|
-
"Phase entered: $new_phase" 2>/dev/null || true
|
|
4402
|
-
}
|
|
4403
|
-
|
|
4404
4283
|
# v7.5.12: append a log entry to the iteration-N task in in-progress.json.
|
|
4405
4284
|
# Args: iteration, phase, level, message. All silent on failure -- this
|
|
4406
4285
|
# must NEVER kill the run.
|
|
@@ -5729,438 +5608,6 @@ stop_status_monitor() {
|
|
|
5729
5608
|
# Web Dashboard
|
|
5730
5609
|
#===============================================================================
|
|
5731
5610
|
|
|
5732
|
-
generate_dashboard() {
|
|
5733
|
-
# Copy dashboard from skill installation (v4.0.0 with Anthropic design language)
|
|
5734
|
-
local skill_dashboard="$SCRIPT_DIR/.loki/dashboard/index.html"
|
|
5735
|
-
local project_name=$(basename "$(pwd)")
|
|
5736
|
-
local project_path=$(pwd)
|
|
5737
|
-
|
|
5738
|
-
if [ -f "$skill_dashboard" ]; then
|
|
5739
|
-
# v7.5.8: Escape sed-special chars in project_name/project_path before
|
|
5740
|
-
# interpolating into the substitution RHS. Without this, a project
|
|
5741
|
-
# whose path contains '|' (the chosen sed delimiter), '&' (RHS
|
|
5742
|
-
# backref), '\' or '/' would either break the substitution or allow
|
|
5743
|
-
# attacker-controlled text to be smuggled into the served HTML.
|
|
5744
|
-
local project_name_sed project_path_sed
|
|
5745
|
-
project_name_sed=$(printf '%s' "$project_name" | sed -e 's/[\\&|/]/\\&/g')
|
|
5746
|
-
project_path_sed=$(printf '%s' "$project_path" | sed -e 's/[\\&|/]/\\&/g')
|
|
5747
|
-
|
|
5748
|
-
# Copy and inject project info
|
|
5749
|
-
sed -e "s|Loki Mode</title>|Loki Mode - ${project_name_sed}</title>|g" \
|
|
5750
|
-
-e "s|<div class=\"project-name\" id=\"project-name\">--|<div class=\"project-name\" id=\"project-name\">${project_name_sed}|g" \
|
|
5751
|
-
-e "s|<div class=\"project-path\" id=\"project-path\" title=\"\">--|<div class=\"project-path\" id=\"project-path\" title=\"${project_path_sed}\">${project_path_sed}|g" \
|
|
5752
|
-
"$skill_dashboard" > .loki/dashboard/index.html
|
|
5753
|
-
log_info "Dashboard copied from skill installation"
|
|
5754
|
-
log_info "Project: $project_name ($project_path)"
|
|
5755
|
-
return
|
|
5756
|
-
fi
|
|
5757
|
-
|
|
5758
|
-
# Fallback: Generate basic dashboard if external file not found
|
|
5759
|
-
cat > .loki/dashboard/index.html << 'DASHBOARD_HTML'
|
|
5760
|
-
<!DOCTYPE html>
|
|
5761
|
-
<html lang="en">
|
|
5762
|
-
<head>
|
|
5763
|
-
<meta charset="UTF-8">
|
|
5764
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
5765
|
-
<title>Loki Mode Dashboard</title>
|
|
5766
|
-
<style>
|
|
5767
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
5768
|
-
body {
|
|
5769
|
-
font-family: 'Söhne', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
5770
|
-
background: #FAF9F6;
|
|
5771
|
-
color: #1A1A1A;
|
|
5772
|
-
padding: 24px;
|
|
5773
|
-
min-height: 100vh;
|
|
5774
|
-
}
|
|
5775
|
-
.header {
|
|
5776
|
-
text-align: center;
|
|
5777
|
-
padding: 32px 20px;
|
|
5778
|
-
margin-bottom: 32px;
|
|
5779
|
-
}
|
|
5780
|
-
.header h1 {
|
|
5781
|
-
color: #D97757;
|
|
5782
|
-
font-size: 28px;
|
|
5783
|
-
font-weight: 600;
|
|
5784
|
-
letter-spacing: -0.5px;
|
|
5785
|
-
margin-bottom: 8px;
|
|
5786
|
-
}
|
|
5787
|
-
.header .subtitle {
|
|
5788
|
-
color: #666;
|
|
5789
|
-
font-size: 14px;
|
|
5790
|
-
font-weight: 400;
|
|
5791
|
-
}
|
|
5792
|
-
.header .phase {
|
|
5793
|
-
display: inline-block;
|
|
5794
|
-
margin-top: 16px;
|
|
5795
|
-
padding: 8px 16px;
|
|
5796
|
-
background: #FFF;
|
|
5797
|
-
border: 1px solid #E5E3DE;
|
|
5798
|
-
border-radius: 20px;
|
|
5799
|
-
font-size: 13px;
|
|
5800
|
-
color: #1A1A1A;
|
|
5801
|
-
font-weight: 500;
|
|
5802
|
-
}
|
|
5803
|
-
.stats {
|
|
5804
|
-
display: flex;
|
|
5805
|
-
justify-content: center;
|
|
5806
|
-
gap: 16px;
|
|
5807
|
-
margin-bottom: 40px;
|
|
5808
|
-
flex-wrap: wrap;
|
|
5809
|
-
}
|
|
5810
|
-
.stat {
|
|
5811
|
-
background: #FFF;
|
|
5812
|
-
border: 1px solid #E5E3DE;
|
|
5813
|
-
border-radius: 12px;
|
|
5814
|
-
padding: 20px 32px;
|
|
5815
|
-
text-align: center;
|
|
5816
|
-
min-width: 140px;
|
|
5817
|
-
transition: box-shadow 0.2s ease;
|
|
5818
|
-
}
|
|
5819
|
-
.stat:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.06); }
|
|
5820
|
-
.stat .number { font-size: 36px; font-weight: 600; margin-bottom: 4px; }
|
|
5821
|
-
.stat .label { font-size: 12px; color: #888; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
5822
|
-
.stat.pending .number { color: #D97757; }
|
|
5823
|
-
.stat.progress .number { color: #5B8DEF; }
|
|
5824
|
-
.stat.completed .number { color: #2E9E6E; }
|
|
5825
|
-
.stat.failed .number { color: #D44F4F; }
|
|
5826
|
-
.stat.agents .number { color: #9B6DD6; }
|
|
5827
|
-
.section-header {
|
|
5828
|
-
text-align: center;
|
|
5829
|
-
font-size: 16px;
|
|
5830
|
-
font-weight: 600;
|
|
5831
|
-
color: #666;
|
|
5832
|
-
margin: 40px 0 20px 0;
|
|
5833
|
-
text-transform: uppercase;
|
|
5834
|
-
letter-spacing: 1px;
|
|
5835
|
-
}
|
|
5836
|
-
.agents-grid {
|
|
5837
|
-
display: grid;
|
|
5838
|
-
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
5839
|
-
gap: 16px;
|
|
5840
|
-
max-width: 1400px;
|
|
5841
|
-
margin: 0 auto 40px auto;
|
|
5842
|
-
}
|
|
5843
|
-
.agent-card {
|
|
5844
|
-
background: #FFF;
|
|
5845
|
-
border: 1px solid #E5E3DE;
|
|
5846
|
-
border-radius: 12px;
|
|
5847
|
-
padding: 16px;
|
|
5848
|
-
transition: box-shadow 0.2s ease, border-color 0.2s ease;
|
|
5849
|
-
}
|
|
5850
|
-
.agent-card:hover {
|
|
5851
|
-
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
5852
|
-
border-color: #9B6DD6;
|
|
5853
|
-
}
|
|
5854
|
-
.agent-card .agent-header {
|
|
5855
|
-
display: flex;
|
|
5856
|
-
justify-content: space-between;
|
|
5857
|
-
align-items: flex-start;
|
|
5858
|
-
margin-bottom: 12px;
|
|
5859
|
-
}
|
|
5860
|
-
.agent-card .agent-id {
|
|
5861
|
-
font-size: 11px;
|
|
5862
|
-
color: #999;
|
|
5863
|
-
font-family: monospace;
|
|
5864
|
-
}
|
|
5865
|
-
.agent-card .model-badge {
|
|
5866
|
-
padding: 4px 10px;
|
|
5867
|
-
border-radius: 6px;
|
|
5868
|
-
font-size: 10px;
|
|
5869
|
-
font-weight: 600;
|
|
5870
|
-
text-transform: uppercase;
|
|
5871
|
-
letter-spacing: 0.5px;
|
|
5872
|
-
}
|
|
5873
|
-
.agent-card .model-badge.sonnet {
|
|
5874
|
-
background: #E8F0FD;
|
|
5875
|
-
color: #5B8DEF;
|
|
5876
|
-
}
|
|
5877
|
-
.agent-card .model-badge.haiku {
|
|
5878
|
-
background: #FFF4E6;
|
|
5879
|
-
color: #F59E0B;
|
|
5880
|
-
}
|
|
5881
|
-
.agent-card .model-badge.opus {
|
|
5882
|
-
background: #F3E8FF;
|
|
5883
|
-
color: #9B6DD6;
|
|
5884
|
-
}
|
|
5885
|
-
.agent-card .agent-type {
|
|
5886
|
-
font-size: 14px;
|
|
5887
|
-
font-weight: 600;
|
|
5888
|
-
color: #1A1A1A;
|
|
5889
|
-
margin-bottom: 8px;
|
|
5890
|
-
}
|
|
5891
|
-
.agent-card .agent-status {
|
|
5892
|
-
display: inline-block;
|
|
5893
|
-
padding: 3px 8px;
|
|
5894
|
-
border-radius: 4px;
|
|
5895
|
-
font-size: 10px;
|
|
5896
|
-
font-weight: 500;
|
|
5897
|
-
margin-bottom: 12px;
|
|
5898
|
-
}
|
|
5899
|
-
.agent-card .agent-status.active {
|
|
5900
|
-
background: #E6F5EE;
|
|
5901
|
-
color: #2E9E6E;
|
|
5902
|
-
}
|
|
5903
|
-
.agent-card .agent-status.completed {
|
|
5904
|
-
background: #F0EFEA;
|
|
5905
|
-
color: #666;
|
|
5906
|
-
}
|
|
5907
|
-
.agent-card .agent-work {
|
|
5908
|
-
font-size: 12px;
|
|
5909
|
-
color: #666;
|
|
5910
|
-
line-height: 1.5;
|
|
5911
|
-
margin-bottom: 8px;
|
|
5912
|
-
}
|
|
5913
|
-
.agent-card .agent-meta {
|
|
5914
|
-
display: flex;
|
|
5915
|
-
gap: 12px;
|
|
5916
|
-
font-size: 11px;
|
|
5917
|
-
color: #999;
|
|
5918
|
-
margin-top: 8px;
|
|
5919
|
-
padding-top: 8px;
|
|
5920
|
-
border-top: 1px solid #F0EFEA;
|
|
5921
|
-
}
|
|
5922
|
-
.agent-card .agent-meta span {
|
|
5923
|
-
display: flex;
|
|
5924
|
-
align-items: center;
|
|
5925
|
-
gap: 4px;
|
|
5926
|
-
}
|
|
5927
|
-
.columns {
|
|
5928
|
-
display: flex;
|
|
5929
|
-
gap: 20px;
|
|
5930
|
-
overflow-x: auto;
|
|
5931
|
-
padding-bottom: 24px;
|
|
5932
|
-
max-width: 1400px;
|
|
5933
|
-
margin: 0 auto;
|
|
5934
|
-
}
|
|
5935
|
-
.column {
|
|
5936
|
-
flex: 1;
|
|
5937
|
-
min-width: 300px;
|
|
5938
|
-
max-width: 350px;
|
|
5939
|
-
background: #FFF;
|
|
5940
|
-
border: 1px solid #E5E3DE;
|
|
5941
|
-
border-radius: 12px;
|
|
5942
|
-
padding: 20px;
|
|
5943
|
-
}
|
|
5944
|
-
.column h2 {
|
|
5945
|
-
font-size: 13px;
|
|
5946
|
-
font-weight: 600;
|
|
5947
|
-
color: #666;
|
|
5948
|
-
margin-bottom: 16px;
|
|
5949
|
-
display: flex;
|
|
5950
|
-
align-items: center;
|
|
5951
|
-
gap: 10px;
|
|
5952
|
-
text-transform: uppercase;
|
|
5953
|
-
letter-spacing: 0.5px;
|
|
5954
|
-
}
|
|
5955
|
-
.column h2 .count {
|
|
5956
|
-
background: #F0EFEA;
|
|
5957
|
-
padding: 3px 10px;
|
|
5958
|
-
border-radius: 12px;
|
|
5959
|
-
font-size: 11px;
|
|
5960
|
-
color: #1A1A1A;
|
|
5961
|
-
}
|
|
5962
|
-
.column.pending h2 .count { background: #FCEEE8; color: #D97757; }
|
|
5963
|
-
.column.progress h2 .count { background: #E8F0FD; color: #5B8DEF; }
|
|
5964
|
-
.column.completed h2 .count { background: #E6F5EE; color: #2E9E6E; }
|
|
5965
|
-
.column.failed h2 .count { background: #FCE8E8; color: #D44F4F; }
|
|
5966
|
-
.task {
|
|
5967
|
-
background: #FAF9F6;
|
|
5968
|
-
border: 1px solid #E5E3DE;
|
|
5969
|
-
border-radius: 8px;
|
|
5970
|
-
padding: 14px;
|
|
5971
|
-
margin-bottom: 12px;
|
|
5972
|
-
transition: border-color 0.2s ease;
|
|
5973
|
-
}
|
|
5974
|
-
.task:hover { border-color: #D97757; }
|
|
5975
|
-
.task .id { font-size: 10px; color: #999; margin-bottom: 6px; font-family: monospace; }
|
|
5976
|
-
.task .type {
|
|
5977
|
-
display: inline-block;
|
|
5978
|
-
background: #FCEEE8;
|
|
5979
|
-
color: #D97757;
|
|
5980
|
-
padding: 3px 10px;
|
|
5981
|
-
border-radius: 4px;
|
|
5982
|
-
font-size: 11px;
|
|
5983
|
-
font-weight: 500;
|
|
5984
|
-
margin-bottom: 8px;
|
|
5985
|
-
}
|
|
5986
|
-
.task .title { font-size: 13px; color: #1A1A1A; line-height: 1.5; }
|
|
5987
|
-
.task .error {
|
|
5988
|
-
font-size: 11px;
|
|
5989
|
-
color: #D44F4F;
|
|
5990
|
-
margin-top: 10px;
|
|
5991
|
-
padding: 10px;
|
|
5992
|
-
background: #FCE8E8;
|
|
5993
|
-
border-radius: 6px;
|
|
5994
|
-
font-family: monospace;
|
|
5995
|
-
}
|
|
5996
|
-
.refresh {
|
|
5997
|
-
position: fixed;
|
|
5998
|
-
bottom: 24px;
|
|
5999
|
-
right: 24px;
|
|
6000
|
-
background: #D97757;
|
|
6001
|
-
color: white;
|
|
6002
|
-
border: none;
|
|
6003
|
-
padding: 12px 24px;
|
|
6004
|
-
border-radius: 8px;
|
|
6005
|
-
cursor: pointer;
|
|
6006
|
-
font-size: 14px;
|
|
6007
|
-
font-weight: 500;
|
|
6008
|
-
transition: background 0.2s ease;
|
|
6009
|
-
box-shadow: 0 4px 12px rgba(217, 119, 87, 0.3);
|
|
6010
|
-
}
|
|
6011
|
-
.refresh:hover { background: #C56747; }
|
|
6012
|
-
.updated {
|
|
6013
|
-
text-align: center;
|
|
6014
|
-
color: #999;
|
|
6015
|
-
font-size: 12px;
|
|
6016
|
-
margin-top: 24px;
|
|
6017
|
-
}
|
|
6018
|
-
.empty {
|
|
6019
|
-
color: #999;
|
|
6020
|
-
font-size: 13px;
|
|
6021
|
-
text-align: center;
|
|
6022
|
-
padding: 24px;
|
|
6023
|
-
font-style: italic;
|
|
6024
|
-
}
|
|
6025
|
-
.powered-by {
|
|
6026
|
-
text-align: center;
|
|
6027
|
-
margin-top: 40px;
|
|
6028
|
-
padding-top: 24px;
|
|
6029
|
-
border-top: 1px solid #E5E3DE;
|
|
6030
|
-
color: #999;
|
|
6031
|
-
font-size: 12px;
|
|
6032
|
-
}
|
|
6033
|
-
.powered-by span { color: #D97757; font-weight: 500; }
|
|
6034
|
-
</style>
|
|
6035
|
-
</head>
|
|
6036
|
-
<body>
|
|
6037
|
-
<div class="header">
|
|
6038
|
-
<h1>LOKI MODE</h1>
|
|
6039
|
-
<div class="subtitle">Autonomous Spec-to-Product System</div>
|
|
6040
|
-
<div class="phase" id="phase">Loading...</div>
|
|
6041
|
-
</div>
|
|
6042
|
-
<div class="stats">
|
|
6043
|
-
<div class="stat agents"><div class="number" id="agents-count">-</div><div class="label">Active Agents</div></div>
|
|
6044
|
-
<div class="stat pending"><div class="number" id="pending-count">-</div><div class="label">Pending</div></div>
|
|
6045
|
-
<div class="stat progress"><div class="number" id="progress-count">-</div><div class="label">In Progress</div></div>
|
|
6046
|
-
<div class="stat completed"><div class="number" id="completed-count">-</div><div class="label">Completed</div></div>
|
|
6047
|
-
<div class="stat failed"><div class="number" id="failed-count">-</div><div class="label">Failed</div></div>
|
|
6048
|
-
</div>
|
|
6049
|
-
<div class="section-header">Active Agents</div>
|
|
6050
|
-
<div class="agents-grid" id="agents-grid"></div>
|
|
6051
|
-
<div class="section-header">Task Queue</div>
|
|
6052
|
-
<div class="columns">
|
|
6053
|
-
<div class="column pending"><h2>Pending <span class="count" id="pending-badge">0</span></h2><div id="pending-tasks"></div></div>
|
|
6054
|
-
<div class="column progress"><h2>In Progress <span class="count" id="progress-badge">0</span></h2><div id="progress-tasks"></div></div>
|
|
6055
|
-
<div class="column completed"><h2>Completed <span class="count" id="completed-badge">0</span></h2><div id="completed-tasks"></div></div>
|
|
6056
|
-
<div class="column failed"><h2>Failed <span class="count" id="failed-badge">0</span></h2><div id="failed-tasks"></div></div>
|
|
6057
|
-
</div>
|
|
6058
|
-
<div class="updated" id="updated">Last updated: -</div>
|
|
6059
|
-
<div class="powered-by">Powered by <span>${PROVIDER_DISPLAY_NAME:-Claude}</span></div>
|
|
6060
|
-
<button class="refresh" onclick="loadData()">Refresh</button>
|
|
6061
|
-
<script>
|
|
6062
|
-
async function loadJSON(path) {
|
|
6063
|
-
try {
|
|
6064
|
-
const res = await fetch(path + '?t=' + Date.now());
|
|
6065
|
-
if (!res.ok) return [];
|
|
6066
|
-
const text = await res.text();
|
|
6067
|
-
if (!text.trim()) return [];
|
|
6068
|
-
const data = JSON.parse(text);
|
|
6069
|
-
return Array.isArray(data) ? data : (data.tasks || data.agents || []);
|
|
6070
|
-
} catch { return []; }
|
|
6071
|
-
}
|
|
6072
|
-
function getModelClass(model) {
|
|
6073
|
-
if (!model) return 'sonnet';
|
|
6074
|
-
const m = model.toLowerCase();
|
|
6075
|
-
if (m.includes('haiku')) return 'haiku';
|
|
6076
|
-
if (m.includes('opus')) return 'opus';
|
|
6077
|
-
return 'sonnet';
|
|
6078
|
-
}
|
|
6079
|
-
function formatDuration(isoDate) {
|
|
6080
|
-
if (!isoDate) return 'Unknown';
|
|
6081
|
-
const start = new Date(isoDate);
|
|
6082
|
-
const now = new Date();
|
|
6083
|
-
const seconds = Math.floor((now - start) / 1000);
|
|
6084
|
-
if (seconds < 60) return seconds + 's';
|
|
6085
|
-
if (seconds < 3600) return Math.floor(seconds / 60) + 'm';
|
|
6086
|
-
return Math.floor(seconds / 3600) + 'h ' + Math.floor((seconds % 3600) / 60) + 'm';
|
|
6087
|
-
}
|
|
6088
|
-
function renderAgent(agent) {
|
|
6089
|
-
const modelClass = getModelClass(agent.model);
|
|
6090
|
-
const modelName = agent.model || 'Sonnet 4.5';
|
|
6091
|
-
const agentType = agent.agent_type || 'general-purpose';
|
|
6092
|
-
const status = agent.status === 'completed' ? 'completed' : 'active';
|
|
6093
|
-
const currentTask = agent.current_task || (agent.tasks_completed && agent.tasks_completed.length > 0
|
|
6094
|
-
? 'Completed: ' + agent.tasks_completed.join(', ')
|
|
6095
|
-
: 'Initializing...');
|
|
6096
|
-
const duration = formatDuration(agent.spawned_at);
|
|
6097
|
-
const tasksCount = agent.tasks_completed ? agent.tasks_completed.length : 0;
|
|
6098
|
-
|
|
6099
|
-
return `
|
|
6100
|
-
<div class="agent-card">
|
|
6101
|
-
<div class="agent-header">
|
|
6102
|
-
<div class="agent-id">${agent.agent_id || 'Unknown'}</div>
|
|
6103
|
-
<div class="model-badge ${modelClass}">${modelName}</div>
|
|
6104
|
-
</div>
|
|
6105
|
-
<div class="agent-type">${agentType}</div>
|
|
6106
|
-
<div class="agent-status ${status}">${status}</div>
|
|
6107
|
-
<div class="agent-work">${currentTask}</div>
|
|
6108
|
-
<div class="agent-meta">
|
|
6109
|
-
<span>${duration}</span>
|
|
6110
|
-
<span>${tasksCount} tasks</span>
|
|
6111
|
-
</div>
|
|
6112
|
-
</div>
|
|
6113
|
-
`;
|
|
6114
|
-
}
|
|
6115
|
-
function renderTask(task) {
|
|
6116
|
-
const payload = task.payload || {};
|
|
6117
|
-
const title = payload.description || payload.action || task.type || 'Task';
|
|
6118
|
-
const error = task.lastError ? `<div class="error">${task.lastError}</div>` : '';
|
|
6119
|
-
return `<div class="task"><div class="id">${task.id}</div><span class="type">${task.type || 'general'}</span><div class="title">${title}</div>${error}</div>`;
|
|
6120
|
-
}
|
|
6121
|
-
async function loadData() {
|
|
6122
|
-
const [pending, progress, completed, failed, agents] = await Promise.all([
|
|
6123
|
-
loadJSON('../queue/pending.json'),
|
|
6124
|
-
loadJSON('../queue/in-progress.json'),
|
|
6125
|
-
loadJSON('../queue/completed.json'),
|
|
6126
|
-
loadJSON('../queue/failed.json'),
|
|
6127
|
-
loadJSON('../state/agents.json')
|
|
6128
|
-
]);
|
|
6129
|
-
|
|
6130
|
-
// Agent stats
|
|
6131
|
-
document.getElementById('agents-count').textContent = agents.length;
|
|
6132
|
-
document.getElementById('agents-grid').innerHTML = agents.length
|
|
6133
|
-
? agents.map(renderAgent).join('')
|
|
6134
|
-
: '<div class="empty">No active agents</div>';
|
|
6135
|
-
|
|
6136
|
-
// Task stats
|
|
6137
|
-
document.getElementById('pending-count').textContent = pending.length;
|
|
6138
|
-
document.getElementById('progress-count').textContent = progress.length;
|
|
6139
|
-
document.getElementById('completed-count').textContent = completed.length;
|
|
6140
|
-
document.getElementById('failed-count').textContent = failed.length;
|
|
6141
|
-
document.getElementById('pending-badge').textContent = pending.length;
|
|
6142
|
-
document.getElementById('progress-badge').textContent = progress.length;
|
|
6143
|
-
document.getElementById('completed-badge').textContent = completed.length;
|
|
6144
|
-
document.getElementById('failed-badge').textContent = failed.length;
|
|
6145
|
-
document.getElementById('pending-tasks').innerHTML = pending.length ? pending.map(renderTask).join('') : '<div class="empty">No pending tasks</div>';
|
|
6146
|
-
document.getElementById('progress-tasks').innerHTML = progress.length ? progress.map(renderTask).join('') : '<div class="empty">No tasks in progress</div>';
|
|
6147
|
-
document.getElementById('completed-tasks').innerHTML = completed.length ? completed.slice(-10).reverse().map(renderTask).join('') : '<div class="empty">No completed tasks</div>';
|
|
6148
|
-
document.getElementById('failed-tasks').innerHTML = failed.length ? failed.map(renderTask).join('') : '<div class="empty">No failed tasks</div>';
|
|
6149
|
-
|
|
6150
|
-
try {
|
|
6151
|
-
const state = await fetch('../state/orchestrator.json?t=' + Date.now()).then(r => r.json());
|
|
6152
|
-
document.getElementById('phase').textContent = 'Phase: ' + (state.currentPhase || 'UNKNOWN');
|
|
6153
|
-
} catch { document.getElementById('phase').textContent = 'Phase: UNKNOWN'; }
|
|
6154
|
-
document.getElementById('updated').textContent = 'Last updated: ' + new Date().toLocaleTimeString();
|
|
6155
|
-
}
|
|
6156
|
-
loadData();
|
|
6157
|
-
setInterval(loadData, 3000);
|
|
6158
|
-
</script>
|
|
6159
|
-
</body>
|
|
6160
|
-
</html>
|
|
6161
|
-
DASHBOARD_HTML
|
|
6162
|
-
}
|
|
6163
|
-
|
|
6164
5611
|
update_agents_state() {
|
|
6165
5612
|
# Aggregate agent information from .agent/sub-agents/*.json into .loki/state/agents.json
|
|
6166
5613
|
local agents_dir=".agent/sub-agents"
|
|
@@ -6853,34 +6300,6 @@ init_learnings_db() {
|
|
|
6853
6300
|
log_info "Learnings database initialized at: $learnings_dir"
|
|
6854
6301
|
}
|
|
6855
6302
|
|
|
6856
|
-
save_learning() {
|
|
6857
|
-
# Save a learning to the cross-project database
|
|
6858
|
-
local learning_type="$1" # pattern, mistake, success
|
|
6859
|
-
local category="$2"
|
|
6860
|
-
local description="$3"
|
|
6861
|
-
local project="${4:-$(basename "$(pwd)")}"
|
|
6862
|
-
|
|
6863
|
-
local learnings_dir="${HOME}/.loki/learnings"
|
|
6864
|
-
local target_file="$learnings_dir/${learning_type}s.jsonl"
|
|
6865
|
-
|
|
6866
|
-
if [ ! -d "$learnings_dir" ]; then
|
|
6867
|
-
init_learnings_db
|
|
6868
|
-
fi
|
|
6869
|
-
|
|
6870
|
-
local learning_entry
|
|
6871
|
-
if command -v jq >/dev/null 2>&1; then
|
|
6872
|
-
learning_entry=$(jq -n --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg proj "$project" --arg cat "$category" --arg desc "$description" '{timestamp:$ts,project:$proj,category:$cat,description:$desc}')
|
|
6873
|
-
else
|
|
6874
|
-
local safe_proj safe_cat safe_desc
|
|
6875
|
-
safe_proj=$(printf '%s' "$project" | sed 's/["\\]/\\&/g; s/\n/\\n/g')
|
|
6876
|
-
safe_cat=$(printf '%s' "$category" | sed 's/["\\]/\\&/g; s/\n/\\n/g')
|
|
6877
|
-
safe_desc=$(printf '%s' "$description" | sed 's/["\\]/\\&/g; s/\n/\\n/g')
|
|
6878
|
-
learning_entry="{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"project\":\"$safe_proj\",\"category\":\"$safe_cat\",\"description\":\"$safe_desc\"}"
|
|
6879
|
-
fi
|
|
6880
|
-
echo "$learning_entry" >> "$target_file"
|
|
6881
|
-
log_info "Saved $learning_type: $category"
|
|
6882
|
-
}
|
|
6883
|
-
|
|
6884
6303
|
get_relevant_learnings() {
|
|
6885
6304
|
# Get learnings relevant to the current context
|
|
6886
6305
|
local context="$1"
|
|
@@ -7882,24 +7301,6 @@ with open(gate_file, 'w') as f:
|
|
|
7882
7301
|
" 2>/dev/null || true
|
|
7883
7302
|
}
|
|
7884
7303
|
|
|
7885
|
-
get_gate_failure_count() {
|
|
7886
|
-
local gate_name="$1"
|
|
7887
|
-
local gate_file="${TARGET_DIR:-.}/.loki/quality/gate-failure-count.json"
|
|
7888
|
-
[ -f "$gate_file" ] || { echo "0"; return; }
|
|
7889
|
-
|
|
7890
|
-
_GATE_FILE="$gate_file" _GATE_NAME="$gate_name" python3 -c "
|
|
7891
|
-
import json, os
|
|
7892
|
-
gate_file = os.environ['_GATE_FILE']
|
|
7893
|
-
gate_name = os.environ['_GATE_NAME']
|
|
7894
|
-
try:
|
|
7895
|
-
with open(gate_file) as f:
|
|
7896
|
-
counts = json.load(f)
|
|
7897
|
-
print(counts.get(gate_name, 0))
|
|
7898
|
-
except (json.JSONDecodeError, FileNotFoundError, OSError):
|
|
7899
|
-
print(0)
|
|
7900
|
-
" 2>/dev/null || echo "0"
|
|
7901
|
-
}
|
|
7902
|
-
|
|
7903
7304
|
# ============================================================================
|
|
7904
7305
|
# Hard Quality Gate: Test Coverage (v6.7.0)
|
|
7905
7306
|
# Detects test runner and runs tests with coverage reporting
|
|
@@ -10102,182 +9503,6 @@ DA_AGG_PATCH
|
|
|
10102
9503
|
# Only runs when complexity >= standard (6+ agents).
|
|
10103
9504
|
#===============================================================================
|
|
10104
9505
|
|
|
10105
|
-
run_adversarial_testing() {
|
|
10106
|
-
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
10107
|
-
local adversarial_dir="$loki_dir/quality/adversarial"
|
|
10108
|
-
local test_id
|
|
10109
|
-
test_id="adversarial-$(date -u +%Y%m%dT%H%M%SZ)-${ITERATION_COUNT:-0}"
|
|
10110
|
-
mkdir -p "$adversarial_dir/$test_id"
|
|
10111
|
-
|
|
10112
|
-
# Only run for Standard+ complexity
|
|
10113
|
-
local complexity="${LOKI_COMPLEXITY:-auto}"
|
|
10114
|
-
if [ "$complexity" = "simple" ]; then
|
|
10115
|
-
log_debug "Adversarial testing skipped: simple complexity tier"
|
|
10116
|
-
return 0
|
|
10117
|
-
fi
|
|
10118
|
-
|
|
10119
|
-
# Check if adversarial testing is disabled
|
|
10120
|
-
if [ "${LOKI_ADVERSARIAL_TESTING:-true}" = "false" ]; then
|
|
10121
|
-
log_debug "Adversarial testing disabled via LOKI_ADVERSARIAL_TESTING=false"
|
|
10122
|
-
return 0
|
|
10123
|
-
fi
|
|
10124
|
-
|
|
10125
|
-
log_header "ADVERSARIAL TESTING: $test_id"
|
|
10126
|
-
|
|
10127
|
-
# Get diff for adversarial analysis
|
|
10128
|
-
local diff_content
|
|
10129
|
-
diff_content=$(git -C "${TARGET_DIR:-.}" diff HEAD~1 2>/dev/null || git -C "${TARGET_DIR:-.}" diff --cached 2>/dev/null || echo "")
|
|
10130
|
-
if [ -z "$diff_content" ]; then
|
|
10131
|
-
log_info "Adversarial testing: No diff to test, skipping"
|
|
10132
|
-
return 0
|
|
10133
|
-
fi
|
|
10134
|
-
|
|
10135
|
-
local changed_files
|
|
10136
|
-
changed_files=$(git -C "${TARGET_DIR:-.}" diff --name-only HEAD~1 2>/dev/null || git -C "${TARGET_DIR:-.}" diff --name-only --cached 2>/dev/null || echo "")
|
|
10137
|
-
|
|
10138
|
-
# Write analysis files -- use printf to prevent shell variable expansion (#78)
|
|
10139
|
-
local diff_file="$adversarial_dir/$test_id/diff.txt"
|
|
10140
|
-
local files_file="$adversarial_dir/$test_id/files.txt"
|
|
10141
|
-
printf '%s\n' "$diff_content" > "$diff_file"
|
|
10142
|
-
printf '%s\n' "$changed_files" > "$files_file"
|
|
10143
|
-
|
|
10144
|
-
# Build adversarial prompt -- use heredoc with quoted delimiter to prevent
|
|
10145
|
-
# shell variable expansion in diff content (fixes #78)
|
|
10146
|
-
local files_content changed_content
|
|
10147
|
-
files_content=$(cat "$files_file")
|
|
10148
|
-
changed_content=$(head -500 "$diff_file")
|
|
10149
|
-
local adversarial_prompt
|
|
10150
|
-
read -r -d '' adversarial_prompt <<'ADVERSARIAL_EOF' || true
|
|
10151
|
-
You are an ADVERSARIAL TESTER. Your goal is to BREAK the implementation.
|
|
10152
|
-
|
|
10153
|
-
CHANGED FILES:
|
|
10154
|
-
__FILES_PLACEHOLDER__
|
|
10155
|
-
|
|
10156
|
-
DIFF:
|
|
10157
|
-
__DIFF_PLACEHOLDER__
|
|
10158
|
-
|
|
10159
|
-
YOUR MISSION:
|
|
10160
|
-
1. Find edge cases that will cause crashes or incorrect behavior
|
|
10161
|
-
2. Identify inputs that bypass validation
|
|
10162
|
-
3. Find race conditions or concurrency issues
|
|
10163
|
-
4. Discover security vulnerabilities (injection, auth bypass, SSRF)
|
|
10164
|
-
5. Find resource exhaustion vectors (unbounded loops, memory leaks)
|
|
10165
|
-
6. Identify error handling gaps (missing try/catch, unchecked returns)
|
|
10166
|
-
|
|
10167
|
-
OUTPUT FORMAT (STRICT):
|
|
10168
|
-
ATTACK_VECTORS:
|
|
10169
|
-
- [severity] [category] description | reproduction steps
|
|
10170
|
-
Severity: Critical, High, Medium, Low
|
|
10171
|
-
Category: crash, security, correctness, performance, resource
|
|
10172
|
-
|
|
10173
|
-
SUGGESTED_TESTS:
|
|
10174
|
-
- Test description that would catch this issue
|
|
10175
|
-
|
|
10176
|
-
OVERALL_RISK: HIGH or MEDIUM or LOW
|
|
10177
|
-
ADVERSARIAL_EOF
|
|
10178
|
-
# Substitute placeholders with actual content (safe from shell expansion)
|
|
10179
|
-
adversarial_prompt="${adversarial_prompt/__FILES_PLACEHOLDER__/$files_content}"
|
|
10180
|
-
adversarial_prompt="${adversarial_prompt/__DIFF_PLACEHOLDER__/$changed_content}"
|
|
10181
|
-
|
|
10182
|
-
local result_file="$adversarial_dir/$test_id/result.txt"
|
|
10183
|
-
|
|
10184
|
-
# Run adversarial agent
|
|
10185
|
-
log_info "Spawning adversarial agent..."
|
|
10186
|
-
case "${PROVIDER_NAME:-claude}" in
|
|
10187
|
-
claude)
|
|
10188
|
-
if command -v claude &>/dev/null; then
|
|
10189
|
-
# EMBED 2 + 3 (v7.33.0). Adversarial probe subcall.
|
|
10190
|
-
# $adversarial_prompt is fully self-contained (instructions +
|
|
10191
|
-
# changed files + diff inlined via the heredoc above) and output
|
|
10192
|
-
# is captured to $result_file. So:
|
|
10193
|
-
# EMBED 2 (--bare): no hooks/LSP/CLAUDE.md/MCP needed; cheaper.
|
|
10194
|
-
# Opt out LOKI_BARE_SUBCALLS=0.
|
|
10195
|
-
# EMBED 3 (--disallowedTools): keep an adversarial agent from
|
|
10196
|
-
# casually mutating the tree. Deny Edit/Write/NotebookEdit +
|
|
10197
|
-
# git mutation forms (incl. git -C / --git-dir evasions);
|
|
10198
|
-
# read-only git stays allowed. Guardrail, not a sandbox.
|
|
10199
|
-
# Opt out LOKI_REVIEW_TOOL_GUARD=0.
|
|
10200
|
-
local _adv_argv=("--dangerously-skip-permissions")
|
|
10201
|
-
if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
|
|
10202
|
-
_adv_argv+=("--bare")
|
|
10203
|
-
fi
|
|
10204
|
-
if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
|
|
10205
|
-
_adv_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
|
|
10206
|
-
fi
|
|
10207
|
-
# EMBED 3b (--allowedTools, #167): positive least-privilege
|
|
10208
|
-
# allowlist. DEFAULT OFF (opt-in LOKI_REVIEW_ALLOWLIST=1).
|
|
10209
|
-
# Emitted ALONGSIDE the denylist (deny precedence verified
|
|
10210
|
-
# live, holds under --dangerously-skip-permissions). See
|
|
10211
|
-
# loki_review_allowlist.
|
|
10212
|
-
if type loki_review_allowlist_enabled >/dev/null 2>&1 && loki_review_allowlist_enabled; then
|
|
10213
|
-
_adv_argv+=("--allowedTools" "$(loki_review_allowlist)")
|
|
10214
|
-
fi
|
|
10215
|
-
# caveman HARD-SUPPRESS (parsed output): the adversarial probe's
|
|
10216
|
-
# output is parsed for findings/severity. Disable caveman
|
|
10217
|
-
# unconditionally (CAVEMAN_DEFAULT_MODE=off) so compression cannot
|
|
10218
|
-
# drop or reword a finding. No-op when caveman is absent.
|
|
10219
|
-
CAVEMAN_DEFAULT_MODE=off \
|
|
10220
|
-
claude "${_adv_argv[@]}" -p "$adversarial_prompt" \
|
|
10221
|
-
--output-format text > "$result_file" 2>/dev/null || true
|
|
10222
|
-
fi
|
|
10223
|
-
;;
|
|
10224
|
-
codex)
|
|
10225
|
-
if command -v codex &>/dev/null; then
|
|
10226
|
-
codex exec --sandbox workspace-write --skip-git-repo-check "$adversarial_prompt" \
|
|
10227
|
-
> "$result_file" 2>/dev/null || true
|
|
10228
|
-
fi
|
|
10229
|
-
;;
|
|
10230
|
-
cline)
|
|
10231
|
-
if command -v cline &>/dev/null; then
|
|
10232
|
-
invoke_cline_capture "$adversarial_prompt" \
|
|
10233
|
-
> "$result_file" 2>/dev/null || true
|
|
10234
|
-
fi
|
|
10235
|
-
;;
|
|
10236
|
-
aider)
|
|
10237
|
-
if command -v aider &>/dev/null; then
|
|
10238
|
-
invoke_aider_capture "$adversarial_prompt" \
|
|
10239
|
-
> "$result_file" 2>/dev/null || true
|
|
10240
|
-
fi
|
|
10241
|
-
;;
|
|
10242
|
-
*)
|
|
10243
|
-
echo "ATTACK_VECTORS: None (unknown provider)" > "$result_file"
|
|
10244
|
-
echo "OVERALL_RISK: LOW" >> "$result_file"
|
|
10245
|
-
;;
|
|
10246
|
-
esac
|
|
10247
|
-
|
|
10248
|
-
if [ ! -s "$result_file" ]; then
|
|
10249
|
-
log_warn "Adversarial agent produced no output"
|
|
10250
|
-
return 0
|
|
10251
|
-
fi
|
|
10252
|
-
|
|
10253
|
-
# Parse risk level
|
|
10254
|
-
local risk_level
|
|
10255
|
-
risk_level=$(grep -i "OVERALL_RISK:" "$result_file" | head -1 | sed 's/.*OVERALL_RISK:[[:space:]]*//' | awk '{print toupper($1)}')
|
|
10256
|
-
|
|
10257
|
-
# Count critical/high attack vectors
|
|
10258
|
-
local critical_count high_count
|
|
10259
|
-
critical_count=$(grep -ci "\[critical\]" "$result_file" 2>/dev/null || echo "0")
|
|
10260
|
-
high_count=$(grep -ci "\[high\]" "$result_file" 2>/dev/null || echo "0")
|
|
10261
|
-
|
|
10262
|
-
log_info "Adversarial testing complete: risk=$risk_level, critical=$critical_count, high=$high_count"
|
|
10263
|
-
|
|
10264
|
-
emit_event_json "adversarial_test_complete" \
|
|
10265
|
-
"test_id=$test_id" \
|
|
10266
|
-
"risk_level=${risk_level:-UNKNOWN}" \
|
|
10267
|
-
"critical_count=$critical_count" \
|
|
10268
|
-
"high_count=$high_count" \
|
|
10269
|
-
"iteration=$ITERATION_COUNT"
|
|
10270
|
-
|
|
10271
|
-
# Block on critical findings
|
|
10272
|
-
if [ "$critical_count" -gt 0 ]; then
|
|
10273
|
-
log_error "ADVERSARIAL TEST BLOCKED: $critical_count critical attack vectors found"
|
|
10274
|
-
log_error "Details: $adversarial_dir/$test_id/result.txt"
|
|
10275
|
-
return 1
|
|
10276
|
-
fi
|
|
10277
|
-
|
|
10278
|
-
return 0
|
|
10279
|
-
}
|
|
10280
|
-
|
|
10281
9506
|
load_solutions_context() {
|
|
10282
9507
|
# Load relevant structured solutions for the current task context
|
|
10283
9508
|
local context="$1"
|
|
@@ -10567,118 +9792,6 @@ print(json.dumps({'id':m['id'],'ts':m['timestamp'],'iter':m['iteration'],'task':
|
|
|
10567
9792
|
_LAST_CHECKPOINT_ID="$checkpoint_id"
|
|
10568
9793
|
}
|
|
10569
9794
|
|
|
10570
|
-
rollback_to_checkpoint() {
|
|
10571
|
-
# Rollback state files to a specific checkpoint
|
|
10572
|
-
# Args: $1 = checkpoint_id
|
|
10573
|
-
local checkpoint_id="$1"
|
|
10574
|
-
local checkpoint_dir=".loki/state/checkpoints"
|
|
10575
|
-
|
|
10576
|
-
# Validate checkpoint ID (prevent path traversal)
|
|
10577
|
-
if [[ ! "$checkpoint_id" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
10578
|
-
log_error "Invalid checkpoint ID: must be alphanumeric, hyphens, underscores only"
|
|
10579
|
-
return 1
|
|
10580
|
-
fi
|
|
10581
|
-
|
|
10582
|
-
local cp_dir="${checkpoint_dir}/${checkpoint_id}"
|
|
10583
|
-
|
|
10584
|
-
if [ ! -d "$cp_dir" ]; then
|
|
10585
|
-
log_error "Checkpoint not found: ${checkpoint_id}"
|
|
10586
|
-
return 1
|
|
10587
|
-
fi
|
|
10588
|
-
|
|
10589
|
-
# Read checkpoint metadata
|
|
10590
|
-
local git_sha
|
|
10591
|
-
git_sha=$(_CP_META="${cp_dir}/metadata.json" python3 -c "import json, os; print(json.load(open(os.environ['_CP_META']))['git_sha'])" 2>/dev/null || echo "")
|
|
10592
|
-
|
|
10593
|
-
log_warn "Rolling back to checkpoint: ${checkpoint_id}"
|
|
10594
|
-
|
|
10595
|
-
# R6 re-undoability invariant: force a pre-rollback snapshot of CURRENT state
|
|
10596
|
-
# before overwriting, even if the git tree is clean (the .loki/ state we are
|
|
10597
|
-
# about to clobber is not git-tracked). _LOKI_CP_FORCE bypasses the clean-tree
|
|
10598
|
-
# guard. Capture the snapshot id so we can tell the user how to undo the undo.
|
|
10599
|
-
_LOKI_CP_FORCE=1 create_checkpoint "pre-rollback snapshot (before restoring ${checkpoint_id})" "rollback"
|
|
10600
|
-
local pre_rollback_id="${_LAST_CHECKPOINT_ID:-}"
|
|
10601
|
-
if [ -n "$pre_rollback_id" ]; then
|
|
10602
|
-
log_info "Saved prior state as ${pre_rollback_id} (undo this rollback with: loki rollback to ${pre_rollback_id})"
|
|
10603
|
-
fi
|
|
10604
|
-
|
|
10605
|
-
# Restore state files (R6: CONTINUITY.md restores iteration/conversation context)
|
|
10606
|
-
for f in state/orchestrator.json queue/pending.json queue/completed.json queue/in-progress.json queue/current-task.json CONTINUITY.md; do
|
|
10607
|
-
if [ -f "${cp_dir}/${f}" ]; then
|
|
10608
|
-
local target_dir=".loki/$(dirname "$f")"
|
|
10609
|
-
mkdir -p "$target_dir"
|
|
10610
|
-
cp "${cp_dir}/${f}" ".loki/${f}" 2>/dev/null || true
|
|
10611
|
-
fi
|
|
10612
|
-
done
|
|
10613
|
-
|
|
10614
|
-
# Log the rollback (use python3 for safe JSON serialization)
|
|
10615
|
-
# v7.5.10: route through safe_append_event_jsonl() so parallel-worktree
|
|
10616
|
-
# rollbacks cannot interleave partial JSONL lines (POSIX append is
|
|
10617
|
-
# only atomic for <PIPE_BUF and not all platforms honor it).
|
|
10618
|
-
local timestamp rb_event
|
|
10619
|
-
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
10620
|
-
rb_event=$(_RB_CPID="$checkpoint_id" _RB_SHA="$git_sha" _RB_TS="$timestamp" \
|
|
10621
|
-
python3 -c "
|
|
10622
|
-
import json,os
|
|
10623
|
-
print(json.dumps({'event':'rollback','checkpoint':os.environ['_RB_CPID'],'git_sha':os.environ['_RB_SHA'],'timestamp':os.environ['_RB_TS']}))
|
|
10624
|
-
" 2>/dev/null) || rb_event=""
|
|
10625
|
-
if [ -n "$rb_event" ]; then
|
|
10626
|
-
# Source the emit lib once per call to get safe_append_event_jsonl.
|
|
10627
|
-
# Lib-only mode skips the emit script's normal CLI execution.
|
|
10628
|
-
# shellcheck disable=SC1091
|
|
10629
|
-
if [ -z "${_LOKI_EMIT_LIB_LOADED:-}" ]; then
|
|
10630
|
-
LOKI_EMIT_LIB_ONLY=1 . "$(dirname "${BASH_SOURCE[0]}")/../events/emit.sh" 2>/dev/null \
|
|
10631
|
-
&& _LOKI_EMIT_LIB_LOADED=1
|
|
10632
|
-
fi
|
|
10633
|
-
if declare -f safe_append_event_jsonl >/dev/null 2>&1; then
|
|
10634
|
-
safe_append_event_jsonl ".loki/events.jsonl" "$rb_event" 2>/dev/null || true
|
|
10635
|
-
else
|
|
10636
|
-
# Last-resort fallback: bare append (preserves prior behavior).
|
|
10637
|
-
printf '%s\n' "$rb_event" >> ".loki/events.jsonl" 2>/dev/null || true
|
|
10638
|
-
fi
|
|
10639
|
-
fi
|
|
10640
|
-
|
|
10641
|
-
log_info "State files restored from checkpoint: ${checkpoint_id}"
|
|
10642
|
-
|
|
10643
|
-
# R6: the prior hint `git reset --hard ${git_sha}` was MISLEADING. git_sha is
|
|
10644
|
-
# HEAD (the last commit), and Loki does not commit per iteration, so a hard
|
|
10645
|
-
# reset would discard the iteration's work rather than reconstruct it. The
|
|
10646
|
-
# correct, durable recovery is the anchored working-tree snapshot, if present.
|
|
10647
|
-
if [ -f "${cp_dir}/worktree-snapshot.txt" ]; then
|
|
10648
|
-
log_info "To also restore the working tree to this checkpoint:"
|
|
10649
|
-
log_info " git stash apply refs/loki/cp/${checkpoint_id}"
|
|
10650
|
-
elif [ -n "$git_sha" ] && [ "$git_sha" != "no-git" ]; then
|
|
10651
|
-
log_info "Git SHA at checkpoint (last commit): ${git_sha}"
|
|
10652
|
-
log_info "Note: no working-tree snapshot was captured for this checkpoint;"
|
|
10653
|
-
log_info "code changes since the last commit are not restorable from here."
|
|
10654
|
-
fi
|
|
10655
|
-
}
|
|
10656
|
-
|
|
10657
|
-
list_checkpoints() {
|
|
10658
|
-
# List recent checkpoints
|
|
10659
|
-
local checkpoint_dir=".loki/state/checkpoints"
|
|
10660
|
-
local index_file="${checkpoint_dir}/index.jsonl"
|
|
10661
|
-
local limit="${1:-10}"
|
|
10662
|
-
|
|
10663
|
-
if [ ! -f "$index_file" ]; then
|
|
10664
|
-
echo "No checkpoints found."
|
|
10665
|
-
return
|
|
10666
|
-
fi
|
|
10667
|
-
|
|
10668
|
-
tail -n "$limit" "$index_file" | python3 -c "
|
|
10669
|
-
import sys, json
|
|
10670
|
-
lines = sys.stdin.readlines()
|
|
10671
|
-
for line in reversed(lines):
|
|
10672
|
-
try:
|
|
10673
|
-
cp = json.loads(line)
|
|
10674
|
-
sha = cp.get('sha','')[:8]
|
|
10675
|
-
task = cp.get('task','')[:60]
|
|
10676
|
-
print(f\" {cp['id']} {cp['ts']} [{sha}] {task}\")
|
|
10677
|
-
except:
|
|
10678
|
-
continue
|
|
10679
|
-
"
|
|
10680
|
-
}
|
|
10681
|
-
|
|
10682
9795
|
start_dashboard() {
|
|
10683
9796
|
log_header "Starting Loki Dashboard"
|
|
10684
9797
|
|
|
@@ -11928,17 +11041,6 @@ except Exception as e:
|
|
|
11928
11041
|
}
|
|
11929
11042
|
|
|
11930
11043
|
# Load relevant learnings
|
|
11931
|
-
load_learnings_context() {
|
|
11932
|
-
local learnings=""
|
|
11933
|
-
|
|
11934
|
-
# Get recent learnings (last 7 days)
|
|
11935
|
-
for learning in $(find .loki/memory/learnings -name "*.md" -mtime -7 2>/dev/null | head -5); do
|
|
11936
|
-
learnings+="$(head -30 "$learning")\n---\n"
|
|
11937
|
-
done
|
|
11938
|
-
|
|
11939
|
-
echo -e "$learnings"
|
|
11940
|
-
}
|
|
11941
|
-
|
|
11942
11044
|
# Load pre-computed relevant learnings from CLI startup (SYN-008)
|
|
11943
11045
|
# Reads .loki/state/memory-context.json written by load_memory_context() in CLI
|
|
11944
11046
|
# Note: Different from get_relevant_learnings() which writes to relevant-learnings.json
|
|
@@ -12700,75 +11802,7 @@ PYEOF
|
|
|
12700
11802
|
#===============================================================================
|
|
12701
11803
|
|
|
12702
11804
|
# Enrich prompt context with relevant cross-project patterns
|
|
12703
|
-
enrich_from_knowledge_graph() {
|
|
12704
|
-
local context="$1"
|
|
12705
|
-
local max_patterns="${2:-5}"
|
|
12706
|
-
|
|
12707
|
-
_LOKI_KG_CONTEXT="$context" _LOKI_KG_MAX="$max_patterns" \
|
|
12708
|
-
_LOKI_PROJECT_DIR="$PROJECT_DIR" \
|
|
12709
|
-
python3 << 'PYEOF' 2>/dev/null || echo ""
|
|
12710
|
-
import sys
|
|
12711
|
-
import os
|
|
12712
|
-
import json
|
|
12713
|
-
|
|
12714
|
-
project_dir = os.environ.get('_LOKI_PROJECT_DIR', '')
|
|
12715
|
-
context = os.environ.get('_LOKI_KG_CONTEXT', '')
|
|
12716
|
-
max_results = int(os.environ.get('_LOKI_KG_MAX', '5'))
|
|
12717
|
-
|
|
12718
|
-
if not project_dir:
|
|
12719
|
-
sys.exit(0)
|
|
12720
|
-
sys.path.insert(0, project_dir)
|
|
12721
|
-
try:
|
|
12722
|
-
from memory.knowledge_graph import OrganizationKnowledgeGraph
|
|
12723
|
-
kg = OrganizationKnowledgeGraph()
|
|
12724
|
-
patterns = kg.query_patterns(context, max_results=max_results)
|
|
12725
|
-
if patterns:
|
|
12726
|
-
output = "\n## Cross-Project Knowledge (from knowledge graph)\n"
|
|
12727
|
-
for p in patterns:
|
|
12728
|
-
name = p.get('name', p.get('pattern', 'unnamed'))
|
|
12729
|
-
category = p.get('category', '')
|
|
12730
|
-
desc = p.get('description', '')
|
|
12731
|
-
output += f"- **{name}** ({category}): {desc}\n"
|
|
12732
|
-
print(output)
|
|
12733
|
-
except Exception:
|
|
12734
|
-
pass
|
|
12735
|
-
PYEOF
|
|
12736
|
-
}
|
|
12737
|
-
|
|
12738
11805
|
# Store new patterns to the knowledge graph after successful iterations
|
|
12739
|
-
store_to_knowledge_graph() {
|
|
12740
|
-
local target_dir="${TARGET_DIR:-.}"
|
|
12741
|
-
|
|
12742
|
-
_LOKI_PROJECT_DIR="$PROJECT_DIR" _LOKI_TARGET_DIR="$target_dir" \
|
|
12743
|
-
python3 << 'PYEOF' 2>/dev/null || true
|
|
12744
|
-
import sys
|
|
12745
|
-
import os
|
|
12746
|
-
|
|
12747
|
-
project_dir = os.environ.get('_LOKI_PROJECT_DIR', '')
|
|
12748
|
-
target_dir = os.environ.get('_LOKI_TARGET_DIR', '.')
|
|
12749
|
-
|
|
12750
|
-
sys.path.insert(0, project_dir)
|
|
12751
|
-
try:
|
|
12752
|
-
from memory.knowledge_graph import OrganizationKnowledgeGraph
|
|
12753
|
-
from pathlib import Path
|
|
12754
|
-
|
|
12755
|
-
kg = OrganizationKnowledgeGraph()
|
|
12756
|
-
project_dirs = [Path(target_dir)]
|
|
12757
|
-
|
|
12758
|
-
# Extract and store patterns
|
|
12759
|
-
patterns = kg.extract_patterns(project_dirs)
|
|
12760
|
-
if patterns:
|
|
12761
|
-
patterns = kg.deduplicate_patterns(patterns)
|
|
12762
|
-
kg.save_patterns(patterns)
|
|
12763
|
-
|
|
12764
|
-
# Rebuild graph
|
|
12765
|
-
kg.build_graph(project_dirs)
|
|
12766
|
-
kg.save_graph()
|
|
12767
|
-
except Exception:
|
|
12768
|
-
pass
|
|
12769
|
-
PYEOF
|
|
12770
|
-
}
|
|
12771
|
-
|
|
12772
11806
|
#===============================================================================
|
|
12773
11807
|
# Save/Load Wrapper State
|
|
12774
11808
|
#===============================================================================
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v7.
|
|
5
|
+
**Version:** v7.78.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -395,7 +395,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
|
|
|
395
395
|
# Run Loki Mode in Docker (Claude provider, API-key auth)
|
|
396
396
|
docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
|
|
397
397
|
-v $(pwd):/workspace -w /workspace \
|
|
398
|
-
asklokesh/loki-mode:7.
|
|
398
|
+
asklokesh/loki-mode:7.78.0 start ./my-spec.md
|
|
399
399
|
```
|
|
400
400
|
|
|
401
401
|
##### docker compose + .env (no host install)
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var QQ=Object.defineProperty;var ZQ=($)=>$;function zQ($,Q){this[$]=ZQ.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)QQ($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:zQ.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var q$=import.meta.require;var h1={};b(h1,{lokiDir:()=>P,homeLokiDir:()=>i$,findRepoRootForVersion:()=>t$,REPO_ROOT:()=>g});import{resolve as a,dirname as r$}from"path";import{fileURLToPath as XQ}from"url";import{existsSync as R$}from"fs";import{homedir as KQ}from"os";function qQ(){let $=b1;for(let Q=0;Q<6;Q++){if(R$(a($,"VERSION"))&&R$(a($,"autonomy/run.sh")))return $;let Z=r$($);if(Z===$)break;$=Z}return a(b1,"..","..","..")}function t$($){let Q=$;for(let Z=0;Z<6;Z++){if(R$(a(Q,"VERSION"))&&R$(a(Q,"autonomy/run.sh")))return Q;let z=r$(Q);if(z===Q)break;Q=z}return a($,"..","..","..")}function P(){return process.env.LOKI_DIR??a(process.cwd(),".loki")}function i$(){return a(KQ(),".loki")}var b1,g;var C=L(()=>{b1=r$(XQ(import.meta.url));g=qQ()});import{readFileSync as VQ}from"fs";import{resolve as JQ,dirname as UQ}from"path";import{fileURLToPath as WQ}from"url";function E$(){if(Q$!==null)return Q$;let $="7.
|
|
2
|
+
var QQ=Object.defineProperty;var ZQ=($)=>$;function zQ($,Q){this[$]=ZQ.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)QQ($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:zQ.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var q$=import.meta.require;var h1={};b(h1,{lokiDir:()=>P,homeLokiDir:()=>i$,findRepoRootForVersion:()=>t$,REPO_ROOT:()=>g});import{resolve as a,dirname as r$}from"path";import{fileURLToPath as XQ}from"url";import{existsSync as R$}from"fs";import{homedir as KQ}from"os";function qQ(){let $=b1;for(let Q=0;Q<6;Q++){if(R$(a($,"VERSION"))&&R$(a($,"autonomy/run.sh")))return $;let Z=r$($);if(Z===$)break;$=Z}return a(b1,"..","..","..")}function t$($){let Q=$;for(let Z=0;Z<6;Z++){if(R$(a(Q,"VERSION"))&&R$(a(Q,"autonomy/run.sh")))return Q;let z=r$(Q);if(z===Q)break;Q=z}return a($,"..","..","..")}function P(){return process.env.LOKI_DIR??a(process.cwd(),".loki")}function i$(){return a(KQ(),".loki")}var b1,g;var C=L(()=>{b1=r$(XQ(import.meta.url));g=qQ()});import{readFileSync as VQ}from"fs";import{resolve as JQ,dirname as UQ}from"path";import{fileURLToPath as WQ}from"url";function E$(){if(Q$!==null)return Q$;let $="7.78.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=UQ(WQ(import.meta.url)),Z=t$(Q);Q$=VQ(JQ(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var e$=L(()=>{C()});var g1={};b(g1,{runOrThrow:()=>HQ,run:()=>F,readStreamCapped:()=>m1,commandVersion:()=>BQ,commandExists:()=>f,ShellError:()=>$1,MAX_STDOUT_BYTES:()=>v1});async function m1($,Q=v1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:U}=await Z.read();if(K)break;if(!U)continue;if(q+=U.byteLength,q>Q){let J=U.byteLength-(q-Q);X+=z.decode(U.subarray(0,J),{stream:!0});break}X+=z.decode(U,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function F($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,U]=await Promise.all([m1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function HQ($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new $1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=GQ($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function GQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function BQ($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var v1=16777216,$1;var d=L(()=>{$1=class $1 extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function s($){return YQ?"":$}var YQ,O,S,_,_Z,I,k,h,V;var c=L(()=>{YQ=(process.env.NO_COLOR??"").length>0;O=s("\x1B[0;31m"),S=s("\x1B[0;32m"),_=s("\x1B[1;33m"),_Z=s("\x1B[0;34m"),I=s("\x1B[0;36m"),k=s("\x1B[1m"),h=s("\x1B[2m"),V=s("\x1B[0m")});import{existsSync as jQ}from"fs";async function Z$(){if(Y$!==void 0)return Y$;let $="/opt/homebrew/bin/python3.12";if(jQ($))return Y$=$,$;let Q=await f("python3.12");if(Q)return Y$=Q,Q;let Z=await f("python3");return Y$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var Y$;var V$=L(()=>{d()});var X0={};b(X0,{runStatus:()=>oQ});import{existsSync as y,readFileSync as U$,readdirSync as r1,statSync as t1}from"fs";import{resolve as D,basename as vQ}from"path";import{homedir as mQ}from"os";function i1($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function e1($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*N$/Q);if(X>N$)X=N$;let q=N$-X,K=S;if(z>=80)K=O;else if(z>=50)K=_;let U="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),J=i1($),W=i1(Q);return` ${k}${Z}${V} ${K}[${U}]${V} ${z}% (${J} / ${W})`}async function fQ(){if(await f("jq"))return!0;return process.stdout.write(`${O}Error: jq is required but not installed.${V}
|
|
3
3
|
`),process.stdout.write(`Install with:
|
|
4
4
|
`),process.stdout.write(` brew install jq (macOS)
|
|
5
5
|
`),process.stdout.write(` apt install jq (Debian/Ubuntu)
|
|
@@ -793,4 +793,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
793
793
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
794
794
|
`),process.stderr.write($Q),2}}s1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var qZ=await KZ(Bun.argv.slice(2));process.exit(qZ);
|
|
795
795
|
|
|
796
|
-
//# debugId=
|
|
796
|
+
//# debugId=753B9888BD0ED74B64756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "7.
|
|
4
|
+
"version": "7.78.0",
|
|
5
5
|
"description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "7.
|
|
5
|
+
"version": "7.78.0",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|