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/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
  ;;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """PRD Quality Analyzer for Loki Mode v5.44.0
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