From 078029c97676f0f252589e3713a613f5ae37da77 Mon Sep 17 00:00:00 2001 From: Pan YANG Date: Sat, 18 Jul 2026 05:04:16 +0800 Subject: [PATCH 1/5] fix(ci): restore project maintenance workflows --- .github/CODEOWNERS | 6 +- .github/workflows/ci.yml | 7 +- .github/workflows/deploy-staging.yml | 5 +- .github/workflows/scheduled-checks.yml | 83 ++++------------------- .github/workflows/update-github-stars.yml | 4 +- 5 files changed, 26 insertions(+), 79 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fd7d4417..1a23253d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,10 +15,10 @@ /manifests/**/*.json @aicodingstack/maintainers # Schema files - require careful review as they affect validation -/manifests/schemas/**/*.json @aicodingstack/maintainers +/manifests/$schemas/**/*.json @aicodingstack/maintainers # Build and validation scripts - require careful review -/scripts/**/*.mjs @aicodingstack/maintainers +/scripts/**/*.ts @aicodingstack/maintainers # GitHub Actions workflows - require review for security /.github/workflows/**/*.yml @aicodingstack/maintainers @@ -26,7 +26,7 @@ # Configuration files /package.json @aicodingstack/maintainers /tsconfig.json @aicodingstack/maintainers -/next.config.mjs @aicodingstack/maintainers +/next.config.ts @aicodingstack/maintainers /wrangler.toml @aicodingstack/maintainers # Documentation - can be reviewed by broader team diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fac20e09..77156147 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,6 +108,9 @@ jobs: env: BUILD_TIME: ${{ github.event.head_commit.timestamp }} + - name: Verify generated source is current + run: git diff --exit-code -- src/lib/generated + - name: Upload build artifacts uses: actions/upload-artifact@v6 with: @@ -125,8 +128,8 @@ jobs: steps: - name: Check all jobs run: | - if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then - echo "One or more CI jobs failed" + if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" || "${{ contains(needs.*.result, 'cancelled') }}" == "true" || "${{ contains(needs.*.result, 'skipped') }}" == "true" ]]; then + echo "One or more required CI jobs failed, were cancelled, or were skipped" exit 1 fi echo "All CI jobs passed successfully" diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 441d2ea2..86855f93 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -1,8 +1,7 @@ name: Deploy Staging on: - push: - branches: [develop] + workflow_dispatch: concurrency: group: staging-deploy @@ -38,7 +37,7 @@ jobs: - name: Build with OpenNext run: npm run build env: - BUILD_TIME: ${{ github.event.head_commit.timestamp }} + BUILD_TIME: ${{ github.event.repository.updated_at }} - name: Deploy to Staging run: npx --no-install opennextjs-cloudflare deploy --env staging diff --git a/.github/workflows/scheduled-checks.yml b/.github/workflows/scheduled-checks.yml index 2c1ab7e3..e56cfa76 100644 --- a/.github/workflows/scheduled-checks.yml +++ b/.github/workflows/scheduled-checks.yml @@ -1,23 +1,19 @@ -name: Scheduled Checks +name: Scheduled URL Checks on: schedule: # Run URL validation every Monday at 00:00 UTC - cron: '0 0 * * 1' - # Run GitHub stars update daily at 06:00 UTC - - cron: '0 6 * * *' workflow_dispatch: # Allow manual triggering permissions: - contents: write - pull-requests: write + contents: read issues: write jobs: validate-urls: name: Validate URLs runs-on: ubuntu-latest - if: github.event.schedule == '0 0 * * 1' || github.event_name == 'workflow_dispatch' steps: - name: Checkout code uses: actions/checkout@v6 @@ -37,7 +33,7 @@ jobs: continue-on-error: true - name: Create issue if URLs are broken - if: failure() + if: steps.validate.outcome == 'failure' uses: actions/github-script@v8 with: script: | @@ -75,73 +71,23 @@ jobs: body: body, labels: ['automated', 'url-validation', 'maintenance'] }); + } else { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issues.data[0].number, + body: body + }); } - update-github-stars: - name: Update GitHub Stars - runs-on: ubuntu-latest - if: github.event.schedule == '0 6 * * *' || github.event_name == 'workflow_dispatch' - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '22' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - # TODO: Configure GITHUB_TOKEN with appropriate permissions - # The fetch-github-stars script may need authentication - - name: Fetch GitHub stars - run: npm run fetch:github-stars - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - continue-on-error: true - - - name: Check for changes - id: git-check - run: | - git diff --exit-code manifests/ || echo "changed=true" >> $GITHUB_OUTPUT - - - name: Create pull request - if: steps.git-check.outputs.changed == 'true' - uses: peter-evans/create-pull-request@v8 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: 'chore: update GitHub stars data' - title: '🌟 Update GitHub Stars Data' - body: | - ## Automated GitHub Stars Update - - This PR updates the GitHub stars count for tools in our manifest files. - - **Updated:** ${{ steps.git-check.outputs.changed }} - - ### Changes - - Fetched latest star counts from GitHub API - - Updated manifest files with current data - - ### Review Checklist - - [ ] Verify star counts look reasonable - - [ ] No unexpected changes to other fields - - --- - *This PR was automatically created by the scheduled GitHub stars update workflow.* - branch: automated/update-github-stars - delete-branch: true - labels: | - automated - metadata - github-stars + - name: Fail workflow after reporting broken URLs + if: steps.validate.outcome == 'failure' + run: exit 1 workflow-summary: name: Workflow Summary runs-on: ubuntu-latest - needs: [validate-urls, update-github-stars] + needs: [validate-urls] if: always() steps: - name: Summary @@ -149,4 +95,3 @@ jobs: echo "## Scheduled Checks Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- URL Validation: ${{ needs.validate-urls.result }}" >> $GITHUB_STEP_SUMMARY - echo "- GitHub Stars Update: ${{ needs.update-github-stars.result }}" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/update-github-stars.yml b/.github/workflows/update-github-stars.yml index e2d69e92..ca7d8aa8 100644 --- a/.github/workflows/update-github-stars.yml +++ b/.github/workflows/update-github-stars.yml @@ -41,7 +41,7 @@ jobs: - name: Check for changes id: git-check run: | - git diff --exit-code manifests/github-stars.json src/lib/generated/github-stars.ts || echo "changes=true" >> $GITHUB_OUTPUT + git diff --exit-code data/github-stars.json src/lib/generated/github-stars.ts || echo "changes=true" >> $GITHUB_OUTPUT - name: Create Pull Request if: steps.git-check.outputs.changes == 'true' @@ -55,7 +55,7 @@ jobs: Automated weekly update of GitHub stars data. ### Changes - - Updated `manifests/github-stars.json` with latest star counts + - Updated `data/github-stars.json` with latest star counts - Regenerated `src/lib/generated/github-stars.ts` ### Source From dfe9b93afc7af39f9dbc39a3d5970f09d3c60330 Mon Sep 17 00:00:00 2001 From: Pan YANG Date: Sat, 18 Jul 2026 05:04:41 +0800 Subject: [PATCH 2/5] feat: improve search and model comparisons --- biome.json | 2 +- cspell.json | 2 + src/app/[locale]/models/[slug]/page.tsx | 5 + .../models/compare/[models]/page.client.tsx | 145 +++++++++++------- .../[locale]/models/compare/[models]/page.tsx | 19 --- src/app/[locale]/models/compare/page.tsx | 2 +- src/app/[locale]/models/page.tsx | 2 +- src/app/[locale]/search/page.client.tsx | 8 +- src/app/sitemap.ts | 39 ++++- src/components/Footer.tsx | 3 +- src/components/Header.tsx | 6 +- .../controls/ModelCompareSelector.tsx | 113 ++++++++++++++ src/components/product/GitHubStarHistory.tsx | 41 +---- src/lib/metadata/config.ts | 2 +- src/lib/search.ts | 72 ++++++--- tests/search-and-routes.test.ts | 51 ++++++ translations/de/components/common.json | 2 +- translations/de/components/controls.json | 1 + translations/de/pages/model-compare.json | 2 + translations/de/pages/model-detail.json | 2 +- translations/de/shared.json | 11 +- translations/en/components/common.json | 2 +- translations/en/components/controls.json | 1 + translations/en/pages/model-compare.json | 2 + translations/en/pages/model-detail.json | 2 +- translations/en/shared.json | 7 +- translations/es/components/common.json | 2 +- translations/es/components/controls.json | 1 + translations/es/pages/model-compare.json | 2 + translations/es/pages/model-detail.json | 2 +- translations/es/shared.json | 11 +- translations/fr/components/common.json | 2 +- translations/fr/components/controls.json | 1 + translations/fr/pages/model-compare.json | 2 + translations/fr/pages/model-detail.json | 2 +- translations/fr/shared.json | 11 +- translations/id/components/common.json | 2 +- translations/id/components/controls.json | 1 + translations/id/pages/model-compare.json | 2 + translations/id/pages/model-detail.json | 2 +- translations/id/shared.json | 11 +- translations/ja/components/common.json | 2 +- translations/ja/components/controls.json | 1 + translations/ja/pages/model-compare.json | 2 + translations/ja/pages/model-detail.json | 2 +- translations/ja/shared.json | 11 +- translations/ko/components/common.json | 2 +- translations/ko/components/controls.json | 1 + translations/ko/pages/model-compare.json | 2 + translations/ko/pages/model-detail.json | 2 +- translations/ko/shared.json | 11 +- translations/pt/components/common.json | 2 +- translations/pt/components/controls.json | 1 + translations/pt/pages/model-compare.json | 2 + translations/pt/pages/model-detail.json | 2 +- translations/pt/shared.json | 11 +- translations/ru/components/common.json | 2 +- translations/ru/components/controls.json | 1 + translations/ru/pages/model-compare.json | 2 + translations/ru/pages/model-detail.json | 2 +- translations/ru/shared.json | 11 +- translations/tr/components/common.json | 2 +- translations/tr/components/controls.json | 1 + translations/tr/pages/model-compare.json | 2 + translations/tr/pages/model-detail.json | 2 +- translations/tr/shared.json | 11 +- translations/zh-Hans/components/common.json | 2 +- translations/zh-Hans/components/controls.json | 1 + translations/zh-Hans/pages/model-compare.json | 2 + translations/zh-Hans/pages/model-detail.json | 2 +- translations/zh-Hans/shared.json | 11 +- translations/zh-Hant/components/common.json | 2 +- translations/zh-Hant/components/controls.json | 1 + translations/zh-Hant/pages/model-compare.json | 2 + translations/zh-Hant/pages/model-detail.json | 2 +- translations/zh-Hant/shared.json | 11 +- 76 files changed, 518 insertions(+), 206 deletions(-) create mode 100644 src/components/controls/ModelCompareSelector.tsx create mode 100644 tests/search-and-routes.test.ts diff --git a/biome.json b/biome.json index 8672eb21..ea459018 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.13/schema.json", "vcs": { "enabled": true, "clientKind": "git", diff --git a/cspell.json b/cspell.json index e80b2df2..e0ce4053 100644 --- a/cspell.json +++ b/cspell.json @@ -97,6 +97,7 @@ "İzleme", "İNDEKS", "İnsanlar", + "İşlev", "Integrationen", "Junie", "Kimi", @@ -210,6 +211,7 @@ "mengdebug", "menginstal", "modalli", + "modaliteleri", "modlu", "modu", "Modları", diff --git a/src/app/[locale]/models/[slug]/page.tsx b/src/app/[locale]/models/[slug]/page.tsx index 014280d2..9fe987ca 100644 --- a/src/app/[locale]/models/[slug]/page.tsx +++ b/src/app/[locale]/models/[slug]/page.tsx @@ -1,5 +1,6 @@ import { notFound } from 'next/navigation' import { getTranslations } from 'next-intl/server' +import { ModelCompareSelector } from '@/components/controls/ModelCompareSelector' import { BackToNavigation } from '@/components/navigation/BackToNavigation' import { Breadcrumb } from '@/components/navigation/Breadcrumb' import { ModelBenchmarks } from '@/components/product/ModelBenchmarks' @@ -120,6 +121,10 @@ export default async function ModelPage({ docsUrl={model.docsUrl || undefined} /> +
+ +
+ diff --git a/src/app/[locale]/models/compare/[models]/page.client.tsx b/src/app/[locale]/models/compare/[models]/page.client.tsx index a545e6a9..1467bd63 100644 --- a/src/app/[locale]/models/compare/[models]/page.client.tsx +++ b/src/app/[locale]/models/compare/[models]/page.client.tsx @@ -1,9 +1,11 @@ 'use client' import { ExternalLink } from 'lucide-react' +import { useTranslations } from 'next-intl' import { Fragment, useEffect, useState } from 'react' import type { Locale } from '@/i18n/config' import { Link, useRouter } from '@/i18n/navigation' +import { providersData } from '@/lib/generated' import type { ManifestBenchmarks, ManifestModel, @@ -66,8 +68,14 @@ const formatNumberToK = (value: number | null | undefined): string => { return value ? `${(value / 1000).toFixed(0)}K` : '-' } -const formatPrice = (value: number | null | undefined): string => { - return value ? `$${(value / 1000000).toFixed(2)} / 1M tokens` : '-' +const formatPrice = (value: number | null | undefined, locale: string): string => { + if (value === null || value === undefined) return '-' + const price = new Intl.NumberFormat(locale, { + style: 'currency', + currency: 'USD', + maximumFractionDigits: 3, + }).format(value) + return `${price} / 1M tokens` } const formatPercentage = (value: number | null | undefined): string => { @@ -87,8 +95,11 @@ const createNumberToKRenderer = (getValue: (model: ManifestModel) => number | nu return (model: ManifestModel) => formatNumberToK(getValue(model)) } -const createPriceRenderer = (getValue: (model: ManifestModel) => number | null | undefined) => { - return (model: ManifestModel) => formatPrice(getValue(model)) +const createPriceRenderer = ( + getValue: (model: ManifestModel) => number | null | undefined, + locale: string +) => { + return (model: ManifestModel) => formatPrice(getValue(model), locale) } const createBenchmarkRenderer = (getValue: (model: ManifestModel) => number | null | undefined) => { @@ -164,22 +175,35 @@ export default function ComparePageClient({ allModels, modelsMap, groups, - locale: _locale, + locale, }: ComparePageClientProps) { const router = useRouter() + const tPage = useTranslations('pages.modelCompare') + const tShared = useTranslations('shared') const [selectedModel1, setSelectedModel1] = useState(initialModels[0]?.id ?? '') const [selectedModel2, setSelectedModel2] = useState(initialModels[1]?.id ?? '') const [prevModel1, setPrevModel1] = useState(initialModels[0]?.id ?? '') const [prevModel2, setPrevModel2] = useState(initialModels[1]?.id ?? '') + const findProviderId = (vendor: string): string | null => { + const normalizedVendor = vendor.toLocaleLowerCase() + return ( + providersData.find( + provider => + provider.name.toLocaleLowerCase() === normalizedVendor || + provider.vendor.toLocaleLowerCase() === normalizedVendor + )?.id ?? null + ) + } + const getRows = (): Row[] => { // Basic info rows const basicInfoRows: Row[] = [ { group: 'basicInfo', - groupLabel: 'basicInfo', + groupLabel: tPage('basicInfo'), key: 'name', - label: 'model', + label: tShared('categories.singular.model'), render: model => (
{model.name} @@ -188,37 +212,42 @@ export default function ComparePageClient({ }, { group: 'basicInfo', - groupLabel: 'basicInfo', + groupLabel: tPage('basicInfo'), key: 'vendor', - label: 'vendor', - render: model => ( - - {model.vendor} - - ), + label: tShared('categories.singular.vendor'), + render: model => { + const providerId = findProviderId(model.vendor) + return providerId ? ( + + {model.vendor} + + ) : ( + model.vendor + ) + }, }, { group: 'basicInfo', - groupLabel: 'basicInfo', + groupLabel: tPage('basicInfo'), key: 'lifecycle', - label: 'lifecycle', - render: createSimpleRenderer(m => m.lifecycle), + label: tPage('lifecycle'), + render: model => tShared(`lifecycle.${model.lifecycle}`), }, { group: 'basicInfo', - groupLabel: 'basicInfo', + groupLabel: tPage('basicInfo'), key: 'releaseDate', - label: 'releaseDate', + label: tPage('releaseDate'), render: createSimpleRenderer(m => m.releaseDate), }, { group: 'basicInfo', - groupLabel: 'basicInfo', + groupLabel: tPage('basicInfo'), key: 'knowledgeCutoff', - label: 'knowledgeCutoff', + label: tPage('knowledgeCutoff'), render: createSimpleRenderer(m => m.knowledgeCutoff), }, ] @@ -227,65 +256,65 @@ export default function ComparePageClient({ const capabilitiesRows: Row[] = [ { group: 'capabilities', - groupLabel: 'capabilities', + groupLabel: tShared('capabilities.capabilities'), key: 'size', - label: 'modelSize', + label: tShared('terms.modelSize'), render: createSimpleRenderer(m => m.size), }, { group: 'capabilities', - groupLabel: 'capabilities', + groupLabel: tShared('capabilities.capabilities'), key: 'contextWindow', - label: 'contextWindow', + label: tShared('terms.contextWindow'), render: createNumberToKRenderer(m => m.contextWindow), }, { group: 'capabilities', - groupLabel: 'capabilities', + groupLabel: tShared('capabilities.capabilities'), key: 'maxOutput', - label: 'maxOutput', + label: tShared('terms.maxOutput'), render: createNumberToKRenderer(m => m.maxOutput), }, { group: 'capabilities', - groupLabel: 'capabilities', + groupLabel: tShared('capabilities.capabilities'), key: 'inputModalities', - label: 'inputModalities', + label: tShared('capabilities.inputModalities'), render: createTagsRenderer(m => m.inputModalities), }, { group: 'capabilities', - groupLabel: 'capabilities', + groupLabel: tShared('capabilities.capabilities'), key: 'outputModalities', - label: 'outputModalities', + label: tShared('capabilities.outputModalities'), render: createTagsRenderer(m => m.outputModalities), }, { group: 'capabilities', - groupLabel: 'capabilities', + groupLabel: tShared('capabilities.capabilities'), key: 'function-calling', - label: 'function-calling', + label: tShared('capabilities.functionCalling'), render: createCapabilityCheckRenderer('function-calling'), }, { group: 'capabilities', - groupLabel: 'capabilities', + groupLabel: tShared('capabilities.capabilities'), key: 'tool-choice', - label: 'tool-choice', + label: tShared('capabilities.toolChoice'), render: createCapabilityCheckRenderer('tool-choice'), }, { group: 'capabilities', - groupLabel: 'capabilities', + groupLabel: tShared('capabilities.capabilities'), key: 'structured-outputs', - label: 'structured-outputs', + label: tShared('capabilities.structuredOutputs'), render: createCapabilityCheckRenderer('structured-outputs'), }, { group: 'capabilities', - groupLabel: 'capabilities', + groupLabel: tShared('capabilities.capabilities'), key: 'reasoning', - label: 'reasoning', + label: tShared('capabilities.reasoning'), render: createCapabilityCheckRenderer('reasoning'), }, ] @@ -294,24 +323,24 @@ export default function ComparePageClient({ const pricingRows: Row[] = [ { group: 'pricing', - groupLabel: 'pricing', + groupLabel: tShared('terms.pricing'), key: 'inputPrice', - label: 'inputPrice', - render: createPriceRenderer(m => m.tokenPricing?.input), + label: tPage('inputPrice'), + render: createPriceRenderer(m => m.tokenPricing?.input, locale), }, { group: 'pricing', - groupLabel: 'pricing', + groupLabel: tShared('terms.pricing'), key: 'outputPrice', - label: 'outputPrice', - render: createPriceRenderer(m => m.tokenPricing?.output), + label: tPage('outputPrice'), + render: createPriceRenderer(m => m.tokenPricing?.output, locale), }, { group: 'pricing', - groupLabel: 'pricing', + groupLabel: tShared('terms.pricing'), key: 'cachePrice', - label: 'cachePrice', - render: createPriceRenderer(m => m.tokenPricing?.cache), + label: tPage('cachePrice'), + render: createPriceRenderer(m => m.tokenPricing?.cache, locale), }, ] @@ -327,9 +356,9 @@ export default function ComparePageClient({ ] const benchmarkRows: Row[] = benchmarkKeys.map(key => ({ group: 'benchmark', - groupLabel: 'benchmark', + groupLabel: tShared('terms.benchmarks'), key, - label: key, + label: tShared(`benchmarks.${key}`), render: createBenchmarkRenderer(m => m.benchmarks?.[key] ?? null), })) @@ -341,9 +370,9 @@ export default function ComparePageClient({ ] const platformRows: Row[] = platformKeys.map(key => ({ group: 'platforms', - groupLabel: 'ai platforms', + groupLabel: tShared('labels.findOnAiPlatforms'), key, - label: key, + label: tShared(`platforms.${key}`), render: createPlatformLinkRenderer(m => m.platformUrls?.[key] ?? null), })) @@ -432,7 +461,7 @@ export default function ComparePageClient({ className="appearance-none px-4 py-0 pr-8 bg-[var(--color-bg)] border border-[var(--color-border)] text-[var(--color-text)] min-w-[200px] min-h-[46px] text-center w-full cursor-pointer" style={selectStyle} > - + {availableModels.map(m => (
diff --git a/src/app/[locale]/models/compare/[models]/page.tsx b/src/app/[locale]/models/compare/[models]/page.tsx index 174a8622..4ba50c19 100644 --- a/src/app/[locale]/models/compare/[models]/page.tsx +++ b/src/app/[locale]/models/compare/[models]/page.tsx @@ -10,25 +10,6 @@ import ComparisonPageClient from './page.client' export const revalidate = 3600 -export async function generateStaticParams() { - const allModelIds = allModels.map(m => m.id) - const params: { models: string }[] = [] - - // Generate all two-model comparisons - for (let i = 0; i < allModelIds.length; i++) { - for (let j = i + 1; j < allModelIds.length; j++) { - params.push({ - models: `${allModelIds[i]}-vs-${allModelIds[j]}`, - }) - } - } - - // Add empty path for dynamic selection - params.push({ models: '' }) - - return params -} - export async function generateMetadata({ params, }: { diff --git a/src/app/[locale]/models/compare/page.tsx b/src/app/[locale]/models/compare/page.tsx index 8986a7df..f85c4c20 100644 --- a/src/app/[locale]/models/compare/page.tsx +++ b/src/app/[locale]/models/compare/page.tsx @@ -10,7 +10,7 @@ import ComparisonPageClient from './[models]/page.client' export const revalidate = 3600 function getComparisonGroups(): string[] { - return ['basicInfo', 'capabilities', 'performance', 'pricing', 'platforms'] + return ['basicInfo', 'capabilities', 'pricing', 'benchmark', 'platforms'] } export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) { diff --git a/src/app/[locale]/models/page.tsx b/src/app/[locale]/models/page.tsx index a4464db4..fdbe952b 100644 --- a/src/app/[locale]/models/page.tsx +++ b/src/app/[locale]/models/page.tsx @@ -11,7 +11,7 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s locale: locale as Locale, category: 'models', translationNamespace: 'pages.models', - additionalKeywords: ['LLM for coding', 'Claude Sonnet', 'GPT-4', 'coding AI models 2025'], + additionalKeywords: ['LLM for coding', 'Claude Sonnet', 'GPT-4', 'coding AI models'], }) } diff --git a/src/app/[locale]/search/page.client.tsx b/src/app/[locale]/search/page.client.tsx index 0458d22b..fd846187 100644 --- a/src/app/[locale]/search/page.client.tsx +++ b/src/app/[locale]/search/page.client.tsx @@ -7,6 +7,7 @@ import SearchInput from '@/components/controls/SearchInput' import Footer from '@/components/Footer' import Header from '@/components/Header' import { Link, useRouter } from '@/i18n/navigation' +import { buildManifestPath } from '@/lib/manifest-registry' import type { SearchResult } from '@/lib/search' import { search } from '@/lib/search' @@ -49,11 +50,6 @@ export default function SearchPageClient({ locale, initialQuery }: Props) { setTimeout(() => setIsSearching(false), 300) } - // Get category route - const getCategoryRoute = (category: string, id: string) => { - return `/${category}/${id}` - } - return ( <>
@@ -94,7 +90,7 @@ export default function SearchPageClient({ locale, initialQuery }: Props) { {results.map(result => ( {/* Header with category badge */} diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 31fd874b..11f75bd9 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -1,6 +1,13 @@ import type { MetadataRoute } from 'next' import { locales } from '@/i18n/config' -import { clisData, idesData, modelsData, providersData } from '@/lib/generated' +import { + clisData, + extensionsData, + idesData, + modelsData, + providersData, + vendorsData, +} from '@/lib/generated' import { articles } from '@/lib/generated/articles' import { docSections } from '@/lib/generated/docs' import { SITE_CONFIG } from '@/lib/metadata/config' @@ -51,6 +58,14 @@ export default function sitemap(): MetadataRoute.Sitemap { { path: '/ai-coding-stack', priority: 0.9, changeFreq: 'weekly' as const }, { path: '/docs', priority: 0.8, changeFreq: 'weekly' as const }, { path: '/curated-collections', priority: 0.7, changeFreq: 'monthly' as const }, + { path: '/manifesto', priority: 0.7, changeFreq: 'monthly' as const }, + { path: '/ai-coding-landscape', priority: 0.8, changeFreq: 'weekly' as const }, + { path: '/open-source-rank', priority: 0.8, changeFreq: 'daily' as const }, + { path: '/ides/comparison', priority: 0.7, changeFreq: 'weekly' as const }, + { path: '/clis/comparison', priority: 0.7, changeFreq: 'weekly' as const }, + { path: '/extensions/comparison', priority: 0.7, changeFreq: 'weekly' as const }, + { path: '/models/comparison', priority: 0.7, changeFreq: 'weekly' as const }, + { path: '/models/compare', priority: 0.7, changeFreq: 'weekly' as const }, ] const staticPages: MetadataRoute.Sitemap = staticPaths.flatMap(({ path, priority, changeFreq }) => @@ -123,6 +138,26 @@ export default function sitemap(): MetadataRoute.Sitemap { }) ) + const extensionDetailPages: MetadataRoute.Sitemap = (extensionsData as unknown as ManifestItem[]) + .filter(extension => extension.id) + .flatMap(extension => + generateLocalizedPages(baseUrl, `/extensions/${extension.id}`, { + lastModified: buildDate, + changeFrequency: 'weekly' as const, + priority: 0.6, + }) + ) + + const vendorDetailPages: MetadataRoute.Sitemap = (vendorsData as unknown as ManifestItem[]) + .filter(vendor => vendor.id) + .flatMap(vendor => + generateLocalizedPages(baseUrl, `/vendors/${vendor.id}`, { + lastModified: buildDate, + changeFrequency: 'weekly' as const, + priority: 0.6, + }) + ) + return [ ...staticPages, ...articlePages, @@ -131,5 +166,7 @@ export default function sitemap(): MetadataRoute.Sitemap { ...cliDetailPages, ...modelDetailPages, ...providerDetailPages, + ...extensionDetailPages, + ...vendorDetailPages, ] } diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 48964c41..017d1e8c 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -4,6 +4,7 @@ import { useTranslations } from 'next-intl' import LanguageSwitcher from '@/components/controls/LanguageSwitcher' import ThemeSwitcher from '@/components/controls/ThemeSwitcher' import { Link } from '@/i18n/navigation' +import { METADATA_DEFAULTS } from '@/lib/metadata/config' // Footer link list component to reduce code duplication interface FooterLinkListProps { @@ -96,7 +97,7 @@ export default function Footer() {
- {tComponent('footer.copyright')} + {tComponent('footer.copyright', { year: METADATA_DEFAULTS.currentYear })}
) diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 3ec3c945..b53618c7 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -48,7 +48,7 @@ function Header() { }, { href: '/ai-coding-landscape', translationKey: 'header.landscape', namespace: 'header' }, { - href: '#', + href: '/open-source-rank', translationKey: 'header.ranking', namespace: 'header', hasMegaMenu: true, @@ -113,6 +113,10 @@ function Header() { className="relative" onMouseEnter={() => handleMegaMenuOpen(item.megaMenuType!)} onMouseLeave={handleMegaMenuClose} + onFocus={() => handleMegaMenuOpen(item.megaMenuType!)} + onBlur={event => { + if (!event.currentTarget.contains(event.relatedTarget)) handleMegaMenuClose() + }} > = { latest: 0, maintained: 1, deprecated: 2 } + const availableModels = modelsData + .filter(model => { + if (model.id === currentModelId) return false + if (!normalizedQuery) return true + return ( + model.name.toLocaleLowerCase().includes(normalizedQuery) || + model.vendor.toLocaleLowerCase().includes(normalizedQuery) + ) + }) + .sort((a, b) => { + const orderA = lifecycleOrder[a.lifecycle || 'maintained'] ?? 999 + const orderB = lifecycleOrder[b.lifecycle || 'maintained'] ?? 999 + return orderA - orderB || a.name.localeCompare(b.name) + }) + + return ( +
+ + + {isOpen && ( + <> +
+ ) +} diff --git a/src/components/product/GitHubStarHistory.tsx b/src/components/product/GitHubStarHistory.tsx index 199236b0..73b3d5ba 100644 --- a/src/components/product/GitHubStarHistory.tsx +++ b/src/components/product/GitHubStarHistory.tsx @@ -50,22 +50,9 @@ export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) { const [, owner, repo] = match const repoFullName = `${owner}/${repo}` - // Use Star History API - const response = await fetch( - `https://api.star-history.com/svg?repos=${repoFullName}&type=Date` - ) - - if (!response.ok) { - throw new Error('Failed to fetch star history') - } - - // Star History API returns SVG, but we can use their JSON endpoint - // Alternative: use GitHub API directly const jsonResponse = await fetch(`https://api.star-history.com/json?repos=${repoFullName}`) if (!jsonResponse.ok) { - // Fallback: generate mock data based on current stars - // This is a temporary solution until we implement proper data fetching throw new Error('Star history API unavailable') } @@ -85,19 +72,7 @@ export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) { } catch (err) { console.error('Error fetching star history:', err) setError(err instanceof Error ? err.message : 'Failed to load star history') - - // Generate fallback data for demonstration - const currentDate = new Date() - const fallbackData: StarDataPoint[] = [] - for (let i = 12; i >= 0; i--) { - const date = new Date(currentDate) - date.setMonth(date.getMonth() - i) - fallbackData.push({ - date: date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }), - stars: Math.floor(Math.random() * 50000) + 10000, // Mock data - }) - } - setData(fallbackData) + setData([]) } finally { setLoading(false) } @@ -122,9 +97,7 @@ export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) { ) } - if (error && data.length === 0) { - return null // Don't show anything if there's an error and no fallback data - } + if (error || data.length === 0) return null return (
@@ -139,7 +112,6 @@ export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) { {tComponent('githubStarHistory.description')}

- {/* Chart */}
@@ -209,15 +181,6 @@ export function GitHubStarHistory({ githubUrl }: GitHubStarHistoryProps) {
- - {/* Footer Note */} - {error && ( -
-

- {tComponent('githubStarHistory.fallbackNote')} -

-
- )}
diff --git a/src/lib/metadata/config.ts b/src/lib/metadata/config.ts index ce3457a1..e72d2495 100644 --- a/src/lib/metadata/config.ts +++ b/src/lib/metadata/config.ts @@ -33,7 +33,7 @@ export const OG_IMAGE_CONFIG = { } as const export const METADATA_DEFAULTS = { - currentYear: 2025, + currentYear: new Date().getUTCFullYear(), titleSeparator: ' - ', listSeparator: ' | ', siteName: 'AI Coding Stack', diff --git a/src/lib/search.ts b/src/lib/search.ts index aace7a40..60213e46 100644 --- a/src/lib/search.ts +++ b/src/lib/search.ts @@ -36,6 +36,25 @@ function getLocalizedName( return item.name } +function getLocalizedDescription( + item: { description: string; translations?: { [locale: string]: { description?: string } } }, + locale?: string +): string { + if (locale && item.translations?.[locale]?.description) { + return item.translations[locale].description + } + return item.description +} + +function flattenSearchValue(value: unknown): string[] { + if (typeof value === 'string') return [value] + if (Array.isArray(value)) return value.flatMap(flattenSearchValue) + if (value && typeof value === 'object') { + return Object.values(value).flatMap(flattenSearchValue) + } + return [] +} + /** * Check if query matches item name (supports translations) */ @@ -45,21 +64,33 @@ function matchesQuery( description: string translations?: { [locale: string]: { name?: string; description?: string } } }, - query: string + query: string, + locale?: string ): boolean { const lowerQuery = query.toLowerCase() - - // Search in default name only - if (item.name.toLowerCase().includes(lowerQuery)) return true - - // Search in translations (name only) - if (item.translations) { - for (const translation of Object.values(item.translations)) { - if (translation.name?.toLowerCase().includes(lowerQuery)) return true - } + const searchableItem = item as typeof item & { + vendor?: string + capabilities?: string[] + inputModalities?: string[] + outputModalities?: string[] + platforms?: unknown + type?: string } - - return false + const translation = locale ? item.translations?.[locale] : undefined + const values = [ + item.name, + item.description, + translation?.name, + translation?.description, + searchableItem.vendor, + searchableItem.type, + ...flattenSearchValue(searchableItem.capabilities), + ...flattenSearchValue(searchableItem.inputModalities), + ...flattenSearchValue(searchableItem.outputModalities), + ...flattenSearchValue(searchableItem.platforms), + ] + + return values.some(value => value?.toLowerCase().includes(lowerQuery)) } /** @@ -70,13 +101,14 @@ function calculateRelevance( item: { name: string description: string - i18n?: { [locale: string]: { name?: string; description?: string } } + translations?: { [locale: string]: { name?: string; description?: string } } }, query: string, locale?: string ): number { const lowerQuery = query.toLowerCase() const name = getLocalizedName(item, locale).toLowerCase() + const description = getLocalizedDescription(item, locale).toLowerCase() // Exact match in name if (name === lowerQuery) return 100 @@ -87,17 +119,19 @@ function calculateRelevance( // Contains query in name if (name.includes(lowerQuery)) return 80 - return 0 + if (description.includes(lowerQuery)) return 50 + + return 10 } /** * Build unified search index from all product manifests */ -export function buildSearchIndex(): SearchResult[] { +export function buildSearchIndex(locale?: string): SearchResult[] { return getAllManifests().map(({ category, data: item }) => ({ id: item.id, - name: item.name, - description: item.description, + name: getLocalizedName(item, locale), + description: getLocalizedDescription(item, locale), category, data: item, })) @@ -114,9 +148,9 @@ export function search(query: string, locale?: string): SearchResult[] { return [] } - const index = buildSearchIndex() + const index = buildSearchIndex(locale) const results = index - .filter(item => matchesQuery(item.data, query)) + .filter(item => matchesQuery(item.data, query, locale)) .map(item => ({ ...item, relevance: calculateRelevance(item.data, query, locale), diff --git a/tests/search-and-routes.test.ts b/tests/search-and-routes.test.ts new file mode 100644 index 00000000..1ac8394d --- /dev/null +++ b/tests/search-and-routes.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'vitest' +import { + buildManifestPath, + getAllCategories, + getAllManifests, + getCategoryRouteBase, +} from '@/lib/manifest-registry' +import { search } from '@/lib/search' + +describe('manifest route contracts', () => { + it('maps every category to its public route base', () => { + expect( + Object.fromEntries( + getAllCategories().map(category => [category, getCategoryRouteBase(category)]) + ) + ).toEqual({ + ides: 'ides', + clis: 'clis', + extensions: 'extensions', + models: 'models', + providers: 'model-providers', + vendors: 'vendors', + }) + }) + + it('builds a valid detail path for every manifest entry', () => { + for (const { category, data } of getAllManifests()) { + expect(buildManifestPath(category, data.id)).toBe( + `/${getCategoryRouteBase(category)}/${data.id}` + ) + } + }) + + it('uses the model-provider route for providers', () => { + expect(buildManifestPath('providers', 'openai')).toBe('/model-providers/openai') + }) +}) + +describe('search', () => { + it('returns localized display content and searches localized descriptions', () => { + const cursor = search('代码生成', 'zh-Hans').find(result => result.id === 'cursor') + + expect(cursor?.category).toBe('ides') + expect(cursor?.description).toContain('代码生成') + }) + + it('searches vendors and model capabilities', () => { + expect(search('Anysphere').some(result => result.id === 'cursor')).toBe(true) + expect(search('structured-outputs').some(result => result.category === 'models')).toBe(true) + }) +}) diff --git a/translations/de/components/common.json b/translations/de/components/common.json index 2ce5eb42..4bad9d7b 100644 --- a/translations/de/components/common.json +++ b/translations/de/components/common.json @@ -1,6 +1,6 @@ { "footer": { - "copyright": "© 2025 AI Coding Stack • Mit ❤︎ erstellt • Open Source", + "copyright": "© {year} AI Coding Stack • Mit ❤︎ erstellt • Open Source", "openSource": "Open-Source-KI-Coding-Metadaten-Repository.", "selectLanguage": "Sprache auswählen", "tagline": "Ihr AI-Coding-Ökosystem-Hub.", diff --git a/translations/de/components/controls.json b/translations/de/components/controls.json index a2c3f2db..caa82225 100644 --- a/translations/de/components/controls.json +++ b/translations/de/components/controls.json @@ -13,6 +13,7 @@ "sortNameDesc": "Name (Z-A)" }, "searchDialog": { + "close": "Vergleichsauswahl schließen", "empty": "Start typing to search...", "navigate": "to navigate", "noResultsFor": "No results for \"{query}\"", diff --git a/translations/de/pages/model-compare.json b/translations/de/pages/model-compare.json index 794e957d..ea568e3d 100644 --- a/translations/de/pages/model-compare.json +++ b/translations/de/pages/model-compare.json @@ -9,5 +9,7 @@ "lifecycle": "Lebenszyklus", "outputPrice": "Ausgabepreis", "releaseDate": "Veröffentlichungsdatum", + "selectModel": "Modell auswählen", + "selectTwoModels": "Zwei Modelle zum Vergleichen auswählen", "verified": "Verifiziert" } diff --git a/translations/de/pages/model-detail.json b/translations/de/pages/model-detail.json index cef0fe0e..41cefacd 100644 --- a/translations/de/pages/model-detail.json +++ b/translations/de/pages/model-detail.json @@ -1,3 +1,3 @@ { - "metaTitle": "KI-Programmiermodell | Spezifikationen & Preise 2025" + "metaTitle": "KI-Programmiermodell | Spezifikationen & Preise" } diff --git a/translations/de/shared.json b/translations/de/shared.json index 66cc6f16..9eee5ebd 100644 --- a/translations/de/shared.json +++ b/translations/de/shared.json @@ -28,9 +28,13 @@ "webDevArena": "WebDev Arena" }, "capabilities": { - "capabilities": "Capabilities", - "inputModalities": "Input Modalities", - "outputModalities": "Output Modalities" + "capabilities": "Fähigkeiten", + "functionCalling": "Funktionsaufruf", + "inputModalities": "Eingabemodalitäten", + "outputModalities": "Ausgabemodalitäten", + "reasoning": "Schlussfolgern", + "structuredOutputs": "Strukturierte Ausgaben", + "toolChoice": "Werkzeugauswahl" }, "categories": { "all": { @@ -96,6 +100,7 @@ "aiCoding": "AI-Codierung", "aiCodingStack": "AI Coding Stack", "articles": "Artikel", + "benchmarks": "Benchmarks", "collections": "Sammlungen", "community": "Gemeinschaft", "communityLinks": "Community-Links", diff --git a/translations/en/components/common.json b/translations/en/components/common.json index c08659a7..cfd36031 100644 --- a/translations/en/components/common.json +++ b/translations/en/components/common.json @@ -1,6 +1,6 @@ { "footer": { - "copyright": "© 2025 AI Coding Stack • Built with ❤︎ • Open Source", + "copyright": "© {year} AI Coding Stack • Built with ❤︎ • Open Source", "openSource": "Open source AI coding metadata repository.", "selectLanguage": "Select Language", "tagline": "Your AI Coding Ecosystem Hub.", diff --git a/translations/en/components/controls.json b/translations/en/components/controls.json index 3c02df41..3e31670e 100644 --- a/translations/en/components/controls.json +++ b/translations/en/components/controls.json @@ -13,6 +13,7 @@ "sortNameDesc": "Name (Z-A)" }, "searchDialog": { + "close": "Close comparison selector", "empty": "Start typing to search...", "navigate": "to navigate", "noResultsFor": "No results for \"{query}\"", diff --git a/translations/en/pages/model-compare.json b/translations/en/pages/model-compare.json index 7a257d37..f9183286 100644 --- a/translations/en/pages/model-compare.json +++ b/translations/en/pages/model-compare.json @@ -9,5 +9,7 @@ "lifecycle": "Lifecycle", "outputPrice": "Output Price", "releaseDate": "Release Date", + "selectModel": "Select a model", + "selectTwoModels": "Select two models to compare", "verified": "Verified" } diff --git a/translations/en/pages/model-detail.json b/translations/en/pages/model-detail.json index b7abe73f..502f26da 100644 --- a/translations/en/pages/model-detail.json +++ b/translations/en/pages/model-detail.json @@ -1,3 +1,3 @@ { - "metaTitle": "AI Coding Model | Specs & Pricing 2025" + "metaTitle": "AI Coding Model | Specs & Pricing" } diff --git a/translations/en/shared.json b/translations/en/shared.json index 106fbe3e..5fd10d5f 100644 --- a/translations/en/shared.json +++ b/translations/en/shared.json @@ -29,8 +29,12 @@ }, "capabilities": { "capabilities": "Capabilities", + "functionCalling": "Function Calling", "inputModalities": "Input Modalities", - "outputModalities": "Output Modalities" + "outputModalities": "Output Modalities", + "reasoning": "Reasoning", + "structuredOutputs": "Structured Outputs", + "toolChoice": "Tool Choice" }, "categories": { "all": { @@ -96,6 +100,7 @@ "aiCoding": "AI Coding", "aiCodingStack": "AI Coding Stack", "articles": "Articles", + "benchmarks": "Benchmarks", "collections": "Collections", "community": "Community", "communityLinks": "Community Links", diff --git a/translations/es/components/common.json b/translations/es/components/common.json index b8814f41..f2cffd60 100644 --- a/translations/es/components/common.json +++ b/translations/es/components/common.json @@ -1,6 +1,6 @@ { "footer": { - "copyright": "© 2025 AI Coding Stack • Construido con ❤︎ • Código abierto", + "copyright": "© {year} AI Coding Stack • Construido con ❤︎ • Código abierto", "openSource": "Repositorio de metadatos de codificación con IA de código abierto.", "selectLanguage": "Seleccionar idioma", "tagline": "Tu centro del ecosistema de codificación con IA.", diff --git a/translations/es/components/controls.json b/translations/es/components/controls.json index f73ee48a..72679e3e 100644 --- a/translations/es/components/controls.json +++ b/translations/es/components/controls.json @@ -13,6 +13,7 @@ "sortNameDesc": "Nombre (Z-A)" }, "searchDialog": { + "close": "Cerrar selector de comparación", "empty": "Start typing to search...", "navigate": "to navigate", "noResultsFor": "No results for \"{query}\"", diff --git a/translations/es/pages/model-compare.json b/translations/es/pages/model-compare.json index 386686a0..7848b533 100644 --- a/translations/es/pages/model-compare.json +++ b/translations/es/pages/model-compare.json @@ -9,5 +9,7 @@ "lifecycle": "Ciclo de vida", "outputPrice": "Precio de salida", "releaseDate": "Fecha de lanzamiento", + "selectModel": "Seleccionar un modelo", + "selectTwoModels": "Selecciona dos modelos para comparar", "verified": "Verificado" } diff --git a/translations/es/pages/model-detail.json b/translations/es/pages/model-detail.json index ea4f4c68..86cc12d6 100644 --- a/translations/es/pages/model-detail.json +++ b/translations/es/pages/model-detail.json @@ -1,3 +1,3 @@ { - "metaTitle": "Modelo de IA para Programación | Especificaciones y Precios 2025" + "metaTitle": "Modelo de IA para Programación | Especificaciones y Precios" } diff --git a/translations/es/shared.json b/translations/es/shared.json index 0eac8e82..783eec29 100644 --- a/translations/es/shared.json +++ b/translations/es/shared.json @@ -28,9 +28,13 @@ "webDevArena": "WebDev Arena" }, "capabilities": { - "capabilities": "Capabilities", - "inputModalities": "Input Modalities", - "outputModalities": "Output Modalities" + "capabilities": "Capacidades", + "functionCalling": "Llamada de funciones", + "inputModalities": "Modalidades de entrada", + "outputModalities": "Modalidades de salida", + "reasoning": "Razonamiento", + "structuredOutputs": "Salidas estructuradas", + "toolChoice": "Selección de herramientas" }, "categories": { "all": { @@ -96,6 +100,7 @@ "aiCoding": "Codificación con IA", "aiCodingStack": "AI Coding Stack", "articles": "Artículos", + "benchmarks": "Evaluaciones", "collections": "Colecciones", "community": "Comunidad", "communityLinks": "Enlaces de la comunidad", diff --git a/translations/fr/components/common.json b/translations/fr/components/common.json index cdea613f..5b27b58c 100644 --- a/translations/fr/components/common.json +++ b/translations/fr/components/common.json @@ -1,6 +1,6 @@ { "footer": { - "copyright": "© 2025 AI Coding Stack • Construit avec ❤︎ • Open Source", + "copyright": "© {year} AI Coding Stack • Construit avec ❤︎ • Open Source", "openSource": "Référentiel de métadonnées de codage IA open source.", "selectLanguage": "Choisir la langue", "tagline": "Votre centre de l'écosystème de codage IA.", diff --git a/translations/fr/components/controls.json b/translations/fr/components/controls.json index d0f5d035..0edeb9c6 100644 --- a/translations/fr/components/controls.json +++ b/translations/fr/components/controls.json @@ -13,6 +13,7 @@ "sortNameDesc": "Nom (Z-A)" }, "searchDialog": { + "close": "Fermer le sélecteur de comparaison", "empty": "Start typing to search...", "navigate": "to navigate", "noResultsFor": "No results for \"{query}\"", diff --git a/translations/fr/pages/model-compare.json b/translations/fr/pages/model-compare.json index 27440398..da281397 100644 --- a/translations/fr/pages/model-compare.json +++ b/translations/fr/pages/model-compare.json @@ -9,5 +9,7 @@ "lifecycle": "Cycle de vie", "outputPrice": "Prix de sortie", "releaseDate": "Date de sortie", + "selectModel": "Sélectionner un modèle", + "selectTwoModels": "Sélectionnez deux modèles à comparer", "verified": "Vérifié" } diff --git a/translations/fr/pages/model-detail.json b/translations/fr/pages/model-detail.json index b6942477..54ef2e22 100644 --- a/translations/fr/pages/model-detail.json +++ b/translations/fr/pages/model-detail.json @@ -1,3 +1,3 @@ { - "metaTitle": "Modèle d'IA pour le Code | Spécifications et Prix 2025" + "metaTitle": "Modèle d'IA pour le Code | Spécifications et Prix" } diff --git a/translations/fr/shared.json b/translations/fr/shared.json index 4f642ccb..af9023a9 100644 --- a/translations/fr/shared.json +++ b/translations/fr/shared.json @@ -28,9 +28,13 @@ "webDevArena": "WebDev Arena" }, "capabilities": { - "capabilities": "Capabilities", - "inputModalities": "Input Modalities", - "outputModalities": "Output Modalities" + "capabilities": "Capacités", + "functionCalling": "Appel de fonction", + "inputModalities": "Modalités d'entrée", + "outputModalities": "Modalités de sortie", + "reasoning": "Raisonnement", + "structuredOutputs": "Sorties structurées", + "toolChoice": "Choix d'outil" }, "categories": { "all": { @@ -96,6 +100,7 @@ "aiCoding": "Codage IA", "aiCodingStack": "AI Coding Stack", "articles": "Articles", + "benchmarks": "Bancs d'essai", "collections": "Collections", "community": "Communauté", "communityLinks": "Liens de la communauté", diff --git a/translations/id/components/common.json b/translations/id/components/common.json index 92310421..55a6d494 100644 --- a/translations/id/components/common.json +++ b/translations/id/components/common.json @@ -1,6 +1,6 @@ { "footer": { - "copyright": "© 2025 AI Coding Stack • Dibuat dengan ❤︎ • Open Source", + "copyright": "© {year} AI Coding Stack • Dibuat dengan ❤︎ • Open Source", "openSource": "Repositori metadata coding AI open source.", "selectLanguage": "Pilih Bahasa", "tagline": "Pusat ekosistem coding AI Anda.", diff --git a/translations/id/components/controls.json b/translations/id/components/controls.json index 528c0a46..a1528f00 100644 --- a/translations/id/components/controls.json +++ b/translations/id/components/controls.json @@ -13,6 +13,7 @@ "sortNameDesc": "Nama (Z-A)" }, "searchDialog": { + "close": "Tutup pemilih perbandingan", "empty": "Start typing to search...", "navigate": "to navigate", "noResultsFor": "No results for \"{query}\"", diff --git a/translations/id/pages/model-compare.json b/translations/id/pages/model-compare.json index d85da557..642076c0 100644 --- a/translations/id/pages/model-compare.json +++ b/translations/id/pages/model-compare.json @@ -9,5 +9,7 @@ "lifecycle": "Siklus Hidup", "outputPrice": "Harga Output", "releaseDate": "Tanggal Rilis", + "selectModel": "Pilih model", + "selectTwoModels": "Pilih dua model untuk dibandingkan", "verified": "Terverifikasi" } diff --git a/translations/id/pages/model-detail.json b/translations/id/pages/model-detail.json index 1f8b2273..d42d3c49 100644 --- a/translations/id/pages/model-detail.json +++ b/translations/id/pages/model-detail.json @@ -1,3 +1,3 @@ { - "metaTitle": "Model AI Coding | Spesifikasi dan Harga 2025" + "metaTitle": "Model AI Coding | Spesifikasi dan Harga" } diff --git a/translations/id/shared.json b/translations/id/shared.json index 56702de6..7ce0179b 100644 --- a/translations/id/shared.json +++ b/translations/id/shared.json @@ -28,9 +28,13 @@ "webDevArena": "WebDev Arena" }, "capabilities": { - "capabilities": "Capabilities", - "inputModalities": "Input Modalities", - "outputModalities": "Output Modalities" + "capabilities": "Kemampuan", + "functionCalling": "Pemanggilan fungsi", + "inputModalities": "Modalitas masukan", + "outputModalities": "Modalitas keluaran", + "reasoning": "Penalaran", + "structuredOutputs": "Keluaran terstruktur", + "toolChoice": "Pemilihan alat" }, "categories": { "all": { @@ -96,6 +100,7 @@ "aiCoding": "Coding AI", "aiCodingStack": "AI Coding Stack", "articles": "Articles", + "benchmarks": "Tolok ukur", "collections": "Collections", "community": "Community", "communityLinks": "Community Links", diff --git a/translations/ja/components/common.json b/translations/ja/components/common.json index f3f0c614..319624e0 100644 --- a/translations/ja/components/common.json +++ b/translations/ja/components/common.json @@ -1,6 +1,6 @@ { "footer": { - "copyright": "© 2025 AI Coding Stack • ❤︎で構築 • オープンソース", + "copyright": "© {year} AI Coding Stack • ❤︎で構築 • オープンソース", "openSource": "オープンソース AI コーディングメタデータリポジトリ。", "selectLanguage": "言語を選択", "tagline": "あなたの AI コーディングエコシステムのハブ。", diff --git a/translations/ja/components/controls.json b/translations/ja/components/controls.json index dd769654..df8b9227 100644 --- a/translations/ja/components/controls.json +++ b/translations/ja/components/controls.json @@ -13,6 +13,7 @@ "sortNameDesc": "名前 (Z-A)" }, "searchDialog": { + "close": "比較セレクターを閉じる", "empty": "Start typing to search...", "navigate": "to navigate", "noResultsFor": "No results for \"{query}\"", diff --git a/translations/ja/pages/model-compare.json b/translations/ja/pages/model-compare.json index f618ec69..a6a4594d 100644 --- a/translations/ja/pages/model-compare.json +++ b/translations/ja/pages/model-compare.json @@ -9,5 +9,7 @@ "lifecycle": "ライフサイクル", "outputPrice": "出力価格", "releaseDate": "リリース日", + "selectModel": "モデルを選択", + "selectTwoModels": "比較するモデルを2つ選択してください", "verified": "検証済み" } diff --git a/translations/ja/pages/model-detail.json b/translations/ja/pages/model-detail.json index bc336d98..0e6a3e1d 100644 --- a/translations/ja/pages/model-detail.json +++ b/translations/ja/pages/model-detail.json @@ -1,3 +1,3 @@ { - "metaTitle": "AIコーディングモデル | 仕様と価格 2025" + "metaTitle": "AIコーディングモデル | 仕様と価格" } diff --git a/translations/ja/shared.json b/translations/ja/shared.json index 239cf35e..0648fd63 100644 --- a/translations/ja/shared.json +++ b/translations/ja/shared.json @@ -28,9 +28,13 @@ "webDevArena": "WebDev Arena" }, "capabilities": { - "capabilities": "Capabilities", - "inputModalities": "Input Modalities", - "outputModalities": "Output Modalities" + "capabilities": "機能", + "functionCalling": "関数呼び出し", + "inputModalities": "入力モダリティ", + "outputModalities": "出力モダリティ", + "reasoning": "推論", + "structuredOutputs": "構造化出力", + "toolChoice": "ツール選択" }, "categories": { "all": { @@ -96,6 +100,7 @@ "aiCoding": "AI コーディング", "aiCodingStack": "AI Coding Stack", "articles": "記事", + "benchmarks": "ベンチマーク", "collections": "コレクション", "community": "コミュニティ", "communityLinks": "コミュニティリンク", diff --git a/translations/ko/components/common.json b/translations/ko/components/common.json index de61db19..9f6433dd 100644 --- a/translations/ko/components/common.json +++ b/translations/ko/components/common.json @@ -1,6 +1,6 @@ { "footer": { - "copyright": "© 2025 AI Coding Stack • ❤︎로 구축 • 오픈 소스", + "copyright": "© {year} AI Coding Stack • ❤︎로 구축 • 오픈 소스", "openSource": "오픈 소스 AI 코딩 메타데이터 저장소.", "selectLanguage": "언어 선택", "tagline": "당신의 AI 코딩 생태계 허브.", diff --git a/translations/ko/components/controls.json b/translations/ko/components/controls.json index b6a4ac37..e0f5f2cc 100644 --- a/translations/ko/components/controls.json +++ b/translations/ko/components/controls.json @@ -13,6 +13,7 @@ "sortNameDesc": "이름 (Z-A)" }, "searchDialog": { + "close": "비교 선택기 닫기", "empty": "Start typing to search...", "navigate": "to navigate", "noResultsFor": "No results for \"{query}\"", diff --git a/translations/ko/pages/model-compare.json b/translations/ko/pages/model-compare.json index 3a5b4593..d41aa605 100644 --- a/translations/ko/pages/model-compare.json +++ b/translations/ko/pages/model-compare.json @@ -9,5 +9,7 @@ "lifecycle": "수명 주기", "outputPrice": "출력 가격", "releaseDate": "출시일", + "selectModel": "모델 선택", + "selectTwoModels": "비교할 모델 두 개를 선택하세요", "verified": "확인됨" } diff --git a/translations/ko/pages/model-detail.json b/translations/ko/pages/model-detail.json index 3c9d4a37..4d78d300 100644 --- a/translations/ko/pages/model-detail.json +++ b/translations/ko/pages/model-detail.json @@ -1,3 +1,3 @@ { - "metaTitle": "AI 코딩 모델 | 사양 및 가격 2025" + "metaTitle": "AI 코딩 모델 | 사양 및 가격" } diff --git a/translations/ko/shared.json b/translations/ko/shared.json index e0349da3..5accea93 100644 --- a/translations/ko/shared.json +++ b/translations/ko/shared.json @@ -28,9 +28,13 @@ "webDevArena": "WebDev Arena" }, "capabilities": { - "capabilities": "Capabilities", - "inputModalities": "Input Modalities", - "outputModalities": "Output Modalities" + "capabilities": "기능", + "functionCalling": "함수 호출", + "inputModalities": "입력 모달리티", + "outputModalities": "출력 모달리티", + "reasoning": "추론", + "structuredOutputs": "구조화된 출력", + "toolChoice": "도구 선택" }, "categories": { "all": { @@ -96,6 +100,7 @@ "aiCoding": "AI 코딩", "aiCodingStack": "AI Coding Stack", "articles": "기사", + "benchmarks": "벤치마크", "collections": "컬렉션", "community": "커뮤니티", "communityLinks": "커뮤니티 링크", diff --git a/translations/pt/components/common.json b/translations/pt/components/common.json index b82fc4ec..09e639c9 100644 --- a/translations/pt/components/common.json +++ b/translations/pt/components/common.json @@ -1,6 +1,6 @@ { "footer": { - "copyright": "© 2025 AI Coding Stack • Construído com ❤︎ • Open Source", + "copyright": "© {year} AI Coding Stack • Construído com ❤︎ • Open Source", "openSource": "Repositório de metadados de codificação IA de código aberto.", "selectLanguage": "Selecionar idioma", "tagline": "Seu centro do ecossistema de codificação IA.", diff --git a/translations/pt/components/controls.json b/translations/pt/components/controls.json index f4f5ebab..7ae91c92 100644 --- a/translations/pt/components/controls.json +++ b/translations/pt/components/controls.json @@ -13,6 +13,7 @@ "sortNameDesc": "Nome (Z-A)" }, "searchDialog": { + "close": "Fechar seletor de comparação", "empty": "Start typing to search...", "navigate": "to navigate", "noResultsFor": "No results for \"{query}\"", diff --git a/translations/pt/pages/model-compare.json b/translations/pt/pages/model-compare.json index c4663264..7b3d3bf1 100644 --- a/translations/pt/pages/model-compare.json +++ b/translations/pt/pages/model-compare.json @@ -9,5 +9,7 @@ "lifecycle": "Ciclo de vida", "outputPrice": "Preço de saída", "releaseDate": "Data de lançamento", + "selectModel": "Selecionar um modelo", + "selectTwoModels": "Selecione dois modelos para comparar", "verified": "Verificado" } diff --git a/translations/pt/pages/model-detail.json b/translations/pt/pages/model-detail.json index ed5a543f..09c50b4d 100644 --- a/translations/pt/pages/model-detail.json +++ b/translations/pt/pages/model-detail.json @@ -1,3 +1,3 @@ { - "metaTitle": "Modelo de IA para Programação | Especificações e Preços 2025" + "metaTitle": "Modelo de IA para Programação | Especificações e Preços" } diff --git a/translations/pt/shared.json b/translations/pt/shared.json index 0b824a7d..fd52da0f 100644 --- a/translations/pt/shared.json +++ b/translations/pt/shared.json @@ -28,9 +28,13 @@ "webDevArena": "WebDev Arena" }, "capabilities": { - "capabilities": "Capabilities", - "inputModalities": "Input Modalities", - "outputModalities": "Output Modalities" + "capabilities": "Capacidades", + "functionCalling": "Chamada de função", + "inputModalities": "Modalidades de entrada", + "outputModalities": "Modalidades de saída", + "reasoning": "Raciocínio", + "structuredOutputs": "Saídas estruturadas", + "toolChoice": "Seleção de ferramentas" }, "categories": { "all": { @@ -96,6 +100,7 @@ "aiCoding": "Codificação IA", "aiCodingStack": "AI Coding Stack", "articles": "Artigos", + "benchmarks": "Benchmarks", "collections": "Coleções", "community": "Comunidade", "communityLinks": "Links da comunidade", diff --git a/translations/ru/components/common.json b/translations/ru/components/common.json index 58e89eec..386a8118 100644 --- a/translations/ru/components/common.json +++ b/translations/ru/components/common.json @@ -1,6 +1,6 @@ { "footer": { - "copyright": "© 2025 AI Coding Stack • Создано с ❤︎ • Открытый исходный код", + "copyright": "© {year} AI Coding Stack • Создано с ❤︎ • Открытый исходный код", "openSource": "Репозиторий метаданных AI-программирования с открытым исходным кодом.", "selectLanguage": "Выбрать язык", "tagline": "Ваш центр экосистемы AI-программирования.", diff --git a/translations/ru/components/controls.json b/translations/ru/components/controls.json index b0c0ff0e..20911481 100644 --- a/translations/ru/components/controls.json +++ b/translations/ru/components/controls.json @@ -13,6 +13,7 @@ "sortNameDesc": "Имя (Я-А)" }, "searchDialog": { + "close": "Закрыть выбор сравнения", "empty": "Start typing to search...", "navigate": "to navigate", "noResultsFor": "No results for \"{query}\"", diff --git a/translations/ru/pages/model-compare.json b/translations/ru/pages/model-compare.json index a955f19d..7ef5e81c 100644 --- a/translations/ru/pages/model-compare.json +++ b/translations/ru/pages/model-compare.json @@ -9,5 +9,7 @@ "lifecycle": "Жизненный цикл", "outputPrice": "Цена вывода", "releaseDate": "Дата выпуска", + "selectModel": "Выбрать модель", + "selectTwoModels": "Выберите две модели для сравнения", "verified": "Проверено" } diff --git a/translations/ru/pages/model-detail.json b/translations/ru/pages/model-detail.json index 693406c4..46fe1467 100644 --- a/translations/ru/pages/model-detail.json +++ b/translations/ru/pages/model-detail.json @@ -1,3 +1,3 @@ { - "metaTitle": "Модель ИИ для кодирования | Характеристики и цены 2025" + "metaTitle": "Модель ИИ для кодирования | Характеристики и цены" } diff --git a/translations/ru/shared.json b/translations/ru/shared.json index 2001fe02..8b09a1f5 100644 --- a/translations/ru/shared.json +++ b/translations/ru/shared.json @@ -28,9 +28,13 @@ "webDevArena": "WebDev Arena" }, "capabilities": { - "capabilities": "Capabilities", - "inputModalities": "Input Modalities", - "outputModalities": "Output Modalities" + "capabilities": "Возможности", + "functionCalling": "Вызов функций", + "inputModalities": "Входные модальности", + "outputModalities": "Выходные модальности", + "reasoning": "Рассуждение", + "structuredOutputs": "Структурированный вывод", + "toolChoice": "Выбор инструментов" }, "categories": { "all": { @@ -96,6 +100,7 @@ "aiCoding": "ИИ-кодинг", "aiCodingStack": "AI Coding Stack", "articles": "Articles", + "benchmarks": "Тесты", "collections": "Collections", "community": "Community", "communityLinks": "Community Links", diff --git a/translations/tr/components/common.json b/translations/tr/components/common.json index cf9d8242..d27e73a1 100644 --- a/translations/tr/components/common.json +++ b/translations/tr/components/common.json @@ -1,6 +1,6 @@ { "footer": { - "copyright": "© 2025 AI Coding Stack • ❤︎ ile yapıldı • Açık Kaynak", + "copyright": "© {year} AI Coding Stack • ❤︎ ile yapıldı • Açık Kaynak", "openSource": "Açık kaynak AI kodlama metaveri deposu.", "selectLanguage": "Dil Seçin", "tagline": "AI Kodlama Ekosistemi Merkeziniz.", diff --git a/translations/tr/components/controls.json b/translations/tr/components/controls.json index 163f9c95..a576fd74 100644 --- a/translations/tr/components/controls.json +++ b/translations/tr/components/controls.json @@ -13,6 +13,7 @@ "sortNameDesc": "Ad (Z-A)" }, "searchDialog": { + "close": "Karşılaştırma seçiciyi kapat", "empty": "Start typing to search...", "navigate": "to navigate", "noResultsFor": "No results for \"{query}\"", diff --git a/translations/tr/pages/model-compare.json b/translations/tr/pages/model-compare.json index 2ddda203..b27bf8fd 100644 --- a/translations/tr/pages/model-compare.json +++ b/translations/tr/pages/model-compare.json @@ -9,5 +9,7 @@ "lifecycle": "Yaşam Döngüsü", "outputPrice": "Çıktı Fiyatı", "releaseDate": "Yayın Tarihi", + "selectModel": "Model seçin", + "selectTwoModels": "Karşılaştırmak için iki model seçin", "verified": "Doğrulanmış" } diff --git a/translations/tr/pages/model-detail.json b/translations/tr/pages/model-detail.json index 48171edb..f3d29f48 100644 --- a/translations/tr/pages/model-detail.json +++ b/translations/tr/pages/model-detail.json @@ -1,3 +1,3 @@ { - "metaTitle": "AI Kodlama Modeli | Teknik Özellikler ve Fiyatlandırma 2025" + "metaTitle": "AI Kodlama Modeli | Teknik Özellikler ve Fiyatlandırma" } diff --git a/translations/tr/shared.json b/translations/tr/shared.json index 27bf84a5..152849d6 100644 --- a/translations/tr/shared.json +++ b/translations/tr/shared.json @@ -28,9 +28,13 @@ "webDevArena": "WebDev Arena" }, "capabilities": { - "capabilities": "Capabilities", - "inputModalities": "Input Modalities", - "outputModalities": "Output Modalities" + "capabilities": "Yetenekler", + "functionCalling": "İşlev çağırma", + "inputModalities": "Girdi modaliteleri", + "outputModalities": "Çıktı modaliteleri", + "reasoning": "Akıl yürütme", + "structuredOutputs": "Yapılandırılmış çıktılar", + "toolChoice": "Araç seçimi" }, "categories": { "all": { @@ -96,6 +100,7 @@ "aiCoding": "AI Kodlama", "aiCodingStack": "AI Coding Stack", "articles": "Articles", + "benchmarks": "Karşılaştırmalı testler", "collections": "Collections", "community": "Community", "communityLinks": "Community Links", diff --git a/translations/zh-Hans/components/common.json b/translations/zh-Hans/components/common.json index 89e7a849..8f1149df 100644 --- a/translations/zh-Hans/components/common.json +++ b/translations/zh-Hans/components/common.json @@ -1,6 +1,6 @@ { "footer": { - "copyright": "© 2025 AI Coding Stack • 用 ❤︎ 构建 • 开源", + "copyright": "© {year} AI Coding Stack • 用 ❤︎ 构建 • 开源", "openSource": "开源 AI 编码元数据仓库。", "selectLanguage": "选择语言", "tagline": "你的 AI 编码生态中心。", diff --git a/translations/zh-Hans/components/controls.json b/translations/zh-Hans/components/controls.json index 3765ecca..cb193de1 100644 --- a/translations/zh-Hans/components/controls.json +++ b/translations/zh-Hans/components/controls.json @@ -13,6 +13,7 @@ "sortNameDesc": "名称 (Z-A)" }, "searchDialog": { + "close": "关闭比较选择器", "empty": "Start typing to search...", "navigate": "to navigate", "noResultsFor": "No results for \"{query}\"", diff --git a/translations/zh-Hans/pages/model-compare.json b/translations/zh-Hans/pages/model-compare.json index 6956961b..4c26da80 100644 --- a/translations/zh-Hans/pages/model-compare.json +++ b/translations/zh-Hans/pages/model-compare.json @@ -9,5 +9,7 @@ "lifecycle": "生命周期", "outputPrice": "输出定价", "releaseDate": "发布日期", + "selectModel": "选择模型", + "selectTwoModels": "选择两个模型进行比较", "verified": "已验证" } diff --git a/translations/zh-Hans/pages/model-detail.json b/translations/zh-Hans/pages/model-detail.json index 5c9f799a..daca8358 100644 --- a/translations/zh-Hans/pages/model-detail.json +++ b/translations/zh-Hans/pages/model-detail.json @@ -1,3 +1,3 @@ { - "metaTitle": "AI 编码模型 | 规格与定价 2025" + "metaTitle": "AI 编码模型 | 规格与定价" } diff --git a/translations/zh-Hans/shared.json b/translations/zh-Hans/shared.json index 004c33fd..257733a3 100644 --- a/translations/zh-Hans/shared.json +++ b/translations/zh-Hans/shared.json @@ -28,9 +28,13 @@ "webDevArena": "WebDev Arena" }, "capabilities": { - "capabilities": "Capabilities", - "inputModalities": "Input Modalities", - "outputModalities": "Output Modalities" + "capabilities": "能力", + "functionCalling": "函数调用", + "inputModalities": "输入模态", + "outputModalities": "输出模态", + "reasoning": "推理", + "structuredOutputs": "结构化输出", + "toolChoice": "工具选择" }, "categories": { "all": { @@ -96,6 +100,7 @@ "aiCoding": "AI 编码", "aiCodingStack": "AI Coding Stack", "articles": "文章", + "benchmarks": "基准测试", "collections": "收藏集", "community": "社区", "communityLinks": "社区链接", diff --git a/translations/zh-Hant/components/common.json b/translations/zh-Hant/components/common.json index dcd46adf..8ef1fdf0 100644 --- a/translations/zh-Hant/components/common.json +++ b/translations/zh-Hant/components/common.json @@ -1,6 +1,6 @@ { "footer": { - "copyright": "© 2025 AI Coding Stack • 用 ❤︎ 建構 • 開放原始碼", + "copyright": "© {year} AI Coding Stack • 用 ❤︎ 建構 • 開放原始碼", "openSource": "開放原始碼 AI 編碼元資料儲存庫。", "selectLanguage": "選擇語言", "tagline": "您的 AI 編碼生態系統中心。", diff --git a/translations/zh-Hant/components/controls.json b/translations/zh-Hant/components/controls.json index d36a5192..f77231fc 100644 --- a/translations/zh-Hant/components/controls.json +++ b/translations/zh-Hant/components/controls.json @@ -13,6 +13,7 @@ "sortNameDesc": "名稱 (Z-A)" }, "searchDialog": { + "close": "關閉比較選擇器", "empty": "Start typing to search...", "navigate": "to navigate", "noResultsFor": "No results for \"{query}\"", diff --git a/translations/zh-Hant/pages/model-compare.json b/translations/zh-Hant/pages/model-compare.json index 0a362a00..5c3e608a 100644 --- a/translations/zh-Hant/pages/model-compare.json +++ b/translations/zh-Hant/pages/model-compare.json @@ -9,5 +9,7 @@ "lifecycle": "生命週期", "outputPrice": "輸出價格", "releaseDate": "發布日期", + "selectModel": "選擇模型", + "selectTwoModels": "選擇兩個模型進行比較", "verified": "已驗證" } diff --git a/translations/zh-Hant/pages/model-detail.json b/translations/zh-Hant/pages/model-detail.json index 48ba7460..f8a1a216 100644 --- a/translations/zh-Hant/pages/model-detail.json +++ b/translations/zh-Hant/pages/model-detail.json @@ -1,3 +1,3 @@ { - "metaTitle": "AI 編碼模型 | 規格與定價 2025" + "metaTitle": "AI 編碼模型 | 規格與定價" } diff --git a/translations/zh-Hant/shared.json b/translations/zh-Hant/shared.json index 1eb4380b..8f9e21ce 100644 --- a/translations/zh-Hant/shared.json +++ b/translations/zh-Hant/shared.json @@ -28,9 +28,13 @@ "webDevArena": "WebDev Arena" }, "capabilities": { - "capabilities": "Capabilities", - "inputModalities": "Input Modalities", - "outputModalities": "Output Modalities" + "capabilities": "能力", + "functionCalling": "函數呼叫", + "inputModalities": "輸入模態", + "outputModalities": "輸出模態", + "reasoning": "推理", + "structuredOutputs": "結構化輸出", + "toolChoice": "工具選擇" }, "categories": { "all": { @@ -96,6 +100,7 @@ "aiCoding": "AI 編碼", "aiCodingStack": "AI Coding Stack", "articles": "文章", + "benchmarks": "基準測試", "collections": "收藏集", "community": "社群", "communityLinks": "社群連結", From 3c19ea85aa72ee05ad9e4f1f59431540e8e3f0eb Mon Sep 17 00:00:00 2001 From: Pan YANG Date: Sat, 18 Jul 2026 05:05:37 +0800 Subject: [PATCH 3/5] feat(manifest): add provenance metadata contract --- CONTRIBUTING.md | 89 ++++++++--------------- docs/DATA-TRUST.md | 45 ++++++++++++ manifests/$schemas/ref/entity.schema.json | 55 +++++++++++++- src/types/manifests.ts | 14 ++++ 4 files changed, 144 insertions(+), 59 deletions(-) create mode 100644 docs/DATA-TRUST.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3a27576e..c372cec8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,7 +73,7 @@ Want to contribute code? Great! Please: ### Prerequisites -- **Node.js**: 20.x or higher +- **Node.js**: 22.x or higher - **npm**: Latest version - **Git**: For version control @@ -85,7 +85,7 @@ git clone https://github.com/aicodingstack/aicodingstack.io.git cd aicodingstack.io # Install dependencies -npm install +npm ci # Run validation tests npm run test:validate @@ -106,9 +106,10 @@ Visit `http://localhost:3000` to see the site. - `npm run build` - Build for production - `npm run test:validate` - Run repository validation tests (schemas, translations, alignment, etc.) - `npm run test:urls` - Check URL accessibility (networked; CI-oriented) -- `npm run lint` - Run ESLint +- `npm run biome:check` - Run static analysis +- `npm run type-check` - Run TypeScript checks - `npm run spell` - Run spell checker -- `npm test` - Run tests (if available) +- `npm test` - Run the Vitest suite in watch mode ## Manifest File Guidelines @@ -121,61 +122,34 @@ All manifest files must conform to their respective JSON schemas located in `man - [Model Schema](manifests/$schemas/model.schema.json) - [Provider Schema](manifests/$schemas/provider.schema.json) -### Example: Adding a New IDE - -Create a file in `manifests/ides/your-ide.json`: - -```json -{ - "name": "Your IDE", - "id": "your-ide", - "vendor": "Your Company", - "description": "A brief description of what makes this IDE unique", - "websiteUrl": "https://example.com", - "docsUrl": "https://docs.example.com", - "latestVersion": "1.0.0", - "platforms": ["Windows", "macOS", "Linux"], - "pricing": { - "model": "freemium", - "free": true - } -} -``` +### Examples -### Example: Adding a New Model - -Create a file in `manifests/models/your-model.json`: - -```json -{ - "name": "Your Model", - "id": "your-model", - "vendor": "Your Company", - "size": "175B", - "contextWindow": 128000, - "maxOutput": 4096, - "pricing": { - "input": 0.01, - "output": 0.03, - "currency": "USD", - "unit": "1K tokens" - }, - "urls": { - "website": "https://example.com", - "documentation": "https://docs.example.com" - } -} -``` +The schemas are authoritative and require fields that differ by category. Start from a current, validated manifest rather than copying an abbreviated snippet: + +- [IDE example](manifests/ides/cursor.json) +- [Model example](manifests/models/gpt-5-2.json) + +Copy the closest existing manifest, update its `$schema`, `id`, content, translations, and URLs, then run the validation commands below. ### Field Requirements -#### Common Fields (All Types) -- **`name`**: Official product name (required) -- **`id`**: Lowercase, hyphenated identifier (required, unique) -- **`vendor`**: Company or organization name (required) -- **`description`**: Clear, concise description (required) -- **`websiteUrl`**: Official website (required) -- **`docsUrl`**: Documentation URL (recommended) +#### Common Fields (Most Types) + +- **`$schema`**: Relative path to the category schema +- **`name`**: Official product name +- **`id`**: Lowercase, hyphenated identifier that matches the filename +- **`vendor`**: Company or organization name +- **`description`**: Clear, concise default-language description +- **`translations`**: Localized fields required by the category schema +- **`websiteUrl`**: Official website +- **`docsUrl`**: Documentation URL +- **`sources`**: Authoritative URLs supporting the record or named fields +- **`lastVerifiedAt`**: Date the cited facts were last reviewed +- **`verifiedBy`**: GitHub handle or automation identifier responsible for the review +- **`confidence`**: `high`, `medium`, or `low`, based on source quality and recency + +For new or updated records marked `verified: true`, include the provenance fields above. See +[Data Trust and Verification](docs/DATA-TRUST.md) for the badge semantics and rollout policy. #### Model-Specific Fields - **`size`**: Model size (e.g., "175B", "70B") @@ -257,7 +231,8 @@ chore(deps): update Next.js to 15.1.0 3. ✅ **Validate locally**: ```bash npm run test:validate - npm run test:urls + npm run biome:check + npm run type-check npm run spell npm run build ``` @@ -275,7 +250,7 @@ chore(deps): update Next.js to 15.1.0 ### Review Process -- **Automated checks**: CI must pass (lint, validate, build) +- **Automated checks**: CI must pass (Biome, types, validation, spell check, and build) - **Maintainer review**: At least 1 approval required - **Response time**: We aim to review within 3-5 business days - **Merge**: Once approved, maintainers will merge your PR diff --git a/docs/DATA-TRUST.md b/docs/DATA-TRUST.md new file mode 100644 index 00000000..95800fa4 --- /dev/null +++ b/docs/DATA-TRUST.md @@ -0,0 +1,45 @@ +# Data Trust and Verification + +AI Coding Stack uses `verified` to mean that a record was reviewed by the project team against authoritative sources. It does not mean the vendor endorsed the record, that every field is independently audited, or that rapidly changing pricing and benchmark values remain current forever. + +## Provenance fields + +All entity schemas support these optional fields: + +- `sources`: HTTPS sources for the full record or selected fields; +- `lastVerifiedAt`: the most recent review date in `YYYY-MM-DD` format; +- `verifiedBy`: the GitHub handle or automation identifier responsible for the review; +- `confidence`: `high`, `medium`, or `low`, based on source authority, completeness, and recency. + +Example: + +```json +{ + "verified": true, + "sources": [ + { + "url": "https://example.com/docs/model", + "title": "Official model documentation", + "fields": ["contextWindow", "maxOutput", "capabilities"] + }, + { + "url": "https://example.com/pricing", + "title": "Official API pricing", + "fields": ["tokenPricing"] + } + ], + "lastVerifiedAt": "2026-07-18", + "verifiedBy": "@maintainer", + "confidence": "high" +} +``` + +## Current rollout state + +Legacy records may have `verified: true` without structured provenance because the boolean predates these fields. Treat those badges as evidence of a prior team review, not as a current freshness guarantee. They will be backfilled category by category; no source or review date should be invented merely to complete the fields. + +For new entries and material updates, a `verified: true` change should include at least one authoritative source, `lastVerifiedAt`, `verifiedBy`, and `confidence`. Official product documentation and pricing pages are preferred over third-party summaries. Benchmark fields should cite the benchmark owner or an official model report. + +## Freshness direction + +The next data-health automation should report verified records without provenance, records older than their category threshold, broken source URLs, and fields such as pricing or benchmarks with no field-level source. Until that automation is in place, freshness review remains a pull-request responsibility. diff --git a/manifests/$schemas/ref/entity.schema.json b/manifests/$schemas/ref/entity.schema.json index 15f837b9..844bfaa6 100644 --- a/manifests/$schemas/ref/entity.schema.json +++ b/manifests/$schemas/ref/entity.schema.json @@ -25,7 +25,30 @@ }, "verified": { "type": "boolean", - "description": "Whether the entity has been verified by the AI Coding Stack team (default value is false)" + "description": "Whether the entity has been reviewed by the AI Coding Stack team against cited sources. Legacy verified records may not yet include structured provenance." + }, + "sources": { + "type": "array", + "description": "Sources used to verify this entity or individual fields", + "items": { + "$ref": "#/$defs/source" + }, + "minItems": 1, + "uniqueItems": true + }, + "lastVerifiedAt": { + "type": "string", + "format": "date", + "description": "Date when the cited facts were last reviewed (ISO 8601, YYYY-MM-DD)" + }, + "verifiedBy": { + "type": ["string", "null"], + "description": "GitHub handle or automation identifier responsible for the latest review" + }, + "confidence": { + "type": "string", + "enum": ["high", "medium", "low"], + "description": "Overall confidence in the current record based on source quality and recency" }, "websiteUrl": { "type": "string", @@ -34,5 +57,33 @@ "description": "Official website URL" } }, - "required": ["id", "name", "description", "translations", "verified", "websiteUrl"] + "required": ["id", "name", "description", "translations", "verified", "websiteUrl"], + "$defs": { + "source": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "pattern": "^https://", + "description": "Authoritative source URL" + }, + "title": { + "type": "string", + "description": "Human-readable source title" + }, + "fields": { + "type": "array", + "description": "Manifest fields supported by this source; omit when it supports the whole record", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "required": ["url"], + "additionalProperties": false + } + } } diff --git a/src/types/manifests.ts b/src/types/manifests.ts index b35e33a2..573e4431 100644 --- a/src/types/manifests.ts +++ b/src/types/manifests.ts @@ -57,6 +57,16 @@ export interface ManifestPlatformUrls { openrouter: string | null } +/** + * Source used to verify a manifest record or selected fields. + * Based on: /manifests/$schemas/ref/entity.schema.json#$defs/source + */ +export interface ManifestSource { + url: string + title?: string + fields?: string[] +} + /** * Base Entity - Fundamental properties all manifests share * Based on: /manifests/$schemas/ref/entity.schema.json @@ -67,6 +77,10 @@ export interface ManifestEntity { description: string translations: ManifestTranslations verified: boolean + sources?: ManifestSource[] + lastVerifiedAt?: string + verifiedBy?: string | null + confidence?: 'high' | 'medium' | 'low' websiteUrl: string } From 1b7f62af4ad4c6f32b61089fe20dba06b380b1d8 Mon Sep 17 00:00:00 2001 From: Pan YANG Date: Sat, 18 Jul 2026 05:05:43 +0800 Subject: [PATCH 4/5] fix(data): preserve cached stars on fetch errors --- docs/FETCH_GITHUB_STARS.md | 242 ++++------------------------ scripts/fetch/fetch-github-stars.ts | 14 +- 2 files changed, 40 insertions(+), 216 deletions(-) diff --git a/docs/FETCH_GITHUB_STARS.md b/docs/FETCH_GITHUB_STARS.md index f136789c..46e804b2 100644 --- a/docs/FETCH_GITHUB_STARS.md +++ b/docs/FETCH_GITHUB_STARS.md @@ -1,237 +1,53 @@ -# GitHub Stars Fetcher +# GitHub Stars Refresh -**Last Updated:** January 6, 2026 +The stars refresh reads `githubUrl` from IDE, CLI, and extension manifests, queries the GitHub repository API, and writes the centralized cache at `data/github-stars.json`. -This document describes the GitHub stars fetching system for AI Coding Stack. +## Run it ---- - -## Overview - -The GitHub Stars Fetcher fetches star counts from the GitHub API for all projects in the manifest files and creates a centralized `github-stars.json` data file. - -**Implementation**: `scripts/fetch/fetch-github-stars.mjs` -**Output**: `manifests/github-stars.json` - ---- - -## Schema - -The stars data follows `manifests/$schemas/github-stars.schema.json`: - -```typescript -interface ManifestGitHubStars { - extensions: { [productId: string]: number | null } - clis: { [productId: string]: number | null } - ides: { [productId: string]: number | null } -} -``` - ---- - -## Usage - -### Fetch Stars with GitHub Token (Recommended) +Use a token for the normal GitHub API rate limit: ```bash -GITHUB_TOKEN=your_github_token_here npm run generate +GITHUB_TOKEN=your_token npm run fetch:github-stars ``` -### Fetch Stars Without Token (Rate Limited) +Unauthenticated runs are supported but are limited to 60 requests per hour: ```bash -npm run generate +npm run fetch:github-stars ``` -### Direct Script Execution - -```bash -node scripts/fetch/index.mjs github-stars -``` - ---- - -## Getting a GitHub Token - -1. Go to https://github.com/settings/tokens -2. Click **Generate new token** → **Generate new token (classic)** -3. Give it a name (e.g., "acs-stars-fetcher") -4. Scopes: `public_repo` (or none for public repos only) -5. Click **Generate token** -6. Copy the token and use it as `GITHUB_TOKEN` environment variable +The implementation is `scripts/fetch/fetch-github-stars.ts`; the category runner is `scripts/fetch/index.ts`. ---- +## Data contract -## Rate Limits +The file follows `manifests/$schemas/github-stars.schema.json`: -| Authentication | Requests | Time Period | -|----------------|----------|-------------| -| With token | 5,000 | 1 hour | -| Without token | 60 | 1 hour | - -**Recommendation**: Always use a GitHub token to avoid rate limiting. - ---- - -## How It Works - -### 1. Data Sources - -The script fetches GitHub URLs from these files: - -| Category | File | URL Field | -|----------|------|-----------| -| Extensions | `manifests/extensions/*.json` | `communityUrls.github` | -| CLIs | `manifests/clis/*.json` | `communityUrls.github` | -| IDEs | `manifests/ides/*.json` | `communityUrls.github` | -| Models | `manifests/models/*.json` | `communityUrls.github` | - -### 2. Processing Steps - -For each project: - -1. Extract GitHub URL from `communityUrls.github` field -2. Parse owner/repo from URL (e.g., `microsoft/playwright`) -3. Fetch star count from GitHub API: `GET /repos/:owner/:repo/stargazers` -4. Store raw star count (number) -5. Write to `manifests/github-stars.json` - -### 3. Output Format - -```json -{ - "extensions": { - "playwright": 90450, - "context7": 2150, - "claude-code": 5420 - }, - "clis": { - "github-copilot-cli": 3450, - "codex-cli": 1230 - }, - "ides": { - "cursor": 42000 - } +```ts +interface GitHubStarsData { + extensions: Record + clis: Record + ides: Record } ``` ---- - -## Display Format - -When displaying stars on the site, they are formatted with a helper function: - -```typescript -// Formats: 42000 → "42k", 1500 → "1.5k", 150 → "150" -function formatStars(stars: number | null): string -``` - -This is separate from the stored format (raw numbers). - ---- - -## Usage in Components - -### Import Stars Data - -```typescript -import { githubStars } from '@/lib/generated/github-stars' - -// Get stars for an IDE -const cursorStars = githubStars.ides['cursor'] // Returns number or null - -// Display formatted -const displayStars = formatStars(cursorStars) // "42k" -``` - -### ProductHero Component - -```typescript -import { githubStars } from '@/lib/generated/github-stars' - - -``` - ---- - -## Files Involved - -| Category | File | Purpose | -|----------|------|---------| -| Script | `scripts/fetch/fetch-github-stars.mjs` | Fetch implementation | -| Entry | `scripts/fetch/index.mjs` | Category runner | -| Output | `manifests/github-stars.json` | Stars data | -| Type | `src/types/manifests.ts` | `ManifestGitHubStars` interface | -| Import | `src/lib/generated/github-stars.ts` | Typed import | -| Schema | `manifests/$schemas/github-stars.schema.json` | Validation | - ---- +Values are stored in thousands with one decimal place, matching the current UI contract; for example, `42.3` means approximately 42,300 stars. A `null` value means the manifest has no usable repository URL or no cached value exists. Transient API failures retain the previous cached value instead of replacing it with `null`. -## Integration with Build Process - -The GitHub stars data is automatically generated as part of the build: - -```bash -npm run generate # Includes star fetching -npm run dev # Generate + start dev server -npm run build # Generate + build for production -``` +## Automation ---- +`.github/workflows/update-github-stars.yml` runs weekly and can also be dispatched manually. It: -## Updating Stars +1. installs the locked dependencies; +2. runs `npm run fetch:github-stars` with `GITHUB_TOKEN`; +3. validates the manifest data; +4. opens a pull request when `data/github-stars.json` changed. -To update star counts after changes to manifests: - -```bash -# Single update -npm run generate - -# Force rebuild -rm manifests/github-stars.json && npm run generate -``` - ---- - -## Error Handling - -The script handles: - -1. **Missing GitHub URLs**: Skips products without `communityUrls.github` -2. **Private Repos**: Sets value to `null` for access errors -3. **API Errors**: Logs error and continues -4. **Rate Limits**: Logs warning and returns cached/empty data - ---- - -## Example Output - -``` -🚀 Starting GitHub stars fetcher... - -✅ Using GitHub token for authentication - - 🔍 Fetching stars for Playwright (microsoft/playwright)... - ✅ Updated Playwright: 90450 stars - 🔍 Fetching stars for Context7 (upstash/context7)... - ✅ Updated Context7: 2150 stars - -================================================== -🎉 All files processed! -================================================== - -✅ Written to manifests/github-stars.json -``` +There is only one scheduled owner for this refresh. General scheduled URL checks live in `.github/workflows/scheduled-checks.yml`. ---- +## Troubleshooting -**Related Files:** -- `src/lib/landscape-data.ts` - Uses stars for sorting -- `src/components/product/ProductHero.tsx` - Displays stars on product pages -- `src/app/[locale]/ides/comparison/page.client.tsx` - Comparison rankings +- `403`: provide `GITHUB_TOKEN` or wait for the API limit to reset. +- `404`: check the manifest's `githubUrl` and repository visibility. +- Validation failure: ensure each IDE, CLI, and extension manifest has a matching key and no orphan key remains. +- No pull request: confirm the workflow checked `data/github-stars.json` and that the refreshed values actually differ. -**Last Updated:** January 6, 2026 +Last reviewed: 2026-07-18. diff --git a/scripts/fetch/fetch-github-stars.ts b/scripts/fetch/fetch-github-stars.ts index 70834714..68f24921 100644 --- a/scripts/fetch/fetch-github-stars.ts +++ b/scripts/fetch/fetch-github-stars.ts @@ -179,7 +179,10 @@ async function processFile(filePath: string, fileName: string): Promise { +async function processDirectory( + dirConfig: DirConfig, + existingCategoryData: Record +): Promise { const dirPath = path.join(__dirname, '..', '..', dirConfig.directory) console.log(`\n📁 Processing ${dirConfig.directory}...`) @@ -211,7 +214,9 @@ async function processDirectory(dirConfig: DirConfig): Promise if (result.error) errors++ // Map file ID (filename without .json) to stars value (can be null) - categoryData[result.fileId] = result.stars + categoryData[result.fileId] = result.error + ? (existingCategoryData[result.fileId] ?? null) + : result.stars } console.log( @@ -252,7 +257,10 @@ async function main(): Promise { // Maps file names to stars based on githubUrl field in each manifest file for (const dirConfig of dirsConfig) { try { - const { categoryData, stats } = await processDirectory(dirConfig) + const { categoryData, stats } = await processDirectory( + dirConfig, + starsData[dirConfig.category] ?? {} + ) // Sort the category data by key (alphabetically) const sortedCategoryData = Object.keys(categoryData) From ae83e1dd35459b2c4575f779a6232679693aad5b Mon Sep 17 00:00:00 2001 From: Pan YANG Date: Sat, 18 Jul 2026 05:05:53 +0800 Subject: [PATCH 5/5] docs: add project review delivery tracker --- .github/ISSUE_TEMPLATE/config.yml | 2 +- .../ISSUE_TEMPLATE/metadata_contribution.yml | 8 +- .github/pull_request_template.md | 4 +- README.md | 2 +- docs/COMPONENT-RELATIONSHIP-DIAGRAM.md | 49 +- docs/I18N-ARCHITECTURE-RULES.md | 2 +- docs/PROJECT-REVIEW-2026-07-18.md | 162 ++++++ docs/SCHEMA-ALIGNMENT.md | 6 +- scripts/README.md | 479 ++---------------- 9 files changed, 237 insertions(+), 477 deletions(-) create mode 100644 docs/PROJECT-REVIEW-2026-07-18.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index e507c0c4..aa314b2c 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -7,5 +7,5 @@ contact_links: url: https://github.com/aicodingstack/aicodingstack.io/blob/main/docs about: Read the technical documentation - name: Schema Documentation - url: https://github.com/aicodingstack/aicodingstack.io/tree/main/manifests/schemas + url: https://github.com/aicodingstack/aicodingstack.io/tree/main/manifests/%24schemas about: View JSON schema documentation for manifests diff --git a/.github/ISSUE_TEMPLATE/metadata_contribution.yml b/.github/ISSUE_TEMPLATE/metadata_contribution.yml index 27c096b8..00549d4b 100644 --- a/.github/ISSUE_TEMPLATE/metadata_contribution.yml +++ b/.github/ISSUE_TEMPLATE/metadata_contribution.yml @@ -8,7 +8,7 @@ body: value: | Thank you for contributing to AI Coding Stack! This form will guide you through adding or updating metadata for AI coding tools. - Please check our [Schema Documentation](https://github.com/aicodingstack/aicodingstack.io/tree/main/manifests/schemas) to understand the required fields. + Please check our [Schema Documentation](https://github.com/aicodingstack/aicodingstack.io/tree/main/manifests/%24schemas) to understand the required fields. - type: dropdown id: contribution-type @@ -95,9 +95,9 @@ body: For **Providers**: Include supported models, API documentation See schema files for complete field requirements: - - [Model Schema](https://github.com/aicodingstack/aicodingstack.io/blob/main/manifests/schemas/model.schema.json) - - [IDE Schema](https://github.com/aicodingstack/aicodingstack.io/blob/main/manifests/schemas/ide.schema.json) - - [CLI Schema](https://github.com/aicodingstack/aicodingstack.io/blob/main/manifests/schemas/cli.schema.json) + - [Model Schema](https://github.com/aicodingstack/aicodingstack.io/blob/main/manifests/%24schemas/model.schema.json) + - [IDE Schema](https://github.com/aicodingstack/aicodingstack.io/blob/main/manifests/%24schemas/ide.schema.json) + - [CLI Schema](https://github.com/aicodingstack/aicodingstack.io/blob/main/manifests/%24schemas/cli.schema.json) placeholder: | Example for a model: - Size: 175B parameters diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b1a76003..0c9edf2e 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -55,9 +55,9 @@ Closes #(issue number) - [ ] Local build passes (`npm run build`) -- [ ] All validation checks pass (`npm run validate:manifests`) +- [ ] All validation checks pass (`npm run test:validate`) - [ ] URL validation passes (`npm run validate:urls`) -- [ ] Linting passes (`npm run lint`) +- [ ] Static analysis passes (`npm run biome:check`) - [ ] Spell check passes (`npm run spell`) ## Screenshots (if applicable) diff --git a/README.md b/README.md index b2d105e0..544fe3a1 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ All manifest files are automatically validated against JSON schemas. Make sure y ## Tech Stack -- **Framework**: Next.js 15 with App Router +- **Framework**: Next.js 16 with App Router - **Styling**: Tailwind CSS 4 - **Internationalization**: next-intl - **Content**: MDX for documentation diff --git a/docs/COMPONENT-RELATIONSHIP-DIAGRAM.md b/docs/COMPONENT-RELATIONSHIP-DIAGRAM.md index 725ff835..8598f2c5 100644 --- a/docs/COMPONENT-RELATIONSHIP-DIAGRAM.md +++ b/docs/COMPONENT-RELATIONSHIP-DIAGRAM.md @@ -76,32 +76,33 @@ aicodingstack.io/ ``` manifests/ ├── $schemas/ # JSON Schema definitions for validation -│ ├── ides.schema.json -│ ├── clis.schema.json -│ ├── extensions.schema.json -│ ├── models.schema.json -│ ├── providers.schema.json -│ ├── vendors.schema.json +│ ├── ide.schema.json +│ ├── cli.schema.json +│ ├── extension.schema.json +│ ├── model.schema.json +│ ├── provider.schema.json +│ ├── vendor.schema.json │ └── github-stars.schema.json -├── ides/*.jsonc # IDE manifest files -├── clis/*.jsonc # CLI manifest files -├── extensions/*.jsonc # Extension manifest files -├── models/*.jsonc # Model manifest files -├── providers/*.jsonc # Provider manifest files -├── vendors/*.jsonc # Vendor manifest files -└── github-stars.json # Centralized star counts +├── ides/*.json # IDE manifest files +├── clis/*.json # CLI manifest files +├── extensions/*.json # Extension manifest files +├── models/*.json # Model manifest files +├── providers/*.json # Provider manifest files +├── vendors/*.json # Vendor manifest files ``` +`data/github-stars.json` stores the centralized star counts. + ### scripts/ (Build Tools) ``` scripts/ ├── fetch/ # Data fetching scripts -│ ├── fetch-github-stars.mjs -│ └── index.mjs +│ ├── fetch-github-stars.ts +│ └── index.ts ├── generate/ # Code generation scripts -│ ├── generate-i18n.mjs -│ ├── generate-manifests.mjs -│ └── index.mjs +│ ├── generate-manifest-indexes.ts +│ ├── generate-metadata.ts +│ └── index.ts ├── refactor/ # Refactoring utilities └── _shared/ # Shared utilities for scripts ``` @@ -173,10 +174,10 @@ The manifest system is the core data layer: │ MANIFEST SYSTEM │ ├─────────────────────────────────────────────────────────────────┤ │ │ -│ manifests/*.jsonc (Source) │ +│ manifests/**/*.json (Source) │ │ │ │ │ ▼ │ -│ scripts/generate/generate-manifests.mjs │ +│ scripts/generate/generate-manifest-indexes.ts │ │ │ │ │ ▼ │ │ src/lib/generated/*.ts (Typed Access) │ @@ -258,9 +259,9 @@ The manifest system is the core data layer: │ │ │ Scripts for fetching external data: │ │ │ -│ scripts/fetch/fetch-github-stars.mjs │ +│ scripts/fetch/fetch-github-stars.ts │ │ ├── Fetches from GitHub API │ -│ └── Writes to manifests/github-stars.json │ +│ └── Writes to data/github-stars.json │ │ │ │ Scheduled workflows: │ │ .github/workflows/update-github-stars.yml (daily) │ @@ -352,8 +353,8 @@ The manifest system is the core data layer: │ npm run generate │ │ │ │ │ ▼ │ -│ Script: generate-manifests.mjs │ -│ ├── Reads manifests/*.jsonc │ +│ Script: generate-manifest-indexes.ts │ +│ ├── Reads manifests/**/*.json │ │ ├── Validates against schemas │ │ └── Writes src/lib/generated/*.ts │ │ │ │ diff --git a/docs/I18N-ARCHITECTURE-RULES.md b/docs/I18N-ARCHITECTURE-RULES.md index 3607bb8d..d19a3de9 100644 --- a/docs/I18N-ARCHITECTURE-RULES.md +++ b/docs/I18N-ARCHITECTURE-RULES.md @@ -385,7 +385,7 @@ To align the current codebase with these rules: "closeMenu": "Close menu" }, "footer": { - "copyright": "© 2025 AI Coding Stack • Built with ❤︎ • Open Source", + "copyright": "© {year} AI Coding Stack • Built with ❤︎ • Open Source", "tagline": "Your AI Coding Ecosystem Hub.", "openSource": "Open source AI coding metadata repository.", "selectLanguage": "Select Language", diff --git a/docs/PROJECT-REVIEW-2026-07-18.md b/docs/PROJECT-REVIEW-2026-07-18.md new file mode 100644 index 00000000..6581979f --- /dev/null +++ b/docs/PROJECT-REVIEW-2026-07-18.md @@ -0,0 +1,162 @@ +# Project Review and Delivery Tracker — 2026-07-18 + +This document records the July 2026 project review and tracks the work required to turn AI +Coding Stack from a mostly static directory into a trusted, continuously maintained decision +layer for AI coding tools and models. + +## North Star + +AI Coding Stack should optimize for trustworthy decisions rather than raw page or entry count: + +1. **Data layer:** sourced, dated, reviewable facts about tools, models, providers, and vendors. +2. **Decision layer:** search, filters, comparisons, and use-case-oriented stack recommendations. +3. **Distribution layer:** the website, versioned data exports/API, embeddable comparisons, and + community contribution workflows. + +## Review Baseline + +Baseline captured on 2026-07-18: + +- 151 manifest files across six entity categories. +- 12 supported locales with aligned translation file structures. +- Last manifest change: 2026-01-26. +- Newest model `releaseDate`: 2025-12-23. +- Last successful production deployment and `main` CI run: 2026-01-28. +- 14 open pull requests; 13 reported as blocked at review time. +- 40 `as unknown as` type escapes under `src/` and `scripts/`. +- 39 client-side source files; several product pages exceed 500 lines. +- 33 models produce 528 pair combinations, or 6,336 localized comparison pages if every pair + is statically generated. +- Approximately 36–51% of non-English UI strings are byte-identical to English. This is a proxy + for untranslated placeholders, not a definitive translation-quality score. + +## Status Legend + +- [ ] Planned +- [~] In progress +- [x] Completed +- [!] Blocked or requires a product/operations decision + +## P0 — Restore Maintenance Throughput + +- [x] Remove the deleted `develop` branch from deployment workflows. + - Acceptance: staging has an intentional manual or trunk-based trigger. +- [x] Consolidate GitHub Stars automation into one workflow. + - Acceptance: one schedule, correct `data/github-stars.json` path, generated output included, + and changes result in a reviewable PR. +- [x] Make scheduled URL failures observable and actionable. + - Acceptance: a failed URL check creates or updates an issue and the workflow reports failure. +- [x] Repair stale CODEOWNERS, issue templates, README, contributor guide, and script docs. + - Acceptance: every referenced path and npm command exists; manifest examples validate. +- [ ] Triage and merge/close the existing blocked dependency and data PR backlog. + - Acceptance: `main` is current, required checks run on bot PRs, and no obsolete update PRs + remain open. +- [ ] Define a regular release and data-refresh cadence. + - Acceptance: named owner, weekly data review, and monthly production release review. + +## P0 — Correctness and User Trust + +- [x] Fix provider links emitted by the search results page. +- [x] Fix untranslated/raw labels and invalid inferred provider links in two-model comparisons. +- [x] Replace hard-coded 2025 metadata and copyright values with current or timeless values. +- [x] Complete and integrate `ModelCompareSelector` on model detail pages. +- [x] Add missing sitemap coverage for important static, comparison, extension, and vendor pages. +- [x] Remove randomized fallback data from `GitHubStarHistory`; failed requests now render nothing. +- [x] Define the meaning and limitations of the Verified badge in public documentation. + +## P1 — Data Trust Layer + +- [x] Extend schemas with optional provenance and freshness fields. + - Candidate fields: `sources`, `lastVerifiedAt`, `verifiedBy`, `confidence`, and field-level + effective dates where pricing or benchmarks change independently. +- [ ] Backfill provenance for all verified entries before treating verification as authoritative. +- [ ] Generate a data-health report covering freshness, missing sources, broken URLs, relationship + integrity, translation placeholders, and missing pricing/benchmark values. +- [ ] Add freshness policies (for example 30/60/90-day review thresholds by field/category). +- [ ] Automate change discovery from official sources, but keep human review before merge. +- [ ] Populate a real changelog from manifest diffs instead of keeping an empty static file. + +## P1 — Product Experience + +- [x] Stop statically generating every possible two-model comparison pair. +- [ ] Unify `/models/comparison` and `/models/compare/...` into one understandable comparison + journey. +- [ ] Add “compare” actions to cards and detail pages with persistent selected items. +- [x] Search localized names/descriptions, capabilities, modalities, platforms, vendors, and types. +- [ ] Add explicit use-case metadata and include it in search when the schema supports it. +- [ ] Add guided selection filters: interface, budget, model freedom, privacy/local execution, + platform, and team workflow. +- [x] Make the ranking destination directly reachable from keyboard and mobile navigation. +- [ ] Refocus the homepage on current data, recent changes, popular comparisons, and recommended + stacks rather than primarily explaining the product. + +## P1 — Quality Gates + +- [x] Add route-contract tests for every manifest category. +- [ ] Add browser-level smoke tests for search, navigation, detail pages, locale switching, and + comparison selection. +- [ ] Add automated accessibility checks for dialogs, menus, comparison tables, and mobile nav. +- [x] Run generation in CI and fail when tracked generated output differs. +- [x] Make the aggregate CI job fail on cancelled or skipped required jobs as well as failures. +- [ ] Add a small security workflow/policy for dependency and application review. + +## P2 — Architecture and Scale + +- [ ] Make `ComparisonTable` generic and remove `Record` call-site casting. +- [ ] Extract shared comparison definitions for IDEs, CLIs, and extensions. +- [ ] Use the manifest registry as the single source of truth for route bases in search, sitemap, + navigation, and metadata generation. +- [ ] Split large client components and move static derivations back to server components. +- [x] Keep generated TypeScript artifacts tracked and enforce freshness in CI. +- [ ] Replace duplicated metadata constants and hand-maintained examples with generated values. + +## P2 — Distribution and Growth + +- [ ] Publish versioned JSON exports or a small read-only API. +- [ ] Offer embeddable comparison cards and freshness/verification badges. +- [ ] Generate weekly ecosystem change summaries from reviewed manifest diffs. +- [ ] Let contribution forms generate schema-valid pull requests instead of requiring contributors + to hand-author large JSON documents. +- [ ] Track decision-oriented product metrics: searches completed, comparisons started, outbound + official-link clicks, stale entries resolved, and contribution lead time. + +## Baseline Findings + +These findings were captured before the implementation batch and are retained as historical review +evidence; completed items are reflected in the checklists and implementation log above. + +- `src/app/[locale]/search/page.client.tsx` bypasses the manifest registry route mapping. +- `src/app/[locale]/models/compare/[models]/page.client.tsx` renders several internal keys and + English-only strings directly. +- Model comparison provider URLs are inferred from display names, which does not work for values + such as `Z.ai`, `KwaiKAT`, or non-provider vendors. +- `src/app/[locale]/models/compare/[models]/page.tsx` generates model pairs with O(n²) growth. +- `.github/workflows/deploy-staging.yml` listens to the removed `develop` branch. +- Stars updates are duplicated across two workflows and one workflow references an obsolete path. +- Scheduled URL validation combines `continue-on-error` with a generic `failure()` condition. +- README and contributor documentation reference old framework versions, commands, paths, and + manifest shapes. +- Sitemap coverage excludes several valuable routes and two detail categories. +- `METADATA_DEFAULTS.currentYear`, footer text, and model metadata contain hard-coded 2025 values. +- `GitHubStarHistory` generates random chart data when its external API fails. +- The test suite is strong on repository integrity but thin on user-visible behavior. + +## Implementation Log + +### 2026-07-18 + +- Captured the review baseline and agreed direction. +- Switched active development back to `main`. +- Restored staging, scheduled URL checks, Stars refresh ownership, CI aggregation, and generated + source drift checks. +- Corrected contributor-facing paths, commands, runtime versions, script docs, and issue templates. +- Fixed search routing and coverage, comparison localization/provider routing/pricing, sitemap + coverage, current-year handling, and randomized chart fallback data. +- Integrated the model comparison selector and moved pair pages to on-demand ISR instead of + prebuilding every localized pair. +- Added structured provenance/freshness fields and documented the Verified badge rollout without + inventing source history for legacy records. +- Preserved cached GitHub star values on transient API failures instead of overwriting them with + `null`. +- Added route/search regression tests and completed production verification: Biome, TypeScript, + 25 tests, manifest validation, generation, Next.js build, and OpenNext Cloudflare bundling. diff --git a/docs/SCHEMA-ALIGNMENT.md b/docs/SCHEMA-ALIGNMENT.md index 9f6429dc..e5b4e001 100644 --- a/docs/SCHEMA-ALIGNMENT.md +++ b/docs/SCHEMA-ALIGNMENT.md @@ -388,13 +388,13 @@ When updating schemas: ```bash # Validate all manifests against schemas -npm run test:validate-manifests +npm run test:validate # Check TypeScript types -npx tsc --noEmit +npm run type-check # Lint manifest JSON files -npm run lint:manifests +npm run biome:check ``` --- diff --git a/scripts/README.md b/scripts/README.md index a24e0886..a0e18a25 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,457 +1,54 @@ -# Scripts Documentation +# Scripts -This directory contains utility scripts for managing and validating the AI Coding Stack project. Scripts are organized into four categories: +Repository automation is written in TypeScript and executed with `tsx`. Prefer the npm commands below so local and CI behavior stay aligned. -- **`generate/`** - Generation scripts that create derived files -- **`refactor/`** - Refactoring scripts that reorganize or reformat data -- **`fetch/`** - Data fetching scripts that retrieve external data -- **`validate/`** - URL validation scripts that check website accessibility +## Commands -## Directory Structure +| Purpose | Command | Output or effect | +| --- | --- | --- | +| Generate all derived source | `npm run generate` | Rebuilds `src/lib/generated/` | +| Generate manifest indexes | `npm run generate:manifests` | Rebuilds typed manifest modules | +| Generate content metadata | `npm run generate:metadata` | Rebuilds article, docs, FAQ, and manifesto metadata | +| Sort manifest fields | `npm run refactor:sort-fields` | Reorders manifest JSON using schema order | +| Fetch GitHub stars | `npm run fetch:github-stars` | Updates `data/github-stars.json` | +| Validate manifests and data | `npm run test:validate` | Runs the validation test suite | +| Validate i18n structure | `npm run validate:i18n` | Checks locale alignment and translation shape | +| Validate i18n usage | `npm run validate:i18n-usage` | Checks translation keys referenced by source | +| Validate duplicate i18n values | `npm run validate:i18n-duplicates` | Reports duplicated translation content | +| Check representative URLs | `npm run validate:urls:quick` | Runs the default network URL check | +| Check every URL | `npm run validate:urls:all` | Checks all locales and slugs | -``` -scripts/ -├── generate/ -│ ├── index.mjs # Entry point for all generation scripts -│ ├── generate-manifest-indexes.mjs -│ └── generate-metadata.mjs -├── refactor/ -│ ├── index.mjs # Entry point for all refactoring scripts -│ ├── sort-manifest-fields.mjs -│ ├── sort-locales-fields.mjs -│ └── export-vendors.mjs -├── fetch/ -│ ├── index.mjs # Entry point for all fetch scripts -│ ├── fetch-github-stars.mjs -│ └── compare-models.mjs -├── validate/ -│ ├── visit-all-urls.mjs -│ ├── visit-urls-en-static-all-slugs.mjs -│ ├── visit-urls-en-static-one-slug.mjs -│ ├── visit-urls-all-locales-static-all-slugs.mjs -│ ├── visit-urls-all-locales-static-one-slug.mjs -│ └── lib/ # Shared validation utilities -├── _shared/ -│ └── runner.mjs # Shared script runner for entry points -└── temp/ # Temporary experimental scripts -``` - -## Usage - -### Running All Scripts in a Category - -Each category has an entry point script (`index.mjs`) that can run all scripts in that category: - -```bash -# Run all validation tests -npm run test:validate - -# Run all generation scripts -npm run generate - -# Run all refactoring scripts -npm run refactor - -# Run all fetch scripts -npm run fetch - -# Run all validation scripts (manual execution) -node scripts/validate/visit-all-urls.mjs -``` - -### Running Individual Scripts - -You can also run individual scripts by passing the script name to the entry point: - -```bash -# Generation scripts -npm run generate:manifests -npm run generate:metadata - -# Refactoring scripts -npm run refactor:sort-fields - -# Fetch scripts -npm run fetch:github-stars - -# Validation scripts (run directly with node) -node scripts/validate/visit-all-urls.mjs -node scripts/validate/visit-urls-en-static-all-slugs.mjs -node scripts/validate/visit-urls-all-locales-static-all-slugs.mjs -``` - -Or directly using Node: - -```bash -# Run generation/fetch scripts -node scripts/generate/index.mjs metadata -node scripts/fetch/index.mjs github-stars -node scripts/fetch/index.mjs compare-models - -# Run specific refactor scripts -node scripts/refactor/index.mjs sort-manifest-fields -node scripts/refactor/index.mjs sort-locales-fields -node scripts/refactor/index.mjs export-vendors - -# Run validation scripts -node scripts/validate/visit-all-urls.mjs -node scripts/validate/visit-urls-en-static-all-slugs.mjs -node scripts/validate/visit-urls-all-locales-static-all-slugs.mjs -``` - -## Validation (Test-based) - -Validation is implemented as **Vitest-based automated tests** under `tests/validate/`. - -### Run all validations (recommended) - -```bash -npm run test:validate -``` - -**What it checks:** -- JSON syntax validity -- Schema compliance for each manifest type -- Required fields presence -- Field format validation (URLs, enums, etc.) -- Filename matches the `id` field in the manifest - -**Manifest types validated:** -- `manifests/clis/*.json` - CLI tools -- `manifests/ides/*.json` - IDEs -- `manifests/extensions/*.json` - Editor extensions -- `manifests/providers/*.json` - API providers -- `manifests/models/*.json` - LLM models -- `manifests/vendors/*.json` - Vendor information -- `manifests/collections.json` - Collections data - -### Run GitHub stars consistency validation - -```bash -npm run test:validate -``` - -**What it checks:** -- All entries in `github-stars.json` have corresponding manifest files -- All manifest files are present in `github-stars.json` -- No orphaned entries in either direction - -**Categories validated:** -- `extensions` -- `clis` -- `ides` - -**Common issues:** -- Orphaned entries: Entries in `github-stars.json` without manifest files -- Missing entries: Manifest files without corresponding `github-stars.json` entries - -**How to fix:** -1. Remove orphaned entries from `data/github-stars.json` -2. Add missing entries to `data/github-stars.json` (set value to `null` if unknown) -3. Or remove unused manifest files if they are not needed - -### Run URL validation (networked; CI-oriented) - -```bash -npm run test:urls -``` - -**What it checks:** -- URL accessibility (HTTP status codes) -- Network connectivity -- URL format validity - -**Note:** This check makes HTTP requests and can be flaky; it is typically run in CI and configured as non-blocking. - -## Generation Scripts - -### generate-manifest-indexes.mjs - -Generates TypeScript index files from individual manifest files. - -```bash -npm run generate:manifests -``` - -**What it generates:** -- `src/lib/generated/ides.ts` - IDE manifest index -- `src/lib/generated/clis.ts` - CLI manifest index -- `src/lib/generated/models.ts` - Model manifest index -- `src/lib/generated/providers.ts` - Provider manifest index -- `src/lib/generated/extensions.ts` - Extension manifest index -- `src/lib/generated/vendors.ts` - Vendor manifest index -- `src/lib/generated/index.ts` - Main manifest index -- `src/lib/generated/github-stars.ts` - GitHub stars data - -### generate-metadata.mjs - -Generates TypeScript metadata files from MDX content and manifest data. - -```bash -npm run generate:metadata -``` - -**What it generates:** -- `src/lib/generated/metadata.ts` - Articles, docs, FAQ, and collections metadata -- `src/lib/generated/articles.ts` - Article components and metadata -- `src/lib/generated/docs.ts` - Doc components and metadata -- `src/lib/generated/manifesto.ts` - Manifesto component loader - -## Refactoring Scripts - -### sort-manifest-fields.mjs - -Sorts fields in manifest JSON files according to their schema definitions. - -```bash -npm run refactor:sort-fields -# or -node scripts/refactor/index.mjs sort-manifest-fields -``` - -**What it does:** -- Reorders fields in manifest files to match schema property order -- Ensures consistent field ordering across all manifests -- Handles nested objects and arrays - -### sort-locales-fields.mjs - -Sorts fields in locale translation JSON files alphabetically for consistency. - -```bash -node scripts/refactor/index.mjs sort-locales-fields -``` - -**What it does:** -- Sorts keys in translation files (`translations/*/pages/*.json`, `translations/*/components.json`, etc.) -- Ensures consistent ordering across all locale files -- Helps with maintainability and git diff readability - -### export-vendors.mjs - -Exports vendor information from manifest files to vendor manifests. - -```bash -node scripts/refactor/index.mjs export-vendors -``` - -**What it does:** -- Extracts vendor data from ide, cli, extension, model, and provider manifests -- Creates vendor files in `manifests/vendors/` if they don't already exist -- Merges vendor information from multiple manifest sources -- Skips existing vendor files (does not overwrite) +Networked commands can fail because an external service is unavailable. CI keeps URL validation separate from deterministic manifest, type, and build checks. -**Note:** This script only creates new vendor files. It will skip vendors that already have a manifest file. +## Layout -## Data Fetching Scripts - -### fetch-github-stars.mjs - -Fetches GitHub star counts for projects listed in manifests. - -```bash -npm run fetch:github-stars -# or -node scripts/fetch/index.mjs github-stars -``` - -**What it does:** -- Read `githubUrl` from manifest files -- Fetches star counts from GitHub API -- Updates `data/github-stars.json` with latest counts - -**Environment variables:** -- `GITHUB_TOKEN` - Optional GitHub token to avoid rate limits (recommended) - -**Note:** Without a GitHub token, you may hit rate limits (60 requests/hour). - -### compare-models.mjs - -Compares model manifest data with API reference data to identify mismatches. - -```bash -node scripts/fetch/index.mjs compare-models -# or -node scripts/fetch/compare-models.mjs -``` - -**What it does:** -- Reads model manifests from `manifests/models/` -- Compares with API reference data from `tmp/models-dev-api.json` -- Uses mapping from `manifests/mapping.json` to match vendors and models -- Reports matched models, mismatched fields, and unmatched models - -**Output:** -- Perfectly matched models (all fields match) -- Matched models with field mismatches -- Matched models with skipped fields (null in manifest) -- Unmatched models (not found in API) - -**Note:** This script requires `tmp/models-dev-api.json` to be present. It's used for data validation and synchronization. - -## Additional Tools - -### Benchmark Fetcher - -Fetching benchmark performance data is handled by a separate skill: - -```bash -node .claude/skills/benchmark-fetcher/scripts/fetch-benchmarks.mjs -``` - -**What it does:** -- Fetches benchmark scores from 6 leaderboard websites using Playwright MCP -- Supports SWE-bench, TerminalBench, SciCode, LiveCodeBench, MMMU, MMMU Pro, and WebDevArena -- Updates model manifests with latest benchmark scores - -For full documentation, see `.claude/skills/benchmark-fetcher/SKILL.md` - -## URL Validation Scripts - -The `validate/` directory contains scripts for checking website URL accessibility. These scripts visit URLs and verify they return successful HTTP responses. - -### visit-all-urls.mjs - -Visits all URLs on the website (all locales, all static pages, all slugs). - -```bash -node scripts/validate/visit-all-urls.mjs -``` - -**What it does:** -- Builds a comprehensive list of all website URLs -- Visits each URL with HTTP HEAD requests -- Reports success/failure status for each URL -- Supports retries and timeout handling - -**Environment variables:** -- `BASE_URL` - Base URL to test (default: `http://localhost:3000`) - -### visit-urls-en-static-all-slugs.mjs - -Visits URLs with specific configuration: English locale only, all static pages, all slugs per route type. - -```bash -node scripts/validate/visit-urls-en-static-all-slugs.mjs -``` - -**Configuration:** -- Locales: English only -- Static pages: All -- Dynamic routes: All slugs for each route type - -### visit-urls-en-static-one-slug.mjs - -Visits URLs with specific configuration: English locale only, all static pages, one slug per route type. - -```bash -node scripts/validate/visit-urls-en-static-one-slug.mjs -``` - -**Configuration:** -- Locales: English only -- Static pages: All -- Dynamic routes: One slug per route type (for faster testing) - -### visit-urls-all-locales-static-all-slugs.mjs - -Visits URLs with specific configuration: All locales, all static pages, all slugs per route type. - -```bash -node scripts/validate/visit-urls-all-locales-static-all-slugs.mjs -``` - -**Configuration:** -- Locales: All configured locales -- Static pages: All -- Dynamic routes: All slugs for each route type - -### visit-urls-all-locales-static-one-slug.mjs - -Visits URLs with specific configuration: All locales, all static pages, one slug per route type. - -```bash -node scripts/validate/visit-urls-all-locales-static-one-slug.mjs -``` - -**Configuration:** -- Locales: All configured locales -- Static pages: All -- Dynamic routes: One slug per route type (for faster testing) - -**Common features:** -- Concurrent requests (default: 5 concurrent) -- Request timeout (default: 10 seconds) -- Automatic retries (default: 2 retries) -- Summary statistics - -**Note:** These scripts make HTTP requests and require the website to be running. They are useful for pre-deployment validation and CI/CD pipelines. - -## Build Process - -The build process runs validation tests and generation scripts automatically: - -```bash -npm run build:next -``` - -This runs in order: -1. `test:validate` - Validate repository data integrity (schemas, translations, alignment, etc.) -3. `generate:manifests` - Generate manifest indexes -4. `generate:metadata` - Generate TypeScript metadata -5. Next.js build - -## Development Workflow - -During development, use: - -```bash -npm run dev +```text +scripts/ +├── _shared/ shared runner utilities +├── fetch/ external data refreshes and comparison helpers +├── generate/ derived TypeScript source generation +├── refactor/ mechanical manifest and locale maintenance +├── validate/ i18n and URL validation +└── temp/ ignored experimental workspace ``` -This will: -1. Generate manifest indexes -2. Generate metadata -3. Start Next.js development server - -## CI/CD Integration - -For CI/CD pipelines, you can run the validation test suite: +Each category with an `index.ts` auto-discovers sibling `.ts` scripts. A filename such as `generate-manifest-indexes.ts` is exposed as `manifest-indexes`: ```bash -# Run validations (recommended for CI) -npm run test:validate - -# Run all generation scripts -npm run generate - -# Run all refactoring scripts -npm run refactor - -# Run all fetch scripts (if needed) -npm run fetch - -# Run URL validation scripts (if needed) -node scripts/validate/visit-all-urls.mjs +npx tsx scripts/generate/index.ts manifest-indexes +npx tsx scripts/refactor/index.ts export-vendors +npx tsx scripts/fetch/index.ts github-stars ``` -Or run individual checks as needed: +## Generated files -```bash -npm run test:validate -npm run generate:manifests -npm run generate:metadata -npm run refactor:sort-fields -npm run refactor:sort-locales-fields -npm run fetch:github-stars -node scripts/fetch/index.mjs compare-models -node scripts/validate/visit-all-urls.mjs -``` +Generated modules under `src/lib/generated/` are committed. After changing manifests or content, run `npm run generate` and include the resulting deterministic diff. CI regenerates these files and fails when the committed output is stale. -## Manual Execution +`data/github-stars.json` is source data, not generated build output. Update it with `npm run fetch:github-stars`; the dedicated scheduled workflow also opens a pull request when values change. -To run tests manually without npm, you can use Vitest directly: +## Adding a script -```bash -vitest run tests/validate --reporter=verbose -``` +1. Add a TypeScript file to the relevant category. +2. Use the category prefix when it improves discovery, for example `generate-example.ts` becomes `example`. +3. Add an npm script only when the command is part of the normal contributor or CI workflow. +4. Document external writes, required environment variables, and failure behavior.