forge-dev 0.1.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.
- forge_core/__init__.py +3 -0
- forge_core/agents/__init__.py +1 -0
- forge_core/auditor.py +330 -0
- forge_core/cli.py +552 -0
- forge_core/detector.py +209 -0
- forge_core/editor_bridge.py +543 -0
- forge_core/models.py +332 -0
- forge_core/phases/__init__.py +1 -0
- forge_core/phases/coherence.py +293 -0
- forge_core/phases/context.py +264 -0
- forge_core/phases/intake.py +340 -0
- forge_core/registry.py +247 -0
- forge_core/standards/api-first-design.yaml +24 -0
- forge_core/standards/microservice-packaging.yaml +30 -0
- forge_core/standards/observability.yaml +31 -0
- forge_core/standards/security-baseline.yaml +43 -0
- forge_core/standards/type-safety.yaml +23 -0
- forge_core/templates/__init__.py +1 -0
- forge_core/utils/__init__.py +1 -0
- forge_dev-0.1.0.dist-info/METADATA +134 -0
- forge_dev-0.1.0.dist-info/RECORD +25 -0
- forge_dev-0.1.0.dist-info/WHEEL +4 -0
- forge_dev-0.1.0.dist-info/entry_points.txt +2 -0
- mcp_server/__init__.py +1 -0
- mcp_server/server.py +1086 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""Context Phase — resolves the full project context.
|
|
2
|
+
|
|
3
|
+
Takes detection results + user config + brief and produces
|
|
4
|
+
a ProjectContext that captures all decisions for this project.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import yaml
|
|
13
|
+
|
|
14
|
+
from forge_core.detector import ProjectDetection
|
|
15
|
+
from forge_core.models import (
|
|
16
|
+
AIConfig,
|
|
17
|
+
APIConfig,
|
|
18
|
+
AuthPattern,
|
|
19
|
+
BackendFramework,
|
|
20
|
+
CICDConfig,
|
|
21
|
+
CloudProvider,
|
|
22
|
+
DatabaseType,
|
|
23
|
+
FrontendFramework,
|
|
24
|
+
ObservabilityConfig,
|
|
25
|
+
ProjectContext,
|
|
26
|
+
ProjectType,
|
|
27
|
+
Regulatory,
|
|
28
|
+
StandardsConfig,
|
|
29
|
+
UserConfig,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def resolve_context(
|
|
34
|
+
detection: ProjectDetection,
|
|
35
|
+
user_config: UserConfig,
|
|
36
|
+
project_name: str | None = None,
|
|
37
|
+
overrides: dict | None = None,
|
|
38
|
+
) -> ProjectContext:
|
|
39
|
+
"""Resolve the full project context from detection + user defaults + overrides.
|
|
40
|
+
|
|
41
|
+
Priority order (highest wins):
|
|
42
|
+
1. Explicit overrides passed in
|
|
43
|
+
2. Detection results (what we found in the code)
|
|
44
|
+
3. User config defaults
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
detection: What we found in the directory
|
|
48
|
+
user_config: Global user defaults
|
|
49
|
+
project_name: Override for project name
|
|
50
|
+
overrides: Explicit overrides for any field
|
|
51
|
+
"""
|
|
52
|
+
overrides = overrides or {}
|
|
53
|
+
|
|
54
|
+
# Project name: override > directory name
|
|
55
|
+
name = project_name or overrides.get("name") or detection.path.name
|
|
56
|
+
|
|
57
|
+
# Backend: override > detected > user default (None means ask)
|
|
58
|
+
backend = _resolve_backend(detection, user_config, overrides)
|
|
59
|
+
|
|
60
|
+
# Frontend: override > detected > user default
|
|
61
|
+
frontend = _resolve_enum(
|
|
62
|
+
FrontendFramework,
|
|
63
|
+
overrides.get("frontend"),
|
|
64
|
+
detection.detected_stack.get("frontend"),
|
|
65
|
+
user_config.frontend,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
context = ProjectContext(
|
|
69
|
+
name=name,
|
|
70
|
+
type=overrides.get("type", ProjectType.SAAS),
|
|
71
|
+
description=overrides.get("description", ""),
|
|
72
|
+
regulatory=_resolve_regulatory(overrides),
|
|
73
|
+
cloud=_resolve_enum(
|
|
74
|
+
CloudProvider,
|
|
75
|
+
overrides.get("cloud"),
|
|
76
|
+
None,
|
|
77
|
+
user_config.cloud,
|
|
78
|
+
),
|
|
79
|
+
backend=backend,
|
|
80
|
+
frontend=frontend,
|
|
81
|
+
database=_resolve_enum(
|
|
82
|
+
DatabaseType,
|
|
83
|
+
overrides.get("database"),
|
|
84
|
+
None,
|
|
85
|
+
user_config.database,
|
|
86
|
+
),
|
|
87
|
+
auth=_resolve_enum(
|
|
88
|
+
AuthPattern,
|
|
89
|
+
overrides.get("auth"),
|
|
90
|
+
None,
|
|
91
|
+
user_config.auth,
|
|
92
|
+
),
|
|
93
|
+
ai=_merge_config(AIConfig, user_config.ai, overrides.get("ai", {})),
|
|
94
|
+
observability=_merge_config(
|
|
95
|
+
ObservabilityConfig, user_config.observability, overrides.get("observability", {})
|
|
96
|
+
),
|
|
97
|
+
api=_merge_config(APIConfig, user_config.api, overrides.get("api", {})),
|
|
98
|
+
cicd=_merge_config(CICDConfig, user_config.cicd, overrides.get("cicd", {})),
|
|
99
|
+
standards=_merge_config(
|
|
100
|
+
StandardsConfig, user_config.standards, overrides.get("standards", {})
|
|
101
|
+
),
|
|
102
|
+
forge_version=overrides.get("forge_version", "0.1.0"),
|
|
103
|
+
created_at=datetime.now(timezone.utc).isoformat(),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return context
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def save_context(context: ProjectContext, project_path: Path) -> Path:
|
|
110
|
+
"""Save ProjectContext to .forge/context.yaml."""
|
|
111
|
+
forge_dir = project_path / ".forge"
|
|
112
|
+
forge_dir.mkdir(parents=True, exist_ok=True)
|
|
113
|
+
|
|
114
|
+
context_path = forge_dir / "context.yaml"
|
|
115
|
+
with open(context_path, "w") as f:
|
|
116
|
+
yaml.dump(
|
|
117
|
+
context.model_dump(mode="json"),
|
|
118
|
+
f,
|
|
119
|
+
default_flow_style=False,
|
|
120
|
+
sort_keys=False,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return context_path
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def load_context(project_path: Path) -> ProjectContext | None:
|
|
127
|
+
"""Load an existing ProjectContext."""
|
|
128
|
+
context_path = project_path / ".forge" / "context.yaml"
|
|
129
|
+
if not context_path.exists():
|
|
130
|
+
return None
|
|
131
|
+
with open(context_path) as f:
|
|
132
|
+
data = yaml.safe_load(f) or {}
|
|
133
|
+
return ProjectContext(**data)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def build_context_prompt(detection: ProjectDetection, user_config: UserConfig) -> str:
|
|
137
|
+
"""Build a prompt for the LLM to help resolve context interactively.
|
|
138
|
+
|
|
139
|
+
Used when forge init detects an empty directory and needs to
|
|
140
|
+
have a conversation with the user.
|
|
141
|
+
"""
|
|
142
|
+
return f"""You are Forge, an AI development workflow engine. Help the user set up their project context.
|
|
143
|
+
|
|
144
|
+
## Current Detection
|
|
145
|
+
{detection.summary()}
|
|
146
|
+
|
|
147
|
+
## User Defaults
|
|
148
|
+
- Cloud: {user_config.cloud.value}
|
|
149
|
+
- Backend: {user_config.backend.value if user_config.backend else 'Not set (ask each time)'}
|
|
150
|
+
- Frontend: {user_config.frontend.value}
|
|
151
|
+
- Database: {user_config.database.value}
|
|
152
|
+
- Auth: {user_config.auth.value}
|
|
153
|
+
- AI enabled: {user_config.ai.enabled}
|
|
154
|
+
- Observability: {user_config.observability.apm} + {user_config.observability.metrics} + {user_config.observability.logs}
|
|
155
|
+
|
|
156
|
+
## Instructions
|
|
157
|
+
Based on the detection and user defaults, help resolve the project context.
|
|
158
|
+
If the directory is empty, ask the user:
|
|
159
|
+
1. What is this project? (name, description, type)
|
|
160
|
+
2. Are there any regulatory requirements? (HIPAA, FERPA, etc.)
|
|
161
|
+
3. Backend preference for this project? (user default is to ask each time)
|
|
162
|
+
4. Any overrides to the defaults above?
|
|
163
|
+
5. Does this project use AI capabilities?
|
|
164
|
+
|
|
165
|
+
If the directory has code, confirm the detected stack and ask about anything not detected.
|
|
166
|
+
|
|
167
|
+
Keep questions concise. One question at a time if in conversation mode.
|
|
168
|
+
Suggest defaults based on the user's preferences — they can just confirm."""
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def get_questions_for_empty_project(user_config: UserConfig) -> list[dict]:
|
|
172
|
+
"""Return the questions to ask for a brand new project."""
|
|
173
|
+
questions = [
|
|
174
|
+
{
|
|
175
|
+
"id": "mission",
|
|
176
|
+
"question": "What is this project? Give me a one-liner.",
|
|
177
|
+
"type": "text",
|
|
178
|
+
"required": True,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"id": "type",
|
|
182
|
+
"question": "What type of project is this?",
|
|
183
|
+
"type": "choice",
|
|
184
|
+
"options": [t.value for t in ProjectType],
|
|
185
|
+
"default": ProjectType.SAAS.value,
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"id": "regulatory",
|
|
189
|
+
"question": "Any regulatory requirements?",
|
|
190
|
+
"type": "multi_choice",
|
|
191
|
+
"options": [r.value for r in Regulatory],
|
|
192
|
+
"default": [Regulatory.NONE.value],
|
|
193
|
+
},
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
# Only ask about backend if user doesn't have a default
|
|
197
|
+
if user_config.backend is None:
|
|
198
|
+
questions.append({
|
|
199
|
+
"id": "backend",
|
|
200
|
+
"question": "Backend framework for this project?",
|
|
201
|
+
"type": "choice",
|
|
202
|
+
"options": [b.value for b in BackendFramework if b != BackendFramework.OTHER],
|
|
203
|
+
"default": BackendFramework.FASTAPI.value,
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
questions.append({
|
|
207
|
+
"id": "ai_enabled",
|
|
208
|
+
"question": "Does this project use AI capabilities internally?",
|
|
209
|
+
"type": "boolean",
|
|
210
|
+
"default": True,
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
return questions
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# ── Private helpers ────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
def _resolve_backend(
|
|
219
|
+
detection: ProjectDetection, user_config: UserConfig, overrides: dict
|
|
220
|
+
) -> BackendFramework:
|
|
221
|
+
"""Resolve backend framework with priority: override > detected > user default."""
|
|
222
|
+
if "backend" in overrides:
|
|
223
|
+
return BackendFramework(overrides["backend"])
|
|
224
|
+
if "backend" in detection.detected_stack:
|
|
225
|
+
try:
|
|
226
|
+
return BackendFramework(detection.detected_stack["backend"])
|
|
227
|
+
except ValueError:
|
|
228
|
+
pass
|
|
229
|
+
if user_config.backend is not None:
|
|
230
|
+
return user_config.backend
|
|
231
|
+
# Default fallback
|
|
232
|
+
return BackendFramework.FASTAPI
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _resolve_enum(enum_class, override, detected, default):
|
|
236
|
+
"""Resolve an enum value with priority: override > detected > default."""
|
|
237
|
+
for val in (override, detected, default):
|
|
238
|
+
if val is not None:
|
|
239
|
+
try:
|
|
240
|
+
return enum_class(val)
|
|
241
|
+
except (ValueError, KeyError):
|
|
242
|
+
continue
|
|
243
|
+
return default
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _resolve_regulatory(overrides: dict) -> list[Regulatory]:
|
|
247
|
+
"""Resolve regulatory requirements from overrides."""
|
|
248
|
+
reg = overrides.get("regulatory", [])
|
|
249
|
+
if not reg:
|
|
250
|
+
return []
|
|
251
|
+
return [Regulatory(r) for r in reg if r != "none"]
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _merge_config(model_class, base_config, overrides):
|
|
255
|
+
"""Merge a base config with overrides, returning a new config instance."""
|
|
256
|
+
if isinstance(base_config, dict):
|
|
257
|
+
base_data = base_config
|
|
258
|
+
else:
|
|
259
|
+
base_data = base_config.model_dump(mode="json")
|
|
260
|
+
|
|
261
|
+
if isinstance(overrides, dict):
|
|
262
|
+
base_data.update(overrides)
|
|
263
|
+
|
|
264
|
+
return model_class(**base_data)
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""Intake Phase — transforms any requirement into a normalized Forge Brief.
|
|
2
|
+
|
|
3
|
+
This phase receives whatever the user has:
|
|
4
|
+
- A PRD document
|
|
5
|
+
- A user story or epic
|
|
6
|
+
- A vague conversation or Slack message
|
|
7
|
+
- A feature request
|
|
8
|
+
|
|
9
|
+
And produces a ForgeBrief — a normalized document that all subsequent
|
|
10
|
+
phases consume.
|
|
11
|
+
|
|
12
|
+
The key insight: the output isn't just "what features to build" but
|
|
13
|
+
"in what order should an AI coding agent implement them" — types first,
|
|
14
|
+
then infra, then services, then endpoints, then UI.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
import yaml
|
|
23
|
+
|
|
24
|
+
from forge_core.models import ForgeBrief, RequirementType
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ── AI-Optimized Implementation Ordering ───────────────────────────────────
|
|
28
|
+
|
|
29
|
+
AI_IMPLEMENTATION_PHASES = [
|
|
30
|
+
{
|
|
31
|
+
"phase": 1,
|
|
32
|
+
"name": "types_and_contracts",
|
|
33
|
+
"title": "Types, Schemas & Contracts",
|
|
34
|
+
"description": (
|
|
35
|
+
"Define all TypeScript/Python types, Pydantic models, interfaces, "
|
|
36
|
+
"and OpenAPI schemas. This gives the AI agent complete context for "
|
|
37
|
+
"everything that follows — minimizing hallucinations in later phases."
|
|
38
|
+
),
|
|
39
|
+
"examples": [
|
|
40
|
+
"Pydantic models for all domain entities",
|
|
41
|
+
"TypeScript interfaces for frontend state",
|
|
42
|
+
"OpenAPI 3.1 schema definitions",
|
|
43
|
+
"Database migration schemas",
|
|
44
|
+
"Event/message schemas for async communication",
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"phase": 2,
|
|
49
|
+
"name": "infrastructure",
|
|
50
|
+
"title": "Infrastructure & Configuration",
|
|
51
|
+
"description": (
|
|
52
|
+
"Set up cloud resources, IaC, environment configs, secrets management. "
|
|
53
|
+
"Everything the code will depend on at runtime."
|
|
54
|
+
),
|
|
55
|
+
"examples": [
|
|
56
|
+
"Pulumi/Bicep IaC definitions",
|
|
57
|
+
"Environment configuration (dev/staging/prod)",
|
|
58
|
+
"Secrets management setup",
|
|
59
|
+
"Database provisioning",
|
|
60
|
+
"Container registry and orchestration",
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"phase": 3,
|
|
65
|
+
"name": "auth_and_security",
|
|
66
|
+
"title": "Authentication & Security Foundation",
|
|
67
|
+
"description": (
|
|
68
|
+
"Implement auth flows, RBAC, security middleware. This must exist "
|
|
69
|
+
"before any business logic so every endpoint is secure from the start."
|
|
70
|
+
),
|
|
71
|
+
"examples": [
|
|
72
|
+
"Auth provider integration (Azure AD B2C, Auth0, etc.)",
|
|
73
|
+
"JWT validation middleware",
|
|
74
|
+
"RBAC/permission system",
|
|
75
|
+
"CORS configuration",
|
|
76
|
+
"Rate limiting setup",
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"phase": 4,
|
|
81
|
+
"name": "data_layer",
|
|
82
|
+
"title": "Data Layer & Persistence",
|
|
83
|
+
"description": (
|
|
84
|
+
"Database models, migrations, repositories, data access patterns. "
|
|
85
|
+
"The AI agent needs the data layer complete before writing business logic."
|
|
86
|
+
),
|
|
87
|
+
"examples": [
|
|
88
|
+
"ORM models / database schemas",
|
|
89
|
+
"Migration scripts",
|
|
90
|
+
"Repository pattern implementations",
|
|
91
|
+
"Seed data for development",
|
|
92
|
+
"Database connection management",
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"phase": 5,
|
|
97
|
+
"name": "core_services",
|
|
98
|
+
"title": "Core Business Services",
|
|
99
|
+
"description": (
|
|
100
|
+
"Business logic services that operate on the data layer. "
|
|
101
|
+
"Pure logic, no HTTP concerns — these are reusable across "
|
|
102
|
+
"APIs, workers, and MCP tools."
|
|
103
|
+
),
|
|
104
|
+
"examples": [
|
|
105
|
+
"Domain service classes",
|
|
106
|
+
"Business rule validation",
|
|
107
|
+
"Workflow/state machine implementations",
|
|
108
|
+
"Integration adapters for external services",
|
|
109
|
+
"Event handlers",
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"phase": 6,
|
|
114
|
+
"name": "api_layer",
|
|
115
|
+
"title": "API Endpoints & Controllers",
|
|
116
|
+
"description": (
|
|
117
|
+
"REST/GraphQL endpoints that expose core services. "
|
|
118
|
+
"OpenAPI-documented, versioned, MCP-ready."
|
|
119
|
+
),
|
|
120
|
+
"examples": [
|
|
121
|
+
"Route definitions with OpenAPI decorators",
|
|
122
|
+
"Request/response validation",
|
|
123
|
+
"Error handling middleware",
|
|
124
|
+
"API versioning",
|
|
125
|
+
"Health check endpoints",
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"phase": 7,
|
|
130
|
+
"name": "frontend",
|
|
131
|
+
"title": "Frontend Implementation",
|
|
132
|
+
"description": (
|
|
133
|
+
"UI components, pages, state management. Built against "
|
|
134
|
+
"the typed API contracts from Phase 1."
|
|
135
|
+
),
|
|
136
|
+
"examples": [
|
|
137
|
+
"Component library / design system",
|
|
138
|
+
"Page layouts and routing",
|
|
139
|
+
"State management (React Query, Zustand, etc.)",
|
|
140
|
+
"Form handling with validation",
|
|
141
|
+
"Error boundaries and loading states",
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"phase": 8,
|
|
146
|
+
"name": "observability",
|
|
147
|
+
"title": "Observability & Monitoring",
|
|
148
|
+
"description": (
|
|
149
|
+
"Instrument everything — APM, metrics, logs, dashboards, alerts. "
|
|
150
|
+
"Including AI-specific observability if the project uses AI."
|
|
151
|
+
),
|
|
152
|
+
"examples": [
|
|
153
|
+
"APM instrumentation (App Insights, etc.)",
|
|
154
|
+
"Custom metrics (Prometheus)",
|
|
155
|
+
"Structured logging (Loki)",
|
|
156
|
+
"Grafana dashboard definitions",
|
|
157
|
+
"Alert rules and notification channels",
|
|
158
|
+
"AI cost/safety/session tracking (if applicable)",
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"phase": 9,
|
|
163
|
+
"name": "testing",
|
|
164
|
+
"title": "Testing & Quality Assurance",
|
|
165
|
+
"description": (
|
|
166
|
+
"Unit tests, integration tests, E2E tests. Written after the "
|
|
167
|
+
"implementation to validate against acceptance criteria."
|
|
168
|
+
),
|
|
169
|
+
"examples": [
|
|
170
|
+
"Unit tests for services and utilities",
|
|
171
|
+
"Integration tests for API endpoints",
|
|
172
|
+
"E2E tests for critical user flows",
|
|
173
|
+
"Load testing configuration",
|
|
174
|
+
"Security testing (OWASP baseline)",
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"phase": 10,
|
|
179
|
+
"name": "cicd_and_deploy",
|
|
180
|
+
"title": "CI/CD & Deployment",
|
|
181
|
+
"description": (
|
|
182
|
+
"Pipeline definitions, deployment scripts, smoke tests. "
|
|
183
|
+
"Everything needed to go from merge to production."
|
|
184
|
+
),
|
|
185
|
+
"examples": [
|
|
186
|
+
"GitHub Actions / Azure Pipelines definitions",
|
|
187
|
+
"Docker build and push workflows",
|
|
188
|
+
"Environment promotion workflows",
|
|
189
|
+
"Smoke test suites",
|
|
190
|
+
"Rollback procedures",
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def classify_requirement(content: str) -> RequirementType:
|
|
197
|
+
"""Classify what type of requirement document this is.
|
|
198
|
+
|
|
199
|
+
Uses simple heuristics — a more sophisticated version would use
|
|
200
|
+
an LLM, but this gives a reasonable first pass.
|
|
201
|
+
"""
|
|
202
|
+
content_lower = content.lower()
|
|
203
|
+
|
|
204
|
+
# PRD indicators
|
|
205
|
+
prd_signals = ["product requirements", "prd", "problem statement", "success metrics",
|
|
206
|
+
"user personas", "market analysis", "competitive analysis"]
|
|
207
|
+
if sum(1 for s in prd_signals if s in content_lower) >= 2:
|
|
208
|
+
return RequirementType.PRD
|
|
209
|
+
|
|
210
|
+
# Epic indicators
|
|
211
|
+
epic_signals = ["epic", "initiative", "objective", "key results", "okr"]
|
|
212
|
+
if sum(1 for s in epic_signals if s in content_lower) >= 2:
|
|
213
|
+
return RequirementType.EPIC
|
|
214
|
+
|
|
215
|
+
# User story indicators
|
|
216
|
+
story_signals = ["as a", "i want", "so that", "acceptance criteria",
|
|
217
|
+
"given", "when", "then", "story points"]
|
|
218
|
+
if sum(1 for s in story_signals if s in content_lower) >= 2:
|
|
219
|
+
return RequirementType.USER_STORY
|
|
220
|
+
|
|
221
|
+
# Feature request
|
|
222
|
+
feature_signals = ["feature", "request", "would be nice", "enhancement"]
|
|
223
|
+
if sum(1 for s in feature_signals if s in content_lower) >= 2:
|
|
224
|
+
return RequirementType.FEATURE_REQUEST
|
|
225
|
+
|
|
226
|
+
# Bug fix
|
|
227
|
+
bug_signals = ["bug", "fix", "broken", "error", "crash", "regression"]
|
|
228
|
+
if sum(1 for s in bug_signals if s in content_lower) >= 2:
|
|
229
|
+
return RequirementType.BUG_FIX
|
|
230
|
+
|
|
231
|
+
return RequirementType.CONVERSATION
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def build_intake_prompt(content: str, requirement_type: RequirementType) -> str:
|
|
235
|
+
"""Build the prompt for the LLM to analyze the requirement.
|
|
236
|
+
|
|
237
|
+
This prompt is designed to produce a structured ForgeBrief
|
|
238
|
+
from any kind of requirement input.
|
|
239
|
+
"""
|
|
240
|
+
return f"""You are Forge, an AI development workflow engine. Analyze the following requirement and produce a structured brief.
|
|
241
|
+
|
|
242
|
+
## Requirement Type Detected: {requirement_type.value}
|
|
243
|
+
|
|
244
|
+
## Input Requirement:
|
|
245
|
+
{content}
|
|
246
|
+
|
|
247
|
+
## Your Task:
|
|
248
|
+
Analyze this requirement and produce a JSON object with exactly this structure:
|
|
249
|
+
|
|
250
|
+
{{
|
|
251
|
+
"source_type": "{requirement_type.value}",
|
|
252
|
+
"completeness_score": <float 0-1, how complete/specific this requirement is>,
|
|
253
|
+
"objective": "<one paragraph: what this project/feature achieves>",
|
|
254
|
+
"users": ["<target user persona 1>", "<target user persona 2>"],
|
|
255
|
+
"features": [
|
|
256
|
+
{{
|
|
257
|
+
"name": "<feature name>",
|
|
258
|
+
"description": "<what it does>",
|
|
259
|
+
"mvp": <true/false>,
|
|
260
|
+
"priority": "<high/medium/low>"
|
|
261
|
+
}}
|
|
262
|
+
],
|
|
263
|
+
"mvp_defined": <true if the input explicitly defines MVP scope, false if you inferred it>,
|
|
264
|
+
"mvp_features": ["<feature names that are MVP>"],
|
|
265
|
+
"external_dependencies": ["<external system or service this depends on>"],
|
|
266
|
+
"integrations": ["<third party integrations needed>"],
|
|
267
|
+
"regulatory_requirements": ["<hipaa/ferpa/gdpr/soc2/pci-dss/none>"],
|
|
268
|
+
"gaps": [
|
|
269
|
+
{{
|
|
270
|
+
"severity": "<critical/warning/info>",
|
|
271
|
+
"area": "<what area is missing info>",
|
|
272
|
+
"description": "<what's missing>",
|
|
273
|
+
"suggestion": "<what we'd recommend>"
|
|
274
|
+
}}
|
|
275
|
+
],
|
|
276
|
+
"assumptions": ["<assumptions made to fill non-critical gaps>"],
|
|
277
|
+
"implementation_order": [
|
|
278
|
+
{{
|
|
279
|
+
"phase": <1-10>,
|
|
280
|
+
"name": "<phase_name>",
|
|
281
|
+
"title": "<Phase Title>",
|
|
282
|
+
"tasks": ["<specific task for this project in this phase>"]
|
|
283
|
+
}}
|
|
284
|
+
]
|
|
285
|
+
}}
|
|
286
|
+
|
|
287
|
+
## Rules:
|
|
288
|
+
1. Be thorough but honest — if information is missing, flag it as a gap
|
|
289
|
+
2. Don't invent features that aren't in the requirement
|
|
290
|
+
3. If MVP scope isn't explicitly defined, make a reasonable suggestion and set mvp_defined to false
|
|
291
|
+
4. The implementation_order MUST follow the AI-optimized sequence:
|
|
292
|
+
1. types_and_contracts
|
|
293
|
+
2. infrastructure
|
|
294
|
+
3. auth_and_security
|
|
295
|
+
4. data_layer
|
|
296
|
+
5. core_services
|
|
297
|
+
6. api_layer
|
|
298
|
+
7. frontend
|
|
299
|
+
8. observability
|
|
300
|
+
9. testing
|
|
301
|
+
10. cicd_and_deploy
|
|
302
|
+
5. Only include phases that are relevant to this requirement
|
|
303
|
+
6. Be specific in tasks — "Create User model with email, role, tenant fields" not "Create models"
|
|
304
|
+
|
|
305
|
+
Respond with ONLY the JSON object. No markdown, no explanation."""
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def create_empty_brief() -> ForgeBrief:
|
|
309
|
+
"""Create an empty brief for interactive filling."""
|
|
310
|
+
return ForgeBrief(
|
|
311
|
+
source_type=RequirementType.CONVERSATION,
|
|
312
|
+
implementation_order=AI_IMPLEMENTATION_PHASES,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def save_brief(brief: ForgeBrief, project_path: Path) -> Path:
|
|
317
|
+
"""Save a ForgeBrief to the project's .forge/ directory."""
|
|
318
|
+
forge_dir = project_path / ".forge"
|
|
319
|
+
forge_dir.mkdir(parents=True, exist_ok=True)
|
|
320
|
+
|
|
321
|
+
brief_path = forge_dir / "brief.yaml"
|
|
322
|
+
with open(brief_path, "w") as f:
|
|
323
|
+
yaml.dump(brief.model_dump(mode="json"), f, default_flow_style=False, sort_keys=False)
|
|
324
|
+
|
|
325
|
+
return brief_path
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def load_brief(project_path: Path) -> ForgeBrief | None:
|
|
329
|
+
"""Load an existing ForgeBrief from the project."""
|
|
330
|
+
brief_path = project_path / ".forge" / "brief.yaml"
|
|
331
|
+
if not brief_path.exists():
|
|
332
|
+
return None
|
|
333
|
+
with open(brief_path) as f:
|
|
334
|
+
data = yaml.safe_load(f) or {}
|
|
335
|
+
return ForgeBrief(**data)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def get_implementation_phases() -> list[dict[str, Any]]:
|
|
339
|
+
"""Return the standard AI implementation phases for reference."""
|
|
340
|
+
return AI_IMPLEMENTATION_PHASES
|