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
forge_core/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Forge audit agents."""
|
forge_core/auditor.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""Forge Audit Engine — validates code against governance rules.
|
|
2
|
+
|
|
3
|
+
The auditor doesn't just find problems — it explains them, references
|
|
4
|
+
the specific standard violated, and provides the fix. It generates
|
|
5
|
+
prompts that the AI editor can use to self-correct.
|
|
6
|
+
|
|
7
|
+
Audit categories:
|
|
8
|
+
- Pattern compliance: Does code follow established patterns?
|
|
9
|
+
- Security: Auth, input validation, secrets, SQL injection
|
|
10
|
+
- API design: OpenAPI, versioning, error format, MCP-readiness
|
|
11
|
+
- Observability: Instrumentation, logging, metrics
|
|
12
|
+
- Type safety: Types, validation, no 'any'
|
|
13
|
+
- Duplication: Similar code that should be consolidated
|
|
14
|
+
- Regulatory: HIPAA/FERPA/GDPR-specific checks
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
import yaml
|
|
25
|
+
|
|
26
|
+
from forge_core.models import ProjectContext, Regulatory
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class AuditFinding:
|
|
31
|
+
"""A single audit finding."""
|
|
32
|
+
severity: str # critical, warning, info, suggestion
|
|
33
|
+
category: str # security, api, observability, type-safety, duplication, pattern, regulatory
|
|
34
|
+
standard: str # Which standard this violates
|
|
35
|
+
rule: str # Specific rule within the standard
|
|
36
|
+
file: str # File where the issue was found (or "project-wide")
|
|
37
|
+
line: int | None # Line number if applicable
|
|
38
|
+
description: str # What's wrong
|
|
39
|
+
fix: str # How to fix it
|
|
40
|
+
code_example: str = "" # Example of correct code
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class AuditReport:
|
|
45
|
+
"""Complete audit report for a project."""
|
|
46
|
+
project: str
|
|
47
|
+
timestamp: str
|
|
48
|
+
forge_version: str
|
|
49
|
+
total_files_scanned: int = 0
|
|
50
|
+
findings: list[AuditFinding] = field(default_factory=list)
|
|
51
|
+
summary: dict[str, int] = field(default_factory=dict)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def critical_count(self) -> int:
|
|
55
|
+
return sum(1 for f in self.findings if f.severity == "critical")
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def warning_count(self) -> int:
|
|
59
|
+
return sum(1 for f in self.findings if f.severity == "warning")
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def passed(self) -> bool:
|
|
63
|
+
return self.critical_count == 0
|
|
64
|
+
|
|
65
|
+
def to_markdown(self) -> str:
|
|
66
|
+
"""Generate a markdown audit report."""
|
|
67
|
+
lines = [
|
|
68
|
+
f"# Forge Audit Report — {self.project}",
|
|
69
|
+
f"**Date:** {self.timestamp}",
|
|
70
|
+
f"**Files scanned:** {self.total_files_scanned}",
|
|
71
|
+
f"**Findings:** {len(self.findings)} "
|
|
72
|
+
f"({self.critical_count} critical, {self.warning_count} warnings)",
|
|
73
|
+
f"**Status:** {'PASSED' if self.passed else 'FAILED'}",
|
|
74
|
+
"",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
if not self.findings:
|
|
78
|
+
lines.append("No issues found. Code is compliant with all standards.")
|
|
79
|
+
return "\n".join(lines)
|
|
80
|
+
|
|
81
|
+
# Group by category
|
|
82
|
+
by_category: dict[str, list[AuditFinding]] = {}
|
|
83
|
+
for f in self.findings:
|
|
84
|
+
by_category.setdefault(f.category, []).append(f)
|
|
85
|
+
|
|
86
|
+
for category, findings in sorted(by_category.items()):
|
|
87
|
+
lines.append(f"## {category.replace('-', ' ').title()}")
|
|
88
|
+
lines.append("")
|
|
89
|
+
for f in findings:
|
|
90
|
+
icon = {"critical": "🔴", "warning": "🟡", "info": "🔵", "suggestion": "💡"}
|
|
91
|
+
lines.append(
|
|
92
|
+
f"{icon.get(f.severity, '•')} **[{f.severity.upper()}]** {f.description}"
|
|
93
|
+
)
|
|
94
|
+
if f.file != "project-wide":
|
|
95
|
+
loc = f"{f.file}" + (f":{f.line}" if f.line else "")
|
|
96
|
+
lines.append(f" *File:* `{loc}`")
|
|
97
|
+
lines.append(f" *Standard:* {f.standard} — {f.rule}")
|
|
98
|
+
lines.append(f" *Fix:* {f.fix}")
|
|
99
|
+
if f.code_example:
|
|
100
|
+
lines.append(f" ```\n {f.code_example}\n ```")
|
|
101
|
+
lines.append("")
|
|
102
|
+
|
|
103
|
+
return "\n".join(lines)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def build_audit_prompt(
|
|
107
|
+
project_path: Path,
|
|
108
|
+
context: ProjectContext,
|
|
109
|
+
standards: list[dict],
|
|
110
|
+
target_files: list[str] | None = None,
|
|
111
|
+
) -> str:
|
|
112
|
+
"""Build the prompt for LLM-powered code auditing.
|
|
113
|
+
|
|
114
|
+
This prompt is designed to be sent to Claude or any LLM to perform
|
|
115
|
+
a deep audit of the code against Forge standards.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
project_path: Root of the project
|
|
119
|
+
context: Project context with stack and config
|
|
120
|
+
standards: All applicable standards
|
|
121
|
+
target_files: Specific files to audit (None = full project)
|
|
122
|
+
"""
|
|
123
|
+
# Build standards section
|
|
124
|
+
standards_text = ""
|
|
125
|
+
for std in standards:
|
|
126
|
+
standards_text += f"\n### {std.get('name', 'unnamed')} ({std.get('area', 'general')})\n"
|
|
127
|
+
standards_text += f"{std.get('description', '')}\n"
|
|
128
|
+
rules = std.get("rules", [])
|
|
129
|
+
for i, rule in enumerate(rules, 1):
|
|
130
|
+
standards_text += f" {i}. {rule}\n"
|
|
131
|
+
|
|
132
|
+
# Build regulatory section
|
|
133
|
+
regulatory_text = ""
|
|
134
|
+
for reg in context.regulatory:
|
|
135
|
+
if reg == Regulatory.HIPAA:
|
|
136
|
+
regulatory_text += """
|
|
137
|
+
### HIPAA Requirements
|
|
138
|
+
- All PHI encrypted at rest (AES-256) and in transit (TLS 1.2+)
|
|
139
|
+
- Audit logging of ALL PHI access
|
|
140
|
+
- Minimum necessary access via RBAC
|
|
141
|
+
- Session timeouts <= 15 minutes
|
|
142
|
+
- Never log PHI in application logs
|
|
143
|
+
"""
|
|
144
|
+
elif reg == Regulatory.FERPA:
|
|
145
|
+
regulatory_text += """
|
|
146
|
+
### FERPA Requirements
|
|
147
|
+
- Student records must have restricted access controls
|
|
148
|
+
- Consent tracking for data sharing
|
|
149
|
+
- Audit trail for all record access
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
# Build file targets
|
|
153
|
+
if target_files:
|
|
154
|
+
files_instruction = f"Audit ONLY these files:\n" + "\n".join(f"- {f}" for f in target_files)
|
|
155
|
+
else:
|
|
156
|
+
files_instruction = "Audit ALL code files in the project."
|
|
157
|
+
|
|
158
|
+
return f"""You are Forge, an AI development governance engine. Perform a thorough code audit.
|
|
159
|
+
|
|
160
|
+
## Project Context
|
|
161
|
+
- Name: {context.name}
|
|
162
|
+
- Type: {context.type.value}
|
|
163
|
+
- Backend: {context.backend.value}
|
|
164
|
+
- Frontend: {context.frontend.value}
|
|
165
|
+
- Cloud: {context.cloud.value}
|
|
166
|
+
- Auth: {context.auth.value}
|
|
167
|
+
- Database: {context.database.value}
|
|
168
|
+
- AI Enabled: {context.ai.enabled}
|
|
169
|
+
- Regulatory: {', '.join(r.value for r in context.regulatory) or 'None'}
|
|
170
|
+
|
|
171
|
+
## Standards to Enforce
|
|
172
|
+
{standards_text}
|
|
173
|
+
|
|
174
|
+
{regulatory_text}
|
|
175
|
+
|
|
176
|
+
## Scope
|
|
177
|
+
{files_instruction}
|
|
178
|
+
|
|
179
|
+
## Audit Instructions
|
|
180
|
+
|
|
181
|
+
For each file, check:
|
|
182
|
+
|
|
183
|
+
1. **Pattern Compliance**: Does the code follow established patterns? Is there code that should be reusing existing utilities but isn't?
|
|
184
|
+
|
|
185
|
+
2. **Security**:
|
|
186
|
+
- Input validation at API boundaries
|
|
187
|
+
- No hardcoded secrets
|
|
188
|
+
- Parameterized SQL queries
|
|
189
|
+
- Auth/RBAC on all endpoints
|
|
190
|
+
- Security headers
|
|
191
|
+
|
|
192
|
+
3. **API Design**:
|
|
193
|
+
- OpenAPI documentation present
|
|
194
|
+
- Correct versioning ({context.api.versioning})
|
|
195
|
+
- RFC 7807 error responses
|
|
196
|
+
- Rate limiting configured
|
|
197
|
+
- MCP-ready design (clear inputs/outputs)
|
|
198
|
+
|
|
199
|
+
4. **Observability**:
|
|
200
|
+
- APM instrumentation ({context.observability.apm})
|
|
201
|
+
- Structured JSON logging with correlation IDs
|
|
202
|
+
- Custom metrics for business events
|
|
203
|
+
- No print() or console.log() in production code
|
|
204
|
+
|
|
205
|
+
5. **Type Safety**:
|
|
206
|
+
- All functions have type annotations
|
|
207
|
+
- No 'any' types (TypeScript) or untyped parameters (Python)
|
|
208
|
+
- Pydantic/Zod validation at boundaries
|
|
209
|
+
|
|
210
|
+
6. **Duplication**:
|
|
211
|
+
- Similar logic that should be consolidated
|
|
212
|
+
- Copy-pasted code blocks
|
|
213
|
+
- Services that overlap in responsibility
|
|
214
|
+
|
|
215
|
+
7. **Regulatory** (if applicable):
|
|
216
|
+
- PHI/PII handling (HIPAA)
|
|
217
|
+
- Access controls (FERPA/GDPR)
|
|
218
|
+
- Audit logging completeness
|
|
219
|
+
|
|
220
|
+
## Output Format
|
|
221
|
+
|
|
222
|
+
Respond with a JSON array of findings:
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
[
|
|
226
|
+
{{
|
|
227
|
+
"severity": "critical|warning|info|suggestion",
|
|
228
|
+
"category": "security|api|observability|type-safety|duplication|pattern|regulatory",
|
|
229
|
+
"standard": "name of the standard violated",
|
|
230
|
+
"rule": "specific rule text",
|
|
231
|
+
"file": "path/to/file.py",
|
|
232
|
+
"line": 42,
|
|
233
|
+
"description": "What is wrong",
|
|
234
|
+
"fix": "How to fix it — be specific and actionable",
|
|
235
|
+
"code_example": "Optional: example of correct code"
|
|
236
|
+
}}
|
|
237
|
+
]
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Be thorough but practical. Focus on findings that actually matter for production quality.
|
|
241
|
+
Do NOT flag stylistic preferences — only standard violations and real issues."""
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def build_file_audit_prompt(
|
|
245
|
+
file_content: str,
|
|
246
|
+
file_path: str,
|
|
247
|
+
context: ProjectContext,
|
|
248
|
+
standards: list[dict],
|
|
249
|
+
) -> str:
|
|
250
|
+
"""Build an audit prompt for a single file.
|
|
251
|
+
|
|
252
|
+
Used when the editor wants to audit code it just generated
|
|
253
|
+
before presenting it to the user.
|
|
254
|
+
"""
|
|
255
|
+
# Compact standards for single-file context
|
|
256
|
+
rules_summary = []
|
|
257
|
+
for std in standards:
|
|
258
|
+
name = std.get("name", "unnamed")
|
|
259
|
+
for rule in std.get("rules", []):
|
|
260
|
+
rules_summary.append(f"[{name}] {rule}")
|
|
261
|
+
|
|
262
|
+
rules_text = "\n".join(f"- {r}" for r in rules_summary)
|
|
263
|
+
|
|
264
|
+
return f"""Audit this code against the project standards. Be specific and actionable.
|
|
265
|
+
|
|
266
|
+
## Project: {context.name}
|
|
267
|
+
Stack: {context.backend.value} + {context.frontend.value} | Auth: {context.auth.value}
|
|
268
|
+
Regulatory: {', '.join(r.value for r in context.regulatory) or 'None'}
|
|
269
|
+
|
|
270
|
+
## Standards
|
|
271
|
+
{rules_text}
|
|
272
|
+
|
|
273
|
+
## File: {file_path}
|
|
274
|
+
```
|
|
275
|
+
{file_content}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Response Format
|
|
279
|
+
Return ONLY a JSON array of findings (empty array if compliant):
|
|
280
|
+
[{{"severity":"...", "category":"...", "standard":"...", "rule":"...", "line":N, "description":"...", "fix":"...", "code_example":"..."}}]"""
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def save_audit_report(report: AuditReport, project_path: Path) -> Path:
|
|
284
|
+
"""Save an audit report to the project's .forge/audit/ directory."""
|
|
285
|
+
audit_dir = project_path / ".forge" / "audit"
|
|
286
|
+
audit_dir.mkdir(parents=True, exist_ok=True)
|
|
287
|
+
|
|
288
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
|
289
|
+
|
|
290
|
+
# Save markdown report
|
|
291
|
+
md_path = audit_dir / f"audit_{timestamp}.md"
|
|
292
|
+
md_path.write_text(report.to_markdown())
|
|
293
|
+
|
|
294
|
+
# Save structured data
|
|
295
|
+
data_path = audit_dir / f"audit_{timestamp}.yaml"
|
|
296
|
+
data = {
|
|
297
|
+
"project": report.project,
|
|
298
|
+
"timestamp": report.timestamp,
|
|
299
|
+
"forge_version": report.forge_version,
|
|
300
|
+
"total_files_scanned": report.total_files_scanned,
|
|
301
|
+
"passed": report.passed,
|
|
302
|
+
"summary": {
|
|
303
|
+
"critical": report.critical_count,
|
|
304
|
+
"warning": report.warning_count,
|
|
305
|
+
"total": len(report.findings),
|
|
306
|
+
},
|
|
307
|
+
"findings": [
|
|
308
|
+
{
|
|
309
|
+
"severity": f.severity,
|
|
310
|
+
"category": f.category,
|
|
311
|
+
"standard": f.standard,
|
|
312
|
+
"rule": f.rule,
|
|
313
|
+
"file": f.file,
|
|
314
|
+
"line": f.line,
|
|
315
|
+
"description": f.description,
|
|
316
|
+
"fix": f.fix,
|
|
317
|
+
}
|
|
318
|
+
for f in report.findings
|
|
319
|
+
],
|
|
320
|
+
}
|
|
321
|
+
with open(data_path, "w") as fp:
|
|
322
|
+
yaml.dump(data, fp, default_flow_style=False, sort_keys=False)
|
|
323
|
+
|
|
324
|
+
# Update latest symlink
|
|
325
|
+
latest_md = audit_dir / "latest.md"
|
|
326
|
+
if latest_md.exists() or latest_md.is_symlink():
|
|
327
|
+
latest_md.unlink()
|
|
328
|
+
latest_md.symlink_to(md_path.name)
|
|
329
|
+
|
|
330
|
+
return md_path
|