foundry-mcp 0.3.3__py3-none-any.whl → 0.7.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.
Files changed (60) hide show
  1. foundry_mcp/__init__.py +7 -1
  2. foundry_mcp/cli/commands/plan.py +10 -3
  3. foundry_mcp/cli/commands/review.py +19 -4
  4. foundry_mcp/cli/commands/specs.py +38 -208
  5. foundry_mcp/cli/output.py +3 -3
  6. foundry_mcp/config.py +235 -5
  7. foundry_mcp/core/ai_consultation.py +146 -9
  8. foundry_mcp/core/discovery.py +6 -6
  9. foundry_mcp/core/error_store.py +2 -2
  10. foundry_mcp/core/intake.py +933 -0
  11. foundry_mcp/core/llm_config.py +20 -2
  12. foundry_mcp/core/metrics_store.py +2 -2
  13. foundry_mcp/core/progress.py +70 -0
  14. foundry_mcp/core/prompts/fidelity_review.py +149 -4
  15. foundry_mcp/core/prompts/markdown_plan_review.py +5 -1
  16. foundry_mcp/core/prompts/plan_review.py +5 -1
  17. foundry_mcp/core/providers/claude.py +6 -47
  18. foundry_mcp/core/providers/codex.py +6 -57
  19. foundry_mcp/core/providers/cursor_agent.py +3 -44
  20. foundry_mcp/core/providers/gemini.py +6 -57
  21. foundry_mcp/core/providers/opencode.py +35 -5
  22. foundry_mcp/core/research/__init__.py +68 -0
  23. foundry_mcp/core/research/memory.py +425 -0
  24. foundry_mcp/core/research/models.py +437 -0
  25. foundry_mcp/core/research/workflows/__init__.py +22 -0
  26. foundry_mcp/core/research/workflows/base.py +204 -0
  27. foundry_mcp/core/research/workflows/chat.py +271 -0
  28. foundry_mcp/core/research/workflows/consensus.py +396 -0
  29. foundry_mcp/core/research/workflows/ideate.py +682 -0
  30. foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
  31. foundry_mcp/core/responses.py +450 -0
  32. foundry_mcp/core/spec.py +2438 -236
  33. foundry_mcp/core/task.py +1064 -19
  34. foundry_mcp/core/testing.py +512 -123
  35. foundry_mcp/core/validation.py +313 -42
  36. foundry_mcp/dashboard/components/charts.py +0 -57
  37. foundry_mcp/dashboard/launcher.py +11 -0
  38. foundry_mcp/dashboard/views/metrics.py +25 -35
  39. foundry_mcp/dashboard/views/overview.py +1 -65
  40. foundry_mcp/resources/specs.py +25 -25
  41. foundry_mcp/schemas/intake-schema.json +89 -0
  42. foundry_mcp/schemas/sdd-spec-schema.json +33 -5
  43. foundry_mcp/server.py +38 -0
  44. foundry_mcp/tools/unified/__init__.py +4 -2
  45. foundry_mcp/tools/unified/authoring.py +2423 -267
  46. foundry_mcp/tools/unified/documentation_helpers.py +69 -6
  47. foundry_mcp/tools/unified/environment.py +235 -6
  48. foundry_mcp/tools/unified/error.py +18 -1
  49. foundry_mcp/tools/unified/lifecycle.py +8 -0
  50. foundry_mcp/tools/unified/plan.py +113 -1
  51. foundry_mcp/tools/unified/research.py +658 -0
  52. foundry_mcp/tools/unified/review.py +370 -16
  53. foundry_mcp/tools/unified/spec.py +367 -0
  54. foundry_mcp/tools/unified/task.py +1163 -48
  55. foundry_mcp/tools/unified/test.py +69 -8
  56. {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.7.0.dist-info}/METADATA +7 -1
  57. {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.7.0.dist-info}/RECORD +60 -48
  58. {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.7.0.dist-info}/WHEEL +0 -0
  59. {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.7.0.dist-info}/entry_points.txt +0 -0
  60. {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.7.0.dist-info}/licenses/LICENSE +0 -0
foundry_mcp/__init__.py CHANGED
@@ -1,6 +1,12 @@
1
1
  """Foundry MCP - MCP server for SDD toolkit spec management."""
2
2
 
3
- __version__ = "0.1.0"
3
+ from importlib.metadata import version, PackageNotFoundError
4
+
5
+ try:
6
+ __version__ = version("foundry-mcp")
7
+ except PackageNotFoundError:
8
+ # Package not installed (development mode without editable install)
9
+ __version__ = "0.7.0"
4
10
 
5
11
  from foundry_mcp.server import create_server, main
6
12
 
@@ -20,6 +20,7 @@ from foundry_mcp.cli.resilience import (
20
20
  handle_keyboard_interrupt,
21
21
  )
22
22
  from foundry_mcp.core.spec import find_specs_directory
23
+ from foundry_mcp.core.llm_config import get_consultation_config
23
24
 
24
25
  logger = get_cli_logger()
25
26
 
@@ -135,8 +136,8 @@ def plan_group() -> None:
135
136
  "--type",
136
137
  "review_type",
137
138
  type=click.Choice(REVIEW_TYPES),
138
- default="full",
139
- help="Type of review to perform.",
139
+ default=None,
140
+ help="Type of review to perform (defaults to config value, typically 'full').",
140
141
  )
141
142
  @click.option(
142
143
  "--ai-provider",
@@ -165,7 +166,7 @@ def plan_group() -> None:
165
166
  def plan_review_cmd(
166
167
  ctx: click.Context,
167
168
  plan_path: str,
168
- review_type: str,
169
+ review_type: Optional[str],
169
170
  ai_provider: Optional[str],
170
171
  ai_timeout: float,
171
172
  no_consultation_cache: bool,
@@ -184,6 +185,12 @@ def plan_review_cmd(
184
185
 
185
186
  sdd plan review ./PLAN.md --ai-provider gemini
186
187
  """
188
+ # Get default review_type from config if not provided
189
+ if review_type is None:
190
+ consultation_config = get_consultation_config()
191
+ workflow_config = consultation_config.get_workflow_config("markdown_plan_review")
192
+ review_type = workflow_config.default_review_type
193
+
187
194
  start_time = time.perf_counter()
188
195
 
189
196
  llm_status = _get_llm_status()
@@ -16,6 +16,7 @@ AI-enhanced reviews use:
16
16
 
17
17
  import json
18
18
  import time
19
+ from pathlib import Path
19
20
  from typing import Any, Dict, List, Optional
20
21
 
21
22
  import click
@@ -42,6 +43,7 @@ from foundry_mcp.tools.unified.review_helpers import (
42
43
  _run_ai_review,
43
44
  _run_quick_review,
44
45
  )
46
+ from foundry_mcp.core.llm_config import get_consultation_config
45
47
 
46
48
  logger = get_cli_logger()
47
49
 
@@ -121,8 +123,8 @@ def review_group() -> None:
121
123
  "--type",
122
124
  "review_type",
123
125
  type=click.Choice(REVIEW_TYPES),
124
- default="quick",
125
- help="Type of review to perform.",
126
+ default=None,
127
+ help="Type of review to perform (defaults to config value, typically 'full').",
126
128
  )
127
129
  @click.option(
128
130
  "--tools",
@@ -159,7 +161,7 @@ def review_group() -> None:
159
161
  def review_spec_cmd(
160
162
  ctx: click.Context,
161
163
  spec_id: str,
162
- review_type: str,
164
+ review_type: Optional[str],
163
165
  tools: Optional[str],
164
166
  model: Optional[str],
165
167
  ai_provider: Optional[str],
@@ -172,6 +174,12 @@ def review_spec_cmd(
172
174
  cli_ctx = get_context(ctx)
173
175
  specs_dir = cli_ctx.specs_dir
174
176
 
177
+ # Get default review_type from config if not provided
178
+ if review_type is None:
179
+ consultation_config = get_consultation_config()
180
+ workflow_config = consultation_config.get_workflow_config("plan_review")
181
+ review_type = workflow_config.default_review_type
182
+
175
183
  if specs_dir is None:
176
184
  emit_error(
177
185
  "No specs directory found",
@@ -538,8 +546,15 @@ def _run_fidelity_review(
538
546
  spec_requirements = _build_spec_requirements(spec_data, task_id, phase_id)
539
547
 
540
548
  # Build implementation artifacts (file contents, git diff if incremental)
549
+ workspace_root = Path(specs_dir).parent if specs_dir else None
541
550
  implementation_artifacts = _build_implementation_artifacts(
542
- spec_data, task_id, phase_id, files, incremental, base_branch
551
+ spec_data,
552
+ task_id,
553
+ phase_id,
554
+ files,
555
+ incremental,
556
+ base_branch,
557
+ workspace_root=workspace_root,
543
558
  )
544
559
 
545
560
  # Build test results section
@@ -27,7 +27,8 @@ from foundry_mcp.core.spec import list_specs as core_list_specs, load_spec
27
27
  logger = get_cli_logger()
28
28
 
29
29
  # Valid templates and categories
30
- TEMPLATES = ("simple", "medium", "complex", "security")
30
+ # Note: Only 'empty' template is supported. Use phase templates to add structure.
31
+ TEMPLATES = ("empty",)
31
32
  CATEGORIES = ("investigation", "implementation", "refactoring", "decision", "research")
32
33
 
33
34
 
@@ -51,20 +52,31 @@ def generate_spec_id(name: str) -> str:
51
52
  def get_template_structure(template: str, category: str) -> Dict[str, Any]:
52
53
  """Get the hierarchical structure for a spec template.
53
54
 
55
+ Only 'empty' template is supported. Use phase templates to add structure.
56
+
54
57
  Args:
55
- template: Template type (simple, medium, complex, security).
58
+ template: Template type (only 'empty' is valid).
56
59
  category: Default task category.
57
60
 
58
61
  Returns:
59
62
  Hierarchy dict for the spec.
63
+
64
+ Raises:
65
+ ValueError: If template is not 'empty'.
60
66
  """
61
- base_hierarchy = {
67
+ if template != "empty":
68
+ raise ValueError(
69
+ f"Invalid template '{template}'. Only 'empty' template is supported. "
70
+ f"Use phase templates to add structure."
71
+ )
72
+
73
+ return {
62
74
  "spec-root": {
63
75
  "type": "spec",
64
76
  "title": "", # Filled in later
65
77
  "status": "pending",
66
78
  "parent": None,
67
- "children": ["phase-1"],
79
+ "children": [],
68
80
  "total_tasks": 0,
69
81
  "completed_tasks": 0,
70
82
  "metadata": {
@@ -77,180 +89,8 @@ def get_template_structure(template: str, category: str) -> Dict[str, Any]:
77
89
  "depends": [],
78
90
  },
79
91
  },
80
- "phase-1": {
81
- "type": "phase",
82
- "title": "Planning & Discovery",
83
- "status": "pending",
84
- "parent": "spec-root",
85
- "children": ["task-1-1"],
86
- "total_tasks": 1,
87
- "completed_tasks": 0,
88
- "metadata": {
89
- "purpose": "Initial planning and requirements gathering",
90
- "estimated_hours": 2,
91
- },
92
- "dependencies": {
93
- "blocks": [],
94
- "blocked_by": [],
95
- "depends": [],
96
- },
97
- },
98
- "task-1-1": {
99
- "type": "task",
100
- "title": "Define requirements",
101
- "status": "pending",
102
- "parent": "phase-1",
103
- "children": [],
104
- "total_tasks": 1,
105
- "completed_tasks": 0,
106
- "metadata": {
107
- "details": "Document the requirements and acceptance criteria",
108
- "category": category,
109
- "estimated_hours": 1,
110
- },
111
- "dependencies": {
112
- "blocks": [],
113
- "blocked_by": [],
114
- "depends": [],
115
- },
116
- },
117
92
  }
118
93
 
119
- if template == "simple":
120
- return base_hierarchy
121
-
122
- # Medium template adds implementation phase
123
- if template in ("medium", "complex", "security"):
124
- base_hierarchy["spec-root"]["children"].append("phase-2")
125
- base_hierarchy["phase-1"]["dependencies"]["blocks"].append("phase-2")
126
- base_hierarchy["phase-2"] = {
127
- "type": "phase",
128
- "title": "Implementation",
129
- "status": "pending",
130
- "parent": "spec-root",
131
- "children": ["task-2-1"],
132
- "total_tasks": 1,
133
- "completed_tasks": 0,
134
- "metadata": {
135
- "purpose": "Core implementation work",
136
- "estimated_hours": 8,
137
- },
138
- "dependencies": {
139
- "blocks": [],
140
- "blocked_by": ["phase-1"],
141
- "depends": [],
142
- },
143
- }
144
- base_hierarchy["task-2-1"] = {
145
- "type": "task",
146
- "title": "Implement core functionality",
147
- "status": "pending",
148
- "parent": "phase-2",
149
- "children": [],
150
- "total_tasks": 1,
151
- "completed_tasks": 0,
152
- "metadata": {
153
- "details": "Implement the main features",
154
- "category": category,
155
- "estimated_hours": 4,
156
- },
157
- "dependencies": {
158
- "blocks": [],
159
- "blocked_by": [],
160
- "depends": [],
161
- },
162
- }
163
- base_hierarchy["spec-root"]["total_tasks"] = 2
164
- base_hierarchy["phase-1"]["total_tasks"] = 1
165
-
166
- # Complex template adds verification phase
167
- if template in ("complex", "security"):
168
- base_hierarchy["spec-root"]["children"].append("phase-3")
169
- base_hierarchy["phase-2"]["dependencies"]["blocks"].append("phase-3")
170
- base_hierarchy["phase-3"] = {
171
- "type": "phase",
172
- "title": "Verification & Testing",
173
- "status": "pending",
174
- "parent": "spec-root",
175
- "children": ["verify-3-1"],
176
- "total_tasks": 1,
177
- "completed_tasks": 0,
178
- "metadata": {
179
- "purpose": "Verify implementation meets requirements",
180
- "estimated_hours": 4,
181
- },
182
- "dependencies": {
183
- "blocks": [],
184
- "blocked_by": ["phase-2"],
185
- "depends": [],
186
- },
187
- }
188
- base_hierarchy["verify-3-1"] = {
189
- "type": "verify",
190
- "title": "Run test suite",
191
- "status": "pending",
192
- "parent": "phase-3",
193
- "children": [],
194
- "total_tasks": 1,
195
- "completed_tasks": 0,
196
- "metadata": {
197
- "verification_type": "auto",
198
- "command": "pytest",
199
- "expected": "All tests pass",
200
- },
201
- "dependencies": {
202
- "blocks": [],
203
- "blocked_by": [],
204
- "depends": [],
205
- },
206
- }
207
- base_hierarchy["spec-root"]["total_tasks"] = 3
208
-
209
- # Security template adds security review phase
210
- if template == "security":
211
- base_hierarchy["spec-root"]["children"].append("phase-4")
212
- base_hierarchy["phase-3"]["dependencies"]["blocks"].append("phase-4")
213
- base_hierarchy["phase-4"] = {
214
- "type": "phase",
215
- "title": "Security Review",
216
- "status": "pending",
217
- "parent": "spec-root",
218
- "children": ["task-4-1"],
219
- "total_tasks": 1,
220
- "completed_tasks": 0,
221
- "metadata": {
222
- "purpose": "Security audit and hardening",
223
- "estimated_hours": 4,
224
- },
225
- "dependencies": {
226
- "blocks": [],
227
- "blocked_by": ["phase-3"],
228
- "depends": [],
229
- },
230
- }
231
- base_hierarchy["task-4-1"] = {
232
- "type": "task",
233
- "title": "Security audit",
234
- "status": "pending",
235
- "parent": "phase-4",
236
- "children": [],
237
- "total_tasks": 1,
238
- "completed_tasks": 0,
239
- "metadata": {
240
- "details": "Review for security vulnerabilities",
241
- "category": "investigation",
242
- "estimated_hours": 2,
243
- },
244
- "dependencies": {
245
- "blocks": [],
246
- "blocked_by": [],
247
- "depends": [],
248
- },
249
- }
250
- base_hierarchy["spec-root"]["total_tasks"] = 4
251
-
252
- return base_hierarchy
253
-
254
94
 
255
95
  @click.group("specs")
256
96
  def specs() -> None:
@@ -260,36 +100,18 @@ def specs() -> None:
260
100
 
261
101
  # Template definitions for listing/showing
262
102
  TEMPLATE_INFO = {
263
- "simple": {
264
- "name": "simple",
265
- "description": "Minimal spec with single planning phase",
266
- "phases": 1,
267
- "tasks": 1,
268
- "use_cases": ["Quick fixes", "Small features", "Simple investigations"],
269
- },
270
- "medium": {
271
- "name": "medium",
272
- "description": "Standard spec with planning and implementation phases",
273
- "phases": 2,
274
- "tasks": 2,
275
- "use_cases": ["New features", "Moderate refactoring", "Standard development"],
276
- },
277
- "complex": {
278
- "name": "complex",
279
- "description": "Full spec with planning, implementation, and verification phases",
280
- "phases": 3,
281
- "tasks": 3,
282
- "use_cases": ["Large features", "Major refactoring", "Critical systems"],
283
- },
284
- "security": {
285
- "name": "security",
286
- "description": "Complete spec with security review phase",
287
- "phases": 4,
288
- "tasks": 4,
289
- "use_cases": ["Security-sensitive features", "Authentication", "Data handling"],
103
+ "empty": {
104
+ "name": "empty",
105
+ "description": "Blank spec with no phases - use phase templates to add structure",
106
+ "phases": 0,
107
+ "tasks": 0,
108
+ "use_cases": ["All specs - add phases via phase-add-bulk or phase-template apply"],
290
109
  },
291
110
  }
292
111
 
112
+ # Phase templates available for adding structure
113
+ PHASE_TEMPLATES = ("planning", "implementation", "testing", "security", "documentation")
114
+
293
115
 
294
116
  @specs.command("template")
295
117
  @click.argument("action", type=click.Choice(["list", "show"]))
@@ -456,8 +278,8 @@ def analyze(ctx: click.Context, directory: Optional[str] = None) -> None:
456
278
  @click.option(
457
279
  "--template",
458
280
  type=click.Choice(TEMPLATES),
459
- default="medium",
460
- help="Spec template: simple, medium, complex, or security.",
281
+ default="empty",
282
+ help="Spec template (only 'empty' supported - use phase templates to add structure).",
461
283
  )
462
284
  @click.option(
463
285
  "--category",
@@ -465,6 +287,12 @@ def analyze(ctx: click.Context, directory: Optional[str] = None) -> None:
465
287
  default="implementation",
466
288
  help="Default task category.",
467
289
  )
290
+ @click.option(
291
+ "--mission",
292
+ type=str,
293
+ default="",
294
+ help="Optional mission statement for the spec.",
295
+ )
468
296
  @click.pass_context
469
297
  @cli_command("create")
470
298
  @handle_keyboard_interrupt()
@@ -474,6 +302,7 @@ def create(
474
302
  name: str,
475
303
  template: str,
476
304
  category: str,
305
+ mission: str,
477
306
  ) -> None:
478
307
  """Create a new specification.
479
308
 
@@ -523,8 +352,9 @@ def create(
523
352
  "last_updated": now,
524
353
  "metadata": {
525
354
  "description": "",
355
+ "mission": mission.strip(),
526
356
  "objectives": [],
527
- "complexity": "medium" if template in ("medium", "complex") else "low",
357
+ "complexity": "low", # Set explicitly via metadata, not template
528
358
  "estimated_hours": sum(
529
359
  node.get("metadata", {}).get("estimated_hours", 0)
530
360
  for node in hierarchy.values()
@@ -534,13 +364,13 @@ def create(
534
364
  "status": "pending",
535
365
  "owner": "",
536
366
  "progress_percentage": 0,
537
- "current_phase": "phase-1",
367
+ "current_phase": None, # Empty template has no phases
538
368
  "category": category,
539
369
  "template": template,
540
370
  },
541
371
  "progress_percentage": 0,
542
372
  "status": "pending",
543
- "current_phase": "phase-1",
373
+ "current_phase": None, # Empty template has no phases
544
374
  "hierarchy": hierarchy,
545
375
  "journal": [],
546
376
  }
foundry_mcp/cli/output.py CHANGED
@@ -35,12 +35,12 @@ def emit(data: Any) -> None:
35
35
  """Emit JSON to stdout.
36
36
 
37
37
  This is the single output function for all CLI commands.
38
- Data is serialized with indent=2 for readability when debugging.
38
+ Data is serialized in minified format for smaller payloads.
39
39
 
40
40
  Args:
41
41
  data: Any JSON-serializable data structure.
42
42
  """
43
- print(json.dumps(data, indent=2, default=str))
43
+ print(json.dumps(data, separators=(",", ":"), default=str))
44
44
 
45
45
 
46
46
  def emit_error(
@@ -75,7 +75,7 @@ def emit_error(
75
75
  details=details,
76
76
  request_id=_ensure_request_id(),
77
77
  )
78
- print(json.dumps(asdict(response), indent=2, default=str), file=sys.stderr)
78
+ print(json.dumps(asdict(response), separators=(",", ":"), default=str), file=sys.stderr)
79
79
  sys.exit(1)
80
80
 
81
81