loki-mode 6.23.0 → 6.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v6.23.0
6
+ # Loki Mode v6.24.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -267,4 +267,4 @@ The following features are documented in skill modules but not yet fully automat
267
267
  | Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
268
268
  | Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
269
269
 
270
- **v6.23.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
270
+ **v6.24.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 6.23.0
1
+ 6.24.0
package/autonomy/loki CHANGED
@@ -439,6 +439,7 @@ show_help() {
439
439
  echo " onboard [path] Analyze a repo and generate CLAUDE.md (structure, conventions, commands)"
440
440
  echo " plan <PRD> Dry-run PRD analysis: complexity, cost, and execution plan"
441
441
  echo " ci [opts] CI/CD quality gate integration (--pr, --report, --github-comment)"
442
+ echo " test [opts] AI-powered test generation (--file, --dir, --changed, --dry-run)"
442
443
  echo " version Show version"
443
444
  echo " help Show this help"
444
445
  echo ""
@@ -9197,6 +9198,9 @@ main() {
9197
9198
  ci)
9198
9199
  cmd_ci "$@"
9199
9200
  ;;
9201
+ test)
9202
+ cmd_test "$@"
9203
+ ;;
9200
9204
  version|--version|-v)
9201
9205
  cmd_version
9202
9206
  ;;
@@ -15326,4 +15330,963 @@ for t in tests:
15326
15330
  return "$exit_code"
15327
15331
  }
15328
15332
 
15333
+ # AI-powered test generation (v6.24.0)
15334
+ cmd_test() {
15335
+ local test_file=""
15336
+ local test_dir=""
15337
+ local test_changed=false
15338
+ local test_coverage=80
15339
+ local test_format=""
15340
+ local test_output=""
15341
+ local test_dry_run=false
15342
+ local test_json=false
15343
+ local test_verbose=false
15344
+
15345
+ while [[ $# -gt 0 ]]; do
15346
+ case "$1" in
15347
+ --help|-h)
15348
+ echo -e "${BOLD}loki test${NC} - AI-powered test generation"
15349
+ echo ""
15350
+ echo "Usage: loki test [options] [file-or-dir]"
15351
+ echo ""
15352
+ echo "Analyzes source files and generates comprehensive test suites."
15353
+ echo "Detects language and framework automatically. Works without API keys."
15354
+ echo ""
15355
+ echo "Sources (default: git-changed files):"
15356
+ echo " --file <path> Test a specific file"
15357
+ echo " --dir <path> Test all files in a directory"
15358
+ echo " --changed Test only git-changed files (default if no source given)"
15359
+ echo " <file-or-dir> Positional: auto-detect file or directory"
15360
+ echo ""
15361
+ echo "Options:"
15362
+ echo " --coverage <n> Target coverage percentage (default: 80)"
15363
+ echo " --format <fmt> Test framework: auto, jest, pytest, vitest, mocha, go-test"
15364
+ echo " --output <path> Custom output directory for generated tests"
15365
+ echo " --dry-run Preview what tests would be generated without writing"
15366
+ echo " --json JSON output"
15367
+ echo " --verbose Detailed output"
15368
+ echo " --help, -h Show this help"
15369
+ echo ""
15370
+ echo "Supported languages:"
15371
+ echo " JavaScript/TypeScript (.js, .ts, .jsx, .tsx) -> jest, vitest, mocha"
15372
+ echo " Python (.py) -> pytest"
15373
+ echo " Go (.go) -> go-test"
15374
+ echo " Rust (.rs) -> cargo-test"
15375
+ echo " Java (.java) -> junit"
15376
+ echo " Ruby (.rb) -> rspec"
15377
+ echo " Shell/Bash (.sh) -> bats"
15378
+ echo ""
15379
+ echo "Exit codes:"
15380
+ echo " 0 Tests generated successfully"
15381
+ echo " 1 No testable files found"
15382
+ echo " 2 Error (invalid arguments, missing paths)"
15383
+ echo ""
15384
+ echo "Examples:"
15385
+ echo " loki test # Generate tests for git-changed files"
15386
+ echo " loki test --file src/utils.ts # Test a specific file"
15387
+ echo " loki test --dir src/lib/ # Test all files in directory"
15388
+ echo " loki test --changed --format jest # Changed files, force Jest format"
15389
+ echo " loki test --dry-run # Preview without writing"
15390
+ echo " loki test --coverage 90 # Target 90% coverage"
15391
+ echo " loki test --json # Machine-readable output"
15392
+ echo " loki test src/utils.ts # Positional file argument"
15393
+ return 0
15394
+ ;;
15395
+ --file)
15396
+ shift
15397
+ test_file="${1:-}"
15398
+ if [ -z "$test_file" ]; then
15399
+ echo -e "${RED}Error: --file requires a path${NC}"
15400
+ return 2
15401
+ fi
15402
+ shift
15403
+ ;;
15404
+ --dir)
15405
+ shift
15406
+ test_dir="${1:-}"
15407
+ if [ -z "$test_dir" ]; then
15408
+ echo -e "${RED}Error: --dir requires a path${NC}"
15409
+ return 2
15410
+ fi
15411
+ shift
15412
+ ;;
15413
+ --changed) test_changed=true; shift ;;
15414
+ --coverage)
15415
+ shift
15416
+ test_coverage="${1:-80}"
15417
+ if ! [[ "$test_coverage" =~ ^[0-9]+$ ]] || [ "$test_coverage" -lt 1 ] || [ "$test_coverage" -gt 100 ]; then
15418
+ echo -e "${RED}Error: --coverage must be a number between 1 and 100${NC}"
15419
+ return 2
15420
+ fi
15421
+ shift
15422
+ ;;
15423
+ --format)
15424
+ shift
15425
+ test_format="${1:-}"
15426
+ if [ -z "$test_format" ]; then
15427
+ echo -e "${RED}Error: --format requires a framework name${NC}"
15428
+ return 2
15429
+ fi
15430
+ test_format="$(echo "$test_format" | tr '[:upper:]' '[:lower:]')"
15431
+ case "$test_format" in
15432
+ auto|jest|pytest|vitest|mocha|go-test|cargo-test|junit|rspec|bats) ;;
15433
+ *) echo -e "${RED}Error: unsupported format '$test_format'. Use: auto, jest, pytest, vitest, mocha, go-test, cargo-test, junit, rspec, bats${NC}"; return 2 ;;
15434
+ esac
15435
+ shift
15436
+ ;;
15437
+ --output)
15438
+ shift
15439
+ test_output="${1:-}"
15440
+ if [ -z "$test_output" ]; then
15441
+ echo -e "${RED}Error: --output requires a path${NC}"
15442
+ return 2
15443
+ fi
15444
+ shift
15445
+ ;;
15446
+ --dry-run) test_dry_run=true; shift ;;
15447
+ --json) test_json=true; shift ;;
15448
+ --verbose) test_verbose=true; shift ;;
15449
+ -*)
15450
+ echo -e "${RED}Unknown option: $1${NC}"
15451
+ echo "Run 'loki test --help' for usage."
15452
+ return 2
15453
+ ;;
15454
+ *)
15455
+ # Positional argument: auto-detect file or directory
15456
+ if [ -f "$1" ]; then
15457
+ test_file="$1"
15458
+ elif [ -d "$1" ]; then
15459
+ test_dir="$1"
15460
+ else
15461
+ echo -e "${RED}Error: '$1' is not a file or directory${NC}"
15462
+ return 2
15463
+ fi
15464
+ shift
15465
+ ;;
15466
+ esac
15467
+ done
15468
+
15469
+ # Default to --changed if no source specified
15470
+ if [ -z "$test_file" ] && [ -z "$test_dir" ] && [ "$test_changed" = false ]; then
15471
+ test_changed=true
15472
+ fi
15473
+
15474
+ # --- Collect source files ---
15475
+ local -a source_files=()
15476
+
15477
+ if [ -n "$test_file" ]; then
15478
+ if [ ! -f "$test_file" ]; then
15479
+ echo -e "${RED}Error: file not found: $test_file${NC}"
15480
+ return 2
15481
+ fi
15482
+ source_files+=("$test_file")
15483
+ elif [ -n "$test_dir" ]; then
15484
+ if [ ! -d "$test_dir" ]; then
15485
+ echo -e "${RED}Error: directory not found: $test_dir${NC}"
15486
+ return 2
15487
+ fi
15488
+ while IFS= read -r f; do
15489
+ source_files+=("$f")
15490
+ done < <(_test_find_source_files "$test_dir")
15491
+ elif [ "$test_changed" = true ]; then
15492
+ while IFS= read -r f; do
15493
+ [ -n "$f" ] && [ -f "$f" ] && source_files+=("$f")
15494
+ done < <(git diff --name-only HEAD 2>/dev/null; git diff --name-only --cached 2>/dev/null; git diff --name-only 2>/dev/null)
15495
+ # Deduplicate
15496
+ if [ ${#source_files[@]} -gt 0 ]; then
15497
+ local -a unique_files=()
15498
+ local seen_list=""
15499
+ for f in "${source_files[@]}"; do
15500
+ if [[ "$seen_list" != *"|$f|"* ]]; then
15501
+ seen_list="${seen_list}|$f|"
15502
+ unique_files+=("$f")
15503
+ fi
15504
+ done
15505
+ source_files=("${unique_files[@]}")
15506
+ fi
15507
+ fi
15508
+
15509
+ # Filter to testable source files only (exclude test files, configs, etc.)
15510
+ local -a testable_files=()
15511
+ for f in "${source_files[@]}"; do
15512
+ if _test_is_testable_file "$f"; then
15513
+ testable_files+=("$f")
15514
+ fi
15515
+ done
15516
+
15517
+ if [ ${#testable_files[@]} -eq 0 ]; then
15518
+ if [ "$test_json" = true ]; then
15519
+ echo '{"status":"no_files","message":"No testable source files found","files_scanned":'"${#source_files[@]}"'}'
15520
+ else
15521
+ echo -e "${YELLOW}No testable source files found.${NC}"
15522
+ if [ "$test_changed" = true ]; then
15523
+ echo "Tip: Use --file or --dir to specify source files directly."
15524
+ fi
15525
+ fi
15526
+ return 1
15527
+ fi
15528
+
15529
+ # --- Generate tests ---
15530
+ local total_files=${#testable_files[@]}
15531
+ local generated=0
15532
+ local skipped=0
15533
+ local -a generated_paths=()
15534
+ local json_entries=""
15535
+
15536
+ if [ "$test_json" = false ] && [ "$test_dry_run" = false ]; then
15537
+ echo -e "${BOLD}Loki Test Generator${NC}"
15538
+ echo -e "Target coverage: ${CYAN}${test_coverage}%${NC}"
15539
+ echo -e "Files to process: ${CYAN}${total_files}${NC}"
15540
+ echo ""
15541
+ elif [ "$test_json" = false ] && [ "$test_dry_run" = true ]; then
15542
+ echo -e "${BOLD}Loki Test Generator (dry run)${NC}"
15543
+ echo -e "Files to process: ${CYAN}${total_files}${NC}"
15544
+ echo ""
15545
+ fi
15546
+
15547
+ for src_file in "${testable_files[@]}"; do
15548
+ local lang=""
15549
+ local framework=""
15550
+ local test_path=""
15551
+
15552
+ # Detect language
15553
+ lang=$(_test_detect_language "$src_file")
15554
+ if [ -z "$lang" ]; then
15555
+ ((++skipped))
15556
+ [ "$test_verbose" = true ] && echo -e " ${DIM}Skip: $src_file (unsupported language)${NC}"
15557
+ continue
15558
+ fi
15559
+
15560
+ # Detect or use specified framework
15561
+ if [ -n "$test_format" ] && [ "$test_format" != "auto" ]; then
15562
+ framework="$test_format"
15563
+ else
15564
+ framework=$(_test_detect_framework "$lang")
15565
+ fi
15566
+
15567
+ # Determine test output path
15568
+ test_path=$(_test_output_path "$src_file" "$lang" "$framework" "$test_output")
15569
+
15570
+ # Extract testable constructs
15571
+ local constructs=""
15572
+ constructs=$(_test_extract_constructs "$src_file" "$lang")
15573
+
15574
+ if [ -z "$constructs" ]; then
15575
+ ((++skipped))
15576
+ [ "$test_verbose" = true ] && echo -e " ${DIM}Skip: $src_file (no testable constructs found)${NC}"
15577
+ continue
15578
+ fi
15579
+
15580
+ local construct_count
15581
+ construct_count=$(echo "$constructs" | wc -l | tr -d ' ')
15582
+
15583
+ if [ "$test_dry_run" = true ]; then
15584
+ if [ "$test_json" = true ]; then
15585
+ local json_entry
15586
+ json_entry=$(printf '{"source":"%s","test_path":"%s","language":"%s","framework":"%s","constructs":%d}' \
15587
+ "$src_file" "$test_path" "$lang" "$framework" "$construct_count")
15588
+ if [ -n "$json_entries" ]; then
15589
+ json_entries="${json_entries},${json_entry}"
15590
+ else
15591
+ json_entries="$json_entry"
15592
+ fi
15593
+ else
15594
+ echo -e " ${CYAN}$src_file${NC}"
15595
+ echo -e " -> ${GREEN}$test_path${NC}"
15596
+ echo -e " Language: $lang | Framework: $framework | Constructs: $construct_count"
15597
+ if [ "$test_verbose" = true ]; then
15598
+ echo "$constructs" | while IFS= read -r c; do
15599
+ echo -e " - $c"
15600
+ done
15601
+ fi
15602
+ fi
15603
+ ((++generated))
15604
+ continue
15605
+ fi
15606
+
15607
+ # Generate test content
15608
+ local test_content=""
15609
+ test_content=$(_test_generate_content "$src_file" "$lang" "$framework" "$constructs" "$test_coverage")
15610
+
15611
+ # Write test file
15612
+ local test_dir_path
15613
+ test_dir_path=$(dirname "$test_path")
15614
+ mkdir -p "$test_dir_path"
15615
+ echo "$test_content" > "$test_path"
15616
+
15617
+ generated_paths+=("$test_path")
15618
+ ((++generated))
15619
+
15620
+ if [ "$test_json" = false ]; then
15621
+ echo -e " ${GREEN}[generated]${NC} $test_path (${construct_count} constructs, $framework)"
15622
+ else
15623
+ local json_entry
15624
+ json_entry=$(printf '{"source":"%s","test_path":"%s","language":"%s","framework":"%s","constructs":%d,"status":"generated"}' \
15625
+ "$src_file" "$test_path" "$lang" "$framework" "$construct_count")
15626
+ if [ -n "$json_entries" ]; then
15627
+ json_entries="${json_entries},${json_entry}"
15628
+ else
15629
+ json_entries="$json_entry"
15630
+ fi
15631
+ fi
15632
+ done
15633
+
15634
+ # --- Output summary ---
15635
+ if [ "$test_json" = true ]; then
15636
+ local mode="generate"
15637
+ [ "$test_dry_run" = true ] && mode="dry_run"
15638
+ printf '{"status":"success","mode":"%s","files_scanned":%d,"tests_generated":%d,"skipped":%d,"coverage_target":%d,"results":[%s]}\n' \
15639
+ "$mode" "$total_files" "$generated" "$skipped" "$test_coverage" "$json_entries"
15640
+ else
15641
+ echo ""
15642
+ if [ "$test_dry_run" = true ]; then
15643
+ echo -e "${BOLD}Dry run summary:${NC} $generated test files would be generated, $skipped skipped"
15644
+ else
15645
+ echo -e "${BOLD}Summary:${NC} $generated test files generated, $skipped skipped"
15646
+ if [ ${#generated_paths[@]} -gt 0 ] && [ "$test_verbose" = true ]; then
15647
+ echo ""
15648
+ echo "Generated files:"
15649
+ for p in "${generated_paths[@]}"; do
15650
+ echo " $p"
15651
+ done
15652
+ fi
15653
+ fi
15654
+ fi
15655
+
15656
+ if [ "$generated" -eq 0 ]; then
15657
+ return 1
15658
+ fi
15659
+ return 0
15660
+ }
15661
+
15662
+ # --- Test generation helper functions ---
15663
+
15664
+ # Find source files in a directory (non-test, non-config files)
15665
+ _test_find_source_files() {
15666
+ local dir="$1"
15667
+ find "$dir" -type f \( \
15668
+ -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" \
15669
+ -o -name "*.py" -o -name "*.go" -o -name "*.rs" \
15670
+ -o -name "*.java" -o -name "*.rb" -o -name "*.sh" \
15671
+ \) ! -path "*/node_modules/*" ! -path "*/.git/*" ! -path "*/vendor/*" \
15672
+ ! -path "*/__pycache__/*" ! -path "*/target/*" ! -path "*/dist/*" \
15673
+ ! -path "*/build/*" ! -path "*/.loki/*" \
15674
+ 2>/dev/null | sort
15675
+ }
15676
+
15677
+ # Check if a file is a testable source file (not a test, config, or generated file)
15678
+ _test_is_testable_file() {
15679
+ local f="$1"
15680
+ local base
15681
+ base=$(basename "$f")
15682
+
15683
+ # Must be a recognized source file extension
15684
+ case "$f" in
15685
+ *.js|*.ts|*.jsx|*.tsx|*.py|*.go|*.rs|*.java|*.rb|*.sh) ;;
15686
+ *) return 1 ;;
15687
+ esac
15688
+
15689
+ # Skip test files
15690
+ case "$base" in
15691
+ test_*|*_test.*|*.test.*|*.spec.*|*_spec.*|test.*) return 1 ;;
15692
+ esac
15693
+ case "$f" in
15694
+ */__tests__/*|*/tests/*|*/test/*|*/spec/*|*_test/*) return 1 ;;
15695
+ esac
15696
+
15697
+ # Skip config and generated files
15698
+ case "$base" in
15699
+ setup.*|config.*|*.config.*|*.d.ts|__init__.py|conftest.py) return 1 ;;
15700
+ jest.config.*|vitest.config.*|webpack.config.*|rollup.config.*) return 1 ;;
15701
+ .eslintrc.*|.prettierrc.*|tsconfig.*|package.json) return 1 ;;
15702
+ Makefile|Dockerfile|*.lock|*.min.js|*.min.css) return 1 ;;
15703
+ esac
15704
+
15705
+ return 0
15706
+ }
15707
+
15708
+ # Detect language from file extension
15709
+ _test_detect_language() {
15710
+ local f="$1"
15711
+ case "$f" in
15712
+ *.ts|*.tsx) echo "typescript" ;;
15713
+ *.js|*.jsx) echo "javascript" ;;
15714
+ *.py) echo "python" ;;
15715
+ *.go) echo "go" ;;
15716
+ *.rs) echo "rust" ;;
15717
+ *.java) echo "java" ;;
15718
+ *.rb) echo "ruby" ;;
15719
+ *.sh) echo "bash" ;;
15720
+ *) echo "" ;;
15721
+ esac
15722
+ }
15723
+
15724
+ # Detect test framework based on language and project files
15725
+ _test_detect_framework() {
15726
+ local lang="$1"
15727
+ case "$lang" in
15728
+ typescript|javascript)
15729
+ if [ -f "vitest.config.ts" ] || [ -f "vitest.config.js" ]; then
15730
+ echo "vitest"
15731
+ elif [ -f ".mocharc.yml" ] || [ -f ".mocharc.json" ] || [ -f ".mocharc.js" ]; then
15732
+ echo "mocha"
15733
+ elif [ -f "jest.config.ts" ] || [ -f "jest.config.js" ] || [ -f "jest.config.json" ]; then
15734
+ echo "jest"
15735
+ elif [ -f "package.json" ] && grep -q '"vitest"' package.json 2>/dev/null; then
15736
+ echo "vitest"
15737
+ elif [ -f "package.json" ] && grep -q '"jest"' package.json 2>/dev/null; then
15738
+ echo "jest"
15739
+ else
15740
+ echo "jest"
15741
+ fi
15742
+ ;;
15743
+ python) echo "pytest" ;;
15744
+ go) echo "go-test" ;;
15745
+ rust) echo "cargo-test" ;;
15746
+ java) echo "junit" ;;
15747
+ ruby) echo "rspec" ;;
15748
+ bash) echo "bats" ;;
15749
+ *) echo "unknown" ;;
15750
+ esac
15751
+ }
15752
+
15753
+ # Determine output path for generated test file
15754
+ _test_output_path() {
15755
+ local src="$1"
15756
+ local lang="$2"
15757
+ local framework="$3"
15758
+ local custom_output="$4"
15759
+
15760
+ local dir
15761
+ local base
15762
+ local name
15763
+ local ext
15764
+ dir=$(dirname "$src")
15765
+ base=$(basename "$src")
15766
+ name="${base%.*}"
15767
+ ext="${base##*.}"
15768
+
15769
+ # Use custom output dir if specified
15770
+ if [ -n "$custom_output" ]; then
15771
+ dir="$custom_output"
15772
+ fi
15773
+
15774
+ case "$framework" in
15775
+ jest|vitest)
15776
+ # __tests__/ directory convention
15777
+ if [ -n "$custom_output" ]; then
15778
+ echo "${dir}/${name}.test.${ext}"
15779
+ else
15780
+ echo "${dir}/__tests__/${name}.test.${ext}"
15781
+ fi
15782
+ ;;
15783
+ mocha)
15784
+ if [ -n "$custom_output" ]; then
15785
+ echo "${dir}/${name}.spec.${ext}"
15786
+ else
15787
+ echo "${dir}/test/${name}.spec.${ext}"
15788
+ fi
15789
+ ;;
15790
+ pytest)
15791
+ if [ -n "$custom_output" ]; then
15792
+ echo "${dir}/test_${name}.py"
15793
+ else
15794
+ echo "${dir}/tests/test_${name}.py"
15795
+ fi
15796
+ ;;
15797
+ go-test)
15798
+ # Go tests live alongside source
15799
+ echo "${dir}/${name}_test.go"
15800
+ ;;
15801
+ cargo-test)
15802
+ # Rust tests in same file or tests/ dir
15803
+ if [ -n "$custom_output" ]; then
15804
+ echo "${dir}/${name}_test.rs"
15805
+ else
15806
+ echo "${dir}/tests/${name}_test.rs"
15807
+ fi
15808
+ ;;
15809
+ junit)
15810
+ if [ -n "$custom_output" ]; then
15811
+ echo "${dir}/${name}Test.java"
15812
+ else
15813
+ echo "${dir}/test/${name}Test.java"
15814
+ fi
15815
+ ;;
15816
+ rspec)
15817
+ if [ -n "$custom_output" ]; then
15818
+ echo "${dir}/${name}_spec.rb"
15819
+ else
15820
+ echo "${dir}/spec/${name}_spec.rb"
15821
+ fi
15822
+ ;;
15823
+ bats)
15824
+ if [ -n "$custom_output" ]; then
15825
+ echo "${dir}/${name}.bats"
15826
+ else
15827
+ echo "${dir}/tests/${name}.bats"
15828
+ fi
15829
+ ;;
15830
+ *)
15831
+ echo "${dir}/tests/test_${name}.${ext}"
15832
+ ;;
15833
+ esac
15834
+ }
15835
+
15836
+ # Extract testable constructs from a source file (functions, classes, exports)
15837
+ _test_extract_constructs() {
15838
+ local src="$1"
15839
+ local lang="$2"
15840
+
15841
+ case "$lang" in
15842
+ javascript|typescript)
15843
+ # Match exported functions, classes, consts, and standalone functions
15844
+ grep -E '^\s*(export\s+)?(default\s+)?(async\s+)?function\s+\w+|^\s*(export\s+)?(const|let|var)\s+\w+\s*=\s*(async\s+)?\(|^\s*(export\s+)?class\s+\w+|^module\.exports' "$src" 2>/dev/null \
15845
+ | sed 's/^\s*//' | head -50
15846
+ ;;
15847
+ python)
15848
+ # Match function definitions, class definitions
15849
+ grep -E '^\s*(async\s+)?def\s+\w+|^\s*class\s+\w+' "$src" 2>/dev/null \
15850
+ | grep -v '^\s*def\s+_' \
15851
+ | sed 's/^\s*//' | head -50
15852
+ ;;
15853
+ go)
15854
+ # Match function definitions (exported only - capitalized)
15855
+ grep -E '^func\s+(\([^)]+\)\s+)?[A-Z]\w*\(' "$src" 2>/dev/null \
15856
+ | head -50
15857
+ ;;
15858
+ rust)
15859
+ # Match pub functions and impl blocks
15860
+ grep -E '^\s*pub\s+(async\s+)?fn\s+\w+|^\s*impl\s+' "$src" 2>/dev/null \
15861
+ | sed 's/^\s*//' | head -50
15862
+ ;;
15863
+ java)
15864
+ # Match public methods and classes
15865
+ grep -E '^\s*public\s+(static\s+)?\w+\s+\w+\s*\(|^\s*public\s+class\s+\w+' "$src" 2>/dev/null \
15866
+ | sed 's/^\s*//' | head -50
15867
+ ;;
15868
+ ruby)
15869
+ # Match def and class
15870
+ grep -E '^\s*def\s+\w+|^\s*class\s+\w+' "$src" 2>/dev/null \
15871
+ | sed 's/^\s*//' | head -50
15872
+ ;;
15873
+ bash)
15874
+ # Match function definitions
15875
+ grep -E '^\s*\w+\s*\(\)\s*\{|^\s*function\s+\w+' "$src" 2>/dev/null \
15876
+ | sed 's/^\s*//' | head -50
15877
+ ;;
15878
+ *) echo "" ;;
15879
+ esac
15880
+ }
15881
+
15882
+ # Generate test file content for a given source file
15883
+ _test_generate_content() {
15884
+ local src="$1"
15885
+ local lang="$2"
15886
+ local framework="$3"
15887
+ local constructs="$4"
15888
+ local coverage="$5"
15889
+
15890
+ local base
15891
+ local name
15892
+ base=$(basename "$src")
15893
+ name="${base%.*}"
15894
+
15895
+ case "$framework" in
15896
+ jest|vitest)
15897
+ _test_gen_jest_vitest "$src" "$name" "$base" "$framework" "$constructs" "$coverage"
15898
+ ;;
15899
+ mocha)
15900
+ _test_gen_mocha "$src" "$name" "$base" "$constructs" "$coverage"
15901
+ ;;
15902
+ pytest)
15903
+ _test_gen_pytest "$src" "$name" "$base" "$constructs" "$coverage"
15904
+ ;;
15905
+ go-test)
15906
+ _test_gen_go "$src" "$name" "$base" "$constructs" "$coverage"
15907
+ ;;
15908
+ cargo-test)
15909
+ _test_gen_rust "$src" "$name" "$base" "$constructs" "$coverage"
15910
+ ;;
15911
+ junit)
15912
+ _test_gen_junit "$src" "$name" "$base" "$constructs" "$coverage"
15913
+ ;;
15914
+ rspec)
15915
+ _test_gen_rspec "$src" "$name" "$base" "$constructs" "$coverage"
15916
+ ;;
15917
+ bats)
15918
+ _test_gen_bats "$src" "$name" "$base" "$constructs" "$coverage"
15919
+ ;;
15920
+ *)
15921
+ echo "// Unsupported framework: $framework"
15922
+ ;;
15923
+ esac
15924
+ }
15925
+
15926
+ # --- Framework-specific generators ---
15927
+
15928
+ _test_gen_jest_vitest() {
15929
+ local src="$1" name="$2" base="$3" framework="$4" constructs="$5" coverage="$6"
15930
+
15931
+ local import_style="require"
15932
+ local ext="${base##*.}"
15933
+ if [[ "$ext" == "ts" || "$ext" == "tsx" ]]; then
15934
+ import_style="import"
15935
+ fi
15936
+
15937
+ # Header
15938
+ if [ "$framework" = "vitest" ]; then
15939
+ echo "import { describe, it, expect, beforeEach, afterEach } from 'vitest';"
15940
+ fi
15941
+
15942
+ # Import statement
15943
+ local rel_path
15944
+ rel_path=$(echo "$src" | sed 's|^\./||')
15945
+ local import_path="../${base%.*}"
15946
+
15947
+ if [ "$import_style" = "import" ]; then
15948
+ echo "import { /* TODO: import constructs */ } from '${import_path}';"
15949
+ else
15950
+ echo "const ${name} = require('${import_path}');"
15951
+ fi
15952
+ echo ""
15953
+ echo "// Generated test suite for ${base}"
15954
+ echo "// Target coverage: ${coverage}%"
15955
+ echo "// Source: ${src}"
15956
+ echo ""
15957
+ echo "describe('${name}', () => {"
15958
+
15959
+ # Generate test cases from constructs
15960
+ echo "$constructs" | while IFS= read -r construct; do
15961
+ [ -z "$construct" ] && continue
15962
+ # Skip module.exports lines
15963
+ echo "$construct" | grep -q '^module\.exports' && continue
15964
+ local func_name=""
15965
+ # Extract function/class name
15966
+ func_name=$(echo "$construct" | grep -oE '(function|const|let|var|class)\s+\w+' | awk '{print $2}')
15967
+ if [ -z "$func_name" ]; then
15968
+ func_name=$(echo "$construct" | grep -oE '\w+\s*=' | sed 's/\s*=$//')
15969
+ fi
15970
+ [ -z "$func_name" ] && continue
15971
+
15972
+ # Check if it's a class
15973
+ if echo "$construct" | grep -q 'class\s'; then
15974
+ echo ""
15975
+ echo " describe('${func_name}', () => {"
15976
+ echo " it('should be instantiable', () => {"
15977
+ echo " // TODO: const instance = new ${func_name}();"
15978
+ echo " // expect(instance).toBeDefined();"
15979
+ echo " });"
15980
+ echo ""
15981
+ echo " it('should have expected properties', () => {"
15982
+ echo " // TODO: verify instance properties"
15983
+ echo " });"
15984
+ echo " });"
15985
+ else
15986
+ echo ""
15987
+ echo " describe('${func_name}', () => {"
15988
+ echo " it('should return expected result for valid input', () => {"
15989
+ echo " // TODO: const result = ${func_name}(/* args */);"
15990
+ echo " // expect(result).toEqual(/* expected */);"
15991
+ echo " });"
15992
+ echo ""
15993
+ echo " it('should handle edge cases', () => {"
15994
+ echo " // TODO: test with null, undefined, empty, boundary values"
15995
+ echo " });"
15996
+ echo ""
15997
+ echo " it('should throw on invalid input', () => {"
15998
+ echo " // TODO: expect(() => ${func_name}(/* invalid */)).toThrow();"
15999
+ echo " });"
16000
+ echo " });"
16001
+ fi
16002
+ done
16003
+
16004
+ echo "});"
16005
+ }
16006
+
16007
+ _test_gen_mocha() {
16008
+ local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"
16009
+
16010
+ echo "const { expect } = require('chai');"
16011
+ echo "const ${name} = require('../${name}');"
16012
+ echo ""
16013
+ echo "// Generated test suite for ${base}"
16014
+ echo "// Target coverage: ${coverage}%"
16015
+ echo ""
16016
+ echo "describe('${name}', function () {"
16017
+
16018
+ echo "$constructs" | while IFS= read -r construct; do
16019
+ [ -z "$construct" ] && continue
16020
+ echo "$construct" | grep -q '^module\.exports' && continue
16021
+ local func_name=""
16022
+ func_name=$(echo "$construct" | grep -oE '(function|const|let|var|class)\s+\w+' | awk '{print $2}')
16023
+ [ -z "$func_name" ] && continue
16024
+
16025
+ echo ""
16026
+ echo " describe('${func_name}', function () {"
16027
+ echo " it('should return expected result', function () {"
16028
+ echo " // TODO: const result = ${name}.${func_name}(/* args */);"
16029
+ echo " // expect(result).to.equal(/* expected */);"
16030
+ echo " });"
16031
+ echo ""
16032
+ echo " it('should handle edge cases', function () {"
16033
+ echo " // TODO: test edge cases"
16034
+ echo " });"
16035
+ echo " });"
16036
+ done
16037
+
16038
+ echo "});"
16039
+ }
16040
+
16041
+ _test_gen_pytest() {
16042
+ local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"
16043
+
16044
+ local module_name
16045
+ module_name=$(echo "$name" | tr '-' '_')
16046
+
16047
+ echo "\"\"\"Tests for ${base}."
16048
+ echo ""
16049
+ echo "Target coverage: ${coverage}%"
16050
+ echo "Source: ${src}"
16051
+ echo "\"\"\""
16052
+ echo "import pytest"
16053
+ echo "# from ${module_name} import # TODO: import from source module"
16054
+ echo ""
16055
+
16056
+ echo "$constructs" | while IFS= read -r construct; do
16057
+ [ -z "$construct" ] && continue
16058
+ local func_name=""
16059
+
16060
+ if echo "$construct" | grep -q '^\s*class\s'; then
16061
+ func_name=$(echo "$construct" | grep -oE 'class\s+\w+' | awk '{print $2}')
16062
+ [ -z "$func_name" ] && continue
16063
+ echo ""
16064
+ echo "class Test${func_name}:"
16065
+ echo " \"\"\"Tests for ${func_name} class.\"\"\""
16066
+ echo ""
16067
+ echo " def test_init(self):"
16068
+ echo " \"\"\"Test ${func_name} instantiation.\"\"\""
16069
+ echo " # TODO: instance = ${func_name}()"
16070
+ echo " # assert instance is not None"
16071
+ echo " pass"
16072
+ echo ""
16073
+ echo " def test_expected_behavior(self):"
16074
+ echo " \"\"\"Test ${func_name} expected behavior.\"\"\""
16075
+ echo " # TODO: implement"
16076
+ echo " pass"
16077
+ else
16078
+ func_name=$(echo "$construct" | grep -oE 'def\s+\w+' | awk '{print $2}')
16079
+ [ -z "$func_name" ] && continue
16080
+ echo ""
16081
+ echo "def test_${func_name}_valid_input():"
16082
+ echo " \"\"\"Test ${func_name} with valid input.\"\"\""
16083
+ echo " # TODO: result = ${func_name}(/* args */)"
16084
+ echo " # assert result == expected"
16085
+ echo " pass"
16086
+ echo ""
16087
+ echo ""
16088
+ echo "def test_${func_name}_edge_cases():"
16089
+ echo " \"\"\"Test ${func_name} edge cases.\"\"\""
16090
+ echo " # TODO: test with None, empty, boundary values"
16091
+ echo " pass"
16092
+ echo ""
16093
+ echo ""
16094
+ echo "def test_${func_name}_invalid_input():"
16095
+ echo " \"\"\"Test ${func_name} with invalid input.\"\"\""
16096
+ echo " # TODO: with pytest.raises(ValueError):"
16097
+ echo " # ${func_name}(/* invalid */)"
16098
+ echo " pass"
16099
+ fi
16100
+ done
16101
+ }
16102
+
16103
+ _test_gen_go() {
16104
+ local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"
16105
+
16106
+ local pkg
16107
+ pkg=$(grep -m1 '^package\s' "$src" 2>/dev/null | awk '{print $2}')
16108
+ [ -z "$pkg" ] && pkg="main"
16109
+
16110
+ echo "package ${pkg}"
16111
+ echo ""
16112
+ echo "import ("
16113
+ echo " \"testing\""
16114
+ echo ")"
16115
+ echo ""
16116
+ echo "// Generated tests for ${base}"
16117
+ echo "// Target coverage: ${coverage}%"
16118
+
16119
+ echo "$constructs" | while IFS= read -r construct; do
16120
+ [ -z "$construct" ] && continue
16121
+ local func_name=""
16122
+ func_name=$(echo "$construct" | grep -oE 'func\s+(\([^)]+\)\s+)?\w+' | grep -oE '\w+$')
16123
+ [ -z "$func_name" ] && continue
16124
+
16125
+ echo ""
16126
+ echo "func Test${func_name}(t *testing.T) {"
16127
+ echo " t.Run(\"valid input\", func(t *testing.T) {"
16128
+ echo " // TODO: result := ${func_name}(/* args */)"
16129
+ echo " // if result != expected {"
16130
+ echo " // t.Errorf(\"${func_name}() = %v, want %v\", result, expected)"
16131
+ echo " // }"
16132
+ echo " })"
16133
+ echo ""
16134
+ echo " t.Run(\"edge cases\", func(t *testing.T) {"
16135
+ echo " // TODO: test boundary values"
16136
+ echo " })"
16137
+ echo "}"
16138
+ done
16139
+ }
16140
+
16141
+ _test_gen_rust() {
16142
+ local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"
16143
+
16144
+ echo "// Generated tests for ${base}"
16145
+ echo "// Target coverage: ${coverage}%"
16146
+ echo ""
16147
+ echo "#[cfg(test)]"
16148
+ echo "mod tests {"
16149
+ echo " use super::*;"
16150
+ echo ""
16151
+
16152
+ echo "$constructs" | while IFS= read -r construct; do
16153
+ [ -z "$construct" ] && continue
16154
+ local func_name=""
16155
+ func_name=$(echo "$construct" | grep -oE 'fn\s+\w+' | awk '{print $2}')
16156
+ [ -z "$func_name" ] && continue
16157
+
16158
+ echo " #[test]"
16159
+ echo " fn test_${func_name}_valid_input() {"
16160
+ echo " // TODO: let result = ${func_name}(/* args */);"
16161
+ echo " // assert_eq!(result, expected);"
16162
+ echo " }"
16163
+ echo ""
16164
+ echo " #[test]"
16165
+ echo " fn test_${func_name}_edge_cases() {"
16166
+ echo " // TODO: test boundary values"
16167
+ echo " }"
16168
+ echo ""
16169
+ done
16170
+
16171
+ echo "}"
16172
+ }
16173
+
16174
+ _test_gen_junit() {
16175
+ local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"
16176
+
16177
+ echo "import org.junit.jupiter.api.Test;"
16178
+ echo "import org.junit.jupiter.api.BeforeEach;"
16179
+ echo "import static org.junit.jupiter.api.Assertions.*;"
16180
+ echo ""
16181
+ echo "/**"
16182
+ echo " * Generated tests for ${base}"
16183
+ echo " * Target coverage: ${coverage}%"
16184
+ echo " */"
16185
+ echo "class ${name}Test {"
16186
+ echo ""
16187
+
16188
+ echo "$constructs" | while IFS= read -r construct; do
16189
+ [ -z "$construct" ] && continue
16190
+ local func_name=""
16191
+ func_name=$(echo "$construct" | grep -oE '(class|void|int|String|boolean|double|float|long)\s+\w+' | awk '{print $NF}')
16192
+ [ -z "$func_name" ] && continue
16193
+
16194
+ # Skip class definitions
16195
+ if echo "$construct" | grep -q 'class\s'; then
16196
+ continue
16197
+ fi
16198
+
16199
+ echo " @Test"
16200
+ echo " void test${func_name^}ValidInput() {"
16201
+ echo " // TODO: var result = instance.${func_name}(/* args */);"
16202
+ echo " // assertEquals(expected, result);"
16203
+ echo " }"
16204
+ echo ""
16205
+ echo " @Test"
16206
+ echo " void test${func_name^}EdgeCases() {"
16207
+ echo " // TODO: test boundary values"
16208
+ echo " }"
16209
+ echo ""
16210
+ done
16211
+
16212
+ echo "}"
16213
+ }
16214
+
16215
+ _test_gen_rspec() {
16216
+ local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"
16217
+
16218
+ echo "# Generated tests for ${base}"
16219
+ echo "# Target coverage: ${coverage}%"
16220
+ echo ""
16221
+ echo "require_relative '../${name}'"
16222
+ echo ""
16223
+ echo "RSpec.describe ${name^} do"
16224
+
16225
+ echo "$constructs" | while IFS= read -r construct; do
16226
+ [ -z "$construct" ] && continue
16227
+ local func_name=""
16228
+
16229
+ if echo "$construct" | grep -q 'class\s'; then
16230
+ func_name=$(echo "$construct" | grep -oE 'class\s+\w+' | awk '{print $2}')
16231
+ [ -z "$func_name" ] && continue
16232
+ echo ""
16233
+ echo " describe ${func_name} do"
16234
+ echo " it 'is instantiable' do"
16235
+ echo " # TODO: instance = ${func_name}.new"
16236
+ echo " # expect(instance).not_to be_nil"
16237
+ echo " end"
16238
+ echo " end"
16239
+ else
16240
+ func_name=$(echo "$construct" | grep -oE 'def\s+\w+' | awk '{print $2}')
16241
+ [ -z "$func_name" ] && continue
16242
+ echo ""
16243
+ echo " describe '#${func_name}' do"
16244
+ echo " it 'returns expected result' do"
16245
+ echo " # TODO: result = subject.${func_name}"
16246
+ echo " # expect(result).to eq(expected)"
16247
+ echo " end"
16248
+ echo ""
16249
+ echo " it 'handles edge cases' do"
16250
+ echo " # TODO: test edge cases"
16251
+ echo " end"
16252
+ echo " end"
16253
+ fi
16254
+ done
16255
+
16256
+ echo "end"
16257
+ }
16258
+
16259
+ _test_gen_bats() {
16260
+ local src="$1" name="$2" base="$3" constructs="$4" coverage="$5"
16261
+
16262
+ echo "#!/usr/bin/env bats"
16263
+ echo "# Generated tests for ${base}"
16264
+ echo "# Target coverage: ${coverage}%"
16265
+ echo ""
16266
+ echo "setup() {"
16267
+ echo " source \"\$(dirname \"\$BATS_TEST_FILENAME\")/../${base}\""
16268
+ echo "}"
16269
+ echo ""
16270
+
16271
+ echo "$constructs" | while IFS= read -r construct; do
16272
+ [ -z "$construct" ] && continue
16273
+ local func_name=""
16274
+ func_name=$(echo "$construct" | grep -oE '\w+' | head -1)
16275
+ [ -z "$func_name" ] && continue
16276
+
16277
+ echo "@test \"${func_name} returns expected result\" {"
16278
+ echo " # TODO: run ${func_name} args"
16279
+ echo " # [ \"\$status\" -eq 0 ]"
16280
+ echo " # [ \"\$output\" = \"expected\" ]"
16281
+ echo " skip \"TODO: implement\""
16282
+ echo "}"
16283
+ echo ""
16284
+ echo "@test \"${func_name} handles edge cases\" {"
16285
+ echo " # TODO: test edge cases"
16286
+ echo " skip \"TODO: implement\""
16287
+ echo "}"
16288
+ echo ""
16289
+ done
16290
+ }
16291
+
15329
16292
  main "$@"
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "6.23.0"
10
+ __version__ = "6.24.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.23.0
5
+ **Version:** v6.24.0
6
6
 
7
7
  ---
8
8
 
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '6.23.0'
60
+ __version__ = '6.24.0'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "6.23.0",
3
+ "version": "6.24.0",
4
4
  "description": "Loki Mode by Autonomi - Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
5
5
  "keywords": [
6
6
  "agent",