x-ipe 1.0.17__py3-none-any.whl → 1.0.18__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 +36 -0
- x_ipe/core/scaffold.py +114 -0
- x_ipe/resources/planning/features.md +53 -0
- x_ipe/resources/planning/task-board.md +77 -0
- x_ipe/static/3rdparty/html2canvas.min.js +20 -0
- x_ipe/static/css/uiux-feedback.css +35 -0
- x_ipe/static/js/features/workplace.js +43 -0
- x_ipe/static/js/terminal-v2.js +25 -6
- x_ipe/static/js/uiux-feedback.js +276 -21
- x_ipe/templates/base.html +1 -1
- {x_ipe-1.0.17.dist-info → x_ipe-1.0.18.dist-info}/METADATA +1 -1
- {x_ipe-1.0.17.dist-info → x_ipe-1.0.18.dist-info}/RECORD +15 -12
- {x_ipe-1.0.17.dist-info → x_ipe-1.0.18.dist-info}/WHEEL +0 -0
- {x_ipe-1.0.17.dist-info → x_ipe-1.0.18.dist-info}/entry_points.txt +0 -0
- {x_ipe-1.0.17.dist-info → x_ipe-1.0.18.dist-info}/licenses/LICENSE +0 -0
x_ipe/cli/main.py
CHANGED
|
@@ -211,6 +211,9 @@ 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
|
+
|
|
214
217
|
# Create config file
|
|
215
218
|
scaffold.create_config_file()
|
|
216
219
|
|
|
@@ -218,6 +221,39 @@ def init(ctx: click.Context, force: bool, dry_run: bool, no_skills: bool) -> Non
|
|
|
218
221
|
if (project_root / ".git").exists():
|
|
219
222
|
scaffold.update_gitignore()
|
|
220
223
|
|
|
224
|
+
# MCP config merge with user confirmation
|
|
225
|
+
mcp_servers = scaffold.get_project_mcp_servers()
|
|
226
|
+
if mcp_servers and not dry_run:
|
|
227
|
+
click.echo("\n" + "-" * 40)
|
|
228
|
+
click.echo("MCP Server Configuration")
|
|
229
|
+
click.echo("-" * 40)
|
|
230
|
+
|
|
231
|
+
# Show available servers
|
|
232
|
+
click.echo(f"\nFound {len(mcp_servers)} MCP server(s) in project config:")
|
|
233
|
+
for name in mcp_servers:
|
|
234
|
+
click.echo(f" • {name}")
|
|
235
|
+
|
|
236
|
+
# Confirm each server
|
|
237
|
+
servers_to_merge = []
|
|
238
|
+
for name in mcp_servers:
|
|
239
|
+
if click.confirm(f"\nAdd '{name}' to global MCP config?", default=True):
|
|
240
|
+
servers_to_merge.append(name)
|
|
241
|
+
|
|
242
|
+
if servers_to_merge:
|
|
243
|
+
# Confirm target path
|
|
244
|
+
default_path = Path.home() / ".copilot" / "mcp-config.json"
|
|
245
|
+
target_path = click.prompt(
|
|
246
|
+
"\nTarget MCP config path",
|
|
247
|
+
default=str(default_path),
|
|
248
|
+
type=click.Path(dir_okay=False, path_type=Path)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
scaffold.merge_mcp_config(
|
|
252
|
+
servers_to_merge=servers_to_merge,
|
|
253
|
+
target_path=target_path
|
|
254
|
+
)
|
|
255
|
+
click.echo(f"\n✓ Merged {len(servers_to_merge)} MCP server(s) to {target_path}")
|
|
256
|
+
|
|
221
257
|
# Show summary
|
|
222
258
|
created, skipped = scaffold.get_summary()
|
|
223
259
|
|
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:
|
|
@@ -143,6 +144,90 @@ class ScaffoldManager:
|
|
|
143
144
|
shutil.copy2(source, target)
|
|
144
145
|
self.created.append(target)
|
|
145
146
|
|
|
147
|
+
def get_project_mcp_servers(self) -> dict:
|
|
148
|
+
"""Get MCP servers from project's .github/copilot/mcp-config.json.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Dict of server_name -> server_config, or empty dict if not found.
|
|
152
|
+
"""
|
|
153
|
+
project_mcp = self.project_root / ".github" / "copilot" / "mcp-config.json"
|
|
154
|
+
if not project_mcp.exists():
|
|
155
|
+
return {}
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
project_config = json.loads(project_mcp.read_text())
|
|
159
|
+
return project_config.get("mcpServers", {})
|
|
160
|
+
except (json.JSONDecodeError, IOError):
|
|
161
|
+
return {}
|
|
162
|
+
|
|
163
|
+
def merge_mcp_config(
|
|
164
|
+
self,
|
|
165
|
+
servers_to_merge: Optional[List[str]] = None,
|
|
166
|
+
target_path: Optional[Path] = None
|
|
167
|
+
) -> None:
|
|
168
|
+
"""Merge project's MCP servers into global config.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
servers_to_merge: List of server names to merge. If None, merges all.
|
|
172
|
+
target_path: Path to target mcp-config.json. Defaults to ~/.copilot/mcp-config.json.
|
|
173
|
+
|
|
174
|
+
This allows project-specific MCP servers to be available globally.
|
|
175
|
+
Deep-merges mcpServers objects, with project servers added to global config.
|
|
176
|
+
Existing global servers are preserved unless --force is used for conflicts.
|
|
177
|
+
"""
|
|
178
|
+
project_servers = self.get_project_mcp_servers()
|
|
179
|
+
if not project_servers:
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
# Filter to requested servers if specified
|
|
183
|
+
if servers_to_merge is not None:
|
|
184
|
+
project_servers = {k: v for k, v in project_servers.items() if k in servers_to_merge}
|
|
185
|
+
if not project_servers:
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
# Target: configurable or default to ~/.copilot/mcp-config.json
|
|
189
|
+
if target_path is None:
|
|
190
|
+
global_copilot_dir = Path.home() / ".copilot"
|
|
191
|
+
global_mcp = global_copilot_dir / "mcp-config.json"
|
|
192
|
+
else:
|
|
193
|
+
global_mcp = Path(target_path)
|
|
194
|
+
global_copilot_dir = global_mcp.parent
|
|
195
|
+
|
|
196
|
+
if self.dry_run:
|
|
197
|
+
self.created.append(global_mcp)
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
# Load or create global config
|
|
201
|
+
global_config = {"mcpServers": {}}
|
|
202
|
+
if global_mcp.exists():
|
|
203
|
+
try:
|
|
204
|
+
global_config = json.loads(global_mcp.read_text())
|
|
205
|
+
if "mcpServers" not in global_config:
|
|
206
|
+
global_config["mcpServers"] = {}
|
|
207
|
+
except (json.JSONDecodeError, IOError):
|
|
208
|
+
global_config = {"mcpServers": {}}
|
|
209
|
+
|
|
210
|
+
# Merge: add project servers to global
|
|
211
|
+
merged_count = 0
|
|
212
|
+
skipped_count = 0
|
|
213
|
+
for server_name, server_config in project_servers.items():
|
|
214
|
+
if server_name in global_config["mcpServers"]:
|
|
215
|
+
if self.force:
|
|
216
|
+
global_config["mcpServers"][server_name] = server_config
|
|
217
|
+
merged_count += 1
|
|
218
|
+
else:
|
|
219
|
+
skipped_count += 1
|
|
220
|
+
else:
|
|
221
|
+
global_config["mcpServers"][server_name] = server_config
|
|
222
|
+
merged_count += 1
|
|
223
|
+
|
|
224
|
+
if merged_count > 0:
|
|
225
|
+
global_copilot_dir.mkdir(parents=True, exist_ok=True)
|
|
226
|
+
global_mcp.write_text(json.dumps(global_config, indent=2) + "\n")
|
|
227
|
+
self.created.append(global_mcp)
|
|
228
|
+
elif skipped_count > 0:
|
|
229
|
+
self.skipped.append(global_mcp)
|
|
230
|
+
|
|
146
231
|
def copy_config_files(self) -> None:
|
|
147
232
|
"""Copy config files (copilot-prompt.json, tools.json, .env.example) to x-ipe-docs/config/."""
|
|
148
233
|
config_source = self._get_resource_path("config")
|
|
@@ -170,6 +255,33 @@ class ScaffoldManager:
|
|
|
170
255
|
shutil.copy2(source_file, target_file)
|
|
171
256
|
self.created.append(target_file)
|
|
172
257
|
|
|
258
|
+
def copy_planning_templates(self) -> None:
|
|
259
|
+
"""Copy planning templates (features.md, task-board.md) to x-ipe-docs/planning/."""
|
|
260
|
+
planning_source = self._get_resource_path("planning")
|
|
261
|
+
if planning_source is None or not planning_source.exists():
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
target_dir = self.project_root / "x-ipe-docs" / "planning"
|
|
265
|
+
|
|
266
|
+
# Copy each planning file individually (don't overwrite existing)
|
|
267
|
+
planning_files = ["features.md", "task-board.md"]
|
|
268
|
+
for filename in planning_files:
|
|
269
|
+
source_file = planning_source / filename
|
|
270
|
+
target_file = target_dir / filename
|
|
271
|
+
|
|
272
|
+
if not source_file.exists():
|
|
273
|
+
continue
|
|
274
|
+
|
|
275
|
+
if target_file.exists():
|
|
276
|
+
if not self.force:
|
|
277
|
+
self.skipped.append(target_file)
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
if not self.dry_run:
|
|
281
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
282
|
+
shutil.copy2(source_file, target_file)
|
|
283
|
+
self.created.append(target_file)
|
|
284
|
+
|
|
173
285
|
def create_config_file(self, config_content: Optional[str] = None) -> None:
|
|
174
286
|
"""Create .x-ipe.yaml with defaults.
|
|
175
287
|
|
|
@@ -243,8 +355,10 @@ server:
|
|
|
243
355
|
self.copy_skills()
|
|
244
356
|
self.copy_copilot_instructions()
|
|
245
357
|
self.copy_config_files()
|
|
358
|
+
self.copy_planning_templates()
|
|
246
359
|
self.create_config_file()
|
|
247
360
|
self.update_gitignore()
|
|
361
|
+
self.merge_mcp_config()
|
|
248
362
|
return self.get_summary()
|
|
249
363
|
|
|
250
364
|
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
|
+
---
|