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,543 @@
|
|
|
1
|
+
"""CLAUDE.md Generator — bridges Forge governance to AI editors.
|
|
2
|
+
|
|
3
|
+
This is the core value proposition of Forge: it translates all governance
|
|
4
|
+
knowledge (standards, patterns, anti-patterns, project context, journal
|
|
5
|
+
learnings, MCP recommendations) into a single CLAUDE.md file that any
|
|
6
|
+
AI coding editor can read and follow.
|
|
7
|
+
|
|
8
|
+
The generated CLAUDE.md acts as the "constitution" for the AI editor —
|
|
9
|
+
it tells the editor exactly how to behave in this specific project.
|
|
10
|
+
|
|
11
|
+
Supported outputs:
|
|
12
|
+
- CLAUDE.md for Claude Code
|
|
13
|
+
- .cursorrules for Cursor
|
|
14
|
+
- .github/copilot-instructions.md for GitHub Copilot
|
|
15
|
+
- Generic .ai-rules.md for any editor
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
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
|
+
from forge_core.registry import load_mcps, load_standards
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def generate_editor_instructions(
|
|
31
|
+
project_path: Path,
|
|
32
|
+
context: ProjectContext,
|
|
33
|
+
format: str = "claude", # claude, cursor, copilot, generic
|
|
34
|
+
) -> str:
|
|
35
|
+
"""Generate complete editor instructions from Forge governance.
|
|
36
|
+
|
|
37
|
+
This is the main function — it reads ALL Forge knowledge and
|
|
38
|
+
produces a single document that tells the AI editor everything
|
|
39
|
+
it needs to know to generate correct code in this project.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
project_path: Root of the project
|
|
43
|
+
context: Resolved project context
|
|
44
|
+
format: Output format (claude, cursor, copilot, generic)
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Complete instruction document as string
|
|
48
|
+
"""
|
|
49
|
+
sections: list[str] = []
|
|
50
|
+
|
|
51
|
+
# ── Header ──
|
|
52
|
+
sections.append(_generate_header(context, format))
|
|
53
|
+
|
|
54
|
+
# ── Project Identity ──
|
|
55
|
+
sections.append(_generate_project_identity(context))
|
|
56
|
+
|
|
57
|
+
# ── Stack & Architecture Rules ──
|
|
58
|
+
sections.append(_generate_stack_rules(context))
|
|
59
|
+
|
|
60
|
+
# ── Standards (the hard rules) ──
|
|
61
|
+
standards = load_standards(include_user=True)
|
|
62
|
+
sections.append(_generate_standards_section(standards, context))
|
|
63
|
+
|
|
64
|
+
# ── Patterns & Anti-Patterns ──
|
|
65
|
+
sections.append(_generate_patterns_section(project_path))
|
|
66
|
+
|
|
67
|
+
# ── API Design Rules ──
|
|
68
|
+
sections.append(_generate_api_rules(context))
|
|
69
|
+
|
|
70
|
+
# ── Security & Compliance ──
|
|
71
|
+
sections.append(_generate_security_rules(context))
|
|
72
|
+
|
|
73
|
+
# ── Observability Rules ──
|
|
74
|
+
sections.append(_generate_observability_rules(context))
|
|
75
|
+
|
|
76
|
+
# ── AI-Specific Rules (if project uses AI) ──
|
|
77
|
+
if context.ai.enabled:
|
|
78
|
+
sections.append(_generate_ai_rules(context))
|
|
79
|
+
|
|
80
|
+
# ── Code Quality Rules ──
|
|
81
|
+
sections.append(_generate_code_quality_rules(context))
|
|
82
|
+
|
|
83
|
+
# ── Project Journal (learnings & nuances) ──
|
|
84
|
+
journal_content = _load_journal(project_path)
|
|
85
|
+
if journal_content:
|
|
86
|
+
sections.append(_generate_journal_section(journal_content))
|
|
87
|
+
|
|
88
|
+
# ── MCP Recommendations ──
|
|
89
|
+
mcps = load_mcps()
|
|
90
|
+
if mcps:
|
|
91
|
+
sections.append(_generate_mcp_section(mcps))
|
|
92
|
+
|
|
93
|
+
# ── Self-Audit Instructions ──
|
|
94
|
+
sections.append(_generate_self_audit_instructions(context))
|
|
95
|
+
|
|
96
|
+
# ── Implementation Ordering ──
|
|
97
|
+
sections.append(_generate_implementation_order())
|
|
98
|
+
|
|
99
|
+
return "\n\n".join(sections)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def write_editor_file(
|
|
103
|
+
project_path: Path,
|
|
104
|
+
context: ProjectContext,
|
|
105
|
+
format: str = "claude",
|
|
106
|
+
) -> Path:
|
|
107
|
+
"""Generate and write the editor instructions file.
|
|
108
|
+
|
|
109
|
+
Returns the path to the written file.
|
|
110
|
+
"""
|
|
111
|
+
content = generate_editor_instructions(project_path, context, format)
|
|
112
|
+
|
|
113
|
+
filename_map = {
|
|
114
|
+
"claude": "CLAUDE.md",
|
|
115
|
+
"cursor": ".cursorrules",
|
|
116
|
+
"copilot": ".github/copilot-instructions.md",
|
|
117
|
+
"generic": ".ai-rules.md",
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
filename = filename_map.get(format, "CLAUDE.md")
|
|
121
|
+
file_path = project_path / filename
|
|
122
|
+
|
|
123
|
+
# Ensure parent directory exists (for copilot)
|
|
124
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
|
|
126
|
+
file_path.write_text(content)
|
|
127
|
+
return file_path
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def sync_editor_file(project_path: Path, context: ProjectContext) -> list[Path]:
|
|
131
|
+
"""Regenerate all editor instruction files that exist in the project.
|
|
132
|
+
|
|
133
|
+
Only regenerates files that already exist — doesn't create new ones.
|
|
134
|
+
Returns list of updated files.
|
|
135
|
+
"""
|
|
136
|
+
updated = []
|
|
137
|
+
checks = [
|
|
138
|
+
("claude", "CLAUDE.md"),
|
|
139
|
+
("cursor", ".cursorrules"),
|
|
140
|
+
("copilot", ".github/copilot-instructions.md"),
|
|
141
|
+
("generic", ".ai-rules.md"),
|
|
142
|
+
]
|
|
143
|
+
for fmt, filename in checks:
|
|
144
|
+
path = project_path / filename
|
|
145
|
+
if path.exists():
|
|
146
|
+
write_editor_file(project_path, context, fmt)
|
|
147
|
+
updated.append(path)
|
|
148
|
+
|
|
149
|
+
return updated
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ── Section Generators ─────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
def _generate_header(context: ProjectContext, format: str) -> str:
|
|
155
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
|
|
156
|
+
return f"""# {context.name} — AI Editor Instructions
|
|
157
|
+
# Generated by Forge v{context.forge_version} on {timestamp}
|
|
158
|
+
# DO NOT EDIT MANUALLY — regenerate with `forge sync`
|
|
159
|
+
# Source of truth: .forge/context.yaml + ~/.forge/ standards"""
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _generate_project_identity(context: ProjectContext) -> str:
|
|
163
|
+
lines = [
|
|
164
|
+
"## Project Overview",
|
|
165
|
+
"",
|
|
166
|
+
f"**Name:** {context.name}",
|
|
167
|
+
f"**Type:** {context.type.value}",
|
|
168
|
+
]
|
|
169
|
+
if context.description:
|
|
170
|
+
lines.append(f"**Purpose:** {context.description}")
|
|
171
|
+
if context.regulatory:
|
|
172
|
+
regs = ", ".join(r.value.upper() for r in context.regulatory)
|
|
173
|
+
lines.append(f"**Regulatory:** {regs} — all code must comply with these requirements")
|
|
174
|
+
return "\n".join(lines)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _generate_stack_rules(context: ProjectContext) -> str:
|
|
178
|
+
return f"""## Stack & Architecture
|
|
179
|
+
|
|
180
|
+
This project uses the following stack. All code must be consistent with these choices.
|
|
181
|
+
|
|
182
|
+
- **Cloud:** {context.cloud.value}
|
|
183
|
+
- **Backend:** {context.backend.value}
|
|
184
|
+
- **Frontend:** {context.frontend.value}
|
|
185
|
+
- **Database:** {context.database.value}
|
|
186
|
+
- **Auth:** {context.auth.value}
|
|
187
|
+
- **API Style:** {context.api.style} with {context.api.spec}
|
|
188
|
+
- **IaC:** {context.cicd.iac}
|
|
189
|
+
- **CI/CD:** {context.cicd.provider}
|
|
190
|
+
|
|
191
|
+
Do NOT introduce alternative frameworks, libraries, or patterns that conflict with this stack
|
|
192
|
+
unless explicitly approved and documented in .forge/overrides/."""
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _generate_standards_section(standards: list[dict], context: ProjectContext) -> str:
|
|
196
|
+
if not standards:
|
|
197
|
+
return "## Standards\n\nNo standards configured yet."
|
|
198
|
+
|
|
199
|
+
lines = [
|
|
200
|
+
"## Standards — Hard Rules",
|
|
201
|
+
"",
|
|
202
|
+
"These are non-negotiable. All code must comply.",
|
|
203
|
+
"",
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
for std in standards:
|
|
207
|
+
name = std.get("name", "unnamed")
|
|
208
|
+
area = std.get("area", "general")
|
|
209
|
+
desc = std.get("description", "")
|
|
210
|
+
enforcement = std.get("enforcement", "required")
|
|
211
|
+
rules = std.get("rules", [])
|
|
212
|
+
|
|
213
|
+
lines.append(f"### {name} ({area}) [{enforcement}]")
|
|
214
|
+
lines.append(f"{desc}")
|
|
215
|
+
lines.append("")
|
|
216
|
+
|
|
217
|
+
if rules:
|
|
218
|
+
for rule in rules:
|
|
219
|
+
lines.append(f"- {rule}")
|
|
220
|
+
lines.append("")
|
|
221
|
+
|
|
222
|
+
# Include examples if available
|
|
223
|
+
examples = std.get("examples", {})
|
|
224
|
+
if examples.get("good"):
|
|
225
|
+
lines.append("**Good:**")
|
|
226
|
+
for ex in examples["good"]:
|
|
227
|
+
lines.append(f"- `{ex}`")
|
|
228
|
+
lines.append("")
|
|
229
|
+
if examples.get("bad"):
|
|
230
|
+
lines.append("**Bad (never do this):**")
|
|
231
|
+
for ex in examples["bad"]:
|
|
232
|
+
lines.append(f"- `{ex}`")
|
|
233
|
+
lines.append("")
|
|
234
|
+
|
|
235
|
+
return "\n".join(lines)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _generate_patterns_section(project_path: Path) -> str:
|
|
239
|
+
"""Load patterns and anti-patterns from both global and project level."""
|
|
240
|
+
lines = [
|
|
241
|
+
"## Approved Patterns & Anti-Patterns",
|
|
242
|
+
"",
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
# Global patterns
|
|
246
|
+
from forge_core.registry import USER_DIR
|
|
247
|
+
patterns_dir = USER_DIR / "patterns"
|
|
248
|
+
anti_dir = USER_DIR / "anti-patterns"
|
|
249
|
+
|
|
250
|
+
# Project-level overrides
|
|
251
|
+
project_patterns_dir = project_path / ".forge" / "overrides" / "patterns"
|
|
252
|
+
project_anti_dir = project_path / ".forge" / "overrides" / "anti-patterns"
|
|
253
|
+
|
|
254
|
+
patterns = _load_yaml_dir(patterns_dir) + _load_yaml_dir(project_patterns_dir)
|
|
255
|
+
anti_patterns = _load_yaml_dir(anti_dir) + _load_yaml_dir(project_anti_dir)
|
|
256
|
+
|
|
257
|
+
if patterns:
|
|
258
|
+
lines.append("### Approved Patterns (use these)")
|
|
259
|
+
lines.append("")
|
|
260
|
+
for p in patterns:
|
|
261
|
+
lines.append(f"**{p.get('name', 'unnamed')}**: {p.get('description', '')}")
|
|
262
|
+
if p.get("example"):
|
|
263
|
+
lines.append(f"```\n{p['example']}\n```")
|
|
264
|
+
lines.append("")
|
|
265
|
+
|
|
266
|
+
if anti_patterns:
|
|
267
|
+
lines.append("### Anti-Patterns (NEVER do these)")
|
|
268
|
+
lines.append("")
|
|
269
|
+
for ap in anti_patterns:
|
|
270
|
+
lines.append(f"**{ap.get('name', 'unnamed')}**: {ap.get('description', '')}")
|
|
271
|
+
if ap.get("instead"):
|
|
272
|
+
lines.append(f"**Instead:** {ap['instead']}")
|
|
273
|
+
if ap.get("example_bad"):
|
|
274
|
+
lines.append(f"```\n# BAD — do not do this\n{ap['example_bad']}\n```")
|
|
275
|
+
if ap.get("example_good"):
|
|
276
|
+
lines.append(f"```\n# GOOD — do this instead\n{ap['example_good']}\n```")
|
|
277
|
+
lines.append("")
|
|
278
|
+
|
|
279
|
+
if not patterns and not anti_patterns:
|
|
280
|
+
lines.append("No patterns or anti-patterns defined yet.")
|
|
281
|
+
lines.append("Add them via `forge standards` or in ~/.forge/user/patterns/")
|
|
282
|
+
|
|
283
|
+
return "\n".join(lines)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _generate_api_rules(context: ProjectContext) -> str:
|
|
287
|
+
lines = [
|
|
288
|
+
"## API Design Rules",
|
|
289
|
+
"",
|
|
290
|
+
"When creating any API endpoint, controller, or route:",
|
|
291
|
+
"",
|
|
292
|
+
f"- Use {context.api.versioning} versioning (e.g., /v1/resource)",
|
|
293
|
+
f"- Document with {context.api.spec} decorators/annotations",
|
|
294
|
+
"- Return RFC 7807 Problem Details for all errors",
|
|
295
|
+
"- Include pagination (cursor-based preferred) for list endpoints",
|
|
296
|
+
"- Add rate limiting configuration",
|
|
297
|
+
"- Include health check endpoint (/health, /ready)",
|
|
298
|
+
"- Every endpoint must have request/response type validation",
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
if context.api.mcp_ready:
|
|
302
|
+
lines.extend([
|
|
303
|
+
"",
|
|
304
|
+
"### MCP-Ready Design",
|
|
305
|
+
"- Every endpoint must be usable as an MCP tool — clear inputs, clear outputs, no implicit state",
|
|
306
|
+
"- Response schemas must be self-describing (include field descriptions)",
|
|
307
|
+
"- Endpoints must be independently callable without session context",
|
|
308
|
+
])
|
|
309
|
+
|
|
310
|
+
return "\n".join(lines)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _generate_security_rules(context: ProjectContext) -> str:
|
|
314
|
+
lines = [
|
|
315
|
+
"## Security Rules",
|
|
316
|
+
"",
|
|
317
|
+
f"- Authentication: {context.auth.value} — implement before any business endpoint",
|
|
318
|
+
"- All user input must be validated at the API boundary with typed models",
|
|
319
|
+
"- Secrets must NEVER appear in code or config — use environment variables or vault",
|
|
320
|
+
"- CORS must be explicitly configured — never use wildcard (*) in production",
|
|
321
|
+
"- All SQL must use parameterized queries — no string concatenation",
|
|
322
|
+
"- Dependencies must be scanned for vulnerabilities in CI",
|
|
323
|
+
"- Security headers must be set (HSTS, X-Content-Type-Options, CSP)",
|
|
324
|
+
]
|
|
325
|
+
|
|
326
|
+
for reg in context.regulatory:
|
|
327
|
+
if reg == Regulatory.HIPAA:
|
|
328
|
+
lines.extend([
|
|
329
|
+
"",
|
|
330
|
+
"### HIPAA Compliance (REQUIRED)",
|
|
331
|
+
"- All PHI must be encrypted at rest (AES-256) and in transit (TLS 1.2+)",
|
|
332
|
+
"- Audit logging of ALL PHI access with user identity and timestamp",
|
|
333
|
+
"- Minimum necessary access via RBAC — no broad data access",
|
|
334
|
+
"- Session timeouts must not exceed 15 minutes of inactivity",
|
|
335
|
+
"- BAA must be in place for all cloud services handling PHI",
|
|
336
|
+
"- Never log PHI in application logs — use tokenized references",
|
|
337
|
+
])
|
|
338
|
+
elif reg == Regulatory.FERPA:
|
|
339
|
+
lines.extend([
|
|
340
|
+
"",
|
|
341
|
+
"### FERPA Compliance (REQUIRED)",
|
|
342
|
+
"- Student education records must have restricted access controls",
|
|
343
|
+
"- Consent tracking for all data sharing",
|
|
344
|
+
"- Audit trail for all record access",
|
|
345
|
+
])
|
|
346
|
+
elif reg == Regulatory.GDPR:
|
|
347
|
+
lines.extend([
|
|
348
|
+
"",
|
|
349
|
+
"### GDPR Compliance (REQUIRED)",
|
|
350
|
+
"- Explicit consent must be recorded for data processing",
|
|
351
|
+
"- Right to deletion must be implementable for all user data",
|
|
352
|
+
"- Data portability exports must be available",
|
|
353
|
+
])
|
|
354
|
+
elif reg == Regulatory.SOC2:
|
|
355
|
+
lines.extend([
|
|
356
|
+
"",
|
|
357
|
+
"### SOC2 Compliance (REQUIRED)",
|
|
358
|
+
"- Access controls and audit logging on all systems",
|
|
359
|
+
"- Change management procedures must be documented",
|
|
360
|
+
"- Incident response procedures must exist",
|
|
361
|
+
])
|
|
362
|
+
|
|
363
|
+
return "\n".join(lines)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _generate_observability_rules(context: ProjectContext) -> str:
|
|
367
|
+
obs = context.observability
|
|
368
|
+
return f"""## Observability Rules
|
|
369
|
+
|
|
370
|
+
Every piece of code must be observable from day one.
|
|
371
|
+
|
|
372
|
+
- **APM:** {obs.apm} — instrument all HTTP requests, database queries, and external calls
|
|
373
|
+
- **Metrics:** {obs.metrics} — expose custom metrics via /metrics endpoint
|
|
374
|
+
- **Logs:** {obs.logs} — ALL logs must be structured JSON with correlation IDs
|
|
375
|
+
- **Dashboards:** {obs.dashboards} — dashboard definitions must be version-controlled
|
|
376
|
+
- **Tracing:** {'Enabled' if obs.tracing else 'Disabled'} — use W3C Trace Context for distributed tracing
|
|
377
|
+
|
|
378
|
+
### When creating any new service or endpoint:
|
|
379
|
+
1. Add APM instrumentation (auto or manual)
|
|
380
|
+
2. Add custom metrics for business-relevant events
|
|
381
|
+
3. Use structured logging with correlation ID propagation
|
|
382
|
+
4. Add to the service's Grafana dashboard
|
|
383
|
+
5. Define alert rules for error rate and latency"""
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _generate_ai_rules(context: ProjectContext) -> str:
|
|
387
|
+
ai = context.ai
|
|
388
|
+
providers = ", ".join(ai.providers) if ai.providers else "not specified"
|
|
389
|
+
return f"""## AI Integration Rules
|
|
390
|
+
|
|
391
|
+
This project uses AI capabilities internally.
|
|
392
|
+
|
|
393
|
+
- **Providers:** {providers}
|
|
394
|
+
- **Observability:** {'Required' if ai.observability else 'Optional'}
|
|
395
|
+
|
|
396
|
+
### When integrating AI:
|
|
397
|
+
- Track per-request: model, tokens in/out, latency, cost estimate
|
|
398
|
+
- Implement cost controls — daily spend limits and alerts
|
|
399
|
+
- Add prompt injection detection on all user-facing AI inputs
|
|
400
|
+
- Log all AI interactions with trace IDs for debugging
|
|
401
|
+
- Monitor context window utilization — alert if consistently >80%
|
|
402
|
+
- Implement graceful degradation when AI services are unavailable
|
|
403
|
+
- Rate limit AI-powered endpoints separately from standard endpoints
|
|
404
|
+
- Never expose raw model responses to users without safety filtering
|
|
405
|
+
- Cache AI responses where appropriate to reduce cost and latency
|
|
406
|
+
- Track and alert on AI response quality metrics where measurable"""
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _generate_code_quality_rules(context: ProjectContext) -> str:
|
|
410
|
+
std = context.standards
|
|
411
|
+
return f"""## Code Quality Rules
|
|
412
|
+
|
|
413
|
+
- **Type checking:** {std.type_checking} — no exceptions
|
|
414
|
+
- **Linting:** {std.linting} — must pass before commit
|
|
415
|
+
- **Test coverage minimum:** {std.test_coverage_min}%
|
|
416
|
+
|
|
417
|
+
### Before writing any code:
|
|
418
|
+
1. Check if similar functionality already exists — do NOT duplicate
|
|
419
|
+
2. Follow existing patterns in the codebase — consistency over personal preference
|
|
420
|
+
3. Add types to everything — parameters, returns, variables where non-obvious
|
|
421
|
+
4. Write tests for new functionality
|
|
422
|
+
5. Ensure all imports are used and all exports are intentional
|
|
423
|
+
|
|
424
|
+
### Self-audit checklist (run mentally before completing any task):
|
|
425
|
+
- [ ] Does this follow the project's established patterns?
|
|
426
|
+
- [ ] Are all types explicit and correct?
|
|
427
|
+
- [ ] Is error handling consistent with the rest of the codebase?
|
|
428
|
+
- [ ] Is there any duplicated logic that should be extracted?
|
|
429
|
+
- [ ] Are security boundaries respected?
|
|
430
|
+
- [ ] Is this observable? (logs, metrics, traces)
|
|
431
|
+
- [ ] Would a new team member understand this code?"""
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _generate_journal_section(journal_content: str) -> str:
|
|
435
|
+
return f"""## Project-Specific Context (Journal)
|
|
436
|
+
|
|
437
|
+
These are learnings and nuances specific to this project. Read and respect them.
|
|
438
|
+
|
|
439
|
+
{journal_content}"""
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _generate_mcp_section(mcps: list) -> str:
|
|
443
|
+
lines = [
|
|
444
|
+
"## Recommended MCP Servers",
|
|
445
|
+
"",
|
|
446
|
+
"Use these MCP servers for better development context:",
|
|
447
|
+
"",
|
|
448
|
+
]
|
|
449
|
+
for mcp in mcps:
|
|
450
|
+
lines.append(f"- **{mcp.name}**: {mcp.description}")
|
|
451
|
+
if mcp.url:
|
|
452
|
+
lines.append(f" Command: `{mcp.url}`")
|
|
453
|
+
return "\n".join(lines)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _generate_self_audit_instructions(context: ProjectContext) -> str:
|
|
457
|
+
return """## Self-Audit Instructions
|
|
458
|
+
|
|
459
|
+
After generating or modifying any code, perform these checks automatically:
|
|
460
|
+
|
|
461
|
+
### 1. Pattern Compliance
|
|
462
|
+
- Does the new code follow established patterns in the codebase?
|
|
463
|
+
- If introducing a new pattern, is it documented and justified?
|
|
464
|
+
- Are there any anti-patterns present?
|
|
465
|
+
|
|
466
|
+
### 2. Security Check
|
|
467
|
+
- Is all user input validated?
|
|
468
|
+
- Are secrets properly managed (no hardcoded values)?
|
|
469
|
+
- Is authentication/authorization applied correctly?
|
|
470
|
+
- Are SQL queries parameterized?
|
|
471
|
+
|
|
472
|
+
### 3. API Compliance (if applicable)
|
|
473
|
+
- Does the endpoint have OpenAPI documentation?
|
|
474
|
+
- Does it follow the versioning strategy?
|
|
475
|
+
- Are error responses in RFC 7807 format?
|
|
476
|
+
- Is rate limiting configured?
|
|
477
|
+
- Is it MCP-ready (clear inputs/outputs, no implicit state)?
|
|
478
|
+
|
|
479
|
+
### 4. Observability Check
|
|
480
|
+
- Is the code instrumented for APM?
|
|
481
|
+
- Are relevant custom metrics added?
|
|
482
|
+
- Is logging structured with correlation IDs?
|
|
483
|
+
- Are dashboard updates needed?
|
|
484
|
+
|
|
485
|
+
### 5. Type Safety
|
|
486
|
+
- Are all types explicit?
|
|
487
|
+
- Are there any `any` or untyped values?
|
|
488
|
+
- Is input/output validation in place at boundaries?
|
|
489
|
+
|
|
490
|
+
### 6. Duplication Check
|
|
491
|
+
- Does similar functionality already exist?
|
|
492
|
+
- Can existing utilities/services be reused?
|
|
493
|
+
- Should this be extracted into a shared utility?
|
|
494
|
+
|
|
495
|
+
If any check fails, fix it before presenting the code."""
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _generate_implementation_order() -> str:
|
|
499
|
+
return """## Implementation Order for New Features
|
|
500
|
+
|
|
501
|
+
When building anything new, follow this order to ensure each step has full context:
|
|
502
|
+
|
|
503
|
+
1. **Types & Contracts** — Define models, interfaces, schemas first
|
|
504
|
+
2. **Data Layer** — Migrations, repository methods
|
|
505
|
+
3. **Business Logic** — Service classes, pure logic
|
|
506
|
+
4. **API Endpoints** — Thin controllers wiring services to HTTP
|
|
507
|
+
5. **Frontend** — Components consuming typed API contracts
|
|
508
|
+
6. **Observability** — Instrument the new code
|
|
509
|
+
7. **Tests** — Validate against acceptance criteria
|
|
510
|
+
|
|
511
|
+
This order minimizes errors because each step has complete context from the previous steps."""
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
# ── Helpers ────────────────────────────────────────────────────────────────
|
|
515
|
+
|
|
516
|
+
def _load_journal(project_path: Path) -> str:
|
|
517
|
+
"""Load the project journal content (just the entries, not the header)."""
|
|
518
|
+
journal_path = project_path / ".forge" / "journal.md"
|
|
519
|
+
if not journal_path.exists():
|
|
520
|
+
return ""
|
|
521
|
+
|
|
522
|
+
content = journal_path.read_text()
|
|
523
|
+
# Extract just the entries section
|
|
524
|
+
marker = "## Entries"
|
|
525
|
+
if marker in content:
|
|
526
|
+
entries = content.split(marker, 1)[1].strip()
|
|
527
|
+
return entries if entries else ""
|
|
528
|
+
return ""
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _load_yaml_dir(dir_path: Path) -> list[dict]:
|
|
532
|
+
"""Load all YAML files from a directory."""
|
|
533
|
+
if not dir_path.exists():
|
|
534
|
+
return []
|
|
535
|
+
results = []
|
|
536
|
+
for f in sorted(dir_path.glob("*.yaml")):
|
|
537
|
+
try:
|
|
538
|
+
with open(f) as fp:
|
|
539
|
+
data = yaml.safe_load(fp) or {}
|
|
540
|
+
results.append(data)
|
|
541
|
+
except Exception:
|
|
542
|
+
continue
|
|
543
|
+
return results
|