loki-mode 6.1.0 → 6.2.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/loki +315 -0
- package/autonomy/sandbox.sh +181 -1
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/templates/clusters/README.md +21 -0
- package/templates/clusters/code-review.json +36 -0
- package/templates/clusters/performance-audit.json +29 -0
- package/templates/clusters/refactoring.json +29 -0
- package/templates/clusters/security-review.json +36 -0
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v6.
|
|
6
|
+
# Loki Mode v6.2.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -263,4 +263,4 @@ The following features are documented in skill modules but not yet fully automat
|
|
|
263
263
|
| Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
|
|
264
264
|
| Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
|
|
265
265
|
|
|
266
|
-
**v6.
|
|
266
|
+
**v6.2.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
6.
|
|
1
|
+
6.2.0
|
package/autonomy/loki
CHANGED
|
@@ -448,6 +448,12 @@ show_help() {
|
|
|
448
448
|
echo " --parallel Enable parallel mode with git worktrees"
|
|
449
449
|
echo " --budget USD Set cost budget limit"
|
|
450
450
|
echo ""
|
|
451
|
+
echo "Progressive Isolation (for 'run'):"
|
|
452
|
+
echo " --worktree, -w Git worktree isolation (separate branch)"
|
|
453
|
+
echo " --pr Auto-create PR after completion (implies --worktree)"
|
|
454
|
+
echo " --ship Auto-merge after PR (implies --pr)"
|
|
455
|
+
echo " --detach, -d Run in background (implies --worktree)"
|
|
456
|
+
echo ""
|
|
451
457
|
echo "Examples:"
|
|
452
458
|
echo " loki run 123 # GitHub issue from current repo"
|
|
453
459
|
echo " loki run PROJ-456 # Jira issue"
|
|
@@ -2519,6 +2525,10 @@ cmd_run() {
|
|
|
2519
2525
|
local start_args=()
|
|
2520
2526
|
local no_start=false
|
|
2521
2527
|
local provider_override=""
|
|
2528
|
+
local use_worktree=false
|
|
2529
|
+
local create_pr=false
|
|
2530
|
+
local auto_merge=false
|
|
2531
|
+
local run_detached=false
|
|
2522
2532
|
|
|
2523
2533
|
# Parse arguments
|
|
2524
2534
|
while [[ $# -gt 0 ]]; do
|
|
@@ -2551,6 +2561,14 @@ cmd_run() {
|
|
|
2551
2561
|
echo " --sandbox Run in Docker sandbox"
|
|
2552
2562
|
echo " --budget USD Set cost budget limit"
|
|
2553
2563
|
echo ""
|
|
2564
|
+
echo "Progressive Isolation:"
|
|
2565
|
+
echo " --worktree, -w Git worktree isolation (separate branch)"
|
|
2566
|
+
echo " --pr Worktree + auto-create PR (implies --worktree)"
|
|
2567
|
+
echo " --ship Worktree + PR + auto-merge (implies --pr)"
|
|
2568
|
+
echo " --detach, -d Run in background (implies --worktree)"
|
|
2569
|
+
echo ""
|
|
2570
|
+
echo " Cascade: --ship implies --pr implies --worktree"
|
|
2571
|
+
echo ""
|
|
2554
2572
|
echo "Environment Variables:"
|
|
2555
2573
|
echo " JIRA_API_TOKEN Jira API token (for Jira issues)"
|
|
2556
2574
|
echo " JIRA_URL Jira base URL (for Jira issues)"
|
|
@@ -2563,6 +2581,10 @@ cmd_run() {
|
|
|
2563
2581
|
echo " loki run https://gitlab.com/o/r/-/issues/42 # GitLab issue"
|
|
2564
2582
|
echo " loki run 123 --dry-run # Preview PRD"
|
|
2565
2583
|
echo " loki run 123 --parallel --provider codex # Parallel mode with Codex"
|
|
2584
|
+
echo " loki run 123 --worktree # Isolated branch"
|
|
2585
|
+
echo " loki run 123 --pr # Auto-create PR"
|
|
2586
|
+
echo " loki run 123 --ship # Full automation: PR + merge"
|
|
2587
|
+
echo " loki run 123 --ship -d # Background, full automation"
|
|
2566
2588
|
exit 0
|
|
2567
2589
|
;;
|
|
2568
2590
|
--dry-run)
|
|
@@ -2626,6 +2648,30 @@ cmd_run() {
|
|
|
2626
2648
|
start_args+=("--budget" "${1#*=}")
|
|
2627
2649
|
shift
|
|
2628
2650
|
;;
|
|
2651
|
+
--worktree|-w)
|
|
2652
|
+
start_args+=("--parallel")
|
|
2653
|
+
use_worktree=true
|
|
2654
|
+
shift
|
|
2655
|
+
;;
|
|
2656
|
+
--pr)
|
|
2657
|
+
use_worktree=true
|
|
2658
|
+
create_pr=true
|
|
2659
|
+
start_args+=("--parallel")
|
|
2660
|
+
shift
|
|
2661
|
+
;;
|
|
2662
|
+
--ship)
|
|
2663
|
+
use_worktree=true
|
|
2664
|
+
create_pr=true
|
|
2665
|
+
auto_merge=true
|
|
2666
|
+
start_args+=("--parallel")
|
|
2667
|
+
shift
|
|
2668
|
+
;;
|
|
2669
|
+
--detach|-d)
|
|
2670
|
+
use_worktree=true
|
|
2671
|
+
run_detached=true
|
|
2672
|
+
start_args+=("--parallel")
|
|
2673
|
+
shift
|
|
2674
|
+
;;
|
|
2629
2675
|
-*)
|
|
2630
2676
|
echo -e "${RED}Unknown option: $1${NC}"
|
|
2631
2677
|
echo "Run 'loki run --help' for usage."
|
|
@@ -2687,10 +2733,46 @@ cmd_run() {
|
|
|
2687
2733
|
fi
|
|
2688
2734
|
echo ""
|
|
2689
2735
|
|
|
2736
|
+
# Progressive isolation: set up worktree branch naming
|
|
2737
|
+
if $use_worktree; then
|
|
2738
|
+
local branch_name="issue/${issue_provider}-${number:-$(date +%s)}"
|
|
2739
|
+
log_info "Progressive isolation: branch $branch_name"
|
|
2740
|
+
export LOKI_PARALLEL_MODE=true
|
|
2741
|
+
export LOKI_WORKTREE_BRANCH="$branch_name"
|
|
2742
|
+
fi
|
|
2743
|
+
|
|
2690
2744
|
# Generate PRD
|
|
2691
2745
|
local prd_content
|
|
2692
2746
|
prd_content=$(echo "$issue_json" | generate_prd_from_issue)
|
|
2693
2747
|
|
|
2748
|
+
# Detached mode: fork to background
|
|
2749
|
+
if $run_detached; then
|
|
2750
|
+
local log_file="$LOKI_DIR/logs/run-${number:-$(date +%s)}.log"
|
|
2751
|
+
mkdir -p "$(dirname "$log_file")"
|
|
2752
|
+
echo -e "${GREEN}Running detached. Logs: $log_file${NC}"
|
|
2753
|
+
echo -e "Check status: ${CYAN}loki status${NC}"
|
|
2754
|
+
|
|
2755
|
+
# Write PRD first so background process can use it
|
|
2756
|
+
mkdir -p "$LOKI_DIR"
|
|
2757
|
+
local prd_content_detach
|
|
2758
|
+
prd_content_detach=$(echo "$issue_json" | generate_prd_from_issue)
|
|
2759
|
+
local detach_prd="$LOKI_DIR/prd-issue-${number}.md"
|
|
2760
|
+
echo "$prd_content_detach" > "$detach_prd"
|
|
2761
|
+
|
|
2762
|
+
nohup bash -c "
|
|
2763
|
+
cd $(pwd)
|
|
2764
|
+
export LOKI_DETACHED=true
|
|
2765
|
+
export LOKI_PARALLEL_MODE=true
|
|
2766
|
+
export LOKI_WORKTREE_BRANCH=\"$branch_name\"
|
|
2767
|
+
$(command -v loki || echo "$0") start \"$detach_prd\" --parallel ${start_args[*]+"${start_args[*]}"}
|
|
2768
|
+
" > "$log_file" 2>&1 &
|
|
2769
|
+
|
|
2770
|
+
local bg_pid=$!
|
|
2771
|
+
echo "$bg_pid" > "$LOKI_DIR/run-${number:-detached}.pid"
|
|
2772
|
+
echo "Background PID: $bg_pid"
|
|
2773
|
+
return 0
|
|
2774
|
+
fi
|
|
2775
|
+
|
|
2694
2776
|
# Handle dry-run
|
|
2695
2777
|
if [[ "$dry_run" == "true" ]]; then
|
|
2696
2778
|
echo -e "${BOLD}Generated PRD Preview:${NC}"
|
|
@@ -2728,6 +2810,75 @@ cmd_run() {
|
|
|
2728
2810
|
echo ""
|
|
2729
2811
|
echo -e "${GREEN}Starting Loki Mode with generated PRD...${NC}"
|
|
2730
2812
|
cmd_start "$output_file" ${start_args[@]+"${start_args[@]}"}
|
|
2813
|
+
|
|
2814
|
+
# Progressive isolation: create PR
|
|
2815
|
+
if $create_pr; then
|
|
2816
|
+
echo ""
|
|
2817
|
+
echo -e "${GREEN}Creating pull request...${NC}"
|
|
2818
|
+
local pr_title="${title:-Implementation for issue ${issue_ref}}"
|
|
2819
|
+
local pr_body="Implemented by Loki Mode (autonomous agent)
|
|
2820
|
+
|
|
2821
|
+
Issue: ${issue_ref}
|
|
2822
|
+
Provider: ${issue_provider}
|
|
2823
|
+
|
|
2824
|
+
## Changes
|
|
2825
|
+
$(git log --oneline "main..HEAD" 2>/dev/null || echo "See diff")"
|
|
2826
|
+
|
|
2827
|
+
case "${issue_provider:-github}" in
|
|
2828
|
+
github)
|
|
2829
|
+
if command -v gh &>/dev/null; then
|
|
2830
|
+
local branch_current
|
|
2831
|
+
branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
2832
|
+
if [[ -n "$branch_current" && "$branch_current" != "main" && "$branch_current" != "master" ]]; then
|
|
2833
|
+
git push origin "$branch_current" 2>/dev/null || true
|
|
2834
|
+
gh pr create --title "$pr_title" --body "$pr_body" --head "$branch_current" 2>/dev/null && \
|
|
2835
|
+
echo -e "${GREEN}PR created${NC}" || \
|
|
2836
|
+
echo -e "${YELLOW}PR creation failed${NC}"
|
|
2837
|
+
fi
|
|
2838
|
+
fi
|
|
2839
|
+
;;
|
|
2840
|
+
gitlab)
|
|
2841
|
+
if command -v glab &>/dev/null; then
|
|
2842
|
+
local branch_current
|
|
2843
|
+
branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
2844
|
+
if [[ -n "$branch_current" && "$branch_current" != "main" && "$branch_current" != "master" ]]; then
|
|
2845
|
+
git push origin "$branch_current" 2>/dev/null || true
|
|
2846
|
+
glab mr create --title "$pr_title" --description "$pr_body" --source-branch "$branch_current" 2>/dev/null && \
|
|
2847
|
+
echo -e "${GREEN}MR created${NC}" || \
|
|
2848
|
+
echo -e "${YELLOW}MR creation failed${NC}"
|
|
2849
|
+
fi
|
|
2850
|
+
fi
|
|
2851
|
+
;;
|
|
2852
|
+
*)
|
|
2853
|
+
echo -e "${YELLOW}PR creation not supported for provider: $issue_provider${NC}"
|
|
2854
|
+
;;
|
|
2855
|
+
esac
|
|
2856
|
+
fi
|
|
2857
|
+
|
|
2858
|
+
# Progressive isolation: auto-merge
|
|
2859
|
+
if $auto_merge; then
|
|
2860
|
+
echo -e "${GREEN}Auto-merging...${NC}"
|
|
2861
|
+
case "${issue_provider:-github}" in
|
|
2862
|
+
github)
|
|
2863
|
+
local branch_current
|
|
2864
|
+
branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
2865
|
+
gh pr merge "$branch_current" --squash --delete-branch 2>/dev/null && \
|
|
2866
|
+
echo -e "${GREEN}PR merged and branch deleted${NC}" || \
|
|
2867
|
+
echo -e "${YELLOW}Auto-merge failed. PR remains open.${NC}"
|
|
2868
|
+
if [[ -n "${number:-}" ]]; then
|
|
2869
|
+
gh issue close "$number" --comment "Resolved by Loki Mode" 2>/dev/null || true
|
|
2870
|
+
fi
|
|
2871
|
+
;;
|
|
2872
|
+
gitlab)
|
|
2873
|
+
glab mr merge --squash --remove-source-branch 2>/dev/null && \
|
|
2874
|
+
echo -e "${GREEN}MR merged and branch deleted${NC}" || \
|
|
2875
|
+
echo -e "${YELLOW}Auto-merge failed. MR remains open.${NC}"
|
|
2876
|
+
;;
|
|
2877
|
+
*)
|
|
2878
|
+
echo -e "${YELLOW}Auto-merge not supported for provider: $issue_provider${NC}"
|
|
2879
|
+
;;
|
|
2880
|
+
esac
|
|
2881
|
+
fi
|
|
2731
2882
|
fi
|
|
2732
2883
|
}
|
|
2733
2884
|
|
|
@@ -6739,6 +6890,167 @@ cmd_migrate() {
|
|
|
6739
6890
|
cmd_migrate_start "$codebase_path" "$target" "$plan_only" "$phase" "$parallel" "$compliance" "$dry_run" "$do_resume" "$multi_repo" "$export_report" "$no_dashboard" "$no_docs"
|
|
6740
6891
|
}
|
|
6741
6892
|
|
|
6893
|
+
#===============================================================================
|
|
6894
|
+
# loki cluster - Custom workflow templates (v6.2.0)
|
|
6895
|
+
#===============================================================================
|
|
6896
|
+
|
|
6897
|
+
cmd_cluster() {
|
|
6898
|
+
local subcmd="${1:-list}"
|
|
6899
|
+
shift 2>/dev/null || true
|
|
6900
|
+
|
|
6901
|
+
case "$subcmd" in
|
|
6902
|
+
--help|-h|help)
|
|
6903
|
+
echo -e "${BOLD}loki cluster${NC} - Custom workflow templates (v6.2.0)"
|
|
6904
|
+
echo ""
|
|
6905
|
+
echo "Usage: loki cluster <command> [options]"
|
|
6906
|
+
echo ""
|
|
6907
|
+
echo "Commands:"
|
|
6908
|
+
echo " list List available workflow templates"
|
|
6909
|
+
echo " validate <name> Validate a cluster template"
|
|
6910
|
+
echo " run <name> [args] Execute a cluster workflow"
|
|
6911
|
+
echo " info <name> Show template details"
|
|
6912
|
+
echo ""
|
|
6913
|
+
echo "Examples:"
|
|
6914
|
+
echo " loki cluster list"
|
|
6915
|
+
echo " loki cluster validate security-review"
|
|
6916
|
+
echo " loki cluster info code-review"
|
|
6917
|
+
;;
|
|
6918
|
+
list)
|
|
6919
|
+
echo -e "${BOLD}Available Cluster Templates${NC}"
|
|
6920
|
+
echo ""
|
|
6921
|
+
local template_dir="$SKILL_DIR/templates/clusters"
|
|
6922
|
+
if [[ ! -d "$template_dir" ]]; then
|
|
6923
|
+
echo "No templates found at $template_dir"
|
|
6924
|
+
return 1
|
|
6925
|
+
fi
|
|
6926
|
+
for f in "$template_dir"/*.json; do
|
|
6927
|
+
[[ ! -f "$f" ]] && continue
|
|
6928
|
+
local name
|
|
6929
|
+
name=$(basename "$f" .json)
|
|
6930
|
+
local desc
|
|
6931
|
+
desc=$(python3 -c "
|
|
6932
|
+
import json, sys
|
|
6933
|
+
try:
|
|
6934
|
+
with open(sys.argv[1]) as f:
|
|
6935
|
+
d = json.load(f)
|
|
6936
|
+
print(d.get('description', 'No description'))
|
|
6937
|
+
except: print('Error reading template')
|
|
6938
|
+
" "$f" 2>/dev/null || echo "")
|
|
6939
|
+
local agent_count
|
|
6940
|
+
agent_count=$(python3 -c "
|
|
6941
|
+
import json, sys
|
|
6942
|
+
try:
|
|
6943
|
+
with open(sys.argv[1]) as f:
|
|
6944
|
+
d = json.load(f)
|
|
6945
|
+
print(len(d.get('agents', [])))
|
|
6946
|
+
except: print('?')
|
|
6947
|
+
" "$f" 2>/dev/null || echo "?")
|
|
6948
|
+
printf " %-20s %s agents %s\n" "$name" "$agent_count" "$desc"
|
|
6949
|
+
done
|
|
6950
|
+
;;
|
|
6951
|
+
validate)
|
|
6952
|
+
local template_name="${1:-}"
|
|
6953
|
+
if [[ -z "$template_name" ]]; then
|
|
6954
|
+
echo -e "${RED}Usage: loki cluster validate <template-name>${NC}"
|
|
6955
|
+
return 1
|
|
6956
|
+
fi
|
|
6957
|
+
local template_file="$SKILL_DIR/templates/clusters/${template_name}.json"
|
|
6958
|
+
if [[ ! -f "$template_file" ]]; then
|
|
6959
|
+
echo -e "${RED}Template not found: $template_name${NC}"
|
|
6960
|
+
echo "Run 'loki cluster list' to see available templates."
|
|
6961
|
+
return 1
|
|
6962
|
+
fi
|
|
6963
|
+
echo -e "${CYAN}Validating: $template_name${NC}"
|
|
6964
|
+
local errors
|
|
6965
|
+
errors=$(python3 -c "
|
|
6966
|
+
import json, sys
|
|
6967
|
+
sys.path.insert(0, '$(dirname "$SKILL_DIR/swarm/")')
|
|
6968
|
+
sys.path.insert(0, '$SKILL_DIR')
|
|
6969
|
+
from swarm.patterns import TopologyValidator
|
|
6970
|
+
errors = TopologyValidator.validate_file(sys.argv[1])
|
|
6971
|
+
if errors:
|
|
6972
|
+
for e in errors:
|
|
6973
|
+
print(f'ERROR: {e}')
|
|
6974
|
+
else:
|
|
6975
|
+
print('VALID')
|
|
6976
|
+
" "$template_file" 2>&1)
|
|
6977
|
+
if echo "$errors" | grep -q "^VALID$"; then
|
|
6978
|
+
echo -e "${GREEN}Template is valid${NC}"
|
|
6979
|
+
return 0
|
|
6980
|
+
else
|
|
6981
|
+
echo -e "${RED}Validation errors:${NC}"
|
|
6982
|
+
echo "$errors"
|
|
6983
|
+
return 1
|
|
6984
|
+
fi
|
|
6985
|
+
;;
|
|
6986
|
+
info)
|
|
6987
|
+
local template_name="${1:-}"
|
|
6988
|
+
if [[ -z "$template_name" ]]; then
|
|
6989
|
+
echo -e "${RED}Usage: loki cluster info <template-name>${NC}"
|
|
6990
|
+
return 1
|
|
6991
|
+
fi
|
|
6992
|
+
local template_file="$SKILL_DIR/templates/clusters/${template_name}.json"
|
|
6993
|
+
if [[ ! -f "$template_file" ]]; then
|
|
6994
|
+
echo -e "${RED}Template not found: $template_name${NC}"
|
|
6995
|
+
return 1
|
|
6996
|
+
fi
|
|
6997
|
+
python3 -c "
|
|
6998
|
+
import json, sys
|
|
6999
|
+
with open(sys.argv[1]) as f:
|
|
7000
|
+
d = json.load(f)
|
|
7001
|
+
print(f\"Name: {d.get('name', 'unknown')}\")
|
|
7002
|
+
print(f\"Description: {d.get('description', 'none')}\")
|
|
7003
|
+
print(f\"Version: {d.get('version', 'unknown')}\")
|
|
7004
|
+
print(f\"Topology: {d.get('topology', 'unknown')}\")
|
|
7005
|
+
print(f\"Agents: {len(d.get('agents', []))}\")
|
|
7006
|
+
print()
|
|
7007
|
+
for a in d.get('agents', []):
|
|
7008
|
+
subs = ', '.join(a.get('subscribes', []))
|
|
7009
|
+
pubs = ', '.join(a.get('publishes', []))
|
|
7010
|
+
print(f\" [{a['id']}] ({a.get('type', '?')})\")
|
|
7011
|
+
print(f\" Role: {a.get('role', '?')}\")
|
|
7012
|
+
print(f\" Subscribes: {subs}\")
|
|
7013
|
+
print(f\" Publishes: {pubs}\")
|
|
7014
|
+
print()
|
|
7015
|
+
" "$template_file"
|
|
7016
|
+
;;
|
|
7017
|
+
run)
|
|
7018
|
+
local template_name="${1:-}"
|
|
7019
|
+
if [[ -z "$template_name" ]]; then
|
|
7020
|
+
echo -e "${RED}Usage: loki cluster run <template-name> [args]${NC}"
|
|
7021
|
+
return 1
|
|
7022
|
+
fi
|
|
7023
|
+
local template_file="$SKILL_DIR/templates/clusters/${template_name}.json"
|
|
7024
|
+
if [[ ! -f "$template_file" ]]; then
|
|
7025
|
+
echo -e "${RED}Template not found: $template_name${NC}"
|
|
7026
|
+
return 1
|
|
7027
|
+
fi
|
|
7028
|
+
# Validate first
|
|
7029
|
+
local errors
|
|
7030
|
+
errors=$(python3 -c "
|
|
7031
|
+
import json, sys
|
|
7032
|
+
sys.path.insert(0, '$SKILL_DIR')
|
|
7033
|
+
from swarm.patterns import TopologyValidator
|
|
7034
|
+
errors = TopologyValidator.validate_file(sys.argv[1])
|
|
7035
|
+
for e in errors: print(e)
|
|
7036
|
+
" "$template_file" 2>&1)
|
|
7037
|
+
if [[ -n "$errors" ]]; then
|
|
7038
|
+
echo -e "${RED}Template validation failed:${NC}"
|
|
7039
|
+
echo "$errors"
|
|
7040
|
+
return 1
|
|
7041
|
+
fi
|
|
7042
|
+
echo -e "${GREEN}Cluster template: $template_name${NC}"
|
|
7043
|
+
echo -e "${YELLOW}Note: Cluster execution engine is planned for v6.3.0.${NC}"
|
|
7044
|
+
echo -e "Template validated successfully. Use 'loki cluster info $template_name' for details."
|
|
7045
|
+
;;
|
|
7046
|
+
*)
|
|
7047
|
+
echo -e "${RED}Unknown cluster command: $subcmd${NC}"
|
|
7048
|
+
echo "Run 'loki cluster --help' for usage."
|
|
7049
|
+
return 1
|
|
7050
|
+
;;
|
|
7051
|
+
esac
|
|
7052
|
+
}
|
|
7053
|
+
|
|
6742
7054
|
# Main command dispatcher
|
|
6743
7055
|
main() {
|
|
6744
7056
|
if [ $# -eq 0 ]; then
|
|
@@ -6869,6 +7181,9 @@ main() {
|
|
|
6869
7181
|
migrate)
|
|
6870
7182
|
cmd_migrate "$@"
|
|
6871
7183
|
;;
|
|
7184
|
+
cluster)
|
|
7185
|
+
cmd_cluster "$@"
|
|
7186
|
+
;;
|
|
6872
7187
|
metrics)
|
|
6873
7188
|
cmd_metrics "$@"
|
|
6874
7189
|
;;
|
package/autonomy/sandbox.sh
CHANGED
|
@@ -62,6 +62,34 @@ DASHBOARD_PORT="${LOKI_DASHBOARD_PORT:-57374}"
|
|
|
62
62
|
# Security: Prompt injection disabled by default for enterprise security
|
|
63
63
|
PROMPT_INJECTION_ENABLED="${LOKI_PROMPT_INJECTION:-false}"
|
|
64
64
|
|
|
65
|
+
#===============================================================================
|
|
66
|
+
# Docker Credential Mount Presets
|
|
67
|
+
#===============================================================================
|
|
68
|
+
|
|
69
|
+
# Preset definitions: "host_path:container_path:mode"
|
|
70
|
+
# Container runs as user 'loki' (UID 1000), so mount to /home/loki/
|
|
71
|
+
declare -A DOCKER_MOUNT_PRESETS
|
|
72
|
+
DOCKER_MOUNT_PRESETS=(
|
|
73
|
+
[gh]="$HOME/.config/gh:/home/loki/.config/gh:ro"
|
|
74
|
+
[git]="$HOME/.gitconfig:/home/loki/.gitconfig:ro"
|
|
75
|
+
[ssh]="$HOME/.ssh:/home/loki/.ssh:ro"
|
|
76
|
+
[aws]="$HOME/.aws:/home/loki/.aws:ro"
|
|
77
|
+
[azure]="$HOME/.azure:/home/loki/.azure:ro"
|
|
78
|
+
[kube]="$HOME/.kube:/home/loki/.kube:ro"
|
|
79
|
+
[terraform]="$HOME/.terraform.d:/home/loki/.terraform.d:ro"
|
|
80
|
+
[gcloud]="$HOME/.config/gcloud:/home/loki/.config/gcloud:ro"
|
|
81
|
+
[npm]="$HOME/.npmrc:/home/loki/.npmrc:ro"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Environment variables auto-passed per preset (comma-separated)
|
|
85
|
+
declare -A DOCKER_ENV_PRESETS
|
|
86
|
+
DOCKER_ENV_PRESETS=(
|
|
87
|
+
[aws]="AWS_REGION,AWS_PROFILE,AWS_DEFAULT_REGION"
|
|
88
|
+
[azure]="AZURE_SUBSCRIPTION_ID,AZURE_TENANT_ID"
|
|
89
|
+
[gcloud]="GOOGLE_PROJECT,GOOGLE_REGION,GCLOUD_PROJECT"
|
|
90
|
+
[terraform]="TF_VAR_*"
|
|
91
|
+
)
|
|
92
|
+
|
|
65
93
|
#===============================================================================
|
|
66
94
|
# Utility Functions
|
|
67
95
|
#===============================================================================
|
|
@@ -767,9 +795,129 @@ docker_desktop_sandbox_run() {
|
|
|
767
795
|
# Docker Container Sandbox (standard Docker - fallback from Docker Desktop)
|
|
768
796
|
#===============================================================================
|
|
769
797
|
|
|
798
|
+
# Resolve Docker credential mount presets
|
|
799
|
+
# Reads from: .loki/config/settings.json dockerMounts, LOKI_DOCKER_MOUNTS env var
|
|
800
|
+
# Returns: string of -v and -e flags for docker run
|
|
801
|
+
resolve_docker_mounts() {
|
|
802
|
+
local mount_args=""
|
|
803
|
+
|
|
804
|
+
# Read configured presets (JSON array of strings)
|
|
805
|
+
local presets_json=""
|
|
806
|
+
if [[ -f "${PROJECT_DIR}/.loki/config/settings.json" ]]; then
|
|
807
|
+
presets_json=$(python3 -c "
|
|
808
|
+
import json, sys
|
|
809
|
+
try:
|
|
810
|
+
with open('${PROJECT_DIR}/.loki/config/settings.json') as f:
|
|
811
|
+
print(json.dumps(json.load(f).get('dockerMounts', [])))
|
|
812
|
+
except: print('[]')
|
|
813
|
+
" 2>/dev/null || echo "[]")
|
|
814
|
+
fi
|
|
815
|
+
|
|
816
|
+
# Override with env var if set
|
|
817
|
+
if [[ -n "${LOKI_DOCKER_MOUNTS:-}" ]]; then
|
|
818
|
+
presets_json="$LOKI_DOCKER_MOUNTS"
|
|
819
|
+
fi
|
|
820
|
+
|
|
821
|
+
# Parse and resolve
|
|
822
|
+
if [[ "$presets_json" != "[]" ]] && [[ "$presets_json" != "" ]]; then
|
|
823
|
+
local preset_names
|
|
824
|
+
preset_names=$(python3 -c "
|
|
825
|
+
import json, sys
|
|
826
|
+
try:
|
|
827
|
+
data = json.loads(sys.argv[1])
|
|
828
|
+
if isinstance(data, list):
|
|
829
|
+
for item in data:
|
|
830
|
+
print(item)
|
|
831
|
+
except: pass
|
|
832
|
+
" "$presets_json" 2>/dev/null || echo "")
|
|
833
|
+
|
|
834
|
+
local name
|
|
835
|
+
while IFS= read -r name; do
|
|
836
|
+
[[ -z "$name" ]] && continue
|
|
837
|
+
|
|
838
|
+
local preset_value="${DOCKER_MOUNT_PRESETS[$name]:-}"
|
|
839
|
+
if [[ -z "$preset_value" ]]; then
|
|
840
|
+
log_warn "Unknown Docker mount preset: $name"
|
|
841
|
+
continue
|
|
842
|
+
fi
|
|
843
|
+
|
|
844
|
+
IFS=':' read -ra parts <<< "$preset_value"
|
|
845
|
+
local host_path="${parts[0]}"
|
|
846
|
+
local container_path="${parts[1]}"
|
|
847
|
+
local mode="${parts[2]:-ro}"
|
|
848
|
+
|
|
849
|
+
# Expand ~ and $HOME
|
|
850
|
+
host_path=$(eval echo "$host_path" 2>/dev/null || echo "$host_path")
|
|
851
|
+
|
|
852
|
+
# Only mount if host path exists
|
|
853
|
+
if [[ -e "$host_path" ]]; then
|
|
854
|
+
mount_args="$mount_args -v ${host_path}:${container_path}:${mode}"
|
|
855
|
+
log_info " Mount preset [$name]: $host_path -> $container_path ($mode)"
|
|
856
|
+
fi
|
|
857
|
+
|
|
858
|
+
# Add associated env vars
|
|
859
|
+
local env_list="${DOCKER_ENV_PRESETS[$name]:-}"
|
|
860
|
+
if [[ -n "$env_list" ]]; then
|
|
861
|
+
IFS=',' read -ra env_names <<< "$env_list"
|
|
862
|
+
local env_name
|
|
863
|
+
for env_name in "${env_names[@]}"; do
|
|
864
|
+
if [[ "$env_name" == *"*" ]]; then
|
|
865
|
+
# Wildcard: pass all matching env vars
|
|
866
|
+
local prefix="${env_name%\*}"
|
|
867
|
+
while IFS='=' read -r key val; do
|
|
868
|
+
[[ "$key" == "$prefix"* ]] && [[ -n "$val" ]] && \
|
|
869
|
+
mount_args="$mount_args -e $key"
|
|
870
|
+
done < <(env)
|
|
871
|
+
elif [[ -n "${!env_name:-}" ]]; then
|
|
872
|
+
mount_args="$mount_args -e $env_name"
|
|
873
|
+
fi
|
|
874
|
+
done
|
|
875
|
+
fi
|
|
876
|
+
done <<< "$preset_names"
|
|
877
|
+
fi
|
|
878
|
+
|
|
879
|
+
echo "$mount_args"
|
|
880
|
+
}
|
|
881
|
+
|
|
770
882
|
start_sandbox() {
|
|
771
|
-
|
|
883
|
+
# Parse arguments: extract flags before positional args
|
|
884
|
+
local prd_path=""
|
|
772
885
|
local provider="${LOKI_PROVIDER:-claude}"
|
|
886
|
+
local no_mounts=false
|
|
887
|
+
local -a custom_mounts=()
|
|
888
|
+
|
|
889
|
+
while [[ $# -gt 0 ]]; do
|
|
890
|
+
case "$1" in
|
|
891
|
+
--mount)
|
|
892
|
+
if [[ -n "${2:-}" ]]; then
|
|
893
|
+
custom_mounts+=("$2")
|
|
894
|
+
shift 2
|
|
895
|
+
else
|
|
896
|
+
log_error "--mount requires HOST:CONTAINER:MODE argument"
|
|
897
|
+
return 1
|
|
898
|
+
fi
|
|
899
|
+
;;
|
|
900
|
+
--no-mounts)
|
|
901
|
+
no_mounts=true
|
|
902
|
+
shift
|
|
903
|
+
;;
|
|
904
|
+
--provider)
|
|
905
|
+
if [[ -n "${2:-}" ]]; then
|
|
906
|
+
provider="$2"
|
|
907
|
+
shift 2
|
|
908
|
+
else
|
|
909
|
+
shift
|
|
910
|
+
fi
|
|
911
|
+
;;
|
|
912
|
+
*)
|
|
913
|
+
# First non-flag argument is the PRD path
|
|
914
|
+
if [[ -z "$prd_path" ]]; then
|
|
915
|
+
prd_path="$1"
|
|
916
|
+
fi
|
|
917
|
+
shift
|
|
918
|
+
;;
|
|
919
|
+
esac
|
|
920
|
+
done
|
|
773
921
|
|
|
774
922
|
# Set up signal handler to cleanup on Ctrl+C
|
|
775
923
|
trap cleanup_container INT TERM
|
|
@@ -887,6 +1035,38 @@ start_sandbox() {
|
|
|
887
1035
|
docker_args+=("--volume" "$HOME/.config/gh:/home/loki/.config/gh:ro")
|
|
888
1036
|
fi
|
|
889
1037
|
|
|
1038
|
+
# Apply Docker credential mount presets (additive on top of defaults above)
|
|
1039
|
+
if [[ "$no_mounts" != "true" ]] && [[ "${LOKI_NO_DOCKER_MOUNTS:-}" != "true" ]]; then
|
|
1040
|
+
local preset_mounts
|
|
1041
|
+
preset_mounts=$(resolve_docker_mounts)
|
|
1042
|
+
if [[ -n "$preset_mounts" ]]; then
|
|
1043
|
+
# shellcheck disable=SC2206
|
|
1044
|
+
docker_args+=($preset_mounts)
|
|
1045
|
+
fi
|
|
1046
|
+
fi
|
|
1047
|
+
|
|
1048
|
+
# Apply custom --mount arguments
|
|
1049
|
+
local custom_mount
|
|
1050
|
+
for custom_mount in "${custom_mounts[@]+"${custom_mounts[@]}"}"; do
|
|
1051
|
+
if [[ -n "$custom_mount" ]]; then
|
|
1052
|
+
IFS=':' read -ra mount_parts <<< "$custom_mount"
|
|
1053
|
+
local c_host="${mount_parts[0]:-}"
|
|
1054
|
+
local c_container="${mount_parts[1]:-}"
|
|
1055
|
+
local c_mode="${mount_parts[2]:-ro}"
|
|
1056
|
+
if [[ -n "$c_host" ]] && [[ -n "$c_container" ]]; then
|
|
1057
|
+
c_host=$(eval echo "$c_host" 2>/dev/null || echo "$c_host")
|
|
1058
|
+
if [[ -e "$c_host" ]]; then
|
|
1059
|
+
docker_args+=("--volume" "${c_host}:${c_container}:${c_mode}")
|
|
1060
|
+
log_info " Custom mount: $c_host -> $c_container ($c_mode)"
|
|
1061
|
+
else
|
|
1062
|
+
log_warn "Custom mount source does not exist: $c_host"
|
|
1063
|
+
fi
|
|
1064
|
+
else
|
|
1065
|
+
log_warn "Invalid mount format: $custom_mount (expected HOST:CONTAINER[:MODE])"
|
|
1066
|
+
fi
|
|
1067
|
+
fi
|
|
1068
|
+
done
|
|
1069
|
+
|
|
890
1070
|
# Pass API keys as environment variables (more secure than mounting files)
|
|
891
1071
|
if [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then
|
|
892
1072
|
docker_args+=("--env" "ANTHROPIC_API_KEY")
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Cluster Templates
|
|
2
|
+
|
|
3
|
+
Pre-built workflow topologies for common development patterns.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
loki cluster list # List available templates
|
|
8
|
+
loki cluster validate <template> # Validate topology
|
|
9
|
+
loki cluster run <template> [args] # Execute workflow
|
|
10
|
+
|
|
11
|
+
## Template Format
|
|
12
|
+
|
|
13
|
+
Each template is a JSON file defining agents, their pub/sub topics,
|
|
14
|
+
and the workflow topology.
|
|
15
|
+
|
|
16
|
+
## Available Templates
|
|
17
|
+
|
|
18
|
+
- security-review: Multi-agent security audit pipeline
|
|
19
|
+
- performance-audit: Performance analysis with profiling
|
|
20
|
+
- refactoring: Structured refactoring with test preservation
|
|
21
|
+
- code-review: Multi-reviewer code review process
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Code Review",
|
|
3
|
+
"description": "Multi-reviewer code review with conflict resolution",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"topology": "fan-out-fan-in",
|
|
6
|
+
"agents": [
|
|
7
|
+
{
|
|
8
|
+
"id": "reviewer-arch",
|
|
9
|
+
"type": "review-code",
|
|
10
|
+
"role": "Review architecture and design patterns",
|
|
11
|
+
"subscribes": ["task.start"],
|
|
12
|
+
"publishes": ["review.architecture"]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "reviewer-security",
|
|
16
|
+
"type": "review-security",
|
|
17
|
+
"role": "Review security implications",
|
|
18
|
+
"subscribes": ["task.start"],
|
|
19
|
+
"publishes": ["review.security"]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "reviewer-tests",
|
|
23
|
+
"type": "eng-qa",
|
|
24
|
+
"role": "Review test coverage and quality",
|
|
25
|
+
"subscribes": ["task.start"],
|
|
26
|
+
"publishes": ["review.tests"]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "resolver",
|
|
30
|
+
"type": "review-code",
|
|
31
|
+
"role": "Synthesize all review findings and resolve conflicts",
|
|
32
|
+
"subscribes": ["review.architecture", "review.security", "review.tests"],
|
|
33
|
+
"publishes": ["task.complete"]
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Performance Audit",
|
|
3
|
+
"description": "Performance analysis with profiling and optimization recommendations",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"topology": "fan-out-fan-in",
|
|
6
|
+
"agents": [
|
|
7
|
+
{
|
|
8
|
+
"id": "profiler",
|
|
9
|
+
"type": "eng-perf",
|
|
10
|
+
"role": "Profile application and identify bottlenecks",
|
|
11
|
+
"subscribes": ["task.start"],
|
|
12
|
+
"publishes": ["profile.results"]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "db-analyzer",
|
|
16
|
+
"type": "eng-database",
|
|
17
|
+
"role": "Analyze database queries and index usage",
|
|
18
|
+
"subscribes": ["task.start"],
|
|
19
|
+
"publishes": ["db.analysis"]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "optimizer",
|
|
23
|
+
"type": "eng-perf",
|
|
24
|
+
"role": "Generate optimization recommendations from all analyses",
|
|
25
|
+
"subscribes": ["profile.results", "db.analysis"],
|
|
26
|
+
"publishes": ["task.complete"]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Refactoring",
|
|
3
|
+
"description": "Structured refactoring with test preservation and review",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"topology": "pipeline",
|
|
6
|
+
"agents": [
|
|
7
|
+
{
|
|
8
|
+
"id": "analyzer",
|
|
9
|
+
"type": "review-code",
|
|
10
|
+
"role": "Analyze code for refactoring opportunities",
|
|
11
|
+
"subscribes": ["task.start"],
|
|
12
|
+
"publishes": ["analysis.complete"]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "refactorer",
|
|
16
|
+
"type": "eng-backend",
|
|
17
|
+
"role": "Apply refactoring changes with test preservation",
|
|
18
|
+
"subscribes": ["analysis.complete"],
|
|
19
|
+
"publishes": ["refactor.complete"]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "verifier",
|
|
23
|
+
"type": "eng-qa",
|
|
24
|
+
"role": "Verify all tests pass and behavior is preserved",
|
|
25
|
+
"subscribes": ["refactor.complete"],
|
|
26
|
+
"publishes": ["task.complete"]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Security Review",
|
|
3
|
+
"description": "Multi-agent security audit with adversarial testing",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"topology": "pipeline",
|
|
6
|
+
"agents": [
|
|
7
|
+
{
|
|
8
|
+
"id": "scanner",
|
|
9
|
+
"type": "ops-security",
|
|
10
|
+
"role": "Run SAST/dependency scanning on codebase",
|
|
11
|
+
"subscribes": ["task.start"],
|
|
12
|
+
"publishes": ["scan.results"]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "reviewer",
|
|
16
|
+
"type": "review-security",
|
|
17
|
+
"role": "Review findings from scanner, assess severity",
|
|
18
|
+
"subscribes": ["scan.results"],
|
|
19
|
+
"publishes": ["review.findings"]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "adversarial",
|
|
23
|
+
"type": "review-security",
|
|
24
|
+
"role": "Attempt to exploit discovered vulnerabilities",
|
|
25
|
+
"subscribes": ["review.findings"],
|
|
26
|
+
"publishes": ["exploit.results"]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "reporter",
|
|
30
|
+
"type": "prod-techwriter",
|
|
31
|
+
"role": "Compile security report from all findings",
|
|
32
|
+
"subscribes": ["scan.results", "review.findings", "exploit.results"],
|
|
33
|
+
"publishes": ["task.complete"]
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|