loki-mode 6.19.0 → 6.20.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.19.0
6
+ # Loki Mode v6.20.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.19.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
270
+ **v6.20.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 6.19.0
1
+ 6.20.0
package/autonomy/loki CHANGED
@@ -420,7 +420,7 @@ show_help() {
420
420
  echo " checkpoint|cp Save/restore session checkpoints"
421
421
  echo " projects Multi-project registry management"
422
422
  echo " audit [cmd] Agent audit log and quality scanning (log|scan)"
423
- echo " review [dir] Run quality gates on any project (standalone, no AI needed)"
423
+ echo " review [opts] Standalone code review with quality gates (diff, staged, PR, files)"
424
424
  echo " optimize Optimize prompts based on session history"
425
425
  echo " enterprise Enterprise feature management (tokens, OIDC)"
426
426
  echo " metrics Prometheus/OpenMetrics metrics from dashboard"
@@ -9205,224 +9205,477 @@ main() {
9205
9205
  ;;
9206
9206
  esac
9207
9207
  }
9208
-
9209
- # Standalone project review - run quality gates on any codebase (v6.14.0)
9208
+ # Standalone code review - diff-based quality gates (v6.20.0)
9210
9209
  cmd_review() {
9211
- local target_dir="."
9212
- local json_output=""
9213
- local verbose=""
9210
+ local review_staged=false
9211
+ local review_pr=""
9212
+ local review_since=""
9213
+ local review_target=""
9214
+ local review_format="text"
9215
+ local review_severity="all"
9214
9216
 
9215
9217
  while [[ $# -gt 0 ]]; do
9216
9218
  case "$1" in
9217
- --json) json_output="true"; shift ;;
9218
- --verbose|-v) verbose="true"; shift ;;
9219
9219
  --help|-h)
9220
- echo -e "${BOLD}loki review${NC} - Run quality gates on any project (standalone)"
9220
+ echo -e "${BOLD}loki review${NC} - Standalone code review with quality gates"
9221
9221
  echo ""
9222
- echo "Usage: loki review [directory] [options]"
9222
+ echo "Usage: loki review [options] [file-or-dir]"
9223
9223
  echo ""
9224
- echo "Arguments:"
9225
- echo " directory Path to project (default: current directory)"
9224
+ echo "Reviews code changes using static analysis, security scanning,"
9225
+ echo "style checks, and anti-pattern detection. No AI needed."
9226
+ echo ""
9227
+ echo "Sources (default: uncommitted changes via git diff):"
9228
+ echo " --staged Review staged changes only"
9229
+ echo " --pr <number> Review a GitHub PR"
9230
+ echo " --since <commit> Review changes since a commit"
9231
+ echo " <file-or-dir> Review specific files or directory"
9226
9232
  echo ""
9227
9233
  echo "Options:"
9228
- echo " --json Machine-readable JSON output"
9229
- echo " --verbose, -v Show detailed output per gate"
9230
- echo " --help, -h Show this help"
9234
+ echo " --format json Output as JSON (for CI integration)"
9235
+ echo " --severity <level> Filter: critical, high, medium, low, info (shows level+above)"
9236
+ echo " --help, -h Show this help"
9231
9237
  echo ""
9232
- echo "Gates: project-type, lint, tests, security, dependencies, structure"
9233
- echo "Exit code: 0 if all pass, 1 if any fail"
9238
+ echo "Exit codes:"
9239
+ echo " 0 No HIGH or CRITICAL findings"
9240
+ echo " 1 HIGH severity findings present"
9241
+ echo " 2 CRITICAL severity findings present"
9242
+ echo ""
9243
+ echo "Examples:"
9244
+ echo " loki review # Review uncommitted changes"
9245
+ echo " loki review --staged # Review staged changes"
9246
+ echo " loki review --pr 42 # Review GitHub PR #42"
9247
+ echo " loki review src/ # Review all files in src/"
9248
+ echo " loki review --since HEAD~5 # Changes in last 5 commits"
9249
+ echo " loki review --format json # JSON output for CI"
9250
+ echo " loki review --severity high # Only HIGH+ findings"
9234
9251
  return 0
9235
9252
  ;;
9253
+ --staged) review_staged=true; shift ;;
9254
+ --pr)
9255
+ shift
9256
+ review_pr="${1:-}"
9257
+ if [ -z "$review_pr" ]; then
9258
+ echo -e "${RED}Error: --pr requires a PR number${NC}"
9259
+ return 1
9260
+ fi
9261
+ shift
9262
+ ;;
9263
+ --since)
9264
+ shift
9265
+ review_since="${1:-}"
9266
+ if [ -z "$review_since" ]; then
9267
+ echo -e "${RED}Error: --since requires a commit reference${NC}"
9268
+ return 1
9269
+ fi
9270
+ shift
9271
+ ;;
9272
+ --format)
9273
+ shift
9274
+ review_format="${1:-text}"
9275
+ shift
9276
+ ;;
9277
+ --severity)
9278
+ shift
9279
+ review_severity="${1:-all}"
9280
+ review_severity="$(echo "$review_severity" | tr '[:upper:]' '[:lower:]')"
9281
+ shift
9282
+ ;;
9236
9283
  -*) echo -e "${RED}Unknown option: $1${NC}"; return 1 ;;
9237
- *) target_dir="$1"; shift ;;
9284
+ *) review_target="$1"; shift ;;
9238
9285
  esac
9239
9286
  done
9240
9287
 
9241
- # Resolve and validate directory
9242
- target_dir="$(cd "$target_dir" 2>/dev/null && pwd)" || {
9243
- echo -e "${RED}Error: Directory not found: $target_dir${NC}"; return 1
9244
- }
9245
-
9246
- local project_type="unknown"
9247
- local results=() # gate:status pairs
9248
- local total_pass=0 total_warn=0 total_fail=0
9249
- local gate_details=()
9250
-
9251
- # Helper: record gate result
9252
- _review_gate() {
9253
- local gate="$1" status="$2" detail="${3:-}"
9254
- results+=("$gate:$status")
9255
- gate_details+=("$gate:$detail")
9256
- case "$status" in
9257
- PASS) total_pass=$((total_pass + 1)) ;;
9258
- WARN) total_warn=$((total_warn + 1)) ;;
9259
- FAIL) total_fail=$((total_fail + 1)) ;;
9288
+ # Severity level ordering (higher number = more severe)
9289
+ _review_sev_level() {
9290
+ case "$1" in
9291
+ INFO) echo 1 ;;
9292
+ LOW) echo 2 ;;
9293
+ MEDIUM) echo 3 ;;
9294
+ HIGH) echo 4 ;;
9295
+ CRITICAL) echo 5 ;;
9296
+ *) echo 0 ;;
9260
9297
  esac
9261
9298
  }
9262
9299
 
9263
- # Gate 1: Detect project type
9264
- if [ -f "$target_dir/package.json" ]; then
9265
- project_type="node"
9266
- elif [ -f "$target_dir/pyproject.toml" ] || [ -f "$target_dir/setup.py" ] || [ -f "$target_dir/requirements.txt" ]; then
9267
- project_type="python"
9268
- elif [ -f "$target_dir/go.mod" ]; then
9269
- project_type="go"
9270
- elif [ -f "$target_dir/Cargo.toml" ]; then
9271
- project_type="rust"
9272
- fi
9273
- if [ "$project_type" != "unknown" ]; then
9274
- _review_gate "project-type" "PASS" "Detected: $project_type"
9300
+ local min_sev=0
9301
+ case "$review_severity" in
9302
+ info) min_sev=1 ;;
9303
+ low) min_sev=2 ;;
9304
+ medium) min_sev=3 ;;
9305
+ high) min_sev=4 ;;
9306
+ critical) min_sev=5 ;;
9307
+ all) min_sev=0 ;;
9308
+ esac
9309
+
9310
+ # Gather code to review
9311
+ local diff_content=""
9312
+ local review_files=""
9313
+ local source_desc=""
9314
+
9315
+ if [ -n "$review_pr" ]; then
9316
+ # PR review via gh CLI
9317
+ if ! command -v gh &>/dev/null; then
9318
+ echo -e "${RED}Error: gh CLI required for PR review. Install: https://cli.github.com${NC}"
9319
+ return 1
9320
+ fi
9321
+ diff_content=$(gh pr diff "$review_pr" 2>&1) || {
9322
+ echo -e "${RED}Error: Could not fetch PR #${review_pr}: $diff_content${NC}"
9323
+ return 1
9324
+ }
9325
+ source_desc="PR #${review_pr}"
9326
+ elif [ -n "$review_since" ]; then
9327
+ diff_content=$(git diff "$review_since" 2>&1) || {
9328
+ echo -e "${RED}Error: Invalid commit reference: $review_since${NC}"
9329
+ return 1
9330
+ }
9331
+ source_desc="changes since $review_since"
9332
+ elif [ -n "$review_target" ]; then
9333
+ # File or directory target - get full contents
9334
+ if [ -d "$review_target" ]; then
9335
+ review_files=$(find "$review_target" -type f \
9336
+ \( -name "*.sh" -o -name "*.bash" -o -name "*.py" -o -name "*.js" -o -name "*.ts" \
9337
+ -o -name "*.jsx" -o -name "*.tsx" -o -name "*.go" -o -name "*.rs" -o -name "*.rb" \
9338
+ -o -name "*.java" -o -name "*.c" -o -name "*.cpp" -o -name "*.h" \) \
9339
+ -not -path "*/node_modules/*" -not -path "*/.git/*" \
9340
+ -not -path "*/vendor/*" -not -path "*/__pycache__/*" \
9341
+ -not -path "*/.venv/*" -not -path "*/target/*" 2>/dev/null)
9342
+ source_desc="directory $review_target"
9343
+ elif [ -f "$review_target" ]; then
9344
+ review_files="$review_target"
9345
+ source_desc="file $review_target"
9346
+ else
9347
+ echo -e "${RED}Error: Not found: $review_target${NC}"
9348
+ return 1
9349
+ fi
9350
+ elif [ "$review_staged" = true ]; then
9351
+ diff_content=$(git diff --cached 2>/dev/null)
9352
+ source_desc="staged changes"
9275
9353
  else
9276
- _review_gate "project-type" "WARN" "Could not detect project type"
9354
+ diff_content=$(git diff 2>/dev/null)
9355
+ source_desc="uncommitted changes"
9277
9356
  fi
9278
9357
 
9279
- # Gate 2: Lint check
9280
- local lint_output="" lint_status="PASS"
9281
- case "$project_type" in
9282
- node)
9283
- if [ -f "$target_dir/.eslintrc.js" ] || [ -f "$target_dir/.eslintrc.json" ] || [ -f "$target_dir/.eslintrc.yml" ] || [ -f "$target_dir/eslint.config.js" ] || [ -f "$target_dir/eslint.config.mjs" ]; then
9284
- lint_output=$(cd "$target_dir" && npx eslint . --max-warnings=0 2>&1) || lint_status="FAIL"
9285
- else
9286
- lint_output="No ESLint config found"; lint_status="WARN"
9287
- fi ;;
9288
- python)
9289
- if command -v ruff &>/dev/null; then
9290
- lint_output=$(cd "$target_dir" && ruff check . 2>&1) || lint_status="FAIL"
9291
- elif command -v pylint &>/dev/null; then
9292
- lint_output=$(cd "$target_dir" && pylint --recursive=y . 2>&1) || lint_status="FAIL"
9293
- else
9294
- lint_output="No linter available (install ruff or pylint)"; lint_status="WARN"
9295
- fi ;;
9296
- go)
9297
- if command -v golangci-lint &>/dev/null; then
9298
- lint_output=$(cd "$target_dir" && golangci-lint run 2>&1) || lint_status="FAIL"
9299
- else
9300
- lint_output=$(cd "$target_dir" && go vet ./... 2>&1) || lint_status="FAIL"
9301
- fi ;;
9302
- rust)
9303
- lint_output=$(cd "$target_dir" && cargo clippy -- -D warnings 2>&1) || lint_status="FAIL"
9304
- ;;
9305
- *) lint_output="Skipped (unknown project type)"; lint_status="WARN" ;;
9306
- esac
9307
- _review_gate "lint" "$lint_status" "$lint_output"
9308
-
9309
- # Gate 3: Tests
9310
- local test_output="" test_status="PASS"
9311
- case "$project_type" in
9312
- node)
9313
- if grep -q '"test"' "$target_dir/package.json" 2>/dev/null; then
9314
- test_output=$(cd "$target_dir" && npm test 2>&1) || test_status="FAIL"
9315
- else
9316
- test_output="No test script in package.json"; test_status="WARN"
9317
- fi ;;
9318
- python)
9319
- if command -v pytest &>/dev/null; then
9320
- test_output=$(cd "$target_dir" && pytest --tb=short 2>&1) || test_status="FAIL"
9321
- else
9322
- test_output="pytest not available"; test_status="WARN"
9323
- fi ;;
9324
- go) test_output=$(cd "$target_dir" && go test ./... 2>&1) || test_status="FAIL" ;;
9325
- rust) test_output=$(cd "$target_dir" && cargo test 2>&1) || test_status="FAIL" ;;
9326
- *) test_output="Skipped (unknown project type)"; test_status="WARN" ;;
9327
- esac
9328
- _review_gate "tests" "$test_status" "$test_output"
9329
-
9330
- # Gate 4: Security - grep for hardcoded secrets
9331
- local secret_output="" secret_status="PASS"
9332
- local secret_patterns='(API_KEY|SECRET_KEY|PASSWORD|TOKEN|PRIVATE_KEY)\s*[=:]\s*["\x27][A-Za-z0-9+/=_-]{8,}'
9333
- secret_output=$(grep -rEn "$secret_patterns" "$target_dir" \
9334
- --include="*.js" --include="*.ts" --include="*.py" --include="*.go" --include="*.rs" \
9335
- --include="*.jsx" --include="*.tsx" --include="*.java" --include="*.rb" \
9336
- --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=vendor \
9337
- --exclude-dir=__pycache__ --exclude-dir=.venv --exclude-dir=target 2>/dev/null) || true
9338
- if [ -n "$secret_output" ]; then
9339
- secret_status="FAIL"
9340
- secret_output="Potential hardcoded secrets found:
9341
- $secret_output"
9342
- else
9343
- secret_output="No hardcoded secrets detected"
9358
+ # Check if there is anything to review
9359
+ if [ -z "$diff_content" ] && [ -z "$review_files" ]; then
9360
+ if [ "$review_format" = "json" ]; then
9361
+ echo '{"source":"'"$source_desc"'","findings":[],"summary":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"total":0},"exit_code":0}'
9362
+ else
9363
+ echo -e "${GREEN}No changes to review ($source_desc).${NC}"
9364
+ fi
9365
+ return 0
9344
9366
  fi
9345
- _review_gate "security" "$secret_status" "$secret_output"
9346
9367
 
9347
- # Gate 5: Dependency audit
9348
- local dep_output="" dep_status="PASS"
9349
- case "$project_type" in
9350
- node)
9351
- if [ -f "$target_dir/package-lock.json" ] || [ -f "$target_dir/yarn.lock" ]; then
9352
- dep_output=$(cd "$target_dir" && npm audit --production 2>&1) || dep_status="WARN"
9353
- else
9354
- dep_output="No lockfile found"; dep_status="WARN"
9355
- fi ;;
9356
- python)
9357
- if command -v pip-audit &>/dev/null; then
9358
- dep_output=$(cd "$target_dir" && pip-audit 2>&1) || dep_status="WARN"
9359
- else
9360
- dep_output="pip-audit not available"; dep_status="WARN"
9361
- fi ;;
9362
- *) dep_output="No dependency audit available for $project_type"; dep_status="WARN" ;;
9363
- esac
9364
- _review_gate "dependencies" "$dep_status" "$dep_output"
9365
-
9366
- # Gate 6: Structure check
9367
- local struct_output="" struct_status="PASS" struct_issues=()
9368
- [ ! -f "$target_dir/README.md" ] && [ ! -f "$target_dir/README.rst" ] && [ ! -f "$target_dir/README" ] && struct_issues+=("Missing README")
9369
- local file_count
9370
- file_count=$(find "$target_dir" -type f -not -path '*/.git/*' -not -path '*/node_modules/*' -not -path '*/vendor/*' -not -path '*/__pycache__/*' -not -path '*/.venv/*' -not -path '*/target/*' 2>/dev/null | wc -l | tr -d ' ')
9371
- [ "$file_count" -gt 5000 ] && struct_issues+=("Large project: $file_count files")
9372
- local huge_files
9373
- huge_files=$(find "$target_dir" -type f -size +1M -not -path '*/.git/*' -not -path '*/node_modules/*' -not -path '*/vendor/*' -not -path '*/target/*' 2>/dev/null | head -5)
9374
- [ -n "$huge_files" ] && struct_issues+=("Files >1MB found: $(echo "$huge_files" | wc -l | tr -d ' ')")
9375
- if [ ${#struct_issues[@]} -gt 0 ]; then
9376
- struct_status="WARN"
9377
- struct_output=$(printf '%s\n' "${struct_issues[@]}")
9368
+ # Build file content for non-diff reviews
9369
+ local all_content=""
9370
+ if [ -n "$review_files" ]; then
9371
+ while IFS= read -r f; do
9372
+ [ -z "$f" ] && continue
9373
+ all_content+="=== FILE: $f ===
9374
+ $(cat "$f" 2>/dev/null)
9375
+ "
9376
+ done <<< "$review_files"
9378
9377
  else
9379
- struct_output="README present, $file_count files, no oversized files"
9380
- fi
9381
- _review_gate "structure" "$struct_status" "$struct_output"
9382
-
9383
- # Output results
9384
- if [ -n "$json_output" ]; then
9385
- local gates_json="["
9386
- local first="true"
9387
- for r in "${results[@]}"; do
9388
- local gate="${r%%:*}" status="${r#*:}"
9389
- [ "$first" = "true" ] && first="" || gates_json+=","
9390
- gates_json+="{\"gate\":\"$gate\",\"status\":\"$status\"}"
9378
+ all_content="$diff_content"
9379
+ fi
9380
+
9381
+ # Findings array: "file|line|severity|category|finding|suggestion"
9382
+ local findings=()
9383
+ local has_critical=false
9384
+ local has_high=false
9385
+
9386
+ # Helper to add a finding
9387
+ _add_finding() {
9388
+ local file="$1" line="$2" sev="$3" cat="$4" finding="$5" suggestion="$6"
9389
+ local sev_num
9390
+ sev_num=$(_review_sev_level "$sev")
9391
+ if [ "$sev_num" -ge "$min_sev" ]; then
9392
+ findings+=("${file}|${line}|${sev}|${cat}|${finding}|${suggestion}")
9393
+ [ "$sev" = "CRITICAL" ] && has_critical=true || true
9394
+ [ "$sev" = "HIGH" ] && has_high=true || true
9395
+ fi
9396
+ }
9397
+
9398
+ # --- Gate 1: Static Analysis ---
9399
+ # Run shellcheck on shell files if available
9400
+ if command -v shellcheck &>/dev/null; then
9401
+ local shell_files=""
9402
+ if [ -n "$review_files" ]; then
9403
+ shell_files=$(echo "$review_files" | grep -E '\.(sh|bash)$' || true)
9404
+ elif [ -n "$diff_content" ]; then
9405
+ shell_files=$(echo "$diff_content" | grep -E '^\+\+\+ b/' | sed 's|^+++ b/||' | grep -E '\.(sh|bash)$' || true)
9406
+ fi
9407
+ if [ -n "$shell_files" ]; then
9408
+ while IFS= read -r sf; do
9409
+ [ -z "$sf" ] && continue
9410
+ [ ! -f "$sf" ] && continue
9411
+ local sc_out
9412
+ sc_out=$(shellcheck -f gcc "$sf" 2>/dev/null || true)
9413
+ while IFS= read -r sc_line; do
9414
+ [ -z "$sc_line" ] && continue
9415
+ local sc_file sc_lineno sc_sev sc_msg
9416
+ sc_file=$(echo "$sc_line" | cut -d: -f1)
9417
+ sc_lineno=$(echo "$sc_line" | cut -d: -f2)
9418
+ sc_sev=$(echo "$sc_line" | sed -n 's/.*: \(warning\|error\|note\|info\):.*/\1/p')
9419
+ sc_msg=$(echo "$sc_line" | sed 's/.*: \(warning\|error\|note\|info\): //')
9420
+ local mapped_sev="LOW"
9421
+ case "$sc_sev" in
9422
+ error) mapped_sev="HIGH" ;;
9423
+ warning) mapped_sev="MEDIUM" ;;
9424
+ *) mapped_sev="LOW" ;;
9425
+ esac
9426
+ _add_finding "$sc_file" "$sc_lineno" "$mapped_sev" "static-analysis" "$sc_msg" "Fix shellcheck finding"
9427
+ done <<< "$sc_out"
9428
+ done <<< "$shell_files"
9429
+ fi
9430
+ fi
9431
+
9432
+ # Run eslint if available on JS/TS files
9433
+ if command -v npx &>/dev/null; then
9434
+ local js_files=""
9435
+ if [ -n "$review_files" ]; then
9436
+ js_files=$(echo "$review_files" | grep -E '\.(js|ts|jsx|tsx)$' || true)
9437
+ elif [ -n "$diff_content" ]; then
9438
+ js_files=$(echo "$diff_content" | grep -E '^\+\+\+ b/' | sed 's|^+++ b/||' | grep -E '\.(js|ts|jsx|tsx)$' || true)
9439
+ fi
9440
+ if [ -n "$js_files" ]; then
9441
+ while IFS= read -r jsf; do
9442
+ [ -z "$jsf" ] && continue
9443
+ [ ! -f "$jsf" ] && continue
9444
+ if [ -f ".eslintrc.js" ] || [ -f ".eslintrc.json" ] || [ -f "eslint.config.js" ] || [ -f "eslint.config.mjs" ]; then
9445
+ local eslint_out
9446
+ eslint_out=$(npx eslint --format compact "$jsf" 2>/dev/null || true)
9447
+ while IFS= read -r el; do
9448
+ [ -z "$el" ] && continue
9449
+ [[ "$el" == *"problem"* ]] && continue
9450
+ local el_file el_line el_msg
9451
+ el_file=$(echo "$el" | cut -d: -f1)
9452
+ el_line=$(echo "$el" | cut -d: -f2)
9453
+ el_msg=$(echo "$el" | sed 's/^[^)]*) //')
9454
+ local el_sev="LOW"
9455
+ [[ "$el" == *"Error"* ]] && el_sev="MEDIUM"
9456
+ _add_finding "$el_file" "$el_line" "$el_sev" "static-analysis" "$el_msg" "Fix eslint finding"
9457
+ done <<< "$eslint_out"
9458
+ fi
9459
+ done <<< "$js_files"
9460
+ fi
9461
+ fi
9462
+
9463
+ # --- Gate 2: Security Scan ---
9464
+ # Helper: scan for a pattern across files or diff content
9465
+ _review_scan() {
9466
+ local pattern="$1" sev="$2" cat="$3" finding="$4" suggestion="$5"
9467
+ if [ -n "$review_files" ]; then
9468
+ # Scan actual files with grep -Hn (file:line:content)
9469
+ local scan_files_arr=()
9470
+ while IFS= read -r sf; do
9471
+ [ -z "$sf" ] && continue
9472
+ [ -f "$sf" ] && scan_files_arr+=("$sf")
9473
+ done <<< "$review_files"
9474
+ [ ${#scan_files_arr[@]} -eq 0 ] && return
9475
+ while IFS= read -r match; do
9476
+ [ -z "$match" ] && continue
9477
+ local mf ml
9478
+ mf=$(echo "$match" | cut -d: -f1)
9479
+ ml=$(echo "$match" | cut -d: -f2)
9480
+ _add_finding "$mf" "$ml" "$sev" "$cat" "$finding" "$suggestion"
9481
+ done < <(grep -HnE "$pattern" "${scan_files_arr[@]}" 2>/dev/null || true)
9482
+ else
9483
+ # Scan diff content (line numbers are from the text blob)
9484
+ while IFS= read -r match; do
9485
+ [ -z "$match" ] && continue
9486
+ local ml
9487
+ ml=$(echo "$match" | cut -d: -f1)
9488
+ _add_finding "diff" "$ml" "$sev" "$cat" "$finding" "$suggestion"
9489
+ done < <(echo "$all_content" | grep -nE "$pattern" 2>/dev/null || true)
9490
+ fi
9491
+ }
9492
+
9493
+ # Hardcoded secrets
9494
+ local secret_patterns=(
9495
+ 'API_KEY\s*[=:]\s*["\x27][A-Za-z0-9+/=_-]{8,}'
9496
+ 'SECRET_KEY\s*[=:]\s*["\x27][A-Za-z0-9+/=_-]{8,}'
9497
+ 'PASSWORD\s*[=:]\s*["\x27][^\x27"]{4,}'
9498
+ 'PRIVATE_KEY\s*[=:]\s*["\x27][A-Za-z0-9+/=_-]{8,}'
9499
+ 'AWS_ACCESS_KEY_ID\s*[=:]\s*["\x27]AK[A-Z0-9]{18}'
9500
+ 'ghp_[A-Za-z0-9]{36}'
9501
+ 'sk-[A-Za-z0-9]{32,}'
9502
+ 'Bearer\s+[A-Za-z0-9._-]{20,}'
9503
+ )
9504
+ for pattern in "${secret_patterns[@]}"; do
9505
+ _review_scan "$pattern" "CRITICAL" "security" \
9506
+ "Potential hardcoded secret detected" \
9507
+ "Use environment variables or a secrets manager instead"
9508
+ done
9509
+
9510
+ # SQL injection patterns
9511
+ _review_scan '(SELECT|INSERT|UPDATE|DELETE|DROP)\s.*\+\s*(req\.|request\.|params\.|user)' \
9512
+ "HIGH" "security" \
9513
+ "Potential SQL injection: string concatenation in query" \
9514
+ "Use parameterized queries or prepared statements"
9515
+
9516
+ # eval/exec usage
9517
+ _review_scan '(^|\s)(eval|exec)\s*\(' \
9518
+ "HIGH" "security" \
9519
+ "Dangerous eval/exec usage detected" \
9520
+ "Avoid eval/exec with dynamic input; use safer alternatives"
9521
+
9522
+ # Unsafe deserialization
9523
+ _review_scan '(pickle\.loads?|yaml\.load\s*\()' \
9524
+ "HIGH" "security" \
9525
+ "Unsafe deserialization detected (pickle/yaml.load)" \
9526
+ "Use yaml.safe_load or avoid pickle with untrusted data"
9527
+
9528
+ # --- Gate 3: Code Style ---
9529
+ # Long functions (>100 lines heuristic from diff)
9530
+ if [ -n "$review_files" ]; then
9531
+ while IFS= read -r f; do
9532
+ [ -z "$f" ] && continue
9533
+ [ ! -f "$f" ] && continue
9534
+ local line_count
9535
+ line_count=$(wc -l < "$f" | tr -d ' ')
9536
+ if [ "$line_count" -gt 500 ]; then
9537
+ _add_finding "$f" "1" "MEDIUM" "style" \
9538
+ "File has $line_count lines (>500)" \
9539
+ "Consider breaking into smaller modules"
9540
+ fi
9541
+ # Check for very long lines
9542
+ local long_lines
9543
+ long_lines=$(awk 'length > 200 {count++} END {print count+0}' "$f" 2>/dev/null)
9544
+ if [ "$long_lines" -gt 5 ]; then
9545
+ _add_finding "$f" "1" "LOW" "style" \
9546
+ "$long_lines lines exceed 200 characters" \
9547
+ "Break long lines for readability"
9548
+ fi
9549
+ done <<< "$review_files"
9550
+ fi
9551
+
9552
+ # TODO/FIXME/HACK/XXX detection
9553
+ _review_scan '(TODO|FIXME|HACK|XXX):' "INFO" "style" \
9554
+ "TODO/FIXME/HACK/XXX comment marker found" \
9555
+ "Track in issue tracker instead of code comments"
9556
+
9557
+ # --- Gate 4: Anti-pattern Detection ---
9558
+ # console.log left in code (JS/TS)
9559
+ _review_scan 'console\.(log|debug|warn)\(' "LOW" "anti-pattern" \
9560
+ "console.log statement (debug artifact?)" \
9561
+ "Remove debug logging or use a proper logger"
9562
+
9563
+ # Catch bare except in Python
9564
+ _review_scan 'except\s*:' "MEDIUM" "anti-pattern" \
9565
+ "Bare except clause catches all exceptions" \
9566
+ "Catch specific exceptions (e.g., except ValueError)"
9567
+
9568
+ # Disabled SSL verification
9569
+ _review_scan '(verify\s*=\s*False|VERIFY_SSL\s*=\s*False|NODE_TLS_REJECT_UNAUTHORIZED.*0|rejectUnauthorized.*false)' \
9570
+ "HIGH" "anti-pattern" \
9571
+ "SSL verification disabled" \
9572
+ "Enable SSL verification in production"
9573
+
9574
+ # Hardcoded IP addresses (skip localhost/0.0.0.0/version patterns)
9575
+ _review_scan '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' \
9576
+ "LOW" "anti-pattern" \
9577
+ "Hardcoded IP address detected" \
9578
+ "Use configuration or DNS names instead"
9579
+
9580
+ # --- Tally results ---
9581
+ local count_critical=0 count_high=0 count_medium=0 count_low=0 count_info=0
9582
+ for f in "${findings[@]}"; do
9583
+ local sev
9584
+ sev=$(echo "$f" | cut -d'|' -f3)
9585
+ case "$sev" in
9586
+ CRITICAL) count_critical=$((count_critical + 1)) ;;
9587
+ HIGH) count_high=$((count_high + 1)) ;;
9588
+ MEDIUM) count_medium=$((count_medium + 1)) ;;
9589
+ LOW) count_low=$((count_low + 1)) ;;
9590
+ INFO) count_info=$((count_info + 1)) ;;
9591
+ esac
9592
+ done
9593
+ local count_total=${#findings[@]}
9594
+
9595
+ # Determine exit code
9596
+ local exit_code=0
9597
+ [ "$has_high" = true ] && exit_code=1 || true
9598
+ [ "$has_critical" = true ] && exit_code=2 || true
9599
+
9600
+ # --- Output ---
9601
+ if [ "$review_format" = "json" ]; then
9602
+ local json_findings="["
9603
+ local first=true
9604
+ for f in "${findings[@]}"; do
9605
+ local f_file f_line f_sev f_cat f_finding f_suggestion
9606
+ f_file=$(echo "$f" | cut -d'|' -f1)
9607
+ f_line=$(echo "$f" | cut -d'|' -f2)
9608
+ f_sev=$(echo "$f" | cut -d'|' -f3)
9609
+ f_cat=$(echo "$f" | cut -d'|' -f4)
9610
+ f_finding=$(echo "$f" | cut -d'|' -f5)
9611
+ f_suggestion=$(echo "$f" | cut -d'|' -f6)
9612
+ # Escape quotes for JSON
9613
+ f_finding=$(echo "$f_finding" | sed 's/"/\\"/g')
9614
+ f_suggestion=$(echo "$f_suggestion" | sed 's/"/\\"/g')
9615
+ f_file=$(echo "$f_file" | sed 's/"/\\"/g')
9616
+ [ "$first" = true ] && first=false || json_findings+=","
9617
+ json_findings+="{\"file\":\"$f_file\",\"line\":$f_line,\"severity\":\"$f_sev\",\"category\":\"$f_cat\",\"finding\":\"$f_finding\",\"suggestion\":\"$f_suggestion\"}"
9391
9618
  done
9392
- gates_json+="]"
9393
- printf '{"directory":"%s","project_type":"%s","pass":%d,"warn":%d,"fail":%d,"gates":%s}\n' \
9394
- "$target_dir" "$project_type" "$total_pass" "$total_warn" "$total_fail" "$gates_json"
9619
+ json_findings+="]"
9620
+ printf '{"source":"%s","findings":%s,"summary":{"critical":%d,"high":%d,"medium":%d,"low":%d,"info":%d,"total":%d},"exit_code":%d}\n' \
9621
+ "$source_desc" "$json_findings" "$count_critical" "$count_high" "$count_medium" "$count_low" "$count_info" "$count_total" "$exit_code"
9395
9622
  else
9396
- echo -e "${BOLD}Loki Review: $target_dir${NC}"
9397
- echo -e "Project type: ${CYAN}$project_type${NC}"
9623
+ echo -e "${BOLD}Loki Code Review${NC}"
9624
+ echo -e "Source: ${CYAN}$source_desc${NC}"
9398
9625
  echo "---"
9399
- for r in "${results[@]}"; do
9400
- local gate="${r%%:*}" status="${r#*:}"
9401
- case "$status" in
9402
- PASS) echo -e " ${GREEN}[PASS]${NC} $gate" ;;
9403
- WARN) echo -e " ${YELLOW}[WARN]${NC} $gate" ;;
9404
- FAIL) echo -e " ${RED}[FAIL]${NC} $gate" ;;
9405
- esac
9406
- done
9407
- echo "---"
9408
- echo -e "Results: ${GREEN}$total_pass passed${NC}, ${YELLOW}$total_warn warnings${NC}, ${RED}$total_fail failed${NC}"
9409
9626
 
9410
- if [ -n "$verbose" ]; then
9411
- echo ""
9412
- echo -e "${BOLD}Details:${NC}"
9413
- for d in "${gate_details[@]}"; do
9414
- local gate="${d%%:*}" detail="${d#*:}"
9415
- echo -e "\n${CYAN}[$gate]${NC}"
9416
- echo "$detail" | head -30
9627
+ if [ "$count_total" -eq 0 ]; then
9628
+ echo -e "${GREEN}No findings. Code looks clean.${NC}"
9629
+ else
9630
+ # Group by severity
9631
+ for sev_name in CRITICAL HIGH MEDIUM LOW INFO; do
9632
+ local printed_header=false
9633
+ for f in "${findings[@]}"; do
9634
+ local f_sev
9635
+ f_sev=$(echo "$f" | cut -d'|' -f3)
9636
+ [ "$f_sev" != "$sev_name" ] && continue
9637
+ if [ "$printed_header" = false ]; then
9638
+ local sev_color="$NC"
9639
+ case "$sev_name" in
9640
+ CRITICAL) sev_color="$RED" ;;
9641
+ HIGH) sev_color="$RED" ;;
9642
+ MEDIUM) sev_color="$YELLOW" ;;
9643
+ LOW) sev_color="$CYAN" ;;
9644
+ INFO) sev_color="$DIM" ;;
9645
+ esac
9646
+ echo ""
9647
+ echo -e "${sev_color}${BOLD}[$sev_name]${NC}"
9648
+ printed_header=true
9649
+ fi
9650
+ local f_file f_line f_cat f_finding f_suggestion
9651
+ f_file=$(echo "$f" | cut -d'|' -f1)
9652
+ f_line=$(echo "$f" | cut -d'|' -f2)
9653
+ f_cat=$(echo "$f" | cut -d'|' -f4)
9654
+ f_finding=$(echo "$f" | cut -d'|' -f5)
9655
+ f_suggestion=$(echo "$f" | cut -d'|' -f6)
9656
+ echo -e " ${DIM}$f_file:$f_line${NC} [$f_cat] $f_finding"
9657
+ echo -e " -> $f_suggestion"
9658
+ done
9417
9659
  done
9418
9660
  fi
9661
+
9662
+ echo ""
9663
+ echo "---"
9664
+ echo -e "Summary: ${RED}$count_critical critical${NC}, ${RED}$count_high high${NC}, ${YELLOW}$count_medium medium${NC}, ${CYAN}$count_low low${NC}, ${DIM}$count_info info${NC} ($count_total total)"
9665
+
9666
+ if [ "$exit_code" -eq 2 ]; then
9667
+ echo -e "${RED}CRITICAL findings detected. Review required.${NC}"
9668
+ elif [ "$exit_code" -eq 1 ]; then
9669
+ echo -e "${RED}HIGH severity findings detected.${NC}"
9670
+ else
9671
+ echo -e "${GREEN}No HIGH or CRITICAL findings.${NC}"
9672
+ fi
9419
9673
  fi
9420
9674
 
9421
- # Exit code: 1 if any failures
9422
- [ "$total_fail" -gt 0 ] && return 1
9423
- return 0
9675
+ return "$exit_code"
9424
9676
  }
9425
9677
 
9678
+
9426
9679
  # Worktree management (v6.7.0)
9427
9680
  cmd_worktree() {
9428
9681
  local subcommand="${1:-list}"
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "6.19.0"
10
+ __version__ = "6.20.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.19.0
5
+ **Version:** v6.20.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.19.0'
60
+ __version__ = '6.20.0'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "6.19.0",
3
+ "version": "6.20.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",