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/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"
@@ -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
- local prd_path="${1:-}"
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")
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "6.0.0"
10
+ __version__ = "6.2.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v6.0.0
5
+ **Version:** v6.2.0
6
6
 
7
7
  ---
8
8
 
@@ -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.