loki-mode 6.74.6 → 6.75.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/README.md +1 -53
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +989 -0
- package/autonomy/run.sh +106 -2
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/mcp/server.py +177 -0
- package/package.json +1 -1
- package/references/mcp-integration.md +59 -0
- package/skills/00-index.md +9 -0
- package/skills/documentation.md +123 -0
- package/skills/quality-gates.md +25 -1
- package/web-app/dist/assets/{AdminPage-Cwqm_kDg.js → AdminPage-D4QSV6Zi.js} +1 -1
- package/web-app/dist/assets/{Avatar-BgcFY2E5.js → Avatar-88MlpLO5.js} +1 -1
- package/web-app/dist/assets/{Badge-DeFGfZLB.js → Badge-DbGjLr4i.js} +1 -1
- package/web-app/dist/assets/{Button-Dg1EkPtN.js → Button-sp_FVGZj.js} +1 -1
- package/web-app/dist/assets/{ComparePage-D-wvMVP2.js → ComparePage-p2ENnfa7.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-B_Jm7CmJ.js → GitHubIssuesPanel-DBbBTG9w.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-B5i8Q99N.js → GitHubPRsPanel-Bi_yrcAE.js} +1 -1
- package/web-app/dist/assets/{HomePage-CUDTdntY.js → HomePage-BB83YPiX.js} +1 -1
- package/web-app/dist/assets/{LoginPage-BobwVXx5.js → LoginPage-BXUudCJ9.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-DmM--20B.js → MetricsPage-CX0Ahy-_.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-6_gjeogD.js → NotFoundPage-C4JqatEk.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-C3R2wbFt.js → ProjectPage-t5J2XAJT.js} +46 -46
- package/web-app/dist/assets/{ProjectsPage-DQr06iBk.js → ProjectsPage-Bzpz1clk.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-eROlGKB9.js → SettingsPage-y_yl8FvH.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-DEA5tT_R.js → ShowcasePage-B7d6pzMq.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-C_rIbgZg.js → SystemSettingsPage-C4tR33KU.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-DZGoYZnD.js → TeamsPage-DIOCfZIP.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-DVblWpbO.js → TemplatesPage-DlKyapXX.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-DnESY9zk.js → TerminalOutput-Czg-ZC2k.js} +1 -1
- package/web-app/dist/assets/{activity-COLsZyo1.js → activity-h1wU9a0L.js} +1 -1
- package/web-app/dist/assets/{bell-BjLe9xXk.js → bell-Bu8lsWOp.js} +1 -1
- package/web-app/dist/assets/{bot-Dz62aBIi.js → bot-rWO7KjkQ.js} +1 -1
- package/web-app/dist/assets/{check-BQPQjkH4.js → check-BWp8L5Cy.js} +1 -1
- package/web-app/dist/assets/{chevron-left-BVvOVUQ8.js → chevron-left-Bw4I1yGm.js} +1 -1
- package/web-app/dist/assets/{circle-alert-DqoLW238.js → circle-alert-C37PKXiC.js} +1 -1
- package/web-app/dist/assets/{clock-Cn9fFUva.js → clock-DDScLol4.js} +1 -1
- package/web-app/dist/assets/{cloud-COJxbgUu.js → cloud-DaYKPLaM.js} +1 -1
- package/web-app/dist/assets/{copy-DOn0hVgy.js → copy-DKIRv0VK.js} +1 -1
- package/web-app/dist/assets/{database-Bj3Llvnk.js → database-CYZBHz51.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-BcmncygQ.js → dollar-sign-CydJu0kl.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-BysxoSyu.js → file-code-corner-DqZ9gpdv.js} +1 -1
- package/web-app/dist/assets/{file-plus-Dwi1MqSS.js → file-plus-CzeFJWp3.js} +1 -1
- package/web-app/dist/assets/{folder-open-WzXNCq2x.js → folder-open-4YWk08dP.js} +1 -1
- package/web-app/dist/assets/{git-commit-horizontal-D85UUfNF.js → git-commit-horizontal-wbqFPNID.js} +1 -1
- package/web-app/dist/assets/{globe-CegT0VaL.js → globe-Cby-g5Yb.js} +1 -1
- package/web-app/dist/assets/{hammer-ZGKvu_xy.js → hammer-BNScgGdp.js} +1 -1
- package/web-app/dist/assets/{index-_2iPl2nX.js → index-6Z4B0I6r.js} +74 -74
- package/web-app/dist/assets/{layers-B40lki5j.js → layers-XfssQc5V.js} +1 -1
- package/web-app/dist/assets/{lightbulb-CFSoqUsV.js → lightbulb-EhnzRw7M.js} +1 -1
- package/web-app/dist/assets/{loader-circle-womi7Brk.js → loader-circle-BA0QIVGA.js} +1 -1
- package/web-app/dist/assets/{lock-CEWBb_SL.js → lock-BABtHe6K.js} +1 -1
- package/web-app/dist/assets/{mail-DimGrYf8.js → mail-Dokiey5S.js} +1 -1
- package/web-app/dist/assets/{package-DysIuuIJ.js → package-DbJyS1Ft.js} +1 -1
- package/web-app/dist/assets/{plus-DG1hW27_.js → plus-BcAN8Kaj.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-X31ig0S6.js → refresh-cw-B3dG1-Sb.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-CBXooICo.js → rotate-ccw-Cs1Phctm.js} +1 -1
- package/web-app/dist/assets/{save-DQVrWTjZ.js → save-DsrNCZrP.js} +1 -1
- package/web-app/dist/assets/{server-BYAMALfa.js → server-CpN2GX4G.js} +1 -1
- package/web-app/dist/assets/{shield-alert-B3G7vLiW.js → shield-alert-CKJ1pzCz.js} +1 -1
- package/web-app/dist/assets/{trash-2-ChVunC-C.js → trash-2-C9vZqTqw.js} +1 -1
- package/web-app/dist/assets/{trending-down-DgsuOE2t.js → trending-down-BNLTrF5P.js} +1 -1
- package/web-app/dist/assets/{trending-up-CZHZsGeN.js → trending-up-DmFIdVOc.js} +1 -1
- package/web-app/dist/assets/{usePolling-ns_dFCVn.js → usePolling-vUlY-o6P.js} +1 -1
- package/web-app/dist/assets/{user-FQUrWHhF.js → user-Dh00W8De.js} +1 -1
- package/web-app/dist/index.html +1 -1
- 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,991 @@ 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
|
+
case "$provider" in
|
|
18484
|
+
claude)
|
|
18485
|
+
result=$(claude -p "$prompt" 2>/dev/null) || exit_code=$?
|
|
18486
|
+
;;
|
|
18487
|
+
codex)
|
|
18488
|
+
result=$(codex exec --full-auto "$prompt" 2>/dev/null) || exit_code=$?
|
|
18489
|
+
;;
|
|
18490
|
+
gemini)
|
|
18491
|
+
result=$(gemini --approval-mode=yolo "$prompt" 2>/dev/null) || exit_code=$?
|
|
18492
|
+
;;
|
|
18493
|
+
cline)
|
|
18494
|
+
result=$(cline -y "$prompt" 2>/dev/null) || exit_code=$?
|
|
18495
|
+
;;
|
|
18496
|
+
aider)
|
|
18497
|
+
result=$(aider --message "$prompt" --yes-always --no-auto-commits < /dev/null 2>/dev/null) || exit_code=$?
|
|
18498
|
+
;;
|
|
18499
|
+
*)
|
|
18500
|
+
log_error "Unknown provider: $provider"
|
|
18501
|
+
return 1
|
|
18502
|
+
;;
|
|
18503
|
+
esac
|
|
18504
|
+
|
|
18505
|
+
if [ $exit_code -ne 0 ] && [ -z "$result" ]; then
|
|
18506
|
+
log_error "Provider '$provider' failed (exit $exit_code)"
|
|
18507
|
+
return 1
|
|
18508
|
+
fi
|
|
18509
|
+
|
|
18510
|
+
echo "$result"
|
|
18511
|
+
}
|
|
18512
|
+
|
|
18513
|
+
# --- Docs helper: write manifest ---
|
|
18514
|
+
_docs_write_manifest() {
|
|
18515
|
+
local docs_dir="$1"
|
|
18516
|
+
shift
|
|
18517
|
+
# Remaining args are file names
|
|
18518
|
+
|
|
18519
|
+
local git_sha=""
|
|
18520
|
+
git_sha=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
18521
|
+
local generated_at
|
|
18522
|
+
generated_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
18523
|
+
|
|
18524
|
+
local files_json="{"
|
|
18525
|
+
local first=true
|
|
18526
|
+
for doc_file in "$@"; do
|
|
18527
|
+
local fname
|
|
18528
|
+
fname=$(basename "$doc_file")
|
|
18529
|
+
if [ -f "$docs_dir/$fname" ]; then
|
|
18530
|
+
local file_sha
|
|
18531
|
+
file_sha=$(shasum -a 256 "$docs_dir/$fname" 2>/dev/null | awk '{print $1}' || echo "unknown")
|
|
18532
|
+
if [ "$first" = true ]; then
|
|
18533
|
+
first=false
|
|
18534
|
+
else
|
|
18535
|
+
files_json="${files_json},"
|
|
18536
|
+
fi
|
|
18537
|
+
files_json="${files_json}
|
|
18538
|
+
\"$fname\": {\"sha256\": \"$file_sha\", \"generated_at\": \"$generated_at\"}"
|
|
18539
|
+
fi
|
|
18540
|
+
done
|
|
18541
|
+
files_json="${files_json}
|
|
18542
|
+
}"
|
|
18543
|
+
|
|
18544
|
+
cat > "$docs_dir/docs-manifest.json" <<MANIFEST_EOF
|
|
18545
|
+
{
|
|
18546
|
+
"generated_at": "$generated_at",
|
|
18547
|
+
"git_sha": "$git_sha",
|
|
18548
|
+
"files": $files_json
|
|
18549
|
+
}
|
|
18550
|
+
MANIFEST_EOF
|
|
18551
|
+
}
|
|
18552
|
+
|
|
18553
|
+
# --- loki docs generate ---
|
|
18554
|
+
_docs_generate() {
|
|
18555
|
+
local target_path="."
|
|
18556
|
+
|
|
18557
|
+
while [[ $# -gt 0 ]]; do
|
|
18558
|
+
case "$1" in
|
|
18559
|
+
--help|-h)
|
|
18560
|
+
echo "Usage: loki docs generate [path]"
|
|
18561
|
+
echo "Generate full documentation suite in .loki/docs/"
|
|
18562
|
+
return 0
|
|
18563
|
+
;;
|
|
18564
|
+
-*)
|
|
18565
|
+
log_error "Unknown option: $1"
|
|
18566
|
+
return 1
|
|
18567
|
+
;;
|
|
18568
|
+
*)
|
|
18569
|
+
target_path="$1"
|
|
18570
|
+
shift
|
|
18571
|
+
;;
|
|
18572
|
+
esac
|
|
18573
|
+
done
|
|
18574
|
+
|
|
18575
|
+
if [ ! -d "$target_path" ]; then
|
|
18576
|
+
log_error "Directory not found: $target_path"
|
|
18577
|
+
return 1
|
|
18578
|
+
fi
|
|
18579
|
+
target_path="$(cd "$target_path" && pwd)"
|
|
18580
|
+
|
|
18581
|
+
local docs_dir="$target_path/.loki/docs"
|
|
18582
|
+
mkdir -p "$docs_dir"
|
|
18583
|
+
|
|
18584
|
+
log_info "Scanning project at: $target_path"
|
|
18585
|
+
|
|
18586
|
+
# Scan project
|
|
18587
|
+
local tree_output
|
|
18588
|
+
tree_output=$(_docs_scan_project "$target_path")
|
|
18589
|
+
local context
|
|
18590
|
+
context=$(_docs_build_context "$target_path" "$tree_output")
|
|
18591
|
+
|
|
18592
|
+
local project_name
|
|
18593
|
+
project_name="$(basename "$target_path")"
|
|
18594
|
+
|
|
18595
|
+
# Check if provider CLI is available
|
|
18596
|
+
local provider="${LOKI_PROVIDER:-claude}"
|
|
18597
|
+
if [ -f "$target_path/.loki/state/provider" ]; then
|
|
18598
|
+
provider=$(cat "$target_path/.loki/state/provider" 2>/dev/null)
|
|
18599
|
+
fi
|
|
18600
|
+
|
|
18601
|
+
local provider_available=true
|
|
18602
|
+
if ! command -v "$provider" &>/dev/null; then
|
|
18603
|
+
provider_available=false
|
|
18604
|
+
log_warn "Provider '$provider' not found. Generating template-based docs instead."
|
|
18605
|
+
fi
|
|
18606
|
+
|
|
18607
|
+
# Doc types to generate
|
|
18608
|
+
local doc_types="README ARCHITECTURE API SETUP COMPONENTS TESTING DECISIONS CLAUDE"
|
|
18609
|
+
local generated_files=""
|
|
18610
|
+
|
|
18611
|
+
for doc_type in $doc_types; do
|
|
18612
|
+
local doc_file="${doc_type}.md"
|
|
18613
|
+
local prompt=""
|
|
18614
|
+
|
|
18615
|
+
echo -e " ${CYAN}Generating${NC} $doc_file ..."
|
|
18616
|
+
|
|
18617
|
+
if [ "$provider_available" = true ]; then
|
|
18618
|
+
# Build a focused prompt for each doc type
|
|
18619
|
+
case "$doc_type" in
|
|
18620
|
+
README)
|
|
18621
|
+
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.
|
|
18622
|
+
|
|
18623
|
+
$context"
|
|
18624
|
+
;;
|
|
18625
|
+
ARCHITECTURE)
|
|
18626
|
+
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.
|
|
18627
|
+
|
|
18628
|
+
$context"
|
|
18629
|
+
;;
|
|
18630
|
+
API)
|
|
18631
|
+
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.
|
|
18632
|
+
|
|
18633
|
+
$context"
|
|
18634
|
+
;;
|
|
18635
|
+
SETUP)
|
|
18636
|
+
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.
|
|
18637
|
+
|
|
18638
|
+
$context"
|
|
18639
|
+
;;
|
|
18640
|
+
COMPONENTS)
|
|
18641
|
+
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.
|
|
18642
|
+
|
|
18643
|
+
$context"
|
|
18644
|
+
;;
|
|
18645
|
+
TESTING)
|
|
18646
|
+
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.
|
|
18647
|
+
|
|
18648
|
+
$context"
|
|
18649
|
+
;;
|
|
18650
|
+
DECISIONS)
|
|
18651
|
+
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.
|
|
18652
|
+
|
|
18653
|
+
$context"
|
|
18654
|
+
;;
|
|
18655
|
+
CLAUDE)
|
|
18656
|
+
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.
|
|
18657
|
+
|
|
18658
|
+
$context"
|
|
18659
|
+
;;
|
|
18660
|
+
esac
|
|
18661
|
+
|
|
18662
|
+
local result=""
|
|
18663
|
+
result=$(_docs_invoke_provider "$prompt" 2>/dev/null) || true
|
|
18664
|
+
|
|
18665
|
+
if [ -n "$result" ]; then
|
|
18666
|
+
echo "$result" > "$docs_dir/$doc_file"
|
|
18667
|
+
generated_files="$generated_files $doc_file"
|
|
18668
|
+
else
|
|
18669
|
+
# Fallback to template
|
|
18670
|
+
_docs_generate_template "$doc_type" "$target_path" "$context" > "$docs_dir/$doc_file"
|
|
18671
|
+
generated_files="$generated_files $doc_file"
|
|
18672
|
+
fi
|
|
18673
|
+
else
|
|
18674
|
+
# Template-based generation (no AI provider)
|
|
18675
|
+
_docs_generate_template "$doc_type" "$target_path" "$context" > "$docs_dir/$doc_file"
|
|
18676
|
+
generated_files="$generated_files $doc_file"
|
|
18677
|
+
fi
|
|
18678
|
+
|
|
18679
|
+
echo -e " ${GREEN}[done]${NC} $doc_file"
|
|
18680
|
+
done
|
|
18681
|
+
|
|
18682
|
+
# Write manifest
|
|
18683
|
+
# shellcheck disable=SC2086
|
|
18684
|
+
_docs_write_manifest "$docs_dir" $generated_files
|
|
18685
|
+
|
|
18686
|
+
echo ""
|
|
18687
|
+
log_info "Documentation generated in $docs_dir/"
|
|
18688
|
+
log_info "Files: $(echo "$generated_files" | wc -w | tr -d ' ') docs + manifest"
|
|
18689
|
+
echo -e " Manifest: ${CYAN}$docs_dir/docs-manifest.json${NC}"
|
|
18690
|
+
}
|
|
18691
|
+
|
|
18692
|
+
# --- Docs helper: generate template-based doc (fallback when no AI provider) ---
|
|
18693
|
+
_docs_generate_template() {
|
|
18694
|
+
local doc_type="$1"
|
|
18695
|
+
local target_path="$2"
|
|
18696
|
+
local context="$3"
|
|
18697
|
+
local project_name
|
|
18698
|
+
project_name="$(basename "$target_path")"
|
|
18699
|
+
local timestamp
|
|
18700
|
+
timestamp=$(date +%Y-%m-%d)
|
|
18701
|
+
|
|
18702
|
+
# Extract basic metadata from context
|
|
18703
|
+
local files_line
|
|
18704
|
+
files_line=$(echo "$context" | grep '^FILES:' | head -1 || echo "FILES: unknown")
|
|
18705
|
+
|
|
18706
|
+
case "$doc_type" in
|
|
18707
|
+
README)
|
|
18708
|
+
cat <<EOF
|
|
18709
|
+
# $project_name
|
|
18710
|
+
|
|
18711
|
+
## Overview
|
|
18712
|
+
|
|
18713
|
+
Project at \`$target_path\`.
|
|
18714
|
+
|
|
18715
|
+
$files_line
|
|
18716
|
+
|
|
18717
|
+
## Getting Started
|
|
18718
|
+
|
|
18719
|
+
See SETUP.md for installation and development instructions.
|
|
18720
|
+
|
|
18721
|
+
## Documentation
|
|
18722
|
+
|
|
18723
|
+
- [Architecture](ARCHITECTURE.md)
|
|
18724
|
+
- [API Reference](API.md)
|
|
18725
|
+
- [Setup Guide](SETUP.md)
|
|
18726
|
+
- [Components](COMPONENTS.md)
|
|
18727
|
+
- [Testing](TESTING.md)
|
|
18728
|
+
- [Decisions](DECISIONS.md)
|
|
18729
|
+
|
|
18730
|
+
---
|
|
18731
|
+
Generated by loki docs generate on $timestamp
|
|
18732
|
+
EOF
|
|
18733
|
+
;;
|
|
18734
|
+
ARCHITECTURE)
|
|
18735
|
+
cat <<EOF
|
|
18736
|
+
# Architecture
|
|
18737
|
+
|
|
18738
|
+
## Overview
|
|
18739
|
+
|
|
18740
|
+
This document describes the architecture of $project_name.
|
|
18741
|
+
|
|
18742
|
+
## Directory Structure
|
|
18743
|
+
|
|
18744
|
+
\`\`\`
|
|
18745
|
+
$(echo "$context" | sed -n '/^DIRECTORY STRUCTURE:/,/^$/p' | tail -n +2)
|
|
18746
|
+
\`\`\`
|
|
18747
|
+
|
|
18748
|
+
## Key Components
|
|
18749
|
+
|
|
18750
|
+
See COMPONENTS.md for detailed component documentation.
|
|
18751
|
+
|
|
18752
|
+
---
|
|
18753
|
+
Generated by loki docs generate on $timestamp
|
|
18754
|
+
EOF
|
|
18755
|
+
;;
|
|
18756
|
+
API)
|
|
18757
|
+
cat <<EOF
|
|
18758
|
+
# API Reference
|
|
18759
|
+
|
|
18760
|
+
## Overview
|
|
18761
|
+
|
|
18762
|
+
Public API documentation for $project_name.
|
|
18763
|
+
|
|
18764
|
+
TODO: Document public endpoints, functions, and classes.
|
|
18765
|
+
|
|
18766
|
+
---
|
|
18767
|
+
Generated by loki docs generate on $timestamp
|
|
18768
|
+
EOF
|
|
18769
|
+
;;
|
|
18770
|
+
SETUP)
|
|
18771
|
+
cat <<EOF
|
|
18772
|
+
# Setup Guide
|
|
18773
|
+
|
|
18774
|
+
## Prerequisites
|
|
18775
|
+
|
|
18776
|
+
See the project configuration files for required dependencies.
|
|
18777
|
+
|
|
18778
|
+
## Installation
|
|
18779
|
+
|
|
18780
|
+
Clone the repository and install dependencies.
|
|
18781
|
+
|
|
18782
|
+
## Running Locally
|
|
18783
|
+
|
|
18784
|
+
Refer to the project's build and run scripts.
|
|
18785
|
+
|
|
18786
|
+
---
|
|
18787
|
+
Generated by loki docs generate on $timestamp
|
|
18788
|
+
EOF
|
|
18789
|
+
;;
|
|
18790
|
+
COMPONENTS)
|
|
18791
|
+
cat <<EOF
|
|
18792
|
+
# Components
|
|
18793
|
+
|
|
18794
|
+
## Overview
|
|
18795
|
+
|
|
18796
|
+
Component documentation for $project_name.
|
|
18797
|
+
|
|
18798
|
+
## Modules
|
|
18799
|
+
|
|
18800
|
+
$(echo "$context" | sed -n '/^DIRECTORY STRUCTURE:/,/^$/p' | tail -n +2 | awk '{print "### " $2 "\n\n" $1 " files\n"}')
|
|
18801
|
+
|
|
18802
|
+
---
|
|
18803
|
+
Generated by loki docs generate on $timestamp
|
|
18804
|
+
EOF
|
|
18805
|
+
;;
|
|
18806
|
+
TESTING)
|
|
18807
|
+
cat <<EOF
|
|
18808
|
+
# Testing
|
|
18809
|
+
|
|
18810
|
+
## Overview
|
|
18811
|
+
|
|
18812
|
+
Test documentation for $project_name.
|
|
18813
|
+
|
|
18814
|
+
## Running Tests
|
|
18815
|
+
|
|
18816
|
+
Refer to the project's test configuration for instructions.
|
|
18817
|
+
|
|
18818
|
+
---
|
|
18819
|
+
Generated by loki docs generate on $timestamp
|
|
18820
|
+
EOF
|
|
18821
|
+
;;
|
|
18822
|
+
DECISIONS)
|
|
18823
|
+
cat <<EOF
|
|
18824
|
+
# Architectural Decision Records
|
|
18825
|
+
|
|
18826
|
+
## Overview
|
|
18827
|
+
|
|
18828
|
+
Key architectural decisions for $project_name.
|
|
18829
|
+
|
|
18830
|
+
## ADR-001: [Title]
|
|
18831
|
+
|
|
18832
|
+
- **Status**: Proposed
|
|
18833
|
+
- **Context**: [Why this decision was needed]
|
|
18834
|
+
- **Decision**: [What was decided]
|
|
18835
|
+
- **Consequences**: [What are the trade-offs]
|
|
18836
|
+
|
|
18837
|
+
---
|
|
18838
|
+
Generated by loki docs generate on $timestamp
|
|
18839
|
+
EOF
|
|
18840
|
+
;;
|
|
18841
|
+
CLAUDE)
|
|
18842
|
+
cat <<EOF
|
|
18843
|
+
# $project_name
|
|
18844
|
+
|
|
18845
|
+
## Project Overview
|
|
18846
|
+
|
|
18847
|
+
$files_line
|
|
18848
|
+
|
|
18849
|
+
## Project Structure
|
|
18850
|
+
|
|
18851
|
+
\`\`\`
|
|
18852
|
+
$(echo "$context" | sed -n '/^FILE TREE:/,/^---/p' | head -50 | tail -n +2)
|
|
18853
|
+
\`\`\`
|
|
18854
|
+
|
|
18855
|
+
## Key Commands
|
|
18856
|
+
|
|
18857
|
+
Refer to the project's configuration files for build, test, and run commands.
|
|
18858
|
+
|
|
18859
|
+
---
|
|
18860
|
+
Generated by loki docs generate on $timestamp
|
|
18861
|
+
EOF
|
|
18862
|
+
;;
|
|
18863
|
+
esac
|
|
18864
|
+
}
|
|
18865
|
+
|
|
18866
|
+
# --- loki docs update ---
|
|
18867
|
+
_docs_update() {
|
|
18868
|
+
local target_path="."
|
|
18869
|
+
|
|
18870
|
+
while [[ $# -gt 0 ]]; do
|
|
18871
|
+
case "$1" in
|
|
18872
|
+
--help|-h)
|
|
18873
|
+
echo "Usage: loki docs update [path]"
|
|
18874
|
+
echo "Incrementally update docs based on git changes since last generation."
|
|
18875
|
+
return 0
|
|
18876
|
+
;;
|
|
18877
|
+
-*)
|
|
18878
|
+
log_error "Unknown option: $1"
|
|
18879
|
+
return 1
|
|
18880
|
+
;;
|
|
18881
|
+
*)
|
|
18882
|
+
target_path="$1"
|
|
18883
|
+
shift
|
|
18884
|
+
;;
|
|
18885
|
+
esac
|
|
18886
|
+
done
|
|
18887
|
+
|
|
18888
|
+
if [ ! -d "$target_path" ]; then
|
|
18889
|
+
log_error "Directory not found: $target_path"
|
|
18890
|
+
return 1
|
|
18891
|
+
fi
|
|
18892
|
+
target_path="$(cd "$target_path" && pwd)"
|
|
18893
|
+
|
|
18894
|
+
local docs_dir="$target_path/.loki/docs"
|
|
18895
|
+
local manifest="$docs_dir/docs-manifest.json"
|
|
18896
|
+
|
|
18897
|
+
if [ ! -f "$manifest" ]; then
|
|
18898
|
+
log_warn "No docs-manifest.json found. Running full generation instead."
|
|
18899
|
+
_docs_generate "$target_path"
|
|
18900
|
+
return $?
|
|
18901
|
+
fi
|
|
18902
|
+
|
|
18903
|
+
# Get the SHA from last generation
|
|
18904
|
+
local last_sha=""
|
|
18905
|
+
if command -v python3 &>/dev/null; then
|
|
18906
|
+
last_sha=$(python3 -c "
|
|
18907
|
+
import json
|
|
18908
|
+
try:
|
|
18909
|
+
d = json.load(open('$manifest'))
|
|
18910
|
+
print(d.get('git_sha', ''))
|
|
18911
|
+
except: pass
|
|
18912
|
+
" 2>/dev/null || true)
|
|
18913
|
+
fi
|
|
18914
|
+
|
|
18915
|
+
if [ -z "$last_sha" ] || [ "$last_sha" = "unknown" ]; then
|
|
18916
|
+
log_warn "Cannot determine last generation SHA. Running full generation."
|
|
18917
|
+
_docs_generate "$target_path"
|
|
18918
|
+
return $?
|
|
18919
|
+
fi
|
|
18920
|
+
|
|
18921
|
+
# Check if HEAD is the same as last generation
|
|
18922
|
+
local current_sha
|
|
18923
|
+
current_sha=$(cd "$target_path" && git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
18924
|
+
|
|
18925
|
+
if [ "$current_sha" = "$last_sha" ]; then
|
|
18926
|
+
log_info "Documentation is up to date (SHA: ${last_sha:0:8})"
|
|
18927
|
+
return 0
|
|
18928
|
+
fi
|
|
18929
|
+
|
|
18930
|
+
# Get changed files since last generation
|
|
18931
|
+
local changed_files=""
|
|
18932
|
+
changed_files=$(cd "$target_path" && git diff --name-only "$last_sha"..HEAD 2>/dev/null || true)
|
|
18933
|
+
|
|
18934
|
+
if [ -z "$changed_files" ]; then
|
|
18935
|
+
log_info "No file changes detected since last generation."
|
|
18936
|
+
return 0
|
|
18937
|
+
fi
|
|
18938
|
+
|
|
18939
|
+
local changed_count
|
|
18940
|
+
changed_count=$(echo "$changed_files" | wc -l | tr -d ' ')
|
|
18941
|
+
log_info "Found $changed_count changed files since last generation (${last_sha:0:8}..${current_sha:0:8})"
|
|
18942
|
+
|
|
18943
|
+
# Determine which doc types need updating based on changed files
|
|
18944
|
+
local docs_to_update=""
|
|
18945
|
+
|
|
18946
|
+
# README: always update if any significant files changed
|
|
18947
|
+
if echo "$changed_files" | grep -qE '\.(md|txt|json|toml|yaml|yml)$'; then
|
|
18948
|
+
docs_to_update="$docs_to_update README"
|
|
18949
|
+
fi
|
|
18950
|
+
|
|
18951
|
+
# ARCHITECTURE: update if directory structure or config changed
|
|
18952
|
+
if echo "$changed_files" | grep -qE '(package\.json|pyproject\.toml|Cargo\.toml|go\.mod|Dockerfile|docker-compose)'; then
|
|
18953
|
+
docs_to_update="$docs_to_update ARCHITECTURE"
|
|
18954
|
+
fi
|
|
18955
|
+
|
|
18956
|
+
# API: update if source files changed
|
|
18957
|
+
if echo "$changed_files" | grep -qE '\.(ts|js|py|go|rs|java|rb)$'; then
|
|
18958
|
+
docs_to_update="$docs_to_update API COMPONENTS"
|
|
18959
|
+
fi
|
|
18960
|
+
|
|
18961
|
+
# SETUP: update if config/dependency files changed
|
|
18962
|
+
if echo "$changed_files" | grep -qE '(package\.json|requirements\.txt|pyproject\.toml|Cargo\.toml|go\.mod|Dockerfile|\.env)'; then
|
|
18963
|
+
docs_to_update="$docs_to_update SETUP"
|
|
18964
|
+
fi
|
|
18965
|
+
|
|
18966
|
+
# TESTING: update if test files or test config changed
|
|
18967
|
+
if echo "$changed_files" | grep -qE '(test|spec|\.test\.|\.spec\.|jest\.config|vitest\.config|pytest|\.bats)'; then
|
|
18968
|
+
docs_to_update="$docs_to_update TESTING"
|
|
18969
|
+
fi
|
|
18970
|
+
|
|
18971
|
+
# CLAUDE: always update on any source change
|
|
18972
|
+
if echo "$changed_files" | grep -qE '\.(ts|js|py|go|rs|java|rb|sh)$'; then
|
|
18973
|
+
docs_to_update="$docs_to_update CLAUDE"
|
|
18974
|
+
fi
|
|
18975
|
+
|
|
18976
|
+
# Deduplicate
|
|
18977
|
+
docs_to_update=$(echo "$docs_to_update" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
|
|
18978
|
+
|
|
18979
|
+
if [ -z "$docs_to_update" ]; then
|
|
18980
|
+
log_info "No documentation updates needed for the changed files."
|
|
18981
|
+
# Still update manifest SHA
|
|
18982
|
+
_docs_write_manifest "$docs_dir" $(ls "$docs_dir"/*.md 2>/dev/null | xargs -I{} basename {} || true)
|
|
18983
|
+
return 0
|
|
18984
|
+
fi
|
|
18985
|
+
|
|
18986
|
+
log_info "Updating docs: $docs_to_update"
|
|
18987
|
+
|
|
18988
|
+
# Re-scan and rebuild context
|
|
18989
|
+
local tree_output
|
|
18990
|
+
tree_output=$(_docs_scan_project "$target_path")
|
|
18991
|
+
local context
|
|
18992
|
+
context=$(_docs_build_context "$target_path" "$tree_output")
|
|
18993
|
+
|
|
18994
|
+
# Add changed files context
|
|
18995
|
+
context="${context}
|
|
18996
|
+
RECENTLY CHANGED FILES (since last doc generation):
|
|
18997
|
+
$changed_files
|
|
18998
|
+
"
|
|
18999
|
+
|
|
19000
|
+
local provider="${LOKI_PROVIDER:-claude}"
|
|
19001
|
+
if [ -f "$target_path/.loki/state/provider" ]; then
|
|
19002
|
+
provider=$(cat "$target_path/.loki/state/provider" 2>/dev/null)
|
|
19003
|
+
fi
|
|
19004
|
+
|
|
19005
|
+
local provider_available=true
|
|
19006
|
+
if ! command -v "$provider" &>/dev/null; then
|
|
19007
|
+
provider_available=false
|
|
19008
|
+
log_warn "Provider '$provider' not found. Generating template-based docs instead."
|
|
19009
|
+
fi
|
|
19010
|
+
|
|
19011
|
+
local updated_files=""
|
|
19012
|
+
for doc_type in $docs_to_update; do
|
|
19013
|
+
local doc_file="${doc_type}.md"
|
|
19014
|
+
echo -e " ${CYAN}Updating${NC} $doc_file ..."
|
|
19015
|
+
|
|
19016
|
+
if [ "$provider_available" = true ]; then
|
|
19017
|
+
local existing_content=""
|
|
19018
|
+
[ -f "$docs_dir/$doc_file" ] && existing_content=$(cat "$docs_dir/$doc_file" 2>/dev/null || true)
|
|
19019
|
+
|
|
19020
|
+
local prompt="Based on the following project context, update the ${doc_type}.md documentation. Here are the files that changed since the last generation:
|
|
19021
|
+
|
|
19022
|
+
$changed_files
|
|
19023
|
+
|
|
19024
|
+
Current ${doc_type}.md content:
|
|
19025
|
+
$existing_content
|
|
19026
|
+
|
|
19027
|
+
Project context:
|
|
19028
|
+
$context
|
|
19029
|
+
|
|
19030
|
+
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."
|
|
19031
|
+
|
|
19032
|
+
local result=""
|
|
19033
|
+
result=$(_docs_invoke_provider "$prompt" 2>/dev/null) || true
|
|
19034
|
+
if [ -n "$result" ]; then
|
|
19035
|
+
echo "$result" > "$docs_dir/$doc_file"
|
|
19036
|
+
fi
|
|
19037
|
+
else
|
|
19038
|
+
_docs_generate_template "$doc_type" "$target_path" "$context" > "$docs_dir/$doc_file"
|
|
19039
|
+
fi
|
|
19040
|
+
|
|
19041
|
+
updated_files="$updated_files $doc_file"
|
|
19042
|
+
echo -e " ${GREEN}[done]${NC} $doc_file"
|
|
19043
|
+
done
|
|
19044
|
+
|
|
19045
|
+
# Update manifest with all existing docs
|
|
19046
|
+
local all_doc_files=""
|
|
19047
|
+
for f in "$docs_dir"/*.md; do
|
|
19048
|
+
[ -f "$f" ] && all_doc_files="$all_doc_files $(basename "$f")"
|
|
19049
|
+
done
|
|
19050
|
+
# shellcheck disable=SC2086
|
|
19051
|
+
_docs_write_manifest "$docs_dir" $all_doc_files
|
|
19052
|
+
|
|
19053
|
+
echo ""
|
|
19054
|
+
log_info "Updated $(echo "$updated_files" | wc -w | tr -d ' ') documentation files"
|
|
19055
|
+
}
|
|
19056
|
+
|
|
19057
|
+
# --- loki docs check ---
|
|
19058
|
+
_docs_check() {
|
|
19059
|
+
local target_path="."
|
|
19060
|
+
|
|
19061
|
+
while [[ $# -gt 0 ]]; do
|
|
19062
|
+
case "$1" in
|
|
19063
|
+
--help|-h)
|
|
19064
|
+
echo "Usage: loki docs check [path]"
|
|
19065
|
+
echo "Validate documentation coverage and staleness. Exit 0=pass, 1=fail."
|
|
19066
|
+
return 0
|
|
19067
|
+
;;
|
|
19068
|
+
-*)
|
|
19069
|
+
log_error "Unknown option: $1"
|
|
19070
|
+
return 1
|
|
19071
|
+
;;
|
|
19072
|
+
*)
|
|
19073
|
+
target_path="$1"
|
|
19074
|
+
shift
|
|
19075
|
+
;;
|
|
19076
|
+
esac
|
|
19077
|
+
done
|
|
19078
|
+
|
|
19079
|
+
if [ ! -d "$target_path" ]; then
|
|
19080
|
+
log_error "Directory not found: $target_path"
|
|
19081
|
+
return 1
|
|
19082
|
+
fi
|
|
19083
|
+
target_path="$(cd "$target_path" && pwd)"
|
|
19084
|
+
|
|
19085
|
+
local docs_dir="$target_path/.loki/docs"
|
|
19086
|
+
local manifest="$docs_dir/docs-manifest.json"
|
|
19087
|
+
local pass=true
|
|
19088
|
+
|
|
19089
|
+
echo -e "${BOLD}Documentation Check${NC}"
|
|
19090
|
+
echo "----------------"
|
|
19091
|
+
|
|
19092
|
+
# Check 1: docs directory exists
|
|
19093
|
+
if [ ! -d "$docs_dir" ]; then
|
|
19094
|
+
echo -e " ${RED}[FAIL]${NC} No .loki/docs/ directory found"
|
|
19095
|
+
echo ""
|
|
19096
|
+
echo "Run 'loki docs generate' to create documentation."
|
|
19097
|
+
return 1
|
|
19098
|
+
fi
|
|
19099
|
+
|
|
19100
|
+
# Check 2: README.md exists and is non-empty
|
|
19101
|
+
if [ -f "$docs_dir/README.md" ] && [ -s "$docs_dir/README.md" ]; then
|
|
19102
|
+
echo -e " ${GREEN}[PASS]${NC} README.md exists and is non-empty"
|
|
19103
|
+
else
|
|
19104
|
+
echo -e " ${RED}[FAIL]${NC} README.md missing or empty"
|
|
19105
|
+
pass=false
|
|
19106
|
+
fi
|
|
19107
|
+
|
|
19108
|
+
# Check 3: manifest exists
|
|
19109
|
+
if [ -f "$manifest" ]; then
|
|
19110
|
+
echo -e " ${GREEN}[PASS]${NC} docs-manifest.json exists"
|
|
19111
|
+
else
|
|
19112
|
+
echo -e " ${RED}[FAIL]${NC} docs-manifest.json missing"
|
|
19113
|
+
pass=false
|
|
19114
|
+
fi
|
|
19115
|
+
|
|
19116
|
+
# Check 4: expected doc files exist
|
|
19117
|
+
local expected_docs="README.md ARCHITECTURE.md API.md SETUP.md COMPONENTS.md TESTING.md DECISIONS.md CLAUDE.md"
|
|
19118
|
+
local doc_count=0
|
|
19119
|
+
local expected_count=0
|
|
19120
|
+
for doc in $expected_docs; do
|
|
19121
|
+
((++expected_count))
|
|
19122
|
+
if [ -f "$docs_dir/$doc" ] && [ -s "$docs_dir/$doc" ]; then
|
|
19123
|
+
((++doc_count))
|
|
19124
|
+
else
|
|
19125
|
+
echo -e " ${YELLOW}[WARN]${NC} Missing or empty: $doc"
|
|
19126
|
+
fi
|
|
19127
|
+
done
|
|
19128
|
+
local coverage_pct=0
|
|
19129
|
+
[ "$expected_count" -gt 0 ] && coverage_pct=$(( doc_count * 100 / expected_count ))
|
|
19130
|
+
echo -e " ${BOLD}Coverage:${NC} $doc_count/$expected_count docs ($coverage_pct%)"
|
|
19131
|
+
|
|
19132
|
+
# Check 5: staleness (commits since last doc generation)
|
|
19133
|
+
if [ -f "$manifest" ] && command -v python3 &>/dev/null; then
|
|
19134
|
+
local last_sha=""
|
|
19135
|
+
last_sha=$(python3 -c "
|
|
19136
|
+
import json
|
|
19137
|
+
try:
|
|
19138
|
+
d = json.load(open('$manifest'))
|
|
19139
|
+
print(d.get('git_sha', ''))
|
|
19140
|
+
except: pass
|
|
19141
|
+
" 2>/dev/null || true)
|
|
19142
|
+
|
|
19143
|
+
if [ -n "$last_sha" ] && [ "$last_sha" != "unknown" ]; then
|
|
19144
|
+
local commits_behind=0
|
|
19145
|
+
commits_behind=$(cd "$target_path" && git rev-list --count "$last_sha"..HEAD 2>/dev/null || echo "0")
|
|
19146
|
+
if [ "$commits_behind" -gt 10 ]; then
|
|
19147
|
+
echo -e " ${RED}[FAIL]${NC} Docs are $commits_behind commits behind HEAD (threshold: 10)"
|
|
19148
|
+
pass=false
|
|
19149
|
+
elif [ "$commits_behind" -gt 0 ]; then
|
|
19150
|
+
echo -e " ${YELLOW}[WARN]${NC} Docs are $commits_behind commits behind HEAD"
|
|
19151
|
+
else
|
|
19152
|
+
echo -e " ${GREEN}[PASS]${NC} Docs are up to date with HEAD"
|
|
19153
|
+
fi
|
|
19154
|
+
fi
|
|
19155
|
+
fi
|
|
19156
|
+
|
|
19157
|
+
# Check 6: component coverage (check if major dirs have doc entries)
|
|
19158
|
+
local tree_output
|
|
19159
|
+
tree_output=$(_docs_scan_project "$target_path")
|
|
19160
|
+
local major_dirs
|
|
19161
|
+
major_dirs=$(echo "$tree_output" | sed 's|/.*||' | sort -u | head -20)
|
|
19162
|
+
local dir_count=0
|
|
19163
|
+
local documented_dirs=0
|
|
19164
|
+
if [ -f "$docs_dir/COMPONENTS.md" ]; then
|
|
19165
|
+
local components_content
|
|
19166
|
+
components_content=$(cat "$docs_dir/COMPONENTS.md" 2>/dev/null || true)
|
|
19167
|
+
while IFS= read -r dir; do
|
|
19168
|
+
[ -z "$dir" ] && continue
|
|
19169
|
+
[ ! -d "$target_path/$dir" ] && continue
|
|
19170
|
+
((++dir_count))
|
|
19171
|
+
if echo "$components_content" | grep -qi "$dir" 2>/dev/null; then
|
|
19172
|
+
((++documented_dirs))
|
|
19173
|
+
fi
|
|
19174
|
+
done <<< "$major_dirs"
|
|
19175
|
+
if [ "$dir_count" -gt 0 ]; then
|
|
19176
|
+
local dir_pct=$(( documented_dirs * 100 / dir_count ))
|
|
19177
|
+
echo -e " ${BOLD}Component coverage:${NC} $documented_dirs/$dir_count directories ($dir_pct%)"
|
|
19178
|
+
fi
|
|
19179
|
+
fi
|
|
19180
|
+
|
|
19181
|
+
echo ""
|
|
19182
|
+
if [ "$pass" = true ] && [ "$coverage_pct" -ge 75 ]; then
|
|
19183
|
+
echo -e "${GREEN}Result: PASS${NC}"
|
|
19184
|
+
return 0
|
|
19185
|
+
else
|
|
19186
|
+
echo -e "${RED}Result: FAIL${NC}"
|
|
19187
|
+
[ "$coverage_pct" -lt 75 ] && echo " Doc coverage below 75%. Run 'loki docs generate' to create missing docs."
|
|
19188
|
+
return 1
|
|
19189
|
+
fi
|
|
19190
|
+
}
|
|
19191
|
+
|
|
19192
|
+
# --- loki docs status ---
|
|
19193
|
+
_docs_status() {
|
|
19194
|
+
local target_path="."
|
|
19195
|
+
|
|
19196
|
+
while [[ $# -gt 0 ]]; do
|
|
19197
|
+
case "$1" in
|
|
19198
|
+
--help|-h)
|
|
19199
|
+
echo "Usage: loki docs status [path]"
|
|
19200
|
+
echo "Show documentation metrics."
|
|
19201
|
+
return 0
|
|
19202
|
+
;;
|
|
19203
|
+
-*)
|
|
19204
|
+
log_error "Unknown option: $1"
|
|
19205
|
+
return 1
|
|
19206
|
+
;;
|
|
19207
|
+
*)
|
|
19208
|
+
target_path="$1"
|
|
19209
|
+
shift
|
|
19210
|
+
;;
|
|
19211
|
+
esac
|
|
19212
|
+
done
|
|
19213
|
+
|
|
19214
|
+
if [ ! -d "$target_path" ]; then
|
|
19215
|
+
log_error "Directory not found: $target_path"
|
|
19216
|
+
return 1
|
|
19217
|
+
fi
|
|
19218
|
+
target_path="$(cd "$target_path" && pwd)"
|
|
19219
|
+
|
|
19220
|
+
local docs_dir="$target_path/.loki/docs"
|
|
19221
|
+
local manifest="$docs_dir/docs-manifest.json"
|
|
19222
|
+
|
|
19223
|
+
echo -e "${BOLD}Documentation Status${NC}"
|
|
19224
|
+
echo "===================="
|
|
19225
|
+
echo ""
|
|
19226
|
+
|
|
19227
|
+
if [ ! -d "$docs_dir" ]; then
|
|
19228
|
+
echo "No documentation generated yet."
|
|
19229
|
+
echo "Run 'loki docs generate' to create documentation."
|
|
19230
|
+
return 0
|
|
19231
|
+
fi
|
|
19232
|
+
|
|
19233
|
+
# Doc count and sizes
|
|
19234
|
+
local doc_count=0
|
|
19235
|
+
local total_size=0
|
|
19236
|
+
echo -e "${BOLD}Documents:${NC}"
|
|
19237
|
+
for f in "$docs_dir"/*.md; do
|
|
19238
|
+
[ ! -f "$f" ] && continue
|
|
19239
|
+
((++doc_count))
|
|
19240
|
+
local fsize
|
|
19241
|
+
fsize=$(wc -c < "$f" 2>/dev/null | tr -d ' ')
|
|
19242
|
+
total_size=$((total_size + fsize))
|
|
19243
|
+
local flines
|
|
19244
|
+
flines=$(wc -l < "$f" 2>/dev/null | tr -d ' ')
|
|
19245
|
+
local fname
|
|
19246
|
+
fname=$(basename "$f")
|
|
19247
|
+
printf " %-22s %5d lines %6s\n" "$fname" "$flines" "$(_docs_human_size "$fsize")"
|
|
19248
|
+
done
|
|
19249
|
+
echo ""
|
|
19250
|
+
echo " Total: $doc_count documents, $(_docs_human_size "$total_size")"
|
|
19251
|
+
echo ""
|
|
19252
|
+
|
|
19253
|
+
# Coverage
|
|
19254
|
+
local tree_output
|
|
19255
|
+
tree_output=$(_docs_scan_project "$target_path")
|
|
19256
|
+
local total_files
|
|
19257
|
+
total_files=$(echo "$tree_output" | { grep -c . || true; })
|
|
19258
|
+
local src_files
|
|
19259
|
+
src_files=$(echo "$tree_output" | { grep -cE '\.(js|ts|tsx|jsx|py|rs|go|rb|java|kt|c|cpp|h|hpp|sh)$' || true; })
|
|
19260
|
+
|
|
19261
|
+
local expected_docs="README.md ARCHITECTURE.md API.md SETUP.md COMPONENTS.md TESTING.md DECISIONS.md CLAUDE.md"
|
|
19262
|
+
local existing=0
|
|
19263
|
+
local expected_total=0
|
|
19264
|
+
for doc in $expected_docs; do
|
|
19265
|
+
((++expected_total))
|
|
19266
|
+
[ -f "$docs_dir/$doc" ] && [ -s "$docs_dir/$doc" ] && ((++existing))
|
|
19267
|
+
done
|
|
19268
|
+
local pct=0
|
|
19269
|
+
[ "$expected_total" -gt 0 ] && pct=$(( existing * 100 / expected_total ))
|
|
19270
|
+
echo -e "${BOLD}Coverage:${NC} $existing/$expected_total doc types ($pct%)"
|
|
19271
|
+
echo " Project: $total_files files ($src_files source)"
|
|
19272
|
+
echo ""
|
|
19273
|
+
|
|
19274
|
+
# Staleness
|
|
19275
|
+
if [ -f "$manifest" ] && command -v python3 &>/dev/null; then
|
|
19276
|
+
local last_sha="" generated_at=""
|
|
19277
|
+
eval "$(python3 -c "
|
|
19278
|
+
import json
|
|
19279
|
+
try:
|
|
19280
|
+
d = json.load(open('$manifest'))
|
|
19281
|
+
print('last_sha=\"' + d.get('git_sha', 'unknown') + '\"')
|
|
19282
|
+
print('generated_at=\"' + d.get('generated_at', 'unknown') + '\"')
|
|
19283
|
+
except: pass
|
|
19284
|
+
" 2>/dev/null || true)"
|
|
19285
|
+
|
|
19286
|
+
echo -e "${BOLD}Last generated:${NC} $generated_at"
|
|
19287
|
+
if [ -n "$last_sha" ] && [ "$last_sha" != "unknown" ]; then
|
|
19288
|
+
echo " Git SHA: ${last_sha:0:12}"
|
|
19289
|
+
local commits_behind=0
|
|
19290
|
+
commits_behind=$(cd "$target_path" && git rev-list --count "$last_sha"..HEAD 2>/dev/null || echo "0")
|
|
19291
|
+
if [ "$commits_behind" -eq 0 ]; then
|
|
19292
|
+
echo -e " Staleness: ${GREEN}up to date${NC}"
|
|
19293
|
+
elif [ "$commits_behind" -le 10 ]; then
|
|
19294
|
+
echo -e " Staleness: ${YELLOW}$commits_behind commits behind${NC}"
|
|
19295
|
+
else
|
|
19296
|
+
echo -e " Staleness: ${RED}$commits_behind commits behind${NC} (consider running 'loki docs update')"
|
|
19297
|
+
fi
|
|
19298
|
+
fi
|
|
19299
|
+
fi
|
|
19300
|
+
}
|
|
19301
|
+
|
|
19302
|
+
# --- Docs helper: human-readable file size ---
|
|
19303
|
+
_docs_human_size() {
|
|
19304
|
+
local bytes="$1"
|
|
19305
|
+
if [ "$bytes" -ge 1048576 ]; then
|
|
19306
|
+
echo "$((bytes / 1048576))MB"
|
|
19307
|
+
elif [ "$bytes" -ge 1024 ]; then
|
|
19308
|
+
echo "$((bytes / 1024))KB"
|
|
19309
|
+
else
|
|
19310
|
+
echo "${bytes}B"
|
|
19311
|
+
fi
|
|
19312
|
+
}
|
|
19313
|
+
|
|
18325
19314
|
# CI/CD quality gate integration (v6.22.0)
|
|
18326
19315
|
cmd_ci() {
|
|
18327
19316
|
local ci_pr=false
|