@rune-kit/rune 2.10.0 → 2.11.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/LICENSE +21 -21
- package/README.md +8 -6
- package/commands/rune.md +168 -168
- package/contexts/dev.md +34 -34
- package/contexts/research.md +43 -43
- package/contexts/review.md +55 -55
- package/extensions/ai-ml/PACK.md +88 -88
- package/extensions/ai-ml/skills/ai-agents.md +172 -172
- package/extensions/ai-ml/skills/code-sandbox.md +187 -187
- package/extensions/ai-ml/skills/deep-research.md +146 -146
- package/extensions/ai-ml/skills/embedding-search.md +66 -66
- package/extensions/ai-ml/skills/fine-tuning-guide.md +74 -74
- package/extensions/ai-ml/skills/llm-architect.md +125 -125
- package/extensions/ai-ml/skills/llm-integration.md +64 -64
- package/extensions/ai-ml/skills/prompt-patterns.md +72 -72
- package/extensions/ai-ml/skills/rag-patterns.md +66 -66
- package/extensions/ai-ml/skills/web-extraction.md +114 -114
- package/extensions/analytics/PACK.md +92 -92
- package/extensions/analytics/skills/ab-testing.md +72 -72
- package/extensions/analytics/skills/dashboard-patterns.md +83 -83
- package/extensions/analytics/skills/data-validation.md +68 -68
- package/extensions/analytics/skills/funnel-analysis.md +81 -81
- package/extensions/analytics/skills/sql-patterns.md +57 -57
- package/extensions/analytics/skills/statistical-analysis.md +79 -79
- package/extensions/analytics/skills/tracking-setup.md +71 -71
- package/extensions/backend/PACK.md +104 -104
- package/extensions/backend/skills/api-patterns.md +84 -84
- package/extensions/backend/skills/async-pipeline.md +193 -193
- package/extensions/backend/skills/auth-patterns.md +97 -97
- package/extensions/backend/skills/background-jobs.md +133 -133
- package/extensions/backend/skills/caching-patterns.md +108 -108
- package/extensions/backend/skills/cli-generation.md +133 -133
- package/extensions/backend/skills/database-patterns.md +87 -87
- package/extensions/backend/skills/middleware-patterns.md +104 -104
- package/extensions/chrome-ext/PACK.md +93 -93
- package/extensions/chrome-ext/skills/cws-preflight.md +143 -143
- package/extensions/chrome-ext/skills/cws-publish.md +104 -104
- package/extensions/chrome-ext/skills/ext-ai-integration.md +251 -251
- package/extensions/chrome-ext/skills/ext-messaging.md +139 -139
- package/extensions/chrome-ext/skills/ext-storage.md +133 -133
- package/extensions/chrome-ext/skills/mv3-scaffold.md +164 -164
- package/extensions/content/PACK.md +96 -96
- package/extensions/content/skills/blog-patterns.md +88 -88
- package/extensions/content/skills/cms-integration.md +131 -131
- package/extensions/content/skills/content-scoring.md +107 -107
- package/extensions/content/skills/i18n.md +83 -83
- package/extensions/content/skills/mdx-authoring.md +137 -137
- package/extensions/content/skills/reference.md +1014 -1014
- package/extensions/content/skills/seo-patterns.md +67 -67
- package/extensions/content/skills/video-repurpose.md +153 -153
- package/extensions/devops/PACK.md +101 -101
- package/extensions/devops/skills/chaos-testing.md +67 -67
- package/extensions/devops/skills/ci-cd.md +75 -75
- package/extensions/devops/skills/docker.md +58 -58
- package/extensions/devops/skills/edge-serverless.md +163 -163
- package/extensions/devops/skills/infra-as-code.md +158 -158
- package/extensions/devops/skills/kubernetes.md +110 -110
- package/extensions/devops/skills/monitoring.md +57 -57
- package/extensions/devops/skills/server-setup.md +64 -64
- package/extensions/devops/skills/ssl-domain.md +42 -42
- package/extensions/ecommerce/PACK.md +116 -116
- package/extensions/ecommerce/skills/cart-system.md +79 -79
- package/extensions/ecommerce/skills/inventory-mgmt.md +102 -102
- package/extensions/ecommerce/skills/order-management.md +126 -126
- package/extensions/ecommerce/skills/payment-integration.md +472 -472
- package/extensions/ecommerce/skills/shopify-dev.md +69 -69
- package/extensions/ecommerce/skills/subscription-billing.md +93 -93
- package/extensions/ecommerce/skills/tax-compliance.md +117 -117
- package/extensions/gamedev/PACK.md +142 -142
- package/extensions/gamedev/skills/asset-pipeline.md +74 -74
- package/extensions/gamedev/skills/audio-system.md +129 -129
- package/extensions/gamedev/skills/camera-system.md +87 -87
- package/extensions/gamedev/skills/ecs.md +98 -98
- package/extensions/gamedev/skills/game-loops.md +72 -72
- package/extensions/gamedev/skills/input-system.md +199 -199
- package/extensions/gamedev/skills/multiplayer.md +180 -180
- package/extensions/gamedev/skills/particles.md +105 -105
- package/extensions/gamedev/skills/physics-engine.md +89 -89
- package/extensions/gamedev/skills/scene-management.md +146 -146
- package/extensions/gamedev/skills/threejs-patterns.md +90 -90
- package/extensions/gamedev/skills/webgl.md +71 -71
- package/extensions/mobile/PACK.md +106 -106
- package/extensions/mobile/skills/app-store-connect.md +152 -152
- package/extensions/mobile/skills/app-store-prep.md +66 -66
- package/extensions/mobile/skills/deep-linking.md +109 -109
- package/extensions/mobile/skills/flutter.md +60 -60
- package/extensions/mobile/skills/ios-build-pipeline.md +142 -142
- package/extensions/mobile/skills/native-bridge.md +66 -66
- package/extensions/mobile/skills/ota-updates.md +97 -97
- package/extensions/mobile/skills/push-notifications.md +111 -111
- package/extensions/mobile/skills/react-native.md +82 -82
- package/extensions/saas/PACK.md +116 -116
- package/extensions/saas/skills/billing-integration.md +200 -200
- package/extensions/saas/skills/feature-flags.md +130 -130
- package/extensions/saas/skills/multi-tenant.md +103 -103
- package/extensions/saas/skills/onboarding-flow.md +139 -139
- package/extensions/saas/skills/subscription-flow.md +95 -95
- package/extensions/saas/skills/team-management.md +144 -144
- package/extensions/security/PACK.md +99 -99
- package/extensions/security/skills/api-security.md +140 -140
- package/extensions/security/skills/compliance.md +68 -68
- package/extensions/security/skills/owasp-audit.md +64 -64
- package/extensions/security/skills/pentest-patterns.md +77 -77
- package/extensions/security/skills/secret-mgmt.md +65 -65
- package/extensions/security/skills/supply-chain.md +65 -65
- package/extensions/trading/PACK.md +80 -80
- package/extensions/trading/skills/chart-components.md +55 -55
- package/extensions/trading/skills/experiment-loop.md +125 -125
- package/extensions/trading/skills/fintech-patterns.md +47 -47
- package/extensions/trading/skills/indicator-library.md +58 -58
- package/extensions/trading/skills/quant-analysis.md +111 -111
- package/extensions/trading/skills/realtime-data.md +58 -58
- package/extensions/trading/skills/trade-logic.md +104 -104
- package/extensions/ui/PACK.md +130 -130
- package/extensions/ui/skills/a11y-audit.md +91 -91
- package/extensions/ui/skills/animation-patterns.md +127 -127
- package/extensions/ui/skills/component-patterns.md +100 -100
- package/extensions/ui/skills/design-decision.md +108 -108
- package/extensions/ui/skills/design-system.md +68 -68
- package/extensions/ui/skills/landing-patterns.md +155 -155
- package/extensions/ui/skills/palette-picker.md +173 -173
- package/extensions/ui/skills/react-health.md +90 -90
- package/extensions/ui/skills/type-system.md +125 -125
- package/extensions/ui/skills/web-vitals.md +153 -153
- package/extensions/zalo/PACK.md +145 -145
- package/extensions/zalo/skills/zalo-oa-mcp.md +317 -317
- package/extensions/zalo/skills/zalo-oa-messaging.md +429 -429
- package/extensions/zalo/skills/zalo-oa-setup.md +236 -236
- package/extensions/zalo/skills/zalo-oa-webhook.md +189 -189
- package/extensions/zalo/skills/zalo-personal-messaging.md +194 -194
- package/extensions/zalo/skills/zalo-personal-setup.md +153 -153
- package/extensions/zalo/skills/zalo-rate-guard.md +219 -219
- package/hooks/auto-format/index.cjs +48 -48
- package/hooks/hooks.json +111 -111
- package/hooks/post-session-reflect/index.cjs +189 -189
- package/hooks/pre-compact/index.cjs +95 -95
- package/hooks/run-hook.cmd +1 -1
- package/hooks/secrets-scan/index.cjs +100 -100
- package/hooks/session-start/index.cjs +71 -71
- package/hooks/typecheck/index.cjs +65 -65
- package/package.json +63 -63
- package/references/ui-pro-max-data/LICENSE-UI-PRO-MAX +21 -21
- package/references/ui-pro-max-data/charts.csv +26 -26
- package/references/ui-pro-max-data/colors.csv +161 -161
- package/references/ui-pro-max-data/styles.csv +68 -68
- package/references/ui-pro-max-data/typography.csv +74 -74
- package/references/ui-pro-max-data/ui-reasoning.csv +162 -162
- package/references/ui-pro-max-data/ux-guidelines.csv +99 -99
- package/skills/adversary/SKILL.md +283 -283
- package/skills/asset-creator/SKILL.md +157 -157
- package/skills/audit/SKILL.md +147 -2
- package/skills/autopsy/SKILL.md +335 -335
- package/skills/brainstorm/SKILL.md +342 -342
- package/skills/browser-pilot/SKILL.md +168 -168
- package/skills/constraint-check/SKILL.md +165 -165
- package/skills/context-engine/SKILL.md +404 -404
- package/skills/cook/SKILL.md +917 -863
- package/skills/db/SKILL.md +273 -273
- package/skills/debug/SKILL.md +465 -465
- package/skills/dependency-doctor/SKILL.md +265 -235
- package/skills/deploy/SKILL.md +274 -231
- package/skills/design/DESIGN-REFERENCE.md +365 -365
- package/skills/design/SKILL.md +589 -589
- package/skills/doc-processor/SKILL.md +254 -254
- package/skills/docs/SKILL.md +374 -374
- package/skills/docs-seeker/SKILL.md +177 -177
- package/skills/fix/SKILL.md +330 -330
- package/skills/git/SKILL.md +339 -339
- package/skills/hallucination-guard/SKILL.md +219 -219
- package/skills/incident/SKILL.md +254 -253
- package/skills/integrity-check/SKILL.md +169 -169
- package/skills/journal/SKILL.md +240 -240
- package/skills/launch/SKILL.md +344 -344
- package/skills/logic-guardian/SKILL.md +251 -251
- package/skills/marketing/SKILL.md +290 -289
- package/skills/mcp-builder/SKILL.md +425 -425
- package/skills/neural-memory/SKILL.md +362 -362
- package/skills/onboard/SKILL.md +404 -403
- package/skills/perf/SKILL.md +346 -346
- package/skills/plan/SKILL.md +433 -428
- package/skills/preflight/SKILL.md +415 -415
- package/skills/problem-solver/SKILL.md +380 -284
- package/skills/rescue/SKILL.md +474 -474
- package/skills/retro/SKILL.md +3 -1
- package/skills/review/SKILL.md +612 -588
- package/skills/review-intake/SKILL.md +249 -249
- package/skills/safeguard/SKILL.md +200 -200
- package/skills/sast/SKILL.md +190 -190
- package/skills/scaffold/SKILL.md +328 -287
- package/skills/scope-guard/SKILL.md +180 -180
- package/skills/scout/SKILL.md +263 -263
- package/skills/sentinel/SKILL.md +382 -381
- package/skills/sentinel-env/SKILL.md +254 -254
- package/skills/sequential-thinking/SKILL.md +234 -234
- package/skills/session-bridge/SKILL.md +543 -543
- package/skills/skill-forge/SKILL.md +581 -581
- package/skills/skill-router/SKILL.md +3 -0
- package/skills/surgeon/SKILL.md +215 -215
- package/skills/team/SKILL.md +556 -537
- package/skills/test/SKILL.md +614 -614
- package/skills/trend-scout/SKILL.md +145 -145
- package/skills/verification/SKILL.md +326 -326
- package/skills/video-creator/SKILL.md +201 -201
- package/skills/watchdog/SKILL.md +168 -168
- package/skills/worktree/SKILL.md +140 -140
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "blog-patterns"
|
|
3
|
-
pack: "@rune/content"
|
|
4
|
-
description: "Post management, categories/tags, pagination, RSS feeds, reading time, related posts, comment systems."
|
|
5
|
-
model: sonnet
|
|
6
|
-
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# blog-patterns
|
|
10
|
-
|
|
11
|
-
Blog system patterns — post management, categories/tags, pagination, RSS feeds, reading time, related posts, comment systems.
|
|
12
|
-
|
|
13
|
-
#### Workflow
|
|
14
|
-
|
|
15
|
-
**Step 1 — Detect blog architecture**
|
|
16
|
-
Use Glob to find blog-related files: `blog/`, `posts/`, `articles/`, `*.mdx`, `*.md` in content directories. Use Grep to find blog utilities: `getStaticPaths`, `generateStaticParams`, `allPosts`, `contentlayer`, `reading-time`. Read the post listing page and individual post page to understand: data source, routing strategy, and rendering pipeline.
|
|
17
|
-
|
|
18
|
-
**Step 2 — Audit blog completeness**
|
|
19
|
-
Check for: missing RSS feed (`feed.xml` or `/api/rss`), no reading time estimation, pagination absent on listing pages (all posts loaded at once), no category/tag filtering, missing related posts, no draft/published state, and OG images not generated per-post.
|
|
20
|
-
|
|
21
|
-
**Step 3 — Emit blog patterns**
|
|
22
|
-
Emit: typed post schema with frontmatter validation, paginated listing with category filter, RSS feed generator, reading time calculator, and related posts by tag similarity.
|
|
23
|
-
|
|
24
|
-
#### Example
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
// Next.js App Router — blog listing with pagination and categories
|
|
28
|
-
import { allPosts, type Post } from 'contentlayer/generated';
|
|
29
|
-
|
|
30
|
-
function getPublishedPosts(category?: string): Post[] {
|
|
31
|
-
return allPosts
|
|
32
|
-
.filter(p => p.status === 'published')
|
|
33
|
-
.filter(p => !category || p.category === category)
|
|
34
|
-
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Reading time utility
|
|
38
|
-
function readingTime(content: string): string {
|
|
39
|
-
const words = content.trim().split(/\s+/).length;
|
|
40
|
-
const minutes = Math.ceil(words / 238);
|
|
41
|
-
return `${minutes} min read`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// RSS feed — app/feed.xml/route.ts
|
|
45
|
-
export async function GET() {
|
|
46
|
-
const posts = getPublishedPosts();
|
|
47
|
-
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
48
|
-
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
49
|
-
<channel>
|
|
50
|
-
<title>My Blog</title>
|
|
51
|
-
<link>${process.env.SITE_URL}</link>
|
|
52
|
-
<atom:link href="${process.env.SITE_URL}/feed.xml" rel="self" type="application/rss+xml"/>
|
|
53
|
-
${posts.slice(0, 20).map(p => `<item>
|
|
54
|
-
<title>${escapeXml(p.title)}</title>
|
|
55
|
-
<link>${process.env.SITE_URL}${p.url}</link>
|
|
56
|
-
<pubDate>${new Date(p.date).toUTCString()}</pubDate>
|
|
57
|
-
<description>${escapeXml(p.excerpt)}</description>
|
|
58
|
-
</item>`).join('\n')}
|
|
59
|
-
</channel>
|
|
60
|
-
</rss>`;
|
|
61
|
-
return new Response(xml, { headers: { 'Content-Type': 'application/xml' } });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Related posts by tag overlap — score by number of shared tags
|
|
65
|
-
function getRelatedPosts(current: Post, all: Post[], limit = 3): Post[] {
|
|
66
|
-
const currentTags = new Set(current.tags ?? []);
|
|
67
|
-
return all
|
|
68
|
-
.filter(p => p.slug !== current.slug && p.status === 'published')
|
|
69
|
-
.map(p => ({ post: p, score: (p.tags ?? []).filter(t => currentTags.has(t)).length }))
|
|
70
|
-
.filter(({ score }) => score > 0)
|
|
71
|
-
.sort((a, b) => b.score - a.score)
|
|
72
|
-
.slice(0, limit)
|
|
73
|
-
.map(({ post }) => post);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Paginated listing
|
|
77
|
-
const PAGE_SIZE = 10;
|
|
78
|
-
function paginatePosts(posts: Post[], page: number) {
|
|
79
|
-
const start = (page - 1) * PAGE_SIZE;
|
|
80
|
-
return {
|
|
81
|
-
posts: posts.slice(start, start + PAGE_SIZE),
|
|
82
|
-
total: posts.length,
|
|
83
|
-
totalPages: Math.ceil(posts.length / PAGE_SIZE),
|
|
84
|
-
hasNext: start + PAGE_SIZE < posts.length,
|
|
85
|
-
hasPrev: page > 1,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
```
|
|
1
|
+
---
|
|
2
|
+
name: "blog-patterns"
|
|
3
|
+
pack: "@rune/content"
|
|
4
|
+
description: "Post management, categories/tags, pagination, RSS feeds, reading time, related posts, comment systems."
|
|
5
|
+
model: sonnet
|
|
6
|
+
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# blog-patterns
|
|
10
|
+
|
|
11
|
+
Blog system patterns — post management, categories/tags, pagination, RSS feeds, reading time, related posts, comment systems.
|
|
12
|
+
|
|
13
|
+
#### Workflow
|
|
14
|
+
|
|
15
|
+
**Step 1 — Detect blog architecture**
|
|
16
|
+
Use Glob to find blog-related files: `blog/`, `posts/`, `articles/`, `*.mdx`, `*.md` in content directories. Use Grep to find blog utilities: `getStaticPaths`, `generateStaticParams`, `allPosts`, `contentlayer`, `reading-time`. Read the post listing page and individual post page to understand: data source, routing strategy, and rendering pipeline.
|
|
17
|
+
|
|
18
|
+
**Step 2 — Audit blog completeness**
|
|
19
|
+
Check for: missing RSS feed (`feed.xml` or `/api/rss`), no reading time estimation, pagination absent on listing pages (all posts loaded at once), no category/tag filtering, missing related posts, no draft/published state, and OG images not generated per-post.
|
|
20
|
+
|
|
21
|
+
**Step 3 — Emit blog patterns**
|
|
22
|
+
Emit: typed post schema with frontmatter validation, paginated listing with category filter, RSS feed generator, reading time calculator, and related posts by tag similarity.
|
|
23
|
+
|
|
24
|
+
#### Example
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// Next.js App Router — blog listing with pagination and categories
|
|
28
|
+
import { allPosts, type Post } from 'contentlayer/generated';
|
|
29
|
+
|
|
30
|
+
function getPublishedPosts(category?: string): Post[] {
|
|
31
|
+
return allPosts
|
|
32
|
+
.filter(p => p.status === 'published')
|
|
33
|
+
.filter(p => !category || p.category === category)
|
|
34
|
+
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Reading time utility
|
|
38
|
+
function readingTime(content: string): string {
|
|
39
|
+
const words = content.trim().split(/\s+/).length;
|
|
40
|
+
const minutes = Math.ceil(words / 238);
|
|
41
|
+
return `${minutes} min read`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// RSS feed — app/feed.xml/route.ts
|
|
45
|
+
export async function GET() {
|
|
46
|
+
const posts = getPublishedPosts();
|
|
47
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
48
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
49
|
+
<channel>
|
|
50
|
+
<title>My Blog</title>
|
|
51
|
+
<link>${process.env.SITE_URL}</link>
|
|
52
|
+
<atom:link href="${process.env.SITE_URL}/feed.xml" rel="self" type="application/rss+xml"/>
|
|
53
|
+
${posts.slice(0, 20).map(p => `<item>
|
|
54
|
+
<title>${escapeXml(p.title)}</title>
|
|
55
|
+
<link>${process.env.SITE_URL}${p.url}</link>
|
|
56
|
+
<pubDate>${new Date(p.date).toUTCString()}</pubDate>
|
|
57
|
+
<description>${escapeXml(p.excerpt)}</description>
|
|
58
|
+
</item>`).join('\n')}
|
|
59
|
+
</channel>
|
|
60
|
+
</rss>`;
|
|
61
|
+
return new Response(xml, { headers: { 'Content-Type': 'application/xml' } });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Related posts by tag overlap — score by number of shared tags
|
|
65
|
+
function getRelatedPosts(current: Post, all: Post[], limit = 3): Post[] {
|
|
66
|
+
const currentTags = new Set(current.tags ?? []);
|
|
67
|
+
return all
|
|
68
|
+
.filter(p => p.slug !== current.slug && p.status === 'published')
|
|
69
|
+
.map(p => ({ post: p, score: (p.tags ?? []).filter(t => currentTags.has(t)).length }))
|
|
70
|
+
.filter(({ score }) => score > 0)
|
|
71
|
+
.sort((a, b) => b.score - a.score)
|
|
72
|
+
.slice(0, limit)
|
|
73
|
+
.map(({ post }) => post);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Paginated listing
|
|
77
|
+
const PAGE_SIZE = 10;
|
|
78
|
+
function paginatePosts(posts: Post[], page: number) {
|
|
79
|
+
const start = (page - 1) * PAGE_SIZE;
|
|
80
|
+
return {
|
|
81
|
+
posts: posts.slice(start, start + PAGE_SIZE),
|
|
82
|
+
total: posts.length,
|
|
83
|
+
totalPages: Math.ceil(posts.length / PAGE_SIZE),
|
|
84
|
+
hasNext: start + PAGE_SIZE < posts.length,
|
|
85
|
+
hasPrev: page > 1,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
```
|
|
@@ -1,131 +1,131 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "cms-integration"
|
|
3
|
-
pack: "@rune/content"
|
|
4
|
-
description: "Sanity, Contentful, Strapi, PocketBase. Content modeling, preview mode, webhook-triggered rebuilds, draft/published workflows."
|
|
5
|
-
model: sonnet
|
|
6
|
-
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# cms-integration
|
|
10
|
-
|
|
11
|
-
CMS integration — Sanity, Contentful, Strapi, PocketBase. Content modeling, preview mode, webhook-triggered rebuilds, draft/published workflows.
|
|
12
|
-
|
|
13
|
-
#### Workflow
|
|
14
|
-
|
|
15
|
-
**Step 1 — Detect CMS setup**
|
|
16
|
-
Use Grep to find CMS SDK usage: `createClient` (Sanity), `contentful`, `strapi`, `PocketBase`, `GROQ`, `graphql` in content-fetching files. Read the CMS client initialization and content queries to understand: CMS provider, content types, preview mode setup, and caching strategy.
|
|
17
|
-
|
|
18
|
-
**Step 2 — Audit CMS integration**
|
|
19
|
-
Check for: no preview/draft mode (editors can't preview before publish), missing webhook for on-demand ISR (content updates require full rebuild), no content validation (malformed CMS data crashes the page), stale cache without revalidation strategy, images served from CMS without optimization (no next/image or equivalent), and missing error boundary for CMS fetch failures.
|
|
20
|
-
|
|
21
|
-
**Step 3 — Emit CMS patterns**
|
|
22
|
-
For Sanity: emit typed GROQ queries with Zod validation, preview mode toggle, and webhook handler. For Contentful: emit typed GraphQL queries, draft/published content switching. For any CMS: emit ISR revalidation endpoint and image optimization pipeline.
|
|
23
|
-
|
|
24
|
-
#### Example — Sanity
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
// Sanity — typed client with preview mode and ISR webhook
|
|
28
|
-
import { createClient, type QueryParams } from '@sanity/client';
|
|
29
|
-
import { z } from 'zod';
|
|
30
|
-
|
|
31
|
-
const client = createClient({
|
|
32
|
-
projectId: process.env.SANITY_PROJECT_ID!,
|
|
33
|
-
dataset: 'production',
|
|
34
|
-
apiVersion: '2024-01-01',
|
|
35
|
-
useCdn: true,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const previewClient = client.withConfig({ useCdn: false, token: process.env.SANITY_PREVIEW_TOKEN });
|
|
39
|
-
|
|
40
|
-
const PostSchema = z.object({
|
|
41
|
-
_id: z.string(),
|
|
42
|
-
title: z.string(),
|
|
43
|
-
slug: z.string(),
|
|
44
|
-
body: z.array(z.any()),
|
|
45
|
-
publishedAt: z.string().datetime(),
|
|
46
|
-
author: z.object({ name: z.string(), image: z.string().url().optional() }),
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
export async function getPost(slug: string, preview = false) {
|
|
50
|
-
const query = `*[_type == "post" && slug.current == $slug][0]{
|
|
51
|
-
_id, title, "slug": slug.current, body, publishedAt,
|
|
52
|
-
"author": author->{ name, "image": image.asset->url }
|
|
53
|
-
}`;
|
|
54
|
-
const result = await (preview ? previewClient : client).fetch(query, { slug });
|
|
55
|
-
return PostSchema.parse(result);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Webhook handler for on-demand ISR — app/api/revalidate/route.ts
|
|
59
|
-
export async function POST(req: Request) {
|
|
60
|
-
const body = await req.json();
|
|
61
|
-
const secret = req.headers.get('x-sanity-webhook-secret');
|
|
62
|
-
if (secret !== process.env.SANITY_WEBHOOK_SECRET) {
|
|
63
|
-
return new Response('Unauthorized', { status: 401 });
|
|
64
|
-
}
|
|
65
|
-
const { revalidatePath } = await import('next/cache');
|
|
66
|
-
revalidatePath(`/blog/${body.slug.current}`);
|
|
67
|
-
return Response.json({ revalidated: true });
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
#### Example — Contentful
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
// Contentful — typed GraphQL with draft/published switching
|
|
75
|
-
import { createClient } from 'contentful';
|
|
76
|
-
|
|
77
|
-
const client = createClient({
|
|
78
|
-
space: process.env.CONTENTFUL_SPACE_ID!,
|
|
79
|
-
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const previewClient = createClient({
|
|
83
|
-
space: process.env.CONTENTFUL_SPACE_ID!,
|
|
84
|
-
accessToken: process.env.CONTENTFUL_PREVIEW_TOKEN!,
|
|
85
|
-
host: 'preview.contentful.com',
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
export async function getArticle(slug: string, preview = false) {
|
|
89
|
-
const c = preview ? previewClient : client;
|
|
90
|
-
const entries = await c.getEntries({
|
|
91
|
-
content_type: 'article',
|
|
92
|
-
'fields.slug': slug,
|
|
93
|
-
include: 2,
|
|
94
|
-
limit: 1,
|
|
95
|
-
});
|
|
96
|
-
if (!entries.items.length) return null;
|
|
97
|
-
const entry = entries.items[0];
|
|
98
|
-
return {
|
|
99
|
-
title: entry.fields.title as string,
|
|
100
|
-
slug: entry.fields.slug as string,
|
|
101
|
-
body: entry.fields.body,
|
|
102
|
-
publishedAt: entry.sys.createdAt,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
#### Example — Strapi
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
// Strapi v5 — REST with populate and draft/live modes
|
|
111
|
-
const STRAPI = process.env.STRAPI_URL ?? 'http://localhost:1337';
|
|
112
|
-
const TOKEN = process.env.STRAPI_API_TOKEN!;
|
|
113
|
-
|
|
114
|
-
async function strapiGet<T>(path: string, params: Record<string, string> = {}): Promise<T> {
|
|
115
|
-
const url = new URL(`${STRAPI}/api${path}`);
|
|
116
|
-
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
117
|
-
const res = await fetch(url.toString(), {
|
|
118
|
-
headers: { Authorization: `Bearer ${TOKEN}` },
|
|
119
|
-
next: { revalidate: 60 },
|
|
120
|
-
});
|
|
121
|
-
if (!res.ok) throw new Error(`Strapi error: ${res.status}`);
|
|
122
|
-
return res.json();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export const getArticles = () =>
|
|
126
|
-
strapiGet<{ data: StrapiArticle[] }>('/articles', {
|
|
127
|
-
'filters[publishedAt][$notNull]': 'true',
|
|
128
|
-
'populate': 'cover,author,category',
|
|
129
|
-
'sort': 'publishedAt:desc',
|
|
130
|
-
});
|
|
131
|
-
```
|
|
1
|
+
---
|
|
2
|
+
name: "cms-integration"
|
|
3
|
+
pack: "@rune/content"
|
|
4
|
+
description: "Sanity, Contentful, Strapi, PocketBase. Content modeling, preview mode, webhook-triggered rebuilds, draft/published workflows."
|
|
5
|
+
model: sonnet
|
|
6
|
+
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# cms-integration
|
|
10
|
+
|
|
11
|
+
CMS integration — Sanity, Contentful, Strapi, PocketBase. Content modeling, preview mode, webhook-triggered rebuilds, draft/published workflows.
|
|
12
|
+
|
|
13
|
+
#### Workflow
|
|
14
|
+
|
|
15
|
+
**Step 1 — Detect CMS setup**
|
|
16
|
+
Use Grep to find CMS SDK usage: `createClient` (Sanity), `contentful`, `strapi`, `PocketBase`, `GROQ`, `graphql` in content-fetching files. Read the CMS client initialization and content queries to understand: CMS provider, content types, preview mode setup, and caching strategy.
|
|
17
|
+
|
|
18
|
+
**Step 2 — Audit CMS integration**
|
|
19
|
+
Check for: no preview/draft mode (editors can't preview before publish), missing webhook for on-demand ISR (content updates require full rebuild), no content validation (malformed CMS data crashes the page), stale cache without revalidation strategy, images served from CMS without optimization (no next/image or equivalent), and missing error boundary for CMS fetch failures.
|
|
20
|
+
|
|
21
|
+
**Step 3 — Emit CMS patterns**
|
|
22
|
+
For Sanity: emit typed GROQ queries with Zod validation, preview mode toggle, and webhook handler. For Contentful: emit typed GraphQL queries, draft/published content switching. For any CMS: emit ISR revalidation endpoint and image optimization pipeline.
|
|
23
|
+
|
|
24
|
+
#### Example — Sanity
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// Sanity — typed client with preview mode and ISR webhook
|
|
28
|
+
import { createClient, type QueryParams } from '@sanity/client';
|
|
29
|
+
import { z } from 'zod';
|
|
30
|
+
|
|
31
|
+
const client = createClient({
|
|
32
|
+
projectId: process.env.SANITY_PROJECT_ID!,
|
|
33
|
+
dataset: 'production',
|
|
34
|
+
apiVersion: '2024-01-01',
|
|
35
|
+
useCdn: true,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const previewClient = client.withConfig({ useCdn: false, token: process.env.SANITY_PREVIEW_TOKEN });
|
|
39
|
+
|
|
40
|
+
const PostSchema = z.object({
|
|
41
|
+
_id: z.string(),
|
|
42
|
+
title: z.string(),
|
|
43
|
+
slug: z.string(),
|
|
44
|
+
body: z.array(z.any()),
|
|
45
|
+
publishedAt: z.string().datetime(),
|
|
46
|
+
author: z.object({ name: z.string(), image: z.string().url().optional() }),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export async function getPost(slug: string, preview = false) {
|
|
50
|
+
const query = `*[_type == "post" && slug.current == $slug][0]{
|
|
51
|
+
_id, title, "slug": slug.current, body, publishedAt,
|
|
52
|
+
"author": author->{ name, "image": image.asset->url }
|
|
53
|
+
}`;
|
|
54
|
+
const result = await (preview ? previewClient : client).fetch(query, { slug });
|
|
55
|
+
return PostSchema.parse(result);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Webhook handler for on-demand ISR — app/api/revalidate/route.ts
|
|
59
|
+
export async function POST(req: Request) {
|
|
60
|
+
const body = await req.json();
|
|
61
|
+
const secret = req.headers.get('x-sanity-webhook-secret');
|
|
62
|
+
if (secret !== process.env.SANITY_WEBHOOK_SECRET) {
|
|
63
|
+
return new Response('Unauthorized', { status: 401 });
|
|
64
|
+
}
|
|
65
|
+
const { revalidatePath } = await import('next/cache');
|
|
66
|
+
revalidatePath(`/blog/${body.slug.current}`);
|
|
67
|
+
return Response.json({ revalidated: true });
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
#### Example — Contentful
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// Contentful — typed GraphQL with draft/published switching
|
|
75
|
+
import { createClient } from 'contentful';
|
|
76
|
+
|
|
77
|
+
const client = createClient({
|
|
78
|
+
space: process.env.CONTENTFUL_SPACE_ID!,
|
|
79
|
+
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const previewClient = createClient({
|
|
83
|
+
space: process.env.CONTENTFUL_SPACE_ID!,
|
|
84
|
+
accessToken: process.env.CONTENTFUL_PREVIEW_TOKEN!,
|
|
85
|
+
host: 'preview.contentful.com',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export async function getArticle(slug: string, preview = false) {
|
|
89
|
+
const c = preview ? previewClient : client;
|
|
90
|
+
const entries = await c.getEntries({
|
|
91
|
+
content_type: 'article',
|
|
92
|
+
'fields.slug': slug,
|
|
93
|
+
include: 2,
|
|
94
|
+
limit: 1,
|
|
95
|
+
});
|
|
96
|
+
if (!entries.items.length) return null;
|
|
97
|
+
const entry = entries.items[0];
|
|
98
|
+
return {
|
|
99
|
+
title: entry.fields.title as string,
|
|
100
|
+
slug: entry.fields.slug as string,
|
|
101
|
+
body: entry.fields.body,
|
|
102
|
+
publishedAt: entry.sys.createdAt,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### Example — Strapi
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// Strapi v5 — REST with populate and draft/live modes
|
|
111
|
+
const STRAPI = process.env.STRAPI_URL ?? 'http://localhost:1337';
|
|
112
|
+
const TOKEN = process.env.STRAPI_API_TOKEN!;
|
|
113
|
+
|
|
114
|
+
async function strapiGet<T>(path: string, params: Record<string, string> = {}): Promise<T> {
|
|
115
|
+
const url = new URL(`${STRAPI}/api${path}`);
|
|
116
|
+
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
117
|
+
const res = await fetch(url.toString(), {
|
|
118
|
+
headers: { Authorization: `Bearer ${TOKEN}` },
|
|
119
|
+
next: { revalidate: 60 },
|
|
120
|
+
});
|
|
121
|
+
if (!res.ok) throw new Error(`Strapi error: ${res.status}`);
|
|
122
|
+
return res.json();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const getArticles = () =>
|
|
126
|
+
strapiGet<{ data: StrapiArticle[] }>('/articles', {
|
|
127
|
+
'filters[publishedAt][$notNull]': 'true',
|
|
128
|
+
'populate': 'cover,author,category',
|
|
129
|
+
'sort': 'publishedAt:desc',
|
|
130
|
+
});
|
|
131
|
+
```
|
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "content-scoring"
|
|
3
|
-
pack: "@rune/content"
|
|
4
|
-
description: "Engagement and virality scoring for content pieces. Analyze hooks, readability, shareability, and platform fit."
|
|
5
|
-
model: sonnet
|
|
6
|
-
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# content-scoring
|
|
10
|
-
|
|
11
|
-
Engagement and virality scoring for content pieces. Analyze hooks, readability, shareability, and platform fit. Works for both video clips and written articles.
|
|
12
|
-
|
|
13
|
-
#### Workflow
|
|
14
|
-
|
|
15
|
-
**Step 1 — Detect content type**
|
|
16
|
-
Determine if scoring target is:
|
|
17
|
-
- **Video clip** (from video-repurpose pipeline or standalone)
|
|
18
|
-
- **Blog post / article** (markdown, MDX, or CMS content)
|
|
19
|
-
- **Social post** (short-form text, tweet, thread)
|
|
20
|
-
|
|
21
|
-
**Step 2 — Score across 4 dimensions**
|
|
22
|
-
```typescript
|
|
23
|
-
interface ContentScore {
|
|
24
|
-
hook: {
|
|
25
|
-
score: number; // 0-25
|
|
26
|
-
type: 'question' | 'statistic' | 'story' | 'contrast' | 'bold-claim' | 'how-to';
|
|
27
|
-
assessment: string; // Why this hook works or doesn't
|
|
28
|
-
};
|
|
29
|
-
engagement: {
|
|
30
|
-
score: number; // 0-25
|
|
31
|
-
readability: number; // Flesch-Kincaid grade level
|
|
32
|
-
pacing: string; // 'too-slow' | 'good' | 'too-fast'
|
|
33
|
-
callToAction: boolean;
|
|
34
|
-
};
|
|
35
|
-
value: {
|
|
36
|
-
score: number; // 0-25
|
|
37
|
-
teaches: boolean;
|
|
38
|
-
entertains: boolean;
|
|
39
|
-
uniqueInsight: boolean;
|
|
40
|
-
};
|
|
41
|
-
shareability: {
|
|
42
|
-
score: number; // 0-25
|
|
43
|
-
emotionalTrigger: string | null; // 'surprise' | 'anger' | 'joy' | 'fear'
|
|
44
|
-
quotable: string[]; // Extract quotable one-liners
|
|
45
|
-
platformFit: Record<Platform, number>; // 0-10 per platform
|
|
46
|
-
};
|
|
47
|
-
total: number; // 0-100
|
|
48
|
-
tier: 'viral' | 'strong' | 'average' | 'weak'; // >80 viral, >60 strong, >40 average
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
**Step 3 — Platform-specific optimization hints**
|
|
53
|
-
Each platform has different engagement patterns:
|
|
54
|
-
| Platform | Hook Window | Optimal Length | Key Factor |
|
|
55
|
-
|----------|-------------|---------------|------------|
|
|
56
|
-
| TikTok | 0-1s | 15-30s | Pattern interrupt, trend audio |
|
|
57
|
-
| YouTube | 0-3s | 8-12 min (long), 30-60s (Shorts) | Curiosity gap, retention graph |
|
|
58
|
-
| Twitter/X | First line | 280 chars or 4-tweet thread | Hot take, data point |
|
|
59
|
-
| LinkedIn | First 2 lines | 150-300 words | Professional insight, personal story |
|
|
60
|
-
| Blog | Title + first paragraph | 1500-2500 words | SEO keyword + value promise |
|
|
61
|
-
|
|
62
|
-
**Step 4 — Emit improvement suggestions**
|
|
63
|
-
For each dimension scoring < 20/25, emit specific actionable improvement:
|
|
64
|
-
- Hook weak → suggest rewrite with stronger opening pattern
|
|
65
|
-
- Engagement low → identify pacing issues, suggest cuts or restructures
|
|
66
|
-
- Value low → identify where content is generic, suggest unique angle
|
|
67
|
-
- Shareability low → suggest quotable reformulations, emotional triggers
|
|
68
|
-
|
|
69
|
-
#### Example
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
// Scoring a blog post
|
|
73
|
-
const score = await scoreContent({
|
|
74
|
-
type: 'article',
|
|
75
|
-
title: 'How We Cut Our AWS Bill by 60%',
|
|
76
|
-
content: articleMarkdown,
|
|
77
|
-
targetPlatforms: ['blog', 'twitter', 'linkedin'],
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Result:
|
|
81
|
-
// {
|
|
82
|
-
// hook: { score: 22, type: 'statistic', assessment: 'Strong — specific number creates curiosity' },
|
|
83
|
-
// engagement: { score: 18, readability: 8.2, pacing: 'good', callToAction: true },
|
|
84
|
-
// value: { score: 20, teaches: true, entertains: false, uniqueInsight: true },
|
|
85
|
-
// shareability: {
|
|
86
|
-
// score: 19, emotionalTrigger: 'surprise',
|
|
87
|
-
// quotable: ['We were paying $12K/mo for a service we used 3% of'],
|
|
88
|
-
// platformFit: { blog: 9, twitter: 8, linkedin: 9 }
|
|
89
|
-
// },
|
|
90
|
-
// total: 79, tier: 'strong'
|
|
91
|
-
// }
|
|
92
|
-
|
|
93
|
-
// Improvement suggestions:
|
|
94
|
-
// - Shareability: Add a contrarian angle ("Everyone says X, but we found Y")
|
|
95
|
-
// - Engagement: Add a visual comparison (before/after cost graph)
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
// Scoring a video clip
|
|
100
|
-
const clipScore = await scoreContent({
|
|
101
|
-
type: 'video-clip',
|
|
102
|
-
transcript: clipTranscript,
|
|
103
|
-
duration: 28_000, // 28 seconds
|
|
104
|
-
hookType: 'question',
|
|
105
|
-
targetPlatforms: ['tiktok', 'youtube-shorts', 'instagram-reels'],
|
|
106
|
-
});
|
|
107
|
-
```
|
|
1
|
+
---
|
|
2
|
+
name: "content-scoring"
|
|
3
|
+
pack: "@rune/content"
|
|
4
|
+
description: "Engagement and virality scoring for content pieces. Analyze hooks, readability, shareability, and platform fit."
|
|
5
|
+
model: sonnet
|
|
6
|
+
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# content-scoring
|
|
10
|
+
|
|
11
|
+
Engagement and virality scoring for content pieces. Analyze hooks, readability, shareability, and platform fit. Works for both video clips and written articles.
|
|
12
|
+
|
|
13
|
+
#### Workflow
|
|
14
|
+
|
|
15
|
+
**Step 1 — Detect content type**
|
|
16
|
+
Determine if scoring target is:
|
|
17
|
+
- **Video clip** (from video-repurpose pipeline or standalone)
|
|
18
|
+
- **Blog post / article** (markdown, MDX, or CMS content)
|
|
19
|
+
- **Social post** (short-form text, tweet, thread)
|
|
20
|
+
|
|
21
|
+
**Step 2 — Score across 4 dimensions**
|
|
22
|
+
```typescript
|
|
23
|
+
interface ContentScore {
|
|
24
|
+
hook: {
|
|
25
|
+
score: number; // 0-25
|
|
26
|
+
type: 'question' | 'statistic' | 'story' | 'contrast' | 'bold-claim' | 'how-to';
|
|
27
|
+
assessment: string; // Why this hook works or doesn't
|
|
28
|
+
};
|
|
29
|
+
engagement: {
|
|
30
|
+
score: number; // 0-25
|
|
31
|
+
readability: number; // Flesch-Kincaid grade level
|
|
32
|
+
pacing: string; // 'too-slow' | 'good' | 'too-fast'
|
|
33
|
+
callToAction: boolean;
|
|
34
|
+
};
|
|
35
|
+
value: {
|
|
36
|
+
score: number; // 0-25
|
|
37
|
+
teaches: boolean;
|
|
38
|
+
entertains: boolean;
|
|
39
|
+
uniqueInsight: boolean;
|
|
40
|
+
};
|
|
41
|
+
shareability: {
|
|
42
|
+
score: number; // 0-25
|
|
43
|
+
emotionalTrigger: string | null; // 'surprise' | 'anger' | 'joy' | 'fear'
|
|
44
|
+
quotable: string[]; // Extract quotable one-liners
|
|
45
|
+
platformFit: Record<Platform, number>; // 0-10 per platform
|
|
46
|
+
};
|
|
47
|
+
total: number; // 0-100
|
|
48
|
+
tier: 'viral' | 'strong' | 'average' | 'weak'; // >80 viral, >60 strong, >40 average
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Step 3 — Platform-specific optimization hints**
|
|
53
|
+
Each platform has different engagement patterns:
|
|
54
|
+
| Platform | Hook Window | Optimal Length | Key Factor |
|
|
55
|
+
|----------|-------------|---------------|------------|
|
|
56
|
+
| TikTok | 0-1s | 15-30s | Pattern interrupt, trend audio |
|
|
57
|
+
| YouTube | 0-3s | 8-12 min (long), 30-60s (Shorts) | Curiosity gap, retention graph |
|
|
58
|
+
| Twitter/X | First line | 280 chars or 4-tweet thread | Hot take, data point |
|
|
59
|
+
| LinkedIn | First 2 lines | 150-300 words | Professional insight, personal story |
|
|
60
|
+
| Blog | Title + first paragraph | 1500-2500 words | SEO keyword + value promise |
|
|
61
|
+
|
|
62
|
+
**Step 4 — Emit improvement suggestions**
|
|
63
|
+
For each dimension scoring < 20/25, emit specific actionable improvement:
|
|
64
|
+
- Hook weak → suggest rewrite with stronger opening pattern
|
|
65
|
+
- Engagement low → identify pacing issues, suggest cuts or restructures
|
|
66
|
+
- Value low → identify where content is generic, suggest unique angle
|
|
67
|
+
- Shareability low → suggest quotable reformulations, emotional triggers
|
|
68
|
+
|
|
69
|
+
#### Example
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Scoring a blog post
|
|
73
|
+
const score = await scoreContent({
|
|
74
|
+
type: 'article',
|
|
75
|
+
title: 'How We Cut Our AWS Bill by 60%',
|
|
76
|
+
content: articleMarkdown,
|
|
77
|
+
targetPlatforms: ['blog', 'twitter', 'linkedin'],
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Result:
|
|
81
|
+
// {
|
|
82
|
+
// hook: { score: 22, type: 'statistic', assessment: 'Strong — specific number creates curiosity' },
|
|
83
|
+
// engagement: { score: 18, readability: 8.2, pacing: 'good', callToAction: true },
|
|
84
|
+
// value: { score: 20, teaches: true, entertains: false, uniqueInsight: true },
|
|
85
|
+
// shareability: {
|
|
86
|
+
// score: 19, emotionalTrigger: 'surprise',
|
|
87
|
+
// quotable: ['We were paying $12K/mo for a service we used 3% of'],
|
|
88
|
+
// platformFit: { blog: 9, twitter: 8, linkedin: 9 }
|
|
89
|
+
// },
|
|
90
|
+
// total: 79, tier: 'strong'
|
|
91
|
+
// }
|
|
92
|
+
|
|
93
|
+
// Improvement suggestions:
|
|
94
|
+
// - Shareability: Add a contrarian angle ("Everyone says X, but we found Y")
|
|
95
|
+
// - Engagement: Add a visual comparison (before/after cost graph)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Scoring a video clip
|
|
100
|
+
const clipScore = await scoreContent({
|
|
101
|
+
type: 'video-clip',
|
|
102
|
+
transcript: clipTranscript,
|
|
103
|
+
duration: 28_000, // 28 seconds
|
|
104
|
+
hookType: 'question',
|
|
105
|
+
targetPlatforms: ['tiktok', 'youtube-shorts', 'instagram-reels'],
|
|
106
|
+
});
|
|
107
|
+
```
|