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/run.sh
CHANGED
|
@@ -7034,21 +7034,163 @@ except: pass
|
|
|
7034
7034
|
" 2>/dev/null || true)
|
|
7035
7035
|
fi
|
|
7036
7036
|
|
|
7037
|
+
# BMAD context injection (if available)
|
|
7038
|
+
local bmad_context=""
|
|
7039
|
+
if [[ -f ".loki/bmad-metadata.json" ]]; then
|
|
7040
|
+
local bmad_arch=""
|
|
7041
|
+
if [[ -f ".loki/bmad-architecture-summary.md" ]]; then
|
|
7042
|
+
bmad_arch=$(head -c 16000 ".loki/bmad-architecture-summary.md")
|
|
7043
|
+
fi
|
|
7044
|
+
local bmad_tasks=""
|
|
7045
|
+
if [[ -f ".loki/bmad-tasks.json" ]]; then
|
|
7046
|
+
bmad_tasks=$(head -c 32000 ".loki/bmad-tasks.json")
|
|
7047
|
+
fi
|
|
7048
|
+
local bmad_validation=""
|
|
7049
|
+
if [[ -f ".loki/bmad-validation.md" ]]; then
|
|
7050
|
+
bmad_validation=$(head -c 8000 ".loki/bmad-validation.md")
|
|
7051
|
+
fi
|
|
7052
|
+
bmad_context="BMAD_CONTEXT: This project uses BMAD Method structured artifacts. Architecture decisions and epic/story breakdown are provided below."
|
|
7053
|
+
if [[ -n "$bmad_arch" ]]; then
|
|
7054
|
+
bmad_context="$bmad_context ARCHITECTURE DECISIONS: $bmad_arch"
|
|
7055
|
+
fi
|
|
7056
|
+
if [[ -n "$bmad_tasks" ]]; then
|
|
7057
|
+
bmad_context="$bmad_context EPIC/STORY TASKS (from BMAD): $bmad_tasks"
|
|
7058
|
+
fi
|
|
7059
|
+
if [[ -n "$bmad_validation" ]]; then
|
|
7060
|
+
bmad_context="$bmad_context ARTIFACT VALIDATION: $bmad_validation"
|
|
7061
|
+
fi
|
|
7062
|
+
fi
|
|
7063
|
+
|
|
7037
7064
|
if [ $retry -eq 0 ]; then
|
|
7038
7065
|
if [ -n "$prd" ]; then
|
|
7039
|
-
echo "Loki Mode with PRD at $prd. $human_directive $queue_tasks $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
7066
|
+
echo "Loki Mode with PRD at $prd. $human_directive $queue_tasks $bmad_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
7040
7067
|
else
|
|
7041
|
-
echo "Loki Mode. $human_directive $queue_tasks $checklist_status $app_runner_info $playwright_info $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
7068
|
+
echo "Loki Mode. $human_directive $queue_tasks $bmad_context $checklist_status $app_runner_info $playwright_info $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
7042
7069
|
fi
|
|
7043
7070
|
else
|
|
7044
7071
|
if [ -n "$prd" ]; then
|
|
7045
|
-
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $queue_tasks $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
7072
|
+
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $queue_tasks $bmad_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
7046
7073
|
else
|
|
7047
|
-
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $queue_tasks $checklist_status $app_runner_info $playwright_info $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
7074
|
+
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $queue_tasks $bmad_context $checklist_status $app_runner_info $playwright_info $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
7048
7075
|
fi
|
|
7049
7076
|
fi
|
|
7050
7077
|
}
|
|
7051
7078
|
|
|
7079
|
+
#===============================================================================
|
|
7080
|
+
# BMAD Task Queue Population
|
|
7081
|
+
#===============================================================================
|
|
7082
|
+
|
|
7083
|
+
# Populate the task queue from BMAD epic/story artifacts
|
|
7084
|
+
# Only runs once -- skips if queue was already populated from BMAD
|
|
7085
|
+
populate_bmad_queue() {
|
|
7086
|
+
# Skip if no BMAD tasks file
|
|
7087
|
+
if [[ ! -f ".loki/bmad-tasks.json" ]]; then
|
|
7088
|
+
return 0
|
|
7089
|
+
fi
|
|
7090
|
+
|
|
7091
|
+
# Skip if already populated (marker file)
|
|
7092
|
+
if [[ -f ".loki/queue/.bmad-populated" ]]; then
|
|
7093
|
+
log_info "BMAD queue already populated, skipping"
|
|
7094
|
+
return 0
|
|
7095
|
+
fi
|
|
7096
|
+
|
|
7097
|
+
log_step "Populating task queue from BMAD stories..."
|
|
7098
|
+
|
|
7099
|
+
# Ensure queue directory exists
|
|
7100
|
+
mkdir -p ".loki/queue"
|
|
7101
|
+
|
|
7102
|
+
# Read BMAD tasks and create queue entries
|
|
7103
|
+
python3 << 'BMAD_QUEUE_EOF' 2>/dev/null
|
|
7104
|
+
import json
|
|
7105
|
+
import os
|
|
7106
|
+
import sys
|
|
7107
|
+
|
|
7108
|
+
bmad_tasks_path = ".loki/bmad-tasks.json"
|
|
7109
|
+
pending_path = ".loki/queue/pending.json"
|
|
7110
|
+
|
|
7111
|
+
try:
|
|
7112
|
+
with open(bmad_tasks_path, "r") as f:
|
|
7113
|
+
bmad_data = json.load(f)
|
|
7114
|
+
except (json.JSONDecodeError, FileNotFoundError) as e:
|
|
7115
|
+
print(f"Warning: Could not read BMAD tasks: {e}", file=sys.stderr)
|
|
7116
|
+
sys.exit(0)
|
|
7117
|
+
|
|
7118
|
+
# Extract stories from BMAD structure
|
|
7119
|
+
# Supports both flat list and nested epic/story format
|
|
7120
|
+
stories = []
|
|
7121
|
+
if isinstance(bmad_data, list):
|
|
7122
|
+
stories = bmad_data
|
|
7123
|
+
elif isinstance(bmad_data, dict):
|
|
7124
|
+
# Handle {"epics": [...]} or {"tasks": [...]} formats
|
|
7125
|
+
for key in ("epics", "tasks", "stories"):
|
|
7126
|
+
if key in bmad_data:
|
|
7127
|
+
items = bmad_data[key]
|
|
7128
|
+
if isinstance(items, list):
|
|
7129
|
+
for item in items:
|
|
7130
|
+
if isinstance(item, dict) and "stories" in item:
|
|
7131
|
+
# Epic with nested stories
|
|
7132
|
+
epic_name = item.get("title", item.get("name", ""))
|
|
7133
|
+
for story in item["stories"]:
|
|
7134
|
+
if isinstance(story, dict):
|
|
7135
|
+
story.setdefault("epic", epic_name)
|
|
7136
|
+
stories.append(story)
|
|
7137
|
+
else:
|
|
7138
|
+
stories.append(item)
|
|
7139
|
+
break
|
|
7140
|
+
|
|
7141
|
+
if not stories:
|
|
7142
|
+
print("No BMAD stories found to queue", file=sys.stderr)
|
|
7143
|
+
sys.exit(0)
|
|
7144
|
+
|
|
7145
|
+
# Load existing pending tasks (if any)
|
|
7146
|
+
existing = []
|
|
7147
|
+
if os.path.exists(pending_path):
|
|
7148
|
+
try:
|
|
7149
|
+
with open(pending_path, "r") as f:
|
|
7150
|
+
data = json.load(f)
|
|
7151
|
+
if isinstance(data, list):
|
|
7152
|
+
existing = data
|
|
7153
|
+
elif isinstance(data, dict) and "tasks" in data:
|
|
7154
|
+
existing = data["tasks"]
|
|
7155
|
+
except (json.JSONDecodeError, FileNotFoundError):
|
|
7156
|
+
existing = []
|
|
7157
|
+
|
|
7158
|
+
# Convert BMAD stories to queue task format
|
|
7159
|
+
for i, story in enumerate(stories):
|
|
7160
|
+
if not isinstance(story, dict):
|
|
7161
|
+
continue
|
|
7162
|
+
task = {
|
|
7163
|
+
"id": f"bmad-{i+1}",
|
|
7164
|
+
"title": story.get("title", story.get("name", f"BMAD Story {i+1}")),
|
|
7165
|
+
"description": story.get("description", story.get("action", "")),
|
|
7166
|
+
"priority": story.get("priority", "medium"),
|
|
7167
|
+
"source": "bmad",
|
|
7168
|
+
}
|
|
7169
|
+
epic = story.get("epic", "")
|
|
7170
|
+
if epic:
|
|
7171
|
+
task["epic"] = epic
|
|
7172
|
+
acceptance = story.get("acceptance_criteria", story.get("criteria", []))
|
|
7173
|
+
if acceptance:
|
|
7174
|
+
task["acceptance_criteria"] = acceptance
|
|
7175
|
+
existing.append(task)
|
|
7176
|
+
|
|
7177
|
+
# Write updated pending queue
|
|
7178
|
+
with open(pending_path, "w") as f:
|
|
7179
|
+
json.dump(existing, f, indent=2)
|
|
7180
|
+
|
|
7181
|
+
print(f"Added {len(stories)} BMAD stories to task queue")
|
|
7182
|
+
BMAD_QUEUE_EOF
|
|
7183
|
+
|
|
7184
|
+
if [[ $? -ne 0 ]]; then
|
|
7185
|
+
log_warn "Failed to populate BMAD queue (python3 error)"
|
|
7186
|
+
return 0
|
|
7187
|
+
fi
|
|
7188
|
+
|
|
7189
|
+
# Mark as populated so we don't re-add on restart
|
|
7190
|
+
touch ".loki/queue/.bmad-populated"
|
|
7191
|
+
log_info "BMAD queue population complete"
|
|
7192
|
+
}
|
|
7193
|
+
|
|
7052
7194
|
#===============================================================================
|
|
7053
7195
|
# Main Autonomous Loop
|
|
7054
7196
|
#===============================================================================
|
|
@@ -7130,6 +7272,9 @@ run_autonomous() {
|
|
|
7130
7272
|
fi
|
|
7131
7273
|
fi
|
|
7132
7274
|
|
|
7275
|
+
# Populate task queue from BMAD artifacts (if present, runs once)
|
|
7276
|
+
populate_bmad_queue
|
|
7277
|
+
|
|
7133
7278
|
# Check max iterations before starting
|
|
7134
7279
|
if check_max_iterations; then
|
|
7135
7280
|
log_error "Max iterations already reached. Reset with: rm .loki/autonomy-state.json"
|
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
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# BMAD Integration -- Epic and Story Breakdown
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-02-25
|
|
4
|
+
**Scope:** Epic 1 only (P0 -- BMAD Artifact Pipeline)
|
|
5
|
+
**Version Target:** v6.1.0
|
|
6
|
+
|
|
7
|
+
Epics 2 (Engine Embedding) and 3 (Voice Agent Layer) are documented as future work
|
|
8
|
+
but explicitly deferred pending P0 value validation.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Epic 1: BMAD Artifact Pipeline (P0 -- Must Have)
|
|
13
|
+
|
|
14
|
+
**Goal:** Enable Loki Mode to consume BMAD Method output artifacts as structured input,
|
|
15
|
+
producing higher-quality analysis scores and richer execution context than freeform PRDs.
|
|
16
|
+
|
|
17
|
+
**Success Metric:** `loki start --bmad-project <path>` discovers, parses, and loads BMAD
|
|
18
|
+
artifacts with zero regression on existing non-BMAD workflows.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
### Story 1.1: BMAD PRD Format Adapter
|
|
23
|
+
|
|
24
|
+
**Size:** M
|
|
25
|
+
**Dependencies:** None
|
|
26
|
+
**Files to create:** `autonomy/bmad-adapter.py`
|
|
27
|
+
|
|
28
|
+
**Description:**
|
|
29
|
+
Create a standalone Python module (stdlib only) that discovers BMAD output artifacts,
|
|
30
|
+
parses their YAML frontmatter, normalizes heading structure, extracts project classification
|
|
31
|
+
metadata, and produces a normalized PRD document suitable for prd-analyzer.py.
|
|
32
|
+
|
|
33
|
+
**Acceptance Criteria:**
|
|
34
|
+
|
|
35
|
+
- **Given** a directory containing `_bmad-output/planning-artifacts/prd-*.md`
|
|
36
|
+
**When** bmad-adapter.py is invoked with the project path
|
|
37
|
+
**Then** it discovers the PRD file automatically
|
|
38
|
+
|
|
39
|
+
- **Given** a BMAD PRD with YAML frontmatter (`stepsCompleted`, `inputDocuments`, `workflowType`)
|
|
40
|
+
**When** the adapter parses it
|
|
41
|
+
**Then** frontmatter is extracted as metadata and stripped from the document body
|
|
42
|
+
|
|
43
|
+
- **Given** a BMAD PRD with sections like `## Executive Summary`, `## Project Classification`
|
|
44
|
+
**When** the adapter normalizes headings
|
|
45
|
+
**Then** sections are preserved as-is (no destructive remapping)
|
|
46
|
+
|
|
47
|
+
- **Given** a BMAD project with `architecture.md` alongside the PRD
|
|
48
|
+
**When** the adapter runs
|
|
49
|
+
**Then** architecture content is appended as supplementary context
|
|
50
|
+
|
|
51
|
+
- **Given** a BMAD project with `epics.md`
|
|
52
|
+
**When** the adapter runs
|
|
53
|
+
**Then** epic/story data is extracted into a structured task list (JSON)
|
|
54
|
+
|
|
55
|
+
- **Given** a non-BMAD project directory (no `_bmad-output/`)
|
|
56
|
+
**When** the adapter is invoked
|
|
57
|
+
**Then** it exits with a clear error message and non-zero exit code
|
|
58
|
+
|
|
59
|
+
- **Given** a BMAD project with incomplete workflow state (`stepsCompleted` missing entries)
|
|
60
|
+
**When** the adapter runs
|
|
61
|
+
**Then** it warns about incomplete artifacts but processes what exists
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### Story 1.2: Enhanced PRD Analyzer for BMAD Documents
|
|
66
|
+
|
|
67
|
+
**Size:** S
|
|
68
|
+
**Dependencies:** Story 1.1 (adapter output format)
|
|
69
|
+
**Files to modify:** `autonomy/prd-analyzer.py`
|
|
70
|
+
|
|
71
|
+
**Description:**
|
|
72
|
+
Add BMAD-specific heading patterns and content patterns to the existing dimension system.
|
|
73
|
+
Add an optional `--architecture` flag to score an architecture document alongside the PRD.
|
|
74
|
+
Maintain full backward compatibility with freeform PRDs.
|
|
75
|
+
|
|
76
|
+
**Acceptance Criteria:**
|
|
77
|
+
|
|
78
|
+
- **Given** a BMAD PRD with `## Executive Summary`
|
|
79
|
+
**When** prd-analyzer.py scores it
|
|
80
|
+
**Then** the section is recognized (new heading pattern in `feature_list` or new dimension)
|
|
81
|
+
|
|
82
|
+
- **Given** a BMAD PRD with `## Functional Requirements` containing `FR1:` items
|
|
83
|
+
**When** prd-analyzer.py scores it
|
|
84
|
+
**Then** `feature_list` dimension scores HIGH
|
|
85
|
+
|
|
86
|
+
- **Given** a BMAD PRD with `## Non-Functional Requirements` > `### Security`, `### Performance`
|
|
87
|
+
**When** prd-analyzer.py scores it
|
|
88
|
+
**Then** `security` and `deployment` dimensions score at least PARTIAL
|
|
89
|
+
|
|
90
|
+
- **Given** the `--architecture path/to/architecture.md` flag
|
|
91
|
+
**When** prd-analyzer.py runs
|
|
92
|
+
**Then** tech_stack, data_model, and api_spec dimensions are also scored from architecture.md
|
|
93
|
+
|
|
94
|
+
- **Given** a freeform PRD (no BMAD structure)
|
|
95
|
+
**When** prd-analyzer.py scores it
|
|
96
|
+
**Then** results are identical to the current version (zero regression)
|
|
97
|
+
|
|
98
|
+
- **Given** a BMAD PRD and a freeform PRD of equivalent completeness
|
|
99
|
+
**When** both are scored
|
|
100
|
+
**Then** BMAD PRD scores equal or higher (structured methodology bonus not required but allowed)
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### Story 1.3: `--bmad-project` CLI Flag
|
|
105
|
+
|
|
106
|
+
**Size:** M
|
|
107
|
+
**Dependencies:** Story 1.1 (adapter), Story 1.2 (enhanced analyzer)
|
|
108
|
+
**Files to modify:** `autonomy/loki`, `autonomy/run.sh`
|
|
109
|
+
|
|
110
|
+
**Description:**
|
|
111
|
+
Add a `--bmad-project <path>` flag to `loki start`. When provided, the CLI runs
|
|
112
|
+
bmad-adapter.py to discover and parse BMAD artifacts, then feeds the normalized output
|
|
113
|
+
into the standard PRD analysis pipeline. BMAD context (architecture decisions, epic list)
|
|
114
|
+
is injected into build_prompt() as a supplementary context block.
|
|
115
|
+
|
|
116
|
+
**Acceptance Criteria:**
|
|
117
|
+
|
|
118
|
+
- **Given** `loki start --bmad-project ./my-project`
|
|
119
|
+
**When** `./my-project/_bmad-output/` exists with BMAD artifacts
|
|
120
|
+
**Then** artifacts are discovered, parsed, and loaded into `.loki/` state
|
|
121
|
+
|
|
122
|
+
- **Given** `loki start --bmad-project ./my-project`
|
|
123
|
+
**When** `./my-project/_bmad-output/` does NOT exist
|
|
124
|
+
**Then** CLI prints error and exits with non-zero code
|
|
125
|
+
|
|
126
|
+
- **Given** BMAD artifacts are loaded
|
|
127
|
+
**When** build_prompt() constructs the iteration prompt
|
|
128
|
+
**Then** a `BMAD_CONTEXT` block is injected with architecture summary and active epic
|
|
129
|
+
|
|
130
|
+
- **Given** BMAD epics are loaded
|
|
131
|
+
**When** the task queue is populated
|
|
132
|
+
**Then** each BMAD story becomes a `.loki/queue/` task with priority and acceptance criteria
|
|
133
|
+
|
|
134
|
+
- **Given** `loki start ./prd.md` (no --bmad-project flag)
|
|
135
|
+
**When** the CLI runs
|
|
136
|
+
**Then** behavior is identical to current version (zero regression)
|
|
137
|
+
|
|
138
|
+
- **Given** `loki start --bmad-project ./my-project --prd ./override.md`
|
|
139
|
+
**When** both flags are provided
|
|
140
|
+
**Then** the explicit PRD takes precedence; BMAD artifacts provide supplementary context only
|
|
141
|
+
|
|
142
|
+
- **Given** the `--bmad-project` flag
|
|
143
|
+
**When** `loki help start` is run
|
|
144
|
+
**Then** the flag appears in help text with usage description
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### Story 1.4: BMAD Artifact Chain Validator
|
|
149
|
+
|
|
150
|
+
**Size:** S
|
|
151
|
+
**Dependencies:** Story 1.1 (adapter)
|
|
152
|
+
**Files to create:** Validation logic within `autonomy/bmad-adapter.py`
|
|
153
|
+
|
|
154
|
+
**Description:**
|
|
155
|
+
Validate that BMAD artifacts form a consistent chain: product-brief references are
|
|
156
|
+
reflected in PRD, PRD requirements trace to architecture decisions, architecture maps
|
|
157
|
+
to epics. Report consistency gaps as warnings (not blockers).
|
|
158
|
+
|
|
159
|
+
**Acceptance Criteria:**
|
|
160
|
+
|
|
161
|
+
- **Given** a BMAD project with product-brief, PRD, architecture, and epics
|
|
162
|
+
**When** the validator runs
|
|
163
|
+
**Then** it checks that PRD references product-brief themes
|
|
164
|
+
|
|
165
|
+
- **Given** a BMAD PRD with 20 Functional Requirements and epics.md
|
|
166
|
+
**When** the validator runs
|
|
167
|
+
**Then** it reports how many FRs are covered by at least one epic story
|
|
168
|
+
|
|
169
|
+
- **Given** a BMAD project missing architecture.md
|
|
170
|
+
**When** the validator runs
|
|
171
|
+
**Then** it warns about the missing artifact but does not block processing
|
|
172
|
+
|
|
173
|
+
- **Given** an inconsistency (FR in PRD with no matching story in epics)
|
|
174
|
+
**When** the validator runs
|
|
175
|
+
**Then** the gap is reported as a warning in the adapter output
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
### Story 1.5: BMAD Integration Tests
|
|
180
|
+
|
|
181
|
+
**Size:** M
|
|
182
|
+
**Dependencies:** Stories 1.1-1.4
|
|
183
|
+
**Files to create:** `tests/test-bmad-integration.sh`, `tests/fixtures/bmad/`
|
|
184
|
+
|
|
185
|
+
**Description:**
|
|
186
|
+
Comprehensive test suite covering BMAD adapter parsing, analyzer scoring, CLI flag
|
|
187
|
+
behavior, artifact chain validation, and backward compatibility. Test fixtures created
|
|
188
|
+
from BMAD's own templates populated with realistic data.
|
|
189
|
+
|
|
190
|
+
**Acceptance Criteria:**
|
|
191
|
+
|
|
192
|
+
- **Given** test fixtures in `tests/fixtures/bmad/` with realistic BMAD output
|
|
193
|
+
**When** `bash tests/test-bmad-integration.sh` runs
|
|
194
|
+
**Then** all tests pass with zero failures
|
|
195
|
+
|
|
196
|
+
- **Given** a fixture with a well-formed BMAD PRD
|
|
197
|
+
**When** the adapter and analyzer are run on it
|
|
198
|
+
**Then** quality score is >= 7.0/10
|
|
199
|
+
|
|
200
|
+
- **Given** a fixture with a freeform PRD (non-BMAD)
|
|
201
|
+
**When** the analyzer scores it before and after the Story 1.2 changes
|
|
202
|
+
**Then** scores are identical (backward compatibility)
|
|
203
|
+
|
|
204
|
+
- **Given** a fixture with malformed BMAD artifacts (missing frontmatter, partial steps)
|
|
205
|
+
**When** the adapter processes it
|
|
206
|
+
**Then** it handles gracefully with warnings, no crashes
|
|
207
|
+
|
|
208
|
+
- **Given** a fixture with incomplete artifact chain (PRD but no architecture)
|
|
209
|
+
**When** the adapter and validator run
|
|
210
|
+
**Then** processing succeeds with chain-gap warnings
|
|
211
|
+
|
|
212
|
+
- **Given** the `--bmad-project` CLI flag
|
|
213
|
+
**When** tested against fixtures
|
|
214
|
+
**Then** discovery, loading, and prompt injection all work correctly
|
|
215
|
+
|
|
216
|
+
- **Given** the test suite
|
|
217
|
+
**When** run after any code change
|
|
218
|
+
**Then** execution completes in under 30 seconds
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Epic 2: BMAD Engine Embedding (P1 -- Deferred)
|
|
223
|
+
|
|
224
|
+
**Goal:** Programmatically execute BMAD step-file workflows within Loki Mode, enabling
|
|
225
|
+
interactive requirements elicitation without requiring a separate BMAD-aware LLM session.
|
|
226
|
+
|
|
227
|
+
**Status:** Deferred. Requires P0 success validation first.
|
|
228
|
+
|
|
229
|
+
### Planned Stories (not detailed)
|
|
230
|
+
|
|
231
|
+
- Story 2.1: BMAD agent YAML parser
|
|
232
|
+
- Story 2.2: Step-file processor (execute BMAD workflows programmatically)
|
|
233
|
+
- Story 2.3: Session state manager (track step progress, partial artifacts)
|
|
234
|
+
- Story 2.4: Dashboard "Elicitation" panel
|
|
235
|
+
- Story 2.5: Party Mode adapter (multi-agent collaboration)
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Epic 3: Voice Agent Layer (P2 -- Deferred)
|
|
240
|
+
|
|
241
|
+
**Goal:** Transform voice.sh from simple transcription to a structured dialogue system
|
|
242
|
+
that can facilitate BMAD requirements elicitation conversations.
|
|
243
|
+
|
|
244
|
+
**Status:** Deferred. Highest risk. Requires both P0 and P1 success validation.
|
|
245
|
+
|
|
246
|
+
### Planned Stories (not detailed)
|
|
247
|
+
|
|
248
|
+
- Story 3.1: Structured dialogue manager
|
|
249
|
+
- Story 3.2: BMAD step-to-voice mapper
|
|
250
|
+
- Story 3.3: Technical term correction loop
|
|
251
|
+
- Story 3.4: Voice session persistence
|
|
252
|
+
- Story 3.5: Dual-mode interface (voice for elicitation, visual for review)
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Dependency Graph
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
Story 1.1 (Adapter) -----> Story 1.2 (Analyzer Enhancement)
|
|
260
|
+
| |
|
|
261
|
+
| v
|
|
262
|
+
+---> Story 1.4 (Validator) ---> Story 1.3 (CLI Flag)
|
|
263
|
+
|
|
|
264
|
+
v
|
|
265
|
+
Story 1.5 (Tests)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Stories 1.1 and 1.2 can be developed in parallel.
|
|
269
|
+
Story 1.3 depends on both 1.1 and 1.2.
|
|
270
|
+
Story 1.4 is part of 1.1 but can be developed in parallel.
|
|
271
|
+
Story 1.5 depends on all other stories but fixture creation can start immediately.
|