moai-adk 0.3.0__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.

Files changed (87) hide show
  1. moai_adk/__init__.py +8 -0
  2. moai_adk/__main__.py +86 -0
  3. moai_adk/cli/__init__.py +2 -0
  4. moai_adk/cli/commands/__init__.py +16 -0
  5. moai_adk/cli/commands/backup.py +56 -0
  6. moai_adk/cli/commands/doctor.py +184 -0
  7. moai_adk/cli/commands/init.py +284 -0
  8. moai_adk/cli/commands/restore.py +77 -0
  9. moai_adk/cli/commands/status.py +79 -0
  10. moai_adk/cli/commands/update.py +133 -0
  11. moai_adk/cli/main.py +12 -0
  12. moai_adk/cli/prompts/__init__.py +5 -0
  13. moai_adk/cli/prompts/init_prompts.py +159 -0
  14. moai_adk/core/__init__.py +2 -0
  15. moai_adk/core/git/__init__.py +24 -0
  16. moai_adk/core/git/branch.py +26 -0
  17. moai_adk/core/git/branch_manager.py +137 -0
  18. moai_adk/core/git/checkpoint.py +140 -0
  19. moai_adk/core/git/commit.py +68 -0
  20. moai_adk/core/git/event_detector.py +81 -0
  21. moai_adk/core/git/manager.py +127 -0
  22. moai_adk/core/project/__init__.py +2 -0
  23. moai_adk/core/project/backup_utils.py +84 -0
  24. moai_adk/core/project/checker.py +302 -0
  25. moai_adk/core/project/detector.py +105 -0
  26. moai_adk/core/project/initializer.py +174 -0
  27. moai_adk/core/project/phase_executor.py +297 -0
  28. moai_adk/core/project/validator.py +118 -0
  29. moai_adk/core/quality/__init__.py +6 -0
  30. moai_adk/core/quality/trust_checker.py +441 -0
  31. moai_adk/core/quality/validators/__init__.py +6 -0
  32. moai_adk/core/quality/validators/base_validator.py +19 -0
  33. moai_adk/core/template/__init__.py +8 -0
  34. moai_adk/core/template/backup.py +95 -0
  35. moai_adk/core/template/config.py +95 -0
  36. moai_adk/core/template/languages.py +44 -0
  37. moai_adk/core/template/merger.py +117 -0
  38. moai_adk/core/template/processor.py +310 -0
  39. moai_adk/templates/.claude/agents/alfred/cc-manager.md +474 -0
  40. moai_adk/templates/.claude/agents/alfred/code-builder.md +534 -0
  41. moai_adk/templates/.claude/agents/alfred/debug-helper.md +302 -0
  42. moai_adk/templates/.claude/agents/alfred/doc-syncer.md +175 -0
  43. moai_adk/templates/.claude/agents/alfred/git-manager.md +200 -0
  44. moai_adk/templates/.claude/agents/alfred/project-manager.md +152 -0
  45. moai_adk/templates/.claude/agents/alfred/spec-builder.md +256 -0
  46. moai_adk/templates/.claude/agents/alfred/tag-agent.md +247 -0
  47. moai_adk/templates/.claude/agents/alfred/trust-checker.md +332 -0
  48. moai_adk/templates/.claude/commands/alfred/0-project.md +523 -0
  49. moai_adk/templates/.claude/commands/alfred/1-spec.md +531 -0
  50. moai_adk/templates/.claude/commands/alfred/2-build.md +413 -0
  51. moai_adk/templates/.claude/commands/alfred/3-sync.md +552 -0
  52. moai_adk/templates/.claude/hooks/alfred/README.md +238 -0
  53. moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +165 -0
  54. moai_adk/templates/.claude/hooks/alfred/core/__init__.py +79 -0
  55. moai_adk/templates/.claude/hooks/alfred/core/checkpoint.py +271 -0
  56. moai_adk/templates/.claude/hooks/alfred/core/context.py +110 -0
  57. moai_adk/templates/.claude/hooks/alfred/core/project.py +284 -0
  58. moai_adk/templates/.claude/hooks/alfred/core/tags.py +244 -0
  59. moai_adk/templates/.claude/hooks/alfred/handlers/__init__.py +23 -0
  60. moai_adk/templates/.claude/hooks/alfred/handlers/compact.py +51 -0
  61. moai_adk/templates/.claude/hooks/alfred/handlers/notification.py +25 -0
  62. moai_adk/templates/.claude/hooks/alfred/handlers/session.py +80 -0
  63. moai_adk/templates/.claude/hooks/alfred/handlers/tool.py +71 -0
  64. moai_adk/templates/.claude/hooks/alfred/handlers/user.py +41 -0
  65. moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +635 -0
  66. moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +691 -0
  67. moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +469 -0
  68. moai_adk/templates/.claude/settings.json +135 -0
  69. moai_adk/templates/.github/PULL_REQUEST_TEMPLATE.md +68 -0
  70. moai_adk/templates/.github/workflows/moai-gitflow.yml +255 -0
  71. moai_adk/templates/.gitignore +41 -0
  72. moai_adk/templates/.moai/config.json +89 -0
  73. moai_adk/templates/.moai/memory/development-guide.md +367 -0
  74. moai_adk/templates/.moai/memory/spec-metadata.md +277 -0
  75. moai_adk/templates/.moai/project/product.md +121 -0
  76. moai_adk/templates/.moai/project/structure.md +150 -0
  77. moai_adk/templates/.moai/project/tech.md +221 -0
  78. moai_adk/templates/CLAUDE.md +733 -0
  79. moai_adk/templates/__init__.py +2 -0
  80. moai_adk/utils/__init__.py +8 -0
  81. moai_adk/utils/banner.py +42 -0
  82. moai_adk/utils/logger.py +152 -0
  83. moai_adk-0.3.0.dist-info/METADATA +20 -0
  84. moai_adk-0.3.0.dist-info/RECORD +87 -0
  85. moai_adk-0.3.0.dist-info/WHEEL +4 -0
  86. moai_adk-0.3.0.dist-info/entry_points.txt +2 -0
  87. moai_adk-0.3.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,44 @@
1
+ # @CODE:CORE-PROJECT-001 | SPEC: SPEC-CORE-PROJECT-001.md | TEST: tests/unit/test_language_mapping.py
2
+ """Template mapping by language.
3
+
4
+ Defines template paths for 20 programming languages.
5
+ """
6
+
7
+ LANGUAGE_TEMPLATES: dict[str, str] = {
8
+ "python": ".moai/project/tech/python.md.j2",
9
+ "typescript": ".moai/project/tech/typescript.md.j2",
10
+ "javascript": ".moai/project/tech/javascript.md.j2",
11
+ "java": ".moai/project/tech/java.md.j2",
12
+ "go": ".moai/project/tech/go.md.j2",
13
+ "rust": ".moai/project/tech/rust.md.j2",
14
+ "dart": ".moai/project/tech/dart.md.j2",
15
+ "swift": ".moai/project/tech/swift.md.j2",
16
+ "kotlin": ".moai/project/tech/kotlin.md.j2",
17
+ "csharp": ".moai/project/tech/csharp.md.j2",
18
+ "php": ".moai/project/tech/php.md.j2",
19
+ "ruby": ".moai/project/tech/ruby.md.j2",
20
+ "elixir": ".moai/project/tech/elixir.md.j2",
21
+ "scala": ".moai/project/tech/scala.md.j2",
22
+ "clojure": ".moai/project/tech/clojure.md.j2",
23
+ "haskell": ".moai/project/tech/haskell.md.j2",
24
+ "c": ".moai/project/tech/c.md.j2",
25
+ "cpp": ".moai/project/tech/cpp.md.j2",
26
+ "lua": ".moai/project/tech/lua.md.j2",
27
+ "ocaml": ".moai/project/tech/ocaml.md.j2",
28
+ }
29
+
30
+
31
+ def get_language_template(language: str) -> str:
32
+ """Return the template path for a language (case-insensitive).
33
+
34
+ Args:
35
+ language: Language name (case-insensitive).
36
+
37
+ Returns:
38
+ Template path; defaults to default.md.j2 for unknown languages.
39
+ """
40
+ if not language:
41
+ return ".moai/project/tech/default.md.j2"
42
+
43
+ language_lower = language.lower()
44
+ return LANGUAGE_TEMPLATES.get(language_lower, ".moai/project/tech/default.md.j2")
@@ -0,0 +1,117 @@
1
+ # @CODE:TEMPLATE-001 | SPEC: SPEC-INIT-003.md | Chain: TEMPLATE-001
2
+ """Template file merger (SPEC-INIT-003 v0.3.0).
3
+
4
+ Intelligently merges existing user files with new templates.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import shutil
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+
15
+ class TemplateMerger:
16
+ """Encapsulate template merging logic."""
17
+
18
+ def __init__(self, target_path: Path) -> None:
19
+ """Initialize the merger.
20
+
21
+ Args:
22
+ target_path: Project path (absolute).
23
+ """
24
+ self.target_path = target_path.resolve()
25
+
26
+ def merge_claude_md(self, template_path: Path, existing_path: Path) -> None:
27
+ """Smart merge for CLAUDE.md.
28
+
29
+ Rules:
30
+ - Use the latest template structure/content.
31
+ - Preserve the existing "## 프로젝트 정보" section.
32
+
33
+ Args:
34
+ template_path: Template CLAUDE.md.
35
+ existing_path: Existing CLAUDE.md.
36
+ """
37
+ # Extract the existing "## 프로젝트 정보" section
38
+ existing_content = existing_path.read_text(encoding="utf-8")
39
+ project_info_start = existing_content.find("## 프로젝트 정보")
40
+ project_info = ""
41
+ if project_info_start != -1:
42
+ # Extract until EOF
43
+ project_info = existing_content[project_info_start:]
44
+
45
+ # Load template content
46
+ template_content = template_path.read_text(encoding="utf-8")
47
+
48
+ # Merge when project info exists
49
+ if project_info:
50
+ # Remove the project info section from the template
51
+ template_project_start = template_content.find("## 프로젝트 정보")
52
+ if template_project_start != -1:
53
+ template_content = template_content[:template_project_start].rstrip()
54
+
55
+ # Merge template content with the preserved section
56
+ merged_content = f"{template_content}\n\n{project_info}"
57
+ existing_path.write_text(merged_content, encoding="utf-8")
58
+ else:
59
+ # No project info; copy the template as-is
60
+ shutil.copy2(template_path, existing_path)
61
+
62
+ def merge_gitignore(self, template_path: Path, existing_path: Path) -> None:
63
+ """.gitignore merge.
64
+
65
+ Rules:
66
+ - Keep existing entries.
67
+ - Add new entries from the template.
68
+ - Remove duplicates.
69
+
70
+ Args:
71
+ template_path: Template .gitignore file.
72
+ existing_path: Existing .gitignore file.
73
+ """
74
+ template_lines = set(template_path.read_text(encoding="utf-8").splitlines())
75
+ existing_lines = existing_path.read_text(encoding="utf-8").splitlines()
76
+
77
+ # Merge while removing duplicates
78
+ merged_lines = existing_lines + [
79
+ line for line in template_lines if line not in existing_lines
80
+ ]
81
+
82
+ existing_path.write_text("\n".join(merged_lines) + "\n", encoding="utf-8")
83
+
84
+ def merge_config(self, detected_language: str | None = None) -> dict[str, str]:
85
+ """Smart merge for config.json.
86
+
87
+ Rules:
88
+ - Prefer existing settings.
89
+ - Use detected language plus defaults for new projects.
90
+
91
+ Args:
92
+ detected_language: Detected language.
93
+
94
+ Returns:
95
+ Merged configuration dictionary.
96
+ """
97
+ config_path = self.target_path / ".moai" / "config.json"
98
+
99
+ # Load existing config if present
100
+ existing_config: dict[str, Any] = {}
101
+ if config_path.exists():
102
+ with open(config_path, encoding="utf-8") as f:
103
+ existing_config = json.load(f)
104
+
105
+ # Build new config while preferring existing values
106
+ new_config: dict[str, str] = {
107
+ "projectName": existing_config.get(
108
+ "projectName", self.target_path.name
109
+ ),
110
+ "mode": existing_config.get("mode", "personal"),
111
+ "locale": existing_config.get("locale", "ko"),
112
+ "language": existing_config.get(
113
+ "language", detected_language or "generic"
114
+ ),
115
+ }
116
+
117
+ return new_config
@@ -0,0 +1,310 @@
1
+ # @CODE:TEMPLATE-001 | SPEC: SPEC-INIT-003.md | Chain: TEMPLATE-001
2
+ """Template copy and backup processor (SPEC-INIT-003 v0.3.0: preserve user content)."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import shutil
7
+ from pathlib import Path
8
+
9
+ from rich.console import Console
10
+
11
+ from moai_adk.core.template.backup import TemplateBackup
12
+ from moai_adk.core.template.merger import TemplateMerger
13
+
14
+ console = Console()
15
+
16
+
17
+ class TemplateProcessor:
18
+ """Orchestrate template copying and backups."""
19
+
20
+ # User data protection paths (never touch) - SPEC-INIT-003 v0.3.0
21
+ PROTECTED_PATHS = [
22
+ ".moai/specs/", # User SPEC documents
23
+ ".moai/reports/", # User reports
24
+ ".moai/project/", # User project documents (product/structure/tech.md)
25
+ ".moai/config.json", # User configuration (merged via /alfred:9-update flow)
26
+ ]
27
+
28
+ # Paths excluded from backups
29
+ BACKUP_EXCLUDE = PROTECTED_PATHS
30
+
31
+ def __init__(self, target_path: Path) -> None:
32
+ """Initialize the processor.
33
+
34
+ Args:
35
+ target_path: Project path.
36
+ """
37
+ self.target_path = target_path.resolve()
38
+ self.template_root = self._get_template_root()
39
+ self.backup = TemplateBackup(self.target_path)
40
+ self.merger = TemplateMerger(self.target_path)
41
+
42
+ def _get_template_root(self) -> Path:
43
+ """Return the template root path."""
44
+ # src/moai_adk/core/template/processor.py → src/moai_adk/templates/
45
+ current_file = Path(__file__).resolve()
46
+ package_root = current_file.parent.parent.parent
47
+ return package_root / "templates"
48
+
49
+ def copy_templates(self, backup: bool = True, silent: bool = False) -> None:
50
+ """Copy template files into the project.
51
+
52
+ Args:
53
+ backup: Whether to create a backup.
54
+ silent: Reduce log output when True.
55
+ """
56
+ # 1. Create a backup when existing files are present
57
+ if backup and self._has_existing_files():
58
+ backup_path = self.create_backup()
59
+ if not silent:
60
+ console.print(f"💾 Backup created: {backup_path.name}")
61
+
62
+ # 2. Copy templates
63
+ if not silent:
64
+ console.print("📄 Copying templates...")
65
+
66
+ self._copy_claude(silent)
67
+ self._copy_moai(silent)
68
+ self._copy_claude_md(silent)
69
+ self._copy_gitignore(silent)
70
+
71
+ if not silent:
72
+ console.print("✅ Templates copied successfully")
73
+
74
+ def _has_existing_files(self) -> bool:
75
+ """Determine whether project files exist (backup decision helper)."""
76
+ return self.backup.has_existing_files()
77
+
78
+ def create_backup(self) -> Path:
79
+ """Create a timestamped backup (delegated)."""
80
+ return self.backup.create_backup()
81
+
82
+ def _copy_exclude_protected(self, src: Path, dst: Path) -> None:
83
+ """Copy content while excluding protected paths.
84
+
85
+ Args:
86
+ src: Source directory.
87
+ dst: Destination directory.
88
+ """
89
+ dst.mkdir(parents=True, exist_ok=True)
90
+
91
+ # PROTECTED_PATHS: only specs/ and reports/ are excluded during copying
92
+ # project/ and config.json are preserved only when they already exist
93
+ template_protected_paths = [
94
+ "specs",
95
+ "reports",
96
+ ]
97
+
98
+ for item in src.rglob("*"):
99
+ rel_path = item.relative_to(src)
100
+ rel_path_str = str(rel_path)
101
+
102
+ # Skip template copy for specs/ and reports/
103
+ if any(rel_path_str.startswith(p) for p in template_protected_paths):
104
+ continue
105
+
106
+ dst_item = dst / rel_path
107
+ if item.is_file():
108
+ # Preserve user content by skipping existing files (v0.3.0)
109
+ # This automatically protects project/ and config.json
110
+ if dst_item.exists():
111
+ continue
112
+ dst_item.parent.mkdir(parents=True, exist_ok=True)
113
+ shutil.copy2(item, dst_item)
114
+ elif item.is_dir():
115
+ dst_item.mkdir(parents=True, exist_ok=True)
116
+
117
+ def _copy_claude(self, silent: bool = False) -> None:
118
+ """.claude/ directory copy (selective with alfred folder overwrite).
119
+
120
+ Strategy:
121
+ - Alfred folders (commands/agents/hooks/output-styles/alfred) → copy wholesale (delete & overwrite)
122
+ * Creates individual backup before deletion for safety
123
+ - Other files/folders → copy individually (preserve existing)
124
+ """
125
+ src = self.template_root / ".claude"
126
+ dst = self.target_path / ".claude"
127
+
128
+ if not src.exists():
129
+ if not silent:
130
+ console.print("⚠️ .claude/ template not found")
131
+ return
132
+
133
+ # Create .claude directory if not exists
134
+ dst.mkdir(parents=True, exist_ok=True)
135
+
136
+ # Alfred folders to copy wholesale (overwrite)
137
+ alfred_folders = [
138
+ "hooks/alfred",
139
+ "commands/alfred",
140
+ "output-styles/alfred",
141
+ "agents/alfred",
142
+ ]
143
+
144
+ # 1. Copy Alfred folders wholesale (backup before delete & overwrite)
145
+ for folder in alfred_folders:
146
+ src_folder = src / folder
147
+ dst_folder = dst / folder
148
+
149
+ if src_folder.exists():
150
+ # Backup this folder before deletion (safety measure)
151
+ if dst_folder.exists():
152
+ self._backup_alfred_folder(dst_folder, folder)
153
+ shutil.rmtree(dst_folder)
154
+
155
+ # Create parent directory if needed
156
+ dst_folder.parent.mkdir(parents=True, exist_ok=True)
157
+ shutil.copytree(src_folder, dst_folder)
158
+ if not silent:
159
+ console.print(f" ✅ .claude/{folder}/ overwritten")
160
+
161
+ # 2. Copy other files/folders individually (preserve existing)
162
+ for item in src.iterdir():
163
+ rel_path = item.relative_to(src)
164
+ dst_item = dst / rel_path
165
+
166
+ # Skip Alfred parent folders (already handled above)
167
+ if item.is_dir() and item.name in ["hooks", "commands", "output-styles", "agents"]:
168
+ continue
169
+
170
+ if item.is_file():
171
+ # Copy file, skip if exists (preserve user modifications)
172
+ if not dst_item.exists():
173
+ shutil.copy2(item, dst_item)
174
+ elif item.is_dir():
175
+ # Copy directory recursively (preserve existing files)
176
+ if not dst_item.exists():
177
+ shutil.copytree(item, dst_item)
178
+
179
+ if not silent:
180
+ console.print(" ✅ .claude/ copy complete (alfred folders overwritten, others preserved)")
181
+
182
+ def _backup_alfred_folder(self, folder_path: Path, folder_name: str) -> None:
183
+ """Backup an Alfred folder before overwriting (safety measure).
184
+
185
+ Args:
186
+ folder_path: Path to the folder to backup.
187
+ folder_name: Name of the folder (e.g., "hooks/alfred").
188
+ """
189
+ if not folder_path.exists():
190
+ return
191
+
192
+ # Create backup directory in .moai-backups/.claude-backups/{timestamp}/
193
+ backup_base = self.target_path / ".moai-backups" / ".claude-backups"
194
+ backup_base.mkdir(parents=True, exist_ok=True)
195
+
196
+ # Generate timestamp-based backup directory
197
+ from moai_adk.core.project.backup_utils import generate_backup_dir_name
198
+
199
+ timestamp = generate_backup_dir_name()
200
+ backup_dir = backup_base / timestamp
201
+
202
+ # Backup this specific folder
203
+ backup_folder = backup_dir / folder_name
204
+ backup_folder.parent.mkdir(parents=True, exist_ok=True)
205
+ shutil.copytree(folder_path, backup_folder)
206
+
207
+ def _copy_moai(self, silent: bool = False) -> None:
208
+ """.moai/ directory copy (excludes protected paths)."""
209
+ src = self.template_root / ".moai"
210
+ dst = self.target_path / ".moai"
211
+
212
+ if not src.exists():
213
+ if not silent:
214
+ console.print("⚠️ .moai/ template not found")
215
+ return
216
+
217
+ # Paths excluded from template copying (specs/, reports/)
218
+ template_protected_paths = [
219
+ "specs",
220
+ "reports",
221
+ ]
222
+
223
+ # Copy while skipping protected paths
224
+ for item in src.rglob("*"):
225
+ rel_path = item.relative_to(src)
226
+ rel_path_str = str(rel_path)
227
+
228
+ # Skip specs/ and reports/
229
+ if any(rel_path_str.startswith(p) for p in template_protected_paths):
230
+ continue
231
+
232
+ dst_item = dst / rel_path
233
+ if item.is_file():
234
+ # Skip existing files to preserve user content (v0.3.0)
235
+ if dst_item.exists():
236
+ continue
237
+ dst_item.parent.mkdir(parents=True, exist_ok=True)
238
+ shutil.copy2(item, dst_item)
239
+ elif item.is_dir():
240
+ dst_item.mkdir(parents=True, exist_ok=True)
241
+
242
+ if not silent:
243
+ console.print(" ✅ .moai/ copy complete (user content preserved)")
244
+
245
+ def _copy_claude_md(self, silent: bool = False) -> None:
246
+ """Copy CLAUDE.md with smart merging."""
247
+ src = self.template_root / "CLAUDE.md"
248
+ dst = self.target_path / "CLAUDE.md"
249
+
250
+ if not src.exists():
251
+ if not silent:
252
+ console.print("⚠️ CLAUDE.md template not found")
253
+ return
254
+
255
+ # Preserve project information when the file exists
256
+ if dst.exists():
257
+ self._merge_claude_md(src, dst)
258
+ if not silent:
259
+ console.print(" 🔄 CLAUDE.md merged (project information preserved)")
260
+ else:
261
+ shutil.copy2(src, dst)
262
+ if not silent:
263
+ console.print(" ✅ CLAUDE.md copy complete")
264
+
265
+ def _merge_claude_md(self, src: Path, dst: Path) -> None:
266
+ """Delegate the smart merge for CLAUDE.md.
267
+
268
+ Args:
269
+ src: Template CLAUDE.md.
270
+ dst: Project CLAUDE.md.
271
+ """
272
+ self.merger.merge_claude_md(src, dst)
273
+
274
+ def _copy_gitignore(self, silent: bool = False) -> None:
275
+ """.gitignore copy (optional)."""
276
+ src = self.template_root / ".gitignore"
277
+ dst = self.target_path / ".gitignore"
278
+
279
+ if not src.exists():
280
+ return
281
+
282
+ # Merge with the existing .gitignore when present
283
+ if dst.exists():
284
+ self._merge_gitignore(src, dst)
285
+ if not silent:
286
+ console.print(" 🔄 .gitignore merged")
287
+ else:
288
+ shutil.copy2(src, dst)
289
+ if not silent:
290
+ console.print(" ✅ .gitignore copy complete")
291
+
292
+ def _merge_gitignore(self, src: Path, dst: Path) -> None:
293
+ """Delegate the .gitignore merge.
294
+
295
+ Args:
296
+ src: Template .gitignore.
297
+ dst: Project .gitignore.
298
+ """
299
+ self.merger.merge_gitignore(src, dst)
300
+
301
+ def merge_config(self, detected_language: str | None = None) -> dict[str, str]:
302
+ """Delegate the smart merge for config.json.
303
+
304
+ Args:
305
+ detected_language: Detected language.
306
+
307
+ Returns:
308
+ Merged configuration dictionary.
309
+ """
310
+ return self.merger.merge_config(detected_language)