From 6983a6a1759db62fab495e851665444ef3b8accf Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 09:46:01 +0000 Subject: [PATCH] feat(scripts): follow metadata instructions for described use-cases Load the Use Cases section of .github/instructions/metadata.instructions.md into the --describe prompt so generated use-cases match the repository style, and make useCase an array of phrases per usage group. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Q2D9GYbnSV56MAVVhHPRhH --- scripts/iconUsage.md | 2 +- scripts/iconUsage.mts | 2 +- scripts/iconUsageContext.mts | 54 ++++++++++++++++++++++++++++++------ 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/scripts/iconUsage.md b/scripts/iconUsage.md index 85359ceb2..3cdd981fb 100644 --- a/scripts/iconUsage.md +++ b/scripts/iconUsage.md @@ -24,7 +24,7 @@ pnpm icon-usage --icons-file exports.txt --describe --max-contexts 30 `--context` extracts how each icon is used in every validated file: what kind of usage it is (a JSX/template element, a prop such as `icon={Trash2}`, a config object such as `{ title: 'Settings', icon: Settings }`, or a `data-lucide` attribute), the parent elements (for example `DropdownMenuItem` or `Button`), the enclosing component, label text (`aria-label`, `title`, visible text, i18n keys like `t('actions.delete')`), event handlers, links, and a short code snippet. Each usage gets a heuristic description such as `Trash2 in labelled "Delete Project" within NavProjects`. Each icon gets a summary of the most common labels, parents and handlers, plus example links to the exact lines on GitHub. -`--describe` does the same, then sends the collected contexts to an OpenAI model (`OPENAI_API_KEY` required; `--model` or `OPENAI_MODEL` selects the model, `gpt-5-mini` by default). The model groups them into several use cases per icon, each with a short lowercase `useCase` phrase in the style of the `use-cases` field in `icons/*.json`, a description of where the icon appears in the UI, its approximate share of the sample, and example links. The icon's existing `use-cases` are included in the prompt. Responses are cached in the cache directory. +`--describe` does the same, then sends the collected contexts to an OpenAI model (`OPENAI_API_KEY` required; `--model` or `OPENAI_MODEL` selects the model, `gpt-5-mini` by default). The model groups them into 1 to 4 use cases per icon. Each has a `useCase` array of 1 to 3 phrases for the `use-cases` field in `icons/*.json`, written following the "Use Cases" section of `.github/instructions/metadata.instructions.md` (loaded into the prompt at runtime), a description of where the icon appears in the UI, its approximate share of the sample, and example links. The icon's existing `use-cases` are included in the prompt. Responses are cached in the cache directory. Use `--output ` (or `-o`) to write the results per icon to a JSON file shaped as `{ [icon]: [...usage] }`. With `--describe`, each entry is a use case (`useCase`, `description`, `sampleShare`, `examples`). Without it, each entry is an individual usage (`description`, `repository`, `url`, `kind`, `parents`, `component`, `labels`, `handlers`, `links`). `--output` implies `--context`, and the normal terminal, `--json` or `--csv` output is still printed. diff --git a/scripts/iconUsage.mts b/scripts/iconUsage.mts index 8d5de244f..34a24ab4e 100644 --- a/scripts/iconUsage.mts +++ b/scripts/iconUsage.mts @@ -1445,7 +1445,7 @@ const printUsage = (usage: Record) => { console.log(' use cases:'); for (const description of descriptions) { console.log( - ` - ${description.useCase} (~${formatPercent(description.sampleShare * 100)})`, + ` - ${description.useCase.join(' / ')} (~${formatPercent(description.sampleShare * 100)})`, ); console.log(` ${description.description}`); for (const example of description.examples) console.log(` ${example}`); diff --git a/scripts/iconUsageContext.mts b/scripts/iconUsageContext.mts index 6e5b54ec8..d0b50dc2b 100644 --- a/scripts/iconUsageContext.mts +++ b/scripts/iconUsageContext.mts @@ -514,7 +514,7 @@ export const summarizeContexts = (matches: ContextMatch[]): IconContextSummary = }; export type UsageDescription = { - useCase: string; + useCase: string[]; description: string; sampleShare: number; examples: string[]; @@ -531,6 +531,33 @@ type DescribeOptions = { writeCache: (key: string, value: UsageDescription[]) => Promise; }; +const METADATA_INSTRUCTIONS = new URL( + '../.github/instructions/metadata.instructions.md', + import.meta.url, +); + +const FALLBACK_USE_CASE_GUIDELINES = `Write each use-case as a short phrase of roughly 4-12 words starting with a present participle verb (e.g. "Indicating a low battery charge level"), from the interface's perspective, naming the concrete context where useful. No trailing period, no first person, no marketing language, and never just the icon's name.`; + +/** + * Returns the "Use Cases" section of the repository's metadata instructions + * (including its examples), so generated use-cases follow the same style. + */ +const loadUseCaseGuidelines = async () => { + try { + const fs = await import('node:fs/promises'); + const instructions = await fs.readFile(METADATA_INSTRUCTIONS, 'utf8'); + const start = instructions.indexOf('## Use Cases'); + if (start === -1) return FALLBACK_USE_CASE_GUIDELINES; + const sections = instructions.slice(start).split(/\n(?=## )/); + return sections + .filter((section, index) => index === 0 || section.startsWith('## Examples')) + .join('\n') + .trim(); + } catch { + return FALLBACK_USE_CASE_GUIDELINES; + } +}; + /** * Asks an OpenAI model to cluster the collected usage contexts into distinct, * real-world use cases for the icon. Responses are cached by prompt hash. @@ -560,15 +587,24 @@ export const describeIconUsage = async ({ const input = `You are maintaining the metadata for the Lucide icon library. Below are real usages of the \`${icon}\` icon found in public GitHub repositories, extracted from source code. -Group these usages into distinct use cases and describe each one. +Group these usages by what the icon means in them, and describe each group. -Guidelines: -- useCase: short lowercase phrase describing the concrete situation the icon represents (e.g. "deleting an item from a list"). No trailing punctuation. Match the style of the existing use-cases if any. +Fields for each group: +- useCase: an array of 1-3 use-case phrases for this group, written exactly as the use-case guidelines below describe. Add more than one phrase only when the group covers genuinely distinct uses; never rephrase the same idea. Don't repeat a phrase across groups. - description: one or two sentences describing how and where the icon appears in the UI (e.g. inside a ghost button in a table row, next to a "Delete" label). -- sampleShare: the approximate fraction (0-1) of the samples that belong to this use case. -- examples: ids of up to 3 samples that best illustrate the use case. -- Return between 1 and 8 use cases, most common first. Ignore samples without enough context to judge. -- Base everything on the samples; do not invent use cases that are not supported by them. +- sampleShare: the approximate fraction (0-1) of the samples that belong to this group. +- examples: ids of up to 3 samples that best illustrate the group. + +Rules: +- Return between 1 and 4 groups, most common first. Ignore samples without enough context to judge. +- The samples are your source material: extract the function the icon serves in the interface and discard implementation details such as component names, class names or file paths. +- Base everything on the samples; never invent use cases that are not supported by them. +- Match the style of the existing use-cases, and avoid duplicating them. + +Use-case guidelines (from .github/instructions/metadata.instructions.md): + +${await loadUseCaseGuidelines()} + Existing use-cases for "${icon}": ${JSON.stringify(existingUseCases)} @@ -591,7 +627,7 @@ ${JSON.stringify(samples, null, 1)}`; const schema = z.object({ useCases: z.array( z.object({ - useCase: z.string(), + useCase: z.array(z.string()), description: z.string(), sampleShare: z.number(), examples: z.array(z.number()),