@raftlabs/raftstack 1.6.3 → 1.7.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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: database
3
- description: Use when designing database schemas, writing Drizzle ORM code, creating tables, choosing indexes, or when queries are slow and indexes might be missing
3
+ description: Use when writing Drizzle ORM schemas, creating database tables, adding indexes, writing SQL migrations, defining relations, or optimizing PostgreSQL queries. Use for foreign key indexes, JSONB columns, composite indexes, or prepared statements.
4
4
  ---
5
5
 
6
6
  # Database Development
@@ -125,20 +125,66 @@ export const users = pgTable("users", {
125
125
  | Strategy | Use When | Pros | Cons |
126
126
  |----------|----------|------|------|
127
127
  | **UUID** | Distributed systems, public IDs | No collisions, hide growth | Larger, slower indexes |
128
- | **Serial/bigserial** | Single DB, internal IDs | Compact, fast | Exposes growth, sequence issues |
128
+ | **Identity** | Single DB, internal IDs (2025+) | Compact, fast, SQL standard | Exposes growth |
129
129
  | **ULID/NanoID** | Need sortability + randomness | Sortable, compact | Custom generation |
130
130
 
131
131
  ```typescript
132
- // UUID (default recommendation)
132
+ // UUID (default for distributed/public IDs)
133
133
  id: uuid("id").defaultRandom().primaryKey(),
134
134
 
135
- // Serial for internal/analytics tables
136
- id: serial("id").primaryKey(),
135
+ // GOOD: Identity columns (PostgreSQL 10+, preferred over serial)
136
+ id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
137
+
138
+ // For bigint IDs
139
+ id: bigint("id", { mode: "number" }).primaryKey().generatedAlwaysAsIdentity(),
140
+
141
+ // ❌ AVOID: Serial (legacy, has sequence ownership issues)
142
+ id: serial("id").primaryKey(), // Use identity instead
137
143
 
138
144
  // ULID for sortable + random
139
145
  id: varchar("id", { length: 26 }).primaryKey().$default(() => ulid()),
140
146
  ```
141
147
 
148
+ **Why Identity over Serial:**
149
+ - SQL standard (serial is PostgreSQL-specific)
150
+ - No sequence ownership issues during migrations
151
+ - Better `COPY` and `pg_dump` behavior
152
+ - Cannot be overwritten accidentally (`GENERATED ALWAYS`)
153
+
154
+ ### 7. Full-Text Search with GIN
155
+
156
+ For searchable text columns, use PostgreSQL's built-in full-text search:
157
+
158
+ ```typescript
159
+ import { sql } from "drizzle-orm";
160
+ import { index, pgTable, text, tsvector } from "drizzle-orm/pg-core";
161
+
162
+ export const products = pgTable("products", {
163
+ id: uuid("id").defaultRandom().primaryKey(),
164
+ name: varchar("name", { length: 255 }).notNull(),
165
+ description: text("description"),
166
+ // Generated tsvector column for search
167
+ searchVector: tsvector("search_vector").generatedAlwaysAs(
168
+ sql`to_tsvector('english', coalesce(${products.name}, '') || ' ' || coalesce(${products.description}, ''))`
169
+ ),
170
+ }, (table) => ({
171
+ // GIN index for fast full-text search
172
+ searchIdx: index("products_search_idx").using("gin", table.searchVector),
173
+ }));
174
+
175
+ // Query with full-text search
176
+ const results = await db
177
+ .select()
178
+ .from(products)
179
+ .where(sql`${products.searchVector} @@ plainto_tsquery('english', ${query})`);
180
+ ```
181
+
182
+ **Key patterns:**
183
+ - Use `tsvector` for searchable content
184
+ - `generatedAlwaysAs` auto-updates on row changes
185
+ - GIN index makes searches fast (vs sequential scan)
186
+ - `plainto_tsquery` for simple user queries
187
+
142
188
  ## Migration Strategy
143
189
 
144
190
  ### Generate vs Push
@@ -429,11 +475,14 @@ if (duration > 100) {
429
475
 
430
476
  - [Drizzle ORM Documentation](https://orm.drizzle.team/docs/overview) - Relations, prepared statements, migrations
431
477
  - [PostgreSQL Indexes](https://www.postgresql.org/docs/current/indexes.html) - Index types and usage
478
+ - [PostgreSQL Full-Text Search](https://www.postgresql.org/docs/current/textsearch.html) - tsvector, GIN indexes
432
479
  - [Drizzle Kit](https://orm.drizzle.team/docs/kit-overview) - Migration management
433
480
 
434
481
  **Version Notes:**
435
- - Drizzle ORM 0.30+: Improved relations API
482
+ - Drizzle ORM 0.30+: Improved relations API, identity column support
436
483
  - Drizzle Kit 0.20+: Enhanced migration generation
484
+ - PostgreSQL 10+: Identity columns (prefer over serial)
485
+ - PostgreSQL 15+: Improved JSON and full-text performance
437
486
 
438
487
  ## Red Flags - STOP and Add Indexes
439
488
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: react
3
- description: Use when writing React components, using hooks, handling state, working with Next.js/Remix/Vite/Astro, or when components feel bloated, have unnecessary re-renders, or violate single responsibility
3
+ description: Use when writing React components, creating hooks, using useState/useEffect/useReducer, building Next.js pages, implementing Server Components or Client Components, working with Remix loaders, Astro islands, or Vite SPA. Use for React performance issues, re-render problems, or component refactoring.
4
4
  ---
5
5
 
6
6
  # React Development
@@ -229,6 +229,60 @@ export function SignupForm() {
229
229
 
230
230
  ## React 19 Features
231
231
 
232
+ ### React Compiler (Auto-Memoization)
233
+
234
+ React Compiler automatically adds memoization - no more manual `useMemo`, `useCallback`, or `React.memo`.
235
+
236
+ ```typescript
237
+ // ❌ OLD: Manual memoization everywhere
238
+ const MemoizedComponent = React.memo(({ user }) => {
239
+ const formattedName = useMemo(() => formatName(user), [user]);
240
+ const handleClick = useCallback(() => saveUser(user), [user]);
241
+ return <button onClick={handleClick}>{formattedName}</button>;
242
+ });
243
+
244
+ // ✅ NEW: React Compiler handles it automatically
245
+ function UserButton({ user }) {
246
+ const formattedName = formatName(user);
247
+ const handleClick = () => saveUser(user);
248
+ return <button onClick={handleClick}>{formattedName}</button>;
249
+ }
250
+ // Compiler adds memoization during build - 25-40% fewer re-renders
251
+ ```
252
+
253
+ **Key benefits:**
254
+ - No manual memoization needed
255
+ - Reduces bundle size (no memo wrappers)
256
+ - 25-40% fewer re-renders in typical apps
257
+ - Works with existing React 19 codebases
258
+
259
+ **Enable in Next.js 15+:**
260
+ ```javascript
261
+ // next.config.js
262
+ module.exports = {
263
+ experimental: {
264
+ reactCompiler: true,
265
+ },
266
+ };
267
+ ```
268
+
269
+ ### Server/Client Component Decision Tree
270
+
271
+ ```
272
+ Need this in the component?
273
+ ├── Event handlers (onClick, onChange) → 'use client'
274
+ ├── useState, useEffect, useReducer → 'use client'
275
+ ├── Browser APIs (localStorage, window) → 'use client'
276
+ ├── Third-party client libs (charts, maps) → 'use client'
277
+
278
+ ├── Data fetching from DB/API → Server Component ✅
279
+ ├── Access backend resources → Server Component ✅
280
+ ├── Keep sensitive data (tokens, keys) → Server Component ✅
281
+ └── Reduce client bundle → Server Component ✅
282
+ ```
283
+
284
+ **Rule of thumb:** Start with Server Components. Only add 'use client' when you hit a boundary that requires it.
285
+
232
286
  ### use() Hook - Read Promises/Context
233
287
 
234
288
  ```typescript
@@ -279,6 +333,59 @@ function TodoList({ todos }: { todos: Todo[] }) {
279
333
 
280
334
  **Auto-reverts on error** - no manual rollback needed.
281
335
 
336
+ ### Partial Pre-rendering (PPR) - Next.js 15+
337
+
338
+ Combine static shell with dynamic content in a single request:
339
+
340
+ ```typescript
341
+ // app/products/[id]/page.tsx
342
+ import { Suspense } from 'react';
343
+
344
+ // Static shell - pre-rendered at build time
345
+ export default async function ProductPage({ params }) {
346
+ const product = await getProduct(params.id); // Cached/static
347
+
348
+ return (
349
+ <div>
350
+ {/* Static content - instant load */}
351
+ <h1>{product.name}</h1>
352
+ <p>{product.description}</p>
353
+
354
+ {/* Dynamic content - streams in */}
355
+ <Suspense fallback={<PriceSkeleton />}>
356
+ <DynamicPrice productId={params.id} />
357
+ </Suspense>
358
+
359
+ <Suspense fallback={<InventorySkeleton />}>
360
+ <InventoryStatus productId={params.id} />
361
+ </Suspense>
362
+ </div>
363
+ );
364
+ }
365
+
366
+ // Dynamic component - fetches real-time data
367
+ async function DynamicPrice({ productId }) {
368
+ const price = await getCurrentPrice(productId); // Always fresh
369
+ return <span className="price">${price}</span>;
370
+ }
371
+ ```
372
+
373
+ **Enable PPR:**
374
+ ```javascript
375
+ // next.config.js
376
+ module.exports = {
377
+ experimental: {
378
+ ppr: true,
379
+ },
380
+ };
381
+ ```
382
+
383
+ **Benefits:**
384
+ - Static shell loads instantly (like SSG)
385
+ - Dynamic parts stream in (like SSR)
386
+ - Best of both worlds in one request
387
+ - No full-page waterfall
388
+
282
389
  ## Streaming & Suspense
283
390
 
284
391
  ### Full Page Streaming with loading.tsx
@@ -384,12 +491,15 @@ test('shows fallback then content', async () => {
384
491
 
385
492
  - [Next.js Documentation](https://nextjs.org/docs) - App Router, Server Components, Server Actions
386
493
  - [React 19 Release](https://react.dev/blog/2024/12/05/react-19) - use(), useActionState, useOptimistic
494
+ - [React Compiler](https://react.dev/learn/react-compiler) - Auto-memoization setup and usage
387
495
  - [React Server Components](https://react.dev/reference/rsc/server-components) - Official RSC guide
388
496
 
389
497
  **Version Notes:**
390
498
  - Next.js 14+: App Router stable, Server Actions stable
391
- - Next.js 15: Enhanced caching, Turbopack, React 19 support
499
+ - Next.js 15: Turbopack stable, React Compiler support, PPR experimental
392
500
  - React 19: use(), Actions, useOptimistic, useActionState
501
+ - React 19.2+: Partial Pre-rendering (PPR), enhanced Suspense
502
+ - React Compiler: Auto-memoization, 25-40% fewer re-renders
393
503
 
394
504
  ## Red Flags - STOP and Restructure
395
505
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: seo
3
- description: Use when creating web pages, adding metadata, optimizing Core Web Vitals, implementing structured data, or when pages aren't ranking or showing rich snippets in Google
3
+ description: Use when adding page metadata, implementing OpenGraph tags, creating JSON-LD structured data, generating sitemaps, optimizing LCP/INP/CLS, or configuring robots.txt. Use for Next.js Metadata API, Article/Product/FAQ schemas, or image optimization with next/image.
4
4
  ---
5
5
 
6
6
  # SEO Optimization
@@ -128,6 +128,35 @@ input.addEventListener('input', (e) => {
128
128
 
129
129
  ### 2. Use Next.js Metadata API Correctly
130
130
 
131
+ #### Root Layout: metadataBase + title.template
132
+
133
+ **Critical:** Set `metadataBase` in your root layout. Without it, relative OG image URLs break.
134
+
135
+ ```typescript
136
+ // app/layout.tsx
137
+ import { Metadata } from 'next';
138
+
139
+ export const metadata: Metadata = {
140
+ metadataBase: new URL('https://site.com'), // REQUIRED for OG images
141
+ title: {
142
+ default: 'Brand Name',
143
+ template: '%s | Brand Name', // Auto-appends to all pages
144
+ },
145
+ description: 'Default site description',
146
+ openGraph: {
147
+ siteName: 'Brand Name',
148
+ locale: 'en_US',
149
+ type: 'website',
150
+ },
151
+ };
152
+ ```
153
+
154
+ **Why metadataBase matters:**
155
+ - Without it: `images: ['/og.png']` → broken URL
156
+ - With it: `images: ['/og.png']` → `https://site.com/og.png`
157
+
158
+ #### Page-Level Metadata
159
+
131
160
  ```typescript
132
161
  // app/products/[slug]/page.tsx
133
162
  import { Metadata } from 'next';
@@ -136,11 +165,11 @@ export async function generateMetadata({ params }): Promise<Metadata> {
136
165
  const product = await getProduct(params.slug);
137
166
 
138
167
  return {
139
- title: `${product.name} | Brand - $${product.price}`,
168
+ title: product.name, // Becomes "Product Name | Brand Name" via template
140
169
  description: truncate(product.description, 155), // Max 155 chars
141
170
 
142
171
  alternates: {
143
- canonical: `https://site.com/products/${product.slug}`,
172
+ canonical: `/products/${product.slug}`, // Relative OK with metadataBase
144
173
  },
145
174
 
146
175
  // OpenGraph - use 'product' for e-commerce
@@ -278,6 +307,87 @@ const faqJsonLd = {
278
307
  };
279
308
  ```
280
309
 
310
+ #### BreadcrumbList Schema
311
+
312
+ Shows breadcrumb trail in search results:
313
+
314
+ ```typescript
315
+ const breadcrumbJsonLd = {
316
+ '@context': 'https://schema.org',
317
+ '@type': 'BreadcrumbList',
318
+ itemListElement: [
319
+ {
320
+ '@type': 'ListItem',
321
+ position: 1,
322
+ name: 'Home',
323
+ item: 'https://site.com',
324
+ },
325
+ {
326
+ '@type': 'ListItem',
327
+ position: 2,
328
+ name: 'Products',
329
+ item: 'https://site.com/products',
330
+ },
331
+ {
332
+ '@type': 'ListItem',
333
+ position: 3,
334
+ name: product.name,
335
+ item: `https://site.com/products/${product.slug}`,
336
+ },
337
+ ],
338
+ };
339
+ ```
340
+
341
+ ### 6. Answer Engine Optimization (AEO)
342
+
343
+ AI search engines (ChatGPT, Perplexity, Claude) are becoming traffic sources. Optimize for them:
344
+
345
+ **Key AEO strategies:**
346
+
347
+ | Strategy | Implementation |
348
+ |----------|----------------|
349
+ | **FAQ sections** | Add FAQPage schema - AI pulls from structured Q&A |
350
+ | **Direct answers** | Start content with clear, factual statements |
351
+ | **Structured data** | Schema.org markup helps AI understand content |
352
+ | **Topic authority** | Comprehensive coverage on topic clusters |
353
+ | **Citation-friendly** | Include stats, dates, sources that AI can cite |
354
+
355
+ ```typescript
356
+ // ✅ GOOD: Content structure for AI search
357
+ function ProductPage({ product }) {
358
+ return (
359
+ <>
360
+ {/* Direct answer for AI to extract */}
361
+ <p className="lead">
362
+ The {product.name} is a {product.category} that {product.keyBenefit}.
363
+ Priced at ${product.price}, it's ideal for {product.targetAudience}.
364
+ </p>
365
+
366
+ {/* FAQ section with schema */}
367
+ <section>
368
+ <h2>Frequently Asked Questions</h2>
369
+ {product.faqs.map(faq => (
370
+ <details key={faq.id}>
371
+ <summary>{faq.question}</summary>
372
+ <p>{faq.answer}</p>
373
+ </details>
374
+ ))}
375
+ </section>
376
+
377
+ {/* Structured data for both Google and AI */}
378
+ <JsonLd data={productJsonLd} />
379
+ <JsonLd data={faqJsonLd} />
380
+ </>
381
+ );
382
+ }
383
+ ```
384
+
385
+ **Why AEO matters:**
386
+ - 40% of Gen Z uses TikTok/AI for search over Google
387
+ - AI search engines cite well-structured content
388
+ - FAQ sections appear in AI answers
389
+ - Structured data = machine-readable = AI-friendly
390
+
281
391
  ## Sitemap & Robots
282
392
 
283
393
  ### Dynamic Sitemap Generation
@@ -408,13 +518,15 @@ new PerformanceObserver((list) => {
408
518
  - [Core Web Vitals INP Guide](https://web.dev/articles/inp) - Official INP optimization patterns
409
519
  - [Optimize INP](https://web.dev/articles/optimize-inp) - Three-phase optimization approach
410
520
  - [Next.js Image Component](https://nextjs.org/docs/app/api-reference/components/image) - priority, placeholder, sizes
411
- - [Next.js Metadata API](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) - generateMetadata patterns
521
+ - [Next.js Metadata API](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) - generateMetadata, metadataBase
412
522
  - [Schema.org](https://schema.org/) - Structured data vocabulary
523
+ - [Rich Results Test](https://search.google.com/test/rich-results) - Validate structured data
413
524
 
414
525
  **Version Notes:**
415
526
  - INP replaced FID as Core Web Vital (March 2024)
416
- - Next.js 15: Enhanced Image component with automatic blur
527
+ - Next.js 15: Enhanced Image component, metadataBase required for OG
417
528
  - Good INP: < 200ms (improving from 500ms → 200ms = 22% engagement boost)
529
+ - AEO emerging: AI search engines (ChatGPT, Perplexity) use structured data
418
530
 
419
531
  ## Red Flags - STOP and Fix
420
532
 
package/dist/cli.js CHANGED
@@ -1598,15 +1598,18 @@ function getPackageSkillsDir() {
1598
1598
  const packageRoot = join15(dirname2(currentFilePath), "..");
1599
1599
  return join15(packageRoot, ".claude", "skills");
1600
1600
  }
1601
- async function copyDirectory(srcDir, destDir, result, baseDir) {
1601
+ async function copyDirectory(srcDir, destDir, result, baseDir, skipDirs) {
1602
1602
  await ensureDir(destDir);
1603
1603
  const entries = await readdir(srcDir, { withFileTypes: true });
1604
1604
  for (const entry of entries) {
1605
+ if (skipDirs && entry.isDirectory() && skipDirs.includes(entry.name)) {
1606
+ continue;
1607
+ }
1605
1608
  const srcPath = join15(srcDir, entry.name);
1606
1609
  const destPath = join15(destDir, entry.name);
1607
1610
  const relativePath = destPath.replace(baseDir + "/", "");
1608
1611
  if (entry.isDirectory()) {
1609
- await copyDirectory(srcPath, destPath, result, baseDir);
1612
+ await copyDirectory(srcPath, destPath, result, baseDir, skipDirs);
1610
1613
  } else {
1611
1614
  if (existsSync5(destPath)) {
1612
1615
  const backupPath = await backupFile(destPath);
@@ -1619,7 +1622,7 @@ async function copyDirectory(srcDir, destDir, result, baseDir) {
1619
1622
  }
1620
1623
  }
1621
1624
  }
1622
- async function generateClaudeSkills(targetDir) {
1625
+ async function generateClaudeSkills(targetDir, options) {
1623
1626
  const result = {
1624
1627
  created: [],
1625
1628
  modified: [],
@@ -1635,7 +1638,11 @@ async function generateClaudeSkills(targetDir) {
1635
1638
  return result;
1636
1639
  }
1637
1640
  await ensureDir(join15(targetDir, ".claude"));
1638
- await copyDirectory(packageSkillsDir, targetSkillsDir, result, targetDir);
1641
+ const skipDirs = [];
1642
+ if (!options?.includeAsana) {
1643
+ skipDirs.push("asana");
1644
+ }
1645
+ await copyDirectory(packageSkillsDir, targetSkillsDir, result, targetDir, skipDirs);
1639
1646
  return result;
1640
1647
  }
1641
1648
 
@@ -2201,7 +2208,9 @@ async function runInit(targetDir = process.cwd()) {
2201
2208
  await generateContributing(targetDir, !!config.asanaBaseUrl, config.packageManager)
2202
2209
  );
2203
2210
  results.push(await generateQuickReference(targetDir, config.packageManager));
2204
- results.push(await generateClaudeSkills(targetDir));
2211
+ results.push(await generateClaudeSkills(targetDir, {
2212
+ includeAsana: !!config.asanaBaseUrl
2213
+ }));
2205
2214
  results.push(await updateProjectPackageJson(targetDir, config));
2206
2215
  spinner4.stop("Configuration files generated!");
2207
2216
  } catch (error) {
@@ -2662,7 +2671,7 @@ ${pc4.bold("Branches")}
2662
2671
  // package.json
2663
2672
  var package_default = {
2664
2673
  name: "@raftlabs/raftstack",
2665
- version: "1.6.3",
2674
+ version: "1.7.1",
2666
2675
  description: "CLI tool for setting up Git hooks, commit conventions, and GitHub integration",
2667
2676
  type: "module",
2668
2677
  main: "./dist/index.js",