loki-mode 6.74.6 → 6.75.1

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.
Files changed (70) hide show
  1. package/README.md +2 -54
  2. package/SKILL.md +2 -2
  3. package/VERSION +1 -1
  4. package/autonomy/loki +995 -0
  5. package/autonomy/run.sh +107 -2
  6. package/dashboard/__init__.py +1 -1
  7. package/docs/INSTALLATION.md +1 -1
  8. package/mcp/__init__.py +1 -1
  9. package/mcp/server.py +177 -0
  10. package/package.json +1 -1
  11. package/references/mcp-integration.md +59 -0
  12. package/skills/00-index.md +9 -0
  13. package/skills/documentation.md +123 -0
  14. package/skills/quality-gates.md +25 -1
  15. package/web-app/dist/assets/{AdminPage-Cwqm_kDg.js → AdminPage-D4QSV6Zi.js} +1 -1
  16. package/web-app/dist/assets/{Avatar-BgcFY2E5.js → Avatar-88MlpLO5.js} +1 -1
  17. package/web-app/dist/assets/{Badge-DeFGfZLB.js → Badge-DbGjLr4i.js} +1 -1
  18. package/web-app/dist/assets/{Button-Dg1EkPtN.js → Button-sp_FVGZj.js} +1 -1
  19. package/web-app/dist/assets/{ComparePage-D-wvMVP2.js → ComparePage-p2ENnfa7.js} +1 -1
  20. package/web-app/dist/assets/{GitHubIssuesPanel-B_Jm7CmJ.js → GitHubIssuesPanel-DBbBTG9w.js} +1 -1
  21. package/web-app/dist/assets/{GitHubPRsPanel-B5i8Q99N.js → GitHubPRsPanel-Bi_yrcAE.js} +1 -1
  22. package/web-app/dist/assets/{HomePage-CUDTdntY.js → HomePage-BB83YPiX.js} +1 -1
  23. package/web-app/dist/assets/{LoginPage-BobwVXx5.js → LoginPage-BXUudCJ9.js} +1 -1
  24. package/web-app/dist/assets/{MetricsPage-DmM--20B.js → MetricsPage-CX0Ahy-_.js} +1 -1
  25. package/web-app/dist/assets/{NotFoundPage-6_gjeogD.js → NotFoundPage-C4JqatEk.js} +1 -1
  26. package/web-app/dist/assets/{ProjectPage-C3R2wbFt.js → ProjectPage-t5J2XAJT.js} +46 -46
  27. package/web-app/dist/assets/{ProjectsPage-DQr06iBk.js → ProjectsPage-Bzpz1clk.js} +1 -1
  28. package/web-app/dist/assets/{SettingsPage-eROlGKB9.js → SettingsPage-y_yl8FvH.js} +1 -1
  29. package/web-app/dist/assets/{ShowcasePage-DEA5tT_R.js → ShowcasePage-B7d6pzMq.js} +1 -1
  30. package/web-app/dist/assets/{SystemSettingsPage-C_rIbgZg.js → SystemSettingsPage-C4tR33KU.js} +1 -1
  31. package/web-app/dist/assets/{TeamsPage-DZGoYZnD.js → TeamsPage-DIOCfZIP.js} +1 -1
  32. package/web-app/dist/assets/{TemplatesPage-DVblWpbO.js → TemplatesPage-DlKyapXX.js} +1 -1
  33. package/web-app/dist/assets/{TerminalOutput-DnESY9zk.js → TerminalOutput-Czg-ZC2k.js} +1 -1
  34. package/web-app/dist/assets/{activity-COLsZyo1.js → activity-h1wU9a0L.js} +1 -1
  35. package/web-app/dist/assets/{bell-BjLe9xXk.js → bell-Bu8lsWOp.js} +1 -1
  36. package/web-app/dist/assets/{bot-Dz62aBIi.js → bot-rWO7KjkQ.js} +1 -1
  37. package/web-app/dist/assets/{check-BQPQjkH4.js → check-BWp8L5Cy.js} +1 -1
  38. package/web-app/dist/assets/{chevron-left-BVvOVUQ8.js → chevron-left-Bw4I1yGm.js} +1 -1
  39. package/web-app/dist/assets/{circle-alert-DqoLW238.js → circle-alert-C37PKXiC.js} +1 -1
  40. package/web-app/dist/assets/{clock-Cn9fFUva.js → clock-DDScLol4.js} +1 -1
  41. package/web-app/dist/assets/{cloud-COJxbgUu.js → cloud-DaYKPLaM.js} +1 -1
  42. package/web-app/dist/assets/{copy-DOn0hVgy.js → copy-DKIRv0VK.js} +1 -1
  43. package/web-app/dist/assets/{database-Bj3Llvnk.js → database-CYZBHz51.js} +1 -1
  44. package/web-app/dist/assets/{dollar-sign-BcmncygQ.js → dollar-sign-CydJu0kl.js} +1 -1
  45. package/web-app/dist/assets/{file-code-corner-BysxoSyu.js → file-code-corner-DqZ9gpdv.js} +1 -1
  46. package/web-app/dist/assets/{file-plus-Dwi1MqSS.js → file-plus-CzeFJWp3.js} +1 -1
  47. package/web-app/dist/assets/{folder-open-WzXNCq2x.js → folder-open-4YWk08dP.js} +1 -1
  48. package/web-app/dist/assets/{git-commit-horizontal-D85UUfNF.js → git-commit-horizontal-wbqFPNID.js} +1 -1
  49. package/web-app/dist/assets/{globe-CegT0VaL.js → globe-Cby-g5Yb.js} +1 -1
  50. package/web-app/dist/assets/{hammer-ZGKvu_xy.js → hammer-BNScgGdp.js} +1 -1
  51. package/web-app/dist/assets/{index-_2iPl2nX.js → index-6Z4B0I6r.js} +74 -74
  52. package/web-app/dist/assets/{layers-B40lki5j.js → layers-XfssQc5V.js} +1 -1
  53. package/web-app/dist/assets/{lightbulb-CFSoqUsV.js → lightbulb-EhnzRw7M.js} +1 -1
  54. package/web-app/dist/assets/{loader-circle-womi7Brk.js → loader-circle-BA0QIVGA.js} +1 -1
  55. package/web-app/dist/assets/{lock-CEWBb_SL.js → lock-BABtHe6K.js} +1 -1
  56. package/web-app/dist/assets/{mail-DimGrYf8.js → mail-Dokiey5S.js} +1 -1
  57. package/web-app/dist/assets/{package-DysIuuIJ.js → package-DbJyS1Ft.js} +1 -1
  58. package/web-app/dist/assets/{plus-DG1hW27_.js → plus-BcAN8Kaj.js} +1 -1
  59. package/web-app/dist/assets/{refresh-cw-X31ig0S6.js → refresh-cw-B3dG1-Sb.js} +1 -1
  60. package/web-app/dist/assets/{rotate-ccw-CBXooICo.js → rotate-ccw-Cs1Phctm.js} +1 -1
  61. package/web-app/dist/assets/{save-DQVrWTjZ.js → save-DsrNCZrP.js} +1 -1
  62. package/web-app/dist/assets/{server-BYAMALfa.js → server-CpN2GX4G.js} +1 -1
  63. package/web-app/dist/assets/{shield-alert-B3G7vLiW.js → shield-alert-CKJ1pzCz.js} +1 -1
  64. package/web-app/dist/assets/{trash-2-ChVunC-C.js → trash-2-C9vZqTqw.js} +1 -1
  65. package/web-app/dist/assets/{trending-down-DgsuOE2t.js → trending-down-BNLTrF5P.js} +1 -1
  66. package/web-app/dist/assets/{trending-up-CZHZsGeN.js → trending-up-DmFIdVOc.js} +1 -1
  67. package/web-app/dist/assets/{usePolling-ns_dFCVn.js → usePolling-vUlY-o6P.js} +1 -1
  68. package/web-app/dist/assets/{user-FQUrWHhF.js → user-Dh00W8De.js} +1 -1
  69. package/web-app/dist/index.html +1 -1
  70. package/web-app/server.py +196 -0
package/autonomy/loki CHANGED
@@ -449,6 +449,7 @@ show_help() {
449
449
  echo " failover [cmd] Cross-provider auto-failover (status|--enable|--test|--chain)"
450
450
  echo " onboard [path] Analyze a repo and generate CLAUDE.md (structure, conventions, commands)"
451
451
  echo ' explain [path] Analyze any codebase and explain its architecture in plain English'
452
+ echo " docs [cmd] Generate, update, check project documentation"
452
453
  echo " plan <PRD> Dry-run PRD analysis: complexity, cost, and execution plan"
453
454
  echo " ci [opts] CI/CD quality gate integration (--pr, --report, --github-comment)"
454
455
  echo " test [opts] AI-powered test generation (--file, --dir, --changed, --dry-run)"
@@ -11195,6 +11196,9 @@ main() {
11195
11196
  explain)
11196
11197
  cmd_explain "$@"
11197
11198
  ;;
11199
+ docs)
11200
+ cmd_docs "$@"
11201
+ ;;
11198
11202
  ci)
11199
11203
  cmd_ci "$@"
11200
11204
  ;;
@@ -18322,6 +18326,997 @@ Generated by loki explain on $(date +%Y-%m-%d)"
18322
18326
  set -euo pipefail
18323
18327
  }
18324
18328
 
18329
+ # Generate, update, check, and manage project documentation (v6.75.0)
18330
+ cmd_docs() {
18331
+ local subcmd="${1:-}"
18332
+ shift 2>/dev/null || true
18333
+
18334
+ case "$subcmd" in
18335
+ generate) _docs_generate "$@" ;;
18336
+ update) _docs_update "$@" ;;
18337
+ check) _docs_check "$@" ;;
18338
+ status) _docs_status "$@" ;;
18339
+ --help|-h|help|"")
18340
+ echo -e "${BOLD}loki docs${NC} - Generate, update, check project documentation"
18341
+ echo ""
18342
+ echo "Usage: loki docs <command> [options]"
18343
+ echo ""
18344
+ echo "Commands:"
18345
+ echo " generate [path] Generate full documentation suite in .loki/docs/"
18346
+ echo " update [path] Incremental update based on git changes since last generation"
18347
+ echo " check [path] Validate documentation coverage and staleness (exit 0=pass, 1=fail)"
18348
+ echo " status [path] Show documentation metrics (count, coverage, staleness)"
18349
+ echo ""
18350
+ echo "Options:"
18351
+ echo " --help, -h Show this help"
18352
+ echo ""
18353
+ echo "Generated documentation:"
18354
+ echo " .loki/docs/README.md Project overview, setup, usage"
18355
+ echo " .loki/docs/ARCHITECTURE.md System design, data flow, key decisions"
18356
+ echo " .loki/docs/API.md Public API reference (endpoints, functions, classes)"
18357
+ echo " .loki/docs/SETUP.md Dev environment setup, dependencies, running locally"
18358
+ echo " .loki/docs/COMPONENTS.md Per-component/module documentation"
18359
+ echo " .loki/docs/TESTING.md Test strategy, running tests, coverage"
18360
+ echo " .loki/docs/DECISIONS.md Architectural Decision Records"
18361
+ echo " .loki/docs/CLAUDE.md AI agent context file"
18362
+ echo " .loki/docs/docs-manifest.json Manifest with SHAs and timestamps"
18363
+ echo ""
18364
+ echo "Examples:"
18365
+ echo " loki docs generate # Generate docs for current project"
18366
+ echo " loki docs generate ~/projects/app # Generate docs for specific project"
18367
+ echo " loki docs update # Update only changed components"
18368
+ echo " loki docs check # Validate docs (CI-friendly)"
18369
+ echo " loki docs status # Show doc metrics"
18370
+ return 0
18371
+ ;;
18372
+ *)
18373
+ log_error "Unknown docs command: $subcmd"
18374
+ echo "Run 'loki docs --help' for usage."
18375
+ return 1
18376
+ ;;
18377
+ esac
18378
+ }
18379
+
18380
+ # --- Docs helper: scan project and build context ---
18381
+ _docs_scan_project() {
18382
+ local target_path="$1"
18383
+
18384
+ # Build file tree (excluding common noise)
18385
+ local tree_output=""
18386
+ if command -v git &>/dev/null && [ -d "$target_path/.git" ]; then
18387
+ tree_output=$(cd "$target_path" && git ls-files 2>/dev/null | head -500 || true)
18388
+ else
18389
+ tree_output=$(find "$target_path" -maxdepth 4 -type f \
18390
+ -not -path '*/node_modules/*' -not -path '*/.git/*' \
18391
+ -not -path '*/vendor/*' -not -path '*/__pycache__/*' \
18392
+ -not -path '*/dist/*' -not -path '*/build/*' \
18393
+ -not -path '*/.next/*' -not -path '*/target/*' \
18394
+ 2>/dev/null | sed "s|$target_path/||" | sort | head -500)
18395
+ fi
18396
+ echo "$tree_output"
18397
+ }
18398
+
18399
+ _docs_build_context() {
18400
+ local target_path="$1"
18401
+ local tree_output="$2"
18402
+ local context=""
18403
+
18404
+ context="PROJECT PATH: $target_path
18405
+ PROJECT NAME: $(basename "$target_path")
18406
+ "
18407
+
18408
+ # File tree summary
18409
+ local total_files src_count test_count
18410
+ total_files=$(echo "$tree_output" | { grep -c . || true; })
18411
+ src_count=$(echo "$tree_output" | { grep -cE '\.(js|ts|tsx|jsx|py|rs|go|rb|java|kt|c|cpp|h|hpp|sh|swift|cs|php)$' || true; })
18412
+ test_count=$(echo "$tree_output" | { grep -cE '(\.test\.|\.spec\.|_test\.|_spec\.|tests/|test/|__tests__/)' || true; })
18413
+ context="${context}
18414
+ FILES: $total_files total, $src_count source, $test_count test
18415
+ "
18416
+
18417
+ # Top-level directories
18418
+ local top_dirs
18419
+ top_dirs=$(echo "$tree_output" | sed 's|/.*||' | sort | uniq -c | sort -rn | head -20)
18420
+ context="${context}
18421
+ DIRECTORY STRUCTURE:
18422
+ $top_dirs
18423
+ "
18424
+
18425
+ # File tree (first 200 files)
18426
+ context="${context}
18427
+ FILE TREE:
18428
+ $(echo "$tree_output" | head -200)
18429
+ "
18430
+
18431
+ # Key config files - read first 50 lines of each
18432
+ for cfg in package.json pyproject.toml Cargo.toml go.mod Gemfile pom.xml requirements.txt Makefile Dockerfile docker-compose.yml; do
18433
+ if [ -f "$target_path/$cfg" ]; then
18434
+ context="${context}
18435
+ --- $cfg (first 50 lines) ---
18436
+ $(head -50 "$target_path/$cfg" 2>/dev/null || true)
18437
+ "
18438
+ fi
18439
+ done
18440
+
18441
+ # README if present
18442
+ for f in README.md readme.md README.rst README; do
18443
+ if [ -f "$target_path/$f" ]; then
18444
+ context="${context}
18445
+ --- $f (first 80 lines) ---
18446
+ $(head -80 "$target_path/$f" 2>/dev/null || true)
18447
+ "
18448
+ break
18449
+ fi
18450
+ done
18451
+
18452
+ # Key entry point files (first 50 lines)
18453
+ local entry_count=0
18454
+ for candidate in \
18455
+ "src/index.ts" "src/index.js" "src/main.ts" "src/main.js" "src/app.ts" "src/app.js" \
18456
+ "index.ts" "index.js" "main.ts" "main.js" "app.ts" "app.js" "server.ts" "server.js" \
18457
+ "src/App.tsx" "src/App.jsx" "pages/index.tsx" "app/page.tsx" \
18458
+ "main.py" "app.py" "manage.py" "src/main.py" "__main__.py" \
18459
+ "main.go" "cmd/main.go" "src/main.rs" "src/lib.rs"; do
18460
+ if [ -f "$target_path/$candidate" ]; then
18461
+ context="${context}
18462
+ --- $candidate (first 50 lines) ---
18463
+ $(head -50 "$target_path/$candidate" 2>/dev/null || true)
18464
+ "
18465
+ ((++entry_count))
18466
+ [ "$entry_count" -ge 5 ] && break
18467
+ fi
18468
+ done
18469
+
18470
+ echo "$context"
18471
+ }
18472
+
18473
+ # --- Docs helper: invoke AI provider and capture output ---
18474
+ _docs_invoke_provider() {
18475
+ local prompt="$1"
18476
+ local provider="${LOKI_PROVIDER:-claude}"
18477
+ if [ -f ".loki/state/provider" ]; then
18478
+ provider=$(cat ".loki/state/provider" 2>/dev/null)
18479
+ fi
18480
+
18481
+ local result=""
18482
+ local exit_code=0
18483
+ local timeout_cmd="timeout"
18484
+ command -v gtimeout &>/dev/null && timeout_cmd="gtimeout"
18485
+ command -v $timeout_cmd &>/dev/null || timeout_cmd=""
18486
+ local t_prefix=""
18487
+ [ -n "$timeout_cmd" ] && t_prefix="$timeout_cmd 120"
18488
+
18489
+ case "$provider" in
18490
+ claude)
18491
+ result=$($t_prefix claude -p "$prompt" 2>/dev/null) || exit_code=$?
18492
+ ;;
18493
+ codex)
18494
+ result=$($t_prefix codex exec --full-auto "$prompt" 2>/dev/null) || exit_code=$?
18495
+ ;;
18496
+ gemini)
18497
+ result=$($t_prefix gemini --approval-mode=yolo "$prompt" 2>/dev/null) || exit_code=$?
18498
+ ;;
18499
+ cline)
18500
+ result=$($t_prefix cline -y "$prompt" 2>/dev/null) || exit_code=$?
18501
+ ;;
18502
+ aider)
18503
+ result=$($t_prefix aider --message "$prompt" --yes-always --no-auto-commits < /dev/null 2>/dev/null) || exit_code=$?
18504
+ ;;
18505
+ *)
18506
+ log_error "Unknown provider: $provider"
18507
+ return 1
18508
+ ;;
18509
+ esac
18510
+
18511
+ if [ $exit_code -ne 0 ] && [ -z "$result" ]; then
18512
+ log_error "Provider '$provider' failed (exit $exit_code)"
18513
+ return 1
18514
+ fi
18515
+
18516
+ echo "$result"
18517
+ }
18518
+
18519
+ # --- Docs helper: write manifest ---
18520
+ _docs_write_manifest() {
18521
+ local docs_dir="$1"
18522
+ shift
18523
+ # Remaining args are file names
18524
+
18525
+ local git_sha=""
18526
+ git_sha=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
18527
+ local generated_at
18528
+ generated_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
18529
+
18530
+ local files_json="{"
18531
+ local first=true
18532
+ for doc_file in "$@"; do
18533
+ local fname
18534
+ fname=$(basename "$doc_file")
18535
+ if [ -f "$docs_dir/$fname" ]; then
18536
+ local file_sha
18537
+ file_sha=$(shasum -a 256 "$docs_dir/$fname" 2>/dev/null | awk '{print $1}' || echo "unknown")
18538
+ if [ "$first" = true ]; then
18539
+ first=false
18540
+ else
18541
+ files_json="${files_json},"
18542
+ fi
18543
+ files_json="${files_json}
18544
+ \"$fname\": {\"sha256\": \"$file_sha\", \"generated_at\": \"$generated_at\"}"
18545
+ fi
18546
+ done
18547
+ files_json="${files_json}
18548
+ }"
18549
+
18550
+ cat > "$docs_dir/docs-manifest.json" <<MANIFEST_EOF
18551
+ {
18552
+ "generated_at": "$generated_at",
18553
+ "git_sha": "$git_sha",
18554
+ "files": $files_json
18555
+ }
18556
+ MANIFEST_EOF
18557
+ }
18558
+
18559
+ # --- loki docs generate ---
18560
+ _docs_generate() {
18561
+ local target_path="."
18562
+
18563
+ while [[ $# -gt 0 ]]; do
18564
+ case "$1" in
18565
+ --help|-h)
18566
+ echo "Usage: loki docs generate [path]"
18567
+ echo "Generate full documentation suite in .loki/docs/"
18568
+ return 0
18569
+ ;;
18570
+ -*)
18571
+ log_error "Unknown option: $1"
18572
+ return 1
18573
+ ;;
18574
+ *)
18575
+ target_path="$1"
18576
+ shift
18577
+ ;;
18578
+ esac
18579
+ done
18580
+
18581
+ if [ ! -d "$target_path" ]; then
18582
+ log_error "Directory not found: $target_path"
18583
+ return 1
18584
+ fi
18585
+ target_path="$(cd "$target_path" && pwd)"
18586
+
18587
+ local docs_dir="$target_path/.loki/docs"
18588
+ mkdir -p "$docs_dir"
18589
+
18590
+ log_info "Scanning project at: $target_path"
18591
+
18592
+ # Scan project
18593
+ local tree_output
18594
+ tree_output=$(_docs_scan_project "$target_path")
18595
+ local context
18596
+ context=$(_docs_build_context "$target_path" "$tree_output")
18597
+
18598
+ local project_name
18599
+ project_name="$(basename "$target_path")"
18600
+
18601
+ # Check if provider CLI is available
18602
+ local provider="${LOKI_PROVIDER:-claude}"
18603
+ if [ -f "$target_path/.loki/state/provider" ]; then
18604
+ provider=$(cat "$target_path/.loki/state/provider" 2>/dev/null)
18605
+ fi
18606
+
18607
+ local provider_available=true
18608
+ if ! command -v "$provider" &>/dev/null; then
18609
+ provider_available=false
18610
+ log_warn "Provider '$provider' not found. Generating template-based docs instead."
18611
+ fi
18612
+
18613
+ # Doc types to generate
18614
+ local doc_types="README ARCHITECTURE API SETUP COMPONENTS TESTING DECISIONS CLAUDE"
18615
+ local generated_files=""
18616
+
18617
+ for doc_type in $doc_types; do
18618
+ local doc_file="${doc_type}.md"
18619
+ local prompt=""
18620
+
18621
+ echo -e " ${CYAN}Generating${NC} $doc_file ..."
18622
+
18623
+ if [ "$provider_available" = true ]; then
18624
+ # Build a focused prompt for each doc type
18625
+ case "$doc_type" in
18626
+ README)
18627
+ prompt="Based on the following project context, generate a comprehensive README.md file. Include: project title, description, features, installation instructions, usage examples, and contribution guidelines. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.
18628
+
18629
+ $context"
18630
+ ;;
18631
+ ARCHITECTURE)
18632
+ prompt="Based on the following project context, generate an ARCHITECTURE.md file. Include: system overview, high-level design, component diagram (as text), data flow, directory structure explanation, key design decisions, and technology choices. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.
18633
+
18634
+ $context"
18635
+ ;;
18636
+ API)
18637
+ prompt="Based on the following project context, generate an API.md file. Document all public APIs: REST endpoints, exported functions, classes, and their signatures. Include parameter types, return types, and brief descriptions. If no API is detected, document the main public interfaces. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.
18638
+
18639
+ $context"
18640
+ ;;
18641
+ SETUP)
18642
+ prompt="Based on the following project context, generate a SETUP.md file. Include: prerequisites, installation steps, environment variables, database setup, running locally, running in Docker (if applicable), and common troubleshooting. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.
18643
+
18644
+ $context"
18645
+ ;;
18646
+ COMPONENTS)
18647
+ prompt="Based on the following project context, generate a COMPONENTS.md file. Document each major component/module/directory: its purpose, key files, public interface, and dependencies on other components. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.
18648
+
18649
+ $context"
18650
+ ;;
18651
+ TESTING)
18652
+ prompt="Based on the following project context, generate a TESTING.md file. Include: test strategy, test types (unit, integration, e2e), how to run tests, test configuration, coverage goals, and CI integration. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.
18653
+
18654
+ $context"
18655
+ ;;
18656
+ DECISIONS)
18657
+ prompt="Based on the following project context, generate a DECISIONS.md file with Architectural Decision Records (ADRs). Infer key decisions from the technology stack, directory structure, and configuration. Each ADR should have: title, status, context, decision, and consequences. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.
18658
+
18659
+ $context"
18660
+ ;;
18661
+ CLAUDE)
18662
+ prompt="Based on the following project context, generate a CLAUDE.md file for AI agent context. Include: project overview, key commands (build, test, run, lint), project structure with brief descriptions of each directory, coding conventions, important files, and any gotchas. This file helps AI coding assistants understand the project quickly. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations.
18663
+
18664
+ $context"
18665
+ ;;
18666
+ esac
18667
+
18668
+ local result=""
18669
+ result=$(_docs_invoke_provider "$prompt" 2>/dev/null) || true
18670
+
18671
+ if [ -n "$result" ]; then
18672
+ echo "$result" > "$docs_dir/$doc_file"
18673
+ generated_files="$generated_files $doc_file"
18674
+ else
18675
+ # Fallback to template
18676
+ _docs_generate_template "$doc_type" "$target_path" "$context" > "$docs_dir/$doc_file"
18677
+ generated_files="$generated_files $doc_file"
18678
+ fi
18679
+ else
18680
+ # Template-based generation (no AI provider)
18681
+ _docs_generate_template "$doc_type" "$target_path" "$context" > "$docs_dir/$doc_file"
18682
+ generated_files="$generated_files $doc_file"
18683
+ fi
18684
+
18685
+ echo -e " ${GREEN}[done]${NC} $doc_file"
18686
+ done
18687
+
18688
+ # Write manifest
18689
+ # shellcheck disable=SC2086
18690
+ _docs_write_manifest "$docs_dir" $generated_files
18691
+
18692
+ echo ""
18693
+ log_info "Documentation generated in $docs_dir/"
18694
+ log_info "Files: $(echo "$generated_files" | wc -w | tr -d ' ') docs + manifest"
18695
+ echo -e " Manifest: ${CYAN}$docs_dir/docs-manifest.json${NC}"
18696
+ }
18697
+
18698
+ # --- Docs helper: generate template-based doc (fallback when no AI provider) ---
18699
+ _docs_generate_template() {
18700
+ local doc_type="$1"
18701
+ local target_path="$2"
18702
+ local context="$3"
18703
+ local project_name
18704
+ project_name="$(basename "$target_path")"
18705
+ local timestamp
18706
+ timestamp=$(date +%Y-%m-%d)
18707
+
18708
+ # Extract basic metadata from context
18709
+ local files_line
18710
+ files_line=$(echo "$context" | grep '^FILES:' | head -1 || echo "FILES: unknown")
18711
+
18712
+ case "$doc_type" in
18713
+ README)
18714
+ cat <<EOF
18715
+ # $project_name
18716
+
18717
+ ## Overview
18718
+
18719
+ Project at \`$target_path\`.
18720
+
18721
+ $files_line
18722
+
18723
+ ## Getting Started
18724
+
18725
+ See SETUP.md for installation and development instructions.
18726
+
18727
+ ## Documentation
18728
+
18729
+ - [Architecture](ARCHITECTURE.md)
18730
+ - [API Reference](API.md)
18731
+ - [Setup Guide](SETUP.md)
18732
+ - [Components](COMPONENTS.md)
18733
+ - [Testing](TESTING.md)
18734
+ - [Decisions](DECISIONS.md)
18735
+
18736
+ ---
18737
+ Generated by loki docs generate on $timestamp
18738
+ EOF
18739
+ ;;
18740
+ ARCHITECTURE)
18741
+ cat <<EOF
18742
+ # Architecture
18743
+
18744
+ ## Overview
18745
+
18746
+ This document describes the architecture of $project_name.
18747
+
18748
+ ## Directory Structure
18749
+
18750
+ \`\`\`
18751
+ $(echo "$context" | sed -n '/^DIRECTORY STRUCTURE:/,/^$/p' | tail -n +2)
18752
+ \`\`\`
18753
+
18754
+ ## Key Components
18755
+
18756
+ See COMPONENTS.md for detailed component documentation.
18757
+
18758
+ ---
18759
+ Generated by loki docs generate on $timestamp
18760
+ EOF
18761
+ ;;
18762
+ API)
18763
+ cat <<EOF
18764
+ # API Reference
18765
+
18766
+ ## Overview
18767
+
18768
+ Public API documentation for $project_name.
18769
+
18770
+ TODO: Document public endpoints, functions, and classes.
18771
+
18772
+ ---
18773
+ Generated by loki docs generate on $timestamp
18774
+ EOF
18775
+ ;;
18776
+ SETUP)
18777
+ cat <<EOF
18778
+ # Setup Guide
18779
+
18780
+ ## Prerequisites
18781
+
18782
+ See the project configuration files for required dependencies.
18783
+
18784
+ ## Installation
18785
+
18786
+ Clone the repository and install dependencies.
18787
+
18788
+ ## Running Locally
18789
+
18790
+ Refer to the project's build and run scripts.
18791
+
18792
+ ---
18793
+ Generated by loki docs generate on $timestamp
18794
+ EOF
18795
+ ;;
18796
+ COMPONENTS)
18797
+ cat <<EOF
18798
+ # Components
18799
+
18800
+ ## Overview
18801
+
18802
+ Component documentation for $project_name.
18803
+
18804
+ ## Modules
18805
+
18806
+ $(echo "$context" | sed -n '/^DIRECTORY STRUCTURE:/,/^$/p' | tail -n +2 | awk '{print "### " $2 "\n\n" $1 " files\n"}')
18807
+
18808
+ ---
18809
+ Generated by loki docs generate on $timestamp
18810
+ EOF
18811
+ ;;
18812
+ TESTING)
18813
+ cat <<EOF
18814
+ # Testing
18815
+
18816
+ ## Overview
18817
+
18818
+ Test documentation for $project_name.
18819
+
18820
+ ## Running Tests
18821
+
18822
+ Refer to the project's test configuration for instructions.
18823
+
18824
+ ---
18825
+ Generated by loki docs generate on $timestamp
18826
+ EOF
18827
+ ;;
18828
+ DECISIONS)
18829
+ cat <<EOF
18830
+ # Architectural Decision Records
18831
+
18832
+ ## Overview
18833
+
18834
+ Key architectural decisions for $project_name.
18835
+
18836
+ ## ADR-001: [Title]
18837
+
18838
+ - **Status**: Proposed
18839
+ - **Context**: [Why this decision was needed]
18840
+ - **Decision**: [What was decided]
18841
+ - **Consequences**: [What are the trade-offs]
18842
+
18843
+ ---
18844
+ Generated by loki docs generate on $timestamp
18845
+ EOF
18846
+ ;;
18847
+ CLAUDE)
18848
+ cat <<EOF
18849
+ # $project_name
18850
+
18851
+ ## Project Overview
18852
+
18853
+ $files_line
18854
+
18855
+ ## Project Structure
18856
+
18857
+ \`\`\`
18858
+ $(echo "$context" | sed -n '/^FILE TREE:/,/^---/p' | head -50 | tail -n +2)
18859
+ \`\`\`
18860
+
18861
+ ## Key Commands
18862
+
18863
+ Refer to the project's configuration files for build, test, and run commands.
18864
+
18865
+ ---
18866
+ Generated by loki docs generate on $timestamp
18867
+ EOF
18868
+ ;;
18869
+ esac
18870
+ }
18871
+
18872
+ # --- loki docs update ---
18873
+ _docs_update() {
18874
+ local target_path="."
18875
+
18876
+ while [[ $# -gt 0 ]]; do
18877
+ case "$1" in
18878
+ --help|-h)
18879
+ echo "Usage: loki docs update [path]"
18880
+ echo "Incrementally update docs based on git changes since last generation."
18881
+ return 0
18882
+ ;;
18883
+ -*)
18884
+ log_error "Unknown option: $1"
18885
+ return 1
18886
+ ;;
18887
+ *)
18888
+ target_path="$1"
18889
+ shift
18890
+ ;;
18891
+ esac
18892
+ done
18893
+
18894
+ if [ ! -d "$target_path" ]; then
18895
+ log_error "Directory not found: $target_path"
18896
+ return 1
18897
+ fi
18898
+ target_path="$(cd "$target_path" && pwd)"
18899
+
18900
+ local docs_dir="$target_path/.loki/docs"
18901
+ local manifest="$docs_dir/docs-manifest.json"
18902
+
18903
+ if [ ! -f "$manifest" ]; then
18904
+ log_warn "No docs-manifest.json found. Running full generation instead."
18905
+ _docs_generate "$target_path"
18906
+ return $?
18907
+ fi
18908
+
18909
+ # Get the SHA from last generation
18910
+ local last_sha=""
18911
+ if command -v python3 &>/dev/null; then
18912
+ last_sha=$(python3 -c "
18913
+ import json
18914
+ try:
18915
+ d = json.load(open('$manifest'))
18916
+ print(d.get('git_sha', ''))
18917
+ except: pass
18918
+ " 2>/dev/null || true)
18919
+ fi
18920
+
18921
+ if [ -z "$last_sha" ] || [ "$last_sha" = "unknown" ]; then
18922
+ log_warn "Cannot determine last generation SHA. Running full generation."
18923
+ _docs_generate "$target_path"
18924
+ return $?
18925
+ fi
18926
+
18927
+ # Check if HEAD is the same as last generation
18928
+ local current_sha
18929
+ current_sha=$(cd "$target_path" && git rev-parse HEAD 2>/dev/null || echo "unknown")
18930
+
18931
+ if [ "$current_sha" = "$last_sha" ]; then
18932
+ log_info "Documentation is up to date (SHA: ${last_sha:0:8})"
18933
+ return 0
18934
+ fi
18935
+
18936
+ # Get changed files since last generation
18937
+ local changed_files=""
18938
+ changed_files=$(cd "$target_path" && git diff --name-only "$last_sha"..HEAD 2>/dev/null || true)
18939
+
18940
+ if [ -z "$changed_files" ]; then
18941
+ log_info "No file changes detected since last generation."
18942
+ return 0
18943
+ fi
18944
+
18945
+ local changed_count
18946
+ changed_count=$(echo "$changed_files" | wc -l | tr -d ' ')
18947
+ log_info "Found $changed_count changed files since last generation (${last_sha:0:8}..${current_sha:0:8})"
18948
+
18949
+ # Determine which doc types need updating based on changed files
18950
+ local docs_to_update=""
18951
+
18952
+ # README: always update if any significant files changed
18953
+ if echo "$changed_files" | grep -qE '\.(md|txt|json|toml|yaml|yml)$'; then
18954
+ docs_to_update="$docs_to_update README"
18955
+ fi
18956
+
18957
+ # ARCHITECTURE: update if directory structure or config changed
18958
+ if echo "$changed_files" | grep -qE '(package\.json|pyproject\.toml|Cargo\.toml|go\.mod|Dockerfile|docker-compose)'; then
18959
+ docs_to_update="$docs_to_update ARCHITECTURE"
18960
+ fi
18961
+
18962
+ # API: update if source files changed
18963
+ if echo "$changed_files" | grep -qE '\.(ts|js|py|go|rs|java|rb)$'; then
18964
+ docs_to_update="$docs_to_update API COMPONENTS"
18965
+ fi
18966
+
18967
+ # SETUP: update if config/dependency files changed
18968
+ if echo "$changed_files" | grep -qE '(package\.json|requirements\.txt|pyproject\.toml|Cargo\.toml|go\.mod|Dockerfile|\.env)'; then
18969
+ docs_to_update="$docs_to_update SETUP"
18970
+ fi
18971
+
18972
+ # TESTING: update if test files or test config changed
18973
+ if echo "$changed_files" | grep -qE '(test|spec|\.test\.|\.spec\.|jest\.config|vitest\.config|pytest|\.bats)'; then
18974
+ docs_to_update="$docs_to_update TESTING"
18975
+ fi
18976
+
18977
+ # CLAUDE: always update on any source change
18978
+ if echo "$changed_files" | grep -qE '\.(ts|js|py|go|rs|java|rb|sh)$'; then
18979
+ docs_to_update="$docs_to_update CLAUDE"
18980
+ fi
18981
+
18982
+ # Deduplicate
18983
+ docs_to_update=$(echo "$docs_to_update" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
18984
+
18985
+ if [ -z "$docs_to_update" ]; then
18986
+ log_info "No documentation updates needed for the changed files."
18987
+ # Still update manifest SHA
18988
+ _docs_write_manifest "$docs_dir" $(ls "$docs_dir"/*.md 2>/dev/null | xargs -I{} basename {} || true)
18989
+ return 0
18990
+ fi
18991
+
18992
+ log_info "Updating docs: $docs_to_update"
18993
+
18994
+ # Re-scan and rebuild context
18995
+ local tree_output
18996
+ tree_output=$(_docs_scan_project "$target_path")
18997
+ local context
18998
+ context=$(_docs_build_context "$target_path" "$tree_output")
18999
+
19000
+ # Add changed files context
19001
+ context="${context}
19002
+ RECENTLY CHANGED FILES (since last doc generation):
19003
+ $changed_files
19004
+ "
19005
+
19006
+ local provider="${LOKI_PROVIDER:-claude}"
19007
+ if [ -f "$target_path/.loki/state/provider" ]; then
19008
+ provider=$(cat "$target_path/.loki/state/provider" 2>/dev/null)
19009
+ fi
19010
+
19011
+ local provider_available=true
19012
+ if ! command -v "$provider" &>/dev/null; then
19013
+ provider_available=false
19014
+ log_warn "Provider '$provider' not found. Generating template-based docs instead."
19015
+ fi
19016
+
19017
+ local updated_files=""
19018
+ for doc_type in $docs_to_update; do
19019
+ local doc_file="${doc_type}.md"
19020
+ echo -e " ${CYAN}Updating${NC} $doc_file ..."
19021
+
19022
+ if [ "$provider_available" = true ]; then
19023
+ local existing_content=""
19024
+ [ -f "$docs_dir/$doc_file" ] && existing_content=$(cat "$docs_dir/$doc_file" 2>/dev/null || true)
19025
+
19026
+ local prompt="Based on the following project context, update the ${doc_type}.md documentation. Here are the files that changed since the last generation:
19027
+
19028
+ $changed_files
19029
+
19030
+ Current ${doc_type}.md content:
19031
+ $existing_content
19032
+
19033
+ Project context:
19034
+ $context
19035
+
19036
+ Generate an updated ${doc_type}.md that reflects the current state of the project. Use markdown formatting. Do NOT use emojis. Output ONLY the markdown content, no explanations."
19037
+
19038
+ local result=""
19039
+ result=$(_docs_invoke_provider "$prompt" 2>/dev/null) || true
19040
+ if [ -n "$result" ]; then
19041
+ echo "$result" > "$docs_dir/$doc_file"
19042
+ fi
19043
+ else
19044
+ _docs_generate_template "$doc_type" "$target_path" "$context" > "$docs_dir/$doc_file"
19045
+ fi
19046
+
19047
+ updated_files="$updated_files $doc_file"
19048
+ echo -e " ${GREEN}[done]${NC} $doc_file"
19049
+ done
19050
+
19051
+ # Update manifest with all existing docs
19052
+ local all_doc_files=""
19053
+ for f in "$docs_dir"/*.md; do
19054
+ [ -f "$f" ] && all_doc_files="$all_doc_files $(basename "$f")"
19055
+ done
19056
+ # shellcheck disable=SC2086
19057
+ _docs_write_manifest "$docs_dir" $all_doc_files
19058
+
19059
+ echo ""
19060
+ log_info "Updated $(echo "$updated_files" | wc -w | tr -d ' ') documentation files"
19061
+ }
19062
+
19063
+ # --- loki docs check ---
19064
+ _docs_check() {
19065
+ local target_path="."
19066
+
19067
+ while [[ $# -gt 0 ]]; do
19068
+ case "$1" in
19069
+ --help|-h)
19070
+ echo "Usage: loki docs check [path]"
19071
+ echo "Validate documentation coverage and staleness. Exit 0=pass, 1=fail."
19072
+ return 0
19073
+ ;;
19074
+ -*)
19075
+ log_error "Unknown option: $1"
19076
+ return 1
19077
+ ;;
19078
+ *)
19079
+ target_path="$1"
19080
+ shift
19081
+ ;;
19082
+ esac
19083
+ done
19084
+
19085
+ if [ ! -d "$target_path" ]; then
19086
+ log_error "Directory not found: $target_path"
19087
+ return 1
19088
+ fi
19089
+ target_path="$(cd "$target_path" && pwd)"
19090
+
19091
+ local docs_dir="$target_path/.loki/docs"
19092
+ local manifest="$docs_dir/docs-manifest.json"
19093
+ local pass=true
19094
+
19095
+ echo -e "${BOLD}Documentation Check${NC}"
19096
+ echo "----------------"
19097
+
19098
+ # Check 1: docs directory exists
19099
+ if [ ! -d "$docs_dir" ]; then
19100
+ echo -e " ${RED}[FAIL]${NC} No .loki/docs/ directory found"
19101
+ echo ""
19102
+ echo "Run 'loki docs generate' to create documentation."
19103
+ return 1
19104
+ fi
19105
+
19106
+ # Check 2: README.md exists and is non-empty
19107
+ if [ -f "$docs_dir/README.md" ] && [ -s "$docs_dir/README.md" ]; then
19108
+ echo -e " ${GREEN}[PASS]${NC} README.md exists and is non-empty"
19109
+ else
19110
+ echo -e " ${RED}[FAIL]${NC} README.md missing or empty"
19111
+ pass=false
19112
+ fi
19113
+
19114
+ # Check 3: manifest exists
19115
+ if [ -f "$manifest" ]; then
19116
+ echo -e " ${GREEN}[PASS]${NC} docs-manifest.json exists"
19117
+ else
19118
+ echo -e " ${RED}[FAIL]${NC} docs-manifest.json missing"
19119
+ pass=false
19120
+ fi
19121
+
19122
+ # Check 4: expected doc files exist
19123
+ local expected_docs="README.md ARCHITECTURE.md API.md SETUP.md COMPONENTS.md TESTING.md DECISIONS.md CLAUDE.md"
19124
+ local doc_count=0
19125
+ local expected_count=0
19126
+ for doc in $expected_docs; do
19127
+ ((++expected_count))
19128
+ if [ -f "$docs_dir/$doc" ] && [ -s "$docs_dir/$doc" ]; then
19129
+ ((++doc_count))
19130
+ else
19131
+ echo -e " ${YELLOW}[WARN]${NC} Missing or empty: $doc"
19132
+ fi
19133
+ done
19134
+ local coverage_pct=0
19135
+ [ "$expected_count" -gt 0 ] && coverage_pct=$(( doc_count * 100 / expected_count ))
19136
+ echo -e " ${BOLD}Coverage:${NC} $doc_count/$expected_count docs ($coverage_pct%)"
19137
+
19138
+ # Check 5: staleness (commits since last doc generation)
19139
+ if [ -f "$manifest" ] && command -v python3 &>/dev/null; then
19140
+ local last_sha=""
19141
+ last_sha=$(python3 -c "
19142
+ import json
19143
+ try:
19144
+ d = json.load(open('$manifest'))
19145
+ print(d.get('git_sha', ''))
19146
+ except: pass
19147
+ " 2>/dev/null || true)
19148
+
19149
+ if [ -n "$last_sha" ] && [ "$last_sha" != "unknown" ]; then
19150
+ local commits_behind=0
19151
+ commits_behind=$(cd "$target_path" && git rev-list --count "$last_sha"..HEAD 2>/dev/null || echo "0")
19152
+ if [ "$commits_behind" -gt 10 ]; then
19153
+ echo -e " ${RED}[FAIL]${NC} Docs are $commits_behind commits behind HEAD (threshold: 10)"
19154
+ pass=false
19155
+ elif [ "$commits_behind" -gt 0 ]; then
19156
+ echo -e " ${YELLOW}[WARN]${NC} Docs are $commits_behind commits behind HEAD"
19157
+ else
19158
+ echo -e " ${GREEN}[PASS]${NC} Docs are up to date with HEAD"
19159
+ fi
19160
+ fi
19161
+ fi
19162
+
19163
+ # Check 6: component coverage (check if major dirs have doc entries)
19164
+ local tree_output
19165
+ tree_output=$(_docs_scan_project "$target_path")
19166
+ local major_dirs
19167
+ major_dirs=$(echo "$tree_output" | sed 's|/.*||' | sort -u | head -20)
19168
+ local dir_count=0
19169
+ local documented_dirs=0
19170
+ if [ -f "$docs_dir/COMPONENTS.md" ]; then
19171
+ local components_content
19172
+ components_content=$(cat "$docs_dir/COMPONENTS.md" 2>/dev/null || true)
19173
+ while IFS= read -r dir; do
19174
+ [ -z "$dir" ] && continue
19175
+ [ ! -d "$target_path/$dir" ] && continue
19176
+ ((++dir_count))
19177
+ if echo "$components_content" | grep -qi "$dir" 2>/dev/null; then
19178
+ ((++documented_dirs))
19179
+ fi
19180
+ done <<< "$major_dirs"
19181
+ if [ "$dir_count" -gt 0 ]; then
19182
+ local dir_pct=$(( documented_dirs * 100 / dir_count ))
19183
+ echo -e " ${BOLD}Component coverage:${NC} $documented_dirs/$dir_count directories ($dir_pct%)"
19184
+ fi
19185
+ fi
19186
+
19187
+ echo ""
19188
+ if [ "$pass" = true ] && [ "$coverage_pct" -ge 75 ]; then
19189
+ echo -e "${GREEN}Result: PASS${NC}"
19190
+ return 0
19191
+ else
19192
+ echo -e "${RED}Result: FAIL${NC}"
19193
+ [ "$coverage_pct" -lt 75 ] && echo " Doc coverage below 75%. Run 'loki docs generate' to create missing docs."
19194
+ return 1
19195
+ fi
19196
+ }
19197
+
19198
+ # --- loki docs status ---
19199
+ _docs_status() {
19200
+ local target_path="."
19201
+
19202
+ while [[ $# -gt 0 ]]; do
19203
+ case "$1" in
19204
+ --help|-h)
19205
+ echo "Usage: loki docs status [path]"
19206
+ echo "Show documentation metrics."
19207
+ return 0
19208
+ ;;
19209
+ -*)
19210
+ log_error "Unknown option: $1"
19211
+ return 1
19212
+ ;;
19213
+ *)
19214
+ target_path="$1"
19215
+ shift
19216
+ ;;
19217
+ esac
19218
+ done
19219
+
19220
+ if [ ! -d "$target_path" ]; then
19221
+ log_error "Directory not found: $target_path"
19222
+ return 1
19223
+ fi
19224
+ target_path="$(cd "$target_path" && pwd)"
19225
+
19226
+ local docs_dir="$target_path/.loki/docs"
19227
+ local manifest="$docs_dir/docs-manifest.json"
19228
+
19229
+ echo -e "${BOLD}Documentation Status${NC}"
19230
+ echo "===================="
19231
+ echo ""
19232
+
19233
+ if [ ! -d "$docs_dir" ]; then
19234
+ echo "No documentation generated yet."
19235
+ echo "Run 'loki docs generate' to create documentation."
19236
+ return 0
19237
+ fi
19238
+
19239
+ # Doc count and sizes
19240
+ local doc_count=0
19241
+ local total_size=0
19242
+ echo -e "${BOLD}Documents:${NC}"
19243
+ for f in "$docs_dir"/*.md; do
19244
+ [ ! -f "$f" ] && continue
19245
+ ((++doc_count))
19246
+ local fsize
19247
+ fsize=$(wc -c < "$f" 2>/dev/null | tr -d ' ')
19248
+ total_size=$((total_size + fsize))
19249
+ local flines
19250
+ flines=$(wc -l < "$f" 2>/dev/null | tr -d ' ')
19251
+ local fname
19252
+ fname=$(basename "$f")
19253
+ printf " %-22s %5d lines %6s\n" "$fname" "$flines" "$(_docs_human_size "$fsize")"
19254
+ done
19255
+ echo ""
19256
+ echo " Total: $doc_count documents, $(_docs_human_size "$total_size")"
19257
+ echo ""
19258
+
19259
+ # Coverage
19260
+ local tree_output
19261
+ tree_output=$(_docs_scan_project "$target_path")
19262
+ local total_files
19263
+ total_files=$(echo "$tree_output" | { grep -c . || true; })
19264
+ local src_files
19265
+ src_files=$(echo "$tree_output" | { grep -cE '\.(js|ts|tsx|jsx|py|rs|go|rb|java|kt|c|cpp|h|hpp|sh)$' || true; })
19266
+
19267
+ local expected_docs="README.md ARCHITECTURE.md API.md SETUP.md COMPONENTS.md TESTING.md DECISIONS.md CLAUDE.md"
19268
+ local existing=0
19269
+ local expected_total=0
19270
+ for doc in $expected_docs; do
19271
+ ((++expected_total))
19272
+ [ -f "$docs_dir/$doc" ] && [ -s "$docs_dir/$doc" ] && ((++existing))
19273
+ done
19274
+ local pct=0
19275
+ [ "$expected_total" -gt 0 ] && pct=$(( existing * 100 / expected_total ))
19276
+ echo -e "${BOLD}Coverage:${NC} $existing/$expected_total doc types ($pct%)"
19277
+ echo " Project: $total_files files ($src_files source)"
19278
+ echo ""
19279
+
19280
+ # Staleness
19281
+ if [ -f "$manifest" ] && command -v python3 &>/dev/null; then
19282
+ local last_sha="" generated_at=""
19283
+ eval "$(python3 -c "
19284
+ import json
19285
+ try:
19286
+ d = json.load(open('$manifest'))
19287
+ print('last_sha=\"' + d.get('git_sha', 'unknown') + '\"')
19288
+ print('generated_at=\"' + d.get('generated_at', 'unknown') + '\"')
19289
+ except: pass
19290
+ " 2>/dev/null || true)"
19291
+
19292
+ echo -e "${BOLD}Last generated:${NC} $generated_at"
19293
+ if [ -n "$last_sha" ] && [ "$last_sha" != "unknown" ]; then
19294
+ echo " Git SHA: ${last_sha:0:12}"
19295
+ local commits_behind=0
19296
+ commits_behind=$(cd "$target_path" && git rev-list --count "$last_sha"..HEAD 2>/dev/null || echo "0")
19297
+ if [ "$commits_behind" -eq 0 ]; then
19298
+ echo -e " Staleness: ${GREEN}up to date${NC}"
19299
+ elif [ "$commits_behind" -le 10 ]; then
19300
+ echo -e " Staleness: ${YELLOW}$commits_behind commits behind${NC}"
19301
+ else
19302
+ echo -e " Staleness: ${RED}$commits_behind commits behind${NC} (consider running 'loki docs update')"
19303
+ fi
19304
+ fi
19305
+ fi
19306
+ }
19307
+
19308
+ # --- Docs helper: human-readable file size ---
19309
+ _docs_human_size() {
19310
+ local bytes="$1"
19311
+ if [ "$bytes" -ge 1048576 ]; then
19312
+ echo "$((bytes / 1048576))MB"
19313
+ elif [ "$bytes" -ge 1024 ]; then
19314
+ echo "$((bytes / 1024))KB"
19315
+ else
19316
+ echo "${bytes}B"
19317
+ fi
19318
+ }
19319
+
18325
19320
  # CI/CD quality gate integration (v6.22.0)
18326
19321
  cmd_ci() {
18327
19322
  local ci_pr=false