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.
Files changed (167) hide show
  1. package/README.md +58 -10
  2. package/bin/chainproof.js +126 -0
  3. package/bin/devforge.js +2 -1
  4. package/package.json +33 -7
  5. package/src/chainproof-bridge.js +330 -0
  6. package/src/ci-mode.js +85 -0
  7. package/src/claude-configurator.js +87 -49
  8. package/src/cli.js +35 -12
  9. package/src/composer.js +159 -34
  10. package/src/doctor-checks-chainproof.js +106 -0
  11. package/src/doctor-checks.js +39 -20
  12. package/src/doctor-prompts.js +9 -9
  13. package/src/doctor.js +37 -4
  14. package/src/guided.js +3 -3
  15. package/src/index.js +31 -10
  16. package/src/init-mode.js +64 -11
  17. package/src/menu.js +178 -0
  18. package/src/prompts.js +5 -12
  19. package/src/recommender.js +134 -10
  20. package/src/scanner.js +57 -2
  21. package/src/uat-generator.js +204 -189
  22. package/src/update-check.js +9 -4
  23. package/src/update.js +1 -1
  24. package/src/utils.js +65 -6
  25. package/templates/ai/guardrails-py/backend/app/ai/__init__.py +29 -0
  26. package/templates/ai/guardrails-py/backend/app/ai/audit_log.py +133 -0
  27. package/templates/ai/guardrails-py/backend/app/ai/client.py.template +323 -0
  28. package/templates/ai/guardrails-py/backend/app/ai/health.py.template +157 -0
  29. package/templates/ai/guardrails-py/backend/app/ai/input_guard.py +98 -0
  30. package/templates/ai/guardrails-ts/src/lib/ai/audit-log.ts.template +164 -0
  31. package/templates/ai/guardrails-ts/src/lib/ai/client.ts.template +403 -0
  32. package/templates/ai/guardrails-ts/src/lib/ai/health.ts.template +165 -0
  33. package/templates/ai/guardrails-ts/src/lib/ai/index.ts.template +17 -0
  34. package/templates/ai/guardrails-ts/src/lib/ai/input-guard.ts.template +124 -0
  35. package/templates/auth/nextauth/src/lib/auth.ts.template +12 -7
  36. package/templates/backend/express/Dockerfile.template +18 -0
  37. package/templates/backend/express/package.json.template +33 -0
  38. package/templates/backend/express/src/index.ts.template +34 -0
  39. package/templates/backend/express/src/routes/health.ts.template +27 -0
  40. package/templates/backend/express/tsconfig.json +17 -0
  41. package/templates/backend/fastapi/backend/Dockerfile.template +5 -0
  42. package/templates/backend/fastapi/backend/app/api/health.py.template +1 -1
  43. package/templates/backend/fastapi/backend/app/core/config.py.template +1 -1
  44. package/templates/backend/fastapi/backend/app/core/errors.py +1 -1
  45. package/templates/backend/fastapi/backend/app/main.py.template +3 -1
  46. package/templates/backend/fastapi/backend/requirements.txt.template +2 -0
  47. package/templates/backend/hono/Dockerfile.template +18 -0
  48. package/templates/backend/hono/package.json.template +31 -0
  49. package/templates/backend/hono/src/index.ts.template +32 -0
  50. package/templates/backend/hono/src/routes/health.ts.template +27 -0
  51. package/templates/backend/hono/tsconfig.json +18 -0
  52. package/templates/base/docs/plans/.gitkeep +0 -0
  53. package/templates/base/docs/uat/UAT_CHECKLIST.csv.template +2 -0
  54. package/templates/base/docs/uat/UAT_TEMPLATE.md.template +22 -0
  55. package/templates/chainproof/base/.chainproof/config.json.template +11 -0
  56. package/templates/chainproof/base/.chainproof/mcp-server.mjs +310 -0
  57. package/templates/chainproof/base/.mcp.json +9 -0
  58. package/templates/chainproof/fastapi/.chainproof/middleware.json.template +14 -0
  59. package/templates/chainproof/nextjs/.chainproof/hooks.json.template +19 -0
  60. package/templates/chainproof/polyglot/.chainproof/config.json.template +21 -0
  61. package/templates/claude-code/agents/architect.md +25 -11
  62. package/templates/claude-code/agents/build-error-resolver.md +22 -7
  63. package/templates/claude-code/agents/chief-of-staff.md +42 -8
  64. package/templates/claude-code/agents/code-quality-reviewer.md +15 -1
  65. package/templates/claude-code/agents/database-reviewer.md +16 -2
  66. package/templates/claude-code/agents/deep-reviewer.md +191 -0
  67. package/templates/claude-code/agents/doc-updater.md +19 -5
  68. package/templates/claude-code/agents/docs-lookup.md +19 -5
  69. package/templates/claude-code/agents/e2e-runner.md +26 -12
  70. package/templates/claude-code/agents/enforcement-gate.md +102 -0
  71. package/templates/claude-code/agents/frontend-builder.md +188 -0
  72. package/templates/claude-code/agents/harness-optimizer.md +61 -0
  73. package/templates/claude-code/agents/loop-operator.md +27 -12
  74. package/templates/claude-code/agents/planner.md +21 -7
  75. package/templates/claude-code/agents/product-strategist.md +138 -0
  76. package/templates/claude-code/agents/production-readiness.md +14 -0
  77. package/templates/claude-code/agents/prompt-auditor.md +115 -0
  78. package/templates/claude-code/agents/refactor-cleaner.md +22 -8
  79. package/templates/claude-code/agents/security-reviewer.md +15 -0
  80. package/templates/claude-code/agents/spec-validator.md +45 -1
  81. package/templates/claude-code/agents/tdd-guide.md +21 -7
  82. package/templates/claude-code/agents/uat-validator.md +18 -0
  83. package/templates/claude-code/claude-md/base.md +15 -7
  84. package/templates/claude-code/claude-md/fastapi.md +8 -8
  85. package/templates/claude-code/claude-md/fullstack.md +6 -6
  86. package/templates/claude-code/claude-md/hono.md +18 -0
  87. package/templates/claude-code/claude-md/nextjs.md +5 -5
  88. package/templates/claude-code/claude-md/remix.md +18 -0
  89. package/templates/claude-code/commands/audit-security.md +14 -0
  90. package/templates/claude-code/commands/audit-spec.md +14 -0
  91. package/templates/claude-code/commands/audit-wiring.md +14 -0
  92. package/templates/claude-code/commands/build-fix.md +28 -0
  93. package/templates/claude-code/commands/build-ui.md +59 -0
  94. package/templates/claude-code/commands/code-review.md +54 -26
  95. package/templates/claude-code/commands/fix-loop.md +211 -0
  96. package/templates/claude-code/commands/full-audit.md +37 -8
  97. package/templates/claude-code/commands/generate-prd.md +1 -1
  98. package/templates/claude-code/commands/generate-sdd.md +74 -0
  99. package/templates/claude-code/commands/generate-uat.md +107 -35
  100. package/templates/claude-code/commands/help.md +68 -0
  101. package/templates/claude-code/commands/live-uat.md +268 -0
  102. package/templates/claude-code/commands/optimize-claude-md.md +15 -1
  103. package/templates/claude-code/commands/plan.md +3 -3
  104. package/templates/claude-code/commands/pre-pr.md +57 -19
  105. package/templates/claude-code/commands/product-strategist.md +21 -0
  106. package/templates/claude-code/commands/resume-session.md +10 -10
  107. package/templates/claude-code/commands/run-uat.md +59 -2
  108. package/templates/claude-code/commands/save-session.md +10 -10
  109. package/templates/claude-code/commands/simplify.md +36 -0
  110. package/templates/claude-code/commands/tdd.md +17 -18
  111. package/templates/claude-code/commands/verify-all.md +24 -0
  112. package/templates/claude-code/commands/verify-intent.md +55 -0
  113. package/templates/claude-code/commands/workflows.md +52 -37
  114. package/templates/claude-code/hooks/polyglot.json +10 -1
  115. package/templates/claude-code/hooks/python.json +10 -1
  116. package/templates/claude-code/hooks/scripts/autofix-polyglot.mjs +20 -10
  117. package/templates/claude-code/hooks/scripts/autofix-python.mjs +4 -5
  118. package/templates/claude-code/hooks/scripts/autofix-typescript.mjs +4 -4
  119. package/templates/claude-code/hooks/scripts/code-hygiene.mjs +293 -0
  120. package/templates/claude-code/hooks/scripts/guard-protected-files.mjs +2 -2
  121. package/templates/claude-code/hooks/scripts/pre-commit-gate.mjs +207 -0
  122. package/templates/claude-code/hooks/typescript.json +10 -1
  123. package/templates/claude-code/skills/ai-prompts/SKILL.md +119 -41
  124. package/templates/claude-code/skills/git-workflow/SKILL.md +6 -6
  125. package/templates/claude-code/skills/nextjs/SKILL.md +1 -1
  126. package/templates/claude-code/skills/playwright/SKILL.md +6 -5
  127. package/templates/claude-code/skills/security-api/SKILL.md +1 -1
  128. package/templates/claude-code/skills/security-web/SKILL.md +2 -1
  129. package/templates/claude-code/skills/testing-patterns/SKILL.md +9 -9
  130. package/templates/database/prisma-postgres/{.env.example → .env.example.template} +1 -0
  131. package/templates/database/sqlalchemy-postgres/{.env.example → .env.example.template} +1 -0
  132. package/templates/docs-portal/fastapi/backend/app/portal/__init__.py +0 -0
  133. package/templates/docs-portal/fastapi/backend/app/portal/__pycache__/docs_reader.cpython-314.pyc +0 -0
  134. package/templates/docs-portal/fastapi/backend/app/portal/docs_reader.py +201 -0
  135. package/templates/docs-portal/fastapi/backend/app/portal/html_renderer.py +229 -0
  136. package/templates/docs-portal/fastapi/backend/app/portal/router.py.template +35 -0
  137. package/templates/docs-portal/nextjs/src/app/portal/[category]/[slug]/page.tsx +81 -0
  138. package/templates/docs-portal/nextjs/src/app/portal/[category]/page.tsx +65 -0
  139. package/templates/docs-portal/nextjs/src/app/portal/layout.tsx.template +54 -0
  140. package/templates/docs-portal/nextjs/src/app/portal/page.tsx +85 -0
  141. package/templates/docs-portal/nextjs/src/components/portal/markdown-renderer.tsx +101 -0
  142. package/templates/docs-portal/nextjs/src/components/portal/mobile-portal-nav.tsx +81 -0
  143. package/templates/docs-portal/nextjs/src/components/portal/portal-nav.tsx +86 -0
  144. package/templates/docs-portal/nextjs/src/lib/docs.ts +139 -0
  145. package/templates/frontend/nextjs/package.json.template +3 -1
  146. package/templates/frontend/react/index.html.template +12 -0
  147. package/templates/frontend/react/package.json.template +34 -0
  148. package/templates/frontend/react/src/App.tsx.template +10 -0
  149. package/templates/frontend/react/src/index.css +1 -0
  150. package/templates/frontend/react/src/main.tsx +10 -0
  151. package/templates/frontend/react/tsconfig.json +17 -0
  152. package/templates/frontend/react/vite.config.ts.template +15 -0
  153. package/templates/frontend/react/vitest.config.ts +9 -0
  154. package/templates/frontend/remix/app/root.tsx.template +31 -0
  155. package/templates/frontend/remix/app/routes/_index.tsx.template +19 -0
  156. package/templates/frontend/remix/app/routes/api.health.ts.template +10 -0
  157. package/templates/frontend/remix/app/tailwind.css +1 -0
  158. package/templates/frontend/remix/package.json.template +39 -0
  159. package/templates/frontend/remix/tsconfig.json +18 -0
  160. package/templates/frontend/remix/vite.config.ts.template +7 -0
  161. package/templates/infra/github-actions/.github/workflows/ci.yml.template +52 -0
  162. package/templates/testing/pytest/backend/tests/__init__.py +0 -0
  163. package/templates/testing/pytest/backend/tests/conftest.py.template +11 -0
  164. package/templates/testing/pytest/backend/tests/test_health.py.template +10 -0
  165. package/templates/testing/vitest/vitest.config.ts.template +18 -0
  166. package/CLAUDE.md +0 -38
  167. package/templates/claude-code/commands/done.md +0 -19
@@ -1,44 +1,122 @@
1
1
  ---
2
- name: AI Prompts
3
- description: AI/LLM integration patterns and best practices
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
- ## Structured Output
9
- - Always validate AI responses with Pydantic (Python) or Zod (TypeScript)
10
- - Never use raw string responses in application logic
11
- - Define response schemas before making API calls
12
- - Handle malformed responses gracefully
13
-
14
- ## Prompt Engineering
15
- - Use system prompts for consistent behavior
16
- - Include examples (few-shot) for complex tasks
17
- - Be specific about output format
18
- - Test prompts with edge cases
19
-
20
- ## Failover Patterns
21
- - Implement rule-based fallback when AI is unavailable
22
- - Set aggressive timeouts (30-60s for most calls)
23
- - Retry with exponential backoff (max 3 attempts)
24
- - Cache responses when appropriate
25
- - Monitor token usage and costs
26
-
27
- ## Rate Limiting
28
- - Implement client-side rate limiting before API calls
29
- - Queue requests during high load
30
- - Use batch APIs when processing multiple items
31
- - Handle 429 responses gracefully
32
-
33
- ## Security
34
- - Never include user secrets in prompts
35
- - Sanitize user input before including in prompts
36
- - Protect against prompt injection attacks (delimiter tokens, input validation, output filtering)
37
- - Validate and sanitize AI output before using
38
- - Don't trust AI output for security decisions
39
-
40
- ## Testing
41
- - Use golden datasets for regression testing
42
- - Mock AI responses in unit tests
43
- - Test timeout and error handling paths
44
- - Monitor response quality over time
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` 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
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 commits that have been pushed and shared
63
- - Never merge main into feature branches (rebase instead)
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
- - Don't use `useEffect` for data fetching in Server Components
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
- ## Selectors
15
- - Prefer `data-testid` attributes: `page.getByTestId('submit-btn')`
16
- - Use accessible selectors: `page.getByRole('button', { name: 'Submit' })`
17
- - Use text selectors: `page.getByText('Welcome')`
18
- - Avoid CSS selectors they break when styles change
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 never raw SQL strings
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 never use `dangerouslySetInnerHTML`
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 test pyramid, AAA pattern, mocking strategies, and coverage targets
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 \ Few, slow, high confidence
10
- / Integration \ Some, medium speed
11
- / Unit Tests \— Many, fast, focused
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 set up test data
24
+ // Arrange: set up test data
25
25
  const items = [{ price: 10 }, { price: 20 }];
26
26
  const taxRate = 0.1;
27
27
 
28
- // Act execute the function
28
+ // Act: execute the function
29
29
  const total = calculateTotal(items, taxRate);
30
30
 
31
- // Assert verify the result
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 — they're slow and unreliable |
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 high coverage with weak assertions is useless
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
@@ -1 +1,2 @@
1
+ # Local development only. Change credentials before deploying to production.
1
2
  DATABASE_URL="postgresql://postgres:postgres@localhost:5432/{{PROJECT_NAME_SNAKE}}"
@@ -1,2 +1,3 @@
1
+ # Local development only. Change credentials before deploying to production.
1
2
  DATABASE_URL="postgresql+asyncpg://postgres:postgres@localhost:5432/{{PROJECT_NAME_SNAKE}}"
2
3
  JWT_SECRET_KEY="change-me-generate-a-random-secret"
@@ -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