loki-mode 6.0.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/README.md +20 -0
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/bmad-adapter.py +776 -0
- package/autonomy/loki +393 -0
- package/autonomy/prd-analyzer.py +26 -4
- package/autonomy/run.sh +149 -4
- package/autonomy/sandbox.sh +181 -1
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/architecture/bmad-integration-epic.md +271 -0
- package/docs/architecture/bmad-integration-review.md +86 -0
- package/docs/architecture/bmad-integration-validation.md +249 -0
- package/docs/architecture/bmad-loki-voice-agent-council-analysis.md +61 -0
- package/mcp/__init__.py +1 -1
- package/mcp/requirements.txt +1 -0
- package/mcp/server.py +152 -0
- 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/autonomy/loki
CHANGED
|
@@ -438,6 +438,7 @@ show_help() {
|
|
|
438
438
|
echo " --skip-memory Skip loading memory context at startup"
|
|
439
439
|
echo " --compliance PRESET Enable compliance mode (default|healthcare|fintech|government)"
|
|
440
440
|
echo " --budget USD Set cost budget limit (display in dashboard/status)"
|
|
441
|
+
echo " --bmad-project PATH Use BMAD Method project artifacts as input"
|
|
441
442
|
echo ""
|
|
442
443
|
echo "Options for 'run' (v6.0.0):"
|
|
443
444
|
echo " --dry-run Preview generated PRD without starting"
|
|
@@ -447,6 +448,12 @@ show_help() {
|
|
|
447
448
|
echo " --parallel Enable parallel mode with git worktrees"
|
|
448
449
|
echo " --budget USD Set cost budget limit"
|
|
449
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 ""
|
|
450
457
|
echo "Examples:"
|
|
451
458
|
echo " loki run 123 # GitHub issue from current repo"
|
|
452
459
|
echo " loki run PROJ-456 # Jira issue"
|
|
@@ -473,6 +480,7 @@ cmd_start() {
|
|
|
473
480
|
local args=()
|
|
474
481
|
local prd_file=""
|
|
475
482
|
local provider=""
|
|
483
|
+
local bmad_project_path=""
|
|
476
484
|
|
|
477
485
|
while [[ $# -gt 0 ]]; do
|
|
478
486
|
case "$1" in
|
|
@@ -496,6 +504,7 @@ cmd_start() {
|
|
|
496
504
|
echo " --skip-memory Skip loading memory context at startup"
|
|
497
505
|
echo " --compliance PRESET Enable compliance mode (default|healthcare|fintech|government)"
|
|
498
506
|
echo " --budget USD Cost budget limit (auto-pause when exceeded)"
|
|
507
|
+
echo " --bmad-project PATH Use BMAD Method project artifacts as input"
|
|
499
508
|
echo " --yes, -y Skip confirmation prompts (auto-confirm)"
|
|
500
509
|
echo ""
|
|
501
510
|
echo "Environment Variables:"
|
|
@@ -510,6 +519,7 @@ cmd_start() {
|
|
|
510
519
|
echo " loki start ./prd.md # Start with PRD file"
|
|
511
520
|
echo " loki start ./prd.md --parallel # Parallel mode with worktrees"
|
|
512
521
|
echo " loki start --provider codex # Use OpenAI Codex CLI"
|
|
522
|
+
echo " loki start --bmad-project ./my-project # Start from BMAD artifacts"
|
|
513
523
|
echo " loki start --yes # Skip confirmation prompt"
|
|
514
524
|
echo " LOKI_PRD_FILE=./prd.md loki start # PRD via env var"
|
|
515
525
|
exit 0
|
|
@@ -581,6 +591,19 @@ cmd_start() {
|
|
|
581
591
|
export LOKI_AUTO_CONFIRM=true
|
|
582
592
|
shift
|
|
583
593
|
;;
|
|
594
|
+
--bmad-project)
|
|
595
|
+
if [[ -n "${2:-}" ]]; then
|
|
596
|
+
bmad_project_path="$2"
|
|
597
|
+
shift 2
|
|
598
|
+
else
|
|
599
|
+
echo -e "${RED}--bmad-project requires a path to a BMAD project directory${NC}"
|
|
600
|
+
exit 1
|
|
601
|
+
fi
|
|
602
|
+
;;
|
|
603
|
+
--bmad-project=*)
|
|
604
|
+
bmad_project_path="${1#*=}"
|
|
605
|
+
shift
|
|
606
|
+
;;
|
|
584
607
|
--budget)
|
|
585
608
|
if [[ -n "${2:-}" ]]; then
|
|
586
609
|
if ! echo "$2" | grep -qE '^[0-9]+(\.[0-9]+)?$'; then
|
|
@@ -619,6 +642,67 @@ cmd_start() {
|
|
|
619
642
|
prd_file="$LOKI_PRD_FILE"
|
|
620
643
|
fi
|
|
621
644
|
|
|
645
|
+
# BMAD project validation and adapter execution
|
|
646
|
+
if [[ -n "$bmad_project_path" ]]; then
|
|
647
|
+
# Resolve to absolute path
|
|
648
|
+
if [[ ! "$bmad_project_path" = /* ]]; then
|
|
649
|
+
bmad_project_path="$(cd "$bmad_project_path" 2>/dev/null && pwd)" || {
|
|
650
|
+
echo -e "${RED}Error: BMAD project path does not exist: $bmad_project_path${NC}"
|
|
651
|
+
exit 1
|
|
652
|
+
}
|
|
653
|
+
fi
|
|
654
|
+
|
|
655
|
+
# Validate path is a directory
|
|
656
|
+
if [[ ! -d "$bmad_project_path" ]]; then
|
|
657
|
+
echo -e "${RED}Error: BMAD project path is not a directory: $bmad_project_path${NC}"
|
|
658
|
+
exit 1
|
|
659
|
+
fi
|
|
660
|
+
|
|
661
|
+
# Check for BMAD artifacts (_bmad-output/ or common BMAD markers)
|
|
662
|
+
if [[ ! -d "$bmad_project_path/_bmad-output" ]]; then
|
|
663
|
+
echo -e "${YELLOW}Warning: No _bmad-output/ directory found in $bmad_project_path${NC}"
|
|
664
|
+
echo "Expected a BMAD Method project with _bmad-output/ artifacts."
|
|
665
|
+
echo -e "Continue anyway? [y/N] \\c"
|
|
666
|
+
local _auto="${LOKI_AUTO_CONFIRM:-${CI:-false}}"
|
|
667
|
+
if [[ "$_auto" == "true" ]]; then
|
|
668
|
+
echo "y (auto-confirmed)"
|
|
669
|
+
else
|
|
670
|
+
read -r _bmad_confirm
|
|
671
|
+
if [[ ! "$_bmad_confirm" =~ ^[Yy] ]]; then
|
|
672
|
+
echo "Aborted."
|
|
673
|
+
exit 0
|
|
674
|
+
fi
|
|
675
|
+
fi
|
|
676
|
+
fi
|
|
677
|
+
|
|
678
|
+
# Export for run.sh to access
|
|
679
|
+
export BMAD_PROJECT_PATH="$bmad_project_path"
|
|
680
|
+
|
|
681
|
+
# Ensure .loki directory exists for adapter output
|
|
682
|
+
mkdir -p "$LOKI_DIR"
|
|
683
|
+
|
|
684
|
+
# Run the BMAD adapter to normalize artifacts
|
|
685
|
+
echo -e "${CYAN}Running BMAD adapter...${NC}"
|
|
686
|
+
local adapter_script="${SCRIPT_DIR:-$(dirname "$0")}/bmad-adapter.py"
|
|
687
|
+
if [[ ! -f "$adapter_script" ]]; then
|
|
688
|
+
echo -e "${RED}Error: BMAD adapter not found at $adapter_script${NC}"
|
|
689
|
+
echo "Please ensure autonomy/bmad-adapter.py exists."
|
|
690
|
+
exit 1
|
|
691
|
+
fi
|
|
692
|
+
|
|
693
|
+
if ! python3 "$adapter_script" "$bmad_project_path" --output-dir "$LOKI_DIR" --validate; then
|
|
694
|
+
echo -e "${RED}Error: BMAD adapter failed. Check the project artifacts.${NC}"
|
|
695
|
+
exit 1
|
|
696
|
+
fi
|
|
697
|
+
echo -e "${GREEN}BMAD artifacts normalized successfully.${NC}"
|
|
698
|
+
|
|
699
|
+
# If no explicit PRD was provided, use the normalized BMAD PRD
|
|
700
|
+
if [[ -z "$prd_file" ]] && [[ -f "$LOKI_DIR/bmad-prd-normalized.md" ]]; then
|
|
701
|
+
prd_file="$LOKI_DIR/bmad-prd-normalized.md"
|
|
702
|
+
echo -e "${CYAN}Using normalized BMAD PRD: $prd_file${NC}"
|
|
703
|
+
fi
|
|
704
|
+
fi
|
|
705
|
+
|
|
622
706
|
if [ -n "$prd_file" ]; then
|
|
623
707
|
args+=("$prd_file")
|
|
624
708
|
else
|
|
@@ -2441,6 +2525,10 @@ cmd_run() {
|
|
|
2441
2525
|
local start_args=()
|
|
2442
2526
|
local no_start=false
|
|
2443
2527
|
local provider_override=""
|
|
2528
|
+
local use_worktree=false
|
|
2529
|
+
local create_pr=false
|
|
2530
|
+
local auto_merge=false
|
|
2531
|
+
local run_detached=false
|
|
2444
2532
|
|
|
2445
2533
|
# Parse arguments
|
|
2446
2534
|
while [[ $# -gt 0 ]]; do
|
|
@@ -2473,6 +2561,14 @@ cmd_run() {
|
|
|
2473
2561
|
echo " --sandbox Run in Docker sandbox"
|
|
2474
2562
|
echo " --budget USD Set cost budget limit"
|
|
2475
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 ""
|
|
2476
2572
|
echo "Environment Variables:"
|
|
2477
2573
|
echo " JIRA_API_TOKEN Jira API token (for Jira issues)"
|
|
2478
2574
|
echo " JIRA_URL Jira base URL (for Jira issues)"
|
|
@@ -2485,6 +2581,10 @@ cmd_run() {
|
|
|
2485
2581
|
echo " loki run https://gitlab.com/o/r/-/issues/42 # GitLab issue"
|
|
2486
2582
|
echo " loki run 123 --dry-run # Preview PRD"
|
|
2487
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"
|
|
2488
2588
|
exit 0
|
|
2489
2589
|
;;
|
|
2490
2590
|
--dry-run)
|
|
@@ -2548,6 +2648,30 @@ cmd_run() {
|
|
|
2548
2648
|
start_args+=("--budget" "${1#*=}")
|
|
2549
2649
|
shift
|
|
2550
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
|
+
;;
|
|
2551
2675
|
-*)
|
|
2552
2676
|
echo -e "${RED}Unknown option: $1${NC}"
|
|
2553
2677
|
echo "Run 'loki run --help' for usage."
|
|
@@ -2609,10 +2733,46 @@ cmd_run() {
|
|
|
2609
2733
|
fi
|
|
2610
2734
|
echo ""
|
|
2611
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
|
+
|
|
2612
2744
|
# Generate PRD
|
|
2613
2745
|
local prd_content
|
|
2614
2746
|
prd_content=$(echo "$issue_json" | generate_prd_from_issue)
|
|
2615
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
|
+
|
|
2616
2776
|
# Handle dry-run
|
|
2617
2777
|
if [[ "$dry_run" == "true" ]]; then
|
|
2618
2778
|
echo -e "${BOLD}Generated PRD Preview:${NC}"
|
|
@@ -2650,6 +2810,75 @@ cmd_run() {
|
|
|
2650
2810
|
echo ""
|
|
2651
2811
|
echo -e "${GREEN}Starting Loki Mode with generated PRD...${NC}"
|
|
2652
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
|
|
2653
2882
|
fi
|
|
2654
2883
|
}
|
|
2655
2884
|
|
|
@@ -6661,6 +6890,167 @@ cmd_migrate() {
|
|
|
6661
6890
|
cmd_migrate_start "$codebase_path" "$target" "$plan_only" "$phase" "$parallel" "$compliance" "$dry_run" "$do_resume" "$multi_repo" "$export_report" "$no_dashboard" "$no_docs"
|
|
6662
6891
|
}
|
|
6663
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
|
+
|
|
6664
7054
|
# Main command dispatcher
|
|
6665
7055
|
main() {
|
|
6666
7056
|
if [ $# -eq 0 ]; then
|
|
@@ -6791,6 +7181,9 @@ main() {
|
|
|
6791
7181
|
migrate)
|
|
6792
7182
|
cmd_migrate "$@"
|
|
6793
7183
|
;;
|
|
7184
|
+
cluster)
|
|
7185
|
+
cmd_cluster "$@"
|
|
7186
|
+
;;
|
|
6794
7187
|
metrics)
|
|
6795
7188
|
cmd_metrics "$@"
|
|
6796
7189
|
;;
|
package/autonomy/prd-analyzer.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""PRD Quality Analyzer for Loki Mode
|
|
2
|
+
"""PRD Quality Analyzer for Loki Mode v6.1.0
|
|
3
3
|
|
|
4
4
|
Analyzes PRD structure and completeness using regex-based heuristics.
|
|
5
5
|
Writes observations to .loki/prd-observations.md with quality score,
|
|
@@ -10,6 +10,7 @@ Stdlib only - no pip dependencies required.
|
|
|
10
10
|
Usage:
|
|
11
11
|
python3 prd-analyzer.py path/to/prd.md --output .loki/prd-observations.md
|
|
12
12
|
python3 prd-analyzer.py path/to/prd.md --output .loki/prd-observations.md --interactive
|
|
13
|
+
python3 prd-analyzer.py path/to/prd.md --architecture path/to/architecture.md
|
|
13
14
|
"""
|
|
14
15
|
|
|
15
16
|
import argparse
|
|
@@ -28,10 +29,13 @@ DIMENSIONS = {
|
|
|
28
29
|
"weight": 1.5,
|
|
29
30
|
"heading_patterns": [
|
|
30
31
|
r"(?i)#+\s.*(?:feature|requirement|scope|functional|capability)",
|
|
32
|
+
r"(?i)^##\s+Functional\s+Requirements",
|
|
33
|
+
r"(?i)^##\s+Product\s+Scope",
|
|
31
34
|
],
|
|
32
35
|
"content_patterns": [
|
|
33
36
|
r"^\s*[-*]\s+\S",
|
|
34
37
|
r"^\s*\d+\.\s+\S",
|
|
38
|
+
r"(?i)^FR\d+:",
|
|
35
39
|
],
|
|
36
40
|
"description": "Numbered or bulleted list of features/requirements",
|
|
37
41
|
},
|
|
@@ -55,6 +59,7 @@ DIMENSIONS = {
|
|
|
55
59
|
"weight": 1.0,
|
|
56
60
|
"heading_patterns": [
|
|
57
61
|
r"(?i)#+\s.*(?:user\s+(?:stor|flow|journey)|persona|use\s+case)",
|
|
62
|
+
r"(?i)^##\s+User\s+Journeys",
|
|
58
63
|
],
|
|
59
64
|
"content_patterns": [
|
|
60
65
|
r"(?i)\bas\s+a\s+\w+",
|
|
@@ -68,11 +73,13 @@ DIMENSIONS = {
|
|
|
68
73
|
"weight": 1.0,
|
|
69
74
|
"heading_patterns": [
|
|
70
75
|
r"(?i)#+\s.*(?:acceptance|criteria|definition\s+of\s+done|done\s+when)",
|
|
76
|
+
r"(?i)^##\s+Success\s+Criteria",
|
|
71
77
|
],
|
|
72
78
|
"content_patterns": [
|
|
73
79
|
r"^\s*-\s*\[\s*[xX ]?\s*\]",
|
|
74
80
|
r"(?i)\bgiven\b.*\bwhen\b.*\bthen\b",
|
|
75
81
|
r"(?i)\bmust\s+(?:be|have|support|handle)\b",
|
|
82
|
+
r"(?i)^\s*\*\*Given\*\*",
|
|
76
83
|
],
|
|
77
84
|
"description": "Measurable completion criteria or checklists",
|
|
78
85
|
},
|
|
@@ -107,6 +114,7 @@ DIMENSIONS = {
|
|
|
107
114
|
"weight": 0.75,
|
|
108
115
|
"heading_patterns": [
|
|
109
116
|
r"(?i)#+\s.*(?:deploy|hosting|infra|ci.?cd|environment)",
|
|
117
|
+
r"(?i)^##\s+Non-Functional\s+Requirements",
|
|
110
118
|
],
|
|
111
119
|
"content_patterns": [
|
|
112
120
|
r"(?i)\b(?:deploy|hosting|ci.?cd|pipeline|staging|production)\b",
|
|
@@ -131,6 +139,7 @@ DIMENSIONS = {
|
|
|
131
139
|
"weight": 1.0,
|
|
132
140
|
"heading_patterns": [
|
|
133
141
|
r"(?i)#+\s.*(?:security|auth|permission|access\s+control)",
|
|
142
|
+
r"(?i)^##\s+Non-Functional\s+Requirements",
|
|
134
143
|
],
|
|
135
144
|
"content_patterns": [
|
|
136
145
|
r"(?i)\b(?:auth(?:entication|orization)?|oauth|jwt|token)\b",
|
|
@@ -153,8 +162,9 @@ SCOPE_THRESHOLDS = [
|
|
|
153
162
|
class PrdAnalyzer:
|
|
154
163
|
"""Analyzes PRD quality and completeness."""
|
|
155
164
|
|
|
156
|
-
def __init__(self, prd_path):
|
|
165
|
+
def __init__(self, prd_path, architecture_path=None):
|
|
157
166
|
self.prd_path = Path(prd_path)
|
|
167
|
+
self.architecture_path = Path(architecture_path) if architecture_path else None
|
|
158
168
|
self.content = ""
|
|
159
169
|
self.lines = []
|
|
160
170
|
self.results = {}
|
|
@@ -163,13 +173,20 @@ class PrdAnalyzer:
|
|
|
163
173
|
self.score = 0.0
|
|
164
174
|
|
|
165
175
|
def load(self):
|
|
166
|
-
"""Load and validate the PRD file."""
|
|
176
|
+
"""Load and validate the PRD file, optionally appending architecture doc."""
|
|
167
177
|
if not self.prd_path.exists():
|
|
168
178
|
raise FileNotFoundError(f"PRD file not found: {self.prd_path}")
|
|
169
179
|
self.content = self.prd_path.read_text(encoding="utf-8", errors="replace")
|
|
170
180
|
if not self.content.strip():
|
|
171
181
|
raise ValueError(f"PRD file is empty: {self.prd_path}")
|
|
172
182
|
self.lines = self.content.splitlines()
|
|
183
|
+
# Append architecture document lines for pattern matching
|
|
184
|
+
if self.architecture_path:
|
|
185
|
+
if not self.architecture_path.exists():
|
|
186
|
+
raise FileNotFoundError(f"Architecture file not found: {self.architecture_path}")
|
|
187
|
+
arch_content = self.architecture_path.read_text(encoding="utf-8", errors="replace")
|
|
188
|
+
if arch_content.strip():
|
|
189
|
+
self.lines.extend(arch_content.splitlines())
|
|
173
190
|
|
|
174
191
|
def analyze(self):
|
|
175
192
|
"""Run all analysis dimensions and compute score."""
|
|
@@ -420,6 +437,11 @@ def main():
|
|
|
420
437
|
default=".loki/prd-observations.md",
|
|
421
438
|
help="Output path for observations (default: .loki/prd-observations.md)",
|
|
422
439
|
)
|
|
440
|
+
parser.add_argument(
|
|
441
|
+
"--architecture",
|
|
442
|
+
metavar="PATH",
|
|
443
|
+
help="Optional architecture document to include in analysis",
|
|
444
|
+
)
|
|
423
445
|
parser.add_argument(
|
|
424
446
|
"--interactive",
|
|
425
447
|
action="store_true",
|
|
@@ -428,7 +450,7 @@ def main():
|
|
|
428
450
|
args = parser.parse_args()
|
|
429
451
|
|
|
430
452
|
try:
|
|
431
|
-
analyzer = PrdAnalyzer(args.prd_path)
|
|
453
|
+
analyzer = PrdAnalyzer(args.prd_path, architecture_path=args.architecture)
|
|
432
454
|
analyzer.analyze()
|
|
433
455
|
observations = analyzer.generate_observations()
|
|
434
456
|
|