foundry-mcp 0.8.22__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 foundry-mcp might be problematic. Click here for more details.
- foundry_mcp/__init__.py +13 -0
- foundry_mcp/cli/__init__.py +67 -0
- foundry_mcp/cli/__main__.py +9 -0
- foundry_mcp/cli/agent.py +96 -0
- foundry_mcp/cli/commands/__init__.py +37 -0
- foundry_mcp/cli/commands/cache.py +137 -0
- foundry_mcp/cli/commands/dashboard.py +148 -0
- foundry_mcp/cli/commands/dev.py +446 -0
- foundry_mcp/cli/commands/journal.py +377 -0
- foundry_mcp/cli/commands/lifecycle.py +274 -0
- foundry_mcp/cli/commands/modify.py +824 -0
- foundry_mcp/cli/commands/plan.py +640 -0
- foundry_mcp/cli/commands/pr.py +393 -0
- foundry_mcp/cli/commands/review.py +667 -0
- foundry_mcp/cli/commands/session.py +472 -0
- foundry_mcp/cli/commands/specs.py +686 -0
- foundry_mcp/cli/commands/tasks.py +807 -0
- foundry_mcp/cli/commands/testing.py +676 -0
- foundry_mcp/cli/commands/validate.py +982 -0
- foundry_mcp/cli/config.py +98 -0
- foundry_mcp/cli/context.py +298 -0
- foundry_mcp/cli/logging.py +212 -0
- foundry_mcp/cli/main.py +44 -0
- foundry_mcp/cli/output.py +122 -0
- foundry_mcp/cli/registry.py +110 -0
- foundry_mcp/cli/resilience.py +178 -0
- foundry_mcp/cli/transcript.py +217 -0
- foundry_mcp/config.py +1454 -0
- foundry_mcp/core/__init__.py +144 -0
- foundry_mcp/core/ai_consultation.py +1773 -0
- foundry_mcp/core/batch_operations.py +1202 -0
- foundry_mcp/core/cache.py +195 -0
- foundry_mcp/core/capabilities.py +446 -0
- foundry_mcp/core/concurrency.py +898 -0
- foundry_mcp/core/context.py +540 -0
- foundry_mcp/core/discovery.py +1603 -0
- foundry_mcp/core/error_collection.py +728 -0
- foundry_mcp/core/error_store.py +592 -0
- foundry_mcp/core/health.py +749 -0
- foundry_mcp/core/intake.py +933 -0
- foundry_mcp/core/journal.py +700 -0
- foundry_mcp/core/lifecycle.py +412 -0
- foundry_mcp/core/llm_config.py +1376 -0
- foundry_mcp/core/llm_patterns.py +510 -0
- foundry_mcp/core/llm_provider.py +1569 -0
- foundry_mcp/core/logging_config.py +374 -0
- foundry_mcp/core/metrics_persistence.py +584 -0
- foundry_mcp/core/metrics_registry.py +327 -0
- foundry_mcp/core/metrics_store.py +641 -0
- foundry_mcp/core/modifications.py +224 -0
- foundry_mcp/core/naming.py +146 -0
- foundry_mcp/core/observability.py +1216 -0
- foundry_mcp/core/otel.py +452 -0
- foundry_mcp/core/otel_stubs.py +264 -0
- foundry_mcp/core/pagination.py +255 -0
- foundry_mcp/core/progress.py +387 -0
- foundry_mcp/core/prometheus.py +564 -0
- foundry_mcp/core/prompts/__init__.py +464 -0
- foundry_mcp/core/prompts/fidelity_review.py +691 -0
- foundry_mcp/core/prompts/markdown_plan_review.py +515 -0
- foundry_mcp/core/prompts/plan_review.py +627 -0
- foundry_mcp/core/providers/__init__.py +237 -0
- foundry_mcp/core/providers/base.py +515 -0
- foundry_mcp/core/providers/claude.py +472 -0
- foundry_mcp/core/providers/codex.py +637 -0
- foundry_mcp/core/providers/cursor_agent.py +630 -0
- foundry_mcp/core/providers/detectors.py +515 -0
- foundry_mcp/core/providers/gemini.py +426 -0
- foundry_mcp/core/providers/opencode.py +718 -0
- foundry_mcp/core/providers/opencode_wrapper.js +308 -0
- foundry_mcp/core/providers/package-lock.json +24 -0
- foundry_mcp/core/providers/package.json +25 -0
- foundry_mcp/core/providers/registry.py +607 -0
- foundry_mcp/core/providers/test_provider.py +171 -0
- foundry_mcp/core/providers/validation.py +857 -0
- foundry_mcp/core/rate_limit.py +427 -0
- foundry_mcp/core/research/__init__.py +68 -0
- foundry_mcp/core/research/memory.py +528 -0
- foundry_mcp/core/research/models.py +1234 -0
- foundry_mcp/core/research/providers/__init__.py +40 -0
- foundry_mcp/core/research/providers/base.py +242 -0
- foundry_mcp/core/research/providers/google.py +507 -0
- foundry_mcp/core/research/providers/perplexity.py +442 -0
- foundry_mcp/core/research/providers/semantic_scholar.py +544 -0
- foundry_mcp/core/research/providers/tavily.py +383 -0
- foundry_mcp/core/research/workflows/__init__.py +25 -0
- foundry_mcp/core/research/workflows/base.py +298 -0
- foundry_mcp/core/research/workflows/chat.py +271 -0
- foundry_mcp/core/research/workflows/consensus.py +539 -0
- foundry_mcp/core/research/workflows/deep_research.py +4142 -0
- foundry_mcp/core/research/workflows/ideate.py +682 -0
- foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
- foundry_mcp/core/resilience.py +600 -0
- foundry_mcp/core/responses.py +1624 -0
- foundry_mcp/core/review.py +366 -0
- foundry_mcp/core/security.py +438 -0
- foundry_mcp/core/spec.py +4119 -0
- foundry_mcp/core/task.py +2463 -0
- foundry_mcp/core/testing.py +839 -0
- foundry_mcp/core/validation.py +2357 -0
- foundry_mcp/dashboard/__init__.py +32 -0
- foundry_mcp/dashboard/app.py +119 -0
- foundry_mcp/dashboard/components/__init__.py +17 -0
- foundry_mcp/dashboard/components/cards.py +88 -0
- foundry_mcp/dashboard/components/charts.py +177 -0
- foundry_mcp/dashboard/components/filters.py +136 -0
- foundry_mcp/dashboard/components/tables.py +195 -0
- foundry_mcp/dashboard/data/__init__.py +11 -0
- foundry_mcp/dashboard/data/stores.py +433 -0
- foundry_mcp/dashboard/launcher.py +300 -0
- foundry_mcp/dashboard/views/__init__.py +12 -0
- foundry_mcp/dashboard/views/errors.py +217 -0
- foundry_mcp/dashboard/views/metrics.py +164 -0
- foundry_mcp/dashboard/views/overview.py +96 -0
- foundry_mcp/dashboard/views/providers.py +83 -0
- foundry_mcp/dashboard/views/sdd_workflow.py +255 -0
- foundry_mcp/dashboard/views/tool_usage.py +139 -0
- foundry_mcp/prompts/__init__.py +9 -0
- foundry_mcp/prompts/workflows.py +525 -0
- foundry_mcp/resources/__init__.py +9 -0
- foundry_mcp/resources/specs.py +591 -0
- foundry_mcp/schemas/__init__.py +38 -0
- foundry_mcp/schemas/intake-schema.json +89 -0
- foundry_mcp/schemas/sdd-spec-schema.json +414 -0
- foundry_mcp/server.py +150 -0
- foundry_mcp/tools/__init__.py +10 -0
- foundry_mcp/tools/unified/__init__.py +92 -0
- foundry_mcp/tools/unified/authoring.py +3620 -0
- foundry_mcp/tools/unified/context_helpers.py +98 -0
- foundry_mcp/tools/unified/documentation_helpers.py +268 -0
- foundry_mcp/tools/unified/environment.py +1341 -0
- foundry_mcp/tools/unified/error.py +479 -0
- foundry_mcp/tools/unified/health.py +225 -0
- foundry_mcp/tools/unified/journal.py +841 -0
- foundry_mcp/tools/unified/lifecycle.py +640 -0
- foundry_mcp/tools/unified/metrics.py +777 -0
- foundry_mcp/tools/unified/plan.py +876 -0
- foundry_mcp/tools/unified/pr.py +294 -0
- foundry_mcp/tools/unified/provider.py +589 -0
- foundry_mcp/tools/unified/research.py +1283 -0
- foundry_mcp/tools/unified/review.py +1042 -0
- foundry_mcp/tools/unified/review_helpers.py +314 -0
- foundry_mcp/tools/unified/router.py +102 -0
- foundry_mcp/tools/unified/server.py +565 -0
- foundry_mcp/tools/unified/spec.py +1283 -0
- foundry_mcp/tools/unified/task.py +3846 -0
- foundry_mcp/tools/unified/test.py +431 -0
- foundry_mcp/tools/unified/verification.py +520 -0
- foundry_mcp-0.8.22.dist-info/METADATA +344 -0
- foundry_mcp-0.8.22.dist-info/RECORD +153 -0
- foundry_mcp-0.8.22.dist-info/WHEEL +4 -0
- foundry_mcp-0.8.22.dist-info/entry_points.txt +3 -0
- foundry_mcp-0.8.22.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"""PR workflow commands for SDD CLI.
|
|
2
|
+
|
|
3
|
+
Provides commands for creating GitHub PRs with SDD spec context
|
|
4
|
+
and AI-enhanced PR descriptions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import subprocess
|
|
9
|
+
import time
|
|
10
|
+
from typing import Any, Dict, Optional, cast
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
|
|
14
|
+
from foundry_mcp.cli.logging import cli_command, get_cli_logger
|
|
15
|
+
from foundry_mcp.cli.output import emit_error, emit_success
|
|
16
|
+
from foundry_mcp.cli.registry import get_context
|
|
17
|
+
from foundry_mcp.cli.resilience import (
|
|
18
|
+
FAST_TIMEOUT,
|
|
19
|
+
MEDIUM_TIMEOUT,
|
|
20
|
+
with_sync_timeout,
|
|
21
|
+
handle_keyboard_interrupt,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = get_cli_logger()
|
|
25
|
+
|
|
26
|
+
# Default timeout for PR operations
|
|
27
|
+
PR_TIMEOUT = 120
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@click.group("pr")
|
|
31
|
+
def pr_group() -> None:
|
|
32
|
+
"""Pull request workflow commands."""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pr_group.command("create")
|
|
37
|
+
@click.argument("spec_id")
|
|
38
|
+
@click.option(
|
|
39
|
+
"--title",
|
|
40
|
+
help="PR title (default: auto-generated from spec).",
|
|
41
|
+
)
|
|
42
|
+
@click.option(
|
|
43
|
+
"--base",
|
|
44
|
+
"base_branch",
|
|
45
|
+
default="main",
|
|
46
|
+
help="Base branch for PR.",
|
|
47
|
+
)
|
|
48
|
+
@click.option(
|
|
49
|
+
"--include-journals/--no-journals",
|
|
50
|
+
default=True,
|
|
51
|
+
help="Include journal entries in PR description.",
|
|
52
|
+
)
|
|
53
|
+
@click.option(
|
|
54
|
+
"--include-diffs/--no-diffs",
|
|
55
|
+
default=True,
|
|
56
|
+
help="Include git diffs in LLM context.",
|
|
57
|
+
)
|
|
58
|
+
@click.option(
|
|
59
|
+
"--model",
|
|
60
|
+
help="LLM model for description generation.",
|
|
61
|
+
)
|
|
62
|
+
@click.option(
|
|
63
|
+
"--dry-run",
|
|
64
|
+
is_flag=True,
|
|
65
|
+
help="Preview PR content without creating.",
|
|
66
|
+
)
|
|
67
|
+
@click.pass_context
|
|
68
|
+
@cli_command("create")
|
|
69
|
+
@handle_keyboard_interrupt()
|
|
70
|
+
@with_sync_timeout(PR_TIMEOUT, "PR creation timed out")
|
|
71
|
+
def pr_create_cmd(
|
|
72
|
+
ctx: click.Context,
|
|
73
|
+
spec_id: str,
|
|
74
|
+
title: Optional[str],
|
|
75
|
+
base_branch: str,
|
|
76
|
+
include_journals: bool,
|
|
77
|
+
include_diffs: bool,
|
|
78
|
+
model: Optional[str],
|
|
79
|
+
dry_run: bool,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Create a GitHub PR with AI-enhanced description from SDD spec.
|
|
82
|
+
|
|
83
|
+
SPEC_ID is the specification identifier.
|
|
84
|
+
|
|
85
|
+
Uses spec context (tasks, journals, progress) to generate
|
|
86
|
+
comprehensive PR descriptions. Requires GitHub CLI (gh).
|
|
87
|
+
"""
|
|
88
|
+
start_time = time.perf_counter()
|
|
89
|
+
cli_ctx = get_context(ctx)
|
|
90
|
+
specs_dir = cli_ctx.specs_dir
|
|
91
|
+
|
|
92
|
+
if specs_dir is None:
|
|
93
|
+
emit_error(
|
|
94
|
+
"No specs directory found",
|
|
95
|
+
code="VALIDATION_ERROR",
|
|
96
|
+
error_type="validation",
|
|
97
|
+
remediation="Use --specs-dir option or set SDD_SPECS_DIR environment variable",
|
|
98
|
+
details={"hint": "Use --specs-dir or set SDD_SPECS_DIR"},
|
|
99
|
+
)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
# Get LLM status
|
|
103
|
+
llm_status = _get_llm_status()
|
|
104
|
+
|
|
105
|
+
# Build command
|
|
106
|
+
cmd = ["sdd", "create-pr", spec_id, "--json"]
|
|
107
|
+
|
|
108
|
+
if title:
|
|
109
|
+
cmd.extend(["--title", title])
|
|
110
|
+
cmd.extend(["--base", base_branch])
|
|
111
|
+
|
|
112
|
+
if include_journals:
|
|
113
|
+
cmd.append("--include-journals")
|
|
114
|
+
if include_diffs:
|
|
115
|
+
cmd.append("--include-diffs")
|
|
116
|
+
if model:
|
|
117
|
+
cmd.extend(["--model", model])
|
|
118
|
+
if specs_dir:
|
|
119
|
+
cmd.extend(["--path", str(specs_dir.parent)])
|
|
120
|
+
if dry_run:
|
|
121
|
+
cmd.append("--dry-run")
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
result = subprocess.run(
|
|
125
|
+
cmd,
|
|
126
|
+
capture_output=True,
|
|
127
|
+
text=True,
|
|
128
|
+
timeout=PR_TIMEOUT,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
132
|
+
|
|
133
|
+
if result.returncode != 0:
|
|
134
|
+
error_msg = result.stderr.strip() if result.stderr else "PR creation failed"
|
|
135
|
+
emit_error(
|
|
136
|
+
f"PR creation failed: {error_msg}",
|
|
137
|
+
code="PR_FAILED",
|
|
138
|
+
error_type="internal",
|
|
139
|
+
remediation="Ensure GitHub CLI is authenticated and repository is properly configured",
|
|
140
|
+
details={
|
|
141
|
+
"spec_id": spec_id,
|
|
142
|
+
"exit_code": result.returncode,
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
# Parse output
|
|
148
|
+
try:
|
|
149
|
+
pr_data = json.loads(result.stdout)
|
|
150
|
+
except json.JSONDecodeError:
|
|
151
|
+
pr_data = {"raw_output": result.stdout}
|
|
152
|
+
|
|
153
|
+
emit_success(
|
|
154
|
+
{
|
|
155
|
+
"spec_id": spec_id,
|
|
156
|
+
"dry_run": dry_run,
|
|
157
|
+
"llm_status": llm_status,
|
|
158
|
+
**pr_data,
|
|
159
|
+
"telemetry": {"duration_ms": round(duration_ms, 2)},
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
except subprocess.TimeoutExpired:
|
|
164
|
+
emit_error(
|
|
165
|
+
f"PR creation timed out after {PR_TIMEOUT}s",
|
|
166
|
+
code="TIMEOUT",
|
|
167
|
+
error_type="internal",
|
|
168
|
+
remediation="Check network connectivity and GitHub API status",
|
|
169
|
+
details={
|
|
170
|
+
"spec_id": spec_id,
|
|
171
|
+
"timeout_seconds": PR_TIMEOUT,
|
|
172
|
+
},
|
|
173
|
+
)
|
|
174
|
+
except FileNotFoundError:
|
|
175
|
+
emit_error(
|
|
176
|
+
"SDD CLI not found",
|
|
177
|
+
code="CLI_NOT_FOUND",
|
|
178
|
+
error_type="internal",
|
|
179
|
+
remediation="Ensure 'sdd' is installed and in PATH",
|
|
180
|
+
details={"hint": "Ensure 'sdd' is installed and in PATH"},
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@pr_group.command("context")
|
|
185
|
+
@click.argument("spec_id")
|
|
186
|
+
@click.option(
|
|
187
|
+
"--include-tasks/--no-tasks",
|
|
188
|
+
default=True,
|
|
189
|
+
help="Include completed task summaries.",
|
|
190
|
+
)
|
|
191
|
+
@click.option(
|
|
192
|
+
"--include-journals/--no-journals",
|
|
193
|
+
default=True,
|
|
194
|
+
help="Include recent journal entries.",
|
|
195
|
+
)
|
|
196
|
+
@click.option(
|
|
197
|
+
"--include-progress/--no-progress",
|
|
198
|
+
default=True,
|
|
199
|
+
help="Include phase/task progress stats.",
|
|
200
|
+
)
|
|
201
|
+
@click.pass_context
|
|
202
|
+
@cli_command("context")
|
|
203
|
+
@handle_keyboard_interrupt()
|
|
204
|
+
@with_sync_timeout(MEDIUM_TIMEOUT, "PR context lookup timed out")
|
|
205
|
+
def pr_context_cmd(
|
|
206
|
+
ctx: click.Context,
|
|
207
|
+
spec_id: str,
|
|
208
|
+
include_tasks: bool,
|
|
209
|
+
include_journals: bool,
|
|
210
|
+
include_progress: bool,
|
|
211
|
+
) -> None:
|
|
212
|
+
"""Get specification context for PR description generation.
|
|
213
|
+
|
|
214
|
+
SPEC_ID is the specification identifier.
|
|
215
|
+
|
|
216
|
+
Retrieves completed tasks, journal entries, and progress
|
|
217
|
+
to help craft meaningful PR descriptions.
|
|
218
|
+
"""
|
|
219
|
+
start_time = time.perf_counter()
|
|
220
|
+
cli_ctx = get_context(ctx)
|
|
221
|
+
specs_dir = cli_ctx.specs_dir
|
|
222
|
+
|
|
223
|
+
if specs_dir is None:
|
|
224
|
+
emit_error(
|
|
225
|
+
"No specs directory found",
|
|
226
|
+
code="VALIDATION_ERROR",
|
|
227
|
+
error_type="validation",
|
|
228
|
+
remediation="Use --specs-dir option or set SDD_SPECS_DIR environment variable",
|
|
229
|
+
details={"hint": "Use --specs-dir or set SDD_SPECS_DIR"},
|
|
230
|
+
)
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
# Use prepare_review_context from core/review for consistent context gathering
|
|
234
|
+
from foundry_mcp.core.review import ReviewContext, prepare_review_context
|
|
235
|
+
|
|
236
|
+
review_ctx = prepare_review_context(
|
|
237
|
+
spec_id=spec_id,
|
|
238
|
+
specs_dir=specs_dir,
|
|
239
|
+
include_tasks=include_tasks,
|
|
240
|
+
include_journals=include_journals,
|
|
241
|
+
max_journal_entries=5,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if review_ctx is None:
|
|
245
|
+
emit_error(
|
|
246
|
+
f"Specification not found: {spec_id}",
|
|
247
|
+
code="SPEC_NOT_FOUND",
|
|
248
|
+
error_type="not_found",
|
|
249
|
+
remediation="Verify the spec ID exists using: sdd specs list",
|
|
250
|
+
details={"spec_id": spec_id},
|
|
251
|
+
)
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
review_ctx = cast(ReviewContext, review_ctx)
|
|
255
|
+
|
|
256
|
+
context: Dict[str, Any] = {
|
|
257
|
+
"spec_id": spec_id,
|
|
258
|
+
"title": review_ctx.title,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# Get progress stats from context
|
|
262
|
+
if include_progress:
|
|
263
|
+
context["progress"] = {
|
|
264
|
+
"total_tasks": review_ctx.progress.get("total_tasks", 0),
|
|
265
|
+
"completed_tasks": review_ctx.progress.get("completed_tasks", 0),
|
|
266
|
+
"percentage": review_ctx.progress.get("percentage", 0),
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# Get completed tasks from context
|
|
270
|
+
if include_tasks:
|
|
271
|
+
context["completed_tasks"] = review_ctx.completed_tasks
|
|
272
|
+
|
|
273
|
+
# Get journal entries from context
|
|
274
|
+
if include_journals:
|
|
275
|
+
context["journals"] = review_ctx.journal_entries
|
|
276
|
+
|
|
277
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
278
|
+
|
|
279
|
+
emit_success(
|
|
280
|
+
{
|
|
281
|
+
**context,
|
|
282
|
+
"telemetry": {"duration_ms": round(duration_ms, 2)},
|
|
283
|
+
}
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@pr_group.command("status")
|
|
288
|
+
@click.pass_context
|
|
289
|
+
@cli_command("status")
|
|
290
|
+
@handle_keyboard_interrupt()
|
|
291
|
+
@with_sync_timeout(FAST_TIMEOUT, "PR status check timed out")
|
|
292
|
+
def pr_status_cmd(ctx: click.Context) -> None:
|
|
293
|
+
"""Check prerequisites for PR creation."""
|
|
294
|
+
start_time = time.perf_counter()
|
|
295
|
+
|
|
296
|
+
# Check GitHub CLI
|
|
297
|
+
gh_available = False
|
|
298
|
+
gh_version = None
|
|
299
|
+
try:
|
|
300
|
+
result = subprocess.run(
|
|
301
|
+
["gh", "--version"],
|
|
302
|
+
capture_output=True,
|
|
303
|
+
text=True,
|
|
304
|
+
timeout=5.0,
|
|
305
|
+
)
|
|
306
|
+
gh_available = result.returncode == 0
|
|
307
|
+
if gh_available:
|
|
308
|
+
gh_version = result.stdout.split("\n")[0].strip()
|
|
309
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
310
|
+
pass
|
|
311
|
+
|
|
312
|
+
# Check if authenticated
|
|
313
|
+
gh_authenticated = False
|
|
314
|
+
if gh_available:
|
|
315
|
+
try:
|
|
316
|
+
result = subprocess.run(
|
|
317
|
+
["gh", "auth", "status"],
|
|
318
|
+
capture_output=True,
|
|
319
|
+
text=True,
|
|
320
|
+
timeout=5.0,
|
|
321
|
+
)
|
|
322
|
+
gh_authenticated = result.returncode == 0
|
|
323
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
324
|
+
pass
|
|
325
|
+
|
|
326
|
+
# Check LLM status
|
|
327
|
+
llm_status = _get_llm_status()
|
|
328
|
+
|
|
329
|
+
# Check git status
|
|
330
|
+
git_clean = False
|
|
331
|
+
try:
|
|
332
|
+
result = subprocess.run(
|
|
333
|
+
["git", "status", "--porcelain"],
|
|
334
|
+
capture_output=True,
|
|
335
|
+
text=True,
|
|
336
|
+
timeout=5.0,
|
|
337
|
+
)
|
|
338
|
+
git_clean = result.returncode == 0 and not result.stdout.strip()
|
|
339
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
340
|
+
pass
|
|
341
|
+
|
|
342
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
343
|
+
|
|
344
|
+
# Build status
|
|
345
|
+
prerequisites = {
|
|
346
|
+
"github_cli": {
|
|
347
|
+
"available": gh_available,
|
|
348
|
+
"version": gh_version,
|
|
349
|
+
"authenticated": gh_authenticated,
|
|
350
|
+
},
|
|
351
|
+
"git": {
|
|
352
|
+
"clean": git_clean,
|
|
353
|
+
},
|
|
354
|
+
"llm": llm_status,
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
ready = gh_available and gh_authenticated
|
|
358
|
+
recommendations = []
|
|
359
|
+
|
|
360
|
+
if not gh_available:
|
|
361
|
+
recommendations.append("Install GitHub CLI: https://cli.github.com/")
|
|
362
|
+
elif not gh_authenticated:
|
|
363
|
+
recommendations.append("Authenticate with: gh auth login")
|
|
364
|
+
if not git_clean:
|
|
365
|
+
recommendations.append("Commit or stash uncommitted changes")
|
|
366
|
+
if not llm_status.get("configured"):
|
|
367
|
+
recommendations.append("Configure LLM for enhanced PR descriptions")
|
|
368
|
+
|
|
369
|
+
emit_success(
|
|
370
|
+
{
|
|
371
|
+
"ready": ready,
|
|
372
|
+
"prerequisites": prerequisites,
|
|
373
|
+
"recommendations": recommendations,
|
|
374
|
+
"telemetry": {"duration_ms": round(duration_ms, 2)},
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _get_llm_status() -> dict:
|
|
380
|
+
"""Get LLM configuration status."""
|
|
381
|
+
try:
|
|
382
|
+
from foundry_mcp.core.llm_config import get_llm_config
|
|
383
|
+
|
|
384
|
+
config = get_llm_config()
|
|
385
|
+
return {
|
|
386
|
+
"configured": config.get_api_key() is not None,
|
|
387
|
+
"provider": config.provider.value,
|
|
388
|
+
"model": config.get_model(),
|
|
389
|
+
}
|
|
390
|
+
except ImportError:
|
|
391
|
+
return {"configured": False, "error": "LLM config not available"}
|
|
392
|
+
except Exception as e:
|
|
393
|
+
return {"configured": False, "error": str(e)}
|