x-ipe 1.0.17__py3-none-any.whl → 1.0.19__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.
x_ipe/cli/main.py CHANGED
@@ -211,6 +211,12 @@ def init(ctx: click.Context, force: bool, dry_run: bool, no_skills: bool) -> Non
211
211
  # Copy config files (copilot-prompt.json, tools.json, .env.example)
212
212
  scaffold.copy_config_files()
213
213
 
214
+ # Copy planning templates (features.md, task-board.md)
215
+ scaffold.copy_planning_templates()
216
+
217
+ # Copy default theme
218
+ scaffold.copy_themes()
219
+
214
220
  # Create config file
215
221
  scaffold.create_config_file()
216
222
 
@@ -218,6 +224,39 @@ def init(ctx: click.Context, force: bool, dry_run: bool, no_skills: bool) -> Non
218
224
  if (project_root / ".git").exists():
219
225
  scaffold.update_gitignore()
220
226
 
227
+ # MCP config merge with user confirmation
228
+ mcp_servers = scaffold.get_project_mcp_servers()
229
+ if mcp_servers and not dry_run:
230
+ click.echo("\n" + "-" * 40)
231
+ click.echo("MCP Server Configuration")
232
+ click.echo("-" * 40)
233
+
234
+ # Show available servers
235
+ click.echo(f"\nFound {len(mcp_servers)} MCP server(s) in project config:")
236
+ for name in mcp_servers:
237
+ click.echo(f" • {name}")
238
+
239
+ # Confirm each server
240
+ servers_to_merge = []
241
+ for name in mcp_servers:
242
+ if click.confirm(f"\nAdd '{name}' to global MCP config?", default=True):
243
+ servers_to_merge.append(name)
244
+
245
+ if servers_to_merge:
246
+ # Confirm target path
247
+ default_path = Path.home() / ".copilot" / "mcp-config.json"
248
+ target_path = click.prompt(
249
+ "\nTarget MCP config path",
250
+ default=str(default_path),
251
+ type=click.Path(dir_okay=False, path_type=Path)
252
+ )
253
+
254
+ scaffold.merge_mcp_config(
255
+ servers_to_merge=servers_to_merge,
256
+ target_path=target_path
257
+ )
258
+ click.echo(f"\n✓ Merged {len(servers_to_merge)} MCP server(s) to {target_path}")
259
+
221
260
  # Show summary
222
261
  created, skipped = scaffold.get_summary()
223
262
 
x_ipe/core/scaffold.py CHANGED
@@ -3,6 +3,7 @@ from pathlib import Path
3
3
  from typing import List, Tuple, Optional
4
4
  import shutil
5
5
  import os
6
+ import json
6
7
 
7
8
 
8
9
  class ScaffoldManager:
@@ -14,6 +15,7 @@ class ScaffoldManager:
14
15
  "x-ipe-docs/features",
15
16
  "x-ipe-docs/ideas",
16
17
  "x-ipe-docs/config",
18
+ "x-ipe-docs/themes",
17
19
  ]
18
20
 
19
21
  GITIGNORE_ENTRIES = [
@@ -143,6 +145,90 @@ class ScaffoldManager:
143
145
  shutil.copy2(source, target)
144
146
  self.created.append(target)
145
147
 
148
+ def get_project_mcp_servers(self) -> dict:
149
+ """Get MCP servers from project's .github/copilot/mcp-config.json.
150
+
151
+ Returns:
152
+ Dict of server_name -> server_config, or empty dict if not found.
153
+ """
154
+ project_mcp = self.project_root / ".github" / "copilot" / "mcp-config.json"
155
+ if not project_mcp.exists():
156
+ return {}
157
+
158
+ try:
159
+ project_config = json.loads(project_mcp.read_text())
160
+ return project_config.get("mcpServers", {})
161
+ except (json.JSONDecodeError, IOError):
162
+ return {}
163
+
164
+ def merge_mcp_config(
165
+ self,
166
+ servers_to_merge: Optional[List[str]] = None,
167
+ target_path: Optional[Path] = None
168
+ ) -> None:
169
+ """Merge project's MCP servers into global config.
170
+
171
+ Args:
172
+ servers_to_merge: List of server names to merge. If None, merges all.
173
+ target_path: Path to target mcp-config.json. Defaults to ~/.copilot/mcp-config.json.
174
+
175
+ This allows project-specific MCP servers to be available globally.
176
+ Deep-merges mcpServers objects, with project servers added to global config.
177
+ Existing global servers are preserved unless --force is used for conflicts.
178
+ """
179
+ project_servers = self.get_project_mcp_servers()
180
+ if not project_servers:
181
+ return
182
+
183
+ # Filter to requested servers if specified
184
+ if servers_to_merge is not None:
185
+ project_servers = {k: v for k, v in project_servers.items() if k in servers_to_merge}
186
+ if not project_servers:
187
+ return
188
+
189
+ # Target: configurable or default to ~/.copilot/mcp-config.json
190
+ if target_path is None:
191
+ global_copilot_dir = Path.home() / ".copilot"
192
+ global_mcp = global_copilot_dir / "mcp-config.json"
193
+ else:
194
+ global_mcp = Path(target_path)
195
+ global_copilot_dir = global_mcp.parent
196
+
197
+ if self.dry_run:
198
+ self.created.append(global_mcp)
199
+ return
200
+
201
+ # Load or create global config
202
+ global_config = {"mcpServers": {}}
203
+ if global_mcp.exists():
204
+ try:
205
+ global_config = json.loads(global_mcp.read_text())
206
+ if "mcpServers" not in global_config:
207
+ global_config["mcpServers"] = {}
208
+ except (json.JSONDecodeError, IOError):
209
+ global_config = {"mcpServers": {}}
210
+
211
+ # Merge: add project servers to global
212
+ merged_count = 0
213
+ skipped_count = 0
214
+ for server_name, server_config in project_servers.items():
215
+ if server_name in global_config["mcpServers"]:
216
+ if self.force:
217
+ global_config["mcpServers"][server_name] = server_config
218
+ merged_count += 1
219
+ else:
220
+ skipped_count += 1
221
+ else:
222
+ global_config["mcpServers"][server_name] = server_config
223
+ merged_count += 1
224
+
225
+ if merged_count > 0:
226
+ global_copilot_dir.mkdir(parents=True, exist_ok=True)
227
+ global_mcp.write_text(json.dumps(global_config, indent=2) + "\n")
228
+ self.created.append(global_mcp)
229
+ elif skipped_count > 0:
230
+ self.skipped.append(global_mcp)
231
+
146
232
  def copy_config_files(self) -> None:
147
233
  """Copy config files (copilot-prompt.json, tools.json, .env.example) to x-ipe-docs/config/."""
148
234
  config_source = self._get_resource_path("config")
@@ -170,6 +256,60 @@ class ScaffoldManager:
170
256
  shutil.copy2(source_file, target_file)
171
257
  self.created.append(target_file)
172
258
 
259
+ def copy_planning_templates(self) -> None:
260
+ """Copy planning templates (features.md, task-board.md) to x-ipe-docs/planning/."""
261
+ planning_source = self._get_resource_path("planning")
262
+ if planning_source is None or not planning_source.exists():
263
+ return
264
+
265
+ target_dir = self.project_root / "x-ipe-docs" / "planning"
266
+
267
+ # Copy each planning file individually (don't overwrite existing)
268
+ planning_files = ["features.md", "task-board.md"]
269
+ for filename in planning_files:
270
+ source_file = planning_source / filename
271
+ target_file = target_dir / filename
272
+
273
+ if not source_file.exists():
274
+ continue
275
+
276
+ if target_file.exists():
277
+ if not self.force:
278
+ self.skipped.append(target_file)
279
+ continue
280
+
281
+ if not self.dry_run:
282
+ target_dir.mkdir(parents=True, exist_ok=True)
283
+ shutil.copy2(source_file, target_file)
284
+ self.created.append(target_file)
285
+
286
+ def copy_themes(self) -> None:
287
+ """Copy default theme to x-ipe-docs/themes/."""
288
+ themes_source = self._get_resource_path("themes")
289
+ if themes_source is None or not themes_source.exists():
290
+ return
291
+
292
+ target_dir = self.project_root / "x-ipe-docs" / "themes"
293
+
294
+ # Copy entire theme-default folder
295
+ theme_source = themes_source / "theme-default"
296
+ theme_target = target_dir / "theme-default"
297
+
298
+ if not theme_source.exists():
299
+ return
300
+
301
+ if theme_target.exists():
302
+ if not self.force:
303
+ self.skipped.append(theme_target)
304
+ return
305
+
306
+ if not self.dry_run:
307
+ target_dir.mkdir(parents=True, exist_ok=True)
308
+ if theme_target.exists() and self.force:
309
+ shutil.rmtree(theme_target)
310
+ shutil.copytree(theme_source, theme_target, dirs_exist_ok=True)
311
+ self.created.append(theme_target)
312
+
173
313
  def create_config_file(self, config_content: Optional[str] = None) -> None:
174
314
  """Create .x-ipe.yaml with defaults.
175
315
 
@@ -243,8 +383,11 @@ server:
243
383
  self.copy_skills()
244
384
  self.copy_copilot_instructions()
245
385
  self.copy_config_files()
386
+ self.copy_planning_templates()
387
+ self.copy_themes()
246
388
  self.create_config_file()
247
389
  self.update_gitignore()
390
+ self.merge_mcp_config()
248
391
  return self.get_summary()
249
392
 
250
393
  def get_summary(self) -> Tuple[List[Path], List[Path]]:
@@ -0,0 +1,53 @@
1
+ # Feature Board
2
+
3
+ > Last Updated: {DATE}
4
+
5
+ ## Overview
6
+
7
+ This board tracks all features across the project lifecycle.
8
+
9
+ **Status Definitions:**
10
+ - **Planned** - Feature identified, awaiting refinement
11
+ - **Refined** - Specification complete, ready for design
12
+ - **Designed** - Technical design complete, ready for implementation
13
+ - **Implemented** - Code complete, ready for testing
14
+ - **Tested** - Tests complete, ready for deployment
15
+ - **Completed** - Feature fully deployed and verified
16
+
17
+ ---
18
+
19
+ ## Feature Tracking
20
+
21
+ | Feature ID | Feature Title | Version | Status | Specification Link | Created | Last Updated |
22
+ |------------|---------------|---------|--------|-------------------|---------|--------------|
23
+ | _No features yet_ | | | | | | |
24
+
25
+ ---
26
+
27
+ ## Status Details
28
+
29
+ ### Planned (0)
30
+ - None
31
+
32
+ ### Refined (0)
33
+ - None
34
+
35
+ ### Designed (0)
36
+ - None
37
+
38
+ ### Implemented (0)
39
+ - None
40
+
41
+ ### Tested (0)
42
+ - None
43
+
44
+ ### Completed (0)
45
+ - None
46
+
47
+ ---
48
+
49
+ ## Feature Details
50
+
51
+ _Features will appear here after creation_
52
+
53
+ ---
@@ -0,0 +1,77 @@
1
+ # Task Board
2
+
3
+ > Task Board Management - Task Tracking
4
+
5
+ ## Global Settings
6
+
7
+ ```yaml
8
+ auto_proceed: false # Set to true for automatic task chaining
9
+ ```
10
+
11
+ ---
12
+
13
+ ## Active Tasks
14
+
15
+ | Task ID | Task Type | Description | Role | Status | Last Updated | Output Links | Next Task |
16
+ |---------|-----------|-------------|------|--------|--------------|--------------|----------|
17
+ | | | | | | | | |
18
+
19
+ ---
20
+
21
+ ## Completed Tasks
22
+
23
+ | Task ID | Task Type | Description | Role | Last Updated | Output Links | Notes |
24
+ |---------|-----------|-------------|------|--------------|--------------|-------|
25
+ | | | | | | | |
26
+
27
+ ---
28
+
29
+ ## Cancelled Tasks
30
+
31
+ | Task ID | Task Type | Description | Reason | Last Updated | Output Links |
32
+ |---------|-----------|-------------|--------|--------------|--------------|
33
+ | | | | | | |
34
+
35
+ ---
36
+
37
+ ## Status Legend
38
+
39
+ | Status | Symbol | Description |
40
+ |--------|--------|-------------|
41
+ | pending | ⏳ | Waiting to start |
42
+ | in_progress | 🔄 | Working |
43
+ | blocked | 🚫 | Waiting for dependency |
44
+ | deferred | ⏸️ | Paused by human |
45
+ | completed | ✅ | Done |
46
+ | cancelled | ❌ | Stopped |
47
+
48
+ ---
49
+
50
+ ## Task Type Quick Reference
51
+
52
+ | Task Type | Skill | Default Next |
53
+ |-----------|-------|--------------|
54
+ | Requirement Gathering | task-type-requirement-gathering | Feature Breakdown |
55
+ | Feature Breakdown | task-type-feature-breakdown | Technical Design |
56
+ | Technical Design | task-type-technical-design | Test Generation |
57
+ | Test Generation | task-type-test-generation | Code Implementation |
58
+ | Code Implementation | task-type-code-implementation | Human Playground |
59
+ | Human Playground | task-type-human-playground | Feature Closing |
60
+ | Feature Closing | task-type-feature-closing | - |
61
+ | Code Refactor | task-type-code-refactor | - |
62
+ | Project Initialization | task-type-project-init | Dev Environment Setup |
63
+ | Dev Environment Setup | task-type-dev-environment | - |
64
+
65
+ ---
66
+
67
+ ## Quick Stats
68
+
69
+ - **Total Active:** 0
70
+ - **In Progress:** 0
71
+ - **Pending:** 0
72
+ - **Pending Review:** 0
73
+ - **Blocked:** 0
74
+ - **Deferred:** 0
75
+ - **Completed:** 0
76
+
77
+ ---