moai-adk 0.25.4__py3-none-any.whl โ 0.32.8__py3-none-any.whl
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.
Potentially problematic release.
This version of moai-adk might be problematic. Click here for more details.
- moai_adk/__init__.py +2 -5
- moai_adk/__main__.py +114 -82
- moai_adk/cli/__init__.py +6 -1
- moai_adk/cli/commands/__init__.py +1 -3
- moai_adk/cli/commands/analyze.py +5 -16
- moai_adk/cli/commands/doctor.py +6 -18
- moai_adk/cli/commands/init.py +56 -125
- moai_adk/cli/commands/language.py +14 -35
- moai_adk/cli/commands/status.py +9 -15
- moai_adk/cli/commands/update.py +1555 -190
- moai_adk/cli/prompts/init_prompts.py +112 -56
- moai_adk/cli/spec_status.py +263 -0
- moai_adk/cli/ui/__init__.py +44 -0
- moai_adk/cli/ui/progress.py +422 -0
- moai_adk/cli/ui/prompts.py +389 -0
- moai_adk/cli/ui/theme.py +129 -0
- moai_adk/cli/worktree/__init__.py +27 -0
- moai_adk/cli/worktree/__main__.py +31 -0
- moai_adk/cli/worktree/cli.py +672 -0
- moai_adk/cli/worktree/exceptions.py +89 -0
- moai_adk/cli/worktree/manager.py +490 -0
- moai_adk/cli/worktree/models.py +65 -0
- moai_adk/cli/worktree/registry.py +128 -0
- moai_adk/core/PHASE2_OPTIMIZATIONS.md +467 -0
- moai_adk/core/analysis/session_analyzer.py +17 -56
- moai_adk/core/claude_integration.py +26 -54
- moai_adk/core/command_helpers.py +10 -10
- moai_adk/core/comprehensive_monitoring_system.py +1183 -0
- moai_adk/core/config/auto_spec_config.py +5 -11
- moai_adk/core/config/migration.py +19 -9
- moai_adk/core/config/unified.py +436 -0
- moai_adk/core/context_manager.py +6 -12
- moai_adk/core/enterprise_features.py +1404 -0
- moai_adk/core/error_recovery_system.py +725 -112
- moai_adk/core/event_driven_hook_system.py +1371 -0
- moai_adk/core/git/__init__.py +8 -0
- moai_adk/core/git/branch_manager.py +3 -11
- moai_adk/core/git/checkpoint.py +1 -3
- moai_adk/core/git/conflict_detector.py +413 -0
- moai_adk/core/git/manager.py +91 -1
- moai_adk/core/hooks/post_tool_auto_spec_completion.py +56 -80
- moai_adk/core/input_validation_middleware.py +1006 -0
- moai_adk/core/integration/engine.py +6 -18
- moai_adk/core/integration/integration_tester.py +10 -9
- moai_adk/core/integration/utils.py +1 -1
- moai_adk/core/issue_creator.py +10 -28
- moai_adk/core/jit_context_loader.py +956 -0
- moai_adk/core/jit_enhanced_hook_manager.py +1987 -0
- moai_adk/core/language_config_resolver.py +485 -0
- moai_adk/core/language_validator.py +28 -41
- moai_adk/core/mcp/setup.py +15 -12
- moai_adk/core/merge/__init__.py +9 -0
- moai_adk/core/merge/analyzer.py +481 -0
- moai_adk/core/migration/alfred_to_moai_migrator.py +383 -0
- moai_adk/core/migration/backup_manager.py +78 -9
- moai_adk/core/migration/custom_element_scanner.py +358 -0
- moai_adk/core/migration/file_migrator.py +8 -17
- moai_adk/core/migration/interactive_checkbox_ui.py +488 -0
- moai_adk/core/migration/selective_restorer.py +470 -0
- moai_adk/core/migration/template_utils.py +74 -0
- moai_adk/core/migration/user_selection_ui.py +338 -0
- moai_adk/core/migration/version_detector.py +6 -10
- moai_adk/core/migration/version_migrator.py +3 -3
- moai_adk/core/performance/cache_system.py +8 -10
- moai_adk/core/phase_optimized_hook_scheduler.py +879 -0
- moai_adk/core/project/checker.py +2 -4
- moai_adk/core/project/detector.py +1 -3
- moai_adk/core/project/initializer.py +135 -23
- moai_adk/core/project/phase_executor.py +54 -81
- moai_adk/core/project/validator.py +6 -12
- moai_adk/core/quality/trust_checker.py +9 -27
- moai_adk/core/realtime_monitoring_dashboard.py +1724 -0
- moai_adk/core/robust_json_parser.py +611 -0
- moai_adk/core/rollback_manager.py +73 -148
- moai_adk/core/session_manager.py +10 -26
- moai_adk/core/skill_loading_system.py +579 -0
- moai_adk/core/spec/confidence_scoring.py +31 -100
- moai_adk/core/spec/ears_template_engine.py +351 -286
- moai_adk/core/spec/quality_validator.py +35 -69
- moai_adk/core/spec_status_manager.py +64 -74
- moai_adk/core/template/backup.py +45 -20
- moai_adk/core/template/config.py +112 -39
- moai_adk/core/template/merger.py +11 -19
- moai_adk/core/template/processor.py +253 -149
- moai_adk/core/template_engine.py +73 -40
- moai_adk/core/template_variable_synchronizer.py +417 -0
- moai_adk/core/unified_permission_manager.py +745 -0
- moai_adk/core/user_behavior_analytics.py +851 -0
- moai_adk/core/version_sync.py +429 -0
- moai_adk/foundation/__init__.py +56 -0
- moai_adk/foundation/backend.py +1027 -0
- moai_adk/foundation/database.py +1115 -0
- moai_adk/foundation/devops.py +1585 -0
- moai_adk/foundation/ears.py +431 -0
- moai_adk/foundation/frontend.py +870 -0
- moai_adk/foundation/git/commit_templates.py +4 -12
- moai_adk/foundation/git.py +376 -0
- moai_adk/foundation/langs.py +484 -0
- moai_adk/foundation/ml_ops.py +1162 -0
- moai_adk/foundation/testing.py +1524 -0
- moai_adk/foundation/trust/trust_principles.py +23 -72
- moai_adk/foundation/trust/validation_checklist.py +57 -162
- moai_adk/project/__init__.py +0 -0
- moai_adk/project/configuration.py +1084 -0
- moai_adk/project/documentation.py +566 -0
- moai_adk/project/schema.py +447 -0
- moai_adk/statusline/alfred_detector.py +1 -3
- moai_adk/statusline/config.py +13 -4
- moai_adk/statusline/enhanced_output_style_detector.py +23 -15
- moai_adk/statusline/main.py +51 -15
- moai_adk/statusline/renderer.py +104 -48
- moai_adk/statusline/update_checker.py +3 -9
- moai_adk/statusline/version_reader.py +140 -46
- moai_adk/templates/.claude/agents/moai/ai-nano-banana.md +549 -0
- moai_adk/templates/.claude/agents/moai/builder-agent.md +445 -0
- moai_adk/templates/.claude/agents/moai/builder-command.md +1132 -0
- moai_adk/templates/.claude/agents/moai/builder-skill.md +601 -0
- moai_adk/templates/.claude/agents/moai/expert-backend.md +831 -0
- moai_adk/templates/.claude/agents/moai/expert-database.md +774 -0
- moai_adk/templates/.claude/agents/moai/expert-debug.md +396 -0
- moai_adk/templates/.claude/agents/moai/expert-devops.md +711 -0
- moai_adk/templates/.claude/agents/moai/expert-frontend.md +666 -0
- moai_adk/templates/.claude/agents/moai/expert-security.md +474 -0
- moai_adk/templates/.claude/agents/moai/expert-uiux.md +1038 -0
- moai_adk/templates/.claude/agents/moai/manager-claude-code.md +429 -0
- moai_adk/templates/.claude/agents/moai/manager-docs.md +570 -0
- moai_adk/templates/.claude/agents/moai/manager-git.md +937 -0
- moai_adk/templates/.claude/agents/moai/manager-project.md +891 -0
- moai_adk/templates/.claude/agents/moai/manager-quality.md +598 -0
- moai_adk/templates/.claude/agents/moai/manager-spec.md +713 -0
- moai_adk/templates/.claude/agents/moai/manager-strategy.md +600 -0
- moai_adk/templates/.claude/agents/moai/manager-tdd.md +603 -0
- moai_adk/templates/.claude/agents/moai/mcp-context7.md +369 -0
- moai_adk/templates/.claude/agents/moai/mcp-figma.md +1567 -0
- moai_adk/templates/.claude/agents/moai/mcp-notion.md +749 -0
- moai_adk/templates/.claude/agents/moai/mcp-playwright.md +427 -0
- moai_adk/templates/.claude/agents/moai/mcp-sequential-thinking.md +994 -0
- moai_adk/templates/.claude/commands/moai/0-project.md +1143 -0
- moai_adk/templates/.claude/commands/moai/1-plan.md +1435 -0
- moai_adk/templates/.claude/commands/moai/2-run.md +883 -0
- moai_adk/templates/.claude/commands/moai/3-sync.md +993 -0
- moai_adk/templates/.claude/commands/moai/9-feedback.md +314 -0
- moai_adk/templates/.claude/hooks/__init__.py +8 -0
- moai_adk/templates/.claude/hooks/moai/__init__.py +8 -0
- moai_adk/templates/.claude/hooks/moai/lib/__init__.py +85 -0
- moai_adk/templates/.claude/hooks/moai/lib/checkpoint.py +244 -0
- moai_adk/templates/.claude/hooks/moai/lib/common.py +131 -0
- moai_adk/templates/.claude/hooks/moai/lib/config_manager.py +446 -0
- moai_adk/templates/.claude/hooks/moai/lib/config_validator.py +639 -0
- moai_adk/templates/.claude/hooks/moai/lib/example_config.json +104 -0
- moai_adk/templates/.claude/hooks/moai/lib/git_operations_manager.py +590 -0
- moai_adk/templates/.claude/hooks/moai/lib/language_validator.py +317 -0
- moai_adk/templates/.claude/hooks/moai/lib/models.py +102 -0
- moai_adk/templates/.claude/hooks/moai/lib/path_utils.py +28 -0
- moai_adk/templates/.claude/hooks/moai/lib/project.py +768 -0
- moai_adk/templates/.claude/hooks/moai/lib/test_hooks_improvements.py +443 -0
- moai_adk/templates/.claude/hooks/moai/lib/timeout.py +160 -0
- moai_adk/templates/.claude/hooks/moai/lib/unified_timeout_manager.py +530 -0
- moai_adk/templates/.claude/hooks/moai/session_end__auto_cleanup.py +862 -0
- moai_adk/templates/.claude/hooks/moai/session_start__show_project_info.py +921 -0
- moai_adk/templates/.claude/output-styles/moai/r2d2.md +380 -0
- moai_adk/templates/.claude/output-styles/moai/yoda.md +338 -0
- moai_adk/templates/.claude/settings.json +172 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/SKILL.md +247 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/README.md +44 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/api-documentation.md +130 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/code-documentation.md +152 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/multi-format-output.md +178 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/user-guides.md +147 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/SKILL.md +319 -0
- moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +320 -0
- moai_adk/templates/.claude/skills/moai-domain-database/modules/README.md +53 -0
- moai_adk/templates/.claude/skills/moai-domain-database/modules/mongodb.md +231 -0
- moai_adk/templates/.claude/skills/moai-domain-database/modules/postgresql.md +169 -0
- moai_adk/templates/.claude/skills/moai-domain-database/modules/redis.md +262 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +496 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/SKILL.md +453 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/examples.md +560 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/accessibility-wcag.md +260 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/component-architecture.md +228 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/design-system-tokens.md +405 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/icon-libraries.md +401 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/theming-system.md +373 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/reference.md +243 -0
- moai_adk/templates/.claude/skills/moai-formats-data/SKILL.md +491 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/README.md +98 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/SKILL-MODULARIZATION-TEMPLATE.md +278 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/caching-performance.md +459 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/data-validation.md +485 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/json-optimization.md +374 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/toon-encoding.md +308 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/SKILL.md +201 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/best-practices-checklist.md +616 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-custom-slash-commands-official.md +729 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-hooks-official.md +560 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-iam-official.md +635 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-memory-official.md +543 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-settings-official.md +663 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-skills-official.md +113 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-sub-agents-official.md +238 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/complete-configuration-guide.md +175 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/skill-examples.md +1674 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/skill-formatting-guide.md +729 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/sub-agents/sub-agent-examples.md +1513 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/sub-agents/sub-agent-formatting-guide.md +1086 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/sub-agents/sub-agent-integration-patterns.md +1100 -0
- moai_adk/templates/.claude/skills/moai-foundation-context/SKILL.md +438 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/SKILL.md +515 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/README.md +296 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/agents-reference.md +346 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/commands-reference.md +432 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/delegation-patterns.md +757 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/execution-rules.md +687 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/modular-system.md +665 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/progressive-disclosure.md +649 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/spec-first-tdd.md +864 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/token-optimization.md +708 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/trust-5-framework.md +981 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/SKILL.md +362 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/examples.md +1232 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/modules/best-practices.md +261 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/modules/integration-patterns.md +194 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/modules/proactive-analysis.md +229 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/modules/trust5-validation.md +169 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/reference.md +1266 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/scripts/quality-gate.sh +668 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/templates/github-actions-quality.yml +481 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/templates/quality-config.yaml +519 -0
- moai_adk/templates/.claude/skills/moai-integration-mcp/SKILL.md +352 -0
- moai_adk/templates/.claude/skills/moai-integration-mcp/modules/README.md +52 -0
- moai_adk/templates/.claude/skills/moai-integration-mcp/modules/error-handling.md +334 -0
- moai_adk/templates/.claude/skills/moai-integration-mcp/modules/integration-patterns.md +310 -0
- moai_adk/templates/.claude/skills/moai-integration-mcp/modules/security-authentication.md +256 -0
- moai_adk/templates/.claude/skills/moai-integration-mcp/modules/server-architecture.md +253 -0
- moai_adk/templates/.claude/skills/moai-lang-unified/README.md +133 -0
- moai_adk/templates/.claude/skills/moai-lang-unified/SKILL.md +296 -0
- moai_adk/templates/.claude/skills/moai-lang-unified/examples.md +1269 -0
- moai_adk/templates/.claude/skills/moai-lang-unified/reference.md +331 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/SKILL.md +298 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/advanced-patterns.md +465 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/examples.md +270 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/optimization.md +440 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/reference.md +228 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/SKILL.md +316 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/advanced-patterns.md +336 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/advanced-deployment-patterns.md +182 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/advanced-patterns.md +17 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/configuration.md +57 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/content-architecture-optimization.md +162 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/deployment.md +52 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/framework-core-configuration.md +186 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/i18n-setup.md +55 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/mdx-components.md +52 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/optimization.md +303 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/SKILL.md +370 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/examples.md +575 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/modules/advanced-patterns.md +394 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/modules/optimization.md +278 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/modules/shadcn-components.md +457 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/modules/shadcn-theming.md +373 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/reference.md +74 -0
- moai_adk/templates/.claude/skills/moai-platform-baas/README.md +186 -0
- moai_adk/templates/.claude/skills/moai-platform-baas/SKILL.md +290 -0
- moai_adk/templates/.claude/skills/moai-platform-baas/examples.md +1225 -0
- moai_adk/templates/.claude/skills/moai-platform-baas/reference.md +567 -0
- moai_adk/templates/.claude/skills/moai-platform-baas/scripts/provider-selector.py +323 -0
- moai_adk/templates/.claude/skills/moai-platform-baas/templates/stack-config.yaml +204 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/SKILL.md +446 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/advanced-patterns.md +379 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/optimization.md +286 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/README.md +190 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/SKILL.md +387 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/__init__.py +520 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/complete_workflow_demo_fixed.py +574 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/complete_project_setup.py +317 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/complete_workflow_demo.py +663 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/config-migration-example.json +190 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/question-examples.json +135 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/quick_start.py +196 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/__init__.py +17 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/advanced-patterns.md +158 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/ask_user_integration.py +340 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/batch_questions.py +713 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/config_manager.py +538 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/documentation_manager.py +1336 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/language_initializer.py +730 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/migration_manager.py +608 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/template_optimizer.py +1005 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/schemas/config-schema.json +316 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/schemas/tab_schema.json +1362 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/config-template.json +71 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/doc-templates/product-template.md +44 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/doc-templates/structure-template.md +48 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/doc-templates/tech-template.md +71 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/config-manager-setup.json +109 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/language-initializer.json +228 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/menu-project-config.json +130 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/project-batch-questions.json +97 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/spec-workflow-setup.json +150 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/test_integration_simple.py +436 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/SKILL.md +374 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/modules/code-templates.md +124 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/modules/feedback-templates.md +100 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/modules/template-optimizer.md +138 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/LICENSE.txt +202 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/SKILL.md +453 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/advanced-patterns.md +576 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples/ai-powered-testing.py +294 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples/console_logging.py +35 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples/element_discovery.py +40 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples/static_html_automation.py +34 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/README.md +220 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/ai-debugging.md +845 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review.md +1416 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization.md +1234 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/smart-refactoring.md +1243 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd-context7.md +1260 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/optimization.md +505 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/reference/playwright-best-practices.md +57 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/scripts/with_server.py +218 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/templates/alfred-integration.md +376 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/workflows/enterprise-testing-workflow.py +571 -0
- moai_adk/templates/.claude/skills/moai-worktree/SKILL.md +410 -0
- moai_adk/templates/.claude/skills/moai-worktree/examples.md +606 -0
- moai_adk/templates/.claude/skills/moai-worktree/modules/integration-patterns.md +982 -0
- moai_adk/templates/.claude/skills/moai-worktree/modules/parallel-development.md +778 -0
- moai_adk/templates/.claude/skills/moai-worktree/modules/worktree-commands.md +646 -0
- moai_adk/templates/.claude/skills/moai-worktree/modules/worktree-management.md +782 -0
- moai_adk/templates/.claude/skills/moai-worktree/reference.md +357 -0
- moai_adk/templates/.git-hooks/pre-commit +103 -41
- moai_adk/templates/.git-hooks/pre-push +116 -21
- moai_adk/templates/.github/workflows/ci-universal.yml +513 -0
- moai_adk/templates/.github/workflows/security-secrets-check.yml +179 -0
- moai_adk/templates/.gitignore +184 -44
- moai_adk/templates/.mcp.json +7 -9
- moai_adk/templates/.moai/cache/personalization.json +10 -0
- moai_adk/templates/.moai/config/config.yaml +344 -0
- moai_adk/templates/.moai/config/presets/manual.yaml +28 -0
- moai_adk/templates/.moai/config/presets/personal.yaml +30 -0
- moai_adk/templates/.moai/config/presets/team.yaml +33 -0
- moai_adk/templates/.moai/config/questions/_schema.yaml +79 -0
- moai_adk/templates/.moai/config/questions/tab1-user.yaml +108 -0
- moai_adk/templates/.moai/config/questions/tab2-project.yaml +122 -0
- moai_adk/templates/.moai/config/questions/tab3-git.yaml +542 -0
- moai_adk/templates/.moai/config/questions/tab4-quality.yaml +167 -0
- moai_adk/templates/.moai/config/questions/tab5-system.yaml +152 -0
- moai_adk/templates/.moai/config/sections/git-strategy.yaml +40 -0
- moai_adk/templates/.moai/config/sections/language.yaml +11 -0
- moai_adk/templates/.moai/config/sections/project.yaml +13 -0
- moai_adk/templates/.moai/config/sections/quality.yaml +15 -0
- moai_adk/templates/.moai/config/sections/system.yaml +14 -0
- moai_adk/templates/.moai/config/sections/user.yaml +5 -0
- moai_adk/templates/.moai/config/statusline-config.yaml +86 -0
- moai_adk/templates/.moai/scripts/setup-glm.py +136 -0
- moai_adk/templates/CLAUDE.md +382 -501
- moai_adk/utils/__init__.py +24 -1
- moai_adk/utils/banner.py +7 -10
- moai_adk/utils/common.py +16 -30
- moai_adk/utils/link_validator.py +4 -12
- moai_adk/utils/safe_file_reader.py +2 -6
- moai_adk/utils/timeout.py +160 -0
- moai_adk/utils/toon_utils.py +256 -0
- moai_adk/version.py +22 -0
- moai_adk-0.32.8.dist-info/METADATA +2478 -0
- moai_adk-0.32.8.dist-info/RECORD +396 -0
- {moai_adk-0.25.4.dist-info โ moai_adk-0.32.8.dist-info}/WHEEL +1 -1
- {moai_adk-0.25.4.dist-info โ moai_adk-0.32.8.dist-info}/entry_points.txt +1 -0
- moai_adk/cli/commands/backup.py +0 -82
- moai_adk/cli/commands/improve_user_experience.py +0 -348
- moai_adk/cli/commands/migrate.py +0 -158
- moai_adk/cli/commands/validate_links.py +0 -118
- moai_adk/templates/.github/workflows/moai-gitflow.yml +0 -413
- moai_adk/templates/.github/workflows/moai-release-create.yml +0 -100
- moai_adk/templates/.github/workflows/moai-release-pipeline.yml +0 -188
- moai_adk/utils/user_experience.py +0 -531
- moai_adk-0.25.4.dist-info/METADATA +0 -2279
- moai_adk-0.25.4.dist-info/RECORD +0 -112
- {moai_adk-0.25.4.dist-info โ moai_adk-0.32.8.dist-info}/licenses/LICENSE +0 -0
moai_adk/cli/commands/update.py
CHANGED
|
@@ -44,17 +44,26 @@ from __future__ import annotations
|
|
|
44
44
|
|
|
45
45
|
import json
|
|
46
46
|
import logging
|
|
47
|
+
import shutil
|
|
47
48
|
import subprocess
|
|
48
49
|
from datetime import datetime
|
|
49
50
|
from pathlib import Path
|
|
50
|
-
from typing import Any, cast
|
|
51
|
+
from typing import Any, Union, cast
|
|
51
52
|
|
|
52
53
|
import click
|
|
54
|
+
import yaml
|
|
53
55
|
from packaging import version
|
|
54
56
|
from rich.console import Console
|
|
55
57
|
|
|
56
58
|
from moai_adk import __version__
|
|
59
|
+
from moai_adk.core.merge import MergeAnalyzer
|
|
57
60
|
from moai_adk.core.migration import VersionMigrator
|
|
61
|
+
from moai_adk.core.migration.alfred_to_moai_migrator import AlfredToMoaiMigrator
|
|
62
|
+
|
|
63
|
+
# Import new custom element restoration modules
|
|
64
|
+
from moai_adk.core.migration.custom_element_scanner import create_custom_element_scanner
|
|
65
|
+
from moai_adk.core.migration.selective_restorer import create_selective_restorer
|
|
66
|
+
from moai_adk.core.migration.user_selection_ui import create_user_selection_ui
|
|
58
67
|
from moai_adk.core.template.processor import TemplateProcessor
|
|
59
68
|
|
|
60
69
|
console = Console()
|
|
@@ -98,6 +107,45 @@ class TemplateSyncError(UpdateError):
|
|
|
98
107
|
pass
|
|
99
108
|
|
|
100
109
|
|
|
110
|
+
def _get_config_path(project_path: Path) -> tuple[Path, bool]:
|
|
111
|
+
"""Get config file path, preferring YAML over JSON.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Tuple of (config_path, is_yaml)
|
|
115
|
+
"""
|
|
116
|
+
yaml_path = project_path / ".moai" / "config" / "config.yaml"
|
|
117
|
+
json_path = project_path / ".moai" / "config" / "config.json"
|
|
118
|
+
|
|
119
|
+
if yaml_path.exists():
|
|
120
|
+
return yaml_path, True
|
|
121
|
+
return json_path, False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _load_config(config_path: Path) -> dict[str, Any]:
|
|
125
|
+
"""Load config from YAML or JSON file."""
|
|
126
|
+
if not config_path.exists():
|
|
127
|
+
return {}
|
|
128
|
+
|
|
129
|
+
is_yaml = config_path.suffix in (".yaml", ".yml")
|
|
130
|
+
content = config_path.read_text(encoding="utf-8")
|
|
131
|
+
|
|
132
|
+
if is_yaml:
|
|
133
|
+
return yaml.safe_load(content) or {}
|
|
134
|
+
return json.loads(content)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _save_config(config_path: Path, config_data: dict[str, Any]) -> None:
|
|
138
|
+
"""Save config to YAML or JSON file."""
|
|
139
|
+
is_yaml = config_path.suffix in (".yaml", ".yml")
|
|
140
|
+
|
|
141
|
+
if is_yaml:
|
|
142
|
+
content = yaml.safe_dump(config_data, default_flow_style=False, allow_unicode=True, sort_keys=False)
|
|
143
|
+
else:
|
|
144
|
+
content = json.dumps(config_data, indent=2, ensure_ascii=False) + "\n"
|
|
145
|
+
|
|
146
|
+
config_path.write_text(content, encoding="utf-8")
|
|
147
|
+
|
|
148
|
+
|
|
101
149
|
def _is_installed_via_uv_tool() -> bool:
|
|
102
150
|
"""Check if moai-adk installed via uv tool.
|
|
103
151
|
|
|
@@ -220,9 +268,7 @@ def _get_latest_version() -> str:
|
|
|
220
268
|
import urllib.request
|
|
221
269
|
|
|
222
270
|
url = "https://pypi.org/pypi/moai-adk/json"
|
|
223
|
-
with urllib.request.urlopen(
|
|
224
|
-
url, timeout=5
|
|
225
|
-
) as response: # nosec B310 - URL is hardcoded HTTPS to PyPI API, no user input
|
|
271
|
+
with urllib.request.urlopen(url, timeout=5) as response: # nosec B310 - URL is hardcoded HTTPS to PyPI API, no user input
|
|
226
272
|
data = json.loads(response.read().decode("utf-8"))
|
|
227
273
|
return cast(str, data["info"]["version"])
|
|
228
274
|
except (urllib.error.URLError, json.JSONDecodeError, KeyError, TimeoutError) as e:
|
|
@@ -280,42 +326,398 @@ def _get_project_config_version(project_path: Path) -> str:
|
|
|
280
326
|
Returns "0.0.0" if template_version field not found (indicates no prior sync)
|
|
281
327
|
|
|
282
328
|
Raises:
|
|
283
|
-
ValueError: If config
|
|
329
|
+
ValueError: If config file exists but cannot be parsed
|
|
284
330
|
"""
|
|
285
331
|
|
|
286
|
-
def
|
|
332
|
+
def _is_placeholder_val(value: str) -> bool:
|
|
287
333
|
"""Check if value contains unsubstituted template placeholders."""
|
|
288
|
-
return (
|
|
289
|
-
isinstance(value, str) and value.startswith("{{") and value.endswith("}}")
|
|
290
|
-
)
|
|
334
|
+
return isinstance(value, str) and value.startswith("{{") and value.endswith("}}")
|
|
291
335
|
|
|
292
|
-
config_path = project_path
|
|
336
|
+
config_path, _ = _get_config_path(project_path)
|
|
293
337
|
|
|
294
338
|
if not config_path.exists():
|
|
295
339
|
# No config yet, treat as version 0.0.0 (needs initial sync)
|
|
296
340
|
return "0.0.0"
|
|
297
341
|
|
|
298
342
|
try:
|
|
299
|
-
config_data =
|
|
343
|
+
config_data = _load_config(config_path)
|
|
300
344
|
# Check for template_version in project section
|
|
301
345
|
template_version = config_data.get("project", {}).get("template_version")
|
|
302
|
-
if template_version and not
|
|
346
|
+
if template_version and not _is_placeholder_val(template_version):
|
|
303
347
|
return template_version
|
|
304
348
|
|
|
305
349
|
# Fallback to moai version if no template_version exists
|
|
306
350
|
moai_version = config_data.get("moai", {}).get("version")
|
|
307
|
-
if moai_version and not
|
|
351
|
+
if moai_version and not _is_placeholder_val(moai_version):
|
|
308
352
|
return moai_version
|
|
309
353
|
|
|
310
354
|
# If values are placeholders or don't exist, treat as uninitialized (0.0.0 triggers sync)
|
|
311
355
|
return "0.0.0"
|
|
312
|
-
except json.JSONDecodeError as e:
|
|
313
|
-
raise ValueError(f"Failed to parse project config
|
|
356
|
+
except (json.JSONDecodeError, yaml.YAMLError) as e:
|
|
357
|
+
raise ValueError(f"Failed to parse project config: {e}") from e
|
|
314
358
|
|
|
315
359
|
|
|
316
|
-
def
|
|
317
|
-
|
|
318
|
-
|
|
360
|
+
def _ask_merge_strategy(yes: bool = False) -> str:
|
|
361
|
+
"""
|
|
362
|
+
Ask user to choose merge strategy via CLI prompt.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
yes: If True, auto-select "auto" (for --yes flag)
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
"auto" or "manual"
|
|
369
|
+
"""
|
|
370
|
+
if yes:
|
|
371
|
+
return "auto"
|
|
372
|
+
|
|
373
|
+
console.print("\n[cyan]๐ Choose merge strategy:[/cyan]")
|
|
374
|
+
console.print("[cyan] [1] Auto-merge (default)[/cyan]")
|
|
375
|
+
console.print("[dim] โ Template installs fresh + user changes preserved + minimal conflicts[/dim]")
|
|
376
|
+
console.print("[cyan] [2] Manual merge[/cyan]")
|
|
377
|
+
console.print("[dim] โ Backup preserved + merge guide generated + you control merging[/dim]")
|
|
378
|
+
|
|
379
|
+
response = click.prompt("Select [1 or 2]", default="1")
|
|
380
|
+
if response == "2":
|
|
381
|
+
return "manual"
|
|
382
|
+
return "auto"
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _generate_manual_merge_guide(backup_path: Path, template_path: Path, project_path: Path) -> Path:
|
|
386
|
+
"""
|
|
387
|
+
Generate comprehensive merge guide for manual merging.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
backup_path: Path to backup directory
|
|
391
|
+
template_path: Path to template directory
|
|
392
|
+
project_path: Project root path
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
Path to generated merge guide
|
|
396
|
+
"""
|
|
397
|
+
guide_dir = project_path / ".moai" / "guides"
|
|
398
|
+
guide_dir.mkdir(parents=True, exist_ok=True)
|
|
399
|
+
|
|
400
|
+
guide_path = guide_dir / "merge-guide.md"
|
|
401
|
+
|
|
402
|
+
# Find changed files
|
|
403
|
+
changed_files = []
|
|
404
|
+
backup_claude = backup_path / ".claude"
|
|
405
|
+
backup_path / ".moai"
|
|
406
|
+
|
|
407
|
+
# Compare .claude/
|
|
408
|
+
if backup_claude.exists():
|
|
409
|
+
for file in backup_claude.rglob("*"):
|
|
410
|
+
if file.is_file():
|
|
411
|
+
rel_path = file.relative_to(backup_path)
|
|
412
|
+
current_file = project_path / rel_path
|
|
413
|
+
if current_file.exists():
|
|
414
|
+
if file.read_text(encoding="utf-8", errors="ignore") != current_file.read_text(
|
|
415
|
+
encoding="utf-8", errors="ignore"
|
|
416
|
+
):
|
|
417
|
+
changed_files.append(f" - {rel_path}")
|
|
418
|
+
else:
|
|
419
|
+
changed_files.append(f" - {rel_path} (new)")
|
|
420
|
+
|
|
421
|
+
# Generate guide
|
|
422
|
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
423
|
+
guide_content = f"""# Merge Guide - Manual Merge Mode
|
|
424
|
+
|
|
425
|
+
**Generated**: {timestamp}
|
|
426
|
+
**Backup Location**: `{backup_path.relative_to(project_path)}/`
|
|
427
|
+
|
|
428
|
+
## Summary
|
|
429
|
+
|
|
430
|
+
During this update, the following files were changed:
|
|
431
|
+
|
|
432
|
+
{chr(10).join(changed_files) if changed_files else " (No changes detected)"}
|
|
433
|
+
|
|
434
|
+
## How to Merge
|
|
435
|
+
|
|
436
|
+
### Option 1: Using diff (Terminal)
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
# Compare specific files
|
|
440
|
+
diff {backup_path.name}/.claude/settings.json .claude/settings.json
|
|
441
|
+
|
|
442
|
+
# View all differences
|
|
443
|
+
diff -r {backup_path.name}/ .
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Option 2: Using Visual Merge Tool
|
|
447
|
+
|
|
448
|
+
```bash
|
|
449
|
+
# macOS/Linux - Using meld
|
|
450
|
+
meld {backup_path.relative_to(project_path)}/ .
|
|
451
|
+
|
|
452
|
+
# Using VSCode
|
|
453
|
+
code --diff {backup_path.relative_to(project_path)}/.claude/settings.json .claude/settings.json
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Option 3: Manual Line-by-Line
|
|
457
|
+
|
|
458
|
+
1. Open backup file in your editor
|
|
459
|
+
2. Open current file side-by-side
|
|
460
|
+
3. Manually copy your customizations
|
|
461
|
+
|
|
462
|
+
## Key Files to Review
|
|
463
|
+
|
|
464
|
+
### .claude/settings.json
|
|
465
|
+
- Contains MCP servers, hooks, environment variables
|
|
466
|
+
- **Action**: Restore any custom MCP servers and environment variables
|
|
467
|
+
- **Location**: {backup_path.relative_to(project_path)}/.claude/settings.json
|
|
468
|
+
|
|
469
|
+
### .moai/config/config.json
|
|
470
|
+
- Contains project configuration and metadata
|
|
471
|
+
- **Action**: Verify user-specific settings are preserved
|
|
472
|
+
- **Location**: {backup_path.relative_to(project_path)}/.moai/config/config.json
|
|
473
|
+
|
|
474
|
+
### .claude/commands/, .claude/agents/, .claude/hooks/
|
|
475
|
+
- Contains custom scripts and automation
|
|
476
|
+
- **Action**: Restore any custom scripts outside of /moai/ folders
|
|
477
|
+
- **Location**: {backup_path.relative_to(project_path)}/.claude/
|
|
478
|
+
|
|
479
|
+
## Migration Checklist
|
|
480
|
+
|
|
481
|
+
- [ ] Compare `.claude/settings.json`
|
|
482
|
+
- [ ] Restore custom MCP servers
|
|
483
|
+
- [ ] Restore environment variables
|
|
484
|
+
- [ ] Verify hooks are properly configured
|
|
485
|
+
|
|
486
|
+
- [ ] Review `.moai/config/config.json`
|
|
487
|
+
- [ ] Check version was updated
|
|
488
|
+
- [ ] Verify user settings preserved
|
|
489
|
+
|
|
490
|
+
- [ ] Restore custom scripts
|
|
491
|
+
- [ ] Any custom commands outside /moai/
|
|
492
|
+
- [ ] Any custom agents outside /moai/
|
|
493
|
+
- [ ] Any custom hooks outside /moai/
|
|
494
|
+
|
|
495
|
+
- [ ] Run tests
|
|
496
|
+
```bash
|
|
497
|
+
uv run pytest
|
|
498
|
+
moai-adk validate
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
- [ ] Commit changes
|
|
502
|
+
```bash
|
|
503
|
+
git add .
|
|
504
|
+
git commit -m "merge: Update templates with manual merge"
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
## Rollback if Needed
|
|
508
|
+
|
|
509
|
+
If you want to cancel and restore the backup:
|
|
510
|
+
|
|
511
|
+
```bash
|
|
512
|
+
# Restore everything from backup
|
|
513
|
+
cp -r {backup_path.relative_to(project_path)}/.claude .
|
|
514
|
+
cp -r {backup_path.relative_to(project_path)}/.moai .
|
|
515
|
+
cp {backup_path.relative_to(project_path)}/CLAUDE.md .
|
|
516
|
+
|
|
517
|
+
# Or restore specific files
|
|
518
|
+
cp {backup_path.relative_to(project_path)}/.claude/settings.json .claude/
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## Questions?
|
|
522
|
+
|
|
523
|
+
If you encounter merge conflicts or issues:
|
|
524
|
+
|
|
525
|
+
1. Check the backup folder for original files
|
|
526
|
+
2. Compare line-by-line using diff tools
|
|
527
|
+
3. Consult documentation: https://adk.mo.ai.kr/update-merge
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
**Backup**: `{backup_path}/`
|
|
532
|
+
**Generated**: {timestamp}
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
guide_path.write_text(guide_content, encoding="utf-8")
|
|
536
|
+
logger.info(f"โ
Merge guide created: {guide_path}")
|
|
537
|
+
return guide_path
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def _migrate_legacy_logs(project_path: Path, dry_run: bool = False) -> bool:
|
|
541
|
+
"""Migrate legacy log files to unified directory structure.
|
|
542
|
+
|
|
543
|
+
Creates new unified directory structure (.moai/docs/, .moai/logs/archive/) and
|
|
544
|
+
migrates files from legacy locations to new unified structure:
|
|
545
|
+
- .moai/memory/last-session-state.json โ .moai/logs/sessions/
|
|
546
|
+
- .moai/error_logs/ โ .moai/logs/errors/
|
|
547
|
+
- .moai/reports/ โ .moai/docs/reports/
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
project_path: Project directory path (absolute)
|
|
551
|
+
dry_run: If True, only simulate migration without making changes
|
|
552
|
+
|
|
553
|
+
Returns:
|
|
554
|
+
True if migration succeeded or no migration needed, False otherwise
|
|
555
|
+
|
|
556
|
+
Raises:
|
|
557
|
+
Exception: If migration fails during actual execution
|
|
558
|
+
"""
|
|
559
|
+
try:
|
|
560
|
+
# Define source and target directories
|
|
561
|
+
legacy_memory = project_path / ".moai" / "memory"
|
|
562
|
+
legacy_error_logs = project_path / ".moai" / "error_logs"
|
|
563
|
+
legacy_reports = project_path / ".moai" / "reports"
|
|
564
|
+
|
|
565
|
+
# Create new unified directory structure
|
|
566
|
+
new_logs_dir = project_path / ".moai" / "logs"
|
|
567
|
+
new_docs_dir = project_path / ".moai" / "docs"
|
|
568
|
+
new_sessions_dir = new_logs_dir / "sessions"
|
|
569
|
+
new_errors_dir = new_logs_dir / "errors"
|
|
570
|
+
new_archive_dir = new_logs_dir / "archive"
|
|
571
|
+
new_docs_reports_dir = new_docs_dir / "reports"
|
|
572
|
+
|
|
573
|
+
migration_log = []
|
|
574
|
+
files_migrated = 0
|
|
575
|
+
files_skipped = 0
|
|
576
|
+
|
|
577
|
+
# Check if any legacy directories exist
|
|
578
|
+
has_legacy_files = legacy_memory.exists() or legacy_error_logs.exists() or legacy_reports.exists()
|
|
579
|
+
|
|
580
|
+
if not has_legacy_files:
|
|
581
|
+
if not dry_run:
|
|
582
|
+
# Create new directory structure anyway for consistency
|
|
583
|
+
new_logs_dir.mkdir(parents=True, exist_ok=True)
|
|
584
|
+
new_docs_dir.mkdir(parents=True, exist_ok=True)
|
|
585
|
+
new_sessions_dir.mkdir(parents=True, exist_ok=True)
|
|
586
|
+
new_errors_dir.mkdir(parents=True, exist_ok=True)
|
|
587
|
+
new_archive_dir.mkdir(parents=True, exist_ok=True)
|
|
588
|
+
new_docs_reports_dir.mkdir(parents=True, exist_ok=True)
|
|
589
|
+
return True
|
|
590
|
+
|
|
591
|
+
if dry_run:
|
|
592
|
+
console.print("[cyan]๐ Legacy log migration (dry run):[/cyan]")
|
|
593
|
+
|
|
594
|
+
# Create new directories if not dry run
|
|
595
|
+
if not dry_run:
|
|
596
|
+
new_logs_dir.mkdir(parents=True, exist_ok=True)
|
|
597
|
+
new_docs_dir.mkdir(parents=True, exist_ok=True)
|
|
598
|
+
new_sessions_dir.mkdir(parents=True, exist_ok=True)
|
|
599
|
+
new_errors_dir.mkdir(parents=True, exist_ok=True)
|
|
600
|
+
new_archive_dir.mkdir(parents=True, exist_ok=True)
|
|
601
|
+
new_docs_reports_dir.mkdir(parents=True, exist_ok=True)
|
|
602
|
+
|
|
603
|
+
# Migration 1: .moai/memory/last-session-state.json โ .moai/logs/sessions/
|
|
604
|
+
if legacy_memory.exists():
|
|
605
|
+
session_file = legacy_memory / "last-session-state.json"
|
|
606
|
+
if session_file.exists():
|
|
607
|
+
target_file = new_sessions_dir / "last-session-state.json"
|
|
608
|
+
|
|
609
|
+
if target_file.exists():
|
|
610
|
+
files_skipped += 1
|
|
611
|
+
migration_log.append(f"Skipped: {session_file.relative_to(project_path)} (target already exists)")
|
|
612
|
+
else:
|
|
613
|
+
if not dry_run:
|
|
614
|
+
shutil.copy2(session_file, target_file)
|
|
615
|
+
# Preserve original timestamp
|
|
616
|
+
shutil.copystat(session_file, target_file)
|
|
617
|
+
src_path = session_file.relative_to(project_path)
|
|
618
|
+
dst_path = target_file.relative_to(project_path)
|
|
619
|
+
migration_log.append(f"Migrated: {src_path} โ {dst_path}")
|
|
620
|
+
else:
|
|
621
|
+
src_path = session_file.relative_to(project_path)
|
|
622
|
+
dst_path = target_file.relative_to(project_path)
|
|
623
|
+
migration_log.append(f"Would migrate: {src_path} โ {dst_path}")
|
|
624
|
+
files_migrated += 1
|
|
625
|
+
|
|
626
|
+
# Migration 2: .moai/error_logs/ โ .moai/logs/errors/
|
|
627
|
+
if legacy_error_logs.exists() and legacy_error_logs.is_dir():
|
|
628
|
+
for error_file in legacy_error_logs.rglob("*"):
|
|
629
|
+
if error_file.is_file():
|
|
630
|
+
relative_path = error_file.relative_to(legacy_error_logs)
|
|
631
|
+
target_file = new_errors_dir / relative_path
|
|
632
|
+
|
|
633
|
+
# Ensure target directory exists
|
|
634
|
+
if not dry_run:
|
|
635
|
+
target_file.parent.mkdir(parents=True, exist_ok=True)
|
|
636
|
+
|
|
637
|
+
if target_file.exists():
|
|
638
|
+
files_skipped += 1
|
|
639
|
+
error_path = error_file.relative_to(project_path)
|
|
640
|
+
migration_log.append(f"Skipped: {error_path} (target already exists)")
|
|
641
|
+
else:
|
|
642
|
+
if not dry_run:
|
|
643
|
+
shutil.copy2(error_file, target_file)
|
|
644
|
+
shutil.copystat(error_file, target_file)
|
|
645
|
+
error_path = error_file.relative_to(project_path)
|
|
646
|
+
target_path = target_file.relative_to(project_path)
|
|
647
|
+
migration_log.append(f"Migrated: {error_path} โ {target_path}")
|
|
648
|
+
else:
|
|
649
|
+
error_path = error_file.relative_to(project_path)
|
|
650
|
+
target_path = target_file.relative_to(project_path)
|
|
651
|
+
migration_log.append(f"Would migrate: {error_path} โ {target_path}")
|
|
652
|
+
files_migrated += 1
|
|
653
|
+
|
|
654
|
+
# Migration 3: .moai/reports/ โ .moai/docs/reports/
|
|
655
|
+
if legacy_reports.exists() and legacy_reports.is_dir():
|
|
656
|
+
for report_file in legacy_reports.rglob("*"):
|
|
657
|
+
if report_file.is_file():
|
|
658
|
+
relative_path = report_file.relative_to(legacy_reports)
|
|
659
|
+
target_file = new_docs_reports_dir / relative_path
|
|
660
|
+
|
|
661
|
+
# Ensure target directory exists
|
|
662
|
+
if not dry_run:
|
|
663
|
+
target_file.parent.mkdir(parents=True, exist_ok=True)
|
|
664
|
+
|
|
665
|
+
if target_file.exists():
|
|
666
|
+
files_skipped += 1
|
|
667
|
+
report_path = report_file.relative_to(project_path)
|
|
668
|
+
migration_log.append(f"Skipped: {report_path} (target already exists)")
|
|
669
|
+
else:
|
|
670
|
+
if not dry_run:
|
|
671
|
+
shutil.copy2(report_file, target_file)
|
|
672
|
+
shutil.copystat(report_file, target_file)
|
|
673
|
+
report_path = report_file.relative_to(project_path)
|
|
674
|
+
target_path = target_file.relative_to(project_path)
|
|
675
|
+
migration_log.append(f"Migrated: {report_path} โ {target_path}")
|
|
676
|
+
else:
|
|
677
|
+
report_path = report_file.relative_to(project_path)
|
|
678
|
+
target_path = target_file.relative_to(project_path)
|
|
679
|
+
migration_log.append(f"Would migrate: {report_path} โ {target_path}")
|
|
680
|
+
files_migrated += 1
|
|
681
|
+
|
|
682
|
+
# Create migration log
|
|
683
|
+
migration_log_path = new_logs_dir / "migration-log.json"
|
|
684
|
+
if not dry_run and files_migrated > 0:
|
|
685
|
+
migration_data = {
|
|
686
|
+
"migration_timestamp": datetime.now().isoformat(),
|
|
687
|
+
"moai_adk_version": __version__,
|
|
688
|
+
"files_migrated": files_migrated,
|
|
689
|
+
"files_skipped": files_skipped,
|
|
690
|
+
"migration_log": migration_log,
|
|
691
|
+
"legacy_directories_found": [
|
|
692
|
+
str(d.relative_to(project_path))
|
|
693
|
+
for d in [legacy_memory, legacy_error_logs, legacy_reports]
|
|
694
|
+
if d.exists()
|
|
695
|
+
],
|
|
696
|
+
}
|
|
697
|
+
json_content = json.dumps(migration_data, indent=2, ensure_ascii=False)
|
|
698
|
+
migration_log_path.write_text(json_content + "\n", encoding="utf-8")
|
|
699
|
+
|
|
700
|
+
# Display results
|
|
701
|
+
if files_migrated > 0 or files_skipped > 0:
|
|
702
|
+
if dry_run:
|
|
703
|
+
console.print(f" [yellow]Would migrate {files_migrated} files, skip {files_skipped} files[/yellow]")
|
|
704
|
+
else:
|
|
705
|
+
console.print(f" [green]โ Migrated {files_migrated} legacy log files[/green]")
|
|
706
|
+
if files_skipped > 0:
|
|
707
|
+
console.print(f" [yellow]โ Skipped {files_skipped} files (already exist)[/yellow]")
|
|
708
|
+
console.print(f" [dim] Migration log: {migration_log_path.relative_to(project_path)}[/dim]")
|
|
709
|
+
elif has_legacy_files:
|
|
710
|
+
console.print(" [dim] No files to migrate[/dim]")
|
|
711
|
+
|
|
712
|
+
return True
|
|
713
|
+
|
|
714
|
+
except Exception as e:
|
|
715
|
+
console.print(f" [red]โ Log migration failed: {e}[/red]")
|
|
716
|
+
logger.error(f"Legacy log migration failed: {e}", exc_info=True)
|
|
717
|
+
return False
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def _detect_stale_cache(upgrade_output: str, current_version: str, latest_version: str) -> bool:
|
|
319
721
|
"""
|
|
320
722
|
Detect if uv cache is stale by comparing versions.
|
|
321
723
|
|
|
@@ -410,9 +812,7 @@ def _clear_uv_package_cache(package_name: str = "moai-adk") -> bool:
|
|
|
410
812
|
return False
|
|
411
813
|
|
|
412
814
|
|
|
413
|
-
def _execute_upgrade_with_retry(
|
|
414
|
-
installer_cmd: list[str], package_name: str = "moai-adk"
|
|
415
|
-
) -> bool:
|
|
815
|
+
def _execute_upgrade_with_retry(installer_cmd: list[str], package_name: str = "moai-adk") -> bool:
|
|
416
816
|
"""
|
|
417
817
|
Execute upgrade with automatic cache retry on stale detection.
|
|
418
818
|
|
|
@@ -459,9 +859,7 @@ def _execute_upgrade_with_retry(
|
|
|
459
859
|
"""
|
|
460
860
|
# Stage 1: First upgrade attempt
|
|
461
861
|
try:
|
|
462
|
-
result = subprocess.run(
|
|
463
|
-
installer_cmd, capture_output=True, text=True, timeout=60, check=False
|
|
464
|
-
)
|
|
862
|
+
result = subprocess.run(installer_cmd, capture_output=True, text=True, timeout=60, check=False)
|
|
465
863
|
except subprocess.TimeoutExpired:
|
|
466
864
|
raise # Re-raise timeout for caller to handle
|
|
467
865
|
except Exception:
|
|
@@ -530,9 +928,7 @@ def _execute_upgrade(installer_cmd: list[str]) -> bool:
|
|
|
530
928
|
subprocess.TimeoutExpired: If upgrade times out
|
|
531
929
|
"""
|
|
532
930
|
try:
|
|
533
|
-
result = subprocess.run(
|
|
534
|
-
installer_cmd, capture_output=True, text=True, timeout=60, check=False
|
|
535
|
-
)
|
|
931
|
+
result = subprocess.run(installer_cmd, capture_output=True, text=True, timeout=60, check=False)
|
|
536
932
|
return result.returncode == 0
|
|
537
933
|
except subprocess.TimeoutExpired:
|
|
538
934
|
raise # Re-raise timeout for caller to handle
|
|
@@ -540,12 +936,539 @@ def _execute_upgrade(installer_cmd: list[str]) -> bool:
|
|
|
540
936
|
return False
|
|
541
937
|
|
|
542
938
|
|
|
543
|
-
def
|
|
939
|
+
def _preserve_user_settings(project_path: Path) -> dict[str, Path | None]:
|
|
940
|
+
"""Back up user-specific settings files before template sync.
|
|
941
|
+
|
|
942
|
+
Args:
|
|
943
|
+
project_path: Project directory path
|
|
944
|
+
|
|
945
|
+
Returns:
|
|
946
|
+
Dictionary with backup paths of preserved files
|
|
947
|
+
"""
|
|
948
|
+
preserved = {}
|
|
949
|
+
claude_dir = project_path / ".claude"
|
|
950
|
+
|
|
951
|
+
# Preserve settings.local.json (user MCP and GLM configuration)
|
|
952
|
+
settings_local = claude_dir / "settings.local.json"
|
|
953
|
+
if settings_local.exists():
|
|
954
|
+
try:
|
|
955
|
+
backup_dir = project_path / ".moai-backups" / "settings-backup"
|
|
956
|
+
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
957
|
+
backup_path = backup_dir / "settings.local.json"
|
|
958
|
+
backup_path.write_text(settings_local.read_text(encoding="utf-8"))
|
|
959
|
+
preserved["settings.local.json"] = backup_path
|
|
960
|
+
console.print(" [cyan]๐พ Backed up user settings[/cyan]")
|
|
961
|
+
except Exception as e:
|
|
962
|
+
logger.warning(f"Failed to backup settings.local.json: {e}")
|
|
963
|
+
preserved["settings.local.json"] = None
|
|
964
|
+
else:
|
|
965
|
+
preserved["settings.local.json"] = None
|
|
966
|
+
|
|
967
|
+
return preserved
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
def _restore_user_settings(project_path: Path, preserved: dict[str, Path | None]) -> bool:
|
|
971
|
+
"""Restore user-specific settings files after template sync.
|
|
972
|
+
|
|
973
|
+
Args:
|
|
974
|
+
project_path: Project directory path
|
|
975
|
+
preserved: Dictionary of backup paths from _preserve_user_settings()
|
|
976
|
+
|
|
977
|
+
Returns:
|
|
978
|
+
True if restoration succeeded, False otherwise
|
|
979
|
+
"""
|
|
980
|
+
claude_dir = project_path / ".claude"
|
|
981
|
+
claude_dir.mkdir(parents=True, exist_ok=True)
|
|
982
|
+
|
|
983
|
+
success = True
|
|
984
|
+
|
|
985
|
+
# Restore settings.local.json
|
|
986
|
+
if preserved.get("settings.local.json"):
|
|
987
|
+
try:
|
|
988
|
+
backup_path = preserved["settings.local.json"]
|
|
989
|
+
settings_local = claude_dir / "settings.local.json"
|
|
990
|
+
settings_local.write_text(backup_path.read_text(encoding="utf-8"))
|
|
991
|
+
console.print(" [cyan]โ Restored user settings[/cyan]")
|
|
992
|
+
except Exception as e:
|
|
993
|
+
console.print(f" [yellow]โ ๏ธ Failed to restore settings.local.json: {e}[/yellow]")
|
|
994
|
+
logger.warning(f"Failed to restore settings.local.json: {e}")
|
|
995
|
+
success = False
|
|
996
|
+
|
|
997
|
+
return success
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
def _get_template_skill_names() -> set[str]:
|
|
1001
|
+
"""Get set of skill folder names from installed template.
|
|
1002
|
+
|
|
1003
|
+
Returns:
|
|
1004
|
+
Set of skill folder names that are part of the template package.
|
|
1005
|
+
"""
|
|
1006
|
+
template_path = Path(__file__).parent.parent.parent / "templates"
|
|
1007
|
+
skills_path = template_path / ".claude" / "skills"
|
|
1008
|
+
|
|
1009
|
+
if not skills_path.exists():
|
|
1010
|
+
return set()
|
|
1011
|
+
|
|
1012
|
+
return {d.name for d in skills_path.iterdir() if d.is_dir()}
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
def _get_template_command_names() -> set[str]:
|
|
1016
|
+
"""Get set of command file names from installed template.
|
|
1017
|
+
|
|
1018
|
+
Returns:
|
|
1019
|
+
Set of .md command file names from .claude/commands/moai/ in template.
|
|
1020
|
+
"""
|
|
1021
|
+
template_path = Path(__file__).parent.parent.parent / "templates"
|
|
1022
|
+
commands_path = template_path / ".claude" / "commands" / "moai"
|
|
1023
|
+
|
|
1024
|
+
if not commands_path.exists():
|
|
1025
|
+
return set()
|
|
1026
|
+
|
|
1027
|
+
return {f.name for f in commands_path.iterdir() if f.is_file() and f.suffix == ".md"}
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
def _get_template_agent_names() -> set[str]:
|
|
1031
|
+
"""Get set of agent file names from installed template.
|
|
1032
|
+
|
|
1033
|
+
Returns:
|
|
1034
|
+
Set of agent file names from .claude/agents/ in template.
|
|
1035
|
+
"""
|
|
1036
|
+
template_path = Path(__file__).parent.parent.parent / "templates"
|
|
1037
|
+
agents_path = template_path / ".claude" / "agents"
|
|
1038
|
+
|
|
1039
|
+
if not agents_path.exists():
|
|
1040
|
+
return set()
|
|
1041
|
+
|
|
1042
|
+
return {f.name for f in agents_path.iterdir() if f.is_file()}
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
def _get_template_hook_names() -> set[str]:
|
|
1046
|
+
"""Get set of hook file names from installed template.
|
|
1047
|
+
|
|
1048
|
+
Returns:
|
|
1049
|
+
Set of .py hook file names from .claude/hooks/moai/ in template.
|
|
1050
|
+
"""
|
|
1051
|
+
template_path = Path(__file__).parent.parent.parent / "templates"
|
|
1052
|
+
hooks_path = template_path / ".claude" / "hooks" / "moai"
|
|
1053
|
+
|
|
1054
|
+
if not hooks_path.exists():
|
|
1055
|
+
return set()
|
|
1056
|
+
|
|
1057
|
+
return {f.name for f in hooks_path.iterdir() if f.is_file() and f.suffix == ".py"}
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
def _detect_custom_commands(project_path: Path, template_commands: set[str]) -> list[str]:
|
|
1061
|
+
"""Detect custom commands NOT in template (user-created).
|
|
1062
|
+
|
|
1063
|
+
Args:
|
|
1064
|
+
project_path: Project path (absolute)
|
|
1065
|
+
template_commands: Set of template command file names
|
|
1066
|
+
|
|
1067
|
+
Returns:
|
|
1068
|
+
Sorted list of custom command file names.
|
|
1069
|
+
"""
|
|
1070
|
+
commands_path = project_path / ".claude" / "commands" / "moai"
|
|
1071
|
+
|
|
1072
|
+
if not commands_path.exists():
|
|
1073
|
+
return []
|
|
1074
|
+
|
|
1075
|
+
project_commands = {f.name for f in commands_path.iterdir() if f.is_file() and f.suffix == ".md"}
|
|
1076
|
+
custom_commands = project_commands - template_commands
|
|
1077
|
+
|
|
1078
|
+
return sorted(custom_commands)
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
def _detect_custom_agents(project_path: Path, template_agents: set[str]) -> list[str]:
|
|
1082
|
+
"""Detect custom agents NOT in template (user-created).
|
|
1083
|
+
|
|
1084
|
+
Args:
|
|
1085
|
+
project_path: Project path (absolute)
|
|
1086
|
+
template_agents: Set of template agent file names
|
|
1087
|
+
|
|
1088
|
+
Returns:
|
|
1089
|
+
Sorted list of custom agent file names.
|
|
1090
|
+
"""
|
|
1091
|
+
agents_path = project_path / ".claude" / "agents"
|
|
1092
|
+
|
|
1093
|
+
if not agents_path.exists():
|
|
1094
|
+
return []
|
|
1095
|
+
|
|
1096
|
+
project_agents = {f.name for f in agents_path.iterdir() if f.is_file()}
|
|
1097
|
+
custom_agents = project_agents - template_agents
|
|
1098
|
+
|
|
1099
|
+
return sorted(custom_agents)
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
def _detect_custom_hooks(project_path: Path, template_hooks: set[str]) -> list[str]:
|
|
1103
|
+
"""Detect custom hooks NOT in template (user-created).
|
|
1104
|
+
|
|
1105
|
+
Args:
|
|
1106
|
+
project_path: Project path (absolute)
|
|
1107
|
+
template_hooks: Set of template hook file names
|
|
1108
|
+
|
|
1109
|
+
Returns:
|
|
1110
|
+
Sorted list of custom hook file names.
|
|
1111
|
+
"""
|
|
1112
|
+
hooks_path = project_path / ".claude" / "hooks" / "moai"
|
|
1113
|
+
|
|
1114
|
+
if not hooks_path.exists():
|
|
1115
|
+
return []
|
|
1116
|
+
|
|
1117
|
+
project_hooks = {f.name for f in hooks_path.iterdir() if f.is_file() and f.suffix == ".py"}
|
|
1118
|
+
custom_hooks = project_hooks - template_hooks
|
|
1119
|
+
|
|
1120
|
+
return sorted(custom_hooks)
|
|
1121
|
+
|
|
1122
|
+
|
|
1123
|
+
def _group_custom_files_by_type(
|
|
1124
|
+
custom_commands: list[str],
|
|
1125
|
+
custom_agents: list[str],
|
|
1126
|
+
custom_hooks: list[str],
|
|
1127
|
+
) -> dict[str, list[str]]:
|
|
1128
|
+
"""Group custom files by type for UI display.
|
|
1129
|
+
|
|
1130
|
+
Args:
|
|
1131
|
+
custom_commands: List of custom command file names
|
|
1132
|
+
custom_agents: List of custom agent file names
|
|
1133
|
+
custom_hooks: List of custom hook file names
|
|
1134
|
+
|
|
1135
|
+
Returns:
|
|
1136
|
+
Dictionary with keys: commands, agents, hooks
|
|
1137
|
+
"""
|
|
1138
|
+
return {
|
|
1139
|
+
"commands": custom_commands,
|
|
1140
|
+
"agents": custom_agents,
|
|
1141
|
+
"hooks": custom_hooks,
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
def _prompt_custom_files_restore(
|
|
1146
|
+
custom_commands: list[str],
|
|
1147
|
+
custom_agents: list[str],
|
|
1148
|
+
custom_hooks: list[str],
|
|
1149
|
+
yes: bool = False,
|
|
1150
|
+
) -> dict[str, list[str]]:
|
|
1151
|
+
"""Interactive fuzzy checkbox for custom files restore with search support.
|
|
1152
|
+
|
|
1153
|
+
Args:
|
|
1154
|
+
custom_commands: List of custom command file names
|
|
1155
|
+
custom_agents: List of custom agent file names
|
|
1156
|
+
custom_hooks: List of custom hook file names
|
|
1157
|
+
yes: Auto-confirm flag (skips restoration in CI/CD mode)
|
|
1158
|
+
|
|
1159
|
+
Returns:
|
|
1160
|
+
Dictionary with selected files grouped by type.
|
|
1161
|
+
"""
|
|
1162
|
+
# If no custom files, skip UI
|
|
1163
|
+
if not (custom_commands or custom_agents or custom_hooks):
|
|
1164
|
+
return {
|
|
1165
|
+
"commands": [],
|
|
1166
|
+
"agents": [],
|
|
1167
|
+
"hooks": [],
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
# In --yes mode, skip restoration (safest default)
|
|
1171
|
+
if yes:
|
|
1172
|
+
console.print("\n[dim] Skipping custom files restoration (--yes mode)[/dim]\n")
|
|
1173
|
+
return {
|
|
1174
|
+
"commands": [],
|
|
1175
|
+
"agents": [],
|
|
1176
|
+
"hooks": [],
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
# Try to use new UI, fallback to questionary if import fails
|
|
1180
|
+
try:
|
|
1181
|
+
from moai_adk.cli.ui.prompts import create_grouped_choices, fuzzy_checkbox
|
|
1182
|
+
|
|
1183
|
+
# Build grouped choices for fuzzy checkbox
|
|
1184
|
+
groups: dict[str, list[dict[str, str]]] = {}
|
|
1185
|
+
|
|
1186
|
+
if custom_commands:
|
|
1187
|
+
groups["Commands (.claude/commands/moai/)"] = [
|
|
1188
|
+
{"name": cmd, "value": f"cmd:{cmd}"} for cmd in custom_commands
|
|
1189
|
+
]
|
|
1190
|
+
|
|
1191
|
+
if custom_agents:
|
|
1192
|
+
groups["Agents (.claude/agents/)"] = [{"name": agent, "value": f"agent:{agent}"} for agent in custom_agents]
|
|
1193
|
+
|
|
1194
|
+
if custom_hooks:
|
|
1195
|
+
groups["Hooks (.claude/hooks/moai/)"] = [{"name": hook, "value": f"hook:{hook}"} for hook in custom_hooks]
|
|
1196
|
+
|
|
1197
|
+
choices = create_grouped_choices(groups)
|
|
1198
|
+
|
|
1199
|
+
console.print("\n[#DA7756]๐ฆ Custom files detected in backup:[/#DA7756]")
|
|
1200
|
+
console.print("[dim] Use fuzzy search to find files quickly[/dim]\n")
|
|
1201
|
+
|
|
1202
|
+
selected = fuzzy_checkbox(
|
|
1203
|
+
"Select custom files to restore:",
|
|
1204
|
+
choices=choices,
|
|
1205
|
+
instruction="[Space] Toggle [Tab] All [Enter] Confirm [Type to search]",
|
|
1206
|
+
)
|
|
1207
|
+
|
|
1208
|
+
except ImportError:
|
|
1209
|
+
# Fallback to questionary if new UI not available
|
|
1210
|
+
import questionary
|
|
1211
|
+
from questionary import Choice, Separator
|
|
1212
|
+
|
|
1213
|
+
choices_legacy: list[Union[Separator, Choice]] = []
|
|
1214
|
+
|
|
1215
|
+
if custom_commands:
|
|
1216
|
+
choices_legacy.append(Separator("Commands (.claude/commands/moai/)"))
|
|
1217
|
+
for cmd in custom_commands:
|
|
1218
|
+
choices_legacy.append(Choice(title=cmd, value=f"cmd:{cmd}"))
|
|
1219
|
+
|
|
1220
|
+
if custom_agents:
|
|
1221
|
+
choices_legacy.append(Separator("Agents (.claude/agents/)"))
|
|
1222
|
+
for agent in custom_agents:
|
|
1223
|
+
choices_legacy.append(Choice(title=agent, value=f"agent:{agent}"))
|
|
1224
|
+
|
|
1225
|
+
if custom_hooks:
|
|
1226
|
+
choices_legacy.append(Separator("Hooks (.claude/hooks/moai/)"))
|
|
1227
|
+
for hook in custom_hooks:
|
|
1228
|
+
choices_legacy.append(Choice(title=hook, value=f"hook:{hook}"))
|
|
1229
|
+
|
|
1230
|
+
console.print("\n[cyan]๐ฆ Custom files detected in backup:[/cyan]")
|
|
1231
|
+
console.print("[dim] Select files to restore (none selected by default)[/dim]\n")
|
|
1232
|
+
|
|
1233
|
+
selected = questionary.checkbox(
|
|
1234
|
+
"Select custom files to restore:",
|
|
1235
|
+
choices=choices_legacy,
|
|
1236
|
+
).ask()
|
|
1237
|
+
|
|
1238
|
+
# Parse results
|
|
1239
|
+
result_commands = []
|
|
1240
|
+
result_agents = []
|
|
1241
|
+
result_hooks = []
|
|
1242
|
+
|
|
1243
|
+
if selected:
|
|
1244
|
+
for item in selected:
|
|
1245
|
+
if item.startswith("cmd:"):
|
|
1246
|
+
result_commands.append(item[4:])
|
|
1247
|
+
elif item.startswith("agent:"):
|
|
1248
|
+
result_agents.append(item[6:])
|
|
1249
|
+
elif item.startswith("hook:"):
|
|
1250
|
+
result_hooks.append(item[5:])
|
|
1251
|
+
|
|
1252
|
+
return {
|
|
1253
|
+
"commands": result_commands,
|
|
1254
|
+
"agents": result_agents,
|
|
1255
|
+
"hooks": result_hooks,
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
|
|
1259
|
+
def _restore_custom_files(
|
|
1260
|
+
project_path: Path,
|
|
1261
|
+
backup_path: Path,
|
|
1262
|
+
selected_commands: list[str],
|
|
1263
|
+
selected_agents: list[str],
|
|
1264
|
+
selected_hooks: list[str],
|
|
1265
|
+
) -> bool:
|
|
1266
|
+
"""Restore selected custom files from backup to project.
|
|
1267
|
+
|
|
1268
|
+
Args:
|
|
1269
|
+
project_path: Project directory path
|
|
1270
|
+
backup_path: Backup directory path
|
|
1271
|
+
selected_commands: List of command files to restore
|
|
1272
|
+
selected_agents: List of agent files to restore
|
|
1273
|
+
selected_hooks: List of hook files to restore
|
|
1274
|
+
|
|
1275
|
+
Returns:
|
|
1276
|
+
True if all restorations succeeded, False otherwise.
|
|
1277
|
+
"""
|
|
1278
|
+
import shutil
|
|
1279
|
+
|
|
1280
|
+
success = True
|
|
1281
|
+
|
|
1282
|
+
# Restore commands
|
|
1283
|
+
if selected_commands:
|
|
1284
|
+
commands_dst = project_path / ".claude" / "commands" / "moai"
|
|
1285
|
+
commands_dst.mkdir(parents=True, exist_ok=True)
|
|
1286
|
+
|
|
1287
|
+
for cmd_file in selected_commands:
|
|
1288
|
+
src = backup_path / ".claude" / "commands" / "moai" / cmd_file
|
|
1289
|
+
dst = commands_dst / cmd_file
|
|
1290
|
+
|
|
1291
|
+
if src.exists():
|
|
1292
|
+
try:
|
|
1293
|
+
shutil.copy2(src, dst)
|
|
1294
|
+
except Exception as e:
|
|
1295
|
+
logger.warning(f"Failed to restore command {cmd_file}: {e}")
|
|
1296
|
+
success = False
|
|
1297
|
+
else:
|
|
1298
|
+
logger.warning(f"Command file not in backup: {cmd_file}")
|
|
1299
|
+
success = False
|
|
1300
|
+
|
|
1301
|
+
# Restore agents
|
|
1302
|
+
if selected_agents:
|
|
1303
|
+
agents_dst = project_path / ".claude" / "agents"
|
|
1304
|
+
agents_dst.mkdir(parents=True, exist_ok=True)
|
|
1305
|
+
|
|
1306
|
+
for agent_file in selected_agents:
|
|
1307
|
+
src = backup_path / ".claude" / "agents" / agent_file
|
|
1308
|
+
dst = agents_dst / agent_file
|
|
1309
|
+
|
|
1310
|
+
if src.exists():
|
|
1311
|
+
try:
|
|
1312
|
+
shutil.copy2(src, dst)
|
|
1313
|
+
except Exception as e:
|
|
1314
|
+
logger.warning(f"Failed to restore agent {agent_file}: {e}")
|
|
1315
|
+
success = False
|
|
1316
|
+
else:
|
|
1317
|
+
logger.warning(f"Agent file not in backup: {agent_file}")
|
|
1318
|
+
success = False
|
|
1319
|
+
|
|
1320
|
+
# Restore hooks
|
|
1321
|
+
if selected_hooks:
|
|
1322
|
+
hooks_dst = project_path / ".claude" / "hooks" / "moai"
|
|
1323
|
+
hooks_dst.mkdir(parents=True, exist_ok=True)
|
|
1324
|
+
|
|
1325
|
+
for hook_file in selected_hooks:
|
|
1326
|
+
src = backup_path / ".claude" / "hooks" / "moai" / hook_file
|
|
1327
|
+
dst = hooks_dst / hook_file
|
|
1328
|
+
|
|
1329
|
+
if src.exists():
|
|
1330
|
+
try:
|
|
1331
|
+
shutil.copy2(src, dst)
|
|
1332
|
+
except Exception as e:
|
|
1333
|
+
logger.warning(f"Failed to restore hook {hook_file}: {e}")
|
|
1334
|
+
success = False
|
|
1335
|
+
else:
|
|
1336
|
+
logger.warning(f"Hook file not in backup: {hook_file}")
|
|
1337
|
+
success = False
|
|
1338
|
+
|
|
1339
|
+
return success
|
|
1340
|
+
|
|
1341
|
+
|
|
1342
|
+
def _detect_custom_skills(project_path: Path, template_skills: set[str]) -> list[str]:
|
|
1343
|
+
"""Detect skills NOT in template (user-created).
|
|
1344
|
+
|
|
1345
|
+
Args:
|
|
1346
|
+
project_path: Project path (absolute)
|
|
1347
|
+
template_skills: Set of template skill names
|
|
1348
|
+
|
|
1349
|
+
Returns:
|
|
1350
|
+
Sorted list of custom skill names.
|
|
1351
|
+
"""
|
|
1352
|
+
skills_path = project_path / ".claude" / "skills"
|
|
1353
|
+
|
|
1354
|
+
if not skills_path.exists():
|
|
1355
|
+
return []
|
|
1356
|
+
|
|
1357
|
+
project_skills = {d.name for d in skills_path.iterdir() if d.is_dir()}
|
|
1358
|
+
custom_skills = project_skills - template_skills
|
|
1359
|
+
|
|
1360
|
+
return sorted(custom_skills)
|
|
1361
|
+
|
|
1362
|
+
|
|
1363
|
+
def _prompt_skill_restore(custom_skills: list[str], yes: bool = False) -> list[str]:
|
|
1364
|
+
"""Interactive fuzzy checkbox for skill restore with search support.
|
|
1365
|
+
|
|
1366
|
+
Args:
|
|
1367
|
+
custom_skills: List of custom skill names
|
|
1368
|
+
yes: Auto-confirm flag (skips restoration in CI/CD mode)
|
|
1369
|
+
|
|
1370
|
+
Returns:
|
|
1371
|
+
List of skills user selected to restore.
|
|
1372
|
+
"""
|
|
1373
|
+
if not custom_skills:
|
|
1374
|
+
return []
|
|
1375
|
+
|
|
1376
|
+
console.print("\n[#DA7756]๐ฆ Custom skills detected in backup:[/#DA7756]")
|
|
1377
|
+
for skill in custom_skills:
|
|
1378
|
+
console.print(f" โข {skill}")
|
|
1379
|
+
console.print()
|
|
1380
|
+
|
|
1381
|
+
if yes:
|
|
1382
|
+
console.print("[dim] Skipping restoration (--yes mode)[/dim]\n")
|
|
1383
|
+
return []
|
|
1384
|
+
|
|
1385
|
+
# Try new UI, fallback to questionary
|
|
1386
|
+
try:
|
|
1387
|
+
from moai_adk.cli.ui.prompts import fuzzy_checkbox
|
|
1388
|
+
|
|
1389
|
+
choices = [{"name": skill, "value": skill} for skill in custom_skills]
|
|
1390
|
+
|
|
1391
|
+
selected = fuzzy_checkbox(
|
|
1392
|
+
"Select skills to restore (type to search):",
|
|
1393
|
+
choices=choices,
|
|
1394
|
+
instruction="[Space] Toggle [Tab] All [Enter] Confirm [Type to search]",
|
|
1395
|
+
)
|
|
1396
|
+
|
|
1397
|
+
except ImportError:
|
|
1398
|
+
import questionary
|
|
1399
|
+
|
|
1400
|
+
selected = questionary.checkbox(
|
|
1401
|
+
"Select skills to restore (none selected by default):",
|
|
1402
|
+
choices=[questionary.Choice(title=skill, checked=False) for skill in custom_skills],
|
|
1403
|
+
).ask()
|
|
1404
|
+
|
|
1405
|
+
return selected if selected else []
|
|
1406
|
+
|
|
1407
|
+
|
|
1408
|
+
def _restore_selected_skills(skills: list[str], backup_path: Path, project_path: Path) -> bool:
|
|
1409
|
+
"""Restore selected skills from backup.
|
|
1410
|
+
|
|
1411
|
+
Args:
|
|
1412
|
+
skills: List of skill names to restore
|
|
1413
|
+
backup_path: Backup directory path
|
|
1414
|
+
project_path: Project path (absolute)
|
|
1415
|
+
|
|
1416
|
+
Returns:
|
|
1417
|
+
True if all restorations succeeded.
|
|
1418
|
+
"""
|
|
1419
|
+
import shutil
|
|
1420
|
+
|
|
1421
|
+
if not skills:
|
|
1422
|
+
return True
|
|
1423
|
+
|
|
1424
|
+
console.print("\n[cyan]๐ฅ Restoring selected skills...[/cyan]")
|
|
1425
|
+
skills_dst = project_path / ".claude" / "skills"
|
|
1426
|
+
skills_dst.mkdir(parents=True, exist_ok=True)
|
|
1427
|
+
|
|
1428
|
+
success = True
|
|
1429
|
+
for skill_name in skills:
|
|
1430
|
+
src = backup_path / ".claude" / "skills" / skill_name
|
|
1431
|
+
dst = skills_dst / skill_name
|
|
1432
|
+
|
|
1433
|
+
if src.exists():
|
|
1434
|
+
try:
|
|
1435
|
+
shutil.copytree(src, dst, dirs_exist_ok=True)
|
|
1436
|
+
console.print(f" [green]โ Restored: {skill_name}[/green]")
|
|
1437
|
+
except Exception as e:
|
|
1438
|
+
console.print(f" [red]โ Failed: {skill_name} - {e}[/red]")
|
|
1439
|
+
success = False
|
|
1440
|
+
else:
|
|
1441
|
+
console.print(f" [yellow]โ Not in backup: {skill_name}[/yellow]")
|
|
1442
|
+
success = False
|
|
1443
|
+
|
|
1444
|
+
return success
|
|
1445
|
+
|
|
1446
|
+
|
|
1447
|
+
def _show_post_update_guidance(backup_path: Path) -> None:
|
|
1448
|
+
"""Show post-update guidance for running /moai:0-project update.
|
|
1449
|
+
|
|
1450
|
+
Args:
|
|
1451
|
+
backup_path: Backup directory path for reference
|
|
1452
|
+
"""
|
|
1453
|
+
console.print("\n" + "[cyan]" + "=" * 60 + "[/cyan]")
|
|
1454
|
+
console.print("[green]โ
Update complete![/green]")
|
|
1455
|
+
console.print("\n[yellow]๐ IMPORTANT - Next step:[/yellow]")
|
|
1456
|
+
console.print(" Run [cyan]/moai:0-project update[/cyan] in Claude Code")
|
|
1457
|
+
console.print("\n This will:")
|
|
1458
|
+
console.print(" โข Merge your settings with new templates")
|
|
1459
|
+
console.print(" โข Validate configuration compatibility")
|
|
1460
|
+
console.print("\n[dim]๐ก Personal instructions should go in CLAUDE.local.md[/dim]")
|
|
1461
|
+
console.print(f"[dim]๐ Backup location: {backup_path}[/dim]")
|
|
1462
|
+
console.print("[cyan]" + "=" * 60 + "[/cyan]\n")
|
|
1463
|
+
|
|
1464
|
+
|
|
1465
|
+
def _sync_templates(project_path: Path, force: bool = False, yes: bool = False) -> bool:
|
|
544
1466
|
"""Sync templates to project with rollback mechanism.
|
|
545
1467
|
|
|
546
1468
|
Args:
|
|
547
1469
|
project_path: Project path (absolute)
|
|
548
1470
|
force: Force update without backup
|
|
1471
|
+
yes: Auto-confirm flag (skips interactive prompts)
|
|
549
1472
|
|
|
550
1473
|
Returns:
|
|
551
1474
|
True if sync succeeded, False otherwise
|
|
@@ -554,6 +1477,20 @@ def _sync_templates(project_path: Path, force: bool = False) -> bool:
|
|
|
554
1477
|
|
|
555
1478
|
backup_path = None
|
|
556
1479
|
try:
|
|
1480
|
+
# NEW: Detect custom files and skills BEFORE backup/sync
|
|
1481
|
+
template_skills = _get_template_skill_names()
|
|
1482
|
+
_detect_custom_skills(project_path, template_skills)
|
|
1483
|
+
|
|
1484
|
+
# Detect custom commands, agents, and hooks
|
|
1485
|
+
template_commands = _get_template_command_names()
|
|
1486
|
+
_detect_custom_commands(project_path, template_commands)
|
|
1487
|
+
|
|
1488
|
+
template_agents = _get_template_agent_names()
|
|
1489
|
+
_detect_custom_agents(project_path, template_agents)
|
|
1490
|
+
|
|
1491
|
+
template_hooks = _get_template_hook_names()
|
|
1492
|
+
_detect_custom_hooks(project_path, template_hooks)
|
|
1493
|
+
|
|
557
1494
|
processor = TemplateProcessor(project_path)
|
|
558
1495
|
|
|
559
1496
|
# Create pre-sync backup for rollback
|
|
@@ -563,6 +1500,26 @@ def _sync_templates(project_path: Path, force: bool = False) -> bool:
|
|
|
563
1500
|
backup_path = backup.create_backup()
|
|
564
1501
|
console.print(f"๐พ Created backup: {backup_path.name}")
|
|
565
1502
|
|
|
1503
|
+
# Merge analysis using Claude Code headless mode
|
|
1504
|
+
try:
|
|
1505
|
+
analyzer = MergeAnalyzer(project_path)
|
|
1506
|
+
# Template source path from installed package
|
|
1507
|
+
template_path = Path(__file__).parent.parent.parent / "templates"
|
|
1508
|
+
|
|
1509
|
+
console.print("\n[cyan]๐ Starting merge analysis (max 2 mins)...[/cyan]")
|
|
1510
|
+
console.print("[dim] Analyzing intelligent merge with Claude Code.[/dim]")
|
|
1511
|
+
console.print("[dim] Please wait...[/dim]\n")
|
|
1512
|
+
analysis = analyzer.analyze_merge(backup_path, template_path)
|
|
1513
|
+
|
|
1514
|
+
# Ask user confirmation
|
|
1515
|
+
if not analyzer.ask_user_confirmation(analysis):
|
|
1516
|
+
console.print("[yellow]โ ๏ธ User cancelled the update.[/yellow]")
|
|
1517
|
+
backup.restore_backup(backup_path)
|
|
1518
|
+
return False
|
|
1519
|
+
except Exception as e:
|
|
1520
|
+
console.print(f"[yellow]โ ๏ธ Merge analysis failed: {e}[/yellow]")
|
|
1521
|
+
console.print("[yellow]Proceeding with automatic merge.[/yellow]")
|
|
1522
|
+
|
|
566
1523
|
# Load existing config
|
|
567
1524
|
existing_config = _load_existing_config(project_path)
|
|
568
1525
|
|
|
@@ -571,18 +1528,34 @@ def _sync_templates(project_path: Path, force: bool = False) -> bool:
|
|
|
571
1528
|
if context:
|
|
572
1529
|
processor.set_context(context)
|
|
573
1530
|
|
|
574
|
-
# Copy templates
|
|
1531
|
+
# Copy templates (including moai folder)
|
|
575
1532
|
processor.copy_templates(backup=False, silent=True)
|
|
576
1533
|
|
|
1534
|
+
# Stage 1.5: Alfred โ Moai migration (AFTER template sync)
|
|
1535
|
+
# Execute migration after template copy (moai folders must exist first)
|
|
1536
|
+
migrator = AlfredToMoaiMigrator(project_path)
|
|
1537
|
+
if migrator.needs_migration():
|
|
1538
|
+
console.print("\n[cyan]๐ Migrating folder structure: Alfred โ Moai[/cyan]")
|
|
1539
|
+
try:
|
|
1540
|
+
if not migrator.execute_migration(backup_path):
|
|
1541
|
+
console.print("[red]โ Alfred โ Moai migration failed[/red]")
|
|
1542
|
+
if backup_path:
|
|
1543
|
+
console.print("[yellow]๐ Restoring from backup...[/yellow]")
|
|
1544
|
+
backup = TemplateBackup(project_path)
|
|
1545
|
+
backup.restore_backup(backup_path)
|
|
1546
|
+
return False
|
|
1547
|
+
except Exception as e:
|
|
1548
|
+
console.print(f"[red]โ Error during migration: {e}[/red]")
|
|
1549
|
+
if backup_path:
|
|
1550
|
+
backup = TemplateBackup(project_path)
|
|
1551
|
+
backup.restore_backup(backup_path)
|
|
1552
|
+
return False
|
|
1553
|
+
|
|
577
1554
|
# Validate template substitution
|
|
578
|
-
validation_passed = _validate_template_substitution_with_rollback(
|
|
579
|
-
project_path, backup_path
|
|
580
|
-
)
|
|
1555
|
+
validation_passed = _validate_template_substitution_with_rollback(project_path, backup_path)
|
|
581
1556
|
if not validation_passed:
|
|
582
1557
|
if backup_path:
|
|
583
|
-
console.print(
|
|
584
|
-
f"[yellow]๐ Rolling back to backup: {backup_path.name}[/yellow]"
|
|
585
|
-
)
|
|
1558
|
+
console.print(f"[yellow]๐ Rolling back to backup: {backup_path.name}[/yellow]")
|
|
586
1559
|
backup.restore_backup(backup_path)
|
|
587
1560
|
return False
|
|
588
1561
|
|
|
@@ -593,13 +1566,50 @@ def _sync_templates(project_path: Path, force: bool = False) -> bool:
|
|
|
593
1566
|
# Set optimized=false
|
|
594
1567
|
set_optimized_false(project_path)
|
|
595
1568
|
|
|
1569
|
+
# Update companyAnnouncements in settings.local.json
|
|
1570
|
+
try:
|
|
1571
|
+
import sys
|
|
1572
|
+
|
|
1573
|
+
utils_dir = (
|
|
1574
|
+
Path(__file__).parent.parent.parent / "templates" / ".claude" / "hooks" / "moai" / "shared" / "utils"
|
|
1575
|
+
)
|
|
1576
|
+
|
|
1577
|
+
if utils_dir.exists():
|
|
1578
|
+
sys.path.insert(0, str(utils_dir))
|
|
1579
|
+
try:
|
|
1580
|
+
from announcement_translator import auto_translate_and_update
|
|
1581
|
+
|
|
1582
|
+
console.print("[cyan]Updating announcements...[/cyan]")
|
|
1583
|
+
auto_translate_and_update(project_path)
|
|
1584
|
+
console.print("[green]โ Announcements updated[/green]")
|
|
1585
|
+
except Exception as e:
|
|
1586
|
+
console.print(f"[yellow]โ ๏ธ Announcement update failed: {e}[/yellow]")
|
|
1587
|
+
finally:
|
|
1588
|
+
sys.path.remove(str(utils_dir))
|
|
1589
|
+
|
|
1590
|
+
except Exception as e:
|
|
1591
|
+
console.print(f"[yellow]โ ๏ธ Announcement module not available: {e}[/yellow]")
|
|
1592
|
+
|
|
1593
|
+
# NEW: Interactive custom element restore using new system
|
|
1594
|
+
_handle_custom_element_restoration(project_path, backup_path, yes)
|
|
1595
|
+
|
|
1596
|
+
# NEW: Migrate legacy logs to unified structure
|
|
1597
|
+
console.print("\n[cyan]๐ Migrating legacy log files...[/cyan]")
|
|
1598
|
+
if not _migrate_legacy_logs(project_path):
|
|
1599
|
+
console.print("[yellow]โ ๏ธ Legacy log migration failed, but update continuing[/yellow]")
|
|
1600
|
+
|
|
1601
|
+
# Clean up legacy presets directory
|
|
1602
|
+
_cleanup_legacy_presets(project_path)
|
|
1603
|
+
|
|
1604
|
+
# NEW: Show post-update guidance
|
|
1605
|
+
if backup_path:
|
|
1606
|
+
_show_post_update_guidance(backup_path)
|
|
1607
|
+
|
|
596
1608
|
return True
|
|
597
1609
|
except Exception as e:
|
|
598
1610
|
console.print(f"[red]โ Template sync failed: {e}[/red]")
|
|
599
1611
|
if backup_path:
|
|
600
|
-
console.print(
|
|
601
|
-
f"[yellow]๐ Rolling back to backup: {backup_path.name}[/yellow]"
|
|
602
|
-
)
|
|
1612
|
+
console.print(f"[yellow]๐ Rolling back to backup: {backup_path.name}[/yellow]")
|
|
603
1613
|
try:
|
|
604
1614
|
backup = TemplateBackup(project_path)
|
|
605
1615
|
backup.restore_backup(backup_path)
|
|
@@ -626,47 +1636,38 @@ def get_latest_version() -> str | None:
|
|
|
626
1636
|
|
|
627
1637
|
|
|
628
1638
|
def set_optimized_false(project_path: Path) -> None:
|
|
629
|
-
"""Set config
|
|
1639
|
+
"""Set config's optimized field to false.
|
|
630
1640
|
|
|
631
1641
|
Args:
|
|
632
1642
|
project_path: Project path (absolute).
|
|
633
1643
|
"""
|
|
634
|
-
config_path = project_path
|
|
1644
|
+
config_path, _ = _get_config_path(project_path)
|
|
635
1645
|
if not config_path.exists():
|
|
636
1646
|
return
|
|
637
1647
|
|
|
638
1648
|
try:
|
|
639
|
-
config_data =
|
|
1649
|
+
config_data = _load_config(config_path)
|
|
640
1650
|
config_data.setdefault("project", {})["optimized"] = False
|
|
641
|
-
config_path
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
)
|
|
645
|
-
except (json.JSONDecodeError, KeyError):
|
|
646
|
-
# Ignore errors if config.json is invalid
|
|
1651
|
+
_save_config(config_path, config_data)
|
|
1652
|
+
except (json.JSONDecodeError, yaml.YAMLError, KeyError):
|
|
1653
|
+
# Ignore errors if config is invalid
|
|
647
1654
|
pass
|
|
648
1655
|
|
|
649
1656
|
|
|
650
1657
|
def _load_existing_config(project_path: Path) -> dict[str, Any]:
|
|
651
|
-
"""Load existing config
|
|
652
|
-
config_path = project_path
|
|
1658
|
+
"""Load existing config (YAML or JSON) if available."""
|
|
1659
|
+
config_path, _ = _get_config_path(project_path)
|
|
653
1660
|
if config_path.exists():
|
|
654
1661
|
try:
|
|
655
|
-
return
|
|
656
|
-
except json.JSONDecodeError:
|
|
657
|
-
console.print(
|
|
658
|
-
"[yellow]โ Existing config.json could not be parsed. Proceeding with defaults.[/yellow]"
|
|
659
|
-
)
|
|
1662
|
+
return _load_config(config_path)
|
|
1663
|
+
except (json.JSONDecodeError, yaml.YAMLError):
|
|
1664
|
+
console.print("[yellow]โ Existing config could not be parsed. Proceeding with defaults.[/yellow]")
|
|
660
1665
|
return {}
|
|
661
1666
|
|
|
662
1667
|
|
|
663
1668
|
def _is_placeholder(value: Any) -> bool:
|
|
664
1669
|
"""Check if a string value is an unsubstituted template placeholder."""
|
|
665
|
-
return (
|
|
666
|
-
isinstance(value, str)
|
|
667
|
-
and value.strip().startswith("{{")
|
|
668
|
-
and value.strip().endswith("}}")
|
|
669
|
-
)
|
|
1670
|
+
return isinstance(value, str) and value.strip().startswith("{{") and value.strip().endswith("}}")
|
|
670
1671
|
|
|
671
1672
|
|
|
672
1673
|
def _coalesce(*values: Any, default: str = "") -> str:
|
|
@@ -730,19 +1731,82 @@ def _build_template_context(
|
|
|
730
1731
|
)
|
|
731
1732
|
|
|
732
1733
|
# Detect OS for cross-platform Hook path configuration
|
|
733
|
-
hook_project_dir = (
|
|
734
|
-
"%CLAUDE_PROJECT_DIR%"
|
|
735
|
-
if platform.system() == "Windows"
|
|
736
|
-
else "$CLAUDE_PROJECT_DIR"
|
|
737
|
-
)
|
|
1734
|
+
hook_project_dir = "%CLAUDE_PROJECT_DIR%" if platform.system() == "Windows" else "$CLAUDE_PROJECT_DIR"
|
|
738
1735
|
|
|
739
|
-
# Extract language configuration
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1736
|
+
# Extract and resolve language configuration using centralized resolver
|
|
1737
|
+
try:
|
|
1738
|
+
from moai_adk.core.language_config_resolver import get_resolver
|
|
1739
|
+
|
|
1740
|
+
# Use language resolver to get complete configuration
|
|
1741
|
+
resolver = get_resolver(str(project_path))
|
|
1742
|
+
resolved_config = resolver.resolve_config()
|
|
1743
|
+
|
|
1744
|
+
# Extract language configuration with environment variable priority
|
|
1745
|
+
language_config = {
|
|
1746
|
+
"conversation_language": resolved_config.get("conversation_language", "en"),
|
|
1747
|
+
"conversation_language_name": resolved_config.get("conversation_language_name", "English"),
|
|
1748
|
+
"agent_prompt_language": resolved_config.get("agent_prompt_language", "en"),
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
# Extract user personalization
|
|
1752
|
+
user_name = resolved_config.get("user_name", "")
|
|
1753
|
+
personalized_greeting = resolver.get_personalized_greeting(resolved_config)
|
|
1754
|
+
config_source = resolved_config.get("config_source", "config_file")
|
|
1755
|
+
|
|
1756
|
+
except ImportError:
|
|
1757
|
+
# Fallback to basic language config extraction if resolver not available
|
|
1758
|
+
language_config = existing_config.get("language", {})
|
|
1759
|
+
if not isinstance(language_config, dict):
|
|
1760
|
+
language_config = {}
|
|
1761
|
+
|
|
1762
|
+
user_name = existing_config.get("user", {}).get("name", "")
|
|
1763
|
+
conv_lang = language_config.get("conversation_language")
|
|
1764
|
+
personalized_greeting = f"{user_name}๋" if user_name and conv_lang == "ko" else user_name
|
|
1765
|
+
config_source = "config_file"
|
|
1766
|
+
|
|
1767
|
+
# Enhanced version formatting (matches TemplateProcessor.get_enhanced_version_context)
|
|
1768
|
+
def format_short_version(v: str) -> str:
|
|
1769
|
+
"""Remove 'v' prefix if present."""
|
|
1770
|
+
return v[1:] if v.startswith("v") else v
|
|
1771
|
+
|
|
1772
|
+
def format_display_version(v: str) -> str:
|
|
1773
|
+
"""Format display version with proper formatting."""
|
|
1774
|
+
if v == "unknown":
|
|
1775
|
+
return "MoAI-ADK unknown version"
|
|
1776
|
+
elif v.startswith("v"):
|
|
1777
|
+
return f"MoAI-ADK {v}"
|
|
1778
|
+
else:
|
|
1779
|
+
return f"MoAI-ADK v{v}"
|
|
1780
|
+
|
|
1781
|
+
def format_trimmed_version(v: str, max_length: int = 10) -> str:
|
|
1782
|
+
"""Format version with maximum length for UI displays."""
|
|
1783
|
+
if v == "unknown":
|
|
1784
|
+
return "unknown"
|
|
1785
|
+
clean_version = v[1:] if v.startswith("v") else v
|
|
1786
|
+
if len(clean_version) > max_length:
|
|
1787
|
+
return clean_version[:max_length]
|
|
1788
|
+
return clean_version
|
|
1789
|
+
|
|
1790
|
+
def format_semver_version(v: str) -> str:
|
|
1791
|
+
"""Format version as semantic version."""
|
|
1792
|
+
if v == "unknown":
|
|
1793
|
+
return "0.0.0"
|
|
1794
|
+
clean_version = v[1:] if v.startswith("v") else v
|
|
1795
|
+
import re
|
|
1796
|
+
|
|
1797
|
+
semver_match = re.match(r"^(\d+\.\d+\.\d+)", clean_version)
|
|
1798
|
+
if semver_match:
|
|
1799
|
+
return semver_match.group(1)
|
|
1800
|
+
return "0.0.0"
|
|
743
1801
|
|
|
744
1802
|
return {
|
|
745
1803
|
"MOAI_VERSION": version_for_config,
|
|
1804
|
+
"MOAI_VERSION_SHORT": format_short_version(version_for_config),
|
|
1805
|
+
"MOAI_VERSION_DISPLAY": format_display_version(version_for_config),
|
|
1806
|
+
"MOAI_VERSION_TRIMMED": format_trimmed_version(version_for_config),
|
|
1807
|
+
"MOAI_VERSION_SEMVER": format_semver_version(version_for_config),
|
|
1808
|
+
"MOAI_VERSION_VALID": "true" if version_for_config != "unknown" else "false",
|
|
1809
|
+
"MOAI_VERSION_SOURCE": "config_cached",
|
|
746
1810
|
"PROJECT_NAME": project_name,
|
|
747
1811
|
"PROJECT_MODE": project_mode,
|
|
748
1812
|
"PROJECT_DESCRIPTION": project_description,
|
|
@@ -750,9 +1814,19 @@ def _build_template_context(
|
|
|
750
1814
|
"CREATION_TIMESTAMP": created_at,
|
|
751
1815
|
"PROJECT_DIR": hook_project_dir,
|
|
752
1816
|
"CONVERSATION_LANGUAGE": language_config.get("conversation_language", "en"),
|
|
753
|
-
"CONVERSATION_LANGUAGE_NAME": language_config.get(
|
|
754
|
-
|
|
1817
|
+
"CONVERSATION_LANGUAGE_NAME": language_config.get("conversation_language_name", "English"),
|
|
1818
|
+
"AGENT_PROMPT_LANGUAGE": language_config.get("agent_prompt_language", "en"),
|
|
1819
|
+
"GIT_COMMIT_MESSAGES_LANGUAGE": language_config.get("git_commit_messages", "en"),
|
|
1820
|
+
"CODE_COMMENTS_LANGUAGE": language_config.get("code_comments", "en"),
|
|
1821
|
+
"DOCUMENTATION_LANGUAGE": language_config.get(
|
|
1822
|
+
"documentation", language_config.get("conversation_language", "en")
|
|
1823
|
+
),
|
|
1824
|
+
"ERROR_MESSAGES_LANGUAGE": language_config.get(
|
|
1825
|
+
"error_messages", language_config.get("conversation_language", "en")
|
|
755
1826
|
),
|
|
1827
|
+
"USER_NAME": user_name,
|
|
1828
|
+
"PERSONALIZED_GREETING": personalized_greeting,
|
|
1829
|
+
"LANGUAGE_CONFIG_SOURCE": config_source,
|
|
756
1830
|
"CODEBASE_LANGUAGE": project_section.get("language", "generic"),
|
|
757
1831
|
"PROJECT_OWNER": project_section.get("author", "@user"),
|
|
758
1832
|
"AUTHOR": project_section.get("author", "@user"),
|
|
@@ -765,18 +1839,18 @@ def _preserve_project_metadata(
|
|
|
765
1839
|
existing_config: dict[str, Any],
|
|
766
1840
|
version_for_config: str,
|
|
767
1841
|
) -> None:
|
|
768
|
-
"""Restore project-specific metadata in the new config.
|
|
1842
|
+
"""Restore project-specific metadata in the new config (YAML or JSON).
|
|
769
1843
|
|
|
770
1844
|
Also updates template_version to track which template version is synchronized.
|
|
771
1845
|
"""
|
|
772
|
-
config_path = project_path
|
|
1846
|
+
config_path, _ = _get_config_path(project_path)
|
|
773
1847
|
if not config_path.exists():
|
|
774
1848
|
return
|
|
775
1849
|
|
|
776
1850
|
try:
|
|
777
|
-
config_data =
|
|
778
|
-
except json.JSONDecodeError:
|
|
779
|
-
console.print("[red]โ Failed to parse config
|
|
1851
|
+
config_data = _load_config(config_path)
|
|
1852
|
+
except (json.JSONDecodeError, yaml.YAMLError):
|
|
1853
|
+
console.print("[red]โ Failed to parse config after template copy[/red]")
|
|
780
1854
|
return
|
|
781
1855
|
|
|
782
1856
|
project_data = config_data.setdefault("project", {})
|
|
@@ -796,9 +1870,7 @@ def _preserve_project_metadata(
|
|
|
796
1870
|
if locale:
|
|
797
1871
|
project_data["locale"] = locale
|
|
798
1872
|
|
|
799
|
-
language = _coalesce(
|
|
800
|
-
existing_project.get("language"), existing_config.get("language")
|
|
801
|
-
)
|
|
1873
|
+
language = _coalesce(existing_project.get("language"), existing_config.get("language"))
|
|
802
1874
|
if language:
|
|
803
1875
|
project_data["language"] = language
|
|
804
1876
|
|
|
@@ -808,9 +1880,7 @@ def _preserve_project_metadata(
|
|
|
808
1880
|
# This allows Stage 2 to compare package vs project template versions
|
|
809
1881
|
project_data["template_version"] = version_for_config
|
|
810
1882
|
|
|
811
|
-
config_path
|
|
812
|
-
json.dumps(config_data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8"
|
|
813
|
-
)
|
|
1883
|
+
_save_config(config_path, config_data)
|
|
814
1884
|
|
|
815
1885
|
|
|
816
1886
|
def _apply_context_to_file(processor: TemplateProcessor, target_path: Path) -> None:
|
|
@@ -823,9 +1893,7 @@ def _apply_context_to_file(processor: TemplateProcessor, target_path: Path) -> N
|
|
|
823
1893
|
except UnicodeDecodeError:
|
|
824
1894
|
return
|
|
825
1895
|
|
|
826
|
-
substituted, warnings = processor._substitute_variables(
|
|
827
|
-
content
|
|
828
|
-
) # pylint: disable=protected-access
|
|
1896
|
+
substituted, warnings = processor._substitute_variables(content) # pylint: disable=protected-access
|
|
829
1897
|
if warnings:
|
|
830
1898
|
console.print("[yellow]โ Template warnings:[/yellow]")
|
|
831
1899
|
for warning in warnings:
|
|
@@ -856,28 +1924,20 @@ def _validate_template_substitution(project_path: Path) -> None:
|
|
|
856
1924
|
unsubstituted = re.findall(r"\{\{([A-Z_]+)\}\}", content)
|
|
857
1925
|
if unsubstituted:
|
|
858
1926
|
unique_vars = sorted(set(unsubstituted))
|
|
859
|
-
issues_found.append(
|
|
860
|
-
f"{file_path.relative_to(project_path)}: {', '.join(unique_vars)}"
|
|
861
|
-
)
|
|
1927
|
+
issues_found.append(f"{file_path.relative_to(project_path)}: {', '.join(unique_vars)}")
|
|
862
1928
|
except Exception as e:
|
|
863
|
-
console.print(
|
|
864
|
-
f"[yellow]โ ๏ธ Could not validate {file_path.relative_to(project_path)}: {e}[/yellow]"
|
|
865
|
-
)
|
|
1929
|
+
console.print(f"[yellow]โ ๏ธ Could not validate {file_path.relative_to(project_path)}: {e}[/yellow]")
|
|
866
1930
|
|
|
867
1931
|
if issues_found:
|
|
868
1932
|
console.print("[red]โ Template substitution validation failed:[/red]")
|
|
869
1933
|
for issue in issues_found:
|
|
870
1934
|
console.print(f" {issue}")
|
|
871
|
-
console.print(
|
|
872
|
-
"[yellow]๐ก Run '/alfred:0-project' to fix template variables[/yellow]"
|
|
873
|
-
)
|
|
1935
|
+
console.print("[yellow]๐ก Run '/moai:0-project' to fix template variables[/yellow]")
|
|
874
1936
|
else:
|
|
875
1937
|
console.print("[green]โ
Template substitution validation passed[/green]")
|
|
876
1938
|
|
|
877
1939
|
|
|
878
|
-
def _validate_template_substitution_with_rollback(
|
|
879
|
-
project_path: Path, backup_path: Path | None
|
|
880
|
-
) -> bool:
|
|
1940
|
+
def _validate_template_substitution_with_rollback(project_path: Path, backup_path: Path | None) -> bool:
|
|
881
1941
|
"""Validate template substitution with rollback capability.
|
|
882
1942
|
|
|
883
1943
|
Returns:
|
|
@@ -903,13 +1963,9 @@ def _validate_template_substitution_with_rollback(
|
|
|
903
1963
|
unsubstituted = re.findall(r"\{\{([A-Z_]+)\}\}", content)
|
|
904
1964
|
if unsubstituted:
|
|
905
1965
|
unique_vars = sorted(set(unsubstituted))
|
|
906
|
-
issues_found.append(
|
|
907
|
-
f"{file_path.relative_to(project_path)}: {', '.join(unique_vars)}"
|
|
908
|
-
)
|
|
1966
|
+
issues_found.append(f"{file_path.relative_to(project_path)}: {', '.join(unique_vars)}")
|
|
909
1967
|
except Exception as e:
|
|
910
|
-
console.print(
|
|
911
|
-
f"[yellow]โ ๏ธ Could not validate {file_path.relative_to(project_path)}: {e}[/yellow]"
|
|
912
|
-
)
|
|
1968
|
+
console.print(f"[yellow]โ ๏ธ Could not validate {file_path.relative_to(project_path)}: {e}[/yellow]")
|
|
913
1969
|
|
|
914
1970
|
if issues_found:
|
|
915
1971
|
console.print("[red]โ Template substitution validation failed:[/red]")
|
|
@@ -917,13 +1973,9 @@ def _validate_template_substitution_with_rollback(
|
|
|
917
1973
|
console.print(f" {issue}")
|
|
918
1974
|
|
|
919
1975
|
if backup_path:
|
|
920
|
-
console.print(
|
|
921
|
-
"[yellow]๐ Rolling back due to validation failure...[/yellow]"
|
|
922
|
-
)
|
|
1976
|
+
console.print("[yellow]๐ Rolling back due to validation failure...[/yellow]")
|
|
923
1977
|
else:
|
|
924
|
-
console.print(
|
|
925
|
-
"[yellow]๐ก Run '/alfred:0-project' to fix template variables[/yellow]"
|
|
926
|
-
)
|
|
1978
|
+
console.print("[yellow]๐ก Run '/moai:0-project' to fix template variables[/yellow]")
|
|
927
1979
|
console.print("[red]โ ๏ธ No backup available - manual fix required[/red]")
|
|
928
1980
|
|
|
929
1981
|
return False
|
|
@@ -978,18 +2030,14 @@ def _show_network_error_help() -> None:
|
|
|
978
2030
|
console.print("Options:")
|
|
979
2031
|
console.print(" 1. Check network connection")
|
|
980
2032
|
console.print(" 2. Try again with: [cyan]moai-adk update --force[/cyan]")
|
|
981
|
-
console.print(
|
|
982
|
-
" 3. Skip version check: [cyan]moai-adk update --templates-only[/cyan]"
|
|
983
|
-
)
|
|
2033
|
+
console.print(" 3. Skip version check: [cyan]moai-adk update --templates-only[/cyan]")
|
|
984
2034
|
|
|
985
2035
|
|
|
986
2036
|
def _show_template_sync_failure_help() -> None:
|
|
987
2037
|
"""Show help when template sync fails."""
|
|
988
2038
|
console.print("[yellow]โ ๏ธ Template sync failed[/yellow]\n")
|
|
989
2039
|
console.print("Rollback options:")
|
|
990
|
-
console.print(
|
|
991
|
-
" 1. Restore from backup: [cyan]cp -r .moai-backups/TIMESTAMP .moai/[/cyan]"
|
|
992
|
-
)
|
|
2040
|
+
console.print(" 1. Restore from backup: [cyan]cp -r .moai-backups/TIMESTAMP .moai/[/cyan]")
|
|
993
2041
|
console.print(" 2. Skip backup and retry: [cyan]moai-adk update --force[/cyan]")
|
|
994
2042
|
console.print(" 3. Report issue: https://github.com/modu-ai/moai-adk/issues")
|
|
995
2043
|
|
|
@@ -1027,24 +2075,16 @@ def _execute_migration_if_needed(project_path: Path, yes: bool = False) -> bool:
|
|
|
1027
2075
|
console.print()
|
|
1028
2076
|
console.print(" This will migrate configuration files to new locations:")
|
|
1029
2077
|
console.print(" โข .moai/config.json โ .moai/config/config.json")
|
|
1030
|
-
console.print(
|
|
1031
|
-
" โข .claude/statusline-config.yaml โ .moai/config/statusline-config.yaml"
|
|
1032
|
-
)
|
|
2078
|
+
console.print(" โข .claude/statusline-config.yaml โ .moai/config/statusline-config.yaml")
|
|
1033
2079
|
console.print()
|
|
1034
2080
|
console.print(" A backup will be created automatically.")
|
|
1035
2081
|
console.print()
|
|
1036
2082
|
|
|
1037
2083
|
# Confirm with user (unless --yes)
|
|
1038
2084
|
if not yes:
|
|
1039
|
-
if not click.confirm(
|
|
1040
|
-
"
|
|
1041
|
-
|
|
1042
|
-
console.print(
|
|
1043
|
-
"[yellow]โ ๏ธ Migration skipped. Some features may not work correctly.[/yellow]"
|
|
1044
|
-
)
|
|
1045
|
-
console.print(
|
|
1046
|
-
"[cyan]๐ก Run 'moai-adk migrate' manually when ready[/cyan]"
|
|
1047
|
-
)
|
|
2085
|
+
if not click.confirm("Do you want to proceed with migration?", default=True):
|
|
2086
|
+
console.print("[yellow]โ ๏ธ Migration skipped. Some features may not work correctly.[/yellow]")
|
|
2087
|
+
console.print("[cyan]๐ก Run 'moai-adk migrate' manually when ready[/cyan]")
|
|
1048
2088
|
return False
|
|
1049
2089
|
|
|
1050
2090
|
# Execute migration
|
|
@@ -1056,9 +2096,7 @@ def _execute_migration_if_needed(project_path: Path, yes: bool = False) -> bool:
|
|
|
1056
2096
|
return True
|
|
1057
2097
|
else:
|
|
1058
2098
|
console.print("[red]โ Migration failed[/red]")
|
|
1059
|
-
console.print(
|
|
1060
|
-
"[cyan]๐ก Use 'moai-adk migrate --rollback' to restore from backup[/cyan]"
|
|
1061
|
-
)
|
|
2099
|
+
console.print("[cyan]๐ก Use 'moai-adk migrate --rollback' to restore from backup[/cyan]")
|
|
1062
2100
|
return False
|
|
1063
2101
|
|
|
1064
2102
|
except Exception as e:
|
|
@@ -1076,14 +2114,29 @@ def _execute_migration_if_needed(project_path: Path, yes: bool = False) -> bool:
|
|
|
1076
2114
|
)
|
|
1077
2115
|
@click.option("--force", is_flag=True, help="Skip backup and force the update")
|
|
1078
2116
|
@click.option("--check", is_flag=True, help="Only check version (do not update)")
|
|
2117
|
+
@click.option("--templates-only", is_flag=True, help="Skip package upgrade, sync templates only")
|
|
2118
|
+
@click.option("--yes", is_flag=True, help="Auto-confirm all prompts (CI/CD mode)")
|
|
1079
2119
|
@click.option(
|
|
1080
|
-
"--
|
|
2120
|
+
"--merge",
|
|
2121
|
+
"merge_strategy",
|
|
2122
|
+
flag_value="auto",
|
|
2123
|
+
help="Auto-merge: Apply template + preserve user changes",
|
|
2124
|
+
)
|
|
2125
|
+
@click.option(
|
|
2126
|
+
"--manual",
|
|
2127
|
+
"merge_strategy",
|
|
2128
|
+
flag_value="manual",
|
|
2129
|
+
help="Manual merge: Preserve backup, generate merge guide",
|
|
1081
2130
|
)
|
|
1082
|
-
@click.option("--yes", is_flag=True, help="Auto-confirm all prompts (CI/CD mode)")
|
|
1083
2131
|
def update(
|
|
1084
|
-
path: str,
|
|
2132
|
+
path: str,
|
|
2133
|
+
force: bool,
|
|
2134
|
+
check: bool,
|
|
2135
|
+
templates_only: bool,
|
|
2136
|
+
yes: bool,
|
|
2137
|
+
merge_strategy: str | None,
|
|
1085
2138
|
) -> None:
|
|
1086
|
-
"""Update command with 3-stage workflow (v0.
|
|
2139
|
+
"""Update command with 3-stage workflow + merge strategy selection (v0.26.0+).
|
|
1087
2140
|
|
|
1088
2141
|
Stage 1 (Package Version Check):
|
|
1089
2142
|
- Fetches current and latest versions from PyPI
|
|
@@ -1095,18 +2148,34 @@ def update(
|
|
|
1095
2148
|
- If versions match: skips Stage 3 (already up-to-date)
|
|
1096
2149
|
- Performance improvement: 70-80% faster for unchanged projects (3-4s vs 12-18s)
|
|
1097
2150
|
|
|
1098
|
-
Stage 3 (Template Sync):
|
|
2151
|
+
Stage 3 (Template Sync with Merge Strategy - NEW in v0.26.0):
|
|
1099
2152
|
- Syncs templates only if versions differ
|
|
2153
|
+
- User chooses merge strategy:
|
|
2154
|
+
* Auto-merge (default): Template + preserved user changes
|
|
2155
|
+
* Manual merge: Backup + comprehensive merge guide (full control)
|
|
1100
2156
|
- Updates .claude/, .moai/, CLAUDE.md, config.json
|
|
1101
2157
|
- Preserves specs and reports
|
|
1102
2158
|
- Saves new template_version to config.json
|
|
1103
2159
|
|
|
1104
2160
|
Examples:
|
|
1105
|
-
python -m moai_adk update #
|
|
1106
|
-
python -m moai_adk update --
|
|
2161
|
+
python -m moai_adk update # interactive merge strategy selection
|
|
2162
|
+
python -m moai_adk update --merge # auto-merge (template + user changes)
|
|
2163
|
+
python -m moai_adk update --manual # manual merge (backup + guide)
|
|
2164
|
+
python -m moai_adk update --force # force template sync (no backup)
|
|
1107
2165
|
python -m moai_adk update --check # check version only
|
|
1108
2166
|
python -m moai_adk update --templates-only # skip package upgrade
|
|
1109
|
-
python -m moai_adk update --yes # CI/CD mode (auto-confirm)
|
|
2167
|
+
python -m moai_adk update --yes # CI/CD mode (auto-confirm + auto-merge)
|
|
2168
|
+
|
|
2169
|
+
Merge Strategies:
|
|
2170
|
+
--merge: Auto-merge applies template + preserves your changes (default)
|
|
2171
|
+
Generated files: backup, merge report
|
|
2172
|
+
--manual: Manual merge preserves backup + generates comprehensive guide
|
|
2173
|
+
Generated files: backup, merge guide
|
|
2174
|
+
|
|
2175
|
+
Generated Files:
|
|
2176
|
+
- Backup: .moai-backups/pre-update-backup_{timestamp}/
|
|
2177
|
+
- Report: .moai/reports/merge-report.md (auto-merge only)
|
|
2178
|
+
- Guide: .moai/guides/merge-guide.md (manual merge only)
|
|
1110
2179
|
"""
|
|
1111
2180
|
try:
|
|
1112
2181
|
project_path = Path(path).resolve()
|
|
@@ -1120,14 +2189,24 @@ def update(
|
|
|
1120
2189
|
# Note: If --check is used, always fetch versions even if --templates-only is also present
|
|
1121
2190
|
if check or not templates_only:
|
|
1122
2191
|
try:
|
|
1123
|
-
|
|
1124
|
-
|
|
2192
|
+
# Try to use new spinner UI
|
|
2193
|
+
try:
|
|
2194
|
+
from moai_adk.cli.ui.progress import SpinnerContext
|
|
2195
|
+
|
|
2196
|
+
with SpinnerContext("Checking for updates...") as spinner:
|
|
2197
|
+
current = _get_current_version()
|
|
2198
|
+
spinner.update("Fetching latest version from PyPI...")
|
|
2199
|
+
latest = _get_latest_version()
|
|
2200
|
+
spinner.success("Version check complete")
|
|
2201
|
+
except ImportError:
|
|
2202
|
+
# Fallback to simple console output
|
|
2203
|
+
console.print("[dim]Checking for updates...[/dim]")
|
|
2204
|
+
current = _get_current_version()
|
|
2205
|
+
latest = _get_latest_version()
|
|
1125
2206
|
except RuntimeError as e:
|
|
1126
2207
|
console.print(f"[red]Error: {e}[/red]")
|
|
1127
2208
|
if not force:
|
|
1128
|
-
console.print(
|
|
1129
|
-
"[yellow]โ Cannot check for updates. Use --force to update anyway.[/yellow]"
|
|
1130
|
-
)
|
|
2209
|
+
console.print("[yellow]โ Cannot check for updates. Use --force to update anyway.[/yellow]")
|
|
1131
2210
|
raise click.Abort()
|
|
1132
2211
|
# With --force, proceed to Stage 2 even if version check fails
|
|
1133
2212
|
current = __version__
|
|
@@ -1139,23 +2218,24 @@ def update(
|
|
|
1139
2218
|
if check:
|
|
1140
2219
|
comparison = _compare_versions(current, latest)
|
|
1141
2220
|
if comparison < 0:
|
|
1142
|
-
console.print(
|
|
1143
|
-
f"\n[yellow]๐ฆ Update available: {current} โ {latest}[/yellow]"
|
|
1144
|
-
)
|
|
2221
|
+
console.print(f"\n[yellow]๐ฆ Update available: {current} โ {latest}[/yellow]")
|
|
1145
2222
|
console.print(" Run 'moai-adk update' to upgrade")
|
|
1146
2223
|
elif comparison == 0:
|
|
1147
2224
|
console.print(f"[green]โ Already up to date ({current})[/green]")
|
|
1148
2225
|
else:
|
|
1149
|
-
console.print(
|
|
1150
|
-
f"[cyan]โน๏ธ Dev version: {current} (latest: {latest})[/cyan]"
|
|
1151
|
-
)
|
|
2226
|
+
console.print(f"[cyan]โน๏ธ Dev version: {current} (latest: {latest})[/cyan]")
|
|
1152
2227
|
return
|
|
1153
2228
|
|
|
1154
2229
|
# Step 2: Handle --templates-only (skip upgrade, go straight to sync)
|
|
1155
2230
|
if templates_only:
|
|
1156
2231
|
console.print("[cyan]๐ Syncing templates only...[/cyan]")
|
|
2232
|
+
|
|
2233
|
+
# Preserve user-specific settings before sync
|
|
2234
|
+
console.print(" [cyan]๐พ Preserving user settings...[/cyan]")
|
|
2235
|
+
preserved_settings = _preserve_user_settings(project_path)
|
|
2236
|
+
|
|
1157
2237
|
try:
|
|
1158
|
-
if not _sync_templates(project_path, force):
|
|
2238
|
+
if not _sync_templates(project_path, force, yes):
|
|
1159
2239
|
raise TemplateSyncError("Template sync returned False")
|
|
1160
2240
|
except TemplateSyncError:
|
|
1161
2241
|
console.print("[red]Error: Template sync failed[/red]")
|
|
@@ -1166,10 +2246,11 @@ def update(
|
|
|
1166
2246
|
_show_template_sync_failure_help()
|
|
1167
2247
|
raise click.Abort()
|
|
1168
2248
|
|
|
2249
|
+
# Restore user-specific settings after sync
|
|
2250
|
+
_restore_user_settings(project_path, preserved_settings)
|
|
2251
|
+
|
|
1169
2252
|
console.print(" [green]โ
.claude/ update complete[/green]")
|
|
1170
|
-
console.print(
|
|
1171
|
-
" [green]โ
.moai/ update complete (specs/reports preserved)[/green]"
|
|
1172
|
-
)
|
|
2253
|
+
console.print(" [green]โ
.moai/ update complete (specs/reports preserved)[/green]")
|
|
1173
2254
|
console.print(" [green]๐ CLAUDE.md merge complete[/green]")
|
|
1174
2255
|
console.print(" [green]๐ config.json merge complete[/green]")
|
|
1175
2256
|
console.print("\n[green]โ Template sync complete![/green]")
|
|
@@ -1204,9 +2285,7 @@ def update(
|
|
|
1204
2285
|
try:
|
|
1205
2286
|
upgrade_result = _execute_upgrade(installer_cmd)
|
|
1206
2287
|
if not upgrade_result:
|
|
1207
|
-
raise UpgradeError(
|
|
1208
|
-
f"Upgrade command failed: {' '.join(installer_cmd)}"
|
|
1209
|
-
)
|
|
2288
|
+
raise UpgradeError(f"Upgrade command failed: {' '.join(installer_cmd)}")
|
|
1210
2289
|
except subprocess.TimeoutExpired:
|
|
1211
2290
|
_show_timeout_error_help()
|
|
1212
2291
|
raise click.Abort()
|
|
@@ -1216,9 +2295,7 @@ def update(
|
|
|
1216
2295
|
|
|
1217
2296
|
# Prompt re-run
|
|
1218
2297
|
console.print("\n[green]โ Upgrade complete![/green]")
|
|
1219
|
-
console.print(
|
|
1220
|
-
"[cyan]๐ข Run 'moai-adk update' again to sync templates[/cyan]"
|
|
1221
|
-
)
|
|
2298
|
+
console.print("[cyan]๐ข Run 'moai-adk update' again to sync templates[/cyan]")
|
|
1222
2299
|
return
|
|
1223
2300
|
|
|
1224
2301
|
# Stage 1.5: Migration Check (NEW in v0.24.0)
|
|
@@ -1227,9 +2304,12 @@ def update(
|
|
|
1227
2304
|
# Execute migration if needed
|
|
1228
2305
|
if not _execute_migration_if_needed(project_path, yes):
|
|
1229
2306
|
console.print("[yellow]โ ๏ธ Update continuing without migration[/yellow]")
|
|
1230
|
-
console.print(
|
|
1231
|
-
|
|
1232
|
-
|
|
2307
|
+
console.print("[cyan]๐ก Some features may require migration to work correctly[/cyan]")
|
|
2308
|
+
|
|
2309
|
+
# Migrate config.json โ config.yaml (v0.32.0+)
|
|
2310
|
+
console.print("\n[cyan]๐ Checking for config format migration...[/cyan]")
|
|
2311
|
+
if not _migrate_config_json_to_yaml(project_path):
|
|
2312
|
+
console.print("[yellow]โ ๏ธ Config migration failed, continuing with existing format[/yellow]")
|
|
1233
2313
|
|
|
1234
2314
|
# Stage 2: Config Version Comparison
|
|
1235
2315
|
try:
|
|
@@ -1246,32 +2326,66 @@ def update(
|
|
|
1246
2326
|
console.print(f" Project config: {project_config_version}")
|
|
1247
2327
|
|
|
1248
2328
|
try:
|
|
1249
|
-
config_comparison = _compare_versions(
|
|
1250
|
-
package_config_version, project_config_version
|
|
1251
|
-
)
|
|
2329
|
+
config_comparison = _compare_versions(package_config_version, project_config_version)
|
|
1252
2330
|
except version.InvalidVersion as e:
|
|
1253
2331
|
# Handle invalid version strings (e.g., unsubstituted template placeholders, corrupted configs)
|
|
1254
2332
|
console.print(f"[yellow]โ Invalid version format in config: {e}[/yellow]")
|
|
1255
|
-
console.print(
|
|
1256
|
-
"[cyan]โน๏ธ Forcing template sync to repair configuration...[/cyan]"
|
|
1257
|
-
)
|
|
2333
|
+
console.print("[cyan]โน๏ธ Forcing template sync to repair configuration...[/cyan]")
|
|
1258
2334
|
# Force template sync by treating project version as outdated
|
|
1259
2335
|
config_comparison = 1 # package_config_version > project_config_version
|
|
1260
2336
|
|
|
1261
2337
|
# If versions are equal, no sync needed
|
|
1262
2338
|
if config_comparison <= 0:
|
|
1263
|
-
console.print(
|
|
1264
|
-
|
|
1265
|
-
)
|
|
1266
|
-
console.print(
|
|
1267
|
-
"[cyan]โน๏ธ Templates are up to date! No changes needed.[/cyan]"
|
|
1268
|
-
)
|
|
2339
|
+
console.print(f"\n[green]โ Project already has latest template version ({project_config_version})[/green]")
|
|
2340
|
+
console.print("[cyan]โน๏ธ Templates are up to date! No changes needed.[/cyan]")
|
|
1269
2341
|
return
|
|
1270
2342
|
|
|
1271
2343
|
# Stage 3: Template Sync (Only if package_config_version > project_config_version)
|
|
1272
|
-
console.print(
|
|
1273
|
-
|
|
1274
|
-
)
|
|
2344
|
+
console.print(f"\n[cyan]๐ Syncing templates ({project_config_version} โ {package_config_version})...[/cyan]")
|
|
2345
|
+
|
|
2346
|
+
# Determine merge strategy (default: auto-merge)
|
|
2347
|
+
final_merge_strategy = merge_strategy or "auto"
|
|
2348
|
+
|
|
2349
|
+
# Handle merge strategy
|
|
2350
|
+
if final_merge_strategy == "manual":
|
|
2351
|
+
# Manual merge mode: Create full backup + generate guide, no template sync
|
|
2352
|
+
console.print("\n[cyan]๐ Manual merge mode selected[/cyan]")
|
|
2353
|
+
|
|
2354
|
+
# Create full project backup
|
|
2355
|
+
console.print(" [cyan]๐พ Creating full project backup...[/cyan]")
|
|
2356
|
+
try:
|
|
2357
|
+
from moai_adk.core.migration.backup_manager import BackupManager
|
|
2358
|
+
|
|
2359
|
+
backup_manager = BackupManager(project_path)
|
|
2360
|
+
full_backup_path = backup_manager.create_full_project_backup(description="pre-update-backup")
|
|
2361
|
+
console.print(f" [green]โ Backup: {full_backup_path.relative_to(project_path)}/[/green]")
|
|
2362
|
+
|
|
2363
|
+
# Generate merge guide
|
|
2364
|
+
console.print(" [cyan]๐ Generating merge guide...[/cyan]")
|
|
2365
|
+
template_path = Path(__file__).parent.parent.parent / "templates"
|
|
2366
|
+
guide_path = _generate_manual_merge_guide(full_backup_path, template_path, project_path)
|
|
2367
|
+
console.print(f" [green]โ Guide: {guide_path.relative_to(project_path)}[/green]")
|
|
2368
|
+
|
|
2369
|
+
# Summary
|
|
2370
|
+
console.print("\n[green]โ Manual merge setup complete![/green]")
|
|
2371
|
+
console.print(f"[cyan]๐ Backup location: {full_backup_path.relative_to(project_path)}/[/cyan]")
|
|
2372
|
+
console.print(f"[cyan]๐ Merge guide: {guide_path.relative_to(project_path)}[/cyan]")
|
|
2373
|
+
console.print("\n[yellow]โ ๏ธ Next steps:[/yellow]")
|
|
2374
|
+
console.print("[yellow] 1. Review the merge guide[/yellow]")
|
|
2375
|
+
console.print("[yellow] 2. Compare files using diff or visual tools[/yellow]")
|
|
2376
|
+
console.print("[yellow] 3. Manually merge your customizations[/yellow]")
|
|
2377
|
+
console.print("[yellow] 4. Test and commit changes[/yellow]")
|
|
2378
|
+
|
|
2379
|
+
except Exception as e:
|
|
2380
|
+
console.print(f"[red]Error: Manual merge setup failed - {e}[/red]")
|
|
2381
|
+
raise click.Abort()
|
|
2382
|
+
|
|
2383
|
+
return
|
|
2384
|
+
|
|
2385
|
+
# Auto merge mode: Preserve user-specific settings before sync
|
|
2386
|
+
console.print("\n[cyan]๐ Auto-merge mode selected[/cyan]")
|
|
2387
|
+
console.print(" [cyan]๐พ Preserving user settings...[/cyan]")
|
|
2388
|
+
preserved_settings = _preserve_user_settings(project_path)
|
|
1275
2389
|
|
|
1276
2390
|
# Create backup unless --force
|
|
1277
2391
|
if not force:
|
|
@@ -1279,19 +2393,21 @@ def update(
|
|
|
1279
2393
|
try:
|
|
1280
2394
|
processor = TemplateProcessor(project_path)
|
|
1281
2395
|
backup_path = processor.create_backup()
|
|
1282
|
-
console.print(
|
|
1283
|
-
f" [green]โ Backup: {backup_path.relative_to(project_path)}/[/green]"
|
|
1284
|
-
)
|
|
2396
|
+
console.print(f" [green]โ Backup: {backup_path.relative_to(project_path)}/[/green]")
|
|
1285
2397
|
except Exception as e:
|
|
1286
2398
|
console.print(f" [yellow]โ Backup failed: {e}[/yellow]")
|
|
1287
2399
|
console.print(" [yellow]โ Continuing without backup...[/yellow]")
|
|
1288
2400
|
else:
|
|
1289
2401
|
console.print(" [yellow]โ Skipping backup (--force)[/yellow]")
|
|
1290
2402
|
|
|
1291
|
-
# Sync templates
|
|
2403
|
+
# Sync templates (NO spinner - user interaction may be required)
|
|
2404
|
+
# SpinnerContext blocks stdin, causing hang when click.confirm() is called
|
|
1292
2405
|
try:
|
|
1293
|
-
|
|
2406
|
+
console.print(" [cyan]Syncing templates...[/cyan]")
|
|
2407
|
+
if not _sync_templates(project_path, force, yes):
|
|
1294
2408
|
raise TemplateSyncError("Template sync returned False")
|
|
2409
|
+
_restore_user_settings(project_path, preserved_settings)
|
|
2410
|
+
console.print(" [green]โ Template sync complete[/green]")
|
|
1295
2411
|
except TemplateSyncError:
|
|
1296
2412
|
console.print("[red]Error: Template sync failed[/red]")
|
|
1297
2413
|
_show_template_sync_failure_help()
|
|
@@ -1302,20 +2418,269 @@ def update(
|
|
|
1302
2418
|
raise click.Abort()
|
|
1303
2419
|
|
|
1304
2420
|
console.print(" [green]โ
.claude/ update complete[/green]")
|
|
1305
|
-
console.print(
|
|
1306
|
-
" [green]โ
.moai/ update complete (specs/reports preserved)[/green]"
|
|
1307
|
-
)
|
|
2421
|
+
console.print(" [green]โ
.moai/ update complete (specs/reports preserved)[/green]")
|
|
1308
2422
|
console.print(" [green]๐ CLAUDE.md merge complete[/green]")
|
|
1309
2423
|
console.print(" [green]๐ config.json merge complete[/green]")
|
|
1310
|
-
console.print(
|
|
1311
|
-
" [yellow]โ๏ธ Set optimized=false (optimization needed)[/yellow]"
|
|
1312
|
-
)
|
|
2424
|
+
console.print(" [yellow]โ๏ธ Set optimized=false (optimization needed)[/yellow]")
|
|
1313
2425
|
|
|
1314
2426
|
console.print("\n[green]โ Update complete![/green]")
|
|
1315
|
-
console.print(
|
|
1316
|
-
"[cyan]โน๏ธ Next step: Run /alfred:0-project update to optimize template changes[/cyan]"
|
|
1317
|
-
)
|
|
2427
|
+
console.print("[cyan]โน๏ธ Next step: Run /moai:0-project update to optimize template changes[/cyan]")
|
|
1318
2428
|
|
|
1319
2429
|
except Exception as e:
|
|
1320
2430
|
console.print(f"[red]โ Update failed: {e}[/red]")
|
|
1321
2431
|
raise click.ClickException(str(e)) from e
|
|
2432
|
+
|
|
2433
|
+
|
|
2434
|
+
def _handle_custom_element_restoration(project_path: Path, backup_path: Path | None, yes: bool = False) -> None:
|
|
2435
|
+
"""Handle custom element restoration using the enhanced system.
|
|
2436
|
+
|
|
2437
|
+
This function provides an improved interface for restoring user-created custom elements
|
|
2438
|
+
(agents, commands, skills, hooks) from backup during MoAI-ADK updates.
|
|
2439
|
+
|
|
2440
|
+
Key improvements:
|
|
2441
|
+
- Preserves unselected elements (fixes disappearing issue)
|
|
2442
|
+
- Only overwrites/creates selected elements from backup
|
|
2443
|
+
- Interactive checkbox selection with arrow key navigation
|
|
2444
|
+
- Includes all categories (Agents, Commands, Skills, Hooks)
|
|
2445
|
+
|
|
2446
|
+
Args:
|
|
2447
|
+
project_path: Path to the MoAI-ADK project directory
|
|
2448
|
+
backup_path: Path to the backup directory (None if no backup)
|
|
2449
|
+
yes: Whether to automatically accept defaults (non-interactive mode)
|
|
2450
|
+
"""
|
|
2451
|
+
if not backup_path:
|
|
2452
|
+
# No backup available, cannot restore
|
|
2453
|
+
return
|
|
2454
|
+
|
|
2455
|
+
try:
|
|
2456
|
+
# Create scanner to find custom elements in backup (not current project)
|
|
2457
|
+
backup_scanner = create_custom_element_scanner(backup_path)
|
|
2458
|
+
|
|
2459
|
+
# Get count of custom elements in backup
|
|
2460
|
+
backup_element_count = backup_scanner.get_element_count()
|
|
2461
|
+
|
|
2462
|
+
if backup_element_count == 0:
|
|
2463
|
+
# No custom elements found in backup
|
|
2464
|
+
console.print("[green]โ No custom elements found in backup to restore[/green]")
|
|
2465
|
+
return
|
|
2466
|
+
|
|
2467
|
+
# Create enhanced user selection UI
|
|
2468
|
+
ui = create_user_selection_ui(project_path)
|
|
2469
|
+
|
|
2470
|
+
console.print(f"\n[cyan]๐ Found {backup_element_count} custom elements in backup[/cyan]")
|
|
2471
|
+
|
|
2472
|
+
# If yes mode is enabled, restore all elements automatically
|
|
2473
|
+
if yes:
|
|
2474
|
+
console.print(f"[cyan]๐ Auto-restoring {backup_element_count} custom elements...[/cyan]")
|
|
2475
|
+
backup_custom_elements = backup_scanner.scan_custom_elements()
|
|
2476
|
+
selected_elements = []
|
|
2477
|
+
|
|
2478
|
+
# Collect all element paths from backup
|
|
2479
|
+
for element_type, elements in backup_custom_elements.items():
|
|
2480
|
+
if element_type == "skills":
|
|
2481
|
+
for skill in elements:
|
|
2482
|
+
selected_elements.append(str(skill.path))
|
|
2483
|
+
else:
|
|
2484
|
+
for element_path in elements:
|
|
2485
|
+
selected_elements.append(str(element_path))
|
|
2486
|
+
else:
|
|
2487
|
+
# Interactive mode - prompt user for selection using enhanced UI
|
|
2488
|
+
selected_elements = ui.prompt_user_selection(backup_available=True)
|
|
2489
|
+
|
|
2490
|
+
if not selected_elements:
|
|
2491
|
+
console.print("[yellow]โ No elements selected for restoration[/yellow]")
|
|
2492
|
+
console.print("[green]โ All existing custom elements will be preserved[/green]")
|
|
2493
|
+
return
|
|
2494
|
+
|
|
2495
|
+
# Confirm selection
|
|
2496
|
+
if not ui.confirm_selection(selected_elements):
|
|
2497
|
+
console.print("[yellow]โ Restoration cancelled by user[/yellow]")
|
|
2498
|
+
console.print("[green]โ All existing custom elements will be preserved[/green]")
|
|
2499
|
+
return
|
|
2500
|
+
|
|
2501
|
+
# Perform selective restoration - ONLY restore selected elements
|
|
2502
|
+
if selected_elements:
|
|
2503
|
+
console.print(f"[cyan]๐ Restoring {len(selected_elements)} selected elements from backup...[/cyan]")
|
|
2504
|
+
restorer = create_selective_restorer(project_path, backup_path)
|
|
2505
|
+
success, stats = restorer.restore_elements(selected_elements)
|
|
2506
|
+
|
|
2507
|
+
if success:
|
|
2508
|
+
console.print(f"[green]โ
Successfully restored {stats['success']} custom elements[/green]")
|
|
2509
|
+
console.print("[green]โ All unselected elements remain preserved[/green]")
|
|
2510
|
+
else:
|
|
2511
|
+
console.print(f"[yellow]โ ๏ธ Partial restoration: {stats['success']}/{stats['total']} elements[/yellow]")
|
|
2512
|
+
if stats["failed"] > 0:
|
|
2513
|
+
console.print(f"[red]โ Failed to restore {stats['failed']} elements[/red]")
|
|
2514
|
+
console.print("[yellow]โ ๏ธ All other elements remain preserved[/yellow]")
|
|
2515
|
+
else:
|
|
2516
|
+
console.print("[green]โ No elements selected, all custom elements preserved[/green]")
|
|
2517
|
+
|
|
2518
|
+
except Exception as e:
|
|
2519
|
+
console.print(f"[yellow]โ ๏ธ Custom element restoration failed: {e}[/yellow]")
|
|
2520
|
+
logger.warning(f"Custom element restoration error: {e}")
|
|
2521
|
+
console.print("[yellow]โ ๏ธ All existing custom elements remain as-is[/yellow]")
|
|
2522
|
+
# Don't fail the entire update process, just log the error
|
|
2523
|
+
pass
|
|
2524
|
+
|
|
2525
|
+
|
|
2526
|
+
def _cleanup_legacy_presets(project_path: Path) -> None:
|
|
2527
|
+
"""Remove legacy JSON preset files (not the entire directory).
|
|
2528
|
+
|
|
2529
|
+
This function cleans up obsolete .json preset files in .moai/config/presets/
|
|
2530
|
+
that may exist from previous versions of MoAI-ADK. The directory itself
|
|
2531
|
+
and .yaml files are preserved since they are part of the current template.
|
|
2532
|
+
|
|
2533
|
+
Args:
|
|
2534
|
+
project_path: Project directory path (absolute)
|
|
2535
|
+
"""
|
|
2536
|
+
presets_dir = project_path / ".moai" / "config" / "presets"
|
|
2537
|
+
|
|
2538
|
+
if not presets_dir.exists() or not presets_dir.is_dir():
|
|
2539
|
+
return
|
|
2540
|
+
|
|
2541
|
+
# Only remove legacy .json files, preserve .yaml files and the directory
|
|
2542
|
+
json_files = list(presets_dir.glob("*.json"))
|
|
2543
|
+
if not json_files:
|
|
2544
|
+
return
|
|
2545
|
+
|
|
2546
|
+
removed_count = 0
|
|
2547
|
+
for json_file in json_files:
|
|
2548
|
+
try:
|
|
2549
|
+
json_file.unlink()
|
|
2550
|
+
removed_count += 1
|
|
2551
|
+
logger.debug(f"Removed legacy preset file: {json_file}")
|
|
2552
|
+
except Exception as e:
|
|
2553
|
+
logger.warning(f"Failed to remove legacy preset {json_file}: {e}")
|
|
2554
|
+
|
|
2555
|
+
if removed_count > 0:
|
|
2556
|
+
console.print(f" [cyan]๐งน Cleaned up {removed_count} legacy JSON preset files[/cyan]")
|
|
2557
|
+
logger.info(f"Removed {removed_count} legacy JSON preset files from {presets_dir}")
|
|
2558
|
+
|
|
2559
|
+
|
|
2560
|
+
def _migrate_config_json_to_yaml(project_path: Path) -> bool:
|
|
2561
|
+
"""Migrate legacy config.json to config.yaml format.
|
|
2562
|
+
|
|
2563
|
+
This function:
|
|
2564
|
+
1. Checks if config.json exists
|
|
2565
|
+
2. Converts it to config.yaml using YAML format
|
|
2566
|
+
3. Removes the old config.json file
|
|
2567
|
+
4. Also migrates preset files from JSON to YAML
|
|
2568
|
+
|
|
2569
|
+
Args:
|
|
2570
|
+
project_path: Project directory path (absolute)
|
|
2571
|
+
|
|
2572
|
+
Returns:
|
|
2573
|
+
bool: True if migration successful or not needed, False on error
|
|
2574
|
+
"""
|
|
2575
|
+
try:
|
|
2576
|
+
import yaml
|
|
2577
|
+
except ImportError:
|
|
2578
|
+
console.print(" [yellow]โ ๏ธ PyYAML not available, skipping config migration[/yellow]")
|
|
2579
|
+
return True # Not a critical error
|
|
2580
|
+
|
|
2581
|
+
config_dir = project_path / ".moai" / "config"
|
|
2582
|
+
json_path = config_dir / "config.json"
|
|
2583
|
+
yaml_path = config_dir / "config.yaml"
|
|
2584
|
+
|
|
2585
|
+
# Check if migration needed
|
|
2586
|
+
if not json_path.exists():
|
|
2587
|
+
# No JSON file, migration not needed
|
|
2588
|
+
return True
|
|
2589
|
+
|
|
2590
|
+
if yaml_path.exists():
|
|
2591
|
+
# YAML already exists, just remove JSON
|
|
2592
|
+
try:
|
|
2593
|
+
json_path.unlink()
|
|
2594
|
+
console.print(" [cyan]๐ Removed legacy config.json (YAML version exists)[/cyan]")
|
|
2595
|
+
logger.info(f"Removed legacy config.json: {json_path}")
|
|
2596
|
+
return True
|
|
2597
|
+
except Exception as e:
|
|
2598
|
+
console.print(f" [yellow]โ ๏ธ Failed to remove legacy config.json: {e}[/yellow]")
|
|
2599
|
+
logger.warning(f"Failed to remove {json_path}: {e}")
|
|
2600
|
+
return True # Not critical
|
|
2601
|
+
|
|
2602
|
+
# Perform migration
|
|
2603
|
+
try:
|
|
2604
|
+
# Read JSON config
|
|
2605
|
+
with open(json_path, "r", encoding="utf-8") as f:
|
|
2606
|
+
config_data = json.load(f)
|
|
2607
|
+
|
|
2608
|
+
# Write YAML config
|
|
2609
|
+
with open(yaml_path, "w", encoding="utf-8") as f:
|
|
2610
|
+
yaml.safe_dump(
|
|
2611
|
+
config_data,
|
|
2612
|
+
f,
|
|
2613
|
+
default_flow_style=False,
|
|
2614
|
+
allow_unicode=True,
|
|
2615
|
+
sort_keys=False,
|
|
2616
|
+
)
|
|
2617
|
+
|
|
2618
|
+
# Remove old JSON file
|
|
2619
|
+
json_path.unlink()
|
|
2620
|
+
|
|
2621
|
+
console.print(" [green]โ Migrated config.json โ config.yaml[/green]")
|
|
2622
|
+
logger.info(f"Migrated config from JSON to YAML: {json_path} โ {yaml_path}")
|
|
2623
|
+
|
|
2624
|
+
# Migrate preset files if they exist
|
|
2625
|
+
_migrate_preset_files_to_yaml(config_dir)
|
|
2626
|
+
|
|
2627
|
+
return True
|
|
2628
|
+
|
|
2629
|
+
except Exception as e:
|
|
2630
|
+
console.print(f" [red]โ Config migration failed: {e}[/red]")
|
|
2631
|
+
logger.error(f"Failed to migrate config.json to YAML: {e}")
|
|
2632
|
+
return False
|
|
2633
|
+
|
|
2634
|
+
|
|
2635
|
+
def _migrate_preset_files_to_yaml(config_dir: Path) -> None:
|
|
2636
|
+
"""Migrate preset files from JSON to YAML format.
|
|
2637
|
+
|
|
2638
|
+
Args:
|
|
2639
|
+
config_dir: .moai/config directory path
|
|
2640
|
+
"""
|
|
2641
|
+
try:
|
|
2642
|
+
import yaml
|
|
2643
|
+
except ImportError:
|
|
2644
|
+
return
|
|
2645
|
+
|
|
2646
|
+
presets_dir = config_dir / "presets"
|
|
2647
|
+
if not presets_dir.exists():
|
|
2648
|
+
return
|
|
2649
|
+
|
|
2650
|
+
migrated_count = 0
|
|
2651
|
+
for json_file in presets_dir.glob("*.json"):
|
|
2652
|
+
yaml_file = json_file.with_suffix(".yaml")
|
|
2653
|
+
|
|
2654
|
+
# Skip if YAML already exists
|
|
2655
|
+
if yaml_file.exists():
|
|
2656
|
+
# Just remove the JSON file
|
|
2657
|
+
try:
|
|
2658
|
+
json_file.unlink()
|
|
2659
|
+
migrated_count += 1
|
|
2660
|
+
except Exception as e:
|
|
2661
|
+
logger.warning(f"Failed to remove {json_file}: {e}")
|
|
2662
|
+
continue
|
|
2663
|
+
|
|
2664
|
+
# Migrate JSON โ YAML
|
|
2665
|
+
try:
|
|
2666
|
+
with open(json_file, "r", encoding="utf-8") as f:
|
|
2667
|
+
preset_data = json.load(f)
|
|
2668
|
+
|
|
2669
|
+
with open(yaml_file, "w", encoding="utf-8") as f:
|
|
2670
|
+
yaml.safe_dump(
|
|
2671
|
+
preset_data,
|
|
2672
|
+
f,
|
|
2673
|
+
default_flow_style=False,
|
|
2674
|
+
allow_unicode=True,
|
|
2675
|
+
sort_keys=False,
|
|
2676
|
+
)
|
|
2677
|
+
|
|
2678
|
+
json_file.unlink()
|
|
2679
|
+
migrated_count += 1
|
|
2680
|
+
|
|
2681
|
+
except Exception as e:
|
|
2682
|
+
logger.warning(f"Failed to migrate preset {json_file}: {e}")
|
|
2683
|
+
|
|
2684
|
+
if migrated_count > 0:
|
|
2685
|
+
console.print(f" [cyan]๐ Migrated {migrated_count} preset file(s) to YAML[/cyan]")
|
|
2686
|
+
logger.info(f"Migrated {migrated_count} preset files to YAML")
|