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 ADDED
@@ -0,0 +1,3 @@
1
+ """Forge — AI-Native Development Workflow Engine."""
2
+
3
+ __version__ = "0.1.0"
@@ -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