moai-adk 0.35.1__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 +10 -0
- moai_adk/__main__.py +199 -0
- moai_adk/cli/__init__.py +6 -0
- moai_adk/cli/commands/__init__.py +17 -0
- moai_adk/cli/commands/analyze.py +116 -0
- moai_adk/cli/commands/doctor.py +272 -0
- moai_adk/cli/commands/init.py +372 -0
- moai_adk/cli/commands/language.py +248 -0
- moai_adk/cli/commands/status.py +104 -0
- moai_adk/cli/commands/update.py +2686 -0
- moai_adk/cli/main.py +13 -0
- moai_adk/cli/prompts/__init__.py +5 -0
- moai_adk/cli/prompts/init_prompts.py +219 -0
- 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 +683 -0
- moai_adk/cli/worktree/exceptions.py +89 -0
- moai_adk/cli/worktree/manager.py +493 -0
- moai_adk/cli/worktree/models.py +65 -0
- moai_adk/cli/worktree/registry.py +422 -0
- moai_adk/core/PHASE2_OPTIMIZATIONS.md +467 -0
- moai_adk/core/__init__.py +1 -0
- moai_adk/core/analysis/__init__.py +9 -0
- moai_adk/core/analysis/session_analyzer.py +400 -0
- moai_adk/core/claude_integration.py +393 -0
- moai_adk/core/command_helpers.py +270 -0
- moai_adk/core/comprehensive_monitoring_system.py +1183 -0
- moai_adk/core/config/__init__.py +19 -0
- moai_adk/core/config/auto_spec_config.py +340 -0
- moai_adk/core/config/migration.py +244 -0
- moai_adk/core/config/unified.py +436 -0
- moai_adk/core/context_manager.py +273 -0
- moai_adk/core/diagnostics/__init__.py +19 -0
- moai_adk/core/diagnostics/slash_commands.py +159 -0
- moai_adk/core/enterprise_features.py +1404 -0
- moai_adk/core/error_recovery_system.py +1902 -0
- moai_adk/core/event_driven_hook_system.py +1371 -0
- moai_adk/core/git/__init__.py +31 -0
- moai_adk/core/git/branch.py +25 -0
- moai_adk/core/git/branch_manager.py +129 -0
- moai_adk/core/git/checkpoint.py +134 -0
- moai_adk/core/git/commit.py +67 -0
- moai_adk/core/git/conflict_detector.py +413 -0
- moai_adk/core/git/event_detector.py +79 -0
- moai_adk/core/git/manager.py +216 -0
- moai_adk/core/hooks/post_tool_auto_spec_completion.py +901 -0
- moai_adk/core/input_validation_middleware.py +1006 -0
- moai_adk/core/integration/__init__.py +22 -0
- moai_adk/core/integration/engine.py +157 -0
- moai_adk/core/integration/integration_tester.py +226 -0
- moai_adk/core/integration/models.py +88 -0
- moai_adk/core/integration/utils.py +211 -0
- moai_adk/core/issue_creator.py +305 -0
- moai_adk/core/jit_context_loader.py +956 -0
- moai_adk/core/jit_enhanced_hook_manager.py +1987 -0
- moai_adk/core/language_config.py +202 -0
- moai_adk/core/language_config_resolver.py +572 -0
- moai_adk/core/language_validator.py +543 -0
- moai_adk/core/mcp/setup.py +116 -0
- moai_adk/core/merge/__init__.py +9 -0
- moai_adk/core/merge/analyzer.py +605 -0
- moai_adk/core/migration/__init__.py +18 -0
- moai_adk/core/migration/alfred_to_moai_migrator.py +383 -0
- moai_adk/core/migration/backup_manager.py +277 -0
- moai_adk/core/migration/custom_element_scanner.py +358 -0
- moai_adk/core/migration/file_migrator.py +209 -0
- 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 +139 -0
- moai_adk/core/migration/version_migrator.py +228 -0
- moai_adk/core/performance/__init__.py +6 -0
- moai_adk/core/performance/cache_system.py +316 -0
- moai_adk/core/performance/parallel_processor.py +116 -0
- moai_adk/core/phase_optimized_hook_scheduler.py +879 -0
- moai_adk/core/project/__init__.py +1 -0
- moai_adk/core/project/backup_utils.py +70 -0
- moai_adk/core/project/checker.py +300 -0
- moai_adk/core/project/detector.py +293 -0
- moai_adk/core/project/initializer.py +387 -0
- moai_adk/core/project/phase_executor.py +716 -0
- moai_adk/core/project/validator.py +139 -0
- moai_adk/core/quality/__init__.py +6 -0
- moai_adk/core/quality/trust_checker.py +377 -0
- moai_adk/core/quality/validators/__init__.py +6 -0
- moai_adk/core/quality/validators/base_validator.py +19 -0
- moai_adk/core/realtime_monitoring_dashboard.py +1724 -0
- moai_adk/core/robust_json_parser.py +611 -0
- moai_adk/core/rollback_manager.py +918 -0
- moai_adk/core/session_manager.py +651 -0
- moai_adk/core/skill_loading_system.py +579 -0
- moai_adk/core/spec/confidence_scoring.py +680 -0
- moai_adk/core/spec/ears_template_engine.py +1247 -0
- moai_adk/core/spec/quality_validator.py +687 -0
- moai_adk/core/spec_status_manager.py +478 -0
- moai_adk/core/template/__init__.py +7 -0
- moai_adk/core/template/backup.py +174 -0
- moai_adk/core/template/config.py +191 -0
- moai_adk/core/template/languages.py +43 -0
- moai_adk/core/template/merger.py +233 -0
- moai_adk/core/template/processor.py +1200 -0
- moai_adk/core/template_engine.py +310 -0
- 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 +557 -0
- 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 +676 -0
- moai_adk/foundation/trust/validation_checklist.py +1573 -0
- 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/__init__.py +38 -0
- moai_adk/statusline/alfred_detector.py +105 -0
- moai_adk/statusline/config.py +376 -0
- moai_adk/statusline/enhanced_output_style_detector.py +372 -0
- moai_adk/statusline/git_collector.py +190 -0
- moai_adk/statusline/main.py +322 -0
- moai_adk/statusline/metrics_tracker.py +78 -0
- moai_adk/statusline/renderer.py +343 -0
- moai_adk/statusline/update_checker.py +129 -0
- moai_adk/statusline/version_reader.py +741 -0
- moai_adk/templates/.claude/agents/moai/ai-nano-banana.md +714 -0
- moai_adk/templates/.claude/agents/moai/builder-agent.md +474 -0
- moai_adk/templates/.claude/agents/moai/builder-command.md +1172 -0
- moai_adk/templates/.claude/agents/moai/builder-plugin.md +637 -0
- moai_adk/templates/.claude/agents/moai/builder-skill.md +666 -0
- moai_adk/templates/.claude/agents/moai/expert-backend.md +899 -0
- moai_adk/templates/.claude/agents/moai/expert-database.md +777 -0
- moai_adk/templates/.claude/agents/moai/expert-debug.md +401 -0
- moai_adk/templates/.claude/agents/moai/expert-devops.md +720 -0
- moai_adk/templates/.claude/agents/moai/expert-frontend.md +734 -0
- moai_adk/templates/.claude/agents/moai/expert-performance.md +657 -0
- moai_adk/templates/.claude/agents/moai/expert-security.md +513 -0
- moai_adk/templates/.claude/agents/moai/expert-testing.md +733 -0
- moai_adk/templates/.claude/agents/moai/expert-uiux.md +1041 -0
- moai_adk/templates/.claude/agents/moai/manager-claude-code.md +432 -0
- moai_adk/templates/.claude/agents/moai/manager-docs.md +573 -0
- moai_adk/templates/.claude/agents/moai/manager-git.md +1060 -0
- moai_adk/templates/.claude/agents/moai/manager-project.md +891 -0
- moai_adk/templates/.claude/agents/moai/manager-quality.md +624 -0
- moai_adk/templates/.claude/agents/moai/manager-spec.md +809 -0
- moai_adk/templates/.claude/agents/moai/manager-strategy.md +780 -0
- moai_adk/templates/.claude/agents/moai/manager-tdd.md +784 -0
- moai_adk/templates/.claude/agents/moai/mcp-context7.md +458 -0
- moai_adk/templates/.claude/agents/moai/mcp-figma.md +1607 -0
- moai_adk/templates/.claude/agents/moai/mcp-notion.md +789 -0
- moai_adk/templates/.claude/agents/moai/mcp-playwright.md +469 -0
- moai_adk/templates/.claude/agents/moai/mcp-sequential-thinking.md +1032 -0
- moai_adk/templates/.claude/commands/moai/0-project.md +1386 -0
- moai_adk/templates/.claude/commands/moai/1-plan.md +1427 -0
- moai_adk/templates/.claude/commands/moai/2-run.md +943 -0
- moai_adk/templates/.claude/commands/moai/3-sync.md +1324 -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 +1083 -0
- moai_adk/templates/.claude/output-styles/moai/r2d2.md +560 -0
- moai_adk/templates/.claude/output-styles/moai/yoda.md +359 -0
- moai_adk/templates/.claude/settings.json +172 -0
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/SKILL.md +307 -0
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/examples.md +431 -0
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/scripts/batch_generate.py +560 -0
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/scripts/generate_image.py +362 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/SKILL.md +249 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/examples.md +406 -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-docs-generation/reference.md +328 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/SKILL.md +320 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/examples.md +718 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/reference.md +464 -0
- moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +323 -0
- moai_adk/templates/.claude/skills/moai-domain-database/examples.md +830 -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-database/reference.md +545 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +497 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/examples.md +968 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/reference.md +664 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/SKILL.md +455 -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/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 +492 -0
- moai_adk/templates/.claude/skills/moai-formats-data/examples.md +804 -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-formats-data/reference.md +585 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/SKILL.md +202 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/examples.md +732 -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-claude/reference.md +209 -0
- moai_adk/templates/.claude/skills/moai-foundation-context/SKILL.md +441 -0
- moai_adk/templates/.claude/skills/moai-foundation-context/examples.md +1048 -0
- moai_adk/templates/.claude/skills/moai-foundation-context/reference.md +246 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/SKILL.md +420 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/examples.md +358 -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 +359 -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-core/reference.md +478 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/SKILL.md +315 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/examples.md +228 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/modules/assumption-matrix.md +80 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/modules/cognitive-bias.md +199 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/modules/first-principles.md +140 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/modules/trade-off-analysis.md +154 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/reference.md +157 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/SKILL.md +364 -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-lang-cpp/SKILL.md +649 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/SKILL.md +478 -0
- moai_adk/templates/.claude/skills/moai-lang-elixir/SKILL.md +612 -0
- moai_adk/templates/.claude/skills/moai-lang-flutter/SKILL.md +477 -0
- moai_adk/templates/.claude/skills/moai-lang-flutter/examples.md +1090 -0
- moai_adk/templates/.claude/skills/moai-lang-flutter/reference.md +686 -0
- moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +376 -0
- moai_adk/templates/.claude/skills/moai-lang-go/examples.md +919 -0
- moai_adk/templates/.claude/skills/moai-lang-go/reference.md +737 -0
- moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +385 -0
- moai_adk/templates/.claude/skills/moai-lang-java/examples.md +864 -0
- moai_adk/templates/.claude/skills/moai-lang-java/reference.md +291 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/SKILL.md +382 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/examples.md +1006 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/reference.md +562 -0
- moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +644 -0
- moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +481 -0
- moai_adk/templates/.claude/skills/moai-lang-python/examples.md +977 -0
- moai_adk/templates/.claude/skills/moai-lang-python/reference.md +804 -0
- moai_adk/templates/.claude/skills/moai-lang-r/SKILL.md +579 -0
- moai_adk/templates/.claude/skills/moai-lang-ruby/SKILL.md +687 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +372 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/examples.md +659 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/reference.md +504 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +497 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/examples.md +633 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/reference.md +423 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/SKILL.md +497 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/examples.md +918 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/reference.md +672 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +368 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/examples.md +1089 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/reference.md +731 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/SKILL.md +300 -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 +319 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/advanced-patterns.md +336 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/examples.md +592 -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-nextra/reference.md +379 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/SKILL.md +372 -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-mcp-figma/SKILL.md +402 -0
- moai_adk/templates/.claude/skills/moai-mcp-figma/advanced-patterns.md +607 -0
- moai_adk/templates/.claude/skills/moai-mcp-notion/SKILL.md +300 -0
- moai_adk/templates/.claude/skills/moai-mcp-notion/advanced-patterns.md +537 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/SKILL.md +291 -0
- moai_adk/templates/.claude/skills/moai-platform-clerk/SKILL.md +390 -0
- moai_adk/templates/.claude/skills/moai-platform-convex/SKILL.md +398 -0
- moai_adk/templates/.claude/skills/moai-platform-firebase-auth/SKILL.md +379 -0
- moai_adk/templates/.claude/skills/moai-platform-firestore/SKILL.md +358 -0
- moai_adk/templates/.claude/skills/moai-platform-neon/SKILL.md +467 -0
- moai_adk/templates/.claude/skills/moai-platform-railway/SKILL.md +377 -0
- moai_adk/templates/.claude/skills/moai-platform-supabase/SKILL.md +466 -0
- moai_adk/templates/.claude/skills/moai-platform-vercel/SKILL.md +482 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/SKILL.md +474 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/examples.md +621 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/migration.md +341 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/reference.md +463 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/validation.md +373 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/SKILL.md +275 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/adaptive-mfa.md +233 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/akamai-integration.md +215 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/application-credentials.md +280 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/attack-protection-log-events.md +225 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/attack-protection-overview.md +140 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/bot-detection.md +144 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/breached-password-detection.md +187 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/brute-force-protection.md +189 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/certifications.md +282 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/compliance-overview.md +263 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/continuous-session-protection.md +307 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/customize-mfa.md +178 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/dpop-implementation.md +283 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/fapi-implementation.md +259 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/gdpr-compliance.md +313 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/guardian-configuration.md +269 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/highly-regulated-identity.md +272 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/jwt-fundamentals.md +248 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mdl-verification.md +211 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mfa-api-management.md +278 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mfa-factors.md +226 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mfa-overview.md +174 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mtls-sender-constraining.md +316 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/ropg-flow-mfa.md +217 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/security-center.md +325 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/security-guidance.md +277 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/state-parameters.md +178 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/step-up-authentication.md +251 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/suspicious-ip-throttling.md +240 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/tenant-access-control.md +180 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/webauthn-fido.md +235 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/SKILL.md +449 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/advanced-patterns.md +379 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/examples.md +544 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/optimization.md +286 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/reference.md +307 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/README.md +190 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/SKILL.md +390 -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 +175 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/quick_start.py +196 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples.md +547 -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/reference.md +275 -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 +1434 -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 +92 -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-spec/SKILL.md +534 -0
- moai_adk/templates/.claude/skills/moai-workflow-spec/examples.md +900 -0
- moai_adk/templates/.claude/skills/moai-workflow-spec/reference.md +704 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/SKILL.md +377 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/examples.md +552 -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-templates/reference.md +346 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/LICENSE.txt +202 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/SKILL.md +456 -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/examples.md +672 -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/reference.md +440 -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 +411 -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 +128 -0
- moai_adk/templates/.git-hooks/pre-push +365 -0
- moai_adk/templates/.github/workflows/ci-universal.yml +513 -0
- moai_adk/templates/.github/workflows/security-secrets-check.yml +179 -0
- moai_adk/templates/.github/workflows/spec-issue-sync.yml +337 -0
- moai_adk/templates/.gitignore +222 -0
- moai_adk/templates/.mcp.json +13 -0
- moai_adk/templates/.moai/config/config.yaml +58 -0
- moai_adk/templates/.moai/config/questions/_schema.yaml +174 -0
- moai_adk/templates/.moai/config/questions/tab0-init.yaml +251 -0
- moai_adk/templates/.moai/config/questions/tab1-user.yaml +107 -0
- moai_adk/templates/.moai/config/questions/tab2-project.yaml +79 -0
- moai_adk/templates/.moai/config/questions/tab3-git.yaml +632 -0
- moai_adk/templates/.moai/config/questions/tab4-quality.yaml +182 -0
- moai_adk/templates/.moai/config/questions/tab5-system.yaml +96 -0
- moai_adk/templates/.moai/config/sections/git-strategy.yaml +116 -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 +17 -0
- moai_adk/templates/.moai/config/sections/system.yaml +24 -0
- moai_adk/templates/.moai/config/sections/user.yaml +5 -0
- moai_adk/templates/.moai/config/statusline-config.yaml +92 -0
- moai_adk/templates/.moai/scripts/setup-glm.py +136 -0
- moai_adk/templates/CLAUDE.md +642 -0
- moai_adk/utils/__init__.py +30 -0
- moai_adk/utils/banner.py +38 -0
- moai_adk/utils/common.py +294 -0
- moai_adk/utils/link_validator.py +241 -0
- moai_adk/utils/logger.py +147 -0
- moai_adk/utils/safe_file_reader.py +206 -0
- moai_adk/utils/timeout.py +160 -0
- moai_adk/utils/toon_utils.py +256 -0
- moai_adk/version.py +22 -0
- moai_adk-0.35.1.dist-info/METADATA +3018 -0
- moai_adk-0.35.1.dist-info/RECORD +502 -0
- moai_adk-0.35.1.dist-info/WHEEL +4 -0
- moai_adk-0.35.1.dist-info/entry_points.txt +3 -0
- moai_adk-0.35.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,1200 @@
|
|
|
1
|
+
# # REMOVED_ORPHAN_CODE:TEMPLATE-001 | SPEC: SPEC-INIT-003/spec.md | Chain: TEMPLATE-001
|
|
2
|
+
"""Enhanced Template copy and backup processor with improved version handling and validation.
|
|
3
|
+
|
|
4
|
+
SPEC-INIT-003 v0.3.0: preserve user content
|
|
5
|
+
Enhanced with:
|
|
6
|
+
- Comprehensive version field management
|
|
7
|
+
- Template substitution validation
|
|
8
|
+
- Performance optimization
|
|
9
|
+
- Error handling improvements
|
|
10
|
+
- Configuration-driven behavior
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import re
|
|
18
|
+
import shutil
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Dict, List, Optional
|
|
22
|
+
|
|
23
|
+
from rich.console import Console
|
|
24
|
+
|
|
25
|
+
from moai_adk.core.template.backup import TemplateBackup
|
|
26
|
+
from moai_adk.core.template.merger import TemplateMerger
|
|
27
|
+
from moai_adk.statusline.version_reader import VersionConfig, VersionReader
|
|
28
|
+
|
|
29
|
+
console = Console()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class TemplateProcessorConfig:
|
|
34
|
+
"""Configuration for TemplateProcessor behavior."""
|
|
35
|
+
|
|
36
|
+
# Version handling configuration
|
|
37
|
+
version_cache_ttl_seconds: int = 120
|
|
38
|
+
version_fallback: str = "unknown"
|
|
39
|
+
version_format_regex: str = r"^v?(\d+\.\d+\.\d+(-[a-zA-Z0-9]+)?)$"
|
|
40
|
+
enable_version_validation: bool = True
|
|
41
|
+
preserve_user_version: bool = True
|
|
42
|
+
|
|
43
|
+
# Template substitution configuration
|
|
44
|
+
validate_template_variables: bool = True
|
|
45
|
+
max_variable_length: int = 50
|
|
46
|
+
allowed_variable_pattern: str = r"^[A-Z_]+$"
|
|
47
|
+
enable_substitution_warnings: bool = True
|
|
48
|
+
|
|
49
|
+
# Performance configuration
|
|
50
|
+
enable_caching: bool = True
|
|
51
|
+
cache_size: int = 100
|
|
52
|
+
async_operations: bool = False
|
|
53
|
+
|
|
54
|
+
# Error handling configuration
|
|
55
|
+
graceful_degradation: bool = True
|
|
56
|
+
verbose_logging: bool = False
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def from_dict(cls, config_dict: Dict[str, Any]) -> "TemplateProcessorConfig":
|
|
60
|
+
"""Create config from dictionary."""
|
|
61
|
+
config_dict = config_dict or {}
|
|
62
|
+
return cls(
|
|
63
|
+
version_cache_ttl_seconds=config_dict.get("version_cache_ttl_seconds", 120),
|
|
64
|
+
version_fallback=config_dict.get("version_fallback", "unknown"),
|
|
65
|
+
version_format_regex=config_dict.get("version_format_regex", r"^v?(\d+\.\d+\.\d+(-[a-zA-Z0-9]+)?)$"),
|
|
66
|
+
enable_version_validation=config_dict.get("enable_version_validation", True),
|
|
67
|
+
preserve_user_version=config_dict.get("preserve_user_version", True),
|
|
68
|
+
validate_template_variables=config_dict.get("validate_template_variables", True),
|
|
69
|
+
max_variable_length=config_dict.get("max_variable_length", 50),
|
|
70
|
+
allowed_variable_pattern=config_dict.get("allowed_variable_pattern", r"^[A-Z_]+$"),
|
|
71
|
+
enable_substitution_warnings=config_dict.get("enable_substitution_warnings", True),
|
|
72
|
+
enable_caching=config_dict.get("enable_caching", True),
|
|
73
|
+
cache_size=config_dict.get("cache_size", 100),
|
|
74
|
+
async_operations=config_dict.get("async_operations", False),
|
|
75
|
+
graceful_degradation=config_dict.get("graceful_degradation", True),
|
|
76
|
+
verbose_logging=config_dict.get("verbose_logging", False),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TemplateProcessor:
|
|
81
|
+
"""Orchestrate template copying and backups with enhanced version handling and validation."""
|
|
82
|
+
|
|
83
|
+
# User data protection paths (never touch) - SPEC-INIT-003 v0.3.0
|
|
84
|
+
PROTECTED_PATHS = [
|
|
85
|
+
".moai/specs/", # User SPEC documents
|
|
86
|
+
".moai/reports/", # User reports
|
|
87
|
+
".moai/project/", # User project documents (product/structure/tech.md)
|
|
88
|
+
# config.json is now FORCE OVERWRITTEN (backup in .moai-backups/)
|
|
89
|
+
# Merge via /moai:0-project when optimized=false
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
# Paths excluded from backups
|
|
93
|
+
BACKUP_EXCLUDE = PROTECTED_PATHS
|
|
94
|
+
|
|
95
|
+
# Common template variables with validation hints
|
|
96
|
+
COMMON_TEMPLATE_VARIABLES = {
|
|
97
|
+
"PROJECT_DIR": "Cross-platform project path (run /moai:0-project to set)",
|
|
98
|
+
"PROJECT_NAME": "Project name (run /moai:0-project to set)",
|
|
99
|
+
"AUTHOR": "Project author (run /moai:0-project to set)",
|
|
100
|
+
"CONVERSATION_LANGUAGE": "Interface language (run /moai:0-project to set)",
|
|
101
|
+
"MOAI_VERSION": "MoAI-ADK version (should be set automatically)",
|
|
102
|
+
"MOAI_VERSION_SHORT": "Short MoAI-ADK version (without 'v' prefix)",
|
|
103
|
+
"MOAI_VERSION_DISPLAY": "Display version with proper formatting",
|
|
104
|
+
"MOAI_VERSION_TRIMMED": "Trimmed version for UI displays",
|
|
105
|
+
"MOAI_VERSION_SEMVER": "Semantic version format (major.minor.patch)",
|
|
106
|
+
"MOAI_VERSION_VALID": "Version validation status",
|
|
107
|
+
"MOAI_VERSION_SOURCE": "Version source information",
|
|
108
|
+
"MOAI_VERSION_CACHE_AGE": "Cache age for debugging",
|
|
109
|
+
"CREATION_TIMESTAMP": "Project creation timestamp",
|
|
110
|
+
"STATUSLINE_COMMAND": "Cross-platform statusline command (OS-specific)",
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
def __init__(self, target_path: Path, config: Optional[TemplateProcessorConfig] = None) -> None:
|
|
114
|
+
"""Initialize the processor with enhanced configuration.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
target_path: Project path.
|
|
118
|
+
config: Optional configuration for processor behavior.
|
|
119
|
+
"""
|
|
120
|
+
self.target_path = target_path.resolve()
|
|
121
|
+
self.template_root = self._get_template_root()
|
|
122
|
+
self.backup = TemplateBackup(self.target_path)
|
|
123
|
+
self.merger = TemplateMerger(self.target_path)
|
|
124
|
+
self.context: dict[str, str] = {} # Template variable substitution context
|
|
125
|
+
self._version_reader: VersionReader | None = None
|
|
126
|
+
self.config = config or TemplateProcessorConfig()
|
|
127
|
+
self._substitution_cache: Dict[
|
|
128
|
+
int, tuple[str, list[str]]
|
|
129
|
+
] = {} # Cache for substitution results (key: hash, value: (content, warnings))
|
|
130
|
+
self._variable_validation_cache: Dict[str, bool] = {} # Cache for variable validation
|
|
131
|
+
self.logger = logging.getLogger(__name__)
|
|
132
|
+
|
|
133
|
+
if self.config.verbose_logging:
|
|
134
|
+
self.logger.info(f"TemplateProcessor initialized with config: {self.config}")
|
|
135
|
+
|
|
136
|
+
def set_context(self, context: dict[str, str]) -> None:
|
|
137
|
+
"""Set variable substitution context with enhanced validation.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
context: Dictionary of template variables.
|
|
141
|
+
"""
|
|
142
|
+
self.context = context
|
|
143
|
+
self._substitution_cache.clear() # Clear cache when context changes
|
|
144
|
+
self._variable_validation_cache.clear()
|
|
145
|
+
|
|
146
|
+
if self.config.verbose_logging:
|
|
147
|
+
self.logger.debug(f"Context set with {len(context)} variables")
|
|
148
|
+
|
|
149
|
+
# Validate template variables if enabled
|
|
150
|
+
if self.config.validate_template_variables:
|
|
151
|
+
self._validate_template_variables(context)
|
|
152
|
+
|
|
153
|
+
# Add deprecation mapping for HOOK_PROJECT_DIR
|
|
154
|
+
if "PROJECT_DIR" in self.context and "HOOK_PROJECT_DIR" not in self.context:
|
|
155
|
+
self.context["HOOK_PROJECT_DIR"] = self.context["PROJECT_DIR"]
|
|
156
|
+
|
|
157
|
+
def _get_version_reader(self) -> VersionReader:
|
|
158
|
+
"""
|
|
159
|
+
Get or create version reader instance with enhanced configuration.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
VersionReader instance
|
|
163
|
+
"""
|
|
164
|
+
if self._version_reader is None:
|
|
165
|
+
version_config = VersionConfig(
|
|
166
|
+
cache_ttl_seconds=self.config.version_cache_ttl_seconds,
|
|
167
|
+
fallback_version=self.config.version_fallback,
|
|
168
|
+
version_format_regex=self.config.version_format_regex,
|
|
169
|
+
debug_mode=self.config.verbose_logging,
|
|
170
|
+
)
|
|
171
|
+
self._version_reader = VersionReader(version_config)
|
|
172
|
+
|
|
173
|
+
if self.config.verbose_logging:
|
|
174
|
+
self.logger.info("VersionReader created with enhanced configuration")
|
|
175
|
+
return self._version_reader
|
|
176
|
+
|
|
177
|
+
def _validate_template_variables(self, context: Dict[str, str]) -> None:
|
|
178
|
+
"""
|
|
179
|
+
Validate template variables with comprehensive checking.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
context: Dictionary of template variables to validate
|
|
183
|
+
"""
|
|
184
|
+
import re
|
|
185
|
+
|
|
186
|
+
if not self.config.validate_template_variables:
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
validation_errors: List[str] = []
|
|
190
|
+
warning_messages: List[str] = []
|
|
191
|
+
|
|
192
|
+
# Check variable names against pattern
|
|
193
|
+
variable_pattern = re.compile(self.config.allowed_variable_pattern)
|
|
194
|
+
|
|
195
|
+
for var_name, var_value in context.items():
|
|
196
|
+
# Check variable name format
|
|
197
|
+
if not variable_pattern.match(var_name):
|
|
198
|
+
validation_errors.append(f"Invalid variable name format: '{var_name}'")
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
# Check variable length
|
|
202
|
+
if len(var_name) > self.config.max_variable_length:
|
|
203
|
+
warning_messages.append(f"Variable name '{var_name}' exceeds maximum length")
|
|
204
|
+
|
|
205
|
+
# Check variable value length
|
|
206
|
+
if len(var_value) > self.config.max_variable_length * 2:
|
|
207
|
+
warning_messages.append(f"Variable value '{var_value[:20]}...' is very long")
|
|
208
|
+
|
|
209
|
+
# Check for potentially dangerous values
|
|
210
|
+
if "{{" in var_value or "}}" in var_value:
|
|
211
|
+
warning_messages.append(f"Variable '{var_name}' contains placeholder patterns")
|
|
212
|
+
|
|
213
|
+
# Check for common variables that should be present
|
|
214
|
+
missing_common_vars = []
|
|
215
|
+
for common_var in self.COMMON_TEMPLATE_VARIABLES:
|
|
216
|
+
if common_var not in context:
|
|
217
|
+
missing_common_vars.append(common_var)
|
|
218
|
+
|
|
219
|
+
if missing_common_vars and self.config.enable_substitution_warnings:
|
|
220
|
+
warning_messages.append(f"Common variables missing: {', '.join(missing_common_vars[:3])}")
|
|
221
|
+
|
|
222
|
+
# Report validation results
|
|
223
|
+
if validation_errors and not self.config.graceful_degradation:
|
|
224
|
+
raise ValueError(f"Template variable validation failed: {validation_errors}")
|
|
225
|
+
|
|
226
|
+
if validation_errors and self.config.graceful_degradation:
|
|
227
|
+
self.logger.warning(f"Template variable validation warnings: {validation_errors}")
|
|
228
|
+
|
|
229
|
+
if warning_messages and self.config.enable_substitution_warnings:
|
|
230
|
+
self.logger.warning(f"Template variable warnings: {warning_messages}")
|
|
231
|
+
|
|
232
|
+
if self.config.verbose_logging:
|
|
233
|
+
self.logger.debug(f"Template variables validated: {len(context)} variables checked")
|
|
234
|
+
|
|
235
|
+
def get_enhanced_version_context(self) -> dict[str, str]:
|
|
236
|
+
"""
|
|
237
|
+
Get enhanced version context with proper error handling and caching.
|
|
238
|
+
|
|
239
|
+
Returns comprehensive version information including multiple format options
|
|
240
|
+
and debugging information.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Dictionary containing enhanced version-related template variables
|
|
244
|
+
"""
|
|
245
|
+
version_context = {}
|
|
246
|
+
logger = logging.getLogger(__name__)
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
version_reader = self._get_version_reader()
|
|
250
|
+
moai_version = version_reader.get_version()
|
|
251
|
+
|
|
252
|
+
# Basic version information
|
|
253
|
+
version_context["MOAI_VERSION"] = moai_version
|
|
254
|
+
version_context["MOAI_VERSION_SHORT"] = self._format_short_version(moai_version)
|
|
255
|
+
version_context["MOAI_VERSION_DISPLAY"] = self._format_display_version(moai_version)
|
|
256
|
+
|
|
257
|
+
# Enhanced formatting options
|
|
258
|
+
version_context["MOAI_VERSION_TRIMMED"] = self._format_trimmed_version(moai_version, max_length=10)
|
|
259
|
+
version_context["MOAI_VERSION_SEMVER"] = self._format_semver_version(moai_version)
|
|
260
|
+
|
|
261
|
+
# Validation and source information
|
|
262
|
+
version_context["MOAI_VERSION_VALID"] = "true" if moai_version != "unknown" else "false"
|
|
263
|
+
version_context["MOAI_VERSION_SOURCE"] = self._get_version_source(version_reader)
|
|
264
|
+
|
|
265
|
+
# Performance metrics
|
|
266
|
+
cache_age = version_reader.get_cache_age_seconds()
|
|
267
|
+
if cache_age is not None:
|
|
268
|
+
version_context["MOAI_VERSION_CACHE_AGE"] = f"{cache_age:.2f}s"
|
|
269
|
+
else:
|
|
270
|
+
version_context["MOAI_VERSION_CACHE_AGE"] = "uncached"
|
|
271
|
+
|
|
272
|
+
# Additional metadata
|
|
273
|
+
if self.config.enable_version_validation:
|
|
274
|
+
is_valid = self._is_valid_version_format(moai_version)
|
|
275
|
+
version_context["MOAI_VERSION_FORMAT_VALID"] = "true" if is_valid else "false"
|
|
276
|
+
|
|
277
|
+
if self.config.verbose_logging:
|
|
278
|
+
logger.debug(f"Enhanced version context generated: {version_context}")
|
|
279
|
+
|
|
280
|
+
except Exception as e:
|
|
281
|
+
logger.warning(f"Failed to read version for template context: {e}")
|
|
282
|
+
# Use fallback version with comprehensive formatting
|
|
283
|
+
fallback_version = self.config.version_fallback
|
|
284
|
+
version_context["MOAI_VERSION"] = fallback_version
|
|
285
|
+
version_context["MOAI_VERSION_SHORT"] = self._format_short_version(fallback_version)
|
|
286
|
+
version_context["MOAI_VERSION_DISPLAY"] = self._format_display_version(fallback_version)
|
|
287
|
+
version_context["MOAI_VERSION_TRIMMED"] = self._format_trimmed_version(fallback_version, max_length=10)
|
|
288
|
+
version_context["MOAI_VERSION_SEMVER"] = self._format_semver_version(fallback_version)
|
|
289
|
+
version_context["MOAI_VERSION_VALID"] = "false" if fallback_version == "unknown" else "true"
|
|
290
|
+
version_context["MOAI_VERSION_SOURCE"] = "fallback_config"
|
|
291
|
+
version_context["MOAI_VERSION_CACHE_AGE"] = "unavailable"
|
|
292
|
+
version_context["MOAI_VERSION_FORMAT_VALID"] = "false"
|
|
293
|
+
|
|
294
|
+
return version_context
|
|
295
|
+
|
|
296
|
+
def _is_valid_version_format(self, version: str) -> bool:
|
|
297
|
+
"""
|
|
298
|
+
Validate version format using configured regex pattern.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
version: Version string to validate
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
True if version format is valid
|
|
305
|
+
"""
|
|
306
|
+
import re
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
pattern = re.compile(self.config.version_format_regex)
|
|
310
|
+
return bool(pattern.match(version))
|
|
311
|
+
except re.error:
|
|
312
|
+
# Fallback to default pattern if custom one is invalid
|
|
313
|
+
default_pattern = re.compile(r"^v?(\d+\.\d+\.\d+(-[a-zA-Z0-9]+)?)$")
|
|
314
|
+
return bool(default_pattern.match(version))
|
|
315
|
+
|
|
316
|
+
def _format_short_version(self, version: str) -> str:
|
|
317
|
+
"""
|
|
318
|
+
Format short version by removing 'v' prefix if present.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
version: Version string
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Short version string
|
|
325
|
+
"""
|
|
326
|
+
return version[1:] if version.startswith("v") else version
|
|
327
|
+
|
|
328
|
+
def _format_display_version(self, version: str) -> str:
|
|
329
|
+
"""
|
|
330
|
+
Format display version with proper formatting.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
version: Version string
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Display version string
|
|
337
|
+
"""
|
|
338
|
+
if version == "unknown":
|
|
339
|
+
return "MoAI-ADK unknown version"
|
|
340
|
+
elif version.startswith("v"):
|
|
341
|
+
return f"MoAI-ADK {version}"
|
|
342
|
+
else:
|
|
343
|
+
return f"MoAI-ADK v{version}"
|
|
344
|
+
|
|
345
|
+
def _format_trimmed_version(self, version: str, max_length: int = 10) -> str:
|
|
346
|
+
"""
|
|
347
|
+
Format version with maximum length, suitable for UI displays.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
version: Version string
|
|
351
|
+
max_length: Maximum allowed length for the version string
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
Trimmed version string
|
|
355
|
+
"""
|
|
356
|
+
if version == "unknown":
|
|
357
|
+
return "unknown"
|
|
358
|
+
|
|
359
|
+
# Remove 'v' prefix for trimming
|
|
360
|
+
clean_version = version[1:] if version.startswith("v") else version
|
|
361
|
+
|
|
362
|
+
# Trim if necessary
|
|
363
|
+
if len(clean_version) > max_length:
|
|
364
|
+
return clean_version[:max_length]
|
|
365
|
+
return clean_version
|
|
366
|
+
|
|
367
|
+
def _format_semver_version(self, version: str) -> str:
|
|
368
|
+
"""
|
|
369
|
+
Format version as semantic version with major.minor.patch structure.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
version: Version string
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
Semantic version string
|
|
376
|
+
"""
|
|
377
|
+
if version == "unknown":
|
|
378
|
+
return "0.0.0"
|
|
379
|
+
|
|
380
|
+
# Remove 'v' prefix and extract semantic version
|
|
381
|
+
clean_version = version[1:] if version.startswith("v") else version
|
|
382
|
+
|
|
383
|
+
# Extract core semantic version (remove pre-release and build metadata)
|
|
384
|
+
import re
|
|
385
|
+
|
|
386
|
+
semver_match = re.match(r"^(\d+\.\d+\.\d+)", clean_version)
|
|
387
|
+
if semver_match:
|
|
388
|
+
return semver_match.group(1)
|
|
389
|
+
return "0.0.0"
|
|
390
|
+
|
|
391
|
+
def _get_version_source(self, version_reader: VersionReader) -> str:
|
|
392
|
+
"""
|
|
393
|
+
Determine the source of the version information.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
version_reader: VersionReader instance
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
String indicating version source
|
|
400
|
+
"""
|
|
401
|
+
config = version_reader.get_config()
|
|
402
|
+
cache_age = version_reader.get_cache_age_seconds()
|
|
403
|
+
|
|
404
|
+
if cache_age is not None and cache_age < config.cache_ttl_seconds:
|
|
405
|
+
return "config_cached"
|
|
406
|
+
elif cache_age is not None:
|
|
407
|
+
return "config_stale"
|
|
408
|
+
else:
|
|
409
|
+
return config.fallback_source.value
|
|
410
|
+
|
|
411
|
+
def _get_template_root(self) -> Path:
|
|
412
|
+
"""Return the template root path."""
|
|
413
|
+
# src/moai_adk/core/template/processor.py → src/moai_adk/templates/
|
|
414
|
+
current_file = Path(__file__).resolve()
|
|
415
|
+
package_root = current_file.parent.parent.parent
|
|
416
|
+
return package_root / "templates"
|
|
417
|
+
|
|
418
|
+
def _substitute_variables(self, content: str) -> tuple[str, list[str]]:
|
|
419
|
+
"""
|
|
420
|
+
Substitute template variables in content with enhanced validation and caching.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
content: Content to substitute variables in
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
Tuple of (substituted_content, warnings_list)
|
|
427
|
+
"""
|
|
428
|
+
warnings = []
|
|
429
|
+
logger = logging.getLogger(__name__)
|
|
430
|
+
|
|
431
|
+
# Check cache first if enabled
|
|
432
|
+
cache_key = hash((frozenset(self.context.items()), content[:1000]))
|
|
433
|
+
if self.config.enable_caching and cache_key in self._substitution_cache:
|
|
434
|
+
cached_result = self._substitution_cache[cache_key]
|
|
435
|
+
if self.config.verbose_logging:
|
|
436
|
+
logger.debug("Using cached substitution result")
|
|
437
|
+
return cached_result
|
|
438
|
+
|
|
439
|
+
# Enhanced variable substitution with validation
|
|
440
|
+
substitution_count = 0
|
|
441
|
+
for key, value in self.context.items():
|
|
442
|
+
placeholder = f"{{{{{key}}}}}" # {{KEY}}
|
|
443
|
+
if placeholder in content:
|
|
444
|
+
if self.config.validate_template_variables:
|
|
445
|
+
# Validate variable before substitution
|
|
446
|
+
if not self._is_valid_template_variable(key, value):
|
|
447
|
+
warnings.append(f"Invalid variable {key} - skipped substitution")
|
|
448
|
+
continue
|
|
449
|
+
|
|
450
|
+
safe_value = self._sanitize_value(value)
|
|
451
|
+
content = content.replace(placeholder, safe_value)
|
|
452
|
+
substitution_count += 1
|
|
453
|
+
|
|
454
|
+
if self.config.verbose_logging:
|
|
455
|
+
logger.debug(f"Substituted {key}: {safe_value[:50]}...")
|
|
456
|
+
|
|
457
|
+
# Detect unsubstituted variables with enhanced error messages
|
|
458
|
+
remaining = re.findall(r"\{\{([A-Z_]+)\}\}", content)
|
|
459
|
+
if remaining:
|
|
460
|
+
unique_remaining = sorted(set(remaining))
|
|
461
|
+
|
|
462
|
+
# Build detailed warning message with enhanced suggestions
|
|
463
|
+
warning_parts = []
|
|
464
|
+
for var in unique_remaining:
|
|
465
|
+
if var in self.COMMON_TEMPLATE_VARIABLES:
|
|
466
|
+
suggestion = self.COMMON_TEMPLATE_VARIABLES[var]
|
|
467
|
+
warning_parts.append(f"{{{{{var}}}}} → {suggestion}")
|
|
468
|
+
else:
|
|
469
|
+
warning_parts.append(f"{{{{{var}}}}} → Unknown variable (check template)")
|
|
470
|
+
|
|
471
|
+
warnings.append("Template variables not substituted:")
|
|
472
|
+
warnings.extend(f" • {part}" for part in warning_parts)
|
|
473
|
+
|
|
474
|
+
if self.config.enable_substitution_warnings:
|
|
475
|
+
warnings.append("💡 Run 'uv run moai-adk update' to fix template variables")
|
|
476
|
+
|
|
477
|
+
# Add performance information if verbose logging is enabled
|
|
478
|
+
if self.config.verbose_logging:
|
|
479
|
+
warnings.append(f" 📊 Substituted {substitution_count} variables")
|
|
480
|
+
|
|
481
|
+
# Cache the result if enabled
|
|
482
|
+
if self.config.enable_caching:
|
|
483
|
+
result = (content, warnings)
|
|
484
|
+
self._substitution_cache[cache_key] = result
|
|
485
|
+
|
|
486
|
+
# Manage cache size
|
|
487
|
+
if len(self._substitution_cache) > self.config.cache_size:
|
|
488
|
+
# Remove oldest entry (simple FIFO)
|
|
489
|
+
oldest_key = next(iter(self._substitution_cache))
|
|
490
|
+
del self._substitution_cache[oldest_key]
|
|
491
|
+
if self.config.verbose_logging:
|
|
492
|
+
logger.debug("Cache size limit reached, removed oldest entry")
|
|
493
|
+
|
|
494
|
+
return content, warnings
|
|
495
|
+
|
|
496
|
+
def _is_valid_template_variable(self, key: str, value: str) -> bool:
|
|
497
|
+
"""
|
|
498
|
+
Validate a template variable before substitution.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
key: Variable name
|
|
502
|
+
value: Variable value
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
True if variable is valid
|
|
506
|
+
"""
|
|
507
|
+
import re
|
|
508
|
+
|
|
509
|
+
# Check variable name format
|
|
510
|
+
if not re.match(self.config.allowed_variable_pattern, key):
|
|
511
|
+
return False
|
|
512
|
+
|
|
513
|
+
# Check variable length
|
|
514
|
+
if len(key) > self.config.max_variable_length:
|
|
515
|
+
return False
|
|
516
|
+
|
|
517
|
+
# Check value length
|
|
518
|
+
if len(value) > self.config.max_variable_length * 2:
|
|
519
|
+
return False
|
|
520
|
+
|
|
521
|
+
# Note: {{ }} patterns are handled by sanitization, not validation
|
|
522
|
+
|
|
523
|
+
# Check for empty values
|
|
524
|
+
if not value.strip():
|
|
525
|
+
return False
|
|
526
|
+
|
|
527
|
+
return True
|
|
528
|
+
|
|
529
|
+
def clear_substitution_cache(self) -> None:
|
|
530
|
+
"""Clear the substitution cache."""
|
|
531
|
+
self._substitution_cache.clear()
|
|
532
|
+
if self.config.verbose_logging:
|
|
533
|
+
self.logger.debug("Substitution cache cleared")
|
|
534
|
+
|
|
535
|
+
def get_cache_stats(self) -> Dict[str, Any]:
|
|
536
|
+
"""
|
|
537
|
+
Get cache statistics.
|
|
538
|
+
|
|
539
|
+
Returns:
|
|
540
|
+
Dictionary containing cache statistics
|
|
541
|
+
"""
|
|
542
|
+
return {
|
|
543
|
+
"cache_size": len(self._substitution_cache),
|
|
544
|
+
"max_cache_size": self.config.cache_size,
|
|
545
|
+
"cache_enabled": self.config.enable_caching,
|
|
546
|
+
"cache_hit_ratio": 0.0, # Would need to track hits to implement this
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
def _sanitize_value(self, value: str) -> str:
|
|
550
|
+
"""Sanitize value to prevent recursive substitution and control characters.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
value: Value to sanitize.
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
Sanitized value.
|
|
557
|
+
"""
|
|
558
|
+
# Remove control characters (keep printable and whitespace)
|
|
559
|
+
value = "".join(c for c in value if c.isprintable() or c in "\n\r\t")
|
|
560
|
+
# Prevent recursive substitution by removing placeholder patterns
|
|
561
|
+
value = value.replace("{{", "").replace("}}", "")
|
|
562
|
+
return value
|
|
563
|
+
|
|
564
|
+
def _is_text_file(self, file_path: Path) -> bool:
|
|
565
|
+
"""Check if file is text-based (not binary).
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
file_path: File path to check.
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
True if file is text-based.
|
|
572
|
+
"""
|
|
573
|
+
text_extensions = {
|
|
574
|
+
".md",
|
|
575
|
+
".json",
|
|
576
|
+
".txt",
|
|
577
|
+
".py",
|
|
578
|
+
".ts",
|
|
579
|
+
".js",
|
|
580
|
+
".yaml",
|
|
581
|
+
".yml",
|
|
582
|
+
".toml",
|
|
583
|
+
".xml",
|
|
584
|
+
".sh",
|
|
585
|
+
".bash",
|
|
586
|
+
}
|
|
587
|
+
return file_path.suffix.lower() in text_extensions
|
|
588
|
+
|
|
589
|
+
def _localize_yaml_description(self, content: str, language: str = "en") -> str:
|
|
590
|
+
"""Localize multilingual YAML description field.
|
|
591
|
+
|
|
592
|
+
Converts multilingual description maps to single-language strings:
|
|
593
|
+
description:
|
|
594
|
+
en: "English text"
|
|
595
|
+
ko: "Korean text"
|
|
596
|
+
→
|
|
597
|
+
description: "Korean text" (if language="ko")
|
|
598
|
+
|
|
599
|
+
Args:
|
|
600
|
+
content: File content.
|
|
601
|
+
language: Target language code (en, ko, ja, zh).
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
Content with localized descriptions.
|
|
605
|
+
"""
|
|
606
|
+
import yaml # type: ignore[import-untyped]
|
|
607
|
+
|
|
608
|
+
# Pattern to match YAML frontmatter
|
|
609
|
+
frontmatter_pattern = r"^---\n(.*?)\n---"
|
|
610
|
+
match = re.match(frontmatter_pattern, content, re.DOTALL)
|
|
611
|
+
|
|
612
|
+
if not match:
|
|
613
|
+
return content
|
|
614
|
+
|
|
615
|
+
try:
|
|
616
|
+
yaml_content = match.group(1)
|
|
617
|
+
yaml_data = yaml.safe_load(yaml_content)
|
|
618
|
+
|
|
619
|
+
# Check if description is a dict (multilingual)
|
|
620
|
+
if isinstance(yaml_data.get("description"), dict):
|
|
621
|
+
# Select language (fallback to English)
|
|
622
|
+
descriptions = yaml_data["description"]
|
|
623
|
+
selected_desc = descriptions.get(language, descriptions.get("en", ""))
|
|
624
|
+
|
|
625
|
+
# Replace description with selected language
|
|
626
|
+
yaml_data["description"] = selected_desc
|
|
627
|
+
|
|
628
|
+
# Reconstruct frontmatter
|
|
629
|
+
new_yaml = yaml.dump(yaml_data, allow_unicode=True, sort_keys=False)
|
|
630
|
+
# Preserve the rest of the content
|
|
631
|
+
rest_content = content[match.end() :]
|
|
632
|
+
return f"---\n{new_yaml}---{rest_content}"
|
|
633
|
+
|
|
634
|
+
except Exception:
|
|
635
|
+
# If YAML parsing fails, return original content
|
|
636
|
+
pass
|
|
637
|
+
|
|
638
|
+
return content
|
|
639
|
+
|
|
640
|
+
def _copy_file_with_substitution(self, src: Path, dst: Path) -> list[str]:
|
|
641
|
+
"""Copy file with variable substitution and description localization for text files.
|
|
642
|
+
|
|
643
|
+
Args:
|
|
644
|
+
src: Source file path.
|
|
645
|
+
dst: Destination file path.
|
|
646
|
+
|
|
647
|
+
Returns:
|
|
648
|
+
List of warnings.
|
|
649
|
+
"""
|
|
650
|
+
import stat
|
|
651
|
+
|
|
652
|
+
warnings = []
|
|
653
|
+
|
|
654
|
+
# Text files: read, substitute, write
|
|
655
|
+
if self._is_text_file(src) and self.context:
|
|
656
|
+
try:
|
|
657
|
+
content = src.read_text(encoding="utf-8")
|
|
658
|
+
content, file_warnings = self._substitute_variables(content)
|
|
659
|
+
|
|
660
|
+
# Apply description localization for command/output-style files
|
|
661
|
+
if src.suffix == ".md" and ("commands/alfred" in str(src) or "output-styles/alfred" in str(src)):
|
|
662
|
+
lang = self.context.get("CONVERSATION_LANGUAGE", "en")
|
|
663
|
+
content = self._localize_yaml_description(content, lang)
|
|
664
|
+
|
|
665
|
+
dst.write_text(content, encoding="utf-8")
|
|
666
|
+
warnings.extend(file_warnings)
|
|
667
|
+
except UnicodeDecodeError:
|
|
668
|
+
# Binary file fallback
|
|
669
|
+
shutil.copy2(src, dst)
|
|
670
|
+
else:
|
|
671
|
+
# Binary file or no context: simple copy
|
|
672
|
+
shutil.copy2(src, dst)
|
|
673
|
+
|
|
674
|
+
# Ensure executable permission for shell scripts
|
|
675
|
+
if src.suffix == ".sh":
|
|
676
|
+
# Always make shell scripts executable regardless of source permissions
|
|
677
|
+
dst_mode = dst.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
|
|
678
|
+
dst.chmod(dst_mode)
|
|
679
|
+
|
|
680
|
+
return warnings
|
|
681
|
+
|
|
682
|
+
def _copy_dir_with_substitution(self, src: Path, dst: Path) -> None:
|
|
683
|
+
"""Recursively copy directory with variable substitution for text files.
|
|
684
|
+
|
|
685
|
+
Args:
|
|
686
|
+
src: Source directory path.
|
|
687
|
+
dst: Destination directory path.
|
|
688
|
+
"""
|
|
689
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
690
|
+
|
|
691
|
+
for item in src.rglob("*"):
|
|
692
|
+
rel_path = item.relative_to(src)
|
|
693
|
+
dst_item = dst / rel_path
|
|
694
|
+
|
|
695
|
+
if item.is_file():
|
|
696
|
+
# Create parent directory if needed
|
|
697
|
+
dst_item.parent.mkdir(parents=True, exist_ok=True)
|
|
698
|
+
# Copy with variable substitution
|
|
699
|
+
self._copy_file_with_substitution(item, dst_item)
|
|
700
|
+
elif item.is_dir():
|
|
701
|
+
dst_item.mkdir(parents=True, exist_ok=True)
|
|
702
|
+
|
|
703
|
+
def copy_templates(self, backup: bool = True, silent: bool = False) -> None:
|
|
704
|
+
"""Copy template files into the project.
|
|
705
|
+
|
|
706
|
+
Args:
|
|
707
|
+
backup: Whether to create a backup.
|
|
708
|
+
silent: Reduce log output when True.
|
|
709
|
+
"""
|
|
710
|
+
# 1. Create a backup when existing files are present
|
|
711
|
+
if backup and self._has_existing_files():
|
|
712
|
+
backup_path = self.create_backup()
|
|
713
|
+
if not silent:
|
|
714
|
+
console.print(f"💾 Backup created: {backup_path.name}")
|
|
715
|
+
|
|
716
|
+
# 2. Copy templates
|
|
717
|
+
if not silent:
|
|
718
|
+
console.print("📄 Copying templates...")
|
|
719
|
+
|
|
720
|
+
self._copy_claude(silent)
|
|
721
|
+
self._copy_moai(silent)
|
|
722
|
+
self._copy_github(silent)
|
|
723
|
+
self._copy_claude_md(silent)
|
|
724
|
+
self._copy_gitignore(silent)
|
|
725
|
+
self._copy_mcp_json(silent)
|
|
726
|
+
|
|
727
|
+
if not silent:
|
|
728
|
+
console.print("✅ Templates copied successfully")
|
|
729
|
+
|
|
730
|
+
def _has_existing_files(self) -> bool:
|
|
731
|
+
"""Determine whether project files exist (backup decision helper)."""
|
|
732
|
+
return self.backup.has_existing_files()
|
|
733
|
+
|
|
734
|
+
def create_backup(self) -> Path:
|
|
735
|
+
"""Create a timestamped backup (delegated)."""
|
|
736
|
+
return self.backup.create_backup()
|
|
737
|
+
|
|
738
|
+
def _copy_exclude_protected(self, src: Path, dst: Path) -> None:
|
|
739
|
+
"""Copy content while excluding protected paths.
|
|
740
|
+
|
|
741
|
+
Args:
|
|
742
|
+
src: Source directory.
|
|
743
|
+
dst: Destination directory.
|
|
744
|
+
"""
|
|
745
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
746
|
+
|
|
747
|
+
# PROTECTED_PATHS: only specs/ and reports/ are excluded during copying
|
|
748
|
+
# project/ and config.json are preserved only when they already exist
|
|
749
|
+
template_protected_paths = [
|
|
750
|
+
"specs",
|
|
751
|
+
"reports",
|
|
752
|
+
]
|
|
753
|
+
|
|
754
|
+
for item in src.rglob("*"):
|
|
755
|
+
rel_path = item.relative_to(src)
|
|
756
|
+
rel_path_str = str(rel_path)
|
|
757
|
+
|
|
758
|
+
# Skip template copy for specs/ and reports/
|
|
759
|
+
if any(rel_path_str.startswith(p) for p in template_protected_paths):
|
|
760
|
+
continue
|
|
761
|
+
|
|
762
|
+
dst_item = dst / rel_path
|
|
763
|
+
if item.is_file():
|
|
764
|
+
# Preserve user content by skipping existing files (v0.3.0)
|
|
765
|
+
# This automatically protects project/ and config.json
|
|
766
|
+
if dst_item.exists():
|
|
767
|
+
continue
|
|
768
|
+
dst_item.parent.mkdir(parents=True, exist_ok=True)
|
|
769
|
+
shutil.copy2(item, dst_item)
|
|
770
|
+
elif item.is_dir():
|
|
771
|
+
dst_item.mkdir(parents=True, exist_ok=True)
|
|
772
|
+
|
|
773
|
+
def _copy_claude(self, silent: bool = False) -> None:
|
|
774
|
+
""".claude/ directory copy with variable substitution (selective with alfred folder overwrite).
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
Strategy:
|
|
778
|
+
- Alfred folders (commands/agents/hooks/output-styles/alfred) → copy wholesale (delete & overwrite)
|
|
779
|
+
* Creates individual backup before deletion for safety
|
|
780
|
+
* Commands: 0-project.md, 1-plan.md, 2-run.md, 3-sync.md
|
|
781
|
+
- Other files/folders → copy individually (preserve existing)
|
|
782
|
+
"""
|
|
783
|
+
src = self.template_root / ".claude"
|
|
784
|
+
dst = self.target_path / ".claude"
|
|
785
|
+
|
|
786
|
+
if not src.exists():
|
|
787
|
+
if not silent:
|
|
788
|
+
console.print("⚠️ .claude/ template not found")
|
|
789
|
+
return
|
|
790
|
+
|
|
791
|
+
# Create .claude directory if not exists
|
|
792
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
793
|
+
|
|
794
|
+
# Alfred and Moai folders to copy wholesale (overwrite)
|
|
795
|
+
# Including both legacy alfred/ and new moai/ structure
|
|
796
|
+
alfred_moai_folders = [
|
|
797
|
+
"hooks/alfred",
|
|
798
|
+
"hooks/moai",
|
|
799
|
+
"commands/alfred", # Contains 0-project.md, 1-plan.md, 2-run.md, 3-sync.md
|
|
800
|
+
"commands/moai",
|
|
801
|
+
"output-styles/moai",
|
|
802
|
+
"agents/alfred",
|
|
803
|
+
"agents/moai",
|
|
804
|
+
"skills", # NEW: Complete replacement for skills folder
|
|
805
|
+
]
|
|
806
|
+
|
|
807
|
+
# 1. Copy Alfred and Moai folders wholesale (backup before delete & overwrite)
|
|
808
|
+
for folder in alfred_moai_folders:
|
|
809
|
+
src_folder = src / folder
|
|
810
|
+
dst_folder = dst / folder
|
|
811
|
+
|
|
812
|
+
if src_folder.exists():
|
|
813
|
+
# Remove existing folder (backup is already handled by create_backup() in update.py)
|
|
814
|
+
if dst_folder.exists():
|
|
815
|
+
shutil.rmtree(dst_folder)
|
|
816
|
+
|
|
817
|
+
# Create parent directory if needed
|
|
818
|
+
dst_folder.parent.mkdir(parents=True, exist_ok=True)
|
|
819
|
+
shutil.copytree(src_folder, dst_folder)
|
|
820
|
+
if not silent:
|
|
821
|
+
console.print(f" ✅ .claude/{folder}/ overwritten")
|
|
822
|
+
|
|
823
|
+
# 1.5 Copy other subdirectories in parent folders (e.g., output-styles/moai, hooks/shared)
|
|
824
|
+
# This ensures non-alfred subdirectories are also copied
|
|
825
|
+
parent_folders_with_subdirs = ["output-styles", "hooks", "commands", "agents"]
|
|
826
|
+
for parent_name in parent_folders_with_subdirs:
|
|
827
|
+
src_parent = src / parent_name
|
|
828
|
+
if not src_parent.exists():
|
|
829
|
+
continue
|
|
830
|
+
|
|
831
|
+
for subdir in src_parent.iterdir():
|
|
832
|
+
if not subdir.is_dir():
|
|
833
|
+
continue
|
|
834
|
+
|
|
835
|
+
# Skip alfred subdirectories (already handled above)
|
|
836
|
+
if subdir.name == "alfred":
|
|
837
|
+
continue
|
|
838
|
+
|
|
839
|
+
rel_subdir = f"{parent_name}/{subdir.name}"
|
|
840
|
+
dst_subdir = dst / parent_name / subdir.name
|
|
841
|
+
|
|
842
|
+
if dst_subdir.exists():
|
|
843
|
+
# For non-alfred directories, overwrite with merge if necessary
|
|
844
|
+
shutil.rmtree(dst_subdir)
|
|
845
|
+
|
|
846
|
+
# Copy the subdirectory
|
|
847
|
+
shutil.copytree(subdir, dst_subdir)
|
|
848
|
+
if not silent:
|
|
849
|
+
console.print(f" ✅ .claude/{rel_subdir}/ copied")
|
|
850
|
+
|
|
851
|
+
# 2. Copy other files/folders individually (smart merge for settings.json and config.json)
|
|
852
|
+
all_warnings = []
|
|
853
|
+
for item in src.iterdir():
|
|
854
|
+
rel_path = item.relative_to(src)
|
|
855
|
+
dst_item = dst / rel_path
|
|
856
|
+
|
|
857
|
+
# Skip Alfred parent folders (already handled above)
|
|
858
|
+
if item.is_dir() and item.name in [
|
|
859
|
+
"hooks",
|
|
860
|
+
"commands",
|
|
861
|
+
"output-styles",
|
|
862
|
+
"agents",
|
|
863
|
+
]:
|
|
864
|
+
continue
|
|
865
|
+
|
|
866
|
+
if item.is_file():
|
|
867
|
+
# Smart merge for settings.json
|
|
868
|
+
if item.name == "settings.json":
|
|
869
|
+
self._merge_settings_json(item, dst_item)
|
|
870
|
+
# Apply variable substitution to merged settings.json (for cross-platform Hook paths)
|
|
871
|
+
if self.context:
|
|
872
|
+
content = dst_item.read_text(encoding="utf-8")
|
|
873
|
+
content, file_warnings = self._substitute_variables(content)
|
|
874
|
+
dst_item.write_text(content, encoding="utf-8")
|
|
875
|
+
all_warnings.extend(file_warnings)
|
|
876
|
+
if not silent:
|
|
877
|
+
console.print(" 🔄 settings.json merged (Hook paths configured for your OS)")
|
|
878
|
+
# Smart merge for config.json
|
|
879
|
+
elif item.name == "config.json":
|
|
880
|
+
self._merge_config_json(item, dst_item)
|
|
881
|
+
if not silent:
|
|
882
|
+
console.print(" 🔄 config.json merged (user preferences preserved)")
|
|
883
|
+
else:
|
|
884
|
+
# FORCE OVERWRITE: Always copy other files (no skip)
|
|
885
|
+
warnings = self._copy_file_with_substitution(item, dst_item)
|
|
886
|
+
all_warnings.extend(warnings)
|
|
887
|
+
elif item.is_dir():
|
|
888
|
+
# FORCE OVERWRITE: Always copy directories (no skip)
|
|
889
|
+
self._copy_dir_with_substitution(item, dst_item)
|
|
890
|
+
|
|
891
|
+
# Print warnings if any
|
|
892
|
+
if all_warnings and not silent:
|
|
893
|
+
console.print("[yellow]⚠️ Template warnings:[/yellow]")
|
|
894
|
+
for warning in set(all_warnings): # Deduplicate
|
|
895
|
+
console.print(f" {warning}")
|
|
896
|
+
|
|
897
|
+
if not silent:
|
|
898
|
+
console.print(" ✅ .claude/ copy complete (variables substituted)")
|
|
899
|
+
|
|
900
|
+
def _copy_moai(self, silent: bool = False) -> None:
|
|
901
|
+
""".moai/ directory copy with variable substitution (excludes protected paths)."""
|
|
902
|
+
src = self.template_root / ".moai"
|
|
903
|
+
dst = self.target_path / ".moai"
|
|
904
|
+
|
|
905
|
+
if not src.exists():
|
|
906
|
+
if not silent:
|
|
907
|
+
console.print("⚠️ .moai/ template not found")
|
|
908
|
+
return
|
|
909
|
+
|
|
910
|
+
# Paths excluded from template copying (specs/, reports/, .moai/config/config.json)
|
|
911
|
+
template_protected_paths = [
|
|
912
|
+
"specs",
|
|
913
|
+
"reports",
|
|
914
|
+
".moai/config/config.json",
|
|
915
|
+
]
|
|
916
|
+
|
|
917
|
+
all_warnings = []
|
|
918
|
+
|
|
919
|
+
# Copy while skipping protected paths
|
|
920
|
+
for item in src.rglob("*"):
|
|
921
|
+
rel_path = item.relative_to(src)
|
|
922
|
+
rel_path_str = str(rel_path)
|
|
923
|
+
|
|
924
|
+
# Skip specs/ and reports/
|
|
925
|
+
if any(rel_path_str.startswith(p) for p in template_protected_paths):
|
|
926
|
+
continue
|
|
927
|
+
|
|
928
|
+
dst_item = dst / rel_path
|
|
929
|
+
if item.is_file():
|
|
930
|
+
# FORCE OVERWRITE: Always copy files (no skip)
|
|
931
|
+
dst_item.parent.mkdir(parents=True, exist_ok=True)
|
|
932
|
+
# Copy with variable substitution
|
|
933
|
+
warnings = self._copy_file_with_substitution(item, dst_item)
|
|
934
|
+
all_warnings.extend(warnings)
|
|
935
|
+
elif item.is_dir():
|
|
936
|
+
dst_item.mkdir(parents=True, exist_ok=True)
|
|
937
|
+
|
|
938
|
+
# Print warnings if any
|
|
939
|
+
if all_warnings and not silent:
|
|
940
|
+
console.print("[yellow]⚠️ Template warnings:[/yellow]")
|
|
941
|
+
for warning in set(all_warnings): # Deduplicate
|
|
942
|
+
console.print(f" {warning}")
|
|
943
|
+
|
|
944
|
+
if not silent:
|
|
945
|
+
console.print(" ✅ .moai/ copy complete (variables substituted)")
|
|
946
|
+
|
|
947
|
+
def _copy_github(self, silent: bool = False) -> None:
|
|
948
|
+
""".github/ directory copy with smart merge (preserves user workflows)."""
|
|
949
|
+
src = self.template_root / ".github"
|
|
950
|
+
dst = self.target_path / ".github"
|
|
951
|
+
|
|
952
|
+
if not src.exists():
|
|
953
|
+
if not silent:
|
|
954
|
+
console.print("⚠️ .github/ template not found")
|
|
955
|
+
return
|
|
956
|
+
|
|
957
|
+
# Smart merge: preserve existing user workflows
|
|
958
|
+
if dst.exists():
|
|
959
|
+
self._merge_github_workflows(src, dst)
|
|
960
|
+
else:
|
|
961
|
+
# First time: just copy
|
|
962
|
+
self._copy_dir_with_substitution(src, dst)
|
|
963
|
+
|
|
964
|
+
if not silent:
|
|
965
|
+
console.print(" 🔄 .github/ merged (user workflows preserved, variables substituted)")
|
|
966
|
+
|
|
967
|
+
def _copy_claude_md(self, silent: bool = False) -> None:
|
|
968
|
+
"""Copy CLAUDE.md with complete replacement (no merge)."""
|
|
969
|
+
src = self.template_root / "CLAUDE.md"
|
|
970
|
+
dst = self.target_path / "CLAUDE.md"
|
|
971
|
+
|
|
972
|
+
if not src.exists():
|
|
973
|
+
if not silent:
|
|
974
|
+
console.print("⚠️ CLAUDE.md template not found")
|
|
975
|
+
return
|
|
976
|
+
|
|
977
|
+
# Simple copy with substitution (no merge)
|
|
978
|
+
self._copy_file_with_substitution(src, dst)
|
|
979
|
+
|
|
980
|
+
if not silent:
|
|
981
|
+
console.print(" ✅ CLAUDE.md replaced (use CLAUDE.local.md for personal instructions)")
|
|
982
|
+
|
|
983
|
+
def _merge_claude_md(self, src: Path, dst: Path) -> None:
|
|
984
|
+
"""Delegate the smart merge for CLAUDE.md.
|
|
985
|
+
|
|
986
|
+
Args:
|
|
987
|
+
src: Template CLAUDE.md.
|
|
988
|
+
dst: Project CLAUDE.md.
|
|
989
|
+
"""
|
|
990
|
+
self.merger.merge_claude_md(src, dst)
|
|
991
|
+
|
|
992
|
+
def _merge_github_workflows(self, src: Path, dst: Path) -> None:
|
|
993
|
+
"""Delegate the smart merge for .github/workflows/.
|
|
994
|
+
|
|
995
|
+
Args:
|
|
996
|
+
src: Template .github directory.
|
|
997
|
+
dst: Project .github directory.
|
|
998
|
+
"""
|
|
999
|
+
self.merger.merge_github_workflows(src, dst)
|
|
1000
|
+
|
|
1001
|
+
def _merge_settings_json(self, src: Path, dst: Path) -> None:
|
|
1002
|
+
"""Delegate the smart merge for settings.json.
|
|
1003
|
+
|
|
1004
|
+
Args:
|
|
1005
|
+
src: Template settings.json.
|
|
1006
|
+
dst: Project settings.json.
|
|
1007
|
+
"""
|
|
1008
|
+
# Find the latest backup for user settings extraction
|
|
1009
|
+
backup_path = None
|
|
1010
|
+
latest_backup = self.backup.get_latest_backup()
|
|
1011
|
+
if latest_backup:
|
|
1012
|
+
backup_settings = latest_backup / ".claude" / "settings.json"
|
|
1013
|
+
if backup_settings.exists():
|
|
1014
|
+
backup_path = backup_settings
|
|
1015
|
+
|
|
1016
|
+
self.merger.merge_settings_json(src, dst, backup_path)
|
|
1017
|
+
|
|
1018
|
+
def _merge_config_json(self, src: Path, dst: Path) -> None:
|
|
1019
|
+
"""Smart merge for config.json using LanguageConfigResolver priority system.
|
|
1020
|
+
|
|
1021
|
+
Args:
|
|
1022
|
+
src: Template config.json.
|
|
1023
|
+
dst: Project config.json.
|
|
1024
|
+
"""
|
|
1025
|
+
import json
|
|
1026
|
+
|
|
1027
|
+
# Load template config
|
|
1028
|
+
try:
|
|
1029
|
+
template_config = json.loads(src.read_text(encoding="utf-8"))
|
|
1030
|
+
except (json.JSONDecodeError, FileNotFoundError) as e:
|
|
1031
|
+
console.print(f"⚠️ Warning: Could not read template config.json: {e}")
|
|
1032
|
+
return
|
|
1033
|
+
|
|
1034
|
+
# Find latest backup config.json
|
|
1035
|
+
latest_backup = self.backup.get_latest_backup()
|
|
1036
|
+
if latest_backup:
|
|
1037
|
+
backup_config_path = latest_backup / ".moai" / "config" / "config.json"
|
|
1038
|
+
if backup_config_path.exists():
|
|
1039
|
+
try:
|
|
1040
|
+
json.loads(backup_config_path.read_text(encoding="utf-8"))
|
|
1041
|
+
except json.JSONDecodeError as e:
|
|
1042
|
+
console.print(f"⚠️ Warning: Could not read backup config.json: {e}")
|
|
1043
|
+
|
|
1044
|
+
# Load existing project config.json
|
|
1045
|
+
existing_config = {}
|
|
1046
|
+
if dst.exists():
|
|
1047
|
+
try:
|
|
1048
|
+
existing_config = json.loads(dst.read_text(encoding="utf-8"))
|
|
1049
|
+
except json.JSONDecodeError as e:
|
|
1050
|
+
console.print(f"⚠️ Warning: Could not read existing config.json: {e}")
|
|
1051
|
+
|
|
1052
|
+
# Merge with priority system: Environment > Existing User > Template
|
|
1053
|
+
# We'll use LanguageConfigResolver to handle this properly
|
|
1054
|
+
try:
|
|
1055
|
+
# Import LanguageConfigResolver for priority-based merging
|
|
1056
|
+
from moai_adk.core.language_config_resolver import LanguageConfigResolver
|
|
1057
|
+
|
|
1058
|
+
# Create temporary resolver to handle merging
|
|
1059
|
+
temp_project_path = self.target_path / ".moai" / "config"
|
|
1060
|
+
temp_project_path.mkdir(parents=True, exist_ok=True)
|
|
1061
|
+
|
|
1062
|
+
# Start with template config as base
|
|
1063
|
+
merged_config = template_config.copy()
|
|
1064
|
+
|
|
1065
|
+
# Apply existing user config (higher priority than template)
|
|
1066
|
+
for key, value in existing_config.items():
|
|
1067
|
+
if key not in ["config_source"]: # Skip metadata
|
|
1068
|
+
if key in merged_config and isinstance(merged_config[key], dict) and isinstance(value, dict):
|
|
1069
|
+
# Deep merge for nested objects
|
|
1070
|
+
merged_config[key].update(value)
|
|
1071
|
+
else:
|
|
1072
|
+
merged_config[key] = value
|
|
1073
|
+
|
|
1074
|
+
# Apply environment variables (highest priority)
|
|
1075
|
+
import os
|
|
1076
|
+
|
|
1077
|
+
env_mappings = {
|
|
1078
|
+
"MOAI_USER_NAME": ("user", "name"),
|
|
1079
|
+
"MOAI_CONVERSATION_LANG": ("language", "conversation_language"),
|
|
1080
|
+
"MOAI_AGENT_PROMPT_LANG": ("language", "agent_prompt_language"),
|
|
1081
|
+
"MOAI_CONVERSATION_LANG_NAME": (
|
|
1082
|
+
"language",
|
|
1083
|
+
"conversation_language_name",
|
|
1084
|
+
),
|
|
1085
|
+
"MOAI_GIT_COMMIT_MESSAGES_LANG": ("language", "git_commit_messages"),
|
|
1086
|
+
"MOAI_CODE_COMMENTS_LANG": ("language", "code_comments"),
|
|
1087
|
+
"MOAI_DOCUMENTATION_LANG": ("language", "documentation"),
|
|
1088
|
+
"MOAI_ERROR_MESSAGES_LANG": ("language", "error_messages"),
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
for env_var, (section, key) in env_mappings.items():
|
|
1092
|
+
env_value = os.getenv(env_var)
|
|
1093
|
+
if env_value:
|
|
1094
|
+
if section not in merged_config:
|
|
1095
|
+
merged_config[section] = {}
|
|
1096
|
+
merged_config[section][key] = env_value
|
|
1097
|
+
|
|
1098
|
+
# Ensure consistency
|
|
1099
|
+
resolver = LanguageConfigResolver(str(self.target_path))
|
|
1100
|
+
merged_config = resolver._ensure_consistency(merged_config)
|
|
1101
|
+
|
|
1102
|
+
# Write merged config
|
|
1103
|
+
dst.write_text(
|
|
1104
|
+
json.dumps(merged_config, indent=2, ensure_ascii=False) + "\n",
|
|
1105
|
+
encoding="utf-8",
|
|
1106
|
+
)
|
|
1107
|
+
|
|
1108
|
+
except ImportError:
|
|
1109
|
+
# Fallback: simple merge without LanguageConfigResolver
|
|
1110
|
+
merged_config = template_config.copy()
|
|
1111
|
+
|
|
1112
|
+
# Apply existing config
|
|
1113
|
+
for key, value in existing_config.items():
|
|
1114
|
+
if key not in ["config_source"]:
|
|
1115
|
+
merged_config[key] = value
|
|
1116
|
+
|
|
1117
|
+
dst.write_text(
|
|
1118
|
+
json.dumps(merged_config, indent=2, ensure_ascii=False) + "\n",
|
|
1119
|
+
encoding="utf-8",
|
|
1120
|
+
)
|
|
1121
|
+
console.print(" ⚠️ Warning: Using simple merge (LanguageConfigResolver not available)")
|
|
1122
|
+
|
|
1123
|
+
def _copy_gitignore(self, silent: bool = False) -> None:
|
|
1124
|
+
""".gitignore copy (optional)."""
|
|
1125
|
+
src = self.template_root / ".gitignore"
|
|
1126
|
+
dst = self.target_path / ".gitignore"
|
|
1127
|
+
|
|
1128
|
+
if not src.exists():
|
|
1129
|
+
return
|
|
1130
|
+
|
|
1131
|
+
# Merge with the existing .gitignore when present
|
|
1132
|
+
if dst.exists():
|
|
1133
|
+
self._merge_gitignore(src, dst)
|
|
1134
|
+
if not silent:
|
|
1135
|
+
console.print(" 🔄 .gitignore merged")
|
|
1136
|
+
else:
|
|
1137
|
+
shutil.copy2(src, dst)
|
|
1138
|
+
if not silent:
|
|
1139
|
+
console.print(" ✅ .gitignore copy complete")
|
|
1140
|
+
|
|
1141
|
+
def _merge_gitignore(self, src: Path, dst: Path) -> None:
|
|
1142
|
+
"""Delegate the .gitignore merge.
|
|
1143
|
+
|
|
1144
|
+
Args:
|
|
1145
|
+
src: Template .gitignore.
|
|
1146
|
+
dst: Project .gitignore.
|
|
1147
|
+
"""
|
|
1148
|
+
self.merger.merge_gitignore(src, dst)
|
|
1149
|
+
|
|
1150
|
+
def _copy_mcp_json(self, silent: bool = False) -> None:
|
|
1151
|
+
""".mcp.json copy (smart merge with existing MCP server configuration)."""
|
|
1152
|
+
src = self.template_root / ".mcp.json"
|
|
1153
|
+
dst = self.target_path / ".mcp.json"
|
|
1154
|
+
|
|
1155
|
+
if not src.exists():
|
|
1156
|
+
return
|
|
1157
|
+
|
|
1158
|
+
# Merge with existing .mcp.json when present (preserve user-added MCP servers)
|
|
1159
|
+
if dst.exists():
|
|
1160
|
+
self._merge_mcp_json(src, dst)
|
|
1161
|
+
if not silent:
|
|
1162
|
+
console.print(" 🔄 .mcp.json merged (user MCP servers preserved)")
|
|
1163
|
+
else:
|
|
1164
|
+
shutil.copy2(src, dst)
|
|
1165
|
+
if not silent:
|
|
1166
|
+
console.print(" ✅ .mcp.json copy complete")
|
|
1167
|
+
|
|
1168
|
+
def _merge_mcp_json(self, src: Path, dst: Path) -> None:
|
|
1169
|
+
"""Smart merge for .mcp.json (preserve user-added MCP servers).
|
|
1170
|
+
|
|
1171
|
+
Args:
|
|
1172
|
+
src: Template .mcp.json.
|
|
1173
|
+
dst: Project .mcp.json.
|
|
1174
|
+
"""
|
|
1175
|
+
try:
|
|
1176
|
+
src_data = json.loads(src.read_text(encoding="utf-8"))
|
|
1177
|
+
dst_data = json.loads(dst.read_text(encoding="utf-8"))
|
|
1178
|
+
|
|
1179
|
+
# Merge mcpServers: preserve user servers, update template servers
|
|
1180
|
+
if "mcpServers" in src_data:
|
|
1181
|
+
if "mcpServers" not in dst_data:
|
|
1182
|
+
dst_data["mcpServers"] = {}
|
|
1183
|
+
# Update with template servers (preserves existing user servers)
|
|
1184
|
+
dst_data["mcpServers"].update(src_data["mcpServers"])
|
|
1185
|
+
|
|
1186
|
+
# Write merged result back
|
|
1187
|
+
dst.write_text(json.dumps(dst_data, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
1188
|
+
except json.JSONDecodeError as e:
|
|
1189
|
+
console.print(f"[yellow]⚠️ Failed to merge .mcp.json: {e}[/yellow]")
|
|
1190
|
+
|
|
1191
|
+
def merge_config(self, detected_language: str | None = None) -> dict[str, str]:
|
|
1192
|
+
"""Delegate the smart merge for config.json.
|
|
1193
|
+
|
|
1194
|
+
Args:
|
|
1195
|
+
detected_language: Detected language.
|
|
1196
|
+
|
|
1197
|
+
Returns:
|
|
1198
|
+
Merged configuration dictionary.
|
|
1199
|
+
"""
|
|
1200
|
+
return self.merger.merge_config(detected_language)
|