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,366 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Non-LLM review logic for SDD specifications.
|
|
3
|
+
|
|
4
|
+
Provides structural review capabilities that don't require AI/LLM integration.
|
|
5
|
+
For LLM-powered reviews, use the sdd-toolkit:sdd-plan-review skill.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from foundry_mcp.core.spec import load_spec, find_specs_directory
|
|
13
|
+
from foundry_mcp.core.validation import (
|
|
14
|
+
ValidationResult,
|
|
15
|
+
validate_spec,
|
|
16
|
+
calculate_stats,
|
|
17
|
+
SpecStats,
|
|
18
|
+
)
|
|
19
|
+
from foundry_mcp.core.progress import get_progress_summary, list_phases
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Review types that don't require LLM
|
|
23
|
+
QUICK_REVIEW_TYPES = ["quick"]
|
|
24
|
+
|
|
25
|
+
# Review types that require LLM
|
|
26
|
+
LLM_REQUIRED_TYPES = ["full", "security", "feasibility"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ReviewFinding:
|
|
31
|
+
"""
|
|
32
|
+
A single review finding from structural analysis.
|
|
33
|
+
"""
|
|
34
|
+
code: str # Finding code (e.g., "EMPTY_PHASE", "MISSING_ESTIMATES")
|
|
35
|
+
message: str # Human-readable description
|
|
36
|
+
severity: str # "error", "warning", "info"
|
|
37
|
+
category: str # "structure", "quality", "completeness", "metadata"
|
|
38
|
+
location: Optional[str] = None # Node ID or path where issue occurred
|
|
39
|
+
suggestion: Optional[str] = None # Suggested fix
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class QuickReviewResult:
|
|
44
|
+
"""
|
|
45
|
+
Result of a quick (non-LLM) structural review.
|
|
46
|
+
"""
|
|
47
|
+
spec_id: str
|
|
48
|
+
title: str
|
|
49
|
+
review_type: str = "quick"
|
|
50
|
+
is_valid: bool = True
|
|
51
|
+
findings: List[ReviewFinding] = field(default_factory=list)
|
|
52
|
+
stats: Optional[SpecStats] = None
|
|
53
|
+
progress: Optional[Dict[str, Any]] = None
|
|
54
|
+
phases: Optional[List[Dict[str, Any]]] = None
|
|
55
|
+
summary: str = ""
|
|
56
|
+
error_count: int = 0
|
|
57
|
+
warning_count: int = 0
|
|
58
|
+
info_count: int = 0
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class ReviewContext:
|
|
63
|
+
"""
|
|
64
|
+
Context for review operations.
|
|
65
|
+
|
|
66
|
+
Provides spec data, progress, and other context needed for reviews.
|
|
67
|
+
"""
|
|
68
|
+
spec_id: str
|
|
69
|
+
spec_data: Dict[str, Any]
|
|
70
|
+
title: str
|
|
71
|
+
progress: Dict[str, Any]
|
|
72
|
+
phases: List[Dict[str, Any]]
|
|
73
|
+
stats: SpecStats
|
|
74
|
+
validation: ValidationResult
|
|
75
|
+
completed_tasks: List[Dict[str, Any]]
|
|
76
|
+
journal_entries: List[Dict[str, Any]]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def get_llm_status() -> Dict[str, Any]:
|
|
80
|
+
"""
|
|
81
|
+
Get LLM configuration status.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Dict with configured, provider, and model info
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
from foundry_mcp.core.llm_config import get_llm_config
|
|
88
|
+
|
|
89
|
+
config = get_llm_config()
|
|
90
|
+
return {
|
|
91
|
+
"configured": config.get_api_key() is not None,
|
|
92
|
+
"provider": config.provider.value,
|
|
93
|
+
"model": config.get_model(),
|
|
94
|
+
}
|
|
95
|
+
except ImportError:
|
|
96
|
+
return {"configured": False, "error": "LLM config not available"}
|
|
97
|
+
except Exception as e:
|
|
98
|
+
return {"configured": False, "error": str(e)}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def prepare_review_context(
|
|
102
|
+
spec_id: str,
|
|
103
|
+
specs_dir: Optional[Path] = None,
|
|
104
|
+
include_tasks: bool = True,
|
|
105
|
+
include_journals: bool = True,
|
|
106
|
+
max_journal_entries: int = 10,
|
|
107
|
+
) -> Optional[ReviewContext]:
|
|
108
|
+
"""
|
|
109
|
+
Prepare context for review operations.
|
|
110
|
+
|
|
111
|
+
Gathers spec data, validation results, progress, and other context
|
|
112
|
+
needed for both quick and LLM-powered reviews.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
spec_id: Specification ID to review
|
|
116
|
+
specs_dir: Optional specs directory (auto-discovered if not provided)
|
|
117
|
+
include_tasks: Include completed task summaries
|
|
118
|
+
include_journals: Include recent journal entries
|
|
119
|
+
max_journal_entries: Maximum journal entries to include
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
ReviewContext with all gathered data, or None if spec not found
|
|
123
|
+
"""
|
|
124
|
+
# Discover specs directory if not provided
|
|
125
|
+
if specs_dir is None:
|
|
126
|
+
specs_dir = find_specs_directory()
|
|
127
|
+
if specs_dir is None:
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
# Load spec
|
|
131
|
+
spec_data = load_spec(spec_id, specs_dir)
|
|
132
|
+
if spec_data is None:
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
title = spec_data.get("title", "Untitled")
|
|
136
|
+
hierarchy = spec_data.get("hierarchy", {})
|
|
137
|
+
|
|
138
|
+
# Get validation results
|
|
139
|
+
validation = validate_spec(spec_data)
|
|
140
|
+
|
|
141
|
+
# Get stats
|
|
142
|
+
stats = calculate_stats(spec_data)
|
|
143
|
+
|
|
144
|
+
# Get progress
|
|
145
|
+
progress = get_progress_summary(spec_data)
|
|
146
|
+
|
|
147
|
+
# Get phases
|
|
148
|
+
phases = list_phases(spec_data)
|
|
149
|
+
|
|
150
|
+
# Get completed tasks
|
|
151
|
+
completed_tasks = []
|
|
152
|
+
if include_tasks:
|
|
153
|
+
for node_id, node in hierarchy.items():
|
|
154
|
+
if node.get("type") in ("task", "subtask", "verify"):
|
|
155
|
+
if node.get("status") == "completed":
|
|
156
|
+
completed_tasks.append({
|
|
157
|
+
"id": node_id,
|
|
158
|
+
"title": node.get("title", "Untitled"),
|
|
159
|
+
"type": node.get("type"),
|
|
160
|
+
"parent": node.get("parent"),
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
# Get journal entries
|
|
164
|
+
journal_entries = []
|
|
165
|
+
if include_journals:
|
|
166
|
+
journal = spec_data.get("journal", [])
|
|
167
|
+
# Get most recent entries
|
|
168
|
+
journal_entries = journal[-max_journal_entries:] if journal else []
|
|
169
|
+
|
|
170
|
+
return ReviewContext(
|
|
171
|
+
spec_id=spec_id,
|
|
172
|
+
spec_data=spec_data,
|
|
173
|
+
title=title,
|
|
174
|
+
progress=progress,
|
|
175
|
+
phases=phases,
|
|
176
|
+
stats=stats,
|
|
177
|
+
validation=validation,
|
|
178
|
+
completed_tasks=completed_tasks,
|
|
179
|
+
journal_entries=journal_entries,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def quick_review(
|
|
184
|
+
spec_id: str,
|
|
185
|
+
specs_dir: Optional[Path] = None,
|
|
186
|
+
) -> QuickReviewResult:
|
|
187
|
+
"""
|
|
188
|
+
Perform a quick structural review of a specification.
|
|
189
|
+
|
|
190
|
+
This is a non-LLM review that checks:
|
|
191
|
+
- Spec structure and validation
|
|
192
|
+
- Progress and phase organization
|
|
193
|
+
- Task completeness and estimates
|
|
194
|
+
- Common quality issues
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
spec_id: Specification ID to review
|
|
198
|
+
specs_dir: Optional specs directory
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
QuickReviewResult with findings and stats
|
|
202
|
+
"""
|
|
203
|
+
# Get review context
|
|
204
|
+
context = prepare_review_context(
|
|
205
|
+
spec_id=spec_id,
|
|
206
|
+
specs_dir=specs_dir,
|
|
207
|
+
include_tasks=True,
|
|
208
|
+
include_journals=False,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if context is None:
|
|
212
|
+
return QuickReviewResult(
|
|
213
|
+
spec_id=spec_id,
|
|
214
|
+
title="Unknown",
|
|
215
|
+
is_valid=False,
|
|
216
|
+
summary="Specification not found",
|
|
217
|
+
findings=[
|
|
218
|
+
ReviewFinding(
|
|
219
|
+
code="SPEC_NOT_FOUND",
|
|
220
|
+
message=f"Specification '{spec_id}' not found",
|
|
221
|
+
severity="error",
|
|
222
|
+
category="structure",
|
|
223
|
+
)
|
|
224
|
+
],
|
|
225
|
+
error_count=1,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
findings: List[ReviewFinding] = []
|
|
229
|
+
|
|
230
|
+
# Convert validation diagnostics to findings
|
|
231
|
+
for diag in context.validation.diagnostics:
|
|
232
|
+
findings.append(
|
|
233
|
+
ReviewFinding(
|
|
234
|
+
code=diag.code,
|
|
235
|
+
message=diag.message,
|
|
236
|
+
severity=diag.severity,
|
|
237
|
+
category=diag.category,
|
|
238
|
+
location=diag.location,
|
|
239
|
+
suggestion=diag.suggested_fix,
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Check for empty phases (phases with no children in the hierarchy)
|
|
244
|
+
hierarchy = context.spec_data.get("hierarchy", {})
|
|
245
|
+
for node_id, node in hierarchy.items():
|
|
246
|
+
if node.get("type") == "phase":
|
|
247
|
+
children = node.get("children", [])
|
|
248
|
+
if len(children) == 0:
|
|
249
|
+
findings.append(
|
|
250
|
+
ReviewFinding(
|
|
251
|
+
code="EMPTY_PHASE",
|
|
252
|
+
message=f"Phase '{node.get('title', node_id)}' has no tasks",
|
|
253
|
+
severity="warning",
|
|
254
|
+
category="structure",
|
|
255
|
+
location=node_id,
|
|
256
|
+
suggestion="Add tasks to this phase or remove it",
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Check for missing estimates
|
|
261
|
+
tasks_without_estimates = 0
|
|
262
|
+
for node_id, node in hierarchy.items():
|
|
263
|
+
if node.get("type") in ("task", "subtask"):
|
|
264
|
+
metadata = node.get("metadata", {})
|
|
265
|
+
if metadata.get("estimated_hours") is None:
|
|
266
|
+
tasks_without_estimates += 1
|
|
267
|
+
|
|
268
|
+
if tasks_without_estimates > 0:
|
|
269
|
+
findings.append(
|
|
270
|
+
ReviewFinding(
|
|
271
|
+
code="MISSING_ESTIMATES",
|
|
272
|
+
message=f"{tasks_without_estimates} task(s) are missing time estimates",
|
|
273
|
+
severity="info",
|
|
274
|
+
category="completeness",
|
|
275
|
+
suggestion="Add estimated_hours to task metadata for better planning",
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Check for tasks without file paths
|
|
280
|
+
tasks_without_files = 0
|
|
281
|
+
for node_id, node in hierarchy.items():
|
|
282
|
+
if node.get("type") in ("task", "subtask"):
|
|
283
|
+
metadata = node.get("metadata", {})
|
|
284
|
+
if not metadata.get("file_path"):
|
|
285
|
+
tasks_without_files += 1
|
|
286
|
+
|
|
287
|
+
if tasks_without_files > 0:
|
|
288
|
+
findings.append(
|
|
289
|
+
ReviewFinding(
|
|
290
|
+
code="MISSING_FILE_PATHS",
|
|
291
|
+
message=f"{tasks_without_files} task(s) are missing file path references",
|
|
292
|
+
severity="info",
|
|
293
|
+
category="completeness",
|
|
294
|
+
suggestion="Add file_path to task metadata for better navigation",
|
|
295
|
+
)
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Check for blocked tasks
|
|
299
|
+
blocked_count = 0
|
|
300
|
+
for node_id, node in hierarchy.items():
|
|
301
|
+
if node.get("status") == "blocked":
|
|
302
|
+
blocked_count += 1
|
|
303
|
+
|
|
304
|
+
if blocked_count > 0:
|
|
305
|
+
findings.append(
|
|
306
|
+
ReviewFinding(
|
|
307
|
+
code="BLOCKED_TASKS",
|
|
308
|
+
message=f"{blocked_count} task(s) are currently blocked",
|
|
309
|
+
severity="warning",
|
|
310
|
+
category="quality",
|
|
311
|
+
suggestion="Resolve blockers to continue progress",
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Check overall progress
|
|
316
|
+
progress = context.progress
|
|
317
|
+
percentage = progress.get("percentage", 0)
|
|
318
|
+
|
|
319
|
+
# Count severities
|
|
320
|
+
error_count = sum(1 for f in findings if f.severity == "error")
|
|
321
|
+
warning_count = sum(1 for f in findings if f.severity == "warning")
|
|
322
|
+
info_count = sum(1 for f in findings if f.severity == "info")
|
|
323
|
+
|
|
324
|
+
# Build summary
|
|
325
|
+
summary_parts = [
|
|
326
|
+
f"Reviewed '{context.title}' ({spec_id})",
|
|
327
|
+
f"Progress: {percentage:.0f}% ({progress.get('completed_tasks', 0)}/{progress.get('total_tasks', 0)} tasks)",
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
if error_count > 0:
|
|
331
|
+
summary_parts.append(f"{error_count} error(s)")
|
|
332
|
+
if warning_count > 0:
|
|
333
|
+
summary_parts.append(f"{warning_count} warning(s)")
|
|
334
|
+
if info_count > 0:
|
|
335
|
+
summary_parts.append(f"{info_count} info finding(s)")
|
|
336
|
+
|
|
337
|
+
if error_count == 0 and warning_count == 0:
|
|
338
|
+
summary_parts.append("No critical issues found")
|
|
339
|
+
|
|
340
|
+
return QuickReviewResult(
|
|
341
|
+
spec_id=spec_id,
|
|
342
|
+
title=context.title,
|
|
343
|
+
review_type="quick",
|
|
344
|
+
is_valid=context.validation.is_valid,
|
|
345
|
+
findings=findings,
|
|
346
|
+
stats=context.stats,
|
|
347
|
+
progress=context.progress,
|
|
348
|
+
phases=context.phases,
|
|
349
|
+
summary=". ".join(summary_parts) + ".",
|
|
350
|
+
error_count=error_count,
|
|
351
|
+
warning_count=warning_count,
|
|
352
|
+
info_count=info_count,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def review_type_requires_llm(review_type: str) -> bool:
|
|
357
|
+
"""
|
|
358
|
+
Check if a review type requires LLM.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
review_type: Review type to check
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
True if LLM is required, False for quick reviews
|
|
365
|
+
"""
|
|
366
|
+
return review_type in LLM_REQUIRED_TYPES
|