forgedev 1.1.3 → 1.3.0
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.
- package/README.md +58 -10
- package/bin/chainproof.js +126 -0
- package/bin/devforge.js +2 -1
- package/package.json +33 -7
- package/src/chainproof-bridge.js +330 -0
- package/src/ci-mode.js +85 -0
- package/src/claude-configurator.js +87 -49
- package/src/cli.js +35 -12
- package/src/composer.js +159 -34
- package/src/doctor-checks-chainproof.js +106 -0
- package/src/doctor-checks.js +39 -20
- package/src/doctor-prompts.js +9 -9
- package/src/doctor.js +37 -4
- package/src/guided.js +3 -3
- package/src/index.js +31 -10
- package/src/init-mode.js +64 -11
- package/src/menu.js +178 -0
- package/src/prompts.js +5 -12
- package/src/recommender.js +134 -10
- package/src/scanner.js +57 -2
- package/src/uat-generator.js +204 -189
- package/src/update-check.js +9 -4
- package/src/update.js +1 -1
- package/src/utils.js +65 -6
- package/templates/ai/guardrails-py/backend/app/ai/__init__.py +29 -0
- package/templates/ai/guardrails-py/backend/app/ai/audit_log.py +133 -0
- package/templates/ai/guardrails-py/backend/app/ai/client.py.template +323 -0
- package/templates/ai/guardrails-py/backend/app/ai/health.py.template +157 -0
- package/templates/ai/guardrails-py/backend/app/ai/input_guard.py +98 -0
- package/templates/ai/guardrails-ts/src/lib/ai/audit-log.ts.template +164 -0
- package/templates/ai/guardrails-ts/src/lib/ai/client.ts.template +403 -0
- package/templates/ai/guardrails-ts/src/lib/ai/health.ts.template +165 -0
- package/templates/ai/guardrails-ts/src/lib/ai/index.ts.template +17 -0
- package/templates/ai/guardrails-ts/src/lib/ai/input-guard.ts.template +124 -0
- package/templates/auth/nextauth/src/lib/auth.ts.template +12 -7
- package/templates/backend/express/Dockerfile.template +18 -0
- package/templates/backend/express/package.json.template +33 -0
- package/templates/backend/express/src/index.ts.template +34 -0
- package/templates/backend/express/src/routes/health.ts.template +27 -0
- package/templates/backend/express/tsconfig.json +17 -0
- package/templates/backend/fastapi/backend/Dockerfile.template +5 -0
- package/templates/backend/fastapi/backend/app/api/health.py.template +1 -1
- package/templates/backend/fastapi/backend/app/core/config.py.template +1 -1
- package/templates/backend/fastapi/backend/app/core/errors.py +1 -1
- package/templates/backend/fastapi/backend/app/main.py.template +3 -1
- package/templates/backend/fastapi/backend/requirements.txt.template +2 -0
- package/templates/backend/hono/Dockerfile.template +18 -0
- package/templates/backend/hono/package.json.template +31 -0
- package/templates/backend/hono/src/index.ts.template +32 -0
- package/templates/backend/hono/src/routes/health.ts.template +27 -0
- package/templates/backend/hono/tsconfig.json +18 -0
- package/templates/base/docs/plans/.gitkeep +0 -0
- package/templates/base/docs/uat/UAT_CHECKLIST.csv.template +2 -0
- package/templates/base/docs/uat/UAT_TEMPLATE.md.template +22 -0
- package/templates/chainproof/base/.chainproof/config.json.template +11 -0
- package/templates/chainproof/base/.chainproof/mcp-server.mjs +310 -0
- package/templates/chainproof/base/.mcp.json +9 -0
- package/templates/chainproof/fastapi/.chainproof/middleware.json.template +14 -0
- package/templates/chainproof/nextjs/.chainproof/hooks.json.template +19 -0
- package/templates/chainproof/polyglot/.chainproof/config.json.template +21 -0
- package/templates/claude-code/agents/architect.md +25 -11
- package/templates/claude-code/agents/build-error-resolver.md +22 -7
- package/templates/claude-code/agents/chief-of-staff.md +42 -8
- package/templates/claude-code/agents/code-quality-reviewer.md +15 -1
- package/templates/claude-code/agents/database-reviewer.md +16 -2
- package/templates/claude-code/agents/deep-reviewer.md +191 -0
- package/templates/claude-code/agents/doc-updater.md +19 -5
- package/templates/claude-code/agents/docs-lookup.md +19 -5
- package/templates/claude-code/agents/e2e-runner.md +26 -12
- package/templates/claude-code/agents/enforcement-gate.md +102 -0
- package/templates/claude-code/agents/frontend-builder.md +188 -0
- package/templates/claude-code/agents/harness-optimizer.md +61 -0
- package/templates/claude-code/agents/loop-operator.md +27 -12
- package/templates/claude-code/agents/planner.md +21 -7
- package/templates/claude-code/agents/product-strategist.md +138 -0
- package/templates/claude-code/agents/production-readiness.md +14 -0
- package/templates/claude-code/agents/prompt-auditor.md +115 -0
- package/templates/claude-code/agents/refactor-cleaner.md +22 -8
- package/templates/claude-code/agents/security-reviewer.md +15 -0
- package/templates/claude-code/agents/spec-validator.md +45 -1
- package/templates/claude-code/agents/tdd-guide.md +21 -7
- package/templates/claude-code/agents/uat-validator.md +18 -0
- package/templates/claude-code/claude-md/base.md +15 -7
- package/templates/claude-code/claude-md/fastapi.md +8 -8
- package/templates/claude-code/claude-md/fullstack.md +6 -6
- package/templates/claude-code/claude-md/hono.md +18 -0
- package/templates/claude-code/claude-md/nextjs.md +5 -5
- package/templates/claude-code/claude-md/remix.md +18 -0
- package/templates/claude-code/commands/audit-security.md +14 -0
- package/templates/claude-code/commands/audit-spec.md +14 -0
- package/templates/claude-code/commands/audit-wiring.md +14 -0
- package/templates/claude-code/commands/build-fix.md +28 -0
- package/templates/claude-code/commands/build-ui.md +59 -0
- package/templates/claude-code/commands/code-review.md +54 -26
- package/templates/claude-code/commands/fix-loop.md +211 -0
- package/templates/claude-code/commands/full-audit.md +37 -8
- package/templates/claude-code/commands/generate-prd.md +1 -1
- package/templates/claude-code/commands/generate-sdd.md +74 -0
- package/templates/claude-code/commands/generate-uat.md +107 -35
- package/templates/claude-code/commands/help.md +68 -0
- package/templates/claude-code/commands/live-uat.md +268 -0
- package/templates/claude-code/commands/optimize-claude-md.md +15 -1
- package/templates/claude-code/commands/plan.md +3 -3
- package/templates/claude-code/commands/pre-pr.md +57 -19
- package/templates/claude-code/commands/product-strategist.md +21 -0
- package/templates/claude-code/commands/resume-session.md +10 -10
- package/templates/claude-code/commands/run-uat.md +59 -2
- package/templates/claude-code/commands/save-session.md +10 -10
- package/templates/claude-code/commands/simplify.md +36 -0
- package/templates/claude-code/commands/tdd.md +17 -18
- package/templates/claude-code/commands/verify-all.md +24 -0
- package/templates/claude-code/commands/verify-intent.md +55 -0
- package/templates/claude-code/commands/workflows.md +52 -37
- package/templates/claude-code/hooks/polyglot.json +10 -1
- package/templates/claude-code/hooks/python.json +10 -1
- package/templates/claude-code/hooks/scripts/autofix-polyglot.mjs +20 -10
- package/templates/claude-code/hooks/scripts/autofix-python.mjs +4 -5
- package/templates/claude-code/hooks/scripts/autofix-typescript.mjs +4 -4
- package/templates/claude-code/hooks/scripts/code-hygiene.mjs +293 -0
- package/templates/claude-code/hooks/scripts/guard-protected-files.mjs +2 -2
- package/templates/claude-code/hooks/scripts/pre-commit-gate.mjs +207 -0
- package/templates/claude-code/hooks/typescript.json +10 -1
- package/templates/claude-code/skills/ai-prompts/SKILL.md +119 -41
- package/templates/claude-code/skills/git-workflow/SKILL.md +6 -6
- package/templates/claude-code/skills/nextjs/SKILL.md +1 -1
- package/templates/claude-code/skills/playwright/SKILL.md +6 -5
- package/templates/claude-code/skills/security-api/SKILL.md +1 -1
- package/templates/claude-code/skills/security-web/SKILL.md +2 -1
- package/templates/claude-code/skills/testing-patterns/SKILL.md +9 -9
- package/templates/database/prisma-postgres/{.env.example → .env.example.template} +1 -0
- package/templates/database/sqlalchemy-postgres/{.env.example → .env.example.template} +1 -0
- package/templates/docs-portal/fastapi/backend/app/portal/__init__.py +0 -0
- package/templates/docs-portal/fastapi/backend/app/portal/__pycache__/docs_reader.cpython-314.pyc +0 -0
- package/templates/docs-portal/fastapi/backend/app/portal/docs_reader.py +201 -0
- package/templates/docs-portal/fastapi/backend/app/portal/html_renderer.py +229 -0
- package/templates/docs-portal/fastapi/backend/app/portal/router.py.template +35 -0
- package/templates/docs-portal/nextjs/src/app/portal/[category]/[slug]/page.tsx +81 -0
- package/templates/docs-portal/nextjs/src/app/portal/[category]/page.tsx +65 -0
- package/templates/docs-portal/nextjs/src/app/portal/layout.tsx.template +54 -0
- package/templates/docs-portal/nextjs/src/app/portal/page.tsx +85 -0
- package/templates/docs-portal/nextjs/src/components/portal/markdown-renderer.tsx +101 -0
- package/templates/docs-portal/nextjs/src/components/portal/mobile-portal-nav.tsx +81 -0
- package/templates/docs-portal/nextjs/src/components/portal/portal-nav.tsx +86 -0
- package/templates/docs-portal/nextjs/src/lib/docs.ts +139 -0
- package/templates/frontend/nextjs/package.json.template +3 -1
- package/templates/frontend/react/index.html.template +12 -0
- package/templates/frontend/react/package.json.template +34 -0
- package/templates/frontend/react/src/App.tsx.template +10 -0
- package/templates/frontend/react/src/index.css +1 -0
- package/templates/frontend/react/src/main.tsx +10 -0
- package/templates/frontend/react/tsconfig.json +17 -0
- package/templates/frontend/react/vite.config.ts.template +15 -0
- package/templates/frontend/react/vitest.config.ts +9 -0
- package/templates/frontend/remix/app/root.tsx.template +31 -0
- package/templates/frontend/remix/app/routes/_index.tsx.template +19 -0
- package/templates/frontend/remix/app/routes/api.health.ts.template +10 -0
- package/templates/frontend/remix/app/tailwind.css +1 -0
- package/templates/frontend/remix/package.json.template +39 -0
- package/templates/frontend/remix/tsconfig.json +18 -0
- package/templates/frontend/remix/vite.config.ts.template +7 -0
- package/templates/infra/github-actions/.github/workflows/ci.yml.template +52 -0
- package/templates/testing/pytest/backend/tests/__init__.py +0 -0
- package/templates/testing/pytest/backend/tests/conftest.py.template +11 -0
- package/templates/testing/pytest/backend/tests/test_health.py.template +10 -0
- package/templates/testing/vitest/vitest.config.ts.template +18 -0
- package/CLAUDE.md +0 -38
- package/templates/claude-code/commands/done.md +0 -19
|
@@ -1,44 +1,122 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: AI/LLM integration patterns and
|
|
2
|
+
name: ai-prompts
|
|
3
|
+
description: AI/LLM integration patterns, guardrails infrastructure, and compliance (EU AI Act, NIST AI RMF)
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# AI/LLM Integration
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
6
|
+
# AI/LLM Integration & Guardrails
|
|
7
|
+
|
|
8
|
+
This project includes AI guardrails infrastructure in `src/lib/ai/` (TypeScript) or `app/ai/` (Python).
|
|
9
|
+
|
|
10
|
+
## Using the AI Client
|
|
11
|
+
|
|
12
|
+
All AI calls MUST go through the guardrails client. Never call the Anthropic SDK directly.
|
|
13
|
+
|
|
14
|
+
**TypeScript:**
|
|
15
|
+
```typescript
|
|
16
|
+
import { getAIClient } from '@/lib/ai';
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
|
|
19
|
+
const ai = getAIClient();
|
|
20
|
+
const result = await ai.generate({
|
|
21
|
+
prompt: 'Analyze this text',
|
|
22
|
+
schema: z.object({ sentiment: z.enum(['positive', 'negative', 'neutral']), confidence: z.number() }),
|
|
23
|
+
purpose: 'sentiment-analysis', // Required for audit trail
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (result.needsHumanReview) {
|
|
27
|
+
// Route to approval queue — confidence below threshold
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Python:**
|
|
32
|
+
```python
|
|
33
|
+
from app.ai import get_ai_client
|
|
34
|
+
from pydantic import BaseModel
|
|
35
|
+
|
|
36
|
+
class Sentiment(BaseModel):
|
|
37
|
+
sentiment: str
|
|
38
|
+
confidence: float
|
|
39
|
+
|
|
40
|
+
ai = get_ai_client()
|
|
41
|
+
result = await ai.generate(prompt="Analyze this text", schema=Sentiment, purpose="sentiment-analysis")
|
|
42
|
+
|
|
43
|
+
if result.needs_human_review:
|
|
44
|
+
# Route to approval queue
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Guardrails Architecture
|
|
48
|
+
|
|
49
|
+
| Layer | What It Does | Compliance |
|
|
50
|
+
|-------|-------------|------------|
|
|
51
|
+
| **Input Guard** | Prompt injection detection, input sanitization, length limits | EU AI Act Art. 15, NIST Manage 2.2 |
|
|
52
|
+
| **Output Validation** | Zod/Pydantic schema validation with retry on parse failure | NIST Manage 2.4 |
|
|
53
|
+
| **Confidence Scoring** | Scores each response (0-1), routes low confidence to human review | NIST Measure 2.5, EU AI Act Art. 14 |
|
|
54
|
+
| **Audit Logger** | Structured logging of every AI interaction (input preview, output, confidence, model, latency) | EU AI Act Art. 12, NIST Manage 1.3 |
|
|
55
|
+
| **Health Metrics** | AI-specific health endpoint: availability, confidence distribution, error rates, per-model stats | NIST Manage 3.2, EU AI Act Art. 9 |
|
|
56
|
+
| **AI Disclosure** | All AI responses carry `aiGenerated: true` flag | EU AI Act Art. 50 |
|
|
57
|
+
|
|
58
|
+
## Rules
|
|
59
|
+
|
|
60
|
+
- **Never call the Anthropic/OpenAI SDK directly.** Always use `getAIClient()` / `get_ai_client()`
|
|
61
|
+
- **Never use raw string responses in application logic.** Always validate with Zod/Pydantic schemas
|
|
62
|
+
- **Never skip the `purpose` parameter.** Every AI call must be tagged for audit traceability
|
|
63
|
+
- **Never trust AI output for security decisions.** Always validate independently
|
|
64
|
+
- **Never log full prompts.** The audit logger captures only a 200-char preview to avoid PII leakage
|
|
65
|
+
- **Always handle `needsHumanReview`.** If confidence is below threshold, the response must be reviewed before acting on it
|
|
66
|
+
|
|
67
|
+
## Confidence Thresholds
|
|
68
|
+
|
|
69
|
+
| Confidence | Action | Use Case |
|
|
70
|
+
|-----------|--------|----------|
|
|
71
|
+
| > 0.9 | Auto-accept | Low-risk: summaries, formatting, classification |
|
|
72
|
+
| 0.7 - 0.9 | Accept with logging | Medium-risk: recommendations, content generation |
|
|
73
|
+
| 0.5 - 0.7 | Flag for review | High-risk: decisions, user-facing content |
|
|
74
|
+
| < 0.5 | Require human approval | Critical: financial, medical, legal, safety |
|
|
75
|
+
|
|
76
|
+
Configure the threshold per-call via `confidenceThreshold` parameter.
|
|
77
|
+
|
|
78
|
+
## Prompt Injection Protection
|
|
79
|
+
|
|
80
|
+
The input guard detects:
|
|
81
|
+
- **Instruction override**: "ignore previous instructions", "disregard your rules"
|
|
82
|
+
- **Role manipulation**: "you are now a...", "pretend to be..."
|
|
83
|
+
- **System prompt extraction**: "show me your system prompt", "repeat your instructions"
|
|
84
|
+
- **Delimiter injection**: `<system>`, `[INST]`, `<|im_start|>`
|
|
85
|
+
- **Data exfiltration**: "send this data to..."
|
|
86
|
+
|
|
87
|
+
Detected injections are blocked and logged. Suspicious patterns (encoded payloads, code execution) are logged but not blocked.
|
|
88
|
+
|
|
89
|
+
## AI Health Endpoint
|
|
90
|
+
|
|
91
|
+
Mount at `/api/ai/health`. Returns:
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"status": "ok | degraded | unhealthy",
|
|
95
|
+
"aiAvailable": true,
|
|
96
|
+
"metrics": {
|
|
97
|
+
"totalCalls": 142,
|
|
98
|
+
"successRate": 0.97,
|
|
99
|
+
"avgConfidence": 0.84,
|
|
100
|
+
"avgLatencyMs": 1230,
|
|
101
|
+
"lowConfidenceRate": 0.08,
|
|
102
|
+
"errorRate": 0.03
|
|
103
|
+
},
|
|
104
|
+
"models": {
|
|
105
|
+
"claude-sonnet-4-20250514": { "calls": 142, "successRate": 0.97, "avgLatencyMs": 1230 }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Recommended Libraries
|
|
111
|
+
|
|
112
|
+
**TypeScript:** Zod (validation), @anthropic-ai/sdk (model calls), @instructor-ai/instructor (structured extraction)
|
|
113
|
+
**Python:** Pydantic (validation), anthropic (model calls), instructor (structured extraction), guardrails-ai (advanced validation), presidio (PII detection)
|
|
114
|
+
**Observability:** Langfuse (open-source LLM tracing), OpenTelemetry (spans), Helicone (proxy logging)
|
|
115
|
+
|
|
116
|
+
## Testing AI Integrations
|
|
117
|
+
|
|
118
|
+
- Mock AI responses in unit tests — never make real API calls in CI
|
|
119
|
+
- Use golden datasets for regression testing prompt quality
|
|
120
|
+
- Test timeout, retry, and error handling paths explicitly
|
|
121
|
+
- Test with adversarial inputs (prompt injection patterns)
|
|
122
|
+
- Monitor confidence score distribution for drift over time
|
|
@@ -5,10 +5,10 @@ description: Git branching, commits, PR workflow, and conflict resolution patter
|
|
|
5
5
|
|
|
6
6
|
## Branching Strategy
|
|
7
7
|
|
|
8
|
-
- `main`
|
|
9
|
-
- `feat/<name>`
|
|
10
|
-
- `fix/<name>`
|
|
11
|
-
- `chore/<name>`
|
|
8
|
+
- `main` - production-ready code, never commit directly
|
|
9
|
+
- `feat/<name>` - new features, branch from main
|
|
10
|
+
- `fix/<name>` - bug fixes, branch from main
|
|
11
|
+
- `chore/<name>` - maintenance, config changes, dependency updates
|
|
12
12
|
|
|
13
13
|
Always create a feature branch before starting work:
|
|
14
14
|
```bash
|
|
@@ -59,6 +59,6 @@ If rebase gets messy: `git rebase --abort` and start over.
|
|
|
59
59
|
|
|
60
60
|
- Never force-push to `main`
|
|
61
61
|
- Never commit `.env` files, credentials, or large binaries
|
|
62
|
-
- Never rebase
|
|
63
|
-
-
|
|
62
|
+
- Never rebase shared branches (`main`, `develop`). Only rebase your own feature branches
|
|
63
|
+
- Update feature branches with `git rebase origin/main`, not `git merge main` (force-push your branch after)
|
|
64
64
|
- Always pull before pushing: `git pull --rebase` (pulls current branch's upstream)
|
|
@@ -33,7 +33,7 @@ description: Next.js 15 App Router patterns and best practices
|
|
|
33
33
|
- Intercepting routes `(.)` for modals
|
|
34
34
|
|
|
35
35
|
## Common Mistakes
|
|
36
|
-
-
|
|
36
|
+
- Avoid Client Components with `useEffect` for data fetching. Prefer async Server Components instead
|
|
37
37
|
- Don't pass functions as props from Server to Client Components
|
|
38
38
|
- Don't use `router.push` when `<Link>` works
|
|
39
39
|
- Don't create API routes for data that Server Components can fetch directly
|
|
@@ -11,11 +11,12 @@ description: Playwright E2E testing patterns and best practices
|
|
|
11
11
|
- Use `test.beforeEach()` for common setup (navigation, auth)
|
|
12
12
|
- Use page object pattern for complex pages
|
|
13
13
|
|
|
14
|
-
##
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
## Selector Priority
|
|
15
|
+
1. `getByRole()`: buttons, links, headings (best for accessibility)
|
|
16
|
+
2. `getByLabel()`: form inputs by label
|
|
17
|
+
3. `getByText()`: visible text content
|
|
18
|
+
4. `getByTestId()`: `data-testid` attributes (last resort)
|
|
19
|
+
- Avoid CSS selectors or XPath. They break when styles change
|
|
19
20
|
|
|
20
21
|
## Assertions
|
|
21
22
|
- Use `expect(locator)` for element assertions
|
|
@@ -20,7 +20,7 @@ description: API security best practices
|
|
|
20
20
|
- Validate file uploads (size, type, extension)
|
|
21
21
|
|
|
22
22
|
## SQL Injection Prevention
|
|
23
|
-
- Use SQLAlchemy ORM
|
|
23
|
+
- Use SQLAlchemy ORM. Never raw SQL strings
|
|
24
24
|
- If raw SQL needed, use `text()` with bound parameters
|
|
25
25
|
- Never interpolate user input into queries
|
|
26
26
|
|
|
@@ -6,7 +6,7 @@ description: Web application security best practices
|
|
|
6
6
|
# Web Application Security
|
|
7
7
|
|
|
8
8
|
## XSS Prevention
|
|
9
|
-
- React escapes by default
|
|
9
|
+
- React escapes by default. Never use `dangerouslySetInnerHTML`
|
|
10
10
|
- Sanitize user input before rendering
|
|
11
11
|
- Use Content Security Policy headers
|
|
12
12
|
- Validate URLs before using in `href` or `src`
|
|
@@ -39,3 +39,4 @@ description: Web application security best practices
|
|
|
39
39
|
- Use Prisma `select` or `omit` to control returned fields
|
|
40
40
|
- Never log sensitive data
|
|
41
41
|
- Strip internal IDs from client-facing responses when possible
|
|
42
|
+
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: testing-patterns
|
|
3
|
-
description: Universal testing principles
|
|
3
|
+
description: Universal testing principles - test pyramid, AAA pattern, mocking strategies, and coverage targets
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## Test Pyramid
|
|
7
7
|
|
|
8
8
|
```
|
|
9
|
-
/ E2E \
|
|
10
|
-
/ Integration \
|
|
11
|
-
/ Unit Tests
|
|
9
|
+
/ E2E \ - Few, slow, high confidence
|
|
10
|
+
/ Integration \ - Some, medium speed
|
|
11
|
+
/ Unit Tests \- Many, fast, focused
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
- **Unit tests** (70%): Test individual functions in isolation. Fast, many.
|
|
@@ -21,14 +21,14 @@ Every test follows this structure:
|
|
|
21
21
|
|
|
22
22
|
```
|
|
23
23
|
test('should calculate total with tax', () => {
|
|
24
|
-
// Arrange
|
|
24
|
+
// Arrange: set up test data
|
|
25
25
|
const items = [{ price: 10 }, { price: 20 }];
|
|
26
26
|
const taxRate = 0.1;
|
|
27
27
|
|
|
28
|
-
// Act
|
|
28
|
+
// Act: execute the function
|
|
29
29
|
const total = calculateTotal(items, taxRate);
|
|
30
30
|
|
|
31
|
-
// Assert
|
|
31
|
+
// Assert: verify the result
|
|
32
32
|
expect(total).toBe(33);
|
|
33
33
|
});
|
|
34
34
|
```
|
|
@@ -52,7 +52,7 @@ test('should calculate total with tax', () => {
|
|
|
52
52
|
|
|
53
53
|
| What | When to Mock |
|
|
54
54
|
|------|-------------|
|
|
55
|
-
| External APIs | Always
|
|
55
|
+
| External APIs | Always. They're slow and unreliable |
|
|
56
56
|
| Database | Integration tests use real DB, unit tests mock |
|
|
57
57
|
| Time/Date | When testing time-dependent logic |
|
|
58
58
|
| File system | When testing file operations |
|
|
@@ -84,7 +84,7 @@ Use descriptive names that explain the scenario:
|
|
|
84
84
|
|
|
85
85
|
- **80% minimum** for all code
|
|
86
86
|
- **100% required** for: auth logic, financial calculations, security-critical code
|
|
87
|
-
- Coverage measures lines hit, not correctness
|
|
87
|
+
- Coverage measures lines hit, not correctness. High coverage with weak assertions is useless
|
|
88
88
|
- Focus on meaningful assertions, not just line coverage
|
|
89
89
|
|
|
90
90
|
## Common Anti-Patterns
|
|
File without changes
|
package/templates/docs-portal/fastapi/backend/app/portal/__pycache__/docs_reader.cpython-314.pyc
ADDED
|
Binary file
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Reads and organizes markdown files from the docs/ directory."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
import bleach
|
|
9
|
+
import markdown
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
DOCS_ROOT = Path(__file__).resolve().parent.parent.parent.parent / "docs"
|
|
14
|
+
|
|
15
|
+
ALLOWED_HTML_TAGS = [
|
|
16
|
+
"a",
|
|
17
|
+
"p",
|
|
18
|
+
"ul",
|
|
19
|
+
"ol",
|
|
20
|
+
"li",
|
|
21
|
+
"strong",
|
|
22
|
+
"em",
|
|
23
|
+
"code",
|
|
24
|
+
"pre",
|
|
25
|
+
"blockquote",
|
|
26
|
+
"hr",
|
|
27
|
+
"br",
|
|
28
|
+
"h1",
|
|
29
|
+
"h2",
|
|
30
|
+
"h3",
|
|
31
|
+
"h4",
|
|
32
|
+
"h5",
|
|
33
|
+
"h6",
|
|
34
|
+
"table",
|
|
35
|
+
"thead",
|
|
36
|
+
"tbody",
|
|
37
|
+
"tr",
|
|
38
|
+
"th",
|
|
39
|
+
"td",
|
|
40
|
+
]
|
|
41
|
+
ALLOWED_HTML_ATTRIBUTES = {
|
|
42
|
+
"a": ["href", "title"],
|
|
43
|
+
"th": ["align"],
|
|
44
|
+
"td": ["align"],
|
|
45
|
+
}
|
|
46
|
+
ALLOWED_PROTOCOLS = ["http", "https", "mailto"]
|
|
47
|
+
|
|
48
|
+
CATEGORY_META: dict[str, dict] = {
|
|
49
|
+
"prd": {"label": "Product", "description": "Product requirements and roadmap", "icon": "📋", "order": 1},
|
|
50
|
+
"sdd": {"label": "Architecture", "description": "System design and technical architecture", "icon": "🏗️", "order": 2},
|
|
51
|
+
"uat": {"label": "Testing", "description": "Test plans, results, and acceptance criteria", "icon": "✅", "order": 3},
|
|
52
|
+
"sessions": {"label": "Session Logs", "description": "Testing session results and bug reports", "icon": "📝", "order": 4},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class DocFile:
|
|
58
|
+
slug: str
|
|
59
|
+
title: str
|
|
60
|
+
content: str
|
|
61
|
+
html: str
|
|
62
|
+
category: str
|
|
63
|
+
category_label: str
|
|
64
|
+
last_modified: datetime
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class DocCategory:
|
|
69
|
+
id: str
|
|
70
|
+
label: str
|
|
71
|
+
description: str
|
|
72
|
+
icon: str
|
|
73
|
+
order: int
|
|
74
|
+
docs: list[dict] = field(default_factory=list)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _extract_title(content: str, filename: str) -> str:
|
|
78
|
+
for line in content.split("\n"):
|
|
79
|
+
line = line.strip()
|
|
80
|
+
if line.startswith("# ") and not line.startswith("##"):
|
|
81
|
+
return line[2:].strip()
|
|
82
|
+
return filename.replace(".md", "").replace("-", " ").replace("_", " ").title()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _render_markdown(content: str) -> str:
|
|
86
|
+
md = markdown.Markdown(extensions=["tables", "fenced_code", "toc"])
|
|
87
|
+
html = md.convert(content)
|
|
88
|
+
return bleach.clean(
|
|
89
|
+
html,
|
|
90
|
+
tags=ALLOWED_HTML_TAGS,
|
|
91
|
+
attributes=ALLOWED_HTML_ATTRIBUTES,
|
|
92
|
+
protocols=ALLOWED_PROTOCOLS,
|
|
93
|
+
strip=True,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_categories() -> list[DocCategory]:
|
|
98
|
+
if not DOCS_ROOT.exists():
|
|
99
|
+
return []
|
|
100
|
+
|
|
101
|
+
categories: list[DocCategory] = []
|
|
102
|
+
|
|
103
|
+
for entry in sorted(DOCS_ROOT.iterdir()):
|
|
104
|
+
if not entry.is_dir():
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
category_id = entry.name
|
|
108
|
+
meta = CATEGORY_META.get(category_id, {
|
|
109
|
+
"label": category_id.capitalize(),
|
|
110
|
+
"description": "",
|
|
111
|
+
"icon": "📄",
|
|
112
|
+
"order": 99,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
md_files = sorted(entry.glob("*.md"))
|
|
116
|
+
if not md_files:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
docs = []
|
|
120
|
+
for f in md_files:
|
|
121
|
+
try:
|
|
122
|
+
content = f.read_text(encoding="utf-8")
|
|
123
|
+
docs.append({
|
|
124
|
+
"slug": f.stem,
|
|
125
|
+
"title": _extract_title(content, f.name),
|
|
126
|
+
})
|
|
127
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
128
|
+
logger.warning("Skipping docs file %s: %s", f.name, exc)
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
categories.append(DocCategory(
|
|
132
|
+
id=category_id,
|
|
133
|
+
label=meta["label"],
|
|
134
|
+
description=meta["description"],
|
|
135
|
+
icon=meta["icon"],
|
|
136
|
+
order=meta["order"],
|
|
137
|
+
docs=docs,
|
|
138
|
+
))
|
|
139
|
+
|
|
140
|
+
# Top-level markdown files
|
|
141
|
+
top_level = sorted(DOCS_ROOT.glob("*.md"))
|
|
142
|
+
if top_level:
|
|
143
|
+
docs = []
|
|
144
|
+
for f in top_level:
|
|
145
|
+
content = f.read_text(encoding="utf-8")
|
|
146
|
+
docs.append({
|
|
147
|
+
"slug": f.stem,
|
|
148
|
+
"title": _extract_title(content, f.name),
|
|
149
|
+
})
|
|
150
|
+
categories.append(DocCategory(
|
|
151
|
+
id="_general",
|
|
152
|
+
label="General",
|
|
153
|
+
description="Project documentation",
|
|
154
|
+
icon="📄",
|
|
155
|
+
order=99,
|
|
156
|
+
docs=docs,
|
|
157
|
+
))
|
|
158
|
+
|
|
159
|
+
return sorted(categories, key=lambda c: c.order)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def get_doc(category: str, slug: str) -> DocFile | None:
|
|
163
|
+
if category == "_general":
|
|
164
|
+
file_path = DOCS_ROOT / f"{slug}.md"
|
|
165
|
+
else:
|
|
166
|
+
file_path = DOCS_ROOT / category / f"{slug}.md"
|
|
167
|
+
|
|
168
|
+
# Prevent path traversal — resolved path must stay within DOCS_ROOT
|
|
169
|
+
docs_root_resolved = DOCS_ROOT.resolve()
|
|
170
|
+
resolved = file_path.resolve()
|
|
171
|
+
try:
|
|
172
|
+
resolved.relative_to(docs_root_resolved)
|
|
173
|
+
except ValueError:
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
if not resolved.exists():
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
content = resolved.read_text(encoding="utf-8")
|
|
180
|
+
stat = resolved.stat()
|
|
181
|
+
meta = CATEGORY_META.get(category, {"label": "General"})
|
|
182
|
+
|
|
183
|
+
return DocFile(
|
|
184
|
+
slug=slug,
|
|
185
|
+
title=_extract_title(content, f"{slug}.md"),
|
|
186
|
+
content=content,
|
|
187
|
+
html=_render_markdown(content),
|
|
188
|
+
category=category,
|
|
189
|
+
category_label=meta["label"],
|
|
190
|
+
last_modified=datetime.fromtimestamp(stat.st_mtime),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def get_all_docs() -> list[DocFile]:
|
|
195
|
+
docs: list[DocFile] = []
|
|
196
|
+
for cat in get_categories():
|
|
197
|
+
for doc_meta in cat.docs:
|
|
198
|
+
doc = get_doc(cat.id, doc_meta["slug"])
|
|
199
|
+
if doc:
|
|
200
|
+
docs.append(doc)
|
|
201
|
+
return docs
|