loki-mode 5.53.0 → 5.55.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 +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +939 -4
- package/autonomy/migration-agents.sh +500 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/activity_logger.py +231 -0
- package/dashboard/failure_extractor.py +228 -0
- package/dashboard/migration_engine.py +886 -0
- package/dashboard/prompt_optimizer.py +281 -0
- package/dashboard/rigour_integration.py +331 -0
- package/dashboard/server.py +428 -0
- package/dashboard/static/index.html +1667 -350
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
package/autonomy/loki
CHANGED
|
@@ -409,7 +409,8 @@ show_help() {
|
|
|
409
409
|
echo " council [cmd] Completion council (status|verdicts|convergence|force-review|report)"
|
|
410
410
|
echo " checkpoint|cp Save/restore session checkpoints"
|
|
411
411
|
echo " projects Multi-project registry management"
|
|
412
|
-
echo " audit
|
|
412
|
+
echo " audit [cmd] Agent audit log and quality scanning (log|scan)"
|
|
413
|
+
echo " optimize Optimize prompts based on session history"
|
|
413
414
|
echo " enterprise Enterprise feature management (tokens, OIDC)"
|
|
414
415
|
echo " metrics Prometheus/OpenMetrics metrics from dashboard"
|
|
415
416
|
echo " dogfood Show self-development statistics"
|
|
@@ -431,6 +432,7 @@ show_help() {
|
|
|
431
432
|
echo " --no-dashboard Disable web dashboard"
|
|
432
433
|
echo " --sandbox Run in Docker sandbox for isolation"
|
|
433
434
|
echo " --skip-memory Skip loading memory context at startup"
|
|
435
|
+
echo " --compliance PRESET Enable compliance mode (default|healthcare|fintech|government)"
|
|
434
436
|
echo " --budget USD Set cost budget limit (display in dashboard/status)"
|
|
435
437
|
echo ""
|
|
436
438
|
echo "Options for 'issue':"
|
|
@@ -484,6 +486,7 @@ cmd_start() {
|
|
|
484
486
|
echo " --no-dashboard Disable web dashboard"
|
|
485
487
|
echo " --sandbox Run in Docker sandbox"
|
|
486
488
|
echo " --skip-memory Skip loading memory context at startup"
|
|
489
|
+
echo " --compliance PRESET Enable compliance mode (default|healthcare|fintech|government)"
|
|
487
490
|
echo " --budget USD Cost budget limit (auto-pause when exceeded)"
|
|
488
491
|
echo " --yes, -y Skip confirmation prompts (auto-confirm)"
|
|
489
492
|
echo ""
|
|
@@ -550,6 +553,22 @@ cmd_start() {
|
|
|
550
553
|
export LOKI_SKIP_MEMORY=true
|
|
551
554
|
shift
|
|
552
555
|
;;
|
|
556
|
+
--compliance)
|
|
557
|
+
if [[ -n "${2:-}" ]]; then
|
|
558
|
+
export LOKI_COMPLIANCE_PRESET="$2"
|
|
559
|
+
echo -e "${CYAN}Compliance mode: $2${NC}"
|
|
560
|
+
shift 2
|
|
561
|
+
else
|
|
562
|
+
echo -e "${RED}--compliance requires a preset name (default, healthcare, fintech, government)${NC}"
|
|
563
|
+
exit 1
|
|
564
|
+
fi
|
|
565
|
+
;;
|
|
566
|
+
--compliance=*)
|
|
567
|
+
local compliance_val="${1#*=}"
|
|
568
|
+
export LOKI_COMPLIANCE_PRESET="$compliance_val"
|
|
569
|
+
echo -e "${CYAN}Compliance mode: $compliance_val${NC}"
|
|
570
|
+
shift
|
|
571
|
+
;;
|
|
553
572
|
--yes|-y)
|
|
554
573
|
export LOKI_AUTO_CONFIRM=true
|
|
555
574
|
shift
|
|
@@ -4688,6 +4707,794 @@ except Exception:
|
|
|
4688
4707
|
esac
|
|
4689
4708
|
}
|
|
4690
4709
|
|
|
4710
|
+
# Prompt optimization via dashboard API
|
|
4711
|
+
cmd_optimize() {
|
|
4712
|
+
local sessions=10
|
|
4713
|
+
local dry_run=""
|
|
4714
|
+
|
|
4715
|
+
while [[ $# -gt 0 ]]; do
|
|
4716
|
+
case "$1" in
|
|
4717
|
+
--sessions) if [[ -z "${2:-}" ]]; then echo -e "${RED}Error: --sessions requires a value${NC}"; exit 1; fi; sessions="$2"; shift 2 ;;
|
|
4718
|
+
--sessions=*) sessions="${1#*=}"; shift ;;
|
|
4719
|
+
--dry-run) dry_run="true"; shift ;;
|
|
4720
|
+
--help|-h)
|
|
4721
|
+
echo -e "${BOLD}loki optimize${NC} - Optimize prompts based on session history"
|
|
4722
|
+
echo ""
|
|
4723
|
+
echo "Usage: loki optimize [options]"
|
|
4724
|
+
echo ""
|
|
4725
|
+
echo "Analyzes recent session failures and proposes prompt improvements."
|
|
4726
|
+
echo "Requires the dashboard API to be running (loki serve)."
|
|
4727
|
+
echo ""
|
|
4728
|
+
echo "Options:"
|
|
4729
|
+
echo " --sessions N Number of recent sessions to analyze (default: 10)"
|
|
4730
|
+
echo " --dry-run Show proposed changes without applying"
|
|
4731
|
+
echo ""
|
|
4732
|
+
echo "Examples:"
|
|
4733
|
+
echo " loki optimize # Optimize using last 10 sessions"
|
|
4734
|
+
echo " loki optimize --sessions 20 # Analyze last 20 sessions"
|
|
4735
|
+
echo " loki optimize --dry-run # Preview changes only"
|
|
4736
|
+
exit 0
|
|
4737
|
+
;;
|
|
4738
|
+
*)
|
|
4739
|
+
echo -e "${RED}Unknown option: $1${NC}"
|
|
4740
|
+
echo "Run 'loki optimize --help' for usage."
|
|
4741
|
+
exit 1
|
|
4742
|
+
;;
|
|
4743
|
+
esac
|
|
4744
|
+
done
|
|
4745
|
+
|
|
4746
|
+
local port="${LOKI_DASHBOARD_PORT:-57374}"
|
|
4747
|
+
local host="127.0.0.1"
|
|
4748
|
+
local dry_run_param="false"
|
|
4749
|
+
if [ -n "$dry_run" ]; then
|
|
4750
|
+
dry_run_param="true"
|
|
4751
|
+
fi
|
|
4752
|
+
local url="http://${host}:${port}/api/prompt-optimize?sessions=${sessions}&dry_run=${dry_run_param}"
|
|
4753
|
+
|
|
4754
|
+
echo -e "${BOLD}Analyzing sessions for prompt optimization...${NC}"
|
|
4755
|
+
echo -e "${DIM}Sessions: $sessions | Dry run: ${dry_run_param}${NC}"
|
|
4756
|
+
echo ""
|
|
4757
|
+
|
|
4758
|
+
local response http_code
|
|
4759
|
+
response=$(curl -s -w "\n%{http_code}" -X POST "$url" 2>/dev/null) || true
|
|
4760
|
+
http_code=$(echo "$response" | tail -1)
|
|
4761
|
+
response=$(echo "$response" | sed '$d')
|
|
4762
|
+
if [ -z "$http_code" ] || [ "$http_code" = "000" ]; then
|
|
4763
|
+
echo -e "${RED}Error: Could not connect to dashboard API at http://${host}:${port}${NC}"
|
|
4764
|
+
echo "Make sure the dashboard is running: loki serve"
|
|
4765
|
+
exit 1
|
|
4766
|
+
fi
|
|
4767
|
+
if [ "$http_code" -ge 400 ] 2>/dev/null; then
|
|
4768
|
+
echo -e "${RED}Error: Dashboard API returned HTTP $http_code${NC}"
|
|
4769
|
+
[ -n "$response" ] && echo "$response"
|
|
4770
|
+
exit 1
|
|
4771
|
+
fi
|
|
4772
|
+
|
|
4773
|
+
if ! command -v python3 &>/dev/null; then
|
|
4774
|
+
echo "$response" | jq . 2>/dev/null || echo "$response"
|
|
4775
|
+
else
|
|
4776
|
+
echo "$response" | python3 -c "
|
|
4777
|
+
import json, sys
|
|
4778
|
+
data = json.loads(sys.stdin.read())
|
|
4779
|
+
|
|
4780
|
+
failures = data.get('failures_analyzed', 0)
|
|
4781
|
+
changes = data.get('changes', [])
|
|
4782
|
+
version = data.get('version', None)
|
|
4783
|
+
|
|
4784
|
+
print(f'Failures analyzed: {failures}')
|
|
4785
|
+
print(f'Changes proposed: {len(changes)}')
|
|
4786
|
+
print('---')
|
|
4787
|
+
|
|
4788
|
+
for i, change in enumerate(changes, 1):
|
|
4789
|
+
prompt = change.get('prompt', '?')
|
|
4790
|
+
rationale = change.get('rationale', 'No rationale provided')
|
|
4791
|
+
old = change.get('old_value', '')
|
|
4792
|
+
new = change.get('new_value', '')
|
|
4793
|
+
print(f'')
|
|
4794
|
+
print(f' {i}. {prompt}')
|
|
4795
|
+
print(f' Rationale: {rationale}')
|
|
4796
|
+
if old:
|
|
4797
|
+
print(f' Old: {old[:80]}...' if len(old) > 80 else f' Old: {old}')
|
|
4798
|
+
if new:
|
|
4799
|
+
print(f' New: {new[:80]}...' if len(new) > 80 else f' New: {new}')
|
|
4800
|
+
|
|
4801
|
+
if version is not None:
|
|
4802
|
+
print(f'')
|
|
4803
|
+
print(f'Prompts optimized to version {version}')
|
|
4804
|
+
elif not changes:
|
|
4805
|
+
print(f'')
|
|
4806
|
+
print('No optimization changes proposed.')
|
|
4807
|
+
print()
|
|
4808
|
+
" 2>/dev/null || echo "$response"
|
|
4809
|
+
fi
|
|
4810
|
+
}
|
|
4811
|
+
|
|
4812
|
+
# ============================================================================
|
|
4813
|
+
# Migration Command
|
|
4814
|
+
# ============================================================================
|
|
4815
|
+
|
|
4816
|
+
cmd_migrate_help() {
|
|
4817
|
+
echo -e "${BOLD}loki migrate${NC} - Codebase migration with autonomous agents"
|
|
4818
|
+
echo ""
|
|
4819
|
+
echo "Usage: loki migrate <path-to-codebase> [options]"
|
|
4820
|
+
echo ""
|
|
4821
|
+
echo "Migrates a codebase to a new language, framework, or architecture using"
|
|
4822
|
+
echo "multi-agent orchestration with guardrails and incremental verification."
|
|
4823
|
+
echo ""
|
|
4824
|
+
echo "Options:"
|
|
4825
|
+
echo " --target <lang|framework|arch> Migration target (e.g. typescript, react, microservices)"
|
|
4826
|
+
echo " --plan-only Generate migration plan without executing"
|
|
4827
|
+
echo " --show-plan Display current migration plan"
|
|
4828
|
+
echo " --phase <phase> Run specific phase:"
|
|
4829
|
+
echo " understand - Analyze codebase structure"
|
|
4830
|
+
echo " guardrail - Set up tests and safety nets"
|
|
4831
|
+
echo " migrate - Execute migration transforms"
|
|
4832
|
+
echo " verify - Run verification suite"
|
|
4833
|
+
echo " --parallel <N> Max parallel agents (default: 4, max: 10)"
|
|
4834
|
+
echo " --compliance <preset> Compliance preset: healthcare, fintech, government"
|
|
4835
|
+
echo " --dry-run Show what would change without executing"
|
|
4836
|
+
echo " --resume Resume from last checkpoint"
|
|
4837
|
+
echo " --multi-repo <glob> Multiple repository paths (glob pattern)"
|
|
4838
|
+
echo " --export-report Export migration report to file"
|
|
4839
|
+
echo " --list List all migrations"
|
|
4840
|
+
echo " --status [migration-id] Show migration status"
|
|
4841
|
+
echo ""
|
|
4842
|
+
echo "Examples:"
|
|
4843
|
+
echo " loki migrate ./my-app --target typescript"
|
|
4844
|
+
echo " loki migrate ./my-app --target react --plan-only"
|
|
4845
|
+
echo " loki migrate ./my-app --target microservices --compliance fintech"
|
|
4846
|
+
echo " loki migrate --list"
|
|
4847
|
+
echo " loki migrate --status mig-20260223-001"
|
|
4848
|
+
echo " loki migrate ./my-app --target typescript --resume"
|
|
4849
|
+
echo " loki migrate ./my-app --target typescript --phase verify"
|
|
4850
|
+
echo " loki migrate --multi-repo './services/*' --target typescript"
|
|
4851
|
+
}
|
|
4852
|
+
|
|
4853
|
+
cmd_migrate_list() {
|
|
4854
|
+
python3 -c "
|
|
4855
|
+
import sys
|
|
4856
|
+
sys.path.insert(0, '.')
|
|
4857
|
+
from dashboard.migration_engine import list_migrations
|
|
4858
|
+
migrations = list_migrations()
|
|
4859
|
+
if not migrations:
|
|
4860
|
+
print('No migrations found.')
|
|
4861
|
+
print('Start a migration with: loki migrate <path> --target <target>')
|
|
4862
|
+
else:
|
|
4863
|
+
print(f'\033[1mMigrations\033[0m ({len(migrations)} total)')
|
|
4864
|
+
print('---')
|
|
4865
|
+
for m in migrations:
|
|
4866
|
+
print(f\" {m['id']}\")
|
|
4867
|
+
print(f\" Target: {m.get('target', '?')}\")
|
|
4868
|
+
print(f\" Source: {m.get('source_path', '?')}\")
|
|
4869
|
+
print(f\" Status: {m.get('status', '?')}\")
|
|
4870
|
+
print(f\" Created: {m.get('created_at', '?')[:10] if m.get('created_at') else '?'}\")
|
|
4871
|
+
print()
|
|
4872
|
+
" || {
|
|
4873
|
+
echo -e "${RED}Error listing migrations${NC}"
|
|
4874
|
+
return 1
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
|
|
4878
|
+
cmd_migrate_status() {
|
|
4879
|
+
local migration_id="${1:-}"
|
|
4880
|
+
local migrations_dir="${HOME}/.loki/migrations"
|
|
4881
|
+
|
|
4882
|
+
if [ -z "$migration_id" ]; then
|
|
4883
|
+
# Show status of most recent migration
|
|
4884
|
+
local latest
|
|
4885
|
+
latest=$(find "$migrations_dir" -name "manifest.json" -maxdepth 2 2>/dev/null | sort | tail -1)
|
|
4886
|
+
if [ -z "$latest" ]; then
|
|
4887
|
+
echo -e "${YELLOW}No migrations found.${NC}"
|
|
4888
|
+
return 0
|
|
4889
|
+
fi
|
|
4890
|
+
migration_id=$(python3 -c "import json, sys; print(json.load(open(sys.argv[1]))['id'])" "$latest" 2>/dev/null || echo "")
|
|
4891
|
+
fi
|
|
4892
|
+
|
|
4893
|
+
local manifest="${migrations_dir}/${migration_id}/manifest.json"
|
|
4894
|
+
if [ ! -f "$manifest" ]; then
|
|
4895
|
+
echo -e "${RED}Error: Migration not found: ${migration_id}${NC}"
|
|
4896
|
+
echo "Run 'loki migrate --list' to see available migrations."
|
|
4897
|
+
return 1
|
|
4898
|
+
fi
|
|
4899
|
+
|
|
4900
|
+
python3 -c "
|
|
4901
|
+
import json, sys
|
|
4902
|
+
sys.path.insert(0, '.')
|
|
4903
|
+
from dashboard.migration_engine import MigrationPipeline
|
|
4904
|
+
try:
|
|
4905
|
+
pipeline = MigrationPipeline.load(sys.argv[1])
|
|
4906
|
+
progress = pipeline.get_progress()
|
|
4907
|
+
print(json.dumps(progress, indent=2))
|
|
4908
|
+
except Exception as e:
|
|
4909
|
+
print(f'\033[0;31mError reading migration: {e}\033[0m', file=sys.stderr)
|
|
4910
|
+
sys.exit(1)
|
|
4911
|
+
" "$migration_id" || {
|
|
4912
|
+
echo -e "${RED}Error reading migration manifest${NC}"
|
|
4913
|
+
return 1
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4916
|
+
|
|
4917
|
+
cmd_migrate_show_plan() {
|
|
4918
|
+
local codebase_path="${1:-$(pwd)}"
|
|
4919
|
+
local migrations_dir="${HOME}/.loki/migrations"
|
|
4920
|
+
|
|
4921
|
+
# Find plan for this codebase
|
|
4922
|
+
local plan_file=""
|
|
4923
|
+
if [ -f "${codebase_path}/.loki/migration-plan.json" ]; then
|
|
4924
|
+
plan_file="${codebase_path}/.loki/migration-plan.json"
|
|
4925
|
+
else
|
|
4926
|
+
# Search in global migrations dir
|
|
4927
|
+
local latest
|
|
4928
|
+
latest=$(find "$migrations_dir" -name "migration-plan.json" -maxdepth 2 2>/dev/null | sort | tail -1)
|
|
4929
|
+
if [ -n "$latest" ]; then
|
|
4930
|
+
plan_file="$latest"
|
|
4931
|
+
fi
|
|
4932
|
+
fi
|
|
4933
|
+
|
|
4934
|
+
if [ -z "$plan_file" ] || [ ! -f "$plan_file" ]; then
|
|
4935
|
+
echo -e "${YELLOW}No migration plan found.${NC}"
|
|
4936
|
+
echo "Generate one with: loki migrate <path> --target <target> --plan-only"
|
|
4937
|
+
return 0
|
|
4938
|
+
fi
|
|
4939
|
+
|
|
4940
|
+
echo -e "${BOLD}Migration Plan${NC}"
|
|
4941
|
+
echo -e "${DIM}Source: ${plan_file}${NC}"
|
|
4942
|
+
echo "---"
|
|
4943
|
+
|
|
4944
|
+
# Try engine-based summary first, fall back to raw JSON display
|
|
4945
|
+
local migration_id_for_plan=""
|
|
4946
|
+
migration_id_for_plan=$(python3 -c "
|
|
4947
|
+
import json, sys
|
|
4948
|
+
with open(sys.argv[1]) as f:
|
|
4949
|
+
data = json.load(f)
|
|
4950
|
+
# Try to extract migration ID from path or data
|
|
4951
|
+
print(data.get('id', ''))
|
|
4952
|
+
" "$plan_file" 2>/dev/null || echo "")
|
|
4953
|
+
|
|
4954
|
+
if [ -n "$migration_id_for_plan" ]; then
|
|
4955
|
+
python3 -c "
|
|
4956
|
+
import sys
|
|
4957
|
+
sys.path.insert(0, '.')
|
|
4958
|
+
from dashboard.migration_engine import MigrationPipeline
|
|
4959
|
+
pipeline = MigrationPipeline.load(sys.argv[1])
|
|
4960
|
+
print(pipeline.generate_plan_summary())
|
|
4961
|
+
" "$migration_id_for_plan" 2>/dev/null || {
|
|
4962
|
+
# Fall back to basic display if engine fails
|
|
4963
|
+
python3 -c "
|
|
4964
|
+
import json, sys
|
|
4965
|
+
with open(sys.argv[1]) as f:
|
|
4966
|
+
plan = json.load(f)
|
|
4967
|
+
print(json.dumps(plan, indent=2))
|
|
4968
|
+
" "$plan_file" || echo -e "${RED}Error reading migration plan${NC}"
|
|
4969
|
+
}
|
|
4970
|
+
else
|
|
4971
|
+
python3 -c "
|
|
4972
|
+
import json, sys
|
|
4973
|
+
with open(sys.argv[1]) as f:
|
|
4974
|
+
plan = json.load(f)
|
|
4975
|
+
print(json.dumps(plan, indent=2))
|
|
4976
|
+
" "$plan_file" || {
|
|
4977
|
+
echo -e "${RED}Error reading migration plan${NC}"
|
|
4978
|
+
return 1
|
|
4979
|
+
}
|
|
4980
|
+
fi
|
|
4981
|
+
}
|
|
4982
|
+
|
|
4983
|
+
cmd_migrate_start() {
|
|
4984
|
+
local codebase_path="$1"
|
|
4985
|
+
local target="$2"
|
|
4986
|
+
local plan_only="$3"
|
|
4987
|
+
local phase="$4"
|
|
4988
|
+
local parallel="$5"
|
|
4989
|
+
local compliance="$6"
|
|
4990
|
+
local dry_run="$7"
|
|
4991
|
+
local resume="$8"
|
|
4992
|
+
local multi_repo="$9"
|
|
4993
|
+
local export_report="${10}"
|
|
4994
|
+
|
|
4995
|
+
local migrations_dir="${HOME}/.loki/migrations"
|
|
4996
|
+
local migration_id=""
|
|
4997
|
+
|
|
4998
|
+
# Check python3 availability
|
|
4999
|
+
if ! command -v python3 &>/dev/null; then
|
|
5000
|
+
echo -e "${RED}Error: python3 is required for migration${NC}"
|
|
5001
|
+
return 1
|
|
5002
|
+
fi
|
|
5003
|
+
|
|
5004
|
+
# Check for path traversal BEFORE canonicalization
|
|
5005
|
+
case "$codebase_path" in
|
|
5006
|
+
*..*)
|
|
5007
|
+
echo -e "${RED}Error: Path traversal not allowed in codebase path${NC}"
|
|
5008
|
+
return 1
|
|
5009
|
+
;;
|
|
5010
|
+
esac
|
|
5011
|
+
|
|
5012
|
+
# Validate codebase path
|
|
5013
|
+
if [ ! -d "$codebase_path" ]; then
|
|
5014
|
+
echo -e "${RED}Error: Codebase path does not exist: ${codebase_path}${NC}"
|
|
5015
|
+
return 1
|
|
5016
|
+
fi
|
|
5017
|
+
|
|
5018
|
+
# Then canonicalize
|
|
5019
|
+
codebase_path=$(cd "$codebase_path" && pwd) || {
|
|
5020
|
+
echo -e "${RED}Error: Cannot access $codebase_path${NC}"
|
|
5021
|
+
return 1
|
|
5022
|
+
}
|
|
5023
|
+
|
|
5024
|
+
# Validate target
|
|
5025
|
+
if [ -z "$target" ]; then
|
|
5026
|
+
echo -e "${RED}Error: --target is required${NC}"
|
|
5027
|
+
echo "Specify the migration target (e.g. --target typescript, --target react, --target microservices)"
|
|
5028
|
+
echo ""
|
|
5029
|
+
echo "Run 'loki migrate --help' for usage."
|
|
5030
|
+
return 1
|
|
5031
|
+
fi
|
|
5032
|
+
|
|
5033
|
+
# Validate compliance preset
|
|
5034
|
+
if [ -n "$compliance" ]; then
|
|
5035
|
+
case "$compliance" in
|
|
5036
|
+
healthcare|fintech|government) ;;
|
|
5037
|
+
*)
|
|
5038
|
+
echo -e "${RED}Error: Invalid compliance preset: ${compliance}${NC}"
|
|
5039
|
+
echo "Valid presets: healthcare, fintech, government"
|
|
5040
|
+
return 1
|
|
5041
|
+
;;
|
|
5042
|
+
esac
|
|
5043
|
+
fi
|
|
5044
|
+
|
|
5045
|
+
# Validate parallel count
|
|
5046
|
+
if [ -n "$parallel" ]; then
|
|
5047
|
+
if ! [[ "$parallel" =~ ^[0-9]+$ ]]; then
|
|
5048
|
+
echo -e "${RED}Error: --parallel must be a number${NC}"
|
|
5049
|
+
return 1
|
|
5050
|
+
fi
|
|
5051
|
+
if [ "$parallel" -lt 1 ] || [ "$parallel" -gt 10 ]; then
|
|
5052
|
+
echo -e "${RED}Error: --parallel must be between 1 and 10${NC}"
|
|
5053
|
+
return 1
|
|
5054
|
+
fi
|
|
5055
|
+
else
|
|
5056
|
+
parallel=4
|
|
5057
|
+
fi
|
|
5058
|
+
|
|
5059
|
+
# Handle resume
|
|
5060
|
+
if [ "$resume" = "true" ]; then
|
|
5061
|
+
local latest_manifest
|
|
5062
|
+
latest_manifest=$(find "$migrations_dir" -name "manifest.json" -maxdepth 2 2>/dev/null | \
|
|
5063
|
+
xargs python3 -c "
|
|
5064
|
+
import json, sys
|
|
5065
|
+
codebase = sys.argv[1]
|
|
5066
|
+
manifests = []
|
|
5067
|
+
for path in sys.argv[2:]:
|
|
5068
|
+
try:
|
|
5069
|
+
with open(path) as f:
|
|
5070
|
+
m = json.load(f)
|
|
5071
|
+
if m.get('source_path') == codebase and m.get('status') in ('paused', 'running', 'failed'):
|
|
5072
|
+
manifests.append(path)
|
|
5073
|
+
except: pass
|
|
5074
|
+
if manifests:
|
|
5075
|
+
print(manifests[-1])
|
|
5076
|
+
" "$codebase_path" 2>/dev/null || echo "")
|
|
5077
|
+
|
|
5078
|
+
if [ -z "$latest_manifest" ]; then
|
|
5079
|
+
echo -e "${RED}Error: No resumable migration found for ${codebase_path}${NC}"
|
|
5080
|
+
echo "Start a new migration with: loki migrate ${codebase_path} --target ${target}"
|
|
5081
|
+
return 1
|
|
5082
|
+
fi
|
|
5083
|
+
|
|
5084
|
+
migration_id=$(python3 -c "import json, sys; print(json.load(open(sys.argv[1]))['id'])" "$latest_manifest" 2>/dev/null)
|
|
5085
|
+
echo -e "${GREEN}Resuming migration: ${migration_id}${NC}"
|
|
5086
|
+
echo -e "${DIM}Loading checkpoint...${NC}"
|
|
5087
|
+
fi
|
|
5088
|
+
|
|
5089
|
+
# Handle multi-repo
|
|
5090
|
+
if [ -n "$multi_repo" ]; then
|
|
5091
|
+
local repo_count=0
|
|
5092
|
+
local repos=()
|
|
5093
|
+
local _repo
|
|
5094
|
+
# Use an array to safely handle glob expansion with spaces in paths
|
|
5095
|
+
local _expanded_repos=()
|
|
5096
|
+
# Save and restore globbing options
|
|
5097
|
+
local _old_nullglob=$(shopt -p nullglob 2>/dev/null || true)
|
|
5098
|
+
shopt -s nullglob
|
|
5099
|
+
local _expanded_repos=($multi_repo)
|
|
5100
|
+
eval "$_old_nullglob" 2>/dev/null || true
|
|
5101
|
+
for _repo in "${_expanded_repos[@]}"; do
|
|
5102
|
+
if [ -d "$_repo" ]; then
|
|
5103
|
+
repos+=("$_repo")
|
|
5104
|
+
repo_count=$((repo_count + 1))
|
|
5105
|
+
fi
|
|
5106
|
+
done
|
|
5107
|
+
if [ "$repo_count" -eq 0 ]; then
|
|
5108
|
+
echo -e "${RED}Error: No repositories found matching: ${multi_repo}${NC}"
|
|
5109
|
+
return 1
|
|
5110
|
+
fi
|
|
5111
|
+
echo -e "${BOLD}Multi-repo migration${NC} ($repo_count repositories)"
|
|
5112
|
+
echo "---"
|
|
5113
|
+
for repo in "${repos[@]}"; do
|
|
5114
|
+
echo " - $repo"
|
|
5115
|
+
done
|
|
5116
|
+
echo ""
|
|
5117
|
+
fi
|
|
5118
|
+
|
|
5119
|
+
# Create migration via engine
|
|
5120
|
+
local create_result
|
|
5121
|
+
create_result=$(python3 -c "
|
|
5122
|
+
import json, sys
|
|
5123
|
+
sys.path.insert(0, '.')
|
|
5124
|
+
from dashboard.migration_engine import MigrationPipeline
|
|
5125
|
+
pipeline = MigrationPipeline(sys.argv[1], sys.argv[2])
|
|
5126
|
+
manifest = pipeline.create_manifest()
|
|
5127
|
+
print(json.dumps({'id': manifest.id, 'dir': str(pipeline.migration_dir)}))
|
|
5128
|
+
" "$codebase_path" "$target" 2>&1) || {
|
|
5129
|
+
echo -e "${RED}Error: Failed to create migration${NC}"
|
|
5130
|
+
echo "$create_result"
|
|
5131
|
+
return 1
|
|
5132
|
+
}
|
|
5133
|
+
migration_id=$(echo "$create_result" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
|
|
5134
|
+
local migration_dir
|
|
5135
|
+
migration_dir=$(echo "$create_result" | python3 -c "import json,sys; print(json.load(sys.stdin)['dir'])")
|
|
5136
|
+
|
|
5137
|
+
# Security scan (first step of understand phase)
|
|
5138
|
+
echo -e "${BOLD}[Security] Scanning for secrets...${NC}"
|
|
5139
|
+
local secrets_found=0
|
|
5140
|
+
if command -v grep &>/dev/null; then
|
|
5141
|
+
local secret_patterns="(password|secret|api_key|apikey|token|private_key|AWS_SECRET|DB_PASS)\\s*[=:]\\s*['\"][^'\"]+['\"]"
|
|
5142
|
+
secrets_found=$(grep -rEli "$secret_patterns" "$codebase_path" --include='*.js' --include='*.ts' --include='*.py' --include='*.rb' --include='*.go' --include='*.java' --include='*.env' --include='*.yml' --include='*.yaml' --include='*.json' --include='*.toml' --include='*.cfg' --include='*.ini' 2>/dev/null | wc -l | tr -d ' ')
|
|
5143
|
+
fi
|
|
5144
|
+
|
|
5145
|
+
if [ "$secrets_found" -gt 0 ]; then
|
|
5146
|
+
echo -e "${RED}WARNING: Found $secrets_found file(s) potentially containing secrets${NC}"
|
|
5147
|
+
echo -e "${YELLOW}Review these files before migration to avoid leaking credentials.${NC}"
|
|
5148
|
+
echo ""
|
|
5149
|
+
else
|
|
5150
|
+
echo -e "${GREEN}No secrets detected.${NC}"
|
|
5151
|
+
echo ""
|
|
5152
|
+
fi
|
|
5153
|
+
|
|
5154
|
+
# Handle dry-run
|
|
5155
|
+
if [ "$dry_run" = "true" ]; then
|
|
5156
|
+
echo -e "${BOLD}[Dry Run] Migration preview${NC}"
|
|
5157
|
+
echo "---"
|
|
5158
|
+
echo -e " Codebase: ${codebase_path}"
|
|
5159
|
+
echo -e " Target: ${target}"
|
|
5160
|
+
echo -e " Parallel: ${parallel} agents"
|
|
5161
|
+
[ -n "$compliance" ] && echo -e " Compliance: ${compliance}"
|
|
5162
|
+
[ -n "$multi_repo" ] && echo -e " Multi-repo: ${multi_repo}"
|
|
5163
|
+
echo ""
|
|
5164
|
+
echo -e "${DIM}Phases that would execute:${NC}"
|
|
5165
|
+
echo " 1. understand - Analyze codebase structure and dependencies"
|
|
5166
|
+
echo " 2. guardrail - Set up test safety nets and rollback points"
|
|
5167
|
+
echo " 3. migrate - Execute migration transforms"
|
|
5168
|
+
echo " 4. verify - Run verification suite and regression tests"
|
|
5169
|
+
echo ""
|
|
5170
|
+
echo -e "${YELLOW}No changes were made (dry-run mode).${NC}"
|
|
5171
|
+
# Clean up migration dir created by engine
|
|
5172
|
+
rm -rf "$migration_dir" 2>/dev/null || true
|
|
5173
|
+
return 0
|
|
5174
|
+
fi
|
|
5175
|
+
|
|
5176
|
+
emit_event migration cli start "id=$migration_id" "target=$target" "source=$codebase_path" 2>/dev/null || true
|
|
5177
|
+
|
|
5178
|
+
echo -e "${BOLD}Migration: ${migration_id}${NC}"
|
|
5179
|
+
echo "---"
|
|
5180
|
+
echo -e " Codebase: ${codebase_path}"
|
|
5181
|
+
echo -e " Target: ${target}"
|
|
5182
|
+
echo -e " Parallel: ${parallel} agents"
|
|
5183
|
+
[ -n "$compliance" ] && echo -e " Compliance: ${compliance}"
|
|
5184
|
+
echo ""
|
|
5185
|
+
|
|
5186
|
+
# Determine which phases to run
|
|
5187
|
+
local phases_to_run=()
|
|
5188
|
+
if [ -n "$phase" ]; then
|
|
5189
|
+
case "$phase" in
|
|
5190
|
+
understand|guardrail|migrate|verify)
|
|
5191
|
+
phases_to_run=("$phase")
|
|
5192
|
+
;;
|
|
5193
|
+
*)
|
|
5194
|
+
echo -e "${RED}Error: Invalid phase: ${phase}${NC}"
|
|
5195
|
+
echo "Valid phases: understand, guardrail, migrate, verify"
|
|
5196
|
+
return 1
|
|
5197
|
+
;;
|
|
5198
|
+
esac
|
|
5199
|
+
elif [ "$plan_only" = "true" ]; then
|
|
5200
|
+
phases_to_run=("understand" "guardrail")
|
|
5201
|
+
else
|
|
5202
|
+
phases_to_run=("understand" "guardrail" "migrate" "verify")
|
|
5203
|
+
fi
|
|
5204
|
+
|
|
5205
|
+
# Execute phases
|
|
5206
|
+
for p in "${phases_to_run[@]}"; do
|
|
5207
|
+
echo -e "${CYAN}[Phase: ${p}]${NC} Starting..."
|
|
5208
|
+
|
|
5209
|
+
# Start phase via engine
|
|
5210
|
+
python3 -c "
|
|
5211
|
+
import sys
|
|
5212
|
+
sys.path.insert(0, '.')
|
|
5213
|
+
from dashboard.migration_engine import MigrationPipeline
|
|
5214
|
+
pipeline = MigrationPipeline.load(sys.argv[1])
|
|
5215
|
+
pipeline.start_phase(sys.argv[2])
|
|
5216
|
+
" "$migration_id" "$p" || {
|
|
5217
|
+
echo -e "${RED}Error: Failed to start phase $p${NC}"
|
|
5218
|
+
return 1
|
|
5219
|
+
}
|
|
5220
|
+
|
|
5221
|
+
# Phase-specific logic
|
|
5222
|
+
case "$p" in
|
|
5223
|
+
understand)
|
|
5224
|
+
echo -e " ${DIM}Analyzing codebase structure...${NC}"
|
|
5225
|
+
echo -e " ${DIM}Detecting languages, frameworks, dependencies...${NC}"
|
|
5226
|
+
echo -e " ${DIM}Building dependency graph...${NC}"
|
|
5227
|
+
;;
|
|
5228
|
+
guardrail)
|
|
5229
|
+
echo -e " ${DIM}Identifying existing tests...${NC}"
|
|
5230
|
+
echo -e " ${DIM}Creating rollback checkpoints...${NC}"
|
|
5231
|
+
echo -e " ${DIM}Setting up safety nets...${NC}"
|
|
5232
|
+
;;
|
|
5233
|
+
migrate)
|
|
5234
|
+
echo -e " ${DIM}Executing migration transforms (${parallel} parallel agents)...${NC}"
|
|
5235
|
+
echo -e " ${DIM}Applying incremental changes...${NC}"
|
|
5236
|
+
;;
|
|
5237
|
+
verify)
|
|
5238
|
+
echo -e " ${DIM}Running verification suite...${NC}"
|
|
5239
|
+
echo -e " ${DIM}Checking regression tests...${NC}"
|
|
5240
|
+
echo -e " ${DIM}Validating build output...${NC}"
|
|
5241
|
+
;;
|
|
5242
|
+
esac
|
|
5243
|
+
|
|
5244
|
+
# Complete phase via engine
|
|
5245
|
+
python3 -c "
|
|
5246
|
+
import sys
|
|
5247
|
+
sys.path.insert(0, '.')
|
|
5248
|
+
from dashboard.migration_engine import MigrationPipeline
|
|
5249
|
+
pipeline = MigrationPipeline.load(sys.argv[1])
|
|
5250
|
+
pipeline.advance_phase(sys.argv[2])
|
|
5251
|
+
" "$migration_id" "$p" || {
|
|
5252
|
+
echo -e "${RED}Error: Failed to complete phase $p${NC}"
|
|
5253
|
+
return 1
|
|
5254
|
+
}
|
|
5255
|
+
|
|
5256
|
+
echo -e "${GREEN} [Phase: ${p}] Complete${NC}"
|
|
5257
|
+
echo ""
|
|
5258
|
+
done
|
|
5259
|
+
|
|
5260
|
+
# Plan-only: generate plan file and stop
|
|
5261
|
+
if [ "$plan_only" = "true" ]; then
|
|
5262
|
+
echo -e "${BOLD}Migration plan generated.${NC}"
|
|
5263
|
+
echo -e "View with: loki migrate --show-plan"
|
|
5264
|
+
|
|
5265
|
+
python3 -c "
|
|
5266
|
+
import json, sys
|
|
5267
|
+
from datetime import datetime
|
|
5268
|
+
plan = {
|
|
5269
|
+
'target': sys.argv[1],
|
|
5270
|
+
'source_path': sys.argv[2],
|
|
5271
|
+
'estimated_hours': '?',
|
|
5272
|
+
'risk_level': 'medium',
|
|
5273
|
+
'steps': [],
|
|
5274
|
+
'dependencies': [],
|
|
5275
|
+
'breaking_changes': [],
|
|
5276
|
+
'generated_at': datetime.now().isoformat()
|
|
5277
|
+
}
|
|
5278
|
+
with open(sys.argv[3], 'w') as f:
|
|
5279
|
+
json.dump(plan, f, indent=2)
|
|
5280
|
+
" "$target" "$codebase_path" "${migration_dir}/migration-plan.json" || {
|
|
5281
|
+
echo -e "${RED}Error: Failed to create migration plan${NC}"
|
|
5282
|
+
return 1
|
|
5283
|
+
}
|
|
5284
|
+
|
|
5285
|
+
# Update status to paused
|
|
5286
|
+
python3 -c "
|
|
5287
|
+
import json, sys
|
|
5288
|
+
from datetime import datetime
|
|
5289
|
+
manifest_path = sys.argv[1]
|
|
5290
|
+
with open(manifest_path) as f:
|
|
5291
|
+
m = json.load(f)
|
|
5292
|
+
m['status'] = 'paused'
|
|
5293
|
+
m['updated_at'] = datetime.now().isoformat()
|
|
5294
|
+
with open(manifest_path, 'w') as f:
|
|
5295
|
+
json.dump(m, f, indent=2)
|
|
5296
|
+
" "${migration_dir}/manifest.json" || {
|
|
5297
|
+
echo -e "${RED}Error: Failed to update migration state${NC}"
|
|
5298
|
+
return 1
|
|
5299
|
+
}
|
|
5300
|
+
return 0
|
|
5301
|
+
fi
|
|
5302
|
+
|
|
5303
|
+
# Mark migration complete
|
|
5304
|
+
python3 -c "
|
|
5305
|
+
import json, sys
|
|
5306
|
+
from datetime import datetime
|
|
5307
|
+
manifest_path = sys.argv[1]
|
|
5308
|
+
with open(manifest_path) as f:
|
|
5309
|
+
m = json.load(f)
|
|
5310
|
+
m['status'] = 'completed'
|
|
5311
|
+
m['progress_pct'] = 100
|
|
5312
|
+
m['updated_at'] = datetime.now().isoformat()
|
|
5313
|
+
with open(manifest_path, 'w') as f:
|
|
5314
|
+
json.dump(m, f, indent=2)
|
|
5315
|
+
" "${migration_dir}/manifest.json" || {
|
|
5316
|
+
echo -e "${RED}Error: Failed to update migration state${NC}"
|
|
5317
|
+
return 1
|
|
5318
|
+
}
|
|
5319
|
+
|
|
5320
|
+
emit_event migration cli complete "id=$migration_id" "target=$target" 2>/dev/null || true
|
|
5321
|
+
|
|
5322
|
+
# Export report if requested
|
|
5323
|
+
if [ "$export_report" = "true" ]; then
|
|
5324
|
+
local report_file="${codebase_path}/migration-report-${migration_id}.json"
|
|
5325
|
+
cp "${migration_dir}/manifest.json" "$report_file" 2>/dev/null
|
|
5326
|
+
echo -e "${GREEN}Report exported: ${report_file}${NC}"
|
|
5327
|
+
fi
|
|
5328
|
+
|
|
5329
|
+
echo -e "${GREEN}${BOLD}Migration complete: ${migration_id}${NC}"
|
|
5330
|
+
echo -e "View details: loki migrate --status ${migration_id}"
|
|
5331
|
+
}
|
|
5332
|
+
|
|
5333
|
+
cmd_migrate() {
|
|
5334
|
+
local codebase_path=""
|
|
5335
|
+
local target=""
|
|
5336
|
+
local plan_only="false"
|
|
5337
|
+
local show_plan="false"
|
|
5338
|
+
local phase=""
|
|
5339
|
+
local parallel=""
|
|
5340
|
+
local compliance=""
|
|
5341
|
+
local dry_run="false"
|
|
5342
|
+
local do_resume="false"
|
|
5343
|
+
local multi_repo=""
|
|
5344
|
+
local export_report="false"
|
|
5345
|
+
local do_list="false"
|
|
5346
|
+
local do_status="false"
|
|
5347
|
+
local status_id=""
|
|
5348
|
+
|
|
5349
|
+
if [ $# -eq 0 ]; then
|
|
5350
|
+
cmd_migrate_help
|
|
5351
|
+
return 0
|
|
5352
|
+
fi
|
|
5353
|
+
|
|
5354
|
+
while [[ $# -gt 0 ]]; do
|
|
5355
|
+
case "$1" in
|
|
5356
|
+
--help|-h)
|
|
5357
|
+
cmd_migrate_help
|
|
5358
|
+
return 0
|
|
5359
|
+
;;
|
|
5360
|
+
--list)
|
|
5361
|
+
do_list="true"
|
|
5362
|
+
shift
|
|
5363
|
+
;;
|
|
5364
|
+
--status)
|
|
5365
|
+
do_status="true"
|
|
5366
|
+
if [[ -n "${2:-}" ]] && [[ ! "$2" =~ ^-- ]]; then
|
|
5367
|
+
status_id="$2"
|
|
5368
|
+
shift
|
|
5369
|
+
fi
|
|
5370
|
+
shift
|
|
5371
|
+
;;
|
|
5372
|
+
--show-plan)
|
|
5373
|
+
show_plan="true"
|
|
5374
|
+
shift
|
|
5375
|
+
;;
|
|
5376
|
+
--target)
|
|
5377
|
+
if [[ -z "${2:-}" ]]; then
|
|
5378
|
+
echo -e "${RED}Error: --target requires a value${NC}"
|
|
5379
|
+
return 1
|
|
5380
|
+
fi
|
|
5381
|
+
target="$2"
|
|
5382
|
+
shift 2
|
|
5383
|
+
;;
|
|
5384
|
+
--target=*)
|
|
5385
|
+
target="${1#*=}"
|
|
5386
|
+
shift
|
|
5387
|
+
;;
|
|
5388
|
+
--plan-only)
|
|
5389
|
+
plan_only="true"
|
|
5390
|
+
shift
|
|
5391
|
+
;;
|
|
5392
|
+
--phase)
|
|
5393
|
+
if [[ -z "${2:-}" ]]; then
|
|
5394
|
+
echo -e "${RED}Error: --phase requires a value${NC}"
|
|
5395
|
+
return 1
|
|
5396
|
+
fi
|
|
5397
|
+
phase="$2"
|
|
5398
|
+
shift 2
|
|
5399
|
+
;;
|
|
5400
|
+
--phase=*)
|
|
5401
|
+
phase="${1#*=}"
|
|
5402
|
+
shift
|
|
5403
|
+
;;
|
|
5404
|
+
--parallel)
|
|
5405
|
+
if [[ -z "${2:-}" ]]; then
|
|
5406
|
+
echo -e "${RED}Error: --parallel requires a value${NC}"
|
|
5407
|
+
return 1
|
|
5408
|
+
fi
|
|
5409
|
+
parallel="$2"
|
|
5410
|
+
shift 2
|
|
5411
|
+
;;
|
|
5412
|
+
--parallel=*)
|
|
5413
|
+
parallel="${1#*=}"
|
|
5414
|
+
shift
|
|
5415
|
+
;;
|
|
5416
|
+
--compliance)
|
|
5417
|
+
if [[ -z "${2:-}" ]]; then
|
|
5418
|
+
echo -e "${RED}Error: --compliance requires a value${NC}"
|
|
5419
|
+
return 1
|
|
5420
|
+
fi
|
|
5421
|
+
compliance="$2"
|
|
5422
|
+
shift 2
|
|
5423
|
+
;;
|
|
5424
|
+
--compliance=*)
|
|
5425
|
+
compliance="${1#*=}"
|
|
5426
|
+
shift
|
|
5427
|
+
;;
|
|
5428
|
+
--dry-run)
|
|
5429
|
+
dry_run="true"
|
|
5430
|
+
shift
|
|
5431
|
+
;;
|
|
5432
|
+
--resume)
|
|
5433
|
+
do_resume="true"
|
|
5434
|
+
shift
|
|
5435
|
+
;;
|
|
5436
|
+
--multi-repo)
|
|
5437
|
+
if [[ -z "${2:-}" ]]; then
|
|
5438
|
+
echo -e "${RED}Error: --multi-repo requires a value${NC}"
|
|
5439
|
+
return 1
|
|
5440
|
+
fi
|
|
5441
|
+
multi_repo="$2"
|
|
5442
|
+
shift 2
|
|
5443
|
+
;;
|
|
5444
|
+
--multi-repo=*)
|
|
5445
|
+
multi_repo="${1#*=}"
|
|
5446
|
+
shift
|
|
5447
|
+
;;
|
|
5448
|
+
--export-report)
|
|
5449
|
+
export_report="true"
|
|
5450
|
+
shift
|
|
5451
|
+
;;
|
|
5452
|
+
-*)
|
|
5453
|
+
echo -e "${RED}Unknown option: $1${NC}"
|
|
5454
|
+
echo "Run 'loki migrate --help' for usage."
|
|
5455
|
+
return 1
|
|
5456
|
+
;;
|
|
5457
|
+
*)
|
|
5458
|
+
if [ -z "$codebase_path" ]; then
|
|
5459
|
+
codebase_path="$1"
|
|
5460
|
+
else
|
|
5461
|
+
echo -e "${RED}Error: Unexpected argument: $1${NC}"
|
|
5462
|
+
echo "Run 'loki migrate --help' for usage."
|
|
5463
|
+
return 1
|
|
5464
|
+
fi
|
|
5465
|
+
shift
|
|
5466
|
+
;;
|
|
5467
|
+
esac
|
|
5468
|
+
done
|
|
5469
|
+
|
|
5470
|
+
# Route to subcommands
|
|
5471
|
+
if [ "$do_list" = "true" ]; then
|
|
5472
|
+
cmd_migrate_list
|
|
5473
|
+
return 0
|
|
5474
|
+
fi
|
|
5475
|
+
|
|
5476
|
+
if [ "$do_status" = "true" ]; then
|
|
5477
|
+
cmd_migrate_status "$status_id"
|
|
5478
|
+
return 0
|
|
5479
|
+
fi
|
|
5480
|
+
|
|
5481
|
+
if [ "$show_plan" = "true" ]; then
|
|
5482
|
+
cmd_migrate_show_plan "${codebase_path:-$(pwd)}"
|
|
5483
|
+
return 0
|
|
5484
|
+
fi
|
|
5485
|
+
|
|
5486
|
+
# Default: start or continue migration
|
|
5487
|
+
if [ -z "$codebase_path" ]; then
|
|
5488
|
+
echo -e "${RED}Error: Codebase path is required${NC}"
|
|
5489
|
+
echo "Usage: loki migrate <path-to-codebase> --target <target>"
|
|
5490
|
+
echo ""
|
|
5491
|
+
echo "Run 'loki migrate --help' for usage."
|
|
5492
|
+
return 1
|
|
5493
|
+
fi
|
|
5494
|
+
|
|
5495
|
+
cmd_migrate_start "$codebase_path" "$target" "$plan_only" "$phase" "$parallel" "$compliance" "$dry_run" "$do_resume" "$multi_repo" "$export_report"
|
|
5496
|
+
}
|
|
5497
|
+
|
|
4691
5498
|
# Main command dispatcher
|
|
4692
5499
|
main() {
|
|
4693
5500
|
if [ $# -eq 0 ]; then
|
|
@@ -4803,6 +5610,12 @@ main() {
|
|
|
4803
5610
|
audit)
|
|
4804
5611
|
cmd_audit "$@"
|
|
4805
5612
|
;;
|
|
5613
|
+
optimize)
|
|
5614
|
+
cmd_optimize "$@"
|
|
5615
|
+
;;
|
|
5616
|
+
migrate)
|
|
5617
|
+
cmd_migrate "$@"
|
|
5618
|
+
;;
|
|
4806
5619
|
metrics)
|
|
4807
5620
|
cmd_metrics "$@"
|
|
4808
5621
|
;;
|
|
@@ -4826,7 +5639,7 @@ main() {
|
|
|
4826
5639
|
esac
|
|
4827
5640
|
}
|
|
4828
5641
|
|
|
4829
|
-
# Agent action audit log
|
|
5642
|
+
# Agent action audit log and quality scanning
|
|
4830
5643
|
cmd_audit() {
|
|
4831
5644
|
local subcommand="${1:-help}"
|
|
4832
5645
|
local audit_file="$LOKI_DIR/logs/agent-audit.jsonl"
|
|
@@ -4885,16 +5698,138 @@ print(f' {\"TOTAL\":25s} {sum(counts.values())}')
|
|
|
4885
5698
|
echo " python3 required for count summary"
|
|
4886
5699
|
fi
|
|
4887
5700
|
;;
|
|
5701
|
+
scan)
|
|
5702
|
+
shift
|
|
5703
|
+
local preset="default"
|
|
5704
|
+
local do_export=""
|
|
5705
|
+
|
|
5706
|
+
while [[ $# -gt 0 ]]; do
|
|
5707
|
+
case "$1" in
|
|
5708
|
+
--preset) if [[ -z "${2:-}" ]]; then echo -e "${RED}Error: --preset requires a value${NC}"; exit 1; fi; preset="$2"; shift 2 ;;
|
|
5709
|
+
--preset=*) preset="${1#*=}"; shift ;;
|
|
5710
|
+
--export) do_export="true"; shift ;;
|
|
5711
|
+
--help|-h)
|
|
5712
|
+
echo -e "${BOLD}loki audit scan${NC} - Run quality scan"
|
|
5713
|
+
echo ""
|
|
5714
|
+
echo "Usage: loki audit scan [options]"
|
|
5715
|
+
echo ""
|
|
5716
|
+
echo "Options:"
|
|
5717
|
+
echo " --preset NAME Compliance preset (default|healthcare|fintech|government)"
|
|
5718
|
+
echo " --export Save report to .loki/quality/report-{date}.json"
|
|
5719
|
+
echo ""
|
|
5720
|
+
exit 0
|
|
5721
|
+
;;
|
|
5722
|
+
*) echo -e "${RED}Unknown option: $1${NC}"; exit 1 ;;
|
|
5723
|
+
esac
|
|
5724
|
+
done
|
|
5725
|
+
|
|
5726
|
+
case "$preset" in
|
|
5727
|
+
default|healthcare|fintech|government) ;;
|
|
5728
|
+
*) echo -e "${RED}Error: Invalid preset '$preset'. Must be one of: default, healthcare, fintech, government${NC}"; exit 1 ;;
|
|
5729
|
+
esac
|
|
5730
|
+
|
|
5731
|
+
local port="${LOKI_DASHBOARD_PORT:-57374}"
|
|
5732
|
+
local host="127.0.0.1"
|
|
5733
|
+
local url="http://${host}:${port}/api/quality-scan?preset=${preset}"
|
|
5734
|
+
|
|
5735
|
+
echo -e "${BOLD}Running quality scan...${NC} (preset: $preset)"
|
|
5736
|
+
echo ""
|
|
5737
|
+
|
|
5738
|
+
local response http_code
|
|
5739
|
+
response=$(curl -s -w "\n%{http_code}" -X POST "$url" 2>/dev/null) || true
|
|
5740
|
+
http_code=$(echo "$response" | tail -1)
|
|
5741
|
+
response=$(echo "$response" | sed '$d')
|
|
5742
|
+
if [ -z "$http_code" ] || [ "$http_code" = "000" ]; then
|
|
5743
|
+
echo -e "${RED}Error: Could not connect to dashboard API at http://${host}:${port}${NC}"
|
|
5744
|
+
echo "Make sure the dashboard is running: loki serve"
|
|
5745
|
+
exit 1
|
|
5746
|
+
fi
|
|
5747
|
+
if [ "$http_code" -ge 400 ] 2>/dev/null; then
|
|
5748
|
+
echo -e "${RED}Error: Dashboard API returned HTTP $http_code${NC}"
|
|
5749
|
+
[ -n "$response" ] && echo "$response"
|
|
5750
|
+
exit 1
|
|
5751
|
+
fi
|
|
5752
|
+
|
|
5753
|
+
if ! command -v python3 &>/dev/null; then
|
|
5754
|
+
echo "$response" | jq . 2>/dev/null || echo "$response"
|
|
5755
|
+
else
|
|
5756
|
+
echo "$response" | python3 -c "
|
|
5757
|
+
import json, sys
|
|
5758
|
+
data = json.loads(sys.stdin.read())
|
|
5759
|
+
|
|
5760
|
+
score = data.get('score', 0)
|
|
5761
|
+
grade = data.get('grade', '?')
|
|
5762
|
+
print(f'Quality Score: {score}/100 Grade: {grade}')
|
|
5763
|
+
print('---')
|
|
5764
|
+
|
|
5765
|
+
# Category scores
|
|
5766
|
+
categories = data.get('categories', {})
|
|
5767
|
+
if categories:
|
|
5768
|
+
print()
|
|
5769
|
+
print('Category Scores:')
|
|
5770
|
+
for cat, cat_score in sorted(categories.items()):
|
|
5771
|
+
print(f' {cat:20s} {cat_score}/100')
|
|
5772
|
+
|
|
5773
|
+
# Findings by severity
|
|
5774
|
+
findings = data.get('findings', [])
|
|
5775
|
+
if findings:
|
|
5776
|
+
print()
|
|
5777
|
+
print('Findings:')
|
|
5778
|
+
for f in findings:
|
|
5779
|
+
sev = f.get('severity', 'info')
|
|
5780
|
+
msg = f.get('message', '')
|
|
5781
|
+
cat = f.get('category', '')
|
|
5782
|
+
if sev == 'critical':
|
|
5783
|
+
print(f' \033[0;31m[CRITICAL]\033[0m {cat}: {msg}')
|
|
5784
|
+
elif sev == 'major':
|
|
5785
|
+
print(f' \033[1;33m[MAJOR]\033[0m {cat}: {msg}')
|
|
5786
|
+
else:
|
|
5787
|
+
print(f' [MINOR] {cat}: {msg}')
|
|
5788
|
+
print()
|
|
5789
|
+
" 2>/dev/null || echo "$response"
|
|
5790
|
+
fi
|
|
5791
|
+
|
|
5792
|
+
# Export report if requested
|
|
5793
|
+
if [ -n "$do_export" ]; then
|
|
5794
|
+
local report_dir="$LOKI_DIR/quality"
|
|
5795
|
+
mkdir -p "$report_dir"
|
|
5796
|
+
local date_str
|
|
5797
|
+
date_str=$(date +%Y-%m-%d)
|
|
5798
|
+
local report_file="$report_dir/report-${date_str}.json"
|
|
5799
|
+
|
|
5800
|
+
local report_url="http://${host}:${port}/api/quality-report?format=json"
|
|
5801
|
+
local report_data
|
|
5802
|
+
report_data=$(curl -sf "$report_url" 2>/dev/null) || true
|
|
5803
|
+
if [ -n "$report_data" ]; then
|
|
5804
|
+
echo "$report_data" > "$report_file"
|
|
5805
|
+
echo -e "${GREEN}Report exported to $report_file${NC}"
|
|
5806
|
+
else
|
|
5807
|
+
# Fall back to scan response
|
|
5808
|
+
echo "$response" > "$report_file"
|
|
5809
|
+
echo -e "${GREEN}Report exported to $report_file${NC}"
|
|
5810
|
+
fi
|
|
5811
|
+
fi
|
|
5812
|
+
;;
|
|
5813
|
+
--preset|--export)
|
|
5814
|
+
# Handle flags passed directly to 'loki audit' (shortcut for 'loki audit scan')
|
|
5815
|
+
cmd_audit scan "$@"
|
|
5816
|
+
return
|
|
5817
|
+
;;
|
|
4888
5818
|
--help|-h|help)
|
|
4889
|
-
echo -e "${BOLD}loki audit${NC} - Agent
|
|
5819
|
+
echo -e "${BOLD}loki audit${NC} - Agent audit log and quality scanning"
|
|
4890
5820
|
echo ""
|
|
4891
|
-
echo "Usage: loki audit <subcommand>"
|
|
5821
|
+
echo "Usage: loki audit <subcommand> [options]"
|
|
4892
5822
|
echo ""
|
|
4893
5823
|
echo "Subcommands:"
|
|
4894
5824
|
echo " log [N] Show last N audit log entries (default: 50)"
|
|
4895
5825
|
echo " count Count actions by type"
|
|
5826
|
+
echo " scan Run quality scan against dashboard API"
|
|
4896
5827
|
echo " help Show this help"
|
|
4897
5828
|
echo ""
|
|
5829
|
+
echo "Quality Scan Options (loki audit scan):"
|
|
5830
|
+
echo " --preset NAME Compliance preset (default|healthcare|fintech|government)"
|
|
5831
|
+
echo " --export Save report to .loki/quality/report-{date}.json"
|
|
5832
|
+
echo ""
|
|
4898
5833
|
echo "The agent audit log records actions taken during Loki sessions,"
|
|
4899
5834
|
echo "including CLI invocations, git commits, and session lifecycle events."
|
|
4900
5835
|
echo "Log file: $audit_file"
|