cc-codeconductor 0.2.1

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 (93) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +319 -0
  3. package/dist/index.js +12741 -0
  4. package/package.json +51 -0
  5. package/policy.yml +124 -0
  6. package/presets/claude/CLAUDE.md +598 -0
  7. package/presets/claude/commands/cc/feature.md +115 -0
  8. package/presets/claude/commands/cc/fix.md +121 -0
  9. package/presets/claude/commands/cc/refactor.md +148 -0
  10. package/presets/claude/commands/cc/review.md +126 -0
  11. package/presets/claude/commands/cc/tdd-cycle.md +226 -0
  12. package/presets/claude/commands/cc/test-plan.md +138 -0
  13. package/presets/claude/settings.json +37 -0
  14. package/presets/claude/skills/api-versioning/SKILL.md +389 -0
  15. package/presets/claude/skills/django-orm/SKILL.md +455 -0
  16. package/presets/claude/skills/django-testing/SKILL.md +409 -0
  17. package/presets/claude/skills/jpa-postgres/SKILL.md +618 -0
  18. package/presets/claude/skills/python/SKILL.md +606 -0
  19. package/presets/claude/skills/python-django-stack/SKILL.md +492 -0
  20. package/presets/claude/skills/python-fastapi-stack/SKILL.md +457 -0
  21. package/presets/claude/skills/spring-boot-feature/SKILL.md +558 -0
  22. package/presets/claude/skills/spring-boot-kotlin/SKILL.md +400 -0
  23. package/presets/claude/skills/sqlalchemy/SKILL.md +466 -0
  24. package/presets/claude/skills/testing-strategy/SKILL.md +479 -0
  25. package/presets/codex/AGENTS.md +883 -0
  26. package/presets/codex/README.md +102 -0
  27. package/presets/codex/skills/api-versioning/SKILL.md +389 -0
  28. package/presets/codex/skills/django-orm/SKILL.md +455 -0
  29. package/presets/codex/skills/django-testing/SKILL.md +409 -0
  30. package/presets/codex/skills/jpa-postgres/SKILL.md +618 -0
  31. package/presets/codex/skills/python/SKILL.md +606 -0
  32. package/presets/codex/skills/python-django-stack/SKILL.md +492 -0
  33. package/presets/codex/skills/python-fastapi-stack/SKILL.md +457 -0
  34. package/presets/codex/skills/spring-boot-feature/SKILL.md +558 -0
  35. package/presets/codex/skills/spring-boot-kotlin/SKILL.md +400 -0
  36. package/presets/codex/skills/sqlalchemy/SKILL.md +466 -0
  37. package/presets/codex/skills/testing-strategy/SKILL.md +479 -0
  38. package/presets/opencode/README.md +191 -0
  39. package/presets/opencode/agents/architect.md +133 -0
  40. package/presets/opencode/agents/docs.md +113 -0
  41. package/presets/opencode/agents/implementer.md +116 -0
  42. package/presets/opencode/agents/orchestrator.md +248 -0
  43. package/presets/opencode/agents/repo-explorer.md +119 -0
  44. package/presets/opencode/agents/reviewer.md +130 -0
  45. package/presets/opencode/agents/task-coach.md +103 -0
  46. package/presets/opencode/agents/tester.md +269 -0
  47. package/presets/opencode/commands/cc-feature.md +114 -0
  48. package/presets/opencode/commands/cc-fix.md +123 -0
  49. package/presets/opencode/commands/cc-refactor.md +148 -0
  50. package/presets/opencode/commands/cc-review.md +141 -0
  51. package/presets/opencode/commands/cc-tdd-cycle.md +225 -0
  52. package/presets/opencode/commands/cc-test-plan.md +144 -0
  53. package/presets/opencode/opencode.jsonc +89 -0
  54. package/presets/opencode/prompts/v0.1.0/architect.md +213 -0
  55. package/presets/opencode/prompts/v0.1.0/docs.md +181 -0
  56. package/presets/opencode/prompts/v0.1.0/implementer.md +154 -0
  57. package/presets/opencode/prompts/v0.1.0/orchestrator.md +169 -0
  58. package/presets/opencode/prompts/v0.1.0/repo-explorer.md +102 -0
  59. package/presets/opencode/prompts/v0.1.0/reviewer.md +183 -0
  60. package/presets/opencode/prompts/v0.1.0/task-coach.md +142 -0
  61. package/presets/opencode/prompts/v0.1.0/tester.md +160 -0
  62. package/presets/opencode/prompts/v0.2.0/architect.md +219 -0
  63. package/presets/opencode/prompts/v0.2.0/docs.md +187 -0
  64. package/presets/opencode/prompts/v0.2.0/implementer.md +160 -0
  65. package/presets/opencode/prompts/v0.2.0/orchestrator.md +238 -0
  66. package/presets/opencode/prompts/v0.2.0/repo-explorer.md +108 -0
  67. package/presets/opencode/prompts/v0.2.0/reviewer.md +190 -0
  68. package/presets/opencode/prompts/v0.2.0/task-coach.md +153 -0
  69. package/presets/opencode/prompts/v0.2.0/tester.md +249 -0
  70. package/presets/opencode/skills/api-versioning/SKILL.md +388 -0
  71. package/presets/opencode/skills/astro/SKILL.md +313 -0
  72. package/presets/opencode/skills/code-review/SKILL.md +202 -0
  73. package/presets/opencode/skills/django-orm/SKILL.md +455 -0
  74. package/presets/opencode/skills/django-testing/SKILL.md +408 -0
  75. package/presets/opencode/skills/django-uv/SKILL.md +400 -0
  76. package/presets/opencode/skills/jpa-postgres/SKILL.md +617 -0
  77. package/presets/opencode/skills/nextjs-typescript/SKILL.md +385 -0
  78. package/presets/opencode/skills/python/SKILL.md +605 -0
  79. package/presets/opencode/skills/python-django-stack/SKILL.md +491 -0
  80. package/presets/opencode/skills/python-fastapi-stack/SKILL.md +456 -0
  81. package/presets/opencode/skills/security/SKILL.md +376 -0
  82. package/presets/opencode/skills/spring-boot-feature/SKILL.md +557 -0
  83. package/presets/opencode/skills/spring-boot-kotlin/SKILL.md +399 -0
  84. package/presets/opencode/skills/spring-boot-testing-strategy/SKILL.md +470 -0
  85. package/presets/opencode/skills/sqlalchemy/SKILL.md +465 -0
  86. package/presets/opencode/skills/testing-tdd/SKILL.md +586 -0
  87. package/src/presets/council/council.yml +59 -0
  88. package/src/presets/manifests/claude.yml +23 -0
  89. package/src/presets/manifests/codex.yml +12 -0
  90. package/src/presets/manifests/opencode.yml +18 -0
  91. package/src/presets/models/claude.yml +37 -0
  92. package/src/presets/models/codex.yml +38 -0
  93. package/src/presets/models/opencode.yml +37 -0
@@ -0,0 +1,388 @@
1
+ ---
2
+ id: api-versioning
3
+ version: 1.0.0
4
+ name: API Versioning
5
+ description:
6
+ Provides expert knowledge for designing, implementing, and managing REST API
7
+ versioning strategies with deprecation workflows.
8
+
9
+ compatibility:
10
+ tools: [claude, codex, opencode]
11
+ stacks:
12
+ languages: [kotlin, java, typescript, python, go]
13
+ frameworks: [spring-boot, spring-mvc, express, fastapi]
14
+
15
+ risk:
16
+ level: high
17
+ can_execute_shell: false
18
+ can_modify_files: true
19
+ requires_network: false
20
+
21
+ inputs:
22
+ - source_files
23
+ - openapi spec files
24
+ - existing controller classes
25
+
26
+ outputs:
27
+ - versioned controller classes
28
+ - OpenAPI spec updates
29
+ - deprecation headers
30
+ - changelog entries
31
+ - contract test scaffolding
32
+
33
+ quality:
34
+ reviewed_by: codeconductor-core
35
+ version: 0.1.0
36
+ ---
37
+ # API Versioning
38
+
39
+ ## Versioning Strategies
40
+
41
+ ### URL Path Versioning (recommended for breaking changes)
42
+
43
+ ```text
44
+ GET /api/v1/users
45
+ GET /api/v2/users
46
+ ```
47
+
48
+ Tradeoffs:
49
+
50
+ - Explicit and visible in logs, proxies, and browser history
51
+ - Easy to cache at the CDN level — the URL uniquely identifies the resource
52
+ version
53
+ - Easy to route at the load balancer
54
+ - Results in some duplication of controller code
55
+ - Changing the URL violates REST HATEOAS principles, though in practice this is
56
+ acceptable
57
+
58
+ Use this when: you have breaking changes and need maximum visibility and
59
+ cacheability.
60
+
61
+ ### Header Versioning
62
+
63
+ ```text
64
+ GET /api/users
65
+ Accept: application/vnd.myapp+json;version=1
66
+ ```
67
+
68
+ Tradeoffs:
69
+
70
+ - Cleaner URLs
71
+ - Harder to test manually — browsers and curl require extra flags
72
+ - Cannot be bookmarked or linked directly
73
+ - CDN caching requires `Vary: Accept` header, which reduces cache hit rates
74
+
75
+ Use this when: you need clean URLs and your clients are all programmatic (no
76
+ browsers).
77
+
78
+ ### Query Parameter Versioning (avoid)
79
+
80
+ ```text
81
+ GET /api/users?version=1
82
+ ```
83
+
84
+ This approach contaminates resource URLs with transport concerns. The version is
85
+ not part of the resource identity. Do not use it. The only valid exception is
86
+ temporary backward-compat support during a migration window.
87
+
88
+ ## When to Version
89
+
90
+ Version when the change is breaking. Not every change requires a version bump.
91
+
92
+ **Breaking — requires new version:**
93
+
94
+ - Removing a field from a response
95
+ - Renaming a field
96
+ - Changing a field's type (e.g., `string` to `object`)
97
+ - Changing the meaning of an existing field
98
+ - Removing an endpoint
99
+ - Changing required fields in a request
100
+ - Changing status codes in a non-additive way
101
+
102
+ **Not breaking — no version bump needed:**
103
+
104
+ - Adding an optional field to a response
105
+ - Adding a new endpoint
106
+ - Adding an optional request parameter
107
+ - Deprecating a field (marking it, but still returning it)
108
+ - Performance improvements
109
+ - Bug fixes that restore documented behavior
110
+
111
+ ## Deprecation Process
112
+
113
+ When a version or endpoint is being phased out, follow this process:
114
+
115
+ **Step 1: Mark in OpenAPI.**
116
+
117
+ ```yaml
118
+ paths:
119
+ /api/v1/users/{id}:
120
+ get:
121
+ deprecated: true
122
+ description: |
123
+ Deprecated since 2026-05-07. Use /api/v2/users/{id} instead.
124
+ Sunset date: 2026-11-07.
125
+ summary: Get user by ID (deprecated)
126
+ ```
127
+
128
+ **Step 2: Add deprecation headers to responses.**
129
+
130
+ ```kotlin
131
+ @GetMapping("/{id}")
132
+ fun getUserV1(@PathVariable id: UUID, response: HttpServletResponse): ResponseEntity<UserV1Response> {
133
+ response.addHeader("Deprecation", "date=\"Wed, 07 May 2026 00:00:00 GMT\"")
134
+ response.addHeader("Sunset", "Mon, 07 Nov 2026 00:00:00 GMT")
135
+ response.addHeader("Link", "</api/v2/users/$id>; rel=\"successor-version\"")
136
+ return ResponseEntity.ok(userService.getById(id).toV1Response())
137
+ }
138
+ ```
139
+
140
+ **Step 3: Document in CHANGELOG.**
141
+
142
+ ```markdown
143
+ ## Deprecated
144
+
145
+ - `GET /api/v1/users/{id}` — deprecated in favor of `GET /api/v2/users/{id}`.
146
+ Sunset: 2026-11-07.
147
+ ```
148
+
149
+ **Step 4: Maintain dual support.**
150
+
151
+ Keep at least two active major versions at all times. When v3 ships, v1 can be
152
+ removed (v2 and v3 remain active).
153
+
154
+ **Step 5: Communicate the sunset date.**
155
+
156
+ Notify consumers before the sunset date through:
157
+
158
+ - API changelog
159
+ - Developer portal announcements
160
+ - Deprecation headers (machine-readable)
161
+ - Direct contact if you have consumer registration data
162
+
163
+ Do not remove a version without a minimum 6-month notice period. 3 months is the
164
+ absolute minimum if forced.
165
+
166
+ ## OpenAPI Conventions
167
+
168
+ ### One file per version (simple cases)
169
+
170
+ ```text
171
+ openapi-v1.yaml
172
+ openapi-v2.yaml
173
+ ```
174
+
175
+ Each file is self-contained and independently valid.
176
+
177
+ ### Single file with version in info (evolving APIs)
178
+
179
+ ```yaml
180
+ openapi: '3.1.0'
181
+ info:
182
+ title: Users API
183
+ version: '2.0.0'
184
+ ```
185
+
186
+ Use `$ref` to share schemas across versions without duplication.
187
+
188
+ ### Cross-version schema reuse
189
+
190
+ ```yaml
191
+ # schemas/user-base.yaml
192
+ UserBase:
193
+ type: object
194
+ properties:
195
+ id:
196
+ type: string
197
+ format: uuid
198
+ email:
199
+ type: string
200
+
201
+ # openapi-v1.yaml
202
+ components:
203
+ schemas:
204
+ UserResponse:
205
+ allOf:
206
+ - $ref: './schemas/user-base.yaml#/UserBase'
207
+ - properties:
208
+ full_name:
209
+ type: string
210
+
211
+ # openapi-v2.yaml — splits full_name into first_name + last_name
212
+ components:
213
+ schemas:
214
+ UserResponse:
215
+ allOf:
216
+ - $ref: './schemas/user-base.yaml#/UserBase'
217
+ - properties:
218
+ first_name:
219
+ type: string
220
+ last_name:
221
+ type: string
222
+ ```
223
+
224
+ ### Documenting breaking changes
225
+
226
+ Put the breaking change in the endpoint description, not just in a changelog:
227
+
228
+ ```yaml
229
+ /api/v2/users/{id}:
230
+ get:
231
+ description: |
232
+ Returns user details.
233
+
234
+ Breaking changes from v1:
235
+ - `full_name` has been replaced by `first_name` and `last_name`
236
+ ```
237
+
238
+ ## Spring Boot Implementation
239
+
240
+ ### URL path versioning
241
+
242
+ ```kotlin
243
+ // V1 controller — never modify once published
244
+ @RestController
245
+ @RequestMapping("/api/v1/users")
246
+ class UserV1Controller(private val userService: UserService) {
247
+
248
+ @GetMapping("/{id}")
249
+ @Deprecated("Use /api/v2/users/{id}", ReplaceWith("UserV2Controller.getUser()"))
250
+ fun getUser(
251
+ @PathVariable id: UUID,
252
+ response: HttpServletResponse
253
+ ): ResponseEntity<UserV1Response> {
254
+ response.addHeader("Deprecation", "date=\"Wed, 07 May 2026 00:00:00 GMT\"")
255
+ response.addHeader("Sunset", "Mon, 07 Nov 2026 00:00:00 GMT")
256
+ return when (val result = userService.getById(id)) {
257
+ is UserResult.Found -> ResponseEntity.ok(result.user.toV1Response())
258
+ is UserResult.NotFound -> ResponseEntity.notFound().build()
259
+ }
260
+ }
261
+ }
262
+
263
+ // V2 controller — new version, new controller, shared service
264
+ @RestController
265
+ @RequestMapping("/api/v2/users")
266
+ class UserV2Controller(private val userService: UserService) {
267
+
268
+ @GetMapping("/{id}")
269
+ fun getUser(@PathVariable id: UUID): ResponseEntity<UserV2Response> {
270
+ return when (val result = userService.getById(id)) {
271
+ is UserResult.Found -> ResponseEntity.ok(result.user.toV2Response())
272
+ is UserResult.NotFound -> ResponseEntity.notFound().build()
273
+ }
274
+ }
275
+ }
276
+ ```
277
+
278
+ Rules:
279
+
280
+ - Create a new controller for each new version — do not modify the existing one
281
+ - The service layer is shared across versions — only the controller and DTO
282
+ change
283
+ - DTO mapper functions are version-specific: `toV1Response()`, `toV2Response()`
284
+ - Never delete a versioned controller until after the sunset date
285
+
286
+ ### DTO versioning
287
+
288
+ ```kotlin
289
+ // V1 — original shape
290
+ data class UserV1Response(
291
+ val id: UUID,
292
+ val email: String,
293
+ val full_name: String
294
+ )
295
+
296
+ // V2 — breaking change: split full_name
297
+ data class UserV2Response(
298
+ val id: UUID,
299
+ val email: String,
300
+ val first_name: String,
301
+ val last_name: String
302
+ )
303
+
304
+ // Extension functions for mapping
305
+ fun User.toV1Response(): UserV1Response = UserV1Response(
306
+ id = id,
307
+ email = email,
308
+ full_name = "$firstName $lastName"
309
+ )
310
+
311
+ fun User.toV2Response(): UserV2Response = UserV2Response(
312
+ id = id,
313
+ email = email,
314
+ first_name = firstName,
315
+ last_name = lastName
316
+ )
317
+ ```
318
+
319
+ ## Contract Testing
320
+
321
+ Contract tests verify that your API does not break existing consumers before
322
+ changes reach production.
323
+
324
+ **When to run:** in CI, before merging any change that touches a controller,
325
+ DTO, or OpenAPI spec.
326
+
327
+ **Tool: Pact (consumer-driven contracts)**
328
+
329
+ Consumer writes a pact:
330
+
331
+ ```kotlin
332
+ // In the consumer service test
333
+ @ExtendWith(PactConsumerTestExt::class)
334
+ class UserServiceConsumerTest {
335
+
336
+ @Pact(consumer = "order-service", provider = "user-service")
337
+ fun getUserPact(builder: PactDslWithProvider): RequestResponsePact {
338
+ return builder
339
+ .given("user with id exists")
340
+ .uponReceiving("a request for user by id")
341
+ .path("/api/v1/users/123e4567-e89b-12d3-a456-426614174000")
342
+ .method("GET")
343
+ .willRespondWith()
344
+ .status(200)
345
+ .body(LambdaDsl.newJsonBody { body ->
346
+ body.uuid("id")
347
+ body.stringType("email")
348
+ body.stringType("full_name")
349
+ }.build())
350
+ .toPact()
351
+ }
352
+ }
353
+ ```
354
+
355
+ Provider verifies the pact:
356
+
357
+ ```kotlin
358
+ @Provider("user-service")
359
+ @PactFolder("pacts")
360
+ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
361
+ class UserServiceProviderTest {
362
+
363
+ @TestTarget
364
+ lateinit var target: HttpTestTarget
365
+
366
+ @BeforeEach
367
+ fun setUp(@LocalServerPort port: Int) {
368
+ target = HttpTestTarget("localhost", port)
369
+ }
370
+ }
371
+ ```
372
+
373
+ **Rule:** run contract tests in CI before any merge that touches an API surface.
374
+ A broken contract test means a consumer will break in production.
375
+
376
+ ## Test Structure Per Version
377
+
378
+ Each API version must have its own test class:
379
+
380
+ ```text
381
+ src/test/kotlin/{package}/user/
382
+ controller/
383
+ UserV1ControllerTest.kt # tests for v1 endpoints
384
+ UserV2ControllerTest.kt # tests for v2 endpoints
385
+ ```
386
+
387
+ Do not share test cases across versions. V1 behavior must be tested
388
+ independently from V2 — they can diverge.
@@ -0,0 +1,313 @@
1
+ ---
2
+ id: astro
3
+ version: 1.0.0
4
+ name: Astro
5
+ description: >
6
+ Provides expert knowledge for building Astro 5+ sites with Islands Architecture, Content Collections, TypeScript, and performance-first rendering strategies.
7
+
8
+ compatibility:
9
+ tools: [claude, codex, opencode]
10
+ stacks:
11
+ languages: []
12
+ frameworks: []
13
+
14
+ risk:
15
+ level: low
16
+ can_execute_shell: false
17
+ can_modify_files: true
18
+ requires_network: false
19
+
20
+ inputs: []
21
+
22
+ outputs: []
23
+
24
+ quality:
25
+ reviewed_by: codeconductor-core
26
+ version: 0.1.0
27
+ ---
28
+
29
+
30
+
31
+ # Astro
32
+
33
+ ## Islands Architecture
34
+
35
+ Astro renders everything to static HTML by default. JavaScript ships only for
36
+ components that explicitly opt in — these are called Islands.
37
+
38
+ ### Hydration Directives
39
+
40
+ | Directive | When JS loads | Use case |
41
+ |-----------|--------------|----------|
42
+ | `client:load` | On page load | Interactive above-the-fold UI |
43
+ | `client:idle` | When browser is idle | Non-critical interactive widgets |
44
+ | `client:visible` | When element enters viewport | Below-the-fold islands |
45
+ | `client:media` | When CSS media query matches | Responsive interactive components |
46
+ | `client:only` | Client-only, no SSR | Components that require the DOM (e.g., charting libs) |
47
+
48
+ ```astro
49
+ ---
50
+ import Counter from '../components/Counter.tsx';
51
+ import HeavyChart from '../components/HeavyChart.tsx';
52
+ import MobileNav from '../components/MobileNav.tsx';
53
+ ---
54
+
55
+ <!-- Hydrates immediately — user interacts right away -->
56
+ <Counter client:load />
57
+
58
+ <!-- Hydrates when scrolled into view — saves initial JS -->
59
+ <HeavyChart client:visible />
60
+
61
+ <!-- Only on mobile, only when query matches -->
62
+ <MobileNav client:media="(max-width: 768px)" />
63
+ ```
64
+
65
+ Rules:
66
+
67
+ - Default to no hydration directive — most UI does not need JavaScript
68
+ - `client:load` is the most expensive directive; use it sparingly
69
+ - `client:only` skips server rendering entirely — the component receives no
70
+ props from the server; pass all data via props or fetch inside the component
71
+ - Do not use `client:load` for components that could use `client:visible`
72
+
73
+ ### Framework Components Inside Astro
74
+
75
+ ```astro
76
+ ---
77
+ import ReactButton from './Button.tsx'; // React island
78
+ import VueWidget from './Widget.vue'; // Vue island
79
+ ---
80
+
81
+ <!-- Both can coexist on the same page -->
82
+ <ReactButton client:idle label="Click me" />
83
+ <VueWidget client:visible />
84
+ ```
85
+
86
+ Each framework ships its own runtime only when at least one island of that
87
+ framework is on the page.
88
+
89
+ ## Content Collections
90
+
91
+ Content Collections provide type-safe access to Markdown, MDX, and data files.
92
+ Define schemas in `src/content/config.ts`.
93
+
94
+ ### Schema Definition
95
+
96
+ ```typescript
97
+ // src/content/config.ts
98
+ import { defineCollection, z } from 'astro:content';
99
+
100
+ const blog = defineCollection({
101
+ type: 'content', // .md or .mdx files
102
+ schema: z.object({
103
+ title: z.string(),
104
+ description: z.string(),
105
+ pubDate: z.coerce.date(),
106
+ updatedDate: z.coerce.date().optional(),
107
+ author: z.string().default('Anonymous'),
108
+ tags: z.array(z.string()).default([]),
109
+ draft: z.boolean().default(false),
110
+ image: z.object({
111
+ src: z.string(),
112
+ alt: z.string(),
113
+ }).optional(),
114
+ }),
115
+ });
116
+
117
+ const docs = defineCollection({
118
+ type: 'content',
119
+ schema: z.object({
120
+ title: z.string(),
121
+ order: z.number(),
122
+ section: z.enum(['guide', 'reference', 'tutorial']),
123
+ }),
124
+ });
125
+
126
+ export const collections = { blog, docs };
127
+ ```
128
+
129
+ ### Querying Collections
130
+
131
+ ```astro
132
+ ---
133
+ import { getCollection, getEntry } from 'astro:content';
134
+
135
+ // All published posts, sorted by date
136
+ const posts = (await getCollection('blog', ({ data }) => !data.draft))
137
+ .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
138
+
139
+ // Single entry by slug
140
+ const post = await getEntry('blog', 'my-first-post');
141
+ const { Content } = await post.render();
142
+ ---
143
+
144
+ {posts.map(post => (
145
+ <article>
146
+ <h2><a href={`/blog/${post.slug}`}>{post.data.title}</a></h2>
147
+ <time>{post.data.pubDate.toLocaleDateString()}</time>
148
+ </article>
149
+ ))}
150
+ ```
151
+
152
+ ### Dynamic Routes from Collections
153
+
154
+ ```astro
155
+ ---
156
+ // src/pages/blog/[slug].astro
157
+ import { getCollection } from 'astro:content';
158
+
159
+ export async function getStaticPaths() {
160
+ const posts = await getCollection('blog');
161
+ return posts.map(post => ({
162
+ params: { slug: post.slug },
163
+ props: { post },
164
+ }));
165
+ }
166
+
167
+ const { post } = Astro.props;
168
+ const { Content, headings } = await post.render();
169
+ ---
170
+
171
+ <article>
172
+ <h1>{post.data.title}</h1>
173
+ <Content />
174
+ </article>
175
+ ```
176
+
177
+ ## Rendering Strategies
178
+
179
+ ### SSG (Static Site Generation) — default
180
+
181
+ Every page is pre-rendered at build time. Best for content that does not change
182
+ per request.
183
+
184
+ ```javascript
185
+ // astro.config.mjs — no output config needed; SSG is the default
186
+ export default defineConfig({
187
+ site: 'https://example.com',
188
+ });
189
+ ```
190
+
191
+ ### SSR (Server-Side Rendering)
192
+
193
+ Renders pages on each request. Required for: authenticated routes, personalized
194
+ content, live data.
195
+
196
+ ```javascript
197
+ // astro.config.mjs
198
+ import node from '@astrojs/node';
199
+
200
+ export default defineConfig({
201
+ output: 'server',
202
+ adapter: node({ mode: 'standalone' }),
203
+ });
204
+ ```
205
+
206
+ ### Hybrid Mode
207
+
208
+ Mix SSG and SSR on a per-page basis. Most pages are static; specific routes opt
209
+ into server rendering.
210
+
211
+ ```javascript
212
+ // astro.config.mjs
213
+ export default defineConfig({
214
+ output: 'hybrid',
215
+ adapter: node({ mode: 'standalone' }),
216
+ });
217
+ ```
218
+
219
+ ```astro
220
+ ---
221
+ // src/pages/dashboard.astro — this page is server-rendered
222
+ export const prerender = false;
223
+
224
+ // src/pages/about.astro — this page is statically generated (hybrid default)
225
+ export const prerender = true;
226
+ ---
227
+ ```
228
+
229
+ Use hybrid mode when: most content is static but a few routes need auth or
230
+ live data. Do not make everything `output: 'server'` — you lose the performance
231
+ benefits of static generation.
232
+
233
+ ## Image Optimization
234
+
235
+ Use the built-in `<Image>` and `<Picture>` components. Never use raw `<img>`
236
+ for local assets — you lose automatic optimization.
237
+
238
+ ```astro
239
+ ---
240
+ import { Image, Picture } from 'astro:assets';
241
+ import heroImage from '../assets/hero.png';
242
+ ---
243
+
244
+ <!-- Optimized single image -->
245
+ <Image
246
+ src={heroImage}
247
+ alt="Hero illustration"
248
+ width={800}
249
+ height={600}
250
+ format="webp"
251
+ quality={80}
252
+ />
253
+
254
+ <!-- Responsive with multiple formats -->
255
+ <Picture
256
+ src={heroImage}
257
+ formats={['avif', 'webp']}
258
+ alt="Hero illustration"
259
+ widths={[400, 800, 1200]}
260
+ sizes="(max-width: 800px) 100vw, 800px"
261
+ />
262
+ ```
263
+
264
+ Rules:
265
+
266
+ - Always provide `alt` — empty string is acceptable only for decorative images
267
+ - Prefer `avif` + `webp` fallback for best compression
268
+ - Use `widths` + `sizes` on above-the-fold images to serve the right size per
269
+ viewport
270
+ - Remote images require explicit `width` and `height` to prevent layout shift
271
+
272
+ ## Project Structure
273
+
274
+ ```text
275
+ src/
276
+ assets/ — images and static assets processed by Astro
277
+ components/ — .astro components (and framework islands)
278
+ content/
279
+ blog/ — .md and .mdx files
280
+ config.ts — collection schemas
281
+ layouts/ — page shell layouts
282
+ pages/ — file-based routing; every file is a route
283
+ styles/ — global CSS
284
+ astro.config.mjs
285
+ tsconfig.json
286
+ ```
287
+
288
+ Colocation rule: put framework island components (`.tsx`, `.vue`) in
289
+ `src/components/`. Do not scatter them in `src/pages/`.
290
+
291
+ ## TypeScript Conventions
292
+
293
+ ```json
294
+ // tsconfig.json — use the strict Astro preset
295
+ {
296
+ "extends": "astro/tsconfigs/strict"
297
+ }
298
+ ```
299
+
300
+ ```astro
301
+ ---
302
+ // Type props explicitly in the frontmatter
303
+ interface Props {
304
+ title: string;
305
+ description?: string;
306
+ tags: string[];
307
+ }
308
+
309
+ const { title, description = '', tags } = Astro.props;
310
+ ---
311
+ ```
312
+
313
+ Astro infers prop types from `interface Props` automatically. Do not use `any`.