[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build an AI-Powered Changelog Generator

Build an AI-Powered Changelog Generator

Generate user-facing changelogs from Git commits and PRs using AI — grouping changes by category, writing clear descriptions, and publishing automatically with every release.

Development#typescript#tsconfig#type-checking#type-safety#javascript-migration
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 93/100·All SAFE
>

typescript

v1.0.0

Sets up and repairs TypeScript projects: picks tsconfig.json settings for a Node.js service, an npm library or a bundled web app, runs type-checking in CI, and replaces `any` and unsafe casts with types the compiler can verify. Use when someone asks to "set up TypeScript", "fix this tsconfig", "fix a TypeScript error", "upgrade to TypeScript 6 or 7", "migrate JavaScript to TypeScript", "publish types for a package", "run .ts files in Node" or "make this type-safe". Covers the changed defaults in TypeScript 6.0 and 7.0, module and moduleResolution, strict mode, project references, declaration output, narrowing, satisfies and common compiler errors.

93/100 quality
7.30× impact
SAFE· stale score
View skill
>

ai-sdk

v1.1.0

The Vercel AI SDK is a TypeScript toolkit for calling LLMs from Node.js, Next.js, Nuxt, SvelteKit and edge runtimes through one API. It covers text generation, streaming, structured output with Zod, tool calling, agents (ToolLoopAgent) and chat UIs with useChat. Use when asked to "add AI to my Next.js app", "stream a chat response", "get typed JSON from an LLM", "switch between OpenAI, Anthropic and Gemini", or "build a tool-calling agent".

77/100 quality
5.47× impact
SAFE· stale score
View skill
>

github-actions

v1.1.0

GitHub Actions runs CI/CD pipelines defined as YAML workflows inside a GitHub repository. Use when someone asks to "set up CI", "add a GitHub Actions workflow", "run tests on every pull request", "cache dependencies", "build a matrix", "deploy from Actions", "push a Docker image to ghcr.io" or "reuse a workflow", or needs triggers, secrets, environments, artifacts and permissions explained.

100/100 quality
2.66× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
$

The Problem

A SaaS product ships weekly releases but nobody writes changelogs. The "What's New" page hasn't been updated in 4 months. Customers discover new features by accident. The support team doesn't know what changed, leading to confused ticket responses. When a PM finally writes release notes, they take 3 hours to compile by reading 80+ commits and 20+ PRs — and the result is either too technical ("refactored auth middleware") or too vague ("improvements and bug fixes").

Step 1: Change Data Collector

typescript
// src/changelog/collector.ts
import { z } from 'zod';

const ChangeItem = z.object({
  type: z.enum(['commit', 'pr']),
  sha: z.string().optional(),
  prNumber: z.number().optional(),
  title: z.string(),
  body: z.string(),
  author: z.string(),
  labels: z.array(z.string()),
  files: z.array(z.string()),
  mergedAt: z.string().datetime(),
});

export async function collectChanges(
  owner: string,
  repo: string,
  fromTag: string,
  toTag: string,
  token: string
): Promise<z.infer<typeof ChangeItem>[]> {
  const headers = { Authorization: `token ${token}`, Accept: 'application/vnd.github.v3+json' };

  // Get commits between tags
  const compareRes = await fetch(
    `https://api.github.com/repos/${owner}/${repo}/compare/${fromTag}...${toTag}`,
    { headers }
  );
  const compare = await compareRes.json() as any;

  // Get PRs merged in this range
  const prs: z.infer<typeof ChangeItem>[] = [];
  const prNumbers = new Set<number>();

  for (const commit of compare.commits ?? []) {
    // Extract PR number from merge commit
    const prMatch = commit.commit.message.match(/Merge pull request #(\d+)/);
    if (prMatch) prNumbers.add(parseInt(prMatch[1]));

    // Also check for squash merges
    const squashMatch = commit.commit.message.match(/\(#(\d+)\)$/);
    if (squashMatch) prNumbers.add(parseInt(squashMatch[1]));
  }

  for (const prNum of prNumbers) {
    const prRes = await fetch(
      `https://api.github.com/repos/${owner}/${repo}/pulls/${prNum}`,
      { headers }
    );
    const pr = await prRes.json() as any;

    // Get files changed
    const filesRes = await fetch(
      `https://api.github.com/repos/${owner}/${repo}/pulls/${prNum}/files`,
      { headers }
    );
    const files = await filesRes.json() as any[];

    prs.push({
      type: 'pr',
      prNumber: prNum,
      title: pr.title,
      body: pr.body ?? '',
      author: pr.user.login,
      labels: (pr.labels ?? []).map((l: any) => l.name),
      files: files.map((f: any) => f.filename),
      mergedAt: pr.merged_at,
    });
  }

  return prs;
}

Step 2: AI Changelog Writer

typescript
// src/changelog/generator.ts
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const Changelog = z.object({
  version: z.string(),
  date: z.string(),
  summary: z.string().max(200),
  sections: z.array(z.object({
    category: z.enum(['✨ New Features', '🔧 Improvements', '🐛 Bug Fixes', '🔒 Security', '⚡ Performance', '📝 Documentation']),
    items: z.array(z.object({
      title: z.string(),
      description: z.string(),
      prNumber: z.number().optional(),
      impact: z.enum(['high', 'medium', 'low']),
    })),
  })),
  highlights: z.array(z.string()).max(3),
  breakingChanges: z.array(z.object({
    description: z.string(),
    migration: z.string(),
  })),
});

export async function generateChangelog(
  changes: any[],
  version: string,
  previousChangelog?: string
): Promise<z.infer<typeof Changelog>> {
  const changesSummary = changes.map(c =>
    `PR #${c.prNumber}: ${c.title}\n  Labels: ${c.labels.join(', ')}\n  Description: ${c.body.slice(0, 300)}\n  Files: ${c.files.slice(0, 10).join(', ')}`
  ).join('\n\n');

  const { object } = await generateObject({
    model: openai('gpt-4o'),
    schema: Changelog,
    prompt: `Generate a user-facing changelog from these pull requests.

Version: ${version}
Date: ${new Date().toISOString().split('T')[0]}

## Pull Requests:
${changesSummary}

Rules:
- Write for USERS, not developers. "You can now export reports as PDF" not "Added PDF export endpoint"
- Group by category: features, improvements, bugs, security, performance, docs
- Skip internal refactors, dependency updates, and CI changes unless user-facing
- Highlights: pick the 1-3 most impactful changes
- Breaking changes: mention any that require user action
- Be specific about what changed and why it matters
- Include PR numbers for reference
- Keep descriptions to 1-2 sentences max`,
  });

  return object;
}

Step 3: Publisher

typescript
// src/changelog/publisher.ts
import type { Changelog } from './generator';

export function renderMarkdown(changelog: any): string {
  let md = `# ${changelog.version} (${changelog.date})\n\n`;
  md += `> ${changelog.summary}\n\n`;

  if (changelog.highlights.length > 0) {
    md += `## Highlights\n\n`;
    for (const h of changelog.highlights) {
      md += `- 🌟 ${h}\n`;
    }
    md += '\n';
  }

  for (const section of changelog.sections) {
    if (section.items.length === 0) continue;
    md += `## ${section.category}\n\n`;
    for (const item of section.items) {
      md += `- **${item.title}**`;
      if (item.prNumber) md += ` (#${item.prNumber})`;
      md += `\n  ${item.description}\n`;
    }
    md += '\n';
  }

  if (changelog.breakingChanges.length > 0) {
    md += `## ⚠️ Breaking Changes\n\n`;
    for (const bc of changelog.breakingChanges) {
      md += `- ${bc.description}\n  **Migration:** ${bc.migration}\n`;
    }
  }

  return md;
}

export async function publishChangelog(
  changelog: any,
  targets: Array<'github' | 'website' | 'slack' | 'email'>
): Promise<void> {
  const markdown = renderMarkdown(changelog);

  for (const target of targets) {
    switch (target) {
      case 'github':
        // Create GitHub release
        break;
      case 'slack':
        // Post to #product-updates
        await fetch(process.env.SLACK_WEBHOOK!, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            text: `📋 *${changelog.version}* released!\n\n${changelog.summary}\n\n${changelog.highlights.map((h: string) => `• ${h}`).join('\n')}`,
          }),
        });
        break;
    }
  }
}

Results

  • Changelog turnaround: 2 minutes (was 3 hours manual compilation)
  • Update frequency: every release (was once every 4 months)
  • Customer awareness: 40% more users discover new features within a week
  • Support team: knows what changed, resolves tickets faster
  • PM time saved: 3 hours/week freed from release note writing
  • Quality: AI writes user-focused descriptions, not developer jargon
  • Consistency: every release has a changelog, no exceptions

Related use cases